# SSR support, status ledger

**Filing a new downstream SSR-seam report?** Use the `ssr` label plus
[`.github/ISSUE_TEMPLATE/ssr-seam-report.md`](../../../../../../.github/ISSUE_TEMPLATE/ssr-seam-report.md)
(gh#1384), it captures the resolved `@adia-ai/web-components` version, the
downstream kit version, the component + fixture, an SSR-vs-CSR diff or
minimal repro, and a cosmetic/behavioral/blocked-upstream classification, so
a report lands triageable instead of free-form (the archaeology the #284/#288
rows below needed). `docs/ops/spec/spec-ssr-kit.md` is referenced by some
downstream reports but does not exist in this repo, it lives in adiav2's own
repo (`@adiav2/ssr-kit`'s spec); do not look for it here.

**This file drifts fast, re-verify against the live issue tracker
(`gh issue view <n>`) before trusting it for anything beyond a quick orientation.**
Every row was true as of 2026-07-17; a closed issue below may have re-opened, a PR
number may have been superseded by a follow-up fix, etc. This ledger is a map, not
the territory: the territory is `gh issue list` / `gh issue view`.

| Issue | Failure shape | Status | Shipped in | Notes |
|---|---|---|---|---|
| [#285](https://github.com/adiahealth/gen-ui-kit/issues/285) | §1, browser-only API called unconditionally | **CLOSED** | PR #292 (merged 2026-07-17) | `UIElement` constructor + `adoptStyles()` + 22 component/trait/module files guarded; 10 more already correct from an earlier pass |
| [#1430](https://github.com/adiahealth/gen-ui-kit/issues/1430) | §1, browser-only API called unconditionally (module scope: `window.matchMedia` behind a `typeof window` guard) | **FIXED 2026-08-17** | `core/responsive.js` feature-detects `matchMedia` + `innerWidth`; `swiper.class.js` `play()` guarded; `scripts/dev/ssr-linkedom-smoke.mjs` + `test/ssr-linkedom-smoke.test.js` gate the whole catalog under linkedom | 6 components (`block`/`col`/`grid`/`row`/`text`/`demo-toggle`) were un-importable server-side; 118→124 of 124. Side findings on the issue: gh#285's "shim no longer necessary" over-reached (two of the consumer's shim sections patch `custom-elements-ssr` itself, not this library, see `consumer-workarounds.md`); `audit-template-child-conflict.mjs` isn't shipped in the package. |
| [#1436](https://github.com/adiahealth/gen-ui-kit/issues/1436) | §1, bare `instanceof Node/Element/HTMLElement` (ReferenceError under linkedom, only `HTMLElement` is global) | **FIXED 2026-08-17** | `core/dom.js` `isNode()`/`isElement()`; 15 sites swept; plus rAF/MutationObserver/getBoundingClientRect guards in feed, noodles, preview, toolbar, nav-group, stepper, nav found by the same gate's render sweep | Same PR as #1430, one root-cause class. |
| [#1435](https://github.com/adiahealth/gen-ui-kit/issues/1435) | reported as §2, check/radio/switch "lose" a `<span slot="label">` child | **NOT an SSR seam** (2026-08-17) | yaml/a2ui `slots` descriptions clarified for the three (separate follow-up PR, a docs-contract change, not a shape-1 guard) | The fixture is not a supported form in CSR either: `label` on all three is ATTRIBUTE-driven (`label="…"` → `aria-label` + `::after content: attr(label)` / template-owned span); a late CSR upgrade replaces the foreign child the same way. The yaml `slots:` entries name template-OWNED anatomy parts (`box`/`dot`/`track`/`thumb`/`label`), which a downstream reimplementation of the audit read as consumer insertion points, the audit's own definition (`slots.default` + non-null template) is what "consumer children" means here. |
| [#286](https://github.com/adiahealth/gen-ui-kit/issues/286) | §3, connect-time measurement treated as confirmed | **CLOSED** (for `admin-sidebar` specifically) | PR #290 (merged 2026-07-17) | The GENERAL pattern (any other component reading a rect/size synchronously at connect) is NOT swept, only this one instance is fixed |
| [#284](https://github.com/adiahealth/gen-ui-kit/issues/284) | §2, destructive `stamp()` on connect | **CLOSED 2026-07-18, narrowed AND separately fixed** | `scripts/dev/audit-template-child-conflict.mjs` (PR #295) + `packages/web-components/core/element.js` connectedCallback resync (PR #309) | Two distinct findings. (1) The ORIGINAL diagnosis (destructive `stamp()`) was narrowed 2026-07-17: every component the issue names has `static template = () => null`, so `stamp()` never runs for them, zero shipped components exposed; a static audit gate catches a future regression instead of a lifecycle rewrite. (2) A SEPARATE, real, live bug was found in the same investigation area and fixed 2026-07-18: happy-dom/linkedom don't replay `attributeChangedCallback` for attributes already present at custom-element upgrade (spec §4.13.5 step 6), any `reflect: true` property seeded only from pre-parsed/SSR HTML stayed at its class default after upgrade (e.g. `<nav-item-ui text="Profile">` rendered with an empty label). `connectedCallback` now re-syncs every declared property from its live attribute before `connected()` runs. See `failure-shapes.md` §2 for the full narrative. |
| [#288](https://github.com/adiahealth/gen-ui-kit/issues/288) | §4, property-only components can't seed from SSR HTML | **CLOSED 2026-07-18** | `table.class.js` `data="[…]"` attribute hydration | Never actually blocked on #284 (table-ui/chart-ui/select-ui all use `static template = () => null`, so the stamp() question never applied). Scope was narrower than filed: `select-ui` already parsed declarative `<option>` children, `chart-ui` already hydrated `.data` from a `data="[…]"` attribute, only `table-ui`'s `.data` had no declarative form. Fixed in the same attribute-hydration shape as chart-ui, not the JSON-script-child form originally proposed. |
| [#287](https://github.com/adiahealth/gen-ui-kit/issues/287) | Unrelated to the three SSR shapes above, a Phosphor-icon `import.meta.glob` path failure under workspace hoisting, plus the dep-optimizer silently emptying the icon registry | **CLOSED** | PR #294 (merged 2026-07-17) | Root cause confirmed empirically (a from-scratch pnpm workspace repro): the glob's leading `/` joined to Vite's configured root, which only reaches a transitive dependency's assets when hoisted there. Fixed by switching to an entry-file-relative glob (`../node_modules/...`). A second, independent cause (Vite's dev-server `optimizeDeps` pre-bundling) has no package-side fix, documented as a consumer `optimizeDeps.exclude` workaround. A bundler/workspace-resolution bug, not an SSR-lifecycle bug, still doesn't route through this pack's failure taxonomy. |
| [#1678](https://github.com/adiahealth/gen-ui-kit/issues/1678) | §5 (new), a custom `render()`/`connected()` unconditionally rebuilds a subtree that already matches; §5.1 (new), `isEqualNode()` proves structural safety only, never runtime-state safety (a renderer-owned listener can be silently dropped on adoption, CodeRabbit finding, closed in the same PR); §6 (new), the conditional-inject class, decision recorded | **MERGED 2026-08-20** (PR #1756, branch `1678-table-ssr-adopt-or-diff`, core table-ui render path) | `table.class.js` `adoptOrDiffChildren()` + `RENDERER_OWNED` + `setAttrIfChanged()`/`removeAttrIfPresent()`; `cell-types.js`'s `attachesListeners: true` on `actions` | §5/§5.1 fixed for table-ui only, no framework-wide static audit exists for this shape yet. §6's decision: querySelector-guard-before-innerHTML (`search-ui`, `pagination-ui`) is SSR-safe when the guard target is structurally specific; [#1755](https://github.com/adiahealth/gen-ui-kit/issues/1755) (filed alongside this PR, already merged) extends the idempotent-write guard to those two components' own post-adopt attribute writes, scoped OUT of this PR. |

## What "done" looks like for the open items

- **#284's original scope is closed as a non-issue against 0.8.4**, see the ledger
  row above. Re-open only if a FUTURE component's yaml/class.js trips
  `audit-template-child-conflict.mjs`, or if `adiav2`'s production workaround
  (`consumer-workarounds.md`) turns out to be catching something this repo's
  synthetic repro didn't (confirm via the issue thread before assuming so).
- **#288 is done**, see the ledger row above. `table-ui`'s `data="[…]"` attribute
  is the shipped form; `select-ui` and `chart-ui` already had their own declarative
  forms before this issue was even filed.

## Re-syncing this ledger

When any row's status changes, update the table AND re-check whether
[`consumer-workarounds.md`](consumer-workarounds.md)'s claims about what's "now
deletable" are still accurate, a workaround section that says "should no longer be
necessary" for a shape that's since re-opened is a false manifest, not a stale detail.
