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