From 14db336e3a201198cc6e6c3083e1f8d68444e644 Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Thu, 22 Jan 2026 20:17:45 -0600 Subject: [PATCH] fix(app): flash of fallback icon for projects --- .../src/components/dialog-edit-project.tsx | 1 + packages/app/src/context/global-sync.tsx | 35 ++++++++++++++++++- packages/app/src/context/layout.tsx | 10 +++++- 3 files changed, 44 insertions(+), 2 deletions(-) diff --git a/packages/app/src/components/dialog-edit-project.tsx b/packages/app/src/components/dialog-edit-project.tsx index a90cac169..9e2bddc6b 100644 --- a/packages/app/src/components/dialog-edit-project.tsx +++ b/packages/app/src/components/dialog-edit-project.tsx @@ -85,6 +85,7 @@ export function DialogEditProject(props: { project: LocalProject }) { icon: { color: store.color, override: store.iconUrl }, commands: { start }, }) + globalSync.project.icon(props.project.worktree, store.iconUrl || undefined) setStore("saving", false) dialog.close() return diff --git a/packages/app/src/context/global-sync.tsx b/packages/app/src/context/global-sync.tsx index b6d9b518f..5f0f3d76f 100644 --- a/packages/app/src/context/global-sync.tsx +++ b/packages/app/src/context/global-sync.tsx @@ -61,6 +61,7 @@ type State = { command: Command[] project: string projectMeta: ProjectMeta | undefined + icon: string | undefined provider: ProviderListResponse config: Config path: Path @@ -107,6 +108,12 @@ type MetaCache = { ready: Accessor } +type IconCache = { + store: Store<{ value: string | undefined }> + setStore: SetStoreFunction<{ value: string | undefined }> + ready: Accessor +} + type ChildOptions = { bootstrap?: boolean } @@ -119,6 +126,7 @@ function createGlobalSync() { if (!owner) throw new Error("GlobalSync must be created within owner") const vcsCache = new Map() const metaCache = new Map() + const iconCache = new Map() const [projectCache, setProjectCache, , projectCacheReady] = persisted( Persist.global("globalSync.project", ["globalSync.project.v1"]), @@ -126,12 +134,13 @@ function createGlobalSync() { ) const sanitizeProject = (project: Project) => { - if (!project.icon?.url) return project + if (!project.icon?.url && !project.icon?.override) return project return { ...project, icon: { ...project.icon, url: undefined, + override: undefined, }, } } @@ -207,10 +216,20 @@ function createGlobalSync() { if (!meta) throw new Error("Failed to create persisted project metadata") metaCache.set(directory, { store: meta[0], setStore: meta[1], ready: meta[3] }) + const icon = runWithOwner(owner, () => + persisted( + Persist.workspace(directory, "icon", ["icon.v1"]), + createStore({ value: undefined as string | undefined }), + ), + ) + if (!icon) throw new Error("Failed to create persisted project icon") + iconCache.set(directory, { store: icon[0], setStore: icon[1], ready: icon[3] }) + const init = () => { const child = createStore({ project: "", projectMeta: meta[0].value, + icon: icon[0].value, provider: { all: [], connected: [], default: {} }, config: {}, path: { state: "", config: "", worktree: "", directory: "", home: "" }, @@ -237,6 +256,10 @@ function createGlobalSync() { createEffect(() => { child[1]("projectMeta", meta[0].value) }) + + createEffect(() => { + child[1]("icon", icon[0].value) + }) } runWithOwner(owner, init) @@ -811,6 +834,15 @@ function createGlobalSync() { setStore("projectMeta", next) } + function projectIcon(directory: string, value: string | undefined) { + const [store, setStore] = ensureChild(directory) + const cached = iconCache.get(directory) + if (!cached) return + if (store.icon === value) return + cached.setStore("value", value) + setStore("icon", value) + } + return { data: globalStore, set: setGlobalStore, @@ -833,6 +865,7 @@ function createGlobalSync() { project: { loadSessions, meta: projectMeta, + icon: projectIcon, }, } } diff --git a/packages/app/src/context/layout.tsx b/packages/app/src/context/layout.tsx index 9db03b25f..444a36d65 100644 --- a/packages/app/src/context/layout.tsx +++ b/packages/app/src/context/layout.tsx @@ -235,7 +235,7 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext( ...project, icon: { url: metadata?.icon?.url, - override: metadata?.icon?.override, + override: metadata?.icon?.override ?? childStore.icon, color: metadata?.icon?.color, }, } @@ -306,6 +306,14 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext( const projects = enriched() if (projects.length === 0) return + if (globalSync.ready) { + for (const project of projects) { + if (!project.id) continue + if (project.id === "global") continue + globalSync.project.icon(project.worktree, project.icon?.override) + } + } + const used = new Set() for (const project of projects) { const color = project.icon?.color ?? colors[project.worktree]