@hienlh/ppm 0.18.13 → 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 (542) hide show
  1. package/.rdtest2-cf.out +0 -0
  2. package/CHANGELOG.md +23 -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 +18 -0
  6. package/assets/skills/ppm/references/http-api.md +8 -1
  7. package/dist/web/assets/{adaptive-context-menu-CbgPYQmj.js → adaptive-context-menu-ChBZWLLq.js} +1 -1
  8. package/dist/web/assets/{ai-resource-editor-unM2HNmg.js → ai-resource-editor-C8aAY8Po.js} +1 -1
  9. package/dist/web/assets/{ai-settings-section-CdktdrwF.js → ai-settings-section-ERu1WiZc.js} +1 -1
  10. package/dist/web/assets/{angular-html-DcpbdcNG.js → angular-html-CXgHIQ2U.js} +1 -1
  11. package/dist/web/assets/{angular-ts-Bv_WIk34.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-BdCtex9i.js → api-settings-R59c66DE.js} +1 -1
  15. package/dist/web/assets/{api-themes-pRRoLvgD.js → api-themes-DKxrwJda.js} +1 -1
  16. package/dist/web/assets/{apl-D5_lLszu.js → apl-DkGH8laI.js} +1 -1
  17. package/dist/web/assets/architecture-PBZL5I3N-CCalSKAt.js +1 -0
  18. package/dist/web/assets/{astro-CBHwddYL.js → astro-tHUd8Zlg.js} +1 -1
  19. package/dist/web/assets/{audio-preview-BD1iDovf.js → audio-preview-B_C6Dpvz.js} +1 -1
  20. package/dist/web/assets/{blade-DGe1d2Pq.js → blade-BbKpjIYK.js} +1 -1
  21. package/dist/web/assets/{bsl-2lyAzRJA.js → bsl-DA0UYto0.js} +1 -1
  22. package/dist/web/assets/{button-2WCAS8dI.js → button-DBSpDPS0.js} +1 -1
  23. package/dist/web/assets/{cairo-DjzZeJrq.js → cairo-79ayucbB.js} +1 -1
  24. package/dist/web/assets/chat-tab-BraiJsYG.js +15 -0
  25. package/dist/web/assets/{cobol-cC3XIuAJ.js → cobol-DSS2EOYo.js} +1 -1
  26. package/dist/web/assets/code-editor-lhmLNhXu.js +10 -0
  27. package/dist/web/assets/{coffee-4eGboVoU.js → coffee-DvSxq2iD.js} +1 -1
  28. package/dist/web/assets/{conflict-editor-DdJag4QK.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-DF9_G-OU.js → cpp-BhWZ-FSn.js} +1 -1
  31. package/dist/web/assets/{crystal-OavZlzcm.js → crystal-yP2OeF-f.js} +1 -1
  32. package/dist/web/assets/{csv-preview-DnXwouF3.js → csv-preview-D46W_icb.js} +1 -1
  33. package/dist/web/assets/{data-grid-overlay-editor-B9Gpjm3l.js → data-grid-overlay-editor-DeGkPYtA.js} +1 -1
  34. package/dist/web/assets/{database-viewer-Dk8oGyZx.js → database-viewer-DFI4YtrH.js} +1 -1
  35. package/dist/web/assets/{dialog-B-l2ZxVm.js → dialog-RIpeb87v.js} +1 -1
  36. package/dist/web/assets/diff-viewer-eaNOLcBL.js +4 -0
  37. package/dist/web/assets/{docx-preview-DAdHPlhZ.js → docx-preview-Cbj-pOK3.js} +1 -1
  38. package/dist/web/assets/{edge-TzYh8OlN.js → edge-D3K9cd-p.js} +1 -1
  39. package/dist/web/assets/{edit-diff-preview-DlwP3UIU.js → edit-diff-preview-DDqy3KLA.js} +1 -1
  40. package/dist/web/assets/{elixir-Cy3RhKKH.js → elixir-D4PBmsLp.js} +1 -1
  41. package/dist/web/assets/{elm-BTBZdweK.js → elm-DSpXjuaH.js} +1 -1
  42. package/dist/web/assets/{erb-dv1mI0y4.js → erb-CaYZMTJW.js} +1 -1
  43. package/dist/web/assets/{erlang-DjeiDrkl.js → erlang-DX5BFqJj.js} +1 -1
  44. package/dist/web/assets/{esm-FRwvcesu.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-W0qRfaAU.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-BCH9DAIJ.js → fortran-fixed-form-B0bAufUe.js} +1 -1
  51. package/dist/web/assets/{fsharp-C93NYKYD.js → fsharp-C2AL2b1M.js} +1 -1
  52. package/dist/web/assets/{gdresource-BcIEE2eX.js → gdresource-DRQsDMNI.js} +1 -1
  53. package/dist/web/assets/{git-commit-BVjlXEy8.js → git-commit-GtBdnrzk.js} +1 -1
  54. package/dist/web/assets/{git-log-panel-2_4HuP47.js → git-log-panel-hwV1flSO.js} +1 -1
  55. package/dist/web/assets/{git-rebase-Bj_21B1V.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-C20yj4Zv.js → glimmer-js-Bxwu93bS.js} +1 -1
  59. package/dist/web/assets/{glimmer-ts-ipbYC7kh.js → glimmer-ts-CIUYdR4E.js} +1 -1
  60. package/dist/web/assets/{glsl-Dovazu3t.js → glsl-CfxLI5jn.js} +1 -1
  61. package/dist/web/assets/{graphql-DpMlM7hC.js → graphql-CstqDPde.js} +1 -1
  62. package/dist/web/assets/group-chat-tab-B7mJO1gG.js +1 -0
  63. package/dist/web/assets/{hack-gc7LCBuV.js → hack-Bjqnssb5.js} +1 -1
  64. package/dist/web/assets/{haml-vtlFm9gm.js → haml-lVc-DfbM.js} +1 -1
  65. package/dist/web/assets/hand-x0ejq3fS.js +1 -0
  66. package/dist/web/assets/{handlebars-DGKhpCji.js → handlebars-Yy6fPQI_.js} +1 -1
  67. package/dist/web/assets/{html-derivative-C3oTSSCN.js → html-derivative-CchzCY1z.js} +1 -1
  68. package/dist/web/assets/{html-DvfhQbXb.js → html-lqtcy6-2.js} +1 -1
  69. package/dist/web/assets/{http-SG6zLTo7.js → http-ChclRrko.js} +1 -1
  70. package/dist/web/assets/{hurl-BWXsYlqW.js → hurl-CoOK7sXa.js} +1 -1
  71. package/dist/web/assets/{hxml-DUdMzaoh.js → hxml-fZSZ7Iyt.js} +1 -1
  72. package/dist/web/assets/{image-preview-DxgFOV1T.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-DuAF-Stj.js → input-DXPgUtxi.js} +1 -1
  77. package/dist/web/assets/{jinja-955w9RKM.js → jinja-Y0w_KBdE.js} +1 -1
  78. package/dist/web/assets/{jison-WWfqPo50.js → jison-BILj3F2e.js} +1 -1
  79. package/dist/web/assets/{julia-BFrfnaNl.js → julia-DRfOPDts.js} +1 -1
  80. package/dist/web/assets/{just-BrZdyR_I.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-CwtPPASD.js → latex-D6gztEOL.js} +1 -1
  85. package/dist/web/assets/{liquid-CYZuvDDd.js → liquid-DPqCEVb9.js} +1 -1
  86. package/dist/web/assets/{lua-BV095ZyY.js → lua-I40KPUm-.js} +1 -1
  87. package/dist/web/assets/markdown-renderer-BwOkiZC1.js +1 -0
  88. package/dist/web/assets/{markdown-renderer-CnRcKK3d.js → markdown-renderer-oJ9jPdQ2.js} +3 -3
  89. package/dist/web/assets/{marko-CNeljZ1C.js → marko-DMjsThSW.js} +1 -1
  90. package/dist/web/assets/{mdc-JfGapShy.js → mdc-CN2alsbo.js} +1 -1
  91. package/dist/web/assets/{media-url-Bm4PsduE.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-DD5C8nqX.js → nextflow-CGa4JeZ6.js} +1 -1
  95. package/dist/web/assets/{nginx-Ca7B__x_.js → nginx-CmqrIj84.js} +1 -1
  96. package/dist/web/assets/{nim-B8FogsDw.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-DG9xJwEG.js → number-overlay-editor-BEzJ1YeD.js} +1 -1
  99. package/dist/web/assets/{overview-panel-s53bnl0q.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-DYt3aBzM.js → pdf-preview-BMHM5cR2.js} +1 -1
  103. package/dist/web/assets/{perl-BR78hS8R.js → perl-BOwpb6Tb.js} +1 -1
  104. package/dist/web/assets/{php-DXBOMC9z.js → php-wT9fVxxu.js} +1 -1
  105. package/dist/web/assets/pie-UPGHQEXC-BXSJ2God.js +1 -0
  106. package/dist/web/assets/{postgres-viewer-BvoWSuoZ.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-CvTwVVKV.js → pug-DdlNnj-p.js} +1 -1
  110. package/dist/web/assets/{qml-CPSoZ1-B.js → qml-C5zBdydf.js} +1 -1
  111. package/dist/web/assets/radar-KQ55EAFF-D6XrM7HH.js +1 -0
  112. package/dist/web/assets/{razor-BUA1YKtF.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-BJsq7ISz.js → rst-DiMiMYUO.js} +1 -1
  118. package/dist/web/assets/{ruby-DSDMeYf5.js → ruby-DK62iflA.js} +1 -1
  119. package/dist/web/assets/{run-in-terminal-C9xpskFn.js → run-in-terminal-DJSL6nwZ.js} +1 -1
  120. package/dist/web/assets/{sas-B1nJOjbN.js → sas-6QPGJRa5.js} +1 -1
  121. package/dist/web/assets/{scroll-area-DBcG1fQo.js → scroll-area-DXGXeiRz.js} +1 -1
  122. package/dist/web/assets/{scss-BZFyRiQn.js → scss-DmUqh48_.js} +1 -1
  123. package/dist/web/assets/{send-to-chat-BIg70MUZ.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-laINRXBo.js → shaderlab-DopFR1mi.js} +1 -1
  128. package/dist/web/assets/{shellsession-BpOOvWVR.js → shellsession-Bp_TGuXb.js} +1 -1
  129. package/dist/web/assets/{soy-BW4d91Sa.js → soy-DEB9KEFb.js} +1 -1
  130. package/dist/web/assets/{sparql-BsvvyoHn.js → sparql-C8lEwO1p.js} +1 -1
  131. package/dist/web/assets/{sql-query-editor-qQce5QJ6.js → sql-query-editor-MMfk0Kbz.js} +1 -1
  132. package/dist/web/assets/{sqlite-viewer-yhc0YNvu.js → sqlite-viewer-vglzrgnZ.js} +1 -1
  133. package/dist/web/assets/{stata-K-dZInUt.js → stata-DVep4apN.js} +1 -1
  134. package/dist/web/assets/{surrealql-CpNp2sgf.js → surrealql-fSlJApOA.js} +1 -1
  135. package/dist/web/assets/{svelte-BG6iBA8R.js → svelte-DezWBEDi.js} +1 -1
  136. package/dist/web/assets/{switch-_NcrktLe.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-CaOhD0AU.js → system-monitor-window-content-CiJ2yIsr.js} +1 -1
  140. package/dist/web/assets/tab-host-window-content-DYlXR0cg.js +1 -0
  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-B1jkKkDi.js → team-member-window-content-qrgFTC96.js} +1 -1
  144. package/dist/web/assets/{templ-gxNKXmt3.js → templ-D0zB42a9.js} +1 -1
  145. package/dist/web/assets/{terminal-tab-UvwM0sty.js → terminal-tab-D_-R5rGm.js} +3 -3
  146. package/dist/web/assets/{tex-C0ON27g5.js → tex-BJiPijcH.js} +1 -1
  147. package/dist/web/assets/{tool-cards-jB7GT-wj.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-BX_CTVgQ.js → ts-tags-Dcr4u6y5.js} +1 -1
  150. package/dist/web/assets/{twig-CwL7XnU0.js → twig-B-SQU0bg.js} +1 -1
  151. package/dist/web/assets/{use-blob-url-DcRcjNTE.js → use-blob-url-BtPlqwLr.js} +1 -1
  152. package/dist/web/assets/use-monaco-theme-DQY1ZSaf.js +1 -0
  153. package/dist/web/assets/{use-open-team-member-1hRsHLKg.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-rxJLbFqy.js → use-websocket-l39Odhlp.js} +1 -1
  156. package/dist/web/assets/{vendor-mermaid-CHTa6D69.js → vendor-mermaid-CtDsSxbM.js} +1 -1
  157. package/dist/web/assets/{video-preview-DMhnf2Gi.js → video-preview-C5AhkIDl.js} +1 -1
  158. package/dist/web/assets/{vue-Bykm6wSS.js → vue-Cw10Sfbh.js} +1 -1
  159. package/dist/web/assets/{vue-html-CETdK_iV.js → vue-html-Cc5C_Z_w.js} +1 -1
  160. package/dist/web/assets/{vue-vine-DiJb6fO5.js → vue-vine-BuNMlnvu.js} +1 -1
  161. package/dist/web/assets/window-pip-registry-C7oz3Dfy.js +1 -0
  162. package/dist/web/assets/window-store-B5ZswdYg.js +1 -0
  163. package/dist/web/assets/{xml-BNw8J5YW.js → xml-DqT1wkky.js} +1 -1
  164. package/dist/web/assets/{xsl-CsG9L31b.js → xsl-DLkge3A2.js} +1 -1
  165. package/dist/web/index.html +45 -42
  166. package/dist/web/sw.js +1 -1
  167. package/docs/architecture/ai-chat-and-providers.md +3 -0
  168. package/docs/architecture/workspace-and-ui.md +8 -0
  169. package/docs/design-guidelines.md +3 -2
  170. package/package.json +1 -1
  171. package/src/cli/commands/backup-cmd.ts +119 -0
  172. package/src/index.ts +3 -0
  173. package/src/providers/claude-agent-sdk.ts +29 -1
  174. package/src/server/index.ts +42 -15
  175. package/src/server/routes/accounts.ts +30 -2
  176. package/src/server/routes/chat.ts +8 -1
  177. package/src/server/routes/named-tunnel.ts +5 -2
  178. package/src/server/routes/remote-desktop.ts +64 -0
  179. package/src/server/routes/settings.ts +1 -0
  180. package/src/server/ws/chat.ts +126 -7
  181. package/src/server/ws/remote-desktop.ts +86 -0
  182. package/src/services/account.service.ts +106 -23
  183. package/src/services/db-backup/db-backup-paths.ts +66 -0
  184. package/src/services/db-backup/db-backup-restore.ts +105 -0
  185. package/src/services/db-backup/db-backup-retention.ts +96 -0
  186. package/src/services/db-backup/db-backup-sync.ts +113 -0
  187. package/src/services/db-backup/db-backup.service.ts +61 -0
  188. package/src/services/db.service.ts +29 -1
  189. package/src/services/fs-credential-path-guard.ts +19 -1
  190. package/src/services/named-tunnel/cloudflared-login.service.ts +2 -2
  191. package/src/services/nested-subagent-spy.ts +206 -0
  192. package/src/services/remote-desktop/access-unit-assembler.ts +90 -0
  193. package/src/services/remote-desktop/avc1-codec-string.ts +18 -0
  194. package/src/services/remote-desktop/nal-splitter.ts +100 -0
  195. package/src/services/remote-desktop/remote-desktop-capture.ts +120 -0
  196. package/src/services/remote-desktop/remote-desktop-encoder-args.ts +42 -0
  197. package/src/services/remote-desktop/remote-desktop-flag.ts +10 -0
  198. package/src/services/remote-desktop/remote-desktop-input.ts +217 -0
  199. package/src/services/remote-desktop/remote-desktop-nonce.ts +38 -0
  200. package/src/services/remote-desktop/remote-desktop-session.ts +189 -0
  201. package/src/services/remote-desktop/remote-desktop-vk-map.ts +41 -0
  202. package/src/services/subagent-transcript-merger.ts +92 -75
  203. package/src/services/subprocess-retention.ts +29 -6
  204. package/src/services/supervisor.ts +58 -0
  205. package/src/services/team-member-activity/subagent-transcript-index.ts +56 -0
  206. package/src/types/chat.ts +2 -0
  207. package/src/web/app.tsx +8 -0
  208. package/src/web/components/chat/account-dialogs.tsx +11 -4
  209. package/src/web/components/chat/question-card.tsx +9 -2
  210. package/src/web/components/chat/usage-badge.tsx +17 -3
  211. package/src/web/components/floating-window/floating-window.tsx +8 -1
  212. package/src/web/components/floating-window/open-tab-in-pip.ts +78 -0
  213. package/src/web/components/floating-window/window-content-registry.ts +2 -0
  214. package/src/web/components/floating-window/window-pip-registry.ts +46 -0
  215. package/src/web/components/floating-window/window-store-types.ts +1 -1
  216. package/src/web/components/layout/mobile-drawer.tsx +38 -27
  217. package/src/web/components/layout/nav-section-rail.tsx +10 -2
  218. package/src/web/components/layout/tab-pop-out-menu-item.tsx +25 -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-DfXViPKz.js +0 -1
  247. package/dist/web/assets/architecture-PBZL5I3N-BzFm9ELu.js +0 -1
  248. package/dist/web/assets/chat-tab-DzK8LGti.js +0 -15
  249. package/dist/web/assets/code-editor-CuMfPN9X.js +0 -10
  250. package/dist/web/assets/diff-viewer-eaUBjLCH.js +0 -4
  251. package/dist/web/assets/explorer-body-DkCkSVrN.js +0 -2
  252. package/dist/web/assets/explorer-window-content-MsnTVngu.js +0 -1
  253. package/dist/web/assets/gitGraph-HDMCJU4V-B72vcb2r.js +0 -1
  254. package/dist/web/assets/glide-data-grid-B0IY6n_Q.js +0 -138
  255. package/dist/web/assets/group-chat-tab-DRLDHOp6.js +0 -1
  256. package/dist/web/assets/index-CBn4SOoT.js +0 -52
  257. package/dist/web/assets/index-DWpNR3nB.css +0 -2
  258. package/dist/web/assets/info-3K5VOQVL-3irPxRK_.js +0 -1
  259. package/dist/web/assets/keybindings-store-d54pv2NK.js +0 -1
  260. package/dist/web/assets/markdown-renderer-BTS8ifdu.js +0 -1
  261. package/dist/web/assets/mobile-bottom-sheet-M5iZDX7y.js +0 -1
  262. package/dist/web/assets/mobile-explorer-sheet-BqK2-WuN.js +0 -1
  263. package/dist/web/assets/notification-store-Yvy1I8gd.js +0 -1
  264. package/dist/web/assets/packet-RMMSAZCW-Z5jWjFCV.js +0 -1
  265. package/dist/web/assets/pie-UPGHQEXC-Bq7UMi8P.js +0 -1
  266. package/dist/web/assets/process-table-BYwKigiD.js +0 -1
  267. package/dist/web/assets/radar-KQ55EAFF-BLo4BOTD.js +0 -1
  268. package/dist/web/assets/settings-store-GV5pkkke.js +0 -2
  269. package/dist/web/assets/settings-tab-BePvIcjd.js +0 -3
  270. package/dist/web/assets/settings-tab-Br-lJSPa.js +0 -1
  271. package/dist/web/assets/system-monitor-body-D8Z5QXBB.js +0 -2
  272. package/dist/web/assets/system-monitor-tab-BclYXFTe.js +0 -1
  273. package/dist/web/assets/tab-host-window-content-f_Rc5rJK.js +0 -1
  274. package/dist/web/assets/team-member-sheet-QDHoYCs0.js +0 -2
  275. package/dist/web/assets/treemap-KZPCXAKY-dyrE6SW2.js +0 -1
  276. package/dist/web/assets/use-monaco-theme--9tOOkDA.js +0 -1
  277. package/dist/web/assets/window-store-CQ1lghvv.js +0 -1
  278. /package/dist/web/assets/{abap-DGlrX_8G.js → abap-CKgBNk5K.js} +0 -0
  279. /package/dist/web/assets/{actionscript-3-CUqRtUJE.js → actionscript-3-oARhAY6c.js} +0 -0
  280. /package/dist/web/assets/{ada-CY3Spa33.js → ada-2vi8ASy3.js} +0 -0
  281. /package/dist/web/assets/{andromeeda-Lxs2TJn7.js → andromeeda-B3vMjPKd.js} +0 -0
  282. /package/dist/web/assets/{apache-DfxgGdgM.js → apache-DnIEr338.js} +0 -0
  283. /package/dist/web/assets/{apex-QVDF1dnI.js → apex-B7Qs5AY5.js} +0 -0
  284. /package/dist/web/assets/{api-client-oY1tC3tM.js → api-client-BkGKTjsq.js} +0 -0
  285. /package/dist/web/assets/{applescript-pwJ8QXh-.js → applescript-EbQNUCWb.js} +0 -0
  286. /package/dist/web/assets/{apply-theme-B_bMVL6a.js → apply-theme-Dd_cqGa-.js} +0 -0
  287. /package/dist/web/assets/{ara-mPjeX5YX.js → ara-CtHdhFRw.js} +0 -0
  288. /package/dist/web/assets/{asciidoc-Bf7LsDkI.js → asciidoc-BH6qin_O.js} +0 -0
  289. /package/dist/web/assets/{asm-rRKC1OUx.js → asm-D1XU1ACk.js} +0 -0
  290. /package/dist/web/assets/{aurora-x-DwQr0DkG.js → aurora-x-BkhcxkuS.js} +0 -0
  291. /package/dist/web/assets/{awk-Bpt768fN.js → awk-DijzB6o7.js} +0 -0
  292. /package/dist/web/assets/{ayu-dark-D9lwQHMs.js → ayu-dark-tysG77WC.js} +0 -0
  293. /package/dist/web/assets/{ayu-light-B4-ptrU5.js → ayu-light-Dg0WMETC.js} +0 -0
  294. /package/dist/web/assets/{ayu-mirage-DAyRzH5Q.js → ayu-mirage--RsJCjC4.js} +0 -0
  295. /package/dist/web/assets/{ballerina-Cgdpj1HC.js → ballerina-DT_5-l82.js} +0 -0
  296. /package/dist/web/assets/{bat-4FrN36S2.js → bat-BRpDlkv0.js} +0 -0
  297. /package/dist/web/assets/{beancount-DkcxQER3.js → beancount--8Df-WGG.js} +0 -0
  298. /package/dist/web/assets/{berry-Dnuut-Ml.js → berry-Bc9fo35J.js} +0 -0
  299. /package/dist/web/assets/{bibtex-CCDwmbuA.js → bibtex-CwyBpp9X.js} +0 -0
  300. /package/dist/web/assets/{bicep-BC0ryoGM.js → bicep-DiyH2Bs7.js} +0 -0
  301. /package/dist/web/assets/{bird2-C50V_A-0.js → bird2-CHgK4a2A.js} +0 -0
  302. /package/dist/web/assets/{c-Ddorhq0U.js → c-BM1qL6-f.js} +0 -0
  303. /package/dist/web/assets/{c3-DdIf5r9E.js → c3-D2oOjrP7.js} +0 -0
  304. /package/dist/web/assets/{cadence-s9pd1Wng.js → cadence-nS45wqF6.js} +0 -0
  305. /package/dist/web/assets/{can-open-in-ppm-C6hZV1OP.js → can-open-in-ppm-DwP9KU8o.js} +0 -0
  306. /package/dist/web/assets/{catppuccin-frappe-CzJNTyu-.js → catppuccin-frappe-BpnJHiCj.js} +0 -0
  307. /package/dist/web/assets/{catppuccin-latte-BNpt-m7s.js → catppuccin-latte-Bb95Wj3h.js} +0 -0
  308. /package/dist/web/assets/{catppuccin-macchiato-FYGkfsJ5.js → catppuccin-macchiato-Btx8c17W.js} +0 -0
  309. /package/dist/web/assets/{catppuccin-mocha-it2g5D1q.js → catppuccin-mocha-B4DZOUGI.js} +0 -0
  310. /package/dist/web/assets/{clarity-BVFBTjMe.js → clarity-1ps69jU6.js} +0 -0
  311. /package/dist/web/assets/{clipboard-BWznymz7.js → clipboard-CrHX8AYw.js} +0 -0
  312. /package/dist/web/assets/{clojure-BSYvXMI8.js → clojure-BklC0hKV.js} +0 -0
  313. /package/dist/web/assets/{cmake-DGEqH-Np.js → cmake-B573LYAQ.js} +0 -0
  314. /package/dist/web/assets/{codeowners-D8FCOxWg.js → codeowners-BoYeX89M.js} +0 -0
  315. /package/dist/web/assets/{codeql-COV0eluG.js → codeql-7Dhf5Udk.js} +0 -0
  316. /package/dist/web/assets/{common-lisp-xHnZUlgt.js → common-lisp-m1nxlthv.js} +0 -0
  317. /package/dist/web/assets/{coq-Cs_2i3Xg.js → coq-DNhGmd4a.js} +0 -0
  318. /package/dist/web/assets/{csharp-B1ONB4PT.js → csharp-P-nAurtS.js} +0 -0
  319. /package/dist/web/assets/{css-BgGQbvrZ.js → css-LYbSeh3-.js} +0 -0
  320. /package/dist/web/assets/{csv-CNHmQBzs.js → csv-DHZZZm7X.js} +0 -0
  321. /package/dist/web/assets/{csv-parser-DWR24J17.js → csv-parser-CY9Ggtg5.js} +0 -0
  322. /package/dist/web/assets/{cue-ZXAKzwIy.js → cue-D8yLUOle.js} +0 -0
  323. /package/dist/web/assets/{cypher-4PL5bmT5.js → cypher-9cbW8RYA.js} +0 -0
  324. /package/dist/web/assets/{d-CEsV3RT2.js → d-ZihzhxqI.js} +0 -0
  325. /package/dist/web/assets/{dark-plus-NtFnYSDz.js → dark-plus-C8xIA1u3.js} +0 -0
  326. /package/dist/web/assets/{dart-CKHn4mDf.js → dart-CBm6jKc7.js} +0 -0
  327. /package/dist/web/assets/{data-grid-types-CxU8oo-j.js → data-grid-types-DkEQ-Na9.js} +0 -0
  328. /package/dist/web/assets/{dax-Cmkj1I_M.js → dax-B3cx6YeV.js} +0 -0
  329. /package/dist/web/assets/{desktop-DJLalvPQ.js → desktop-C03SpXth.js} +0 -0
  330. /package/dist/web/assets/{diff-BnS2wpj6.js → diff-D8LnnvFY.js} +0 -0
  331. /package/dist/web/assets/{dist-uYjs3Wv6.js → dist-C4XoS_zo.js} +0 -0
  332. /package/dist/web/assets/{dist-CuIdRyUB.js → dist-Do3fljss.js} +0 -0
  333. /package/dist/web/assets/{docker-CovNy7ml.js → docker-DwFiOQWW.js} +0 -0
  334. /package/dist/web/assets/{dotenv-Gh4w3I6B.js → dotenv-CVJ_vFSQ.js} +0 -0
  335. /package/dist/web/assets/{dracula-NF0tAHXl.js → dracula-DMw8UACY.js} +0 -0
  336. /package/dist/web/assets/{dracula-soft-Cj_qBurK.js → dracula-soft-BnB3nDrA.js} +0 -0
  337. /package/dist/web/assets/{dream-maker-CRuZPcl7.js → dream-maker-2riXUQXo.js} +0 -0
  338. /package/dist/web/assets/{emacs-lisp-BACmC9qj.js → emacs-lisp-BQdYaiB5.js} +0 -0
  339. /package/dist/web/assets/{everforest-dark-Dd9av4QG.js → everforest-dark-C2FPIWdj.js} +0 -0
  340. /package/dist/web/assets/{everforest-light-C-jsss-B.js → everforest-light-DMOWf9sD.js} +0 -0
  341. /package/dist/web/assets/{fennel-BJA9XnL8.js → fennel-JtSHp0vW.js} +0 -0
  342. /package/dist/web/assets/{fish-CKlhNSc2.js → fish-CflDcRfe.js} +0 -0
  343. /package/dist/web/assets/{fluent-wfglRk5D.js → fluent-D4eu6Vac.js} +0 -0
  344. /package/dist/web/assets/{format-bytes-utwHMTry.js → format-bytes-DR3228bu.js} +0 -0
  345. /package/dist/web/assets/{fortran-free-form-BlXCdid3.js → fortran-free-form-hO4RuC9Z.js} +0 -0
  346. /package/dist/web/assets/{gdscript-BwAVG6Et.js → gdscript-D7n3a8VM.js} +0 -0
  347. /package/dist/web/assets/{gdshader-DPc_T7ib.js → gdshader-BhUmrlzG.js} +0 -0
  348. /package/dist/web/assets/{genie-B-zZSLCX.js → genie-VbyftqBp.js} +0 -0
  349. /package/dist/web/assets/{gherkin-CJ0ajpGs.js → gherkin-BZzhvUUb.js} +0 -0
  350. /package/dist/web/assets/{github-dark-Dz8ZkzSP.js → github-dark-C4q41woW.js} +0 -0
  351. /package/dist/web/assets/{github-dark-default-CNUYo_kp.js → github-dark-default-CMNi7dQX.js} +0 -0
  352. /package/dist/web/assets/{github-dark-dimmed-241z_Ycx.js → github-dark-dimmed-BRf_-LlY.js} +0 -0
  353. /package/dist/web/assets/{github-dark-high-contrast-7r6yZZYH.js → github-dark-high-contrast-xanKBsRI.js} +0 -0
  354. /package/dist/web/assets/{github-light-C8gmZn-D.js → github-light-BEQeNQCs.js} +0 -0
  355. /package/dist/web/assets/{github-light-default-GAEgq1eA.js → github-light-default-ceQrtdXg.js} +0 -0
  356. /package/dist/web/assets/{github-light-high-contrast-3z91352E.js → github-light-high-contrast-doKbXzgW.js} +0 -0
  357. /package/dist/web/assets/{gleam-C7vxW5zd.js → gleam-Chzqf4fT.js} +0 -0
  358. /package/dist/web/assets/{gn-CyJIXwHP.js → gn-CCN_-6fp.js} +0 -0
  359. /package/dist/web/assets/{gnuplot-CqLbGgPd.js → gnuplot-CIplgwwP.js} +0 -0
  360. /package/dist/web/assets/{go-DdDcK9aX.js → go-Cjj-9dRs.js} +0 -0
  361. /package/dist/web/assets/{groovy-UYHggEqH.js → groovy-PqD4K7X1.js} +0 -0
  362. /package/dist/web/assets/{gruvbox-dark-hard-B4ierkJn.js → gruvbox-dark-hard-BAbpUSF-.js} +0 -0
  363. /package/dist/web/assets/{gruvbox-dark-medium-DYwkDSZ6.js → gruvbox-dark-medium-BWcWXDBB.js} +0 -0
  364. /package/dist/web/assets/{gruvbox-dark-soft-CVL4P-Lz.js → gruvbox-dark-soft-Dkx1okkk.js} +0 -0
  365. /package/dist/web/assets/{gruvbox-light-hard-BJ0LewUZ.js → gruvbox-light-hard-WVsDK0MM.js} +0 -0
  366. /package/dist/web/assets/{gruvbox-light-medium-CaqqjeGJ.js → gruvbox-light-medium-tiR9t7gr.js} +0 -0
  367. /package/dist/web/assets/{gruvbox-light-soft-ByKMoVNr.js → gruvbox-light-soft-HzHohaX6.js} +0 -0
  368. /package/dist/web/assets/{haskell-BilYRVPp.js → haskell-B1eIZfr6.js} +0 -0
  369. /package/dist/web/assets/{haxe-Brs9Bj1s.js → haxe-BcDdi5xr.js} +0 -0
  370. /package/dist/web/assets/{hcl-ClrZpol-.js → hcl-Dq-HKVni.js} +0 -0
  371. /package/dist/web/assets/{hjson-9PvHnYMX.js → hjson-Dr9D9WVU.js} +0 -0
  372. /package/dist/web/assets/{hlsl-BbBk4vqV.js → hlsl-Bo8t2FuZ.js} +0 -0
  373. /package/dist/web/assets/{horizon-E5Py0cny.js → horizon-DWG5IXc-.js} +0 -0
  374. /package/dist/web/assets/{horizon-bright-DVCsNGEk.js → horizon-bright-DFr9wTTx.js} +0 -0
  375. /package/dist/web/assets/{houston-JdFEax9Y.js → houston-NAVNhkup.js} +0 -0
  376. /package/dist/web/assets/{hy-W_W3tLKT.js → hy-DnyvUk3B.js} +0 -0
  377. /package/dist/web/assets/{image-gallery-5IURd6db.js → image-gallery-DoFdNF9i.js} +0 -0
  378. /package/dist/web/assets/{imba-CfAqJz7T.js → imba-CPbR5W_A.js} +0 -0
  379. /package/dist/web/assets/{ini-C_3gvKWs.js → ini-lc8_Lt0y.js} +0 -0
  380. /package/dist/web/assets/{java-CKqtXZ-j.js → java-B0KCo2vK.js} +0 -0
  381. /package/dist/web/assets/{javascript-C7gSEfae.js → javascript-YqEbNRpd.js} +0 -0
  382. /package/dist/web/assets/{json-BoN6e11T.js → json-Co0WtOlh.js} +0 -0
  383. /package/dist/web/assets/{json5-CgE7C8HW.js → json5-2Jhb0QUN.js} +0 -0
  384. /package/dist/web/assets/{jsonc-DjyoattY.js → jsonc-e72F11nU.js} +0 -0
  385. /package/dist/web/assets/{jsonl-lI82wGSw.js → jsonl-Bff8IPye.js} +0 -0
  386. /package/dist/web/assets/{jsonnet-CszEI8va.js → jsonnet-DRCkb-_H.js} +0 -0
  387. /package/dist/web/assets/{jssm-CZXDlszb.js → jssm-C6UiwVEm.js} +0 -0
  388. /package/dist/web/assets/{jsx-BeRz0eEz.js → jsx-u1aZlnfA.js} +0 -0
  389. /package/dist/web/assets/{kanagawa-dragon-BOmnUiyd.js → kanagawa-dragon-De-RQFV3.js} +0 -0
  390. /package/dist/web/assets/{kanagawa-lotus-ffU_P5w-.js → kanagawa-lotus-K8elE4QU.js} +0 -0
  391. /package/dist/web/assets/{kanagawa-wave-DKTJZrMu.js → kanagawa-wave-BrStl_zx.js} +0 -0
  392. /package/dist/web/assets/{kdl-BWECJveH.js → kdl-CLCfMeZy.js} +0 -0
  393. /package/dist/web/assets/{kotlin-DpVwBCSt.js → kotlin-DEEC3pNh.js} +0 -0
  394. /package/dist/web/assets/{kusto-BgVqjUZA.js → kusto-DTqRhCup.js} +0 -0
  395. /package/dist/web/assets/{laserwave-CEVCi8em.js → laserwave-DsgpUSbA.js} +0 -0
  396. /package/dist/web/assets/{lean-CGyfRhLJ.js → lean-Dl9MI0Y3.js} +0 -0
  397. /package/dist/web/assets/{less-B0_H10Ku.js → less-Bg21rao6.js} +0 -0
  398. /package/dist/web/assets/{lib-hrC3bruJ.js → lib-CXUwxw1M.js} +0 -0
  399. /package/dist/web/assets/{libesm-DVpFmwJu.js → libesm-CVkncAQz.js} +0 -0
  400. /package/dist/web/assets/{light-plus-DqzMqPID.js → light-plus-DGNbbK1V.js} +0 -0
  401. /package/dist/web/assets/{llvm-9ucnfNiF.js → llvm-91_ZX3p-.js} +0 -0
  402. /package/dist/web/assets/{loader-circle-Dpqcv0eS.js → loader-circle-DouKgIXy.js} +0 -0
  403. /package/dist/web/assets/{lock-DldbT31x.js → lock-CNxUGpTk.js} +0 -0
  404. /package/dist/web/assets/{log-m6A2dOSj.js → log-DSXzbq8u.js} +0 -0
  405. /package/dist/web/assets/{logo-ByPcsC9P.js → logo-BQRNd4tc.js} +0 -0
  406. /package/dist/web/assets/{luau-DFCo8nBn.js → luau-BE-TMpiZ.js} +0 -0
  407. /package/dist/web/assets/{make-C90w3MeA.js → make-ePYGKTpc.js} +0 -0
  408. /package/dist/web/assets/{markdown-DT0uywZG.js → markdown-Cst3wgMW.js} +0 -0
  409. /package/dist/web/assets/{material-theme-BVUO93tJ.js → material-theme-9XyvnNLb.js} +0 -0
  410. /package/dist/web/assets/{material-theme-darker-xXgGDwnF.js → material-theme-darker-H1VN4MTS.js} +0 -0
  411. /package/dist/web/assets/{material-theme-lighter-BjStPzWB.js → material-theme-lighter-DAreBbuF.js} +0 -0
  412. /package/dist/web/assets/{material-theme-ocean-wFmSiKjy.js → material-theme-ocean-BpTR-3II.js} +0 -0
  413. /package/dist/web/assets/{material-theme-palenight-e-0CtRoe.js → material-theme-palenight-DUNzX711.js} +0 -0
  414. /package/dist/web/assets/{matlab-BNaNQAUH.js → matlab-BpYNufBU.js} +0 -0
  415. /package/dist/web/assets/{mdx-DfpVWAqO.js → mdx-BYjfe1DK.js} +0 -0
  416. /package/dist/web/assets/{mermaid-Bpq7wFCP.js → mermaid-DPCcIW9B.js} +0 -0
  417. /package/dist/web/assets/{min-dark-CL2Y0WH6.js → min-dark-DnGOAGCo.js} +0 -0
  418. /package/dist/web/assets/{min-light-DOgr_IdP.js → min-light-CwV-o31y.js} +0 -0
  419. /package/dist/web/assets/{mipsasm-C9ie2zlB.js → mipsasm-DdeCPC97.js} +0 -0
  420. /package/dist/web/assets/{mojo-Md2A2sJy.js → mojo-BmxxZ3iM.js} +0 -0
  421. /package/dist/web/assets/{monitor-4vWfqzuN.js → monitor-C835ucWH.js} +0 -0
  422. /package/dist/web/assets/{monokai-nbXIEySB.js → monokai-Cx6cQ-MA.js} +0 -0
  423. /package/dist/web/assets/{moonbit-CJ-T-s4A.js → moonbit-D2-Gj5vj.js} +0 -0
  424. /package/dist/web/assets/{move-DwEehLIK.js → move-DSWGoQa8.js} +0 -0
  425. /package/dist/web/assets/{narrat-CuF4sIKr.js → narrat-D9TnZSSu.js} +0 -0
  426. /package/dist/web/assets/{nextflow-groovy-1dYhfPfq.js → nextflow-groovy-BbL-8PwD.js} +0 -0
  427. /package/dist/web/assets/{night-owl-gZS5mfzY.js → night-owl-CZitKm5d.js} +0 -0
  428. /package/dist/web/assets/{night-owl-light-DDAster5.js → night-owl-light-DJVX1Pno.js} +0 -0
  429. /package/dist/web/assets/{nix-BlbEYs2c.js → nix-R6hr3oln.js} +0 -0
  430. /package/dist/web/assets/{nord-BRiPb8Vy.js → nord-ChGHya3l.js} +0 -0
  431. /package/dist/web/assets/{nushell-DgWicB1i.js → nushell-QQWB-GWx.js} +0 -0
  432. /package/dist/web/assets/{objective-c-D8mPZ42Y.js → objective-c-C0pZ6y73.js} +0 -0
  433. /package/dist/web/assets/{objective-cpp-B0pNXzuj.js → objective-cpp-BdzVM31a.js} +0 -0
  434. /package/dist/web/assets/{ocaml-lQd7aK5d.js → ocaml-D4KktNlS.js} +0 -0
  435. /package/dist/web/assets/{odin-CxtIOBjm.js → odin-Y-fypmGT.js} +0 -0
  436. /package/dist/web/assets/{one-dark-pro-T_UFnWfe.js → one-dark-pro-BwecIxQL.js} +0 -0
  437. /package/dist/web/assets/{one-light-LBVqwyV-.js → one-light-Cz7UVzLU.js} +0 -0
  438. /package/dist/web/assets/{openscad-C5dnS6J7.js → openscad-DtRe3qYD.js} +0 -0
  439. /package/dist/web/assets/{pascal-CmWCWQML.js → pascal-Ck6I1fGG.js} +0 -0
  440. /package/dist/web/assets/{pkl-BWWOtDQk.js → pkl-DdFK94VW.js} +0 -0
  441. /package/dist/web/assets/{plastic-ekhupckF.js → plastic-BQSAzZwR.js} +0 -0
  442. /package/dist/web/assets/{play-CpCAoQRZ.js → play-CJlWpVMw.js} +0 -0
  443. /package/dist/web/assets/{plsql-BPsDHXx4.js → plsql-DjXg_CP0.js} +0 -0
  444. /package/dist/web/assets/{po-BX2FiuW2.js → po-Bw2SuM4r.js} +0 -0
  445. /package/dist/web/assets/{poimandres-DzXOdZcN.js → poimandres-YMK6c5VN.js} +0 -0
  446. /package/dist/web/assets/{polar-DDRXgJzG.js → polar-BMu17-O0.js} +0 -0
  447. /package/dist/web/assets/{portal-container-context-a399Hd9F.js → portal-container-context-DSGdlXrJ.js} +0 -0
  448. /package/dist/web/assets/{postcss-BHW6XBhx.js → postcss-DWib10RZ.js} +0 -0
  449. /package/dist/web/assets/{powerquery-B89c2NBb.js → powerquery-Dht4CK0b.js} +0 -0
  450. /package/dist/web/assets/{powershell-Dvxrd3Tu.js → powershell-DvrCg8ji.js} +0 -0
  451. /package/dist/web/assets/{prisma-lMK8EFLU.js → prisma-DbodYvCM.js} +0 -0
  452. /package/dist/web/assets/{prolog-D0YrtMG4.js → prolog-DZ-m6ysB.js} +0 -0
  453. /package/dist/web/assets/{proto-J0mM25aw.js → proto-HHpG3tGO.js} +0 -0
  454. /package/dist/web/assets/{puppet-CUT0xkhh.js → puppet-BljHe_75.js} +0 -0
  455. /package/dist/web/assets/{purescript-CoJWSGRp.js → purescript-DzCiFR0B.js} +0 -0
  456. /package/dist/web/assets/{python-BBrmuz6P.js → python-B5BQ27cC.js} +0 -0
  457. /package/dist/web/assets/{qmldir-CUtzs4Q7.js → qmldir-CYlcXrnn.js} +0 -0
  458. /package/dist/web/assets/{qss-Dm1RZ8aL.js → qss-Ce7T_TEg.js} +0 -0
  459. /package/dist/web/assets/{r-BGP7qZTe.js → r-Bku-2Jp_.js} +0 -0
  460. /package/dist/web/assets/{racket-K_6U1lqJ.js → racket-DtfSL3WR.js} +0 -0
  461. /package/dist/web/assets/{raku-bNSiZeW_.js → raku-CnDLsBJr.js} +0 -0
  462. /package/dist/web/assets/{react-X4jUntEf.js → react-Dfio3PfK.js} +0 -0
  463. /package/dist/web/assets/{red-uVeIEF0f.js → red-vbqcE9b-.js} +0 -0
  464. /package/dist/web/assets/{refresh-cw-B9iv7csb.js → refresh-cw-84mz8aOE.js} +0 -0
  465. /package/dist/web/assets/{reg-DSm9GWjh.js → reg-DIDLT8-A.js} +0 -0
  466. /package/dist/web/assets/{regexp-CljRm_j_.js → regexp-CB4h-VPu.js} +0 -0
  467. /package/dist/web/assets/{rel-d3JcopZg.js → rel-CiQEnWXa.js} +0 -0
  468. /package/dist/web/assets/{resolve-theme-DLKmm-lE.js → resolve-theme-DPTQHAy1.js} +0 -0
  469. /package/dist/web/assets/{riscv-CJmaELjT.js → riscv-R78iq5Rf.js} +0 -0
  470. /package/dist/web/assets/{ron-D0g9B1rW.js → ron-DhYENxc4.js} +0 -0
  471. /package/dist/web/assets/{rose-pine-DOrD2BSH.js → rose-pine-5wBeyjfJ.js} +0 -0
  472. /package/dist/web/assets/{rose-pine-dawn-CzZQ-LCF.js → rose-pine-dawn-FpKtPCuH.js} +0 -0
  473. /package/dist/web/assets/{rose-pine-moon-CdmsYp7C.js → rose-pine-moon-BwcIYc7C.js} +0 -0
  474. /package/dist/web/assets/{rosmsg-CmDth6pO.js → rosmsg-DWIGAYMl.js} +0 -0
  475. /package/dist/web/assets/{rotate-ccw-DnuPzJRE.js → rotate-ccw-CNTk2J7l.js} +0 -0
  476. /package/dist/web/assets/{rust-DGdP6jiR.js → rust-DU_ofKgt.js} +0 -0
  477. /package/dist/web/assets/{sass-Dkgt2FKZ.js → sass-U9SUuYEy.js} +0 -0
  478. /package/dist/web/assets/{save-DxonD4y1.js → save-CtW9ojKk.js} +0 -0
  479. /package/dist/web/assets/{scala-BQLTON6C.js → scala-op4gjA6j.js} +0 -0
  480. /package/dist/web/assets/{scheme-DKlxUGCi.js → scheme-tEjkR8rL.js} +0 -0
  481. /package/dist/web/assets/{sdbl-C3iZJQeZ.js → sdbl-C6SSRmZg.js} +0 -0
  482. /package/dist/web/assets/{send-B-t5Mz26.js → send-C_sLFt56.js} +0 -0
  483. /package/dist/web/assets/{shallow-jwPzEunk.js → shallow-C1YoJQA-.js} +0 -0
  484. /package/dist/web/assets/{shellscript-BRu-ZQAx.js → shellscript-Bf2KiwQj.js} +0 -0
  485. /package/dist/web/assets/{shield-check-CdTiK1j2.js → shield-check-Du6HKYn9.js} +0 -0
  486. /package/dist/web/assets/{shield-off-DuetkXKM.js → shield-off-CWKb1MQL.js} +0 -0
  487. /package/dist/web/assets/{slack-dark-qkm_u2zz.js → slack-dark-oGgCey8r.js} +0 -0
  488. /package/dist/web/assets/{slack-ochin-BP8_8e4m.js → slack-ochin-CemlB8cE.js} +0 -0
  489. /package/dist/web/assets/{smalltalk-Bnz4gt07.js → smalltalk-CTE-6jrZ.js} +0 -0
  490. /package/dist/web/assets/{snazzy-light-fbPZg5io.js → snazzy-light-DoEIGnmz.js} +0 -0
  491. /package/dist/web/assets/{solarized-dark-CM0vTRoi.js → solarized-dark-CStiJyBg.js} +0 -0
  492. /package/dist/web/assets/{solarized-light-C4_W9mvs.js → solarized-light-CfuNycS9.js} +0 -0
  493. /package/dist/web/assets/{solidity-Bz6v1PoD.js → solidity-DL6sO5rd.js} +0 -0
  494. /package/dist/web/assets/{splunk-DyNxu-0i.js → splunk-yK4ZUFBB.js} +0 -0
  495. /package/dist/web/assets/{sql-DXRL1gw1.js → sql-Lw03eoN8.js} +0 -0
  496. /package/dist/web/assets/{ssh-config-2z1QHuOG.js → ssh-config-DeRp25TR.js} +0 -0
  497. /package/dist/web/assets/{stylus-D4I-VaeY.js → stylus-D8OMsPh-.js} +0 -0
  498. /package/dist/web/assets/{swift-C_2G-tIc.js → swift-nsl9stPV.js} +0 -0
  499. /package/dist/web/assets/{synthwave-84-osBjChRP.js → synthwave-84-exAgsgpf.js} +0 -0
  500. /package/dist/web/assets/{system-verilog-zQP2qPNU.js → system-verilog-DBWf00T6.js} +0 -0
  501. /package/dist/web/assets/{systemd-UGfjRSBM.js → systemd-nhu27Dw5.js} +0 -0
  502. /package/dist/web/assets/{table-FLU8ei8b.js → table-GAR4upb6.js} +0 -0
  503. /package/dist/web/assets/{talonscript-C1XrFPwo.js → talonscript-CNOEcGDc.js} +0 -0
  504. /package/dist/web/assets/{tasl-B5raVn43.js → tasl-CjYZenSH.js} +0 -0
  505. /package/dist/web/assets/{tcl-3-vbeMIc.js → tcl-BVA09RDz.js} +0 -0
  506. /package/dist/web/assets/{terraform-a8VXZDm9.js → terraform-C5EloH1q.js} +0 -0
  507. /package/dist/web/assets/{text-wrap-B05VGRa-.js → text-wrap-D71Jqz5U.js} +0 -0
  508. /package/dist/web/assets/{tokyo-night-4SRhf-Hz.js → tokyo-night-D764DdhO.js} +0 -0
  509. /package/dist/web/assets/{toml-B4N0i8wT.js → toml-CCDYblSB.js} +0 -0
  510. /package/dist/web/assets/{trash-2-CTXlTflF.js → trash-2-C08fhIg1.js} +0 -0
  511. /package/dist/web/assets/{triangle-alert-BRq0VAfd.js → triangle-alert-85BQgs3T.js} +0 -0
  512. /package/dist/web/assets/{tsv-DA5k9Lvw.js → tsv-BZ4sA05e.js} +0 -0
  513. /package/dist/web/assets/{tsx-D9SZ8owC.js → tsx-DPLBzAt5.js} +0 -0
  514. /package/dist/web/assets/{turtle-BTHTvyY_.js → turtle-CwGHCq40.js} +0 -0
  515. /package/dist/web/assets/{typescript-DYzV9p5L.js → typescript-CUH0vBAl.js} +0 -0
  516. /package/dist/web/assets/{typespec-BC15eh7F.js → typespec-Bq31Vq4u.js} +0 -0
  517. /package/dist/web/assets/{typst-BJUqzA6D.js → typst-MD9BR9W5.js} +0 -0
  518. /package/dist/web/assets/{use-is-mobile-DFWViA4S.js → use-is-mobile-DUuki97q.js} +0 -0
  519. /package/dist/web/assets/{utils-C9OjCrou.js → utils-u2fbYPrY.js} +0 -0
  520. /package/dist/web/assets/{v-50Lps6oA.js → v-DZV7IxIT.js} +0 -0
  521. /package/dist/web/assets/{vala-8wiUvvrD.js → vala-urd1zi9S.js} +0 -0
  522. /package/dist/web/assets/{vb-BlgPLt6B.js → vb-DIZU3zbJ.js} +0 -0
  523. /package/dist/web/assets/{vendor-xterm-BZOrwF_F.js → vendor-xterm-DCj0zgSC.js} +0 -0
  524. /package/dist/web/assets/{verilog-CD9pVW5p.js → verilog-Ndz_ciTA.js} +0 -0
  525. /package/dist/web/assets/{vesper-BrivW4PT.js → vesper-C86M7LJp.js} +0 -0
  526. /package/dist/web/assets/{vhdl-Bunxfa7N.js → vhdl-_klwDUjd.js} +0 -0
  527. /package/dist/web/assets/{viml-DndUb2qM.js → viml-DzRXWoQh.js} +0 -0
  528. /package/dist/web/assets/{vitesse-black-BurZSkxW.js → vitesse-black-TWZ20_3U.js} +0 -0
  529. /package/dist/web/assets/{vitesse-dark-8UShspzC.js → vitesse-dark-Dl7R6fy9.js} +0 -0
  530. /package/dist/web/assets/{vitesse-light-DghCAZ90.js → vitesse-light-Cs0Dt2_n.js} +0 -0
  531. /package/dist/web/assets/{vyper-b-Clr-8u.js → vyper-qnCVj8ia.js} +0 -0
  532. /package/dist/web/assets/{wasm-CpcKStkw.js → wasm-Ckx9jBPe.js} +0 -0
  533. /package/dist/web/assets/{wasm-C5v2T06Y.js → wasm-Dy-SJ3W9.js} +0 -0
  534. /package/dist/web/assets/{wenyan-Dz2srLZR.js → wenyan-DTEy2Lx-.js} +0 -0
  535. /package/dist/web/assets/{wgsl-CeD1-dRb.js → wgsl-DC7n63Sy.js} +0 -0
  536. /package/dist/web/assets/{wikitext-CyeS-9FH.js → wikitext-1zFoZiW6.js} +0 -0
  537. /package/dist/web/assets/{wit-Kr-q-qBQ.js → wit-DtTfCRph.js} +0 -0
  538. /package/dist/web/assets/{wolfram-BKm8bmln.js → wolfram-B2Bgu1KL.js} +0 -0
  539. /package/dist/web/assets/{x-YDu4m2Iq.js → x-CeN0Qybc.js} +0 -0
  540. /package/dist/web/assets/{yaml-2XSxomjM.js → yaml-B-Jxk0IQ.js} +0 -0
  541. /package/dist/web/assets/{zenscript-CKIS34vf.js → zenscript-BWRfvNER.js} +0 -0
  542. /package/dist/web/assets/{zig-DdHGNllX.js → zig-CkwuvETm.js} +0 -0
