@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,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
- ```