Skip to content

refactor(billing): retire consolidated billing rollout flag - #14615

Merged
dante01yoon merged 1 commit into
dante/canonical-billing-statusfrom
dante/retire-consolidated-billing-flag
Aug 10, 2026
Merged

refactor(billing): retire consolidated billing rollout flag#14615
dante01yoon merged 1 commit into
dante/canonical-billing-statusfrom
dante/retire-consolidated-billing-flag

Conversation

@dante01yoon

@dante01yoon dante01yoon commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

Part 4/4 of the workspace and billing rollout retirement stack.

Predecessor: #14614
Successor: none (final stack PR)

Summary

Retires consolidated_billing_enabled after canonical billing discovery and explicit rail preservation landed in #14614. Loaded Cloud workspaces now use unified pricing, while personal workspaces on legacy_stripe retain legacy account operations.

Root cause

The final billing route still depended on a compatibility feature key even after the workspace billing contract and canonical rail status were available. Removing the backend key first would therefore change personal-workspace routing during bootstrap or strand legacy Stripe balance/top-up/management behavior. The frontend needs a deterministic route based on distribution, workspace readiness/type, and the canonical billing rail before backend compatibility keys disappear.

Changes

  • Removes the consolidated-billing key from remote-config types, enum/getter, authenticated cache/refresh, local override path, Storybook mocks, and browser payloads.
  • Routes OSS and unloaded Cloud workspaces through legacy bootstrap.
  • Routes loaded Cloud team workspaces and personal unknown/stripe rails through workspace billing.
  • Preserves legacy account operations for personal legacy_stripe while keeping unified pricing for every loaded Cloud workspace.
  • Keeps the legacy adapter, balance, top-up, management behavior, and meaningful regression coverage.
  • Re-runs the canonical-status rail regression with both retired feature keys absent from /api/features.

AS IS

A loaded Cloud personal workspace still consults consolidated_billing_enabled to decide its billing adapter and pricing route.

No intended visual change. This is rollout cleanup, so no screenshot is required.

TO BE

Loaded Cloud workspaces use unified pricing without a rollout key. Canonical billing_rail determines whether personal account operations remain on legacy_stripe; team and migrated/unknown personal workspaces use workspace billing.

No intended visual change. The existing legacy rail UI remains available where the canonical status selects it.

Regression coverage

  • OSS and unloaded-workspace legacy bootstrap.
  • Cloud team workspace billing.
  • Cloud personal unknown/Stripe workspace billing.
  • Cloud personal legacy_stripe legacy account operations plus unified pricing.
  • Billing-context adapter switching and preserved legacy adapter behavior.
  • Playwright avatar-popover rail selection with /api/features omitting both retired keys, canonical status discovery, legacy balance/credits, and no stale workspace zero balance.
  • Repository search confirms no consolidated-flag symbol/string remains in src or browser_tests.

Validation

  • pnpm test:unit src/composables/useFeatureFlags.test.ts src/composables/billing/useBillingRouting.test.ts src/composables/billing/useBillingContext.test.ts src/platform/remoteConfig/refreshRemoteConfig.test.ts — 83 passed.
  • pnpm typecheck — passed.
  • pnpm typecheck:browser — passed.
  • Targeted oxfmt/oxlint/ESLint commit checks — passed.
  • pnpm knip --cache pre-push check — passed.
  • PLAYWRIGHT_LOCAL=1 PLAYWRIGHT_TEST_URL=http://localhost:5174 pnpm test:browser browser_tests/tests/billingFacadeConsumers.spec.ts -g 'avatar popover' — 1 passed.
  • git diff --check dante/canonical-billing-status...HEAD — passed.

Review focus

Please review only this PR's removal of the consolidated flag and the final routing matrix. Canonical endpoint migration and pending-checkout stabilization belong to #14614; workspace-flag retirement belongs to #14613.

Stack/deployment order

  1. #14612 — workspace initialization recovery
  2. #14613 — retire team workspace rollout flag
  3. #14614 — canonical billing status and legacy rail preservation
  4. #14615 — this PR; retire consolidated billing rollout flag

Deploy the frontend stack in this order before the backend removes the /api/features compatibility keys. Each branch remains independently deployable; do not deploy a later PR without its predecessor. This is also a drain requirement: age out deployed frontend versions and open tabs that still read either retired key before removing backend compatibility keys. Confirm consolidated_billing_enabled is at 100% and update Comfy-Org/cloud#6040 to depend on this selected stack rather than competing PR #14530 before merge. After backend key removal, reverting this PR is not a behavioral restore because a reinstated read of the missing key resolves to the off branch.

@dosubot dosubot Bot added the size:M This PR changes 30-99 lines, ignoring generated files. label Aug 3, 2026
@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 0634e923-f406-467a-9dce-7d304da4de8c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@dosubot

dosubot Bot commented Aug 3, 2026

Copy link
Copy Markdown

📄 Knowledge review

Dosu skipped reviewing this PR because your organization has used its 200 included credits for the month. Your usage will reset on 2026-09-01. To have Dosu review this PR before then, ask your organization admin to upgrade to a pro account.


Leave Feedback Ask Dosu about ComfyUI_frontend Add Dosu to your team

@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown

🎭 Playwright: ✅ 1782 passed, 0 failed · 6 flaky

📊 Browser Reports
  • chromium: View Report (✅ 1763 / ❌ 0 / ⚠️ 5 / ⏭️ 5)
  • chromium-2x: View Report (✅ 2 / ❌ 0 / ⚠️ 0 / ⏭️ 0)
  • chromium-0.5x: View Report (✅ 1 / ❌ 0 / ⚠️ 0 / ⏭️ 0)
  • mobile-chrome: View Report (✅ 16 / ❌ 0 / ⚠️ 1 / ⏭️ 0)

🎨 Storybook: ✅ Built — View Storybook

Details

⏰ Completed at: 08/08/2026, 11:59:35 PM UTC

Links

📦 Bundle Size

⏳ Size data collection in progress…

⚡ Performance Report

