Skip to content

fix(dashboard): explain the credits pill instead of hiding it when the usage scrape is opted out - #7628

Merged
bolichen97 merged 1 commit into
kirodotdev:mainfrom
jeeshofone:fix/7623-credits-pill-scrape-disabled
Sep 6, 2026
Merged

fix(dashboard): explain the credits pill instead of hiding it when the usage scrape is opted out#7628
bolichen97 merged 1 commit into
kirodotdev:mainfrom
jeeshofone:fix/7623-credits-pill-scrape-disabled

Conversation

@jeeshofone

Copy link
Copy Markdown
Contributor

Problem / Motivation

The credits usage indicator vanished from the dashboard's top bar between v0.1.3 and v0.4.1 (#7623). The reporter's account has a working plan (kiro-cli /usage answers fine by hand), yet the top bar shows only CPU/MEM/DSK — no credits slot at all, on any dashboard surface, with no settings toggle to be found.

Why it matters

The pill didn't break — it was silently degraded. #2039 rightly made the billed /usage text scrape opt-in (dashboard.usage_text_scrape_enabled, default off), and degraded to available: false when it's off and the free GetUsageLimits read returns no plan. The frontend answers that signal by hiding the pill entirely. For every account where the API path yields no plan, users went from "pill rendered (via scrape)" to "pill silently absent", with nothing anywhere naming the knob that restores it. A permanent, user-addressable state was rendered indistinguishable from "this provider has no credits".

What changed (motivation → approach → change)

Observed symptom: pill absent → root cause: the scrape-disabled degrade reuses the same bare available: false marker as the definitive "kiro-cli absent" verdict, so the frontend cannot tell "nothing to show, ever" from "off until you flip a documented setting" → change: distinguish the states and explain instead of hiding, following the existing api_key_auth precedent (a terminal state that renders a dimmed dash + explanation rather than hiding):

  • sessions.py: _cache_without_scrape accepts an optional reason; the scrape-disabled call site passes reason="scrape_disabled". The kiro-cli-absent and scrape-backoff markers are unchanged (the former should keep hiding the pill; the latter is a transient parked state, out of scope here).
  • App.tsx: classifies reason === 'scrape_disabled' into a new 'scrape-disabled' state and renders the same terminal dash as 'api-key', with a tooltip naming dashboard.usage_text_scrape_enabled.
  • KiroAccountModal.tsx: the 'scrape-disabled' state gets a fuller explanation (free API returned no plan, billed fallback off by default, how to enable it, and that enabling spends a small number of credits per refresh).
  • i18n: two new keys in en.manual.json plus translations in all 11 target catalogs; en-XA regenerated via npm run i18n:pseudo.

Tests

  • test_disabled_marker_names_the_reason — the scrape-disabled unavailable marker carries reason: "scrape_disabled".
  • test_no_kiro_bin_marker_stays_reason_free — the definitive kiro-cli-absent marker stays exactly {"available": False}, so the hide-the-pill behavior for non-Kiro providers is pinned.
  • KiroAccountModal.test.tsx: the 'scrape-disabled' state renders the knob-naming explanation, not the generic unavailable line, and does not spin.
  • All 95 tests in test/test_session_usage.py and all 12 in KiroAccountModal.test.tsx pass; tsc -b clean.

Manual verification

N/A beyond unit coverage for the state plumbing — the new state's render path is byte-for-byte the existing 'api-key' branch (same classes, same dash glyph); only the title/aria-label string and the modal message differ, both asserted in tests.

Screenshots / video

The rendered pixels are identical to the existing API-key unavailable state (dimmed Coins glyph + dash, same classes) — no new visual variant is introduced; the change is which STATE reaches that rendering plus the explanatory strings. Happy to add a seeded-harness capture if reviewers want one.

Related Issues

Fixes #7623

Pattern harvest

Rule candidate: review-prompt
Pattern: "a degrade path reuses another state's sentinel payload, making a user-addressable condition indistinguishable from a terminal one — when adding an opt-out that degrades UI, carry a machine-readable reason and surface the knob"

Checklist

  • At most two commits (one is the norm), with a Conventional Commits title (feat|fix|docs|refactor|perf|test|chore|ci|build|revert: ...)
  • Existing tests pass and new tests added for new functionality
  • Self-review completed; code follows project style guidelines
  • Documentation updated (if applicable)
  • No secrets, credentials, or internal references in the diff

