[backport core/1.48] fix(workspace): let ended subscriptions resubscribe to the same plan - #14610
Conversation
…14607) After cancelling a subscription on Stripe's side, access is correctly revoked, but Plans & Pricing still showed the lost plan as "Current Plan" with a disabled button, so the user could not resubscribe to it. The other tiers also mislabelled as "Change to X" instead of "Subscribe to X". ## Root cause An ended subscription still reports its `plan_slug` and `subscription_tier` on `GET /api/billing/status` (only `is_active` flips to false). The pricing table modelled just two states for the plan you hold — active (disabled "Current Plan") and cancel-scheduled (enabled "Resubscribe") — and `isCancelled` is derived from `subscription_status === 'canceled'` only, so an `ended` subscription fell into the active bucket. This is the residual half of #14504, which fixed the same `ended` blind spot in the subscription panel but not here. ## Change `UnifiedPricingTable.vue` treats `subscription_status: 'ended'` as holding no current plan, so the CTA becomes an enabled "Subscribe to <plan>". Applies to both the personal tier cards (`isCurrentPlan`, `currentTierKey`) and the team credit stop (`isTeamSubscribed`). Backend needs no change — the subscribe path already treats an ended subscription as "no subscription". ## Verification - Two regression tests added; both fail on `main` and pass with the fix. - `UnifiedPricingTable` suite: 12 passed, incl. under `--coverage` (matching CI). - Workspace + billing + subscription suites: 84 files, 1339 tests passed. - `vue-tsc --noEmit`, eslint, oxfmt clean.
|
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 |
📄 Knowledge reviewDosu skipped reviewing this PR because your organization has used its |
🎨 Storybook: ✅ Built — View Storybook🎭 Playwright: ✅ 1762 passed, 0 failed · 1 flaky📊 Browser Reports
📦 Bundle Size
⚡ Performance Report
Show regressions
All metrics
Historical variance (last 15 runs)
Trend (last 15 commits on main)
Raw data{
"timestamp": "2026-08-03T18:22:28.973Z",
"gitSha": "9e1d9886fe01b6426691ae4c8f2d608f9f12cf30",
"branch": "backport-14607-to-core-1.48",
"measurements": [
{
"name": "canvas-idle",
"durationMs": 2047.0610000000136,
"styleRecalcs": 10,
"styleRecalcDurationMs": 6.671999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 474.533,
"heapDeltaBytes": 4011660,
"heapUsedBytes": 71876976,
"domNodes": 20,
"jsHeapTotalBytes": 20316160,
"scriptDurationMs": 21.348000000000003,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "canvas-idle",
"durationMs": 2039.650999999992,
"styleRecalcs": 10,
"styleRecalcDurationMs": 7.576999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 515.558,
"heapDeltaBytes": -21694484,
"heapUsedBytes": 46076684,
"domNodes": -272,
"jsHeapTotalBytes": 20180992,
"scriptDurationMs": 22.901,
"eventListeners": -146,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66999999999998,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "canvas-mouse-sweep",
"durationMs": 2059.710999999993,
"styleRecalcs": 79,
"styleRecalcDurationMs": 52.11000000000001,
"layouts": 12,
"layoutDurationMs": 9.131,
"taskDurationMs": 996.2639999999999,
"heapDeltaBytes": -15612564,
"heapUsedBytes": 52299500,
"domNodes": -271,
"jsHeapTotalBytes": 20443136,
"scriptDurationMs": 137.153,
"eventListeners": -148,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "canvas-mouse-sweep",
"durationMs": 1874.66900000004,
"styleRecalcs": 76,
"styleRecalcDurationMs": 36.98500000000001,
"layouts": 12,
"layoutDurationMs": 3.543,
"taskDurationMs": 880.854,
"heapDeltaBytes": -16652360,
"heapUsedBytes": 51493588,
"domNodes": -272,
"jsHeapTotalBytes": 21229568,
"scriptDurationMs": 109.015,
"eventListeners": -148,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1709.8269999999616,
"styleRecalcs": 30,
"styleRecalcDurationMs": 15.408,
"layouts": 6,
"layoutDurationMs": 0.541,
"taskDurationMs": 359.532,
"heapDeltaBytes": 7038100,
"heapUsedBytes": 74812424,
"domNodes": 77,
"jsHeapTotalBytes": 20054016,
"scriptDurationMs": 19.063999999999997,
"eventListeners": 21,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1740.6559999999445,
"styleRecalcs": 32,
"styleRecalcDurationMs": 14.559000000000001,
"layouts": 6,
"layoutDurationMs": 0.5599999999999999,
"taskDurationMs": 368.896,
"heapDeltaBytes": 7292784,
"heapUsedBytes": 75176552,
"domNodes": 77,
"jsHeapTotalBytes": 20054016,
"scriptDurationMs": 22.025999999999996,
"eventListeners": 21,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "dom-widget-clipping",
"durationMs": 574.3449999999939,
"styleRecalcs": 12,
"styleRecalcDurationMs": 7.2860000000000005,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 359.18300000000005,
"heapDeltaBytes": -12430740,
"heapUsedBytes": 55628992,
"domNodes": 20,
"jsHeapTotalBytes": 21364736,
"scriptDurationMs": 51.532999999999994,
"eventListeners": 0,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "dom-widget-clipping",
"durationMs": 592.3139999999876,
"styleRecalcs": 11,
"styleRecalcDurationMs": 6.707,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 358.709,
"heapDeltaBytes": -12450460,
"heapUsedBytes": 55505420,
"domNodes": 18,
"jsHeapTotalBytes": 21889024,
"scriptDurationMs": 51.836,
"eventListeners": 2,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999727
},
{
"name": "large-graph-idle",
"durationMs": 2026.5810000000215,
"styleRecalcs": 9,
"styleRecalcDurationMs": 6.978999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 653.2309999999999,
"heapDeltaBytes": 6929460,
"heapUsedBytes": 66637900,
"domNodes": -276,
"jsHeapTotalBytes": 4521984,
"scriptDurationMs": 99.763,
"eventListeners": -144,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "large-graph-idle",
"durationMs": 2031.5949999999816,
"styleRecalcs": 9,
"styleRecalcDurationMs": 7.535,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 661.0400000000001,
"heapDeltaBytes": 6979840,
"heapUsedBytes": 65673796,
"domNodes": -275,
"jsHeapTotalBytes": 5046272,
"scriptDurationMs": 95.87399999999998,
"eventListeners": -144,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "large-graph-pan",
"durationMs": 2191.8440000000032,
"styleRecalcs": 69,
"styleRecalcDurationMs": 12.993,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 1267.371,
"heapDeltaBytes": -3798220,
"heapUsedBytes": 56062392,
"domNodes": -276,
"jsHeapTotalBytes": 5308416,
"scriptDurationMs": 416.805,
"eventListeners": -144,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66999999999998,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "large-graph-pan",
"durationMs": 2161.7389999998977,
"styleRecalcs": 69,
"styleRecalcDurationMs": 13.881,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 1243.349,
"heapDeltaBytes": 2001404,
"heapUsedBytes": 61767456,
"domNodes": -277,
"jsHeapTotalBytes": 4726784,
"scriptDurationMs": 389.247,
"eventListeners": -142,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "large-graph-zoom",
"durationMs": 3200.9479999999826,
"styleRecalcs": 66,
"styleRecalcDurationMs": 14.639999999999997,
"layouts": 60,
"layoutDurationMs": 7.354999999999999,
"taskDurationMs": 1424.2780000000002,
"heapDeltaBytes": -1760384,
"heapUsedBytes": 60027512,
"domNodes": -278,
"jsHeapTotalBytes": 7405568,
"scriptDurationMs": 476.894,
"eventListeners": -148,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-zoom",
"durationMs": 3184.970999999905,
"styleRecalcs": 65,
"styleRecalcDurationMs": 13.811999999999998,
"layouts": 60,
"layoutDurationMs": 7.138,
"taskDurationMs": 1444.667,
"heapDeltaBytes": -3528440,
"heapUsedBytes": 57866072,
"domNodes": -280,
"jsHeapTotalBytes": 7667712,
"scriptDurationMs": 481.399,
"eventListeners": -148,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "minimap-idle",
"durationMs": 2040.9259999999563,
"styleRecalcs": 9,
"styleRecalcDurationMs": 6.666999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 660.3990000000002,
"heapDeltaBytes": 6794852,
"heapUsedBytes": 67057024,
"domNodes": -274,
"jsHeapTotalBytes": 4521984,
"scriptDurationMs": 92.303,
"eventListeners": -144,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "minimap-idle",
"durationMs": 2027.924999999982,
"styleRecalcs": 8,
"styleRecalcDurationMs": 6.7150000000000025,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 652.9659999999999,
"heapDeltaBytes": 6998476,
"heapUsedBytes": 67820696,
"domNodes": -277,
"jsHeapTotalBytes": 4259840,
"scriptDurationMs": 93.15400000000001,
"eventListeners": -144,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 564.0339999999924,
"styleRecalcs": 48,
"styleRecalcDurationMs": 10.538,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 386.44100000000003,
"heapDeltaBytes": -12138436,
"heapUsedBytes": 55783008,
"domNodes": 22,
"jsHeapTotalBytes": 22151168,
"scriptDurationMs": 114.55600000000001,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666682,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 558.5419999999885,
"styleRecalcs": 45,
"styleRecalcDurationMs": 7.783000000000002,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 369.721,
"heapDeltaBytes": -12119856,
"heapUsedBytes": 55838196,
"domNodes": 16,
"jsHeapTotalBytes": 22675456,
"scriptDurationMs": 109.963,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666682,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-idle",
"durationMs": 2006.1719999999923,
"styleRecalcs": 9,
"styleRecalcDurationMs": 6.1640000000000015,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 435.851,
"heapDeltaBytes": 3445456,
"heapUsedBytes": 71494136,
"domNodes": 18,
"jsHeapTotalBytes": 20578304,
"scriptDurationMs": 17.369,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-idle",
"durationMs": 2034.0359999999578,
"styleRecalcs": 11,
"styleRecalcDurationMs": 7.542999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 483.20899999999995,
"heapDeltaBytes": -21693456,
"heapUsedBytes": 46254792,
"domNodes": -271,
"jsHeapTotalBytes": 20180992,
"scriptDurationMs": 15.477,
"eventListeners": -146,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1694.9179999999728,
"styleRecalcs": 76,
"styleRecalcDurationMs": 33.669000000000004,
"layouts": 16,
"layoutDurationMs": 3.6400000000000006,
"taskDurationMs": 730.051,
"heapDeltaBytes": -5565908,
"heapUsedBytes": 62436036,
"domNodes": 64,
"jsHeapTotalBytes": 20578304,
"scriptDurationMs": 79.888,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1729.7470000000317,
"styleRecalcs": 77,
"styleRecalcDurationMs": 31.230999999999998,
"layouts": 16,
"layoutDurationMs": 4.185,
"taskDurationMs": 765.207,
"heapDeltaBytes": -19953884,
"heapUsedBytes": 48047724,
"domNodes": -272,
"jsHeapTotalBytes": 20705280,
"scriptDurationMs": 75.332,
"eventListeners": -148,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66999999999998,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-transition-enter",
"durationMs": 1351.0220000000004,
"styleRecalcs": 19,
"styleRecalcDurationMs": 26.606000000000005,
"layouts": 15,
"layoutDurationMs": 10.926000000000002,
"taskDurationMs": 884.0789999999998,
"heapDeltaBytes": 9199100,
"heapUsedBytes": 102502756,
"domNodes": 13673,
"jsHeapTotalBytes": 10485760,
"scriptDurationMs": 33.965999999999994,
"eventListeners": 2371,
"totalBlockingTimeMs": 114,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "viewport-pan-sweep",
"durationMs": 8186.024999999972,
"styleRecalcs": 249,
"styleRecalcDurationMs": 35.391,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 4245.830000000001,
"heapDeltaBytes": 10753568,
"heapUsedBytes": 69472496,
"domNodes": -274,
"jsHeapTotalBytes": 6823936,
"scriptDurationMs": 1252.6139999999998,
"eventListeners": -128,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "viewport-pan-sweep",
"durationMs": 8150.525000000016,
"styleRecalcs": 249,
"styleRecalcDurationMs": 35.439,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 4253.650000000001,
"heapDeltaBytes": 1899708,
"heapUsedBytes": 60815560,
"domNodes": -277,
"jsHeapTotalBytes": 6881280,
"scriptDurationMs": 1263.514,
"eventListeners": -128,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333338,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-idle",
"durationMs": 15150.778000000002,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 15103.379,
"heapDeltaBytes": -57391692,
"heapUsedBytes": 167314212,
"domNodes": -8312,
"jsHeapTotalBytes": -9633792,
"scriptDurationMs": 552.995,
"eventListeners": -16389,
"totalBlockingTimeMs": 0,
"frameDurationMs": 17.77333333333336,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-idle",
"durationMs": 14904.665000000023,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 14880.841,
"heapDeltaBytes": -37628876,
"heapUsedBytes": 171583952,
"domNodes": -8312,
"jsHeapTotalBytes": -7540736,
"scriptDurationMs": 521.347,
"eventListeners": -16387,
"totalBlockingTimeMs": 0,
"frameDurationMs": 18.333333333333332,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-pan",
"durationMs": 18032.189000000017,
"styleRecalcs": 123,
"styleRecalcDurationMs": 13.977000000000018,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 18007.775999999998,
"heapDeltaBytes": -51264748,
"heapUsedBytes": 156787092,
"domNodes": -8317,
"jsHeapTotalBytes": -13307904,
"scriptDurationMs": 822.8420000000001,
"eventListeners": -16385,
"totalBlockingTimeMs": 72,
"frameDurationMs": 17.220000000000073,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-pan",
"durationMs": 18316.824999999993,
"styleRecalcs": 125,
"styleRecalcDurationMs": 14.433000000000002,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 18277.102,
"heapDeltaBytes": -65961532,
"heapUsedBytes": 153695636,
"domNodes": -8312,
"jsHeapTotalBytes": -13832192,
"scriptDurationMs": 866.139,
"eventListeners": -16381,
"totalBlockingTimeMs": 20,
"frameDurationMs": 17.780000000000047,
"p95FrameDurationMs": 16.80000000000291
},
{
"name": "workflow-execution",
"durationMs": 458.41400000000476,
"styleRecalcs": 15,
"styleRecalcDurationMs": 18.262,
"layouts": 3,
"layoutDurationMs": 0.71,
"taskDurationMs": 116.405,
"heapDeltaBytes": -15916916,
"heapUsedBytes": 51081348,
"domNodes": 132,
"jsHeapTotalBytes": 7995392,
"scriptDurationMs": 9.491000000000001,
"eventListeners": 65,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.663333333333338,
"p95FrameDurationMs": 16.700000000000273
},
{
"name": "workflow-execution",
"durationMs": 109.18000000003758,
"styleRecalcs": 7,
"styleRecalcDurationMs": 14.435,
"layouts": 2,
"layoutDurationMs": 1.174,
"taskDurationMs": 75.195,
"heapDeltaBytes": 2846092,
"heapUsedBytes": 69945140,
"domNodes": 125,
"jsHeapTotalBytes": 2621440,
"scriptDurationMs": 4.664999999999999,
"eventListeners": 17,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000273
}
]
} |
Codecov Report✅ All modified and coverable lines are covered by tests. @@ Coverage Diff @@
## core/1.48 #14610 +/- ##
=============================================
+ Coverage 79.33% 79.34% +0.01%
=============================================
Files 1746 1746
Lines 99377 99385 +8
Branches 31860 32616 +756
=============================================
+ Hits 78842 78859 +17
+ Misses 20133 20124 -9
Partials 402 402
Flags with carried forward coverage won't be shown. Click here to find out more.
... and 1 file with indirect coverage changes 🚀 New features to boost your workflow:
|
Manual backport of #14607 — the auto-backport workflow failed.
Cherry-pick of
10c56a1applied cleanly; no changes beyond the original PR.Why this is manual
The backport run (30839173649) hit a conflict on
core/1.47only. That failed the whole "Backport commits" step, which skipped "Create PR for each successful backport" for the targets that did apply cleanly, and the cleanup step then deleted their branches. Filed as a follow-up on the workflow.Original fix
After a Stripe-side cancellation, access is revoked but Plans & Pricing still showed the lost plan as a disabled "Current Plan", so the user could not resubscribe to it. An ended subscription still reports its
plan_slugandsubscription_tier, and the pricing table only treated a cancel-scheduled plan as re-buyable.subscription_status: 'ended'now counts as holding no current plan.Verification
Unit tests pass on this branch; CI covers the rest.