@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,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)
@@ -7,6 +7,7 @@
7
7
  * Same component names, same API — behavior adapts automatically.
8
8
  */
9
9
  import React, { useState, useRef, useCallback, type ReactNode } from "react";
10
+ import { CircleIcon } from "lucide-react";
10
11
  import * as Radix from "./context-menu";
11
12
  import { useIsMobile } from "@/hooks/use-is-mobile";
12
13
  import { cn } from "@/lib/utils";
@@ -19,10 +20,15 @@ import {
19
20
  BottomSheetSubContent,
20
21
  } from "./mobile-bottom-sheet";
21
22
 
22
- const LONG_PRESS_MS = 500;
23
+ // Shared with the desktop-width coarse-pointer path (use-coarse-long-press.ts) — both must
24
+ // agree so a synthetic contextmenu fired early isn't swallowed by this trigger's own timer.
25
+ const LONG_PRESS_MS = 400;
23
26
 
24
27
  const IsMobileCtx = React.createContext(false);
25
28
 
29
+ /** Carries a radio group's active value/setter down to its flattened mobile items. */
30
+ const RadioGroupCtx = React.createContext<{ value?: string; onValueChange?(value: string): void }>({});
31
+
26
32
  /* ------------------------------------------------------------------ */
27
33
  /* Root */
28
34
  /* ------------------------------------------------------------------ */