@@ -0,0 +1,224 @@
1
+ /**
2
+ * Multi-touch gesture engine for the mobile remote-desktop viewer: pinch-zoom/pan (CSS
3
+ * transform on the caller's "stage" element) plus tap/drag → pointer + wheel messages over the
4
+ * shared connection. Two modes (see design doc `research-260907-1538-remote-desktop-mobile-
5
+ * touch.md`): TOUCH maps a 1-finger drag straight to an absolute drag at the touch point;
6
+ * MOUSE keeps a client-side virtual cursor that a 1-finger drag moves *relatively*, with a tap
7
+ * clicking wherever the cursor currently sits. Two-finger gestures are shared by both modes:
8
+ * tap = right click, pinch = zoom, drag = pan (while zoomed) or scroll wheel (otherwise). The
9
+ * actual decision rules live in `remote-desktop-touch-gesture-tracker.ts`; this file only wires
10
+ * DOM touch events to them and to React state.
11
+ *
12
+ * All touch listeners are native (`addEventListener`, non-passive) rather than React props, so
13
+ * `stopPropagation()` here also stops the ancestor `BottomSheet`'s swipe-to-dismiss (a React
14
+ * synthetic handler) from ever seeing the event — same trick `mobile-explorer-sheet.tsx` uses
15
+ * for its inline-rename case.
16
+ */
17
+ import { useEffect, useRef, useState, useCallback } from "react";
18
+ import { letterboxedContentRect, type ZoomPanTransform, type Fraction, type Rect } from "./remote-desktop-coords";
19
+ import {
20
+ beginSingleTouch, advanceSingleTouch, resolveSingleTouchEnd,
21
+ beginTwoFingerTouch, advanceTwoFingerTouch, resolveTwoFingerEnd,
22
+ type SingleTrack, type TwoFingerTrack, type RemoteDesktopInputMode,
23
+ } from "./remote-desktop-touch-gesture-tracker";
24
+
25
+ export type { RemoteDesktopInputMode };
26
+
27
+ const IDENTITY_TRANSFORM: ZoomPanTransform = { scale: 1, panX: 0, panY: 0 };
28
+ /** CSS px of two-finger drag per wheel notch (`WHEEL_DELTA` = 120 units, matching a real wheel
29
+ * "click"). Tune here if a live test finds scrolling too fast/slow. */
30
+ const WHEEL_PIXELS_PER_NOTCH = 40;
31
+ const WHEEL_DELTA = 120;
32
+
33
+ export interface UseRemoteDesktopTouchOptions {
34
+ /** The never-transformed element touch listeners attach to and whose rect is the reference
35
+ * frame at `scale=1, pan=0` — must fill the same box the "stage" (canvas) sits in at rest. */
36
+ containerRef: React.RefObject<HTMLElement | null>;
37
+ /** The canvas actually being drawn into. Its CSS box is the same size as `containerRef` (it
38
+ * fills the stage, which fills the container), but its drawing-buffer size (`.width`/
39
+ * `.height`, the real capture resolution) is almost never the same *aspect ratio* — a tap
40
+ * must be measured against the letterboxed video rect this produces inside the container,
41
+ * not the container's full box, or it lands off by however big the letterbox bars are. */
42
+ canvasRef: React.RefObject<HTMLCanvasElement | null>;
43
+ mode: RemoteDesktopInputMode;
44
+ sendMessage: (msg: Record<string, unknown>) => void;
45
+ enabled: boolean;
46
+ }
47
+
48
+ export interface UseRemoteDesktopTouchResult {
49
+ /** Apply as `transform: translate(panXpx, panYpx) scale(scale)` on the stage element. */
50
+ transform: ZoomPanTransform;
51
+ /** Client-side cursor position (mouse mode only) — draw a marker at this fraction inside the
52
+ * stage. `null` in touch mode (no persistent cursor concept there). */
53
+ virtualCursor: Fraction | null;
54
+ resetZoom: () => void;
55
+ }
56
+
57
+ export function useRemoteDesktopTouch({
58
+ containerRef,
59
+ canvasRef,
60
+ mode,
61
+ sendMessage,
62
+ enabled,
63
+ }: UseRemoteDesktopTouchOptions): UseRemoteDesktopTouchResult {
64
+ const [transform, setTransform] = useState<ZoomPanTransform>(IDENTITY_TRANSFORM);
65
+ const [virtualCursor, setVirtualCursor] = useState<Fraction>({ xFrac: 0.5, yFrac: 0.5 });
66
+ const transformRef = useRef(transform);
67
+ const cursorRef = useRef(virtualCursor);
68
+ const modeRef = useRef(mode);
69
+ modeRef.current = mode;
70
+
71
+ const pendingRef = useRef<{ transform: ZoomPanTransform | null; cursor: Fraction | null; sendCursorMove: boolean; wheelPx: number }>(
72
+ { transform: null, cursor: null, sendCursorMove: false, wheelPx: 0 },
73
+ );
74
+ const rafIdRef = useRef(0);
75
+
76
+ const flush = useCallback(() => {
77
+ rafIdRef.current = 0;
78
+ const p = pendingRef.current;
79
+ if (p.transform) { transformRef.current = p.transform; setTransform(p.transform); p.transform = null; }
80
+ if (p.cursor) {
81
+ cursorRef.current = p.cursor;
82
+ setVirtualCursor(p.cursor);
83
+ if (p.sendCursorMove) sendMessage({ type: "pointer", xFrac: p.cursor.xFrac, yFrac: p.cursor.yFrac, button: null, down: null });
84
+ p.cursor = null;
85
+ p.sendCursorMove = false;
86
+ }
87
+ if (p.wheelPx !== 0) {
88
+ // Fingers moving up (negative px) reads as "scroll content up" — a positive (forward)
89
+ // wheel notch, matching a physical wheel rotated away from the user.
90
+ sendMessage({ type: "wheel", dy: -(p.wheelPx / WHEEL_PIXELS_PER_NOTCH) * WHEEL_DELTA });
91
+ p.wheelPx = 0;
92
+ }
93
+ }, [sendMessage]);
94
+
95
+ const schedule = useCallback(() => {
96
+ if (!rafIdRef.current) rafIdRef.current = requestAnimationFrame(flush);
97
+ }, [flush]);
98
+
99
+ const resetZoom = useCallback(() => {
100
+ transformRef.current = IDENTITY_TRANSFORM;
101
+ setTransform(IDENTITY_TRANSFORM);
102
+ }, []);
103
+
104
+ useEffect(() => {
105
+ const container = containerRef.current;
106
+ if (!container || !enabled) return;
107
+
108
+ let single: SingleTrack | null = null;
109
+ let two: TwoFingerTrack | null = null;
110
+ const sendAll = (msgs: Record<string, unknown>[]) => msgs.forEach(sendMessage);
111
+
112
+ // The container's rect (never transformed) is only the OUTER box the video is letterboxed
113
+ // within — `object-fit: contain` (or the matching sizing on the canvas) means the canvas
114
+ // element's CSS box fills that outer box, but the pixels it actually draws are narrower or
115
+ // shorter depending on how the capture's aspect ratio compares to the phone's. Without this,
116
+ // gestures were measured against the full outer box, so a tap landed at the finger's raw
117
+ // screen position instead of the corresponding video pixel whenever there was a letterbox.
118
+ const getVideoRect = (): Rect => {
119
+ const box = container.getBoundingClientRect();
120
+ const canvas = canvasRef.current;
121
+ if (!canvas || !canvas.width || !canvas.height) return box;
122
+ return letterboxedContentRect(box, canvas.width, canvas.height);
123
+ };
124
+
125
+ const onTouchStart = (e: TouchEvent) => {
126
+ e.preventDefault();
127
+ e.stopPropagation();
128
+ const rect = getVideoRect();
129
+ if (e.touches.length === 1) {
130
+ two = null;
131
+ const t = e.touches[0]!;
132
+ single = beginSingleTouch(t.clientX, t.clientY, rect);
133
+ } else if (e.touches.length === 2) {
134
+ single = null;
135
+ const [a, b] = [e.touches[0]!, e.touches[1]!];
136
+ two = beginTwoFingerTouch({ x: a.clientX, y: a.clientY }, { x: b.clientX, y: b.clientY }, transformRef.current.scale);
137
+ } else {
138
+ // 3+ fingers: not a gesture this viewer understands — drop tracking rather than guess.
139
+ single = null;
140
+ two = null;
141
+ }
142
+ };
143
+
144
+ const onTouchMove = (e: TouchEvent) => {
145
+ e.preventDefault();
146
+ e.stopPropagation();
147
+ if (single && e.touches.length === 1) {
148
+ const t = e.touches[0]!;
149
+ const r = advanceSingleTouch(single, t.clientX, t.clientY, modeRef.current, transformRef.current, cursorRef.current);
150
+ if (r.message) sendMessage(r.message);
151
+ if (r.cursor) {
152
+ // Mouse mode is DELTA-based (each call moves the cursor *relative* to `cursorRef`).
153
+ // rAF coalesces the state-commit + wire-send below to once per frame, but the delta
154
+ // math itself must not wait for that — updating `cursorRef.current` only inside
155
+ // `flush()` meant back-to-back touchmove events landing in the same animation frame
156
+ // each recomputed their delta against the SAME stale base position, so only the last
157
+ // one "counted" and most of the finger's movement was silently dropped (the host
158
+ // cursor barely moved). Updating the ref here, synchronously, makes every event
159
+ // compound on the last regardless of how many land before the next flush.
160
+ cursorRef.current = r.cursor;
161
+ pendingRef.current.cursor = r.cursor;
162
+ pendingRef.current.sendCursorMove = !!r.sendCursorMove;
163
+ schedule();
164
+ }
165
+ return;
166
+ }
167
+ if (two && e.touches.length === 2) {
168
+ const [a, b] = [e.touches[0]!, e.touches[1]!];
169
+ const r = advanceTwoFingerTouch(two, { x: a.clientX, y: a.clientY }, { x: b.clientX, y: b.clientY }, transformRef.current);
170
+ if (r.transform) {
171
+ // Same reasoning as the cursor above: pan is delta-based off `transformRef`, so it
172
+ // must be updated eagerly too, not just inside the throttled flush.
173
+ transformRef.current = r.transform;
174
+ pendingRef.current.transform = r.transform;
175
+ schedule();
176
+ }
177
+ if (r.wheelDeltaPx) { pendingRef.current.wheelPx += r.wheelDeltaPx; schedule(); }
178
+ }
179
+ };
180
+
181
+ const onTouchEnd = (e: TouchEvent) => {
182
+ e.preventDefault();
183
+ e.stopPropagation();
184
+ const t = e.changedTouches[0];
185
+ if (single) {
186
+ const x = t ? t.clientX : single.last.x;
187
+ const y = t ? t.clientY : single.last.y;
188
+ sendAll(resolveSingleTouchEnd(single, x, y, modeRef.current, transformRef.current, cursorRef.current));
189
+ single = null;
190
+ }
191
+ if (two) {
192
+ sendAll(resolveTwoFingerEnd(two, getVideoRect(), transformRef.current));
193
+ two = null;
194
+ }
195
+ };
196
+
197
+ const onTouchCancel = (e: TouchEvent) => {
198
+ e.stopPropagation();
199
+ // A held button left down on the host would be worse than a dropped click — release it.
200
+ // Only touch mode ever sends a "down" during a drag (mouse mode only moves the virtual
201
+ // cursor until a tap), so only touch mode has anything to release here.
202
+ if (single?.moved && modeRef.current === "touch") {
203
+ sendAll(resolveSingleTouchEnd(single, single.last.x, single.last.y, "touch", transformRef.current, cursorRef.current));
204
+ }
205
+ single = null;
206
+ two = null;
207
+ };
208
+
209
+ container.addEventListener("touchstart", onTouchStart, { passive: false });
210
+ container.addEventListener("touchmove", onTouchMove, { passive: false });
211
+ container.addEventListener("touchend", onTouchEnd, { passive: false });
212
+ container.addEventListener("touchcancel", onTouchCancel, { passive: false });
213
+
214
+ return () => {
215
+ if (rafIdRef.current) cancelAnimationFrame(rafIdRef.current);
216
+ container.removeEventListener("touchstart", onTouchStart);
217
+ container.removeEventListener("touchmove", onTouchMove);
218
+ container.removeEventListener("touchend", onTouchEnd);
219
+ container.removeEventListener("touchcancel", onTouchCancel);
220
+ };
221
+ }, [containerRef, canvasRef, enabled, sendMessage, schedule]);
222
+
223
+ return { transform, virtualCursor: mode === "mouse" ? virtualCursor : null, resetZoom };
224
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Forwards keydown/keyup from a hidden `<input>` to the shared connection, so a toolbar
3
+ * "Keyboard" button can bring up the phone's soft keyboard without a physical keyboard ever
4
+ * being attached. Mirrors `use-remote-input-capture.ts`'s key handling exactly: `code`
5
+ * (physical key, layout-independent) is sent, never `key` — the host maps `code` → VK.
6
+ */
7
+ import { useEffect, useRef, useCallback } from "react";
8
+
9
+ export interface UseRemoteDesktopVirtualKeyboardResult {
10
+ inputRef: React.RefObject<HTMLInputElement | null>;
11
+ /** Focus the hidden input, which brings up the soft keyboard on a touch device. */
12
+ show: () => void;
13
+ }
14
+
15
+ export function useRemoteDesktopVirtualKeyboard(
16
+ sendMessage: (msg: Record<string, unknown>) => void,
17
+ enabled: boolean,
18
+ ): UseRemoteDesktopVirtualKeyboardResult {
19
+ const inputRef = useRef<HTMLInputElement | null>(null);
20
+
21
+ useEffect(() => {
22
+ const input = inputRef.current;
23
+ if (!input || !enabled) return;
24
+
25
+ // A lost keyup (blur, tab hidden, WS drop) must not leave a modifier logically held on the
26
+ // host — force a release on every path that could lose the matching keyup.
27
+ const releaseAll = () => sendMessage({ type: "releaseAll" });
28
+ const onKeyDown = (e: KeyboardEvent) => { e.preventDefault(); sendMessage({ type: "key", code: e.code, down: true }); };
29
+ const onKeyUp = (e: KeyboardEvent) => { e.preventDefault(); sendMessage({ type: "key", code: e.code, down: false }); };
30
+ const onBlur = () => releaseAll();
31
+ const onVisibilityChange = () => { if (document.hidden) releaseAll(); };
32
+
33
+ input.addEventListener("keydown", onKeyDown);
34
+ input.addEventListener("keyup", onKeyUp);
35
+ input.addEventListener("blur", onBlur);
36
+ document.addEventListener("visibilitychange", onVisibilityChange);
37
+
38
+ return () => {
39
+ input.removeEventListener("keydown", onKeyDown);
40
+ input.removeEventListener("keyup", onKeyUp);
41
+ input.removeEventListener("blur", onBlur);
42
+ document.removeEventListener("visibilitychange", onVisibilityChange);
43
+ releaseAll();
44
+ };
45
+ }, [enabled, sendMessage]);
46
+
47
+ const show = useCallback(() => inputRef.current?.focus(), []);
48
+
49
+ return { inputRef, show };
50
+ }
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Captures pointer + keyboard on the canvas and forwards them as JSON over the caller's WS.
3
+ * `code` (physical key, layout-independent) is sent for keys, never `key` — the host maps
4
+ * `code` → VK, so a client-side layout mismatch can't inject the wrong character.
5
+ */
6
+ import { useEffect, useCallback } from "react";
7
+ import { fractionFromPoint } from "./remote-desktop-coords";
8
+
9
+ type PointerButton = "left" | "right" | null;
10
+
11
+ export function useRemoteInputCapture(
12
+ canvasRef: React.RefObject<HTMLCanvasElement | null>,
13
+ sendMessage: (msg: Record<string, unknown>) => void,
14
+ enabled: boolean,
15
+ ): void {
16
+ const sendPointer = useCallback((clientX: number, clientY: number, button: PointerButton, down: boolean | null) => {
17
+ const canvas = canvasRef.current;
18
+ if (!canvas) return;
19
+ const { xFrac, yFrac } = fractionFromPoint(clientX, clientY, canvas.getBoundingClientRect());
20
+ sendMessage({ type: "pointer", xFrac, yFrac, button, down });
21
+ }, [canvasRef, sendMessage]);
22
+
23
+ useEffect(() => {
24
+ const canvas = canvasRef.current;
25
+ if (!canvas || !enabled) return;
26
+
27
+ const toButton = (b: number): PointerButton => (b === 0 ? "left" : b === 2 ? "right" : null);
28
+ // A lost keyup (blur, tab hidden, WS drop) must not leave a modifier logically held on
29
+ // the host — force a release on every path that could lose the matching keyup.
30
+ const releaseAll = () => sendMessage({ type: "releaseAll" });
31
+
32
+ // Coalesce moves to one message per animation frame (~60Hz). Raw pointermove fires per
33
+ // pixel — unthrottled that floods the WS, and over a relayed tunnel the move backlog
34
+ // starves the button down/up that follow (clicks "don't land"). Down/up/keys are never
35
+ // throttled so they stay prompt.
36
+ let pendingMove: { x: number; y: number } | null = null;
37
+ let rafId = 0;
38
+ const flushMove = () => {
39
+ rafId = 0;
40
+ if (pendingMove) {
41
+ sendPointer(pendingMove.x, pendingMove.y, null, null);
42
+ pendingMove = null;
43
+ }
44
+ };
45
+ const flushPendingNow = () => {
46
+ if (rafId) { cancelAnimationFrame(rafId); rafId = 0; }
47
+ if (pendingMove) { sendPointer(pendingMove.x, pendingMove.y, null, null); pendingMove = null; }
48
+ };
49
+
50
+ const onPointerDown = (e: PointerEvent) => {
51
+ e.preventDefault();
52
+ canvas.setPointerCapture(e.pointerId);
53
+ canvas.focus();
54
+ flushPendingNow(); // land any queued move at the press position first
55
+ sendPointer(e.clientX, e.clientY, toButton(e.button), true);
56
+ };
57
+ const onPointerMove = (e: PointerEvent) => {
58
+ pendingMove = { x: e.clientX, y: e.clientY };
59
+ if (!rafId) rafId = requestAnimationFrame(flushMove);
60
+ };
61
+ const onPointerUp = (e: PointerEvent) => {
62
+ flushPendingNow();
63
+ sendPointer(e.clientX, e.clientY, toButton(e.button), false);
64
+ };
65
+ const onContextMenu = (e: MouseEvent) => e.preventDefault();
66
+ const onKeyDown = (e: KeyboardEvent) => { e.preventDefault(); sendMessage({ type: "key", code: e.code, down: true }); };
67
+ const onKeyUp = (e: KeyboardEvent) => { e.preventDefault(); sendMessage({ type: "key", code: e.code, down: false }); };
68
+ const onVisibilityChange = () => { if (document.hidden) releaseAll(); };
69
+
70
+ canvas.tabIndex = 0; // focusable so it can receive keydown/keyup at all
71
+ canvas.addEventListener("pointerdown", onPointerDown);
72
+ canvas.addEventListener("pointermove", onPointerMove);
73
+ canvas.addEventListener("pointerup", onPointerUp);
74
+ canvas.addEventListener("contextmenu", onContextMenu);
75
+ canvas.addEventListener("keydown", onKeyDown);
76
+ canvas.addEventListener("keyup", onKeyUp);
77
+ canvas.addEventListener("blur", releaseAll);
78
+ document.addEventListener("visibilitychange", onVisibilityChange);
79
+
80
+ return () => {
81
+ if (rafId) cancelAnimationFrame(rafId);
82
+ canvas.removeEventListener("pointerdown", onPointerDown);
83
+ canvas.removeEventListener("pointermove", onPointerMove);
84
+ canvas.removeEventListener("pointerup", onPointerUp);
85
+ canvas.removeEventListener("contextmenu", onContextMenu);
86
+ canvas.removeEventListener("keydown", onKeyDown);
87
+ canvas.removeEventListener("keyup", onKeyUp);
88
+ canvas.removeEventListener("blur", releaseAll);
89
+ document.removeEventListener("visibilitychange", onVisibilityChange);
90
+ };
91
+ }, [canvasRef, enabled, sendPointer, sendMessage]);
92
+ }
@@ -1,96 +1,96 @@
1
1
  /**
2
2
  * All user-facing strings for the named-tunnel setup flow, kept in one file so
3
- * a future i18n pass has a single place to swap. Vietnamese is the primary
4
- * language here — no English fallback is implemented yet.
3
+ * a future i18n pass has a single place to swap. English, like the rest of the
4
+ * PPM interface.
5
5
  */
6
6
  export const namedTunnelCopy = {
7
7
  askDomain: {
8
- title: "Bạn đã domain trên Cloudflare chưa?",
9
- body: " domain riêng thì PPM thể gắn một địa chỉ cố định (không đổi khi khởi động lại). Không domain thì vẫn dùng bình thường với link tạm thời.",
10
- yes: "",
11
- no: "Chưa",
8
+ title: "Do you have a domain on Cloudflare?",
9
+ body: "With your own domain, PPM can keep one fixed address that survives restarts. Without one, everything still works through a temporary link.",
10
+ yes: "Yes",
11
+ no: "Not yet",
12
12
  },
13
13
  noDomain: {
14
- title: "Không sao cả",
15
- body: "PPM sẽ tiếp tục dùng link tạm thời (quick tunnel). Bạn thể bật lại tính năng này bất cứ lúc nào trong mục Tunnel Manager.",
16
- close: "Đóng",
14
+ title: "No problem",
15
+ body: "PPM will keep using a temporary link (quick tunnel). You can turn this on later from Tunnel Manager.",
16
+ close: "Close",
17
17
  },
18
18
  login: {
19
- title: "Đăng nhập Cloudflare",
20
- hint: "Mở link bên dưới để đăng nhập thể làm trên điện thoại nếu tiện hơn.",
21
- finishOnPhone: " thể hoàn tất bước này trên điện thoại nếu dễ hơn.",
22
- copy: "Sao chép",
23
- copied: "Đã sao chép",
24
- open: "Mở link",
25
- waiting: "Đang chờ đăng nhập…",
26
- slowTitle: "Vẫn đang đăng nhập?",
27
- slowBody: "Cloudflare đôi khi mất vài phút. Tiến trình vẫn đang chạybạn thể tiếp tục chờ hoặc huỷ.",
28
- keepWaiting: "Tiếp tục chờ",
29
- cancel: "Huỷ",
19
+ title: "Sign in to Cloudflare",
20
+ hint: "Open the link below to sign ina phone works just as well.",
21
+ finishOnPhone: "You can finish this step on your phone if that is easier.",
22
+ copy: "Copy",
23
+ copied: "Copied",
24
+ open: "Open link",
25
+ waiting: "Waiting for sign-in…",
26
+ slowTitle: "Still signing in?",
27
+ slowBody: "Cloudflare sometimes takes a few minutes. The process is still runningkeep waiting or cancel.",
28
+ keepWaiting: "Keep waiting",
29
+ cancel: "Cancel",
30
30
  },
31
31
  timeout: {
32
- title: "Link đăng nhập đã hết hạn",
33
- body: "Link hết hạn sau 5 phút không dùng. Bấm Thử lại để lấy link mới.",
34
- retry: "Thử lại",
32
+ title: "Sign-in link expired",
33
+ body: "The link expires after 5 minutes unused. Press Retry to get a fresh one.",
34
+ retry: "Retry",
35
35
  },
36
36
  cancelled: {
37
- title: "Đã huỷ đăng nhập",
38
- body: "Bạn đã huỷ quá trình đăng nhập Cloudflare.",
39
- retry: "Thử lại",
37
+ title: "Sign-in cancelled",
38
+ body: "You cancelled the Cloudflare sign-in.",
39
+ retry: "Retry",
40
40
  },
41
41
  confirmZone: {
42
- title: "Xác nhận vùng miền",
43
- body: (zone: string) => `Máy này sẽ được gán một địa chỉ cố định dưới ${zone}. Tiếp tục?`,
44
- confirm: "Tiếp tục",
45
- startOver: "Bắt đầu lại",
42
+ title: "Confirm your zone",
43
+ body: (zone: string) => `This machine will get a fixed address under ${zone}. Continue?`,
44
+ confirm: "Continue",
45
+ startOver: "Start over",
46
46
  },
47
47
  needsRelogin: {
48
- title: "Cần đăng nhập lại Cloudflare",
49
- certInvalid: "Phiên đăng nhập Cloudflare đã hết hiệu lực.",
50
- certMismatch: "Chứng chỉ này thuộc một tài khoản Cloudflare kháccần đăng nhập lại.",
51
- action: "Đăng nhập lại",
48
+ title: "Cloudflare sign-in needed",
49
+ certInvalid: "Your Cloudflare sign-in is no longer valid.",
50
+ certMismatch: "This certificate belongs to a different Cloudflare accountsign in again.",
51
+ action: "Sign in again",
52
52
  },
53
53
  hostname: {
54
- title: "Chọn địa chỉ",
55
- prefixLabel: "Tiền tố",
56
- suffixHint: "Phần đuôi được cố định theo tài khoản bạn vừa đăng nhập.",
57
- submit: "Xác nhận",
54
+ title: "Choose the address",
55
+ prefixLabel: "Prefix",
56
+ suffixHint: "The suffix is fixed by the account you just signed in with.",
57
+ submit: "Confirm",
58
58
  },
59
59
  applying: {
60
- title: "Đang thiết lập…",
60
+ title: "Setting up…",
61
61
  },
62
62
  done: {
63
- title: "Đã thiết lập xong",
64
- body: (hostname: string) => `Địa chỉ cố định: https://${hostname}`,
65
- close: "Đóng",
63
+ title: "All set",
64
+ body: (hostname: string) => `Fixed address: https://${hostname}`,
65
+ close: "Close",
66
66
  },
67
67
  pending: {
68
- title: "Đã lưu đang chờ áp dụng",
69
- restartHint: "Chạy lệnh `ppm restart` để áp dụng ngay.",
70
- close: "Đóng",
68
+ title: "Savedwaiting to apply",
69
+ restartHint: "Run `ppm restart` to apply it now.",
70
+ close: "Close",
71
71
  },
72
72
  error: {
73
- title: " lỗi xảy ra",
74
- retry: "Thử lại",
75
- close: "Đóng",
73
+ title: "Something went wrong",
74
+ retry: "Retry",
75
+ close: "Close",
76
76
  },
77
77
  section: {
78
78
  title: "Named Tunnel",
79
79
  modeQuick: "quick",
80
80
  modeNamed: "named",
81
- hostnameLabel: "Địa chỉ",
81
+ hostnameLabel: "Address",
82
82
  tokenLabel: "Token",
83
- setup: "Thiết lập named tunnel",
84
- retry: "Thử lại",
85
- relogin: "Đăng nhập lại",
86
- disable: "Chuyển về quick tunnel",
87
- disableConfirm: "Bấm lần nữa để xác nhận",
83
+ setup: "Set up named tunnel",
84
+ retry: "Retry",
85
+ relogin: "Sign in again",
86
+ disable: "Switch back to quick tunnel",
87
+ disableConfirm: "Press again to confirm",
88
88
  disableSelfCut:
89
- "Bạn đang mở PPM qua chính tên miền này tắt sẽ ngắt trang ngay bạn sẽ không kịp thấy link tạm. Mở PPM qua địa chỉ mạng nội bộ rồi tắt.",
90
- certInvalid: "Cần đăng nhập lại Cloudflare",
91
- certMismatch: "Chứng chỉ thuộc tài khoản Cloudflare kháccần đăng nhập lại",
92
- authDisabled: "Bật xác thực PPM để dùng tên miền riêng",
89
+ "You are viewing PPM through this very domainturning it off cuts this page immediately and you would never see the temporary link. Open PPM on your local network first, then turn it off.",
90
+ certInvalid: "Cloudflare sign-in needed",
91
+ certMismatch: "Certificate belongs to a different Cloudflare accountsign in again",
92
+ authDisabled: "Enable PPM authentication to use your own domain",
93
93
  /** Small note next to the live-mode badge when the configured mode hasn't landed yet. */
94
- configuredAs: (mode: "quick" | "named") => `đã cấu hình: ${mode}`,
94
+ configuredAs: (mode: "quick" | "named") => `configured: ${mode}`,
95
95
  },
96
96
  } as const;
@@ -52,10 +52,10 @@ export function NamedTunnelSection() {
52
52
  setDisabling(true);
53
53
  try {
54
54
  await namedTunnelApi.disable();
55
- toast.success("Đã chuyển về quick tunnel");
55
+ toast.success("Switched back to a quick tunnel");
56
56
  await t.refreshStatus();
57
57
  } catch (e) {
58
- toast.error(e instanceof Error ? e.message : "Không chuyển được về quick tunnel");
58
+ toast.error(e instanceof Error ? e.message : "Could not switch back to a quick tunnel");
59
59
  } finally {
60
60
  setDisabling(false);
61
61
  }
@@ -1,8 +1,8 @@
1
1
  import { useEffect, useRef } from "react";
2
- import { useNotificationStore, selectTotalUnread } from "@/stores/notification-store";
2
+ import { useNotificationStore, selectProjectUnread } from "@/stores/notification-store";
3
3
  import { useProjectStore } from "@/stores/project-store";
4
4
  import { useSettingsStore } from "@/stores/settings-store";
5
- import { useStreamingStore, selectAnyStreaming } from "@/stores/streaming-store";
5
+ import { useStreamingStore, selectProjectStreaming } from "@/stores/streaming-store";
6
6
  import { setFavicon, STREAM_FRAME_COUNT } from "@/lib/favicon";
7
7
 
8
8
  function buildTitle(unread: number, projectName?: string, deviceName?: string): string {
@@ -11,25 +11,33 @@ function buildTitle(unread: number, projectName?: string, deviceName?: string):
11
11
  }
12
12
 
13
13
  /** Syncs document.title and favicon with unread notification count + streaming state.
14
- * When any chat is streaming, favicon alternates between blue and amber every 800ms.
15
- * Uses direct Zustand subscription to update immediately even in background tabs. */
14
+ * When a chat in this window's project is streaming, favicon alternates between blue and
15
+ * amber every 800ms. Uses direct Zustand subscription to update immediately even in
16
+ * background tabs.
17
+ *
18
+ * Everything here is scoped to `activeProject`, because the title bar and the tab icon
19
+ * describe *this* window and a window shows one project. Both inputs used to be app-wide:
20
+ * `/ws/global` delivers phase and unread changes for every project, so opening three
21
+ * workspaces in three PWA windows gave all three the same count and the same streaming
22
+ * animation whenever any one of them was busy. */
16
23
  export function useNotificationBadge(): void {
17
24
  const activeProject = useProjectStore((s) => s.activeProject);
18
25
  const deviceName = useSettingsStore((s) => s.deviceName);
19
- const anyStreaming = useStreamingStore(selectAnyStreaming);
26
+ const projectName = activeProject?.name;
27
+ const projectStreaming = useStreamingStore(selectProjectStreaming(projectName));
20
28
  const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
21
29
 
22
30
  useEffect(() => {
23
- const getHasBadge = () => selectTotalUnread(useNotificationStore.getState()) > 0;
31
+ const unreadHere = () => selectProjectUnread(projectName)(useNotificationStore.getState());
32
+ const getHasBadge = () => unreadHere() > 0;
24
33
 
25
34
  const updateTitle = () => {
26
- const unread = selectTotalUnread(useNotificationStore.getState());
27
- document.title = buildTitle(unread, activeProject?.name, deviceName ?? undefined);
35
+ document.title = buildTitle(unreadHere(), projectName, deviceName ?? undefined);
28
36
  };
29
37
 
30
38
  updateTitle();
31
39
 
32
- if (anyStreaming) {
40
+ if (projectStreaming) {
33
41
  // Cycle through typing-dots frames (3 dots + 1 rest frame, Messenger style) every 300ms
34
42
  let frame = 0;
35
43
  setFavicon(getHasBadge(), frame);
@@ -45,7 +53,7 @@ export function useNotificationBadge(): void {
45
53
  const unsub = useNotificationStore.subscribe(() => {
46
54
  updateTitle();
47
55
  // Static favicon update only when not streaming (interval handles streaming)
48
- if (!anyStreaming) setFavicon(getHasBadge());
56
+ if (!projectStreaming) setFavicon(getHasBadge());
49
57
  });
50
58
 
51
59
  return () => {
@@ -55,5 +63,5 @@ export function useNotificationBadge(): void {
55
63
  intervalRef.current = null;
56
64
  }
57
65
  };
58
- }, [activeProject?.name, deviceName, anyStreaming]);
66
+ }, [projectName, deviceName, projectStreaming]);
59
67
  }
@@ -12,6 +12,8 @@ export const FEATURE_BADGES = {
12
12
  teams: "beta",
13
13
  /** Floating OS-style file explorer window / mobile sheet, shipped in 0.18.0. */
14
14
  "os-explorer": "new",
15
+ /** Remote Desktop — stream + control the host desktop in a floating window. */
16
+ "remote-desktop": "beta",
15
17
  } as const satisfies Record<string, FeatureBadgeKind>;
16
18
 
17
19
  export type FeatureBadgeId = keyof typeof FEATURE_BADGES;