@micha.bigler/ui-core-micha 2.33.1 → 2.34.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.
Files changed (82) hide show
  1. package/WORK_ORDERS.md +4 -2
  2. package/dist/auth/ReauthModal.js +3 -1
  3. package/dist/components/AccessCodeManager.js +16 -14
  4. package/dist/components/AccessCodeSingleUseToggle.js +2 -1
  5. package/dist/components/AllowedEmailDomainsManager.js +3 -1
  6. package/dist/components/AuthFactorRequirementCard.js +2 -1
  7. package/dist/components/BulkInviteCsvTab.js +7 -7
  8. package/dist/components/LoginForm.js +2 -1
  9. package/dist/components/MFAComponent.js +11 -5
  10. package/dist/components/MfaLoginComponent.js +4 -1
  11. package/dist/components/PasskeysComponent.js +4 -2
  12. package/dist/components/QrSignupManager.js +2 -1
  13. package/dist/components/QrSignupValidityManager.js +4 -1
  14. package/dist/components/RegistrationMethodsManager.js +3 -1
  15. package/dist/components/SecurityComponent.js +3 -1
  16. package/dist/components/SupportRecoveryRequestsTab.js +4 -1
  17. package/dist/components/UserInviteComponent.js +8 -8
  18. package/dist/components/UserListComponent.js +8 -4
  19. package/dist/components/charts/ChartFrame.js +5 -2
  20. package/dist/components/charts/TimeSeriesChart.js +7 -4
  21. package/dist/index.js +1 -1
  22. package/dist/layout/PageLayout.js +3 -2
  23. package/dist/messaging/AttachmentList.js +6 -1
  24. package/dist/messaging/Composer.js +2 -1
  25. package/dist/messaging/DirectMessageLauncher.js +2 -1
  26. package/dist/messaging/MessageBubble.js +29 -8
  27. package/dist/messaging/PollCard.js +6 -4
  28. package/dist/messaging/Thread.js +2 -1
  29. package/dist/notifications/NotificationSettings.js +17 -3
  30. package/dist/notifications/PopupSurface.js +2 -1
  31. package/dist/onboarding/steps/BrowserPushStep.js +2 -1
  32. package/dist/pages/AccountPage.js +2 -1
  33. package/dist/pages/LoginPage.js +2 -1
  34. package/dist/pages/SignUpPage.js +2 -1
  35. package/dist/pages/SignupConfirmPage.js +2 -1
  36. package/dist/theme/index.js +2 -1
  37. package/dist/theme/kitSxRegistry.js +187 -0
  38. package/dist/theme/themeCompleteness.js +218 -0
  39. package/dist/theme/tokens.js +1 -0
  40. package/package.json +1 -1
  41. package/src/auth/ReauthModal.jsx +5 -2
  42. package/src/components/AccessCodeManager.jsx +22 -20
  43. package/src/components/AccessCodeSingleUseToggle.jsx +4 -2
  44. package/src/components/AllowedEmailDomainsManager.jsx +6 -3
  45. package/src/components/AuthFactorRequirementCard.jsx +4 -2
  46. package/src/components/BulkInviteCsvTab.jsx +11 -10
  47. package/src/components/LoginForm.jsx +5 -3
  48. package/src/components/MFAComponent.jsx +14 -7
  49. package/src/components/MfaLoginComponent.jsx +9 -5
  50. package/src/components/PasskeysComponent.jsx +7 -4
  51. package/src/components/QrSignupManager.jsx +6 -4
  52. package/src/components/QrSignupValidityManager.jsx +8 -4
  53. package/src/components/RegistrationMethodsManager.jsx +7 -4
  54. package/src/components/SecurityComponent.jsx +10 -7
  55. package/src/components/SupportRecoveryRequestsTab.jsx +8 -4
  56. package/src/components/UserInviteComponent.jsx +10 -10
  57. package/src/components/UserListComponent.jsx +12 -7
  58. package/src/components/charts/ChartFrame.jsx +6 -2
  59. package/src/components/charts/TimeSeriesChart.jsx +8 -4
  60. package/src/index.js +3 -0
  61. package/src/layout/PageLayout.jsx +4 -2
  62. package/src/messaging/AttachmentList.jsx +9 -3
  63. package/src/messaging/Composer.jsx +3 -1
  64. package/src/messaging/DirectMessageLauncher.jsx +3 -1
  65. package/src/messaging/MessageBubble.jsx +31 -9
  66. package/src/messaging/PollCard.jsx +8 -6
  67. package/src/messaging/Thread.jsx +3 -1
  68. package/src/notifications/NotificationSettings.jsx +24 -9
  69. package/src/notifications/PopupSurface.jsx +3 -1
  70. package/src/onboarding/steps/BrowserPushStep.jsx +3 -1
  71. package/src/pages/AccountPage.jsx +11 -9
  72. package/src/pages/LoginPage.jsx +6 -4
  73. package/src/pages/SignUpPage.jsx +4 -2
  74. package/src/pages/SignupConfirmPage.jsx +4 -2
  75. package/src/theme/index.js +3 -0
  76. package/src/theme/kitSxRegistry.js +246 -0
  77. package/src/theme/themeCompleteness.js +228 -0
  78. package/src/theme/tokens.js +1 -0
  79. package/tests/createAppTheme.test.js +1 -0
  80. package/tests/themeCompleteness.test.js +214 -0
  81. package/work-orders/THEME-3.md +210 -0
  82. package/work-orders/THEME-4.md +271 -0
