Index
# `src/sass` — Implementation Plan **Input:** [SASS_ARCHITECTURE_REVIEW.md](SASS_ARCHITECTURE_REVIEW.md) (Phase A) **Scope:** `src/sass/**`, `Theme.php`, `DartSass.php`, `JHSCompiler.php`, consumer `.jhs` files, docs **Baseline version:** `mmo.client` 3.0.13 **Status:** plan only — no code changed. Every fix below was validated by **simulating it on a copy of `src/sass` and compiling with the vendored `vendor/dart/sass`**. Claims marked **[verified]** were produced that way; the rest are specification. --- ## Part 0 — Corrections to Phase A Mandatory before planning: four Phase A statements were wrong or incomplete. ### C1 — Dark mode is a shipped, user-facing feature (raises FIX-001 from latent to visible) Phase A asked *"is theme switching expected to be the application's job?"* and said `.theme-dark` was unreachable. **Wrong.** I grepped `src/js`; the logic lives in a `.jhs` file: ```text mmo-app.jhs:53 ← moon icon, "Thème sombre" mmo-app.jhs:914-923 setThemeMode() → document.body.classList.add('theme-' + scheme) → localStorage.setItem('themeMode', scheme) → dispatchEvent(window, 'theme', scheme) ``` So dark mode has a toggle in the app shell's user card, persists across reloads, and emits a `theme` event. **Every user who has ever flipped that switch has been looking at the broken palette.** FIX-001 stops being a latent defect and becomes a visible bug fix — and correspondingly, it *will* change what existing users see. ### C2 — Finding 2's implied fix was wrong; pass-through is load-bearing Phase A noted `size()` returns unrecognised names verbatim while every other guard `@error`s, implying it should `@error` too. **That would break the framework.** Actual first-argument census across `src/sass` + `src/customElements`: | value | count | must keep working | |---|---|---| | `'s' 'xs' 'm' 'l' 'xl' 'xxl'` | 499 | scale words | | `0` | 67 | ✅ number | | `50%` | 10 | ✅ number | | `auto` | 7 | ✅ keyword | | `2px`, `-8px`, `100px`, `$gap`, `$nav-size` | 5 | ✅ number/variable | | `'xxs'` | 1 | ❌ **bug** | | `'0'` (quoted) | 1 | ❌ **bug** — new, not in Phase A | The fix is a *type-aware* guard, not an unconditional error. And there is a second live instance: [mmo-notification.jhs:132](src/customElements/elements/mmo-notification.jhs:132) emits `padding-bottom: "0"`. ### C3 — Finding 4 overstated; the intended model is in-tree co-evolution Phase A said applications have "no supported extension point at all". Two corrections: - `Theme::$stylesheet` **is** configurable ([Theme.php:45](src/Theme.php:45)), so an app *can* own the entry point and `@use 'styles'` from it. **[verified]** — a test app entry compiled to 787 rules vs the framework's 786, framework styles intact. - More importantly, I missed the framework's own stated position ([guides/frontEnd/dependencies/sass.md](demo/www/contents/pages/guides/frontEnd/dependencies/sass.md)): > The styles in `MMO/libs/client/src/sass` are open to change and can be modified if necessary. > Any changes must be validated by the entire team before implementation. and [addingTheme.md](demo/www/contents/pages/guides/frontEnd/themes/addingTheme.md) instructs authors to add theme files **inside** `/libs/client/src/sass/themes`. So this is an in-house framework where app and framework evolve together, and "fork the tree" is the *documented* workflow, not a workaround. That lowers the priority of a formal extension point — but see D2 below, because the same docs also promise an app-side override path that does not work. ### C4 — There are five documentation surfaces, not two Phase A audited `docs/Styles.md` and `docs/mcp/Styling.md`. Also present: ```text demo/www/contents/pages/sass/*.phtml 11 pages (border, color, flex, font, margin, padding, shadow, typography, …) demo/www/contents/pages/guides/frontEnd/themes/addingTheme.md demo/www/contents/pages/guides/frontEnd/sass/z-index.phtml demo/www/contents/pages/guides/frontEnd/dependencies/sass.md ``` `addingTheme.md` drifts too — its template lists a `'link'` token that does not exist (same phantom as `docs/Styles.md`). Documentation work is therefore larger than Phase A scoped it. --- ## Part 1 — Master defect register | ID | Problem | Evidence | Impact | Root cause | Risk of fix | Deps | Migration | |---|---|---|---|---|---|---|---| | **D1** | Dark theme resolves palette refs against the **default** palette; 16 of 27 tokens wrong | `_theme.scss:524-529` `include-colors()` drops `$theme` | Visible: dark mode ships with wrong colors (C1) | `$theme` not forwarded to `set-color()` | Low — light theme byte-identical **[verified]** | D0 | Visual change for dark users | | **D2** | App config overrides are **silently discarded** | `config.merge`/`config.theme` do `deep-merge($-config, $new)` → framework defaults win | Apps cannot retheme; failure is silent, compiles clean | Merge direction; defaults registered as overrides not defaults | Low — output identical when no app config **[verified]** | D0 | None (nothing works today) | | **D3** | Invalid size names emit invalid CSS silently | `_theme.scss:170-171`; `_basic.scss:139` → `font-size:"xxs"`; `mmo-notification.jhs:132` → `padding-bottom:"0"` | `
` has no font size; one element has no padding | Pass-through has no type discrimination (C2) | Low–Med — must not reject numbers/`auto` | D0 | None | | **D4** | Dark tokens not contrast-checked; after D1, `muted` ≈ `text` | measured 13.09 vs 13.24 on `app-bg` **[verified]** | "Muted" text is indistinguishable from body text in dark | Token values never validated against a background | Low | D1 | Visual change | | **D5** | `dark` declares neither `background` nor `icon` | compiled `.theme-dark` **[verified]** | Works only by `:root` cascade accident | `$all`/`$merge-map` has nothing to merge — no root `colors` map | Low | D1 | None | | **D6** | `docs/mcp/Styling.md` token values wrong (font scale, base sizes) | Phase A §6 F6 | Agents generate off-scale CSS | Doc written from memory, never re-derived | None | D0 | None | | **D7** | `docs/Styles.md` describes a framework that does not exist (palettes, `danger`, 4 phantom themes, wrong signature, wrong `tl`) | Phase A §6 F6 | Onboarding actively misleads | Doc predates implementation | None | — | None | | **D8** | `addingTheme.md` promises default-theme inheritance the config layer does not implement; `sass.md` promises an app override path that is never compiled | C3, C4 | Developers follow instructions that silently do nothing | Docs describe intent, not implementation | None | D2 | None | | **D9** | Sass color math escapes the theming layer | `classes/_inputs.scss:3-6,27,596` | Focus rings frozen to `default` theme | `rgba()` cannot take `var()` | Med — browser support | D0 | None | | **D10** | `$-in-shadow-dom` / `in-shadow-dom()` never read | `_theme.scss:29-32`; 4 occurrences, all definitional | Dead abstraction; misleads readers | Unknown — needs author | Low | Q2 | None | | **D11** | Dead code: `$CSS_COLORS`; 27 of 77 placeholders never extended; stale `mmo.client.demo.css` from a deleted source | Phase A §6 F7 | Maintenance noise; two parallel color systems | Drift | Low | — | None | | **D12** | `assets['scss']` reads as compile targets, is a staleness list | `Theme.php:41`; `../doc/src/bootstrap.php:121` | Cost a sibling package real debugging time | Misleading key name | Low | — | Rename = breaking | | **D13** | Two unrelated asset-URL mechanisms; `{ASSETS_URL}` silently inert in `styles.scss` | `DartSass.php:61` (only in `compileText`) vs `theme.printf` | Confusing; a `{ASSETS_URL}` in a `.scss` file fails silently | Two features added at different times | Low | — | None | | **D14** | `const.xss` / `font-xss` unreachable — not in `size()`'s accepted set | `_default_config.scss:9,16` vs `_theme.scss:159` | Dead config, invites the `'xxs'` typo | Scale vocabulary drift | Low | D3 | None | Not included: per-shadow-root CSS duplication. See Part 10 — it is inherent, measured, and small. --- ## Part 2 — Classification and challenge pass Type: **A** bug fix · **B** hardening · **C** refactor · **D** API · **E** new capability · **F** architectural. | ID | Type | Significant? | Root cause? | Simpler option? | Verdict | |---|---|---|---|---|---| | D1 | **A** | Yes — user-visible (C1) | Yes | Editing dark's values would mask it | **Fix now** | | D2 | **A** | Conditional (see below) | Yes | — | **Fix now**, cheap either way | | D3 | **A** | Yes — 2 live bugs | Yes | Patch 2 call sites only | **Fix guard + sites** | | D4 | **B** | Yes | Yes | — | **Fix after D1** | | D5 | **A** | Low but trivial | Yes | Rely on cascade | **Fix, make explicit** | | D6 | **A** | Yes — agent-facing | Yes | — | **Fix now** | | D7 | **A** | Yes | Yes | Delete the file | **Rewrite** | | D8 | **A** | Yes | Yes | — | **Fix with D2** | | D9 | **B** | Low — 2 sites | Yes | — | **Postpone (P2)** | | D10 | **C** | Low | Unknown | — | **Blocked on Q2** | | D11 | **C** | Low | Yes | — | **P3** | | D12 | **D** | Low, already cost time | Yes | Document instead of rename | **Document (P3)** | | D13 | **C** | Low | Yes | Document | **P3** | | D14 | **C** | Low | Yes | Fold into D3 | **Fold into D3** | ### Challenge notes worth recording **D2 — is it worth fixing given C3?** If the model is genuinely "fork the tree", app-level config override is unnecessary. Two reasons to fix anyway: (a) the current behaviour is not "unsupported", it is a **silent trap** — `@use 'inc/config' with (...)` compiles cleanly and does nothing **[verified]**; (b) `sass.md` already tells developers to create app-side override files. The fix is ~8 lines and provably output-neutral, so cost is near zero. Fixing it converts a trap into a feature. **D9 — postponed deliberately.** `color-mix()` needs Safari 16.2+/Chrome 111+, and relative color syntax is newer still. Two call sites, both focus rings, both currently correct in light mode. Revisit once a browser baseline is stated. Postponing costs nothing. **D12 — rename rejected.** Renaming `assets['scss']` is a config-breaking change for aesthetics. A doc-comment at [Theme.php:41](src/Theme.php:41) solves the actual problem (a reader misreading it) at zero risk. --- ## Part 3 — Priorities ```text P0 MUST FIX D0 (harness) D1 D2 D3 P1 FIX SOON D4 D5 D6 D7 D8 P2 PLANNED D9 + dark-mode hardening (prefers-color-scheme) P3 OPTIONAL D10 D11 D12 D13 ``` P0 rationale: D1 is a visible defect in a shipped feature; D3 produces invalid CSS today; D2 is a silent trap that is nearly free to remove. D0 precedes all three because all three change compiled output and nothing currently checks compiled output — which is exactly why D1 survived. --- ## Part 4 — Dependency graph and order ```text D0 (regression harness) │ ├── D1 (forward $theme) ──┬── D4 (dark contrast pass) │ └── D5 (declare background/icon) │ ├── D2 (merge direction) ──── D8 (fix the docs that describe it) │ ├── D3 (size guard) ───────── D14 (retire xss / align scale) │ └── D9 (color-mix) [P2 — independent, gated on browser baseline] D6, D7 (documentation) ── independent, parallelisable, but land AFTER D1-D3 so they document the corrected behaviour D10, D11, D12, D13 ── independent cleanups, any time ``` **Parallelisable:** {D1, D2, D3} touch disjoint code (`include-colors`, `config` mixins, `size`). **Must not precede:** D4 before D1 (you would be tuning values the compiler never reads); D6/D7 before D1–D3 (you would document behaviour about to change). **Blocked:** D10 on author Q2. --- ## Part 5 — Baseline measurements Collected on this machine before any change. Timings are hardware-dependent — treat the *ratios* and the byte counts as the contract, not the milliseconds. | Metric | Method | Baseline | Target | Regression threshold | |---|---|---|---|---| | Global sheet compile | `sass --load-path=src/sass src/sass/styles.scss`, 3 runs | **~82 ms** | unchanged | >120 ms | | Per-element compile | same, on a 2-line `@use "shadow_theme"` file, 10 runs | **11.2 ms** | unchanged | >20 ms | | Marginal theme-parse cost | per-element minus a no-theme file (8.2 ms) | **~3 ms** | unchanged | >6 ms | | Full jhs pass (40 elements) | 40 × per-element | **~0.45 s** | unchanged | >1 s | | `mmo.client.css` size | `wc -c`, expanded | **110 195 B** | ±2 % | >115 000 B | | `mmo.client.jhs.js` size | `wc -c` built artifact | **849 317 B** | ±2 % | >880 000 B | | Distinct custom properties | `grep -o '--mmo-[a-z-]*:' \| sort -u \| wc -l` | **35** | 37 after D5 | any unexplained change | | `var()` references | `grep -o 'var(--mmo-' \| wc -l` | **978** | ≥978 | a *drop* means a token was inlined | | Invalid declarations | grep compiled CSS for `: "` | **1** (`font-size:"xxs"`) | **0** | >0 | | `:root` token block | byte-compare | fixed snapshot | identical through D1–D3 | any diff | | Test coverage of sass | — | **none** | D0 harness green | — | **Cannot be measured objectively here:** rendering performance and memory. Neither is plausibly affected by any P0/P1 fix (no selector or property counts change materially), so I am not proposing benchmarks for them. If D9 later replaces static colors with `color-mix()`, paint cost should be measured then. --- ## Part 6 — FIX-000 — Sass regression harness *(prerequisite)* ### Problem Nothing validates compiled CSS. D1 survived indefinitely because no test reads the token blocks, and D3 emits invalid CSS that no test would catch. ### Proposed change A minimal PHP CLI harness — not a framework. The repo already has `DartSass::Shared()->compileText()` and a PHP bootstrap, so this adds no dependency. Existing `tests/` is Closure-based browser JS and is the wrong tool for compiler output. ```text tests/sass/run.php compile fixtures, assert, print pass/fail tests/sass/fixtures/*.scss small inputs tests/sass/expected/*.css golden snapshots for token blocks only ``` Three assertion kinds, which is enough to cover every P0 fix: 1. **Token snapshot** — compile `styles.scss`, extract `:root{…}` and `.theme-*{…}`, compare to golden. 2. **Contains / not-contains** — e.g. no `: "` anywhere in output (catches D3 class permanently). 3. **Compile-must-fail** — assert `theme.padding('xxs')` raises, once D3 lands. ### Complexity **S** — Implementation S, Tests (self), Docs XS. ### Acceptance - [ ] `php tests/sass/run.php` exits non-zero on any failure - [ ] Golden snapshot of current `:root` committed **before** D1 - [ ] Assertion 2 passes only after D3 - [ ] Runs in CI or is documented in `docs/Workflow.md` --- ## Part 7 — P0 / P1 fix specifications ### FIX-001 — Forward `$theme` in `include-colors()` · Type A · P0 · **XS** **Problem.** `.theme-dark` resolves every palette reference against the `default` palette. 16 of 27 tokens are wrong, including `--mmo-color-info: #00498A` (near-black navy used as an informational accent *on a dark background*). **Evidence.** [`_theme.scss:524-529`](src/sass/_theme.scss:524): ```scss @mixin include-colors($name, $all: false) { @each $key,$value in config.get('colors', null, $name, $all) { @include set-color($key, $value); // $name is not passed } } ``` `set-color` defaults `$theme: null`, which [`_theme.scss:45`](src/sass/_theme.scss:45) resolves as *the currently active theme*. `theme.include()` never calls `config.set('theme', …)` — only `theme.set()` does — so during `.theme-dark` emission the active theme is still `default`. **Root cause.** A dropped parameter. Consequence: `themes/_dark.scss`'s entire `palettes` block (13 lines) is **dead** — it is never read. *This also settles Phase A Q1:* the reversal cannot have been written to compensate for this bug, because reversing data nobody reads has no effect. The reversal is deliberate intent. **Proposed change.** Pass `$name` as the third argument. One token. **Files.** `src/sass/_theme.scss` · `tests/sass/expected/theme-dark.css` · `docs/mcp/Styling.md` **API impact.** None — `include-colors` is internal, called only by `theme.include()`. **Backwards compatibility.** *Mostly compatible.* **[verified]** the `:root` block is **byte-identical** before and after; only `.theme-dark` changes. Any app that applies `.theme-dark` (i.e. any app using `mmo-app`'s toggle) sees different colors — better ones, but different. **Verified effect** (16 changed tokens; the consequential ones): | token | before | after | WCAG on `app-bg` | |---|---|---|---| | `info` | `#00498A` | `#8CC9FF` | 2.01 → **8.10** | | `title` | `#778B99` | `#E8EFF3` | 5.15 → **12.29** | | `secondary` | `#FFFFFF` | `#AAB3BA` | 18.20 → 6.71 | | `light` | `#000000` | `#2C343F` | — | | `dark-grey` | `#F0F6F9` | `#5A6D7A` | — | **Tests (must fail before, pass after).** 1. Golden `.theme-dark` snapshot asserting `--mmo-color-info: #8CC9FF`. 2. Regression: `:root` snapshot must remain byte-identical. 3. A partial theme (only `primary`) emits exactly one color var. **Validation.** Load the demo, toggle "Thème sombre", confirm informational surfaces are legible. **Risks.** Any hand-tuned CSS written to compensate for the wrong dark colors will now be wrong in the other direction. Grep for `.theme-dark` overrides in app code before releasing. **Acceptance.** - [ ] `:root` byte-identical · [ ] `.theme-dark` matches new golden · [ ] demo toggle reviewed visually - [ ] FIX-004 scheduled (fix alone leaves `muted` broken) · [ ] `docs/mcp/Styling.md` note added --- ### FIX-002 — Register framework defaults as defaults · Type A · P0 · **S** **Problem.** An application cannot override any token. `@use 'inc/config' with ($-config: (…))` compiles cleanly and is silently discarded. **[verified]** an app setting `padding: 99px` gets `1rem`. **Evidence.** [`inc/_config.scss`](src/sass/inc/_config.scss:97) — both registration mixins merge *new over old*: ```scss @mixin merge($config) { $-config: map.deep-merge($-config, $config) !global; } @mixin theme($name, $config) { $-config: map.deep-merge($-config, ('themes': ($name: $config))) !global; } ``` `_default_config.scss` and `themes/*.scss` call these, so framework defaults overwrite whatever the app configured. Because Sass requires `@use` before any rule, the app has no other injection point. **Root cause.** Defaults are registered through an *override* API. Two sites, not one — fixing only `merge` leaves colors broken **[verified]**. **Proposed change.** Add two mixins expressing "fill gaps only", and switch the three framework registration sites to them. `merge`/`theme` keep their current semantics untouched. ```scss @mixin defaults($config) { $-config: map.deep-merge($config, $-config) !global; } @mixin theme-defaults($name, $config) { $-config: map.deep-merge(('themes': ($name: $config)), $-config) !global; } ``` **Files.** `inc/_config.scss` · `_default_config.scss` · `themes/_default.scss` · `themes/_dark.scss` · `tests/sass/` · `addingTheme.md` · `sass.md` **API impact.** Additive. `config.merge()` and `config.theme()` are unchanged, so the documented theme-authoring workflow in `addingTheme.md` still works verbatim. **Backwards compatibility.** *Fully compatible.* When `$-config` is empty — always true inside the framework, since these are the first things evaluated — `deep-merge(A, {})` ≡ `deep-merge({}, A)`. **[verified]** compiled output identical except the two tokens a test app deliberately overrode. No `.jhs` file calls `config.merge`/`config.theme` (only `mmo-workflow.jhs` uses an unrelated JS `config.set`). **Tests.** (1) no-app-config → output byte-identical to baseline; (2) app overriding `sizes/padding` → `--mmo-size-padding: 99px`; (3) app overriding `themes/default/colors/primary` → resolves to the app's palette color; (4) `config.merge()` still overrides, protecting theme authors. **Risks.** Low. The only way to regress is if some file depends on defaults clobbering earlier config — impossible, as nothing runs before them. **Acceptance.** - [ ] Baseline output unchanged · [ ] Both override tests pass · [ ] `merge`/`theme` semantics unchanged - [ ] `sass.md`'s app-override claim now true, or corrected (FIX-008) --- ### FIX-003 — Type-aware guard in `size()` · Type A · P0 · **S** **Problem.** An unrecognised scale word is emitted as a quoted string, producing invalid CSS that the browser discards silently. Two live instances: `
` has had no `font-size` ([`_basic.scss:139`](src/sass/styles/_basic.scss:139) → `font-size: "xxs"` **[verified]**), and [`mmo-notification.jhs:132`](src/customElements/elements/mmo-notification.jhs:132) → `padding-bottom: "0"`. **Evidence.** [`_theme.scss:159-172`](src/sass/_theme.scss:159) — the `@else` falls through to `@return $size` with no validation. **Root cause.** Pass-through is *required* (C2: 67 uses of `0`, 10 of `50%`, 7 of `auto`) but is implemented as an untyped catch-all, so a typo is indistinguishable from a legitimate CSS value. **Proposed change.** Discriminate by type before falling through: ```text number (0, 12px, 50%, -8px, $var) → return as-is ← keeps all 85 legitimate uses string in scale set → calc(factor * var()) ← existing behaviour string in ('auto','inherit','initial','unset','none') → return as-is ← keeps the 7 `auto` uses anything else → @error "unknown size [x] — expected one of …" ``` Then fix the two call sites (`'xxs'` → `'xs'`, `'0'` → `0`) and, as D14, either add `xss` to the accepted set or delete it from `_default_config.scss`. **Recommend deleting** — a `0.1` multiplier between `xss` and `xs` has no call sites and its name is one keystroke from the typo that caused this. **Files.** `_theme.scss` · `styles/_basic.scss` · `mmo-notification.jhs` · `_default_config.scss` · `tests/sass/` · `docs/Styles.md` **API impact.** Public behaviour change: previously-silent misuse now fails the build. This is the point of the fix. **Backwards compatibility.** *Mostly compatible.* Every value used anywhere in the repo continues to work — census in C2 is exhaustive over `src/sass` and `src/customElements`. **An application passing a bad size will now fail to compile instead of silently producing no style.** That is the desired trade, but it is the one fix here that can break someone's build, so it should ship with a release note. Application `.jhs` files outside this repo were not surveyed — see Q1. **Tests.** (1) each of `0`, `50%`, `auto`, `2px`, `-8px` passes through unchanged; (2) each scale word produces `calc(f * var(--mmo-size-*))`; (3) `theme.font-size('xxs')` **fails compilation**; (4) compiled CSS contains no `: "` anywhere; (5) `h6` has a real `font-size`. **Risks.** An allow-list that is too narrow breaks a legitimate CSS keyword. Mitigation: the census is exhaustive for this repo, and the error message names the accepted set. **Acceptance.** - [ ] All 85 legitimate values unchanged · [ ] Both bad sites fixed · [ ] Invalid-CSS assertion green - [ ] `xss` removed or reachable · [ ] Release note: sizes now validated --- ### FIX-004 — Dark theme contrast pass · Type B · P1 · **M** **Problem.** Dark token values have never been validated against a background. After FIX-001, `muted` lands at **13.09** contrast on `app-bg` against `text` at **13.24** **[verified]** — a 1 % difference. "Muted" text becomes indistinguishable from body text. **Root cause.** The ramp-reversal model assumes a weight means the same *role* in both themes, but the author picked `greys-400` for `muted` in light and `greys-800` in dark. Under reversal those land at opposite ends. This is a data problem, not a code problem — which is why it must follow FIX-001. **Proposed change.** Audit all 27 dark tokens against `app-bg` and `surface` with the WCAG formula already implemented in [`inc/_functions.scss:237`](src/sass/inc/_functions.scss:237) (`luminance()`). Targets: body text ≥ 4.5, large/title ≥ 3.0, `muted` deliberately **between 4.5 and text's ratio**, non-text borders ≥ 3.0. Expected change: `muted` → a mid grey (`greys-200`/`greys-400` under dark's reversed ramp). Also worth recording: `border` fails 3.0 in **both** themes (light 1.95, dark 1.10). Decorative borders are exempt from WCAG; borders that convey state (`%invalid`, `%required`) are not. Scope this fix to the latter only — do not chase a number for hairlines. **Files.** `themes/_dark.scss` · optionally `themes/_default.scss` · `tests/sass/` **Backwards compatibility.** *Mostly compatible* — dark-mode appearance changes again. Bundle with FIX-001 in a single release so users see one visual change, not two. **Tests.** A contrast assertion in the harness: parse the compiled token block, compute ratios for a declared pair list, fail below threshold. This is the test that makes the whole theming layer self-checking, and is reusable for every future theme. **Risks.** Over-tuning to hit numbers can flatten the palette. Require visual review alongside the numeric gate. --- ### FIX-005 — `dark` declares `background` and `icon` · Type A · P1 · **XS** `.theme-dark` omits both **[verified]**; they work only because `:root` values cascade. Declare them explicitly in `themes/_dark.scss`. Also correct the `$all` parameter's doc comment at [`_theme.scss:520`](src/sass/_theme.scss:520): it maps onto `get()`'s `$merge-map` and is currently a no-op for colors, since no `colors` map exists at config root. Either document that or drop the parameter. **XS**, no API change. --- ### FIX-006 — Correct `docs/mcp/Styling.md` · Type A · P1 · **S** The agent-facing doc has wrong numbers, so agents generate off-scale CSS. | Claim | Actual | |---|---| | `font-xs: 0.5, font-s: 0.75, font-m: 1, font-l: 1.25, font-xl: 1.5` | `font-xs: 0.8, font-s: 1, font-m: 1.25, font-l: 1.5, font-xl: 2` | | base `font: 1rem`, `icon: 24px` | `0.8rem`, `22px` | | const omits `xxl` | `xxl: 5` exists | | "both themes define this key set" | `dark` omits `background`, `icon` (until FIX-005) | Add: the `classes/` placeholder layer is the shadow-DOM-facing API (Phase A §4) — currently undocumented anywhere, despite 16 `.jhs` files using it. **Derive every number from `_default_config.scss` at edit time; do not transcribe.** --- ### FIX-007 — Rewrite `docs/Styles.md` · Type A · P1 · **M** Contains phantom palettes (`oceans`, `greylights`, `whites`), a phantom `danger`/`black`/`link`, four phantom themes, a wrong `color-bg()` signature, and a worked example whose prose contradicts its code (`'tl'` described as "top and bottom"). Rewrite from source; add the real palettes (`lights`, `limes`) and the three-tier color model. Consider making this the canonical human doc and reducing `demo/www/contents/pages/sass/*.phtml` to runnable examples that link to it. --- ### FIX-008 — Make the app-extension contract true · Type D · P1 · **S** `sass.md` tells developers to create `my_app/src/sass/*/_your_file.scss`; nothing compiles it. `addingTheme.md` claims alternative themes inherit the default theme's values; the config layer has no such fallback (it works at runtime only via the `:root` CSS cascade — an accident worth stating). After FIX-002, document the one path that actually works and that I verified end to end: ```text 1. app entry point: app/src/sass/app.scss 2. @use 'inc/config' with ($-config: ( … app overrides … )); 3. @use 'styles'; // the whole framework sheet 4. app's own rules below 5. point Theme::$stylesheet at app.scss ``` Also correct `addingTheme.md`'s token list (drop the phantom `link`) and note at [`Theme.php:41`](src/Theme.php:41) that `assets['scss']` is a staleness list, not compile targets (D12 — comment, not rename). --- ## Part 8 — Quick wins High value, low complexity, low risk, independently shippable: | Fix | Why it qualifies | |---|---| | **FIX-001** | One parameter. Light theme byte-identical. Fixes a visible bug. | | **FIX-005** | Two lines of data. | | **FIX-006** | Docs only; directly improves every agent-authored `.jhs`. | | **D11 partial** | Delete `$CSS_COLORS` and the stale `mmo.client.demo.css`. Zero risk. | | **D12 comment** | One doc-comment; already cost a sibling package real time. | FIX-001 is the standout: smallest possible diff, largest user-visible improvement, and *provably* cannot affect the light theme. --- ## Part 9 — High-risk changes **FIX-003 (size guard)** — the only P0 that can break a build. *What breaks:* an application `.jhs` passing a size value not in my census. I surveyed `src/sass` and `src/customElements`; **application code outside this repo was not surveyed.** *Before:* run the harness across every `.jhs` in every consuming app. *Mitigation:* ship behind a warning first — emit `@warn` for one minor version, promote to `@error` in the next. This costs one release cycle and removes essentially all risk. *Rollback:* revert the guard; the two call-site corrections are independently safe and stay. **FIX-001 + FIX-004 (dark appearance)** — not risky technically, but *visible*. *What breaks:* any app CSS hand-tuned against the broken dark palette. *Before:* grep consuming apps for `.theme-dark`. *Mitigation:* ship FIX-001 and FIX-004 in the **same** release so users see one change. A feature flag is not appropriate — two dark themes is worse than one corrected one. *Rollback:* single-commit revert; no data migration. **Not risky, contrary to expectation:** FIX-002. It reads like a semantic change to the config core, but is provably output-neutral when no app supplies config. --- ## Part 10 — What should NOT be changed **Per-shadow-root CSS duplication.** Phase A measured ~21 KB of repeated `%button` rules across 8 shadow roots in an 849 KB bundle (~2.5 %). Deduplicating means constructable stylesheets and `adoptedStyleSheets` — a category F change to the compiler and every element's init path, to recover 2.5 %. **Rejected.** The duplication is inherent to shadow DOM, and the current design trades bytes for zero runtime machinery. Revisit only if the element count roughly triples. **The `calc(factor * var(--size))` idiom.** Looks redundant next to plain custom properties; it is the mechanism that makes the whole scale runtime-rescalable. Do not "simplify". **`config.merge()` / `config.theme()` semantics.** FIX-002 adds siblings rather than changing these, specifically so `addingTheme.md`'s documented workflow keeps working. **The `'a/b/c'` path-string config API.** The `meta.call(meta.get-function(...))` implementation is opaque, but rewriting it is pure churn: identical behaviour, identical cost, a diff across the most load-bearing file in the folder. If it is ever touched, it should be *while* fixing something else. **The `classes/` ÷ `styles/` split.** The eight colliding basenames look like duplication and are not. Document it (FIX-006); do not merge it. **`@error` in `color-value()`, `sizeToIndex()`, `device-bound()`.** Already correct. D3 brings `size()` *up* to this standard rather than relaxing the others. **The dark palette reversal.** Now that FIX-001 makes it actually take effect, it is a coherent model. FIX-004 tunes values within it; it should not be replaced with per-theme absolute palettes before seeing how it behaves once it is live. --- ## Part 11 — Backlog | ID | Title | Pri | Type | Deps | Cx | Main files | Tests | |---|---|---|---|---|---|---|---| | FIX-000 | Sass regression harness | P0 | — | — | S | `tests/sass/run.php` | self | | FIX-001 | Forward `$theme` in `include-colors()` | P0 | A | 000 | XS | `_theme.scss:524` | dark golden; `:root` invariant | | FIX-002 | `config.defaults()` / `theme-defaults()` | P0 | A | 000 | S | `inc/_config.scss`, `_default_config.scss`, `themes/*` | output-neutral; 2 override tests | | FIX-003 | Type-aware `size()` guard (+2 call sites, retire `xss`) | P0 | A | 000 | S | `_theme.scss:159`, `_basic.scss:139`, `mmo-notification.jhs:132` | 85 pass-through; 1 must-fail; no `: "` | | FIX-004 | Dark contrast pass | P1 | B | 001 | M | `themes/_dark.scss` | contrast assertions | | FIX-005 | Declare dark `background`/`icon`; clarify `$all` | P1 | A | 001 | XS | `themes/_dark.scss`, `_theme.scss:520` | token-count assertion | | FIX-006 | Correct `docs/mcp/Styling.md` | P1 | A | 001-003 | S | `docs/mcp/Styling.md` | values re-derived from source | | FIX-007 | Rewrite `docs/Styles.md` | P1 | A | 001-003 | M | `docs/Styles.md` | — | | FIX-008 | True app-extension contract | P1 | D | 002 | S | `sass.md`, `addingTheme.md`, `Theme.php:41` | documented path compiles | | FIX-009 | `prefers-color-scheme` default | P2 | E | 001,004 | M | `styles/_basic.scss`, `mmo-app.jhs` | first-load honours OS | | FIX-010 | `color-mix()` for focus rings | P2 | B | browser baseline | M | `classes/_inputs.scss` | theme-switch visual | | FIX-011 | Dead code sweep | P3 | C | — | S | `classes/_colors.scss`, `demo/www/bin/*` | output-neutral | | FIX-012 | Resolve `$-in-shadow-dom` | P3 | C | **Q2** | XS | `_theme.scss:29`, `_shadow_theme.scss` | output-neutral | | FIX-013 | Unify asset-URL mechanisms | P3 | C | — | S | `DartSass.php:61`, `_theme.scss:20` | — | --- ## Part 12 — Phases **Phase 0 — Safety net.** FIX-000. *Outcome:* compiled output becomes assertable; current `:root` frozen as a golden file. *Validation:* harness green on unmodified tree. *Risk:* none. **Phase 1 — Correctness.** FIX-001, FIX-002, FIX-003 (as `@warn`), FIX-005. *Outcome:* dark mode renders its own palette; app config works; no invalid CSS. *Validation:* `:root` byte-identical; dark golden matches; zero `: "`. *Risk:* dark appearance changes visibly — hold until FIX-004 lands with it. **Phase 2 — Theme quality.** FIX-004, promote FIX-003 to `@error`. *Outcome:* dark mode is contrast-validated and self-checking. *Validation:* contrast gate + visual review. **Phase 3 — Documentation truth.** FIX-006, FIX-007, FIX-008. *Outcome:* all five doc surfaces describe the implementation. *Dependency:* must follow Phases 1–2. **Phase 4 — Capability.** FIX-009, then FIX-010 once a browser baseline exists. **Phase 5 — Cleanup.** FIX-011, FIX-012, FIX-013. --- ## Part 13 — Release and migration Current version is `3.0.13` (`mmo.json`), so `x.y.z` applies. ```text 3.0.14 Phase 0 + docs-only no compiled-output change 3.1.0 Phase 1 + 2 together dark mode visibly changes; config.defaults() added; size() warns. MINOR: new behaviour, no removed API 3.2.0 Phase 3 + FIX-009 size() errors (announced in 3.1.0); OS colour scheme 3.3.0 Phase 5 cleanup 4.0.0 only if FIX-012/013 force a breaking change ``` **Why 3.1.0 and not 3.0.14 for Phase 1:** dark-mode colors change visibly. Even though every change is a bug fix, "the app looks different after a patch release" is the wrong signal. **Deprecation.** Only `size()`: `@warn` in 3.1.0, `@error` in 3.2.0 — one minor version, which is generous for a compile-time diagnostic. **Migration tooling.** Not warranted. The only possible app-side breakage is an invalid size literal, which the compiler will name precisely, at the exact line. A codemod would be more work than the fix. **Rollback.** Every fix is a single-commit revert with no persisted state. `localStorage['themeMode']` survives a rollback and still means the same thing. --- ## Part 14 — Documentation plan | Surface | Change | Fix | |---|---|---| | `docs/mcp/Styling.md` | correct token values; document `classes/` layer | FIX-006 | | `docs/Styles.md` | full rewrite from source | FIX-007 | | `docs/SASS_ARCHITECTURE_REVIEW.md` | append Part 0 corrections | this plan | | `guides/frontEnd/dependencies/sass.md` | make the app-override path real | FIX-008 | | `guides/frontEnd/themes/addingTheme.md` | drop phantom `link`; state that theme inheritance is CSS-cascade, not config | FIX-005/008 | | `demo/www/contents/pages/sass/*.phtml` | re-derive every number after FIX-003 | FIX-007 | | `Theme.php:41` | doc-comment: `assets['scss']` is a staleness list | FIX-008 | | `docs/Workflow.md` | how to run the sass harness | FIX-000 | | release notes | dark appearance change; `size()` deprecation | 3.1.0 | **Needs a diagram, and has none today:** the two-compile-path / custom-property-inheritance model (Phase A §2.3). It is the single thing a newcomer must understand, and it is currently nowhere. --- ## Part 15 — Recommended order and First Sprint ```text FIX-000 ──┬── FIX-001 ──┬── FIX-004 ──┐ │ └── FIX-005 ──┤ ├── FIX-002 ────────────────┼── FIX-006 ── FIX-007 ── FIX-008 └── FIX-003 ────────────────┘ └── FIX-009 ── FIX-010 ``` 1. **FIX-000** — everything after this changes compiled output; nothing currently checks it. This is the fix that would have caught D1. 2. **FIX-001** — largest user-visible gain per line changed, and provably cannot touch the light theme. 3. **FIX-005** — trivial, same file family, ships with 001. 4. **FIX-003 (warn mode)** — starts the deprecation clock early so the error can land a version sooner. 5. **FIX-002** — independent; removes a silent trap for near-zero cost. 6. **FIX-004** — must follow 001, and must ship *with* it so dark mode changes exactly once. ### First Sprint **FIX-000 → FIX-001 → FIX-005 → FIX-004**, released together as **3.1.0**, with **FIX-003 in warn mode** and **FIX-002** if capacity allows. This satisfies the criteria: it establishes the safety net first; fixes the most consequential issue (a shipped feature rendering wrong colors); introduces zero architectural churn — the code delta is one parameter, two data lines, and a set of tuned hex values; produces a measurable improvement (`info` contrast 2.01 → 8.10, `title` 5.15 → 12.29); and leaves the tree stable, because the light theme is byte-identical throughout **[verified]**. Explicitly **not** in the first sprint: documentation (it should describe the corrected behaviour, not the in-flight one) and anything in Phase 4–5. --- ## Part 16 — Questions still open Phase A's Q1 and Q4 are now answered from source (Part 0). Three remain, and only Q1 blocks work. 1. **(Blocks FIX-003.)** Which repositories consume `mmo.client`? I need to run the size census across their `.jhs` files before promoting `size()` to `@error`. If that survey is impractical, ship warn-only indefinitely. 2. **(Blocks FIX-012.)** Was `$-in-shadow-dom` meant to branch something? If it is vestigial, deleting it and folding `* {box-sizing}` into `_theme.scss` removes a file. 3. **(Shapes FIX-008's priority, not its content.)** Given that `sass.md` says the framework tree is modified in place by the team, is app-level config override something you actually want — or should `sass.md` simply stop promising it? FIX-002 is worth doing either way; only the documentation differs.