fix(mobile): the Android usage widget lists and scrolls every limit again#23077

open3
main
GitHub fork: fix/android-widget-scroll-all-limits
opened20d agobyVvoida6
View on GitHubTwo-way sync enabledPR last imported 2h ago

Problem

The Android "Subscription usage" widget shows at most two limits per provider, followed by "N more in T3", cannot scroll, has no Subscription usage (N) header, and labels its timestamp "As of" instead of "Last checked". #13474 shipped a scrolling full list with the header and "Last checked" label; #12046 (the move to expo-widgets) dropped it. The snapshot still publishes every limit on Android (SubscriptionUsageCoordinator passes Infinity), so only the Compose layout in SubscriptionUsage.android.tsx was hiding rows (const limit = 2, slice(0, limit)).

Change

SubscriptionUsage.android.tsx only:

  • Render every provider and every window inside a LazyColumn (exported by @expo/ui/jetpack-compose, already mapped by the widget runtime) under a pinned title: Subscription usage (N), where N counts the limits shown. Providers with no fresh data ("Open T3 to refresh", "Open T3 to connect") are not counted, and the title is plain when N is 0.
  • Remove the two-row cap and the "N more in T3" line.
  • Restore the "Last checked" label. It is the last list item rather than a pinned footer: the widget runtime ignores the weight modifier, so a footer placed after the list is pushed out of the card entirely (observed on the emulator).
  • A LazyColumn swallows taps meant for the card's Button behind it (observed: taps on rows did nothing, header taps opened the app). Each list item is now wrapped in its own Button with no container colour, which the runtime renders as a plain clickable box, so tapping any row opens Usage > Limits as before. Footer text is wrapped in a Column because a Button holding bare text renders as a Material button.

Scope and approval

Fixes #15227 (labelled bug, via-triage). Julius's triage comment confirms the cause and says "A maintainer will decide on the fix direction"; there is no explicit approval of this direction yet. This restores the behavior #13474 shipped, with no new settings or features. Out of scope: #15225 (resize floor), #15158 (picker preview), the unreferenced bools.xml files, and #14158.

Verification

  • Checks on apps/mobile: vp lint, vp fmt --check on the changed file, and vp run typecheck all exit 0.

  • No test was added. SubscriptionUsage.android.test.ts was deleted in #22557 (it mocked @expo/ui and string-matched JSON), and the only new logic is a sum over rows, so I did not bring that pattern back. subscriptionUsageSnapshot.test.ts already asserts that the Android snapshot carries every limit.

  • Manual check on an Android 16 x86_64 emulator (headless, Pixel 6 profile, dev client built from this branch, JS from Metro). The data was invented, not from a real account: a temporary, uncommitted edit to publishSubscriptionUsage.android.ts published Codex with 5 windows (5 hours 60%, Weekly 8%, Spark 5 hours 100%, Spark weekly 97%, Code review 85%) and Claude with 4 (Session 72%, Weekly 41%, Weekly Opus 12%, Weekly Sonnet 55%). The widget was placed from the launcher at the default 4x3 size.

    • Before (main): 2 rows per provider, "3 more in T3" and "2 more in T3", "As of ..." footer, no header.

      Before
    • After: Subscription usage (9) header, all 9 limits listed, list scrolls under the pinned header, and the last item reads "Last checked ...".

      After, top After, scrolled to the end

      Scroll recording: after-scroll-emulator.mp4

    • Tap on a row opens the app on Usage > Limits (settings/usage?tab=limits).

      After tap
  • Not checked: a physical device (Samsung S25 Ultra, Android 16), Android versions other than 16, dark theme and the themed palette, widget sizes other than the default, the stale and empty states after this change (the code path is unchanged apart from layout), TalkBack, and the 4-limit-or-fewer case where nothing needs to scroll.

Authored with Claude Sonnet 5.5 (implementation) and Claude Opus 5.5 (orchestration) via Claude Code in T3 Code.

🤖 Generated with Claude Code

Fixes pingdotgg/t3code#15227

Checks

20 successful · 11 skipped
All checks passed
483c548

Added 1 commit

  • fix(mobile): the Android usage widget lists and scrolls every limit againa6e10a1
Synced from GitHub

Review in Change Stack →

Important

Review skipped

Review was skipped as selected files did not have any reviewable changes.

External image Run configuration

External image Commits

Reviewing files that changed from the base of the PR and between fe85b5877653a8d602c4e4478e42b5ad79c31e74 and 483c548bb97420ed87db2fbe79e9a8a1915fd4c4.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • Trigger review

No actionable comments were generated in the recent review.

Walkthrough

The Android subscription usage widget now shows all non-stale usage windows in a scrollable list. It adds individual tap targets, displays the number of entries in the title, and labels the timestamp “Last checked.”