canvas-idle: · 60.0 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 0ms TBT · 66.8 MB heap
canvas-mouse-sweep: · 60.0 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 0ms TBT · 55.6 MB heap
canvas-zoom-sweep: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 69.5 MB heap
dom-widget-clipping: · 60.0 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 0ms TBT · 50.7 MB heap
large-graph-idle: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 62.6 MB heap
large-graph-pan: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 56.7 MB heap
large-graph-zoom: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 53.5 MB heap
minimap-idle: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 64.8 MB heap
subgraph-dom-widget-clipping: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 51.2 MB heap
subgraph-idle: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 56.2 MB heap
subgraph-mouse-sweep: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 58.2 MB heap
subgraph-transition-enter: · 60.0 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 114ms TBT · 95.7 MB heap
viewport-pan-sweep: · 60.0 avg FPS · 59.9 P5 FPS ✅ (target: ≥52) · 0ms TBT · 65.0 MB heap
vue-large-graph-idle: · 56.2 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 0ms TBT · 153.2 MB heap
vue-large-graph-pan: · 56.3 avg FPS · 59.5 P5 FPS ✅ (target: ≥52) · 24ms TBT · 158.8 MB heap
workflow-execution: · 60.0 avg FPS · 59.7 P5 FPS ✅ (target: ≥52) · 0ms TBT · 65.5 MB heap

ℹ️ No baseline found — significance unavailable.

