@hienlh/ppm 0.17.56 → 0.18.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 (583) hide show
  1. package/.release-run.sh +5 -0
  2. package/CHANGELOG.md +21 -0
  3. package/CLAUDE.md +4 -0
  4. package/assets/skills/ppm/SKILL.md +1 -1
  5. package/assets/skills/ppm/references/http-api.md +16 -2
  6. package/bun.lock +3 -0
  7. package/dist/web/assets/{ai-resource-editor-DL011EDu.js → ai-resource-editor-BLYgg033.js} +1 -1
  8. package/dist/web/assets/{angular-html-D4YJUMLP.js → angular-html-jfMqUuRd.js} +1 -1
  9. package/dist/web/assets/{angular-ts-CUvq2h3X.js → angular-ts-wytn9PMm.js} +1 -1
  10. package/dist/web/assets/{api-client-COQtP-dB.js → api-client-B9OFv3nY.js} +1 -1
  11. package/dist/web/assets/api-client-CKo60P6G.js +1 -0
  12. package/dist/web/assets/{api-settings-Do0Z0IRN.js → api-settings-DGhshDRg.js} +1 -1
  13. package/dist/web/assets/api-settings-fdkhqLSF.js +1 -0
  14. package/dist/web/assets/{api-themes-DXi8sz-9.js → api-themes-okiyhhp9.js} +1 -1
  15. package/dist/web/assets/{apl-DSANFLx0.js → apl-i-ahKx6O.js} +1 -1
  16. package/dist/web/assets/architecture-PBZL5I3N-B4J2-O8Q.js +1 -0
  17. package/dist/web/assets/{astro-CXIotiNS.js → astro-DVBCJfG6.js} +1 -1
  18. package/dist/web/assets/{audio-preview-H16VO99g.js → audio-preview-y8XpCNrh.js} +1 -1
  19. package/dist/web/assets/{blade-BvrP2gPd.js → blade-BHlL8SqU.js} +1 -1
  20. package/dist/web/assets/{bsl-CGf6Xuyu.js → bsl-D-UOfR1F.js} +1 -1
  21. package/dist/web/assets/{cairo-BboZ9qIf.js → cairo-Bn6rucli.js} +1 -1
  22. package/dist/web/assets/can-open-in-ppm-BzVXoV-d.js +1 -0
  23. package/dist/web/assets/chat-tab-BcR-3_6R.js +13 -0
  24. package/dist/web/assets/clock-B1CPBp2g.js +1 -0
  25. package/dist/web/assets/{cobol-D39V6ukp.js → cobol-Byn9FYKu.js} +1 -1
  26. package/dist/web/assets/code-editor-CJ8g7dqm.js +10 -0
  27. package/dist/web/assets/{coffee-DTtK7kKi.js → coffee-DjBl4gm-.js} +1 -1
  28. package/dist/web/assets/{conflict-editor-B_wAb0Kb.js → conflict-editor-BP7JFZzG.js} +3 -3
  29. package/dist/web/assets/context-menu-BSE5yuMG.js +1 -0
  30. package/dist/web/assets/copy-Su9m2utd.js +1 -0
  31. package/dist/web/assets/{cpp-CnU9hfBq.js → cpp-CKzic-3E.js} +1 -1
  32. package/dist/web/assets/{crystal-DMjCL--0.js → crystal-Bkg-Ly_g.js} +1 -1
  33. package/dist/web/assets/csv-preview-xAwUDNat.js +2 -0
  34. package/dist/web/assets/{data-grid-overlay-editor-Bdc80TJ1.js → data-grid-overlay-editor-DMTCHIG9.js} +1 -1
  35. package/dist/web/assets/{database-viewer-awmnuDCt.js → database-viewer-BVLWYPa4.js} +1 -1
  36. package/dist/web/assets/dialog-D__LRQTn.js +1 -0
  37. package/dist/web/assets/diff-viewer-BheSOHfl.js +4 -0
  38. package/dist/web/assets/{docx-preview-DDw3duw9.js → docx-preview-CGlMqltP.js} +1 -1
  39. package/dist/web/assets/{edge-CTom35R_.js → edge-DVbZnd3Y.js} +1 -1
  40. package/dist/web/assets/{edit-diff-preview-D1TEK7Jz.js → edit-diff-preview-ClJAJwlL.js} +1 -1
  41. package/dist/web/assets/{elixir-BAX9HSOu.js → elixir-Dwi8Wu7W.js} +1 -1
  42. package/dist/web/assets/{elm-CpD28xsW.js → elm-DtIv-HXJ.js} +1 -1
  43. package/dist/web/assets/{erb-DHX2IIEs.js → erb-D3HEDz1x.js} +1 -1
  44. package/dist/web/assets/{erlang-B_etLcPG.js → erlang-2MjTCzGz.js} +1 -1
  45. package/dist/web/assets/{esm-PmlN1RTs.js → esm-BfqDE09P.js} +1 -1
  46. package/dist/web/assets/explorer-body-DE5339g9.js +2 -0
  47. package/dist/web/assets/explorer-window-content-BrTHAoXM.js +1 -0
  48. package/dist/web/assets/{extension-webview-DkTI51P3.js → extension-webview-DRq1tWC1.js} +1 -1
  49. package/dist/web/assets/{fortran-fixed-form-CpR0cN7O.js → fortran-fixed-form-BLyAtyPM.js} +1 -1
  50. package/dist/web/assets/{fsharp-DxITxZW5.js → fsharp-Bxbn_gbr.js} +1 -1
  51. package/dist/web/assets/{gdresource-DJ7IvROZ.js → gdresource-sFMY4D2Q.js} +1 -1
  52. package/dist/web/assets/{git-commit-sQznNP43.js → git-commit-BzB9Lio5.js} +1 -1
  53. package/dist/web/assets/git-log-panel-pjyOjchU.js +1 -0
  54. package/dist/web/assets/{git-rebase-DkHvejAR.js → git-rebase-CKSVd848.js} +1 -1
  55. package/dist/web/assets/gitGraph-HDMCJU4V-CtQ3yXN-.js +1 -0
  56. package/dist/web/assets/{glide-data-grid-Bpgp72Hg.js → glide-data-grid-LX3N4SsJ.js} +9 -9
  57. package/dist/web/assets/{glimmer-js-BwJnDfL-.js → glimmer-js-BY8aExAJ.js} +1 -1
  58. package/dist/web/assets/{glimmer-ts-Coz155XK.js → glimmer-ts-Bak54dBX.js} +1 -1
  59. package/dist/web/assets/{glsl-BED7ZmUZ.js → glsl-nR2eaJQe.js} +1 -1
  60. package/dist/web/assets/{graphql-B_KyE6oz.js → graphql-CGPPFAU6.js} +1 -1
  61. package/dist/web/assets/group-chat-tab-CVQfptkT.js +1 -0
  62. package/dist/web/assets/{hack-sxywdIAQ.js → hack-CXIMX-Ol.js} +1 -1
  63. package/dist/web/assets/{haml-Dts9JlPU.js → haml-tPY-9Pfm.js} +1 -1
  64. package/dist/web/assets/{handlebars-C9HXIMtj.js → handlebars-BuW1LT7T.js} +1 -1
  65. package/dist/web/assets/{html-2p0RvXi4.js → html-bgVcwZ-t.js} +1 -1
  66. package/dist/web/assets/{html-derivative-BiTPuOba.js → html-derivative-D6-IxTgn.js} +1 -1
  67. package/dist/web/assets/{http-CHd2gYPf.js → http-CJ1zyB-h.js} +1 -1
  68. package/dist/web/assets/{hurl-boMPgDm9.js → hurl-CmoT7l3T.js} +1 -1
  69. package/dist/web/assets/{hxml-BQvjiuBt.js → hxml-CztOsL8o.js} +1 -1
  70. package/dist/web/assets/{image-preview-Dwg0EuJ2.js → image-preview-BDySmiim.js} +1 -1
  71. package/dist/web/assets/index-XoDY9bc0.js +52 -0
  72. package/dist/web/assets/index-YCTraslE.css +2 -0
  73. package/dist/web/assets/info-3K5VOQVL-Cg0mIJn9.js +1 -0
  74. package/dist/web/assets/{jinja-B-zKiwlp.js → jinja-B1pq3jJq.js} +1 -1
  75. package/dist/web/assets/{jison-C2vJYjbJ.js → jison-D8B5pCwA.js} +1 -1
  76. package/dist/web/assets/{julia-DJ8MrQPA.js → julia-D0K3bwpU.js} +1 -1
  77. package/dist/web/assets/{just-BvQHq7VE.js → just-B9Jlddu-.js} +1 -1
  78. package/dist/web/assets/keybindings-store-BE1jVUZW.js +1 -0
  79. package/dist/web/assets/{latex-ChNeshUC.js → latex-BPJSSE6j.js} +1 -1
  80. package/dist/web/assets/{liquid-KNJEBOwW.js → liquid-lv10fo4U.js} +1 -1
  81. package/dist/web/assets/{lua-D8NNsLbC.js → lua-PNuaIBEF.js} +1 -1
  82. package/dist/web/assets/{markdown-renderer-C7dlPs2S.js → markdown-renderer-B8Kz5rvP.js} +3 -3
  83. package/dist/web/assets/markdown-renderer-Btync7yg.js +1 -0
  84. package/dist/web/assets/{marko-HGo5Qtz4.js → marko-Du3frD6X.js} +1 -1
  85. package/dist/web/assets/{mdc-omkodQB5.js → mdc-BeDVcFRl.js} +1 -1
  86. package/dist/web/assets/mobile-bottom-sheet-CzRXE6mY.js +1 -0
  87. package/dist/web/assets/mobile-explorer-sheet-CDU8xriw.js +1 -0
  88. package/dist/web/assets/{nextflow-Adq02pv-.js → nextflow-DkcXP71s.js} +1 -1
  89. package/dist/web/assets/{nginx-ByvWruGD.js → nginx-C5n8N_bt.js} +1 -1
  90. package/dist/web/assets/{nim-BrMojX4O.js → nim-CIhyfs-A.js} +1 -1
  91. package/dist/web/assets/notification-store-Bi7VHAy4.js +1 -0
  92. package/dist/web/assets/{number-overlay-editor-CunvWJnN.js → number-overlay-editor-B8P26qGC.js} +1 -1
  93. package/dist/web/assets/packet-RMMSAZCW-D79vAFR_.js +1 -0
  94. package/dist/web/assets/{panel-store-CpKvXNnm.js → panel-store-gbNJvZB_.js} +1 -1
  95. package/dist/web/assets/{pdf-preview-CEWYMxdp.js → pdf-preview-CtZWvujY.js} +1 -1
  96. package/dist/web/assets/{perl-Bd_0l-b4.js → perl-Cf31sZYE.js} +1 -1
  97. package/dist/web/assets/{php-D-DLTnWJ.js → php-Dzl_MDIW.js} +1 -1
  98. package/dist/web/assets/pie-UPGHQEXC-DYUOH68U.js +1 -0
  99. package/dist/web/assets/{postgres-viewer-D7PggslU.js → postgres-viewer-fqMUi0xS.js} +3 -3
  100. package/dist/web/assets/{project-store-D0ALup9Z.js → project-store-D1D1MN03.js} +1 -1
  101. package/dist/web/assets/{pug-D2aO-l2T.js → pug-CxSYHrB3.js} +1 -1
  102. package/dist/web/assets/{qml-CyQVzNXS.js → qml-DlEjGhwN.js} +1 -1
  103. package/dist/web/assets/radar-KQ55EAFF-B1eznNtd.js +1 -0
  104. package/dist/web/assets/{razor-8tvJXsO3.js → razor-KVt5uW14.js} +1 -1
  105. package/dist/web/assets/{rst-C4AZyQQl.js → rst-D5BwNTnw.js} +1 -1
  106. package/dist/web/assets/{ruby-O_z2NTLY.js → ruby-D5fejQwd.js} +1 -1
  107. package/dist/web/assets/{sas-BtnF4zaV.js → sas-Bj7B1CB2.js} +1 -1
  108. package/dist/web/assets/{scss-GAyp5euH.js → scss-BB0CUoCL.js} +1 -1
  109. package/dist/web/assets/settings-store-Bm7uL8VA.js +2 -0
  110. package/dist/web/assets/settings-tab-DxtBG0DW.js +1 -0
  111. package/dist/web/assets/{shaderlab-nM5YZwa5.js → shaderlab-BdjEZUMc.js} +1 -1
  112. package/dist/web/assets/{shellsession-D2OCI4Gv.js → shellsession-Bp1OA1rR.js} +1 -1
  113. package/dist/web/assets/{soy-DrLNhqfp.js → soy-DlYz3wsC.js} +1 -1
  114. package/dist/web/assets/sparkles-BvJ1EGWd.js +1 -0
  115. package/dist/web/assets/{sparql-CsM6F2Yv.js → sparql-CdfII5Jd.js} +1 -1
  116. package/dist/web/assets/{sql-query-editor-ZnKcxCyj.js → sql-query-editor-D2UxRvG7.js} +1 -1
  117. package/dist/web/assets/sqlite-viewer-CTBwzT-r.js +1 -0
  118. package/dist/web/assets/{stata-Bk3cgfXJ.js → stata-BvUA6GUW.js} +1 -1
  119. package/dist/web/assets/{surrealql-Dkz0DPK8.js → surrealql--DLNbTyI.js} +1 -1
  120. package/dist/web/assets/{svelte-23si1KV5.js → svelte-CBfSsG4n.js} +1 -1
  121. package/dist/web/assets/{system-monitor-tab-Ck3REHUg.js → system-monitor-tab-Bscf6tzK.js} +1 -1
  122. package/dist/web/assets/{tab-store-BQmDVh9c.js → tab-store-DnNnaAcE.js} +1 -1
  123. package/dist/web/assets/{templ-CPxlP4vF.js → templ-Dv8zpKZW.js} +1 -1
  124. package/dist/web/assets/{terminal-tab-D6tYDFSF.js → terminal-tab-CTKU5gaF.js} +3 -3
  125. package/dist/web/assets/{tex-ugJpgYPc.js → tex-CsrOiqh1.js} +1 -1
  126. package/dist/web/assets/tool-cards-xstpayaW.js +7 -0
  127. package/dist/web/assets/treemap-KZPCXAKY-SeI2TxLs.js +1 -0
  128. package/dist/web/assets/{ts-tags-BovP9TMu.js → ts-tags-3T98k4EJ.js} +1 -1
  129. package/dist/web/assets/{twig-B7VUm2Ca.js → twig-u6QZ45FH.js} +1 -1
  130. package/dist/web/assets/{use-blob-url-B3YovXF6.js → use-blob-url-BLRmhlZI.js} +1 -1
  131. package/dist/web/assets/{use-monaco-theme-wKbyNwIn.js → use-monaco-theme-DxYstgYa.js} +1 -1
  132. package/dist/web/assets/{vendor-mermaid-CYOBtZtD.js → vendor-mermaid-C977xNeE.js} +1 -1
  133. package/dist/web/assets/{vendor-ui-DMRGWRuc.js → vendor-ui-CXJtBULr.js} +1 -1
  134. package/dist/web/assets/{video-preview-DwrfenNA.js → video-preview-cfaoRxG3.js} +1 -1
  135. package/dist/web/assets/{vue-m75pWowk.js → vue-CxIsxtwn.js} +1 -1
  136. package/dist/web/assets/{vue-html-odHs7IMn.js → vue-html-BGL5qnuH.js} +1 -1
  137. package/dist/web/assets/{vue-vine-BbBB3V_G.js → vue-vine-DHPh_gMr.js} +1 -1
  138. package/dist/web/assets/{xml-BHzIcDWI.js → xml-C-vM2m7J.js} +1 -1
  139. package/dist/web/assets/{xsl-DTLhN7uu.js → xsl-DvTXNwuO.js} +1 -1
  140. package/dist/web/index.html +26 -23
  141. package/dist/web/sw.js +1 -1
  142. package/docs/codebase-summary.md +10 -1
  143. package/docs/design-guidelines.md +31 -0
  144. package/docs/project-roadmap.md +10 -0
  145. package/docs/system-architecture.md +59 -0
  146. package/package.json +2 -1
  147. package/src/providers/claude-agent-sdk.ts +6 -0
  148. package/src/server/index.ts +8 -0
  149. package/src/server/routes/files.ts +2 -2
  150. package/src/server/routes/fs-browse.ts +99 -74
  151. package/src/server/routes/fs-ops.ts +118 -0
  152. package/src/server/routes/fs-sqlite.ts +129 -0
  153. package/src/server/routes/host-info.ts +17 -0
  154. package/src/server/routes/settings.ts +2 -0
  155. package/src/services/download-token.service.ts +21 -5
  156. package/src/services/file.service.ts +8 -12
  157. package/src/services/fs-browse.service.ts +104 -161
  158. package/src/services/fs-ops/fs-content-disposition.ts +17 -0
  159. package/src/services/fs-ops/fs-core-ops.ts +149 -0
  160. package/src/services/fs-ops/fs-error-response.ts +24 -0
  161. package/src/services/fs-ops/fs-git-init.service.ts +23 -0
  162. package/src/services/fs-ops/fs-hidden-names.ts +17 -0
  163. package/src/services/fs-ops/fs-list-files.service.ts +68 -0
  164. package/src/services/fs-ops/fs-ops-copy-move.service.ts +60 -0
  165. package/src/services/fs-ops/fs-ops-mutate.service.ts +59 -0
  166. package/src/services/fs-ops/fs-ops-read-write.service.ts +70 -0
  167. package/src/services/fs-ops/fs-ops-stat.service.ts +109 -0
  168. package/src/services/fs-ops/fs-ops-trash.service.ts +111 -0
  169. package/src/services/fs-ops/fs-real-path.ts +44 -0
  170. package/src/services/fs-ops/sql-row-reader.ts +26 -0
  171. package/src/services/fs-ops/sql-statement-guard.ts +63 -0
  172. package/src/services/fs-path-guard.service.ts +196 -0
  173. package/src/services/host-info/apple-bookmark-decoder.ts +97 -0
  174. package/src/services/host-info/apple-plist-xml-parser.ts +107 -0
  175. package/src/services/host-info/concurrency-limit.ts +20 -0
  176. package/src/services/host-info/host-drives.ts +190 -0
  177. package/src/services/host-info/host-info.service.ts +171 -0
  178. package/src/services/host-info/host-known-folders.ts +180 -0
  179. package/src/services/host-info/pinned-linux-bookmarks.ts +89 -0
  180. package/src/services/host-info/pinned-macos-finder-favorites.ts +175 -0
  181. package/src/services/host-info/pinned-windows-quick-access.ts +87 -0
  182. package/src/services/host-info/spawn-runner.ts +41 -0
  183. package/src/services/jsonl-transcript-parser.ts +6 -0
  184. package/src/services/sqlite-types.ts +26 -0
  185. package/src/services/sqlite.service.ts +38 -29
  186. package/src/services/subagent-transcript-merger.ts +144 -0
  187. package/src/types/system.ts +50 -0
  188. package/src/web/app.tsx +19 -3
  189. package/src/web/components/editor/code-editor.tsx +3 -9
  190. package/src/web/components/explorer/file-tree.tsx +103 -21
  191. package/src/web/components/explorer/tree-node-context-menu.tsx +6 -1
  192. package/src/web/components/explorer/tree-node.tsx +34 -93
  193. package/src/web/components/explorer/use-file-upload-drag.ts +14 -9
  194. package/src/web/components/explorer/use-tree-row-dnd.ts +206 -0
  195. package/src/web/components/floating-window/default-window-chrome.tsx +71 -0
  196. package/src/web/components/floating-window/floating-window.tsx +167 -0
  197. package/src/web/components/floating-window/use-window-drag.ts +28 -0
  198. package/src/web/components/floating-window/use-window-gesture-context.ts +44 -0
  199. package/src/web/components/floating-window/use-window-keyboard.ts +55 -0
  200. package/src/web/components/floating-window/use-window-resize.ts +30 -0
  201. package/src/web/components/floating-window/window-chrome-contract.ts +38 -0
  202. package/src/web/components/floating-window/window-content-registry.ts +51 -0
  203. package/src/web/components/floating-window/window-geometry.ts +121 -0
  204. package/src/web/components/floating-window/window-layer.tsx +92 -0
  205. package/src/web/components/floating-window/window-persistence.ts +104 -0
  206. package/src/web/components/floating-window/window-resize-handles.tsx +81 -0
  207. package/src/web/components/floating-window/window-store-types.ts +20 -0
  208. package/src/web/components/floating-window/window-store.ts +192 -0
  209. package/src/web/components/layout/command-palette.tsx +10 -0
  210. package/src/web/components/layout/mobile-drawer.tsx +11 -2
  211. package/src/web/components/layout/nav-section-rail.tsx +7 -1
  212. package/src/web/components/os-explorer/actions/explorer-actions-clipboard.ts +186 -0
  213. package/src/web/components/os-explorer/actions/explorer-actions-mutate.ts +109 -0
  214. package/src/web/components/os-explorer/actions/explorer-actions-open.ts +67 -0
  215. package/src/web/components/os-explorer/actions/explorer-actions-transfer-helpers.ts +35 -0
  216. package/src/web/components/os-explorer/actions/use-explorer-actions.ts +205 -0
  217. package/src/web/components/os-explorer/can-open-in-ppm.ts +89 -0
  218. package/src/web/components/os-explorer/dnd/drag-depth-counter.ts +36 -0
  219. package/src/web/components/os-explorer/dnd/drag-ghost.ts +44 -0
  220. package/src/web/components/os-explorer/dnd/drop-target-decision.ts +46 -0
  221. package/src/web/components/os-explorer/dnd/drop-target-style.ts +7 -0
  222. package/src/web/components/os-explorer/dnd/entry-drag-payload.ts +115 -0
  223. package/src/web/components/os-explorer/dnd/entry-drag-state.ts +35 -0
  224. package/src/web/components/os-explorer/dnd/entry-drop-executor.ts +43 -0
  225. package/src/web/components/os-explorer/dnd/use-drag-auto-scroll.ts +70 -0
  226. package/src/web/components/os-explorer/dnd/use-drop-transfer.tsx +82 -0
  227. package/src/web/components/os-explorer/dnd/use-entry-drag-source.ts +73 -0
  228. package/src/web/components/os-explorer/dnd/use-entry-drop-target.ts +150 -0
  229. package/src/web/components/os-explorer/dnd/use-entry-row-dnd.ts +66 -0
  230. package/src/web/components/os-explorer/dnd/use-path-drop-target.ts +30 -0
  231. package/src/web/components/os-explorer/explorer-body.tsx +153 -0
  232. package/src/web/components/os-explorer/explorer-breadcrumb.tsx +108 -0
  233. package/src/web/components/os-explorer/explorer-context-menu.tsx +131 -0
  234. package/src/web/components/os-explorer/explorer-dialogs.tsx +103 -0
  235. package/src/web/components/os-explorer/explorer-modal-shell.tsx +60 -0
  236. package/src/web/components/os-explorer/explorer-pins-store.ts +63 -0
  237. package/src/web/components/os-explorer/explorer-sidebar.tsx +198 -0
  238. package/src/web/components/os-explorer/explorer-sort-menu.tsx +68 -0
  239. package/src/web/components/os-explorer/explorer-status-bar.tsx +35 -0
  240. package/src/web/components/os-explorer/explorer-store.ts +179 -0
  241. package/src/web/components/os-explorer/explorer-toolbar.tsx +139 -0
  242. package/src/web/components/os-explorer/explorer-window-content.tsx +36 -0
  243. package/src/web/components/os-explorer/format-file-meta.ts +74 -0
  244. package/src/web/components/os-explorer/icons/file-type-icon-map.ts +106 -0
  245. package/src/web/components/os-explorer/icons/file-type-icon.tsx +56 -0
  246. package/src/web/components/os-explorer/icons/thumbnail-fetch-semaphore.ts +44 -0
  247. package/src/web/components/os-explorer/icons/thumbnail-image.tsx +124 -0
  248. package/src/web/components/os-explorer/mobile/mobile-bottom-toolbar.tsx +102 -0
  249. package/src/web/components/os-explorer/mobile/mobile-explorer-sheet.tsx +61 -0
  250. package/src/web/components/os-explorer/mobile/mobile-explorer-top-bar.tsx +41 -0
  251. package/src/web/components/os-explorer/mobile/mobile-more-sheet.tsx +117 -0
  252. package/src/web/components/os-explorer/mobile/mobile-places-sheet.tsx +120 -0
  253. package/src/web/components/os-explorer/mobile/mobile-places-strip.tsx +109 -0
  254. package/src/web/components/os-explorer/mobile/mobile-tap-action.ts +19 -0
  255. package/src/web/components/os-explorer/mobile/use-mobile-row-tap.ts +50 -0
  256. package/src/web/components/os-explorer/open-explorer.ts +35 -0
  257. package/src/web/components/os-explorer/properties-dialog.tsx +99 -0
  258. package/src/web/components/os-explorer/skins/explorer-skin-chrome.tsx +17 -0
  259. package/src/web/components/os-explorer/skins/folder-icon-macos.tsx +42 -0
  260. package/src/web/components/os-explorer/skins/folder-icon-windows.tsx +25 -0
  261. package/src/web/components/os-explorer/skins/macos-skin.ts +14 -0
  262. package/src/web/components/os-explorer/skins/macos-window-chrome.tsx +69 -0
  263. package/src/web/components/os-explorer/skins/skin-types.ts +40 -0
  264. package/src/web/components/os-explorer/skins/skins.css +25 -0
  265. package/src/web/components/os-explorer/skins/use-explorer-skin.ts +21 -0
  266. package/src/web/components/os-explorer/skins/windows-skin.ts +11 -0
  267. package/src/web/components/os-explorer/skins/windows-window-chrome.tsx +56 -0
  268. package/src/web/components/os-explorer/sort-and-filter-entries.ts +52 -0
  269. package/src/web/components/os-explorer/sort-options.ts +29 -0
  270. package/src/web/components/os-explorer/toolbar-icon-button.ts +18 -0
  271. package/src/web/components/os-explorer/use-coarse-long-press.ts +97 -0
  272. package/src/web/components/os-explorer/use-explorer-keyboard.ts +124 -0
  273. package/src/web/components/os-explorer/use-explorer-navigation.ts +155 -0
  274. package/src/web/components/os-explorer/use-explorer-open-state.ts +67 -0
  275. package/src/web/components/os-explorer/use-host-info.ts +85 -0
  276. package/src/web/components/os-explorer/views/column-row.tsx +126 -0
  277. package/src/web/components/os-explorer/views/column-view-column.tsx +112 -0
  278. package/src/web/components/os-explorer/views/column-view-mobile.tsx +98 -0
  279. package/src/web/components/os-explorer/views/column-view-preview.tsx +70 -0
  280. package/src/web/components/os-explorer/views/column-view.tsx +204 -0
  281. package/src/web/components/os-explorer/views/compose-interactive-row-props.ts +77 -0
  282. package/src/web/components/os-explorer/views/explorer-view-registry.tsx +55 -0
  283. package/src/web/components/os-explorer/views/icons-view-tile.tsx +158 -0
  284. package/src/web/components/os-explorer/views/icons-view.tsx +188 -0
  285. package/src/web/components/os-explorer/views/inline-name-input.tsx +60 -0
  286. package/src/web/components/os-explorer/views/list-row.tsx +162 -0
  287. package/src/web/components/os-explorer/views/list-view.tsx +177 -0
  288. package/src/web/components/os-explorer/views/use-column-view-state.ts +158 -0
  289. package/src/web/components/os-explorer/views/use-entry-selection.ts +178 -0
  290. package/src/web/components/settings/settings-tab.tsx +29 -2
  291. package/src/web/components/sqlite/sqlite-viewer.tsx +5 -2
  292. package/src/web/components/sqlite/use-sqlite.ts +7 -2
  293. package/src/web/components/ui/adaptive-context-menu.tsx +57 -1
  294. package/src/web/components/ui/file-browser-picker.tsx +1 -18
  295. package/src/web/hooks/use-chat.ts +22 -4
  296. package/src/web/lib/file-download.ts +6 -4
  297. package/src/web/lib/fs-api.ts +185 -0
  298. package/src/web/stores/file-store.ts +38 -1
  299. package/src/web/stores/settings-store.ts +14 -0
  300. package/src/web/styles/globals.css +1 -0
  301. package/dist/web/assets/api-client-RtNXvS0P.js +0 -1
  302. package/dist/web/assets/api-settings-De--HNHp.js +0 -1
  303. package/dist/web/assets/architecture-PBZL5I3N-DVRYSsQn.js +0 -1
  304. package/dist/web/assets/chat-tab-D2nZCpAo.js +0 -13
  305. package/dist/web/assets/code-editor-DqQy2Qm8.js +0 -10
  306. package/dist/web/assets/context-menu-DIiZdfbE.js +0 -1
  307. package/dist/web/assets/csv-preview-BL0I3CKz.js +0 -2
  308. package/dist/web/assets/dialog-HHdNInEH.js +0 -1
  309. package/dist/web/assets/diff-viewer-_yQtutHR.js +0 -4
  310. package/dist/web/assets/git-log-panel-NkghB_rO.js +0 -1
  311. package/dist/web/assets/gitGraph-HDMCJU4V-CutkUNY_.js +0 -1
  312. package/dist/web/assets/group-chat-tab-CQOVBmVp.js +0 -1
  313. package/dist/web/assets/index-CJ0Wp_rW.js +0 -50
  314. package/dist/web/assets/index-CVJmlKhp.css +0 -2
  315. package/dist/web/assets/info-3K5VOQVL-BpNjgbj9.js +0 -1
  316. package/dist/web/assets/keybindings-store-ClNYe9vD.js +0 -1
  317. package/dist/web/assets/markdown-renderer-CwpP5Ola.js +0 -1
  318. package/dist/web/assets/notification-store-DwAhNzTy.js +0 -1
  319. package/dist/web/assets/packet-RMMSAZCW-D5IUZ66-.js +0 -1
  320. package/dist/web/assets/pie-UPGHQEXC-CM29eKDR.js +0 -1
  321. package/dist/web/assets/radar-KQ55EAFF-QtJGaSGt.js +0 -1
  322. package/dist/web/assets/settings-store-B3LfpCPa.js +0 -2
  323. package/dist/web/assets/settings-tab-CeSJQIfF.js +0 -1
  324. package/dist/web/assets/sparkles-B3DmG8PJ.js +0 -1
  325. package/dist/web/assets/sqlite-viewer-8fk_wut8.js +0 -1
  326. package/dist/web/assets/tool-cards-C3ka1Y7I.js +0 -7
  327. package/dist/web/assets/treemap-KZPCXAKY-CjUqpVnS.js +0 -1
  328. /package/dist/web/assets/{abap-BM5PMK0-.js → abap-C1K0-wzc.js} +0 -0
  329. /package/dist/web/assets/{actionscript-3-CML-MTNy.js → actionscript-3-CJhQVjLA.js} +0 -0
  330. /package/dist/web/assets/{ada-DzO1tMqV.js → ada-Bt8r9fME.js} +0 -0
  331. /package/dist/web/assets/{andromeeda-C3nkvRhj.js → andromeeda-DF1I8qi3.js} +0 -0
  332. /package/dist/web/assets/{apache-Dg7oWQbs.js → apache-CnlleE6P.js} +0 -0
  333. /package/dist/web/assets/{apex-3O2170Ju.js → apex-j0C6Xmk6.js} +0 -0
  334. /package/dist/web/assets/{applescript-5AlzBqQO.js → applescript-DO7AnO8U.js} +0 -0
  335. /package/dist/web/assets/{apply-theme-BSJDABU3.js → apply-theme-CMQXnUAa.js} +0 -0
  336. /package/dist/web/assets/{ara-BGtOX5Hn.js → ara-i83AydZj.js} +0 -0
  337. /package/dist/web/assets/{asciidoc-CIsC4kkq.js → asciidoc-DbBA6-sF.js} +0 -0
  338. /package/dist/web/assets/{asm-p1-PUkh3.js → asm-B5S47Rgr.js} +0 -0
  339. /package/dist/web/assets/{aurora-x-BPgGL50S.js → aurora-x-De_IaSKw.js} +0 -0
  340. /package/dist/web/assets/{awk-Cj93pRL8.js → awk-CfyLHXqS.js} +0 -0
  341. /package/dist/web/assets/{ayu-dark-C50MYKhw.js → ayu-dark-DTLdZ1Si.js} +0 -0
  342. /package/dist/web/assets/{ayu-light-DyIp0Idy.js → ayu-light-DTOfkzhu.js} +0 -0
  343. /package/dist/web/assets/{ayu-mirage-BAdm8pKU.js → ayu-mirage-DsAECCMv.js} +0 -0
  344. /package/dist/web/assets/{ballerina-DUO9mwyV.js → ballerina-CumGp9A7.js} +0 -0
  345. /package/dist/web/assets/{bat-Cg8hlSct.js → bat-Ca2SBbYN.js} +0 -0
  346. /package/dist/web/assets/{beancount-f8f3BBqB.js → beancount-CqyXBwwF.js} +0 -0
  347. /package/dist/web/assets/{berry-BqVQYZZr.js → berry-yl-kJr_H.js} +0 -0
  348. /package/dist/web/assets/{bibtex-C_q8ygAM.js → bibtex-DoFFszjV.js} +0 -0
  349. /package/dist/web/assets/{bicep-DWS-2uWR.js → bicep-CWPi4nfK.js} +0 -0
  350. /package/dist/web/assets/{bird2-C0pvipi_.js → bird2-DXew-ut0.js} +0 -0
  351. /package/dist/web/assets/{bot-E9mcVZnr.js → bot-BHXjpQ7x.js} +0 -0
  352. /package/dist/web/assets/{c-BQFvJ6Ux.js → c-clbySDvd.js} +0 -0
  353. /package/dist/web/assets/{c3-Da53KeG6.js → c3-B65jvSM1.js} +0 -0
  354. /package/dist/web/assets/{cadence-B5X2vWm3.js → cadence-Cm8tQTHN.js} +0 -0
  355. /package/dist/web/assets/{catppuccin-frappe-Dm3rVY3b.js → catppuccin-frappe-DLeGslh_.js} +0 -0
  356. /package/dist/web/assets/{catppuccin-latte-ZxS-ga_K.js → catppuccin-latte-CR5GeHws.js} +0 -0
  357. /package/dist/web/assets/{catppuccin-macchiato-B0bAzXh0.js → catppuccin-macchiato-p-XgjxhP.js} +0 -0
  358. /package/dist/web/assets/{catppuccin-mocha-BeMC5hqi.js → catppuccin-mocha-B8QOAZLl.js} +0 -0
  359. /package/dist/web/assets/{chevron-right-DeUue2aK.js → chevron-right-CtcHmZg9.js} +0 -0
  360. /package/dist/web/assets/{circle-question-mark-PaJgx-3d.js → circle-question-mark-EVgisosn.js} +0 -0
  361. /package/dist/web/assets/{clarity-BRdirlsw.js → clarity-DPulbvKH.js} +0 -0
  362. /package/dist/web/assets/{clojure-pSLH-KQp.js → clojure-DFuWS20u.js} +0 -0
  363. /package/dist/web/assets/{cmake-DsZu4U4F.js → cmake-DejCqcu4.js} +0 -0
  364. /package/dist/web/assets/{code-TekEnDFM.js → code-Dupqw5Fr.js} +0 -0
  365. /package/dist/web/assets/{codeowners-CtkKTnsH.js → codeowners-BwAyqWIH.js} +0 -0
  366. /package/dist/web/assets/{codeql-BpP1PDrj.js → codeql-DgSwFjqI.js} +0 -0
  367. /package/dist/web/assets/{common-lisp-BX1XgHTt.js → common-lisp-CxIVJCtf.js} +0 -0
  368. /package/dist/web/assets/{coq-D2bX6-8g.js → coq-DLSjSnmf.js} +0 -0
  369. /package/dist/web/assets/{csharp-DcR97P0D.js → csharp-C1HHzXBp.js} +0 -0
  370. /package/dist/web/assets/{css-CIjciIIk.js → css-VXsMY--y.js} +0 -0
  371. /package/dist/web/assets/{csv-DW48Z5d-.js → csv-BEbcJdHz.js} +0 -0
  372. /package/dist/web/assets/{csv-parser-DVHBcvuE.js → csv-parser-DHvoTjMF.js} +0 -0
  373. /package/dist/web/assets/{cue-Byh1xHko.js → cue-BkDnFqUD.js} +0 -0
  374. /package/dist/web/assets/{cypher-TVlMMWma.js → cypher-1cMqojSQ.js} +0 -0
  375. /package/dist/web/assets/{d-CxaScvxf.js → d-BnYjRXtH.js} +0 -0
  376. /package/dist/web/assets/{dark-plus-sn-FFTT1.js → dark-plus-XfbaH7kZ.js} +0 -0
  377. /package/dist/web/assets/{dart-BmpakMe-.js → dart-Ca7s-8hK.js} +0 -0
  378. /package/dist/web/assets/{data-grid-types-BuMHLKXV.js → data-grid-types-DP-ZexEN.js} +0 -0
  379. /package/dist/web/assets/{database-COYAMZaw.js → database-Dj0DxgtX.js} +0 -0
  380. /package/dist/web/assets/{dax-B1aRUMXT.js → dax-CmuOw-mY.js} +0 -0
  381. /package/dist/web/assets/{desktop-C6lD62ar.js → desktop-C2gJ6Ovb.js} +0 -0
  382. /package/dist/web/assets/{diff-CupGmT1a.js → diff-BCEo1Ej7.js} +0 -0
  383. /package/dist/web/assets/{dist-Bi4gRyhr.js → dist-CipvJy9w.js} +0 -0
  384. /package/dist/web/assets/{dist-m_oJqIDk.js → dist-D__U4ZJz.js} +0 -0
  385. /package/dist/web/assets/{docker-DjDr3TJF.js → docker-DN2jlyAh.js} +0 -0
  386. /package/dist/web/assets/{dotenv-QeT1SLTv.js → dotenv-NExMY2kS.js} +0 -0
  387. /package/dist/web/assets/{dracula-yI5kpKp5.js → dracula-B9lbE01y.js} +0 -0
  388. /package/dist/web/assets/{dracula-soft-CYF6cJce.js → dracula-soft-CMBBfKF_.js} +0 -0
  389. /package/dist/web/assets/{dream-maker-TBDKiHrV.js → dream-maker-DEyDwFsd.js} +0 -0
  390. /package/dist/web/assets/{emacs-lisp-B7aZTnJ_.js → emacs-lisp-D_qP2zS6.js} +0 -0
  391. /package/dist/web/assets/{everforest-dark-CN77B1NJ.js → everforest-dark-DbHa2U-p.js} +0 -0
  392. /package/dist/web/assets/{everforest-light-CyFxGMIs.js → everforest-light-BISkJchP.js} +0 -0
  393. /package/dist/web/assets/{fennel-C6ktGUzp.js → fennel-6B22SfJR.js} +0 -0
  394. /package/dist/web/assets/{file-exclamation-point-DBEC-Kst.js → file-exclamation-point-D-ZORzYB.js} +0 -0
  395. /package/dist/web/assets/{fish-Vr62C2vP.js → fish-CubIIvJD.js} +0 -0
  396. /package/dist/web/assets/{fluent-CH3IJUki.js → fluent-DF55zQ09.js} +0 -0
  397. /package/dist/web/assets/{fortran-free-form-BN7VmmxZ.js → fortran-free-form-DvOu8jAt.js} +0 -0
  398. /package/dist/web/assets/{gdscript-BYdRNu_P.js → gdscript-DCPxdLpq.js} +0 -0
  399. /package/dist/web/assets/{gdshader-C2XOq90k.js → gdshader-CTZ381FO.js} +0 -0
  400. /package/dist/web/assets/{genie-B4hkIXNU.js → genie-C_QDtvgU.js} +0 -0
  401. /package/dist/web/assets/{gherkin-CW9NH2DW.js → gherkin-BcvK42tF.js} +0 -0
  402. /package/dist/web/assets/{github-dark-BWCf99kM.js → github-dark-C9afs_4w.js} +0 -0
  403. /package/dist/web/assets/{github-dark-default-DZ1m_6OP.js → github-dark-default-CKGVrl3Z.js} +0 -0
  404. /package/dist/web/assets/{github-dark-dimmed-CUXSQ8bS.js → github-dark-dimmed-C3dQZ_LC.js} +0 -0
  405. /package/dist/web/assets/{github-dark-high-contrast-B6BvU8H-.js → github-dark-high-contrast-DW78QukY.js} +0 -0
  406. /package/dist/web/assets/{github-light-Ch-E92cy.js → github-light-BnP2dznS.js} +0 -0
  407. /package/dist/web/assets/{github-light-default-CKFslV7i.js → github-light-default-C2fPZzPG.js} +0 -0
  408. /package/dist/web/assets/{github-light-high-contrast-DiJVeRlZ.js → github-light-high-contrast-BzIuUFR2.js} +0 -0
  409. /package/dist/web/assets/{gleam-D4dP7V5M.js → gleam-BpeKwEIO.js} +0 -0
  410. /package/dist/web/assets/{gn-DuO2D2eW.js → gn-nUb_Mit0.js} +0 -0
  411. /package/dist/web/assets/{gnuplot-Df_8pFbI.js → gnuplot-D7I0v20p.js} +0 -0
  412. /package/dist/web/assets/{go-BL16cMJO.js → go-D7Qqska7.js} +0 -0
  413. /package/dist/web/assets/{groovy-rfHeCRqy.js → groovy-DS-pne5-.js} +0 -0
  414. /package/dist/web/assets/{gruvbox-dark-hard-CLbDN-E3.js → gruvbox-dark-hard-C0W01fsq.js} +0 -0
  415. /package/dist/web/assets/{gruvbox-dark-medium-Df0L0qje.js → gruvbox-dark-medium-Bqo6fQbg.js} +0 -0
  416. /package/dist/web/assets/{gruvbox-dark-soft-DV6TAcen.js → gruvbox-dark-soft-D-XSM_fu.js} +0 -0
  417. /package/dist/web/assets/{gruvbox-light-hard-CWXUzp9b.js → gruvbox-light-hard-IVAwMJRl.js} +0 -0
  418. /package/dist/web/assets/{gruvbox-light-medium-BOHMe1-7.js → gruvbox-light-medium-38ATiG7i.js} +0 -0
  419. /package/dist/web/assets/{gruvbox-light-soft-DrrS2sdV.js → gruvbox-light-soft-DnZr3c_0.js} +0 -0
  420. /package/dist/web/assets/{haskell-DybKb9uW.js → haskell-uQcywkDH.js} +0 -0
  421. /package/dist/web/assets/{haxe-DLRwZxmt.js → haxe-c6bI4_GT.js} +0 -0
  422. /package/dist/web/assets/{hcl-CusCzwOk.js → hcl-CQXTiEXq.js} +0 -0
  423. /package/dist/web/assets/{hjson-BygWIJ-3.js → hjson-ChETf2bz.js} +0 -0
  424. /package/dist/web/assets/{hlsl-DCKvuEyZ.js → hlsl-BMC4JGoE.js} +0 -0
  425. /package/dist/web/assets/{horizon-DSOVAzyH.js → horizon-BusFlhLW.js} +0 -0
  426. /package/dist/web/assets/{horizon-bright-DKfVjGj_.js → horizon-bright-2oD0Mz7W.js} +0 -0
  427. /package/dist/web/assets/{houston-Ddm8AgpV.js → houston-DdbbjqFg.js} +0 -0
  428. /package/dist/web/assets/{hy-DzZa6alE.js → hy-D_SOuqw9.js} +0 -0
  429. /package/dist/web/assets/{image-gallery-CObzTisL.js → image-gallery-BWFpZAAP.js} +0 -0
  430. /package/dist/web/assets/{imba-aiXalPnK.js → imba-BxLOHO5I.js} +0 -0
  431. /package/dist/web/assets/{ini-CAvrVa5R.js → ini-ox-CFcg0.js} +0 -0
  432. /package/dist/web/assets/{java-PRMSQp24.js → java-Bzo8yIal.js} +0 -0
  433. /package/dist/web/assets/{javascript-DQR6GQKs.js → javascript-DHuz9VXn.js} +0 -0
  434. /package/dist/web/assets/{json-uWUKqOxq.js → json-0_y80FD5.js} +0 -0
  435. /package/dist/web/assets/{json5-R_LXJXL0.js → json5-CEQ50pGM.js} +0 -0
  436. /package/dist/web/assets/{jsonc-tC5zvrPO.js → jsonc-CfrLEAKA.js} +0 -0
  437. /package/dist/web/assets/{jsonl-BAwnWh7U.js → jsonl-fKbrANC2.js} +0 -0
  438. /package/dist/web/assets/{jsonnet-CPj4HCRm.js → jsonnet-DKbkPaQ2.js} +0 -0
  439. /package/dist/web/assets/{jssm-B7HswxOM.js → jssm-Dxxx1Hqu.js} +0 -0
  440. /package/dist/web/assets/{jsx-z4TGELE1.js → jsx-DlJL-Oj3.js} +0 -0
  441. /package/dist/web/assets/{kanagawa-dragon-BpeLlraV.js → kanagawa-dragon-CwN4SV1c.js} +0 -0
  442. /package/dist/web/assets/{kanagawa-lotus-CLnIPAGs.js → kanagawa-lotus-BHltayiE.js} +0 -0
  443. /package/dist/web/assets/{kanagawa-wave-BglPniBb.js → kanagawa-wave-D-KXPjw6.js} +0 -0
  444. /package/dist/web/assets/{kdl-B6blIqLq.js → kdl-DvtoJdFo.js} +0 -0
  445. /package/dist/web/assets/{kotlin-DVAEYiwY.js → kotlin-CTS4MLcx.js} +0 -0
  446. /package/dist/web/assets/{kusto-Ba87977j.js → kusto-HtSE_sKm.js} +0 -0
  447. /package/dist/web/assets/{laserwave-C-NuzoMp.js → laserwave-DaXZG9ge.js} +0 -0
  448. /package/dist/web/assets/{lean-CJ_KLkrJ.js → lean-BYtGUfWx.js} +0 -0
  449. /package/dist/web/assets/{less-Dfn9YC_2.js → less-BoxmU_Mk.js} +0 -0
  450. /package/dist/web/assets/{lib-DTaN9riD.js → lib-Bpbn00xM.js} +0 -0
  451. /package/dist/web/assets/{libesm-DdtEjhIi.js → libesm-tR4XlhMj.js} +0 -0
  452. /package/dist/web/assets/{light-plus-B3BLlguG.js → light-plus-Bt5CyUuo.js} +0 -0
  453. /package/dist/web/assets/{llvm-6UDP3NQb.js → llvm-CSdk9q9V.js} +0 -0
  454. /package/dist/web/assets/{log-QW3dqr1D.js → log-B-kp_3CL.js} +0 -0
  455. /package/dist/web/assets/{logo-DUinJheI.js → logo-DdIsXFDO.js} +0 -0
  456. /package/dist/web/assets/{luau-B1vcBzl8.js → luau-DUBpLlzQ.js} +0 -0
  457. /package/dist/web/assets/{make-CqNL10gS.js → make-B3JVjoW_.js} +0 -0
  458. /package/dist/web/assets/{markdown-BQNMNiYS.js → markdown-Seon0vHs.js} +0 -0
  459. /package/dist/web/assets/{material-theme-rbV8EefR.js → material-theme-Nq-ChX1P.js} +0 -0
  460. /package/dist/web/assets/{material-theme-darker-BZrskvoo.js → material-theme-darker-CPV4ayXJ.js} +0 -0
  461. /package/dist/web/assets/{material-theme-lighter-B0CBBjBw.js → material-theme-lighter-0kyjYqEd.js} +0 -0
  462. /package/dist/web/assets/{material-theme-ocean-CX_CHnQq.js → material-theme-ocean-DGpAp5co.js} +0 -0
  463. /package/dist/web/assets/{material-theme-palenight-ztiOCPbw.js → material-theme-palenight-B0AUn0NG.js} +0 -0
  464. /package/dist/web/assets/{matlab-tHQDNC9G.js → matlab-C2waytxs.js} +0 -0
  465. /package/dist/web/assets/{mdx-D4kZ6c6G.js → mdx-DAis6R6I.js} +0 -0
  466. /package/dist/web/assets/{mermaid-DlR4U4Pe.js → mermaid-_rnT2YGD.js} +0 -0
  467. /package/dist/web/assets/{min-dark-BX-xHAD2.js → min-dark-BSSjchUB.js} +0 -0
  468. /package/dist/web/assets/{min-light-Bxw0_Gqq.js → min-light-DomAFQsa.js} +0 -0
  469. /package/dist/web/assets/{mipsasm-DYyZAum-.js → mipsasm-bBgtvPyG.js} +0 -0
  470. /package/dist/web/assets/{mojo-q9wuEYKR.js → mojo-Cp9BayW0.js} +0 -0
  471. /package/dist/web/assets/{monokai-D9vrJ8JM.js → monokai-CL_9gbmn.js} +0 -0
  472. /package/dist/web/assets/{moonbit-C9roWxMb.js → moonbit-BS_fcr-P.js} +0 -0
  473. /package/dist/web/assets/{move-BvhjG2ic.js → move-C79AH45U.js} +0 -0
  474. /package/dist/web/assets/{narrat-BXGbPwG3.js → narrat-CJ5s3BDf.js} +0 -0
  475. /package/dist/web/assets/{nextflow-groovy-CMy5-Z4k.js → nextflow-groovy-ZHa4JrPJ.js} +0 -0
  476. /package/dist/web/assets/{night-owl-CBQBcbmw.js → night-owl-Jkf9zFQK.js} +0 -0
  477. /package/dist/web/assets/{night-owl-light-CC-brflp.js → night-owl-light-DRPeaG5J.js} +0 -0
  478. /package/dist/web/assets/{nix-B9EAdptu.js → nix-D2Jet9DN.js} +0 -0
  479. /package/dist/web/assets/{nord-DxASd4qG.js → nord-CREVs22j.js} +0 -0
  480. /package/dist/web/assets/{nushell-BsY59i7N.js → nushell-CAiDi_mR.js} +0 -0
  481. /package/dist/web/assets/{objective-c-CoeQAcnP.js → objective-c-CVkmJkC1.js} +0 -0
  482. /package/dist/web/assets/{objective-cpp-BysdFvND.js → objective-cpp-DIKDO1Ly.js} +0 -0
  483. /package/dist/web/assets/{ocaml-CrtQYMMK.js → ocaml-R9qtGQhs.js} +0 -0
  484. /package/dist/web/assets/{odin-B8pz4gIL.js → odin-DTYNQqVr.js} +0 -0
  485. /package/dist/web/assets/{one-dark-pro-3sSHqoqQ.js → one-dark-pro-BSLWUJn5.js} +0 -0
  486. /package/dist/web/assets/{one-light-LUZAvT8g.js → one-light-DbkRXG3t.js} +0 -0
  487. /package/dist/web/assets/{openscad-DcKia-sV.js → openscad-BlPcSnmT.js} +0 -0
  488. /package/dist/web/assets/{pascal-zM5SlmgF.js → pascal-B1Y46ZZB.js} +0 -0
  489. /package/dist/web/assets/{pkl-C_r_zLLt.js → pkl-BhJSJbFv.js} +0 -0
  490. /package/dist/web/assets/{plastic-B3cRtgKD.js → plastic-BzxsCTpi.js} +0 -0
  491. /package/dist/web/assets/{plsql-BzyjheXU.js → plsql-DBba7dyP.js} +0 -0
  492. /package/dist/web/assets/{plus-DcuJuDC3.js → plus-Dm36mgLh.js} +0 -0
  493. /package/dist/web/assets/{po-Dz4OaDNi.js → po-BvgLlnNo.js} +0 -0
  494. /package/dist/web/assets/{poimandres-BBbtYwmR.js → poimandres-DUaI_1JP.js} +0 -0
  495. /package/dist/web/assets/{polar-Che1XC2Z.js → polar-C7uILwJn.js} +0 -0
  496. /package/dist/web/assets/{postcss-DouzwG4F.js → postcss-BgJlpFdw.js} +0 -0
  497. /package/dist/web/assets/{powerquery-YOdcTZmd.js → powerquery-Dn1MIpZu.js} +0 -0
  498. /package/dist/web/assets/{powershell-8ojzYH34.js → powershell-Bo-DYzOS.js} +0 -0
  499. /package/dist/web/assets/{prisma-DCmjceML.js → prisma-CTLs5IdT.js} +0 -0
  500. /package/dist/web/assets/{prolog-ByNNz2pB.js → prolog-QRGun3eG.js} +0 -0
  501. /package/dist/web/assets/{proto-DWMt6Qu1.js → proto-B7PI7apn.js} +0 -0
  502. /package/dist/web/assets/{puppet-osfKyZp4.js → puppet-CBq0tLrP.js} +0 -0
  503. /package/dist/web/assets/{purescript-ClIblkvh.js → purescript-DFewOQb2.js} +0 -0
  504. /package/dist/web/assets/{python-Ctyr4faP.js → python-iWuxKXvT.js} +0 -0
  505. /package/dist/web/assets/{qmldir-CbNgyhnI.js → qmldir-cXiW3AsF.js} +0 -0
  506. /package/dist/web/assets/{qss-LbRHzb21.js → qss-DASwAbBL.js} +0 -0
  507. /package/dist/web/assets/{r-Dvfjp0wc.js → r-TU2eaIPw.js} +0 -0
  508. /package/dist/web/assets/{racket-DI8v8nzD.js → racket-SkRKqrLY.js} +0 -0
  509. /package/dist/web/assets/{raku-CX1q9GW0.js → raku-D31lthkZ.js} +0 -0
  510. /package/dist/web/assets/{react-8hrD3-kv.js → react-DjdYfcEe.js} +0 -0
  511. /package/dist/web/assets/{red-CSz3QEOw.js → red-CTcQZevb.js} +0 -0
  512. /package/dist/web/assets/{reg-FMjhUQE-.js → reg-D8YAsTGf.js} +0 -0
  513. /package/dist/web/assets/{regexp-CCSe1Fc4.js → regexp-CD2uvAWx.js} +0 -0
  514. /package/dist/web/assets/{rel-Ce0UO-OB.js → rel-CAaI-1fg.js} +0 -0
  515. /package/dist/web/assets/{resolve-theme-B9Mgd5GG.js → resolve-theme-B0eotaPq.js} +0 -0
  516. /package/dist/web/assets/{riscv-BFZrdZtB.js → riscv-DR-hJVIj.js} +0 -0
  517. /package/dist/web/assets/{ron-D0t4kvJG.js → ron-CuUCJ0n4.js} +0 -0
  518. /package/dist/web/assets/{rose-pine-HzwmOzHM.js → rose-pine-aFHxW48i.js} +0 -0
  519. /package/dist/web/assets/{rose-pine-dawn-CSp9xAF9.js → rose-pine-dawn-Bqz2sBRY.js} +0 -0
  520. /package/dist/web/assets/{rose-pine-moon-Cnzr2Cw0.js → rose-pine-moon-wqyh0an4.js} +0 -0
  521. /package/dist/web/assets/{rosmsg-DEJ8Snbv.js → rosmsg-CI1ywimO.js} +0 -0
  522. /package/dist/web/assets/{rust-2jBzrC0l.js → rust-p7Imk4G_.js} +0 -0
  523. /package/dist/web/assets/{sass-DD75Izu_.js → sass-C8JfaqCV.js} +0 -0
  524. /package/dist/web/assets/{save-BM4p9_9n.js → save-ignW_jk2.js} +0 -0
  525. /package/dist/web/assets/{scala-DpBKROIu.js → scala-DNxCW1By.js} +0 -0
  526. /package/dist/web/assets/{scheme-DM2jzwUK.js → scheme-BYuUPJoL.js} +0 -0
  527. /package/dist/web/assets/{sdbl-C34Cg6e7.js → sdbl-DbPeJPUr.js} +0 -0
  528. /package/dist/web/assets/{shellscript-DxfzKSgU.js → shellscript-DMiHlR3q.js} +0 -0
  529. /package/dist/web/assets/{shield-check-C3qYSU8a.js → shield-check-BSA9P3nI.js} +0 -0
  530. /package/dist/web/assets/{shield-off-D5tU7gQT.js → shield-off-Cb9J9x_n.js} +0 -0
  531. /package/dist/web/assets/{slack-dark-DIIV3bpD.js → slack-dark-C4xGcQXI.js} +0 -0
  532. /package/dist/web/assets/{slack-ochin-BTkxWjEP.js → slack-ochin-BzgRt_wm.js} +0 -0
  533. /package/dist/web/assets/{smalltalk-Bm0qg6SA.js → smalltalk-LDeL9PYH.js} +0 -0
  534. /package/dist/web/assets/{snazzy-light-ClAuuhn4.js → snazzy-light-CXE7a5j_.js} +0 -0
  535. /package/dist/web/assets/{solarized-dark-93sEWmYT.js → solarized-dark-DgUhY74x.js} +0 -0
  536. /package/dist/web/assets/{solarized-light-C74FvgcO.js → solarized-light-DD9C-H-9.js} +0 -0
  537. /package/dist/web/assets/{solidity-H9fSjkQP.js → solidity-CAbDB4D4.js} +0 -0
  538. /package/dist/web/assets/{splunk-CDTN2iAG.js → splunk-kQREPfIc.js} +0 -0
  539. /package/dist/web/assets/{sql-CoqObhgo.js → sql-CBzeNAD8.js} +0 -0
  540. /package/dist/web/assets/{ssh-config-C3Wv6qRp.js → ssh-config-L1euxrsM.js} +0 -0
  541. /package/dist/web/assets/{stylus-D0uTFXCy.js → stylus-DvBnYniP.js} +0 -0
  542. /package/dist/web/assets/{swift-DtMMgvRI.js → swift-DF5NDQp6.js} +0 -0
  543. /package/dist/web/assets/{synthwave-84-DAroyZBu.js → synthwave-84-DbBhB_Hq.js} +0 -0
  544. /package/dist/web/assets/{system-verilog-Bj7X_lq4.js → system-verilog-D8s2ZW_k.js} +0 -0
  545. /package/dist/web/assets/{systemd-CH8ApCUs.js → systemd-a5-XZJsG.js} +0 -0
  546. /package/dist/web/assets/{table-Di8pv2Y_.js → table-4P8kS7sg.js} +0 -0
  547. /package/dist/web/assets/{talonscript-70lVC3cw.js → talonscript-BLa0URTW.js} +0 -0
  548. /package/dist/web/assets/{tasl-WQR8VslV.js → tasl-DIoV4XcC.js} +0 -0
  549. /package/dist/web/assets/{tcl-caTR_frn.js → tcl-ztYAdXfQ.js} +0 -0
  550. /package/dist/web/assets/{terraform-CVRZ4SZ4.js → terraform-DvpCCiOZ.js} +0 -0
  551. /package/dist/web/assets/{tokyo-night-CDyT1jCI.js → tokyo-night-BkHlYUv0.js} +0 -0
  552. /package/dist/web/assets/{toml-B7D2mIDU.js → toml-acIrxos6.js} +0 -0
  553. /package/dist/web/assets/{tsv-2kdn5dqL.js → tsv-BJB5EVCT.js} +0 -0
  554. /package/dist/web/assets/{tsx-DJfG3fzg.js → tsx-B1MmXGT3.js} +0 -0
  555. /package/dist/web/assets/{turtle-DaQac_UT.js → turtle-DFceTiqd.js} +0 -0
  556. /package/dist/web/assets/{typescript-BLfAIchl.js → typescript-Dz9wU5Y1.js} +0 -0
  557. /package/dist/web/assets/{typespec-ymBaOi_c.js → typespec-CPuYZijj.js} +0 -0
  558. /package/dist/web/assets/{typst-DkvUYMT-.js → typst-CfsCIYv8.js} +0 -0
  559. /package/dist/web/assets/{use-is-mobile-BcuCjH19.js → use-is-mobile-K1a38BwI.js} +0 -0
  560. /package/dist/web/assets/{utils-E0yyGxXt.js → utils-B9H0d3Fh.js} +0 -0
  561. /package/dist/web/assets/{v-D4997FGs.js → v-CGo0jcXA.js} +0 -0
  562. /package/dist/web/assets/{vala-AFg9DmKE.js → vala-AsSR0hrb.js} +0 -0
  563. /package/dist/web/assets/{vb-DNUJ9fOP.js → vb-C7tvbXv1.js} +0 -0
  564. /package/dist/web/assets/{vendor-xterm-QDr0jvpJ.js → vendor-xterm-ClRVIJ-k.js} +0 -0
  565. /package/dist/web/assets/{verilog-BrnAQ3tF.js → verilog--a33ND-p.js} +0 -0
  566. /package/dist/web/assets/{vesper-DCZnFmY0.js → vesper-B_Z0h4pn.js} +0 -0
  567. /package/dist/web/assets/{vhdl-DoK4PjhE.js → vhdl-CWL5dZPN.js} +0 -0
  568. /package/dist/web/assets/{viml-D6EKG66q.js → viml-BglmT14Y.js} +0 -0
  569. /package/dist/web/assets/{vitesse-black-C6KILWdF.js → vitesse-black-RKFsSqQI.js} +0 -0
  570. /package/dist/web/assets/{vitesse-dark-BuSr6OT5.js → vitesse-dark-D7zDWNmc.js} +0 -0
  571. /package/dist/web/assets/{vitesse-light-M-eYyOoe.js → vitesse-light-MAp87W5b.js} +0 -0
  572. /package/dist/web/assets/{vyper-CwSN64Jn.js → vyper-CgCBYg1G.js} +0 -0
  573. /package/dist/web/assets/{wasm-BhQEkzil.js → wasm-BOG7ZeTT.js} +0 -0
  574. /package/dist/web/assets/{wasm-CVlf9Jwb.js → wasm-Bd258nS1.js} +0 -0
  575. /package/dist/web/assets/{wenyan-BC7irgM3.js → wenyan-DpShPKXZ.js} +0 -0
  576. /package/dist/web/assets/{wgsl-W-3UHMDO.js → wgsl-BakcCblK.js} +0 -0
  577. /package/dist/web/assets/{wikitext-T1DoFcm8.js → wikitext-NroRWVAO.js} +0 -0
  578. /package/dist/web/assets/{wit-DIrx_eCI.js → wit-B5CM2pg6.js} +0 -0
  579. /package/dist/web/assets/{wolfram-Cza4b443.js → wolfram-7MTFBJpR.js} +0 -0
  580. /package/dist/web/assets/{x-BsFLg-T8.js → x-BtzqXv1z.js} +0 -0
  581. /package/dist/web/assets/{yaml-DOZ10aZe.js → yaml-Bwr01G2K.js} +0 -0
  582. /package/dist/web/assets/{zenscript-DDAXkseY.js → zenscript-Dj8GATvi.js} +0 -0
  583. /package/dist/web/assets/{zig-CrzwMpQE.js → zig-LAE-fbS-.js} +0 -0
