shadow-claw 1.40.1 → 1.41.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.well-known/mcp/server-card.json +1 -1
- package/.well-known/mcp.json +1 -1
- package/README.md +2 -1
- package/dist/cli/build/build.js +1 -1
- package/dist/cli/{chunk-Bfz2NYgD.js → chunk-0IeC97mU.js} +7 -7
- package/dist/cli/chunk-3oLxlxPn.js +1 -0
- package/dist/cli/chunk-63s8NnIS.js +93 -0
- package/dist/cli/{chunk-BXg4-9Rq2.js → chunk-8CrZyP1b2.js} +1 -1
- package/dist/cli/{chunk-BP5Hg6CM.js → chunk-B--ueArB.js} +1 -1
- package/dist/cli/chunk-BATVgkFY.js +17 -0
- package/dist/cli/chunk-BIh7rXlJ2.js +1 -0
- package/dist/cli/{chunk-Drr0VSGA2.js → chunk-BKnTOLLS2.js} +1 -1
- package/dist/cli/{chunk-C5PuRV08.js → chunk-BLMC1Bxv.js} +1 -1
- package/dist/cli/{chunk-B2tNSlFy2.js → chunk-BOigzz2U2.js} +1 -1
- package/dist/cli/{chunk-DOf3DZdR.js → chunk-BOzn335y.js} +1 -1
- package/dist/cli/chunk-BjOeVLb42.js +1 -0
- package/dist/cli/{chunk-DqTx0rRp2.js → chunk-Bjqh5_SU2.js} +1 -1
- package/dist/cli/chunk-BqWBd_jF2.js +1 -0
- package/dist/cli/{chunk-DIbSoQmd2.js → chunk-C6FFByBU2.js} +2 -2
- package/dist/cli/{chunk-Drq18VZe.js → chunk-C9-aL3rF.js} +1 -1
- package/dist/cli/{chunk-BUhqHeEA2.js → chunk-C9czddEh2.js} +2 -2
- package/dist/cli/chunk-CCx8WOwz2.js +1 -0
- package/dist/cli/{chunk-BkMSjVJb.js → chunk-CK6ejMig.js} +1 -1
- package/dist/cli/chunk-CUUERioR2.js +1 -0
- package/dist/cli/chunk-CXAobnJd.js +1 -0
- package/dist/cli/chunk-CY1X0pOF2.js +1 -0
- package/dist/cli/chunk-CZvvgLVv.js +1 -0
- package/dist/cli/chunk-CbcwbfJB.js +1 -0
- package/dist/cli/{chunk-quilB3TV2.js → chunk-Ci9OJGcd2.js} +1 -1
- package/dist/cli/chunk-Cj9wl0qx.js +1 -0
- package/dist/cli/{chunk-3gRhOmGo2.js → chunk-CjJsVrKI2.js} +1 -1
- package/dist/cli/{chunk-CmEfzm-_.js → chunk-ClPZ4Rea.js} +1 -1
- package/dist/cli/chunk-Cmdxoq9q.js +13 -0
- package/dist/cli/{chunk-WMPAfIvV2.js → chunk-DHJCGRuG2.js} +1 -1
- package/dist/cli/{chunk-C7Y2xIk4.js → chunk-DKVkYTV9.js} +1 -1
- package/dist/cli/chunk-DR2XtWRo.js +1 -0
- package/dist/cli/{chunk-BiVJVh_S.js → chunk-DUlIqkaX.js} +7 -7
- package/dist/cli/{chunk-CkpueGaZ2.js → chunk-DjWle8FF2.js} +1 -1
- package/dist/cli/{chunk-CSvHmNOk.js → chunk-DmRXVLfy.js} +1 -1
- package/dist/cli/{chunk-BT1xXZn72.js → chunk-HlSiTC-u2.js} +1 -1
- package/dist/cli/{chunk-da0bsiOn.js → chunk-P4JSeFBI.js} +1 -1
- package/dist/cli/chunk-UcNbHaRQ.js +1 -0
- package/dist/cli/{chunk-BXFHFbEd.js → chunk-UweLmfQS.js} +1 -1
- package/dist/cli/{chunk-CGiXWHTi2.js → chunk-gwpxl-0W2.js} +1 -1
- package/dist/cli/{chunk-NH8koEis2.js → chunk-nmDhxXH_2.js} +1 -1
- package/dist/cli/chunk-p0n_c-I2.js +1 -0
- package/dist/cli/{chunk-D7xP72La.js → chunk-q7r4FosZ.js} +1 -1
- package/dist/cli/cli.js +1 -1
- package/dist/cli/prerender/dsd-shell/prerender-dsd-shell.js +1 -1
- package/dist/electron/main.cjs +1 -1
- package/dist/headless-agent.js +1 -1
- package/dist/lib/{app-routes-BAEMLu9g.js → app-routes-CKkiX12g.js} +7 -2
- package/dist/lib/app-routes-CKkiX12g.js.map +1 -0
- package/dist/lib/components/index.js +26 -26
- package/dist/lib/components/settings/shadow-claw-accounts.js +1 -1
- package/dist/lib/components/settings/shadow-claw-channel-config.js +1 -1
- package/dist/lib/components/settings/shadow-claw-control-plane.js +1 -1
- package/dist/lib/components/settings/shadow-claw-git.js +1 -1
- package/dist/lib/components/settings/shadow-claw-integrations.js +1 -1
- package/dist/lib/components/settings/shadow-claw-llm.js +1 -1
- package/dist/lib/components/settings/shadow-claw-mcp-remote.js +1 -1
- package/dist/lib/components/settings/shadow-claw-networking.js +1 -1
- package/dist/lib/components/settings/shadow-claw-notifications.js +1 -1
- package/dist/lib/components/settings/shadow-claw-peerjs.js +1 -1
- package/dist/lib/components/settings/shadow-claw-storage.js +1 -1
- package/dist/lib/components/settings/shadow-claw-task-server.js +1 -1
- package/dist/lib/components/settings/shadow-claw-webvm.js +1 -1
- package/dist/lib/components/shadow-claw/utils/syncSlottedSidebarActiveLinks.d.ts +10 -0
- package/dist/lib/components/shadow-claw-a2ui.js +1 -1
- package/dist/lib/components/shadow-claw-accounts.js +1 -1
- package/dist/lib/components/shadow-claw-channel-config.js +1 -1
- package/dist/lib/components/shadow-claw-channels.js +1 -1
- package/dist/lib/components/shadow-claw-chat.js +1 -1
- package/dist/lib/components/shadow-claw-control-plane.js +1 -1
- package/dist/lib/components/shadow-claw-conversations.js +1 -1
- package/dist/lib/components/shadow-claw-file-viewer.js +1 -1
- package/dist/lib/components/shadow-claw-files.js +1 -1
- package/dist/lib/components/shadow-claw-git.js +1 -1
- package/dist/lib/components/shadow-claw-integrations.js +1 -1
- package/dist/lib/components/shadow-claw-llm.js +1 -1
- package/dist/lib/components/shadow-claw-mcp-remote.js +1 -1
- package/dist/lib/components/shadow-claw-networking.js +1 -1
- package/dist/lib/components/shadow-claw-notifications.js +1 -1
- package/dist/lib/components/shadow-claw-page-header.js +1 -1
- package/dist/lib/components/shadow-claw-pages.js +1 -1
- package/dist/lib/components/shadow-claw-peerjs.js +1 -1
- package/dist/lib/components/shadow-claw-settings.js +1 -1
- package/dist/lib/components/shadow-claw-storage.js +1 -1
- package/dist/lib/components/shadow-claw-task-server.js +1 -1
- package/dist/lib/components/shadow-claw-tasks.js +1 -1
- package/dist/lib/components/shadow-claw-terminal.js +1 -1
- package/dist/lib/components/shadow-claw-tools.js +1 -1
- package/dist/lib/components/shadow-claw-webvm.js +1 -1
- package/dist/lib/components/shadow-claw.js +1 -1
- package/dist/lib/{config-CPHDmyTx.js → config-DZkF9LiO.js} +2 -2
- package/dist/lib/{config-CPHDmyTx.js.map → config-DZkF9LiO.js.map} +1 -1
- package/dist/lib/{configurePeerJs-BsJoycQd.js → configurePeerJs-B3IE9BY2.js} +3 -3
- package/dist/lib/{configurePeerJs-BsJoycQd.js.map → configurePeerJs-B3IE9BY2.js.map} +1 -1
- package/dist/lib/{connections-BdIru7bF.js → connections-CalJGWvZ.js} +2 -2
- package/dist/lib/{connections-BdIru7bF.js.map → connections-CalJGWvZ.js.map} +1 -1
- package/dist/lib/{downloadGroupFile-Dn0KsMlV.js → downloadGroupFile-DDE1fIhJ.js} +2 -2
- package/dist/lib/{downloadGroupFile-Dn0KsMlV.js.map → downloadGroupFile-DDE1fIhJ.js.map} +1 -1
- package/dist/lib/{file-viewer-BYrXXu-X.js → file-viewer-Bll2EoGc.js} +5 -5
- package/dist/lib/{file-viewer-BYrXXu-X.js.map → file-viewer-Bll2EoGc.js.map} +1 -1
- package/dist/lib/{getGroupDir-DF-RZO8X.js → getGroupDir-MHv7Lvmp.js} +4 -4
- package/dist/lib/{getGroupDir-DF-RZO8X.js.map → getGroupDir-MHv7Lvmp.js.map} +1 -1
- package/dist/lib/{git-s_0cgKm1.js → git-Bv_M3dgw.js} +3 -3
- package/dist/lib/{git-s_0cgKm1.js.map → git-Bv_M3dgw.js.map} +1 -1
- package/dist/lib/{iframe-sanitizer-DFdda1jJ.js → iframe-sanitizer-C7ZGDjEI.js} +2 -2
- package/dist/lib/{iframe-sanitizer-DFdda1jJ.js.map → iframe-sanitizer-C7ZGDjEI.js.map} +1 -1
- package/dist/lib/{iframe-storage-proxy-qrhZmQyO.js → iframe-storage-proxy-CR8ZYHPd.js} +2 -2
- package/dist/lib/{iframe-storage-proxy-qrhZmQyO.js.map → iframe-storage-proxy-CR8ZYHPd.js.map} +1 -1
- package/dist/lib/index.js +27 -27
- package/dist/lib/{initChatSplitResize-DuNlwCuI.js → initChatSplitResize-DiKTI-wk.js} +2 -2
- package/dist/lib/{initChatSplitResize-DuNlwCuI.js.map → initChatSplitResize-DiKTI-wk.js.map} +1 -1
- package/dist/lib/{initControlPlane-D4UZ4EhV.js → initControlPlane-CzjtQ4aM.js} +15 -15
- package/dist/lib/{initControlPlane-D4UZ4EhV.js.map → initControlPlane-CzjtQ4aM.js.map} +1 -1
- package/dist/lib/{markdown-CRRwbN9h.js → markdown-k8wA9LKy.js} +4 -4
- package/dist/lib/{markdown-CRRwbN9h.js.map → markdown-k8wA9LKy.js.map} +1 -1
- package/dist/lib/{mcp-reconnect-COdZ-cea.js → mcp-reconnect-CGIdfZnS.js} +2 -2
- package/dist/lib/{mcp-reconnect-COdZ-cea.js.map → mcp-reconnect-CGIdfZnS.js.map} +1 -1
- package/dist/lib/{memoryStorage-BsMe2FOQ.js → memoryStorage-DL-Bo3dh.js} +2 -2
- package/dist/lib/{memoryStorage-BsMe2FOQ.js.map → memoryStorage-DL-Bo3dh.js.map} +1 -1
- package/dist/lib/{orchestrator-D9f5fDCb.js → orchestrator-DKHCoikY.js} +11 -11
- package/dist/lib/{orchestrator-D9f5fDCb.js.map → orchestrator-DKHCoikY.js.map} +1 -1
- package/dist/lib/{orchestrator-CMN43L0F.js → orchestrator-Dn1ZOz9B.js} +12 -12
- package/dist/lib/{orchestrator-CMN43L0F.js.map → orchestrator-Dn1ZOz9B.js.map} +1 -1
- package/dist/lib/{peerjs-6u8KtenE.js → peerjs-BK41iBvP.js} +7 -7
- package/dist/lib/{peerjs-6u8KtenE.js.map → peerjs-BK41iBvP.js.map} +1 -1
- package/dist/lib/{push-client-DCnB2-OU.js → push-client-DKgVFD2Z.js} +3 -3
- package/dist/lib/{push-client-DCnB2-OU.js.map → push-client-DKgVFD2Z.js.map} +1 -1
- package/dist/lib/{readGroupFileBytes-nKNJ6sbl.js → readGroupFileBytes-CgU_UfGv.js} +2 -2
- package/dist/lib/{readGroupFileBytes-nKNJ6sbl.js.map → readGroupFileBytes-CgU_UfGv.js.map} +1 -1
- package/dist/lib/{shadow-claw-4x3LqlII.js → shadow-claw-CtCj5Rn_.js} +121 -16
- package/dist/lib/shadow-claw-CtCj5Rn_.js.map +1 -0
- package/dist/lib/{shadow-claw-a2ui-Dipe8nY2.js → shadow-claw-a2ui-CvK5w940.js} +2 -2
- package/dist/lib/{shadow-claw-a2ui-Dipe8nY2.js.map → shadow-claw-a2ui-CvK5w940.js.map} +1 -1
- package/dist/lib/{shadow-claw-accounts-BgCkz3Zv.js → shadow-claw-accounts-Br3XdHro.js} +3 -3
- package/dist/lib/{shadow-claw-accounts-BgCkz3Zv.js.map → shadow-claw-accounts-Br3XdHro.js.map} +1 -1
- package/dist/lib/{shadow-claw-channel-config-CIm_Bxd2.js → shadow-claw-channel-config-Da2dN19M.js} +3 -3
- package/dist/lib/{shadow-claw-channel-config-CIm_Bxd2.js.map → shadow-claw-channel-config-Da2dN19M.js.map} +1 -1
- package/dist/lib/{shadow-claw-channels-CaceK52s.js → shadow-claw-channels-BofYHgz8.js} +3 -3
- package/dist/lib/{shadow-claw-channels-CaceK52s.js.map → shadow-claw-channels-BofYHgz8.js.map} +1 -1
- package/dist/lib/{shadow-claw-chat-BD18-mAR.js → shadow-claw-chat-Dw32DPV2.js} +12 -12
- package/dist/lib/{shadow-claw-chat-BD18-mAR.js.map → shadow-claw-chat-Dw32DPV2.js.map} +1 -1
- package/dist/lib/{shadow-claw-control-plane-U6LsfRPm.js → shadow-claw-control-plane-CwFotFDh.js} +7 -7
- package/dist/lib/{shadow-claw-control-plane-U6LsfRPm.js.map → shadow-claw-control-plane-CwFotFDh.js.map} +1 -1
- package/dist/lib/{shadow-claw-conversations-CDsza_oB.js → shadow-claw-conversations-0UXge23k.js} +5 -5
- package/dist/lib/{shadow-claw-conversations-CDsza_oB.js.map → shadow-claw-conversations-0UXge23k.js.map} +1 -1
- package/dist/lib/{shadow-claw-file-viewer-CuDhkVcL.js → shadow-claw-file-viewer-CAsn9NxH.js} +10 -10
- package/dist/lib/{shadow-claw-file-viewer-CuDhkVcL.js.map → shadow-claw-file-viewer-CAsn9NxH.js.map} +1 -1
- package/dist/lib/{shadow-claw-files-BI0P3m8a.js → shadow-claw-files-xGGhsHNH.js} +9 -9
- package/dist/lib/{shadow-claw-files-BI0P3m8a.js.map → shadow-claw-files-xGGhsHNH.js.map} +1 -1
- package/dist/lib/{shadow-claw-git-xRApLEMN.js → shadow-claw-git-B606hcMU.js} +6 -6
- package/dist/lib/{shadow-claw-git-xRApLEMN.js.map → shadow-claw-git-B606hcMU.js.map} +1 -1
- package/dist/lib/{shadow-claw-integrations-B2e1Nfg1.js → shadow-claw-integrations-Bwq5cba1.js} +4 -4
- package/dist/lib/{shadow-claw-integrations-B2e1Nfg1.js.map → shadow-claw-integrations-Bwq5cba1.js.map} +1 -1
- package/dist/lib/{shadow-claw-llm-Bc2mWuGb.js → shadow-claw-llm-BgpcMBIy.js} +6 -6
- package/dist/lib/{shadow-claw-llm-Bc2mWuGb.js.map → shadow-claw-llm-BgpcMBIy.js.map} +1 -1
- package/dist/lib/{shadow-claw-mcp-remote-D_Bh3aeC.js → shadow-claw-mcp-remote-DvQCA-Bk.js} +4 -4
- package/dist/lib/{shadow-claw-mcp-remote-D_Bh3aeC.js.map → shadow-claw-mcp-remote-DvQCA-Bk.js.map} +1 -1
- package/dist/lib/{shadow-claw-networking-F5W3n5ci.js → shadow-claw-networking-Cxvf9c7E.js} +2 -2
- package/dist/lib/{shadow-claw-networking-F5W3n5ci.js.map → shadow-claw-networking-Cxvf9c7E.js.map} +1 -1
- package/dist/lib/{shadow-claw-notifications-lvMt9WJ1.js → shadow-claw-notifications-CGVcFYBy.js} +3 -3
- package/dist/lib/{shadow-claw-notifications-lvMt9WJ1.js.map → shadow-claw-notifications-CGVcFYBy.js.map} +1 -1
- package/dist/lib/{shadow-claw-page-header-6gq6EMqX.js → shadow-claw-page-header-DAYz3I_o.js} +2 -2
- package/dist/lib/{shadow-claw-page-header-6gq6EMqX.js.map → shadow-claw-page-header-DAYz3I_o.js.map} +1 -1
- package/dist/lib/{shadow-claw-pages-Bo8_z2S-.js → shadow-claw-pages-BLeEZb9k.js} +24 -12
- package/dist/lib/shadow-claw-pages-BLeEZb9k.js.map +1 -0
- package/dist/lib/{shadow-claw-peerjs-DRTLCVDQ.js → shadow-claw-peerjs-BcFnbK74.js} +3 -3
- package/dist/lib/{shadow-claw-peerjs-DRTLCVDQ.js.map → shadow-claw-peerjs-BcFnbK74.js.map} +1 -1
- package/dist/lib/{shadow-claw-settings-D00B4p4L.js → shadow-claw-settings-B7o0Z4hu.js} +9 -9
- package/dist/lib/{shadow-claw-settings-D00B4p4L.js.map → shadow-claw-settings-B7o0Z4hu.js.map} +1 -1
- package/dist/lib/{shadow-claw-storage-DgMe7-Zo.js → shadow-claw-storage-afMVqB7E.js} +5 -5
- package/dist/lib/{shadow-claw-storage-DgMe7-Zo.js.map → shadow-claw-storage-afMVqB7E.js.map} +1 -1
- package/dist/lib/{shadow-claw-task-server-D103YOuI.js → shadow-claw-task-server-B9-MQjl6.js} +2 -2
- package/dist/lib/{shadow-claw-task-server-D103YOuI.js.map → shadow-claw-task-server-B9-MQjl6.js.map} +1 -1
- package/dist/lib/{shadow-claw-tasks-6OhALkq1.js → shadow-claw-tasks-Dc4BC-t-.js} +8 -8
- package/dist/lib/{shadow-claw-tasks-6OhALkq1.js.map → shadow-claw-tasks-Dc4BC-t-.js.map} +1 -1
- package/dist/lib/{shadow-claw-terminal-BGtxA1qD.js → shadow-claw-terminal-BINcTdsk.js} +2 -2
- package/dist/lib/{shadow-claw-terminal-BGtxA1qD.js.map → shadow-claw-terminal-BINcTdsk.js.map} +1 -1
- package/dist/lib/{shadow-claw-tools-OeIwAnnd.js → shadow-claw-tools-BjFgXNLR.js} +7 -7
- package/dist/lib/{shadow-claw-tools-OeIwAnnd.js.map → shadow-claw-tools-BjFgXNLR.js.map} +1 -1
- package/dist/lib/{shadow-claw-webvm-gVFZ197u.js → shadow-claw-webvm-Xe3ByMDw.js} +3 -3
- package/dist/lib/{shadow-claw-webvm-gVFZ197u.js.map → shadow-claw-webvm-Xe3ByMDw.js.map} +1 -1
- package/dist/lib/subsystems/mcp/webmcp.d.ts +64 -0
- package/dist/lib/{syncWebMcpRegistration-Bbph0XZl.js → syncWebMcpRegistration-D7alChz0.js} +21 -6
- package/dist/lib/syncWebMcpRegistration-D7alChz0.js.map +1 -0
- package/dist/lib/{tools-D0OlSzMD.js → tools-Cmhpk3X8.js} +4 -4
- package/dist/lib/{tools-D0OlSzMD.js.map → tools-Cmhpk3X8.js.map} +1 -1
- package/dist/lib/utils/index.js +1 -1
- package/dist/lib/utils/namespacedStorage.js +1 -1
- package/dist/lib/utils/utils.js +1 -1
- package/dist/lib/{utils-DYdrO2KI.js → utils-CzgKe_XZ.js} +4 -4
- package/dist/lib/{utils-DYdrO2KI.js.map → utils-CzgKe_XZ.js.map} +1 -1
- package/dist/lib/{webmcp-D2bocsgh.js → webmcp-oEP9YpR2.js} +119 -3
- package/dist/lib/{webmcp-D2bocsgh.js.map → webmcp-oEP9YpR2.js.map} +1 -1
- package/dist/public/.well-known/mcp/server-card.json +1 -1
- package/dist/public/.well-known/mcp.json +1 -1
- package/dist/public/AGENTS.md +8 -2
- package/dist/public/README.md +2 -1
- package/dist/public/agent.worker.js +1 -1
- package/dist/public/app-routes-C6-H2zPH.js +1 -0
- package/dist/public/components/shadow-claw/shadow-claw.css +1 -1
- package/dist/public/components/shadow-claw/shadow-claw.html +1 -1
- package/dist/public/components/shadow-claw-pages/shadow-claw-pages.css +1 -1
- package/dist/public/{config-D4lVqMs8.js → config-Dewnqegf.js} +1 -1
- package/dist/public/{configurePeerJs-DDOAVSaH.js → configurePeerJs-CaPU2sN5.js} +1 -1
- package/dist/public/{connections-CiukjGiH.js → connections-CwE7jE9l.js} +1 -1
- package/dist/public/{custom-element-security-piU2U-Ec.js → custom-element-security-DLgHMf5R.js} +1 -1
- package/dist/public/docs/README.md +1 -0
- package/dist/public/docs/example/article/index.html +4 -4
- package/dist/public/docs/guides/publishing-to-github-pages.md +95 -6
- package/dist/public/docs/publishing/index.html +4 -4
- package/dist/public/docs/skill-creator/index.html +4 -4
- package/dist/public/docs/subsystems/pages.md +10 -3
- package/dist/public/docs/subsystems/theming.md +242 -0
- package/dist/public/docs/subsystems/webmcp.md +89 -1
- package/dist/public/{downloadGroupFile-BiZQYY-O.js → downloadGroupFile-ChWHuap_.js} +1 -1
- package/dist/public/e2e/README.md +3 -2
- package/dist/public/{e2e-bridge-CybB810Q.js → e2e-bridge-BkaldjV2.js} +1 -1
- package/dist/public/{file-viewer-DLUBvOVD.js → file-viewer-Df4qUJCn.js} +1 -1
- package/dist/public/{getGroupDir-CLz5c8cg.js → getGroupDir-CeSr4fzS.js} +1 -1
- package/dist/public/{git-Ba8AeviI.js → git-Bscc1tK-.js} +2 -2
- package/dist/public/{iframe-sanitizer-CKOO7cxp.js → iframe-sanitizer-BVdkQsor.js} +1 -1
- package/dist/public/{iframe-storage-proxy-BvRiccH0.js → iframe-storage-proxy-Cg8aFw7o.js} +1 -1
- package/dist/public/index.css +1 -1
- package/dist/public/index.html +4 -4
- package/dist/public/index.js +1 -1
- package/dist/public/{initChatSplitResize-osJXM2gu.js → initChatSplitResize-C3Rp1LeZ.js} +1 -1
- package/dist/public/{initControlPlane-CNggzsRw.js → initControlPlane-C1pnzG16.js} +1 -1
- package/dist/public/llms.txt +2 -2
- package/dist/public/main/index.html +4 -4
- package/dist/public/main/memory/index.html +4 -4
- package/dist/public/{markdown-CZA-Ye2Q.js → markdown-D3A902nT.js} +1 -1
- package/dist/public/{mcp-reconnect-BK1bYBdC.js → mcp-reconnect-BJWxQBm_.js} +1 -1
- package/dist/public/{memoryStorage-Bew1X8Uy.js → memoryStorage-ykv-Ri-y.js} +1 -1
- package/dist/public/{orchestrator-D6tel2Zp.js → orchestrator-BVnC4Vtt.js} +2 -2
- package/dist/public/{orchestrator-Dc_Q73La.js → orchestrator-CQcc7OFA.js} +2 -2
- package/dist/public/{peerjs-CYs1F7sq.js → peerjs-CYC2cI1Y.js} +1 -1
- package/dist/public/{push-client-_HUyJlFJ.js → push-client-BJrnOULM.js} +1 -1
- package/dist/public/service-worker/fetch-proxy.js +1 -1
- package/dist/public/service-worker.js +1 -1
- package/dist/public/{shadow-claw-Dabb2AEp.js → shadow-claw-WwfOu-tu.js} +33 -3
- package/dist/public/{shadow-claw-accounts-BqGrm47Z.js → shadow-claw-accounts-BFZxrPsH.js} +1 -1
- package/dist/public/{shadow-claw-channels-Ci2UPaaA.js → shadow-claw-channels-C0w0c1EF.js} +1 -1
- package/dist/public/{shadow-claw-chat-BxR6cBYQ.js → shadow-claw-chat-DIJ0A-QN.js} +1 -1
- package/dist/public/{shadow-claw-control-plane-C1A-CZ9H.js → shadow-claw-control-plane-DZoxUd-1.js} +2 -2
- package/dist/public/{shadow-claw-conversations-DcwG05-H.js → shadow-claw-conversations-DMyG_5zj.js} +1 -1
- package/dist/public/{shadow-claw-file-viewer-CDs1QGXk.js → shadow-claw-file-viewer-DYgZjZb9.js} +1 -1
- package/dist/public/{shadow-claw-files-B774Xz1d.js → shadow-claw-files-DIOUdKJr.js} +1 -1
- package/dist/public/{shadow-claw-git-CTIPP0vs.js → shadow-claw-git-if5S0QJr.js} +2 -2
- package/dist/public/{shadow-claw-integrations-CjtkA6l0.js → shadow-claw-integrations-BQt4hcWe.js} +1 -1
- package/dist/public/{shadow-claw-llm-DfmITHde.js → shadow-claw-llm-9uf1KFA0.js} +2 -2
- package/dist/public/{shadow-claw-mcp-remote-BPuV22So.js → shadow-claw-mcp-remote-COpNIzJd.js} +1 -1
- package/dist/public/{shadow-claw-networking-DEMMygy0.js → shadow-claw-networking-AOVRUn6B.js} +1 -1
- package/dist/public/{shadow-claw-notifications-B9Xhcj1P.js → shadow-claw-notifications-CKB-W1TA.js} +1 -1
- package/dist/public/{shadow-claw-page-header-CAXokjQy.js → shadow-claw-page-header-D4LmUigA.js} +1 -1
- package/dist/public/{shadow-claw-pages-BRQw8bTm.js → shadow-claw-pages-BpHRY4vH.js} +12 -3
- package/dist/public/{shadow-claw-settings-BGrDNyqh.js → shadow-claw-settings-CE-aUo_J.js} +3 -3
- package/dist/public/{shadow-claw-storage-DK3pf11w.js → shadow-claw-storage-CFKC-Y3V.js} +1 -1
- package/dist/public/{shadow-claw-task-server-CMp5Vjza.js → shadow-claw-task-server-B_ZjqK4B.js} +1 -1
- package/dist/public/{shadow-claw-tasks-BO8aXM2B.js → shadow-claw-tasks-CGsEv48K.js} +1 -1
- package/dist/public/{shadow-claw-terminal-BSeTuyDa.js → shadow-claw-terminal-DcaS7cPH.js} +1 -1
- package/dist/public/{shadow-claw-tools-Bj6DLZ-2.js → shadow-claw-tools-Cnu9hKy0.js} +1 -1
- package/dist/public/{shadow-claw-webvm-M7d5RH8H.js → shadow-claw-webvm-BB8SFvJ8.js} +1 -1
- package/dist/public/syncWebMcpRegistration-D2Zx4oA1.js +1 -0
- package/dist/public/theme-init.js +1 -1
- package/dist/public/{tools-nysPKnGT.js → tools-sOLCHTj8.js} +1 -1
- package/dist/public/{webmcp-Bo3KTgRb.js → webmcp-YFt6vM1_.js} +1 -1
- package/dist/server.js +188 -188
- package/package.json +1 -1
- package/src/cli/build/build.ts +18 -11
- package/src/cli/prerender/dsd-shell/prerender-dsd-shell.test.ts +4 -1
- package/src/cli/prerender/dsd-shell/prerender-dsd-shell.ts +13 -1
- package/src/cli/prerender/pretty-paths/prerender-pretty-paths.ts +22 -0
- package/src/cli/prerender/utils/insert-before-closing-head.test.ts +34 -1
- package/src/cli/prerender/utils/insert-before-closing-head.ts +20 -6
- package/src/cli/site-config/apply.test.ts +90 -0
- package/src/cli/site-config/apply.ts +189 -22
- package/src/components/shadow-claw/shadow-claw.css +26 -0
- package/src/components/shadow-claw/shadow-claw.html +4 -0
- package/src/components/shadow-claw/utils/applyRoute.ts +3 -0
- package/src/components/shadow-claw/utils/bindEventListeners.test.ts +6 -0
- package/src/components/shadow-claw/utils/bindEventListeners.ts +30 -1
- package/src/components/shadow-claw/utils/getRoute.test.ts +52 -0
- package/src/components/shadow-claw/utils/getRoute.ts +18 -0
- package/src/components/shadow-claw/utils/navigateToRoute.test.ts +21 -0
- package/src/components/shadow-claw/utils/navigateToRoute.ts +10 -0
- package/src/components/shadow-claw/utils/sidebarVisibility.ts +12 -4
- package/src/components/shadow-claw/utils/syncSlottedSidebarActiveLinks.test.ts +93 -0
- package/src/components/shadow-claw/utils/syncSlottedSidebarActiveLinks.ts +100 -0
- package/src/components/shadow-claw-pages/shadow-claw-pages.css +10 -1
- package/src/components/shadow-claw-pages/utils/removePreviewIframe.ts +3 -0
- package/src/core/orchestrator/utils/syncWebMcpRegistration.test.ts +52 -0
- package/src/core/orchestrator/utils/syncWebMcpRegistration.ts +28 -1
- package/src/server/middleware/static-files.test.ts +36 -0
- package/src/server/middleware/static-files.ts +14 -6
- package/src/storage/staticRouting.test.ts +36 -0
- package/src/storage/staticRouting.ts +31 -3
- package/src/subsystems/mcp/webmcp.test.ts +255 -0
- package/src/subsystems/mcp/webmcp.ts +253 -0
- package/dist/cli/chunk-00GAzVPl2.js +0 -1
- package/dist/cli/chunk-2hnFoNNC.js +0 -1
- package/dist/cli/chunk-BKQL4nzF2.js +0 -1
- package/dist/cli/chunk-BQ4K3fqj2.js +0 -1
- package/dist/cli/chunk-BVCGo6uf2.js +0 -1
- package/dist/cli/chunk-Bc9nRNyN.js +0 -11
- package/dist/cli/chunk-BcTH_-FL.js +0 -1
- package/dist/cli/chunk-BmD_sPRt.js +0 -1
- package/dist/cli/chunk-BsxNqqer.js +0 -1
- package/dist/cli/chunk-CCcF5T-c.js +0 -1
- package/dist/cli/chunk-CFhvwUeL.js +0 -91
- package/dist/cli/chunk-CwWtvULn.js +0 -1
- package/dist/cli/chunk-DID5VvOG.js +0 -17
- package/dist/cli/chunk-Drf-hXIR2.js +0 -1
- package/dist/cli/chunk-Dt5HNYVi.js +0 -1
- package/dist/cli/chunk-DwZQi9Ax.js +0 -1
- package/dist/cli/chunk-m9ZX7TyF2.js +0 -1
- package/dist/lib/app-routes-BAEMLu9g.js.map +0 -1
- package/dist/lib/shadow-claw-4x3LqlII.js.map +0 -1
- package/dist/lib/shadow-claw-pages-Bo8_z2S-.js.map +0 -1
- package/dist/lib/syncWebMcpRegistration-Bbph0XZl.js.map +0 -1
- package/dist/public/app-routes-BMQ4HFaM.js +0 -1
- package/dist/public/syncWebMcpRegistration-4bT0YUyZ.js +0 -1
|
@@ -136,8 +136,12 @@
|
|
|
136
136
|
<li class="nav-item" data-page="files">📁 Files</li>
|
|
137
137
|
</ul>
|
|
138
138
|
</nav>
|
|
139
|
+
<slot name="sidebar-nav"></slot>
|
|
140
|
+
<slot name="sidebar"></slot>
|
|
141
|
+
<slot name="sidebar-content"></slot>
|
|
139
142
|
<div class="sidebar-spacer"></div>
|
|
140
143
|
<shadow-claw-conversations></shadow-claw-conversations>
|
|
144
|
+
<slot name="sidebar-footer"></slot>
|
|
141
145
|
<div class="sidebar-footer">
|
|
142
146
|
<button class="settings-btn" data-action="show-settings">
|
|
143
147
|
⚙️ Settings
|
|
@@ -2,6 +2,7 @@ import { DEFAULT_GROUP_ID } from "../../../config/config.js";
|
|
|
2
2
|
|
|
3
3
|
import { applyAnchorWithRetry } from "./applyAnchorWithRetry.js";
|
|
4
4
|
import { showPage } from "./showPage.js";
|
|
5
|
+
import { syncSlottedSidebarActiveLinks } from "./syncSlottedSidebarActiveLinks.js";
|
|
5
6
|
|
|
6
7
|
import type { ShadowClawDatabase } from "../../../db/types.js";
|
|
7
8
|
import type { ShadowClawAppRoute } from "../../../core/app-routes.js";
|
|
@@ -139,4 +140,6 @@ export async function applyRoute(
|
|
|
139
140
|
}
|
|
140
141
|
}
|
|
141
142
|
}
|
|
143
|
+
|
|
144
|
+
syncSlottedSidebarActiveLinks(shadowClaw);
|
|
142
145
|
}
|
|
@@ -817,6 +817,12 @@ describe("bindEventListeners", () => {
|
|
|
817
817
|
oStore,
|
|
818
818
|
new URL("https://example.com/files/main/"),
|
|
819
819
|
);
|
|
820
|
+
|
|
821
|
+
// Verify that popstateListener does not call applyRouteFromCurrentLocation when inFeedScroll is true
|
|
822
|
+
mockApplyRouteFromCurrentLocation.mockClear();
|
|
823
|
+
(win as any).history = { state: { inFeedScroll: true } };
|
|
824
|
+
shadowClaw.popstateListener();
|
|
825
|
+
expect(mockApplyRouteFromCurrentLocation).not.toHaveBeenCalled();
|
|
820
826
|
});
|
|
821
827
|
});
|
|
822
828
|
|
|
@@ -12,6 +12,7 @@ import { showPage } from "./showPage.js";
|
|
|
12
12
|
import { supportsNavigationApi } from "./supportsNavigationApi.js";
|
|
13
13
|
import { setupAppLifecycle } from "./setupAppLifecycle.js";
|
|
14
14
|
import { syncPageHeaderMainVisibilityOverride } from "./syncPageHeaderMainVisibilityOverride.js";
|
|
15
|
+
import { setupSlottedSidebarActiveLinks } from "./syncSlottedSidebarActiveLinks.js";
|
|
15
16
|
|
|
16
17
|
import { toggleActivityLogVisibility } from "./toggleActivityLogVisibility.js";
|
|
17
18
|
import { togglePageHeaderMainVisibility } from "./togglePageHeaderMainVisibility.js";
|
|
@@ -88,6 +89,28 @@ export function bindEventListeners(
|
|
|
88
89
|
});
|
|
89
90
|
});
|
|
90
91
|
|
|
92
|
+
sidebar.addEventListener("click", (e: Event) => {
|
|
93
|
+
const target = e.target as HTMLElement | null;
|
|
94
|
+
if (
|
|
95
|
+
win.innerWidth < 896 &&
|
|
96
|
+
target?.closest("a, button, .nav-item, .sidebar-link, .sidebar-item")
|
|
97
|
+
) {
|
|
98
|
+
sidebar.classList.remove("open");
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
if (typeof (shadowClaw as any)?.addEventListener === "function") {
|
|
103
|
+
shadowClaw.addEventListener("click", (e: Event) => {
|
|
104
|
+
const target = e.target as HTMLElement | null;
|
|
105
|
+
if (
|
|
106
|
+
win.innerWidth < 896 &&
|
|
107
|
+
target?.closest('[slot^="sidebar"] a, .sidebar-custom-sections a')
|
|
108
|
+
) {
|
|
109
|
+
sidebar.classList.remove("open");
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
91
114
|
// Close sidebar when clicking outside of it (mobile only).
|
|
92
115
|
// Use composedPath() instead of e.target because Shadow DOM event
|
|
93
116
|
// retargeting makes e.target the host element at the doc level,
|
|
@@ -251,7 +274,11 @@ export function bindEventListeners(
|
|
|
251
274
|
|
|
252
275
|
shadowClaw.navigationListenerAttached = true;
|
|
253
276
|
} else {
|
|
254
|
-
shadowClaw.popstateListener = () => {
|
|
277
|
+
shadowClaw.popstateListener = (ev?: any) => {
|
|
278
|
+
const state = ev?.state || (win as any).history?.state;
|
|
279
|
+
if (state?.inFeedScroll || state?.scrollSpy || state?.suppressRouter) {
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
255
282
|
applyRouteFromCurrentLocation(
|
|
256
283
|
shadow,
|
|
257
284
|
shadowClaw,
|
|
@@ -312,4 +339,6 @@ export function bindEventListeners(
|
|
|
312
339
|
shadowClaw.orchestrator,
|
|
313
340
|
);
|
|
314
341
|
}
|
|
342
|
+
|
|
343
|
+
setupSlottedSidebarActiveLinks(shadowClaw);
|
|
315
344
|
}
|
|
@@ -93,4 +93,56 @@ describe("getRoute", () => {
|
|
|
93
93
|
|
|
94
94
|
expect(result).toBeUndefined();
|
|
95
95
|
});
|
|
96
|
+
|
|
97
|
+
it("returns undefined if destination state indicates an in-feed scroll", () => {
|
|
98
|
+
const navigateEvent = new Event("navigate") as any;
|
|
99
|
+
navigateEvent.navigationType = "replace";
|
|
100
|
+
navigateEvent.destination = {
|
|
101
|
+
url: `${window.location.origin}/test`,
|
|
102
|
+
getState: () => ({ inFeedScroll: true }),
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const result = getRoute(db, navigateEvent);
|
|
106
|
+
expect(result).toBeUndefined();
|
|
107
|
+
expect(isPossibleAppRouteMock).not.toHaveBeenCalled();
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("returns undefined if window.history.state indicates an in-feed scroll", () => {
|
|
111
|
+
const origState = window.history.state;
|
|
112
|
+
try {
|
|
113
|
+
window.history.replaceState({ inFeedScroll: true }, "", "/test");
|
|
114
|
+
const navigateEvent = new Event("navigate") as any;
|
|
115
|
+
navigateEvent.navigationType = "replace";
|
|
116
|
+
navigateEvent.destination = {
|
|
117
|
+
url: `${window.location.origin}/test`,
|
|
118
|
+
getState: () => null,
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const result = getRoute(db, navigateEvent);
|
|
122
|
+
expect(result).toBeUndefined();
|
|
123
|
+
expect(isPossibleAppRouteMock).not.toHaveBeenCalled();
|
|
124
|
+
} finally {
|
|
125
|
+
window.history.replaceState(origState, "", "/");
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("does not ignore push navigation even if window.history.state has inFeedScroll: true", () => {
|
|
130
|
+
const origState = window.history.state;
|
|
131
|
+
try {
|
|
132
|
+
window.history.replaceState({ inFeedScroll: true }, "", "/");
|
|
133
|
+
const navigateEvent = new Event("navigate") as any;
|
|
134
|
+
navigateEvent.navigationType = "push";
|
|
135
|
+
navigateEvent.destination = {
|
|
136
|
+
url: `${window.location.origin}/about`,
|
|
137
|
+
getState: () => null,
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const result = getRoute(db, navigateEvent);
|
|
141
|
+
expect(result).toBeDefined();
|
|
142
|
+
expect(result?.parsedUrl.pathname).toBe("/about");
|
|
143
|
+
expect(isPossibleAppRouteMock).toHaveBeenCalledWith("/about");
|
|
144
|
+
} finally {
|
|
145
|
+
window.history.replaceState(origState, "", "/");
|
|
146
|
+
}
|
|
147
|
+
});
|
|
96
148
|
});
|
|
@@ -11,6 +11,24 @@ export function getRoute(db: ShadowClawDatabase, ev: Event) {
|
|
|
11
11
|
return;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
const destinationState =
|
|
15
|
+
typeof navigateEvent?.destination?.getState === "function"
|
|
16
|
+
? navigateEvent.destination.getState()
|
|
17
|
+
: null;
|
|
18
|
+
const navState =
|
|
19
|
+
destinationState ||
|
|
20
|
+
(navigateEvent.navigationType === "replace" &&
|
|
21
|
+
typeof history !== "undefined"
|
|
22
|
+
? history.state
|
|
23
|
+
: null);
|
|
24
|
+
if (
|
|
25
|
+
navState?.inFeedScroll ||
|
|
26
|
+
navState?.scrollSpy ||
|
|
27
|
+
navState?.suppressRouter
|
|
28
|
+
) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
14
32
|
const destinationUrl = navigateEvent?.destination?.url;
|
|
15
33
|
if (typeof destinationUrl !== "string") {
|
|
16
34
|
return;
|
|
@@ -111,4 +111,25 @@ describe("navigateToRoute", () => {
|
|
|
111
111
|
route,
|
|
112
112
|
);
|
|
113
113
|
});
|
|
114
|
+
|
|
115
|
+
it("should applyRoute directly without calling nav.navigate if already at finalPath", async () => {
|
|
116
|
+
mockSupportsNavigationApi.mockReturnValue(true);
|
|
117
|
+
const origPathname = window.location.pathname;
|
|
118
|
+
try {
|
|
119
|
+
window.history.replaceState({}, "", "/base/chat");
|
|
120
|
+
await navigateToRoute(shadowRoot, shadowClaw, db, fStore, oStore, route);
|
|
121
|
+
|
|
122
|
+
expect(mockNav.navigate).not.toHaveBeenCalled();
|
|
123
|
+
expect(mockApplyRoute).toHaveBeenCalledWith(
|
|
124
|
+
shadowRoot,
|
|
125
|
+
shadowClaw,
|
|
126
|
+
db,
|
|
127
|
+
fStore,
|
|
128
|
+
oStore,
|
|
129
|
+
route,
|
|
130
|
+
);
|
|
131
|
+
} finally {
|
|
132
|
+
window.history.replaceState({}, "", origPathname);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
114
135
|
});
|
|
@@ -21,6 +21,16 @@ export async function navigateToRoute(
|
|
|
21
21
|
const targetPath = buildRoutePath(route);
|
|
22
22
|
const finalPath = applyBasePath(targetPath);
|
|
23
23
|
|
|
24
|
+
const currentPath =
|
|
25
|
+
typeof window !== "undefined" && window.location
|
|
26
|
+
? `${window.location.pathname}${window.location.search}${window.location.hash}`
|
|
27
|
+
: "";
|
|
28
|
+
|
|
29
|
+
if (currentPath && finalPath === currentPath) {
|
|
30
|
+
await applyRoute(shadow, shadowClaw, db, fStore, oStore, route);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
24
34
|
if (supportsNavigationApi()) {
|
|
25
35
|
const nav = (window as any).navigation;
|
|
26
36
|
|
|
@@ -34,12 +34,20 @@ export function applySidebarNavVisibility(
|
|
|
34
34
|
`.nav-item[data-page="${page}"]`,
|
|
35
35
|
) as HTMLElement | null;
|
|
36
36
|
|
|
37
|
-
if (
|
|
38
|
-
|
|
37
|
+
if (navItem) {
|
|
38
|
+
navItem.hidden = hidden;
|
|
39
|
+
navItem.setAttribute("aria-hidden", String(hidden));
|
|
39
40
|
}
|
|
40
41
|
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
if (page === "chat") {
|
|
43
|
+
const convoEl = shadow.querySelector(
|
|
44
|
+
"shadow-claw-conversations",
|
|
45
|
+
) as HTMLElement | null;
|
|
46
|
+
if (convoEl) {
|
|
47
|
+
convoEl.hidden = hidden;
|
|
48
|
+
convoEl.setAttribute("aria-hidden", String(hidden));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
43
51
|
}
|
|
44
52
|
|
|
45
53
|
/**
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from "@jest/globals";
|
|
2
|
+
import { syncSlottedSidebarActiveLinks } from "./syncSlottedSidebarActiveLinks.js";
|
|
3
|
+
|
|
4
|
+
describe("syncSlottedSidebarActiveLinks", () => {
|
|
5
|
+
let host: HTMLElement;
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
host = document.createElement("div");
|
|
9
|
+
host.innerHTML = `
|
|
10
|
+
<div slot="sidebar" class="sidebar-custom-sections">
|
|
11
|
+
<a href="/" class="sidebar-link">Home</a>
|
|
12
|
+
<a href="/about" class="sidebar-link">About</a>
|
|
13
|
+
<a href="/portfolio" class="sidebar-link">Portfolio</a>
|
|
14
|
+
<a href="https://external.example.com" class="sidebar-link" target="_blank">External</a>
|
|
15
|
+
</div>
|
|
16
|
+
`;
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("marks matching internal link as active and sets aria-current='page'", () => {
|
|
20
|
+
const url = new URL("https://example.com/about");
|
|
21
|
+
syncSlottedSidebarActiveLinks(host, url);
|
|
22
|
+
|
|
23
|
+
const homeLink = host.querySelector('a[href="/"]');
|
|
24
|
+
const aboutLink = host.querySelector('a[href="/about"]');
|
|
25
|
+
const portfolioLink = host.querySelector('a[href="/portfolio"]');
|
|
26
|
+
const externalLink = host.querySelector(
|
|
27
|
+
'a[href="https://external.example.com"]',
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
expect(aboutLink?.classList.contains("active")).toBe(true);
|
|
31
|
+
expect(aboutLink?.getAttribute("aria-current")).toBe("page");
|
|
32
|
+
|
|
33
|
+
expect(homeLink?.classList.contains("active")).toBe(false);
|
|
34
|
+
expect(homeLink?.hasAttribute("aria-current")).toBe(false);
|
|
35
|
+
|
|
36
|
+
expect(portfolioLink?.classList.contains("active")).toBe(false);
|
|
37
|
+
expect(externalLink?.classList.contains("active")).toBe(false);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("handles root path matching correctly with trailing slashes", () => {
|
|
41
|
+
const url = new URL("https://example.com/");
|
|
42
|
+
syncSlottedSidebarActiveLinks(host, url);
|
|
43
|
+
|
|
44
|
+
const homeLink = host.querySelector('a[href="/"]');
|
|
45
|
+
const aboutLink = host.querySelector('a[href="/about"]');
|
|
46
|
+
|
|
47
|
+
expect(homeLink?.classList.contains("active")).toBe(true);
|
|
48
|
+
expect(homeLink?.getAttribute("aria-current")).toBe("page");
|
|
49
|
+
expect(aboutLink?.classList.contains("active")).toBe(false);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("never marks external links as active", () => {
|
|
53
|
+
const url = new URL("https://example.com/");
|
|
54
|
+
syncSlottedSidebarActiveLinks(host, url);
|
|
55
|
+
|
|
56
|
+
const externalLink = host.querySelector(
|
|
57
|
+
'a[href="https://external.example.com"]',
|
|
58
|
+
);
|
|
59
|
+
expect(externalLink?.classList.contains("active")).toBe(false);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("handles hash links properly without activating sub-items on base route", () => {
|
|
63
|
+
host.innerHTML = `
|
|
64
|
+
<div slot="sidebar">
|
|
65
|
+
<a href="/portfolio" class="sidebar-link">Portfolio</a>
|
|
66
|
+
<a href="/portfolio#web-apps" class="sidebar-link">Web Apps</a>
|
|
67
|
+
<a href="/portfolio#corporate-sites" class="sidebar-link">Corporate Sites</a>
|
|
68
|
+
</div>
|
|
69
|
+
`;
|
|
70
|
+
|
|
71
|
+
// 1. Browsed to base /portfolio with no hash
|
|
72
|
+
const baseUrl = new URL("https://example.com/portfolio");
|
|
73
|
+
syncSlottedSidebarActiveLinks(host, baseUrl);
|
|
74
|
+
|
|
75
|
+
const portfolioLink = host.querySelector('a[href="/portfolio"]');
|
|
76
|
+
const webAppsLink = host.querySelector('a[href="/portfolio#web-apps"]');
|
|
77
|
+
const corporateLink = host.querySelector(
|
|
78
|
+
'a[href="/portfolio#corporate-sites"]',
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
expect(portfolioLink?.classList.contains("active")).toBe(true);
|
|
82
|
+
expect(webAppsLink?.classList.contains("active")).toBe(false);
|
|
83
|
+
expect(corporateLink?.classList.contains("active")).toBe(false);
|
|
84
|
+
|
|
85
|
+
// 2. Browsed to /portfolio#web-apps with hash
|
|
86
|
+
const hashUrl = new URL("https://example.com/portfolio#web-apps");
|
|
87
|
+
syncSlottedSidebarActiveLinks(host, hashUrl);
|
|
88
|
+
|
|
89
|
+
expect(portfolioLink?.classList.contains("active")).toBe(false);
|
|
90
|
+
expect(webAppsLink?.classList.contains("active")).toBe(true);
|
|
91
|
+
expect(corporateLink?.classList.contains("active")).toBe(false);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sync active state on slotted sidebar navigation links.
|
|
3
|
+
*/
|
|
4
|
+
export function syncSlottedSidebarActiveLinks(
|
|
5
|
+
host: HTMLElement | null,
|
|
6
|
+
currentUrl?: URL,
|
|
7
|
+
): void {
|
|
8
|
+
if (!host || typeof host.querySelectorAll !== "function") {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const url =
|
|
13
|
+
currentUrl ||
|
|
14
|
+
(typeof window !== "undefined" && window.location?.href
|
|
15
|
+
? new URL(window.location.href)
|
|
16
|
+
: null);
|
|
17
|
+
if (!url) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const currentPath = url.pathname.replace(/\/+$/, "") || "/";
|
|
22
|
+
|
|
23
|
+
// Select all links slotted into sidebar slots or in custom sections
|
|
24
|
+
const slottedLinks = host.querySelectorAll<HTMLAnchorElement>(
|
|
25
|
+
'[slot="sidebar"] a[href], [slot="sidebar-nav"] a[href], [slot="sidebar-content"] a[href], [slot="sidebar-footer"] a[href], .sidebar-custom-sections a[href]',
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
slottedLinks.forEach((link) => {
|
|
29
|
+
try {
|
|
30
|
+
const href = link.getAttribute("href") || "";
|
|
31
|
+
if (
|
|
32
|
+
!href ||
|
|
33
|
+
href.startsWith("#") ||
|
|
34
|
+
href.startsWith("javascript:") ||
|
|
35
|
+
href.startsWith("mailto:") ||
|
|
36
|
+
href.startsWith("tel:")
|
|
37
|
+
) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const linkUrl = new URL(href, url.origin);
|
|
42
|
+
if (linkUrl.origin !== url.origin) {
|
|
43
|
+
link.classList.remove("active");
|
|
44
|
+
link.removeAttribute("aria-current");
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const linkPath = linkUrl.pathname.replace(/\/+$/, "") || "/";
|
|
49
|
+
const isPathMatch = linkPath === currentPath;
|
|
50
|
+
const isMatch =
|
|
51
|
+
isPathMatch && (linkUrl.hash ? linkUrl.hash === url.hash : !url.hash);
|
|
52
|
+
|
|
53
|
+
if (isMatch) {
|
|
54
|
+
link.classList.add("active");
|
|
55
|
+
link.setAttribute("aria-current", "page");
|
|
56
|
+
} else {
|
|
57
|
+
link.classList.remove("active");
|
|
58
|
+
link.removeAttribute("aria-current");
|
|
59
|
+
}
|
|
60
|
+
} catch {
|
|
61
|
+
// Ignore URL parsing errors
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Sets up listeners so slotted sidebar active links stay in sync whenever
|
|
68
|
+
* the URL changes (popstate, Navigation API navigate, or iframe replaceState).
|
|
69
|
+
* Returns a cleanup function to remove the listeners.
|
|
70
|
+
*/
|
|
71
|
+
export function setupSlottedSidebarActiveLinks(
|
|
72
|
+
host: HTMLElement | null,
|
|
73
|
+
): () => void {
|
|
74
|
+
if (!host || typeof window === "undefined") {
|
|
75
|
+
return () => {};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const sync = () => syncSlottedSidebarActiveLinks(host);
|
|
79
|
+
|
|
80
|
+
// Initial sync
|
|
81
|
+
sync();
|
|
82
|
+
|
|
83
|
+
// Re-sync on back/forward navigation and replaceState-driven URL changes
|
|
84
|
+
window.addEventListener("popstate", sync, { passive: true });
|
|
85
|
+
|
|
86
|
+
// Re-sync on Navigation API navigate (Chrome 102+) — covers pushState/replaceState too
|
|
87
|
+
const nav = (window as unknown as { navigation?: EventTarget }).navigation;
|
|
88
|
+
if (nav && typeof nav.addEventListener === "function") {
|
|
89
|
+
nav.addEventListener("navigate", sync, {
|
|
90
|
+
passive: true,
|
|
91
|
+
} as EventListenerOptions);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return () => {
|
|
95
|
+
window.removeEventListener("popstate", sync);
|
|
96
|
+
if (nav && typeof nav.removeEventListener === "function") {
|
|
97
|
+
nav.removeEventListener("navigate", sync);
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
}
|
|
@@ -186,6 +186,11 @@ article > h2,
|
|
|
186
186
|
.pages__rendered > h1,
|
|
187
187
|
.pages__rendered > h2 {
|
|
188
188
|
margin-top: 0;
|
|
189
|
+
margin-bottom: var(--shadow-claw-article-header-margin-bottom, 1.5rem);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.pages__rendered :is(h1, h2, h3, h4) {
|
|
193
|
+
clear: both;
|
|
189
194
|
}
|
|
190
195
|
|
|
191
196
|
.pages__iframe {
|
|
@@ -219,7 +224,7 @@ article > h2,
|
|
|
219
224
|
|
|
220
225
|
.pages__dropdown-container {
|
|
221
226
|
align-items: center;
|
|
222
|
-
display: flex;
|
|
227
|
+
display: var(--shadow-claw-pages-dropdown-container-display, flex);
|
|
223
228
|
gap: 0.5rem;
|
|
224
229
|
margin-bottom: 1rem;
|
|
225
230
|
position: relative;
|
|
@@ -487,6 +492,10 @@ article > h2,
|
|
|
487
492
|
}
|
|
488
493
|
}
|
|
489
494
|
|
|
495
|
+
shadow-claw-page-header {
|
|
496
|
+
display: var(--shadow-claw-page-header-display, block);
|
|
497
|
+
}
|
|
498
|
+
|
|
490
499
|
ul {
|
|
491
500
|
line-height: 1.5;
|
|
492
501
|
}
|
|
@@ -23,10 +23,25 @@ const mockRegisterWebMcpTools = jest
|
|
|
23
23
|
const mockUnregisterWebMcpTools = jest.fn<() => void>();
|
|
24
24
|
const mockApplyWebMcpMode = jest.fn<(mode: WebMcpMode) => void>();
|
|
25
25
|
|
|
26
|
+
let lastActivatedListener: ((event: any) => void) | null = null;
|
|
27
|
+
let lastCancelListener: ((event: any) => void) | null = null;
|
|
28
|
+
const mockUnsubscribeActivated = jest.fn<() => void>();
|
|
29
|
+
const mockUnsubscribeCancel = jest.fn<() => void>();
|
|
30
|
+
const mockOnWebMcpToolActivated = jest.fn((cb: any) => {
|
|
31
|
+
lastActivatedListener = cb;
|
|
32
|
+
return mockUnsubscribeActivated;
|
|
33
|
+
});
|
|
34
|
+
const mockOnWebMcpToolCancel = jest.fn((cb: any) => {
|
|
35
|
+
lastCancelListener = cb;
|
|
36
|
+
return mockUnsubscribeCancel;
|
|
37
|
+
});
|
|
38
|
+
|
|
26
39
|
jest.unstable_mockModule("../../../subsystems/mcp/webmcp.js", () => ({
|
|
27
40
|
registerWebMcpTools: mockRegisterWebMcpTools,
|
|
28
41
|
unregisterWebMcpTools: mockUnregisterWebMcpTools,
|
|
29
42
|
setWebMcpMode: mockApplyWebMcpMode,
|
|
43
|
+
onWebMcpToolActivated: mockOnWebMcpToolActivated,
|
|
44
|
+
onWebMcpToolCancel: mockOnWebMcpToolCancel,
|
|
30
45
|
}));
|
|
31
46
|
|
|
32
47
|
const mockHandleWorkerMessage = jest
|
|
@@ -157,6 +172,7 @@ interface MockOrchestrator {
|
|
|
157
172
|
webMcpToolsEnabled: boolean;
|
|
158
173
|
webMcpRegistrationLock: Promise<void>;
|
|
159
174
|
agentWorker: Worker | null;
|
|
175
|
+
events?: { emit: jest.Mock };
|
|
160
176
|
}
|
|
161
177
|
|
|
162
178
|
describe("syncWebMcpRegistration", () => {
|
|
@@ -180,6 +196,7 @@ describe("syncWebMcpRegistration", () => {
|
|
|
180
196
|
webMcpToolsEnabled: true,
|
|
181
197
|
webMcpRegistrationLock: Promise.resolve(),
|
|
182
198
|
agentWorker: {} as unknown as Worker,
|
|
199
|
+
events: { emit: jest.fn() },
|
|
183
200
|
};
|
|
184
201
|
mockDiscoverSkills.mockResolvedValue({ skills: [] });
|
|
185
202
|
mockLoadDeclarativeTools.mockResolvedValue({
|
|
@@ -211,6 +228,41 @@ describe("syncWebMcpRegistration", () => {
|
|
|
211
228
|
expect(mockUnregisterWebMcpTools).toHaveBeenCalled();
|
|
212
229
|
});
|
|
213
230
|
|
|
231
|
+
it("should subscribe to toolactivated and toolcancel events and emit on orchestrator.events", async () => {
|
|
232
|
+
syncWebMcpRegistration(mockOrchestrator as unknown as Orchestrator, mockDb);
|
|
233
|
+
await mockOrchestrator.webMcpRegistrationLock;
|
|
234
|
+
|
|
235
|
+
expect(mockOnWebMcpToolActivated).toHaveBeenCalled();
|
|
236
|
+
expect(mockOnWebMcpToolCancel).toHaveBeenCalled();
|
|
237
|
+
|
|
238
|
+
// Trigger activated listener
|
|
239
|
+
lastActivatedListener?.({ toolName: "read_file" });
|
|
240
|
+
expect(mockOrchestrator.events?.emit).toHaveBeenCalledWith(
|
|
241
|
+
"webmcp-tool-activated",
|
|
242
|
+
{ toolName: "read_file" },
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
// Trigger cancel listener
|
|
246
|
+
lastCancelListener?.({ toolName: "read_file" });
|
|
247
|
+
expect(mockOrchestrator.events?.emit).toHaveBeenCalledWith(
|
|
248
|
+
"webmcp-tool-cancel",
|
|
249
|
+
{ toolName: "read_file" },
|
|
250
|
+
);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it("should unsubscribe from toolactivated and toolcancel when webMcpEffectCleanup is executed", async () => {
|
|
254
|
+
syncWebMcpRegistration(mockOrchestrator as unknown as Orchestrator, mockDb);
|
|
255
|
+
await mockOrchestrator.webMcpRegistrationLock;
|
|
256
|
+
|
|
257
|
+
const cleanup = mockOrchestrator.webMcpEffectCleanup;
|
|
258
|
+
expect(cleanup).toBeDefined();
|
|
259
|
+
|
|
260
|
+
cleanup!();
|
|
261
|
+
|
|
262
|
+
expect(mockUnsubscribeActivated).toHaveBeenCalled();
|
|
263
|
+
expect(mockUnsubscribeCancel).toHaveBeenCalled();
|
|
264
|
+
});
|
|
265
|
+
|
|
214
266
|
it("should setup effect and register tools filtered by group toolTags", async () => {
|
|
215
267
|
syncWebMcpRegistration(mockOrchestrator as unknown as Orchestrator, mockDb);
|
|
216
268
|
|
|
@@ -5,6 +5,8 @@ import {
|
|
|
5
5
|
setWebMcpMode as applyWebMcpMode,
|
|
6
6
|
registerWebMcpTools,
|
|
7
7
|
unregisterWebMcpTools,
|
|
8
|
+
onWebMcpToolActivated,
|
|
9
|
+
onWebMcpToolCancel,
|
|
8
10
|
} from "../../../subsystems/mcp/webmcp.js";
|
|
9
11
|
|
|
10
12
|
import { effect } from "../../effect.js";
|
|
@@ -38,6 +40,19 @@ export function syncWebMcpRegistration(
|
|
|
38
40
|
return;
|
|
39
41
|
}
|
|
40
42
|
|
|
43
|
+
// Subscribe to WebMCP lifecycle events (PR #245 / Chrome 156+ with backwards compatibility).
|
|
44
|
+
const unsubscribeActivated = onWebMcpToolActivated((event) => {
|
|
45
|
+
orchestrator.events?.emit("webmcp-tool-activated", {
|
|
46
|
+
toolName: event.toolName,
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const unsubscribeCancel = onWebMcpToolCancel((event) => {
|
|
51
|
+
orchestrator.events?.emit("webmcp-tool-cancel", {
|
|
52
|
+
toolName: event.toolName,
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
41
56
|
// Register WebMCP tools and re-register when tool config changes.
|
|
42
57
|
// This effect runs once immediately to perform the initial registration.
|
|
43
58
|
// We intentionally do NOT call isWebMcpSupported() here — that accesses
|
|
@@ -45,7 +60,7 @@ export function syncWebMcpRegistration(
|
|
|
45
60
|
// early-preview renderer. Instead, registerWebMcpTools handles feature detection
|
|
46
61
|
// internally and skips modelContext access entirely when 0 tools are
|
|
47
62
|
// passed.
|
|
48
|
-
|
|
63
|
+
const disposeEffect = effect(() => {
|
|
49
64
|
// Access signals to establish tracking.
|
|
50
65
|
const activeGroupId = orchestratorStore.activeGroupId;
|
|
51
66
|
const allTools = toolsStore.allTools;
|
|
@@ -109,6 +124,18 @@ export function syncWebMcpRegistration(
|
|
|
109
124
|
console.error("WebMCP registration failed:", err);
|
|
110
125
|
});
|
|
111
126
|
});
|
|
127
|
+
|
|
128
|
+
orchestrator.webMcpEffectCleanup = () => {
|
|
129
|
+
try {
|
|
130
|
+
unsubscribeActivated();
|
|
131
|
+
} catch {}
|
|
132
|
+
try {
|
|
133
|
+
unsubscribeCancel();
|
|
134
|
+
} catch {}
|
|
135
|
+
if (typeof disposeEffect === "function") {
|
|
136
|
+
disposeEffect();
|
|
137
|
+
}
|
|
138
|
+
};
|
|
112
139
|
}
|
|
113
140
|
export async function setWebMcpMode(
|
|
114
141
|
_state: Pick<
|
|
@@ -175,6 +175,42 @@ describe("static-files-middleware", () => {
|
|
|
175
175
|
}
|
|
176
176
|
});
|
|
177
177
|
|
|
178
|
+
it("serves SPA shell for app routes even when route directory exists on disk", async () => {
|
|
179
|
+
await register();
|
|
180
|
+
const middleware = app.use.mock.calls[1][0];
|
|
181
|
+
|
|
182
|
+
for (const appPath of ["/files", "/files/", "/pages", "/pages/"]) {
|
|
183
|
+
const req = {
|
|
184
|
+
method: "GET",
|
|
185
|
+
headers: {
|
|
186
|
+
accept: "text/html",
|
|
187
|
+
"sec-fetch-dest": "document",
|
|
188
|
+
"sec-fetch-mode": "navigate",
|
|
189
|
+
},
|
|
190
|
+
originalUrl: appPath,
|
|
191
|
+
url: appPath,
|
|
192
|
+
};
|
|
193
|
+
const res = { setHeader: jest.fn(), sendFile: jest.fn() };
|
|
194
|
+
const next = jest.fn();
|
|
195
|
+
|
|
196
|
+
fsMock.stat.mockImplementation((_targetPath: string, cb: any) => {
|
|
197
|
+
cb(null, { isFile: () => false, isDirectory: () => true });
|
|
198
|
+
});
|
|
199
|
+
fsMock.access.mockImplementation(
|
|
200
|
+
(_targetPath: string, _mode: any, cb: any) => {
|
|
201
|
+
cb(new Error("ENOENT"));
|
|
202
|
+
},
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
middleware(req, res, next);
|
|
206
|
+
|
|
207
|
+
expect(res.sendFile).toHaveBeenCalledWith(
|
|
208
|
+
expect.stringContaining("/root/index.html"),
|
|
209
|
+
);
|
|
210
|
+
expect(next).not.toHaveBeenCalled();
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
|
|
178
214
|
it("does not serve SPA shell for non-HTML missing requests", async () => {
|
|
179
215
|
await register();
|
|
180
216
|
const middleware = app.use.mock.calls[1][0];
|