Skip to content

fix(site-kit): StatusBanner reads its children once, so a second banner hydrates (D-028) - #65

Merged
jlc488 merged 1 commit into
mainfrom
fix/status-banner-children
Sep 16, 2026
Merged

jlc488 merged 1 commit into
mainfrom
fix/status-banner-children

Conversation

@jlc488

@jlc488 jlc488 commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Why

BookLinq's /status rendered site-kit's error layout in vite dev (jlc488/booklinq#51). StatusBanner read props.children twice — a truthiness check, then the insert. A compiled <StatusBanner><ul>…</ul></StatusBanner> child is a getter, so the first read built the list (and every <li> under it), threw it away, and consumed hydration keys the server never wrote into the HTML. BookLinq shipped a workaround (build the list into a const); the kit is where the read-once rule belongs (D-027).

What

  • layouts.tsxStatusBanner resolves children through one createMemo; the check and the insert share that value. Same idiom as the D-027 header/footer memo.
  • tests/site-kit-status-banner-hydration.test.mjs — SSR two banners with inline <ul><For> children (the BookLinq shape) using dist/solid.server.js, hydrate with dist/solid.js in a child process, assert no throw, no diagnostics, zero lost server data-hk keys, both lists and every item still keyed. Hooked into verify:site-kit:ui next to the shell hydration test.
  • Changeset — patch for @devslab/site-kit, bilingual.
  • docs/decisions.md — D-028.

What the reproduction taught

The production build cannot show this bug. Server and client waste the same number of keys on the discarded read, so the counters stay aligned, and when the client looks up a key that is not in the registry the production getNextElement silently clones a template — zero lost keys, page fine. Two earlier test shapes (a plain <p>, then the exact For list) were green on the unfixed kit. The development build (web/dist/dev.js) throws Hydration Mismatch at the same spot, which is what every consumer's dev server runs and what BookLinq saw. So the test hydrates with --conditions=browser --conditions=development. Red before the fix (Unable to find DOM nodes for hydration key: 0010), green after.

Verify

  • pnpm run verify — exit 0.
  • pnpm run verify:site-kit:ui — exit 0; hydration tests 4/4 (3 existing + the new one).
  • npm publishing is currently failing (E404 on OIDC trusted publishing, owner action); this ships with 0.12.2 once that is resolved. Do not merge yet per the task.

BookLinq /statusvite dev에서 site-kit의 에러 레이아웃을 그렸다(jlc488/booklinq#51). StatusBannerprops.children을 두 번 읽었다 — 진위 검사 뒤 삽입. 컴파일된 <StatusBanner><ul>…</ul></StatusBanner> children은 게터라 첫 읽기가 목록(과 그 아래 <li> 전부)을 만들어 버렸고, 서버 HTML에 없는 하이드레이션 키를 소모했다. BookLinq는 목록을 const로 만드는 우회로 출하했고, "받은 것을 한 번만 읽는다"(D-027)는 규칙은 kit의 것이다.

무엇

  • layouts.tsxStatusBannerchildrencreateMemo 하나로 해석해 검사와 삽입이 같은 값을 쓴다. D-027의 header/footer 메모와 같은 관용구.
  • tests/site-kit-status-banner-hydration.test.mjs — 배너 둘을 인라인 <ul><For> children(BookLinq 모양)으로 dist/solid.server.js로 SSR, 자식 프로세스에서 dist/solid.js로 하이드레이션, 예외 0·진단 0·잃은 서버 data-hk 0·목록과 항목 전부 키 유지 단언. verify:site-kit:ui에 셸 하이드레이션 테스트 옆으로 연결.
  • 체인지셋@devslab/site-kit patch, 양어.
  • docs/decisions.md — D-028.

재현이 가르쳐 준 것

프로덕션 빌드로는 이 버그가 보이지 않는다. 서버와 클라이언트가 버려지는 읽기에서 같은 수의 키를 낭비해 카운터가 안 어긋나고, 없는 키를 조회하면 프로덕션 getNextElement가 조용히 템플릿을 복제한다 — 잃은 키 0, 화면 정상. 앞선 두 판(평문 <p>, 그다음 For 목록 그대로)이 미수정 kit에서 초록이었다. 개발 빌드(web/dist/dev.js)는 같은 자리에서 Hydration Mismatch를 던지고, 그게 모든 소비자의 dev 서버가 도는 빌드이며 BookLinq가 본 것이다. 그래서 테스트는 --conditions=browser --conditions=development로 하이드레이션한다. 수정 전 빨강(hydration key: 0010), 수정 후 초록.

검증

  • pnpm run verify — exit 0.
  • pnpm run verify:site-kit:ui — exit 0, 하이드레이션 테스트 4/4(기존 3 + 신규 1).
  • npm 발행은 현재 실패 중(OIDC trusted publishing E404, 소유자 액션) — 해결되면 0.12.2에 실린다. 지시대로 머지하지 않음.

…er hydrates (D-028)

`{props.children && <div>{props.children}</div>}` read a compiled JSX getter
twice. The first read built the child (a list and every <li> under it) and
threw it away, consuming hydration keys the server never wrote. Solid's
production build clones a template when a key is missing, so counters stayed
aligned and nothing showed; the development build throws Hydration Mismatch,
and BookLinq's /status fell to the error layout in `vite dev`
(jlc488/booklinq#51). One createMemo is now the check and the insert.

The new test renders two banners with inline `<ul><For>` children and
hydrates with `--conditions=development`. Two earlier shapes (plain <p>, then
the For list) were green on the unfixed kit under the production build, which
is the finding: this defect is only visible where a missing key throws.
Red before the fix (`hydration key: 0010`), green after. Wired into
verify:site-kit:ui next to the shell hydration test.
@jlc488
jlc488 merged commit f19fde6 into main Sep 16, 2026
2 checks passed
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.

1 participant