[FE-1250] Add credit auto-reload settings UI#13762
Conversation
🎨 Storybook: ✅ Built — View Storybook🎭 Playwright: ✅ 1713 passed, 0 failed · 2 flaky📊 Browser Reports
📦 Bundle: 7.92 MB gzip 🔴 +9.81 kBDetailsSummary
Category Glance App Entry Points — 47.3 kB (baseline 47.3 kB) • ⚪ 0 BMain entry bundles and manifests
Status: 1 added / 1 removed Graph Workspace — 1.25 MB (baseline 1.25 MB) • 🔴 +37 BGraph editor runtime, canvas, workflow orchestration
Status: 1 added / 1 removed Views & Navigation — 109 kB (baseline 109 kB) • 🔴 +40 BTop-level views, pages, and routed surfaces
Status: 12 added / 12 removed / 2 unchanged Panels & Settings — 550 kB (baseline 550 kB) • 🔴 +33 BConfiguration panels, inspectors, and settings screens
Status: 11 added / 11 removed / 15 unchanged User & Accounts — 29.1 kB (baseline 29.1 kB) • ⚪ 0 BAuthentication, profile, and account management bundles
Status: 7 added / 7 removed / 3 unchanged Editors & Dialogs — 122 kB (baseline 121 kB) • 🔴 +40 BModals, dialogs, drawers, and in-app editors
Status: 6 added / 6 removed UI Components — 52.7 kB (baseline 56.8 kB) • 🟢 -4.12 kBReusable component library chunks
Status: 9 added / 9 removed / 4 unchanged Data & Services — 270 kB (baseline 270 kB) • ⚪ 0 BStores, services, APIs, and repositories
Status: 13 added / 13 removed / 3 unchanged Utilities & Hooks — 3.4 MB (baseline 3.4 MB) • 🔴 +7.71 kBHelpers, composables, and utility bundles
Status: 20 added / 19 removed / 17 unchanged Vendor & Third-Party — 15.7 MB (baseline 15.7 MB) • 🔴 +4.02 kBExternal libraries and shared vendor chunks
Status: 1 added / 1 removed / 15 unchanged Other — 11.9 MB (baseline 11.9 MB) • 🔴 +35.5 kBBundles that do not match a named category
Status: 92 added / 90 removed / 73 unchanged ⚡ Performance Report
✅ No regressions detected. All metrics
Historical variance (last 15 runs)
Trend (last 15 commits on main)
Raw data{
"timestamp": "2026-07-17T22:28:07.321Z",
"gitSha": "19579a3a9057bdceebce25cee96361ff8ebe6a88",
"branch": "jaewon/fe-1250-settings-billing-credit-auto-top-up-monthly-top-up-budget",
"measurements": [
{
"name": "canvas-idle",
"durationMs": 2076.134999999994,
"styleRecalcs": 9,
"styleRecalcDurationMs": 6.666000000000002,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 410.79499999999996,
"heapDeltaBytes": 3897276,
"heapUsedBytes": 72589736,
"domNodes": 18,
"jsHeapTotalBytes": 19935232,
"scriptDurationMs": 17.897000000000002,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "canvas-idle",
"durationMs": 2029.600999999957,
"styleRecalcs": 8,
"styleRecalcDurationMs": 6.957999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 406.64000000000004,
"heapDeltaBytes": -21508048,
"heapUsedBytes": 47235980,
"domNodes": -267,
"jsHeapTotalBytes": 19537920,
"scriptDurationMs": 16.306,
"eventListeners": -133,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "canvas-mouse-sweep",
"durationMs": 1776.094999999998,
"styleRecalcs": 74,
"styleRecalcDurationMs": 34.815,
"layouts": 12,
"layoutDurationMs": 3.5370000000000004,
"taskDurationMs": 757.733,
"heapDeltaBytes": -15265600,
"heapUsedBytes": 53715564,
"domNodes": -265,
"jsHeapTotalBytes": 19537920,
"scriptDurationMs": 115.724,
"eventListeners": -133,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-mouse-sweep",
"durationMs": 1795.1060000000325,
"styleRecalcs": 71,
"styleRecalcDurationMs": 32.977,
"layouts": 12,
"layoutDurationMs": 3.359,
"taskDurationMs": 762.786,
"heapDeltaBytes": -15503960,
"heapUsedBytes": 53294096,
"domNodes": -269,
"jsHeapTotalBytes": 20848640,
"scriptDurationMs": 117.16299999999998,
"eventListeners": -133,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.670000000000012,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1738.8599999999883,
"styleRecalcs": 31,
"styleRecalcDurationMs": 16.579000000000004,
"layouts": 6,
"layoutDurationMs": 0.576,
"taskDurationMs": 300.296,
"heapDeltaBytes": 7694952,
"heapUsedBytes": 76583960,
"domNodes": 76,
"jsHeapTotalBytes": 19410944,
"scriptDurationMs": 18.761,
"eventListeners": 19,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1682.262000000037,
"styleRecalcs": 32,
"styleRecalcDurationMs": 18.192,
"layouts": 6,
"layoutDurationMs": 0.771,
"taskDurationMs": 325.351,
"heapDeltaBytes": 7418152,
"heapUsedBytes": 76189752,
"domNodes": 78,
"jsHeapTotalBytes": 19935232,
"scriptDurationMs": 21.666,
"eventListeners": 19,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "dom-widget-clipping",
"durationMs": 596.9770000000096,
"styleRecalcs": 12,
"styleRecalcDurationMs": 9.036999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 357.539,
"heapDeltaBytes": -11617228,
"heapUsedBytes": 57207156,
"domNodes": 20,
"jsHeapTotalBytes": 19673088,
"scriptDurationMs": 59.940000000000005,
"eventListeners": 2,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "dom-widget-clipping",
"durationMs": 591.9680000000085,
"styleRecalcs": 12,
"styleRecalcDurationMs": 13.711999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 341.411,
"heapDeltaBytes": -11637392,
"heapUsedBytes": 57150100,
"domNodes": 20,
"jsHeapTotalBytes": 19935232,
"scriptDurationMs": 60.467999999999996,
"eventListeners": 0,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-idle",
"durationMs": 2023.1840000000147,
"styleRecalcs": 9,
"styleRecalcDurationMs": 8.918,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 523.846,
"heapDeltaBytes": 5547784,
"heapUsedBytes": 64792580,
"domNodes": -268,
"jsHeapTotalBytes": 4927488,
"scriptDurationMs": 86.643,
"eventListeners": -129,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "large-graph-idle",
"durationMs": 2034.2180000000099,
"styleRecalcs": 9,
"styleRecalcDurationMs": 10.147,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 542.497,
"heapDeltaBytes": 4806880,
"heapUsedBytes": 64228684,
"domNodes": -268,
"jsHeapTotalBytes": 4141056,
"scriptDurationMs": 89.21300000000001,
"eventListeners": -129,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "large-graph-pan",
"durationMs": 2116.42599999999,
"styleRecalcs": 68,
"styleRecalcDurationMs": 13.300000000000002,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 1110.767,
"heapDeltaBytes": 1069056,
"heapUsedBytes": 62082856,
"domNodes": -270,
"jsHeapTotalBytes": 4870144,
"scriptDurationMs": 400.05199999999996,
"eventListeners": -127,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "large-graph-pan",
"durationMs": 2118.50800000002,
"styleRecalcs": 68,
"styleRecalcDurationMs": 14.042000000000002,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 1069.059,
"heapDeltaBytes": -4105168,
"heapUsedBytes": 55850308,
"domNodes": -269,
"jsHeapTotalBytes": 5189632,
"scriptDurationMs": 388.57599999999996,
"eventListeners": -129,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-zoom",
"durationMs": 3149.1060000000175,
"styleRecalcs": 66,
"styleRecalcDurationMs": 15.382000000000003,
"layouts": 60,
"layoutDurationMs": 7.189,
"taskDurationMs": 1321.472,
"heapDeltaBytes": -5664716,
"heapUsedBytes": 56655668,
"domNodes": -271,
"jsHeapTotalBytes": 8335360,
"scriptDurationMs": 473.685,
"eventListeners": -133,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-zoom",
"durationMs": 3135.874000000001,
"styleRecalcs": 66,
"styleRecalcDurationMs": 15.722,
"layouts": 60,
"layoutDurationMs": 7.169999999999999,
"taskDurationMs": 1346.697,
"heapDeltaBytes": -5307196,
"heapUsedBytes": 56374620,
"domNodes": -271,
"jsHeapTotalBytes": 8073216,
"scriptDurationMs": 497.20699999999994,
"eventListeners": -133,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66999999999998,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "minimap-idle",
"durationMs": 2017.2749999999837,
"styleRecalcs": 8,
"styleRecalcDurationMs": 7.1850000000000005,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 521.9430000000001,
"heapDeltaBytes": 5279636,
"heapUsedBytes": 66246464,
"domNodes": -269,
"jsHeapTotalBytes": 5451776,
"scriptDurationMs": 87.77099999999999,
"eventListeners": -127,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "minimap-idle",
"durationMs": 2020.5459999999675,
"styleRecalcs": 7,
"styleRecalcDurationMs": 7.078999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 544.141,
"heapDeltaBytes": 5139356,
"heapUsedBytes": 66554912,
"domNodes": -272,
"jsHeapTotalBytes": 5713920,
"scriptDurationMs": 96.56200000000001,
"eventListeners": -129,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 542.6989999999705,
"styleRecalcs": 47,
"styleRecalcDurationMs": 10.578,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 349.1650000000001,
"heapDeltaBytes": -11036320,
"heapUsedBytes": 57792448,
"domNodes": 20,
"jsHeapTotalBytes": 19935232,
"scriptDurationMs": 117.38000000000001,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 603.240000000028,
"styleRecalcs": 49,
"styleRecalcDurationMs": 12.399000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 380.60699999999997,
"heapDeltaBytes": -11550044,
"heapUsedBytes": 57649080,
"domNodes": 24,
"jsHeapTotalBytes": 20459520,
"scriptDurationMs": 121.44300000000001,
"eventListeners": 8,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-idle",
"durationMs": 2018.6589999999853,
"styleRecalcs": 8,
"styleRecalcDurationMs": 7.356,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 379.347,
"heapDeltaBytes": -21483192,
"heapUsedBytes": 47615308,
"domNodes": -268,
"jsHeapTotalBytes": 20586496,
"scriptDurationMs": 12.749,
"eventListeners": -133,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-idle",
"durationMs": 2037.2820000000047,
"styleRecalcs": 9,
"styleRecalcDurationMs": 7.681,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 362.257,
"heapDeltaBytes": 3960160,
"heapUsedBytes": 72845460,
"domNodes": 18,
"jsHeapTotalBytes": 19935232,
"scriptDurationMs": 11.649000000000001,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1714.2959999999903,
"styleRecalcs": 77,
"styleRecalcDurationMs": 36.869,
"layouts": 16,
"layoutDurationMs": 3.842,
"taskDurationMs": 691.196,
"heapDeltaBytes": -19043220,
"heapUsedBytes": 49841008,
"domNodes": -264,
"jsHeapTotalBytes": 19537920,
"scriptDurationMs": 87.97399999999999,
"eventListeners": -133,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1682.2260000000142,
"styleRecalcs": 75,
"styleRecalcDurationMs": 33.455,
"layouts": 16,
"layoutDurationMs": 4.113,
"taskDurationMs": 627.1,
"heapDeltaBytes": -5064548,
"heapUsedBytes": 63853516,
"domNodes": 63,
"jsHeapTotalBytes": 20459520,
"scriptDurationMs": 85.07900000000001,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-transition-enter",
"durationMs": 997.5519999999847,
"styleRecalcs": 19,
"styleRecalcDurationMs": 32.749,
"layouts": 15,
"layoutDurationMs": 15.138999999999996,
"taskDurationMs": 787.52,
"heapDeltaBytes": 1178004,
"heapUsedBytes": 85507980,
"domNodes": 13673,
"jsHeapTotalBytes": 14417920,
"scriptDurationMs": 30.04799999999999,
"eventListeners": 2369,
"totalBlockingTimeMs": 145,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "viewport-pan-sweep",
"durationMs": 8168.806999999959,
"styleRecalcs": 250,
"styleRecalcDurationMs": 37.927,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 3688.37,
"heapDeltaBytes": 15168356,
"heapUsedBytes": 74297536,
"domNodes": -266,
"jsHeapTotalBytes": 6705152,
"scriptDurationMs": 1217.3890000000001,
"eventListeners": -113,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.670000000000012,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "viewport-pan-sweep",
"durationMs": 8168.203000000062,
"styleRecalcs": 249,
"styleRecalcDurationMs": 38.114000000000004,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 3879.269,
"heapDeltaBytes": 15704040,
"heapUsedBytes": 74941176,
"domNodes": -266,
"jsHeapTotalBytes": 6705152,
"scriptDurationMs": 1249.865,
"eventListeners": -113,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-idle",
"durationMs": 12670.585999999957,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 12654.143999999998,
"heapDeltaBytes": -55685880,
"heapUsedBytes": 170784212,
"domNodes": -8311,
"jsHeapTotalBytes": -8851456,
"scriptDurationMs": 520.702,
"eventListeners": -16386,
"totalBlockingTimeMs": 0,
"frameDurationMs": 17.223333333333358,
"p95FrameDurationMs": 16.80000000000291
},
{
"name": "vue-large-graph-idle",
"durationMs": 13014.348999999926,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 12999.690999999999,
"heapDeltaBytes": -44942680,
"heapUsedBytes": 166297908,
"domNodes": -8311,
"jsHeapTotalBytes": -15405056,
"scriptDurationMs": 561.4780000000001,
"eventListeners": -16384,
"totalBlockingTimeMs": 0,
"frameDurationMs": 17.223333333333358,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-pan",
"durationMs": 15266.612000000008,
"styleRecalcs": 75,
"styleRecalcDurationMs": 11.648999999999965,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 15243.827,
"heapDeltaBytes": -44598136,
"heapUsedBytes": 163735324,
"domNodes": -8311,
"jsHeapTotalBytes": -5705728,
"scriptDurationMs": 826.8539999999999,
"eventListeners": -16380,
"totalBlockingTimeMs": 12,
"frameDurationMs": 17.223333333333358,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-pan",
"durationMs": 15744.745999999963,
"styleRecalcs": 79,
"styleRecalcDurationMs": 13.999999999999957,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 15721.426999999998,
"heapDeltaBytes": -43432680,
"heapUsedBytes": 164719024,
"domNodes": -8311,
"jsHeapTotalBytes": -15667200,
"scriptDurationMs": 862.31,
"eventListeners": -16384,
"totalBlockingTimeMs": 50,
"frameDurationMs": 16.66333333333326,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "workflow-execution",
"durationMs": 470.8479999999895,
"styleRecalcs": 18,
"styleRecalcDurationMs": 23.383000000000003,
"layouts": 3,
"layoutDurationMs": 1.179,
"taskDurationMs": 130.072,
"heapDeltaBytes": -15491928,
"heapUsedBytes": 52688912,
"domNodes": 154,
"jsHeapTotalBytes": 6565888,
"scriptDurationMs": 10.653999999999998,
"eventListeners": 67,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "workflow-execution",
"durationMs": 453.5710000000108,
"styleRecalcs": 14,
"styleRecalcDurationMs": 20.352999999999998,
"layouts": 3,
"layoutDurationMs": 0.7260000000000001,
"taskDurationMs": 111.72699999999999,
"heapDeltaBytes": -16571632,
"heapUsedBytes": 51664316,
"domNodes": 119,
"jsHeapTotalBytes": 7614464,
"scriptDurationMs": 9.268999999999998,
"eventListeners": 65,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
}
]
} |
📝 WalkthroughWalkthroughAdds a frontend credit auto-reload experience with workspace-scoped state, configuration and status UI, billing-panel gating, frozen lifecycle states, localized validation, dialog integration, and comprehensive component, composable, and integration tests. ChangesCredit auto-reload
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant SubscriptionPanelContentWorkspace
participant AutoReloadSection
participant dialogService
participant AutoReloadDialogContent
participant useAutoReload
SubscriptionPanelContentWorkspace->>AutoReloadSection: render when flag and permission allow
AutoReloadSection->>dialogService: showAutoReloadDialog()
dialogService->>AutoReloadDialogContent: open headless workspace dialog
AutoReloadDialogContent->>useAutoReload: save validated configuration
useAutoReload->>AutoReloadSection: expose updated scoped state
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 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: 4
🤖 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/workspace/components/dialogs/AutoReloadDialogContent.vue`:
- Line 52: Update the currency indicator spans in AutoReloadDialogContent to use
vue-i18n/Intl.NumberFormat for localized USD formatting instead of hardcoded
"$". Preserve the existing conditional rendering and display the locale-aware
USD symbol or localized USD label in both occurrences.
- Around line 53-84: Update the validation state used by the Update action to
cover every disabling condition: an empty reload threshold and an enabled zero
budget, in addition to the existing reload minimum check. Add user-friendly,
actionable localized messages via vue-i18n, expose each field’s invalid state
with aria-invalid, and connect its message through aria-describedby. Apply this
consistently to the reload amount input and the corresponding budget input
validation flow.
In
`@src/platform/workspace/components/dialogs/settings/AutoReloadSection.test.ts`:
- Around line 102-120: Replace the Tailwind class assertions in the
AutoReloadSection tests, including the near-limit and exhausted-budget cases,
with assertions against visible or accessible semantic state. Update the
relevant component symbol to expose a semantic near-limit status if none exists,
then assert that status and retain the existing text, paused-state, and progress
value checks.
In `@src/platform/workspace/composables/useAutoReload.ts`:
- Around line 29-36: Update the isPaused calculation in the auto-reload state
logic to pause whenever the remaining budget cannot fund another reload,
including positive budgetLeftCents values below reloadCostCents. Preserve
existing disabled and exhausted-budget behavior, and add a regression case
covering 0 < budgetLeftCents < reloadCostCents.
🪄 Autofix (Beta)
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: 15b7e184-f7d3-463d-808f-48c07e8d764c
📒 Files selected for processing (16)
docs/superpowers/specs/2026-07-17-fe-1250-credit-auto-reload-design.mdsrc/components/ui/switch/Switch.stories.tssrc/components/ui/switch/Switch.test.tssrc/components/ui/switch/Switch.vuesrc/locales/en/main.jsonsrc/platform/workspace/components/SubscriptionPanelContentWorkspace.test.tssrc/platform/workspace/components/SubscriptionPanelContentWorkspace.vuesrc/platform/workspace/components/dialogs/AutoReloadDialogContent.test.tssrc/platform/workspace/components/dialogs/AutoReloadDialogContent.vuesrc/platform/workspace/components/dialogs/settings/AutoReloadSection.test.tssrc/platform/workspace/components/dialogs/settings/AutoReloadSection.vuesrc/platform/workspace/components/dialogs/settings/ProgressBar.vuesrc/platform/workspace/composables/useAutoReload.test.tssrc/platform/workspace/composables/useAutoReload.tssrc/services/dialogService.renderer.test.tssrc/services/dialogService.ts
Codecov Report❌ Patch coverage is @@ Coverage Diff @@
## main #13762 +/- ##
==========================================
+ Coverage 78.45% 78.50% +0.04%
==========================================
Files 1679 1689 +10
Lines 96185 96868 +683
Branches 34920 35200 +280
==========================================
+ Hits 75462 76045 +583
- Misses 20100 20393 +293
+ Partials 623 430 -193
Flags with carried forward coverage won't be shown. Click here to find out more.
... and 362 files with indirect coverage changes 🚀 New features to boost your workflow:
|
…gs-billing-credit-auto-top-up-monthly-top-up-budget
DrJKL
left a comment
There was a problem hiding this comment.
Please consider consolidating these tests before merge. The goal is a smaller suite with the same behavioral confidence.
| mockError.value = null | ||
| }) | ||
|
|
||
| it('preserves the existing billing UI while billing control is disabled', () => { |
There was a problem hiding this comment.
This block repeats the complete lifecycle matrix already covered by isAutoReloadFrozen. Since AutoReloadSection is stubbed here, these cases mostly retest mocked access state. Can we keep the full status table in useAutoReloadAccess.test.ts, then retain only three composition cases here: reactive flag gating, reactive permission gating, and one representative assertion that the computed frozen value reaches the child?
| setConfig() | ||
| }) | ||
|
|
||
| it('blocks an update below the minimum reload amount', async () => { |
There was a problem hiding this comment.
The component suite below exercises a full locale-aware numeric parser and conversion matrix through a 518-line SFC. That makes pure input rules slow and costly to refactor. Can we extract the parser and canonical credits/cents form model, table-test malformed input, locale digits/grouping, rounding, and overflow directly, then keep only representative component cases for minimum reload, required budget, one malformed value, and unsafe USD overflow?
| ) | ||
| }) | ||
|
|
||
| it('renders normalized values for malformed financial state', () => { |
There was a problem hiding this comment.
This malformed-state case repeats normalization already tested directly in deriveAutoReloadState. The section tests should prove that meaningful derived states render, while invalid-number permutations stay in the pure function suite. Can we delete this case and table-drive the healthy, warning, and paused rendering cases instead?
Summary
Adds the designer-authored credit auto-reload and optional monthly budget UI to workspace billing settings, gated by
billing_control_enabledso the existing billing UI remains unchanged when the flag is off.Changes
Delivery status
Switchand main-app PrimeVue migration have been extracted to #13778.mainand remove its duplicate local Switch implementation.Backend boundary
billing_control_enabledmust remain off for customer rollout.Testing boundary
Review focus
Validation
3e4e2510e7: 130 passed across 8 filespnpm typecheckpnpm lint— passes; 3 pre-existing Turnstile warnings remainpnpm format:checkpnpm knippnpm build:cloudScreenshots
Captured from commit
5646df91c6at a fixed 1440 × 1200 viewport in one authenticated Chrome/CDP session athttps://local.comfy.org:5173, running the branch frontend againstcloud.comfy.org.BE-3324 is not implemented, so non-default spend, lifecycle, permission, and flag states were supplied through a temporary local-only harness. The harness and scenario query parameter are not included in this PR. The flag-off containment follow-up only moves the unpadded container boundary into the already gated branch; it preserves the captured markup and responsive content width, so this same-session set remains the current visual evidence.
Rollout and access
Feature flag off — existing billing UI
Feature flag on — not configured
Member — auto-reload controls hidden
Setup interaction sequence
1. Opened from Set up auto-reload
2. Monthly budget enabled without an amount
3. Valid Credits budget
4. Converted to USD
5. Configuration saved
6. Edit reopens the saved values
Configured states
Enabled without a monthly budget
Healthy monthly budget
Near limit
Remaining budget cannot fund one reload — Paused
Budget exhausted — Paused
Configured Off
Billing lifecycle
Paused plan — shared banner
Paused, inactive, or ended plan — section frozen
Payment at risk — warning shown; section remains interactive
Active cancellation — ending banner shown; section remains interactive
References