@hienlh/ppm 0.18.14 → 0.18.15

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 (539) hide show
  1. package/.rdtest2-cf.out +0 -0
  2. package/CHANGELOG.md +18 -0
  3. package/CLAUDE.md +3 -0
  4. package/assets/skills/ppm/SKILL.md +1 -1
  5. package/assets/skills/ppm/references/cli-reference.md +22 -4
  6. package/assets/skills/ppm/references/http-api.md +8 -1
  7. package/dist/web/assets/{adaptive-context-menu-DCZtgOL7.js → adaptive-context-menu-ChBZWLLq.js} +1 -1
  8. package/dist/web/assets/{ai-resource-editor-D5CPBg06.js → ai-resource-editor-C8aAY8Po.js} +1 -1
  9. package/dist/web/assets/{ai-settings-section-DY-t1bK8.js → ai-settings-section-ERu1WiZc.js} +1 -1
  10. package/dist/web/assets/{angular-html-CTvM4ulF.js → angular-html-CXgHIQ2U.js} +1 -1
  11. package/dist/web/assets/{angular-ts-B_vSkjco.js → angular-ts-C8renMV9.js} +1 -1
  12. package/dist/web/assets/api-client-WpZ2FDHg.js +1 -0
  13. package/dist/web/assets/api-settings-BgSEE6et.js +1 -0
  14. package/dist/web/assets/{api-settings-B2iNXQ0c.js → api-settings-R59c66DE.js} +1 -1
  15. package/dist/web/assets/{api-themes-DrWnJbhs.js → api-themes-DKxrwJda.js} +1 -1
  16. package/dist/web/assets/{apl-DywzTry0.js → apl-DkGH8laI.js} +1 -1
  17. package/dist/web/assets/architecture-PBZL5I3N-CCalSKAt.js +1 -0
  18. package/dist/web/assets/{astro-1JaRgaSb.js → astro-tHUd8Zlg.js} +1 -1
  19. package/dist/web/assets/{audio-preview-DAuGvOiK.js → audio-preview-B_C6Dpvz.js} +1 -1
  20. package/dist/web/assets/{blade-CDeeJWBb.js → blade-BbKpjIYK.js} +1 -1
  21. package/dist/web/assets/{bsl-BfgoPVYR.js → bsl-DA0UYto0.js} +1 -1
  22. package/dist/web/assets/{button-tNy0ba3y.js → button-DBSpDPS0.js} +1 -1
  23. package/dist/web/assets/{cairo-C26gNwWW.js → cairo-79ayucbB.js} +1 -1
  24. package/dist/web/assets/chat-tab-BraiJsYG.js +15 -0
  25. package/dist/web/assets/{cobol-D92nBnON.js → cobol-DSS2EOYo.js} +1 -1
  26. package/dist/web/assets/code-editor-lhmLNhXu.js +10 -0
  27. package/dist/web/assets/{coffee-CIWH40R1.js → coffee-DvSxq2iD.js} +1 -1
  28. package/dist/web/assets/{conflict-editor-CDp0I3mD.js → conflict-editor-C4VCv51R.js} +1 -1
  29. package/dist/web/assets/{context-menu-BO1c9jKy.js → context-menu-BpwUny-o.js} +1 -1
  30. package/dist/web/assets/{cpp-DOrBqXCn.js → cpp-BhWZ-FSn.js} +1 -1
  31. package/dist/web/assets/{crystal-BNKqmHux.js → crystal-yP2OeF-f.js} +1 -1
  32. package/dist/web/assets/{csv-preview-D4svNDAq.js → csv-preview-D46W_icb.js} +1 -1
  33. package/dist/web/assets/{data-grid-overlay-editor-BqV0AxZg.js → data-grid-overlay-editor-DeGkPYtA.js} +1 -1
  34. package/dist/web/assets/{database-viewer-BCvv_Bwf.js → database-viewer-DFI4YtrH.js} +1 -1
  35. package/dist/web/assets/{dialog-DcLGDwaX.js → dialog-RIpeb87v.js} +1 -1
  36. package/dist/web/assets/{diff-viewer-D41fCFAi.js → diff-viewer-eaNOLcBL.js} +1 -1
  37. package/dist/web/assets/{docx-preview-DfaQn9v7.js → docx-preview-Cbj-pOK3.js} +1 -1
  38. package/dist/web/assets/{edge-Dbjp42Pt.js → edge-D3K9cd-p.js} +1 -1
  39. package/dist/web/assets/{edit-diff-preview-BqcUuFCc.js → edit-diff-preview-DDqy3KLA.js} +1 -1
  40. package/dist/web/assets/{elixir-na3b1pYy.js → elixir-D4PBmsLp.js} +1 -1
  41. package/dist/web/assets/{elm-_Nalil1R.js → elm-DSpXjuaH.js} +1 -1
  42. package/dist/web/assets/{erb-CW_7dubf.js → erb-CaYZMTJW.js} +1 -1
  43. package/dist/web/assets/{erlang-DmZ7Zzb0.js → erlang-DX5BFqJj.js} +1 -1
  44. package/dist/web/assets/{esm-CwyBetO1.js → esm-Cv7HsFnk.js} +1 -1
  45. package/dist/web/assets/explorer-body-BxbSttfl.js +2 -0
  46. package/dist/web/assets/explorer-window-content-DUqQCC7A.js +1 -0
  47. package/dist/web/assets/{extension-store-DUaZINyC.js → extension-store-B1EClUAr.js} +1 -1
  48. package/dist/web/assets/{extension-webview-BW_mccb3.js → extension-webview-BaGvDk8v.js} +1 -1
  49. package/dist/web/assets/{file-store-yReVbR8q.js → file-store-m9crh6uq.js} +1 -1
  50. package/dist/web/assets/{fortran-fixed-form-DB2A8L4-.js → fortran-fixed-form-B0bAufUe.js} +1 -1
  51. package/dist/web/assets/{fsharp-BYHrti7z.js → fsharp-C2AL2b1M.js} +1 -1
  52. package/dist/web/assets/{gdresource-D5GBin40.js → gdresource-DRQsDMNI.js} +1 -1
  53. package/dist/web/assets/{git-commit-CUiOBoIL.js → git-commit-GtBdnrzk.js} +1 -1
  54. package/dist/web/assets/{git-log-panel-C1QDkmgl.js → git-log-panel-hwV1flSO.js} +1 -1
  55. package/dist/web/assets/{git-rebase-DOxGnpdw.js → git-rebase-h_0CGA6F.js} +1 -1
  56. package/dist/web/assets/gitGraph-HDMCJU4V-DAUrJCqi.js +1 -0
  57. package/dist/web/assets/glide-data-grid-YsN-xm6x.js +138 -0
  58. package/dist/web/assets/{glimmer-js-Begy4UT7.js → glimmer-js-Bxwu93bS.js} +1 -1
  59. package/dist/web/assets/{glimmer-ts-rPApRY8N.js → glimmer-ts-CIUYdR4E.js} +1 -1
  60. package/dist/web/assets/{glsl-wrbPvrBL.js → glsl-CfxLI5jn.js} +1 -1
  61. package/dist/web/assets/{graphql-Cu4-NR__.js → graphql-CstqDPde.js} +1 -1
  62. package/dist/web/assets/group-chat-tab-B7mJO1gG.js +1 -0
  63. package/dist/web/assets/{hack-Ddo93XKD.js → hack-Bjqnssb5.js} +1 -1
  64. package/dist/web/assets/{haml-BFX_1i8j.js → haml-lVc-DfbM.js} +1 -1
  65. package/dist/web/assets/hand-x0ejq3fS.js +1 -0
  66. package/dist/web/assets/{handlebars-5gFKA4H5.js → handlebars-Yy6fPQI_.js} +1 -1
  67. package/dist/web/assets/{html-derivative-e5JLAFZF.js → html-derivative-CchzCY1z.js} +1 -1
  68. package/dist/web/assets/{html-C06I-zeO.js → html-lqtcy6-2.js} +1 -1
  69. package/dist/web/assets/{http-CrAAhSU7.js → http-ChclRrko.js} +1 -1
  70. package/dist/web/assets/{hurl-T1V3SX9T.js → hurl-CoOK7sXa.js} +1 -1
  71. package/dist/web/assets/{hxml-B056LLKN.js → hxml-fZSZ7Iyt.js} +1 -1
  72. package/dist/web/assets/{image-preview-DWyuCYT3.js → image-preview-ClAcqDlL.js} +1 -1
  73. package/dist/web/assets/index-CtJbMNNI.css +2 -0
  74. package/dist/web/assets/index-H_gScEOk.js +52 -0
  75. package/dist/web/assets/info-3K5VOQVL-Cx1ZfJbp.js +1 -0
  76. package/dist/web/assets/{input-DouJ9Zvt.js → input-DXPgUtxi.js} +1 -1
  77. package/dist/web/assets/{jinja-DGdtRh43.js → jinja-Y0w_KBdE.js} +1 -1
  78. package/dist/web/assets/{jison-Ah6QobPS.js → jison-BILj3F2e.js} +1 -1
  79. package/dist/web/assets/{julia-BJeGOK0K.js → julia-DRfOPDts.js} +1 -1
  80. package/dist/web/assets/{just-iw9oRs7j.js → just-n3U_oODO.js} +1 -1
  81. package/dist/web/assets/keybindings-store-BqctreaI.js +1 -0
  82. package/dist/web/assets/{keybindings-store-0Wqfa7Qp.js → keybindings-store-D-CHdq-G.js} +1 -1
  83. package/dist/web/assets/keyboard-DBBaoQSu.js +1 -0
  84. package/dist/web/assets/{latex-DHjj5hyN.js → latex-D6gztEOL.js} +1 -1
  85. package/dist/web/assets/{liquid-B-FFecDc.js → liquid-DPqCEVb9.js} +1 -1
  86. package/dist/web/assets/{lua-DDianJp_.js → lua-I40KPUm-.js} +1 -1
  87. package/dist/web/assets/markdown-renderer-BwOkiZC1.js +1 -0
  88. package/dist/web/assets/{markdown-renderer-BHiUhQN8.js → markdown-renderer-oJ9jPdQ2.js} +3 -3
  89. package/dist/web/assets/{marko-BW5SBVng.js → marko-DMjsThSW.js} +1 -1
  90. package/dist/web/assets/{mdc-B9gL9bjl.js → mdc-CN2alsbo.js} +1 -1
  91. package/dist/web/assets/{media-url-DUK-k0bB.js → media-url-COUvasjn.js} +1 -1
  92. package/dist/web/assets/mobile-bottom-sheet-Cj1YBq-c.js +1 -0
  93. package/dist/web/assets/mobile-explorer-sheet-D1nM0Ik1.js +1 -0
  94. package/dist/web/assets/{nextflow-dTluY6qy.js → nextflow-CGa4JeZ6.js} +1 -1
  95. package/dist/web/assets/{nginx-DlwghGI-.js → nginx-CmqrIj84.js} +1 -1
  96. package/dist/web/assets/{nim-N9jddb5D.js → nim-CqhmTeMa.js} +1 -1
  97. package/dist/web/assets/notification-store-n-XEQ5pH.js +1 -0
  98. package/dist/web/assets/{number-overlay-editor-BVXM7aAs.js → number-overlay-editor-BEzJ1YeD.js} +1 -1
  99. package/dist/web/assets/{overview-panel-DCCDmtCQ.js → overview-panel-Bf9Gn8qV.js} +1 -1
  100. package/dist/web/assets/packet-RMMSAZCW-Bqwiy7dz.js +1 -0
  101. package/dist/web/assets/{panel-store-B4rijFKN.js → panel-store-UWCH6B-_.js} +1 -1
  102. package/dist/web/assets/{pdf-preview-D7yLmAZW.js → pdf-preview-BMHM5cR2.js} +1 -1
  103. package/dist/web/assets/{perl-CkmpYKa_.js → perl-BOwpb6Tb.js} +1 -1
  104. package/dist/web/assets/{php-BwZ3KDPo.js → php-wT9fVxxu.js} +1 -1
  105. package/dist/web/assets/pie-UPGHQEXC-BXSJ2God.js +1 -0
  106. package/dist/web/assets/{postgres-viewer--CRN1fvO.js → postgres-viewer-D8rfEY5b.js} +3 -3
  107. package/dist/web/assets/process-table-CsQ-hDJr.js +1 -0
  108. package/dist/web/assets/{project-store-BuY7rxn_.js → project-store-CKAOr23O.js} +1 -1
  109. package/dist/web/assets/{pug-B3rBPqQo.js → pug-DdlNnj-p.js} +1 -1
  110. package/dist/web/assets/{qml-BfDbWFmg.js → qml-C5zBdydf.js} +1 -1
  111. package/dist/web/assets/radar-KQ55EAFF-D6XrM7HH.js +1 -0
  112. package/dist/web/assets/{razor-B1ww1r38.js → razor-DqcJX4iq.js} +1 -1
  113. package/dist/web/assets/remote-desktop-mobile-sheet-CTiipn4k.js +2 -0
  114. package/dist/web/assets/remote-desktop-mobile-view-Cf5B0dKo.js +1 -0
  115. package/dist/web/assets/remote-desktop-stats-overlay-vhJBIW7s.js +1 -0
  116. package/dist/web/assets/remote-desktop-window-content-wCJUfI6G.js +1 -0
  117. package/dist/web/assets/{rst-C0MSq6m-.js → rst-DiMiMYUO.js} +1 -1
  118. package/dist/web/assets/{ruby-ClIZ1wb3.js → ruby-DK62iflA.js} +1 -1
  119. package/dist/web/assets/{run-in-terminal-4efbW2Xv.js → run-in-terminal-DJSL6nwZ.js} +1 -1
  120. package/dist/web/assets/{sas-DR0j3o4_.js → sas-6QPGJRa5.js} +1 -1
  121. package/dist/web/assets/{scroll-area-ZP8wcwTI.js → scroll-area-DXGXeiRz.js} +1 -1
  122. package/dist/web/assets/{scss-Vh3JLayR.js → scss-DmUqh48_.js} +1 -1
  123. package/dist/web/assets/{send-to-chat-BNrEpAyC.js → send-to-chat-BOony0sO.js} +1 -1
  124. package/dist/web/assets/settings-store-BpcY5aYE.js +2 -0
  125. package/dist/web/assets/settings-tab-BVqRO01j.js +1 -0
  126. package/dist/web/assets/settings-tab-FH581A_i.js +3 -0
  127. package/dist/web/assets/{shaderlab-0rHRnQ18.js → shaderlab-DopFR1mi.js} +1 -1
  128. package/dist/web/assets/{shellsession-Dxdw6D8c.js → shellsession-Bp_TGuXb.js} +1 -1
  129. package/dist/web/assets/{soy-BHlSNj1y.js → soy-DEB9KEFb.js} +1 -1
  130. package/dist/web/assets/{sparql-B2QaPpni.js → sparql-C8lEwO1p.js} +1 -1
  131. package/dist/web/assets/{sql-query-editor-R_iKjPP9.js → sql-query-editor-MMfk0Kbz.js} +1 -1
  132. package/dist/web/assets/{sqlite-viewer-b2onA0SP.js → sqlite-viewer-vglzrgnZ.js} +1 -1
  133. package/dist/web/assets/{stata-CwIxjsEJ.js → stata-DVep4apN.js} +1 -1
  134. package/dist/web/assets/{surrealql-C6vo-rp-.js → surrealql-fSlJApOA.js} +1 -1
  135. package/dist/web/assets/{svelte-LLdJ6rlH.js → svelte-DezWBEDi.js} +1 -1
  136. package/dist/web/assets/{switch-DV-BDjyk.js → switch-DqL_ZjB_.js} +1 -1
  137. package/dist/web/assets/system-monitor-body-BYUfyF8f.js +2 -0
  138. package/dist/web/assets/system-monitor-tab-CKGXB8lH.js +1 -0
  139. package/dist/web/assets/{system-monitor-window-content-BPif0azo.js → system-monitor-window-content-CiJ2yIsr.js} +1 -1
  140. package/dist/web/assets/{tab-host-window-content-BnAN6m4K.js → tab-host-window-content-DYlXR0cg.js} +1 -1
  141. package/dist/web/assets/{tab-store-tFnyPmm4.js → tab-store-D2thn4Vv.js} +1 -1
  142. package/dist/web/assets/team-member-sheet-Bnygu7Xw.js +2 -0
  143. package/dist/web/assets/{team-member-window-content-BSyU-5Uc.js → team-member-window-content-qrgFTC96.js} +1 -1
  144. package/dist/web/assets/{templ-CJGvhEP5.js → templ-D0zB42a9.js} +1 -1
  145. package/dist/web/assets/{terminal-tab-Ncor0Rfu.js → terminal-tab-D_-R5rGm.js} +3 -3
  146. package/dist/web/assets/{tex-psXWhc4t.js → tex-BJiPijcH.js} +1 -1
  147. package/dist/web/assets/{tool-cards-DdVP1wrD.js → tool-cards-BW7Xc20J.js} +4 -4
  148. package/dist/web/assets/treemap-KZPCXAKY-BobWhcHN.js +1 -0
  149. package/dist/web/assets/{ts-tags-D5Ar_ZCO.js → ts-tags-Dcr4u6y5.js} +1 -1
  150. package/dist/web/assets/{twig-vJgGC-qY.js → twig-B-SQU0bg.js} +1 -1
  151. package/dist/web/assets/{use-blob-url-BOVGWg9F.js → use-blob-url-BtPlqwLr.js} +1 -1
  152. package/dist/web/assets/{use-monaco-theme-BshnLXoY.js → use-monaco-theme-DQY1ZSaf.js} +1 -1
  153. package/dist/web/assets/{use-open-team-member-CY3wOdWL.js → use-open-team-member-C5LQ_TCM.js} +1 -1
  154. package/dist/web/assets/use-visual-viewport-pVxLfE5B.js +1 -0
  155. package/dist/web/assets/{use-websocket-B7xjNsyD.js → use-websocket-l39Odhlp.js} +1 -1
  156. package/dist/web/assets/{vendor-mermaid-999ksD45.js → vendor-mermaid-CtDsSxbM.js} +1 -1
  157. package/dist/web/assets/{video-preview-7F72sRwf.js → video-preview-C5AhkIDl.js} +1 -1
  158. package/dist/web/assets/{vue-BWngFyav.js → vue-Cw10Sfbh.js} +1 -1
  159. package/dist/web/assets/{vue-html-BN5ZLu-H.js → vue-html-Cc5C_Z_w.js} +1 -1
  160. package/dist/web/assets/{vue-vine-BZHnO8yV.js → vue-vine-BuNMlnvu.js} +1 -1
  161. package/dist/web/assets/{window-pip-registry-BnYU83LE.js → window-pip-registry-C7oz3Dfy.js} +1 -1
  162. package/dist/web/assets/window-store-B5ZswdYg.js +1 -0
  163. package/dist/web/assets/{xml-DS2beZHp.js → xml-DqT1wkky.js} +1 -1
  164. package/dist/web/assets/{xsl-Ciiku1Bq.js → xsl-DLkge3A2.js} +1 -1
  165. package/dist/web/index.html +45 -43
  166. package/dist/web/sw.js +1 -1
  167. package/docs/architecture/ai-chat-and-providers.md +3 -0
  168. package/package.json +1 -1
  169. package/packages/vscode-compat/node_modules/.bin/tsc.bunx +0 -0
  170. package/packages/vscode-compat/node_modules/.bin/tsc.exe +0 -0
  171. package/packages/vscode-compat/node_modules/.bin/tsserver.bunx +0 -0
  172. package/packages/vscode-compat/node_modules/.bin/tsserver.exe +0 -0
  173. package/src/cli/commands/backup-cmd.ts +119 -0
  174. package/src/index.ts +3 -0
  175. package/src/providers/claude-agent-sdk.ts +29 -1
  176. package/src/server/index.ts +42 -15
  177. package/src/server/routes/accounts.ts +30 -2
  178. package/src/server/routes/chat.ts +8 -1
  179. package/src/server/routes/named-tunnel.ts +5 -2
  180. package/src/server/routes/remote-desktop.ts +64 -0
  181. package/src/server/routes/settings.ts +1 -0
  182. package/src/server/ws/chat.ts +126 -7
  183. package/src/server/ws/remote-desktop.ts +86 -0
  184. package/src/services/account.service.ts +106 -23
  185. package/src/services/db-backup/db-backup-paths.ts +66 -0
  186. package/src/services/db-backup/db-backup-restore.ts +105 -0
  187. package/src/services/db-backup/db-backup-retention.ts +96 -0
  188. package/src/services/db-backup/db-backup-sync.ts +113 -0
  189. package/src/services/db-backup/db-backup.service.ts +61 -0
  190. package/src/services/db.service.ts +29 -1
  191. package/src/services/edge-forwarder.ts +191 -191
  192. package/src/services/fs-credential-path-guard.ts +19 -1
  193. package/src/services/named-tunnel/cloudflared-login.service.ts +2 -2
  194. package/src/services/nested-subagent-spy.ts +206 -0
  195. package/src/services/remote-desktop/access-unit-assembler.ts +90 -0
  196. package/src/services/remote-desktop/avc1-codec-string.ts +18 -0
  197. package/src/services/remote-desktop/nal-splitter.ts +100 -0
  198. package/src/services/remote-desktop/remote-desktop-capture.ts +120 -0
  199. package/src/services/remote-desktop/remote-desktop-encoder-args.ts +42 -0
  200. package/src/services/remote-desktop/remote-desktop-flag.ts +10 -0
  201. package/src/services/remote-desktop/remote-desktop-input.ts +217 -0
  202. package/src/services/remote-desktop/remote-desktop-nonce.ts +38 -0
  203. package/src/services/remote-desktop/remote-desktop-session.ts +189 -0
  204. package/src/services/remote-desktop/remote-desktop-vk-map.ts +41 -0
  205. package/src/services/subagent-transcript-merger.ts +92 -75
  206. package/src/services/subprocess-retention.ts +29 -6
  207. package/src/services/supervisor.ts +58 -0
  208. package/src/services/team-member-activity/subagent-transcript-index.ts +56 -0
  209. package/src/services/windows-process-tree.ts +351 -351
  210. package/src/types/chat.ts +2 -0
  211. package/src/web/app.tsx +8 -0
  212. package/src/web/components/chat/account-dialogs.tsx +11 -4
  213. package/src/web/components/chat/question-card.tsx +9 -2
  214. package/src/web/components/chat/usage-badge.tsx +17 -3
  215. package/src/web/components/floating-window/window-content-registry.ts +2 -0
  216. package/src/web/components/floating-window/window-store-types.ts +1 -1
  217. package/src/web/components/layout/mobile-drawer.tsx +38 -27
  218. package/src/web/components/layout/nav-section-rail.tsx +10 -2
  219. package/src/web/components/remote-desktop/open-remote-desktop.ts +26 -0
  220. package/src/web/components/remote-desktop/remote-desktop-coords.ts +98 -0
  221. package/src/web/components/remote-desktop/remote-desktop-frame-timestamp.ts +21 -0
  222. package/src/web/components/remote-desktop/remote-desktop-gesture-classifiers.ts +56 -0
  223. package/src/web/components/remote-desktop/remote-desktop-mobile-key-bar.tsx +131 -0
  224. package/src/web/components/remote-desktop/remote-desktop-mobile-sheet.tsx +43 -0
  225. package/src/web/components/remote-desktop/remote-desktop-mobile-toolbar.tsx +79 -0
  226. package/src/web/components/remote-desktop/remote-desktop-mobile-view.tsx +190 -0
  227. package/src/web/components/remote-desktop/remote-desktop-stats-overlay.tsx +54 -0
  228. package/src/web/components/remote-desktop/remote-desktop-stats.ts +37 -0
  229. package/src/web/components/remote-desktop/remote-desktop-touch-gesture-tracker.ts +195 -0
  230. package/src/web/components/remote-desktop/remote-desktop-window-content.tsx +72 -0
  231. package/src/web/components/remote-desktop/remote-desktop-ws-url.ts +22 -0
  232. package/src/web/components/remote-desktop/use-h264-canvas-decoder.ts +175 -0
  233. package/src/web/components/remote-desktop/use-remote-desktop-available.ts +29 -0
  234. package/src/web/components/remote-desktop/use-remote-desktop-connection.ts +147 -0
  235. package/src/web/components/remote-desktop/use-remote-desktop-mobile-open-state.ts +18 -0
  236. package/src/web/components/remote-desktop/use-remote-desktop-touch.ts +224 -0
  237. package/src/web/components/remote-desktop/use-remote-desktop-virtual-keyboard.ts +50 -0
  238. package/src/web/components/remote-desktop/use-remote-input-capture.ts +92 -0
  239. package/src/web/components/tunnels/named-tunnel/named-tunnel-copy.ts +59 -59
  240. package/src/web/components/tunnels/named-tunnel/named-tunnel-section.tsx +2 -2
  241. package/src/web/hooks/use-notification-badge.ts +19 -11
  242. package/src/web/lib/feature-badges.ts +2 -0
  243. package/src/web/stores/settings-store.ts +13 -0
  244. package/src/web/stores/streaming-store.ts +21 -2
  245. package/dist/web/assets/api-client-BogqsFna.js +0 -1
  246. package/dist/web/assets/api-settings-DRF9oYkn.js +0 -1
  247. package/dist/web/assets/architecture-PBZL5I3N-nw5-j_I_.js +0 -1
  248. package/dist/web/assets/chat-tab-IcnU3nqQ.js +0 -15
  249. package/dist/web/assets/code-editor-Dno5Srkm.js +0 -10
  250. package/dist/web/assets/explorer-body-DB1L30pa.js +0 -2
  251. package/dist/web/assets/explorer-window-content-Cwlt3C3I.js +0 -1
  252. package/dist/web/assets/gitGraph-HDMCJU4V-C1xf87gN.js +0 -1
  253. package/dist/web/assets/glide-data-grid-Dz8lx6n2.js +0 -138
  254. package/dist/web/assets/group-chat-tab-DFwr-pig.js +0 -1
  255. package/dist/web/assets/index-DWpNR3nB.css +0 -2
  256. package/dist/web/assets/index-oUKV5nIA.js +0 -52
  257. package/dist/web/assets/info-3K5VOQVL-C2z7FTDe.js +0 -1
  258. package/dist/web/assets/keybindings-store-d54pv2NK.js +0 -1
  259. package/dist/web/assets/markdown-renderer-CnE36_gd.js +0 -1
  260. package/dist/web/assets/mobile-bottom-sheet-CJ7ApOkI.js +0 -1
  261. package/dist/web/assets/mobile-explorer-sheet-BzjlS1Xn.js +0 -1
  262. package/dist/web/assets/notification-store-C7UBqxYP.js +0 -1
  263. package/dist/web/assets/packet-RMMSAZCW-DQUxN0wU.js +0 -1
  264. package/dist/web/assets/pie-UPGHQEXC-BOr9ek4Q.js +0 -1
  265. package/dist/web/assets/process-table-CXC10GSy.js +0 -1
  266. package/dist/web/assets/radar-KQ55EAFF-DSErxZFb.js +0 -1
  267. package/dist/web/assets/settings-store-OLJE7dPH.js +0 -2
  268. package/dist/web/assets/settings-tab-B1rvHFc6.js +0 -3
  269. package/dist/web/assets/settings-tab-BUlmPJ2w.js +0 -1
  270. package/dist/web/assets/system-monitor-body-DPYQlBEt.js +0 -2
  271. package/dist/web/assets/system-monitor-tab-CPQdWv27.js +0 -1
  272. package/dist/web/assets/team-member-sheet-Bu2wG1sm.js +0 -2
  273. package/dist/web/assets/treemap-KZPCXAKY-C17p0LQw.js +0 -1
  274. package/dist/web/assets/window-store-CQ1lghvv.js +0 -1
  275. /package/dist/web/assets/{abap-CjzrhAS2.js → abap-CKgBNk5K.js} +0 -0
  276. /package/dist/web/assets/{actionscript-3-B59Kbl-9.js → actionscript-3-oARhAY6c.js} +0 -0
  277. /package/dist/web/assets/{ada-B7gItKAE.js → ada-2vi8ASy3.js} +0 -0
  278. /package/dist/web/assets/{andromeeda-Dy5zcy0Q.js → andromeeda-B3vMjPKd.js} +0 -0
  279. /package/dist/web/assets/{apache-B6T9jrc0.js → apache-DnIEr338.js} +0 -0
  280. /package/dist/web/assets/{apex-CTsIxDQw.js → apex-B7Qs5AY5.js} +0 -0
  281. /package/dist/web/assets/{api-client-oY1tC3tM.js → api-client-BkGKTjsq.js} +0 -0
  282. /package/dist/web/assets/{applescript-DuycKmmb.js → applescript-EbQNUCWb.js} +0 -0
  283. /package/dist/web/assets/{apply-theme-r8tKDrCR.js → apply-theme-Dd_cqGa-.js} +0 -0
  284. /package/dist/web/assets/{ara-CISL3599.js → ara-CtHdhFRw.js} +0 -0
  285. /package/dist/web/assets/{asciidoc-BXyE2Lpr.js → asciidoc-BH6qin_O.js} +0 -0
  286. /package/dist/web/assets/{asm-y258phtP.js → asm-D1XU1ACk.js} +0 -0
  287. /package/dist/web/assets/{aurora-x-Dq3Jc78M.js → aurora-x-BkhcxkuS.js} +0 -0
  288. /package/dist/web/assets/{awk-DxL6XSJv.js → awk-DijzB6o7.js} +0 -0
  289. /package/dist/web/assets/{ayu-dark-DjoK6Evo.js → ayu-dark-tysG77WC.js} +0 -0
  290. /package/dist/web/assets/{ayu-light-CKKXBLMI.js → ayu-light-Dg0WMETC.js} +0 -0
  291. /package/dist/web/assets/{ayu-mirage-DGXSrrXB.js → ayu-mirage--RsJCjC4.js} +0 -0
  292. /package/dist/web/assets/{ballerina-DZSXFmUf.js → ballerina-DT_5-l82.js} +0 -0
  293. /package/dist/web/assets/{bat-BNsZk6nY.js → bat-BRpDlkv0.js} +0 -0
  294. /package/dist/web/assets/{beancount-cPl4gJct.js → beancount--8Df-WGG.js} +0 -0
  295. /package/dist/web/assets/{berry-DGAodERk.js → berry-Bc9fo35J.js} +0 -0
  296. /package/dist/web/assets/{bibtex-DftVoRif.js → bibtex-CwyBpp9X.js} +0 -0
  297. /package/dist/web/assets/{bicep-Suioyyzh.js → bicep-DiyH2Bs7.js} +0 -0
  298. /package/dist/web/assets/{bird2-DjU24I2P.js → bird2-CHgK4a2A.js} +0 -0
  299. /package/dist/web/assets/{c-DFi8Y_nN.js → c-BM1qL6-f.js} +0 -0
  300. /package/dist/web/assets/{c3-1eSHnH5V.js → c3-D2oOjrP7.js} +0 -0
  301. /package/dist/web/assets/{cadence-DLLsFwQP.js → cadence-nS45wqF6.js} +0 -0
  302. /package/dist/web/assets/{can-open-in-ppm-in727eFQ.js → can-open-in-ppm-DwP9KU8o.js} +0 -0
  303. /package/dist/web/assets/{catppuccin-frappe-DDj0JEiY.js → catppuccin-frappe-BpnJHiCj.js} +0 -0
  304. /package/dist/web/assets/{catppuccin-latte-DPyANHyR.js → catppuccin-latte-Bb95Wj3h.js} +0 -0
  305. /package/dist/web/assets/{catppuccin-macchiato-CO-h8VTT.js → catppuccin-macchiato-Btx8c17W.js} +0 -0
  306. /package/dist/web/assets/{catppuccin-mocha-BCmbN2IZ.js → catppuccin-mocha-B4DZOUGI.js} +0 -0
  307. /package/dist/web/assets/{clarity-C0pYsZjs.js → clarity-1ps69jU6.js} +0 -0
  308. /package/dist/web/assets/{clipboard-DBLGKMrH.js → clipboard-CrHX8AYw.js} +0 -0
  309. /package/dist/web/assets/{clojure-JFr6s8iZ.js → clojure-BklC0hKV.js} +0 -0
  310. /package/dist/web/assets/{cmake-EEcLEgVy.js → cmake-B573LYAQ.js} +0 -0
  311. /package/dist/web/assets/{codeowners-Deh3Lx0u.js → codeowners-BoYeX89M.js} +0 -0
  312. /package/dist/web/assets/{codeql-C317igT5.js → codeql-7Dhf5Udk.js} +0 -0
  313. /package/dist/web/assets/{common-lisp-DepbATZs.js → common-lisp-m1nxlthv.js} +0 -0
  314. /package/dist/web/assets/{coq-MxgxU5ya.js → coq-DNhGmd4a.js} +0 -0
  315. /package/dist/web/assets/{csharp-eEFXdsAq.js → csharp-P-nAurtS.js} +0 -0
  316. /package/dist/web/assets/{css-Db-syWW9.js → css-LYbSeh3-.js} +0 -0
  317. /package/dist/web/assets/{csv-DHeYcbaq.js → csv-DHZZZm7X.js} +0 -0
  318. /package/dist/web/assets/{csv-parser-_JWMLGl3.js → csv-parser-CY9Ggtg5.js} +0 -0
  319. /package/dist/web/assets/{cue-BmUNowrt.js → cue-D8yLUOle.js} +0 -0
  320. /package/dist/web/assets/{cypher-XjXHgCNS.js → cypher-9cbW8RYA.js} +0 -0
  321. /package/dist/web/assets/{d-BWJnZtig.js → d-ZihzhxqI.js} +0 -0
  322. /package/dist/web/assets/{dark-plus-Db7umas1.js → dark-plus-C8xIA1u3.js} +0 -0
  323. /package/dist/web/assets/{dart-DSI4HYh1.js → dart-CBm6jKc7.js} +0 -0
  324. /package/dist/web/assets/{data-grid-types-CfVHh9gv.js → data-grid-types-DkEQ-Na9.js} +0 -0
  325. /package/dist/web/assets/{dax-yw1JsAni.js → dax-B3cx6YeV.js} +0 -0
  326. /package/dist/web/assets/{desktop-C2QO4RvD.js → desktop-C03SpXth.js} +0 -0
  327. /package/dist/web/assets/{diff-Drt8aEyp.js → diff-D8LnnvFY.js} +0 -0
  328. /package/dist/web/assets/{dist-DNRvhVdz.js → dist-C4XoS_zo.js} +0 -0
  329. /package/dist/web/assets/{dist-VBxOHLmN.js → dist-Do3fljss.js} +0 -0
  330. /package/dist/web/assets/{docker-ByDKbmLl.js → docker-DwFiOQWW.js} +0 -0
  331. /package/dist/web/assets/{dotenv-BkfOLytR.js → dotenv-CVJ_vFSQ.js} +0 -0
  332. /package/dist/web/assets/{dracula-DAmrm-Un.js → dracula-DMw8UACY.js} +0 -0
  333. /package/dist/web/assets/{dracula-soft-BUOEnKqR.js → dracula-soft-BnB3nDrA.js} +0 -0
  334. /package/dist/web/assets/{dream-maker-DQBIfSiO.js → dream-maker-2riXUQXo.js} +0 -0
  335. /package/dist/web/assets/{emacs-lisp-Blh5kBD3.js → emacs-lisp-BQdYaiB5.js} +0 -0
  336. /package/dist/web/assets/{everforest-dark-CEzhDxHq.js → everforest-dark-C2FPIWdj.js} +0 -0
  337. /package/dist/web/assets/{everforest-light-CPk_5Khm.js → everforest-light-DMOWf9sD.js} +0 -0
  338. /package/dist/web/assets/{fennel-B1MT_EBZ.js → fennel-JtSHp0vW.js} +0 -0
  339. /package/dist/web/assets/{fish-C_HGcewt.js → fish-CflDcRfe.js} +0 -0
  340. /package/dist/web/assets/{fluent-Dp1Iclmx.js → fluent-D4eu6Vac.js} +0 -0
  341. /package/dist/web/assets/{format-bytes-53WBtyi2.js → format-bytes-DR3228bu.js} +0 -0
  342. /package/dist/web/assets/{fortran-free-form-B_-UA4XC.js → fortran-free-form-hO4RuC9Z.js} +0 -0
  343. /package/dist/web/assets/{gdscript-Bm_JpjS_.js → gdscript-D7n3a8VM.js} +0 -0
  344. /package/dist/web/assets/{gdshader-C_PNmfYU.js → gdshader-BhUmrlzG.js} +0 -0
  345. /package/dist/web/assets/{genie-B5rmR2TF.js → genie-VbyftqBp.js} +0 -0
  346. /package/dist/web/assets/{gherkin-BTJ34TJ9.js → gherkin-BZzhvUUb.js} +0 -0
  347. /package/dist/web/assets/{github-dark-DOkNVj9H.js → github-dark-C4q41woW.js} +0 -0
  348. /package/dist/web/assets/{github-dark-default-DTsmiyVo.js → github-dark-default-CMNi7dQX.js} +0 -0
  349. /package/dist/web/assets/{github-dark-dimmed-CjbxAe9s.js → github-dark-dimmed-BRf_-LlY.js} +0 -0
  350. /package/dist/web/assets/{github-dark-high-contrast-r_M8F_k7.js → github-dark-high-contrast-xanKBsRI.js} +0 -0
  351. /package/dist/web/assets/{github-light-BzfxyMyb.js → github-light-BEQeNQCs.js} +0 -0
  352. /package/dist/web/assets/{github-light-default-BOqiBIy5.js → github-light-default-ceQrtdXg.js} +0 -0
  353. /package/dist/web/assets/{github-light-high-contrast-DQzBrDM4.js → github-light-high-contrast-doKbXzgW.js} +0 -0
  354. /package/dist/web/assets/{gleam-BWmuAtD8.js → gleam-Chzqf4fT.js} +0 -0
  355. /package/dist/web/assets/{gn-CNQPjiZW.js → gn-CCN_-6fp.js} +0 -0
  356. /package/dist/web/assets/{gnuplot-BlHjcPMe.js → gnuplot-CIplgwwP.js} +0 -0
  357. /package/dist/web/assets/{go-Bn8Ba9uz.js → go-Cjj-9dRs.js} +0 -0
  358. /package/dist/web/assets/{groovy-C1Y1_F4V.js → groovy-PqD4K7X1.js} +0 -0
  359. /package/dist/web/assets/{gruvbox-dark-hard-DueOsHav.js → gruvbox-dark-hard-BAbpUSF-.js} +0 -0
  360. /package/dist/web/assets/{gruvbox-dark-medium-DaQjD538.js → gruvbox-dark-medium-BWcWXDBB.js} +0 -0
  361. /package/dist/web/assets/{gruvbox-dark-soft-CbbX2WOm.js → gruvbox-dark-soft-Dkx1okkk.js} +0 -0
  362. /package/dist/web/assets/{gruvbox-light-hard-E2M70ljZ.js → gruvbox-light-hard-WVsDK0MM.js} +0 -0
  363. /package/dist/web/assets/{gruvbox-light-medium-B_DDLY6h.js → gruvbox-light-medium-tiR9t7gr.js} +0 -0
  364. /package/dist/web/assets/{gruvbox-light-soft-2SZmyC1F.js → gruvbox-light-soft-HzHohaX6.js} +0 -0
  365. /package/dist/web/assets/{haskell-D8DeoXtG.js → haskell-B1eIZfr6.js} +0 -0
  366. /package/dist/web/assets/{haxe-BmZm7B0W.js → haxe-BcDdi5xr.js} +0 -0
  367. /package/dist/web/assets/{hcl-CKnBM3ng.js → hcl-Dq-HKVni.js} +0 -0
  368. /package/dist/web/assets/{hjson-Ct9t4-ci.js → hjson-Dr9D9WVU.js} +0 -0
  369. /package/dist/web/assets/{hlsl-BZSASBqS.js → hlsl-Bo8t2FuZ.js} +0 -0
  370. /package/dist/web/assets/{horizon-CzX1AZ08.js → horizon-DWG5IXc-.js} +0 -0
  371. /package/dist/web/assets/{horizon-bright-CCVNeRIP.js → horizon-bright-DFr9wTTx.js} +0 -0
  372. /package/dist/web/assets/{houston-DBlJN7xc.js → houston-NAVNhkup.js} +0 -0
  373. /package/dist/web/assets/{hy-Dyc6h6Xb.js → hy-DnyvUk3B.js} +0 -0
  374. /package/dist/web/assets/{image-gallery-DujlrjOC.js → image-gallery-DoFdNF9i.js} +0 -0
  375. /package/dist/web/assets/{imba-Bauru7s2.js → imba-CPbR5W_A.js} +0 -0
  376. /package/dist/web/assets/{ini-SjdwtaTp.js → ini-lc8_Lt0y.js} +0 -0
  377. /package/dist/web/assets/{java-D2r0ocpf.js → java-B0KCo2vK.js} +0 -0
  378. /package/dist/web/assets/{javascript-y-lof7iP.js → javascript-YqEbNRpd.js} +0 -0
  379. /package/dist/web/assets/{json-DO4LSx5A.js → json-Co0WtOlh.js} +0 -0
  380. /package/dist/web/assets/{json5-BEwWf-W3.js → json5-2Jhb0QUN.js} +0 -0
  381. /package/dist/web/assets/{jsonc-OBEFv7gP.js → jsonc-e72F11nU.js} +0 -0
  382. /package/dist/web/assets/{jsonl-DOyW4Mg3.js → jsonl-Bff8IPye.js} +0 -0
  383. /package/dist/web/assets/{jsonnet-BL5R2CpP.js → jsonnet-DRCkb-_H.js} +0 -0
  384. /package/dist/web/assets/{jssm-ksPusAyg.js → jssm-C6UiwVEm.js} +0 -0
  385. /package/dist/web/assets/{jsx-B6XEu6O-.js → jsx-u1aZlnfA.js} +0 -0
  386. /package/dist/web/assets/{kanagawa-dragon-JBVe_ji_.js → kanagawa-dragon-De-RQFV3.js} +0 -0
  387. /package/dist/web/assets/{kanagawa-lotus-ChprO68_.js → kanagawa-lotus-K8elE4QU.js} +0 -0
  388. /package/dist/web/assets/{kanagawa-wave-D_uB_R0Q.js → kanagawa-wave-BrStl_zx.js} +0 -0
  389. /package/dist/web/assets/{kdl-pf5B8N_m.js → kdl-CLCfMeZy.js} +0 -0
  390. /package/dist/web/assets/{kotlin-BdF0METC.js → kotlin-DEEC3pNh.js} +0 -0
  391. /package/dist/web/assets/{kusto-BneVP-OU.js → kusto-DTqRhCup.js} +0 -0
  392. /package/dist/web/assets/{laserwave-C4-UPIyG.js → laserwave-DsgpUSbA.js} +0 -0
  393. /package/dist/web/assets/{lean-BN6Jdymq.js → lean-Dl9MI0Y3.js} +0 -0
  394. /package/dist/web/assets/{less-Cx-fdlDC.js → less-Bg21rao6.js} +0 -0
  395. /package/dist/web/assets/{lib-CMuBa85c.js → lib-CXUwxw1M.js} +0 -0
  396. /package/dist/web/assets/{libesm-BB74PRsH.js → libesm-CVkncAQz.js} +0 -0
  397. /package/dist/web/assets/{light-plus-DFnnn_iL.js → light-plus-DGNbbK1V.js} +0 -0
  398. /package/dist/web/assets/{llvm-BGxtW0Nw.js → llvm-91_ZX3p-.js} +0 -0
  399. /package/dist/web/assets/{loader-circle-Dpqcv0eS.js → loader-circle-DouKgIXy.js} +0 -0
  400. /package/dist/web/assets/{lock-DldbT31x.js → lock-CNxUGpTk.js} +0 -0
  401. /package/dist/web/assets/{log-kQb4eJnj.js → log-DSXzbq8u.js} +0 -0
  402. /package/dist/web/assets/{logo-DqAzaq7s.js → logo-BQRNd4tc.js} +0 -0
  403. /package/dist/web/assets/{luau-Dc2cp-zS.js → luau-BE-TMpiZ.js} +0 -0
  404. /package/dist/web/assets/{make-DYg9dwaJ.js → make-ePYGKTpc.js} +0 -0
  405. /package/dist/web/assets/{markdown-O4EPpS3K.js → markdown-Cst3wgMW.js} +0 -0
  406. /package/dist/web/assets/{material-theme-ClP8yfQ2.js → material-theme-9XyvnNLb.js} +0 -0
  407. /package/dist/web/assets/{material-theme-darker-LOK55hGu.js → material-theme-darker-H1VN4MTS.js} +0 -0
  408. /package/dist/web/assets/{material-theme-lighter-j72tzZqi.js → material-theme-lighter-DAreBbuF.js} +0 -0
  409. /package/dist/web/assets/{material-theme-ocean-C19LKClA.js → material-theme-ocean-BpTR-3II.js} +0 -0
  410. /package/dist/web/assets/{material-theme-palenight-DJ5tN2ax.js → material-theme-palenight-DUNzX711.js} +0 -0
  411. /package/dist/web/assets/{matlab-DQNZ_NU8.js → matlab-BpYNufBU.js} +0 -0
  412. /package/dist/web/assets/{mdx-CJayB7XK.js → mdx-BYjfe1DK.js} +0 -0
  413. /package/dist/web/assets/{mermaid-Cb0TRX92.js → mermaid-DPCcIW9B.js} +0 -0
  414. /package/dist/web/assets/{min-dark-CWM8Bo-m.js → min-dark-DnGOAGCo.js} +0 -0
  415. /package/dist/web/assets/{min-light-BBR1sjS2.js → min-light-CwV-o31y.js} +0 -0
  416. /package/dist/web/assets/{mipsasm-Jc7u7YSS.js → mipsasm-DdeCPC97.js} +0 -0
  417. /package/dist/web/assets/{mojo-CTB0cutd.js → mojo-BmxxZ3iM.js} +0 -0
  418. /package/dist/web/assets/{monitor-4vWfqzuN.js → monitor-C835ucWH.js} +0 -0
  419. /package/dist/web/assets/{monokai-CvM_YNKx.js → monokai-Cx6cQ-MA.js} +0 -0
  420. /package/dist/web/assets/{moonbit-CbFEfYYM.js → moonbit-D2-Gj5vj.js} +0 -0
  421. /package/dist/web/assets/{move-BT38p0kT.js → move-DSWGoQa8.js} +0 -0
  422. /package/dist/web/assets/{narrat-55QZ_kOG.js → narrat-D9TnZSSu.js} +0 -0
  423. /package/dist/web/assets/{nextflow-groovy-Bg8peyqg.js → nextflow-groovy-BbL-8PwD.js} +0 -0
  424. /package/dist/web/assets/{night-owl-DzS77iPK.js → night-owl-CZitKm5d.js} +0 -0
  425. /package/dist/web/assets/{night-owl-light-DER7fGnx.js → night-owl-light-DJVX1Pno.js} +0 -0
  426. /package/dist/web/assets/{nix-BSI8YesN.js → nix-R6hr3oln.js} +0 -0
  427. /package/dist/web/assets/{nord-9bn7iNWo.js → nord-ChGHya3l.js} +0 -0
  428. /package/dist/web/assets/{nushell-qDVZn_lw.js → nushell-QQWB-GWx.js} +0 -0
  429. /package/dist/web/assets/{objective-c-Blf-ATos.js → objective-c-C0pZ6y73.js} +0 -0
  430. /package/dist/web/assets/{objective-cpp-Bz8bAq47.js → objective-cpp-BdzVM31a.js} +0 -0
  431. /package/dist/web/assets/{ocaml-B9heAsc3.js → ocaml-D4KktNlS.js} +0 -0
  432. /package/dist/web/assets/{odin-BaFYnOwO.js → odin-Y-fypmGT.js} +0 -0
  433. /package/dist/web/assets/{one-dark-pro-D6B_hSe-.js → one-dark-pro-BwecIxQL.js} +0 -0
  434. /package/dist/web/assets/{one-light-Ba-f0wyk.js → one-light-Cz7UVzLU.js} +0 -0
  435. /package/dist/web/assets/{openscad-UvYyL2MK.js → openscad-DtRe3qYD.js} +0 -0
  436. /package/dist/web/assets/{pascal-B4-qvEoG.js → pascal-Ck6I1fGG.js} +0 -0
  437. /package/dist/web/assets/{pkl-B146nqGx.js → pkl-DdFK94VW.js} +0 -0
  438. /package/dist/web/assets/{plastic-L38t-Id3.js → plastic-BQSAzZwR.js} +0 -0
  439. /package/dist/web/assets/{play-CpCAoQRZ.js → play-CJlWpVMw.js} +0 -0
  440. /package/dist/web/assets/{plsql-ItOG5Cca.js → plsql-DjXg_CP0.js} +0 -0
  441. /package/dist/web/assets/{po-imiCTaNX.js → po-Bw2SuM4r.js} +0 -0
  442. /package/dist/web/assets/{poimandres-Bu6sIba9.js → poimandres-YMK6c5VN.js} +0 -0
  443. /package/dist/web/assets/{polar-CTeGoFxV.js → polar-BMu17-O0.js} +0 -0
  444. /package/dist/web/assets/{portal-container-context-a399Hd9F.js → portal-container-context-DSGdlXrJ.js} +0 -0
  445. /package/dist/web/assets/{postcss-jvFe6GNE.js → postcss-DWib10RZ.js} +0 -0
  446. /package/dist/web/assets/{powerquery-D9Dw1hJp.js → powerquery-Dht4CK0b.js} +0 -0
  447. /package/dist/web/assets/{powershell-CZqvYQtg.js → powershell-DvrCg8ji.js} +0 -0
  448. /package/dist/web/assets/{prisma-B4D2nO_7.js → prisma-DbodYvCM.js} +0 -0
  449. /package/dist/web/assets/{prolog-CQbyUcCF.js → prolog-DZ-m6ysB.js} +0 -0
  450. /package/dist/web/assets/{proto-Cwpo0cW1.js → proto-HHpG3tGO.js} +0 -0
  451. /package/dist/web/assets/{puppet-CKxCwBod.js → puppet-BljHe_75.js} +0 -0
  452. /package/dist/web/assets/{purescript-B-MyXMMp.js → purescript-DzCiFR0B.js} +0 -0
  453. /package/dist/web/assets/{python-D6QbSR2S.js → python-B5BQ27cC.js} +0 -0
  454. /package/dist/web/assets/{qmldir-BpTSVcw_.js → qmldir-CYlcXrnn.js} +0 -0
  455. /package/dist/web/assets/{qss-DBc4gbpe.js → qss-Ce7T_TEg.js} +0 -0
  456. /package/dist/web/assets/{r-i9jMMi0s.js → r-Bku-2Jp_.js} +0 -0
  457. /package/dist/web/assets/{racket-BWNaxU7C.js → racket-DtfSL3WR.js} +0 -0
  458. /package/dist/web/assets/{raku-fqTZLn7L.js → raku-CnDLsBJr.js} +0 -0
  459. /package/dist/web/assets/{react-X4jUntEf.js → react-Dfio3PfK.js} +0 -0
  460. /package/dist/web/assets/{red-D-JEcuzA.js → red-vbqcE9b-.js} +0 -0
  461. /package/dist/web/assets/{refresh-cw-B9iv7csb.js → refresh-cw-84mz8aOE.js} +0 -0
  462. /package/dist/web/assets/{reg-BbirlU1S.js → reg-DIDLT8-A.js} +0 -0
  463. /package/dist/web/assets/{regexp-D8VTFrtg.js → regexp-CB4h-VPu.js} +0 -0
  464. /package/dist/web/assets/{rel-ByQGKiNM.js → rel-CiQEnWXa.js} +0 -0
  465. /package/dist/web/assets/{resolve-theme-Cqb9zWYc.js → resolve-theme-DPTQHAy1.js} +0 -0
  466. /package/dist/web/assets/{riscv-B5nqCfLE.js → riscv-R78iq5Rf.js} +0 -0
  467. /package/dist/web/assets/{ron-DOVh_iZR.js → ron-DhYENxc4.js} +0 -0
  468. /package/dist/web/assets/{rose-pine-jp51ZUzi.js → rose-pine-5wBeyjfJ.js} +0 -0
  469. /package/dist/web/assets/{rose-pine-dawn-B7hkmmWy.js → rose-pine-dawn-FpKtPCuH.js} +0 -0
  470. /package/dist/web/assets/{rose-pine-moon-Ff063yLj.js → rose-pine-moon-BwcIYc7C.js} +0 -0
  471. /package/dist/web/assets/{rosmsg-Cyk7Il8z.js → rosmsg-DWIGAYMl.js} +0 -0
  472. /package/dist/web/assets/{rotate-ccw-DnuPzJRE.js → rotate-ccw-CNTk2J7l.js} +0 -0
  473. /package/dist/web/assets/{rust-DRNM5epF.js → rust-DU_ofKgt.js} +0 -0
  474. /package/dist/web/assets/{sass-D_cB0-M1.js → sass-U9SUuYEy.js} +0 -0
  475. /package/dist/web/assets/{save-DxonD4y1.js → save-CtW9ojKk.js} +0 -0
  476. /package/dist/web/assets/{scala-Crl-1kKS.js → scala-op4gjA6j.js} +0 -0
  477. /package/dist/web/assets/{scheme-DCaWyEJ0.js → scheme-tEjkR8rL.js} +0 -0
  478. /package/dist/web/assets/{sdbl-BbXNqwtX.js → sdbl-C6SSRmZg.js} +0 -0
  479. /package/dist/web/assets/{send-B-t5Mz26.js → send-C_sLFt56.js} +0 -0
  480. /package/dist/web/assets/{shallow-4zAfs2KW.js → shallow-C1YoJQA-.js} +0 -0
  481. /package/dist/web/assets/{shellscript-BAgksBhW.js → shellscript-Bf2KiwQj.js} +0 -0
  482. /package/dist/web/assets/{shield-check-CdTiK1j2.js → shield-check-Du6HKYn9.js} +0 -0
  483. /package/dist/web/assets/{shield-off-DuetkXKM.js → shield-off-CWKb1MQL.js} +0 -0
  484. /package/dist/web/assets/{slack-dark-GB-ElUW_.js → slack-dark-oGgCey8r.js} +0 -0
  485. /package/dist/web/assets/{slack-ochin-tU6CFD3S.js → slack-ochin-CemlB8cE.js} +0 -0
  486. /package/dist/web/assets/{smalltalk-DSVGUbtL.js → smalltalk-CTE-6jrZ.js} +0 -0
  487. /package/dist/web/assets/{snazzy-light-DZhi3PoS.js → snazzy-light-DoEIGnmz.js} +0 -0
  488. /package/dist/web/assets/{solarized-dark-D6AMCco7.js → solarized-dark-CStiJyBg.js} +0 -0
  489. /package/dist/web/assets/{solarized-light-DepuTWSE.js → solarized-light-CfuNycS9.js} +0 -0
  490. /package/dist/web/assets/{solidity-CsxN618l.js → solidity-DL6sO5rd.js} +0 -0
  491. /package/dist/web/assets/{splunk-CF1m0Kkv.js → splunk-yK4ZUFBB.js} +0 -0
  492. /package/dist/web/assets/{sql-DvyMDgrB.js → sql-Lw03eoN8.js} +0 -0
  493. /package/dist/web/assets/{ssh-config-a8sDo-Q8.js → ssh-config-DeRp25TR.js} +0 -0
  494. /package/dist/web/assets/{stylus-DCJ-6AjT.js → stylus-D8OMsPh-.js} +0 -0
  495. /package/dist/web/assets/{swift-c-ZFxyPP.js → swift-nsl9stPV.js} +0 -0
  496. /package/dist/web/assets/{synthwave-84-9NPgBcuv.js → synthwave-84-exAgsgpf.js} +0 -0
  497. /package/dist/web/assets/{system-verilog-DPD02IOo.js → system-verilog-DBWf00T6.js} +0 -0
  498. /package/dist/web/assets/{systemd-BUr5HRtU.js → systemd-nhu27Dw5.js} +0 -0
  499. /package/dist/web/assets/{table-FLU8ei8b.js → table-GAR4upb6.js} +0 -0
  500. /package/dist/web/assets/{talonscript-BNBQV5YJ.js → talonscript-CNOEcGDc.js} +0 -0
  501. /package/dist/web/assets/{tasl-BETjpSyu.js → tasl-CjYZenSH.js} +0 -0
  502. /package/dist/web/assets/{tcl-Clp_AZiC.js → tcl-BVA09RDz.js} +0 -0
  503. /package/dist/web/assets/{terraform-B0kZ0lim.js → terraform-C5EloH1q.js} +0 -0
  504. /package/dist/web/assets/{text-wrap-B05VGRa-.js → text-wrap-D71Jqz5U.js} +0 -0
  505. /package/dist/web/assets/{tokyo-night-oOJ0a7Dk.js → tokyo-night-D764DdhO.js} +0 -0
  506. /package/dist/web/assets/{toml-DOPCSumy.js → toml-CCDYblSB.js} +0 -0
  507. /package/dist/web/assets/{trash-2-CTXlTflF.js → trash-2-C08fhIg1.js} +0 -0
  508. /package/dist/web/assets/{triangle-alert-BRq0VAfd.js → triangle-alert-85BQgs3T.js} +0 -0
  509. /package/dist/web/assets/{tsv-D6qUGf84.js → tsv-BZ4sA05e.js} +0 -0
  510. /package/dist/web/assets/{tsx-B6qYhT6O.js → tsx-DPLBzAt5.js} +0 -0
  511. /package/dist/web/assets/{turtle-C0Rjzw73.js → turtle-CwGHCq40.js} +0 -0
  512. /package/dist/web/assets/{typescript-BiydShEu.js → typescript-CUH0vBAl.js} +0 -0
  513. /package/dist/web/assets/{typespec-DjmF6Aww.js → typespec-Bq31Vq4u.js} +0 -0
  514. /package/dist/web/assets/{typst-CPuscfcL.js → typst-MD9BR9W5.js} +0 -0
  515. /package/dist/web/assets/{use-is-mobile-b94q8RvF.js → use-is-mobile-DUuki97q.js} +0 -0
  516. /package/dist/web/assets/{utils-C9OjCrou.js → utils-u2fbYPrY.js} +0 -0
  517. /package/dist/web/assets/{v-DE4vpLLQ.js → v-DZV7IxIT.js} +0 -0
  518. /package/dist/web/assets/{vala-CmUF2pS5.js → vala-urd1zi9S.js} +0 -0
  519. /package/dist/web/assets/{vb-BvcRoX4b.js → vb-DIZU3zbJ.js} +0 -0
  520. /package/dist/web/assets/{vendor-xterm-K8kpLxdb.js → vendor-xterm-DCj0zgSC.js} +0 -0
  521. /package/dist/web/assets/{verilog-BGIzyH3R.js → verilog-Ndz_ciTA.js} +0 -0
  522. /package/dist/web/assets/{vesper-CG7IrHQQ.js → vesper-C86M7LJp.js} +0 -0
  523. /package/dist/web/assets/{vhdl-DMSumomw.js → vhdl-_klwDUjd.js} +0 -0
  524. /package/dist/web/assets/{viml-Drt7AEmU.js → viml-DzRXWoQh.js} +0 -0
  525. /package/dist/web/assets/{vitesse-black-DmsjfN0b.js → vitesse-black-TWZ20_3U.js} +0 -0
  526. /package/dist/web/assets/{vitesse-dark-Dl55_WeJ.js → vitesse-dark-Dl7R6fy9.js} +0 -0
  527. /package/dist/web/assets/{vitesse-light-Crbv9aGg.js → vitesse-light-Cs0Dt2_n.js} +0 -0
  528. /package/dist/web/assets/{vyper-CB7psEJj.js → vyper-qnCVj8ia.js} +0 -0
  529. /package/dist/web/assets/{wasm-Dck8KPgP.js → wasm-Ckx9jBPe.js} +0 -0
  530. /package/dist/web/assets/{wasm-BY1tuM5m.js → wasm-Dy-SJ3W9.js} +0 -0
  531. /package/dist/web/assets/{wenyan-DhewQ68b.js → wenyan-DTEy2Lx-.js} +0 -0
  532. /package/dist/web/assets/{wgsl-DlmiP456.js → wgsl-DC7n63Sy.js} +0 -0
  533. /package/dist/web/assets/{wikitext-cQCnqNgE.js → wikitext-1zFoZiW6.js} +0 -0
  534. /package/dist/web/assets/{wit-S5MaCngB.js → wit-DtTfCRph.js} +0 -0
  535. /package/dist/web/assets/{wolfram-Bh8n_9zr.js → wolfram-B2Bgu1KL.js} +0 -0
  536. /package/dist/web/assets/{x-YDu4m2Iq.js → x-CeN0Qybc.js} +0 -0
  537. /package/dist/web/assets/{yaml-BoK_17l9.js → yaml-B-Jxk0IQ.js} +0 -0
  538. /package/dist/web/assets/{zenscript-DB3ZFwNJ.js → zenscript-BWRfvNER.js} +0 -0
  539. /package/dist/web/assets/{zig-Bv1BWmfT.js → zig-CkwuvETm.js} +0 -0
