@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,271 +0,0 @@
1
- # WORK ORDER MSG-6i (ui-core-micha) — attachment tile size, poll spacing/denominator, wider emoji picker
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 polish requests 2026-08-03, gathered in a
11
- requirements round after MSG-6h shipped; four independent, small scopes bundled the same way
12
- MSG-6f bundled A/B/C.
13
-
14
- ## TIER
15
- Tier 2 — shared-core UI consumed by every host app. Independent `reviewer` + `ui_reviewer` mandatory
16
- (scope C changes poll math, not just styling).
17
-
18
- ## SCOPE
19
-
20
- **A. Attachment tile: 64px → 120px.**
21
-
22
- `AttachmentList.jsx`'s tile `sx={{ width: 64, height: 64, ... }}` (both the `ButtonBase` and its
23
- internal thumbnail `Box component="img"`, which is `width: '100%', height: '100%'` and inherits).
24
- Change both dimensions to `120`. Check the non-image tile's icon/caption layout still reads well at
25
- the larger size (currently sized for 64px — `InsertDriveFileOutlinedIcon fontSize="small"` and a
26
- `caption` typography may look sparse/small inside a 120px box; consider `fontSize="medium"` or larger
27
- if it looks disproportionate — operator did not specify, use judgement, flag if genuinely unsure).
28
- `Stack direction="row" flexWrap="wrap"` spacing stays as-is unless the larger tiles visibly break
29
- wrapping at typical bubble widths.
30
-
31
- **B. Poll card: more breathing room, not a wider bubble.**
32
-
33
- Operator, verbatim (after seeing a screenshot of the current cramped layout — checkbox, option text,
34
- and percentage squeezed onto one tight line, thin bars close to the text, minimal gap between options):
35
- *"Nicht generell breiter, aber luftiger."* (Not generally wider, but airier.) This is **not** scope A
36
- of a width change — the poll's outer bubble width stays exactly as-is (still shares
37
- `MessageBubble.jsx`'s `maxWidth: min(75%, 680px)` with every other message type). Increase internal
38
- spacing in `PollCard.jsx`: the `Stack spacing={0.75}` wrapping the whole card, the per-option
39
- `Stack direction="row" spacing={0.5}` (control + label), and the `LinearProgress`'s `height: 6` are
40
- the likely levers — raise spacing values and/or bar height/thickness so the card reads as roomier.
41
- No prescribed exact values; operator did not send further numeric guidance beyond the "airier, not
42
- wider" direction — use reasonable judgement (e.g. spacing bumped by ~50%, bar height 8-10px) and
43
- **flag this specific change for a live screenshot check before considering it closed** — same
44
- disclosed-limitation pattern as MSG-6h's reaction-spacing fix (no staging credentials this round).
45
-
46
- **C. Poll percentage denominator: respondents of this poll, not sum of votes cast.**
47
-
48
- **This reverses an explicit non-goal from `MSG-6g`'s own envelope**, confirmed deliberately by the
49
- operator in this round (not an oversight): MSG-6g stated *"Denominator: total votes cast, not
50
- participant count. A poll's turnout relative to group size is a different question and is not in
51
- scope."* — operator's correction: for a **multi-select** poll, summing `vote_count` across options can
52
- exceed the number of people who actually answered (one person can vote for several options), so a
53
- per-option percentage computed against that inflated sum reads as misleadingly low and the bars
54
- don't sum to a sensible 100%. The new denominator: **the number of distinct people who cast at least
55
- one vote in this poll** — not total conversation participants (an un-started, never-voting group
56
- member should not silently deflate everyone else's percentages further).
57
-
58
- Compute this **client-side, no backend change**: `serialize_poll`'s existing `option.voters` arrays
59
- (already present, already used for the viewer's-own-vote marking) already carry every voter's id per
60
- option — the union of all options' `voters` arrays, deduplicated, is exactly "everyone who answered
61
- this poll." Replace `PollCard.jsx`'s current
62
- `totalVotes = options.reduce((sum, option) => sum + (option.vote_count ?? option.voters?.length ?? 0), 0)`
63
- with a respondent count derived from the union of `option.voters` across all options (a `Set` of voter
64
- ids). **Zero-vote rendering must stay `NaN`-free** (existing guard, do not regress it) — zero
65
- respondents still means 0% for every option, not a division error.
66
-
67
- **D. Composer emoji picker: its own, wider curated list, decoupled from reaction quick-picks.**
68
-
69
- `Composer.jsx:144` currently reuses `QUICK_EMOJIS` from `ReactionBar.jsx` (`['👍', '❤️', '😂', '🎉',
70
- '👀']`, 5 entries) — the same tiny set meant for quick message reactions, not for composing free-form
71
- text. Operator confirmed direction after discussion: mobile users already have a full native emoji
72
- keyboard (no in-app work needed there); desktop users lack an obvious equivalent, so a **wider curated
73
- grid (~20-30 common emojis)** in the Composer's own picker adds real value. **Not** a full
74
- search-based picker (out of scope — meaningfully more engineering for a need mobile already covers).
75
-
76
- Define a new, separate constant (e.g. `COMPOSER_EMOJIS` in `Composer.jsx` itself, or a shared
77
- `emojiSets.js` if that reads cleaner) with ~20-30 common emojis, grouped loosely by theme (smileys,
78
- gestures, hearts, objects/travel — operator did not specify an exact list; use a reasonable, common
79
- default and flag it as easily adjustable). Render as a grid inside the existing `Menu` (currently a
80
- plain vertical `MenuItem` list — at 25-30 entries that would be a very long scroll; switch to a
81
- wrapping grid layout, e.g. a `Box` with `display: grid` or `flexWrap`, inside the `Menu`). Keep
82
- `ReactionBar.jsx`'s own `QUICK_EMOJIS` untouched (5, curated, unchanged) — reactions stay small and
83
- fast, this is a Composer-only change.
84
-
85
- ## NON-GOALS / DO NOT TOUCH
86
- - Do not touch `ReactionBar.jsx`'s own reaction UI or its 5-emoji `QUICK_EMOJIS` — scope D adds a
87
- second, separate, larger set for the Composer only.
88
- - Do not widen the message bubble's `maxWidth` (`MessageBubble.jsx`) — scope B is internal spacing
89
- only, confirmed explicitly by the operator.
90
- - Do not touch `MessageBubble.jsx`'s reaction-overlap fix from MSG-6h, or the lightbox/context-menu
91
- logic from MSG-6h — this WO only resizes the tile (scope A), doesn't change its interaction model.
92
- - No backend change for scope C — the data needed already exists in the current API response.
93
- - Do not build a full emoji search/picker component (scope D) — a curated grid only.
94
-
95
- ## RISKS
96
- - Scope C is a **behaviour change visible to every host app** the moment this version is consumed —
97
- a poll's displayed percentages will differ from before for any multi-select poll where the same
98
- person voted for multiple options. State this plainly; it is the operator's own explicit correction,
99
- not a silent reinterpretation.
100
- - Scope A's larger tile changes vertical rhythm inside a message bubble containing attachments —
101
- check it doesn't visually crowd the meta row (timestamp/read-ticks) directly below it.
102
- - Scope B and D have no prescribed exact values — implement a reasonable default, disclose the
103
- specific choices made (spacing multipliers, the exact emoji list) in the completion note so the
104
- operator can request a numeric tweak without re-deriving what was chosen.
105
-
106
- ## REQUIRED TESTS TO WRITE
107
- Narrow and behavioural. Do NOT run the full suite.
108
-
109
- 1. Attachment tile renders at 120×120 (image and non-image cases).
110
- 2. Poll respondent-count denominator: a multi-select poll where one voter voted for two options must
111
- show percentages based on the **respondent count** (e.g. 1 respondent → both their chosen options
112
- show 100%, not summed-and-diluted), not `sum(vote_count)`. This must **fail against the current
113
- `sum(vote_count)` code** — prove it with a case where the two differ (multi-select, one voter,
114
- two options voted).
115
- 3. Poll respondent-count denominator: the existing single-choice zero-vote case still shows 0%, no
116
- `NaN`, no regression (reuse/extend the existing MSG-6g zero-vote test's assertion shape).
117
- 4. Composer emoji picker: opening it shows more than 5 emoji options (the expanded set, not
118
- `ReactionBar`'s `QUICK_EMOJIS`); selecting one still inserts it at the cursor position (existing
119
- `insertEmoji` behaviour, unchanged — just fed from the new list).
120
- 5. `ReactionBar`'s own picker is unaffected — still exactly its original 5 `QUICK_EMOJIS` (regression
121
- guard that scope D didn't leak into scope-unrelated reaction UI).
122
-
123
- ## TEST SCOPE FOR THE GATE (orchestrator)
124
- Attachment-, poll-, and Composer-adjacent messaging test files. Not the full suite.
125
-
126
- ## TARGET REPO
127
- `C:\Users\biglmi\Documents\webapps\ui-core-micha`. Branch `main`. Publish per the repo's release flow;
128
- the jg-ferien pin bump is part of THIS WO's completion, not a deferred step (standing correction this
129
- session: publish alone is not delivery).
130
-
131
- ## MINI-HANDOVER (pastable)
132
-
133
- > Repo: `C:\Users\biglmi\Documents\webapps\ui-core-micha` (branch `main`). Work order:
134
- > `work-orders/MSG-6i.md` — read it fully, then follow the `orchestrate-codex` skill.
135
- >
136
- > Four independent scopes: (A) attachment tile 64px→120px in `AttachmentList.jsx`. (B) `PollCard.jsx`
137
- > internal spacing increased ("airier, not wider" — bubble maxWidth unchanged) — no exact numbers
138
- > given, use judgement, flag for a live screenshot check. (C) poll percentage denominator changes from
139
- > `sum(vote_count)` to the count of distinct respondents (union of all options' `voters` arrays,
140
- > client-side, no backend change) — **this deliberately reverses an explicit MSG-6g non-goal**, operator
141
- > re-confirmed. (D) Composer's emoji picker gets its own ~20-30-entry curated grid, decoupled from
142
- > `ReactionBar`'s 5-entry `QUICK_EMOJIS` (untouched) — no full search picker, mobile already has a
143
- > native one.
144
-
145
- ## PROGRESS CONTRACT
146
- Emit `PLAN: <steps>` up front, then a single-line `PROGRESS: [<n>/<total>] <action>` before every
147
- relevant action and `PROGRESS: [<n>/<total>] done` on completion, spaced so no gap exceeds ~2 min,
148
- stdout unbuffered, and exactly one final `RESULT: DONE|BLOCKED <reason>`.
149
-
150
- ---
151
-
152
- ## Part B — Implementation map (Orchestrator) / as-built record
153
-
154
- **Codex was unavailable (out of credits) — implemented directly by the Orchestrator.**
155
-
156
- ### Scope A — `AttachmentList.jsx`
157
- Tile `sx={{ width: 64, height: 64, ... }}` → `120, 120`. Non-image tile's icon bumped
158
- `fontSize="small"` → `"large"` and its `Stack` spacing `0.25` → `0.75` — the original
159
- small icon+caption pairing read sparse against the much larger tile.
160
-
161
- ### Scope B — `PollCard.jsx` spacing
162
- Outer `Paper` padding `p: 1` → `1.5`; outer `Stack` (question/options/close-row) spacing
163
- `0.75` → `1.5`; per-option `richLabel` `Stack` spacing `0.25` → `0.75`; `LinearProgress`
164
- `height: 6` → `8`. No exact values were specified by the operator beyond "airier, not
165
- wider" (confirmed via a screenshot of the cramped original) — **not independently
166
- visually re-verified against a fresh screenshot before closing** (no staging/live
167
- credentials for this specific check this round); flagging per the same disclosed-
168
- limitation pattern as MSG-6h's reaction-spacing fix.
169
-
170
- ### Scope C — `PollCard.jsx` percentage denominator
171
- `totalVotes = sum(option.vote_count)` replaced with
172
- `respondentCount = new Set(options.flatMap(option => option.voters || [])).size` — the
173
- count of distinct people who voted for *any* option, computed client-side from data
174
- already present in the response (`voters` arrays, already used for the viewer's-own-vote
175
- marking). `percent` now divides by `respondentCount`.
176
-
177
- ### Scope D — `Composer.jsx` emoji picker
178
- New `COMPOSER_EMOJIS` (28 entries) defined locally in `Composer.jsx`, rendered as a
179
- 7-column CSS grid of `IconButton`s inside the existing `Menu` (replacing the old
180
- `QUICK_EMOJIS.map(... <MenuItem>...)` vertical list — `MenuItem` import removed, now
181
- unused). `ReactionBar.jsx`'s own `QUICK_EMOJIS` (5, curated) untouched.
182
-
183
- ### Companion fix found and fixed — `Thread.jsx` (not in this WO's original scope)
184
- Discovered while live-verifying jg-ferien's `MSG-18` (mobile Composer hidden behind the
185
- fixed bottom nav) in the *same* browser session as this WO's own work: after fixing every
186
- jg-side layout offset, the Composer was **still** rendering behind the nav. Root cause
187
- traced into this package: `Thread.jsx`'s root `<Stack>` had no `flex`/`minHeight`, and its
188
- scroll `Box` used a **hardcoded `maxHeight: 560`** — neither participates in a host's
189
- flex-column layout at all, so on a short mobile viewport the thread never shrinks to fit,
190
- regardless of how correctly the *host* sizes its own containers. Fixed: both the root
191
- `Stack` and the scroll `Box` gained `flex: 1, minHeight: 0` (the classic flex-item
192
- `min-height: auto` trap — a flex item won't shrink below its content's natural size
193
- without this). `maxHeight: 560` kept as an upper-bound fallback, so a host that does
194
- **not** embed `Thread` in a flex-column context sees **zero behaviour change** (both new
195
- properties are no-ops outside a flex context). Live-verified end-to-end (see MSG-18's own
196
- as-built record in jg-ferien for the full trace): the composer moved from `top: 742`
197
- (inside the fixed nav's own `740–812` range) to `top: 629.5` (a clean 64px gap above the
198
- nav at `740`), by locally building this package's `dist/` and patching it into jg's
199
- `node_modules` for verification — no publish needed to prove the fix, but IS needed to
200
- actually ship it (bundled into this same release, not deferred).
201
-
202
- ### Tests
203
- `tests/messagingMsg6i.test.jsx` (new) — 7 tests: all 5 WO-required cases, one extra
204
- (emoji-insertion behaviour still works from the new grid), plus a 6th added post-review
205
- (see R3 below) asserting `Thread.jsx`'s `flex`/`minHeight` are actually applied.
206
-
207
- ### Verification (pre-review)
208
- Full suite 249 → 255 (6 new), `tsc` clean.
209
-
210
- ### Independent review — `reviewer` + `ui_reviewer`, both run in parallel, findings + fixes
211
-
212
- Both reviewers independently converged on the same P2 keyboard-navigation regression
213
- (scope D) — treated as one finding below (R1/U1).
214
-
215
- - **R1/U1 (P2, confirmed by both reviewers) — Composer's emoji grid broke MUI `Menu`'s
216
- arrow-key roving-focus.** Nesting the 28 `IconButton`s inside an intervening
217
- `<Box role="group">` meant `MenuList`'s `moveFocus` (which only traverses **direct**
218
- children) saw a single non-tabbable child, so ArrowDown/Up/Home/End all became dead —
219
- none of the 28 buttons reachable via the menu's own keyboard model, and nesting
220
- `role="button"` descendants under `role="menu"` isn't a conformant ARIA pattern either.
221
- **Fixed**: swapped `<Menu>` for `<Popover>` (`Composer.jsx`) — a grid of plain buttons
222
- was never actually a linear menu, so this stops advertising semantics it can't deliver
223
- and leaves keyboard traversal to the browser's native Tab order, which does reach every
224
- button. Replicated Menu's default `anchorOrigin`/`transformOrigin`
225
- (`{vertical:'bottom',horizontal:'left'}` / `{vertical:'top',horizontal:'left'}`) so the
226
- picker's on-screen position is unchanged.
227
- - **R2 (P2, `reviewer`) — optimistic vote could desync `vote_count` from `voters`.**
228
- `MessagingProvider.applyOptimisticVote` bumped `vote_count` unconditionally but only
229
- updated `voters` when `userId` was known (a documented "brief window" before
230
- `AuthContext` populates `user` — in practice also the default state for any host/test
231
- that doesn't wrap `MessagingProvider` in `AuthContext` at all, confirmed while fixing
232
- this). Scope C's new `respondentCount` denominator reads only from `voters`, so that
233
- window could show a transiently inconsistent/>-100% percentage. **Fixed**: added a
234
- sentinel (`OPTIMISTIC_ANONYMOUS_VOTER`, a space-prefixed string no real UUID can match)
235
- used in place of `userId` in `voters` when it's unknown, so `vote_count` and `voters`
236
- move together in every case — the real vote still overwrites this on
237
- reconcile/revert. Re-ran every existing poll-vote test
238
- (`messagingMsg6g/Interactions/PollRendering/CompactBubble/Api`) to confirm the
239
- already-tested optimistic-count behaviour (which relies on the no-`AuthContext` test
240
- shape) still holds — it does.
241
- - **R3 (P2, `reviewer`) — `Thread.jsx`'s flex/minHeight companion fix had zero regression
242
- coverage**, and the "not meaningfully assertable via jsdom" justification didn't hold up
243
- (this WO's own tile-size test already proves resolved `sx` values ARE readable via
244
- `getComputedStyle` without real layout). **Fixed**: added test 6 to
245
- `tests/messagingMsg6i.test.jsx`, asserting `flexGrow`/`minHeight` on both `Thread`'s
246
- root `Stack` and scroll `Box`, plus that `maxHeight: 560` is still present as the
247
- fallback cap.
248
- - **U2 (P3, `ui_reviewer`) — hardcoded `fontSize: '1.15rem'`** on the per-emoji
249
- `IconButton`. No existing theme/typography token covers emoji-glyph sizing (checked
250
- `ReactionBar.jsx`'s own emoji rendering for precedent — none exists there either).
251
- **Not fixed** — P3, no clear correct token to switch to; noted here rather than
252
- silently dropped.
253
- - **U3 (P3, `ui_reviewer`) — the emoji grid reused `MessagingComposer.ADD_EMOJI`** as its
254
- `role="group"` `aria-label`, identical to the trigger button's own label, so a screen
255
- reader announces "Add emoji" twice back-to-back. **Fixed**: added a dedicated
256
- `MessagingComposer.EMOJI_PICKER` key ("Choose an emoji" / "Emoji auswählen" / "Choisir
257
- un emoji") for the grid.
258
- - **R4 (P3, `reviewer`) — test 4b's assertion (`input.value.length > 2`) didn't prove
259
- cursor-position insertion**, only that something got appended. **Fixed**: rewrote it to
260
- match the exact expected string at a mid-string cursor position (same shape as the
261
- pre-existing `messagingComposerChunk5.test.jsx` assertion).
262
- - **R5 (P3, `reviewer`) — residual, not fixed**: the 120px tile stretches whatever
263
- `getAttachmentThumbnail` returns via `objectFit: cover`; if that thumbnail is
264
- server-sized for the old 64px target, the larger tile could show blur. Not verifiable
265
- from this repo (backend-controlled, untouched by this WO) — flagged as disclosed
266
- residual risk, consistent with scope B's own "no live screenshot check this round" note.
267
-
268
- ### Verification (post-review)
269
- Full suite (single-forked to avoid an unrelated vitest worker-pool timeout seen under the
270
- default parallel pool): **45 test files, 256/256 passed** (255 + 1 new). `tsc -p
271
- tsconfig.build.json --noEmit` clean.
@@ -1,117 +0,0 @@
1
- # WORK ORDER MSG-6j — poll option row collides with its vote count under a tight bubble width
2
-
3
- **EXECUTION DIRECTIVE.** If you are the implementer reading this as your own spec, this section is
4
- not addressed to you — it tells the Orchestrator how to invoke you; you ARE that invocation, do not
5
- shell out to `codex exec`. Orchestrator: implement through `codex exec` in the background, invoked
6
- **directly via Bash** (never the `debugger`/`*_coder` Agent wrappers) with **both** flags
7
- `--skip-git-repo-check` and `--dangerously-bypass-approvals-and-sandbox`, prompt passed as a
8
- positional argument from this file. Fall back to direct Claude implementation only on Codex quota /
9
- rate-limit / non-zero exit.
10
-
11
- Feature-level WO (Expertenchat envelope). Operator screenshots from `jg-ferien` production, 2026-08-04:
12
- a poll option's label text runs directly into its vote-count/percent text with **zero visible gap**
13
- ("soidfsdoöifn**2** (100 %)" — the bold "2" is the vote count, not part of the option text). The
14
- percentage math itself is confirmed correct once both votes had landed (a second screenshot showed
15
- "2 (100 %)" / "1 (50 %)" as expected) — **this WO is width/layout only, no data or percentage logic
16
- change.**
17
-
18
- ## TIER
19
- Tier 2 — `ui-core-micha` is shared-core, which forces Tier 2 regardless of change size per AGENTS.md.
20
- Independent `reviewer` mandatory. No `sec_reviewer` needed (pure layout, no data/permission surface).
21
-
22
- ## ROOT CAUSE — verified against the landed code (`src/messaging/PollCard.jsx`, `MessageBubble.jsx`)
23
-
24
- `MessageBubble.jsx:100` sizes every message bubble as `width: 'fit-content', maxWidth: 'min(75%, 680px)'`
25
- — it shrinks to exactly the width its content needs, with **no minimum floor**. Inside a poll,
26
- `PollCard.jsx`'s `optionLabel()` renders each option's label and its `resultText` ("{count} ({percent}
27
- %)") in a `Stack direction="row" justifyContent="space-between"` with **no `spacing`/`gap`** of its
28
- own (`:112-116`). `justifyContent: 'space-between'` only inserts space when the row has *leftover*
29
- width beyond what its children need — but here the bubble's own width is derived FROM this exact
30
- content (`fit-content`), so by construction there is no leftover space, and space-between reserves
31
- **zero minimum gap**. The label (`Typography noWrap`, which MUI resolves to
32
- `overflow:hidden;textOverflow:ellipsis;whiteSpace:nowrap`) shows its full text without truncating
33
- because the row isn't actually being squeezed narrower than content — it's exactly content-width, so
34
- label and result text end up flush against each other with nothing between.
35
-
36
- Two independent, additive fixes, both needed (confirmed by re-reading the code, not guessed):
37
-
38
- 1. **Give the label/result row an explicit minimum gap that `space-between` cannot collapse to zero.**
39
- Add `spacing={1}` (or equivalent `gap`) to the `Stack direction="row" justifyContent="space-between"`
40
- at `PollCard.jsx:113` (inside `optionLabel()`'s `richLabel`). MUI's `Stack` applies its `spacing`
41
- prop as margin between children, independent of `justifyContent` — this guarantees a floor gap
42
- even when the row has no leftover width to distribute.
43
- 2. **Give the poll card itself a sensible minimum width** so a short question/option set doesn't pull
44
- the whole bubble in tighter than is comfortable to read (the operator's "insbesondere in der Breite
45
- mehr Platz" — more room specifically in width). Add a `minWidth` to the `Paper` at
46
- `PollCard.jsx:123` (e.g. `minWidth: 280` — pick a value that reads comfortably at this repo's base
47
- font size; check against `MessageBubble.jsx`'s own `maxWidth` ceiling so a minWidth poll never
48
- exceeds the bubble's own max on a narrow viewport — clamp or use `Math.min` logic if needed, verify
49
- in the browser at a mobile width e.g. 375px, not just desktop).
50
-
51
- ## NON-GOALS / DO NOT TOUCH
52
-
53
- - Do not touch percentage/vote-count computation (`respondentCount`, `optionLabel`'s `percent`
54
- calculation) — confirmed correct by the operator's second screenshot; this WO is layout-only.
55
- - Do not touch `MessageBubble.jsx`'s own `maxWidth`/`width: fit-content` — other message kinds
56
- (plain text, attachments, announcements) rely on that tight fit; scope the fix to the poll's own
57
- minimum, not a global bubble-sizing change.
58
- - Do not touch `applyOptimisticVote`, `castPollVote`, or any vote-casting logic.
59
-
60
- ## RISKS
61
-
62
- - A `minWidth` on the poll `Paper` that isn't reconciled against `MessageBubble`'s `maxWidth` could
63
- overflow the bubble container on a narrow mobile viewport (375px) if chosen carelessly — verify at
64
- that width explicitly, not just desktop, per the WO's own note in `PollCard.jsx`'s existing
65
- accessibility comment about width assumptions.
66
- - `spacing` on a `Stack` that already has other margin/padding assumptions elsewhere in the row could
67
- shift alignment of the progress bar below it (`LinearProgress` sits in the same parent `Stack`,
68
- outside the row) — verify visually that the bar still aligns with the row above it.
69
-
70
- ## REQUIRED TESTS TO WRITE
71
-
72
- Narrow, matching this repo's existing `PollCard` test conventions — extend
73
- `tests/messagingPollRendering.test.jsx` (the file covering poll rendering today) rather than
74
- inventing a new file. `tests/messagingMsg6i.test.jsx` and `messagingMsg6g.test.jsx` are the other two
75
- poll-adjacent suites; skim them for the established assertion style before writing new tests.
76
-
77
- 1. A poll with a short question/options renders its `Paper`/container at at least the chosen minimum
78
- width (assert a computed style or a `sx` prop value — whatever this repo's existing width
79
- assertions already use elsewhere, e.g. `MessageBubble`'s own tests, follow that pattern).
80
- 2. The label/result-text row has non-zero spacing between its two children even when both are their
81
- natural (non-ellipsized) width — a regression test for the exact reported bug: render a poll with
82
- one option's label + result text combined width close to the container's available width, assert
83
- there is a rendered gap (e.g. via a wrapping element's `gap`/margin style, or simply that the
84
- two `Typography` nodes are not adjacent DOM siblings with zero computed margin — use whatever
85
- assertion style this repo's other spacing regression tests already use, e.g. MSG-6i's own
86
- `getComputedStyle`-based test for `Thread.jsx`'s flex fix, same technique).
87
-
88
- **Non-vacuity:** test 2 must fail against the pre-fix code (space-between with no `spacing`) — verify
89
- that before considering this WO done.
90
-
91
- ## VERIFICATION
92
-
93
- Live browser check at both a normal desktop width and a 375px mobile width (this repo's own
94
- established convention per prior MSG-6 WOs) with a real poll: two options with realistic-length text,
95
- confirm the label and vote-count/percent never visually touch, and the card is not uncomfortably
96
- narrow for a two-option poll.
97
-
98
- ## TARGET REPO
99
-
100
- `C:\Users\biglmi\Documents\webapps\ui-core-micha` (branch `main`, no `develop` in this repo).
101
-
102
- ## MINI-HANDOVER (pastable)
103
-
104
- > Repo: `C:\Users\biglmi\Documents\webapps\ui-core-micha` (branch `main`). Work order:
105
- > `work-orders/MSG-6j.md` — read it fully, then follow the `orchestrate-codex` skill.
106
- >
107
- > `PollCard.jsx`'s option row uses `justifyContent: space-between` with no `spacing`, inside a message
108
- > bubble that is `width: fit-content` — so there is never leftover space for space-between to
109
- > distribute, and the option's label text runs directly into its vote-count/percent text with zero
110
- > gap. Fix: add `spacing` to that row, and add a sensible `minWidth` to the poll's `Paper` (reconciled
111
- > against `MessageBubble.jsx`'s `maxWidth: min(75%, 680px)` ceiling, verified at 375px too). No
112
- > percentage/vote-count logic changes — that part is already correct.
113
-
114
- ## PROGRESS CONTRACT
115
- Emit `PLAN: <steps>` up front, then a single-line `PROGRESS: [<n>/<total>] <action>` before every
116
- relevant action and `PROGRESS: [<n>/<total>] done` on completion, spaced so no gap exceeds ~2 min,
117
- stdout unbuffered, and exactly one final `RESULT: DONE|BLOCKED <reason>`.
@@ -1,214 +0,0 @@
1
- # WORK ORDER NOTIF-12 (ui-core-micha + django-core-micha) — popup channel via the wizard renderer
2
-
3
- **EXECUTION DIRECTIVE.** Implement through `codex exec` in the background — invoked **directly via
4
- Bash** (never the `debugger`/`*_coder` Agent wrappers) with **both** flags `--skip-git-repo-check`
5
- and `--dangerously-bypass-approvals-and-sandbox`, prompt passed as a positional argument from this
6
- file. Fall back to direct Claude implementation only on Codex quota / rate-limit / non-zero exit.
7
-
8
- Feature-level WO (Expertenchat envelope). Seams verified against ucm 2.12.0 / dcm 2.32.0.
9
-
10
- ## HARD GATE — SATISFIED 2026-07-30
11
- NOTIF-13 landed and published: ucm **2.13.0** (`7a83ee9`), dcm **2.33.0** (`de77335`), both verified
12
- live on npm/PyPI. This WO was re-verified against the landed code on 2026-07-30 and the context
13
- package below describes the **actual shipped API**, not the pre-NOTIF-13 state. Cleared to start.
14
-
15
- ## TIER
16
- Tier 2 — this extracts a renderer out of `OnboardingWizard`, which **cockpit and jg run in
17
- production**. The roadmap calls the popup channel "uncritical"; that refers to business urgency, not
18
- blast radius. Independent `reviewer` mandatory. `sec_reviewer` only on explicit operator request.
19
-
20
- ## SCAFFOLDING NOTICE — READ FIRST
21
- **This channel ships with zero producers, deliberately.** Verified 2026-07-29 across dcm, jg-ferien,
22
- cockpit, hram, spesix and survey_app: **no notification type declares `popup` in `eligible_channels`**,
23
- so `resolve_channels()` will never route anything to it until some app opts a type in. The operator
24
- decided to build the scaffolding anyway (2026-07-29), having been shown this.
25
-
26
- Two obligations follow, and they are part of the acceptance criteria:
27
- 1. **Do not overclaim.** The CHANGELOG entry and any docstring must state plainly that the popup
28
- channel is wired but inactive until a notification type declares `popup`. Precedent: NOTIF-4's
29
- reviewer (R1) had to soften a CHANGELOG that promised retry behaviour no shipped dispatcher could
30
- reach. Do not repeat that.
31
- 2. **Scaffolding must be tested scaffolding.** Register a test-local notification type that declares
32
- `popup` and prove the whole path end-to-end. Untested dead code is not an acceptable outcome.
33
-
34
- ## GOAL
35
- Make `popup` a real delivery channel: a notification whose type opts into `popup` is rendered to the
36
- recipient in a modal that reuses the onboarding **wizard renderer**, with its seen/dismissed status
37
- recorded on `NotificationRecipient` — **not** in the onboarding progress store.
38
-
39
- ## EXPECTED OUTCOME
40
- - dcm's `PopupDispatcher` stops being a logging stub and delivers for real.
41
- - ucm exports a popup surface that renders popup-eligible notifications and marks them via the
42
- existing `feed/mark/` path.
43
- - Onboarding behaviour is bit-for-bit unchanged for existing consumers.
44
- - With no producer registered, nothing visibly changes in any app.
45
-
46
- ## CONTEXT PACKAGE — verified current state
47
-
48
- **dcm `notifications/dispatch.py`:**
49
- - `PopupDispatcher` (`:117-121`) is a stub: logs "queued for unimplemented popup channel" and returns
50
- `DeliveryResult(ok=None, error="pending")`. It is registered in `_DISPATCHERS` (`:126-133`).
51
- - The model to copy is `ChipDispatcher` (`dispatch.py:64-75`, post-NOTIF-13): it calls
52
- `push_to_users([recipient.user], notification_envelope({"type": notification.notification_type, "content": ..., "notification_id": ...}))`
53
- and returns `DeliveryResult(ok=True)`.
54
-
55
- **How NOTIF-13 actually shipped — read this before writing scope D.** The envelope is a
56
- **domain-level** discriminator, not a per-channel one:
57
- - `delivery.py:25` `NOTIFICATION_ENVELOPE = "notification"`; `delivery.py:28-35`
58
- `notification_envelope(payload)` merges `{"envelope": "notification"}` into any payload. **Additive**
59
- — the pre-existing fields, including the overloaded `type`, are unchanged.
60
- - ucm `src/notifications/realtime.jsx:68` routes purely on `data.envelope ?? DEFAULT_ENVELOPE`, then
61
- fans to every handler subscribed to that envelope.
62
- - **Inside** the notification envelope, `NotificationsProvider` keeps the original two-way split:
63
- `data.type === 'notification.status'` patches status (`:98`), and **everything else is appended to
64
- the feed with `unreadCount + 1`** (`:120`).
65
-
66
- **Consequence — the trap this WO must avoid:** if `PopupDispatcher` simply calls
67
- `notification_envelope({...})` like Chip does, its message is **indistinguishable from a chip
68
- message**. The popup surface subscribing to the `"notification"` envelope would receive every chip
69
- notification and pop up for all of them, and a notification routed to *both* chip and popup would
70
- produce **two feed entries and `unreadCount + 2` for one notification**. Scope D and D2 below exist
71
- to close exactly this.
72
- - `_render_content(content, user)` (`:44-60`) renders `title_key`/`body_key`/`params` in the
73
- recipient's language — reuse it rather than re-deriving text client-side if the payload needs text.
74
- - `prefs.py:22` already treats `popup` as a channel defaulting to True; `router.py:24` narrows to
75
- `ntype.eligible_channels`. **No prefs or router change is needed** — only a type opting in.
76
-
77
- **ucm `src/onboarding/OnboardingWizard.jsx` (75 LOC)** is the renderer to reuse:
78
- - MUI `Dialog open fullWidth maxWidth="sm"` (`:59`), `DialogTitle` with a step counter +
79
- `LinearProgress` (`:60-67`), `DialogContent` rendering
80
- `<StepComponent onComplete={...} onDismiss={...} ctx={...} />` (`:69`).
81
- - Step contract: `{ id, Component, blocking, persistDismissed }`. `blocking` disables escape/backdrop
82
- close (`:59`).
83
- - **The parts that must NOT come along** (design decision D-F7,
84
- `django-core-micha/docs/design/notifications-platform.md:70-71`): `onboarding.markStepSeen` (`:35`),
85
- `dismissStep` (`:54`), and the sequential progress store behind them. Popup status lives on
86
- `NotificationRecipient`.
87
- - Exported at `src/index.js:61`; the four existing steps at `:62-65`.
88
-
89
- **ucm notification status API:** `useNotifications()` already returns `markSeen` / `markDismissed` /
90
- `markDone`, backed by `feed/mark/`. Use `markSeen` when a popup is shown and `markDismissed` when the
91
- user closes it. **Never `markDone`** — done is a todo-channel semantic.
92
-
93
- ## SCOPE
94
-
95
- **A. ucm — extract the dialog shell.** Lift the presentational shell out of `OnboardingWizard` into a
96
- reusable renderer (dialog frame, counter/progress header, the `onComplete`/`onDismiss`/`ctx` child
97
- contract). `OnboardingWizard` keeps its current behaviour by composing that shell with the onboarding
98
- store; the popup surface composes the same shell with notification state.
99
-
100
- **B. ucm — the popup surface.** A component that renders popup-eligible notifications through the
101
- shell, marks `seen` on display and `dismissed` on close. For live popups it subscribes via
102
- `useRealtime()` to the **`"notification"` envelope** (`DEFAULT_ENVELOPE` — there is no popup-specific
103
- envelope, see D) and filters on `channel === "popup"`, ignoring `notification.status` messages and
104
- channel-less legacy payloads. Export additively from `src/index.js`.
105
-
106
- **C. Coexistence rule with onboarding — specify and test it.** Both surfaces render a MUI `Dialog`, so
107
- two modals can be open at once. Required behaviour: **a blocking onboarding step wins**; the popup
108
- waits until no blocking onboarding step is active. Non-blocking coexistence must not stack two dialogs.
109
-
110
- **D. dcm — implement `PopupDispatcher.deliver`, with a channel discriminator *inside* the envelope.**
111
- Emit through `notification_envelope()` (so Layer-1 routing stays domain-keyed and messaging can still
112
- ride alongside) and add an explicit **`"channel": "popup"`** field to the payload. Return
113
- `DeliveryResult(ok=True)`. Keep delivery-record semantics identical to `ChipDispatcher` — the
114
- surrounding `dispatch()` machinery already records pending → sent/failed; do not re-implement it.
115
-
116
- **Do NOT invent a second envelope value** (e.g. `"notification.popup"`). The envelope discriminates
117
- *domains* so a future messaging stream can share the socket; overloading it with channels would
118
- undo NOTIF-13's design. Channel selection belongs inside the payload.
119
-
120
- For symmetry and so the client can tell the cases apart, `ChipDispatcher` should carry
121
- `"channel": "chip"` as well. **Backward compatibility:** a payload with **no** `channel` field must
122
- keep behaving exactly as today (feed entry + unread increment), because an app pinned to an older dcm
123
- will still send channel-less payloads.
124
-
125
- **D2. ucm — de-duplicate by `notification_id` in `NotificationsProvider`.** A notification routed to
126
- both chip and popup now produces two WS messages. The provider must fold them into **one** feed entry
127
- and **one** unread increment (de-dupe on `notification_id`, last-write-wins on content). Without this,
128
- enabling popup alongside chip double-counts the badge. This is an edit to `NotificationsProvider`,
129
- which is explicitly in scope here — it is not part of NOTIF-13's realtime core.
130
-
131
- ## DO NOT TOUCH
132
- - **The onboarding progress store** — `markStepSeen`, `dismissStep`, `persistDismissed` persistence,
133
- `OnboardingProvider`'s step-selection semantics, `stepSelection.js`.
134
- - The four existing step components (`CookieConsentStep`, `CompleteNameStep`, `BrowserPushStep`,
135
- `PwaInstallStep`) and their props.
136
- - **NOTIF-13's realtime core** (`src/notifications/realtime.jsx`) — consume `useRealtime()` /
137
- `subscribe(envelope, handler)`, do not restructure it. In particular leave the `seeded` gate, the
138
- backoff/reconnect semantics and the envelope-routing alone. (`NotificationsProvider` itself IS
139
- editable — scope D2 requires it.)
140
- - `feed/*` REST contract, `NotificationBell`, `NotificationSettings`.
141
- - `prefs.py`, `router.py`, `resolve_channels` — popup already works there; a type opting in is an
142
- app-side change, not this WO's.
143
- - Schema, migrations, dependencies, CI.
144
- - **Do not register a `popup` producer in any app.** This WO ships the channel only.
145
-
146
- ## RISKS
147
- - **Onboarding regression** is the main risk: the shell extraction touches a wizard that cockpit and jg
148
- run in production. The step counter (`totalRef`/`completed`), the session-dismissed Set, and the
149
- `blocking` escape-key behaviour are easy to break silently.
150
- - **Stacked modals** if C is skipped — two dialogs, neither reachable.
151
- - **Badge double-count** if D2 is skipped: a notification routed to both chip and popup arrives as two
152
- WS messages and increments `unreadCount` twice for one notification. This is the single most likely
153
- silent defect in this WO.
154
- - **Popup storm** if B subscribes to the envelope without filtering on `channel` — every chip
155
- notification would pop up a modal.
156
- - **Regressing the NOTIF-13 R1 fix:** the `seeded` gate in `NotificationsProvider` exists because
157
- decoupling the REST seed from the socket connect opened a silent-message-loss race. Do not touch it
158
- while implementing D2.
159
- - **Dead code rot:** with no producer, only the tests exercise the path. Hence the mandatory test-local
160
- producer type.
161
- - **Status semantics drift:** marking a popup `done` instead of `dismissed` would pollute todo-channel
162
- semantics.
163
-
164
- ## REQUIRED TESTS / ACCEPTANCE
165
- ucm (`pnpm test`, vitest):
166
- - a popup-eligible notification renders in the shell; `markSeen` fires once on display;
167
- - closing it fires `markDismissed`, never `markDone`;
168
- - **coexistence:** with a blocking onboarding step active, no popup dialog renders; once it clears, the
169
- popup appears — and never two dialogs at once;
170
- - **onboarding regression guard:** existing `OnboardingWizard` behaviour unchanged — step counter,
171
- progress bar, session-dismissed set, `blocking` disabling escape/backdrop close, `persistDismissed`
172
- still calling `dismissStep`;
173
- - with no popup-eligible notification present, nothing renders;
174
- - **no popup storm:** a `channel: "chip"` message and a channel-less legacy message both leave the
175
- popup surface silent;
176
- - **no double-count (D2):** two WS messages for the same `notification_id` (one `chip`, one `popup`)
177
- yield exactly one feed entry and `unreadCount + 1`;
178
- - **NOTIF-13 R1 guard:** the `seeded` gate still holds — no socket messages are processed before the
179
- REST seed completes.
180
-
181
- dcm (`pytest` on the notifications package):
182
- - register a **test-local** notification type declaring `eligible_channels=["popup"]`, call `notify()`,
183
- and assert the dispatcher delivered (`ok=True`) with `envelope: "notification"` **and**
184
- `channel: "popup"` on the wire — the end-to-end proof required by the scaffolding notice;
185
- - a chip delivery still carries `envelope: "notification"` and now `channel: "chip"`, with every other
186
- field unchanged (no regression for existing consumers);
187
- - `resolve_channels` still excludes `popup` for types that do not declare it (no accidental widening);
188
- - the existing `test_dispatch.py` registry assertion (`:25`) still passes.
189
-
190
- Plus `pnpm build` (tsc) green.
191
-
192
- ## RELEASE
193
- ucm minor bump + publish. dcm minor bump + publish (publish-from-main), with a **registry live-check
194
- before any app pin bump**. No app pin bumps in this WO. CHANGELOG in both repos must carry the
195
- inactive-until-a-producer-opts-in wording from the scaffolding notice.
196
-
197
- ## TARGET REPO / WORKING DIRECTORY
198
- - Primary: `C:\Users\biglmi\Documents\webapps\ui-core-micha`
199
- - Secondary (scope D): `C:\Users\biglmi\Documents\webapps\django-core-micha`
200
- - Never the workspace root `…\webapps`.
201
- Both are platform repos: commit directly to the trunk, no feature branches.
202
-
203
- ## PROGRESS CONTRACT
204
- Emit a `PLAN: <step1> | <step2> | …` line up front, then a single-line
205
- `PROGRESS: [<n>/<total>] <present-tense action>` **before every relevant action** (file opened, file
206
- edited, command/test run) and `PROGRESS: [<n>/<total>] done` on step completion, spaced so no gap
207
- exceeds ~2 min. stdout unbuffered. Exactly one final `RESULT: DONE|BLOCKED <reason>`.
208
-
209
- ## MINI-HANDOVER (paste into a fresh Orchestrator session)
210
- ```
211
- Orchestrator: implement work-orders/NOTIF-12.md in ui-core-micha (main; scope D also touches
212
- django-core-micha). The NOTIF-13 gate is satisfied (ucm 2.13.0 / dcm 2.33.0). git pull, read the WO,
213
- then follow orchestrate-codex (Codex-first, own independent review, commit on green).
214
- ```