fix(site-kit): StatusBanner reads its children once, so a second banner hydrates (D-028) - #65
Merged
Merged
Conversation
…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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
BookLinq's
/statusrendered site-kit's error layout invite dev(jlc488/booklinq#51).StatusBannerreadprops.childrentwice — 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.tsx—StatusBannerresolveschildrenthrough onecreateMemo; 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) usingdist/solid.server.js, hydrate withdist/solid.jsin a child process, assert no throw, no diagnostics, zero lost serverdata-hkkeys, both lists and every item still keyed. Hooked intoverify:site-kit:uinext to the shell hydration test.@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
getNextElementsilently clones a template — zero lost keys, page fine. Two earlier test shapes (a plain<p>, then the exactForlist) were green on the unfixed kit. The development build (web/dist/dev.js) throwsHydration Mismatchat 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).왜
BookLinq
/status가vite dev에서 site-kit의 에러 레이아웃을 그렸다(jlc488/booklinq#51).StatusBanner가props.children을 두 번 읽었다 — 진위 검사 뒤 삽입. 컴파일된<StatusBanner><ul>…</ul></StatusBanner>children은 게터라 첫 읽기가 목록(과 그 아래<li>전부)을 만들어 버렸고, 서버 HTML에 없는 하이드레이션 키를 소모했다. BookLinq는 목록을 const로 만드는 우회로 출하했고, "받은 것을 한 번만 읽는다"(D-027)는 규칙은 kit의 것이다.무엇
layouts.tsx—StatusBanner가children을createMemo하나로 해석해 검사와 삽입이 같은 값을 쓴다. 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-hk0·목록과 항목 전부 키 유지 단언.verify:site-kit:ui에 셸 하이드레이션 테스트 옆으로 연결.@devslab/site-kitpatch, 양어.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).