@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.
- package/.rdtest2-cf.out +0 -0
- package/CHANGELOG.md +26 -0
- package/CLAUDE.md +3 -0
- package/assets/skills/ppm/SKILL.md +1 -1
- package/assets/skills/ppm/references/cli-reference.md +22 -4
- package/assets/skills/ppm/references/http-api.md +8 -1
- package/dist/web/assets/{adaptive-context-menu-DCZtgOL7.js → adaptive-context-menu-BbuRVTLn.js} +1 -1
- package/dist/web/assets/{ai-resource-editor-D5CPBg06.js → ai-resource-editor-Cm4n3VXt.js} +1 -1
- package/dist/web/assets/ai-settings-section-BEM1z7kp.js +1 -0
- package/dist/web/assets/{angular-html-CTvM4ulF.js → angular-html-BlcrLjUx.js} +1 -1
- package/dist/web/assets/{angular-ts-B_vSkjco.js → angular-ts-BUjtceAJ.js} +1 -1
- package/dist/web/assets/api-client-D1CePgQ4.js +1 -0
- package/dist/web/assets/api-settings-CNtKkHqW.js +1 -0
- package/dist/web/assets/{api-settings-B2iNXQ0c.js → api-settings-Cubuqjsu.js} +1 -1
- package/dist/web/assets/{api-themes-DrWnJbhs.js → api-themes-YsZgf6Xj.js} +1 -1
- package/dist/web/assets/{apl-DywzTry0.js → apl-B3g0v1pk.js} +1 -1
- package/dist/web/assets/architecture-PBZL5I3N-6konx-J1.js +1 -0
- package/dist/web/assets/{astro-1JaRgaSb.js → astro-FRnyn7cH.js} +1 -1
- package/dist/web/assets/{audio-preview-DAuGvOiK.js → audio-preview-DWTRm4HC.js} +1 -1
- package/dist/web/assets/{blade-CDeeJWBb.js → blade-NLdOqUKw.js} +1 -1
- package/dist/web/assets/{bsl-BfgoPVYR.js → bsl-CenHZfaU.js} +1 -1
- package/dist/web/assets/{button-tNy0ba3y.js → button-Cq8yixjf.js} +1 -1
- package/dist/web/assets/{cairo-C26gNwWW.js → cairo-B_WR0aiQ.js} +1 -1
- package/dist/web/assets/chat-tab-C2e6Pwc2.js +15 -0
- package/dist/web/assets/{cobol-D92nBnON.js → cobol-DOynhWYj.js} +1 -1
- package/dist/web/assets/code-editor-DWtv_VDo.js +10 -0
- package/dist/web/assets/{coffee-CIWH40R1.js → coffee-Bn6dbw4P.js} +1 -1
- package/dist/web/assets/{conflict-editor-CDp0I3mD.js → conflict-editor-Bi2RWfKQ.js} +1 -1
- package/dist/web/assets/{context-menu-BO1c9jKy.js → context-menu-DfWZAxQy.js} +1 -1
- package/dist/web/assets/{cpp-DOrBqXCn.js → cpp-CoZR9t8r.js} +1 -1
- package/dist/web/assets/{crystal-BNKqmHux.js → crystal-BSheONSg.js} +1 -1
- package/dist/web/assets/{csv-preview-D4svNDAq.js → csv-preview-DxGZ1qg5.js} +1 -1
- package/dist/web/assets/{data-grid-overlay-editor-BqV0AxZg.js → data-grid-overlay-editor-gwPoVSCU.js} +1 -1
- package/dist/web/assets/{database-viewer-BCvv_Bwf.js → database-viewer-DSPRzZny.js} +1 -1
- package/dist/web/assets/{dialog-DcLGDwaX.js → dialog-BSfuiTkJ.js} +1 -1
- package/dist/web/assets/{diff-viewer-D41fCFAi.js → diff-viewer-YbpnNv5Y.js} +1 -1
- package/dist/web/assets/{docx-preview-DfaQn9v7.js → docx-preview-DqVDOMmv.js} +1 -1
- package/dist/web/assets/{edge-Dbjp42Pt.js → edge-DTtoslQu.js} +1 -1
- package/dist/web/assets/{edit-diff-preview-BqcUuFCc.js → edit-diff-preview-CXSbSUBX.js} +1 -1
- package/dist/web/assets/{elixir-na3b1pYy.js → elixir-CIBCZ3Zi.js} +1 -1
- package/dist/web/assets/{elm-_Nalil1R.js → elm-BLmb7K1H.js} +1 -1
- package/dist/web/assets/{erb-CW_7dubf.js → erb-BBY8ApRd.js} +1 -1
- package/dist/web/assets/{erlang-DmZ7Zzb0.js → erlang-D5Zb5_P0.js} +1 -1
- package/dist/web/assets/{esm-CwyBetO1.js → esm-6F8v1jLL.js} +1 -1
- package/dist/web/assets/explorer-body-DpQndBVw.js +2 -0
- package/dist/web/assets/explorer-window-content-m0K6Mtcg.js +1 -0
- package/dist/web/assets/{extension-store-DUaZINyC.js → extension-store-Bhu--fPX.js} +1 -1
- package/dist/web/assets/{extension-webview-BW_mccb3.js → extension-webview-BSh6KGd7.js} +1 -1
- package/dist/web/assets/{file-store-yReVbR8q.js → file-store-6HW2Lfl4.js} +1 -1
- package/dist/web/assets/{fortran-fixed-form-DB2A8L4-.js → fortran-fixed-form-CBdwkEUJ.js} +1 -1
- package/dist/web/assets/{fsharp-BYHrti7z.js → fsharp-AT4XdVYH.js} +1 -1
- package/dist/web/assets/{gdresource-D5GBin40.js → gdresource-D852fWBR.js} +1 -1
- package/dist/web/assets/{git-commit-CUiOBoIL.js → git-commit-DWfWAyxy.js} +1 -1
- package/dist/web/assets/{git-log-panel-C1QDkmgl.js → git-log-panel-CkTiR70w.js} +1 -1
- package/dist/web/assets/{git-rebase-DOxGnpdw.js → git-rebase-C6opk8gr.js} +1 -1
- package/dist/web/assets/gitGraph-HDMCJU4V-ikUwsekd.js +1 -0
- package/dist/web/assets/glide-data-grid-BB5mCaCD.js +138 -0
- package/dist/web/assets/{glimmer-js-Begy4UT7.js → glimmer-js-DLi5mWYO.js} +1 -1
- package/dist/web/assets/{glimmer-ts-rPApRY8N.js → glimmer-ts-D-scFX30.js} +1 -1
- package/dist/web/assets/{glsl-wrbPvrBL.js → glsl-dbQzF4iA.js} +1 -1
- package/dist/web/assets/{graphql-Cu4-NR__.js → graphql-D1m5Wen2.js} +1 -1
- package/dist/web/assets/group-chat-tab-BPIOdFJw.js +1 -0
- package/dist/web/assets/{hack-Ddo93XKD.js → hack-CqYmpO7u.js} +1 -1
- package/dist/web/assets/{haml-BFX_1i8j.js → haml-BXzKWEag.js} +1 -1
- package/dist/web/assets/hand-x0ejq3fS.js +1 -0
- package/dist/web/assets/{handlebars-5gFKA4H5.js → handlebars-DXnrtHtq.js} +1 -1
- package/dist/web/assets/{html-C06I-zeO.js → html-DhgQJ-xL.js} +1 -1
- package/dist/web/assets/{html-derivative-e5JLAFZF.js → html-derivative-xZRft-YE.js} +1 -1
- package/dist/web/assets/{http-CrAAhSU7.js → http-B53mSj14.js} +1 -1
- package/dist/web/assets/{hurl-T1V3SX9T.js → hurl-CsomVy04.js} +1 -1
- package/dist/web/assets/{hxml-B056LLKN.js → hxml-LffjWwKA.js} +1 -1
- package/dist/web/assets/{image-preview-DWyuCYT3.js → image-preview-CNA6XMNS.js} +1 -1
- package/dist/web/assets/index-BHdtSKzb.js +52 -0
- package/dist/web/assets/index-DN9XUQ8D.css +2 -0
- package/dist/web/assets/info-3K5VOQVL-BAgmboBp.js +1 -0
- package/dist/web/assets/{input-DouJ9Zvt.js → input-D0ol5jW0.js} +1 -1
- package/dist/web/assets/{jinja-DGdtRh43.js → jinja-CCHURl7V.js} +1 -1
- package/dist/web/assets/{jison-Ah6QobPS.js → jison-7qBd8dnR.js} +1 -1
- package/dist/web/assets/{julia-BJeGOK0K.js → julia-BY9pnTcy.js} +1 -1
- package/dist/web/assets/{just-iw9oRs7j.js → just-D7wRSNRr.js} +1 -1
- package/dist/web/assets/{keybindings-store-0Wqfa7Qp.js → keybindings-store-BGkB72gM.js} +1 -1
- package/dist/web/assets/keybindings-store-Bq6VWl5X.js +1 -0
- package/dist/web/assets/keyboard-DBBaoQSu.js +1 -0
- package/dist/web/assets/{latex-DHjj5hyN.js → latex-RgsCae55.js} +1 -1
- package/dist/web/assets/{liquid-B-FFecDc.js → liquid-CnDFzvOZ.js} +1 -1
- package/dist/web/assets/{lua-DDianJp_.js → lua-0lj5W8sm.js} +1 -1
- package/dist/web/assets/{markdown-renderer-BHiUhQN8.js → markdown-renderer-B9wzBWm9.js} +3 -3
- package/dist/web/assets/markdown-renderer-DeSV1CCQ.js +1 -0
- package/dist/web/assets/{marko-BW5SBVng.js → marko-CQgqAhbV.js} +1 -1
- package/dist/web/assets/{mdc-B9gL9bjl.js → mdc-BkIcTiIy.js} +1 -1
- package/dist/web/assets/{media-url-DUK-k0bB.js → media-url-DDciwvLU.js} +1 -1
- package/dist/web/assets/mobile-bottom-sheet-pjcHKorA.js +1 -0
- package/dist/web/assets/mobile-explorer-sheet-DPdxpnKh.js +1 -0
- package/dist/web/assets/monitor-smartphone-DLZ6Q05s.js +1 -0
- package/dist/web/assets/{nextflow-dTluY6qy.js → nextflow-DvIW68DS.js} +1 -1
- package/dist/web/assets/{nginx-DlwghGI-.js → nginx-O-hft0gG.js} +1 -1
- package/dist/web/assets/{nim-N9jddb5D.js → nim-BxrR4HwQ.js} +1 -1
- package/dist/web/assets/notification-store-BtijuGez.js +1 -0
- package/dist/web/assets/{number-overlay-editor-BVXM7aAs.js → number-overlay-editor-BoLRYuZT.js} +1 -1
- package/dist/web/assets/{overview-panel-DCCDmtCQ.js → overview-panel-l3g1gT5e.js} +1 -1
- package/dist/web/assets/packet-RMMSAZCW-RVlbbDRS.js +1 -0
- package/dist/web/assets/{panel-store-B4rijFKN.js → panel-store-UOV8wrfR.js} +1 -1
- package/dist/web/assets/{pdf-preview-D7yLmAZW.js → pdf-preview-B2CJ_EKv.js} +1 -1
- package/dist/web/assets/{perl-CkmpYKa_.js → perl-Da-uNqmL.js} +1 -1
- package/dist/web/assets/{php-BwZ3KDPo.js → php-JMu45mEx.js} +1 -1
- package/dist/web/assets/pie-UPGHQEXC-ZWb4izpk.js +1 -0
- package/dist/web/assets/{postgres-viewer--CRN1fvO.js → postgres-viewer-23xdZYxp.js} +3 -3
- package/dist/web/assets/process-table-CUsPf7wv.js +1 -0
- package/dist/web/assets/{project-store-BuY7rxn_.js → project-store-DTUCS65m.js} +1 -1
- package/dist/web/assets/{pug-B3rBPqQo.js → pug-CLzyQhia.js} +1 -1
- package/dist/web/assets/{qml-BfDbWFmg.js → qml-BHXfGB1l.js} +1 -1
- package/dist/web/assets/radar-KQ55EAFF-bsP5x1Bx.js +1 -0
- package/dist/web/assets/{razor-B1ww1r38.js → razor-BqetJLN3.js} +1 -1
- package/dist/web/assets/remote-desktop-mobile-sheet-FTF6IBhF.js +2 -0
- package/dist/web/assets/remote-desktop-mobile-view-DSwG4OSk.js +1 -0
- package/dist/web/assets/remote-desktop-stats-overlay-Bz6xZ5y9.js +1 -0
- package/dist/web/assets/remote-desktop-warning-gate-DrYySLZ5.js +1 -0
- package/dist/web/assets/remote-desktop-window-content-Ndvezorb.js +1 -0
- package/dist/web/assets/{rst-C0MSq6m-.js → rst-iQdJZ0BP.js} +1 -1
- package/dist/web/assets/{ruby-ClIZ1wb3.js → ruby-CNwzZGdH.js} +1 -1
- package/dist/web/assets/{run-in-terminal-4efbW2Xv.js → run-in-terminal-CBXmVGWa.js} +1 -1
- package/dist/web/assets/{sas-DR0j3o4_.js → sas-BpBIT2eR.js} +1 -1
- package/dist/web/assets/{scroll-area-ZP8wcwTI.js → scroll-area-lc0ZHORr.js} +1 -1
- package/dist/web/assets/{scss-Vh3JLayR.js → scss-D1MotuEp.js} +1 -1
- package/dist/web/assets/{send-to-chat-BNrEpAyC.js → send-to-chat-DJAdlvJ8.js} +1 -1
- package/dist/web/assets/settings-store-zbPkG7o-.js +2 -0
- package/dist/web/assets/settings-tab-Cn-AqUP_.js +1 -0
- package/dist/web/assets/settings-tab-xyRpt2gq.js +3 -0
- package/dist/web/assets/{shaderlab-0rHRnQ18.js → shaderlab-BNqmcPQi.js} +1 -1
- package/dist/web/assets/{shellsession-Dxdw6D8c.js → shellsession-BBLOVOO-.js} +1 -1
- package/dist/web/assets/{soy-BHlSNj1y.js → soy-BpTGo9uy.js} +1 -1
- package/dist/web/assets/{sparql-B2QaPpni.js → sparql-D7A3yKob.js} +1 -1
- package/dist/web/assets/{sql-query-editor-R_iKjPP9.js → sql-query-editor-BE3mLYyz.js} +1 -1
- package/dist/web/assets/{sqlite-viewer-b2onA0SP.js → sqlite-viewer-wN5RP8sd.js} +1 -1
- package/dist/web/assets/{stata-CwIxjsEJ.js → stata-B7N56ptS.js} +1 -1
- package/dist/web/assets/{surrealql-C6vo-rp-.js → surrealql-CTmnD-lv.js} +1 -1
- package/dist/web/assets/{svelte-LLdJ6rlH.js → svelte-BppwY9TR.js} +1 -1
- package/dist/web/assets/{switch-DV-BDjyk.js → switch-BmjKoIpR.js} +1 -1
- package/dist/web/assets/system-monitor-body-B2M0MC6y.js +2 -0
- package/dist/web/assets/system-monitor-tab-OYca4qaf.js +1 -0
- package/dist/web/assets/{system-monitor-window-content-BPif0azo.js → system-monitor-window-content-CHz9WdTm.js} +1 -1
- package/dist/web/assets/{tab-host-window-content-BnAN6m4K.js → tab-host-window-content-DKev3GJz.js} +1 -1
- package/dist/web/assets/{tab-store-tFnyPmm4.js → tab-store-DzYSsw5a.js} +1 -1
- package/dist/web/assets/team-member-sheet-BQ738Lli.js +2 -0
- package/dist/web/assets/{team-member-window-content-BSyU-5Uc.js → team-member-window-content-CK08f5cK.js} +1 -1
- package/dist/web/assets/{templ-CJGvhEP5.js → templ-CvTMKKra.js} +1 -1
- package/dist/web/assets/{terminal-tab-Ncor0Rfu.js → terminal-tab-DZ3QiYcs.js} +3 -3
- package/dist/web/assets/{tex-psXWhc4t.js → tex--c4POEUE.js} +1 -1
- package/dist/web/assets/{tool-cards-DdVP1wrD.js → tool-cards-Bw1P0MKU.js} +4 -4
- package/dist/web/assets/treemap-KZPCXAKY-Dh1oxNdQ.js +1 -0
- package/dist/web/assets/{ts-tags-D5Ar_ZCO.js → ts-tags--kx7D-Hw.js} +1 -1
- package/dist/web/assets/{twig-vJgGC-qY.js → twig-UG9uMHGd.js} +1 -1
- package/dist/web/assets/{use-blob-url-BOVGWg9F.js → use-blob-url-Bdxopwwc.js} +1 -1
- package/dist/web/assets/{use-monaco-theme-BshnLXoY.js → use-monaco-theme-BOmd2wpe.js} +1 -1
- package/dist/web/assets/{use-open-team-member-CY3wOdWL.js → use-open-team-member-D9EOB8-i.js} +1 -1
- package/dist/web/assets/use-visual-viewport-Sfb6CZ80.js +1 -0
- package/dist/web/assets/{use-websocket-B7xjNsyD.js → use-websocket-B7Xj-Q4W.js} +1 -1
- package/dist/web/assets/{vendor-mermaid-999ksD45.js → vendor-mermaid-0Nt-ew76.js} +1 -1
- package/dist/web/assets/{video-preview-7F72sRwf.js → video-preview-B0nbTGjQ.js} +1 -1
- package/dist/web/assets/{vue-html-BN5ZLu-H.js → vue-html-8KEW6-I9.js} +1 -1
- package/dist/web/assets/{vue-BWngFyav.js → vue-jjTomlbt.js} +1 -1
- package/dist/web/assets/{vue-vine-BZHnO8yV.js → vue-vine-C5tohCkU.js} +1 -1
- package/dist/web/assets/{window-pip-registry-BnYU83LE.js → window-pip-registry-FdZdGF7h.js} +1 -1
- package/dist/web/assets/window-store-D0x_fxr0.js +1 -0
- package/dist/web/assets/{xml-DS2beZHp.js → xml-ByFFosqL.js} +1 -1
- package/dist/web/assets/{xsl-Ciiku1Bq.js → xsl-CZUwiLkj.js} +1 -1
- package/dist/web/index.html +46 -43
- package/dist/web/sw.js +1 -1
- package/docs/architecture/ai-chat-and-providers.md +3 -0
- package/package.json +1 -1
- package/packages/vscode-compat/node_modules/.bin/tsc.bunx +0 -0
- package/packages/vscode-compat/node_modules/.bin/tsc.exe +0 -0
- package/packages/vscode-compat/node_modules/.bin/tsserver.bunx +0 -0
- package/packages/vscode-compat/node_modules/.bin/tsserver.exe +0 -0
- package/src/cli/commands/backup-cmd.ts +119 -0
- package/src/index.ts +3 -0
- package/src/providers/claude-agent-sdk.ts +29 -1
- package/src/server/index.ts +42 -15
- package/src/server/routes/accounts.ts +30 -2
- package/src/server/routes/chat.ts +8 -1
- package/src/server/routes/named-tunnel.ts +5 -2
- package/src/server/routes/remote-desktop.ts +64 -0
- package/src/server/routes/settings.ts +2 -0
- package/src/server/ws/chat.ts +126 -7
- package/src/server/ws/remote-desktop.ts +86 -0
- package/src/services/account.service.ts +106 -23
- package/src/services/db-backup/db-backup-paths.ts +66 -0
- package/src/services/db-backup/db-backup-restore.ts +105 -0
- package/src/services/db-backup/db-backup-retention.ts +96 -0
- package/src/services/db-backup/db-backup-sync.ts +113 -0
- package/src/services/db-backup/db-backup.service.ts +61 -0
- package/src/services/db.service.ts +29 -1
- package/src/services/edge-forwarder.ts +191 -191
- package/src/services/fs-credential-path-guard.ts +19 -1
- package/src/services/named-tunnel/cloudflare-dns-api.ts +20 -4
- package/src/services/named-tunnel/cloudflared-login.service.ts +2 -2
- package/src/services/named-tunnel/named-tunnel-args.ts +17 -3
- package/src/services/named-tunnel/named-tunnel-setup.service.ts +20 -2
- package/src/services/nested-subagent-spy.ts +206 -0
- package/src/services/remote-desktop/access-unit-assembler.ts +90 -0
- package/src/services/remote-desktop/avc1-codec-string.ts +18 -0
- package/src/services/remote-desktop/nal-splitter.ts +100 -0
- package/src/services/remote-desktop/remote-desktop-capture.ts +120 -0
- package/src/services/remote-desktop/remote-desktop-encoder-args.ts +42 -0
- package/src/services/remote-desktop/remote-desktop-flag.ts +12 -0
- package/src/services/remote-desktop/remote-desktop-input.ts +217 -0
- package/src/services/remote-desktop/remote-desktop-nonce.ts +38 -0
- package/src/services/remote-desktop/remote-desktop-session.ts +189 -0
- package/src/services/remote-desktop/remote-desktop-vk-map.ts +41 -0
- package/src/services/subagent-transcript-merger.ts +92 -75
- package/src/services/subprocess-retention.ts +29 -6
- package/src/services/supervisor.ts +58 -0
- package/src/services/team-member-activity/subagent-transcript-index.ts +56 -0
- package/src/services/windows-process-tree.ts +351 -351
- package/src/types/chat.ts +2 -0
- package/src/web/app.tsx +8 -0
- package/src/web/components/chat/account-dialogs.tsx +11 -4
- package/src/web/components/chat/question-card.tsx +9 -2
- package/src/web/components/chat/usage-badge.tsx +17 -3
- package/src/web/components/floating-window/window-content-registry.ts +2 -0
- package/src/web/components/floating-window/window-store-types.ts +1 -1
- package/src/web/components/layout/mobile-drawer.tsx +38 -27
- package/src/web/components/layout/nav-section-rail.tsx +10 -2
- package/src/web/components/remote-desktop/open-remote-desktop.ts +26 -0
- package/src/web/components/remote-desktop/remote-desktop-coords.ts +98 -0
- package/src/web/components/remote-desktop/remote-desktop-frame-timestamp.ts +21 -0
- package/src/web/components/remote-desktop/remote-desktop-gesture-classifiers.ts +56 -0
- package/src/web/components/remote-desktop/remote-desktop-mobile-key-bar.tsx +131 -0
- package/src/web/components/remote-desktop/remote-desktop-mobile-sheet.tsx +48 -0
- package/src/web/components/remote-desktop/remote-desktop-mobile-toolbar.tsx +79 -0
- package/src/web/components/remote-desktop/remote-desktop-mobile-view.tsx +190 -0
- package/src/web/components/remote-desktop/remote-desktop-stats-overlay.tsx +54 -0
- package/src/web/components/remote-desktop/remote-desktop-stats.ts +37 -0
- package/src/web/components/remote-desktop/remote-desktop-touch-gesture-tracker.ts +195 -0
- package/src/web/components/remote-desktop/remote-desktop-warning-gate.tsx +106 -0
- package/src/web/components/remote-desktop/remote-desktop-window-content.tsx +86 -0
- package/src/web/components/remote-desktop/remote-desktop-ws-url.ts +22 -0
- package/src/web/components/remote-desktop/use-h264-canvas-decoder.ts +175 -0
- package/src/web/components/remote-desktop/use-remote-desktop-available.ts +29 -0
- package/src/web/components/remote-desktop/use-remote-desktop-connection.ts +147 -0
- package/src/web/components/remote-desktop/use-remote-desktop-mobile-open-state.ts +18 -0
- package/src/web/components/remote-desktop/use-remote-desktop-touch.ts +224 -0
- package/src/web/components/remote-desktop/use-remote-desktop-virtual-keyboard.ts +50 -0
- package/src/web/components/remote-desktop/use-remote-input-capture.ts +92 -0
- package/src/web/components/tunnels/named-tunnel/named-tunnel-copy.ts +59 -59
- package/src/web/components/tunnels/named-tunnel/named-tunnel-section.tsx +2 -2
- package/src/web/hooks/use-notification-badge.ts +19 -11
- package/src/web/lib/feature-badges.ts +2 -0
- package/src/web/stores/settings-store.ts +25 -0
- package/src/web/stores/streaming-store.ts +21 -2
- package/dist/web/assets/ai-settings-section-DY-t1bK8.js +0 -1
- package/dist/web/assets/api-client-BogqsFna.js +0 -1
- package/dist/web/assets/api-settings-DRF9oYkn.js +0 -1
- package/dist/web/assets/architecture-PBZL5I3N-nw5-j_I_.js +0 -1
- package/dist/web/assets/chat-tab-IcnU3nqQ.js +0 -15
- package/dist/web/assets/code-editor-Dno5Srkm.js +0 -10
- package/dist/web/assets/explorer-body-DB1L30pa.js +0 -2
- package/dist/web/assets/explorer-window-content-Cwlt3C3I.js +0 -1
- package/dist/web/assets/gitGraph-HDMCJU4V-C1xf87gN.js +0 -1
- package/dist/web/assets/glide-data-grid-Dz8lx6n2.js +0 -138
- package/dist/web/assets/group-chat-tab-DFwr-pig.js +0 -1
- package/dist/web/assets/index-DWpNR3nB.css +0 -2
- package/dist/web/assets/index-oUKV5nIA.js +0 -52
- package/dist/web/assets/info-3K5VOQVL-C2z7FTDe.js +0 -1
- package/dist/web/assets/keybindings-store-d54pv2NK.js +0 -1
- package/dist/web/assets/markdown-renderer-CnE36_gd.js +0 -1
- package/dist/web/assets/mobile-bottom-sheet-CJ7ApOkI.js +0 -1
- package/dist/web/assets/mobile-explorer-sheet-BzjlS1Xn.js +0 -1
- package/dist/web/assets/notification-store-C7UBqxYP.js +0 -1
- package/dist/web/assets/packet-RMMSAZCW-DQUxN0wU.js +0 -1
- package/dist/web/assets/pie-UPGHQEXC-BOr9ek4Q.js +0 -1
- package/dist/web/assets/process-table-CXC10GSy.js +0 -1
- package/dist/web/assets/radar-KQ55EAFF-DSErxZFb.js +0 -1
- package/dist/web/assets/settings-store-OLJE7dPH.js +0 -2
- package/dist/web/assets/settings-tab-B1rvHFc6.js +0 -3
- package/dist/web/assets/settings-tab-BUlmPJ2w.js +0 -1
- package/dist/web/assets/system-monitor-body-DPYQlBEt.js +0 -2
- package/dist/web/assets/system-monitor-tab-CPQdWv27.js +0 -1
- package/dist/web/assets/team-member-sheet-Bu2wG1sm.js +0 -2
- package/dist/web/assets/treemap-KZPCXAKY-C17p0LQw.js +0 -1
- package/dist/web/assets/window-store-CQ1lghvv.js +0 -1
- /package/dist/web/assets/{abap-CjzrhAS2.js → abap-C5DFF0s3.js} +0 -0
- /package/dist/web/assets/{actionscript-3-B59Kbl-9.js → actionscript-3-B0qyRNbc.js} +0 -0
- /package/dist/web/assets/{ada-B7gItKAE.js → ada-bAJzplug.js} +0 -0
- /package/dist/web/assets/{andromeeda-Dy5zcy0Q.js → andromeeda-DUCpISy6.js} +0 -0
- /package/dist/web/assets/{apache-B6T9jrc0.js → apache-BhAr84Z_.js} +0 -0
- /package/dist/web/assets/{apex-CTsIxDQw.js → apex-CRv8YnNq.js} +0 -0
- /package/dist/web/assets/{api-client-oY1tC3tM.js → api-client-BzWM7XPR.js} +0 -0
- /package/dist/web/assets/{applescript-DuycKmmb.js → applescript-CD-Cw8bt.js} +0 -0
- /package/dist/web/assets/{apply-theme-r8tKDrCR.js → apply-theme-DJ794zSQ.js} +0 -0
- /package/dist/web/assets/{ara-CISL3599.js → ara-BUzeQYoG.js} +0 -0
- /package/dist/web/assets/{asciidoc-BXyE2Lpr.js → asciidoc-CT2hTg-v.js} +0 -0
- /package/dist/web/assets/{asm-y258phtP.js → asm-CAu35JeY.js} +0 -0
- /package/dist/web/assets/{aurora-x-Dq3Jc78M.js → aurora-x-BeFJoIA1.js} +0 -0
- /package/dist/web/assets/{awk-DxL6XSJv.js → awk-Vau-DrGN.js} +0 -0
- /package/dist/web/assets/{ayu-dark-DjoK6Evo.js → ayu-dark-CLe-OTsQ.js} +0 -0
- /package/dist/web/assets/{ayu-light-CKKXBLMI.js → ayu-light-CJbQfu4M.js} +0 -0
- /package/dist/web/assets/{ayu-mirage-DGXSrrXB.js → ayu-mirage-GWaKwdrx.js} +0 -0
- /package/dist/web/assets/{ballerina-DZSXFmUf.js → ballerina-DWu0USI5.js} +0 -0
- /package/dist/web/assets/{bat-BNsZk6nY.js → bat-_07Huw7E.js} +0 -0
- /package/dist/web/assets/{beancount-cPl4gJct.js → beancount-EcvogrcF.js} +0 -0
- /package/dist/web/assets/{berry-DGAodERk.js → berry-BBB87SY7.js} +0 -0
- /package/dist/web/assets/{bibtex-DftVoRif.js → bibtex-DbueS87A.js} +0 -0
- /package/dist/web/assets/{bicep-Suioyyzh.js → bicep-tcmXnUo4.js} +0 -0
- /package/dist/web/assets/{bird2-DjU24I2P.js → bird2-B2qwilIR.js} +0 -0
- /package/dist/web/assets/{c-DFi8Y_nN.js → c-Z58oivSt.js} +0 -0
- /package/dist/web/assets/{c3-1eSHnH5V.js → c3-U40cGT9p.js} +0 -0
- /package/dist/web/assets/{cadence-DLLsFwQP.js → cadence-BFcD-LyM.js} +0 -0
- /package/dist/web/assets/{can-open-in-ppm-in727eFQ.js → can-open-in-ppm-CdCK2c2a.js} +0 -0
- /package/dist/web/assets/{catppuccin-frappe-DDj0JEiY.js → catppuccin-frappe-RkC9AX6J.js} +0 -0
- /package/dist/web/assets/{catppuccin-latte-DPyANHyR.js → catppuccin-latte-Bsi4l-nO.js} +0 -0
- /package/dist/web/assets/{catppuccin-macchiato-CO-h8VTT.js → catppuccin-macchiato-B8JIdTW-.js} +0 -0
- /package/dist/web/assets/{catppuccin-mocha-BCmbN2IZ.js → catppuccin-mocha-oH8wwrDj.js} +0 -0
- /package/dist/web/assets/{clarity-C0pYsZjs.js → clarity-C3WNeRCR.js} +0 -0
- /package/dist/web/assets/{clipboard-DBLGKMrH.js → clipboard-B-gwJ8DF.js} +0 -0
- /package/dist/web/assets/{clojure-JFr6s8iZ.js → clojure-CFoAz7u9.js} +0 -0
- /package/dist/web/assets/{cmake-EEcLEgVy.js → cmake-Bcsqp9yR.js} +0 -0
- /package/dist/web/assets/{codeowners-Deh3Lx0u.js → codeowners-DC2Z7heP.js} +0 -0
- /package/dist/web/assets/{codeql-C317igT5.js → codeql-BfG68Al5.js} +0 -0
- /package/dist/web/assets/{common-lisp-DepbATZs.js → common-lisp-Z5P-lKQe.js} +0 -0
- /package/dist/web/assets/{coq-MxgxU5ya.js → coq-BJslqrnv.js} +0 -0
- /package/dist/web/assets/{csharp-eEFXdsAq.js → csharp-DhhZTOTs.js} +0 -0
- /package/dist/web/assets/{css-Db-syWW9.js → css-DiFIpMfE.js} +0 -0
- /package/dist/web/assets/{csv-DHeYcbaq.js → csv-DViLbHxI.js} +0 -0
- /package/dist/web/assets/{csv-parser-_JWMLGl3.js → csv-parser-B2v8MoUy.js} +0 -0
- /package/dist/web/assets/{cue-BmUNowrt.js → cue-CoG3mg2I.js} +0 -0
- /package/dist/web/assets/{cypher-XjXHgCNS.js → cypher-jTxC8NAB.js} +0 -0
- /package/dist/web/assets/{d-BWJnZtig.js → d-6hKIUAbU.js} +0 -0
- /package/dist/web/assets/{dark-plus-Db7umas1.js → dark-plus-D_-np2Sr.js} +0 -0
- /package/dist/web/assets/{dart-DSI4HYh1.js → dart-CVpxLpVn.js} +0 -0
- /package/dist/web/assets/{data-grid-types-CfVHh9gv.js → data-grid-types-Dq_H1IPa.js} +0 -0
- /package/dist/web/assets/{dax-yw1JsAni.js → dax-BPqJ8qIY.js} +0 -0
- /package/dist/web/assets/{desktop-C2QO4RvD.js → desktop-u_y5_Hr5.js} +0 -0
- /package/dist/web/assets/{diff-Drt8aEyp.js → diff-Duj1XMrd.js} +0 -0
- /package/dist/web/assets/{dist-DNRvhVdz.js → dist-CEZ4plgH.js} +0 -0
- /package/dist/web/assets/{dist-VBxOHLmN.js → dist-aDSxTR_v.js} +0 -0
- /package/dist/web/assets/{docker-ByDKbmLl.js → docker-8UGSCc7N.js} +0 -0
- /package/dist/web/assets/{dotenv-BkfOLytR.js → dotenv-DVnj8kyR.js} +0 -0
- /package/dist/web/assets/{dracula-DAmrm-Un.js → dracula-D0cEmSg7.js} +0 -0
- /package/dist/web/assets/{dracula-soft-BUOEnKqR.js → dracula-soft-DfGS5Yfr.js} +0 -0
- /package/dist/web/assets/{dream-maker-DQBIfSiO.js → dream-maker-qG8kMGTF.js} +0 -0
- /package/dist/web/assets/{emacs-lisp-Blh5kBD3.js → emacs-lisp-iVqyMLyT.js} +0 -0
- /package/dist/web/assets/{everforest-dark-CEzhDxHq.js → everforest-dark-Cruygd5U.js} +0 -0
- /package/dist/web/assets/{everforest-light-CPk_5Khm.js → everforest-light-BiJa8RHL.js} +0 -0
- /package/dist/web/assets/{fennel-B1MT_EBZ.js → fennel-H6XSEIoz.js} +0 -0
- /package/dist/web/assets/{fish-C_HGcewt.js → fish-ho_7CxX6.js} +0 -0
- /package/dist/web/assets/{fluent-Dp1Iclmx.js → fluent-1l5ssJTF.js} +0 -0
- /package/dist/web/assets/{format-bytes-53WBtyi2.js → format-bytes-gQw5wzqa.js} +0 -0
- /package/dist/web/assets/{fortran-free-form-B_-UA4XC.js → fortran-free-form-Bo1x233A.js} +0 -0
- /package/dist/web/assets/{gdscript-Bm_JpjS_.js → gdscript-DffFMp_k.js} +0 -0
- /package/dist/web/assets/{gdshader-C_PNmfYU.js → gdshader-CvnpAiAF.js} +0 -0
- /package/dist/web/assets/{genie-B5rmR2TF.js → genie-2eOvAEzW.js} +0 -0
- /package/dist/web/assets/{gherkin-BTJ34TJ9.js → gherkin-DL-5OUlV.js} +0 -0
- /package/dist/web/assets/{github-dark-DOkNVj9H.js → github-dark-DrTZprgG.js} +0 -0
- /package/dist/web/assets/{github-dark-default-DTsmiyVo.js → github-dark-default-_mDH8IMP.js} +0 -0
- /package/dist/web/assets/{github-dark-dimmed-CjbxAe9s.js → github-dark-dimmed-C48hJuLn.js} +0 -0
- /package/dist/web/assets/{github-dark-high-contrast-r_M8F_k7.js → github-dark-high-contrast-BQ-zGGNe.js} +0 -0
- /package/dist/web/assets/{github-light-BzfxyMyb.js → github-light-BYc90Yx8.js} +0 -0
- /package/dist/web/assets/{github-light-default-BOqiBIy5.js → github-light-default-Daecj4J5.js} +0 -0
- /package/dist/web/assets/{github-light-high-contrast-DQzBrDM4.js → github-light-high-contrast-DoafCbsU.js} +0 -0
- /package/dist/web/assets/{gleam-BWmuAtD8.js → gleam-hUWWjmnp.js} +0 -0
- /package/dist/web/assets/{gn-CNQPjiZW.js → gn-7PJ3R70o.js} +0 -0
- /package/dist/web/assets/{gnuplot-BlHjcPMe.js → gnuplot-V-Pxck7b.js} +0 -0
- /package/dist/web/assets/{go-Bn8Ba9uz.js → go-ARF6DprD.js} +0 -0
- /package/dist/web/assets/{groovy-C1Y1_F4V.js → groovy-CKGBIPOz.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-hard-DueOsHav.js → gruvbox-dark-hard-Cm9wqdI3.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-medium-DaQjD538.js → gruvbox-dark-medium-Cmsd6dG8.js} +0 -0
- /package/dist/web/assets/{gruvbox-dark-soft-CbbX2WOm.js → gruvbox-dark-soft-BBGlGGCz.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-hard-E2M70ljZ.js → gruvbox-light-hard-BCR7KmqG.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-medium-B_DDLY6h.js → gruvbox-light-medium-CP32bga8.js} +0 -0
- /package/dist/web/assets/{gruvbox-light-soft-2SZmyC1F.js → gruvbox-light-soft-qjxk8ps6.js} +0 -0
- /package/dist/web/assets/{haskell-D8DeoXtG.js → haskell-C5DzwGRa.js} +0 -0
- /package/dist/web/assets/{haxe-BmZm7B0W.js → haxe-C8f_jCVv.js} +0 -0
- /package/dist/web/assets/{hcl-CKnBM3ng.js → hcl-DetJkf6E.js} +0 -0
- /package/dist/web/assets/{hjson-Ct9t4-ci.js → hjson-DeZhwIdo.js} +0 -0
- /package/dist/web/assets/{hlsl-BZSASBqS.js → hlsl-SMIDLRyX.js} +0 -0
- /package/dist/web/assets/{horizon-CzX1AZ08.js → horizon-BMAVDxMx.js} +0 -0
- /package/dist/web/assets/{horizon-bright-CCVNeRIP.js → horizon-bright-3BaV_VGD.js} +0 -0
- /package/dist/web/assets/{houston-DBlJN7xc.js → houston-BWBbEPvb.js} +0 -0
- /package/dist/web/assets/{hy-Dyc6h6Xb.js → hy-DcsmE-Nm.js} +0 -0
- /package/dist/web/assets/{image-gallery-DujlrjOC.js → image-gallery-Bqb-wjNw.js} +0 -0
- /package/dist/web/assets/{imba-Bauru7s2.js → imba-CnkgnYMf.js} +0 -0
- /package/dist/web/assets/{ini-SjdwtaTp.js → ini-BnvTdau7.js} +0 -0
- /package/dist/web/assets/{java-D2r0ocpf.js → java-BLFACXat.js} +0 -0
- /package/dist/web/assets/{javascript-y-lof7iP.js → javascript-BKcFWENk.js} +0 -0
- /package/dist/web/assets/{json-DO4LSx5A.js → json-3AJtJADf.js} +0 -0
- /package/dist/web/assets/{json5-BEwWf-W3.js → json5-DLyDVlwe.js} +0 -0
- /package/dist/web/assets/{jsonc-OBEFv7gP.js → jsonc-B5DgF1jW.js} +0 -0
- /package/dist/web/assets/{jsonl-DOyW4Mg3.js → jsonl-CV_O71a2.js} +0 -0
- /package/dist/web/assets/{jsonnet-BL5R2CpP.js → jsonnet-DZfPLgYz.js} +0 -0
- /package/dist/web/assets/{jssm-ksPusAyg.js → jssm-Cr1zYOsY.js} +0 -0
- /package/dist/web/assets/{jsx-B6XEu6O-.js → jsx-VN-JS2_q.js} +0 -0
- /package/dist/web/assets/{kanagawa-dragon-JBVe_ji_.js → kanagawa-dragon-D9CcT3xg.js} +0 -0
- /package/dist/web/assets/{kanagawa-lotus-ChprO68_.js → kanagawa-lotus-DLXsB8fr.js} +0 -0
- /package/dist/web/assets/{kanagawa-wave-D_uB_R0Q.js → kanagawa-wave-weDs3Wjz.js} +0 -0
- /package/dist/web/assets/{kdl-pf5B8N_m.js → kdl-0XF56jnT.js} +0 -0
- /package/dist/web/assets/{kotlin-BdF0METC.js → kotlin-BPkH9vzk.js} +0 -0
- /package/dist/web/assets/{kusto-BneVP-OU.js → kusto-DMVpADb9.js} +0 -0
- /package/dist/web/assets/{laserwave-C4-UPIyG.js → laserwave-Cl6T2cGP.js} +0 -0
- /package/dist/web/assets/{lean-BN6Jdymq.js → lean-BvwzQGl1.js} +0 -0
- /package/dist/web/assets/{less-Cx-fdlDC.js → less-DI_03xkC.js} +0 -0
- /package/dist/web/assets/{lib-CMuBa85c.js → lib-v9x_qXgI.js} +0 -0
- /package/dist/web/assets/{libesm-BB74PRsH.js → libesm-dcbw4_18.js} +0 -0
- /package/dist/web/assets/{light-plus-DFnnn_iL.js → light-plus-hIiIsQN_.js} +0 -0
- /package/dist/web/assets/{llvm-BGxtW0Nw.js → llvm-B5hjjSWc.js} +0 -0
- /package/dist/web/assets/{loader-circle-Dpqcv0eS.js → loader-circle-DouKgIXy.js} +0 -0
- /package/dist/web/assets/{lock-DldbT31x.js → lock-CNxUGpTk.js} +0 -0
- /package/dist/web/assets/{log-kQb4eJnj.js → log-D28raQiE.js} +0 -0
- /package/dist/web/assets/{logo-DqAzaq7s.js → logo-6Cdd4ASb.js} +0 -0
- /package/dist/web/assets/{luau-Dc2cp-zS.js → luau-CK9nKpOX.js} +0 -0
- /package/dist/web/assets/{make-DYg9dwaJ.js → make-CVnxKCB6.js} +0 -0
- /package/dist/web/assets/{markdown-O4EPpS3K.js → markdown-B9mMQzD5.js} +0 -0
- /package/dist/web/assets/{material-theme-ClP8yfQ2.js → material-theme-BZSs2o0j.js} +0 -0
- /package/dist/web/assets/{material-theme-darker-LOK55hGu.js → material-theme-darker-BeAQBzQ_.js} +0 -0
- /package/dist/web/assets/{material-theme-lighter-j72tzZqi.js → material-theme-lighter-BAe0IXcs.js} +0 -0
- /package/dist/web/assets/{material-theme-ocean-C19LKClA.js → material-theme-ocean-CiPIf0Np.js} +0 -0
- /package/dist/web/assets/{material-theme-palenight-DJ5tN2ax.js → material-theme-palenight-DJBb4_XN.js} +0 -0
- /package/dist/web/assets/{matlab-DQNZ_NU8.js → matlab-CY0dbp_s.js} +0 -0
- /package/dist/web/assets/{mdx-CJayB7XK.js → mdx-DU9zbF7h.js} +0 -0
- /package/dist/web/assets/{mermaid-Cb0TRX92.js → mermaid-UmmjGwdh.js} +0 -0
- /package/dist/web/assets/{min-dark-CWM8Bo-m.js → min-dark-C59GkzOm.js} +0 -0
- /package/dist/web/assets/{min-light-BBR1sjS2.js → min-light-B9QglrdQ.js} +0 -0
- /package/dist/web/assets/{mipsasm-Jc7u7YSS.js → mipsasm-Dc6Jsr_D.js} +0 -0
- /package/dist/web/assets/{mojo-CTB0cutd.js → mojo-D5ij-nGW.js} +0 -0
- /package/dist/web/assets/{monitor-4vWfqzuN.js → monitor-CxPV_fQI.js} +0 -0
- /package/dist/web/assets/{monokai-CvM_YNKx.js → monokai-QAtE_yCP.js} +0 -0
- /package/dist/web/assets/{moonbit-CbFEfYYM.js → moonbit-BMtIdiTL.js} +0 -0
- /package/dist/web/assets/{move-BT38p0kT.js → move-BB_jMHYZ.js} +0 -0
- /package/dist/web/assets/{narrat-55QZ_kOG.js → narrat-BvvoKGVn.js} +0 -0
- /package/dist/web/assets/{nextflow-groovy-Bg8peyqg.js → nextflow-groovy-CqxtAsnF.js} +0 -0
- /package/dist/web/assets/{night-owl-DzS77iPK.js → night-owl-CLoE9kjt.js} +0 -0
- /package/dist/web/assets/{night-owl-light-DER7fGnx.js → night-owl-light-BwIFT5Xd.js} +0 -0
- /package/dist/web/assets/{nix-BSI8YesN.js → nix-BKlnVT9r.js} +0 -0
- /package/dist/web/assets/{nord-9bn7iNWo.js → nord-B7R1KRAK.js} +0 -0
- /package/dist/web/assets/{nushell-qDVZn_lw.js → nushell-CzUme-NJ.js} +0 -0
- /package/dist/web/assets/{objective-c-Blf-ATos.js → objective-c-D-UKKiOc.js} +0 -0
- /package/dist/web/assets/{objective-cpp-Bz8bAq47.js → objective-cpp-BucWn4Fx.js} +0 -0
- /package/dist/web/assets/{ocaml-B9heAsc3.js → ocaml-EJCNXMGo.js} +0 -0
- /package/dist/web/assets/{odin-BaFYnOwO.js → odin-CEjnRU_3.js} +0 -0
- /package/dist/web/assets/{one-dark-pro-D6B_hSe-.js → one-dark-pro-Bv469NX5.js} +0 -0
- /package/dist/web/assets/{one-light-Ba-f0wyk.js → one-light-BlzO5RWI.js} +0 -0
- /package/dist/web/assets/{openscad-UvYyL2MK.js → openscad-Czl6alLw.js} +0 -0
- /package/dist/web/assets/{pascal-B4-qvEoG.js → pascal-BI9fLvG4.js} +0 -0
- /package/dist/web/assets/{pkl-B146nqGx.js → pkl-ChQLsyjU.js} +0 -0
- /package/dist/web/assets/{plastic-L38t-Id3.js → plastic-DGL3mtdL.js} +0 -0
- /package/dist/web/assets/{play-CpCAoQRZ.js → play-DpEEBJTT.js} +0 -0
- /package/dist/web/assets/{plsql-ItOG5Cca.js → plsql-BJDjqcWp.js} +0 -0
- /package/dist/web/assets/{po-imiCTaNX.js → po-DK5O3OpF.js} +0 -0
- /package/dist/web/assets/{poimandres-Bu6sIba9.js → poimandres-CnuqZT4T.js} +0 -0
- /package/dist/web/assets/{polar-CTeGoFxV.js → polar-DCsUxSsQ.js} +0 -0
- /package/dist/web/assets/{portal-container-context-a399Hd9F.js → portal-container-context-D-495m5C.js} +0 -0
- /package/dist/web/assets/{postcss-jvFe6GNE.js → postcss-8UjdFmis.js} +0 -0
- /package/dist/web/assets/{powerquery-D9Dw1hJp.js → powerquery-CAp0CGQj.js} +0 -0
- /package/dist/web/assets/{powershell-CZqvYQtg.js → powershell-CbBsKZJP.js} +0 -0
- /package/dist/web/assets/{prisma-B4D2nO_7.js → prisma-BnNpXCJN.js} +0 -0
- /package/dist/web/assets/{prolog-CQbyUcCF.js → prolog-CC-l9E8Y.js} +0 -0
- /package/dist/web/assets/{proto-Cwpo0cW1.js → proto-CV_1k2rL.js} +0 -0
- /package/dist/web/assets/{puppet-CKxCwBod.js → puppet-2D8HU2bv.js} +0 -0
- /package/dist/web/assets/{purescript-B-MyXMMp.js → purescript-BEJAORgU.js} +0 -0
- /package/dist/web/assets/{python-D6QbSR2S.js → python-BBdbb7nn.js} +0 -0
- /package/dist/web/assets/{qmldir-BpTSVcw_.js → qmldir-D5ZYI4Ie.js} +0 -0
- /package/dist/web/assets/{qss-DBc4gbpe.js → qss-BOJVGyXE.js} +0 -0
- /package/dist/web/assets/{r-i9jMMi0s.js → r-B5dlEtkj.js} +0 -0
- /package/dist/web/assets/{racket-BWNaxU7C.js → racket-BUAEygJ6.js} +0 -0
- /package/dist/web/assets/{raku-fqTZLn7L.js → raku-BG0Ss0ID.js} +0 -0
- /package/dist/web/assets/{react-X4jUntEf.js → react-B549sK_T.js} +0 -0
- /package/dist/web/assets/{red-D-JEcuzA.js → red-C5yyQbtG.js} +0 -0
- /package/dist/web/assets/{refresh-cw-B9iv7csb.js → refresh-cw-BQU5g2CY.js} +0 -0
- /package/dist/web/assets/{reg-BbirlU1S.js → reg-DHoBWtLf.js} +0 -0
- /package/dist/web/assets/{regexp-D8VTFrtg.js → regexp-DDtJrlKv.js} +0 -0
- /package/dist/web/assets/{rel-ByQGKiNM.js → rel-CR6WJDRD.js} +0 -0
- /package/dist/web/assets/{resolve-theme-Cqb9zWYc.js → resolve-theme-4fexHWl6.js} +0 -0
- /package/dist/web/assets/{riscv-B5nqCfLE.js → riscv-2V9tC-nx.js} +0 -0
- /package/dist/web/assets/{ron-DOVh_iZR.js → ron-QLTmqGUw.js} +0 -0
- /package/dist/web/assets/{rose-pine-jp51ZUzi.js → rose-pine-be0__X3A.js} +0 -0
- /package/dist/web/assets/{rose-pine-dawn-B7hkmmWy.js → rose-pine-dawn-n_WQJwsW.js} +0 -0
- /package/dist/web/assets/{rose-pine-moon-Ff063yLj.js → rose-pine-moon-t2aLtzgB.js} +0 -0
- /package/dist/web/assets/{rosmsg-Cyk7Il8z.js → rosmsg-Ce1JD8vt.js} +0 -0
- /package/dist/web/assets/{rotate-ccw-DnuPzJRE.js → rotate-ccw-CpjRo90J.js} +0 -0
- /package/dist/web/assets/{rust-DRNM5epF.js → rust-5fHH-ZoW.js} +0 -0
- /package/dist/web/assets/{sass-D_cB0-M1.js → sass-DPmGFiOJ.js} +0 -0
- /package/dist/web/assets/{save-DxonD4y1.js → save-DYLiVzU8.js} +0 -0
- /package/dist/web/assets/{scala-Crl-1kKS.js → scala-BRMVFbZF.js} +0 -0
- /package/dist/web/assets/{scheme-DCaWyEJ0.js → scheme-BMSJWevF.js} +0 -0
- /package/dist/web/assets/{sdbl-BbXNqwtX.js → sdbl-DTZ-pEHI.js} +0 -0
- /package/dist/web/assets/{send-B-t5Mz26.js → send-C_sLFt56.js} +0 -0
- /package/dist/web/assets/{shallow-4zAfs2KW.js → shallow-CmPMAPte.js} +0 -0
- /package/dist/web/assets/{shellscript-BAgksBhW.js → shellscript-6-9yg5Cz.js} +0 -0
- /package/dist/web/assets/{shield-check-CdTiK1j2.js → shield-check-CUI36aSc.js} +0 -0
- /package/dist/web/assets/{shield-off-DuetkXKM.js → shield-off-GdkkVhb3.js} +0 -0
- /package/dist/web/assets/{slack-dark-GB-ElUW_.js → slack-dark-DKJgiR7e.js} +0 -0
- /package/dist/web/assets/{slack-ochin-tU6CFD3S.js → slack-ochin-WjehVgyK.js} +0 -0
- /package/dist/web/assets/{smalltalk-DSVGUbtL.js → smalltalk-BChf4SN1.js} +0 -0
- /package/dist/web/assets/{snazzy-light-DZhi3PoS.js → snazzy-light-C1WAwOx6.js} +0 -0
- /package/dist/web/assets/{solarized-dark-D6AMCco7.js → solarized-dark-BxREZrGx.js} +0 -0
- /package/dist/web/assets/{solarized-light-DepuTWSE.js → solarized-light-DG29B1pR.js} +0 -0
- /package/dist/web/assets/{solidity-CsxN618l.js → solidity-bDbtGwdR.js} +0 -0
- /package/dist/web/assets/{splunk-CF1m0Kkv.js → splunk-Ceqx5njI.js} +0 -0
- /package/dist/web/assets/{sql-DvyMDgrB.js → sql-OwMEKbz8.js} +0 -0
- /package/dist/web/assets/{ssh-config-a8sDo-Q8.js → ssh-config-WAR3lu2k.js} +0 -0
- /package/dist/web/assets/{stylus-DCJ-6AjT.js → stylus-Cjmoz3d8.js} +0 -0
- /package/dist/web/assets/{swift-c-ZFxyPP.js → swift-S_ZSBG1q.js} +0 -0
- /package/dist/web/assets/{synthwave-84-9NPgBcuv.js → synthwave-84-DiUeWMSh.js} +0 -0
- /package/dist/web/assets/{system-verilog-DPD02IOo.js → system-verilog-RRjqPjUV.js} +0 -0
- /package/dist/web/assets/{systemd-BUr5HRtU.js → systemd-BD3SkXoU.js} +0 -0
- /package/dist/web/assets/{table-FLU8ei8b.js → table-D5Q4ap4W.js} +0 -0
- /package/dist/web/assets/{talonscript-BNBQV5YJ.js → talonscript-DWefISOm.js} +0 -0
- /package/dist/web/assets/{tasl-BETjpSyu.js → tasl-DCjSS-09.js} +0 -0
- /package/dist/web/assets/{tcl-Clp_AZiC.js → tcl-DTYqT_66.js} +0 -0
- /package/dist/web/assets/{terraform-B0kZ0lim.js → terraform-CkbP5QnR.js} +0 -0
- /package/dist/web/assets/{text-wrap-B05VGRa-.js → text-wrap-DBvQD3kc.js} +0 -0
- /package/dist/web/assets/{tokyo-night-oOJ0a7Dk.js → tokyo-night-qVSqO9xy.js} +0 -0
- /package/dist/web/assets/{toml-DOPCSumy.js → toml-D9F5Osqs.js} +0 -0
- /package/dist/web/assets/{trash-2-CTXlTflF.js → trash-2-By4VMbC4.js} +0 -0
- /package/dist/web/assets/{triangle-alert-BRq0VAfd.js → triangle-alert-CUTURFbG.js} +0 -0
- /package/dist/web/assets/{tsv-D6qUGf84.js → tsv-C5tpHoxm.js} +0 -0
- /package/dist/web/assets/{tsx-B6qYhT6O.js → tsx-CMWkGtGm.js} +0 -0
- /package/dist/web/assets/{turtle-C0Rjzw73.js → turtle-Cn4z3xOi.js} +0 -0
- /package/dist/web/assets/{typescript-BiydShEu.js → typescript-DSyAEWsn.js} +0 -0
- /package/dist/web/assets/{typespec-DjmF6Aww.js → typespec-B93Qap7y.js} +0 -0
- /package/dist/web/assets/{typst-CPuscfcL.js → typst-DtlOmFlX.js} +0 -0
- /package/dist/web/assets/{use-is-mobile-b94q8RvF.js → use-is-mobile-B9UZNwSm.js} +0 -0
- /package/dist/web/assets/{utils-C9OjCrou.js → utils-QHe_Q6UF.js} +0 -0
- /package/dist/web/assets/{v-DE4vpLLQ.js → v-Dfzf-szS.js} +0 -0
- /package/dist/web/assets/{vala-CmUF2pS5.js → vala-CyiQZQ9f.js} +0 -0
- /package/dist/web/assets/{vb-BvcRoX4b.js → vb-C7DGSldd.js} +0 -0
- /package/dist/web/assets/{vendor-xterm-K8kpLxdb.js → vendor-xterm-BHKF_Nsf.js} +0 -0
- /package/dist/web/assets/{verilog-BGIzyH3R.js → verilog-kF-hClY2.js} +0 -0
- /package/dist/web/assets/{vesper-CG7IrHQQ.js → vesper-BmKcADuz.js} +0 -0
- /package/dist/web/assets/{vhdl-DMSumomw.js → vhdl-CDnb16KJ.js} +0 -0
- /package/dist/web/assets/{viml-Drt7AEmU.js → viml-B8cs6Ady.js} +0 -0
- /package/dist/web/assets/{vitesse-black-DmsjfN0b.js → vitesse-black-BR6o9Gto.js} +0 -0
- /package/dist/web/assets/{vitesse-dark-Dl55_WeJ.js → vitesse-dark-Dye14-KB.js} +0 -0
- /package/dist/web/assets/{vitesse-light-Crbv9aGg.js → vitesse-light-BLpswIxf.js} +0 -0
- /package/dist/web/assets/{vyper-CB7psEJj.js → vyper-DU51Z97W.js} +0 -0
- /package/dist/web/assets/{wasm-BY1tuM5m.js → wasm-CVqlJAR2.js} +0 -0
- /package/dist/web/assets/{wasm-Dck8KPgP.js → wasm-ChtccNfv.js} +0 -0
- /package/dist/web/assets/{wenyan-DhewQ68b.js → wenyan-DhCa3hCn.js} +0 -0
- /package/dist/web/assets/{wgsl-DlmiP456.js → wgsl-Bj1Ec4vv.js} +0 -0
- /package/dist/web/assets/{wikitext-cQCnqNgE.js → wikitext-C90DHNa0.js} +0 -0
- /package/dist/web/assets/{wit-S5MaCngB.js → wit-DUgbOD7Z.js} +0 -0
- /package/dist/web/assets/{wolfram-Bh8n_9zr.js → wolfram-BgwUqD1I.js} +0 -0
- /package/dist/web/assets/{x-YDu4m2Iq.js → x-DiOUR_mp.js} +0 -0
- /package/dist/web/assets/{yaml-BoK_17l9.js → yaml-DEQulj-8.js} +0 -0
- /package/dist/web/assets/{zenscript-DB3ZFwNJ.js → zenscript-C7Jp7TgT.js} +0 -0
- /package/dist/web/assets/{zig-Bv1BWmfT.js → zig-9x4qXYLg.js} +0 -0
package/src/types/chat.ts
CHANGED
|
@@ -49,6 +49,8 @@ export interface AIProvider {
|
|
|
49
49
|
getUsage?(): Promise<UsageInfo>;
|
|
50
50
|
/** True when a live streaming subprocess exists for this session */
|
|
51
51
|
hasStreamingSession?(sessionId: string): boolean;
|
|
52
|
+
/** Prompt-cache lifetime for this session, in ms — how long holding its subprocess pays. */
|
|
53
|
+
promptCacheTtlMs?(sessionId: string): number;
|
|
52
54
|
}
|
|
53
55
|
|
|
54
56
|
export interface ModelOption {
|
package/src/web/app.tsx
CHANGED
|
@@ -48,6 +48,9 @@ const MobileExplorerSheet = lazy(() =>
|
|
|
48
48
|
const TeamMemberSheet = lazy(() =>
|
|
49
49
|
import("@/components/chat/team-member-sheet").then((m) => ({ default: m.TeamMemberSheet })),
|
|
50
50
|
);
|
|
51
|
+
const RemoteDesktopMobileSheet = lazy(() =>
|
|
52
|
+
import("@/components/remote-desktop/remote-desktop-mobile-sheet").then((m) => ({ default: m.RemoteDesktopMobileSheet })),
|
|
53
|
+
);
|
|
51
54
|
|
|
52
55
|
type AuthState = "checking" | "authenticated" | "unauthenticated";
|
|
53
56
|
|
|
@@ -357,6 +360,11 @@ export function App() {
|
|
|
357
360
|
<TeamMemberSheet />
|
|
358
361
|
</Suspense>
|
|
359
362
|
|
|
363
|
+
{/* Mobile full-screen remote-desktop viewer — same reason as TeamMemberSheet above */}
|
|
364
|
+
<Suspense fallback={null}>
|
|
365
|
+
<RemoteDesktopMobileSheet />
|
|
366
|
+
</Suspense>
|
|
367
|
+
|
|
360
368
|
{/* Global bug report popup */}
|
|
361
369
|
<BugReportPopup />
|
|
362
370
|
|
|
@@ -70,9 +70,14 @@ export function AddAccountDialog({ open, onOpenChange, onSuccess }: AddAccountDi
|
|
|
70
70
|
try {
|
|
71
71
|
let code = oauthCode.trim();
|
|
72
72
|
if (code.includes("#")) code = code.split("#")[0] ?? code;
|
|
73
|
-
await exchangeOAuthCode(code, oauthState);
|
|
73
|
+
const acc = await exchangeOAuthCode(code, oauthState);
|
|
74
74
|
handleClose();
|
|
75
|
-
|
|
75
|
+
// Signing in again on an account this machine parked carries the token in but
|
|
76
|
+
// deliberately leaves it out of the rotation. A flat success over a toggle that is
|
|
77
|
+
// still off reads as a bug, and this is the only place the user finds out otherwise.
|
|
78
|
+
onSuccess(acc?.status === "disabled"
|
|
79
|
+
? "Signed in. This account is still disabled — enable it to use it."
|
|
80
|
+
: "Account connected via OAuth!");
|
|
76
81
|
} catch (e) {
|
|
77
82
|
setAddError((e as Error).message);
|
|
78
83
|
}
|
|
@@ -84,9 +89,11 @@ export function AddAccountDialog({ open, onOpenChange, onSuccess }: AddAccountDi
|
|
|
84
89
|
setAdding(true);
|
|
85
90
|
setAddError(null);
|
|
86
91
|
try {
|
|
87
|
-
await addAccount({ apiKey: newToken.trim(), label: newLabel.trim() || undefined });
|
|
92
|
+
const acc = await addAccount({ apiKey: newToken.trim(), label: newLabel.trim() || undefined });
|
|
88
93
|
handleClose();
|
|
89
|
-
onSuccess(
|
|
94
|
+
onSuccess(acc?.status === "disabled"
|
|
95
|
+
? "Token updated. This account is still disabled — enable it to use it."
|
|
96
|
+
: "Account added!");
|
|
90
97
|
} catch (e) {
|
|
91
98
|
setAddError((e as Error).message);
|
|
92
99
|
}
|
|
@@ -90,7 +90,11 @@ function useQuestionKeyboard(config: {
|
|
|
90
90
|
useEffect(() => {
|
|
91
91
|
if (!config.enabled) return;
|
|
92
92
|
const handleKeyDown = (e: KeyboardEvent) => {
|
|
93
|
-
|
|
93
|
+
// Resolve focus from the input's OWN document: this card can be rendered inside a
|
|
94
|
+
// picture-in-picture window, and the module-global `document` is always the main one —
|
|
95
|
+
// there the input is never active, so every printable key would be treated as a shortcut.
|
|
96
|
+
const input = config.customInputRef.current;
|
|
97
|
+
const isTyping = !!input && input.ownerDocument.activeElement === input;
|
|
94
98
|
|
|
95
99
|
// Number keys 1-9
|
|
96
100
|
if (!isTyping && e.key >= "1" && e.key <= "9") {
|
|
@@ -132,7 +136,10 @@ function useQuestionKeyboard(config: {
|
|
|
132
136
|
if (el) {
|
|
133
137
|
el.addEventListener("keydown", handleKeyDown);
|
|
134
138
|
el.setAttribute("tabindex", "0");
|
|
135
|
-
|
|
139
|
+
// Same reason: asking the main document whether focus is already inside the card answers
|
|
140
|
+
// "no" for a card living in another document, and this effect re-runs on every render —
|
|
141
|
+
// which would yank focus out of the custom input after each keystroke.
|
|
142
|
+
if (!el.contains(el.ownerDocument.activeElement)) el.focus();
|
|
136
143
|
}
|
|
137
144
|
return () => { el?.removeEventListener("keydown", handleKeyDown); };
|
|
138
145
|
}, [config, focusedOption]);
|
|
@@ -154,11 +154,12 @@ function formatLastUpdated(ts: number | null | undefined): string | null {
|
|
|
154
154
|
return `${days}d ago`;
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
-
function AccountUsageCard({ entry, isActive, accountInfo, onToggle, onDelete, onExport, onViewProfile, flash, fullscreen }: {
|
|
157
|
+
function AccountUsageCard({ entry, isActive, accountInfo, onToggle, toggling, onDelete, onExport, onViewProfile, flash, fullscreen }: {
|
|
158
158
|
entry: AccountUsageEntry;
|
|
159
159
|
isActive: boolean;
|
|
160
160
|
accountInfo?: AccountInfo;
|
|
161
161
|
onToggle?: (id: string, status: string) => void;
|
|
162
|
+
toggling?: boolean;
|
|
162
163
|
onDelete?: (id: string, display: string) => void;
|
|
163
164
|
onExport?: (id: string) => void;
|
|
164
165
|
onViewProfile?: (profile: OAuthProfileData, accountId: string) => void;
|
|
@@ -207,7 +208,7 @@ function AccountUsageCard({ entry, isActive, accountInfo, onToggle, onDelete, on
|
|
|
207
208
|
<Switch
|
|
208
209
|
checked={status !== "disabled"}
|
|
209
210
|
onCheckedChange={() => onToggle(entry.accountId, status)}
|
|
210
|
-
disabled={status === "cooldown"}
|
|
211
|
+
disabled={toggling || status === "cooldown"}
|
|
211
212
|
className="scale-[0.6] cursor-pointer"
|
|
212
213
|
/>
|
|
213
214
|
)}
|
|
@@ -269,6 +270,7 @@ export function UsageDetailPanel({ usage, visible, onClose, onReload, loading, l
|
|
|
269
270
|
const [exportPreselect, setExportPreselect] = useState<string | null>(null);
|
|
270
271
|
const [isFullscreen, setIsFullscreen] = useState(false);
|
|
271
272
|
const [message, setMessage] = useState<string | null>(null);
|
|
273
|
+
const [togglingId, setTogglingId] = useState<string | null>(null);
|
|
272
274
|
const msgTimer = useRef<ReturnType<typeof setTimeout>>(undefined);
|
|
273
275
|
const prevUsagesRef = useRef<AccountUsageEntry[]>([]);
|
|
274
276
|
|
|
@@ -345,7 +347,18 @@ export function UsageDetailPanel({ usage, visible, onClose, onReload, loading, l
|
|
|
345
347
|
const fsRows = Math.ceil(fsCount / fsCols);
|
|
346
348
|
|
|
347
349
|
async function handleToggle(id: string, status: string) {
|
|
348
|
-
|
|
350
|
+
// Enabling a parked account proves its token server-side first, which is a network
|
|
351
|
+
// round trip that can come back 400 — and patchAccount throws on that. Without the
|
|
352
|
+
// catch the message the server takes care to write reaches nobody: unhandled
|
|
353
|
+
// rejection, no toast, no reload, and the Switch silently snaps back. The pending
|
|
354
|
+
// flag is because that round trip can take most of a minute in the worst case.
|
|
355
|
+
setTogglingId(id);
|
|
356
|
+
try {
|
|
357
|
+
await patchAccount(id, { status: status === "disabled" ? "active" : "disabled" });
|
|
358
|
+
} catch (e) {
|
|
359
|
+
showMessage((e as Error).message);
|
|
360
|
+
}
|
|
361
|
+
setTogglingId(null);
|
|
349
362
|
loadAll();
|
|
350
363
|
onReload?.();
|
|
351
364
|
}
|
|
@@ -440,6 +453,7 @@ export function UsageDetailPanel({ usage, visible, onClose, onReload, loading, l
|
|
|
440
453
|
isActive={entry.accountId === (activeAccountId ?? usage.activeAccountId)}
|
|
441
454
|
accountInfo={accountMap.get(entry.accountId)}
|
|
442
455
|
onToggle={handleToggle}
|
|
456
|
+
toggling={togglingId === entry.accountId}
|
|
443
457
|
onDelete={(id, display) => setDeleteTarget({ id, display })}
|
|
444
458
|
onExport={(id) => { setExportPreselect(id); setShowExportDialog(true); }}
|
|
445
459
|
onViewProfile={(profile, accountId) => setProfileView({ profile, accountId })}
|
|
@@ -22,6 +22,7 @@ export const WINDOW_CONTENT: Record<WindowKind, LazyExoticComponent<ComponentTyp
|
|
|
22
22
|
"team-member": lazy(() => import("@/components/chat/team-member-window-content")),
|
|
23
23
|
"system-monitor": lazy(() => import("@/components/system/system-monitor-window-content")),
|
|
24
24
|
"tab-host": lazy(() => import("./tab-host-window-content")),
|
|
25
|
+
"remote-desktop": lazy(() => import("@/components/remote-desktop/remote-desktop-window-content")),
|
|
25
26
|
};
|
|
26
27
|
|
|
27
28
|
/** Titlebar text for a window. Falls back to the kind's generic name. */
|
|
@@ -33,6 +34,7 @@ export function windowTitle(kind: WindowKind, payload?: Record<string, unknown>)
|
|
|
33
34
|
return typeof member === "string" && member ? `Session — ${member}` : "Team member";
|
|
34
35
|
}
|
|
35
36
|
if (kind === "system-monitor") return "System Monitor";
|
|
37
|
+
if (kind === "remote-desktop") return "Remote Desktop";
|
|
36
38
|
// A detached tab carries its title in the payload; the generic name only shows for a
|
|
37
39
|
// window whose tab has not been resolved yet (restore before the layout is loaded).
|
|
38
40
|
if (kind === "tab-host") return "Tab";
|
|
@@ -7,7 +7,7 @@ import type { Rect } from "./window-geometry";
|
|
|
7
7
|
* Single source of truth: the persistence layer filters this list instead of repeating it,
|
|
8
8
|
* so a new kind can never be silently dropped on reload.
|
|
9
9
|
*/
|
|
10
|
-
export const WINDOW_KINDS = ["explorer", "team-member", "system-monitor", "tab-host"] as const;
|
|
10
|
+
export const WINDOW_KINDS = ["explorer", "team-member", "system-monitor", "tab-host", "remote-desktop"] as const;
|
|
11
11
|
|
|
12
12
|
export type WindowKind = (typeof WINDOW_KINDS)[number];
|
|
13
13
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useState, useCallback, useEffect, useMemo } from "react";
|
|
2
|
-
import { X, Bug as BugIcon, Cloud, FolderTree } from "lucide-react";
|
|
2
|
+
import { X, Bug as BugIcon, Cloud, FolderTree, MonitorSmartphone } from "lucide-react";
|
|
3
3
|
import { useShallow } from "zustand/react/shallow";
|
|
4
4
|
import { useProjectStore } from "@/stores/project-store";
|
|
5
5
|
import { useSettingsStore, type SidebarActiveTab } from "@/stores/settings-store";
|
|
@@ -23,7 +23,10 @@ import { CloudSharePopover } from "@/components/layout/cloud-share-popover";
|
|
|
23
23
|
import { BottomSheet } from "@/components/ui/mobile-bottom-sheet";
|
|
24
24
|
import { isMobileDevice } from "@/hooks/use-is-mobile";
|
|
25
25
|
import { openExplorer } from "@/components/os-explorer/open-explorer";
|
|
26
|
+
import { useOpenRemoteDesktop } from "@/components/remote-desktop/open-remote-desktop";
|
|
27
|
+
import { useRemoteDesktopAvailable } from "@/components/remote-desktop/use-remote-desktop-available";
|
|
26
28
|
import { FeatureBadge } from "@/components/ui/feature-badge";
|
|
29
|
+
import type { FeatureBadgeId } from "@/lib/feature-badges";
|
|
27
30
|
import { cn } from "@/lib/utils";
|
|
28
31
|
|
|
29
32
|
// Tab ids the mobile drawer can render content for. `search` is desktop-only for now;
|
|
@@ -33,6 +36,24 @@ const MOBILE_SUPPORTED = new Set<string>([
|
|
|
33
36
|
]);
|
|
34
37
|
const isMobileSupported = (id: SidebarActiveTab) => MOBILE_SUPPORTED.has(id) || id.startsWith("ext:");
|
|
35
38
|
|
|
39
|
+
/** One utility tile in the drawer footer grid — icon over a short single-line label, with an
|
|
40
|
+
* optional corner feature badge. Uniform size so the grid wraps cleanly as tiles are added,
|
|
41
|
+
* instead of a single cramped row where long labels ("Cloud & Share") wrapped and overflowed. */
|
|
42
|
+
function FooterTile({ icon: Icon, label, badge, onClick }: {
|
|
43
|
+
icon: React.ElementType; label: string; badge?: FeatureBadgeId; onClick: () => void;
|
|
44
|
+
}) {
|
|
45
|
+
return (
|
|
46
|
+
<button
|
|
47
|
+
onClick={onClick}
|
|
48
|
+
className="group relative flex flex-col items-center justify-center gap-1 rounded-lg py-2.5 text-text-subtle hover:bg-surface-elevated hover:text-text-secondary transition-colors"
|
|
49
|
+
>
|
|
50
|
+
<Icon className="size-4" />
|
|
51
|
+
<span className="text-[10px] leading-none">{label}</span>
|
|
52
|
+
<FeatureBadge id={badge} variant="corner" />
|
|
53
|
+
</button>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
36
57
|
interface MobileDrawerProps {
|
|
37
58
|
isOpen: boolean;
|
|
38
59
|
onClose: () => void;
|
|
@@ -47,6 +68,8 @@ export function MobileDrawer({ isOpen, onClose, initialTab }: MobileDrawerProps)
|
|
|
47
68
|
const sidebarTabOrder = useSettingsStore((s) => s.sidebarTabOrder);
|
|
48
69
|
const setSidebarTabOrder = useSettingsStore((s) => s.setSidebarTabOrder);
|
|
49
70
|
const contributions = useExtensionStore((s) => s.contributions);
|
|
71
|
+
const { available: remoteDesktopAvailable } = useRemoteDesktopAvailable();
|
|
72
|
+
const openRemoteDesktop = useOpenRemoteDesktop();
|
|
50
73
|
const [activeTab, setActiveTab] = useState<SidebarActiveTab>(initialTab ?? "explorer");
|
|
51
74
|
const [cloudOpen, setCloudOpen] = useState(false);
|
|
52
75
|
|
|
@@ -138,33 +161,21 @@ export function MobileDrawer({ isOpen, onClose, initialTab }: MobileDrawerProps)
|
|
|
138
161
|
onReorder={setSidebarTabOrder}
|
|
139
162
|
/>
|
|
140
163
|
|
|
141
|
-
{/*
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
<div className="
|
|
164
|
+
{/* Footer: version/upgrade on its own line, then a uniform wrapping grid of utility
|
|
165
|
+
tiles (opens explorer/remote/cloud, report bug) — scales cleanly as tiles are added. */}
|
|
166
|
+
<div className="border-t border-border px-3 py-2">
|
|
167
|
+
<div className="grid grid-cols-4 gap-1">
|
|
145
168
|
{/* Not a sidebar tab — the explorer opens as its own full-screen sheet. */}
|
|
146
|
-
<
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
className="flex items-center gap-1 text-[10px] text-text-subtle hover:text-text-secondary transition-colors"
|
|
157
|
-
>
|
|
158
|
-
<Cloud className="size-3" />
|
|
159
|
-
<span>Cloud & Share</span>
|
|
160
|
-
</button>
|
|
161
|
-
<button
|
|
162
|
-
onClick={handleReportBug}
|
|
163
|
-
className="flex items-center gap-1 text-[10px] text-text-subtle hover:text-text-secondary transition-colors"
|
|
164
|
-
>
|
|
165
|
-
<BugIcon className="size-3" />
|
|
166
|
-
<span>Report Bug</span>
|
|
167
|
-
</button>
|
|
169
|
+
<FooterTile icon={FolderTree} label="Files" badge="os-explorer" onClick={() => { onClose(); void openExplorer(); }} />
|
|
170
|
+
{remoteDesktopAvailable && (
|
|
171
|
+
<FooterTile icon={MonitorSmartphone} label="Remote" badge="remote-desktop" onClick={() => { onClose(); openRemoteDesktop(); }} />
|
|
172
|
+
)}
|
|
173
|
+
<FooterTile icon={Cloud} label="Cloud" onClick={() => setCloudOpen(true)} />
|
|
174
|
+
<FooterTile icon={BugIcon} label="Bug" onClick={handleReportBug} />
|
|
175
|
+
</div>
|
|
176
|
+
{/* Version / upgrade pinned at the very bottom, under a divider. */}
|
|
177
|
+
<div className="mt-2 pt-2 border-t border-border px-1 text-[11px]">
|
|
178
|
+
<UpgradeButton align="left" />
|
|
168
179
|
</div>
|
|
169
180
|
</div>
|
|
170
181
|
</div>
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { useState, useRef, useEffect, useMemo, memo } from "react";
|
|
2
2
|
import { createPortal } from "react-dom";
|
|
3
|
-
import { Settings, Bug, Cloud, FolderTree } from "lucide-react";
|
|
3
|
+
import { Settings, Bug, Cloud, FolderTree, MonitorSmartphone } from "lucide-react";
|
|
4
4
|
import { openExplorer } from "@/components/os-explorer/open-explorer";
|
|
5
|
+
import { useOpenRemoteDesktop } from "@/components/remote-desktop/open-remote-desktop";
|
|
6
|
+
import { useRemoteDesktopAvailable } from "@/components/remote-desktop/use-remote-desktop-available";
|
|
5
7
|
import { FeatureBadge } from "@/components/ui/feature-badge";
|
|
6
8
|
import type { FeatureBadgeId } from "@/lib/feature-badges";
|
|
7
9
|
import { useSettingsStore, type SidebarActiveTab } from "@/stores/settings-store";
|
|
@@ -147,6 +149,9 @@ export const NavSectionRail = memo(function NavSectionRail({ className }: { clas
|
|
|
147
149
|
return () => window.removeEventListener("open-cloud-share", open);
|
|
148
150
|
}, []);
|
|
149
151
|
|
|
152
|
+
const openRemoteDesktop = useOpenRemoteDesktop();
|
|
153
|
+
const { available: remoteDesktopAvailable } = useRemoteDesktopAvailable();
|
|
154
|
+
|
|
150
155
|
const handleReportBug = () => openBugReportPopup(version);
|
|
151
156
|
|
|
152
157
|
// Rail tab click: collapsed → open on any tab; open → clicking the active tab closes it.
|
|
@@ -209,8 +214,11 @@ export const NavSectionRail = memo(function NavSectionRail({ className }: { clas
|
|
|
209
214
|
</>,
|
|
210
215
|
document.body,
|
|
211
216
|
)}
|
|
212
|
-
{/* Not
|
|
217
|
+
{/* Not sidebar tabs — these open their own floating windows. */}
|
|
213
218
|
<FooterUtil icon={FolderTree} label="File Explorer" featureBadge="os-explorer" onClick={() => void openExplorer()} />
|
|
219
|
+
{remoteDesktopAvailable && (
|
|
220
|
+
<FooterUtil icon={MonitorSmartphone} label="Remote Desktop" featureBadge="remote-desktop" onClick={openRemoteDesktop} />
|
|
221
|
+
)}
|
|
214
222
|
<FooterUtil icon={Bug} label="Report Bug" onClick={handleReportBug} />
|
|
215
223
|
<FooterUtil icon={Settings} label="Settings" active={sidebarActiveTab === "settings"} onClick={() => handleTabClick("settings")} />
|
|
216
224
|
</div>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Opens the Remote Desktop viewer: a floating window on desktop, the full-screen mobile sheet
|
|
3
|
+
* below `md` (`WindowLayer` renders nothing there — same split `open-explorer.ts` makes for the
|
|
4
|
+
* file explorer). There is one host to control, so a second call on desktop focuses the
|
|
5
|
+
* existing window instead of opening another; on mobile the sheet is a singleton already.
|
|
6
|
+
*/
|
|
7
|
+
import { useCallback } from "react";
|
|
8
|
+
import { useWindowStore } from "@/components/floating-window/window-store";
|
|
9
|
+
import { isMobileDevice } from "@/hooks/use-is-mobile";
|
|
10
|
+
import { useRemoteDesktopMobileOpenState } from "./use-remote-desktop-mobile-open-state";
|
|
11
|
+
|
|
12
|
+
export function useOpenRemoteDesktop(): () => void {
|
|
13
|
+
const openWindow = useWindowStore((s) => s.open);
|
|
14
|
+
const focusWindow = useWindowStore((s) => s.focus);
|
|
15
|
+
const openMobileSheet = useRemoteDesktopMobileOpenState((s) => s.open);
|
|
16
|
+
|
|
17
|
+
return useCallback(() => {
|
|
18
|
+
if (isMobileDevice()) {
|
|
19
|
+
openMobileSheet();
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
const existing = Object.values(useWindowStore.getState().windows).find((w) => w.kind === "remote-desktop");
|
|
23
|
+
if (existing) focusWindow(existing.id);
|
|
24
|
+
else openWindow("remote-desktop");
|
|
25
|
+
}, [openWindow, focusWindow, openMobileSheet]);
|
|
26
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client → host coordinate mapping. Only the canvas's CSS-pixel bounding rect matters —
|
|
3
|
+
* `devicePixelRatio` describes the *viewer's* screen (irrelevant to host pixels, and would
|
|
4
|
+
* double-scale/misplace the cursor if folded in), so it is deliberately never read here.
|
|
5
|
+
*/
|
|
6
|
+
export interface Fraction {
|
|
7
|
+
xFrac: number;
|
|
8
|
+
yFrac: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Plain bounding-box shape every function here accepts — deliberately not `DOMRect` so pure
|
|
12
|
+
* computed rects (e.g. `letterboxedContentRect`'s output) satisfy it without faking DOM-only
|
|
13
|
+
* members (`right`, `bottom`, `toJSON`, …) that nothing here ever reads. */
|
|
14
|
+
export interface Rect {
|
|
15
|
+
left: number;
|
|
16
|
+
top: number;
|
|
17
|
+
width: number;
|
|
18
|
+
height: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function clamp01(n: number): number {
|
|
22
|
+
return Math.min(Math.max(n, 0), 1);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** `rect` is the canvas element's `getBoundingClientRect()`. Fractions are clamped to 0..1 so
|
|
26
|
+
* a pointer that slips just outside the element (fast drag) still maps to an edge, not a
|
|
27
|
+
* wildly out-of-range host coordinate. */
|
|
28
|
+
export function fractionFromPoint(clientX: number, clientY: number, rect: Rect): Fraction {
|
|
29
|
+
const xFrac = rect.width > 0 ? (clientX - rect.left) / rect.width : 0;
|
|
30
|
+
const yFrac = rect.height > 0 ? (clientY - rect.top) / rect.height : 0;
|
|
31
|
+
return { xFrac: clamp01(xFrac), yFrac: clamp01(yFrac) };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** A CSS `transform: translate(panX, panY) scale(scale)` applied around the transformed
|
|
35
|
+
* element's own center (the mobile viewer's default `transform-origin`). */
|
|
36
|
+
export interface ZoomPanTransform {
|
|
37
|
+
scale: number;
|
|
38
|
+
panX: number;
|
|
39
|
+
panY: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Reverse-maps a screen point through an active pinch-zoom/pan transform back to a 0..1
|
|
44
|
+
* fraction of the capture — used by the mobile touch viewer, which zooms/pans the canvas with
|
|
45
|
+
* a CSS transform instead of re-rendering at a different resolution.
|
|
46
|
+
*
|
|
47
|
+
* `rect` MUST be the bounding rect of the never-transformed container the canvas fills at
|
|
48
|
+
* `scale=1, pan=0` (not the live, possibly-transformed canvas rect) — that container is the
|
|
49
|
+
* stable reference frame this function projects screen points back into. Deliberately pure
|
|
50
|
+
* arithmetic rather than reading the live (transformed) element's `getBoundingClientRect()`:
|
|
51
|
+
* jsdom does not compute CSS transforms into that rect at all, so this stays unit-testable,
|
|
52
|
+
* and it avoids relying on `transform-origin` math the browser applies opaquely.
|
|
53
|
+
*/
|
|
54
|
+
export function fractionFromZoomedPoint(
|
|
55
|
+
clientX: number,
|
|
56
|
+
clientY: number,
|
|
57
|
+
rect: Rect,
|
|
58
|
+
transform: ZoomPanTransform,
|
|
59
|
+
): Fraction {
|
|
60
|
+
const scale = transform.scale || 1;
|
|
61
|
+
const cx = rect.left + rect.width / 2;
|
|
62
|
+
const cy = rect.top + rect.height / 2;
|
|
63
|
+
const baseX = cx + (clientX - cx - transform.panX) / scale;
|
|
64
|
+
const baseY = cy + (clientY - cy - transform.panY) / scale;
|
|
65
|
+
return fractionFromPoint(baseX, baseY, rect);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* The on-screen sub-rectangle a canvas actually paints its content into under `object-fit:
|
|
70
|
+
* contain` (or the equivalent CSS2.1 replaced-element max-width/max-height algorithm) — the
|
|
71
|
+
* canvas ELEMENT's own box does not shrink to the letterboxed video; only the pixels drawn
|
|
72
|
+
* inside it do. `box` is that element's box at rest (`scale=1, pan=0` — never the live,
|
|
73
|
+
* zoomed/panned rect: see `fractionFromZoomedPoint`'s doc comment for why), `contentWidth`/
|
|
74
|
+
* `contentHeight` are the canvas's drawing-buffer dimensions (its `.width`/`.height`
|
|
75
|
+
* attributes, i.e. the real capture resolution, NOT its CSS size).
|
|
76
|
+
*
|
|
77
|
+
* `object-fit: contain` centers the letterboxed content within `box` by default
|
|
78
|
+
* (`object-position: 50% 50%`), so this rect's own center always coincides with `box`'s
|
|
79
|
+
* center — that is what keeps `fractionFromZoomedPoint`'s transform-origin math (which derives
|
|
80
|
+
* `cx,cy` from whatever rect it's given) correct when this rect is passed to it instead of the
|
|
81
|
+
* raw container box.
|
|
82
|
+
*
|
|
83
|
+
* Falls back to `box` unchanged if the content has no size yet (e.g. before the first decoded
|
|
84
|
+
* frame sets `canvas.width`/`canvas.height`).
|
|
85
|
+
*/
|
|
86
|
+
export function letterboxedContentRect(box: Rect, contentWidth: number, contentHeight: number): Rect {
|
|
87
|
+
if (contentWidth <= 0 || contentHeight <= 0 || box.width <= 0 || box.height <= 0) return box;
|
|
88
|
+
const boxAspect = box.width / box.height;
|
|
89
|
+
const contentAspect = contentWidth / contentHeight;
|
|
90
|
+
if (contentAspect > boxAspect) {
|
|
91
|
+
// Content is relatively WIDER than the box -> width-constrained, letterboxed top/bottom.
|
|
92
|
+
const height = box.width / contentAspect;
|
|
93
|
+
return { left: box.left, top: box.top + (box.height - height) / 2, width: box.width, height };
|
|
94
|
+
}
|
|
95
|
+
// Content is relatively TALLER/narrower than the box -> height-constrained, letterboxed sides.
|
|
96
|
+
const width = box.height * contentAspect;
|
|
97
|
+
return { left: box.left + (box.width - width) / 2, top: box.top, width, height: box.height };
|
|
98
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strictly-increasing microsecond timestamps for `EncodedVideoChunk`.
|
|
3
|
+
*
|
|
4
|
+
* The decoder previously used `performance.now() * 1000` per chunk. Some decoders — especially
|
|
5
|
+
* mobile hardware H.264 decoders — reject a chunk whose timestamp does not strictly increase
|
|
6
|
+
* from the previous one; two chunks landing in the same millisecond (easy at 30fps under normal
|
|
7
|
+
* network/encode jitter) get equal timestamps and can trip that check.
|
|
8
|
+
*
|
|
9
|
+
* This is decode-only playback (no A/V sync, nothing ever reads the timestamp back to schedule
|
|
10
|
+
* presentation), so the value only has to satisfy "strictly greater than the last one" — a
|
|
11
|
+
* frame-index-based synthetic clock does that unconditionally, independent of wall-clock
|
|
12
|
+
* resolution or jitter.
|
|
13
|
+
*/
|
|
14
|
+
const DEFAULT_FPS = 30;
|
|
15
|
+
|
|
16
|
+
/** `frameIndex` is a 0-based, ever-incrementing counter of chunks decoded by ONE decoder
|
|
17
|
+
* instance — reset it to 0 whenever a new `VideoDecoder` is created (a fresh instance has no
|
|
18
|
+
* timestamp history to stay monotonic against). */
|
|
19
|
+
export function frameTimestampMicros(frameIndex: number, fps: number = DEFAULT_FPS): number {
|
|
20
|
+
return Math.round((frameIndex * 1_000_000) / fps);
|
|
21
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure classifiers the mobile touch viewer's gesture engine uses to tell "tap" apart from
|
|
3
|
+
* "drag", and "pinch-zoom" apart from "two-finger scroll". Kept dependency-free (no DOM,
|
|
4
|
+
* no React) so they can be unit-tested without a browser.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** One sampled point in a gesture: client-space coordinates + a timestamp (ms, any monotonic
|
|
8
|
+
* clock — `performance.now()` in the real hook). */
|
|
9
|
+
export interface PointerSample {
|
|
10
|
+
x: number;
|
|
11
|
+
y: number;
|
|
12
|
+
t: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** A tap is a touch that ends close to where it started, quickly — anything else (moved
|
|
16
|
+
* further, or held longer) is a drag/long-press instead. Both thresholds must pass; a fast
|
|
17
|
+
* but far swipe is a drag, and a slow-but-still finger is a long-press, not a tap. */
|
|
18
|
+
export function isTapGesture(
|
|
19
|
+
start: PointerSample,
|
|
20
|
+
end: PointerSample,
|
|
21
|
+
moveThresholdPx = 10,
|
|
22
|
+
maxDurationMs = 300,
|
|
23
|
+
): boolean {
|
|
24
|
+
const dist = Math.hypot(end.x - start.x, end.y - start.y);
|
|
25
|
+
const duration = end.t - start.t;
|
|
26
|
+
return dist <= moveThresholdPx && duration <= maxDurationMs;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export type TwoFingerGesture = "pinch" | "scroll";
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Two-finger gestures start ambiguous — pinch-zoom and two-finger-scroll both begin as "two
|
|
33
|
+
* fingers moved a little". They are told apart by which signal crosses its threshold first:
|
|
34
|
+
* the distance *between* the fingers changing (pinch) vs. their midpoint translating while
|
|
35
|
+
* staying roughly the same distance apart (scroll). Once a gesture is classified, the caller
|
|
36
|
+
* should keep calling this with the *same* `startDistance` for the rest of the gesture and
|
|
37
|
+
* lock in the first non-null-equivalent result — see `use-remote-desktop-touch.ts`.
|
|
38
|
+
*/
|
|
39
|
+
export function classifyTwoFingerGesture(
|
|
40
|
+
startDistance: number,
|
|
41
|
+
currentDistance: number,
|
|
42
|
+
distanceThresholdPx = 15,
|
|
43
|
+
): TwoFingerGesture {
|
|
44
|
+
return Math.abs(currentDistance - startDistance) > distanceThresholdPx ? "pinch" : "scroll";
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Euclidean distance between two touch points — shared helper so the hook and its tests
|
|
48
|
+
* compute "finger spread" identically. */
|
|
49
|
+
export function touchDistance(a: { x: number; y: number }, b: { x: number; y: number }): number {
|
|
50
|
+
return Math.hypot(b.x - a.x, b.y - a.y);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Midpoint between two touch points. */
|
|
54
|
+
export function touchMidpoint(a: { x: number; y: number }, b: { x: number; y: number }): { x: number; y: number } {
|
|
55
|
+
return { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };
|
|
56
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compact function/combo key row shown above the toolbar while the virtual keyboard is open.
|
|
3
|
+
* Modifiers (Ctrl/Alt/Shift/Win) are STICKY: tap to hold (sends the key-down immediately,
|
|
4
|
+
* highlights the button), tap again to release (key-down was already sent, so this just sends
|
|
5
|
+
* the matching key-up) — lets a chord like "Ctrl + tap on the video" be built one tap at a time.
|
|
6
|
+
* The combo buttons (Ctrl+C etc.) are a shortcut for the same idea: one tap sends the whole
|
|
7
|
+
* down/up sequence, independent of whatever sticky modifiers happen to be held.
|
|
8
|
+
*
|
|
9
|
+
* No server changes needed — every button here is just the existing `{type:"key",code,down}`
|
|
10
|
+
* protocol `use-remote-input-capture.ts` already speaks; the server's own `heldKeyCodes`
|
|
11
|
+
* tracking (`remote-desktop-session.ts`) already releases anything sent down:true here too on
|
|
12
|
+
* disconnect/heartbeat-timeout, the same as a real held key.
|
|
13
|
+
*/
|
|
14
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
15
|
+
import { cn } from "@/lib/utils";
|
|
16
|
+
|
|
17
|
+
export interface RemoteDesktopMobileKeyBarProps {
|
|
18
|
+
sendMessage: (msg: Record<string, unknown>) => void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const MODIFIERS = [
|
|
22
|
+
{ label: "Ctrl", code: "ControlLeft" },
|
|
23
|
+
{ label: "Alt", code: "AltLeft" },
|
|
24
|
+
{ label: "Shift", code: "ShiftLeft" },
|
|
25
|
+
{ label: "Win", code: "MetaLeft" },
|
|
26
|
+
] as const;
|
|
27
|
+
|
|
28
|
+
const FUNCTION_KEYS = [
|
|
29
|
+
{ label: "Esc", code: "Escape" },
|
|
30
|
+
{ label: "Tab", code: "Tab" },
|
|
31
|
+
{ label: "←", code: "ArrowLeft" },
|
|
32
|
+
{ label: "↑", code: "ArrowUp" },
|
|
33
|
+
{ label: "↓", code: "ArrowDown" },
|
|
34
|
+
{ label: "→", code: "ArrowRight" },
|
|
35
|
+
] as const;
|
|
36
|
+
|
|
37
|
+
const COMBOS: readonly { label: string; codes: readonly string[] }[] = [
|
|
38
|
+
{ label: "Ctrl+C", codes: ["ControlLeft", "KeyC"] },
|
|
39
|
+
{ label: "Ctrl+V", codes: ["ControlLeft", "KeyV"] },
|
|
40
|
+
{ label: "Ctrl+Z", codes: ["ControlLeft", "KeyZ"] },
|
|
41
|
+
{ label: "Ctrl+A", codes: ["ControlLeft", "KeyA"] },
|
|
42
|
+
{ label: "Alt+Tab", codes: ["AltLeft", "Tab"] },
|
|
43
|
+
// Windows blocks synthetic Ctrl+Alt+Del (the Secure Attention Sequence) from SendInput
|
|
44
|
+
// regardless of privilege level — this is an OS restriction, not a bug here. The button is
|
|
45
|
+
// best-effort: harmless to try, and there is no way to detect the block ahead of time.
|
|
46
|
+
{ label: "Ctrl+Alt+Del", codes: ["ControlLeft", "AltLeft", "Delete"] },
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
function KeyBarButton({
|
|
50
|
+
onClick,
|
|
51
|
+
active,
|
|
52
|
+
className,
|
|
53
|
+
children,
|
|
54
|
+
}: {
|
|
55
|
+
onClick: () => void;
|
|
56
|
+
active?: boolean;
|
|
57
|
+
className?: string;
|
|
58
|
+
children: React.ReactNode;
|
|
59
|
+
}) {
|
|
60
|
+
return (
|
|
61
|
+
<button
|
|
62
|
+
type="button"
|
|
63
|
+
onClick={onClick}
|
|
64
|
+
className={cn(
|
|
65
|
+
"min-h-11 min-w-11 shrink-0 whitespace-nowrap rounded-md px-2.5 text-xs font-medium text-white/80",
|
|
66
|
+
"active:bg-white/15 transition-colors",
|
|
67
|
+
active && "bg-primary text-primary-foreground",
|
|
68
|
+
className,
|
|
69
|
+
)}
|
|
70
|
+
>
|
|
71
|
+
{children}
|
|
72
|
+
</button>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function RemoteDesktopMobileKeyBar({ sendMessage }: RemoteDesktopMobileKeyBarProps) {
|
|
77
|
+
const [held, setHeld] = useState<ReadonlySet<string>>(new Set());
|
|
78
|
+
const heldRef = useRef(held);
|
|
79
|
+
heldRef.current = held;
|
|
80
|
+
|
|
81
|
+
// Safety net: a modifier left toggled on must not stay logically held on the host once this
|
|
82
|
+
// bar goes away (keyboard dismissed, sheet closed) — same reasoning as `releaseAll` elsewhere.
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
return () => {
|
|
85
|
+
heldRef.current.forEach((code) => sendMessage({ type: "key", code, down: false }));
|
|
86
|
+
};
|
|
87
|
+
}, [sendMessage]);
|
|
88
|
+
|
|
89
|
+
const toggleModifier = useCallback((code: string) => {
|
|
90
|
+
setHeld((prev) => {
|
|
91
|
+
const isHeld = prev.has(code);
|
|
92
|
+
sendMessage({ type: "key", code, down: !isHeld });
|
|
93
|
+
const next = new Set(prev);
|
|
94
|
+
if (isHeld) next.delete(code); else next.add(code);
|
|
95
|
+
return next;
|
|
96
|
+
});
|
|
97
|
+
}, [sendMessage]);
|
|
98
|
+
|
|
99
|
+
const tapKey = useCallback((code: string) => {
|
|
100
|
+
sendMessage({ type: "key", code, down: true });
|
|
101
|
+
sendMessage({ type: "key", code, down: false });
|
|
102
|
+
}, [sendMessage]);
|
|
103
|
+
|
|
104
|
+
const sendCombo = useCallback((codes: readonly string[]) => {
|
|
105
|
+
// Press down in order, release in reverse — mirrors physically pressing then releasing a
|
|
106
|
+
// chord (last key down first key up would read as a different shortcut on some apps).
|
|
107
|
+
codes.forEach((code) => sendMessage({ type: "key", code, down: true }));
|
|
108
|
+
[...codes].reverse().forEach((code) => sendMessage({ type: "key", code, down: false }));
|
|
109
|
+
}, [sendMessage]);
|
|
110
|
+
|
|
111
|
+
return (
|
|
112
|
+
<div
|
|
113
|
+
className="flex shrink-0 gap-1 overflow-x-auto border-t border-white/10 bg-black/90 px-2 py-1.5"
|
|
114
|
+
data-testid="remote-desktop-mobile-key-bar"
|
|
115
|
+
>
|
|
116
|
+
{MODIFIERS.map((m) => (
|
|
117
|
+
<KeyBarButton key={m.code} onClick={() => toggleModifier(m.code)} active={held.has(m.code)}>
|
|
118
|
+
{m.label}
|
|
119
|
+
</KeyBarButton>
|
|
120
|
+
))}
|
|
121
|
+
<div className="mx-0.5 w-px shrink-0 bg-white/10" />
|
|
122
|
+
{FUNCTION_KEYS.map((k) => (
|
|
123
|
+
<KeyBarButton key={k.code} onClick={() => tapKey(k.code)}>{k.label}</KeyBarButton>
|
|
124
|
+
))}
|
|
125
|
+
<div className="mx-0.5 w-px shrink-0 bg-white/10" />
|
|
126
|
+
{COMBOS.map((c) => (
|
|
127
|
+
<KeyBarButton key={c.label} onClick={() => sendCombo(c.codes)}>{c.label}</KeyBarButton>
|
|
128
|
+
))}
|
|
129
|
+
</div>
|
|
130
|
+
);
|
|
131
|
+
}
|