@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,106 @@
1
+ /**
2
+ * Warning shown before the Remote Desktop viewer connects — wraps both the desktop window body
3
+ * (`remote-desktop-window-content.tsx`) and the mobile full-screen view
4
+ * (`remote-desktop-mobile-sheet.tsx`). The viewer is only mounted after the user continues, so
5
+ * no session nonce is minted and no frame is captured while the warning is up.
6
+ *
7
+ * Shown on every open until the user ticks "don't show again", which persists via the shared
8
+ * `remoteDesktopWarningDismissed` UI pref (server-side, like the stats-overlay toggle) so the
9
+ * choice follows them across devices. Dark chrome on purpose: it sits where the black canvas
10
+ * will be, on both surfaces.
11
+ */
12
+ import { useState, type ReactNode } from "react";
13
+ import { MonitorSmartphone, ShieldAlert } from "lucide-react";
14
+ import { useSettingsStore } from "@/stores/settings-store";
15
+
16
+ export interface RemoteDesktopWarningGateProps {
17
+ /** Called when the user backs out instead of continuing — close the window/sheet. */
18
+ onCancel: () => void;
19
+ children: ReactNode;
20
+ }
21
+
22
+ export function RemoteDesktopWarningGate({ onCancel, children }: RemoteDesktopWarningGateProps) {
23
+ const dismissed = useSettingsStore((s) => s.remoteDesktopWarningDismissed);
24
+ const setDismissed = useSettingsStore((s) => s.setRemoteDesktopWarningDismissed);
25
+ const [accepted, setAccepted] = useState(false);
26
+ const [dontShowAgain, setDontShowAgain] = useState(false);
27
+
28
+ if (dismissed || accepted) return <>{children}</>;
29
+
30
+ const onContinue = () => {
31
+ if (dontShowAgain) setDismissed(true);
32
+ setAccepted(true);
33
+ };
34
+
35
+ return (
36
+ <div
37
+ className="flex h-full w-full items-center justify-center overflow-y-auto bg-black p-4 text-white"
38
+ data-testid="remote-desktop-warning-gate"
39
+ >
40
+ <div className="flex w-full max-w-sm flex-col gap-4 rounded-xl border border-white/10 bg-white/5 p-5">
41
+ <div className="flex items-center gap-2">
42
+ <MonitorSmartphone className="size-5 shrink-0 text-primary" />
43
+ <h2 className="text-base font-semibold">Remote Desktop</h2>
44
+ <span className="ml-auto rounded-full bg-amber-500/20 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-amber-300">
45
+ Beta
46
+ </span>
47
+ </div>
48
+
49
+ <ul className="flex flex-col gap-2 text-sm text-white/80">
50
+ <li className="flex gap-2">
51
+ <ShieldAlert className="mt-0.5 size-4 shrink-0 text-amber-300" />
52
+ <span>
53
+ This streams the host&apos;s <strong>whole screen</strong> and lets you <strong>move its mouse and type</strong> as if
54
+ you were sitting at it. Anything you do here happens on the real desktop.
55
+ </span>
56
+ </li>
57
+ <li className="flex gap-2">
58
+ <ShieldAlert className="mt-0.5 size-4 shrink-0 text-amber-300" />
59
+ <span>
60
+ Anyone signed in to this PPM can do the same. Avoid opening it over a public share link you don&apos;t fully
61
+ trust; a private network (Tailscale, LAN) is safer.
62
+ </span>
63
+ </li>
64
+ <li className="flex gap-2">
65
+ <ShieldAlert className="mt-0.5 size-4 shrink-0 text-amber-300" />
66
+ <span>
67
+ Still beta: the host must be unlocked with an active session, UAC prompts stay hidden, and a person at the
68
+ host will fight you for the cursor.
69
+ </span>
70
+ </li>
71
+ </ul>
72
+
73
+ <label className="flex min-h-11 cursor-pointer items-center gap-2 text-sm text-white/70">
74
+ <input
75
+ type="checkbox"
76
+ checked={dontShowAgain}
77
+ onChange={(e) => setDontShowAgain(e.target.checked)}
78
+ className="size-4 accent-primary"
79
+ data-testid="remote-desktop-warning-dont-show"
80
+ />
81
+ Don&apos;t show this again
82
+ </label>
83
+
84
+ {/* Primary action last (thumb zone on mobile); both ≥44px tall. */}
85
+ <div className="flex gap-2">
86
+ <button
87
+ type="button"
88
+ onClick={onCancel}
89
+ className="min-h-11 flex-1 rounded-md bg-white/10 px-3 text-sm hover:bg-white/20"
90
+ data-testid="remote-desktop-warning-cancel"
91
+ >
92
+ Cancel
93
+ </button>
94
+ <button
95
+ type="button"
96
+ onClick={onContinue}
97
+ className="min-h-11 flex-1 rounded-md bg-primary px-3 text-sm font-medium text-primary-foreground hover:bg-primary/90"
98
+ data-testid="remote-desktop-warning-continue"
99
+ >
100
+ Continue
101
+ </button>
102
+ </div>
103
+ </div>
104
+ </div>
105
+ );
106
+ }
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Remote Desktop window body: thin wrapper around `useRemoteDesktopConnection` (WS/nonce/ping/
3
+ * decode, shared with the mobile viewer) that renders the canvas, overlay states, and wires
4
+ * pointer/keyboard capture over the connection's `sendMessage`. Sits behind
5
+ * `RemoteDesktopWarningGate`, so the connection hook (and its session nonce) only runs once the
6
+ * user has read the warning.
7
+ */
8
+ import { useCallback, useRef } from "react";
9
+ import { RotateCw, MonitorX, Gauge } from "lucide-react";
10
+ import type { WindowContentProps } from "@/components/floating-window/window-content-registry";
11
+ import { useWindowStore } from "@/components/floating-window/window-store";
12
+ import { useSettingsStore } from "@/stores/settings-store";
13
+ import { cn } from "@/lib/utils";
14
+ import { useRemoteDesktopConnection } from "./use-remote-desktop-connection";
15
+ import { useRemoteInputCapture } from "./use-remote-input-capture";
16
+ import { RemoteDesktopStatsOverlay } from "./remote-desktop-stats-overlay";
17
+ import { RemoteDesktopWarningGate } from "./remote-desktop-warning-gate";
18
+
19
+ export default function RemoteDesktopWindowContent({ id }: WindowContentProps) {
20
+ const closeWindow = useWindowStore((s) => s.close);
21
+ const onCancel = useCallback(() => closeWindow(id), [closeWindow, id]);
22
+ return (
23
+ <RemoteDesktopWarningGate onCancel={onCancel}>
24
+ <RemoteDesktopViewer />
25
+ </RemoteDesktopWarningGate>
26
+ );
27
+ }
28
+
29
+ function RemoteDesktopViewer() {
30
+ const canvasRef = useRef<HTMLCanvasElement | null>(null);
31
+ const { connState, errorMessage, decoderStatus, decoderErrorMessage, sendMessage, reconnect, getFrameCount, getTotalBytes } =
32
+ useRemoteDesktopConnection(canvasRef);
33
+ const statsVisible = useSettingsStore((s) => s.remoteDesktopStatsVisible);
34
+ const toggleStats = useSettingsStore((s) => s.toggleRemoteDesktopStatsVisible);
35
+
36
+ useRemoteInputCapture(canvasRef, sendMessage, connState === "streaming");
37
+
38
+ const overlayMessage = decoderStatus === "unsupported"
39
+ ? decoderErrorMessage
40
+ : connState === "error"
41
+ ? errorMessage
42
+ : connState === "closed"
43
+ ? "Disconnected"
44
+ : connState === "connecting"
45
+ ? "Connecting…"
46
+ : null;
47
+
48
+ return (
49
+ <div
50
+ className="relative flex h-full w-full items-center justify-center bg-black"
51
+ data-testid="remote-desktop-window"
52
+ data-conn-state={connState}
53
+ >
54
+ <canvas ref={canvasRef} data-testid="remote-desktop-canvas" className="max-h-full max-w-full outline-none" />
55
+ <RemoteDesktopStatsOverlay canvasRef={canvasRef} getFrameCount={getFrameCount} getTotalBytes={getTotalBytes} />
56
+ {/* Small corner toggle for the stats overlay above — top-right so it never collides with
57
+ the overlay itself (top-left) or the window's own title bar/controls above this body. */}
58
+ <button
59
+ type="button"
60
+ onClick={toggleStats}
61
+ aria-label="Toggle stats overlay"
62
+ aria-pressed={statsVisible}
63
+ className={cn(
64
+ "absolute right-1 top-1 z-40 flex size-6 items-center justify-center rounded bg-black/40 text-white/70 hover:bg-black/60 hover:text-white",
65
+ statsVisible && "bg-primary/70 text-primary-foreground hover:bg-primary/80",
66
+ )}
67
+ >
68
+ <Gauge className="size-3.5" />
69
+ </button>
70
+ {overlayMessage && (
71
+ <div className="absolute inset-0 flex flex-col items-center justify-center gap-3 bg-black/70 text-sm text-white">
72
+ <MonitorX className="size-6 opacity-70" />
73
+ <span className="max-w-sm text-center px-4">{overlayMessage}</span>
74
+ {(connState === "error" || connState === "closed") && decoderStatus !== "unsupported" && (
75
+ <button
76
+ onClick={reconnect}
77
+ className="flex items-center gap-1.5 rounded-md bg-white/10 px-3 py-1.5 hover:bg-white/20"
78
+ >
79
+ <RotateCw className="size-3.5" /> Reconnect
80
+ </button>
81
+ )}
82
+ </div>
83
+ )}
84
+ </div>
85
+ );
86
+ }
@@ -0,0 +1,22 @@
1
+ /** Pure URL builder — mirrors `ws-client.ts`'s dev-port bypass so it's unit-testable without
2
+ * mounting a real WebSocket or window.location. */
3
+ import { withWsAuth } from "@/lib/ws-auth";
4
+
5
+ export interface LocationParts {
6
+ protocol: string;
7
+ hostname: string;
8
+ host: string;
9
+ }
10
+
11
+ export function resolveRemoteDesktopWsUrl(loc: LocationParts, isDev: boolean, devPort = "8081"): string {
12
+ const authed = withWsAuth("/ws/remote-desktop");
13
+ const wsProtocol = loc.protocol === "https:" ? "wss:" : "ws:";
14
+ if (isDev && loc.protocol !== "https:") {
15
+ // Same bypass as ws-client.ts: Vite's dev proxy has unreliable WS upgrade handling.
16
+ // Port defaults to 8081 (the normal `bun dev:server` port) but is overridable via
17
+ // `VITE_DEV_API_PORT` for an alt-port dev stack (see `PPM_DEV_API` in vite.config.ts) —
18
+ // without this, a second dev stack on e.g. 8082 would always connect to the wrong server.
19
+ return `ws://${loc.hostname}:${devPort}${authed}`;
20
+ }
21
+ return `${wsProtocol}//${loc.host}${authed}`;
22
+ }
@@ -0,0 +1,175 @@
1
+ /**
2
+ * WebCodecs `VideoDecoder` wrapper — Annex-B H.264 in, `<canvas>` frames out.
3
+ *
4
+ * Two config traps this guards against: (1) the codec string must come from the real
5
+ * bitstream (`avc1-codec-string.ts` on the server derives it from the actual SPS) — a
6
+ * hardcoded guess can mismatch the encoder's actual profile/level and make `configure()`
7
+ * throw; (2) no `description` field is passed, which is what tells WebCodecs to expect
8
+ * Annex-B (start-code-prefixed) input rather than length-prefixed AVCC — passing a
9
+ * `description` here would silently break decode of every chunk this session sends.
10
+ *
11
+ * Self-healing on decode error: a mobile hardware decoder (confirmed: works on desktop Chrome,
12
+ * dies on mobile Chromium after ~one GOP) can throw a hard decode error and never recover on its
13
+ * own — WebCodecs offers no "just skip this frame" API once that happens, the decoder is dead.
14
+ * Rather than freeze the stream forever, a decode error tears down the errored `VideoDecoder`
15
+ * and creates a fresh one with the same cached codec string, then waits for the next server
16
+ * keyframe before feeding it anything (same guard the very first connect already uses) — the
17
+ * server sends one roughly every second (30-frame GOP @ 30fps), so recovery is bounded to about
18
+ * that long instead of permanent.
19
+ */
20
+ import { useCallback, useRef, useState } from "react";
21
+ import { frameTimestampMicros } from "./remote-desktop-frame-timestamp";
22
+
23
+ export type DecoderStatus = "idle" | "unsupported" | "ready" | "error";
24
+
25
+ /**
26
+ * Whether an access unit should be fed to the decoder right now. Decode cannot start on a delta
27
+ * chunk — true both on the very first connect (before any keyframe has arrived) and after an
28
+ * auto-recovered decode error, since a freshly (re)created `VideoDecoder` has no prior reference
29
+ * frame either way. A keyframe is always decodable on its own. Pure so this resync rule is
30
+ * testable without a real `VideoDecoder`.
31
+ */
32
+ export function shouldDecodeAccessUnit(hasSeenKeyframeThisInstance: boolean, isKey: boolean): boolean {
33
+ return isKey || hasSeenKeyframeThisInstance;
34
+ }
35
+
36
+ export interface UseH264CanvasDecoderResult {
37
+ status: DecoderStatus;
38
+ errorMessage: string | null;
39
+ /** Configure (or reconfigure) the decoder once the server's `{type:"config"}` message
40
+ * arrives with a codec string derived from the real SPS. */
41
+ configure: (codec: string) => Promise<void>;
42
+ /** Feed one access unit (header byte already stripped by the caller) to the decoder. */
43
+ decodeAccessUnit: (bytes: Uint8Array, isKey: boolean) => void;
44
+ reset: () => void;
45
+ /** Total frames the decoder has handed to `output()` (and drawn, or attempted to) since the
46
+ * last `reset()`. Ref-backed, not React state — read it from a poll (e.g. the stats overlay),
47
+ * not a render dependency, so every decoded frame doesn't force a re-render. */
48
+ getFrameCount: () => number;
49
+ }
50
+
51
+ export function useH264CanvasDecoder(
52
+ canvasRef: React.RefObject<HTMLCanvasElement | null>,
53
+ ): UseH264CanvasDecoderResult {
54
+ const [status, setStatus] = useState<DecoderStatus>("idle");
55
+ const [errorMessage, setErrorMessage] = useState<string | null>(null);
56
+ const decoderRef = useRef<VideoDecoder | null>(null);
57
+ const ctxRef = useRef<CanvasRenderingContext2D | null>(null);
58
+ const decodedAnyKeyRef = useRef(false);
59
+ const frameCountRef = useRef(0);
60
+ const frameIndexRef = useRef(0); // per-decoder-instance timestamp counter, see decodeAccessUnit
61
+ const lastCodecRef = useRef<string | null>(null);
62
+ const activeRef = useRef(false); // false once reset()/unmount has torn this session down
63
+
64
+ const fail = useCallback((message: string) => {
65
+ setStatus("error");
66
+ setErrorMessage(message);
67
+ }, []);
68
+
69
+ const configure = useCallback(async (codec: string) => {
70
+ lastCodecRef.current = codec;
71
+ if (typeof VideoDecoder === "undefined") {
72
+ setStatus("unsupported");
73
+ setErrorMessage("This browser has no WebCodecs VideoDecoder — try Chrome, Edge, or Safari 16.4+.");
74
+ return;
75
+ }
76
+ const config: VideoDecoderConfig = { codec, optimizeForLatency: true };
77
+ try {
78
+ const support = await VideoDecoder.isConfigSupported(config);
79
+ if (!support.supported) {
80
+ setStatus("unsupported");
81
+ setErrorMessage(`This browser's decoder does not support ${codec}.`);
82
+ return;
83
+ }
84
+ } catch (e) {
85
+ fail((e as Error).message);
86
+ return;
87
+ }
88
+
89
+ decoderRef.current?.close();
90
+ decodedAnyKeyRef.current = false;
91
+ frameIndexRef.current = 0;
92
+ const decoder = new VideoDecoder({
93
+ output: (frame) => {
94
+ frameCountRef.current += 1;
95
+ const canvas = canvasRef.current;
96
+ if (canvas) {
97
+ if (canvas.width !== frame.displayWidth || canvas.height !== frame.displayHeight) {
98
+ canvas.width = frame.displayWidth;
99
+ canvas.height = frame.displayHeight;
100
+ }
101
+ if (!ctxRef.current) ctxRef.current = canvas.getContext("2d");
102
+ ctxRef.current?.drawImage(frame, 0, 0, canvas.width, canvas.height);
103
+ }
104
+ frame.close();
105
+ },
106
+ error: (e) => recoverOrFail(e.message),
107
+ });
108
+ try {
109
+ decoder.configure(config);
110
+ } catch (e) {
111
+ fail((e as Error).message);
112
+ return;
113
+ }
114
+ decoderRef.current = decoder;
115
+ activeRef.current = true;
116
+ setStatus("ready");
117
+ setErrorMessage(null);
118
+ }, [canvasRef, fail]);
119
+
120
+ /** Shared by the decoder's async `error` callback and `decodeAccessUnit`'s synchronous catch
121
+ * (a `.decode()` call can also throw directly) — both are "this decoder just broke", handled
122
+ * identically: recreate rather than give up, unless the session has already been torn down
123
+ * (`reset()`/unmount) or there is no cached codec to recreate with (should not happen — codec
124
+ * is cached before any decode is possible). */
125
+ const recoverOrFail = useCallback((message: string) => {
126
+ if (!activeRef.current) return; // torn down already — nothing to recover into
127
+ const codec = lastCodecRef.current;
128
+ if (!codec) { fail(message); return; }
129
+ try { decoderRef.current?.close(); } catch { /* discarding the errored instance regardless */ }
130
+ decoderRef.current = null;
131
+ void configure(codec); // recreate; configure() itself resets decodedAnyKeyRef, so decode
132
+ // waits for the next server keyframe instead of feeding a fresh decoder mid-GOP deltas.
133
+ }, [configure, fail]);
134
+
135
+ const decodeAccessUnit = useCallback((bytes: Uint8Array, isKey: boolean) => {
136
+ const decoder = decoderRef.current;
137
+ if (!decoder || decoder.state !== "configured") return;
138
+ if (!shouldDecodeAccessUnit(decodedAnyKeyRef.current, isKey)) return;
139
+ if (isKey) decodedAnyKeyRef.current = true;
140
+ try {
141
+ decoder.decode(new EncodedVideoChunk({
142
+ type: isKey ? "key" : "delta",
143
+ timestamp: frameTimestampMicros(frameIndexRef.current++),
144
+ data: bytes,
145
+ }));
146
+ } catch (e) {
147
+ recoverOrFail((e as Error).message);
148
+ }
149
+ }, [recoverOrFail]);
150
+
151
+ const reset = useCallback(() => {
152
+ activeRef.current = false;
153
+ // Closing an already-errored/half-configured decoder can throw in some browsers; this runs
154
+ // from a React effect cleanup, and an uncaught throw there crashes the whole tree (no error
155
+ // boundary catches effect-cleanup errors) — swallow it, we're discarding the decoder anyway.
156
+ try { decoderRef.current?.close(); } catch { /* discarding regardless */ }
157
+ decoderRef.current = null;
158
+ decodedAnyKeyRef.current = false;
159
+ frameCountRef.current = 0;
160
+ frameIndexRef.current = 0;
161
+ setStatus("idle");
162
+ setErrorMessage(null);
163
+ }, []);
164
+
165
+ const getFrameCount = useCallback(() => frameCountRef.current, []);
166
+
167
+ return {
168
+ status,
169
+ errorMessage,
170
+ configure,
171
+ decodeAccessUnit,
172
+ reset,
173
+ getFrameCount,
174
+ };
175
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Fetches remote-desktop capabilities once and reports whether the feature should surface in
3
+ * the UI. Returns `available=false` when the host opted out (`REMOTE_DESKTOP_ENABLED=0` — the
4
+ * endpoint 404s) or video capture is unavailable (no ffmpeg), so the nav entry stays hidden on
5
+ * hosts that can't or won't stream.
6
+ */
7
+ import { useEffect, useState } from "react";
8
+ import { api } from "@/lib/api-client";
9
+
10
+ interface Capabilities {
11
+ ffmpegAvailable: boolean;
12
+ videoAvailable: boolean;
13
+ inputAvailable: boolean;
14
+ authRequired: boolean;
15
+ }
16
+
17
+ export function useRemoteDesktopAvailable(): { available: boolean; authRequired: boolean } {
18
+ const [caps, setCaps] = useState<Capabilities | null>(null);
19
+
20
+ useEffect(() => {
21
+ let cancelled = false;
22
+ api.get<Capabilities>("/api/remote-desktop/capabilities")
23
+ .then((c) => { if (!cancelled) setCaps(c); })
24
+ .catch(() => { if (!cancelled) setCaps(null); }); // 404 (flag off) or network error — stay hidden
25
+ return () => { cancelled = true; };
26
+ }, []);
27
+
28
+ return { available: !!caps?.videoAvailable, authRequired: !!caps?.authRequired };
29
+ }
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Owns one remote-desktop WS connection end to end: mints a session nonce, opens the socket,
3
+ * feeds binary access units to the H.264 decoder, and answers the server's heartbeat ping.
4
+ * Factored out of the desktop floating-window body so the mobile full-screen viewer can share
5
+ * the exact same connection logic instead of a parallel implementation — see
6
+ * `remote-desktop-window-content.tsx` (desktop) and `remote-desktop-mobile-view.tsx` (mobile),
7
+ * both now thin callers of this hook.
8
+ *
9
+ * Connection is one-shot — a lost connection reports `connState: "closed"`/`"error"` and waits
10
+ * for the caller to bump `reconnect()`. We deliberately do NOT auto-reconnect on every close (a
11
+ * flaky link reconnecting in a loop would fight the server's "one session per host" eviction).
12
+ * The one exception is the tab becoming visible again: browsers throttle the ping
13
+ * `setInterval` in a backgrounded tab, so a user who glances away can trip the server's
14
+ * heartbeat timeout — on return we re-ping (or, if the session already died, reconnect once).
15
+ * That's a discrete user-driven event, not a loop.
16
+ */
17
+ import { useCallback, useEffect, useRef, useState } from "react";
18
+ import { api } from "@/lib/api-client";
19
+ import { resolveRemoteDesktopWsUrl } from "./remote-desktop-ws-url";
20
+ import { useH264CanvasDecoder, type DecoderStatus } from "./use-h264-canvas-decoder";
21
+
22
+ export type RemoteDesktopConnState = "connecting" | "streaming" | "error" | "closed";
23
+
24
+ const PING_INTERVAL_MS = 5_000;
25
+
26
+ export interface UseRemoteDesktopConnectionResult {
27
+ connState: RemoteDesktopConnState;
28
+ errorMessage: string | null;
29
+ decoderStatus: DecoderStatus;
30
+ decoderErrorMessage: string | null;
31
+ sendMessage: (msg: Record<string, unknown>) => void;
32
+ /** Force a fresh connection (e.g. a manual "Reconnect" button). */
33
+ reconnect: () => void;
34
+ /** Total bytes of binary (access-unit) WS messages received since the last (re)connect.
35
+ * Ref-backed — poll it (e.g. the stats overlay), don't treat it as a render dependency. Feeds
36
+ * the KB/s stat; frame count below feeds fps. */
37
+ getTotalBytes: () => number;
38
+ /** Forwards the decoder's own frame counter (see `use-h264-canvas-decoder.ts`). */
39
+ getFrameCount: () => number;
40
+ }
41
+
42
+ export function useRemoteDesktopConnection(
43
+ canvasRef: React.RefObject<HTMLCanvasElement | null>,
44
+ ): UseRemoteDesktopConnectionResult {
45
+ const wsRef = useRef<WebSocket | null>(null);
46
+ const totalBytesRef = useRef(0);
47
+ const [connState, setConnState] = useState<RemoteDesktopConnState>("connecting");
48
+ const [errorMessage, setErrorMessage] = useState<string | null>(null);
49
+ const [generation, setGeneration] = useState(0); // bump to force a manual reconnect
50
+
51
+ const decoder = useH264CanvasDecoder(canvasRef);
52
+
53
+ const sendMessage = useCallback((msg: Record<string, unknown>) => {
54
+ const ws = wsRef.current;
55
+ if (ws?.readyState === WebSocket.OPEN) ws.send(JSON.stringify(msg));
56
+ }, []);
57
+
58
+ useEffect(() => {
59
+ let cancelled = false;
60
+ let pingTimer: ReturnType<typeof setInterval> | null = null;
61
+ setConnState("connecting");
62
+ setErrorMessage(null);
63
+ totalBytesRef.current = 0;
64
+ decoder.reset();
65
+
66
+ (async () => {
67
+ let nonce: string;
68
+ try {
69
+ // `wsPath` in the response is always "/ws/remote-desktop" — the client already knows
70
+ // that path; the response is only consumed for the nonce.
71
+ const res = await api.post<{ nonce: string; wsPath: string }>("/api/remote-desktop/session");
72
+ nonce = res.nonce;
73
+ } catch (e) {
74
+ if (!cancelled) { setConnState("error"); setErrorMessage((e as Error).message); }
75
+ return;
76
+ }
77
+ if (cancelled) return;
78
+
79
+ const url = resolveRemoteDesktopWsUrl(window.location, import.meta.env.DEV, import.meta.env.VITE_DEV_API_PORT);
80
+ const ws = new WebSocket(url);
81
+ ws.binaryType = "arraybuffer";
82
+ wsRef.current = ws;
83
+
84
+ ws.onopen = () => {
85
+ ws.send(JSON.stringify({ type: "auth", nonce }));
86
+ pingTimer = setInterval(() => sendMessage({ type: "ping" }), PING_INTERVAL_MS);
87
+ };
88
+ ws.onmessage = (event) => {
89
+ if (typeof event.data === "string") {
90
+ let msg: Record<string, unknown>;
91
+ try { msg = JSON.parse(event.data); } catch { return; }
92
+ if (msg.type === "config" && typeof msg.codec === "string") {
93
+ decoder.configure(msg.codec).then(() => { if (!cancelled) setConnState("streaming"); });
94
+ } else if (msg.type === "error") {
95
+ setConnState("error");
96
+ setErrorMessage(typeof msg.message === "string" ? msg.message : "Server error");
97
+ }
98
+ return;
99
+ }
100
+ const bytes = new Uint8Array(event.data as ArrayBuffer);
101
+ if (bytes.length < 1) return;
102
+ totalBytesRef.current += bytes.length;
103
+ decoder.decodeAccessUnit(bytes.subarray(1), bytes[0] === 1);
104
+ };
105
+ ws.onerror = () => { if (!cancelled) { setConnState("error"); setErrorMessage("WebSocket error"); } };
106
+ ws.onclose = () => { if (!cancelled) setConnState((s) => (s === "error" ? s : "closed")); };
107
+ })();
108
+
109
+ // A backgrounded tab throttles the ping interval, which can trip the server heartbeat. When
110
+ // the tab is visible again, re-ping immediately; if the session already died, reconnect once
111
+ // (bumping generation re-runs this effect). Fires only on the visible transition — not a loop.
112
+ const onVisibility = () => {
113
+ if (document.hidden || cancelled) return;
114
+ const ws = wsRef.current;
115
+ if (ws && ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify({ type: "ping" }));
116
+ else setGeneration((g) => g + 1);
117
+ };
118
+ document.addEventListener("visibilitychange", onVisibility);
119
+
120
+ return () => {
121
+ cancelled = true;
122
+ document.removeEventListener("visibilitychange", onVisibility);
123
+ if (pingTimer) clearInterval(pingTimer);
124
+ // A throw here (e.g. closing a socket/decoder mid-teardown) runs inside an effect cleanup,
125
+ // which no error boundary catches — left unguarded it can blank the entire app on unmount,
126
+ // not just this component (this is what closing the mobile sheet used to do).
127
+ try { wsRef.current?.close(); } catch { /* tearing down regardless */ }
128
+ wsRef.current = null;
129
+ try { decoder.reset(); } catch { /* tearing down regardless */ }
130
+ };
131
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- reconnect is driven by `generation`
132
+ }, [generation]);
133
+
134
+ const reconnect = useCallback(() => setGeneration((g) => g + 1), []);
135
+ const getTotalBytes = useCallback(() => totalBytesRef.current, []);
136
+
137
+ return {
138
+ connState,
139
+ errorMessage,
140
+ decoderStatus: decoder.status,
141
+ decoderErrorMessage: decoder.errorMessage,
142
+ sendMessage,
143
+ reconnect,
144
+ getTotalBytes,
145
+ getFrameCount: decoder.getFrameCount,
146
+ };
147
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Global open/close state for the single mobile remote-desktop sheet. There is exactly one
3
+ * host to control (same as the desktop floating window, which also dedupes to one instance),
4
+ * so — unlike the mobile explorer's per-path slices — this is just an open flag.
5
+ */
6
+ import { create } from "zustand";
7
+
8
+ interface RemoteDesktopMobileOpenState {
9
+ isOpen: boolean;
10
+ open(): void;
11
+ close(): void;
12
+ }
13
+
14
+ export const useRemoteDesktopMobileOpenState = create<RemoteDesktopMobileOpenState>((set) => ({
15
+ isOpen: false,
16
+ open: () => set({ isOpen: true }),
17
+ close: () => set({ isOpen: false }),
18
+ }));