Skip to content

FEAT: add Resets view and earliest-expiry redemption - #48

Merged
merefield merged 4 commits into
mainfrom
feat/quota-reset-expiry
Sep 11, 2026
Merged

FEAT: add Resets view and earliest-expiry redemption#48
merefield merged 4 commits into
mainfrom
feat/quota-reset-expiry

Conversation

@merefield

@merefield merefield commented Sep 11, 2026

Copy link
Copy Markdown
Owner

Why

Reset credits can expire independently of quota windows. A count alone does not let users spot an expiring credit, and leaving redemption to the backend does not guarantee earliest-expiry selection. This adds a dedicated place to review reset details without crowding the quota gauges.

Changes

  • Add Quota → Resets to the responsive sub-tabs and v cycle, including saved view preferences and translated tab labels.
  • Show available counts, grant dates, expiry dates, backend descriptions and the next known credit. Support Up/Down and Page Up/Page Down scrolling.
  • Show an expiry warning and the reset shortcut when a known available credit expires in less than 72 hours, even below the configurable consumption threshold. The default consumption threshold remains 80%.
  • The warning is a separate clickable countdown before the normal reset button. It underlines on hover and opens Quota → Resets without arming confirmation or submitting a reset. Responsive sizing supports shortened labels and separate rows with shared rendering/hit geometry.
  • Add --reset-warning-hours HOURS (default 72, 0 disables expiry warnings and their consumption-threshold bypass). For testing known later expiries, use --reset-warning-hours 168. Invalid or overflowing durations are rejected before launching the UI.
  • Keep the Resets view available regardless of consumption; redemption still requires a supported source, verified account and fresh data.
  • Open Resets on the first reset-button press and require a second explicit confirmation within ten seconds.
  • Select the earliest-expiring known available quota-reset credit, with non-expiring credits last, and pass its creditId to the app-server.
  • Bind the chosen credit to confirmation and preserve its ID and idempotency key across uncertain retries. Reject fresh submissions when the selected credit disappears/expires or quota data becomes stale.
  • Document the behaviour in the README and intro post; keep pricing provenance off the credit-inventory view, which does not show price estimates.

Data limitations

Individual credit details are optional and may be capped by the backend. The view explicitly discloses partial/missing details. “Earliest” means earliest known expiry; undisclosed credits cannot be compared. With count-only data, redemption falls back to backend selection and the confirmation explains that its expiry order is unavailable. A supplied null expiry means the credit does not expire.

Confirmation explicitly warns that unused allowance does not carry over or stack and that the weekly reset schedule changes. Both the inventory and confirmation flag unavailable/incomplete expiry information: no warning does not mean no expiry, and undisclosed credits may expire sooner. These safety messages are translated across all supported locales. Expiry alerts are prompts to review, not recommendations to redeem immediately.

Verification

  • Full Go test suite.
  • Targeted reset UI/client race tests.
  • go vet ./... and git diff --check.
  • Regression coverage for credit decoding and wire-level credit selection, ordering, expiry warnings, partial details, stale data, confirmation/retry binding, responsive hit surfaces and scrolling.
  • Verify warning and button click surfaces across narrow/wide layouts and all 17 locales; test configurable lead times, disabled warnings and CLI validation.
  • Local binary rebuilt for review. No real reset was redeemed during testing.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

Preserve missing-versus-null expiry state and localize the newly added reset safety and inventory messages.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Adds a dedicated Quota → Resets view with expiry warnings and earliest-known-credit redemption.

Changes:

  • Adds responsive navigation, scrolling, preferences, and reset inventory details.
  • Adds configurable expiry warnings and credit-bound redemption retries.
  • Updates documentation, wire types, tests, and locale catalogues.
