@micha.bigler/ui-core-micha 3.1.2 → 3.2.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.
@@ -0,0 +1,169 @@
1
+ # UCM-CHART-16 — The x-axis band starves MUI's tick-fit check, and two consumers have patched around it
2
+
3
+ ## Part A — Envelope
4
+
5
+ *Authored by the Expertenchat. Authoritative WHAT/WHY.*
6
+
7
+ ### Goal
8
+
9
+ A chart with an axis title **and** horizontal tick labels renders its tick labels. No consumer needs
10
+ to set `xAxis[].height` by hand to make that happen.
11
+
12
+ ### The defect
13
+
14
+ When the x-axis stays horizontal, `resolveXAxisGeometry` reserves one combined band from
15
+ `TICK_BAND_BASE_PX` + `AXIS_TITLE_BAND_PX`. Once an axis title is also taken from that budget, MUI's
16
+ own internal fit check (`shortenLabels`, against `tickLabelsMaxHeight`) is left **just under one line
17
+ of 12 px text** — so it fails for **every** tick and blanks **every** label, regardless of content.
18
+
19
+ The signature is distinctive and easy to misread: correctly positioned tick marks, zero non-empty
20
+ label texts. It looks like a data or formatter problem and is neither.
21
+
22
+ ### The evidence: two consumers, same workaround, same magic number
23
+
24
+ In hram, independently, in two different work orders:
25
+
26
+ ```
27
+ StructuralReachabilityPanel.jsx:386 height: 56 (HRAM-RES-32)
28
+ AccessGapScatterPanel.jsx:212 height: 56 (HRAM-RES-39)
29
+ ```
30
+
31
+ `HRAM-RES-32`'s own comment already diagnosed it correctly and called it what it is — *"a
32
+ pre-existing, latent **shared-core** near-miss"* — then patched locally, because a shared-core fix was
33
+ outside that WO. `HRAM-RES-39` hit the identical wall, read the MUI source, arrived at the same
34
+ cause and the same 56, and patched locally again.
35
+
36
+ Two things follow. **The third chart with a titled horizontal axis will hit it too**, and its author
37
+ will have to rediscover both the cause and the number. And the package is violating its own stated
38
+ rule: `chartDefaults.js` says *"Rule 1: nothing outside the resolver does chart arithmetic"* — a
39
+ caller hand-computing an axis height is exactly that, now in two places.
40
+
41
+ ### A third occurrence — and it proves scope item 2
42
+
43
+ Reported by the operator on 2026-08-25, hram's `AccessibilityPanel` in its
44
+ `Urban/Peri-urban/Rural × Division` mode: five band categories, five rendered bar groups, **no tick
45
+ labels at all**. Same signature — positioned ticks, blank text.
46
+
47
+ This one is different in a way that matters. That chart has **no `xAxisLabel`**
48
+ (`AccessibilityPanel.jsx:164-178`), so the axis title is *not* competing for the band. What it does
49
+ have is `tickLabelStyle: { fontSize: 13 }` — one pixel above the 12 px the base band was sized
50
+ against.
51
+
52
+ **So the trigger is not "a title takes the room". It is that the band is a near-miss at all**, and
53
+ anything that consumes a little more — a title, a slightly larger font, presumably a taller line
54
+ height — pushes it over. A fixed `height: 56` patched into a third consumer would work at 13 px and
55
+ fail at 14. That is precisely why scope item 2 requires the resolver to **derive** the requirement
56
+ from the tick font size it already knows, rather than adopting the constant two consumers measured
57
+ empirically at 12 px.
58
+
59
+ (Signature-matched, not yet instrumented — confirm the mechanism on this third case as part of the
60
+ work, since it is the one that distinguishes a derived fix from a bigger constant.)
61
+
62
+ ### Scope
63
+
64
+ 1. **Fix the reservation in the resolver.** The band must leave MUI's fit check room for one full
65
+ line of tick text *after* the axis title's share, not just under it. The near-miss is small, which
66
+ is why it went unnoticed — the fix is a correction to the arithmetic, not a redesign.
67
+ 2. **Derive it, do not add a second magic number.** 56 is what two consumers measured empirically
68
+ against a 12 px font; the resolver knows the tick font size and the title band, so it can compute
69
+ the requirement instead of hard-coding a constant that will be wrong at a different font size.
70
+ 3. **Say what changed and why in the comment**, in the style the file already uses — this is the
71
+ third time this arithmetic has been reasoned about and the first time it is written down where it
72
+ belongs.
73
+
74
+ ### Non-goals / do not touch
75
+
76
+ - The rotated-tick path (`xLabels: "angled"`, `spaceForRotatedTicks`). The defect is specific to the
77
+ horizontal case; a rotated axis reserves its own projected band and is unaffected.
78
+ - `CHART_SIZE_SPACING_UNITS` and the size scale — `UCM-CHART-15`, done.
79
+ - `ChartFrame`, margins, the legend band, `resolveChartLayout`'s other outputs.
80
+ - The consuming panels. Removing hram's two workarounds is `HRAM-CHT-6`, after this ships.
81
+
82
+ ### Risks
83
+
84
+ - **A mocked prop-assertion test cannot catch this.** The file already records the same trap for a
85
+ neighbouring bug: *"a mocked prop-assertion test cannot catch this, it only reproduces on an actual
86
+ MUI scale computation"*. A test that asserts the computed band's number would pass whatever the
87
+ number is. The test has to observe **rendered label text**, or it is decoration — see test 1.
88
+ - **Raising the band changes every titled-horizontal-axis chart's plot area** by the difference. Small,
89
+ but it is a visual change to consumers, and it lands on top of `UCM-CHART-15`'s 80 px shift.
90
+ - Fixing it too generously wastes vertical space on every chart. The margin of the near-miss is the
91
+ budget; do not round it up "to be safe".
92
+
93
+ ### Tests to write
94
+
95
+ 1. **A rendered test**: a chart with an axis title and horizontal ticks produces **non-empty** tick
96
+ label texts. Written so it fails against the current resolver — a blank-label render is the
97
+ defect, so the test must be able to see blankness.
98
+ 2. The rotated path is unchanged — same band as before for `xLabels: "angled"`.
99
+ 3. An axis **without** a title still reserves only the tick band (Rule 2: a band that is not needed
100
+ collapses to zero).
101
+
102
+ ---
103
+
104
+ ## Part B — Implementation map
105
+
106
+ `.claude/models.local.json` has no local override, so the default applies: `implementation.runtime`
107
+ is `claude`/`sonnet` — the Orchestrator implemented directly, no Codex dispatch.
108
+
109
+ **The defect could not be diagnosed from source reading alone** (see Risk section: "a mocked
110
+ prop-assertion test cannot catch this"). MUI's internal fit check (`shortenLabels`, weighed
111
+ against `tickLabelsMaxHeight`, in `node_modules/@mui/x-charts/ChartsXAxis/ChartsSingleXAxisTicks.mjs`
112
+ and `shortenLabels.mjs`) depends on `getBBox`, which jsdom does not implement — so the mechanism
113
+ was confirmed and the fix calibrated against a REAL browser render, not derived from reading MUI's
114
+ source alone:
115
+
116
+ - `dev/entries.jsx`'s dev harness (`vite`, real Chrome via the internal preview browser) was used
117
+ to dynamically render the actual, unmocked `BarChart` with a titled horizontal axis under this
118
+ package's own `createAppTheme`/DM Sans default. Confirmed the defect reproduces exactly as
119
+ described (blank tick labels, correctly positioned) and does NOT reproduce under MUI's default
120
+ Roboto stack (real glyph metrics run ~1.3-1.36× fontSize for DM Sans vs ~1.06-1.17× for Roboto —
121
+ explains why the defect is font-dependent and was missed until a consumer used a taller font).
122
+ - Binary-searched the real, minimal `xAxis[].height` needed to stop blanking, at tick font sizes
123
+ 11-18px, with and without a title (ten measured thresholds total). The result matched
124
+ `ceil(fontSize * 1.3) + 9` (no title) / `+ 11` (with title, i.e. the same plus 2px) almost
125
+ exactly — over by at most 1px, never under, across all ten points. `9` corresponds to MUI's own
126
+ `tickSize`(6, default) + `TICK_LABEL_GAP`(3) internal overhead
127
+ (`ChartsXAxis/utilities.mjs`/`ChartsSingleXAxisTicks.mjs`); the extra `+2` when a title is
128
+ present was constant across every font size tested, independent of font.
129
+ - The fix (`resolveXAxisGeometry`'s `angle === 0` branch, `chartDefaults.js`) was re-verified
130
+ against the SAME real-browser harness after implementing, confirming both reproduced scenarios
131
+ (title + default font; no title + `tickLabelStyle.fontSize: 13`, the `AccessibilityPanel`
132
+ signature) now render correctly with no explicit `height` override needed.
133
+
134
+ Context package: `chartDefaults.js:322-341` (`TICK_BAND_BASE_PX`/`AXIS_TITLE_BAND_PX`/new
135
+ constants), `:366-430ish` (`resolveXAxisGeometry`, the `angle === 0` early-return that was the
136
+ defect site), `:450-475` (`resolveChartLayout`'s `xAxisBand`/`xTitleBand` composition, unchanged).
137
+ Non-goal boundary respected: the `angle !== 0` (rotated) branch below the fixed code is untouched.
138
+
139
+ ---
140
+
141
+ ## Part C — Orchestrator only
142
+
143
+ > **STOP — everything below addresses the Orchestrator.**
144
+
145
+ **Tier 3 · tests: the three cases above, at least one of them rendered**
146
+
147
+ Shared-core, by the tiering table regardless of diff size.
148
+
149
+ - `reviewer` — the arithmetic, and above all whether test 1 can actually fail.
150
+ - `ui_reviewer` — the plot-area change this causes across consumers.
151
+ - `sec_reviewer` — not run; no auth, data or exposure surface.
152
+
153
+ ### Release
154
+
155
+ Patch or minor per the repo's convention — this repairs existing behaviour rather than adding
156
+ capability, so **patch** unless the plot-area change is judged large enough to warrant a minor. State
157
+ which and why in the CHANGELOG; `UCM-CHART-15`'s entry is the model for describing a visual
158
+ consequence plainly.
159
+
160
+ ### Register
161
+
162
+ Row `UCM-CHART-16`, both review notes, the published version, landing SHA(s) in a second commit
163
+ before the push. Record that the fix arrived **third** — two consumers patched around it first — so
164
+ the cost of leaving a shared-core near-miss to consumers is on the record.
165
+
166
+ ### Log
167
+
168
+ - **2026-08-25** — Envelope authored after `HRAM-RES-39` hit the same defect `HRAM-RES-32` had
169
+ already diagnosed as shared-core, and applied the identical `height: 56` workaround independently.