[backport cloud/1.51] feat(workspace): enable local credit workspace switching FE-1584 - #15557
Conversation
) ## Why Local/Desktop users cannot select the workspace that owns their subscription or credits. The existing workspace and billing clients also resolve through `api.apiURL()`, which points at the local ComfyUI server off Cloud, so simply exposing the Cloud switcher leaves workspace hydration dormant and returns 404s. ## Root cause The Cloud-only UI guard hid the switcher, but the deeper boundary was also Cloud-only: - workspace token exchange, workspace listing, and `/billing/*` used same-origin URLs - generic auth helpers mixed Firebase user identity with workspace-scoped identity - Cloud workspace switching reloads the app and changes workflow-persistence namespaces, which is incorrect for Local/Desktop where workspaces are credit wallets only ## Change - Expose the existing workspace switcher in Local/Desktop after Firebase auth hydrates; keep workspace creation Cloud-only. - Route workspace auth, listing, and billing requests to the build's Firebase-paired Cloud gateway (`cloud.comfy.org` in production, `testcloud.comfy.org` in non-production) while Cloud remains same-origin. - Separate Firebase-authenticated `/customers/*` calls from workspace-authenticated billing and partner-node execution. - Switch the active local credit context without reloading or moving local workflow persistence; persist the selected wallet per device. - Keep Add credits permission-aware and return completed/pending top-ups to Settings → Credits on Local/Desktop. ### Implementation scope and rationale | Area | Change | Why it is required | | --- | --- | --- | | Workspace API routing | Resolve workspace and billing requests through the Firebase-paired Cloud gateway outside the Cloud distribution. | Local `/api` targets the local ComfyUI server, while workspace and billing state is owned by Cloud ingest. | | Authentication boundary | Separate Firebase user authentication from workspace-scoped authentication, including partner-node queue execution. | A Firebase token identifies the user; the workspace token deterministically identifies the wallet that owns credits and receives usage charges. | | Workspace bootstrap | Share and await the in-flight Local workspace initialization, and fail closed when no active workspace can be established. | Prompt submission during bootstrap must not run without credentials or silently fall back to the personal workspace. | | Local switch lifecycle | Switch the active credit workspace without reloading the document. | Cloud reload semantics are unnecessary for Local and would interrupt the current local workflow. | | Workflow persistence | Keep Local workflow storage in the personal namespace when the credit workspace changes. | Local workspace selection changes the billing wallet only; it must not move, clear, or split locally stored workflows. | | Billing routing | Use workspace billing after Local workspace hydration. | Balances, plans, billing events, and top-ups must follow the selected credit workspace rather than the personal account. | | Billing permissions | Keep Credits visible while hiding Add credits for members who cannot top up. | Backend top-up operations are owner-only, so the UI must not offer an action the selected workspace role cannot perform. | | Top-up completion | Return completed or pending Local top-ups to Settings → Credits. | Local does not expose the Cloud workspace-management destination used by the existing Cloud flow. | | Cloud customer endpoints | Explicitly retain Firebase authentication for user-scoped customer and subscription calls. | Changing generic auth precedence must not send a workspace token to endpoints whose contract is user-scoped. | | Feature-flag isolation | Disable `unified_cloud_auth` outside the Cloud distribution. | Unified Cloud JWT minting is not available in Local/Desktop; accepting that flag there would break workspace discovery and execution authentication. | | Regression coverage | Add focused unit tests and a Local Playwright workspace-switching flow. | The tests protect workspace attribution, Cloud-gateway routing, no-reload switching, workflow retention, permissions, and bootstrap race handling. | Backend dependency: [cloud#6635](Comfy-Org/cloud#6635) is required and merged. Its Local/Desktop CORS scope covers `POST /api/auth/token`, `GET /api/workspaces`, and `GET`/`POST /api/billing/*`; Local keeps workspace creation hidden. The backend PR is merged into `main`, and its production manifest contains the loopback origins. A live preflight currently passes on `testcloud.comfy.org`, while `cloud.comfy.org` still omits `Access-Control-Allow-Origin` for loopback origins. No additional backend code change is currently identified; the remaining release check is to confirm the merged configuration has rolled out to the production runtime and repeat the preflight. [#15151](#15151) independently fixes the existing Settings → Credits activity spinner and billing-portal rail. ## Plan / QA 1. Review the auth and persistence boundaries in this PR. 2. Land #15151 so activity and Invoice History follow the selected billing rail. 3. Confirm the merged cloud#6635 configuration has rolled out to the production runtime and repeat the live preflight. 4. Verify on a Desktop build against the deployed gateway: switch/relaunch, popover and Settings balances, top-up attribution, partner-node debit and allowlist, member permissions, and zero-credit local runs. Regression audit added a focused Local Playwright flow. It caught and fixed two release blockers: Local rendered the legacy profile popover even after workspace hydration, and `unified_cloud_auth=true` incorrectly selected an unavailable Cloud JWT outside the Cloud distribution. The E2E verifies workspace/role rendering, workspace token exchange, Cloud-gateway billing routing, and that switching neither reloads the document nor loses the open workflow tab. A second auth/accounting audit found two queue-time races: a prompt could be submitted without a workspace token while Local workspace bootstrap was in flight, or fall back to the personal Firebase identity after bootstrap failed. Queue authentication now shares and awaits the in-flight workspace initialization and fails closed if no active workspace can be established. Identity-generation guards prevent a previous user's initialization from overwriting the next user's state. Design of record: [MVP before/after](https://www.figma.com/design/CkFTD4c20PyRGpNVAJgpfV/Team-Plan---Workspaces?node-id=5874-73181) · [popover and switcher](https://www.figma.com/design/CkFTD4c20PyRGpNVAJgpfV/Team-Plan---Workspaces?node-id=5874-72771) The implementation references those prototype states: the profile popover gains the workspace row, the selector opens to the left, and the selected workspace is highlighted with a check while roles remain visible. Local-specific adaptations are the compact single-line trigger and the retained Credits entry, because this scope adds wallet switching without exposing Cloud workspace management. | AS IS — Local profile menu | TO BE — Local workspace selector | | --- | --- | |  |  | Captured from the actual Local frontend at 1920×1200 with deterministic auth/workspace/billing API fixtures; no Figma image is substituted for the implementation capture. ## Validation - Focused Local workspace Playwright E2E: 1/1 passed on Chromium before the final auth-race patch; final local rerun was blocked during fixture setup because the 8188 ComfyUI test backend was unavailable, before any product assertion ran - Final focused workspace/auth/queue unit suites: 316/316 passed when run in their stable focused groups - Earlier affected unit/component suites: 650/650 passed before rebase; 594/594 passed after rebase - `pnpm typecheck` - targeted ESLint and oxfmt checks - `pnpm knip` reports only existing repository-wide unused dependency/export findings - Real-app screenshot Playwright run: 2/2 passed (AS IS without a workspace roster; TO BE with the selector open) - Live CORS preflight: staging passes; production runtime rollout/sync verification remains the release blocker Linear: [FE-1584](https://linear.app/comfyorg/issue/FE-1584/add-workspace-switcher-to-localdesktop-credits-only-no-settings) --------- Co-authored-by: Amp <amp@ampcode.com> Co-authored-by: comfydesigner <alextov@comfy.org>
|
Important Review skippedIgnore keyword(s) in the title. ⛔ Ignored keywords (2)
Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
🎭 Playwright: ✅ 1829 passed, 0 failed📊 Browser Reports
🎨 Storybook: ✅ Built — View Storybook📦 Bundle Size
⚡ Performance Report
Absolute values
Raw data{
"timestamp": "2026-08-21T13:48:32.296Z",
"gitSha": "37bf58d7f3698456995586987adc274e69be99b2",
"branch": "dante01yoon/backport-15164-to-cloud-1.51",
"measurements": [
{
"name": "canvas-idle",
"durationMs": 2008.4909999999923,
"styleRecalcs": 9,
"styleRecalcDurationMs": 8.242999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 517.217,
"heapDeltaBytes": 4994352,
"heapUsedBytes": 70223656,
"domNodes": 18,
"jsHeapTotalBytes": 24903680,
"scriptDurationMs": 7.387000000000001,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "canvas-idle",
"durationMs": 2015.4589999999644,
"styleRecalcs": 10,
"styleRecalcDurationMs": 9.449,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 470.63500000000005,
"heapDeltaBytes": 4954352,
"heapUsedBytes": 70322168,
"domNodes": 20,
"jsHeapTotalBytes": 24641536,
"scriptDurationMs": 6.357999999999999,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "canvas-mouse-sweep",
"durationMs": 1878.957000000014,
"styleRecalcs": 77,
"styleRecalcDurationMs": 37.049,
"layouts": 12,
"layoutDurationMs": 3.1650000000000005,
"taskDurationMs": 882.1239999999999,
"heapDeltaBytes": -18618116,
"heapUsedBytes": 46831652,
"domNodes": 0,
"jsHeapTotalBytes": 23171072,
"scriptDurationMs": 114.11200000000001,
"eventListeners": -153,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-mouse-sweep",
"durationMs": 1873.2129999999643,
"styleRecalcs": 75,
"styleRecalcDurationMs": 35.516000000000005,
"layouts": 12,
"layoutDurationMs": 3.0939999999999994,
"taskDurationMs": 884.6429999999999,
"heapDeltaBytes": -18659992,
"heapUsedBytes": 46805332,
"domNodes": 0,
"jsHeapTotalBytes": 23171072,
"scriptDurationMs": 111.486,
"eventListeners": -153,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1746.1759999999913,
"styleRecalcs": 30,
"styleRecalcDurationMs": 18.401,
"layouts": 6,
"layoutDurationMs": 0.6899999999999998,
"taskDurationMs": 370.95900000000006,
"heapDeltaBytes": 7988476,
"heapUsedBytes": 73426216,
"domNodes": 77,
"jsHeapTotalBytes": 24379392,
"scriptDurationMs": 9.458000000000002,
"eventListeners": 19,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1736.8579999999838,
"styleRecalcs": 32,
"styleRecalcDurationMs": 18.11,
"layouts": 6,
"layoutDurationMs": 0.607,
"taskDurationMs": 378.18,
"heapDeltaBytes": 7918316,
"heapUsedBytes": 73153288,
"domNodes": 78,
"jsHeapTotalBytes": 24641536,
"scriptDurationMs": 9.181000000000001,
"eventListeners": 19,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "dom-widget-clipping",
"durationMs": 577.0400000000109,
"styleRecalcs": 10,
"styleRecalcDurationMs": 7.667999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 379.70899999999995,
"heapDeltaBytes": -11334776,
"heapUsedBytes": 54074728,
"domNodes": 16,
"jsHeapTotalBytes": 25165824,
"scriptDurationMs": 61.898,
"eventListeners": 0,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "dom-widget-clipping",
"durationMs": 576.5380000000278,
"styleRecalcs": 11,
"styleRecalcDurationMs": 7.377999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 376.049,
"heapDeltaBytes": -10962044,
"heapUsedBytes": 54364216,
"domNodes": 18,
"jsHeapTotalBytes": 24379392,
"scriptDurationMs": 57.364000000000004,
"eventListeners": 0,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "large-graph-idle",
"durationMs": 2027.6119999999764,
"styleRecalcs": 9,
"styleRecalcDurationMs": 8.005999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 590.446,
"heapDeltaBytes": 12919908,
"heapUsedBytes": 73177800,
"domNodes": -282,
"jsHeapTotalBytes": 3768320,
"scriptDurationMs": 16.052999999999997,
"eventListeners": -149,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-idle",
"durationMs": 2037.2280000000274,
"styleRecalcs": 9,
"styleRecalcDurationMs": 8.166,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 613.2850000000001,
"heapDeltaBytes": 12778468,
"heapUsedBytes": 72911940,
"domNodes": -282,
"jsHeapTotalBytes": 2981888,
"scriptDurationMs": 13.846,
"eventListeners": -149,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "large-graph-pan",
"durationMs": 2132.1569999999497,
"styleRecalcs": 68,
"styleRecalcDurationMs": 14.543000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 1175.873,
"heapDeltaBytes": 7465672,
"heapUsedBytes": 68484488,
"domNodes": -283,
"jsHeapTotalBytes": 3436544,
"scriptDurationMs": 327.895,
"eventListeners": -147,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-pan",
"durationMs": 2212.24200000006,
"styleRecalcs": 69,
"styleRecalcDurationMs": 14.882000000000003,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 1238.161,
"heapDeltaBytes": 8253696,
"heapUsedBytes": 69068772,
"domNodes": -283,
"jsHeapTotalBytes": 3960832,
"scriptDurationMs": 340.318,
"eventListeners": -151,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.670000000000012,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "large-graph-zoom",
"durationMs": 3163.5120000000256,
"styleRecalcs": 66,
"styleRecalcDurationMs": 15.601,
"layouts": 60,
"layoutDurationMs": 7.204000000000001,
"taskDurationMs": 1340.7,
"heapDeltaBytes": 17702092,
"heapUsedBytes": 79752696,
"domNodes": 14,
"jsHeapTotalBytes": 5505024,
"scriptDurationMs": 390.80099999999993,
"eventListeners": 8,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "large-graph-zoom",
"durationMs": 3232.3900000000094,
"styleRecalcs": 65,
"styleRecalcDurationMs": 14.050000000000004,
"layouts": 60,
"layoutDurationMs": 7.109000000000001,
"taskDurationMs": 1387.5549999999998,
"heapDeltaBytes": -1078652,
"heapUsedBytes": 60979604,
"domNodes": -284,
"jsHeapTotalBytes": 3506176,
"scriptDurationMs": 375.912,
"eventListeners": -153,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "minimap-idle",
"durationMs": 2041.8070000000057,
"styleRecalcs": 7,
"styleRecalcDurationMs": 6.551999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 596.4779999999998,
"heapDeltaBytes": 11813216,
"heapUsedBytes": 73277296,
"domNodes": -283,
"jsHeapTotalBytes": 2981888,
"scriptDurationMs": 15.537000000000003,
"eventListeners": -149,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "minimap-idle",
"durationMs": 2011.7779999999357,
"styleRecalcs": 8,
"styleRecalcDurationMs": 7.353999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 608.3259999999999,
"heapDeltaBytes": 8070192,
"heapUsedBytes": 69364356,
"domNodes": -283,
"jsHeapTotalBytes": 3506176,
"scriptDurationMs": 15.893999999999998,
"eventListeners": -181,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 589.0640000000076,
"styleRecalcs": 46,
"styleRecalcDurationMs": 10.517999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 409.74100000000004,
"heapDeltaBytes": -10081120,
"heapUsedBytes": 55615196,
"domNodes": 18,
"jsHeapTotalBytes": 24641536,
"scriptDurationMs": 120.497,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 583.6140000000114,
"styleRecalcs": 47,
"styleRecalcDurationMs": 10.776000000000002,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 407.79600000000005,
"heapDeltaBytes": -10754248,
"heapUsedBytes": 54762116,
"domNodes": 20,
"jsHeapTotalBytes": 25952256,
"scriptDurationMs": 117.338,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "subgraph-idle",
"durationMs": 1990.2839999999742,
"styleRecalcs": 10,
"styleRecalcDurationMs": 9.968,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 449.49899999999997,
"heapDeltaBytes": 5373488,
"heapUsedBytes": 70994304,
"domNodes": 20,
"jsHeapTotalBytes": 23855104,
"scriptDurationMs": 6.395,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66999999999998,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-idle",
"durationMs": 2005.3869999999279,
"styleRecalcs": 11,
"styleRecalcDurationMs": 10.087,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 456.99,
"heapDeltaBytes": 5358816,
"heapUsedBytes": 70944028,
"domNodes": 22,
"jsHeapTotalBytes": 24117248,
"scriptDurationMs": 6.0820000000000025,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1701.3279999999895,
"styleRecalcs": 76,
"styleRecalcDurationMs": 36.455,
"layouts": 16,
"layoutDurationMs": 3.975,
"taskDurationMs": 745.3789999999999,
"heapDeltaBytes": -3625796,
"heapUsedBytes": 61813460,
"domNodes": 63,
"jsHeapTotalBytes": 24641536,
"scriptDurationMs": 84.33399999999999,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1691.500000000019,
"styleRecalcs": 76,
"styleRecalcDurationMs": 36.979,
"layouts": 16,
"layoutDurationMs": 4.36,
"taskDurationMs": 784.8230000000001,
"heapDeltaBytes": -3708084,
"heapUsedBytes": 61832744,
"domNodes": 62,
"jsHeapTotalBytes": 24903680,
"scriptDurationMs": 85.536,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "subgraph-transition-enter",
"durationMs": 1400.1729999999952,
"styleRecalcs": 20,
"styleRecalcDurationMs": 31.471,
"layouts": 15,
"layoutDurationMs": 13.089999999999998,
"taskDurationMs": 888.206,
"heapDeltaBytes": -3977128,
"heapUsedBytes": 80448056,
"domNodes": 13673,
"jsHeapTotalBytes": 9961472,
"scriptDurationMs": 16.679000000000006,
"eventListeners": 2375,
"totalBlockingTimeMs": 143,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "viewport-pan-sweep",
"durationMs": 8421.581000000004,
"styleRecalcs": 248,
"styleRecalcDurationMs": 34.49,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 4159.349,
"heapDeltaBytes": 13264968,
"heapUsedBytes": 73957472,
"domNodes": -285,
"jsHeapTotalBytes": 3174400,
"scriptDurationMs": 1018.7610000000001,
"eventListeners": -131,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "viewport-pan-sweep",
"durationMs": 8352.54400000008,
"styleRecalcs": 248,
"styleRecalcDurationMs": 36.04,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 4284.568,
"heapDeltaBytes": 6777096,
"heapUsedBytes": 67084588,
"domNodes": -282,
"jsHeapTotalBytes": 4222976,
"scriptDurationMs": 1045.318,
"eventListeners": -135,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "vue-large-graph-idle",
"durationMs": 17459.518000000004,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 16678.708,
"heapDeltaBytes": -25985520,
"heapUsedBytes": 171370760,
"domNodes": -8312,
"jsHeapTotalBytes": -6758400,
"scriptDurationMs": 124.241,
"eventListeners": -16391,
"totalBlockingTimeMs": 0,
"frameDurationMs": 17.776666666666642,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-idle",
"durationMs": 18570.369000000028,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 17574.451,
"heapDeltaBytes": -29691344,
"heapUsedBytes": 167589116,
"domNodes": -8312,
"jsHeapTotalBytes": -13049856,
"scriptDurationMs": 121.61400000000002,
"eventListeners": -16385,
"totalBlockingTimeMs": 0,
"frameDurationMs": 17.776666666666642,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-pan",
"durationMs": 20986.399000000005,
"styleRecalcs": 172,
"styleRecalcDurationMs": 17.94300000000004,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 20432.266,
"heapDeltaBytes": -27626240,
"heapUsedBytes": 185288028,
"domNodes": -8312,
"jsHeapTotalBytes": -14426112,
"scriptDurationMs": 410.971,
"eventListeners": -16383,
"totalBlockingTimeMs": 35,
"frameDurationMs": 17.776666666666642,
"p95FrameDurationMs": 16.80000000000291
},
{
"name": "vue-large-graph-pan",
"durationMs": 22707.875999999942,
"styleRecalcs": 182,
"styleRecalcDurationMs": 19.11999999999997,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 21919.752000000004,
"heapDeltaBytes": -14357156,
"heapUsedBytes": 183056232,
"domNodes": -8312,
"jsHeapTotalBytes": -13119488,
"scriptDurationMs": 411.32099999999997,
"eventListeners": -16383,
"totalBlockingTimeMs": 228,
"frameDurationMs": 17.776666666666642,
"p95FrameDurationMs": 16.80000000000291
},
{
"name": "workflow-execution",
"durationMs": 157.8810000000317,
"styleRecalcs": 9,
"styleRecalcDurationMs": 23.153,
"layouts": 3,
"layoutDurationMs": 3.6169999999999995,
"taskDurationMs": 110.78399999999999,
"heapDeltaBytes": 3180932,
"heapUsedBytes": 67682812,
"domNodes": 139,
"jsHeapTotalBytes": 2883584,
"scriptDurationMs": 10.105,
"eventListeners": 49,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "workflow-execution",
"durationMs": 114.94500000003427,
"styleRecalcs": 7,
"styleRecalcDurationMs": 15.315999999999999,
"layouts": 3,
"layoutDurationMs": 1.1239999999999999,
"taskDurationMs": 74.25099999999999,
"heapDeltaBytes": 2941748,
"heapUsedBytes": 67405384,
"domNodes": 128,
"jsHeapTotalBytes": 2883584,
"scriptDurationMs": 4.559999999999998,
"eventListeners": 25,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
}
]
} |
Codecov Report❌ Patch coverage is @@ Coverage Diff @@
## cloud/1.51 #15557 +/- ##
=============================================
Coverage ? 81.53%
=============================================
Files ? 1884
Lines ? 117713
Branches ? 37248
=============================================
Hits ? 95973
Misses ? 21270
Partials ? 470
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
Summary
Manual backport of #15164 to
cloud/1.51, preserving the original local/Desktop credit-workspace switching behavior and regression coverage.Changes
848cd39ed06a2da6fea389305cf7210a0a18c5eeonto the currentcloud/1.51tip.d4910c5a0c7422e75a8e878248822032be6ab222).Verification
pnpm test:unit <24 changed workspace switcher/auth/billing/persistence test files>knip --cachepassed.Review Focus
Confirm local credit-workspace switching, workspace-auth routing, billing attribution, and local workflow persistence remain equivalent to #15164 on the 1.51 release line.