Skip to content

Commit 03186e0

Browse files
perf(mobile): project favicons mount their image in one commit (#26047)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 6369e28 commit 03186e0

3 files changed

Lines changed: 54 additions & 9 deletions

File tree

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import type { ReactNode } from "react";
2+
import { act, Profiler } from "react";
3+
import { createRoot } from "react-dom/client";
4+
import { expect, it, vi } from "vite-plus/test";
5+
6+
const harness = vi.hoisted(() => ({
7+
Passthrough: ({ children }: { children?: unknown }) => children,
8+
}));
9+
10+
vi.mock("react-native", () => ({ View: harness.Passthrough }));
11+
vi.mock("expo-image", () => ({ Image: () => <img alt="" /> }));
12+
vi.mock("./ui/AppSymbol", () => ({ SymbolView: () => null }));
13+
vi.mock("./ui/AppText", () => ({
14+
AppText: ({ children }: { children?: ReactNode }) => <span>{children}</span>,
15+
}));
16+
vi.mock("../state/assets", async () => {
17+
const { Atom } = await import("effect/reactivity");
18+
const url = Atom.make<string | null>("https://example.test/favicon.png");
19+
return { projectFaviconUrlAtom: () => url };
20+
});
21+
22+
import { EnvironmentId } from "@t3tools/contracts";
23+
24+
import { ProjectFavicon } from "./ProjectFavicon";
25+
26+
it("mounts the favicon image in the first commit", async () => {
27+
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
28+
const container = document.createElement("div");
29+
const root = createRoot(container);
30+
const commits: string[] = [];
31+
try {
32+
await act(async () =>
33+
root.render(
34+
<Profiler id="favicon" onRender={(_id, phase) => commits.push(phase)}>
35+
<ProjectFavicon
36+
environmentId={EnvironmentId.make("env-1")}
37+
projectTitle="Project"
38+
workspaceRoot="/repo"
39+
/>
40+
</Profiler>,
41+
),
42+
);
43+
expect(container.querySelector("img")).not.toBeNull();
44+
expect(commits).toEqual(["mount"]);
45+
} finally {
46+
await act(async () => root.unmount());
47+
}
48+
});

‎apps/mobile/src/components/ProjectFavicon.tsx‎

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -163,14 +163,11 @@ function ProjectFaviconImage(props: {
163163
() => createProjectFaviconRequest(props.cacheKey, props.faviconUrl),
164164
[props.cacheKey, props.faviconUrl],
165165
);
166-
const [activeFaviconRequest, setActiveFaviconRequest] = useState<typeof faviconRequest>(null);
166+
// Registers with the module-level loader before the Image's native onLoad/onError can fire,
167+
// so stale loads for a superseded URL under the same cacheKey are ignored.
167168
useLayoutEffect(() => {
168169
if (faviconRequest === null) return;
169-
170-
const endRequest = beginProjectFaviconRequest(faviconRequest);
171-
// oxlint-disable-next-line react/set-state-in-effect -- Publishes the favicon request registered with the external loader until its cleanup runs.
172-
setActiveFaviconRequest(faviconRequest);
173-
return endRequest;
170+
return beginProjectFaviconRequest(faviconRequest);
174171
}, [faviconRequest]);
175172

176173
const [status, setStatus] = useState<"loading" | "loaded" | "error">(() =>
@@ -179,8 +176,7 @@ function ProjectFaviconImage(props: {
179176
: "loading",
180177
);
181178

182-
const requestIsActive = faviconRequest !== null && activeFaviconRequest === faviconRequest;
183-
const showImage = requestIsActive && status === "loaded";
179+
const showImage = faviconRequest !== null && status === "loaded";
184180
const fallbackGlyph = showImage ? null : projectAutoMonogram(props.projectTitle);
185181

186182
return (
@@ -203,7 +199,7 @@ function ProjectFaviconImage(props: {
203199
/>
204200
)}
205201

206-
{requestIsActive ? (
202+
{faviconRequest !== null ? (
207203
<Image
208204
key={faviconRequest.faviconUrl}
209205
source={

‎apps/mobile/vite.config.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const reactTests = [
1212
"src/features/threads/thread-inspector-navigation.test.tsx",
1313
"src/features/diffs/nativeReviewDiffSurface.test.tsx",
1414
"src/features/pull-requests/usePullRequestDiff.test.tsx",
15+
"src/components/ProjectFavicon.test.tsx",
1516
"src/components/ui/Dialog.android.test.tsx",
1617
"src/features/pull-requests/guide/useReviewGuide.test.tsx",
1718
"src/features/settings/SettingsServerControlsRouteScreen.test.tsx",

0 commit comments

Comments
 (0)