@micha.bigler/ui-core-micha 2.37.1 → 2.39.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,177 @@
1
+ # PRIM-1 — Promote `StatTile` and `SoftChip` into the shared kit
2
+
3
+ **Target repo:** `ui-core-micha` (branch `main`)
4
+ **Tier:** 3 — shared-core.
5
+
6
+ New register prefix `PRIM-*`: shared dashboard/data primitives — small presentational components that
7
+ are neither chart (`CHART-*`) nor app chrome (`SHELL-*`).
8
+
9
+ ---
10
+
11
+ ## A. Envelope
12
+
13
+ ### Goal
14
+
15
+ Both components already exist **twice**, built independently, and the second build in each case is an
16
+ **inline component inside a page** rather than a file — which is why a filename search misses it:
17
+
18
+ | Component | First build | Second build |
19
+ |---|---|---|
20
+ | `StatTile` | `hram/frontend/src/components/ResearchComponents/StatTile.jsx` (36 lines) | `fitness-monitor/frontend/src/pages/BodyHistoryPage.jsx:554-568` |
21
+ | `SoftChip` | `hram/frontend/src/components/ResearchComponents/SoftChip.jsx` (67 lines) | `cockpit/frontend/src/cockpit/BoardView.jsx:65-73` (the lane pill) |
22
+
23
+ Promote both into the kit as the single implementation. The promotion trigger is met on evidence — two
24
+ independent builds each, and a third app in view for each.
25
+
26
+ ### The visual spec, and the seven decisions in it
27
+
28
+ **Prototype:** [`work-orders/assets/PRIM-1-dashboard-primitives.html`](work-orders/assets/PRIM-1-dashboard-primitives.html).
29
+ It renders the promoted spec at 375 px and 1280 px, keeps both original builds side by side as the
30
+ record of what changed, and computes its contrast ratios in-page against the *blended* fill.
31
+
32
+ The two builds diverged in seven places. All seven were decided by the operator on 2026-08-12, and the
33
+ prototype already reflects them:
34
+
35
+ | | Decision | Which app changes |
36
+ |---|---|---|
37
+ | StatTile value size | **`h5` — 18/600** | hram, 16 → 18 |
38
+ | StatTile digits | **`tabular-nums`, always** — it is what makes a KPI row read as a row | fitness-monitor |
39
+ | StatTile row behaviour | **`flex: 1` with a `minWidth` floor** — one rule covering both existing behaviours, no prop | hram, tiles now share the row |
40
+ | StatTile `accent` | **kept** — 2 px `primary.main`; without it fitness-monitor cannot adopt | neither |
41
+ | StatTile padding | **responsive `{xs: 1, sm: 1.5}`** | hram, was fixed |
42
+ | SoftChip `status` typography | **`overline` — 11/600, uppercase, .4 px tracking** | hram (`passed` → `PASSED`); cockpit drops 700 → 600 |
43
+ | SoftChip leading element + border | **dot by default, any icon via prop; border optional** | neither — both adopt unchanged |
44
+
45
+ **Why `overline` rather than a new variant:** cockpit's build independently arrived at 11-with-uppercase-
46
+ and-.4 px-tracking, which is the baseline's `overline` in all but the weight step. So the shared choice
47
+ is what the second consumer already made, and it satisfies `THEME-4` **without a baseline change**.
48
+
49
+ ### Token delta list
50
+
51
+ **One entry** — the rest is drawn from the baseline's own tokens, so there is nothing else to reconcile.
52
+
53
+ - **`caveat` variant text: 12/500.** The baseline's nearest variant is `caption` at 12/**400**; there is
54
+ no 12/500. **Resolved by the operator as: keep the prototype value, do not add a baseline variant in
55
+ this WO.** It will surface as a `THEME-4` finding on the promoted component and must be **exempted
56
+ with that reason stated** — not silently flattened to 400, and not fixed by extending the baseline
57
+ here. Adding a variant is an app-level decision that does not belong inside a component promotion.
58
+
59
+ ### Scope
60
+
61
+ 1. **`StatTile`** as a kit component, per the decisions above. Props: `label`, `value`, `caption`,
62
+ `accent`, `children`, and a `minWidth` floor with a sensible default.
63
+ 2. **`SoftChip`** as a kit component. Props: `label`, `tone` (`success`/`warning`/`error`/`info`),
64
+ `variant` (`caveat`/`status`), `icon` (defaulting to the 6 px dot), a border toggle, and `title` for
65
+ the tooltip.
66
+ 3. **Colour derivation stays as authored** — fill `alpha(main, .12)`, border `alpha(main, .30)`, text and
67
+ dot `darken(main, .3)`, read from `theme.palette[tone]`. **Do not restate any hex.** This is the
68
+ property that makes the component exemplary and the reason its four tones all pass AA (measured in the
69
+ prototype: 7.11 / 7.33 / 7.67 / 9.20).
70
+ 4. **Keep `enterTouchDelay={0}`** on the tooltip. It is why SoftChip's `title` is reachable on a touch
71
+ pointer — the exact failure `AUTH-4` is blocked on elsewhere. Do not drop it as boilerplate.
72
+ 5. Exports from the package root, and the README line.
73
+
74
+ ### Non-goals / do not touch
75
+
76
+ - **The three app adoptions.** hram, fitness-monitor and cockpit each get their own small follow-up WO.
77
+ This WO ships the kit component and nothing in any app.
78
+ - **No baseline/theme change.** No new typography variant, no palette edit. See the token delta list.
79
+ - **The other primitives in hram's `ResearchComponents/`** — `CardTitle`, `InfoPopover`,
80
+ `ProvisionalChip`, `FieldInfo`. `ProvisionalChip` wraps `SoftChip` and is the obvious next candidate,
81
+ but it has one build and is out of scope here.
82
+ - **`SoftChip`'s `caveat` weight.** Left at 12/500 deliberately.
83
+ - Any chart wrapper, shell component, or existing kit surface.
84
+
85
+ ### Risks
86
+
87
+ - **Two apps' visible appearance changes when they adopt** — hram's status chips become uppercase and its
88
+ KPI values grow 16 → 18; cockpit's pills lose a weight step. All three are decided, not accidental, and
89
+ all three land in the *adoption* WOs rather than here. Worth stating in each so nobody reports them as
90
+ regressions.
91
+ - **Neither `StatTile` build has a test.** The promoted component's tests are written here from scratch,
92
+ not ported. `SoftChip` has `SoftChip.test.jsx` in hram — read it, but do not assume it covers the
93
+ promoted API.
94
+ - **`THEME-4` will flag the `caveat` 12/500.** Expected. Exempt with the reason; an unexplained exemption
95
+ is itself a finding by that check's own design.
96
+ - **Version:** this is a new capability area (two new components), so a **minor** — `2.38.0` → `2.39.0`.
97
+
98
+ ### Required tests to WRITE
99
+
100
+ - **`StatTile`**: renders label/value/caption; omits the value row when `value` is undefined; renders
101
+ `children`; `accent` applies the 2 px border and the default does not; the value carries
102
+ `tabular-nums`.
103
+ - **`SoftChip`**: each of the four tones derives fill/border/text from `theme.palette[tone]` rather than a
104
+ literal (assert against the theme object, not against a hex string); `status` vs `caveat` differ in
105
+ radius and typography; the default leading element is the dot and a passed `icon` replaces it; the
106
+ border toggle works; `title` renders a tooltip and `enterTouchDelay` is 0.
107
+ - **Do not write a contrast assertion using `getContrastRatio`** — it ignores alpha, so on a 12 % tint it
108
+ answers a different question and would pass while meaning nothing. The prototype computes the real
109
+ ratios; if a contrast test is wanted it must blend the fill first.
110
+
111
+ Scope the run to these two new specs plus anything the new exports touch. Not the full suite — no
112
+ dependency change, no theme change.
113
+
114
+ ### Parity guardrail
115
+
116
+ Visual and API only. The promoted components must be capable of rendering **both** existing builds'
117
+ appearances where a decision preserved them, and exactly the decided appearance where it did not. No
118
+ behaviour, permission or data-contract surface exists here.
119
+
120
+ ---
121
+
122
+ ## B. Implementation map
123
+
124
+ *Filled by the Orchestrator on `git pull` — see `AGENTS.md` → "Work Order".*
125
+
126
+ ---
127
+
128
+ ## C. Orchestrator only
129
+
130
+ > **STOP — if you are the implementer reading this work order as your own specification, this section is
131
+ > NOT addressed to you. Skip it entirely.** It tells the Orchestrator how to invoke you, how the result
132
+ > is reviewed, and how it is committed. **You ARE that invocation — do NOT shell out to `codex exec`, do
133
+ > NOT spawn reviewers, do NOT run tests beyond your own new ones, do NOT edit `WORK_ORDERS.md`, and do
134
+ > NOT `git add`/`commit`/`push`.** Leave the diff in the working tree.
135
+
136
+ ### Execution directive
137
+
138
+ Check `.claude/codex-status.md` first. **No line for the current date means use Codex.** A line recording
139
+ `unavailable` for the current date means skip the attempt, implement directly in Claude, and name the
140
+ record and its date — that flips authorship. If the newest confirmation is older than today, make exactly
141
+ one probe and write the outcome back either way.
142
+
143
+ ### Review routing
144
+
145
+ Tier 3: **independent `reviewer`** and **`ui_reviewer`**, concurrent, in one background batch, before the
146
+ commit. **No `sec_reviewer`** — no auth, permission, money or schema surface.
147
+
148
+ Ask `ui_reviewer` specifically whether the `overline` decision reads correctly at both variants, and
149
+ whether the `caveat` exemption is stated rather than assumed.
150
+
151
+ ### Verification
152
+
153
+ **A prototype is in scope, so the rendered side-by-side at 375 px and 1280 px is a commit gate** — see
154
+ `AGENTS.md` → Reviews. Note the known constraint: the Browser pane composites only while displayed and
155
+ screenshots have not been legibly obtainable in this repo. If capture fails, **declare the substitution
156
+ in the register Notiz** and say what was inspected instead (computed styles, DOM) — an undeclared
157
+ substitution is a skipped gate, and that fallback cannot see whether a screen reads heavy or dense.
158
+
159
+ The prototype's own contrast numbers are computed at render time; re-read them rather than restating
160
+ these.
161
+
162
+ ### Register + commit
163
+
164
+ One row, `PRIM-1`, with the `PRIM-*` prefix documented in the register header. `done` only with both
165
+ reviewers and their verdicts named, plus the rendered-gate evidence or its declared substitution.
166
+
167
+ Publishing is a separate step from committing: **a landed commit is not a publish.** If the three
168
+ adoption WOs are to follow, `2.39.0` must actually be on the registry first — verify against npm, not
169
+ against a commit.
170
+
171
+ ### Mini-handover
172
+
173
+ Repo: `ui-core-micha` (`C:\Users\biglmi\Documents\webapps\ui-core-micha`), branch `main`.
174
+ WO: `work-orders/PRIM-1.md`. Promotes two components whose second builds are inline in
175
+ `fitness-monitor/BodyHistoryPage.jsx:554` and `cockpit/BoardView.jsx:65`. Seven divergences already
176
+ decided — the table in Part A is the spec, the prototype renders it. Tier 3, both reviewers, rendered
177
+ gate applies, minor version `2.39.0`. App adoptions are separate WOs. Follow `orchestrate-codex`.
@@ -0,0 +1,247 @@
1
+ # THEME-6 — An app-side check for `sx` values the theme already gives
2
+
3
+ **Target repo:** `ui-core-micha` (branch `main` — this repo has no `develop`)
4
+ **Tier:** 3 (shared core)
5
+ **Review:** independent `reviewer` — mandatory (new logic). **`ui_reviewer` does not apply**: this WO
6
+ adds a check and fixes nothing, so no rendered output changes. If that turns out to be false, it
7
+ applies after all.
8
+ **Version target:** the next **minor** after the published version — a new exported check is additive
9
+ API. Read the published version at implementation time.
10
+ **Strand:** `DS-18`, the app-side half. Trigger fired when cockpit adopted `createAppTheme` (`UI-6`);
11
+ deferred once because cockpit was mid-rebuild in `UI-3`, **which has since landed.**
12
+
13
+ ---
14
+
15
+ ## A. Envelope
16
+
17
+ ### Goal
18
+
19
+ `THEME-4` made it mechanical that no **kit** component defeats the baseline. Apps have no equivalent:
20
+ cockpit alone carries **464 `sx` props across 37 files** (measured 2026-08-11), and nothing checks any
21
+ of them against the theme they sit on.
22
+
23
+ Give apps an exported check they call from their own suite, the way they already call
24
+ `assertThemeComplete`.
25
+
26
+ ### This is NOT a port of `THEME-4`, and getting that wrong would flood the app
27
+
28
+ In the **kit**, an `sx` value that duplicates a baseline default is a bug by definition: the default
29
+ becomes inert for every consumer. So `THEME-4` compares **key sets** and demands disjointness.
30
+
31
+ In an **app**, `sx` beating the theme is **the intended mechanism** — that is how a screen customises
32
+ one instance. A key-collision check over 464 props would flag mostly legitimate customisation, get
33
+ blanket-exempted, and become the no-op `THEME-4`'s own WO warned about.
34
+
35
+ **So the app-side rule is different: flag an `sx` value that is *equal* to what the theme already
36
+ resolves for that component and property.** Not "you overrode a default" — "you re-stated a value you
37
+ already had". That is dead weight which **silently diverges the next time the token changes**, which is
38
+ the real failure mode and the one worth a check.
39
+
40
+ Grounded in cockpit, measured: `borderColor: 'divider'` (×2) and `bgcolor: 'background.paper'` (×3) on
41
+ surfaces whose theme already sets exactly that, and `borderRadius: 1` (×3) which resolves to the
42
+ baseline's own `shape.borderRadius`. Those are the findings this check exists for.
43
+
44
+ ### Scope
45
+
46
+ **1. An exported check, source-level.** Same finding contract as its siblings
47
+ (`{ findings: [{ surface, reason }] }`), same shape as `reportThemeAdoption` and
48
+ `reportOffPaletteColours`: it takes the app's own sources plus its theme, and returns findings. The app
49
+ globs its `src/**` in its own test and passes them.
50
+
51
+ For each `sx` entry it can attribute to a MUI component the theme styles, resolve what the theme gives
52
+ for that property and flag **equality**. The attribution comes from the JSX element name in the source
53
+ text — `<Paper sx={{ borderRadius: 1 }}>` → `MuiPaper` → compare against
54
+ `theme.components.MuiPaper.styleOverrides.root.borderRadius`.
55
+
56
+ **2. Report-only, with a ratchet.** New findings must not turn a green app red on a pin bump. Ship it
57
+ **report-only**, and let each app make it a hard assertion in its own cleanup WO — the same ratchet
58
+ `DS-2` used for `assertThemeComplete`, and for the same reason.
59
+
60
+ **3. Run it against cockpit and report the count. Fix nothing.** cockpit is the first real consumer and
61
+ the reason the trigger fired. The cleanup is cockpit's own work order, not this one — bundling an
62
+ unbounded app cleanup into a shared-core WO is what `AUTH-4` was split out of `DS-11` to avoid.
63
+
64
+ ### Reuse, do not rebuild — and one gap in the reuse
65
+
66
+ **The hardcoded-colour half already exists and is already app-callable.**
67
+ `reportOffPaletteColours(sources, { palette })` (`themeCompleteness.js:759`) takes sources and an
68
+ optional palette, so an app can pass its own files and its own palette today. cockpit's
69
+ `bgcolor: '#fff'` (×2) is exactly what it reports. **Do not duplicate it.**
70
+
71
+ **But its skip pattern assumes ucm's layout.** It skips paths matching `src/theme/` — with a trailing
72
+ slash (`:768`). ucm's tokens live in `src/theme/tokens.js`; an app's theme is usually
73
+ `src/theme.js`, which **does not match** and therefore gets scanned, so every identity token in it
74
+ (cockpit: `#3D5A99`, `#eff2f7`, `#e4e8f1`, …) would be reported as off-palette.
75
+
76
+ Resolve it **app-side**, not by widening the pattern: the app excludes its own theme file from the
77
+ sources it passes. That keeps ucm's behaviour unchanged and puts the knowledge where the layout is
78
+ known. Say so in the check's doc comment so the next app does not rediscover it.
79
+
80
+ ### The bound, and it is open
81
+
82
+ Attribution from source text is a **heuristic**. It will miss `sx` on a variable, spread props, an
83
+ aliased import (`import { Paper as Surface }`), a `styled()` component, and a MUI component rendered
84
+ through `component={...}`. **Write the bound as open — "unknown attribution gaps remain" — not as a
85
+ list.** `THEME-4`'s WO enumerated its limits and the first real miss (shorthand alias spelling) was not
86
+ on the list; `THEME-5` was told to phrase it openly and that phrasing then held when the fourth
87
+ shadowing layer appeared. An enumeration reads as coverage.
88
+
89
+ This check is a lower bound on redundancy, and green means "nothing found by this method".
90
+
91
+ ### Non-goals / do not touch
92
+
93
+ - **No fixes anywhere.** Not in cockpit, not in the kit. This WO ships a check and a count.
94
+ - **No change to `reportOffPaletteColours`**, `assertKitSxDisjoint`, `reportKitSxBypasses`,
95
+ `reportThemeAdoption` or `assertThemeComplete` — including no widening of the `src/theme/` skip.
96
+ - **Not a hard assertion anywhere yet.** Report-only; the ratchet is each app's own.
97
+ - `DS-18`'s remaining half — "which components *can violate* a rule", derived from a
98
+ `grep 'shadows\['` lower bound — stays out and stays without a driver.
99
+ - No baseline token, component default or public prop change.
100
+
101
+ ### Risks
102
+
103
+ - **False positives are the failure mode**, not false negatives. A check that flags legitimate
104
+ customisation gets switched off. That is why it compares values rather than keys, and why it ships
105
+ report-only.
106
+ - **The count from cockpit may be large.** 464 props is the denominator. If it is large enough that no
107
+ app could plausibly act on it, that is a finding to report — the check may need a narrower first
108
+ scope (e.g. only the ~23 component keys the baseline actually styles) rather than shipping noise.
109
+ - **No staging net.** A version bump on `main` publishes; the independent review is the only gate.
110
+ - **Other sessions are frequently active in this repo.** Read `git status` and
111
+ `git log origin/main..HEAD` before reviewing; this strand has had concurrent work in the same files
112
+ throughout.
113
+
114
+ ### Required tests to WRITE
115
+
116
+ 1. **Non-vacuity:** a fixture source with `<Paper sx={{ borderColor: 'divider' }}>` against a theme
117
+ whose `MuiPaper` already sets `borderColor: divider` produces a finding naming the file, line,
118
+ component and property.
119
+ 2. **The legitimate case produces nothing:** the same fixture with a *different* value
120
+ (`borderColor: 'error.main'`) is **not** flagged. This is the test that keeps the check usable.
121
+ 3. A component the theme does not style is not flagged at all.
122
+ 4. The finding shape matches its siblings, so a consumer handles one contract.
123
+ 5. `reportOffPaletteColours` is unchanged — its existing tests stay green untouched.
124
+
125
+ Plus `tsc -p tsconfig.build.json --noEmit` clean. **No full-suite run** — affected set is the theme
126
+ module's specs.
127
+
128
+ ### Verification
129
+
130
+ No rendered surface. The evidence is test 1 plus the cockpit count, reported in the register note with
131
+ the exact number and a sample of what it found. A check nobody has run against real code is not
132
+ verified, however green its fixtures.
133
+
134
+ ### Parity guardrail
135
+
136
+ Nothing renders differently. No existing check changes behaviour.
137
+
138
+ ---
139
+
140
+ ## B. Implementation map
141
+
142
+ *Filled by the Orchestrator on `git pull` — see `AGENTS.md` → "Work Order".*
143
+
144
+ ### Execution directive (read this first)
145
+
146
+ > **If you are the implementer reading this work order as your own specification: this section is
147
+ > NOT addressed to you.** It tells the Orchestrator how to invoke you. **You ARE that invocation —
148
+ > do NOT shell out to `codex exec`.**
149
+ >
150
+ > Implement through `codex exec` in the background — invoked directly via Bash (never the
151
+ > `debugger`/`*_coder` Agent wrappers) with BOTH flags `--skip-git-repo-check` and
152
+ > `--dangerously-bypass-approvals-and-sandbox`. **Pass the WO via stdin, not as a positional
153
+ > argument.** Fallback to direct Claude implementation only on Codex quota/rate-limit/non-zero exit —
154
+ > and that flips authorship, so the independent `reviewer` stays mandatory either way.
155
+ >
156
+ > **Do NOT edit `WORK_ORDERS.md`.** Before your own review read `git log origin/main..HEAD` and
157
+ > `git status`, and check the register row carries no review claim you did not make yourself.
158
+
159
+ ### Context package
160
+
161
+ **Precondition confirmed:** `SHELL-6` landed and published (`f119b70`/`3b746d4`, `package.json` reads
162
+ `2.37.1`). Clean starting tree.
163
+
164
+ **Reusable primitives already in `src/theme/themeCompleteness.js` — do not rebuild these:**
165
+ - `getPath(value, path)` (`:13-15`) and `sameValue(left, right)` (`:17-21`) — exactly what "resolve
166
+ what the theme gives for that property and flag equality" needs; both already handle the
167
+ string/JSON-stringify comparison this check requires.
168
+ - `BASELINE_STYLED_MUI_COMPONENTS` (`:484-508`, 23-entry list), `jsxOpeningTag(source, start)`
169
+ (`:510-530`, returns the full `<Tag ...>` text starting at a match) and `topLevelSxValue(tag)`
170
+ (`:532-553`, extracts the raw `sx={...}` attribute text, top-level braces only) — the exact
171
+ tag-scanning machinery `reportKitSxBypasses` (`:560-`) already uses. Reuse the same
172
+ `<(${componentPattern})\\b` regex approach for finding tags; you will need a NEW small parser to
173
+ walk `topLevelSxValue`'s result and extract individual top-level `property: value` pairs from a
174
+ literal object (`{ borderColor: 'divider', p: 2 }` → `[['borderColor', "'divider'"], ['p', '2']]`) —
175
+ this doesn't exist yet, write it narrowly (top-level only, matching the existing lower-bound
176
+ philosophy; do not attempt to parse nested objects, media-query keys, or `theme =>` callbacks).
177
+ - `reportOffPaletteColours`'s `normalizedSources`/path-skip pattern (`:747-753`, `:768-769`) — same
178
+ `{ path, source }` input shape, reuse `normalizedSources` directly, do NOT touch or widen its
179
+ `/(^|\/)src\/theme\//` skip (Non-goal, explicit).
180
+
181
+ **A real discrepancy found while preparing this map, worth resolving deliberately rather than
182
+ silently — the WO's own "grounded in cockpit" evidence does not match its own stated attribution
183
+ method:** the cited `borderColor: 'divider'` (×2), `bgcolor: 'background.paper'` (×3, one is inside a
184
+ `'&:hover'` nested selector), and `borderRadius: 1` (×3) all live in
185
+ `cockpit/frontend/src/cockpit/ChatView.jsx:75,82,95`, on **`<Box component="button" sx={{...}}>`** —
186
+ verified directly, not assumed. `Box` is not in `BASELINE_STYLED_MUI_COMPONENTS`, and the WO's own
187
+ "Scope" section describes attribution purely by JSX tag name (`<Paper sx=...> → MuiPaper`), while its
188
+ own "bound" section separately, explicitly lists a component rendered through `component={...}` as a
189
+ KNOWN, ACCEPTED GAP. **These two things conflict**: the check as scoped will NOT catch the WO's own
190
+ cited grounding examples. Meanwhile, cockpit's actual `<Paper>`/`<Card>`/etc. usages elsewhere
191
+ (`HeaderStrip.jsx`, `KnowledgeView.jsx`, `SettingsView.jsx`, and others) mostly use **template-literal**
192
+ sx values like `` border: `1px solid ${palette.divider}` `` — a JS expression referencing cockpit's own
193
+ imported `palette` object, not a plain string literal — which a source-level literal-value parser also
194
+ cannot resolve to "equals the theme's value" without evaluating arbitrary JS.
195
+
196
+ **Resolve this by NOT expanding scope to chase the cited numbers** (that would be exactly the
197
+ "enumeration reads as coverage" mistake the WO warns against, applied to attribution instead of the
198
+ bound's phrasing) — implement the check exactly as scoped (tag-name attribution, literal `'string'`/
199
+ `"string"`/numeric sx values only, skip anything that isn't a simple literal — a template literal,
200
+ identifier reference, or expression is out of scope and simply produces no finding for that property,
201
+ same lower-bound philosophy as every sibling check). **Then, when running it against cockpit for the
202
+ required count and sample, report what it ACTUALLY finds — which may be zero, or may differ from the
203
+ Envelope's cited 2+3+3 — and say so explicitly in the register note as a correction to the Envelope's
204
+ own grounding claim**, the same way prior WOs in this strand corrected their own stale counts
205
+ (`THEME-4`'s "51 files", `SHELL-4`'s "ten to six"). Do not silently match the cited numbers by quietly
206
+ special-casing `component=` or template-literal parsing to make them fit — if the honest run finds
207
+ different examples (or none) on properly-attributable tags, that is the real, reportable result.
208
+
209
+ **Where to add the new export:** same file, `src/theme/themeCompleteness.js`, alongside its siblings.
210
+ Suggested name (Codex's call, keep it short and consistent with `reportKitSxBypasses`/
211
+ `reportOffPaletteColours`): `reportRedundantThemeValues` or `reportAppSxRedundancy`. Same
212
+ `{ findings: [{ surface, reason }] }` contract; `surface` should follow the sibling convention
213
+ (`${path}:${line}.${property}` or similar — check `reportOffPaletteColours`'s `${path}:${line}.colour`
214
+ shape for the exact precedent). Export it from `src/theme/index.js` and `src/index.js` (follow the
215
+ existing export-list pattern in both — this is additive public API, matching the "next minor" version
216
+ target).
217
+
218
+ **Signature, per Scope item 1 + 2:** `reportRedundantThemeValues(sources, { theme })` — takes the
219
+ app's own sources (same `{path, source}`/string-array shape as siblings) and a **resolved theme
220
+ object** (an app passes its own `createAppTheme(...)` result, or — for ucm's own required tests — a
221
+ theme built the same way `assertKitSxDisjoint`'s tests do). For each JSX tag matching
222
+ `BASELINE_STYLED_MUI_COMPONENTS`, for each top-level literal `property: value` pair in its `sx`,
223
+ resolve `theme.components.Mui<Tag>.styleOverrides.root.<property>` via `getPath` and flag if
224
+ `sameValue(parsedValue, themeValue)` — matching value AND matching property AND matching component,
225
+ report-only (no exemption arm needed per the WO — this ships report-only with no hard-assertion path
226
+ yet, so skip building an exemption contract this round, that's each app's own future ratchet WO).
227
+
228
+ **Running it against cockpit (Scope item 3, required for the register note):** cockpit's frontend
229
+ source lives at `C:\Users\biglmi\Documents\webapps\cockpit\frontend\src` (37 `.jsx` files, confirmed).
230
+ Glob it (a small Node script or a throwaway test-like harness is fine — this does not need to be a
231
+ committed cockpit-side test, just evidence for the register note) the same way `reportOffPaletteColours`'s
232
+ own `'keeps current kit source free of hard off-palette findings'` test globs `src/` — exclude
233
+ `cockpit/frontend/src/theme.js` explicitly from the sources passed (per Scope's "resolve it app-side"
234
+ instruction — do NOT widen `reportOffPaletteColours`'s or this new check's own skip pattern to do it).
235
+ Build the theme via cockpit's own `createAppTheme(...)` call if one exists (check `theme.js` for the
236
+ palette it passes), or a reasonable equivalent. Report the exact count and 2-3 sample findings in the
237
+ register note, per Verification's explicit requirement — a check nobody has run against real code is
238
+ not verified.
239
+
240
+ **Invariants:** no fix in cockpit or the kit; no change to `reportOffPaletteColours`,
241
+ `assertKitSxDisjoint`, `reportKitSxBypasses`, `reportThemeAdoption`, or `assertThemeComplete`; no new
242
+ baseline token; no hard assertion anywhere (report-only only).
243
+
244
+ ### Mini-handover
245
+
246
+ Repo: `ui-core-micha` (`C:\Users\biglmi\Documents\webapps\ui-core-micha`), branch `main`.
247
+ WO: `work-orders/THEME-6.md`. Follow `orchestrate-codex`.