@@ -0,0 +1,271 @@
1
+ # THEME-4 — Make it impossible for a kit component to silently defeat the baseline
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` becomes mandatory too if
6
+ the check surfaces findings that require touching a component's rendering**, which is likely; see
7
+ "What test 2 will probably do".
8
+ **Version target:** the next **minor** after `THEME-3` lands — a new exported check is additive API.
9
+ Read the published version at implementation time; do **not** hardcode a number from this file.
10
+ **Strand:** `DS-18`, first half
11
+ **Sequencing:** **after `THEME-3`.** See Risks — that work order is in flight in these exact files.
12
+
13
+ ---
14
+
15
+ ## A. Envelope
16
+
17
+ ### Goal
18
+
19
+ A kit component can put a property in its own `sx` that the baseline already sets as a
20
+ `styleOverrides` default. Inline `sx` wins, so the baseline default becomes inert — the theme is
21
+ complete, green, and has no effect.
22
+
23
+ This is not hypothetical. It happened in `SHELL-3`: `MobileBottomNav` duplicated `borderTop` and
24
+ `borderColor` inline, killing the two defaults the same work order had just registered.
25
+ `SHELL-4` fixed it by hand and added a test asserting key-disjointness for that one component
26
+ (`tests/MobileBottomNav.test.jsx:215`). **Make that mechanical for the whole kit.**
27
+
28
+ ### Why the whole kit, measured
29
+
30
+ - **51 `.jsx` files under `src/` carry an `sx=` prop.**
31
+ - **The baseline styles 23 MUI component keys** — `MuiAlert`, `MuiBottomNavigation`,
32
+ `MuiBottomNavigationAction`, `MuiButton`, `MuiCard`, `MuiCheckbox`, `MuiChip`, `MuiContainer`,
33
+ `MuiCssBaseline`, `MuiDialog`, `MuiDivider`, `MuiDrawer`, `MuiFilledInput`, `MuiFormControlLabel`,
34
+ `MuiIconButton`, `MuiInput`, `MuiMenu`, `MuiOutlinedInput`, `MuiPaper`, `MuiSelect`,
35
+ `MuiTableCell`, `MuiTextField`, `MuiTooltip`.
36
+ - `SHELL-4`'s test covers **one** component pair. The other 50 files are unchecked.
37
+
38
+ `MobileBottomNav` was not special. `ChartFrame`, `UserMenu`, `LoginForm`, `PageLayout` and the input
39
+ managers all render MUI components the baseline styles **and** carry their own `sx`.
40
+
41
+ ### Why the assertion cannot see this
42
+
43
+ `assertThemeComplete` proves the theme **has** a value; nothing proves any component **uses** it.
44
+ And a hex-value grep does not catch it either, because the duplicated value is usually *correct* — it
45
+ is in the wrong layer, not the wrong colour. Reviewing for "no hardcoded colours" passes it. The one
46
+ thing that caught it was a human reading a diff.
47
+
48
+ ### Scope
49
+
50
+ **1. A convention, already demonstrated.** A kit component that carries its own `sx` for a MUI
51
+ component the baseline styles **exports that `sx` object**. `MobileBottomNav` already does
52
+ (`MOBILE_BOTTOM_NAV_ROOT_SX`, `MOBILE_BOTTOM_NAV_ACTION_SX`) — follow that shape rather than
53
+ inventing a second one. A small registry maps each exported object to the MUI key it targets.
54
+
55
+ **2. An exported check.** Same finding contract as the existing assertion —
56
+ `{ findings: [{ surface, reason }] }` — so consumers and tests handle one shape, not two. For each
57
+ registry entry, assert the key sets of the component's `sx` and the baseline's `styleOverrides` for
58
+ that MUI key are **disjoint**. A collision names the component, the MUI key and the property.
59
+
60
+ **3. A bypass check, so the convention cannot be skipped in silence.** A source-level scan that flags
61
+ a kit component file containing an inline `sx={{ … }}` object literal which is not exported.
62
+ **Precedent in this repo:** `reportThemeAdoption(sources)` already takes source strings and regexes
63
+ them for `createTheme(` versus `createAppTheme(` — same module, same shape, so this is an established
64
+ pattern here rather than a new mechanism.
65
+
66
+ **4. Wire it as a hard failure in ucm's own suite.**
67
+
68
+ **This check cannot turn any consuming app red.** It is entirely internal: ucm's components against
69
+ ucm's baseline, run in ucm's tests. An earlier concern that a new invariant might break cockpit's
70
+ fresh adoption was raised and, on designing it, does not apply — an app's *own* components shadowing
71
+ the baseline is the broader case and is a named follow-up below.
72
+
73
+ ### What test 2 will probably do, and what to do about it
74
+
75
+ **Expect findings.** 51 files have never been checked against 23 styled component keys. A clean first
76
+ run would be a pleasant surprise, not the base case.
77
+
78
+ - **Each finding is resolved by `SHELL-4`'s rule: one home per property.** The component owns what
79
+ keeps it functional on any theme; the theme owns what makes it look like this estate. A property in
80
+ both layers is the defect.
81
+ - **Do not exempt your way to green.** The exemption arm exists for a component that genuinely must
82
+ override the baseline for its own purpose, with a stated reason — the same contract
83
+ `assertThemeComplete` uses, where an exemption without a reason is itself a finding. A mass
84
+ exemption converts this work order into a no-op.
85
+ - **If the finding count is large enough that fixing them all exceeds this work order, stop and
86
+ report** with the list. Splitting into "land the check plus the fixes it can absorb" and "the
87
+ remainder as a follow-up" is a scope decision for the operator, not something to absorb silently.
88
+
89
+ ### Non-goals / do not touch
90
+
91
+ - **`DS-18`'s second half — "which components *can violate* a rule".** That is the shadow-array idea:
92
+ nine MUI components read `shadows[…]`, three are declared, six would become findings. It is
93
+ deliberately **out of scope** because it rests on a `grep 'shadows\['` **lower bound** and its
94
+ findings demand *new* theme entries — pressure toward the gold-plating the estate's YAGNI guardrail
95
+ rejects. Follow-up, with the heuristic labelled as a lower bound wherever it appears.
96
+ - **App-side scanning.** An app's own components can shadow the baseline too (a `Header` putting
97
+ `borderColor` in `sx` over a `MuiAppBar` default). Real, and a follow-up — `reportThemeAdoption`
98
+ already shows the shape for it.
99
+ - **No rendering-based check.** Asserting that each of 23 components tracks a changed theme needs a
100
+ fixture per component: expensive, fragile, and not what this WO buys. `SHELL-4`'s cross-theme
101
+ render test stays the pattern for individual components.
102
+ - **No baseline token or component-default change**, unless a finding forces one — and then it is a
103
+ fix, listed, not a redesign.
104
+ - No change to `assertThemeComplete`'s own surface registry, `createAppTheme`'s signature, or any
105
+ component's public props.
106
+
107
+ ### Risks
108
+
109
+ - **`THEME-3` is in flight in this repo right now.** Verified: `src/theme/tokens.js`,
110
+ `src/theme/themeCompleteness.js`, `tests/createAppTheme.test.js` and
111
+ `tests/themeCompleteness.test.js` are all modified in the working tree with Codex running. **Those
112
+ are exactly this work order's files.** Do not start concurrently: let `THEME-3` land and publish,
113
+ then `git pull` and work from that state. Check `git status` and `git log origin/main..HEAD` first.
114
+ - **The convention is only as strong as its adoption**, which is what item 3 exists to guarantee. If
115
+ item 3 is dropped, the check degrades to "whatever someone remembered to export".
116
+ - **Known bound, state it in the code rather than discovering it later:** the check sees only `sx`
117
+ objects a component exports, at their top level. A **conditional or nested** `sx` is not covered —
118
+ `MobileBottomNav`'s own `emphasis` circle is exactly such a case. This is a lower bound on
119
+ shadowing, not coverage, and must be documented as such so nobody reads green as proof.
120
+ - **No staging net.** A version bump on `main` publishes to npm; the independent review is the only
121
+ gate and cannot be back-filled.
122
+
123
+ ### Required tests to WRITE
124
+
125
+ 1. **Non-vacuity, reconstructing the real incident.** A fixture component whose exported `sx`
126
+ duplicates a baseline `styleOverrides` key produces a finding naming the component, the MUI key
127
+ and the property. This is `SHELL-3`'s arrangement; the test must fail if the check is inert.
128
+ 2. **The current kit.** Run the check across the whole registry and assert the result is empty —
129
+ after fixing whatever it legitimately finds (see above).
130
+ 3. **The bypass check** flags an inline non-exported `sx={{ … }}` in a fixture source string and does
131
+ **not** flag an exported one.
132
+ 4. **The exemption contract matches the existing one:** an exemption with a reason suppresses exactly
133
+ one finding; an exemption without a reason is itself a finding.
134
+ 5. `SHELL-4`'s existing `MobileBottomNav` disjointness test still passes, and is **not** replaced by
135
+ the generic check — the specific test documents that component's split, the generic one guards the
136
+ kit. Keeping both is deliberate.
137
+
138
+ Plus `tsc -p tsconfig.build.json --noEmit` clean. **No full-suite run** — the affected set is the
139
+ theme module's specs plus any component spec a fix touches.
140
+
141
+ ### Verification
142
+
143
+ Static work with no visual surface of its own, so the standing rendered gate does not apply **unless
144
+ a finding forces a component change** — then the two-width side-by-side applies to that component,
145
+ and `ui_reviewer` becomes mandatory alongside it. Say in the register note which of the two happened.
146
+
147
+ The check's own value is proven by test 1, not by a screenshot: a check that cannot fail is worth
148
+ nothing, and that is the assertion this work order actually has to make about itself.
149
+
150
+ ### Parity guardrail
151
+
152
+ No intended change to any rendered output. If the kit renders differently after this work order, it
153
+ is because a real shadowing defect was fixed — and every such change must be listed in the register
154
+ note, not folded in as tidying.
155
+
156
+ ---
157
+
158
+ ## B. Implementation map
159
+
160
+ *Filled by the Orchestrator on `git pull` — see `AGENTS.md` → "Work Order".*
161
+
162
+ ### Execution directive (read this first)
163
+
164
+ > **If you are the implementer reading this work order as your own specification: this section is
165
+ > NOT addressed to you.** It tells the Orchestrator how to invoke you. **You ARE that invocation —
166
+ > do NOT shell out to `codex exec`.**
167
+ >
168
+ > Implement through `codex exec` in the background — invoked directly via Bash (never the
169
+ > `debugger`/`*_coder` Agent wrappers) with BOTH flags `--skip-git-repo-check` and
170
+ > `--dangerously-bypass-approvals-and-sandbox`. **Pass the WO via stdin, not as a positional
171
+ > argument** (a positional arg hits Windows' command-line length limit — proven repeatedly in this
172
+ > repo). Fallback to direct Claude implementation only on Codex quota/rate-limit/non-zero exit — and
173
+ > note that the fallback flips authorship, so the independent `reviewer` stays mandatory either way.
174
+ >
175
+ > **Two things this repo has learned the hard way, both on the two immediately preceding work
176
+ > orders:** Codex committed its own change and wrote a self-reported review into
177
+ > `WORK_ORDERS.md` on `SHELL-3` **and** on `SHELL-4`. Read `git log origin/main..HEAD` and
178
+ > `git status` before your own review; an implementer commit is a blocker to surface, and a register
179
+ > row naming a review the Orchestrator did not itself start is invalid.
180
+
181
+ ### Context package
182
+
183
+ **Precondition confirmed:** `THEME-3` is landed and published (`1b25117`, register row `done`).
184
+ `git log --oneline -3` at hand-off time shows the Orchestrator's own docs commits on top of it, no
185
+ implementer commit — clean to start from.
186
+
187
+ **The established `sx`-export convention (scope item 1's shape to follow), already shipped in
188
+ `src/components/MobileBottomNav.jsx`:**
189
+ ```js
190
+ export const MOBILE_BOTTOM_NAV_ROOT_SX = {
191
+ position: 'fixed', left: 0, right: 0, bottom: 0,
192
+ borderTop: '1px solid', borderColor: 'divider',
193
+ paddingBottom: 'env(safe-area-inset-bottom)',
194
+ };
195
+ export const MOBILE_BOTTOM_NAV_ACTION_SX = { minWidth: 0, maxWidth: 'none' };
196
+ ```
197
+ Both are plain top-level `export const` object literals, used directly as (or spread into) the
198
+ `sx` prop on the MUI element they style. This is the shape the registry (item 1) should match
199
+ against — a component "carries its own `sx` for a MUI component the baseline styles" means: look for
200
+ `<BottomNavigation`, `<Button`, `<Card`, etc. (the 23 baseline-styled MUI component names) receiving
201
+ an `sx=` prop inside `src/**/*.jsx`, then check whether that `sx` is a top-level exported identifier
202
+ (covered) or an inline/conditional literal (item 3's bypass check should flag the latter if it isn't
203
+ exported).
204
+
205
+ **The exemption contract to mirror exactly (scope item 2 + required test 4), from
206
+ `src/theme/themeCompleteness.js:333-350` (`assertThemeComplete`):**
207
+ ```js
208
+ export function assertThemeComplete(theme, { exemptions = [] } = {}) {
209
+ const declaredExemptions = [...(theme?.themeCompleteness?.exemptions || []), ...exemptions];
210
+ const validExemptions = new Set();
211
+ const findings = [];
212
+ for (const exemption of declaredExemptions) {
213
+ if (!exemption?.surface || !exemption?.reason?.trim()) {
214
+ findings.push({ surface: `exemption.${exemption?.surface || 'unknown'}`,
215
+ reason: 'A completeness exemption must name a surface and include a reason.' });
216
+ continue;
217
+ }
218
+ validExemptions.add(exemption.surface);
219
+ }
220
+ ...
221
+ }
222
+ ```
223
+ The new check's `{ findings: [{ surface, reason }] }` return shape and exemption handling should be
224
+ structurally identical to this, not a reinvention — same two failure modes (missing reason is itself
225
+ a finding; a valid exemption suppresses exactly one finding).
226
+
227
+ **The bypass-check precedent (scope item 3), from `src/theme/themeCompleteness.js:370-379`
228
+ (`reportThemeAdoption`):**
229
+ ```js
230
+ export function reportThemeAdoption(sources = []) {
231
+ const normalized = sources.map((entry, index) => typeof entry === 'string'
232
+ ? { path: `source-${index + 1}`, source: entry }
233
+ : { path: entry.path || `source-${index + 1}`, source: entry.source || '' });
234
+ const themeBypassPaths = normalized
235
+ .filter(({ source }) => /\bcreateTheme\s*\(/.test(source) && !/\bcreateAppTheme\s*\(/.test(source))
236
+ .map(({ path }) => path);
237
+ ...
238
+ }
239
+ ```
240
+ Same module, same input shape (an array of `{ path, source }` or raw source strings), same
241
+ regex-over-source-text technique — the new bypass check takes the same input shape and regexes for
242
+ an inline `sx={{ ... }}` object literal that is not a reference to a top-level exported identifier.
243
+
244
+ **Where to put the new code:** co-locate in `src/theme/themeCompleteness.js` alongside
245
+ `reportThemeAdoption` (the WO's own "same module" language for item 3), or a new sibling file if
246
+ the additions make that file unwieldy — Codex's call, but export everything new from
247
+ `src/theme/index.js` and `src/index.js` (follow the existing export list pattern in both) so the
248
+ check is part of the public API per the WO's "additive API is a minor" versioning note.
249
+
250
+ **Discovery is the actual task, not pre-solved here:** the WO's own measurement (51 `.jsx` files
251
+ under `src/` carry `sx=`, baseline styles 23 MUI keys) is a starting estimate, not a fixed list —
252
+ grep `src/**/*.jsx` for `sx=` against the 23 keys named in the Envelope's "Why the whole kit,
253
+ measured" section to build the actual registry. This is scope item 1 itself; do not skip it by
254
+ registering only `MobileBottomNav`'s two already-known exports.
255
+
256
+ **On the "stop and report if large" clause:** if fixing every real finding from test 2 would grow
257
+ this WO well beyond a layer-relocation pass (i.e. touches component *behaviour*, not just moving an
258
+ `sx` value into an export), STOP before committing anything and report the finding list instead of
259
+ silently absorbing it all — this is explicitly named in the Envelope as an operator-level scope
260
+ decision, not an implementer judgment call.
261
+
262
+ **Known repeat-offense warning, restated because it has happened on the two immediately preceding
263
+ work orders in this exact repo:** do not run `git commit` or `git push` under any circumstance. Leave
264
+ the diff for the Orchestrator. If you find yourself about to commit "because everything passed", stop
265
+ — that decision is not yours in this workflow, regardless of how clean the result looks.
266
+
267
+ ### Mini-handover
268
+
269
+ Repo: `ui-core-micha` (`C:\Users\biglmi\Documents\webapps\ui-core-micha`), branch `main`.
270
+ WO: `work-orders/THEME-4.md`. **Wait for `THEME-3` to land and publish first** — same files.
271
+ Follow `orchestrate-codex`.