@micha.bigler/ui-core-micha 3.14.3 → 4.0.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.
Files changed (367) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +7 -0
  3. package/dist/auth/UserMenu.js +1 -1
  4. package/dist/auth/apiClient.js +7 -6
  5. package/dist/chat/ChatList.js +1 -1
  6. package/dist/chat/ChatThread.js +2 -1
  7. package/dist/chat/ChatWorkspace.js +1 -1
  8. package/dist/chat/ModelPicker.js +2 -5
  9. package/dist/chat/kitSxRegistry.js +9 -0
  10. package/dist/chat/useChatSocket.js +1 -1
  11. package/dist/components/LoginForm.js +1 -1
  12. package/dist/components/SecurityComponent.js +1 -1
  13. package/dist/components/UserListComponent.js +2 -8
  14. package/dist/components/charts/ChartFrame.js +1 -1
  15. package/dist/components/charts/ScatterChart.js +2 -1
  16. package/dist/components/charts/kitSxRegistry.js +6 -0
  17. package/dist/entries/charts.js +6 -0
  18. package/dist/entries/chat.js +8 -0
  19. package/dist/entries/markdown.js +1 -0
  20. package/dist/index.js +0 -14
  21. package/dist/layout/SectionNav.js +1 -1
  22. package/dist/messaging/AttachmentList.js +2 -2
  23. package/dist/messaging/Composer.js +2 -1
  24. package/dist/messaging/ConversationLaunchers.js +1 -4
  25. package/dist/messaging/ConversationList.js +1 -1
  26. package/dist/messaging/DirectMessageLauncher.js +1 -1
  27. package/dist/messaging/MessagingProvider.js +0 -0
  28. package/dist/messaging/PollCard.js +7 -5
  29. package/dist/messaging/ReadTicks.js +2 -2
  30. package/dist/messaging/Thread.js +5 -3
  31. package/dist/notifications/NotificationBell.js +4 -7
  32. package/dist/notifications/NotificationSettings.js +1 -1
  33. package/dist/onboarding/OnboardingProvider.js +3 -6
  34. package/dist/pages/AccountPage.js +1 -2
  35. package/dist/pages/LoginPage.js +2 -3
  36. package/dist/pages/PasswordChangePage.js +2 -3
  37. package/dist/pages/PasswordInvitePage.js +2 -3
  38. package/dist/pages/PasswordResetRequestPage.js +2 -3
  39. package/dist/pages/SignUpPage.js +2 -3
  40. package/dist/pages/SignupConfirmPage.js +2 -3
  41. package/dist/theme/createAppTheme.js +5 -4
  42. package/dist/theme/kitSxEntry.js +11 -0
  43. package/dist/theme/kitSxRegistry.js +2 -15
  44. package/dist/theme/themeCompleteness.js +3 -12
  45. package/dist/utils/auth-errors.js +4 -3
  46. package/package.json +41 -16
  47. package/.github/workflows/publish.yml +0 -126
  48. package/DESIGN.md +0 -60
  49. package/WORK_ORDERS.md +0 -148
  50. package/dev/entries.jsx +0 -721
  51. package/dev/fixtures.js +0 -26
  52. package/dev/main.jsx +0 -75
  53. package/dev/mockTransport.jsx +0 -128
  54. package/docs/CHART-LAYOUT.md +0 -157
  55. package/docs/DARK-TOKENS.md +0 -140
  56. package/docs/TYPE-SCALE.md +0 -111
  57. package/docs/messaging-deviations.md +0 -118
  58. package/index.html +0 -12
  59. package/renovate.json +0 -10
  60. package/scripts/chart-api-census.mjs +0 -415
  61. package/src/auth/AuthContext.jsx +0 -158
  62. package/src/auth/ReauthModal.jsx +0 -82
  63. package/src/auth/UserMenu.jsx +0 -121
  64. package/src/auth/apiClient.jsx +0 -232
  65. package/src/auth/authApi.jsx +0 -539
  66. package/src/auth/authConfig.jsx +0 -34
  67. package/src/auth/reauth.js +0 -47
  68. package/src/chat/ChatComposer.jsx +0 -36
  69. package/src/chat/ChatList.jsx +0 -87
  70. package/src/chat/ChatThread.jsx +0 -128
  71. package/src/chat/ChatWorkspace.jsx +0 -244
  72. package/src/chat/MarkdownReply.jsx +0 -22
  73. package/src/chat/ModelPicker.jsx +0 -85
  74. package/src/chat/api.js +0 -18
  75. package/src/chat/useChatSocket.js +0 -32
  76. package/src/chat/wire.js +0 -7
  77. package/src/components/AccessCodeManager.jsx +0 -288
  78. package/src/components/AccessCodeSingleUseToggle.jsx +0 -92
  79. package/src/components/AllowedEmailDomainsManager.jsx +0 -92
  80. package/src/components/AuthFactorRequirementCard.jsx +0 -95
  81. package/src/components/BulkInviteCsvTab.jsx +0 -198
  82. package/src/components/EmptyState.jsx +0 -38
  83. package/src/components/LoginForm.jsx +0 -159
  84. package/src/components/MFAComponent.jsx +0 -350
  85. package/src/components/MfaLoginComponent.jsx +0 -252
  86. package/src/components/MobileBottomNav.jsx +0 -82
  87. package/src/components/PasskeysComponent.jsx +0 -252
  88. package/src/components/PasswordChangeForm.jsx +0 -87
  89. package/src/components/PasswordResetRequestForm.jsx +0 -43
  90. package/src/components/PasswordSetForm.jsx +0 -97
  91. package/src/components/ProfileComponent.jsx +0 -348
  92. package/src/components/QrSignupManager.jsx +0 -404
  93. package/src/components/QrSignupValidityManager.jsx +0 -106
  94. package/src/components/RegistrationMethodsManager.jsx +0 -150
  95. package/src/components/SecurityComponent.jsx +0 -141
  96. package/src/components/SocialLoginButtons.jsx +0 -83
  97. package/src/components/SoftChip.jsx +0 -93
  98. package/src/components/StatTile.jsx +0 -39
  99. package/src/components/SupportRecoveryRequestsTab.jsx +0 -482
  100. package/src/components/TurnstileWidget.jsx +0 -99
  101. package/src/components/UserInviteComponent.jsx +0 -76
  102. package/src/components/UserListComponent.jsx +0 -618
  103. package/src/components/charts/BarChart.jsx +0 -102
  104. package/src/components/charts/ChartFrame.jsx +0 -214
  105. package/src/components/charts/LineChart.jsx +0 -126
  106. package/src/components/charts/ScatterChart.jsx +0 -299
  107. package/src/components/charts/TimeSeriesChart.jsx +0 -300
  108. package/src/components/charts/chartDefaults.js +0 -644
  109. package/src/components/charts/chartLabels.js +0 -36
  110. package/src/components/charts/exportChart.js +0 -258
  111. package/src/components/charts/formatters.js +0 -39
  112. package/src/components/charts/palette.js +0 -33
  113. package/src/components/charts/yearTickInterval.js +0 -7
  114. package/src/i18n/authTranslations.ts +0 -2651
  115. package/src/i18n/chartsTranslations.ts +0 -91
  116. package/src/i18n/chatTranslations.ts +0 -55
  117. package/src/i18n/messagingTranslations.ts +0 -120
  118. package/src/i18n/notificationsTranslations.ts +0 -32
  119. package/src/i18n/onboardingTranslations.ts +0 -29
  120. package/src/i18n/sectionNavTranslations.ts +0 -20
  121. package/src/i18n/uiCoreTranslations.js +0 -29
  122. package/src/i18n/userMenuTranslations.ts +0 -5
  123. package/src/index.js +0 -158
  124. package/src/layout/PageLayout.jsx +0 -36
  125. package/src/layout/SectionNav.jsx +0 -320
  126. package/src/messaging/AttachmentList.jsx +0 -145
  127. package/src/messaging/Composer.jsx +0 -197
  128. package/src/messaging/ConversationLaunchers.jsx +0 -51
  129. package/src/messaging/ConversationList.jsx +0 -109
  130. package/src/messaging/DirectMessageLauncher.jsx +0 -112
  131. package/src/messaging/MessageBubble.jsx +0 -183
  132. package/src/messaging/MessagingProvider.jsx +0 -0
  133. package/src/messaging/MessagingScopeConfig.jsx +0 -24
  134. package/src/messaging/PollCard.jsx +0 -163
  135. package/src/messaging/ReactionBar.jsx +0 -43
  136. package/src/messaging/ReadTicks.jsx +0 -82
  137. package/src/messaging/Thread.jsx +0 -195
  138. package/src/messaging/api.js +0 -48
  139. package/src/messaging/composerImageCompression.js +0 -35
  140. package/src/messaging/linkifyText.jsx +0 -37
  141. package/src/notifications/NotificationBell.jsx +0 -84
  142. package/src/notifications/NotificationSettings.jsx +0 -330
  143. package/src/notifications/NotificationsProvider.jsx +0 -224
  144. package/src/notifications/PopupSurface.jsx +0 -105
  145. package/src/notifications/api.js +0 -55
  146. package/src/notifications/feedApi.js +0 -18
  147. package/src/notifications/realtime.jsx +0 -138
  148. package/src/notifications/serviceWorker/sw.js +0 -50
  149. package/src/onboarding/OnboardingProvider.jsx +0 -326
  150. package/src/onboarding/OnboardingWizard.jsx +0 -61
  151. package/src/onboarding/WizardDialogShell.jsx +0 -51
  152. package/src/onboarding/api.js +0 -13
  153. package/src/onboarding/stepSelection.js +0 -16
  154. package/src/onboarding/steps/BrowserPushStep.jsx +0 -121
  155. package/src/onboarding/steps/CompleteNameStep.jsx +0 -46
  156. package/src/onboarding/steps/CookieConsentStep.jsx +0 -133
  157. package/src/onboarding/steps/PwaInstallStep.jsx +0 -47
  158. package/src/pages/AccountPage.jsx +0 -394
  159. package/src/pages/LoginPage.jsx +0 -343
  160. package/src/pages/PasswordChangePage.jsx +0 -62
  161. package/src/pages/PasswordInvitePage.jsx +0 -177
  162. package/src/pages/PasswordResetRequestPage.jsx +0 -68
  163. package/src/pages/SignUpPage.jsx +0 -268
  164. package/src/pages/SignupConfirmPage.jsx +0 -155
  165. package/src/testing/checkKitIntegration.js +0 -233
  166. package/src/theme/createAppTheme.js +0 -423
  167. package/src/theme/fonts.js +0 -3
  168. package/src/theme/index.js +0 -21
  169. package/src/theme/kitSxRegistry.js +0 -285
  170. package/src/theme/themeCompleteness.js +0 -1045
  171. package/src/theme/tokens.js +0 -478
  172. package/src/utils/auth-errors.js +0 -76
  173. package/src/utils/authService.js +0 -223
  174. package/src/utils/resolveErrorText.js +0 -28
  175. package/src/utils/webauthn.js +0 -51
  176. package/tests/AccountPage.profileStatements.test.jsx +0 -162
  177. package/tests/AccountPage.test.jsx +0 -200
  178. package/tests/AuthContext.test.jsx +0 -129
  179. package/tests/BarChart.test.jsx +0 -112
  180. package/tests/BrowserPushStep.test.jsx +0 -110
  181. package/tests/ChartFrame.test.jsx +0 -336
  182. package/tests/ConversationList.test.jsx +0 -144
  183. package/tests/CookieConsentStep.test.jsx +0 -69
  184. package/tests/EmptyState.test.jsx +0 -62
  185. package/tests/LineChart.test.jsx +0 -80
  186. package/tests/MessagingProvider.test.jsx +0 -50
  187. package/tests/MobileBottomNav.test.jsx +0 -319
  188. package/tests/NotificationBell.test.jsx +0 -60
  189. package/tests/NotificationSettings.test.jsx +0 -276
  190. package/tests/NotificationsProvider.test.jsx +0 -240
  191. package/tests/OnboardingProvider.test.jsx +0 -294
  192. package/tests/OnboardingWizard.test.jsx +0 -143
  193. package/tests/PasswordChangeForm.test.jsx +0 -70
  194. package/tests/PasswordInvitePage.test.jsx +0 -142
  195. package/tests/PasswordSetForm.test.jsx +0 -70
  196. package/tests/PopupSurface.test.jsx +0 -142
  197. package/tests/ProfileComponent.refresh.test.jsx +0 -194
  198. package/tests/QrSignupManager.registrationContext.test.jsx +0 -106
  199. package/tests/QrSignupManagerInfoBox.test.jsx +0 -49
  200. package/tests/ScatterChart.test.jsx +0 -270
  201. package/tests/SectionNav.test.jsx +0 -363
  202. package/tests/SignUpPage.test.jsx +0 -208
  203. package/tests/SoftChip.test.jsx +0 -105
  204. package/tests/StatTile.test.jsx +0 -57
  205. package/tests/Thread.test.jsx +0 -151
  206. package/tests/TimeSeriesChart.test.jsx +0 -367
  207. package/tests/UserListComponent.test.jsx +0 -192
  208. package/tests/UserMenu.test.jsx +0 -171
  209. package/tests/apiClientLanguage.test.js +0 -33
  210. package/tests/apiClientPublicPaths.test.js +0 -178
  211. package/tests/authApiLogin.test.js +0 -42
  212. package/tests/authApiRegistration.test.js +0 -31
  213. package/tests/authErrorCodeRendering.test.jsx +0 -77
  214. package/tests/authErrors.test.js +0 -74
  215. package/tests/authTranslations.test.js +0 -74
  216. package/tests/chart-api-census.test.js +0 -366
  217. package/tests/chartAxisTickBand.test.jsx +0 -95
  218. package/tests/chartDefaults.test.js +0 -902
  219. package/tests/chartLabels.test.js +0 -50
  220. package/tests/chartsPalette.test.js +0 -99
  221. package/tests/chatApi.test.js +0 -48
  222. package/tests/chatDriverNames.test.jsx +0 -71
  223. package/tests/chatExports.test.js +0 -3
  224. package/tests/chatList.test.jsx +0 -84
  225. package/tests/chatPicker.test.jsx +0 -82
  226. package/tests/chatSocket.test.jsx +0 -26
  227. package/tests/chatThread.test.jsx +0 -116
  228. package/tests/chatTranslations.test.js +0 -47
  229. package/tests/chatWorkspace.test.jsx +0 -234
  230. package/tests/checkKitIntegration.test.jsx +0 -219
  231. package/tests/createAppTheme.test.js +0 -132
  232. package/tests/createAppThemeDark.test.js +0 -127
  233. package/tests/createAppThemeLightGuard.test.js +0 -40
  234. package/tests/createAppThemePageContrast.test.js +0 -108
  235. package/tests/createAppThemeTypography.test.js +0 -53
  236. package/tests/errorCallSiteGuard.test.js +0 -32
  237. package/tests/exportChart.test.js +0 -313
  238. package/tests/feedApi.test.js +0 -46
  239. package/tests/fixtures/light-theme-baseline.json +0 -2234
  240. package/tests/i18nAggregate.test.js +0 -120
  241. package/tests/i18nGermanVariant.test.js +0 -65
  242. package/tests/loginPageErrorResolution.test.jsx +0 -102
  243. package/tests/messagingApi.test.js +0 -32
  244. package/tests/messagingChunk6.test.jsx +0 -116
  245. package/tests/messagingCompactBubble.test.jsx +0 -64
  246. package/tests/messagingComposer.test.jsx +0 -136
  247. package/tests/messagingComposerChunk5.test.jsx +0 -113
  248. package/tests/messagingContractConformance.test.js +0 -69
  249. package/tests/messagingDirectMessageLauncher.test.jsx +0 -151
  250. package/tests/messagingExports.test.js +0 -21
  251. package/tests/messagingInteractions.test.jsx +0 -136
  252. package/tests/messagingLastMessagePreview.test.jsx +0 -93
  253. package/tests/messagingLinkify.test.jsx +0 -51
  254. package/tests/messagingMessageActions.test.jsx +0 -163
  255. package/tests/messagingMsg6.test.jsx +0 -105
  256. package/tests/messagingMsg6f.test.jsx +0 -155
  257. package/tests/messagingMsg6g.test.jsx +0 -166
  258. package/tests/messagingMsg6h.test.jsx +0 -136
  259. package/tests/messagingMsg6i.test.jsx +0 -129
  260. package/tests/messagingPollRendering.test.jsx +0 -142
  261. package/tests/messagingReactionMerge.test.jsx +0 -77
  262. package/tests/messagingReadTicksTooltip.test.jsx +0 -36
  263. package/tests/messagingThreadReceipts.test.jsx +0 -178
  264. package/tests/messagingThreadingQuoting.test.jsx +0 -87
  265. package/tests/messagingUnreadLifecycle.test.jsx +0 -78
  266. package/tests/notificationsApi.test.js +0 -41
  267. package/tests/notificationsExports.test.js +0 -34
  268. package/tests/onboardingDescriptors.test.js +0 -132
  269. package/tests/packageTreeShaking.test.js +0 -126
  270. package/tests/realtime.test.jsx +0 -219
  271. package/tests/resolveErrorText.test.js +0 -72
  272. package/tests/stepSelection.test.js +0 -53
  273. package/tests/themeCompleteness.test.js +0 -626
  274. package/tests/timeSeriesLegend.test.js +0 -99
  275. package/tests/userScreenTranslations.test.jsx +0 -218
  276. package/tsconfig.build.json +0 -18
  277. package/vite.config.mjs +0 -6
  278. package/work-orders/AUTH-1.md +0 -74
  279. package/work-orders/AUTH-2.md +0 -74
  280. package/work-orders/AUTH-4.md +0 -183
  281. package/work-orders/AUTH-5.md +0 -154
  282. package/work-orders/AUTH-7.md +0 -181
  283. package/work-orders/AUTH-8.md +0 -184
  284. package/work-orders/CHART-1.md +0 -285
  285. package/work-orders/CHART-2.md +0 -222
  286. package/work-orders/CHART-3.md +0 -163
  287. package/work-orders/CHART-4.md +0 -203
  288. package/work-orders/CHART-5.md +0 -226
  289. package/work-orders/CHART-6.md +0 -428
  290. package/work-orders/CHART-7.md +0 -175
  291. package/work-orders/DX-1.md +0 -169
  292. package/work-orders/DX-2.md +0 -188
  293. package/work-orders/DX-3.md +0 -195
  294. package/work-orders/I18N-1.md +0 -254
  295. package/work-orders/I18N-3.md +0 -207
  296. package/work-orders/MSG-3.md +0 -375
  297. package/work-orders/MSG-3b.md +0 -1002
  298. package/work-orders/MSG-3c.md +0 -378
  299. package/work-orders/MSG-3d.md +0 -268
  300. package/work-orders/MSG-3e.md +0 -164
  301. package/work-orders/MSG-3f.md +0 -391
  302. package/work-orders/MSG-6e.md +0 -308
  303. package/work-orders/MSG-6f.md +0 -214
  304. package/work-orders/MSG-6g.md +0 -244
  305. package/work-orders/MSG-6h.md +0 -238
  306. package/work-orders/MSG-6i.md +0 -271
  307. package/work-orders/MSG-6j.md +0 -117
  308. package/work-orders/NOTIF-12.md +0 -214
  309. package/work-orders/NOTIF-13.md +0 -142
  310. package/work-orders/NOTIF-14.md +0 -151
  311. package/work-orders/PRIM-1.md +0 -177
  312. package/work-orders/SHELL-1.md +0 -255
  313. package/work-orders/SHELL-2.md +0 -197
  314. package/work-orders/SHELL-3.md +0 -359
  315. package/work-orders/SHELL-4.md +0 -351
  316. package/work-orders/SHELL-5.md +0 -345
  317. package/work-orders/SHELL-6.md +0 -210
  318. package/work-orders/THEME-1.md +0 -587
  319. package/work-orders/THEME-10.md +0 -303
  320. package/work-orders/THEME-11.md +0 -208
  321. package/work-orders/THEME-2.md +0 -339
  322. package/work-orders/THEME-3.md +0 -210
  323. package/work-orders/THEME-4.md +0 -271
  324. package/work-orders/THEME-5.md +0 -341
  325. package/work-orders/THEME-6.md +0 -247
  326. package/work-orders/THEME-7.md +0 -82
  327. package/work-orders/THEME-8.md +0 -211
  328. package/work-orders/THEME-9.md +0 -194
  329. package/work-orders/UCM-AUTH-10.md +0 -490
  330. package/work-orders/UCM-AUTH-11.md +0 -185
  331. package/work-orders/UCM-AUTH-12.md +0 -98
  332. package/work-orders/UCM-AUTH-13.md +0 -47
  333. package/work-orders/UCM-AUTH-9.md +0 -207
  334. package/work-orders/UCM-CHART-10.md +0 -150
  335. package/work-orders/UCM-CHART-11.md +0 -132
  336. package/work-orders/UCM-CHART-12.md +0 -348
  337. package/work-orders/UCM-CHART-13.md +0 -122
  338. package/work-orders/UCM-CHART-14.md +0 -198
  339. package/work-orders/UCM-CHART-15.md +0 -187
  340. package/work-orders/UCM-CHART-16.md +0 -169
  341. package/work-orders/UCM-CHART-17.md +0 -195
  342. package/work-orders/UCM-CHART-18.md +0 -200
  343. package/work-orders/UCM-CHART-8.md +0 -147
  344. package/work-orders/UCM-CHART-9.md +0 -265
  345. package/work-orders/UCM-CHAT-1.md +0 -190
  346. package/work-orders/UCM-CHAT-2.md +0 -75
  347. package/work-orders/UCM-DEP-1.md +0 -63
  348. package/work-orders/UCM-I18N-4.md +0 -226
  349. package/work-orders/UCM-I18N-5.md +0 -211
  350. package/work-orders/UCM-MSG-20.md +0 -192
  351. package/work-orders/UCM-PRIM-2.md +0 -114
  352. package/work-orders/UCM-SHELL-7.md +0 -134
  353. package/work-orders/UCM-SHELL-8.md +0 -125
  354. package/work-orders/UCM-TEST-1.md +0 -304
  355. package/work-orders/UCM-THEME-13.md +0 -69
  356. package/work-orders/UCM-THEME-14.md +0 -312
  357. package/work-orders/UCM-THEME-15.md +0 -356
  358. package/work-orders/UCM-THEME-16.md +0 -80
  359. package/work-orders/UCM-THEME-17.md +0 -111
  360. package/work-orders/UCM-THEME-18.md +0 -184
  361. package/work-orders/assets/AUTH-4-user-list-card.html +0 -407
  362. package/work-orders/assets/PRIM-1-dashboard-primitives.html +0 -624
  363. package/work-orders/assets/SHELL-2-account-navigation.html +0 -1159
  364. package/work-orders/assets/THEME-1-baseline-reference-sheet.html +0 -1352
  365. package/work-orders/assets/UCM-CHAT-1-ai-chat.html +0 -447
  366. package/work-orders/assets/UCM-THEME-13-dark-prototype.html +0 -390
  367. package/work-orders/assets/UCM-THEME-17-type-scale.html +0 -391
