Skip to content

meta(theme): theming system status + polish roadmap — where to help land this #3184

Description

@ten9876

Claude here. The theming system reached its first end-to-end milestone with #3176 merging earlier today. This issue is a status snapshot + a guide for contributors who want to help take it from "rough first pass" to "ship-quality."

What just shipped (in #3176)

The theme system pivoted from a flat global namespace to a container-scoped design-token architecture. Concretely:

  • Inline Theme Editor (no more spawned sub-dialogs). Single dialog with a custom color picker, gradient editor, font row, and a 2×10 grid of recent colors that persists per-theme.
  • Scope tree + v2 JSON schema. Themes can now nest scopes (applet/tx, dialog/themeEditor, spectrum/vfo). Tokens cascade — child scopes inherit from parents until they override. Stored on disk as {primitives: …, scopes: {root: {tokens, scopes: …}}}.
  • Primitives palette. Common colors (color.blue.500, color.gray.900, etc.) are extracted into a flat palette; semantic tokens reference them via {color.blue.500} aliases. Edit a primitive once → every semantic that points at it shifts.
  • Compound font tokens. font.family.ui / freq / segment7 etc. now store {family, size, color} together as a single typographic role. Editing the freq compound's size resizes the freq label tree-wide.
  • Container declarations on ~63 widgets (every applet, every dialog, the four titlebars, the main status bar, the spectrum, VfoWidget). Inspect mode auto-selects the picked widget's scope.
  • Built-in protection. Default Dark / Default Light can't be edited in place — first edit pops a Save As to fork into a user copy.
  • Auto-persist. Every confirmed edit hits disk immediately. No more "edits gone after restart" surprises.
  • Columnar inheritance view in the editor. Selecting a leaf scope shows Object | root | applet | tx | Value columns with "inherited" in italics where the scope falls through.

Test plan is in #3179 — that's the test-debt invoice.

Architecture at a glance

ThemeManager
├── m_primitives:   flat palette  (color.blue.500 = "#00b4d8")
├── m_rootScope:    tree of ThemeScopes
│   ├── tokens:     semantic overrides at this scope (alias-resolvable)
│   └── children:   nested scopes (applet, spectrum, dialog, …)
├── m_scopeByPath:  fast index for editor's scope picker
└── m_declaredContainers:  widget-registered paths, replayed after theme load

Widgets
├── theme::setContainer(this, "applet/tx")   ← per-widget declaration
├── tm.color(this, "color.accent")           ← walks Qt-parent chain → scope chain → root
└── applyStyleSheet(this, "{{color.accent}}") ← scope-aware QSS resolve

JSON v1 themes auto-migrate on load (flat tokens land in root scope, primitives stay empty). v2 is the only on-disk shape going forward.

Where it's rough — focus areas

Tier 1: tests + correctness (highest leverage)

  • test(theme): add coverage for v2 scope tree + primitives + compound fonts #3179 — write the smoke tests we should have had during the refactor. The catalog there is concrete; pick a section and own it.
  • Destructor SEGV root cause. We deployed a QTimer::singleShot(0) deferral in onContainerChanged to dodge a QImageData::~QImageData crash during scope changes inside the inspector's signal cascade. Don't actually know why the QPixmap refcount got bad. If anyone has a debug-symbols-on Qt6 build, this is a great hunt.

Tier 2: widget migration (mechanical but bulky)

  • Reader sites that still hardcode color/family. The compound font ships, but most paint code reads color.text.primary separately from font.family.ui — the compound's color field is only consumed at sites we explicitly templated. Going through paint code and routing through fontToken(widget, role) for compound roles would actually exercise the field.
  • Container declarations on remaining surfaces. Strip*Panel widgets and popups intentionally inherit from their host applets via Qt parent chain, but if a popup spawns to a top-level window (no parent in scope chain), it'd miss the inheritance. Audit each *Popup.cpp / floating window for the case.
  • Inspector regions (declareWidgetRegions). SpectrumWidget declares one broad token list — clicking the panadapter trace surfaces all 26 of its tokens. Splitting into trace / waterfall / slice-flag regions is captured in feat(theme): Phase 5 PR 2 — Theme Editor inspector mode + 505-site reverse-map sweep #3144's TODO list; pick one and narrow it.

Tier 3: editor UX polish

  • Primitive editing surface. Primitives are written to disk and resolve through aliases, but the editor has no UI to edit them. Designers can edit semantic tokens; they can't edit the underlying palette. A "Primitives" view (similar layout to the token list, but for color.blue.* / gray.* / red.* etc.) would unlock the design-token tier.
  • "Reset to default" at non-root scopes. Currently always pulls from the bundled factory. At a nested scope it should mean "clear my override here" (which is what right-click → Clear Override already does). The Reset button should mirror that semantic.
  • Filter persistence across scope changes. When you change the scope dropdown, the inspector subset filter is lost. Easy to fix in refreshTokenList.
  • Theme-delete-time cleanup. Per-theme recent-color buckets in AppSettings orphan when a theme is deleted. Small leak but easy hygiene.

Tier 4: design system maturity

  • Tailwind-style scale audit. We extracted blue/red/amber/green/gray 50–950 from the existing palette. The numbering is approximate — would benefit from a designer's eye to align with established design-system scales (e.g. ensure 500 is the "base" rather than ad-hoc).
  • Token namespace conventions. Currently mixed: some tokens are role-named (color.text.primary), some surface-named (color.spectrum.trace), some channel-named (color.meter.crst). Worth a discussion on whether to standardize.
  • Documentation. docs/theming/ (if it exists) should grow to cover the v2 schema, the scope cascade rules, the alias syntax, the compound font shape, and the container-declaration pattern.

Related issues to consider closing or updating

How to contribute

  1. Pick one bullet above that matches your skill area.
  2. If it's not already an issue, open one referencing this overview.
  3. Branch from main, base the work on the canonical convention pattern from existing sites (the 63 container declarations are uniform — copy that shape).
  4. Smoke-test against both Default Dark and Default Light before opening a PR — light theme catches assumptions that only hold in dark.

Questions, design pushback, or scope arguments — drop them in this thread. This is where the next phase gets shaped.

73, Jeremy KK7GWY & Claude (AI dev partner)

Metadata

Metadata

Labels

GUIUser interfaceenhancementImprovement to existing featuremaintainer-reviewRequires maintainer review before any action is takenpriority: lowLow priorityrfc

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions