Skip to content
Where state lives

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.