Skip to content

Add weft_shop, a streamed storefront example, and fix the Weft bugs it found - #14

Merged
e-cline merged 3 commits into
mainfrom
claude/weft-example-app-ideas-d248b2
Oct 24, 2026
Merged

e-cline merged 3 commits into
mainfrom
claude/weft-example-app-ideas-d248b2

Conversation

@e-cline

@e-cline e-cline commented Oct 24, 2026

Copy link
Copy Markdown
Owner

What and why

A larger Weft example than weft_notes, built to exercise how Weft's features work together, with the latency of real services in between, and to find bugs the smaller examples don't. It found six; each is fixed here with a regression test (checked to fail without the fix) and a changeset.

examples/weft_shop ("Loom & Co.")

  • Server-driven pages: the home and category pages are a tree of CMS blocks kept as data, personalised per shopper (signed in or not, experiment half) and changed at runtime. Each block streams under its own Suspense boundary inside a BlockFrame entrypoint (error boundary with Retry, refetched from its own route, polled when live), and keeps its state when a refresh moves it.
  • Synthetic latency (Latency.res): seeded random delays and failures in vp dev/vp run start; gates the tests open, in the order they choose.
  • Catalog filters as navigations, "Load more" through an RPC returning server-rendered cards, search suggestions cancelling older requests (Rpc.call with a signal), variant offers refetched by themselves, a cart in a signals store handed down by context, portals (drawer, sign-in dialog), cookie sessions through servy middleware, a three-step checkout, a self-refreshing order page.
  • Tests: 45 scenario tests through the plugin's harness; 16 Playwright tests against the build, one project with gated latency and one with seeded random latency and failures. The shop's e2e is added to CI's browser job.

Framework fixes

  1. weft: a pending boundary whose content arrived after hydration started, and suspended while hydrating (an entrypoint's chunk still loading), rendered its content a second time after the server's, which stayed unhydrated.
  2. weft_signals: the hooks gave useSyncExternalStore no server snapshot, so an entrypoint reading a signal couldn't render on the server (error 15).
  3. weft_dom: hydration compared style as written, not as the browser reads it back (hsl → rgb, -0% → 0%), warning about identical styles; autoFocus didn't focus elements rendered after page load. Also new aria and form props.
  4. weft_server: Navigation.navigate/load/refresh resolved when their transition started, not once the new page was on screen as documented.
  5. weft_server: a navigation the server redirected recorded and pushed the URL it asked for, not the redirect's final URL.
  6. Test harness (weft_vite_plugin): no cookies, no redirects, and pages from earlier tests stayed mounted and acted in later ones. It now keeps cookies, follows redirects, and closes a page when the next opens (Bootstrap.start ~signal).

Known follow-ups, not in this PR

  • useRefetchable's refetch resolves when its render starts (documented); BlockFrame's Retry waits on isPending before resetting its error boundary.
  • After a navigation, a hydrated boundary's new content is inserted after its end marker (nothing visible breaks).
  • document.title isn't updated on navigation (already in the server components plan's follow-ups).

Checklist

  • pnpm run check, pnpm run build and pnpm run test pass
  • Tests cover the change
  • A changeset is included (pnpm changeset) if a published package changed

🤖 Generated with Claude Code

e-cline and others added 3 commits October 23, 2026 21:02
- weft: a pending Suspense boundary whose content arrived after
  hydration started, and suspended as it hydrated (an entrypoint's chunk
  still loading), rendered its content again after the server's, which
  stayed unhydrated. It now stays dehydrated until what it waits on
  settles.
- weft_signals: the hooks gave useSyncExternalStore no server snapshot,
  so an entrypoint reading a signal failed to render on the server
  (error 15) and the page fell back to client rendering.
- weft_dom: hydration compared style values as written, while browsers
  read them back normalised (hsl as rgb, -0% as 0%), warning about
  styles that were the same. autoFocus now focuses an element rendered
  after the page loaded. New aria and form props.
- weft_server: Navigation.navigate, load and refresh resolved when their
  transition started, not once the new page was on screen as
  documented. Bootstrap.start takes a ~signal that stops the page.
- weft_vite_plugin: the harness keeps cookies as a browser does, and
  opening a page closes the one before (its root unmounted), so a page
  left open by one test can't act in the next.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A larger example than weft_notes, built to exercise how Weft's features
work together with the latency of real services in between:

- Home and category pages are server-driven: a tree of CMS blocks kept
  as data, personalised per shopper and changed at runtime. Each block
  streams under its own boundary, inside a BlockFrame entrypoint (an
  error boundary with Retry, refetched from its own route, polled when
  live), and keeps its state when a refresh moves it.
- Every slow call waits on Latency: seeded random delays and failures
  in dev and `vp run start`, gates the tests open in `manual` mode.
- Catalog filters as navigations, "Load more" through an RPC returning
  server-rendered cards, search suggestions cancelling older requests,
  variant offers refetched by themselves, a cart in a signals store
  handed down by context, portals, cookie sessions through servy
  middleware, a three-step checkout and a self-refreshing order page.

Tests: 45 scenario tests through the plugin's harness, and 16 Playwright
tests against the build (one project with gated latency, one with
seeded random latency and failures), now in CI's browser job.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A route answering a navigation's payload request with a redirect showed
the redirected page under the URL that was asked for. Now:

- weft_server: Decoder.fetch resolves with `fetched` ({stream, url?}),
  webFetch reporting where the browser's fetch was redirected to, and
  Decoder.Latest.fetchElement with the element and its URL. Navigation
  records that URL, navigate passes it to onNavigate, and a redirected
  load or refresh calls the new ~onRedirect (Bootstrap replaces the
  history entry).
- weft_vite_plugin: the harness's fetch follows same-origin redirects
  as a browser does (bounded, 303 as GET, manual/error respected),
  setting url/redirected on its responses; a redirected first page is
  at the URL it led to (WeftTesting.url).
- weft_shop: /account (signed out) redirects to /signin?next=/account
  again, and an empty cart's /checkout to /cart, with harness and e2e
  tests of the URL after following a link.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@e-cline
e-cline merged commit 79a1b2d into main Oct 24, 2026
3 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