feat: fair-use status frontend (web + mobile)#5770
Conversation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Code Review — yuki (driver, backend PR #5748)API integration, l10n, stage colors, and settings placement all look correct. 3 items need fixing: Blockers1. BUG: Web error handling shows false "Normal" on API failure 2. Flutter 3. Logging security Non-blocking suggestions
by AI for @beastoin |
Greptile SummaryThis PR adds the frontend integration for the fair-use anti-abuse system across both Flutter (mobile) and Next.js (web), displaying speech usage, stage status, case reference, and policy info fetched from Key issues found:
Confidence Score: 3/5
Important Files Changed
Sequence DiagramsequenceDiagram
participant User
participant FlutterUI as Flutter FairUsePage
participant WebUI as Web FairUseStatus
participant FlutterAPI as users.dart getFairUseStatus()
participant WebAPI as api.ts getFairUseStatus()
participant Backend as GET /v1/fair-use/status
User->>FlutterUI: Opens Settings > Fair Use
FlutterUI->>FlutterAPI: getFairUseStatus()
FlutterAPI->>Backend: GET /v1/fair-use/status (+ auth token)
Backend-->>FlutterAPI: 200 { stage, usage_pct, limits, case_ref, message }
FlutterAPI-->>FlutterUI: Map<String, dynamic>?
FlutterUI-->>User: Stage card + usage bars + message
User->>WebUI: Navigates to /fair-use
WebUI->>WebAPI: getFairUseStatus()
WebAPI->>Backend: GET /v1/fair-use/status (+ Firebase token)
Backend-->>WebAPI: 200 FairUseStatus JSON
WebAPI-->>WebUI: FairUseStatus | null
WebUI-->>User: Stage card + usage bars + copy case ref
note over FlutterAPI,WebAPI: On error: Flutter propagates exception → error UI shown<br/>Web catches error → returns null → "Normal" shown (bug)
|
| decoration: BoxDecoration(color: const Color(0xFF1C1C1E), borderRadius: BorderRadius.circular(16)), | ||
| child: Column( | ||
| crossAxisAlignment: CrossAxisAlignment.start, | ||
| children: [ | ||
| Text( | ||
| context.l10n.fairUseSpeechUsage, | ||
| style: const TextStyle(color: Colors.white, fontSize: 16, fontWeight: FontWeight.w600), | ||
| ), | ||
| const SizedBox(height: 16), | ||
| _buildUsageBar( | ||
| label: context.l10n.fairUseToday, | ||
| hours: speechToday, | ||
| limit: (limits['daily_hours'] as num?)?.toDouble() ?? 2.0, | ||
| pct: (usagePct['daily'] as num?)?.toDouble() ?? 0, | ||
| ), | ||
| const SizedBox(height: 12), | ||
| _buildUsageBar( | ||
| label: context.l10n.fairUse3Day, | ||
| hours: speech3day, |
There was a problem hiding this comment.
_buildUnavailable() misleadingly shows "Normal" status on API failure
When getFairUseStatus() returns null (e.g. HTTP 4xx/5xx, including 404 before PR #5748 is deployed), _status is null and the _error branch is bypassed entirely — the function didn't throw, it returned null. _buildUnavailable() then renders a green check with fairUseStatusNormal text, silently implying to the user that everything is fine when in reality the data could not be fetched.
Consider differentiating a "could not load" null from a genuine "no issues" response. One option is to treat a null return the same as the error path, or to display a neutral "Status unavailable" message instead of the green "Normal" indicator:
Widget _buildUnavailable() {
return Center(
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const FaIcon(FontAwesomeIcons.circleQuestion, color: Color(0xFF8E8E93), size: 40),
const SizedBox(height: 16),
Text(
context.l10n.fairUseStatusUnavailable,
style: const TextStyle(color: Colors.white, fontSize: 16),
textAlign: TextAlign.center,
),
const SizedBox(height: 16),
TextButton(
onPressed: _loadStatus,
child: Text(context.l10n.retry, style: const TextStyle(color: Color(0xFF8B5CF6))),
),
],
),
),
);
}…lback Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Re-review — APPROVEDAll 3 blockers fixed:
Also fixed Merge order: PR #5748 (backend) first, then PR #5770 (frontend). by AI for @beastoin |
Live Local Test Evidence (Level 1)Local Backend API Test{
"stage": "none",
"case_ref": "",
"speech_hours_today": 0.0,
"speech_hours_3day": 0.0,
"speech_hours_weekly": 0.0,
"limits": { "daily_hours": 2.0, "three_day_hours": 8.0, "weekly_hours": 10.0 },
"usage_pct": { "daily": 0.0, "three_day": 0.0, "weekly": 0.0 },
"message": "Your usage is within normal limits."
}Flutter Fair Use Page — All Stages
Test Setup
Verified
by AI for @beastoin |
|
@beastoin I found two blockers before this can be approved: in by AI for @beastoin |
When getFairUseStatus() returns null, show the error screen instead of the misleading green checkmark "Normal" state. Remove dead _buildUnavailable() method. Also localize the case_ref copy snackbar. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add try/catch around navigator.clipboard.writeText for contexts where clipboard API is unavailable. Replace <a href> with Next.js Link for client-side navigation to /fair-use page. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New localization key for the case reference copy snackbar message, translated across all supported locales with real translations. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Re-verified latest head commit Checks performed:
No new blockers found in this commit; ready for external approval/merge. by AI for @beastoin |
Tests cover: loading state, error state, success render for all 4 stages, usage bar color thresholds (79.9/80.0/99.9/100.0/115/0), case_ref copy to clipboard with localized snackbar, message card visibility, and pure unit tests for color logic. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
All Checkpoints Passed — Ready for MergeCheckpoint Summary
Review Cycle Summary
Test Evidence
PR is ready for merge. Depends on PR #5748 (backend) being merged first. by AI for @beastoin |
All jq invocations were using default 2-space indent instead of --indent 4, causing the entire ARB file content to be reformatted. This commit restores 4-space indent matching the main branch. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The English ARB uses 2-space indent on main, while non-English ARBs use 4-space. Preserve this existing convention. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace big stage card with scary icons (triangleExclamation, ban, gaugeHigh) with a subtle inline status banner using colored dots. Hide banner entirely for normal stage. Usage bars are now the hero content. About section moved to small footer text. Removed font_awesome dependency from this page. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace big icon stage card with subtle inline status banner using colored dots. Remove STAGE_CONFIG with scary lucide icons. Usage section is now the hero. About section is a plain footnote. Message shown without card wrapper. Matches the Flutter redesign approach. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Update harnesses to match new status banner (colored dot + label) instead of old stage card (big icons + colored borders). Remove font_awesome import. Add tests for: no banner on normal stage, case_ref hidden when no banner. All 32 tests pass. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
UI Redesign: Minimal Fair Use PagePer manager feedback to make the UI "less scary, more seamless, elegant and minimal": Changes
Screenshots (v2 redesign)
TestsAll 32 widget tests pass with updated harnesses matching the new UI structure. by AI for @beastoin |
Web Fair Use UI — Redesigned (v2)Web component now matches the Flutter redesign: minimal, no scary icons, usage-first. Web Screenshots
Design consistency (Flutter vs Web)Both platforms share the same minimal approach:
by AI for @beastoin |
Server component that fetches case status from the public unauthenticated
endpoint GET /v1/fair-use/case/{case_ref}/status. Shows stage, created
date, last updated date, message, and support email contact. No auth
required. Validates case_ref format before fetching.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Minimal, unauthenticated case status view showing stage with colored dot, created/updated dates, case message, and support email (team@basedhardware.com). Shows a contact banner when case hasn't been updated in 3+ days. Uses support_email from API response with fallback to default. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New: Public Case Status Page (
|
| Warning (recent) | Restricted (stale, 4+ days) |
|---|---|
![]() |
![]() |
by AI for @beastoin
Shows daily transcription budget bar for restricted users when dg_budget is present in the API response. Displays used/limit in minutes, progress bar (purple normal, red exhausted), exhausted message, and reset countdown. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds fairUseDailyTranscription, fairUseBudgetUsed, fairUseBudgetExhausted, fairUseBudgetResetsAt with real translations for 16 major locales. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Auto-generated from flutter gen-l10n after adding budget ARB keys. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
7 new tests covering budget section visibility, used/limit display, exhausted state, bar colors, and zero-limit hiding. 39 total tests passing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Optional dg_budget field with daily_limit_ms, used_ms, remaining_ms, exhausted, and resets_at for restricted user budget display. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Shows daily transcription budget between usage card and message. Renders progress bar, used/limit minutes, exhausted state with red styling, and countdown to reset. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Backend returns dg_budget for all stages; only show the budget bar when stage is 'restrict' since other stages don't have active budget caps. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Match Flutter behavior: only show DG budget bar when stage is 'restrict'. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Verifies budget section hidden for non-restrict stages even when dg_budget data is present. 40 tests passing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
lgtm |
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>











Summary
Frontend integration for the fair-use anti-abuse system (#5746). Companion to PR #5748 (backend by @yuki).
/fair-useauthenticated route +/case/{ref}public case lookup pageGET /v1/fair-use/status+ publicGET /v1/fair-use/case/{case_ref}/statusFlutter (mobile) — Redesigned v2
FairUsePageaccessible from Settings > Fair Usefont_awesome_flutterdependency from this pageWeb — Fair Use Dashboard
/fair-useauthenticated route with same minimal design as FlutterWeb — Public Case Status Page (NEW)
/case/{ref}— unauthenticated public page for case status lookupGET /v1/fair-use/case/{case_ref}/status(no auth required)team@basedhardware.com)Review fixes
response.statusCodeonly, no raw bodyfairUseCaseRefCopiedin all 34 locales including ca/et/lt/lv/ms/noDeployment
This PR has no special deploy requirements — standard pipelines:
Deploy order (coordinated with @yuki and @mon)
/v1/fair-use/status,/v1/fair-use/case/{case_ref}/status) must exist before this frontend shipsgcp_backend_listen_helm.yml(GKE Helm) withFAIR_USE_ENABLED=falseinitiallytrue, this frontend PR can merge and deployTest plan
flutter gen-l10ncompiles cleanApp screenshots (v2 redesigned)
Web screenshots (v2 redesigned)
DG Budget (restricted user with exhausted budget)
Public case status page
Dependencies
Closes #5746 (with #5748)
by AI for @beastoin