Changes: Android subscription usage

Layer / File(s)Summary
Render all usage windows
apps/mobile/src/widgets/SubscriptionUsage.android.tsx
The widget replaces the two-window limit with a scrollable list of all non-stale windows. Each provider, usage window, and footer has an individual tap target. The title includes the displayed entry count, and the timestamp label changes to “Last checked.”

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix · Severity of issue fixed: Medium

Possibly related issues

  • pingdotgg/t3code#15225 — The issue calls for the widget to remain usable below its default size. This change adds scrolling but does not change the widget’s resize floor.

Suggested reviewers: juliusmarminge

Merge Risk: External image Minimal · previous review fe85b

The scrolling usage-list change is ready to merge after normal checks; no material outstanding issue was identified.

Pre-merge checks: Passed 3 · Failed 1

Failed Failed checks: 1 warning

Check nameStatusExplanationResolution
Description checkWarningThe description covers the problem, change, scope, and verification in detail. However, the Scope and approval section says there is no explicit maintainer approval of the fix direction, and it does n…Add a link to a triaged issue or discussion with explicit maintainer approval of the fix direction and scope. If this change qualifies as a very small, focused fix of an obvious bug, explain why it qualifies without prior approval; otherwis…
Passed Passed checks: 3 passed

Check nameStatusExplanation
Title checkPassedThe title is concise, specific, and accurately describes restoring the Android usage widget's complete, scrollable limit list.
Linked Issues checkPassedDirectly linked issue #15227 is open and active, so its coding requirements apply. The whole-PR diff (merge base to head) changes only apps/mobile/src/widgets/SubscriptionUsage.android.tsx.