File summaries
File Summary
README.md Documents reset views, warnings, and redemption behavior.
main.go Adds warning-hours configuration.
main_test.go Tests CLI validation and propagation.
intro-post.md Documents the reset workflow.
internal/ui/view.go Renders the Resets view.
internal/ui/view_test.go Tests Resets rendering.
internal/ui/theme.go Adds the Resets view.
internal/ui/tabs.go Adds responsive navigation.
internal/ui/tabs_test.go Tests tab behavior.
internal/ui/reset_warning_test.go Tests warning visibility and geometry.
internal/ui/reset_credits_test.go Tests ordering, selection, scrolling, and confirmation.
internal/ui/quota_reset.go Implements reset inventory, warnings, and redemption.
internal/ui/quota_api_eq_test.go Covers pricing exclusions.
internal/ui/preferences.go Persists the Resets preference.
internal/ui/preferences_test.go Tests preference persistence.
internal/ui/model.go Adds reset state, navigation, and scrolling.
internal/ui/localisation_test.go Tests reset localization.
internal/ui/english_snapshot_test.go Updates presentation snapshots.
internal/i18n/locales/zh-Hans.json Adds Simplified Chinese translations.
internal/i18n/locales/tr.json Adds Turkish translations.
internal/i18n/locales/sv.json Adds Swedish translations.
internal/i18n/locales/ru.json Adds Russian translations.
internal/i18n/locales/pt-PT.json Adds European Portuguese translations.
internal/i18n/locales/pt-BR.json Adds Brazilian Portuguese translations.
internal/i18n/locales/nl.json Adds Dutch translations.
internal/i18n/locales/nb.json Adds Norwegian translations.
internal/i18n/locales/ja.json Adds Japanese translations.
internal/i18n/locales/it.json Adds Italian translations.
internal/i18n/locales/fr.json Adds French translations.
internal/i18n/locales/fi.json Adds Finnish translations.
internal/i18n/locales/et.json Adds Estonian translations.
internal/i18n/locales/es.json Adds Spanish translations.
internal/i18n/locales/en-GB.json Adds English catalogue entries.
internal/i18n/locales/de.json Adds German translations.
internal/i18n/locales/da.json Adds Danish translations.
internal/codex/types.go Adds reset-credit wire types.
internal/codex/reset_credits_test.go Tests credit decoding.
internal/codex/client.go Sends selected credit IDs.
internal/codex/client_test.go Tests targeted reset requests.
Review details

Suppressed comments (4)

internal/ui/quota_reset.go:382

  • The Resets inventory's count-only disclosure is hard-coded English. With partial/count-only data, non-English users will see Backend selects the next credit; expiry order is unknown. untranslated, so the safety disclosure is inconsistent with the translated expiry notices and the stated locale coverage; add a catalogue entry and translate this line.
	if len(credits) == 0 {
		return append(lines, "Backend selects the next credit; expiry order is unknown.")

internal/ui/quota_reset.go:292

  • This newly added stale-data notice, along with the analogous no-credit and disappearing-credit notices below, is emitted as a raw English literal. It makes the new confirmation safety path mixed-language outside en-GB; use translated i18n keys for these messages.
		if m.loading || m.err != nil || m.snapshot.FetchedAt.IsZero() || time.Since(m.snapshot.FetchedAt) > 2*m.refreshEvery {
			m.resetNotice = "Quota data is not fresh; refresh and confirm again. No reset submitted."
			return m, nil
		}
		if m.snapshot.RateLimitResetCredits == nil || m.snapshot.RateLimitResetCredits.AvailableCount <= 0 {

internal/ui/quota_reset.go:374

  • Most of the new inventory UI text in this renderer (AVAILABLE, the no-resets/backend-selection text, count summary, expiry/grant labels, and the expiry-soon warning) is hard-coded English. Since user-facing UI literals are routed through internal/i18n and every locale has a complete catalogue, the Resets view is only partially translated; add translated Text/Format keys for these labels.
	lines := []string{fmt.Sprintf("AVAILABLE // %d", summary.AvailableCount)}
	credits := m.availableResetCredits()
	if notice := m.resetExpiryDataNotice(); notice != "" {
		lines = append(lines, colors.label().Foreground(colors.warning).Render(notice))
	}

internal/ui/quota_reset.go:275

  • The new confirmation suffixes on these lines bypass the localization boundary: Expiry order unavailable... and Selected: ... remain English in every non-en-GB locale, despite the PR's requirement that the reset safety messages be translated. Route these strings through i18n.Text/i18n.Format and add the corresponding catalogue entries.
			m.resetNotice += " Expiry order unavailable; backend chooses the credit."
		} else {
			for _, credit := range m.availableResetCredits() {
				if credit.ID == m.resetCreditID {
					m.resetNotice += " Selected: " + resetCreditTitle(credit) + " — " + resetCreditExpiry(credit) + "."
  • Files reviewed: 39/39 changed files
  • Comments generated: 3
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread internal/codex/types.go
Comment thread internal/ui/quota_reset.go Outdated
Comment thread internal/ui/quota_reset.go Outdated
@merefield
merefield merged commit 82307f3 into main Sep 11, 2026
3 checks passed
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.

2 participants