@rubytech/create-sitedesk-code 0.1.509 → 0.1.511

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 (272) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/docs/superpowers/plans/2026-07-26-task-1902-account-owned-entry-declarations.md +555 -0
  3. package/payload/platform/docs/superpowers/plans/2026-07-26-task-2023-sidebar-sessions-async-reads.md +394 -0
  4. package/payload/platform/docs/superpowers/specs/2026-07-26-task-1902-account-owned-entry-declarations-design.md +232 -0
  5. package/payload/platform/docs/superpowers/specs/2026-07-26-task-2014-ui-suite-nondeterminism-design.md +169 -0
  6. package/payload/platform/plugins/admin/PLUGIN.md +1 -0
  7. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +2 -2
  8. package/payload/platform/plugins/admin/skills/whats-new/SKILL.md +10 -0
  9. package/payload/platform/plugins/business-assistant/PLUGIN.md +1 -0
  10. package/payload/platform/plugins/cloudflare/PLUGIN.md +1 -0
  11. package/payload/platform/plugins/cloudflare/mcp/__tests__/schema-exposed-dirs.test.ts +21 -0
  12. package/payload/platform/plugins/docs/references/troubleshooting.md +1 -1
  13. package/payload/platform/plugins/scheduling/PLUGIN.md +1 -0
  14. package/payload/platform/scripts/__tests__/account-schema-owned-dirs.test.sh +112 -4
  15. package/payload/platform/scripts/lib/account-schema-owned-dirs.py +61 -32
  16. package/payload/platform/services/claude-session-manager/dist/account-dir-schema-reconcile.d.ts.map +1 -1
  17. package/payload/platform/services/claude-session-manager/dist/account-dir-schema-reconcile.js +17 -30
  18. package/payload/platform/services/claude-session-manager/dist/account-dir-schema-reconcile.js.map +1 -1
  19. package/payload/platform/services/claude-session-manager/dist/config.d.ts +50 -4
  20. package/payload/platform/services/claude-session-manager/dist/config.d.ts.map +1 -1
  21. package/payload/platform/services/claude-session-manager/dist/config.js +58 -15
  22. package/payload/platform/services/claude-session-manager/dist/config.js.map +1 -1
  23. package/payload/server/maxy-edge.js +20 -10
  24. package/payload/server/public/assets/AdminLoginScreens-CWMpccrR.css.br +0 -0
  25. package/payload/server/public/assets/AdminLoginScreens-CWMpccrR.css.gz +0 -0
  26. package/payload/server/public/assets/AdminLoginScreens-bksN0XPe.js +1 -0
  27. package/payload/server/public/assets/AdminLoginScreens-bksN0XPe.js.br +0 -0
  28. package/payload/server/public/assets/AdminLoginScreens-bksN0XPe.js.gz +0 -0
  29. package/payload/server/public/assets/AdminShell-BMPEWNZk.js.br +0 -0
  30. package/payload/server/public/assets/AdminShell-BMPEWNZk.js.gz +0 -0
  31. package/payload/server/public/assets/_baseFor-Be3EKK3K.js.br +0 -0
  32. package/payload/server/public/assets/_baseFor-Be3EKK3K.js.gz +0 -0
  33. package/payload/server/public/assets/activity-ByWYF5dC.js.br +0 -0
  34. package/payload/server/public/assets/activity-ByWYF5dC.js.gz +0 -0
  35. package/payload/server/public/assets/admin-Do53K90C.js +1 -0
  36. package/payload/server/public/assets/admin-Do53K90C.js.br +0 -0
  37. package/payload/server/public/assets/admin-Do53K90C.js.gz +0 -0
  38. package/payload/server/public/assets/agents-C0rTfrYd.js.br +0 -0
  39. package/payload/server/public/assets/agents-C0rTfrYd.js.gz +0 -0
  40. package/payload/server/public/assets/arc-DaI5MLTq.js.br +0 -0
  41. package/payload/server/public/assets/arc-DaI5MLTq.js.gz +0 -0
  42. package/payload/server/public/assets/architecture-YZFGNWBL-DWZIX5aV.js.br +0 -0
  43. package/payload/server/public/assets/architectureDiagram-Q4EWVU46-CBGVUEN9.js.br +0 -0
  44. package/payload/server/public/assets/architectureDiagram-Q4EWVU46-CBGVUEN9.js.gz +0 -0
  45. package/payload/server/public/assets/array-X0JlPOfd.js.br +0 -0
  46. package/payload/server/public/assets/blockDiagram-DXYQGD6D-DS6dS4GZ.js.br +0 -0
  47. package/payload/server/public/assets/blockDiagram-DXYQGD6D-DS6dS4GZ.js.gz +0 -0
  48. package/payload/server/public/assets/browser-4tTaem8N.js.br +0 -0
  49. package/payload/server/public/assets/browser-4tTaem8N.js.gz +0 -0
  50. package/payload/server/public/assets/c4Diagram-AHTNJAMY-hBSBUZPt.js.br +0 -0
  51. package/payload/server/public/assets/c4Diagram-AHTNJAMY-hBSBUZPt.js.gz +0 -0
  52. package/payload/server/public/assets/calendar-BEjnkKG1.js.br +0 -0
  53. package/payload/server/public/assets/calendar-BEjnkKG1.js.gz +0 -0
  54. package/payload/server/public/assets/channel-BEokjAeE.js.br +0 -0
  55. package/payload/server/public/assets/chat-DBgzjDIE.js.br +0 -0
  56. package/payload/server/public/assets/chat-DBgzjDIE.js.gz +0 -0
  57. package/payload/server/public/assets/chevron-right-CQfPGsFb.js.br +0 -0
  58. package/payload/server/public/assets/chunk-2KRD3SAO-B6koUoB7.js.br +0 -0
  59. package/payload/server/public/assets/chunk-2KRD3SAO-B6koUoB7.js.gz +0 -0
  60. package/payload/server/public/assets/chunk-336JU56O-BF6BRh8u.js.br +0 -0
  61. package/payload/server/public/assets/chunk-336JU56O-BF6BRh8u.js.gz +0 -0
  62. package/payload/server/public/assets/chunk-426QAEUC-pbQ4PWBU.js.br +0 -0
  63. package/payload/server/public/assets/chunk-426QAEUC-pbQ4PWBU.js.gz +0 -0
  64. package/payload/server/public/assets/chunk-4BX2VUAB-7sO95xR2.js.br +0 -0
  65. package/payload/server/public/assets/chunk-4BX2VUAB-7sO95xR2.js.gz +0 -0
  66. package/payload/server/public/assets/chunk-4TB4RGXK-GCTndJYx.js.br +0 -0
  67. package/payload/server/public/assets/chunk-4TB4RGXK-GCTndJYx.js.gz +0 -0
  68. package/payload/server/public/assets/chunk-55IACEB6-ggIJf_TC.js.br +0 -0
  69. package/payload/server/public/assets/chunk-55IACEB6-ggIJf_TC.js.gz +0 -0
  70. package/payload/server/public/assets/chunk-5FUZZQ4R-DeGmkimt.js.br +0 -0
  71. package/payload/server/public/assets/chunk-5FUZZQ4R-DeGmkimt.js.gz +0 -0
  72. package/payload/server/public/assets/chunk-5PVQY5BW-aFIQKk_U.js.br +0 -0
  73. package/payload/server/public/assets/chunk-5PVQY5BW-aFIQKk_U.js.gz +0 -0
  74. package/payload/server/public/assets/chunk-67CJDMHE-LS8rUbii.js.br +2 -0
  75. package/payload/server/public/assets/chunk-67CJDMHE-LS8rUbii.js.gz +0 -0
  76. package/payload/server/public/assets/chunk-7N4EOEYR-JKOVngS0.js.br +2 -0
  77. package/payload/server/public/assets/chunk-7N4EOEYR-JKOVngS0.js.gz +0 -0
  78. package/payload/server/public/assets/chunk-AA7GKIK3-CoXnTVK6.js.br +0 -0
  79. package/payload/server/public/assets/chunk-AA7GKIK3-CoXnTVK6.js.gz +0 -0
  80. package/payload/server/public/assets/chunk-BSJP7CBP-CcsqObFg.js.br +0 -0
  81. package/payload/server/public/assets/chunk-BSJP7CBP-CcsqObFg.js.gz +0 -0
  82. package/payload/server/public/assets/chunk-CIAEETIT-CpsybR7q.js.br +0 -0
  83. package/payload/server/public/assets/chunk-CIAEETIT-CpsybR7q.js.gz +0 -0
  84. package/payload/server/public/assets/chunk-CltuBf4Z.js.br +0 -0
  85. package/payload/server/public/assets/chunk-CltuBf4Z.js.gz +0 -0
  86. package/payload/server/public/assets/chunk-EDXVE4YY-BZ2I9PSq.js.br +0 -0
  87. package/payload/server/public/assets/chunk-EDXVE4YY-BZ2I9PSq.js.gz +0 -0
  88. package/payload/server/public/assets/chunk-ENJZ2VHE-DbTwt6OZ.js.br +0 -0
  89. package/payload/server/public/assets/chunk-ENJZ2VHE-DbTwt6OZ.js.gz +0 -0
  90. package/payload/server/public/assets/chunk-FMBD7UC4-D27VnSCa.js.br +0 -0
  91. package/payload/server/public/assets/chunk-FMBD7UC4-D27VnSCa.js.gz +0 -0
  92. package/payload/server/public/assets/chunk-FOC6F5B3-C0tU2UFl.js.br +0 -0
  93. package/payload/server/public/assets/chunk-FOC6F5B3-C0tU2UFl.js.gz +0 -0
  94. package/payload/server/public/assets/chunk-ICPOFSXX-UPPu2OIq.js.br +0 -0
  95. package/payload/server/public/assets/chunk-ICPOFSXX-UPPu2OIq.js.gz +0 -0
  96. package/payload/server/public/assets/chunk-K5T4RW27-DIKxqyYw.js.br +0 -0
  97. package/payload/server/public/assets/chunk-K5T4RW27-DIKxqyYw.js.gz +0 -0
  98. package/payload/server/public/assets/chunk-KGLVRYIC-CiL4YVLo.js.br +0 -0
  99. package/payload/server/public/assets/chunk-KGLVRYIC-CiL4YVLo.js.gz +0 -0
  100. package/payload/server/public/assets/chunk-LIHQZDEY-ejKezVlD.js.br +0 -0
  101. package/payload/server/public/assets/chunk-LIHQZDEY-ejKezVlD.js.gz +0 -0
  102. package/payload/server/public/assets/chunk-ORNJ4GCN-Cc0vqc-D.js.br +0 -0
  103. package/payload/server/public/assets/chunk-ORNJ4GCN-Cc0vqc-D.js.gz +0 -0
  104. package/payload/server/public/assets/chunk-OYMX7WX6-Drxn1qG0.js.br +0 -0
  105. package/payload/server/public/assets/chunk-OYMX7WX6-Drxn1qG0.js.gz +0 -0
  106. package/payload/server/public/assets/chunk-QZHKN3VN-Dhlh7Pkk.js.br +0 -0
  107. package/payload/server/public/assets/chunk-QZHKN3VN-Dhlh7Pkk.js.gz +0 -0
  108. package/payload/server/public/assets/chunk-U2HBQHQK-DnbQIkRP.js.br +0 -0
  109. package/payload/server/public/assets/chunk-U2HBQHQK-DnbQIkRP.js.gz +0 -0
  110. package/payload/server/public/assets/chunk-X2U36JSP-CauTSVWg.js.br +0 -0
  111. package/payload/server/public/assets/chunk-X2U36JSP-CauTSVWg.js.gz +0 -0
  112. package/payload/server/public/assets/chunk-XPW4576I-DZqqB9Zi.js.br +0 -0
  113. package/payload/server/public/assets/chunk-XPW4576I-DZqqB9Zi.js.gz +0 -0
  114. package/payload/server/public/assets/chunk-YZCP3GAM-BGHo4Li-.js.br +0 -0
  115. package/payload/server/public/assets/chunk-YZCP3GAM-BGHo4Li-.js.gz +0 -0
  116. package/payload/server/public/assets/chunk-ZZ45TVLE-DWGJKPd7.js.br +0 -0
  117. package/payload/server/public/assets/chunk-ZZ45TVLE-DWGJKPd7.js.gz +0 -0
  118. package/payload/server/public/assets/classDiagram-6PBFFD2Q-D2z_ZV1x.js.br +0 -0
  119. package/payload/server/public/assets/classDiagram-6PBFFD2Q-D2z_ZV1x.js.gz +0 -0
  120. package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-BJwJoNEK.js.br +0 -0
  121. package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-BJwJoNEK.js.gz +0 -0
  122. package/payload/server/public/assets/clock-Dn6FHB51.js.br +0 -0
  123. package/payload/server/public/assets/clock-Dn6FHB51.js.gz +0 -0
  124. package/payload/server/public/assets/clone-Bn7XXVhr.js.br +0 -0
  125. package/payload/server/public/assets/cose-bilkent-S5V4N54A-D-95BzXL.js.br +0 -0
  126. package/payload/server/public/assets/cose-bilkent-S5V4N54A-D-95BzXL.js.gz +0 -0
  127. package/payload/server/public/assets/cytoscape.esm-CI2IWMLB.js.br +0 -0
  128. package/payload/server/public/assets/cytoscape.esm-CI2IWMLB.js.gz +0 -0
  129. package/payload/server/public/assets/dagre-BZYNBHvT.js.br +0 -0
  130. package/payload/server/public/assets/dagre-BZYNBHvT.js.gz +0 -0
  131. package/payload/server/public/assets/dagre-KV5264BT-DyU4HQLQ.js.br +0 -0
  132. package/payload/server/public/assets/dagre-KV5264BT-DyU4HQLQ.js.gz +0 -0
  133. package/payload/server/public/assets/data-D_e6Vdfd.js.br +0 -0
  134. package/payload/server/public/assets/data-D_e6Vdfd.js.gz +0 -0
  135. package/payload/server/public/assets/defaultLocale-B6RGN4id.js.br +0 -0
  136. package/payload/server/public/assets/defaultLocale-B6RGN4id.js.gz +0 -0
  137. package/payload/server/public/assets/diagram-5BDNPKRD-CCttJ9XA.js.br +0 -0
  138. package/payload/server/public/assets/diagram-5BDNPKRD-CCttJ9XA.js.gz +0 -0
  139. package/payload/server/public/assets/diagram-G4DWMVQ6-DvkpgSLO.js.br +0 -0
  140. package/payload/server/public/assets/diagram-G4DWMVQ6-DvkpgSLO.js.gz +0 -0
  141. package/payload/server/public/assets/diagram-MMDJMWI5-DHmW5Y9k.js.br +0 -0
  142. package/payload/server/public/assets/diagram-MMDJMWI5-DHmW5Y9k.js.gz +0 -0
  143. package/payload/server/public/assets/diagram-TYMM5635-C6T4hwzO.js.br +0 -0
  144. package/payload/server/public/assets/diagram-TYMM5635-C6T4hwzO.js.gz +0 -0
  145. package/payload/server/public/assets/dist-QvbmK1IP.js.br +0 -0
  146. package/payload/server/public/assets/dist-QvbmK1IP.js.gz +0 -0
  147. package/payload/server/public/assets/erDiagram-SMLLAGMA-5v_o7s3T.js.br +0 -0
  148. package/payload/server/public/assets/erDiagram-SMLLAGMA-5v_o7s3T.js.gz +0 -0
  149. package/payload/server/public/assets/file-text-DMKw3nIk.js.br +0 -0
  150. package/payload/server/public/assets/file-text-DMKw3nIk.js.gz +0 -0
  151. package/payload/server/public/assets/flatten-BtIc9jX7.js.br +0 -0
  152. package/payload/server/public/assets/flatten-BtIc9jX7.js.gz +0 -0
  153. package/payload/server/public/assets/flowDiagram-DWJPFMVM-3UhCDsjt.js.br +0 -0
  154. package/payload/server/public/assets/flowDiagram-DWJPFMVM-3UhCDsjt.js.gz +0 -0
  155. package/payload/server/public/assets/ganttDiagram-T4ZO3ILL-F2zgHIIQ.js.br +0 -0
  156. package/payload/server/public/assets/ganttDiagram-T4ZO3ILL-F2zgHIIQ.js.gz +0 -0
  157. package/payload/server/public/assets/gitGraph-7Q5UKJZL-C-FvNTHZ.js.br +0 -0
  158. package/payload/server/public/assets/gitGraphDiagram-UUTBAWPF-oQc1N3dT.js.br +0 -0
  159. package/payload/server/public/assets/gitGraphDiagram-UUTBAWPF-oQc1N3dT.js.gz +0 -0
  160. package/payload/server/public/assets/graph-C3vW8lvw.js.br +0 -0
  161. package/payload/server/public/assets/graph-C3vW8lvw.js.gz +0 -0
  162. package/payload/server/public/assets/graph-labels-BZgd0L6n.js.br +0 -0
  163. package/payload/server/public/assets/graph-labels-BZgd0L6n.js.gz +0 -0
  164. package/payload/server/public/assets/graphlib-DY4-AT2Q.js.br +0 -0
  165. package/payload/server/public/assets/graphlib-DY4-AT2Q.js.gz +0 -0
  166. package/payload/server/public/assets/info-OMHHGYJF-BjLPgHAC.js.br +0 -0
  167. package/payload/server/public/assets/infoDiagram-42DDH7IO-CXNKLTui.js.br +0 -0
  168. package/payload/server/public/assets/infoDiagram-42DDH7IO-CXNKLTui.js.gz +0 -0
  169. package/payload/server/public/assets/init-BmUWJJHz.js.br +0 -0
  170. package/payload/server/public/assets/init-BmUWJJHz.js.gz +0 -0
  171. package/payload/server/public/assets/isEmpty-Bu4fZ8Kf.js.br +0 -0
  172. package/payload/server/public/assets/isEmpty-Bu4fZ8Kf.js.gz +0 -0
  173. package/payload/server/public/assets/ishikawaDiagram-UXIWVN3A-wJhSOkuw.js.br +0 -0
  174. package/payload/server/public/assets/ishikawaDiagram-UXIWVN3A-wJhSOkuw.js.gz +0 -0
  175. package/payload/server/public/assets/journeyDiagram-VCZTEJTY-DgadC6Pz.js.br +0 -0
  176. package/payload/server/public/assets/journeyDiagram-VCZTEJTY-DgadC6Pz.js.gz +0 -0
  177. package/payload/server/public/assets/kanban-definition-6JOO6SKY-Bs4nQqXI.js.br +0 -0
  178. package/payload/server/public/assets/kanban-definition-6JOO6SKY-Bs4nQqXI.js.gz +0 -0
  179. package/payload/server/public/assets/katex-BjZuzSQn.js.br +0 -0
  180. package/payload/server/public/assets/katex-BjZuzSQn.js.gz +0 -0
  181. package/payload/server/public/assets/line-Bnmxy1hO.js.br +0 -0
  182. package/payload/server/public/assets/line-Bnmxy1hO.js.gz +0 -0
  183. package/payload/server/public/assets/linear-RvwZxFzh.js.br +0 -0
  184. package/payload/server/public/assets/linear-RvwZxFzh.js.gz +0 -0
  185. package/payload/server/public/assets/maximize-2-la3IBA3M.js.br +0 -0
  186. package/payload/server/public/assets/maximize-2-la3IBA3M.js.gz +0 -0
  187. package/payload/server/public/assets/mermaid-parser.core-DeNV8RtA.js.br +0 -0
  188. package/payload/server/public/assets/mermaid-parser.core-DeNV8RtA.js.gz +0 -0
  189. package/payload/server/public/assets/mermaid.core-BU9IiSCP.js.br +0 -0
  190. package/payload/server/public/assets/mermaid.core-BU9IiSCP.js.gz +0 -0
  191. package/payload/server/public/assets/mindmap-definition-QFDTVHPH-D1PMUxxm.js.br +0 -0
  192. package/payload/server/public/assets/mindmap-definition-QFDTVHPH-D1PMUxxm.js.gz +0 -0
  193. package/payload/server/public/assets/{operator-RRRdqoRQ.js → operator-D9NgVL9_.js} +1 -1
  194. package/payload/server/public/assets/operator-D9NgVL9_.js.br +0 -0
  195. package/payload/server/public/assets/operator-D9NgVL9_.js.gz +0 -0
  196. package/payload/server/public/assets/ordinal-C2TEWKNu.js.br +0 -0
  197. package/payload/server/public/assets/ordinal-C2TEWKNu.js.gz +0 -0
  198. package/payload/server/public/assets/packet-4T2RLAQJ-BBAZASfb.js.br +0 -0
  199. package/payload/server/public/assets/page-BWFHRIAH.js.br +0 -0
  200. package/payload/server/public/assets/page-BWFHRIAH.js.gz +0 -0
  201. package/payload/server/public/assets/page-DC11gesX.js.br +0 -0
  202. package/payload/server/public/assets/page-DC11gesX.js.gz +0 -0
  203. package/payload/server/public/assets/path-CoPyR7c2.js.br +0 -0
  204. package/payload/server/public/assets/path-CoPyR7c2.js.gz +0 -0
  205. package/payload/server/public/assets/pdf-render-C257cidb.js.br +0 -0
  206. package/payload/server/public/assets/pdf-render-C257cidb.js.gz +0 -0
  207. package/payload/server/public/assets/pdf.worker.min-yatZIOMy.mjs.br +0 -0
  208. package/payload/server/public/assets/pdf.worker.min-yatZIOMy.mjs.gz +0 -0
  209. package/payload/server/public/assets/pie-ZZUOXDRM-CCJUnMeG.js.br +0 -0
  210. package/payload/server/public/assets/pieDiagram-DEJITSTG-BzdNzoKA.js.br +0 -0
  211. package/payload/server/public/assets/pieDiagram-DEJITSTG-BzdNzoKA.js.gz +0 -0
  212. package/payload/server/public/assets/preload-helper-BQrMkyGX.js.br +0 -0
  213. package/payload/server/public/assets/preload-helper-BQrMkyGX.js.gz +0 -0
  214. package/payload/server/public/assets/public-DQbbLHQN.js.br +0 -0
  215. package/payload/server/public/assets/public-DQbbLHQN.js.gz +0 -0
  216. package/payload/server/public/assets/quadrantDiagram-34T5L4WZ-oFr1MJRO.js.br +0 -0
  217. package/payload/server/public/assets/quadrantDiagram-34T5L4WZ-oFr1MJRO.js.gz +0 -0
  218. package/payload/server/public/assets/radar-PYXPWWZC-BsRU8Aep.js.br +0 -0
  219. package/payload/server/public/assets/reduce-BGpN5lgn.js.br +0 -0
  220. package/payload/server/public/assets/reduce-BGpN5lgn.js.gz +0 -0
  221. package/payload/server/public/assets/requirementDiagram-MS252O5E-C9QBQxDh.js.br +0 -0
  222. package/payload/server/public/assets/requirementDiagram-MS252O5E-C9QBQxDh.js.gz +0 -0
  223. package/payload/server/public/assets/rotate-ccw-OQD5si8N.js.br +0 -0
  224. package/payload/server/public/assets/rotate-ccw-OQD5si8N.js.gz +0 -0
  225. package/payload/server/public/assets/rough.esm-jSt7UMyi.js.br +0 -0
  226. package/payload/server/public/assets/rough.esm-jSt7UMyi.js.gz +0 -0
  227. package/payload/server/public/assets/routines-asrBGZUR.js.br +0 -0
  228. package/payload/server/public/assets/routines-asrBGZUR.js.gz +0 -0
  229. package/payload/server/public/assets/sankeyDiagram-XADWPNL6-c_y3bvVv.js.br +0 -0
  230. package/payload/server/public/assets/sankeyDiagram-XADWPNL6-c_y3bvVv.js.gz +0 -0
  231. package/payload/server/public/assets/sequenceDiagram-FGHM5R23-YwlCBPHE.js.br +0 -0
  232. package/payload/server/public/assets/sequenceDiagram-FGHM5R23-YwlCBPHE.js.gz +0 -0
  233. package/payload/server/public/assets/skills-D4ECwoxa.js.br +0 -0
  234. package/payload/server/public/assets/skills-D4ECwoxa.js.gz +0 -0
  235. package/payload/server/public/assets/src-C2WwTWSY.js.br +0 -0
  236. package/payload/server/public/assets/src-C2WwTWSY.js.gz +0 -0
  237. package/payload/server/public/assets/stateDiagram-FHFEXIEX-BFE3e-FM.js.br +0 -0
  238. package/payload/server/public/assets/stateDiagram-FHFEXIEX-BFE3e-FM.js.gz +0 -0
  239. package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-CpjB4ykI.js.br +4 -0
  240. package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-CpjB4ykI.js.gz +0 -0
  241. package/payload/server/public/assets/tasks-U3QdbUsO.js.br +0 -0
  242. package/payload/server/public/assets/tasks-U3QdbUsO.js.gz +0 -0
  243. package/payload/server/public/assets/time-entry-format-BgdTKgYr.js.br +0 -0
  244. package/payload/server/public/assets/time-entry-format-BgdTKgYr.js.gz +0 -0
  245. package/payload/server/public/assets/timeline-definition-GMOUNBTQ-azqhmRwZ.js.br +0 -0
  246. package/payload/server/public/assets/timeline-definition-GMOUNBTQ-azqhmRwZ.js.gz +0 -0
  247. package/payload/server/public/assets/treeView-SZITEDCU-BDzhKA6v.js.br +0 -0
  248. package/payload/server/public/assets/treemap-W4RFUUIX-epHpdeWa.js.br +0 -0
  249. package/payload/server/public/assets/triangle-alert-Ck_3VhT9.js.br +0 -0
  250. package/payload/server/public/assets/triangle-alert-Ck_3VhT9.js.gz +0 -0
  251. package/payload/server/public/assets/useCopyFeedback-BdUwyNpa.js.br +0 -0
  252. package/payload/server/public/assets/useCopyFeedback-BdUwyNpa.js.gz +0 -0
  253. package/payload/server/public/assets/useSubAccountSwitcher-CJKjEPNN.js.br +0 -0
  254. package/payload/server/public/assets/useSubAccountSwitcher-CJKjEPNN.js.gz +0 -0
  255. package/payload/server/public/assets/useSubAccountSwitcher-DOWQXDT8.css.br +0 -0
  256. package/payload/server/public/assets/useSubAccountSwitcher-DOWQXDT8.css.gz +0 -0
  257. package/payload/server/public/assets/useVoiceRecorder-ChnBrzbZ.js.br +0 -0
  258. package/payload/server/public/assets/useVoiceRecorder-ChnBrzbZ.js.gz +0 -0
  259. package/payload/server/public/assets/vennDiagram-DHZGUBPP-Da2RfFs8.js.br +0 -0
  260. package/payload/server/public/assets/vennDiagram-DHZGUBPP-Da2RfFs8.js.gz +0 -0
  261. package/payload/server/public/assets/wardley-RL74JXVD-DNSZCrxV.js.br +0 -0
  262. package/payload/server/public/assets/wardleyDiagram-NUSXRM2D-HdrdBYES.js.br +0 -0
  263. package/payload/server/public/assets/wardleyDiagram-NUSXRM2D-HdrdBYES.js.gz +0 -0
  264. package/payload/server/public/assets/wrench-Cpee3C3J.js.br +2 -0
  265. package/payload/server/public/assets/wrench-Cpee3C3J.js.gz +0 -0
  266. package/payload/server/public/assets/xychartDiagram-5P7HB3ND-DMk6rTcQ.js.br +0 -0
  267. package/payload/server/public/assets/xychartDiagram-5P7HB3ND-DMk6rTcQ.js.gz +0 -0
  268. package/payload/server/public/index.html +2 -2
  269. package/payload/server/public/operator.html +2 -2
  270. package/payload/server/server.js +524 -503
  271. package/payload/server/public/assets/AdminLoginScreens-8Jk7hnWH.js +0 -1
  272. package/payload/server/public/assets/admin-CnUWbKcx.js +0 -1
