shadow-claw 1.23.3 → 1.24.0

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 (286) hide show
  1. package/README.md +42 -5
  2. package/bin/assert-version-bump.mjs +60 -38
  3. package/bin/assert-version-bump.test.mjs +74 -11
  4. package/bin/cli.mjs +166 -0
  5. package/bin/cli.test.mjs +60 -0
  6. package/bin/commands/backup.mjs +70 -0
  7. package/bin/commands/clients.mjs +41 -0
  8. package/bin/commands/peer-id.mjs +76 -0
  9. package/bin/commands/peer-id.test.mjs +104 -0
  10. package/bin/commands/send.mjs +51 -0
  11. package/bin/commands/tasks.mjs +59 -0
  12. package/bin/commands/webrtc-listen.mjs +254 -0
  13. package/bin/prerender-dsd-shell/prerender-dsd-shell.test.mjs +46 -0
  14. package/bin/rolldown.config.test.mjs +401 -8
  15. package/bin/utils/control-client.mjs +243 -0
  16. package/bin/utils/control-client.test.mjs +83 -0
  17. package/bin/utils/ulid.mjs +45 -0
  18. package/bin/utils/webrtc-control-client.mjs +699 -0
  19. package/bin/utils/webrtc-control-client.test.mjs +318 -0
  20. package/dist/electron/main.cjs +1 -1
  21. package/dist/public/AGENTS.md +5 -3
  22. package/dist/public/README.md +42 -5
  23. package/dist/public/agent.worker.js +6 -6
  24. package/dist/public/{app-routes-B0ePT49v.js → app-routes-Bs-riIvr.js} +1 -1
  25. package/dist/public/backup-controller-C_s9in2t.js +1 -0
  26. package/dist/public/components/settings/shadow-claw-control-plane/shadow-claw-control-plane.css +1 -0
  27. package/dist/public/components/settings/shadow-claw-control-plane/shadow-claw-control-plane.html +1 -0
  28. package/dist/public/components/settings/shadow-claw-storage/shadow-claw-storage.html +1 -1
  29. package/dist/public/components/settings/shadow-claw-task-server/shadow-claw-task-server.css +1 -1
  30. package/dist/public/components/settings/shadow-claw-task-server/shadow-claw-task-server.html +1 -1
  31. package/dist/public/components/shadow-claw-files/shadow-claw-files.css +1 -1
  32. package/dist/public/components/shadow-claw-files/shadow-claw-files.html +1 -1
  33. package/dist/public/components/shadow-claw-settings/shadow-claw-settings.html +1 -1
  34. package/dist/public/config-CrHchneq.js +1 -0
  35. package/dist/public/{configurePeerJs-B7kNU8AQ.js → configurePeerJs-CxpZeDJJ.js} +1 -1
  36. package/dist/public/{connections-xd_66YVU.js → connections-DvbgdkaB.js} +1 -1
  37. package/dist/public/{custom-element-security-CZ92anB8.js → custom-element-security-NCqQ5lmC.js} +1 -1
  38. package/dist/public/docs/README.md +3 -1
  39. package/dist/public/docs/architecture/overview.md +12 -12
  40. package/dist/public/docs/architecture/storage.md +5 -2
  41. package/dist/public/docs/decisions/bundled-typescript-architecture.md +1 -1
  42. package/dist/public/docs/decisions/indexeddb-and-opfs-storage.md +6 -3
  43. package/dist/public/docs/decisions/native-web-components-and-signals.md +1 -1
  44. package/dist/public/docs/example/article/index.html +2 -2
  45. package/dist/public/docs/guides/configuring-messaging-channels.md +41 -6
  46. package/dist/public/docs/guides/server-development-configuration.md +24 -0
  47. package/dist/public/docs/publishing/index.html +19 -76
  48. package/dist/public/docs/skill-creator/index.html +2 -2
  49. package/dist/public/docs/subsystems/backup.md +56 -0
  50. package/dist/public/docs/subsystems/cli.md +124 -0
  51. package/dist/public/docs/subsystems/control-plane.md +158 -0
  52. package/dist/public/docs/subsystems/notifications.md +54 -2
  53. package/dist/public/docs/subsystems/reactive-ui.md +1 -0
  54. package/dist/public/downloadGroupFile-By0GR6eV.js +1 -0
  55. package/dist/public/e2e/README.md +5 -1
  56. package/dist/public/{e2e-bridge-DkS5xFGX.js → e2e-bridge-IGWsRl4i.js} +1 -1
  57. package/dist/public/file-viewer-D0eflgt6.js +1 -0
  58. package/dist/public/files/main/~/docs/publishing-to-github-pages.md +17 -82
  59. package/dist/public/getAllTasks-vpBlgGsc.js +1 -0
  60. package/dist/public/getGroupDir-P1h9wl6S.js +1 -0
  61. package/dist/public/{git-BkjrsRXX.js → git-7Vq_SJSp.js} +2 -2
  62. package/dist/public/{iframe-sanitizer-L6Jyu__Z.js → iframe-sanitizer-y2Z1pPwY.js} +1 -1
  63. package/dist/public/index.html +2 -2
  64. package/dist/public/index.js +1 -1
  65. package/dist/public/{initChatSplitResize-BslPXAXS.js → initChatSplitResize-CzV2E7Ul.js} +1 -1
  66. package/dist/public/initControlPlane-C9jpnWev.js +1 -0
  67. package/dist/public/llms.txt +1 -1
  68. package/dist/public/main/index.html +2 -2
  69. package/dist/public/main/memory/index.html +2 -2
  70. package/dist/public/{markdown-6MBrIo-S.js → markdown-BnpKxSxl.js} +1 -1
  71. package/dist/public/mcp-reconnect-9emsqGRe.js +1 -0
  72. package/dist/public/{memoryStorage-CjGseKI1.js → memoryStorage-C0KvLNUp.js} +1 -1
  73. package/dist/public/{orchestrator-B9KmIj8Q.js → orchestrator-3h9xxHUm.js} +676 -676
  74. package/dist/public/orchestrator-nJj59RqD.js +15 -0
  75. package/dist/public/pages/main/~/docs/publishing-to-github-pages.md +17 -82
  76. package/dist/public/{peerjs-Dv8JNm7v.js → peerjs-BP4laMXj.js} +1 -1
  77. package/dist/public/{push-client-Dqq06wbB.js → push-client-CrjEXK3U.js} +1 -1
  78. package/dist/public/service-worker/fetch-proxy.js +1 -1
  79. package/dist/public/service-worker/push-handler.js +1 -1
  80. package/dist/public/service-worker.js +1 -1
  81. package/dist/public/{shadow-claw-Bd1rfuVC.js → shadow-claw-CbwETBWf.js} +2 -2
  82. package/dist/public/{shadow-claw-accounts-DL2l3oM0.js → shadow-claw-accounts-B4sBTQEj.js} +3 -3
  83. package/dist/public/{shadow-claw-channels-BGXDsQ38.js → shadow-claw-channels-BDWG5Qa3.js} +1 -1
  84. package/dist/public/{shadow-claw-chat-NXg6zz_l.js → shadow-claw-chat-Crv0DTCY.js} +6 -6
  85. package/dist/public/shadow-claw-control-plane-CX1IGbh9.js +199 -0
  86. package/dist/public/{shadow-claw-conversations-C8eqGpP9.js → shadow-claw-conversations-_9qAaRXk.js} +1 -1
  87. package/dist/public/shadow-claw-dialog-BrOwAOdk.js +1 -0
  88. package/dist/public/shadow-claw-file-viewer-Blf6yGVD.js +577 -0
  89. package/dist/public/shadow-claw-files-CYySBDCl.js +840 -0
  90. package/dist/public/{shadow-claw-git-C0ntz7aX.js → shadow-claw-git-v8S0JjuR.js} +3 -3
  91. package/dist/public/{shadow-claw-integrations-DCXV2r69.js → shadow-claw-integrations-D7ufpwhX.js} +1 -1
  92. package/dist/public/{shadow-claw-llm-CudgFQsS.js → shadow-claw-llm-GWgoVfhI.js} +2 -2
  93. package/dist/public/{shadow-claw-mcp-remote-B8RkpX28.js → shadow-claw-mcp-remote-kY4LMFNN.js} +1 -1
  94. package/dist/public/{shadow-claw-networking-BLiRsrsK.js → shadow-claw-networking-qHbP95z9.js} +1 -1
  95. package/dist/public/{shadow-claw-notifications-CQhQcjtL.js → shadow-claw-notifications-YyHOK2Fr.js} +1 -1
  96. package/dist/public/{shadow-claw-page-header-NeRD0tKU.js → shadow-claw-page-header-DMKbkv3s.js} +1 -1
  97. package/dist/public/{shadow-claw-pages-DCHnFh6r.js → shadow-claw-pages-DFZHhzFZ.js} +3 -3
  98. package/dist/public/{shadow-claw-settings-VAfjKFQg.js → shadow-claw-settings-DhxrKIfA.js} +10 -3
  99. package/dist/public/shadow-claw-storage-BFynbP0j.js +219 -0
  100. package/dist/public/{shadow-claw-task-server-Dk33N_1G.js → shadow-claw-task-server-3BIeb5oZ.js} +25 -4
  101. package/dist/public/{shadow-claw-tasks-xA1VfmWN.js → shadow-claw-tasks-Caxc9atL.js} +4 -4
  102. package/dist/public/{shadow-claw-terminal-Du2r0ciq.js → shadow-claw-terminal-Beb5qF-U.js} +1 -1
  103. package/dist/public/{shadow-claw-tools-CZj4rq7l.js → shadow-claw-tools-BfJx3A9W.js} +2 -2
  104. package/dist/public/{shadow-claw-webvm-JYUkWICG.js → shadow-claw-webvm-BCS8ykeP.js} +1 -1
  105. package/dist/public/static-main/~/docs/publishing-to-github-pages.md +17 -82
  106. package/dist/public/static-main-manifest.json +1 -1
  107. package/dist/public/{syncWebMcpRegistration-DfpvASnh.js → syncWebMcpRegistration-B_M2fCjH.js} +1 -1
  108. package/dist/server.js +286 -189
  109. package/package.json +2 -1
  110. package/src/__mocks__/jszip.cjs +15 -5
  111. package/src/components/settings/shadow-claw-accounts/shadow-claw-accounts.test.ts +35 -0
  112. package/src/components/settings/shadow-claw-control-plane/shadow-claw-control-plane.css +117 -0
  113. package/src/components/settings/shadow-claw-control-plane/shadow-claw-control-plane.html +81 -0
  114. package/src/components/settings/shadow-claw-control-plane/shadow-claw-control-plane.test.ts +127 -0
  115. package/src/components/settings/shadow-claw-control-plane/shadow-claw-control-plane.ts +292 -0
  116. package/src/components/settings/shadow-claw-git/shadow-claw-git.test.ts +72 -0
  117. package/src/components/settings/shadow-claw-integrations/connection-test-auth.test.ts +20 -30
  118. package/src/components/settings/shadow-claw-integrations/shadow-claw-integrations.test.ts +116 -0
  119. package/src/components/settings/shadow-claw-llm/model-ranking.test.ts +143 -51
  120. package/src/components/settings/shadow-claw-llm/shadow-claw-llm.test.ts +307 -4
  121. package/src/components/settings/shadow-claw-llm/shadow-claw-llm.ts +8 -8
  122. package/src/components/settings/shadow-claw-mcp-remote/shadow-claw-mcp-remote.test.ts +31 -0
  123. package/src/components/settings/shadow-claw-peerjs/shadow-claw-peerjs.test.ts +186 -0
  124. package/src/components/settings/shadow-claw-storage/shadow-claw-storage.html +17 -0
  125. package/src/components/settings/shadow-claw-storage/shadow-claw-storage.test.ts +120 -0
  126. package/src/components/settings/shadow-claw-storage/shadow-claw-storage.ts +56 -0
  127. package/src/components/settings/shadow-claw-task-server/shadow-claw-task-server.css +23 -2
  128. package/src/components/settings/shadow-claw-task-server/shadow-claw-task-server.html +1 -1
  129. package/src/components/settings/shadow-claw-task-server/shadow-claw-task-server.test.ts +19 -0
  130. package/src/components/settings/shadow-claw-task-server/shadow-claw-task-server.ts +2 -1
  131. package/src/components/shadow-claw/utils/initChatSplitResize.test.ts +180 -13
  132. package/src/components/shadow-claw/utils/setupEffects.test.ts +39 -0
  133. package/src/components/shadow-claw-a2ui/catalog/basic/shared.test.ts +117 -0
  134. package/src/components/shadow-claw-a2ui/shadow-claw-a2ui.test.ts +1159 -1
  135. package/src/components/shadow-claw-chat/shadow-claw-chat.test.ts +1523 -19
  136. package/src/components/shadow-claw-chat/utils/escapeHtml.test.ts +13 -0
  137. package/src/components/shadow-claw-chat/utils/getPeerChatDisplayStatus.test.ts +20 -0
  138. package/src/components/shadow-claw-conversations/shadow-claw-conversations.test.ts +494 -0
  139. package/src/components/shadow-claw-dialog/shadow-claw-dialog.ts +13 -2
  140. package/src/components/shadow-claw-file-viewer/shadow-claw-file-viewer.test.ts +378 -0
  141. package/src/components/shadow-claw-file-viewer/shadow-claw-file-viewer.ts +11 -9
  142. package/src/components/shadow-claw-files/shadow-claw-files.css +41 -17
  143. package/src/components/shadow-claw-files/shadow-claw-files.html +35 -0
  144. package/src/components/shadow-claw-files/shadow-claw-files.test.ts +627 -1
  145. package/src/components/shadow-claw-files/shadow-claw-files.ts +170 -6
  146. package/src/components/shadow-claw-pages/utils/resolveWorkspaceLinkPath.test.ts +31 -0
  147. package/src/components/shadow-claw-pages/utils/resolveWorkspaceLinkPath.ts +2 -1
  148. package/src/components/shadow-claw-pages/utils/rewriteWorkspacePreviewHtml.test.ts +15 -0
  149. package/src/components/shadow-claw-pdf-viewer/utils/installGetOrInsertComputedPolyfill.test.ts +65 -0
  150. package/src/components/shadow-claw-settings/shadow-claw-settings.html +7 -0
  151. package/src/components/shadow-claw-settings/shadow-claw-settings.test.ts +221 -0
  152. package/src/components/shadow-claw-settings/shadow-claw-settings.ts +2 -0
  153. package/src/components/shadow-claw-tasks/shadow-claw-tasks.test.ts +407 -0
  154. package/src/components/shadow-claw-terminal/utils/consumeEscapeSequence.test.ts +58 -0
  155. package/src/components/shadow-claw-tools/shadow-claw-tools.test.ts +80 -0
  156. package/src/config/config.test.ts +9 -0
  157. package/src/config/config.ts +15 -0
  158. package/src/core/app-routes.test.ts +56 -0
  159. package/src/core/app-routes.ts +8 -1
  160. package/src/core/backup-controller.test.ts +101 -0
  161. package/src/core/backup-controller.ts +137 -0
  162. package/src/core/control-plane-client.test.ts +408 -0
  163. package/src/core/control-plane-client.ts +425 -0
  164. package/src/core/orchestrator/orchestrator.ts +4 -0
  165. package/src/core/orchestrator/utils/compactContext.test.ts +78 -0
  166. package/src/core/orchestrator/utils/operations/channel.test.ts +254 -9
  167. package/src/core/orchestrator/utils/operations/provider.test.ts +326 -34
  168. package/src/core/orchestrator/utils/operations/vm.test.ts +114 -2
  169. package/src/core/orchestrator/utils/syncProxyConfigToServiceWorker.test.ts +60 -0
  170. package/src/core/utils/initControlPlane.test.ts +276 -0
  171. package/src/core/utils/initControlPlane.ts +437 -0
  172. package/src/core/utils/initializeApp.ts +18 -0
  173. package/src/db/getSession.test.ts +13 -1
  174. package/src/db/openDatabase.test.ts +10 -1
  175. package/src/db/openDatabase.ts +3 -1
  176. package/src/server/app.test.ts +12 -0
  177. package/src/server/app.ts +18 -8
  178. package/src/server/client-registry.test.ts +250 -0
  179. package/src/server/client-registry.ts +477 -0
  180. package/src/server/config.ts +15 -0
  181. package/src/server/control-plane-types.ts +120 -0
  182. package/src/server/control-plane.test.ts +472 -0
  183. package/src/server/control-plane.ts +581 -0
  184. package/src/server/routes/backup.test.ts +263 -0
  185. package/src/server/routes/backup.ts +262 -0
  186. package/src/server/routes/integrations-email.test.ts +532 -0
  187. package/src/server/routes/llamafile.test.ts +172 -48
  188. package/src/server/routes/mesh-llm.test.ts +192 -0
  189. package/src/server/routes/ollama.test.ts +49 -0
  190. package/src/server/server-peer.test.ts +86 -0
  191. package/src/server/server-peer.ts +330 -0
  192. package/src/server/server.ts +35 -0
  193. package/src/server/services/llamafile-manager.test.ts +230 -6
  194. package/src/server/services/transformers-runtime.test.ts +186 -18
  195. package/src/server/webrtc.test.ts +21 -0
  196. package/src/server/webrtc.ts +72 -0
  197. package/src/service-worker/fetch-proxy.test.ts +64 -1
  198. package/src/service-worker/push-handler.ts +39 -0
  199. package/src/shell/vm.test.ts +744 -12
  200. package/src/storage/ensureMainGroupIndex.test.ts +153 -0
  201. package/src/storage/getGroupFile.test.ts +66 -0
  202. package/src/storage/memoryStorage.test.ts +82 -2
  203. package/src/storage/migrateLegacyOpfs.test.ts +327 -0
  204. package/src/storage/migrateLegacyOpfs.ts +117 -0
  205. package/src/storage/writeGroupFileBytes.test.ts +143 -0
  206. package/src/stores/chat-ui.test.ts +20 -0
  207. package/src/stores/orchestrator.test.ts +134 -0
  208. package/src/stores/toast.test.ts +50 -0
  209. package/src/stores/tools.test.ts +147 -10
  210. package/src/subsystems/channels/imessage.test.ts +59 -0
  211. package/src/subsystems/channels/peerjs.test.ts +500 -62
  212. package/src/subsystems/channels/peerjs.ts +71 -4
  213. package/src/subsystems/channels/telegram.test.ts +177 -0
  214. package/src/subsystems/email/catalog.test.ts +1 -0
  215. package/src/subsystems/email/connections.test.ts +39 -1
  216. package/src/subsystems/git/credentials.test.ts +160 -825
  217. package/src/subsystems/git/git.test.ts +463 -0
  218. package/src/subsystems/notifications/__mocks__/node-sqlite.cjs +12 -1
  219. package/src/subsystems/notifications/push-routes.test.ts +33 -0
  220. package/src/subsystems/notifications/push-routes.ts +23 -0
  221. package/src/subsystems/notifications/push-store.ts +1 -1
  222. package/src/subsystems/providers/builtin-ai-tasks.test.ts +74 -0
  223. package/src/subsystems/providers/litert-lm-provider.test.ts +212 -51
  224. package/src/subsystems/providers/model-registry.test.ts +115 -0
  225. package/src/subsystems/providers/providers.test.ts +94 -1
  226. package/src/subsystems/providers/transformers-js-provider.test.ts +279 -0
  227. package/src/subsystems/providers/utils/createModelCacheFetch.test.ts +2 -2
  228. package/src/subsystems/providers/utils/downloadModelToCache.test.ts +2 -2
  229. package/src/subsystems/providers/utils/loadModelStream.test.ts +2 -2
  230. package/src/subsystems/skills/activateSkill.test.ts +101 -0
  231. package/src/subsystems/skills/discoverSkills.test.ts +251 -0
  232. package/src/subsystems/tools/cron.test.ts +64 -0
  233. package/src/subsystems/tools/declarative.test.ts +255 -89
  234. package/src/testing/assets/v86.9pfs/libv86.mjs +63 -0
  235. package/src/testing/assets/v86.ext2/libv86.mjs +3 -0
  236. package/src/testing/assets_missing_v86/assets/v86.9pfs/libv86.mjs +1 -0
  237. package/src/testing/jest-setup.ts +328 -40
  238. package/src/testing/mock-v86.mjs +49 -0
  239. package/src/ui/a2ui/types.ts +4 -3
  240. package/src/ui/a2ui/utils/normaliseComponentsToMap.test.ts +102 -0
  241. package/src/ui/a2ui/utils/resolveChildIds.test.ts +63 -0
  242. package/src/ui/a2ui/utils/resolveDynamicNumber.test.ts +13 -1
  243. package/src/worker/tools/bash/utils/waitForVMReady.test.ts +40 -0
  244. package/src/worker/tools/email/email.test.ts +193 -8
  245. package/src/worker/tools/email/utils/asEmailAttachmentInputs.test.ts +47 -0
  246. package/src/worker/tools/git/git.test.ts +214 -3
  247. package/src/worker/tools/rooms/invite-to-room.test.ts +54 -0
  248. package/src/worker/tools/rooms/leave-room.test.ts +40 -0
  249. package/src/worker/tools/rooms/list-room-members.test.ts +75 -0
  250. package/src/worker/tools/tasks/tasks-utils.test.ts +22 -0
  251. package/src/worker/tools/ui/ask-user.test.ts +50 -0
  252. package/src/worker/tools/ui/clear-chat.test.ts +19 -0
  253. package/src/worker/tools/ui/get-current-time.test.ts +19 -0
  254. package/src/worker/tools/ui/javascript.test.ts +116 -0
  255. package/src/worker/tools/ui/list-components.test.ts +10 -0
  256. package/src/worker/tools/ui/list-tool-profiles.test.ts +42 -0
  257. package/src/worker/tools/ui/manage-tools.test.ts +34 -0
  258. package/src/worker/tools/ui/render-component.test.ts +346 -0
  259. package/src/worker/tools/ui/send-notification.test.ts +40 -0
  260. package/src/worker/tools/ui/utils/getAllowFullInternetAccess.test.ts +31 -0
  261. package/src/worker/tools/ui/utils/parseBooleanConfig.test.ts +25 -0
  262. package/src/worker/tools/workspace/attach-file.test.ts +84 -0
  263. package/src/worker/tools/workspace/copy-file.test.ts +69 -0
  264. package/src/worker/tools/workspace/create-directory.test.ts +40 -0
  265. package/src/worker/tools/workspace/delete-file.test.ts +73 -0
  266. package/src/worker/tools/workspace/diff-files.test.ts +94 -0
  267. package/src/worker/tools/workspace/list-files.test.ts +36 -0
  268. package/src/worker/tools/workspace/move-file.test.ts +66 -0
  269. package/src/worker/tools/workspace/open-file.test.ts +57 -0
  270. package/src/worker/tools/workspace/patch-file.test.ts +81 -0
  271. package/src/worker/tools/workspace/read-file.test.ts +116 -0
  272. package/src/worker/tools/workspace/send-file.test.ts +82 -0
  273. package/src/worker/tools/workspace/update-memory.test.ts +29 -0
  274. package/src/worker/tools/workspace/write-file.test.ts +28 -0
  275. package/src/worker/utils/rate-limit.test.ts +199 -0
  276. package/src/worker/utils/tool-state.test.ts +29 -0
  277. package/src/worker/utils/transformers-js-utils.test.ts +59 -0
  278. package/dist/public/config-AL77QAx4.js +0 -1
  279. package/dist/public/downloadGroupFile-BABv_1mJ.js +0 -1
  280. package/dist/public/file-viewer-CCSABMsE.js +0 -1
  281. package/dist/public/mcp-reconnect-DoicazQd.js +0 -1
  282. package/dist/public/orchestrator-4su7iyWj.js +0 -15
  283. package/dist/public/shadow-claw-dialog-ByyHgxM9.js +0 -1
  284. package/dist/public/shadow-claw-file-viewer-9Qkwc30T.js +0 -577
  285. package/dist/public/shadow-claw-files-aDyq6Hv7.js +0 -781
  286. package/dist/public/shadow-claw-storage-Cf31np81.js +0 -202
