Archilyzer · Source

archilyzer

Archilyzer
git clone https://archilyzer.pages.dev/source/archilyzer.git
Log | Files | Refs | README | LICENSE

commit db57de0ac47061b0a73b83d1295224717b745a80
parent beae1f1357a9557a74cce95df440507440819b26
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Tue, 29 Sep 2026 23:02:18 -0400

editor: the provider's two effects write the cookie without an optimistic state change

The visit-a-site's-page effect and the localStorage migration run as the page
hydrates. Through `choose` they set state there, which re-rendered the picker
before React replayed a choice made on the server-rendered select before
hydration: the re-render put the select back to its old value, and the replayed
change read that value instead (site-scope.spec's two /sites/<id>/… cases
navigated nowhere). They now only write the cookie (`record`), and the value
comes back through the server's re-render; `choose`, with its optimistic
update, is for the picker's own changes. The visit is recorded once per arrival
at a site's pages, so a StrictMode or Fast Refresh re-run of the effect does
not write again.

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

Diffstat:
Meditor/app/components/SiteScopeProvider.tsx | 33++++++++++++++++++++++++++++-----
1 file changed, 28 insertions(+), 5 deletions(-)

diff --git a/editor/app/components/SiteScopeProvider.tsx b/editor/app/components/SiteScopeProvider.tsx @@ -34,6 +34,11 @@ import { setActiveSiteAction } from "../lib/activeSiteActions"; // cookie gets it copied into the cookie, then the key is removed. That is the // only localStorage read, and the one paint it may cost is on the first visit // after the update. +// Both only WRITE the cookie (`record`); the value comes back through the +// server's re-render. They run as the page hydrates, and a state change there +// would re-render the picker before React replays a choice made on the +// server-rendered select before hydration: the re-render resets the select to +// its old value, and the replayed change reads that instead of the choice. type SiteScope = { siteIds: string[]; // The stored selection: a site id or "__all__". The picker resolves it @@ -97,14 +102,32 @@ export function SiteScopeProvider({ return ok; }, []); - // Visiting a site's own page records it. + // Write a value the page did not choose — no optimistic state (see above). + const record = useCallback(async (value: string): Promise<boolean> => { + try { + return await setActiveSiteAction(value); + } catch { + return false; + } + }, []); + + // Visiting a site's own page records it: once per arrival at that site's + // pages (a re-run of the effect — StrictMode, Fast Refresh — does not write + // again), and not when the store already holds it. const pathname = usePathname(); const pathSite = siteIdFromPathname(pathname)?.siteId ?? null; const known = pathSite !== null && siteIds.includes(pathSite); + const recordedFor = useRef<string | null>(null); useEffect(() => { - if (!known || pathSite === storedRef.current) return; - void choose(pathSite as string); - }, [known, pathSite, choose]); + if (!known || pathSite === null) { + recordedFor.current = null; + return; + } + if (recordedFor.current === pathSite) return; + recordedFor.current = pathSite; + if (pathSite === storedRef.current) return; + void record(pathSite); + }, [known, pathSite, record]); // The one-time move from localStorage. Guarded by a ref, not by deps: it is // about this page load, and StrictMode's second mount must not repeat it. @@ -136,7 +159,7 @@ export function SiteScopeProvider({ return; } // Removed only once the cookie holds it, so a failed write loses nothing. - void choose(legacy).then((ok) => { + void record(legacy).then((ok) => { if (ok) forget(); }); // Once per page load, on purpose (see the ref).