@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,198 @@
1
+ /**
2
+ * Places sidebar: PPM pins, the OS's own pinned folders, the known folders, and the
3
+ * drives.
4
+ *
5
+ * Host-info provider failures arrive as `warnings[]` rather than as an error — a Mac
6
+ * without Full Disk Access still has drives and known folders — so they render as a muted
7
+ * hint row instead of replacing the sidebar.
8
+ */
9
+
10
+ import type { ComponentType } from "react";
11
+ import { AlertTriangle, HardDrive, Pin, Usb, Network } from "lucide-react";
12
+ import type { HostInfo, Drive } from "../../../types/system";
13
+ import { cn } from "@/lib/utils";
14
+ import { DROP_TARGET_CLASS } from "./dnd/drop-target-style";
15
+ import type { DropRunner } from "./dnd/entry-drop-executor";
16
+ import { usePathDropTarget } from "./dnd/use-path-drop-target";
17
+ import { useDropTransfer } from "./dnd/use-drop-transfer";
18
+ import { useExplorerPinsStore } from "./explorer-pins-store";
19
+ import { FileTypeIcon } from "./icons/file-type-icon";
20
+ import type { SkinVocab } from "./skins/skin-types";
21
+ import { usePrefersCoarsePointer } from "./use-coarse-long-press";
22
+
23
+ /** A folder-type place row draws the current skin's folder glyph via `FileTypeIcon`'s context. */
24
+ function FolderPlaceIcon({ className }: { className?: string }) {
25
+ return <FileTypeIcon name="" kind="directory" className={cn("size-4 shrink-0", className)} />;
26
+ }
27
+
28
+ const DRIVE_ICON: Record<Drive["kind"], typeof HardDrive> = {
29
+ fixed: HardDrive,
30
+ removable: Usb,
31
+ network: Network,
32
+ unknown: HardDrive,
33
+ };
34
+
35
+ type PlaceIcon = ComponentType<{ className?: string }>;
36
+
37
+ interface PlaceProps {
38
+ label: string;
39
+ path: string;
40
+ active: boolean;
41
+ icon: PlaceIcon;
42
+ onNavigate(path: string): void;
43
+ onUnpin?(): void;
44
+ /** Every place is a directory, so a drop always has somewhere valid to land. */
45
+ run: DropRunner;
46
+ }
47
+
48
+ function Place({ label, path, active, icon: Icon, onNavigate, onUnpin, run }: PlaceProps) {
49
+ // Gated on real pointer coarseness, not a `md:` breakpoint — an iPad is ≥768px but has no
50
+ // mouse, so a breakpoint-only rule was silently dropping the 44px minimum for it.
51
+ const coarse = usePrefersCoarsePointer();
52
+ const drop = usePathDropTarget({ targetDir: path, run });
53
+ return (
54
+ <div className="group/place relative flex items-center">
55
+ <button
56
+ type="button"
57
+ title={path}
58
+ onClick={() => onNavigate(path)}
59
+ className={cn(
60
+ "flex w-full items-center gap-2 rounded px-2 py-1 text-left text-[13px]",
61
+ coarse ? "min-h-[44px]" : "min-h-0",
62
+ "can-hover:hover:bg-surface-elevated",
63
+ active ? "bg-accent-wash text-text" : "text-text-2",
64
+ drop.isOver && DROP_TARGET_CLASS,
65
+ )}
66
+ {...drop.handlers}
67
+ >
68
+ <Icon className="size-4 shrink-0 text-text-subtle" />
69
+ <span className="truncate">{label}</span>
70
+ </button>
71
+ {onUnpin && (
72
+ <button
73
+ type="button"
74
+ aria-label={`Unpin ${label}`}
75
+ title="Unpin"
76
+ onClick={onUnpin}
77
+ // Always reachable on touch; only revealed on hover where hovering exists.
78
+ className="absolute right-1 rounded p-1 text-text-subtle can-hover:opacity-0 can-hover:group-hover/place:opacity-100 can-hover:hover:text-text"
79
+ >
80
+ <Pin className="size-3.5" />
81
+ </button>
82
+ )}
83
+ </div>
84
+ );
85
+ }
86
+
87
+ function Section({ title, children }: { title: string; children: React.ReactNode }) {
88
+ return (
89
+ <div className="pb-2">
90
+ <p className="px-2 pb-0.5 pt-2 text-[10px] font-semibold uppercase tracking-wide text-text-subtle">
91
+ {title}
92
+ </p>
93
+ {children}
94
+ </div>
95
+ );
96
+ }
97
+
98
+ /** Used when a caller renders the sidebar without a resolved skin (defensive default). */
99
+ const DEFAULT_VOCAB: SkinVocab = { home: "Home", pinned: "Quick access", known: "Folders", drives: "Drives" };
100
+
101
+ export interface ExplorerSidebarProps {
102
+ host: HostInfo | null;
103
+ currentPath: string;
104
+ onNavigate(path: string): void;
105
+ /** Section labels for the current OS skin — Finder says "Favorites"/"Locations". */
106
+ vocab?: SkinVocab;
107
+ }
108
+
109
+ export function ExplorerSidebar({ host, currentPath, onNavigate, vocab = DEFAULT_VOCAB }: ExplorerSidebarProps) {
110
+ const pins = useExplorerPinsStore((s) => s.pins);
111
+ const unpin = useExplorerPinsStore((s) => s.unpin);
112
+ const isActive = (path: string) => path === currentPath;
113
+ // One drop-transfer context for the whole sidebar — every place lands through the same
114
+ // collision prompt rather than each row owning its own dialog state.
115
+ const { run, prompts } = useDropTransfer(host?.sep ?? "/", host?.platform);
116
+
117
+ return (
118
+ <aside
119
+ aria-label="Places"
120
+ data-testid="explorer-sidebar"
121
+ className="hidden w-44 shrink-0 overflow-y-auto border-r border-border bg-[var(--x-sidebar-bg,var(--panel-2))] px-1 py-1 sm:block"
122
+ >
123
+ {pins.length > 0 && (
124
+ <Section title="Pinned">
125
+ {pins.map((pin) => (
126
+ <Place
127
+ key={pin.path}
128
+ label={pin.name}
129
+ path={pin.path}
130
+ icon={Pin}
131
+ active={isActive(pin.path)}
132
+ onNavigate={onNavigate}
133
+ onUnpin={() => unpin(pin.path)}
134
+ run={run}
135
+ />
136
+ ))}
137
+ </Section>
138
+ )}
139
+
140
+ {host && host.pinned.length > 0 && (
141
+ <Section title={vocab.pinned}>
142
+ {host.pinned.map((entry) => (
143
+ <Place
144
+ key={entry.path}
145
+ label={entry.name}
146
+ path={entry.path}
147
+ icon={FolderPlaceIcon}
148
+ active={isActive(entry.path)}
149
+ onNavigate={onNavigate}
150
+ run={run}
151
+ />
152
+ ))}
153
+ </Section>
154
+ )}
155
+
156
+ {host && (
157
+ <Section title={vocab.known}>
158
+ <Place label={vocab.home} path={host.homedir} icon={FolderPlaceIcon} active={isActive(host.homedir)} onNavigate={onNavigate} run={run} />
159
+ {host.knownFolders.map((folder) => (
160
+ <Place
161
+ key={folder.path}
162
+ label={folder.name}
163
+ path={folder.path}
164
+ icon={FolderPlaceIcon}
165
+ active={isActive(folder.path)}
166
+ onNavigate={onNavigate}
167
+ run={run}
168
+ />
169
+ ))}
170
+ </Section>
171
+ )}
172
+
173
+ {host && host.drives.length > 0 && (
174
+ <Section title={vocab.drives}>
175
+ {host.drives.map((drive) => (
176
+ <Place
177
+ key={drive.path}
178
+ label={drive.label ? `${drive.label} (${drive.name})` : drive.name}
179
+ path={drive.path}
180
+ icon={DRIVE_ICON[drive.kind]}
181
+ active={isActive(drive.path)}
182
+ onNavigate={onNavigate}
183
+ run={run}
184
+ />
185
+ ))}
186
+ </Section>
187
+ )}
188
+
189
+ {host?.warnings.map((warning) => (
190
+ <p key={warning} className="flex gap-1.5 px-2 py-1 text-[11px] leading-snug text-text-subtle">
191
+ <AlertTriangle className="mt-0.5 size-3 shrink-0" />
192
+ <span>{warning}</span>
193
+ </p>
194
+ ))}
195
+ {prompts}
196
+ </aside>
197
+ );
198
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Toolbar "Sort" menu: the only sort control reachable from Icons and Column view, which
3
+ * have no clickable column header of their own (List view keeps that shortcut too — see
4
+ * `views/list-view.tsx`). Backed by the same `prefs.sort` every view already reads.
5
+ */
6
+
7
+ import { ArrowUpDown } from "lucide-react";
8
+ import {
9
+ DropdownMenu,
10
+ DropdownMenuContent,
11
+ DropdownMenuLabel,
12
+ DropdownMenuRadioGroup,
13
+ DropdownMenuRadioItem,
14
+ DropdownMenuSeparator,
15
+ DropdownMenuTrigger,
16
+ } from "@/components/ui/dropdown-menu";
17
+ import { useExplorerStore, type SortDir, type SortKey } from "./explorer-store";
18
+ import { SORT_DIR_OPTIONS, SORT_KEY_OPTIONS, sortDirLabel, sortKeyLabel } from "./sort-options";
19
+ import { toolbarButtonClass } from "./toolbar-icon-button";
20
+
21
+ export interface ExplorerSortMenuProps {
22
+ coarse: boolean;
23
+ }
24
+
25
+ export function ExplorerSortMenu({ coarse }: ExplorerSortMenuProps) {
26
+ const sort = useExplorerStore((s) => s.sort);
27
+ const setPrefs = useExplorerStore((s) => s.setPrefs);
28
+
29
+ // No skin currently defines a `vocab` slot for this label, so both the Windows and
30
+ // macOS chrome fall back to the same plain word.
31
+ const label = "Sort";
32
+ const title = `${label}: ${sortKeyLabel(sort.key)}, ${sortDirLabel(sort.dir)}`;
33
+
34
+ return (
35
+ <DropdownMenu>
36
+ <DropdownMenuTrigger asChild>
37
+ <button type="button" aria-label={label} title={title} className={toolbarButtonClass(coarse)}>
38
+ <ArrowUpDown className="size-4" />
39
+ </button>
40
+ </DropdownMenuTrigger>
41
+ <DropdownMenuContent align="end">
42
+ <DropdownMenuLabel>Sort by</DropdownMenuLabel>
43
+ <DropdownMenuRadioGroup
44
+ value={sort.key}
45
+ onValueChange={(key) => setPrefs({ sort: { key: key as SortKey, dir: sort.dir } })}
46
+ >
47
+ {SORT_KEY_OPTIONS.map(({ key, label: keyLabel }) => (
48
+ <DropdownMenuRadioItem key={key} value={key}>
49
+ {keyLabel}
50
+ </DropdownMenuRadioItem>
51
+ ))}
52
+ </DropdownMenuRadioGroup>
53
+ <DropdownMenuSeparator />
54
+ <DropdownMenuLabel>Order</DropdownMenuLabel>
55
+ <DropdownMenuRadioGroup
56
+ value={sort.dir}
57
+ onValueChange={(dir) => setPrefs({ sort: { key: sort.key, dir: dir as SortDir } })}
58
+ >
59
+ {SORT_DIR_OPTIONS.map(({ dir, label: dirLabel }) => (
60
+ <DropdownMenuRadioItem key={dir} value={dir}>
61
+ {dirLabel}
62
+ </DropdownMenuRadioItem>
63
+ ))}
64
+ </DropdownMenuRadioGroup>
65
+ </DropdownMenuContent>
66
+ </DropdownMenu>
67
+ );
68
+ }
@@ -0,0 +1,35 @@
1
+ /** Item count, selection count and the selected size — the strip along the window's foot. */
2
+
3
+ import type { FsEntry } from "@/lib/fs-api";
4
+ import { formatSize } from "./format-file-meta";
5
+ import { totalSize } from "./sort-and-filter-entries";
6
+
7
+ export interface ExplorerStatusBarProps {
8
+ /** Rows currently visible (after filtering). */
9
+ entries: FsEntry[];
10
+ selection: Set<string>;
11
+ /** True when the server capped the listing, so the counts are a lower bound. */
12
+ truncated: boolean;
13
+ }
14
+
15
+ export function ExplorerStatusBar({ entries, selection, truncated }: ExplorerStatusBarProps) {
16
+ const selected = entries.filter((e) => selection.has(e.path));
17
+ const parts = [`${entries.length}${truncated ? "+" : ""} item${entries.length === 1 ? "" : "s"}`];
18
+ if (selected.length > 0) {
19
+ parts.push(`${selected.length} selected`);
20
+ const bytes = totalSize(selected);
21
+ if (bytes > 0) parts.push(formatSize(bytes));
22
+ }
23
+
24
+ return (
25
+ <div
26
+ data-testid="explorer-status"
27
+ className="flex shrink-0 items-center gap-2 border-t border-border bg-panel-2 px-2 py-1 text-[11px] text-text-subtle"
28
+ >
29
+ {parts.join(" · ")}
30
+ {truncated && (
31
+ <span className="ml-auto">Listing capped — narrow the filter to see the rest.</span>
32
+ )}
33
+ </div>
34
+ );
35
+ }
@@ -0,0 +1,179 @@
1
+ /**
2
+ * Per-window explorer state, keyed by floating-window id.
3
+ *
4
+ * Each window browses independently (own path, history, selection, filter), so the state
5
+ * cannot live in a single global slice. View preferences are the exception: sort, view
6
+ * mode and the hidden-files toggle are user settings and are shared plus persisted.
7
+ *
8
+ * Windows also have to agree about the disk. After any mutation the acting window calls
9
+ * `fsChanged(dir)` and every window currently showing that directory refetches, which is
10
+ * what makes "cut here, paste there" look instantaneous in both frames.
11
+ */
12
+
13
+ import { create } from "zustand";
14
+ import type { FsBreadcrumb, FsEntry } from "@/lib/fs-api";
15
+
16
+ export type SortKey = "name" | "size" | "modified" | "kind";
17
+ export type SortDir = "asc" | "desc";
18
+ export type ViewMode = "list" | "icons" | "columns";
19
+
20
+ export interface ExplorerError {
21
+ message: string;
22
+ code: string;
23
+ hint?: string;
24
+ }
25
+
26
+ /** Inline editing state: renaming an existing entry, or naming a new one. */
27
+ export type InlineEdit =
28
+ | { kind: "rename"; path: string; initial: string }
29
+ | { kind: "new-file" | "new-folder"; initial: string };
30
+
31
+ export interface ExplorerSlice {
32
+ path: string;
33
+ /** Visited paths; `historyIndex` points at the current one. */
34
+ history: string[];
35
+ historyIndex: number;
36
+ entries: FsEntry[];
37
+ breadcrumbs: FsBreadcrumb[];
38
+ parent: string | null;
39
+ sep: string;
40
+ truncated: boolean;
41
+ loading: boolean;
42
+ error: ExplorerError | null;
43
+ selection: Set<string>;
44
+ /** Range-selection anchor; shift-click extends from here. */
45
+ anchor: string | null;
46
+ filter: string;
47
+ inlineEdit: InlineEdit | null;
48
+ }
49
+
50
+ export interface ExplorerViewPrefs {
51
+ sort: { key: SortKey; dir: SortDir };
52
+ viewMode: ViewMode;
53
+ showHidden: boolean;
54
+ }
55
+
56
+ const VIEW_PREFS_KEY = "ppm-explorer-view";
57
+
58
+ const DEFAULT_PREFS: ExplorerViewPrefs = {
59
+ sort: { key: "name", dir: "asc" },
60
+ viewMode: "list",
61
+ showHidden: false,
62
+ };
63
+
64
+ function loadPrefs(): ExplorerViewPrefs {
65
+ try {
66
+ const raw = localStorage.getItem(VIEW_PREFS_KEY);
67
+ if (!raw) return DEFAULT_PREFS;
68
+ const parsed = JSON.parse(raw) as Partial<ExplorerViewPrefs>;
69
+ return {
70
+ sort: parsed.sort?.key ? parsed.sort : DEFAULT_PREFS.sort,
71
+ viewMode: parsed.viewMode ?? DEFAULT_PREFS.viewMode,
72
+ showHidden: parsed.showHidden ?? DEFAULT_PREFS.showHidden,
73
+ };
74
+ } catch {
75
+ return DEFAULT_PREFS;
76
+ }
77
+ }
78
+
79
+ function savePrefs(prefs: ExplorerViewPrefs): void {
80
+ try {
81
+ localStorage.setItem(VIEW_PREFS_KEY, JSON.stringify(prefs));
82
+ } catch {
83
+ /* quota or private mode — preferences simply do not persist */
84
+ }
85
+ }
86
+
87
+ function emptySlice(path: string): ExplorerSlice {
88
+ return {
89
+ path,
90
+ history: [path],
91
+ historyIndex: 0,
92
+ entries: [],
93
+ breadcrumbs: [],
94
+ parent: null,
95
+ sep: "/",
96
+ truncated: false,
97
+ loading: true,
98
+ error: null,
99
+ selection: new Set(),
100
+ anchor: null,
101
+ filter: "",
102
+ inlineEdit: null,
103
+ };
104
+ }
105
+
106
+ interface ExplorerStore extends ExplorerViewPrefs {
107
+ slices: Record<string, ExplorerSlice>;
108
+ /** Create the slice for a window if it does not exist yet. */
109
+ ensure(id: string, path: string): void;
110
+ /** Drop a window's slice when it closes. */
111
+ discard(id: string): void;
112
+ patch(id: string, partial: Partial<ExplorerSlice>): void;
113
+ setPrefs(partial: Partial<ExplorerViewPrefs>): void;
114
+ }
115
+
116
+ export const useExplorerStore = create<ExplorerStore>((set, get) => ({
117
+ ...loadPrefs(),
118
+ slices: {},
119
+
120
+ ensure: (id, path) => {
121
+ if (get().slices[id]) return;
122
+ set((s) => ({ slices: { ...s.slices, [id]: emptySlice(path) } }));
123
+ },
124
+
125
+ discard: (id) => {
126
+ set((s) => {
127
+ if (!s.slices[id]) return s;
128
+ const slices = { ...s.slices };
129
+ delete slices[id];
130
+ return { slices };
131
+ });
132
+ },
133
+
134
+ patch: (id, partial) => {
135
+ set((s) => {
136
+ const current = s.slices[id];
137
+ if (!current) return s;
138
+ return { slices: { ...s.slices, [id]: { ...current, ...partial } } };
139
+ });
140
+ },
141
+
142
+ setPrefs: (partial) => {
143
+ set((s) => {
144
+ const next: ExplorerViewPrefs = {
145
+ sort: partial.sort ?? s.sort,
146
+ viewMode: partial.viewMode ?? s.viewMode,
147
+ showHidden: partial.showHidden ?? s.showHidden,
148
+ };
149
+ savePrefs(next);
150
+ return next;
151
+ });
152
+ },
153
+ }));
154
+
155
+ /** Read a window's slice outside React (action modules, keyboard handlers). */
156
+ export function sliceOf(id: string): ExplorerSlice | undefined {
157
+ return useExplorerStore.getState().slices[id];
158
+ }
159
+
160
+ // ── Cross-window refresh ───────────────────────────────────────────
161
+
162
+ type DirListener = (dir: string) => void;
163
+ const dirListeners = new Set<DirListener>();
164
+
165
+ /**
166
+ * Announce that `dir`'s contents changed on disk. Every window showing that
167
+ * directory — including the one that caused the change — refetches.
168
+ */
169
+ export function fsChanged(...dirs: (string | null | undefined)[]): void {
170
+ for (const dir of dirs) {
171
+ if (!dir) continue;
172
+ for (const listener of dirListeners) listener(dir);
173
+ }
174
+ }
175
+
176
+ export function onFsChanged(listener: DirListener): () => void {
177
+ dirListeners.add(listener);
178
+ return () => dirListeners.delete(listener);
179
+ }
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Window toolbar: history navigation, the breadcrumb (switchable to a raw path field),
3
+ * a name filter, the view switch and the hidden-files toggle.
4
+ *
5
+ * The path field is a deliberate toggle rather than a click-to-edit breadcrumb: pasting a
6
+ * path is the fastest way to reach a deep folder, and a breadcrumb that turns into a field
7
+ * on click makes the crumbs themselves hard to hit.
8
+ */
9
+
10
+ import { useEffect, useState } from "react";
11
+ import {
12
+ ArrowLeft, ArrowRight, ArrowUp, Columns3, Eye, EyeOff, LayoutGrid, List, PencilLine, RefreshCw, Search, X,
13
+ } from "lucide-react";
14
+ import { cn } from "@/lib/utils";
15
+ import { ExplorerBreadcrumb } from "./explorer-breadcrumb";
16
+ import { ExplorerSortMenu } from "./explorer-sort-menu";
17
+ import { useExplorerStore, type ExplorerSlice, type ViewMode } from "./explorer-store";
18
+ import { toolbarButtonClass as buttonClass } from "./toolbar-icon-button";
19
+ import { usePrefersCoarsePointer } from "./use-coarse-long-press";
20
+ import type { ExplorerNavigation } from "./use-explorer-navigation";
21
+ import { AVAILABLE_VIEW_MODES } from "./views/explorer-view-registry";
22
+
23
+ const VIEW_ICON: Record<ViewMode, typeof List> = { list: List, icons: LayoutGrid, columns: Columns3 };
24
+ const VIEW_LABEL: Record<ViewMode, string> = { list: "List view", icons: "Icons view", columns: "Column view" };
25
+
26
+ export interface ExplorerToolbarProps {
27
+ windowId: string;
28
+ slice: ExplorerSlice;
29
+ nav: ExplorerNavigation;
30
+ }
31
+
32
+ export function ExplorerToolbar({ windowId, slice, nav }: ExplorerToolbarProps) {
33
+ const patch = useExplorerStore((s) => s.patch);
34
+ const showHidden = useExplorerStore((s) => s.showHidden);
35
+ const viewMode = useExplorerStore((s) => s.viewMode);
36
+ const setPrefs = useExplorerStore((s) => s.setPrefs);
37
+
38
+ const [editingPath, setEditingPath] = useState(false);
39
+ const [draftPath, setDraftPath] = useState(slice.path);
40
+ useEffect(() => setDraftPath(slice.path), [slice.path]);
41
+ const coarse = usePrefersCoarsePointer();
42
+
43
+ return (
44
+ <div className="flex shrink-0 flex-wrap items-center gap-1 border-b border-border bg-[var(--x-toolbar-bg,var(--panel-2))] px-1.5 py-1">
45
+ <button type="button" aria-label="Back" title="Back" disabled={!nav.canGoBack} onClick={nav.back} className={buttonClass(coarse)}>
46
+ <ArrowLeft className="size-4" />
47
+ </button>
48
+ <button type="button" aria-label="Forward" title="Forward" disabled={!nav.canGoForward} onClick={nav.forward} className={buttonClass(coarse)}>
49
+ <ArrowRight className="size-4" />
50
+ </button>
51
+ <button type="button" aria-label="Up one level" title="Up" disabled={!slice.parent} onClick={nav.up} className={buttonClass(coarse)}>
52
+ <ArrowUp className="size-4" />
53
+ </button>
54
+ <button type="button" aria-label="Refresh" title="Refresh" onClick={nav.refresh} className={buttonClass(coarse)}>
55
+ <RefreshCw className={cn("size-4", slice.loading && "animate-spin")} />
56
+ </button>
57
+
58
+ <div className="flex min-w-[8rem] flex-1 items-center gap-1 rounded border border-border bg-panel px-1">
59
+ {editingPath ? (
60
+ <input
61
+ autoFocus
62
+ aria-label="Path"
63
+ value={draftPath}
64
+ onChange={(e) => setDraftPath(e.target.value)}
65
+ onBlur={() => setEditingPath(false)}
66
+ onKeyDown={(e) => {
67
+ e.stopPropagation();
68
+ if (e.key === "Enter") { nav.go(draftPath.trim()); setEditingPath(false); }
69
+ else if (e.key === "Escape") { setDraftPath(slice.path); setEditingPath(false); }
70
+ }}
71
+ className="min-w-0 flex-1 bg-transparent py-1 font-mono text-xs text-text outline-none"
72
+ />
73
+ ) : (
74
+ <ExplorerBreadcrumb crumbs={slice.breadcrumbs} onNavigate={nav.go} sep={slice.sep} className="flex-1 overflow-hidden py-0.5" />
75
+ )}
76
+ <button
77
+ type="button"
78
+ aria-label={editingPath ? "Show breadcrumb" : "Edit path"}
79
+ title={editingPath ? "Show breadcrumb" : "Edit path"}
80
+ onClick={() => setEditingPath((v) => !v)}
81
+ className={buttonClass(coarse)}
82
+ >
83
+ <PencilLine className="size-3.5" />
84
+ </button>
85
+ </div>
86
+
87
+ <div className="flex items-center gap-1 rounded border border-border bg-panel px-1">
88
+ <Search className="size-3.5 shrink-0 text-text-subtle" />
89
+ <input
90
+ aria-label="Filter entries"
91
+ placeholder="Filter"
92
+ value={slice.filter}
93
+ onChange={(e) => patch(windowId, { filter: e.target.value })}
94
+ onKeyDown={(e) => {
95
+ e.stopPropagation();
96
+ if (e.key === "Escape") patch(windowId, { filter: "" });
97
+ }}
98
+ className="w-24 min-w-0 bg-transparent py-1 text-xs text-text outline-none placeholder:text-text-subtle"
99
+ />
100
+ {slice.filter && (
101
+ <button type="button" aria-label="Clear filter" onClick={() => patch(windowId, { filter: "" })} className={buttonClass(coarse)}>
102
+ <X className="size-3.5" />
103
+ </button>
104
+ )}
105
+ </div>
106
+
107
+ {AVAILABLE_VIEW_MODES.length > 1 &&
108
+ AVAILABLE_VIEW_MODES.map((mode) => {
109
+ const Icon = VIEW_ICON[mode];
110
+ return (
111
+ <button
112
+ key={mode}
113
+ type="button"
114
+ aria-label={VIEW_LABEL[mode]}
115
+ title={VIEW_LABEL[mode]}
116
+ aria-pressed={viewMode === mode}
117
+ onClick={() => setPrefs({ viewMode: mode })}
118
+ className={cn(buttonClass(coarse), viewMode === mode && "bg-accent-wash text-primary")}
119
+ >
120
+ <Icon className="size-4" />
121
+ </button>
122
+ );
123
+ })}
124
+
125
+ <ExplorerSortMenu coarse={coarse} />
126
+
127
+ <button
128
+ type="button"
129
+ aria-label={showHidden ? "Hide hidden files" : "Show hidden files"}
130
+ title={showHidden ? "Hide hidden files" : "Show hidden files"}
131
+ aria-pressed={showHidden}
132
+ onClick={() => setPrefs({ showHidden: !showHidden })}
133
+ className={cn(buttonClass(coarse), showHidden && "bg-accent-wash text-primary")}
134
+ >
135
+ {showHidden ? <Eye className="size-4" /> : <EyeOff className="size-4" />}
136
+ </button>
137
+ </div>
138
+ );
139
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * `WINDOW_CONTENT["explorer"]` — the adapter between the generic floating-window frame and
3
+ * the explorer body.
4
+ *
5
+ * It owns only two things the body should not care about: turning the window payload into
6
+ * a starting path, and dropping the window's slice when the window closes.
7
+ */
8
+
9
+ import { useEffect } from "react";
10
+ import type { WindowContentProps } from "@/components/floating-window/window-content-registry";
11
+ import { ExplorerBody } from "./explorer-body";
12
+ import { useExplorerStore } from "./explorer-store";
13
+ import { cachedHomedir, useHostInfo } from "./use-host-info";
14
+
15
+ export default function ExplorerWindowContent({ id, payload }: WindowContentProps) {
16
+ const { host, error } = useHostInfo();
17
+ const discard = useExplorerStore((s) => s.discard);
18
+
19
+ // A window's state is per-window; leaving it behind would leak one slice per open.
20
+ useEffect(() => () => discard(id), [id, discard]);
21
+
22
+ const requested = typeof payload?.path === "string" ? payload.path : "";
23
+ // Without a path (or before host info lands) there is nothing safe to browse: "/" does
24
+ // not exist on Windows, so wait for the home directory rather than showing a bogus error.
25
+ const initialPath = requested || cachedHomedir() || host?.homedir || "";
26
+
27
+ if (!initialPath) {
28
+ return (
29
+ <div className="flex h-full items-center justify-center p-4 text-xs text-text-2">
30
+ {error ? `Could not reach the host: ${error}` : "Loading…"}
31
+ </div>
32
+ );
33
+ }
34
+
35
+ return <ExplorerBody windowId={id} initialPath={initialPath} />;
36
+ }