Require…
Out of Scope Changes checkPassedThe whole-PR diff changes only apps/mobile/src/widgets/SubscriptionUsage.android.tsx. Each hunk supports #15227:
- LazyColumn import and use: scrolling list (#15227).
- `import type { ReactNode } …
Full details: Linked Issues check

Explanation

Directly linked issue #15227 is open and active, so its coding requirements apply. The whole-PR diff (merge base to head) changes only apps/mobile/src/widgets/SubscriptionUsage.android.tsx.

Requirements and evidence:

  • Every limit listed: the const limit = 2 cap and slice(0, limit) are removed. shown holds all non-stale provider.windows. The Android snapshot already carries every limit (#15227, Fix direction).
  • Scrolling: the provider rows, windows, and footer sit in a LazyColumn (imported from @expo/ui/jetpack-compose).
  • Header Subscription usage (N): rendered as a Text outside the LazyColumn, so it stays pinned. N is the sum of shown.length across providers. Placeholder rows are not counted. When N is 0, the header reads Subscription usage with no count. The issue does not specify the zero case.
  • N more in T3 removed: the hidden computation and its Text are deleted.
  • Last checked label: the footer text now starts with Last checked instead of As of . The footer is now the last LazyColumn item, not a pinned footer. The issue does not require a pinned footer, so the label requirement is met.
  • Row taps: each provider row, window row, and the footer is wrapped in its own Button (tappable), which keeps taps routed to the app as #15227 requires for lazy column rows.
  • Automated tests: the PR adds no test. The PR description states that SubscriptionUsage.android.test.ts was already deleted in #22557. The issue asks for test updates only for that file. subscriptionUsageSnapshot.test.ts is cited by the PR as asserting Android carries every limit. No test requirement remains unmet.

The previous assessment described the footer as pinned. The current code places it in the list. That conclusion is revised. The requirement it supported is still met.

Limit: the incremental diff from the previous reviewed head could not be read because changed blobs were missing. This review uses the whole-PR diff from the merge base to head. Device behavior rests on the author's emulator report and was not independently verified.

Full details: Out of Scope Changes check

Explanation

The whole-PR diff changes only apps/mobile/src/widgets/SubscriptionUsage.android.tsx. Each hunk supports #15227:

  • LazyColumn import and use: scrolling list (#15227).
  • import type { ReactNode } from "react": type annotation for the tappable helper. It is type-only and adds no runtime code.
  • limits count and Subscription usage (N) title: header requirement (#15227).
  • rows/stale/shown computation: replaces the per-provider slice. It keeps the existing stale-state logic.
  • tappable wrapper and per-item Button: row tap behavior required by #15227.
  • Footer Column wrapper and the two {/* */} comments: explain the layout choice. They support the footer change.
  • Last checked text: label change required by #15227.

No hunk touches the separately scoped items named in the PR: #15225 (resize), #15158 (picker preview), orphaned bools.xml files, or #14158. The PR description lists these as excluded, and the diff does not touch them.

Limit: the incremental diff from the previous reviewed head could not be read. This review uses the whole-PR diff from the merge base to head.

Full details: Description check

Explanation

The description covers the problem, change, scope, and verification in detail. However, the Scope and approval section says there is no explicit maintainer approval of the fix direction, and it does not explain why this change qualifies for the small, obvious bug exemption.

Resolution

Add a link to a triaged issue or discussion with explicit maintainer approval of the fix direction and scope. If this change qualifies as a very small, focused fix of an obvious bug, explain why it qualifies without prior approval; otherwise, obtain and document maintainer approval before merging.

Finishing Touches

đź§Ş Generate unit tests (beta)

  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
External image Recent review info

External image Run configuration

External image Commits

Reviewing files that changed from the base of the PR and between a6e10a1d14747429827252f986dc382799b41e6e and fe85b5877653a8d602c4e4478e42b5ad79c31e74.

External image Files selected for processing: 1

  • apps/mobile/src/widgets/SubscriptionUsage.android.tsx

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 0 remain after this review.

Added 1 commit

  • chore(mobile): merge main into android widget repairfe85b58

Note

Written by gpt-6.1-sol on behalf of Maria

thanks @voida6. the original correctly puts every fresh limit in its own native scrolling item and preserves taps into usage limits. i kept that implementation and your commit intact, merged current main (1316d48b0c68), and pushed fe85b5877653a8d602c4e4478e42b5ad79c31e74 to this branch. the final diff remains one android widget file. review found no safe structural simplification worth changing the behavior.

verification passed:

  • blacksmith vp run check:changed 1316d48b0c68151afd8b47eb0c11a114cf0b4daa: format, lint, both knip passes, mobile and scripts typechecks.
  • blacksmith existing subscriptionUsageSnapshot.test.ts: all 5 tests passed.
  • all 28 head checks are green. coderabbit reported no actionable findings.
  • two independent code reviews approved this exact head after inspecting the installed expo-widgets converter. no required findings remain. optional comment/callback cleanup was left alone.

unverified: native android rendering, scrolling past the second row, pinned title, footer reachability, and row deep-link taps. this vm has no attached android device. the default dev server and system chrome through repository playwright worked, but the web app cannot exercise a native home-screen widget. no new before/after captures or recording were produced; the contributor's existing emulator evidence remains in the description and was not independently reproduced. dev and check processes are stopped.

maintainer approval of the focused scrolling direction remains for maria. before treating native verification as complete, scroll a widget with more than two limits to its footer and tap a lower row to open usage limits.

Synced from GitHub

Thanks @maria-rcks. On the two native checks, the existing Android 16 emulator evidence (Pixel 6 profile, default 4x3 widget, pre-merge head a6e10a1) covers part of it:

  • Scrolled to the footer: after-scrolled is the end of a 9-limit list. The Subscription usage (9) header stays pinned, and "Last checked" is reachable as the last item. The scroll recording shows the full scroll.
  • Row taps: taps on the header and on rows opened Usage > Limits (after-tap). I can't confirm from those captures that the tapped row was one only reachable after scrolling.

If it helps, I can rebuild on fe85b58 and record exactly that sequence on the emulator: scroll to the footer, then tap a lower row. Just say.

Synced from GitHub

@maria-rcks the sequence you asked for, on this head (fe85b58), Android 16 emulator (Pixel 6 profile, default widget size). The widget has 9 limits from a local fixture (not committed).

  • Recording (30 s): scrolls the widget to the footer, then taps "Weekly Opus", a row only visible after scrolling. The app opens on Usage > Limits (final frame).
  • Scrolled to the footer: "Last checked" is the last item.
TopScrolled to the footer
External imageExternal image

Not checked: other widget sizes, tablets, dark theme, and a physical device.

Note

Written by gpt-6.1-sol on behalf of Maria

Verified in a real dev environment

no changed behavior could be independently exercised on this linux boat vm. this is an unverifiable result, not a passing runtime verification.

inspected the description, one-file diff, discussion, commit authors, current head and reported checks. the pr is open at fe85b5877653a8d602c4e4478e42b5ad79c31e74. its two pr commits list voida6 (with claude attribution) and maria-rcks, with neither excluded maintainer listed as an author. git.cafe reports 29 successful checks, 0 failing and 0 pending. these are existing ci results, not checks run during this verification.

the diff only changes the native android home-screen subscription usage widget. web or provider-turn success cannot prove its scrolling and tap behavior.

runtime prerequisite transcript:

text
1$ command -v adb
2/usr/bin/adb
3$ adb devices -l
4List of devices attached
5[no devices]
6$ command -v emulator
7[no executable found]
8$ device_list
9Agent device access is turned off for this environment.

Not verified

the original bug on main and the changed behavior on the pr were not exercised. no android device is attached, no emulator executable is on PATH, and t3 device access is disabled. the mobile verification skill requires stopping when the device tools or selected device are unavailable, without installing or switching automation systems.

unverified: scrolling beyond the second limit, pinned title/count, footer reachability, lower-row taps opening usage limits, stale/empty states, and the short-list case. no real-account provider-to-widget path was exercised. no before/after screenshots or recording were captured or uploaded. the contributor's fixture-based emulator captures were not independently reproduced and do not satisfy this real-data verification request.

no dev server was started, no main worktree or dependency installation was created, and no source, branches or labels were changed. no commit or push was made. native android verification with real account data remains required.

Synced from GitHub

@maria-rcks real-data run at fe85b58 on an Android 16 emulator, with no fixture or debug code. The dev server read this machine's own Codex login.

Widget with real dataAfter tapping the lower row
External imageExternal image
  • Short-list case: "Subscription usage (2)" is pinned, both real Codex rows show, and "Last checked" sits in the footer without scrolling.
  • Tap: tapping the Weekly row opens Usage > Limits (recording).
  • Scrolling past the second row cannot be shown with real Codex data. codexUsageLimits.ts only maps the base codex limit to its primary and secondary windows, so a real Codex account never yields more than two rows. A longer real list needs a second provider reporting windows, and Claude here is signed in with a long-lived token, which reports none. Scrolling, the footer after scrolling, and a tap on a row only reachable by scrolling are therefore covered only by the earlier fixture recording on this head (comment above).

Not checked: the stale state, other widget sizes, dark theme, and a physical device.

Note

Written by gpt-6.1-sol on behalf of Maria

Verified on Android emulator (API 35) against a real dev server

partial verification only. the changed scrolling behavior remains unverified.

tested exact head fe85b5877653a8d602c4e4478e42b5ad79c31e74 on the t3 x86_64 android emulator, API 35, with KVM. installed the matching native dev client from the shared cache, loaded this checkout through Metro on port 8081, and paired it to a real isolated dev server on port 13773 using /home/user/t3code/.t3. no mocks, fixture limits, or demo data were used.

the server created a real project rooted at apps/server. from the android app, selected Codex gpt-6.1-sol, sent a real turn through the local proxy, and saw the completed response android verification connected. in the client and persisted messages.

placed the default 4x3 subscription usage widget on the launcher. the real provider state has no available subscription limits. verified that the empty widget renders the plain Subscription usage title without (0), shows provider placeholders, and tapping the footer opens the app at usage > limits. the destination displays Codex could not read usage (JSON-RPC -32600).

before/after below refer to the footer tap on the PR head, not main versus PR. the short recording shows that same interaction. inspected the captures and checked uploaded video playback and seeking.

android widget before tapping its footer

usage limits after tapping the widget footer

android widget footer tap opens usage limits

completed real codex turn in the android client

Not verified

ios cannot run on this linux VM. the core regression is also unverified: scrolling beyond two real limits, the nonzero count and pinned title during scrolling, Last checked with real quota data, and taps on limits revealed by scrolling. stale limits, other sizes/themes, and a physical device were not exercised. the original bug was not reproduced on github/main, so there is no main/PR before-and-after comparison.

the concrete blocker is the real Codex account usage path, not emulator availability. the server's native request fails with:

text
1CodexAppServerRequestError: codex account authentication required to read rate limits

the local proxy supports real turns but does not supply authenticated subscription quota data here. completing the requested proof requires a real subscription account whose provider exposes more than two widget limits. invented limits would not satisfy this request.

no source change, commit, or push was made. existing CI reports 29 successful checks, 0 failing and 0 pending; those are not new runtime proof. no new lint/typecheck/test run was needed for this verification-only pass.

Synced from GitHub

@maria-rcks follow-up: I also paired the Android dev client at fe85b58 to my everyday T3 server (pooled Codex accounts, Claude configured). It reports only 2 limits as well (Session and Weekly; Claude reports none), so real data on my side can't push the widget past the second row either. Scrolling, the pinned header past row 2, and tapping a row that's only reachable after scrolling are covered by the fixture run above, not by real data. Happy to redo it with real data if you have a server that reports 3 or more windows.

Added 1 commit

  • Merge remote-tracking branch 'origin/main' into fix/android-widget-scroll-all-limits483c548

The automated pipeline stopped on this pull request and will not merge it:

the capture step could not show the problem on the base commit, so there is nothing to prove fixed.

On head: pair the app, open Usage > Limits, add Subscription usage at its default 4Ă—3 size, dismiss resize handles, swipe upward inside the widget, and tap the newly revealed Session row. Observed all ten limits across the scroll, the pinned count header, Last checked, and navigation to Usage > Limits. Base backend and Metro started, but device failures prevented completing its reproduction.

It stays open for a person to finish or close.