@micha.bigler/ui-core-micha 2.29.2 → 2.30.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/DESIGN.md +5 -5
- package/WORK_ORDERS.md +5 -0
- package/dev/entries.jsx +130 -1
- package/dev/main.jsx +10 -3
- package/dist/components/charts/palette.js +7 -3
- package/dist/index.js +2 -0
- package/dist/theme/createAppTheme.js +168 -0
- package/dist/theme/index.js +2 -0
- package/dist/theme/themeCompleteness.js +320 -0
- package/dist/theme/tokens.js +325 -0
- package/package.json +4 -1
- package/src/components/charts/palette.js +6 -3
- package/src/index.js +9 -0
- package/src/theme/createAppTheme.js +212 -0
- package/src/theme/index.js +7 -0
- package/src/theme/themeCompleteness.js +355 -0
- package/src/theme/tokens.js +376 -0
- package/tests/chartsPalette.test.js +40 -0
- package/tests/createAppTheme.test.js +103 -0
- package/tests/themeCompleteness.test.js +63 -0
- package/work-orders/CHART-6.md +230 -0
- package/work-orders/SHELL-2.md +197 -0
- package/work-orders/THEME-1.md +587 -0
- package/work-orders/assets/SHELL-2-account-navigation.html +1159 -0
- package/work-orders/assets/THEME-1-baseline-reference-sheet.html +1352 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
# CHART-6 — Chart-chrome defaults in the three wrappers, plus the two generic fixes hoisted up
|
|
2
|
+
|
|
3
|
+
**Target repo:** `ui-core-micha` (branch `main` — this repo has no `develop`)
|
|
4
|
+
**Tier:** 2 (shared core — `ui-core-micha` is named in AGENTS.md's Tier-2 forcing list)
|
|
5
|
+
**Review:** independent `reviewer` (Sonnet, full) **and** `ui_reviewer` — spawned concurrently, both mandatory before commit
|
|
6
|
+
**Decision record:** `webapp-management/DESIGN_SYSTEM_PROGRAM.md` (DS-4), plus the operator decisions of 2026-08-10 recorded below
|
|
7
|
+
**Version target:** `2.31.0`
|
|
8
|
+
**Hard precondition:** **THEME-1 must have landed and published as `2.30.0` first** — see Preconditions
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## A. Envelope
|
|
13
|
+
|
|
14
|
+
### Goal
|
|
15
|
+
|
|
16
|
+
Close the chart-chrome default surface in ucm's three chart wrappers, resolve
|
|
17
|
+
`TimeSeriesChart`'s double legend, and hoist the two generic fixes that live in a consuming app
|
|
18
|
+
today up into the kit.
|
|
19
|
+
|
|
20
|
+
Expected outcome: a chart rendered through the kit looks deliberate without the caller setting
|
|
21
|
+
anything, and the four props that today decide whether it looks deliberate stop being the
|
|
22
|
+
caller's problem.
|
|
23
|
+
|
|
24
|
+
### Why
|
|
25
|
+
|
|
26
|
+
Both hram and fitness-monitor import the **same** thin wrappers. There is no app-local chart
|
|
27
|
+
module to promote — the quality gap is **three unset props plus one library default**. The
|
|
28
|
+
wrappers are roughly 18 lines of logic each: they enforce axis labels, apply a `hideLegend`
|
|
29
|
+
heuristic, force axis-trigger tooltips, and supply a palette fallback. Tick font, grid, margins
|
|
30
|
+
and marker styling are simply absent, so MUI X-Charts' own visual language shows through
|
|
31
|
+
untouched.
|
|
32
|
+
|
|
33
|
+
### Measured evidence (2026-08-08/09, verified)
|
|
34
|
+
|
|
35
|
+
- **Hand discipline fails even in the exemplar.** hram sets `tickLabelStyle.fontSize` at only
|
|
36
|
+
**4 of 9** wrapper call sites — absent in `ExpertTimelinePanel`, `ReferralMetricsPanel`,
|
|
37
|
+
`ResultsTimelinePanel`, `TotalMetricsPanel` and `GenericAnalysisPlot`'s top chart. This is the
|
|
38
|
+
argument for defaults at the chokepoint rather than a convention.
|
|
39
|
+
- **The hollow-marker default is a library value, verified in source:**
|
|
40
|
+
`@mui/x-charts/LineChart/MarkElement.js:27` sets `fill: theme.palette.background.paper` — a
|
|
41
|
+
white-filled marker with a coloured stroke. hram avoids it by disabling markers globally;
|
|
42
|
+
fitness-monitor turns them on and inherits it.
|
|
43
|
+
- **`TimeSeriesChart` renders a double legend** whenever ≥2 series are visible: its toolbar toggle
|
|
44
|
+
row names and toggles the series, and MUI draws its own legend underneath.
|
|
45
|
+
- **The label-collapse workaround has two independent consumers** — fitness-monitor's
|
|
46
|
+
`utils/chartLabels.js` (36 lines) and jg-ferien's `ActivitySection.jsx:10-16`, which documents
|
|
47
|
+
the identical upstream issue. The promotion test is met.
|
|
48
|
+
- **`TeachingRunDetailPanel` (hram) bypasses the wrapper entirely** with a direct
|
|
49
|
+
`@mui/x-charts` import — noted, not fixed here.
|
|
50
|
+
- **X-Charts `8.28.2` API limits, both verified in the installed types:** the legend's `position`
|
|
51
|
+
is a 3×3 grid (`vertical: top|middle|bottom` × `horizontal: start|center|end`) and participates
|
|
52
|
+
in layout — it reserves space rather than overlaying the plot. `skipAnimation` is a boolean per
|
|
53
|
+
chart with **no separate enter/update control**.
|
|
54
|
+
|
|
55
|
+
### Scope
|
|
56
|
+
|
|
57
|
+
#### A. Prop defaults in all three wrappers
|
|
58
|
+
|
|
59
|
+
Applies to `LineChart`, `BarChart` **and `TimeSeriesChart`** — the third was overlooked when this
|
|
60
|
+
strand was first written and is the kit's most mature preset.
|
|
61
|
+
|
|
62
|
+
| Default | Value | Replaces |
|
|
63
|
+
|---|---|---|
|
|
64
|
+
| `tickLabelStyle.fontSize` | the baseline's tick size | MUI's ~12px, unstyled |
|
|
65
|
+
| `showMark` | `false` | MUI's `true` |
|
|
66
|
+
| marker style, when a caller opts markers back in | **filled** | MUI's hollow `background.paper` fill |
|
|
67
|
+
| `grid` | `{ horizontal: true }` | MUI's default of no grid at all |
|
|
68
|
+
| bottom margin | increased automatically when tick labels are rotated | hand-tuned per call site, or forgotten |
|
|
69
|
+
| `legendPosition` | **new prop** over MUI's nine positions; default **bottom-start**, filled marks, aligned to `ChartFrame`'s `p: 2` | MUI's centered-top with dash marks |
|
|
70
|
+
|
|
71
|
+
**Every one of these is a default, not an override.** A prop set by the caller always wins — hram's
|
|
72
|
+
four tuned `tickLabelStyle` call sites must render exactly as they do today.
|
|
73
|
+
|
|
74
|
+
#### B. `TimeSeriesChart`'s double legend
|
|
75
|
+
|
|
76
|
+
Where the toolbar toggle row is present, **MUI's legend is switched off and the toolbar becomes
|
|
77
|
+
the legend** (filled colour dots on the toggles). A row that both names and toggles the series is
|
|
78
|
+
the more capable of the two, and two elements saying the same thing is one too many.
|
|
79
|
+
|
|
80
|
+
The toggles must keep their accessible names — this must not become a row of unlabelled controls
|
|
81
|
+
for keyboard or screen-reader users.
|
|
82
|
+
|
|
83
|
+
#### C. Motion
|
|
84
|
+
|
|
85
|
+
- **Charts animate on entry** (operator, 2026-08-10), at the baseline's `motion.chart` duration.
|
|
86
|
+
- **Charts that poll or re-render on a timer get `skipAnimation`** — named concretely: cockpit's
|
|
87
|
+
status board and hram's `CampaignMonitorPanel`. There the animation replays on every refresh and
|
|
88
|
+
the value never comes to rest.
|
|
89
|
+
- Because `skipAnimation` is all-or-nothing in 8.28.2, those two also lose their entry animation.
|
|
90
|
+
**A library limit, documented as such — not a design choice.**
|
|
91
|
+
|
|
92
|
+
#### D. The two hoists from fitness-monitor
|
|
93
|
+
|
|
94
|
+
- **`chartLabels.js`** — the documented X-Charts label-collapse workaround, 36 lines. Two
|
|
95
|
+
independent consumers already, so this is a promotion and not a bet.
|
|
96
|
+
- **`yearTickInterval`** — hoisted **on its own merits**, not by association: it currently lives
|
|
97
|
+
page-locally in fitness-monitor's `BodyHistoryPage` and is **not** part of `chartLabels.js`. The
|
|
98
|
+
duplicated-year-tick defect it fixes hits any dense time axis, so it belongs to every
|
|
99
|
+
`TimeSeriesChart` consumer. Ships as **its own export**, not mixed into `chartLabels.js`.
|
|
100
|
+
|
|
101
|
+
Both apps keep working unchanged. **Removing their local copies is not part of this WO** — that
|
|
102
|
+
happens in each app's own adoption WO, so this one stays reviewable in a single repo.
|
|
103
|
+
|
|
104
|
+
#### E. Version
|
|
105
|
+
|
|
106
|
+
`2.31.0` — minor: new props and new exports, nothing removed.
|
|
107
|
+
|
|
108
|
+
### Non-goals / do not touch
|
|
109
|
+
|
|
110
|
+
- **The legend auto-placement heuristic.** The operator's idea — detect whether the top-right or
|
|
111
|
+
bottom-right corner of the plot is empty and place the legend there, otherwise below — is good,
|
|
112
|
+
but X-Charts' built-in legend reserves layout space instead of overlaying, so a true in-plot
|
|
113
|
+
legend needs a custom overlay. **Own strand, once someone has verified that path.** Deliberately
|
|
114
|
+
kept out of a Tier-2 shared-core WO in a repo with no staging net, so this one stays fully
|
|
115
|
+
verifiable.
|
|
116
|
+
- **The theme factory** — THEME-1.
|
|
117
|
+
- **`useNeutralChartPalette`'s ramp transition** — owned by THEME-1 (decision 22). This WO must not
|
|
118
|
+
touch `charts/palette.js`.
|
|
119
|
+
- **The six hand-drawn hram plots** — DS-7/8/9.
|
|
120
|
+
- **`TeachingRunDetailPanel`'s wrapper bypass** — its own small WO in hram.
|
|
121
|
+
- **Removing the hoisted helpers' local copies** in fitness-monitor and jg-ferien.
|
|
122
|
+
- **Per-app adoption** of any of this.
|
|
123
|
+
- **No behaviour, permission or data-contract change.**
|
|
124
|
+
|
|
125
|
+
### Replaces / removes
|
|
126
|
+
|
|
127
|
+
1. MUI's **hollow marker** as the effective marker whenever a caller opts markers in.
|
|
128
|
+
2. MUI's **centered-top legend with dash marks** as the effective default.
|
|
129
|
+
3. **`TimeSeriesChart`'s second legend** — the MUI one, where the toolbar row exists.
|
|
130
|
+
4. Nothing is removed from the public API. No export disappears, no prop is dropped.
|
|
131
|
+
|
|
132
|
+
### Deliberately keeps
|
|
133
|
+
|
|
134
|
+
- **The `hideLegend={series.length <= 1}` heuristic.** A one-series legend is noise, and giving the
|
|
135
|
+
legend a good position does not change that.
|
|
136
|
+
- **The required `xAxisLabel` / `yAxisLabel` contract** — the wrappers throw without them, and that
|
|
137
|
+
is already generic and correct.
|
|
138
|
+
- **The `palette=` convention** and the wrapper's palette fallback.
|
|
139
|
+
- **`ChartFrame`'s `p: 2` padding and its foot row**, byte-for-byte — 16 panels depend on them.
|
|
140
|
+
- **Every caller's explicit props.** A set prop beats a default; hram's four tuned call sites must
|
|
141
|
+
be visually unchanged.
|
|
142
|
+
|
|
143
|
+
### Risks
|
|
144
|
+
|
|
145
|
+
- **Adding a default is a behaviour change for every call site that did not set the prop**, and the
|
|
146
|
+
effect is deliberately uneven: hram's four tuned `tickLabelStyle` sites stay identical, the other
|
|
147
|
+
five change. That is the intended fix, but it means the rendered check must look at more than one
|
|
148
|
+
chart.
|
|
149
|
+
- **No staging net.** A push to `main` touching `src/**` publishes to npm at once; the independent
|
|
150
|
+
review is the only gate and is not back-fillable.
|
|
151
|
+
- **Ordering against THEME-1 is hard, not preferential.** Both WOs touch `src/index.js` and
|
|
152
|
+
`src/components/charts/`, and decision 23 fixes the release order. Two implementers in this repo
|
|
153
|
+
at once would race the version bump or ship half of this WO inside `2.30.0`.
|
|
154
|
+
- **A chart-preset bump deserves the same treatment as a theme bump.** Decision 11's argument
|
|
155
|
+
applies unchanged: the consumer's next pin bump is a visual verification event, not a dependency
|
|
156
|
+
line.
|
|
157
|
+
- **`skipAnimation` is all-or-nothing**, so the two polling charts lose entry animation too.
|
|
158
|
+
|
|
159
|
+
### Required tests to WRITE
|
|
160
|
+
|
|
161
|
+
Narrow per AGENTS.md → "Test scope". Written as part of implementation; **run only by the
|
|
162
|
+
Orchestrator**.
|
|
163
|
+
|
|
164
|
+
**`tests/chartDefaults.test.js`** (new, parametrised across `LineChart` / `BarChart` /
|
|
165
|
+
`TimeSeriesChart` where applicable)
|
|
166
|
+
|
|
167
|
+
1. A minimal call receives the tick font size, `grid: { horizontal: true }` and `showMark: false`.
|
|
168
|
+
2. A caller-set prop **beats** the default — asserted separately for each of tick font, grid,
|
|
169
|
+
`showMark` and `legendPosition`.
|
|
170
|
+
3. Rotated tick labels increase the bottom margin; unrotated labels do not.
|
|
171
|
+
4. When a caller opts markers back in, the marker renders **filled** — not MUI's
|
|
172
|
+
`background.paper` hollow default.
|
|
173
|
+
5. `legendPosition` defaults to bottom-start, and a caller-set position wins.
|
|
174
|
+
|
|
175
|
+
**`tests/timeSeriesLegend.test.js`** (new)
|
|
176
|
+
|
|
177
|
+
6. With the toolbar row present and ≥2 series, MUI's legend is **absent** — assert the absence
|
|
178
|
+
explicitly, this is a removal.
|
|
179
|
+
7. The toolbar toggles keep an accessible name.
|
|
180
|
+
8. With a single series, `hideLegend` still applies.
|
|
181
|
+
|
|
182
|
+
**`tests/chartLabels.test.js`** (new, ported with the hoist)
|
|
183
|
+
|
|
184
|
+
9. The label-collapse workaround behaves as it does in fitness-monitor today — port its existing
|
|
185
|
+
cases rather than inventing new ones.
|
|
186
|
+
10. `yearTickInterval` produces one tick per year across a dense multi-year range. **Prove
|
|
187
|
+
non-vacuity** by removing it and confirming duplicate year labels reappear — this is the
|
|
188
|
+
defect that started the whole investigation.
|
|
189
|
+
|
|
190
|
+
Plus `tsc -p tsconfig.build.json --noEmit` clean.
|
|
191
|
+
|
|
192
|
+
**No full-suite run.** The affected-areas set is the three new files plus the existing
|
|
193
|
+
`tests/chartsPalette.test.js` if the hoist touches it.
|
|
194
|
+
|
|
195
|
+
### Verification
|
|
196
|
+
|
|
197
|
+
- The **harness page** from THEME-1 gains chart specimens, so the defaults get a rendered
|
|
198
|
+
acceptance rather than only assertions.
|
|
199
|
+
- The Orchestrator runs the **rendered two-width side-by-side** before commit (DS-1's hard gate),
|
|
200
|
+
and names the two screenshots in the register Notiz.
|
|
201
|
+
- Because the effect is uneven, the rendered check must cover **at least one chart that set the
|
|
202
|
+
props and one that did not** — otherwise it proves nothing about the change.
|
|
203
|
+
|
|
204
|
+
### Preconditions
|
|
205
|
+
|
|
206
|
+
- **THEME-1 landed and published as `2.30.0`.** Not before: both WOs touch `src/index.js` and
|
|
207
|
+
`src/components/charts/`, this repo publishes on push, and decision 23 fixes the order.
|
|
208
|
+
- The baseline supplies `motion.chart` and the tick/grid token values this WO applies.
|
|
209
|
+
|
|
210
|
+
### Parity guardrail
|
|
211
|
+
|
|
212
|
+
Visual/UX only. **No behaviour, permission or data-contract change.** The hard condition: a caller
|
|
213
|
+
that already sets a prop sees **no change at all** — the defaults fill absence, they do not
|
|
214
|
+
override intent.
|
|
215
|
+
|
|
216
|
+
### Operator decisions binding this WO
|
|
217
|
+
|
|
218
|
+
- **2026-08-10:** charts animate on entry; polling charts get `skipAnimation` (light movement is
|
|
219
|
+
wanted, and AAA is explicitly not the bar).
|
|
220
|
+
- **2026-08-10:** the toolbar row becomes the legend in `TimeSeriesChart`; MUI's legend goes.
|
|
221
|
+
- **2026-08-10:** `yearTickInterval` is hoisted, with its own justification and as its own export.
|
|
222
|
+
- **2026-08-10:** `legendPosition` ships as a prop with a fixed default; the auto-placement
|
|
223
|
+
heuristic becomes its own strand because the in-plot overlay path is unverified.
|
|
224
|
+
- **Decision 23:** `2.31.0`, after THEME-1's `2.30.0`.
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## B. Implementation map
|
|
229
|
+
|
|
230
|
+
*To be filled by the Orchestrator on `git pull` — see AGENTS.md → "Work Order".*
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# SHELL-2 — AccountPage: from a horizontal tab strip to a grouped sidebar + mobile drawer
|
|
2
|
+
|
|
3
|
+
**Target repo:** `ui-core-micha` (branch `main` — this repo has no `develop`)
|
|
4
|
+
**Tier:** 2 (shared core)
|
|
5
|
+
**Review:** independent `reviewer` (Sonnet, full) **and** `ui_reviewer` — spawned concurrently, both mandatory before commit
|
|
6
|
+
**Prototype (visual spec):** [`work-orders/assets/SHELL-2-account-navigation.html`](assets/SHELL-2-account-navigation.html)
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## A. Envelope
|
|
11
|
+
|
|
12
|
+
### Goal
|
|
13
|
+
|
|
14
|
+
Make `AccountPage` usable on a 375 px viewport, and give the account surface the same
|
|
15
|
+
navigation pattern the estate already uses in jg-ferien's `EventInfoPage`.
|
|
16
|
+
|
|
17
|
+
Expected outcome: the horizontal tab strip is gone. On desktop the sections live in a
|
|
18
|
+
sticky, grouped sidebar; on mobile they live in a bottom drawer opened by an in-page
|
|
19
|
+
trigger bar. The users section renders cards instead of a table below `md`.
|
|
20
|
+
|
|
21
|
+
### Measured evidence (2026-08-08, live at 375 px)
|
|
22
|
+
|
|
23
|
+
Measured in a running cockpit (which mounts ucm's `AccountPage` at `/account`), with the
|
|
24
|
+
host header neutralised so the numbers describe ucm alone:
|
|
25
|
+
|
|
26
|
+
- All six sections render without document overflow — `AccountPage` itself does **not**
|
|
27
|
+
break the layout at 375 px.
|
|
28
|
+
- The tab strip: scroller `clientWidth` 343 vs `scrollWidth` 589, `overflow-x: auto`,
|
|
29
|
+
246 px of scroll travel, and **`scrollButtons: 0`**. At rest, Profil / Sicherheit /
|
|
30
|
+
Benutzer / Einladen are reachable; Support and the consumer-injected section are off
|
|
31
|
+
screen with no arrow, no fade, no hint that they exist. This is a discoverability
|
|
32
|
+
failure, not a reachability failure — the tabs do respond to a horizontal swipe.
|
|
33
|
+
- The users table: 1045 px of content inside a 341 px `MuiTableContainer` with
|
|
34
|
+
`overflow-x: auto`. One third is visible. The `Aktionen` column sits roughly 700 px to
|
|
35
|
+
the right. Column `minWidth` values: E-Mail 220, Name 180, Neu 90, Letzte Anmeldung 120,
|
|
36
|
+
Rolle 180, Aktionen 220.
|
|
37
|
+
- Interactive controls on the auth pages measure 34–36 px tall, below the 44 px touch
|
|
38
|
+
target guideline. In scope only where this WO touches the control anyway.
|
|
39
|
+
|
|
40
|
+
### Scope
|
|
41
|
+
|
|
42
|
+
**A. Section navigation — both halves of the jg-ferien pattern**
|
|
43
|
+
(reference: `jg-ferien/frontend/src/components/EventInfoHub/SectionSwitcher.jsx`,
|
|
44
|
+
driven from `jg-ferien/frontend/src/pages/EventInfoPage.jsx:1126-1159`)
|
|
45
|
+
|
|
46
|
+
- **`md` and up:** a CSS grid `280px minmax(0, 1fr)`. Left, a sticky vertical section list
|
|
47
|
+
grouped under headings — per group an outlined `Paper` with a heading row and a `List` of
|
|
48
|
+
`ListItemButton`, the active entry in the `selected` state. Right, the section content
|
|
49
|
+
with `minWidth: 0`.
|
|
50
|
+
- **Below `md`:** the content spans the full width. The grouped list moves into a bottom
|
|
51
|
+
`Drawer`: top corners rounded 18 px, `maxHeight: 78dvh`, bottom padding
|
|
52
|
+
`max(16px, env(safe-area-inset-bottom))`, a title row, list scrolls inside.
|
|
53
|
+
- **The trigger** — the part the reference does not supply. In jg-ferien the button that
|
|
54
|
+
opens the drawer lives in that app's own page header; `AccountPage` has no header of its
|
|
55
|
+
own (the consuming app provides it), so the trigger must live inside `AccountPage`. Per
|
|
56
|
+
the prototype: a full-width 56 px bar at the top of the content, showing a small "Bereich"
|
|
57
|
+
caption above the current section name, with a trailing chevron.
|
|
58
|
+
|
|
59
|
+
**B. Grouping, and the `extraTabs` extension**
|
|
60
|
+
|
|
61
|
+
- Fixed groups owned by ucm, in this order: `Mein Konto` (profile, security) ·
|
|
62
|
+
`Verwaltung` (users, invite) · `Hilfe` (support) · `Weitere` (catch-all, last).
|
|
63
|
+
- `extraTabs` entries gain an **optional** `group` field. An entry without `group` falls
|
|
64
|
+
into `Weitere`. Every existing call site keeps working unchanged — this is additive only.
|
|
65
|
+
|
|
66
|
+
**C. Users section: table to cards below `md`**
|
|
67
|
+
|
|
68
|
+
- At `md` and up the table stays exactly as it is, columns and `minWidth` values unchanged.
|
|
69
|
+
- Below `md`, one card per user carrying the same fields as a row — email, name, the "Neu"
|
|
70
|
+
flag, last login, role, actions — with the actions reachable without horizontal scrolling
|
|
71
|
+
and with touch targets of at least 44 px.
|
|
72
|
+
|
|
73
|
+
**D. Version**
|
|
74
|
+
|
|
75
|
+
- Bump to `2.30.0`. Minor rather than patch because `extraTabs` gains a new public field;
|
|
76
|
+
additive API is a minor under semver.
|
|
77
|
+
|
|
78
|
+
### Non-goals / do not touch
|
|
79
|
+
|
|
80
|
+
- Pagination or filtering for the user list. Absent today, absent in the prototype,
|
|
81
|
+
explicitly not part of this WO — do not add it "while in there".
|
|
82
|
+
- The internal content of the Profil, Sicherheit, Einladen, Support and consumer-injected
|
|
83
|
+
sections. Only their placement in the navigation changes.
|
|
84
|
+
- The access-code, QR-signup and invite management components.
|
|
85
|
+
- The six auth pages (Login, SignUp, SignupConfirm, PasswordChange, PasswordResetRequest,
|
|
86
|
+
PasswordInvite). Measured clean at 375 px; out of scope.
|
|
87
|
+
- Messaging, notifications, charts, onboarding.
|
|
88
|
+
- The consuming apps. No host repo is touched by this WO — in particular **cockpit's header
|
|
89
|
+
is a separate work order**; its toolbar is what inflates the document to 498 px on every
|
|
90
|
+
route, including `/account`, and fixing it here would be out of scope.
|
|
91
|
+
- No behaviour change, no permission change, no data-contract change (parity guardrail).
|
|
92
|
+
|
|
93
|
+
### Replaces / removes — within the prototype's declared coverage
|
|
94
|
+
|
|
95
|
+
The prototype is the visual spec; **this list, not prototype silence, is the removal
|
|
96
|
+
authority.** Everything named here must be gone when the WO is done:
|
|
97
|
+
|
|
98
|
+
1. **The horizontal MUI `Tabs` strip — on desktop as well as mobile.** It is replaced by
|
|
99
|
+
the sidebar (desktop) and the drawer (mobile). Keeping it at any breakpoint means the
|
|
100
|
+
WO is not done. The operator was asked about this specifically and chose both halves.
|
|
101
|
+
2. **On mobile, the section-title heading below the trigger bar.** The trigger bar is the
|
|
102
|
+
sole carrier of the current section name there. Do not re-add it as a "missing" heading.
|
|
103
|
+
Desktop keeps its section heading, because desktop has no trigger bar to carry the name.
|
|
104
|
+
3. The scrollable-tabs affordance question disappears with the strip — do not "fix" it by
|
|
105
|
+
adding scroll buttons or a fade to something that no longer exists.
|
|
106
|
+
|
|
107
|
+
A redesign is a subtraction task. If the finished screen carries more UI than it does
|
|
108
|
+
today, that is a stop signal, not thoroughness.
|
|
109
|
+
|
|
110
|
+
### Deliberately keeps — undrawn in places, but must survive
|
|
111
|
+
|
|
112
|
+
- **The `?tab=<value>` URL contract** (`src/pages/AccountPage.jsx:60`), including the
|
|
113
|
+
fallback to `profile` when the value is unknown *or* not permitted for the user
|
|
114
|
+
(`:180-186`). Cockpit's `/user-management`, `/profile` and `/change-password` redirects
|
|
115
|
+
depend on it. This is the single most breakable thing in the WO.
|
|
116
|
+
- The `?from=recovery` and `?from=weak_login` entry states (`:64-65`).
|
|
117
|
+
- The full `extraTabs` contract (`:146-155`): `value`, `label` as a string **or a function**
|
|
118
|
+
receiving `{ user, perms, isSuperUser, t }`, `visible(context)`, `render`. Extended by
|
|
119
|
+
`group` only; nothing removed, nothing made required.
|
|
120
|
+
- The permission-conditional section list: `users` on `canViewUsers`, `invite` on
|
|
121
|
+
`canViewInvite`, `support` on `isSuperUser || perms.can_view_support`, profile and
|
|
122
|
+
security always (`:127-144`). The section list is per-user and can be as short as two
|
|
123
|
+
entries.
|
|
124
|
+
- `WidePage` as the page container, and its title.
|
|
125
|
+
- The desktop section heading above the content.
|
|
126
|
+
- The users table's column set and `minWidth` values at `md` and up.
|
|
127
|
+
- No standalone "Übersicht" entry above the groups. jg-ferien has one; `AccountPage` has no
|
|
128
|
+
overview screen for it to point at, so none is invented. Navigation starts at the first
|
|
129
|
+
group. Settled with the operator.
|
|
130
|
+
|
|
131
|
+
### Risks
|
|
132
|
+
|
|
133
|
+
- **No staging net.** This repo has no `develop`. A push to `main` that touches `src/**` or
|
|
134
|
+
`package.json` publishes to npm immediately via `.github/workflows/publish.yml`. The
|
|
135
|
+
independent review is the only gate before publication — it is not back-fillable.
|
|
136
|
+
- **Fourteen consumers inherit the desktop change** on their next pin bump. Nobody is
|
|
137
|
+
forced: every consumer pins an exact version (eight sit on 2.4.5). Only cockpit is
|
|
138
|
+
expected to bump soon, and that bump belongs to cockpit's own WO.
|
|
139
|
+
- **`AccountPage` has zero tests today.** None of the 20 files under `tests/` touches it or
|
|
140
|
+
`UserListComponent`. The `?tab=` contract is currently unguarded, so a regression there
|
|
141
|
+
would surface first as three broken redirects in cockpit, silently.
|
|
142
|
+
- **Drawer stacking.** jg-ferien had to raise its own `SectionSwitcher` drawer to
|
|
143
|
+
`theme.zIndex.drawer + 3` to clear its bottom navigation bar. ucm has no bottom nav, but a
|
|
144
|
+
consuming app may — the drawer must not assume it is topmost, and must not hard-code a
|
|
145
|
+
value that fights a host's own layering.
|
|
146
|
+
- **Safe-area insets are inert without `viewport-fit=cover`.** jg-ferien uses
|
|
147
|
+
`env(safe-area-inset-bottom)` while its `index.html` omits `viewport-fit=cover`, so those
|
|
148
|
+
insets resolve to zero there. Copy the padding by all means, but do not assume it is
|
|
149
|
+
doing anything, and do not change any consuming app's viewport meta tag from here.
|
|
150
|
+
|
|
151
|
+
### Tests to write
|
|
152
|
+
|
|
153
|
+
Narrow and new — both files are new; there is nothing to extend. Written as part of the
|
|
154
|
+
implementation, run by the Orchestrator.
|
|
155
|
+
|
|
156
|
+
**`tests/AccountPage.test.jsx`**
|
|
157
|
+
|
|
158
|
+
1. The grouped list renders: the four groups in fixed order, each built-in section under
|
|
159
|
+
its declared group.
|
|
160
|
+
2. `?tab=users` activates the users section.
|
|
161
|
+
3. `?tab=<unknown>` falls back to `profile`.
|
|
162
|
+
4. `?tab=users` for a user **without** `canViewUsers` falls back to `profile` — the
|
|
163
|
+
permission fallback, not just the unknown-value one.
|
|
164
|
+
5. A short permission set (profile + security only) renders a two-entry list without an
|
|
165
|
+
empty `Verwaltung` or `Hilfe` group heading.
|
|
166
|
+
6. `extraTabs` without `group` lands in `Weitere`; with `group: 'Mein Konto'` lands there.
|
|
167
|
+
7. `extraTabs` with a **function** `label` and with `visible()` returning false are both
|
|
168
|
+
still honoured (guards the contract this WO extends).
|
|
169
|
+
8. Below `md`: the trigger bar renders and names the current section; the duplicate section
|
|
170
|
+
heading is **absent** (assert its absence explicitly — this is a removal).
|
|
171
|
+
9. At `md` and up: the sidebar renders and no trigger bar is present.
|
|
172
|
+
|
|
173
|
+
Mock the breakpoint per this estate's established pattern —
|
|
174
|
+
`vi.mock('@mui/material/useMediaQuery')` with a per-test return value, as
|
|
175
|
+
`jg-ferien/frontend/src/components/Header.test.jsx` does.
|
|
176
|
+
|
|
177
|
+
**`tests/UserListComponent.test.jsx`**
|
|
178
|
+
|
|
179
|
+
10. At `md` and up: a table with the six columns.
|
|
180
|
+
11. Below `md`: one card per user, each carrying email, name, the "Neu" flag, last login,
|
|
181
|
+
role and the actions; assert there is no horizontally scrolling container.
|
|
182
|
+
|
|
183
|
+
Prove non-vacuity for at least tests 4 and 8 by temporarily removing the corresponding
|
|
184
|
+
behaviour and confirming the test fails.
|
|
185
|
+
|
|
186
|
+
### Parity guardrail
|
|
187
|
+
|
|
188
|
+
The prototype is the visual and UX spec. Visual/UX only: **no behaviour, no permission, no
|
|
189
|
+
data-contract change.** `ui_reviewer` checks it in both directions — everything in the
|
|
190
|
+
prototype present, and nothing outside it surviving. Because this is a visual redesign,
|
|
191
|
+
before the WO is marked done the Orchestrator asks the operator to run a side-by-side of
|
|
192
|
+
the built screen against the prototype; surviving legacy has repeatedly slipped past a
|
|
193
|
+
static diff review in this estate.
|
|
194
|
+
|
|
195
|
+
### Preconditions
|
|
196
|
+
|
|
197
|
+
None. This WO is independent of cockpit's navigation WO and can land first.
|