@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,244 +0,0 @@
1
- # WORK ORDER MSG-6g (ui-core-micha) — poll UI: tap-to-vote with result bars, no separate vote button
2
-
3
- **EXECUTION DIRECTIVE.** If you are the implementer reading this as your own spec, this section is not
4
- addressed to you — it tells the Orchestrator how to invoke you; you ARE that invocation, do not shell
5
- out to `codex exec`. Orchestrator: implement through `codex exec` in the background, invoked **directly
6
- via Bash** (never the `debugger`/`*_coder` Agent wrappers) with **both** flags `--skip-git-repo-check`
7
- and `--dangerously-bypass-approvals-and-sandbox`, prompt passed as a positional argument from this
8
- file. Fall back to direct Claude implementation only on Codex quota / rate-limit / non-zero exit.
9
-
10
- Feature-level WO (Expertenchat envelope). Operator request 2026-08-03: *"bei der Abstimmung wäre es
11
- schön, wenn es ähnlicher wie WhatsApp mit dem Balken sowie ohne Abstimmungsbutton möglich wäre."*
12
-
13
- This is a **UX improvement, not a defect** — the current poll works. It is scoped separately so it never
14
- blocks the messaging defect WOs.
15
-
16
- ## TIER
17
- Tier 2 — shared-core UI consumed by every app. Independent `reviewer` + `ui_reviewer` mandatory.
18
-
19
- ## CURRENT STATE
20
-
21
- `PollCard.jsx` renders each option as a MUI `FormControlLabel` with a `Checkbox` (multi-select) or radio,
22
- labelled `"<text> (<vote_count>)"`, plus a separate **"Abstimmen"** submit button and a
23
- **"Umfrage schliessen"** action for the owner. Voting is therefore a two-step interaction — select, then
24
- submit — and the result is a bare number in parentheses.
25
-
26
- ## GOAL
27
-
28
- Voting is one tap on the option itself, and the distribution is readable at a glance from proportional
29
- bars rather than parenthesised counts.
30
-
31
- ## SCOPE
32
-
33
- **A. Tap-to-vote — remove the separate submit button.**
34
-
35
- - **Single-choice poll** (`allow_multiple: false`): tapping an option casts the vote immediately.
36
- Tapping a different option moves the vote. The "Abstimmen" button goes away entirely.
37
- - **Multi-select poll** (`allow_multiple: true`): tapping an option toggles that option immediately —
38
- each tap is its own vote update, not a batched submit. **Confirm dcm's vote endpoint accepts the full
39
- option-id set per call** (it takes `option_ids`, so a toggle sends the new complete set, not a delta);
40
- if it does not, stop and report rather than inventing a delta protocol.
41
- - Every tap is an optimistic UI update reconciled against the server response, following the pattern
42
- `MessagingProvider` already uses for sends. **A failed vote must visibly revert** — a silently
43
- swallowed failure that leaves the bar showing a vote nobody recorded is worse than the current
44
- two-step flow.
45
- - Keep "Umfrage schliessen" — that is an owner action, not part of voting.
46
-
47
- **B. Result bars.**
48
-
49
- - Each option shows a horizontal proportional bar plus its count and percentage, in place of the
50
- `(<n>)` suffix.
51
- - The viewer's own choice(s) are visually marked. `voted_option_ids` already exists for this and is
52
- carried in the REST vote/create/close response — note the standing constraint documented in
53
- `MessagingProvider.jsx`: it is **viewer-specific and never present on a `poll_updated` frame**, so
54
- frame handling must not wipe a locally-known selection. Do not regress that; it was a deliberate fix.
55
- - Denominator: total votes cast, not participant count. A poll's turnout relative to group size is a
56
- different question and is **not** in scope.
57
- - Percentages must not mislead at zero votes — define and implement the zero-vote rendering explicitly
58
- rather than letting a division produce `NaN`.
59
-
60
- **C. Closed polls.** A closed poll shows final bars and is not interactive. Verify the closed state is
61
- visually unmistakable — with the submit button gone, "closed" can no longer be inferred from a missing
62
- button.
63
-
64
- ## NON-GOALS / DO NOT TOUCH
65
- - No backend change. If a scope-A requirement appears to need one, stop and report — that is a dcm WO.
66
- - Do not change `voted_option_ids` handling in `applyFrame` beyond what B needs to read it (see the
67
- merge-trap note above).
68
- - Do not add a "who voted for what" disclosure. Per-voter visibility is a privacy decision nobody has
69
- taken, and the existing `voters` data in `serialize_poll` is not licence to surface it.
70
- - Do not change poll creation (`Composer`'s dialog) — this WO is the display and voting surface only.
71
- - Do not touch `ReadTicks`, conversation titles, or anything in the MSG-6f scope.
72
-
73
- ## RISKS
74
- - **Tap-to-vote removes the "are you sure" step.** On a phone a mis-tap now votes. Mitigate by making
75
- the vote reversible (tap again / tap another) and by ensuring the optimistic state is obviously
76
- distinct from the confirmed state; do not add a confirmation dialog, which would reinstate the
77
- two-step flow this WO removes.
78
- - Rapid successive taps race. The last write must win deterministically — state how (request ordering,
79
- or discard stale responses by comparing against the latest local intent).
80
- - Accessibility: an option becomes an interactive control with a name, a pressed/checked state and
81
- keyboard operability. Bars are decorative; the count and percentage must be in the accessible name or
82
- adjacent text, not conveyed by width alone. `ui_reviewer` should check this specifically.
83
- - Every host app inherits the new interaction — a visible behaviour change beyond jg-ferien.
84
-
85
- ## REQUIRED TESTS TO WRITE
86
- Narrow and behavioural. Do NOT run the full suite.
87
-
88
- 1. Single-choice: tapping an option votes without any further interaction; tapping a second option
89
- moves the vote (the first is no longer selected, the counts reflect it).
90
- 2. Multi-select: tapping toggles one option on and off, and each tap sends the complete option-id set.
91
- 3. A rejected vote reverts the optimistic state and surfaces an error — assert the bar returns to its
92
- prior value, not merely that an error appeared.
93
- 4. Bars render proportionally, including the **zero-vote** case, with no `NaN` and no division error.
94
- 5. The viewer's own selection survives a `poll_updated` frame that does not carry `voted_option_ids`
95
- (the documented merge trap — this must not regress).
96
- 6. A closed poll is non-interactive: tapping an option does nothing and issues no request.
97
-
98
- **Non-vacuity:** test 5 must fail if the merge-trap guard is removed. Test 3 must assert the reverted
99
- value, not the presence of an error message.
100
-
101
- ## TEST SCOPE FOR THE GATE (orchestrator)
102
- `PollCard` and its neighbours in the messaging test files. Not the full suite.
103
-
104
- ## TARGET REPO
105
- `C:\Users\biglmi\Documents\webapps\ui-core-micha`. Branch `main` (or `develop` if present). Publish per
106
- the repo's release flow; the consuming pin bump is a separate step.
107
-
108
- ## MINI-HANDOVER (pastable)
109
-
110
- > Repo: `C:\Users\biglmi\Documents\webapps\ui-core-micha` (branch `main`, or `develop` if present).
111
- > Work order: `work-orders/MSG-6g.md` — read it fully, then follow the `orchestrate-codex` skill.
112
- >
113
- > **UX improvement, not a defect — this blocks nothing and is blocked by nothing.** Do not let it hold
114
- > up the messaging defect WOs (dcm MSG-10, ucm MSG-6f).
115
- >
116
- > Frontend only: no backend change is in scope. If tap-to-vote appears to need one, stop and report.
117
- > Two things that are easy to break and are called out in the WO: a failed vote must visibly revert
118
- > (not fail silently), and the viewer's `voted_option_ids` must survive a `poll_updated` frame that
119
- > does not carry it — that merge-trap guard was a deliberate earlier fix and test 5 pins it.
120
-
121
- ## PROGRESS CONTRACT
122
- Emit `PLAN: <steps>` up front, then a single-line `PROGRESS: [<n>/<total>] <action>` before every
123
- relevant action and `PROGRESS: [<n>/<total>] done` on completion, spaced so no gap exceeds ~2 min,
124
- stdout unbuffered, and exactly one final `RESULT: DONE|BLOCKED <reason>`.
125
-
126
- ---
127
-
128
- ## Part B — Implementation map (Orchestrator) / as-built record
129
-
130
- **Codex was unavailable (out of credits) — implemented directly by the Orchestrator.**
131
-
132
- ### Blocker found and resolved before implementation
133
-
134
- Line 38-40's own contingency ("confirm dcm's vote endpoint accepts the full option-id set
135
- per call... if it does not, stop and report") fired for real: `vote_poll` (dcm
136
- `services.py:247-259`) only cleared prior votes for single-choice polls; multi-select only
137
- ever `bulk_create`d, so a smaller `option_ids` set never retracted an omitted vote —
138
- untoggling was silently a no-op. Reported to the operator, who confirmed fixing it
139
- directly in dcm rather than working around it client-side. Fixed and published as dcm
140
- `2.40.1` — see dcm `work-orders/MSG-11.md`. `option_ids` is now authoritative (delete
141
- anything no longer in the set, then re-create the requested set) for both poll kinds.
142
-
143
- ### Files changed
144
-
145
- - `src/messaging/MessagingProvider.jsx`:
146
- - New module-level `applyOptimisticVote(poll, previousSelectedIds, nextSelectedIds, userId)`
147
- — adjusts only the options whose membership in the vote actually changed (count +/-1,
148
- `voters` array +/- the acting user), leaves everything else untouched.
149
- - New `pollVoteTokensRef` (per-poll "latest tap wins" token) alongside the existing
150
- `activeIdRef`/`seenEventsRef`/`filtersRef`.
151
- - `castPollVote(messageId, poll, optionIds)` rewritten: captures `previousPoll` from
152
- `cache.messages[messageId].poll` (falling back to the `poll` param), patches the
153
- optimistic projection into the cache immediately (same `patchMessage` shape
154
- `toggleReaction` already uses), then on success reconciles with the server's poll, on
155
- failure reverts to `previousPoll` — both guarded by the token so a stale response from
156
- a since-superseded tap can't stomp a newer one.
157
- - `src/messaging/PollCard.jsx`: full rewrite.
158
- - Single-choice (`allow_multiple: false`) → `Radio` per option (native "tap a different
159
- option moves the vote, tap the same one is a no-op" semantics — matches the WO's
160
- stated design directly, no extra toggle-off logic needed).
161
- - Multi-select → `Checkbox` per option (existing toggle semantics preserved).
162
- - Whole-option-row tap target (`Box onClick`, `stopPropagation` on the control's own
163
- `onClick` to avoid a double-fire) — WhatsApp-style, matches the operator's own framing.
164
- - `LinearProgress` bar + `{{count}} ({{percent}}%)` (`MessagingPoll.OPTION_RESULT`, new
165
- key) next to each option; `percent = totalVotes > 0 ? round(count/totalVotes*100) : 0`
166
- — never `NaN`.
167
- - Accessible name (`aria-label` on the control) carries option text + result text +
168
- "you voted" — never width-only.
169
- - Closed state: `Chip` with a lock icon + `MessagingPoll.CLOSED`, replacing the old
170
- caption-only text (now the *only* closed signal, since the submit button is gone).
171
- - Local `selected` state + a `voteTokenRef` mirror the provider's own token guard, since
172
- `PollCard` is a "dumb" prop-driven component (documented in
173
- `tests/messagingPollRendering.test.jsx`) — a rejected vote must revert visibly even
174
- when the host never re-passes a fresh `message` prop from its own cache.
175
- - Removed: the "Abstimmen" submit button and its `SELECT_OPTION` validation entirely.
176
- - `src/i18n/messagingTranslations.ts`: removed now-orphaned `MessagingPoll.VOTE` and
177
- `MessagingPoll.SELECT_OPTION`; added `MessagingPoll.OPTION_RESULT`.
178
-
179
- ### Tests
180
-
181
- - `tests/messagingMsg6g.test.jsx` (new) — the 6 WO-required cases verbatim.
182
- - `tests/messagingPollRendering.test.jsx` — updated for radio (not checkbox) on
183
- single-choice, the new label/aria-label shape, and the closed-chip instead of a
184
- disabled button; its `t` mock upgraded to interpolate `{{count}}`/`{{percent}}` (was
185
- bare-key-only, insufficient to assert rendered numbers).
186
- `tests/messagingInteractions.test.jsx`'s poll test rewritten for tap-to-vote (no submit
187
- click). `tests/messagingCompactBubble.test.jsx`'s poll smoke test needed no change
188
- (empty `options: []`, no control interaction).
189
-
190
- ### Verification
191
- Full suite 234 → 240, `tsc` clean (pre-review).
192
-
193
- ### Independent review: `reviewer` + `ui_reviewer`, both mandatory (author = Orchestrator, not Codex)
194
-
195
- **`reviewer` — 3 findings, all fixed:**
196
- - **R1 (P1, real bug):** `castPollVote`'s optimistic count projection was built from the
197
- caller-supplied `poll` argument, not from `previousPoll` (the value already freshly read
198
- from `cache.messages[messageId].poll` two lines above, and correctly used as the revert
199
- target on failure). For a second rapid tap (e.g. switching a single-choice vote A→B)
200
- where the caller's `poll` reference hadn't yet been refreshed — exactly how `PollCard`
201
- is documented to behave, a "dumb" prop-driven component — the count math computed
202
- against the stale pre-first-tap baseline, landing a silently wrong transient
203
- `vote_count` in the **shared cache** (not just local render state) until the server's
204
- response eventually reconciled it away. Fixed: project from `previousPoll`. Verified
205
- non-vacuous: reverted the fix, confirmed the new regression test below fails with the
206
- exact predicted wrong number (4 instead of 5), then restored it.
207
- - **R2 (P2, coverage gap):** no test asserted vote-count values across a multi-tap
208
- sequence with non-zero starting counts — the zero-vote fixtures used elsewhere
209
- happened to mask R1 via the `Math.max(0, ...)` clamp. Closed by a new
210
- `messagingMsg6g.test.jsx` case using non-zero seed counts (5/2) and a genuinely
211
- cache-connected reader alongside a *statically-propped* `PollCard` (the scenario that
212
- actually reproduces the staleness, per R1's own trace — a cache-connected `PollCard`
213
- re-renders with a fresh `poll` prop between taps and would not have caught this).
214
- - **R3 (P3):** `applyOptimisticVote` could push a literal `undefined` into an option's
215
- `voters` array when `user?.id` is momentarily unset (`AuthContext` not yet populated).
216
- Fixed with a `userId != null` guard.
217
-
218
- **`ui_reviewer` — 2 findings, both fixed:**
219
- - **U1 (P2):** no visual distinction between an in-flight optimistic vote and a
220
- server-confirmed one, contradicting the WO's own risk-mitigation requirement. Fixed by
221
- reusing `ReactionBar.jsx`'s established `pending` Set convention — dims the tapped
222
- option's bar/text while its request is in flight, but (unlike `ReactionBar`) does
223
- **not** disable the control, since the WO explicitly requires rapid re-taps to remain
224
- possible and race deterministically (last-write-wins via the token guard), not be
225
- blocked.
226
- - **U2 (P2):** single-choice rendered bare, ungrouped `Radio` controls — no `RadioGroup`,
227
- no shared `name`. Diverged from this repo's own established radio idiom
228
- (`AuthFactorRequirementCard.jsx`'s `RadioGroup` + `FormControlLabel`) and lost native
229
- arrow-key navigation between options plus the screen-reader group announcement. Fixed
230
- by wrapping single-choice options in a real `RadioGroup`; multi-select's `Checkbox`
231
- options are unaffected (no equivalent ARIA grouping concept applies). Switching to
232
- `FormControlLabel` for both control types also let the manual `Box onClick` +
233
- `stopPropagation()` whole-row-tap hack be removed entirely — `FormControlLabel`'s own
234
- `<label>` wrapping already makes the whole row natively clickable.
235
- - Found and fixed while addressing U2, not itself a reviewer finding: `RadioGroup`'s own
236
- `onChange` only ever hands back `event.target.value`, a DOM string, which would coerce
237
- a non-string option id (dcm's are UUID strings today, but the type isn't a guaranteed
238
- contract) — caught by `tests/messagingInteractions.test.jsx`'s existing numeric-id
239
- fixture failing after the `RadioGroup` change. Fixed by keeping each `Radio`'s own
240
- `onChange` (closure-typed, matching the `Checkbox` branch) instead of relying on the
241
- group-level handler.
242
-
243
- Re-verified after all fixes: full suite 234 → **241** (7 new: 6 WO-required cases +
244
- 1 R1/R2 regression), `tsc` clean.
@@ -1,238 +0,0 @@
1
- # WORK ORDER MSG-6h (ui-core-micha) — attachment gallery (thumbnail/lightbox/right-click download), reaction overlap
2
-
3
- **EXECUTION DIRECTIVE.** If you are the implementer reading this as your own spec, this section is not
4
- addressed to you — it tells the Orchestrator how to invoke you; you ARE that invocation, do not shell
5
- out to `codex exec`. Orchestrator: implement through `codex exec` in the background, invoked **directly
6
- via Bash** (never the `debugger`/`*_coder` Agent wrappers) with **both** flags `--skip-git-repo-check`
7
- and `--dangerously-bypass-approvals-and-sandbox`, prompt passed as a positional argument from this
8
- file. Fall back to direct Claude implementation only on Codex quota / rate-limit / non-zero exit.
9
-
10
- Feature-level WO (Expertenchat envelope). Operator report 2026-08-03 from a staging screenshot, seen
11
- now for the first time end-to-end because `django-core-micha` `MSG-10` just fixed attachment uploads
12
- (they were unconditionally rejected before — this display code has effectively never been exercised
13
- against a real upload on the platform path).
14
-
15
- ## TIER
16
- Tier 2 — shared-core UI consumed by every app. Independent `reviewer` + `ui_reviewer` mandatory.
17
-
18
- ## SCOPE
19
-
20
- **A. Attachment gallery.**
21
-
22
- Current state (`AttachmentList.jsx`): each attachment renders as a `Button` containing a 36×36px
23
- thumbnail (if the file is an image and its thumbnail loaded) plus a text label
24
- `t('MessagingAttachments.DOWNLOAD', { name: nameOf(attachment) })`. `nameOf` is
25
- `attachment.filename || attachment.name || attachment.id` — since dcm's `serialize_attachment` has
26
- never returned `filename` (see `django-core-micha` `work-orders/MSG-12.md`, its companion, fixing
27
- that), this **always** fell through to the raw UUID `id`. Clicking anywhere on the button downloads
28
- the file — there is no preview step.
29
-
30
- Operator's target shape, verbatim: *"bitte kleines Vorschaubild, bei Klick darauf grosse Vorschau,
31
- rechtsklick zum Kontextmenu mit Download (analog Whatsapp)"* — a small thumbnail; clicking it opens a
32
- large preview (lightbox); right-click opens a context menu with a download action.
33
-
34
- Build this:
35
- - **Image attachments**: small thumbnail (reuse the existing `getAttachmentThumbnail` fetch — already
36
- wired). Click → a lightbox (MUI `Dialog` or `Modal`) showing the full-size image, fetched via the
37
- existing `getAttachment` blob endpoint. Right-click (`onContextMenu`) → a small context menu (MUI
38
- `Menu`, matching the pattern already used for message actions in `MessageBubble.jsx`) with a
39
- Download action, calling the same download logic `AttachmentList` already has.
40
- - **Non-image attachments** (no thumbnail available): keep a compact file-type indicator (icon +
41
- filename, once `MSG-12` supplies a real one) as the tap target; single click downloads directly
42
- (there is nothing to preview) — right-click still offers the same context menu for consistency, even
43
- though its one item duplicates the click action.
44
- - Filename: once `MSG-12` ships, `nameOf` should read the real `filename` first; keep the existing
45
- `|| attachment.id` fallback for interop with a not-yet-bumped dcm pin (an older host may still be on
46
- a dcm version that omits it) — never let a missing filename break rendering, just fall back visibly.
47
- - Keep the download-only design principle: bytes never touch anything but the existing authenticated
48
- `getAttachment`/`getAttachmentThumbnail` blob fetches already in `MessagingProvider`. No new
49
- endpoints, no new byte-handling path.
50
-
51
- **B. Reaction bar overlap.**
52
-
53
- `MessageBubble.jsx:139` renders `ReactionBar` as a sibling *after* the bubble's `Paper`, with
54
- `sx={{ mt: -0.5, ml: 0.75, position: 'relative', zIndex: 1 }}` — a small negative top margin
55
- deliberately pulls it up to overlap the bubble's bottom edge (a common "chip peeking out" treatment).
56
- Operator report: this overlap now reads as broken/unnatural, screenshotted on a message that has
57
- attachments — worth checking specifically whether the fixed `-0.5` offset was tuned against a
58
- plain-text bubble and no longer sits right against the bottom of an attachment row or (from `MSG-6g`)
59
- a poll's result bars, both of which changed the bubble's actual bottom content since this offset was
60
- last touched.
61
-
62
- **Live-verify against the operator's original screenshot before considering this closed** — the
63
- Orchestrator does not have staging credentials this round; if a local repro isn't conclusive, say so
64
- and ask the operator to confirm the fix visually rather than guessing further.
65
-
66
- ## NON-GOALS / DO NOT TOUCH
67
- - Do not touch `PollCard.jsx`, `ReadTicks.jsx`, or conversation titles — unrelated recent WOs.
68
- - Do not add any new backend endpoint — scope A only consumes what already exists (plus `MSG-12`'s
69
- additive `filename` field).
70
- - Do not change the reaction *toggle* logic (`ReactionBar.jsx`'s `react`/`pending` mechanics) — only
71
- its positioning relative to the bubble, scope B.
72
- - Do not implement a generic lightbox/gallery component library dependency — build directly with MUI
73
- primitives already used elsewhere in this codebase (`Dialog`, `Menu`), matching existing patterns.
74
-
75
- ## RISKS
76
- - A right-click context menu on a message bubble already exists for message-level actions
77
- (`MessageBubble.jsx`'s `openMenu`/`canOpenMenu`, triggered by `onContextMenu`). An attachment's own
78
- right-click context menu must not fight with or accidentally trigger the message-level one when the
79
- attachment sits inside the bubble — `event.stopPropagation()` where appropriate.
80
- - Lightbox must be keyboard-dismissable (Escape) and trap focus reasonably — it is a modal.
81
- - The thumbnail fetch already exists and works (`AttachmentList.jsx`'s current `useEffect`); reuse it,
82
- don't re-invent — but the full-size lightbox fetch (`getAttachment`) is a new call path, verify it
83
- doesn't double-fetch what the thumbnail effect already has cached.
84
-
85
- ## REQUIRED TESTS TO WRITE
86
- Narrow and behavioural. Do NOT run the full suite.
87
-
88
- 1. An image attachment renders a thumbnail; clicking it opens a lightbox showing the full image
89
- (fetched via `getAttachment`).
90
- 2. Right-clicking an image attachment opens a context menu with a Download action; selecting it
91
- downloads (same assertion shape as the existing pre-redesign download test, if one exists — check).
92
- 3. A non-image attachment (no `thumbnail_url`) renders a file indicator, not a broken image; clicking
93
- it downloads directly.
94
- 4. `nameOf` prefers `attachment.filename` when present, falls back to `attachment.id` when absent
95
- (interop with a dcm pin that predates `MSG-12`) — assert both cases render without crashing.
96
- 5. The attachment's own context menu does not also trigger the message-level action menu (no
97
- double-open, no propagation leak) — regression guard given `MessageBubble.jsx` already has its own
98
- `onContextMenu`.
99
-
100
- ## TEST SCOPE FOR THE GATE (orchestrator)
101
- Attachment- and reaction-bar-adjacent messaging test files. Not the full suite.
102
-
103
- ## TARGET REPO
104
- `C:\Users\biglmi\Documents\webapps\ui-core-micha`. Branch `main`. Publish per the repo's release flow;
105
- consuming pin bumps (jg-ferien, and `django-core-micha` `MSG-12` for the filename) are separate,
106
- subsequent steps — **but per standing feedback this session, do not report this WO as "done" until
107
- those consuming steps have actually landed**, not merely published.
108
-
109
- ## MINI-HANDOVER (pastable)
110
-
111
- > Repo: `C:\Users\biglmi\Documents\webapps\ui-core-micha` (branch `main`). Work order:
112
- > `work-orders/MSG-6h.md` — read it fully, then follow the `orchestrate-codex` skill.
113
- >
114
- > Two independent scopes, both operator-reported live on staging: (A) `AttachmentList.jsx` needs a
115
- > real thumbnail-click-lightbox / right-click-download-menu gallery, WhatsApp-style, instead of a bare
116
- > download button showing a raw UUID (the UUID-fallback root cause is fixed separately in
117
- > `django-core-micha` `MSG-12`, a companion WO — consume its new `filename` field with a graceful
118
- > fallback for an older pin). (B) `MessageBubble.jsx:139`'s `ReactionBar` has a fixed `mt: -0.5`
119
- > negative-margin overlap trick that now reads as visually broken against certain bubble content
120
- > (attachments, and possibly `MSG-6g`'s poll bars) — investigate and fix, but flag if it needs a live
121
- > staging screenshot to confirm rather than guessing blind.
122
-
123
- ## PROGRESS CONTRACT
124
- Emit `PLAN: <steps>` up front, then a single-line `PROGRESS: [<n>/<total>] <action>` before every
125
- relevant action and `PROGRESS: [<n>/<total>] done` on completion, spaced so no gap exceeds ~2 min,
126
- stdout unbuffered, and exactly one final `RESULT: DONE|BLOCKED <reason>`.
127
-
128
- ---
129
-
130
- ## Part B — Implementation map (Orchestrator) / as-built record
131
-
132
- **Codex was unavailable (out of credits) — implemented directly by the Orchestrator.**
133
-
134
- ### Scope A — `src/messaging/AttachmentList.jsx` (full rewrite)
135
-
136
- - Each attachment is now a 64×64 `ButtonBase` tile (not a full-width text button): an image
137
- attachment shows its existing thumbnail fetch (unchanged `getAttachmentThumbnail` `useEffect`,
138
- reused verbatim); a non-image attachment shows a generic file icon +
139
- filename (`InsertDriveFileOutlinedIcon`).
140
- - Click: image → `openLightbox` (fetches the full-resolution blob via the existing `getAttachment`,
141
- cached per-attachment in a `fullUrlCacheRef` so a re-open within the same mount doesn't re-fetch);
142
- non-image → `download` (unchanged logic, just relocated).
143
- - Right-click (`onContextMenu`): opens a MUI `Menu` (anchored to click position) with one Download
144
- item, for both image and non-image attachments. `event.stopPropagation()` inside the handler stops
145
- it from also reaching `MessageBubble.jsx`'s own bubble-level `onContextMenu` (verified with a
146
- dedicated regression test, item 5 below).
147
- - Lightbox: a MUI `Dialog` with the full image + a close `IconButton` (also closes via the Dialog's
148
- own backdrop-click/Escape handling, native to MUI).
149
- - `nameOf(attachment)` unchanged in shape (`filename || name || id`), now actually resolves to a real
150
- name once the host's dcm pin includes `MSG-12`; falls back to `id` for an older pin (test 4).
151
- - 3 new i18n keys: `MessagingAttachments.PREVIEW`, `.CLOSE_PREVIEW`, `.DOWNLOAD_ACTION`. Existing
152
- `.LABEL`/`.DOWNLOAD` kept — `.DOWNLOAD` is now also the accessible name for a non-image tile (no
153
- visible "Download X" text on it any more, per the new compact-tile design — updated one pre-existing
154
- test in `messagingCompactBubble.test.jsx` that asserted on the old visible text).
155
-
156
- ### Scope B — `src/messaging/MessageBubble.jsx:139`
157
-
158
- - `ReactionBar`'s `sx` changed from `{ mt: -0.5, ml: 0.75, position: 'relative', zIndex: 1 }` to
159
- `{ mt: 0.5, ml: 0.75 }` — a small **positive** gap instead of the negative-margin "peek from the
160
- bubble's bottom edge" trick. That trick pulled the chip up into whatever sits immediately above it
161
- in normal document flow, which used to always be plain text or the compact meta row; since MSG-6g
162
- added poll bars and this WO's own gallery redesign changed the attachment row's height/borders, the
163
- fixed offset started visually cutting into bordered, boxier content instead of text. Also dropped
164
- `position: relative` / `zIndex: 1`, no longer needed without the overlap.
165
- - **Not independently visually re-verified against the operator's original staging screenshot** — no
166
- staging credentials this round (same limitation noted when the ucm pin bump was last reported). The
167
- fix is a conservative, low-risk "stop overlapping anything" change; flagging per the WO's own
168
- instruction to say so rather than claim a live-confirmed fix.
169
-
170
- ### Tests
171
- `tests/messagingMsg6h.test.jsx` (new) — the 5 WO-required cases. `tests/messagingCompactBubble.test.jsx`
172
- updated (pre-existing test asserted on now-removed visible DOWNLOAD text). No other existing test
173
- needed a change (`messagingComposer.test.jsx`'s AttachmentList smoke test only checked the outer
174
- `aria-label`, unaffected).
175
-
176
- ### Verification (pre-review)
177
- Full suite 241 → 246 (5 new), `tsc` clean.
178
-
179
- ### Independent review: `reviewer` + `ui_reviewer`, both mandatory (author = Orchestrator, not Codex)
180
-
181
- Both reviews converged on the same core gap from different angles — fixed together in one pass.
182
-
183
- **`reviewer` — 3 findings:**
184
- - **R1 (P2, real regression):** the pre-diff design let every attachment (image or not) download via a
185
- direct click/Enter/Space on a plain `Button`. The redesign made an image's click open the lightbox
186
- instead, which had only a Close button — the *only* way left to download an image was a real mouse
187
- right-click, unreachable by keyboard or (with no long-press) touch. **Fixed**: the lightbox now has
188
- its own Download `IconButton`, so download is reachable via click, Tab+Enter, or touch, without ever
189
- needing the context menu.
190
- - **R2 (P2):** the new lightbox fetch had no loading or error state — a click gave no feedback until
191
- the network round trip resolved, and a failure silently no-opped. **Fixed**: `lightbox.status`
192
- (`'loading' | 'ready' | 'error'`) drives a `CircularProgress` while fetching and an `Alert` on
193
- failure; a stale in-flight fetch (opened, then a *different* attachment opened before the first
194
- resolves) is guarded by comparing the resolved attachment's id against the current lightbox state
195
- before applying the result.
196
- - **R3 (P3):** the lightbox `Dialog` had no accessible name. **Fixed**: `aria-label` on the `Dialog`
197
- itself, set to the previewed attachment's name.
198
-
199
- Verified correct, no bug: object-URL lifecycle (no leak/double-revoke/revoke-while-displayed), the
200
- context-menu `stopPropagation` (traced against React's synthetic event dispatch + the dedicated test),
201
- no stale-closure risk in the menu's download handler, `fullUrlCacheRef` can't go stale across messages
202
- (`Thread.jsx` keys `MessageBubble` by `message.id`, always unmounts on swap), i18n coverage.
203
-
204
- **`ui_reviewer` — 6 findings:**
205
- - **U1 (P1, same root cause as R1):** touch users had no way to download an image at all (no
206
- long-press implemented, lightbox had no download control). **Fixed by the same lightbox Download
207
- button as R1** — one fix closes both findings.
208
- - **U2 (P2):** no visual affordance signals a tile is right-clickable, and right-click-for-download is
209
- a non-obvious pattern without a fallback for a mouse user who doesn't know it. **Resolved by the R1/U1
210
- fix, not a separate change**: right-click is no longer the *only* path to anything — an image is
211
- fully reachable via primary click → lightbox → visible Download button, and a non-image already
212
- downloads on primary click. Right-click remains a discoverable-if-you-know-it shortcut, not a
213
- required, hidden one; adding a second, redundant hover-reveal affordance on top of an already-fully-
214
- reachable primary interaction would be scope creep past what the finding's own root concern requires.
215
- - **U3 (P2):** a non-image tile's filename truncates in a 64px box with no way to recover the full name
216
- before downloading. **Fixed**: `title={nameOf(attachment)}` on the tile (native browser tooltip on
217
- hover).
218
- - **U4 (P3):** the French `PREVIEW` translation (`'Aperçu de {{name}}'`, noun-first) broke grammatical
219
- parallelism with the sibling `DOWNLOAD`/`DOWNLOAD_ACTION` (verb-first). **Fixed**: `'Prévisualiser
220
- {{name}}'`.
221
- - **U5 (P3):** the lightbox close button omitted the `boxShadow` the codebase's analogous floating-icon
222
- pattern (`MessageBubble.jsx`'s hover-revealed `MoreVertIcon`) uses for contrast. **Fixed**: both the
223
- new Download button and the Close button now carry `boxShadow: 1`.
224
- - **U6 (P3, not fixed, disclosed):** `openLightbox`'s failure path is a silent no-op if `getAttachment`
225
- is bypassed some other way — the reviewer themselves noted this mirrors `download()`'s own
226
- pre-existing silent-failure shape, not a new defect class. Left as-is, consistent with that existing
227
- convention elsewhere in this component; R2's loading/error state above already covers the actual
228
- reachable failure path (a real fetch rejection).
229
-
230
- ### Not independently visually re-verified
231
- Scope B's `ReactionBar` spacing fix (`MessageBubble.jsx:145`) was not confirmed against the operator's
232
- original staging screenshot — no staging credentials this round. Both reviewers examined it by static
233
- code reading only and found the fix low-risk (a strict `mt` sign flip, no z-stacking or other side
234
- effect traced), but this is a judgment call, not a live-rendered confirmation. Flagging per the WO's
235
- own instruction rather than claiming a visual fix I have not actually seen.
236
-
237
- ### Re-verification after all fixes
238
- Full suite 241 → 249 (8 new: 5 original + 3 closing R1/R2), `tsc` clean.