Skip to content

fix(clients): persist project icons across reloads and reconnects - #10138

Merged
juliusmarminge merged 6 commits into
pingdotgg:mainfrom
StiensWout:t3code/cache-remote-project-icon
Sep 6, 2026
Merged

fix(clients): persist project icons across reloads and reconnects#10138
juliusmarminge merged 6 commits into
pingdotgg:mainfrom
StiensWout:t3code/cache-remote-project-icon

Conversation

@StiensWout

@StiensWout StiensWout commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Project icons fall back to generic icons while remote environments reconnect or the page reloads. Keep the last resolved URL during reconnects, and persist the icon image so cached sidebar entries can show their icons before the next remote response arrives.

The shared cache is scoped to the environment, workspace, and selected favicon path. Icons are fetched and stored exactly as served when they fit 32 KiB, which covers SVG, ICO, and small PNG favicons; only larger bitmaps are downscaled to 96×96 (48×48 retry) through createImageBitmap on web and expo-image on mobile, and larger SVGs stay remote. Downloads are capped at 4 MiB while streaming. Each icon is its own record: IndexedDB on web and desktop, a project-favicon row in client_cache on mobile so Settings → Client storage counts and clears it. The cache holds at most 128 entries and 1 MiB. Hydrate before restoring the cached sidebar; refresh when the image revision changes, retain the previous image during failures, and remove it on a confirmed missing-icon response or when its environment is removed. Signed URL and origin changes reuse the same saved image. Storage is best effort. No new native dependencies.