Absolute values
Metric Value
canvas-idle: avg frame time 17ms
canvas-idle: p95 frame time 17ms
canvas-idle: layout duration 0ms
canvas-idle: style recalc duration 6ms
canvas-idle: layout count 0
canvas-idle: style recalc count 10
canvas-idle: task duration 547ms
canvas-idle: script duration 20ms
canvas-idle: TBT 0ms
canvas-idle: heap used 66.8 MB
canvas-idle: DOM nodes 19
canvas-idle: event listeners 5
canvas-mouse-sweep: avg frame time 17ms
canvas-mouse-sweep: p95 frame time 17ms
canvas-mouse-sweep: layout duration 3ms
canvas-mouse-sweep: style recalc duration 33ms
canvas-mouse-sweep: layout count 12
canvas-mouse-sweep: style recalc count 76
canvas-mouse-sweep: task duration 833ms
canvas-mouse-sweep: script duration 105ms
canvas-mouse-sweep: TBT 0ms
canvas-mouse-sweep: heap used 55.6 MB
canvas-mouse-sweep: DOM nodes -112
canvas-mouse-sweep: event listeners -75
canvas-zoom-sweep: avg frame time 17ms
canvas-zoom-sweep: p95 frame time 17ms
canvas-zoom-sweep: layout duration 1ms
canvas-zoom-sweep: style recalc duration 14ms
canvas-zoom-sweep: layout count 6
canvas-zoom-sweep: style recalc count 32
canvas-zoom-sweep: task duration 363ms
canvas-zoom-sweep: script duration 20ms
canvas-zoom-sweep: TBT 0ms
canvas-zoom-sweep: heap used 69.5 MB
canvas-zoom-sweep: DOM nodes 80
canvas-zoom-sweep: event listeners 20
dom-widget-clipping: avg frame time 17ms
dom-widget-clipping: p95 frame time 17ms
dom-widget-clipping: layout duration 0ms
dom-widget-clipping: style recalc duration 10ms
dom-widget-clipping: layout count 0
dom-widget-clipping: style recalc count 13
dom-widget-clipping: task duration 361ms
dom-widget-clipping: script duration 51ms
dom-widget-clipping: TBT 0ms
dom-widget-clipping: heap used 50.7 MB
dom-widget-clipping: DOM nodes 22
dom-widget-clipping: event listeners 0
large-graph-idle: avg frame time 17ms
large-graph-idle: p95 frame time 17ms
large-graph-idle: layout duration 0ms
large-graph-idle: style recalc duration 7ms
large-graph-idle: layout count 0
large-graph-idle: style recalc count 10
large-graph-idle: task duration 650ms
large-graph-idle: script duration 94ms
large-graph-idle: TBT 0ms
large-graph-idle: heap used 62.6 MB
large-graph-idle: DOM nodes -281
large-graph-idle: event listeners -164
large-graph-pan: avg frame time 17ms
large-graph-pan: p95 frame time 17ms
large-graph-pan: layout duration 0ms
large-graph-pan: style recalc duration 13ms
large-graph-pan: layout count 0
large-graph-pan: style recalc count 69
large-graph-pan: task duration 1218ms
large-graph-pan: script duration 399ms
large-graph-pan: TBT 0ms
large-graph-pan: heap used 56.7 MB
large-graph-pan: DOM nodes -283
large-graph-pan: event listeners -163
large-graph-zoom: avg frame time 17ms
large-graph-zoom: p95 frame time 17ms
large-graph-zoom: layout duration 7ms
large-graph-zoom: style recalc duration 14ms
large-graph-zoom: layout count 60
large-graph-zoom: style recalc count 65
large-graph-zoom: task duration 1411ms
large-graph-zoom: script duration 467ms
large-graph-zoom: TBT 0ms
large-graph-zoom: heap used 53.5 MB
large-graph-zoom: DOM nodes -288
large-graph-zoom: event listeners -168
minimap-idle: avg frame time 17ms
minimap-idle: p95 frame time 17ms
minimap-idle: layout duration 0ms
minimap-idle: style recalc duration 7ms
minimap-idle: layout count 0
minimap-idle: style recalc count 9
minimap-idle: task duration 643ms
minimap-idle: script duration 92ms
minimap-idle: TBT 0ms
minimap-idle: heap used 64.8 MB
minimap-idle: DOM nodes -283
minimap-idle: event listeners -164
subgraph-dom-widget-clipping: avg frame time 17ms
subgraph-dom-widget-clipping: p95 frame time 17ms
subgraph-dom-widget-clipping: layout duration 0ms
subgraph-dom-widget-clipping: style recalc duration 10ms
subgraph-dom-widget-clipping: layout count 0
subgraph-dom-widget-clipping: style recalc count 48
subgraph-dom-widget-clipping: task duration 364ms
subgraph-dom-widget-clipping: script duration 115ms
subgraph-dom-widget-clipping: TBT 0ms
subgraph-dom-widget-clipping: heap used 51.2 MB
subgraph-dom-widget-clipping: DOM nodes 21
subgraph-dom-widget-clipping: event listeners 7
subgraph-idle: avg frame time 17ms
subgraph-idle: p95 frame time 17ms
subgraph-idle: layout duration 0ms
subgraph-idle: style recalc duration 6ms
subgraph-idle: layout count 0
subgraph-idle: style recalc count 10
subgraph-idle: task duration 460ms
subgraph-idle: script duration 17ms
subgraph-idle: TBT 0ms
subgraph-idle: heap used 56.2 MB
subgraph-idle: DOM nodes -130
subgraph-idle: event listeners -73
subgraph-mouse-sweep: avg frame time 17ms
subgraph-mouse-sweep: p95 frame time 17ms
subgraph-mouse-sweep: layout duration 4ms
subgraph-mouse-sweep: style recalc duration 32ms
subgraph-mouse-sweep: layout count 16
subgraph-mouse-sweep: style recalc count 76
subgraph-mouse-sweep: task duration 726ms
subgraph-mouse-sweep: script duration 76ms
subgraph-mouse-sweep: TBT 0ms
subgraph-mouse-sweep: heap used 58.2 MB
subgraph-mouse-sweep: DOM nodes 63
subgraph-mouse-sweep: event listeners 4
subgraph-transition-enter: avg frame time 17ms
subgraph-transition-enter: p95 frame time 17ms
subgraph-transition-enter: layout duration 11ms
subgraph-transition-enter: style recalc duration 27ms
subgraph-transition-enter: layout count 15
subgraph-transition-enter: style recalc count 19
subgraph-transition-enter: task duration 852ms
subgraph-transition-enter: script duration 32ms
subgraph-transition-enter: TBT 114ms
subgraph-transition-enter: heap used 95.7 MB
subgraph-transition-enter: DOM nodes 13673
subgraph-transition-enter: event listeners 2375
viewport-pan-sweep: avg frame time 17ms
viewport-pan-sweep: p95 frame time 17ms
viewport-pan-sweep: layout duration 0ms
viewport-pan-sweep: style recalc duration 35ms
viewport-pan-sweep: layout count 0
viewport-pan-sweep: style recalc count 250
viewport-pan-sweep: task duration 4207ms
viewport-pan-sweep: script duration 1237ms
viewport-pan-sweep: TBT 0ms
viewport-pan-sweep: heap used 65.0 MB
viewport-pan-sweep: DOM nodes -282
viewport-pan-sweep: event listeners -148
vue-large-graph-idle: avg frame time 18ms
vue-large-graph-idle: p95 frame time 17ms
vue-large-graph-idle: layout duration 0ms
vue-large-graph-idle: style recalc duration 0ms
vue-large-graph-idle: layout count 0
vue-large-graph-idle: style recalc count 0
vue-large-graph-idle: task duration 14989ms
vue-large-graph-idle: script duration 532ms
vue-large-graph-idle: TBT 0ms
vue-large-graph-idle: heap used 153.2 MB
vue-large-graph-idle: DOM nodes -8312
vue-large-graph-idle: event listeners -16389
vue-large-graph-pan: avg frame time 18ms
vue-large-graph-pan: p95 frame time 17ms
vue-large-graph-pan: layout duration 0ms
vue-large-graph-pan: style recalc duration 14ms
vue-large-graph-pan: layout count 0
vue-large-graph-pan: style recalc count 122
vue-large-graph-pan: task duration 17956ms
vue-large-graph-pan: script duration 860ms
vue-large-graph-pan: TBT 24ms
vue-large-graph-pan: heap used 158.8 MB
vue-large-graph-pan: DOM nodes -8312
vue-large-graph-pan: event listeners -16384
workflow-execution: avg frame time 17ms
workflow-execution: p95 frame time 17ms
workflow-execution: layout duration 1ms
workflow-execution: style recalc duration 20ms
workflow-execution: layout count 3
workflow-execution: style recalc count 14
workflow-execution: task duration 113ms
workflow-execution: script duration 9ms
workflow-execution: TBT 0ms
workflow-execution: heap used 65.5 MB
workflow-execution: DOM nodes 130
workflow-execution: event listeners 97
Raw data
{
  "timestamp": "2026-08-09T00:09:02.488Z",
  "gitSha": "028840078a9aac2cdb9aecb409db2cebd2a0b466",
  "branch": "dante/retire-consolidated-billing-flag",
  "measurements": [
    {
      "name": "canvas-idle",
      "durationMs": 2104.7019999999748,
      "styleRecalcs": 8,
      "styleRecalcDurationMs": 5.418999999999999,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 604.8979999999999,
      "heapDeltaBytes": 5885732,
      "heapUsedBytes": 70148608,
      "domNodes": 16,
      "jsHeapTotalBytes": 24903680,
      "scriptDurationMs": 17.870000000000005,
      "eventListeners": 4,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "canvas-idle",
      "durationMs": 1996.2050000000318,
      "styleRecalcs": 11,
      "styleRecalcDurationMs": 7.020999999999999,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 489.866,
      "heapDeltaBytes": 5624452,
      "heapUsedBytes": 69859468,
      "domNodes": 22,
      "jsHeapTotalBytes": 25427968,
      "scriptDurationMs": 21.381000000000004,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "canvas-mouse-sweep",
      "durationMs": 1781.6120000000524,
      "styleRecalcs": 74,
      "styleRecalcDurationMs": 31.064,
      "layouts": 12,
      "layoutDurationMs": 3.398,
      "taskDurationMs": 773.2139999999999,
      "heapDeltaBytes": -129728,
      "heapUsedBytes": 64314348,
      "domNodes": 56,
      "jsHeapTotalBytes": 25165824,
      "scriptDurationMs": 101.992,
      "eventListeners": 4,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "canvas-mouse-sweep",
      "durationMs": 1873.091000000045,
      "styleRecalcs": 78,
      "styleRecalcDurationMs": 35.912,
      "layouts": 12,
      "layoutDurationMs": 3.574,
      "taskDurationMs": 893.254,
      "heapDeltaBytes": -12135316,
      "heapUsedBytes": 52220108,
      "domNodes": -280,
      "jsHeapTotalBytes": 24092672,
      "scriptDurationMs": 107.42599999999999,
      "eventListeners": -153,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "canvas-zoom-sweep",
      "durationMs": 1660.587000000021,
      "styleRecalcs": 31,
      "styleRecalcDurationMs": 14.096,
      "layouts": 6,
      "layoutDurationMs": 0.605,
      "taskDurationMs": 356.70799999999997,
      "heapDeltaBytes": 8494168,
      "heapUsedBytes": 72536652,
      "domNodes": 81,
      "jsHeapTotalBytes": 24903680,
      "scriptDurationMs": 18.724,
      "eventListeners": 21,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333335,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "canvas-zoom-sweep",
      "durationMs": 1723.3929999999873,
      "styleRecalcs": 32,
      "styleRecalcDurationMs": 14.801,
      "layouts": 6,
      "layoutDurationMs": 0.6199999999999999,
      "taskDurationMs": 369.78599999999994,
      "heapDeltaBytes": 8824120,
      "heapUsedBytes": 73158320,
      "domNodes": 78,
      "jsHeapTotalBytes": 24903680,
      "scriptDurationMs": 21.801999999999996,
      "eventListeners": 19,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "dom-widget-clipping",
      "durationMs": 595.9920000000238,
      "styleRecalcs": 13,
      "styleRecalcDurationMs": 13.011,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 369.82599999999996,
      "heapDeltaBytes": -11310476,
      "heapUsedBytes": 53080068,
      "domNodes": 22,
      "jsHeapTotalBytes": 24903680,
      "scriptDurationMs": 53.17100000000001,
      "eventListeners": 0,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666682,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "dom-widget-clipping",
      "durationMs": 561.8469999999434,
      "styleRecalcs": 13,
      "styleRecalcDurationMs": 7.697999999999999,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 351.453,
      "heapDeltaBytes": -11219668,
      "heapUsedBytes": 53166472,
      "domNodes": 22,
      "jsHeapTotalBytes": 25165824,
      "scriptDurationMs": 49.739,
      "eventListeners": 0,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666682,
      "p95FrameDurationMs": 16.799999999999727
    },
    {
      "name": "large-graph-idle",
      "durationMs": 2039.0810000000101,
      "styleRecalcs": 9,
      "styleRecalcDurationMs": 6.856999999999998,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 642.4380000000001,
      "heapDeltaBytes": 6777196,
      "heapUsedBytes": 66267956,
      "domNodes": -282,
      "jsHeapTotalBytes": 2461696,
      "scriptDurationMs": 93.691,
      "eventListeners": -149,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "large-graph-idle",
      "durationMs": 2024.0009999999984,
      "styleRecalcs": 10,
      "styleRecalcDurationMs": 7.6320000000000014,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 657.573,
      "heapDeltaBytes": 3273540,
      "heapUsedBytes": 64935756,
      "domNodes": -279,
      "jsHeapTotalBytes": 3510272,
      "scriptDurationMs": 93.841,
      "eventListeners": -179,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "large-graph-pan",
      "durationMs": 2148.4570000000076,
      "styleRecalcs": 68,
      "styleRecalcDurationMs": 12.773,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 1195.5770000000002,
      "heapDeltaBytes": -4971916,
      "heapUsedBytes": 55548128,
      "domNodes": -284,
      "jsHeapTotalBytes": 5345280,
      "scriptDurationMs": 380.094,
      "eventListeners": -179,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.699999999999818
    },
    {
      "name": "large-graph-pan",
      "durationMs": 2162.742000000094,
      "styleRecalcs": 70,
      "styleRecalcDurationMs": 14.103000000000002,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 1241.1209999999999,
      "heapDeltaBytes": 2550416,
      "heapUsedBytes": 63295936,
      "domNodes": -282,
      "jsHeapTotalBytes": 4751360,
      "scriptDurationMs": 417.491,
      "eventListeners": -147,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "large-graph-zoom",
      "durationMs": 3128.7269999999694,
      "styleRecalcs": 65,
      "styleRecalcDurationMs": 13.652000000000005,
      "layouts": 60,
      "layoutDurationMs": 7.222000000000001,
      "taskDurationMs": 1408.133,
      "heapDeltaBytes": -6516024,
      "heapUsedBytes": 55629348,
      "domNodes": -289,
      "jsHeapTotalBytes": 6131712,
      "scriptDurationMs": 463.6580000000001,
      "eventListeners": -183,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333335,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "large-graph-zoom",
      "durationMs": 3164.675999999986,
      "styleRecalcs": 65,
      "styleRecalcDurationMs": 14.019999999999998,
      "layouts": 60,
      "layoutDurationMs": 7.365,
      "taskDurationMs": 1413.182,
      "heapDeltaBytes": -5719476,
      "heapUsedBytes": 56523528,
      "domNodes": -287,
      "jsHeapTotalBytes": 6656000,
      "scriptDurationMs": 470.014,
      "eventListeners": -153,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "minimap-idle",
      "durationMs": 2039.32199999997,
      "styleRecalcs": 9,
      "styleRecalcDurationMs": 6.780000000000001,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 649.193,
      "heapDeltaBytes": 7587864,
      "heapUsedBytes": 68171456,
      "domNodes": -284,
      "jsHeapTotalBytes": 3510272,
      "scriptDurationMs": 91.034,
      "eventListeners": -179,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333335,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "minimap-idle",
      "durationMs": 2022.9689999999891,
      "styleRecalcs": 9,
      "styleRecalcDurationMs": 6.9479999999999995,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 636.787,
      "heapDeltaBytes": 7010784,
      "heapUsedBytes": 67790080,
      "domNodes": -281,
      "jsHeapTotalBytes": 3510272,
      "scriptDurationMs": 92.90599999999999,
      "eventListeners": -149,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "subgraph-dom-widget-clipping",
      "durationMs": 555.0240000000031,
      "styleRecalcs": 48,
      "styleRecalcDurationMs": 10.311999999999998,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 346.47,
      "heapDeltaBytes": -10818240,
      "heapUsedBytes": 53480092,
      "domNodes": 22,
      "jsHeapTotalBytes": 25952256,
      "scriptDurationMs": 117.663,
      "eventListeners": 8,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.663333333333338,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "subgraph-dom-widget-clipping",
      "durationMs": 567.9909999998927,
      "styleRecalcs": 47,
      "styleRecalcDurationMs": 9.011,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 380.808,
      "heapDeltaBytes": -10860296,
      "heapUsedBytes": 53883904,
      "domNodes": 20,
      "jsHeapTotalBytes": 26476544,
      "scriptDurationMs": 112.15100000000001,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666682,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "subgraph-idle",
      "durationMs": 2003.358999999989,
      "styleRecalcs": 10,
      "styleRecalcDurationMs": 6.362999999999999,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 435.588,
      "heapDeltaBytes": 5758080,
      "heapUsedBytes": 70448700,
      "domNodes": 20,
      "jsHeapTotalBytes": 24641536,
      "scriptDurationMs": 17.493000000000002,
      "eventListeners": 6,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "subgraph-idle",
      "durationMs": 2016.6360000000623,
      "styleRecalcs": 10,
      "styleRecalcDurationMs": 6.447999999999999,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 484.796,
      "heapDeltaBytes": -17014960,
      "heapUsedBytes": 47456520,
      "domNodes": -280,
      "jsHeapTotalBytes": 23306240,
      "scriptDurationMs": 16.354,
      "eventListeners": -151,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.670000000000012,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "subgraph-mouse-sweep",
      "durationMs": 1677.989000000025,
      "styleRecalcs": 75,
      "styleRecalcDurationMs": 30.250000000000004,
      "layouts": 16,
      "layoutDurationMs": 3.538,
      "taskDurationMs": 709.218,
      "heapDeltaBytes": -3278004,
      "heapUsedBytes": 61397104,
      "domNodes": 63,
      "jsHeapTotalBytes": 24117248,
      "scriptDurationMs": 75.16099999999999,
      "eventListeners": 4,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333332,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "subgraph-mouse-sweep",
      "durationMs": 1714.4640000000209,
      "styleRecalcs": 77,
      "styleRecalcDurationMs": 33.96399999999999,
      "layouts": 16,
      "layoutDurationMs": 4.553,
      "taskDurationMs": 742.9539999999998,
      "heapDeltaBytes": -3912768,
      "heapUsedBytes": 60720660,
      "domNodes": 62,
      "jsHeapTotalBytes": 25165824,
      "scriptDurationMs": 77.497,
      "eventListeners": 4,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.66333333333335,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "subgraph-transition-enter",
      "durationMs": 1368.0259999999862,
      "styleRecalcs": 19,
      "styleRecalcDurationMs": 26.530000000000005,
      "layouts": 15,
      "layoutDurationMs": 10.722,
      "taskDurationMs": 851.5070000000001,
      "heapDeltaBytes": 32913792,
      "heapUsedBytes": 100385156,
      "domNodes": 13673,
      "jsHeapTotalBytes": 13107200,
      "scriptDurationMs": 31.55100000000001,
      "eventListeners": 2375,
      "totalBlockingTimeMs": 114,
      "frameDurationMs": 16.66999999999998,
      "p95FrameDurationMs": 16.800000000000182
    },
    {
      "name": "viewport-pan-sweep",
      "durationMs": 8187.3769999999695,
      "styleRecalcs": 249,
      "styleRecalcDurationMs": 34.307,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 4179.536,
      "heapDeltaBytes": 6395716,
      "heapUsedBytes": 68624836,
      "domNodes": -283,
      "jsHeapTotalBytes": 7634944,
      "scriptDurationMs": 1229.827,
      "eventListeners": -133,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "viewport-pan-sweep",
      "durationMs": 8210.365000000023,
      "styleRecalcs": 250,
      "styleRecalcDurationMs": 35.901,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 4233.6720000000005,
      "heapDeltaBytes": 7344932,
      "heapUsedBytes": 67708952,
      "domNodes": -280,
      "jsHeapTotalBytes": 5275648,
      "scriptDurationMs": 1244.385,
      "eventListeners": -163,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "vue-large-graph-idle",
      "durationMs": 15033.913999999982,
      "styleRecalcs": 0,
      "styleRecalcDurationMs": 0,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 15006.823,
      "heapDeltaBytes": -64260096,
      "heapUsedBytes": 154572620,
      "domNodes": -8312,
      "jsHeapTotalBytes": -10952704,
      "scriptDurationMs": 532.912,
      "eventListeners": -16389,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 17.223333333333358,
      "p95FrameDurationMs": 16.80000000000291
    },
    {
      "name": "vue-large-graph-idle",
      "durationMs": 15005.231999999978,
      "styleRecalcs": 0,
      "styleRecalcDurationMs": 0,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 14971.496,
      "heapDeltaBytes": -56909056,
      "heapUsedBytes": 166785404,
      "domNodes": -8312,
      "jsHeapTotalBytes": -6754304,
      "scriptDurationMs": 530.528,
      "eventListeners": -16389,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 18.333333333333332,
      "p95FrameDurationMs": 16.80000000000291
    },
    {
      "name": "vue-large-graph-pan",
      "durationMs": 17808.690000000013,
      "styleRecalcs": 122,
      "styleRecalcDurationMs": 13.396999999999991,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 17767.393,
      "heapDeltaBytes": -39827800,
      "heapUsedBytes": 165966896,
      "domNodes": -8312,
      "jsHeapTotalBytes": -12263424,
      "scriptDurationMs": 838.5600000000001,
      "eventListeners": -16387,
      "totalBlockingTimeMs": 29,
      "frameDurationMs": 18.33666666666674,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "vue-large-graph-pan",
      "durationMs": 18182.537000000026,
      "styleRecalcs": 122,
      "styleRecalcDurationMs": 14.833999999999985,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 18144.982,
      "heapDeltaBytes": -55914404,
      "heapUsedBytes": 167033376,
      "domNodes": -8312,
      "jsHeapTotalBytes": -9641984,
      "scriptDurationMs": 881.478,
      "eventListeners": -16381,
      "totalBlockingTimeMs": 19,
      "frameDurationMs": 17.216666666666665,
      "p95FrameDurationMs": 16.799999999999272
    },
    {
      "name": "workflow-execution",
      "durationMs": 445.0930000000426,
      "styleRecalcs": 12,
      "styleRecalcDurationMs": 16.942,
      "layouts": 3,
      "layoutDurationMs": 0.593,
      "taskDurationMs": 103.58500000000001,
      "heapDeltaBytes": 5312376,
      "heapUsedBytes": 68747832,
      "domNodes": 134,
      "jsHeapTotalBytes": 4980736,
      "scriptDurationMs": 9.405,
      "eventListeners": 97,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.670000000000012,
      "p95FrameDurationMs": 16.700000000000728
    },
    {
      "name": "workflow-execution",
      "durationMs": 465.09199999991324,
      "styleRecalcs": 15,
      "styleRecalcDurationMs": 22.586999999999996,
      "layouts": 3,
      "layoutDurationMs": 1.1010000000000002,
      "taskDurationMs": 123.29499999999999,
      "heapDeltaBytes": 5092284,
      "heapUsedBytes": 68547420,
      "domNodes": 125,
      "jsHeapTotalBytes": 5767168,
      "scriptDurationMs": 9.111,
      "eventListeners": 97,
      "totalBlockingTimeMs": 0,
      "frameDurationMs": 16.666666666666668,
      "p95FrameDurationMs": 16.800000000000182
    }
  ]
}

