@micha.bigler/ui-core-micha 2.28.1 → 2.28.2

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/WORK_ORDERS.md CHANGED
@@ -23,6 +23,7 @@ prefixed ID; never reuse a bare flat number across workstreams.
23
23
 
24
24
  | ID | Titel | Beschreibung | Datum | Status | Commit(s) | Notiz |
25
25
  |---|---|---|---|---|---|---|
26
+ | CHART-4 | Fix `TimeSeriesChart` chart canvas rendering at zero height | Neither `ChartFrame` nor `BarChart` ever receive a height from `TimeSeriesChart` — MUI X-Charts' responsive container measures a zero-height parent and draws no bars/axes, while the legend (sized by its own content) renders regardless, which is why the live symptom was "legend shows, chart canvas doesn't". Fix supplies a preset default height (`CHART_HEIGHT = 320`, `minHeight` on `ChartFrame` + `height` forwarded to the real `MuiBarChart`), matching the working `dev/entries.jsx` reference pattern. Full spec: [work-orders/CHART-4.md](work-orders/CHART-4.md). | 2026-08-07 | done | f0953a1, fcb289b | Second, independent bug found live in jg-ferien immediately after CHART-3 + the ucm pin bump landed — CHART-3's own fix is what let this one become visible for the first time (it was previously masked by the permanently-empty-chart bug). Root cause of it shipping twice undetected: every test in `tests/TimeSeriesChart.test.jsx`/`tests/BarChart.test.jsx` mocks the real `@mui/x-charts/BarChart`, so no test has ever mounted the real chart and observed its size. **Codex out of credits, known up front** — implemented directly (author-flip, independent review mandatory). **Test-writing pitfall found and self-corrected during implementation**: first wrote an *unmocked* render test asserting the real chart SVG's `viewBox` height was non-zero — verified vacuous (stays green even fully reverting the fix, since jsdom's `ResizeObserver`/layout handling doesn't reproduce the real zero-height-parent browser behavior this bug depends on). Deleted; replaced with a `chartSpy`-mocked prop assertion (`props.height` non-zero) matching the file's existing convention, confirmed non-vacuous (fails pre-fix with `props.height` undefined). Independent `reviewer` + `ui_reviewer` ran concurrently: `reviewer` verified the fix mechanism end-to-end (no prop-name collision between `BarChart`'s own `minHeight`/`aspect` and the forwarded `height`) and the test's non-vacuity claim, no findings. `ui_reviewer`: two P3 informational notes, neither blocking — (U1) the fixed 320px height is a deliberate, documented exception to `DESIGN.md` #14 ("responsive = container-sized, not fixed px"), needed specifically because MUI X-Charts' own `ResizeObserver`-based height measurement is what caused CHART-4 in the first place; a code comment was added warning future readers not to "fix" this back to a responsive height without re-verifying the underlying MUI bug is resolved. (U2) `CHART_HEIGHT` uses 320 for both `ChartFrame.minHeight` and `BarChart.height`, vs. `dev/entries.jsx`'s 360/320 split — functionally harmless (WO explicitly permits one fixed value), no change made. 13/13 scoped vitest tests green, `tsc` build clean. Published: ucm 2.28.2 (patch, no interface change). No app pin bumps in this WO — jg-ferien picks it up on its next ucm bump. |
26
27
  | CHART-3 | Fix `TimeSeriesChart` rendering nothing on async data load | `visibleKeys` was a lazy `useState` initialized once from `data` at first mount; any host that fetches asynchronously after mount (the normal case, e.g. jg-ferien's `ActivitySection`) ends up with a permanently-empty `visibleKeys`, so `isDataEmpty` stays `true` forever and the chart body never renders even once real data arrives. Fix syncs newly-seen series keys to visible-by-default while preserving a user's manual toggle-off across data updates. Full spec: [work-orders/CHART-3.md](work-orders/CHART-3.md). | 2026-08-06 | done | 9a1bd9d, 48cefa8 | Found live in jg-ferien's Aktivität screen (ACT-3 consumer) — toolbar rendered, chart body stayed on `ChartFrame`'s empty state permanently. **Codex was out of workspace credits** (genuine quota error on invocation, no partial work) — orchestrator implemented directly per the Codex→Claude fallback rule; independence flip applies (author = orchestrator, so independent review was mandatory, not self-check). Independent `reviewer` + `ui_reviewer` both ran (background, concurrent): `ui_reviewer` found no findings; `reviewer` found R1 (P2, real bug — the seen-keys tracker was a `useRef` mutated during the render body alongside a paired `setVisibleKeys` call; under React concurrent rendering an interrupted/discarded render leaves the ref mutation applied while the paired state update is discarded, permanently hiding a key without it ever reaching `visibleKeys` — a different-shaped recurrence of the exact bug this WO exists to fix) and R2 (P3, test-file duplication — a second near-duplicate render helper). Both fixed: seen-keys tracking moved from a ref to a second `useState` (both states updated together in the render body, so a discarded render rolls both back together — React's documented pattern for this), and the duplicate test helper collapsed into the existing `renderChart`'s `rerenderWith`. Re-verified: 12/12 scoped vitest tests green (3 new CHART-3 regression tests, 2 of which confirmed non-vacuous by failing against the pre-fix code), `tsc` build clean. Published: ucm 2.28.1 (patch, no interface change). No app pin bumps in this WO — jg-ferien picks it up on its next ucm bump. |
27
28
  | ONB-1 | Per-app configurable notifications onboarding step | New `browserPush` prop `{nagUntil, showOnce}` on `OnboardingProvider`; parameterizes the `browser_push` descriptor's condition (default changes from implicit "all-channels" to "any-channel", stopping the over-nag); `showOnce` via a persisted `onboarding_seen` set (frozen-at-mount ref, no mid-session flicker) | 2026-07-17 | done | 1ae7c20 | Default behavior change affects cockpit and all consumers on their next ucm bump. jg-ferien companion WO pins the new version with `browserPush={{nagUntil: 'any-channel'}}` explicit (matches new default, but pinned explicitly per the WO). |
28
29
  | PERF-3B1 | Parallel auth bootstrap after CSRF | Starts auth-methods and current-user concurrently once CSRF is available, while preserving error handling and loading semantics. | 2026-07-19 | done | ee89329 | Independent `reviewer` + `sec_reviewer` passes both clean (no findings); one P3 test-coverage gap from the reviewer (missing mirror case: auth-methods rejects, current-user succeeds) closed with an added regression test — 45/45 tests green. Published as 2.10.1 (patch, no interface change) and pinned in jg-ferien alongside its PERF-3A companion WO. |
@@ -8,6 +8,22 @@ import { useNeutralChartPalette } from './palette';
8
8
  // Fixed platform-wide range -> granularity mapping (operator decision, CHART-2 /
9
9
  // dcm ACT-1) — the resolution follows the window and is never an independent
10
10
  // caller-supplied control. Every preset lands between a dozen and fifty points.
11
+ // Preset default height (CHART-4): MUI X-Charts' responsive container
12
+ // measures its actual parent height via ResizeObserver and draws nothing
13
+ // if that parent has none — ChartFrame's flex-centered content Box does
14
+ // NOT stretch its child to fill available height (alignItems: 'center'),
15
+ // so BarChart needs its own explicit size, not just ChartFrame's.
16
+ // `height` forwards straight to MuiBarChart's own native height prop
17
+ // (bypassing ResizeObserver-based measurement entirely), matching the
18
+ // confirmed-working dev/entries.jsx BarChartEntry reference.
19
+ // Deliberate, known exception to DESIGN.md #14 ("responsive = container-
20
+ // sized, not fixed px"): width stays responsive (unset, still auto-
21
+ // measured), only height is fixed, and only because MUI X-Charts' own
22
+ // ResizeObserver-based height measurement is what silently produced a
23
+ // zero-height chart in the first place (CHART-4). Do not "fix" this back
24
+ // to a responsive/aspect-ratio height without re-verifying the underlying
25
+ // MUI measurement bug is actually resolved.
26
+ const CHART_HEIGHT = 320;
11
27
  const RANGE_OPTIONS = [
12
28
  { key: '1d', granularity: 'hour', labelKey: 'TimeSeriesChart.RANGE_1_DAY' },
13
29
  { key: '1w', granularity: '4hour', labelKey: 'TimeSeriesChart.RANGE_1_WEEK' },
@@ -86,6 +102,6 @@ export function TimeSeriesChart({ title, subtitle, xAxisLabel, yAxisLabel, data,
86
102
  color: palette[index % palette.length],
87
103
  '&.Mui-checked': { color: palette[index % palette.length] },
88
104
  } })), label: series.label }, series.key))) }))] }));