@@ -1,169 +0,0 @@
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.
@@ -1,195 +0,0 @@
1
- # UCM-CHART-17 — The two export buttons make two different promises; make them keep them
2
-
3
- ## Part A — Envelope
4
-
5
- *Authored by the Expertenchat. Authoritative WHAT/WHY.*
6
-
7
- ### Goal
8
-
9
- **Export SVG** yields the chart as scalable, correctly styled vector art. **Export PNG** yields what
10
- the user is looking at — legend, size key and footnotes included.
11
-
12
- ### The defect, from the source
13
-
14
- `exportChart.js:7-11` clones the first `<svg>` in the container and serialises it raw:
15
-
16
- ```js
17
- const svg = findSvg(chartContainer).cloneNode(true);
18
- svg.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
19
- return new Blob([new XMLSerializer().serializeToString(svg)], …)
20
- ```
21
-
22
- Three consequences, and they compound:
23
-
24
- 1. **No styles are carried.** MUI X-Charts styles nearly everything through emotion CSS classes —
25
- tick fonts and sizes, axis and grid colours, text fill, the theme's typography. The clone keeps
26
- `class="MuiChartsAxis-tickLabel css-1abc"` and none of the rules behind it, so a standalone
27
- viewer applies bare SVG defaults. This is the "graphically massively different" the operator
28
- reported. **The PNG inherits it**, because it rasterises the same blob.
29
-
30
- 2. **The legend is gone, and that is ours.** `findSvg` takes the *first* `<svg>`, i.e. the chart.
31
- Since `HRAM-RES-39`/`HRAM-VIS-2`, consuming panels hide MUI's own legend and render a hand-built
32
- one as **HTML** beside the chart, with the size key as a *second* SVG. Both sit outside the
33
- exported element. **Before that consolidation, MUI's legend was inside the chart SVG and did get
34
- exported** — unstyled, but present. Fixing a legend overlap silently degraded the export, and
35
- nobody noticed because nobody opened an exported file.
36
-
37
- 3. **"The first SVG" is fragile** regardless — a panel that renders any SVG ahead of the chart
38
- exports the wrong element. It works today by accident of ordering.
39
-
40
- ### The decision: two paths, two meanings
41
-
42
- The operator chose option C on 2026-08-25. The two buttons already promise different things and
43
- should stop pretending to be one mechanism:
44
-
45
- - **SVG — the chart, faithfully styled.** Keep the clone-and-serialise path, but **inline computed
46
- styles onto the cloned nodes** before serialising. Stays true vector: scalable, editable in
47
- Illustrator/Inkscape, chart only. It does **not** gain the legend, and that is correct for what
48
- this format is for.
49
- - **PNG — the container, as seen.** Rasterise the whole `chartRef` node rather than the chart SVG, so
50
- legend, size key and footnotes are in the file and the output matches the screen by construction.
51
-
52
- **Make the difference legible to the user, not only to us.** Two buttons that produce visibly
53
- different content need labels or tooltips saying so; today they read as one thing in two formats.
54
-
55
- ### Scope
56
-
57
- **`src/components/charts/exportChart.js`**
58
-
59
- 1. **Inline computed styles** into the SVG clone before serialisation. Walk the clone against the
60
- live tree and write the resolved properties as inline style. Keep it to the properties that matter
61
- for chart rendering rather than dumping every computed property — a full dump produces enormous
62
- files and is its own defect.
63
- 2. **Rasterise the container for PNG.** The `chartRef` node ChartFrame already passes
64
- (`ChartFrame.jsx:138`) is the right root — it holds the chart plus everything the panel renders
65
- beside it.
66
- 3. **Stop relying on "the first SVG"** for the SVG path — take the chart deliberately, not
67
- positionally.
68
- 4. **Preserve the public signatures.** `exportChartSvg(container, filename)` and
69
- `exportChartPng(container, filename, backgroundColour)` are consumed by `ChartFrame` and possibly
70
- directly; the change is what they produce, not how they are called.
71
-
72
- **`src/components/charts/ChartFrame.jsx`** — labels or tooltips that distinguish the two outputs.
73
- Translated via the existing `chartsTranslations` path, not hard-coded.
74
-
75
- ### Non-goals / do not touch
76
-
77
- - The chart presets, `resolveChartLayout`, the size scale, the axis bands.
78
- - Consuming apps. hram's six panels with `exportOptions` gain the fix through the pin bump; that is
79
- a separate row.
80
- - Adding export to panels that do not have it (`HRAM-RES-43` did that for one panel in hram).
81
-
82
- ### Risks
83
-
84
- - **A rasteriser is a new dependency in shared-core**, and this package currently has exactly one
85
- runtime dependency (`@fontsource/dm-sans`). Adding a second is a real decision — check whether the
86
- job can be done with the platform's own APIs first, and if a library is warranted, say why in the
87
- WO's implementation record rather than just installing one.
88
- - **Canvas tainting.** DOM-to-canvas rasterisation routes through an SVG `foreignObject`; any
89
- external image or font reference taints the canvas and `toBlob` throws. The existing PNG path
90
- already has this exposure and may already be failing on it — worth checking before assuming the new
91
- path introduces it.
92
- - **Fonts in the SVG.** Inlining computed styles records `font-family: "DM Sans"`, which a machine
93
- without that font will substitute. Either embed the font or accept the substitution — but decide it
94
- and write it down, because a silently substituted font is exactly the "looks different" complaint
95
- this WO is fixing.
96
- - **Style inlining can bloat.** See scope item 1.
97
- - **This has no test that can see it.** A unit test can assert that a `style` attribute exists; it
98
- cannot see that the file looks right. See verification.
99
-
100
- ### Verification
101
-
102
- **Open the exported files.** That is the whole gate, and its absence is why this defect shipped: the
103
- SVG in a viewer and the PNG in an image viewer, both compared against the rendered panel.
104
-
105
- Do it for **two** panels with different shapes — one with a hand-built legend and size key
106
- (`AccessGapScatterPanel`), one without — because the legend behaviour is precisely what differs
107
- between the two export paths.
108
-
109
- Unit tests: the SVG clone carries inlined styles for tick labels and axis strokes; both entry points
110
- still resolve with their existing signatures; the PNG path resolves for a container holding more than
111
- one SVG.
112
-
113
- ---
114
-
115
- ## Part B — Implementation map
116
-
117
- `.claude/models.local.json` has no local override, so the default applies: `implementation.runtime`
118
- is `claude`/`sonnet` — the Orchestrator implemented directly, no Codex dispatch.
119
-
120
- **Dependency decision (risk section, and Part C's approval gate):** no library added. Checked
121
- first, per the WO's own instruction, and it held up: the platform's own `<foreignObject>` → `Image`
122
- → `<canvas>` pipeline works for arbitrary DOM rasterisation with no dependency at all. The one
123
- obstacle — **confirmed live, not assumed** — is that Chrome taints the canvas on `toBlob`
124
- unconditionally the moment the source SVG contains a `<foreignObject>` with HTML content, even with
125
- zero external references (a minimal repro: a `<foreignObject>` holding one `<div>` with inline
126
- `color: red` styling and no images/fonts/external refs still throws `SecurityError: Tainted
127
- canvases may not be exported` when the SVG is loaded via `URL.createObjectURL`). The SAME SVG loaded
128
- via a `data:` URI instead does **not** taint the canvas — confirmed with an identical minimal repro,
129
- then re-confirmed end-to-end against the real `exportChartPng`/`ChartFrame` (real Chrome, via the
130
- dev harness). This is why the approval gate never triggered: the platform-only approach IS viable,
131
- it just needed the `blob:` → `data:` URI swap in `rasterize` (`exportChart.js`), documented inline
132
- there.
133
-
134
- **Canvas tainting on the PRE-FIX path:** checked per the risk section's own instruction ("may
135
- already be failing on it"). It was not — a plain SVG-only blob (no `foreignObject`) loaded via
136
- `URL.createObjectURL` does **not** taint the canvas; the exposure is specific to the `foreignObject`
137
- this WO's PNG path adds, not a pre-existing defect.
138
-
139
- **Verification (both real-browser, via the dev harness's module graph — `dev/entries.jsx` never
140
- committed a probe entry, all verification was done by dynamically rendering `ChartFrame` into a
141
- detached container and calling the real `exportChartSvg`/`exportChartPng`):**
142
-
143
- - Panel shape WITH a hand-built legend + size key (mirroring `AccessGapScatterPanel`: MUI's own
144
- legend hidden, a `<div>` HTML legend with a coloured swatch, a second `<svg>` "size key" beside
145
- the chart): PNG rasterised at 2x scale, no taint, and pixel-sampling the produced PNG at the
146
- swatch's and size-key's known screen coordinates read back their EXACT source colours
147
- (`rgb(25,118,210)` / `#1976d2` and `rgb(156,39,176)` / `#9c27b0`) — the legend and size key are
148
- genuinely present in the raster, not just "no error thrown". SVG export of the same panel:
149
- contains `MuiChartsSurface-root` (the chart), does NOT contain the legend's own text/markup —
150
- chart-only as designed.
151
- - Panel shape WITHOUT a hand-built legend (plain `BarChart` inside `ChartFrame`): both exports
152
- resolve cleanly, no regression for the common case.
153
-
154
- Context package used: `exportChart.js` (rewritten in full, was short), `ChartFrame.jsx:42-108`
155
- (the `runExport`/`exportOptions` wiring) and `:137-151` (`chartRef`, the export root — confirmed
156
- correct: it wraps `content` only, excluding the meta/export-button footer), `chartsTranslations.ts`
157
- (new `EXPORT_SVG_TOOLTIP`/`EXPORT_PNG_TOOLTIP` keys, all four locales).
158
-
159
- ---
160
-
161
- ## Part C — Orchestrator only
162
-
163
- > **STOP — everything below addresses the Orchestrator.**
164
-
165
- **Tier 3 · verification: opened files for two panel shapes, plus the unit tests above**
166
-
167
- Shared-core, and — depending on scope item 2's answer — a dependency addition, which is its own
168
- approval surface under Core Behaviour. If a library is needed, **stop and get the operator's
169
- approval for that specific dependency** before adding it; the approval given on 2026-08-25 was for
170
- the approach, not for a named package.
171
-
172
- - `reviewer` — the inlining's completeness and the taint/font questions.
173
- - `ui_reviewer` — whether the two buttons now read as two different things to a user.
174
- - `sec_reviewer` — not run; no auth, data or exposure surface.
175
-
176
- ### Release
177
-
178
- **Minor → 3.3.0**, per the operator. The PNG's *content* changes (it now includes the legend and
179
- footnotes), so the CHANGELOG entry must **lead** with that, in the style `UCM-CHART-15` established:
180
- a consumer who bumps and finds their exported PNGs different should read why in the first line, not
181
- find it in a bullet.
182
-
183
- ### Register
184
-
185
- Row `UCM-CHART-17`, both review notes, the published version, landing SHA(s) in a second commit
186
- before the push. Record what the opened files actually looked like — that evidence is the point.
187
-
188
- Consumer adoption (hram's pin bump) is a separate row; a shared-core fix that is published but not
189
- consumed is not finished.
190
-
191
- ### Log
192
-
193
- - **2026-08-25** — Envelope authored after the operator reported exports producing graphically very
194
- different plots. Diagnosed from source, not reproduced by the Expertenchat: the operator has seen
195
- the files. Option C chosen — SVG stays vector and chart-only, PNG becomes what the screen shows.
@@ -1,200 +0,0 @@
1
- # UCM-CHART-18 — The PNG export shows what the chart says, not what the user can click
2
-
3
- ## Part A — Envelope
4
-
5
- *Authored by the Expertenchat. Authoritative WHAT/WHY.*
6
-
7
- ### Goal
8
-
9
- The whole-container PNG export contains the chart and everything that **explains** it, and none of the
10
- controls that **operate** it. And a rotated element exports rotated.
11
-
12
- ### Two defects, one file, a few lines apart
13
-
14
- Both raised by `HRAM-CHT-7`'s independent `ui_reviewer` on 2026-08-25 and confirmed — partly by
15
- reading, partly live in the running app.
16
-
17
- **1. Interactive controls are in the image.** Since 3.3.0 the PNG rasterises `ChartFrame`'s whole
18
- `chartRef` box, and `chartRef` wraps `children` only — not `toolbar`, `controls` or `meta`. Anything a
19
- panel renders as children is now in the file. In hram:
20
-
21
- - `StructuralReachabilityPanel.jsx:468` — the footer "More about these caveats" `IconButton`, always
22
- visible, so **every** PNG of that panel carries a button.
23
- - `AllocationPerformancePanel.jsx:536,548` — the drill-down `Button`, and `ResearchDrilldownPanel`
24
- itself when open, both inside the `<ChartFrame>` children.
25
- - `OptimizationResultsPanel.jsx:1120,1141` — the same shape on the frontier chart.
26
-
27
- **2. `transform` is not inlined.** `CHART_STYLE_PROPERTIES` (`:32-36`) and
28
- `CONTAINER_LAYOUT_STYLE_PROPERTIES` (`:44-52`) do not list it, so
29
- `OptimizationResultsPanel.jsx:1107`'s rotated legend swatch exports as a **square instead of a
30
- diamond**.
31
-
32
- ### Why this is fixed here and not in the consuming app
33
-
34
- The alternative was moving each panel's controls out of `children` into `ChartFrame`'s `controls` /
35
- `toolbar` slots, which sit outside `chartRef`. That was rejected, for two reasons:
36
-
37
- - **It is a property of the element kind, not of each panel.** An exported image should show what the
38
- chart communicates, not what can be clicked. Solved generically here, it also holds for every
39
- consumer nobody has looked at.
40
- - **It would undo a deliberate design decision.** `StructuralReachabilityPanel`'s info affordance sits
41
- in the **footer**, beside the caveat line, because `HRAM-RES-30` put it there. Hoisting it into the
42
- toolbar to dodge an export defect would trade a design choice for a workaround.
43
-
44
- After this, hram needs **only a pin bump** — `HRAM-CHT-8` becomes a consumer row, not a rebuild of
45
- three panels.
46
-
47
- ### Scope
48
-
49
- `src/components/charts/exportChart.js`
50
-
51
- 1. **Drop interactive controls from the container clone** in `containerSvgBlob` (`:120`) — `button`,
52
- `input`, `select`, `textarea`, `[role="button"]` at minimum. The SVG path is unaffected: it
53
- exports the chart surface, which holds no HTML controls.
54
-
55
- 2. **Do it *after* `inlineComputedStyles`, never before.** That function walks source and clone
56
- **in lockstep by index** (`:70-77`, `sourceNodes[index]` ↔ `cloneNodes[index]`). Removing nodes
57
- from the clone first shifts every subsequent index, so elements silently receive their
58
- neighbours' styles — an export that looks plausible and is wrong everywhere after the first
59
- removal. **This is the single most likely way to get this WO wrong.**
60
-
61
- 3. **Add `transform` (and `transform-origin`) to the inlined properties.**
62
-
63
- ### Non-goals / do not touch
64
-
65
- - The SVG export path, `findChartSvg`, the `data:`-URI decision, the taint workaround —
66
- `UCM-CHART-17`, done and verified.
67
- - `ChartFrame`'s slot structure. `chartRef` wrapping `children` only is correct; the fix is what the
68
- rasteriser does with them.
69
- - The consuming panels. hram's adoption is `HRAM-CHT-8`.
70
-
71
- ### Risks
72
-
73
- - **The lockstep-index trap.** Scope item 2. Name it in the code comment too, not only here.
74
- - **What disappears must be operation, not content.** A consumer whose legend items are `<button>`s —
75
- MUI's own legend can be interactive — would lose its legend to this change. The affected hram panels
76
- all pass `hideLegend`, so they are not the test case. **Verify against a chart that keeps MUI's own
77
- legend**, and if it vanishes, that is a finding that changes the approach, not a detail to paper
78
- over.
79
- - **`transform` may double-apply on SVG nodes.** An SVG element can carry `transform` as a
80
- *presentation attribute*, which the clone already keeps, while `getComputedStyle` returns the
81
- resolved CSS transform. Writing that inline could compose with the attribute and move the element
82
- twice. Check on a chart with rotated tick labels (`xLabels="angled"`) before assuming it is inert.
83
- - Removing nodes changes layout if anything sized around them — a flex row that held a button now
84
- has one fewer item. Expected, but look at it rather than assuming it is invisible.
85
-
86
- ### Verification
87
-
88
- **Open the files.** Same gate as `UCM-CHART-17`, and for the same reason — both of these defects were
89
- invisible to every test and visible immediately in an exported image.
90
-
91
- 1. A PNG of a panel with a control in `children`: the control is gone, everything else is where it
92
- was.
93
- 2. A PNG of a chart that keeps **MUI's own legend**: the legend is still there. This is the
94
- counter-check for the risk above, and it is not optional.
95
- 3. A rotated element exports rotated — the diamond is a diamond.
96
- 4. A chart with `xLabels="angled"`: tick labels are not doubly rotated or displaced.
97
-
98
- Unit tests: the clone contains no `button` after export; a non-interactive sibling at the same depth
99
- survives; `transform` appears in the inlined declarations.
100
-
101
- ---
102
-
103
- ## Part B — Implementation map
104
-
105
- `.claude/models.local.json` has no local override, so the default applies: `implementation.runtime`
106
- is `claude`/`sonnet` — the Orchestrator implemented directly, no Codex dispatch.
107
-
108
- Implemented exactly per scope: `transform`/`transform-origin` added to `CHART_STYLE_PROPERTIES`
109
- (shared by both export paths); a new `INTERACTIVE_CONTROL_SELECTOR`
110
- (`button, input, select, textarea, [role="button"]`) and `stripInteractiveControls` helper, called
111
- in `containerSvgBlob` strictly **after** `inlineComputedStyles` (the lockstep-index trap named in
112
- the WO) — verified by temporarily reverting the ordering-safe call and confirming the new tests
113
- fail, then restoring.
114
-
115
- **Verification (real Chrome, via the dev harness's module graph — no probe entry committed to
116
- `dev/entries.jsx`, all four cases rendered via direct script injection into detached containers):**
117
-
118
- 1. Control in `children` (an `IconButton` beside a `<span>` caveat note, mirroring
119
- `StructuralReachabilityPanel`'s footer affordance): captured the PNG path's actual intermediate
120
- SVG (intercepting `Image.src`) and confirmed no `<button` tag and no button text present, while
121
- the sibling text and the chart itself both survive.
122
- 2. MUI's own legend (two-series `BarChart`, no `hideLegend`): confirmed live that MUI's legend
123
- renders as `<ul>/<li>/<div>/<span>` (no `button`/`role="button"` in it), then confirmed both
124
- series labels ("Opened", "Resolved") and the `MuiChartsLegend-root` class survive the export
125
- fully intact.
126
- 3. Rotated element: a 24x24px swatch with `transform: rotate(45deg)`, exported to PNG and
127
- pixel-sampled on a real `<canvas>` — the swatch's own bounding-box corner reads back as
128
- background white, its centre as the swatch's own red, i.e. a genuine diamond (clipped corners),
129
- not an unrotated square.
130
- 4. `xLabels="angled"` (7 long category labels): the exported tick `<text>` carries BOTH its
131
- original `transform="rotate(-45, 30, 9)"` XML presentation attribute (kept by `cloneNode`) AND
132
- the newly-inlined `style="transform: matrix(0.707107, -0.707107, 0.707107, 0.707107, ...)"`.
133
- The matrix values are exactly `cos(-45°)=0.707107, sin(-45°)=-0.707107` — a SINGLE 45° rotation,
134
- not a doubled/composed one (which would show different values, e.g. matching -90°) — confirming
135
- the inline `style` declaration wins over the presentation attribute (SVG2: presentation
136
- attributes carry the lowest specificity) rather than composing with it. Tick text still renders
137
- non-blank.
138
-
139
- Context package used: `exportChart.js` in full (short, already read start to finish),
140
- `ChartFrame.jsx:137-151` (`chartRef` — re-confirmed it wraps `children` only).
141
-
142
- **Post-review correction, both reviewers converging independently on the same gap the WO's own
143
- risk section named:** the initial `stripInteractiveControls` removed EVERY matched element
144
- outright, including MUI's own chart legend when it is interactive
145
- (`onItemClick`/`toggleVisibilityOnClick`) — X-Charts renders each legend item as
146
- `<button role="button" class="...MuiChartsLegend-series...">` wrapping the series swatch and
147
- label, so a blanket removal deleted the legend's content along with its click handler. This is
148
- EXACTLY the WO's own risk section, made concrete: "if it vanishes, that is a finding that changes
149
- the approach, not a detail to paper over." Fixed by UNWRAPPING `MuiChartsLegend-series` matches
150
- (replace with a plain `<span>` carrying the same class/style/children) instead of removing them —
151
- content survives, only the (irrelevant, in a static image) clickability is dropped. Re-verified
152
- live against a REAL interactive legend (`slotProps={{ legend: { toggleVisibilityOnClick: true } }}`,
153
- confirmed the live legend items render as actual `<BUTTON>` tags): the export contains zero
154
- `<button` tags, but `MuiChartsLegend-series` and both series labels survive intact. `INTERACTIVE_
155
- CONTROL_SELECTOR` also broadened to `a[href]`/`[role="link"]` (reviewer finding — a drill-down
156
- built as a link, not only a `<Button>`, is equally out of place in an export).
157
-
158
- Also fixed a genuine test-coverage gap the reviewer found: the original ordering test's `<button>`
159
- was the LAST descendant in its fixture, so a "strip before inline" regression would not have shifted
160
- any node the test actually asserted on — content-presence checks can't observe a style
161
- misattribution at all, only a missing/wrong STYLE VALUE can. Rewrote it with a distinctively-styled
162
- sibling AFTER the button (stylesheet-driven colour, not an inline style `cloneNode` would copy
163
- regardless) and confirmed, by temporarily reverting the call order, that the new assertion actually
164
- fails when the order is wrong (the button's own style — `border-style: none`, `text-align: center`
165
- — leaks onto the trailing sibling).
166
-
167
- ---
168
-
169
- ## Part C — Orchestrator only
170
-
171
- > **STOP — everything below addresses the Orchestrator.**
172
-
173
- **Tier 3 · verification: opened files for the four cases above, plus the unit tests**
174
-
175
- Shared-core, by the tiering table regardless of diff size.
176
-
177
- - `ui_reviewer` — what belongs in an exported image, and the MUI-legend counter-check.
178
- - `reviewer` — the ordering trap and the `transform` double-apply question.
179
- - `sec_reviewer` — not run; no auth, data or exposure surface.
180
-
181
- ### Release
182
-
183
- **Patch → 3.3.1.** This repairs behaviour 3.3.0 itself introduced — the whole-container PNG was never
184
- meant to carry buttons — rather than adding capability. `UCM-CHART-16` (a repair) went out as a patch
185
- on the same reasoning, while `-15` and `-17` were minors because they changed what a consumer gets on
186
- purpose. The CHANGELOG should still say plainly that exported PNGs no longer contain controls; a
187
- consumer who liked the button in their image deserves to find out by reading, not by looking.
188
-
189
- ### Register
190
-
191
- Row `UCM-CHART-18`, both review notes, the published version, landing SHA(s) in a second commit
192
- before the push. Record what the opened files looked like, including the MUI-legend counter-check.
193
-
194
- Consumer adoption is `HRAM-CHT-8`, which this WO reduces to a pin bump.
195
-
196
- ### Log
197
-
198
- - **2026-08-25** — Envelope authored. The operator chose the shared-core fix over moving controls in
199
- three hram panels, so that the rule holds for every consumer and `HRAM-RES-30`'s footer placement
200
- survives.