@micha.bigler/ui-core-micha 3.14.3 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -0
- package/README.md +7 -0
- package/dist/auth/UserMenu.js +1 -1
- package/dist/auth/apiClient.js +7 -6
- package/dist/chat/ChatList.js +1 -1
- package/dist/chat/ChatThread.js +2 -1
- package/dist/chat/ChatWorkspace.js +1 -1
- package/dist/chat/ModelPicker.js +2 -5
- package/dist/chat/kitSxRegistry.js +9 -0
- package/dist/chat/useChatSocket.js +1 -1
- package/dist/components/LoginForm.js +1 -1
- package/dist/components/SecurityComponent.js +1 -1
- package/dist/components/UserListComponent.js +2 -8
- package/dist/components/charts/ChartFrame.js +1 -1
- package/dist/components/charts/ScatterChart.js +2 -1
- 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/layout/SectionNav.js +1 -1
- package/dist/messaging/AttachmentList.js +2 -2
- package/dist/messaging/Composer.js +2 -1
- package/dist/messaging/ConversationLaunchers.js +1 -4
- package/dist/messaging/ConversationList.js +1 -1
- package/dist/messaging/DirectMessageLauncher.js +1 -1
- package/dist/messaging/MessagingProvider.js +0 -0
- package/dist/messaging/PollCard.js +7 -5
- package/dist/messaging/ReadTicks.js +2 -2
- package/dist/messaging/Thread.js +5 -3
- package/dist/notifications/NotificationBell.js +4 -7
- package/dist/notifications/NotificationSettings.js +1 -1
- package/dist/onboarding/OnboardingProvider.js +3 -6
- package/dist/pages/AccountPage.js +1 -2
- package/dist/pages/LoginPage.js +2 -3
- package/dist/pages/PasswordChangePage.js +2 -3
- package/dist/pages/PasswordInvitePage.js +2 -3
- package/dist/pages/PasswordResetRequestPage.js +2 -3
- package/dist/pages/SignUpPage.js +2 -3
- package/dist/pages/SignupConfirmPage.js +2 -3
- package/dist/theme/createAppTheme.js +5 -4
- package/dist/theme/kitSxEntry.js +11 -0
- package/dist/theme/kitSxRegistry.js +2 -15
- package/dist/theme/themeCompleteness.js +3 -12
- package/dist/utils/auth-errors.js +4 -3
- package/package.json +41 -16
- package/.github/workflows/publish.yml +0 -126
- package/DESIGN.md +0 -60
- package/WORK_ORDERS.md +0 -148
- 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-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-3e.md
DELETED
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
# MSG-3e — per-candidate scope in `DirectMessageLauncher`
|
|
2
|
-
|
|
3
|
-
Status: planned · Tier 2 · Target repo: `ui-core-micha` (main)
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## Part A — Envelope (2026-08-02)
|
|
8
|
-
|
|
9
|
-
### Goal
|
|
10
|
-
|
|
11
|
-
Let a host present **one flat direct-message picker whose candidates live in different messaging
|
|
12
|
-
scopes**. Today `DirectMessageLauncher` takes a single `scope` prop and applies it to every candidate,
|
|
13
|
-
so a host with people spread across several scopes must either render one launcher per scope or
|
|
14
|
-
re-implement the component.
|
|
15
|
-
|
|
16
|
-
### Why now
|
|
17
|
-
|
|
18
|
-
jg-ferien MSG-6 turns `/messages` into a global inbox across **all** of a user's events. In jg's model a
|
|
19
|
-
direct conversation is scoped to an event, so a flat "everyone I may DM" list carries a **different
|
|
20
|
-
scope per person** — the operator's explicit requirement ("DM über alle, Gruppen pro Event"). Without
|
|
21
|
-
this seam jg would have to copy ~90 lines of this component locally, which AGENTS.md's
|
|
22
|
-
reuse-before-reimplement rule exists to prevent. The need is generic: any host whose people span
|
|
23
|
-
multiple scopes hits it.
|
|
24
|
-
|
|
25
|
-
### Expected outcome
|
|
26
|
-
|
|
27
|
-
`candidates[n].scope` (optional) takes precedence over the `scope` prop **for the selected candidate
|
|
28
|
-
only**. Precisely:
|
|
29
|
-
|
|
30
|
-
| selected candidate's `scope` | `scope` prop | scope sent to `createDirectConversation` |
|
|
31
|
-
|---|---|---|
|
|
32
|
-
| present | present | the candidate's |
|
|
33
|
-
| present | absent | the candidate's |
|
|
34
|
-
| absent | present | the prop's (today's behaviour) |
|
|
35
|
-
| absent | absent | no `scope` key in the payload at all (today's behaviour) |
|
|
36
|
-
|
|
37
|
-
Strictly additive and backward compatible: a host that passes no per-candidate scope sees byte-identical
|
|
38
|
-
behaviour. Note the last row — the current code omits the key entirely rather than sending
|
|
39
|
-
`scope: null`, and that must not change; dcm distinguishes the two.
|
|
40
|
-
|
|
41
|
-
### Scope / non-goals
|
|
42
|
-
|
|
43
|
-
In scope: `src/messaging/DirectMessageLauncher.jsx` and its test file.
|
|
44
|
-
|
|
45
|
-
Explicit non-goals — do **not**:
|
|
46
|
-
- change the dialog's layout, copy, i18n keys, or a11y semantics;
|
|
47
|
-
- add grouping, sectioning, or per-candidate secondary text (a scope is not a label — jg groups its
|
|
48
|
-
*group* picker by event locally, and that stays a host concern);
|
|
49
|
-
- touch `MessagingProvider`'s `openDirectConversation` signature or any other component;
|
|
50
|
-
- validate or interpret the scope value — it is an opaque host-supplied identifier, passed through.
|
|
51
|
-
|
|
52
|
-
### Tier
|
|
53
|
-
|
|
54
|
-
Tier 2. Shared-core (`ui-core-micha`) is excluded from the Tier-1 fast path by AGENTS.md regardless of
|
|
55
|
-
diff size, so this one-line-of-logic change gets a WO, Codex-first, and a mandatory independent review.
|
|
56
|
-
|
|
57
|
-
### Tests to WRITE (scoped — run only these)
|
|
58
|
-
|
|
59
|
-
Extend `tests/messagingDirectMessageLauncher.test.jsx`:
|
|
60
|
-
|
|
61
|
-
1. **per-candidate scope wins** — candidates `[{id: 2, display_name: 'Alex', scope: 'scope-a'}, {id: 3,
|
|
62
|
-
display_name: 'Sam', scope: 'scope-b'}]`, launcher `scope="scope-prop"`; select Sam, start; assert
|
|
63
|
-
`createDirectConversation` called with `{target_user_id: 3, scope: 'scope-b'}`.
|
|
64
|
-
2. **fallback to the prop** (regression) — a candidate without its own `scope` in the same mixed list
|
|
65
|
-
still sends the prop's scope.
|
|
66
|
-
3. **neither present** — no `scope` key in the payload (`expect(Object.keys(payload)).toEqual(
|
|
67
|
-
['target_user_id'])` or equivalent — asserting absence, not `undefined`).
|
|
68
|
-
|
|
69
|
-
The three existing tests in that file must keep passing unchanged; they are the backward-compatibility
|
|
70
|
-
proof and must not be edited to accommodate the change.
|
|
71
|
-
|
|
72
|
-
Command: `pnpm vitest run tests/messagingDirectMessageLauncher.test.jsx`
|
|
73
|
-
|
|
74
|
-
### Release
|
|
75
|
-
|
|
76
|
-
One version bump `2.19.0` → **`2.20.0`** (minor: additive, backward-compatible) + npm publish at WO end.
|
|
77
|
-
jg-ferien MSG-6 pins this version, so the publish is the precondition for that WO's final test run.
|
|
78
|
-
Per `reference_dcm_ucm_publish_topology`: publish-from-main, and the target version must be verified
|
|
79
|
-
live on the registry before jg bumps its pin.
|
|
80
|
-
|
|
81
|
-
### Risks
|
|
82
|
-
|
|
83
|
-
Low. The one real trap is the "absent/absent" row above — a naive `scope: candidate.scope ?? scope`
|
|
84
|
-
inside the object literal would start sending `scope: undefined`, which serialises differently and
|
|
85
|
-
changes an existing contract. Build the payload conditionally, as the current code already does.
|
|
86
|
-
|
|
87
|
-
### Execution directive
|
|
88
|
-
|
|
89
|
-
**If you are the implementer reading this as your own spec, this section is not addressed to you — it
|
|
90
|
-
tells the Orchestrator how to invoke you. You ARE that invocation: do not shell out to `codex exec`.**
|
|
91
|
-
|
|
92
|
-
Orchestrator: implement through `codex exec` in the background, invoked **directly via Bash** (never the
|
|
93
|
-
`debugger`/`*_coder` Agent wrappers), with **both** flags `--skip-git-repo-check` and
|
|
94
|
-
`--dangerously-bypass-approvals-and-sandbox`, the WO passed as a positional argument from this committed
|
|
95
|
-
file. Fall back to direct Claude implementation only on Codex quota / rate-limit / non-zero exit; if the
|
|
96
|
-
fallback is used the Orchestrator becomes author and an independent `reviewer` is mandatory.
|
|
97
|
-
|
|
98
|
-
### Mini-handover (pastable)
|
|
99
|
-
|
|
100
|
-
Orchestrator: implement `work-orders/MSG-3e.md` in `ui-core-micha` (main). `git pull` first, read the WO,
|
|
101
|
-
then follow `orchestrate-codex` (Codex-first, own independent review, publish 2.20.0 at WO end).
|
|
102
|
-
|
|
103
|
-
---
|
|
104
|
-
|
|
105
|
-
## Part B — Implementation map (Orchestrator)
|
|
106
|
-
|
|
107
|
-
**Single chunk.** **Target repo working directory:** `C:\Users\Micha Bigler\Documents\webapps\ui-core-micha`
|
|
108
|
-
(repo root). Work from this package; open only the named files to verify.
|
|
109
|
-
|
|
110
|
-
### The one file that changes
|
|
111
|
-
|
|
112
|
-
`src/messaging/DirectMessageLauncher.jsx` — 96 lines, whole file is in scope but only `start()` and one
|
|
113
|
-
derivation change.
|
|
114
|
-
|
|
115
|
-
Current state (verified while scoping, do not re-derive):
|
|
116
|
-
|
|
117
|
-
- `:25` — `export function DirectMessageLauncher({ candidates = [], scope, onOpen })`.
|
|
118
|
-
- `:31` — the component tracks **only** `selectedId`, never the selected candidate object. A lookup is
|
|
119
|
-
therefore needed; mirror the pattern jg already uses in its own `AllGroupsLauncher`
|
|
120
|
-
(`jg-ferien/frontend/src/pages/messaging/AllGroupsLauncher.jsx:40-44`):
|
|
121
|
-
`const selectedCandidate = useMemo(() => candidates.find((c) => c.id === selectedId) || null,
|
|
122
|
-
[candidates, selectedId]);` — `useMemo` is not yet imported in this file (`:1` imports `useId, useState`).
|
|
123
|
-
- `:44-60` — `start()`; the payload is built at `:49-52`:
|
|
124
|
-
```js
|
|
125
|
-
const conversation = await openDirectConversation({
|
|
126
|
-
target_user_id: selectedId,
|
|
127
|
-
...(scope == null ? {} : { scope }),
|
|
128
|
-
});
|
|
129
|
-
```
|
|
130
|
-
This spread-or-nothing shape is the invariant the third test pins: when there is no scope the key is
|
|
131
|
-
**absent**, not `undefined`. Keep the shape; only the value fed into it changes:
|
|
132
|
-
`const effectiveScope = selectedCandidate?.scope ?? scope;` then `...(effectiveScope == null ? {} :
|
|
133
|
-
{ scope: effectiveScope })`. `??` (not `||`) so a falsy-but-valid scope id survives.
|
|
134
|
-
- `:75-82` — the `ListItemButton` render loop; **do not** change it. Candidate rendering already falls
|
|
135
|
-
back through `display_name || name || label || id` and the scope must not appear in the UI.
|
|
136
|
-
|
|
137
|
-
### Invariants / do-not-touch
|
|
138
|
-
|
|
139
|
-
- `extractApiErrorMessage` error path (`:55-59`) and the `starting` guard stay exactly as they are.
|
|
140
|
-
- `openDirectConversation` comes from `useMessaging()` (`:19, :27`) — its signature is dcm's contract,
|
|
141
|
-
unchanged.
|
|
142
|
-
- `src/index.js:72` already exports the component; no export change.
|
|
143
|
-
- No new i18n keys (`MessagingDirect.*` set is complete for this change).
|
|
144
|
-
|
|
145
|
-
### Docs
|
|
146
|
-
|
|
147
|
-
`docs/messaging-deviations.md` currently has zero BLOCKED entries (MSG-3d closed them) — this WO adds no
|
|
148
|
-
deviation and must not add a row. If the repo carries a prop-level API note for this component, update it;
|
|
149
|
-
if not, do not invent a docs file.
|
|
150
|
-
|
|
151
|
-
### Progress contract
|
|
152
|
-
|
|
153
|
-
Emit a `PLAN: …` line first, then a single-line `PROGRESS: [n/total] <action>` before every relevant
|
|
154
|
-
action and `… done` on completion, no gap > ~2 min, unbuffered stdout, and exactly one final
|
|
155
|
-
`RESULT: DONE|BLOCKED <reason>`.
|
|
156
|
-
|
|
157
|
-
### Constraints for the implementer
|
|
158
|
-
|
|
159
|
-
The WO text is the COMPLETE spec. Read `AGENTS.md` and `.codex/skills/frontend-engineering` only for
|
|
160
|
-
conventions. Stay in scope. Do not touch the Core-Behaviour approval surfaces. Do not update `MEMORY.md`.
|
|
161
|
-
Do **not** `git add/commit/push` — leave the diff for the Orchestrator's review. Do **not** bump the
|
|
162
|
-
version or publish — the Orchestrator owns the release. WRITE the three tests above and RUN ONLY that one
|
|
163
|
-
test file; no full suite, no self-review — the Orchestrator owns the authoritative run and the
|
|
164
|
-
independent review, and those are the gate.
|
package/work-orders/MSG-3f.md
DELETED
|
@@ -1,391 +0,0 @@
|
|
|
1
|
-
# MSG-3f — compact chat-bubble message layout
|
|
2
|
-
|
|
3
|
-
Status: planned · Tier 2 · Target repo: `ui-core-micha` (main)
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## Part A — Envelope (Expertenchat, 2026-08-02)
|
|
8
|
-
|
|
9
|
-
### Goal
|
|
10
|
-
|
|
11
|
-
Make a message occupy the space a message deserves. Today a one-word message renders as **six stacked
|
|
12
|
-
rows**; it must become **one**.
|
|
13
|
-
|
|
14
|
-
### The problem
|
|
15
|
-
|
|
16
|
-
`MessageBubble` (`src/messaging/MessageBubble.jsx`) wraps everything in a vertical
|
|
17
|
-
`Stack spacing={0.75}`, so every element gets its own full-width row: the meta line, the body, the
|
|
18
|
-
`ReactionBar`, the reply control (a full MUI `Button`, which is why "Antworten" sits centred on a line of
|
|
19
|
-
its own), the overflow-menu `IconButton`, and finally `children` — where the host's `ReadTicks` lands as
|
|
20
|
-
a "Zugestellt an 0" text line. For the word "Hallo" that is roughly 260px of vertical space.
|
|
21
|
-
|
|
22
|
-
Operator direction: much more compact, icon-based rather than text-labelled, and modelled on WhatsApp.
|
|
23
|
-
|
|
24
|
-
### "Like WhatsApp" — the actual specification
|
|
25
|
-
|
|
26
|
-
"Like WhatsApp" is not a spec, so here is what it means for this WO. Each point is a requirement.
|
|
27
|
-
|
|
28
|
-
**1. Bubbles.** Own messages right-aligned on a tinted surface; incoming messages left-aligned on a
|
|
29
|
-
neutral surface. The bubble shrinks to its content and is capped at roughly three quarters of the thread
|
|
30
|
-
width — not the current full-width outlined `Paper`. Exact radius, tint, and elevation are design-system
|
|
31
|
-
choices, not fixed here; what is fixed is the alignment split and the shrink-to-content behaviour.
|
|
32
|
-
|
|
33
|
-
**2. Meta moves inside the bubble — this is the line-count fix.** Timestamp and status icons sit
|
|
34
|
-
**inside** the bubble, bottom right, instead of being their own full-width sibling rows. Whether they
|
|
35
|
-
share the last text line or occupy their own narrow trailing line is left to the implementation:
|
|
36
|
-
riding the last line is nicer and welcome if it falls out naturally, but it is **explicitly not
|
|
37
|
-
required** (operator decision, 2026-08-02 — "muss nicht perfekt sein"). Do not spend the WO fighting
|
|
38
|
-
that CSS. What matters is that the meta is inside the bubble and right-aligned, not that it is inline.
|
|
39
|
-
|
|
40
|
-
**3. Sender name.** Only for non-direct conversations, small, above the text, inside the bubble. Direct
|
|
41
|
-
conversations show no sender at all (already the case today, keep it).
|
|
42
|
-
|
|
43
|
-
**4. Status as icons.** Replace the "Zugestellt an N" text with tick icons — sent / delivered /
|
|
44
|
-
all-read, distinguished by tick count and emphasis. Own messages only; the existing
|
|
45
|
-
`canShowReadTicks` gate is correct and stays.
|
|
46
|
-
|
|
47
|
-
**5. Actions collapse into one affordance.** No standalone reply button, no permanently rendered
|
|
48
|
-
reaction row. One overflow affordance at the bubble's top edge opens a single menu carrying Reply,
|
|
49
|
-
React, and the existing Edit / Delete / Copy entries.
|
|
50
|
-
|
|
51
|
-
How it is reached (operator decision, 2026-08-02 — **no long-press**):
|
|
52
|
-
- **Desktop:** the affordance appears on hover, and on keyboard focus. The existing right-click
|
|
53
|
-
`onContextMenu` path stays.
|
|
54
|
-
- **Touch:** a plain tap on the bubble reveals the affordance. No long-press, no swipe, no custom
|
|
55
|
-
pointer-event handling — the gesture edge cases (scroll conflict, text selection, iOS's own
|
|
56
|
-
context menu) are not worth their cost here.
|
|
57
|
-
- **React** is a menu entry that expands the existing `ReactionBar` emoji row. Do **not** try to nest an
|
|
58
|
-
emoji picker inside the MUI `Menu` — MUI does not nest menus gracefully, and the existing expandable
|
|
59
|
-
row already works.
|
|
60
|
-
|
|
61
|
-
**5b. The menu must open for messages the user cannot edit.** Today the whole menu is gated on `canAct`
|
|
62
|
-
(own message, or moderator), because it only contained Edit / Delete / Copy. Once Reply lives there, an
|
|
63
|
-
**incoming** message needs a menu too — you reply to other people's messages. So there are two variants:
|
|
64
|
-
any message offers Reply / React / Copy; a message the user may act on additionally offers Edit /
|
|
65
|
-
Delete. Getting this wrong makes replying to anyone else impossible, and it will not show up in a test
|
|
66
|
-
fixture that only contains own messages.
|
|
67
|
-
|
|
68
|
-
**6. Reactions.** Existing reaction chips render as a compact row overlapping the bubble's bottom edge,
|
|
69
|
-
not as their own stacked row. Adding a reaction moves into the action menu.
|
|
70
|
-
|
|
71
|
-
**7. Reply quote.** Stays inside the bubble as a tinted block with a coloured leading border, replacing
|
|
72
|
-
today's full-width text `Button`. Clicking it must still jump to the quoted message.
|
|
73
|
-
|
|
74
|
-
### Confirmed against operator-supplied reference screenshots (2026-08-02)
|
|
75
|
-
|
|
76
|
-
The operator supplied two WhatsApp screenshots as the reference. What they pin down beyond the points
|
|
77
|
-
above:
|
|
78
|
-
|
|
79
|
-
- In the reference, a two-line message has its timestamp at the right end of the *second* line — so
|
|
80
|
-
WhatsApp's own rule is "meta rides the final line". Per point 2 this is **not** binding here; it is
|
|
81
|
-
recorded so the implementation knows what the ideal looks like if it comes cheaply.
|
|
82
|
-
- **Incoming messages carry no status icon at all** — not even a "sent" tick. Only own messages show
|
|
83
|
-
status.
|
|
84
|
-
- **At rest there is no visible action affordance whatsoever.** No reply control, no reaction button, no
|
|
85
|
-
overflow glyph. The bubble is text plus meta and nothing else until the user hovers or long-presses.
|
|
86
|
-
- **The meta is visually recessed**: noticeably smaller than the body and in a lower-contrast grey; the
|
|
87
|
-
read state is carried by colour on the ticks, not by the timestamp.
|
|
88
|
-
- Bubbles shrink to content — the short incoming message's bubble is markedly narrower than the long
|
|
89
|
-
one's, confirming that the width cap is a maximum and not a fixed width.
|
|
90
|
-
|
|
91
|
-
A bubble tail/notch is visible in the reference. Treat it as optional design-system garnish, not a
|
|
92
|
-
requirement of this WO.
|
|
93
|
-
|
|
94
|
-
### Acceptance criterion
|
|
95
|
-
|
|
96
|
-
A message with short text, no reactions, no attachments and no reply quote occupies **at most two visual
|
|
97
|
-
lines** — the text and, at most, a narrow right-aligned meta line inside the bubble. One line if the meta
|
|
98
|
-
lands inline without a fight. Today's six rows for that case is what this WO exists to kill; anything
|
|
99
|
-
still costing a third line has not met it.
|
|
100
|
-
|
|
101
|
-
### Non-negotiable: the icons must keep their accessible names
|
|
102
|
-
|
|
103
|
-
Replacing text with icons is exactly how a UI becomes unusable with a screen reader. Every icon
|
|
104
|
-
introduced by this WO carries the text it replaced as its accessible name — the status icons keep the
|
|
105
|
-
existing `MessagingReadTicks.DELIVERED` / `ALL_READ` strings as `aria-label`, the action affordance keeps
|
|
106
|
-
a label, and no i18n key is deleted because its text stopped being displayed. A visually compact UI that
|
|
107
|
-
announces "button" to a screen reader is a regression, not a redesign.
|
|
108
|
-
|
|
109
|
-
Second, related trap: today's overflow menu is hover-only (`'& .message-actions': { opacity: 0 }`), which
|
|
110
|
-
makes it unreachable on touch **and** invisible to keyboard users. This WO must not carry that forward.
|
|
111
|
-
The affordance must be focusable and operable by keyboard. Note that `opacity: 0` does **not** remove an
|
|
112
|
-
element from the tab order — it is only invisible — so a `:focus-within` (or `:focus-visible`) rule that
|
|
113
|
-
reveals it on keyboard focus closes the keyboard half cheaply. The touch half is point 5's tap-to-reveal.
|
|
114
|
-
That this is a pre-existing defect does not make it acceptable to reproduce.
|
|
115
|
-
|
|
116
|
-
### Scope
|
|
117
|
-
|
|
118
|
-
`src/messaging/MessageBubble.jsx` primarily; `src/messaging/ReadTicks.jsx` (text → icon),
|
|
119
|
-
`src/messaging/ReactionBar.jsx` (compact placement, add-reaction moves into the menu), and the small part
|
|
120
|
-
of `src/messaging/Thread.jsx` that positions bubbles and passes `children`. Their tests, the messaging
|
|
121
|
-
i18n table if new labels are genuinely needed, and the `dev/` harness entry so the result can actually be
|
|
122
|
-
looked at.
|
|
123
|
-
|
|
124
|
-
### Non-goals / do-not-touch
|
|
125
|
-
|
|
126
|
-
- **No custom touch gestures at all** — no swipe-to-reply and, per point 5, **no long-press**. Both need
|
|
127
|
-
hand-rolled pointer handling whose real cost is the edge cases (scroll conflict, text selection, iOS's
|
|
128
|
-
own context menu), and neither is needed for "compact". Operator decision 2026-08-02. If either is
|
|
129
|
-
wanted later it is its own WO.
|
|
130
|
-
- **No Composer change.** The screenshot that triggered this also shows a tall composer, but that is not
|
|
131
|
-
what was asked. Raise it separately rather than folding it in.
|
|
132
|
-
- **No change to `Thread`'s data flow**, pagination, reply-thread expansion, unread-reply marker
|
|
133
|
-
(MSG-3d), or the read-status contract.
|
|
134
|
-
- **No dcm change.** See the read-status note below.
|
|
135
|
-
- **No change to what `MessageBubble` accepts or renders as `children`** — hosts pass `ReadTicks` through
|
|
136
|
-
it today and must keep working.
|
|
137
|
-
- No new dependency. MUI plus the existing icon set is enough.
|
|
138
|
-
|
|
139
|
-
### Read status — an observation, deliberately NOT a precondition
|
|
140
|
-
|
|
141
|
-
`ReadTicks` fetches per message (`getMessageReadStatus` in a `useEffect`), and dcm's `serialize_message`
|
|
142
|
-
carries no read state, so a thread fires one REST call per own message. Tick icons make that fan-out more
|
|
143
|
-
visible but do **not** worsen it: `canShowReadTicks` already bounds it to own messages, and the code
|
|
144
|
-
comments that bound as deliberate. Putting the delivered/read aggregate onto `serialize_message` — the
|
|
145
|
-
way `reply_count`/`last_reply_at` already ride it since MSG-3d, and viewer-independent in the same way —
|
|
146
|
-
would remove the fan-out entirely. That is a worthwhile dcm follow-up and should be raised as one, but
|
|
147
|
-
gating a UI fix behind a dcm release is the wrong trade. **Do not implement it here; do report it.**
|
|
148
|
-
|
|
149
|
-
### Tier
|
|
150
|
-
|
|
151
|
-
**Tier 2.** Shared-core, and this is an opinionated visual decision imposed on every future consumer of
|
|
152
|
-
ucm's messaging surface — see the cross-consumer note. Codex-first, mandatory independent review, plus a
|
|
153
|
-
`ui_reviewer` pass: this WO is entirely about how something looks and the harness exists to look at it.
|
|
154
|
-
|
|
155
|
-
### Cross-consumer note
|
|
156
|
-
|
|
157
|
-
ucm is consumer-agnostic by design, and this WO commits it to a specific chat aesthetic. That is
|
|
158
|
-
acceptable **now** because jg-ferien is currently the only messaging consumer (spesix is deferred per the
|
|
159
|
-
messaging-centralization course correction), so the design lands with one adopter rather than being
|
|
160
|
-
retrofitted onto several. Say so in the register row rather than letting a future consumer discover it.
|
|
161
|
-
What must **not** happen is jg-specific vocabulary or layout assumptions leaking into the package — the
|
|
162
|
-
same line MSG-3d drew for `external_key` applies here.
|
|
163
|
-
|
|
164
|
-
### Risks
|
|
165
|
-
|
|
166
|
-
- **Do not get drawn into the inline-meta CSS.** Placing timestamp and ticks on the last text line, and
|
|
167
|
-
wrapping cleanly when the text ends near the edge, is a known-awkward problem needing a reserved inline
|
|
168
|
-
spacer or a floated meta element. Point 2 deliberately does not require it. If a first attempt does not
|
|
169
|
-
fall out cleanly, take the trailing meta line and move on — this risk is listed to be avoided, not
|
|
170
|
-
managed.
|
|
171
|
-
- **Long text, long words, RTL and zoom.** A shrink-to-content bubble with a right-aligned meta
|
|
172
|
-
breaks in interesting ways with a single unbroken 60-character token, at 200% zoom, and in narrow
|
|
173
|
-
mobile widths. Check those three explicitly.
|
|
174
|
-
- **Losing an action.** Reply, React, Edit, Delete and Copy all currently exist. Collapsing them into one
|
|
175
|
-
menu must not quietly drop one — particularly React, which is moving from a permanent control into the
|
|
176
|
-
menu and is the easiest to forget.
|
|
177
|
-
- **Announcements and polls.** `MessageBubble` also renders announcement titles, announcement link
|
|
178
|
-
buttons, poll cards and attachment lists. A bubble redesign that only considers plain text will make
|
|
179
|
-
those look broken. Each needs a look in the harness.
|
|
180
|
-
|
|
181
|
-
### Tests to WRITE (scoped — run only these)
|
|
182
|
-
|
|
183
|
-
- a short own message renders its timestamp and status **inside** the bubble, not as a sibling row (the
|
|
184
|
-
acceptance criterion, asserted structurally rather than by pixel — do not assert that the meta shares
|
|
185
|
-
the text's line, which point 2 leaves open);
|
|
186
|
-
- the action menu opens for an **incoming** message and offers Reply, and offers Edit/Delete only where
|
|
187
|
-
`canAct` holds (point 5b — a fixture containing only own messages will not catch this);
|
|
188
|
-
- the affordance is reachable on touch without a long press;
|
|
189
|
-
- no standalone reply button and no permanently rendered reaction row exist in the DOM for a plain
|
|
190
|
-
message;
|
|
191
|
-
- own and incoming messages are distinguishable (alignment/styling hook), and the sender name renders for
|
|
192
|
-
a group conversation but not a direct one;
|
|
193
|
-
- the status icon exposes the same accessible name the text label used, and appears only on own messages;
|
|
194
|
-
- the action affordance is reachable and operable by keyboard alone — no hover, no long press;
|
|
195
|
-
- the action menu contains Reply, React, Edit, Delete and Copy for a message the user may act on, and the
|
|
196
|
-
reply quote still triggers `onJumpToMessage`;
|
|
197
|
-
- an announcement, a poll and an attachment message each still render their existing content inside the
|
|
198
|
-
new bubble.
|
|
199
|
-
|
|
200
|
-
Command: `pnpm vitest run tests/Thread.test.jsx tests/messagingMessageActions.test.jsx tests/messagingInteractions.test.jsx tests/messagingPollRendering.test.jsx` plus any new file. The existing messaging tests are the regression net — if several of them need rewriting, that is a signal the change went wider than the envelope, and it should be reported before proceeding.
|
|
201
|
-
|
|
202
|
-
### Release
|
|
203
|
-
|
|
204
|
-
One version bump + npm publish at WO end. jg-ferien bumps its pin afterwards to pick the design up; that
|
|
205
|
-
pin bump is not part of this WO.
|
|
206
|
-
|
|
207
|
-
### Preconditions
|
|
208
|
-
|
|
209
|
-
None. Independent of `MSG-3e` (per-candidate scope in `DirectMessageLauncher`) — different files, either
|
|
210
|
-
order, but if both are in flight coordinate the version bumps so two releases don't collide.
|
|
211
|
-
|
|
212
|
-
### Approval Gate #1
|
|
213
|
-
|
|
214
|
-
Granted by the operator 2026-08-02: compact, icon-based, chat bubbles, WhatsApp as the model, across all
|
|
215
|
-
three design questions asked.
|
|
216
|
-
|
|
217
|
-
### Mini-handover (pastable)
|
|
218
|
-
|
|
219
|
-
Orchestrator: implement `work-orders/MSG-3f.md` in `ui-core-micha` (main). `git pull` first, read the WO,
|
|
220
|
-
then follow `orchestrate-codex` (write Part B yourself, Codex-first, own independent review plus a
|
|
221
|
-
`ui_reviewer` pass on the harness, publish at WO end). **The acceptance criterion is one visual line for a
|
|
222
|
-
short message — check it before declaring done.**
|
|
223
|
-
|
|
224
|
-
---
|
|
225
|
-
|
|
226
|
-
## Part B — Implementation map (Orchestrator)
|
|
227
|
-
|
|
228
|
-
**Target repo working directory:** `C:\Users\Micha Bigler\Documents\webapps\ui-core-micha` (repo root; main
|
|
229
|
-
branch). Work from this package; open only the named files to verify.
|
|
230
|
-
|
|
231
|
-
### Current shape (verified while scoping — do not re-derive)
|
|
232
|
-
|
|
233
|
-
**`src/messaging/MessageBubble.jsx`** (105 lines) — one `Paper` (`component="article"`, `:66`) wrapping a
|
|
234
|
-
single `Stack spacing={0.75}` (`:67`) with, in row order: reply-quote `Button` (`:68-73`, only if `replyTo`),
|
|
235
|
-
meta row — sender name (`conversation?.kind !== 'direct'`, `:75`) + `created_at` + "edited" caption
|
|
236
|
-
(`:74-78`), announcement title/link (`:79-80`), error `Alert` (`:81`), body/edit-`TextField` (`:82`),
|
|
237
|
-
`!deleted` wrapper with `AttachmentList` + `<ReactionBar message={message} />` + `PollCard` (`:83-87`),
|
|
238
|
-
standalone "Antworten" reply `Button` (`:88`, **not** inside the existing `Menu` — reply is currently
|
|
239
|
-
ungated by `canAct`, unlike edit/delete/copy), overflow `IconButton` (`:89`, `className="message-actions"`,
|
|
240
|
-
`canAct`-gated), `{children}` (`:90` — this is where `Thread.jsx` injects `<ReadTicks>`, currently its own
|
|
241
|
-
trailing row, not "inside the bubble"), then the edit/delete/copy `Menu` (`:92-96`) and delete-confirm
|
|
242
|
-
`Dialog` (`:97-101`).
|
|
243
|
-
|
|
244
|
-
- `canAct` (`:35-37`): `!deleted && message.kind !== 'poll' && ((Boolean(currentUser?.id) &&
|
|
245
|
-
message.sender?.id === currentUser?.id) || canModerateMessages)` — gates both `onContextMenu` (`:66`) and
|
|
246
|
-
the overflow `IconButton`. **No existing `isOwn`/alignment boolean** — the own-message check is inlined
|
|
247
|
-
into `canAct` only; introduce a named `isOwn` for the alignment split (same condition, without the
|
|
248
|
-
`!deleted`/`kind !== 'poll'` parts that are specific to action-gating, not alignment).
|
|
249
|
-
- Hover-only CSS trap (`:66`, inline `sx` on the `Paper`): `'& .message-actions': { opacity: 0 },
|
|
250
|
-
'&:hover .message-actions': { opacity: 1 }` — pure `:hover`, no `:focus-within`/`:focus-visible` variant,
|
|
251
|
-
no touch/tap path. This is the exact defect the WO's "Non-negotiable" section calls out — fix it here
|
|
252
|
-
(add a focus-visible rule; touch is a tap-to-reveal state, not CSS-only).
|
|
253
|
-
- `Thread.jsx:103` and `:113` are the two `<MessageBubble>` mount sites (root messages and nested replies
|
|
254
|
-
respectively), both passing `children={canShowReadTicks(message, user) && <ReadTicks .../>}`.
|
|
255
|
-
`canShowReadTicks` (`Thread.jsx:25-27`) duplicates (more strictly — adds a pending-optimistic-row guard)
|
|
256
|
-
the same own-message check as `canAct` — a third near-duplicate inline check. Consider factoring these
|
|
257
|
-
three (MessageBubble's `canAct`, Thread's `canShowReadTicks`, the new alignment `isOwn`) toward one
|
|
258
|
-
shared own-message predicate if it falls out cleanly; not required if it adds risk late in the WO.
|
|
259
|
-
- Direct-vs-group signal: `conversation?.kind !== 'direct'`, exactly this string comparison, reused
|
|
260
|
-
identically at `MessageBubble.jsx:75` and `ReadTicks.jsx:21` — no enum, just keep using it.
|
|
261
|
-
|
|
262
|
-
**`src/messaging/ReadTicks.jsx`** (25 lines) — fetch-per-message `useEffect` (`:13-17`,
|
|
263
|
-
`getMessageReadStatus(messageId)` from `useMessaging()`). Only **two** states exist in the data today:
|
|
264
|
-
`status.all_read` (bool) vs `status.delivered_count` (number) — **there is no third "sent, not yet
|
|
265
|
-
delivered" state** in the current contract; do not invent one that the backend can't back. Map the two
|
|
266
|
-
existing states to icon variants (e.g. a single check for "delivered", a double/colored check for
|
|
267
|
-
"all read") — do not add a third tick state without a data source. `status.recipient_detail` (only present
|
|
268
|
-
for non-direct conversations, `:21` comment already documents the DM carve-out) drives the existing
|
|
269
|
-
clickable popover of names (`:24`) — keep that behavior, just change what triggers it from a text caption to
|
|
270
|
-
an icon (`aria-haspopup="dialog"` must move with it). i18n keys already exist and are reusable as
|
|
271
|
-
`aria-label`s: `MessagingReadTicks.ALL_READ`, `.DELIVERED` (`{{count}}`), `.RECIPIENTS`.
|
|
272
|
-
|
|
273
|
-
**`src/messaging/ReactionBar.jsx`** (47 lines) — currently its own sibling row (`Stack`, `:32`), mounted
|
|
274
|
-
inside MessageBubble's `!deleted` wrapper (`MessageBubble.jsx:85`). **Add-reaction already exists and is
|
|
275
|
-
reusable as-is**: `:35`, an `IconButton` (`AddReactionOutlinedIcon`, `aria-label={t('MessagingReactions.ADD')}`)
|
|
276
|
-
toggling local `expanded` state; when expanded, `:37-43` renders `QUICK_EMOJIS`
|
|
277
|
-
(`['👍','❤️','😂','🎉','👀']`, `:8`) as `Chip`s — this is "the existing expandable row" the WO says already
|
|
278
|
-
works; reuse it verbatim inside the action menu's React entry, don't rebuild it. `ReactionBar` takes only
|
|
279
|
-
`message` as a prop today — no position/z-index hook back to the parent; if overlapping the bubble's bottom
|
|
280
|
-
edge needs coordination (e.g. negative margin colliding with bubble padding), that's a MessageBubble-side
|
|
281
|
-
CSS concern, only touch `ReactionBar`'s own layout if the compact-row placement itself needs to change (it
|
|
282
|
-
likely does — from a full sibling row to a smaller overlapping one).
|
|
283
|
-
|
|
284
|
-
### The new bubble shape — map the seams, not the CSS
|
|
285
|
-
|
|
286
|
-
This WO is explicitly NOT asking for the inline-meta-shares-last-line CSS to be solved (Risks section, Part
|
|
287
|
-
A) — implement the straightforward version (meta as its own narrow trailing line inside the bubble,
|
|
288
|
-
right-aligned) and stop; do not spend cycles chasing the inline variant.
|
|
289
|
-
|
|
290
|
-
1. **Alignment split**: introduce `isOwn` (own-message boolean, computed once, see above). Own → bubble
|
|
291
|
-
right-aligned on a tinted `Paper`; incoming → left-aligned, neutral surface. Shrink-to-content, capped at
|
|
292
|
-
~75% of thread width (today's `maxWidth: 'min(100%, 680px)'`, `:66`, is a fixed cap regardless of
|
|
293
|
-
alignment — replace with a percentage-based cap and keep `min(...)` against a sane absolute ceiling for
|
|
294
|
-
very wide threads).
|
|
295
|
-
2. **Meta moves inside**: move the timestamp + status-tick block from its own row into a small trailing
|
|
296
|
-
`Stack` inside the bubble's content area, right-aligned, "recessed" styling (smaller, lower-contrast —
|
|
297
|
-
Part A's confirmed-against-screenshots section). Sender name stays where it is conceptually (above text,
|
|
298
|
-
inside the bubble) but only for `conversation?.kind !== 'direct'` (unchanged condition).
|
|
299
|
-
3. **Status as icons**: `ReadTicks.jsx` swaps its two `Typography` text branches for icon components
|
|
300
|
-
(`Done`/`DoneAll` from `@mui/icons-material`, following the repo's `Outlined`-variant convention where one
|
|
301
|
-
exists — check availability before picking exact icon names) with the existing i18n strings as
|
|
302
|
-
`aria-label`, own-messages-only (unchanged `canShowReadTicks` gate in `Thread.jsx`, do not touch it).
|
|
303
|
-
4. **Actions collapse into one menu**: move the standalone reply `Button` (`:88`) INTO the existing `Menu`
|
|
304
|
-
(`:92-96`) as a new first entry, alongside React (opens `ReactionBar`'s existing `expanded` row — do not
|
|
305
|
-
nest an emoji picker inside the MUI `Menu`, per Part A) and the existing Edit/Delete/Copy entries. The
|
|
306
|
-
overflow `IconButton` (`:89`) becomes the single affordance — reuse the existing `MoreVertIcon` (already
|
|
307
|
-
the established overflow icon in this repo, `ConversationList.jsx` too), don't introduce a different one.
|
|
308
|
-
5. **Two menu variants (5b — the easy-to-miss one)**: any message (own or incoming) gets Reply + React +
|
|
309
|
-
Copy; only a message where `canAct`-minus-the-reply-specific-parts holds (i.e. own message or moderator,
|
|
310
|
-
the existing author/moderator check) additionally gets Edit + Delete. This means the menu's visibility
|
|
311
|
-
condition changes from "`canAct` gates the whole menu" to "the menu is always offered when there's
|
|
312
|
-
anything to put in it (any message), but its CONTENTS vary" — restructure the render condition
|
|
313
|
-
accordingly; a fixture with only own messages will not catch a regression here (Part A's own warning).
|
|
314
|
-
6. **Desktop hover+focus, touch tap**: fix the CSS trap directly — add a `&:focus-within .message-actions`
|
|
315
|
-
(or `:focus-visible`) rule alongside the existing `:hover` rule so keyboard Tab-focus reveals the
|
|
316
|
-
affordance (the element is already in the tab order, per Part A's analysis — `opacity:0` doesn't remove
|
|
317
|
-
it). For touch, add a tap-to-reveal state (e.g. local `actionsVisible` state toggled by a tap handler on
|
|
318
|
-
the bubble, independent of hover/focus) — no long-press, no swipe, no custom pointer-event tracking.
|
|
319
|
-
7. **Reactions compact row**: `ReactionBar`'s existing-reactions row (not the add-reaction affordance, which
|
|
320
|
-
moves into the menu per point 4) becomes a compact row overlapping the bubble's bottom edge rather than a
|
|
321
|
-
full sibling Stack row — likely a negative top margin pulling it up into the bubble's bottom padding, or
|
|
322
|
-
an absolutely-positioned row anchored to the bubble. `ReactionBar`'s own `expanded`/`QUICK_EMOJIS` block
|
|
323
|
-
stays as-is; only its trigger changes (moves from `ReactionBar`'s own `AddReactionOutlinedIcon` button —
|
|
324
|
-
now redundant, since React lives in the menu — but the `expanded` row itself is unchanged).
|
|
325
|
-
8. **Reply quote**: keep inside the bubble but restyle from a full-width `Button` (`:68-73`) to a tinted
|
|
326
|
-
block with a coloured leading border (e.g. `borderLeft`) — must still call `onJumpToMessage` on click
|
|
327
|
-
(existing prop, keep the click handler, only restyle the container).
|
|
328
|
-
9. **Announcements/polls/attachments** (`:79-80`, `:83-87`): keep rendering inside the new bubble container;
|
|
329
|
-
check each in the harness (Part A's Risks section) since none of the four scoped test files assert on
|
|
330
|
-
their visual placement, only their data/behavior.
|
|
331
|
-
|
|
332
|
-
### Verification harness — extend, don't just eyeball the diff
|
|
333
|
-
|
|
334
|
-
`dev/entries.jsx:140-157` already has a `messaging-message-bubble` entry, but it mounts a single bubble with
|
|
335
|
-
no `conversation` prop, no `currentUser` (so `canAct` is always false — no own-message demo), and no
|
|
336
|
-
`replyTo`/`children`/ReadTicks wired in — **insufficient to visually verify this WO's acceptance criterion**.
|
|
337
|
-
Either extend this entry with a currentUser matching one message's `sender.id` (for an own-message demo)
|
|
338
|
-
plus a second incoming-message instance, a `replyTo` prop, and mock `ReadTicks` children — or, likely
|
|
339
|
-
cleaner, extend the existing `messaging-thread` entry (`dev/entries.jsx:156`, `ThreadEntry` against
|
|
340
|
-
`messagingHarnessApi`) which already exercises the real Thread→MessageBubble wiring with realistic
|
|
341
|
-
`sender`/`reply_count`/replies data. Add fixture messages covering: a short own message (acceptance
|
|
342
|
-
criterion — one/two lines), a short incoming message, a long-single-unbroken-token message, a message at
|
|
343
|
-
200% zoom (use the harness's viewport toggle), a group-conversation message (sender name visible) vs a
|
|
344
|
-
direct one (no sender name), an announcement, a poll, and an attachment. This is the actual verification
|
|
345
|
-
step — visually confirm the acceptance criterion before reporting `RESULT: DONE`.
|
|
346
|
-
|
|
347
|
-
### Invariants / do-not-touch
|
|
348
|
-
|
|
349
|
-
- No Composer change (a different component, out of scope even though the triggering screenshot showed one).
|
|
350
|
-
- No change to `Thread.jsx`'s data flow, pagination, reply-thread expansion, unread-reply marker (MSG-3d),
|
|
351
|
-
or the read-status REST contract (`getMessageReadStatus` shape stays exactly as-is — two states, no new
|
|
352
|
-
backend call).
|
|
353
|
-
- No dcm change — the fan-out observation (one `getMessageReadStatus` call per own message) is a known,
|
|
354
|
-
pre-existing, and deliberately bounded cost; report the read-aggregate-on-`serialize_message` idea as a
|
|
355
|
-
dcm follow-up suggestion in the WO's own docs, do not implement it.
|
|
356
|
-
- `MessageBubble`'s `children` contract stays: hosts (jg-ferien today) pass `ReadTicks` through it and must
|
|
357
|
-
keep working unchanged from the outside.
|
|
358
|
-
- No new dependency — MUI plus `@mui/icons-material` is enough (both already used throughout `src/messaging/`).
|
|
359
|
-
- Every icon this WO introduces keeps its text as an `aria-label` — no i18n key gets deleted because its
|
|
360
|
-
text stopped being visually displayed (Part A's "Non-negotiable" section).
|
|
361
|
-
|
|
362
|
-
### Tests — see Part A's "Tests to WRITE" list verbatim
|
|
363
|
-
|
|
364
|
-
Run: `pnpm vitest run tests/Thread.test.jsx tests/messagingMessageActions.test.jsx
|
|
365
|
-
tests/messagingInteractions.test.jsx tests/messagingPollRendering.test.jsx` plus any new file for the new
|
|
366
|
-
assertions (own/incoming alignment, menu-for-incoming-message, keyboard/touch affordance reachability,
|
|
367
|
-
icon aria-labels). These four existing files assert on data/behavior (menu gating, reaction round-trips,
|
|
368
|
-
poll rendering, ReadTicks own-message-only mounting), not DOM layout — good news for a redesign, but per
|
|
369
|
-
Part A: **if several existing tests need rewriting (not just relocating an assertion), that's a signal the
|
|
370
|
-
change went wider than the envelope — stop and report before proceeding**, don't just patch tests to match
|
|
371
|
-
whatever the implementation produced.
|
|
372
|
-
|
|
373
|
-
### Progress contract
|
|
374
|
-
|
|
375
|
-
Emit a `PLAN: …` line first, then a single-line `PROGRESS: [n/total] <action>` before every relevant action
|
|
376
|
-
and `… done` on completion, no gap > ~2 min, unbuffered stdout, and exactly one final
|
|
377
|
-
`RESULT: DONE|BLOCKED <reason>`.
|
|
378
|
-
|
|
379
|
-
### Constraints for the implementer
|
|
380
|
-
|
|
381
|
-
The WO text (Part A + this Part B) is the COMPLETE spec. Read `AGENTS.md` and
|
|
382
|
-
`.codex/skills/frontend-engineering` only for conventions. Stay in scope: `MessageBubble.jsx`,
|
|
383
|
-
`ReadTicks.jsx`, `ReactionBar.jsx`, the small `Thread.jsx` slice that mounts them, their tests, the messaging
|
|
384
|
-
i18n file if new labels are genuinely needed, and the `dev/` harness entry. Do not touch `Composer.jsx`,
|
|
385
|
-
`ConversationList.jsx`, poll/attachment components' internals, or anything outside this list. Do not update
|
|
386
|
-
`MEMORY.md`. **Do NOT `git add`/`commit`/`push`** — leave the diff for the Orchestrator's review; a prior WO
|
|
387
|
-
in this campaign (`ui-core-micha` MSG-3e) had its implementer self-commit and self-publish against this same
|
|
388
|
-
instruction — that was a governance breach, not a template to follow. **Do NOT bump the version or
|
|
389
|
-
publish** — the Orchestrator owns the release. WRITE the tests specified in Part A and RUN ONLY the listed
|
|
390
|
-
files plus any new ones — no full suite, no self-review; the Orchestrator owns the authoritative run, the
|
|
391
|
-
independent review, AND the `ui_reviewer` harness pass, and those are the gate.
|