@micha.bigler/ui-core-micha 2.41.2 → 2.42.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -93,6 +93,74 @@ No API, behaviour or visual change for `de`/`fr`/`en` consumers. Purely additive
93
93
 
94
94
  *Filled by the Orchestrator on `git pull` — see `AGENTS.md` → "Work Order".*
95
95
 
96
+ **Files to change (exactly two):**
97
+ - [`src/i18n/messagingTranslations.ts`](../src/i18n/messagingTranslations.ts) — 118 lines, 116 keys
98
+ (`export const messagingTranslations = { 'Key': { de, en, fr }, ... }`), every value currently a
99
+ 3-locale object. Add a fourth `sw: '...'` to every one, matching the existing key ordering
100
+ `de, en, fr` → append `sw` last (matches the property order already used in
101
+ `notificationsTranslations.ts`/`onboardingTranslations.ts`, both `de, fr, en, sw`, so put `sw`
102
+ last regardless of the de/en/fr internal order already in this file — do not reorder de/en/fr).
103
+ - [`tests/i18nAggregate.test.js`](../tests/i18nAggregate.test.js) — line 30:
104
+ `const REQUIRED_LOCALES = ['de', 'en', 'fr'];` → `['de', 'en', 'fr', 'sw'];`, and delete the
105
+ explanatory comment at lines 27-29 (`// Scoped to the three locales ... see the Notiz on the
106
+ I18N-1 register row ...`) since it will no longer describe the state. Nothing else in this file
107
+ changes — the other three `it()` blocks (aggregate completeness, no-collision, per-bundle
108
+ availability) are untouched.
109
+
110
+ **Existing Swahili glossary to reuse — do not coin alternate wording for these concepts** (grep
111
+ `sw:` across `src/i18n/*.ts` yourself to see full context; this is not exhaustive, check for any
112
+ other overlap you find):
113
+ - Notification/notify → **Arifa** (`notificationsTranslations.ts`, `onboardingTranslations.ts`)
114
+ - Save → **Hifadhi** (`onboardingTranslations.ts`: `Onboarding.SAVE`)
115
+ - Close → **Funga** (`notificationsTranslations.ts`: `PopupSurface.CLOSE`)
116
+ - Continue → **Endelea** (`onboardingTranslations.ts`: `Onboarding.CONTINUE`)
117
+ - Skip → **Ruka** (`onboardingTranslations.ts`: `Onboarding.SKIP`)
118
+ - Change/switch (section) → **Badilisha** (`sectionNavTranslations.ts`)
119
+ - User menu / profile / logout → **Menyu ya mtumiaji / Wasifu / Toka** (`userMenuTranslations.ts`)
120
+ - "Could not be saved/loaded" error pattern → e.g. **"...haikuweza kuhifadhiwa"** /
121
+ **"...haikuweza kupakiwa"** (`notificationsTranslations.ts` `SAVE_ERROR`/`LOAD_ERROR`) — messaging
122
+ has the same error-string shape repeated ~15 times (`... konnte nicht ... werden: {{message}}`),
123
+ reuse this grammatical pattern rather than inventing a new one per key.
124
+ - "Please try again" → **"Tafadhali jaribu tena"** (`onboardingTranslations.ts`)
125
+
126
+ **None of messaging's specific domain terms (conversation, reply, attachment, reaction, poll,
127
+ announcement, unread, archive, mute) exist in the other bundles' Swahili** — those need fresh
128
+ translation. That is expected and fine; the instruction above is narrower: reuse wording only where
129
+ the *same* concept (save/cancel/close/error phrasing/etc.) already has a Swahili rendering
130
+ elsewhere, don't invent a second one for it.
131
+
132
+ **Do-not-touch:** `de`/`en`/`fr` values in `messagingTranslations.ts` (verify byte-identical before
133
+ and after — a diff review will check this); the other six bundles; `tests/i18nAggregate.test.js`'s
134
+ other three tests, the aggregate export, the directory ratchet, the ordering (`de, en, fr` internal
135
+ order in messaging stays as-is, `sw` only appended).
136
+
137
+ **Progress contract:** `PLAN: …` naming the two files, `PROGRESS: [1/2] backfill sw into
138
+ messagingTranslations.ts … done`, `PROGRESS: [2/2] restore REQUIRED_LOCALES + drop scope-down
139
+ comment in i18nAggregate.test.js … done`, then run the scoped tests named below and report which
140
+ keys (if any) you were unsure of, then `RESULT: DONE`.
141
+
142
+ ## Preamble
143
+
144
+ The text above is the COMPLETE spec — the committed WO file's content, not a plan to refine; there is
145
+ no separate plan file. Read the nearest `AGENTS.md`, the relevant `.codex/skills/<role>/SKILL.md`, and
146
+ the app `MEMORY.md` ONLY for conventions. Stay in scope; do not touch auth/permissions/deps/schema/CI
147
+ unless the spec says so; do not update `MEMORY.md`. **Do NOT edit `WORK_ORDERS.md`** — the register row
148
+ and the review verdicts are the orchestrator's alone. Do NOT `git add`/`commit`/`push` — leave every
149
+ change uncommitted in the working tree for the orchestrator's independent review. This WO's "Required
150
+ tests to WRITE" is explicitly **none new** — the restored `REQUIRED_LOCALES` assertion IS the test;
151
+ run it (and the messaging export specs) to confirm your backfill makes it pass — that is the ONLY test
152
+ run you do (NOT the app's affected/full suite, NOT any review). **State plainly in your final report:
153
+ the source of the Swahili translations (e.g. "machine-translated via <tool>, not reviewed by a fluent
154
+ speaker" — say so honestly, do not imply review that didn't happen) and list any specific key you were
155
+ uncertain of**, per the Envelope's explicit disclosure requirement. The orchestrator re-runs the
156
+ authoritative set + does the independent review after you finish — those are the gate; your own run
157
+ does not count as the gate.
158
+
159
+ Narrate continuously: a `PLAN: <step1> | <step2> | …` line up front, then a single-line
160
+ `PROGRESS: [<n>/<total>] <present-tense action>` before every relevant action (and `… done` on
161
+ completion), spaced so no gap exceeds ~2 min, stdout unbuffered, plus exactly one final
162
+ `RESULT: DONE|BLOCKED <reason>`.
163
+
96
164
  ---
97
165
 
98
166
  ## C. Orchestrator only
@@ -0,0 +1,147 @@
1
+ # UCM-CHART-8 — Make the chart presets stop producing dead space below the chart
2
+
3
+ - **Repo:** `ui-core-micha`, branch `main` (shared-core; publishes from `main` without a staging step)
4
+ - **Tier:** 3 — shared-core. Every consuming app inherits the behaviour change.
5
+ - **Status:** planned
6
+ - **Workstream:** `CHART-*` (chart presets)
7
+ - **Files:** `src/components/charts/ScatterChart.jsx`, `BarChart.jsx`, `LineChart.jsx`,
8
+ `TimeSeriesChart.jsx`, **`ChartFrame.jsx`** — all five destructure `minHeight` **and** `aspect`
9
+ (verified 2026-08-21) — plus their tests. **Note for the implementer: only `ScatterChart` and
10
+ `TimeSeriesChart` reference `height` at all.** `BarChart`, `LineChart` and `ChartFrame` never
11
+ destructure it, so for them the incoherent "pair" exists only via the rest spread into the MUI
12
+ chart. Do not go looking for a declared `height` prop there.
13
+
14
+ > **Why.** `minHeight` and `height` are two different things on these presets, and the API lets a
15
+ > caller pass both. `minHeight` lands on the **wrapper `Box`** —
16
+ > `sx={{ width: '100%', minHeight, aspectRatio: aspect }}` (`ScatterChart.jsx:260`, same
17
+ > construction in `BarChart.jsx`) — while `height` sizes the **chart**. When `minHeight > height`,
18
+ > the difference is dead space **below the chart**.
19
+ >
20
+ > Measured in `hram` on 2026-08-20:
21
+ >
22
+ > | panel | `minHeight` | `height` | dead space |
23
+ > |---|---|---|---|
24
+ > | `StructuralReachabilityPanel` | 420 | 380 | 40 px |
25
+ > | `ExpertTimelinePanel` | 340 | 280 (×2) | 60 px |
26
+ > | `ResultsTimelinePanel` | 320 | 280 | 40 px |
27
+ > | `GroupMetricsPanel` | 300 | **none** | the whole chart is unsized |
28
+ > | Cost, Ratio, Accessibility, Referral, Total | equal | equal | 0 |
29
+ >
30
+ > **The equal-value majority is why this survived.** Passing both looks like harmless
31
+ > belt-and-braces and costs nothing when the numbers match, so the pattern spread by copy-paste and
32
+ > nobody noticed the cases where they diverge. It has been diagnosed and fixed **pointwise twice**
33
+ > in hram (`AllocationPerformancePanel.jsx:462-478`, `OptimizationResultsPanel.jsx:928-940`, both to
34
+ > `height={420}`) — with the explanation written as a comment **inside those two files**, where no
35
+ > new panel will ever read it.
36
+ >
37
+ > This is not N panel bugs. It is one shared API that permits an incoherent pair, and it will keep
38
+ > recurring for as long as it does. Fixing it here fixes every consumer at once, including the ones
39
+ > nobody sweeps.
40
+
41
+ ---
42
+
43
+ ## Part A — Envelope (authoritative WHAT/WHY)
44
+
45
+ ### Goal
46
+
47
+ A caller can no longer produce dead space by passing `minHeight`. The presets resolve the pair
48
+ themselves, and the incoherent combination becomes impossible rather than merely discouraged.
49
+
50
+ ### Definition of Done
51
+
52
+ - [ ] **The resolution is three-way, not two-way — `aspect` is the third input and it changes the
53
+ answer.** Amended 2026-08-21; the original two-way rule would have broken four live call sites.
54
+ - **`height` present** → it sizes the chart, and the wrapper reserves no more than that.
55
+ - **No `height`, no `aspect`** → **`minHeight` sizes the chart**, not merely the wrapper.
56
+ This is what a caller in that shape meant (`GroupMetricsPanel`: `minHeight={300}`, no
57
+ `height`, no `aspect` — chart unsized today).
58
+ - **No `height`, but `aspect` present** → **today's behaviour is correct and must not change.**
59
+ The wrapper is `width: 100%` with `aspectRatio`, so its height derives from its width, and
60
+ `minHeight` is the floor that stops it collapsing on a narrow viewport. Here `minHeight`
61
+ means exactly what its name says.
62
+
63
+ **Same prop shape, opposite intent** — which is why the rule cannot be stated on
64
+ `minHeight`/`height` alone.
65
+ - [ ] **`minHeight > height` produces no wrapper padding.** The chart is sized by `height` and the
66
+ wrapper does not reserve more than the chart occupies. The extra space was never intentional.
67
+ - [ ] **Equal values behave exactly as today** — that is the majority of call sites and they must
68
+ not shift by a pixel.
69
+ - [ ] **A dev-mode warning names the offending call site** when both are passed and they disagree.
70
+ Not a throw: a shared package that throws on a prop combination in production breaks a
71
+ consumer's page over a layout nit.
72
+ - [ ] **JSDoc on every preset that takes it** states in one line what `minHeight` now does, so the
73
+ next caller does not have to infer it from a comment in a consumer app.
74
+
75
+ ### Deliberately NOT done
76
+
77
+ - **`minHeight` is not removed from the API.** Removing it is a breaking change to a shared package,
78
+ which would make every consumer's pin bump a Tier-3 change with a real chance of unnoticed layout
79
+ regressions — a worse outcome than the defect. Deprecate in the docstring, coerce in the code.
80
+ - No change to chart internals, palettes, axis defaults or `aspect`.
81
+ - No consumer-side sweep in this WO. hram's call-site cleanup is its own follow-up, gated on the pin
82
+ bump — see below.
83
+
84
+ ### Preconditions and ordering
85
+
86
+ The fix only reaches hram after **(a)** this lands and publishes and **(b)** hram bumps its
87
+ `@micha.bigler/ui-core-micha` pin. Verify the published version is actually live before the consumer
88
+ bump — this package publishes from `main` with no staging step, so a failed publish is silent until
89
+ a consumer installs it.
90
+
91
+ ### Risks
92
+
93
+ - **The consumer check is done, and it found something — 2026-08-21.** It was written here as a
94
+ hypothetical ("a consumer may be relying on the padding as spacing"); the real finding is
95
+ different and larger. **Four live call sites pass `aspect` together with `minHeight` and no
96
+ `height`:**
97
+
98
+ | call site | props |
99
+ |---|---|
100
+ | `hram/AccessGapScatterPanel.jsx:182` | `aspect={1.8}` + `minHeight={320}` |
101
+ | `fitness-monitor/BodyHistoryPage.jsx:~328` | `aspect="960 / 380"` + `minHeight={320}` |
102
+ | `fitness-monitor/BodyHistoryPage.jsx:~355` | `aspect="960 / 380"` + `minHeight={320}` |
103
+ | `fitness-monitor/BodyHistoryPage.jsx:~450` | `aspect="400 / 220"` + `minHeight={220}` |
104
+
105
+ These are not relying on padding-as-spacing. They are using `minHeight` **as a floor under an
106
+ aspect-derived height**, which is legitimate and is what the name promises. The original
107
+ Definition of Done would have turned all four into fixed-height charts and removed their
108
+ responsive behaviour. **That is the reason for the three-way rule above**, and it is the case to
109
+ hold onto when reading the rest of this order.
110
+
111
+ Scope note: `survey-renderer` is named above as a consumer to check. **The standalone repo is
112
+ retired** (2026-07-31); the widget now lives at `survey_app/frontend/packages/survey-renderer`,
113
+ and neither it nor `cockpit` or `jg-ferien` passes `aspect` to a chart preset. jg's one `aspect=`
114
+ hit is an `ImageCropDialog`, unrelated.
115
+ - The dev warning must not fire on the equal-value majority, or it becomes noise that trains people
116
+ to ignore it.
117
+
118
+ ### Tests to WRITE — narrow
119
+
120
+ - `minHeight` alone, **no `aspect`** → the chart receives that height.
121
+ - **`minHeight` + `aspect`, no `height` → unchanged from today**: the wrapper keeps its
122
+ `aspectRatio` and `minHeight` stays a floor, and the chart is **not** given a fixed height. This is
123
+ the four-call-site case in Risks and the one a two-way rule would have broken — **it is the test
124
+ that has to fail if someone later re-simplifies the resolution.**
125
+ - `minHeight > height` → the chart receives `height` and the wrapper reserves no more than that.
126
+ - `minHeight === height` → byte-identical to the current output.
127
+ - `height` alone → unchanged.
128
+ - The dev warning fires only on the disagreeing pair, and names the values. It must **not** fire on
129
+ `minHeight` + `aspect` without `height`, which is a legitimate combination.
130
+
131
+ No full suite.
132
+
133
+ ---
134
+
135
+ ## Part B — Implementation map
136
+
137
+ > **TO BE FILLED BY THE ORCHESTRATOR** on `git pull`. Must carry the preset files with `path:line`
138
+ > anchors (`ScatterChart.jsx:173-174, :260` and the equivalents), the absolute working directory,
139
+ > the progress contract, and the preamble. **Do not dispatch while this placeholder stands.**
140
+
141
+ ---
142
+
143
+ ## Part C — Orchestrator-only
144
+
145
+ > **TO BE FILLED BY THE ORCHESTRATOR.** Review routing (`reviewer` + `ui_reviewer`, full context —
146
+ > Tier 3 shared-core), verification, register maintenance, commit, publish check, and the execution
147
+ > directive with its self-address guard.