89
- return (_jsx(ChartFrame, { title: title, subtitle: subtitle, toolbar: toolbar, loading: loading, error: error, isEmpty: isDataEmpty, children: !isDataEmpty && (_jsx(BarChart, { xAxisLabel: xAxisLabel, yAxisLabel: yAxisLabel, xAxis: [{ data: (data === null || data === void 0 ? void 0 : data.xLabels) || [] }], series: visibleSeries.map((series) => ({ data: series.data, label: series.label })), palette: visiblePalette })) }));
105
+ return (_jsx(ChartFrame, { title: title, subtitle: subtitle, toolbar: toolbar, loading: loading, error: error, isEmpty: isDataEmpty, minHeight: CHART_HEIGHT, children: !isDataEmpty && (_jsx(BarChart, { xAxisLabel: xAxisLabel, yAxisLabel: yAxisLabel, xAxis: [{ data: (data === null || data === void 0 ? void 0 : data.xLabels) || [] }], series: visibleSeries.map((series) => ({ data: series.data, label: series.label })), palette: visiblePalette, height: CHART_HEIGHT })) }));
90
106
  }
91
107
  export default TimeSeriesChart;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@micha.bigler/ui-core-micha",
3
- "version": "2.28.1",
3
+ "version": "2.28.2",
4
4
  "main": "dist/index.js",