@@ -1385,6 +1385,111 @@ describe("shadow-claw-file-viewer", () => {
1385
1385
  expect(navigate).toHaveBeenCalledWith("/#Pages?path=docs/linked.md");
1386
1386
  });
1387
1387
 
1388
+ it("navigates relative image link in root via route URL", async () => {
1389
+ const component = new ShadowClawFileViewer();
1390
+ component.db = {} as any;
1391
+
1392
+ const navigate = jest.fn();
1393
+ Object.defineProperty(window as any, "navigation", {
1394
+ configurable: true,
1395
+ value: { navigate },
1396
+ });
1397
+
1398
+ (fileViewerStore as any).file = {
1399
+ path: "index.md",
1400
+ name: "index.md",
1401
+ kind: "text",
1402
+ content: "# index",
1403
+ };
1404
+
1405
+ const body = document.createElement("div");
1406
+ const link = document.createElement("a");
1407
+ link.setAttribute("href", "01M1A1R4Y708YJNPGV2NHQKYF7-IMG_0233.jpeg");
1408
+ body.appendChild(link);
1409
+
1410
+ const event = new MouseEvent("click", {
1411
+ bubbles: true,
1412
+ cancelable: true,
1413
+ button: 0,
1414
+ });
1415
+ Object.defineProperty(event, "target", {
1416
+ configurable: true,
1417
+ value: link,
1418
+ });
1419
+
1420
+ await component.handlePreviewLinkClick(event);
1421
+
1422
+ expect(event.defaultPrevented).toBe(true);
1423
+ expect(navigate).toHaveBeenCalledWith(
1424
+ "/files/test-group/01M1A1R4Y708YJNPGV2NHQKYF7-IMG_0233.jpeg",
1425
+ );
1426
+ });
1427
+
1428
+ it("rewrites relative asset hrefs in markdown preview HTML", () => {
1429
+ const component = new ShadowClawFileViewer();
1430
+ const inputHtml =
1431
+ '<p><a href="01M1A1R4Y708YJNPGV2NHQKYF7-IMG_0233.jpeg">image</a></p>';
1432
+ const rewritten = component.rewriteWorkspacePreviewHtml(
1433
+ inputHtml,
1434
+ "index.md",
1435
+ );
1436
+ expect(rewritten).toContain(
1437
+ 'href="/files/test-group/01M1A1R4Y708YJNPGV2NHQKYF7-IMG_0233.jpeg"',
1438
+ );
1439
+ });
1440
+
1441
+ it("rewrites relative asset hrefs when subpath base path is active", () => {
1442
+ document.querySelectorAll("base").forEach((el) => el.remove());
1443
+ const baseEl = document.createElement("base");
1444
+ baseEl.setAttribute("href", "/shadow-claw/");
1445
+ document.head.appendChild(baseEl);
1446
+ (globalThis as any).__applyBasePathCacheReset?.();
1447
+
1448
+ try {
1449
+ const component = new ShadowClawFileViewer();
1450
+ const inputHtml =
1451
+ '<p><a href="01M1A1R4Y708YJNPGV2NHQKYF7-IMG_0233.jpeg">image</a></p>';
1452
+ const rewritten = component.rewriteWorkspacePreviewHtml(
1453
+ inputHtml,
1454
+ "index.md",
1455
+ );
1456
+ expect(rewritten).toContain(
1457
+ 'href="/shadow-claw/files/test-group/01M1A1R4Y708YJNPGV2NHQKYF7-IMG_0233.jpeg"',
1458
+ );
1459
+ } finally {
1460
+ baseEl.remove();
1461
+ (globalThis as any).__applyBasePathCacheReset?.();
1462
+ }
1463
+ });
1464
+
1465
+ it("bindEventListeners delegates clicks within .file-content to handlePreviewLinkClick", () => {
1466
+ const component = new ShadowClawFileViewer();
1467
+ const root = document.createElement("div");
1468
+ const content = document.createElement("div");
1469
+ content.className = "file-content";
1470
+ const link = document.createElement("a");
1471
+ link.setAttribute("href", "01M1A1R4Y708YJNPGV2NHQKYF7-IMG_0233.jpeg");
1472
+ content.appendChild(link);
1473
+ root.appendChild(content);
1474
+ Object.defineProperty(component, "shadowRoot", {
1475
+ configurable: true,
1476
+ value: root,
1477
+ });
1478
+
1479
+ const spy = jest
1480
+ .spyOn(component, "handlePreviewLinkClick")
1481
+ .mockImplementation(async () => {});
1482
+
1483
+ component.bindEventListeners();
1484
+
1485
+ link.dispatchEvent(
1486
+ new MouseEvent("click", { bubbles: true, cancelable: true }),
1487
+ );
1488
+
1489
+ expect(spy).toHaveBeenCalledTimes(1);
1490
+ spy.mockRestore();
1491
+ });
1492
+
1388
1493
  it("resolves relative links against the opened file directory", () => {
1389
1494
  const component = new ShadowClawFileViewer();
1390
1495
 
@@ -1757,4 +1862,277 @@ describe("shadow-claw-file-viewer", () => {
1757
1862
  expect(resolved).toContain("data:image/png;base64,iVBORw==");
1758
1863
  });
1759
1864
  });
