@micha.bigler/ui-core-micha 3.14.4 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -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 +32 -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/work-orders/MSG-6i.md
DELETED
|
@@ -1,271 +0,0 @@
|
|
|
1
|
-
# WORK ORDER MSG-6i (ui-core-micha) — attachment tile size, poll spacing/denominator, wider emoji picker
|
|
2
|
-
|
|
3
|
-
**EXECUTION DIRECTIVE.** If you are the implementer reading this as your own spec, this section is not
|
|
4
|
-
addressed to you — it tells the Orchestrator how to invoke you; you ARE that invocation, do not shell
|
|
5
|
-
out to `codex exec`. Orchestrator: implement through `codex exec` in the background, invoked **directly
|
|
6
|
-
via Bash** (never the `debugger`/`*_coder` Agent wrappers) with **both** flags `--skip-git-repo-check`
|
|
7
|
-
and `--dangerously-bypass-approvals-and-sandbox`, prompt passed as a positional argument from this
|
|
8
|
-
file. Fall back to direct Claude implementation only on Codex quota / rate-limit / non-zero exit.
|
|
9
|
-
|
|
10
|
-
Feature-level WO (Expertenchat envelope). Operator polish requests 2026-08-03, gathered in a
|
|
11
|
-
requirements round after MSG-6h shipped; four independent, small scopes bundled the same way
|
|
12
|
-
MSG-6f bundled A/B/C.
|
|
13
|
-
|
|
14
|
-
## TIER
|
|
15
|
-
Tier 2 — shared-core UI consumed by every host app. Independent `reviewer` + `ui_reviewer` mandatory
|
|
16
|
-
(scope C changes poll math, not just styling).
|
|
17
|
-
|
|
18
|
-
## SCOPE
|
|
19
|
-
|
|
20
|
-
**A. Attachment tile: 64px → 120px.**
|
|
21
|
-
|
|
22
|
-
`AttachmentList.jsx`'s tile `sx={{ width: 64, height: 64, ... }}` (both the `ButtonBase` and its
|
|
23
|
-
internal thumbnail `Box component="img"`, which is `width: '100%', height: '100%'` and inherits).
|
|
24
|
-
Change both dimensions to `120`. Check the non-image tile's icon/caption layout still reads well at
|
|
25
|
-
the larger size (currently sized for 64px — `InsertDriveFileOutlinedIcon fontSize="small"` and a
|
|
26
|
-
`caption` typography may look sparse/small inside a 120px box; consider `fontSize="medium"` or larger
|
|
27
|
-
if it looks disproportionate — operator did not specify, use judgement, flag if genuinely unsure).
|
|
28
|
-
`Stack direction="row" flexWrap="wrap"` spacing stays as-is unless the larger tiles visibly break
|
|
29
|
-
wrapping at typical bubble widths.
|
|
30
|
-
|
|
31
|
-
**B. Poll card: more breathing room, not a wider bubble.**
|
|
32
|
-
|
|
33
|
-
Operator, verbatim (after seeing a screenshot of the current cramped layout — checkbox, option text,
|
|
34
|
-
and percentage squeezed onto one tight line, thin bars close to the text, minimal gap between options):
|
|
35
|
-
*"Nicht generell breiter, aber luftiger."* (Not generally wider, but airier.) This is **not** scope A
|
|
36
|
-
of a width change — the poll's outer bubble width stays exactly as-is (still shares
|
|
37
|
-
`MessageBubble.jsx`'s `maxWidth: min(75%, 680px)` with every other message type). Increase internal
|
|
38
|
-
spacing in `PollCard.jsx`: the `Stack spacing={0.75}` wrapping the whole card, the per-option
|
|
39
|
-
`Stack direction="row" spacing={0.5}` (control + label), and the `LinearProgress`'s `height: 6` are
|
|
40
|
-
the likely levers — raise spacing values and/or bar height/thickness so the card reads as roomier.
|
|
41
|
-
No prescribed exact values; operator did not send further numeric guidance beyond the "airier, not
|
|
42
|
-
wider" direction — use reasonable judgement (e.g. spacing bumped by ~50%, bar height 8-10px) and
|
|
43
|
-
**flag this specific change for a live screenshot check before considering it closed** — same
|
|
44
|
-
disclosed-limitation pattern as MSG-6h's reaction-spacing fix (no staging credentials this round).
|
|
45
|
-
|
|
46
|
-
**C. Poll percentage denominator: respondents of this poll, not sum of votes cast.**
|
|
47
|
-
|
|
48
|
-
**This reverses an explicit non-goal from `MSG-6g`'s own envelope**, confirmed deliberately by the
|
|
49
|
-
operator in this round (not an oversight): MSG-6g stated *"Denominator: total votes cast, not
|
|
50
|
-
participant count. A poll's turnout relative to group size is a different question and is not in
|
|
51
|
-
scope."* — operator's correction: for a **multi-select** poll, summing `vote_count` across options can
|
|
52
|
-
exceed the number of people who actually answered (one person can vote for several options), so a
|
|
53
|
-
per-option percentage computed against that inflated sum reads as misleadingly low and the bars
|
|
54
|
-
don't sum to a sensible 100%. The new denominator: **the number of distinct people who cast at least
|
|
55
|
-
one vote in this poll** — not total conversation participants (an un-started, never-voting group
|
|
56
|
-
member should not silently deflate everyone else's percentages further).
|
|
57
|
-
|
|
58
|
-
Compute this **client-side, no backend change**: `serialize_poll`'s existing `option.voters` arrays
|
|
59
|
-
(already present, already used for the viewer's-own-vote marking) already carry every voter's id per
|
|
60
|
-
option — the union of all options' `voters` arrays, deduplicated, is exactly "everyone who answered
|
|
61
|
-
this poll." Replace `PollCard.jsx`'s current
|
|
62
|
-
`totalVotes = options.reduce((sum, option) => sum + (option.vote_count ?? option.voters?.length ?? 0), 0)`
|
|
63
|
-
with a respondent count derived from the union of `option.voters` across all options (a `Set` of voter
|
|
64
|
-
ids). **Zero-vote rendering must stay `NaN`-free** (existing guard, do not regress it) — zero
|
|
65
|
-
respondents still means 0% for every option, not a division error.
|
|
66
|
-
|
|
67
|
-
**D. Composer emoji picker: its own, wider curated list, decoupled from reaction quick-picks.**
|
|
68
|
-
|
|
69
|
-
`Composer.jsx:144` currently reuses `QUICK_EMOJIS` from `ReactionBar.jsx` (`['👍', '❤️', '😂', '🎉',
|
|
70
|
-
'👀']`, 5 entries) — the same tiny set meant for quick message reactions, not for composing free-form
|
|
71
|
-
text. Operator confirmed direction after discussion: mobile users already have a full native emoji
|
|
72
|
-
keyboard (no in-app work needed there); desktop users lack an obvious equivalent, so a **wider curated
|
|
73
|
-
grid (~20-30 common emojis)** in the Composer's own picker adds real value. **Not** a full
|
|
74
|
-
search-based picker (out of scope — meaningfully more engineering for a need mobile already covers).
|
|
75
|
-
|
|
76
|
-
Define a new, separate constant (e.g. `COMPOSER_EMOJIS` in `Composer.jsx` itself, or a shared
|
|
77
|
-
`emojiSets.js` if that reads cleaner) with ~20-30 common emojis, grouped loosely by theme (smileys,
|
|
78
|
-
gestures, hearts, objects/travel — operator did not specify an exact list; use a reasonable, common
|
|
79
|
-
default and flag it as easily adjustable). Render as a grid inside the existing `Menu` (currently a
|
|
80
|
-
plain vertical `MenuItem` list — at 25-30 entries that would be a very long scroll; switch to a
|
|
81
|
-
wrapping grid layout, e.g. a `Box` with `display: grid` or `flexWrap`, inside the `Menu`). Keep
|
|
82
|
-
`ReactionBar.jsx`'s own `QUICK_EMOJIS` untouched (5, curated, unchanged) — reactions stay small and
|
|
83
|
-
fast, this is a Composer-only change.
|
|
84
|
-
|
|
85
|
-
## NON-GOALS / DO NOT TOUCH
|
|
86
|
-
- Do not touch `ReactionBar.jsx`'s own reaction UI or its 5-emoji `QUICK_EMOJIS` — scope D adds a
|
|
87
|
-
second, separate, larger set for the Composer only.
|
|
88
|
-
- Do not widen the message bubble's `maxWidth` (`MessageBubble.jsx`) — scope B is internal spacing
|
|
89
|
-
only, confirmed explicitly by the operator.
|
|
90
|
-
- Do not touch `MessageBubble.jsx`'s reaction-overlap fix from MSG-6h, or the lightbox/context-menu
|
|
91
|
-
logic from MSG-6h — this WO only resizes the tile (scope A), doesn't change its interaction model.
|
|
92
|
-
- No backend change for scope C — the data needed already exists in the current API response.
|
|
93
|
-
- Do not build a full emoji search/picker component (scope D) — a curated grid only.
|
|
94
|
-
|
|
95
|
-
## RISKS
|
|
96
|
-
- Scope C is a **behaviour change visible to every host app** the moment this version is consumed —
|
|
97
|
-
a poll's displayed percentages will differ from before for any multi-select poll where the same
|
|
98
|
-
person voted for multiple options. State this plainly; it is the operator's own explicit correction,
|
|
99
|
-
not a silent reinterpretation.
|
|
100
|
-
- Scope A's larger tile changes vertical rhythm inside a message bubble containing attachments —
|
|
101
|
-
check it doesn't visually crowd the meta row (timestamp/read-ticks) directly below it.
|
|
102
|
-
- Scope B and D have no prescribed exact values — implement a reasonable default, disclose the
|
|
103
|
-
specific choices made (spacing multipliers, the exact emoji list) in the completion note so the
|
|
104
|
-
operator can request a numeric tweak without re-deriving what was chosen.
|
|
105
|
-
|
|
106
|
-
## REQUIRED TESTS TO WRITE
|
|
107
|
-
Narrow and behavioural. Do NOT run the full suite.
|
|
108
|
-
|
|
109
|
-
1. Attachment tile renders at 120×120 (image and non-image cases).
|
|
110
|
-
2. Poll respondent-count denominator: a multi-select poll where one voter voted for two options must
|
|
111
|
-
show percentages based on the **respondent count** (e.g. 1 respondent → both their chosen options
|
|
112
|
-
show 100%, not summed-and-diluted), not `sum(vote_count)`. This must **fail against the current
|
|
113
|
-
`sum(vote_count)` code** — prove it with a case where the two differ (multi-select, one voter,
|
|
114
|
-
two options voted).
|
|
115
|
-
3. Poll respondent-count denominator: the existing single-choice zero-vote case still shows 0%, no
|
|
116
|
-
`NaN`, no regression (reuse/extend the existing MSG-6g zero-vote test's assertion shape).
|
|
117
|
-
4. Composer emoji picker: opening it shows more than 5 emoji options (the expanded set, not
|
|
118
|
-
`ReactionBar`'s `QUICK_EMOJIS`); selecting one still inserts it at the cursor position (existing
|
|
119
|
-
`insertEmoji` behaviour, unchanged — just fed from the new list).
|
|
120
|
-
5. `ReactionBar`'s own picker is unaffected — still exactly its original 5 `QUICK_EMOJIS` (regression
|
|
121
|
-
guard that scope D didn't leak into scope-unrelated reaction UI).
|
|
122
|
-
|
|
123
|
-
## TEST SCOPE FOR THE GATE (orchestrator)
|
|
124
|
-
Attachment-, poll-, and Composer-adjacent messaging test files. Not the full suite.
|
|
125
|
-
|
|
126
|
-
## TARGET REPO
|
|
127
|
-
`C:\Users\biglmi\Documents\webapps\ui-core-micha`. Branch `main`. Publish per the repo's release flow;
|
|
128
|
-
the jg-ferien pin bump is part of THIS WO's completion, not a deferred step (standing correction this
|
|
129
|
-
session: publish alone is not delivery).
|
|
130
|
-
|
|
131
|
-
## MINI-HANDOVER (pastable)
|
|
132
|
-
|
|
133
|
-
> Repo: `C:\Users\biglmi\Documents\webapps\ui-core-micha` (branch `main`). Work order:
|
|
134
|
-
> `work-orders/MSG-6i.md` — read it fully, then follow the `orchestrate-codex` skill.
|
|
135
|
-
>
|
|
136
|
-
> Four independent scopes: (A) attachment tile 64px→120px in `AttachmentList.jsx`. (B) `PollCard.jsx`
|
|
137
|
-
> internal spacing increased ("airier, not wider" — bubble maxWidth unchanged) — no exact numbers
|
|
138
|
-
> given, use judgement, flag for a live screenshot check. (C) poll percentage denominator changes from
|
|
139
|
-
> `sum(vote_count)` to the count of distinct respondents (union of all options' `voters` arrays,
|
|
140
|
-
> client-side, no backend change) — **this deliberately reverses an explicit MSG-6g non-goal**, operator
|
|
141
|
-
> re-confirmed. (D) Composer's emoji picker gets its own ~20-30-entry curated grid, decoupled from
|
|
142
|
-
> `ReactionBar`'s 5-entry `QUICK_EMOJIS` (untouched) — no full search picker, mobile already has a
|
|
143
|
-
> native one.
|
|
144
|
-
|
|
145
|
-
## PROGRESS CONTRACT
|
|
146
|
-
Emit `PLAN: <steps>` up front, then a single-line `PROGRESS: [<n>/<total>] <action>` before every
|
|
147
|
-
relevant action and `PROGRESS: [<n>/<total>] done` on completion, spaced so no gap exceeds ~2 min,
|
|
148
|
-
stdout unbuffered, and exactly one final `RESULT: DONE|BLOCKED <reason>`.
|
|
149
|
-
|
|
150
|
-
---
|
|
151
|
-
|
|
152
|
-
## Part B — Implementation map (Orchestrator) / as-built record
|
|
153
|
-
|
|
154
|
-
**Codex was unavailable (out of credits) — implemented directly by the Orchestrator.**
|
|
155
|
-
|
|
156
|
-
### Scope A — `AttachmentList.jsx`
|
|
157
|
-
Tile `sx={{ width: 64, height: 64, ... }}` → `120, 120`. Non-image tile's icon bumped
|
|
158
|
-
`fontSize="small"` → `"large"` and its `Stack` spacing `0.25` → `0.75` — the original
|
|
159
|
-
small icon+caption pairing read sparse against the much larger tile.
|
|
160
|
-
|
|
161
|
-
### Scope B — `PollCard.jsx` spacing
|
|
162
|
-
Outer `Paper` padding `p: 1` → `1.5`; outer `Stack` (question/options/close-row) spacing
|
|
163
|
-
`0.75` → `1.5`; per-option `richLabel` `Stack` spacing `0.25` → `0.75`; `LinearProgress`
|
|
164
|
-
`height: 6` → `8`. No exact values were specified by the operator beyond "airier, not
|
|
165
|
-
wider" (confirmed via a screenshot of the cramped original) — **not independently
|
|
166
|
-
visually re-verified against a fresh screenshot before closing** (no staging/live
|
|
167
|
-
credentials for this specific check this round); flagging per the same disclosed-
|
|
168
|
-
limitation pattern as MSG-6h's reaction-spacing fix.
|
|
169
|
-
|
|
170
|
-
### Scope C — `PollCard.jsx` percentage denominator
|
|
171
|
-
`totalVotes = sum(option.vote_count)` replaced with
|
|
172
|
-
`respondentCount = new Set(options.flatMap(option => option.voters || [])).size` — the
|
|
173
|
-
count of distinct people who voted for *any* option, computed client-side from data
|
|
174
|
-
already present in the response (`voters` arrays, already used for the viewer's-own-vote
|
|
175
|
-
marking). `percent` now divides by `respondentCount`.
|
|
176
|
-
|
|
177
|
-
### Scope D — `Composer.jsx` emoji picker
|
|
178
|
-
New `COMPOSER_EMOJIS` (28 entries) defined locally in `Composer.jsx`, rendered as a
|
|
179
|
-
7-column CSS grid of `IconButton`s inside the existing `Menu` (replacing the old
|
|
180
|
-
`QUICK_EMOJIS.map(... <MenuItem>...)` vertical list — `MenuItem` import removed, now
|
|
181
|
-
unused). `ReactionBar.jsx`'s own `QUICK_EMOJIS` (5, curated) untouched.
|
|
182
|
-
|
|
183
|
-
### Companion fix found and fixed — `Thread.jsx` (not in this WO's original scope)
|
|
184
|
-
Discovered while live-verifying jg-ferien's `MSG-18` (mobile Composer hidden behind the
|
|
185
|
-
fixed bottom nav) in the *same* browser session as this WO's own work: after fixing every
|
|
186
|
-
jg-side layout offset, the Composer was **still** rendering behind the nav. Root cause
|
|
187
|
-
traced into this package: `Thread.jsx`'s root `<Stack>` had no `flex`/`minHeight`, and its
|
|
188
|
-
scroll `Box` used a **hardcoded `maxHeight: 560`** — neither participates in a host's
|
|
189
|
-
flex-column layout at all, so on a short mobile viewport the thread never shrinks to fit,
|
|
190
|
-
regardless of how correctly the *host* sizes its own containers. Fixed: both the root
|
|
191
|
-
`Stack` and the scroll `Box` gained `flex: 1, minHeight: 0` (the classic flex-item
|
|
192
|
-
`min-height: auto` trap — a flex item won't shrink below its content's natural size
|
|
193
|
-
without this). `maxHeight: 560` kept as an upper-bound fallback, so a host that does
|
|
194
|
-
**not** embed `Thread` in a flex-column context sees **zero behaviour change** (both new
|
|
195
|
-
properties are no-ops outside a flex context). Live-verified end-to-end (see MSG-18's own
|
|
196
|
-
as-built record in jg-ferien for the full trace): the composer moved from `top: 742`
|
|
197
|
-
(inside the fixed nav's own `740–812` range) to `top: 629.5` (a clean 64px gap above the
|
|
198
|
-
nav at `740`), by locally building this package's `dist/` and patching it into jg's
|
|
199
|
-
`node_modules` for verification — no publish needed to prove the fix, but IS needed to
|
|
200
|
-
actually ship it (bundled into this same release, not deferred).
|
|
201
|
-
|
|
202
|
-
### Tests
|
|
203
|
-
`tests/messagingMsg6i.test.jsx` (new) — 7 tests: all 5 WO-required cases, one extra
|
|
204
|
-
(emoji-insertion behaviour still works from the new grid), plus a 6th added post-review
|
|
205
|
-
(see R3 below) asserting `Thread.jsx`'s `flex`/`minHeight` are actually applied.
|
|
206
|
-
|
|
207
|
-
### Verification (pre-review)
|
|
208
|
-
Full suite 249 → 255 (6 new), `tsc` clean.
|
|
209
|
-
|
|
210
|
-
### Independent review — `reviewer` + `ui_reviewer`, both run in parallel, findings + fixes
|
|
211
|
-
|
|
212
|
-
Both reviewers independently converged on the same P2 keyboard-navigation regression
|
|
213
|
-
(scope D) — treated as one finding below (R1/U1).
|
|
214
|
-
|
|
215
|
-
- **R1/U1 (P2, confirmed by both reviewers) — Composer's emoji grid broke MUI `Menu`'s
|
|
216
|
-
arrow-key roving-focus.** Nesting the 28 `IconButton`s inside an intervening
|
|
217
|
-
`<Box role="group">` meant `MenuList`'s `moveFocus` (which only traverses **direct**
|
|
218
|
-
children) saw a single non-tabbable child, so ArrowDown/Up/Home/End all became dead —
|
|
219
|
-
none of the 28 buttons reachable via the menu's own keyboard model, and nesting
|
|
220
|
-
`role="button"` descendants under `role="menu"` isn't a conformant ARIA pattern either.
|
|
221
|
-
**Fixed**: swapped `<Menu>` for `<Popover>` (`Composer.jsx`) — a grid of plain buttons
|
|
222
|
-
was never actually a linear menu, so this stops advertising semantics it can't deliver
|
|
223
|
-
and leaves keyboard traversal to the browser's native Tab order, which does reach every
|
|
224
|
-
button. Replicated Menu's default `anchorOrigin`/`transformOrigin`
|
|
225
|
-
(`{vertical:'bottom',horizontal:'left'}` / `{vertical:'top',horizontal:'left'}`) so the
|
|
226
|
-
picker's on-screen position is unchanged.
|
|
227
|
-
- **R2 (P2, `reviewer`) — optimistic vote could desync `vote_count` from `voters`.**
|
|
228
|
-
`MessagingProvider.applyOptimisticVote` bumped `vote_count` unconditionally but only
|
|
229
|
-
updated `voters` when `userId` was known (a documented "brief window" before
|
|
230
|
-
`AuthContext` populates `user` — in practice also the default state for any host/test
|
|
231
|
-
that doesn't wrap `MessagingProvider` in `AuthContext` at all, confirmed while fixing
|
|
232
|
-
this). Scope C's new `respondentCount` denominator reads only from `voters`, so that
|
|
233
|
-
window could show a transiently inconsistent/>-100% percentage. **Fixed**: added a
|
|
234
|
-
sentinel (`OPTIMISTIC_ANONYMOUS_VOTER`, a space-prefixed string no real UUID can match)
|
|
235
|
-
used in place of `userId` in `voters` when it's unknown, so `vote_count` and `voters`
|
|
236
|
-
move together in every case — the real vote still overwrites this on
|
|
237
|
-
reconcile/revert. Re-ran every existing poll-vote test
|
|
238
|
-
(`messagingMsg6g/Interactions/PollRendering/CompactBubble/Api`) to confirm the
|
|
239
|
-
already-tested optimistic-count behaviour (which relies on the no-`AuthContext` test
|
|
240
|
-
shape) still holds — it does.
|
|
241
|
-
- **R3 (P2, `reviewer`) — `Thread.jsx`'s flex/minHeight companion fix had zero regression
|
|
242
|
-
coverage**, and the "not meaningfully assertable via jsdom" justification didn't hold up
|
|
243
|
-
(this WO's own tile-size test already proves resolved `sx` values ARE readable via
|
|
244
|
-
`getComputedStyle` without real layout). **Fixed**: added test 6 to
|
|
245
|
-
`tests/messagingMsg6i.test.jsx`, asserting `flexGrow`/`minHeight` on both `Thread`'s
|
|
246
|
-
root `Stack` and scroll `Box`, plus that `maxHeight: 560` is still present as the
|
|
247
|
-
fallback cap.
|
|
248
|
-
- **U2 (P3, `ui_reviewer`) — hardcoded `fontSize: '1.15rem'`** on the per-emoji
|
|
249
|
-
`IconButton`. No existing theme/typography token covers emoji-glyph sizing (checked
|
|
250
|
-
`ReactionBar.jsx`'s own emoji rendering for precedent — none exists there either).
|
|
251
|
-
**Not fixed** — P3, no clear correct token to switch to; noted here rather than
|
|
252
|
-
silently dropped.
|
|
253
|
-
- **U3 (P3, `ui_reviewer`) — the emoji grid reused `MessagingComposer.ADD_EMOJI`** as its
|
|
254
|
-
`role="group"` `aria-label`, identical to the trigger button's own label, so a screen
|
|
255
|
-
reader announces "Add emoji" twice back-to-back. **Fixed**: added a dedicated
|
|
256
|
-
`MessagingComposer.EMOJI_PICKER` key ("Choose an emoji" / "Emoji auswählen" / "Choisir
|
|
257
|
-
un emoji") for the grid.
|
|
258
|
-
- **R4 (P3, `reviewer`) — test 4b's assertion (`input.value.length > 2`) didn't prove
|
|
259
|
-
cursor-position insertion**, only that something got appended. **Fixed**: rewrote it to
|
|
260
|
-
match the exact expected string at a mid-string cursor position (same shape as the
|
|
261
|
-
pre-existing `messagingComposerChunk5.test.jsx` assertion).
|
|
262
|
-
- **R5 (P3, `reviewer`) — residual, not fixed**: the 120px tile stretches whatever
|
|
263
|
-
`getAttachmentThumbnail` returns via `objectFit: cover`; if that thumbnail is
|
|
264
|
-
server-sized for the old 64px target, the larger tile could show blur. Not verifiable
|
|
265
|
-
from this repo (backend-controlled, untouched by this WO) — flagged as disclosed
|
|
266
|
-
residual risk, consistent with scope B's own "no live screenshot check this round" note.
|
|
267
|
-
|
|
268
|
-
### Verification (post-review)
|
|
269
|
-
Full suite (single-forked to avoid an unrelated vitest worker-pool timeout seen under the
|
|
270
|
-
default parallel pool): **45 test files, 256/256 passed** (255 + 1 new). `tsc -p
|
|
271
|
-
tsconfig.build.json --noEmit` clean.
|
package/work-orders/MSG-6j.md
DELETED
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
# WORK ORDER MSG-6j — poll option row collides with its vote count under a tight bubble width
|
|
2
|
-
|
|
3
|
-
**EXECUTION DIRECTIVE.** If you are the implementer reading this as your own spec, this section is
|
|
4
|
-
not addressed to you — it tells the Orchestrator how to invoke you; you ARE that invocation, do not
|
|
5
|
-
shell out to `codex exec`. Orchestrator: implement through `codex exec` in the background, invoked
|
|
6
|
-
**directly via Bash** (never the `debugger`/`*_coder` Agent wrappers) with **both** flags
|
|
7
|
-
`--skip-git-repo-check` and `--dangerously-bypass-approvals-and-sandbox`, prompt passed as a
|
|
8
|
-
positional argument from this file. Fall back to direct Claude implementation only on Codex quota /
|
|
9
|
-
rate-limit / non-zero exit.
|
|
10
|
-
|
|
11
|
-
Feature-level WO (Expertenchat envelope). Operator screenshots from `jg-ferien` production, 2026-08-04:
|
|
12
|
-
a poll option's label text runs directly into its vote-count/percent text with **zero visible gap**
|
|
13
|
-
("soidfsdoöifn**2** (100 %)" — the bold "2" is the vote count, not part of the option text). The
|
|
14
|
-
percentage math itself is confirmed correct once both votes had landed (a second screenshot showed
|
|
15
|
-
"2 (100 %)" / "1 (50 %)" as expected) — **this WO is width/layout only, no data or percentage logic
|
|
16
|
-
change.**
|
|
17
|
-
|
|
18
|
-
## TIER
|
|
19
|
-
Tier 2 — `ui-core-micha` is shared-core, which forces Tier 2 regardless of change size per AGENTS.md.
|
|
20
|
-
Independent `reviewer` mandatory. No `sec_reviewer` needed (pure layout, no data/permission surface).
|
|
21
|
-
|
|
22
|
-
## ROOT CAUSE — verified against the landed code (`src/messaging/PollCard.jsx`, `MessageBubble.jsx`)
|
|
23
|
-
|
|
24
|
-
`MessageBubble.jsx:100` sizes every message bubble as `width: 'fit-content', maxWidth: 'min(75%, 680px)'`
|
|
25
|
-
— it shrinks to exactly the width its content needs, with **no minimum floor**. Inside a poll,
|
|
26
|
-
`PollCard.jsx`'s `optionLabel()` renders each option's label and its `resultText` ("{count} ({percent}
|
|
27
|
-
%)") in a `Stack direction="row" justifyContent="space-between"` with **no `spacing`/`gap`** of its
|
|
28
|
-
own (`:112-116`). `justifyContent: 'space-between'` only inserts space when the row has *leftover*
|
|
29
|
-
width beyond what its children need — but here the bubble's own width is derived FROM this exact
|
|
30
|
-
content (`fit-content`), so by construction there is no leftover space, and space-between reserves
|
|
31
|
-
**zero minimum gap**. The label (`Typography noWrap`, which MUI resolves to
|
|
32
|
-
`overflow:hidden;textOverflow:ellipsis;whiteSpace:nowrap`) shows its full text without truncating
|
|
33
|
-
because the row isn't actually being squeezed narrower than content — it's exactly content-width, so
|
|
34
|
-
label and result text end up flush against each other with nothing between.
|
|
35
|
-
|
|
36
|
-
Two independent, additive fixes, both needed (confirmed by re-reading the code, not guessed):
|
|
37
|
-
|
|
38
|
-
1. **Give the label/result row an explicit minimum gap that `space-between` cannot collapse to zero.**
|
|
39
|
-
Add `spacing={1}` (or equivalent `gap`) to the `Stack direction="row" justifyContent="space-between"`
|
|
40
|
-
at `PollCard.jsx:113` (inside `optionLabel()`'s `richLabel`). MUI's `Stack` applies its `spacing`
|
|
41
|
-
prop as margin between children, independent of `justifyContent` — this guarantees a floor gap
|
|
42
|
-
even when the row has no leftover width to distribute.
|
|
43
|
-
2. **Give the poll card itself a sensible minimum width** so a short question/option set doesn't pull
|
|
44
|
-
the whole bubble in tighter than is comfortable to read (the operator's "insbesondere in der Breite
|
|
45
|
-
mehr Platz" — more room specifically in width). Add a `minWidth` to the `Paper` at
|
|
46
|
-
`PollCard.jsx:123` (e.g. `minWidth: 280` — pick a value that reads comfortably at this repo's base
|
|
47
|
-
font size; check against `MessageBubble.jsx`'s own `maxWidth` ceiling so a minWidth poll never
|
|
48
|
-
exceeds the bubble's own max on a narrow viewport — clamp or use `Math.min` logic if needed, verify
|
|
49
|
-
in the browser at a mobile width e.g. 375px, not just desktop).
|
|
50
|
-
|
|
51
|
-
## NON-GOALS / DO NOT TOUCH
|
|
52
|
-
|
|
53
|
-
- Do not touch percentage/vote-count computation (`respondentCount`, `optionLabel`'s `percent`
|
|
54
|
-
calculation) — confirmed correct by the operator's second screenshot; this WO is layout-only.
|
|
55
|
-
- Do not touch `MessageBubble.jsx`'s own `maxWidth`/`width: fit-content` — other message kinds
|
|
56
|
-
(plain text, attachments, announcements) rely on that tight fit; scope the fix to the poll's own
|
|
57
|
-
minimum, not a global bubble-sizing change.
|
|
58
|
-
- Do not touch `applyOptimisticVote`, `castPollVote`, or any vote-casting logic.
|
|
59
|
-
|
|
60
|
-
## RISKS
|
|
61
|
-
|
|
62
|
-
- A `minWidth` on the poll `Paper` that isn't reconciled against `MessageBubble`'s `maxWidth` could
|
|
63
|
-
overflow the bubble container on a narrow mobile viewport (375px) if chosen carelessly — verify at
|
|
64
|
-
that width explicitly, not just desktop, per the WO's own note in `PollCard.jsx`'s existing
|
|
65
|
-
accessibility comment about width assumptions.
|
|
66
|
-
- `spacing` on a `Stack` that already has other margin/padding assumptions elsewhere in the row could
|
|
67
|
-
shift alignment of the progress bar below it (`LinearProgress` sits in the same parent `Stack`,
|
|
68
|
-
outside the row) — verify visually that the bar still aligns with the row above it.
|
|
69
|
-
|
|
70
|
-
## REQUIRED TESTS TO WRITE
|
|
71
|
-
|
|
72
|
-
Narrow, matching this repo's existing `PollCard` test conventions — extend
|
|
73
|
-
`tests/messagingPollRendering.test.jsx` (the file covering poll rendering today) rather than
|
|
74
|
-
inventing a new file. `tests/messagingMsg6i.test.jsx` and `messagingMsg6g.test.jsx` are the other two
|
|
75
|
-
poll-adjacent suites; skim them for the established assertion style before writing new tests.
|
|
76
|
-
|
|
77
|
-
1. A poll with a short question/options renders its `Paper`/container at at least the chosen minimum
|
|
78
|
-
width (assert a computed style or a `sx` prop value — whatever this repo's existing width
|
|
79
|
-
assertions already use elsewhere, e.g. `MessageBubble`'s own tests, follow that pattern).
|
|
80
|
-
2. The label/result-text row has non-zero spacing between its two children even when both are their
|
|
81
|
-
natural (non-ellipsized) width — a regression test for the exact reported bug: render a poll with
|
|
82
|
-
one option's label + result text combined width close to the container's available width, assert
|
|
83
|
-
there is a rendered gap (e.g. via a wrapping element's `gap`/margin style, or simply that the
|
|
84
|
-
two `Typography` nodes are not adjacent DOM siblings with zero computed margin — use whatever
|
|
85
|
-
assertion style this repo's other spacing regression tests already use, e.g. MSG-6i's own
|
|
86
|
-
`getComputedStyle`-based test for `Thread.jsx`'s flex fix, same technique).
|
|
87
|
-
|
|
88
|
-
**Non-vacuity:** test 2 must fail against the pre-fix code (space-between with no `spacing`) — verify
|
|
89
|
-
that before considering this WO done.
|
|
90
|
-
|
|
91
|
-
## VERIFICATION
|
|
92
|
-
|
|
93
|
-
Live browser check at both a normal desktop width and a 375px mobile width (this repo's own
|
|
94
|
-
established convention per prior MSG-6 WOs) with a real poll: two options with realistic-length text,
|
|
95
|
-
confirm the label and vote-count/percent never visually touch, and the card is not uncomfortably
|
|
96
|
-
narrow for a two-option poll.
|
|
97
|
-
|
|
98
|
-
## TARGET REPO
|
|
99
|
-
|
|
100
|
-
`C:\Users\biglmi\Documents\webapps\ui-core-micha` (branch `main`, no `develop` in this repo).
|
|
101
|
-
|
|
102
|
-
## MINI-HANDOVER (pastable)
|
|
103
|
-
|
|
104
|
-
> Repo: `C:\Users\biglmi\Documents\webapps\ui-core-micha` (branch `main`). Work order:
|
|
105
|
-
> `work-orders/MSG-6j.md` — read it fully, then follow the `orchestrate-codex` skill.
|
|
106
|
-
>
|
|
107
|
-
> `PollCard.jsx`'s option row uses `justifyContent: space-between` with no `spacing`, inside a message
|
|
108
|
-
> bubble that is `width: fit-content` — so there is never leftover space for space-between to
|
|
109
|
-
> distribute, and the option's label text runs directly into its vote-count/percent text with zero
|
|
110
|
-
> gap. Fix: add `spacing` to that row, and add a sensible `minWidth` to the poll's `Paper` (reconciled
|
|
111
|
-
> against `MessageBubble.jsx`'s `maxWidth: min(75%, 680px)` ceiling, verified at 375px too). No
|
|
112
|
-
> percentage/vote-count logic changes — that part is already correct.
|
|
113
|
-
|
|
114
|
-
## PROGRESS CONTRACT
|
|
115
|
-
Emit `PLAN: <steps>` up front, then a single-line `PROGRESS: [<n>/<total>] <action>` before every
|
|
116
|
-
relevant action and `PROGRESS: [<n>/<total>] done` on completion, spaced so no gap exceeds ~2 min,
|
|
117
|
-
stdout unbuffered, and exactly one final `RESULT: DONE|BLOCKED <reason>`.
|
package/work-orders/NOTIF-12.md
DELETED
|
@@ -1,214 +0,0 @@
|
|
|
1
|
-
# WORK ORDER NOTIF-12 (ui-core-micha + django-core-micha) — popup channel via the wizard renderer
|
|
2
|
-
|
|
3
|
-
**EXECUTION DIRECTIVE.** Implement through `codex exec` in the background — invoked **directly via
|
|
4
|
-
Bash** (never the `debugger`/`*_coder` Agent wrappers) with **both** flags `--skip-git-repo-check`
|
|
5
|
-
and `--dangerously-bypass-approvals-and-sandbox`, prompt passed as a positional argument from this
|
|
6
|
-
file. Fall back to direct Claude implementation only on Codex quota / rate-limit / non-zero exit.
|
|
7
|
-
|
|
8
|
-
Feature-level WO (Expertenchat envelope). Seams verified against ucm 2.12.0 / dcm 2.32.0.
|
|
9
|
-
|
|
10
|
-
## HARD GATE — SATISFIED 2026-07-30
|
|
11
|
-
NOTIF-13 landed and published: ucm **2.13.0** (`7a83ee9`), dcm **2.33.0** (`de77335`), both verified
|
|
12
|
-
live on npm/PyPI. This WO was re-verified against the landed code on 2026-07-30 and the context
|
|
13
|
-
package below describes the **actual shipped API**, not the pre-NOTIF-13 state. Cleared to start.
|
|
14
|
-
|
|
15
|
-
## TIER
|
|
16
|
-
Tier 2 — this extracts a renderer out of `OnboardingWizard`, which **cockpit and jg run in
|
|
17
|
-
production**. The roadmap calls the popup channel "uncritical"; that refers to business urgency, not
|
|
18
|
-
blast radius. Independent `reviewer` mandatory. `sec_reviewer` only on explicit operator request.
|
|
19
|
-
|
|
20
|
-
## SCAFFOLDING NOTICE — READ FIRST
|
|
21
|
-
**This channel ships with zero producers, deliberately.** Verified 2026-07-29 across dcm, jg-ferien,
|
|
22
|
-
cockpit, hram, spesix and survey_app: **no notification type declares `popup` in `eligible_channels`**,
|
|
23
|
-
so `resolve_channels()` will never route anything to it until some app opts a type in. The operator
|
|
24
|
-
decided to build the scaffolding anyway (2026-07-29), having been shown this.
|
|
25
|
-
|
|
26
|
-
Two obligations follow, and they are part of the acceptance criteria:
|
|
27
|
-
1. **Do not overclaim.** The CHANGELOG entry and any docstring must state plainly that the popup
|
|
28
|
-
channel is wired but inactive until a notification type declares `popup`. Precedent: NOTIF-4's
|
|
29
|
-
reviewer (R1) had to soften a CHANGELOG that promised retry behaviour no shipped dispatcher could
|
|
30
|
-
reach. Do not repeat that.
|
|
31
|
-
2. **Scaffolding must be tested scaffolding.** Register a test-local notification type that declares
|
|
32
|
-
`popup` and prove the whole path end-to-end. Untested dead code is not an acceptable outcome.
|
|
33
|
-
|
|
34
|
-
## GOAL
|
|
35
|
-
Make `popup` a real delivery channel: a notification whose type opts into `popup` is rendered to the
|
|
36
|
-
recipient in a modal that reuses the onboarding **wizard renderer**, with its seen/dismissed status
|
|
37
|
-
recorded on `NotificationRecipient` — **not** in the onboarding progress store.
|
|
38
|
-
|
|
39
|
-
## EXPECTED OUTCOME
|
|
40
|
-
- dcm's `PopupDispatcher` stops being a logging stub and delivers for real.
|
|
41
|
-
- ucm exports a popup surface that renders popup-eligible notifications and marks them via the
|
|
42
|
-
existing `feed/mark/` path.
|
|
43
|
-
- Onboarding behaviour is bit-for-bit unchanged for existing consumers.
|
|
44
|
-
- With no producer registered, nothing visibly changes in any app.
|
|
45
|
-
|
|
46
|
-
## CONTEXT PACKAGE — verified current state
|
|
47
|
-
|
|
48
|
-
**dcm `notifications/dispatch.py`:**
|
|
49
|
-
- `PopupDispatcher` (`:117-121`) is a stub: logs "queued for unimplemented popup channel" and returns
|
|
50
|
-
`DeliveryResult(ok=None, error="pending")`. It is registered in `_DISPATCHERS` (`:126-133`).
|
|
51
|
-
- The model to copy is `ChipDispatcher` (`dispatch.py:64-75`, post-NOTIF-13): it calls
|
|
52
|
-
`push_to_users([recipient.user], notification_envelope({"type": notification.notification_type, "content": ..., "notification_id": ...}))`
|
|
53
|
-
and returns `DeliveryResult(ok=True)`.
|
|
54
|
-
|
|
55
|
-
**How NOTIF-13 actually shipped — read this before writing scope D.** The envelope is a
|
|
56
|
-
**domain-level** discriminator, not a per-channel one:
|
|
57
|
-
- `delivery.py:25` `NOTIFICATION_ENVELOPE = "notification"`; `delivery.py:28-35`
|
|
58
|
-
`notification_envelope(payload)` merges `{"envelope": "notification"}` into any payload. **Additive**
|
|
59
|
-
— the pre-existing fields, including the overloaded `type`, are unchanged.
|
|
60
|
-
- ucm `src/notifications/realtime.jsx:68` routes purely on `data.envelope ?? DEFAULT_ENVELOPE`, then
|
|
61
|
-
fans to every handler subscribed to that envelope.
|
|
62
|
-
- **Inside** the notification envelope, `NotificationsProvider` keeps the original two-way split:
|
|
63
|
-
`data.type === 'notification.status'` patches status (`:98`), and **everything else is appended to
|
|
64
|
-
the feed with `unreadCount + 1`** (`:120`).
|
|
65
|
-
|
|
66
|
-
**Consequence — the trap this WO must avoid:** if `PopupDispatcher` simply calls
|
|
67
|
-
`notification_envelope({...})` like Chip does, its message is **indistinguishable from a chip
|
|
68
|
-
message**. The popup surface subscribing to the `"notification"` envelope would receive every chip
|
|
69
|
-
notification and pop up for all of them, and a notification routed to *both* chip and popup would
|
|
70
|
-
produce **two feed entries and `unreadCount + 2` for one notification**. Scope D and D2 below exist
|
|
71
|
-
to close exactly this.
|
|
72
|
-
- `_render_content(content, user)` (`:44-60`) renders `title_key`/`body_key`/`params` in the
|
|
73
|
-
recipient's language — reuse it rather than re-deriving text client-side if the payload needs text.
|
|
74
|
-
- `prefs.py:22` already treats `popup` as a channel defaulting to True; `router.py:24` narrows to
|
|
75
|
-
`ntype.eligible_channels`. **No prefs or router change is needed** — only a type opting in.
|
|
76
|
-
|
|
77
|
-
**ucm `src/onboarding/OnboardingWizard.jsx` (75 LOC)** is the renderer to reuse:
|
|
78
|
-
- MUI `Dialog open fullWidth maxWidth="sm"` (`:59`), `DialogTitle` with a step counter +
|
|
79
|
-
`LinearProgress` (`:60-67`), `DialogContent` rendering
|
|
80
|
-
`<StepComponent onComplete={...} onDismiss={...} ctx={...} />` (`:69`).
|
|
81
|
-
- Step contract: `{ id, Component, blocking, persistDismissed }`. `blocking` disables escape/backdrop
|
|
82
|
-
close (`:59`).
|
|
83
|
-
- **The parts that must NOT come along** (design decision D-F7,
|
|
84
|
-
`django-core-micha/docs/design/notifications-platform.md:70-71`): `onboarding.markStepSeen` (`:35`),
|
|
85
|
-
`dismissStep` (`:54`), and the sequential progress store behind them. Popup status lives on
|
|
86
|
-
`NotificationRecipient`.
|
|
87
|
-
- Exported at `src/index.js:61`; the four existing steps at `:62-65`.
|
|
88
|
-
|
|
89
|
-
**ucm notification status API:** `useNotifications()` already returns `markSeen` / `markDismissed` /
|
|
90
|
-
`markDone`, backed by `feed/mark/`. Use `markSeen` when a popup is shown and `markDismissed` when the
|
|
91
|
-
user closes it. **Never `markDone`** — done is a todo-channel semantic.
|
|
92
|
-
|
|
93
|
-
## SCOPE
|
|
94
|
-
|
|
95
|
-
**A. ucm — extract the dialog shell.** Lift the presentational shell out of `OnboardingWizard` into a
|
|
96
|
-
reusable renderer (dialog frame, counter/progress header, the `onComplete`/`onDismiss`/`ctx` child
|
|
97
|
-
contract). `OnboardingWizard` keeps its current behaviour by composing that shell with the onboarding
|
|
98
|
-
store; the popup surface composes the same shell with notification state.
|
|
99
|
-
|
|
100
|
-
**B. ucm — the popup surface.** A component that renders popup-eligible notifications through the
|
|
101
|
-
shell, marks `seen` on display and `dismissed` on close. For live popups it subscribes via
|
|
102
|
-
`useRealtime()` to the **`"notification"` envelope** (`DEFAULT_ENVELOPE` — there is no popup-specific
|
|
103
|
-
envelope, see D) and filters on `channel === "popup"`, ignoring `notification.status` messages and
|
|
104
|
-
channel-less legacy payloads. Export additively from `src/index.js`.
|
|
105
|
-
|
|
106
|
-
**C. Coexistence rule with onboarding — specify and test it.** Both surfaces render a MUI `Dialog`, so
|
|
107
|
-
two modals can be open at once. Required behaviour: **a blocking onboarding step wins**; the popup
|
|
108
|
-
waits until no blocking onboarding step is active. Non-blocking coexistence must not stack two dialogs.
|
|
109
|
-
|
|
110
|
-
**D. dcm — implement `PopupDispatcher.deliver`, with a channel discriminator *inside* the envelope.**
|
|
111
|
-
Emit through `notification_envelope()` (so Layer-1 routing stays domain-keyed and messaging can still
|
|
112
|
-
ride alongside) and add an explicit **`"channel": "popup"`** field to the payload. Return
|
|
113
|
-
`DeliveryResult(ok=True)`. Keep delivery-record semantics identical to `ChipDispatcher` — the
|
|
114
|
-
surrounding `dispatch()` machinery already records pending → sent/failed; do not re-implement it.
|
|
115
|
-
|
|
116
|
-
**Do NOT invent a second envelope value** (e.g. `"notification.popup"`). The envelope discriminates
|
|
117
|
-
*domains* so a future messaging stream can share the socket; overloading it with channels would
|
|
118
|
-
undo NOTIF-13's design. Channel selection belongs inside the payload.
|
|
119
|
-
|
|
120
|
-
For symmetry and so the client can tell the cases apart, `ChipDispatcher` should carry
|
|
121
|
-
`"channel": "chip"` as well. **Backward compatibility:** a payload with **no** `channel` field must
|
|
122
|
-
keep behaving exactly as today (feed entry + unread increment), because an app pinned to an older dcm
|
|
123
|
-
will still send channel-less payloads.
|
|
124
|
-
|
|
125
|
-
**D2. ucm — de-duplicate by `notification_id` in `NotificationsProvider`.** A notification routed to
|
|
126
|
-
both chip and popup now produces two WS messages. The provider must fold them into **one** feed entry
|
|
127
|
-
and **one** unread increment (de-dupe on `notification_id`, last-write-wins on content). Without this,
|
|
128
|
-
enabling popup alongside chip double-counts the badge. This is an edit to `NotificationsProvider`,
|
|
129
|
-
which is explicitly in scope here — it is not part of NOTIF-13's realtime core.
|
|
130
|
-
|
|
131
|
-
## DO NOT TOUCH
|
|
132
|
-
- **The onboarding progress store** — `markStepSeen`, `dismissStep`, `persistDismissed` persistence,
|
|
133
|
-
`OnboardingProvider`'s step-selection semantics, `stepSelection.js`.
|
|
134
|
-
- The four existing step components (`CookieConsentStep`, `CompleteNameStep`, `BrowserPushStep`,
|
|
135
|
-
`PwaInstallStep`) and their props.
|
|
136
|
-
- **NOTIF-13's realtime core** (`src/notifications/realtime.jsx`) — consume `useRealtime()` /
|
|
137
|
-
`subscribe(envelope, handler)`, do not restructure it. In particular leave the `seeded` gate, the
|
|
138
|
-
backoff/reconnect semantics and the envelope-routing alone. (`NotificationsProvider` itself IS
|
|
139
|
-
editable — scope D2 requires it.)
|
|
140
|
-
- `feed/*` REST contract, `NotificationBell`, `NotificationSettings`.
|
|
141
|
-
- `prefs.py`, `router.py`, `resolve_channels` — popup already works there; a type opting in is an
|
|
142
|
-
app-side change, not this WO's.
|
|
143
|
-
- Schema, migrations, dependencies, CI.
|
|
144
|
-
- **Do not register a `popup` producer in any app.** This WO ships the channel only.
|
|
145
|
-
|
|
146
|
-
## RISKS
|
|
147
|
-
- **Onboarding regression** is the main risk: the shell extraction touches a wizard that cockpit and jg
|
|
148
|
-
run in production. The step counter (`totalRef`/`completed`), the session-dismissed Set, and the
|
|
149
|
-
`blocking` escape-key behaviour are easy to break silently.
|
|
150
|
-
- **Stacked modals** if C is skipped — two dialogs, neither reachable.
|
|
151
|
-
- **Badge double-count** if D2 is skipped: a notification routed to both chip and popup arrives as two
|
|
152
|
-
WS messages and increments `unreadCount` twice for one notification. This is the single most likely
|
|
153
|
-
silent defect in this WO.
|
|
154
|
-
- **Popup storm** if B subscribes to the envelope without filtering on `channel` — every chip
|
|
155
|
-
notification would pop up a modal.
|
|
156
|
-
- **Regressing the NOTIF-13 R1 fix:** the `seeded` gate in `NotificationsProvider` exists because
|
|
157
|
-
decoupling the REST seed from the socket connect opened a silent-message-loss race. Do not touch it
|
|
158
|
-
while implementing D2.
|
|
159
|
-
- **Dead code rot:** with no producer, only the tests exercise the path. Hence the mandatory test-local
|
|
160
|
-
producer type.
|
|
161
|
-
- **Status semantics drift:** marking a popup `done` instead of `dismissed` would pollute todo-channel
|
|
162
|
-
semantics.
|
|
163
|
-
|
|
164
|
-
## REQUIRED TESTS / ACCEPTANCE
|
|
165
|
-
ucm (`pnpm test`, vitest):
|
|
166
|
-
- a popup-eligible notification renders in the shell; `markSeen` fires once on display;
|
|
167
|
-
- closing it fires `markDismissed`, never `markDone`;
|
|
168
|
-
- **coexistence:** with a blocking onboarding step active, no popup dialog renders; once it clears, the
|
|
169
|
-
popup appears — and never two dialogs at once;
|
|
170
|
-
- **onboarding regression guard:** existing `OnboardingWizard` behaviour unchanged — step counter,
|
|
171
|
-
progress bar, session-dismissed set, `blocking` disabling escape/backdrop close, `persistDismissed`
|
|
172
|
-
still calling `dismissStep`;
|
|
173
|
-
- with no popup-eligible notification present, nothing renders;
|
|
174
|
-
- **no popup storm:** a `channel: "chip"` message and a channel-less legacy message both leave the
|
|
175
|
-
popup surface silent;
|
|
176
|
-
- **no double-count (D2):** two WS messages for the same `notification_id` (one `chip`, one `popup`)
|
|
177
|
-
yield exactly one feed entry and `unreadCount + 1`;
|
|
178
|
-
- **NOTIF-13 R1 guard:** the `seeded` gate still holds — no socket messages are processed before the
|
|
179
|
-
REST seed completes.
|
|
180
|
-
|
|
181
|
-
dcm (`pytest` on the notifications package):
|
|
182
|
-
- register a **test-local** notification type declaring `eligible_channels=["popup"]`, call `notify()`,
|
|
183
|
-
and assert the dispatcher delivered (`ok=True`) with `envelope: "notification"` **and**
|
|
184
|
-
`channel: "popup"` on the wire — the end-to-end proof required by the scaffolding notice;
|
|
185
|
-
- a chip delivery still carries `envelope: "notification"` and now `channel: "chip"`, with every other
|
|
186
|
-
field unchanged (no regression for existing consumers);
|
|
187
|
-
- `resolve_channels` still excludes `popup` for types that do not declare it (no accidental widening);
|
|
188
|
-
- the existing `test_dispatch.py` registry assertion (`:25`) still passes.
|
|
189
|
-
|
|
190
|
-
Plus `pnpm build` (tsc) green.
|
|
191
|
-
|
|
192
|
-
## RELEASE
|
|
193
|
-
ucm minor bump + publish. dcm minor bump + publish (publish-from-main), with a **registry live-check
|
|
194
|
-
before any app pin bump**. No app pin bumps in this WO. CHANGELOG in both repos must carry the
|
|
195
|
-
inactive-until-a-producer-opts-in wording from the scaffolding notice.
|
|
196
|
-
|
|
197
|
-
## TARGET REPO / WORKING DIRECTORY
|
|
198
|
-
- Primary: `C:\Users\biglmi\Documents\webapps\ui-core-micha`
|
|
199
|
-
- Secondary (scope D): `C:\Users\biglmi\Documents\webapps\django-core-micha`
|
|
200
|
-
- Never the workspace root `…\webapps`.
|
|
201
|
-
Both are platform repos: commit directly to the trunk, no feature branches.
|
|
202
|
-
|
|
203
|
-
## PROGRESS CONTRACT
|
|
204
|
-
Emit a `PLAN: <step1> | <step2> | …` line up front, then a single-line
|
|
205
|
-
`PROGRESS: [<n>/<total>] <present-tense action>` **before every relevant action** (file opened, file
|
|
206
|
-
edited, command/test run) and `PROGRESS: [<n>/<total>] done` on step completion, spaced so no gap
|
|
207
|
-
exceeds ~2 min. stdout unbuffered. Exactly one final `RESULT: DONE|BLOCKED <reason>`.
|
|
208
|
-
|
|
209
|
-
## MINI-HANDOVER (paste into a fresh Orchestrator session)
|
|
210
|
-
```
|
|
211
|
-
Orchestrator: implement work-orders/NOTIF-12.md in ui-core-micha (main; scope D also touches
|
|
212
|
-
django-core-micha). The NOTIF-13 gate is satisfied (ucm 2.13.0 / dcm 2.33.0). git pull, read the WO,
|
|
213
|
-
then follow orchestrate-codex (Codex-first, own independent review, commit on green).
|
|
214
|
-
```
|