[backport core/1.48] fix(billing): resume a pending top-up as a top-up, not a subscription - #14722
Conversation
…#14715) ## Description Billing status can return an in-flight billing operation (`pending_billing_op_id` + `action_url`) so a client that lost its local reference can resume the payment — a reload, a cleared browser, a different device. The recovery path in `useWorkspaceBilling` assumed that operation was always a subscription and passed a hardcoded `'subscription'`. That assumption is a latent bug regardless of what the server sends today: any non-subscription operation surfaced there takes the wrong branch throughout. - `isSettingUp` turns true, so `SubscriptionPanelContentWorkspace` replaces the whole panel with a "setting up your subscription" spinner — hiding the user's actual plan for as long as the operation is pending - `topupActionOperation` requires `type === 'topup'`, so the top-up dialog's own recovery prompt never matches - `handleSuccess` runs `reconcileSubscriptionSuccess()` instead of `fetchStatus()` + `fetchBalance()`, so credits just purchased never appear - the toast and telemetry describe a subscription checkout Uses the operation type when the API reports one, falling back to `'subscription'` when it does not — so this is inert against a server that never sends the field, and correct the moment one does. **Status:** the server side does not populate a top-up here yet. This is the client half, landing first because it is safe in isolation; without it, a server that starts reporting top-ups would drive the wrong UI. Treat it as removing a hardcoded assumption, not as shipping a user-visible fix on its own. ## Checklist - [x] Test added — `recovers a pending top-up as a top-up, not a subscription`, confirmed to fail without the fix - [x] `pnpm test:unit` (72 passed in the touched suite), `pnpm typecheck`, `pnpm lint`, `pnpm format` ## Follow-up (not in this PR) `billingOperationStore` gives a top-up 120s to discover an authentication URL (`TIMEOUT_MS`) while a subscription gets 5min (`SUBSCRIPTION_ACTION_DISCOVERY_TIMEOUT_MS`). Only bites when the payment page is slow to appear, so it is kept separate.
📄 Knowledge reviewDosu skipped reviewing this PR because your organization has used its |
🎭 Playwright: ✅ 1765 passed, 0 failed · 2 flaky📊 Browser Reports
🎨 Storybook: ✅ Built — View Storybook📦 Bundle Size
⚡ Performance Report
Show regressions
All metrics
Historical variance (last 15 runs)
Trend (last 15 commits on main)
Raw data{
"timestamp": "2026-08-04T23:49:23.657Z",
"gitSha": "1db29348aad14c1aae5349b389cb97763e178818",
"branch": "backport-14715-to-core-1.48",
"measurements": [
{
"name": "canvas-idle",
"durationMs": 2032.656999999972,
"styleRecalcs": 10,
"styleRecalcDurationMs": 7.897000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 498.174,
"heapDeltaBytes": -22026168,
"heapUsedBytes": 46018144,
"domNodes": -270,
"jsHeapTotalBytes": 20443136,
"scriptDurationMs": 14.989999999999998,
"eventListeners": -148,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-idle",
"durationMs": 2040.7920000000104,
"styleRecalcs": 10,
"styleRecalcDurationMs": 8.160999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 451.19100000000003,
"heapDeltaBytes": 3866688,
"heapUsedBytes": 71903044,
"domNodes": 20,
"jsHeapTotalBytes": 20316160,
"scriptDurationMs": 14.874000000000002,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "canvas-mouse-sweep",
"durationMs": 2173.944000000006,
"styleRecalcs": 80,
"styleRecalcDurationMs": 47.08,
"layouts": 12,
"layoutDurationMs": 3.7889999999999997,
"taskDurationMs": 1120.5179999999998,
"heapDeltaBytes": -15890668,
"heapUsedBytes": 52305496,
"domNodes": -275,
"jsHeapTotalBytes": 20443136,
"scriptDurationMs": 149.792,
"eventListeners": -148,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-mouse-sweep",
"durationMs": 1798.8149999999905,
"styleRecalcs": 71,
"styleRecalcDurationMs": 33.152,
"layouts": 12,
"layoutDurationMs": 3.438,
"taskDurationMs": 855.5010000000001,
"heapDeltaBytes": -17026328,
"heapUsedBytes": 50913624,
"domNodes": -273,
"jsHeapTotalBytes": 21753856,
"scriptDurationMs": 106.86499999999998,
"eventListeners": -148,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1679.5960000000036,
"styleRecalcs": 31,
"styleRecalcDurationMs": 14.383999999999999,
"layouts": 6,
"layoutDurationMs": 0.576,
"taskDurationMs": 360.88399999999996,
"heapDeltaBytes": 7051988,
"heapUsedBytes": 75059968,
"domNodes": 76,
"jsHeapTotalBytes": 20316160,
"scriptDurationMs": 16.663999999999998,
"eventListeners": 19,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1737.9689999999073,
"styleRecalcs": 31,
"styleRecalcDurationMs": 16.653000000000002,
"layouts": 6,
"layoutDurationMs": 0.6579999999999999,
"taskDurationMs": 362.809,
"heapDeltaBytes": 7123516,
"heapUsedBytes": 75181896,
"domNodes": 78,
"jsHeapTotalBytes": 20316160,
"scriptDurationMs": 17.297,
"eventListeners": 19,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "dom-widget-clipping",
"durationMs": 687.6760000000104,
"styleRecalcs": 13,
"styleRecalcDurationMs": 8.579999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 407.476,
"heapDeltaBytes": -12697628,
"heapUsedBytes": 55314688,
"domNodes": 22,
"jsHeapTotalBytes": 21626880,
"scriptDurationMs": 61.73199999999999,
"eventListeners": 2,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "dom-widget-clipping",
"durationMs": 576.2670000000298,
"styleRecalcs": 12,
"styleRecalcDurationMs": 7.746999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 364.63,
"heapDeltaBytes": -12458228,
"heapUsedBytes": 55547872,
"domNodes": 20,
"jsHeapTotalBytes": 21364736,
"scriptDurationMs": 53.278,
"eventListeners": 0,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "large-graph-idle",
"durationMs": 2041.957000000025,
"styleRecalcs": 8,
"styleRecalcDurationMs": 6.752999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 662.886,
"heapDeltaBytes": 7464444,
"heapUsedBytes": 66595528,
"domNodes": -276,
"jsHeapTotalBytes": 5046272,
"scriptDurationMs": 93.633,
"eventListeners": -142,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66999999999998,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "large-graph-idle",
"durationMs": 2033.8269999999739,
"styleRecalcs": 8,
"styleRecalcDurationMs": 6.509000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 665.7850000000001,
"heapDeltaBytes": 7239540,
"heapUsedBytes": 66252804,
"domNodes": -276,
"jsHeapTotalBytes": 4521984,
"scriptDurationMs": 94.84400000000001,
"eventListeners": -142,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.670000000000012,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "large-graph-pan",
"durationMs": 2184.9109999999996,
"styleRecalcs": 69,
"styleRecalcDurationMs": 15.747000000000003,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 1280.424,
"heapDeltaBytes": 2646188,
"heapUsedBytes": 62452908,
"domNodes": -275,
"jsHeapTotalBytes": 5775360,
"scriptDurationMs": 422.057,
"eventListeners": -142,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "large-graph-pan",
"durationMs": 2143.58100000004,
"styleRecalcs": 69,
"styleRecalcDurationMs": 14.954000000000002,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 1192.184,
"heapDeltaBytes": 5485672,
"heapUsedBytes": 65460120,
"domNodes": -277,
"jsHeapTotalBytes": 5513216,
"scriptDurationMs": 394.31699999999995,
"eventListeners": -144,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "large-graph-zoom",
"durationMs": 3147.289999999998,
"styleRecalcs": 64,
"styleRecalcDurationMs": 14.107999999999999,
"layouts": 60,
"layoutDurationMs": 8.033,
"taskDurationMs": 1471.298,
"heapDeltaBytes": -5529636,
"heapUsedBytes": 56550128,
"domNodes": -283,
"jsHeapTotalBytes": 7929856,
"scriptDurationMs": 511.695,
"eventListeners": -148,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-zoom",
"durationMs": 3232.5490000000627,
"styleRecalcs": 65,
"styleRecalcDurationMs": 15.414000000000001,
"layouts": 60,
"layoutDurationMs": 7.96,
"taskDurationMs": 1481.633,
"heapDeltaBytes": 1231584,
"heapUsedBytes": 63164416,
"domNodes": -283,
"jsHeapTotalBytes": 7929856,
"scriptDurationMs": 517.984,
"eventListeners": -148,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "minimap-idle",
"durationMs": 2026.4940000000138,
"styleRecalcs": 9,
"styleRecalcDurationMs": 7.120000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 656.4669999999999,
"heapDeltaBytes": 6956444,
"heapUsedBytes": 67904308,
"domNodes": -277,
"jsHeapTotalBytes": 4784128,
"scriptDurationMs": 91.26799999999999,
"eventListeners": -144,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.670000000000012,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "minimap-idle",
"durationMs": 2039.5320000000083,
"styleRecalcs": 7,
"styleRecalcDurationMs": 5.572000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 660.334,
"heapDeltaBytes": 7330632,
"heapUsedBytes": 67923056,
"domNodes": -274,
"jsHeapTotalBytes": 4521984,
"scriptDurationMs": 91.11899999999999,
"eventListeners": -142,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 577.8339999999957,
"styleRecalcs": 47,
"styleRecalcDurationMs": 9.798,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 389.206,
"heapDeltaBytes": -12376992,
"heapUsedBytes": 55842432,
"domNodes": 20,
"jsHeapTotalBytes": 22151168,
"scriptDurationMs": 119.093,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 600.0689999999622,
"styleRecalcs": 47,
"styleRecalcDurationMs": 10.352,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 399.39000000000004,
"heapDeltaBytes": -12005672,
"heapUsedBytes": 55873372,
"domNodes": 20,
"jsHeapTotalBytes": 22413312,
"scriptDurationMs": 122.173,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-idle",
"durationMs": 2010.9460000000468,
"styleRecalcs": 8,
"styleRecalcDurationMs": 5.991000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 491.113,
"heapDeltaBytes": -21797668,
"heapUsedBytes": 46182808,
"domNodes": -276,
"jsHeapTotalBytes": 20443136,
"scriptDurationMs": 12.324,
"eventListeners": -148,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "subgraph-idle",
"durationMs": 2013.508999999999,
"styleRecalcs": 11,
"styleRecalcDurationMs": 8.270000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 474.967,
"heapDeltaBytes": 3833708,
"heapUsedBytes": 72068596,
"domNodes": 22,
"jsHeapTotalBytes": 20840448,
"scriptDurationMs": 12.285999999999998,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1713.2050000000163,
"styleRecalcs": 75,
"styleRecalcDurationMs": 33.351,
"layouts": 16,
"layoutDurationMs": 4.324,
"taskDurationMs": 765.548,
"heapDeltaBytes": -19063540,
"heapUsedBytes": 48927668,
"domNodes": -273,
"jsHeapTotalBytes": 21229568,
"scriptDurationMs": 86.149,
"eventListeners": -148,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1746.857000000091,
"styleRecalcs": 79,
"styleRecalcDurationMs": 35.301,
"layouts": 16,
"layoutDurationMs": 4.027,
"taskDurationMs": 778.9939999999999,
"heapDeltaBytes": -21305428,
"heapUsedBytes": 46797216,
"domNodes": -271,
"jsHeapTotalBytes": 21491712,
"scriptDurationMs": 82.16900000000001,
"eventListeners": -148,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-transition-enter",
"durationMs": 1401.9289999999955,
"styleRecalcs": 18,
"styleRecalcDurationMs": 28.876000000000005,
"layouts": 14,
"layoutDurationMs": 11.348,
"taskDurationMs": 892.9110000000003,
"heapDeltaBytes": 29125160,
"heapUsedBytes": 98241740,
"domNodes": 13673,
"jsHeapTotalBytes": 15990784,
"scriptDurationMs": 41.409,
"eventListeners": 2375,
"totalBlockingTimeMs": 118,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "viewport-pan-sweep",
"durationMs": 8181.47799999997,
"styleRecalcs": 250,
"styleRecalcDurationMs": 41.043000000000006,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 4311.638,
"heapDeltaBytes": 21662576,
"heapUsedBytes": 80547324,
"domNodes": -274,
"jsHeapTotalBytes": 6299648,
"scriptDurationMs": 1314.7920000000001,
"eventListeners": -158,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "viewport-pan-sweep",
"durationMs": 8190.067999999997,
"styleRecalcs": 248,
"styleRecalcDurationMs": 39.599999999999994,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 4237.112,
"heapDeltaBytes": 9632028,
"heapUsedBytes": 69670812,
"domNodes": -278,
"jsHeapTotalBytes": 7348224,
"scriptDurationMs": 1305.129,
"eventListeners": -128,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-idle",
"durationMs": 17406.588999999996,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 17377.178999999996,
"heapDeltaBytes": -53129128,
"heapUsedBytes": 165771176,
"domNodes": -8312,
"jsHeapTotalBytes": -11997184,
"scriptDurationMs": 528.906,
"eventListeners": -16387,
"totalBlockingTimeMs": 0,
"frameDurationMs": 17.776666666666642,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "vue-large-graph-idle",
"durationMs": 12928.500999999982,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 12913.009,
"heapDeltaBytes": -51117440,
"heapUsedBytes": 170827844,
"domNodes": -8312,
"jsHeapTotalBytes": -9900032,
"scriptDurationMs": 508.19499999999994,
"eventListeners": -16389,
"totalBlockingTimeMs": 0,
"frameDurationMs": 17.220000000000073,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-pan",
"durationMs": 21538.207,
"styleRecalcs": 151,
"styleRecalcDurationMs": 19.06299999999997,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 21485.06,
"heapDeltaBytes": -69179412,
"heapUsedBytes": 153834980,
"domNodes": -8312,
"jsHeapTotalBytes": -11993088,
"scriptDurationMs": 858.343,
"eventListeners": -16381,
"totalBlockingTimeMs": 312,
"frameDurationMs": 17.779999999999927,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-pan",
"durationMs": 20988.427000000003,
"styleRecalcs": 143,
"styleRecalcDurationMs": 18.687000000000037,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 20963.406,
"heapDeltaBytes": -51700648,
"heapUsedBytes": 166470760,
"domNodes": -8312,
"jsHeapTotalBytes": -14618624,
"scriptDurationMs": 832.9580000000001,
"eventListeners": -16381,
"totalBlockingTimeMs": 271,
"frameDurationMs": 17.776666666666642,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "workflow-execution",
"durationMs": 100.43999999993503,
"styleRecalcs": 9,
"styleRecalcDurationMs": 15.017000000000001,
"layouts": 2,
"layoutDurationMs": 1.1419999999999997,
"taskDurationMs": 75.465,
"heapDeltaBytes": 3104072,
"heapUsedBytes": 70160496,
"domNodes": 136,
"jsHeapTotalBytes": 2621440,
"scriptDurationMs": 6.14,
"eventListeners": 17,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "workflow-execution",
"durationMs": 96.69300000007297,
"styleRecalcs": 7,
"styleRecalcDurationMs": 12.956999999999999,
"layouts": 2,
"layoutDurationMs": 0.5659999999999998,
"taskDurationMs": 68.39800000000001,
"heapDeltaBytes": 2888184,
"heapUsedBytes": 70002892,
"domNodes": 106,
"jsHeapTotalBytes": 3145728,
"scriptDurationMs": 4.4559999999999995,
"eventListeners": 33,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999727
}
]
} |
Codecov Report✅ All modified and coverable lines are covered by tests. @@ Coverage Diff @@
## core/1.48 #14722 +/- ##
===========================================
Coverage 79.39% 79.39%
===========================================
Files 1749 1749
Lines 99604 99605 +1
Branches 31920 32671 +751
===========================================
+ Hits 79080 79081 +1
Misses 20124 20124
Partials 400 400
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
Backport of #14715 to
core/1.48Automatically created by backport workflow.