1865
+
1866
+ describe("syncIframeTheme & storage proxy bridge", () => {
1867
+ it("syncs theme and custom properties to previewFrameWindow", () => {
1868
+ const component = new ShadowClawFileViewer() as any;
1869
+ const mockPostMessage = jest.fn();
1870
+ component.previewFrameWindow = { postMessage: mockPostMessage };
1871
+
1872
+ component.syncIframeTheme();
1873
+
1874
+ expect(mockPostMessage).toHaveBeenCalledWith(
1875
+ expect.objectContaining({
1876
+ type: "shadow-claw-theme-update",
1877
+ theme: expect.any(String),
1878
+ customProperties: expect.any(Object),
1879
+ }),
1880
+ "*",
1881
+ );
1882
+ });
1883
+
1884
+ it("handles storage proxy requests (setItem, removeItem, getAllItems, idb-put, idb-get)", async () => {
1885
+ const component = new ShadowClawFileViewer() as any;
1886
+ const mockSource = { postMessage: jest.fn() };
1887
+
1888
+ (fileViewerStore as any).file = { path: "demo.html", name: "demo.html" };
1889
+
1890
+ // 1. setItem
1891
+ await component.handleStorageProxyRequest(
1892
+ mockSource,
1893
+ "req-1",
1894
+ "setItem",
1895
+ {
1896
+ key: "pref",
1897
+ value: "dark",
1898
+ },
1899
+ );
1900
+ expect(mockSource.postMessage).toHaveBeenCalledWith(
1901
+ expect.objectContaining({
1902
+ type: "shadow-claw-storage-proxy-result",
1903
+ requestId: "req-1",
1904
+ result: null,
1905
+ }),
1906
+ "*",
1907
+ );
1908
+
1909
+ // 2. idb-put
1910
+ await component.handleStorageProxyRequest(
1911
+ mockSource,
1912
+ "req-2",
1913
+ "idb-put",
1914
+ {
1915
+ dbName: "test-db",
1916
+ storeName: "items",
1917
+ key: "k1",
1918
+ value: { foo: "bar" },
1919
+ },
1920
+ );
1921
+ expect(mockSource.postMessage).toHaveBeenCalledWith(
1922
+ expect.objectContaining({
1923
+ type: "shadow-claw-storage-proxy-result",
1924
+ requestId: "req-2",
1925
+ result: "k1",
1926
+ }),
1927
+ "*",
1928
+ );
1929
+
1930
+ // 3. Unknown method sends error
1931
+ await component.handleStorageProxyRequest(
1932
+ mockSource,
1933
+ "req-3",
1934
+ "unknownMethod",
1935
+ {},
1936
+ );
1937
+ expect(mockSource.postMessage).toHaveBeenCalledWith(
1938
+ expect.objectContaining({
1939
+ type: "shadow-claw-storage-proxy-result",
1940
+ requestId: "req-3",
1941
+ error: expect.stringContaining("Unknown storage proxy method"),
1942
+ }),
1943
+ "*",
1944
+ );
1945
+ });
1946
+
1947
+ it("builds web share File instances from native files and binary buffers", () => {
1948
+ const component = new ShadowClawFileViewer() as any;
1949
+
1950
+ const native = new File(["content"], "test.txt", {
1951
+ type: "text/plain",
1952
+ });
1953
+ expect(component.buildWebShareFile({ nativeFile: native })).toBe(native);
1954
+
1955
+ const binaryFile = component.buildWebShareFile({
1956
+ name: "data.bin",
1957
+ binaryContent: new Uint8Array([1, 2, 3]),
1958
+ mimeType: "application/octet-stream",
1959
+ });
1960
+ expect(binaryFile).toBeInstanceOf(File);
1961
+ expect(binaryFile.name).toBe("data.bin");
1962
+
1963
+ expect(component.buildWebShareFile(null)).toBeNull();
1964
+ expect(component.buildWebShareFile({ binaryContent: null })).toBeNull();
1965
+ expect(
1966
+ component.buildWebShareFile({ binaryContent: new Uint8Array([]) }),
1967
+ ).toBeNull();
1968
+ });
1969
+
1970
+ it("cleans up resources and disconnects observers on disconnectedCallback", () => {
1971
+ const component = new ShadowClawFileViewer() as any;
1972
+ const disposeMock = jest.fn();
1973
+ component.broadcastProxy = { dispose: disposeMock };
1974
+ const disconnectMock = jest.fn();
1975
+ component.themeObserver = { disconnect: disconnectMock };
1976
+ const revokeMock = jest.spyOn(component, "revokeObjectUrl");
1977
+
1978
+ component.disconnectedCallback();
1979
+
1980
+ expect(disposeMock).toHaveBeenCalledTimes(1);
1981
+ expect(component.broadcastProxy).toBeNull();
1982
+ expect(disconnectMock).toHaveBeenCalledTimes(1);
1983
+ expect(component.themeObserver).toBeNull();
1984
+ expect(revokeMock).toHaveBeenCalledTimes(1);
1985
+ });
1986
+
1987
+ it("synchronizes iframe theme with document dark/light mode class", () => {
1988
+ const component = new ShadowClawFileViewer() as any;
1989
+ const mockPostMessage = jest.fn();
1990
+ component.previewFrameWindow = { postMessage: mockPostMessage };
1991
+
1992
+ document.documentElement.className = "dark-theme";
1993
+ component.syncIframeTheme();
1994
+
1995
+ expect(mockPostMessage).toHaveBeenCalledWith(
1996
+ expect.objectContaining({
1997
+ type: "shadow-claw-theme-update",
1998
+ }),
1999
+ "*",
2000
+ );
2001
+ });
2002
+
2003
+ it("handles openFolderInFilesView, openWorkspaceLink, and viewer dismissal", async () => {
2004
+ const component = new ShadowClawFileViewer() as any;
2005
+ component.db = {} as any;
2006
+
2007
+ // 1. openFolderInFilesView
2008
+ await component.openFolderInFilesView("docs/subfolder/");
2009
+ expect(orchestratorStore.setCurrentPath).toHaveBeenCalledWith(
2010
+ component.db,
2011
+ "docs/subfolder",
2012
+ );
2013
+ expect(fileViewerStore.closeFile).toHaveBeenCalled();
2014
+
2015
+ // 2. openWorkspaceLink with extension
2016
+ await component.openWorkspaceLink("notes.txt", "docs");
2017
+ expect(fileViewerStore.openFile).toHaveBeenCalledWith(
2018
+ component.db,
2019
+ expect.any(String),
2020
+ expect.any(String),
2021
+ );
2022
+
2023
+ // 3. canDismissViewer when not dirty
2024
+ component.isDirty = false;
2025
+ const canDismiss = await component.canDismissViewer();
2026
+ expect(canDismiss).toBe(true);
2027
+
2028
+ // 4. canDismissViewer when dirty
2029
+ component.isDirty = true;
2030
+ component.requestConfirmation = jest.fn<any>().mockResolvedValue(true);
2031
+ const canDismissDirty = await component.canDismissViewer();
2032
+ expect(canDismissDirty).toBe(true);
2033
+
2034
+ // 5. requestCloseViewer
2035
+ component.isDirty = false;
2036
+ const closed = await component.requestCloseViewer();
2037
+ expect(closed).toBe(true);
2038
+ expect(fileViewerStore.closeFile).toHaveBeenCalled();
2039
+ });
2040
+
2041
+ it("handles handleSave, handleShareFile, and toggleFullscreenMode", async () => {
2042
+ const { ShadowClawFileViewer } =
2043
+ await import("./shadow-claw-file-viewer.js");
2044
+ const { writeGroupFile } =
2045
+ await import("../../storage/writeGroupFile.js");
2046
+
2047
+ const component = new ShadowClawFileViewer() as any;
2048
+ document.body.appendChild(component);
2049
+ component.db = {} as any;
2050
+
2051
+ // 1. handleSave
2052
+ (fileViewerStore as any).file = {
2053
+ name: "test.txt",
2054
+ path: "docs/test.txt",
2055
+ kind: "text",
2056
+ content: "old content",
2057
+ groupId: "g1",
2058
+ };
2059
+ let editor = component.shadowRoot?.querySelector(".file-editor");
2060
+ if (!editor) {
2061
+ editor = document.createElement("textarea");
2062
+ editor.className = "file-editor";
2063
+ component.shadowRoot?.appendChild(editor);
2064
+ }
2065
+ editor.value = "new content";
2066
+
2067
+ await component.handleSave();
2068
+ expect(writeGroupFile).toHaveBeenCalledWith(
2069
+ component.db,
2070
+ "g1",
2071
+ "docs/test.txt",
2072
+ "new content",
2073
+ );
2074
+
2075
+ // 2. handleShareFile
2076
+ const mockShare = jest.fn<any>().mockResolvedValue(undefined);
2077
+ Object.defineProperty(navigator, "share", {
2078
+ value: mockShare,
2079
+ configurable: true,
2080
+ writable: true,
2081
+ });
2082
+ await component.handleShareFile();
2083
+ expect(mockShare).toHaveBeenCalledWith(
2084
+ expect.objectContaining({ title: "test.txt" }),
2085
+ );
2086
+
2087
+ // 3. renderBinaryPreview - empty
2088
+ const contentEl = document.createElement("div");
2089
+ component.renderBinaryPreview(contentEl, {
2090
+ name: "empty.bin",
2091
+ mimeType: "application/octet-stream",
2092
+ binaryContent: new Uint8Array(0),
2093
+ });
2094
+ expect(contentEl.textContent).toBe("Binary content unavailable.");
2095
+
2096
+ // 4. renderBinaryPreview - image
2097
+ (global.URL.createObjectURL as any) = jest
2098
+ .fn<any>()
2099
+ .mockReturnValue("blob:image-url");
2100
+ component.renderBinaryPreview(contentEl, {
2101
+ name: "img.jpg",
2102
+ mimeType: "image/jpeg",
2103
+ binaryContent: new Uint8Array([1, 2, 3]),
2104
+ });
2105
+ expect(contentEl.querySelector("img")).not.toBeNull();
2106
+
2107
+ // 5. renderBinaryPreview - video
2108
+ component.renderBinaryPreview(contentEl, {
2109
+ name: "clip.mp4",
2110
+ mimeType: "video/mp4",
2111
+ binaryContent: new Uint8Array([1, 2, 3]),
2112
+ });
2113
+ expect(contentEl.querySelector("video")).not.toBeNull();
2114
+
2115
+ // 6. renderBinaryPreview - audio
2116
+ component.renderBinaryPreview(contentEl, {
2117
+ name: "song.mp3",
2118
+ mimeType: "audio/mp3",
2119
+ binaryContent: new Uint8Array([1, 2, 3]),
2120
+ });
2121
+ expect(contentEl.querySelector("audio")).not.toBeNull();
2122
+
2123
+ // 7. renderBinaryPreview - iframe/pdf
2124
+ component.renderBinaryPreview(contentEl, {
2125
+ name: "doc.pdf",
2126
+ mimeType: "application/pdf",
2127
+ binaryContent: new Uint8Array([1, 2, 3]),
2128
+ });
2129
+ expect(contentEl.querySelector("iframe")).not.toBeNull();
2130
+
2131
+ // 8. resetContent
2132
+ component.resetContent();
2133
+ expect(component.isFullscreenMode).toBe(false);
2134
+
2135
+ document.body.removeChild(component);
2136
+ });
2137
+ });
1760
2138
  });
