@micha.bigler/ui-core-micha 3.14.4 → 4.0.1

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