LearnNewsExamplesServices
Frontmatter
id18145
titleTheme value files declare variables only: 26 of them carry CSS selectors, 62 blocks
stateClosed
labels
enhancementdesignaiarchitecture
assigneesneo-opus-grace
createdAtSep 2, 2026, 11:36 PM
updatedAtSep 15, 2026, 10:35 AM
githubUrlhttps://github.com/neomjs/neo/issues/18145
authorneo-opus-grace
commentsCount3
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtSep 15, 2026, 10:35 AM

Theme value files declare variables only: 26 of them carry CSS selectors, 62 blocks

Closed v13.2.0/archive-v13-2-0-chunk-18 enhancementdesignaiarchitecture
neo-opus-grace
neo-opus-grace commented on Sep 2, 2026, 11:36 PM

Context

Operator rule, stated 2026-09-02 while reviewing the theme contract behind #18141:

"For existing themes, there should NEVER be overrides inside THEME VAR files that add css selectors. Such cases must get added to scss/src, and add new vars as needed."

#18141 is the first defect this rule would have prevented: tab/Container.scss exists in all four themes, carries selector-scoped blocks, and its flat value tokens drifted out of the classic pair unnoticed. A theme file that is only a value list makes an omission obvious; a theme file that also carries selectors hides one among them.

The Problem

A theme value file answers "what is this theme's value for X". A selector answers "which elements get X" — a structural question the theme has no business deciding, because every theme then has to re-answer it identically and any one of them can drift.

Measured across resources/scss/theme-*:

theme files carrying nested selector blocks 26
nested selector blocks total 62

Largest concentrations:

blocks file
10 theme-neo-light/Global.scss, theme-neo-dark/Global.scss
3 {theme-neo-light,theme-neo-dark}/tab/header/Button.scss
3 {theme-neo-light,theme-neo-dark}/dialog/Base.scss
3 {theme-neo-light,theme-neo-dark}/apps/portal/news/pulls/Component.scss
2 {theme-light,theme-dark,theme-neo-light,theme-neo-dark}/tab/Container.scss
2 {theme-neo-light,theme-neo-dark}/button/Base.scss
1 each Global.scss (light/dark/cyberpunk), grid/Container.scss (×4), grid/footer/Toolbar.scss (×2), apps/portal/home/FooterContainer.scss (×2)

The tab/Container.scss instance is the worked example: all four themes repeat the same four-selector list (.neo-tab-container-inline, its detached-drag-proxy self-scoped twin, and the -standalone pair) purely to scope --tab-button-* values. The selector list is identical in every theme; only the values differ. That is a structural rule with a value hole in it, written out four times.

The Architectural Reality

The correct split already exists in the tree and works: resources/scss/src/tab/Container.scss owns .neo-tab-container-inline > .neo-tab-header-toolbar and reads --tab-header-inline-*; the themes value those tokens flat. Everything in the table above is the same shape not yet applied.

Do not fix this by deleting the selectors. Each one exists because a value is variant-scoped, so each move needs a new token: the structural sheet grows the selector and reads var(--new-token), and every theme declares --new-token flat. Per #18141, every theme must declare it — a token declared by some themes and not others is inherited across a nested theme boundary rather than reset.

The Fix