@codecov

codecov Bot commented Aug 3, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

@@                        Coverage Diff                        @@
##             dante/canonical-billing-status   #14615   +/-   ##
=================================================================
  Coverage                                  ?   78.73%           
=================================================================
  Files                                     ?     1866           
  Lines                                     ?   115401           
  Branches                                  ?    33540           
=================================================================
  Hits                                      ?    90860           
  Misses                                    ?    23998           
  Partials                                  ?      543           
Flag Coverage Δ
unit 72.29% <100.00%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/composables/billing/useBillingRouting.ts 86.36% <100.00%> (ø)
src/composables/useFeatureFlags.ts 76.22% <ø> (ø)
src/platform/remoteConfig/refreshRemoteConfig.ts 96.87% <ø> (ø)
src/platform/remoteConfig/remoteConfig.ts 100.00% <ø> (ø)
src/storybook/mocks/useFeatureFlags.ts 0.00% <ø> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@christian-byrne christian-byrne left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+19/-121 pure deletion, and the sweep looks complete: no remaining reader of consolidated_billing_enabled anywhere in src/, browser_tests/ or the storybook mocks. One non-blocking question inline, and it is inherited from the rail predicate rather than created here.

Worth stating explicitly, since this PR is the point of no return for the second flag: I could not verify from this repo that consolidated_billing_enabled is at 100 percent, and after the backend removes the key from common/featuregates/flags.go, reverting this PR does not restore the previous behaviour, because the reinstated read of a deleted key resolves to false, which is the off branch. flags.go carries the BE-119 rule requiring confirmation that no current frontend release reads a flag before it is removed, added after cloud#3171 had to be reverted by #3227/#3228 for exactly this. The backend PR that removes both keys, Comfy-Org/cloud#6040, pins its dependency to #14530 rather than to this stack, so please confirm the ordering and the rollout number before this merges.