@@ -0,0 +1,177 @@
1
+ /**
2
+ * List view: a sortable header plus virtualised rows.
3
+ *
4
+ * Virtualisation is not optional here — `C:\Windows\System32` is ~5 000 entries and the
5
+ * server hands all of them over in one listing, so only the visible slice may ever be in
6
+ * the DOM.
7
+ */
8
+
9
+ import { useEffect, useMemo, useRef } from "react";
10
+ import { useVirtualizer } from "@tanstack/react-virtual";
11
+ import { ChevronDown, ChevronUp } from "lucide-react";
12
+ import { ContextMenu, ContextMenuTrigger } from "@/components/ui/adaptive-context-menu";
13
+ import { useFileStore } from "@/stores/file-store";
14
+ import { cn } from "@/lib/utils";
15
+ import { DROP_TARGET_CLASS } from "../dnd/drop-target-style";
16
+ import { useDragAutoScroll } from "../dnd/use-drag-auto-scroll";
17
+ import { usePathDropTarget } from "../dnd/use-path-drop-target";
18
+ import { ExplorerContextMenu } from "../explorer-context-menu";
19
+ import { useExplorerStore, type SortKey } from "../explorer-store";
20
+ import { SORT_KEY_OPTIONS } from "../sort-options";
21
+ import { useExplorerSkin } from "../skins/use-explorer-skin";
22
+ import { activeEntries } from "../use-explorer-keyboard";
23
+ import { usePrefersCoarsePointer } from "../use-coarse-long-press";
24
+ import type { ExplorerViewProps } from "./explorer-view-registry";
25
+ import { InlineNameInput } from "./inline-name-input";
26
+ import { ListRow } from "./list-row";
27
+
28
+ /** Layout per column; labels/keys come from the shared sort vocabulary. */
29
+ const COLUMN_CLASSNAME: Record<SortKey, string> = {
30
+ name: "flex-1 text-left",
31
+ size: "w-20 text-right",
32
+ modified: "hidden w-24 text-right sm:block",
33
+ kind: "hidden w-16 text-right md:block",
34
+ };
35
+ const COLUMNS = SORT_KEY_OPTIONS.map((option) => ({ ...option, className: COLUMN_CLASSNAME[option.key] }));
36
+
37
+ export function ListView({
38
+ slice, entries, actions, selection, inlineError, hasClipboard, isPinned, rowHeight, backgroundLongPress,
39
+ }: ExplorerViewProps) {
40
+ const sort = useExplorerStore((s) => s.sort);
41
+ const setPrefs = useExplorerStore((s) => s.setPrefs);
42
+ const cutPaths = useFileStore((s) => (s.clipboard?.operation === "cut" ? s.clipboard.paths : null));
43
+ const scrollRef = useRef<HTMLDivElement>(null);
44
+ useDragAutoScroll(scrollRef);
45
+ const skin = useExplorerSkin();
46
+ const coarse = usePrefersCoarsePointer();
47
+
48
+ const virtualizer = useVirtualizer({
49
+ count: entries.length,
50
+ getScrollElement: () => scrollRef.current,
51
+ estimateSize: () => rowHeight,
52
+ overscan: 12,
53
+ });
54
+
55
+ // Keep the cursor row visible when it moved by keyboard rather than by click.
56
+ const anchorIndex = slice.anchor ? entries.findIndex((e) => e.path === slice.anchor) : -1;
57
+ useEffect(() => {
58
+ if (anchorIndex >= 0) virtualizer.scrollToIndex(anchorIndex, { align: "auto" });
59
+ }, [anchorIndex]); // eslint-disable-line react-hooks/exhaustive-deps
60
+
61
+ const menuTargets = useMemo(() => activeEntries(slice, entries), [slice, entries]);
62
+ const creating = slice.inlineEdit?.kind === "new-file" || slice.inlineEdit?.kind === "new-folder";
63
+ // The view background is the current directory itself — a row already claims the drop
64
+ // (and stops the event) when it is a directory, so this only ever fires over empty space.
65
+ const backgroundDrop = usePathDropTarget({ targetDir: slice.path, run: actions.transferInto });
66
+
67
+ const toggleSort = (key: SortKey) =>
68
+ setPrefs({ sort: { key, dir: sort.key === key && sort.dir === "asc" ? "desc" : "asc" } });
69
+
70
+ return (
71
+ <div className="flex h-full min-h-0 flex-col">
72
+ <div
73
+ role="row"
74
+ className={cn(
75
+ "flex shrink-0 items-center gap-2 border-b border-border bg-[var(--x-toolbar-bg,var(--panel-2))] px-2 py-1 text-[11px] font-medium text-text-subtle",
76
+ // Windows Explorer's details header separates columns with a thin divider.
77
+ skin.id === "windows" && "divide-x divide-border",
78
+ )}
79
+ >
80
+ {COLUMNS.map((column) => (
81
+ <button
82
+ key={column.key}
83
+ type="button"
84
+ aria-label={`Sort by ${column.label}`}
85
+ onClick={() => toggleSort(column.key)}
86
+ className={cn(
87
+ "relative flex items-center gap-0.5 can-hover:hover:text-text",
88
+ skin.id === "windows" && column.key !== "name" && "pl-2",
89
+ column.className,
90
+ column.key !== "name" && "justify-end",
91
+ // Header row stays visually unchanged — only the tap-registering area grows
92
+ // to the 44px minimum on a coarse pointer, via an invisible expanded hit box.
93
+ coarse && "before:absolute before:-inset-x-2 before:-inset-y-3 before:content-['']",
94
+ )}
95
+ >
96
+ {column.label}
97
+ {sort.key === column.key &&
98
+ (sort.dir === "asc" ? <ChevronUp className="size-3" /> : <ChevronDown className="size-3" />)}
99
+ </button>
100
+ ))}
101
+ </div>
102
+
103
+ <ContextMenu>
104
+ <ContextMenuTrigger asChild>
105
+ <div
106
+ ref={scrollRef}
107
+ role="grid"
108
+ tabIndex={0}
109
+ data-testid="explorer-list"
110
+ aria-label="File list"
111
+ // Clicking the empty area below the rows clears the selection, as in the OS.
112
+ onMouseDown={(e) => { if (e.target === e.currentTarget) selection.clear(); }}
113
+ className={cn("flex-1 overflow-auto outline-none", backgroundDrop.isOver && DROP_TARGET_CLASS)}
114
+ {...backgroundLongPress}
115
+ {...backgroundDrop.handlers}
116
+ >
117
+ {creating && (
118
+ <div className="flex items-center gap-2 px-2" style={{ height: rowHeight }}>
119
+ <span className="size-4 shrink-0" />
120
+ <InlineNameInput
121
+ initial=""
122
+ error={inlineError}
123
+ onCommit={(value) => void actions.commitInline(value)}
124
+ onCancel={actions.cancelInline}
125
+ />
126
+ </div>
127
+ )}
128
+ <div className="relative w-full" style={{ height: virtualizer.getTotalSize() }}>
129
+ {virtualizer.getVirtualItems().map((item) => {
130
+ const entry = entries[item.index]!;
131
+ return (
132
+ <div
133
+ key={entry.path}
134
+ className={cn(
135
+ "absolute left-0 top-0 w-full",
136
+ // Finder's list view alternates row shading instead of a hover-only cue.
137
+ skin.id === "macos" && item.index % 2 === 1 && "bg-panel-2/40",
138
+ )}
139
+ style={{ transform: `translateY(${item.start}px)` }}
140
+ >
141
+ <ListRow
142
+ entry={entry}
143
+ currentDir={slice.path}
144
+ selected={slice.selection.has(entry.path)}
145
+ focused={slice.anchor === entry.path}
146
+ cut={cutPaths?.includes(entry.path) === true}
147
+ renaming={slice.inlineEdit?.kind === "rename" && slice.inlineEdit.path === entry.path}
148
+ height={rowHeight}
149
+ menuTargets={slice.selection.has(entry.path) ? menuTargets : [entry]}
150
+ actions={actions}
151
+ selection={selection}
152
+ hasClipboard={hasClipboard}
153
+ isPinned={isPinned}
154
+ inlineError={inlineError}
155
+ />
156
+ </div>
157
+ );
158
+ })}
159
+ </div>
160
+ {entries.length === 0 && !slice.loading && !creating && (
161
+ <p className="p-3 text-xs text-text-subtle">
162
+ {slice.filter ? "No entries match the filter." : "This folder is empty."}
163
+ </p>
164
+ )}
165
+ </div>
166
+ </ContextMenuTrigger>
167
+ <ExplorerContextMenu
168
+ targets={[]}
169
+ currentDir={slice.path}
170
+ hasClipboard={hasClipboard}
171
+ isPinned={isPinned}
172
+ actions={actions}
173
+ />
174
+ </ContextMenu>
175
+ </div>
176
+ );
177
+ }
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Column (Miller) view fetch state: one entry per breadcrumb ancestor, each cached and
3
+ * fetched independently so drilling one level deeper never re-fetches the columns already
4
+ * on screen. The deepest column always mirrors the window's own (already fetched, sorted,
5
+ * filtered) entries instead of fetching a second time.
6
+ *
7
+ * The reducer is a plain function — given the same paths/state it does the same thing —
8
+ * so "selecting a divergent branch truncates and aborts every column after it" is
9
+ * unit-testable without a DOM or a real network call. It does not call `.abort()` itself
10
+ * (that would be a side effect inside a reducer, and React may invoke a reducer more than
11
+ * once per dispatch under StrictMode); it only *identifies* which controllers went stale,
12
+ * and the hook's effect is the one thing that actually aborts them. The hook is likewise
13
+ * the only thing that calls `fsApi.browse`.
14
+ */
15
+
16
+ import { useEffect, useReducer, useRef } from "react";
17
+ import { fsApi, type FsEntry } from "@/lib/fs-api";
18
+
19
+ export interface ColumnState {
20
+ path: string;
21
+ entries: FsEntry[];
22
+ loading: boolean;
23
+ error: string | null;
24
+ abort: AbortController;
25
+ }
26
+
27
+ interface ColumnsState {
28
+ columns: ColumnState[];
29
+ /** Controllers the last `sync` replaced or truncated; consumed once by an effect. */
30
+ staleControllers: AbortController[];
31
+ }
32
+
33
+ type ColumnsAction =
34
+ | { type: "sync"; paths: string[]; lastEntries: FsEntry[]; lastLoading: boolean; makeAbort(): AbortController }
35
+ | { type: "loaded"; path: string; entries: FsEntry[] }
36
+ | { type: "error"; path: string; message: string };
37
+
38
+ export function reduceColumns(state: ColumnsState, action: ColumnsAction): ColumnsState {
39
+ switch (action.type) {
40
+ case "sync": {
41
+ const { paths, lastEntries, lastLoading, makeAbort } = action;
42
+ const lastIndex = paths.length - 1;
43
+ const next: ColumnState[] = paths.map((path, index) => {
44
+ const existing = state.columns[index];
45
+ const isLast = index === lastIndex;
46
+ if (existing && existing.path === path) {
47
+ // Unchanged column: keep its cache. The deepest column still mirrors the
48
+ // caller's live copy since sort/filter/hidden-toggle may have changed it.
49
+ return isLast ? { ...existing, entries: lastEntries, loading: lastLoading, error: null } : existing;
50
+ }
51
+ return isLast
52
+ ? { path, entries: lastEntries, loading: lastLoading, error: null, abort: makeAbort() }
53
+ : { path, entries: [], loading: true, error: null, abort: makeAbort() };
54
+ });
55
+ // Anything at an index whose path changed — including a truncated tail, where there
56
+ // is no replacement at all — had its own fetch in flight and must stop. Collected
57
+ // here, aborted by the hook's effect (this function stays a pure computation).
58
+ const staleControllers: AbortController[] = [];
59
+ for (let i = 0; i < state.columns.length; i++) {
60
+ const stale = state.columns[i]!;
61
+ const replacement = next[i];
62
+ if (!replacement || replacement.path !== stale.path) staleControllers.push(stale.abort);
63
+ }
64
+ return { columns: next, staleControllers };
65
+ }
66
+ case "loaded": {
67
+ return {
68
+ columns: state.columns.map((c) =>
69
+ c.path === action.path ? { ...c, entries: action.entries, loading: false, error: null } : c,
70
+ ),
71
+ staleControllers: [],
72
+ };
73
+ }
74
+ case "error": {
75
+ return {
76
+ columns: state.columns.map((c) =>
77
+ c.path === action.path ? { ...c, loading: false, error: action.message } : c,
78
+ ),
79
+ staleControllers: [],
80
+ };
81
+ }
82
+ }
83
+ }
84
+
85
+ export interface UseColumnViewState {
86
+ columns: ColumnState[];
87
+ }
88
+
89
+ /**
90
+ * Ties the reducer to real fetches. `paths` is the breadcrumb chain (root … current); the
91
+ * last path's entries always come from `lastEntries`/`lastLoading`, already owned by the
92
+ * window's own navigation hook.
93
+ */
94
+ export function useColumnViewState(
95
+ paths: string[],
96
+ lastEntries: FsEntry[],
97
+ lastLoading: boolean,
98
+ ): UseColumnViewState {
99
+ const [state, dispatch] = useReducer(reduceColumns, { columns: [], staleControllers: [] });
100
+ // Keyed by controller identity, not path: re-entering a path whose earlier attempt was
101
+ // aborted (drill A -> B -> back to A before B's abort settles) gets a *fresh* controller
102
+ // from the reducer, so it must not be blocked by the guard entry the stale attempt left
103
+ // behind — a path-keyed Set left that fresh column stuck on "Loading…" forever.
104
+ const fetching = useRef(new Set<AbortController>());
105
+
106
+ // A literal NUL separator (never a legal path character on any OS PPM targets) so two
107
+ // paths whose own text happens to contain a space still produce distinct keys.
108
+ const pathsKey = paths.join("\u0000");
109
+ useEffect(() => {
110
+ dispatch({ type: "sync", paths, lastEntries, lastLoading, makeAbort: () => new AbortController() });
111
+ // `pathsKey` is the real dependency for `paths`; `lastEntries`/`lastLoading` are objects
112
+ // that change on every fetch tick, which is exactly when this should re-run too.
113
+ // eslint-disable-next-line react-hooks/exhaustive-deps
114
+ }, [pathsKey, lastEntries, lastLoading]);
115
+
116
+ // The one place that actually aborts a stale fetch — the reducer only identified it.
117
+ useEffect(() => {
118
+ for (const controller of state.staleControllers) controller.abort();
119
+ }, [state.staleControllers]);
120
+
121
+ // Whatever is still live when this view unmounts (switched to List/Icons, window closed)
122
+ // must stop fetching too; nothing else ever aborts a column that simply falls out of view.
123
+ const columnsRef = useRef(state.columns);
124
+ columnsRef.current = state.columns;
125
+ useEffect(() => {
126
+ return () => {
127
+ for (const column of columnsRef.current) column.abort.abort();
128
+ };
129
+ }, []);
130
+
131
+ useEffect(() => {
132
+ const lastIndex = state.columns.length - 1;
133
+ state.columns.forEach((column, index) => {
134
+ // The deepest column is never fetched here — it mirrors the window's own listing.
135
+ if (index === lastIndex) return;
136
+ if (!column.loading || fetching.current.has(column.abort)) return;
137
+ fetching.current.add(column.abort);
138
+ fsApi
139
+ .browse(column.path, { signal: column.abort.signal })
140
+ .then((result) => {
141
+ fetching.current.delete(column.abort);
142
+ if (column.abort.signal.aborted) return;
143
+ dispatch({ type: "loaded", path: column.path, entries: result.entries });
144
+ })
145
+ .catch((e: unknown) => {
146
+ fetching.current.delete(column.abort);
147
+ if (column.abort.signal.aborted || (e instanceof DOMException && e.name === "AbortError")) return;
148
+ dispatch({
149
+ type: "error",
150
+ path: column.path,
151
+ message: e instanceof Error ? e.message : "Failed to read directory",
152
+ });
153
+ });
154
+ });
155
+ }, [state.columns]);
156
+
157
+ return { columns: state.columns };
158
+ }
@@ -0,0 +1,178 @@
1
+ /**
2
+ * Selection semantics for the explorer views — plain click, ctrl/cmd toggle, shift range,
3
+ * arrow-key cursor and type-ahead.
4
+ *
5
+ * The reducer is pure and separate from the hook so the fiddly parts (which row does
6
+ * shift-click extend from after a ctrl-click? does type-ahead wrap?) are unit-testable
7
+ * without a DOM.
8
+ */
9
+
10
+ import { useCallback, useMemo, useRef } from "react";
11
+ import { useExplorerStore } from "../explorer-store";
12
+
13
+ export interface SelectionState {
14
+ selection: Set<string>;
15
+ /** The cursor row: shift-range extends from here and arrows move it. */
16
+ anchor: string | null;
17
+ }
18
+
19
+ export type SelectionEvent =
20
+ | { type: "click"; path: string; shift?: boolean; ctrl?: boolean }
21
+ | { type: "select-all" }
22
+ | { type: "clear" }
23
+ | { type: "set"; paths: string[]; anchor?: string | null };
24
+
25
+ /** How long a type-ahead buffer stays alive between keystrokes. */
26
+ export const TYPE_AHEAD_RESET_MS = 800;
27
+
28
+ function rangeBetween(order: string[], from: string | null, to: string): string[] {
29
+ const start = from == null ? -1 : order.indexOf(from);
30
+ const end = order.indexOf(to);
31
+ if (end < 0) return [];
32
+ if (start < 0) return [to];
33
+ const [lo, hi] = start <= end ? [start, end] : [end, start];
34
+ return order.slice(lo, hi + 1);
35
+ }
36
+
37
+ /** Apply a selection event against the current visible row order. */
38
+ export function applySelection(
39
+ state: SelectionState,
40
+ order: string[],
41
+ event: SelectionEvent,
42
+ ): SelectionState {
43
+ switch (event.type) {
44
+ case "clear":
45
+ return { selection: new Set(), anchor: null };
46
+
47
+ case "select-all":
48
+ return { selection: new Set(order), anchor: state.anchor ?? order[0] ?? null };
49
+
50
+ case "set":
51
+ return {
52
+ selection: new Set(event.paths),
53
+ anchor: event.anchor !== undefined ? event.anchor : (event.paths[event.paths.length - 1] ?? null),
54
+ };
55
+
56
+ case "click": {
57
+ if (event.ctrl) {
58
+ const selection = new Set(state.selection);
59
+ if (selection.has(event.path)) selection.delete(event.path);
60
+ else selection.add(event.path);
61
+ // Ctrl-click moves the anchor so a following shift-click extends from the row
62
+ // the user just touched — matching Explorer and Finder.
63
+ return { selection, anchor: event.path };
64
+ }
65
+ if (event.shift) {
66
+ const range = rangeBetween(order, state.anchor, event.path);
67
+ // The anchor stays put: repeated shift-clicks grow and shrink one range.
68
+ return { selection: new Set(range), anchor: state.anchor ?? event.path };
69
+ }
70
+ return { selection: new Set([event.path]), anchor: event.path };
71
+ }
72
+ }
73
+ }
74
+
75
+ /** Row the cursor moves to for an arrow key. Clamps at both ends (no wrap). */
76
+ export function moveCursor(order: string[], anchor: string | null, delta: number): string | null {
77
+ if (order.length === 0) return null;
78
+ const current = anchor == null ? -1 : order.indexOf(anchor);
79
+ if (current < 0) return delta > 0 ? order[0]! : order[order.length - 1]!;
80
+ const next = Math.min(order.length - 1, Math.max(0, current + delta));
81
+ return order[next]!;
82
+ }
83
+
84
+ /**
85
+ * First name matching the typed prefix, searching after the current row and wrapping.
86
+ * Returns the index in `names`, or -1.
87
+ */
88
+ export function typeAheadIndex(names: string[], buffer: string, currentIndex: number): number {
89
+ if (!buffer) return -1;
90
+ const needle = buffer.toLowerCase();
91
+ // A repeated single character cycles through the matches instead of sticking on one.
92
+ const start = currentIndex < 0 ? 0 : currentIndex + 1;
93
+ for (let i = 0; i < names.length; i++) {
94
+ const index = (start + i) % names.length;
95
+ if (names[index]!.toLowerCase().startsWith(needle)) return index;
96
+ }
97
+ return -1;
98
+ }
99
+
100
+ export interface EntrySelection {
101
+ onRowClick(path: string, event: { shiftKey: boolean; ctrlKey: boolean; metaKey: boolean }): void;
102
+ selectOnly(path: string): void;
103
+ selectAll(): void;
104
+ clear(): void;
105
+ setSelection(paths: string[], anchor?: string | null): void;
106
+ moveTo(delta: number, extend: boolean): void;
107
+ /** Feed a printable key; returns the path it landed on, if any. */
108
+ typeAhead(key: string): string | null;
109
+ }
110
+
111
+ /** Binds the reducer to one window's slice. `order` is the visible row order. */
112
+ export function useEntrySelection(windowId: string, order: string[]): EntrySelection {
113
+ const patch = useExplorerStore((s) => s.patch);
114
+ const orderRef = useRef(order);
115
+ orderRef.current = order;
116
+ const bufferRef = useRef({ text: "", at: 0 });
117
+
118
+ const dispatch = useCallback(
119
+ (event: SelectionEvent) => {
120
+ const slice = useExplorerStore.getState().slices[windowId];
121
+ if (!slice) return;
122
+ patch(windowId, applySelection(slice, orderRef.current, event));
123
+ },
124
+ [windowId, patch],
125
+ );
126
+
127
+ const moveTo = useCallback(
128
+ (delta: number, extend: boolean) => {
129
+ const slice = useExplorerStore.getState().slices[windowId];
130
+ if (!slice) return;
131
+ const target = moveCursor(orderRef.current, slice.anchor, delta);
132
+ if (target == null) return;
133
+ dispatch({ type: "click", path: target, shift: extend });
134
+ },
135
+ [windowId, dispatch],
136
+ );
137
+
138
+ const typeAhead = useCallback(
139
+ (key: string) => {
140
+ const slice = useExplorerStore.getState().slices[windowId];
141
+ if (!slice) return null;
142
+ const now = Date.now();
143
+ const buffer = now - bufferRef.current.at > TYPE_AHEAD_RESET_MS ? key : bufferRef.current.text + key;
144
+ bufferRef.current = { text: buffer, at: now };
145
+
146
+ const paths = orderRef.current;
147
+ const names = paths.map((p) => p.split(/[/\\]/).pop() ?? p);
148
+ // With a one-character buffer the search starts after the cursor so pressing the
149
+ // same letter walks through every match.
150
+ const from = buffer.length === 1 && slice.anchor ? paths.indexOf(slice.anchor) : -1;
151
+ const index = typeAheadIndex(names, buffer, from);
152
+ if (index < 0) return null;
153
+ dispatch({ type: "click", path: paths[index]! });
154
+ return paths[index]!;
155
+ },
156
+ [windowId, dispatch],
157
+ );
158
+
159
+ const onRowClick = useCallback(
160
+ (path: string, event: { shiftKey: boolean; ctrlKey: boolean; metaKey: boolean }) =>
161
+ dispatch({ type: "click", path, shift: event.shiftKey, ctrl: event.ctrlKey || event.metaKey }),
162
+ [dispatch],
163
+ );
164
+ const selectOnly = useCallback((path: string) => dispatch({ type: "click", path }), [dispatch]);
165
+ const selectAll = useCallback(() => dispatch({ type: "select-all" }), [dispatch]);
166
+ const clear = useCallback(() => dispatch({ type: "clear" }), [dispatch]);
167
+ const setSelection = useCallback(
168
+ (paths: string[], anchor?: string | null) => dispatch({ type: "set", paths, anchor }),
169
+ [dispatch],
170
+ );
171
+
172
+ // A fresh object every render would defeat memo(ListRow) (this is a prop several levels
173
+ // down) even though every method here is already a stable useCallback.
174
+ return useMemo(
175
+ () => ({ onRowClick, selectOnly, selectAll, clear, setSelection, moveTo, typeAhead }),
176
+ [onRowClick, selectOnly, selectAll, clear, setSelection, moveTo, typeAhead],
177
+ );
178
+ }
@@ -9,7 +9,7 @@ import { Switch } from "@/components/ui/switch";
9
9
  import { ScrollArea } from "@/components/ui/scroll-area";