One batch at a time, in the shape #18107 uses for its own theme sweep. Suggested ordering, cheapest and most defect-adjacent first:

  1. tab/Container.scss (4 files, 8 blocks) — the #18141 neighbour, and the pattern demonstration.
  2. grid/Container.scss + grid/footer/Toolbar.scss (6 files, 6 blocks).
  3. button/Base.scss, tab/header/Button.scss, dialog/Base.scss (6 files, 16 blocks).
  4. apps/portal/** (4 files, 8 blocks).
  5. Global.scss (5 files, 23 blocks) — largest and least mechanical. Graduated to #18232 (2026-09-03), as this line anticipated: the measurement there found only 5 divergent declarations across the two neo files, and a weight drop that repaints realworld2 headings.

Each batch: move the selector into resources/scss/src/, introduce the tokens it needs, declare them in all themes, rebuild and diff the emitted CSS per theme to prove the paint is unchanged.

Acceptance Criteria

  • AC-1 — a named batch's theme files contain variable declarations only; the selectors that scoped them live in resources/scss/src/.
  • AC-2 — every token introduced by the move is declared in all themes that style the component, so no nested theme boundary inherits a value it should reset (#18141).
  • AC-3 — emitted CSS is compared per theme before and after; any intended difference is named in the PR body and any unintended one is zero.
  • AC-4 — a guard, once at least one batch has landed: theme value files declare no selectors. Baseline the remaining offenders so it can be introduced before the sweep finishes rather than after.

Out of Scope

  • The --tab-header-inline-* value omission itself — that is #18141.
  • Components with no classic-theme styling at all (markdown component, portal app). The operator's read: a real gap, a different one — eventually every theme should support every widget. Its own ticket.
  • :where() weight on theme value sheets — #18107 owns that surface and the two sweeps should not be run as one.

Avoided Traps

  • Deleting a selector without replacing its scope. The values are variant-scoped for a reason; a straight lift into the flat block applies them to every tab container.
  • Declaring the new token in only the themes that need a non-default value. That is exactly #18141, one directory over.
  • Doing all 62 in one PR. The operator's direction was explicit: this does not need to be one-shot, and a 26-file diff cannot be reviewed against emitted-CSS parity.

Related

  • #18141 — the first defect this rule prevents; the tab/Container.scss value omission.
  • #18095 / PR #18097 — introduced the omission.
  • #18107 — the :where() weight sweep, adjacent surface, deliberately separate.

Live latest-open sweep: checked the latest 20 open issues at 2026-09-02T21:26Z plus the newly filed #18141; no equivalent — #18107 is selector weight, this is selector presence. A2A claim sweep over the last 12 messages, all read-states: no competing claim. Memory Core rationale sweep surfaced #18095/#18097 and Vega's 2026-08-31 specificity findings, both cited above. Own-assignment sweep: none on themes.

Origin Session ID: fb58e855-74b2-4367-9f83-5877f151f024

Retrieval Hint: theme value files must declare variables only no css selectors move to scss/src new tokens 26 files 62 nested blocks batch sweep

tobiu referenced in commit 671cd74 - "refactor(tab): the inline and standalone selectors leave the theme value files (#18145) (#18167) on Sep 3, 2026, 10:41 AM
tobiu referenced in commit 862edb3 - "refactor(grid): the grid selectors leave the theme value files (#18145) (#18168) on Sep 3, 2026, 10:46 AM
tobiu referenced in commit ecaac36 - "refactor(button): the button custom-override block leaves the theme value files (#18145) (#18170) on Sep 3, 2026, 11:44 AM
tobiu referenced in commit 78ed259 - "refactor(portal): the portal selectors leave the theme value files (#18145) (#18172) on Sep 3, 2026, 11:45 AM
tobiu referenced in commit 9ec2697 - "build(scss): a theme value file declares variables, never selectors (#18145) (#18196) on Sep 3, 2026, 4:16 PM
tobiu referenced in commit beb52b3 - "docs(themes): three of the sixteen are an importance question, not a specificity one (#18145) on Sep 4, 2026, 11:47 AM
tobiu referenced in commit 324d859 - "docs(themes): the exemption reason is 13 states, and it fits in one sentence (#18145) on Sep 4, 2026, 12:00 PM
tobiu referenced in commit 27e19ed - "docs(themes): the dialog exemption survives a real attempt, and the recorded reason was wrong about why (#18145) (#18264) on Sep 4, 2026, 12:32 PM
tobiu referenced in commit a57ffbe - "refactor(themes): global element typography is structure, so it lives in src (#18232) (#18454) on Sep 8, 2026, 12:02 PM
tobiu referenced in commit 8672557 - "docs(scss): an exempt theme file is settled by measurement, not waiting on a decision (#18145) (#18725) on Sep 15, 2026, 10:35 AM
tobiu closed this issue on Sep 15, 2026, 10:35 AM