From aaeb125584f8d1afcb803495fc3c70e6d9bd470f Mon Sep 17 00:00:00 2001 From: pythonlearner1025 <77006616+pythonlearner1025@users.noreply.github.com> Date: Wed, 2 Sep 2026 01:01:11 +0000 Subject: [PATCH 1/2] fix(components): read the repos cache lazily per store `allGitHubReposCacheAtom` was built with `atom(githubReposCache.readAll())`, so the localStorage read ran once at module import and became the atom's `init`. `init` belongs to the atom, not to a store, so every store created afterwards started from that import-time snapshot and missed repo lists cached since. `atomWithDefault` evaluates its default per store, on first read, and stays writable -- verified against the pinned jotai 2.17.1 -- so `setWorkspaceReposCacheAtom` is unchanged. It also moves the localStorage read out of module scope, matching the `atomFamily` and `atomWithStorage` atoms beside it. Model: claude-opus-5 --- packages/components/src/atoms/local-storage-cache.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/components/src/atoms/local-storage-cache.ts b/packages/components/src/atoms/local-storage-cache.ts index 6a2db61ed..79495f854 100644 --- a/packages/components/src/atoms/local-storage-cache.ts +++ b/packages/components/src/atoms/local-storage-cache.ts @@ -1,6 +1,6 @@ import { atom } from 'jotai'; import type { PersistedMentionRange } from '@/components/mentions/mention-persistence'; -import { atomFamily, atomWithStorage } from 'jotai/utils'; +import { atomFamily, atomWithDefault, atomWithStorage } from 'jotai/utils'; import type { PastedTextDraft } from '@/lib/pasted-text-draft'; import { type CachedGitHubRepo, @@ -10,7 +10,13 @@ import { // ============ GitHub Repos Cache ============ -export const allGitHubReposCacheAtom = atom>( +// `atomWithDefault`, not `atom(githubReposCache.readAll())`: a primitive atom's +// `init` is evaluated once, when this module is imported, and is then shared by +// every store. Any store created after import would start from that import-time +// snapshot and silently miss repo lists cached since. Reading lazily gives each +// store the localStorage contents at its own first read, and keeps the read out +// of module scope. +export const allGitHubReposCacheAtom = atomWithDefault>(() => githubReposCache.readAll() ); From c013fb033de31d446e346e32dc01d0e048660818 Mon Sep 17 00:00:00 2001 From: pythonlearner1025 <77006616+pythonlearner1025@users.noreply.github.com> Date: Wed, 2 Sep 2026 01:57:07 +0000 Subject: [PATCH 2/2] fix(components): read the GitHub repos cache lazily, per store MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `allGitHubReposCacheAtom` was built as `atom(githubReposCache.readAll())`. A primitive atom's `init` is evaluated once — when the module is imported — and that one value is then shared by every store. Any store created after import therefore starts from the import-time snapshot and silently misses repo lists cached since, even though `setWorkspaceReposCacheAtom` wrote them to localStorage. It also puts a localStorage read in module scope, which runs on import whether or not the atom is ever used. `atomWithDefault` evaluates its default lazily and per store, so each store reads the localStorage contents at its own first read. It stays writable, so `setWorkspaceReposCacheAtom` is unchanged. SCOPE, stated honestly: this is a latent store-dependency, not a user-facing bug fix. The OSS composition has exactly one store — `jotaiStore` in `lib/utils.ts`, created at import and passed to the single provider in `main.tsx` — and the only stores built later in this tree are `createTourStore()` and Storybook stories. I could not trace a path from either to a consumer of this atom, so I cannot exhibit an in-app reproduction. What is demonstrated is the staleness itself, which the test pins. Verified against the pinned jotai 2.17.1 rather than assumed: a store created after a write reads the import-time snapshot from `atom(readAll())` and the current contents from `atomWithDefault(() => readAll())`. Model: claude-opus-5 --- .../tests/atoms-local-storage-cache.test.ts | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 packages/components/tests/atoms-local-storage-cache.test.ts diff --git a/packages/components/tests/atoms-local-storage-cache.test.ts b/packages/components/tests/atoms-local-storage-cache.test.ts new file mode 100644 index 000000000..d8129cdbc --- /dev/null +++ b/packages/components/tests/atoms-local-storage-cache.test.ts @@ -0,0 +1,58 @@ +/** @vitest-environment jsdom */ + +import { createStore } from 'jotai'; +import { beforeEach, describe, expect, it } from 'vitest'; + +// Importing this module evaluates the repos-cache atom's default exactly once, +// against an empty localStorage. Every write below happens after that, which is +// the whole point: a store built later must not inherit that snapshot. +import { + allGitHubReposCacheAtom, + setWorkspaceReposCacheAtom, + workspaceReposCacheAtomFamily, +} from '../src/atoms/local-storage-cache'; +import { githubReposCache, type CachedGitHubRepo } from '../src/lib/local-storage-cache'; + +const REPOSITORY: CachedGitHubRepo = { fullName: 'LodyAI/Lody', description: null }; + +beforeEach(() => { + localStorage.clear(); +}); + +describe('allGitHubReposCacheAtom', () => { + it('gives a store created after a write the cached repos, not the import-time snapshot', () => { + githubReposCache.set('ws-1', { repositories: [REPOSITORY], updatedAt: 1 }); + + const laterStore = createStore(); + + expect(laterStore.get(allGitHubReposCacheAtom)['ws-1']?.repositories).toEqual([REPOSITORY]); + expect(laterStore.get(workspaceReposCacheAtomFamily('ws-1'))).toEqual([REPOSITORY]); + }); + + it('stays writable through setWorkspaceReposCacheAtom, in localStorage and in the store', () => { + const store = createStore(); + + store.set(setWorkspaceReposCacheAtom, { workspaceId: 'ws-2', repositories: [REPOSITORY] }); + + expect(store.get(workspaceReposCacheAtomFamily('ws-2'))).toEqual([REPOSITORY]); + expect(githubReposCache.get('ws-2')?.repositories).toEqual([REPOSITORY]); + }); + + it('keeps each store independent', () => { + const writingStore = createStore(); + writingStore.set(setWorkspaceReposCacheAtom, { + workspaceId: 'ws-3', + repositories: [REPOSITORY], + }); + + // A store built afterwards re-reads localStorage rather than sharing state. + const laterStore = createStore(); + expect(laterStore.get(workspaceReposCacheAtomFamily('ws-3'))).toEqual([REPOSITORY]); + + localStorage.clear(); + const cleanStore = createStore(); + expect(cleanStore.get(workspaceReposCacheAtomFamily('ws-3'))).toBeNull(); + // The store that did the write keeps its own value. + expect(writingStore.get(workspaceReposCacheAtomFamily('ws-3'))).toEqual([REPOSITORY]); + }); +});