Architecture decisions
The choices behind Purity, including proposed work.
- Decision indexArchitecture Decision Records (ADRs) for Purity. Each ADR captures a significant choice, the context that drove it, and the consequences we accept. Numbered sequentially.
- SSR strategy for 1.0Status: Superseded by 0004 Date: 2026-05-09 Context Purity is currently client-rendered only. The role-play first-impressions review flagged "no SSR / hydration" as the l
- Devtools approachStatus: Superseded for the development panel; inspector hook retained Date: 2026-05-09 2026-10-01 update: @purityjs/vite-plugin now offers an opt-in in-page reactive grap
- Path to 1.0Status: Proposed Date: 2026-05-09 Context When this ADR was proposed, Purity was at 0.1.0 . The README states that the API may break between minor versions. This ADR prop
- SSR MVP via Declarative Shadow DOMStatus: Accepted Date: 2026-05-09 Context ADR 0001 committed Purity to "client-rendered only, by design" for 1.0, with a static-prerender path slated for 1.x and full SSR
- Marker-walking, non-lossy hydrationStatus: Accepted Date: 2026-05-10 Context ADR 0004 shipped the SSR MVP with lossy hydration : hydrate() cleared the SSR-rendered children of the container and ran the com
- Streaming SSR with Suspense boundariesStatus: Accepted Date: 2026-05-10 Note: Promoted from Proposed to Accepted in the ADR-0027 housekeeping pass. All six named phases (boundary markers + per-boundary timeou
- Opt-in static text-content rewriting on hydration mismatchStatus: Accepted Date: 2026-05-10 Context ADR 0005 established that the hydrator walks SSR markers and binds in place — but explicitly does not rewrite static text conten
- Per-component head / meta tag managementStatus: Accepted Date: 2026-05-10 Context Purity SSR apps render a component to an HTML string that the user splices into a hand-written shell: The <head lives outside th
- Request context for SSR componentsStatus: Accepted Date: 2026-05-10 Context Until now, Purity SSR components rendered without any access to the incoming HTTP request. Components knew nothing about the URL
- Static site generation driverStatus: Accepted Date: 2026-05-10 Context renderToString (ADR 0004) renders a Purity component to HTML for one request. renderToStream (ADR 0006) does the same with a str
- Router primitivesStatus: Accepted Date: 2026-05-10 Context ADR 0009 exposes the incoming Request via getRequest() and the SSR example refactor in the same iteration showed a typical use:
- Server actions / form-enhancement primitiveStatus: Accepted Date: 2026-05-10 Context Purity ships read-side SSR ( renderToString , renderToStream , renderStatic ) but has no write-side primitive — no built-in way
- Link auto-interceptionStatus: Accepted Date: 2026-05-10 Context ADR 0011 shipped navigate(href) as the programmatic way to change the URL on the client. The pattern at each call site is: Five
- URL search and hash signalsStatus: Accepted Date: 2026-05-10 Context ADR 0011 shipped currentPath() — reactive pathname access with SSR/client parity. The deferred items in that ADR included URL se
- Navigation scroll managementStatus: Accepted Date: 2026-05-10 Context ADR 0013 shipped interceptLinks() and explicitly punted on scroll restoration / focus management: No focus management / scroll r
- Navigation focus managementStatus: Accepted Date: 2026-05-10 Context ADR 0013 and ADR 0015 both explicitly deferred focus management as a follow-up: ADR 0013: A real router needs to handle scroll p
- View Transitions API integrationStatus: Accepted Date: 2026-05-10 Context ADRs 0013, 0015, and 0016 all named view-transition integration as a deferred follow-up: ADR 0013: No view transitions API integ
- Server-only module strip from client bundlesStatus: Accepted Date: 2026-05-11 Context ADR 0012 shipped serverAction() and explicitly deferred the bundler-side stripping of handler bodies: No client-bundle handler-b
- File-system routing — manifest generationStatus: Accepted Date: 2026-05-11 Context ADR 0011 shipped currentPath() , navigate() , and matchRoute() . They cover ~80% of the value of a router for apps with a handfu
- File-system layouts — `_layout` per directory0020: File-system layouts — layout per directory Status: Accepted Date: 2026-05-11 Context ADR 0019 shipped the file-system route manifest. Each RouteEntry is { pattern,
- Error boundaries + 404 — `_error` per directory, root `_404`0021: Error boundaries + 404 — error per directory, root 404 Status: Accepted Date: 2026-05-11 Context ADR 0019 shipped the file-system route manifest. ADR 0020 added lay
- Data loaders — `loader` named export per route + layout0022: Data loaders — loader named export per route + layout Status: Accepted Date: 2026-05-11 Context ADRs 0019, 0020, and 0021 ship a self-describing route manifest with
- Isomorphic conditional primitives — `when` / `match` / `each` SSR auto-detect0023: Isomorphic conditional primitives — when / match / each SSR auto-detect Status: Accepted Date: 2026-05-11 Context Purity ships two parallel families of control-flow
- SSR-aware `lazyResource.fetch()` — register pending promises with multipass0024: SSR-aware lazyResource.fetch() — register pending promises with multipass Status: Accepted Date: 2026-05-11 Context resource() already participates in the SSR multi
- `asyncRoute` runtime composer — manifest-driven view assembly0025: asyncRoute runtime composer — manifest-driven view assembly Status: Accepted Date: 2026-05-11 Context ADRs 0019-0024 ship the file-system-routing manifest, layout c
- `loaderData()` context accessor — per-component loader-data slot0026: loaderData() context accessor — per-component loader-data slot Status: Accepted Date: 2026-05-11 Context ADR 0022 shipped loader detection on the manifest ( hasLoad
- `configureNavigation()` — single setup for the four `manageNav*` opt-ins0027: configureNavigation() — single setup for the four manageNav opt-ins Status: Accepted Date: 2026-05-11 Context ADRs 0013 + 0015 - 0016 + 0017 each ship a small opt-i
- Per-directory `_404.ts` — nested not-found chain0028: Per-directory 404.ts — nested not-found chain Status: Accepted Date: 2026-05-11 Context ADR 0021 shipped root-only 404.{ts,tsx,js,jsx} . Nested 404 pages were expli
- `prefetchManifestLinks()` — hover-prefetch route modules0029: prefetchManifestLinks() — hover-prefetch route modules Status: Accepted Date: 2026-05-11 Context ADR 0013 installs a global click listener that converts same-origin
- `manageTitle(fn)` — reactive `<title>` sync0030: manageTitle(fn) — reactive <title sync Status: Accepted Date: 2026-05-11 Context ADR 0008 shipped head(content) — an SSR-only helper that appends HTML to the <head
- `RouteParams<P>` — template-literal-derived route params0031: RouteParams<P — template-literal-derived route params Status: Accepted Date: 2026-05-11 Context ADR 0019 ships the manifest with patterns like /users/:id and /blog/
- `emitTo` — on-disk manifest emit0032: emitTo — on-disk manifest emit Status: Accepted Date: 2026-05-11 Context ADR 0019 exposes the route manifest via a virtual module ( purity:routes ). The plugin's lo
- Eager manifest emit for non-Vite consumersStatus: Accepted Date: 2026-05-11 Context ADR 0032 added the emitTo plugin option, which writes the generated route manifest to disk every time the virtual purity:routes
- `LoaderDataOf<P, R>` — typed loader data from the manifest0034: LoaderDataOf<P, R — typed loader data from the manifest Status: Accepted Date: 2026-05-11 Context ADR 0022 ships the loader named export convention — each route or
- Expose `ElementInternals.states` for component lifecycle0035: Expose ElementInternals.states for component lifecycle Status: Proposed Date: 2026-05-27 Context Purity components have no first-class way to surface lifecycle / as
- Form-associated components via options bagStatus: Proposed Date: 2026-05-27 Context Custom Elements have supported form participation since Safari 16.4 (March 2023) via static formAssociated = true and ElementInt
- Use `moveBefore` in `each()` reorder for state-preserving keyed list updates0037: Use moveBefore in each() reorder for state-preserving keyed list updates Status: Proposed Date: 2026-05-27 Context each() 's LIS-based reordering uses Element.inser
- Islands — opt-in per-subtree hydrationStatus: Proposed Date: 2026-05-27 Context The SSR pipeline shipped under ADRs 0004 and 0005 treats every page as one tree: the server emits a complete HTML document (DSD
- Persistence + lifecycle signal primitivesStatus: Proposed Date: 2026-05-28 Context Three categories of state today fall outside Purity's signal model and force apps into hand-rolled event plumbing: 1. Persisted
- Observer-as-signal primitivesStatus: Proposed Date: 2026-05-28 Context Four browser observer APIs — IntersectionObserver , MutationObserver , matchMedia , ResizeObserver — back ubiquitous UI patterns
- Environment + system preference signalsStatus: Proposed Date: 2026-05-28 Context Eight pieces of always-queryable browser/OS state recur in apps so often that hand-rolled signal wrappers for them appear in nea
- Capability + permission signalsStatus: Proposed Date: 2026-05-28 Context ADR 0041 shipped the always-available environment signals (online, prefers-\ , locale, orientation, DPR, fullscreen). What's lef
- Smart `serverAction()` body-only stripping0043: Smart serverAction() body-only stripping Status: Proposed Date: 2026-05-12 Context ADR 0018 shipped the convention-based strip: any file matching .server.{ts,js,tsx
- Sibling `routes.d.ts` with per-route typed `importFn`0044: Sibling routes.d.ts with per-route typed importFn Status: Proposed Date: 2026-05-12 Context ADR 0034 shipped LoaderDataOf<P, R — a pure-type helper that derives a r
- ARIA live-region announce on navigateStatus: Proposed Date: 2026-05-12 Context ADR 0016 shipped manageNavFocus() — move keyboard focus into the new page's landmark after every nav, so AT vendors announce the
- Async-aware view transitionsStatus: Proposed Date: 2026-05-12 Context ADR 0017 shipped manageNavTransitions() with an explicit non-feature carry-out: Async route handlers (a route view that depends
- Live data signals — `eventSourceSignal`, `webSocketSignal`0047: Live data signals — eventSourceSignal , webSocketSignal Status: Proposed Context ADR 0039 lifted persistence + lifecycle state into signals. ADR 0040 lifted the obs
- `query()` — stale-while-revalidate over `resource()`0048: query() — stale-while-revalidate over resource() Status: Proposed Context resource() (ADRs 0024 / 0026) already handles the per-component "fetch this and react to d
- `optimistic()` — optimistic-update server-action wrapper0049: optimistic() — optimistic-update server-action wrapper Status: Proposed Context ADR 0012 ships serverAction(url, handler) : register a handler at a URL; get back {