fix: recover legacy top-up state after returning from Stripe checkout - #14886
Conversation
Amp-Thread-ID: https://ampcode.com/threads/T-019fda6a-25f1-7305-bdd1-4e8c732834f3 Co-authored-by: Amp <amp@ampcode.com>
🎨 Storybook: ✅ Built — View Storybook🎭 Playwright: ✅ 1797 passed, 0 failed · 2 flaky📊 Browser Reports
📦 Bundle: 8.69 MB gzip 🔴 +290 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 — 565 kB (baseline 570 kB) • 🟢 -4.5 kBConfiguration panels, inspectors, and settings screens
Status: 10 added / 10 removed / 16 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) • ⚪ 0 BStores, services, APIs, and repositories
Status: 14 added / 14 removed / 3 unchanged Utilities & Hooks — 550 kB (baseline 550 kB) • ⚪ 0 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) • 🔴 +5.54 kBBundles that do not match a named category
Status: 70 added / 70 removed / 216 unchanged ⚡ Performance Report
Show regressions
All metrics
Historical variance (last 15 runs)
Trend (last 15 commits on main)
Raw data{
"timestamp": "2026-08-09T05:05:37.380Z",
"gitSha": "797202bc63254ced1e4bcfb6626eec1490f690b7",
"branch": "fix/payment-topup-refresh",
"measurements": [
{
"name": "canvas-idle",
"durationMs": 2016.9109999999364,
"styleRecalcs": 9,
"styleRecalcDurationMs": 7.213999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 496.043,
"heapDeltaBytes": 5981744,
"heapUsedBytes": 70616220,
"domNodes": 18,
"jsHeapTotalBytes": 24379392,
"scriptDurationMs": 16.755,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "canvas-idle",
"durationMs": 2034.7150000000056,
"styleRecalcs": 9,
"styleRecalcDurationMs": 8.944999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 535.7650000000001,
"heapDeltaBytes": 6000096,
"heapUsedBytes": 70259944,
"domNodes": 18,
"jsHeapTotalBytes": 24903680,
"scriptDurationMs": 22.319,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "canvas-mouse-sweep",
"durationMs": 2006.2619999999924,
"styleRecalcs": 79,
"styleRecalcDurationMs": 43.49,
"layouts": 12,
"layoutDurationMs": 4.044,
"taskDurationMs": 1017.8749999999999,
"heapDeltaBytes": -12706196,
"heapUsedBytes": 51535632,
"domNodes": -282,
"jsHeapTotalBytes": 24879104,
"scriptDurationMs": 143.323,
"eventListeners": -151,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-mouse-sweep",
"durationMs": 1952.7659999998832,
"styleRecalcs": 77,
"styleRecalcDurationMs": 45.89,
"layouts": 12,
"layoutDurationMs": 3.934,
"taskDurationMs": 979.5279999999999,
"heapDeltaBytes": -10801172,
"heapUsedBytes": 53818236,
"domNodes": -284,
"jsHeapTotalBytes": 24879104,
"scriptDurationMs": 126.616,
"eventListeners": -153,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1739.8250000001099,
"styleRecalcs": 33,
"styleRecalcDurationMs": 17.212000000000003,
"layouts": 6,
"layoutDurationMs": 0.644,
"taskDurationMs": 376.18,
"heapDeltaBytes": 8740560,
"heapUsedBytes": 73162472,
"domNodes": 79,
"jsHeapTotalBytes": 24379392,
"scriptDurationMs": 19.459000000000003,
"eventListeners": 19,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1753.537999999935,
"styleRecalcs": 32,
"styleRecalcDurationMs": 16.702,
"layouts": 6,
"layoutDurationMs": 0.7360000000000001,
"taskDurationMs": 376.58000000000004,
"heapDeltaBytes": 8570240,
"heapUsedBytes": 72994608,
"domNodes": 77,
"jsHeapTotalBytes": 24903680,
"scriptDurationMs": 19.336000000000002,
"eventListeners": 19,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "dom-widget-clipping",
"durationMs": 590.6120000000783,
"styleRecalcs": 11,
"styleRecalcDurationMs": 8.226,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 385.07899999999995,
"heapDeltaBytes": -11168532,
"heapUsedBytes": 53098024,
"domNodes": 18,
"jsHeapTotalBytes": 25427968,
"scriptDurationMs": 57.833000000000006,
"eventListeners": 0,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "dom-widget-clipping",
"durationMs": 607.9479999998512,
"styleRecalcs": 11,
"styleRecalcDurationMs": 8.305,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 396.393,
"heapDeltaBytes": -11218120,
"heapUsedBytes": 53214348,
"domNodes": 18,
"jsHeapTotalBytes": 24903680,
"scriptDurationMs": 60.995,
"eventListeners": 2,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "large-graph-idle",
"durationMs": 2089.3089999999575,
"styleRecalcs": 10,
"styleRecalcDurationMs": 9.936999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 721.6320000000001,
"heapDeltaBytes": 7550764,
"heapUsedBytes": 67013148,
"domNodes": -281,
"jsHeapTotalBytes": 3248128,
"scriptDurationMs": 102.48899999999999,
"eventListeners": -149,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-idle",
"durationMs": 2043.8970000000154,
"styleRecalcs": 8,
"styleRecalcDurationMs": 7.837999999999997,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 839.327,
"heapDeltaBytes": 7306812,
"heapUsedBytes": 67341320,
"domNodes": -283,
"jsHeapTotalBytes": 4034560,
"scriptDurationMs": 125.327,
"eventListeners": -147,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-pan",
"durationMs": 2160.2170000001024,
"styleRecalcs": 70,
"styleRecalcDurationMs": 15.792999999999997,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 1262.121,
"heapDeltaBytes": 3298872,
"heapUsedBytes": 65960060,
"domNodes": -281,
"jsHeapTotalBytes": 4489216,
"scriptDurationMs": 411.89500000000004,
"eventListeners": -147,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "large-graph-pan",
"durationMs": 2199.1199999999935,
"styleRecalcs": 68,
"styleRecalcDurationMs": 14.923000000000002,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 1347.1840000000002,
"heapDeltaBytes": 4778032,
"heapUsedBytes": 65402112,
"domNodes": -285,
"jsHeapTotalBytes": 3702784,
"scriptDurationMs": 431.856,
"eventListeners": -151,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "large-graph-zoom",
"durationMs": 3196.9740000000684,
"styleRecalcs": 65,
"styleRecalcDurationMs": 16.376,
"layouts": 60,
"layoutDurationMs": 8.319999999999999,
"taskDurationMs": 1537.9279999999999,
"heapDeltaBytes": -2102256,
"heapUsedBytes": 60119208,
"domNodes": -286,
"jsHeapTotalBytes": 6393856,
"scriptDurationMs": 528.6759999999999,
"eventListeners": -153,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "large-graph-zoom",
"durationMs": 3379.0019999999004,
"styleRecalcs": 65,
"styleRecalcDurationMs": 16.814000000000004,
"layouts": 60,
"layoutDurationMs": 8.706999999999999,
"taskDurationMs": 1676.594,
"heapDeltaBytes": 7572740,
"heapUsedBytes": 69935500,
"domNodes": -288,
"jsHeapTotalBytes": 5607424,
"scriptDurationMs": 584.9590000000001,
"eventListeners": -153,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "minimap-idle",
"durationMs": 2033.6170000000493,
"styleRecalcs": 8,
"styleRecalcDurationMs": 6.225000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 670.123,
"heapDeltaBytes": 6732936,
"heapUsedBytes": 67549404,
"domNodes": -286,
"jsHeapTotalBytes": 4558848,
"scriptDurationMs": 92.84699999999998,
"eventListeners": -149,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "minimap-idle",
"durationMs": 2013.3030000001781,
"styleRecalcs": 8,
"styleRecalcDurationMs": 7.140000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 671.646,
"heapDeltaBytes": 5995360,
"heapUsedBytes": 66518600,
"domNodes": -284,
"jsHeapTotalBytes": 4034560,
"scriptDurationMs": 95.23300000000002,
"eventListeners": -147,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 590.3209999999035,
"styleRecalcs": 47,
"styleRecalcDurationMs": 10.619,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 407.105,
"heapDeltaBytes": -10981616,
"heapUsedBytes": 53528216,
"domNodes": 20,
"jsHeapTotalBytes": 26738688,
"scriptDurationMs": 123.90199999999999,
"eventListeners": 8,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 606.7519999999149,
"styleRecalcs": 47,
"styleRecalcDurationMs": 11.539,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 427.3419999999999,
"heapDeltaBytes": -10409196,
"heapUsedBytes": 54144812,
"domNodes": 20,
"jsHeapTotalBytes": 25690112,
"scriptDurationMs": 125.34800000000001,
"eventListeners": 8,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-idle",
"durationMs": 2015.5019999999695,
"styleRecalcs": 10,
"styleRecalcDurationMs": 8.859,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 500.72499999999997,
"heapDeltaBytes": 5940336,
"heapUsedBytes": 70292444,
"domNodes": 20,
"jsHeapTotalBytes": 24903680,
"scriptDurationMs": 15.978,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "subgraph-idle",
"durationMs": 2030.1829999998517,
"styleRecalcs": 10,
"styleRecalcDurationMs": 8.848999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 530.1579999999999,
"heapDeltaBytes": -18518736,
"heapUsedBytes": 45926440,
"domNodes": -1,
"jsHeapTotalBytes": 23044096,
"scriptDurationMs": 16.669,
"eventListeners": -153,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1687.9849999999124,
"styleRecalcs": 75,
"styleRecalcDurationMs": 34.928,
"layouts": 16,
"layoutDurationMs": 4.382,
"taskDurationMs": 737.82,
"heapDeltaBytes": -3527704,
"heapUsedBytes": 60656348,
"domNodes": 61,
"jsHeapTotalBytes": 25427968,
"scriptDurationMs": 87.896,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1778.2540000000608,
"styleRecalcs": 79,
"styleRecalcDurationMs": 42.166999999999994,
"layouts": 16,
"layoutDurationMs": 5.038000000000001,
"taskDurationMs": 913.86,
"heapDeltaBytes": -17948480,
"heapUsedBytes": 46423128,
"domNodes": 0,
"jsHeapTotalBytes": 23830528,
"scriptDurationMs": 103.252,
"eventListeners": -153,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-transition-enter",
"durationMs": 1392.8480000001855,
"styleRecalcs": 18,
"styleRecalcDurationMs": 31.157000000000004,
"layouts": 14,
"layoutDurationMs": 12.073999999999998,
"taskDurationMs": 1037.113,
"heapDeltaBytes": 30671208,
"heapUsedBytes": 99822384,
"domNodes": 13673,
"jsHeapTotalBytes": 15990784,
"scriptDurationMs": 38.077,
"eventListeners": 2375,
"totalBlockingTimeMs": 135,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "viewport-pan-sweep",
"durationMs": 8227.919000000042,
"styleRecalcs": 250,
"styleRecalcDurationMs": 41.211,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 4338.2570000000005,
"heapDeltaBytes": 11797504,
"heapUsedBytes": 71152912,
"domNodes": -282,
"jsHeapTotalBytes": 5537792,
"scriptDurationMs": 1317.075,
"eventListeners": -133,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.80000000000109
},
{
"name": "viewport-pan-sweep",
"durationMs": 8393.162999999959,
"styleRecalcs": 248,
"styleRecalcDurationMs": 43.611999999999995,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 4822.1720000000005,
"heapDeltaBytes": 5554200,
"heapUsedBytes": 65035108,
"domNodes": -281,
"jsHeapTotalBytes": 5013504,
"scriptDurationMs": 1397.258,
"eventListeners": -163,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "vue-large-graph-idle",
"durationMs": 17931.286,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 17906.668,
"heapDeltaBytes": -37097472,
"heapUsedBytes": 167046064,
"domNodes": -8312,
"jsHeapTotalBytes": -8593408,
"scriptDurationMs": 574.736,
"eventListeners": -16387,
"totalBlockingTimeMs": 0,
"frameDurationMs": 18.330000000000048,
"p95FrameDurationMs": 16.80000000000291
},
{
"name": "vue-large-graph-idle",
"durationMs": 18225.081000000046,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 18203.970999999998,
"heapDeltaBytes": -30921280,
"heapUsedBytes": 175825916,
"domNodes": -8312,
"jsHeapTotalBytes": -6754304,
"scriptDurationMs": 598.4509999999999,
"eventListeners": -16387,
"totalBlockingTimeMs": 0,
"frameDurationMs": 17.776666666666642,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-pan",
"durationMs": 22491.22800000009,
"styleRecalcs": 168,
"styleRecalcDurationMs": 25.55000000000002,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 22459.314,
"heapDeltaBytes": -43529304,
"heapUsedBytes": 164338076,
"domNodes": -8312,
"jsHeapTotalBytes": -10133504,
"scriptDurationMs": 953.398,
"eventListeners": -16381,
"totalBlockingTimeMs": 670,
"frameDurationMs": 17.776666666666642,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "vue-large-graph-pan",
"durationMs": 22625.530000000028,
"styleRecalcs": 166,
"styleRecalcDurationMs": 24.453000000000003,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 22597.066,
"heapDeltaBytes": -27352188,
"heapUsedBytes": 179091496,
"domNodes": -8312,
"jsHeapTotalBytes": -7352320,
"scriptDurationMs": 947.8780000000002,
"eventListeners": -16381,
"totalBlockingTimeMs": 638,
"frameDurationMs": 17.779999999999927,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "workflow-execution",
"durationMs": 467.08300000000236,
"styleRecalcs": 12,
"styleRecalcDurationMs": 20.756999999999998,
"layouts": 3,
"layoutDurationMs": 0.709,
"taskDurationMs": 115.983,
"heapDeltaBytes": 5061220,
"heapUsedBytes": 68643560,
"domNodes": 124,
"jsHeapTotalBytes": 5242880,
"scriptDurationMs": 11.043000000000001,
"eventListeners": 99,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "workflow-execution",
"durationMs": 455.6450000000041,
"styleRecalcs": 16,
"styleRecalcDurationMs": 24.334,
"layouts": 4,
"layoutDurationMs": 1.315,
"taskDurationMs": 117.529,
"heapDeltaBytes": 5035688,
"heapUsedBytes": 68444580,
"domNodes": 125,
"jsHeapTotalBytes": 5242880,
"scriptDurationMs": 9.620999999999999,
"eventListeners": 97,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
}
]
} |
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThe top-up tracker preserves valid pending markers during refresh checks. ChangesTop-up refresh reconciliation
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant WindowFocus
participant CreditsTile
participant CustomerEvents
participant TopupTracker
WindowFocus->>CreditsTile: trigger refresh
CreditsTile->>TopupTracker: check pending marker
CreditsTile->>CustomerEvents: retrieve recent customer events
CustomerEvents-->>CreditsTile: return billing events
CreditsTile->>TopupTracker: check completed credit_added event
Possibly related PRs
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 |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/platform/telemetry/topupTracker.test.ts`:
- Around line 240-253: Update the marker-age tests around
pendingTopupNeedsRefresh to use Vitest fake timers and a fixed
vi.setSystemTime() before deriving timestamps. Restore real timers after each
test, following the pattern in docs/testing/vitest-patterns.md, while preserving
the existing fresh and expired marker assertions.
- Around line 232-258: The pendingTopupNeedsRefresh tests should verify
observable storage state rather than removeItem call counts. Replace the
stateless getItem mock with a stateful storage fake or real test storage, then
assert the fresh marker remains after repeated checks and the expired marker is
absent after checking; keep the no-marker behavior assertion.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: fbba9e3d-db0a-4aa2-9c4a-7e2307a590f8
📒 Files selected for processing (4)
src/platform/cloud/subscription/components/CreditsTile.test.tssrc/platform/cloud/subscription/components/CreditsTile.vuesrc/platform/telemetry/topupTracker.test.tssrc/platform/telemetry/topupTracker.ts
Codecov Report✅ All modified and coverable lines are covered by tests. @@ Coverage Diff @@
## main #14886 +/- ##
==========================================
+ Coverage 78.71% 78.78% +0.07%
==========================================
Files 1804 1869 +65
Lines 107023 113081 +6058
Branches 30683 33536 +2853
==========================================
+ Hits 84238 89095 +4857
- Misses 22330 23491 +1161
- Partials 455 495 +40
Flags with carried forward coverage won't be shown. Click here to find out more.
... and 494 files with indirect coverage changes 🚀 New features to boost your workflow:
|
Amp-Thread-ID: https://ampcode.com/threads/T-019fda6a-25f1-7305-bdd1-4e8c732834f3 Co-authored-by: Amp <amp@ampcode.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/platform/cloud/subscription/components/CreditsTile.vue`:
- Around line 424-427: Update the legacy customer-events path in the refresh
flow around getMyEvents and handleRefresh so a null failure response is not
passed to checkForCompletedTopup or treated as success. Make getMyEvents reject
or rethrow its request error, preserve successful response handling, and add a
test covering legacy request failure followed by retry.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: f1aee5c4-4071-4197-80c6-890c6b16ddcd
📒 Files selected for processing (4)
src/platform/cloud/subscription/components/CreditsTile.test.tssrc/platform/cloud/subscription/components/CreditsTile.vuesrc/platform/telemetry/topupTracker.test.tssrc/platform/telemetry/topupTracker.ts
Amp-Thread-ID: https://ampcode.com/threads/T-019fda6a-25f1-7305-bdd1-4e8c732834f3 Co-authored-by: Amp <amp@ampcode.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/composables/auth/useAuthActions.test.ts`:
- Around line 143-154: Add a failure-path test alongside the existing
`purchaseCreditsDirect` checkout test, configuring `initiateCreditPurchase` to
reject or return no `checkout_url`; assert that both `mockStartTopupTracking`
and the spied `window.open` remain uncalled, while preserving the existing
successful-flow assertions.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 46b7f5ff-bddc-42b3-8cda-5a072d30e67f
📒 Files selected for processing (1)
src/composables/auth/useAuthActions.test.ts
|
Scope clarification: This PR fixes only the legacy billing top-up recovery path ( The normal workspace/new billing top-up path is not changed; it already tracks completion using Current flows, API-contract differences, risks, and possible unification are documented here: |
Amp-Thread-ID: https://ampcode.com/threads/T-019fdd02-66c1-75de-9b05-396c583b4650 Co-authored-by: Amp <amp@ampcode.com>
christian-byrne
left a comment
There was a problem hiding this comment.
Reviewing this as the PAY-004 fix. Context for anyone landing it: Marwan confirmed the six Pylon tickets (#16322, #16286, #16285, #16284, #16280, #16274) are all this same bug, the only workaround is a manual refresh, and one of those customers is threatening a formal payment dispute. This PR has been sitting blocked for a day on a single CodeRabbit nit asking for one extra test. That should not be what holds it.
The refresh design looks right to me — preserving the marker across focus events instead of consuming it on first focus, reconciling against /customers/events before clearing, treating a failed event request as a failed refresh so a later focus retries, and serializing overlapping refreshes with one trailing refresh. That directly addresses "the user refreshed and it worked", which is what the tickets describe.
One thing I do want an answer on before this lands.
issue: this narrows top-up detection to the legacy rail and deletes the test that covered the other one.
topupTracker.ts previously matched both event types:
(e.event_type === 'credit_added' || e.event_type === 'topup_completed') &&with the comment "Legacy /customers/events emits credit_added; the unified /api/billing/events feed emits topup_completed." This PR drops the topup_completed arm, and also removes topupTracker.test.ts's "should detect completed topup for topup_completed (unified billing feed)" case rather than updating it.
checkForCompletedTopup is still reached from UsageLogsTable.vue:164 and CreditsTile.vue:430, and customerEventsService.ts:99 still treats topup_completed as a real event type, so the unified feed can still deliver that event to this code path. The PR body says the workspace/new flow is untouched because "workspace top-ups already track completion with billing_op_id polling" — that may well make the topup_completed arm redundant, but if so it is a deliberate behavioural removal and neither the diff nor the Changes list says so.
Two ways to resolve, either is fine by me:
- If the removal is deliberate, add it to the Changes list and keep a test asserting that
topup_completedis intentionally ignored here, so the next person does not "restore" it. Deleting the covering test leaves no record that the behaviour was ever considered. - If it is incidental, restore the
topup_completedarm and its test.
Given the rails coexist during the migration, I would rather not silently reduce detection coverage on the exact code path we are fixing because it under-detected.
nitpick (non-blocking): getPendingTopupTimestamp now uses Number() where the old code used parseInt(..., 10). That is stricter and I think better — Number('12abc') is NaN and gets cleaned up, where parseInt would have accepted it — but combined with Number.isSafeInteger it means any previously-tolerated malformed marker is now dropped rather than honoured. Worth a line in the PR body since it changes recovery behaviour for existing stored markers.
Not blocking on either point. Once the topup_completed question has an answer, this should go in — it is a customer-facing P0 with an active dispute risk attached.
…ure paths The narrowing to credit_added is deliberate (see 'scope top-up recovery to legacy billing'), but the existing exclusion test only used unrelated event types, so nothing recorded that topup_completed is skipped on purpose. A future reader would reasonably restore it. Also adds the direct-purchase failure paths CodeRabbit asked for: neither tracking nor the checkout window should start when the purchase request rejects or returns no checkout_url.
christian-byrne
left a comment
There was a problem hiding this comment.
Resolving my own review comment, and approving so this is not stuck over the weekend.
On the topup_completed question — it is deliberate, and I was missing the commit that says so. The head commit is literally "fix: scope top-up recovery to legacy billing". My concern was that the intent existed nowhere a future reader would find it: the removal is not in the PR body's Changes list, and the surviving exclusion test (should only match credit_added events, not other event types) only uses api_usage_completed and account_created, so nothing pinned the one event type that was deliberately dropped.
Since dante01yoon is out, I pushed the two small test additions rather than leave a P0 blocked on them (ce8a230):
topupTracker.test.ts— a case assertingtopup_completedis ignored, named for the reason (the unified rail tracks completion viabilling_op_idpolling). Verified red: re-adding thetopup_completedarm to the source makes it fail, so it genuinely pins the scope rather than passing incidentally.useAuthActions.test.ts— the direct-purchase failure paths CodeRabbit asked for: neitherstartTopupTrackingnorwindow.openfires wheninitiateCreditPurchaserejects or returns nocheckout_url.
No production code touched. pnpm typecheck exit 0, 39 tests pass across the two files.
One thing still worth a line in the PR body before merge, not blocking: getPendingTopupTimestamp moved from parseInt(..., 10) to Number() + Number.isSafeInteger. That is stricter and I think correct, but it changes recovery behaviour for markers already in users' localStorage — a value parseInt would have tolerated is now discarded.
Approving. For anyone landing this: it is the fix for the six Pylon tickets (#16322, #16286, #16285, #16284, #16280, #16274), one of which has a customer threatening a formal payment dispute. Tracking is now at FE-1540.
Scope note carried over from that ticket, for follow-up rather than for this PR: this recovery keys off a pending top-up marker and hangs off CreditsTile. The tickets describe subscription-active users, and the Run gate has no recovery path of its own — CloudRunButtonWrapper's focus refetch is gated behind payment-recovery, so it never fires for a plain status desync. That gap is FE-1540 and I am picking it up separately.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/platform/telemetry/topupTracker.test.ts`:
- Around line 193-197: The test around checkForCompletedTopup must also assert
that the pending_topup_timestamp marker remains in localStorage after processing
the topup_completed event. Add a behavioral localStorage assertion alongside the
existing result and telemetry assertions, preserving the current expectations.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 576c65ca-8ecb-4136-8bc1-e36d8c73af9f
📒 Files selected for processing (2)
src/composables/auth/useAuthActions.test.tssrc/platform/telemetry/topupTracker.test.ts
|
@coderabbitai approve |
✅ Action performedComments resolved and changes approved. |
|
|
Summary
PAY-004 — Successful legacy top-up not reflected until refresh.
Fixes the frontend race that can leave Run locked and the credit balance stale after a successful legacy Stripe top-up through
/customers/credit.This PR does not change the normal workspace/new billing top-up flow. Workspace top-ups already track completion with
billing_op_idpolling.Changes
pending_topup_timestampacross focus events instead of consuming it on the first focus./customers/eventsand clear the marker only after a latercredit_addedevent.Review focus and limitations
The marker controls refresh eligibility; the backend balance, subscription status, and
credit_addedevent remain the state sources.This PR intentionally does not add timed polling. If backend propagation is delayed beyond the return refresh, the marker remains and the next focus or manual refresh retries recovery. The broader legacy/workspace top-up and subscription comparison, API gaps, and possible recovery unification are documented in Notion.
Regression coverage
Screenshots
AS IS — production QA evidence: after a real $10 Visa top-up on cloud.comfy.org, the editor still shows Run locked because the balance and entitlement remain stale.
AS IS — deterministic pre-fix replay: an early focus consumes the pending marker before payment completion; the real checkout return then performs no second balance request.
TO BE — real Cloud editor with the fix: the later checkout return refreshes billing state, updates the credit balance, and enables Run.