[backport cloud/1.49] fix(billing): resume a pending top-up as a top-up, not a subscription - #14725
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: ✅ 1784 passed, 0 failed · 3 flaky📊 Browser Reports
🎨 Storybook: ✅ Built — View Storybook📦 Bundle Size
⚡ Performance Report
Absolute values
Raw data{
"timestamp": "2026-08-04T23:49:44.735Z",
"gitSha": "5547d0791d766e60de5c7745dc5bf87fc9f89029",
"branch": "backport-14715-to-cloud-1.49",
"measurements": [
{
"name": "canvas-idle",
"durationMs": 2037.5769999999989,
"styleRecalcs": 9,
"styleRecalcDurationMs": 7.390999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 492.70399999999995,
"heapDeltaBytes": 5264016,
"heapUsedBytes": 73515288,
"domNodes": 18,
"jsHeapTotalBytes": 20041728,
"scriptDurationMs": 15.481,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "canvas-idle",
"durationMs": 2044.3669999999656,
"styleRecalcs": 9,
"styleRecalcDurationMs": 6.6629999999999985,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 467.965,
"heapDeltaBytes": 5169108,
"heapUsedBytes": 73283376,
"domNodes": 18,
"jsHeapTotalBytes": 20041728,
"scriptDurationMs": 15.945,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-mouse-sweep",
"durationMs": 2228.815999999995,
"styleRecalcs": 86,
"styleRecalcDurationMs": 48.348,
"layouts": 12,
"layoutDurationMs": 5.244999999999999,
"taskDurationMs": 1126.952,
"heapDeltaBytes": -16672848,
"heapUsedBytes": 51639080,
"domNodes": -283,
"jsHeapTotalBytes": 20430848,
"scriptDurationMs": 137.641,
"eventListeners": -151,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "canvas-mouse-sweep",
"durationMs": 1875.978000000032,
"styleRecalcs": 76,
"styleRecalcDurationMs": 38.061,
"layouts": 12,
"layoutDurationMs": 3.9889999999999994,
"taskDurationMs": 896.496,
"heapDeltaBytes": -17429416,
"heapUsedBytes": 50895896,
"domNodes": -280,
"jsHeapTotalBytes": 21217280,
"scriptDurationMs": 119.35099999999998,
"eventListeners": -153,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1721.7890000000011,
"styleRecalcs": 31,
"styleRecalcDurationMs": 14.847999999999999,
"layouts": 6,
"layoutDurationMs": 0.576,
"taskDurationMs": 362.445,
"heapDeltaBytes": 8109356,
"heapUsedBytes": 76253800,
"domNodes": 77,
"jsHeapTotalBytes": 20303872,
"scriptDurationMs": 17.553,
"eventListeners": 19,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1760.99099999999,
"styleRecalcs": 30,
"styleRecalcDurationMs": 16.663999999999998,
"layouts": 6,
"layoutDurationMs": 0.77,
"taskDurationMs": 375.47999999999996,
"heapDeltaBytes": 7826212,
"heapUsedBytes": 75945908,
"domNodes": 77,
"jsHeapTotalBytes": 20303872,
"scriptDurationMs": 19.533999999999995,
"eventListeners": 19,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "dom-widget-clipping",
"durationMs": 629.5080000000439,
"styleRecalcs": 11,
"styleRecalcDurationMs": 7.092,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 380.10699999999997,
"heapDeltaBytes": -12139464,
"heapUsedBytes": 55852896,
"domNodes": 18,
"jsHeapTotalBytes": 21090304,
"scriptDurationMs": 57.372,
"eventListeners": 2,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "dom-widget-clipping",
"durationMs": 606.7050000000336,
"styleRecalcs": 11,
"styleRecalcDurationMs": 7.535,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 387.304,
"heapDeltaBytes": -12562540,
"heapUsedBytes": 55558528,
"domNodes": 18,
"jsHeapTotalBytes": 21090304,
"scriptDurationMs": 60.723000000000006,
"eventListeners": 0,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "large-graph-idle",
"durationMs": 2039.414000000022,
"styleRecalcs": 9,
"styleRecalcDurationMs": 7.07,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 652.865,
"heapDeltaBytes": 8523256,
"heapUsedBytes": 67617732,
"domNodes": -282,
"jsHeapTotalBytes": 5300224,
"scriptDurationMs": 87.542,
"eventListeners": -147,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "large-graph-idle",
"durationMs": 2044.112000000041,
"styleRecalcs": 9,
"styleRecalcDurationMs": 7.678000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 692.3640000000001,
"heapDeltaBytes": 8267596,
"heapUsedBytes": 67604708,
"domNodes": -282,
"jsHeapTotalBytes": 5038080,
"scriptDurationMs": 97.582,
"eventListeners": -149,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-pan",
"durationMs": 2161.5899999999897,
"styleRecalcs": 69,
"styleRecalcDurationMs": 15.272000000000004,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 1237.0149999999999,
"heapDeltaBytes": 5417324,
"heapUsedBytes": 65639892,
"domNodes": -284,
"jsHeapTotalBytes": 5525504,
"scriptDurationMs": 400.03499999999997,
"eventListeners": -147,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-pan",
"durationMs": 2168.877000000066,
"styleRecalcs": 68,
"styleRecalcDurationMs": 14.837000000000003,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 1246.324,
"heapDeltaBytes": 5909800,
"heapUsedBytes": 66048004,
"domNodes": -284,
"jsHeapTotalBytes": 4214784,
"scriptDurationMs": 406.205,
"eventListeners": -149,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "large-graph-zoom",
"durationMs": 3142.5330000000145,
"styleRecalcs": 64,
"styleRecalcDurationMs": 14.831000000000001,
"layouts": 60,
"layoutDurationMs": 8.102000000000002,
"taskDurationMs": 1422.483,
"heapDeltaBytes": 23779880,
"heapUsedBytes": 85639864,
"domNodes": 10,
"jsHeapTotalBytes": 7340032,
"scriptDurationMs": 514.435,
"eventListeners": 8,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-zoom",
"durationMs": 3231.194000000073,
"styleRecalcs": 65,
"styleRecalcDurationMs": 15.620999999999995,
"layouts": 60,
"layoutDurationMs": 8.328,
"taskDurationMs": 1539.5330000000001,
"heapDeltaBytes": -188484,
"heapUsedBytes": 61897612,
"domNodes": -288,
"jsHeapTotalBytes": 7397376,
"scriptDurationMs": 533.591,
"eventListeners": -153,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "minimap-idle",
"durationMs": 2018.9609999999902,
"styleRecalcs": 7,
"styleRecalcDurationMs": 5.696,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 663.703,
"heapDeltaBytes": 6042404,
"heapUsedBytes": 67138588,
"domNodes": -284,
"jsHeapTotalBytes": 5562368,
"scriptDurationMs": 87.89999999999999,
"eventListeners": -179,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "minimap-idle",
"durationMs": 2031.0080000000426,
"styleRecalcs": 5,
"styleRecalcDurationMs": 3.9879999999999987,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 650.848,
"heapDeltaBytes": 8030808,
"heapUsedBytes": 69677660,
"domNodes": -286,
"jsHeapTotalBytes": 5300224,
"scriptDurationMs": 91.06400000000001,
"eventListeners": -149,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 600.4090000000133,
"styleRecalcs": 47,
"styleRecalcDurationMs": 10.463,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 396.4360000000001,
"heapDeltaBytes": -11949764,
"heapUsedBytes": 56283284,
"domNodes": 20,
"jsHeapTotalBytes": 22401024,
"scriptDurationMs": 119.998,
"eventListeners": 8,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.670000000000012,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 588.9250000000175,
"styleRecalcs": 45,
"styleRecalcDurationMs": 9.041,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 402.51099999999997,
"heapDeltaBytes": -12267876,
"heapUsedBytes": 56056960,
"domNodes": 16,
"jsHeapTotalBytes": 22663168,
"scriptDurationMs": 123.50199999999998,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-idle",
"durationMs": 2009.0119999999843,
"styleRecalcs": 11,
"styleRecalcDurationMs": 8.488000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 454.014,
"heapDeltaBytes": 4937832,
"heapUsedBytes": 73030920,
"domNodes": 22,
"jsHeapTotalBytes": 20303872,
"scriptDurationMs": 12.546,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-idle",
"durationMs": 2009.0529999999944,
"styleRecalcs": 10,
"styleRecalcDurationMs": 8.351,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 457.77799999999996,
"heapDeltaBytes": 4871344,
"heapUsedBytes": 73061480,
"domNodes": 20,
"jsHeapTotalBytes": 20566016,
"scriptDurationMs": 13.234000000000002,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1689.7920000000113,
"styleRecalcs": 75,
"styleRecalcDurationMs": 35.547,
"layouts": 16,
"layoutDurationMs": 4.619,
"taskDurationMs": 726.573,
"heapDeltaBytes": -4862060,
"heapUsedBytes": 63584280,
"domNodes": 61,
"jsHeapTotalBytes": 21352448,
"scriptDurationMs": 89.87,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1758.999000000017,
"styleRecalcs": 76,
"styleRecalcDurationMs": 37.009,
"layouts": 16,
"layoutDurationMs": 4.617,
"taskDurationMs": 808.408,
"heapDeltaBytes": -20659556,
"heapUsedBytes": 47645956,
"domNodes": -279,
"jsHeapTotalBytes": 20430848,
"scriptDurationMs": 96.101,
"eventListeners": -153,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-transition-enter",
"durationMs": 1384.4010000000253,
"styleRecalcs": 17,
"styleRecalcDurationMs": 28.429999999999996,
"layouts": 13,
"layoutDurationMs": 11.159000000000002,
"taskDurationMs": 903.1089999999999,
"heapDeltaBytes": 30972972,
"heapUsedBytes": 99622972,
"domNodes": 13673,
"jsHeapTotalBytes": 15204352,
"scriptDurationMs": 32.562999999999995,
"eventListeners": 2375,
"totalBlockingTimeMs": 119,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "viewport-pan-sweep",
"durationMs": 8248.891000000014,
"styleRecalcs": 249,
"styleRecalcDurationMs": 40.881,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 4340.932000000001,
"heapDeltaBytes": 13229496,
"heapUsedBytes": 72720568,
"domNodes": -283,
"jsHeapTotalBytes": 8146944,
"scriptDurationMs": 1356.595,
"eventListeners": -133,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "viewport-pan-sweep",
"durationMs": 8223.463000000038,
"styleRecalcs": 249,
"styleRecalcDurationMs": 41.675000000000004,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 4370.436000000001,
"heapDeltaBytes": 12484380,
"heapUsedBytes": 71843356,
"domNodes": -284,
"jsHeapTotalBytes": 6836224,
"scriptDurationMs": 1338.368,
"eventListeners": -133,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-idle",
"durationMs": 17657.942000000046,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 17631.299,
"heapDeltaBytes": -57090800,
"heapUsedBytes": 167400552,
"domNodes": -8312,
"jsHeapTotalBytes": -12521472,
"scriptDurationMs": 533.462,
"eventListeners": -16387,
"totalBlockingTimeMs": 0,
"frameDurationMs": 17.776666666666642,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-idle",
"durationMs": 17422.593000000008,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 17391.472999999998,
"heapDeltaBytes": -43705760,
"heapUsedBytes": 167729076,
"domNodes": -8312,
"jsHeapTotalBytes": -7802880,
"scriptDurationMs": 530.51,
"eventListeners": -16387,
"totalBlockingTimeMs": 0,
"frameDurationMs": 17.780000000000047,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-pan",
"durationMs": 21743.305000000022,
"styleRecalcs": 152,
"styleRecalcDurationMs": 19.874999999999975,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 21699.866,
"heapDeltaBytes": -49368412,
"heapUsedBytes": 177295584,
"domNodes": -8341,
"jsHeapTotalBytes": -8134656,
"scriptDurationMs": 915.293,
"eventListeners": -16381,
"totalBlockingTimeMs": 340,
"frameDurationMs": 18.333333333333332,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-pan",
"durationMs": 21917.587000000025,
"styleRecalcs": 157,
"styleRecalcDurationMs": 20.82700000000004,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 21882.907,
"heapDeltaBytes": -65086840,
"heapUsedBytes": 156405232,
"domNodes": -8312,
"jsHeapTotalBytes": -9375744,
"scriptDurationMs": 895.7109999999999,
"eventListeners": -16383,
"totalBlockingTimeMs": 489,
"frameDurationMs": 17.780000000000047,
"p95FrameDurationMs": 16.80000000000291
},
{
"name": "workflow-execution",
"durationMs": 464.8530000000619,
"styleRecalcs": 14,
"styleRecalcDurationMs": 20.816000000000003,
"layouts": 3,
"layoutDurationMs": 0.672,
"taskDurationMs": 125.73,
"heapDeltaBytes": -15274312,
"heapUsedBytes": 52060192,
"domNodes": 134,
"jsHeapTotalBytes": 7458816,
"scriptDurationMs": 9.721,
"eventListeners": 99,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "workflow-execution",
"durationMs": 471.0129999999708,
"styleRecalcs": 17,
"styleRecalcDurationMs": 20.332000000000004,
"layouts": 3,
"layoutDurationMs": 1.117,
"taskDurationMs": 122.882,
"heapDeltaBytes": -15953048,
"heapUsedBytes": 51518388,
"domNodes": 147,
"jsHeapTotalBytes": 7458816,
"scriptDurationMs": 9.748999999999999,
"eventListeners": 97,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666682,
"p95FrameDurationMs": 16.700000000000728
}
]
} |
Codecov Report✅ All modified and coverable lines are covered by tests. @@ Coverage Diff @@
## cloud/1.49 #14725 +/- ##
=============================================
Coverage ? 78.60%
=============================================
Files ? 1789
Lines ? 104423
Branches ? 32024
=============================================
Hits ? 82078
Misses ? 21915
Partials ? 430
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
cloud/1.49Automatically created by backport workflow.