@hienlh/ppm 0.18.14 → 0.18.16

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 (546) hide show
  1. package/.rdtest2-cf.out +0 -0
  2. package/CHANGELOG.md +26 -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-BbuRVTLn.js} +1 -1
  8. package/dist/web/assets/{ai-resource-editor-D5CPBg06.js → ai-resource-editor-Cm4n3VXt.js} +1 -1
  9. package/dist/web/assets/ai-settings-section-BEM1z7kp.js +1 -0
  10. package/dist/web/assets/{angular-html-CTvM4ulF.js → angular-html-BlcrLjUx.js} +1 -1
  11. package/dist/web/assets/{angular-ts-B_vSkjco.js → angular-ts-BUjtceAJ.js} +1 -1
  12. package/dist/web/assets/api-client-D1CePgQ4.js +1 -0
  13. package/dist/web/assets/api-settings-CNtKkHqW.js +1 -0
  14. package/dist/web/assets/{api-settings-B2iNXQ0c.js → api-settings-Cubuqjsu.js} +1 -1
  15. package/dist/web/assets/{api-themes-DrWnJbhs.js → api-themes-YsZgf6Xj.js} +1 -1
  16. package/dist/web/assets/{apl-DywzTry0.js → apl-B3g0v1pk.js} +1 -1
  17. package/dist/web/assets/architecture-PBZL5I3N-6konx-J1.js +1 -0
  18. package/dist/web/assets/{astro-1JaRgaSb.js → astro-FRnyn7cH.js} +1 -1
  19. package/dist/web/assets/{audio-preview-DAuGvOiK.js → audio-preview-DWTRm4HC.js} +1 -1
  20. package/dist/web/assets/{blade-CDeeJWBb.js → blade-NLdOqUKw.js} +1 -1
  21. package/dist/web/assets/{bsl-BfgoPVYR.js → bsl-CenHZfaU.js} +1 -1
  22. package/dist/web/assets/{button-tNy0ba3y.js → button-Cq8yixjf.js} +1 -1
  23. package/dist/web/assets/{cairo-C26gNwWW.js → cairo-B_WR0aiQ.js} +1 -1
  24. package/dist/web/assets/chat-tab-C2e6Pwc2.js +15 -0
  25. package/dist/web/assets/{cobol-D92nBnON.js → cobol-DOynhWYj.js} +1 -1
  26. package/dist/web/assets/code-editor-DWtv_VDo.js +10 -0
  27. package/dist/web/assets/{coffee-CIWH40R1.js → coffee-Bn6dbw4P.js} +1 -1
  28. package/dist/web/assets/{conflict-editor-CDp0I3mD.js → conflict-editor-Bi2RWfKQ.js} +1 -1
  29. package/dist/web/assets/{context-menu-BO1c9jKy.js → context-menu-DfWZAxQy.js} +1 -1
  30. package/dist/web/assets/{cpp-DOrBqXCn.js → cpp-CoZR9t8r.js} +1 -1
  31. package/dist/web/assets/{crystal-BNKqmHux.js → crystal-BSheONSg.js} +1 -1
  32. package/dist/web/assets/{csv-preview-D4svNDAq.js → csv-preview-DxGZ1qg5.js} +1 -1
  33. package/dist/web/assets/{data-grid-overlay-editor-BqV0AxZg.js → data-grid-overlay-editor-gwPoVSCU.js} +1 -1
  34. package/dist/web/assets/{database-viewer-BCvv_Bwf.js → database-viewer-DSPRzZny.js} +1 -1
  35. package/dist/web/assets/{dialog-DcLGDwaX.js → dialog-BSfuiTkJ.js} +1 -1
  36. package/dist/web/assets/{diff-viewer-D41fCFAi.js → diff-viewer-YbpnNv5Y.js} +1 -1
  37. package/dist/web/assets/{docx-preview-DfaQn9v7.js → docx-preview-DqVDOMmv.js} +1 -1
  38. package/dist/web/assets/{edge-Dbjp42Pt.js → edge-DTtoslQu.js} +1 -1
  39. package/dist/web/assets/{edit-diff-preview-BqcUuFCc.js → edit-diff-preview-CXSbSUBX.js} +1 -1
  40. package/dist/web/assets/{elixir-na3b1pYy.js → elixir-CIBCZ3Zi.js} +1 -1
  41. package/dist/web/assets/{elm-_Nalil1R.js → elm-BLmb7K1H.js} +1 -1
  42. package/dist/web/assets/{erb-CW_7dubf.js → erb-BBY8ApRd.js} +1 -1
  43. package/dist/web/assets/{erlang-DmZ7Zzb0.js → erlang-D5Zb5_P0.js} +1 -1
  44. package/dist/web/assets/{esm-CwyBetO1.js → esm-6F8v1jLL.js} +1 -1
  45. package/dist/web/assets/explorer-body-DpQndBVw.js +2 -0
  46. package/dist/web/assets/explorer-window-content-m0K6Mtcg.js +1 -0
  47. package/dist/web/assets/{extension-store-DUaZINyC.js → extension-store-Bhu--fPX.js} +1 -1
  48. package/dist/web/assets/{extension-webview-BW_mccb3.js → extension-webview-BSh6KGd7.js} +1 -1
  49. package/dist/web/assets/{file-store-yReVbR8q.js → file-store-6HW2Lfl4.js} +1 -1
  50. package/dist/web/assets/{fortran-fixed-form-DB2A8L4-.js → fortran-fixed-form-CBdwkEUJ.js} +1 -1
  51. package/dist/web/assets/{fsharp-BYHrti7z.js → fsharp-AT4XdVYH.js} +1 -1
  52. package/dist/web/assets/{gdresource-D5GBin40.js → gdresource-D852fWBR.js} +1 -1
  53. package/dist/web/assets/{git-commit-CUiOBoIL.js → git-commit-DWfWAyxy.js} +1 -1
  54. package/dist/web/assets/{git-log-panel-C1QDkmgl.js → git-log-panel-CkTiR70w.js} +1 -1
  55. package/dist/web/assets/{git-rebase-DOxGnpdw.js → git-rebase-C6opk8gr.js} +1 -1
  56. package/dist/web/assets/gitGraph-HDMCJU4V-ikUwsekd.js +1 -0
  57. package/dist/web/assets/glide-data-grid-BB5mCaCD.js +138 -0
  58. package/dist/web/assets/{glimmer-js-Begy4UT7.js → glimmer-js-DLi5mWYO.js} +1 -1
  59. package/dist/web/assets/{glimmer-ts-rPApRY8N.js → glimmer-ts-D-scFX30.js} +1 -1
  60. package/dist/web/assets/{glsl-wrbPvrBL.js → glsl-dbQzF4iA.js} +1 -1
  61. package/dist/web/assets/{graphql-Cu4-NR__.js → graphql-D1m5Wen2.js} +1 -1
  62. package/dist/web/assets/group-chat-tab-BPIOdFJw.js +1 -0
  63. package/dist/web/assets/{hack-Ddo93XKD.js → hack-CqYmpO7u.js} +1 -1
  64. package/dist/web/assets/{haml-BFX_1i8j.js → haml-BXzKWEag.js} +1 -1
  65. package/dist/web/assets/hand-x0ejq3fS.js +1 -0
  66. package/dist/web/assets/{handlebars-5gFKA4H5.js → handlebars-DXnrtHtq.js} +1 -1
  67. package/dist/web/assets/{html-C06I-zeO.js → html-DhgQJ-xL.js} +1 -1
  68. package/dist/web/assets/{html-derivative-e5JLAFZF.js → html-derivative-xZRft-YE.js} +1 -1
  69. package/dist/web/assets/{http-CrAAhSU7.js → http-B53mSj14.js} +1 -1
  70. package/dist/web/assets/{hurl-T1V3SX9T.js → hurl-CsomVy04.js} +1 -1
  71. package/dist/web/assets/{hxml-B056LLKN.js → hxml-LffjWwKA.js} +1 -1
  72. package/dist/web/assets/{image-preview-DWyuCYT3.js → image-preview-CNA6XMNS.js} +1 -1
  73. package/dist/web/assets/index-BHdtSKzb.js +52 -0
  74. package/dist/web/assets/index-DN9XUQ8D.css +2 -0
  75. package/dist/web/assets/info-3K5VOQVL-BAgmboBp.js +1 -0
  76. package/dist/web/assets/{input-DouJ9Zvt.js → input-D0ol5jW0.js} +1 -1
  77. package/dist/web/assets/{jinja-DGdtRh43.js → jinja-CCHURl7V.js} +1 -1
  78. package/dist/web/assets/{jison-Ah6QobPS.js → jison-7qBd8dnR.js} +1 -1
  79. package/dist/web/assets/{julia-BJeGOK0K.js → julia-BY9pnTcy.js} +1 -1
  80. package/dist/web/assets/{just-iw9oRs7j.js → just-D7wRSNRr.js} +1 -1
  81. package/dist/web/assets/{keybindings-store-0Wqfa7Qp.js → keybindings-store-BGkB72gM.js} +1 -1
  82. package/dist/web/assets/keybindings-store-Bq6VWl5X.js +1 -0
  83. package/dist/web/assets/keyboard-DBBaoQSu.js +1 -0
  84. package/dist/web/assets/{latex-DHjj5hyN.js → latex-RgsCae55.js} +1 -1
  85. package/dist/web/assets/{liquid-B-FFecDc.js → liquid-CnDFzvOZ.js} +1 -1
  86. package/dist/web/assets/{lua-DDianJp_.js → lua-0lj5W8sm.js} +1 -1
  87. package/dist/web/assets/{markdown-renderer-BHiUhQN8.js → markdown-renderer-B9wzBWm9.js} +3 -3
  88. package/dist/web/assets/markdown-renderer-DeSV1CCQ.js +1 -0
  89. package/dist/web/assets/{marko-BW5SBVng.js → marko-CQgqAhbV.js} +1 -1
  90. package/dist/web/assets/{mdc-B9gL9bjl.js → mdc-BkIcTiIy.js} +1 -1
  91. package/dist/web/assets/{media-url-DUK-k0bB.js → media-url-DDciwvLU.js} +1 -1
  92. package/dist/web/assets/mobile-bottom-sheet-pjcHKorA.js +1 -0
  93. package/dist/web/assets/mobile-explorer-sheet-DPdxpnKh.js +1 -0
  94. package/dist/web/assets/monitor-smartphone-DLZ6Q05s.js +1 -0
  95. package/dist/web/assets/{nextflow-dTluY6qy.js → nextflow-DvIW68DS.js} +1 -1
  96. package/dist/web/assets/{nginx-DlwghGI-.js → nginx-O-hft0gG.js} +1 -1
  97. package/dist/web/assets/{nim-N9jddb5D.js → nim-BxrR4HwQ.js} +1 -1
  98. package/dist/web/assets/notification-store-BtijuGez.js +1 -0
  99. package/dist/web/assets/{number-overlay-editor-BVXM7aAs.js → number-overlay-editor-BoLRYuZT.js} +1 -1
  100. package/dist/web/assets/{overview-panel-DCCDmtCQ.js → overview-panel-l3g1gT5e.js} +1 -1
  101. package/dist/web/assets/packet-RMMSAZCW-RVlbbDRS.js +1 -0
  102. package/dist/web/assets/{panel-store-B4rijFKN.js → panel-store-UOV8wrfR.js} +1 -1
  103. package/dist/web/assets/{pdf-preview-D7yLmAZW.js → pdf-preview-B2CJ_EKv.js} +1 -1
  104. package/dist/web/assets/{perl-CkmpYKa_.js → perl-Da-uNqmL.js} +1 -1
  105. package/dist/web/assets/{php-BwZ3KDPo.js → php-JMu45mEx.js} +1 -1
  106. package/dist/web/assets/pie-UPGHQEXC-ZWb4izpk.js +1 -0
  107. package/dist/web/assets/{postgres-viewer--CRN1fvO.js → postgres-viewer-23xdZYxp.js} +3 -3
  108. package/dist/web/assets/process-table-CUsPf7wv.js +1 -0
  109. package/dist/web/assets/{project-store-BuY7rxn_.js → project-store-DTUCS65m.js} +1 -1
  110. package/dist/web/assets/{pug-B3rBPqQo.js → pug-CLzyQhia.js} +1 -1
  111. package/dist/web/assets/{qml-BfDbWFmg.js → qml-BHXfGB1l.js} +1 -1
  112. package/dist/web/assets/radar-KQ55EAFF-bsP5x1Bx.js +1 -0
  113. package/dist/web/assets/{razor-B1ww1r38.js → razor-BqetJLN3.js} +1 -1
  114. package/dist/web/assets/remote-desktop-mobile-sheet-FTF6IBhF.js +2 -0
  115. package/dist/web/assets/remote-desktop-mobile-view-DSwG4OSk.js +1 -0
  116. package/dist/web/assets/remote-desktop-stats-overlay-Bz6xZ5y9.js +1 -0
  117. package/dist/web/assets/remote-desktop-warning-gate-DrYySLZ5.js +1 -0
  118. package/dist/web/assets/remote-desktop-window-content-Ndvezorb.js +1 -0
  119. package/dist/web/assets/{rst-C0MSq6m-.js → rst-iQdJZ0BP.js} +1 -1
  120. package/dist/web/assets/{ruby-ClIZ1wb3.js → ruby-CNwzZGdH.js} +1 -1
  121. package/dist/web/assets/{run-in-terminal-4efbW2Xv.js → run-in-terminal-CBXmVGWa.js} +1 -1
  122. package/dist/web/assets/{sas-DR0j3o4_.js → sas-BpBIT2eR.js} +1 -1
  123. package/dist/web/assets/{scroll-area-ZP8wcwTI.js → scroll-area-lc0ZHORr.js} +1 -1
  124. package/dist/web/assets/{scss-Vh3JLayR.js → scss-D1MotuEp.js} +1 -1
  125. package/dist/web/assets/{send-to-chat-BNrEpAyC.js → send-to-chat-DJAdlvJ8.js} +1 -1
  126. package/dist/web/assets/settings-store-zbPkG7o-.js +2 -0
  127. package/dist/web/assets/settings-tab-Cn-AqUP_.js +1 -0
  128. package/dist/web/assets/settings-tab-xyRpt2gq.js +3 -0
  129. package/dist/web/assets/{shaderlab-0rHRnQ18.js → shaderlab-BNqmcPQi.js} +1 -1
  130. package/dist/web/assets/{shellsession-Dxdw6D8c.js → shellsession-BBLOVOO-.js} +1 -1
  131. package/dist/web/assets/{soy-BHlSNj1y.js → soy-BpTGo9uy.js} +1 -1
  132. package/dist/web/assets/{sparql-B2QaPpni.js → sparql-D7A3yKob.js} +1 -1
  133. package/dist/web/assets/{sql-query-editor-R_iKjPP9.js → sql-query-editor-BE3mLYyz.js} +1 -1
  134. package/dist/web/assets/{sqlite-viewer-b2onA0SP.js → sqlite-viewer-wN5RP8sd.js} +1 -1
  135. package/dist/web/assets/{stata-CwIxjsEJ.js → stata-B7N56ptS.js} +1 -1
  136. package/dist/web/assets/{surrealql-C6vo-rp-.js → surrealql-CTmnD-lv.js} +1 -1
  137. package/dist/web/assets/{svelte-LLdJ6rlH.js → svelte-BppwY9TR.js} +1 -1
  138. package/dist/web/assets/{switch-DV-BDjyk.js → switch-BmjKoIpR.js} +1 -1
  139. package/dist/web/assets/system-monitor-body-B2M0MC6y.js +2 -0
  140. package/dist/web/assets/system-monitor-tab-OYca4qaf.js +1 -0
  141. package/dist/web/assets/{system-monitor-window-content-BPif0azo.js → system-monitor-window-content-CHz9WdTm.js} +1 -1
  142. package/dist/web/assets/{tab-host-window-content-BnAN6m4K.js → tab-host-window-content-DKev3GJz.js} +1 -1
  143. package/dist/web/assets/{tab-store-tFnyPmm4.js → tab-store-DzYSsw5a.js} +1 -1
  144. package/dist/web/assets/team-member-sheet-BQ738Lli.js +2 -0
  145. package/dist/web/assets/{team-member-window-content-BSyU-5Uc.js → team-member-window-content-CK08f5cK.js} +1 -1
  146. package/dist/web/assets/{templ-CJGvhEP5.js → templ-CvTMKKra.js} +1 -1
  147. package/dist/web/assets/{terminal-tab-Ncor0Rfu.js → terminal-tab-DZ3QiYcs.js} +3 -3
  148. package/dist/web/assets/{tex-psXWhc4t.js → tex--c4POEUE.js} +1 -1
  149. package/dist/web/assets/{tool-cards-DdVP1wrD.js → tool-cards-Bw1P0MKU.js} +4 -4
  150. package/dist/web/assets/treemap-KZPCXAKY-Dh1oxNdQ.js +1 -0
  151. package/dist/web/assets/{ts-tags-D5Ar_ZCO.js → ts-tags--kx7D-Hw.js} +1 -1
  152. package/dist/web/assets/{twig-vJgGC-qY.js → twig-UG9uMHGd.js} +1 -1
  153. package/dist/web/assets/{use-blob-url-BOVGWg9F.js → use-blob-url-Bdxopwwc.js} +1 -1
  154. package/dist/web/assets/{use-monaco-theme-BshnLXoY.js → use-monaco-theme-BOmd2wpe.js} +1 -1
  155. package/dist/web/assets/{use-open-team-member-CY3wOdWL.js → use-open-team-member-D9EOB8-i.js} +1 -1
  156. package/dist/web/assets/use-visual-viewport-Sfb6CZ80.js +1 -0
  157. package/dist/web/assets/{use-websocket-B7xjNsyD.js → use-websocket-B7Xj-Q4W.js} +1 -1
  158. package/dist/web/assets/{vendor-mermaid-999ksD45.js → vendor-mermaid-0Nt-ew76.js} +1 -1
  159. package/dist/web/assets/{video-preview-7F72sRwf.js → video-preview-B0nbTGjQ.js} +1 -1
  160. package/dist/web/assets/{vue-html-BN5ZLu-H.js → vue-html-8KEW6-I9.js} +1 -1
  161. package/dist/web/assets/{vue-BWngFyav.js → vue-jjTomlbt.js} +1 -1
  162. package/dist/web/assets/{vue-vine-BZHnO8yV.js → vue-vine-C5tohCkU.js} +1 -1
  163. package/dist/web/assets/{window-pip-registry-BnYU83LE.js → window-pip-registry-FdZdGF7h.js} +1 -1
  164. package/dist/web/assets/window-store-D0x_fxr0.js +1 -0
  165. package/dist/web/assets/{xml-DS2beZHp.js → xml-ByFFosqL.js} +1 -1
  166. package/dist/web/assets/{xsl-Ciiku1Bq.js → xsl-CZUwiLkj.js} +1 -1
  167. package/dist/web/index.html +46 -43
  168. package/dist/web/sw.js +1 -1
  169. package/docs/architecture/ai-chat-and-providers.md +3 -0
  170. package/package.json +1 -1
  171. package/packages/vscode-compat/node_modules/.bin/tsc.bunx +0 -0
  172. package/packages/vscode-compat/node_modules/.bin/tsc.exe +0 -0
  173. package/packages/vscode-compat/node_modules/.bin/tsserver.bunx +0 -0
  174. package/packages/vscode-compat/node_modules/.bin/tsserver.exe +0 -0
  175. package/src/cli/commands/backup-cmd.ts +119 -0
  176. package/src/index.ts +3 -0
  177. package/src/providers/claude-agent-sdk.ts +29 -1
  178. package/src/server/index.ts +42 -15
  179. package/src/server/routes/accounts.ts +30 -2
  180. package/src/server/routes/chat.ts +8 -1
  181. package/src/server/routes/named-tunnel.ts +5 -2
  182. package/src/server/routes/remote-desktop.ts +64 -0
  183. package/src/server/routes/settings.ts +2 -0
  184. package/src/server/ws/chat.ts +126 -7
  185. package/src/server/ws/remote-desktop.ts +86 -0
  186. package/src/services/account.service.ts +106 -23
  187. package/src/services/db-backup/db-backup-paths.ts +66 -0
  188. package/src/services/db-backup/db-backup-restore.ts +105 -0
  189. package/src/services/db-backup/db-backup-retention.ts +96 -0
  190. package/src/services/db-backup/db-backup-sync.ts +113 -0
  191. package/src/services/db-backup/db-backup.service.ts +61 -0
  192. package/src/services/db.service.ts +29 -1
  193. package/src/services/edge-forwarder.ts +191 -191
  194. package/src/services/fs-credential-path-guard.ts +19 -1
  195. package/src/services/named-tunnel/cloudflare-dns-api.ts +20 -4
  196. package/src/services/named-tunnel/cloudflared-login.service.ts +2 -2
  197. package/src/services/named-tunnel/named-tunnel-args.ts +17 -3
  198. package/src/services/named-tunnel/named-tunnel-setup.service.ts +20 -2
  199. package/src/services/nested-subagent-spy.ts +206 -0
  200. package/src/services/remote-desktop/access-unit-assembler.ts +90 -0
  201. package/src/services/remote-desktop/avc1-codec-string.ts +18 -0
  202. package/src/services/remote-desktop/nal-splitter.ts +100 -0
  203. package/src/services/remote-desktop/remote-desktop-capture.ts +120 -0
  204. package/src/services/remote-desktop/remote-desktop-encoder-args.ts +42 -0
  205. package/src/services/remote-desktop/remote-desktop-flag.ts +12 -0
  206. package/src/services/remote-desktop/remote-desktop-input.ts +217 -0
  207. package/src/services/remote-desktop/remote-desktop-nonce.ts +38 -0
  208. package/src/services/remote-desktop/remote-desktop-session.ts +189 -0
  209. package/src/services/remote-desktop/remote-desktop-vk-map.ts +41 -0
  210. package/src/services/subagent-transcript-merger.ts +92 -75
  211. package/src/services/subprocess-retention.ts +29 -6
  212. package/src/services/supervisor.ts +58 -0
  213. package/src/services/team-member-activity/subagent-transcript-index.ts +56 -0
  214. package/src/services/windows-process-tree.ts +351 -351
  215. package/src/types/chat.ts +2 -0
  216. package/src/web/app.tsx +8 -0
  217. package/src/web/components/chat/account-dialogs.tsx +11 -4
  218. package/src/web/components/chat/question-card.tsx +9 -2
  219. package/src/web/components/chat/usage-badge.tsx +17 -3
  220. package/src/web/components/floating-window/window-content-registry.ts +2 -0
  221. package/src/web/components/floating-window/window-store-types.ts +1 -1
  222. package/src/web/components/layout/mobile-drawer.tsx +38 -27
  223. package/src/web/components/layout/nav-section-rail.tsx +10 -2
  224. package/src/web/components/remote-desktop/open-remote-desktop.ts +26 -0
  225. package/src/web/components/remote-desktop/remote-desktop-coords.ts +98 -0
  226. package/src/web/components/remote-desktop/remote-desktop-frame-timestamp.ts +21 -0
  227. package/src/web/components/remote-desktop/remote-desktop-gesture-classifiers.ts +56 -0
  228. package/src/web/components/remote-desktop/remote-desktop-mobile-key-bar.tsx +131 -0
  229. package/src/web/components/remote-desktop/remote-desktop-mobile-sheet.tsx +48 -0
  230. package/src/web/components/remote-desktop/remote-desktop-mobile-toolbar.tsx +79 -0
  231. package/src/web/components/remote-desktop/remote-desktop-mobile-view.tsx +190 -0
  232. package/src/web/components/remote-desktop/remote-desktop-stats-overlay.tsx +54 -0
  233. package/src/web/components/remote-desktop/remote-desktop-stats.ts +37 -0
  234. package/src/web/components/remote-desktop/remote-desktop-touch-gesture-tracker.ts +195 -0
  235. package/src/web/components/remote-desktop/remote-desktop-warning-gate.tsx +106 -0
  236. package/src/web/components/remote-desktop/remote-desktop-window-content.tsx +86 -0
  237. package/src/web/components/remote-desktop/remote-desktop-ws-url.ts +22 -0
  238. package/src/web/components/remote-desktop/use-h264-canvas-decoder.ts +175 -0
  239. package/src/web/components/remote-desktop/use-remote-desktop-available.ts +29 -0
  240. package/src/web/components/remote-desktop/use-remote-desktop-connection.ts +147 -0
  241. package/src/web/components/remote-desktop/use-remote-desktop-mobile-open-state.ts +18 -0
  242. package/src/web/components/remote-desktop/use-remote-desktop-touch.ts +224 -0
  243. package/src/web/components/remote-desktop/use-remote-desktop-virtual-keyboard.ts +50 -0
  244. package/src/web/components/remote-desktop/use-remote-input-capture.ts +92 -0
  245. package/src/web/components/tunnels/named-tunnel/named-tunnel-copy.ts +59 -59
  246. package/src/web/components/tunnels/named-tunnel/named-tunnel-section.tsx +2 -2
  247. package/src/web/hooks/use-notification-badge.ts +19 -11
  248. package/src/web/lib/feature-badges.ts +2 -0
  249. package/src/web/stores/settings-store.ts +25 -0
  250. package/src/web/stores/streaming-store.ts +21 -2
  251. package/dist/web/assets/ai-settings-section-DY-t1bK8.js +0 -1
  252. package/dist/web/assets/api-client-BogqsFna.js +0 -1
  253. package/dist/web/assets/api-settings-DRF9oYkn.js +0 -1
  254. package/dist/web/assets/architecture-PBZL5I3N-nw5-j_I_.js +0 -1
  255. package/dist/web/assets/chat-tab-IcnU3nqQ.js +0 -15
  256. package/dist/web/assets/code-editor-Dno5Srkm.js +0 -10
  257. package/dist/web/assets/explorer-body-DB1L30pa.js +0 -2
  258. package/dist/web/assets/explorer-window-content-Cwlt3C3I.js +0 -1
  259. package/dist/web/assets/gitGraph-HDMCJU4V-C1xf87gN.js +0 -1
  260. package/dist/web/assets/glide-data-grid-Dz8lx6n2.js +0 -138
  261. package/dist/web/assets/group-chat-tab-DFwr-pig.js +0 -1
  262. package/dist/web/assets/index-DWpNR3nB.css +0 -2
  263. package/dist/web/assets/index-oUKV5nIA.js +0 -52
  264. package/dist/web/assets/info-3K5VOQVL-C2z7FTDe.js +0 -1
  265. package/dist/web/assets/keybindings-store-d54pv2NK.js +0 -1
  266. package/dist/web/assets/markdown-renderer-CnE36_gd.js +0 -1
  267. package/dist/web/assets/mobile-bottom-sheet-CJ7ApOkI.js +0 -1
  268. package/dist/web/assets/mobile-explorer-sheet-BzjlS1Xn.js +0 -1
  269. package/dist/web/assets/notification-store-C7UBqxYP.js +0 -1
  270. package/dist/web/assets/packet-RMMSAZCW-DQUxN0wU.js +0 -1
  271. package/dist/web/assets/pie-UPGHQEXC-BOr9ek4Q.js +0 -1
  272. package/dist/web/assets/process-table-CXC10GSy.js +0 -1
  273. package/dist/web/assets/radar-KQ55EAFF-DSErxZFb.js +0 -1
  274. package/dist/web/assets/settings-store-OLJE7dPH.js +0 -2
  275. package/dist/web/assets/settings-tab-B1rvHFc6.js +0 -3
  276. package/dist/web/assets/settings-tab-BUlmPJ2w.js +0 -1
  277. package/dist/web/assets/system-monitor-body-DPYQlBEt.js +0 -2
  278. package/dist/web/assets/system-monitor-tab-CPQdWv27.js +0 -1
  279. package/dist/web/assets/team-member-sheet-Bu2wG1sm.js +0 -2
  280. package/dist/web/assets/treemap-KZPCXAKY-C17p0LQw.js +0 -1
  281. package/dist/web/assets/window-store-CQ1lghvv.js +0 -1
  282. /package/dist/web/assets/{abap-CjzrhAS2.js → abap-C5DFF0s3.js} +0 -0
  283. /package/dist/web/assets/{actionscript-3-B59Kbl-9.js → actionscript-3-B0qyRNbc.js} +0 -0
  284. /package/dist/web/assets/{ada-B7gItKAE.js → ada-bAJzplug.js} +0 -0
  285. /package/dist/web/assets/{andromeeda-Dy5zcy0Q.js → andromeeda-DUCpISy6.js} +0 -0
  286. /package/dist/web/assets/{apache-B6T9jrc0.js → apache-BhAr84Z_.js} +0 -0
  287. /package/dist/web/assets/{apex-CTsIxDQw.js → apex-CRv8YnNq.js} +0 -0
  288. /package/dist/web/assets/{api-client-oY1tC3tM.js → api-client-BzWM7XPR.js} +0 -0
  289. /package/dist/web/assets/{applescript-DuycKmmb.js → applescript-CD-Cw8bt.js} +0 -0
  290. /package/dist/web/assets/{apply-theme-r8tKDrCR.js → apply-theme-DJ794zSQ.js} +0 -0
  291. /package/dist/web/assets/{ara-CISL3599.js → ara-BUzeQYoG.js} +0 -0
  292. /package/dist/web/assets/{asciidoc-BXyE2Lpr.js → asciidoc-CT2hTg-v.js} +0 -0
  293. /package/dist/web/assets/{asm-y258phtP.js → asm-CAu35JeY.js} +0 -0
  294. /package/dist/web/assets/{aurora-x-Dq3Jc78M.js → aurora-x-BeFJoIA1.js} +0 -0
  295. /package/dist/web/assets/{awk-DxL6XSJv.js → awk-Vau-DrGN.js} +0 -0
  296. /package/dist/web/assets/{ayu-dark-DjoK6Evo.js → ayu-dark-CLe-OTsQ.js} +0 -0
  297. /package/dist/web/assets/{ayu-light-CKKXBLMI.js → ayu-light-CJbQfu4M.js} +0 -0
  298. /package/dist/web/assets/{ayu-mirage-DGXSrrXB.js → ayu-mirage-GWaKwdrx.js} +0 -0
  299. /package/dist/web/assets/{ballerina-DZSXFmUf.js → ballerina-DWu0USI5.js} +0 -0
  300. /package/dist/web/assets/{bat-BNsZk6nY.js → bat-_07Huw7E.js} +0 -0
  301. /package/dist/web/assets/{beancount-cPl4gJct.js → beancount-EcvogrcF.js} +0 -0
  302. /package/dist/web/assets/{berry-DGAodERk.js → berry-BBB87SY7.js} +0 -0
  303. /package/dist/web/assets/{bibtex-DftVoRif.js → bibtex-DbueS87A.js} +0 -0
  304. /package/dist/web/assets/{bicep-Suioyyzh.js → bicep-tcmXnUo4.js} +0 -0
  305. /package/dist/web/assets/{bird2-DjU24I2P.js → bird2-B2qwilIR.js} +0 -0
  306. /package/dist/web/assets/{c-DFi8Y_nN.js → c-Z58oivSt.js} +0 -0
  307. /package/dist/web/assets/{c3-1eSHnH5V.js → c3-U40cGT9p.js} +0 -0
  308. /package/dist/web/assets/{cadence-DLLsFwQP.js → cadence-BFcD-LyM.js} +0 -0
  309. /package/dist/web/assets/{can-open-in-ppm-in727eFQ.js → can-open-in-ppm-CdCK2c2a.js} +0 -0
  310. /package/dist/web/assets/{catppuccin-frappe-DDj0JEiY.js → catppuccin-frappe-RkC9AX6J.js} +0 -0
  311. /package/dist/web/assets/{catppuccin-latte-DPyANHyR.js → catppuccin-latte-Bsi4l-nO.js} +0 -0
  312. /package/dist/web/assets/{catppuccin-macchiato-CO-h8VTT.js → catppuccin-macchiato-B8JIdTW-.js} +0 -0
  313. /package/dist/web/assets/{catppuccin-mocha-BCmbN2IZ.js → catppuccin-mocha-oH8wwrDj.js} +0 -0
  314. /package/dist/web/assets/{clarity-C0pYsZjs.js → clarity-C3WNeRCR.js} +0 -0
  315. /package/dist/web/assets/{clipboard-DBLGKMrH.js → clipboard-B-gwJ8DF.js} +0 -0
  316. /package/dist/web/assets/{clojure-JFr6s8iZ.js → clojure-CFoAz7u9.js} +0 -0
  317. /package/dist/web/assets/{cmake-EEcLEgVy.js → cmake-Bcsqp9yR.js} +0 -0
  318. /package/dist/web/assets/{codeowners-Deh3Lx0u.js → codeowners-DC2Z7heP.js} +0 -0
  319. /package/dist/web/assets/{codeql-C317igT5.js → codeql-BfG68Al5.js} +0 -0
  320. /package/dist/web/assets/{common-lisp-DepbATZs.js → common-lisp-Z5P-lKQe.js} +0 -0
  321. /package/dist/web/assets/{coq-MxgxU5ya.js → coq-BJslqrnv.js} +0 -0
  322. /package/dist/web/assets/{csharp-eEFXdsAq.js → csharp-DhhZTOTs.js} +0 -0
  323. /package/dist/web/assets/{css-Db-syWW9.js → css-DiFIpMfE.js} +0 -0
  324. /package/dist/web/assets/{csv-DHeYcbaq.js → csv-DViLbHxI.js} +0 -0
  325. /package/dist/web/assets/{csv-parser-_JWMLGl3.js → csv-parser-B2v8MoUy.js} +0 -0
  326. /package/dist/web/assets/{cue-BmUNowrt.js → cue-CoG3mg2I.js} +0 -0
  327. /package/dist/web/assets/{cypher-XjXHgCNS.js → cypher-jTxC8NAB.js} +0 -0
  328. /package/dist/web/assets/{d-BWJnZtig.js → d-6hKIUAbU.js} +0 -0
  329. /package/dist/web/assets/{dark-plus-Db7umas1.js → dark-plus-D_-np2Sr.js} +0 -0
  330. /package/dist/web/assets/{dart-DSI4HYh1.js → dart-CVpxLpVn.js} +0 -0
  331. /package/dist/web/assets/{data-grid-types-CfVHh9gv.js → data-grid-types-Dq_H1IPa.js} +0 -0
  332. /package/dist/web/assets/{dax-yw1JsAni.js → dax-BPqJ8qIY.js} +0 -0
  333. /package/dist/web/assets/{desktop-C2QO4RvD.js → desktop-u_y5_Hr5.js} +0 -0
  334. /package/dist/web/assets/{diff-Drt8aEyp.js → diff-Duj1XMrd.js} +0 -0
  335. /package/dist/web/assets/{dist-DNRvhVdz.js → dist-CEZ4plgH.js} +0 -0
  336. /package/dist/web/assets/{dist-VBxOHLmN.js → dist-aDSxTR_v.js} +0 -0
  337. /package/dist/web/assets/{docker-ByDKbmLl.js → docker-8UGSCc7N.js} +0 -0
  338. /package/dist/web/assets/{dotenv-BkfOLytR.js → dotenv-DVnj8kyR.js} +0 -0
  339. /package/dist/web/assets/{dracula-DAmrm-Un.js → dracula-D0cEmSg7.js} +0 -0
  340. /package/dist/web/assets/{dracula-soft-BUOEnKqR.js → dracula-soft-DfGS5Yfr.js} +0 -0
  341. /package/dist/web/assets/{dream-maker-DQBIfSiO.js → dream-maker-qG8kMGTF.js} +0 -0
  342. /package/dist/web/assets/{emacs-lisp-Blh5kBD3.js → emacs-lisp-iVqyMLyT.js} +0 -0
  343. /package/dist/web/assets/{everforest-dark-CEzhDxHq.js → everforest-dark-Cruygd5U.js} +0 -0
  344. /package/dist/web/assets/{everforest-light-CPk_5Khm.js → everforest-light-BiJa8RHL.js} +0 -0
  345. /package/dist/web/assets/{fennel-B1MT_EBZ.js → fennel-H6XSEIoz.js} +0 -0
  346. /package/dist/web/assets/{fish-C_HGcewt.js → fish-ho_7CxX6.js} +0 -0
  347. /package/dist/web/assets/{fluent-Dp1Iclmx.js → fluent-1l5ssJTF.js} +0 -0
  348. /package/dist/web/assets/{format-bytes-53WBtyi2.js → format-bytes-gQw5wzqa.js} +0 -0
  349. /package/dist/web/assets/{fortran-free-form-B_-UA4XC.js → fortran-free-form-Bo1x233A.js} +0 -0
  350. /package/dist/web/assets/{gdscript-Bm_JpjS_.js → gdscript-DffFMp_k.js} +0 -0
  351. /package/dist/web/assets/{gdshader-C_PNmfYU.js → gdshader-CvnpAiAF.js} +0 -0
  352. /package/dist/web/assets/{genie-B5rmR2TF.js → genie-2eOvAEzW.js} +0 -0
  353. /package/dist/web/assets/{gherkin-BTJ34TJ9.js → gherkin-DL-5OUlV.js} +0 -0
  354. /package/dist/web/assets/{github-dark-DOkNVj9H.js → github-dark-DrTZprgG.js} +0 -0
  355. /package/dist/web/assets/{github-dark-default-DTsmiyVo.js → github-dark-default-_mDH8IMP.js} +0 -0
  356. /package/dist/web/assets/{github-dark-dimmed-CjbxAe9s.js → github-dark-dimmed-C48hJuLn.js} +0 -0
  357. /package/dist/web/assets/{github-dark-high-contrast-r_M8F_k7.js → github-dark-high-contrast-BQ-zGGNe.js} +0 -0
  358. /package/dist/web/assets/{github-light-BzfxyMyb.js → github-light-BYc90Yx8.js} +0 -0
  359. /package/dist/web/assets/{github-light-default-BOqiBIy5.js → github-light-default-Daecj4J5.js} +0 -0
  360. /package/dist/web/assets/{github-light-high-contrast-DQzBrDM4.js → github-light-high-contrast-DoafCbsU.js} +0 -0
  361. /package/dist/web/assets/{gleam-BWmuAtD8.js → gleam-hUWWjmnp.js} +0 -0
  362. /package/dist/web/assets/{gn-CNQPjiZW.js → gn-7PJ3R70o.js} +0 -0
  363. /package/dist/web/assets/{gnuplot-BlHjcPMe.js → gnuplot-V-Pxck7b.js} +0 -0
  364. /package/dist/web/assets/{go-Bn8Ba9uz.js → go-ARF6DprD.js} +0 -0
  365. /package/dist/web/assets/{groovy-C1Y1_F4V.js → groovy-CKGBIPOz.js} +0 -0
  366. /package/dist/web/assets/{gruvbox-dark-hard-DueOsHav.js → gruvbox-dark-hard-Cm9wqdI3.js} +0 -0
  367. /package/dist/web/assets/{gruvbox-dark-medium-DaQjD538.js → gruvbox-dark-medium-Cmsd6dG8.js} +0 -0
  368. /package/dist/web/assets/{gruvbox-dark-soft-CbbX2WOm.js → gruvbox-dark-soft-BBGlGGCz.js} +0 -0
  369. /package/dist/web/assets/{gruvbox-light-hard-E2M70ljZ.js → gruvbox-light-hard-BCR7KmqG.js} +0 -0
  370. /package/dist/web/assets/{gruvbox-light-medium-B_DDLY6h.js → gruvbox-light-medium-CP32bga8.js} +0 -0
  371. /package/dist/web/assets/{gruvbox-light-soft-2SZmyC1F.js → gruvbox-light-soft-qjxk8ps6.js} +0 -0
  372. /package/dist/web/assets/{haskell-D8DeoXtG.js → haskell-C5DzwGRa.js} +0 -0
  373. /package/dist/web/assets/{haxe-BmZm7B0W.js → haxe-C8f_jCVv.js} +0 -0
  374. /package/dist/web/assets/{hcl-CKnBM3ng.js → hcl-DetJkf6E.js} +0 -0
  375. /package/dist/web/assets/{hjson-Ct9t4-ci.js → hjson-DeZhwIdo.js} +0 -0
  376. /package/dist/web/assets/{hlsl-BZSASBqS.js → hlsl-SMIDLRyX.js} +0 -0
  377. /package/dist/web/assets/{horizon-CzX1AZ08.js → horizon-BMAVDxMx.js} +0 -0
  378. /package/dist/web/assets/{horizon-bright-CCVNeRIP.js → horizon-bright-3BaV_VGD.js} +0 -0
  379. /package/dist/web/assets/{houston-DBlJN7xc.js → houston-BWBbEPvb.js} +0 -0
  380. /package/dist/web/assets/{hy-Dyc6h6Xb.js → hy-DcsmE-Nm.js} +0 -0
  381. /package/dist/web/assets/{image-gallery-DujlrjOC.js → image-gallery-Bqb-wjNw.js} +0 -0
  382. /package/dist/web/assets/{imba-Bauru7s2.js → imba-CnkgnYMf.js} +0 -0
  383. /package/dist/web/assets/{ini-SjdwtaTp.js → ini-BnvTdau7.js} +0 -0
  384. /package/dist/web/assets/{java-D2r0ocpf.js → java-BLFACXat.js} +0 -0
  385. /package/dist/web/assets/{javascript-y-lof7iP.js → javascript-BKcFWENk.js} +0 -0
  386. /package/dist/web/assets/{json-DO4LSx5A.js → json-3AJtJADf.js} +0 -0
  387. /package/dist/web/assets/{json5-BEwWf-W3.js → json5-DLyDVlwe.js} +0 -0
  388. /package/dist/web/assets/{jsonc-OBEFv7gP.js → jsonc-B5DgF1jW.js} +0 -0
  389. /package/dist/web/assets/{jsonl-DOyW4Mg3.js → jsonl-CV_O71a2.js} +0 -0
  390. /package/dist/web/assets/{jsonnet-BL5R2CpP.js → jsonnet-DZfPLgYz.js} +0 -0
  391. /package/dist/web/assets/{jssm-ksPusAyg.js → jssm-Cr1zYOsY.js} +0 -0
  392. /package/dist/web/assets/{jsx-B6XEu6O-.js → jsx-VN-JS2_q.js} +0 -0
  393. /package/dist/web/assets/{kanagawa-dragon-JBVe_ji_.js → kanagawa-dragon-D9CcT3xg.js} +0 -0
  394. /package/dist/web/assets/{kanagawa-lotus-ChprO68_.js → kanagawa-lotus-DLXsB8fr.js} +0 -0
  395. /package/dist/web/assets/{kanagawa-wave-D_uB_R0Q.js → kanagawa-wave-weDs3Wjz.js} +0 -0
  396. /package/dist/web/assets/{kdl-pf5B8N_m.js → kdl-0XF56jnT.js} +0 -0
  397. /package/dist/web/assets/{kotlin-BdF0METC.js → kotlin-BPkH9vzk.js} +0 -0
  398. /package/dist/web/assets/{kusto-BneVP-OU.js → kusto-DMVpADb9.js} +0 -0
  399. /package/dist/web/assets/{laserwave-C4-UPIyG.js → laserwave-Cl6T2cGP.js} +0 -0
  400. /package/dist/web/assets/{lean-BN6Jdymq.js → lean-BvwzQGl1.js} +0 -0
  401. /package/dist/web/assets/{less-Cx-fdlDC.js → less-DI_03xkC.js} +0 -0
  402. /package/dist/web/assets/{lib-CMuBa85c.js → lib-v9x_qXgI.js} +0 -0
  403. /package/dist/web/assets/{libesm-BB74PRsH.js → libesm-dcbw4_18.js} +0 -0
  404. /package/dist/web/assets/{light-plus-DFnnn_iL.js → light-plus-hIiIsQN_.js} +0 -0
  405. /package/dist/web/assets/{llvm-BGxtW0Nw.js → llvm-B5hjjSWc.js} +0 -0
  406. /package/dist/web/assets/{loader-circle-Dpqcv0eS.js → loader-circle-DouKgIXy.js} +0 -0
  407. /package/dist/web/assets/{lock-DldbT31x.js → lock-CNxUGpTk.js} +0 -0
  408. /package/dist/web/assets/{log-kQb4eJnj.js → log-D28raQiE.js} +0 -0
  409. /package/dist/web/assets/{logo-DqAzaq7s.js → logo-6Cdd4ASb.js} +0 -0
  410. /package/dist/web/assets/{luau-Dc2cp-zS.js → luau-CK9nKpOX.js} +0 -0
  411. /package/dist/web/assets/{make-DYg9dwaJ.js → make-CVnxKCB6.js} +0 -0
  412. /package/dist/web/assets/{markdown-O4EPpS3K.js → markdown-B9mMQzD5.js} +0 -0
  413. /package/dist/web/assets/{material-theme-ClP8yfQ2.js → material-theme-BZSs2o0j.js} +0 -0
  414. /package/dist/web/assets/{material-theme-darker-LOK55hGu.js → material-theme-darker-BeAQBzQ_.js} +0 -0
  415. /package/dist/web/assets/{material-theme-lighter-j72tzZqi.js → material-theme-lighter-BAe0IXcs.js} +0 -0
  416. /package/dist/web/assets/{material-theme-ocean-C19LKClA.js → material-theme-ocean-CiPIf0Np.js} +0 -0
  417. /package/dist/web/assets/{material-theme-palenight-DJ5tN2ax.js → material-theme-palenight-DJBb4_XN.js} +0 -0
  418. /package/dist/web/assets/{matlab-DQNZ_NU8.js → matlab-CY0dbp_s.js} +0 -0
  419. /package/dist/web/assets/{mdx-CJayB7XK.js → mdx-DU9zbF7h.js} +0 -0
  420. /package/dist/web/assets/{mermaid-Cb0TRX92.js → mermaid-UmmjGwdh.js} +0 -0
  421. /package/dist/web/assets/{min-dark-CWM8Bo-m.js → min-dark-C59GkzOm.js} +0 -0
  422. /package/dist/web/assets/{min-light-BBR1sjS2.js → min-light-B9QglrdQ.js} +0 -0
  423. /package/dist/web/assets/{mipsasm-Jc7u7YSS.js → mipsasm-Dc6Jsr_D.js} +0 -0
  424. /package/dist/web/assets/{mojo-CTB0cutd.js → mojo-D5ij-nGW.js} +0 -0
  425. /package/dist/web/assets/{monitor-4vWfqzuN.js → monitor-CxPV_fQI.js} +0 -0
  426. /package/dist/web/assets/{monokai-CvM_YNKx.js → monokai-QAtE_yCP.js} +0 -0
  427. /package/dist/web/assets/{moonbit-CbFEfYYM.js → moonbit-BMtIdiTL.js} +0 -0
  428. /package/dist/web/assets/{move-BT38p0kT.js → move-BB_jMHYZ.js} +0 -0
  429. /package/dist/web/assets/{narrat-55QZ_kOG.js → narrat-BvvoKGVn.js} +0 -0
  430. /package/dist/web/assets/{nextflow-groovy-Bg8peyqg.js → nextflow-groovy-CqxtAsnF.js} +0 -0
  431. /package/dist/web/assets/{night-owl-DzS77iPK.js → night-owl-CLoE9kjt.js} +0 -0
  432. /package/dist/web/assets/{night-owl-light-DER7fGnx.js → night-owl-light-BwIFT5Xd.js} +0 -0
  433. /package/dist/web/assets/{nix-BSI8YesN.js → nix-BKlnVT9r.js} +0 -0
  434. /package/dist/web/assets/{nord-9bn7iNWo.js → nord-B7R1KRAK.js} +0 -0
  435. /package/dist/web/assets/{nushell-qDVZn_lw.js → nushell-CzUme-NJ.js} +0 -0
  436. /package/dist/web/assets/{objective-c-Blf-ATos.js → objective-c-D-UKKiOc.js} +0 -0
  437. /package/dist/web/assets/{objective-cpp-Bz8bAq47.js → objective-cpp-BucWn4Fx.js} +0 -0
  438. /package/dist/web/assets/{ocaml-B9heAsc3.js → ocaml-EJCNXMGo.js} +0 -0
  439. /package/dist/web/assets/{odin-BaFYnOwO.js → odin-CEjnRU_3.js} +0 -0
  440. /package/dist/web/assets/{one-dark-pro-D6B_hSe-.js → one-dark-pro-Bv469NX5.js} +0 -0
  441. /package/dist/web/assets/{one-light-Ba-f0wyk.js → one-light-BlzO5RWI.js} +0 -0
  442. /package/dist/web/assets/{openscad-UvYyL2MK.js → openscad-Czl6alLw.js} +0 -0
  443. /package/dist/web/assets/{pascal-B4-qvEoG.js → pascal-BI9fLvG4.js} +0 -0
  444. /package/dist/web/assets/{pkl-B146nqGx.js → pkl-ChQLsyjU.js} +0 -0
  445. /package/dist/web/assets/{plastic-L38t-Id3.js → plastic-DGL3mtdL.js} +0 -0
  446. /package/dist/web/assets/{play-CpCAoQRZ.js → play-DpEEBJTT.js} +0 -0
  447. /package/dist/web/assets/{plsql-ItOG5Cca.js → plsql-BJDjqcWp.js} +0 -0
  448. /package/dist/web/assets/{po-imiCTaNX.js → po-DK5O3OpF.js} +0 -0
  449. /package/dist/web/assets/{poimandres-Bu6sIba9.js → poimandres-CnuqZT4T.js} +0 -0
  450. /package/dist/web/assets/{polar-CTeGoFxV.js → polar-DCsUxSsQ.js} +0 -0
  451. /package/dist/web/assets/{portal-container-context-a399Hd9F.js → portal-container-context-D-495m5C.js} +0 -0
  452. /package/dist/web/assets/{postcss-jvFe6GNE.js → postcss-8UjdFmis.js} +0 -0
  453. /package/dist/web/assets/{powerquery-D9Dw1hJp.js → powerquery-CAp0CGQj.js} +0 -0
  454. /package/dist/web/assets/{powershell-CZqvYQtg.js → powershell-CbBsKZJP.js} +0 -0
  455. /package/dist/web/assets/{prisma-B4D2nO_7.js → prisma-BnNpXCJN.js} +0 -0
  456. /package/dist/web/assets/{prolog-CQbyUcCF.js → prolog-CC-l9E8Y.js} +0 -0
  457. /package/dist/web/assets/{proto-Cwpo0cW1.js → proto-CV_1k2rL.js} +0 -0
  458. /package/dist/web/assets/{puppet-CKxCwBod.js → puppet-2D8HU2bv.js} +0 -0
  459. /package/dist/web/assets/{purescript-B-MyXMMp.js → purescript-BEJAORgU.js} +0 -0
  460. /package/dist/web/assets/{python-D6QbSR2S.js → python-BBdbb7nn.js} +0 -0
  461. /package/dist/web/assets/{qmldir-BpTSVcw_.js → qmldir-D5ZYI4Ie.js} +0 -0
  462. /package/dist/web/assets/{qss-DBc4gbpe.js → qss-BOJVGyXE.js} +0 -0
  463. /package/dist/web/assets/{r-i9jMMi0s.js → r-B5dlEtkj.js} +0 -0
  464. /package/dist/web/assets/{racket-BWNaxU7C.js → racket-BUAEygJ6.js} +0 -0
  465. /package/dist/web/assets/{raku-fqTZLn7L.js → raku-BG0Ss0ID.js} +0 -0
  466. /package/dist/web/assets/{react-X4jUntEf.js → react-B549sK_T.js} +0 -0
  467. /package/dist/web/assets/{red-D-JEcuzA.js → red-C5yyQbtG.js} +0 -0
  468. /package/dist/web/assets/{refresh-cw-B9iv7csb.js → refresh-cw-BQU5g2CY.js} +0 -0
  469. /package/dist/web/assets/{reg-BbirlU1S.js → reg-DHoBWtLf.js} +0 -0
  470. /package/dist/web/assets/{regexp-D8VTFrtg.js → regexp-DDtJrlKv.js} +0 -0
  471. /package/dist/web/assets/{rel-ByQGKiNM.js → rel-CR6WJDRD.js} +0 -0
  472. /package/dist/web/assets/{resolve-theme-Cqb9zWYc.js → resolve-theme-4fexHWl6.js} +0 -0
  473. /package/dist/web/assets/{riscv-B5nqCfLE.js → riscv-2V9tC-nx.js} +0 -0
  474. /package/dist/web/assets/{ron-DOVh_iZR.js → ron-QLTmqGUw.js} +0 -0
  475. /package/dist/web/assets/{rose-pine-jp51ZUzi.js → rose-pine-be0__X3A.js} +0 -0
  476. /package/dist/web/assets/{rose-pine-dawn-B7hkmmWy.js → rose-pine-dawn-n_WQJwsW.js} +0 -0
  477. /package/dist/web/assets/{rose-pine-moon-Ff063yLj.js → rose-pine-moon-t2aLtzgB.js} +0 -0
  478. /package/dist/web/assets/{rosmsg-Cyk7Il8z.js → rosmsg-Ce1JD8vt.js} +0 -0
  479. /package/dist/web/assets/{rotate-ccw-DnuPzJRE.js → rotate-ccw-CpjRo90J.js} +0 -0
  480. /package/dist/web/assets/{rust-DRNM5epF.js → rust-5fHH-ZoW.js} +0 -0
  481. /package/dist/web/assets/{sass-D_cB0-M1.js → sass-DPmGFiOJ.js} +0 -0
  482. /package/dist/web/assets/{save-DxonD4y1.js → save-DYLiVzU8.js} +0 -0
  483. /package/dist/web/assets/{scala-Crl-1kKS.js → scala-BRMVFbZF.js} +0 -0
  484. /package/dist/web/assets/{scheme-DCaWyEJ0.js → scheme-BMSJWevF.js} +0 -0
  485. /package/dist/web/assets/{sdbl-BbXNqwtX.js → sdbl-DTZ-pEHI.js} +0 -0
  486. /package/dist/web/assets/{send-B-t5Mz26.js → send-C_sLFt56.js} +0 -0
  487. /package/dist/web/assets/{shallow-4zAfs2KW.js → shallow-CmPMAPte.js} +0 -0
  488. /package/dist/web/assets/{shellscript-BAgksBhW.js → shellscript-6-9yg5Cz.js} +0 -0
  489. /package/dist/web/assets/{shield-check-CdTiK1j2.js → shield-check-CUI36aSc.js} +0 -0
  490. /package/dist/web/assets/{shield-off-DuetkXKM.js → shield-off-GdkkVhb3.js} +0 -0
  491. /package/dist/web/assets/{slack-dark-GB-ElUW_.js → slack-dark-DKJgiR7e.js} +0 -0
  492. /package/dist/web/assets/{slack-ochin-tU6CFD3S.js → slack-ochin-WjehVgyK.js} +0 -0
  493. /package/dist/web/assets/{smalltalk-DSVGUbtL.js → smalltalk-BChf4SN1.js} +0 -0
  494. /package/dist/web/assets/{snazzy-light-DZhi3PoS.js → snazzy-light-C1WAwOx6.js} +0 -0
  495. /package/dist/web/assets/{solarized-dark-D6AMCco7.js → solarized-dark-BxREZrGx.js} +0 -0
  496. /package/dist/web/assets/{solarized-light-DepuTWSE.js → solarized-light-DG29B1pR.js} +0 -0
  497. /package/dist/web/assets/{solidity-CsxN618l.js → solidity-bDbtGwdR.js} +0 -0
  498. /package/dist/web/assets/{splunk-CF1m0Kkv.js → splunk-Ceqx5njI.js} +0 -0
  499. /package/dist/web/assets/{sql-DvyMDgrB.js → sql-OwMEKbz8.js} +0 -0
  500. /package/dist/web/assets/{ssh-config-a8sDo-Q8.js → ssh-config-WAR3lu2k.js} +0 -0
  501. /package/dist/web/assets/{stylus-DCJ-6AjT.js → stylus-Cjmoz3d8.js} +0 -0
  502. /package/dist/web/assets/{swift-c-ZFxyPP.js → swift-S_ZSBG1q.js} +0 -0
  503. /package/dist/web/assets/{synthwave-84-9NPgBcuv.js → synthwave-84-DiUeWMSh.js} +0 -0
  504. /package/dist/web/assets/{system-verilog-DPD02IOo.js → system-verilog-RRjqPjUV.js} +0 -0
  505. /package/dist/web/assets/{systemd-BUr5HRtU.js → systemd-BD3SkXoU.js} +0 -0
  506. /package/dist/web/assets/{table-FLU8ei8b.js → table-D5Q4ap4W.js} +0 -0
  507. /package/dist/web/assets/{talonscript-BNBQV5YJ.js → talonscript-DWefISOm.js} +0 -0
  508. /package/dist/web/assets/{tasl-BETjpSyu.js → tasl-DCjSS-09.js} +0 -0
  509. /package/dist/web/assets/{tcl-Clp_AZiC.js → tcl-DTYqT_66.js} +0 -0
  510. /package/dist/web/assets/{terraform-B0kZ0lim.js → terraform-CkbP5QnR.js} +0 -0
  511. /package/dist/web/assets/{text-wrap-B05VGRa-.js → text-wrap-DBvQD3kc.js} +0 -0
  512. /package/dist/web/assets/{tokyo-night-oOJ0a7Dk.js → tokyo-night-qVSqO9xy.js} +0 -0
  513. /package/dist/web/assets/{toml-DOPCSumy.js → toml-D9F5Osqs.js} +0 -0
  514. /package/dist/web/assets/{trash-2-CTXlTflF.js → trash-2-By4VMbC4.js} +0 -0
  515. /package/dist/web/assets/{triangle-alert-BRq0VAfd.js → triangle-alert-CUTURFbG.js} +0 -0
  516. /package/dist/web/assets/{tsv-D6qUGf84.js → tsv-C5tpHoxm.js} +0 -0
  517. /package/dist/web/assets/{tsx-B6qYhT6O.js → tsx-CMWkGtGm.js} +0 -0
  518. /package/dist/web/assets/{turtle-C0Rjzw73.js → turtle-Cn4z3xOi.js} +0 -0
  519. /package/dist/web/assets/{typescript-BiydShEu.js → typescript-DSyAEWsn.js} +0 -0
  520. /package/dist/web/assets/{typespec-DjmF6Aww.js → typespec-B93Qap7y.js} +0 -0
  521. /package/dist/web/assets/{typst-CPuscfcL.js → typst-DtlOmFlX.js} +0 -0
  522. /package/dist/web/assets/{use-is-mobile-b94q8RvF.js → use-is-mobile-B9UZNwSm.js} +0 -0
  523. /package/dist/web/assets/{utils-C9OjCrou.js → utils-QHe_Q6UF.js} +0 -0
  524. /package/dist/web/assets/{v-DE4vpLLQ.js → v-Dfzf-szS.js} +0 -0
  525. /package/dist/web/assets/{vala-CmUF2pS5.js → vala-CyiQZQ9f.js} +0 -0
  526. /package/dist/web/assets/{vb-BvcRoX4b.js → vb-C7DGSldd.js} +0 -0
  527. /package/dist/web/assets/{vendor-xterm-K8kpLxdb.js → vendor-xterm-BHKF_Nsf.js} +0 -0
  528. /package/dist/web/assets/{verilog-BGIzyH3R.js → verilog-kF-hClY2.js} +0 -0
  529. /package/dist/web/assets/{vesper-CG7IrHQQ.js → vesper-BmKcADuz.js} +0 -0
  530. /package/dist/web/assets/{vhdl-DMSumomw.js → vhdl-CDnb16KJ.js} +0 -0
  531. /package/dist/web/assets/{viml-Drt7AEmU.js → viml-B8cs6Ady.js} +0 -0
  532. /package/dist/web/assets/{vitesse-black-DmsjfN0b.js → vitesse-black-BR6o9Gto.js} +0 -0
  533. /package/dist/web/assets/{vitesse-dark-Dl55_WeJ.js → vitesse-dark-Dye14-KB.js} +0 -0
  534. /package/dist/web/assets/{vitesse-light-Crbv9aGg.js → vitesse-light-BLpswIxf.js} +0 -0
  535. /package/dist/web/assets/{vyper-CB7psEJj.js → vyper-DU51Z97W.js} +0 -0
  536. /package/dist/web/assets/{wasm-BY1tuM5m.js → wasm-CVqlJAR2.js} +0 -0
  537. /package/dist/web/assets/{wasm-Dck8KPgP.js → wasm-ChtccNfv.js} +0 -0
  538. /package/dist/web/assets/{wenyan-DhewQ68b.js → wenyan-DhCa3hCn.js} +0 -0
  539. /package/dist/web/assets/{wgsl-DlmiP456.js → wgsl-Bj1Ec4vv.js} +0 -0
  540. /package/dist/web/assets/{wikitext-cQCnqNgE.js → wikitext-C90DHNa0.js} +0 -0
  541. /package/dist/web/assets/{wit-S5MaCngB.js → wit-DUgbOD7Z.js} +0 -0
  542. /package/dist/web/assets/{wolfram-Bh8n_9zr.js → wolfram-BgwUqD1I.js} +0 -0
  543. /package/dist/web/assets/{x-YDu4m2Iq.js → x-DiOUR_mp.js} +0 -0
  544. /package/dist/web/assets/{yaml-BoK_17l9.js → yaml-DEQulj-8.js} +0 -0
  545. /package/dist/web/assets/{zenscript-DB3ZFwNJ.js → zenscript-C7Jp7TgT.js} +0 -0
  546. /package/dist/web/assets/{zig-Bv1BWmfT.js → zig-9x4qXYLg.js} +0 -0
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Full-screen mobile presentation of the Remote Desktop viewer — `WindowLayer` renders nothing
3
+ * below `md`, so this is what mobile opens instead of the desktop floating window (see
4
+ * `open-remote-desktop.ts`). Self-gated singleton, mounted once at the app root beside the
5
+ * other mobile overlays (`MobileExplorerSheet`, `TeamMemberSheet`) — renders nothing until
6
+ * `useRemoteDesktopMobileOpenState().open()` is called.
7
+ *
8
+ * Deliberately NOT the shared `BottomSheet`: that component tracks `visualViewport` and shrinks
9
+ * its panel to leave room above the on-screen keyboard (right for a chat input you need visible
10
+ * next to the keyboard), but this viewer's "keyboard" is just a hidden input forwarding
11
+ * keystrokes — nothing here needs to stay visible above it, and shrinking the canvas viewport
12
+ * every time the toolbar's Keyboard button is tapped is exactly the wrong behavior. It also
13
+ * wires swipe-to-dismiss on touchstart/move/end, which fights the gesture engine's own
14
+ * `stopPropagation()`-based touch handling. A plain `fixed inset-0` portal sidesteps both —
15
+ * this stays full-viewport no matter what the keyboard does; `remote-desktop-mobile-view.tsx`
16
+ * reads `visualViewport` itself and floats just its toolbar/key-bar row above the keyboard.
17
+ *
18
+ * The heavy viewer (canvas, WebCodecs decoder, gesture engine) is a nested lazy import, same as
19
+ * `TeamMemberSheet` does for its content — nothing here loads until the sheet actually opens,
20
+ * and the lazy chunk itself sits behind `RemoteDesktopWarningGate`, so it isn't even fetched
21
+ * until the user continues past the warning.
22
+ */
23
+ import { Suspense, lazy } from "react";
24
+ import { createPortal } from "react-dom";
25
+ import { usePortalContainer } from "@/components/ui/portal-container-context";
26
+ import { useRemoteDesktopMobileOpenState } from "./use-remote-desktop-mobile-open-state";
27
+ import { RemoteDesktopWarningGate } from "./remote-desktop-warning-gate";
28
+
29
+ const RemoteDesktopMobileView = lazy(() => import("./remote-desktop-mobile-view"));
30
+
31
+ export function RemoteDesktopMobileSheet() {
32
+ const isOpen = useRemoteDesktopMobileOpenState((s) => s.isOpen);
33
+ const close = useRemoteDesktopMobileOpenState((s) => s.close);
34
+ const portalContainer = usePortalContainer();
35
+
36
+ if (!isOpen) return null;
37
+
38
+ return createPortal(
39
+ <div className="fixed inset-0 z-[45] bg-black" data-testid="remote-desktop-mobile-sheet">
40
+ <RemoteDesktopWarningGate onCancel={close}>
41
+ <Suspense fallback={<div className="flex h-full items-center justify-center text-sm text-text-2">Loading…</div>}>
42
+ <RemoteDesktopMobileView onClose={close} />
43
+ </Suspense>
44
+ </RemoteDesktopWarningGate>
45
+ </div>,
46
+ portalContainer ?? document.getElementById("root") ?? document.body,
47
+ );
48
+ }
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Bottom (thumb-zone) toolbar for the mobile remote-desktop viewer: Touch/Mouse mode toggle,
3
+ * virtual keyboard, zoom reset, close. Every button is a 44px+ touch target per
4
+ * `docs/design-guidelines.md`'s Mobile-First UI Rules.
5
+ */
6
+ import { Hand, MousePointer2, Keyboard, ZoomOut, Gauge, X } from "lucide-react";
7
+ import { cn } from "@/lib/utils";
8
+ import { useSettingsStore } from "@/stores/settings-store";
9
+ import type { RemoteDesktopInputMode } from "./use-remote-desktop-touch";
10
+
11
+ export interface RemoteDesktopMobileToolbarProps {
12
+ mode: RemoteDesktopInputMode;
13
+ onToggleMode: () => void;
14
+ onOpenKeyboard: () => void;
15
+ onResetZoom: () => void;
16
+ onClose: () => void;
17
+ }
18
+
19
+ function ToolbarButton({
20
+ onClick,
21
+ label,
22
+ active,
23
+ children,
24
+ }: {
25
+ onClick: () => void;
26
+ label: string;
27
+ active?: boolean;
28
+ children: React.ReactNode;
29
+ }) {
30
+ return (
31
+ <button
32
+ type="button"
33
+ onClick={onClick}
34
+ aria-label={label}
35
+ className={cn(
36
+ "flex min-h-11 min-w-11 flex-1 flex-col items-center justify-center gap-0.5 rounded-lg py-1.5 text-[10px]",
37
+ "text-white/80 active:bg-white/10 transition-colors",
38
+ active && "bg-white/15 text-white",
39
+ )}
40
+ >
41
+ {children}
42
+ <span>{label}</span>
43
+ </button>
44
+ );
45
+ }
46
+
47
+ export function RemoteDesktopMobileToolbar({
48
+ mode,
49
+ onToggleMode,
50
+ onOpenKeyboard,
51
+ onResetZoom,
52
+ onClose,
53
+ }: RemoteDesktopMobileToolbarProps) {
54
+ const statsVisible = useSettingsStore((s) => s.remoteDesktopStatsVisible);
55
+ const toggleStats = useSettingsStore((s) => s.toggleRemoteDesktopStatsVisible);
56
+
57
+ return (
58
+ <div
59
+ className="flex shrink-0 items-stretch gap-1 border-t border-white/10 bg-black/90 px-2 pb-[max(0.375rem,env(safe-area-inset-bottom))] pt-1.5"
60
+ data-testid="remote-desktop-mobile-toolbar"
61
+ >
62
+ <ToolbarButton onClick={onToggleMode} label={mode === "mouse" ? "Mouse" : "Touch"} active>
63
+ {mode === "mouse" ? <MousePointer2 className="size-5" /> : <Hand className="size-5" />}
64
+ </ToolbarButton>
65
+ <ToolbarButton onClick={onOpenKeyboard} label="Keyboard">
66
+ <Keyboard className="size-5" />
67
+ </ToolbarButton>
68
+ <ToolbarButton onClick={onResetZoom} label="Reset zoom">
69
+ <ZoomOut className="size-5" />
70
+ </ToolbarButton>
71
+ <ToolbarButton onClick={toggleStats} label="Stats" active={statsVisible}>
72
+ <Gauge className="size-5" />
73
+ </ToolbarButton>
74
+ <ToolbarButton onClick={onClose} label="Close">
75
+ <X className="size-5" />
76
+ </ToolbarButton>
77
+ </div>
78
+ );
79
+ }
@@ -0,0 +1,190 @@
1
+ /**
2
+ * Mobile full-screen remote-desktop viewer: canvas + pinch-zoom/pan stage, gesture engine
3
+ * (`use-remote-desktop-touch`), virtual keyboard, and the bottom toolbar — hosted inside
4
+ * `remote-desktop-mobile-sheet.tsx`'s plain full-viewport portal. Shares the exact WS/nonce/
5
+ * ping/decode connection logic the desktop floating window uses
6
+ * (`use-remote-desktop-connection`), so this is presentation + input wiring only, not a
7
+ * parallel connection implementation.
8
+ */
9
+ import { useRef, useState, useCallback } from "react";
10
+ import { RotateCw, MonitorX } from "lucide-react";
11
+ import { useVisualViewport } from "@/hooks/use-visual-viewport";
12
+ import { useRemoteDesktopConnection } from "./use-remote-desktop-connection";
13
+ import { useRemoteDesktopTouch, type RemoteDesktopInputMode } from "./use-remote-desktop-touch";
14
+ import { useRemoteDesktopVirtualKeyboard } from "./use-remote-desktop-virtual-keyboard";
15
+ import { RemoteDesktopMobileToolbar } from "./remote-desktop-mobile-toolbar";
16
+ import { RemoteDesktopMobileKeyBar } from "./remote-desktop-mobile-key-bar";
17
+ import { RemoteDesktopStatsOverlay } from "./remote-desktop-stats-overlay";
18
+ import { letterboxedContentRect } from "./remote-desktop-coords";
19
+
20
+ export interface RemoteDesktopMobileViewProps {
21
+ onClose: () => void;
22
+ }
23
+
24
+ export default function RemoteDesktopMobileView({ onClose }: RemoteDesktopMobileViewProps) {
25
+ const containerRef = useRef<HTMLDivElement | null>(null);
26
+ const canvasRef = useRef<HTMLCanvasElement | null>(null);
27
+ const [mode, setMode] = useState<RemoteDesktopInputMode>("mouse");
28
+ const [keyBarOpen, setKeyBarOpen] = useState(false);
29
+
30
+ const {
31
+ connState, errorMessage, decoderStatus, decoderErrorMessage, sendMessage, reconnect,
32
+ getTotalBytes, getFrameCount,
33
+ } = useRemoteDesktopConnection(canvasRef);
34
+ const streaming = connState === "streaming";
35
+
36
+ const { transform, virtualCursor, resetZoom } = useRemoteDesktopTouch({
37
+ containerRef,
38
+ canvasRef,
39
+ mode,
40
+ sendMessage,
41
+ enabled: streaming,
42
+ });
43
+ const { inputRef: keyboardInputRef, show: showKeyboard } = useRemoteDesktopVirtualKeyboard(sendMessage, streaming);
44
+
45
+ // Lifts the toolbar/key-bar row above the on-screen keyboard instead of the whole sheet
46
+ // shrinking to make room for it (that was the previous, since-reverted behavior — see
47
+ // remote-desktop-mobile-sheet.tsx's header comment for why it was wrong here).
48
+ const viewportInsets = useVisualViewport(true);
49
+ const keyboardInset = viewportInsets?.keyboardInset ?? 0;
50
+
51
+ const toggleMode = useCallback(() => setMode((m) => (m === "mouse" ? "touch" : "mouse")), []);
52
+ const openKeyboard = useCallback(() => { showKeyboard(); setKeyBarOpen(true); }, [showKeyboard]);
53
+
54
+ const overlayMessage = decoderStatus === "unsupported"
55
+ ? decoderErrorMessage
56
+ : connState === "error"
57
+ ? errorMessage
58
+ : connState === "closed"
59
+ ? "Disconnected"
60
+ : connState === "connecting"
61
+ ? "Connecting…"
62
+ : null;
63
+
64
+ const stageStyle = { transform: `translate(${transform.panX}px, ${transform.panY}px) scale(${transform.scale})` };
65
+
66
+ // Positions the virtual-cursor marker (mouse mode) at the video's actual displayed pixel,
67
+ // not a raw percentage of the stage — the canvas is letterboxed (object-contain) inside the
68
+ // stage whenever the capture's aspect ratio doesn't match the phone's, so a naive `left:
69
+ // xFrac*100%` would land inside the letterbox bars instead of on the video. Mirrors exactly
70
+ // what the gesture engine does for hit-testing (`letterboxedContentRect`), just forward
71
+ // instead of inverse. Reading layout here (not in an effect) is fine: it only feeds a cosmetic
72
+ // overlay position, recomputed on every render the cursor itself changes, which already
73
+ // happens on every drag frame.
74
+ let cursorLeft = `${(virtualCursor?.xFrac ?? 0.5) * 100}%`;
75
+ let cursorTop = `${(virtualCursor?.yFrac ?? 0.5) * 100}%`;
76
+ if (virtualCursor && containerRef.current && canvasRef.current?.width && canvasRef.current.height) {
77
+ const box = containerRef.current.getBoundingClientRect();
78
+ const videoRect = letterboxedContentRect({ left: 0, top: 0, width: box.width, height: box.height }, canvasRef.current.width, canvasRef.current.height);
79
+ cursorLeft = `${videoRect.left + virtualCursor.xFrac * videoRect.width}px`;
80
+ cursorTop = `${videoRect.top + virtualCursor.yFrac * videoRect.height}px`;
81
+ }
82
+
83
+ return (
84
+ <div className="relative h-full w-full bg-black" data-testid="remote-desktop-mobile-view" data-conn-state={connState}>
85
+ {/* Shrinks to the space above the on-screen keyboard (`bottom: keyboardInset`) so the
86
+ whole desktop stays visible, just smaller — `object-contain` on the canvas then
87
+ refits the video into whatever's left, same as it does for the bottom toolbar/notch
88
+ insets on a normal phone. `top:0` + explicit `bottom` (not `inset-0`) is what lets
89
+ `bottom` do that shrinking; gesture hit-testing already reads this element's own
90
+ `getBoundingClientRect()` fresh per gesture, so it stays correct as the box resizes. */}
91
+ <div
92
+ ref={containerRef}
93
+ className="absolute inset-x-0 top-0 flex items-center justify-center overflow-hidden"
94
+ style={{ touchAction: "none", bottom: keyboardInset }}
95
+ data-testid="remote-desktop-mobile-stage-container"
96
+ >
97
+ {/* `h-full w-full` on both this stage and the canvas below is load-bearing: it gives
98
+ the canvas a definite box to be letterboxed within (`object-contain` needs one), and
99
+ keeps the CSS transform's default `transform-origin: 50% 50%` exactly at this
100
+ container's center — the same point `fractionFromZoomedPoint` assumes. `min-h-0
101
+ min-w-0` on the canvas defeats the flex-item automatic-minimum-size floor (a
102
+ well-known flexbox gotcha for replaced elements): without it, a flex item's implicit
103
+ `min-height/width: auto` can hold the canvas at its intrinsic capture resolution
104
+ despite `height/width: 100%`, which is what made the video render oversized and get
105
+ clipped instead of actually shrinking to fit. */}
106
+ <div className="relative flex h-full w-full items-center justify-center" style={stageStyle}>
107
+ <canvas
108
+ ref={canvasRef}
109
+ data-testid="remote-desktop-canvas"
110
+ className="block h-full w-full min-h-0 min-w-0 object-contain outline-none"
111
+ />
112
+ {mode === "mouse" && virtualCursor && streaming && (
113
+ <div
114
+ className="pointer-events-none absolute rounded-full border-2 border-white bg-primary shadow-[0_0_0_1px_rgba(0,0,0,0.6)]"
115
+ style={{
116
+ left: cursorLeft,
117
+ top: cursorTop,
118
+ width: 14,
119
+ height: 14,
120
+ // Counter-scale by 1/zoom: this marker lives inside the zoomed stage, so
121
+ // without this it would grow right along with the video when zoomed in.
122
+ transform: `translate(-50%, -50%) scale(${1 / transform.scale})`,
123
+ }}
124
+ data-testid="remote-desktop-virtual-cursor"
125
+ />
126
+ )}
127
+ </div>
128
+
129
+ {overlayMessage && (
130
+ <div className="absolute inset-0 flex flex-col items-center justify-center gap-3 bg-black/70 text-sm text-white">
131
+ <MonitorX className="size-6 opacity-70" />
132
+ <span className="max-w-sm px-4 text-center">{overlayMessage}</span>
133
+ {(connState === "error" || connState === "closed") && decoderStatus !== "unsupported" && (
134
+ <button
135
+ onClick={reconnect}
136
+ className="flex items-center gap-1.5 rounded-md bg-white/10 px-3 py-1.5 hover:bg-white/20"
137
+ >
138
+ <RotateCw className="size-3.5" /> Reconnect
139
+ </button>
140
+ )}
141
+ </div>
142
+ )}
143
+
144
+ {/* Hidden input the toolbar's Keyboard button focuses to bring up the soft keyboard —
145
+ kept in normal layout flow (not display:none) since a hidden element cannot receive
146
+ focus, just visually collapsed to nothing. */}
147
+ <input
148
+ ref={keyboardInputRef}
149
+ type="text"
150
+ autoComplete="off"
151
+ autoCapitalize="off"
152
+ autoCorrect="off"
153
+ spellCheck={false}
154
+ className="absolute size-px opacity-0"
155
+ aria-hidden="true"
156
+ tabIndex={-1}
157
+ onBlur={() => setKeyBarOpen(false)}
158
+ data-testid="remote-desktop-virtual-keyboard-input"
159
+ />
160
+ </div>
161
+
162
+ {/* Floats above the on-screen keyboard via `keyboardInset` instead of the sheet shrinking
163
+ to make room — `translateY` rather than `bottom` so it animates smoothly and needs no
164
+ extra reflow. Sits at the true bottom (translateY(0)) once the keyboard is closed. */}
165
+ <div
166
+ className="absolute inset-x-0 bottom-0"
167
+ style={keyboardInset > 0 ? { transform: `translateY(-${keyboardInset}px)` } : undefined}
168
+ data-testid="remote-desktop-mobile-bottom-bar"
169
+ >
170
+ {keyBarOpen && <RemoteDesktopMobileKeyBar sendMessage={sendMessage} />}
171
+ <RemoteDesktopMobileToolbar
172
+ mode={mode}
173
+ onToggleMode={toggleMode}
174
+ onOpenKeyboard={openKeyboard}
175
+ onResetZoom={resetZoom}
176
+ onClose={onClose}
177
+ />
178
+ </div>
179
+
180
+ {/* Pushed below the app's top-left device-name pill + the notch safe-area (they overlap a
181
+ plain top-1 position on the phone). */}
182
+ <RemoteDesktopStatsOverlay
183
+ canvasRef={canvasRef}
184
+ getFrameCount={getFrameCount}
185
+ getTotalBytes={getTotalBytes}
186
+ positionClassName="left-1 top-[calc(env(safe-area-inset-top,0px)+2.75rem)]"
187
+ />
188
+ </div>
189
+ );
190
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Small, unobtrusive fps/KB-per-s/resolution overlay — shared by the desktop floating window
3
+ * (`remote-desktop-window-content.tsx`) and the mobile full-screen viewer
4
+ * (`remote-desktop-mobile-view.tsx`). Self-gated on `useSettingsStore().remoteDesktopStatsVisible`
5
+ * so both call sites just render it unconditionally; the toggle button on either surface flips
6
+ * that one shared setting.
7
+ *
8
+ * Polls the connection/decoder's ref-backed counters at ~2Hz — not a render dependency on every
9
+ * frame/message, which would defeat the point of keeping them refs.
10
+ */
11
+ import { useEffect, useRef, useState } from "react";
12
+ import { useSettingsStore } from "@/stores/settings-store";
13
+ import { computeRemoteDesktopStats, type RemoteDesktopStatsSample } from "./remote-desktop-stats";
14
+
15
+ const POLL_INTERVAL_MS = 500;
16
+
17
+ export interface RemoteDesktopStatsOverlayProps {
18
+ canvasRef: React.RefObject<HTMLCanvasElement | null>;
19
+ getFrameCount: () => number;
20
+ getTotalBytes: () => number;
21
+ /** Position utility classes (default top-left). The mobile viewer overrides this to sit below
22
+ * the app's top-left device-name pill + the notch safe-area, which otherwise cover it. */
23
+ positionClassName?: string;
24
+ }
25
+
26
+ export function RemoteDesktopStatsOverlay({ canvasRef, getFrameCount, getTotalBytes, positionClassName = "left-1 top-1" }: RemoteDesktopStatsOverlayProps) {
27
+ const visible = useSettingsStore((s) => s.remoteDesktopStatsVisible);
28
+ const [display, setDisplay] = useState({ fps: 0, kbps: 0, width: 0, height: 0 });
29
+ const lastSampleRef = useRef<RemoteDesktopStatsSample>({ frameCount: 0, totalBytes: 0, atMs: 0 });
30
+
31
+ useEffect(() => {
32
+ if (!visible) return;
33
+ lastSampleRef.current = { frameCount: getFrameCount(), totalBytes: getTotalBytes(), atMs: performance.now() };
34
+ const id = setInterval(() => {
35
+ const next: RemoteDesktopStatsSample = { frameCount: getFrameCount(), totalBytes: getTotalBytes(), atMs: performance.now() };
36
+ const { fps, kbps } = computeRemoteDesktopStats(lastSampleRef.current, next);
37
+ lastSampleRef.current = next;
38
+ const canvas = canvasRef.current;
39
+ setDisplay({ fps, kbps, width: canvas?.width ?? 0, height: canvas?.height ?? 0 });
40
+ }, POLL_INTERVAL_MS);
41
+ return () => clearInterval(id);
42
+ }, [visible, canvasRef, getFrameCount, getTotalBytes]);
43
+
44
+ if (!visible) return null;
45
+
46
+ return (
47
+ <div
48
+ className={`pointer-events-none absolute z-40 rounded bg-black/60 px-1.5 py-0.5 font-mono text-[10px] text-white/80 ${positionClassName}`}
49
+ data-testid="remote-desktop-stats-overlay"
50
+ >
51
+ {Math.round(display.fps)} fps · {Math.round(display.kbps)} KB/s · {display.width}×{display.height}
52
+ </div>
53
+ );
54
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Pure fps/KB-per-second math for the stats overlay. Both inputs are cumulative, monotonically
3
+ * increasing counters (decoder frame count, total bytes received) sampled at two points in
4
+ * time — the overlay polls at ~2Hz and calls this on each tick with the previous and current
5
+ * sample, giving a rolling ~500ms window without needing a ring buffer of history.
6
+ */
7
+ export interface RemoteDesktopStatsSample {
8
+ frameCount: number;
9
+ totalBytes: number;
10
+ /** Sample timestamp, e.g. `performance.now()` — any monotonic clock, ms. */
11
+ atMs: number;
12
+ }
13
+
14
+ export interface RemoteDesktopStats {
15
+ fps: number;
16
+ kbps: number;
17
+ }
18
+
19
+ const ZERO_STATS: RemoteDesktopStats = { fps: 0, kbps: 0 };
20
+
21
+ /**
22
+ * Derives instantaneous fps/KB-per-s from two samples. Returns zeros (rather than dividing by
23
+ * zero or a negative interval) when `elapsedMs` isn't positive, and clamps a negative frame/byte
24
+ * delta to zero — the counters reset to 0 on a reconnect, so a sample straddling that moment
25
+ * would otherwise read as a huge negative rate for one tick.
26
+ */
27
+ export function computeRemoteDesktopStats(prev: RemoteDesktopStatsSample, next: RemoteDesktopStatsSample): RemoteDesktopStats {
28
+ const elapsedMs = next.atMs - prev.atMs;
29
+ if (elapsedMs <= 0) return ZERO_STATS;
30
+ const elapsedSec = elapsedMs / 1000;
31
+ const frames = Math.max(0, next.frameCount - prev.frameCount);
32
+ const bytes = Math.max(0, next.totalBytes - prev.totalBytes);
33
+ return {
34
+ fps: frames / elapsedSec,
35
+ kbps: bytes / 1024 / elapsedSec,
36
+ };
37
+ }
@@ -0,0 +1,195 @@
1
+ /**
2
+ * Pure(-ish) per-gesture accumulators for `use-remote-desktop-touch.ts` — kept out of the hook
3
+ * so the DOM-wiring there stays readable and these decision rules live in one place. Each
4
+ * `track` object is created on touchstart and mutated by the `advance*`/`resolve*` functions as
5
+ * touchmove/touchend events arrive; nothing here touches the DOM or sends messages directly,
6
+ * the hook does that with whatever these functions return.
7
+ */
8
+ import { classifyTwoFingerGesture, isTapGesture, touchDistance, touchMidpoint, type PointerSample } from "./remote-desktop-gesture-classifiers";
9
+ import { fractionFromZoomedPoint, type ZoomPanTransform, type Fraction, type Rect } from "./remote-desktop-coords";
10
+
11
+ export type RemoteDesktopInputMode = "touch" | "mouse";
12
+
13
+ export const TAP_MOVE_THRESHOLD_PX = 10;
14
+ export const TAP_MAX_DURATION_MS = 300;
15
+ export const TWO_FINGER_THRESHOLD_PX = 15;
16
+ export const SCALE_MIN = 1;
17
+ export const SCALE_MAX = 4;
18
+
19
+ function clamp(n: number, min: number, max: number): number {
20
+ return Math.min(Math.max(n, min), max);
21
+ }
22
+
23
+ export function clickMessages(xFrac: number, yFrac: number, button: "left" | "right"): Record<string, unknown>[] {
24
+ return [
25
+ { type: "pointer", xFrac, yFrac, button, down: true },
26
+ { type: "pointer", xFrac, yFrac, button, down: false },
27
+ ];
28
+ }
29
+
30
+ /* ------------------------------------------------------------------ */
31
+ /* Single-finger tracking */
32
+ /* ------------------------------------------------------------------ */
33
+
34
+ export interface SingleTrack {
35
+ start: PointerSample;
36
+ last: { x: number; y: number };
37
+ moved: boolean;
38
+ /** The displayed video's rect (post letterbox) at gesture start — cached because it does not
39
+ * change mid-drag. */
40
+ rect: Rect;
41
+ }
42
+
43
+ export function beginSingleTouch(x: number, y: number, rect: Rect): SingleTrack {
44
+ return { start: { x, y, t: performance.now() }, last: { x, y }, moved: false, rect };
45
+ }
46
+
47
+ export interface SingleMoveResult {
48
+ /** Pointer message to send immediately (touch mode: drag-start once, then drag-move). */
49
+ message?: Record<string, unknown>;
50
+ /** New virtual-cursor fraction (mouse mode only). */
51
+ cursor?: Fraction;
52
+ sendCursorMove?: boolean;
53
+ }
54
+
55
+ export function advanceSingleTouch(
56
+ track: SingleTrack,
57
+ x: number,
58
+ y: number,
59
+ mode: RemoteDesktopInputMode,
60
+ transform: ZoomPanTransform,
61
+ cursor: Fraction,
62
+ ): SingleMoveResult {
63
+ const dist = Math.hypot(x - track.start.x, y - track.start.y);
64
+ if (mode === "touch") {
65
+ if (!track.moved && dist > TAP_MOVE_THRESHOLD_PX) {
66
+ track.moved = true;
67
+ const start = fractionFromZoomedPoint(track.start.x, track.start.y, track.rect, transform);
68
+ track.last = { x, y };
69
+ return { message: { type: "pointer", xFrac: start.xFrac, yFrac: start.yFrac, button: "left", down: true } };
70
+ }
71
+ track.last = { x, y };
72
+ if (!track.moved) return {};
73
+ const frac = fractionFromZoomedPoint(x, y, track.rect, transform);
74
+ return { message: { type: "pointer", xFrac: frac.xFrac, yFrac: frac.yFrac, button: null, down: null } };
75
+ }
76
+
77
+ // Mouse mode: relative drag of the client-side virtual cursor, scaled down by zoom so panning
78
+ // while zoomed in still moves the cursor the same *host* distance.
79
+ const dx = (x - track.last.x) / transform.scale;
80
+ const dy = (y - track.last.y) / transform.scale;
81
+ if (dist > TAP_MOVE_THRESHOLD_PX) track.moved = true;
82
+ track.last = { x, y };
83
+ const next: Fraction = {
84
+ xFrac: clamp(cursor.xFrac + dx / track.rect.width, 0, 1),
85
+ yFrac: clamp(cursor.yFrac + dy / track.rect.height, 0, 1),
86
+ };
87
+ return { cursor: next, sendCursorMove: true };
88
+ }
89
+
90
+ export function resolveSingleTouchEnd(
91
+ track: SingleTrack,
92
+ x: number,
93
+ y: number,
94
+ mode: RemoteDesktopInputMode,
95
+ transform: ZoomPanTransform,
96
+ cursor: Fraction,
97
+ ): Record<string, unknown>[] {
98
+ const end: PointerSample = { x, y, t: performance.now() };
99
+ const tapped = isTapGesture(track.start, end, TAP_MOVE_THRESHOLD_PX, TAP_MAX_DURATION_MS);
100
+ if (mode === "touch") {
101
+ const frac = fractionFromZoomedPoint(x, y, track.rect, transform);
102
+ if (!track.moved && tapped) return clickMessages(frac.xFrac, frac.yFrac, "left");
103
+ if (track.moved) return [{ type: "pointer", xFrac: frac.xFrac, yFrac: frac.yFrac, button: "left", down: false }];
104
+ return [];
105
+ }
106
+ if (!track.moved && tapped) return clickMessages(cursor.xFrac, cursor.yFrac, "left");
107
+ return [];
108
+ }
109
+
110
+ /* ------------------------------------------------------------------ */
111
+ /* Two-finger tracking */
112
+ /* ------------------------------------------------------------------ */
113
+
114
+ export interface TwoFingerTrack {
115
+ startDistance: number;
116
+ startMid: { x: number; y: number };
117
+ lastMid: { x: number; y: number };
118
+ startScale: number;
119
+ startTime: number;
120
+ gesture: "pinch" | "scroll" | null;
121
+ }
122
+
123
+ export function beginTwoFingerTouch(
124
+ pa: { x: number; y: number },
125
+ pb: { x: number; y: number },
126
+ currentScale: number,
127
+ ): TwoFingerTrack {
128
+ const mid = touchMidpoint(pa, pb);
129
+ return {
130
+ startDistance: touchDistance(pa, pb),
131
+ startMid: mid,
132
+ lastMid: mid,
133
+ startScale: currentScale,
134
+ startTime: performance.now(),
135
+ gesture: null,
136
+ };
137
+ }
138
+
139
+ export interface TwoFingerMoveResult {
140
+ transform?: ZoomPanTransform;
141
+ wheelDeltaPx?: number;
142
+ }
143
+
144
+ /** Locks in "pinch" vs "scroll" the first time either signal (finger spread, or midpoint
145
+ * translation) crosses `TWO_FINGER_THRESHOLD_PX`, then keeps applying that gesture for the
146
+ * rest of the touch — see `remote-desktop-gesture-classifiers.ts` for why. */
147
+ export function advanceTwoFingerTouch(
148
+ track: TwoFingerTrack,
149
+ pa: { x: number; y: number },
150
+ pb: { x: number; y: number },
151
+ currentTransform: ZoomPanTransform,
152
+ ): TwoFingerMoveResult {
153
+ const currentDistance = touchDistance(pa, pb);
154
+ const currentMid = touchMidpoint(pa, pb);
155
+
156
+ if (track.gesture === null) {
157
+ const distDiff = Math.abs(currentDistance - track.startDistance);
158
+ const midDist = touchDistance(track.startMid, currentMid);
159
+ if (distDiff > TWO_FINGER_THRESHOLD_PX) {
160
+ track.gesture = classifyTwoFingerGesture(track.startDistance, currentDistance, TWO_FINGER_THRESHOLD_PX);
161
+ } else if (midDist > TWO_FINGER_THRESHOLD_PX) {
162
+ track.gesture = "scroll";
163
+ } else {
164
+ track.lastMid = currentMid;
165
+ return {}; // still ambiguous — wait for more movement before committing
166
+ }
167
+ }
168
+
169
+ let result: TwoFingerMoveResult;
170
+ if (track.gesture === "pinch") {
171
+ const scale = clamp(track.startScale * (currentDistance / track.startDistance), SCALE_MIN, SCALE_MAX);
172
+ result = { transform: { scale, panX: currentTransform.panX, panY: currentTransform.panY } };
173
+ } else if (currentTransform.scale > 1.01) {
174
+ // Panning only makes sense once zoomed in — at scale 1 the same drag scrolls instead.
175
+ const dx = currentMid.x - track.lastMid.x;
176
+ const dy = currentMid.y - track.lastMid.y;
177
+ result = { transform: { scale: currentTransform.scale, panX: currentTransform.panX + dx, panY: currentTransform.panY + dy } };
178
+ } else {
179
+ result = { wheelDeltaPx: currentMid.y - track.lastMid.y };
180
+ }
181
+ track.lastMid = currentMid;
182
+ return result;
183
+ }
184
+
185
+ export function resolveTwoFingerEnd(
186
+ track: TwoFingerTrack,
187
+ rect: Rect,
188
+ transform: ZoomPanTransform,
189
+ ): Record<string, unknown>[] {
190
+ if (track.gesture === null && performance.now() - track.startTime <= TAP_MAX_DURATION_MS) {
191
+ const frac = fractionFromZoomedPoint(track.lastMid.x, track.lastMid.y, rect, transform);
192
+ return clickMessages(frac.xFrac, frac.yFrac, "right");
193
+ }
194
+ return [];
195
+ }