refactor(billing): use canonical status and preserve legacy rail - #14614
Conversation
📝 WalkthroughWalkthroughThe subscription status flow now uses the workspace billing API and ChangesBilling status migration
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant SubscriptionComposable
participant WorkspaceApi
participant WorkspaceStore
SubscriptionComposable->>WorkspaceApi: getBillingStatus()
WorkspaceApi-->>SubscriptionComposable: BillingStatusResponse
SubscriptionComposable->>WorkspaceStore: set billing_rail
SubscriptionComposable-->>SubscriptionComposable: apply current workspace status
Suggested reviewers: Important Pre-merge checks failedPlease resolve all errors before merging. Addressing warnings is optional. ❌ Failed checks (1 inconclusive)
✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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 |
📄 Knowledge reviewDosu skipped reviewing this PR because your organization has used its |
🎭 Playwright: ✅ 1801 passed, 0 failed · 1 flaky📊 Browser Reports
🎨 Storybook: ✅ Built — View Storybook📦 Bundle: 8.69 MB gzip 🟢 -744 BDetailsSummary
Category Glance App Entry Points — 3.67 kB (baseline 3.67 kB) • ⚪ 0 BMain entry bundles and manifests
Status: 1 added / 1 removed Graph Workspace — 1.36 MB (baseline 1.36 MB) • ⚪ 0 BGraph editor runtime, canvas, workflow orchestration
Status: 2 added / 2 removed / 1 unchanged Views & Navigation — 124 kB (baseline 124 kB) • ⚪ 0 BTop-level views, pages, and routed surfaces
Status: 13 added / 13 removed / 4 unchanged Panels & Settings — 569 kB (baseline 570 kB) • 🟢 -260 BConfiguration panels, inspectors, and settings screens
Status: 11 added / 11 removed / 15 unchanged User & Accounts — 27 kB (baseline 27 kB) • ⚪ 0 BAuthentication, profile, and account management bundles
Status: 6 added / 6 removed / 4 unchanged Editors & Dialogs — 125 kB (baseline 125 kB) • ⚪ 0 BModals, dialogs, drawers, and in-app editors
Status: 7 added / 7 removed / 1 unchanged UI Components — 67.1 kB (baseline 67.1 kB) • ⚪ 0 BReusable component library chunks
Status: 6 added / 6 removed / 8 unchanged Data & Services — 3.5 MB (baseline 3.5 MB) • 🟢 -3.74 kBStores, services, APIs, and repositories
Status: 14 added / 14 removed / 3 unchanged Utilities & Hooks — 549 kB (baseline 550 kB) • 🟢 -313 BHelpers, composables, and utility bundles
Status: 18 added / 18 removed / 20 unchanged Vendor & Third-Party — 16.3 MB (baseline 16.3 MB) • ⚪ 0 BExternal libraries and shared vendor chunks Status: 17 unchanged Other — 14 MB (baseline 14 MB) • ⚪ 0 BBundles that do not match a named category
Status: 69 added / 69 removed / 217 unchanged ⚡ Performance Report
Show regressions
All metrics
Historical variance (last 15 runs)
Trend (last 15 commits on main)
Raw data{
"timestamp": "2026-08-10T18:34:04.444Z",
"gitSha": "b9049ec66224ad1a282c9024e61d1654afc487fa",
"branch": "dante/canonical-billing-status",
"measurements": [
{
"name": "canvas-idle",
"durationMs": 2129.903000000013,
"styleRecalcs": 10,
"styleRecalcDurationMs": 9.491000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 541.7800000000001,
"heapDeltaBytes": 5915088,
"heapUsedBytes": 70163080,
"domNodes": 20,
"jsHeapTotalBytes": 25165824,
"scriptDurationMs": 20.564,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66999999999998,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "canvas-idle",
"durationMs": 2034.4119999999748,
"styleRecalcs": 9,
"styleRecalcDurationMs": 8.215,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 505.95900000000006,
"heapDeltaBytes": -18308240,
"heapUsedBytes": 46098696,
"domNodes": -280,
"jsHeapTotalBytes": 23306240,
"scriptDurationMs": 17.608,
"eventListeners": -153,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "canvas-mouse-sweep",
"durationMs": 2037.7040000000193,
"styleRecalcs": 77,
"styleRecalcDurationMs": 48.268,
"layouts": 12,
"layoutDurationMs": 4.992,
"taskDurationMs": 1053.056,
"heapDeltaBytes": -10500632,
"heapUsedBytes": 53810300,
"domNodes": -282,
"jsHeapTotalBytes": 23830528,
"scriptDurationMs": 132.588,
"eventListeners": -151,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-mouse-sweep",
"durationMs": 1853.211999999985,
"styleRecalcs": 76,
"styleRecalcDurationMs": 39.013,
"layouts": 12,
"layoutDurationMs": 3.691,
"taskDurationMs": 907.9430000000001,
"heapDeltaBytes": -12894680,
"heapUsedBytes": 51526840,
"domNodes": -285,
"jsHeapTotalBytes": 23830528,
"scriptDurationMs": 125.80200000000002,
"eventListeners": -153,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1691.2080000000174,
"styleRecalcs": 30,
"styleRecalcDurationMs": 16.541,
"layouts": 6,
"layoutDurationMs": 0.48800000000000016,
"taskDurationMs": 383.967,
"heapDeltaBytes": 8406060,
"heapUsedBytes": 72504688,
"domNodes": 78,
"jsHeapTotalBytes": 25427968,
"scriptDurationMs": 21.131000000000004,
"eventListeners": 19,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1754.2020000000775,
"styleRecalcs": 31,
"styleRecalcDurationMs": 18.689000000000004,
"layouts": 6,
"layoutDurationMs": 0.717,
"taskDurationMs": 398.97499999999997,
"heapDeltaBytes": 8672048,
"heapUsedBytes": 73030568,
"domNodes": 79,
"jsHeapTotalBytes": 24903680,
"scriptDurationMs": 21.842999999999996,
"eventListeners": 19,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "dom-widget-clipping",
"durationMs": 590.9259999999676,
"styleRecalcs": 11,
"styleRecalcDurationMs": 8.653,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 402.02,
"heapDeltaBytes": -11180888,
"heapUsedBytes": 53088360,
"domNodes": 18,
"jsHeapTotalBytes": 24903680,
"scriptDurationMs": 59.512,
"eventListeners": 0,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "dom-widget-clipping",
"durationMs": 577.7050000000372,
"styleRecalcs": 9,
"styleRecalcDurationMs": 5.5699999999999985,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 374.53999999999996,
"heapDeltaBytes": -11170260,
"heapUsedBytes": 53143552,
"domNodes": 14,
"jsHeapTotalBytes": 25427968,
"scriptDurationMs": 59.324999999999996,
"eventListeners": 2,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-idle",
"durationMs": 2051.4519999999834,
"styleRecalcs": 9,
"styleRecalcDurationMs": 8.319,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 697.894,
"heapDeltaBytes": 7880200,
"heapUsedBytes": 67896936,
"domNodes": -282,
"jsHeapTotalBytes": 3772416,
"scriptDurationMs": 114.091,
"eventListeners": -149,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "large-graph-idle",
"durationMs": 2038.2779999999912,
"styleRecalcs": 9,
"styleRecalcDurationMs": 8.2,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 729.3509999999999,
"heapDeltaBytes": 6721464,
"heapUsedBytes": 66238600,
"domNodes": -283,
"jsHeapTotalBytes": 2985984,
"scriptDurationMs": 119.619,
"eventListeners": -147,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "large-graph-pan",
"durationMs": 2198.3319999999935,
"styleRecalcs": 68,
"styleRecalcDurationMs": 13.278000000000002,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 1307.2,
"heapDeltaBytes": 4092828,
"heapUsedBytes": 65475376,
"domNodes": -284,
"jsHeapTotalBytes": 4227072,
"scriptDurationMs": 439.81100000000004,
"eventListeners": -147,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "large-graph-pan",
"durationMs": 2199.359999999956,
"styleRecalcs": 69,
"styleRecalcDurationMs": 13.978000000000002,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 1293.72,
"heapDeltaBytes": -5359240,
"heapUsedBytes": 55157824,
"domNodes": -284,
"jsHeapTotalBytes": 3772416,
"scriptDurationMs": 427.551,
"eventListeners": -179,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "large-graph-zoom",
"durationMs": 3329.9729999999954,
"styleRecalcs": 65,
"styleRecalcDurationMs": 14.236000000000002,
"layouts": 60,
"layoutDurationMs": 7.2219999999999995,
"taskDurationMs": 1580.594,
"heapDeltaBytes": 5853332,
"heapUsedBytes": 67884856,
"domNodes": -289,
"jsHeapTotalBytes": 6393856,
"scriptDurationMs": 558.3779999999999,
"eventListeners": -153,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "large-graph-zoom",
"durationMs": 3198.7910000000284,
"styleRecalcs": 64,
"styleRecalcDurationMs": 13.801,
"layouts": 60,
"layoutDurationMs": 7.370000000000001,
"taskDurationMs": 1553.283,
"heapDeltaBytes": -2834508,
"heapUsedBytes": 60051540,
"domNodes": -289,
"jsHeapTotalBytes": 6393856,
"scriptDurationMs": 538.953,
"eventListeners": -153,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "minimap-idle",
"durationMs": 2060.824000000025,
"styleRecalcs": 8,
"styleRecalcDurationMs": 11.002,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 693.4350000000001,
"heapDeltaBytes": 7622636,
"heapUsedBytes": 68205740,
"domNodes": -282,
"jsHeapTotalBytes": 4034560,
"scriptDurationMs": 110.50699999999999,
"eventListeners": -179,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "minimap-idle",
"durationMs": 2010.9630000000607,
"styleRecalcs": 6,
"styleRecalcDurationMs": 5.8740000000000006,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 679.603,
"heapDeltaBytes": 6516656,
"heapUsedBytes": 67808452,
"domNodes": -284,
"jsHeapTotalBytes": 3248128,
"scriptDurationMs": 108.571,
"eventListeners": -149,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 578.8870000000088,
"styleRecalcs": 46,
"styleRecalcDurationMs": 9.726000000000003,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 390.313,
"heapDeltaBytes": -10860392,
"heapUsedBytes": 53705044,
"domNodes": 18,
"jsHeapTotalBytes": 24903680,
"scriptDurationMs": 118.14500000000001,
"eventListeners": 8,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 597.1939999999449,
"styleRecalcs": 48,
"styleRecalcDurationMs": 12.29,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 429.38200000000006,
"heapDeltaBytes": -10467872,
"heapUsedBytes": 53922472,
"domNodes": 22,
"jsHeapTotalBytes": 25427968,
"scriptDurationMs": 122.53299999999999,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-idle",
"durationMs": 2037.8379999999652,
"styleRecalcs": 11,
"styleRecalcDurationMs": 9.527000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 469.2850000000001,
"heapDeltaBytes": 5997532,
"heapUsedBytes": 70397808,
"domNodes": 22,
"jsHeapTotalBytes": 24379392,
"scriptDurationMs": 15.796000000000001,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-idle",
"durationMs": 2013.8489999999365,
"styleRecalcs": 10,
"styleRecalcDurationMs": 9.296000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 479.5489999999999,
"heapDeltaBytes": 5980952,
"heapUsedBytes": 70489140,
"domNodes": 20,
"jsHeapTotalBytes": 24379392,
"scriptDurationMs": 14.274999999999999,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1759.9710000000073,
"styleRecalcs": 78,
"styleRecalcDurationMs": 37.492,
"layouts": 16,
"layoutDurationMs": 4.276000000000001,
"taskDurationMs": 844.9290000000001,
"heapDeltaBytes": -18424948,
"heapUsedBytes": 46102168,
"domNodes": 0,
"jsHeapTotalBytes": 24092672,
"scriptDurationMs": 93.676,
"eventListeners": -153,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1738.4669999999005,
"styleRecalcs": 76,
"styleRecalcDurationMs": 37.041000000000004,
"layouts": 16,
"layoutDurationMs": 4.720000000000001,
"taskDurationMs": 803.324,
"heapDeltaBytes": -3753240,
"heapUsedBytes": 60687236,
"domNodes": 62,
"jsHeapTotalBytes": 25165824,
"scriptDurationMs": 94.581,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "subgraph-transition-enter",
"durationMs": 1424.2659999999887,
"styleRecalcs": 18,
"styleRecalcDurationMs": 30.30899999999999,
"layouts": 14,
"layoutDurationMs": 12.848000000000003,
"taskDurationMs": 943.2499999999999,
"heapDeltaBytes": 3409732,
"heapUsedBytes": 74524600,
"domNodes": 13673,
"jsHeapTotalBytes": 15204352,
"scriptDurationMs": 35.719,
"eventListeners": 2375,
"totalBlockingTimeMs": 151,
"frameDurationMs": 16.666666666666636,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "viewport-pan-sweep",
"durationMs": 8374.326999999994,
"styleRecalcs": 247,
"styleRecalcDurationMs": 33.906000000000006,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 4513.592,
"heapDeltaBytes": 10611296,
"heapUsedBytes": 71054832,
"domNodes": -287,
"jsHeapTotalBytes": 5537792,
"scriptDurationMs": 1395.267,
"eventListeners": -131,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "viewport-pan-sweep",
"durationMs": 8301.329999999893,
"styleRecalcs": 250,
"styleRecalcDurationMs": 36.74,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 4584.405000000001,
"heapDeltaBytes": 10082548,
"heapUsedBytes": 69558892,
"domNodes": -283,
"jsHeapTotalBytes": 6848512,
"scriptDurationMs": 1416.611,
"eventListeners": -133,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "vue-large-graph-idle",
"durationMs": 17187.873000000025,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 17171.809,
"heapDeltaBytes": -58554344,
"heapUsedBytes": 167797992,
"domNodes": -8312,
"jsHeapTotalBytes": -4923392,
"scriptDurationMs": 536.7030000000001,
"eventListeners": -16385,
"totalBlockingTimeMs": 0,
"frameDurationMs": 17.776666666666642,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "vue-large-graph-idle",
"durationMs": 16994.140000000014,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 16955.496,
"heapDeltaBytes": -45678728,
"heapUsedBytes": 166990300,
"domNodes": -8312,
"jsHeapTotalBytes": -9117696,
"scriptDurationMs": 570.4860000000001,
"eventListeners": -16387,
"totalBlockingTimeMs": 0,
"frameDurationMs": 17.780000000000047,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "vue-large-graph-pan",
"durationMs": 20378.434000000027,
"styleRecalcs": 138,
"styleRecalcDurationMs": 16.726999999999993,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 20352.981,
"heapDeltaBytes": -59298568,
"heapUsedBytes": 166431956,
"domNodes": -8312,
"jsHeapTotalBytes": -9904128,
"scriptDurationMs": 870.0609999999999,
"eventListeners": -16383,
"totalBlockingTimeMs": 131,
"frameDurationMs": 17.780000000000047,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-pan",
"durationMs": 20726.992999999995,
"styleRecalcs": 143,
"styleRecalcDurationMs": 17.046000000000006,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 20687.148,
"heapDeltaBytes": -40092636,
"heapUsedBytes": 168759044,
"domNodes": -8312,
"jsHeapTotalBytes": -8331264,
"scriptDurationMs": 890.472,
"eventListeners": -16383,
"totalBlockingTimeMs": 234,
"frameDurationMs": 17.776666666666642,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "workflow-execution",
"durationMs": 451.42999999995936,
"styleRecalcs": 13,
"styleRecalcDurationMs": 22.545,
"layouts": 3,
"layoutDurationMs": 1.2249999999999996,
"taskDurationMs": 118.92,
"heapDeltaBytes": 5058528,
"heapUsedBytes": 68505256,
"domNodes": 124,
"jsHeapTotalBytes": 4980736,
"scriptDurationMs": 9.746,
"eventListeners": 99,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "workflow-execution",
"durationMs": 487.5250000000051,
"styleRecalcs": 16,
"styleRecalcDurationMs": 24.322000000000003,
"layouts": 4,
"layoutDurationMs": 1.349,
"taskDurationMs": 129.887,
"heapDeltaBytes": 5050636,
"heapUsedBytes": 68404228,
"domNodes": 125,
"jsHeapTotalBytes": 5505024,
"scriptDurationMs": 10.405,
"eventListeners": 97,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
}
]
} |
Codecov Report✅ All modified and coverable lines are covered by tests. @@ Coverage Diff @@
## main #14614 +/- ##
==========================================
+ Coverage 80.42% 80.43% +0.01%
==========================================
Files 1872 1872
Lines 118595 118583 -12
Branches 36644 36185 -459
==========================================
+ Hits 95383 95387 +4
+ Misses 22700 22674 -26
- Partials 512 522 +10
Flags with carried forward coverage won't be shown. Click here to find out more.
... and 9 files with indirect coverage changes 🚀 New features to boost your workflow:
|
christian-byrne
left a comment
There was a problem hiding this comment.
The read swap itself checks out. I traced every consumer of the retired /customers/cloud-subscription-status payload and none reads a field the canonical response does not carry, and the end_date to cancel_at repoint is a rename of the same concept, not a semantic change: the registry schema documents end_date as "the date when the subscription is set to end", the ingest schema documents cancel_at as "when the subscription will become inactive (if canceled)", and billingFacadeConsumers.spec.ts on main already maps cancel_at: s.end_date ?? undefined under a comment saying the two shapes mirror each other.
Four comments inline. The first is the one that matters: the regression block the PR body advertises does not execute at all, and it is a one-word fix.
The dependency ordering stated in the PR body ("deploy the frontend stack before the backend removes /api/features compatibility keys") is really a drain requirement rather than a deploy requirement, since already-open tabs re-run refreshRemoteConfig and persist an absent key as false. That applies to the whole stack; I have raised it on #14613, where the coordination question with Comfy-Org/cloud#6040 and #14530 also lives.
64b10c6 to
d718236
Compare
2e7cb4f to
7f2854f
Compare
christian-byrne
left a comment
There was a problem hiding this comment.
One item moved back onto the PR from the issue tracker, because it is a test-reliability regression this PR introduces and it will cost the whole team once it is on main. The remaining leftovers (#14743 cancel_at parity, #14645 routing consolidation) stay as issues since neither is a code change here.
a5b04e8 to
ba7ac38
Compare
christian-byrne
left a comment
There was a problem hiding this comment.
Approving. I traced every consumer of the retired /customers/cloud-subscription-status payload and none reads a field the canonical response does not carry, and the end_date to cancel_at repoint is a rename of the same scheduled-end concept rather than a semantic change.
Everything I raised is addressed:
- The regression block is no longer inert.
checkout_type: 'change'clearsnormalizeAttempt, and the counter reset before the dispatch means the poll proves the recovery fetch rather than the boot fetch. - The rail write now snapshots before the await.
performFetchSubscriptionStatustakesrequestIdandworkspaceIdahead ofgetBillingStatus()and guards on both, matching the shapeuseWorkspaceBillingalready used, so a workspace switch in flight can no longer stamp the old rail onto the new workspace. - The localized subscription toast is restored, so a 500 with an empty body no longer surfaces a raw axios string.
useLegacyBillingpassessubscription_status,billing_statusandteam_credit_stopthrough from the canonical payload, which makes the ended-subscription handling from #14504 and #14607 reachable on the legacy_stripe rail for the first time.- The e2e timing fix does what I asked: it waits for the rail to settle and asserts the legacy balance before seeding the pending attempt.
One non-blocking observation on that last one. expect.poll(() => billingRequests.workspaceStatus).toBeGreaterThan(1) pins the boot double-fetch as required behaviour, so if the redundant second fetch is ever collapsed, which would be an improvement, this test fails for a good change. A settled-count or balance-visible wait would tolerate both shapes. Not worth holding the PR.
|
@coderabbitai approve |
✅ Action performedComments resolved and changes approved. |
|
|
Summary
Part 3/4 of the workspace/billing rollout retirement stack. Standardizes Cloud subscription discovery on canonical workspace billing status while preserving explicit
legacy_stripeaccount operations.Predecessor: #14613. Successor: #14615.
Root cause
Cloud subscription state was read from both
/customers/cloud-subscription-statusand/api/billing/statuswith different response types and cancellation fields (end_dateversuscancel_at). That allowed stale rail selection and could mix workspace status/balance with legacy top-up and management behavior.Changes
/api/billing/statususing the canonical type andcancel_at.legacy_stripebalance, top-up, and management while unified checkout remains workspace-backed.consolidated_billing_enabledrouting intact.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
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
pnpm typecheckandpnpm typecheck:browserpassed via commit hooks.git diff --check dante/retire-team-workspace-flag...HEADpassed.pnpm knip --cachepassed via push hook.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
Deploy the frontend stack before the backend removes
/api/featurescompatibility keys.