@micha.bigler/ui-core-micha 3.7.0 → 3.9.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.
- package/CHANGELOG.md +25 -0
- package/DESIGN.md +8 -0
- package/WORK_ORDERS.md +2 -0
- package/dist/auth/apiClient.js +8 -0
- package/dist/components/AccessCodeManager.js +5 -5
- package/dist/components/AccessCodeSingleUseToggle.js +3 -2
- package/dist/components/AllowedEmailDomainsManager.js +3 -2
- package/dist/components/AuthFactorRequirementCard.js +3 -2
- package/dist/components/BulkInviteCsvTab.js +3 -2
- package/dist/components/MFAComponent.js +9 -8
- package/dist/components/MfaLoginComponent.js +7 -6
- package/dist/components/PasskeysComponent.js +7 -6
- package/dist/components/ProfileComponent.js +4 -3
- package/dist/components/QrSignupManager.js +4 -3
- package/dist/components/QrSignupValidityManager.js +3 -2
- package/dist/components/RegistrationMethodsManager.js +3 -2
- package/dist/components/SecurityComponent.js +5 -4
- package/dist/components/SupportRecoveryRequestsTab.js +8 -7
- package/dist/components/UserInviteComponent.js +3 -2
- package/dist/components/UserListComponent.js +7 -6
- package/dist/i18n/authTranslations.js +420 -0
- package/dist/pages/AccountPage.js +3 -2
- package/dist/pages/LoginPage.js +10 -9
- package/dist/pages/PasswordChangePage.js +4 -3
- package/dist/pages/PasswordInvitePage.js +7 -6
- package/dist/pages/PasswordResetRequestPage.js +5 -4
- package/dist/pages/SignUpPage.js +7 -6
- package/dist/pages/SignupConfirmPage.js +7 -6
- package/dist/theme/createAppTheme.js +62 -9
- package/dist/theme/tokens.js +1 -1
- package/dist/utils/auth-errors.js +45 -8
- package/dist/utils/resolveErrorText.js +24 -0
- package/package.json +1 -1
- package/src/auth/apiClient.jsx +9 -0
- package/src/components/AccessCodeManager.jsx +5 -5
- package/src/components/AccessCodeSingleUseToggle.jsx +3 -2
- package/src/components/AllowedEmailDomainsManager.jsx +3 -2
- package/src/components/AuthFactorRequirementCard.jsx +3 -2
- package/src/components/BulkInviteCsvTab.jsx +3 -2
- package/src/components/MFAComponent.jsx +9 -8
- package/src/components/MfaLoginComponent.jsx +7 -6
- package/src/components/PasskeysComponent.jsx +7 -6
- package/src/components/ProfileComponent.jsx +4 -3
- package/src/components/QrSignupManager.jsx +4 -3
- package/src/components/QrSignupValidityManager.jsx +3 -2
- package/src/components/RegistrationMethodsManager.jsx +3 -2
- package/src/components/SecurityComponent.jsx +5 -4
- package/src/components/SupportRecoveryRequestsTab.jsx +9 -8
- package/src/components/UserInviteComponent.jsx +3 -2
- package/src/components/UserListComponent.jsx +7 -6
- package/src/i18n/authTranslations.ts +421 -0
- package/src/pages/AccountPage.jsx +3 -4
- package/src/pages/LoginPage.jsx +10 -9
- package/src/pages/PasswordChangePage.jsx +4 -3
- package/src/pages/PasswordInvitePage.jsx +7 -6
- package/src/pages/PasswordResetRequestPage.jsx +5 -4
- package/src/pages/SignUpPage.jsx +7 -6
- package/src/pages/SignupConfirmPage.jsx +7 -6
- package/src/theme/createAppTheme.js +68 -4
- package/src/theme/tokens.js +1 -1
- package/src/utils/auth-errors.js +44 -7
- package/src/utils/resolveErrorText.js +28 -0
- package/tests/AccountPage.test.jsx +27 -20
- package/tests/PasswordInvitePage.test.jsx +28 -21
- package/tests/QrSignupManager.registrationContext.test.jsx +5 -1
- package/tests/SignUpPage.test.jsx +14 -3
- package/tests/UserListComponent.test.jsx +22 -15
- package/tests/apiClientLanguage.test.js +33 -0
- package/tests/authErrors.test.js +31 -0
- package/tests/createAppThemePageContrast.test.js +108 -0
- package/tests/errorCallSiteGuard.test.js +32 -0
- package/tests/loginPageErrorResolution.test.jsx +102 -0
- package/tests/resolveErrorText.test.js +72 -0
- package/work-orders/UCM-AUTH-10.md +490 -0
- package/work-orders/UCM-THEME-14.md +3 -1
- package/work-orders/UCM-THEME-15.md +356 -0
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
# UCM-THEME-15: Derive status and control-border colours against the theme's own page
|
|
2
|
+
|
|
3
|
+
# A. Envelope, authored by the Expertenchat
|
|
4
|
+
|
|
5
|
+
## Goal & expected outcome
|
|
6
|
+
|
|
7
|
+
`createAppTheme` already derives one colour against the page it will sit on: `controlBorder.focus`
|
|
8
|
+
(`deriveFocusColour`, `src/theme/createAppTheme.js`). Every other page-sensitive baseline colour is
|
|
9
|
+
fixed. So an app or site whose `background.default` is not near-white gets baseline status and
|
|
10
|
+
border colours that fail WCAG on its own page.
|
|
11
|
+
|
|
12
|
+
After this order, the **baseline-supplied** status `main` colours and control-border colours are
|
|
13
|
+
derived the same way, and only when they would otherwise fail. A theme on the baseline's own page
|
|
14
|
+
resolves exactly as today. A theme on a tinted page gets colours that pass on that page.
|
|
15
|
+
`assertThemeComplete` then returns zero contrast findings for every baseline-supplied colour on the
|
|
16
|
+
estate's real site backgrounds.
|
|
17
|
+
|
|
18
|
+
**Operator decision (2026-09-29):** option (a) of three. The factory derives the colours. Contrast
|
|
19
|
+
findings do **not** become exemptible (option b, which would hide real accessibility failures on
|
|
20
|
+
customer sites), and sites do not get their own status colours (option c, new per-customer design
|
|
21
|
+
decisions).
|
|
22
|
+
|
|
23
|
+
## Context the operator established
|
|
24
|
+
|
|
25
|
+
- **What the check measures** (`src/theme/themeCompleteness.js`, `contrastFindings`): for each status
|
|
26
|
+
key, `main` against `#FFFFFF` **and** `background.default` at **4.5:1**; `controlBorder.main/hover/
|
|
27
|
+
focus/error` against the same two surfaces at **3:1**. The pairs `text`-on-`bg` and
|
|
28
|
+
`fillText`-on-`fill` are self-contained and do not depend on the page. Contrast findings are appended
|
|
29
|
+
after exemption filtering (`themeCompleteness.js:371`), so no exemption can cover them. That stays.
|
|
30
|
+
- **The baseline values** (`src/theme/tokens.js`, `BASELINE_PALETTE`): `success.main` `#1B8038`,
|
|
31
|
+
`warning.main` `#976100`, `error.main` `#BF3227`, `info.main` `#01579B` (`stale` has no `main`), each
|
|
32
|
+
built by `withMainShades`. `controlBorder.main` `rgba(33,37,41,.50)`, `.hover` `rgba(33,37,41,.65)`,
|
|
33
|
+
`.error` `#BF3227`. `focus` is derived.
|
|
34
|
+
- **Measured on the estate's real site backgrounds (2026-09-29),** baseline colours only:
|
|
35
|
+
|
|
36
|
+
| Page `background.default` | Fails | Darkening needed (MUI `darken`) |
|
|
37
|
+
|---|---|---|
|
|
38
|
+
| baseline `#FAFAFA` | nothing | none |
|
|
39
|
+
| `hafen` `#D8E4E1` | success 3.84, warning 4.00, error 4.35; border.main 2.91 | 0.10 / 0.08 / 0.03; border alpha 0.52 |
|
|
40
|
+
| `jg-fegbern` `#eceae2` | success 4.16, warning 4.33; border.main 2.98 | 0.05 / 0.03; alpha 0.51 |
|
|
41
|
+
| `routing` `#E8EAF6` | success 4.18, warning 4.35; border.main 2.98 | 0.05 / 0.03; alpha 0.51 |
|
|
42
|
+
| `uph` `#E6EAF0` | success 4.15, warning 4.32; border.main 2.97 | 0.05 / 0.03; alpha 0.51 |
|
|
43
|
+
| `ygbs` `#E398C7` | success 2.29, warning 2.38, error 2.59, info 3.38; border.main 2.50, border.error 2.59 | 0.40 / 0.38 / 0.34 / 0.21; alpha 0.59 |
|
|
44
|
+
|
|
45
|
+
Sites are `survey_app/frontend/src/sites/*/theme.js`; `ygbs` also lives in `survey_contact_app`.
|
|
46
|
+
- **Every adopted app already passes.** All ten apps on `createAppTheme` assert
|
|
47
|
+
`assertThemeComplete(theme).findings` equals `[]`, so none of them has a contrast finding today.
|
|
48
|
+
|
|
49
|
+
## The rule (decided)
|
|
50
|
+
|
|
51
|
+
1. **Scope of derivation:** `success/warning/error/info.main`, and `controlBorder.main/hover/error`.
|
|
52
|
+
The derived `main` also re-derives its `light`/`dark`/`contrastText` through `withMainShades`, so a
|
|
53
|
+
status entry stays internally consistent. `text`, `fill`, `fillText`, `bg` are untouched.
|
|
54
|
+
2. **Only when it fails.** A value that already clears its threshold against both `#FFFFFF` and
|
|
55
|
+
`background.default` is returned **unchanged, byte for byte**. So no theme that passes today moves.
|
|
56
|
+
3. **Only baseline-supplied values.** If the app's own config sets a status `main` or a
|
|
57
|
+
`controlBorder` state, that value is the app's decision. It is never altered, and its finding
|
|
58
|
+
stays.
|
|
59
|
+
4. **Direction and method:** status `main` and `controlBorder.error` are darkened with MUI `darken` in
|
|
60
|
+
the smallest steps that clear the threshold, as `deriveFocusColour` does. `controlBorder.main/hover`
|
|
61
|
+
keep their ink hue and raise their alpha instead, so they stay a translucent ink.
|
|
62
|
+
5. **Same surfaces as the check:** `#FFFFFF` and `background.default`, exactly the two that
|
|
63
|
+
`contrastFindings` uses, just as the existing comment at `deriveFocusColour` insists.
|
|
64
|
+
6. **Unreachable stays visible:** if the maximum step does not clear the threshold, the loop stops,
|
|
65
|
+
returns the last value and the finding remains. It never throws and always terminates. Dark pages
|
|
66
|
+
are `UCM-THEME-14`'s subject, not this order's.
|
|
67
|
+
|
|
68
|
+
## Scope + non-goals
|
|
69
|
+
|
|
70
|
+
In scope: the derivation in `createAppTheme` (next to `deriveFocusColour`, which may be generalised
|
|
71
|
+
into one helper); `DESIGN.md` (one paragraph: which baseline colours are page-derived, and why);
|
|
72
|
+
`CHANGELOG.md`; a **minor** release. It is a changed default, but only for themes that currently fail,
|
|
73
|
+
and none of the adopted apps does.
|
|
74
|
+
|
|
75
|
+
Non-goals:
|
|
76
|
+
- No change to any baseline token value in `tokens.js`.
|
|
77
|
+
- No exemption path for contrast findings.
|
|
78
|
+
- No change to `text`/`fill`/`fillText`/`bg`, to `dataSeries`, or to anything the app sets itself.
|
|
79
|
+
- No dark-mode handling (`UCM-THEME-14`).
|
|
80
|
+
- No consumer change. `survey_app` and `survey_contact_app` adopt it through their own pin bump.
|
|
81
|
+
|
|
82
|
+
## Tier · precondition / gate
|
|
83
|
+
|
|
84
|
+
- **Tier 3 · tests: derivation on the six measured pages, byte-identical output on the baseline page,
|
|
85
|
+
app-supplied values untouched, termination; the kit's existing theme tests.** A change inside
|
|
86
|
+
shared-core.
|
|
87
|
+
- Precondition: none.
|
|
88
|
+
- **Order with `UCM-THEME-14`:** this lands first. Both touch the focus derivation and the surface
|
|
89
|
+
pair, and 14 is waiting on a design round (`UCM-THEME-13`) anyway.
|
|
90
|
+
- Downstream: `survey_app`/`SVA-THEME-1` and `survey_contact_app`/`SCA-THEME-1` (site half) wait for
|
|
91
|
+
this release **and** for their own pin bump.
|
|
92
|
+
|
|
93
|
+
## Risks
|
|
94
|
+
|
|
95
|
+
- **`ygbs` will look different.** Its status colours darken by up to 40 % (success `#1B8038` becomes
|
|
96
|
+
roughly `#104D22`). That is the price of legibility on a mid-pink page. It is visible on the customer
|
|
97
|
+
site, and the operator sees it in `SVA-THEME-1`'s acceptance.
|
|
98
|
+
- **Silent change for a future app on a tinted page.** Expected and intended: the baseline promises a
|
|
99
|
+
passing colour, not a fixed hex. `DESIGN.md` has to say so, or someone will read the hex in
|
|
100
|
+
`tokens.js` and expect it on screen.
|
|
101
|
+
- **Rule 3 needs to know what the app set.** The merged palette alone cannot tell baseline from app
|
|
102
|
+
values. Read the app's config (`appConfig.palette`) before deciding, not the resolved theme.
|
|
103
|
+
- **`light`/`dark` shades shift with a derived `main`.** Components using `error.dark` for hover move
|
|
104
|
+
with it. Correct, but name it in the changelog.
|
|
105
|
+
|
|
106
|
+
## Required tests to WRITE (you write them and run YOUR OWN new ones; the Orchestrator's run is the gate)
|
|
107
|
+
|
|
108
|
+
Kit test suite, next to the existing theme tests:
|
|
109
|
+
|
|
110
|
+
1. **Six measured pages.** For each `background.default` in the table, with only `palette.primary`
|
|
111
|
+
given, `assertThemeComplete` has no `contrast.*.main-on-*` and no `contrast.controlBorder.*`
|
|
112
|
+
finding.
|
|
113
|
+
2. **No movement where nothing fails.** On the baseline page, the resolved status entries and
|
|
114
|
+
`controlBorder` equal today's values exactly. Show that the test fails when one baseline hex is
|
|
115
|
+
altered by hand.
|
|
116
|
+
3. **App values are untouched.** A config that sets `success.main` to a failing colour on a tinted
|
|
117
|
+
page keeps exactly that colour, and the finding remains.
|
|
118
|
+
4. **Termination.** A page on which the maximum step cannot reach the threshold (a mid-grey such as
|
|
119
|
+
`#767676`) returns without throwing and keeps the finding.
|
|
120
|
+
|
|
121
|
+
Run, do not write: the kit's existing theme and `kitSxRegistry` tests.
|
|
122
|
+
|
|
123
|
+
## Parity guardrail
|
|
124
|
+
|
|
125
|
+
No change for any theme that passes today. No API change, no new option.
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
# B. Implementation map, filled by the Orchestrator and ADDRESSED TO THE IMPLEMENTER
|
|
130
|
+
|
|
131
|
+
## Context package
|
|
132
|
+
|
|
133
|
+
### Current shape (`src/theme/createAppTheme.js`, read in full before editing)
|
|
134
|
+
|
|
135
|
+
- `clearsContrast(colour, surfaces)` (line ~39): `surfaces.every((s) => getContrastRatio(colour, s) >= 3)`
|
|
136
|
+
— hardcoded to 3:1 and to MUI's own `getContrastRatio`. Used ONLY by `deriveFocusColour`.
|
|
137
|
+
- `deriveFocusColour(primary, surfaces)` (line ~47): darkens in `0.05` steps (MUI `darken`), capped at
|
|
138
|
+
coefficient `0.9`, stops and returns the last value if unreached (never throws).
|
|
139
|
+
- `createAppTheme(appConfig)` (line ~170): throws if `!appConfig?.palette?.primary`. Builds
|
|
140
|
+
`paletteTheme = createTheme({palette: appConfig.palette}, {palette: BASELINE_PALETTE}, {palette:
|
|
141
|
+
appConfig.palette})` (app layered, then baseline, then app again — this is why an app-set value
|
|
142
|
+
always wins in `finalPalette`, and why a value present in `BASELINE_PALETTE` but absent from
|
|
143
|
+
`appConfig.palette` is a baseline value). `finalPalette = paletteTheme.palette`. Then:
|
|
144
|
+
```js
|
|
145
|
+
const computedPalette = {
|
|
146
|
+
controlBorder: {
|
|
147
|
+
...finalPalette.controlBorder,
|
|
148
|
+
focus: deriveFocusColour(finalPalette.primary.main, ['#FFFFFF', finalPalette.background.default]),
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
const resolvedPalette = { ...finalPalette, ...computedPalette };
|
|
152
|
+
```
|
|
153
|
+
This is the exact seam this WO extends — do not restructure the surrounding merge order,
|
|
154
|
+
`validateStyleOverrides`, or the final `createTheme(...)` call's argument layering.
|
|
155
|
+
|
|
156
|
+
### Do NOT touch `clearsContrast`/`deriveFocusColour` — add parallel, separate logic instead
|
|
157
|
+
|
|
158
|
+
- The byte-identical-on-baseline-page requirement (test #2) and "no change to anything not
|
|
159
|
+
described here" mean the existing focus derivation must keep behaving exactly as it does today.
|
|
160
|
+
Do not generalise `clearsContrast` into a shared threshold-aware helper by EDITING it in place —
|
|
161
|
+
add new, separate functions for this WO's derivations. (You may factor out a shared shape later if
|
|
162
|
+
you can prove byte-for-byte equivalence, but the safe, required path is: leave the existing two
|
|
163
|
+
functions untouched, add new ones alongside.)
|
|
164
|
+
- **Use `calculateContrastRatio` from `./themeCompleteness`** (already exported, already used by
|
|
165
|
+
`contrastFindings` — the same function the check itself uses) for every NEW contrast test in this
|
|
166
|
+
WO, not MUI's `getContrastRatio`. This matters concretely for `controlBorder.main`/`.hover`: their
|
|
167
|
+
baseline values are translucent (`rgba(33,37,41,.50)`), and `calculateContrastRatio` alpha-composites
|
|
168
|
+
over the background before computing luminance (see its own docstring) — `getContrastRatio` is not
|
|
169
|
+
documented to do this. Using the wrong function here would silently pick the wrong alpha.
|
|
170
|
+
|
|
171
|
+
### The two derivation shapes (rule 4)
|
|
172
|
+
|
|
173
|
+
1. **Darken-based** (status `main` for success/warning/error/info; `controlBorder.error`): same
|
|
174
|
+
step/cap shape as `deriveFocusColour` (start `coefficient = 0.05`, `darken(original, min(coefficient,
|
|
175
|
+
0.9))`, step `+0.05`, stop and return the last value if `coefficient >= 0.9` without clearing) but
|
|
176
|
+
parameterised by threshold (`4.5` for status `main`, `3` for `controlBorder.error`) and using
|
|
177
|
+
`calculateContrastRatio`.
|
|
178
|
+
2. **Alpha-raising** (`controlBorder.main`, `.hover`): the baseline values are the fixed shape
|
|
179
|
+
`rgba(33, 37, 41, <alpha>)` (`.50` and `.65`). Parse `r`,`g`,`b`,`a` (a small regex is enough — this
|
|
180
|
+
path is NEVER invoked on an app-supplied value, see rule 3 below, so it never needs to handle an
|
|
181
|
+
arbitrary CSS colour format). Step `alpha` up by `0.05`, capped at `1`, producing
|
|
182
|
+
`rgba(${r}, ${g}, ${b}, ${alpha})` each step, stopping when `calculateContrastRatio` clears `3`
|
|
183
|
+
against both surfaces or `alpha` reaches `1` (never throws; if unreached at `alpha = 1`, return that
|
|
184
|
+
last candidate and the finding remains — same "give up, stay visible" contract as the darken path).
|
|
185
|
+
|
|
186
|
+
### Rule 3 — baseline vs app-supplied, decided from `appConfig.palette`, not the resolved theme
|
|
187
|
+
|
|
188
|
+
Before deriving anything, for each of the 6 targets check the RAW input, not `finalPalette`:
|
|
189
|
+
- Status keys: `appConfig.palette?.[statusKey]?.main !== undefined` → app-supplied, skip entirely
|
|
190
|
+
(leave `finalPalette[statusKey]` exactly as merged, findings stay if any).
|
|
191
|
+
- `controlBorder.main`/`.hover`/`.error`: `appConfig.palette?.controlBorder?.[state] !== undefined` →
|
|
192
|
+
app-supplied, skip.
|
|
193
|
+
- Only when NOT app-supplied: read the value off `finalPalette` (the merged/baseline value), check it
|
|
194
|
+
against `['#FFFFFF', finalPalette.background.default]` at the relevant threshold, and derive only if
|
|
195
|
+
it fails. **A value that already clears both thresholds is returned unchanged, byte for byte** — do
|
|
196
|
+
not run it through `darken`/alpha-step at all if it already passes (this is what keeps the baseline
|
|
197
|
+
page byte-identical: on `#FAFAFA`, everything already clears, so nothing here ever fires).
|
|
198
|
+
|
|
199
|
+
### Re-deriving a status entry consistently (rule 1)
|
|
200
|
+
|
|
201
|
+
When a status `main` is derived, rebuild that status's `main`/`light`/`dark`/`contrastText` via the
|
|
202
|
+
existing `withMainShades` (`src/theme/tokens.js`, already exported — import it, or re-derive the same
|
|
203
|
+
shades inline using `lighten`/`darken` exactly as `withMainShades` does; prefer importing the real
|
|
204
|
+
function over duplicating its formula) called as `withMainShades(derivedMain, existingContrastText)`
|
|
205
|
+
— `existingContrastText` is `finalPalette[statusKey].contrastText`, unchanged (every baseline status
|
|
206
|
+
already carries `'#FFFFFF'`; do not hardcode `'#FFFFFF'` here, read it off the entry so an
|
|
207
|
+
app-overridden `contrastText` on an otherwise-baseline `main` is respected). `text`/`fill`/`fillText`/
|
|
208
|
+
`bg` on that same status object are spread through unchanged — merge shape:
|
|
209
|
+
`{ ...finalPalette[statusKey], ...withMainShades(derivedMain, finalPalette[statusKey].contrastText) }`.
|
|
210
|
+
|
|
211
|
+
### Wiring it into `createAppTheme`
|
|
212
|
+
|
|
213
|
+
Extend the existing `computedPalette` object (do not add a second, separate merge step) so
|
|
214
|
+
`resolvedPalette` picks up both the existing `controlBorder.focus` line and the new derivations, e.g.
|
|
215
|
+
(shape, not literal code to paste — adapt to how you factor the helper functions):
|
|
216
|
+
```js
|
|
217
|
+
const computedPalette = {
|
|
218
|
+
...derivedStatusEntries, // { success?, warning?, error?, info? } — only keys that were derived
|
|
219
|
+
controlBorder: {
|
|
220
|
+
...finalPalette.controlBorder,
|
|
221
|
+
...derivedControlBorderEntries, // { main?, hover?, error? } — only keys that were derived
|
|
222
|
+
focus: deriveFocusColour(finalPalette.primary.main, ['#FFFFFF', finalPalette.background.default]),
|
|
223
|
+
},
|
|
224
|
+
};
|
|
225
|
+
```
|
|
226
|
+
`stale` has no `main` (`tokens.js`) — `contrastFindings` already skips it via `if (!main) continue;`;
|
|
227
|
+
do not add derivation logic for `stale`.
|
|
228
|
+
|
|
229
|
+
### Test file
|
|
230
|
+
|
|
231
|
+
New file next to the existing theme tests (`tests/createAppTheme.test.js` already exists — read it
|
|
232
|
+
first; extend it if its structure fits, or add `tests/createAppThemePageContrast.test.js`).
|
|
233
|
+
Use the six real background values from the Envelope's table directly (already verified against
|
|
234
|
+
`survey_app/frontend/src/sites/*/theme.js` — `hafen` `#D8E4E1`, `jg-fegbern` `#eceae2`, `routing`
|
|
235
|
+
`#E8EAF6`, `uph` `#E6EAF0`, `ygbs` `#E398C7`, plus the baseline `#FAFAFA`). Any valid `primary.main`
|
|
236
|
+
works for these (the check is independent of primary) — reuse the kit's own test convention, e.g.
|
|
237
|
+
`'#0F62FE'` (see `tests/StatTile.test.jsx`).
|
|
238
|
+
|
|
239
|
+
1. For each of the 6 pages: `createAppTheme({ palette: { primary: { main: '#0F62FE' }, background: {
|
|
240
|
+
default: <page hex> } } })`, then `assertThemeComplete(theme).findings` has no entry whose
|
|
241
|
+
`surface` starts with `contrast.` for any status `main-on-*` or any `contrast.controlBorder.*`
|
|
242
|
+
(other, unrelated findings — e.g. undeclared surfaces this WO doesn't touch — are not this test's
|
|
243
|
+
concern; assert on the specific surface-name prefixes, not on the whole array being empty, unless
|
|
244
|
+
this repo's fixture already guarantees a clean baseline call is fully empty — check
|
|
245
|
+
`tests/createAppTheme.test.js`/`tests/themeCompleteness.test.js` for the existing convention here).
|
|
246
|
+
2. On the baseline page (`background.default` left at its `BASELINE_STATIC`/`BASELINE_PALETTE`
|
|
247
|
+
default, i.e. no override given), `theme.palette.success.main` (and `.light`/`.dark`/
|
|
248
|
+
`.contrastText`), `.warning.main`, `.error.main`, `.info.main`, `.controlBorder.main`, `.hover`,
|
|
249
|
+
`.error` all equal today's exact `BASELINE_PALETTE`/derived values (read them from `tokens.js`,
|
|
250
|
+
do not hand-copy hexes into the test — import `BASELINE_PALETTE` and compare against it directly
|
|
251
|
+
so the test can't drift from the source of truth). Then, per the Envelope's own instruction, show
|
|
252
|
+
this test actually pins something: temporarily hand-edit one baseline hex in a throwaway local copy
|
|
253
|
+
(or otherwise demonstrate) that the assertion fails if the value moved — narrate how you did this
|
|
254
|
+
in your `PROGRESS` line rather than leaving a permanently-broken toggle in the committed test.
|
|
255
|
+
3. A config that sets e.g. `palette.success.main` to a colour that fails on a tinted page (construct
|
|
256
|
+
one deliberately, e.g. a light green on `ygbs`'s pink) keeps EXACTLY that colour after
|
|
257
|
+
`createAppTheme` (`theme.palette.success.main === appConfig.palette.success.main`), and
|
|
258
|
+
`assertThemeComplete` still reports that specific `contrast.success.main-on-*` finding (proving
|
|
259
|
+
rule 3's app-supplied values are never touched AND their findings are never silently hidden).
|
|
260
|
+
4. `createAppTheme({ palette: { primary: { main: '#0F62FE' }, background: { default: '#767676' } } })`
|
|
261
|
+
does not throw, returns a defined `theme.palette.success.main` (etc.), and
|
|
262
|
+
`assertThemeComplete(theme).findings` still contains the relevant `contrast.*` entries (mid-grey
|
|
263
|
+
`#767676` is deliberately unreachable within the darken cap — confirm this numerically before
|
|
264
|
+
relying on it: `calculateContrastRatio('#FFFFFF', '#767676')` and the darkened-`main` variants
|
|
265
|
+
should stay below `4.5` even at the `0.9` darken cap; if your arithmetic says otherwise, pick
|
|
266
|
+
another representative unreachable grey and say which and why in `PROGRESS`).
|
|
267
|
+
|
|
268
|
+
Run, do not write: `tests/createAppTheme.test.js` (existing) and `tests/themeCompleteness.test.js`
|
|
269
|
+
(existing) as the directly-dependent regression set — your own focused run only, the Orchestrator
|
|
270
|
+
re-runs both as the gate.
|
|
271
|
+
|
|
272
|
+
### `DESIGN.md` and `CHANGELOG.md`
|
|
273
|
+
|
|
274
|
+
- `DESIGN.md`: one paragraph — which baseline colours are page-derived (`success`/`warning`/`error`/
|
|
275
|
+
`info.main`, `controlBorder.main`/`.hover`/`.error`, alongside the existing `controlBorder.focus`)
|
|
276
|
+
and why (a fixed hex cannot promise WCAG on every page background; the baseline promises a passing
|
|
277
|
+
colour, not a fixed one — state this explicitly so a future reader doesn't expect the `tokens.js`
|
|
278
|
+
hex to appear unmodified on every site). If `DESIGN.md` does not exist at the repo root, check for
|
|
279
|
+
an equivalent doc (grep for "baseline" or "token" in root `*.md` files) before creating a new one.
|
|
280
|
+
- `CHANGELOG.md`: entry naming the changed default (derived colours are possible now, "byte identical
|
|
281
|
+
when passing today"), and explicitly naming that `light`/`dark` shades move WITH a derived `main`
|
|
282
|
+
(Risk note: a component styled with `error.dark` for hover moves too).
|
|
283
|
+
|
|
284
|
+
## Do-not-touch / invariants
|
|
285
|
+
|
|
286
|
+
- No change to any literal in `BASELINE_PALETTE`/`BASELINE_STATIC` (`tokens.js`).
|
|
287
|
+
- No exemption path added anywhere in `themeCompleteness.js`.
|
|
288
|
+
- `text`/`fill`/`fillText`/`bg` on every status object, `dataSeries`, and anything the app sets itself
|
|
289
|
+
(beyond the 6 targets named here) are untouched.
|
|
290
|
+
- No dark-mode handling (`UCM-THEME-14`'s subject).
|
|
291
|
+
- Do not touch `survey_app`/`survey_contact_app` — this WO is entirely inside `ui-core-micha`.
|
|
292
|
+
|
|
293
|
+
## Target repo working directory (absolute)
|
|
294
|
+
|
|
295
|
+
`C:\Users\biglmi\Documents\webapps\ui-core-micha` (branch `main`)
|
|
296
|
+
|
|
297
|
+
## Preamble — a REQUIRED block IN this file, not something appended at invocation
|
|
298
|
+
|
|
299
|
+
> The text above is the COMPLETE spec — the committed WO file's content, not a plan to refine; there
|
|
300
|
+
> is no separate plan file. Read the nearest `AGENTS.md`, the relevant `.codex/skills/<role>/SKILL.md`, and the
|
|
301
|
+
> app `MEMORY.md` ONLY for conventions. Stay in scope; do not touch auth/permissions/deps/schema/CI
|
|
302
|
+
> unless the spec says so; do not update `MEMORY.md`. **Do NOT edit `WORK_ORDERS.md` — the register
|
|
303
|
+
> row and the review verdicts are the orchestrator's alone.** **Your tools are for editing source
|
|
304
|
+
> and test files (and `DESIGN.md`/`CHANGELOG.md` as named above) and for running the tests you wrote
|
|
305
|
+
> — nothing else.** Do NOT install dependencies, touch a lockfile, run a package manager, or tidy up
|
|
306
|
+
> stray files; if something in the repo state blocks you, stop and report it as
|
|
307
|
+
> `RESULT: BLOCKED <reason>` instead of fixing it. Do NOT `git add`/`commit`/`push` — leave every
|
|
308
|
+
> change uncommitted in the working tree for the orchestrator's independent review. WRITE the tests
|
|
309
|
+
> the `Required tests` section calls for AND **RUN the tests you just wrote** to confirm they execute
|
|
310
|
+
> and pass — that is the ONLY test run you do (NOT the app's affected/full suite, NOT any review).
|
|
311
|
+
> The orchestrator re-runs the authoritative set + does the independent review after you finish —
|
|
312
|
+
> those are the gate; your own run does not count as the gate.
|
|
313
|
+
>
|
|
314
|
+
> Narrate continuously: a `PLAN: <step1> | <step2> | …` line up front, then a single-line
|
|
315
|
+
> `PROGRESS: [<n>/<total>] <present-tense action>` before every relevant action (and `… done` on
|
|
316
|
+
> completion), spaced so no gap exceeds ~2 min, stdout unbuffered, plus exactly one final
|
|
317
|
+
> `RESULT: DONE|BLOCKED <reason>`.
|
|
318
|
+
|
|
319
|
+
---
|
|
320
|
+
|
|
321
|
+
# C. Orchestrator only, NOT ADDRESSED TO THE IMPLEMENTER
|
|
322
|
+
|
|
323
|
+
> **If you are the implementer reading this work order as your own specification: STOP at this line.**
|
|
324
|
+
> Everything below describes what the Orchestrator does AFTER you finish. You do none of it: no
|
|
325
|
+
> reviewers, no verification run, no register edit, no `git add`/`commit`/`push`.
|
|
326
|
+
|
|
327
|
+
### Execution directive
|
|
328
|
+
|
|
329
|
+
Check `.claude/codex-status.md` first (newest-first; read it with `head`). No line for today means use
|
|
330
|
+
Codex. Before `git push`, run `git log origin/main..HEAD` and push only if every listed commit is this
|
|
331
|
+
order's (parallel sessions share this checkout).
|
|
332
|
+
|
|
333
|
+
### Review routing
|
|
334
|
+
|
|
335
|
+
Tier 3: independent `reviewer` (all configured lenses) and `ui_reviewer`, concurrent, one batch,
|
|
336
|
+
before the commit. No `sec_reviewer`. Ask `ui_reviewer` specifically about rule 3: can a
|
|
337
|
+
baseline-supplied value be told apart from an app-supplied one in every merge path?
|
|
338
|
+
|
|
339
|
+
### Verification
|
|
340
|
+
|
|
341
|
+
The contrast values in the Envelope table are the acceptance data: re-derive them from the built
|
|
342
|
+
themes and put the derived hex per site in the register Notiz. No prototype is in scope. The visual
|
|
343
|
+
consequence is accepted per site in `SVA-THEME-1`, not here.
|
|
344
|
+
|
|
345
|
+
### Register + commit + release
|
|
346
|
+
|
|
347
|
+
Row -> `done` with the review Notiz in the `AGENTS.md` shape and the published version. The publish
|
|
348
|
+
runs on push to `main` when `package.json`/`src/**` change (`.github/workflows/publish.yml`). Then add
|
|
349
|
+
`planned` pin-bump rows in `survey_app/WORK_ORDERS.md` and `survey_contact_app/WORK_ORDERS.md` (from
|
|
350
|
+
`2.41.3`; classify each by the dependency-bump test in `AGENTS.md` -> Tiering, reading the changelog
|
|
351
|
+
from `2.41.3` on) and unblock `SVA-THEME-1` / `SCA-THEME-1` against them.
|
|
352
|
+
|
|
353
|
+
### Mini-handover
|
|
354
|
+
|
|
355
|
+
`Orchestrator: implement work-orders/UCM-THEME-15.md in ui-core-micha (main). git pull first, read the
|
|
356
|
+
WO, then follow orchestrate-codex.`
|