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.
Files changed (325) hide show
  1. package/.well-known/mcp/server-card.json +1 -1
  2. package/.well-known/mcp.json +1 -1
  3. package/README.md +2 -1
  4. package/dist/cli/build/build.js +1 -1
  5. package/dist/cli/{chunk-Bfz2NYgD.js → chunk-0IeC97mU.js} +7 -7
  6. package/dist/cli/chunk-3oLxlxPn.js +1 -0
  7. package/dist/cli/chunk-63s8NnIS.js +93 -0
  8. package/dist/cli/{chunk-BXg4-9Rq2.js → chunk-8CrZyP1b2.js} +1 -1
  9. package/dist/cli/{chunk-BP5Hg6CM.js → chunk-B--ueArB.js} +1 -1
  10. package/dist/cli/chunk-BATVgkFY.js +17 -0
  11. package/dist/cli/chunk-BIh7rXlJ2.js +1 -0
  12. package/dist/cli/{chunk-Drr0VSGA2.js → chunk-BKnTOLLS2.js} +1 -1
  13. package/dist/cli/{chunk-C5PuRV08.js → chunk-BLMC1Bxv.js} +1 -1
  14. package/dist/cli/{chunk-B2tNSlFy2.js → chunk-BOigzz2U2.js} +1 -1
  15. package/dist/cli/{chunk-DOf3DZdR.js → chunk-BOzn335y.js} +1 -1
  16. package/dist/cli/chunk-BjOeVLb42.js +1 -0
  17. package/dist/cli/{chunk-DqTx0rRp2.js → chunk-Bjqh5_SU2.js} +1 -1
  18. package/dist/cli/chunk-BqWBd_jF2.js +1 -0
  19. package/dist/cli/{chunk-DIbSoQmd2.js → chunk-C6FFByBU2.js} +2 -2
  20. package/dist/cli/{chunk-Drq18VZe.js → chunk-C9-aL3rF.js} +1 -1
  21. package/dist/cli/{chunk-BUhqHeEA2.js → chunk-C9czddEh2.js} +2 -2
  22. package/dist/cli/chunk-CCx8WOwz2.js +1 -0
  23. package/dist/cli/{chunk-BkMSjVJb.js → chunk-CK6ejMig.js} +1 -1
  24. package/dist/cli/chunk-CUUERioR2.js +1 -0
  25. package/dist/cli/chunk-CXAobnJd.js +1 -0
  26. package/dist/cli/chunk-CY1X0pOF2.js +1 -0
  27. package/dist/cli/chunk-CZvvgLVv.js +1 -0
  28. package/dist/cli/chunk-CbcwbfJB.js +1 -0
  29. package/dist/cli/{chunk-quilB3TV2.js → chunk-Ci9OJGcd2.js} +1 -1
  30. package/dist/cli/chunk-Cj9wl0qx.js +1 -0
  31. package/dist/cli/{chunk-3gRhOmGo2.js → chunk-CjJsVrKI2.js} +1 -1
  32. package/dist/cli/{chunk-CmEfzm-_.js → chunk-ClPZ4Rea.js} +1 -1
  33. package/dist/cli/chunk-Cmdxoq9q.js +13 -0
  34. package/dist/cli/{chunk-WMPAfIvV2.js → chunk-DHJCGRuG2.js} +1 -1
  35. package/dist/cli/{chunk-C7Y2xIk4.js → chunk-DKVkYTV9.js} +1 -1
  36. package/dist/cli/chunk-DR2XtWRo.js +1 -0
  37. package/dist/cli/{chunk-BiVJVh_S.js → chunk-DUlIqkaX.js} +7 -7
  38. package/dist/cli/{chunk-CkpueGaZ2.js → chunk-DjWle8FF2.js} +1 -1
  39. package/dist/cli/{chunk-CSvHmNOk.js → chunk-DmRXVLfy.js} +1 -1
  40. package/dist/cli/{chunk-BT1xXZn72.js → chunk-HlSiTC-u2.js} +1 -1
  41. package/dist/cli/{chunk-da0bsiOn.js → chunk-P4JSeFBI.js} +1 -1
  42. package/dist/cli/chunk-UcNbHaRQ.js +1 -0
  43. package/dist/cli/{chunk-BXFHFbEd.js → chunk-UweLmfQS.js} +1 -1
  44. package/dist/cli/{chunk-CGiXWHTi2.js → chunk-gwpxl-0W2.js} +1 -1
  45. package/dist/cli/{chunk-NH8koEis2.js → chunk-nmDhxXH_2.js} +1 -1
  46. package/dist/cli/chunk-p0n_c-I2.js +1 -0
  47. package/dist/cli/{chunk-D7xP72La.js → chunk-q7r4FosZ.js} +1 -1
  48. package/dist/cli/cli.js +1 -1
  49. package/dist/cli/prerender/dsd-shell/prerender-dsd-shell.js +1 -1
  50. package/dist/electron/main.cjs +1 -1
  51. package/dist/headless-agent.js +1 -1
  52. package/dist/lib/{app-routes-BAEMLu9g.js → app-routes-CKkiX12g.js} +7 -2
  53. package/dist/lib/app-routes-CKkiX12g.js.map +1 -0
  54. package/dist/lib/components/index.js +26 -26
  55. package/dist/lib/components/settings/shadow-claw-accounts.js +1 -1
  56. package/dist/lib/components/settings/shadow-claw-channel-config.js +1 -1
  57. package/dist/lib/components/settings/shadow-claw-control-plane.js +1 -1
  58. package/dist/lib/components/settings/shadow-claw-git.js +1 -1
  59. package/dist/lib/components/settings/shadow-claw-integrations.js +1 -1
  60. package/dist/lib/components/settings/shadow-claw-llm.js +1 -1
  61. package/dist/lib/components/settings/shadow-claw-mcp-remote.js +1 -1
  62. package/dist/lib/components/settings/shadow-claw-networking.js +1 -1
  63. package/dist/lib/components/settings/shadow-claw-notifications.js +1 -1
  64. package/dist/lib/components/settings/shadow-claw-peerjs.js +1 -1
  65. package/dist/lib/components/settings/shadow-claw-storage.js +1 -1
  66. package/dist/lib/components/settings/shadow-claw-task-server.js +1 -1
  67. package/dist/lib/components/settings/shadow-claw-webvm.js +1 -1
  68. package/dist/lib/components/shadow-claw/utils/syncSlottedSidebarActiveLinks.d.ts +10 -0
  69. package/dist/lib/components/shadow-claw-a2ui.js +1 -1
  70. package/dist/lib/components/shadow-claw-accounts.js +1 -1
  71. package/dist/lib/components/shadow-claw-channel-config.js +1 -1
  72. package/dist/lib/components/shadow-claw-channels.js +1 -1
  73. package/dist/lib/components/shadow-claw-chat.js +1 -1
  74. package/dist/lib/components/shadow-claw-control-plane.js +1 -1
  75. package/dist/lib/components/shadow-claw-conversations.js +1 -1
  76. package/dist/lib/components/shadow-claw-file-viewer.js +1 -1
  77. package/dist/lib/components/shadow-claw-files.js +1 -1
  78. package/dist/lib/components/shadow-claw-git.js +1 -1
  79. package/dist/lib/components/shadow-claw-integrations.js +1 -1
  80. package/dist/lib/components/shadow-claw-llm.js +1 -1
  81. package/dist/lib/components/shadow-claw-mcp-remote.js +1 -1
  82. package/dist/lib/components/shadow-claw-networking.js +1 -1
  83. package/dist/lib/components/shadow-claw-notifications.js +1 -1
  84. package/dist/lib/components/shadow-claw-page-header.js +1 -1
  85. package/dist/lib/components/shadow-claw-pages.js +1 -1
  86. package/dist/lib/components/shadow-claw-peerjs.js +1 -1
  87. package/dist/lib/components/shadow-claw-settings.js +1 -1
  88. package/dist/lib/components/shadow-claw-storage.js +1 -1
  89. package/dist/lib/components/shadow-claw-task-server.js +1 -1
  90. package/dist/lib/components/shadow-claw-tasks.js +1 -1
  91. package/dist/lib/components/shadow-claw-terminal.js +1 -1
  92. package/dist/lib/components/shadow-claw-tools.js +1 -1
  93. package/dist/lib/components/shadow-claw-webvm.js +1 -1
  94. package/dist/lib/components/shadow-claw.js +1 -1
  95. package/dist/lib/{config-CPHDmyTx.js → config-DZkF9LiO.js} +2 -2
  96. package/dist/lib/{config-CPHDmyTx.js.map → config-DZkF9LiO.js.map} +1 -1
  97. package/dist/lib/{configurePeerJs-BsJoycQd.js → configurePeerJs-B3IE9BY2.js} +3 -3
  98. package/dist/lib/{configurePeerJs-BsJoycQd.js.map → configurePeerJs-B3IE9BY2.js.map} +1 -1
  99. package/dist/lib/{connections-BdIru7bF.js → connections-CalJGWvZ.js} +2 -2
  100. package/dist/lib/{connections-BdIru7bF.js.map → connections-CalJGWvZ.js.map} +1 -1
  101. package/dist/lib/{downloadGroupFile-Dn0KsMlV.js → downloadGroupFile-DDE1fIhJ.js} +2 -2
  102. package/dist/lib/{downloadGroupFile-Dn0KsMlV.js.map → downloadGroupFile-DDE1fIhJ.js.map} +1 -1
  103. package/dist/lib/{file-viewer-BYrXXu-X.js → file-viewer-Bll2EoGc.js} +5 -5
  104. package/dist/lib/{file-viewer-BYrXXu-X.js.map → file-viewer-Bll2EoGc.js.map} +1 -1
  105. package/dist/lib/{getGroupDir-DF-RZO8X.js → getGroupDir-MHv7Lvmp.js} +4 -4
  106. package/dist/lib/{getGroupDir-DF-RZO8X.js.map → getGroupDir-MHv7Lvmp.js.map} +1 -1
  107. package/dist/lib/{git-s_0cgKm1.js → git-Bv_M3dgw.js} +3 -3
  108. package/dist/lib/{git-s_0cgKm1.js.map → git-Bv_M3dgw.js.map} +1 -1
  109. package/dist/lib/{iframe-sanitizer-DFdda1jJ.js → iframe-sanitizer-C7ZGDjEI.js} +2 -2
  110. package/dist/lib/{iframe-sanitizer-DFdda1jJ.js.map → iframe-sanitizer-C7ZGDjEI.js.map} +1 -1
  111. package/dist/lib/{iframe-storage-proxy-qrhZmQyO.js → iframe-storage-proxy-CR8ZYHPd.js} +2 -2
  112. package/dist/lib/{iframe-storage-proxy-qrhZmQyO.js.map → iframe-storage-proxy-CR8ZYHPd.js.map} +1 -1
  113. package/dist/lib/index.js +27 -27
  114. package/dist/lib/{initChatSplitResize-DuNlwCuI.js → initChatSplitResize-DiKTI-wk.js} +2 -2
  115. package/dist/lib/{initChatSplitResize-DuNlwCuI.js.map → initChatSplitResize-DiKTI-wk.js.map} +1 -1
  116. package/dist/lib/{initControlPlane-D4UZ4EhV.js → initControlPlane-CzjtQ4aM.js} +15 -15
  117. package/dist/lib/{initControlPlane-D4UZ4EhV.js.map → initControlPlane-CzjtQ4aM.js.map} +1 -1
  118. package/dist/lib/{markdown-CRRwbN9h.js → markdown-k8wA9LKy.js} +4 -4
  119. package/dist/lib/{markdown-CRRwbN9h.js.map → markdown-k8wA9LKy.js.map} +1 -1
  120. package/dist/lib/{mcp-reconnect-COdZ-cea.js → mcp-reconnect-CGIdfZnS.js} +2 -2
  121. package/dist/lib/{mcp-reconnect-COdZ-cea.js.map → mcp-reconnect-CGIdfZnS.js.map} +1 -1
  122. package/dist/lib/{memoryStorage-BsMe2FOQ.js → memoryStorage-DL-Bo3dh.js} +2 -2
  123. package/dist/lib/{memoryStorage-BsMe2FOQ.js.map → memoryStorage-DL-Bo3dh.js.map} +1 -1
  124. package/dist/lib/{orchestrator-D9f5fDCb.js → orchestrator-DKHCoikY.js} +11 -11
  125. package/dist/lib/{orchestrator-D9f5fDCb.js.map → orchestrator-DKHCoikY.js.map} +1 -1
  126. package/dist/lib/{orchestrator-CMN43L0F.js → orchestrator-Dn1ZOz9B.js} +12 -12
  127. package/dist/lib/{orchestrator-CMN43L0F.js.map → orchestrator-Dn1ZOz9B.js.map} +1 -1
  128. package/dist/lib/{peerjs-6u8KtenE.js → peerjs-BK41iBvP.js} +7 -7
  129. package/dist/lib/{peerjs-6u8KtenE.js.map → peerjs-BK41iBvP.js.map} +1 -1
  130. package/dist/lib/{push-client-DCnB2-OU.js → push-client-DKgVFD2Z.js} +3 -3
  131. package/dist/lib/{push-client-DCnB2-OU.js.map → push-client-DKgVFD2Z.js.map} +1 -1
  132. package/dist/lib/{readGroupFileBytes-nKNJ6sbl.js → readGroupFileBytes-CgU_UfGv.js} +2 -2
  133. package/dist/lib/{readGroupFileBytes-nKNJ6sbl.js.map → readGroupFileBytes-CgU_UfGv.js.map} +1 -1
  134. package/dist/lib/{shadow-claw-4x3LqlII.js → shadow-claw-CtCj5Rn_.js} +121 -16
  135. package/dist/lib/shadow-claw-CtCj5Rn_.js.map +1 -0
  136. package/dist/lib/{shadow-claw-a2ui-Dipe8nY2.js → shadow-claw-a2ui-CvK5w940.js} +2 -2
  137. package/dist/lib/{shadow-claw-a2ui-Dipe8nY2.js.map → shadow-claw-a2ui-CvK5w940.js.map} +1 -1
  138. package/dist/lib/{shadow-claw-accounts-BgCkz3Zv.js → shadow-claw-accounts-Br3XdHro.js} +3 -3
  139. package/dist/lib/{shadow-claw-accounts-BgCkz3Zv.js.map → shadow-claw-accounts-Br3XdHro.js.map} +1 -1
  140. package/dist/lib/{shadow-claw-channel-config-CIm_Bxd2.js → shadow-claw-channel-config-Da2dN19M.js} +3 -3
  141. package/dist/lib/{shadow-claw-channel-config-CIm_Bxd2.js.map → shadow-claw-channel-config-Da2dN19M.js.map} +1 -1
  142. package/dist/lib/{shadow-claw-channels-CaceK52s.js → shadow-claw-channels-BofYHgz8.js} +3 -3
  143. package/dist/lib/{shadow-claw-channels-CaceK52s.js.map → shadow-claw-channels-BofYHgz8.js.map} +1 -1
  144. package/dist/lib/{shadow-claw-chat-BD18-mAR.js → shadow-claw-chat-Dw32DPV2.js} +12 -12
  145. package/dist/lib/{shadow-claw-chat-BD18-mAR.js.map → shadow-claw-chat-Dw32DPV2.js.map} +1 -1
  146. package/dist/lib/{shadow-claw-control-plane-U6LsfRPm.js → shadow-claw-control-plane-CwFotFDh.js} +7 -7
  147. package/dist/lib/{shadow-claw-control-plane-U6LsfRPm.js.map → shadow-claw-control-plane-CwFotFDh.js.map} +1 -1
  148. package/dist/lib/{shadow-claw-conversations-CDsza_oB.js → shadow-claw-conversations-0UXge23k.js} +5 -5
  149. package/dist/lib/{shadow-claw-conversations-CDsza_oB.js.map → shadow-claw-conversations-0UXge23k.js.map} +1 -1
  150. package/dist/lib/{shadow-claw-file-viewer-CuDhkVcL.js → shadow-claw-file-viewer-CAsn9NxH.js} +10 -10
  151. package/dist/lib/{shadow-claw-file-viewer-CuDhkVcL.js.map → shadow-claw-file-viewer-CAsn9NxH.js.map} +1 -1
  152. package/dist/lib/{shadow-claw-files-BI0P3m8a.js → shadow-claw-files-xGGhsHNH.js} +9 -9
  153. package/dist/lib/{shadow-claw-files-BI0P3m8a.js.map → shadow-claw-files-xGGhsHNH.js.map} +1 -1
  154. package/dist/lib/{shadow-claw-git-xRApLEMN.js → shadow-claw-git-B606hcMU.js} +6 -6
  155. package/dist/lib/{shadow-claw-git-xRApLEMN.js.map → shadow-claw-git-B606hcMU.js.map} +1 -1
  156. package/dist/lib/{shadow-claw-integrations-B2e1Nfg1.js → shadow-claw-integrations-Bwq5cba1.js} +4 -4
  157. package/dist/lib/{shadow-claw-integrations-B2e1Nfg1.js.map → shadow-claw-integrations-Bwq5cba1.js.map} +1 -1
  158. package/dist/lib/{shadow-claw-llm-Bc2mWuGb.js → shadow-claw-llm-BgpcMBIy.js} +6 -6
  159. package/dist/lib/{shadow-claw-llm-Bc2mWuGb.js.map → shadow-claw-llm-BgpcMBIy.js.map} +1 -1
  160. package/dist/lib/{shadow-claw-mcp-remote-D_Bh3aeC.js → shadow-claw-mcp-remote-DvQCA-Bk.js} +4 -4
  161. package/dist/lib/{shadow-claw-mcp-remote-D_Bh3aeC.js.map → shadow-claw-mcp-remote-DvQCA-Bk.js.map} +1 -1
  162. package/dist/lib/{shadow-claw-networking-F5W3n5ci.js → shadow-claw-networking-Cxvf9c7E.js} +2 -2
  163. package/dist/lib/{shadow-claw-networking-F5W3n5ci.js.map → shadow-claw-networking-Cxvf9c7E.js.map} +1 -1
  164. package/dist/lib/{shadow-claw-notifications-lvMt9WJ1.js → shadow-claw-notifications-CGVcFYBy.js} +3 -3
  165. package/dist/lib/{shadow-claw-notifications-lvMt9WJ1.js.map → shadow-claw-notifications-CGVcFYBy.js.map} +1 -1
  166. package/dist/lib/{shadow-claw-page-header-6gq6EMqX.js → shadow-claw-page-header-DAYz3I_o.js} +2 -2
  167. package/dist/lib/{shadow-claw-page-header-6gq6EMqX.js.map → shadow-claw-page-header-DAYz3I_o.js.map} +1 -1
  168. package/dist/lib/{shadow-claw-pages-Bo8_z2S-.js → shadow-claw-pages-BLeEZb9k.js} +24 -12
  169. package/dist/lib/shadow-claw-pages-BLeEZb9k.js.map +1 -0
  170. package/dist/lib/{shadow-claw-peerjs-DRTLCVDQ.js → shadow-claw-peerjs-BcFnbK74.js} +3 -3
  171. package/dist/lib/{shadow-claw-peerjs-DRTLCVDQ.js.map → shadow-claw-peerjs-BcFnbK74.js.map} +1 -1
  172. package/dist/lib/{shadow-claw-settings-D00B4p4L.js → shadow-claw-settings-B7o0Z4hu.js} +9 -9
  173. package/dist/lib/{shadow-claw-settings-D00B4p4L.js.map → shadow-claw-settings-B7o0Z4hu.js.map} +1 -1
  174. package/dist/lib/{shadow-claw-storage-DgMe7-Zo.js → shadow-claw-storage-afMVqB7E.js} +5 -5
  175. package/dist/lib/{shadow-claw-storage-DgMe7-Zo.js.map → shadow-claw-storage-afMVqB7E.js.map} +1 -1
  176. package/dist/lib/{shadow-claw-task-server-D103YOuI.js → shadow-claw-task-server-B9-MQjl6.js} +2 -2
  177. package/dist/lib/{shadow-claw-task-server-D103YOuI.js.map → shadow-claw-task-server-B9-MQjl6.js.map} +1 -1
  178. package/dist/lib/{shadow-claw-tasks-6OhALkq1.js → shadow-claw-tasks-Dc4BC-t-.js} +8 -8
  179. package/dist/lib/{shadow-claw-tasks-6OhALkq1.js.map → shadow-claw-tasks-Dc4BC-t-.js.map} +1 -1
  180. package/dist/lib/{shadow-claw-terminal-BGtxA1qD.js → shadow-claw-terminal-BINcTdsk.js} +2 -2
  181. package/dist/lib/{shadow-claw-terminal-BGtxA1qD.js.map → shadow-claw-terminal-BINcTdsk.js.map} +1 -1
  182. package/dist/lib/{shadow-claw-tools-OeIwAnnd.js → shadow-claw-tools-BjFgXNLR.js} +7 -7
  183. package/dist/lib/{shadow-claw-tools-OeIwAnnd.js.map → shadow-claw-tools-BjFgXNLR.js.map} +1 -1
  184. package/dist/lib/{shadow-claw-webvm-gVFZ197u.js → shadow-claw-webvm-Xe3ByMDw.js} +3 -3
  185. package/dist/lib/{shadow-claw-webvm-gVFZ197u.js.map → shadow-claw-webvm-Xe3ByMDw.js.map} +1 -1
  186. package/dist/lib/subsystems/mcp/webmcp.d.ts +64 -0
  187. package/dist/lib/{syncWebMcpRegistration-Bbph0XZl.js → syncWebMcpRegistration-D7alChz0.js} +21 -6
  188. package/dist/lib/syncWebMcpRegistration-D7alChz0.js.map +1 -0
  189. package/dist/lib/{tools-D0OlSzMD.js → tools-Cmhpk3X8.js} +4 -4
  190. package/dist/lib/{tools-D0OlSzMD.js.map → tools-Cmhpk3X8.js.map} +1 -1
  191. package/dist/lib/utils/index.js +1 -1
  192. package/dist/lib/utils/namespacedStorage.js +1 -1
  193. package/dist/lib/utils/utils.js +1 -1
  194. package/dist/lib/{utils-DYdrO2KI.js → utils-CzgKe_XZ.js} +4 -4
  195. package/dist/lib/{utils-DYdrO2KI.js.map → utils-CzgKe_XZ.js.map} +1 -1
  196. package/dist/lib/{webmcp-D2bocsgh.js → webmcp-oEP9YpR2.js} +119 -3
  197. package/dist/lib/{webmcp-D2bocsgh.js.map → webmcp-oEP9YpR2.js.map} +1 -1
  198. package/dist/public/.well-known/mcp/server-card.json +1 -1
  199. package/dist/public/.well-known/mcp.json +1 -1
  200. package/dist/public/AGENTS.md +8 -2
  201. package/dist/public/README.md +2 -1
  202. package/dist/public/agent.worker.js +1 -1
  203. package/dist/public/app-routes-C6-H2zPH.js +1 -0
  204. package/dist/public/components/shadow-claw/shadow-claw.css +1 -1
  205. package/dist/public/components/shadow-claw/shadow-claw.html +1 -1
  206. package/dist/public/components/shadow-claw-pages/shadow-claw-pages.css +1 -1
  207. package/dist/public/{config-D4lVqMs8.js → config-Dewnqegf.js} +1 -1
  208. package/dist/public/{configurePeerJs-DDOAVSaH.js → configurePeerJs-CaPU2sN5.js} +1 -1
  209. package/dist/public/{connections-CiukjGiH.js → connections-CwE7jE9l.js} +1 -1
  210. package/dist/public/{custom-element-security-piU2U-Ec.js → custom-element-security-DLgHMf5R.js} +1 -1
  211. package/dist/public/docs/README.md +1 -0
  212. package/dist/public/docs/example/article/index.html +4 -4
  213. package/dist/public/docs/guides/publishing-to-github-pages.md +95 -6
  214. package/dist/public/docs/publishing/index.html +4 -4
  215. package/dist/public/docs/skill-creator/index.html +4 -4
  216. package/dist/public/docs/subsystems/pages.md +10 -3
  217. package/dist/public/docs/subsystems/theming.md +242 -0
  218. package/dist/public/docs/subsystems/webmcp.md +89 -1
  219. package/dist/public/{downloadGroupFile-BiZQYY-O.js → downloadGroupFile-ChWHuap_.js} +1 -1
  220. package/dist/public/e2e/README.md +3 -2
  221. package/dist/public/{e2e-bridge-CybB810Q.js → e2e-bridge-BkaldjV2.js} +1 -1
  222. package/dist/public/{file-viewer-DLUBvOVD.js → file-viewer-Df4qUJCn.js} +1 -1
  223. package/dist/public/{getGroupDir-CLz5c8cg.js → getGroupDir-CeSr4fzS.js} +1 -1
  224. package/dist/public/{git-Ba8AeviI.js → git-Bscc1tK-.js} +2 -2
  225. package/dist/public/{iframe-sanitizer-CKOO7cxp.js → iframe-sanitizer-BVdkQsor.js} +1 -1
  226. package/dist/public/{iframe-storage-proxy-BvRiccH0.js → iframe-storage-proxy-Cg8aFw7o.js} +1 -1
  227. package/dist/public/index.css +1 -1
  228. package/dist/public/index.html +4 -4
  229. package/dist/public/index.js +1 -1
  230. package/dist/public/{initChatSplitResize-osJXM2gu.js → initChatSplitResize-C3Rp1LeZ.js} +1 -1
  231. package/dist/public/{initControlPlane-CNggzsRw.js → initControlPlane-C1pnzG16.js} +1 -1
  232. package/dist/public/llms.txt +2 -2
  233. package/dist/public/main/index.html +4 -4
  234. package/dist/public/main/memory/index.html +4 -4
  235. package/dist/public/{markdown-CZA-Ye2Q.js → markdown-D3A902nT.js} +1 -1
  236. package/dist/public/{mcp-reconnect-BK1bYBdC.js → mcp-reconnect-BJWxQBm_.js} +1 -1
  237. package/dist/public/{memoryStorage-Bew1X8Uy.js → memoryStorage-ykv-Ri-y.js} +1 -1
  238. package/dist/public/{orchestrator-D6tel2Zp.js → orchestrator-BVnC4Vtt.js} +2 -2
  239. package/dist/public/{orchestrator-Dc_Q73La.js → orchestrator-CQcc7OFA.js} +2 -2
  240. package/dist/public/{peerjs-CYs1F7sq.js → peerjs-CYC2cI1Y.js} +1 -1
  241. package/dist/public/{push-client-_HUyJlFJ.js → push-client-BJrnOULM.js} +1 -1
  242. package/dist/public/service-worker/fetch-proxy.js +1 -1
  243. package/dist/public/service-worker.js +1 -1
  244. package/dist/public/{shadow-claw-Dabb2AEp.js → shadow-claw-WwfOu-tu.js} +33 -3
  245. package/dist/public/{shadow-claw-accounts-BqGrm47Z.js → shadow-claw-accounts-BFZxrPsH.js} +1 -1
  246. package/dist/public/{shadow-claw-channels-Ci2UPaaA.js → shadow-claw-channels-C0w0c1EF.js} +1 -1
  247. package/dist/public/{shadow-claw-chat-BxR6cBYQ.js → shadow-claw-chat-DIJ0A-QN.js} +1 -1
  248. package/dist/public/{shadow-claw-control-plane-C1A-CZ9H.js → shadow-claw-control-plane-DZoxUd-1.js} +2 -2
  249. package/dist/public/{shadow-claw-conversations-DcwG05-H.js → shadow-claw-conversations-DMyG_5zj.js} +1 -1
  250. package/dist/public/{shadow-claw-file-viewer-CDs1QGXk.js → shadow-claw-file-viewer-DYgZjZb9.js} +1 -1
  251. package/dist/public/{shadow-claw-files-B774Xz1d.js → shadow-claw-files-DIOUdKJr.js} +1 -1
  252. package/dist/public/{shadow-claw-git-CTIPP0vs.js → shadow-claw-git-if5S0QJr.js} +2 -2
  253. package/dist/public/{shadow-claw-integrations-CjtkA6l0.js → shadow-claw-integrations-BQt4hcWe.js} +1 -1
  254. package/dist/public/{shadow-claw-llm-DfmITHde.js → shadow-claw-llm-9uf1KFA0.js} +2 -2
  255. package/dist/public/{shadow-claw-mcp-remote-BPuV22So.js → shadow-claw-mcp-remote-COpNIzJd.js} +1 -1
  256. package/dist/public/{shadow-claw-networking-DEMMygy0.js → shadow-claw-networking-AOVRUn6B.js} +1 -1
  257. package/dist/public/{shadow-claw-notifications-B9Xhcj1P.js → shadow-claw-notifications-CKB-W1TA.js} +1 -1
  258. package/dist/public/{shadow-claw-page-header-CAXokjQy.js → shadow-claw-page-header-D4LmUigA.js} +1 -1
  259. package/dist/public/{shadow-claw-pages-BRQw8bTm.js → shadow-claw-pages-BpHRY4vH.js} +12 -3
  260. package/dist/public/{shadow-claw-settings-BGrDNyqh.js → shadow-claw-settings-CE-aUo_J.js} +3 -3
  261. package/dist/public/{shadow-claw-storage-DK3pf11w.js → shadow-claw-storage-CFKC-Y3V.js} +1 -1
  262. package/dist/public/{shadow-claw-task-server-CMp5Vjza.js → shadow-claw-task-server-B_ZjqK4B.js} +1 -1
  263. package/dist/public/{shadow-claw-tasks-BO8aXM2B.js → shadow-claw-tasks-CGsEv48K.js} +1 -1
  264. package/dist/public/{shadow-claw-terminal-BSeTuyDa.js → shadow-claw-terminal-DcaS7cPH.js} +1 -1
  265. package/dist/public/{shadow-claw-tools-Bj6DLZ-2.js → shadow-claw-tools-Cnu9hKy0.js} +1 -1
  266. package/dist/public/{shadow-claw-webvm-M7d5RH8H.js → shadow-claw-webvm-BB8SFvJ8.js} +1 -1
  267. package/dist/public/syncWebMcpRegistration-D2Zx4oA1.js +1 -0
  268. package/dist/public/theme-init.js +1 -1
  269. package/dist/public/{tools-nysPKnGT.js → tools-sOLCHTj8.js} +1 -1
  270. package/dist/public/{webmcp-Bo3KTgRb.js → webmcp-YFt6vM1_.js} +1 -1
  271. package/dist/server.js +188 -188
  272. package/package.json +1 -1
  273. package/src/cli/build/build.ts +18 -11
  274. package/src/cli/prerender/dsd-shell/prerender-dsd-shell.test.ts +4 -1
  275. package/src/cli/prerender/dsd-shell/prerender-dsd-shell.ts +13 -1
  276. package/src/cli/prerender/pretty-paths/prerender-pretty-paths.ts +22 -0
  277. package/src/cli/prerender/utils/insert-before-closing-head.test.ts +34 -1
  278. package/src/cli/prerender/utils/insert-before-closing-head.ts +20 -6
  279. package/src/cli/site-config/apply.test.ts +90 -0
  280. package/src/cli/site-config/apply.ts +189 -22
  281. package/src/components/shadow-claw/shadow-claw.css +26 -0
  282. package/src/components/shadow-claw/shadow-claw.html +4 -0
  283. package/src/components/shadow-claw/utils/applyRoute.ts +3 -0
  284. package/src/components/shadow-claw/utils/bindEventListeners.test.ts +6 -0
  285. package/src/components/shadow-claw/utils/bindEventListeners.ts +30 -1
  286. package/src/components/shadow-claw/utils/getRoute.test.ts +52 -0
  287. package/src/components/shadow-claw/utils/getRoute.ts +18 -0
  288. package/src/components/shadow-claw/utils/navigateToRoute.test.ts +21 -0
  289. package/src/components/shadow-claw/utils/navigateToRoute.ts +10 -0
  290. package/src/components/shadow-claw/utils/sidebarVisibility.ts +12 -4
  291. package/src/components/shadow-claw/utils/syncSlottedSidebarActiveLinks.test.ts +93 -0
  292. package/src/components/shadow-claw/utils/syncSlottedSidebarActiveLinks.ts +100 -0
  293. package/src/components/shadow-claw-pages/shadow-claw-pages.css +10 -1
  294. package/src/components/shadow-claw-pages/utils/removePreviewIframe.ts +3 -0
  295. package/src/core/orchestrator/utils/syncWebMcpRegistration.test.ts +52 -0
  296. package/src/core/orchestrator/utils/syncWebMcpRegistration.ts +28 -1
  297. package/src/server/middleware/static-files.test.ts +36 -0
  298. package/src/server/middleware/static-files.ts +14 -6
  299. package/src/storage/staticRouting.test.ts +36 -0
  300. package/src/storage/staticRouting.ts +31 -3
  301. package/src/subsystems/mcp/webmcp.test.ts +255 -0
  302. package/src/subsystems/mcp/webmcp.ts +253 -0
  303. package/dist/cli/chunk-00GAzVPl2.js +0 -1
  304. package/dist/cli/chunk-2hnFoNNC.js +0 -1
  305. package/dist/cli/chunk-BKQL4nzF2.js +0 -1
  306. package/dist/cli/chunk-BQ4K3fqj2.js +0 -1
  307. package/dist/cli/chunk-BVCGo6uf2.js +0 -1
  308. package/dist/cli/chunk-Bc9nRNyN.js +0 -11
  309. package/dist/cli/chunk-BcTH_-FL.js +0 -1
  310. package/dist/cli/chunk-BmD_sPRt.js +0 -1
  311. package/dist/cli/chunk-BsxNqqer.js +0 -1
  312. package/dist/cli/chunk-CCcF5T-c.js +0 -1
  313. package/dist/cli/chunk-CFhvwUeL.js +0 -91
  314. package/dist/cli/chunk-CwWtvULn.js +0 -1
  315. package/dist/cli/chunk-DID5VvOG.js +0 -17
  316. package/dist/cli/chunk-Drf-hXIR2.js +0 -1
  317. package/dist/cli/chunk-Dt5HNYVi.js +0 -1
  318. package/dist/cli/chunk-DwZQi9Ax.js +0 -1
  319. package/dist/cli/chunk-m9ZX7TyF2.js +0 -1
  320. package/dist/lib/app-routes-BAEMLu9g.js.map +0 -1
  321. package/dist/lib/shadow-claw-4x3LqlII.js.map +0 -1
  322. package/dist/lib/shadow-claw-pages-Bo8_z2S-.js.map +0 -1
  323. package/dist/lib/syncWebMcpRegistration-Bbph0XZl.js.map +0 -1
  324. package/dist/public/app-routes-BMQ4HFaM.js +0 -1
  325. 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 (!navItem) {
38
- return;
37
+ if (navItem) {
38
+ navItem.hidden = hidden;
39
+ navItem.setAttribute("aria-hidden", String(hidden));
39
40
  }
40
41
 
41
- navItem.hidden = hidden;
42
- navItem.setAttribute("aria-hidden", String(hidden));
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
  }
@@ -8,5 +8,8 @@ export function removePreviewIframe(root: ShadowRoot | HTMLElement): void {
8
8
  }
9
9
 
10
10
  iframe.removeAttribute("srcdoc");
11
+ try {
12
+ iframe.src = "about:blank";
13
+ } catch {}
11
14
  iframe.remove();
12
15
  }
@@ -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
- orchestrator.webMcpEffectCleanup = effect(() => {
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];