Repository navigation
usage: forecast whether an allowance lasts to its reset - #977
Snowyyyyyy1 wants to merge 1 commit into
Conversation
yetone
left a comment
There was a problem hiding this comment.
Thanks for the careful write-up. I reviewed head 226a5e8 and ran it merged with current main (07326a3).
What holds up:
go test ./internal/provider/ ./internal/gui/passes.- The forecast tests pass with
-race -count=5. go vetpasses, and so do the windows and linuxnoguibuilds.- On the GUI side, 443 of 448 cases pass across 60 files (quota-, usage-, panel-, balance-, codex-, upstream-, gui-ja, gui-de, i18n-keys, click-scroll), in Chromium and WebKit.
I also ran QuotaHistories over a copy of a real quota-history.json (Claude, Codex, Copilot, Devin, Grok, Kiro, ZCode, Cursor, Qoder, WorkBuddy, Command Code). Claude's 7-day window at 7% left with half the week to go comes out as "runs out in 5.3h", which is right.
What needs fixing before it can merge:
-
quota-read-at.test.cjsfails 4/4 (Chromium and WebKit, en and zh). It times out waiting for.quota-curve .qc-key.- That test guards #802: hovering a window's key lists its latest readings with when each was read.
- The PR moves "Latest readings" into the plot's
title, and "{name}: {n} left, read {when}" onto the dot, but leaves the test as it was. It passes 4/4 on main. - Please keep #802's behaviour reachable and update the test to where it now lives.
- This file was already on your base (6892a28), so a run of the whole
quota-*set would have caught it.
-
"Too few readings" is shown where it isn't true. A window with readings but no forecast falls through
forecastVerdict(null)to "Too few readings". With the real history:- ZCode's "5 hours" has 15 readings and no forecast (no start/reset to place the cycle).
- Cursor, Qoder, WorkBuddy and Command Code windows have readings but no forecast.
- Codex "5 hours" has 37 readings, all at 100%, and gets state
none.
Please tell these apart: nothing used yet, no reset time to forecast against, or genuinely too few readings. Or show no verdict where there is nothing to say.
-
An early heavy burn is hidden.
- Kiro's monthly credits read 67% left about 6% into the cycle, with an even-burn level of 94%.
- That is under
forecastMinElapsed(8%), so the state isnone. - This is exactly the case a forecast should warn about: a third used in the first two days.
- Please let a clear run-out through before 8% (for example, gate on an absolute minimum elapsed time, or on the even-burn ETA being well short of the reset). Add a test with that reading.
-
The "Every model" / "By family" toggle (from reading the code).
familyQuota's click builds a newquotaWindowsbox. Its plots are drawn while detached, at the 520px fallback.- The per-box
quotaFitResizeObserver is gone, so the labels aren't re-fitted. - Only a size change of
#subscriptionUsageredraws them. Please redraw and re-fit after the toggle, and cover it inquota-forecast.test.cjs.
Interaction with #974 (Codex "Use credits" row): no textual conflict. Merged together, quota-curve.test.cjs "every window draws what was left over its own cycle" fails 4/4: the section after a Codex account now ends with quota-credits on. Whichever of the two merges second has to update that expectation. No conflict with #978.
|
Heads-up: #974 just merged (b37ac53) and adds a "Use credits" row ( |
Keep latest reading times on each plot, omit verdicts when there is no projection, and refit model windows after toggles. A young cycle warns after 30 minutes only when at least 10% was consumed and its even-burn ETA is within half the remaining reset time. Keep the folded account button visible when translated counts make neighboring cards tall. Validation: node syntax and gofmt; provider/gateway/gui tests, go vet, macOS/Linux/Windows builds; 50/50 focused Chromium/WebKit cases after the fix. The original 8% gate fails the Kiro early-burn regression; removing the new 10% floor fails the small-burn regressions. Full related UI run before the fold fix had only its two German fold cases fail (450/452), and the final targeted fold/key/forecast/read-at run passed 50/50. Full Go test has an unrelated netproxy Bun bridge failure also present in main.
226a5e8 to
4d92b3a
Compare
yetone
left a comment
There was a problem hiding this comment.
Thanks for the careful write-up. I reviewed head 226a5e8 and ran it merged with current main (07326a3).
What holds up:
go test ./internal/provider/ ./internal/gui/passes.- The forecast tests pass with
-race -count=5. go vetpasses, and so do the windows and linuxnoguibuilds.- On the GUI side, 443 of 448 cases pass across 60 files (quota-, usage-, panel-, balance-, codex-, upstream-, gui-ja, gui-de, i18n-keys, click-scroll), in Chromium and WebKit.
I also ran QuotaHistories over a copy of a real quota-history.json (Claude, Codex, Copilot, Devin, Grok, Kiro, ZCode, Cursor, Qoder, WorkBuddy, Command Code). Claude's 7-day window at 7% left with half the week to go comes out as "runs out in 5.3h", which is right.
What needs fixing before it can merge:
-
quota-read-at.test.cjsfails 4/4 (Chromium and WebKit, en and zh). It times out waiting for.quota-curve .qc-key.- That test guards #802: hovering a window's key lists its latest readings with when each was read.
- The PR moves "Latest readings" into the plot's
title, and "{name}: {n} left, read {when}" onto the dot, but leaves the test as it was. It passes 4/4 on main. - Please keep #802's behaviour reachable and update the test to where it now lives.
- This file was already on your base (6892a28), so a run of the whole
quota-*set would have caught it.
-
"Too few readings" is shown where it isn't true. A window with readings but no forecast falls through
forecastVerdict(null)to "Too few readings". With the real history:- ZCode's "5 hours" has 15 readings and no forecast (no start/reset to place the cycle).
- Cursor, Qoder, WorkBuddy and Command Code windows have readings but no forecast.
- Codex "5 hours" has 37 readings, all at 100%, and gets state
none.
Please tell these apart: nothing used yet, no reset time to forecast against, or genuinely too few readings. Or show no verdict where there is nothing to say.
-
An early heavy burn is hidden.
- Kiro's monthly credits read 67% left about 6% into the cycle, with an even-burn level of 94%.
- That is under
forecastMinElapsed(8%), so the state isnone. - This is exactly the case a forecast should warn about: a third used in the first two days.
- Please let a clear run-out through before 8% (for example, gate on an absolute minimum elapsed time, or on the even-burn ETA being well short of the reset). Add a test with that reading.
-
The "Every model" / "By family" toggle (from reading the code).
familyQuota's click builds a newquotaWindowsbox. Its plots are drawn while detached, at the 520px fallback.- The per-box
quotaFitResizeObserver is gone, so the labels aren't re-fitted. - Only a size change of
#subscriptionUsageredraws them. Please redraw and re-fit after the toggle, and cover it inquota-forecast.test.cjs.
Interaction with #974 (Codex "Use credits" row): no textual conflict. Merged together, quota-curve.test.cjs "every window draws what was left over its own cycle" fails 4/4: the section after a Codex account now ends with quota-credits on. Whichever of the two merges second has to update that expectation. No conflict with #978.
yetone
left a comment
There was a problem hiding this comment.
Thanks for the rework. All four points from the last review are addressed. I checked 4d92b3a:
go test ./internal/provider ./internal/guipasses under a temp HOME.- 56 GUI files pass in Chromium and WebKit (quota-, usage-, panel-, balance-, click-scroll, gui-ja, gui-de, i18n-keys).
- The Usage page at 1100 and 560px and the tray at 440px have no sideways scroll and no page errors in en, zh, ja or de.
Two things still block the merge:
-
It no longer merges with main.
docs/subsystems/providers-accounts.mdconflicts with b0a96cb (#1004, Sub2API key 5h/1d/7d windows), which landed today. Please rebase. #1004 also adds windows to Sub2API keys, so check that the forecast and the usage card behave on a key that has them. -
German tray at 440px: the verdict is cut off.
.pq-verdict > spanellipsizes (internal/gui/assets/app.css:1362), and the German margin label is long. The Claude Code 5-hour line reads "Reicht bis zum Reset · 1.3× Res…", and Gemini's reads "Reicht bis zum Reset · 1.4× Res…".titleholds the full text, but the tray has room for a second line. When a line carries both the multiplier and theVerlaufmarker (app.js:13749), the marker goes first.
Repro:- Open
?mode=panel, lang de, viewport 440×800, Allowances tab, with the fixtures fromquota-forecast.test.cjs. - Compare
span.scrollWidthwithspan.clientWidthon each.pq-verdict.
en, zh and ja fit. Fix: let the text wrap (for example
white-space: normalwith thebkeptflex: none), or shorten the German string. Please add a check to the quota-forecast tray test that no.pq-verdicttext overflows in every language. - Open
Keep latest reading times on each plot, omit verdicts when there is no projection, and refit model windows after toggles. A young cycle warns after 30 minutes only when at least 10% was consumed and its even-burn ETA is within half the remaining reset time. Keep the folded account button visible when translated counts make neighboring cards tall. Validation: node syntax and gofmt; provider/gateway/gui tests, go vet, macOS/Linux/Windows builds; 50/50 focused Chromium/WebKit cases after the fix. The original 8% gate fails the Kiro early-burn regression; removing the new 10% floor fails the small-burn regressions. Full related UI run before the fold fix had only its two German fold cases fail (450/452), and the final targeted fold/key/forecast/read-at run passed 50/50. Full Go test has an unrelated netproxy Bun bridge failure also present in main.
… sub2api key's own windows are covered (yetone#977) The reviewer on yetone#977 found that at 440px German's tray verdict ("Reicht bis zum Reset · 1.4× Reserve Verlauf") was ellipsized: 215px of text in a 170px box, cut to "…1.4× Res…". The Usage page's line fits; the tray is the narrower one. - app.css: .pq-verdict > span wraps (white-space: normal, overflow-wrap: anywhere) instead of clipping; the window label (b) stays flex: none. - quota-forecast.test.cjs: the tray test measures the rendered box in every language and both themes at 440px — no line overflows its own box (scroll vs client, both axes) and no verdict, label or text leaves its card — without scrolling a card into view. It also adds a sub2api key whose own 5-hour, day and 7-day windows (5 hours / 1 day / 7 days, USD) draw on the Usage card with the forecast, the week's line blending history (yetone#1004).
4d92b3a to
c82a4b8
Compare
|
Sorry for the slow review on this one. At 5fe3470 the two blockers from the last review are addressed in the code: the German tray line wrap, and the Sub2API key windows from #1004. I read those changes but didn't run them at this head, because the branch no longer merges onto main (origin/main 5b14ebf). These four files conflict:
Main has no forecast of its own, so the feature is still wanted. Could you rebase onto main? Please put the forecast verdict onto main's |
Rebased onto main as one commit: main had rewritten the allowance cards (packUsage, quotaFit, the past-cycle picker, colorblind dashes, Pace, quotaEta) since the PR's base, so the verdict goes onto main's own code rather than the PR's earlier per-window plots. - provider: QuotaHistories gives each line a derived forecast from the window's whole stored series (quota_forecast.go): the current cycle's rate against an even burn, and for windows of two days or more the well-covered past cycles, the more conservative winning. ok, spent or none; a clear early burn warns before 8% of the cycle. Cycle breaks are main's newCycle. Never persisted or synced. - Usage card: quotaVerdict puts "Lasts to reset · 1.3×", "Runs out in …" or "Used up" on a line of its own on each window's reset line, after Pace, with a "history" marker when past cycles answered. None for no forecast, state none, no readings, a brief account or a past cycle. Remote gateway-key cards and Sub2API key windows (yetone#1004) get it too. - Tray: one compact line a window under the rings, wrapping, not cut. - The PR's ahead/behind chip is left out: main's Pace says it. - Strings in zh, zh-TW, ja, de. - core_budget_test: core scripts 1,426,000 -> 1,430,000 bytes, app.js 1,250,000 -> 1,254,000: the verdict is shared by the Usage cards and the tray, which stay in the core. Run on macOS, Go 1.27.2, Node with Playwright 1.62.1. The browser suite and the Go provider runs were at c51affd1 (on 1eba266); the rebase onto 138188a (README and site only) and the budget change followed, and go test ./internal/gui was run again at b3aaae83, whose tree equals this commit's: - go test -timeout 45m ./internal/provider (temp HOME): ok. - go test -race -count=3 -run Forecast ./internal/provider: ok. - go test ./internal/gui (temp HOME, unsandboxed): all ok except TestProxyCheckSaysAProxyPassedBy and TestMainWindowCorners, which fail the same way at origin/main 138188a on this machine (run twice there). - go vet ./internal/provider ./internal/gui, GOOS=linux -tags nogui and GOOS=windows: clean. gofmt -l internal/: clean. - quota-forecast.test.cjs, Chromium and WebKit, en/zh/ja/de, light and dark, Usage at 1180 and 560px, tray at 440px (and 260px for the wrap). Broken by hand, each goes red: the past-cycle guard removed, a verdict for an absent forecast, the 10x cap removed, the tray's wrap replaced by an ellipsis (red at 260px in German). - Then added: quota-forecast's "Every model" toggle case (an Antigravity card's pool windows keep their verdict after the toggle and back; the models' own windows have none). The whole file, Chromium and WebKit, 26 of 26, twice. quotaWindowLine broken by hand to ignore a pool window's own name turns it red. - node --test, concurrency 2: every quota-*, usage-*, panel-* file, plus i18n-keys, gui-zh-tw, gui-de, gui-ja, balance-curve: 996 tests, 961 pass, 35 fail. Run alone, file by file, at c51affd1 and at origin/main 138188a: quota-count-wrap (1), quota-name-translated (1), usage-agents-hide (22), usage-checkin (2), usage-curve-zoom-back (1), usage-fold (2), usage-range (2) fail the same count on both; usage-focus-steady passes alone on both (red only under the full run). None of their fixtures carries a forecast. Not tried: the real app window or tray, Linux/Windows GUI.
5fe3470 to
ac45532
Compare
Rebased onto main as one commit: main had rewritten the allowance cards (packUsage, quotaFit, the past-cycle picker, colorblind dashes, Pace, quotaEta) since the PR's base, so the verdict goes onto main's own code rather than the PR's earlier per-window plots. - provider: QuotaHistories gives each line a derived forecast from the window's whole stored series (quota_forecast.go): the current cycle's rate against an even burn, and for windows of two days or more the well-covered past cycles, the more conservative winning. ok, spent or none; a clear early burn warns before 8% of the cycle. Cycle breaks are main's newCycle. Never persisted or synced. - Usage card: quotaVerdict puts "Lasts to reset · 1.3×", "Runs out in …" or "Used up" on a line of its own on each window's reset line, after Pace, with a "history" marker when past cycles answered. None for no forecast, state none, no readings, a brief account or a past cycle. Remote gateway-key cards and Sub2API key windows (yetone#1004) get it too. - Tray: one compact line a window under the rings, wrapping, not cut. - The PR's ahead/behind chip is left out: main's Pace says it. - Strings in zh, zh-TW, ja, de. - core_budget_test: app.js 1,250,000 -> 1,254,000 bytes (1,252,431 with the verdict): it is shared by the Usage cards and the tray, which stay in the core. The core total stays within main's 1,447,000 (yetone#2612). Run on macOS, Go 1.27.2, Node with Playwright 1.62.1, at this commit on origin/main cb05dc2: - go vet ./internal/provider ./internal/gui, GOOS=linux -tags nogui and GOOS=windows builds, gofmt -l internal/, node --check app.js, git diff --check: clean. - go test -race -count=1 -run 'Forecast|QuotaHist' ./internal/provider: ok. - go test ./internal/gui (temp HOME): all ok except TestProxyCheckSaysAProxyPassedBy and TestMainWindowCorners, which fail the same way at origin/main on this machine. - node --test, concurrency 2: every quota-*, usage-*, panel-* file, plus i18n-keys, gui-zh-tw, gui-de, gui-ja, balance-curve: 1000 tests, 969 pass, 31 fail; quota-forecast passes in full. Run alone, file by file, here and at origin/main cb05dc2: quota-count-wrap (1), quota-name-translated (1), usage-agents-hide (22), usage-checkin (2) and usage-fold (2) fail the same count on both. usage-curve-zoom-back's Chromium 1100px case is flaky on both: four runs each failed 5/3/3/4 on main and 1/4/5/5 here. None of their fixtures carries a forecast. Earlier, at c51affd1 (on 1eba266): go test -timeout 45m ./internal/provider ok; quota-forecast.test.cjs in Chromium and WebKit, en/zh/ja/de, light and dark, Usage at 1180 and 560px, tray at 440px and 260px, 26 of 26 twice, each check seen red with the code broken by hand (the past-cycle guard, a verdict for an absent forecast, the 10x cap, the tray's wrap, quotaWindowLine ignoring a pool window's own name). Not tried: the real app window or tray, Linux/Windows GUI.
ac45532 to
44e1789
Compare
The Usage page shows each allowance's readings and what is left, but users still have to estimate whether their current use will last until the next reset. This adds a forecast to each observed allowance window and shows its verdict on the Usage page and in the tray.
Rebased onto current main as one commit (
44e1789a, on maincb05dc2d). Main has rewritten the allowance cards since this PR's base (packUsage,quotaFit, the past-cycle picker, colorblind dashes, Pace,quotaEta), so the verdict goes onto main's own code. The PR's earlier per-window plots and its ahead/behind chip are gone; main's Pace already says ahead or behind.Behavior
QuotaHistoriesgives each line a forecast derived from the window's whole stored series (internal/provider/quota_forecast.go). It compares the current cycle's rate with an even burn. For windows of two days or more it also uses the well-covered past cycles, and the more conservative result wins. The state isok,spentornone. A clear early burn warns before 8% of the cycle has passed. Cycle breaks use main'snewCycle. The forecast is derived on read and is never persisted or synced.quotaVerdictadds "Lasts to reset · 1.3×", "Runs out in …" or "Used up" on a line of its own after each window's reset line and Pace. A "history" marker shows when past cycles decided the answer. No verdict is shown for a missing forecast, statenone, no readings, a brief account or a past cycle. Remote gateway-key cards and Sub2API key windows (gateway, provider, gui: a Sub2API key given 5h/1d/7d limits is weighed and rested by its own windows, like an account #1004) get the verdict too.core_budget_test:app.js1,250,000 → 1,254,000 bytes (1,252,431 with the verdict). The verdict is shared by the Usage cards and the tray, which stay in the core. The core total stays within main's 1,447,000 (test: TestCoreScriptBudget red on main since 1978eabe5 (core scripts 1,426,102 > 1,426,000) #2612).The subsystem reference is updated in
docs/subsystems/providers-accounts.md.Validation
macOS, Go 1.27.2, Node with Playwright 1.62.1, at
44e1789aon origin/maincb05dc2d:go vet ./internal/provider ./internal/gui,GOOS=linux -tags noguiandGOOS=windowsbuilds,gofmt -l internal/,node --checkonapp.js,git diff --check: clean.go test -race -count=1 -run 'Forecast|QuotaHist' ./internal/provider: ok.go test ./internal/gui(temporary HOME): all ok exceptTestProxyCheckSaysAProxyPassedBy(HTTP 503 from the sandboxed network) andTestMainWindowCorners(macOS title bar). Both fail the same way on origin/main on this machine.quota-*,usage-*andpanel-*file plusi18n-keys,gui-zh-tw,gui-de,gui-jaandbalance-curve(concurrency 2): 969 of 1000 pass.quota-forecastpasses in full. Each failing file was then run alone here and on origin/maincb05dc2d.quota-count-wrap(1),quota-name-translated(1),usage-agents-hide(22),usage-checkin(2) andusage-fold(2) fail the same count on both.usage-curve-zoom-back's Chromium 1100px case is flaky on both: four runs each failed 5/3/3/4 on main and 1/4/5/5 here. None of these fixtures carries a forecast.Earlier, at
c51affd1(on main1eba266a):go test -timeout 45m ./internal/provider(temporary HOME): ok.quota-forecast.test.cjscovers Chromium and WebKit, en/zh/ja/de, light and dark, Usage at 1180 and 560px, and the tray at 440px (plus 260px for the wrap): 26/26, twice. Each check was confirmed by breaking the code on purpose and watching it fail. The breaks were: removing the past-cycle guard, showing a verdict for a missing forecast, removing the 10× cap, replacing the tray's wrap with an ellipsis (fails at 260px in German), and makingquotaWindowLineignore a pool window's own name (fails the "Every model" toggle case).Not tried: the real app window or tray, Linux or Windows GUI, a live Sub2API key.
🎬 界面预览
CI 用这个 PR 的代码构建并真实运行 magpie(沙盒环境,配置了真实的 DeepSeek key),按改动自动操作、截图和录屏 ·
44e1789· 运行记录改动(按代码):PR 在用量页的额度卡片和菜单栏托盘的额度卡片上新增预测行:撑得到重置(带余量倍数/历史标记)、预计用完或已用完,并补齐 zh、zh-TW、ja、de 文案与样式。
Note
沙盒里看不到:沙盒的实时额度只出现「撑得到重置」这类预测,无法展示「预计用完」「已用完」、无预测或 state 为 none 时不显示的分支,也无法展示托盘 440px 德语长句换行;这些需要构造额度历史或切换语言才能看到。
用量页的额度预测
Codex 额度卡片:每个窗口的重置与配速后多出的预测行
「7 天」窗口的预测行末尾带「历史」标记
托盘额度页的预测行
托盘额度卡片圆环下方的按窗口预测行