@hienlh/ppm 0.17.57 → 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 (580) hide show
  1. package/.release-run.sh +5 -0
  2. package/CHANGELOG.md +16 -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-CXWQcx3_.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-D7jNc8mi.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-CpGcSopK.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-DTMmic31.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-CR2ZtvNP.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-BTCFWY2A.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-LinTYl0_.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-0SGb7_tH.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-DSxSupDq.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-DEn8amrO.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-CjrM15gz.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-CT5M9PKQ.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-De3oz9zD.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-CciNqtMc.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-k-17kQqf.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-D-S_YnG6.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/server/index.ts +8 -0
  148. package/src/server/routes/files.ts +2 -2
  149. package/src/server/routes/fs-browse.ts +99 -74
  150. package/src/server/routes/fs-ops.ts +118 -0
  151. package/src/server/routes/fs-sqlite.ts +129 -0
  152. package/src/server/routes/host-info.ts +17 -0
  153. package/src/server/routes/settings.ts +2 -0
  154. package/src/services/download-token.service.ts +21 -5
  155. package/src/services/file.service.ts +8 -12
  156. package/src/services/fs-browse.service.ts +104 -161
  157. package/src/services/fs-ops/fs-content-disposition.ts +17 -0
  158. package/src/services/fs-ops/fs-core-ops.ts +149 -0
  159. package/src/services/fs-ops/fs-error-response.ts +24 -0
  160. package/src/services/fs-ops/fs-git-init.service.ts +23 -0
  161. package/src/services/fs-ops/fs-hidden-names.ts +17 -0
  162. package/src/services/fs-ops/fs-list-files.service.ts +68 -0
  163. package/src/services/fs-ops/fs-ops-copy-move.service.ts +60 -0
  164. package/src/services/fs-ops/fs-ops-mutate.service.ts +59 -0
  165. package/src/services/fs-ops/fs-ops-read-write.service.ts +70 -0
  166. package/src/services/fs-ops/fs-ops-stat.service.ts +109 -0
  167. package/src/services/fs-ops/fs-ops-trash.service.ts +111 -0
  168. package/src/services/fs-ops/fs-real-path.ts +44 -0
  169. package/src/services/fs-ops/sql-row-reader.ts +26 -0
  170. package/src/services/fs-ops/sql-statement-guard.ts +63 -0
  171. package/src/services/fs-path-guard.service.ts +196 -0
  172. package/src/services/host-info/apple-bookmark-decoder.ts +97 -0
  173. package/src/services/host-info/apple-plist-xml-parser.ts +107 -0
  174. package/src/services/host-info/concurrency-limit.ts +20 -0
  175. package/src/services/host-info/host-drives.ts +190 -0
  176. package/src/services/host-info/host-info.service.ts +171 -0
  177. package/src/services/host-info/host-known-folders.ts +180 -0
  178. package/src/services/host-info/pinned-linux-bookmarks.ts +89 -0
  179. package/src/services/host-info/pinned-macos-finder-favorites.ts +175 -0
  180. package/src/services/host-info/pinned-windows-quick-access.ts +87 -0
  181. package/src/services/host-info/spawn-runner.ts +41 -0
  182. package/src/services/sqlite-types.ts +26 -0
  183. package/src/services/sqlite.service.ts +38 -29
  184. package/src/services/subagent-transcript-merger.ts +1 -1
  185. package/src/types/system.ts +50 -0
  186. package/src/web/app.tsx +19 -3
  187. package/src/web/components/editor/code-editor.tsx +3 -9
  188. package/src/web/components/explorer/file-tree.tsx +103 -21
  189. package/src/web/components/explorer/tree-node-context-menu.tsx +6 -1
  190. package/src/web/components/explorer/tree-node.tsx +34 -93
  191. package/src/web/components/explorer/use-file-upload-drag.ts +14 -9
  192. package/src/web/components/explorer/use-tree-row-dnd.ts +206 -0
  193. package/src/web/components/floating-window/default-window-chrome.tsx +71 -0
  194. package/src/web/components/floating-window/floating-window.tsx +167 -0
  195. package/src/web/components/floating-window/use-window-drag.ts +28 -0
  196. package/src/web/components/floating-window/use-window-gesture-context.ts +44 -0
  197. package/src/web/components/floating-window/use-window-keyboard.ts +55 -0
  198. package/src/web/components/floating-window/use-window-resize.ts +30 -0
  199. package/src/web/components/floating-window/window-chrome-contract.ts +38 -0
  200. package/src/web/components/floating-window/window-content-registry.ts +51 -0
  201. package/src/web/components/floating-window/window-geometry.ts +121 -0
  202. package/src/web/components/floating-window/window-layer.tsx +92 -0
  203. package/src/web/components/floating-window/window-persistence.ts +104 -0
  204. package/src/web/components/floating-window/window-resize-handles.tsx +81 -0
  205. package/src/web/components/floating-window/window-store-types.ts +20 -0
  206. package/src/web/components/floating-window/window-store.ts +192 -0
  207. package/src/web/components/layout/command-palette.tsx +10 -0
  208. package/src/web/components/layout/mobile-drawer.tsx +11 -2
  209. package/src/web/components/layout/nav-section-rail.tsx +7 -1
  210. package/src/web/components/os-explorer/actions/explorer-actions-clipboard.ts +186 -0
  211. package/src/web/components/os-explorer/actions/explorer-actions-mutate.ts +109 -0
  212. package/src/web/components/os-explorer/actions/explorer-actions-open.ts +67 -0
  213. package/src/web/components/os-explorer/actions/explorer-actions-transfer-helpers.ts +35 -0
  214. package/src/web/components/os-explorer/actions/use-explorer-actions.ts +205 -0
  215. package/src/web/components/os-explorer/can-open-in-ppm.ts +89 -0
  216. package/src/web/components/os-explorer/dnd/drag-depth-counter.ts +36 -0
  217. package/src/web/components/os-explorer/dnd/drag-ghost.ts +44 -0
  218. package/src/web/components/os-explorer/dnd/drop-target-decision.ts +46 -0
  219. package/src/web/components/os-explorer/dnd/drop-target-style.ts +7 -0
  220. package/src/web/components/os-explorer/dnd/entry-drag-payload.ts +115 -0
  221. package/src/web/components/os-explorer/dnd/entry-drag-state.ts +35 -0
  222. package/src/web/components/os-explorer/dnd/entry-drop-executor.ts +43 -0
  223. package/src/web/components/os-explorer/dnd/use-drag-auto-scroll.ts +70 -0
  224. package/src/web/components/os-explorer/dnd/use-drop-transfer.tsx +82 -0
  225. package/src/web/components/os-explorer/dnd/use-entry-drag-source.ts +73 -0
  226. package/src/web/components/os-explorer/dnd/use-entry-drop-target.ts +150 -0
  227. package/src/web/components/os-explorer/dnd/use-entry-row-dnd.ts +66 -0
  228. package/src/web/components/os-explorer/dnd/use-path-drop-target.ts +30 -0
  229. package/src/web/components/os-explorer/explorer-body.tsx +153 -0
  230. package/src/web/components/os-explorer/explorer-breadcrumb.tsx +108 -0
  231. package/src/web/components/os-explorer/explorer-context-menu.tsx +131 -0
  232. package/src/web/components/os-explorer/explorer-dialogs.tsx +103 -0
  233. package/src/web/components/os-explorer/explorer-modal-shell.tsx +60 -0
  234. package/src/web/components/os-explorer/explorer-pins-store.ts +63 -0
  235. package/src/web/components/os-explorer/explorer-sidebar.tsx +198 -0
  236. package/src/web/components/os-explorer/explorer-sort-menu.tsx +68 -0
  237. package/src/web/components/os-explorer/explorer-status-bar.tsx +35 -0
  238. package/src/web/components/os-explorer/explorer-store.ts +179 -0
  239. package/src/web/components/os-explorer/explorer-toolbar.tsx +139 -0
  240. package/src/web/components/os-explorer/explorer-window-content.tsx +36 -0
  241. package/src/web/components/os-explorer/format-file-meta.ts +74 -0
  242. package/src/web/components/os-explorer/icons/file-type-icon-map.ts +106 -0
  243. package/src/web/components/os-explorer/icons/file-type-icon.tsx +56 -0
  244. package/src/web/components/os-explorer/icons/thumbnail-fetch-semaphore.ts +44 -0
  245. package/src/web/components/os-explorer/icons/thumbnail-image.tsx +124 -0
  246. package/src/web/components/os-explorer/mobile/mobile-bottom-toolbar.tsx +102 -0
  247. package/src/web/components/os-explorer/mobile/mobile-explorer-sheet.tsx +61 -0
  248. package/src/web/components/os-explorer/mobile/mobile-explorer-top-bar.tsx +41 -0
  249. package/src/web/components/os-explorer/mobile/mobile-more-sheet.tsx +117 -0
  250. package/src/web/components/os-explorer/mobile/mobile-places-sheet.tsx +120 -0
  251. package/src/web/components/os-explorer/mobile/mobile-places-strip.tsx +109 -0
  252. package/src/web/components/os-explorer/mobile/mobile-tap-action.ts +19 -0
  253. package/src/web/components/os-explorer/mobile/use-mobile-row-tap.ts +50 -0
  254. package/src/web/components/os-explorer/open-explorer.ts +35 -0
  255. package/src/web/components/os-explorer/properties-dialog.tsx +99 -0
  256. package/src/web/components/os-explorer/skins/explorer-skin-chrome.tsx +17 -0
  257. package/src/web/components/os-explorer/skins/folder-icon-macos.tsx +42 -0
  258. package/src/web/components/os-explorer/skins/folder-icon-windows.tsx +25 -0
  259. package/src/web/components/os-explorer/skins/macos-skin.ts +14 -0
  260. package/src/web/components/os-explorer/skins/macos-window-chrome.tsx +69 -0
  261. package/src/web/components/os-explorer/skins/skin-types.ts +40 -0
  262. package/src/web/components/os-explorer/skins/skins.css +25 -0
  263. package/src/web/components/os-explorer/skins/use-explorer-skin.ts +21 -0
  264. package/src/web/components/os-explorer/skins/windows-skin.ts +11 -0
  265. package/src/web/components/os-explorer/skins/windows-window-chrome.tsx +56 -0
  266. package/src/web/components/os-explorer/sort-and-filter-entries.ts +52 -0
  267. package/src/web/components/os-explorer/sort-options.ts +29 -0
  268. package/src/web/components/os-explorer/toolbar-icon-button.ts +18 -0
  269. package/src/web/components/os-explorer/use-coarse-long-press.ts +97 -0
  270. package/src/web/components/os-explorer/use-explorer-keyboard.ts +124 -0
  271. package/src/web/components/os-explorer/use-explorer-navigation.ts +155 -0
  272. package/src/web/components/os-explorer/use-explorer-open-state.ts +67 -0
  273. package/src/web/components/os-explorer/use-host-info.ts +85 -0
  274. package/src/web/components/os-explorer/views/column-row.tsx +126 -0
  275. package/src/web/components/os-explorer/views/column-view-column.tsx +112 -0
  276. package/src/web/components/os-explorer/views/column-view-mobile.tsx +98 -0
  277. package/src/web/components/os-explorer/views/column-view-preview.tsx +70 -0
  278. package/src/web/components/os-explorer/views/column-view.tsx +204 -0
  279. package/src/web/components/os-explorer/views/compose-interactive-row-props.ts +77 -0
  280. package/src/web/components/os-explorer/views/explorer-view-registry.tsx +55 -0
  281. package/src/web/components/os-explorer/views/icons-view-tile.tsx +158 -0
  282. package/src/web/components/os-explorer/views/icons-view.tsx +188 -0
  283. package/src/web/components/os-explorer/views/inline-name-input.tsx +60 -0
  284. package/src/web/components/os-explorer/views/list-row.tsx +162 -0
  285. package/src/web/components/os-explorer/views/list-view.tsx +177 -0
  286. package/src/web/components/os-explorer/views/use-column-view-state.ts +158 -0
  287. package/src/web/components/os-explorer/views/use-entry-selection.ts +178 -0
  288. package/src/web/components/settings/settings-tab.tsx +29 -2
  289. package/src/web/components/sqlite/sqlite-viewer.tsx +5 -2
  290. package/src/web/components/sqlite/use-sqlite.ts +7 -2
  291. package/src/web/components/ui/adaptive-context-menu.tsx +57 -1
  292. package/src/web/components/ui/file-browser-picker.tsx +1 -18
  293. package/src/web/lib/file-download.ts +6 -4
  294. package/src/web/lib/fs-api.ts +185 -0
  295. package/src/web/stores/file-store.ts +38 -1
  296. package/src/web/stores/settings-store.ts +14 -0
  297. package/src/web/styles/globals.css +1 -0
  298. package/dist/web/assets/api-client-RtNXvS0P.js +0 -1
  299. package/dist/web/assets/api-settings-De--HNHp.js +0 -1
  300. package/dist/web/assets/architecture-PBZL5I3N-DVRYSsQn.js +0 -1
  301. package/dist/web/assets/chat-tab-C5U7a_rP.js +0 -13
  302. package/dist/web/assets/code-editor-CqlzLQ7T.js +0 -10
  303. package/dist/web/assets/context-menu-DIiZdfbE.js +0 -1
  304. package/dist/web/assets/csv-preview-DD94T9ff.js +0 -2
  305. package/dist/web/assets/dialog-HHdNInEH.js +0 -1
  306. package/dist/web/assets/diff-viewer-DyQLYP7J.js +0 -4
  307. package/dist/web/assets/git-log-panel-CrUgsLY5.js +0 -1
  308. package/dist/web/assets/gitGraph-HDMCJU4V-CutkUNY_.js +0 -1
  309. package/dist/web/assets/group-chat-tab-BqNJW7y2.js +0 -1
  310. package/dist/web/assets/index-CVJmlKhp.css +0 -2
  311. package/dist/web/assets/index-I2ik0ZF7.js +0 -50
  312. package/dist/web/assets/info-3K5VOQVL-BpNjgbj9.js +0 -1
  313. package/dist/web/assets/keybindings-store-C46tNxJE.js +0 -1
  314. package/dist/web/assets/markdown-renderer-xzyhJklS.js +0 -1
  315. package/dist/web/assets/notification-store-Dek7ClI2.js +0 -1
  316. package/dist/web/assets/packet-RMMSAZCW-D5IUZ66-.js +0 -1
  317. package/dist/web/assets/pie-UPGHQEXC-CM29eKDR.js +0 -1
  318. package/dist/web/assets/radar-KQ55EAFF-QtJGaSGt.js +0 -1
  319. package/dist/web/assets/settings-store-B3LfpCPa.js +0 -2
  320. package/dist/web/assets/settings-tab-DIuR1VIc.js +0 -1
  321. package/dist/web/assets/sparkles-B3DmG8PJ.js +0 -1
  322. package/dist/web/assets/sqlite-viewer-CEObRQ7n.js +0 -1
  323. package/dist/web/assets/tool-cards-Bdx04yYD.js +0 -7
  324. package/dist/web/assets/treemap-KZPCXAKY-CjUqpVnS.js +0 -1
  325. /package/dist/web/assets/{abap-BM5PMK0-.js → abap-C1K0-wzc.js} +0 -0
  326. /package/dist/web/assets/{actionscript-3-CML-MTNy.js → actionscript-3-CJhQVjLA.js} +0 -0
  327. /package/dist/web/assets/{ada-DzO1tMqV.js → ada-Bt8r9fME.js} +0 -0
  328. /package/dist/web/assets/{andromeeda-C3nkvRhj.js → andromeeda-DF1I8qi3.js} +0 -0
  329. /package/dist/web/assets/{apache-Dg7oWQbs.js → apache-CnlleE6P.js} +0 -0
  330. /package/dist/web/assets/{apex-3O2170Ju.js → apex-j0C6Xmk6.js} +0 -0
  331. /package/dist/web/assets/{applescript-5AlzBqQO.js → applescript-DO7AnO8U.js} +0 -0
  332. /package/dist/web/assets/{apply-theme-BSJDABU3.js → apply-theme-CMQXnUAa.js} +0 -0
  333. /package/dist/web/assets/{ara-BGtOX5Hn.js → ara-i83AydZj.js} +0 -0
  334. /package/dist/web/assets/{asciidoc-CIsC4kkq.js → asciidoc-DbBA6-sF.js} +0 -0
  335. /package/dist/web/assets/{asm-p1-PUkh3.js → asm-B5S47Rgr.js} +0 -0
  336. /package/dist/web/assets/{aurora-x-BPgGL50S.js → aurora-x-De_IaSKw.js} +0 -0
  337. /package/dist/web/assets/{awk-Cj93pRL8.js → awk-CfyLHXqS.js} +0 -0
  338. /package/dist/web/assets/{ayu-dark-C50MYKhw.js → ayu-dark-DTLdZ1Si.js} +0 -0
  339. /package/dist/web/assets/{ayu-light-DyIp0Idy.js → ayu-light-DTOfkzhu.js} +0 -0
  340. /package/dist/web/assets/{ayu-mirage-BAdm8pKU.js → ayu-mirage-DsAECCMv.js} +0 -0
  341. /package/dist/web/assets/{ballerina-DUO9mwyV.js → ballerina-CumGp9A7.js} +0 -0
  342. /package/dist/web/assets/{bat-Cg8hlSct.js → bat-Ca2SBbYN.js} +0 -0
  343. /package/dist/web/assets/{beancount-f8f3BBqB.js → beancount-CqyXBwwF.js} +0 -0
  344. /package/dist/web/assets/{berry-BqVQYZZr.js → berry-yl-kJr_H.js} +0 -0
  345. /package/dist/web/assets/{bibtex-C_q8ygAM.js → bibtex-DoFFszjV.js} +0 -0
  346. /package/dist/web/assets/{bicep-DWS-2uWR.js → bicep-CWPi4nfK.js} +0 -0
  347. /package/dist/web/assets/{bird2-C0pvipi_.js → bird2-DXew-ut0.js} +0 -0
  348. /package/dist/web/assets/{bot-E9mcVZnr.js → bot-BHXjpQ7x.js} +0 -0
  349. /package/dist/web/assets/{c-BQFvJ6Ux.js → c-clbySDvd.js} +0 -0
  350. /package/dist/web/assets/{c3-Da53KeG6.js → c3-B65jvSM1.js} +0 -0
  351. /package/dist/web/assets/{cadence-B5X2vWm3.js → cadence-Cm8tQTHN.js} +0 -0
  352. /package/dist/web/assets/{catppuccin-frappe-Dm3rVY3b.js → catppuccin-frappe-DLeGslh_.js} +0 -0
  353. /package/dist/web/assets/{catppuccin-latte-ZxS-ga_K.js → catppuccin-latte-CR5GeHws.js} +0 -0
  354. /package/dist/web/assets/{catppuccin-macchiato-B0bAzXh0.js → catppuccin-macchiato-p-XgjxhP.js} +0 -0
  355. /package/dist/web/assets/{catppuccin-mocha-BeMC5hqi.js → catppuccin-mocha-B8QOAZLl.js} +0 -0
  356. /package/dist/web/assets/{chevron-right-DeUue2aK.js → chevron-right-CtcHmZg9.js} +0 -0
  357. /package/dist/web/assets/{circle-question-mark-PaJgx-3d.js → circle-question-mark-EVgisosn.js} +0 -0
  358. /package/dist/web/assets/{clarity-BRdirlsw.js → clarity-DPulbvKH.js} +0 -0
  359. /package/dist/web/assets/{clojure-pSLH-KQp.js → clojure-DFuWS20u.js} +0 -0
  360. /package/dist/web/assets/{cmake-DsZu4U4F.js → cmake-DejCqcu4.js} +0 -0
  361. /package/dist/web/assets/{code-TekEnDFM.js → code-Dupqw5Fr.js} +0 -0
  362. /package/dist/web/assets/{codeowners-CtkKTnsH.js → codeowners-BwAyqWIH.js} +0 -0
  363. /package/dist/web/assets/{codeql-BpP1PDrj.js → codeql-DgSwFjqI.js} +0 -0
  364. /package/dist/web/assets/{common-lisp-BX1XgHTt.js → common-lisp-CxIVJCtf.js} +0 -0
  365. /package/dist/web/assets/{coq-D2bX6-8g.js → coq-DLSjSnmf.js} +0 -0
  366. /package/dist/web/assets/{csharp-DcR97P0D.js → csharp-C1HHzXBp.js} +0 -0
  367. /package/dist/web/assets/{css-CIjciIIk.js → css-VXsMY--y.js} +0 -0
  368. /package/dist/web/assets/{csv-DW48Z5d-.js → csv-BEbcJdHz.js} +0 -0
  369. /package/dist/web/assets/{csv-parser-DVHBcvuE.js → csv-parser-DHvoTjMF.js} +0 -0
  370. /package/dist/web/assets/{cue-Byh1xHko.js → cue-BkDnFqUD.js} +0 -0
  371. /package/dist/web/assets/{cypher-TVlMMWma.js → cypher-1cMqojSQ.js} +0 -0
  372. /package/dist/web/assets/{d-CxaScvxf.js → d-BnYjRXtH.js} +0 -0
  373. /package/dist/web/assets/{dark-plus-sn-FFTT1.js → dark-plus-XfbaH7kZ.js} +0 -0
  374. /package/dist/web/assets/{dart-BmpakMe-.js → dart-Ca7s-8hK.js} +0 -0
  375. /package/dist/web/assets/{data-grid-types-BuMHLKXV.js → data-grid-types-DP-ZexEN.js} +0 -0
  376. /package/dist/web/assets/{database-COYAMZaw.js → database-Dj0DxgtX.js} +0 -0
  377. /package/dist/web/assets/{dax-B1aRUMXT.js → dax-CmuOw-mY.js} +0 -0
  378. /package/dist/web/assets/{desktop-C6lD62ar.js → desktop-C2gJ6Ovb.js} +0 -0
  379. /package/dist/web/assets/{diff-CupGmT1a.js → diff-BCEo1Ej7.js} +0 -0
  380. /package/dist/web/assets/{dist-Bi4gRyhr.js → dist-CipvJy9w.js} +0 -0
  381. /package/dist/web/assets/{dist-m_oJqIDk.js → dist-D__U4ZJz.js} +0 -0
  382. /package/dist/web/assets/{docker-DjDr3TJF.js → docker-DN2jlyAh.js} +0 -0
  383. /package/dist/web/assets/{dotenv-QeT1SLTv.js → dotenv-NExMY2kS.js} +0 -0
  384. /package/dist/web/assets/{dracula-yI5kpKp5.js → dracula-B9lbE01y.js} +0 -0
  385. /package/dist/web/assets/{dracula-soft-CYF6cJce.js → dracula-soft-CMBBfKF_.js} +0 -0
  386. /package/dist/web/assets/{dream-maker-TBDKiHrV.js → dream-maker-DEyDwFsd.js} +0 -0
  387. /package/dist/web/assets/{emacs-lisp-B7aZTnJ_.js → emacs-lisp-D_qP2zS6.js} +0 -0
  388. /package/dist/web/assets/{everforest-dark-CN77B1NJ.js → everforest-dark-DbHa2U-p.js} +0 -0
  389. /package/dist/web/assets/{everforest-light-CyFxGMIs.js → everforest-light-BISkJchP.js} +0 -0
  390. /package/dist/web/assets/{fennel-C6ktGUzp.js → fennel-6B22SfJR.js} +0 -0
  391. /package/dist/web/assets/{file-exclamation-point-DBEC-Kst.js → file-exclamation-point-D-ZORzYB.js} +0 -0
  392. /package/dist/web/assets/{fish-Vr62C2vP.js → fish-CubIIvJD.js} +0 -0
  393. /package/dist/web/assets/{fluent-CH3IJUki.js → fluent-DF55zQ09.js} +0 -0
  394. /package/dist/web/assets/{fortran-free-form-BN7VmmxZ.js → fortran-free-form-DvOu8jAt.js} +0 -0
  395. /package/dist/web/assets/{gdscript-BYdRNu_P.js → gdscript-DCPxdLpq.js} +0 -0
  396. /package/dist/web/assets/{gdshader-C2XOq90k.js → gdshader-CTZ381FO.js} +0 -0
  397. /package/dist/web/assets/{genie-B4hkIXNU.js → genie-C_QDtvgU.js} +0 -0
  398. /package/dist/web/assets/{gherkin-CW9NH2DW.js → gherkin-BcvK42tF.js} +0 -0
  399. /package/dist/web/assets/{github-dark-BWCf99kM.js → github-dark-C9afs_4w.js} +0 -0
  400. /package/dist/web/assets/{github-dark-default-DZ1m_6OP.js → github-dark-default-CKGVrl3Z.js} +0 -0
  401. /package/dist/web/assets/{github-dark-dimmed-CUXSQ8bS.js → github-dark-dimmed-C3dQZ_LC.js} +0 -0
  402. /package/dist/web/assets/{github-dark-high-contrast-B6BvU8H-.js → github-dark-high-contrast-DW78QukY.js} +0 -0
  403. /package/dist/web/assets/{github-light-Ch-E92cy.js → github-light-BnP2dznS.js} +0 -0
  404. /package/dist/web/assets/{github-light-default-CKFslV7i.js → github-light-default-C2fPZzPG.js} +0 -0
  405. /package/dist/web/assets/{github-light-high-contrast-DiJVeRlZ.js → github-light-high-contrast-BzIuUFR2.js} +0 -0
  406. /package/dist/web/assets/{gleam-D4dP7V5M.js → gleam-BpeKwEIO.js} +0 -0
  407. /package/dist/web/assets/{gn-DuO2D2eW.js → gn-nUb_Mit0.js} +0 -0
  408. /package/dist/web/assets/{gnuplot-Df_8pFbI.js → gnuplot-D7I0v20p.js} +0 -0
  409. /package/dist/web/assets/{go-BL16cMJO.js → go-D7Qqska7.js} +0 -0
  410. /package/dist/web/assets/{groovy-rfHeCRqy.js → groovy-DS-pne5-.js} +0 -0
  411. /package/dist/web/assets/{gruvbox-dark-hard-CLbDN-E3.js → gruvbox-dark-hard-C0W01fsq.js} +0 -0
  412. /package/dist/web/assets/{gruvbox-dark-medium-Df0L0qje.js → gruvbox-dark-medium-Bqo6fQbg.js} +0 -0
  413. /package/dist/web/assets/{gruvbox-dark-soft-DV6TAcen.js → gruvbox-dark-soft-D-XSM_fu.js} +0 -0
  414. /package/dist/web/assets/{gruvbox-light-hard-CWXUzp9b.js → gruvbox-light-hard-IVAwMJRl.js} +0 -0
  415. /package/dist/web/assets/{gruvbox-light-medium-BOHMe1-7.js → gruvbox-light-medium-38ATiG7i.js} +0 -0
  416. /package/dist/web/assets/{gruvbox-light-soft-DrrS2sdV.js → gruvbox-light-soft-DnZr3c_0.js} +0 -0
  417. /package/dist/web/assets/{haskell-DybKb9uW.js → haskell-uQcywkDH.js} +0 -0
  418. /package/dist/web/assets/{haxe-DLRwZxmt.js → haxe-c6bI4_GT.js} +0 -0
  419. /package/dist/web/assets/{hcl-CusCzwOk.js → hcl-CQXTiEXq.js} +0 -0
  420. /package/dist/web/assets/{hjson-BygWIJ-3.js → hjson-ChETf2bz.js} +0 -0
  421. /package/dist/web/assets/{hlsl-DCKvuEyZ.js → hlsl-BMC4JGoE.js} +0 -0
  422. /package/dist/web/assets/{horizon-DSOVAzyH.js → horizon-BusFlhLW.js} +0 -0
  423. /package/dist/web/assets/{horizon-bright-DKfVjGj_.js → horizon-bright-2oD0Mz7W.js} +0 -0
  424. /package/dist/web/assets/{houston-Ddm8AgpV.js → houston-DdbbjqFg.js} +0 -0
  425. /package/dist/web/assets/{hy-DzZa6alE.js → hy-D_SOuqw9.js} +0 -0
  426. /package/dist/web/assets/{image-gallery-CObzTisL.js → image-gallery-BWFpZAAP.js} +0 -0
  427. /package/dist/web/assets/{imba-aiXalPnK.js → imba-BxLOHO5I.js} +0 -0
  428. /package/dist/web/assets/{ini-CAvrVa5R.js → ini-ox-CFcg0.js} +0 -0
  429. /package/dist/web/assets/{java-PRMSQp24.js → java-Bzo8yIal.js} +0 -0
  430. /package/dist/web/assets/{javascript-DQR6GQKs.js → javascript-DHuz9VXn.js} +0 -0
  431. /package/dist/web/assets/{json-uWUKqOxq.js → json-0_y80FD5.js} +0 -0
  432. /package/dist/web/assets/{json5-R_LXJXL0.js → json5-CEQ50pGM.js} +0 -0
  433. /package/dist/web/assets/{jsonc-tC5zvrPO.js → jsonc-CfrLEAKA.js} +0 -0
  434. /package/dist/web/assets/{jsonl-BAwnWh7U.js → jsonl-fKbrANC2.js} +0 -0
  435. /package/dist/web/assets/{jsonnet-CPj4HCRm.js → jsonnet-DKbkPaQ2.js} +0 -0
  436. /package/dist/web/assets/{jssm-B7HswxOM.js → jssm-Dxxx1Hqu.js} +0 -0
  437. /package/dist/web/assets/{jsx-z4TGELE1.js → jsx-DlJL-Oj3.js} +0 -0
  438. /package/dist/web/assets/{kanagawa-dragon-BpeLlraV.js → kanagawa-dragon-CwN4SV1c.js} +0 -0
  439. /package/dist/web/assets/{kanagawa-lotus-CLnIPAGs.js → kanagawa-lotus-BHltayiE.js} +0 -0
  440. /package/dist/web/assets/{kanagawa-wave-BglPniBb.js → kanagawa-wave-D-KXPjw6.js} +0 -0
  441. /package/dist/web/assets/{kdl-B6blIqLq.js → kdl-DvtoJdFo.js} +0 -0
  442. /package/dist/web/assets/{kotlin-DVAEYiwY.js → kotlin-CTS4MLcx.js} +0 -0
  443. /package/dist/web/assets/{kusto-Ba87977j.js → kusto-HtSE_sKm.js} +0 -0
  444. /package/dist/web/assets/{laserwave-C-NuzoMp.js → laserwave-DaXZG9ge.js} +0 -0
  445. /package/dist/web/assets/{lean-CJ_KLkrJ.js → lean-BYtGUfWx.js} +0 -0
  446. /package/dist/web/assets/{less-Dfn9YC_2.js → less-BoxmU_Mk.js} +0 -0
  447. /package/dist/web/assets/{lib-DTaN9riD.js → lib-Bpbn00xM.js} +0 -0
  448. /package/dist/web/assets/{libesm-DdtEjhIi.js → libesm-tR4XlhMj.js} +0 -0
  449. /package/dist/web/assets/{light-plus-B3BLlguG.js → light-plus-Bt5CyUuo.js} +0 -0
  450. /package/dist/web/assets/{llvm-6UDP3NQb.js → llvm-CSdk9q9V.js} +0 -0
  451. /package/dist/web/assets/{log-QW3dqr1D.js → log-B-kp_3CL.js} +0 -0
  452. /package/dist/web/assets/{logo-DUinJheI.js → logo-DdIsXFDO.js} +0 -0
  453. /package/dist/web/assets/{luau-B1vcBzl8.js → luau-DUBpLlzQ.js} +0 -0
  454. /package/dist/web/assets/{make-CqNL10gS.js → make-B3JVjoW_.js} +0 -0
  455. /package/dist/web/assets/{markdown-BQNMNiYS.js → markdown-Seon0vHs.js} +0 -0
  456. /package/dist/web/assets/{material-theme-rbV8EefR.js → material-theme-Nq-ChX1P.js} +0 -0
  457. /package/dist/web/assets/{material-theme-darker-BZrskvoo.js → material-theme-darker-CPV4ayXJ.js} +0 -0
  458. /package/dist/web/assets/{material-theme-lighter-B0CBBjBw.js → material-theme-lighter-0kyjYqEd.js} +0 -0
  459. /package/dist/web/assets/{material-theme-ocean-CX_CHnQq.js → material-theme-ocean-DGpAp5co.js} +0 -0
  460. /package/dist/web/assets/{material-theme-palenight-ztiOCPbw.js → material-theme-palenight-B0AUn0NG.js} +0 -0
  461. /package/dist/web/assets/{matlab-tHQDNC9G.js → matlab-C2waytxs.js} +0 -0
  462. /package/dist/web/assets/{mdx-D4kZ6c6G.js → mdx-DAis6R6I.js} +0 -0
  463. /package/dist/web/assets/{mermaid-DlR4U4Pe.js → mermaid-_rnT2YGD.js} +0 -0
  464. /package/dist/web/assets/{min-dark-BX-xHAD2.js → min-dark-BSSjchUB.js} +0 -0
  465. /package/dist/web/assets/{min-light-Bxw0_Gqq.js → min-light-DomAFQsa.js} +0 -0
  466. /package/dist/web/assets/{mipsasm-DYyZAum-.js → mipsasm-bBgtvPyG.js} +0 -0
  467. /package/dist/web/assets/{mojo-q9wuEYKR.js → mojo-Cp9BayW0.js} +0 -0
  468. /package/dist/web/assets/{monokai-D9vrJ8JM.js → monokai-CL_9gbmn.js} +0 -0
  469. /package/dist/web/assets/{moonbit-C9roWxMb.js → moonbit-BS_fcr-P.js} +0 -0
  470. /package/dist/web/assets/{move-BvhjG2ic.js → move-C79AH45U.js} +0 -0
  471. /package/dist/web/assets/{narrat-BXGbPwG3.js → narrat-CJ5s3BDf.js} +0 -0
  472. /package/dist/web/assets/{nextflow-groovy-CMy5-Z4k.js → nextflow-groovy-ZHa4JrPJ.js} +0 -0
  473. /package/dist/web/assets/{night-owl-CBQBcbmw.js → night-owl-Jkf9zFQK.js} +0 -0
  474. /package/dist/web/assets/{night-owl-light-CC-brflp.js → night-owl-light-DRPeaG5J.js} +0 -0
  475. /package/dist/web/assets/{nix-B9EAdptu.js → nix-D2Jet9DN.js} +0 -0
  476. /package/dist/web/assets/{nord-DxASd4qG.js → nord-CREVs22j.js} +0 -0
  477. /package/dist/web/assets/{nushell-BsY59i7N.js → nushell-CAiDi_mR.js} +0 -0
  478. /package/dist/web/assets/{objective-c-CoeQAcnP.js → objective-c-CVkmJkC1.js} +0 -0
  479. /package/dist/web/assets/{objective-cpp-BysdFvND.js → objective-cpp-DIKDO1Ly.js} +0 -0
  480. /package/dist/web/assets/{ocaml-CrtQYMMK.js → ocaml-R9qtGQhs.js} +0 -0
  481. /package/dist/web/assets/{odin-B8pz4gIL.js → odin-DTYNQqVr.js} +0 -0
  482. /package/dist/web/assets/{one-dark-pro-3sSHqoqQ.js → one-dark-pro-BSLWUJn5.js} +0 -0
  483. /package/dist/web/assets/{one-light-LUZAvT8g.js → one-light-DbkRXG3t.js} +0 -0
  484. /package/dist/web/assets/{openscad-DcKia-sV.js → openscad-BlPcSnmT.js} +0 -0
  485. /package/dist/web/assets/{pascal-zM5SlmgF.js → pascal-B1Y46ZZB.js} +0 -0
  486. /package/dist/web/assets/{pkl-C_r_zLLt.js → pkl-BhJSJbFv.js} +0 -0
  487. /package/dist/web/assets/{plastic-B3cRtgKD.js → plastic-BzxsCTpi.js} +0 -0
  488. /package/dist/web/assets/{plsql-BzyjheXU.js → plsql-DBba7dyP.js} +0 -0
  489. /package/dist/web/assets/{plus-DcuJuDC3.js → plus-Dm36mgLh.js} +0 -0
  490. /package/dist/web/assets/{po-Dz4OaDNi.js → po-BvgLlnNo.js} +0 -0
  491. /package/dist/web/assets/{poimandres-BBbtYwmR.js → poimandres-DUaI_1JP.js} +0 -0
  492. /package/dist/web/assets/{polar-Che1XC2Z.js → polar-C7uILwJn.js} +0 -0
  493. /package/dist/web/assets/{postcss-DouzwG4F.js → postcss-BgJlpFdw.js} +0 -0
  494. /package/dist/web/assets/{powerquery-YOdcTZmd.js → powerquery-Dn1MIpZu.js} +0 -0
  495. /package/dist/web/assets/{powershell-8ojzYH34.js → powershell-Bo-DYzOS.js} +0 -0
  496. /package/dist/web/assets/{prisma-DCmjceML.js → prisma-CTLs5IdT.js} +0 -0
  497. /package/dist/web/assets/{prolog-ByNNz2pB.js → prolog-QRGun3eG.js} +0 -0
  498. /package/dist/web/assets/{proto-DWMt6Qu1.js → proto-B7PI7apn.js} +0 -0
  499. /package/dist/web/assets/{puppet-osfKyZp4.js → puppet-CBq0tLrP.js} +0 -0
  500. /package/dist/web/assets/{purescript-ClIblkvh.js → purescript-DFewOQb2.js} +0 -0
  501. /package/dist/web/assets/{python-Ctyr4faP.js → python-iWuxKXvT.js} +0 -0
  502. /package/dist/web/assets/{qmldir-CbNgyhnI.js → qmldir-cXiW3AsF.js} +0 -0
  503. /package/dist/web/assets/{qss-LbRHzb21.js → qss-DASwAbBL.js} +0 -0
  504. /package/dist/web/assets/{r-Dvfjp0wc.js → r-TU2eaIPw.js} +0 -0
  505. /package/dist/web/assets/{racket-DI8v8nzD.js → racket-SkRKqrLY.js} +0 -0
  506. /package/dist/web/assets/{raku-CX1q9GW0.js → raku-D31lthkZ.js} +0 -0
  507. /package/dist/web/assets/{react-8hrD3-kv.js → react-DjdYfcEe.js} +0 -0
  508. /package/dist/web/assets/{red-CSz3QEOw.js → red-CTcQZevb.js} +0 -0
  509. /package/dist/web/assets/{reg-FMjhUQE-.js → reg-D8YAsTGf.js} +0 -0
  510. /package/dist/web/assets/{regexp-CCSe1Fc4.js → regexp-CD2uvAWx.js} +0 -0
  511. /package/dist/web/assets/{rel-Ce0UO-OB.js → rel-CAaI-1fg.js} +0 -0
  512. /package/dist/web/assets/{resolve-theme-B9Mgd5GG.js → resolve-theme-B0eotaPq.js} +0 -0
  513. /package/dist/web/assets/{riscv-BFZrdZtB.js → riscv-DR-hJVIj.js} +0 -0
  514. /package/dist/web/assets/{ron-D0t4kvJG.js → ron-CuUCJ0n4.js} +0 -0
  515. /package/dist/web/assets/{rose-pine-HzwmOzHM.js → rose-pine-aFHxW48i.js} +0 -0
  516. /package/dist/web/assets/{rose-pine-dawn-CSp9xAF9.js → rose-pine-dawn-Bqz2sBRY.js} +0 -0
  517. /package/dist/web/assets/{rose-pine-moon-Cnzr2Cw0.js → rose-pine-moon-wqyh0an4.js} +0 -0
  518. /package/dist/web/assets/{rosmsg-DEJ8Snbv.js → rosmsg-CI1ywimO.js} +0 -0
  519. /package/dist/web/assets/{rust-2jBzrC0l.js → rust-p7Imk4G_.js} +0 -0
  520. /package/dist/web/assets/{sass-DD75Izu_.js → sass-C8JfaqCV.js} +0 -0
  521. /package/dist/web/assets/{save-BM4p9_9n.js → save-ignW_jk2.js} +0 -0
  522. /package/dist/web/assets/{scala-DpBKROIu.js → scala-DNxCW1By.js} +0 -0
  523. /package/dist/web/assets/{scheme-DM2jzwUK.js → scheme-BYuUPJoL.js} +0 -0
  524. /package/dist/web/assets/{sdbl-C34Cg6e7.js → sdbl-DbPeJPUr.js} +0 -0
  525. /package/dist/web/assets/{shellscript-DxfzKSgU.js → shellscript-DMiHlR3q.js} +0 -0
  526. /package/dist/web/assets/{shield-check-C3qYSU8a.js → shield-check-BSA9P3nI.js} +0 -0
  527. /package/dist/web/assets/{shield-off-D5tU7gQT.js → shield-off-Cb9J9x_n.js} +0 -0
  528. /package/dist/web/assets/{slack-dark-DIIV3bpD.js → slack-dark-C4xGcQXI.js} +0 -0
  529. /package/dist/web/assets/{slack-ochin-BTkxWjEP.js → slack-ochin-BzgRt_wm.js} +0 -0
  530. /package/dist/web/assets/{smalltalk-Bm0qg6SA.js → smalltalk-LDeL9PYH.js} +0 -0
  531. /package/dist/web/assets/{snazzy-light-ClAuuhn4.js → snazzy-light-CXE7a5j_.js} +0 -0
  532. /package/dist/web/assets/{solarized-dark-93sEWmYT.js → solarized-dark-DgUhY74x.js} +0 -0
  533. /package/dist/web/assets/{solarized-light-C74FvgcO.js → solarized-light-DD9C-H-9.js} +0 -0
  534. /package/dist/web/assets/{solidity-H9fSjkQP.js → solidity-CAbDB4D4.js} +0 -0
  535. /package/dist/web/assets/{splunk-CDTN2iAG.js → splunk-kQREPfIc.js} +0 -0
  536. /package/dist/web/assets/{sql-CoqObhgo.js → sql-CBzeNAD8.js} +0 -0
  537. /package/dist/web/assets/{ssh-config-C3Wv6qRp.js → ssh-config-L1euxrsM.js} +0 -0
  538. /package/dist/web/assets/{stylus-D0uTFXCy.js → stylus-DvBnYniP.js} +0 -0
  539. /package/dist/web/assets/{swift-DtMMgvRI.js → swift-DF5NDQp6.js} +0 -0
  540. /package/dist/web/assets/{synthwave-84-DAroyZBu.js → synthwave-84-DbBhB_Hq.js} +0 -0
  541. /package/dist/web/assets/{system-verilog-Bj7X_lq4.js → system-verilog-D8s2ZW_k.js} +0 -0
  542. /package/dist/web/assets/{systemd-CH8ApCUs.js → systemd-a5-XZJsG.js} +0 -0
  543. /package/dist/web/assets/{table-Di8pv2Y_.js → table-4P8kS7sg.js} +0 -0
  544. /package/dist/web/assets/{talonscript-70lVC3cw.js → talonscript-BLa0URTW.js} +0 -0
  545. /package/dist/web/assets/{tasl-WQR8VslV.js → tasl-DIoV4XcC.js} +0 -0
  546. /package/dist/web/assets/{tcl-caTR_frn.js → tcl-ztYAdXfQ.js} +0 -0
  547. /package/dist/web/assets/{terraform-CVRZ4SZ4.js → terraform-DvpCCiOZ.js} +0 -0
  548. /package/dist/web/assets/{tokyo-night-CDyT1jCI.js → tokyo-night-BkHlYUv0.js} +0 -0
  549. /package/dist/web/assets/{toml-B7D2mIDU.js → toml-acIrxos6.js} +0 -0
  550. /package/dist/web/assets/{tsv-2kdn5dqL.js → tsv-BJB5EVCT.js} +0 -0
  551. /package/dist/web/assets/{tsx-DJfG3fzg.js → tsx-B1MmXGT3.js} +0 -0
  552. /package/dist/web/assets/{turtle-DaQac_UT.js → turtle-DFceTiqd.js} +0 -0
  553. /package/dist/web/assets/{typescript-BLfAIchl.js → typescript-Dz9wU5Y1.js} +0 -0
  554. /package/dist/web/assets/{typespec-ymBaOi_c.js → typespec-CPuYZijj.js} +0 -0
  555. /package/dist/web/assets/{typst-DkvUYMT-.js → typst-CfsCIYv8.js} +0 -0
  556. /package/dist/web/assets/{use-is-mobile-BcuCjH19.js → use-is-mobile-K1a38BwI.js} +0 -0
  557. /package/dist/web/assets/{utils-E0yyGxXt.js → utils-B9H0d3Fh.js} +0 -0
  558. /package/dist/web/assets/{v-D4997FGs.js → v-CGo0jcXA.js} +0 -0
  559. /package/dist/web/assets/{vala-AFg9DmKE.js → vala-AsSR0hrb.js} +0 -0
  560. /package/dist/web/assets/{vb-DNUJ9fOP.js → vb-C7tvbXv1.js} +0 -0
  561. /package/dist/web/assets/{vendor-xterm-QDr0jvpJ.js → vendor-xterm-ClRVIJ-k.js} +0 -0
  562. /package/dist/web/assets/{verilog-BrnAQ3tF.js → verilog--a33ND-p.js} +0 -0
  563. /package/dist/web/assets/{vesper-DCZnFmY0.js → vesper-B_Z0h4pn.js} +0 -0
  564. /package/dist/web/assets/{vhdl-DoK4PjhE.js → vhdl-CWL5dZPN.js} +0 -0
  565. /package/dist/web/assets/{viml-D6EKG66q.js → viml-BglmT14Y.js} +0 -0
  566. /package/dist/web/assets/{vitesse-black-C6KILWdF.js → vitesse-black-RKFsSqQI.js} +0 -0
  567. /package/dist/web/assets/{vitesse-dark-BuSr6OT5.js → vitesse-dark-D7zDWNmc.js} +0 -0
  568. /package/dist/web/assets/{vitesse-light-M-eYyOoe.js → vitesse-light-MAp87W5b.js} +0 -0
  569. /package/dist/web/assets/{vyper-CwSN64Jn.js → vyper-CgCBYg1G.js} +0 -0
  570. /package/dist/web/assets/{wasm-BhQEkzil.js → wasm-BOG7ZeTT.js} +0 -0
  571. /package/dist/web/assets/{wasm-CVlf9Jwb.js → wasm-Bd258nS1.js} +0 -0
  572. /package/dist/web/assets/{wenyan-BC7irgM3.js → wenyan-DpShPKXZ.js} +0 -0
  573. /package/dist/web/assets/{wgsl-W-3UHMDO.js → wgsl-BakcCblK.js} +0 -0
  574. /package/dist/web/assets/{wikitext-T1DoFcm8.js → wikitext-NroRWVAO.js} +0 -0
  575. /package/dist/web/assets/{wit-DIrx_eCI.js → wit-B5CM2pg6.js} +0 -0
  576. /package/dist/web/assets/{wolfram-Cza4b443.js → wolfram-7MTFBJpR.js} +0 -0
  577. /package/dist/web/assets/{x-BsFLg-T8.js → x-BtzqXv1z.js} +0 -0
  578. /package/dist/web/assets/{yaml-DOZ10aZe.js → yaml-Bwr01G2K.js} +0 -0
  579. /package/dist/web/assets/{zenscript-DDAXkseY.js → zenscript-Dj8GATvi.js} +0 -0
  580. /package/dist/web/assets/{zig-CrzwMpQE.js → zig-LAE-fbS-.js} +0 -0
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Directory navigation for one explorer window: fetching, history, and cross-window
3
+ * refresh.
4
+ *
5
+ * Two requests for the same window must never race: a slow `C:\Windows\System32` listing
6
+ * arriving after the user already stepped into another folder would repaint stale rows.
7
+ * Each fetch therefore aborts the previous one and re-checks its own identity before
8
+ * writing to the store.
9
+ */
10
+
11
+ import { useCallback, useEffect, useMemo, useRef } from "react";
12
+ import { fsApi, FsError } from "@/lib/fs-api";
13
+ import { useWindowStore } from "@/components/floating-window/window-store";
14
+ import { onFsChanged, useExplorerStore, type ExplorerSlice } from "./explorer-store";
15
+
16
+ export interface ExplorerNavigation {
17
+ go(path: string, opts?: { replace?: boolean }): void;
18
+ back(): void;
19
+ forward(): void;
20
+ up(): void;
21
+ refresh(): void;
22
+ canGoBack: boolean;
23
+ canGoForward: boolean;
24
+ }
25
+
26
+ export function useExplorerNavigation(
27
+ windowId: string,
28
+ slice: ExplorerSlice | undefined,
29
+ showHidden: boolean,
30
+ ): ExplorerNavigation {
31
+ const abortRef = useRef<AbortController | null>(null);
32
+ const patch = useExplorerStore((s) => s.patch);
33
+
34
+ // The load target is read from a ref so `load` keeps a stable identity: it is a
35
+ // dependency of the fsChanged subscription, which must not resubscribe per keystroke.
36
+ const pathRef = useRef(slice?.path ?? "");
37
+ pathRef.current = slice?.path ?? "";
38
+ const hiddenRef = useRef(showHidden);
39
+ hiddenRef.current = showHidden;
40
+
41
+ const load = useCallback(
42
+ async (path: string) => {
43
+ abortRef.current?.abort();
44
+ const controller = new AbortController();
45
+ abortRef.current = controller;
46
+ patch(windowId, { loading: true, error: null });
47
+ try {
48
+ const result = await fsApi.browse(path, {
49
+ showHidden: hiddenRef.current,
50
+ signal: controller.signal,
51
+ });
52
+ if (controller.signal.aborted) return;
53
+ patch(windowId, {
54
+ entries: result.entries,
55
+ breadcrumbs: result.breadcrumbs,
56
+ parent: result.parent,
57
+ sep: result.sep,
58
+ truncated: result.truncated === true,
59
+ loading: false,
60
+ error: null,
61
+ });
62
+ } catch (e) {
63
+ if (controller.signal.aborted || (e instanceof DOMException && e.name === "AbortError")) return;
64
+ patch(windowId, {
65
+ loading: false,
66
+ entries: [],
67
+ error:
68
+ e instanceof FsError
69
+ ? { message: e.message, code: e.code, hint: e.hint }
70
+ : { message: e instanceof Error ? e.message : "Failed to read directory", code: "EUNKNOWN" },
71
+ });
72
+ }
73
+ },
74
+ [windowId, patch],
75
+ );
76
+
77
+ // Fetch whenever the target directory or the hidden-files preference changes.
78
+ const path = slice?.path;
79
+ useEffect(() => {
80
+ if (!path) return;
81
+ void load(path);
82
+ return () => abortRef.current?.abort();
83
+ }, [path, showHidden, load]);
84
+
85
+ // Another window (or this one) mutated a directory we are displaying.
86
+ useEffect(() => {
87
+ return onFsChanged((dir) => {
88
+ if (dir === pathRef.current) void load(dir);
89
+ });
90
+ }, [load]);
91
+
92
+ const go = useCallback(
93
+ (target: string, opts?: { replace?: boolean }) => {
94
+ const current = useExplorerStore.getState().slices[windowId];
95
+ if (!current || target === current.path) return;
96
+ const history = opts?.replace
97
+ ? [...current.history.slice(0, current.historyIndex), target]
98
+ : [...current.history.slice(0, current.historyIndex + 1), target];
99
+ patch(windowId, {
100
+ path: target,
101
+ history: history.slice(-100),
102
+ historyIndex: Math.min(history.length, 100) - 1,
103
+ selection: new Set(),
104
+ anchor: null,
105
+ filter: "",
106
+ inlineEdit: null,
107
+ });
108
+ // Keeps the titlebar in step and makes the window reopen here after a reload.
109
+ useWindowStore.getState().setPayload(windowId, { path: target });
110
+ },
111
+ [windowId, patch],
112
+ );
113
+
114
+ const step = useCallback(
115
+ (delta: number) => {
116
+ const current = useExplorerStore.getState().slices[windowId];
117
+ if (!current) return;
118
+ const index = current.historyIndex + delta;
119
+ const target = current.history[index];
120
+ if (target == null) return;
121
+ patch(windowId, {
122
+ path: target,
123
+ historyIndex: index,
124
+ selection: new Set(),
125
+ anchor: null,
126
+ filter: "",
127
+ inlineEdit: null,
128
+ });
129
+ useWindowStore.getState().setPayload(windowId, { path: target });
130
+ },
131
+ [windowId, patch],
132
+ );
133
+
134
+ const back = useCallback(() => step(-1), [step]);
135
+ const forward = useCallback(() => step(1), [step]);
136
+
137
+ const up = useCallback(() => {
138
+ const current = useExplorerStore.getState().slices[windowId];
139
+ if (current?.parent) go(current.parent);
140
+ }, [windowId, go]);
141
+
142
+ const refresh = useCallback(() => {
143
+ if (pathRef.current) void load(pathRef.current);
144
+ }, [load]);
145
+
146
+ const canGoBack = (slice?.historyIndex ?? 0) > 0;
147
+ const canGoForward = slice ? slice.historyIndex < slice.history.length - 1 : false;
148
+
149
+ // A fresh object every render would defeat memo(ListRow) (it receives this as a prop
150
+ // several levels down) even though the callbacks themselves are already stable.
151
+ return useMemo(
152
+ () => ({ go, back, forward, up, refresh, canGoBack, canGoForward }),
153
+ [go, back, forward, up, refresh, canGoBack, canGoForward],
154
+ );
155
+ }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Global open/close state for the single mobile explorer sheet, plus its one UI-only mode
3
+ * flag ("Select").
4
+ *
5
+ * Desktop windows are many and multiplex through `useWindowStore`; the mobile sheet is
6
+ * always exactly one instance (`windowId = MOBILE_EXPLORER_WINDOW_ID`), so a tiny dedicated
7
+ * store is simpler than threading a "mobile" window kind through the desktop window model.
8
+ */
9
+
10
+ import { create } from "zustand";
11
+ import { useExplorerStore } from "./explorer-store";
12
+ import { cachedHomedir, getHostInfo } from "./use-host-info";
13
+
14
+ /** The one explorer-store slice id the mobile sheet ever uses. */
15
+ export const MOBILE_EXPLORER_WINDOW_ID = "mobile";
16
+
17
+ interface MobileExplorerOpenState {
18
+ isOpen: boolean;
19
+ /** "Select" toolbar mode: rows show checkboxes and a tap toggles selection instead of opening. */
20
+ selectMode: boolean;
21
+ close(): void;
22
+ setSelectMode(value: boolean): void;
23
+ }
24
+
25
+ export const useMobileExplorerOpenState = create<MobileExplorerOpenState>((set) => ({
26
+ isOpen: false,
27
+ selectMode: false,
28
+ close: () => set({ isOpen: false, selectMode: false }),
29
+ setSelectMode: (value) => set({ selectMode: value }),
30
+ }));
31
+
32
+ /**
33
+ * Open the mobile sheet, landing on `path`. With no path: an already-open-before instance
34
+ * resumes wherever it was (the slice survives a close, since only `isOpen` is reset), and a
35
+ * first-ever open falls back to the host home directory — same resolution order as the
36
+ * desktop facade.
37
+ */
38
+ export async function openMobileExplorer(path?: string): Promise<void> {
39
+ const store = useExplorerStore.getState();
40
+ const existing = store.slices[MOBILE_EXPLORER_WINDOW_ID];
41
+ let target = path ?? existing?.path ?? cachedHomedir();
42
+ if (!target) {
43
+ try {
44
+ target = (await getHostInfo()).homedir;
45
+ } catch {
46
+ return; // No usable starting directory — nothing sensible to open.
47
+ }
48
+ }
49
+
50
+ if (!existing) {
51
+ store.ensure(MOBILE_EXPLORER_WINDOW_ID, target);
52
+ } else if (path && existing.path !== path) {
53
+ // Jump an already-open instance to a new path, mirroring what `nav.go` does for a
54
+ // window: fresh history, cleared selection — this is a new place, not a back/forward step.
55
+ store.patch(MOBILE_EXPLORER_WINDOW_ID, {
56
+ path: target,
57
+ history: [target],
58
+ historyIndex: 0,
59
+ selection: new Set(),
60
+ anchor: null,
61
+ filter: "",
62
+ inlineEdit: null,
63
+ });
64
+ }
65
+
66
+ useMobileExplorerOpenState.setState({ isOpen: true, selectMode: false });
67
+ }
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Host facts (platform, separator, home, drives, known folders, OS pins) for the sidebar.
3
+ *
4
+ * Every explorer window needs the same answer, and the server call spawns PowerShell /
5
+ * plutil / findmnt, so the result is cached module-wide and the in-flight promise is
6
+ * shared: N windows opening at once produce exactly one request.
7
+ */
8
+
9
+ import { useEffect, useState } from "react";
10
+ import { api } from "@/lib/api-client";
11
+ import type { HostInfo } from "../../../types/system";
12
+
13
+ const CACHE_TTL_MS = 60_000;
14
+
15
+ let cached: { at: number; value: HostInfo } | null = null;
16
+ let inFlight: Promise<HostInfo> | null = null;
17
+ const subscribers = new Set<(value: HostInfo) => void>();
18
+
19
+ function fetchHostInfo(): Promise<HostInfo> {
20
+ if (cached && Date.now() - cached.at < CACHE_TTL_MS) return Promise.resolve(cached.value);
21
+ if (inFlight) return inFlight;
22
+ inFlight = api
23
+ .get<HostInfo>("/api/system/host")
24
+ .then((value) => {
25
+ cached = { at: Date.now(), value };
26
+ for (const notify of subscribers) notify(value);
27
+ return value;
28
+ })
29
+ .finally(() => {
30
+ inFlight = null;
31
+ });
32
+ return inFlight;
33
+ }
34
+
35
+ export interface HostInfoState {
36
+ host: HostInfo | null;
37
+ loading: boolean;
38
+ error: string | null;
39
+ }
40
+
41
+ export function useHostInfo(): HostInfoState {
42
+ const [state, setState] = useState<HostInfoState>(() => ({
43
+ host: cached?.value ?? null,
44
+ loading: cached == null,
45
+ error: null,
46
+ }));
47
+
48
+ useEffect(() => {
49
+ let alive = true;
50
+ const notify = (value: HostInfo) => {
51
+ if (alive) setState({ host: value, loading: false, error: null });
52
+ };
53
+ subscribers.add(notify);
54
+ fetchHostInfo()
55
+ .then(notify)
56
+ .catch((e: unknown) => {
57
+ if (alive) {
58
+ setState({
59
+ host: null,
60
+ loading: false,
61
+ error: e instanceof Error ? e.message : "Failed to read host info",
62
+ });
63
+ }
64
+ });
65
+ return () => {
66
+ alive = false;
67
+ subscribers.delete(notify);
68
+ };
69
+ }, []);
70
+
71
+ return state;
72
+ }
73
+
74
+ /**
75
+ * Host info for callers outside React (command palette, nav rail): resolves from cache
76
+ * when warm, otherwise fetches. Used to pick a sensible starting directory.
77
+ */
78
+ export function getHostInfo(): Promise<HostInfo> {
79
+ return fetchHostInfo();
80
+ }
81
+
82
+ /** Best-effort home directory without waiting for the server. */
83
+ export function cachedHomedir(): string | null {
84
+ return cached?.value.homedir ?? null;
85
+ }
@@ -0,0 +1,126 @@
1
+ /**
2
+ * One row inside a Miller column: icon, name, a drill-in chevron for directories. Split out
3
+ * of `column-view-column.tsx` to keep that file under the size budget — the column container
4
+ * and one of its rows are different concerns (virtualised layout vs. a single interactive
5
+ * element).
6
+ */
7
+
8
+ import { forwardRef, useContext, type HTMLAttributes } from "react";
9
+ import { BottomSheetCtx } from "@/components/ui/mobile-bottom-sheet";
10
+ import { ContextMenu, ContextMenuTrigger } from "@/components/ui/adaptive-context-menu";
11
+ import { useIsMobile } from "@/hooks/use-is-mobile";
12
+ import type { FsEntry } from "@/lib/fs-api";
13
+ import { cn } from "@/lib/utils";
14
+ import type { ExplorerActions } from "../actions/use-explorer-actions";
15
+ import { DROP_TARGET_CLASS } from "../dnd/drop-target-style";
16
+ import { useEntryRowDnd } from "../dnd/use-entry-row-dnd";
17
+ import { ExplorerContextMenu } from "../explorer-context-menu";
18
+ import { FileTypeIcon } from "../icons/file-type-icon";
19
+ import { mobileTapAction } from "../mobile/mobile-tap-action";
20
+ import { useCoarseLongPress } from "../use-coarse-long-press";
21
+ import { composeInteractiveRowProps, type InjectedRowProps } from "./compose-interactive-row-props";
22
+
23
+ export interface ColumnRowProps {
24
+ entry: FsEntry;
25
+ selected: boolean;
26
+ currentDir: string;
27
+ hasClipboard: boolean;
28
+ isPinned(path: string): boolean;
29
+ actions: ExplorerActions;
30
+ rowHeight: number;
31
+ onSelect(): void;
32
+ onOpen(): void;
33
+ }
34
+
35
+ export function ColumnRow({ entry, selected, currentDir, hasClipboard, isPinned, actions, rowHeight, onSelect, onOpen }: ColumnRowProps) {
36
+ // Column view has no multi-select — a drag always carries just this one entry.
37
+ // Spring-load is Column view's own drill-in gesture (`onSelect` already navigates for a
38
+ // directory), so a rested hover reaches a deep folder without dropping first.
39
+ const dnd = useEntryRowDnd({ entry, dragPaths: [entry.path], run: actions.transferInto, springLoad: onSelect });
40
+ return (
41
+ <ContextMenu>
42
+ <ContextMenuTrigger asChild>
43
+ <ColumnRowInteractive
44
+ entry={entry}
45
+ selected={selected}
46
+ rowHeight={rowHeight}
47
+ onSelect={onSelect}
48
+ onOpen={onOpen}
49
+ dnd={dnd.props}
50
+ dropActive={dnd.isDropTarget}
51
+ />
52
+ </ContextMenuTrigger>
53
+ <ExplorerContextMenu
54
+ targets={[entry]}
55
+ currentDir={currentDir}
56
+ hasClipboard={hasClipboard}
57
+ isPinned={isPinned}
58
+ actions={actions}
59
+ />
60
+ </ContextMenu>
61
+ );
62
+ }
63
+
64
+ /**
65
+ * Rendered inside the row's own `<ContextMenu>` — the position `useContext(BottomSheetCtx)`
66
+ * needs to reach this row's own sheet state (see `use-mobile-row-tap.ts` for the same
67
+ * pattern in List/Icons). Column view keeps its existing single-select semantics on mobile —
68
+ * a directory already navigates on tap via `onSelect`; the only mobile addition is opening a
69
+ * viewable file immediately (no double-click on touch) and surfacing the actions sheet for
70
+ * one that has no viewer, instead of a tap that silently does nothing.
71
+ *
72
+ * `forwardRef` plus `composeInteractiveRowProps` over *everything* injected — not just
73
+ * `onContextMenu` — is required here: `asChild` clones this element and merges its own
74
+ * `onContextMenu` (which actually opens the row's menu), a `ref`, pointer handlers,
75
+ * `data-state` and a `style` that suppresses the native touch callout, but a component that
76
+ * doesn't accept and forward all of them keeps them from ever touching the real DOM node.
77
+ */
78
+ export const ColumnRowInteractive = forwardRef<
79
+ HTMLDivElement,
80
+ Pick<ColumnRowProps, "entry" | "selected" | "rowHeight" | "onSelect" | "onOpen"> & InjectedRowProps
81
+ >(function ColumnRowInteractive({ entry, selected, rowHeight, onSelect, onOpen, dnd, dropActive, ...injected }, ref) {
82
+ const longPress = useCoarseLongPress(onSelect);
83
+ const isMobile = useIsMobile();
84
+ const { setOpen } = useContext(BottomSheetCtx);
85
+
86
+ const handleClick = () => {
87
+ onSelect();
88
+ if (!isMobile || entry.type === "directory") return;
89
+ if (mobileTapAction(entry) === "open") onOpen();
90
+ else setOpen(true);
91
+ };
92
+
93
+ const props = composeInteractiveRowProps<Record<string, unknown>>(
94
+ { ...injected, ...longPress, ...dnd },
95
+ {
96
+ role: "option",
97
+ "aria-selected": selected,
98
+ "data-testid": "explorer-column-row",
99
+ "data-path": entry.path,
100
+ title: entry.name,
101
+ style: { height: rowHeight },
102
+ // Radix's own handler is chained in first (it opens this row's menu); only afterwards
103
+ // does this stop the event from also reaching the background trigger further up the DOM.
104
+ onContextMenu: (e: React.MouseEvent) => {
105
+ onSelect();
106
+ e.stopPropagation();
107
+ },
108
+ onClick: handleClick,
109
+ onDoubleClick: onOpen,
110
+ className: cn(
111
+ "flex w-full items-center gap-1.5 px-2 text-[13px] select-none",
112
+ "can-hover:hover:bg-surface-elevated",
113
+ selected && "bg-accent-wash text-text",
114
+ dropActive && DROP_TARGET_CLASS,
115
+ ),
116
+ },
117
+ );
118
+
119
+ return (
120
+ <div ref={ref} {...(props as HTMLAttributes<HTMLDivElement>)}>
121
+ <FileTypeIcon name={entry.name} kind={entry.kind} className="size-4 shrink-0" />
122
+ <span className={cn("flex-1 truncate", selected ? "text-text" : "text-text-2")}>{entry.name}</span>
123
+ {entry.type === "directory" && <span className="text-text-subtle">›</span>}
124
+ </div>
125
+ );
126
+ });
@@ -0,0 +1,112 @@
1
+ /**
2
+ * One fixed-width, independently virtualised and independently scrolled column of the
3
+ * Miller browser. Column view keeps a single highlighted entry per column rather than the
4
+ * multi-select semantics List/Icons use — Finder's own column browser works the same way,
5
+ * and it is what lets "the highlighted entry" double as "which directory the next column
6
+ * shows" with no extra state.
7
+ */
8
+
9
+ import { useRef } from "react";
10
+ import { useVirtualizer } from "@tanstack/react-virtual";
11
+ import type { FsEntry } from "@/lib/fs-api";
12
+ import { cn } from "@/lib/utils";
13
+ import type { ExplorerActions } from "../actions/use-explorer-actions";
14
+ import { DROP_TARGET_CLASS } from "../dnd/drop-target-style";
15
+ import { useDragAutoScroll } from "../dnd/use-drag-auto-scroll";
16
+ import { usePathDropTarget } from "../dnd/use-path-drop-target";
17
+ import { ColumnRow } from "./column-row";
18
+
19
+ export const COLUMN_WIDTH = 220;
20
+ /** Mobile's single full-width column needs a 44px+ touch target, bigger than the desktop
21
+ * (fine/coarse-pointer) `rowHeight` threaded in from `ExplorerBody`. Exported so
22
+ * `column-view-mobile.tsx` can pass a real value instead of a duplicated magic number —
23
+ * the `fullWidth` branch here always wins over whatever `rowHeight` prop it receives. */
24
+ export const ROW_HEIGHT_MOBILE = 48;
25
+
26
+ export interface ColumnViewColumnProps {
27
+ path: string;
28
+ entries: FsEntry[];
29
+ loading: boolean;
30
+ error: string | null;
31
+ selectedPath: string | null;
32
+ isFocused: boolean;
33
+ currentDir: string;
34
+ hasClipboard: boolean;
35
+ isPinned(path: string): boolean;
36
+ actions: ExplorerActions;
37
+ /** Row height in px; larger on coarse-pointer devices, same as List/Icons. */
38
+ rowHeight: number;
39
+ onSelect(entry: FsEntry): void;
40
+ onOpen(entry: FsEntry): void;
41
+ onFocus(): void;
42
+ /** Mobile's single-column mode: fill the container instead of the fixed desktop width. */
43
+ fullWidth?: boolean;
44
+ }
45
+
46
+ export function ColumnViewColumn({
47
+ path, entries, loading, error, selectedPath, isFocused, currentDir,
48
+ hasClipboard, isPinned, actions, rowHeight, onSelect, onOpen, onFocus, fullWidth,
49
+ }: ColumnViewColumnProps) {
50
+ const scrollRef = useRef<HTMLDivElement>(null);
51
+ useDragAutoScroll(scrollRef);
52
+ // Mobile's single full-width column always wants the bigger touch target, regardless of
53
+ // whatever fine/coarse-pointer density the caller threaded in for the desktop Miller strip.
54
+ const effectiveRowHeight = fullWidth ? ROW_HEIGHT_MOBILE : rowHeight;
55
+ const virtualizer = useVirtualizer({
56
+ count: entries.length,
57
+ getScrollElement: () => scrollRef.current,
58
+ estimateSize: () => effectiveRowHeight,
59
+ overscan: 8,
60
+ });
61
+ // The column's own empty area is a drop target for its own directory — dropping below the
62
+ // last row of an ancestor column moves/copies into that ancestor, not just the deepest one.
63
+ // Mobile's single full-width column never gets one: no drag source can ever start on touch.
64
+ const backgroundDrop = usePathDropTarget({ targetDir: path, run: actions.transferInto, disabled: fullWidth });
65
+
66
+ return (
67
+ <div
68
+ className={cn("flex h-full shrink-0 flex-col", !fullWidth && "border-r border-border", isFocused && "bg-panel-2/40")}
69
+ style={fullWidth ? undefined : { width: COLUMN_WIDTH }}
70
+ >
71
+ <div
72
+ ref={scrollRef}
73
+ role="listbox"
74
+ aria-label={path}
75
+ className={cn("flex-1 overflow-y-auto outline-none", backgroundDrop.isOver && DROP_TARGET_CLASS)}
76
+ {...backgroundDrop.handlers}
77
+ >
78
+ {loading && entries.length === 0 && <p className="p-2 text-xs text-text-subtle">Loading…</p>}
79
+ {error && <p className="p-2 text-xs text-error">{error}</p>}
80
+ {!loading && !error && entries.length === 0 && <p className="p-2 text-xs text-text-subtle">Empty</p>}
81
+ <div className="relative w-full" style={{ height: virtualizer.getTotalSize() }}>
82
+ {virtualizer.getVirtualItems().map((item) => {
83
+ const entry = entries[item.index]!;
84
+ const selected = entry.path === selectedPath;
85
+ return (
86
+ <div
87
+ key={entry.path}
88
+ className="absolute left-0 top-0 w-full"
89
+ style={{ transform: `translateY(${item.start}px)` }}
90
+ >
91
+ <ColumnRow
92
+ entry={entry}
93
+ selected={selected}
94
+ currentDir={currentDir}
95
+ hasClipboard={hasClipboard}
96
+ isPinned={isPinned}
97
+ actions={actions}
98
+ rowHeight={effectiveRowHeight}
99
+ onSelect={() => {
100
+ onFocus();
101
+ onSelect(entry);
102
+ }}
103
+ onOpen={() => onOpen(entry)}
104
+ />
105
+ </div>
106
+ );
107
+ })}
108
+ </div>
109
+ </div>
110
+ </div>
111
+ );
112
+ }
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Column view's mobile presentation: only the focused column ever renders, full width, with
3
+ * a Back row above it instead of the desktop's side-by-side Miller strip — there is no room
4
+ * for more than one column on a phone, and no preview pane (files open directly on tap, see
5
+ * `column-view-column.tsx`'s mobile branch).
6
+ */
7
+
8
+ import type { KeyboardEvent, RefObject } from "react";
9
+ import { ArrowLeft } from "lucide-react";
10
+ import { ContextMenu, ContextMenuTrigger } from "@/components/ui/adaptive-context-menu";
11
+ import type { FsEntry } from "@/lib/fs-api";
12
+ import type { ExplorerActions } from "../actions/use-explorer-actions";
13
+ import { ExplorerContextMenu } from "../explorer-context-menu";
14
+ import type { ExplorerSlice } from "../explorer-store";
15
+ import type { LongPressHandlers } from "../use-coarse-long-press";
16
+ import { ColumnViewColumn, ROW_HEIGHT_MOBILE } from "./column-view-column";
17
+ import type { ColumnState } from "./use-column-view-state";
18
+
19
+ export interface ColumnViewMobileProps {
20
+ containerRef: RefObject<HTMLDivElement | null>;
21
+ onKeyDown(event: KeyboardEvent): void;
22
+ backgroundLongPress: LongPressHandlers;
23
+ columns: ColumnState[];
24
+ focusedIndex: number;
25
+ setFocusedIndex(update: (i: number) => number): void;
26
+ selectedPathFor(index: number): string | null;
27
+ slice: ExplorerSlice;
28
+ hasClipboard: boolean;
29
+ isPinned(path: string): boolean;
30
+ actions: ExplorerActions;
31
+ onSelect(index: number, entry: FsEntry): void;
32
+ onOpen(entry: FsEntry): void;
33
+ }
34
+
35
+ export function ColumnViewMobile({
36
+ containerRef, onKeyDown, backgroundLongPress, columns, focusedIndex, setFocusedIndex,
37
+ selectedPathFor, slice, hasClipboard, isPinned, actions, onSelect, onOpen,
38
+ }: ColumnViewMobileProps) {
39
+ const current = columns[focusedIndex];
40
+
41
+ return (
42
+ <ContextMenu>
43
+ <ContextMenuTrigger asChild>
44
+ <div
45
+ ref={containerRef}
46
+ tabIndex={0}
47
+ data-testid="explorer-columns"
48
+ aria-label="Column view"
49
+ onKeyDown={onKeyDown}
50
+ className="flex h-full min-h-0 w-full flex-col outline-none"
51
+ {...backgroundLongPress}
52
+ >
53
+ {focusedIndex > 0 && (
54
+ <button
55
+ type="button"
56
+ onClick={() => setFocusedIndex((i) => i - 1)}
57
+ aria-label="Back"
58
+ className="flex h-11 shrink-0 items-center gap-1.5 border-b border-border px-3 text-sm text-text-2 active:bg-surface-elevated"
59
+ >
60
+ <ArrowLeft className="size-4" /> Back
61
+ </button>
62
+ )}
63
+ {current && (
64
+ <div
65
+ key={current.path}
66
+ className="min-h-0 flex-1 motion-safe:animate-in motion-safe:slide-in-from-right-4 motion-safe:duration-150"
67
+ >
68
+ <ColumnViewColumn
69
+ path={current.path}
70
+ entries={current.entries}
71
+ loading={current.loading}
72
+ error={current.error}
73
+ selectedPath={selectedPathFor(focusedIndex)}
74
+ isFocused
75
+ fullWidth
76
+ rowHeight={ROW_HEIGHT_MOBILE}
77
+ currentDir={slice.path}
78
+ hasClipboard={hasClipboard}
79
+ isPinned={isPinned}
80
+ actions={actions}
81
+ onSelect={(entry) => onSelect(focusedIndex, entry)}
82
+ onOpen={onOpen}
83
+ onFocus={() => {}}
84
+ />
85
+ </div>
86
+ )}
87
+ </div>
88
+ </ContextMenuTrigger>
89
+ <ExplorerContextMenu
90
+ targets={[]}
91
+ currentDir={slice.path}
92
+ hasClipboard={hasClipboard}
93
+ isPinned={isPinned}
94
+ actions={actions}
95
+ />
96
+ </ContextMenu>
97
+ );
98
+ }