Skip to content

v0.6.0 — resolve all known limitations + SSR/wasm DX#2

Merged
AmbroseNTK merged 9 commits into
mainfrom
feat/known-limitations
May 25, 2026
Merged

v0.6.0 — resolve all known limitations + SSR/wasm DX#2
AmbroseNTK merged 9 commits into
mainfrom
feat/known-limitations

Conversation

@AmbroseNTK
Copy link
Copy Markdown
Contributor

Resolves every entry in the Known limitations section of CLAUDE.md, plus SSR delivery + starter-template DX. Each feature landed with tests across all three layers (host unit, browser via wasmbrowsertest, Playwright e2e) and per-feature commits.

SSR delivery + DX

  • gzip/precompress wasm: serveStaticAsset prefers pre-compressed .br/.gz siblings, else gzips on the fly; gutter build/deploy write max-level .gz; nginx gzip_static. app.wasm ~11.7MB → ~3.0MB.
  • SSR doc template carries the CSR margin reset (fixes stray body padding).
  • Branded gutter new starter: embedded favicon, logo, themes.Meta default, Runway Club footer + slogan, Head for SSR; gutter new --ssr scaffolds SSR + typed RPC + hydration.

Known limitations — now resolved

  • a11y: Body/Caption<p> (Inline opt-out); Scaffold <main>/<footer> landmarks; overlays role=dialog/aria-modal.
  • SSR <head> hints: gutter.Head + RenderDocument collect title/meta/og.
  • AsyncBuilder Deps: re-runs Load on dependency change.
  • Router guards/redirects: NavGuard on every navigation.
  • Tag-stability: canUpdate compares Host.Tag, remounting on change.
  • Portal/teleport: gutter.Portal → body-level root; overlays routed through it.
  • ListBuilder: variable extents (ItemExtent) + horizontal virtualization.
  • Theme via DI: gutter.ThemeProvider per-subtree theme.
  • Async SSR: SSRResolver/RenderDocumentCtx resolve Load server-side.
  • Transition scheduling: gutter.Transition(fn) low-priority SetState.
  • Hydration recovery: fine-grained tag-mismatch (salvages descendant DOM) + warning.
  • Devtools: gutter.Inspect()/EnableDevtools() element-tree inspector.

Fine-grained Provider invalidation is intentionally served by Provider[*Notifier[T]] + ObserverBuilder (documented) rather than a redundant dependent-tracker.

Verification

Host go test, browser GOOS=js go test, Playwright e2e (7/7), go vet both targets — all green. Examples + e2e testapp compile.

🤖 Generated with Claude Code

AmbroseNTK and others added 9 commits May 26, 2026 02:51
…nc deps

Spans three threads of work from this session:

WASM delivery + SSR DX
- serveStaticAsset: prefer pre-compressed .br/.gz siblings, else gzip on the
  fly; gzip the SSR HTML; Vary + Cache-Control:no-cache. ~11.7MB→3.0MB on the
  fullstack app.wasm.
- gutter build/deploy precompress dist (BestCompression); nginx gzip_static.
- SSR doc template carries the CSR margin reset (fixes stray body padding).

Branded starter template
- gutter new embeds gutter.ico → public/favicon.ico, shows the logo, defaults
  to themes.Meta, adds Runway Club footer/caption + a slogan, and a Head with
  favicon/font links so SSR matches CSR.

Known-limitations Batch A (each with tests; host + browser green)
- a11y: Body/Caption render <p> (Inline opt-out); Scaffold <main>/<footer>
  landmarks; overlays get role=dialog/aria-modal/aria-hidden.
- SSR <head> hints: gutter.Head widget + RenderDocument collect title/meta/og;
  ServeSSR injects them.
- AsyncBuilder Deps []any re-runs Load on change (DidUpdateWidget/DeepEqual).
- Router guards/redirects: NavGuard on NewRouter/Guard, applied to every nav.
- Tag-stability: canUpdate compares Host.Tag, remounting on tag change.
- gutter new --ssr scaffolds SSR + typed RPC + hydration.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
gutter.Portal{Child} leaves a zero-size <template> anchor in place and mounts
its child into #gutter-portal-root, so position:fixed overlays escape an
ancestor's transform/overflow/stacking context. portalElement (wasm) handles
mount/hydrate/update/unmount; SSR renders only the placeholder. Popup, Drawer,
and BottomSheet now render through Portal.

Browser test: TestPortalTeleportsChild (mount/update/unmount).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- ItemExtent func(i) float64 for variable row sizes, backed by a prefix-sum
  offset cache + binary-search indexAt (listMetrics); ItemHeight stays the
  uniform fast path.
- Direction ListHorizontal virtualizes along X (scrollLeft/clientWidth, width
  slots, row inner), bounded by Width; vertical unchanged.
- attachScrollListener takes a horizontal flag and reports axis offset/extent.

Tests: list_metrics_test.go (host: metrics + virtualWindow) and
list_wasm_test.go (browser: vertical window, variable extents, horizontal).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ThemeProvider{Theme, Child} rides the same inherited-scope machinery as
Provider, so a subtree can override the theme (correct under isolated SetState
rebuilds and SSR). activeTheme now prefers a ThemeProvider over
BuildContext.Theme, then falls back to Apple.

Documents that Provider's immutable Value needs no separate dependent-tracking
invalidation — reactive cross-tree state is Provider[*Notifier[T]] +
ObserverBuilder by design.

Tests: theme_provider_test.go (override + nested shadowing via RenderToHTML).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A State implementing gutter.SSRResolver resolves its async Load synchronously
during the SSR walk (called before InitState, which then skips re-loading), so
server-rendered HTML shows the resolved UI instead of the Pending placeholder.
AsyncBuilder implements it. RenderDocumentCtx threads a context (SSRHandler
passes the request's) so loads honor deadlines/cancellation.

Documents the hydration-refetch caveat and that HTTP streaming (chunked flush)
is still not implemented.

Tests: AsyncBuilder resolved/error during RenderToHTML + context propagation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
gutter.Transition(fn) marks the SetStates made inside fn as low-priority: they
drain on a macrotask (setTimeout 0) via a separate transition queue, after the
urgent microtask flush, so input stays responsive while a large transition
rebuild runs. Urgent path unchanged; rebuild() idempotency covers the rare
both-queues element. Host stub runs fn directly.

Browser tests: transition defers behind urgent; urgent flushes while a
transition is pending (flushTransitions added as the deterministic test seam).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
On a hydration tag mismatch, recreate only the offending element with the
correct tag and MOVE the server-rendered children into it (then hydrate them),
so descendant DOM identity survives instead of rebuilding the whole subtree —
relevant now that async SSR can render a different (resolved) tag than the
client's initial (pending) one. warnHydrationMismatch logs the divergence.

Browser test: TestHydrateTagMismatchSalvagesChildren (descendant node identity
preserved across a parent tag swap).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
gutter.Inspect() walks every mounted root into a plain InspectNode tree
(Kind/Type/Tag/Key/Children, with String()+Count()) — safe to log or assert on.
gutter.EnableDevtools() installs a Ctrl+Shift+G overlay rendering it. MountInto
registers roots; host stubs return empty.

Tests: inspect_test.go (host: String/Count) and inspect_wasm_test.go (browser:
Inspect walks a mounted tree; EnableDevtools toggles the panel).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@AmbroseNTK AmbroseNTK merged commit 8c24c54 into main May 25, 2026
3 checks passed
@AmbroseNTK AmbroseNTK deleted the feat/known-limitations branch May 25, 2026 20:25
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