Separately, the billing-rail routing is now spread over several sites reading one input (useBillingRouting.ts:32 asks === 'legacy_stripe' while launchCancellationFlow.ts:30 asks !== 'stripe', so metronome classifies differently in each). That is pre-existing and not this PR's job; I will open a follow-up issue for consolidating it and documenting the legacy_stripe exit condition.

Comment thread src/composables/billing/useBillingRouting.ts
@christian-byrne

Copy link
Copy Markdown
Contributor

Follow-up issue filed as promised in the review: #14645 (consolidate the billing_rail routing so metronome cannot be classified one way by useBillingRouting.ts:32 and the other by launchCancellationFlow.ts:30, make the union exhaustive, and write down the legacy_stripe exit condition). Not a blocker for this PR.

@dante01yoon

Copy link
Copy Markdown
Collaborator Author

Addressed the rollout review note in the PR body: it now records the frontend-version/open-tab drain requirement, the revert-is-not-restore constraint, the required 100% consolidated_billing_enabled confirmation, and the need to repoint cloud#6040 from #14530 to this selected stack. Those rollout/backend dependency confirmations remain external merge gates; no code change is appropriate.

@dante01yoon
dante01yoon force-pushed the dante/canonical-billing-status branch from e87d46a to 2e7cb4f Compare August 4, 2026 03:42
@dante01yoon
dante01yoon force-pushed the dante/retire-consolidated-billing-flag branch from c218cfc to a453ab9 Compare August 4, 2026 03:49
@dante01yoon
dante01yoon force-pushed the dante/canonical-billing-status branch from 2e7cb4f to 7f2854f Compare August 4, 2026 04:03
@dante01yoon
dante01yoon force-pushed the dante/retire-consolidated-billing-flag branch 2 times, most recently from 55b1360 to 65caf81 Compare August 4, 2026 04:25
@dante01yoon
dante01yoon force-pushed the dante/retire-consolidated-billing-flag branch from 65caf81 to 51e0fdf Compare August 5, 2026 08:33
@dante01yoon
dante01yoon force-pushed the dante/canonical-billing-status branch from a5b04e8 to ba7ac38 Compare August 5, 2026 08:39
@dante01yoon
dante01yoon force-pushed the dante/retire-consolidated-billing-flag branch from 51e0fdf to da0ad43 Compare August 5, 2026 08:40