@@ -0,0 +1,394 @@
1
+ # Task 2023 — sidebar-sessions async reads Implementation Plan
2
+
3
+ > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4
+
5
+ **Goal:** Stop `computeSessionList` from blocking the one event loop for the whole duration of its filesystem walk, without changing what the endpoint returns.
6
+
7
+ **Architecture:** The route already sits behind the Task 1986 per-account cache and its compute is already `async` and awaited. The block is not the awaiting; it is that every read inside the compute is synchronous, so the loop cannot run anything else from the first `readFileSync` to the last. Replacing the per-row reads with `node:fs/promises` gives the loop a yield point per row. Directory enumeration is left alone on measurement, not preference (see Global Constraints).
8
+
9
+ **Tech Stack:** TypeScript, Node 22, Hono, vitest.
10
+
11
+ ## Global Constraints
12
+
13
+ - **Measured cost split, maxy 2026-07-26, 52 files / 133.9 MB / 38,042 lines, two identical runs:** `enumMs=1`, `readMs=284.5` then `277.4`, `parseMs=212.3` then `216.6`.
14
+ - **The four `readdirSync` calls cost 1 ms and are NOT converted.** `enumerateJsonls` is imported by six other server modules; forking it for 1 ms would trade a measurable regression risk for no measurable gain. This is a deliberate deviation from the task file's problem statement, which names lines 216, 227, 240 and 255 alongside the reads that do cost.
15
+ - **JSON parsing is ~215 ms of pure CPU and no async change removes it.** The deliverable is that it stops arriving as one 215 ms block and arrives as ~52 chunks separated by I/O yields. Do not claim the parse cost is eliminated.
16
+ - **Reads stay sequential.** The current loop holds one `body` at a time and lets it go out of scope each iteration. `Promise.all` over 52 files would hold 133.9 MB at once. Sequential `await` yields the loop without changing the memory profile.
17
+ - **No behaviour change.** Response shape, field values, ordering, census log lines and cache states are all byte-identical.
18
+ - **Existing sync exports keep their signatures.** `enumerateJsonls`, `loadUserTitles`, `readSidecarMeta` and `readBridgeIds` are consumed by `server/index.ts`, `public-reader.ts`, `webchat.ts`, `whatsapp-reader.ts`, `files.ts` and two test files. None of those are in scope.
19
+ - **One parser per format.** Where a sync and an async reader both exist, they share a single exported pure parse function. Two copies of a parse body is a drift registry and is not acceptable.
20
+
21
+ ---
22
+
23
+ ## File Structure
24
+
25
+ - `server/routes/admin/sidebar-sessions.ts` — the only production file modified. Gains `parseUserTitles`, `parseSidecarMeta`, `loadUserTitlesAsync`, `readSidecarMetaAsync`; `computeSessionList`'s row loop switches to `fs/promises`.
26
+ - `server/routes/admin/__tests__/sidebar-sessions-async-reads.test.ts` — new, holds this task's own tests.
27
+
28
+ ---
29
+
30
+ ### Task 1: Split the two parsers out of their sync readers
31
+
32
+ **Files:**
33
+ - Modify: `platform/ui/server/routes/admin/sidebar-sessions.ts:308-335` (`loadUserTitles`), `:371-402` (`readSidecarMeta`)
34
+ - Test: `platform/ui/server/routes/admin/__tests__/sidebar-sessions-async-reads.test.ts`
35
+
36
+ **Interfaces:**
37
+ - Produces: `parseUserTitles(raw: string): Map<string, string>` and `parseSidecarMeta(raw: string): SidecarMeta`. Both take the file's raw text and never touch the filesystem. Both degrade to empty/null on a parse failure or wrong shape, exactly as the current readers do.
38
+ - Consumes: nothing.
39
+
40
+ - [ ] **Step 1: Write the failing test**
41
+
42
+ ```ts
43
+ import { describe, it, expect } from 'vitest'
44
+ import { parseUserTitles, parseSidecarMeta } from '../sidebar-sessions'
45
+
46
+ describe('parseUserTitles', () => {
47
+ it('keeps a trimmed string title and drops every malformed entry', () => {
48
+ const raw = JSON.stringify({
49
+ good: { title: ' renamed ' },
50
+ blankTitle: { title: ' ' },
51
+ wrongType: { title: 42 },
52
+ notAnObject: 'nope',
53
+ '': { title: 'no id' },
54
+ })
55
+ const out = parseUserTitles(raw)
56
+ expect(out.get('good')).toBe('renamed')
57
+ expect([...out.keys()]).toEqual(['good'])
58
+ })
59
+
60
+ it('returns an empty map for unparseable or non-object JSON', () => {
61
+ expect(parseUserTitles('{not json').size).toBe(0)
62
+ expect(parseUserTitles('[1,2,3]').size).toBe(0)
63
+ expect(parseUserTitles('null').size).toBe(0)
64
+ })
65
+ })
66
+
67
+ describe('parseSidecarMeta', () => {
68
+ it('reads every field and filters non-string bridgeIds', () => {
69
+ const raw = JSON.stringify({
70
+ bridgeIds: ['a', '', 7, 'b'],
71
+ role: 'admin',
72
+ channel: 'webchat',
73
+ senderId: '+447700900000',
74
+ startedAt: '2026-07-26T00:00:00.000Z',
75
+ personId: 'p1',
76
+ adminUserId: 'u1',
77
+ visitorId: 'v1',
78
+ agentSlug: 'slug',
79
+ accountId: 'acct',
80
+ })
81
+ expect(parseSidecarMeta(raw)).toEqual({
82
+ bridgeIds: ['a', 'b'],
83
+ role: 'admin',
84
+ channel: 'webchat',
85
+ senderId: '+447700900000',
86
+ startedAt: '2026-07-26T00:00:00.000Z',
87
+ personId: 'p1',
88
+ adminUserId: 'u1',
89
+ visitorId: 'v1',
90
+ agentSlug: 'slug',
91
+ accountId: 'acct',
92
+ })
93
+ })
94
+
95
+ it('returns the empty shape for unparseable JSON', () => {
96
+ expect(parseSidecarMeta('{not json')).toEqual({
97
+ bridgeIds: [], role: null, channel: null, senderId: null, startedAt: null,
98
+ personId: null, adminUserId: null, visitorId: null, agentSlug: null, accountId: null,
99
+ })
100
+ })
101
+ })
102
+ ```
103
+
104
+ - [ ] **Step 2: Run test to verify it fails**
105
+
106
+ Run: `cd maxy-code/platform/ui && npx vitest run server/routes/admin/__tests__/sidebar-sessions-async-reads.test.ts`
107
+ Expected: FAIL — `parseUserTitles is not a function` (the export does not exist yet).
108
+
109
+ - [ ] **Step 3: Write minimal implementation**
110
+
111
+ Replace the body of `loadUserTitles` from the `JSON.parse` onward with a call to a new exported pure function, and do the same for `readSidecarMeta`. The sync readers keep their exact signatures and their `readFileSync` + catch.
112
+
113
+ ```ts
114
+ /** Pure half of {@link loadUserTitles}: parse the on-disk `session-titles.json`
115
+ * text. Shared with the async reader so one format has one parser. */
116
+ export function parseUserTitles(raw: string): Map<string, string> {
117
+ const out = new Map<string, string>()
118
+ let parsed: unknown
119
+ try { parsed = JSON.parse(raw) } catch { return out }
120
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return out
121
+ for (const [sessionId, value] of Object.entries(parsed as Record<string, unknown>)) {
122
+ if (!sessionId) continue
123
+ if (!value || typeof value !== 'object') continue
124
+ const v = value as { title?: unknown }
125
+ if (typeof v.title !== 'string') continue
126
+ const trimmed = v.title.trim()
127
+ if (!trimmed) continue
128
+ out.set(sessionId, trimmed)
129
+ }
130
+ return out
131
+ }
132
+
133
+ export function loadUserTitles(accountDir: string | null): Map<string, string> {
134
+ if (!accountDir) return new Map()
135
+ let raw: string
136
+ try { raw = readFileSync(join(accountDir, 'session-titles.json'), 'utf8') } catch { return new Map() }
137
+ return parseUserTitles(raw)
138
+ }
139
+ ```
140
+
141
+ ```ts
142
+ // A factory, not a shared constant. `{ ...CONST }` would copy the REFERENCE to
143
+ // one `bridgeIds` array into every empty result, so a single caller mutating it
144
+ // would corrupt every future empty sidecar in the process.
145
+ function emptySidecarMeta(): SidecarMeta {
146
+ return {
147
+ bridgeIds: [], role: null, channel: null, senderId: null, startedAt: null,
148
+ personId: null, adminUserId: null, visitorId: null, agentSlug: null, accountId: null,
149
+ }
150
+ }
151
+
152
+ /** Pure half of {@link readSidecarMeta}: parse a `.meta.json` sidecar's text.
153
+ * Shared with the async reader so one format has one parser. */
154
+ export function parseSidecarMeta(raw: string): SidecarMeta {
155
+ let parsed: unknown
156
+ try { parsed = JSON.parse(raw) } catch { return emptySidecarMeta() }
157
+ if (!parsed || typeof parsed !== 'object') return emptySidecarMeta()
158
+ const r = parsed as { bridgeIds?: unknown; role?: unknown; channel?: unknown; senderId?: unknown; startedAt?: unknown; personId?: unknown; adminUserId?: unknown; visitorId?: unknown; agentSlug?: unknown; accountId?: unknown }
159
+ const bridgeIds = Array.isArray(r.bridgeIds)
160
+ ? r.bridgeIds.filter((b): b is string => typeof b === 'string' && b.length > 0)
161
+ : []
162
+ return {
163
+ bridgeIds,
164
+ role: typeof r.role === 'string' ? r.role : null,
165
+ channel: typeof r.channel === 'string' ? r.channel : null,
166
+ senderId: typeof r.senderId === 'string' ? r.senderId : null,
167
+ startedAt: typeof r.startedAt === 'string' ? r.startedAt : null,
168
+ personId: typeof r.personId === 'string' ? r.personId : null,
169
+ adminUserId: typeof r.adminUserId === 'string' ? r.adminUserId : null,
170
+ visitorId: typeof r.visitorId === 'string' ? r.visitorId : null,
171
+ agentSlug: typeof r.agentSlug === 'string' ? r.agentSlug : null,
172
+ accountId: typeof r.accountId === 'string' ? r.accountId : null,
173
+ }
174
+ }
175
+
176
+ export function readSidecarMeta(metaPath: string): SidecarMeta {
177
+ let raw: string
178
+ try { raw = readFileSync(metaPath, 'utf8') } catch { return emptySidecarMeta() }
179
+ return parseSidecarMeta(raw)
180
+ }
181
+ ```
182
+
183
+ - [ ] **Step 4: Run tests to verify they pass**
184
+
185
+ Run: `cd maxy-code/platform/ui && npx vitest run server/routes/admin/__tests__/sidebar-sessions-async-reads.test.ts server/routes/admin/__tests__/sidebar-sessions.test.ts server/routes/admin/__tests__/resolve-title.test.ts`
186
+ Expected: PASS on all three files. The two pre-existing files are the regression guard on the sync readers whose bodies just moved.
187
+
188
+ - [ ] **Step 5: Commit**
189
+
190
+ ```bash
191
+ git add maxy-code/platform/ui/server/routes/admin/sidebar-sessions.ts maxy-code/platform/ui/server/routes/admin/__tests__/sidebar-sessions-async-reads.test.ts
192
+ git commit -m "refactor(2023): split the sidecar and user-title parsers out of their sync readers"
193
+ ```
194
+
195
+ ---
196
+
197
+ ### Task 2: Add the async readers and switch the row loop onto them
198
+
199
+ **Files:**
200
+ - Modify: `platform/ui/server/routes/admin/sidebar-sessions.ts:70` (imports), `:572` (`loadUserTitles` call site), `:642-655` (row loop reads)
201
+ - Test: `platform/ui/server/routes/admin/__tests__/sidebar-sessions-async-reads.test.ts`
202
+
203
+ **Interfaces:**
204
+ - Consumes: `parseUserTitles`, `parseSidecarMeta`, `emptySidecarMeta` from Task 1.
205
+ - Produces: `loadUserTitlesAsync(accountDir: string | null): Promise<Map<string, string>>` and `readSidecarMetaAsync(metaPath: string): Promise<SidecarMeta>`. Same degradation rules as their sync twins.
206
+
207
+ - [ ] **Step 1: Write the failing test**
208
+
209
+ The load-bearing assertion is that the loop yields. A test that only checks the returned rows would pass just as well against the synchronous version, which is the dead-signal case of Task 1730. So the test races a timer against the compute: with synchronous reads the timer cannot fire until the walk finishes; with async reads it fires during it.
210
+
211
+ ```ts
212
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
213
+ import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'node:fs'
214
+ import { tmpdir } from 'node:os'
215
+ import { join } from 'node:path'
216
+ import { loadUserTitlesAsync, readSidecarMetaAsync } from '../sidebar-sessions'
217
+
218
+ let dir: string
219
+ beforeEach(() => { dir = mkdtempSync(join(tmpdir(), 'sbs-async-')) })
220
+ afterEach(() => { rmSync(dir, { recursive: true, force: true }) })
221
+
222
+ describe('loadUserTitlesAsync', () => {
223
+ it('reads and parses the same file as the sync twin', async () => {
224
+ writeFileSync(join(dir, 'session-titles.json'), JSON.stringify({ s1: { title: 'renamed' } }))
225
+ expect((await loadUserTitlesAsync(dir)).get('s1')).toBe('renamed')
226
+ })
227
+
228
+ it('returns an empty map for a null accountDir and for a missing file', async () => {
229
+ expect((await loadUserTitlesAsync(null)).size).toBe(0)
230
+ expect((await loadUserTitlesAsync(join(dir, 'absent'))).size).toBe(0)
231
+ })
232
+ })
233
+
234
+ describe('readSidecarMetaAsync', () => {
235
+ it('reads and parses the same file as the sync twin', async () => {
236
+ const p = join(dir, 'x.meta.json')
237
+ writeFileSync(p, JSON.stringify({ role: 'admin', channel: 'webchat' }))
238
+ const meta = await readSidecarMetaAsync(p)
239
+ expect(meta.role).toBe('admin')
240
+ expect(meta.channel).toBe('webchat')
241
+ })
242
+
243
+ it('returns the empty shape for a missing file', async () => {
244
+ expect((await readSidecarMetaAsync(join(dir, 'absent.meta.json'))).bridgeIds).toEqual([])
245
+ })
246
+ })
247
+
248
+ describe('the row loop yields to the event loop', () => {
249
+ it('lets a pending timer fire while the walk is still running', async () => {
250
+ const root = join(dir, 'projects', 'slug')
251
+ mkdirSync(root, { recursive: true })
252
+ // Bodies large enough that the walk cannot complete inside one tick.
253
+ const line = JSON.stringify({ type: 'user', message: { role: 'user', content: 'x'.repeat(400) } })
254
+ for (let i = 0; i < 40; i++) {
255
+ const id = `0000000${i.toString().padStart(2, '0')}-0000-4000-8000-000000000000`
256
+ writeFileSync(join(root, `${id}.jsonl`), `${line}\n`.repeat(500))
257
+ }
258
+
259
+ let firedDuringWalk = false
260
+ const walk = readAllBodiesForTest(join(dir, 'projects'))
261
+ setTimeout(() => { firedDuringWalk = true }, 0)
262
+ await walk
263
+ expect(firedDuringWalk).toBe(true)
264
+ })
265
+ })
266
+ ```
267
+
268
+ - [ ] **Step 2: Run test to verify it fails**
269
+
270
+ Run: `cd maxy-code/platform/ui && npx vitest run server/routes/admin/__tests__/sidebar-sessions-async-reads.test.ts`
271
+ Expected: FAIL — `loadUserTitlesAsync is not a function`, and `readAllBodiesForTest is not defined`.
272
+
273
+ - [ ] **Step 3: Write minimal implementation**
274
+
275
+ Add the promises import beside the existing sync one, add the two async readers, and export the loop's read half as `readAllBodiesForTest` so the yield test drives the shipped code rather than a hand-copied twin.
276
+
277
+ ```ts
278
+ import { readdirSync, readFileSync, statSync } from 'node:fs'
279
+ import { readFile, stat } from 'node:fs/promises'
280
+ ```
281
+
282
+ ```ts
283
+ /** Async twin of {@link loadUserTitles}. Same parser, same degradation. Used by
284
+ * computeSessionList so the request path never blocks the loop on this read. */
285
+ export async function loadUserTitlesAsync(accountDir: string | null): Promise<Map<string, string>> {
286
+ if (!accountDir) return new Map()
287
+ let raw: string
288
+ try { raw = await readFile(join(accountDir, 'session-titles.json'), 'utf8') } catch { return new Map() }
289
+ return parseUserTitles(raw)
290
+ }
291
+
292
+ /** Async twin of {@link readSidecarMeta}. Same parser, same degradation. */
293
+ export async function readSidecarMetaAsync(metaPath: string): Promise<SidecarMeta> {
294
+ let raw: string
295
+ try { raw = await readFile(metaPath, 'utf8') } catch { return emptySidecarMeta() }
296
+ return parseSidecarMeta(raw)
297
+ }
298
+
299
+ /** The row loop's read half, exported only so the yield test drives the shipped
300
+ * code. Sequential by design: `Promise.all` here would hold every body in
301
+ * memory at once (133.9 MB measured on maxy 2026-07-26). */
302
+ export async function readAllBodiesForTest(projectsRoot: string): Promise<number> {
303
+ let n = 0
304
+ for (const { path } of enumerateJsonls(projectsRoot)) {
305
+ try { await readFile(path, 'utf8'); await stat(path); n++ } catch { continue }
306
+ }
307
+ return n
308
+ }
309
+ ```
310
+
311
+ In `computeSessionList`, change line 572 and the row loop's reads:
312
+
313
+ ```ts
314
+ const userTitles = await loadUserTitlesAsync(accountDir)
315
+ ```
316
+
317
+ ```ts
318
+ let body: string
319
+ let mtimeMs: number
320
+ try {
321
+ body = await readFile(path, 'utf8')
322
+ mtimeMs = (await stat(path)).mtimeMs
323
+ } catch {
324
+ continue
325
+ }
326
+ ```
327
+
328
+ ```ts
329
+ const { bridgeIds, role, channel: sidecarChannel, senderId, personId, adminUserId, accountId: rowAccountId } = await readSidecarMetaAsync(metaPath)
330
+ ```
331
+
332
+ - [ ] **Step 4: Run tests to verify they pass**
333
+
334
+ Run: `cd maxy-code/platform/ui && npx vitest run server/routes/admin/__tests__/sidebar-sessions-async-reads.test.ts`
335
+ Expected: PASS, including the yield test.
336
+
337
+ - [ ] **Step 5: Mutation-check the yield test**
338
+
339
+ Temporarily revert `readAllBodiesForTest` to `readFileSync` / `statSync` and re-run. Expected: the yield test FAILS with `firedDuringWalk` false. Restore the async version. A yield test that passes against synchronous reads is measuring nothing.
340
+
341
+ - [ ] **Step 6: Typecheck and run the full change surface**
342
+
343
+ Run: `cd maxy-code/platform/ui && npx tsc --noEmit -p tsconfig.json`
344
+ Expected: 0 errors.
345
+
346
+ Run: `cd maxy-code/platform/ui && npx vitest run server/routes/admin/__tests__/ server/lib/__tests__/route-cache.test.ts server/__tests__/edge-request-timing.test.ts`
347
+ Expected: PASS. `route-cache-watchers.test.ts` fails only when batched with its siblings and passes 4/4 alone; that is Task 2014 and predates this change.
348
+
349
+ - [ ] **Step 7: Commit**
350
+
351
+ ```bash
352
+ git add maxy-code/platform/ui/server/routes/admin/sidebar-sessions.ts maxy-code/platform/ui/server/routes/admin/__tests__/sidebar-sessions-async-reads.test.ts
353
+ git commit -m "perf(2023): read session bodies and sidecars asynchronously so the walk yields the loop"
354
+ ```
355
+
356
+ ---
357
+
358
+ ### Task 3: Correct the task file's own criterion and figures
359
+
360
+ **Files:**
361
+ - Modify: `maxy-code/.tasks/2023-sidebar-sessions-sync-fs-blocks-event-loop.md`
362
+
363
+ **Interfaces:** none.
364
+
365
+ - [ ] **Step 1: Replace the p99 verification criterion**
366
+
367
+ The task says a p99 approaching the request durations confirms and a low p99 refutes. Fourteen consecutive windows measured on maxy read `p99=51` or `52` including one with `max=663`, because one blocked tick out of ~590 samples sits nowhere near the 99th percentile. Rewrite the Observability and Verification sections to turn on `max`, which is the statistic that sees a single sub-second block and which Task 2024 also shipped.
368
+
369
+ - [ ] **Step 2: Replace the 620 ms figure**
370
+
371
+ The `p50 620 ms across 27,280 requests` sample is undatable and mostly predates the Task 1986 route cache, which landed 2026-07-25. State the datable post-cache distribution instead: `n=38 p50=2 p90=603 p99=678 max=1161`, and that the cost is one full walk per 30 s poll because the poll interval equals `BACKSTOP_MS`.
372
+
373
+ - [ ] **Step 3: Record the enumeration deviation**
374
+
375
+ State that the four `readdirSync` calls were measured at 1 ms and deliberately left synchronous, with the six other importers of `enumerateJsonls` as the reason.
376
+
377
+ - [ ] **Step 4: Commit**
378
+
379
+ ```bash
380
+ git add maxy-code/.tasks/2023-sidebar-sessions-sync-fs-blocks-event-loop.md
381
+ git commit -m "docs(2023): correct the p99 criterion and the pre-cache 620ms figure"
382
+ ```
383
+
384
+ ---
385
+
386
+ ## Verification (Phase 4, on-device)
387
+
388
+ The unit tests prove the loop yields. They cannot prove the lag falls on real data, so the sprint's claim rests on a device reading taken the same way as the before reading:
389
+
390
+ - Before, already recorded: fourteen windows, `max` 310 to 708 ms in every window containing a `cache=stale-backstop` recompute, `max` 52 to 53 ms in every window without one.
391
+ - After: the same grep across a window of real dashboard traffic. A window containing a `stale-backstop` recompute should no longer show a `max` near the recompute's own `ms=`.
392
+ - Refutation case: if `max` still tracks the recompute duration after the change, the remaining block is the ~215 ms of parsing and the change did not achieve its goal. That is a reportable result, not a failed change.
393
+
394
+ This step needs an installer publish to reach the device and is therefore operator-gated.
@@ -0,0 +1,232 @@
1
+ # Task 1902 — every account-root entry is declared by the component that owns it
2
+
3
+ Design agreed 2026-07-26. Source task: `.tasks/pending/1902-account-owned-entry-declarations-are-incomplete.md`.
4
+
5
+ ## The problem this removes
6
+
7
+ `account-schema-owned-dirs.py` unions each plugin's `account-owned-dirs`
8
+ declaration into the account's `SCHEMA.md` `allowed-top-level` block on provision,
9
+ and the standing reconcile
10
+ (`platform/services/claude-session-manager/src/account-dir-schema-reconcile.ts`)
11
+ names every root entry outside that block on the `op=strays` line. Three plugins
12
+ declare anything today: `storage-broker/PLUGIN.md:4` (`pages`),
13
+ `memory/PLUGIN.md:4` (`archive`), and
14
+ `premium-plugins/sitedesk/plugins/sitedesk-job/PLUGIN.md:34` (`quoting`).
15
+
16
+ Two classes of entry are outside that mechanism.
17
+
18
+ Platform-written control-plane files at the account root are kept out of the stray
19
+ count by `PROTECTED_TOP_LEVEL`, a hand-maintained constant in the reconcile that
20
+ has been extended once per casualty (`calendar-availability.json` was Task 1877,
21
+ `agents-disabled.json` was Task 1996). It holds seven such files. The mechanism
22
+ that should own them covers directories only, so no plugin can declare a file, and
23
+ `data-portal.json` was never added to either surface and reports as a stray on
24
+ every run.
25
+
26
+ Directories an agent created at an account root are in no declaration and have no
27
+ writer in the shipped tree. Since Task 1899 removed the move, each costs one
28
+ `op=strays` name per five-minute cycle rather than a broken feature. The cost is a
29
+ permanently noisy signal an operator learns to ignore, which is how the next real
30
+ stray gets missed.
31
+
32
+ ## Evidence
33
+
34
+ Grep over `platform/plugins`, `platform/scripts`, `platform/services`,
35
+ `platform/templates` and `premium-plugins` for an account-root write of each
36
+ undeclared name:
37
+
38
+ - `e-sign/` has one writer: `platform/plugins/business-assistant/skills/e-sign/SKILL.md:452`
39
+ (`mkdir -p "${ACCOUNT_DIR}/e-sign/<DOC_REF>"`), plus the persisted base and the
40
+ per-signer stamped copies alongside it.
41
+ - `clients`, `workers`, `specs`, `templates`, `sites-staging` and `repo` have no
42
+ writer. By the source task's own rule an entry with no creator in the tree is
43
+ agent work product, recorded as such rather than declared.
44
+
45
+ Writers for the seven protected files and for `data-portal.json`:
46
+
47
+ | Root file | Writer | Declared on |
48
+ |---|---|---|
49
+ | `calendar-availability.json` | `plugins/scheduling/mcp/src/scripts/publish-availability.ts` | scheduling |
50
+ | `data-portal.json` | `plugins/cloudflare/bin/portal-index-push.mjs` | cloudflare |
51
+ | `wa-channel-bindings.json` | `services/claude-session-manager/src/wa-channel-store.ts` | admin |
52
+ | `telegram-channel-bindings.json` | `services/claude-session-manager/src/telegram-channel-store.ts` | admin |
53
+ | `webchat-channel-bindings.json` | `services/claude-session-manager/src/webchat-channel-store.ts` | admin |
54
+ | `canonical-webchat-session.json` | `ui/server/canonical-webchat-override.ts` | admin |
55
+ | `session-titles.json` | `services/claude-session-manager/src/account-title-stores.ts` | admin |
56
+ | `agents-disabled.json` | `ui/server/routes/admin/agents.ts` | admin |
57
+
58
+ Six of the eight are written by a shared platform service or the UI server, not by
59
+ the plugin they are declared on. The session manager and the UI server carry no
60
+ manifest the resolver reads, so their files are declared on `admin`, and the rule
61
+ is reach rather than naming: **a declaration is only present on a brand whose
62
+ payload carries the declaring plugin.** `brand.json` `plugins.excluded` drops the
63
+ directory outright (`packages/create-maxy-code/scripts/bundle.js`), and
64
+ `realagent-code` and `property-administrators` both exclude `telegram` while the
65
+ session manager still constructs `TelegramChannelStore`
66
+ (`services/claude-session-manager/src/index.ts`). Declaring
67
+ `telegram-channel-bindings.json` on the telegram plugin would therefore leave
68
+ those two brands reporting it as a stray forever, which is the failure this task
69
+ exists to remove. `admin` is excluded by no brand.
70
+
71
+ The three remaining declarations sit on the plugin whose own code writes them
72
+ (`scheduling`, `cloudflare`, `business-assistant`). That is self-consistent: when
73
+ such a plugin is excluded, its writer is excluded with it and the file is never
74
+ created, so the declaration is present exactly when the file can be.
75
+
76
+ `setup-account.sh:69` runs `reconcile_all_accounts_owned_dirs "$ACCOUNTS_DIR"` on
77
+ every install, and that sweep calls `merge_owned_dirs_into_schema` for every
78
+ account carrying an `account.json`. A declaration therefore reaches every existing
79
+ account on the next install, not only accounts provisioned after it lands.
80
+
81
+ ## Units
82
+
83
+ ### 1. `platform/scripts/lib/account-schema-owned-dirs.py`
84
+
85
+ `_parse_plugin_md` gains a second frontmatter key, `account-owned-files`, parsed
86
+ with the same regex-then-`json.loads` shape as `account-owned-dirs` and keyed
87
+ `file` instead of `dir`:
88
+
89
+ ```
90
+ account-owned-files: [{"file": "data-portal.json", "description": "..."}]
91
+ ```
92
+
93
+ A declaration that fails to parse is skipped and the remaining declarations still
94
+ merge, matching how the dirs key already behaves (`except Exception: pass`).
95
+
96
+ `resolve` returns one list carrying both kinds, each entry gaining a `kind` field
97
+ of `dir` or `file`, so a single ordered union feeds the merge. Dirs keep their
98
+ existing position ahead of files from the same manifest.
99
+
100
+ `merge` unions declared file names into the same `allowed-top-level` block as
101
+ dirs. The block is already a flat list of names that carries `SCHEMA.md`,
102
+ `account.json` and `AGENTS.md`, and `parseAllowedTopLevel` in the reconcile
103
+ compares it against `readdirSync` entries without distinguishing files from
104
+ directories, so no consumer changes. The prune rule is unchanged: an entry the
105
+ resolver no longer targets is dropped when empty or absent and kept-and-logged
106
+ when it holds content, and `_dir_holds_files` already treats a non-empty regular
107
+ file as content.
108
+
109
+ The plugin-owned descriptive region renders a file without the trailing slash a
110
+ directory gets:
111
+
112
+ ```
113
+ - `pages/` — Cloudflare Pages deploy staging for the account's public sites.
114
+ - `data-portal.json` — the account's data-portal index, written by portal-index-push.
115
+ ```
116
+
117
+ `reconcile` prints `ownedFile=<name> present=<true|false>`, the file twin of the
118
+ existing `ownedDir=` line, so a declaration that has not reached an account is
119
+ visible per name.
120
+
121
+ ### 2. Declarations
122
+
123
+ One frontmatter line added per plugin, in the shape the resolver parses:
124
+
125
+ - `plugins/business-assistant/PLUGIN.md` — `account-owned-dirs` gains `e-sign`.
126
+ - `plugins/cloudflare/PLUGIN.md` — `account-owned-files` gains `data-portal.json`.
127
+ - `plugins/scheduling/PLUGIN.md` — `calendar-availability.json`.
128
+ - `plugins/admin/PLUGIN.md` — `wa-channel-bindings.json`,
129
+ `telegram-channel-bindings.json`, `webchat-channel-bindings.json`,
130
+ `canonical-webchat-session.json`, `session-titles.json`, `agents-disabled.json`.
131
+
132
+ The resolver reads every `$PROJECT_DIR/plugins/*/PLUGIN.md` present in the
133
+ installed tree, with only premium bundles gated by `shipsPremiumBundles`. A brand
134
+ that excludes a plugin has no manifest to read, which is why the six
135
+ service-written files sit on `admin`: every brand then allows all nine names,
136
+ which is what `PROTECTED_TOP_LEVEL` did for seven of them.
137
+
138
+ ### 3. `platform/services/claude-session-manager/src/account-dir-schema-reconcile.ts`
139
+
140
+ `PROTECTED_TOP_LEVEL` drops the seven file entries and keeps `.git`, `.claude`,
141
+ `.quarantine`, `SCHEMA.md`, `account.json` and `AGENTS.md`. Its comment is
142
+ rewritten to state the new division: the constant holds the account's own
143
+ machinery, which exists whatever the schema says, and every feature file is held
144
+ by the declaration that owns it.
145
+
146
+ The behavioural consequence, stated rather than hidden: those seven files are now
147
+ kept out of the stray count by the account's `SCHEMA.md`, not by a constant. An
148
+ account whose schema was never re-merged reports them. The sweep in
149
+ `setup-account.sh` closes that window on the install that ships this change, and
150
+ the `ownedFile=` reconcile line names any account it has not reached.
151
+
152
+ ### 4. Tests
153
+
154
+ `platform/scripts/__tests__/account-schema-owned-dirs.test.sh` gains, on a
155
+ synthetic tree in the style of the existing shape-b case:
156
+
157
+ - a plugin declaring `account-owned-files: [{"file": "x.json", ...}]` puts `x.json`
158
+ in the merged `allowed-top-level` block, describes it in the plugin-owned region
159
+ without a trailing slash, and reports `ownedFile=x.json present=true` from
160
+ `reconcile`;
161
+ - a plugin whose `account-owned-files` value is malformed JSON is skipped while a
162
+ sibling plugin's valid declaration still merges;
163
+ - the merge stays byte-identical on a second run with a file declaration present.
164
+
165
+ **Mutation check.** Drop the files branch from the resolver and the new cases must
166
+ fail. Revert.
167
+
168
+ `platform/services/claude-session-manager/src/__tests__/account-dir-schema-reconcile.test.ts`
169
+ carries two tests that prove the code belt: `never counts platform-written
170
+ control-plane root files absent from the allowed block` and `does not count the
171
+ disabled store itself as a stray`. Both seed an account whose `SCHEMA.md` lists no
172
+ such file, which after this change is a stale account rather than a current one.
173
+ They are rewritten to seed the schema a reprovisioned account carries, listing the
174
+ declared file names, and then assert the same outcome: the control-plane files are
175
+ absent from `strayNames` and a genuine operator stray alongside them is still
176
+ counted and named. That is the surface the reconcile reads, so the rewritten tests
177
+ prove the delivered behaviour rather than the retired constant.
178
+
179
+ ### 5. The region parsers and the doc
180
+
181
+ Three readers parse the plugin-owned region and each anchors on the trailing
182
+ slash inside the backticks: `platform/lib/account-schema-regions/src/index.ts:65`
183
+ (shared by the `/data` page grouping and the reconcile's parity audit) and
184
+ `platform/plugins/cloudflare/bin/schema-exposed-dirs.mjs:42-43` (the public
185
+ data-portal exposure set, which keeps its own copy under a parity test). A file
186
+ line therefore yields no bucket in any of them, which is the required behaviour
187
+ and needs no code change. It is pinned by a case in
188
+ `platform/ui/server/lib/__tests__/account-schema-regions.test.ts`, because a
189
+ future edit relaxing that regex would put a control-plane file in front of a
190
+ client.
191
+
192
+ `.docs/data-portal-folder-index.md` is the authoritative description of how the
193
+ allowed set is built and currently describes plugin-owned as directories. It
194
+ gains the second kind and the trailing-slash rule.
195
+
196
+ ## Scope boundaries
197
+
198
+ ### In scope
199
+
200
+ Units 1 to 5.
201
+
202
+ ### Out of scope
203
+
204
+ - Reprovisioning the laptop's accounts, and the two device checks that depend on
205
+ it (`op=strays` naming only recorded work product, and the negative check that
206
+ removing one declaration makes its entry reappear). Both need this change
207
+ published and installed, and publish awaits an explicit operator command. Task
208
+ `2027-reprovision-laptop-accounts-after-1902-declarations.md`.
209
+ - Declaring `clients`, `workers`, `specs`, `templates`, `sites-staging` or `repo`.
210
+ No shipped component writes them, so declaring them would make the allowed list
211
+ a record of what happens to exist rather than of what is owned. They continue to
212
+ appear in `op=strays`, which is the correct report for debris.
213
+ - Relocating agent work product into schema buckets. Task 1894 covers the glsmith
214
+ case.
215
+ - Teaching `fs-schema-guard.sh` to tell a declared file from an operator bucket.
216
+ The guard reads the same `allowed-top-level` fence as an allow-list for
217
+ `Write`/`Edit`/`NotebookEdit`, so declaring these eight files makes them
218
+ agent-writable where they were blocked before. Measured: the same envelope
219
+ returns exit 2 against the pre-merge schema and exit 0 against the merged one.
220
+ The fence already permits `account.json`, `SCHEMA.md`, `secrets/` and
221
+ `.claude/`, so it is a layout guard rather than a trust boundary, and this
222
+ widens it rather than breaches it. Task
223
+ `2028-fs-schema-guard-cannot-tell-a-declared-file-from-an-operator-bucket.md`.
224
+ - The reconcile's report-only posture. Task 1899 settled it.
225
+
226
+ ## Observability
227
+
228
+ The `op=strays` line from Task 1899 is the instrument. A declared entry never
229
+ appears in it, so anything that does is either debris or an undeclared owner, and
230
+ the two are separated by asking what created it. The `ownedDir=` and `ownedFile=`
231
+ lines from `reconcile` are the second instrument: they name, per account, a
232
+ declaration that has not yet reached that account's `SCHEMA.md`.