@jeeshofone
jeeshofone requested a review from a team September 1, 2026 13:50
@jeeshofone
jeeshofone requested a review from a team as a code owner September 1, 2026 13:50
@jeeshofone
jeeshofone requested a review from smeyffret September 1, 2026 13:50
@github-actions github-actions Bot added fork Pull request from a fork (external contributor) readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running and removed readiness: action required A blocking check or review needs attention labels Sep 1, 2026
@github-actions github-actions Bot added readiness: action required A blocking check or review needs attention and removed readiness: checking Automated validation is still running labels Sep 1, 2026
@jeeshofone
jeeshofone force-pushed the fix/7623-credits-pill-scrape-disabled branch from 1d05f96 to ab538a7 Compare September 1, 2026 14:44
@github-actions github-actions Bot added readiness: checking Automated validation is still running readiness: action required A blocking check or review needs attention and removed readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running labels Sep 1, 2026
@jeeshofone
jeeshofone force-pushed the fix/7623-credits-pill-scrape-disabled branch from ab538a7 to d62bb63 Compare September 1, 2026 15:28
@github-actions github-actions Bot added readiness: checking Automated validation is still running and removed readiness: action required A blocking check or review needs attention labels Sep 1, 2026
@jeeshofone
jeeshofone force-pushed the fix/7623-credits-pill-scrape-disabled branch 2 times, most recently from 0a6bcac to 2a4b7bf Compare September 1, 2026 18:43
@github-actions github-actions Bot added readiness: action required A blocking check or review needs attention and removed readiness: checking Automated validation is still running labels Sep 1, 2026
@jeeshofone
jeeshofone force-pushed the fix/7623-credits-pill-scrape-disabled branch from 2a4b7bf to f8d2658 Compare September 2, 2026 09:21
@github-actions github-actions Bot added readiness: checking Automated validation is still running and removed readiness: action required A blocking check or review needs attention labels Sep 2, 2026
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

First Principles Review (Fable 5, fork) — ✅ PASS

Premise-level review of 25e0b2ab695fe28bc66bde992e61d14e4bb59964 via the fork AI-review pipeline — why this exists and whether the shipped surface is the smallest honest version. Updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

I've verified the change against the trusted base: the reason field on the unavailable marker already exists there (api_key_auth at sessions.py:741), _cache_without_scrape has exactly two call sites (lines 799 and 802), and the knob dashboard.usage_text_scrape_enabled is a real config key. The change extends the existing reason mechanism rather than inventing a parallel one, and the one sibling left bare (the backoff path) is explicitly declared out of scope in the description with a rationale. Final review:

First-Principles-Verdict: PASS