Validation: 69 focused tests plus web, mobile, and client-runtime typechecks. In an isolated browser with /api/assets/** aborted across reload, icons restore from storage for both a PNG project and a viewBox-only favicon.svg project, and stay visible after networking is disabled. Native mobile UI was not exercised.

Before: reloading with remote icon requests held, using sample thread titles.

Generic project icons after reload before persistent thumbnails

After: the same requests remain held; the sidebar and breadcrumb use saved icons.

Saved project icons restored before remote responses

Recording of reload with remote icon requests held

After disabling networking and closing the WebSocket:

Project icons remain visible with the offline banner

An SVG favicon without width/height attributes, which browsers report at a 300×150 natural size, was thumbnailed at half size by the earlier canvas approach. Storing the bytes as served fixes it (breadcrumb after reload with asset requests aborted):

Breadcrumb icon for a viewBox-only favicon.svg: before, the icon renders at half size inside its box; after, it fills the box

Model: GPT-6. Harness: Codex in T3 Code. Follow-up fixes: Claude Fable 5, Claude Code in T3 Code.

Note

Persist project favicons across reloads and reconnects via shared cache and atom family

  • Adds a shared projectFaviconCache in client-runtime that downloads, validates, and downscales favicon images into self-contained data URLs, then persists them platform-side so icons survive signed-URL rotation and reconnects.
  • Web stores favicons in a dedicated IndexedDB database (projectFaviconCache.ts); mobile stores them in MobileDatabase under a new project-favicon cache kind (projectFaviconCache.ts).
  • A new projectFaviconUrlAtom atom family replaces the old asset-URL hooks in both ProjectFavicon components, retaining the last successful image during connection failures and returning cached data when available.
  • Both platforms hydrate the favicon cache on shell-cache load and clear it on environment clear; oversized raster images are downscaled (WebP on web, JPEG/PNG on mobile) before storage, bounded to 32 KiB per entry, 128 entries, and 1 MiB total.
  • Risk: MobileDatabase gains a new list-cache operation and project-favicon ClientCacheKind; any out-of-tree consumers of the mobile database schema or operation vocabulary must add support for these values.

Macroscope summarized dd57844.

@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:M 30-99 changed lines (additions + deletions). labels Sep 5, 2026
macroscopeapp[bot]
macroscopeapp Bot previously approved these changes Sep 5, 2026
@macroscopeapp

macroscopeapp Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR introduces substantial web and mobile runtime behavior for persistent favicon fetching, image processing, storage, hydration, refresh, and clearing across connection lifecycles. Its cross-cutting production impact and new persistence layers warrant human review.

You can add or adjust custom eligibility rules. Learn more.

@cursor

cursor Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

@macroscopeapp
macroscopeapp Bot dismissed their stale review September 5, 2026 14:50

Dismissing prior approval to re-evaluate 1933a14

macroscopeapp[bot]
macroscopeapp Bot previously approved these changes Sep 5, 2026
Comment thread packages/client-runtime/src/state/assets.ts
@macroscopeapp
macroscopeapp Bot dismissed their stale review September 5, 2026 18:06

Dismissing prior approval to re-evaluate 176ee22

@github-actions github-actions Bot added size:L 100-499 changed lines (additions + deletions). and removed size:M 30-99 changed lines (additions + deletions). labels Sep 5, 2026
@juliusmarminge juliusmarminge changed the title fix: retain remote project icons during reconnects fix(clients): persist project icons across reloads and reconnects Sep 5, 2026
Comment thread apps/mobile/src/components/ProjectFavicon.tsx
Comment thread apps/web/src/connection/storage.ts
Comment thread apps/web/src/assets/projectFaviconCache.ts Outdated
SVG favicons without width/height attributes report a 300x150 (or, on older
Firefox, 0x0) natural size, so the canvas thumbnail letterboxed the icon at
half size or collapsed it to one pixel and then persisted that image. Fetch
the icon bytes and inline them when they fit the cache limit; only bitmaps
larger than that go through a platform downscaler, and large SVGs stay remote.

Persist one record per icon rather than a single JSON blob so tabs cannot
overwrite each other's entries. On mobile the records live in client_cache so
Settings → Client storage counts and clears them, and clearing also drops the
in-memory images. Remote mobile icons key expo-image's disk cache by revision
again so signed-token rotation reuses cached bytes, and a changed icon starts
from the loading state.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@juliusmarminge

Copy link
Copy Markdown
Member

Pushed 8090a47 on top of 176ee22 to address the audit findings:

  • SVG icons without width/height were thumbnailed at half size (Chrome and Firefox ≥ 141 report a 300×150 natural size for a viewBox-only SVG; Firefox 120–140 report 0×0, which produced a 1×1 WebP). The canvas result then persisted and replaced the correctly rendering live icon. Icons are now fetched and stored as served when they fit the 32 KiB limit; only larger bitmaps go through a downscaler (createImageBitmap on web, expo-image on mobile), and larger SVGs stay remote. favicon.svg is the first candidate the server checks, so this affected a large share of projects.

    Breadcrumb icon for a viewBox-only favicon.svg after reload with asset requests blocked: before, the icon renders at half size inside its box; after, it fills the box

  • Mobile persistence was untested and silently no-ops for SVG on iOS: writeToCacheAsync(imageRef) re-encodes with the ref's sd_imageFormat, which the SVG coder tags as SVG and cannot encode, so nothing is written. With bytes inlined directly, the native decoder is now only used for large bitmaps, where both platforms encode PNG/JPEG.

  • One record per icon instead of a single JSON blob, so tabs no longer overwrite each other's entries (the Macroscope cross-tab finding). Web bumps the IndexedDB to v2 and drops the old store.

  • Mobile rows live in client_cache under kind project-favicon, so Settings → Client storage counts and clears them; clearing also drops the in-memory images.

  • Remote mobile icons key the expo-image disk cache by revision again, so signed-token rotation reuses cached bytes while a changed icon starts from the loading state (the Macroscope stale-loaded finding).

Verified in an isolated dev environment with a viewBox-only favicon.svg project and /api/assets/** aborted across reload. Focused tests (78) and typechecks for client-runtime, web, and mobile pass. Native mobile UI still not exercised.

Model: Claude Fable 5. Harness: Claude Code in T3 Code.

@github-actions github-actions Bot added size:XL 500-999 changed lines (additions + deletions). and removed size:L 100-499 changed lines (additions + deletions). labels Sep 5, 2026
Comment thread packages/client-runtime/src/projectFaviconCache.ts Outdated
Comment thread apps/mobile/src/lib/projectFaviconCache.ts Outdated
…obile runtime

Cap icon responses at 4 MiB while streaming so an oversized image is dropped
before it is materialized. On mobile, the environment cache store hands its
database to the favicon cache instead of the cache calling back into the
application runtime from inside a fiber that already runs on it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@cursor

cursor Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

A resolve that started after the revision bump but before the clear finished
could persist its result once the deletes had run. Downloads now wait for an
in-flight clear before capturing the revision they compare against.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@juliusmarminge
juliusmarminge merged commit eee0557 into pingdotgg:main Sep 6, 2026
26 checks passed
github-actions Bot added a commit to omarcresp/t3code-flake that referenced this pull request Sep 6, 2026
## What's Changed
* fix(server): settle threads using actual pull request terminal timestamps by @maria-rcks in pingdotgg/t3code#9934
* fix(clients): persist project icons across reloads and reconnects by @StiensWout in pingdotgg/t3code#10138
* fix(mobile): use selected theme across input forms and controls by @juliusmarminge in pingdotgg/t3code#10239
* fix(mobile): keep the new-task draft when switching environment by @juliusmarminge in pingdotgg/t3code#10247


**Full Changelog**: pingdotgg/t3code@v0.0.39-nightly.20260906.1291...v0.0.39-nightly.20260906.1292

Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.39-nightly.20260906.1292
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL 500-999 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants