@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.
- package/CHANGELOG.md +29 -0
- package/README.md +7 -0
- package/dist/chat/kitSxRegistry.js +9 -0
- package/dist/components/charts/kitSxRegistry.js +6 -0
- package/dist/entries/charts.js +6 -0
- package/dist/entries/chat.js +8 -0
- package/dist/entries/markdown.js +1 -0
- package/dist/index.js +0 -14
- package/dist/pages/AccountPage.js +1 -2
- package/dist/pages/LoginPage.js +1 -2
- package/dist/pages/PasswordChangePage.js +1 -2
- package/dist/pages/PasswordInvitePage.js +1 -2
- package/dist/pages/PasswordResetRequestPage.js +1 -2
- package/dist/pages/SignUpPage.js +1 -2
- package/dist/pages/SignupConfirmPage.js +1 -2
- package/dist/theme/kitSxEntry.js +11 -0
- package/dist/theme/kitSxRegistry.js +2 -15
- package/package.json +31 -7
- package/.github/workflows/ci.yml +0 -35
- package/.github/workflows/publish.yml +0 -126
- package/DESIGN.md +0 -60
- package/WORK_ORDERS.md +0 -150
- package/dev/entries.jsx +0 -721
- package/dev/fixtures.js +0 -26
- package/dev/main.jsx +0 -75
- package/dev/mockTransport.jsx +0 -128
- package/docs/CHART-LAYOUT.md +0 -157
- package/docs/DARK-TOKENS.md +0 -140
- package/docs/TYPE-SCALE.md +0 -111
- package/docs/messaging-deviations.md +0 -118
- package/index.html +0 -12
- package/renovate.json +0 -10
- package/scripts/chart-api-census.mjs +0 -415
- package/src/auth/AuthContext.jsx +0 -158
- package/src/auth/ReauthModal.jsx +0 -82
- package/src/auth/UserMenu.jsx +0 -121
- package/src/auth/apiClient.jsx +0 -232
- package/src/auth/authApi.jsx +0 -539
- package/src/auth/authConfig.jsx +0 -34
- package/src/auth/reauth.js +0 -47
- package/src/chat/ChatComposer.jsx +0 -36
- package/src/chat/ChatList.jsx +0 -87
- package/src/chat/ChatThread.jsx +0 -128
- package/src/chat/ChatWorkspace.jsx +0 -244
- package/src/chat/MarkdownReply.jsx +0 -22
- package/src/chat/ModelPicker.jsx +0 -85
- package/src/chat/api.js +0 -18
- package/src/chat/useChatSocket.js +0 -32
- package/src/chat/wire.js +0 -7
- package/src/components/AccessCodeManager.jsx +0 -288
- package/src/components/AccessCodeSingleUseToggle.jsx +0 -92
- package/src/components/AllowedEmailDomainsManager.jsx +0 -92
- package/src/components/AuthFactorRequirementCard.jsx +0 -95
- package/src/components/BulkInviteCsvTab.jsx +0 -198
- package/src/components/EmptyState.jsx +0 -38
- package/src/components/LoginForm.jsx +0 -159
- package/src/components/MFAComponent.jsx +0 -350
- package/src/components/MfaLoginComponent.jsx +0 -252
- package/src/components/MobileBottomNav.jsx +0 -82
- package/src/components/PasskeysComponent.jsx +0 -252
- package/src/components/PasswordChangeForm.jsx +0 -87
- package/src/components/PasswordResetRequestForm.jsx +0 -43
- package/src/components/PasswordSetForm.jsx +0 -97
- package/src/components/ProfileComponent.jsx +0 -348
- package/src/components/QrSignupManager.jsx +0 -404
- package/src/components/QrSignupValidityManager.jsx +0 -106
- package/src/components/RegistrationMethodsManager.jsx +0 -150
- package/src/components/SecurityComponent.jsx +0 -141
- package/src/components/SocialLoginButtons.jsx +0 -83
- package/src/components/SoftChip.jsx +0 -93
- package/src/components/StatTile.jsx +0 -39
- package/src/components/SupportRecoveryRequestsTab.jsx +0 -482
- package/src/components/TurnstileWidget.jsx +0 -99
- package/src/components/UserInviteComponent.jsx +0 -76
- package/src/components/UserListComponent.jsx +0 -618
- package/src/components/charts/BarChart.jsx +0 -102
- package/src/components/charts/ChartFrame.jsx +0 -214
- package/src/components/charts/LineChart.jsx +0 -126
- package/src/components/charts/ScatterChart.jsx +0 -299
- package/src/components/charts/TimeSeriesChart.jsx +0 -300
- package/src/components/charts/chartDefaults.js +0 -644
- package/src/components/charts/chartLabels.js +0 -36
- package/src/components/charts/exportChart.js +0 -258
- package/src/components/charts/formatters.js +0 -39
- package/src/components/charts/palette.js +0 -33
- package/src/components/charts/yearTickInterval.js +0 -7
- package/src/i18n/authTranslations.ts +0 -2651
- package/src/i18n/chartsTranslations.ts +0 -91
- package/src/i18n/chatTranslations.ts +0 -55
- package/src/i18n/messagingTranslations.ts +0 -120
- package/src/i18n/notificationsTranslations.ts +0 -32
- package/src/i18n/onboardingTranslations.ts +0 -29
- package/src/i18n/sectionNavTranslations.ts +0 -20
- package/src/i18n/uiCoreTranslations.js +0 -29
- package/src/i18n/userMenuTranslations.ts +0 -5
- package/src/index.js +0 -158
- package/src/layout/PageLayout.jsx +0 -36
- package/src/layout/SectionNav.jsx +0 -320
- package/src/messaging/AttachmentList.jsx +0 -145
- package/src/messaging/Composer.jsx +0 -197
- package/src/messaging/ConversationLaunchers.jsx +0 -51
- package/src/messaging/ConversationList.jsx +0 -109
- package/src/messaging/DirectMessageLauncher.jsx +0 -112
- package/src/messaging/MessageBubble.jsx +0 -183
- package/src/messaging/MessagingProvider.jsx +0 -0
- package/src/messaging/MessagingScopeConfig.jsx +0 -24
- package/src/messaging/PollCard.jsx +0 -163
- package/src/messaging/ReactionBar.jsx +0 -43
- package/src/messaging/ReadTicks.jsx +0 -82
- package/src/messaging/Thread.jsx +0 -195
- package/src/messaging/api.js +0 -48
- package/src/messaging/composerImageCompression.js +0 -35
- package/src/messaging/linkifyText.jsx +0 -37
- package/src/notifications/NotificationBell.jsx +0 -84
- package/src/notifications/NotificationSettings.jsx +0 -330
- package/src/notifications/NotificationsProvider.jsx +0 -224
- package/src/notifications/PopupSurface.jsx +0 -105
- package/src/notifications/api.js +0 -55
- package/src/notifications/feedApi.js +0 -18
- package/src/notifications/realtime.jsx +0 -138
- package/src/notifications/serviceWorker/sw.js +0 -50
- package/src/onboarding/OnboardingProvider.jsx +0 -326
- package/src/onboarding/OnboardingWizard.jsx +0 -61
- package/src/onboarding/WizardDialogShell.jsx +0 -51
- package/src/onboarding/api.js +0 -13
- package/src/onboarding/stepSelection.js +0 -16
- package/src/onboarding/steps/BrowserPushStep.jsx +0 -121
- package/src/onboarding/steps/CompleteNameStep.jsx +0 -46
- package/src/onboarding/steps/CookieConsentStep.jsx +0 -133
- package/src/onboarding/steps/PwaInstallStep.jsx +0 -47
- package/src/pages/AccountPage.jsx +0 -394
- package/src/pages/LoginPage.jsx +0 -343
- package/src/pages/PasswordChangePage.jsx +0 -62
- package/src/pages/PasswordInvitePage.jsx +0 -177
- package/src/pages/PasswordResetRequestPage.jsx +0 -68
- package/src/pages/SignUpPage.jsx +0 -268
- package/src/pages/SignupConfirmPage.jsx +0 -155
- package/src/testing/checkKitIntegration.js +0 -233
- package/src/theme/createAppTheme.js +0 -423
- package/src/theme/fonts.js +0 -3
- package/src/theme/index.js +0 -21
- package/src/theme/kitSxRegistry.js +0 -285
- package/src/theme/themeCompleteness.js +0 -1045
- package/src/theme/tokens.js +0 -478
- package/src/utils/auth-errors.js +0 -76
- package/src/utils/authService.js +0 -223
- package/src/utils/resolveErrorText.js +0 -28
- package/src/utils/webauthn.js +0 -51
- package/tests/AccountPage.profileStatements.test.jsx +0 -162
- package/tests/AccountPage.test.jsx +0 -200
- package/tests/AuthContext.test.jsx +0 -129
- package/tests/BarChart.test.jsx +0 -112
- package/tests/BrowserPushStep.test.jsx +0 -110
- package/tests/ChartFrame.test.jsx +0 -336
- package/tests/ConversationList.test.jsx +0 -144
- package/tests/CookieConsentStep.test.jsx +0 -69
- package/tests/EmptyState.test.jsx +0 -62
- package/tests/LineChart.test.jsx +0 -80
- package/tests/MessagingProvider.test.jsx +0 -50
- package/tests/MobileBottomNav.test.jsx +0 -319
- package/tests/NotificationBell.test.jsx +0 -60
- package/tests/NotificationSettings.test.jsx +0 -276
- package/tests/NotificationsProvider.test.jsx +0 -240
- package/tests/OnboardingProvider.test.jsx +0 -294
- package/tests/OnboardingWizard.test.jsx +0 -143
- package/tests/PasswordChangeForm.test.jsx +0 -70
- package/tests/PasswordInvitePage.test.jsx +0 -142
- package/tests/PasswordSetForm.test.jsx +0 -70
- package/tests/PopupSurface.test.jsx +0 -142
- package/tests/ProfileComponent.refresh.test.jsx +0 -194
- package/tests/QrSignupManager.registrationContext.test.jsx +0 -106
- package/tests/QrSignupManagerInfoBox.test.jsx +0 -49
- package/tests/ScatterChart.test.jsx +0 -270
- package/tests/SectionNav.test.jsx +0 -363
- package/tests/SignUpPage.test.jsx +0 -208
- package/tests/SoftChip.test.jsx +0 -105
- package/tests/StatTile.test.jsx +0 -57
- package/tests/Thread.test.jsx +0 -151
- package/tests/TimeSeriesChart.test.jsx +0 -367
- package/tests/UserListComponent.test.jsx +0 -192
- package/tests/UserMenu.test.jsx +0 -171
- package/tests/apiClientLanguage.test.js +0 -33
- package/tests/apiClientPublicPaths.test.js +0 -178
- package/tests/authApiLogin.test.js +0 -42
- package/tests/authApiRegistration.test.js +0 -31
- package/tests/authErrorCodeRendering.test.jsx +0 -77
- package/tests/authErrors.test.js +0 -74
- package/tests/authTranslations.test.js +0 -74
- package/tests/chart-api-census.test.js +0 -366
- package/tests/chartAxisTickBand.test.jsx +0 -95
- package/tests/chartDefaults.test.js +0 -902
- package/tests/chartLabels.test.js +0 -50
- package/tests/chartsPalette.test.js +0 -99
- package/tests/chatApi.test.js +0 -48
- package/tests/chatDriverNames.test.jsx +0 -71
- package/tests/chatExports.test.js +0 -3
- package/tests/chatList.test.jsx +0 -84
- package/tests/chatPicker.test.jsx +0 -82
- package/tests/chatSocket.test.jsx +0 -26
- package/tests/chatThread.test.jsx +0 -116
- package/tests/chatTranslations.test.js +0 -47
- package/tests/chatWorkspace.test.jsx +0 -234
- package/tests/checkKitIntegration.test.jsx +0 -219
- package/tests/createAppTheme.test.js +0 -132
- package/tests/createAppThemeDark.test.js +0 -127
- package/tests/createAppThemeLightGuard.test.js +0 -40
- package/tests/createAppThemePageContrast.test.js +0 -108
- package/tests/createAppThemeTypography.test.js +0 -53
- package/tests/errorCallSiteGuard.test.js +0 -32
- package/tests/exportChart.test.js +0 -313
- package/tests/feedApi.test.js +0 -46
- package/tests/fixtures/light-theme-baseline.json +0 -2234
- package/tests/i18nAggregate.test.js +0 -120
- package/tests/i18nGermanVariant.test.js +0 -65
- package/tests/loginPageErrorResolution.test.jsx +0 -102
- package/tests/messagingApi.test.js +0 -32
- package/tests/messagingChunk6.test.jsx +0 -116
- package/tests/messagingCompactBubble.test.jsx +0 -64
- package/tests/messagingComposer.test.jsx +0 -136
- package/tests/messagingComposerChunk5.test.jsx +0 -113
- package/tests/messagingContractConformance.test.js +0 -69
- package/tests/messagingDirectMessageLauncher.test.jsx +0 -151
- package/tests/messagingExports.test.js +0 -21
- package/tests/messagingInteractions.test.jsx +0 -136
- package/tests/messagingLastMessagePreview.test.jsx +0 -93
- package/tests/messagingLinkify.test.jsx +0 -51
- package/tests/messagingMessageActions.test.jsx +0 -163
- package/tests/messagingMsg6.test.jsx +0 -105
- package/tests/messagingMsg6f.test.jsx +0 -155
- package/tests/messagingMsg6g.test.jsx +0 -166
- package/tests/messagingMsg6h.test.jsx +0 -136
- package/tests/messagingMsg6i.test.jsx +0 -129
- package/tests/messagingPollRendering.test.jsx +0 -142
- package/tests/messagingReactionMerge.test.jsx +0 -77
- package/tests/messagingReadTicksTooltip.test.jsx +0 -36
- package/tests/messagingThreadReceipts.test.jsx +0 -178
- package/tests/messagingThreadingQuoting.test.jsx +0 -87
- package/tests/messagingUnreadLifecycle.test.jsx +0 -78
- package/tests/notificationsApi.test.js +0 -41
- package/tests/notificationsExports.test.js +0 -34
- package/tests/onboardingDescriptors.test.js +0 -132
- package/tests/packageTreeShaking.test.js +0 -126
- package/tests/realtime.test.jsx +0 -219
- package/tests/resolveErrorText.test.js +0 -72
- package/tests/stepSelection.test.js +0 -53
- package/tests/themeCompleteness.test.js +0 -626
- package/tests/timeSeriesLegend.test.js +0 -99
- package/tests/userScreenTranslations.test.jsx +0 -218
- package/tsconfig.build.json +0 -18
- package/vite.config.mjs +0 -6
- package/work-orders/AUTH-1.md +0 -74
- package/work-orders/AUTH-2.md +0 -74
- package/work-orders/AUTH-4.md +0 -183
- package/work-orders/AUTH-5.md +0 -154
- package/work-orders/AUTH-7.md +0 -181
- package/work-orders/AUTH-8.md +0 -184
- package/work-orders/CHART-1.md +0 -285
- package/work-orders/CHART-2.md +0 -222
- package/work-orders/CHART-3.md +0 -163
- package/work-orders/CHART-4.md +0 -203
- package/work-orders/CHART-5.md +0 -226
- package/work-orders/CHART-6.md +0 -428
- package/work-orders/CHART-7.md +0 -175
- package/work-orders/DX-1.md +0 -169
- package/work-orders/DX-2.md +0 -188
- package/work-orders/DX-3.md +0 -195
- package/work-orders/I18N-1.md +0 -254
- package/work-orders/I18N-3.md +0 -207
- package/work-orders/MSG-3.md +0 -375
- package/work-orders/MSG-3b.md +0 -1002
- package/work-orders/MSG-3c.md +0 -378
- package/work-orders/MSG-3d.md +0 -268
- package/work-orders/MSG-3e.md +0 -164
- package/work-orders/MSG-3f.md +0 -391
- package/work-orders/MSG-6e.md +0 -308
- package/work-orders/MSG-6f.md +0 -214
- package/work-orders/MSG-6g.md +0 -244
- package/work-orders/MSG-6h.md +0 -238
- package/work-orders/MSG-6i.md +0 -271
- package/work-orders/MSG-6j.md +0 -117
- package/work-orders/NOTIF-12.md +0 -214
- package/work-orders/NOTIF-13.md +0 -142
- package/work-orders/NOTIF-14.md +0 -151
- package/work-orders/PRIM-1.md +0 -177
- package/work-orders/SHELL-1.md +0 -255
- package/work-orders/SHELL-2.md +0 -197
- package/work-orders/SHELL-3.md +0 -359
- package/work-orders/SHELL-4.md +0 -351
- package/work-orders/SHELL-5.md +0 -345
- package/work-orders/SHELL-6.md +0 -210
- package/work-orders/THEME-1.md +0 -587
- package/work-orders/THEME-10.md +0 -303
- package/work-orders/THEME-11.md +0 -208
- package/work-orders/THEME-2.md +0 -339
- package/work-orders/THEME-3.md +0 -210
- package/work-orders/THEME-4.md +0 -271
- package/work-orders/THEME-5.md +0 -341
- package/work-orders/THEME-6.md +0 -247
- package/work-orders/THEME-7.md +0 -82
- package/work-orders/THEME-8.md +0 -211
- package/work-orders/THEME-9.md +0 -194
- package/work-orders/UCM-AUTH-10.md +0 -490
- package/work-orders/UCM-AUTH-11.md +0 -185
- package/work-orders/UCM-AUTH-12.md +0 -98
- package/work-orders/UCM-AUTH-13.md +0 -47
- package/work-orders/UCM-AUTH-9.md +0 -207
- package/work-orders/UCM-CHART-10.md +0 -150
- package/work-orders/UCM-CHART-11.md +0 -132
- package/work-orders/UCM-CHART-12.md +0 -348
- package/work-orders/UCM-CHART-13.md +0 -122
- package/work-orders/UCM-CHART-14.md +0 -198
- package/work-orders/UCM-CHART-15.md +0 -187
- package/work-orders/UCM-CHART-16.md +0 -169
- package/work-orders/UCM-CHART-17.md +0 -195
- package/work-orders/UCM-CHART-18.md +0 -200
- package/work-orders/UCM-CHART-8.md +0 -147
- package/work-orders/UCM-CHART-9.md +0 -265
- package/work-orders/UCM-CHAT-1.md +0 -190
- package/work-orders/UCM-CHAT-2.md +0 -75
- package/work-orders/UCM-CI-1.md +0 -65
- package/work-orders/UCM-DEP-1.md +0 -63
- package/work-orders/UCM-I18N-4.md +0 -226
- package/work-orders/UCM-I18N-5.md +0 -211
- package/work-orders/UCM-MSG-20.md +0 -192
- package/work-orders/UCM-PRIM-2.md +0 -114
- package/work-orders/UCM-SHELL-7.md +0 -134
- package/work-orders/UCM-SHELL-8.md +0 -125
- package/work-orders/UCM-TEST-1.md +0 -304
- package/work-orders/UCM-THEME-13.md +0 -69
- package/work-orders/UCM-THEME-14.md +0 -312
- package/work-orders/UCM-THEME-15.md +0 -356
- package/work-orders/UCM-THEME-16.md +0 -80
- package/work-orders/UCM-THEME-17.md +0 -111
- package/work-orders/UCM-THEME-18.md +0 -184
- package/work-orders/assets/AUTH-4-user-list-card.html +0 -407
- package/work-orders/assets/PRIM-1-dashboard-primitives.html +0 -624
- package/work-orders/assets/SHELL-2-account-navigation.html +0 -1159
- package/work-orders/assets/THEME-1-baseline-reference-sheet.html +0 -1352
- package/work-orders/assets/UCM-CHAT-1-ai-chat.html +0 -447
- package/work-orders/assets/UCM-THEME-13-dark-prototype.html +0 -390
- 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
|
-
);
|
package/dev/mockTransport.jsx
DELETED
|
@@ -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
|
-
}
|
package/docs/CHART-LAYOUT.md
DELETED
|
@@ -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).
|
package/docs/DARK-TOKENS.md
DELETED
|
@@ -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.
|
package/docs/TYPE-SCALE.md
DELETED
|
@@ -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.
|