Skip to content

fix(site-kit): the shell reads header/footer once; an empty flag sprite loads its bodies (D-027) - #63

Merged
jlc488 merged 2 commits into
mainfrom
fix/flag-sprite-hydration
Sep 15, 2026
Merged

jlc488 merged 2 commits into
mainfrom
fix/flag-sprite-hydration

Conversation

@jlc488

@jlc488 jlc488 commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Why

AskLinq's flag menu went blank on site-kit 0.12.0 (owner report, within two hours of devslab-kr/asklinq#436). Server HTML had every symbol; the live DOM had an empty sprite and a <use> pointing at a uid the server never wrote. Nearly the whole header had lost its hydration keys.

header={{ … }} compiles to a getter; MarketingShell spread props.header straight into SiteHeader, so each prop read re-evaluated the literal and rebuilt the eager actions JSX inside it, consuming hydration keys a different number of times on the server than in the browser. The client rebuilt the header from templates. The sprite (0.11.0, D-020) was the first header part whose correctness required hydration to adopt server markup — the <select> era had the same drift, invisibly.

What

  • MarketingShell reads header and footer once (createMemo) — an eagerly built prop costs the same keys on both sides.
  • FlagSprite loads bodies when the element is empty (childElementCount), not when "not hydrating" — a drifted hydration no longer leaves a blank box.
  • tests/site-kit-hydration.test.mjs (stage3-4 gate): SSR with the server build → hydrate with the browser build in jsdom → zero lost server keys, 14 symbols, <use> resolves; plus an emptied-sprite case. Both red before the fix.
  • Contract pin on sharedConfig.context moved to the new mechanism. Changeset (patch), README en/ko, D-027.

Verify

pnpm run verify green, verify:source:stage3-4 45/45, site-kit vitest green. First consumer: AskLinq bumps to 0.12.1 with its own get actions() fix (asklinq D-137); production check in a real browser after deploy.


AskLinq가 site-kit 0.12.0을 소비하자 국기 메뉴가 빈 칸이 됐다. 서버 HTML엔 심볼이 다 있었고 라이브 DOM엔 스프라이트가 비어 있었으며 헤더 대부분이 하이드레이션 키를 잃었다. header={{ … }}는 게터로 컴파일되는데 셸이 props.header를 그대로 펼쳐 prop마다 리터럴과 즉시 JSX가 다시 만들어지며 키를 소모했고, 서버와 브라우저의 횟수가 달랐다.

무엇

  • MarketingShellheader·footer를 한 번만 읽는다(createMemo).
  • FlagSprite는 하이드레이션 여부가 아니라 엘리먼트가 비었는지로 본문 로드를 결정한다.
  • 서버 렌더 → jsdom 하이드레이션 테스트(잃은 키 0·심볼 14·빈 스프라이트 재충전), 수정 전엔 빨강. 계약 핀 이동, changeset(patch), README 양어, D-027.

…ads its bodies (D-027)

AskLinq shipped site-kit 0.12.0 and its flag menu became an empty box in
every browser. The server HTML carried every symbol; in the live DOM the
sprite was empty and the trigger's <use> pointed at a uid the server never
wrote. Almost every header element had lost its hydration key — only the
two eagerly built `actions` anchors kept theirs.

`header={{ … }}` compiles to a getter. MarketingShell spread `props.header`
straight into SiteHeader, so every prop read re-evaluated the literal and
rebuilt any JSX built eagerly inside it, consuming hydration keys — a
different number of times on the server than in the browser. From the
first drift the client rebuilt the header from templates: a fresh, empty
sprite, a new uid. The sprite (0.11.0) was simply the first part of the
header that depended on hydration adopting the server markup.

Two guards. MarketingShell reads header and footer once through createMemo,
so an eager prop costs the same keys on both sides whatever the consumer
did. FlagSprite decides "no server HTML" by looking at the element
(childElementCount) rather than the hydration context, so a sprite that
reaches the client empty loads its bodies whatever the reason.

tests/site-kit-hydration.test.mjs server-renders the shell with an
eagerly built action, hydrates it in jsdom with the browser build, and
asserts zero lost server keys and 14 surviving symbols; a second case
empties the sprite before hydration and expects it refilled. Both were
red against the previous code. The contracts pin on sharedConfig.context
moves to the new mechanism. Changeset (patch), README en/ko, D-027.
…urce-only gate

The source-contracts CI job checks out without installing or building;
the hydration test needs the workspace's solid-js and jsdom and a built
site-kit. It moves to verify:site-kit:ui, right after the package build.
@jlc488
jlc488 merged commit 076b65f into main Sep 15, 2026
2 checks passed
@jlc488
jlc488 deleted the fix/flag-sprite-hydration branch September 15, 2026 18:15
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