@@ -251,6 +251,11 @@ export class ShadowClawFileViewer extends ShadowClawElement {
251
251
  return;
252
252
  }
253
253
 
254
+ const content = root.querySelector(".file-content");
255
+ content?.addEventListener("click", (event: Event) => {
256
+ void this.handlePreviewLinkClick(event as MouseEvent);
257
+ });
258
+
254
259
  const modal = root.querySelector(".file-modal");
255
260
  modal?.addEventListener("cancel", (event: Event) => {
256
261
  event.preventDefault();
@@ -706,9 +711,8 @@ export class ShadowClawFileViewer extends ShadowClawElement {
706
711
 
707
712
  const current = fileViewerStore.file;
708
713
  const filePath = current?.path || current?.name || "";
709
- const routeDir = getFileRouteDirPath(
710
- orchestratorStore.activeGroupId,
711
- filePath,
714
+ const routeDir = applyBasePath(
715
+ getFileRouteDirPath(orchestratorStore.activeGroupId, filePath),
712
716
  );
713
717
  const resolved = resolveHrefAgainstRoute(
714
718
  payload.href,
@@ -1110,9 +1114,8 @@ export class ShadowClawFileViewer extends ShadowClawElement {
1110
1114
  return html;
1111
1115
  }
1112
1116
 
1113
- const routeDir = getFileRouteDirPath(
1114
- orchestratorStore.activeGroupId,
1115
- filePath,
1117
+ const routeDir = applyBasePath(
1118
+ getFileRouteDirPath(orchestratorStore.activeGroupId, filePath),
1116
1119
  );
1117
1120
  const parsed = new DOMParser().parseFromString(html, "text/html");
1118
1121
 
@@ -1509,9 +1512,8 @@ export class ShadowClawFileViewer extends ShadowClawElement {
1509
1512
  const current = fileViewerStore.file;
1510
1513
  const basePath = current?.path || current?.name || "";
1511
1514
  const href = link.getAttribute("href") || "";
1512
- const routeDir = getFileRouteDirPath(
1513
- orchestratorStore.activeGroupId,
1514
- basePath,
1515
+ const routeDir = applyBasePath(
1516
+ getFileRouteDirPath(orchestratorStore.activeGroupId, basePath),
1515
1517
  );
1516
1518
  const resolved = resolveHrefAgainstRoute(
1517
1519
  href,
@@ -361,7 +361,8 @@
361
361
 
362
362
  .files__new-label,
363
363
  .files__rename-label,
364
- .files__paste-rename-label {
364
+ .files__paste-rename-label,
365
+ .files__upload-conflict-rename-label {
365
366
  color: var(--shadow-claw-text-secondary);
366
367
  font-size: var(--shadow-claw-font-size-sm);
367
368
  font-weight: 600;
@@ -369,7 +370,8 @@
369
370
 
370
371
  .files__new-input,
371
372
  .files__rename-input,
372
- .files__paste-rename-input {
373
+ .files__paste-rename-input,
374
+ .files__upload-conflict-rename-input {
373
375
  background-color: var(--shadow-claw-bg-primary);
374
376
  border: 0.0625rem solid var(--shadow-claw-border-color);
375
377
  border-radius: var(--shadow-claw-radius-s);
@@ -379,7 +381,8 @@
379
381
  padding: 0.375rem 0.5rem;
380
382
  }
381
383
 
382
- .files__paste-rename-container {
384
+ .files__paste-rename-container,
385
+ .files__upload-conflict-rename-container {
383
386
  display: flex;
384
387
  flex-direction: column;
385
388
  gap: 0.5rem;
@@ -401,7 +404,8 @@
401
404
 
402
405
  .files__new-input:focus,
403
406
  .files__rename-input:focus,
404
- .files__paste-rename-input:focus {
407
+ .files__paste-rename-input:focus,
408
+ .files__upload-conflict-rename-input:focus {
405
409
  border-color: var(--shadow-claw-accent-primary);
406
410
  box-shadow: 0 0 0 0.125rem var(--shadow-claw-bg-tertiary);
407
411
  outline: none;
@@ -469,7 +473,8 @@
469
473
  opacity: 0.5;
470
474
  }
471
475
 
472
- .files__paste-dialog {
476
+ .files__paste-dialog,
477
+ .files__upload-conflict-dialog {
473
478
  background-color: var(--shadow-claw-bg-primary);
474
479
  border: 0.0625rem solid var(--shadow-claw-border-color);
475
480
  border-radius: var(--shadow-claw-radius-l, 1.5rem);
@@ -479,31 +484,36 @@
479
484
  width: calc(100vw - 2rem);
480
485
  }
481
486
 
482
- .files__paste-dialog::backdrop {
487
+ .files__paste-dialog::backdrop,
488
+ .files__upload-conflict-dialog::backdrop {
483
489
  background-color: rgba(0, 0, 0, 0.35);
484
490
  }
485
491
 
486
- .files__paste-form {
492
+ .files__paste-form,
493
+ .files__upload-conflict-form {
487
494
  display: flex;
488
495
  flex-direction: column;
489
496
  gap: 1rem;
490
497
  padding: 1.5rem;
491
498
  }
492
499
 
493
- .files__paste-title {
500
+ .files__paste-title,
501
+ .files__upload-conflict-title {
494
502
  font-size: 1.1rem;
495
503
  font-weight: 700;
496
504
  margin: 0;
497
505
  }
498
506
 
499
- .files__paste-message {
507
+ .files__paste-message,
508
+ .files__upload-conflict-message {
500
509
  color: var(--shadow-claw-text-secondary);
501
510
  font-size: var(--shadow-claw-font-size-sm);
502
511
  line-height: 1.4;
503
512
  margin: 0;
504
513
  }
505
514
 
506
- .files__paste-actions {
515
+ .files__paste-actions,
516
+ .files__upload-conflict-actions {
507
517
  display: flex;
508
518
  gap: 0.5rem;
509
519
  justify-content: flex-end;
@@ -514,7 +524,10 @@
514
524
  .files__paste-continue,
515
525
  .files__paste-apply,
516
526
  .files__paste-ok,
517
- .files__paste-overwrite {
527
+ .files__paste-overwrite,
528
+ .files__upload-conflict-cancel,
529
+ .files__upload-conflict-ok,
530
+ .files__upload-conflict-overwrite {
518
531
  background-color: transparent;
519
532
  border: 0.0625rem solid var(--shadow-claw-border-color);
520
533
  border-radius: var(--shadow-claw-radius-pill);
@@ -528,13 +541,16 @@
528
541
  transition: all 0.15s ease;
529
542
  }
530
543
 
531
- .files__paste-overwrite {
544
+ .files__paste-overwrite,
545
+ .files__upload-conflict-overwrite {
532
546
  border-color: var(--shadow-claw-error-color);
533
547
  color: var(--shadow-claw-error-color);
534
548
  }
535
549
 
536
550
  .files__paste-overwrite:hover,
537
- .files__paste-overwrite:focus-visible {
551
+ .files__paste-overwrite:focus-visible,
552
+ .files__upload-conflict-overwrite:hover,
553
+ .files__upload-conflict-overwrite:focus-visible {
538
554
  background-color: var(--shadow-claw-error-color);
539
555
  color: var(--shadow-claw-on-error, white);
540
556
  outline: none;
@@ -542,7 +558,8 @@
542
558
 
543
559
  .files__paste-continue,
544
560
  .files__paste-apply,
545
- .files__paste-ok {
561
+ .files__paste-ok,
562
+ .files__upload-conflict-ok {
546
563
  background-color: var(--shadow-claw-text-primary);
547
564
  border-color: var(--shadow-claw-text-primary);
548
565
  color: var(--shadow-claw-bg-primary);
@@ -553,7 +570,9 @@
553
570
  .files__paste-apply:hover,
554
571
  .files__paste-apply:focus-visible,
555
572
  .files__paste-ok:hover,
556
- .files__paste-ok:focus-visible {
573
+ .files__paste-ok:focus-visible,
574
+ .files__upload-conflict-ok:hover,
575
+ .files__upload-conflict-ok:focus-visible {
557
576
  background-color: var(--shadow-claw-accent-primary);
558
577
  border-color: var(--shadow-claw-accent-primary);
559
578
  color: var(--shadow-claw-on-primary);
@@ -561,7 +580,9 @@
561
580
  }
562
581
 
563
582
  .files__paste-cancel:hover,
564
- .files__paste-cancel:focus-visible {
583
+ .files__paste-cancel:focus-visible,
584
+ .files__upload-conflict-cancel:hover,
585
+ .files__upload-conflict-cancel:focus-visible {
565
586
  border-color: var(--shadow-claw-text-primary);
566
587
  box-shadow: var(--shadow-claw-shadow-md);
567
588
  outline: none;
@@ -570,7 +591,10 @@
570
591
  .files__paste-cancel:disabled,
571
592
  .files__paste-continue:disabled,
572
593
  .files__paste-apply:disabled,
573
- .files__paste-ok:disabled {
594
+ .files__paste-ok:disabled,
595
+ .files__upload-conflict-cancel:disabled,
596
+ .files__upload-conflict-ok:disabled,
597
+ .files__upload-conflict-overwrite:disabled {
574
598
  cursor: not-allowed;
575
599
  opacity: 0.6;
576
600
  }
@@ -172,6 +172,41 @@
172
172
  </div>
173
173
  </form>
174
174
  </shadow-claw-dialog>
175
+ <shadow-claw-dialog
176
+ dialog-class="files__upload-conflict-dialog"
177
+ dialog-aria-label="File upload conflict"
178
+ >
179
+ <form class="files__upload-conflict-form" method="dialog">
180
+ <h3 class="files__upload-conflict-title">File Already Exists</h3>
181
+ <p class="files__upload-conflict-message"></p>
182
+ <div class="files__upload-conflict-rename-container">
183
+ <label
184
+ class="files__upload-conflict-rename-label"
185
+ for="files-upload-conflict-name"
186
+ >New Name</label
187
+ >
188
+ <input
189
+ class="files__upload-conflict-rename-input"
190
+ id="files-upload-conflict-name"
191
+ name="files-upload-conflict-name"
192
+ type="text"
193
+ autocomplete="off"
194
+ required
195
+ />
196
+ </div>
197
+ <div class="files__upload-conflict-actions">
198
+ <button class="files__upload-conflict-cancel" type="button">
199
+ Cancel
200
+ </button>
201
+ <button class="files__upload-conflict-overwrite" type="button">
202
+ Overwrite
203
+ </button>
204
+ <button class="files__upload-conflict-ok" type="submit">
205
+ Rename
206
+ </button>
207
+ </div>
208
+ </form>
209
+ </shadow-claw-dialog>
175
210
  <div class="files__content">
176
211
  <div class="files__terminal-slot" data-terminal-slot hidden></div>
177
212
  <p class="files__drop-hint">Drop files here to upload</p>