You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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."
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.
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.
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.
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:
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: …}}}.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.font.family.ui/freq/segment7etc. now store{family, size, color}together as a single typographic role. Editing the freq compound's size resizes the freq label tree-wide.Object | root | applet | tx | Valuecolumns with "inherited" in italics where the scope falls through.Test plan is in #3179 — that's the test-debt invoice.
Architecture at a glance
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)
QTimer::singleShot(0)deferral inonContainerChangedto dodge aQImageData::~QImageDatacrash 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)
color.text.primaryseparately fromfont.family.ui— the compound'scolorfield is only consumed at sites we explicitly templated. Going through paint code and routing throughfontToken(widget, role)for compound roles would actually exercise the field.*Popup.cpp/ floating window for the case.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
color.blue.*/gray.*/red.*etc.) would unlock the design-token tier.refreshTokenList.AppSettingsorphan when a theme is deleted. Small leak but easy hygiene.Tier 4: design system maturity
color.text.primary), some surface-named (color.spectrum.trace), some channel-named (color.meter.crst). Worth a discussion on whether to standardize.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
color.background.successtoken cleanup) — good first-issue still valid.How to contribute
main, base the work on the canonical convention pattern from existing sites (the 63 container declarations are uniform — copy that shape).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)