fix: clamp monthly credits remaining to the plan allowance - #14477
fix: clamp monthly credits remaining to the plan allowance#14477christian-byrne wants to merge 3 commits into
Conversation
🎭 Playwright: ✅ 1812 passed, 0 failed📊 Browser Reports
🎨 Storybook: ✅ Built — View Storybook📦 Bundle: 8.85 MB gzip 🔴 +146 BDetailsSummary
Category Glance App Entry Points — 3.71 kB (baseline 3.71 kB) • ⚪ 0 BMain entry bundles and manifests
Status: 1 added / 1 removed Graph Workspace — 1.37 MB (baseline 1.37 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 565 kB) • ⚪ 0 BConfiguration panels, inspectors, and settings screens
Status: 10 added / 10 removed / 16 unchanged User & Accounts — 27.2 kB (baseline 27.2 kB) • ⚪ 0 BAuthentication, profile, and account management bundles
Status: 6 added / 6 removed / 5 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.51 MB (baseline 3.51 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.8 MB (baseline 16.8 MB) • ⚪ 0 BExternal libraries and shared vendor chunks Status: 18 unchanged Other — 14.2 MB (baseline 14.2 MB) • 🔴 +176 BBundles that do not match a named category
Status: 68 added / 68 removed / 218 unchanged ⚡ Performance Report
Show regressions
All metrics
Historical variance (last 15 runs)
Trend (last 15 commits on main)
Raw data{
"timestamp": "2026-08-13T22:36:20.098Z",
"gitSha": "250eee3475d98565a3f1ca15f5b1d2131946ba21",
"branch": "glary/fe-1451-clamp-monthly-remaining-credits",
"measurements": [
{
"name": "canvas-idle",
"durationMs": 2048.171000000025,
"styleRecalcs": 9,
"styleRecalcDurationMs": 7.373999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 447.85699999999997,
"heapDeltaBytes": 6026480,
"heapUsedBytes": 70674588,
"domNodes": 18,
"jsHeapTotalBytes": 24641536,
"scriptDurationMs": 15.366,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-idle",
"durationMs": 2025.2490000000307,
"styleRecalcs": 9,
"styleRecalcDurationMs": 7.504999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 447.67199999999997,
"heapDeltaBytes": 5906168,
"heapUsedBytes": 70588200,
"domNodes": 18,
"jsHeapTotalBytes": 24117248,
"scriptDurationMs": 13.959000000000003,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "canvas-mouse-sweep",
"durationMs": 2067.1389999999974,
"styleRecalcs": 77,
"styleRecalcDurationMs": 41.152,
"layouts": 12,
"layoutDurationMs": 3.4099999999999997,
"taskDurationMs": 1044.77,
"heapDeltaBytes": -16265824,
"heapUsedBytes": 48402644,
"domNodes": -275,
"jsHeapTotalBytes": 24088576,
"scriptDurationMs": 138.214,
"eventListeners": -153,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "canvas-mouse-sweep",
"durationMs": 1758.5219999998571,
"styleRecalcs": 73,
"styleRecalcDurationMs": 36.034000000000006,
"layouts": 12,
"layoutDurationMs": 3.4680000000000004,
"taskDurationMs": 797.6229999999999,
"heapDeltaBytes": 432112,
"heapUsedBytes": 64964728,
"domNodes": 55,
"jsHeapTotalBytes": 24903680,
"scriptDurationMs": 121.64899999999999,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1746.1260000000038,
"styleRecalcs": 30,
"styleRecalcDurationMs": 16.612,
"layouts": 6,
"layoutDurationMs": 0.5580000000000002,
"taskDurationMs": 363.602,
"heapDeltaBytes": 8872384,
"heapUsedBytes": 73585292,
"domNodes": 76,
"jsHeapTotalBytes": 24117248,
"scriptDurationMs": 17.531000000000006,
"eventListeners": 19,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1714.9540000000343,
"styleRecalcs": 30,
"styleRecalcDurationMs": 14.281,
"layouts": 6,
"layoutDurationMs": 0.521,
"taskDurationMs": 350.82099999999997,
"heapDeltaBytes": 8814408,
"heapUsedBytes": 73583888,
"domNodes": 76,
"jsHeapTotalBytes": 24379392,
"scriptDurationMs": 17.319000000000003,
"eventListeners": 19,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "dom-widget-clipping",
"durationMs": 572.1429999999827,
"styleRecalcs": 11,
"styleRecalcDurationMs": 7.097000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 371.913,
"heapDeltaBytes": -11257436,
"heapUsedBytes": 53557904,
"domNodes": 18,
"jsHeapTotalBytes": 25165824,
"scriptDurationMs": 60.239999999999995,
"eventListeners": 0,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66999999999998,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "dom-widget-clipping",
"durationMs": 573.9220000000387,
"styleRecalcs": 11,
"styleRecalcDurationMs": 7.6930000000000005,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 371.79,
"heapDeltaBytes": -11127484,
"heapUsedBytes": 53496432,
"domNodes": 18,
"jsHeapTotalBytes": 26214400,
"scriptDurationMs": 59.772999999999996,
"eventListeners": 0,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "large-graph-idle",
"durationMs": 2039.5200000000386,
"styleRecalcs": 9,
"styleRecalcDurationMs": 7.5340000000000025,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 669.4300000000001,
"heapDeltaBytes": 7241920,
"heapUsedBytes": 67094880,
"domNodes": -281,
"jsHeapTotalBytes": 2981888,
"scriptDurationMs": 98.985,
"eventListeners": -149,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-idle",
"durationMs": 2010.7009999999264,
"styleRecalcs": 8,
"styleRecalcDurationMs": 6.661999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 653.2130000000001,
"heapDeltaBytes": 6423000,
"heapUsedBytes": 66300980,
"domNodes": -284,
"jsHeapTotalBytes": 3244032,
"scriptDurationMs": 98.77,
"eventListeners": -149,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "large-graph-pan",
"durationMs": 2130.8950000000095,
"styleRecalcs": 69,
"styleRecalcDurationMs": 13.364999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 1228.9240000000002,
"heapDeltaBytes": 4429248,
"heapUsedBytes": 65710824,
"domNodes": -284,
"jsHeapTotalBytes": 3698688,
"scriptDurationMs": 415.85799999999995,
"eventListeners": -147,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "large-graph-pan",
"durationMs": 2183.269999999993,
"styleRecalcs": 69,
"styleRecalcDurationMs": 13.513000000000003,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 1311.4050000000002,
"heapDeltaBytes": 2155816,
"heapUsedBytes": 63173244,
"domNodes": -283,
"jsHeapTotalBytes": 4222976,
"scriptDurationMs": 462.582,
"eventListeners": -149,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "large-graph-zoom",
"durationMs": 3164.9909999999863,
"styleRecalcs": 65,
"styleRecalcDurationMs": 13.931,
"layouts": 60,
"layoutDurationMs": 7.113000000000001,
"taskDurationMs": 1421.504,
"heapDeltaBytes": 23704716,
"heapUsedBytes": 86913860,
"domNodes": 12,
"jsHeapTotalBytes": 7077888,
"scriptDurationMs": 510.61199999999997,
"eventListeners": 8,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-zoom",
"durationMs": 3231.5860000001067,
"styleRecalcs": 65,
"styleRecalcDurationMs": 13.994,
"layouts": 60,
"layoutDurationMs": 7.188999999999999,
"taskDurationMs": 1523.388,
"heapDeltaBytes": -1236320,
"heapUsedBytes": 61519968,
"domNodes": -287,
"jsHeapTotalBytes": 7176192,
"scriptDurationMs": 549.933,
"eventListeners": -183,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "minimap-idle",
"durationMs": 2025.0810000000001,
"styleRecalcs": 9,
"styleRecalcDurationMs": 8.121000000000004,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 663.9110000000001,
"heapDeltaBytes": 7015604,
"heapUsedBytes": 67913752,
"domNodes": -283,
"jsHeapTotalBytes": 4030464,
"scriptDurationMs": 100.264,
"eventListeners": -149,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "minimap-idle",
"durationMs": 2036.1900000000333,
"styleRecalcs": 8,
"styleRecalcDurationMs": 6.557,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 654.778,
"heapDeltaBytes": 7128208,
"heapUsedBytes": 67970652,
"domNodes": -284,
"jsHeapTotalBytes": 4292608,
"scriptDurationMs": 98.55999999999997,
"eventListeners": -149,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 578.6960000000363,
"styleRecalcs": 46,
"styleRecalcDurationMs": 9.464,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 377.67599999999993,
"heapDeltaBytes": -10824936,
"heapUsedBytes": 54154220,
"domNodes": 18,
"jsHeapTotalBytes": 26214400,
"scriptDurationMs": 115.012,
"eventListeners": 8,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66999999999998,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 577.9210000000603,
"styleRecalcs": 47,
"styleRecalcDurationMs": 10.855,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 393.898,
"heapDeltaBytes": -10577220,
"heapUsedBytes": 54219004,
"domNodes": 20,
"jsHeapTotalBytes": 26476544,
"scriptDurationMs": 119.407,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-idle",
"durationMs": 1995.2260000000024,
"styleRecalcs": 10,
"styleRecalcDurationMs": 7.808000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 441.88700000000006,
"heapDeltaBytes": 5892656,
"heapUsedBytes": 70742160,
"domNodes": 20,
"jsHeapTotalBytes": 24903680,
"scriptDurationMs": 12.330999999999998,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "subgraph-idle",
"durationMs": 2011.9529999999486,
"styleRecalcs": 10,
"styleRecalcDurationMs": 7.855,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 436.414,
"heapDeltaBytes": 5948052,
"heapUsedBytes": 70861688,
"domNodes": 20,
"jsHeapTotalBytes": 24641536,
"scriptDurationMs": 12.245,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1719.3479999999681,
"styleRecalcs": 77,
"styleRecalcDurationMs": 33.166000000000004,
"layouts": 16,
"layoutDurationMs": 3.605,
"taskDurationMs": 739.37,
"heapDeltaBytes": -3520552,
"heapUsedBytes": 61339036,
"domNodes": 64,
"jsHeapTotalBytes": 24903680,
"scriptDurationMs": 87.74900000000001,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1689.6489999999176,
"styleRecalcs": 76,
"styleRecalcDurationMs": 34.284,
"layouts": 16,
"layoutDurationMs": 4.288,
"taskDurationMs": 739.321,
"heapDeltaBytes": -3445160,
"heapUsedBytes": 61484352,
"domNodes": 62,
"jsHeapTotalBytes": 24903680,
"scriptDurationMs": 90.13199999999999,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-transition-enter",
"durationMs": 1388.9659999999822,
"styleRecalcs": 18,
"styleRecalcDurationMs": 29.721999999999998,
"layouts": 14,
"layoutDurationMs": 12.655,
"taskDurationMs": 887.254,
"heapDeltaBytes": 2181800,
"heapUsedBytes": 74860148,
"domNodes": 13673,
"jsHeapTotalBytes": 15990784,
"scriptDurationMs": 28.445999999999998,
"eventListeners": 2375,
"totalBlockingTimeMs": 137,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "viewport-pan-sweep",
"durationMs": 8236.361000000103,
"styleRecalcs": 249,
"styleRecalcDurationMs": 35.896,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 4322.17,
"heapDeltaBytes": 8510484,
"heapUsedBytes": 68881724,
"domNodes": -284,
"jsHeapTotalBytes": 5795840,
"scriptDurationMs": 1371.4189999999999,
"eventListeners": -133,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.669999999999952,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "viewport-pan-sweep",
"durationMs": 8242.67400000008,
"styleRecalcs": 250,
"styleRecalcDurationMs": 35.596999999999994,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 4578.637000000001,
"heapDeltaBytes": 3500956,
"heapUsedBytes": 63729192,
"domNodes": -283,
"jsHeapTotalBytes": 9011200,
"scriptDurationMs": 1547.299,
"eventListeners": -163,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-idle",
"durationMs": 15957.459999999855,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 15937.647,
"heapDeltaBytes": -52573428,
"heapUsedBytes": 170595824,
"domNodes": -8312,
"jsHeapTotalBytes": -12001280,
"scriptDurationMs": 504.6280000000001,
"eventListeners": -16389,
"totalBlockingTimeMs": 0,
"frameDurationMs": 18.333333333333332,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-idle",
"durationMs": 16217.330999999831,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 16187.859,
"heapDeltaBytes": -40298088,
"heapUsedBytes": 158014572,
"domNodes": -8312,
"jsHeapTotalBytes": -3088384,
"scriptDurationMs": 549.9409999999999,
"eventListeners": -16387,
"totalBlockingTimeMs": 0,
"frameDurationMs": 17.776666666666642,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-pan",
"durationMs": 20013.60799999998,
"styleRecalcs": 136,
"styleRecalcDurationMs": 15.22100000000004,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 19990.905,
"heapDeltaBytes": -56210208,
"heapUsedBytes": 158224520,
"domNodes": -8312,
"jsHeapTotalBytes": -9375744,
"scriptDurationMs": 859.8149999999999,
"eventListeners": -16389,
"totalBlockingTimeMs": 159,
"frameDurationMs": 18.330000000000048,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-pan",
"durationMs": 20260.181999999986,
"styleRecalcs": 141,
"styleRecalcDurationMs": 15.712000000000032,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 20203.405,
"heapDeltaBytes": -37964036,
"heapUsedBytes": 179715908,
"domNodes": -8312,
"jsHeapTotalBytes": -9711616,
"scriptDurationMs": 903.196,
"eventListeners": -16387,
"totalBlockingTimeMs": 130,
"frameDurationMs": 17.780000000000047,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "workflow-execution",
"durationMs": 153.1320000001415,
"styleRecalcs": 8,
"styleRecalcDurationMs": 14.607,
"layouts": 2,
"layoutDurationMs": 0.556,
"taskDurationMs": 88.94399999999999,
"heapDeltaBytes": 3119884,
"heapUsedBytes": 67026056,
"domNodes": 111,
"jsHeapTotalBytes": 2883584,
"scriptDurationMs": 5.549999999999999,
"eventListeners": 49,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "workflow-execution",
"durationMs": 483.8999999999487,
"styleRecalcs": 17,
"styleRecalcDurationMs": 20.251,
"layouts": 3,
"layoutDurationMs": 1.133,
"taskDurationMs": 116.59999999999998,
"heapDeltaBytes": 5284924,
"heapUsedBytes": 69090508,
"domNodes": 145,
"jsHeapTotalBytes": 5505024,
"scriptDurationMs": 10.5,
"eventListeners": 97,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
}
]
} |
|
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 (4)
📝 WalkthroughWalkthroughThe monthly usage utility now returns clamped remaining credits and derives used credits from that value. ChangesMonthly credit usage
Estimated code review effort: 2 (Simple) | ~10 minutes Mergeability Score: ⚪ Minimal · up to This change clamps the monthly credits display to the plan allowance while preserving the full balance, with targeted coverage for the corrected behavior. No actionable merge-blocking risk remains beyond normal checks and review. 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 |
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/utils/creditsProgress.ts`:
- Line 24: Replace the manual Math.min/Math.max composition in the remaining
calculation with es-toolkit/math’s clamp, passing monthlyRemaining with 0 as the
minimum and monthlyTotal as the maximum; add the required import and preserve
the existing bounded result.
🪄 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: 1bcd5628-d3d4-4167-b1a6-56015861da0e
📒 Files selected for processing (4)
src/platform/cloud/subscription/components/CreditsTile.test.tssrc/platform/cloud/subscription/components/CreditsTile.vuesrc/platform/cloud/subscription/utils/creditsProgress.test.tssrc/platform/cloud/subscription/utils/creditsProgress.ts
Codecov Report✅ All modified and coverable lines are covered by tests. @@ Coverage Diff @@
## main #14477 +/- ##
=======================================
Coverage 81.95% 81.95%
=======================================
Files 1882 1882
Lines 119944 119946 +2
Branches 35972 35972
=======================================
+ Hits 98303 98305 +2
+ Misses 21175 21166 -9
- Partials 466 475 +9
Flags with carried forward coverage won't be shown. Click here to find out more.
... and 10 files with indirect coverage changes 🚀 New features to boost your workflow:
|
|
Handoff: I'm off the Cloud billing pod, so @dante01yoon owns this now and I've reassigned it to him. Context that isn't obvious from the assignee change alone: this is one of five of my open billing PRs moved to him on 2026-08-13 (#14867, #14866, #14856, #14865, #14477). They're independent of each other and none is urgent. All of them predate the flag-retirement stack merging, so rebase and re-check CI before reviewing rather than trusting the last green run. The two that are not in that set and do need thought first: #14964 has changes requested, and #15078 is draft, conflicting, and overlaps #15094 on subscription-status fetch races. Don't revive #15078 without reading #15094 first. The live one is #15094, a defect that shipped with the retirement stack. That takes priority over anything here. |
The monthly row reconstructed remaining credits from the cents balance while the total came from the catalog credit grant, so an untouched allowance could render as more left than the plan grants (e.g. "4,201 left of 4,200"). computeMonthlyUsage now returns a clamped remaining whose complement is used, so used + remaining always equals the allowance and the two labels on the row cannot contradict each other.
All five plan rows exercised the same clamp branch and duplicated catalog values that can drift. Keep the Standard repro plus the exact-match boundary.
Repo guidance is to use es-toolkit for utility functions; clamp expresses the bound directly and is already the convention for this in src/composables/video.
c04980f to
3491af1
Compare
|
Note GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer. |
|
@christian-byrne thanks — acted on the one item that applies to this PR: rebased onto current Your warning was justified — this PR did break on rebase. It was 258 commits behind (base was Jul 31,
Fixed by aligning the test with the current harness ( Re-verified after the rebase:
CodeRabbit re-reviewed the rebased head and approved. Noted on the rest and deliberately not touching any of it: #15094 is the priority, #14964 has changes requested, and I won't revive #15078 without #15094 being read first. This one is independent and non-urgent, so it's ready whenever @jaeone94 gets to it. Two open judgement calls for whoever picks it up, both detailed in the PR description: |
|
Small correction on the cc above: it predates the ownership edit in the handoff comment — @dante01yoon owns Cloud billing now, so this one is his whenever he gets to it, not mine. |
benjcooley
left a comment
There was a problem hiding this comment.
Verdict: COMMENT — the Standard clamp works, but the allowance used as the cap is already stale for Founders.
| Severity | Count |
|---|---|
| TRIVIAL | 0 |
| SHOULD FIX | 1 |
| BLOCKER | 0 |
The regression coverage proves the reported Standard behavior in both full and compact labels. Please align the Founder allowance with the live catalog (or source the allowance authoritatively) so this fix does not underreport that plan's monthly entitlement.
|
|
||
| const usedDisplay = computed(() => formatCreditCount(usage.value.used)) | ||
| const monthlyRemainingDisplay = computed(() => | ||
| formatCreditCount(usage.value.remaining) |
There was a problem hiding this comment.
SHOULD FIX: This cap is already wrong for active Founders. The backend catalog defines founder-monthly as exactly 2,588¢ (≈5,461 credits; its contract test expects 5,461), but getTierCredits('founder') returns the stale 5,460 constant. A fresh Founder balance reconstructs as 5,463, so this now renders 5,460 left and 0 used, understating the monthly entitlement while the headline remains 5,463. Align the Founder allowance with the catalog (or source the authoritative allowance) and cover this case before using it as the cap.
benjcooley
left a comment
There was a problem hiding this comment.
Approved with one non-blocking SHOULD FIX.
| Severity | Count |
|---|---|
| TRIVIAL | 0 |
| SHOULD FIX | 1 |
| BLOCKER | 0 |
PR Created by the Glary-Bot Agent
Summary
The monthly row on the Plan & Credits tile could render more credits left than the plan grants — e.g.
4,201 left of 4,200— because the two numbers came from different unit systems; this clamps the displayed remaining to the allowance.Linear: FE-1451
Changes
computeMonthlyUsagenow returns aremainingclamped to0..monthlyTotal, withusedas its complement soused + remainingalways equals the allowance.CreditsTilebinds both the full and compactleft oflabels to it.computeMonthlyUsagehas exactly one consumer (CreditsTile.vue).Why the numbers disagreed
totalis the authoritative integer credit grant from the catalog (credit_grant: "4200").remainingwas reconstructed from the cents ledger: the backend converts the grant to cents with ceiling division (grant.go:331,ceil(4200×100/211) = 1991¢) and the frontend converts back (centsToCredits,round(1991 × 2.11) = 4201).4,200 is not reachable from any integer cents value under
round,floororceil(1990¢→4198.9, 1991¢→4201.01), so this is not fixable by changing a rounding direction. A grant round-trips losslessly only whencredits % 211 == 0, which is why 5 of 7 paid plans drift (standard-monthly +1, standard-annual +2, creator-monthly +2, creator-annual +1, founder-monthly +3) while pro-monthly/annual are clean by coincidence ($100 × 211).This PR is the display-layer stopgap only — the systemic fix (exposing authoritative integer credits from the API) is written up in detail on FE-1451 and deliberately deferred.
Verification
Rendered the real component with the exact value the backend grants for Standard (1991¢):
The
beforeline reproduces the reported screenshot exactly. Both new tests fail without the source change (verified by reverting only the two source files).Gates:
typecheckclean,lint0 errors (changed files warning-free),oxfmt --checkclean,knipclean, subscription suite 260/260. Fulltest:unithas 3 failures — all confirmed pre-existing on a cleanmain(GraphView,onboardingCloudRoutestimeouts, plus one load-dependent flake inpreviewAny); none touch credits.Review Focus
Two points raised in review that I deliberately did not change, with evidence:
1. Does the clamp hide legitimate rollover credits? No. The full balance stays on the headline. Rendering a large overage (Pro, 503,805 credits vs a 21,100 allowance):
Total creditsstill reports 503,805 either way — nothing is hidden from the user, and the monthly row now honours what its own label promises. Notebeforealready showed0 usednext to503,805 left of 21,100, so it was internally contradictory prior to this change. Monthly subscription credits also aren't designed to roll over (PrioritySubscription = 50 // Subscription credits expire at billing period end), so the overage is normally a conversion artifact rather than real carryover.2. The headline can still read 1 credit above
monthly + additional. Intentional. Clamping the headline would understate a balance the user can genuinely spend (the 4,201st credit is real — it exists in the cents ledger). The breakdown cannot sum exactly while the headline is reconstructed from cents and the allowance is an exact catalog integer; that is precisely the root cause deferred above, and it is not fixable in the view layer without lying about the balance.Also deliberate:
monthlyBonusCreditsremains exported fromuseSubscriptionCreditsthough this was its last consumer.knipaccepts it and it is symmetric withtotalCredits/prepaidCredits; removing it would pull in three more files against a minimal fix. Happy to drop it if preferred.Screenshots
Not included — the tile is gated behind an authenticated Comfy Cloud session with a live Metronome subscription, there is no Storybook story for
CreditsTile, and@comfyorg/design-systemships no prebuilt CSS so the themed component can't be rendered standalone without a full workspace build. The diff changes only an interpolated value inside an existing$t()call — no template structure, classes or styles — so there is no visual regression surface. Verified via real-component render output instead (above).