package/src/types/chat.ts CHANGED
@@ -49,6 +49,8 @@ export interface AIProvider {
49
49
  getUsage?(): Promise<UsageInfo>;
50
50
  /** True when a live streaming subprocess exists for this session */
51
51
  hasStreamingSession?(sessionId: string): boolean;
52
+ /** Prompt-cache lifetime for this session, in ms — how long holding its subprocess pays. */
53
+ promptCacheTtlMs?(sessionId: string): number;
52
54
  }
53
55
 
54
56
  export interface ModelOption {
package/src/web/app.tsx CHANGED
@@ -48,6 +48,9 @@ const MobileExplorerSheet = lazy(() =>
48
48
  const TeamMemberSheet = lazy(() =>
49
49
  import("@/components/chat/team-member-sheet").then((m) => ({ default: m.TeamMemberSheet })),
50
50
  );
51
+ const RemoteDesktopMobileSheet = lazy(() =>
52
+ import("@/components/remote-desktop/remote-desktop-mobile-sheet").then((m) => ({ default: m.RemoteDesktopMobileSheet })),
53
+ );
51
54
 
52
55
  type AuthState = "checking" | "authenticated" | "unauthenticated";
53
56
 
@@ -357,6 +360,11 @@ export function App() {
357
360
  <TeamMemberSheet />
358
361
  </Suspense>
359
362
 
363
+ {/* Mobile full-screen remote-desktop viewer — same reason as TeamMemberSheet above */}
364
+ <Suspense fallback={null}>
365
+ <RemoteDesktopMobileSheet />
366
+ </Suspense>
367
+
360
368
  {/* Global bug report popup */}
361
369
  <BugReportPopup />
362
370
 
@@ -70,9 +70,14 @@ export function AddAccountDialog({ open, onOpenChange, onSuccess }: AddAccountDi
70
70
  try {
71
71
  let code = oauthCode.trim();
72
72
  if (code.includes("#")) code = code.split("#")[0] ?? code;
73
- await exchangeOAuthCode(code, oauthState);
73
+ const acc = await exchangeOAuthCode(code, oauthState);
74
74
  handleClose();
75
- onSuccess("Account connected via OAuth!");
75
+ // Signing in again on an account this machine parked carries the token in but
76
+ // deliberately leaves it out of the rotation. A flat success over a toggle that is
77
+ // still off reads as a bug, and this is the only place the user finds out otherwise.
78
+ onSuccess(acc?.status === "disabled"
79
+ ? "Signed in. This account is still disabled — enable it to use it."
80
+ : "Account connected via OAuth!");
76
81
  } catch (e) {
77
82
  setAddError((e as Error).message);
78
83
  }
@@ -84,9 +89,11 @@ export function AddAccountDialog({ open, onOpenChange, onSuccess }: AddAccountDi
84
89
  setAdding(true);
85
90
  setAddError(null);
86
91
  try {
87
- await addAccount({ apiKey: newToken.trim(), label: newLabel.trim() || undefined });
92
+ const acc = await addAccount({ apiKey: newToken.trim(), label: newLabel.trim() || undefined });
88
93
  handleClose();
89
- onSuccess("Account added!");
94
+ onSuccess(acc?.status === "disabled"
95
+ ? "Token updated. This account is still disabled — enable it to use it."
96
+ : "Account added!");
90
97
  } catch (e) {
91
98
  setAddError((e as Error).message);
92
99
  }
@@ -90,7 +90,11 @@ function useQuestionKeyboard(config: {
90
90
  useEffect(() => {
91
91
  if (!config.enabled) return;
92
92
  const handleKeyDown = (e: KeyboardEvent) => {
93
- const isTyping = document.activeElement === config.customInputRef.current;
93
+ // Resolve focus from the input's OWN document: this card can be rendered inside a
94
+ // picture-in-picture window, and the module-global `document` is always the main one —
95
+ // there the input is never active, so every printable key would be treated as a shortcut.
96
+ const input = config.customInputRef.current;
97
+ const isTyping = !!input && input.ownerDocument.activeElement === input;
94
98
 
95
99
  // Number keys 1-9
96
100
  if (!isTyping && e.key >= "1" && e.key <= "9") {
@@ -132,7 +136,10 @@ function useQuestionKeyboard(config: {
132
136
  if (el) {
133
137
  el.addEventListener("keydown", handleKeyDown);
134
138
  el.setAttribute("tabindex", "0");
135
- if (!el.contains(document.activeElement)) el.focus();
139
+ // Same reason: asking the main document whether focus is already inside the card answers
140
+ // "no" for a card living in another document, and this effect re-runs on every render —
141
+ // which would yank focus out of the custom input after each keystroke.
142
+ if (!el.contains(el.ownerDocument.activeElement)) el.focus();
136
143
  }
137
144
  return () => { el?.removeEventListener("keydown", handleKeyDown); };
138
145
  }, [config, focusedOption]);
@@ -154,11 +154,12 @@ function formatLastUpdated(ts: number | null | undefined): string | null {
154
154
  return `${days}d ago`;
155
155
  }
156
156
 
157
- function AccountUsageCard({ entry, isActive, accountInfo, onToggle, onDelete, onExport, onViewProfile, flash, fullscreen }: {
157
+ function AccountUsageCard({ entry, isActive, accountInfo, onToggle, toggling, onDelete, onExport, onViewProfile, flash, fullscreen }: {
158
158
  entry: AccountUsageEntry;
159
159
  isActive: boolean;
160
160
  accountInfo?: AccountInfo;
161
161
  onToggle?: (id: string, status: string) => void;
162
+ toggling?: boolean;
162
163
  onDelete?: (id: string, display: string) => void;
163
164
  onExport?: (id: string) => void;
164
165
  onViewProfile?: (profile: OAuthProfileData, accountId: string) => void;
@@ -207,7 +208,7 @@ function AccountUsageCard({ entry, isActive, accountInfo, onToggle, onDelete, on
207
208
  <Switch
208
209
  checked={status !== "disabled"}
209
210
  onCheckedChange={() => onToggle(entry.accountId, status)}
210
- disabled={status === "cooldown"}
211
+ disabled={toggling || status === "cooldown"}
211
212
  className="scale-[0.6] cursor-pointer"
212
213
  />
213
214
  )}
@@ -269,6 +270,7 @@ export function UsageDetailPanel({ usage, visible, onClose, onReload, loading, l
269
270
  const [exportPreselect, setExportPreselect] = useState<string | null>(null);
270
271
  const [isFullscreen, setIsFullscreen] = useState(false);
271
272
  const [message, setMessage] = useState<string | null>(null);
273
+ const [togglingId, setTogglingId] = useState<string | null>(null);
272
274
  const msgTimer = useRef<ReturnType<typeof setTimeout>>(undefined);
273
275
  const prevUsagesRef = useRef<AccountUsageEntry[]>([]);
274
276
 
@@ -345,7 +347,18 @@ export function UsageDetailPanel({ usage, visible, onClose, onReload, loading, l
345
347
  const fsRows = Math.ceil(fsCount / fsCols);
346
348
 
347
349
  async function handleToggle(id: string, status: string) {
348
- await patchAccount(id, { status: status === "disabled" ? "active" : "disabled" });
350
+ // Enabling a parked account proves its token server-side first, which is a network
351
+ // round trip that can come back 400 — and patchAccount throws on that. Without the
352
+ // catch the message the server takes care to write reaches nobody: unhandled
353
+ // rejection, no toast, no reload, and the Switch silently snaps back. The pending
354
+ // flag is because that round trip can take most of a minute in the worst case.
355
+ setTogglingId(id);
356
+ try {
357
+ await patchAccount(id, { status: status === "disabled" ? "active" : "disabled" });
358
+ } catch (e) {
359
+ showMessage((e as Error).message);
360
+ }
361
+ setTogglingId(null);
349
362
  loadAll();
350
363
  onReload?.();
351
364
  }
@@ -440,6 +453,7 @@ export function UsageDetailPanel({ usage, visible, onClose, onReload, loading, l
440
453
  isActive={entry.accountId === (activeAccountId ?? usage.activeAccountId)}
441
454
  accountInfo={accountMap.get(entry.accountId)}
442
455
  onToggle={handleToggle}
456
+ toggling={togglingId === entry.accountId}
443
457
  onDelete={(id, display) => setDeleteTarget({ id, display })}
444
458
  onExport={(id) => { setExportPreselect(id); setShowExportDialog(true); }}
445
459
  onViewProfile={(profile, accountId) => setProfileView({ profile, accountId })}
@@ -22,6 +22,7 @@ export const WINDOW_CONTENT: Record<WindowKind, LazyExoticComponent<ComponentTyp
22
22
  "team-member": lazy(() => import("@/components/chat/team-member-window-content")),
23
23
  "system-monitor": lazy(() => import("@/components/system/system-monitor-window-content")),
24
24
  "tab-host": lazy(() => import("./tab-host-window-content")),
25
+ "remote-desktop": lazy(() => import("@/components/remote-desktop/remote-desktop-window-content")),
25
26
  };
26
27
 
27
28
  /** Titlebar text for a window. Falls back to the kind's generic name. */
@@ -33,6 +34,7 @@ export function windowTitle(kind: WindowKind, payload?: Record<string, unknown>)
33
34
  return typeof member === "string" && member ? `Session — ${member}` : "Team member";
34
35
  }
35
36
  if (kind === "system-monitor") return "System Monitor";
37
+ if (kind === "remote-desktop") return "Remote Desktop";
36
38
  // A detached tab carries its title in the payload; the generic name only shows for a
37
39
  // window whose tab has not been resolved yet (restore before the layout is loaded).
38
40
  if (kind === "tab-host") return "Tab";
@@ -7,7 +7,7 @@ import type { Rect } from "./window-geometry";
7
7
  * Single source of truth: the persistence layer filters this list instead of repeating it,
8
8
  * so a new kind can never be silently dropped on reload.
9
9
  */
10
- export const WINDOW_KINDS = ["explorer", "team-member", "system-monitor", "tab-host"] as const;
10
+ export const WINDOW_KINDS = ["explorer", "team-member", "system-monitor", "tab-host", "remote-desktop"] as const;
11
11
 
12
12
  export type WindowKind = (typeof WINDOW_KINDS)[number];
13
13
 
@@ -1,5 +1,5 @@
1
1
  import { useState, useCallback, useEffect, useMemo } from "react";
2
- import { X, Bug as BugIcon, Cloud, FolderTree } from "lucide-react";
2
+ import { X, Bug as BugIcon, Cloud, FolderTree, MonitorSmartphone } from "lucide-react";
3
3
  import { useShallow } from "zustand/react/shallow";
4
4
  import { useProjectStore } from "@/stores/project-store";
5
5
  import { useSettingsStore, type SidebarActiveTab } from "@/stores/settings-store";
@@ -23,7 +23,10 @@ import { CloudSharePopover } from "@/components/layout/cloud-share-popover";
23
23
  import { BottomSheet } from "@/components/ui/mobile-bottom-sheet";
24
24
  import { isMobileDevice } from "@/hooks/use-is-mobile";
25
25
  import { openExplorer } from "@/components/os-explorer/open-explorer";
26
+ import { useOpenRemoteDesktop } from "@/components/remote-desktop/open-remote-desktop";
27
+ import { useRemoteDesktopAvailable } from "@/components/remote-desktop/use-remote-desktop-available";
26
28
  import { FeatureBadge } from "@/components/ui/feature-badge";
29
+ import type { FeatureBadgeId } from "@/lib/feature-badges";
27
30
  import { cn } from "@/lib/utils";
28
31
 
29
32
  // Tab ids the mobile drawer can render content for. `search` is desktop-only for now;
@@ -33,6 +36,24 @@ const MOBILE_SUPPORTED = new Set<string>([
33
36
  ]);
34
37
  const isMobileSupported = (id: SidebarActiveTab) => MOBILE_SUPPORTED.has(id) || id.startsWith("ext:");
35
38
 
39
+ /** One utility tile in the drawer footer grid — icon over a short single-line label, with an
40
+ * optional corner feature badge. Uniform size so the grid wraps cleanly as tiles are added,
41
+ * instead of a single cramped row where long labels ("Cloud & Share") wrapped and overflowed. */
42
+ function FooterTile({ icon: Icon, label, badge, onClick }: {
43
+ icon: React.ElementType; label: string; badge?: FeatureBadgeId; onClick: () => void;
44
+ }) {
45
+ return (
46
+ <button
47
+ onClick={onClick}
48
+ className="group relative flex flex-col items-center justify-center gap-1 rounded-lg py-2.5 text-text-subtle hover:bg-surface-elevated hover:text-text-secondary transition-colors"
49
+ >
50
+ <Icon className="size-4" />
51
+ <span className="text-[10px] leading-none">{label}</span>
52
+ <FeatureBadge id={badge} variant="corner" />
53
+ </button>
54
+ );
55
+ }
56
+
36
57
  interface MobileDrawerProps {
37
58
  isOpen: boolean;
38
59
  onClose: () => void;
@@ -47,6 +68,8 @@ export function MobileDrawer({ isOpen, onClose, initialTab }: MobileDrawerProps)
47
68
  const sidebarTabOrder = useSettingsStore((s) => s.sidebarTabOrder);
48
69
  const setSidebarTabOrder = useSettingsStore((s) => s.setSidebarTabOrder);
49
70
  const contributions = useExtensionStore((s) => s.contributions);
71
+ const { available: remoteDesktopAvailable } = useRemoteDesktopAvailable();
72
+ const openRemoteDesktop = useOpenRemoteDesktop();
50
73
  const [activeTab, setActiveTab] = useState<SidebarActiveTab>(initialTab ?? "explorer");
51
74
  const [cloudOpen, setCloudOpen] = useState(false);
52
75
 
@@ -138,33 +161,21 @@ export function MobileDrawer({ isOpen, onClose, initialTab }: MobileDrawerProps)
138
161
  onReorder={setSidebarTabOrder}
139
162
  />
140
163
 
141
- {/* Files (OS explorer) + Report Bug + Cloud & Share + Version / Upgrade */}
142
- <div className="flex items-center justify-between px-4 py-2 border-t border-border text-[11px]">
143
- <UpgradeButton align="left" />
144
- <div className="flex items-center gap-3">
164
+ {/* Footer: version/upgrade on its own line, then a uniform wrapping grid of utility
165
+ tiles (opens explorer/remote/cloud, report bug) scales cleanly as tiles are added. */}
166
+ <div className="border-t border-border px-3 py-2">
167
+ <div className="grid grid-cols-4 gap-1">
145
168
  {/* Not a sidebar tab — the explorer opens as its own full-screen sheet. */}
146
- <button
147
- onClick={() => { onClose(); void openExplorer(); }}
148
- className="flex items-center gap-1 text-[10px] text-text-subtle hover:text-text-secondary transition-colors"
149
- >
150
- <FolderTree className="size-3" />
151
- <span>Files</span>
152
- <FeatureBadge id="os-explorer" className="text-[7px] px-1" />
153
- </button>
154
- <button
155
- onClick={() => setCloudOpen(true)}
156
- className="flex items-center gap-1 text-[10px] text-text-subtle hover:text-text-secondary transition-colors"
157
- >
158
- <Cloud className="size-3" />
159
- <span>Cloud &amp; Share</span>
160
- </button>
161
- <button
162
- onClick={handleReportBug}
163
- className="flex items-center gap-1 text-[10px] text-text-subtle hover:text-text-secondary transition-colors"
164
- >
165
- <BugIcon className="size-3" />
166
- <span>Report Bug</span>
167
- </button>
169
+ <FooterTile icon={FolderTree} label="Files" badge="os-explorer" onClick={() => { onClose(); void openExplorer(); }} />
170
+ {remoteDesktopAvailable && (
171
+ <FooterTile icon={MonitorSmartphone} label="Remote" badge="remote-desktop" onClick={() => { onClose(); openRemoteDesktop(); }} />
172
+ )}
173
+ <FooterTile icon={Cloud} label="Cloud" onClick={() => setCloudOpen(true)} />
174
+ <FooterTile icon={BugIcon} label="Bug" onClick={handleReportBug} />
175
+ </div>
176
+ {/* Version / upgrade pinned at the very bottom, under a divider. */}
177
+ <div className="mt-2 pt-2 border-t border-border px-1 text-[11px]">
178
+ <UpgradeButton align="left" />
168
179
  </div>
169
180
  </div>
170
181
  </div>
@@ -1,7 +1,9 @@
1
1
  import { useState, useRef, useEffect, useMemo, memo } from "react";
2
2
  import { createPortal } from "react-dom";
3
- import { Settings, Bug, Cloud, FolderTree } from "lucide-react";
3
+ import { Settings, Bug, Cloud, FolderTree, MonitorSmartphone } from "lucide-react";
4
4
  import { openExplorer } from "@/components/os-explorer/open-explorer";
5
+ import { useOpenRemoteDesktop } from "@/components/remote-desktop/open-remote-desktop";
6
+ import { useRemoteDesktopAvailable } from "@/components/remote-desktop/use-remote-desktop-available";
5
7
  import { FeatureBadge } from "@/components/ui/feature-badge";
6
8
  import type { FeatureBadgeId } from "@/lib/feature-badges";
7
9
  import { useSettingsStore, type SidebarActiveTab } from "@/stores/settings-store";
@@ -147,6 +149,9 @@ export const NavSectionRail = memo(function NavSectionRail({ className }: { clas
147
149
  return () => window.removeEventListener("open-cloud-share", open);
148
150
  }, []);
149
151
 
152
+ const openRemoteDesktop = useOpenRemoteDesktop();
153
+ const { available: remoteDesktopAvailable } = useRemoteDesktopAvailable();
154
+
150
155
  const handleReportBug = () => openBugReportPopup(version);
151
156
 
152
157
  // Rail tab click: collapsed → open on any tab; open → clicking the active tab closes it.
@@ -209,8 +214,11 @@ export const NavSectionRail = memo(function NavSectionRail({ className }: { clas
209
214
  </>,
210
215
  document.body,
211
216
  )}
212
- {/* Not a sidebar tabthe explorer lives in its own floating window. */}
217
+ {/* Not sidebar tabsthese open their own floating windows. */}
213
218
  <FooterUtil icon={FolderTree} label="File Explorer" featureBadge="os-explorer" onClick={() => void openExplorer()} />
219
+ {remoteDesktopAvailable && (
220
+ <FooterUtil icon={MonitorSmartphone} label="Remote Desktop" featureBadge="remote-desktop" onClick={openRemoteDesktop} />
221
+ )}
214
222
  <FooterUtil icon={Bug} label="Report Bug" onClick={handleReportBug} />
215
223
  <FooterUtil icon={Settings} label="Settings" active={sidebarActiveTab === "settings"} onClick={() => handleTabClick("settings")} />
216
224
  </div>
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Opens the Remote Desktop viewer: a floating window on desktop, the full-screen mobile sheet
3
+ * below `md` (`WindowLayer` renders nothing there — same split `open-explorer.ts` makes for the
4
+ * file explorer). There is one host to control, so a second call on desktop focuses the
5
+ * existing window instead of opening another; on mobile the sheet is a singleton already.
6
+ */
7
+ import { useCallback } from "react";
8
+ import { useWindowStore } from "@/components/floating-window/window-store";
9
+ import { isMobileDevice } from "@/hooks/use-is-mobile";
10
+ import { useRemoteDesktopMobileOpenState } from "./use-remote-desktop-mobile-open-state";
11
+
12
+ export function useOpenRemoteDesktop(): () => void {
13
+ const openWindow = useWindowStore((s) => s.open);
14
+ const focusWindow = useWindowStore((s) => s.focus);
15
+ const openMobileSheet = useRemoteDesktopMobileOpenState((s) => s.open);
16
+
17
+ return useCallback(() => {
18
+ if (isMobileDevice()) {
19
+ openMobileSheet();
20
+ return;
21
+ }
22
+ const existing = Object.values(useWindowStore.getState().windows).find((w) => w.kind === "remote-desktop");
23
+ if (existing) focusWindow(existing.id);
24
+ else openWindow("remote-desktop");
25
+ }, [openWindow, focusWindow, openMobileSheet]);
26
+ }
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Client → host coordinate mapping. Only the canvas's CSS-pixel bounding rect matters —
3
+ * `devicePixelRatio` describes the *viewer's* screen (irrelevant to host pixels, and would
4
+ * double-scale/misplace the cursor if folded in), so it is deliberately never read here.
5
+ */
6
+ export interface Fraction {
7
+ xFrac: number;
8
+ yFrac: number;
9
+ }
10
+
11
+ /** Plain bounding-box shape every function here accepts — deliberately not `DOMRect` so pure
12
+ * computed rects (e.g. `letterboxedContentRect`'s output) satisfy it without faking DOM-only
13
+ * members (`right`, `bottom`, `toJSON`, …) that nothing here ever reads. */
14
+ export interface Rect {
15
+ left: number;
16
+ top: number;
17
+ width: number;
18
+ height: number;
19
+ }
20
+
21
+ function clamp01(n: number): number {
22
+ return Math.min(Math.max(n, 0), 1);
23
+ }
24
+
25
+ /** `rect` is the canvas element's `getBoundingClientRect()`. Fractions are clamped to 0..1 so
26
+ * a pointer that slips just outside the element (fast drag) still maps to an edge, not a
27
+ * wildly out-of-range host coordinate. */
28
+ export function fractionFromPoint(clientX: number, clientY: number, rect: Rect): Fraction {
29
+ const xFrac = rect.width > 0 ? (clientX - rect.left) / rect.width : 0;
30
+ const yFrac = rect.height > 0 ? (clientY - rect.top) / rect.height : 0;
31
+ return { xFrac: clamp01(xFrac), yFrac: clamp01(yFrac) };
32
+ }
33
+
34
+ /** A CSS `transform: translate(panX, panY) scale(scale)` applied around the transformed
35
+ * element's own center (the mobile viewer's default `transform-origin`). */
36
+ export interface ZoomPanTransform {
37
+ scale: number;
38
+ panX: number;
39
+ panY: number;
40
+ }
41
+
42
+ /**
43
+ * Reverse-maps a screen point through an active pinch-zoom/pan transform back to a 0..1
44
+ * fraction of the capture — used by the mobile touch viewer, which zooms/pans the canvas with
45
+ * a CSS transform instead of re-rendering at a different resolution.
46
+ *
47
+ * `rect` MUST be the bounding rect of the never-transformed container the canvas fills at
48
+ * `scale=1, pan=0` (not the live, possibly-transformed canvas rect) — that container is the
49
+ * stable reference frame this function projects screen points back into. Deliberately pure
50
+ * arithmetic rather than reading the live (transformed) element's `getBoundingClientRect()`:
51
+ * jsdom does not compute CSS transforms into that rect at all, so this stays unit-testable,
52
+ * and it avoids relying on `transform-origin` math the browser applies opaquely.
53
+ */
54
+ export function fractionFromZoomedPoint(
55
+ clientX: number,
56
+ clientY: number,
57
+ rect: Rect,
58
+ transform: ZoomPanTransform,
59
+ ): Fraction {
60
+ const scale = transform.scale || 1;
61
+ const cx = rect.left + rect.width / 2;
62
+ const cy = rect.top + rect.height / 2;
63
+ const baseX = cx + (clientX - cx - transform.panX) / scale;
64
+ const baseY = cy + (clientY - cy - transform.panY) / scale;
65
+ return fractionFromPoint(baseX, baseY, rect);
66
+ }
67
+
68
+ /**
69
+ * The on-screen sub-rectangle a canvas actually paints its content into under `object-fit:
70
+ * contain` (or the equivalent CSS2.1 replaced-element max-width/max-height algorithm) — the
71
+ * canvas ELEMENT's own box does not shrink to the letterboxed video; only the pixels drawn
72
+ * inside it do. `box` is that element's box at rest (`scale=1, pan=0` — never the live,
73
+ * zoomed/panned rect: see `fractionFromZoomedPoint`'s doc comment for why), `contentWidth`/
74
+ * `contentHeight` are the canvas's drawing-buffer dimensions (its `.width`/`.height`
75
+ * attributes, i.e. the real capture resolution, NOT its CSS size).
76
+ *
77
+ * `object-fit: contain` centers the letterboxed content within `box` by default
78
+ * (`object-position: 50% 50%`), so this rect's own center always coincides with `box`'s
79
+ * center — that is what keeps `fractionFromZoomedPoint`'s transform-origin math (which derives
80
+ * `cx,cy` from whatever rect it's given) correct when this rect is passed to it instead of the
81
+ * raw container box.
82
+ *
83
+ * Falls back to `box` unchanged if the content has no size yet (e.g. before the first decoded
84
+ * frame sets `canvas.width`/`canvas.height`).
85
+ */
86
+ export function letterboxedContentRect(box: Rect, contentWidth: number, contentHeight: number): Rect {
87
+ if (contentWidth <= 0 || contentHeight <= 0 || box.width <= 0 || box.height <= 0) return box;
88
+ const boxAspect = box.width / box.height;
89
+ const contentAspect = contentWidth / contentHeight;
90
+ if (contentAspect > boxAspect) {
91
+ // Content is relatively WIDER than the box -> width-constrained, letterboxed top/bottom.
92
+ const height = box.width / contentAspect;
93
+ return { left: box.left, top: box.top + (box.height - height) / 2, width: box.width, height };
94
+ }
95
+ // Content is relatively TALLER/narrower than the box -> height-constrained, letterboxed sides.
96
+ const width = box.height * contentAspect;
97
+ return { left: box.left + (box.width - width) / 2, top: box.top, width, height: box.height };
98
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Strictly-increasing microsecond timestamps for `EncodedVideoChunk`.
3
+ *
4
+ * The decoder previously used `performance.now() * 1000` per chunk. Some decoders — especially
5
+ * mobile hardware H.264 decoders — reject a chunk whose timestamp does not strictly increase
6
+ * from the previous one; two chunks landing in the same millisecond (easy at 30fps under normal
7
+ * network/encode jitter) get equal timestamps and can trip that check.
8
+ *
9
+ * This is decode-only playback (no A/V sync, nothing ever reads the timestamp back to schedule
10
+ * presentation), so the value only has to satisfy "strictly greater than the last one" — a
11
+ * frame-index-based synthetic clock does that unconditionally, independent of wall-clock
12
+ * resolution or jitter.
13
+ */
14
+ const DEFAULT_FPS = 30;
15
+
16
+ /** `frameIndex` is a 0-based, ever-incrementing counter of chunks decoded by ONE decoder
17
+ * instance — reset it to 0 whenever a new `VideoDecoder` is created (a fresh instance has no
18
+ * timestamp history to stay monotonic against). */
19
+ export function frameTimestampMicros(frameIndex: number, fps: number = DEFAULT_FPS): number {
20
+ return Math.round((frameIndex * 1_000_000) / fps);
21
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Pure classifiers the mobile touch viewer's gesture engine uses to tell "tap" apart from
3
+ * "drag", and "pinch-zoom" apart from "two-finger scroll". Kept dependency-free (no DOM,
4
+ * no React) so they can be unit-tested without a browser.
5
+ */
6
+
7
+ /** One sampled point in a gesture: client-space coordinates + a timestamp (ms, any monotonic
8
+ * clock — `performance.now()` in the real hook). */
9
+ export interface PointerSample {
10
+ x: number;
11
+ y: number;
12
+ t: number;
13
+ }
14
+
15
+ /** A tap is a touch that ends close to where it started, quickly — anything else (moved
16
+ * further, or held longer) is a drag/long-press instead. Both thresholds must pass; a fast
17
+ * but far swipe is a drag, and a slow-but-still finger is a long-press, not a tap. */
18
+ export function isTapGesture(
19
+ start: PointerSample,
20
+ end: PointerSample,
21
+ moveThresholdPx = 10,
22
+ maxDurationMs = 300,
23
+ ): boolean {
24
+ const dist = Math.hypot(end.x - start.x, end.y - start.y);
25
+ const duration = end.t - start.t;
26
+ return dist <= moveThresholdPx && duration <= maxDurationMs;
27
+ }
28
+
29
+ export type TwoFingerGesture = "pinch" | "scroll";
30
+
31
+ /**
32
+ * Two-finger gestures start ambiguous — pinch-zoom and two-finger-scroll both begin as "two
33
+ * fingers moved a little". They are told apart by which signal crosses its threshold first:
34
+ * the distance *between* the fingers changing (pinch) vs. their midpoint translating while
35
+ * staying roughly the same distance apart (scroll). Once a gesture is classified, the caller
36
+ * should keep calling this with the *same* `startDistance` for the rest of the gesture and
37
+ * lock in the first non-null-equivalent result — see `use-remote-desktop-touch.ts`.
38
+ */
39
+ export function classifyTwoFingerGesture(
40
+ startDistance: number,
41
+ currentDistance: number,
42
+ distanceThresholdPx = 15,
43
+ ): TwoFingerGesture {
44
+ return Math.abs(currentDistance - startDistance) > distanceThresholdPx ? "pinch" : "scroll";
45
+ }
46
+
47
+ /** Euclidean distance between two touch points — shared helper so the hook and its tests
48
+ * compute "finger spread" identically. */
49
+ export function touchDistance(a: { x: number; y: number }, b: { x: number; y: number }): number {
50
+ return Math.hypot(b.x - a.x, b.y - a.y);
51
+ }
52
+
53
+ /** Midpoint between two touch points. */
54
+ export function touchMidpoint(a: { x: number; y: number }, b: { x: number; y: number }): { x: number; y: number } {
55
+ return { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };
56
+ }
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Compact function/combo key row shown above the toolbar while the virtual keyboard is open.
3
+ * Modifiers (Ctrl/Alt/Shift/Win) are STICKY: tap to hold (sends the key-down immediately,
4
+ * highlights the button), tap again to release (key-down was already sent, so this just sends
5
+ * the matching key-up) — lets a chord like "Ctrl + tap on the video" be built one tap at a time.
6
+ * The combo buttons (Ctrl+C etc.) are a shortcut for the same idea: one tap sends the whole
7
+ * down/up sequence, independent of whatever sticky modifiers happen to be held.
8
+ *
9
+ * No server changes needed — every button here is just the existing `{type:"key",code,down}`
10
+ * protocol `use-remote-input-capture.ts` already speaks; the server's own `heldKeyCodes`
11
+ * tracking (`remote-desktop-session.ts`) already releases anything sent down:true here too on
12
+ * disconnect/heartbeat-timeout, the same as a real held key.
13
+ */
14
+ import { useCallback, useEffect, useRef, useState } from "react";
15
+ import { cn } from "@/lib/utils";
16
+
17
+ export interface RemoteDesktopMobileKeyBarProps {
18
+ sendMessage: (msg: Record<string, unknown>) => void;
19
+ }
20
+
21
+ const MODIFIERS = [
22
+ { label: "Ctrl", code: "ControlLeft" },
23
+ { label: "Alt", code: "AltLeft" },
24
+ { label: "Shift", code: "ShiftLeft" },
25
+ { label: "Win", code: "MetaLeft" },
26
+ ] as const;
27
+
28
+ const FUNCTION_KEYS = [
29
+ { label: "Esc", code: "Escape" },
30
+ { label: "Tab", code: "Tab" },
31
+ { label: "←", code: "ArrowLeft" },
32
+ { label: "↑", code: "ArrowUp" },
33
+ { label: "↓", code: "ArrowDown" },
34
+ { label: "→", code: "ArrowRight" },
35
+ ] as const;
36
+
37
+ const COMBOS: readonly { label: string; codes: readonly string[] }[] = [
38
+ { label: "Ctrl+C", codes: ["ControlLeft", "KeyC"] },
39
+ { label: "Ctrl+V", codes: ["ControlLeft", "KeyV"] },
40
+ { label: "Ctrl+Z", codes: ["ControlLeft", "KeyZ"] },
41
+ { label: "Ctrl+A", codes: ["ControlLeft", "KeyA"] },
42
+ { label: "Alt+Tab", codes: ["AltLeft", "Tab"] },
43
+ // Windows blocks synthetic Ctrl+Alt+Del (the Secure Attention Sequence) from SendInput
44
+ // regardless of privilege level — this is an OS restriction, not a bug here. The button is
45
+ // best-effort: harmless to try, and there is no way to detect the block ahead of time.
46
+ { label: "Ctrl+Alt+Del", codes: ["ControlLeft", "AltLeft", "Delete"] },
47
+ ];
48
+
49
+ function KeyBarButton({
50
+ onClick,
51
+ active,
52
+ className,
53
+ children,
54
+ }: {
55
+ onClick: () => void;
56
+ active?: boolean;
57
+ className?: string;
58
+ children: React.ReactNode;
59
+ }) {
60
+ return (
61
+ <button
62
+ type="button"
63
+ onClick={onClick}
64
+ className={cn(
65
+ "min-h-11 min-w-11 shrink-0 whitespace-nowrap rounded-md px-2.5 text-xs font-medium text-white/80",
66
+ "active:bg-white/15 transition-colors",
67
+ active && "bg-primary text-primary-foreground",
68
+ className,
69
+ )}
70
+ >
71
+ {children}
72
+ </button>
73
+ );
74
+ }
75
+
76
+ export function RemoteDesktopMobileKeyBar({ sendMessage }: RemoteDesktopMobileKeyBarProps) {
77
+ const [held, setHeld] = useState<ReadonlySet<string>>(new Set());
78
+ const heldRef = useRef(held);
79
+ heldRef.current = held;
80
+
81
+ // Safety net: a modifier left toggled on must not stay logically held on the host once this
82
+ // bar goes away (keyboard dismissed, sheet closed) — same reasoning as `releaseAll` elsewhere.
83
+ useEffect(() => {
84
+ return () => {
85
+ heldRef.current.forEach((code) => sendMessage({ type: "key", code, down: false }));
86
+ };
87
+ }, [sendMessage]);
88
+
89
+ const toggleModifier = useCallback((code: string) => {
90
+ setHeld((prev) => {
91
+ const isHeld = prev.has(code);
92
+ sendMessage({ type: "key", code, down: !isHeld });
93
+ const next = new Set(prev);
94
+ if (isHeld) next.delete(code); else next.add(code);
95
+ return next;
96
+ });
97
+ }, [sendMessage]);
98
+
99
+ const tapKey = useCallback((code: string) => {
100
+ sendMessage({ type: "key", code, down: true });
101
+ sendMessage({ type: "key", code, down: false });
102
+ }, [sendMessage]);
103
+
104
+ const sendCombo = useCallback((codes: readonly string[]) => {
105
+ // Press down in order, release in reverse — mirrors physically pressing then releasing a
106
+ // chord (last key down first key up would read as a different shortcut on some apps).
107
+ codes.forEach((code) => sendMessage({ type: "key", code, down: true }));
108
+ [...codes].reverse().forEach((code) => sendMessage({ type: "key", code, down: false }));
109
+ }, [sendMessage]);
110
+
111
+ return (
112
+ <div
113
+ className="flex shrink-0 gap-1 overflow-x-auto border-t border-white/10 bg-black/90 px-2 py-1.5"
114
+ data-testid="remote-desktop-mobile-key-bar"
115
+ >
116
+ {MODIFIERS.map((m) => (
117
+ <KeyBarButton key={m.code} onClick={() => toggleModifier(m.code)} active={held.has(m.code)}>
118
+ {m.label}
119
+ </KeyBarButton>
120
+ ))}
121
+ <div className="mx-0.5 w-px shrink-0 bg-white/10" />
122
+ {FUNCTION_KEYS.map((k) => (
123
+ <KeyBarButton key={k.code} onClick={() => tapKey(k.code)}>{k.label}</KeyBarButton>
124
+ ))}
125
+ <div className="mx-0.5 w-px shrink-0 bg-white/10" />
126
+ {COMBOS.map((c) => (
127
+ <KeyBarButton key={c.label} onClick={() => sendCombo(c.codes)}>{c.label}</KeyBarButton>
128
+ ))}
129
+ </div>
130
+ );
131
+ }