@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
|
@@ -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>`.
|