refactor: migrate legacy wire namespace to wrn
Quality / quality (ubuntu-latest) (push) Failing after 9m49s
Quality / quality (windows-latest) (push) Canceled after 0s

This commit is contained in:
2026-08-12 18:51:15 +05:30
parent ec23dd4c93
commit 2c960fc1dc
488 changed files with 27306 additions and 19063 deletions
+13 -13
View File
@@ -21,7 +21,7 @@ other arrays intentionally replace earlier values. Cycles and missing/invalid
entries fail with stable `WRN-CONFIG-LAYER-*` diagnostics. `wrnexus config
--explain` lists every resolved layer source.
> Global CSS bundling, the `--wire-*` design-token theme system, and the `wrnexus.config.ts` app-config loader for WrNexus apps.
> Global CSS bundling, the `--wrn-*` design-token theme system, and the `wrnexus.config.ts` app-config loader for WrNexus apps.
Part of the **WrNexus** framework — an SSR-first, Bun-native full-stack web framework.
@@ -30,7 +30,7 @@ Part of the **WrNexus** framework — an SSR-first, Bun-native full-stack web fr
This package owns three server-side concerns that shape every page a WrNexus app renders:
1. **Global stylesheet pipeline** — finds `app/styles/global.css` (or aggregates `app/styles/*.css`), bundles it with Bun's CSS bundler (which resolves `@import`, including from `node_modules`), and produces one stylesheet that is `<link>`ed into every page's `<head>`. Because it is a plain global sheet, it styles server-rendered markup and hydrated client islands identically. A custom `process` hook lets you swap in Tailwind / PostCSS / Sass.
2. **Theme system** — design tokens exposed as CSS custom properties (`--wire-<key>`), with built-in `light`/`dark` sets, deep-merged user overrides, an SSR `<html data-theme>` render (no flash), and a tiny client runtime to toggle/persist the choice.
2. **Theme system** — design tokens exposed as CSS custom properties (`--wrn-<key>`), with built-in `light`/`dark` sets, deep-merged user overrides, an SSR `<html data-theme>` render (no flash), and a tiny client runtime to toggle/persist the choice.
3. **App config** — loads `wrnexus.config.ts` (the `AppConfig` type), applies named profile overrides, and loads the `.env` cascade.
It runs server-side / at build time. Reach for it when configuring an app, defining themes, or customising how global CSS is produced.
@@ -111,7 +111,7 @@ interface StyleProcessContext {
| Export | Type / Signature | Purpose |
| -------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `DEFAULT_THEMES` | `Record<string, ThemeTokens>` | Built-in `light` and `dark` token maps. |
| `THEME_COOKIE` | `"wire-theme"` | Cookie the resolved theme is read from / persisted to. |
| `THEME_COOKIE` | `"wrn-theme"` | Cookie the resolved theme is read from / persisted to. |
| `THEME_CSS_HREF` | `"/__wrnexus/theme.css"` | URL the generated theme stylesheet is served at. |
| `THEME_JS_HREF` | `"/__wrnexus/theme.js"` | URL the client theme runtime is served at. |
| `resolveThemeConfig` | `(config?: ThemeConfig) => ResolvedTheme` | Deep-merge the user's `theme` config over the defaults; pick the default theme (config's `default` if valid, else `dark`, else the first). |
@@ -119,7 +119,7 @@ interface StyleProcessContext {
| `renderThemeCss` | `(theme: ResolvedTheme) => string` | Generate the theme stylesheet: a `:root{…}` default plus one `[data-theme="<name>"]{…}` block per theme. |
| `renderThemeRuntime` | `(theme: ResolvedTheme) => string` | Generate the client runtime (see below). |
Tokens are emitted as `--wire-<key>` custom properties, **except** the reserved key `color-scheme`, which is emitted as the native `color-scheme` CSS property so form controls and scrollbars match the theme.
Tokens are emitted as `--wrn-<key>` custom properties, **except** the reserved key `color-scheme`, which is emitted as the native `color-scheme` CSS property so form controls and scrollbars match the theme.
`ThemeConfig` / `ThemeTokens` / `ResolvedTheme`:
@@ -164,7 +164,7 @@ theme: {
}
```
The client runtime (`renderThemeRuntime`) exposes `window.wireTheme` with `{ get, set, toggle, bind, themes }`, wires up any `[data-wire-theme-toggle]` and `[data-wire-theme-set]` elements on load, and persists the choice to the `wire-theme` cookie (`max-age` 1 year, `samesite=lax`). `toggle()` cycles through the configured theme names in order.
The client runtime (`renderThemeRuntime`) exposes `window.wrnTheme` with `{ get, set, toggle, bind, themes }`, connects up any `[data-wrn-theme-toggle]` and `[data-wrn-theme-set]` elements on load, and persists the choice to the `wrn-theme` cookie (`max-age` 1 year, `samesite=lax`). `toggle()` cycles through the configured theme names in order.
## Usage
@@ -252,17 +252,17 @@ In templates, consume tokens via the custom properties:
```css
.card {
background: var(--wire-color-surface);
color: var(--wire-color-text);
border: 1px solid var(--wire-color-border);
border-radius: var(--wire-radius);
box-shadow: var(--wire-shadow-1);
background: var(--wrn-color-surface);
color: var(--wrn-color-text);
border: 1px solid var(--wrn-color-border);
border-radius: var(--wrn-radius);
box-shadow: var(--wrn-shadow-1);
}
```
```html
<button data-wire-theme-toggle>Toggle theme</button>
<button data-wire-theme-set="brand">Brand theme</button>
<button data-wrn-theme-toggle>Toggle theme</button>
<button data-wrn-theme-set="brand">Brand theme</button>
```
## Requirements / Notes
@@ -270,4 +270,4 @@ In templates, consume tokens via the custom properties:
- **Bun-only.** `bundleCss` uses `Bun.build`'s CSS bundler for `@import` resolution, nesting, and minification. Node is not supported.
- Config and env loading use `node:fs` / `node:path` / `node:url` and read from `process.env`.
- Peer package: `@wrnexus/core` supplies the `SeoConfig` and `SecurityConfig` types referenced by `AppConfig`.
- The bundled global stylesheet, the theme stylesheet (`THEME_CSS_HREF`), and the theme runtime (`THEME_JS_HREF`) are wired into pages by the framework's server; this package only produces their contents.
- The bundled global stylesheet, the theme stylesheet (`THEME_CSS_HREF`), and the theme runtime (`THEME_JS_HREF`) are connected into pages by the framework's server; this package only produces their contents.
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@wrnexus/styles",
"version": "0.8.9",
"version": "0.8.10",
"type": "module",
"main": "src/index.ts",
"exports": {
+3 -3
View File
@@ -6,11 +6,11 @@ export interface CssTokenAudit {
}
/** Audit framework design-token declarations and var() references. */
export function auditWireTokens(css: string): CssTokenAudit {
export function auditWrnTokens(css: string): CssTokenAudit {
const declared = new Set<string>();
const used = new Set<string>();
for (const match of css.matchAll(/(--wire-[A-Za-z0-9_-]+)\s*:/g)) declared.add(match[1]!);
for (const match of css.matchAll(/var\(\s*(--wire-[A-Za-z0-9_-]+)/g)) used.add(match[1]!);
for (const match of css.matchAll(/(--wrn-[A-Za-z0-9_-]+)\s*:/g)) declared.add(match[1]!);
for (const match of css.matchAll(/var\(\s*(--wrn-[A-Za-z0-9_-]+)/g)) used.add(match[1]!);
return {
declared: [...declared].sort(),
used: [...used].sort(),
+1 -1
View File
@@ -164,7 +164,7 @@ async function packageAwareStyleContext(ctx: StyleProcessContext): Promise<Style
export type { Mode as StylesMode };
export {
auditWireTokens,
auditWrnTokens,
normalizeStyleSources,
tailwindSourceDirectives,
contrast,
+35 -35
View File
@@ -1,7 +1,7 @@
/**
* Theme system - design tokens that work SSR and client-side.
*
* Tokens are plain CSS custom properties (`--wire-<key>`) so they cascade and
* Tokens are plain CSS custom properties (`--wrn-<key>`) so they cascade and
* can be overridden by user CSS. Each theme is a flat token map; the framework
* ships default `light`/`dark` sets and the user's config deep-merges over them.
*
@@ -58,7 +58,7 @@ export function defineThemeTokens<T extends ThemeTokens>(tokens: T): T {
}
export function themeVar(token: ThemeToken, fallback?: string): string {
return `var(--wire-${token}${fallback ? `, ${fallback}` : ""})`;
return `var(--wrn-${token}${fallback ? `, ${fallback}` : ""})`;
}
export const THEME_PALETTE_NAMES = [
@@ -90,7 +90,7 @@ export interface CustomThemePalette {
export interface ThemeAccentConfig {
/**
* Accent used when no `wire-accent` cookie is present.
* Accent used when no `wrn-accent` cookie is present.
*
* - Omitted: use the named `palette`, or `blue` when no palette is configured.
* - `false`: keep the configured base palette until the user explicitly picks an accent.
@@ -105,7 +105,7 @@ export interface ThemeConfig {
palette?: ThemePaletteName | CustomThemePalette;
/** Runtime accent/palette switcher configuration. */
accent?: ThemeAccentConfig;
/** Name of the theme used when no `wire-theme` cookie is present. */
/** Name of the theme used when no `wrn-theme` cookie is present. */
default?: string;
/** Named token maps. Deep-merged over the framework's built-in light/dark. */
themes?: Record<string, ThemeTokens>;
@@ -120,8 +120,8 @@ export interface ResolvedTheme {
}
/** Cookies used by the SSR renderer and client runtime. */
export const THEME_COOKIE = "wire-theme";
export const ACCENT_COOKIE = "wire-accent";
export const THEME_COOKIE = "wrn-theme";
export const ACCENT_COOKIE = "wrn-accent";
export const THEME_CSS_HREF = "/__wrnexus/theme.css";
export const THEME_CSS_PREFIX = "/__wrnexus/theme/";
export const THEME_JS_HREF = "/__wrnexus/theme.js";
@@ -248,7 +248,7 @@ function semanticColorTokens(
[`color-${name}-text`]: dark ? colorMix(color, 64, "white") : colorMix(color, 82, "black"),
/*
* hover and contrast are generated for every semantic colour, not just
* primary and secondary. Components referenced --wire-color-danger-hover
* primary and secondary. Components referenced --wrn-color-danger-hover
* and the rest for a long time with nothing defining them, so those states
* simply did not paint.
*
@@ -517,7 +517,7 @@ export function resolveAccentName(
function tokensToDeclarations(tokens: ThemeTokens): string {
return Object.entries(tokens)
.map(([key, value]) =>
key === "color-scheme" ? `color-scheme:${value};` : `--wire-${key}:${value};`,
key === "color-scheme" ? `color-scheme:${value};` : `--wrn-${key}:${value};`,
)
.join("");
}
@@ -528,12 +528,12 @@ function selectorValue(value: string): string {
function globalThemeCss(): string {
return (
'\nhtml,body{font-family:var(--wire-font-sans,"Plus Jakarta Sans",ui-sans-serif,system-ui,sans-serif);}\n' +
":root{--wire-radius-sm:0.55rem;--wire-radius-md:0.9rem;--wire-radius-lg:1.35rem;" +
"--wire-shadow-1:0 1px 2px color-mix(in srgb, black 22%, transparent)," +
'\nhtml,body{font-family:var(--wrn-font-sans,"Plus Jakarta Sans",ui-sans-serif,system-ui,sans-serif);}\n' +
":root{--wrn-radius-sm:0.55rem;--wrn-radius-md:0.9rem;--wrn-radius-lg:1.35rem;" +
"--wrn-shadow-1:0 1px 2px color-mix(in srgb, black 22%, transparent)," +
"0 10px 30px color-mix(in srgb, black 14%, transparent);" +
"--wire-shadow-2:0 10px 30px color-mix(in srgb, black 18%, transparent);" +
"--wire-shadow-3:0 24px 64px color-mix(in srgb, black 24%, transparent);}\n" +
"--wrn-shadow-2:0 10px 30px color-mix(in srgb, black 18%, transparent);" +
"--wrn-shadow-3:0 24px 64px color-mix(in srgb, black 24%, transparent);}\n" +
"wrn-slot{display:contents;}\n" +
"[data-for]{display:none !important;}\n"
);
@@ -610,8 +610,8 @@ export function renderThemeCss(theme: ResolvedTheme): string {
}
/**
* Generate the client theme runtime. It exposes `window.wireTheme` and
* `window.wireAccent`, and binds theme/accent controls.
* Generate the client theme runtime. It exposes `window.wrnTheme` and
* `window.wrnAccent`, and binds theme/accent controls.
*
* The runtime changes only data attributes and cookies. It never writes inline
* CSS variables and never uses localStorage, so CSS and SSR remain the single
@@ -676,15 +676,15 @@ export function renderThemeRuntime(theme: ResolvedTheme): string {
function syncThemeButtons(root){
var current=getTheme();
(root||document).querySelectorAll("[data-wire-theme-set]").forEach(function(node){
node.setAttribute("aria-pressed",String(node.getAttribute("data-wire-theme-set")===current));
(root||document).querySelectorAll("[data-wrn-theme-set]").forEach(function(node){
node.setAttribute("aria-pressed",String(node.getAttribute("data-wrn-theme-set")===current));
});
}
function syncAccentButtons(root){
var current=getAccent();
(root||document).querySelectorAll("[data-wire-accent-set]").forEach(function(node){
node.setAttribute("aria-pressed",String(node.getAttribute("data-wire-accent-set")===current));
(root||document).querySelectorAll("[data-wrn-accent-set]").forEach(function(node){
node.setAttribute("aria-pressed",String(node.getAttribute("data-wrn-accent-set")===current));
});
}
@@ -722,31 +722,31 @@ export function renderThemeRuntime(theme: ResolvedTheme): string {
function bind(root){
var target=root||document;
target.querySelectorAll("[data-wire-theme-toggle]").forEach(function(node){
if(node.__wireThemeBound)return;
node.__wireThemeBound=1;
target.querySelectorAll("[data-wrn-theme-toggle]").forEach(function(node){
if(node.__wrnThemeBound)return;
node.__wrnThemeBound=1;
node.addEventListener("click",toggleTheme);
});
target.querySelectorAll("[data-wire-theme-set]").forEach(function(node){
if(node.__wireThemeBound)return;
node.__wireThemeBound=1;
target.querySelectorAll("[data-wrn-theme-set]").forEach(function(node){
if(node.__wrnThemeBound)return;
node.__wrnThemeBound=1;
node.addEventListener("click",function(){
setTheme(node.getAttribute("data-wire-theme-set"));
setTheme(node.getAttribute("data-wrn-theme-set"));
});
});
target.querySelectorAll("[data-wire-accent-set]").forEach(function(node){
if(node.__wireAccentBound)return;
node.__wireAccentBound=1;
target.querySelectorAll("[data-wrn-accent-set]").forEach(function(node){
if(node.__wrnAccentBound)return;
node.__wrnAccentBound=1;
node.addEventListener("click",function(){
setAccent(node.getAttribute("data-wire-accent-set"));
setAccent(node.getAttribute("data-wrn-accent-set"));
});
});
target.querySelectorAll("[data-wire-accent-clear]").forEach(function(node){
if(node.__wireAccentBound)return;
node.__wireAccentBound=1;
target.querySelectorAll("[data-wrn-accent-clear]").forEach(function(node){
if(node.__wrnAccentBound)return;
node.__wrnAccentBound=1;
node.addEventListener("click",clearAccent);
});
@@ -761,7 +761,7 @@ export function renderThemeRuntime(theme: ResolvedTheme): string {
var initialAccent=getAccent();
if(initialAccent&&!el.hasAttribute("data-accent"))el.setAttribute("data-accent",initialAccent);
window.wireTheme={
window.wrnTheme={
get:getTheme,
set:setTheme,
toggle:toggleTheme,
@@ -769,7 +769,7 @@ export function renderThemeRuntime(theme: ResolvedTheme): string {
themes:THEMES.slice()
};
window.wireAccent={
window.wrnAccent={
get:getAccent,
set:setAccent,
clear:clearAccent,
+3 -3
View File
@@ -57,7 +57,7 @@ test("resolveProfile: explicit > WRNEXUS_PROFILE > mode default", () => {
});
test("loadAppConfig deep-merges the active profile and strips `profiles`", async () => {
const dir = mkdtempSync(join(tmpdir(), "wire-cfg-"));
const dir = mkdtempSync(join(tmpdir(), "wrn-cfg-"));
writeFileSync(
join(dir, "wrnexus.config.mjs"),
`export default {
@@ -157,7 +157,7 @@ test("config layers resolve packages and reject dependency cycles", async () =>
});
test("loadEnv layers .env files by precedence; real env always wins", () => {
const dir = mkdtempSync(join(tmpdir(), "wire-env-"));
const dir = mkdtempSync(join(tmpdir(), "wrn-env-"));
writeFileSync(join(dir, ".env"), 'BASE=1\nSHARED=base\n# a comment\nQUOTED="hi there"\n');
writeFileSync(join(dir, ".env.prod"), "SHARED=prod\nPROD_ONLY=yes\nREALVAR=fromfile\n");
writeFileSync(join(dir, ".env.prod.local"), "SHARED=local\n");
@@ -177,7 +177,7 @@ test("loadEnv layers .env files by precedence; real env always wins", () => {
});
test("loadAppConfig loads env before evaluating the config module", async () => {
const dir = mkdtempSync(join(tmpdir(), "wire-config-env-"));
const dir = mkdtempSync(join(tmpdir(), "wrn-config-env-"));
writeFileSync(join(dir, ".env"), "DATABASE_URL=postgres://pms-from-env\n");
writeFileSync(
join(dir, "wrnexus.config.mjs"),
+5 -5
View File
@@ -31,11 +31,11 @@ describe("theme accents", () => {
expect(css).toContain('[data-theme="light"][data-accent="emerald"]');
expect(css).toContain('[data-theme="dark"][data-accent="emerald"]');
expect(css).toContain("--wire-color-primary-soft:");
expect(css).toContain("--wire-color-primary-muted:");
expect(css).toContain("--wire-color-primary-text:");
expect(css).toContain("--wire-color-on-primary:");
expect(css).toContain("--wire-color-secondary-soft:");
expect(css).toContain("--wrn-color-primary-soft:");
expect(css).toContain("--wrn-color-primary-muted:");
expect(css).toContain("--wrn-color-primary-text:");
expect(css).toContain("--wrn-color-on-primary:");
expect(css).toContain("--wrn-color-secondary-soft:");
});
test("runtime persists accents with cookies and never writes inline tokens", () => {
+2 -2
View File
@@ -7,6 +7,6 @@ test("typed theme token helpers preserve custom tokens and emit CSS variables",
"space-product-card": "1rem",
});
expect(tokens["space-product-card"]).toBe("1rem");
expect(themeVar("color-primary")).toBe("var(--wire-color-primary)");
expect(themeVar("color-text", "#111")).toBe("var(--wire-color-text, #111)");
expect(themeVar("color-primary")).toBe("var(--wrn-color-primary)");
expect(themeVar("color-text", "#111")).toBe("var(--wrn-color-text, #111)");
});
+4 -4
View File
@@ -74,7 +74,7 @@ test("resolveThemeName validates against configured names", () => {
expect(resolveThemeName(undefined, t)).toBe(t.default);
});
test("renderThemeCss emits :root + per-theme blocks and --wire-* vars", () => {
test("renderThemeCss emits :root + per-theme blocks and --wrn-* vars", () => {
const t = resolveThemeConfig({
default: "dark",
themes: { dark: { "color-primary": "#6c8cff" } },
@@ -82,9 +82,9 @@ test("renderThemeCss emits :root + per-theme blocks and --wire-* vars", () => {
const css = renderThemeCss(t);
expect(css).toContain(":root{");
expect(css).toContain('[data-theme="dark"]{');
expect(css).toContain("--wire-color-primary:#6c8cff");
expect(css).toContain("--wrn-color-primary:#6c8cff");
expect(css).toContain("color-scheme:dark"); // reserved token → native property
expect(css).toContain("font-family:var(--wire-font-sans");
expect(css).toContain("font-family:var(--wrn-font-sans");
});
test("active theme CSS contains only the selected theme and accent", () => {
@@ -101,7 +101,7 @@ test("active theme CSS contains only the selected theme and accent", () => {
test("renderThemeRuntime bakes the theme names for cycling", () => {
const t = resolveThemeConfig();
const js = renderThemeRuntime(t);
expect(js).toContain("data-wire-theme-toggle");
expect(js).toContain("data-wrn-theme-toggle");
expect(js).toContain(JSON.stringify(t.names));
expect(js).toContain('addEventListener("wrnexus:navigated"');
});