5
5
  "module": "dist/index.js",
6
6
  "repository": {
@@ -15,6 +15,23 @@ import { useNeutralChartPalette } from './palette';
15
15
  // Fixed platform-wide range -> granularity mapping (operator decision, CHART-2 /
16
16
  // dcm ACT-1) — the resolution follows the window and is never an independent
17
17
  // caller-supplied control. Every preset lands between a dozen and fifty points.
18
+ // Preset default height (CHART-4): MUI X-Charts' responsive container
19
+ // measures its actual parent height via ResizeObserver and draws nothing
20
+ // if that parent has none — ChartFrame's flex-centered content Box does
21
+ // NOT stretch its child to fill available height (alignItems: 'center'),
22
+ // so BarChart needs its own explicit size, not just ChartFrame's.
23
+ // `height` forwards straight to MuiBarChart's own native height prop
24
+ // (bypassing ResizeObserver-based measurement entirely), matching the
25
+ // confirmed-working dev/entries.jsx BarChartEntry reference.
26
+ // Deliberate, known exception to DESIGN.md #14 ("responsive = container-
27
+ // sized, not fixed px"): width stays responsive (unset, still auto-
28
+ // measured), only height is fixed, and only because MUI X-Charts' own
29
+ // ResizeObserver-based height measurement is what silently produced a
30
+ // zero-height chart in the first place (CHART-4). Do not "fix" this back
31
+ // to a responsive/aspect-ratio height without re-verifying the underlying
32
+ // MUI measurement bug is actually resolved.
33
+ const CHART_HEIGHT = 320;
34
+
18
35
  const RANGE_OPTIONS = [
19
36
  { key: '1d', granularity: 'hour', labelKey: 'TimeSeriesChart.RANGE_1_DAY' },
20
37
  { key: '1w', granularity: '4hour', labelKey: 'TimeSeriesChart.RANGE_1_WEEK' },
@@ -155,6 +172,7 @@ export function TimeSeriesChart({
155
172
  loading={loading}
156
173
  error={error}
157
174
  isEmpty={isDataEmpty}
175
+ minHeight={CHART_HEIGHT}
158
176
  >
159
177
  {!isDataEmpty && (
160
178
  <BarChart
@@ -163,6 +181,7 @@ export function TimeSeriesChart({
163
181
  xAxis={[{ data: data?.xLabels || [] }]}
164
182
  series={visibleSeries.map((series) => ({ data: series.data, label: series.label }))}
165
183
  palette={visiblePalette}
184
+ height={CHART_HEIGHT}
166
185
  />
167
186
  )}
168
187
  </ChartFrame>
@@ -125,6 +125,23 @@ describe('TimeSeriesChart', () => {
125
125
  expect(screen.getByLabelText('Loading chart.')).toBeTruthy();
126
126
  });
127
127
 
128
+ // CHART-4 regression: neither ChartFrame nor BarChart ever received a
129
+ // height, so MUI X-Charts' responsive container measured a zero-height
130
+ // parent and drew no bars/axes (only the legend, sized by its own content,
131
+ // showed) -- confirmed live in production. A structural/rendered-size
132
+ // assertion in jsdom is NOT reliable here (verified: it stays green even
133
+ // without the fix, since jsdom's ResizeObserver handling doesn't reflect
134
+ // the real zero-height-parent browser behaviour) -- this asserts directly
135
+ // on the wiring instead: BarChart must receive a real, non-zero `height`,
136
+ // matching the confirmed-working dev/entries.jsx BarChartEntry reference
137
+ // (MUI's own native height prop, forwarded via BarChart's ...chartProps).
138
+ it('CHART-4: passes a real, non-zero height through to the underlying MUI BarChart', () => {
139
+ renderChart();
140
+
141
+ const props = chartSpy.mock.calls.at(-1)[0];
142
+ expect(props.height).toBeGreaterThan(0);
143
+ });
144
+
128
145
  // CHART-3 regression: visibleKeys must not be captured only at first mount.
129
146
  // Reproduces the live bug — host mounts with empty data, fetches
130
147
  // asynchronously, then rerenders with real data. Before the fix this stayed
@@ -0,0 +1,203 @@
1
+ # WORK ORDER CHART-4 (ui-core-micha) — TimeSeriesChart renders no chart body (zero height)
2
+
3
+ **EXECUTION DIRECTIVE.** If you are the implementer reading this as your own spec, this section is not
4
+ addressed to you — it tells the Orchestrator how to invoke you; you ARE that invocation, do not shell
5
+ out to `codex exec`. Orchestrator: implement through `codex exec` in the background, invoked **directly
6
+ via Bash** (never the `debugger`/`*_coder` Agent wrappers) with **both** flags `--skip-git-repo-check`
7
+ and `--dangerously-bypass-approvals-and-sandbox`, prompt passed as a positional argument from this
8
+ file. Fall back to direct Claude implementation only on Codex quota / rate-limit / non-zero exit.
9
+ **Known at WO authoring time: the Codex workspace is out of credits for this session — the fallback
10
+ applies immediately, do not spend time retrying `codex exec` first.**
11
+
12
+ Bug-fix WO on CHART-2/CHART-3. Found live in jg-ferien's "Aktivität" screen (ACT-3/ACT-5 consumer)
13
+ immediately after CHART-3 shipped and jg's pin was bumped: the toolbar, series checkboxes, and MUI
14
+ X-Charts **legend** now all render correctly (proving CHART-3's fix works and real data is present),
15
+ but the chart canvas itself — bars and axes — is entirely absent. The card ends right after the
16
+ legend row with visible empty space beneath it.
17
+
18
+ ## TIER
19
+ Tier 2 — shared-core UI consumed by every app that adopts `TimeSeriesChart`.
20
+ Independent `reviewer` + `ui_reviewer` mandatory.
21
+
22
+ ## WHY
23
+
24
+ `TimeSeriesChart` composes `ChartFrame` + `BarChart` but never supplies either with a height:
25
+
26
+ ```jsx
27
+ <ChartFrame title={title} subtitle={subtitle} toolbar={toolbar} loading={loading} error={error} isEmpty={isDataEmpty}>
28
+ {!isDataEmpty && (
29
+ <BarChart xAxisLabel={xAxisLabel} yAxisLabel={yAxisLabel} xAxis={[{ data: data?.xLabels || [] }]}
30
+ series={visibleSeries.map(...)} palette={visiblePalette} />
31
+ )}
32
+ </ChartFrame>
33
+ ```
34
+
35
+ Neither `minHeight`/`aspect` (ChartFrame's own sizing props) nor `minHeight`/`aspect`/a raw MUI
36
+ `height` (BarChart's) are passed. `ChartFrame`'s inner content `Box` has `display: flex, alignItems:
37
+ center, justifyContent: center` with no defined height — with `alignItems: center` (not `stretch`),
38
+ a flex child gets its own intrinsic height, not the parent's. `BarChart`'s own wrapping `Box` (`sx:
39
+ {width:'100%', minHeight, aspectRatio}`) therefore also has no height when neither prop is supplied.
40
+ MUI X-Charts' internal responsive container measures its actual parent height via `ResizeObserver` to
41
+ size the SVG — with a zero-height parent, the chart canvas (bars, axes) renders at zero size. The
42
+ **legend** is a separate MUI X-Charts sub-component sized by its own content (text + swatches), so it
43
+ renders regardless of container height — which is exactly why the legend was the only visible sign
44
+ of a mounted chart in the live screenshot.
45
+
46
+ This gap was invisible until now because CHART-3's own bug (`visibleKeys` never populating) kept
47
+ `isDataEmpty` permanently `true`, so `BarChart` never actually mounted in any live deployment before
48
+ today. It also went uncaught by CHART-2's and CHART-3's own tests: both `tests/BarChart.test.jsx` and
49
+ `tests/TimeSeriesChart.test.jsx` mock `@mui/x-charts/BarChart` itself (`vi.mock('@mui/x-charts/BarChart', ...)`
50
+ per CHART-2's own required-tests convention), so no test in this repo has ever mounted the *real* MUI
51
+ chart and observed its rendered size. The working reference is `dev/entries.jsx`'s `BarChartEntry`
52
+ (DX-1 harness), which explicitly sets `minHeight={360}` on `ChartFrame` and `height={320}` directly
53
+ on `BarChart` — i.e. every other real consumer of these primitives in this repo already knows it must
54
+ supply a height; `TimeSeriesChart`, as a preset built on top, was supposed to supply this itself
55
+ (hosts don't see `ChartFrame`/`BarChart` directly through the preset) and doesn't.
56
+
57
+ ## SCOPE
58
+
59
+ **Give `TimeSeriesChart` a sensible built-in height for the chart it composes**, matching the
60
+ DX-1-proven working pattern:
61
+ 1. Pass `minHeight` to `ChartFrame` (sizes the loading/error/empty states too, not just the chart).
62
+ 2. Pass a height down to `BarChart` so the actual MUI chart canvas gets a real, non-zero pixel size —
63
+ either `BarChart`'s own `minHeight`/`aspect` wrapper props, or a raw `height` forwarded via
64
+ `BarChart`'s `...chartProps` pass-through to `MuiBarChart` (the mechanism `dev/entries.jsx` uses
65
+ directly against `BarChart`, confirmed working there). Pick whichever actually renders a non-zero
66
+ chart canvas when verified (see REQUIRED TESTS) — do not assume `aspectRatio` CSS alone reliably
67
+ drives MUI X-Charts' `ResizeObserver` sizing without checking; the raw `height` prop is the
68
+ confirmed-working mechanism in this repo's own reference usage and is the safer default if there
69
+ is any doubt.
70
+ 3. Pick one fixed value (e.g. `320`/`360`, matching `dev/entries.jsx`'s own numbers) as a preset
71
+ default — this is a preset, not a raw primitive; the host does not choose chart height, matching
72
+ `TimeSeriesChart`'s existing "no new props" presentational contract. Do not add a new prop for
73
+ this unless a reviewer specifically asks for one.
74
+
75
+ **This WO's real fix is the test gap, not just the pixels.** The reason this shipped broken twice is
76
+ that every test in this file mocks the real MUI chart component. Required test 1 below must exercise
77
+ the **unmocked** `@mui/x-charts/BarChart` and assert a real rendered size — a mocked-chart test would
78
+ be exactly the kind of vacuous coverage that let this ship.
79
+
80
+ ## NON-GOALS / DO NOT TOUCH
81
+ - Do not change the range picker, series toggles, `chartsTranslations`, or the CHART-3 `visibleKeys`
82
+ sync logic — this WO is scoped to sizing only.
83
+ - Do not change `ChartFrame`'s or `BarChart`'s own prop contracts (`minHeight`/`aspect` already exist
84
+ on both) — only how `TimeSeriesChart` calls them.
85
+ - Do not add a new prop to `TimeSeriesChart`'s public API for this — see SCOPE point 3.
86
+
87
+ ## RISKS
88
+ - Every host that has adopted `TimeSeriesChart` is affected (currently jg-ferien via ACT-3/ACT-5) —
89
+ this changes rendering behaviour for all of them, in the correct direction (chart canvas now
90
+ actually visible).
91
+ - Getting this wrong a second time (e.g. an `aspectRatio`-only fix that still doesn't reliably drive
92
+ MUI X-Charts' internal measurement) ships a third silent failure in the same component. The
93
+ required unmocked-render test exists specifically to catch that before it reaches a live app again.
94
+
95
+ ## REQUIRED TESTS TO WRITE
96
+ Extend `tests/TimeSeriesChart.test.jsx`. Do NOT run the full suite.
97
+
98
+ 1. **The real bug's mechanism, not a jsdom layout measurement.** An initial attempt at this WO wrote
99
+ an *unmocked* test (real `@mui/x-charts/BarChart`, no `vi.mock`) asserting the rendered chart
100
+ surface's `viewBox` height was non-zero — verified, in practice, to be **vacuous**: it stayed green
101
+ even with the pre-fix code (no `height` prop anywhere), because jsdom's `ResizeObserver`/layout
102
+ handling does not reproduce the real-browser zero-height-parent behaviour this bug depends on. Do
103
+ not repeat that approach. Instead, keep the existing `chartSpy` mock convention (`vi.hoisted` +
104
+ `vi.mock('@mui/x-charts/BarChart', ...)`, already at `tests/TimeSeriesChart.test.jsx:9-10`) and
105
+ assert directly on the props the real `MuiBarChart` receives: `chartSpy.mock.calls.at(-1)[0].height`
106
+ must be a real, non-zero number. This is what actually distinguishes fixed from broken — confirmed
107
+ by reverting the fix and re-running: the prop assertion fails pre-fix (`props.height` is
108
+ `undefined`), the unmocked viewBox assertion does not.
109
+ 2. **Non-vacuity**: this test must fail against the current (pre-fix) code — verify before considering
110
+ the fix complete (see note above — already verified once for the prop-assertion approach).
111
+ 3. Existing mocked-`BarChart` tests (range/toggle/empty-state behaviour) stay as they are.
112
+
113
+ ## CONTEXT PACKAGE — verified current state (Orchestrator, Implementation map)
114
+
115
+ Work from this package; do not explore broadly from scratch — open only the named files to verify.
116
+
117
+ **File to change: `src/components/charts/TimeSeriesChart.jsx`.**
118
+
119
+ Current return statement (verified, full slice, `:150-169`):
120
+ ```jsx
121
+ return (
122
+ <ChartFrame
123
+ title={title}
124
+ subtitle={subtitle}
125
+ toolbar={toolbar}
126
+ loading={loading}
127
+ error={error}
128
+ isEmpty={isDataEmpty}
129
+ >
130
+ {!isDataEmpty && (
131
+ <BarChart
132
+ xAxisLabel={xAxisLabel}
133
+ yAxisLabel={yAxisLabel}
134
+ xAxis={[{ data: data?.xLabels || [] }]}
135
+ series={visibleSeries.map((series) => ({ data: series.data, label: series.label }))}
136
+ palette={visiblePalette}
137
+ />
138
+ )}
139
+ </ChartFrame>
140
+ );
141
+ ```
142
+
143
+ **Reference (working) pattern — `dev/entries.jsx:35-39`:**
144
+ ```jsx
145
+ function BarChartEntry() {
146
+ return (
147
+ <ChartFrame title="Monthly cases" subtitle="Standalone BarChart entry" minHeight={360}>
148
+ <BarChart {...barChartFixture} xAxisLabel="Month" yAxisLabel="Cases" height={320} />
149
+ </ChartFrame>
150
+ );
151
+ }
152
+ ```
153
+ Note `height={320}` on `BarChart` here is NOT `BarChart`'s own `minHeight`/`aspect` wrapper prop — it
154
+ lands in `BarChart`'s `...chartProps` rest spread (`BarChart.jsx:24,34`) and is forwarded straight to
155
+ `MuiBarChart`'s own native `height` prop, which MUI X-Charts uses as a fixed pixel height instead of
156
+ measuring the parent via `ResizeObserver`. This is the confirmed-working mechanism in this repo.
157
+
158
+ **`ChartFrame.jsx:86-99`** — the content `Box`: `sx={{ width:'100%', minHeight, aspectRatio: aspect,
159
+ display:'flex', alignItems:'center', justifyContent:'center' }}`. `alignItems:'center'` means a flex
160
+ child (here, `BarChart`'s own `Box`) is NOT stretched to fill this Box's height — it keeps its own
161
+ intrinsic height. Passing `minHeight` to `ChartFrame` sizes this outer Box correctly for the
162
+ loading/error/empty `Alert`/`CircularProgress` states (which have no fixed height of their own and
163
+ DO benefit from the flex-centered parent height), but does **not**, by itself, give `BarChart`'s
164
+ child `Box` any height — that must come from `BarChart` itself (its own `minHeight`/`aspect`, or the
165
+ raw `height` chartProp per the reference pattern above).
166
+
167
+ **`BarChart.jsx:15-44`** — full file, already shown above in this WO's WHY section — `minHeight`/
168
+ `aspect` are explicit named props (`:22-23`) applied to `BarChart`'s own wrapping `Box` (`:32`);
169
+ anything else lands in `...chartProps` (`:24`) and is spread onto `MuiBarChart` directly (`:34`),
170
+ which is how a raw `height` reaches the underlying MUI component.
171
+
172
+ **Test file: `tests/TimeSeriesChart.test.jsx`** — already mocks `@mui/x-charts/BarChart` via
173
+ `vi.hoisted` + `vi.mock` (`:9-10`) and every existing test reads `chartSpy.mock.calls.at(-1)[0]` to
174
+ assert exact props (e.g. the `series`/`colors` assertions at `:87-89,94-99`). Add the new test in the
175
+ same style, asserting `props.height` — see REQUIRED TESTS above for why an unmocked render test does
176
+ NOT work here.
177
+
178
+ ## TARGET REPO
179
+ `C:\Users\biglmi\Documents\webapps\ui-core-micha`. Branch `main`. Publish per the repo's release
180
+ flow (patch bump — bug fix, no interface change).
181
+
182
+ ## TEST SCOPE FOR THE GATE (orchestrator)
183
+ `tests/TimeSeriesChart.test.jsx`. Not the full suite.
184
+
185
+ ## MINI-HANDOVER (pastable)
186
+
187
+ > Repo: `C:\Users\biglmi\Documents\webapps\ui-core-micha` (branch `main`).
188
+ > Work order: `work-orders/CHART-4.md` — read it fully, then follow the `orchestrate-codex` skill.
189
+ > **Codex workspace is out of credits this session — go straight to the direct-Claude fallback, do
190
+ > not attempt `codex exec` first.**
191
+ >
192
+ > Second bug in `TimeSeriesChart` (after CHART-3): the chart legend renders but the actual bars/axes
193
+ > never appear because neither `ChartFrame` nor `BarChart` ever receive a height — MUI X-Charts'
194
+ > responsive container measures a zero-height parent and draws nothing. `dev/entries.jsx`'s
195
+ > `BarChartEntry` is the proof this is fixable: `minHeight={360}` on `ChartFrame` + `height={320}`
196
+ > forwarded straight to `BarChart` (MUI's own native height prop, not the wrapper's `minHeight`/
197
+ > `aspect`). The required regression test must NOT mock `@mui/x-charts/BarChart` — every existing test
198
+ > in this component mocks it, which is exactly how two sizing/rendering bugs shipped undetected.
199
+
200
+ ## PROGRESS CONTRACT
201
+ Emit `PLAN: <steps>` up front, then a single-line `PROGRESS: [<n>/<total>] <action>` before every
202
+ relevant action and `PROGRESS: [<n>/<total>] done` on completion, spaced so no gap exceeds ~2 min,
203
+ stdout unbuffered, and exactly one final `RESULT: DONE|BLOCKED <reason>`.