Where state lives
Keep interface choices, API answers, and shareable list controls in the places each full edition expects. A standalone edition contains the same edition-specific files and its own shared mirror; in a full repository, edit shared/ for common rules and sync rather than changing the mirrors. The generated folders guide lists both contexts. Starters omit the demo API and its persisted changes.
HTML: preferences and demo changes
html/assets/js/preferences.js runs synchronously from html/partials/head.html before paint. It applies theme, density, layout, and direction to the document and stores the selection in localStorage when available.
html/assets/js/app.js creates the mock dataset and API from bundled shared code. It wraps mutations with shared/mock/journal.mjs so a change survives a page-to-page link or reload. Reset demo clears those changes; when storage is blocked, they last only as long as the current page.
List state stays in memory instead of the URL because file:// does not allow the required history writes. In a standalone HTML folder, edit the same edition files inside your folder; its mirrored shared code is yours to edit if you change common behavior.
React: UI, server, list, and selection state
Two kinds, kept apart on purpose, because mixing them is what turns a store into a second copy of the database.
UI state — Zustand. Theme, density, layout, direction and locale. It
belongs to this browser, it is never fetched, and it is persisted to
localStorage under the same key the other four editions use
(src/app/preferences.ts).
Server state — TanStack Query. Everything the API answers, through the
hooks in src/app/queries.ts. Nothing copies a response into a store; the
cache is the copy.
The demo switches (src/app/demo.ts) sit across the line: they are UI, but
they change what a response contains, so they are part of every query key and
changing one invalidates the cache.
List state — the URL. Which page, sorted by what, filtered how, searched
for what: src/app/table.ts reads it out of useSearchParams and writes it
back, so a filtered table is a link someone can send. It is derived, never
stored — two copies of that state is how a back button ends up showing page 3
of rows from page 1. The arithmetic behind it is shared/ui/table.mjs, the
same module the other four editions run.
Row selection — component state. In useSelection, because it exists only
in front of one reader. It survives paging and is dropped when the search or
the filters change.
Vue: UI, server, list, and selection state
Two kinds, kept apart on purpose, because mixing them is what turns a store into a second copy of the database.
UI state — Pinia. Theme, density, layout, direction and locale
(src/app/preferences.ts), the demo switches (src/app/demo.ts) and the
toasts (src/app/toasts.ts). They belong to this browser and are never
fetched.
Server state — TanStack Query. Everything the API answers, through the
composables in src/app/queries.ts. Nothing copies a response into a store;
the cache is the copy. The parameters arrive as refs or getters and the keys
are computed, which is what makes the dashboard's range switch a refetch
rather than a remount.
The demo switches sit across the line: they are UI, but they change what a response contains, so they are part of every query key and changing one invalidates the cache.
Pinia has no persistence of its own. Rather than add a plugin,
startApplyingPreferences writes { state, version } under the same
localStorage key in the same shape zustand's persist middleware writes in the
React edition — which is what lets the pre-paint script in index.html be
byte-identical between the two editions instead of two snippets to audit.
List state
Which page, sorted by what, filtered how, searched for what lives in the URL,
not in Pinia: src/app/table.ts derives it from the route and writes it back
through the router, so a filtered table is a link someone can send and the back
button undoes the last narrowing. The arithmetic is shared/ui/table.mjs — the
same module the other four editions run — and the components that draw the
chrome are in src/table/.
Which rows are ticked is component state, because it exists only in front of one reader. It survives paging and is dropped when the search or the filters change.
Next.js: preferences before paint
Theme, density, layout and direction are stored in a cookie, not in
localStorage (src/server/preferences.ts). The server has to know them: the
attributes go on <html>, and <html> is rendered on the server.
So there is no pre-paint inline script here, and no flash to prevent — the first byte of the response already says:
<html lang="ar" dir="rtl" data-or-theme="dark-charcoal" data-or-density="compact" data-or-layout="rail">src/components/preferences-provider.tsx is the client half: it writes the
cookie, applies the attributes immediately so a change is instant, and
refreshes the route so the server agrees. The cookie records the locale the
direction was chosen in, so an explicit "show me this in RTL" survives a reload
while switching to Arabic still mirrors by itself.
Server answers go through nextjs/src/server/api.ts; sorting, filtering, and paging on nextjs/src/app/[locale]/orders/page.tsx are URL links, while row selection stays in the client island. A link to filtered rows therefore survives a reload or can be shared.
Next.js: demo changes
The mock API runs on this edition's server, so a visitor's changes live in the server's memory until the process restarts — every visitor of one deployment shares them. The demo bar's Reset demo button puts the fixtures back.
Nuxt: preferences before paint
Theme, density, layout and direction are stored in a cookie
(app/stores/preferences.ts). The server has to know them: the attributes go
on <html>, and <html> is rendered on the server.
So there is no pre-paint inline script here, and no flash to prevent — the first byte of the response already says:
<html lang="ar" dir="rtl" data-or-theme="dark-charcoal" data-or-density="compact" data-or-layout="rail">The cookie records the locale the direction was chosen in, so an explicit "show me this in RTL" survives a reload while switching to Arabic still mirrors by itself.
nuxt/app/composables/useApi.ts uses useAsyncData for server-rendered answers; its key includes the path, parameters, and demo switches. nuxt/app/composables/useTable.ts keeps page, sort, filters, and search in the URL so a filtered view survives a reload. In a standalone Nuxt folder these are the same files inside that folder.
Nuxt: demo changes
The mock API runs on this edition's server, so a visitor's changes live in the server's memory until the process restarts — every visitor of one deployment shares them. The demo bar's Reset demo button puts the fixtures back.
Apply this to your folder
For React and Vue, the included paths are relative to those edition folders; in a full repository they begin with react/ or vue/. For Next.js and Nuxt, preferences are server-visible cookies, not the browser-only storage used by HTML, React, and Vue. Do not use server-memory demo writes as a per-user data store: the included server-state sections explain that all visitors of one deployment share them.