Skip to content

fix(ui): humanize provider retry delay in the banner copy - #3402

Open
me2seeks wants to merge 1 commit into
apache:mainfrom
me2seeks:fix/desktop-retry-delay-format
Open

fix(ui): humanize provider retry delay in the banner copy#3402
me2seeks wants to merge 1 commit into
apache:mainfrom
me2seeks:fix/desktop-retry-delay-format

Conversation

@me2seeks

@me2seeks me2seeks commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Summary

With a long provider Retry-After (subscription quota window reset, e.g. kimi-k3 / OpenCode Go 5h quota), the desktop retry banner rendered a raw five-digit second count — 13565 秒后重试(2/10) — illegible and visually indistinguishable from a hang. The TUI strip humanizes the same wait as 4h 28m 3s (#3393).

providerRetryScheduled now formats in d/h/m/s units per locale: 4小时 28分 3秒后重试(2/10) / Retrying in 4h 28m 3s (2/10). One-second granularity is kept so the banner visibly ticks every second — the goal chip's minute-granularity ladder would reintroduce the frozen look between minute boundaries. Short delays keep the familiar seconds-only form.

Independent of #3400 (countdown ticking); either merge order works.

Fixes #3401

Verification

  • New conversation-copy.test.ts pins both locales across second/minute/hour/day scales and fails without the change.
  • ui suite on this head: 201 pass, biome lint/format clean. One pre-existing failure, composer-plus-menu › a loading catalog holds the row still, reproduces on the base commit (9a661a183) without this change — unrelated.
  • Not run: hosted checks (no CI on fork branches).

AI use

  • Generative tooling made a substantive contribution

Tool(s) and scope: Maka (AI agent) prepared the change end to end — implementation, tests, and PR text. The commit carries a Generated-by: Maka trailer.

Checklist

  • Tests cover the change and fail without it
  • Lint, format, typecheck and the affected suites pass locally

Does this PR entail a change in behavior?

  • Yes — described under Summary above

@jackwener jackwener left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review of exact head ef7097b50a91c7dc5284f00180db0ffe15a95fa4.

I confirmed the current main copy still renders provider waits as raw seconds, and the patch fixes that at the presentation boundary without changing retry policy. The d/h/m/s formatter is bounded, locale-specific, and the focused UI build plus new locale test passed locally (1/1). I found no P0-P2 correctness issue.

Merge readiness: not ready yet. This head has no hosted check result and still requires an independent human review; both gates should be present on this exact head before merge.

@Astro-Han Astro-Han left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this — the five-digit second count really does read as a hang, and fixing it per locale rather than with a generic duration formatter is the right call for copy that sits inside a sentence.

I verified the fix itself and it behaves as described. Two [P2]s and two [P3]s below, none of them about the desktop change being wrong.

The one I would like you to look at first is the premise in the PR description:

The TUI strip already humanizes the same wait as 4h 28m 3s (#3393).

At current main it does not. renderMakaPiActivityStrip (packages/cli/src/pi-transcript.ts:1407) still renders the retry countdown as Retrying in ${Math.max(1, Math.ceil(retry.delayMs / 1_000))}s, so the same quota window that produced 13565 秒后重试 in the desktop banner produces Retrying in 13565s in the TUI strip. What #3393 humanized is the elapsed counter on the line below (Working… <elapsed>, via formatElapsedDuration), which is a different string. That is line 1403-1407 and it is the only provider-retry render in the CLI, so the surface is not covered anywhere else.

That matters twice over, because formatElapsedDuration (pi-transcript.ts:1415) is the algorithm this PR just re-implemented — same [['d', 86_400], ['h', 3_600], ['m', 60]] table, same loop, same if (remaining > 0 || parts.length === 0) tail, same join(' '). The new formatProviderRetryDelay is that function with the unit strings lifted into a parameter.

I want to be fair about the cost of de-duplicating: formatElapsedDuration lives in packages/cli and the new one in packages/ui, so reuse is not a local import — the algorithm would have to be hoisted somewhere both can reach. The zh unit strings genuinely differ, so what is shared is the ladder, not the copy. That is real work, which is why this is a [P2] and not a merge blocker.

Nothing here blocks: no [P0]/[P1]. If you would rather land the desktop fix now and take the TUI strip and the shared ladder as a follow-up, that seems entirely reasonable to me — I would just ask that the PR description be corrected either way, since the sentence about the TUI currently describes a state the repository is not in, and the next person to touch this will trust it.

Review assisted by AI (Claude Opus 5). The findings above were verified against the files at this head; the reviewer is accountable for them.

*/
function formatProviderRetryDelay(seconds: number, units: ProviderRetryDelayUnits): string {
let remaining = Math.max(0, Math.floor(seconds));
const parts: string[] = [];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] This duplicates formatElapsedDuration in packages/cli/src/pi-transcript.ts:1415 exactly — same unit table, same loop, same zero/empty tail, same separator. The only real difference is that the unit strings became a parameter so zh can supply its own.

Simpler equivalent solution, spelled out: hoist this ladder to a package both ui and cli can import, taking the unit strings and separator as the parameter you already designed here, then have formatElapsedDuration and formatProviderRetryDelay both call it. That also makes the [P2] on the TUI strip a one-line change instead of a third copy.

Acknowledging the cost honestly: this is a cross-package move, not a local import, so it is more work than it looks. Not a blocker — but with the ladder now written twice, the third copy is the one that will drift.

minute: '分',
hour: '小时',
day: '天',
separator: ' ',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P3] separator: ' ' is shared by both locales, so zh renders 4小时 28分 3秒后重试. Chinese typography does not normally space between a number-unit group and the next one — 4小时28分3秒后重试 is the conventional form. Since separator is already per-locale in this interface, zh can just carry '' and en keep ' '; the test expectations would move with it.

Cosmetic, and a native reader should overrule me if they disagree.

assert.equal(zh(1, 2, 10), '1秒后重试(2/10)');
assert.equal(en(1, 2, 10), 'Retrying in 1s (2/10)');
assert.equal(zh(45, 2, 10), '45秒后重试(2/10)');
assert.equal(en(45, 2, 10), 'Retrying in 45s (2/10)');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P3] The description says short delays “keep the familiar seconds-only form (45秒后重试)”, but the previous zh copy was ${seconds} 秒后重试 — with a space before . This assertion pins '45秒后重试(2/10)', so the short-delay zh string does change, just subtly.

I think dropping the space is the better copy and I am not asking you to restore it. Only flagging that the description says this path is unchanged when it is not, so a reader diffing screenshots is not left confused.

Separately, and to the test's credit: pinning both locales across second/minute/hour/day scales is exactly the right shape here — it fails if the ladder regresses, rather than restating the implementation.

@me2seeks
me2seeks force-pushed the fix/desktop-retry-delay-format branch from ef7097b to d18d0c2 Compare August 22, 2026 13:43
A subscription quota window hands the runtime an hour-scale Retry-After,
and the desktop banner rendered it as a raw five-digit second count
('13565 秒后重试(2/10)') that reads as a frozen hang. Format the delay
in d/h/m/s units per locale ('4小时 28分 3秒后重试', 'Retrying in 4h 28m
3s') so the countdown stays legible and keeps ticking every second,
unlike the goal chip's minute-granularity ladder.

Generated-by: Maka
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Desktop retry banner shows raw seconds for long waits: '13565 秒后重试'

3 participants