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