fix(billing): keep top-up working for local users on the free tier - #14589
fix(billing): keep top-up working for local users on the free tier#14589christian-byrne wants to merge 3 commits into
Conversation
On a non-cloud build `isActiveSubscription` is hardwired true (`isSubscribedOrIsNotCloud`), so `isFreeTier` is the only live term in the top-up subscription gate. Nothing fetches subscription status on a local boot until Settings > Credits mounts CreditsTile, whose `onMounted(handleRefresh)` sets tier=FREE and latches `isFreeTier` true for the shared billing context. From then on `showTopUpCreditsDialog` routes into `showSubscriptionRequiredDialog`, which returns immediately when `!isCloud` — the purchase flow silently dies until app restart. Gate that diversion on `isCloud`, and gate the cloud-only upgrade/pricing CTAs so a local user is never offered a dialog that cannot open. E2E never caught this: `ComfyPage` only mocked auth for `@cloud`-tagged tests, and `@cloud` routes a spec to the cloud project (cloud bundle, `isCloud === true`). Signed-in credits coverage was therefore impossible on the localhost bundle. Add an `@auth` tag so auth mocking is independent of project routing, plus an `@oss @auth` spec covering the reported journey.
Third subscribe surface in the same component; its handler bottoms out in showPricingTable, which returns on !isCloud. The legacy popover already gates the equivalent block, so this brings the two to parity.
🎭 Playwright: ✅ 1781 passed, 0 failed · 1 flaky📊 Browser Reports
🎨 Storybook: ✅ Built — View Storybook📦 Bundle: 8.21 MB gzip 🟢 -28 BDetailsSummary
Category Glance App Entry Points — 3.64 kB (baseline 3.64 kB) • ⚪ 0 BMain entry bundles and manifests
Status: 1 added / 1 removed Graph Workspace — 1.29 MB (baseline 1.29 MB) • ⚪ 0 BGraph editor runtime, canvas, workflow orchestration
Status: 1 added / 1 removed / 1 unchanged Views & Navigation — 111 kB (baseline 111 kB) • ⚪ 0 BTop-level views, pages, and routed surfaces
Status: 12 added / 12 removed / 4 unchanged Panels & Settings — 551 kB (baseline 551 kB) • ⚪ 0 BConfiguration panels, inspectors, and settings screens
Status: 11 added / 11 removed / 15 unchanged User & Accounts — 28.7 kB (baseline 28.7 kB) • ⚪ 0 BAuthentication, profile, and account management bundles
Status: 7 added / 7 removed / 3 unchanged Editors & Dialogs — 124 kB (baseline 124 kB) • ⚪ 0 BModals, dialogs, drawers, and in-app editors
Status: 6 added / 6 removed / 1 unchanged UI Components — 70 kB (baseline 70 kB) • ⚪ 0 BReusable component library chunks
Status: 6 added / 6 removed / 9 unchanged Data & Services — 3.45 MB (baseline 3.45 MB) • 🟢 -195 BStores, services, APIs, and repositories
Status: 14 added / 14 removed / 3 unchanged Utilities & Hooks — 386 kB (baseline 386 kB) • ⚪ 0 BHelpers, composables, and utility bundles
Status: 16 added / 16 removed / 20 unchanged Vendor & Third-Party — 15.7 MB (baseline 15.7 MB) • ⚪ 0 BExternal libraries and shared vendor chunks Status: 16 unchanged Other — 12.7 MB (baseline 12.8 MB) • 🟢 -45 BBundles that do not match a named category
Status: 70 added / 70 removed / 211 unchanged ⚡ Performance Report
Show regressions
All metrics
Historical variance (last 15 runs)
Trend (last 15 commits on main)
Raw data{
"timestamp": "2026-08-03T02:36:34.130Z",
"gitSha": "8c56445bdf752757fac7bb126b90d6edc6a83853",
"branch": "glary/fix-local-credits-upgrade-gating",
"measurements": [
{
"name": "canvas-idle",
"durationMs": 2174.7199999999793,
"styleRecalcs": 6,
"styleRecalcDurationMs": 9.325999999999997,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 665.3090000000001,
"heapDeltaBytes": -22360472,
"heapUsedBytes": 45638880,
"domNodes": 12,
"jsHeapTotalBytes": 20176896,
"scriptDurationMs": 24.314000000000004,
"eventListeners": -148,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "canvas-idle",
"durationMs": 2035.88400000001,
"styleRecalcs": 9,
"styleRecalcDurationMs": 9.156,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 584.1370000000001,
"heapDeltaBytes": 4790864,
"heapUsedBytes": 72891260,
"domNodes": 18,
"jsHeapTotalBytes": 20312064,
"scriptDurationMs": 25.523999999999997,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "canvas-mouse-sweep",
"durationMs": 1874.1929999999911,
"styleRecalcs": 75,
"styleRecalcDurationMs": 36.696000000000005,
"layouts": 12,
"layoutDurationMs": 3.197,
"taskDurationMs": 904.7099999999999,
"heapDeltaBytes": -16225348,
"heapUsedBytes": 51689232,
"domNodes": -280,
"jsHeapTotalBytes": 20963328,
"scriptDurationMs": 127.004,
"eventListeners": -148,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "canvas-mouse-sweep",
"durationMs": 1942.56299999995,
"styleRecalcs": 77,
"styleRecalcDurationMs": 44.134,
"layouts": 12,
"layoutDurationMs": 3.9009999999999994,
"taskDurationMs": 996.47,
"heapDeltaBytes": -20331524,
"heapUsedBytes": 47525992,
"domNodes": -278,
"jsHeapTotalBytes": 19652608,
"scriptDurationMs": 140.15800000000002,
"eventListeners": -148,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1772.7179999999976,
"styleRecalcs": 33,
"styleRecalcDurationMs": 21.424,
"layouts": 6,
"layoutDurationMs": 0.8150000000000002,
"taskDurationMs": 454.90200000000004,
"heapDeltaBytes": 7885708,
"heapUsedBytes": 75868768,
"domNodes": 79,
"jsHeapTotalBytes": 20049920,
"scriptDurationMs": 28.769999999999996,
"eventListeners": 19,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1755.9610000000703,
"styleRecalcs": 32,
"styleRecalcDurationMs": 19.299000000000003,
"layouts": 6,
"layoutDurationMs": 0.593,
"taskDurationMs": 459.68899999999996,
"heapDeltaBytes": 7906248,
"heapUsedBytes": 75830520,
"domNodes": 78,
"jsHeapTotalBytes": 20312064,
"scriptDurationMs": 28.128000000000004,
"eventListeners": 19,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "dom-widget-clipping",
"durationMs": 595.8459999999945,
"styleRecalcs": 12,
"styleRecalcDurationMs": 7.853999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 390.36499999999995,
"heapDeltaBytes": -12270068,
"heapUsedBytes": 55593972,
"domNodes": 20,
"jsHeapTotalBytes": 21884928,
"scriptDurationMs": 60.342,
"eventListeners": 0,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "dom-widget-clipping",
"durationMs": 633.0809999999474,
"styleRecalcs": 12,
"styleRecalcDurationMs": 9.012,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 410.13899999999995,
"heapDeltaBytes": -11767492,
"heapUsedBytes": 56156080,
"domNodes": 20,
"jsHeapTotalBytes": 20574208,
"scriptDurationMs": 62.312999999999995,
"eventListeners": 2,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-idle",
"durationMs": 2058.447000000001,
"styleRecalcs": 9,
"styleRecalcDurationMs": 8.538999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 752.3280000000001,
"heapDeltaBytes": 8399604,
"heapUsedBytes": 67414328,
"domNodes": -281,
"jsHeapTotalBytes": 5304320,
"scriptDurationMs": 125.82099999999998,
"eventListeners": -144,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "large-graph-idle",
"durationMs": 2034.0629999999464,
"styleRecalcs": 6,
"styleRecalcDurationMs": 6.936000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 833.324,
"heapDeltaBytes": 8163828,
"heapUsedBytes": 68233832,
"domNodes": -284,
"jsHeapTotalBytes": 3469312,
"scriptDurationMs": 128.48100000000002,
"eventListeners": -144,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.670000000000012,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "large-graph-pan",
"durationMs": 2221.5759999999705,
"styleRecalcs": 68,
"styleRecalcDurationMs": 13.11,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 1376.571,
"heapDeltaBytes": 7864152,
"heapUsedBytes": 68240472,
"domNodes": -284,
"jsHeapTotalBytes": 4481024,
"scriptDurationMs": 453.092,
"eventListeners": -142,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-pan",
"durationMs": 2343.4439999999768,
"styleRecalcs": 67,
"styleRecalcDurationMs": 13.562999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 1474.079,
"heapDeltaBytes": 8264716,
"heapUsedBytes": 68705416,
"domNodes": -286,
"jsHeapTotalBytes": 5267456,
"scriptDurationMs": 486.07399999999996,
"eventListeners": -144,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.670000000000012,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "large-graph-zoom",
"durationMs": 3330.0540000000183,
"styleRecalcs": 64,
"styleRecalcDurationMs": 14.626000000000003,
"layouts": 60,
"layoutDurationMs": 7.695,
"taskDurationMs": 1622.8170000000002,
"heapDeltaBytes": 6136072,
"heapUsedBytes": 67539396,
"domNodes": -289,
"jsHeapTotalBytes": 8450048,
"scriptDurationMs": 568.727,
"eventListeners": -148,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.670000000000012,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "large-graph-zoom",
"durationMs": 3410.7740000000604,
"styleRecalcs": 64,
"styleRecalcDurationMs": 14.982000000000003,
"layouts": 60,
"layoutDurationMs": 7.743999999999999,
"taskDurationMs": 1688.698,
"heapDeltaBytes": 8951744,
"heapUsedBytes": 70440352,
"domNodes": -288,
"jsHeapTotalBytes": 6352896,
"scriptDurationMs": 592.119,
"eventListeners": -146,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "minimap-idle",
"durationMs": 2050.3510000000347,
"styleRecalcs": 8,
"styleRecalcDurationMs": 9.564000000000004,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 883.1150000000001,
"heapDeltaBytes": 8426540,
"heapUsedBytes": 69321072,
"domNodes": -283,
"jsHeapTotalBytes": 4517888,
"scriptDurationMs": 134.29399999999998,
"eventListeners": -146,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.670000000000012,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "minimap-idle",
"durationMs": 2044.5640000000367,
"styleRecalcs": 7,
"styleRecalcDurationMs": 7.266000000000002,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 808.156,
"heapDeltaBytes": 7442708,
"heapUsedBytes": 68298784,
"domNodes": -283,
"jsHeapTotalBytes": 3469312,
"scriptDurationMs": 132.56400000000002,
"eventListeners": -144,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 587.8940000000057,
"styleRecalcs": 44,
"styleRecalcDurationMs": 8.805,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 405.958,
"heapDeltaBytes": -11540732,
"heapUsedBytes": 56591216,
"domNodes": 14,
"jsHeapTotalBytes": 21622784,
"scriptDurationMs": 120.975,
"eventListeners": 8,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 645.719999999983,
"styleRecalcs": 46,
"styleRecalcDurationMs": 10.463999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 427.84,
"heapDeltaBytes": -12046492,
"heapUsedBytes": 55932364,
"domNodes": 18,
"jsHeapTotalBytes": 22933504,
"scriptDurationMs": 123.05800000000002,
"eventListeners": 8,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "subgraph-idle",
"durationMs": 2021.8699999999785,
"styleRecalcs": 9,
"styleRecalcDurationMs": 10.873000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 530.5139999999999,
"heapDeltaBytes": 4471520,
"heapUsedBytes": 72464040,
"domNodes": 18,
"jsHeapTotalBytes": 20836352,
"scriptDurationMs": 19.577000000000005,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-idle",
"durationMs": 2009.445000000028,
"styleRecalcs": 8,
"styleRecalcDurationMs": 8.93,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 579.596,
"heapDeltaBytes": 4406172,
"heapUsedBytes": 72300456,
"domNodes": 16,
"jsHeapTotalBytes": 21360640,
"scriptDurationMs": 21.934,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1756.6700000000424,
"styleRecalcs": 75,
"styleRecalcDurationMs": 39.409,
"layouts": 16,
"layoutDurationMs": 4.922000000000001,
"taskDurationMs": 879.962,
"heapDeltaBytes": -19669468,
"heapUsedBytes": 48197416,
"domNodes": -280,
"jsHeapTotalBytes": 20701184,
"scriptDurationMs": 100.57199999999999,
"eventListeners": -148,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66999999999998,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1739.5860000000312,
"styleRecalcs": 77,
"styleRecalcDurationMs": 43.88,
"layouts": 16,
"layoutDurationMs": 5.6240000000000006,
"taskDurationMs": 877.854,
"heapDeltaBytes": -4596112,
"heapUsedBytes": 63437964,
"domNodes": 64,
"jsHeapTotalBytes": 21360640,
"scriptDurationMs": 104.87100000000001,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-transition-enter",
"durationMs": 1432.5000000000045,
"styleRecalcs": 19,
"styleRecalcDurationMs": 34.057,
"layouts": 15,
"layoutDurationMs": 14.060999999999996,
"taskDurationMs": 1061.8809999999999,
"heapDeltaBytes": 29169808,
"heapUsedBytes": 98348128,
"domNodes": 13673,
"jsHeapTotalBytes": 16252928,
"scriptDurationMs": 51.47299999999999,
"eventListeners": 2371,
"totalBlockingTimeMs": 153,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "viewport-pan-sweep",
"durationMs": 8507.143999999982,
"styleRecalcs": 248,
"styleRecalcDurationMs": 36.818999999999996,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 4830.084,
"heapDeltaBytes": 12732476,
"heapUsedBytes": 72185460,
"domNodes": -282,
"jsHeapTotalBytes": 7331840,
"scriptDurationMs": 1485.5320000000002,
"eventListeners": -126,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "viewport-pan-sweep",
"durationMs": 8676.871000000006,
"styleRecalcs": 248,
"styleRecalcDurationMs": 37.024,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 4899.4400000000005,
"heapDeltaBytes": 12615248,
"heapUsedBytes": 71973916,
"domNodes": -282,
"jsHeapTotalBytes": 7069696,
"scriptDurationMs": 1491.315,
"eventListeners": -126,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-idle",
"durationMs": 17988.49100000001,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 17957.678,
"heapDeltaBytes": -39324364,
"heapUsedBytes": 165842860,
"domNodes": -8312,
"jsHeapTotalBytes": -15405056,
"scriptDurationMs": 631.6120000000001,
"eventListeners": -16391,
"totalBlockingTimeMs": 0,
"frameDurationMs": 17.776666666666642,
"p95FrameDurationMs": 16.80000000000291
},
{
"name": "vue-large-graph-idle",
"durationMs": 17502.12699999997,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 17470.209,
"heapDeltaBytes": -45808500,
"heapUsedBytes": 166675552,
"domNodes": -8312,
"jsHeapTotalBytes": -16195584,
"scriptDurationMs": 593.029,
"eventListeners": -16389,
"totalBlockingTimeMs": 0,
"frameDurationMs": 17.776666666666642,
"p95FrameDurationMs": 16.80000000000291
},
{
"name": "vue-large-graph-pan",
"durationMs": 22104.573000000015,
"styleRecalcs": 158,
"styleRecalcDurationMs": 22.755000000000024,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 22072.978,
"heapDeltaBytes": -50830256,
"heapUsedBytes": 164715148,
"domNodes": -8312,
"jsHeapTotalBytes": -16195584,
"scriptDurationMs": 987.8989999999999,
"eventListeners": -16379,
"totalBlockingTimeMs": 462,
"frameDurationMs": 17.776666666666642,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-pan",
"durationMs": 21518.44399999993,
"styleRecalcs": 151,
"styleRecalcDurationMs": 20.314999999999973,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 21475.944,
"heapDeltaBytes": -54625136,
"heapUsedBytes": 166568980,
"domNodes": -8312,
"jsHeapTotalBytes": -16977920,
"scriptDurationMs": 955.8120000000001,
"eventListeners": -16381,
"totalBlockingTimeMs": 351,
"frameDurationMs": 17.779999999999927,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "workflow-execution",
"durationMs": 487.3539999999821,
"styleRecalcs": 13,
"styleRecalcDurationMs": 18.712000000000003,
"layouts": 2,
"layoutDurationMs": 0.38999999999999996,
"taskDurationMs": 129.79000000000002,
"heapDeltaBytes": -15986800,
"heapUsedBytes": 51048112,
"domNodes": 130,
"jsHeapTotalBytes": 7204864,
"scriptDurationMs": 11.987,
"eventListeners": 67,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66999999999998,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "workflow-execution",
"durationMs": 480.4900000000316,
"styleRecalcs": 12,
"styleRecalcDurationMs": 19.058,
"layouts": 3,
"layoutDurationMs": 0.704,
"taskDurationMs": 115.64799999999998,
"heapDeltaBytes": -16252352,
"heapUsedBytes": 50506560,
"domNodes": 119,
"jsHeapTotalBytes": 7467008,
"scriptDurationMs": 10.316,
"eventListeners": 65,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
}
]
} |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughLocal and non-cloud deployments now hide cloud-only upgrade, pricing, and resubscription actions. Top-up dialogs remain available. Authentication fixtures, unit tests, and an authenticated browser test cover the updated behavior. ChangesCredits gating
Estimated code review effort: 3 (Moderate) | ~20 minutes Possibly related PRs
Suggested labels: 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)
Comment |
The OSS boot surfaces startup toasts the mocked cloud boot does not, and a toast overlay can intercept the avatar click. Mirrors the guard currentUserPopoverCredits.spec.ts already uses for the same popover.
|
Thanks — noting the review came back with no actionable comments, and the walkthrough matches intent. Resolving the inconclusive pre-merge check
PR title: Commit subjects:
On the substance of that check — the e2e regression coverage it looks for is Each new test was verified to fail against the unfixed source and pass with the fix — including catching one of my own vacuous assertions (a New commit since your review
Declining both Finishing Touches
One thing reviewers should weigh that static analysis won't surface: |
|
Re-review after On the one remaining So the check now recognises the regression test; the only missing input is the title and commit subjects, which it reports as unavailable to it rather than absent. They are unambiguously present:
I posted these already and the check did not pick them up, so this looks like a harness limitation rather than a gap in the change. I'm deliberately not modifying code to chase it — there is no edit to the diff that would supply metadata the checker says it cannot read, and inventing one would be cargo-culting. Flagging for a human to override at merge. Status otherwise: Storybook built, bundle −28 B gzip, Playwright still running. That run is the first CI execution of |
Codecov Report✅ All modified and coverable lines are covered by tests. @@ Coverage Diff @@
## main #14589 +/- ##
=======================================
Coverage 79.14% 79.14%
=======================================
Files 1769 1769
Lines 113307 113308 +1
Branches 35242 35243 +1
=======================================
+ Hits 89678 89679 +1
+ Misses 23092 23087 -5
- Partials 537 542 +5
Flags with carried forward coverage won't be shown. Click here to find out more.
... and 5 files with indirect coverage changes 🚀 New features to boost your workflow:
|
dante01yoon
left a comment
There was a problem hiding this comment.
Reviewed the frontend behavior and regression coverage at the current head. The Cloud-only subscription gating is applied consistently, local top-up remains available, and the focused unit/E2E coverage exercises the failure path. All current checks are green.
|
Thanks @dante01yoon. Two things worth pinning down precisely, since "all current checks are green" is nearly true but glosses over a flaky and a bundle warning — I checked both so nobody has to re-litigate them at merge. The new e2e spec passed in CI. This was the one gap I flagged as genuinely unverified — I could not run Playwright in the sandbox, so this was its first real execution:
Overall: The 1 flaky is pre-existing and unrelated — not from this PR:
Subgraph breadcrumb rendering shares nothing with billing gating, and this diff touches no subgraph code. Naming it explicitly so it doesn't get mis-attributed here. The
Net is Still needs a human at merge: the Otherwise this is ready to come out of draft whenever you want it in the 6pm stable — I've left that call to you since merging is yours. |
|
Done in another PR. |
You're right — I verified it. This PR should be closed. #14587 (
It also covers the parts I assumed were still mine: the non-cloud unit cases in both The only delta left is three On the duplication: I checked for an existing fix before starting and reported finding none — that was wrong. I scanned remote branches for commits ahead of Two findings that aren't in #14587 and may be worth keeping as issues rather than code:
Leaving the close to you, since I can't close it myself. |
|
It's duplicate with #14587 , closing |
|
Agreed — closing is right. I'd reached the same conclusion in my comment above after verifying #14587 on main: it carries the same two guards line for line, plus the non-cloud unit cases and an equivalent local-auth e2e fixture. Nothing here should land alongside it. Two findings from the investigation aren't in #14587, so flagging where they live before this closes:
Sorry for the duplicate work — I checked for an existing fix first but scanned branch tips rather than open PRs, which is why I missed #14587. |
PR Created by the Glary-Bot Agent
Local (desktop/portable) users who are not Cloud subscribers lose the credit purchase flow after opening Settings ▸ Credits, and cannot recover without restarting the app. Reported in #bug-dump; related to BE-999 but frontend-only.
Root cause
Clicking "Upgrade to add credits" does nothing and changes nothing. Merely opening Settings ▸ Credits is what breaks the flow.
On a non-cloud build
isActiveSubscriptionis hardwiredtrue(isSubscribedOrIsNotCloudreturnstruewhenever!isCloud), soisFreeTieris the only live term in the top-up subscription gate.Nothing fetches subscription status on a local boot —
useBillingContext's init watcher returns early becauseactiveWorkspace?.idis undefined off cloud.CreditsTile'sonMounted(handleRefresh)is the first and only thing that ever callsfetchStatus(), so opening Settings ▸ Credits setstier: "FREE"and latchesisFreeTiertrue for the shared billing context.From then on
showTopUpCreditsDialog()routes intoshowSubscriptionRequiredDialog(), which returns immediately when!isCloud— a permanent silent no-op until restart. Independently,CreditsTilerenderedv-if="isFreeTier""Upgrade to add credits" with no distribution guard, whose handlershowPricingTable()also returns on!isCloud.Measured in a real
DISTRIBUTION=localhostbuild, signed in, with/customers/cloud-subscription-statusreturning{is_active: true, subscription_tier: "FREE"}:tierisFreeTierdialogStacknull["top-up-credits"]"FREE""FREE"[]— silent no-opChanges
dialogService.showTopUpCreditsDialog— apply the subscription gate only on cloud. Off cloud there is no subscribe flow, so diverting there strands the purchase dialog. This is the load-bearing fix.CreditsTile.vue— gate the "Upgrade to add credits" CTA onisCloud; local users keep "Add credits".CurrentUserPopoverWorkspace.vue— gate the upgrade CTA, "Plans & pricing", and the subscribe/resubscribe prompt onisCloud, matching guardsCurrentUserPopoverLegacy.vuealready has. Cloud-side hardening: this component never renders off cloud, sinceteamWorkspacesEnabledis hard-false there.Regression lineage
The visible CTA entered ungated in 026b2c4 (#12734, FE-964, 2026-06-25), which replaced
LegacyCreditsPanel.vuewith the facade-drivenCreditsTile. The old panel had no free-tier branch and never calledfetchStatus. First shipped in v1.47.4.It became invisible again in v1.48.4 via 51884bc (#13812), which added
hasActiveWorkspacetogetPermissions— off cloudactiveWorkspaceis null, socanTopUpis false andshowActionButtonsuppresses the whole block. That was accidental masking, not a fix.Visible-CTA window: v1.47.4 → v1.48.3. Reproduced empirically in a v1.47.4 build. The popover was never affected —
CurrentUserPopoverLegacyhas had itsisCloudguard since #11463 (v1.44.7) — which is why the customer saw the button only in Settings ▸ Credits.The top-up dead-end still exists at HEAD, independent of the CTA: v1.48.4 hid the button but left
onMounted(handleRefresh)untouched. That is the part that needs to ship.Why the e2e suite missed it
Not a coverage-volume problem — a structural one.
ComfyPageonly mocked auth for@cloud-tagged tests:@cloudis a project-routing tag: it excludes a spec from thechromiumproject (grepInvert: /…|@cloud/) and routes it to thecloudproject, which runs thefrontend-dist-cloudartifact built withpnpm build:cloud. So getting a signed-in user required the tag that guaranteesisCloud === true. Every credits/billing spec is@cloud-tagged, making local coverage impossible to write.The unit layer had the mirror-image blind spot:
vitest.setup.tspins__DISTRIBUTION__ = 'localhost', butCreditsTile.vuenever read the distribution, anddialogService.topUpCredits.test.tshard-stubbedisCloud: true— so those tests asserted cloud semantics while nominally running local.Fixed by adding an
@authtag so auth mocking is independent of project routing, plusbrowser_tests/tests/localCreditsGating.spec.ts(@oss @auth) walking the reported journey on the localhost bundle. Unit specs now driveisCloudthrough a mockable getter and cover both distributions.Each new test was verified to fail against the unfixed source and pass with the fix.
Verification
pnpm typecheck,pnpm typecheck:browser,pnpm lint,pnpm knipclean; pre-commit gates green.subscription/billing/services/workspace/topbar/dialog.DISTRIBUTION=localhostdev server (isCloud === falseread at runtime), signed in via theCloudAuthHelperFirebase-IndexedDB technique, billing endpoints mocked to a FREE tier. Walked the customer's exact steps with and without the fix, and against a v1.47.4 build.typecheck:browser.Notes
remove-team-ffalso editsCurrentUserPopoverWorkspace.vue, so expect a small conflict; it keeps that popover cloud-only (v-if="isCloud"), so there is no behavioural clash.canRunWorkflowsfoldsisFreeTierinto free-tier quota, so the same latch could swap the Run button for "Subscribe to run" on local. Currently inert only becausefreeTierJobAllowanceEnabledusesresolveFlag(default false) rather than being distribution-gated.Screenshots