Repository navigation
perf(mobile): project favicons mount their image in one commit - #26047
Conversation
Problem: every ProjectFaviconImage with a URL committed twice on mount and on each request change. The first render had activeFaviconRequest === null, so it drew only the fallback; the layout effect then called setState to mount the Image in a synchronous second render. The gate guarded nothing: markProjectFaviconLoaded/Failed only run from expo-image's native onLoad/onError, which arrive after the same commit's layout effect has registered the request. Fix: drop the activeFaviconRequest state and render the Image whenever a request exists. The layout effect stays, now only registering the request with the module loader and returning its end function (external registration, no setState). Effects: ProjectFavicon.tsx 1 -> 1 (layout effect kept, its setState and set-state-in-effect suppression removed). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ApprovabilityVerdict: Approved at Macroscope's review found this PR approvable — This is a focused mobile performance improvement that removes an unnecessary favicon state update, allowing the existing image to mount in the initial commit while preserving request-staleness checks and fallback behavior. The accompanying changes are test-only coverage and test-project registration. You can add or adjust custom eligibility rules. Learn more. |
|
No actionable comments were generated in the recent review.
|
| Layer / File(s) | Summary |
|---|---|
Request registration and rendering apps/mobile/src/components/ProjectFavicon.tsx, apps/mobile/src/components/ProjectFavicon.test.tsx, apps/mobile/vite.config.ts |
The layout effect returns the loader cleanup directly. Image rendering depends on a non-null request. The added test checks that an image renders and that the profiler records one mount commit; the Vite config includes the test. |
Priority:
Estimated code review effort: 2 (Simple) | ~10 minutes
Change: Refactor
Possibly related PRs
- pingdotgg/t3code#25119: Adds a lint suppression for the state update that this PR removes.
Suggested reviewers: maria-rcks
Merge Risk: 
Minimal · up to 2219f
-
No actionable behavior change remains; the PR is ready to merge after normal checks.
Pre-merge checks: 
3 · 
1
-
Failed checks: 1 warning Passed checks: 3 passedFull details: Description check
-
Explanation
The description clearly explains the problem, implementation, behavior impact, and verification. It omits the required Scope and approval section, including an issue, maintainer approval, or a rationale for the focused-fix exemption.
Resolution
Add a Scope and approval section. Link the triaged issue or maintainer approval with the approval comment. If no prior issue or discussion exists, explain why this focused fix qualifies as an obvious bug exemption. Also state whether UI screenshots are not applicable because the change has no visible behavior change, if required by repository policy.
-
Finishing Touches
-
📝 Generate docstrings
-
- Commit to this branch
- Create a new PR
🧪 Generate unit tests (beta)
-
- Commit to this branch
- Create a new PR
-
- Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Comment @coderabbitai help to get the list of available commands.
## What's Changed * fix(mobile): center Android sheets and fit book-mode sheets in the left pane by @PixPMusic in pingdotgg/t3code#18492 * fix(mobile): keep the collapsed Android rail square by @PixPMusic in pingdotgg/t3code#26039 * fix(web): closing the last Browser tab hides the right panel again by @juliusmarminge in pingdotgg/t3code#26058 * refactor(web): pull requests Cmd+F focuses the topbar search directly by @juliusmarminge in pingdotgg/t3code#26045 * refactor(web): install draft model reconciliation at app start by @juliusmarminge in pingdotgg/t3code#26046 * perf(mobile): project favicons mount their image in one commit by @juliusmarminge in pingdotgg/t3code#26047 * refactor(web): the upload queue alone records finished file uploads on drafts by @juliusmarminge in pingdotgg/t3code#26050 * feat(mobile): Usage uses the width on iPad and unfolded phones by @juliusmarminge in pingdotgg/t3code#26061 * fix(gitcafe): PR panel follows renumbered pull requests by @juliusmarminge in pingdotgg/t3code#26079 * chore(infra): bump Alchemy to 2.0.0-beta.84 by @juliusmarminge in pingdotgg/t3code#26057 * refactor(mobile): stop writing the resolved project filter back from an effect by @juliusmarminge in pingdotgg/t3code#26049 * fix(mobile): a pairing link fills the open Add environment form instead of stacking another by @juliusmarminge in pingdotgg/t3code#26052 * fix(mobile): snooze menu times advance with the clock by @juliusmarminge in pingdotgg/t3code#26043 * fix(clients): prompt links find projects cloned from git.cafe by @juliusmarminge in pingdotgg/t3code#26042 * fix(server): retry Codex capacity failures with empty input by @Yash-Singh1 in pingdotgg/t3code#25611 * fix(server): recover handoffs when target context is exhausted by @Yash-Singh1 in pingdotgg/t3code#25610 * fix(web): chips drop their pill and stay readable on every theme by @flamboh in pingdotgg/t3code#25066 * fix(mobile): switching pull request tabs no longer jumps the page by @flamboh in pingdotgg/t3code#26064 * feat(mobile): report crashes to Axiom with symbolicated stacks by @gabrielelpidio in pingdotgg/t3code#19093 * fix(web): chip palette test follows the single design by @juliusmarminge in pingdotgg/t3code#26131 * feat(server): /mcp narrows a client's modes from T3-Mode-Limit by @juliusmarminge in pingdotgg/t3code#16653 * feat(server): t3_thread_launch takes clientRequestId by @juliusmarminge in pingdotgg/t3code#16654 * feat(server): link to another environment by @juliusmarminge in pingdotgg/t3code#16655 * feat(server): agents use linked environments through any T3 tool by @juliusmarminge in pingdotgg/t3code#16684 * feat(server): work a linked environment starts stays within its link by @juliusmarminge in pingdotgg/t3code#16695 * feat(web,mobile): linked environments in Connections by @juliusmarminge in pingdotgg/t3code#16698 * feat(server): delegate_task to a linked environment by @juliusmarminge in pingdotgg/t3code#16719 * feat(server,web,mobile): an agent sets up a link to another environment by @juliusmarminge in pingdotgg/t3code#17129 * feat(server,web,mobile): @-mention a machine, and linked environments become a Labs beta by @juliusmarminge in pingdotgg/t3code#17130 * fix(mobile): dragging a microphone or environment route now saves its new place by @juliusmarminge in pingdotgg/t3code#26096 * feat(web): channel artwork choice on Latest, plus nightly or dev art on any channel by @maria-rcks in pingdotgg/t3code#26138 * feat(mobile): browse ZIP attachments with the shared archive reader by @Bil0000 in pingdotgg/t3code#19463 * fix(server): Claude --settings launch args merge with T3's settings instead of being dropped by @t3dotgg in pingdotgg/t3code#22864 * feat(server,web): see and stop the dev servers a thread started by @t3dotgg in pingdotgg/t3code#22542 * fix(web): composer no longer jumps when a second line is added by @maria-rcks in pingdotgg/t3code#26155 * feat(web,mobile): queued messages show who sent them by @juliusmarminge in pingdotgg/t3code#26159 * fix(preview): agent and browser panel show the same page after a late desktop attach by @t3dotgg in pingdotgg/t3code#22877 * fix(web): pairing, connect, error, and Clerk sign-in surfaces match the app's dialog style by @maria-rcks in pingdotgg/t3code#25281 * fix(server): parallel worktree launches no longer fail on .git/config lock by @juliusmarminge in pingdotgg/t3code#25700 * fix(task-cache): the hook test waits for every flusher it starts by @juliusmarminge in pingdotgg/t3code#26148 * chore(lint): every React effect states its reason at the call site by @juliusmarminge in pingdotgg/t3code#26161 * test(server): the link-card retry test names a new MCP credential by @juliusmarminge in pingdotgg/t3code#26143 * perf(web): stop measuring table cells while replies stream by @t3dotgg in pingdotgg/t3code#26152 * feat(desktop): add a remote server by clicking its t3code://pair link by @t3dotgg in pingdotgg/t3code#24111 * fix(server): agent questions stay answerable after a server restart by @SunkenInTime in pingdotgg/t3code#19229 * chore(lint): the effect lint error walks the alternatives before the suppression by @juliusmarminge in pingdotgg/t3code#26181 * fix(web): settings avatar no longer hugs the modal corner by @t3dotgg in pingdotgg/t3code#26180 * fix(mobile): shared file names can no longer write outside the Android cache by @juliusmarminge in pingdotgg/t3code#26175 * fix(server): repository lookups no longer send GitHub tokens to arbitrary hosts by @juliusmarminge in pingdotgg/t3code#26176 * fix(github): long period runs no longer freeze reference autolinking by @juliusmarminge in pingdotgg/t3code#26177 * fix(mobile): Android diff ellipsis no longer measures text quadratically by @juliusmarminge in pingdotgg/t3code#26174 * fix(server): Codex works again after its app-server is killed by @t3dotgg in pingdotgg/t3code#26188 **Full Changelog**: pingdotgg/t3code@v0.1.4-nightly.20261027.3783...v0.1.4-nightly.20261027.3796 Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.1.4-nightly.20261027.3796
Problem
Each mobile
ProjectFaviconImagewith a URL committed twice on mount and on every request change (one per project row in sidebars, project lists and pickers). The first render hadactiveFaviconRequest === null, so it drew only the fallback; the layout effect then set state to mount theImagein a synchronous second render. The gate protected nothing:markProjectFaviconLoaded/Failedare reached only from expo-image's nativeonLoad/onError, which arrive after the same commit's layout effect has registered the request.Change
activeFaviconRequeststate; render theImagewhenever a request exists, andshowImage = request !== null && status === "loaded".ProjectFavicon.test.tsx(added to the jsdom/React Compiler test list inapps/mobile/vite.config.ts).What this improves
mounts the favicon image in the first commit(Profiler phases) fails on main with['mount', 'nested-update']and passes here with['mount'].useStateand areact/set-state-in-effectsuppression.Behavior changes: none visible. The second commit ran before paint on main, so the favicon does not appear any earlier to the eye.
Effects
apps/mobile/src/components/ProjectFavicon.tsx: 1 -> 1 (layout effect kept as external registration; its setState removed).Verification
vp test run src/components/ProjectFavicon.test.tsx: pass; fails with main'sProjectFavicon.tsxswapped in.vp run check:changed: format, lint, knip 1/2, knip 2/2, typecheck (2 packages), test all pass.Model: Claude Opus 5.5, Claude Code harness.
🤖 Generated with Claude Code