Files
WRNexusJS/docs/plans/2026-07-02-wire-ui-foundation-design.md
T
2026-07-12 15:55:18 +05:30

103 lines
5.0 KiB
Markdown

# WrNexus batteries-included: design & roadmap
Goal: let users start faster with a built-in UI library, theming, i18n, and
validation. Built **foundation-first** so each subsystem lands solid and tested.
Order (one subsystem per turn):
1. **Theme system** ✅ done — design tokens, SSR + client, no-flash, overridable.
2. **Wire UI** (`@wrnexus/ui`) ✅ done — 17 components + layout primitives as `.wrn`,
consuming theme tokens, overridable 4 ways; auto-discovered + `wrnexus eject`.
3. **Layout system** ✅ done (folded into turn 2) — layout primitives (Container,
Stack, HStack, Grid, Divider, Spacer) + page layouts (`app/layout.wrn` + `<slot>`).
4. **Validation** ✅ done — one schema for both form (client) and API (server).
5. **i18n** ✅ done — translations for pages and API responses.
## 4. i18n (done)
- `@wrnexus/i18n`: locales in `app/locales/<lang>.json` (nested/flat keys, `{param}`
interpolation). Per request the lang resolves from the `wire-lang` cookie →
Accept-Language → config default (`wrnexus.config.ts` `i18n.default`).
- **Server:** `ctx.lang` + `ctx.t(key, params)` on every request (pages + API).
- **Views:** `{t:key}` text sugar compiles to a `<span data-t="key">` marker;
`t:<attr>="key"` translates an attribute. `translateHtml` resolves both on the
final HTML (after components + layout), and `<html lang>` is set (no flash).
- **Switch:** `[data-wire-lang-set="es"]` (or `<select data-wire-lang>`) → the tiny
`/__wrnexus/i18n.js` sets the cookie + reloads; injected only when present.
- Fallback chain: current lang → default lang → the key itself.
All four subsystems + the layout system are complete.
## 3. Validation (done)
- `@wrnexus/validation`: fluent `v` builder (`v.object({ email: v.string().email(),
password: v.string().min(8) })`). `.parse(data)` runs server-side (coerce + errors);
`.describe()` emits a JSON descriptor. Shared `checkField`/`applyRule` back both.
- Define once in `app/schemas/<name>.ts`. **API:** `import login from "../schemas/login";
parseBody(login, ctx.req)` → 400 `{errors}` or `{value}`. **Form:** `<form
data-schema="login">` with `[data-error="field"]` spans.
- The framework bakes all descriptors into `/__wrnexus/schemas.js`
(`window.__wireSchemas`) and ships an eval-free `/__wrnexus/validate.js` that validates
`form[data-schema]` on submit/blur — injected only when a page has `data-schema`.
## 2 + 5. Wire UI + layout (done)
- **Distribution:** `@wrnexus/ui` ships `.wrn` components under `components/`; the
framework auto-discovers them (router `componentDirs`), app/components shadow by
name, and `wrnexus eject <name>` copies one into the app to own it.
- **Codegen:** components now bake prop-driven text/attributes server-side
(`__wireHtml`/`__wireAttr`) so static components ship zero JS; state-referencing
text stays a reactive mustache. Reserved-word props (`class`) get `__p_` refs;
attribute `{expr}` enables `variant`/`size`/`class` composition.
- **Slots:** `renderComponents` is nesting-aware and fills `<slot>` with mount
children — this also powers page layouts (`app/layout.wrn` wraps every page).
- **Styles:** one themed stylesheet `/__wrnexus/ui.css` (all `.wire-*` classes use
`var(--wire-*)`), linked theme → ui → app so app CSS overrides win.
---
## 1. Theme system (this turn)
**Tokens.** Kebab-case token keys become `--wire-<key>` CSS custom properties.
Framework ships default `light` + `dark` token sets (colors, radius, etc.); user
config deep-merges over them and may add new themes.
**Config** (`wrnexus.config.ts`):
```ts
theme: {
default: "dark",
themes: {
light: { "color-primary": "#2563eb", "radius": "8px", ... },
dark: { "color-primary": "#6c8cff", ... },
},
}
```
**Generated CSS** (`/__wrnexus/theme.css`), linked first in `<head>` so global.css
and component styles can read/override it:
```css
:root{ /* default theme tokens */ }
[data-theme="light"]{ --wire-color-primary:#2563eb; color-scheme:light; ... }
[data-theme="dark"]{ --wire-color-primary:#6c8cff; color-scheme:dark; ... }
```
**SSR (no flash).** Server reads the `wire-theme` cookie (falls back to config
default) and renders `<html data-theme="…">`, so the correct theme paints on the
first byte. The theme name is validated against configured names.
**Client** (`/__wrnexus/theme.js`, injected only when a page has a toggle):
`window.wireTheme.{get,set,toggle}` — sets `data-theme` on `<html>`, persists the
`wire-theme` cookie, and binds `[data-wire-theme-toggle]` /
`[data-wire-theme-set="name"]` elements so a `.wrn` component can switch themes
without writing JS.
**Override.** Users change tokens in config, or redefine any `--wire-*` variable
in their own CSS (loaded after theme.css → wins). Wire UI components consume
`var(--wire-*)`, so overriding a token restyles every component at once.
**Integration points:** `@wrnexus/styles` (theme module), `@wrnexus/ssr`
(`renderDocument` html attrs), dev runtime/assets, prod server, `wrnexus build`
(emits `dist/theme.css` + `dist/theme.js`, hashed into the asset version).