| State | One immutable Model, an Effect Schema struct holding everything (route, filters, query cache, list, dialogs). | One Model, as in Foldkit, holding the box, filters, layout and cart. Prices live in it too, in Remote’s normalized store keyed by domain name. | State lives where it is used: signals, stores, and memos owned by components and providers. |
|---|
| Change | Typed Messages into a pure update function, composed from reusable Steps. | Typed Messages into a pure update, as in Foldkit. The address is a mirror of the Model (Mirror.url), not a Command per keystroke. | Event handlers write signals; mutations are actions with optimistic writes. |
|---|
| Side effects | Commands: Effect programs the runtime executes and interrupts. | Commands, as in Foldkit, for what the app does itself. Price reads are not Commands: Remote derives them from what the page declares it shows. | Server functions plus split effects (compute → apply) for DOM bridges. |
|---|
| Async data | A KeyedQuery block in the Model; AsyncData values folded into a SearchViewState union. | Declared Surfaces (Top Results, the rows in view, the whole catalog for the Available filter and price sorts) name the domains they need; Remote reads the ones it lacks in one batch and keeps them fresh for a minute. | A router query() keyed by label and range, read with plain createMemo; <Loading> and <Errored> handle the rest. |
|---|
| Progressive loading | A planner reads the VirtualList viewport and schedules the next candidate range. | No planner: WindowList names the rows in view (and two past each end), and that Surface reads exactly those names, not the next 60-name page. | No planner: each mounted row asks for its own page, so scrolling is the schedule. |
|---|
| Cancellation | Query generations reject stale completions; interrupted Commands abort RPCs. | A 150 ms debounce (from foldkit-primitives) settles the label the reads use; Remote aborts a read nothing needs any more. | None: query() never aborts. Lookups wait until the search is stable for 150 ms, so typing a word sends requests for the final query only. |
|---|
| Server rendering | Prerendered and request-time SSR of the shell; prices load in the browser over RPC. | Every page rendered whole by the Worker (foldkit-ssr). The server reads the prices the page shows first, and sends them with the page, so the browser asks for none of them again. Each render costs about 60 ms of CPU for the home page and 190 ms for a search, most of it Foldkit’s own checks of the server markup. | Streaming SSR with an app-level router preload (a loader): the first ranges are fetched before render, rows read them synchronously, and the browser adopts the data. |
|---|
| Before hydration | A bootstrap script keeps the input usable and synced to the URL before the bundle runs. | rh.KeepTyped(): what is typed before boot is kept, with the caret, and replayed when the runtime starts. Nothing answers it before then. | The server HTML paints first and hydration starts on the next frame; HydrationScript captures clicks and typing in between and replays them. |
|---|
| Without JavaScript | The server sends the page shell; prices, results, filters, and the cart need the bundle. With JavaScript off, a search shows no prices. | Search, filters, paging, and the cart work: prices are in the HTML, search and filters are a GET form, the list pages by ?from= links, and a cart button is a form posting its Message, which the server runs through update and answers with a redirect. | Search, filters, paging, and the cart work: rows are priced in the HTML, filters are a GET form with Previous and Next links, and cart buttons post to a server function that redirects back. CSS `@media (scripting: none)` shows these controls only when scripts are off. |
|---|
| Virtual list | @foldkit/ui VirtualList with measured, dynamic row heights. | WindowList from foldkit-mixins-ui in the page’s own scroll; rows not yet drawn are sized as the ones measured. | Hand-rolled window virtualization with fixed row heights per breakpoint (long names are truncated). |
|---|
| Backend | Effect RPC with shared Schemas, typed failures, and request bounds. | Remote’s server (serveFetch) at /remote in the Worker: reads by domain name, each price made from the same sample data. | "use server" functions (RPC at /_server; searches are cacheable GET() reads) plus a small REST API in middleware. |
|---|
| Cart | In the Model; instant, and in-memory only. | In the Model, kept in a cookie through Mirror.cookie, so it survives reloads; the server prices its names again on every read. One cart with scripts or without. | Optimistic action saved through a server function into a cookie, so it survives reloads. Each button is also a form posting to a server function, so the cart works without JavaScript. |
|---|
| Styles | Tailwind 4 and a stylesheet file. | No CSS to write: style.ts, one foldkit-mixins theme and Style code, compiled at build into a hashed stylesheet the page links. | Tailwind 4 and a stylesheet file; the Inspector’s and this page’s styles load with them. |
|---|
| Tooling | Shipped DevTools: Message, Model, Command, and Mount timeline with inspect mode. | Foldkit’s DevTools in development; the deployed build leaves them out. | A request inspector: the request log, ranges loaded, and injected latency and failures. |
|---|
| Deploy | Alchemy · Cloudflare.Website.Foldkit | Wrangler · a Worker with static assets | Alchemy · Cloudflare.Website.Vite |
|---|