specrails-desktop 2.36.0 → 2.42.2
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/README.md +94 -247
- package/client/dist/assets/ActivityFeedPage-DoEMumQS.js +1 -0
- package/client/dist/assets/AgentBrowserCapture-DIh4K0gh.js +1 -0
- package/client/dist/assets/AgentModeAnalyticsPane-mbq4WEoO.js +2 -0
- package/client/dist/assets/AgentModeCodePane-CKh7zuA4.js +2 -0
- package/client/dist/assets/AgentModeJobsPane-BDokPiLJ.js +1 -0
- package/client/dist/assets/AgentsPage-BFVAZbBj.js +87 -0
- package/client/dist/assets/AnalyticsPage-3Y9LuWwz.js +1 -0
- package/client/dist/assets/CodePage-C9KyuV_C.js +3 -0
- package/client/dist/assets/DesktopAnalyticsPage-Bhi54bXQ.js +1 -0
- package/client/dist/assets/{DocsDialog-C76VdBJC.js → DocsDialog-Dj6mt4ga.js} +2 -2
- package/client/dist/assets/{DocsPage-ziMlj6B6.js → DocsPage-24yWglNI.js} +1 -1
- package/client/dist/assets/ExportDropdown-ophapHrT.js +1 -0
- package/client/dist/assets/InteractiveJobComposer-C1ucmxQh.js +19 -0
- package/client/dist/assets/JobDetailModal-DNWmgVHD.js +1 -0
- package/client/dist/assets/JobDetailPage-BcajZyuu.js +1 -0
- package/client/dist/assets/JobsPage-B9nTZw6e.js +1 -0
- package/client/dist/assets/LoopBuilderPage-Oc48iOFW.js +7 -0
- package/client/dist/assets/LoopPreviewModal-ae0Fr6SU.js +1 -0
- package/client/dist/assets/LoopsPage-CUEqzUBI.js +1 -0
- package/client/dist/assets/MinimizedChatsContext-Deg39peL.js +1 -0
- package/client/dist/assets/PluginsPage-C1xtARIx.js +2 -0
- package/client/dist/assets/ProjectSettingsDialog-JZzrXFBN.js +1 -0
- package/client/dist/assets/RepositoryDeliveries-CSDsiVUz.js +5 -0
- package/client/dist/assets/RepositoryScopeSelector-D-RCaknl.js +1 -0
- package/client/dist/assets/ReviewPacketPage-x5EfGTiL.js +1 -0
- package/client/dist/assets/TemplatePreviewModal-BSyuZxGq.js +1 -0
- package/client/dist/assets/TicketDetailModalContext-DqyF0so-.js +14 -0
- package/client/dist/assets/Trans-SnMX4MhW.js +1 -0
- package/client/dist/assets/agent-BdT0SB1D.js +1 -0
- package/client/dist/assets/agent-Bh9K7iEt.js +1 -0
- package/client/dist/assets/agent-C4ibdFk9.js +1 -0
- package/client/dist/assets/agent-CSFY22Zo.js +1 -0
- package/client/dist/assets/agent-DAnqxUn8.js +1 -0
- package/client/dist/assets/agent-DTQiu0Mi.js +1 -0
- package/client/dist/assets/agent-T54WuR_Y.js +1 -0
- package/client/dist/assets/agent-i75WDZOA.js +1 -0
- package/client/dist/assets/browser-05vRDg_y.js +1 -0
- package/client/dist/assets/browser-Bulb-cB1.js +1 -0
- package/client/dist/assets/browser-C7f-Jsg1.js +1 -0
- package/client/dist/assets/browser-CG0PE1j_.js +1 -0
- package/client/dist/assets/browser-CMzaqAjo.js +1 -0
- package/client/dist/assets/browser-DMajYdNX.js +1 -0
- package/client/dist/assets/browser-DxcOHrdP.js +1 -0
- package/client/dist/assets/browser-ObE7xVo3.js +1 -0
- package/client/dist/assets/builder-765NeYTp.js +1 -0
- package/client/dist/assets/builder-8MG3V6ea.js +1 -0
- package/client/dist/assets/builder-B-1UBs5K.js +1 -0
- package/client/dist/assets/builder-BTiGnPT0.js +1 -0
- package/client/dist/assets/builder-Cx9Rz-7u.js +1 -0
- package/client/dist/assets/builder-DKFMELBl.js +1 -0
- package/client/dist/assets/builder-OcmKrC0h.js +1 -0
- package/client/dist/assets/builder-P9qVBXFZ.js +1 -0
- package/client/dist/assets/{button-CJqFnUOL.js → button-DCBqEaZl.js} +1 -1
- package/client/dist/assets/{code-CkUuW4m9.js → code-B37GRg9C.js} +1 -1
- package/client/dist/assets/{code-B0O8-p8k.js → code-BBOEZbf9.js} +1 -1
- package/client/dist/assets/{code-B1VLZDhS.js → code-BRm-jcyx.js} +1 -1
- package/client/dist/assets/{code-Bpj4Ddml.js → code-CF7w62CR.js} +1 -1
- package/client/dist/assets/{code-BykBZ5-e.js → code-CLuvJt2r.js} +1 -1
- package/client/dist/assets/{code-B86IMAfz.js → code-D0RCm59f.js} +1 -1
- package/client/dist/assets/code-DaZ-ZwCT.js +1 -0
- package/client/dist/assets/{code-cAqaqMTx.js → code-iIHkGaCL.js} +1 -1
- package/client/dist/assets/commands-0XjAftla.js +1 -0
- package/client/dist/assets/commands-BAWSYHjQ.js +1 -0
- package/client/dist/assets/commands-BBwowgiE.js +1 -0
- package/client/dist/assets/commands-BacjIZLN.js +1 -0
- package/client/dist/assets/commands-Bs62IAZ3.js +1 -0
- package/client/dist/assets/commands-CLPdh-Hh.js +1 -0
- package/client/dist/assets/commands-DlbFzgrp.js +1 -0
- package/client/dist/assets/commands-LjPSsi5F.js +1 -0
- package/client/dist/assets/common-B5AZNYMH.js +1 -0
- package/client/dist/assets/common-BFZ0c_vN.js +1 -0
- package/client/dist/assets/common-BrlwEnL7.js +1 -0
- package/client/dist/assets/common-BttQx3z4.js +1 -0
- package/client/dist/assets/common-CM65k_-e.js +1 -0
- package/client/dist/assets/common-D4mvFFlu.js +1 -0
- package/client/dist/assets/common-TZ9Ny96S.js +1 -0
- package/client/dist/assets/common-nFpSF4GC.js +1 -0
- package/client/dist/assets/dashboard-B14uYXkG.js +1 -0
- package/client/dist/assets/dashboard-BK_mez-z.js +1 -0
- package/client/dist/assets/dashboard-BfBir_6o.js +1 -0
- package/client/dist/assets/dashboard-Bt0cCzeG.js +1 -0
- package/client/dist/assets/dashboard-BwkjxI6H.js +1 -0
- package/client/dist/assets/{dashboard-C8WOEiPu.js → dashboard-C3bDKf7s.js} +1 -1
- package/client/dist/assets/dashboard-CdvjGfog.js +1 -0
- package/client/dist/assets/dashboard-fRrP0Kv0.js +1 -0
- package/client/dist/assets/dialog-SRw01_QU.js +45 -0
- package/client/dist/assets/dist-js-CJ_2XX1Y.js +1 -0
- package/client/dist/assets/formatDistanceToNow-DSj5DprF.js +1 -0
- package/client/dist/assets/getTimezoneOffsetInMilliseconds-YhD8rIEs.js +1 -0
- package/client/dist/assets/index-DwAmL7m5.css +2 -0
- package/client/dist/assets/index-EzrkUb8B.js +76 -0
- package/client/dist/assets/{jira-api-B1-MUehg.js → jira-api-CSCqGr-Q.js} +1 -1
- package/client/dist/assets/{jobs-CN_zv4Gr.js → jobs-B8rrzfyZ.js} +1 -1
- package/client/dist/assets/{jobs-DUlWSYq2.js → jobs-BS6RTurf.js} +1 -1
- package/client/dist/assets/{jobs-Bpks6doU.js → jobs-BTT0Bsks.js} +1 -1
- package/client/dist/assets/{jobs-Bgq-PZkC.js → jobs-CezU69AE.js} +1 -1
- package/client/dist/assets/{jobs-SwYS2M-B.js → jobs-ClFJae0s.js} +1 -1
- package/client/dist/assets/{jobs-BAls3bWq.js → jobs-DJSFIFVy.js} +1 -1
- package/client/dist/assets/{jobs-DhxKgWFq.js → jobs-DhgGr8vm.js} +1 -1
- package/client/dist/assets/{jobs-B_TeSQxb.js → jobs-DzMjUcaN.js} +1 -1
- package/client/dist/assets/mcp-6rMoZfEO.js +1 -0
- package/client/dist/assets/mcp-BD8sL5mP.js +1 -0
- package/client/dist/assets/mcp-BkcA3I5s.js +1 -0
- package/client/dist/assets/mcp-CCpSelOh.js +1 -0
- package/client/dist/assets/mcp-CUAN6ORK.js +1 -0
- package/client/dist/assets/mcp-CqbRxRTE.js +1 -0
- package/client/dist/assets/mcp-DP2YfVhO.js +1 -0
- package/client/dist/assets/mcp-DUVQamDz.js +1 -0
- package/client/dist/assets/narration-B-ynWTpn.js +1 -0
- package/client/dist/assets/narration-Bj_laZwg.js +1 -0
- package/client/dist/assets/narration-BxHpmMv_.js +1 -0
- package/client/dist/assets/narration-C1Px0-0j.js +1 -0
- package/client/dist/assets/narration-CAXhGbzM.js +1 -0
- package/client/dist/assets/narration-DQ1l_Kq6.js +1 -0
- package/client/dist/assets/narration-XISXwqZd.js +1 -0
- package/client/dist/assets/narration-kwzfglQo.js +1 -0
- package/client/dist/assets/nav-1-Hh2kOz.js +1 -0
- package/client/dist/assets/nav-B34QkESm.js +1 -0
- package/client/dist/assets/nav-Bap_UXAq.js +1 -0
- package/client/dist/assets/{nav-pYuXq7sf.js → nav-BwxXIB24.js} +1 -1
- package/client/dist/assets/nav-Ch-zHBG5.js +1 -0
- package/client/dist/assets/nav-CygC_WN-.js +1 -0
- package/client/dist/assets/{nav-iwjCg5FR.js → nav-KnnLQtOQ.js} +1 -1
- package/client/dist/assets/nav-m00uxYUG.js +1 -0
- package/client/dist/assets/packet-BQ2tXfJI.js +1 -0
- package/client/dist/assets/packet-BdpOJ1Cy.js +1 -0
- package/client/dist/assets/packet-C3Ievb9R.js +1 -0
- package/client/dist/assets/packet-C4zJIG0Y.js +1 -0
- package/client/dist/assets/packet-Cx-zRuWn.js +1 -0
- package/client/dist/assets/packet-DUTGX52M.js +1 -0
- package/client/dist/assets/packet-DhsKtUPB.js +1 -0
- package/client/dist/assets/packet-z3LJ8Zyy.js +1 -0
- package/client/dist/assets/project-repositories-DYhTvwQ1.js +1 -0
- package/client/dist/assets/provider-capabilities-B-wOHfLP.js +1 -0
- package/client/dist/assets/settings-BBt2xtDQ.js +1 -0
- package/client/dist/assets/settings-BJB9RKJj.js +1 -0
- package/client/dist/assets/settings-CHwQR2rB.js +1 -0
- package/client/dist/assets/settings-CrMi3kSi.js +1 -0
- package/client/dist/assets/settings-DKyhDGD1.js +1 -0
- package/client/dist/assets/settings-Deupji9c.js +1 -0
- package/client/dist/assets/settings-DqRMmwAM.js +1 -0
- package/client/dist/assets/settings-J4rj6vo-.js +1 -0
- package/client/dist/assets/setup-2xGgu4Sk.js +1 -0
- package/client/dist/assets/setup-Ba11l2JA.js +1 -0
- package/client/dist/assets/setup-BzW4kMok.js +1 -0
- package/client/dist/assets/setup-COzwYhkJ.js +1 -0
- package/client/dist/assets/setup-CRVULhtF.js +1 -0
- package/client/dist/assets/setup-DgvlxwpE.js +1 -0
- package/client/dist/assets/setup-Dw_wYeEY.js +1 -0
- package/client/dist/assets/setup-ZPtEeJPW.js +1 -0
- package/client/dist/assets/spending-BqbdPm_4.js +1 -0
- package/client/dist/assets/square-Ci-PFcFe.js +1 -0
- package/client/dist/assets/{terminal-BFRZnMPP.js → terminal-BAzpHtpY.js} +1 -1
- package/client/dist/assets/{terminal-IahE4LW9.js → terminal-C7dHeR3x.js} +1 -1
- package/client/dist/assets/{terminal-tITa81iz.js → terminal-CMla5Qsz.js} +1 -1
- package/client/dist/assets/{terminal-BZiIu3rf.js → terminal-CdNXH-n9.js} +1 -1
- package/client/dist/assets/{terminal-ScjyXNxQ.js → terminal-Cw5bZsmV.js} +1 -1
- package/client/dist/assets/{terminal-C2NAd7BW.js → terminal-DFUP4eNv.js} +1 -1
- package/client/dist/assets/{terminal-d7GadXbN.js → terminal-DGqljeK_.js} +1 -1
- package/client/dist/assets/{terminal-QpXZU2Bn.js → terminal-cU70NodO.js} +1 -1
- package/client/dist/assets/useDesktop-RSMTyy7V.js +1 -0
- package/client/dist/assets/useSharedWebSocket-BOdS5_Iz.js +2 -0
- package/client/dist/index.html +34 -35
- package/docs/README.md +3 -0
- package/docs/agent-live-steering.md +57 -0
- package/docs/ci-cd.md +57 -0
- package/docs/code-explorer.md +48 -0
- package/docs/codex.md +12 -1
- package/docs/features/detachable-mission-windows.md +59 -0
- package/docs/guide/de/integrations/5-mcp-server.md +13 -9
- package/docs/guide/de/integrations/6-agent-chat.md +1 -0
- package/docs/guide/en/integrations/5-mcp-server.md +13 -9
- package/docs/guide/en/integrations/6-agent-chat.md +1 -0
- package/docs/guide/es/integrations/5-mcp-server.md +13 -9
- package/docs/guide/es/integrations/6-agent-chat.md +1 -0
- package/docs/guide/fr/integrations/5-mcp-server.md +13 -9
- package/docs/guide/fr/integrations/6-agent-chat.md +1 -0
- package/docs/guide/it/integrations/5-mcp-server.md +13 -9
- package/docs/guide/it/integrations/6-agent-chat.md +1 -0
- package/docs/guide/ja/integrations/5-mcp-server.md +13 -9
- package/docs/guide/ja/integrations/6-agent-chat.md +1 -0
- package/docs/guide/pt/integrations/5-mcp-server.md +13 -9
- package/docs/guide/pt/integrations/6-agent-chat.md +1 -0
- package/docs/guide/zh/integrations/5-mcp-server.md +13 -9
- package/docs/guide/zh/integrations/6-agent-chat.md +1 -0
- package/docs/internals/README.md +1 -0
- package/docs/internals/browser-capture-performance.md +55 -3
- package/docs/internals/browser-login-popups.md +45 -0
- package/docs/internals/browser-native-retina-audit.md +39 -0
- package/docs/internals/ci-cd-audit.md +39 -0
- package/docs/internals/core-runtime-updates.md +68 -0
- package/docs/internals/embedded-browser-native-webview-evaluation.md +2 -0
- package/docs/internals/implementation-reliability-audit.md +93 -0
- package/docs/internals/interactive-jobs.md +110 -6
- package/docs/internals/mcp-mission-audit.md +76 -0
- package/docs/internals/project-builder.md +404 -42
- package/docs/internals/safe-pr-review-flow.md +52 -10
- package/docs/internals/startup-recovery-audit.md +58 -0
- package/docs/mcp.md +74 -41
- package/docs/mission-processes.md +59 -0
- package/docs/multi-repo-projects.md +51 -0
- package/docs/platforms/windows-parity.md +70 -0
- package/docs/platforms/windows.md +13 -11
- package/docs/running-pipelines.md +14 -0
- package/mcp-bridge/dist/specrails-mcp.js +7186 -0
- package/package.json +9 -4
- package/server/dist/agent-chat-manager.js +537 -68
- package/server/dist/agent-chat-router.js +87 -6
- package/server/dist/agent-context-resolver.js +129 -25
- package/server/dist/agent-defaults.js +19 -11
- package/server/dist/agent-input-store.js +206 -0
- package/server/dist/agent-mcp-config.js +54 -15
- package/server/dist/agent-operator-prompt.js +255 -65
- package/server/dist/agent-spec-framing.js +202 -0
- package/server/dist/agent-steering.js +218 -0
- package/server/dist/agent-store.js +153 -0
- package/server/dist/api-not-found.js +8 -0
- package/server/dist/background-process-control.js +164 -0
- package/server/dist/background-process-service.js +61 -0
- package/server/dist/background-process-store.js +242 -0
- package/server/dist/background-windows-bootstrap.js +238 -0
- package/server/dist/blueprint-chat-manager.js +476 -62
- package/server/dist/blueprint-commit.js +21 -1
- package/server/dist/blueprint-draft-parser.js +122 -17
- package/server/dist/blueprint-generation.js +238 -0
- package/server/dist/blueprint-operator-prompt.js +91 -63
- package/server/dist/blueprint-router.js +55 -3
- package/server/dist/blueprint-spec-fixtures.js +85 -0
- package/server/dist/blueprint-spec-quality.js +57 -16
- package/server/dist/blueprint-store.js +92 -1
- package/server/dist/browser-capture-manager.js +86 -30
- package/server/dist/browser-playwright.js +168 -45
- package/server/dist/browser-viewport.js +26 -0
- package/server/dist/chat-manager.js +23 -15
- package/server/dist/chromium-archive.cjs +83 -0
- package/server/dist/chromium-resolver.js +72 -64
- package/server/dist/code-activity.js +145 -0
- package/server/dist/code-explorer-router.js +714 -114
- package/server/dist/core-compat.js +28 -1
- package/server/dist/core-execution.js +111 -0
- package/server/dist/core-package.js +4 -16
- package/server/dist/core-runtime.js +202 -0
- package/server/dist/core-update-manager.js +161 -60
- package/server/dist/core-update-state.js +29 -0
- package/server/dist/db.js +66 -35
- package/server/dist/delivery-evidence.js +142 -39
- package/server/dist/desktop-db.js +239 -43
- package/server/dist/desktop-router.js +109 -9
- package/server/dist/dev-ports.js +8 -3
- package/server/dist/file-provenance.js +34 -22
- package/server/dist/file-story-manager.js +62 -64
- package/server/dist/file-story.js +118 -35
- package/server/dist/file-summary-generator.js +46 -5
- package/server/dist/file-summary-manager.js +355 -106
- package/server/dist/framework-manager.js +98 -110
- package/server/dist/framework-reseed.js +76 -10
- package/server/dist/git-diagnostics.js +2 -2
- package/server/dist/headroom-manager.js +38 -3
- package/server/dist/host-control.js +52 -0
- package/server/dist/index.js +43 -2
- package/server/dist/interactive-job-session.js +149 -16
- package/server/dist/internal-api.js +48 -0
- package/server/dist/jira/jira-materializer.js +1 -0
- package/server/dist/json-tolerant.js +174 -0
- package/server/dist/legacy-migration.js +4 -2
- package/server/dist/loop-command-catalog.js +83 -21
- package/server/dist/loop-constants.js +6 -1
- package/server/dist/loop-decider.js +11 -1
- package/server/dist/loop-executors.js +194 -45
- package/server/dist/loop-factory.js +11 -4
- package/server/dist/loop-graph.js +90 -4
- package/server/dist/loop-run-manager.js +301 -69
- package/server/dist/loop-shell-invocation.js +21 -0
- package/server/dist/loop-step-idle.js +55 -0
- package/server/dist/loop-templates.js +31 -7
- package/server/dist/mcp/agent-capability.js +37 -4
- package/server/dist/mcp/guide.js +149 -36
- package/server/dist/mcp/mcp-admin-router.js +20 -4
- package/server/dist/mcp/mcp-server.js +89 -43
- package/server/dist/mcp/mcp-tiers.js +6 -5
- package/server/dist/mcp/mcp-token.js +3 -1
- package/server/dist/mcp/resources.js +5 -4
- package/server/dist/mcp/tools/catalog.js +4 -0
- package/server/dist/mcp/tools/code.js +125 -14
- package/server/dist/mcp/tools/context.js +153 -0
- package/server/dist/mcp/tools/git.js +15 -5
- package/server/dist/mcp/tools/jobs.js +84 -71
- package/server/dist/mcp/tools/loops.js +17 -6
- package/server/dist/mcp/tools/meta.js +74 -27
- package/server/dist/mcp/tools/mission.js +29 -0
- package/server/dist/mcp/tools/projects.js +22 -6
- package/server/dist/mcp/tools/rails.js +48 -11
- package/server/dist/mcp/tools/specs.js +86 -14
- package/server/dist/mcp/tools/support.js +1 -1
- package/server/dist/mcp/tools/types.js +117 -43
- package/server/dist/mcp/tools/watch.js +144 -51
- package/server/dist/milestone-chain-store.js +164 -0
- package/server/dist/milestone-chain.js +487 -0
- package/server/dist/milestone-progress.js +348 -0
- package/server/dist/mobile/mobile-devices.js +10 -7
- package/server/dist/mobile/mobile-gateway.js +2 -0
- package/server/dist/mobile/mobile-missions.js +214 -0
- package/server/dist/mobile/mobile-redact.js +17 -3
- package/server/dist/mobile/mobile-router.js +46 -4
- package/server/dist/mobile/mobile-ws.js +49 -4
- package/server/dist/multi-repo-bases.js +56 -0
- package/server/dist/multi-repo-checkout.js +101 -0
- package/server/dist/multi-repo-delivery.js +100 -0
- package/server/dist/multi-repo-execution-store.js +65 -0
- package/server/dist/multi-repo-execution.js +453 -0
- package/server/dist/offline-assemble.js +10 -8
- package/server/dist/openspec-runtime-plugin-commands.json +18 -0
- package/server/dist/openspec-runtime-plugin.js +120 -0
- package/server/dist/plugins/codex-spawn.js +49 -0
- package/server/dist/plugins/serena/manifest.js +2 -2
- package/server/dist/plugins/serena/templates/instructions.md +16 -0
- package/server/dist/pr-publisher.js +1 -1
- package/server/dist/profiles-router.js +3 -18
- package/server/dist/project-code-discovery.js +142 -0
- package/server/dist/project-git.js +19 -21
- package/server/dist/project-registry.js +272 -20
- package/server/dist/project-repositories.js +157 -0
- package/server/dist/project-repository-provenance.js +31 -0
- package/server/dist/project-router-background-processes.js +105 -0
- package/server/dist/project-router-git.js +75 -16
- package/server/dist/project-router-jobs.js +2 -126
- package/server/dist/project-router-loop-runs.js +72 -2
- package/server/dist/project-router-tickets.js +53 -8
- package/server/dist/project-router.js +239 -38
- package/server/dist/provider-detection.js +18 -6
- package/server/dist/provider-limit.js +64 -0
- package/server/dist/providers/claude-adapter.js +26 -1
- package/server/dist/providers/claude-background-tasks.js +18 -0
- package/server/dist/providers/claude-live-session.js +383 -0
- package/server/dist/providers/codex-adapter.js +19 -0
- package/server/dist/providers/codex-live-session.js +545 -0
- package/server/dist/providers/gemini-adapter.js +1 -0
- package/server/dist/providers/live-session-types.js +12 -0
- package/server/dist/providers/live-session.js +13 -0
- package/server/dist/providers/terminal-result.js +24 -0
- package/server/dist/queue-manager.js +2 -0
- package/server/dist/rail-isolated-launch.js +141 -16
- package/server/dist/rail-pr-decision.js +364 -99
- package/server/dist/rail-pr-store.js +24 -10
- package/server/dist/rail-worktree-release.js +34 -2
- package/server/dist/rail-worktrees-store.js +4 -4
- package/server/dist/rails-router.js +249 -55
- package/server/dist/repo-lock.js +92 -17
- package/server/dist/review-packet.js +23 -13
- package/server/dist/schemas/file-summary.v1.json +59 -59
- package/server/dist/schemas/profile.v1.json +137 -137
- package/server/dist/setup-manager.js +52 -8
- package/server/dist/shell-integration/bash-shim.bash +45 -0
- package/server/dist/shell-integration/fish-shim.fish +36 -0
- package/server/dist/shell-integration/powershell-shim.ps1 +48 -0
- package/server/dist/shell-integration/zsh-shim.zsh +47 -0
- package/server/dist/smash-runner.js +1 -0
- package/server/dist/spawn-lifecycle.js +44 -27
- package/server/dist/spec-contract-prompt.js +143 -0
- package/server/dist/spec-draft-parser.js +6 -1
- package/server/dist/spec-models.js +1 -0
- package/server/dist/stuck-run-detector.js +1 -0
- package/server/dist/terminal-manager.js +21 -1
- package/server/dist/terminal-shell-integration.js +4 -1
- package/server/dist/transient-children.js +619 -230
- package/server/dist/util/cli-prompt.js +78 -5
- package/server/dist/util/sqlite-migrations.js +20 -0
- package/server/dist/util/win-spawn.js +18 -3
- package/server/dist/util/windows-provider-shell.js +29 -0
- package/server/dist/verification-sentinel.js +14 -0
- package/server/dist/vitest-setup.js +2 -0
- package/server/dist/windows-job-supervisor.js +133 -0
- package/server/dist/workspace-manager.js +9 -30
- package/server/dist/worktree-manager.js +17 -2
- package/server/dist/worktree-overlay.js +3 -1
- package/client/dist/assets/ActivityFeedPage-tAQcWs1R.js +0 -1
- package/client/dist/assets/AgentBrowserCapture-BX8BNae5.js +0 -1
- package/client/dist/assets/AgentModeAnalyticsPane-BVAqCL0K.js +0 -2
- package/client/dist/assets/AgentModeCodePane-9XfqQc9c.js +0 -2
- package/client/dist/assets/AgentModeJobsPane-BCjLsPhc.js +0 -1
- package/client/dist/assets/AgentsPage-C8J_42sm.js +0 -87
- package/client/dist/assets/AnalyticsPage-BtQuyfJm.js +0 -1
- package/client/dist/assets/CodePage-BZ2MwdCu.js +0 -2
- package/client/dist/assets/DesktopAnalyticsPage-Bh_29V4g.js +0 -1
- package/client/dist/assets/ExportDropdown-BrtfWzEG.js +0 -1
- package/client/dist/assets/InteractiveJobComposer-BCZiSfzc.js +0 -19
- package/client/dist/assets/JobDetailModal-NAJmuYIT.js +0 -1
- package/client/dist/assets/JobDetailPage-fE0ZycUX.js +0 -1
- package/client/dist/assets/JobsPage-z5M_HVJG.js +0 -1
- package/client/dist/assets/LoopBuilderPage-j1FQS47c.js +0 -7
- package/client/dist/assets/LoopPreviewModal-DhDJjdCe.js +0 -1
- package/client/dist/assets/LoopsPage-Ll7zORZb.js +0 -1
- package/client/dist/assets/MinimizedChatsContext-Bd3OqR_S.js +0 -1
- package/client/dist/assets/PluginsPage-DN4kGvJt.js +0 -2
- package/client/dist/assets/ProjectSettingsDialog-OgsgxjzF.js +0 -1
- package/client/dist/assets/ReviewPacketPage-DFog4sXn.js +0 -1
- package/client/dist/assets/TemplatePreviewModal-L2XxqiOD.js +0 -1
- package/client/dist/assets/TicketDetailModal-CsF-AWZX.js +0 -14
- package/client/dist/assets/Trans-Bsz9_gyq.js +0 -1
- package/client/dist/assets/agent-BQgKv_2F.js +0 -1
- package/client/dist/assets/agent-C1NRDJqL.js +0 -1
- package/client/dist/assets/agent-CLNleiy6.js +0 -1
- package/client/dist/assets/agent-CmceArkQ.js +0 -1
- package/client/dist/assets/agent-D3UxQ3SR.js +0 -1
- package/client/dist/assets/agent-DB1JeE64.js +0 -1
- package/client/dist/assets/agent-DXkHcQ-D.js +0 -1
- package/client/dist/assets/agent-DjJJJ2M8.js +0 -1
- package/client/dist/assets/auth-D_5bQv5W.js +0 -1
- package/client/dist/assets/ban-E4EaZe3a.js +0 -1
- package/client/dist/assets/bot-DzuuuC5R.js +0 -1
- package/client/dist/assets/browser-BgqHDMXP.js +0 -1
- package/client/dist/assets/browser-CsKJtrpv.js +0 -1
- package/client/dist/assets/browser-Cu58EpZU.js +0 -1
- package/client/dist/assets/browser-CvXOJm-j.js +0 -1
- package/client/dist/assets/browser-DOrFh1c7.js +0 -1
- package/client/dist/assets/browser-DQv_Jo7x.js +0 -1
- package/client/dist/assets/browser-LPgxvJu2.js +0 -1
- package/client/dist/assets/browser-eL-3MIEI.js +0 -1
- package/client/dist/assets/builder-0IWtosUm.js +0 -1
- package/client/dist/assets/builder-Bf2N6XWn.js +0 -1
- package/client/dist/assets/builder-BnqaaBkT.js +0 -1
- package/client/dist/assets/builder-BxxfGgAO.js +0 -1
- package/client/dist/assets/builder-C5IIgWsq.js +0 -1
- package/client/dist/assets/builder-CHuahItu.js +0 -1
- package/client/dist/assets/builder-CjWosXL_.js +0 -1
- package/client/dist/assets/builder-CpCIOoKj.js +0 -1
- package/client/dist/assets/code-CHPkOkDS.js +0 -1
- package/client/dist/assets/commands-6tYQih5m.js +0 -1
- package/client/dist/assets/commands-BK2QinXN.js +0 -1
- package/client/dist/assets/commands-C2Nca76h.js +0 -1
- package/client/dist/assets/commands-CKdLihNX.js +0 -1
- package/client/dist/assets/commands-CYIBTAlc.js +0 -1
- package/client/dist/assets/commands-CjUtYJIM.js +0 -1
- package/client/dist/assets/commands-Cq_u_8y9.js +0 -1
- package/client/dist/assets/commands-D_ZHYQug.js +0 -1
- package/client/dist/assets/common-6MLbD-jM.js +0 -1
- package/client/dist/assets/common-BKGEu4rm.js +0 -1
- package/client/dist/assets/common-BNbnv8cE.js +0 -1
- package/client/dist/assets/common-BqGWtDbt.js +0 -1
- package/client/dist/assets/common-DBtTzeYm.js +0 -1
- package/client/dist/assets/common-DEW3Nr8n.js +0 -1
- package/client/dist/assets/common-DHsv9N5d.js +0 -1
- package/client/dist/assets/common-mkrEcW-Y.js +0 -1
- package/client/dist/assets/dashboard-B9GhC5ku.js +0 -1
- package/client/dist/assets/dashboard-CBnv6zOI.js +0 -1
- package/client/dist/assets/dashboard-CaP0fU87.js +0 -1
- package/client/dist/assets/dashboard-D4ZZ4x84.js +0 -1
- package/client/dist/assets/dashboard-DmuTQBX8.js +0 -1
- package/client/dist/assets/dashboard-DynLYj3d.js +0 -1
- package/client/dist/assets/dashboard-zj4gGFJ1.js +0 -1
- package/client/dist/assets/dialog-CdWCb02j.js +0 -45
- package/client/dist/assets/dist-bVqfYmby.js +0 -1
- package/client/dist/assets/dist-js-CWeUdf5Z.js +0 -1
- package/client/dist/assets/formatDistanceToNow-DQCvMgUc.js +0 -1
- package/client/dist/assets/getTimezoneOffsetInMilliseconds-C16cxSe1.js +0 -1
- package/client/dist/assets/git-refresh-wTW0rO0Z.js +0 -1
- package/client/dist/assets/i18n-hnX2DPmo.js +0 -2
- package/client/dist/assets/index-DIhMEAVw.js +0 -66
- package/client/dist/assets/index-DwygcU7s.css +0 -2
- package/client/dist/assets/mcp-B8X8wBLu.js +0 -1
- package/client/dist/assets/mcp-C2AojZZN.js +0 -1
- package/client/dist/assets/mcp-D8BT78_L.js +0 -1
- package/client/dist/assets/mcp-Df5RWBNk.js +0 -1
- package/client/dist/assets/mcp-Dj7BgMQW.js +0 -1
- package/client/dist/assets/mcp-Q7iT7rcz.js +0 -1
- package/client/dist/assets/mcp-YlvNvJbW.js +0 -1
- package/client/dist/assets/mcp-mKFpTSRb.js +0 -1
- package/client/dist/assets/narration-4Z2VUhZw.js +0 -1
- package/client/dist/assets/narration-BGuiEDj3.js +0 -1
- package/client/dist/assets/narration-BuSoqRmv.js +0 -1
- package/client/dist/assets/narration-CB9t29fy.js +0 -1
- package/client/dist/assets/narration-CU3x8FHD.js +0 -1
- package/client/dist/assets/narration-CWeXhfP5.js +0 -1
- package/client/dist/assets/narration-DRaDnhT5.js +0 -1
- package/client/dist/assets/narration-DtEbQgCw.js +0 -1
- package/client/dist/assets/nav-ApJVg3xu.js +0 -1
- package/client/dist/assets/nav-BKNky1Bg.js +0 -1
- package/client/dist/assets/nav-C6vWWKYx.js +0 -1
- package/client/dist/assets/nav-CXJ6Aw89.js +0 -1
- package/client/dist/assets/nav-CsmFBIDf.js +0 -1
- package/client/dist/assets/nav-DjkmQJjd.js +0 -1
- package/client/dist/assets/packet-B1IxaL5f.js +0 -1
- package/client/dist/assets/packet-Bh5kkbiB.js +0 -1
- package/client/dist/assets/packet-By8fqYxW.js +0 -1
- package/client/dist/assets/packet-CZdvA44E.js +0 -1
- package/client/dist/assets/packet-CrjHRQCP.js +0 -1
- package/client/dist/assets/packet-Dwi2pTtx.js +0 -1
- package/client/dist/assets/packet-_-ZIa0IR.js +0 -1
- package/client/dist/assets/packet-n0W6E1Hn.js +0 -1
- package/client/dist/assets/play-DNlrawbS.js +0 -1
- package/client/dist/assets/provider-capabilities-Doej9lfa.js +0 -1
- package/client/dist/assets/settings-Bd0x4TBa.js +0 -1
- package/client/dist/assets/settings-C1-7cXzC.js +0 -1
- package/client/dist/assets/settings-CAFaT38b.js +0 -1
- package/client/dist/assets/settings-Ct3v1nW2.js +0 -1
- package/client/dist/assets/settings-DHh7_IF5.js +0 -1
- package/client/dist/assets/settings-DQXAmdcv.js +0 -1
- package/client/dist/assets/settings-guzlhpup.js +0 -1
- package/client/dist/assets/settings-sZfTPMEH.js +0 -1
- package/client/dist/assets/setup-3jWaoVUO.js +0 -1
- package/client/dist/assets/setup-BjgRneb2.js +0 -1
- package/client/dist/assets/setup-BzFIjv-f.js +0 -1
- package/client/dist/assets/setup-C4VfQsxE.js +0 -1
- package/client/dist/assets/setup-DfUkpDbN.js +0 -1
- package/client/dist/assets/setup-ECe90Pmu.js +0 -1
- package/client/dist/assets/setup-dlqC4Jmm.js +0 -1
- package/client/dist/assets/setup-jwfLMiM2.js +0 -1
- package/client/dist/assets/spending-EC9xqio-.js +0 -1
- package/client/dist/assets/useDesktop-BQVt8lFd.js +0 -1
|
@@ -7,7 +7,13 @@ exports.updateBlueprintConversation = updateBlueprintConversation;
|
|
|
7
7
|
exports.deleteBlueprintConversation = deleteBlueprintConversation;
|
|
8
8
|
exports.addBlueprintMessage = addBlueprintMessage;
|
|
9
9
|
exports.listBlueprintMessages = listBlueprintMessages;
|
|
10
|
+
exports.saveBlueprintSnapshot = saveBlueprintSnapshot;
|
|
11
|
+
exports.saveBlueprintSnapshotIssue = saveBlueprintSnapshotIssue;
|
|
12
|
+
exports.getBlueprintSnapshot = getBlueprintSnapshot;
|
|
13
|
+
exports.markBlueprintCommitted = markBlueprintCommitted;
|
|
14
|
+
exports.listResumableBlueprintConversations = listResumableBlueprintConversations;
|
|
10
15
|
const crypto_1 = require("crypto");
|
|
16
|
+
const blueprint_draft_parser_1 = require("./blueprint-draft-parser");
|
|
11
17
|
function createBlueprintConversation(db, input = {}) {
|
|
12
18
|
const id = (0, crypto_1.randomUUID)();
|
|
13
19
|
db.prepare('INSERT INTO blueprint_conversations (id, provider, model) VALUES (?, ?, ?)').run(id, input.provider ?? 'claude', input.model ?? null);
|
|
@@ -38,7 +44,7 @@ function deleteBlueprintConversation(db, id) {
|
|
|
38
44
|
}
|
|
39
45
|
function addBlueprintMessage(db, input) {
|
|
40
46
|
const id = (0, crypto_1.randomUUID)();
|
|
41
|
-
db.prepare('INSERT INTO blueprint_messages (id, conversation_id, role, content) VALUES (?, ?, ?, ?)').run(id, input.conversationId, input.role, input.content);
|
|
47
|
+
db.prepare('INSERT INTO blueprint_messages (id, conversation_id, role, content, raw_content, intent) VALUES (?, ?, ?, ?, ?, ?)').run(id, input.conversationId, input.role, input.content, input.rawContent ?? null, input.intent ?? null);
|
|
42
48
|
db.prepare("UPDATE blueprint_conversations SET updated_at = datetime('now') WHERE id = ?").run(input.conversationId);
|
|
43
49
|
return db.prepare('SELECT * FROM blueprint_messages WHERE id = ?').get(id);
|
|
44
50
|
}
|
|
@@ -47,3 +53,88 @@ function listBlueprintMessages(db, conversationId) {
|
|
|
47
53
|
.prepare('SELECT * FROM blueprint_messages WHERE conversation_id = ? ORDER BY created_at, rowid')
|
|
48
54
|
.all(conversationId);
|
|
49
55
|
}
|
|
56
|
+
function parseJsonColumn(value) {
|
|
57
|
+
if (!value)
|
|
58
|
+
return null;
|
|
59
|
+
try {
|
|
60
|
+
return JSON.parse(value);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/** Persist the ACCEPTED snapshot pair and clear any pending rejection. */
|
|
67
|
+
function saveBlueprintSnapshot(db, id, snapshot) {
|
|
68
|
+
db.prepare(`UPDATE blueprint_conversations
|
|
69
|
+
SET blueprint_json = ?, raw_blueprint_json = ?, snapshot_updated_at = datetime('now'),
|
|
70
|
+
snapshot_issue_json = NULL, updated_at = datetime('now')
|
|
71
|
+
WHERE id = ?`).run(JSON.stringify(snapshot.blueprint), JSON.stringify(snapshot.rawBlueprint ?? snapshot.blueprint), id);
|
|
72
|
+
}
|
|
73
|
+
/** Record why the last block was rejected (kept until a snapshot is accepted). */
|
|
74
|
+
function saveBlueprintSnapshotIssue(db, id, issue) {
|
|
75
|
+
db.prepare("UPDATE blueprint_conversations SET snapshot_issue_json = ?, updated_at = datetime('now') WHERE id = ?").run(issue ? JSON.stringify(issue) : null, id);
|
|
76
|
+
}
|
|
77
|
+
/** Read the persisted snapshot (normalized through `coerceBlueprint` so a
|
|
78
|
+
* row written by an older build stays readable; corrupt JSON reads as null). */
|
|
79
|
+
function getBlueprintSnapshot(db, id) {
|
|
80
|
+
const row = getBlueprintConversation(db, id);
|
|
81
|
+
if (!row)
|
|
82
|
+
return { blueprint: null, rawBlueprint: null, snapshotUpdatedAt: null, issue: null };
|
|
83
|
+
const rawBlueprint = parseJsonColumn(row.raw_blueprint_json);
|
|
84
|
+
const blueprint = (0, blueprint_draft_parser_1.coerceBlueprint)(parseJsonColumn(row.blueprint_json) ?? rawBlueprint);
|
|
85
|
+
const issueValue = parseJsonColumn(row.snapshot_issue_json);
|
|
86
|
+
const issue = issueValue && typeof issueValue.reason === 'string' && typeof issueValue.detail === 'string'
|
|
87
|
+
? { reason: issueValue.reason, detail: issueValue.detail, at: typeof issueValue.at === 'string' ? issueValue.at : '' }
|
|
88
|
+
: null;
|
|
89
|
+
return { blueprint, rawBlueprint: blueprint ? rawBlueprint ?? blueprint : null, snapshotUpdatedAt: row.snapshot_updated_at, issue };
|
|
90
|
+
}
|
|
91
|
+
function markBlueprintCommitted(db, id, projectId) {
|
|
92
|
+
db.prepare("UPDATE blueprint_conversations SET committed_project_id = ?, updated_at = datetime('now') WHERE id = ?").run(projectId, id);
|
|
93
|
+
}
|
|
94
|
+
function summarizeBlueprint(bp) {
|
|
95
|
+
if (!bp)
|
|
96
|
+
return { productName: null, platform: null, specCount: 0, specsComplete: false, dimensionsFilled: 0 };
|
|
97
|
+
const dims = [
|
|
98
|
+
bp.product.name !== '' && bp.product.pitch !== '',
|
|
99
|
+
bp.coreFlow !== '',
|
|
100
|
+
bp.platform !== '',
|
|
101
|
+
bp.stack.language !== '' || bp.stack.framework !== '',
|
|
102
|
+
bp.milestones.length > 0,
|
|
103
|
+
];
|
|
104
|
+
return {
|
|
105
|
+
productName: bp.product.name || null,
|
|
106
|
+
platform: bp.platform || null,
|
|
107
|
+
specCount: bp.m1Specs.length,
|
|
108
|
+
specsComplete: bp.specsComplete,
|
|
109
|
+
dimensionsFilled: dims.filter(Boolean).length,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Unfinished Builder conversations worth resuming: not committed and with at
|
|
114
|
+
* least one assistant reply (an empty bootstrap row is noise). Newest first.
|
|
115
|
+
*/
|
|
116
|
+
function listResumableBlueprintConversations(db, limit = 8) {
|
|
117
|
+
const rows = db
|
|
118
|
+
.prepare(`SELECT c.*, (SELECT COUNT(*) FROM blueprint_messages m WHERE m.conversation_id = c.id) AS message_count
|
|
119
|
+
FROM blueprint_conversations c
|
|
120
|
+
WHERE c.committed_project_id IS NULL
|
|
121
|
+
AND EXISTS (SELECT 1 FROM blueprint_messages m WHERE m.conversation_id = c.id AND m.role = 'assistant')
|
|
122
|
+
ORDER BY c.updated_at DESC, c.rowid DESC
|
|
123
|
+
LIMIT ?`)
|
|
124
|
+
.all(limit);
|
|
125
|
+
return rows.map((row) => {
|
|
126
|
+
const snapshot = getBlueprintSnapshot(db, row.id);
|
|
127
|
+
return {
|
|
128
|
+
id: row.id,
|
|
129
|
+
title: row.title,
|
|
130
|
+
provider: row.provider,
|
|
131
|
+
model: row.model,
|
|
132
|
+
created_at: row.created_at,
|
|
133
|
+
updated_at: row.updated_at,
|
|
134
|
+
messageCount: row.message_count,
|
|
135
|
+
...summarizeBlueprint(snapshot.blueprint),
|
|
136
|
+
hasSnapshot: snapshot.blueprint !== null,
|
|
137
|
+
pendingIssue: snapshot.issue?.reason ?? null,
|
|
138
|
+
};
|
|
139
|
+
});
|
|
140
|
+
}
|
|
@@ -10,6 +10,7 @@ const ids_1 = require("./ids");
|
|
|
10
10
|
const attachment_manager_1 = require("./attachment-manager");
|
|
11
11
|
const browser_capture_types_1 = require("./browser-capture-types");
|
|
12
12
|
const browser_playwright_1 = require("./browser-playwright");
|
|
13
|
+
const browser_viewport_1 = require("./browser-viewport");
|
|
13
14
|
const WS_OPEN = 1;
|
|
14
15
|
const DEFAULT_VIEWPORT = { width: 1280, height: 800 };
|
|
15
16
|
/** Playwright throws this family when the page/context/browser died underneath an
|
|
@@ -187,6 +188,14 @@ class BrowserCaptureManager {
|
|
|
187
188
|
this._reserved--;
|
|
188
189
|
throw err;
|
|
189
190
|
}
|
|
191
|
+
if (this.disposed) {
|
|
192
|
+
this._reserved--;
|
|
193
|
+
try {
|
|
194
|
+
await page.close();
|
|
195
|
+
}
|
|
196
|
+
catch { /* ignore */ }
|
|
197
|
+
throw new browser_capture_types_1.BrowserLaunchError('manager disposed');
|
|
198
|
+
}
|
|
190
199
|
// Re-check AFTER the awaits: another create that started before us may have
|
|
191
200
|
// committed its session in the meantime. If the cap is now exceeded, tear the
|
|
192
201
|
// extra page down and reject rather than silently exceeding the limit.
|
|
@@ -296,7 +305,7 @@ class BrowserCaptureManager {
|
|
|
296
305
|
s.popupView = true;
|
|
297
306
|
// Popups inherit the session viewport so coordinate mapping + screencast
|
|
298
307
|
// dimensions stay identical to the root page.
|
|
299
|
-
void popup.setViewport(s.viewport.width, s.viewport.height).catch(() => { });
|
|
308
|
+
void popup.setViewport(s.viewport.width, s.viewport.height, s.viewport.deviceScaleFactor).catch(() => { });
|
|
300
309
|
popup.onClose?.(() => this.dropPopup(s, popup));
|
|
301
310
|
popup.onNavigated?.(() => {
|
|
302
311
|
// Keep the "Login window — <origin>" label fresh while the top popup
|
|
@@ -398,27 +407,55 @@ class BrowserCaptureManager {
|
|
|
398
407
|
}
|
|
399
408
|
catch { /* ignore */ }
|
|
400
409
|
}
|
|
410
|
+
s.lastFrame = null; // never replay a previous popup/root frame under new chrome
|
|
401
411
|
if (desired) {
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
412
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
413
|
+
if (s.closed || !s.screencastDesired || this.activePage(s) !== desired)
|
|
414
|
+
return;
|
|
415
|
+
try {
|
|
416
|
+
await desired.startScreencast((frame) => {
|
|
417
|
+
if (s.closed || !s.screencastDesired || this.activePage(s) !== desired)
|
|
418
|
+
return;
|
|
419
|
+
s.lastFrame = frame.data;
|
|
420
|
+
for (const client of s.clients) {
|
|
421
|
+
if (client.readyState !== WS_OPEN)
|
|
422
|
+
continue;
|
|
423
|
+
// Keep slow clients current instead of queueing stale JPEGs.
|
|
424
|
+
if ((client.bufferedAmount ?? 0) > MAX_CLIENT_BUFFERED_BYTES)
|
|
425
|
+
continue;
|
|
426
|
+
try {
|
|
427
|
+
client.send(frame.data);
|
|
428
|
+
}
|
|
429
|
+
catch { /* drop */ }
|
|
430
|
+
}
|
|
431
|
+
});
|
|
432
|
+
s.screencastPage = desired;
|
|
405
433
|
return;
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
// Conflate for slow consumers: when the socket's send-buffer is
|
|
411
|
-
// already backed up, skip this frame for that client instead of
|
|
412
|
-
// queueing ever-staler frames behind the backlog (latency, not
|
|
413
|
-
// fps, is what makes a screencast feel sluggish).
|
|
414
|
-
if ((client.bufferedAmount ?? 0) > MAX_CLIENT_BUFFERED_BYTES)
|
|
415
|
-
continue;
|
|
434
|
+
}
|
|
435
|
+
catch {
|
|
436
|
+
// A rejected CDP start previously left this session permanently
|
|
437
|
+
// marked as casting. Release partial resources before one retry.
|
|
416
438
|
try {
|
|
417
|
-
|
|
439
|
+
await desired.stopScreencast();
|
|
418
440
|
}
|
|
419
|
-
catch { /*
|
|
441
|
+
catch { /* ignore */ }
|
|
420
442
|
}
|
|
421
|
-
}
|
|
443
|
+
}
|
|
444
|
+
// An OAuth window can self-close during the final failed CDP attach or
|
|
445
|
+
// its cleanup. The queued transition will resume its opener; a failure
|
|
446
|
+
// belonging to the old popup must not disconnect the whole session.
|
|
447
|
+
if (s.closed || !s.screencastDesired || this.activePage(s) !== desired)
|
|
448
|
+
return;
|
|
449
|
+
s.screencastDesired = false;
|
|
450
|
+
// Persistent failure must surface as a disconnected stream, not a ready
|
|
451
|
+
// socket that displays a blank canvas forever. A later attach can retry.
|
|
452
|
+
for (const client of s.clients) {
|
|
453
|
+
try {
|
|
454
|
+
client.close(1011, 'browser_stream_failed');
|
|
455
|
+
}
|
|
456
|
+
catch { /* ignore */ }
|
|
457
|
+
}
|
|
458
|
+
s.clients.clear();
|
|
422
459
|
}
|
|
423
460
|
}).catch(() => { });
|
|
424
461
|
return s.screencastOp;
|
|
@@ -461,16 +498,21 @@ class BrowserCaptureManager {
|
|
|
461
498
|
if (!s)
|
|
462
499
|
return;
|
|
463
500
|
if (event.type === 'resize') {
|
|
501
|
+
const normalized = (0, browser_viewport_1.normalizeBrowserViewport)(event.width, event.height, event.deviceScaleFactor ?? s.viewport.deviceScaleFactor);
|
|
464
502
|
s.viewport = {
|
|
465
|
-
width:
|
|
466
|
-
height:
|
|
503
|
+
width: normalized.width,
|
|
504
|
+
height: normalized.height,
|
|
505
|
+
...(event.deviceScaleFactor !== undefined || s.viewport.deviceScaleFactor !== undefined
|
|
506
|
+
? { deviceScaleFactor: (0, browser_viewport_1.normalizeBrowserDeviceScaleFactor)(event.deviceScaleFactor ?? s.viewport.deviceScaleFactor ?? 1) }
|
|
507
|
+
: {}),
|
|
467
508
|
};
|
|
468
509
|
// Keep every page of the session (root + popups) at the same viewport so
|
|
469
510
|
// switching between them never re-maps coordinates or resizes the canvas.
|
|
470
|
-
|
|
511
|
+
const resize = { type: 'resize', ...s.viewport };
|
|
512
|
+
await s.page.dispatchInput(resize);
|
|
471
513
|
for (const popup of s.popups) {
|
|
472
514
|
try {
|
|
473
|
-
await popup.dispatchInput(
|
|
515
|
+
await popup.dispatchInput(resize);
|
|
474
516
|
}
|
|
475
517
|
catch { /* ignore */ }
|
|
476
518
|
}
|
|
@@ -486,22 +528,36 @@ class BrowserCaptureManager {
|
|
|
486
528
|
const s = this.getSessionForActivity(sessionId);
|
|
487
529
|
if (!s)
|
|
488
530
|
return null;
|
|
531
|
+
// Snapshot before awaiting: an OAuth window can close, or the user can
|
|
532
|
+
// switch views, while a navigation is in flight. Its result still belongs
|
|
533
|
+
// to the page the user acted on, never to the newly visible opener.
|
|
534
|
+
const page = this.activePage(s);
|
|
535
|
+
const isPopup = page !== s.page;
|
|
489
536
|
this.beginOperation(s);
|
|
490
537
|
try {
|
|
491
538
|
let result;
|
|
492
539
|
if (action === 'goto')
|
|
493
|
-
result = await
|
|
540
|
+
result = await page.goto(url ?? 'about:blank');
|
|
494
541
|
else if (action === 'back')
|
|
495
|
-
result = await
|
|
542
|
+
result = await page.goBack();
|
|
496
543
|
else if (action === 'forward')
|
|
497
|
-
result = await
|
|
544
|
+
result = await page.goForward();
|
|
498
545
|
else
|
|
499
|
-
result = await
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
546
|
+
result = await page.reload();
|
|
547
|
+
if (isPopup) {
|
|
548
|
+
if (!s.closed && !this.disposed && this.topPopup(s) === page)
|
|
549
|
+
this.broadcastPopupState(s);
|
|
550
|
+
// Popup redirect/callback URLs must never replace the project page's
|
|
551
|
+
// remembered URL, title or nav events (nor the client's root state).
|
|
552
|
+
return { ...result, target: 'popup' };
|
|
553
|
+
}
|
|
554
|
+
if (!s.closed && !this.disposed) {
|
|
555
|
+
s.url = result.url;
|
|
556
|
+
s.title = result.title;
|
|
557
|
+
if (result.url && result.url !== 'about:blank')
|
|
558
|
+
this.setLastUrl(result.url);
|
|
559
|
+
this.broadcastControl(s, { type: 'nav', url: result.url, title: result.title });
|
|
560
|
+
}
|
|
505
561
|
return result;
|
|
506
562
|
}
|
|
507
563
|
finally {
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
// Screencast uses CDP (`Page.startScreencast`) since Playwright exposes no
|
|
9
9
|
// screencast API; input + screenshot + DOM use Playwright's high-level page API.
|
|
10
10
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.PlaywrightPageHandle = void 0;
|
|
11
12
|
exports.isNavigableUrl = isNavigableUrl;
|
|
12
13
|
exports.screencastParams = screencastParams;
|
|
13
14
|
exports.normalizeUrl = normalizeUrl;
|
|
@@ -15,6 +16,7 @@ exports.chromiumLaunchArgs = chromiumLaunchArgs;
|
|
|
15
16
|
exports.createPlaywrightLauncher = createPlaywrightLauncher;
|
|
16
17
|
const chromium_resolver_1 = require("./chromium-resolver");
|
|
17
18
|
const browser_network_1 = require("./browser-network");
|
|
19
|
+
const browser_viewport_1 = require("./browser-viewport");
|
|
18
20
|
/**
|
|
19
21
|
* Centralized SSRF / scheme guard for EVERY navigation path (WS `navigate`, REST
|
|
20
22
|
* `navigate`, REST create `initialUrl`). Returns true only for an `http`/`https`
|
|
@@ -117,7 +119,7 @@ function screencastParams(env = process.env) {
|
|
|
117
119
|
if (Number.isFinite(n))
|
|
118
120
|
quality = Math.min(100, Math.max(1, Math.round(n)));
|
|
119
121
|
}
|
|
120
|
-
return { format: 'jpeg', quality, everyNthFrame: 1, maxWidth:
|
|
122
|
+
return { format: 'jpeg', quality, everyNthFrame: 1, maxWidth: browser_viewport_1.MAX_BROWSER_RASTER.width, maxHeight: browser_viewport_1.MAX_BROWSER_RASTER.height };
|
|
121
123
|
}
|
|
122
124
|
/**
|
|
123
125
|
* Normalize a user-supplied address into a safe, navigable URL. A bare host
|
|
@@ -144,6 +146,14 @@ class PlaywrightPageHandle {
|
|
|
144
146
|
// dependency on playwright's exported types leaking through the abstraction.
|
|
145
147
|
cdp = null;
|
|
146
148
|
screencastHandler = null;
|
|
149
|
+
screencastStart = null;
|
|
150
|
+
// Captures can render at display density independently of the interactive
|
|
151
|
+
// viewport. CDP emulation scale/DPR changes break screencast or wheel semantics.
|
|
152
|
+
captureCdp = null;
|
|
153
|
+
viewportOp = null;
|
|
154
|
+
pendingViewport = null;
|
|
155
|
+
requestedDeviceScaleFactor = 1;
|
|
156
|
+
captureDeviceScaleFactor = 1;
|
|
147
157
|
// A dedicated, long-lived CDP session for the Network domain — kept SEPARATE
|
|
148
158
|
// from `cdp` (which is attached/detached around screencast) so request capture
|
|
149
159
|
// survives screencast toggles.
|
|
@@ -207,11 +217,33 @@ class PlaywrightPageHandle {
|
|
|
207
217
|
return '';
|
|
208
218
|
}
|
|
209
219
|
}
|
|
210
|
-
async setViewport(width, height) {
|
|
220
|
+
async setViewport(width, height, deviceScaleFactor) {
|
|
221
|
+
if (deviceScaleFactor !== undefined)
|
|
222
|
+
this.requestedDeviceScaleFactor = deviceScaleFactor;
|
|
223
|
+
this.pendingViewport = (0, browser_viewport_1.normalizeBrowserViewport)(width, height, this.requestedDeviceScaleFactor);
|
|
224
|
+
if (this.viewportOp)
|
|
225
|
+
return this.viewportOp;
|
|
226
|
+
const operation = (async () => {
|
|
227
|
+
while (this.pendingViewport) {
|
|
228
|
+
const next = this.pendingViewport;
|
|
229
|
+
this.pendingViewport = null;
|
|
230
|
+
try {
|
|
231
|
+
await this.page.setViewportSize({ width: next.width, height: next.height });
|
|
232
|
+
this.captureDeviceScaleFactor = next.deviceScaleFactor;
|
|
233
|
+
}
|
|
234
|
+
catch {
|
|
235
|
+
this.captureDeviceScaleFactor = 1; // page closed/navigating; input stays best-effort
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
})();
|
|
239
|
+
this.viewportOp = operation;
|
|
211
240
|
try {
|
|
212
|
-
await
|
|
241
|
+
await operation;
|
|
242
|
+
}
|
|
243
|
+
finally {
|
|
244
|
+
if (this.viewportOp === operation)
|
|
245
|
+
this.viewportOp = null;
|
|
213
246
|
}
|
|
214
|
-
catch { /* ignore */ }
|
|
215
247
|
}
|
|
216
248
|
async dispatchInput(event) {
|
|
217
249
|
try {
|
|
@@ -257,7 +289,7 @@ class PlaywrightPageHandle {
|
|
|
257
289
|
}
|
|
258
290
|
}
|
|
259
291
|
else if (event.type === 'resize') {
|
|
260
|
-
await this.setViewport(event.width, event.height);
|
|
292
|
+
await this.setViewport(event.width, event.height, event.deviceScaleFactor);
|
|
261
293
|
}
|
|
262
294
|
}
|
|
263
295
|
catch {
|
|
@@ -265,44 +297,81 @@ class PlaywrightPageHandle {
|
|
|
265
297
|
}
|
|
266
298
|
}
|
|
267
299
|
async startScreencast(onFrame) {
|
|
300
|
+
if (this.screencastStart)
|
|
301
|
+
return this.screencastStart;
|
|
268
302
|
if (this.screencastHandler)
|
|
269
303
|
return;
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
this.cdp = await ctx.newCDPSession(this.page);
|
|
273
|
-
this.cdp.on('Page.screencastFrame', (evt) => {
|
|
274
|
-
// Ack FIRST, fire-and-forget: Chromium throttles the screencast on unacked
|
|
275
|
-
// frames, so acking before the fan-out lets it capture+compress the next
|
|
276
|
-
// frame while this one is being delivered (pipeline overlap → higher fps,
|
|
277
|
-
// lower frame age). The frames are independent JPEGs, so there is no
|
|
278
|
-
// ordering hazard in acking early.
|
|
304
|
+
const startup = (async () => {
|
|
305
|
+
let cdp = null;
|
|
279
306
|
try {
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
307
|
+
cdp = await this.page.context().newCDPSession(this.page);
|
|
308
|
+
this.cdp = cdp;
|
|
309
|
+
this.screencastHandler = onFrame;
|
|
310
|
+
cdp.on('Page.screencastFrame', (evt) => {
|
|
311
|
+
// Ack on the session that emitted the frame. A late callback from an
|
|
312
|
+
// old cast must never ack or render into its replacement.
|
|
313
|
+
try {
|
|
314
|
+
cdp.send('Page.screencastFrameAck', { sessionId: evt.sessionId })?.catch?.(() => { });
|
|
315
|
+
}
|
|
316
|
+
catch { /* ignore */ }
|
|
317
|
+
if (this.cdp !== cdp)
|
|
318
|
+
return;
|
|
319
|
+
try {
|
|
320
|
+
this.screencastHandler?.({
|
|
321
|
+
data: Buffer.from(evt.data, 'base64'),
|
|
322
|
+
width: evt.metadata?.deviceWidth ?? 0,
|
|
323
|
+
height: evt.metadata?.deviceHeight ?? 0,
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
catch { /* never let a handler error kill the CDP session */ }
|
|
288
327
|
});
|
|
328
|
+
await cdp.send('Page.startScreencast', screencastParams());
|
|
289
329
|
}
|
|
290
|
-
catch {
|
|
291
|
-
|
|
292
|
-
|
|
330
|
+
catch (err) {
|
|
331
|
+
this.screencastHandler = null;
|
|
332
|
+
if (this.cdp === cdp)
|
|
333
|
+
this.cdp = null;
|
|
334
|
+
if (cdp) {
|
|
335
|
+
try {
|
|
336
|
+
await cdp.send('Page.stopScreencast');
|
|
337
|
+
}
|
|
338
|
+
catch { /* ignore */ }
|
|
339
|
+
try {
|
|
340
|
+
await cdp.detach();
|
|
341
|
+
}
|
|
342
|
+
catch { /* ignore */ }
|
|
343
|
+
}
|
|
344
|
+
throw err;
|
|
345
|
+
}
|
|
346
|
+
})();
|
|
347
|
+
this.screencastStart = startup;
|
|
348
|
+
try {
|
|
349
|
+
await startup;
|
|
350
|
+
}
|
|
351
|
+
finally {
|
|
352
|
+
if (this.screencastStart === startup)
|
|
353
|
+
this.screencastStart = null;
|
|
354
|
+
}
|
|
293
355
|
}
|
|
294
356
|
async stopScreencast() {
|
|
357
|
+
// Closing while newCDPSession/start is awaiting must also close the eventual
|
|
358
|
+
// CDP handle. Otherwise unmount can leave an invisible capture running.
|
|
359
|
+
try {
|
|
360
|
+
await this.screencastStart;
|
|
361
|
+
}
|
|
362
|
+
catch { /* startup already cleaned up */ }
|
|
295
363
|
this.screencastHandler = null;
|
|
296
|
-
|
|
364
|
+
const cdp = this.cdp;
|
|
365
|
+
this.cdp = null;
|
|
366
|
+
if (cdp) {
|
|
297
367
|
try {
|
|
298
|
-
await
|
|
368
|
+
await cdp.send('Page.stopScreencast');
|
|
299
369
|
}
|
|
300
370
|
catch { /* ignore */ }
|
|
301
371
|
try {
|
|
302
|
-
await
|
|
372
|
+
await cdp.detach();
|
|
303
373
|
}
|
|
304
374
|
catch { /* ignore */ }
|
|
305
|
-
this.cdp = null;
|
|
306
375
|
}
|
|
307
376
|
}
|
|
308
377
|
async screenshotClip(rect) {
|
|
@@ -312,6 +381,29 @@ class PlaywrightPageHandle {
|
|
|
312
381
|
width: Math.max(1, Math.round(rect.width)),
|
|
313
382
|
height: Math.max(1, Math.round(rect.height)),
|
|
314
383
|
};
|
|
384
|
+
await this.viewportOp;
|
|
385
|
+
if (this.captureDeviceScaleFactor > 1) {
|
|
386
|
+
// Screenshot clip.scale re-rasterizes vector/text detail at display
|
|
387
|
+
// density without changing page DPR, input units, scroll, or live JPEGs.
|
|
388
|
+
// CSS layout metrics supply the document offset of the viewport crop.
|
|
389
|
+
this.captureCdp ??= await this.page.context().newCDPSession(this.page);
|
|
390
|
+
try {
|
|
391
|
+
const { cssVisualViewport } = await this.captureCdp.send('Page.getLayoutMetrics');
|
|
392
|
+
const result = await this.captureCdp.send('Page.captureScreenshot', {
|
|
393
|
+
format: 'png', captureBeyondViewport: false,
|
|
394
|
+
clip: { ...clip, x: clip.x + (cssVisualViewport?.pageX ?? 0), y: clip.y + (cssVisualViewport?.pageY ?? 0), scale: this.captureDeviceScaleFactor },
|
|
395
|
+
});
|
|
396
|
+
return Buffer.from(result.data, 'base64');
|
|
397
|
+
}
|
|
398
|
+
catch (error) {
|
|
399
|
+
try {
|
|
400
|
+
await this.captureCdp.detach();
|
|
401
|
+
}
|
|
402
|
+
catch { /* ignore */ }
|
|
403
|
+
this.captureCdp = null;
|
|
404
|
+
throw error;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
315
407
|
return this.page.screenshot({ clip, type: 'png' });
|
|
316
408
|
}
|
|
317
409
|
async extractDom(rect, htmlByteCap) {
|
|
@@ -496,17 +588,15 @@ class PlaywrightPageHandle {
|
|
|
496
588
|
// context — cookies and the window.opener/postMessage relationship are
|
|
497
589
|
// intact, which is exactly what an OAuth redirect/popup flow needs.
|
|
498
590
|
//
|
|
499
|
-
//
|
|
500
|
-
//
|
|
501
|
-
//
|
|
502
|
-
//
|
|
503
|
-
// opener chain resolves to this page and adopt those. `seen` dedupes the
|
|
504
|
-
// two doors; pages from other sessions have a different opener and are
|
|
505
|
-
// ignored.
|
|
591
|
+
// Also watch the context for pages whose direct opener is this page. A
|
|
592
|
+
// chained login can open its next window before its own handle is adopted,
|
|
593
|
+
// so replay existing pages after subscribing. Deduplication covers both
|
|
594
|
+
// events and the replay; another session's pages never share this opener.
|
|
506
595
|
try {
|
|
507
596
|
const seen = new WeakSet();
|
|
597
|
+
let active = !this.page.isClosed?.();
|
|
508
598
|
const adopt = (p) => {
|
|
509
|
-
if (seen.has(p))
|
|
599
|
+
if (!active || p === this.page || p.isClosed?.() || seen.has(p))
|
|
510
600
|
return;
|
|
511
601
|
seen.add(p);
|
|
512
602
|
try {
|
|
@@ -520,9 +610,11 @@ class PlaywrightPageHandle {
|
|
|
520
610
|
const onContextPage = (p) => {
|
|
521
611
|
void (async () => {
|
|
522
612
|
try {
|
|
523
|
-
if (seen.has(p) || p === this.page)
|
|
613
|
+
if (!active || seen.has(p) || p === this.page || p.isClosed?.())
|
|
524
614
|
return;
|
|
525
615
|
const opener = await p.opener?.();
|
|
616
|
+
// `opener()` is async; the opener or popup may have closed while
|
|
617
|
+
// it was pending. adopt checks that lifetime again.
|
|
526
618
|
if (opener === this.page)
|
|
527
619
|
adopt(p);
|
|
528
620
|
}
|
|
@@ -530,13 +622,20 @@ class PlaywrightPageHandle {
|
|
|
530
622
|
})();
|
|
531
623
|
};
|
|
532
624
|
ctx.on('page', onContextPage);
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
this
|
|
625
|
+
for (const p of ctx.pages?.() ?? [])
|
|
626
|
+
onContextPage(p);
|
|
627
|
+
// The shared context outlives this page. Invalidate pending lookups as
|
|
628
|
+
// well as detaching listeners, or a late result can revive a dead tab.
|
|
629
|
+
this.onClose(() => {
|
|
630
|
+
active = false;
|
|
536
631
|
try {
|
|
537
632
|
ctx.off?.('page', onContextPage);
|
|
538
633
|
}
|
|
539
634
|
catch { /* ignore */ }
|
|
635
|
+
try {
|
|
636
|
+
this.page.off?.('popup', adopt);
|
|
637
|
+
}
|
|
638
|
+
catch { /* ignore */ }
|
|
540
639
|
});
|
|
541
640
|
}
|
|
542
641
|
}
|
|
@@ -544,12 +643,21 @@ class PlaywrightPageHandle {
|
|
|
544
643
|
}
|
|
545
644
|
onClose(cb) {
|
|
546
645
|
try {
|
|
547
|
-
|
|
646
|
+
let delivered = false;
|
|
647
|
+
const notify = () => {
|
|
648
|
+
if (delivered)
|
|
649
|
+
return;
|
|
650
|
+
delivered = true;
|
|
548
651
|
try {
|
|
549
652
|
cb();
|
|
550
653
|
}
|
|
551
654
|
catch { /* ignore */ }
|
|
552
|
-
}
|
|
655
|
+
};
|
|
656
|
+
this.page.on('close', notify);
|
|
657
|
+
// An immediate OAuth callback can self-close before adoption finishes.
|
|
658
|
+
// Deliver that state too; waiting only for a future event strands it.
|
|
659
|
+
if (this.page.isClosed?.())
|
|
660
|
+
queueMicrotask(notify);
|
|
553
661
|
}
|
|
554
662
|
catch { /* ignore */ }
|
|
555
663
|
}
|
|
@@ -587,6 +695,14 @@ class PlaywrightPageHandle {
|
|
|
587
695
|
}
|
|
588
696
|
async close() {
|
|
589
697
|
await this.stopScreencast();
|
|
698
|
+
await this.viewportOp;
|
|
699
|
+
if (this.captureCdp) {
|
|
700
|
+
try {
|
|
701
|
+
await this.captureCdp.detach();
|
|
702
|
+
}
|
|
703
|
+
catch { /* ignore */ }
|
|
704
|
+
this.captureCdp = null;
|
|
705
|
+
}
|
|
590
706
|
if (this.netCdp) {
|
|
591
707
|
try {
|
|
592
708
|
await this.netCdp.send('Network.disable');
|
|
@@ -604,6 +720,7 @@ class PlaywrightPageHandle {
|
|
|
604
720
|
catch { /* ignore */ }
|
|
605
721
|
}
|
|
606
722
|
}
|
|
723
|
+
exports.PlaywrightPageHandle = PlaywrightPageHandle;
|
|
607
724
|
// Serialised into the page via page.evaluate. Self-contained: no closures over
|
|
608
725
|
// Node scope. Returns the html + nodes; the Node side stamps url/title/time.
|
|
609
726
|
function domExtractScript(arg) {
|
|
@@ -1100,8 +1217,12 @@ class PlaywrightContextHandle {
|
|
|
1100
1217
|
* sandbox frequently fails to initialise inside a packaged/headless app and would
|
|
1101
1218
|
* otherwise refuse to launch. We never disable the sandbox where it functions.
|
|
1102
1219
|
*/
|
|
1103
|
-
function chromiumLaunchArgs() {
|
|
1104
|
-
const args = ['--disable-dev-shm-usage'
|
|
1220
|
+
function chromiumLaunchArgs(env = process.env) {
|
|
1221
|
+
const args = ['--disable-dev-shm-usage'];
|
|
1222
|
+
// Native acceleration is the browser default. Retain an explicit escape hatch
|
|
1223
|
+
// for machines/drivers that need software rendering, without forcing it on Macs.
|
|
1224
|
+
if (env.SPECRAILS_BROWSER_DISABLE_GPU === 'true')
|
|
1225
|
+
args.push('--disable-gpu');
|
|
1105
1226
|
if (process.platform === 'linux')
|
|
1106
1227
|
args.unshift('--no-sandbox');
|
|
1107
1228
|
return args;
|
|
@@ -1123,6 +1244,8 @@ function createPlaywrightLauncher() {
|
|
|
1123
1244
|
const context = await chromium.launchPersistentContext(opts.userDataDir, {
|
|
1124
1245
|
headless: true,
|
|
1125
1246
|
executablePath,
|
|
1247
|
+
// Playwright defaults this option to false even without --no-sandbox.
|
|
1248
|
+
chromiumSandbox: process.platform !== 'linux',
|
|
1126
1249
|
viewport: opts.viewport,
|
|
1127
1250
|
args: chromiumLaunchArgs(),
|
|
1128
1251
|
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MAX_BROWSER_RASTER = void 0;
|
|
4
|
+
exports.normalizeBrowserDeviceScaleFactor = normalizeBrowserDeviceScaleFactor;
|
|
5
|
+
exports.normalizeBrowserViewport = normalizeBrowserViewport;
|
|
6
|
+
/** CSS layout stays independent from the bounded physical capture raster. */
|
|
7
|
+
exports.MAX_BROWSER_RASTER = { width: 3840, height: 2400 };
|
|
8
|
+
function normalizeBrowserDeviceScaleFactor(value) {
|
|
9
|
+
return Number.isFinite(value) ? Math.min(2, Math.max(1, value)) : 1;
|
|
10
|
+
}
|
|
11
|
+
function normalizeBrowserViewport(width, height, deviceScaleFactor = 1) {
|
|
12
|
+
const dimension = (value, fallback, max) => Number.isFinite(value)
|
|
13
|
+
? Math.min(max, Math.max(1, Math.round(value)))
|
|
14
|
+
: fallback;
|
|
15
|
+
const cssWidth = dimension(width, 1280, exports.MAX_BROWSER_RASTER.width);
|
|
16
|
+
const cssHeight = dimension(height, 800, exports.MAX_BROWSER_RASTER.height);
|
|
17
|
+
const requestedScale = normalizeBrowserDeviceScaleFactor(deviceScaleFactor);
|
|
18
|
+
const scale = Math.min(requestedScale, exports.MAX_BROWSER_RASTER.width / cssWidth, exports.MAX_BROWSER_RASTER.height / cssHeight);
|
|
19
|
+
return {
|
|
20
|
+
width: cssWidth,
|
|
21
|
+
height: cssHeight,
|
|
22
|
+
deviceScaleFactor: scale,
|
|
23
|
+
rasterWidth: Math.round(cssWidth * scale),
|
|
24
|
+
rasterHeight: Math.round(cssHeight * scale),
|
|
25
|
+
};
|
|
26
|
+
}
|