@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
package/dev/fixtures.js DELETED
@@ -1,26 +0,0 @@
1
- export const notificationFixtures = [
2
- {
3
- id: 101,
4
- notification_id: 501,
5
- notification_type: 'case.updated',
6
- content: { title_key: 'harness.caseUpdated', body_key: 'harness.caseUpdatedBody' },
7
- created_at: '2026-07-31T09:00:00Z',
8
- seen_at: null,
9
- },
10
- {
11
- id: 102,
12
- notification_id: 502,
13
- notification_type: 'case.assigned',
14
- content: { title_key: 'harness.caseAssigned', body_key: 'harness.caseAssignedBody' },
15
- created_at: '2026-07-30T12:00:00Z',
16
- seen_at: '2026-07-30T12:05:00Z',
17
- },
18
- ];
19
-
20
- export const barChartFixture = {
21
- series: [
22
- { data: [24, 36, 28, 52], label: 'Opened' },
23
- { data: [18, 29, 31, 44], label: 'Resolved' },
24
- ],
25
- xAxis: [{ data: ['Apr', 'May', 'Jun', 'Jul'] }],
26
- };
package/dev/main.jsx DELETED
@@ -1,75 +0,0 @@
1
- import { StrictMode, useMemo, useState } from 'react';
2
- import { createRoot } from 'react-dom/client';
3
- import { I18nextProvider } from 'react-i18next';
4
- import i18next from 'i18next';
5
- import { MemoryRouter } from 'react-router-dom';
6
- import { Box, Button, ButtonGroup, CssBaseline, FormControl, InputLabel, MenuItem, Paper, Select, Stack, ThemeProvider, Typography, createTheme } from '@mui/material';
7
-
8
- import { chartsTranslations } from '../src/i18n/chartsTranslations';
9
- import { notificationsTranslations } from '../src/i18n/notificationsTranslations';
10
- import { messagingTranslations } from '../src/i18n/messagingTranslations';
11
- import { sectionNavTranslations } from '../src/i18n/sectionNavTranslations';
12
- import { createAppTheme } from '../src/theme/createAppTheme';
13
- import { entries } from './entries';
14
- import { MockTransportProvider } from './mockTransport';
15
-
16
- const translations = Object.fromEntries(
17
- Object.entries({
18
- ...notificationsTranslations,
19
- ...chartsTranslations,
20
- ...messagingTranslations,
21
- ...sectionNavTranslations,
22
- }).map(([key, value]) => [key, value.en]),
23
- );
24
- Object.assign(translations, {
25
- 'harness.caseUpdated': 'Case updated',
26
- 'harness.caseUpdatedBody': 'The case is ready for review.',
27
- 'harness.caseAssigned': 'Case assigned',
28
- 'harness.caseAssignedBody': 'A case was assigned to you.',
29
- });
30
-
31
- const i18n = i18next.createInstance();
32
- i18n.init({ lng: 'en', fallbackLng: 'en', resources: { en: { translation: translations } }, interpolation: { escapeValue: false } });
33
-
34
- function Harness() {
35
- const [entryId, setEntryId] = useState(entries[0].id);
36
- const [mode, setMode] = useState('light');
37
- const [width, setWidth] = useState('desktop');
38
- const entry = entries.find((item) => item.id === entryId) || entries[0];
39
- const usesBaselineTheme = ['theme-baseline', 'mobile-bottom-nav'].includes(entry.id);
40
- const usesBrowserViewport = entry.id === 'mobile-bottom-nav';
41
- const effectiveMode = mode;
42
- const theme = useMemo(
43
- () => usesBaselineTheme
44
- ? createAppTheme({ palette: { mode, primary: { main: '#3D5A99' } } })
45
- : createTheme({ palette: { mode } }),
46
- [mode, usesBaselineTheme],
47
- );
48
- const viewportWidth = width === 'mobile' ? 390 : width === 'tablet' ? 768 : 1180;
49
- const Entry = entry.Component;
50
-
51
- return (
52
- <ThemeProvider theme={theme}>
53
- <CssBaseline />
54
- <Box sx={{ minHeight: '100vh', p: 3, bgcolor: 'background.default' }}>
55
- <Stack direction={{ xs: 'column', md: 'row' }} spacing={2} sx={{ mb: 3 }} alignItems={{ md: 'center' }}>
56
- <Typography variant="h5" sx={{ mr: 'auto' }}>ui-core-micha harness</Typography>
57
- <FormControl size="small" sx={{ minWidth: 220 }}><InputLabel id="entry-label">Entry</InputLabel><Select labelId="entry-label" label="Entry" value={entryId} onChange={(event) => setEntryId(event.target.value)}>{entries.map((item) => <MenuItem key={item.id} value={item.id}>{item.label}</MenuItem>)}</Select></FormControl>
58
- <ButtonGroup size="small"><Button variant={effectiveMode === 'light' ? 'contained' : 'outlined'} onClick={() => setMode('light')}>Light</Button><Button variant={effectiveMode === 'dark' ? 'contained' : 'outlined'} onClick={() => setMode('dark')}>Dark</Button></ButtonGroup>
59
- {usesBrowserViewport ? (
60
- <Typography variant="caption" color="text.secondary">Use the browser viewport for this specimen</Typography>
61
- ) : (
62
- <ButtonGroup size="small">{['mobile', 'tablet', 'desktop'].map((size) => <Button key={size} variant={width === size ? 'contained' : 'outlined'} onClick={() => setWidth(size)}>{size}</Button>)}</ButtonGroup>
63
- )}
64
- </Stack>
65
- <Paper variant="outlined" sx={{ width: viewportWidth, maxWidth: '100%', mx: 'auto', p: 3, overflow: 'hidden', containerType: 'inline-size' }}>
66
- <Entry />
67
- </Paper>
68
- </Box>
69
- </ThemeProvider>
70
- );
71
- }
72
-
73
- createRoot(document.getElementById('root')).render(
74
- <StrictMode><I18nextProvider i18n={i18n}><MemoryRouter><MockTransportProvider><Harness /></MockTransportProvider></MemoryRouter></I18nextProvider></StrictMode>,
75
- );
@@ -1,128 +0,0 @@
1
- import { createContext, useContext, useMemo, useRef } from 'react';
2
-
3
- import apiClient from '../src/auth/apiClient';
4
- import { RealtimeContext } from '../src/notifications/realtime';
5
- import { notificationFixtures } from './fixtures';
6
-
7
- const MockTransportContext = createContext(null);
8
-
9
- /**
10
- * Stands in for the browser's WebSocket wherever a component (e.g.
11
- * NotificationsProvider) opens its own socket internally via
12
- * `useRealtimeCore` rather than reading it from context — that path can't be
13
- * satisfied by wrapping RealtimeContext.Provider, since the component never
14
- * looks at ambient context for it. Registering this as `window.WebSocket`
15
- * lets the harness intercept the connection at the point the component
16
- * actually creates it, instead of opening a real (and always-failing)
17
- * socket to a harness-local URL.
18
- */
19
- class MockWebSocket {
20
- constructor(url) {
21
- this.url = url;
22
- this.readyState = MockWebSocket.CONNECTING;
23
- MockWebSocket._instances.add(this);
24
- // Mirror the async-open timing of a real WebSocket handshake.
25
- setTimeout(() => {
26
- this.readyState = MockWebSocket.OPEN;
27
- this.onopen?.(new Event('open'));
28
- }, 0);
29
- }
30
-
31
- send() {}
32
-
33
- close() {
34
- this.readyState = MockWebSocket.CLOSED;
35
- MockWebSocket._instances.delete(this);
36
- this.onclose?.(new CloseEvent('close'));
37
- }
38
-
39
- static _instances = new Set();
40
-
41
- static dispatch(frame) {
42
- const payload = { data: JSON.stringify(frame) };
43
- MockWebSocket._instances.forEach((socket) => {
44
- if (socket.readyState === MockWebSocket.OPEN) socket.onmessage?.(payload);
45
- });
46
- }
47
- }
48
- MockWebSocket.CONNECTING = 0;
49
- MockWebSocket.OPEN = 1;
50
- MockWebSocket.CLOSING = 2;
51
- MockWebSocket.CLOSED = 3;
52
-
53
- export function createMockTransport({ notifications = notificationFixtures } = {}) {
54
- let feed = [...notifications];
55
- const subscribers = new Map();
56
- const reconnectSubscribers = new Set();
57
-
58
- return {
59
- async adapter(config) {
60
- const { method = 'get', url = '' } = config;
61
- let data = {};
62
- if (method === 'get' && url === '/api/notifications/feed/') {
63
- data = { results: feed };
64
- } else if (method === 'get' && url === '/api/notifications/feed/unread-count/') {
65
- data = { count: feed.filter((item) => !item.seen_at && !item.dismissed_at).length };
66
- } else if (method === 'post' && url === '/api/notifications/feed/mark/') {
67
- const { action, ids = [] } = JSON.parse(config.data || '{}');
68
- feed = feed.map((item) => (ids.includes(item.id)
69
- ? { ...item, [`${action}_at`]: item[`${action}_at`] || new Date().toISOString() }
70
- : item));
71
- data = { updated: ids.length };
72
- } else {
73
- return Promise.reject(new Error(`No harness response for ${method.toUpperCase()} ${url}`));
74
- }
75
- return { config, data, headers: {}, status: 200, statusText: 'OK' };
76
- },
77
- subscribe(envelope, handler) {
78
- const handlers = subscribers.get(envelope) || new Set();
79
- handlers.add(handler);
80
- subscribers.set(envelope, handlers);
81
- return () => handlers.delete(handler);
82
- },
83
- // Mirrors the shape `useRealtimeCore` returns (`{ subscribe, onReconnect }`,
84
- // the MSG-3 chunk-1 additive extension) so a component that calls
85
- // `onReconnect()` internally (e.g. MessagingProvider) can be exercised in
86
- // the harness the same way `dispatchReconnect()` lets it be triggered.
87
- onReconnect(handler) {
88
- reconnectSubscribers.add(handler);
89
- return () => reconnectSubscribers.delete(handler);
90
- },
91
- // Drives both consumption paths: components reading `useRealtime()`
92
- // from an ambient RealtimeContext (e.g. a harness entry composed
93
- // directly under MockTransportProvider), and components that open
94
- // their own socket internally (e.g. NotificationsProvider), which
95
- // receive the frame via the intercepted `window.WebSocket` instead.
96
- dispatch(frame) {
97
- const envelope = frame.envelope || 'notification';
98
- subscribers.get(envelope)?.forEach((handler) => handler(frame));
99
- MockWebSocket.dispatch(frame);
100
- },
101
- dispatchReconnect() {
102
- reconnectSubscribers.forEach((handler) => handler());
103
- },
104
- };
105
- }
106
-
107
- export function MockTransportProvider({ children }) {
108
- const transportRef = useRef(null);
109
- if (!transportRef.current) {
110
- transportRef.current = createMockTransport();
111
- apiClient.defaults.adapter = transportRef.current.adapter;
112
- window.WebSocket = MockWebSocket;
113
- }
114
- const transport = transportRef.current;
115
- const realtimeValue = useMemo(() => ({ subscribe: transport.subscribe, onReconnect: transport.onReconnect }), [transport]);
116
-
117
- return (
118
- <MockTransportContext.Provider value={transport}>
119
- <RealtimeContext.Provider value={realtimeValue}>{children}</RealtimeContext.Provider>
120
- </MockTransportContext.Provider>
121
- );
122
- }
123
-
124
- export function useMockTransport() {
125
- const transport = useContext(MockTransportContext);
126
- if (!transport) throw new Error('useMockTransport must be used within MockTransportProvider');
127
- return transport;
128
- }
@@ -1,157 +0,0 @@
1
- # Chart layout model (UCM-CHART-12)
2
-
3
- One function, `resolveChartLayout` (`src/components/charts/chartDefaults.js`), owns the geometry
4
- of every chart preset (`BarChart`, `LineChart`, `ScatterChart`, and indirectly `TimeSeriesChart`,
5
- which wraps `BarChart`). It replaces `resolveChartHeight`, `spaceForRotatedTicks`, and
6
- `warnOnHeightMismatch` — all three deleted in this version. This is a **breaking major (`3.0.0`)**.
7
-
8
- ## The model
9
-
10
- Every reserved band is derived from the content it holds and collapses to zero when that content
11
- is empty. Consumers express intent, never pixels.
12
-
13
- ```
14
- chartHeight === plotHeight + xAxisBand + xTitleBand + legendBand
15
- ```
16
-
17
- - `xAxisBand` — the tick row. Zero when the axis has no visible tick content (an empty `data`
18
- array, or every formatted tick is a blank string). Non-zero otherwise: for horizontal labels, a
19
- floor of 25px (MUI X-Charts' own `DEFAULT_AXIS_SIZE_HEIGHT`) scaled up from the actual tick font
20
- size once it needs more room than that floor leaves MUI's own internal fit check — a flat floor
21
- alone was a near-miss with essentially no margin at any font size above whatever MUI itself
22
- implicitly assumed (`UCM-CHART-16`); or a measured/estimated projection for rotated ones (same
23
- measurement mechanism as before — a real DOM measurement when available, a per-glyph estimate
24
- otherwise). The title addition (20px, `AXIS_LABEL_DEFAULT_HEIGHT`) grows by a further few pixels
25
- when an axis title shares the tick row's band, matching the extra gap MUI itself reserves
26
- between the two.
27
- - `xTitleBand` — the axis title. Zero unless the x-axis carries a `label`.
28
- - `legendBand` — one row for the legend. Zero when `hideLegend` is set.
29
- - `plotHeight` is the residual: `chartHeight` minus the three bands above.
30
- - The y-axis band collapses to zero the same way the x-axis one does: candidates existed (the
31
- axis has real `min`/`max` or plotted data) but a caller `valueFormatter` blanks every formatted
32
- tick, and there is no axis title either — the axis has nothing to show, so it reserves nothing.
33
- Distinct from "nothing to measure at all" (no `min`/`max`/data), where MUI's own default applies
34
- untouched.
35
-
36
- Width follows the same shape (`yAxisBand + plotWidth + secondaryYAxisBand + rightPad`), but the
37
- presets stay responsive — they never fix a pixel width — so `plotWidth`/`containerWidth` are only
38
- meaningful when a caller explicitly supplies `containerWidth` (tests, or the browser-level check);
39
- the presets themselves never pass it. A **secondary (right-positioned) y-axis** — `TimeSeriesChart`'s
40
- `axis: 'secondary'` feature, or any preset given a two-entry `yAxis` array with one `position:
41
- 'right'` — reserves its own width on the right, added to `margin.right` alongside the fixed
42
- breathing-room pad; an axis with no explicit `position` defaults to the left, matching MUI's own
43
- default-assignment rule.
44
-
45
- ## Removed props, and their replacement
46
-
47
- | Removed prop | Replacement | Why |
48
- |---|---|---|
49
- | `minHeight` | `size` (see below) | A floor was a *card* concern, not a chart one — `ChartFrame` still has its own `minHeight`, unchanged. |
50
- | `aspect` | `size` | One size source, system-wide (operator decision). A chart's height no longer tracks its width. |
51
- | `margin` | *(none)* | The model owns margins completely — spacing between a chart and its neighbours is the card's job. |
52
- | `xAxisAngle` / a caller-set `tickLabelStyle.angle` | `xLabels: "auto" \| "horizontal" \| "angled"` | Under `"auto"` the model decides from the tick count and label length whether rotation is needed — a consumer no longer states a rotation and separately pays for its geometry. |
53
-
54
- Passing a removed prop throws in development (`assertRemovedChartProp`), naming the replacement.
55
- It is inert in production — a shared package must not crash a consumer's page over a layout prop.
56
- This is deliberate, not a silent ignore: every call site that still passes `minHeight`/`aspect`/
57
- `margin` will see it immediately in dev.
58
-
59
- ## `size` — before / after
60
-
61
- ```diff
62
- - <BarChart minHeight={320} series={series} />
63
- + <BarChart size="standard" series={series} />
64
- ```
65
-
66
- ```diff
67
- - <BarChart aspect={1.8} minHeight={280} series={series} />
68
- + <BarChart size="tall" series={series} />
69
- ```
70
-
71
- Tokens resolve through the theme's spacing scale (`theme.spacing`, MUI's 8px unit):
72
-
73
- | Token | Spacing units | Height |
74
- |---|---|---|
75
- | `compact` | 40 | 320px |
76
- | `standard` | 50 | 400px |
77
- | `tall` | 60 | 480px |
78
- | `extra_tall` | 70 | 560px |
79
- | `super_tall` | 80 | 640px |
80
-
81
- **UCM-CHART-15** shifted the whole scale up one step and added `extra_tall`/`super_tall` above the
82
- old ceiling — `tall` (400px) had been the largest token, and hram had already needed the `height`
83
- px escape to get past it. `standard`'s old pin to 320px (`TimeSeriesChart`'s historical
84
- `CHART_HEIGHT`) was a migration guardrail for CHART-12, not a design verdict; that migration is
85
- long complete, so the scale is now a chosen set of five values rather than one inherited number.
86
- `height` (a raw px number) survives as a **documented escape** for a justified special case; prefer
87
- `size`.
88
-
89
- **Four call sites lose responsive height with `aspect` gone**: `hram/AccessGapScatterPanel.jsx`
90
- and three in `fitness-monitor/BodyHistoryPage.jsx` (its `960/380` ratios were a design decision,
91
- not an accident — operator accepted this trade 2026-08-22). Each converts to a size token as a
92
- named, visible decision in its own migration WO — charts still reflow horizontally (`width: 100%`
93
- on the wrapper), only the height stops tracking width.
94
-
95
- ## `xLabels` — before / after
96
-
97
- ```diff
98
- - <BarChart xAxis={[{ data: wardNames, tickLabelStyle: { angle: -45 } }]} />
99
- + <BarChart xAxis={[{ data: wardNames }]} xLabels="angled" />
100
- ```
101
-
102
- ```diff
103
- - <BarChart xAxis={[{ data: months }]} /> {/* horizontal by default, unrotated */}
104
- + <BarChart xAxis={[{ data: months }]} xLabels="auto" /> {/* the new default; equivalent here */}
105
- ```
106
-
107
- `"auto"` (the default) is a **heuristic**, not a true fit computation — the presets stay
108
- responsive, so the resolver has no real container width to measure against before render. It
109
- rotates when there are more than 6 categories *and* the longest label's real MEASURED width
110
- (same DOM measurement the rotated-band projection itself uses — a real browser measurement when
111
- available, a per-glyph estimate otherwise) exceeds 50px; otherwise it stays horizontal.
112
- `"horizontal"` always stays flat; `"angled"` always rotates (at the caller's own
113
- `tickLabelStyle.angle`, or -45° if unset). "Measured labels" (Rule 3) is therefore real — what
114
- remains unmeasured is "available width", since no container width exists before render. Verify a
115
- specific chart's real rendered fit with the browser-level check described below.
116
-
117
- ## `margin` — no replacement
118
-
119
- There is no margin prop any more. Spacing between a chart card and its neighbours is the card's
120
- job (a wrapping `Box`/`Stack`'s own `gap`/`p`), not the chart's.
121
-
122
- ```diff
123
- - <Chart series={series} margin={{ left: 220 }} /> {/* hand-tuned gutter for a long label */}
124
- + <Box sx={{ pl: '220px' }}>
125
- + <Chart series={series} />
126
- + </Box>
127
- ```
128
-
129
- If the extra space was compensating for a wide y-axis label the resolver now sizes itself
130
- (`sizeYAxisForContent`), try dropping the override entirely first — the model may already reserve
131
- the right amount.
132
-
133
- ## What stays unchanged
134
-
135
- - `ChartFrame`'s own `minHeight` — it is a whole-card floor (title, toolbar, chart, legend,
136
- footnotes, export links), not a single chart's size (`UCM-CHART-9` established this distinction).
137
- `height` and `aspect` are **not** part of this — `UCM-CHART-13` removed them, but for different
138
- reasons: `height` was destructured and only ever fed a dev-mode warning, never applied — genuinely
139
- dead. `aspect` was applied (`aspectRatio` on the box, through 3.0.1) and worked.
140
- **Correction (`UCM-CHART-14`, left legible rather than silently rewritten):** `UCM-CHART-13`
141
- originally claimed here that no consumer passed `aspect`. That measurement was false — a
142
- parser-based census (`scripts/chart-api-census.mjs`) found **four live call sites in
143
- fitness-monitor** (`BodyHistoryPage.jsx` ×2, `EnvironmentPage.jsx` ×2) a `head`-truncated grep had
144
- missed. The removal itself still stands; only the affected-consumer count was wrong. Those four
145
- sites are `FM-CHART-1`'s scope. Passing either prop now throws in dev, naming the real replacement
146
- — `aspect`'s message states plainly that it WAS applied and this is a real, visible behaviour
147
- change for whoever still passes it, not a no-op cleanup.
148
- - Palettes, series colours, tooltips, legend content, interaction.
149
- - `sizeYAxisForContent` (y-axis width from tick content) — reused unchanged inside the resolver.
150
-
151
- ## Verifying a migrated chart
152
-
153
- The invariant is arithmetic, but only against **real rendered geometry** — jsdom cannot measure
154
- SVG text (`getComputedTextLength` is unimplemented there), so the unit suite injects a stubbed
155
- measurement. Before shipping a chart that leans on rotated or auto-detected labels, render it in
156
- a real browser at a realistic width and confirm no tick label is clipped and no band is reserved
157
- with nothing in it (the Access-scatter defect this WO exists to close).
@@ -1,140 +0,0 @@
1
- # Dark-mode baseline tokens
2
-
3
- Reference sheet for `UCM-THEME-13`. **This file is the value source** for the dark baseline: `UCM-THEME-14`
4
- (dark mode in `createAppTheme`) takes every dark value from here by token name, and if code and this
5
- sheet ever disagree, this sheet is the one to fix first.
6
-
7
- Instrument: [`work-orders/assets/UCM-THEME-13-dark-prototype.html`](../work-orders/assets/UCM-THEME-13-dark-prototype.html)
8
- (the screen the values were judged on, at 375 px and 1280 px, with contrasts computed in the page).
9
-
10
- ## Frozen
11
-
12
- **Frozen 2026-10-02 by the operator** ("Passt so, bitte einfrieren"), after two value rounds.
13
-
14
- ## Structural decisions (operator, 2026-10-01)
15
-
16
- 1. **Complete baseline.** Every mode-dependent token class of the light baseline has a dark value, so an app
17
- that turns dark on is complete and `assertThemeComplete` can judge dark as it judges light.
18
- 2. **Outlined first, as in light.** Resting surfaces carry no shadow. Structure comes from borders and a
19
- small surface step. Only overlays (dialog, menu, popover, drawer) stand out, through a lighter surface;
20
- their shadow is an edge, not the means of separation.
21
- 3. **Primary is derived unless the app sets it.** The factory lightens the app's light primary until it
22
- clears 4.5:1 on the dark page. An app that supplies its own dark primary (webshop-guenter: `#D49040`)
23
- keeps it.
24
- 4. **Fixed dark status set, derivation as a safety net.** The status colours below are decided values. On
25
- an app page that is not the baseline page, the factory lightens a status colour that would fail there,
26
- the dark counterpart of the darkening `UCM-THEME-15` does on light pages.
27
-
28
- ## Token table
29
-
30
- Every value below is either **distilled** (found in the estate, source named), **derived** (computed by a
31
- stated rule from a decided value) or **invented** (no precedent anywhere; labelled, unvalidated).
32
-
33
- ### Surfaces and ink
34
-
35
- | Token | Light (today) | Dark | Origin |
36
- |---|---|---|---|
37
- | `background.default` (page) | `#FAFAFA` | `#14181B` | distilled: cockpit OBS-10, cockpit KB-23 (`#14181b`), ucm SHELL-2 (`#14171c`) |
38
- | `background.paper` (card) | `#FFFFFF` | `#1B2126` | distilled: cockpit OBS-10, KB-23 |
39
- | `background.subtle` | `#F4F5F6` | `#20262C` | distilled: cockpit OBS-10, KB-23 (`surface2`) |
40
- | overlay surface (dialog, menu, popover, drawer) | = paper, plus shadow | `#232A31` | derived: one step above paper on the same hue, ratio 1.12:1 to paper; new in dark |
41
- | `divider` | `rgba(33,37,41,.10)` | `#2B333A` | distilled: cockpit OBS-10, KB-23 (`border`) |
42
- | `ink.primary` / `text.primary` | `#212529` | `#E6EBEF` | distilled: cockpit KB-23 (`ink`); OBS-10 `#e7ebee` |
43
- | `ink.secondary` / `text.secondary` | `#5B6670` | `#AAB5BF` | distilled: cockpit KB-23 (`ink2`) |
44
- | `ink.muted` / `text.disabled` | `#6A7178` | `#87919A` | distilled: cockpit OBS-10 (`text3`) |
45
-
46
- ### Controls
47
-
48
- | Token | Light (today) | Dark | Origin |
49
- |---|---|---|---|
50
- | `controlBorder.main` | `rgba(33,37,41,.50)` | `rgba(230,235,239,.38)` | derived: `ink.primary` at the smallest alpha **in 0.02 steps from 0.30** that clears 3:1 on page and paper (wording made precise 2026-10-02; a finer step would give 0.37, the frozen value is 0.38) |
51
- | `controlBorder.hover` | `rgba(33,37,41,.65)` | `rgba(230,235,239,.53)` | derived: the light step (+0.15) carried over |
52
- | `controlBorder.error` | `#BF3227` | `#E58B80` | = `error.main` (dark) |
53
- | `controlBorder.focus` | derived from primary | derived from primary | rule: lighten the primary until it clears 3:1 on page and paper |
54
- | autofill field | paper inset, `ink.primary` text and caret | paper inset, `ink.primary` text and caret | derived: the palette-aware block in `createAppTheme.js:97-108` already resolves from the palette and wins over the static light block in `tokens.js`; dark follows once the dark palette is in place |
55
-
56
- ### Status
57
-
58
- `main` = `text` in dark. `light`/`dark`/`contrastText` follow from `main` through `withMainShades`, with
59
- `contrastText` = `#14181B`. A solid fill takes the status colour with the page colour as its text (dark
60
- text on a light fill, the same rule as the primary button).
61
-
62
- | Status | `text` = `main` | `bg` | `fill` | `fillText` | Origin |
63
- |---|---|---|---|---|---|
64
- | success | `#7BC496` | `#1C2A22` | `#7BC496` | `#14181B` | distilled: cockpit KB-23 (`ok`, `ok-bg`) |
65
- | warning | `#D8BD7E` | `#2A2418` | `#D8BD7E` | `#14181B` | distilled: cockpit KB-23 (`warn`, `warn-bg`) |
66
- | error | `#E58B80` | `#3A2320` | `#E58B80` | `#14181B` | distilled: cockpit KB-23 (`err`), OBS-10 (`danger-bg`) |
67
- | info | `#8FB8E0` | `#1A2633` | `#8FB8E0` | `#14181B` | **invented**: no dark info hue anywhere in the estate |
68
- | stale | `#AAB5BF` | `#20262C` | `#AAB5BF` | `#14181B` | **invented**, like the light `stale` it mirrors: `ink.secondary` on `subtle` |
69
-
70
- ### Data series and shadows
71
-
72
- | Token | Light (today) | Dark | Origin |
73
- |---|---|---|---|
74
- | `dataSeries.categorical` | `#3D5A99 #3E80B8 #2E8F8A #7A5FA8 #9C4F86 #8A7355` | `#506BA3 #3E80B8 #2E8F8A #7A5FA8 #A1588C #8A7355` | derived: each light colour lightened in 0.05 steps until it clears 3:1 on paper; only slots 1 and 5 move |
75
- | `shadow.rest` | `none` | `none` | structural decision 2 |
76
- | `shadow.overlay` | `0 8px 24px rgba(20,26,31,.16), 0 2px 8px rgba(20,26,31,.08)` | `0 8px 28px rgba(0,0,0,.45)` | invented: on near-black the light shadow is invisible; the surface step carries the separation |
77
- | scrim (behind a modal) | MUI default | `rgba(0,0,0,.55)` | invented |
78
-
79
- ### App identity in dark (rules, not values)
80
-
81
- | What | Rule | Worked examples (computed) |
82
- |---|---|---|
83
- | primary, app sets none | lighten the light primary in 0.05 steps until it clears 4.5:1 on `#14181B` | `#468AB2` stays `#468AB2` (4.71:1); hram `#2F4F96` -> `#6D84B6` (4.78:1); survey_app `#432CA1` -> `#8576C2` (4.56:1) |
84
- | primary button text | whichever of `#FFFFFF` and `#14181B` contrasts more with the derived primary | `#14181B` in all three examples |
85
- | primary, app sets its own | the app's value | webshop-guenter `#D49040` with `#0E0B08` text (7.35:1) |
86
- | status on an app page that is not `#14181B` | lighten the status colour until it clears 4.5:1 on its `bg` and on the page | webshop-guenter `#0E0B08`: no status colour needs to move |
87
-
88
- ## Contrast table (computed 2026-10-02 with the instrument's own functions)
89
-
90
- | Pair | Foreground | Background | Ratio | Threshold |
91
- |---|---|---|---|---|
92
- | ink.primary on page / paper / overlay | `#E6EBEF` | `#14181B` / `#1B2126` / `#232A31` | 14.87 / 13.54 / 12.09 | 4.5 |
93
- | ink.secondary on page / paper / overlay | `#AAB5BF` | same | 8.56 / 7.79 / 6.96 | 4.5 |
94
- | ink.muted on page / paper | `#87919A` | `#14181B` / `#1B2126` | 5.57 / 5.07 | 3 |
95
- | success text on bg; main on page / paper; fillText on fill | `#7BC496` | | 7.25; 8.65 / 7.88; 8.65 | 4.5 |
96
- | warning, same pairs | `#D8BD7E` | | 8.43; 9.78 / 8.90; 9.78 | 4.5 |
97
- | error, same pairs | `#E58B80` | | 5.78; 7.09 / 6.45; 7.09 | 4.5 |
98
- | info, same pairs | `#8FB8E0` | | 7.38; 8.59 / 7.82; 8.59 | 4.5 |
99
- | stale text on bg; fillText on fill | `#AAB5BF` | `#20262C` | 7.32; 8.56 | 4.5 |
100
- | controlBorder.main on page / paper | `rgba(230,235,239,.38)` | | 3.18 / 3.14 | 3 |
101
- | controlBorder.hover on page / paper | `rgba(230,235,239,.53)` | | 4.98 / 4.79 | 3 |
102
- | controlBorder.error on paper | `#E58B80` | `#1B2126` | 6.45 | 3 |
103
- | dataSeries 1-6 on paper | as above | `#1B2126` | 3.07 / 3.86 / 4.19 / 3.12 / 3.32 / 3.61 | 3 |
104
- | surface steps: paper/page, overlay/paper, divider/paper, subtle/paper | | | 1.10 / 1.12 / 1.27 / 1.06 | none (structure, not text) |
105
-
106
- Every pair passes. Two pairs sit close to their threshold: `controlBorder.main` (3.14:1 on paper) and
107
- `dataSeries` slot 1 (3.07:1). A paper colour that moves lighter breaks them first.
108
-
109
- ## Rejected, and why
110
-
111
- - **Neutral base B** (`#121212` / `#1C1C1C`, no cast, near MUI) and **C** (`#16181B` / `#1D2024`, the light
112
- baseline's own cool ink carried into dark). Round 1: at this lightness none of the three could be told
113
- apart, so the cast was decided on principle: A is the one the estate already uses in three places.
114
- - **Surface step 2** (paper `#1F262C`, overlay `#29313A`) and **step 3** (paper `#232B32`, overlay
115
- `#2F3942`). Round 2: step 1, the estate's own, reads as card and dialog already; the larger steps lower
116
- text and border contrast on cards for no gain the operator could see.
117
-
118
- ## Identity versus baseline
119
-
120
- The baseline owns everything above. An app owns, in dark as in light, only its primary (optional in dark,
121
- derived otherwise) and its font. An app with a decided brand (webshop-guenter) may own its dark surfaces and
122
- ink too; that is the app's identity bucket, not a baseline change.
123
-
124
- ## Unverified
125
-
126
- Declared, not demonstrated, because a static instrument cannot show them: hover and pressed states, focus
127
- rings in a running app, native date and select pickers, scrollbars, transitions, and how browsers paint
128
- autofill in dark. `UCM-THEME-14`'s rendered check is where they are first seen.
129
-
130
- ## Known code that assumes light (input for `UCM-THEME-14`)
131
-
132
- - `src/theme/createAppTheme.js:221` and `src/theme/themeCompleteness.js:298`: contrast surfaces include a
133
- fixed `#FFFFFF`; the derivations only darken.
134
- - `src/theme/tokens.js` `autofill`: fixed `#FFFFFF` inset and `#212529` text. **Shadowed** by the
135
- palette-aware block in `src/theme/createAppTheme.js:97-108` (corrected 2026-10-02: the first version of
136
- this sheet listed it as live). Harmless in both modes, but a duplicate that can mislead.
137
- - `BASELINE_INTENTIONAL_DEFAULT_EXEMPTIONS`: the `background.paper` reason ("deliberately MUI white") is
138
- false in dark.
139
- - `src/components/QrSignupManager.jsx:361`: `#ffffff` behind a QR code. **Stays white on purpose**: a QR code
140
- needs a light quiet zone to scan.
@@ -1,111 +0,0 @@
1
- # Baseline type scale
2
-
3
- Reference sheet for `UCM-THEME-17`. **This file is the value source** for the baseline typography:
4
- `UCM-THEME-18` takes every value from here by variant name, and if code and this sheet ever disagree, this
5
- sheet is the one to fix first.
6
-
7
- Instrument: [`work-orders/assets/UCM-THEME-17-type-scale.html`](../work-orders/assets/UCM-THEME-17-type-scale.html)
8
- (the candidates beside today's baseline and the MUI default on four estate screens at 375 px and 1280 px; sizes,
9
- ratios and frame heights are measured in the page).
10
-
11
- ## Frozen
12
-
13
- **Frozen 2026-10-04 by the operator** ("Ja, einfrieren"), after two value rounds.
14
-
15
- ## Why the scale changed
16
-
17
- The previous baseline stepped its headings by 1 to 3 px (h4 20, h5 18, h6 16, subtitle1 15), so hierarchy rested
18
- on weight alone, and where apps raised every level to 700 or 800 the levels merged (survey_app sites, operator
19
- screenshots 2026-10-04). The approved prototypes in the estate had used the larger title step all along: screen
20
- title about 1.7x body at 1280 px, stepped down on mobile, section labels below body size.
21
-
22
- ## Structural decisions (operator, 2026-10-04)
23
-
24
- 1. **One scale** for every app and page type; no separate reading register. Body stays at the tool density
25
- (`jg-ferien` `JG-DL-7` had rejected a larger body).
26
- 2. **The screen title (h4) steps down on narrow screens.** No other variant does.
27
- 3. **Size carries the hierarchy, weight is fixed**: every heading 600. App overrides are for font family, not
28
- for per-level weight.
29
- 4. **The section label (subtitle1) sits below body size**, set apart by weight.
30
-
31
- Role mapping, unchanged from the `THEME-1` sheet: h4 screen title, h5 panel or dialog, h6 card title,
32
- subtitle1 section label.
33
-
34
- ## Token table
35
-
36
- | Variant | Before | Frozen | Weight | Line height | Origin |
37
- |---|---|---|---|---|---|
38
- | h1 | 32 | **36** | 600 | 1.2 | derived: 4 px steps above h3 |
39
- | h2 | 28 | **32** | 600 | 1.22 | derived: 4 px steps above h3 |
40
- | h3 | 24 | **28** | 600 | 1.25 | derived: 4 px above h4 |
41
- | h4, screen title | 20 | **24** | 600 | 1.3 | distilled: prototype median (KZ-UI-1, UX-10, JG-MEM-1) |
42
- | h4 below `sm` (600 px) | 20 | **23** | 600 | 1.3 | decided round 2: the smallest size that keeps h4 : h5 at 1.12 or more on mobile. The breakpoint is the baseline's own `sm`, derived, not judged |
43
- | h5, panel | 18 | **20** | 600 | 1.3 | distilled: prototypes 18 to 20 |
44
- | h6, card title | 16 | 16 | 600 | 1.35 | unchanged |
45
- | subtitle1, section label | 15/600 | **13** | 600 | 1.4 | distilled: prototypes 12 to 13 at 600 to 700 |
46
- | subtitle2 | 13/500 | 13 | 500 | 1.4 | unchanged |
47
- | body1 | 14 | 14 | 400 | 1.55 | unchanged (decision 1) |
48
- | body2 | 13 | 13 | 400 | 1.55 | unchanged |
49
- | button | 14/500 | 14 | 500 | 1.4 | unchanged |
50
- | caption | 12 | 12 | 400 | 1.4 | unchanged |
51
- | overline | 11/600 | 11 | 600 | 1.4 | unchanged |
52
-
53
- Sizes in px. `letterSpacing` 0 throughout, overline 0.4px uppercase as before.
54
-
55
- ## Measurements (computed in the instrument, 2026-10-04)
56
-
57
- | Ratio | Before | Frozen | MUI |
58
- |---|---|---|---|
59
- | h4 : h5 at 1280 | 1.11 | 1.20 | 1.42 |
60
- | h4 : h5 at 375 | 1.11 | 1.15 | 1.42 |
61
- | h5 : h6 | 1.13 | 1.25 | 1.20 |
62
- | h6 : body1 | 1.14 | 1.14 | 1.25 |
63
- | h4 : body1 at 1280 / 375 | 1.43 / 1.43 | 1.71 / 1.64 | 2.13 / 2.13 |
64
- | subtitle1 : body1 | 1.07 | 0.93 | 1.00 |
65
-
66
- The instrument works with a threshold of 1.12 between two heading levels at the same weight. That number is this
67
- sheet's working assumption, not a standard.
68
-
69
- Frame height against the previous baseline: hram Research -1.1 % / -1.0 % (375 / 1280), jg-ferien dashboard
70
- +0.5 % / +0.5 %, kerzenziehen admin -1.4 % / -1.3 %, survey site +0.7 % / +0.8 %. The smaller section label offsets
71
- the larger titles; the vertical-space risk named in the work order does not occur. MUI by comparison: +4 to +18 %.
72
-
73
- ## Rejected, and why
74
-
75
- - **Candidate A** (h4 22, h5 18, h6 16, subtitle1 13): leaves h5 : h6 at 1.13, the panel and card stay as
76
- close as before.
77
- - **Candidate C** (h4 28, h5 22, h6 17, subtitle1 12): the counter-test; closest to MUI, largest height cost
78
- (up to +2.3 %).
79
- - **Mobile title at desktop minus 2** (round 1, B at 22): h4 : h5 on mobile falls to 1.10, as close as before.
80
- - **h1 to h3 kept at 32 / 28 / 24**: h3 would equal the new h4 and drop out as a level.
81
- - **The MUI scale**: body 16 is the size `jg-ferien` already rejected for tool screens.
82
-
83
- ## Known consequences outside this repo
84
-
85
- These are inputs for the per-app follow-up after the release, not part of the baseline:
86
-
87
- - **Screens that use another variant as their screen title** do not get the new title value or the mobile step.
88
- Measured 2026-10-04 (first heading variant in each `*Page.jsx`, a heuristic): hram 5 pages h5, jg-ferien 3 pages
89
- h5 (`DashboardPage.jsx:187`), kerzenziehen admin h5 (rendered measurement), spesix 1 page h5, fitness-monitor
90
- 1 page h5; survey_app uses h3 on 7 pages, which grows from 24 to 28.
91
- - **Per-level weight overrides** contradict decision 3: survey_app (h1 to h6 800/700, site builder 800),
92
- hram (500), spesix (weights). survey_app's held typography revert (`SVA-THEME-1`) is decided by this sheet.
93
- - **subtitle1 also serves as a title, not only as a section label** (added 2026-10-04, after the freeze; the
94
- inventory missed it, because it read subtitle1 only in its section-label role on hram Research). Uses in
95
- `src/`: hram 72, jg-ferien 39, ui-core-micha itself 12 (`UserMenu` user name, `SectionNav` active item,
96
- announcement titles), innoservice 9, survey_app 7, reimbursements 6, spesix 5. As item or panel titles they
97
- fall below body size. **Decided at the jg-ferien pilot `JG-DEPS-4`, 2026-10-04: subtitle1 stays at 13 px.**
98
- The operator looked at jg-ferien on the 3.12.0 build, including `FinanceSummaryPanel` with subtitle1 as a
99
- panel section title, and accepted it without any remapping. Rollout orders therefore do not remap
100
- subtitle1 by default; a remap happens only where an app's own look rejects a specific screen.
101
- - **Exception to decision 3: spesix keeps its own heading weights** (operator, 2026-10-04, at `SPX-DEP-3`). Its
102
- `DESIGN` block (h1 800, h2-h5 700, h6 600, button 700) belongs to a decided design (`SPX-THEME-1`, identity
103
- bucket), and the new sizes separate the levels on their own. survey_app's 800/700 weights are not covered by
104
- this exception: there the operator found the bold levels themselves to be the problem.
105
- - webshop-guenter keeps its handoff scale (decided brand, identity bucket). Kira, Cinevia and Gustav use no MUI
106
- heading variants and are unaffected.
107
-
108
- ## Unverified
109
-
110
- Declared, not shown by the static instrument: long titles wrapping in French and German at 375 px, dialogs,
111
- the h1 to h3 sizes on real public pages, and the step at exactly 600 px.