@christian-byrne christian-byrne left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-approving at the current head, since my earlier approval predated the review and the branch has moved several times.

The sweep is complete: no remaining reader of consolidated_billing_enabled anywhere in src, browser_tests or the storybook mocks, and the delta since my last look is inherited from the rebase plus fixture updates rather than new logic.

I confirmed offline that consolidated_billing_enabled is at 100 percent, so retiring it does not change behaviour for any live cohort.

The rail predicate is unchanged and I am not re-litigating it. Recording the outcome so the decision is legible later: a personal workspace with an omitted or unrecognised billing_rail routes to workspace billing by design, and the previous server-side recovery lever is gone, so a mistake there needs a frontend rollback. That trade-off is deliberate and was confirmed in thread.

Follow-up for the routing consolidation and the metronome classification split between useBillingRouting and launchCancellationFlow is tracked in #14645, and is not this PR's job.

Note that this stack should not merge ahead of the backend coordination tracked in #14738: Comfy-Org/cloud#6040 still pins its dependency to a competing frontend PR that will never deploy.

@dante01yoon dante01yoon added the needs-backport Fix/change that needs to be cherry-picked to the current feature freeze branch label Aug 5, 2026
balpreetgrowthnatives pushed a commit to balpreetgrowthnatives/ComfyUI_frontend that referenced this pull request Aug 5, 2026
## Summary
Part 1/4 of the workspace/billing rollout retirement stack. Makes Cloud
workspace initialization failures recoverable without changing either
rollout flag or flag-off behavior.

