@micha.bigler/ui-core-micha 2.30.0 → 2.31.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.
@@ -227,4 +227,202 @@ override intent.
227
227
 
228
228
  ## B. Implementation map
229
229
 
230
- *To be filled by the Orchestrator on `git pull` — see AGENTS.md → "Work Order".*
230
+ *Filled by the Orchestrator on `git pull` — see AGENTS.md → "Work Order".*
231
+
232
+ ### Execution directive (read this first)
233
+
234
+ > **If you are the implementer reading this work order as your own specification: this section is
235
+ > NOT addressed to you.** It tells the Orchestrator how to invoke you. **You ARE that invocation —
236
+ > do NOT shell out to `codex exec`.**
237
+ >
238
+ > Implement through `codex exec` in the background — invoked directly via Bash (never the
239
+ > `debugger`/`*_coder` Agent wrappers) with BOTH flags `--skip-git-repo-check` and
240
+ > `--dangerously-bypass-approvals-and-sandbox`. **Pass the WO via stdin, not as a positional
241
+ > argument** — `cat work-orders/CHART-6.md | codex exec --skip-git-repo-check
242
+ > --dangerously-bypass-approvals-and-sandbox -` — a positional arg hit Windows' command-line length
243
+ > limit on THEME-1 ("Argument list too long" from the npm shim); stdin is the proven working
244
+ > invocation for a WO this size on this platform. (Fallback to direct Claude implementation only on
245
+ > Codex quota/rate-limit/non-zero exit.)
246
+
247
+ ### Context package
248
+
249
+ **Precondition verified 2026-08-10:** THEME-1 landed (`b329b39`, `96992cf`, `35f70b2`) and is live on
250
+ npm as `2.30.0` (`npm view @micha.bigler/ui-core-micha version` initially showed the registry cache
251
+ still at `2.29.2` right after push — the `Publish ui-core-micha to npm` GitHub Action had completed
252
+ successfully in ~1m42s; this repo's `package.json` already reads `2.30.0`, which is the authoritative
253
+ signal, not the possibly-cached registry response).
254
+
255
+ **Named files to change:**
256
+
257
+ - `src/components/charts/BarChart.jsx`, `src/components/charts/LineChart.jsx` — near-identical
258
+ wrappers (currently 45 lines each, both shown in full above under "Named files"). Add: (1) default
259
+ `tickLabelStyle` (merged into each axis entry, not replacing a caller's own `xAxis`/`yAxis` array
260
+ entries — same "caller wins" pattern `labelledAxis` already uses for `label`); (2) default
261
+ `showMark: false` on the wrapper's own `chartProps`-level default (only meaningful for `LineChart`
262
+ — `BarChart` has no marks; skip it there or make it a shared no-op, your call, but do not add a
263
+ prop MUI's `BarChart` doesn't accept); (3) default `grid={{ horizontal: true }}`; (4) an explicit
264
+ `hideLegend` prop that DEFAULTS to today's `series.length <= 1` heuristic but is OVERRIDABLE by the
265
+ caller — **this is a real gap, not cosmetic**: today `hideLegend={series.length <= 1}` is hardcoded
266
+ after the `{...chartProps}` spread, so a caller-passed `hideLegend` in `chartProps` is silently
267
+ discarded (JSX prop order: later wins, and the hardcoded one comes last). `TimeSeriesChart` (below)
268
+ needs to force `hideLegend={true}` when it draws its own toolbar-as-legend, so this plumbing gap
269
+ must close first; (5) a new `legendPosition` prop, default `{ vertical: 'bottom', horizontal:
270
+ 'start' }` (MUI's `Position` type — verified in
271
+ `node_modules/@mui/x-charts/models/position.d.ts`: `{ vertical?: 'top'|'middle'|'bottom',
272
+ horizontal?: 'start'|'center'|'end' }`), passed as `slotProps={{ legend: { position:
273
+ legendPosition } }}` merged with the existing `tooltip: { trigger: 'axis' }` slotProp (don't
274
+ clobber one with the other); (6) automatic bottom-margin increase when tick labels are rotated —
275
+ MUI X-Charts reads rotation from `xAxis[i].tickLabelStyle.angle` (or similar; verify the exact
276
+ field in the installed types before relying on it) — when a caller's `xAxis` sets a non-zero
277
+ angle, add `margin.bottom` (a `ChartContainer`-level prop, check exact prop name/shape in
278
+ `node_modules/.pnpm/@mui+x-charts@8.29.2_.../node_modules/@mui/x-charts/ChartContainer` types)
279
+ large enough for the rotated label height — don't hardcode a single magic number without deriving
280
+ it from the tick font size/angle, and don't touch margin at all when no rotation is set (default
281
+ MUI margin must survive unchanged, per the parity guardrail).
282
+ **On-chart marker fill (LineChart only):** verified in
283
+ `node_modules/@mui/x-charts/LineChart/MarkElement.js:26-31` — the hollow marker is `styled('path',
284
+ {name:'MuiMarkElement', slot:'Root'})(({theme}) => ({fill: theme.palette.background.paper, ...}))`,
285
+ a genuine MUI-theme-connected styled component (same mechanism as `MuiButton`/`MuiTableCell` etc.
286
+ already overridden in THEME-1's `BASELINE_STATIC.components`). The cleanest fix, consistent with
287
+ every other component default in this kit, is a `MuiMarkElement: { styleOverrides: { root: {
288
+ fill: 'currentColor' } } }` entry (paired with `stroke: color` already set per-instance by
289
+ MarkElement itself, `color` inherited via the mark's own `color` CSS from its parent — verify
290
+ `currentColor` actually resolves to the series colour in the rendered DOM, since `MarkElementPath`
291
+ doesn't explicitly set a `color` CSS property alongside `stroke`; if `currentColor` doesn't
292
+ resolve, use `fill: 'inherit'` or accept a small amount of extra plumbing) added to
293
+ **`src/theme/tokens.js`'s `BASELINE_STATIC.components`** (a THEME-1 file, but this is a
294
+ theme-component-override, not a `charts/palette.js` change — the WO's non-goal only excludes
295
+ `charts/palette.js`, not `src/theme/`). If that turns out infeasible, fall back to a per-wrapper
296
+ `slotProps`/`sx` override targeting `.MuiMarkElement-root` and say so explicitly in the PR — do not
297
+ silently ship the hollow default.
298
+ **Legend swatch shape:** verified in `LineChart/seriesConfig/seriesProcessor.js:97` — LineChart
299
+ series default to `labelMarkType: 'line'` (a short dash, matching the WO's "dash marks" default to
300
+ replace); `BarChart/seriesConfig/bar/seriesProcessor.js:94` already defaults to `labelMarkType:
301
+ 'square'` (already filled — nothing to change there). Fix: in `LineChart.jsx`'s series mapping,
302
+ default each series entry's `labelMarkType` to `'square'` unless the caller's series object already
303
+ sets one (`{ labelMarkType: 'square', ...series }` — caller key after the default wins the object
304
+ spread, matching the "default, not override" invariant).
305
+ - `src/components/charts/TimeSeriesChart.jsx` — (1) pass `hideLegend` explicitly to its internal
306
+ `BarChart` call (currently absent, so `BarChart`'s own heuristic applies): `hideLegend` should be
307
+ `true` whenever the toolbar's series-toggle row renders (`seriesConfig.length > 0`, same condition
308
+ already used at line 214 to render `FormGroup`) — that removes MUI's own legend while the toolbar
309
+ toggles take over as the visible legend, satisfying Scope B. The toggles already carry an
310
+ accessible name via each `FormControlLabel`'s `label` prop (line 217-231) — verify this still holds
311
+ after your change (test 7), no new work expected there. (2) Pass `motion.chart`-based
312
+ animation/`skipAnimation` through unchanged — `TimeSeriesChart` doesn't currently accept or need a
313
+ `skipAnimation` prop itself; a caller wanting a polling chart to skip animation calls the wrapper
314
+ with `skipAnimation` in `chartProps`, which already passes through `BarChart`'s `{...chartProps}`
315
+ spread untouched — **verify this passthrough still works after your `hideLegend`/`legendPosition`
316
+ changes** (i.e. don't accidentally consume/strip `skipAnimation` while destructuring), there is
317
+ likely nothing to build here beyond that verification.
318
+ - `src/theme/tokens.js` — add the `MuiMarkElement` override described above (if that's the mechanism
319
+ you land on), and a `chart` typography-adjacent tick-size reference if one doesn't already exist
320
+ cleanly reachable (`BASELINE_STATIC.typography.caption` is `12px/400/1.4` — the natural fit for
321
+ `tickLabelStyle.fontSize`; reference it directly via `theme.typography.caption.fontSize` inside the
322
+ wrappers rather than inventing a new token, unless a dedicated token reads better — your call, but
323
+ don't hardcode a bare `'12px'` string in the wrapper divorced from the theme).
324
+ - **New: `src/components/charts/chartLabels.js`** — port `formatShortTime`/`formatShortDate`/
325
+ `formatShortMonth`/`formatShortYear` from
326
+ `fitness-monitor/frontend/src/utils/chartLabels.js` (read that file directly — 37 lines, shown in
327
+ full above under "Named files"; also check `fitness-monitor/frontend/src/utils/chartLabels.test.js`
328
+ for the existing test cases to port per test 9, "port its existing cases rather than inventing new
329
+ ones"). This is a straight port, not a rewrite — keep the exact locale-fallback behaviour and the
330
+ `hour12: false` comment/rationale intact (it documents a real upstream MUI bug, mui-x#18768).
331
+ - **New: `src/components/charts/yearTickInterval.js`** (own export per the WO, not merged into
332
+ `chartLabels.js`) — generalise
333
+ `fitness-monitor/frontend/src/pages/BodyHistoryPage.jsx:39-43`'s `yearTickInterval(range, dates)`.
334
+ That version is app-specific (gated on a literal `range !== "All"` string fitness-monitor owns) —
335
+ the ucm export should NOT take a `range` string at all (this repo's own `TimeSeriesChart` uses
336
+ different range keys, `'1y'` not `'All'`, and other callers may have yet another vocabulary). Ship
337
+ it as `yearTickInterval(dates)`: always computes one Date per calendar year present in `dates` (the
338
+ same `[...new Set(dates.map(d => d.getFullYear()))].sort().map(y => new Date(y, 0, 1))` logic,
339
+ minus the range gate) — the CALLER decides when a dense-enough multi-year range warrants using it
340
+ (as `xAxis[0].tickInterval`), matching the original's actual computation, not its app-specific gate.
341
+ - `src/theme/index.js`, `src/index.js` — export `chartLabels`' functions and `yearTickInterval` (new
342
+ entries near the existing `## 6. Charts` section in `src/index.js`, alongside `ChartFrame`/
343
+ `BarChart`/`LineChart`/`TimeSeriesChart`/palette exports).
344
+ - `package.json` — version `2.30.0` → `2.31.0` (minor).
345
+ - **New: `tests/chartDefaults.test.js`, `tests/timeSeriesLegend.test.js`, `tests/chartLabels.test.js`**
346
+ — per Required tests below. Follow the existing mock convention in `tests/BarChart.test.jsx`
347
+ (shown in full above): `vi.mock('@mui/x-charts/BarChart', () => ({ BarChart: chartSpy }))` with a
348
+ hoisted spy capturing props, assert on `chartSpy.mock.calls[...][0]` rather than deep-rendering the
349
+ real chart. Mirror the same pattern for `@mui/x-charts/LineChart` where `LineChart.jsx` is under
350
+ test.
351
+ - `dev/entries.jsx` — the WO's Verification section requires the harness to gain "chart specimens"
352
+ and the rendered check must cover **at least one chart that set the props and one that did not**.
353
+ Add a small addition to the existing THEME-1 `ThemeBaselineEntry` (or a sibling entry) showing two
354
+ `BarChart`/`LineChart` instances side by side: one with zero extra props (shows the new defaults —
355
+ grid, filled marks, bottom-start legend) and one with an explicit caller override on at least
356
+ `tickLabelStyle`/`legendPosition` (proves the override still wins, visually).
357
+
358
+ ### Do-not-touch / invariants
359
+
360
+ - **`src/components/charts/palette.js`** — untouched, THEME-1 owns the ramp transition.
361
+ - **`ChartFrame`'s `p: 2` padding and its foot row** — untouched, 16 panels depend on them.
362
+ - **hram's four tuned `tickLabelStyle` call sites** (not in this repo — nothing to verify directly
363
+ here beyond making sure an explicit caller `tickLabelStyle` always wins the merge, which the new
364
+ tests must prove per required test 2).
365
+ - **No behaviour, permission, or data-contract change.**
366
+ - **Removing fitness-monitor's/jg-ferien's local copies** of the hoisted helpers — explicitly not
367
+ this WO; both keep working unchanged, on their own exact-pin versions.
368
+ - **The legend auto-placement heuristic** — out of scope, `legendPosition` ships as a plain prop with
369
+ a fixed default, not an auto-detecting overlay.
370
+
371
+ ### Pitfalls (verified against landed code and installed `node_modules` 2026-08-10)
372
+
373
+ - **The WO's Scope A table says X-Charts `8.28.2`; the actually installed version in this repo is
374
+ `8.29.2`** (`node_modules/.pnpm/@mui+x-charts@8.29.2...`). Verify every API claim above against the
375
+ INSTALLED source (`node_modules/.pnpm/@mui+x-charts@8.29.2.../node_modules/@mui/x-charts/`), not
376
+ against the WO's prose or MUI's public docs for a different version.
377
+ - `hideLegend`'s current hardcoded placement in both `BarChart.jsx` and `LineChart.jsx` (after the
378
+ `{...chartProps}` spread) is the same class of bug CHART-1's R2/R3-style findings have caught
379
+ before in this kit — don't just add a new prop next to it without checking whether other
380
+ post-spread hardcoded props (`colors`, `slotProps`) have the same caller-cannot-override issue; the
381
+ WO only asks you to fix `hideLegend` and add `legendPosition`, but note anything else you find
382
+ rather than silently leaving it.
383
+ - `MarkElementPath`'s `fill` comes from `theme.palette.background.paper` via `styled()` — this only
384
+ resolves correctly if the chart is actually rendered under a `ThemeProvider` carrying
385
+ `createAppTheme`'s output (or any MUI theme with the override). A consumer NOT using
386
+ `createAppTheme` yet (all 14 today) still gets MUI's own theme with no `MuiMarkElement` override —
387
+ confirm this is an acceptable, expected "you get it once you adopt THEME-1 + this bump" outcome
388
+ (it is, per the WO's own "next pin bump is a visual verification event" framing), not a regression
389
+ to chase further.
390
+ - `src/index.js` already has a duplicate `// --- 9. ... ---` header comment (pre-existing, noted in
391
+ THEME-1's WO too) — don't compound it.
392
+
393
+ ### Target repo working directory (absolute)
394
+
395
+ `C:\Users\biglmi\Documents\webapps\ui-core-micha`
396
+
397
+ ### Required tests to WRITE (Codex writes them; the Orchestrator runs them)
398
+
399
+ Exactly the 10 tests plus `tsc` check enumerated in Envelope § "Required tests to WRITE" above —
400
+ `tests/chartDefaults.test.js` (5 tests, parametrised across `LineChart`/`BarChart`/`TimeSeriesChart`
401
+ where applicable), `tests/timeSeriesLegend.test.js` (3 tests), `tests/chartLabels.test.js` (2 tests,
402
+ test 9 ported from fitness-monitor's existing cases, test 10 proven non-vacuous by temporarily
403
+ removing `yearTickInterval`'s effect and confirming duplicate year labels reappear, then restoring
404
+ it), plus `tsc -p tsconfig.build.json --noEmit` clean. Do not add more, do not run the full suite —
405
+ the affected-areas set additionally includes `tests/chartsPalette.test.js` ONLY if the hoist
406
+ incidentally touches it (it shouldn't, since `charts/palette.js` is explicitly untouched).
407
+
408
+ ### Preamble (append verbatim)
409
+
410
+ > The text above is the COMPLETE spec — the committed WO file's content, not a plan to refine; there
411
+ > is no separate plan file. Read the nearest `AGENTS.md`, the relevant `.codex/skills/<role>/SKILL.md`, and the
412
+ > app `MEMORY.md` ONLY for conventions. Stay in scope; do not touch auth/permissions/deps/schema/CI
413
+ > unless the spec says so; do not update `MEMORY.md`. Do NOT `git add`/`commit`/`push` — leave every
414
+ > change uncommitted in the working tree for the orchestrator's independent review. WRITE the tests
415
+ > the `Required tests` section calls for AND **RUN the tests you just wrote** to confirm they execute
416
+ > and pass — that is the ONLY test run you do (NOT the app's affected/full suite, NOT any review).
417
+ > The orchestrator re-runs the authoritative set + does the independent review after you finish —
418
+ > those are the gate; your own run does not count as the gate.
419
+ >
420
+ > Narrate continuously: a `PLAN: <step1> | <step2> | …` line up front, then a single-line
421
+ > `PROGRESS: [<n>/<total>] <present-tense action>` before every relevant action (and `… done` on
422
+ > completion), spaced so no gap exceeds ~2 min, stdout unbuffered, plus exactly one final
423
+ > `RESULT: DONE|BLOCKED <reason>`.
424
+
425
+ ### Mini-handover
426
+
427
+ Repo: `ui-core-micha` (`C:\Users\biglmi\Documents\webapps\ui-core-micha`), branch `main`. WO:
428
+ `work-orders/CHART-6.md`. Follow `orchestrate-codex`.