@rubytech/create-maxy-code 0.1.496 → 0.1.498
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-24-task-1942-subaccount-switcher-brand-head.md +327 -0
- package/payload/platform/docs/superpowers/specs/2026-07-24-task-1942-subaccount-switcher-brand-head-design.md +35 -0
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +72 -12
- package/payload/platform/plugins/docs/references/admin-ui.md +50 -10
- package/payload/platform/plugins/docs/references/outlook-guide.md +5 -1
- package/payload/platform/plugins/docs/references/sweep-guide.md +16 -0
- package/payload/platform/plugins/memory/PLUGIN.md +2 -0
- package/payload/platform/plugins/memory/mcp/dist/index.js +2 -2
- package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/scripts/__tests__/dream-cycle-sweep-decision.test.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/scripts/__tests__/dream-cycle-sweep-decision.test.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/scripts/__tests__/dream-cycle-sweep-decision.test.js +84 -0
- package/payload/platform/plugins/memory/mcp/dist/scripts/__tests__/dream-cycle-sweep-decision.test.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/scripts/dream-cycle-sweep-decision.d.ts +54 -0
- package/payload/platform/plugins/memory/mcp/dist/scripts/dream-cycle-sweep-decision.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/scripts/dream-cycle-sweep-decision.js +58 -0
- package/payload/platform/plugins/memory/mcp/dist/scripts/dream-cycle-sweep-decision.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/scripts/dream-cycle-sweep.d.ts +30 -0
- package/payload/platform/plugins/memory/mcp/dist/scripts/dream-cycle-sweep.d.ts.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/scripts/dream-cycle-sweep.js +204 -0
- package/payload/platform/plugins/memory/mcp/dist/scripts/dream-cycle-sweep.js.map +1 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-archive-derive-insights.test.js +30 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-archive-derive-insights.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-derive-insights.d.ts +4 -2
- package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-derive-insights.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-derive-insights.js +38 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-derive-insights.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/vitest.config.ts +2 -0
- package/payload/platform/plugins/memory/skills/conversation-archive-enrich/SKILL.md +10 -4
- package/payload/platform/plugins/outlook/.claude-plugin/plugin.json +1 -1
- package/payload/platform/plugins/outlook/PLUGIN.md +6 -2
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/dispatch-grouping.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/dispatch-grouping.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/dispatch-grouping.test.js +54 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/dispatch-grouping.test.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/fetch-body-fromname.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/fetch-body-fromname.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/fetch-body-fromname.test.js +19 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/fetch-body-fromname.test.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-ingest-decisions.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-ingest-decisions.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-ingest-decisions.test.js +20 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-ingest-decisions.test.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/index.js +21 -0
- package/payload/platform/plugins/outlook/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/lib/conversation-archive-dispatch.d.ts +40 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/conversation-archive-dispatch.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/conversation-archive-dispatch.js +143 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/conversation-archive-dispatch.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/neo4j.d.ts +5 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/neo4j.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/neo4j.js +44 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/neo4j.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-fetch-body.d.ts +14 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-fetch-body.d.ts.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-fetch-body.js +13 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-fetch-body.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-ingest.d.ts +27 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-ingest.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-ingest.js +69 -0
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-ingest.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/package.json +1 -0
- package/payload/platform/plugins/outlook/references/graph-surfaces.md +10 -1
- package/payload/platform/plugins/outlook/skills/outlook/SKILL.md +4 -0
- package/payload/platform/plugins/outlook/skills/outlook-mail-ingest/SKILL.md +85 -0
- package/payload/platform/plugins/sweep/PLUGIN.md +5 -1
- package/payload/platform/plugins/sweep/skills/email-graph-sweep/SKILL.md +165 -0
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js +1 -0
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
- package/payload/platform/templates/specialists/agents/personal-assistant.md +1 -1
- package/payload/server/package.json +2 -1
- package/payload/server/public/activity.html +6 -6
- package/payload/server/public/assets/{AdminLoginScreens-CFF28MGy.js → AdminLoginScreens-Ce5aBBWF.js} +1 -1
- package/payload/server/public/assets/AdminShell-BFVVkWFC.js +2 -0
- package/payload/server/public/assets/{activity-CElZcdjR.js → activity-CKUYOLd2.js} +1 -1
- package/payload/server/public/assets/admin-BWb8me-E.js +1 -0
- package/payload/server/public/assets/{arc-CFMJyMm-.js → arc-CpWWSum8.js} +1 -1
- package/payload/server/public/assets/architecture-YZFGNWBL-D-AX2VuA.js +1 -0
- package/payload/server/public/assets/{architectureDiagram-Q4EWVU46-CiSSgdL8.js → architectureDiagram-Q4EWVU46-D2EzaII0.js} +1 -1
- package/payload/server/public/assets/{blockDiagram-DXYQGD6D-_sXtIW5c.js → blockDiagram-DXYQGD6D-8kDpaNSZ.js} +1 -1
- package/payload/server/public/assets/{bot-Q-JmeVT3.js → bot-DESMpxSQ.js} +1 -1
- package/payload/server/public/assets/{browser-BrfPJMYF.js → browser-CpLq_DIo.js} +1 -1
- package/payload/server/public/assets/{c4Diagram-AHTNJAMY-Bh7OAMXs.js → c4Diagram-AHTNJAMY-CbwmZpK3.js} +1 -1
- package/payload/server/public/assets/calendar-ClOwyuz-.js +1 -0
- package/payload/server/public/assets/channel-DZ5sh6YQ.js +1 -0
- package/payload/server/public/assets/chat-qtOiJX-Y.js +1 -0
- package/payload/server/public/assets/chevron-left-BzIg4in6.js +1 -0
- package/payload/server/public/assets/chevron-right-BcFSa9C3.js +1 -0
- package/payload/server/public/assets/{chunk-2KRD3SAO-D4fktAwW.js → chunk-2KRD3SAO-PFheZ9mw.js} +1 -1
- package/payload/server/public/assets/{chunk-336JU56O-CcQoKH2G.js → chunk-336JU56O-CCFOp5VK.js} +2 -2
- package/payload/server/public/assets/chunk-426QAEUC-CcjBLp4a.js +1 -0
- package/payload/server/public/assets/{chunk-4BX2VUAB-C13Wz3zS.js → chunk-4BX2VUAB-Hz4JWOf2.js} +1 -1
- package/payload/server/public/assets/{chunk-4TB4RGXK-BL48D7gN.js → chunk-4TB4RGXK-4xeV0s85.js} +1 -1
- package/payload/server/public/assets/{chunk-55IACEB6-TmOKdy8a.js → chunk-55IACEB6-4QKgBpoh.js} +1 -1
- package/payload/server/public/assets/{chunk-5FUZZQ4R-DZ_jSF6N.js → chunk-5FUZZQ4R-ms3K6Z5z.js} +1 -1
- package/payload/server/public/assets/{chunk-5PVQY5BW-De4G1vfV.js → chunk-5PVQY5BW-CIC4GIGi.js} +1 -1
- package/payload/server/public/assets/{chunk-67CJDMHE-Cif9Uu5C.js → chunk-67CJDMHE-B-XrWVmV.js} +1 -1
- package/payload/server/public/assets/{chunk-7N4EOEYR-WjTHCXnd.js → chunk-7N4EOEYR-C7ECOUYQ.js} +1 -1
- package/payload/server/public/assets/{chunk-AA7GKIK3-CwCfdQmb.js → chunk-AA7GKIK3-DOZ6Fxwk.js} +1 -1
- package/payload/server/public/assets/{chunk-BSJP7CBP-9A67yXwT.js → chunk-BSJP7CBP-4cJ_b0c6.js} +1 -1
- package/payload/server/public/assets/{chunk-CIAEETIT-DPMiWSBi.js → chunk-CIAEETIT--mhZipWM.js} +1 -1
- package/payload/server/public/assets/{chunk-EDXVE4YY-C2f_Qvx-.js → chunk-EDXVE4YY-jZpVwob_.js} +1 -1
- package/payload/server/public/assets/{chunk-ENJZ2VHE-B-Q9EbSQ.js → chunk-ENJZ2VHE-Nn-p-xIA.js} +1 -1
- package/payload/server/public/assets/{chunk-FMBD7UC4-Bmt_ZRlV.js → chunk-FMBD7UC4-DyNpcKOS.js} +1 -1
- package/payload/server/public/assets/{chunk-FOC6F5B3-DfGJGxpX.js → chunk-FOC6F5B3-C0P8q1uz.js} +1 -1
- package/payload/server/public/assets/{chunk-ICPOFSXX-Wyym1Q8K.js → chunk-ICPOFSXX-CdXQ5SfI.js} +2 -2
- package/payload/server/public/assets/{chunk-K5T4RW27-DMpoaqw2.js → chunk-K5T4RW27-BVZJKKsT.js} +1 -1
- package/payload/server/public/assets/{chunk-KGLVRYIC-DYUFgri2.js → chunk-KGLVRYIC-QlVWTe0V.js} +1 -1
- package/payload/server/public/assets/{chunk-LIHQZDEY-x3LmeKmx.js → chunk-LIHQZDEY-CpGVZutP.js} +1 -1
- package/payload/server/public/assets/{chunk-ORNJ4GCN-BeuNAWkW.js → chunk-ORNJ4GCN-WVcS6fUC.js} +1 -1
- package/payload/server/public/assets/{chunk-OYMX7WX6-BUWVAfQA.js → chunk-OYMX7WX6-B7hiSTed.js} +1 -1
- package/payload/server/public/assets/chunk-QZHKN3VN-B2WjDYBE.js +1 -0
- package/payload/server/public/assets/{chunk-U2HBQHQK-DKS5adQo.js → chunk-U2HBQHQK-DJPnZxGy.js} +1 -1
- package/payload/server/public/assets/{chunk-X2U36JSP-CT_UM4IN.js → chunk-X2U36JSP-DilBs1Yc.js} +1 -1
- package/payload/server/public/assets/{chunk-XPW4576I-ByFnN4JO.js → chunk-XPW4576I-B5-uyrJ0.js} +1 -1
- package/payload/server/public/assets/{chunk-YZCP3GAM-DCetC20J.js → chunk-YZCP3GAM-BNO4z0Mv.js} +1 -1
- package/payload/server/public/assets/{chunk-ZZ45TVLE-Bh1QIv8N.js → chunk-ZZ45TVLE-BzHMQpip.js} +1 -1
- package/payload/server/public/assets/classDiagram-6PBFFD2Q-CT3jNrze.js +1 -0
- package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-DJRsjAkC.js +1 -0
- package/payload/server/public/assets/clock-Dr3NGtjR.js +1 -0
- package/payload/server/public/assets/clone-FcEDnoMT.js +1 -0
- package/payload/server/public/assets/{cose-bilkent-S5V4N54A-DgLEaApT.js → cose-bilkent-S5V4N54A-3DWYmn1o.js} +1 -1
- package/payload/server/public/assets/{dagre-unhMIyVo.js → dagre-Cpy31xJW.js} +1 -1
- package/payload/server/public/assets/{dagre-KV5264BT-BfkUPrEV.js → dagre-KV5264BT-C8fyxHFd.js} +1 -1
- package/payload/server/public/assets/data-BjqC4hyM.js +1 -0
- package/payload/server/public/assets/{diagram-5BDNPKRD-BjjwcTD9.js → diagram-5BDNPKRD-ycyhnLvr.js} +1 -1
- package/payload/server/public/assets/{diagram-G4DWMVQ6-BbgWtVSS.js → diagram-G4DWMVQ6-BkeYicIt.js} +1 -1
- package/payload/server/public/assets/{diagram-MMDJMWI5-DyAnkWGy.js → diagram-MMDJMWI5-BcGZU-Q3.js} +1 -1
- package/payload/server/public/assets/{diagram-TYMM5635-SAMMoLxq.js → diagram-TYMM5635-DuBDCFb_.js} +1 -1
- package/payload/server/public/assets/{dist-C9D_xz81.js → dist-BYc_arNy.js} +1 -1
- package/payload/server/public/assets/{erDiagram-SMLLAGMA-Cvo-5-fD.js → erDiagram-SMLLAGMA-DmkeOzvp.js} +1 -1
- package/payload/server/public/assets/{flatten-BXzaHgtL.js → flatten-QjnJ1Gcl.js} +1 -1
- package/payload/server/public/assets/{flowDiagram-DWJPFMVM-y3o2Ycld.js → flowDiagram-DWJPFMVM-CkE-pjuB.js} +1 -1
- package/payload/server/public/assets/{ganttDiagram-T4ZO3ILL-BEm_CQaK.js → ganttDiagram-T4ZO3ILL-Cjiy3kvE.js} +1 -1
- package/payload/server/public/assets/gitGraph-7Q5UKJZL-D1uhnP9U.js +1 -0
- package/payload/server/public/assets/{gitGraphDiagram-UUTBAWPF-CqmOxS7F.js → gitGraphDiagram-UUTBAWPF-CsI4lKZm.js} +1 -1
- package/payload/server/public/assets/{graph-BLO4nVi3.js → graph-B7niLM97.js} +3 -3
- package/payload/server/public/assets/{graph-labels-ibEi7ggo.js → graph-labels-DykmXYHA.js} +1 -1
- package/payload/server/public/assets/{graphlib-_HfbQcBf.js → graphlib-BkXxC6D5.js} +1 -1
- package/payload/server/public/assets/info-OMHHGYJF-oNm27GAV.js +1 -0
- package/payload/server/public/assets/infoDiagram-42DDH7IO-LxTftqyS.js +2 -0
- package/payload/server/public/assets/{isEmpty-uB1dEu_Z.js → isEmpty-BavJI9Cn.js} +1 -1
- package/payload/server/public/assets/{ishikawaDiagram-UXIWVN3A-C_d0vcZC.js → ishikawaDiagram-UXIWVN3A-D8lZtjRc.js} +1 -1
- package/payload/server/public/assets/{journeyDiagram-VCZTEJTY-DsjRBqlW.js → journeyDiagram-VCZTEJTY-kLE7SiR_.js} +1 -1
- package/payload/server/public/assets/{kanban-definition-6JOO6SKY-BwtK6LS-.js → kanban-definition-6JOO6SKY-ove6JuD3.js} +1 -1
- package/payload/server/public/assets/{line-szBCFOLJ.js → line-CZMSoxM6.js} +1 -1
- package/payload/server/public/assets/{linear-X3O7lwK3.js → linear-Be8IK9hK.js} +1 -1
- package/payload/server/public/assets/{maximize-2-CezchDEF.js → maximize-2-BbzPm7o8.js} +1 -1
- package/payload/server/public/assets/{mermaid-parser.core-BavvMAhU.js → mermaid-parser.core-BBADqbnp.js} +2 -2
- package/payload/server/public/assets/{mermaid.core-Br2F9Hdm.js → mermaid.core-mH3N7K0I.js} +3 -3
- package/payload/server/public/assets/{mindmap-definition-QFDTVHPH-4qlwXoJ-.js → mindmap-definition-QFDTVHPH-BjwwwWil.js} +1 -1
- package/payload/server/public/assets/{operator-B-3vkwO8.js → operator-icJwMkZF.js} +1 -1
- package/payload/server/public/assets/{ordinal-CZY9laxg.js → ordinal-BHeIKG-K.js} +1 -1
- package/payload/server/public/assets/packet-4T2RLAQJ-eQNVGiK3.js +1 -0
- package/payload/server/public/assets/page-DUgrGrJp.js +1 -0
- package/payload/server/public/assets/{page-BL1ko2_-.js → page-iG57nKOg.js} +4 -4
- package/payload/server/public/assets/{pdf-render-H884L3EI.js → pdf-render-fJz4vym9.js} +1 -1
- package/payload/server/public/assets/pie-ZZUOXDRM-E1u-48rt.js +1 -0
- package/payload/server/public/assets/{pieDiagram-DEJITSTG-BbXryIg0.js → pieDiagram-DEJITSTG-D-lXWWta.js} +1 -1
- package/payload/server/public/assets/{public-DxfSLhNp.js → public-CkOumzpf.js} +1 -1
- package/payload/server/public/assets/{quadrantDiagram-34T5L4WZ-BHLjcvh3.js → quadrantDiagram-34T5L4WZ-13QyG_AO.js} +1 -1
- package/payload/server/public/assets/radar-PYXPWWZC-Cn2cGN90.js +1 -0
- package/payload/server/public/assets/{reduce-BGQzAenn.js → reduce-Fm685to3.js} +1 -1
- package/payload/server/public/assets/{requirementDiagram-MS252O5E-BXaVO0lk.js → requirementDiagram-MS252O5E-Bx45Bgn4.js} +1 -1
- package/payload/server/public/assets/{rotate-ccw-CWvGAut4.js → rotate-ccw-L42fL8ey.js} +1 -1
- package/payload/server/public/assets/routines-JOV6xdYh.js +1 -0
- package/payload/server/public/assets/{sankeyDiagram-XADWPNL6-CmN1s17Q.js → sankeyDiagram-XADWPNL6-Cj-Gq25v.js} +1 -1
- package/payload/server/public/assets/{sequenceDiagram-FGHM5R23-DC08e5NY.js → sequenceDiagram-FGHM5R23-B8RDRq93.js} +1 -1
- package/payload/server/public/assets/{src-BTKk8KAG.js → src-D3VYuW_Z.js} +1 -1
- package/payload/server/public/assets/{stateDiagram-FHFEXIEX-DnoQMuOW.js → stateDiagram-FHFEXIEX-DoyFF1Kl.js} +1 -1
- package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-Bml-X4-H.js +1 -0
- package/payload/server/public/assets/{tasks-DlXIN3v3.js → tasks-GNtvZL8F.js} +1 -1
- package/payload/server/public/assets/{time-entry-format-VzHwljTv.js → time-entry-format-BreNpFLJ.js} +1 -1
- package/payload/server/public/assets/{timeline-definition-GMOUNBTQ-eTn3uRaH.js → timeline-definition-GMOUNBTQ-Df4RQXm6.js} +1 -1
- package/payload/server/public/assets/treeView-SZITEDCU-CimabhXy.js +1 -0
- package/payload/server/public/assets/treemap-W4RFUUIX-Cg264rpM.js +1 -0
- package/payload/server/public/assets/{triangle-alert-C5BpoPlq.js → triangle-alert-DumCWC08.js} +1 -1
- package/payload/server/public/assets/{useCopyFeedback-h1qT8mpZ.js → useCopyFeedback-DHCtz8qX.js} +1 -1
- package/payload/server/public/assets/{useSubAccountSwitcher-DDStL7Nt.js → useSubAccountSwitcher-Bc7XHr0L.js} +2 -2
- package/payload/server/public/assets/useSubAccountSwitcher-DPHQJO7E.css +1 -0
- package/payload/server/public/assets/{useVoiceRecorder-kqMitFlk.js → useVoiceRecorder-Cu6zvuFx.js} +2 -2
- package/payload/server/public/assets/{vennDiagram-DHZGUBPP-DNx6o_ZQ.js → vennDiagram-DHZGUBPP-D2o5og9l.js} +1 -1
- package/payload/server/public/assets/wardley-RL74JXVD-tbgfCg0E.js +1 -0
- package/payload/server/public/assets/{wardleyDiagram-NUSXRM2D-BZOchqF3.js → wardleyDiagram-NUSXRM2D-BDueObbS.js} +1 -1
- package/payload/server/public/assets/wrench-iWkROE1V.js +1 -0
- package/payload/server/public/assets/{xychartDiagram-5P7HB3ND-ZBEGH6ZI.js → xychartDiagram-5P7HB3ND-BTYs4Fgh.js} +1 -1
- package/payload/server/public/browser.html +5 -5
- package/payload/server/public/calendar.html +8 -7
- package/payload/server/public/chat.html +15 -15
- package/payload/server/public/data.html +12 -12
- package/payload/server/public/graph.html +10 -11
- package/payload/server/public/index.html +16 -17
- package/payload/server/public/operator.html +16 -16
- package/payload/server/public/public.html +15 -15
- package/payload/server/public/routines.html +20 -0
- package/payload/server/public/tasks.html +7 -7
- package/payload/server/server.js +417 -203
- package/payload/server/public/assets/AdminShell-ClIT8c0K.js +0 -2
- package/payload/server/public/assets/admin-BHiA9_Ks.js +0 -1
- package/payload/server/public/assets/architecture-YZFGNWBL-Cypt5qHL.js +0 -1
- package/payload/server/public/assets/calendar-D45S_aal.js +0 -1
- package/payload/server/public/assets/channel-BMyXE4zc.js +0 -1
- package/payload/server/public/assets/chat-gy8scQov.js +0 -1
- package/payload/server/public/assets/chevron-down-CdxvexgY.js +0 -1
- package/payload/server/public/assets/chevron-left-D5LLz6ke.js +0 -1
- package/payload/server/public/assets/chevron-right-DzPmwDew.js +0 -1
- package/payload/server/public/assets/chunk-426QAEUC-Xu6JKP4d.js +0 -1
- package/payload/server/public/assets/chunk-QZHKN3VN-7_kS3RBy.js +0 -1
- package/payload/server/public/assets/classDiagram-6PBFFD2Q-DJqbNcuC.js +0 -1
- package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-CGglqfQA.js +0 -1
- package/payload/server/public/assets/clone-BiTMTzp_.js +0 -1
- package/payload/server/public/assets/data-CUsNHUpu.js +0 -1
- package/payload/server/public/assets/gitGraph-7Q5UKJZL-C_-XjxLd.js +0 -1
- package/payload/server/public/assets/info-OMHHGYJF-BH8yDCfL.js +0 -1
- package/payload/server/public/assets/infoDiagram-42DDH7IO-DyDXyJ6U.js +0 -2
- package/payload/server/public/assets/packet-4T2RLAQJ-Bajcns4r.js +0 -1
- package/payload/server/public/assets/page-BbxoY4QI.js +0 -1
- package/payload/server/public/assets/pie-ZZUOXDRM-D7hjM5w1.js +0 -1
- package/payload/server/public/assets/radar-PYXPWWZC-B46d5x7a.js +0 -1
- package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-CrstH34p.js +0 -1
- package/payload/server/public/assets/treeView-SZITEDCU-3XQh5xQ1.js +0 -1
- package/payload/server/public/assets/treemap-W4RFUUIX-DwR-_l5z.js +0 -1
- package/payload/server/public/assets/useSubAccountSwitcher-DnfX9Uxe.css +0 -1
- package/payload/server/public/assets/wardley-RL74JXVD-DZp9oCGv.js +0 -1
- /package/payload/server/public/assets/{_baseFor-CMWvz0uA.js → _baseFor-Bvwvhrz4.js} +0 -0
- /package/payload/server/public/assets/{array-Cny_NT6T.js → array-D3d-WgFW.js} +0 -0
- /package/payload/server/public/assets/{chunk-TPjIAO9U.js → chunk-fQC6bEKA.js} +0 -0
- /package/payload/server/public/assets/{cytoscape.esm-De0CCmB2.js → cytoscape.esm-Cun-OM6a.js} +0 -0
- /package/payload/server/public/assets/{defaultLocale-BahRht8U.js → defaultLocale-CisKV5Ew.js} +0 -0
- /package/payload/server/public/assets/{init-DoIvjoJ_.js → init-D9ZOVaZ3.js} +0 -0
- /package/payload/server/public/assets/{katex-Bfn1OZEl.js → katex-C-3ZDK8o.js} +0 -0
- /package/payload/server/public/assets/{path-CTezU8L5.js → path-BMOgQSLM.js} +0 -0
- /package/payload/server/public/assets/{preload-helper-D_sPnjeL.js → preload-helper-DL2DwvxV.js} +0 -0
- /package/payload/server/public/assets/{rough.esm-BJo4pvXu.js → rough.esm-BrTFrov0.js} +0 -0
package/package.json
CHANGED
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
# Task 1942 — Sub-account switcher to sidebar brand head — Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For agentic workers:** REQUIRED SUB-SKILL: superpowers:executing-plans. Steps use checkbox (`- [ ]`) syntax.
|
|
4
|
+
|
|
5
|
+
**Goal:** Open the sub-account picker by clicking the sidebar brand head; remove it from the footer kebab.
|
|
6
|
+
|
|
7
|
+
**Architecture:** `Sidebar.tsx` owns a `switcherOpen` state and renders the brand head as a button (icon + lockup + chevron) when the switcher is available, with `SubAccountPicker` + a full-screen scrim anchored beneath `.side-brand`. `AccountMenu.tsx` loses the switcher block, its five props, and the `SubAccountPicker` import. `globals.css` makes `.side-brand` a positioned anchor, re-anchors the panel left-aligned, and deletes the dead footer overrides.
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** React 18, TypeScript, vitest + jsdom (Node 22), lucide-react icons.
|
|
10
|
+
|
|
11
|
+
## Global Constraints
|
|
12
|
+
|
|
13
|
+
- Node 22 for the jsdom test run (`platform/ui` requirement).
|
|
14
|
+
- Switcher availability gate is exactly `subAccounts.length > 1 && Boolean(onSwitchAccount)`; unchanged from today.
|
|
15
|
+
- No new task-id leaks in shipped docs; docs edit at `platform/plugins/docs/references/admin-ui.md`.
|
|
16
|
+
- All paths below are relative to the worktree's `maxy-code/platform/ui/`.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
### Task 1: Strip the switcher out of AccountMenu
|
|
21
|
+
|
|
22
|
+
**Files:**
|
|
23
|
+
- Modify: `app/components/AccountMenu.tsx`
|
|
24
|
+
- Test: `app/components/__tests__/AccountMenu.test.tsx`
|
|
25
|
+
|
|
26
|
+
**Interfaces:**
|
|
27
|
+
- Produces: `AccountMenuProps` with no `subAccounts`/`activeAccountId`/`switchingAccount`/`onSwitchAccount`/`refreshAccounts` fields.
|
|
28
|
+
|
|
29
|
+
- [ ] **Step 1: Update the test to assert the switcher is gone**
|
|
30
|
+
|
|
31
|
+
In `AccountMenu.test.tsx`, delete the two switcher tests (`renders no Switch account affordance with 0-1 sub-accounts` and `opens the sub-account picker and fires onSwitchAccount + refreshAccounts`, lines 56-88). Add one test:
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
it('renders no Switch account affordance (relocated to sidebar brand head, Task 1942)', () => {
|
|
35
|
+
render(<AccountMenu {...baseProps} variant="admin" cacheKey="k" onOpenConversations={vi.fn()} />)
|
|
36
|
+
expect(screen.queryByRole('menuitem', { name: /switch account/i })).toBeNull()
|
|
37
|
+
expect(screen.queryByPlaceholderText(/search sub-accounts/i)).toBeNull()
|
|
38
|
+
})
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Remove `subAccounts`/`activeAccountId`/`switchingAccount`/`onSwitchAccount`/`refreshAccounts` from any remaining render in the file.
|
|
42
|
+
|
|
43
|
+
- [ ] **Step 2: Run — expect fail (props still typed / item still renders)**
|
|
44
|
+
|
|
45
|
+
Run: `npx vitest run app/components/__tests__/AccountMenu.test.tsx`
|
|
46
|
+
Expected: FAIL (the Switch account menuitem still renders).
|
|
47
|
+
|
|
48
|
+
- [ ] **Step 3: Remove the switcher from AccountMenu.tsx**
|
|
49
|
+
|
|
50
|
+
- Delete the `import { SubAccountPicker } from './SubAccountPicker'` line.
|
|
51
|
+
- In `AccountMenuProps`, delete the five switcher prop declarations and their doc comment block (`subAccounts?`, `activeAccountId?`, `switchingAccount?`, `onSwitchAccount?`, `refreshAccounts?`).
|
|
52
|
+
- In the destructure, remove `subAccounts = []`, `activeAccountId = null`, `switchingAccount = false`, `onSwitchAccount`, `refreshAccounts`.
|
|
53
|
+
- Delete the `switcherOpen` state and `switcherAvailable` const.
|
|
54
|
+
- Delete the whole `{switcherAvailable && ( … <SubAccountPicker … /> … )}` JSX block.
|
|
55
|
+
- Remove `UserRound` from the lucide-react import (it was used only by the deleted block; grep the file to confirm no other use).
|
|
56
|
+
|
|
57
|
+
- [ ] **Step 4: Run — expect pass**
|
|
58
|
+
|
|
59
|
+
Run: `npx vitest run app/components/__tests__/AccountMenu.test.tsx`
|
|
60
|
+
Expected: PASS.
|
|
61
|
+
|
|
62
|
+
- [ ] **Step 5: Commit**
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
git add maxy-code/platform/ui/app/components/AccountMenu.tsx maxy-code/platform/ui/app/components/__tests__/AccountMenu.test.tsx
|
|
66
|
+
git commit -m "refactor(1942): remove sub-account switcher from AccountMenu"
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
### Task 2: Brand head becomes the switcher trigger in Sidebar
|
|
72
|
+
|
|
73
|
+
**Files:**
|
|
74
|
+
- Modify: `app/Sidebar.tsx`
|
|
75
|
+
- Test: `app/__tests__/Sidebar-subaccount-switcher.test.tsx` (create), `app/__tests__/Sidebar-user-footer.test.tsx`
|
|
76
|
+
|
|
77
|
+
**Interfaces:**
|
|
78
|
+
- Consumes: `SubAccountPicker` from `./components/SubAccountPicker`; `ChevronDown` from `lucide-react`; existing `subAccounts`/`activeAccountId`/`switchingAccount`/`onSwitchAccount`/`refreshAccounts` Sidebar props.
|
|
79
|
+
|
|
80
|
+
- [ ] **Step 1: Write the failing test (new file)**
|
|
81
|
+
|
|
82
|
+
Create `app/__tests__/Sidebar-subaccount-switcher.test.tsx`:
|
|
83
|
+
|
|
84
|
+
```tsx
|
|
85
|
+
import React from 'react'
|
|
86
|
+
import { render, screen, fireEvent } from '@testing-library/react'
|
|
87
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
|
88
|
+
import { Sidebar } from '../Sidebar'
|
|
89
|
+
|
|
90
|
+
function jsonResponse(body: unknown, status = 200): Response {
|
|
91
|
+
return new Response(JSON.stringify(body), { status, headers: { 'content-type': 'application/json' } }) as Response
|
|
92
|
+
}
|
|
93
|
+
function routedFetch() {
|
|
94
|
+
return vi.spyOn(global, 'fetch').mockImplementation((input: RequestInfo | URL) => {
|
|
95
|
+
const url = typeof input === 'string' ? input : input.toString()
|
|
96
|
+
if (url.includes('/api/admin/sidebar-sessions')) return Promise.resolve(jsonResponse({ accountId: 'acct', sessions: [] }))
|
|
97
|
+
if (url.includes('/api/admin/system-stats')) return Promise.resolve(jsonResponse({ cpuPct: null, memUsedPct: 0.5, platform: 'darwin' }))
|
|
98
|
+
if (url.includes('/api/admin/version')) return Promise.resolve(jsonResponse({ installed: '1.0.0', latest: null, updateAvailable: false }))
|
|
99
|
+
return Promise.resolve(jsonResponse({}, 404))
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
const ACCOUNTS = [
|
|
103
|
+
{ accountId: 'a1', businessName: 'House', role: 'admin', isHouse: true },
|
|
104
|
+
{ accountId: 'a2', businessName: 'Client', role: 'admin', isHouse: false },
|
|
105
|
+
]
|
|
106
|
+
function baseProps(over: Record<string, unknown> = {}) {
|
|
107
|
+
return {
|
|
108
|
+
businessName: 'House', cacheKey: 'k', role: 'admin', userName: 'Ada Ops', userAvatar: null,
|
|
109
|
+
onSelectProjects: vi.fn(), onSelectTasks: vi.fn(), onCloseMobileDrawer: vi.fn(),
|
|
110
|
+
collapsed: false, mobileDrawerOpen: false, onSelectData: vi.fn(),
|
|
111
|
+
onLogout: vi.fn(), onDisconnect: vi.fn(async () => true), disconnecting: false, claudeConnected: true,
|
|
112
|
+
...over,
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
beforeEach(() => { Object.defineProperty(document, 'hidden', { value: false, configurable: true }) })
|
|
116
|
+
afterEach(() => { vi.restoreAllMocks() })
|
|
117
|
+
|
|
118
|
+
describe('Sidebar sub-account switcher on the brand head (Task 1942)', () => {
|
|
119
|
+
it('brand head is a button that toggles the picker and fires refreshAccounts', () => {
|
|
120
|
+
routedFetch()
|
|
121
|
+
const refreshAccounts = vi.fn()
|
|
122
|
+
render(<Sidebar {...baseProps({ subAccounts: ACCOUNTS, activeAccountId: 'a1', switchingAccount: false, onSwitchAccount: vi.fn(), refreshAccounts })} />)
|
|
123
|
+
const head = screen.getByRole('button', { name: /switch sub-account/i })
|
|
124
|
+
expect(screen.queryByPlaceholderText(/search sub-accounts/i)).toBeNull()
|
|
125
|
+
fireEvent.click(head)
|
|
126
|
+
expect(screen.getByPlaceholderText(/search sub-accounts/i)).toBeInTheDocument()
|
|
127
|
+
expect(refreshAccounts).toHaveBeenCalledTimes(1)
|
|
128
|
+
fireEvent.click(head)
|
|
129
|
+
expect(screen.queryByPlaceholderText(/search sub-accounts/i)).toBeNull()
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
it('selecting a card calls onSwitchAccount and closes the picker', () => {
|
|
133
|
+
routedFetch()
|
|
134
|
+
const onSwitchAccount = vi.fn()
|
|
135
|
+
render(<Sidebar {...baseProps({ subAccounts: ACCOUNTS, activeAccountId: 'a1', switchingAccount: false, onSwitchAccount, refreshAccounts: vi.fn() })} />)
|
|
136
|
+
fireEvent.click(screen.getByRole('button', { name: /switch sub-account/i }))
|
|
137
|
+
fireEvent.click(screen.getByRole('button', { name: 'Client' }))
|
|
138
|
+
expect(onSwitchAccount).toHaveBeenCalledWith('a2')
|
|
139
|
+
expect(screen.queryByPlaceholderText(/search sub-accounts/i)).toBeNull()
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
it('single-account install: brand head is not a switcher button and no picker mounts', () => {
|
|
143
|
+
routedFetch()
|
|
144
|
+
render(<Sidebar {...baseProps({ subAccounts: [ACCOUNTS[0]], activeAccountId: 'a1', onSwitchAccount: vi.fn() })} />)
|
|
145
|
+
expect(screen.queryByRole('button', { name: /switch sub-account/i })).toBeNull()
|
|
146
|
+
expect(screen.queryByPlaceholderText(/search sub-accounts/i)).toBeNull()
|
|
147
|
+
})
|
|
148
|
+
})
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
- [ ] **Step 2: Run — expect fail**
|
|
152
|
+
|
|
153
|
+
Run: `npx vitest run app/__tests__/Sidebar-subaccount-switcher.test.tsx`
|
|
154
|
+
Expected: FAIL (no button named "Switch sub-account").
|
|
155
|
+
|
|
156
|
+
- [ ] **Step 3: Implement the trigger in Sidebar.tsx**
|
|
157
|
+
|
|
158
|
+
Add imports: `SubAccountPicker` from `'./components/SubAccountPicker'`, and `ChevronDown` to the existing `lucide-react` import.
|
|
159
|
+
|
|
160
|
+
Add state beside `footerMenuOpen`:
|
|
161
|
+
|
|
162
|
+
```tsx
|
|
163
|
+
const [switcherOpen, setSwitcherOpen] = useState(false)
|
|
164
|
+
const switcherAvailable = (subAccounts?.length ?? 0) > 1 && Boolean(onSwitchAccount)
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Replace the `<div className="side-brand">…</div>` block (icon + lockup) with:
|
|
168
|
+
|
|
169
|
+
```tsx
|
|
170
|
+
<div className="side-brand">
|
|
171
|
+
{switcherAvailable ? (
|
|
172
|
+
<button
|
|
173
|
+
type="button"
|
|
174
|
+
className="side-brand-trigger"
|
|
175
|
+
aria-haspopup="menu"
|
|
176
|
+
aria-expanded={switcherOpen}
|
|
177
|
+
aria-label="Switch sub-account"
|
|
178
|
+
onClick={() => {
|
|
179
|
+
const opening = !switcherOpen
|
|
180
|
+
setSwitcherOpen(opening)
|
|
181
|
+
if (opening) refreshAccounts?.()
|
|
182
|
+
console.info(`[admin-ui] op=subaccount-switcher-toggle anchor=brand-head open=${opening} available=true accounts=${subAccounts?.length ?? 0}`)
|
|
183
|
+
}}
|
|
184
|
+
>
|
|
185
|
+
<img className="side-brand-icon" src={brandIcon} alt="" aria-hidden="true" />
|
|
186
|
+
<span className="side-brand-lockup">
|
|
187
|
+
<span className="side-brand-business">{displayName}</span>
|
|
188
|
+
<span className="side-brand-product">{brand.productName}</span>
|
|
189
|
+
</span>
|
|
190
|
+
<ChevronDown size={14} className="side-brand-caret" aria-hidden="true" />
|
|
191
|
+
</button>
|
|
192
|
+
) : (
|
|
193
|
+
<>
|
|
194
|
+
<img className="side-brand-icon" src={brandIcon} alt="" aria-hidden="true" />
|
|
195
|
+
<span className="side-brand-lockup">
|
|
196
|
+
<span className="side-brand-business">{displayName}</span>
|
|
197
|
+
<span className="side-brand-product">{brand.productName}</span>
|
|
198
|
+
</span>
|
|
199
|
+
</>
|
|
200
|
+
)}
|
|
201
|
+
{switcherOpen && (
|
|
202
|
+
<>
|
|
203
|
+
<div className="side-brand-scrim" aria-hidden="true" onClick={() => setSwitcherOpen(false)} />
|
|
204
|
+
<SubAccountPicker
|
|
205
|
+
open={switcherOpen}
|
|
206
|
+
accounts={subAccounts ?? []}
|
|
207
|
+
activeAccountId={activeAccountId ?? null}
|
|
208
|
+
switching={switchingAccount ?? false}
|
|
209
|
+
onSelect={(accountId) => onSwitchAccount?.(accountId)}
|
|
210
|
+
onClose={() => setSwitcherOpen(false)}
|
|
211
|
+
/>
|
|
212
|
+
</>
|
|
213
|
+
)}
|
|
214
|
+
</div>
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
In the footer `<AccountMenu … />`, delete the five props `subAccounts`, `activeAccountId`, `switchingAccount`, `onSwitchAccount`, `refreshAccounts` (the Sidebar keeps consuming them for the head). Leave `onOpenConversations` and the rest.
|
|
218
|
+
|
|
219
|
+
- [ ] **Step 4: Run — expect pass**
|
|
220
|
+
|
|
221
|
+
Run: `npx vitest run app/__tests__/Sidebar-subaccount-switcher.test.tsx`
|
|
222
|
+
Expected: PASS.
|
|
223
|
+
|
|
224
|
+
- [ ] **Step 5: Fix the footer test that asserted the switcher was in the kebab**
|
|
225
|
+
|
|
226
|
+
In `app/__tests__/Sidebar-user-footer.test.tsx`, the test `threads the sub-account switcher and Conversations into the kebab menu` (lines 53-75) now asserts a false thing. Rewrite it to cover Conversations only:
|
|
227
|
+
|
|
228
|
+
```tsx
|
|
229
|
+
it('threads Conversations into the kebab menu (switcher moved to brand head, Task 1942)', async () => {
|
|
230
|
+
routedFetch({ cpuPct: null, memUsedPct: 0.5, platform: 'darwin' })
|
|
231
|
+
const onOpenConversations = vi.fn()
|
|
232
|
+
render(<Sidebar {...baseProps({ onOpenConversations })} />)
|
|
233
|
+
fireEvent.click(screen.getByRole('button', { name: 'Account menu' }))
|
|
234
|
+
expect(screen.queryByRole('menuitem', { name: /switch account/i })).toBeNull()
|
|
235
|
+
fireEvent.click(await screen.findByRole('menuitem', { name: /conversations/i }))
|
|
236
|
+
expect(onOpenConversations).toHaveBeenCalledTimes(1)
|
|
237
|
+
})
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
- [ ] **Step 6: Run both Sidebar suites — expect pass**
|
|
241
|
+
|
|
242
|
+
Run: `npx vitest run app/__tests__/Sidebar-subaccount-switcher.test.tsx app/__tests__/Sidebar-user-footer.test.tsx`
|
|
243
|
+
Expected: PASS.
|
|
244
|
+
|
|
245
|
+
- [ ] **Step 7: Commit**
|
|
246
|
+
|
|
247
|
+
```bash
|
|
248
|
+
git add maxy-code/platform/ui/app/Sidebar.tsx maxy-code/platform/ui/app/__tests__/Sidebar-subaccount-switcher.test.tsx maxy-code/platform/ui/app/__tests__/Sidebar-user-footer.test.tsx
|
|
249
|
+
git commit -m "feat(1942): brand head opens the sub-account picker"
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
### Task 3: CSS — anchor the panel under the head, style the trigger, drop dead overrides
|
|
255
|
+
|
|
256
|
+
**Files:**
|
|
257
|
+
- Modify: `app/globals.css`
|
|
258
|
+
|
|
259
|
+
**Interfaces:** consumes class names `side-brand`, `side-brand-trigger`, `side-brand-caret`, `side-brand-scrim`, `sub-account-picker-panel` from Task 2.
|
|
260
|
+
|
|
261
|
+
- [ ] **Step 1: Add `position: relative` to `.side-brand`**
|
|
262
|
+
|
|
263
|
+
In the `.side-brand { … }` rule, add `position: relative;`.
|
|
264
|
+
|
|
265
|
+
- [ ] **Step 2: Add the trigger, caret, and scrim rules** (place beside `.side-brand`)
|
|
266
|
+
|
|
267
|
+
```css
|
|
268
|
+
.side-brand-trigger {
|
|
269
|
+
display: flex; align-items: center; gap: 10px; flex: 1; min-width: 0;
|
|
270
|
+
background: none; border: 0; padding: 0; margin: 0;
|
|
271
|
+
font: inherit; color: inherit; text-align: left; cursor: pointer;
|
|
272
|
+
}
|
|
273
|
+
.side-brand-caret { flex: none; color: var(--text-tertiary); }
|
|
274
|
+
.side-brand-trigger:hover .side-brand-caret { color: var(--text-secondary); }
|
|
275
|
+
.platform.sidebar-collapsed .side-brand-caret { display: none; }
|
|
276
|
+
.side-brand-scrim { position: fixed; inset: 0; z-index: 40; }
|
|
277
|
+
.platform.sidebar-collapsed .side-brand-scrim { display: none; }
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
- [ ] **Step 3: Re-anchor `.sub-account-picker-panel` under the head**
|
|
281
|
+
|
|
282
|
+
The brand head is now the only consumer. In `.sub-account-picker-panel`, remove `left: 50%;` and `transform: translateX(-50%);`, remove the `min-width` (if present), and set `left: 0; right: 0; margin-top: 8px; z-index: 41;` so the panel spans the head's inner width and sits above the scrim.
|
|
283
|
+
|
|
284
|
+
- [ ] **Step 4: Delete the dead footer overrides**
|
|
285
|
+
|
|
286
|
+
Delete the `.side-foot-menu .sub-account-picker-panel { … }` and `.side-foot-menu .sub-account-picker-list { … }` rules and the comment above them that describes the footer kebab anchoring.
|
|
287
|
+
|
|
288
|
+
- [ ] **Step 5: Run the class-name CSS gate + the switcher suites**
|
|
289
|
+
|
|
290
|
+
Run: `npm run -s check:classname-css 2>/dev/null || node scripts/check-classname-css.mjs`
|
|
291
|
+
Then: `npx vitest run app/__tests__/Sidebar-subaccount-switcher.test.tsx`
|
|
292
|
+
Expected: gate clean; tests PASS.
|
|
293
|
+
|
|
294
|
+
- [ ] **Step 6: Commit**
|
|
295
|
+
|
|
296
|
+
```bash
|
|
297
|
+
git add maxy-code/platform/ui/app/globals.css
|
|
298
|
+
git commit -m "style(1942): anchor sub-account picker under the sidebar brand head"
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
---
|
|
302
|
+
|
|
303
|
+
### Task 4: Docs
|
|
304
|
+
|
|
305
|
+
**Files:**
|
|
306
|
+
- Modify: `platform/plugins/docs/references/admin-ui.md`
|
|
307
|
+
|
|
308
|
+
- [ ] **Step 1: Update the switcher location**
|
|
309
|
+
|
|
310
|
+
Find the passage describing the sub-account switcher opening from the footer kebab / user-footer AccountMenu. Rewrite it to state the switcher opens by clicking the sidebar brand head (the account name), with the picker dropping beneath it; note the footer kebab no longer carries it. Keep it to the existing register; no task-id string in the shipped doc.
|
|
311
|
+
|
|
312
|
+
- [ ] **Step 2: Commit**
|
|
313
|
+
|
|
314
|
+
```bash
|
|
315
|
+
git add maxy-code/platform/plugins/docs/references/admin-ui.md
|
|
316
|
+
git commit -m "docs(1942): sub-account switcher now opens from the sidebar brand head"
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
## Self-Review
|
|
320
|
+
|
|
321
|
+
- **Spec coverage:** brand-head trigger (T2), whole-head + chevron + scrim decisions (T2/T3), AccountMenu strip (T1), CSS re-anchor + dead-override delete (T3), observability toggle log (T2 Step 3), docs (T4), test updates incl. the footer-test fix (T2 Step 5). All spec items mapped.
|
|
322
|
+
- **Placeholder scan:** none; every code step shows code.
|
|
323
|
+
- **Type consistency:** `SubAccountPicker` prop names (`open`, `accounts`, `activeAccountId`, `switching`, `onSelect`, `onClose`) match its component signature; Sidebar prop names match the existing signature.
|
|
324
|
+
|
|
325
|
+
## Known follow-ups
|
|
326
|
+
|
|
327
|
+
None at plan time. Any code-review finding out of scope is filed as a new `.tasks/` file per the sprint rules.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Task 1942 — Sub-account switcher moves to the sidebar brand head
|
|
2
|
+
|
|
3
|
+
Design doc. Source task: `.tasks/1942-subaccount-switcher-moves-to-sidebar-brand-head.md`.
|
|
4
|
+
|
|
5
|
+
## Goal
|
|
6
|
+
|
|
7
|
+
Open the sub-account picker by clicking the account name at the top of the sidebar, not from the footer kebab. Remove the switcher from the footer menu.
|
|
8
|
+
|
|
9
|
+
## Resolved decisions (brainstorm)
|
|
10
|
+
|
|
11
|
+
1. **Click target:** the whole brand head (`side-brand`: icon + two-line lockup) becomes one button when the switcher is available.
|
|
12
|
+
2. **Affordance:** a small down-chevron renders after the business name to signal the dropdown.
|
|
13
|
+
3. **Close:** re-clicking the head toggles it shut, and a transparent full-screen scrim behind the panel closes it on any outside click. Mirrors the footer-menu scrim it replaces.
|
|
14
|
+
|
|
15
|
+
## Availability gate (unchanged from today)
|
|
16
|
+
|
|
17
|
+
Switcher is available only when `subAccounts.length > 1 && Boolean(onSwitchAccount)`. Unavailable → the head stays a plain non-interactive `<div>`, no chevron, no picker. This is the current `switcherAvailable` gate, moved from `AccountMenu` to `Sidebar`.
|
|
18
|
+
|
|
19
|
+
## Components and flow
|
|
20
|
+
|
|
21
|
+
- **`Sidebar.tsx`** owns `switcherOpen` state. The brand head renders as `<button aria-haspopup="menu" aria-expanded={switcherOpen}>` when available, else the current `<div>`. Click toggles `switcherOpen`; the closed→open transition fires `refreshAccounts?.()` and emits `[admin-ui] op=subaccount-switcher-toggle anchor=brand-head open=<bool> available=<bool> accounts=<N>`. `SubAccountPicker` renders inside `.side-brand`, anchored beneath it, with a sibling scrim that closes on click.
|
|
22
|
+
- **`AccountMenu.tsx`** loses the `switcherAvailable`/`switcherOpen` block, the `SubAccountPicker` import, and the five switcher props. `UserRound` import dropped if otherwise unused.
|
|
23
|
+
- **`globals.css`** gives `.side-brand` `position: relative` and re-anchors `.sub-account-picker-panel` left-aligned under the head (drop the retired header-title `translateX(-50%)` centering). Adds `.side-brand` button-reset styling (transparent, inherit, pointer) and the chevron. Deletes the dead `.side-foot-menu .sub-account-picker-panel` / `-list` overrides.
|
|
24
|
+
|
|
25
|
+
## Collapsed rail (edge, in scope to not break)
|
|
26
|
+
|
|
27
|
+
When `sidebar-collapsed`, existing CSS hides `.side-brand-lockup` (name + chevron), leaving only the icon. The head-button still functions on the icon, but no new collapsed affordance is added (per task out-of-scope). The picker anchors under `.side-brand` in both states.
|
|
28
|
+
|
|
29
|
+
## Testing
|
|
30
|
+
|
|
31
|
+
vitest + jsdom (Node 22). New `Sidebar` test: available → head is a button, click mounts the picker and fires `refreshAccounts`, re-click closes; unavailable → not a button, no picker. `AccountMenu.test.tsx`: no "Switch account", switcher props dropped. `SubAccountPicker.test.tsx` unchanged.
|
|
32
|
+
|
|
33
|
+
## Out of scope
|
|
34
|
+
|
|
35
|
+
`SubAccountPicker` internals, the switch mechanism, other `AccountMenu` items, a dedicated collapsed-rail affordance, maxy-lite, Task 1940's mobile lite tab bar.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: platform-architecture
|
|
3
3
|
description: Use when grounding any documented-surface claim about what Maxy ships — plugins, skills, specialists, install/deploy flows, internals. This is the install catalogue, not evidence of what is enabled on the current account. For install state on this account, call `capabilities-here`; for documented surface, cite the `Source:` URL inline.
|
|
4
|
-
content-hash: sha256:
|
|
4
|
+
content-hash: sha256:99a8e6ca434399104da91bd275d70b2c95bb10b96f5d1b4666053d66de447468
|
|
5
5
|
brand: maxy-code
|
|
6
6
|
product-name: Maxy
|
|
7
7
|
---
|
|
@@ -1905,6 +1905,22 @@ A site capture becomes one of these too: an enquiry becomes a new contact routed
|
|
|
1905
1905
|
|
|
1906
1906
|
Run a sweep a second time and it proposes nothing it already recorded on the first run, because those records are now in memory and it recognises them. A repeat sweep is quiet on purpose; that is the sign your graph is already up to date.
|
|
1907
1907
|
|
|
1908
|
+
## Automatic email→graph sweep (opt-in)
|
|
1909
|
+
|
|
1910
|
+
The sweep above runs only when you ask, and it proposes rather than writes. There is a second, opt-in routine that runs on a schedule and writes on its own: the automatic email→graph sweep, set up through the `email-graph-sweep` skill. On each run it reads the new inbound mail on your mailbox, pulls out the organisations, people, and live jobs it finds, checks each against what Maxy already holds, saves the genuinely new ones, and then sends you a short report of what it read and saved.
|
|
1911
|
+
|
|
1912
|
+
Turning it on needs two things, and Maxy tells you which is missing if either is absent:
|
|
1913
|
+
|
|
1914
|
+
- **A connected mailbox.** The routine reads your mail, so a mail account (a standard IMAP mailbox, or a Microsoft 365 mailbox) must be connected. With no mailbox connected there is nothing to sweep, and Maxy says so instead of switching anything on.
|
|
1915
|
+
- **A place to send the report.** The routine reports over a persistent channel, so you name the WhatsApp number or Telegram chat that receives it, and that destination must already be registered as an admin or account-manager. If it is not registered, Maxy tells you that rather than falling back to a silent run.
|
|
1916
|
+
|
|
1917
|
+
Two things to know before you switch it on:
|
|
1918
|
+
|
|
1919
|
+
- **It messages you on every run.** It is a report you receive each time, not a silent background job. If you want a quiet, always-on tidy-up of the graph instead, that is a different routine.
|
|
1920
|
+
- **It is one-account, deliberate opt-in.** Maxy does not turn this on for you automatically, and it is held back from broad rollout for now while a safety fix for scheduled runs lands. Enable it for an account you are watching, not as a default.
|
|
1921
|
+
|
|
1922
|
+
Turn it off with the same skill: ask to stop the automatic email sweep and Maxy cancels the schedule, so no further runs fire.
|
|
1923
|
+
|
|
1908
1924
|
---
|
|
1909
1925
|
# Slides
|
|
1910
1926
|
Source: https://docs.getmaxy.com/slides.md
|
|
@@ -2123,6 +2139,7 @@ The `outlook` plugin gives the admin agent access to Microsoft 365 / Outlook.com
|
|
|
2123
2139
|
| `outlook-mail-attachment` | List or download any message's attachments — received or draft. With `messageId` alone, LISTS them (`id`, `name`, `contentType`, `size`, `isInline`, and a `kind` of file/item/reference) via `GET /me/messages/{id}/attachments` and `$select` (no bytes). A draft is a message in the same collection, so passing a draftId lists what that draft carries; the returned `name`/`id` is what `outlook-draft-edit`'s `removeAttachments` takes. With `messageId`+`attachmentId`, DOWNLOADS one file attachment: its bytes are written to `{accountDir}/uploads/outlook/<messageHash>/<bytesHash>-<name>` at `0o600` and the saved path is returned, ready for the Read tool, `SendUserFile`, or an `outlook-mail-reply` attachment. Only file attachments download — item/reference kinds, empty payloads, and anything over 25 MB are refused with nothing written. `outlook-mail-list` / `outlook-mail-search` / `outlook-mail-fetch-body` report `hasAttachments` so the agent knows when to call this. Needs `Mail.Read`. |
|
|
2124
2140
|
| `outlook-mail-reply` | Reply in-thread (`createReply` / `createReplyAll` + send). Threads natively via conversationId. `replyAll`, added cc/bcc, and account-scoped file attachments (up to 25 MB per file; files over Graph's ~3 MB inline limit use a chunked upload session). Body is plain text, prepended above the quote. Needs `Mail.ReadWrite` + `Mail.Send`. |
|
|
2125
2141
|
| `outlook-mail-delete` | Move messages to Deleted Items (`POST /me/messages/{id}/move`). Recoverable — never hard-deletes. An id already gone is counted not-moved. Needs `Mail.ReadWrite`. |
|
|
2142
|
+
| `outlook-mail-ingest` | Write operator-reviewed messages into the business graph as `:ConversationArchive {source:'email'}` threads (the analogue of the IMAP `email-ingest`). Input `decisions`: one `{messageId, disposition:'ingest'\|'discard'}` per listed message. Each `ingest` message's full body + envelope is fetched, threads group by Graph `conversationId`, and participants resolve closed-set against Neo4j (`:Person`/`:AdminUser` by `accountId`); an unresolved From/To/Cc address skips its whole thread and is returned to the operator (no `:Person` auto-create). Resolvable threads dispatch through the shared `conversation-archive-ingest.sh --source email`. Human-in-the-loop: nothing lands without an explicit per-message decision. The plugin's only Neo4j coupling. Needs `Mail.Read`. |
|
|
2126
2143
|
| `outlook-mail-otp-extract` | Poll the inbox for a one-time code from a sender (domain). Reads full bodies to extract it; optional subject regex and timeout (default 60 s). |
|
|
2127
2144
|
| `outlook-calendar-list` | Calendar events in next rangeDays days (default 7, max 365). |
|
|
2128
2145
|
| `outlook-calendar-event` | Full detail of a single event by id. |
|
|
@@ -2159,6 +2176,9 @@ All log lines start with `[outlook-mcp]` and write to `server.log`. They are key
|
|
|
2159
2176
|
| Refresh failed | `token-refresh-failed account=<id> reason=<err>` (terminal) |
|
|
2160
2177
|
| Mail list | `mail-list op=resolve account=<id> folderIn=<name> folderResolved=<token> asRawId=<bool> count=<N> elapsedMs=<N>` (on error, `error=<msg>` replaces `count`) |
|
|
2161
2178
|
| Mail search | `mail-search account=<id> query=<trunc-32> count=<N> elapsedMs=<N>` |
|
|
2179
|
+
| Mail ingest (per message) | `mail-ingest op=ingest id=<id> conversationId=<id> disposition=ingest` |
|
|
2180
|
+
| Mail ingest (per thread) | `mail-ingest op=thread-dispatched root=<key> messages=<N> participants=<N>` (or `op=thread-failed root=<key> status=<N> err=<msg>`; `status=null` with a non-`none` `err` is a spawn launch failure, not a script rejection) |
|
|
2181
|
+
| Mail ingest (summary) | `mail-ingest op=summary ingested=<N> discarded=<N> threadsDispatched=<N> threadsSkipped=<N> unresolved=<N>` — one per call; `threadsSkipped` with `unresolved>0` is the closed-set resolution refusing an unknown participant, not a failure |
|
|
2162
2182
|
| Calendar list | `calendar-list account=<id> rangeDays=<N> count=<N> elapsedMs=<N>` |
|
|
2163
2183
|
| Calendar event | `calendar-event account=<id> eventId=<trunc-12> elapsedMs=<N>` |
|
|
2164
2184
|
| Event created | `cal-create-request account=<id> subj=<trunc-64>` then `event-created account=<id> eventId=<trunc-16> status=201`. A non-201 logs `event-create-failed ... status=<N>` and throws. A 201 whose body carried no readable id logs `event-created-id-unknown account=<id> status=201` and returns a created-but-id-unknown result (the event exists; do not retry) rather than a failure. |
|
|
@@ -2218,7 +2238,7 @@ Latency triage: `mail-list count=0 elapsedMs<200` consistent → permissions iss
|
|
|
2218
2238
|
|
|
2219
2239
|
## Out of scope
|
|
2220
2240
|
|
|
2221
|
-
Mail move to arbitrary folders / flag, hard (permanent) delete, removing an attachment from a **sent** message (Graph forbids it),
|
|
2241
|
+
Mail move to arbitrary folders / flag, hard (permanent) delete, removing an attachment from a **sent** message (Graph forbids it), attachment-byte archival into the graph (`outlook-mail-ingest` lands text threads only; the IMAP `:DigitalDocument` archival is a separate concern), contacts write, recurring-series occurrence editing, the `scheduling` Neo4j calendar and any auto-sync with it, OneDrive / Files, push notifications, on-premises Exchange, M365 admin scopes (`User.Read.All`, `AuditLog.Read.All`), public-agent exposure, multi-tenant federation. Mail read, full-body read, inbound attachment list/download, reply, delete (to Deleted Items), and compose — all four outbound paths carrying attachments, with a draft's attachments editable in place (add, remove, replace) — are supported (`outlook-mail-fetch-body`, `outlook-mail-attachment`, `outlook-mail-reply`, `outlook-mail-delete`, `outlook-mail-ingest`, `outlook-mail-otp-extract`, `outlook-mail-send`, `outlook-draft`, `outlook-draft-edit`, `outlook-draft-send`), and the calendar is read plus control. See `platform/plugins/outlook/PLUGIN.md` for the full out-of-scope list.
|
|
2222
2242
|
|
|
2223
2243
|
---
|
|
2224
2244
|
# Google Calendar
|
|
@@ -2990,6 +3010,35 @@ either is a regression.
|
|
|
2990
3010
|
|
|
2991
3011
|
**Public visitor surface.** The public-host root (`GET /`), the `/:slug` agent routes, and the admin-host `/public` / `/public-chat` previews all serve one shell, `public.html` → [`app/public-entry.tsx`](../../../ui/app/public-entry.tsx) → `PublicChat`. `PublicChat` reuses `useSession` purely as the magic-link gatekeeper — it drives `AccessGate`, resolves the agent slug from the path, and handles `?token=` verification; once the grant is satisfied it mounts `ChatSurface variant="public"`, whose transcript reads the visitor-scoped, **delivered-only** stream `GET /api/public-reader/stream` ([`server/routes/public-reader.ts`](../../../ui/server/routes/public-reader.ts)) — the visitor sees the agent's delivered prose, never the tool/tool-result/directive bytes the legacy `/api/chat` SSE render exposed. The human visitor's branding is resolved client-side by `useSession`. For the head only, the server injects per-agent link-preview meta — `<title>`, `og:title`/`og:description`/`og:image`, `theme-color`, and a per-agent favicon, resolved from the same branding cache `useSession` reads — into the served shell, so a link-preview crawler (which never runs the client bundle) gets a branded card per agent instead of one generic shell; an agent with no branding cache gets the clean brand-default shell with no empty meta tags. There is exactly one public client surface and it is 1:1 visitor↔agent: the earlier `?surface=next` A/B handle is retired, group messaging is retired (not a supported product surface), and a former `/g/<slug>` group URL now serves the same 1:1 shell — a stale bookmark gets the 1:1 chat, never a broken render.
|
|
2992
3012
|
|
|
3013
|
+
### Routines
|
|
3014
|
+
|
|
3015
|
+
The Routines surface (`/routines`) is the admin view of the account's
|
|
3016
|
+
automation `:Event` nodes — the device's scheduled agent turns and tool
|
|
3017
|
+
dispatches, which the Calendar surface deliberately excludes (`calendar.ts`
|
|
3018
|
+
unions only appointment events, `actionPlugin IS NULL`). It renders one card per
|
|
3019
|
+
routine (title, human-readable schedule, next run, channel/destination or tool,
|
|
3020
|
+
status); a card opens a detail modal modeled on the calendar `EventDetailModal`.
|
|
3021
|
+
The modal edits the prose instruction (`agentPrompt`, agent-dispatch routines
|
|
3022
|
+
only) and the schedule via a friendly builder (frequency daily/weekly/monthly,
|
|
3023
|
+
day-of-week, day-of-month, time) that falls back to a raw-cron field for patterns
|
|
3024
|
+
it cannot represent.
|
|
3025
|
+
|
|
3026
|
+
The route (`server/routes/admin/routines.ts`) is account-scoped like `calendar.ts`
|
|
3027
|
+
(a foreign-account or non-automation `eventId` 404s):
|
|
3028
|
+
|
|
3029
|
+
| Route | Behaviour |
|
|
3030
|
+
|-------|-----------|
|
|
3031
|
+
| `GET /api/admin/routines` | Lists automation Events: `recurrence IS NOT NULL OR agentChannel IS NOT NULL OR actionPlugin IS NOT NULL`. Keyed by the `eventId` property. |
|
|
3032
|
+
| `GET /api/admin/routines/:eventId` | One routine, same automation+account guard. |
|
|
3033
|
+
| `PATCH /api/admin/routines/:eventId` | Edits `agentPrompt` and/or `recurrence`. Cron validation and the timezone-aware `nextRun` recompute copy the scheduling plugin's `schedule-update` tool. An invalid cron is rejected 400 with no write. Only creation and deletion stay agent-only (`schedule-event`/`schedule-cancel`). |
|
|
3034
|
+
|
|
3035
|
+
Observability rides `[admin:routines]`: `op=list accountId=<8> count=<n>`,
|
|
3036
|
+
`op=get id=<8> found=<bool>`, and `op=patch id=<8> fields=<…> outcome=ok|rejected
|
|
3037
|
+
reason=<invalid-cron|not-found> nextRun=<…> cron=<serialized>`. A `count=0` while
|
|
3038
|
+
routines exist for the account is the account-scope-miss signature; the logged
|
|
3039
|
+
`cron=` on `outcome=ok` confirms the persisted expression without opening the
|
|
3040
|
+
graph.
|
|
3041
|
+
|
|
2993
3042
|
### Graph
|
|
2994
3043
|
|
|
2995
3044
|
| Mount | Purpose |
|
|
@@ -3298,12 +3347,13 @@ authoritative. This section names the surfaces and what backs each.
|
|
|
3298
3347
|
> owns all navigation; the full-width top header bar, the burger flyout, and the
|
|
3299
3348
|
> "Powered by Claude Code" footer strip are retired. After the redesign:
|
|
3300
3349
|
>
|
|
3301
|
-
> - **Nav rows** are Dashboard, Chat, Tasks, Calendar,
|
|
3302
|
-
>
|
|
3303
|
-
>
|
|
3304
|
-
>
|
|
3305
|
-
>
|
|
3306
|
-
>
|
|
3350
|
+
> - **Nav rows** are Dashboard, Chat, Tasks, Calendar, Routines (the automation
|
|
3351
|
+
> `:Event` nodes at `/routines`, `Zap` icon), Data, the brand container
|
|
3352
|
+
> row (Projects / Properties / Jobs, `Folder` icon, opening the graph filtered
|
|
3353
|
+
> to the brand's primary container), Graph (the unfiltered whole graph at
|
|
3354
|
+
> `/graph`, waypoints icon — active on plain `/graph`; the container row is
|
|
3355
|
+
> active only when a `label=` filter is present), Activity, and Browser. The
|
|
3356
|
+
> active row carries an accent left indicator. The older People / Agents / Artefacts entries in the
|
|
3307
3357
|
> table below are pre-redesign.
|
|
3308
3358
|
> - **Dashboard home** renders on `/` (a greeting, stat cards, a Live-now column,
|
|
3309
3359
|
> a Recent artefacts card, and a Channels column), consuming existing endpoints
|
|
@@ -3312,10 +3362,20 @@ authoritative. This section names the surfaces and what backs each.
|
|
|
3312
3362
|
> the sidebar collapse toggle, the page title, and a Claude status pill on the
|
|
3313
3363
|
> right (Connected / Connect, driven by `/api/health`). The pill replaces the
|
|
3314
3364
|
> retired burger Disconnect visibility and footer strip.
|
|
3315
|
-
> - **Account kebab.** The version / Connect-Disconnect / Change PIN /
|
|
3316
|
-
>
|
|
3317
|
-
>
|
|
3318
|
-
>
|
|
3365
|
+
> - **Account kebab.** The version / Connect-Disconnect / Change PIN / Log out
|
|
3366
|
+
> items moved from the burger onto a kebab on the sidebar user footer
|
|
3367
|
+
> (`AccountMenu`, extracted from the old `HeaderMenu`; the operator variant
|
|
3368
|
+
> still mounts `AccountMenu` for its reduced set).
|
|
3369
|
+
> - **Sub-account switcher on the brand head.** The head at the top of the sidebar
|
|
3370
|
+
> splits into two controls when the install has more than one sub-account: the
|
|
3371
|
+
> brand icon jumps straight to the house account, and the name plus chevron
|
|
3372
|
+
> opens the `SubAccountPicker` directly beneath the head (an outside click or a
|
|
3373
|
+
> second click on the name closes it). Clicking the icon while already on the
|
|
3374
|
+
> house account does nothing. A single-account install shows the same head as a
|
|
3375
|
+
> plain, non-interactive lockup, and the collapsed rail hides both controls. The
|
|
3376
|
+
> switcher used to sit in the account kebab; it no longer does. The switch
|
|
3377
|
+
> navigation itself (`useSubAccountSwitcher`, dropping the `?session=` param) is
|
|
3378
|
+
> unchanged.
|
|
3319
3379
|
> - **Mobile (< 720px).** The sidebar opens as a drawer from a 44x44 header menu
|
|
3320
3380
|
> button, and a fixed bottom tab bar (Home, Chat, Tasks, Data, Calendar) sits
|
|
3321
3381
|
> under the content; Graph, Activity, and Browser stay reachable through the
|
|
@@ -89,6 +89,35 @@ either is a regression.
|
|
|
89
89
|
|
|
90
90
|
**Public visitor surface.** The public-host root (`GET /`), the `/:slug` agent routes, and the admin-host `/public` / `/public-chat` previews all serve one shell, `public.html` → [`app/public-entry.tsx`](../../../ui/app/public-entry.tsx) → `PublicChat`. `PublicChat` reuses `useSession` purely as the magic-link gatekeeper — it drives `AccessGate`, resolves the agent slug from the path, and handles `?token=` verification; once the grant is satisfied it mounts `ChatSurface variant="public"`, whose transcript reads the visitor-scoped, **delivered-only** stream `GET /api/public-reader/stream` ([`server/routes/public-reader.ts`](../../../ui/server/routes/public-reader.ts)) — the visitor sees the agent's delivered prose, never the tool/tool-result/directive bytes the legacy `/api/chat` SSE render exposed. The human visitor's branding is resolved client-side by `useSession`. For the head only, the server injects per-agent link-preview meta — `<title>`, `og:title`/`og:description`/`og:image`, `theme-color`, and a per-agent favicon, resolved from the same branding cache `useSession` reads — into the served shell, so a link-preview crawler (which never runs the client bundle) gets a branded card per agent instead of one generic shell; an agent with no branding cache gets the clean brand-default shell with no empty meta tags. There is exactly one public client surface and it is 1:1 visitor↔agent: the earlier `?surface=next` A/B handle is retired, group messaging is retired (not a supported product surface), and a former `/g/<slug>` group URL now serves the same 1:1 shell — a stale bookmark gets the 1:1 chat, never a broken render.
|
|
91
91
|
|
|
92
|
+
### Routines
|
|
93
|
+
|
|
94
|
+
The Routines surface (`/routines`) is the admin view of the account's
|
|
95
|
+
automation `:Event` nodes — the device's scheduled agent turns and tool
|
|
96
|
+
dispatches, which the Calendar surface deliberately excludes (`calendar.ts`
|
|
97
|
+
unions only appointment events, `actionPlugin IS NULL`). It renders one card per
|
|
98
|
+
routine (title, human-readable schedule, next run, channel/destination or tool,
|
|
99
|
+
status); a card opens a detail modal modeled on the calendar `EventDetailModal`.
|
|
100
|
+
The modal edits the prose instruction (`agentPrompt`, agent-dispatch routines
|
|
101
|
+
only) and the schedule via a friendly builder (frequency daily/weekly/monthly,
|
|
102
|
+
day-of-week, day-of-month, time) that falls back to a raw-cron field for patterns
|
|
103
|
+
it cannot represent.
|
|
104
|
+
|
|
105
|
+
The route (`server/routes/admin/routines.ts`) is account-scoped like `calendar.ts`
|
|
106
|
+
(a foreign-account or non-automation `eventId` 404s):
|
|
107
|
+
|
|
108
|
+
| Route | Behaviour |
|
|
109
|
+
|-------|-----------|
|
|
110
|
+
| `GET /api/admin/routines` | Lists automation Events: `recurrence IS NOT NULL OR agentChannel IS NOT NULL OR actionPlugin IS NOT NULL`. Keyed by the `eventId` property. |
|
|
111
|
+
| `GET /api/admin/routines/:eventId` | One routine, same automation+account guard. |
|
|
112
|
+
| `PATCH /api/admin/routines/:eventId` | Edits `agentPrompt` and/or `recurrence`. Cron validation and the timezone-aware `nextRun` recompute copy the scheduling plugin's `schedule-update` tool. An invalid cron is rejected 400 with no write. Only creation and deletion stay agent-only (`schedule-event`/`schedule-cancel`). |
|
|
113
|
+
|
|
114
|
+
Observability rides `[admin:routines]`: `op=list accountId=<8> count=<n>`,
|
|
115
|
+
`op=get id=<8> found=<bool>`, and `op=patch id=<8> fields=<…> outcome=ok|rejected
|
|
116
|
+
reason=<invalid-cron|not-found> nextRun=<…> cron=<serialized>`. A `count=0` while
|
|
117
|
+
routines exist for the account is the account-scope-miss signature; the logged
|
|
118
|
+
`cron=` on `outcome=ok` confirms the persisted expression without opening the
|
|
119
|
+
graph.
|
|
120
|
+
|
|
92
121
|
### Graph
|
|
93
122
|
|
|
94
123
|
| Mount | Purpose |
|
|
@@ -397,12 +426,13 @@ authoritative. This section names the surfaces and what backs each.
|
|
|
397
426
|
> owns all navigation; the full-width top header bar, the burger flyout, and the
|
|
398
427
|
> "Powered by Claude Code" footer strip are retired. After the redesign:
|
|
399
428
|
>
|
|
400
|
-
> - **Nav rows** are Dashboard, Chat, Tasks, Calendar,
|
|
401
|
-
>
|
|
402
|
-
>
|
|
403
|
-
>
|
|
404
|
-
>
|
|
405
|
-
>
|
|
429
|
+
> - **Nav rows** are Dashboard, Chat, Tasks, Calendar, Routines (the automation
|
|
430
|
+
> `:Event` nodes at `/routines`, `Zap` icon), Data, the brand container
|
|
431
|
+
> row (Projects / Properties / Jobs, `Folder` icon, opening the graph filtered
|
|
432
|
+
> to the brand's primary container), Graph (the unfiltered whole graph at
|
|
433
|
+
> `/graph`, waypoints icon — active on plain `/graph`; the container row is
|
|
434
|
+
> active only when a `label=` filter is present), Activity, and Browser. The
|
|
435
|
+
> active row carries an accent left indicator. The older People / Agents / Artefacts entries in the
|
|
406
436
|
> table below are pre-redesign.
|
|
407
437
|
> - **Dashboard home** renders on `/` (a greeting, stat cards, a Live-now column,
|
|
408
438
|
> a Recent artefacts card, and a Channels column), consuming existing endpoints
|
|
@@ -411,10 +441,20 @@ authoritative. This section names the surfaces and what backs each.
|
|
|
411
441
|
> the sidebar collapse toggle, the page title, and a Claude status pill on the
|
|
412
442
|
> right (Connected / Connect, driven by `/api/health`). The pill replaces the
|
|
413
443
|
> retired burger Disconnect visibility and footer strip.
|
|
414
|
-
> - **Account kebab.** The version / Connect-Disconnect / Change PIN /
|
|
415
|
-
>
|
|
416
|
-
>
|
|
417
|
-
>
|
|
444
|
+
> - **Account kebab.** The version / Connect-Disconnect / Change PIN / Log out
|
|
445
|
+
> items moved from the burger onto a kebab on the sidebar user footer
|
|
446
|
+
> (`AccountMenu`, extracted from the old `HeaderMenu`; the operator variant
|
|
447
|
+
> still mounts `AccountMenu` for its reduced set).
|
|
448
|
+
> - **Sub-account switcher on the brand head.** The head at the top of the sidebar
|
|
449
|
+
> splits into two controls when the install has more than one sub-account: the
|
|
450
|
+
> brand icon jumps straight to the house account, and the name plus chevron
|
|
451
|
+
> opens the `SubAccountPicker` directly beneath the head (an outside click or a
|
|
452
|
+
> second click on the name closes it). Clicking the icon while already on the
|
|
453
|
+
> house account does nothing. A single-account install shows the same head as a
|
|
454
|
+
> plain, non-interactive lockup, and the collapsed rail hides both controls. The
|
|
455
|
+
> switcher used to sit in the account kebab; it no longer does. The switch
|
|
456
|
+
> navigation itself (`useSubAccountSwitcher`, dropping the `?session=` param) is
|
|
457
|
+
> unchanged.
|
|
418
458
|
> - **Mobile (< 720px).** The sidebar opens as a drawer from a 44x44 header menu
|
|
419
459
|
> button, and a fixed bottom tab bar (Home, Chat, Tasks, Data, Calendar) sits
|
|
420
460
|
> under the content; Graph, Activity, and Browser stay reachable through the
|