10
10
  import { Separator } from "@/components/ui/separator";
11
11
  import { useShallow } from "zustand/react/shallow";
12
- import { useSettingsStore, type EditorTabStyle } from "@/stores/settings-store";
12
+ import { useSettingsStore, type EditorTabStyle, type ExplorerSkinPref } from "@/stores/settings-store";
13
13
  import { cn } from "@/lib/utils";
14
14
  import { AISettingsSection } from "./ai-settings-section";
15
15
  import { KeyboardShortcutsSection } from "./keyboard-shortcuts-section";
@@ -30,6 +30,12 @@ const TAB_STYLE_OPTIONS: { value: EditorTabStyle; label: string }[] = [
30
30
  { value: "pill", label: "Pill" },
31
31
  ];
32
32
 
33
+ const EXPLORER_SKIN_OPTIONS: { value: ExplorerSkinPref; label: string }[] = [
34
+ { value: "auto", label: "Auto" },
35
+ { value: "windows", label: "Windows" },
36
+ { value: "macos", label: "macOS" },
37
+ ];
38
+
33
39
  type SettingsCategory = "ai" | "notifications" | "clawbot" | "schedules" | "jira" | "proxy" | "shortcuts" | "extensions" | "files" | "queryAudit";
34
40
 
35
41
  const CATEGORIES: { value: SettingsCategory; label: string; subtitle: string; icon: React.ElementType }[] = [
@@ -46,7 +52,7 @@ const CATEGORIES: { value: SettingsCategory; label: string; subtitle: string; ic
46
52
  ];
47
53
 
48
54
  export function SettingsTab() {
49
- const { theme, setTheme, deviceName, setDeviceName, version, jiraEnabled, setJiraEnabled, tabWrap, toggleTabWrap, editorTabStyle, setEditorTabStyle } = useSettingsStore(useShallow((s) => ({ theme: s.themeMode, setTheme: s.setThemeMode, deviceName: s.deviceName, setDeviceName: s.setDeviceName, version: s.version, jiraEnabled: s.jiraEnabled, setJiraEnabled: s.setJiraEnabled, tabWrap: s.tabWrap, toggleTabWrap: s.toggleTabWrap, editorTabStyle: s.editorTabStyle, setEditorTabStyle: s.setEditorTabStyle })));
55
+ const { theme, setTheme, deviceName, setDeviceName, version, jiraEnabled, setJiraEnabled, tabWrap, toggleTabWrap, editorTabStyle, setEditorTabStyle, explorerSkin, setExplorerSkin } = useSettingsStore(useShallow((s) => ({ theme: s.themeMode, setTheme: s.setThemeMode, deviceName: s.deviceName, setDeviceName: s.setDeviceName, version: s.version, jiraEnabled: s.jiraEnabled, setJiraEnabled: s.setJiraEnabled, tabWrap: s.tabWrap, toggleTabWrap: s.toggleTabWrap, editorTabStyle: s.editorTabStyle, setEditorTabStyle: s.setEditorTabStyle, explorerSkin: s.explorerSkin, setExplorerSkin: s.setExplorerSkin })));
50
56
  const [activeCategory, setActiveCategory] = useState<SettingsCategory | null>(null);
51
57
  const [nameInput, setNameInput] = useState(deviceName ?? "");
52
58
  const [nameSaving, setNameSaving] = useState(false);
@@ -223,6 +229,27 @@ export function SettingsTab() {
223
229
  </div>
224
230
  </section>
225
231
 
232
+ {/* Explorer Skin — chrome the OS File Explorer window wears; Auto follows platform */}
233
+ <section className="space-y-2">
234
+ <h3 className="text-xs font-medium text-muted-foreground">Explorer Skin</h3>
235
+ <div className="flex gap-1.5">
236
+ {EXPLORER_SKIN_OPTIONS.map((opt) => (
237
+ <Button
238
+ key={opt.value}
239
+ variant={explorerSkin === opt.value ? "default" : "outline"}
240
+ size="sm"
241
+ onClick={() => setExplorerSkin(opt.value)}
242
+ className={cn(
243
+ "flex-1 text-xs h-8 cursor-pointer",
244
+ explorerSkin === opt.value && "ring-2 ring-primary",
245
+ )}
246
+ >
247
+ {opt.label}
248
+ </Button>
249
+ ))}
250
+ </div>
251
+ </section>
252
+
226
253
  <Separator />
227
254
 
228
255
  {/* Category navigation list */}
@@ -33,7 +33,10 @@ export function SqliteViewer({ metadata }: SqliteViewerProps) {
33
33
  );
34
34
  }
35
35
 
36
- if (!filePath || !projectName) {
36
+ // A database opened from a file-explorer window has no project; its absolute path is
37
+ // enough for the host filesystem SQLite routes, so only a missing path is fatal.
38
+ const isExternalPath = !!filePath && !projectName && /^(\/|[A-Za-z]:[/\\])/.test(filePath);
39
+ if (!filePath || (!projectName && !isExternalPath)) {
37
40
  return (
38
41
  <div className="flex items-center justify-center h-full text-text-secondary text-sm">
39
42
  <Database className="size-5 mr-2" /> No database file selected.
@@ -43,7 +46,7 @@ export function SqliteViewer({ metadata }: SqliteViewerProps) {
43
46
 
44
47
  return (
45
48
  <SqliteViewerInner
46
- projectName={projectName}
49
+ projectName={projectName ?? ""}
47
50
  dbPath={filePath}
48
51
  queryPanelOpen={queryPanelOpen}
49
52
  onToggleQueryPanel={() => setQueryPanelOpen((v) => !v)}
@@ -19,9 +19,14 @@ export function useSqlite(projectName: string, dbPath: string, connectionId?: nu
19
19
  const [queryError, setQueryError] = useState<string | null>(null);
20
20
  const [queryLoading, setQueryLoading] = useState(false);
21
21
 
22
- // When connectionId present, use unified API; otherwise use project-scoped API
22
+ // Three sources, in order of specificity:
23
+ // - a saved connection → the unified /api/db routes, no path in the query
24
+ // - a file inside a project → the project-scoped route, path relative to the project
25
+ // - an absolute path with no project (opened from a file-explorer window)
26
+ // → the host filesystem route, path absolute
23
27
  const unifiedBase = connectionId ? `/api/db/connections/${connectionId}` : null;
24
- const base = unifiedBase ?? `${projectUrl(projectName)}/sqlite`;
28
+ const isExternalPath = !connectionId && !projectName && /^(\/|[A-Za-z]:[/\\])/.test(dbPath);
29
+ const base = unifiedBase ?? (isExternalPath ? "/api/fs/sqlite" : `${projectUrl(projectName)}/sqlite`);
25
30
  const qs = unifiedBase ? "" : `path=${encodeURIComponent(dbPath)}`;
26
31
 
27
32
  // Fetch tables on mount — use cache when connectionId (sidebar handles live sync)