Fix #41: mutation refetch ordering guard #52

Merged
crueber merged 1 commit from fix/issue-41 into main 2026-09-04 19:46:34 +00:00
Owner

Fixes #41 (post-mutation refetch committing SWR-stale bodies, reaction live-update flake).

Diagnosis verified: ref-dependent GETs serve private, max-age=0, stale-while-revalidate=60 + ETag (probed live stack: /api/refs, /api/resolve, /api/v1/owners), and web/src/lib/data.js had no ordering guard — invalidate() even SKIPPED when a fetch was in flight, and last-to-resolve won unconditionally (reproduced both headless against the old logic).

Centralized fix, no per-page churn (43 useData sites untouched):

  • data.js generations: every start() bumps entry.seq; stale bodies AND stale errors dropped; promise bookkeeping guarded too.
  • invalidate() ALWAYS starts a new generation under ReposClient.withNoStore (cache: no-store + no-cache headers); background reads keep single-flight + TTL.
  • SDK 304 resolves NOT_MODIFIED sentinel (Symbol.for) instead of ReposError(304); silent keep-current in data.js.
  • prefetchData() headless seam (useData effects can't run under the solid-js server build).

Tests: node --test web/test/unit/*.test.js 217/217 green (11 new: data-guard 6, sdk-nostore-304 5); new tests fail on pre-fix code; vite build clean. No runnable Chromium in this env, so no browser pass — node tests + code reasoning only. No new deps. Docs: 12_web_ui.md 2.4/1.2 + Decisions entry.

Fixes #41 (post-mutation refetch committing SWR-stale bodies, reaction live-update flake). Diagnosis verified: ref-dependent GETs serve `private, max-age=0, stale-while-revalidate=60` + ETag (probed live stack: /api/refs, /api/resolve, /api/v1/owners), and web/src/lib/data.js had no ordering guard — invalidate() even SKIPPED when a fetch was in flight, and last-to-resolve won unconditionally (reproduced both headless against the old logic). Centralized fix, no per-page churn (43 useData sites untouched): - data.js generations: every start() bumps entry.seq; stale bodies AND stale errors dropped; promise bookkeeping guarded too. - invalidate() ALWAYS starts a new generation under ReposClient.withNoStore (cache: no-store + no-cache headers); background reads keep single-flight + TTL. - SDK 304 resolves NOT_MODIFIED sentinel (Symbol.for) instead of ReposError(304); silent keep-current in data.js. - prefetchData() headless seam (useData effects can't run under the solid-js server build). Tests: node --test web/test/unit/*.test.js 217/217 green (11 new: data-guard 6, sdk-nostore-304 5); new tests fail on pre-fix code; vite build clean. No runnable Chromium in this env, so no browser pass — node tests + code reasoning only. No new deps. Docs: 12_web_ui.md 2.4/1.2 + Decisions entry.
Sign in to join this conversation.
No description provided.