Skip to content

adopt(linkwarden): ready to send: theme and reader text settings revert when preference responses arrive out of order - #159

Open
MeharPro wants to merge 1 commit into
mainfrom
claude/zen-ramanujan-reujxv
Open

MeharPro wants to merge 1 commit into
mainfrom
claude/zen-ramanujan-reujxv

Conversation

@MeharPro

@MeharPro MeharPro commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

Status: ready to send (after the contact-first step below; everything left for the sender is by hand).

What

A race hunt on Linkwarden's web app (linkwarden/linkwarden dev at 45325d4). It found one real race. useUpdateUserPreference (packages/router/user.tsx) writes every PUT /api/v1/users/:id/preference response over the ["user"] cache and <html data-theme>, in whatever order the responses arrive. So the reader view's light/dark switch clicked twice ends dark on the page while the server stores light, and three font-size "+" clicks end showing 22px or 24px while 26px is stored (in 3 loads the server lost a step too). It also prepares a 16-line fix (only the latest update writes its response) with a Vitest regression test in Linkwarden's style, plus the issue and PR drafts in Linkwarden's templates, with the AI-assistance disclosure.

Files

  • docs/adoption/hunts/linkwarden.md: the report (setup, every scenario with its verdict, the finding in causal order, root cause, fix, the fixed-variant check, problems with GenClass, time spent, how to reproduce)
  • docs/adoption/hunts/linkwarden.patch: the fix and packages/router/user.test.ts, against 45325d45c5326f4418159294dcafdafbb95fbd62; git apply --check is clean on that commit
  • docs/adoption/hunts/linkwarden-issue.md: the "before sending" checklist, the issue (Bug Report template) and the PR text (.github/pull_request_template.md, base dev)
  • docs/adoption/hunts/linkwarden/: hunt.mjs (explore harness), sweep.sh, seed.mjs, repro.mjs (no GenClass), issue-repro.mjs (the issue's script), summarise.mjs, results.json (per-load summary, 88 KB)
  • the row in docs/adoption/hunts/README.md

To send (a person, from a personal account)

  1. Contact first. Linkwarden's README asks contributors to "please stay in touch with @daniel31x13 before starting". Send him the issue text (GitHub or Discord) and ask whether he wants the PR.
  2. Search the tracker for duplicates filed since 2026-11-03 (queries are in the checklist).
  3. Run node docs/adoption/hunts/linkwarden/issue-repro.mjs against a local dev: expect page: dark, server: light without the patch and light/light with it. Run yarn vitest run packages/router/user.test.ts: 1 failed, 1 passed before the patch; 2 passed after. Read the diff.
  4. Record the before/after video the PR template asks for (headed Playwright with recordVideo; DevTools throttling does not reorder the responses).
  5. Open the issue, then fork, branch from dev, apply the patch, and open the PR against dev (check-branch.yml rejects PRs into main unless the head is dev). Tick the template's AI level ("Heavy" fits) and the verification boxes only for what you did yourself. If asked how it was found, the drafts' disclosure sentence covers it.

Measured

  • Explore, unfixed (30 sessions kept in total, 240 loads, all settled; 8 loads per session = 2 fifo baselines + 6 seeded; 300/600/900 ms):
    • font-size: race in all 6 sessions, 3 or 4 of 6 reordered loads each.
    • theme-twice: race in all 3 sessions from seed 7 (3, 3, 4 of 6); no race in the 3 sessions on default seeds 1–6 (see problems).
    • 9 of 9 replays reproduced.
    • The secondary DOM+server oracle agreed on every load.
  • Without GenClass (repro.mjs, first response delayed 1,500 ms, 3 trials per case):
    • theme: dark shown, light stored, 3 of 3.
    • font: 22px shown, 26px stored, 3 of 3.
    • With 0 ms delay: correct in 3 of 3 for each.
  • Fixed variant: 0 of 72 reordered loads differ in 12 sessions (32 of 72 before). The repro ends on the last choice in 6 of 6 trials. Vitest fails 3 of 3 runs before and passes 3 of 3 after. tsc --strict and prettier are clean. Linkwarden's full Vitest suite: 52 passed, 16 skipped; one database test file fails identically without the patch because DATABASE_URL is unset.
  • Negatives (false-report side):
    • A link renamed while the 5 s preservation poll is in flight: 3 sessions, no race, 0 of 18 differing.
    • Pin then unpin: 3 sessions, no race, the responses reversed in 3 of 18 loads, 0 differing.
    • Explore's verdict was right on all of them, but genclass_findings listed 20 false stale-write findings per session (120 total) on TanStack Query bookkeeping, DndContext and component counters.

Problems with GenClass itself (report section 6)

  1. Seeds 1–6 give PUT …/preference#1 priority over #2 every time, so the default session said "No race found" on a racing page at all three latencies. Suggestion: cover both orders of each overlapping pair, or say when the unsafe requests were never reordered.
  2. False overwrote-newer witnesses on every optimistic mutation. A response is counted as overwriting its own change's optimistic write, because TanStack's notifyManager setTimeout(0) flush looks like a newer timer. That leaves 12 per load (12–19 stale-write findings per fixed session).
  3. One cache write becomes 12–16 findings, one per mirroring hook result.
  4. A controlled checkbox's recorded change value is the opposite of its new state.
  5. Harness only: the dev server hit the 30-minute background-job limit mid-sweep, and the first pin-unpin harness clicked a closing Radix menu. 8 sessions were discarded and re-run. Explore still printed "No race found" for the broken pin sessions, whose loads had all failed the same way.

Checks

  • No change outside docs/adoption/; no runtime, MCP, test or model-visible text changed; no dependency added.
  • Runtime typecheck, build and unit tests: not run, since nothing under packages/ changed (the runtime and MCP were built from this checkout for the hunt).
  • Nothing was sent to Linkwarden or anywhere else.

Notes for the reviewer

  • Look at report section 4 first, then section 6 item 1.
  • Not run:
    • Linkwarden's Playwright e2e: its Playwright 1.57 needs Chromium build 1200, and cdn.playwright.dev is blocked here.
    • The mobile app.
    • The profile-menu theme path (same hook, untested).
    • useUpdateUser (same shape, sidebar drag and drop): noted as a pointer only.
  • Branch: pushed to this session's designated branch claude/zen-ramanujan-reujxv, not adopt/linkwarden (the session's git instructions fix the branch name).
  • Time: about 1 h 15 min to the last measurement, plus writing.

Generated by Claude Code

…onses out of order

A GenClass race hunt on Linkwarden (dev at 45325d4), run locally with the
runtime and the MCP server from this checkout injected from outside.

Found: useUpdateUserPreference (packages/router/user.tsx) writes every
PUT /api/v1/users/:id/preference response over the ["user"] cache and
<html data-theme> in onSuccess, in arrival order. The reader view's
light/dark switch clicked twice ends dark on the page with light stored;
three font size steps end showing 22px or 24px with 26px stored.
Explore: 32 of 72 reordered loads in 12 sessions, every replay
reproduced; without GenClass, 3 of 3 trials per case with the first
response delayed 1.5 s (0 of 3 without). Fix: only the latest update
writes its response; Vitest regression test fails before, passes after;
0 of 72 on the fixed variant.

Negatives: a link renamed during the preservation poll, pin then unpin.
GenClass problems recorded in the report, section 6 (seeds 1-6 never
swap the theme's two PUTs; false overwrote-newer witnesses from
TanStack's notify batch).

Files: docs/adoption/hunts/linkwarden.md, linkwarden.patch,
linkwarden-issue.md, linkwarden/ (harness, results.json), README row.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Fqwfq1ezG6pDWFmtkEF799

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants