@micha.bigler/ui-core-micha 3.14.4 → 4.0.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.
Files changed (341) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +7 -0
  3. package/dist/chat/kitSxRegistry.js +9 -0
  4. package/dist/components/charts/kitSxRegistry.js +6 -0
  5. package/dist/entries/charts.js +6 -0
  6. package/dist/entries/chat.js +8 -0
  7. package/dist/entries/markdown.js +1 -0
  8. package/dist/index.js +0 -14
  9. package/dist/pages/AccountPage.js +1 -2
  10. package/dist/pages/LoginPage.js +1 -2
  11. package/dist/pages/PasswordChangePage.js +1 -2
  12. package/dist/pages/PasswordInvitePage.js +1 -2
  13. package/dist/pages/PasswordResetRequestPage.js +1 -2
  14. package/dist/pages/SignUpPage.js +1 -2
  15. package/dist/pages/SignupConfirmPage.js +1 -2
  16. package/dist/theme/kitSxEntry.js +11 -0
  17. package/dist/theme/kitSxRegistry.js +2 -15
  18. package/package.json +31 -7
  19. package/.github/workflows/ci.yml +0 -35
  20. package/.github/workflows/publish.yml +0 -126
  21. package/DESIGN.md +0 -60
  22. package/WORK_ORDERS.md +0 -150
  23. package/dev/entries.jsx +0 -721
  24. package/dev/fixtures.js +0 -26
  25. package/dev/main.jsx +0 -75
  26. package/dev/mockTransport.jsx +0 -128
  27. package/docs/CHART-LAYOUT.md +0 -157
  28. package/docs/DARK-TOKENS.md +0 -140
  29. package/docs/TYPE-SCALE.md +0 -111
  30. package/docs/messaging-deviations.md +0 -118
  31. package/index.html +0 -12
  32. package/renovate.json +0 -10
  33. package/scripts/chart-api-census.mjs +0 -415
  34. package/src/auth/AuthContext.jsx +0 -158
  35. package/src/auth/ReauthModal.jsx +0 -82
  36. package/src/auth/UserMenu.jsx +0 -121
  37. package/src/auth/apiClient.jsx +0 -232
  38. package/src/auth/authApi.jsx +0 -539
  39. package/src/auth/authConfig.jsx +0 -34
  40. package/src/auth/reauth.js +0 -47
  41. package/src/chat/ChatComposer.jsx +0 -36
  42. package/src/chat/ChatList.jsx +0 -87
  43. package/src/chat/ChatThread.jsx +0 -128
  44. package/src/chat/ChatWorkspace.jsx +0 -244
  45. package/src/chat/MarkdownReply.jsx +0 -22
  46. package/src/chat/ModelPicker.jsx +0 -85
  47. package/src/chat/api.js +0 -18
  48. package/src/chat/useChatSocket.js +0 -32
  49. package/src/chat/wire.js +0 -7
  50. package/src/components/AccessCodeManager.jsx +0 -288
  51. package/src/components/AccessCodeSingleUseToggle.jsx +0 -92
  52. package/src/components/AllowedEmailDomainsManager.jsx +0 -92
  53. package/src/components/AuthFactorRequirementCard.jsx +0 -95
  54. package/src/components/BulkInviteCsvTab.jsx +0 -198
  55. package/src/components/EmptyState.jsx +0 -38
  56. package/src/components/LoginForm.jsx +0 -159
  57. package/src/components/MFAComponent.jsx +0 -350
  58. package/src/components/MfaLoginComponent.jsx +0 -252
  59. package/src/components/MobileBottomNav.jsx +0 -82
  60. package/src/components/PasskeysComponent.jsx +0 -252
  61. package/src/components/PasswordChangeForm.jsx +0 -87
  62. package/src/components/PasswordResetRequestForm.jsx +0 -43
  63. package/src/components/PasswordSetForm.jsx +0 -97
  64. package/src/components/ProfileComponent.jsx +0 -348
  65. package/src/components/QrSignupManager.jsx +0 -404
  66. package/src/components/QrSignupValidityManager.jsx +0 -106
  67. package/src/components/RegistrationMethodsManager.jsx +0 -150
  68. package/src/components/SecurityComponent.jsx +0 -141
  69. package/src/components/SocialLoginButtons.jsx +0 -83
  70. package/src/components/SoftChip.jsx +0 -93
  71. package/src/components/StatTile.jsx +0 -39
  72. package/src/components/SupportRecoveryRequestsTab.jsx +0 -482
  73. package/src/components/TurnstileWidget.jsx +0 -99
  74. package/src/components/UserInviteComponent.jsx +0 -76
  75. package/src/components/UserListComponent.jsx +0 -618
  76. package/src/components/charts/BarChart.jsx +0 -102
  77. package/src/components/charts/ChartFrame.jsx +0 -214
  78. package/src/components/charts/LineChart.jsx +0 -126
  79. package/src/components/charts/ScatterChart.jsx +0 -299
  80. package/src/components/charts/TimeSeriesChart.jsx +0 -300
  81. package/src/components/charts/chartDefaults.js +0 -644
  82. package/src/components/charts/chartLabels.js +0 -36
  83. package/src/components/charts/exportChart.js +0 -258
  84. package/src/components/charts/formatters.js +0 -39
  85. package/src/components/charts/palette.js +0 -33
  86. package/src/components/charts/yearTickInterval.js +0 -7
  87. package/src/i18n/authTranslations.ts +0 -2651
  88. package/src/i18n/chartsTranslations.ts +0 -91
  89. package/src/i18n/chatTranslations.ts +0 -55
  90. package/src/i18n/messagingTranslations.ts +0 -120
  91. package/src/i18n/notificationsTranslations.ts +0 -32
  92. package/src/i18n/onboardingTranslations.ts +0 -29
  93. package/src/i18n/sectionNavTranslations.ts +0 -20
  94. package/src/i18n/uiCoreTranslations.js +0 -29
  95. package/src/i18n/userMenuTranslations.ts +0 -5
  96. package/src/index.js +0 -158
  97. package/src/layout/PageLayout.jsx +0 -36
  98. package/src/layout/SectionNav.jsx +0 -320
  99. package/src/messaging/AttachmentList.jsx +0 -145
  100. package/src/messaging/Composer.jsx +0 -197
  101. package/src/messaging/ConversationLaunchers.jsx +0 -51
  102. package/src/messaging/ConversationList.jsx +0 -109
  103. package/src/messaging/DirectMessageLauncher.jsx +0 -112
  104. package/src/messaging/MessageBubble.jsx +0 -183
  105. package/src/messaging/MessagingProvider.jsx +0 -0
  106. package/src/messaging/MessagingScopeConfig.jsx +0 -24
  107. package/src/messaging/PollCard.jsx +0 -163
  108. package/src/messaging/ReactionBar.jsx +0 -43
  109. package/src/messaging/ReadTicks.jsx +0 -82
  110. package/src/messaging/Thread.jsx +0 -195
  111. package/src/messaging/api.js +0 -48
  112. package/src/messaging/composerImageCompression.js +0 -35
  113. package/src/messaging/linkifyText.jsx +0 -37
  114. package/src/notifications/NotificationBell.jsx +0 -84
  115. package/src/notifications/NotificationSettings.jsx +0 -330
  116. package/src/notifications/NotificationsProvider.jsx +0 -224
  117. package/src/notifications/PopupSurface.jsx +0 -105
  118. package/src/notifications/api.js +0 -55
  119. package/src/notifications/feedApi.js +0 -18
  120. package/src/notifications/realtime.jsx +0 -138
  121. package/src/notifications/serviceWorker/sw.js +0 -50
  122. package/src/onboarding/OnboardingProvider.jsx +0 -326
  123. package/src/onboarding/OnboardingWizard.jsx +0 -61
  124. package/src/onboarding/WizardDialogShell.jsx +0 -51
  125. package/src/onboarding/api.js +0 -13
  126. package/src/onboarding/stepSelection.js +0 -16
  127. package/src/onboarding/steps/BrowserPushStep.jsx +0 -121
  128. package/src/onboarding/steps/CompleteNameStep.jsx +0 -46
  129. package/src/onboarding/steps/CookieConsentStep.jsx +0 -133
  130. package/src/onboarding/steps/PwaInstallStep.jsx +0 -47
  131. package/src/pages/AccountPage.jsx +0 -394
  132. package/src/pages/LoginPage.jsx +0 -343
  133. package/src/pages/PasswordChangePage.jsx +0 -62
  134. package/src/pages/PasswordInvitePage.jsx +0 -177
  135. package/src/pages/PasswordResetRequestPage.jsx +0 -68
  136. package/src/pages/SignUpPage.jsx +0 -268
  137. package/src/pages/SignupConfirmPage.jsx +0 -155
  138. package/src/testing/checkKitIntegration.js +0 -233
  139. package/src/theme/createAppTheme.js +0 -423
  140. package/src/theme/fonts.js +0 -3
  141. package/src/theme/index.js +0 -21
  142. package/src/theme/kitSxRegistry.js +0 -285
  143. package/src/theme/themeCompleteness.js +0 -1045
  144. package/src/theme/tokens.js +0 -478
  145. package/src/utils/auth-errors.js +0 -76
  146. package/src/utils/authService.js +0 -223
  147. package/src/utils/resolveErrorText.js +0 -28
  148. package/src/utils/webauthn.js +0 -51
  149. package/tests/AccountPage.profileStatements.test.jsx +0 -162
  150. package/tests/AccountPage.test.jsx +0 -200
  151. package/tests/AuthContext.test.jsx +0 -129
  152. package/tests/BarChart.test.jsx +0 -112
  153. package/tests/BrowserPushStep.test.jsx +0 -110
  154. package/tests/ChartFrame.test.jsx +0 -336
  155. package/tests/ConversationList.test.jsx +0 -144
  156. package/tests/CookieConsentStep.test.jsx +0 -69
  157. package/tests/EmptyState.test.jsx +0 -62
  158. package/tests/LineChart.test.jsx +0 -80
  159. package/tests/MessagingProvider.test.jsx +0 -50
  160. package/tests/MobileBottomNav.test.jsx +0 -319
  161. package/tests/NotificationBell.test.jsx +0 -60
  162. package/tests/NotificationSettings.test.jsx +0 -276
  163. package/tests/NotificationsProvider.test.jsx +0 -240
  164. package/tests/OnboardingProvider.test.jsx +0 -294
  165. package/tests/OnboardingWizard.test.jsx +0 -143
  166. package/tests/PasswordChangeForm.test.jsx +0 -70
  167. package/tests/PasswordInvitePage.test.jsx +0 -142
  168. package/tests/PasswordSetForm.test.jsx +0 -70
  169. package/tests/PopupSurface.test.jsx +0 -142
  170. package/tests/ProfileComponent.refresh.test.jsx +0 -194
  171. package/tests/QrSignupManager.registrationContext.test.jsx +0 -106
  172. package/tests/QrSignupManagerInfoBox.test.jsx +0 -49
  173. package/tests/ScatterChart.test.jsx +0 -270
  174. package/tests/SectionNav.test.jsx +0 -363
  175. package/tests/SignUpPage.test.jsx +0 -208
  176. package/tests/SoftChip.test.jsx +0 -105
  177. package/tests/StatTile.test.jsx +0 -57
  178. package/tests/Thread.test.jsx +0 -151
  179. package/tests/TimeSeriesChart.test.jsx +0 -367
  180. package/tests/UserListComponent.test.jsx +0 -192
  181. package/tests/UserMenu.test.jsx +0 -171
  182. package/tests/apiClientLanguage.test.js +0 -33
  183. package/tests/apiClientPublicPaths.test.js +0 -178
  184. package/tests/authApiLogin.test.js +0 -42
  185. package/tests/authApiRegistration.test.js +0 -31
  186. package/tests/authErrorCodeRendering.test.jsx +0 -77
  187. package/tests/authErrors.test.js +0 -74
  188. package/tests/authTranslations.test.js +0 -74
  189. package/tests/chart-api-census.test.js +0 -366
  190. package/tests/chartAxisTickBand.test.jsx +0 -95
  191. package/tests/chartDefaults.test.js +0 -902
  192. package/tests/chartLabels.test.js +0 -50
  193. package/tests/chartsPalette.test.js +0 -99
  194. package/tests/chatApi.test.js +0 -48
  195. package/tests/chatDriverNames.test.jsx +0 -71
  196. package/tests/chatExports.test.js +0 -3
  197. package/tests/chatList.test.jsx +0 -84
  198. package/tests/chatPicker.test.jsx +0 -82
  199. package/tests/chatSocket.test.jsx +0 -26
  200. package/tests/chatThread.test.jsx +0 -116
  201. package/tests/chatTranslations.test.js +0 -47
  202. package/tests/chatWorkspace.test.jsx +0 -234
  203. package/tests/checkKitIntegration.test.jsx +0 -219
  204. package/tests/createAppTheme.test.js +0 -132
  205. package/tests/createAppThemeDark.test.js +0 -127
  206. package/tests/createAppThemeLightGuard.test.js +0 -40
  207. package/tests/createAppThemePageContrast.test.js +0 -108
  208. package/tests/createAppThemeTypography.test.js +0 -53
  209. package/tests/errorCallSiteGuard.test.js +0 -32
  210. package/tests/exportChart.test.js +0 -313
  211. package/tests/feedApi.test.js +0 -46
  212. package/tests/fixtures/light-theme-baseline.json +0 -2234
  213. package/tests/i18nAggregate.test.js +0 -120
  214. package/tests/i18nGermanVariant.test.js +0 -65
  215. package/tests/loginPageErrorResolution.test.jsx +0 -102
  216. package/tests/messagingApi.test.js +0 -32
  217. package/tests/messagingChunk6.test.jsx +0 -116
  218. package/tests/messagingCompactBubble.test.jsx +0 -64
  219. package/tests/messagingComposer.test.jsx +0 -136
  220. package/tests/messagingComposerChunk5.test.jsx +0 -113
  221. package/tests/messagingContractConformance.test.js +0 -69
  222. package/tests/messagingDirectMessageLauncher.test.jsx +0 -151
  223. package/tests/messagingExports.test.js +0 -21
  224. package/tests/messagingInteractions.test.jsx +0 -136
  225. package/tests/messagingLastMessagePreview.test.jsx +0 -93
  226. package/tests/messagingLinkify.test.jsx +0 -51
  227. package/tests/messagingMessageActions.test.jsx +0 -163
  228. package/tests/messagingMsg6.test.jsx +0 -105
  229. package/tests/messagingMsg6f.test.jsx +0 -155
  230. package/tests/messagingMsg6g.test.jsx +0 -166
  231. package/tests/messagingMsg6h.test.jsx +0 -136
  232. package/tests/messagingMsg6i.test.jsx +0 -129
  233. package/tests/messagingPollRendering.test.jsx +0 -142
  234. package/tests/messagingReactionMerge.test.jsx +0 -77
  235. package/tests/messagingReadTicksTooltip.test.jsx +0 -36
  236. package/tests/messagingThreadReceipts.test.jsx +0 -178
  237. package/tests/messagingThreadingQuoting.test.jsx +0 -87
  238. package/tests/messagingUnreadLifecycle.test.jsx +0 -78
  239. package/tests/notificationsApi.test.js +0 -41
  240. package/tests/notificationsExports.test.js +0 -34
  241. package/tests/onboardingDescriptors.test.js +0 -132
  242. package/tests/packageTreeShaking.test.js +0 -126
  243. package/tests/realtime.test.jsx +0 -219
  244. package/tests/resolveErrorText.test.js +0 -72
  245. package/tests/stepSelection.test.js +0 -53
  246. package/tests/themeCompleteness.test.js +0 -626
  247. package/tests/timeSeriesLegend.test.js +0 -99
  248. package/tests/userScreenTranslations.test.jsx +0 -218
  249. package/tsconfig.build.json +0 -18
  250. package/vite.config.mjs +0 -6
  251. package/work-orders/AUTH-1.md +0 -74
  252. package/work-orders/AUTH-2.md +0 -74
  253. package/work-orders/AUTH-4.md +0 -183
  254. package/work-orders/AUTH-5.md +0 -154
  255. package/work-orders/AUTH-7.md +0 -181
  256. package/work-orders/AUTH-8.md +0 -184
  257. package/work-orders/CHART-1.md +0 -285
  258. package/work-orders/CHART-2.md +0 -222
  259. package/work-orders/CHART-3.md +0 -163
  260. package/work-orders/CHART-4.md +0 -203
  261. package/work-orders/CHART-5.md +0 -226
  262. package/work-orders/CHART-6.md +0 -428
  263. package/work-orders/CHART-7.md +0 -175
  264. package/work-orders/DX-1.md +0 -169
  265. package/work-orders/DX-2.md +0 -188
  266. package/work-orders/DX-3.md +0 -195
  267. package/work-orders/I18N-1.md +0 -254
  268. package/work-orders/I18N-3.md +0 -207
  269. package/work-orders/MSG-3.md +0 -375
  270. package/work-orders/MSG-3b.md +0 -1002
  271. package/work-orders/MSG-3c.md +0 -378
  272. package/work-orders/MSG-3d.md +0 -268
  273. package/work-orders/MSG-3e.md +0 -164
  274. package/work-orders/MSG-3f.md +0 -391
  275. package/work-orders/MSG-6e.md +0 -308
  276. package/work-orders/MSG-6f.md +0 -214
  277. package/work-orders/MSG-6g.md +0 -244
  278. package/work-orders/MSG-6h.md +0 -238
  279. package/work-orders/MSG-6i.md +0 -271
  280. package/work-orders/MSG-6j.md +0 -117
  281. package/work-orders/NOTIF-12.md +0 -214
  282. package/work-orders/NOTIF-13.md +0 -142
  283. package/work-orders/NOTIF-14.md +0 -151
  284. package/work-orders/PRIM-1.md +0 -177
  285. package/work-orders/SHELL-1.md +0 -255
  286. package/work-orders/SHELL-2.md +0 -197
  287. package/work-orders/SHELL-3.md +0 -359
  288. package/work-orders/SHELL-4.md +0 -351
  289. package/work-orders/SHELL-5.md +0 -345
  290. package/work-orders/SHELL-6.md +0 -210
  291. package/work-orders/THEME-1.md +0 -587
  292. package/work-orders/THEME-10.md +0 -303
  293. package/work-orders/THEME-11.md +0 -208
  294. package/work-orders/THEME-2.md +0 -339
  295. package/work-orders/THEME-3.md +0 -210
  296. package/work-orders/THEME-4.md +0 -271
  297. package/work-orders/THEME-5.md +0 -341
  298. package/work-orders/THEME-6.md +0 -247
  299. package/work-orders/THEME-7.md +0 -82
  300. package/work-orders/THEME-8.md +0 -211
  301. package/work-orders/THEME-9.md +0 -194
  302. package/work-orders/UCM-AUTH-10.md +0 -490
  303. package/work-orders/UCM-AUTH-11.md +0 -185
  304. package/work-orders/UCM-AUTH-12.md +0 -98
  305. package/work-orders/UCM-AUTH-13.md +0 -47
  306. package/work-orders/UCM-AUTH-9.md +0 -207
  307. package/work-orders/UCM-CHART-10.md +0 -150
  308. package/work-orders/UCM-CHART-11.md +0 -132
  309. package/work-orders/UCM-CHART-12.md +0 -348
  310. package/work-orders/UCM-CHART-13.md +0 -122
  311. package/work-orders/UCM-CHART-14.md +0 -198
  312. package/work-orders/UCM-CHART-15.md +0 -187
  313. package/work-orders/UCM-CHART-16.md +0 -169
  314. package/work-orders/UCM-CHART-17.md +0 -195
  315. package/work-orders/UCM-CHART-18.md +0 -200
  316. package/work-orders/UCM-CHART-8.md +0 -147
  317. package/work-orders/UCM-CHART-9.md +0 -265
  318. package/work-orders/UCM-CHAT-1.md +0 -190
  319. package/work-orders/UCM-CHAT-2.md +0 -75
  320. package/work-orders/UCM-CI-1.md +0 -65
  321. package/work-orders/UCM-DEP-1.md +0 -63
  322. package/work-orders/UCM-I18N-4.md +0 -226
  323. package/work-orders/UCM-I18N-5.md +0 -211
  324. package/work-orders/UCM-MSG-20.md +0 -192
  325. package/work-orders/UCM-PRIM-2.md +0 -114
  326. package/work-orders/UCM-SHELL-7.md +0 -134
  327. package/work-orders/UCM-SHELL-8.md +0 -125
  328. package/work-orders/UCM-TEST-1.md +0 -304
  329. package/work-orders/UCM-THEME-13.md +0 -69
  330. package/work-orders/UCM-THEME-14.md +0 -312
  331. package/work-orders/UCM-THEME-15.md +0 -356
  332. package/work-orders/UCM-THEME-16.md +0 -80
  333. package/work-orders/UCM-THEME-17.md +0 -111
  334. package/work-orders/UCM-THEME-18.md +0 -184
  335. package/work-orders/assets/AUTH-4-user-list-card.html +0 -407
  336. package/work-orders/assets/PRIM-1-dashboard-primitives.html +0 -624
  337. package/work-orders/assets/SHELL-2-account-navigation.html +0 -1159
  338. package/work-orders/assets/THEME-1-baseline-reference-sheet.html +0 -1352
  339. package/work-orders/assets/UCM-CHAT-1-ai-chat.html +0 -447
  340. package/work-orders/assets/UCM-THEME-13-dark-prototype.html +0 -390
  341. package/work-orders/assets/UCM-THEME-17-type-scale.html +0 -391
