@micha.bigler/ui-core-micha 3.14.4 → 4.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (341) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +7 -0
  3. package/dist/chat/kitSxRegistry.js +9 -0
  4. package/dist/components/charts/kitSxRegistry.js +6 -0
  5. package/dist/entries/charts.js +6 -0
  6. package/dist/entries/chat.js +8 -0
  7. package/dist/entries/markdown.js +1 -0
  8. package/dist/index.js +0 -14
  9. package/dist/pages/AccountPage.js +1 -2
  10. package/dist/pages/LoginPage.js +1 -2
  11. package/dist/pages/PasswordChangePage.js +1 -2
  12. package/dist/pages/PasswordInvitePage.js +1 -2
  13. package/dist/pages/PasswordResetRequestPage.js +1 -2
  14. package/dist/pages/SignUpPage.js +1 -2
  15. package/dist/pages/SignupConfirmPage.js +1 -2
  16. package/dist/theme/kitSxEntry.js +11 -0
  17. package/dist/theme/kitSxRegistry.js +2 -15
  18. package/package.json +31 -7
  19. package/.github/workflows/ci.yml +0 -35
  20. package/.github/workflows/publish.yml +0 -126
  21. package/DESIGN.md +0 -60
  22. package/WORK_ORDERS.md +0 -150
  23. package/dev/entries.jsx +0 -721
  24. package/dev/fixtures.js +0 -26
  25. package/dev/main.jsx +0 -75
  26. package/dev/mockTransport.jsx +0 -128
  27. package/docs/CHART-LAYOUT.md +0 -157
  28. package/docs/DARK-TOKENS.md +0 -140
  29. package/docs/TYPE-SCALE.md +0 -111
  30. package/docs/messaging-deviations.md +0 -118
  31. package/index.html +0 -12
  32. package/renovate.json +0 -10
  33. package/scripts/chart-api-census.mjs +0 -415
  34. package/src/auth/AuthContext.jsx +0 -158
  35. package/src/auth/ReauthModal.jsx +0 -82
  36. package/src/auth/UserMenu.jsx +0 -121
  37. package/src/auth/apiClient.jsx +0 -232
  38. package/src/auth/authApi.jsx +0 -539
  39. package/src/auth/authConfig.jsx +0 -34
  40. package/src/auth/reauth.js +0 -47
  41. package/src/chat/ChatComposer.jsx +0 -36
  42. package/src/chat/ChatList.jsx +0 -87
  43. package/src/chat/ChatThread.jsx +0 -128
  44. package/src/chat/ChatWorkspace.jsx +0 -244
  45. package/src/chat/MarkdownReply.jsx +0 -22
  46. package/src/chat/ModelPicker.jsx +0 -85
  47. package/src/chat/api.js +0 -18
  48. package/src/chat/useChatSocket.js +0 -32
  49. package/src/chat/wire.js +0 -7
  50. package/src/components/AccessCodeManager.jsx +0 -288
  51. package/src/components/AccessCodeSingleUseToggle.jsx +0 -92
  52. package/src/components/AllowedEmailDomainsManager.jsx +0 -92
  53. package/src/components/AuthFactorRequirementCard.jsx +0 -95
  54. package/src/components/BulkInviteCsvTab.jsx +0 -198
  55. package/src/components/EmptyState.jsx +0 -38
  56. package/src/components/LoginForm.jsx +0 -159
  57. package/src/components/MFAComponent.jsx +0 -350
  58. package/src/components/MfaLoginComponent.jsx +0 -252
  59. package/src/components/MobileBottomNav.jsx +0 -82
  60. package/src/components/PasskeysComponent.jsx +0 -252
  61. package/src/components/PasswordChangeForm.jsx +0 -87
  62. package/src/components/PasswordResetRequestForm.jsx +0 -43
  63. package/src/components/PasswordSetForm.jsx +0 -97
  64. package/src/components/ProfileComponent.jsx +0 -348
  65. package/src/components/QrSignupManager.jsx +0 -404
  66. package/src/components/QrSignupValidityManager.jsx +0 -106
  67. package/src/components/RegistrationMethodsManager.jsx +0 -150
  68. package/src/components/SecurityComponent.jsx +0 -141
  69. package/src/components/SocialLoginButtons.jsx +0 -83
  70. package/src/components/SoftChip.jsx +0 -93
  71. package/src/components/StatTile.jsx +0 -39
  72. package/src/components/SupportRecoveryRequestsTab.jsx +0 -482
  73. package/src/components/TurnstileWidget.jsx +0 -99
  74. package/src/components/UserInviteComponent.jsx +0 -76
  75. package/src/components/UserListComponent.jsx +0 -618
  76. package/src/components/charts/BarChart.jsx +0 -102
  77. package/src/components/charts/ChartFrame.jsx +0 -214
  78. package/src/components/charts/LineChart.jsx +0 -126
  79. package/src/components/charts/ScatterChart.jsx +0 -299
  80. package/src/components/charts/TimeSeriesChart.jsx +0 -300
  81. package/src/components/charts/chartDefaults.js +0 -644
  82. package/src/components/charts/chartLabels.js +0 -36
  83. package/src/components/charts/exportChart.js +0 -258
  84. package/src/components/charts/formatters.js +0 -39
  85. package/src/components/charts/palette.js +0 -33
  86. package/src/components/charts/yearTickInterval.js +0 -7
  87. package/src/i18n/authTranslations.ts +0 -2651
  88. package/src/i18n/chartsTranslations.ts +0 -91
  89. package/src/i18n/chatTranslations.ts +0 -55
  90. package/src/i18n/messagingTranslations.ts +0 -120
  91. package/src/i18n/notificationsTranslations.ts +0 -32
  92. package/src/i18n/onboardingTranslations.ts +0 -29
  93. package/src/i18n/sectionNavTranslations.ts +0 -20
  94. package/src/i18n/uiCoreTranslations.js +0 -29
  95. package/src/i18n/userMenuTranslations.ts +0 -5
  96. package/src/index.js +0 -158
  97. package/src/layout/PageLayout.jsx +0 -36
  98. package/src/layout/SectionNav.jsx +0 -320
  99. package/src/messaging/AttachmentList.jsx +0 -145
  100. package/src/messaging/Composer.jsx +0 -197
  101. package/src/messaging/ConversationLaunchers.jsx +0 -51
  102. package/src/messaging/ConversationList.jsx +0 -109
  103. package/src/messaging/DirectMessageLauncher.jsx +0 -112
  104. package/src/messaging/MessageBubble.jsx +0 -183
  105. package/src/messaging/MessagingProvider.jsx +0 -0
  106. package/src/messaging/MessagingScopeConfig.jsx +0 -24
  107. package/src/messaging/PollCard.jsx +0 -163
  108. package/src/messaging/ReactionBar.jsx +0 -43
  109. package/src/messaging/ReadTicks.jsx +0 -82
  110. package/src/messaging/Thread.jsx +0 -195
  111. package/src/messaging/api.js +0 -48
  112. package/src/messaging/composerImageCompression.js +0 -35
  113. package/src/messaging/linkifyText.jsx +0 -37
  114. package/src/notifications/NotificationBell.jsx +0 -84
  115. package/src/notifications/NotificationSettings.jsx +0 -330
  116. package/src/notifications/NotificationsProvider.jsx +0 -224
  117. package/src/notifications/PopupSurface.jsx +0 -105
  118. package/src/notifications/api.js +0 -55
  119. package/src/notifications/feedApi.js +0 -18
  120. package/src/notifications/realtime.jsx +0 -138
  121. package/src/notifications/serviceWorker/sw.js +0 -50
  122. package/src/onboarding/OnboardingProvider.jsx +0 -326
  123. package/src/onboarding/OnboardingWizard.jsx +0 -61
  124. package/src/onboarding/WizardDialogShell.jsx +0 -51
  125. package/src/onboarding/api.js +0 -13
  126. package/src/onboarding/stepSelection.js +0 -16
  127. package/src/onboarding/steps/BrowserPushStep.jsx +0 -121
  128. package/src/onboarding/steps/CompleteNameStep.jsx +0 -46
  129. package/src/onboarding/steps/CookieConsentStep.jsx +0 -133
  130. package/src/onboarding/steps/PwaInstallStep.jsx +0 -47
  131. package/src/pages/AccountPage.jsx +0 -394
  132. package/src/pages/LoginPage.jsx +0 -343
  133. package/src/pages/PasswordChangePage.jsx +0 -62
  134. package/src/pages/PasswordInvitePage.jsx +0 -177
  135. package/src/pages/PasswordResetRequestPage.jsx +0 -68
  136. package/src/pages/SignUpPage.jsx +0 -268
  137. package/src/pages/SignupConfirmPage.jsx +0 -155
  138. package/src/testing/checkKitIntegration.js +0 -233
  139. package/src/theme/createAppTheme.js +0 -423
  140. package/src/theme/fonts.js +0 -3
  141. package/src/theme/index.js +0 -21
  142. package/src/theme/kitSxRegistry.js +0 -285
  143. package/src/theme/themeCompleteness.js +0 -1045
  144. package/src/theme/tokens.js +0 -478
  145. package/src/utils/auth-errors.js +0 -76
  146. package/src/utils/authService.js +0 -223
  147. package/src/utils/resolveErrorText.js +0 -28
  148. package/src/utils/webauthn.js +0 -51
  149. package/tests/AccountPage.profileStatements.test.jsx +0 -162
  150. package/tests/AccountPage.test.jsx +0 -200
  151. package/tests/AuthContext.test.jsx +0 -129
  152. package/tests/BarChart.test.jsx +0 -112
  153. package/tests/BrowserPushStep.test.jsx +0 -110
  154. package/tests/ChartFrame.test.jsx +0 -336
  155. package/tests/ConversationList.test.jsx +0 -144
  156. package/tests/CookieConsentStep.test.jsx +0 -69
  157. package/tests/EmptyState.test.jsx +0 -62
  158. package/tests/LineChart.test.jsx +0 -80
  159. package/tests/MessagingProvider.test.jsx +0 -50
  160. package/tests/MobileBottomNav.test.jsx +0 -319
  161. package/tests/NotificationBell.test.jsx +0 -60
  162. package/tests/NotificationSettings.test.jsx +0 -276
  163. package/tests/NotificationsProvider.test.jsx +0 -240
  164. package/tests/OnboardingProvider.test.jsx +0 -294
  165. package/tests/OnboardingWizard.test.jsx +0 -143
  166. package/tests/PasswordChangeForm.test.jsx +0 -70
  167. package/tests/PasswordInvitePage.test.jsx +0 -142
  168. package/tests/PasswordSetForm.test.jsx +0 -70
  169. package/tests/PopupSurface.test.jsx +0 -142
  170. package/tests/ProfileComponent.refresh.test.jsx +0 -194
  171. package/tests/QrSignupManager.registrationContext.test.jsx +0 -106
  172. package/tests/QrSignupManagerInfoBox.test.jsx +0 -49
  173. package/tests/ScatterChart.test.jsx +0 -270
  174. package/tests/SectionNav.test.jsx +0 -363
  175. package/tests/SignUpPage.test.jsx +0 -208
  176. package/tests/SoftChip.test.jsx +0 -105
  177. package/tests/StatTile.test.jsx +0 -57
  178. package/tests/Thread.test.jsx +0 -151
  179. package/tests/TimeSeriesChart.test.jsx +0 -367
  180. package/tests/UserListComponent.test.jsx +0 -192
  181. package/tests/UserMenu.test.jsx +0 -171
  182. package/tests/apiClientLanguage.test.js +0 -33
  183. package/tests/apiClientPublicPaths.test.js +0 -178
  184. package/tests/authApiLogin.test.js +0 -42
  185. package/tests/authApiRegistration.test.js +0 -31
  186. package/tests/authErrorCodeRendering.test.jsx +0 -77
  187. package/tests/authErrors.test.js +0 -74
  188. package/tests/authTranslations.test.js +0 -74
  189. package/tests/chart-api-census.test.js +0 -366
  190. package/tests/chartAxisTickBand.test.jsx +0 -95
  191. package/tests/chartDefaults.test.js +0 -902
  192. package/tests/chartLabels.test.js +0 -50
  193. package/tests/chartsPalette.test.js +0 -99
  194. package/tests/chatApi.test.js +0 -48
  195. package/tests/chatDriverNames.test.jsx +0 -71
  196. package/tests/chatExports.test.js +0 -3
  197. package/tests/chatList.test.jsx +0 -84
  198. package/tests/chatPicker.test.jsx +0 -82
  199. package/tests/chatSocket.test.jsx +0 -26
  200. package/tests/chatThread.test.jsx +0 -116
  201. package/tests/chatTranslations.test.js +0 -47
  202. package/tests/chatWorkspace.test.jsx +0 -234
  203. package/tests/checkKitIntegration.test.jsx +0 -219
  204. package/tests/createAppTheme.test.js +0 -132
  205. package/tests/createAppThemeDark.test.js +0 -127
  206. package/tests/createAppThemeLightGuard.test.js +0 -40
  207. package/tests/createAppThemePageContrast.test.js +0 -108
  208. package/tests/createAppThemeTypography.test.js +0 -53
  209. package/tests/errorCallSiteGuard.test.js +0 -32
  210. package/tests/exportChart.test.js +0 -313
  211. package/tests/feedApi.test.js +0 -46
  212. package/tests/fixtures/light-theme-baseline.json +0 -2234
  213. package/tests/i18nAggregate.test.js +0 -120
  214. package/tests/i18nGermanVariant.test.js +0 -65
  215. package/tests/loginPageErrorResolution.test.jsx +0 -102
  216. package/tests/messagingApi.test.js +0 -32
  217. package/tests/messagingChunk6.test.jsx +0 -116
  218. package/tests/messagingCompactBubble.test.jsx +0 -64
  219. package/tests/messagingComposer.test.jsx +0 -136
  220. package/tests/messagingComposerChunk5.test.jsx +0 -113
  221. package/tests/messagingContractConformance.test.js +0 -69
  222. package/tests/messagingDirectMessageLauncher.test.jsx +0 -151
  223. package/tests/messagingExports.test.js +0 -21
  224. package/tests/messagingInteractions.test.jsx +0 -136
  225. package/tests/messagingLastMessagePreview.test.jsx +0 -93
  226. package/tests/messagingLinkify.test.jsx +0 -51
  227. package/tests/messagingMessageActions.test.jsx +0 -163
  228. package/tests/messagingMsg6.test.jsx +0 -105
  229. package/tests/messagingMsg6f.test.jsx +0 -155
  230. package/tests/messagingMsg6g.test.jsx +0 -166
  231. package/tests/messagingMsg6h.test.jsx +0 -136
  232. package/tests/messagingMsg6i.test.jsx +0 -129
  233. package/tests/messagingPollRendering.test.jsx +0 -142
  234. package/tests/messagingReactionMerge.test.jsx +0 -77
  235. package/tests/messagingReadTicksTooltip.test.jsx +0 -36
  236. package/tests/messagingThreadReceipts.test.jsx +0 -178
  237. package/tests/messagingThreadingQuoting.test.jsx +0 -87
  238. package/tests/messagingUnreadLifecycle.test.jsx +0 -78
  239. package/tests/notificationsApi.test.js +0 -41
  240. package/tests/notificationsExports.test.js +0 -34
  241. package/tests/onboardingDescriptors.test.js +0 -132
  242. package/tests/packageTreeShaking.test.js +0 -126
  243. package/tests/realtime.test.jsx +0 -219
  244. package/tests/resolveErrorText.test.js +0 -72
  245. package/tests/stepSelection.test.js +0 -53
  246. package/tests/themeCompleteness.test.js +0 -626
  247. package/tests/timeSeriesLegend.test.js +0 -99
  248. package/tests/userScreenTranslations.test.jsx +0 -218
  249. package/tsconfig.build.json +0 -18
  250. package/vite.config.mjs +0 -6
  251. package/work-orders/AUTH-1.md +0 -74
  252. package/work-orders/AUTH-2.md +0 -74
  253. package/work-orders/AUTH-4.md +0 -183
  254. package/work-orders/AUTH-5.md +0 -154
  255. package/work-orders/AUTH-7.md +0 -181
  256. package/work-orders/AUTH-8.md +0 -184
  257. package/work-orders/CHART-1.md +0 -285
  258. package/work-orders/CHART-2.md +0 -222
  259. package/work-orders/CHART-3.md +0 -163
  260. package/work-orders/CHART-4.md +0 -203
  261. package/work-orders/CHART-5.md +0 -226
  262. package/work-orders/CHART-6.md +0 -428
  263. package/work-orders/CHART-7.md +0 -175
  264. package/work-orders/DX-1.md +0 -169
  265. package/work-orders/DX-2.md +0 -188
  266. package/work-orders/DX-3.md +0 -195
  267. package/work-orders/I18N-1.md +0 -254
  268. package/work-orders/I18N-3.md +0 -207
  269. package/work-orders/MSG-3.md +0 -375
  270. package/work-orders/MSG-3b.md +0 -1002
  271. package/work-orders/MSG-3c.md +0 -378
  272. package/work-orders/MSG-3d.md +0 -268
  273. package/work-orders/MSG-3e.md +0 -164
  274. package/work-orders/MSG-3f.md +0 -391
  275. package/work-orders/MSG-6e.md +0 -308
  276. package/work-orders/MSG-6f.md +0 -214
  277. package/work-orders/MSG-6g.md +0 -244
  278. package/work-orders/MSG-6h.md +0 -238
  279. package/work-orders/MSG-6i.md +0 -271
  280. package/work-orders/MSG-6j.md +0 -117
  281. package/work-orders/NOTIF-12.md +0 -214
  282. package/work-orders/NOTIF-13.md +0 -142
  283. package/work-orders/NOTIF-14.md +0 -151
  284. package/work-orders/PRIM-1.md +0 -177
  285. package/work-orders/SHELL-1.md +0 -255
  286. package/work-orders/SHELL-2.md +0 -197
  287. package/work-orders/SHELL-3.md +0 -359
  288. package/work-orders/SHELL-4.md +0 -351
  289. package/work-orders/SHELL-5.md +0 -345
  290. package/work-orders/SHELL-6.md +0 -210
  291. package/work-orders/THEME-1.md +0 -587
  292. package/work-orders/THEME-10.md +0 -303
  293. package/work-orders/THEME-11.md +0 -208
  294. package/work-orders/THEME-2.md +0 -339
  295. package/work-orders/THEME-3.md +0 -210
  296. package/work-orders/THEME-4.md +0 -271
  297. package/work-orders/THEME-5.md +0 -341
  298. package/work-orders/THEME-6.md +0 -247
  299. package/work-orders/THEME-7.md +0 -82
  300. package/work-orders/THEME-8.md +0 -211
  301. package/work-orders/THEME-9.md +0 -194
  302. package/work-orders/UCM-AUTH-10.md +0 -490
  303. package/work-orders/UCM-AUTH-11.md +0 -185
  304. package/work-orders/UCM-AUTH-12.md +0 -98
  305. package/work-orders/UCM-AUTH-13.md +0 -47
  306. package/work-orders/UCM-AUTH-9.md +0 -207
  307. package/work-orders/UCM-CHART-10.md +0 -150
  308. package/work-orders/UCM-CHART-11.md +0 -132
  309. package/work-orders/UCM-CHART-12.md +0 -348
  310. package/work-orders/UCM-CHART-13.md +0 -122
  311. package/work-orders/UCM-CHART-14.md +0 -198
  312. package/work-orders/UCM-CHART-15.md +0 -187
  313. package/work-orders/UCM-CHART-16.md +0 -169
  314. package/work-orders/UCM-CHART-17.md +0 -195
  315. package/work-orders/UCM-CHART-18.md +0 -200
  316. package/work-orders/UCM-CHART-8.md +0 -147
  317. package/work-orders/UCM-CHART-9.md +0 -265
  318. package/work-orders/UCM-CHAT-1.md +0 -190
  319. package/work-orders/UCM-CHAT-2.md +0 -75
  320. package/work-orders/UCM-CI-1.md +0 -65
  321. package/work-orders/UCM-DEP-1.md +0 -63
  322. package/work-orders/UCM-I18N-4.md +0 -226
  323. package/work-orders/UCM-I18N-5.md +0 -211
  324. package/work-orders/UCM-MSG-20.md +0 -192
  325. package/work-orders/UCM-PRIM-2.md +0 -114
  326. package/work-orders/UCM-SHELL-7.md +0 -134
  327. package/work-orders/UCM-SHELL-8.md +0 -125
  328. package/work-orders/UCM-TEST-1.md +0 -304
  329. package/work-orders/UCM-THEME-13.md +0 -69
  330. package/work-orders/UCM-THEME-14.md +0 -312
  331. package/work-orders/UCM-THEME-15.md +0 -356
  332. package/work-orders/UCM-THEME-16.md +0 -80
  333. package/work-orders/UCM-THEME-17.md +0 -111
  334. package/work-orders/UCM-THEME-18.md +0 -184
  335. package/work-orders/assets/AUTH-4-user-list-card.html +0 -407
  336. package/work-orders/assets/PRIM-1-dashboard-primitives.html +0 -624
  337. package/work-orders/assets/SHELL-2-account-navigation.html +0 -1159
  338. package/work-orders/assets/THEME-1-baseline-reference-sheet.html +0 -1352
  339. package/work-orders/assets/UCM-CHAT-1-ai-chat.html +0 -447
  340. package/work-orders/assets/UCM-THEME-13-dark-prototype.html +0 -390
  341. package/work-orders/assets/UCM-THEME-17-type-scale.html +0 -391
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.