# A React hydration race that left the page hidden

> A CodeVetter review caught a React startup race. Inspect the public repair, before-and-after evidence, and replayable hydration regression tests.

Canonical: https://codevetter.com/articles/react-hydration-hidden-root-regression

Last updated: 2026-10-31

A CodeVetter review of [SWE Interview Prep PR #158](https://github.com/Significant-Hobbies/swe-interview-prep/pull/158) surfaced a startup race: removing a saved profile or progress before bootstrap could select hydration while the page root stayed hidden. The finding was then checked with the repository's visibility tests. **Before the repair: two failed, two passed. After the repair: all four passed.**

This is one observed review-and-reproduction workflow, not a catch-rate benchmark. [Inspect the redacted evidence summary](https://codevetter.com/examples/hydration-visibility-case.json) or [replay the checks](https://codevetter.com/articles/react-hydration-hidden-root-regression#reproduce-in-a-disposable-clone).

## How the page could stay hidden

An early script decided whether to show a prerendered public page. A visitor with saved learner state took a different path: the prerendered root kept its visibility guard while the client prepared the returning-user experience.

Bootstrap checked eligibility again. If the saved profile or progress had disappeared in between, the fresh decision now allowed the public page. The [reviewed guard](https://github.com/Significant-Hobbies/swe-interview-prep/blob/9cd5e5ebe84375277f0ff252052321024ec50e7b/src/lib/publicEntry.ts#L13-L19) checked that fresh decision and the root marker, but omitted the original decision. It could therefore choose hydration for a root that the early script had kept hidden.

This is a transition bug. Testing a guest from an empty storage state, or a returning learner whose state never changes, does not exercise the same sequence.

## What established the failure

The [regression tests](https://github.com/Significant-Hobbies/swe-interview-prep/blob/5f79e57fe99bfc22c576465214cf90881c71acab/src/lib/publicEntry.test.ts#L8-L81) read the actual visibility CSS from the application's index.html. They run the early-entry script with a saved profile or progress, remove that state before bootstrap, and check both the hydration decision and the root's computed display.

The vulnerable guard returned hydrate: true with display: none in those two cases. The expected fallback was hydrate: false with display: block. The same four-check target passed after the repair. These are recorded local Vitest/happy-dom results, not measurements of real-user traffic or a browser compatibility study.

## The repair preserves both decisions

The [repaired guard](https://github.com/Significant-Hobbies/swe-interview-prep/blob/5f79e57fe99bfc22c576465214cf90881c71acab/src/lib/publicEntry.ts#L13-L35) requires the original state.prerender decision as well as current entry.prerender eligibility. If either disallows hydration, it removes the prerender marker and clears the old children so normal client rendering can take over visibly.

The other checks retain real anchor destinations, hand one early Settings intent to the actual modal, and cover a profile arriving before bootstrap. React's [hydrateRoot documentation](https://react.dev/reference/react-dom/client/hydrateRoot) explains why hydration must attach to matching initial HTML; it is not a substitute for checking this application's visibility rules.

## Reproduce in a disposable clone

Use a new clone: the second block deliberately replaces one source file. The repository pins pnpm 10.33.2. First run the repaired version:

```bash
git clone https://github.com/Significant-Hobbies/swe-interview-prep.git swe-hydration-case
cd swe-hydration-case
git switch --detach 5f79e57fe99bfc22c576465214cf90881c71acab
corepack pnpm install --frozen-lockfile
corepack pnpm exec vitest run src/lib/publicEntry.test.ts --reporter=json
```

To retain the repaired regression tests while trying the reviewed vulnerable guard, replace only publicEntry.ts. The middle command is expected to exit nonzero; then restore the repaired file and run again:

```bash
git show 9cd5e5ebe84375277f0ff252052321024ec50e7b:src/lib/publicEntry.ts > src/lib/publicEntry.ts
corepack pnpm exec vitest run src/lib/publicEntry.test.ts --reporter=json
# Expected: two failing cases. Restore the repaired file afterward.
git restore --source=5f79e57fe99bfc22c576465214cf90881c71acab -- src/lib/publicEntry.ts
corepack pnpm exec vitest run src/lib/publicEntry.test.ts --reporter=json
```

This replays the focused test target, not the original model-backed review. The [public evidence summary](https://codevetter.com/examples/hydration-visibility-case.json) records the original before/after counts and pinned sources without exposing private review logs.

## What to take into your own agent workflow

When an agent changes startup or persistence behavior, write down the state transition you need to preserve. Bind the exact change, reproduce a suspected failure with repository-owned checks, and keep both the failing and passing results. A review finding identifies a risk; execution establishes the behavior those checks actually cover.

The four checks qualify this startup repair; broader browser behavior and user outcomes need separate evidence. [Download CodeVetter](https://codevetter.com/download?utm_source=case-study&utm_medium=owned&utm_campaign=hydration-visibility) to inspect an agent change locally, or follow the [AI-code verification recipe](https://codevetter.com/verify-ai-generated-code#worked-example).

## Public product links

- [CodeVetter](https://codevetter.com/)
- [Download](https://codevetter.com/download)
- [Documentation](https://codevetter.com/docs/)
- [Source](https://github.com/Codevetter/codevetter)