@@ -1,150 +0,0 @@
1
- # UCM-CHART-10 — The rotated-tick allowance is reserved twice, and the surplus is dead space
2
-
3
- - **Repo:** `ui-core-micha`, branch `main` (shared-core; publishes from `main`, no staging step)
4
- - **Tier:** 3 — shared-core; every consumer's chart proportions change.
5
- - **Status:** planned
6
- - **Workstream:** `CHART-*`
7
- - **Files:** `src/components/charts/chartDefaults.js` (`spaceForRotatedTicks`, `rotatedTickMetrics`)
8
- and its tests. The presets only *call* the helper — do not change them.
9
-
10
- > **Why — measured in hram, 2026-08-22, with ucm 2.42.1 loaded and real data.** `spaceForRotatedTicks`
11
- > adds the same `extraHeight` in **two** places:
12
- >
13
- > ```js
14
- > return { ...axis, height: MUI_LABELLED_X_AXIS_HEIGHT + metrics.extraHeight }; // (1) the axis band
15
- > ...
16
- > ? { ...margin, bottom: MUI_CHART_MARGIN_BOTTOM + largestExtraHeight } // (2) the chart margin
17
- > ```
18
- >
19
- > **In MUI v8 the axis `height` and the chart `margin.bottom` are additive.** The allowance for
20
- > rotated tick labels is therefore counted twice, the axis band already holds the labels, and the
21
- > margin copy is left over as an empty band below the plot.
22
- >
23
- > Measured on the Research → Allocation drill-down at 1280 px:
24
- >
25
- > | card | surface height | plot ends at | empty below the axis | x tick labels |
26
- > |---|---|---|---|---|
27
- > | Ward Metrics | 340 | 131 | **130 px (38 %)** | 35 |
28
- > | Access Ladder | 340 | 131 | **130 px (38 %)** | 35 |
29
- > | Cost | 340 | 194 | 92 px | 35 |
30
- > | Division Metrics | 340 | 199 | 85 px | 5 |
31
- > | Cost-effectiveness | 420 | 331 | 44 px | 22 |
32
- > | Expert Mode: Simulation Trajectory | 340 | 287 | 8 px | 31 |
33
- >
34
- > Two things make this conclusive rather than suggestive. First, the empty band **scales with the
35
- > label load** — 35 long rotated ward names cost 130 px, short labels cost 8 px. Second,
36
- > `emptyBelowLowestLabel` equals `emptyBelowAxisLine` on **every** chart measured: the tick labels sit
37
- > inside the axis band, and the space below them is occupied by nothing at all.
38
- >
39
- > **This is the same failure class as hram's tornado** (`RES-28`), where `margin.left: 220` was
40
- > reserved *in addition to* an unsized `yAxis`, and as `UCM-CHART-8`'s `minHeight`/`height` pair:
41
- > **space reserved in two places that add up.** Third instance in this file. Neither `UCM-CHART-8`
42
- > nor `UCM-CHART-9` could have caught it — both worked on the wrapper box; this one is in the axis
43
- > margin arithmetic.
44
-
45
- ---
46
-
47
- ## Part A — Envelope (authoritative WHAT/WHY)
48
-
49
- ### Goal
50
-
51
- The rotated-tick allowance is reserved **once**. A chart with long rotated x labels uses its surface
52
- for the plot, not for an empty band under the axis.
53
-
54
- ### Definition of Done
55
-
56
- - [x] **`extraHeight` is applied once, not twice.** Kept on `axis.height` only; `margin.bottom`'s
57
- addition removed. MUI needs `axis.height` — the live measurement below shows rendered labels
58
- sit fully inside the axis band with nothing below them, i.e. the band already fully
59
- accommodates the rotation; `margin.bottom`'s copy was reserving space nothing used.
60
- - [x] **The plot area grows by the reclaimed amount.** Measured live on hram (`CLI-Kilombero_2026-per_cell-network`,
61
- real data, 1280 px), same three cards, same candidate, before vs. after swapping in the fixed
62
- build: Ward Metrics (35 labels, 340 px) 129.6 px → 51.6 px empty; Access Ladder (35 labels,
63
- 369 px) 156.9 px → 80.6 px; Division Metrics (5 labels, 340 px) 85.2 px → 41.2 px — each
64
- roughly halved, matching "remove exactly one of two equal additive reservations." The residual
65
- is the legitimate rotation-geometry padding this WO's non-goals say not to chase.
66
- - [x] **Tick labels are not clipped.** Checked at both 1280 px and 375 px on the same three live
67
- cards (lowest label's rendered bottom never exceeded the SVG's own bottom edge at either
68
- width) — no clipping introduced by reserving less.
69
- - [x] **Charts with no rotation are byte-identical.** Unit-tested directly against
70
- `spaceForRotatedTicks` (angle-0 axis entry and margin object both pass through by reference,
71
- not just by value) — see `tests/chartDefaults.test.js`, `spaceForRotatedTicks (CHART-10)`.
72
- - [x] **A caller-set `margin.bottom` still wins.** Implemented as an unconditional guarantee rather
73
- than the named `callerSetBottom` escape hatch: `spaceForRotatedTicks` no longer writes to
74
- `margin` in any branch, so a caller-set value is never at risk regardless of rotation — a
75
- strict superset of the old conditional guard, confirmed by both independent reviewers as a
76
- clean simplification, not a behaviour gap. The named variable/constant it used
77
- (`callerSetBottom`, `MUI_CHART_MARGIN_BOTTOM`) were removed as dead code once nothing reads
78
- them.
79
-
80
- ### Investigate in the same pass — likely the same helper
81
-
82
- hram reports **x-axis tick marks rendering without their labels** on scatter charts
83
- (`AccessGapScatterPanel`, the optimization cost-effectiveness scatter) — `HRAM-RES-29` item 2, still
84
- open. A formatter exists on both axes (`ScatterChart.jsx:193,199`), so it is not a missing formatter.
85
- **The suspicion is that it is this same function in the other direction:** `axis.height` set to a
86
- band the labels do not fit, so MUI draws the ticks and clips the text.
87
-
88
- - [x] **Refuted.** `ScatterChart.jsx` never calls `spaceForRotatedTicks` at all (grep-confirmed:
89
- only `BarChart.jsx`/`LineChart.jsx` import it) — its x-axis `height` is never touched by this
90
- helper in either direction, so this function cannot be the cause. Confirmed further at the two
91
- named hram call sites (`AccessGapScatterPanel.jsx:203`, `OptimizationResultsPanel.jsx:1014`):
92
- neither sets `tickLabelStyle.angle` on its `xAxis`, so `rotatedTickMetrics` would return `null`
93
- even if the helper were in the path. This matches hram's own `HRAM-RES-29` register row
94
- (`WORK_ORDERS.md`), which had already independently traced and closed item 2 the same day
95
- (2026-08-22, before this WO's own "still open" line was written): the real cause was
96
- `ScatterChart.jsx` rendering `<MuiScatterChart>` with no explicit `height` pre-`11d362c`
97
- (`UCM-CHART-8`, `2.41.3`), racing MUI's own collision-avoidance against the wrapper's
98
- `aspect-ratio` CSS and clipping tick text with no missing formatter involved. `2.42.0` already
99
- fixes it for every consumer by passing a resolved `height` explicitly; hram's `develop` already
100
- pinned `2.42.0` (why local measurement looked clean) and only `main`/`hram.ch` (still on
101
- `2.41.3` at the time) showed it — closing on hram's next `develop -> main` promotion. No action
102
- needed here or in `HRAM-RES-29`; this WO's own suspicion is retired, not carried forward.
103
-
104
- ### Non-goals
105
-
106
- - Do not change the four presets or `ChartFrame` — they only call this helper.
107
- - Do not revisit `UCM-CHART-8`'s `minHeight`/`height`/`aspect` resolution or `UCM-CHART-9`'s frame
108
- fix. Both are correct and verified; this is a third, independent reservation.
109
- - Do not "fix" this by shrinking `MUI_LABELLED_X_AXIS_HEIGHT` or `MUI_CHART_MARGIN_BOTTOM` — those
110
- constants are not the bug, the double application is.
111
-
112
- ### Risks
113
-
114
- - **Every consumer's chart proportions change**, and in the visible direction: plots get taller. That
115
- is the point, but it is a shared-core publish and the rendered check below is what proves it.
116
- - The clipping failure mode is the mirror of this one. A fix that reserves too little trades a
117
- cosmetic defect for an information-losing one — clipped labels are worse than empty space.
118
- - hram's `RES-28` fixes the same class in hram's own tornado (`margin.left` vs `yAxis.width`). The
119
- two are independent; do not assume one closes the other.
120
-
121
- ### Tests to WRITE — narrow
122
-
123
- - `spaceForRotatedTicks` with a rotated axis: the total reserved space equals the allowance **once**,
124
- asserted against the computed `axis.height` + `margin.bottom` sum — the assertion that fails today.
125
- - Angle 0: output identical to input, axis and margin untouched.
126
- - A caller-set `margin.bottom`: preserved, allowance not added on top.
127
- - The longest-label path (`longestFormattedTick`) still drives the estimate.
128
-
129
- **Rendered check before publish — numeric, on hram with real data.** The same six cards measured
130
- above, at 1280 px and 375 px: the empty band below the axis, per card, before and after. Named in
131
- the register note. If capture is unavailable, DOM measurement is acceptable **and must be declared
132
- as a substitution** — for this WO that is nearly sufficient anyway, since the acceptance criterion
133
- is a measured pixel count rather than an impression.
134
-
135
- ---
136
-
137
- ## Part B — Implementation map
138
-
139
- > **TO BE FILLED BY THE ORCHESTRATOR** on `git pull`. Must carry `spaceForRotatedTicks` and
140
- > `rotatedTickMetrics` with line anchors, the two constants, the callers, the absolute working
141
- > directory, the progress contract, and the preamble. **Do not dispatch while this placeholder
142
- > stands.**
143
-
144
- ---
145
-
146
- ## Part C — Orchestrator-only
147
-
148
- > **TO BE FILLED BY THE ORCHESTRATOR.** Review routing (`reviewer` + `ui_reviewer`, full context —
149
- > Tier 3 shared-core), the numeric rendered check as a hard commit gate, register maintenance,
150
- > registry publish verification, commit, and the execution directive with its self-address guard.
@@ -1,132 +0,0 @@
1
- # UCM-CHART-11 — The rotated-tick allowance is now counted once, but estimated too high
2
-
3
- - **Repo:** `ui-core-micha`, branch `main` (shared-core; publishes from `main`, no staging step)
4
- - **Tier:** 3 — shared-core; every consumer's chart proportions change.
5
- - **Status:** planned
6
- - **Workstream:** `CHART-*`
7
- - **Files:** `src/components/charts/chartDefaults.js` — `rotatedTickMetrics`, `longestFormattedTick`,
8
- `AVERAGE_GLYPH_WIDTH_EM` — and its tests. The presets only call the helper; leave them alone.
9
-
10
- > **Why — measured in hram on 2026-08-22, ucm 2.42.2 installed via the pin, Teaching → Basic Plots,
11
- > real data, 1280 px.** `UCM-CHART-10` removed the double reservation and that holds. What is left is
12
- > a **single reservation that is too large**:
13
- >
14
- > | card | longest x label | `axis.height` reserves | rendered tick band | unused | axis title? |
15
- > |---|---|---|---|---|---|
16
- > | Ward Metrics | `Msolwa Station` (14 ch, full) | 131 px | **79 px** | **52 px** | no |
17
- > | Access Ladder | `Msolwa Station` (14 ch, full) | 131 px | **79 px** | **52 px** | no |
18
- > | Division Metrics | `mang-ula` (8 ch) | 97 px | **56 px** | **41 px** | no |
19
- > | Cost | `Ching'a…` (**MUI-truncated**) | 68 px | **53 px** | 15 px | no |
20
- >
21
- > All four are `-45°`, all four are the same preset. **The unused band scales with the estimate, not
22
- > with the rendered text.** `rotatedTickMetrics` derives `extraHeight` from
23
- > `longestFormattedTick × AVERAGE_GLYPH_WIDTH_EM (0.6) × sin(angle)` — a character-count estimate of
24
- > how wide the longest label *would* be. The rendered text is consistently shorter than that estimate,
25
- > and the surplus becomes an empty band inside the axis.
26
- >
27
- > **Cost is not the well-behaved case — it is the case where the estimate happens to be small.** MUI
28
- > truncates its labels itself (`Ching'a…`), so the estimator sees a short string. Nothing about Cost
29
- > is configured better; it just has less to overestimate.
30
-
31
- ---
32
-
33
- ## Part A — Envelope (authoritative WHAT/WHY)
34
-
35
- ### Goal
36
-
37
- The rotated-tick allowance reflects the text that is actually rendered. A chart with long rotated
38
- labels reserves what those labels occupy — not what a character-count estimate predicts.
39
-
40
- ### Definition of Done
41
-
42
- - [x] **`extraHeight` is derived from measured text extent, not from character count.** Chose a
43
- transient, invisible, shared SVG `<text>` node (`measureTickTextWidthPx`), not canvas: it is
44
- the same element type MUI's own axis ticks render as, so it inherits font metrics via the
45
- page's own cascade with no extra plumbing. Verified live against hram: a hidden node measuring
46
- "Msolwa Station" at 13px returned 91.203125, and the REAL rendered tick's own
47
- `getComputedTextLength()` for the same string was 91.203125 — exact match. `AVERAGE_GLYPH_WIDTH_EM`
48
- stays (still used by the unrelated `sizeYAxisForContent`, Y-axis width, THEME-9) but is no
49
- longer read by the rotated-tick path; a new per-character-width table
50
- (`estimateTextWidthPx`) is the explicitly-declared ESTIMATE fallback for when no DOM is
51
- available (confirmed live: jsdom's `getComputedTextLength` throws "not a function", so the
52
- fallback is what the unit tests actually exercise; the real measurement only runs in a browser).
53
- `measureTextWidth` is also injectable (`spaceForRotatedTicks`'s new optional 4th param) so
54
- tests stub it for determinism instead of depending on either environment.
55
- - [x] **MUI's own truncation is accounted for.** The estimate is always taken from
56
- `longestFormattedTick`'s OUTPUT (i.e. whatever `axis.valueFormatter` produces), never the raw
57
- `axis.data` value — already true structurally before this WO, confirmed and locked in by a new
58
- test (`measures the formatted (already-truncated) tick text...`) using a formatter that
59
- truncates. Cost's own axis sets an explicit `height: 60` (bypasses this helper entirely,
60
- confirmed by reading `CostMetricsPanel.jsx`), so it was never affected either way.
61
- - [x] **Numeric acceptance, on the same four live cards**, Teaching → Basic Plots, 1280 px, real data:
62
- Ward Metrics 129.6 px → **14.6 px**; Access Ladder 156.9 px → **14.6 px**; Division Metrics
63
- 85.2 px → **14.2 px**; Cost 92.3 px → **14.6 px** (unchanged, as expected — its axis.height is
64
- caller-fixed). All four land under the ~15 px target. Getting there needed correcting not just
65
- the width term but how it combines with the `MUI_LABELLED_X_AXIS_HEIGHT` baseline — see the
66
- code comment on `ROTATED_TICK_CLEARANCE_PX` for why an accurate width alone left ~38 px
67
- unused, verified by direct DOM inspection before adding the second half of the fix.
68
- - [x] **Nothing is clipped, at any width.** Checked at 1280 px and 375 px on all four cards: 0 of the
69
- 35 (Ward Metrics/Access Ladder/Cost) or 5 (Division Metrics) x-axis tick labels exceeded their
70
- SVG's bottom edge at either width.
71
- - [x] **Angle 0 stays byte-identical.** `UCM-CHART-10`'s own byte-identical-by-reference test for
72
- this is unchanged and still green (137/137 total, no rewrite needed).
73
-
74
- ### One trap, and I walked into it myself
75
-
76
- **An axis *title* also sits below the lowest tick label.** In the same measurement pass, `Expert
77
- Mode: Simulation Trajectory` showed 53 px below its lowest tick — and every pixel of it is the
78
- x-axis title (`hasAxisTitle: true`, angle 0). I first read that as waste. It is not.
79
-
80
- The four cards in the table above all have **`hasAxisTitle: false`**, which is why their residual is
81
- genuinely unused. **Any measurement used to validate this fix must separate the axis title from the
82
- empty band**, or a "fix" will reclaim space the title needs and clip it.
83
-
84
- ### Non-goals
85
-
86
- - Do **not** revert `UCM-CHART-10`. Its single-reservation change is correct and verified; this is
87
- the size of that single reservation, not whether there are two.
88
- - Do not touch `ScatterChart` — it never calls this helper (established under `UCM-CHART-10`).
89
- - Do not shrink `MUI_LABELLED_X_AXIS_HEIGHT`. That constant is the un-rotated baseline and is not
90
- where the error is.
91
- - Do not "fix" this by forcing truncation on every consumer. Ward names rendering in full is correct
92
- behaviour; the bug is that the reservation does not match them.
93
-
94
- ### Risks
95
-
96
- - **Every consumer's chart proportions change again**, the third time in this series. Plots get
97
- taller. That is the intent, and the numeric acceptance above is what proves it landed.
98
- - Text measurement is environment-sensitive: a canvas or hidden-node measurement must run with the
99
- same font that will render, or it trades one estimate for another. The font is loaded from the
100
- consumer's theme, not from this package — check what is available at call time.
101
- - If measurement is not reliably available at that point in the render, an *improved* estimate
102
- (per-character widths rather than a flat 0.6 em) is an acceptable fallback — but say so explicitly
103
- rather than leaving it looking like a measurement.
104
-
105
- ### Tests to WRITE — narrow
106
-
107
- - A rotated axis whose longest label is long: the reserved height matches the measured extent within
108
- a stated tolerance, asserted against a stubbed measurement so the test is deterministic.
109
- - A rotated axis whose labels MUI truncates: the reservation follows the **truncated** string.
110
- - Angle 0: identical to input, by reference — `UCM-CHART-10`'s existing assertion, unchanged.
111
- - A caller-set `margin.bottom`: still untouched (`UCM-CHART-10`'s guarantee, unchanged).
112
-
113
- **Rendered check before publish — numeric, on hram with real data**, at 1280 px, the four cards
114
- above, before and after, with the axis-title separation described in the trap section. Declare a DOM
115
- measurement as a substitution if capture is unavailable.
116
-
117
- ---
118
-
119
- ## Part B — Implementation map
120
-
121
- > **TO BE FILLED BY THE ORCHESTRATOR** on `git pull`. Must carry `rotatedTickMetrics`,
122
- > `longestFormattedTick`, `AVERAGE_GLYPH_WIDTH_EM` and `spaceForRotatedTicks` with line anchors, the
123
- > absolute working directory, the progress contract, and the preamble. **Do not dispatch while this
124
- > placeholder stands.**
125
-
126
- ---
127
-
128
- ## Part C — Orchestrator-only
129
-
130
- > **TO BE FILLED BY THE ORCHESTRATOR.** Review routing (`reviewer` + `ui_reviewer`, full context —
131
- > Tier 3 shared-core), the numeric rendered check as a hard commit gate, register maintenance,
132
- > registry publish verification, commit, and the execution directive with its self-address guard.
@@ -1,348 +0,0 @@
1
- # UCM-CHART-12 — One layout model for charts. Breaking, and the end of this series.
2
-
3
- - **Repo:** `ui-core-micha`, branch `main` (shared-core; publishes from `main`, no staging step)
4
- - **Tier:** 3 — shared-core, **BREAKING**: major version, `3.0.0`.
5
- - **Status:** planned
6
- - **Workstream:** `CHART-*`
7
- - **Files:** `src/components/charts/chartDefaults.js` (the new resolver; `resolveChartHeight`,
8
- `spaceForRotatedTicks`, `warnOnHeightMismatch` are **deleted**), the four presets, `ChartFrame.jsx`,
9
- their tests, and a new `docs/CHART-LAYOUT.md` migration guide.
10
- - **Consumers are NOT migrated here.** Operator decision 2026-08-22: ucm first, apps one at a time,
11
- each its own WO gated on its pin bump.
12
-
13
- > **Why — four work orders is the evidence, not the anecdote.**
14
- >
15
- > | WO | what it corrected |
16
- > |---|---|
17
- > | `UCM-CHART-8` | `minHeight` vs `height` — wrapper reserved space the chart did not use |
18
- > | `UCM-CHART-9` | `ChartFrame` given a fixed `height` it must never have |
19
- > | `UCM-CHART-10` | rotated-tick allowance added to **both** `axis.height` and `margin.bottom` |
20
- > | `UCM-CHART-11` | that same allowance **estimated** above the rendered text |
21
- >
22
- > Each fixed exactly one term of an equation nobody had written down. A fifth is already visible
23
- > (`ScatterChart`'s x-axis band stays reserved when the tick labels are empty — hram's
24
- > `AccessGapScatterPanel` shows 58 px of blank inside the SVG where the labels should be), and the
25
- > operator has called a stop: **no more term-by-term corrections.**
26
- >
27
- > **The root cause is structural: no single place owns a chart's composition.** Five interacting
28
- > knobs — `minHeight`, `height`, `aspect`, `margin`, `xAxisAngle` — are assembled differently by each
29
- > preset, and consumers add pixel arithmetic of their own on top (`margin.bottom: 58` in
30
- > `AccessGapScatterPanel`, `margin.left: 220` in hram's tornado). Every one of those is the same
31
- > disease: **space reserved in one place and again in another, or reserved as a constant where the
32
- > content is variable.**
33
- >
34
- > **Measured consumer surface, 2026-08-22** — this is why breaking is cheaper than continuing:
35
- >
36
- > | app | chart files | of which use `minHeight` |
37
- > |---|---|---|
38
- > | hram | 29 | 12 |
39
- > | fitness-monitor | 5 | 2 |
40
- > | cockpit / jg-ferien / spesix | 4 | 0 |
41
- > | **total** | **38** | **14** |
42
- >
43
- > 76 % sits in one app. This is a sweep, not a migration programme.
44
-
45
- ---
46
-
47
- ## Part A — Envelope (authoritative WHAT/WHY)
48
-
49
- ### Goal
50
-
51
- One function owns a chart's geometry. Every reserved band is derived from the content it holds and
52
- collapses to zero when that content is empty. Consumers express intent, never pixels. And a test
53
- asserts the composition adds up, so this class of defect cannot recur.
54
-
55
- ### The model — three rules
56
-
57
- **Rule 1 — one size source, no interaction.**
58
- A chart's height comes from a **named size token**: `size="compact" | "standard" | "tall"`, resolved
59
- through the theme's spacing scale. `height` in pixels survives as a **documented escape** for the
60
- justified special case, and the JSDoc says it requires a reason.
61
- - **`minHeight` is removed from all four presets.** A floor is a *card* concern, not a chart one.
62
- - **`aspect` is removed entirely** (operator decision 2026-08-22). One size source, system-wide.
63
- - **`resolveChartHeight` is therefore deleted, not amended** — with `minHeight` and `aspect` both
64
- gone, its three-way resolution has nothing left to resolve. Do not preserve it out of caution.
65
-
66
- **Rule 2 — every band is its own measured content, or zero.**
67
- Height decomposes into `plot + xAxisBand + xTitleBand + legendBand`; width into
68
- `yAxisBand + plot + rightPad`. Each band is sized from what it actually contains, measured in **one**
69
- place. **An empty band is zero.** That single sentence is the fifth defect's fix: reserved space for
70
- tick labels that turn out empty must collapse, not persist.
71
- - No band is ever a constant.
72
- - No term appears in two addends. That is what `UCM-CHART-10` was.
73
-
74
- **Rule 3 — consumers pass intent, never pixels.**
75
- - **No `margin` prop on the presets.** The model owns margins completely. Spacing between a chart and
76
- its neighbours is the card's job.
77
- - **`xAxisAngle` is replaced by `xLabels: "auto" | "horizontal" | "angled"`.** Under `"auto"` the
78
- model decides from the *measured* labels and the available width whether rotation is needed. A
79
- consumer no longer states a rotation and separately pays for its geometry.
80
-
81
- ### The invariant — the actual deliverable
82
-
83
- ```
84
- chartHeight === plotHeight + xAxisBand + xTitleBand + legendBand
85
- chartWidth === yAxisBand + plotWidth + rightPad
86
- ```
87
-
88
- Asserted as a test, per preset, over a matrix of label loads: none, short, long, long-and-many,
89
- empty-strings.
90
-
91
- **`UCM-CHART-8`, `-10` and `-11` would each have failed this on day one.** The width line catches the
92
- two findings still open elsewhere: a scatter y-axis reserving 72 px for a 33 px label, and the
93
- tornado's 220 px gutter. **This assertion, not the fix, is what ends the series** — and it is the one
94
- thing in this WO that must not be dropped for expedience.
95
-
96
- ### Definition of Done
97
-
98
- - [ ] `resolveChartLayout({ size | height, xLabels, xTitle, yTitle, legend, ticks, measureText })`
99
- returns the **complete** geometry — wrapper `sx`, chart height, margins, and both axis sizes.
100
- Nothing outside it does chart arithmetic.
101
- - [ ] `size` tokens resolve through the theme spacing scale; `height` documented as the escape.
102
- - [ ] `minHeight`, `aspect` and `margin` are gone from the four presets. Passing one is a **dev-mode
103
- error naming the replacement**, not a silent ignore — 38 call sites will hit this and the
104
- message is the migration guide.
105
- - [ ] `ChartFrame` **keeps** its `minHeight` as a card floor. `UCM-CHART-9` established that a frame
106
- is not a chart; do not sweep the frame's floor away with the presets' prop.
107
- - [ ] `resolveChartHeight`, `spaceForRotatedTicks`, `warnOnHeightMismatch` deleted.
108
- - [ ] Both invariants asserted, over the label matrix above.
109
- - [ ] **Empty tick labels produce a zero-height axis band**, tested explicitly. This is the
110
- Access-scatter case and it is the reason the model exists.
111
- - [ ] `docs/CHART-LAYOUT.md`: the model in one page, the removed props with their replacements, and a
112
- before/after per prop. Five apps have to read this.
113
-
114
- ### Non-goals
115
-
116
- - No consumer migration here. Five per-app WOs follow, each gated on its pin bump.
117
- - **No compatibility shim** (operator decision): two models coexisting is the ambiguity that caused
118
- this. One breaking version, clear error messages.
119
- - No change to palettes, colours, series rendering, tooltips or interaction.
120
- - Do not "improve" the estimate again. Under Rule 2 there is no estimate — bands are measured.
121
-
122
- ### Risks
123
-
124
- - **Four call sites lose responsive height** with `aspect` gone: `hram/AccessGapScatterPanel.jsx:182`
125
- and three in `fitness-monitor/BodyHistoryPage.jsx`. fitness-monitor's `960/380` ratios were a
126
- design decision, not an accident. **Each converts to a size token as a named, visible decision in
127
- its own migration WO** — not silently. Charts still reflow horizontally (the wrapper stays
128
- `width: 100%`); only the height stops tracking width. Operator accepted this trade on 2026-08-22.
129
- - **The invariant test cannot run on estimates in jsdom.** `UCM-CHART-11` established that jsdom's
130
- `getComputedTextLength` throws, so the unit tests must inject a measurement stub — and a
131
- **browser-level check is therefore mandatory, not optional**, or the invariant is only asserted
132
- against a fiction.
133
- - **hram has several WOs in flight touching these files** (`RES-28`, `HRAM-RES-29`, `FIX-19`,
134
- `CHT-3`). hram's migration WO must sequence after them, and this WO must not start hram's sweep.
135
- - A `3.0.0` major is the first in this package's history. Check `publish.yml`'s version gate handles a
136
- major, and that no consumer pins with a caret that would silently adopt it.
137
-
138
- ### Tests to WRITE
139
-
140
- - The two invariants, per preset, over the label matrix — the centrepiece.
141
- - Empty tick labels → zero-height band.
142
- - `size` token → height mapping, stable and theme-derived.
143
- - `height` escape overrides the token.
144
- - Each removed prop raises the dev-mode error naming its replacement.
145
- - `ChartFrame`'s `minHeight` floor still behaves as `UCM-CHART-9` fixed it — that test stays green
146
- unchanged.
147
-
148
- **Browser-level check before publish**, on hram with real data at 1280 px and one narrow width: the
149
- invariant holds on rendered geometry, and the Access scatter's blank band is gone. DOM measurement is
150
- acceptable and must be declared; here it is sufficient, because the acceptance is arithmetic.
151
-
152
- ---
153
-
154
- ## Part B — Implementation map
155
-
156
- ### Context package
157
-
158
- **Named files to change**
159
-
160
- - `src/components/charts/chartDefaults.js` — the resolver lives here today as three separate,
161
- interacting functions. **Delete** `resolveChartHeight` (:385-394), `warnOnHeightMismatch`
162
- (:408-421), and `spaceForRotatedTicks` (:229-238) plus its private helper `rotatedTickMetrics`
163
- (:183-210) — but **keep** the text-measurement primitives they call: `measureTickTextWidthPx`
164
- (:120-131, real DOM measurement via a hidden shared SVG `<text>` node — this is the "measured, not
165
- estimated" plumbing Rule 2 needs) and `estimateTextWidthPx`/`glyphWidthEm` (:71-86, the no-DOM
166
- fallback — SSR / jsdom without an injected stub). Also keep `sizeYAxisForContent` (:311-348, THEME-9,
167
- y-axis width from tick content — the new resolver's `yAxisBand` should call this, not reinvent it),
168
- `withGridDefaults`, `withAxisDefaults`, `withChartSlotDefaults`, `defaultNumericTickFormatter`,
169
- `PACKAGE_DEFAULT_MARGIN`/`withMarginDefaults` (margins are now fully owned by the resolver per Rule
170
- 3 — decide whether `withMarginDefaults` survives as an internal helper the resolver calls, or is
171
- folded into it; either is fine, it must not stay a separate exported knob a consumer can pass into).
172
- Add the new `resolveChartLayout` export here.
173
- - `src/components/charts/BarChart.jsx` (:35-98), `LineChart.jsx` (:52-123), `ScatterChart.jsx`
174
- (:173-289), all four accept `{ minHeight, height, aspect, margin }` in their destructuring and call
175
- `resolveChartHeight`/`warnOnHeightMismatch`/`spaceForRotatedTicks` — every one of those call sites
176
- is replaced by a single `resolveChartLayout(...)` call. `xAxisAngle` is not currently a named prop on
177
- any of the three (rotation today comes in via `tickLabelStyle.angle` on a caller-supplied `xAxis`
178
- entry, see `rotatedTickMetrics`'s `axis.tickLabelStyle?.angle`) — introducing `xLabels: "auto" |
179
- "horizontal" | "angled"` as a first-class prop is new surface, not a rename; decide how `"angled"`
180
- maps onto what MUI needs (`tickLabelStyle.angle` + the axis height the resolver computes) and how
181
- `"auto"` decides using the *measured* label widths against `plotWidth` (Rule 3) — this is the one
182
- piece of real algorithmic design in this WO, budget time for it.
183
- - `TimeSeriesChart.jsx` (:269, :286) passes `minHeight={CHART_HEIGHT}` to `ChartFrame` (stays — that's
184
- the frame floor, Rule/DoD says `ChartFrame`'s `minHeight` survives) and `height={CHART_HEIGHT}` to the
185
- inner `BarChart` (:286, must migrate to `size="standard"` or equivalent — decide the token that
186
- reproduces 320px, see size-token note below).
187
- - `src/components/charts/ChartFrame.jsx` (:28-49, :112-124) — **unchanged behaviourally**: keeps
188
- `minHeight`/`height`/`aspect` as its own card-floor props (DoD: "`ChartFrame` keeps its `minHeight`
189
- as a card floor"). It currently also imports `warnOnHeightMismatch` (:14, :62-65) for its own
190
- mismatch note — that function is deleted, so `ChartFrame` needs either an inline replacement of the
191
- same dev-mode console warning (same text/condition, just no longer imported from `chartDefaults`) or
192
- a decision that the note is no longer worth keeping now that the presets' own version of it is gone
193
- — **do not delete `ChartFrame`'s warning silently**; if dropping it, say so in `CHART-LAYOUT.md`.
194
- - New file `docs/CHART-LAYOUT.md` — the model in one page, each removed prop paired with its
195
- replacement and a before/after code snippet. Look at `docs/` (if it exists) or the package README for
196
- the house doc style/tone to match; five apps' implementers read this cold.
197
- - Tests: `tests/chartDefaults.test.js` (imports the now-deleted functions at :17-26 — every one of
198
- those imports must be replaced) and `tests/ChartFrame.test.jsx` (the `minHeight` floor regression
199
- test named in the DoD must stay green, unchanged in intent).
200
-
201
- **The invariant, concretely**
202
-
203
- ```
204
- chartHeight === plotHeight + xAxisBand + xTitleBand + legendBand
205
- chartWidth === yAxisBand + plotWidth + rightPad
206
- ```
207
-
208
- `resolveChartLayout` must return every term on the right as its own named field (not just the totals)
209
- so the test can sum them and assert equality against the height/width it also returns — a test that
210
- can only compare final totals cannot catch a term double-counted against another (which is exactly
211
- what `UCM-CHART-10` was). `xAxisBand` comes from `axis.height` (today's `MUI_LABELLED_X_AXIS_HEIGHT` +
212
- `rotatedTickMetrics`'s extra, now unconditional on `xLabels`, not just on caller-supplied
213
- `tickLabelStyle.angle`), `yAxisBand` from `sizeYAxisForContent`'s width, `legendBand` from whether
214
- `hideLegend`/`legendPosition` puts a legend on the vertical axis (0 when legend is off or horizontal —
215
- this band existed nowhere before this WO; MUI's own legend today just grows the SVG uncounted), `plot`
216
- is whatever remains. Empty tick labels (Rule 2 / DoD, the Access-scatter case) must resolve
217
- `xAxisBand`/`yAxisBand` to 0, not the MUI/package floor — trace `longestFormattedTick` (:271-284)
218
- returning `''` through to the band, since today's `rotatedTickMetrics` floors width at `fontSize`
219
- (:193-194) rather than letting empty collapse to zero.
220
-
221
- **Size tokens**
222
-
223
- `theme.spacing` is MUI's 8px unit (`src/theme/tokens.js:182`). `TimeSeriesChart.jsx`'s existing
224
- `CHART_HEIGHT = 320` (its own comment at :18-33 explains *why* a fixed height exists — read it, the
225
- reason still applies) is the one deployed number every current consumer has already implicitly agreed
226
- looks right; make `"standard"` resolve to exactly that (`theme.spacing(40)`) so this migration doesn't
227
- also silently redraw every existing default-sized chart. Pick `"compact"`/`"tall"` proportionally
228
- (e.g. `theme.spacing(30)` / `theme.spacing(50)`) and document the exact numbers in `CHART-LAYOUT.md` —
229
- these are visible product decisions, not internal plumbing, so state them plainly rather than burying
230
- them in the resolver.
231
-
232
- **Removed-prop dev-mode error**
233
-
234
- Two existing precedents pull in different directions: `warnOnHeightMismatch` (being deleted) was a
235
- `console.warn`, gated on `process.env.NODE_ENV !== 'production'`; `TimeSeriesChart.jsx:195`
236
- (`hasSecondaryAxis && !secondaryYAxisLabel`) is a real `throw new Error(...)`, ungated, in production
237
- too. The DoD text ("a dev-mode error naming the replacement, not a silent ignore") reads as: gate on
238
- `NODE_ENV !== 'production'` like the deleted warning did (a shared package must not crash a
239
- consumer's production page over a layout prop), but make it a `throw`, not a `console.warn`, inside
240
- that gate — loud in dev, inert in prod. Apply it to `minHeight`/`aspect`/`margin` on the four presets;
241
- each error message names the specific replacement (`size`/removed-with-no-replacement/removed
242
- respectively — `margin` has no successor prop at all, the model owns it completely, say that plainly
243
- in the message).
244
-
245
- **Do-not-touch / invariants that must survive unchanged**
246
-
247
- - Palette, series colours, tooltips, legend *content*, interaction — none of this WO's concern (Non-
248
- goals). Only the geometry that decides *how much room* things get.
249
- - `sizeYAxisForContent`'s existing behaviour and its own tests (:311-348) — reuse, do not rewrite.
250
- - `ChartFrame`'s `minHeight` floor test (`UCM-CHART-9`'s regression) — must stay green, unmodified in
251
- intent, even though its `warnOnHeightMismatch` import needs to change.
252
- - `ScatterChart.jsx` keeps its own y-axis-only sizing rationale (:213-223, the `data.map(point => y)`
253
- adapter) — this WO's `resolveChartLayout` change must not silently break that adapter's contract.
254
-
255
- **Known pitfall**
256
-
257
- `UCM-CHART-11`'s own register note: jsdom's `getComputedTextLength` throws / is unimplemented, so
258
- `measureTickTextWidthPx` returns `null` under test and every unit test that needs a real number must
259
- inject a measurement stub (the existing `measureTextWidth` injection point in `spaceForRotatedTicks`
260
- is the pattern — carry the same injectability into `resolveChartLayout`). This is *why* the WO also
261
- requires a **mandatory browser-level check** before publish (see Verification, Part C) — the unit
262
- suite alone cannot prove the invariant holds against real rendered text.
263
-
264
- Directive: work from this package; open only the named files to verify. If you need the full
265
- `docs/` directory listing or README style, or need to search for other `chartDefaults` imports across
266
- the package (there may be call sites in `src/index.js` exports or `dev/` harness files not listed
267
- above), delegate that lookup to a read-only Explore sub-agent rather than open-ended browsing.
268
-
269
- ### Target repo working directory (absolute)
270
-
271
- `C:\Users\biglmi\Documents\webapps\ui-core-micha`
272
-
273
- ### Preamble — REQUIRED, do not dispatch without it
274
-
275
- > The text above is the COMPLETE spec — the committed WO file's content, not a plan to refine; there
276
- > is no separate plan file. Read the nearest `AGENTS.md`, the relevant `.codex/skills/<role>/SKILL.md`,
277
- > and this repo's `MEMORY.md` ONLY for conventions. Stay in scope; do not touch auth/permissions/
278
- > deps/schema/CI unless the spec says so; do not update `MEMORY.md`. **Do NOT edit `WORK_ORDERS.md` —
279
- > the register row and the review verdicts are the orchestrator's alone.** Do NOT `git add`/`commit`/
280
- > `push` — leave every change uncommitted in the working tree for the orchestrator's independent
281
- > review. WRITE the tests the "Tests to WRITE" section (Part A) calls for AND **RUN the tests you just
282
- > wrote** to confirm they execute and pass — that is the ONLY test run you do (NOT the app's
283
- > affected/full suite, NOT any review, NOT the mandatory browser-level check — that is the
284
- > orchestrator's, after you finish). The orchestrator re-runs the authoritative set, does the
285
- > independent review, and drives the browser-level check after you finish — those are the gate; your
286
- > own run does not count as it.
287
- >
288
- > Narrate continuously: a `PLAN: <step1> | <step2> | …` line up front, then a single-line
289
- > `PROGRESS: [<n>/<total>] <present-tense action>` before every relevant action (and `… done` on
290
- > completion), spaced so no gap exceeds ~2 min, stdout unbuffered, plus exactly one final
291
- > `RESULT: DONE|BLOCKED <reason>`.
292
-
293
- ---
294
-
295
- ## Part C — Orchestrator only — NOT ADDRESSED TO THE IMPLEMENTER
296
-
297
- > **If you are the implementer reading this work order as your own specification: STOP at this line.
298
- > Everything below describes what the Orchestrator does AFTER you finish. You do none of it — no
299
- > reviewers, no verification run, no register edit, no commit.** You ARE the invocation described
300
- > below; do NOT shell out to `codex exec`.
301
-
302
- ### Execution directive
303
-
304
- Implement through `codex exec` in the background, invoked directly via Bash (never the
305
- `debugger`/`*_coder` Agent wrappers), with BOTH `--skip-git-repo-check` and
306
- `--dangerously-bypass-approvals-and-sandbox`. `cwd` = this repo's root (never `…/webapps`). Fallback
307
- to direct Claude implementation only on Codex quota/rate-limit/non-zero exit — check
308
- `.claude/codex-status.md` first per `AGENTS.md`; the fallback flips authorship, making an independent
309
- `reviewer` mandatory regardless of the routing below.
310
-
311
- ### Review routing
312
-
313
- Tier 3, shared-core, **breaking**: `reviewer` AND `ui_reviewer` both run, full context, concurrently
314
- in one background batch (not sequential, not after commit) — model Sonnet for `reviewer` given Tier 3.
315
- The Orchestrator additionally runs its own targeted pass for: the invariant test's actual assertion
316
- strength (does it sum the named bands, or only compare totals — the thing that would let a
317
- `UCM-CHART-10`-shaped bug back in), whether the dev-mode error truly stays inert in production, and
318
- whether `ChartFrame`'s dropped/kept mismatch warning was a silent behaviour change.
319
-
320
- ### Verification
321
-
322
- 1. The affected-areas test gate: `tests/chartDefaults.test.js`, `tests/ChartFrame.test.jsx`, and any
323
- new chart-preset test files, via the package's test runner (`pnpm test` scoped to these files/dirs
324
- — check `package.json`'s test script for the right invocation, do not run the full suite unless it
325
- turns out these are the only test files in the package).
326
- 2. **Mandatory browser-level check, not optional** (Part A "Tests to WRITE" + Risks): render on hram
327
- with real data at 1280px and one narrow width (375px), confirm the invariant holds against actually
328
- rendered geometry and that the Access-scatter blank band is gone. DOM measurement is acceptable
329
- here and must be declared as such in the register Notiz (per AGENTS.md → Reviews, prototype-style
330
- substitution disclosure) — this WO's own text calls the browser check "sufficient... because the
331
- acceptance is arithmetic," so a declared DOM-measurement substitution meets the gate; a screenshot
332
- is not required, but the specific measured numbers must be reported, not just "looks fine."
333
- 3. Check `publish.yml`'s version gate (`.github/workflows/publish.yml:72-96`, a plain per-segment
334
- integer `greaterThan` compare) — it already handles a major bump correctly (`3.0.0 > 2.42.3`), no
335
- change needed there. Bump `package.json`'s `"version"` (:3, currently `2.42.3`) to `3.0.0` as part
336
- of this diff — the publish workflow is push-triggered off `main` and gates only on the version
337
- string increasing, so the version bump IS the publish trigger; do not push before everything else
338
- here is green. Per the Risk note, do NOT chase down consumer caret pins in this WO — that is each
339
- consumer's own migration WO's problem (Non-goals: no consumer migration here).
340
-
341
- ### Register + commit
342
-
343
- `WORK_ORDERS.md` row `UCM-CHART-12`: `done`, both reviewers named with verdict, the browser-level
344
- check's declared method (DOM measurement or real screenshots) and its numbers, commit SHA. Single
345
- concise commit subject, e.g. `UCM-CHART-12: one layout model for charts (breaking, 3.0.0)`. Push to
346
- `main` (this repo's trunk — no `develop` step, per the Envelope header). This is a **major, breaking,
347
- shared-core** publish: after push, confirm in the Actions log that `publish.yml` actually ran and
348
- published `3.0.0` — a silent publish failure here is invisible to every consumer's future pin bump.