@micha.bigler/ui-core-micha 2.42.0 → 2.42.1
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 +1 -0
- package/dist/components/charts/ChartFrame.js +8 -9
- package/dist/components/charts/chartDefaults.js +13 -3
- package/package.json +1 -1
- package/src/components/charts/ChartFrame.jsx +11 -9
- package/src/components/charts/chartDefaults.js +13 -3
- package/tests/ChartFrame.test.jsx +41 -17
- package/tests/chartDefaults.test.js +10 -0
- package/work-orders/UCM-CHART-9.md +265 -0
package/WORK_ORDERS.md
CHANGED
|
@@ -91,3 +91,4 @@ Two chunks. **Chunk 1** (Part 1 + Part 2 of the Envelope): extended `SPACING_SHO
|
|
|
91
91
|
| THEME-10 | Ein Scatter-Preset, entworfen an drei realen Konsumenten | hram zeichnet **drei** Scatter vollständig von Hand — eigene Achsenlinien, Gitter, Ticks, Tick-Beschriftungen und gedrehte Achsentitel auf fest verdrahteten Leinwänden; aus diesem Paket kommt nur `ChartFrame`. `AllocationPerformancePanel` (720×420, Wolke + Hüllkurve + drei markierte Punkte, kontinuierliche Färbung), `AccessGapScatterPanel` (Bubble-Wolke mit datengetriebenem Radius + beschriftete y=x-Diagonale, **kategoriale** Färbung mit diskreter Legende, beide Achsen fix 0–1) und `OptimizationResultsPanel` (Wolke + gestrichelte Referenzlinie, Status quo als hohler Marker). Das ist keine Nachlässigkeit — **es gibt hier kein Scatter-Preset**, also gab es nichts zu greifen; jede Verbesserung dieses Kits (Achsengrössen, Tick-Formatierung, Label-Platzierung, Rollenpalette) geht an allen dreien vorbei. Der Operator hat diesen Weg bewusst gewählt, statt die Apps MUI X direkt nutzen zu lassen (hram `FIX-15`, Gabelung b). Voller Auftrag [`work-orders/THEME-10.md`](work-orders/THEME-10.md) | 2026-08-14 | done (npm publish 2.41.0, gebündelt mit THEME-11; Konsumenten-Pin-Bumps offen) | ff67fc7 | Tier 3 (Shared-Core, neue öffentliche API). Codex bereits `unavailable` für heute — direkt in Claude implementiert, Autorenschaft geflippt, `reviewer` + `ui_reviewer` liefen parallel (ohnehin Pflicht bei Tier 3). **`reviewer` R1 (P1, behoben):** `sizeYAxisForContent` erwartet rohe Zahlen in `series[].data`, Scatter liefert `{x,y,z,id}`-Objekte — der Fallback fand still NULL Kandidaten, solange kein `yAxis.min/max` gesetzt war (stiller Rückfall auf MUIs Flat-Default). Behoben per Adapter am Call-Site (`yValueSeries`), NICHT am geteilten Helper — BarChart/LineChart bleiben unangetastet. **R2 (P2, behoben):** `dataIndex`-Semantik im Bubble-Modus (post-Sort-Index, nicht Original-Array) war nicht dokumentiert — jetzt im Docstring. **`ui_reviewer` U1 (P2, aufgeklärt statt Code geändert):** `shape:'square'` schien nicht auf die drei Panels rückführbar — tatsächlich rückführbar auf Allocation-Panels `isTargeted`-Punkte (`ShapeGlyph`), im WO nur nicht explizit zitiert; WO-Text nachgezogen. **U2 (P2, behoben):** Optimization-Form war live nicht eigens gerendert — dritte Dev-Harness-Fixture ergänzt und live verifiziert (genau diese Kombination — kein `yAxis.min/max`, keine explizite Palette — deckte R1 auf, die beiden anderen Fixtures umgingen den Fehler zufällig). **U3 (P3, behoben):** Test ergänzt, dass eine Einzelserie mit eigener `.color` nicht als neutral behandelt wird. **U5 (P3, dokumentiert):** WO-Text stellte klar, dass Allocations eigener Status-quo-Punkt ebenfalls hohl ist, nicht nur Optimizations. **U4 (P3, akzeptierter Rest):** fehlende Nicht-visuelle Kennzeichnung von hohl/Form — vorbestehende Lücke im ganzen Chart-Kit, nicht neu eingeführt, nicht in dieser WO behoben. Alle drei benannten Konsumenten-Formen live im eigenen Dev-Harness gerendert und DOM-vermessen (Mark-Counts 6/6, 7/7, 4/4 — keine stillen Drops); ein zweiter echter Bug (Bubble-Radius pro Serie statt Chart-weit) beim Live-Rendern gefunden und vor Commit behoben, Regressionstest ergänzt. Affected-Set grün (78 Tests). **Version noch nicht gebumpt, Publish zurückgehalten** — Operator kündigte THEME-11 an, Version-Bump + Publish sollen gebündelt werden. **Das ursprünglich benannte Einzelkonsumenten-Risiko ist weitgehend erledigt.** Der erste Entwurf (2026-08-14) warnte, eine Abstraktion aus einem einzigen Beispiel friere die falsche Form ein; danach nannte der Operator das Access-Panel, und eine Suche fand ein drittes. Drei unabhängige Konsumenten unterscheiden eine echte Variationsachse von einer Vermutung — und **zwei der Achsen wurden gefunden, nicht entworfen**: kategoriale Färbung mit diskreter Legende und datengetriebener Bubble-Radius mit Z-Order kommen im Allocation-Panel überhaupt nicht vor. Aus einem Panel gebaut hätte das Preset beide verfehlt. Ersetzt wird das Risiko durch die engere Disziplin: **jede Option muss auf eines der drei Panels zurückführbar sein**, sonst kommt sie nicht rein; wächst die API während der Umsetzung, stop and report. **Neue Hauptgefahr ist die Übergeneralisierung:** dieselbe Suche fand zwei weitere handgemalte Panels, die ebenfalls Kreise zeichnen, aber **ein anderes Diagramm sind** — `CalibrationPanel` (Forest-Plot, `MARGIN.left=150`, Punktschätzer + Intervall je Zeile) und `SensitivityAnalysisPanel` (Tornado, `MARGIN.left=220`, `<rect>`-Balken), dazu die Divergenz-Ansicht in `OptimizationResultsPanel`. Kategoriale y-Achse, 150–220 px Zeilenlabels, Zeilendichte als Layoutproblem — **ausdrücklich ausgeschlossen**, eigenes Preset, eigener WO. **Wichtigster Default bleibt:** die undifferenzierte Wolke ist neutral (Rollenregister), NICHT eine KPI-Identitätsfarbe — genau das macht das Allocation-Panel falsch. Das Access-Panel färbt dagegen **zu Recht** kategorial (Division/Settlement sind echte Datendimensionen und kommen bereits aus dem eingefrorenen Familienregister); neutral ist der Default für „keine Dimension kodiert“, kein Farbverbot. Muss THEME-9s Helfer wiederverwenden (`sizeYAxisForContent`, `defaultNumericTickFormatter`, `withAxisDefaults`, `spaceForRotatedTicks`); ein Preset mit eigener Achsenlogik würde genau das wieder aufreissen, was THEME-9 geschlossen hat. Konkrete Regression, die es stoppt: das Access-Panel verdrahtet sechs Grautöne fest (`#F0F0F0`, `#D0D0D0`, `#999`, `#666`, `#555`, `#B0B0B0`), die keine Theme-Änderung erreicht. Nicht am Publish abschliessen: hram `FIX-15` ist der erste Konsument und blockiert darauf; Access und Optimization sind bekannte Konsumenten **ohne eigenen WO** — separat zu planen, nicht hier einzufalten. Pin-Bump zusammen mit den bereits wartenden THEME-8 und THEME-9 fahren. |
|
|
92
92
|
| THEME-11 | Das Kit hat keine Meinung zu Chart-Rändern, also erbt jedes Diagramm im Estate die von MUI | hrams vier KPI-Karten wurden pixelweise zerlegt: Chart 270 px hoch, davon **202 px Plotfläche** — die restlichen 167 px sind Beiwerk, und die Bilanz geht auf die beobachteten ~370 px Kartenhöhe exakt auf. Der Grund für 20 px auf allen vier Seiten: **dieses Paket bildet nie eine Meinung**. `spaceForRotatedTicks` gibt `margin` unverändert zurück, solange die x-Ticks nicht rotiert sind — reicht der Aufrufer nichts, geht `margin: undefined` an `MuiBarChart` und `DEFAULT_MARGINS = {20,20,20,20}` greift. THEME-9 hat dem Kit Achsen*grössen* gegeben; Ränder waren nie in seinem Scope. Neu: `top: 10`, `bottom: 8`, `left: 8`, `right: 16`, plus `ChartFrame`s Titel-`mb: 2` → `mb: 1`. Voller Auftrag [`work-orders/THEME-11.md`](work-orders/THEME-11.md) | 2026-08-14 | done (npm publish 2.41.0, gebündelt mit THEME-10; Konsumenten-Pin-Bumps offen) | bb7794d | Tier 3 (Shared-Core). Codex bereits `unavailable` — direkt in Claude implementiert, Autorenschaft geflippt, `reviewer` + `ui_reviewer` liefen parallel (ohnehin Pflicht bei Tier 3). **Beide Fallen sauber vermieden** (`reviewer`, an beiden Dateien einzeln verifiziert, keine Befunde): `spaceForRotatedTicks` läuft weiterhin auf dem unveränderten Aufrufer-`margin`, `withMarginDefaults` erst danach gemerged — der rotierte Pfad verliert seinen Zusatzplatz nicht. `MUI_CHART_MARGIN_BOTTOM` (20→8) mitgezogen. **`ui_reviewer` U1-U4 (alle P2/P3, dokumentiert statt Code geändert):** die live gemessene Fast-Kollision (gruppierte Zahl "1,000" bei nur ~1px Abstand vom neuen `right:16`) stammt aus MUIs eigener Standard-Tick-Formatierung, nicht aus THEME-9s Formatter (Fehlzuschreibung korrigiert); **`reviewer` verifizierte zusätzlich**, dass KEIN aktueller hram-Konsument diesen Pfad überhaupt trifft (alle nutzen `scaleType:'band'`; `AllocationPerformancePanel` — der ursprünglich befürchtete Fall — importiert nur `ChartFrame`, zeichnet eigenes SVG, geht gar nicht über `BarChart`/`LineChart`) — "kein Konsument hat das" trifft also exakt zu, nicht nur plausibel. Restrisiko dokumentiert für den ersten künftigen linearen Konsumenten. `ChartFrame`s `mb`-Trim bewusst unbedingt (nicht an Chart-Preset gekoppelt) — Titelzeile und Plot-Rand sind unabhängiges Möbel. **Scope-Lücke benannt, nicht eingefaltet:** `ScatterChart` (THEME-10) bekommt noch keinen `withMarginDefaults` — Folge-WO. Affected-Set grün (91 Tests). Gerendert live verifiziert: rotierter Tick-Chart ohne Overlap (15px Abstand zum Achsentitel), linearer x-Achsen-Chart ohne echtes Clipping (schmal: 5px Abstand; breit: MUIs eigene Kürzung greift bei ~1px, nie Overflow). **Warum die Werte pro Seite verschieden sind — das ist der Kern, kein Feinschliff:** `margin.left` liegt **ausserhalb** der y-Achsenbreite (`drawingArea.left = margin.left + axisSizeLeft`, Kommentar des Pakets selbst), ist also reines Polster; `margin.bottom` liegt **unter** dem x-Achsenblock, dessen `DEFAULT_AXIS_SIZE_HEIGHT` das Tick-Label bereits enthält; `margin.top` trägt dagegen echten Inhalt — das oberste y-Tick-Label ist auf seiner Gitterlinie zentriert und ragt eine halbe Caption-Zeilenhöhe (~9 px) über die Plotfläche, **deshalb 10 und nicht 8**. `right` wird bewusst **weniger** getrimmt (16): bei einer Bandachse sitzen die Ticks mittig, bei einer **linearen** x-Achse sitzt das letzte Label auf der Kante und ragt eine halbe Labelbreite (~13 px) hinaus. Nicht vom Skalentyp abhängig machen — das wäre abgeleitetes Verhalten ohne benannten Konsumenten. **Zwei benannte Fallen.** (1) `spaceForRotatedTicks` erkennt „Aufrufer hat gesetzt“ an `margin?.bottom != null`; wird ein Paket-Default **vor** dieser Prüfung injiziert, sehen rotierte Charts ihn als aufrufergesetzt und verlieren ihren Extraplatz — hrams `RatioMetricsPanel` fährt Labels bei −45° und −90° und würde sie beschneiden. (2) `MUI_CHART_MARGIN_BOTTOM = 20` in `chartDefaults.js` ist eine hartkodierte Kopie des MUI-Defaults und Basis für den rotierten Pfad — muss mitwandern. **Per-Seite mergen, nicht das ganze Objekt ersetzen:** Panels, die schon von Hand tunen (`{left:60, right:8, top:20, …}`), müssen auf ihren gesetzten Seiten unberührt bleiben. `CHART_FRAME_ROOT_SX = { p: 2 }` bleibt bewusst stehen — `ChartFrame` umschliesst auch Nicht-Chart-Inhalte, das ist ein eigener Vorschlag mit anderer Streuweite, kein Anhängsel. **Streuweite:** trifft jedes Diagramm in jeder konsumierenden App, und zwar beim Pin-Bump, nicht beim Publish — ein falscher Seitenwert ist damit eine estate-weite Regression. Der gerenderte Check muss deshalb **einen rotierten** und **einen linearen** x-Achsen-Chart enthalten, nicht nur die KPI-Karten. Pin-Bump zusammen mit THEME-8/9/10 fahren. |
|
|
93
93
|
| UCM-CHART-8 | Make the chart presets stop producing dead space below the chart | `minHeight` and `height` are two different things on these presets and the API lets a caller pass both: `minHeight` lands on the WRAPPER Box (`ScatterChart.jsx:260`, same construction in `BarChart.jsx`), `height` sizes the CHART. When `minHeight > height` the difference is dead space BELOW the chart. Measured in hram 2026-08-20: StructuralReachability 420/380 (40 px), ExpertTimeline 340/280 (60 px), ResultsTimeline 320/280 (40 px), GroupMetrics 300/none (chart unsized). The equal-value majority is WHY it survived -- passing both looks harmless and costs nothing when the numbers match, so it spread by copy-paste. Diagnosed and fixed POINTWISE TWICE in hram (`AllocationPerformancePanel.jsx:462-478`, `OptimizationResultsPanel.jsx:928-940`) with the explanation living as a comment inside those two files, where no new panel reads it. One shared API permitting an incoherent pair, not N panel bugs. | 2026-08-20 | done | 11d362c | Tier 3, shared-core -- every consumer inherits it. Full order: [`work-orders/UCM-CHART-8.md`](work-orders/UCM-CHART-8.md). Deliberately NON-breaking: `minHeight` is deprecated in the docstring and COERCED in code, not removed -- removing it would make every consumer's pin bump a Tier-3 change with unnoticed layout regressions, worse than the defect. `minHeight` alone sizes the chart; `minHeight > height` reserves no wrapper padding; equal values byte-identical; a DEV-ONLY warning names the disagreeing pair (no throw -- a shared package must not break a consumer's page over a layout nit). Before publishing, check the other consumers (dcm-based apps, survey-renderer) for `minHeight` on a chart preset and report what was found -- the one way this could read as a regression is a consumer using the padding as spacing. Publishes from `main` with no staging step, so verify the version is live before any consumer bump. hram's call-site sweep is a separate follow-up GATED on that pin bump. Raised by the operator as "why is there still too much whitespace everywhere" -- the recurrence, not the instances, is the subject. **Envelope amended 2026-08-21 after review — the original rule would have broken four live call sites.** The resolution is **three-way, not two-way**: `aspect` is a third input and it changes the answer. Four consumers pass `aspect` **with** `minHeight` and **no** `height` — `hram/AccessGapScatterPanel.jsx:182` (`aspect={1.8}`, `minHeight={320}`) and three sites in `fitness-monitor/BodyHistoryPage.jsx` — and there `minHeight` is a legitimate **floor under an aspect-derived height**, not stray padding. "`minHeight` alone sizes the chart" as first written would have turned all four into fixed-height charts. Same prop shape, opposite intent, which is why the rule cannot be stated on `minHeight`/`height` alone. Also corrected: `ChartFrame` takes `minHeight`/`aspect` too and was missing from the file list, and **`BarChart`, `LineChart` and `ChartFrame` never destructure `height`** — for them the pair exists only via the rest spread, so there is no declared prop to look for. The Risks section's hypothetical consumer check is now **done**, with the four sites as its result; `survey-renderer` is named there as a consumer to check but the standalone repo is retired (2026-07-31). Tests gained the `aspect` case explicitly — it is the one that must fail if the resolution is later re-simplified. **Implemented 2026-08-21, Codex known-unavailable that day** (`.claude/codex-status.md` already carried an `unavailable` line for today at task start, from `cockpit/LIVE-4` and `hram/OPT-8`) — Orchestrator implemented directly in Claude, flipping authorship, so the independent reviewer + ui_reviewer (both mandatory at Tier 3 regardless) covered a diff the Orchestrator itself wrote. `resolveChartHeight`/`warnOnHeightMismatch` added to `chartDefaults.js`; wired into ScatterChart/BarChart/LineChart (destructuring `height` out of the rest spread, forwarding it explicitly) and ChartFrame (new optional `height` prop, applied to its own content box since it has no inner MUI chart to forward to). TimeSeriesChart.jsx left unmodified — it never exposes `minHeight`/`height`/`aspect` to its own caller, only forwards a single internal `CHART_HEIGHT` constant identically to ChartFrame's `minHeight` and BarChart's `height`, confirmed unchanged by the full existing `TimeSeriesChart.test.jsx` suite passing untouched. **`reviewer` (Sonnet)**: no bugs; two P3s — the `height`+`aspect` combination on Bar/Line/Scatter is pre-existing, untested, and not fully governed by the three-way contract (wrapper still gets `aspectRatio` unconditionally while the chart gets a hardcoded `height`), and the TimeSeriesChart 320/320 coincidence is two independently-resolved branches landing on the same value, not literally the "equal values" case — both accepted as out of this WO's scope, no live call site hits either. **`ui_reviewer` (Sonnet)**: one P1 noted-not-a-bug (the minHeight-alone branch now gives every such consumer a fixed chart height, which is exactly Part A's Definition of Done, not an undisclosed change — flagged here as a residual cross-app risk since only the 4 aspect+minHeight sites were confirmed, not every minHeight-alone call site) and one real P2, **fixed**: `warnOnHeightMismatch` was called in the render body, so it re-fired the console.warn on every re-render of a mismatched instance; moved into a `useEffect` keyed on `[minHeight, height]` in all four components. Re-verified 128/128 scoped tests green after the fix (BarChart/LineChart/ScatterChart/ChartFrame/chartDefaults/TimeSeriesChart/packageTreeShaking). Version bumped 2.41.3 → 2.42.0 (new optional `ChartFrame.height` prop + new exports, non-breaking). **Not done in this WO**: the app-wide check for other `minHeight`-alone consumers ui_reviewer flagged (U1) — no consumer sweep is in scope per Part A's own "Deliberately NOT done"; flagging for the record, not a blocker. Publish-then-verify-then-bump-consumer-pins sequencing from the Envelope still applies before hram's own call-site cleanup. |
|
|
94
|
+
| UCM-CHART-9 | `ChartFrame` is a card, not a chart: it must never take a fixed height | REGRESSION in published 2.42.0, observed on hram staging. UCM-CHART-8 wired `resolveChartHeight()` through the four chart presets AND through `ChartFrame`. Correct for the presets, wrong for the frame: in the `minHeight`-alone branch the resolver returns `chartHeight = minHeight`, and `ChartFrame` applies it as `height` on its content box. A preset's box holds one chart; **`ChartFrame`'s box holds a whole card** -- title, toolbar, chart, legend, footnotes, export links. The floor became a fixed height, the content overflows, and ADJACENT CARDS VISIBLY OVERLAP (card title drawn over the chart above it, three sets of Export SVG/PNG stacked at different offsets). UCM-CHART-8's own Files note recorded that `ChartFrame` never destructures `height` -- the implementation then gave it one. | 2026-08-21 | done | 2026-08-22: `ChartFrame` fix + version bump + accurate mismatch warning | Tier 3, shared-core, live regression in `latest`. Full order: [`work-orders/UCM-CHART-9.md`](work-orders/UCM-CHART-9.md). Operator chose FIX-FORWARD over reverting hram's pin (2026-08-21), because 2.42.0 also appears to have stopped hram's Detailed-Results React #185 crash -- plausible mechanism is that deterministic sizing removed a resize-driven setState loop, but that is a HYPOTHESIS, not a finding, and this WO must re-check whether the crash returns (if it does, that is a finding for HRAM-RES-29, not a reason to undo this). Do NOT revert UCM-CHART-8: its preset behaviour is correct and includes the `aspect` branch four live call sites depend on; this WO narrows one consumer of the resolver only, and any edit needed to a preset test means the change has leaked. **Codex:** out of credits on the one allowed probe today (`.claude/codex-status.md`, 2026-08-22) -- Orchestrator implemented directly, flipping authorship; `reviewer` + `ui_reviewer` were already mandatory at Tier 3, so no routing change. **Reviews:** `reviewer` -- 2 findings (R1 version not bumped in-diff, fixed by bumping `package.json` to 2.42.1 in this commit so `publish.yml`'s version-increase gate fires on push; R2 jsdom-can't-measure-layout note on one test, non-blocking, covered by the live-browser check below). `ui_reviewer` -- 2 findings (U1 `warnOnHeightMismatch`'s message was left factually wrong for `ChartFrame` post-fix -- FIXED: added a `heightWins` param, `ChartFrame` now gets its own accurate wording, test coverage added in both `chartDefaults.test.js` and `ChartFrame.test.jsx`; U2 height-alone-on-ChartFrame is now a silent no-op with no dev warning -- accepted as-is, out of scope for this regression fix). Both reviews clean after fixes, no open findings. **Tests:** affected-area set (`ChartFrame`, `chartDefaults`, all four presets) -- 129/129 green, preset assertions byte-identical/unchanged. **Rendered check:** this repo's own dev harness (`npm run dev`, `dev/entries.jsx` "Charts / ScatterChart (allocation shape)") driven live in the internal preview browser at 375px and 1280px -- DOM/computed-style inspection (no screenshot capture available in this session; declared substitution per the gate's fallback clause), confirmed the content box carries no fixed `height` and, after forcing the inner chart element's rendered height up to 900px/1100px to reproduce the regression scenario, the box grew to match with zero scrollHeight/clientHeight overflow at both widths. The Envelope's full five-card check (Sensitivity, Access, Access Ladder, Allocation, Optimization, rendered in hram itself) is **NOT done by this WO** -- this repo's harness only has 3 of the 5 named cards and no live hram instance was linked against this build. Carried forward as a mandatory precondition on whichever WO next bumps hram's `ui-core-micha` pin past 2.42.0. 2.42.0 stays flagged do-not-adopt; 2.42.1 publishes automatically via `publish.yml` on this push (version-increase gate). PROCESS NOTE, recorded deliberately: 2.42.0 shipped on 188 green unit tests that say nothing about layout -- the rendered check above is the layout-level evidence this WO exists to add, even though it stops short of the full hram five-card scope. |
|
|
@@ -4,16 +4,17 @@ import { Alert, Box, Button, CircularProgress, Paper, Stack, Typography, } from
|
|
|
4
4
|
import { useTranslation } from 'react-i18next';
|
|
5
5
|
import { useTheme } from '@mui/material/styles';
|
|
6
6
|
import { exportChartPng, exportChartSvg } from './exportChart';
|
|
7
|
-
import {
|
|
7
|
+
import { warnOnHeightMismatch } from './chartDefaults';
|
|
8
8
|
export const CHART_FRAME_ROOT_SX = { p: 2 };
|
|
9
9
|
export const CHART_FRAME_ALERT_SX = { mt: 2 };
|
|
10
10
|
/**
|
|
11
11
|
* Chart-type-agnostic chart scaffolding. Children may be an X-Charts primitive or a bespoke SVG.
|
|
12
12
|
*
|
|
13
|
-
* `minHeight` (CHART-
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* `
|
|
13
|
+
* `minHeight` (CHART-9): always just a floor on the content box, which holds a whole card --
|
|
14
|
+
* title, toolbar, chart, legend, footnotes, export links -- not a single chart. `height` is never
|
|
15
|
+
* applied to the box (unlike the four chart presets, whose box holds exactly one chart and do use
|
|
16
|
+
* `resolveChartHeight`'s `height`/`aspect` resolution); it is only compared against `minHeight` for
|
|
17
|
+
* `warnOnHeightMismatch`'s dev-mode note.
|
|
17
18
|
*/
|
|
18
19
|
export function ChartFrame({ title, subtitle, toolbar, controls, loading = false, error = false, isEmpty = false, emptyMessage, minHeight, height, aspect, exportOptions = false, onExportSvg, onExportPng, meta, ariaLabel, children, variant = 'outlined', titleVariant = 'h6', sx, }) {
|
|
19
20
|
const { t } = useTranslation();
|
|
@@ -27,8 +28,7 @@ export function ChartFrame({ title, subtitle, toolbar, controls, loading = false
|
|
|
27
28
|
const hasCustomAriaLabel = Boolean(ariaLabel);
|
|
28
29
|
const chartLabel = ariaLabel || title;
|
|
29
30
|
const callerSx = Array.isArray(sx) ? sx : [sx];
|
|
30
|
-
useEffect(() => warnOnHeightMismatch('ChartFrame', { minHeight, height }), [minHeight, height]);
|
|
31
|
-
const { wrapperMinHeight, chartHeight } = resolveChartHeight({ minHeight, height, aspect });
|
|
31
|
+
useEffect(() => warnOnHeightMismatch('ChartFrame', { minHeight, height }, { heightWins: false }), [minHeight, height]);
|
|
32
32
|
const runExport = async (type) => {
|
|
33
33
|
setExportError(false);
|
|
34
34
|
try {
|
|
@@ -55,8 +55,7 @@ export function ChartFrame({ title, subtitle, toolbar, controls, loading = false
|
|
|
55
55
|
}
|
|
56
56
|
return (_jsxs(Paper, { variant: variant, sx: [CHART_FRAME_ROOT_SX, ...callerSx], children: [_jsxs(Box, { sx: { display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 2, mb: 1 }, children: [_jsxs(Box, { children: [_jsx(Typography, { id: titleId, variant: titleVariant, children: title }), subtitle && _jsx(Typography, { variant: "body2", color: "text.secondary", children: subtitle })] }), toolbar && _jsx(Box, { children: toolbar })] }), controls && _jsx(Box, { sx: { mb: 2 }, children: controls }), _jsx(Box, { ref: chartRef, role: "img", "aria-label": chartLabel, "aria-labelledby": hasCustomAriaLabel ? undefined : titleId, sx: {
|
|
57
57
|
width: '100%',
|
|
58
|
-
minHeight
|
|
59
|
-
height: chartHeight,
|
|
58
|
+
minHeight,
|
|
60
59
|
aspectRatio: aspect,
|
|
61
60
|
display: 'flex',
|
|
62
61
|
alignItems: 'center',
|
|
@@ -275,16 +275,26 @@ export function resolveChartHeight({ minHeight, height, aspect }) {
|
|
|
275
275
|
* -- the incoherent pair this WO closes. Not a throw: a shared package throwing on a prop
|
|
276
276
|
* combination in production would break a consumer's page over a layout nit. Silent on the
|
|
277
277
|
* equal-value majority and on the legitimate `minHeight` + `aspect` (no `height`) combination.
|
|
278
|
+
*
|
|
279
|
+
* CHART-9: `heightWins` distinguishes the two shapes this fires from. The four chart presets
|
|
280
|
+
* (default, `heightWins: true`) DO apply `resolveChartHeight`'s `height` to their box, so the
|
|
281
|
+
* original "height wins" wording is accurate there. `ChartFrame` (`heightWins: false`) never
|
|
282
|
+
* applies `height` to its box at all -- `minHeight` always keeps reserving the floor -- so it gets
|
|
283
|
+
* its own accurate wording instead of reusing the presets' story.
|
|
278
284
|
*/
|
|
279
|
-
export function warnOnHeightMismatch(componentName, { minHeight, height }) {
|
|
285
|
+
export function warnOnHeightMismatch(componentName, { minHeight, height }, { heightWins = true } = {}) {
|
|
280
286
|
if (process.env.NODE_ENV === 'production')
|
|
281
287
|
return;
|
|
282
288
|
if (minHeight == null || height == null || minHeight === height)
|
|
283
289
|
return;
|
|
290
|
+
const resolution = heightWins
|
|
291
|
+
? 'they disagree, so height wins and the wrapper no longer reserves the extra space below the '
|
|
292
|
+
+ 'chart. Pass only height, or drop minHeight if it should just track height.'
|
|
293
|
+
: 'they disagree; height is ignored here and minHeight keeps sizing the wrapper as a floor. '
|
|
294
|
+
+ 'Pass only minHeight, or drop height if it was meant to size the whole card.';
|
|
284
295
|
// eslint-disable-next-line no-console
|
|
285
296
|
console.warn(`[ui-core-micha] <${componentName}> received both minHeight={${minHeight}} and height={${height}}; `
|
|
286
|
-
+
|
|
287
|
-
+ 'chart. Pass only height, or drop minHeight if it should just track height.');
|
|
297
|
+
+ resolution);
|
|
288
298
|
}
|
|
289
299
|
export function withChartSlotDefaults(slotProps, legendPosition) {
|
|
290
300
|
var _a;
|
package/package.json
CHANGED
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
import { useTranslation } from 'react-i18next';
|
|
12
12
|
import { useTheme } from '@mui/material/styles';
|
|
13
13
|
import { exportChartPng, exportChartSvg } from './exportChart';
|
|
14
|
-
import {
|
|
14
|
+
import { warnOnHeightMismatch } from './chartDefaults';
|
|
15
15
|
|
|
16
16
|
export const CHART_FRAME_ROOT_SX = { p: 2 };
|
|
17
17
|
export const CHART_FRAME_ALERT_SX = { mt: 2 };
|
|
@@ -19,10 +19,11 @@ export const CHART_FRAME_ALERT_SX = { mt: 2 };
|
|
|
19
19
|
/**
|
|
20
20
|
* Chart-type-agnostic chart scaffolding. Children may be an X-Charts primitive or a bespoke SVG.
|
|
21
21
|
*
|
|
22
|
-
* `minHeight` (CHART-
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* `
|
|
22
|
+
* `minHeight` (CHART-9): always just a floor on the content box, which holds a whole card --
|
|
23
|
+
* title, toolbar, chart, legend, footnotes, export links -- not a single chart. `height` is never
|
|
24
|
+
* applied to the box (unlike the four chart presets, whose box holds exactly one chart and do use
|
|
25
|
+
* `resolveChartHeight`'s `height`/`aspect` resolution); it is only compared against `minHeight` for
|
|
26
|
+
* `warnOnHeightMismatch`'s dev-mode note.
|
|
26
27
|
*/
|
|
27
28
|
export function ChartFrame({
|
|
28
29
|
title,
|
|
@@ -58,8 +59,10 @@ export function ChartFrame({
|
|
|
58
59
|
const chartLabel = ariaLabel || title;
|
|
59
60
|
const callerSx = Array.isArray(sx) ? sx : [sx];
|
|
60
61
|
|
|
61
|
-
useEffect(
|
|
62
|
-
|
|
62
|
+
useEffect(
|
|
63
|
+
() => warnOnHeightMismatch('ChartFrame', { minHeight, height }, { heightWins: false }),
|
|
64
|
+
[minHeight, height],
|
|
65
|
+
);
|
|
63
66
|
|
|
64
67
|
const runExport = async (type) => {
|
|
65
68
|
setExportError(false);
|
|
@@ -113,8 +116,7 @@ export function ChartFrame({
|
|
|
113
116
|
aria-labelledby={hasCustomAriaLabel ? undefined : titleId}
|
|
114
117
|
sx={{
|
|
115
118
|
width: '100%',
|
|
116
|
-
minHeight
|
|
117
|
-
height: chartHeight,
|
|
119
|
+
minHeight,
|
|
118
120
|
aspectRatio: aspect,
|
|
119
121
|
display: 'flex',
|
|
120
122
|
alignItems: 'center',
|
|
@@ -302,15 +302,25 @@ export function resolveChartHeight({ minHeight, height, aspect }) {
|
|
|
302
302
|
* -- the incoherent pair this WO closes. Not a throw: a shared package throwing on a prop
|
|
303
303
|
* combination in production would break a consumer's page over a layout nit. Silent on the
|
|
304
304
|
* equal-value majority and on the legitimate `minHeight` + `aspect` (no `height`) combination.
|
|
305
|
+
*
|
|
306
|
+
* CHART-9: `heightWins` distinguishes the two shapes this fires from. The four chart presets
|
|
307
|
+
* (default, `heightWins: true`) DO apply `resolveChartHeight`'s `height` to their box, so the
|
|
308
|
+
* original "height wins" wording is accurate there. `ChartFrame` (`heightWins: false`) never
|
|
309
|
+
* applies `height` to its box at all -- `minHeight` always keeps reserving the floor -- so it gets
|
|
310
|
+
* its own accurate wording instead of reusing the presets' story.
|
|
305
311
|
*/
|
|
306
|
-
export function warnOnHeightMismatch(componentName, { minHeight, height }) {
|
|
312
|
+
export function warnOnHeightMismatch(componentName, { minHeight, height }, { heightWins = true } = {}) {
|
|
307
313
|
if (process.env.NODE_ENV === 'production') return;
|
|
308
314
|
if (minHeight == null || height == null || minHeight === height) return;
|
|
315
|
+
const resolution = heightWins
|
|
316
|
+
? 'they disagree, so height wins and the wrapper no longer reserves the extra space below the '
|
|
317
|
+
+ 'chart. Pass only height, or drop minHeight if it should just track height.'
|
|
318
|
+
: 'they disagree; height is ignored here and minHeight keeps sizing the wrapper as a floor. '
|
|
319
|
+
+ 'Pass only minHeight, or drop height if it was meant to size the whole card.';
|
|
309
320
|
// eslint-disable-next-line no-console
|
|
310
321
|
console.warn(
|
|
311
322
|
`[ui-core-micha] <${componentName}> received both minHeight={${minHeight}} and height={${height}}; `
|
|
312
|
-
+
|
|
313
|
-
+ 'chart. Pass only height, or drop minHeight if it should just track height.',
|
|
323
|
+
+ resolution,
|
|
314
324
|
);
|
|
315
325
|
}
|
|
316
326
|
|
|
@@ -212,17 +212,37 @@ describe('ChartFrame', () => {
|
|
|
212
212
|
expect(screen.getByText('Aucune donnée disponible.')).toBeTruthy();
|
|
213
213
|
});
|
|
214
214
|
|
|
215
|
-
// CHART-
|
|
216
|
-
|
|
215
|
+
// CHART-9: ChartFrame's content box is a whole CARD (title, toolbar, chart, legend, footnotes,
|
|
216
|
+
// export links), not a single chart -- unlike the four chart presets, which do apply
|
|
217
|
+
// resolveChartHeight's height/aspect resolution to their own box. ChartFrame's box must never
|
|
218
|
+
// take a fixed `height`; `minHeight` is always just a floor content can exceed.
|
|
219
|
+
describe('minHeight/height resolution (CHART-9)', () => {
|
|
217
220
|
function contentBoxStyle() {
|
|
218
221
|
return window.getComputedStyle(screen.getByTestId('chart-body').parentElement);
|
|
219
222
|
}
|
|
220
223
|
|
|
221
|
-
it('sizes the content box from minHeight alone
|
|
224
|
+
it('sizes the content box from minHeight alone as a floor, never a fixed height', () => {
|
|
222
225
|
renderFrame({ minHeight: 300 });
|
|
223
226
|
const style = contentBoxStyle();
|
|
224
227
|
expect(style.minHeight).toBe('300px');
|
|
225
|
-
expect(style.height).toBe('
|
|
228
|
+
expect(style.height).toBe('auto');
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('does not receive a fixed height when content exceeds minHeight -- the regression itself', () => {
|
|
232
|
+
const i18n = i18next.createInstance();
|
|
233
|
+
i18n.init({ lng: 'en', resources, interpolation: { escapeValue: false } });
|
|
234
|
+
render(
|
|
235
|
+
<ThemeProvider theme={createTheme()}>
|
|
236
|
+
<I18nextProvider i18n={i18n}>
|
|
237
|
+
<ChartFrame title="Chart title" minHeight={100}>
|
|
238
|
+
<svg data-testid="chart-body" style={{ height: 500 }} />
|
|
239
|
+
</ChartFrame>
|
|
240
|
+
</I18nextProvider>
|
|
241
|
+
</ThemeProvider>,
|
|
242
|
+
);
|
|
243
|
+
const style = contentBoxStyle();
|
|
244
|
+
expect(style.minHeight).toBe('100px');
|
|
245
|
+
expect(style.height).toBe('auto');
|
|
226
246
|
});
|
|
227
247
|
|
|
228
248
|
it('leaves minHeight as a floor and gives no fixed height when aspect is set (no height)', () => {
|
|
@@ -233,25 +253,29 @@ describe('ChartFrame', () => {
|
|
|
233
253
|
expect(style.aspectRatio).toBe('1.8 / 1');
|
|
234
254
|
});
|
|
235
255
|
|
|
236
|
-
it('
|
|
237
|
-
renderFrame({ minHeight: 420, height: 380 });
|
|
256
|
+
it('does not apply height as a fixed box height even when passed explicitly, and does not throw', () => {
|
|
257
|
+
expect(() => renderFrame({ minHeight: 420, height: 380 })).not.toThrow();
|
|
238
258
|
const style = contentBoxStyle();
|
|
239
|
-
expect(style.minHeight).toBe('
|
|
240
|
-
expect(style.height).toBe('
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
it('is byte-identical when minHeight equals height', () => {
|
|
244
|
-
renderFrame({ minHeight: 320, height: 320 });
|
|
245
|
-
const style = contentBoxStyle();
|
|
246
|
-
expect(style.minHeight).toBe('320px');
|
|
247
|
-
expect(style.height).toBe('320px');
|
|
259
|
+
expect(style.minHeight).toBe('420px');
|
|
260
|
+
expect(style.height).toBe('auto');
|
|
248
261
|
});
|
|
249
262
|
|
|
250
|
-
it('
|
|
263
|
+
it('ignores height when passed alone, with no minHeight floor either', () => {
|
|
251
264
|
renderFrame({ height: 280 });
|
|
252
265
|
const style = contentBoxStyle();
|
|
253
266
|
expect(style.minHeight).toBe('auto');
|
|
254
|
-
expect(style.height).toBe('
|
|
267
|
+
expect(style.height).toBe('auto');
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it('warns with wording accurate to its own behaviour -- minHeight wins, not height', () => {
|
|
271
|
+
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
272
|
+
renderFrame({ minHeight: 420, height: 380 });
|
|
273
|
+
expect(warnSpy).toHaveBeenCalledOnce();
|
|
274
|
+
const message = warnSpy.mock.calls[0][0];
|
|
275
|
+
expect(message).toContain('ChartFrame');
|
|
276
|
+
expect(message).toContain('height is ignored');
|
|
277
|
+
expect(message).not.toContain('height wins');
|
|
278
|
+
warnSpy.mockRestore();
|
|
255
279
|
});
|
|
256
280
|
});
|
|
257
281
|
});
|
|
@@ -505,4 +505,14 @@ describe('warnOnHeightMismatch (CHART-8)', () => {
|
|
|
505
505
|
warnOnHeightMismatch('BarChart', { minHeight: undefined, height: 280 });
|
|
506
506
|
expect(warn).not.toHaveBeenCalled();
|
|
507
507
|
});
|
|
508
|
+
|
|
509
|
+
it('gives ChartFrame-accurate wording when heightWins is false (CHART-9)', () => {
|
|
510
|
+
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
511
|
+
warnOnHeightMismatch('ChartFrame', { minHeight: 420, height: 380 }, { heightWins: false });
|
|
512
|
+
expect(warn).toHaveBeenCalledOnce();
|
|
513
|
+
const message = warn.mock.calls[0][0];
|
|
514
|
+
expect(message).toContain('ChartFrame');
|
|
515
|
+
expect(message).toContain('height is ignored');
|
|
516
|
+
expect(message).not.toContain('height wins');
|
|
517
|
+
});
|
|
508
518
|
});
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
# UCM-CHART-9 — `ChartFrame` is a card, not a chart: it must never take a fixed height
|
|
2
|
+
|
|
3
|
+
- **Repo:** `ui-core-micha`, branch `main` (shared-core; publishes from `main`, no staging step)
|
|
4
|
+
- **Tier:** 3 — shared-core, and this is a **live layout regression in a published version**.
|
|
5
|
+
- **Status:** planned
|
|
6
|
+
- **Workstream:** `CHART-*`
|
|
7
|
+
- **Files:** `src/components/charts/ChartFrame.jsx` and its tests. `chartDefaults.js` only if the
|
|
8
|
+
resolver needs a second shape — see below; the preset behaviour is correct and must not change.
|
|
9
|
+
|
|
10
|
+
> **Why — regression, observed on staging after hram bumped to 2.42.0.** `UCM-CHART-8` wired
|
|
11
|
+
> `resolveChartHeight()` through the four chart presets **and through `ChartFrame`**. For the
|
|
12
|
+
> presets that is right. For `ChartFrame` it is not, and the difference is what the box contains.
|
|
13
|
+
>
|
|
14
|
+
> In the `minHeight` alone / no `height` / no `aspect` branch, `resolveChartHeight` returns
|
|
15
|
+
> `chartHeight = minHeight`, and `ChartFrame` now applies it:
|
|
16
|
+
>
|
|
17
|
+
> ```diff
|
|
18
|
+
> - minHeight,
|
|
19
|
+
> + minHeight: wrapperMinHeight,
|
|
20
|
+
> + height: chartHeight,
|
|
21
|
+
> ```
|
|
22
|
+
>
|
|
23
|
+
> **A chart preset's box contains exactly one chart. `ChartFrame`'s box contains a whole card** —
|
|
24
|
+
> title, toolbar, the chart, a legend, footnotes, export links. Turning its `minHeight` floor into a
|
|
25
|
+
> fixed `height` makes the box shorter than its content, the content overflows, and **adjacent cards
|
|
26
|
+
> visibly overlap**: the operator's screenshot shows a card title rendered over the chart of the card
|
|
27
|
+
> above it and three sets of "Export SVG / Export PNG" stacked at different offsets.
|
|
28
|
+
>
|
|
29
|
+
> `UCM-CHART-8`'s own Files note recorded that `ChartFrame` "never destructures `height`" — and the
|
|
30
|
+
> implementation then gave it one. The rule was written for presets and applied to a frame.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Part A — Envelope (authoritative WHAT/WHY)
|
|
35
|
+
|
|
36
|
+
### Goal
|
|
37
|
+
|
|
38
|
+
`ChartFrame` stops setting a fixed `height` on its content box. Its `minHeight` goes back to meaning
|
|
39
|
+
what its name says — a floor that content can exceed. The four chart presets keep `UCM-CHART-8`'s
|
|
40
|
+
three-way resolution unchanged.
|
|
41
|
+
|
|
42
|
+
### Definition of Done
|
|
43
|
+
|
|
44
|
+
- [ ] **`ChartFrame` never applies `chartHeight` to its content box.** Its box takes `minHeight` as a
|
|
45
|
+
floor, as it did before 2.42.0. Whether that is done by not calling `resolveChartHeight` at
|
|
46
|
+
all, or by giving the resolver an explicit frame shape, is the implementer's call — but
|
|
47
|
+
`ChartFrame` must not end up with a fixed `height` in any branch.
|
|
48
|
+
- [ ] **A `ChartFrame` whose content exceeds `minHeight` grows.** This is the regression, stated as
|
|
49
|
+
the acceptance criterion: content taller than the floor must extend the box, never overflow it.
|
|
50
|
+
- [ ] **The four presets are untouched.** `ScatterChart`, `BarChart`, `LineChart`, `TimeSeriesChart`
|
|
51
|
+
keep `UCM-CHART-8` exactly as it landed — including the `aspect` branch that four live call
|
|
52
|
+
sites depend on. This WO narrows one consumer of the resolver; it does not revisit the rule.
|
|
53
|
+
- [ ] **`ChartFrame`'s JSDoc is corrected.** 2.42.0 added a paragraph claiming `minHeight`
|
|
54
|
+
"sizes the content box itself" when there is no `height`/`aspect`. That sentence is the bug in
|
|
55
|
+
prose form and must go.
|
|
56
|
+
- [ ] **`warnOnHeightMismatch` stays** on `ChartFrame` — a caller passing both is still worth a
|
|
57
|
+
dev-mode note, even though the frame no longer acts on `height`.
|
|
58
|
+
|
|
59
|
+
### Non-goals
|
|
60
|
+
|
|
61
|
+
- Do **not** revert `UCM-CHART-8`. Its preset behaviour is correct and is the fix hram needs; only
|
|
62
|
+
the frame's participation was wrong.
|
|
63
|
+
- Do not change `resolveChartHeight`'s three-way rule for presets.
|
|
64
|
+
- No consumer-side change in this WO.
|
|
65
|
+
|
|
66
|
+
### Risks
|
|
67
|
+
|
|
68
|
+
- **`ChartFrame` is used by every Research card in hram and by other consumers.** The change restores
|
|
69
|
+
pre-2.42.0 behaviour for it, so the risk is low in direction — but it is still a shared-core
|
|
70
|
+
publish, and the rendered check below is what proves it.
|
|
71
|
+
- 2.42.0 is `latest` on the registry and carries this regression. Publish 2.42.1 promptly; note in
|
|
72
|
+
the register that 2.42.0 should not be adopted by a new consumer.
|
|
73
|
+
- **hram's Detailed Results tab is fixed as of 2.42.0** (operator-confirmed, 2026-08-21) — it
|
|
74
|
+
crashed with React #185 before the bump (`HRAM-RES-29` item 1) and does not any more. What is
|
|
75
|
+
**not** established is *why*: the plausible mechanism is that deterministic sizing removed a
|
|
76
|
+
resize-driven `setState` loop, but nobody has traced it.
|
|
77
|
+
**Consequence for this WO, and the only reason it is mentioned here:** since we do not know what
|
|
78
|
+
fixed it, we cannot know whether moving `ChartFrame` back toward a floor disturbs it. Re-check the
|
|
79
|
+
tab after this lands. If the crash returns, that is a finding for `HRAM-RES-29` and a sizing
|
|
80
|
+
dependency worth understanding — not a reason to undo this fix.
|
|
81
|
+
|
|
82
|
+
### Tests to WRITE — narrow
|
|
83
|
+
|
|
84
|
+
- **The regression test:** a `ChartFrame` with `minHeight` and no `height`/`aspect`, containing
|
|
85
|
+
content taller than `minHeight`, does **not** receive a fixed `height` on its content box.
|
|
86
|
+
- `ChartFrame` with `height` explicitly passed: `height` is still not applied as a fixed box height
|
|
87
|
+
(the frame is not a chart), and no exception is thrown.
|
|
88
|
+
- The four presets' existing `UCM-CHART-8` tests stay green **unchanged** — if one of them has to be
|
|
89
|
+
edited, the change has leaked out of `ChartFrame` and that is a stop-and-report.
|
|
90
|
+
|
|
91
|
+
**Rendered check before the hram pin bump — mandatory, and the reason this WO exists.** The unit
|
|
92
|
+
tests for 2.42.0 were 188 green and said nothing about layout, because they cannot. Before hram's pin
|
|
93
|
+
moves again, the affected cards are checked rendered at 375 px and 1280 px: Sensitivity, Access,
|
|
94
|
+
Access Ladder, Allocation, Optimization. Named in the register note, with the substitution declared
|
|
95
|
+
if capture is unavailable.
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Part B — Implementation map — ADDRESSED TO THE IMPLEMENTER
|
|
100
|
+
|
|
101
|
+
### Context package
|
|
102
|
+
|
|
103
|
+
**Named files to change:**
|
|
104
|
+
- `src/components/charts/ChartFrame.jsx` — the fix (below).
|
|
105
|
+
- `tests/ChartFrame.test.jsx:215-256` — the `describe('minHeight/height resolution (CHART-8)', …)`
|
|
106
|
+
block. It currently **asserts the bug as correct behaviour** (e.g. `minHeight` alone →
|
|
107
|
+
`style.height` toBe `'300px'`). Rewrite it to assert the fixed behaviour; rename the block to
|
|
108
|
+
`(CHART-9)`.
|
|
109
|
+
|
|
110
|
+
**Do NOT touch (verified against the landed code):**
|
|
111
|
+
- `src/components/charts/chartDefaults.js` — `resolveChartHeight` (lines ~289-298) and
|
|
112
|
+
`warnOnHeightMismatch` (lines ~306-315) keep their exact current contract. If the fix seems to need
|
|
113
|
+
a change here, that means the change has leaked out of `ChartFrame` — stop and report, don't edit.
|
|
114
|
+
- `src/components/charts/{BarChart,LineChart,ScatterChart,TimeSeriesChart}.jsx` — all four still call
|
|
115
|
+
`resolveChartHeight({ minHeight, height, aspect })` for their OWN box, unchanged.
|
|
116
|
+
- `tests/chartDefaults.test.js:441+` (`describe('resolveChartHeight (CHART-8)', …)`) and the four
|
|
117
|
+
presets' own test files — must stay green **unchanged**. If one needs an edit, stop and report.
|
|
118
|
+
|
|
119
|
+
**The bug, exactly:** `ChartFrame.jsx:61-62` calls `resolveChartHeight({ minHeight, height, aspect })`
|
|
120
|
+
and applies the result to its content box at `:109-125`:
|
|
121
|
+
```jsx
|
|
122
|
+
sx={{
|
|
123
|
+
width: '100%',
|
|
124
|
+
minHeight: wrapperMinHeight,
|
|
125
|
+
height: chartHeight,
|
|
126
|
+
aspectRatio: aspect,
|
|
127
|
+
...
|
|
128
|
+
}}
|
|
129
|
+
```
|
|
130
|
+
In the "no `height`, no `aspect`" branch, `resolveChartHeight` returns `chartHeight = minHeight` —
|
|
131
|
+
correct for a preset's box (which holds exactly one chart) but wrong for `ChartFrame`'s box (which
|
|
132
|
+
holds a whole card: title, toolbar, chart, legend, footnotes, export links). A fixed `height` there
|
|
133
|
+
makes the box shorter than its content, and the content overflows into the next card.
|
|
134
|
+
|
|
135
|
+
**What "restore pre-2.42.0 behaviour" means concretely** — before `UCM-CHART-8` (commit `11d362c`),
|
|
136
|
+
`ChartFrame` had no `height` prop at all and its content box was:
|
|
137
|
+
```jsx
|
|
138
|
+
sx={{
|
|
139
|
+
width: '100%',
|
|
140
|
+
minHeight,
|
|
141
|
+
aspectRatio: aspect,
|
|
142
|
+
display: 'flex',
|
|
143
|
+
alignItems: 'center',
|
|
144
|
+
justifyContent: 'center',
|
|
145
|
+
}}
|
|
146
|
+
```
|
|
147
|
+
i.e. `minHeight` applied raw, straight from props, never touched by any resolver; no `height` was
|
|
148
|
+
ever set on the box.
|
|
149
|
+
|
|
150
|
+
**The fix:** `ChartFrame`'s content box must end up equivalent to that — `minHeight` (raw, from
|
|
151
|
+
props) as the box's only height-related sx property besides `aspectRatio`, and nothing ever sets
|
|
152
|
+
`height` on the box. The `height` prop stays in `ChartFrame`'s signature (do not remove it — it is
|
|
153
|
+
still a documented prop and `warnOnHeightMismatch('ChartFrame', { minHeight, height })` at line 61
|
|
154
|
+
must keep firing on a caller passing both `minHeight` and a disagreeing `height`), it is simply no
|
|
155
|
+
longer used to size the box. Whether you implement this by dropping the `resolveChartHeight` call
|
|
156
|
+
from `ChartFrame` entirely (simplest — matches the pre-2.42.0 shape exactly) or by giving the
|
|
157
|
+
resolver a second, frame-specific shape in `chartDefaults.js` that never returns a `chartHeight` is
|
|
158
|
+
your call — but per the non-goals below, the existing `resolveChartHeight` export and its three-way
|
|
159
|
+
preset contract must not change.
|
|
160
|
+
|
|
161
|
+
**JSDoc fix:** `ChartFrame.jsx:19-26`'s docblock currently states: *"otherwise (no `height`, no
|
|
162
|
+
`aspect`) it sizes the content box itself. Once `height` is set, `height` sizes the box…"* — that
|
|
163
|
+
sentence describes the bug as intended behaviour and must be corrected to state that `minHeight` is
|
|
164
|
+
always a floor on `ChartFrame`'s box and `height` is never applied to it (only used for the
|
|
165
|
+
dev-mode mismatch warning).
|
|
166
|
+
|
|
167
|
+
**Regression check while you're in there:** hram's Detailed Results tab crashed with React #185
|
|
168
|
+
before the 2.42.0 bump and stopped crashing after it (`HRAM-RES-29` item 1, unconfirmed hypothesis:
|
|
169
|
+
deterministic sizing removed a resize-driven `setState` loop). This fix moves `ChartFrame` back
|
|
170
|
+
toward non-deterministic sizing for that one prop. You are not expected to reproduce or verify the
|
|
171
|
+
hram-side crash — the Orchestrator does that separately after your diff lands — but do not add any
|
|
172
|
+
new resize-driven effect to `ChartFrame` while touching this code.
|
|
173
|
+
|
|
174
|
+
Work from this package; do not explore broadly from scratch — open only the named files to verify.
|
|
175
|
+
If you must dig deeper, delegate to a read-only Explore sub-agent (Haiku).
|
|
176
|
+
|
|
177
|
+
### Target repo working directory (absolute)
|
|
178
|
+
|
|
179
|
+
`C:\Users\Micha Bigler\Documents\webapps\ui-core-micha`
|
|
180
|
+
|
|
181
|
+
### Preamble — binding, read before doing anything else
|
|
182
|
+
|
|
183
|
+
The text in this file is the COMPLETE spec — the committed WO content, not a plan to refine; there
|
|
184
|
+
is no separate plan file. Read the nearest `AGENTS.md`, the relevant `.codex/skills/<role>/SKILL.md`,
|
|
185
|
+
and this repo's own `MEMORY.md` ONLY for conventions. Stay in scope; do not touch
|
|
186
|
+
auth/permissions/deps/schema/CI unless this spec says so; do not update `MEMORY.md`. **Do NOT edit
|
|
187
|
+
`WORK_ORDERS.md`** — the register row and the review verdicts are the orchestrator's alone. Do NOT
|
|
188
|
+
`git add`/`commit`/`push` — leave every change uncommitted in the working tree for the orchestrator's
|
|
189
|
+
independent review. WRITE the tests the "Required tests to WRITE" section (Part A) calls for AND
|
|
190
|
+
**RUN the tests you just wrote** to confirm they execute and pass — that is the ONLY test run you do
|
|
191
|
+
(NOT the app's affected/full suite, NOT any review). The orchestrator re-runs the authoritative set
|
|
192
|
+
and does the independent review after you finish — those are the gate; your own run does not count
|
|
193
|
+
as the gate.
|
|
194
|
+
|
|
195
|
+
Narrate continuously: a `PLAN: <step1> | <step2> | …` line up front, then a single-line
|
|
196
|
+
`PROGRESS: [<n>/<total>] <present-tense action>` before every relevant action (and `… done` on
|
|
197
|
+
completion), spaced so no gap exceeds ~2 min, stdout unbuffered, plus exactly one final
|
|
198
|
+
`RESULT: DONE|BLOCKED <reason>`.
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## Part C — Orchestrator only — NOT ADDRESSED TO THE IMPLEMENTER
|
|
203
|
+
|
|
204
|
+
> **If you are the implementer reading this work order as your own specification: STOP at this
|
|
205
|
+
> line.** Everything below describes what the Orchestrator does AFTER you finish. You do none of it
|
|
206
|
+
> — no reviewers, no verification run, no register edit, no commit. You ARE the invocation described
|
|
207
|
+
> below; do NOT shell out to `codex exec`.
|
|
208
|
+
|
|
209
|
+
### Execution directive
|
|
210
|
+
|
|
211
|
+
Implement through `codex exec` in the background — invoked directly via Bash (never the
|
|
212
|
+
`debugger`/`*_coder` Agent wrappers) with BOTH flags `--skip-git-repo-check` and
|
|
213
|
+
`--dangerously-bypass-approvals-and-sandbox`, `cwd` = the working directory above (never
|
|
214
|
+
`…\webapps`). Fallback to direct Claude implementation only on Codex quota/rate-limit/non-zero exit —
|
|
215
|
+
check `.claude/codex-status.md` first per its own rule (no line for 2026-08-22 → use Codex). The
|
|
216
|
+
fallback flips authorship, making an independent `reviewer` mandatory even if it already was.
|
|
217
|
+
|
|
218
|
+
### Review routing (Tier 3, shared-core)
|
|
219
|
+
|
|
220
|
+
- `reviewer`: **yes**, full context, Sonnet-pinned, background.
|
|
221
|
+
- `ui_reviewer`: **yes** (diff touches frontend), full context, background, same batch as `reviewer`.
|
|
222
|
+
- `sec_reviewer`: not required — no auth/security surface in this diff.
|
|
223
|
+
- Both run concurrently; neither is skippable; commit waits for both clean.
|
|
224
|
+
|
|
225
|
+
### Verification (the authoritative run — Codex's own run is not the gate)
|
|
226
|
+
|
|
227
|
+
1. **Test gate:** `npx vitest run tests/ChartFrame.test.jsx tests/chartDefaults.test.js
|
|
228
|
+
tests/BarChart.test.jsx tests/LineChart.test.jsx tests/ScatterChart.test.jsx
|
|
229
|
+
tests/TimeSeriesChart.test.jsx` (adjust filenames to whatever actually exists — the affected-area
|
|
230
|
+
set per AGENTS.md "Test scope": `ChartFrame` itself plus every consumer of `resolveChartHeight`).
|
|
231
|
+
All must pass; the four presets' pre-existing assertions must be **byte-identical**, not just
|
|
232
|
+
green — a changed preset assertion means the fix leaked scope.
|
|
233
|
+
2. **Rendered check — this WO's own commit gate, scoped to what this repo can render.** This repo
|
|
234
|
+
has no prototype artifact, so the generic prototype side-by-side gate does not apply; this WO's
|
|
235
|
+
own Envelope names a rendered check instead, and it is honoured here at the scope this repo can
|
|
236
|
+
actually reach:
|
|
237
|
+
- Run `npm run dev` (Vite) and open the dev harness at 375 px and 1280 px. It already contains
|
|
238
|
+
`ChartFrame` "shape check" stories that mirror three of the five named hram cards — "Allocation
|
|
239
|
+
performance", "Access gap", "Optimization results" (`dev/entries.jsx`, search `shape check`).
|
|
240
|
+
Confirm none of them, nor any neighbouring card, show overlapping title/content/export rows at
|
|
241
|
+
either width.
|
|
242
|
+
- Additionally render (ad hoc, in the same dev session — e.g. a temporary story or by editing an
|
|
243
|
+
existing one's children) a `ChartFrame` with a `minHeight` smaller than its actual content, to
|
|
244
|
+
directly exercise the Definition of Done's acceptance criterion: the box must grow to fit the
|
|
245
|
+
content, not overflow it. Revert any temporary dev-harness edit before commit (or keep it only
|
|
246
|
+
if it's a genuine permanent addition worth keeping — your call, but say which in the Notiz).
|
|
247
|
+
- This repo's harness has no "Sensitivity" or "Access Ladder" story, so the **full five-card
|
|
248
|
+
rendered check the Envelope describes ("before hram's pin moves again") is NOT done by this WO**
|
|
249
|
+
— it cannot be, without linking a built `ui-core-micha` into a running hram dev server. Declare
|
|
250
|
+
this explicitly in the register Notiz as a carried-forward precondition on whichever WO next
|
|
251
|
+
bumps hram's pin past 2.42.0, not a silently dropped gate.
|
|
252
|
+
- Declare screenshots-vs-DOM-inspection substitution explicitly if real screenshots aren't taken.
|
|
253
|
+
|
|
254
|
+
### Register + commit
|
|
255
|
+
|
|
256
|
+
- `WORK_ORDERS.md` row → `done`, with: the named review verdicts (`reviewer` + `ui_reviewer`, no
|
|
257
|
+
open findings — or the findings and their resolution), the test gate result, the rendered-check
|
|
258
|
+
scope actually achieved (dev harness only, full hram five-card check carried forward), and which
|
|
259
|
+
evidence from `.claude/codex-status.md` was used (or that no fallback was needed).
|
|
260
|
+
- Note in the Notiz: **publish 2.42.1 promptly after commit** (per Part A Risks — 2.42.0 is `latest`
|
|
261
|
+
on the registry and carries this regression) and confirm the version bump lands in the same commit
|
|
262
|
+
or a clearly-linked follow-up; the register row must name whichever it was.
|
|
263
|
+
- Commit message: single concise English subject line (e.g. `fix(ChartFrame): stop applying a fixed
|
|
264
|
+
height to the card box (CHART-9)`). Push to `main` (this repo publishes from `main`, no `develop`
|
|
265
|
+
step) once review + tests are clean.
|