Predecessor: none (base: main). Successor:
[Comfy-Org#14613](Comfy-Org#14613).

## Root cause
`WorkspaceAuthGate` failed closed on config, unified-auth, workspace
initialization, and timeout failures but exposed no recovery action. The
team workspace store also treated `error` as terminal, and the Cloud
account popover disappeared while workspace initialization was loading
or failed, preventing account actions.

## Changes
- Render a fail-closed initialization error with a retry action for
config, timeout, unified-auth, and workspace failures.
- Permit `teamWorkspaceStore.initialize()` to retry from `error`.
- Keep the account menu usable in flag-on loading/error states via
account-only workspace popover content.
- Preserve legacy popover and gate behavior when
`team_workspaces_enabled` is off.
- Filter empty logged-out Workspace settings groups.
- Add only the Cloud boot session/workspace fixture coverage needed by
this layer; both rollout keys remain supported.

## AS IS
Initialization failures permanently gate the app and loading/error
workspace state removes Cloud account actions.

## TO BE
Initialization failures remain fail-closed but can be retried, and Cloud
account actions remain available during loading/error. This is the only
intended visual change in the stack; it is a state-dependent recovery
UI, so no static screenshot is required.

## Regression coverage
- WorkspaceAuthGate failure, timeout, and retry paths with asynchronous
settling.
- Team workspace store `error -> loading -> ready` retry.
- Current user button/popover flag-off, flag-on loading/error, and ready
behavior.
- Logged-out settings group filtering.
- Cloud boot fixture session/workspace bootstrap.

## Validation
- `pnpm test:unit src/platform/workspace/auth/WorkspaceAuthGate.test.ts
src/platform/workspace/stores/teamWorkspaceStore.test.ts
src/components/topbar/CurrentUserButton.test.ts
src/platform/workspace/components/CurrentUserPopoverWorkspace.test.ts
src/platform/settings/composables/useSettingUI.test.ts` (125 passed)
- `pnpm typecheck` (passed via commit hook after final fix)
- `pnpm typecheck:browser` (passed)
- targeted `oxfmt --check` (passed)
- `git diff --check public/main...HEAD` (passed)
- `pnpm knip --cache` (passed via push hook)

## Review focus
Review only recoverability, retry state transitions, and preservation of
both rollout modes. Flag retirement belongs to later PRs.

## Stack/deployment order
1. **This PR** — recovery foundation
2. [Comfy-Org#14613](Comfy-Org#14613) —
retire team workspace rollout flag
3. [Comfy-Org#14614](Comfy-Org#14614) —
canonical billing status and legacy rail preservation
4. [Comfy-Org#14615](Comfy-Org#14615) —
retire consolidated billing rollout flag

Deploy the frontend stack before the backend removes `/api/features`
compatibility keys.

---------

Co-authored-by: Dante <6510430+dante01yoon@users.noreply.github.com>
Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: Christian Byrne <cbyrne@comfy.org>
@dante01yoon
dante01yoon force-pushed the dante/canonical-billing-status branch from ba7ac38 to b1b9451 Compare August 5, 2026 13:08
@dante01yoon
dante01yoon force-pushed the dante/retire-consolidated-billing-flag branch from da0ad43 to ef30c0b Compare August 5, 2026 13:13
@dante01yoon
dante01yoon force-pushed the dante/canonical-billing-status branch from b1b9451 to 22c2523 Compare August 5, 2026 13:37
pull Bot pushed a commit to Penguinjanator/ComfyUI_frontend that referenced this pull request Aug 6, 2026
)

## Summary
Part 2/4 of the workspace/billing rollout retirement stack. Removes
`team_workspaces_enabled` atomically after the recovery foundation in
[Comfy-Org#14612](Comfy-Org#14612).

Predecessor:
[Comfy-Org#14612](Comfy-Org#14612).
Successor:
[Comfy-Org#14614](Comfy-Org#14614).

## Root cause
Workspace mode was split across a long-lived rollout key in session
cookies, token selection, auth restoration, workspace boot, account
UI/settings, URL actions, and billing routing. Once Cloud workspace
support is the default, keeping both branches creates divergent
authentication and billing behavior and makes backend feature-key
removal unsafe.

## Changes
- Remove the team workspace key from remote-config types,
enum/getters/cache/refresh, local override, Storybook, browser payloads,
and obsolete flag tests.
- Make Cloud workspace auth/session restoration/switch/recovery and
workspace UI/loaders unconditional after the authenticated config
checkpoint.
- Preserve OSS and unloaded-workspace legacy bootstrap behavior.
- Preserve `consolidated_billing_enabled` and the intermediate billing
matrix, including explicit `legacy_stripe` account operations with
unified pricing.
- Update Cloud fixtures and affected account, settings, subscription,
pricing, and token-flow coverage.

## AS IS
Cloud workspace/auth/UI/billing paths can diverge depending on
`team_workspaces_enabled`.

## TO BE
Cloud workspace mode is the single path after authenticated config
loads; OSS and unloaded workspace bootstrap remain legacy. No intended
visual change; this is rollout retirement, so no screenshot is required.

## Regression coverage
- Auth session cookie, API/WebSocket token priority, workspace
restoration/switch/recovery.
- Workspace gate, account UI, settings/dialog layout, create/invite
loaders, pricing, and subscription success behavior.
- Intermediate billing routing for OSS, unloaded Cloud, team, personal
consolidated off/on, and `legacy_stripe`.
- Cloud browser fixtures omit the retired key.
- Exact source/browser scan confirms no `team_workspaces_enabled` or
`teamWorkspacesEnabled` references.

## Validation
- 14 focused Vitest files: 453 tests passed.
- `pnpm typecheck` and `pnpm typecheck:browser` passed via commit hook.
- targeted formatting/lint passed via commit hook.
- `git diff --check dante/workspace-init-recovery...HEAD` passed.
- `pnpm knip --cache` passed via push hook.
- Focused Cloud Playwright: billing facade 2 passed; pricing owner flow
1 passed. The member-plan flow timed out during local app boot before
assertions; the corrected full Cloud Playwright CI job passed.

## Review focus
Review only the atomic team-flag removal and intermediate routing
matrix. Canonical billing status and consolidated-flag retirement are
intentionally deferred.

## Stack/deployment order
1. [Comfy-Org#14612](Comfy-Org#14612) —
recovery foundation
2. **This PR** — retire team workspace rollout flag
3. [Comfy-Org#14614](Comfy-Org#14614) —
canonical billing status and legacy rail preservation
4. [Comfy-Org#14615](Comfy-Org#14615) —
retire consolidated billing rollout flag

Deploy the frontend stack before the backend removes `/api/features`
compatibility keys. This is also a drain requirement: age out deployed
frontend versions and open tabs that still read either retired key
before removing backend compatibility keys. Confirm
`team_workspaces_enabled` is at 100% and update `Comfy-Org/cloud#6040`
to depend on the selected stack rather than competing PR Comfy-Org#14530 before
merge. After backend key removal, reverting this PR is not a behavioral
restore because a reinstated read of the missing key resolves to the off
branch.

---------

Co-authored-by: Dante <6510430+dante01yoon@users.noreply.github.com>
Co-authored-by: Amp <amp@ampcode.com>
@christian-byrne
christian-byrne force-pushed the dante/canonical-billing-status branch from 22c2523 to 147988b Compare August 8, 2026 23:55
Rebased onto the rebased #14614 after #14613 landed as a squash commit.
Applied with no conflicts.

Verified after rebase: 14 files / +20 / -130, matching the reviewed change;
zero remaining references to consolidated_billing_enabled or
consolidatedBillingEnabled across src/ and browser_tests/; pnpm typecheck clean;
5 test files and 105 tests pass across composables/billing, useFeatureFlags and
platform/remoteConfig.

Co-Authored-By: dante01yoon <bunggl@naver.com>
@christian-byrne
christian-byrne force-pushed the dante/retire-consolidated-billing-flag branch from ef30c0b to 241da53 Compare August 8, 2026 23:57
@dante01yoon
dante01yoon merged commit 9c58830 into dante/canonical-billing-status Aug 10, 2026
59 checks passed
@dante01yoon
dante01yoon deleted the dante/retire-consolidated-billing-flag branch August 10, 2026 18:02
DrJKL pushed a commit to DrJKL/ComfyUI_frontend that referenced this pull request Aug 10, 2026
…fy-Org#14614)

## Summary
Part 3/4 of the workspace/billing rollout retirement stack. Standardizes
Cloud subscription discovery on canonical workspace billing status while
preserving explicit `legacy_stripe` account operations.

Predecessor:
[Comfy-Org#14613](Comfy-Org#14613).
Successor:
[Comfy-Org#14615](Comfy-Org#14615).

## Root cause
Cloud subscription state was read from both
`/customers/cloud-subscription-status` and `/api/billing/status` with
different response types and cancellation fields (`end_date` versus
`cancel_at`). That allowed stale rail selection and could mix workspace
status/balance with legacy top-up and management behavior.

## Changes
- Read Cloud subscription status only from `/api/billing/status` using
the canonical type and `cancel_at`.
- Avoid ingress status requests for non-Cloud builds.
- Preserve `legacy_stripe` balance, top-up, and management while unified
checkout remains workspace-backed.
- Keep `consolidated_billing_enabled` routing intact.
- Dispatch the production pending-checkout event in the Playwright
helper.
- Strengthen the rail regression to prove canonical discovery, no
old-status request, legacy balance/credits visibility, and rejection of
stale workspace zero balance.

## AS IS
Two status contracts can compete, and cancellation/rail state can be
stale or translated inconsistently.

## TO BE
Canonical status selects the rail; explicit legacy Stripe continues
legacy account operations and unified checkout. No intended visual
change, so no screenshot is required.

## Regression coverage
- Canonical Cloud status and non-Cloud no-ingress behavior.
- Canonical cancellation in watcher and legacy adapter.
- Adapter switching and stale-rail failure.
- Playwright regression serves `consolidated_billing_enabled: true` +
`legacy_stripe`, observes canonical status and legacy balance, proves
old status absent, and verifies credits/top-up UI without stale zero
balance.

## Validation
- 4 focused Vitest files: 79 tests passed.
- `pnpm typecheck` and `pnpm typecheck:browser` passed via commit hooks.
- targeted format/lint passed via commit hooks.
- `git diff --check dante/retire-team-workspace-flag...HEAD` passed.
- `pnpm knip --cache` passed via push hook.
- Focused Playwright legacy-rail regression passed against local Cloud
dev server (1 passed). The full two-test file had one transient initial
boot timeout on its first run; its second pricing-flow test passed, and
the focused failed case passed on rerun.

## Review focus
Review only canonical status/cancellation semantics and the
legacy-account/unified-checkout boundary. Consolidated flag retirement
belongs to part 4.

## Stack/deployment order
1. [Comfy-Org#14612](Comfy-Org#14612) —
recovery foundation
2. [Comfy-Org#14613](Comfy-Org#14613) —
retire team workspace rollout flag
3. **This PR** — canonical status and legacy rail preservation
4. [Comfy-Org#14615](Comfy-Org#14615) —
retire consolidated billing rollout flag

Deploy the frontend stack before the backend removes `/api/features`
compatibility keys.

---------

Co-authored-by: Connor Byrne <c.byrne@comfy.org>
Co-authored-by: Amp <amp@ampcode.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-backport Fix/change that needs to be cherry-picked to the current feature freeze branch size:M This PR changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants