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:
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).