# fix\(mobile\): restore live tool shimmer and add a Thinking row · PR \#10173 · pingdotgg/t3code

[View on GitCafe](https://git.cafe/pingdotgg/t3code/pull/10173)

Repository: [pingdotgg/t3code](https://git.cafe/pingdotgg/t3code)

Visibility: public

State: merged

Source repository: pingdotgg/t3code

Source branch: t3code/fix-mobile-tool-shimmer-thinking-row

Target branch: main

Head commit: 51cc1ec6658b7376e6edf28b41f10035af9310c5

[Discussion and reviews](https://git.cafe/pingdotgg/t3code/pull/10173)

[Commits](https://git.cafe/pingdotgg/t3code/pull/10173/commits?format=markdown)

[Files changed](https://git.cafe/pingdotgg/t3code/pull/10173/diffs?format=markdown)

## Description

---

## Problem

Two mobile regressions against the web chat view:

1. **The running tool row stopped shimmering.** #9106 narrowed the shimmer from "the trailing live run" to "a call whose lifecycle is still `inProgress`". Codex only reports `inProgress` on `item.started`, which the work log drops, so in practice the mobile summary row lost its shine the moment a call completed while the turn kept going. Web keeps shining until the latest call fails, is declined, or is stopped (#9098, restored again in #9777 after the perf pass).
2. **No "Thinking" row.** Web shows a shimmering `Thinking` live activity whenever a turn is working with no shimmering tool row (before the first call, and after a failed one, per #9165). Mobile never had it, so a working turn with no tools rendered nothing above the composer.

## Fix

Mirror web's live-row rules in `deriveThreadFeedPresentation`:

- The trailing tool group shimmers while its latest call is running **or** succeeded. A failed, declined, or stopped call turns the shine off.
- When a turn is working and no tool row shimmers, append one `thinking` feed entry. It renders through the existing `ShimmeringWorkContent` with a brain icon (SF `brain`, Tabler `IconBrain` on Android) and uses the same fixed row height as the group toggle, so the list's size estimates stay exact.

The thinking row object is reused across derivations while `createdAt`/`turnId` are unchanged, so LegendList keeps the row instance during streaming.

## Verification

- `vp test run apps/mobile/src/lib/threadActivity.test.ts` — 90 passed. Updated the completed/failed lifecycle expectations to the new rules and added a test for the standalone Thinking row (present while working, stable identity, absent when idle).
- `tsc --noEmit` for `apps/mobile`, targeted `vp lint` and `vp fmt --check` on the changed files (only pre-existing warnings in `ThreadFeed.tsx`).
- iOS Simulator (iPhone 17 Pro, iOS 26.5) against a disposable base dir seeded with three running turns. Before is `main` at 62ed748, after is this branch, same dev client, same seeded data.

### Working turn, no tool calls yet

![Before: nothing above the composer. After: a shimmering Thinking row with a brain icon.](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/d445d673386560ac/pair-thinking.png)

### Latest tool call completed, turn still running

The label is identical in both; the difference is motion, so here is the recording (before on the left, after on the right):

https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/3b2347f283343702/pair-completed.mp4

![Before and after stills of the Running vp row](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/32551c3e1b7c1951/pair-completed.png)

### Latest tool call failed, turn still running

![Before: only the failed row. After: the failed row stays and a Thinking row appears below it.](https://gh-file-drop-api-prod-mi5fy3sowv63ufte.pinglabs.workers.dev/f/5c5eac3b90e5c31f/pair-failed.png)

Android was not exercised (no emulator on the verification host); the only Android-specific change is the Tabler icon mapping.

Implemented by Claude Fable 5 in the Claude Code harness.


<!-- Macroscope's pull request summary starts here -->
<!-- Macroscope will only edit the content between these invisible markers, and the markers themselves will not be visible in the GitHub rendered markdown. -->
<!-- If you delete either of the start / end markers from your PR's description, Macroscope will append its summary at the bottom of the description. -->
> [!NOTE]
> ### Restore live tool shimmer and add a `thinking` row to mobile thread feed
> - Adds a `thinking` entry variant to the thread feed type union and renders it as a new `ThreadThinkingRow` component with a brain icon and shimmering label.
> - The presentation builder appends one `thinking` row at the tail when a turn is actively working and no work-toggle row is already shimmering; existing `thinking` rows are filtered out before rebuild to avoid duplicates.
> - A cached `thinking` entry keeps object identity stable across repeated derivations unless the active-work timestamp or turn id changes.
> - Updates work-group shimmer logic in `appendToolGroupRows` so a trailing work group shimmers when it has an active activity or when its latest activity succeeded, restoring the live-tool shimmer.
> - Maps the `brain` SF Symbol to the Tabler brain icon in the Android fallback so `ThreadThinkingRow` renders correctly on Android.
> - Risk: `appendPresentedFeedEntry` and `appendToolGroupRows` in [threadActivity.ts](https://github.com/pingdotgg/t3code/pull/10173/files#diff-a18e8ed6aa4a9d6b3d9e32ea2fa3034dfd7621b6af3d640ec1b95a8564c97eb5) now exclude and recompute shimmer for `thinking` entries; any out-of-tree callers of these helpers will need to handle the new `thinking` variant.
>
> <!-- Macroscope's review summary starts here -->
>
> <sup><a href="https://app.macroscope.com">Macroscope</a> summarized 51cc1ec.</sup>
> <!-- Macroscope's review summary ends here -->
>
<!-- Macroscope's pull request summary ends here -->
