fix: hydration-safe banner clock (#44, v0.10.1) #47

Merged
kamilkrzywda merged 4 commits from fix/hydration-jump-dispatch into master 2026-10-04 01:52:28 +02:00
Owner

Follow-up to PR #46 / issue #44 — the last piece that makes v0.10.0 actually usable.

What broke

Every production load of / tripped a React 19 hydration mismatch (#418). In the common race window, the floating TeamBuilder on the default (SSR'd) WORLD MAP tab committed stale props: its DISPATCH button stayed disabled=true permanently — in the very render that printed // LEAD and WILL TAKE N TURN(S) (i.e. blocked === null, leadReason === null). The jump flow was unusable for real users, and CI's two jump tests (the only ones driving that builder on the default tab) timed out — the rest of the suite switches tabs first and passed, which masked it.

Root cause

Next 16 prerenders / statically (build time, s-maxage=31536000), so the SSR HTML carried the build-time wall-clock string while the client rendered the current time. That text mismatch fired #418 on every load; the in-place hydration recovery then raced the 1s setInterval tick and the world/crew fetch setStates, and the builder committed the stale props.

The fix

Hydration-safe banner clock in components/mission-control.tsx: a hydrated state (flipped in a mount useEffect) makes the clock render '' in both SSR and the first client render — no mismatch, no #418, no race. The existing 1s tick keeps the clock moving afterwards, and the builder commits disabled=false as designed. No test changes — the tests are correct; the jump specs go green on CI's e2e against the production build.

Why v0.10.1

One-line behavioural fix on top of v0.10.0, shipped as a patch release (version bump in package.json + CHANGELOG entry). The branch also carries a small docs commit committing the nextjs-agent-rules block that next dev appends to AGENTS.md.

Follow-up to PR #46 / issue #44 — the last piece that makes v0.10.0 actually usable. ## What broke Every production load of `/` tripped a React 19 hydration mismatch (#418). In the common race window, the floating `TeamBuilder` on the default (SSR'd) WORLD MAP tab committed stale props: its DISPATCH button stayed `disabled=true` permanently — in the very render that printed `// LEAD` and `WILL TAKE N TURN(S)` (i.e. `blocked === null`, `leadReason === null`). The jump flow was unusable for real users, and CI's two jump tests (the only ones driving that builder on the default tab) timed out — the rest of the suite switches tabs first and passed, which masked it. ## Root cause Next 16 prerenders `/` statically (build time, `s-maxage=31536000`), so the SSR HTML carried the build-time wall-clock string while the client rendered the current time. That text mismatch fired #418 on **every** load; the in-place hydration recovery then raced the 1s `setInterval` tick and the world/crew fetch `setState`s, and the builder committed the stale props. ## The fix Hydration-safe banner clock in `components/mission-control.tsx`: a `hydrated` state (flipped in a mount `useEffect`) makes the clock render `''` in both SSR and the first client render — no mismatch, no #418, no race. The existing 1s tick keeps the clock moving afterwards, and the builder commits `disabled=false` as designed. No test changes — the tests are correct; the jump specs go green on CI's e2e against the production build. ## Why v0.10.1 One-line behavioural fix on top of v0.10.0, shipped as a patch release (version bump in `package.json` + CHANGELOG entry). The branch also carries a small docs commit committing the `nextjs-agent-rules` block that `next dev` appends to `AGENTS.md`.
kamilkrzywda added 3 commits 2026-10-03 20:38:25 +02:00
kamilkrzywda added 1 commit 2026-10-04 01:42:33 +02:00
kamilkrzywda merged commit 678cd48a94 into master 2026-10-04 01:52:28 +02:00
kamilkrzywda deleted branch fix/hydration-jump-dispatch 2026-10-04 01:52:28 +02:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: OpenCode/SpaceWanderer#47