From 34096519121033ccf72cd43ea5350f9482df188a Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Tue, 22 Sep 2026 17:20:02 -0700 Subject: [PATCH] chore(mobile): name the two project favicon caches by their job (#13160) --- apps/mobile/src/components/ProjectFavicon.tsx | 2 +- apps/mobile/src/connection/environment-cache-store.ts | 9 ++++++--- ...he.test.ts => projectFaviconDatabaseCache.test.ts} | 2 +- ...FaviconCache.ts => projectFaviconDatabaseCache.ts} | 11 ++++++++--- .../projectFaviconRequests.test.ts} | 4 ++-- .../projectFaviconRequests.ts} | 8 ++++++++ apps/mobile/src/state/assets.ts | 4 ++-- apps/mobile/src/state/client-cache-state.ts | 6 +++--- 8 files changed, 31 insertions(+), 15 deletions(-) rename apps/mobile/src/lib/{projectFaviconCache.test.ts => projectFaviconDatabaseCache.test.ts} (97%) rename apps/mobile/src/lib/{projectFaviconCache.ts => projectFaviconDatabaseCache.ts} (90%) rename apps/mobile/src/{components/projectFaviconCache.test.ts => lib/projectFaviconRequests.test.ts} (98%) rename apps/mobile/src/{components/projectFaviconCache.ts => lib/projectFaviconRequests.ts} (87%) diff --git a/apps/mobile/src/components/ProjectFavicon.tsx b/apps/mobile/src/components/ProjectFavicon.tsx index 932fc6779f207430fca222d218e4d6bd8eed3e1c..e1d883a010261fc7a717e9642ff774d7cd934df8 100644 --- a/apps/mobile/src/components/ProjectFavicon.tsx +++ b/apps/mobile/src/components/ProjectFavicon.tsx @@ -18,7 +18,7 @@ import { hasLoadedProjectFavicon, markProjectFaviconFailed, markProjectFaviconLoaded, -} from "./projectFaviconCache"; +} from "../lib/projectFaviconRequests"; const EMPTY_FAVICON_URL = Atom.make(null); diff --git a/apps/mobile/src/connection/environment-cache-store.ts b/apps/mobile/src/connection/environment-cache-store.ts index c4a7cbdcce06362fcb9a2b7ffec15b7c5eea47f0..44a1d311453217f4c85e33572003c64ea0d5bb85 100644 --- a/apps/mobile/src/connection/environment-cache-store.ts +++ b/apps/mobile/src/connection/environment-cache-store.ts @@ -15,7 +15,10 @@ import * as Option from "effect/Option"; import * as Schema from "effect/Schema"; import * as MobileDatabase from "../persistence/mobile-database"; -import { attachProjectFaviconDatabase, projectFaviconCache } from "../lib/projectFaviconCache"; +import { + attachProjectFaviconDatabase, + projectFaviconDatabaseCache, +} from "../lib/projectFaviconDatabaseCache"; const SHELL_SNAPSHOT_CACHE_SCHEMA_VERSION = 1; // v3 adds windowed (paginated) snapshots carrying `page` metadata; the bump @@ -130,7 +133,7 @@ export const make = Effect.fn("MobileEnvironmentCacheStore.make")(function* () { decode: decodeStoredShellSnapshot, select: (stored) => stored.environmentId === environmentId ? Option.some(stored.snapshot) : Option.none(), - }).pipe(Effect.tap(() => Effect.promise(() => projectFaviconCache.hydrate()))), + }).pipe(Effect.tap(() => Effect.promise(() => projectFaviconDatabaseCache.hydrate()))), ), saveShell: Effect.fn("MobileEnvironmentCache.saveShell")(function* (environmentId, snapshot) { const payload = yield* encodeStoredShellSnapshot({ @@ -241,7 +244,7 @@ export const make = Effect.fn("MobileEnvironmentCacheStore.make")(function* () { .pipe(Effect.mapError(mapDatabaseError("clear-vcs-refs"))), ), clear: Effect.fn("MobileEnvironmentCache.clear")((environmentId) => - Effect.promise(() => projectFaviconCache.clearEnvironment(environmentId)).pipe( + Effect.promise(() => projectFaviconDatabaseCache.clearEnvironment(environmentId)).pipe( Effect.andThen(database.clearEnvironmentCache(environmentId)), Effect.mapError(mapDatabaseError("clear-environment")), ), diff --git a/apps/mobile/src/lib/projectFaviconCache.test.ts b/apps/mobile/src/lib/projectFaviconDatabaseCache.test.ts similarity index 97% rename from apps/mobile/src/lib/projectFaviconCache.test.ts rename to apps/mobile/src/lib/projectFaviconDatabaseCache.test.ts index adde56fbf0b18e9f3e539444197a75c5d0cb36d0..1e7344a684c33f18ae26048bf30969ac9575f297 100644 --- a/apps/mobile/src/lib/projectFaviconCache.test.ts +++ b/apps/mobile/src/lib/projectFaviconDatabaseCache.test.ts @@ -27,7 +27,7 @@ vi.mock("expo-file-system", () => ({ }, })); -import { downscaleProjectFavicon } from "./projectFaviconCache"; +import { downscaleProjectFavicon } from "./projectFaviconDatabaseCache"; const png = "iVBORw0KGgoAAAAA"; const image = { url: "https://remote/icon.png" }; diff --git a/apps/mobile/src/lib/projectFaviconCache.ts b/apps/mobile/src/lib/projectFaviconDatabaseCache.ts similarity index 90% rename from apps/mobile/src/lib/projectFaviconCache.ts rename to apps/mobile/src/lib/projectFaviconDatabaseCache.ts index 26a6d848d11d064b0405fbb86c09b5ce62e238af..f24cb2b394f75ec20474cac7e103dae403d7ad66 100644 --- a/apps/mobile/src/lib/projectFaviconCache.ts +++ b/apps/mobile/src/lib/projectFaviconDatabaseCache.ts @@ -1,5 +1,5 @@ import { - createProjectFaviconCache, + createProjectFaviconCache as createSharedProjectFaviconCache, createProjectFaviconImageLoader, PROJECT_FAVICON_MAX_DATA_URL_LENGTH, PROJECT_FAVICON_THUMBNAIL_SIZE, @@ -78,8 +78,13 @@ export async function downscaleProjectFavicon( throw new Error("Project icon thumbnail exceeds the cache limit."); } -/** Rows live in `client_cache` so Settings → Client storage counts and clears them. */ -export const projectFaviconCache = createProjectFaviconCache({ +/** + * The database-backed project favicon cache. Named apart from the shared + * `createProjectFaviconCache` factory it calls and from the in-flight request + * registry in `projectFaviconRequests`. Rows live in `client_cache` so + * Settings → Client storage counts and clears them. + */ +export const projectFaviconDatabaseCache = createSharedProjectFaviconCache({ storage: { list: () => runDatabase((database) => diff --git a/apps/mobile/src/components/projectFaviconCache.test.ts b/apps/mobile/src/lib/projectFaviconRequests.test.ts similarity index 98% rename from apps/mobile/src/components/projectFaviconCache.test.ts rename to apps/mobile/src/lib/projectFaviconRequests.test.ts index d0582a8b5f5ba3c6dc26dfe6cf7d877ea688e7ee..540a4547392634860ff0822e9e94380a5e3266e6 100644 --- a/apps/mobile/src/components/projectFaviconCache.test.ts +++ b/apps/mobile/src/lib/projectFaviconRequests.test.ts @@ -6,9 +6,9 @@ import { hasLoadedProjectFavicon, markProjectFaviconFailed, markProjectFaviconLoaded, -} from "./projectFaviconCache"; +} from "./projectFaviconRequests"; -describe("project favicon cache", () => { +describe("project favicon requests", () => { it("ignores callbacks from a superseded URL", () => { const cacheKey = "environment-1:/workspace:v1-favicon.svg"; const expiredUrl = "https://environment.example/api/assets/expired/v1-favicon.svg"; diff --git a/apps/mobile/src/components/projectFaviconCache.ts b/apps/mobile/src/lib/projectFaviconRequests.ts similarity index 87% rename from apps/mobile/src/components/projectFaviconCache.ts rename to apps/mobile/src/lib/projectFaviconRequests.ts index da77d7613f2d7c6333d4e4f38965a19537194109..202142d175e15a3a37b1e3390f3e392d320a2433 100644 --- a/apps/mobile/src/components/projectFaviconCache.ts +++ b/apps/mobile/src/lib/projectFaviconRequests.ts @@ -1,3 +1,11 @@ +/** + * Request bookkeeping behind `ProjectFavicon`: tracks the favicon URLs with + * mounted views per cache key, keeps one of them current, and remembers which + * keys have already loaded so superseded or already-shown loads settle without + * churn. This is not request coalescing — each mount still issues its own + * load — and it is not the cache; persistence lives in + * `projectFaviconDatabaseCache`. + */ export interface ProjectFaviconRequest { readonly cacheKey: string; readonly faviconUrl: string; diff --git a/apps/mobile/src/state/assets.ts b/apps/mobile/src/state/assets.ts index 15cbd1d9a89fd0a235130dd66d110808c3d240e5..b2fb759c56806c0f93605c4ebecdf473d64f10f2 100644 --- a/apps/mobile/src/state/assets.ts +++ b/apps/mobile/src/state/assets.ts @@ -16,7 +16,7 @@ import { useCallback } from "react"; import { environmentCatalog } from "../connection/catalog"; import { connectionAtomRuntime } from "../connection/runtime"; -import { projectFaviconCache } from "../lib/projectFaviconCache"; +import { projectFaviconDatabaseCache } from "../lib/projectFaviconDatabaseCache"; import { type AssetUrlState, deriveAssetUrlState } from "./asset-url-state"; import { environmentSession, usePreparedConnection } from "./session"; import { useAtomQueryRunner } from "./use-atom-query-runner"; @@ -26,7 +26,7 @@ export type { AssetUrlFailureReason, AssetUrlState } from "./asset-url-state"; export const assetEnvironment = createAssetEnvironmentAtoms(connectionAtomRuntime); export const projectFaviconUrlAtom = createProjectFaviconUrlAtomFamily({ - imageCache: projectFaviconCache, + imageCache: projectFaviconDatabaseCache, createUrl: assetEnvironment.createUrl, preparedConnection: environmentSession.preparedConnectionValueAtom, }); diff --git a/apps/mobile/src/state/client-cache-state.ts b/apps/mobile/src/state/client-cache-state.ts index c210c54f2fdd41d0fa006dca9b312682f41cb39c..0060e6ab0789b754dd9bfed47816ffec991fea38 100644 --- a/apps/mobile/src/state/client-cache-state.ts +++ b/apps/mobile/src/state/client-cache-state.ts @@ -3,7 +3,7 @@ import * as Effect from "effect/Effect"; import { Atom } from "effect/unstable/reactivity"; import { type ClientCacheKind, MobileDatabase } from "../persistence/mobile-database"; -import { projectFaviconCache } from "../lib/projectFaviconCache"; +import { projectFaviconDatabaseCache } from "../lib/projectFaviconDatabaseCache"; import * as Runtime from "../lib/runtime"; export interface EnvironmentClientCacheSummary { @@ -74,8 +74,8 @@ export const clearClientCacheAtom = clientCacheRuntime .fn((scope: ClientCacheClearScope, get) => Effect.promise(() => scope.type === "all" - ? projectFaviconCache.clearAll() - : projectFaviconCache.clearEnvironment(scope.environmentId), + ? projectFaviconDatabaseCache.clearAll() + : projectFaviconDatabaseCache.clearEnvironment(scope.environmentId), ).pipe( Effect.andThen(MobileDatabase), Effect.flatMap((database) => -- 2.54.0