@rubytech/create-maxy-code 0.1.478 → 0.1.480
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.
- package/package.json +1 -1
- package/payload/platform/docs/superpowers/plans/2026-07-20-task-1789-reseat-channel-row-fork.md +1244 -0
- package/payload/platform/docs/superpowers/plans/2026-07-20-task-1818-loop-gate-app-routes.md +462 -0
- package/payload/platform/docs/superpowers/plans/2026-07-20-task-1819-top-level-label-allowlist.md +321 -0
- package/payload/platform/docs/superpowers/plans/2026-07-20-task-1829-platform-ui-typecheck.md +830 -0
- package/payload/platform/docs/superpowers/plans/2026-07-20-task-1831-data-portal-class-derived-allowlist.md +2177 -0
- package/payload/platform/docs/superpowers/specs/2026-07-20-task-1789-reseat-channel-row-fork-design.md +201 -0
- package/payload/platform/docs/superpowers/specs/2026-07-20-task-1818-loop-gate-app-routes-design.md +217 -0
- package/payload/platform/docs/superpowers/specs/2026-07-20-task-1819-top-level-label-allowlist-design.md +80 -0
- package/payload/platform/docs/superpowers/specs/2026-07-20-task-1829-platform-ui-typecheck-design.md +163 -0
- package/payload/platform/docs/superpowers/specs/2026-07-20-task-1831-data-portal-class-derived-allowlist-design.md +163 -0
- package/payload/platform/lib/graph-style/dist/index.d.ts.map +1 -1
- package/payload/platform/lib/graph-style/dist/index.js +22 -0
- package/payload/platform/lib/graph-style/dist/index.js.map +1 -1
- package/payload/platform/lib/graph-style/src/__tests__/parity.test.ts +6 -0
- package/payload/platform/lib/graph-style/src/index.ts +20 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/cf-exec.test.js +155 -12
- package/payload/platform/lib/storage-broker/dist/__tests__/cf-exec.test.js.map +1 -1
- package/payload/platform/lib/storage-broker/dist/cf-exec.d.ts +3 -0
- package/payload/platform/lib/storage-broker/dist/cf-exec.d.ts.map +1 -1
- package/payload/platform/lib/storage-broker/dist/cf-exec.js +68 -28
- package/payload/platform/lib/storage-broker/dist/cf-exec.js.map +1 -1
- package/payload/platform/lib/storage-broker/src/__tests__/cf-exec.test.ts +194 -16
- package/payload/platform/lib/storage-broker/src/cf-exec.ts +74 -29
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +13 -1
- package/payload/platform/plugins/cloudflare/PLUGIN.md +26 -1
- package/payload/platform/plugins/cloudflare/bin/d1-http.mjs +80 -0
- package/payload/platform/plugins/cloudflare/bin/portal-enrol.mjs +17 -2
- package/payload/platform/plugins/cloudflare/bin/portal-index-push.mjs +357 -0
- package/payload/platform/plugins/cloudflare/bin/schema-exposed-dirs.mjs +103 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/authorize.test.ts +2 -2
- package/payload/platform/plugins/cloudflare/mcp/__tests__/d1-http.test.ts +96 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-directory-listing.test.ts +120 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-enrol.test.ts +59 -4
- package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-index-push.test.ts +176 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-index-state-contract.test.ts +75 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-indexed-download.test.ts +131 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-sign.test.ts +68 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/schema-exposed-dirs.test.ts +88 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/template-config.test.ts +39 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/SKILL.md +68 -1
- package/payload/platform/plugins/cloudflare/skills/data-portal/schema.sql +30 -1
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/portal-sign.mjs +87 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/session.ts +8 -4
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/types.ts +10 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/download.ts +107 -1
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/files.ts +82 -2
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/index.html +9 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/portal.css +56 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/portal.js +98 -2
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/wrangler.toml +12 -0
- package/payload/platform/plugins/docs/references/cloudflare.md +12 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/device-flow.test.js +41 -1
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/device-flow.test.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/graph-client.test.js +249 -2
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/graph-client.test.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/token-store.test.js +127 -3
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/token-store.test.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/auth/device-flow.d.ts +17 -0
- package/payload/platform/plugins/outlook/mcp/dist/auth/device-flow.d.ts.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/auth/device-flow.js +26 -1
- package/payload/platform/plugins/outlook/mcp/dist/auth/device-flow.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/auth/token-store.d.ts +37 -1
- package/payload/platform/plugins/outlook/mcp/dist/auth/token-store.d.ts.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/auth/token-store.js +126 -5
- package/payload/platform/plugins/outlook/mcp/dist/auth/token-store.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/lib/graph-client.d.ts +18 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/graph-client.d.ts.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/lib/graph-client.js +123 -18
- package/payload/platform/plugins/outlook/mcp/dist/lib/graph-client.js.map +1 -1
- package/payload/platform/plugins/outlook/references/auth.md +45 -2
- package/payload/platform/plugins/scheduling/mcp/dist/__tests__/portal-index-audit.test.d.ts +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/__tests__/portal-index-audit.test.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/__tests__/portal-index-audit.test.js +66 -0
- package/payload/platform/plugins/scheduling/mcp/dist/__tests__/portal-index-audit.test.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/portal-index-audit.d.ts +41 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/portal-index-audit.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/portal-index-audit.js +75 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/portal-index-audit.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js +122 -0
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js.map +1 -1
- package/payload/server/{chunk-3XLLTG6R.js → chunk-REWXLAPC.js} +30 -10
- package/payload/server/package.json +1 -1
- package/payload/server/public/activity.html +7 -6
- package/payload/server/public/assets/{AdminLoginScreens-DR5eLF-a.js → AdminLoginScreens-DXYJWXaX.js} +1 -1
- package/payload/server/public/assets/AdminShell-UDgNN4CA.js +2 -0
- package/payload/server/public/assets/Checkbox-C08Ulc_a.js +1 -0
- package/payload/server/public/assets/activity-06gsRDVv.js +1 -0
- package/payload/server/public/assets/admin-BhiHyORH.js +1 -0
- package/payload/server/public/assets/{arc-BrHWN2Kr.js → arc-BHXuzTwg.js} +1 -1
- package/payload/server/public/assets/architecture-YZFGNWBL-Ct5q6ks1.js +1 -0
- package/payload/server/public/assets/{architectureDiagram-Q4EWVU46-Clt9NOr0.js → architectureDiagram-Q4EWVU46-DsN_vxau.js} +1 -1
- package/payload/server/public/assets/{blockDiagram-DXYQGD6D-BOe_jmXi.js → blockDiagram-DXYQGD6D-CvNat1uB.js} +1 -1
- package/payload/server/public/assets/browser-Cz4IKS47.js +1 -0
- package/payload/server/public/assets/{c4Diagram-AHTNJAMY-B64FaKMV.js → c4Diagram-AHTNJAMY-E9yqTPTW.js} +1 -1
- package/payload/server/public/assets/calendar-D0xtNqH8.js +1 -0
- package/payload/server/public/assets/channel-PE9vBKEQ.js +1 -0
- package/payload/server/public/assets/chat-DwLJ830n.js +1 -0
- package/payload/server/public/assets/chevron-left-Cv-TFuEf.js +1 -0
- package/payload/server/public/assets/{chunk-2KRD3SAO-BGAUfWvM.js → chunk-2KRD3SAO-DpzI_0EY.js} +1 -1
- package/payload/server/public/assets/{chunk-336JU56O-PFi6uQZ0.js → chunk-336JU56O-eR_I9Eu4.js} +2 -2
- package/payload/server/public/assets/chunk-426QAEUC-BDNZtJsz.js +1 -0
- package/payload/server/public/assets/{chunk-4BX2VUAB-CkxXXCiF.js → chunk-4BX2VUAB-D-6xk1t8.js} +1 -1
- package/payload/server/public/assets/{chunk-4TB4RGXK-B-7tN4IA.js → chunk-4TB4RGXK-BkqUcFpL.js} +1 -1
- package/payload/server/public/assets/{chunk-55IACEB6-CBkNmqrt.js → chunk-55IACEB6-ChCl2pSP.js} +1 -1
- package/payload/server/public/assets/{chunk-5FUZZQ4R-wa9ujn7i.js → chunk-5FUZZQ4R-DasGsg54.js} +1 -1
- package/payload/server/public/assets/{chunk-5PVQY5BW-DIk-xBaE.js → chunk-5PVQY5BW-DXlHUX1h.js} +1 -1
- package/payload/server/public/assets/{chunk-67CJDMHE-BbMV1lgW.js → chunk-67CJDMHE-BZfVpTsE.js} +1 -1
- package/payload/server/public/assets/{chunk-7N4EOEYR-C49Mv-nQ.js → chunk-7N4EOEYR-C6U5TIgT.js} +1 -1
- package/payload/server/public/assets/{chunk-AA7GKIK3-Dy_pJOp_.js → chunk-AA7GKIK3-CA7cVDmw.js} +1 -1
- package/payload/server/public/assets/{chunk-BSJP7CBP-Ce-OmPqn.js → chunk-BSJP7CBP-DxN2GC3p.js} +1 -1
- package/payload/server/public/assets/{chunk-CIAEETIT-tnjq-APC.js → chunk-CIAEETIT-C0-4wA8y.js} +1 -1
- package/payload/server/public/assets/{chunk-EDXVE4YY-Bvv_deZ2.js → chunk-EDXVE4YY--kyb1rLc.js} +1 -1
- package/payload/server/public/assets/{chunk-ENJZ2VHE-CmxCPKyU.js → chunk-ENJZ2VHE-FBN6hR7V.js} +1 -1
- package/payload/server/public/assets/{chunk-FMBD7UC4-Top6y08S.js → chunk-FMBD7UC4-Lpge4M5g.js} +1 -1
- package/payload/server/public/assets/{chunk-FOC6F5B3-B9LPNCUS.js → chunk-FOC6F5B3-CM1rwOou.js} +1 -1
- package/payload/server/public/assets/{chunk-ICPOFSXX-Bi-_c351.js → chunk-ICPOFSXX-JdYLY3yu.js} +2 -2
- package/payload/server/public/assets/{chunk-K5T4RW27-DAnRM04h.js → chunk-K5T4RW27-DCUblGBr.js} +1 -1
- package/payload/server/public/assets/{chunk-KGLVRYIC-C0QYcbLe.js → chunk-KGLVRYIC-BP_cide2.js} +1 -1
- package/payload/server/public/assets/{chunk-LIHQZDEY-BjvA5ykt.js → chunk-LIHQZDEY-DjMa1lOl.js} +1 -1
- package/payload/server/public/assets/{chunk-ORNJ4GCN-Cs46Gj7z.js → chunk-ORNJ4GCN-D51eJAoV.js} +1 -1
- package/payload/server/public/assets/{chunk-OYMX7WX6-BBpimab5.js → chunk-OYMX7WX6-4hWIO544.js} +1 -1
- package/payload/server/public/assets/chunk-QZHKN3VN-B_Hris8x.js +1 -0
- package/payload/server/public/assets/{chunk-U2HBQHQK-CQUSBHoK.js → chunk-U2HBQHQK-BwdQf9cS.js} +1 -1
- package/payload/server/public/assets/{chunk-X2U36JSP-Dm8TevG0.js → chunk-X2U36JSP-DmDxwLOg.js} +1 -1
- package/payload/server/public/assets/{chunk-XPW4576I-B10QuxYb.js → chunk-XPW4576I-JQyAB7ND.js} +1 -1
- package/payload/server/public/assets/{chunk-YZCP3GAM-vaGsu4Wk.js → chunk-YZCP3GAM-BonLdCzI.js} +1 -1
- package/payload/server/public/assets/{chunk-ZZ45TVLE-D1Qi22bL.js → chunk-ZZ45TVLE-BXKB2wSk.js} +1 -1
- package/payload/server/public/assets/classDiagram-6PBFFD2Q-D8Q05iMI.js +1 -0
- package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-Dm3hAxfm.js +1 -0
- package/payload/server/public/assets/clone-C-wOwvpY.js +1 -0
- package/payload/server/public/assets/{cose-bilkent-S5V4N54A-CRDvLRXs.js → cose-bilkent-S5V4N54A-BDnd4Blw.js} +1 -1
- package/payload/server/public/assets/{dagre-CEmqJTt3.js → dagre-BmexzuER.js} +1 -1
- package/payload/server/public/assets/{dagre-KV5264BT-LXMTo7Fp.js → dagre-KV5264BT-D4WVuwPj.js} +1 -1
- package/payload/server/public/assets/data-DUDJfnjI.js +1 -0
- package/payload/server/public/assets/{diagram-5BDNPKRD-zyis6nPt.js → diagram-5BDNPKRD-Bbf4mT8a.js} +1 -1
- package/payload/server/public/assets/{diagram-G4DWMVQ6-BiU0XAAD.js → diagram-G4DWMVQ6-BpqVTUmx.js} +1 -1
- package/payload/server/public/assets/{diagram-MMDJMWI5-rGFiz1SX.js → diagram-MMDJMWI5-DY-k5k6r.js} +1 -1
- package/payload/server/public/assets/{diagram-TYMM5635-B0wak1ZJ.js → diagram-TYMM5635-CXHxwacS.js} +1 -1
- package/payload/server/public/assets/{erDiagram-SMLLAGMA-D8AUxLyX.js → erDiagram-SMLLAGMA-BNKEc3l8.js} +1 -1
- package/payload/server/public/assets/{flatten-naJ5PJ4f.js → flatten-jn8bZgcI.js} +1 -1
- package/payload/server/public/assets/{flowDiagram-DWJPFMVM-BtdUf68b.js → flowDiagram-DWJPFMVM-CbAeuJsi.js} +1 -1
- package/payload/server/public/assets/{ganttDiagram-T4ZO3ILL-C-lkzDmf.js → ganttDiagram-T4ZO3ILL-CeeNnL4l.js} +1 -1
- package/payload/server/public/assets/gitGraph-7Q5UKJZL-BBHsY16R.js +1 -0
- package/payload/server/public/assets/{gitGraphDiagram-UUTBAWPF-DRUKumq2.js → gitGraphDiagram-UUTBAWPF-DG8RCtfg.js} +1 -1
- package/payload/server/public/assets/{graph-DRloNic4.js → graph-BAoktBcx.js} +19 -19
- package/payload/server/public/assets/graph-labels-LeIhl7ZG.js +1 -0
- package/payload/server/public/assets/{graphlib-zRsaoFEC.js → graphlib-BkDExPQi.js} +1 -1
- package/payload/server/public/assets/info-OMHHGYJF-C1j5I_zx.js +1 -0
- package/payload/server/public/assets/infoDiagram-42DDH7IO-ZWkwHWEu.js +2 -0
- package/payload/server/public/assets/{isEmpty-Bnpg7b8f.js → isEmpty-Dkk6_L-B.js} +1 -1
- package/payload/server/public/assets/{ishikawaDiagram-UXIWVN3A-BguMgoR8.js → ishikawaDiagram-UXIWVN3A-D_iMHV64.js} +1 -1
- package/payload/server/public/assets/{journeyDiagram-VCZTEJTY-CXhyK1n-.js → journeyDiagram-VCZTEJTY-BvPRpulx.js} +1 -1
- package/payload/server/public/assets/{kanban-definition-6JOO6SKY-C0UCWVx-.js → kanban-definition-6JOO6SKY-CkWaXWo2.js} +1 -1
- package/payload/server/public/assets/{line--uhS9YDE.js → line-BGQT9unx.js} +1 -1
- package/payload/server/public/assets/{linear-CAOWPodQ.js → linear-DzrYI8-8.js} +1 -1
- package/payload/server/public/assets/{maximize-2-CzDaBugs.js → maximize-2-2MgPSwlx.js} +1 -1
- package/payload/server/public/assets/{mermaid-parser.core-D6kPx7vc.js → mermaid-parser.core-DrMY7FMe.js} +2 -2
- package/payload/server/public/assets/{mermaid.core-CTd4DS6O.js → mermaid.core-BWdf6fZ7.js} +3 -3
- package/payload/server/public/assets/{mindmap-definition-QFDTVHPH-_AyvyG8C.js → mindmap-definition-QFDTVHPH-CgqM9h97.js} +1 -1
- package/payload/server/public/assets/operator-CZifmNhp.js +1 -0
- package/payload/server/public/assets/{ordinal-Wx4m9zHk.js → ordinal-DuJYM7p1.js} +1 -1
- package/payload/server/public/assets/packet-4T2RLAQJ-CGiTbe0N.js +1 -0
- package/payload/server/public/assets/{page-ef-6rRPL.js → page-B4kllGd_.js} +6 -6
- package/payload/server/public/assets/page-DbfE7QwJ.js +1 -0
- package/payload/server/public/assets/{pdf-render-CeX6d2v7.js → pdf-render-riHufgrn.js} +1 -1
- package/payload/server/public/assets/pie-ZZUOXDRM-BrK023dE.js +1 -0
- package/payload/server/public/assets/{pieDiagram-DEJITSTG-6TsUDkkQ.js → pieDiagram-DEJITSTG-BeUGoYS7.js} +1 -1
- package/payload/server/public/assets/public-VtmyYz8E.js +1 -0
- package/payload/server/public/assets/{quadrantDiagram-34T5L4WZ-CWw9gOI4.js → quadrantDiagram-34T5L4WZ-CX82WgKy.js} +1 -1
- package/payload/server/public/assets/radar-PYXPWWZC-p7NRamXX.js +1 -0
- package/payload/server/public/assets/{reduce-BUmc8RDR.js → reduce-COyotL2V.js} +1 -1
- package/payload/server/public/assets/{requirementDiagram-MS252O5E-DCOlxNvx.js → requirementDiagram-MS252O5E-CzqebQXj.js} +1 -1
- package/payload/server/public/assets/{rotate-ccw-DXEKUhXP.js → rotate-ccw-DaZdB2cz.js} +1 -1
- package/payload/server/public/assets/{sankeyDiagram-XADWPNL6-CW4Xbir3.js → sankeyDiagram-XADWPNL6-CaHi1yzU.js} +1 -1
- package/payload/server/public/assets/{sequenceDiagram-FGHM5R23-vhb_pBLG.js → sequenceDiagram-FGHM5R23-BF2AeiKW.js} +1 -1
- package/payload/server/public/assets/{stateDiagram-FHFEXIEX-Cb1WExEX.js → stateDiagram-FHFEXIEX-DtUCA8Ih.js} +1 -1
- package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-a4FvB3L8.js +1 -0
- package/payload/server/public/assets/tasks-CvqVDbfO.js +1 -0
- package/payload/server/public/assets/{time-entry-format-CsYAjplS.js → time-entry-format-TD-PPEIu.js} +1 -1
- package/payload/server/public/assets/{timeline-definition-GMOUNBTQ-BAHTf_AF.js → timeline-definition-GMOUNBTQ-D3Q1YrKY.js} +1 -1
- package/payload/server/public/assets/treeView-SZITEDCU-CoWk8VoG.js +1 -0
- package/payload/server/public/assets/treemap-W4RFUUIX-BhWeGj8Y.js +1 -0
- package/payload/server/public/assets/{triangle-alert-0lSBIUg-.js → triangle-alert-BaLhVNnn.js} +1 -1
- package/payload/server/public/assets/useCopyFeedback-BhjPzRa3.js +1 -0
- package/payload/server/public/assets/useSubAccountSwitcher-CWRpVt1-.css +1 -0
- package/payload/server/public/assets/{useSubAccountSwitcher-BZnzc5C3.js → useSubAccountSwitcher-HEVLb6Z9.js} +2 -2
- package/payload/server/public/assets/useVoiceRecorder-CfGgj3vm.js +2 -0
- package/payload/server/public/assets/{vennDiagram-DHZGUBPP-DtEttvW4.js → vennDiagram-DHZGUBPP-DnjzQDGf.js} +1 -1
- package/payload/server/public/assets/wardley-RL74JXVD-C0q3Hqng.js +1 -0
- package/payload/server/public/assets/{wardleyDiagram-NUSXRM2D-K06y6CVJ.js → wardleyDiagram-NUSXRM2D-T3WHrUvi.js} +1 -1
- package/payload/server/public/assets/{xychartDiagram-5P7HB3ND-D66c40lp.js → xychartDiagram-5P7HB3ND-2Efyv4h2.js} +1 -1
- package/payload/server/public/brand-defaults.css +1 -0
- package/payload/server/public/browser.html +4 -5
- package/payload/server/public/calendar.html +5 -6
- package/payload/server/public/chat.html +12 -13
- package/payload/server/public/data.html +11 -12
- package/payload/server/public/graph.html +10 -11
- package/payload/server/public/index.html +14 -15
- package/payload/server/public/operator.html +14 -15
- package/payload/server/public/privacy.html +1 -0
- package/payload/server/public/public.html +12 -13
- package/payload/server/public/tasks.html +7 -5
- package/payload/server/public/vnc-popout.html +1 -0
- package/payload/server/server.js +833 -389
- package/payload/server/{src-XQ63FPRE.js → src-JJ6PHO5I.js} +1 -1
- package/payload/server/public/assets/AdminShell-D9zaUxPd.js +0 -2
- package/payload/server/public/assets/Checkbox-DcbbAIe5.js +0 -1
- package/payload/server/public/assets/activity-C9P5NjEZ.js +0 -1
- package/payload/server/public/assets/admin-D2KLZnQW.js +0 -1
- package/payload/server/public/assets/architecture-YZFGNWBL-CZQzGRSM.js +0 -1
- package/payload/server/public/assets/browser-CDWqEiCD.js +0 -1
- package/payload/server/public/assets/calendar-BG7fOpbB.js +0 -1
- package/payload/server/public/assets/channel-zDUAN9Ks.js +0 -1
- package/payload/server/public/assets/chat-RyH-WYQC.js +0 -1
- package/payload/server/public/assets/chevron-left-DmXCr6nB.js +0 -1
- package/payload/server/public/assets/chunk-426QAEUC-iJnqNRug.js +0 -1
- package/payload/server/public/assets/chunk-QZHKN3VN-Qlc2PFAj.js +0 -1
- package/payload/server/public/assets/classDiagram-6PBFFD2Q-CFsKL52p.js +0 -1
- package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-BWBUUCiO.js +0 -1
- package/payload/server/public/assets/clone-h5XhkDUL.js +0 -1
- package/payload/server/public/assets/data-BaY_LkLR.js +0 -1
- package/payload/server/public/assets/gitGraph-7Q5UKJZL-CBZPQJLj.js +0 -1
- package/payload/server/public/assets/graph-labels-DD-GqS4v.js +0 -1
- package/payload/server/public/assets/info-OMHHGYJF-1vIoX-kP.js +0 -1
- package/payload/server/public/assets/infoDiagram-42DDH7IO-KHcXRaan.js +0 -2
- package/payload/server/public/assets/operator-BZeeyait.js +0 -1
- package/payload/server/public/assets/packet-4T2RLAQJ-CTU0gCGb.js +0 -1
- package/payload/server/public/assets/page-B0obcg3b.js +0 -1
- package/payload/server/public/assets/pie-ZZUOXDRM-D-EEHxhC.js +0 -1
- package/payload/server/public/assets/public-9D3MMOkC.js +0 -1
- package/payload/server/public/assets/radar-PYXPWWZC-DHXATZGB.js +0 -1
- package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-CxOIKvXh.js +0 -1
- package/payload/server/public/assets/tasks-BB0L7e5a.js +0 -1
- package/payload/server/public/assets/treeView-SZITEDCU-CC7ouMFd.js +0 -1
- package/payload/server/public/assets/treemap-W4RFUUIX-CaCg27im.js +0 -1
- package/payload/server/public/assets/useCopyFeedback-jTBQ2h0V.js +0 -1
- package/payload/server/public/assets/useSelectionMode-BooArySZ.js +0 -1
- package/payload/server/public/assets/useSubAccountSwitcher-D9knHOKq.css +0 -1
- package/payload/server/public/assets/useVoiceRecorder-PUT8q_sm.js +0 -2
- package/payload/server/public/assets/wardley-RL74JXVD-bMPsqQZ7.js +0 -1
- /package/payload/server/public/assets/{_baseFor-k8hYPe-U.js → _baseFor-Bjj9It3Q.js} +0 -0
- /package/payload/server/public/assets/{array-BFDiaBgf.js → array-BrrxEo12.js} +0 -0
- /package/payload/server/public/assets/{cytoscape.esm-Ch0xptA4.js → cytoscape.esm-CTWJxUxo.js} +0 -0
- /package/payload/server/public/assets/{defaultLocale-ZeknFqNe.js → defaultLocale-CFDbRV4k.js} +0 -0
- /package/payload/server/public/assets/{dist-B2N0v8hZ.js → dist-BATfy7Sn.js} +0 -0
- /package/payload/server/public/assets/{init-0VJVrkRJ.js → init-dXCzGgLP.js} +0 -0
- /package/payload/server/public/assets/{katex-Br7bH10N.js → katex-CHA1JFYJ.js} +0 -0
- /package/payload/server/public/assets/{path-INs8XTPH.js → path-CCRpdXhK.js} +0 -0
- /package/payload/server/public/assets/{preload-helper-D_sPnjeL.js → preload-helper-CH6UZRzu.js} +0 -0
- /package/payload/server/public/assets/{rough.esm-DU9bZLvZ.js → rough.esm-Bud4g4fh.js} +0 -0
- /package/payload/server/public/assets/{src-DHSQDYx-.js → src-DNdBRXXx.js} +0 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# Task 1789 — Re-seating a channel row forks it to webchat and destroys the channel seat
|
|
2
|
+
|
|
3
|
+
Design, 2026-07-20.
|
|
4
|
+
|
|
5
|
+
## The defect
|
|
6
|
+
|
|
7
|
+
`session-reseat.ts` builds every fork payload with a hardcoded webchat shape. Re-seating a
|
|
8
|
+
WhatsApp or Telegram row therefore spawns a **webchat** session whose `senderId` is
|
|
9
|
+
`session:<newId>`. The per-sender channel server for the real sender is replaced, its SSE stream
|
|
10
|
+
aborts, and `whatsapp/gateway/routes.ts:56` fires `op=channel-detached`. The fork renders with a
|
|
11
|
+
webchat glyph.
|
|
12
|
+
|
|
13
|
+
The fork is also unreachable from the channel. A sender's session id is
|
|
14
|
+
`adminSessionIdFor(accountId, senderId)` — `sha256` shaped into a v4 UUID, a pure function. Every
|
|
15
|
+
future inbound recomputes the original id, never the fork's.
|
|
16
|
+
|
|
17
|
+
Observed 2026-07-18 on `sitedesk-code`, account `5e99bba5`, sender `447504472444`, session
|
|
18
|
+
`c6ed890f`. Brand-uniform code path.
|
|
19
|
+
|
|
20
|
+
## Three corrections to the task brief
|
|
21
|
+
|
|
22
|
+
The brief was written against a reading of the code that does not hold. All three were confirmed
|
|
23
|
+
by reading the tree and were agreed with the operator before this design was written.
|
|
24
|
+
|
|
25
|
+
**The seam named in Fix #2 is log-only.** `wa-gateway.ts:312`'s `adminSessionIdFor` call feeds the
|
|
26
|
+
`op=inbound … sessionId=` line and nothing else. The id the spawn actually runs under comes from
|
|
27
|
+
the pure builder `whatsapp/gateway/spawn-request.ts:34`, called once at `server/index.ts:418`.
|
|
28
|
+
Telegram mirrors it exactly (`spawn-request.ts:31`, `server/index.ts:700`). Consulting the
|
|
29
|
+
override at the gateway line alone would make the log truthful and route nothing.
|
|
30
|
+
|
|
31
|
+
**The fork has no channel binding until the next inbound.** The manager wires the WhatsApp reply
|
|
32
|
+
tool only when a `waChannel` descriptor (senderId, gatewayUrl, serverPath) is present. The re-seat
|
|
33
|
+
route holds none of those, and the manager's store fallback is keyed on the new session id, which
|
|
34
|
+
is empty for a fresh fork. So the fork spawns correctly labelled but without
|
|
35
|
+
`mcp__whatsapp-channel__reply` until an inbound arrives and `ensureChannelSession` binds it. This
|
|
36
|
+
is accepted, not fixed — the existing re-attach path already covers it. It is made observable
|
|
37
|
+
rather than left silent, because the manager's own `op=channel-binding-lost` invariant is gated on
|
|
38
|
+
the store and cannot fire here.
|
|
39
|
+
|
|
40
|
+
**The audit sweep cannot answer the standing check as specified.** `runDuplicateSenderAudit`
|
|
41
|
+
walks session sidecars on disk. It has no event history, so "a sender whose last event is a
|
|
42
|
+
`channel-detached`" is not derivable from it. The gateway must record detach times for the sweep
|
|
43
|
+
to query.
|
|
44
|
+
|
|
45
|
+
## Scope
|
|
46
|
+
|
|
47
|
+
In scope: WhatsApp and Telegram admin channel rows.
|
|
48
|
+
|
|
49
|
+
Out of scope, deliberately:
|
|
50
|
+
|
|
51
|
+
- **Public channel rows.** Public sessions key their id on `personId` as well as `senderId`, so
|
|
52
|
+
the `<channel>:<senderId>` override key is ambiguous for them. Public re-seat is not evidenced
|
|
53
|
+
as broken. Public rows re-seat exactly as they do today.
|
|
54
|
+
- **Telegram in the standing audit.** `runDuplicateSenderAudit` is WhatsApp-only today. The
|
|
55
|
+
unseated check extends it in place; making the whole sweep multi-channel is separate work.
|
|
56
|
+
- Everything already listed under the task's own "Out of scope": the fork's displayed model
|
|
57
|
+
(Task 1790), the cross-lever clobber (Task 940), re-attaching the live sender, and
|
|
58
|
+
`op=duplicate-sender-sessions` for `447850009518`.
|
|
59
|
+
|
|
60
|
+
## Design
|
|
61
|
+
|
|
62
|
+
### Unit 1 — `platform/ui/server/channel-session-override.ts` (new)
|
|
63
|
+
|
|
64
|
+
Modelled on `canonical-webchat-override.ts`. One JSON file, `channel-session-override.json`, in
|
|
65
|
+
the account directory.
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
{ "bySender": { "whatsapp:447504472444": "fc397054-…" } }
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
- `channelOverridePath(accountDir)` — the file path.
|
|
72
|
+
- `readChannelOverrideId(accountDir, channel, senderId): string | null` — the fork id for that
|
|
73
|
+
sender, or null when unset, malformed, or not a v4-shaped UUID. Never throws; a corrupt file
|
|
74
|
+
reads as null so a bad write can never wedge inbound routing.
|
|
75
|
+
- `writeChannelOverrideId(accountDir, channel, senderId, sessionId): void` — merges into
|
|
76
|
+
`bySender`, preserving other senders' entries. Atomic tmp+rename so a concurrent read never
|
|
77
|
+
sees a half-written file.
|
|
78
|
+
|
|
79
|
+
There is no legacy-shape migration. Unlike the canonical override this file has no predecessor.
|
|
80
|
+
|
|
81
|
+
### Unit 2 — the fork inherits the source row's channel
|
|
82
|
+
|
|
83
|
+
`session-reseat.ts` reads the source session's `.meta.json` through the existing exported
|
|
84
|
+
`readSidecarMeta`, and passes the result to `resolveReseatPlan` as a new optional `source`
|
|
85
|
+
argument. `resolveReseatPlan` stays pure and unit-testable without the gate.
|
|
86
|
+
|
|
87
|
+
The channel shape applies when **all three** hold: sidecar `role` is `admin`, sidecar `channel` is
|
|
88
|
+
`whatsapp` or `telegram`, and sidecar `senderId` is a non-empty string. Then the payload carries:
|
|
89
|
+
|
|
90
|
+
- `channel` — the sidecar's channel
|
|
91
|
+
- `senderId` — the sidecar's senderId
|
|
92
|
+
- `role: 'admin'`
|
|
93
|
+
- `targetAccountId` — the sidecar's `accountId`, when present
|
|
94
|
+
- **no** `webchatChannel` descriptor
|
|
95
|
+
|
|
96
|
+
`personId` is never carried: the admin-only scope means it is always null on these rows.
|
|
97
|
+
|
|
98
|
+
When any of the three conditions fails, the payload is byte-for-byte what it is today.
|
|
99
|
+
|
|
100
|
+
`targetAccountId` is an addition beyond the task text and was approved explicitly. Without it the
|
|
101
|
+
manager defaults the fork to the boot account, so a channel row belonging to a client sub-account
|
|
102
|
+
would fork into the house account — wrong account scope for every Neo4j write the fork makes.
|
|
103
|
+
Pre-983 sidecars carry no `accountId`; those omit the field and keep today's behaviour.
|
|
104
|
+
|
|
105
|
+
### Unit 3 — resolution consults the override
|
|
106
|
+
|
|
107
|
+
`buildWaSpawnRequest` and `buildTelegramSpawnRequest` each gain an optional `overrideSessionId`
|
|
108
|
+
input. When set it is used as `sessionId`; otherwise `adminSessionIdFor` as today. The builders
|
|
109
|
+
keep no filesystem access and stay pure.
|
|
110
|
+
|
|
111
|
+
`server/index.ts` reads the override at both spawn call sites (`:418` WhatsApp, `:700` Telegram),
|
|
112
|
+
resolving the effective account's directory by id through `listValidAccounts()`. Using the
|
|
113
|
+
**effective** account id on the read side matches the write side, which keys on the source
|
|
114
|
+
sidecar's `accountId` — so an account-manager-routed sub-account sender resolves its own override
|
|
115
|
+
and never the house's.
|
|
116
|
+
|
|
117
|
+
The two gateway `op=inbound` log lines consult the same override, so the logged `sessionId` names
|
|
118
|
+
the session the message actually reaches. The gateways do not read the file themselves: each takes
|
|
119
|
+
an optional `resolveSessionOverride?: (accountId, senderId) => string | null` dependency, injected
|
|
120
|
+
by `server/index.ts` at construction. The gateways keep no filesystem knowledge and stay testable
|
|
121
|
+
with a stub, and an absent dependency degrades to the deterministic id.
|
|
122
|
+
|
|
123
|
+
### Chained re-seats
|
|
124
|
+
|
|
125
|
+
Mirroring Task 876's canonical pointer: the route writes the override when the source is either
|
|
126
|
+
the deterministic per-sender id **or** the current override value. Re-seating a fork moves the
|
|
127
|
+
pointer forward instead of stranding it.
|
|
128
|
+
|
|
129
|
+
### Why not a mutable binding
|
|
130
|
+
|
|
131
|
+
Rejected, per the task. `adminSessionIdFor`'s determinism is what lets an inbound resume the right
|
|
132
|
+
JSONL after a bridge or manager restart, and every existing channel session's id derives from it.
|
|
133
|
+
|
|
134
|
+
### Relationship to Task 1746
|
|
135
|
+
|
|
136
|
+
`spawn-request.ts` currently comments that "the deterministic per-sender id is the ONLY id a
|
|
137
|
+
sender's session ever has", written when Task 1746 removed Task 1526's per-firing seat. That seat
|
|
138
|
+
was **salted per scheduled firing**: it minted a new session on every dispatch, which then
|
|
139
|
+
attached under the sender's own hub key and swallowed their real messages, splitting one
|
|
140
|
+
conversation across two sessions.
|
|
141
|
+
|
|
142
|
+
This override is a **stable forward pointer**: one session, repointed once by an operator action,
|
|
143
|
+
resolving identically for every subsequent inbound. It cannot split a conversation because it
|
|
144
|
+
never yields a second id for a sender. Both comments are rewritten to say the deterministic id is
|
|
145
|
+
the default rather than the only id, and to record this distinction so the next reader does not
|
|
146
|
+
re-remove the override.
|
|
147
|
+
|
|
148
|
+
### Unit 4 — observability
|
|
149
|
+
|
|
150
|
+
- `op=request` gains the source's resolved channel and senderId, so the lifeline says what kind of
|
|
151
|
+
row was re-seated.
|
|
152
|
+
- `op=channel-forward senderId=… from=… to=…` at the override write, mirroring the existing
|
|
153
|
+
`op=canonical-forward`.
|
|
154
|
+
- `op=channel-fork-unbound senderId=… session=…` at a channel fork, naming the window in which the
|
|
155
|
+
fork has no reply tool. The brief did not ask for this; it exists because the manager's own
|
|
156
|
+
binding-lost invariant provably cannot fire on this path.
|
|
157
|
+
- **Standing check.** The WhatsApp gateway records a detach timestamp per sender when
|
|
158
|
+
`op=channel-detached` fires, and clears it on attach. `runDuplicateSenderAudit` queries that map
|
|
159
|
+
and emits `op=sender-unseated senderId=… since=…` for any sender still detached longer than one
|
|
160
|
+
sweep interval (15 min). This reconciles expected state — every known sender has a seat or a
|
|
161
|
+
reason not to — against actual, without reproducing the failure. A detached sender with no live
|
|
162
|
+
channel server is otherwise invisible to action logging, because nothing happens.
|
|
163
|
+
|
|
164
|
+
### Comments to correct
|
|
165
|
+
|
|
166
|
+
`session-reseat.ts`'s header (asserts the fork is always webchat-bound),
|
|
167
|
+
`SessionReseatControl.tsx:10-13` (asserts the opposite), and both `spawn-request.ts` Task 1746
|
|
168
|
+
comments. All four must describe the delivered behaviour.
|
|
169
|
+
|
|
170
|
+
## Testing
|
|
171
|
+
|
|
172
|
+
- `session-reseat.test.ts` — a `whatsapp` admin sidecar source produces a payload carrying
|
|
173
|
+
`channel:'whatsapp'`, the source `senderId`, and no `webchatChannel` key; a `telegram` admin
|
|
174
|
+
sidecar does the same for its channel; a `webchat` sidecar produces today's payload
|
|
175
|
+
byte-for-byte; a `whatsapp` sidecar with `role:'public'` produces today's payload; a sidecar with
|
|
176
|
+
no `accountId` omits `targetAccountId`.
|
|
177
|
+
- Override round-trip — write then read returns the fork id for that sender and null for an
|
|
178
|
+
unwritten sender; a second sender's write preserves the first; a malformed file reads null
|
|
179
|
+
rather than throwing.
|
|
180
|
+
- Spawn builders — with an `overrideSessionId` the request carries it; without one it carries
|
|
181
|
+
`adminSessionIdFor`. Both channels.
|
|
182
|
+
- Unseated audit — a sender detached longer than one interval is reported; one detached within the
|
|
183
|
+
interval is not; one that re-attached is not.
|
|
184
|
+
- Regression proof on-device — re-seat a WhatsApp row, confirm no `channel-detached` without a
|
|
185
|
+
following `channel-attached`, then send an inbound and confirm `op=inbound … sessionId=<fork>`.
|
|
186
|
+
|
|
187
|
+
## Files
|
|
188
|
+
|
|
189
|
+
| File | Change |
|
|
190
|
+
|---|---|
|
|
191
|
+
| `platform/ui/server/channel-session-override.ts` | new |
|
|
192
|
+
| `platform/ui/server/routes/admin/session-reseat.ts` | sidecar read, channel payload, override write, lifeline fields |
|
|
193
|
+
| `platform/ui/app/lib/whatsapp/gateway/spawn-request.ts` | optional `overrideSessionId` |
|
|
194
|
+
| `platform/ui/app/lib/telegram/gateway/spawn-request.ts` | optional `overrideSessionId` |
|
|
195
|
+
| `platform/ui/server/index.ts` | override read at both spawn sites; unseated audit |
|
|
196
|
+
| `platform/ui/app/lib/whatsapp/gateway/wa-gateway.ts` | detach-time map; override in `op=inbound` |
|
|
197
|
+
| `platform/ui/app/lib/telegram/gateway/telegram-gateway.ts` | override in `op=inbound` |
|
|
198
|
+
| `platform/ui/app/lib/whatsapp/gateway/duplicate-sender-audit.ts` | `op=sender-unseated` |
|
|
199
|
+
| `platform/ui/app/components/SessionReseatControl.tsx` | comment correction |
|
|
200
|
+
| `.docs/admin-webchat-native-channel.md` | document the override and the unbound window |
|
|
201
|
+
| `.tasks/LANES.md` | archive sync |
|
package/payload/platform/docs/superpowers/specs/2026-07-20-task-1818-loop-gate-app-routes-design.md
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
# Task 1818 — Widen the loop gate to cover `app/**` outside `app/lib/`
|
|
2
|
+
|
|
3
|
+
**Date:** 2026-07-20
|
|
4
|
+
**Lane:** Platform / admin UI · activity view · scheduled work visibility
|
|
5
|
+
**Depends on:** 1810 (landed), 1814 (landed — the widened gate and its marker convention)
|
|
6
|
+
|
|
7
|
+
## Summary
|
|
8
|
+
|
|
9
|
+
`scripts/check-loops-registered.mjs` walks `server/**` and `app/lib/**`. The rest of
|
|
10
|
+
`app/**` is unscanned, so a standing server loop armed there would not be caught and
|
|
11
|
+
would be invisible on `/activity`, which is the omission mode 1810 and 1814 exist to
|
|
12
|
+
close.
|
|
13
|
+
|
|
14
|
+
This task widens the scan to the whole of `app/**` and marks the fifteen browser timers
|
|
15
|
+
already there `loop-exempt:client`.
|
|
16
|
+
|
|
17
|
+
## Premise corrections
|
|
18
|
+
|
|
19
|
+
The task file's approach section rests on three factual claims about the tree. All three
|
|
20
|
+
are wrong, and the first one voids one of its two candidate approaches outright. They are
|
|
21
|
+
recorded here because the task's stated reasoning cannot be followed as written.
|
|
22
|
+
|
|
23
|
+
### 1. There is no Next.js
|
|
24
|
+
|
|
25
|
+
The task describes the unscanned area as "Next.js route and component code" and names
|
|
26
|
+
`app/api/*/route.ts` as the shape of the loop that would escape.
|
|
27
|
+
|
|
28
|
+
`platform/ui` has no `next` dependency and no `next.config`. It is a Vite SPA with ten
|
|
29
|
+
HTML entries (`vite.config.ts`) plus a Hono server bundled by tsup from exactly two
|
|
30
|
+
entries, `server/index.ts` and `server/edge.ts` (`tsup.config.ts`). There is no `app/api/`
|
|
31
|
+
directory and no route file under `app/` at all. Server routes live in `server/routes/`,
|
|
32
|
+
which the gate already scans.
|
|
33
|
+
|
|
34
|
+
The task's second candidate — "treat `app/api/**` and any other server-executing subtree
|
|
35
|
+
as in scope while leaving component directories out" — therefore describes a layout that
|
|
36
|
+
cannot exist in this package. It is void rather than merely less attractive. The task
|
|
37
|
+
states that establishing "whichever the Next.js routing layout actually makes stable" is
|
|
38
|
+
the first work of this task; the answer is that there is no such layout, and the decision
|
|
39
|
+
collapses to the remaining candidate.
|
|
40
|
+
|
|
41
|
+
### 2. `'use client'` is unusable, but not for the stated reason
|
|
42
|
+
|
|
43
|
+
The task rules out the `'use client'` directive as a discriminator on the grounds that
|
|
44
|
+
"the 1814 census confirmed `app/lib/use-now-tick.ts` is a React hook carrying no such
|
|
45
|
+
directive".
|
|
46
|
+
|
|
47
|
+
`app/lib/use-now-tick.ts` carries `'use client'` on line 14, and carried it at 1814's own
|
|
48
|
+
commit (`git show main:…` returns the directive).
|
|
49
|
+
|
|
50
|
+
The conclusion survives on different evidence. Of the eleven files under `app/` outside
|
|
51
|
+
`app/lib/` that arm a browser timer, three carry no directive: `app/chat/page.tsx`,
|
|
52
|
+
`app/chat/transcript-render.tsx`, and `app/graph/settle-lock.ts`. A discriminator that
|
|
53
|
+
misses three of eleven cannot gate a build.
|
|
54
|
+
|
|
55
|
+
### 3. Fifteen sites, not sixteen
|
|
56
|
+
|
|
57
|
+
The task and its LANES row both count sixteen browser timers in the unscanned area.
|
|
58
|
+
|
|
59
|
+
Applying the gate's own `ARM` regex (`/\bsetInterval\s*\(/`) to `app/**` outside
|
|
60
|
+
`app/lib/`, skipping `__tests__`, yields **fifteen** sites across eleven files. The
|
|
61
|
+
difference is that eight further occurrences are `ReturnType<typeof setInterval>` type
|
|
62
|
+
positions, which have no `(` after the identifier and do not match. The sixteenth timer is
|
|
63
|
+
`app/lib/use-now-tick.ts`, which lives inside a scanned root and already carries its
|
|
64
|
+
marker.
|
|
65
|
+
|
|
66
|
+
## What the census found
|
|
67
|
+
|
|
68
|
+
Two findings beyond the count, both bearing on the design.
|
|
69
|
+
|
|
70
|
+
**No hidden standing loop.** Every one of the fifteen sites is genuinely a browser timer.
|
|
71
|
+
The two least obvious were checked directly: `app/graph/settle-lock.ts` samples
|
|
72
|
+
`network.getPositions()` to freeze vis-network canvas physics and is imported only by
|
|
73
|
+
`app/graph/page.tsx`; `app/chat/transcript-render.tsx` is a React render module ticking
|
|
74
|
+
`setNow(Date.now())`. This is the opposite of 1814, whose widening surfaced
|
|
75
|
+
`visitor-event.ts`, a standing loop that had been invisible the whole time. Here the
|
|
76
|
+
widening surfaces nothing: every new site resolves to a marker, and no `registerLoop` call
|
|
77
|
+
is added, so the registry's 26 production loops are unchanged.
|
|
78
|
+
|
|
79
|
+
**Server-reachable code outside `app/lib/` already exists.** A census of every
|
|
80
|
+
`from '…/app/…'` import in `server/` returns `app/lib/…` in every case but one:
|
|
81
|
+
`server/routes/whatsapp-reader.ts:43` imports `SUPPORTED_MIME_SET`, `MAX_FILE_BYTES` and
|
|
82
|
+
`MAX_ATTACHMENTS` from `app/admin-types.ts`. Those are runtime values, not types, so this
|
|
83
|
+
is genuinely server-executing code living outside the scanned tree today. It arms no
|
|
84
|
+
timer, so nothing is currently hidden, but the boundary "server code stops at `app/lib/`"
|
|
85
|
+
is already false and the design must not depend on it. No dynamic `import()` or
|
|
86
|
+
`require()` in `server/` reaches `app/`, and no file under `app/` outside `app/lib/`
|
|
87
|
+
imports a `node:` builtin.
|
|
88
|
+
|
|
89
|
+
## Approach
|
|
90
|
+
|
|
91
|
+
Scan the whole of `app/**`. Mark the fifteen browser timers `loop-exempt:client`.
|
|
92
|
+
|
|
93
|
+
Two alternatives were considered and rejected.
|
|
94
|
+
|
|
95
|
+
**Reachability from the two server entry points.** The gate could resolve the import graph
|
|
96
|
+
from `server/index.ts` and `server/edge.ts` and scan exactly what it reaches. This is the
|
|
97
|
+
most precise statement of the real semantic. It is rejected because it puts a module
|
|
98
|
+
resolver inside a gate whose own header records that hand-rolled parsing is what killed
|
|
99
|
+
its first version: that version stripped `//` comments, ate the closing backtick of every
|
|
100
|
+
template literal containing a URL, and reported `ok` on a real violation. The gate's
|
|
101
|
+
governing principle is to match raw lines and parse nothing.
|
|
102
|
+
|
|
103
|
+
**Close 1818 as void.** Since there is no route-file surface under `app/` in a Vite SPA, a
|
|
104
|
+
server loop arguably has nowhere there to hide. This is rejected because it rests on
|
|
105
|
+
"everything under `app/` is client today", which is the assumption the task exists to stop
|
|
106
|
+
relying on, and which `app/admin-types.ts` already partially falsifies.
|
|
107
|
+
|
|
108
|
+
## Design
|
|
109
|
+
|
|
110
|
+
### The scan root
|
|
111
|
+
|
|
112
|
+
`SCAN_ROOTS` becomes `['server', 'app']`, replacing `['server', 'app/lib']`.
|
|
113
|
+
|
|
114
|
+
`app/lib` is subsumed rather than listed alongside `app`. Listing both would walk `app/lib`
|
|
115
|
+
twice and double its contribution to `files=` and `sites=`, corrupting the exact counter
|
|
116
|
+
the task names as its evidence that the widening took effect.
|
|
117
|
+
|
|
118
|
+
### The fifteen markers
|
|
119
|
+
|
|
120
|
+
Each site gets `// loop-exempt:client <reason>` on its own line, with the reason specific
|
|
121
|
+
to that timer rather than a repeated phrase. `client` is already in the closed category set
|
|
122
|
+
established by 1814 (`per-connection`, `demand-singleton`, `client`, `registry-primitive`),
|
|
123
|
+
so no convention changes. The task puts the marker convention and category set explicitly
|
|
124
|
+
out of scope.
|
|
125
|
+
|
|
126
|
+
The sites:
|
|
127
|
+
|
|
128
|
+
| File | Line | What it ticks |
|
|
129
|
+
|---|---|---|
|
|
130
|
+
| `app/AdminShell.tsx` | 228 | 60s poll |
|
|
131
|
+
| `app/Sidebar.tsx` | 512, 534, 1609 | channel conversations, sessions, system stats |
|
|
132
|
+
| `app/activity/page.tsx` | 428 | 15s activity reload |
|
|
133
|
+
| `app/chat/page.tsx` | 700, 721 | pointer poll, 60s read |
|
|
134
|
+
| `app/chat/transcript-render.tsx` | 80 | 1s relative-time clock |
|
|
135
|
+
| `app/graph/page.tsx` | 1456 | layout poll |
|
|
136
|
+
| `app/graph/settle-lock.ts` | 137 | vis-network motion sampler |
|
|
137
|
+
| `app/hooks/useViewMode.ts` | 110 | view-mode poll |
|
|
138
|
+
| `app/hooks/useVoiceRecorder.ts` | 142 | recording elapsed timer |
|
|
139
|
+
| `app/useAdminAuth.ts` | 219 | auth refresh |
|
|
140
|
+
| `app/useElapsedTimer.ts` | 29, 35 | session tick, elapsed tick |
|
|
141
|
+
|
|
142
|
+
### The header
|
|
143
|
+
|
|
144
|
+
The gate's SCOPE block currently reads "Covers `server/**` and `app/lib/**` … NOT covered:
|
|
145
|
+
the rest of `app/**`, which is Next.js route and component code running in the visitor's
|
|
146
|
+
browser". Every clause of that becomes false or was already false.
|
|
147
|
+
|
|
148
|
+
It is rewritten to state the real boundary (`server/**` and `app/**`, excluding
|
|
149
|
+
`__tests__`), that the server bundles from two entries so `server/**` is where server code
|
|
150
|
+
starts, and that `app/admin-types.ts` is already server-reachable from outside `app/lib/`
|
|
151
|
+
— which is why the whole of `app/` is scanned rather than a subtree of it. A gate whose
|
|
152
|
+
header overclaims is the defect class this file exists to prevent, so the header moves in
|
|
153
|
+
the same change as the behaviour.
|
|
154
|
+
|
|
155
|
+
### Tests
|
|
156
|
+
|
|
157
|
+
The behaviour belongs in the project's own committed suite at
|
|
158
|
+
`scripts/__tests__/check-loops-registered.test.ts`, so these are committed rather than
|
|
159
|
+
ephemeral.
|
|
160
|
+
|
|
161
|
+
One existing test **encodes the hole this task closes**. `ignores files outside the two
|
|
162
|
+
scan roots` (line 87) proves its point with `app/chat/page.tsx`, asserting exit 0 and
|
|
163
|
+
`sites=0`. That file moves in-scope, so the test must be rewritten against a path that is
|
|
164
|
+
genuinely outside both roots, not merely extended around.
|
|
165
|
+
|
|
166
|
+
New cases:
|
|
167
|
+
|
|
168
|
+
- An unmarked module-scope `setInterval` in a component file under `app/` outside
|
|
169
|
+
`app/lib/` fails with exit 1 and the message naming the file.
|
|
170
|
+
- A marked `client` site in that same area passes and is counted under `client=`.
|
|
171
|
+
|
|
172
|
+
The real-tree floor assertions rise so a walk that silently stops reading shows as
|
|
173
|
+
collapsed counts rather than a healthy `ok`.
|
|
174
|
+
|
|
175
|
+
### Mutation check
|
|
176
|
+
|
|
177
|
+
Required by the task on 1814's terms. A real unmarked `setInterval` is inserted into an
|
|
178
|
+
actual file under `app/` outside `app/lib/`, the gate is observed exiting 1 and naming
|
|
179
|
+
that file, and the line is removed. A negative test that passes because the signal is dead
|
|
180
|
+
is the failure this whole line of work exists to remove, and a synthetic fixture under
|
|
181
|
+
`--root` cannot prove the real scan root moved.
|
|
182
|
+
|
|
183
|
+
## Observability
|
|
184
|
+
|
|
185
|
+
`files=` rises from **284** to **393**: 109 non-test source files under `app/` outside
|
|
186
|
+
`app/lib/`. The predicted figure is asserted rather than "the number went up", so a partial
|
|
187
|
+
walk fails instead of passing.
|
|
188
|
+
|
|
189
|
+
`sites=` rises from 10 to 25, `marked=` from 9 to 24, and `client=` from 1 to 16. No
|
|
190
|
+
`registerLoop` call is added, because the census found no standing loop in the newly
|
|
191
|
+
covered area. That pairing — coverage widens, registered loop count holds at 26 — is the
|
|
192
|
+
finding, not an absence of one.
|
|
193
|
+
|
|
194
|
+
## Scope boundaries
|
|
195
|
+
|
|
196
|
+
**In scope:** the scan boundary for `app/**` outside `app/lib/`, the marking that decision
|
|
197
|
+
implies, the gate's SCOPE header, and the test surface above.
|
|
198
|
+
|
|
199
|
+
**Out of scope:** the marker convention and category set, settled in 1814; the two scan
|
|
200
|
+
roots already covered; the gate's raw-line matching, whose two known holes are documented
|
|
201
|
+
in its header and are accepted rather than open.
|
|
202
|
+
|
|
203
|
+
**Answered, not deferred: `app/admin-types.ts` stays where it is.** Being server-reachable
|
|
204
|
+
from outside `app/lib/` reads at first like a filing error worth correcting. It is not.
|
|
205
|
+
That file exists precisely so it cannot import `app/lib/attachments.ts` — its own comment
|
|
206
|
+
records the reason ("cannot import a Node.js module into a client component"), and
|
|
207
|
+
`attachments.ts` opens with `node:crypto`, `node:fs/promises` and `node:fs`. `app/lib/` is
|
|
208
|
+
the Node-touching tree: 56 of its 191 non-test source files import a `node:` builtin.
|
|
209
|
+
Moving the deliberately client-safe mirror into that directory would file it beside the
|
|
210
|
+
module it exists to avoid. Separately, the move would buy this task's outcome nothing,
|
|
211
|
+
because the widened scan covers the file wherever it lives. So there is no structural
|
|
212
|
+
question left open here and no follow-up task to file.
|
|
213
|
+
|
|
214
|
+
**A real adjacent risk, named and not fixed here.** `ACCEPT_MIME` / `SUPPORTED_MIME_SET` in
|
|
215
|
+
`app/admin-types.ts` mirror `app/lib/attachments.ts` by hand, with nothing keeping the two
|
|
216
|
+
in sync. That is a mime-allowlist consistency problem with no bearing on loop coverage, and
|
|
217
|
+
it is not touched by this task.
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Task 1819 — top-level label allowlist drift
|
|
2
|
+
|
|
3
|
+
Design, 2026-07-20.
|
|
4
|
+
|
|
5
|
+
## Problem
|
|
6
|
+
|
|
7
|
+
Two tests fail on `main`:
|
|
8
|
+
|
|
9
|
+
- `server/lib/top-level-labels.test.ts` — deep-equals the derived allowlist against the real references dir
|
|
10
|
+
- `server/routes/__tests__/graph-default-topLevel.test.ts` — resolves the top-level label set for the default view
|
|
11
|
+
|
|
12
|
+
Both compare the hardcoded client mirror `FILTER_TOP_LEVEL_LABELS` against the set derived from `plugins/memory/references/`.
|
|
13
|
+
|
|
14
|
+
## Evidence
|
|
15
|
+
|
|
16
|
+
Measured set diff, both directions:
|
|
17
|
+
|
|
18
|
+
| Direction | Labels |
|
|
19
|
+
|---|---|
|
|
20
|
+
| In `FILTER_TOP_LEVEL_LABELS`, not in derived | `InboundInvoice` |
|
|
21
|
+
| In derived, not in `FILTER_TOP_LEVEL_LABELS` | none |
|
|
22
|
+
|
|
23
|
+
Client mirror 48 entries, derived 47. Contributing files: `schema-construction.md`, `schema-estate-agent.md`. Table-derived 14, static 33.
|
|
24
|
+
|
|
25
|
+
## Cause
|
|
26
|
+
|
|
27
|
+
Commit `7b225f814` (2026-07-18, "promote ledger labels to base") moved the `InboundInvoice` row out of the `## Top-level node types` table in `schema-construction.md` and into the `## Node Types` table in `schema-base.md`.
|
|
28
|
+
|
|
29
|
+
`getTopLevelLabelAllowlist` parses only `## Top-level node types` sections. It deliberately never parses `schema-base.md`, because that table mixes child types (`InvoiceLine`, `InvoicePayment`, `PostalAddress`) in with top-level ones. So the promotion deleted `InboundInvoice` from the derived set.
|
|
30
|
+
|
|
31
|
+
`STATIC_TOP_LEVEL_LABELS` exists precisely for base-schema labels with no top-level section to derive from. `InboundInvoice` was never added to it — `git log -S` confirms the string has never appeared in `top-level-labels.ts`.
|
|
32
|
+
|
|
33
|
+
The other three promoted labels (`InvoiceLine`, `InvoicePayment`, `Credit`) are documented children in `graph-labels.ts` and correctly stay off both sets.
|
|
34
|
+
|
|
35
|
+
Nothing regenerates either side. The rot mechanism is that a schema-doc edit in the memory plugin's references directory silently changes the UI's derived chip set, guarded only by a test that was not run before the promotion landed.
|
|
36
|
+
|
|
37
|
+
## Fix
|
|
38
|
+
|
|
39
|
+
`InboundInvoice` is genuinely top-level and the client mirror is correct. The derivation is missing an entry.
|
|
40
|
+
|
|
41
|
+
1. Add `InboundInvoice` to `STATIC_TOP_LEVEL_LABELS`.
|
|
42
|
+
2. Extend the `STATIC_TOP_LEVEL_LABELS` doc comment to cover the reverse migration. It documents only the forward direction today (a vertical gains a top-level section, so its labels leave static). It is silent on the direction that caused this: a label promoted into `schema-base.md` must enter static, because base is never parsed.
|
|
43
|
+
|
|
44
|
+
Editing either assertion is explicitly not the fix.
|
|
45
|
+
|
|
46
|
+
## CashEntry
|
|
47
|
+
|
|
48
|
+
The same commit added `CashEntry`, absent from both sets, so it fails no test but has no `/graph` chip. Operator decision on this sprint: add it as a top-level label.
|
|
49
|
+
|
|
50
|
+
Justification that it is top-level: the promoting commit describes it as money "not attached to any invoice" with "no parent label". Child labels are reached by drilling down from a parent neighbourhood. `CashEntry` has no parent, so without a chip it is unreachable on `/graph` except by search.
|
|
51
|
+
|
|
52
|
+
Five surfaces:
|
|
53
|
+
|
|
54
|
+
| Surface | Change | Gate |
|
|
55
|
+
|---|---|---|
|
|
56
|
+
| `GRAPH_LABEL_COLOURS` | unique hex | hex uniqueness is test-enforced |
|
|
57
|
+
| `LABEL_ICONS` | Lucide glyph | coverage test fails if a colour label has no glyph |
|
|
58
|
+
| `FILTER_TOP_LEVEL_LABELS` | add | client chip mirror |
|
|
59
|
+
| `STATIC_TOP_LEVEL_LABELS` | add | server derived set |
|
|
60
|
+
| `pickShortLabel` | `CashEntry` branch | properties are `entryId`/`direction`/`amount`/`date`/`description`; none match the generic caption keys, so nodes would render captionless |
|
|
61
|
+
|
|
62
|
+
### Placement
|
|
63
|
+
|
|
64
|
+
Append `CashEntry` at the end of `GRAPH_LABEL_COLOURS`, not beside the ledger family.
|
|
65
|
+
|
|
66
|
+
`SHAPE_BY_LABEL` assigns each label a shape by its index position in `ALL_GRAPH_LABELS`, which is the colour table's key order. A mid-table insert shifts every subsequent index and changes the node shape of roughly 40 labels across the whole graph. No test catches that. Appending costs source-file grouping and buys zero collateral churn.
|
|
67
|
+
|
|
68
|
+
### Build
|
|
69
|
+
|
|
70
|
+
`platform/lib/graph-style/dist` is a symlink to the parent checkout in this worktree. Replace it with a local build before editing, so the parent checkout's build output is never written to.
|
|
71
|
+
|
|
72
|
+
## Scope boundaries
|
|
73
|
+
|
|
74
|
+
**In scope:** the cause of the disagreement, the correct value of the label set, both failing tests, and the five `CashEntry` surfaces.
|
|
75
|
+
|
|
76
|
+
**Out of scope:** the `/graph` view's rendering; the loop registry and its gate (1810, 1814, 1818); other pre-existing red on `main`; retiring the client mirror in favour of the derived set (already noted as a follow-up in `graph-labels.ts`).
|
|
77
|
+
|
|
78
|
+
## Testing
|
|
79
|
+
|
|
80
|
+
The two named tests, passing because the sets agree. Plus the graph-style icon-coverage and hex-uniqueness tests, and the client `graph-labels.test.ts` suite, since the colour table changed.
|
package/payload/platform/docs/superpowers/specs/2026-07-20-task-1829-platform-ui-typecheck-design.md
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# Task 1829 — package-wide typecheck for `platform/ui` (design)
|
|
2
|
+
|
|
3
|
+
**Date:** 2026-07-20
|
|
4
|
+
**Task:** [`.tasks/pending/1829-platform-ui-typechecks-nowhere-so-every-type-contract-is-advisory.md`](../../../../.tasks/pending/1829-platform-ui-typechecks-nowhere-so-every-type-contract-is-advisory.md)
|
|
5
|
+
**Depends on:** 1825 (landed — the narrow loop-contract gate this replaces)
|
|
6
|
+
|
|
7
|
+
## Problem
|
|
8
|
+
|
|
9
|
+
Nothing in `platform/ui` runs the TypeScript compiler. `npm run build` is `vite build && tsup`,
|
|
10
|
+
both esbuild-backed, and esbuild strips types without reading them. There is no `lint` script and
|
|
11
|
+
no `typecheck` script. None of the eighteen prebuild gates invokes `tsc`, and no CI workflow does
|
|
12
|
+
either. A type error in this package ships.
|
|
13
|
+
|
|
14
|
+
Task 1825 built a compile-time contract for the loop-row wire shape, found nothing enforced it, and
|
|
15
|
+
added `scripts/check-loop-contract-types.mjs` — a gate that runs `tsc` and then discards every
|
|
16
|
+
error outside six named files. Six files are gated; roughly three hundred are not.
|
|
17
|
+
|
|
18
|
+
## Measurements
|
|
19
|
+
|
|
20
|
+
Every figure below was measured in the task-1829 worktree on 2026-07-20, Node 22.22.0, with
|
|
21
|
+
`platform/node_modules` and `platform/ui/node_modules` symlinked from the parent checkout.
|
|
22
|
+
|
|
23
|
+
The task file's stated figures do not survive measurement, and three of its premises change as a
|
|
24
|
+
result.
|
|
25
|
+
|
|
26
|
+
| Quantity | Task file | Measured |
|
|
27
|
+
|---|---|---|
|
|
28
|
+
| `tsc --noEmit` wall time | ~51s | 19s cold, 13s warm |
|
|
29
|
+
| Total errors | 103 | 97 before `build:lib`, 80 after |
|
|
30
|
+
| Error classes | 2 | 3 |
|
|
31
|
+
|
|
32
|
+
**The sibling `dist/` question is settled: it is a setup problem, not a source problem.** Running
|
|
33
|
+
`npm run build:lib` in `platform/` (clean, 24s) clears all 14 `TS2307` unresolved-import errors. It
|
|
34
|
+
also removes two files from the report entirely — `../lib/graph-search/src/index.ts` and
|
|
35
|
+
`../plugins/memory/mcp/src/tools/memory-delete.ts` — because with the dists present those imports
|
|
36
|
+
resolve to `dist/*.d.ts` rather than dragging the sibling `.ts` sources into the program. So the
|
|
37
|
+
gate's prerequisite is a documented build order, exactly as the task predicted.
|
|
38
|
+
|
|
39
|
+
**The runtime cost concern dissolves.** At 13–19s there is no case for `--incremental` with a
|
|
40
|
+
cached build-info file, and no case for moving the check to CI. It goes in prebuild.
|
|
41
|
+
|
|
42
|
+
**There is a third error class the task did not anticipate.** After `build:lib`, the 80 remaining
|
|
43
|
+
errors split three ways:
|
|
44
|
+
|
|
45
|
+
| Class | Count | Nature |
|
|
46
|
+
|---|---|---|
|
|
47
|
+
| Test-file errors | 62 | Genuine; implicit-`any` params and stale fixtures |
|
|
48
|
+
| Production-file errors, fixable in-tree | 8 | Genuine |
|
|
49
|
+
| `neo4j-driver` v5/v6 type skew | 10 | Cross-package version conflict |
|
|
50
|
+
|
|
51
|
+
## The `neo4j-driver` skew
|
|
52
|
+
|
|
53
|
+
`platform/ui` declares `neo4j-driver@^6.0.1`. Thirteen workspace packages under `platform/` declare
|
|
54
|
+
`^5.28.1`, which hoists to `platform/node_modules` as 5.28.3. `platform/lib/*` has no
|
|
55
|
+
`package.json`, so those libs compile against the hoisted v5, while `platform/ui` hands them v6
|
|
56
|
+
`Session` objects. That is the eight `TS2739` and two `TS2345` errors.
|
|
57
|
+
|
|
58
|
+
It is not a worktree artefact. It reproduces wherever both trees are installed.
|
|
59
|
+
|
|
60
|
+
At runtime it is currently inert but not honest. `neo4j-driver` is external to the tsup bundle,
|
|
61
|
+
while `platform/lib/*` is inlined into `server.js`. So lib code compiled against v5 types already
|
|
62
|
+
executes against v6 in the server process, and against v5 in each plugin MCP process. Both work
|
|
63
|
+
today.
|
|
64
|
+
|
|
65
|
+
`platform/ui` has carried `^6` since the fold-in commit; it was never deliberately bumped. It uses
|
|
66
|
+
only `neo4j.int`, `isInt`, `driver`, `auth`, the six temporal `is*` guards, and `type { Session }`.
|
|
67
|
+
Every one exists in v5 — the export diff shows v6 is a strict superset and v5 has nothing v6 lacks.
|
|
68
|
+
|
|
69
|
+
**Decision: align downward.** `platform/ui` moves to `^5.28.1`. This is one source file, it lands
|
|
70
|
+
`platform/ui` on the same version as the seventeen other packages and as what `platform/lib/*` is
|
|
71
|
+
compiled against, and it stays inside the task's scope boundary. Aligning upward would move every
|
|
72
|
+
platform plugin MCP, service and premium-plugin onto v6 and breach the task's stated out-of-scope
|
|
73
|
+
line; that is its own sprint.
|
|
74
|
+
|
|
75
|
+
`payload/server/package.json` is generated from `platform/ui/package.json` at bundle time
|
|
76
|
+
(`packages/create-maxy-code/scripts/bundle.js`, `externalDeps`), so the deployed server's driver
|
|
77
|
+
version follows this one file.
|
|
78
|
+
|
|
79
|
+
## The two production defects
|
|
80
|
+
|
|
81
|
+
Both were found by the compiler and neither is cosmetic.
|
|
82
|
+
|
|
83
|
+
**`ChannelIcon` — a live crash.** `GLYPHS` is typed `Record<ReaderChannel, …>` but holds only
|
|
84
|
+
`whatsapp` and `telegram`; `ReaderChannel` is `'whatsapp' | 'telegram' | 'webchat'`. At
|
|
85
|
+
`Sidebar.tsx:1499` a conversation row passes `c.channel`, and `select-sessions.ts:43` states that a
|
|
86
|
+
row can carry any of the three. A webchat row there makes `GLYPHS['webchat']` undefined and `g.fill`
|
|
87
|
+
throws, taking the sidebar subtree with it. `SessionChannelBadge`, in the same file, already routes
|
|
88
|
+
webchat to a globe and unknown tokens to a neutral bubble.
|
|
89
|
+
|
|
90
|
+
The fix is not to invent a webchat brand path. `ChannelIcon` renders brand glyphs by design, which
|
|
91
|
+
is why `BRAND_CHANNELS` maps only two. Its prop type narrows to the brand channels, and
|
|
92
|
+
`Sidebar.tsx:1499` uses `SessionChannelBadge`, the component built for the all-three case. For
|
|
93
|
+
whatsapp and telegram this is behaviour-preserving; `SessionChannelBadge` delegates to
|
|
94
|
+
`ChannelIcon` at the same size.
|
|
95
|
+
|
|
96
|
+
**`webchat-gateway` — the type is wrong, not the value.** `public-session-end-review.ts:41` declares
|
|
97
|
+
`dispatchFor: 'eviction' | 'exit'`. The local callback type at `webchat-gateway.ts:83` narrowed it
|
|
98
|
+
to `'eviction'`. Line 422 passing `'exit'` is the documented public-exit path. Widening line 83 is a
|
|
99
|
+
type-only fix with no behaviour change.
|
|
100
|
+
|
|
101
|
+
## What is built
|
|
102
|
+
|
|
103
|
+
**Root-cause fixes, 70 errors to 0.** No error is suppressed; each is removed at source.
|
|
104
|
+
|
|
105
|
+
| Fix | Clears |
|
|
106
|
+
|---|---|
|
|
107
|
+
| `platform/ui/package.json`: `neo4j-driver` `^6.0.1` → `^5.28.1` | 10 |
|
|
108
|
+
| Test files: annotate implicit-`any` params, update stale fixtures | 62 |
|
|
109
|
+
| `sessions.ts` ×4, `whatsapp-reader.ts` ×1 implicit-`any` | 5 |
|
|
110
|
+
| `ChannelIcon` narrowed; `Sidebar.tsx:1499` uses `SessionChannelBadge` | 1 |
|
|
111
|
+
| `webchat-gateway.ts:83` `dispatchFor` widened | 1 |
|
|
112
|
+
| `sidebar-sessions.ts:283` `port` typed `string`, `requirePortEnv` returns `number` | 1 |
|
|
113
|
+
|
|
114
|
+
**The gate.** `scripts/check-types.mjs` runs `tsc --noEmit` over the whole package and fails on any
|
|
115
|
+
diagnostic. Its ignore list is empty and it says so in its own output, so a future non-zero ignored
|
|
116
|
+
count is visible in build logs rather than inferred.
|
|
117
|
+
|
|
118
|
+
It keeps both of 1825's blind-pass protections, because they are why that gate could be trusted:
|
|
119
|
+
|
|
120
|
+
- A diagnostic with no `path(line,col)` prefix means the compiler failed *before* typechecking
|
|
121
|
+
rather than while typechecking, and is reported as a configuration failure. Exit status cannot
|
|
122
|
+
carry this distinction — `tsc` exits 2 on a normal run with errors and 1 when it cannot find
|
|
123
|
+
`tsconfig.json` at all.
|
|
124
|
+
- A non-zero exit with no parseable diagnostic means nothing was typechecked, and fails loudly
|
|
125
|
+
rather than reporting success.
|
|
126
|
+
|
|
127
|
+
It adds one pre-flight of its own: the sibling `lib/*/dist` artefacts that `platform/ui` imports
|
|
128
|
+
must exist. If they do not, it exits naming `npm run build:lib` instead of emitting fourteen
|
|
129
|
+
confusing unresolved-import errors. This is the documented build order the task asked for, enforced
|
|
130
|
+
as a state check rather than left in prose.
|
|
131
|
+
|
|
132
|
+
`check-loop-contract-types.mjs` is deleted. Two gates asserting the same invariant at different
|
|
133
|
+
scopes is the drift 1825 exists to prevent, reintroduced one layer up.
|
|
134
|
+
|
|
135
|
+
A `typecheck` script is added to `package.json` so the check is runnable on its own.
|
|
136
|
+
|
|
137
|
+
## Testing
|
|
138
|
+
|
|
139
|
+
The load-bearing case is a deliberately introduced type error in a file the narrow gate never
|
|
140
|
+
covered. It must be observed failing, and observed passing once removed. A gate that has only ever
|
|
141
|
+
been seen passing has not been shown to work.
|
|
142
|
+
|
|
143
|
+
Separately, the gate must be shown not to pass blind: pointed at a tree where `tsc` cannot run, it
|
|
144
|
+
exits non-zero rather than reporting success. And the dist pre-flight must be observed firing with a
|
|
145
|
+
lib dist absent.
|
|
146
|
+
|
|
147
|
+
The `neo4j-driver` downgrade is verified against a running Neo4j, not only against the compiler. A
|
|
148
|
+
dependency change that only ever passed a typecheck has not been shown to work either.
|
|
149
|
+
|
|
150
|
+
## Scope boundaries
|
|
151
|
+
|
|
152
|
+
**In scope:** making `tsc` run and pass over `platform/ui`; triaging every pre-existing error;
|
|
153
|
+
retiring the narrow loop-contract gate; aligning `platform/ui` onto `neo4j-driver` v5.
|
|
154
|
+
|
|
155
|
+
**Out of scope:** the other platform packages (`platform/lib/*`, `platform/plugins/*`), which still
|
|
156
|
+
typecheck nowhere — filed as a follow-up task rather than left as prose. Adding eslint. Changing
|
|
157
|
+
`tsconfig.json` strictness. Moving the seventeen v5 packages to v6. The loop-row contract itself,
|
|
158
|
+
which 1825 closed.
|
|
159
|
+
|
|
160
|
+
## Observability
|
|
161
|
+
|
|
162
|
+
None at runtime; this is a build-time contract with no runtime surface. The gate's own output names
|
|
163
|
+
the error count found and the count ignored.
|