A reported defect (#7623) fixed at its cause — the reused bare sentinel — by extending the existing reason mechanism, with nothing riding along.

What this change ships

Intent: let a user whose credits pill silently vanished see why and learn the setting that restores it. This is a FIX.

  1. Credits pill shows a dimmed dash instead of vanishing when the billed scrape is opted out — justified (reported defect Restore credits usage indicator in dashboard top bar #7623, cause-level)
  2. Pill tooltip names dashboard.usage_text_scrape_enabled — justified (the missing hint IS the defect)
  3. Account modal explains the state, the knob, and its credit cost — justified
  4. Unavailable marker carries reason: "scrape_disabled" — justified; extends the existing reason field (api_key_auth, sessions.py:741), not a second spelling
  5. kiro-cli-absent marker stays bare, pill still hides — justified, pinned by test
  6. Two strings × 12 locale catalogs — justified (documented i18n invariant)

Counts run: _cache_without_scrape has 2 call sites (sessions.py:799, 802); one gains the reason, the backoff site is left bare and the description names it out of scope ("a transient parked state"). The reason wire field has 1 pre-existing producer and 1 frontend consumer in base; this adds one value to each side — no zero-consumer surface, no generalization.

[FIRST-PRINCIPLES-REVIEWED] 25e0b2a

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Design Review (Fable 5, fork) — ✅ PASS

Design-level review of 25e0b2ab695fe28bc66bde992e61d14e4bb59964 via the fork AI-review pipeline — updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

The verification confirms everything: the reason field precedent (api_key_auth at sessions.py:741) exists in base, the change extends it additively, the kiro-cli-absent hide path is untouched and pinned by a new test, and the deliberately-scoped-out backoff path is transient and self-healing. The change is additive (new optional field, new frontend state), fully reversible, and follows the existing terminal-state rendering pattern rather than inventing a new one.

Design-Verdict: PASS

Extends the existing api_key_auth reason precedent additively; distinguishes a user-addressable state from a terminal one at the layer that knows the difference.

[DESIGN-REVIEWED] 25e0b2a

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

GPT 5.6 Review (fork) — ✅ no blocking findings

Reviewed 25e0b2ab695fe28bc66bde992e61d14e4bb59964 via the fork AI-review pipeline; updated in place on each push.

Review details

No findings.
[GPT-REVIEWED] 25e0b2a

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

UX Review (Fable 5, fork) — ✅ PASS

UX-level review of 25e0b2ab695fe28bc66bde992e61d14e4bb59964 via the fork AI-review pipeline — updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

The knob is re-read via KiroCrewConfig.load() on every background refresh (sessions.py:174), so the modal's instruction takes effect without a restart — the promised repair path actually works. The new state mirrors the existing api-key terminal pill exactly (same dimmed dash, same click-through-to-modal pattern), the modal copy states what happened, what to do, and the credit cost of doing it, and all 12 locales plus pseudo-locale carry the strings. The only real nit is the pill tooltip's "is opted out" phrasing, which implies a choice the user never made — the scrape is off by default.

UX-Verdict: PASS

A silently-vanishing pill becomes an explained, actionable state that mirrors the existing api-key pattern; the fix instruction works live, no restart needed.

Suggestions

  • app.kiro_credit_usage_scrape_disabled: "live fetch is opted out" implies the user chose this — it's off by default, and most locales already translated it as "disabled". Use "live fetch is off (dashboard.usage_text_scrape_enabled)".

[UX-REVIEWED] 25e0b2a

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Opus 4.8 Review (fork) — ✅ no blocking findings

Reviewed 25e0b2ab695fe28bc66bde992e61d14e4bb59964 via the fork AI-review pipeline; updated in place on each push.

Review details

I've independently verified the change:

  • Backend (sessions.py): reason="scrape_disabled" is threaded only into the else (unavailable) branch of _cache_without_scrape; the preserve/stale branch and the backoff caller correctly omit it. The disabled path is only reached when the free API returned no credits_plan, so no plan-bearing value is mislabeled.
  • Frontend (App.tsx, KiroAccountModal.tsx): backend emits reason: "scrape_disabled"; frontend matches u.reason === 'scrape_disabled' → the 'scrape-disabled' sentinel, renders a terminal dash with a labeled knob, and the modal type union + render branch handle it. Sentinel names match on both sides.
  • i18n parity: both new keys (app.kiro_credit_usage_scrape_disabled and components.kiroAccountModal.credit_usage_scrape_disabled) are added to all 11 translated locales plus en-XA and the hand-authored en.manual.json. en.json is generated and these keys live in en.manual.json, so the union parity the gate enforces holds. Placeholders/wire identifiers preserved verbatim.

No candidate to falsify (discovery produced none), and no new grounded defect at 80+.

No findings.

[OPUS-REVIEWED] 25e0b2a

@github-actions github-actions Bot added readiness: passed Eligible automated validation passed for the current revision and removed readiness: checking Automated validation is still running labels Sep 2, 2026
@jeeshofone
jeeshofone force-pushed the fix/7623-credits-pill-scrape-disabled branch from f8d2658 to 40b4abd Compare September 3, 2026 00:12
@github-actions github-actions Bot added readiness: checking Automated validation is still running readiness: action required A blocking check or review needs attention and removed readiness: passed Eligible automated validation passed for the current revision readiness: checking Automated validation is still running labels Sep 3, 2026
…e usage scrape is opted out

The free GetUsageLimits read returning no plan plus the (default-off)
billed /usage scrape degraded to available:false, which the frontend
answers by hiding the top-bar credits segment entirely — users coming
from v0.1.3 saw the pill silently vanish with no hint a knob exists.

Carry reason=scrape_disabled on the unavailable marker and render the
same terminal dash the api-key state uses, with a label and modal
message that name dashboard.usage_text_scrape_enabled.

Fixes kirodotdev#7623
@jeeshofone
jeeshofone force-pushed the fix/7623-credits-pill-scrape-disabled branch from 40b4abd to 25e0b2a Compare September 3, 2026 02:06
@github-actions github-actions Bot added readiness: checking Automated validation is still running readiness: passed Eligible automated validation passed for the current revision and removed readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running labels Sep 3, 2026
@bolichen97

Copy link
Copy Markdown
Collaborator

Open PR relationship audit

This is a consolidated, point-in-time code-level audit note. It compares complete merge-base diffs and current/merged code; it does not treat a shared topic as duplication or partial coverage as completion.

Relationship findings

  • This PR is OVERLAPPING with PR #2039. The goals differ or the implementations can complement each other; this is not a duplicate claim. Recommended action for PR #7628: KEEP. The merged opt-in is the cause 7628 explains rather than reverts; it covers none of 7628's behavior. Files: src/kiro_crew/dashboard/handlers/sessions.py.
  • This PR is OVERLAPPING with PR #4085. The goals differ or the implementations can complement each other; this is not a duplicate claim. Recommended action for PR #7628: KEEP. Textual adjacency only. A cosmetic glyph-standardization sweep and a new usage state are independent and both landable. Files: website/src/App.tsx.
  • This PR is OVERLAPPING with PR #5750. The goals differ or the implementations can complement each other; this is not a duplicate claim. Recommended action for PR #7628: KEEP. Precedent, not coverage: 5750 established the reason field and the terminal-dash rendering for a different state, which is why 7628 is small and additive. Files: src/kiro_crew/dashboard/handlers/sessions.py, website/src/App.tsx.
  • This PR is OVERLAPPING with PR #6307. The goals differ or the implementations can complement each other; this is not a duplicate claim. Recommended action for PR #7628: KEEP. Different user goals on the same rendering gate; neither subsumes the other and both are wanted. Note the merge order for the App.tsx hunk. Files: website/src/App.tsx, src/kiro_crew/dashboard/handlers/sessions.py.

No PR, Issue, label, branch, or review state was changed by the relationship-note portion of this audit.

@bolichen97 bolichen97 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Approved as Tech Lead review (also satisfies the kirocrew-ux-reviewers requirement of ruleset ux-review-required for the website/src/** files).

Correctness. The fix is at the cause: _cache_without_scrape gained an optional reason and only the scrape-disabled call site passes reason="scrape_disabled". This extends the pre-existing api_key_auth reason mechanism rather than inventing a parallel one, and the definitive kiro-cli-absent marker stays exactly {"available": False} so non-Kiro providers keep hiding the pill — pinned by test_no_kiro_bin_marker_stays_reason_free. The frontend explains rather than misleads: the tooltip and modal both name the exact config key dashboard.usage_text_scrape_enabled, and the modal additionally discloses that enabling it spends credits per refresh, so the user is not steered into a billed path unknowingly.

i18n. No hardcoded user-facing strings — both new strings go through i18nT, the keys land in en.manual.json (correct, since en.json is generated) plus all 11 target catalogues, and en-XA was regenerated via the pseudo-locale script. Union parity holds.

Scope. 18 files but only 3 are non-test/non-locale, and the deliberately-excluded sibling (the transient scrape-backoff marker) is called out with a rationale rather than silently skipped.

Screenshot Evidence gate. I applied the documented no-screenshots maintainer waiver after verifying the claim rather than merging over a red gate. The new 'scrape-disabled' branch in App.tsx is byte-identical to the existing 'api-key' branch (same ${seg} text-muted opacity-60, same Coins size={12}, same glyph); only the i18n key differs. That is precisely the "non-visual change to a visual file" the waiver exists for.

Non-blocking follow-up. The UX lane's nit is fair and worth a future one-line fix: app.kiro_credit_usage_scrape_disabled reads "live fetch is opted out" in English, which implies an act the user never performed (the scrape is off by default) — and every translated catalogue already renders it as "disabled"/"deactivated", so English is the outlier. Not blocking: the string names the exact knob, so the repair path is unambiguous, and the modal copy states "off by default" accurately.

@bolichen97
bolichen97 merged commit 64d8782 into kirodotdev:main Sep 6, 2026
72 of 73 checks passed
@github-actions github-actions Bot removed the readiness: passed Eligible automated validation passed for the current revision label Sep 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fork Pull request from a fork (external contributor) no-screenshots PR has no visual delta; screenshot gate exempt

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Restore credits usage indicator in dashboard top bar

2 participants