@@ -187,6 +193,54 @@ function ContextMenuSeparator({
187
193
  return <BottomSheetSeparator className={className} />;
188
194
  }
189
195
 
196
+ /* ------------------------------------------------------------------ */
197
+ /* Radio group / item (flattened to a checkable list on mobile) */
198
+ /* ------------------------------------------------------------------ */
199
+
200
+ function ContextMenuRadioGroup({
201
+ value,
202
+ onValueChange,
203
+ children,
204
+ ...props
205
+ }: React.ComponentProps<typeof Radix.ContextMenuRadioGroup>) {
206
+ const isMobile = React.useContext(IsMobileCtx);
207
+ if (!isMobile) {
208
+ return (
209
+ <Radix.ContextMenuRadioGroup value={value} onValueChange={onValueChange} {...props}>
210
+ {children}
211
+ </Radix.ContextMenuRadioGroup>
212
+ );
213
+ }
214
+ return <RadioGroupCtx.Provider value={{ value, onValueChange }}>{children}</RadioGroupCtx.Provider>;
215
+ }
216
+
217
+ function ContextMenuRadioItem({
218
+ value,
219
+ children,
220
+ className,
221
+ ...props
222
+ }: React.ComponentProps<typeof Radix.ContextMenuRadioItem>) {
223
+ const isMobile = React.useContext(IsMobileCtx);
224
+ // Both contexts are read unconditionally so the hook order stays stable when
225
+ // the viewport crosses the mobile breakpoint while a menu is mounted.
226
+ const { value: active, onValueChange } = React.useContext(RadioGroupCtx);
227
+ if (!isMobile) {
228
+ return (
229
+ <Radix.ContextMenuRadioItem value={value} className={className} {...props}>
230
+ {children}
231
+ </Radix.ContextMenuRadioItem>
232
+ );
233
+ }
234
+ return (
235
+ <BottomSheetItem className={className} onClick={() => onValueChange?.(value)}>
236
+ <span className="flex size-4 shrink-0 items-center justify-center">
237
+ {active === value && <CircleIcon className="size-2 fill-current" />}
238
+ </span>
239
+ {children}
240
+ </BottomSheetItem>
241
+ );
242
+ }
243
+
190
244
  /* ------------------------------------------------------------------ */
191
245
  /* Sub-menu (flattened on mobile) */
192
246
  /* ------------------------------------------------------------------ */
@@ -239,6 +293,8 @@ export {
239
293
  ContextMenuContent,
240
294
  ContextMenuItem,
241
295
  ContextMenuSeparator,
296
+ ContextMenuRadioGroup,
297
+ ContextMenuRadioItem,
242
298
  ContextMenuSub,
243
299
  ContextMenuSubTrigger,
244
300
  ContextMenuSubContent,
@@ -11,6 +11,7 @@ import {
11
11
  Download, ChevronRight, ArrowLeft, Search, Loader2, Clock, Eye, EyeOff,
12
12
  } from "lucide-react";
13
13
  import { cn } from "@/lib/utils";
14
+ import { formatRelativeTime, formatSize } from "@/components/os-explorer/format-file-meta";
14
15
 
15
16
  // ── Types ──────────────────────────────────────────────────────────
16
17
 
@@ -60,24 +61,6 @@ function saveRecent(dirPath: string): void {
60
61
  localStorage.setItem(RECENT_KEY, JSON.stringify(updated));
61
62
  }
62
63
 
63
- function formatSize(bytes?: number): string {
64
- if (bytes == null) return "";
65
- if (bytes < 1024) return `${bytes} B`;
66
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
67
- return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
68
- }
69
-
70
- function formatRelativeTime(iso: string): string {
71
- const diff = Date.now() - new Date(iso).getTime();
72
- const mins = Math.floor(diff / 60000);
73
- if (mins < 1) return "now";
74
- if (mins < 60) return `${mins}m ago`;
75
- const hrs = Math.floor(mins / 60);
76
- if (hrs < 24) return `${hrs}h ago`;
77
- const days = Math.floor(hrs / 24);
78
- return `${days}d ago`;
79
- }
80
-
81
64
  function fileIcon(entry: BrowseEntry): React.ReactNode {
82
65
  if (entry.type === "directory") return <Folder className="size-4 text-primary" />;
83
66
  const ext = entry.name.split(".").pop()?.toLowerCase();
@@ -1,24 +1,26 @@
1
1
  import { api, projectUrl } from "./api-client";
2
+ import { basename } from "./utils";
2
3
 
3
4
  /** Trigger browser-native file download via hidden <a> tag */
4
5
  export async function downloadFile(projectName: string, filePath: string): Promise<void> {
5
6
  // Absolute paths (external files opened from filesystem browser) use /api/fs routes
6
7
  const isAbsolute = /^(\/|[A-Za-z]:[/\\])/.test(filePath);
7
8
  if (isAbsolute) {
8
- const { token } = await api.post<{ token: string }>("/api/fs/download/token");
9
+ // The token is issued for this exact file and spent on first use.
10
+ const { token } = await api.post<{ token: string }>("/api/fs/download/token", { path: filePath });
9
11
  const url = `/api/fs/raw?path=${encodeURIComponent(filePath)}&download=true&dl_token=${encodeURIComponent(token)}`;
10
- triggerDownload(url, filePath.split("/").pop() ?? "download");
12
+ triggerDownload(url, basename(filePath));
11
13
  return;
12
14
  }
13
15
  const { token } = await api.post<{ token: string }>(`${projectUrl(projectName)}/files/download/token`);
14
16
  const url = `${projectUrl(projectName)}/files/raw?path=${encodeURIComponent(filePath)}&download=true&dl_token=${encodeURIComponent(token)}`;
15
- triggerDownload(url, filePath.split("/").pop() ?? "download");
17
+ triggerDownload(url, basename(filePath));
16
18
  }
17
19
 
18
20
  /** Trigger browser-native folder zip download */
19
21
  export async function downloadFolder(projectName: string, folderPath: string): Promise<void> {
20
22
  const { token } = await api.post<{ token: string }>(`${projectUrl(projectName)}/files/download/token`);
21
- const folderName = folderPath.split("/").pop() ?? "folder";
23
+ const folderName = basename(folderPath) || "folder";
22
24
  const url = `${projectUrl(projectName)}/files/download/zip?path=${encodeURIComponent(folderPath)}&dl_token=${encodeURIComponent(token)}`;
23
25
  triggerDownload(url, `${folderName}.zip`);
24
26
  }