diff --git a/en/frontend/micro-frontends/build-system.md b/en/frontend/micro-frontends/build-system.md index 94b88361..f6657b3b 100644 --- a/en/frontend/micro-frontends/build-system.md +++ b/en/frontend/micro-frontends/build-system.md @@ -1,366 +1,115 @@ --- -title: "Build System" -description: "Wippy frontend apps are built with Vite. Every micro frontend app and web component is an independent Vite project — its own package.json,…" +title: "Build and Dependency Contract" +description: "Canonical output commands, Windows wrappers, Web Host import-map snapshots, and externals." --- -# Build System +# Build and Dependency Contract -Wippy frontend apps are built with [Vite 6](https://vitejs.dev/) on Node.js 20+ (with pnpm or npm). Every micro frontend app and web component is an independent Vite project — its own `package.json`, `vite.config.ts`, and `node_modules`. There is no shared build graph across projects. +## Canonical Wippy project build contract -The `@wippy-fe/vite-plugin` package provides two Vite plugins that bridge your Vite project to the Wippy platform: `wippyPagePlugin()` for micro frontend apps, and `wippyComponentPlugin()` for web components. Their primary job is to emit `wippy-meta.json` alongside your build output so that `wippy/views` can read your component's identity, presentation metadata, and capabilities at registration time. +In a Wippy application or module repository launched by `wippy.exe`, invoke the +repository Make target. Do not run package-manager or Vite build commands +directly. -## `@wippy-fe/vite-plugin` +The Makefile recipe for every production frontend target uses: -Install as a dev dependency: - -```bash -npm install --save-dev @wippy-fe/vite-plugin -``` - -### `wippyPagePlugin()` - -Use this plugin for `view.page` apps (Vue SPAs served in an iframe). It: - -- Reads the `wippy` block from `package.json` at build time -- Resolves any `file://` references in the block (for example, `"file://custom-css.do-not-link.css"` is replaced with the file's UTF-8 contents inline) -- Emits `wippy-meta.json` in the output directory, next to your entry HTML -- Injects the same resolved JSON inline into the HTML as ` - - - -
- -
- - - -``` - -Rules: -- MUST contain ``, ``, charset, viewport. -- MUST contain a ``. -- MUST contain `<script type="importmap">` with the complete `imports` object - fetched from the same Web Host release tag. -- MUST re-fetch that map only when the Web Host tag changes or when adding a - dependency to check whether its exact specifier can be external. -- MUST contain exactly one `<script data-role="@wippy/scripts" src="https://web-host.wippy.ai/<release-tag>/dev-proxy.js">`. The URL always requires a release-tag segment. -- MUST contain `<div id="app"></div>` mount point. -- MUST contain `<wippy-loading title="...">` inside the mount instead of a hand-rolled spinner. -- MUST contain `<script type="module" src="./src/app.ts">` (or your entry path) at end of body. - -**VERIFY**: -```bash -grep -c '<script type="importmap">' app.html # must = 1 -grep -c 'data-role="@wippy/scripts"' app.html # must = 1 -grep -c '<wippy-loading' app.html # must >= 1 -``` - -### 3.4 `src/app.ts` (bootstrap) - -Reference: `gold:main/src/app.ts`. - -```ts -import { addCollection } from '@iconify/vue' -import { VueQueryPlugin } from '@tanstack/vue-query' -import { createWippyPersist, preloadWippyState } from '@wippy-fe/pinia-persist' -import { createPinia } from 'pinia' -import { createApp } from 'vue' -import { PrimeVuePlugin } from '@wippy-fe/theme/primevue-plugin' -// Sync getters from @wippy-fe/proxy — available immediately, never awaited to obtain. -import { config, host, api, on } from '@wippy-fe/proxy' - -import App from './app/app.vue' -import { AXIOS_INSTANCE, HOST_API, WIPPY_INSTANCE } from './constants' -import { createAppRouter } from '@wippy-fe/router' -import { routes } from './router' -import './styles.css' -import './tailwind.css' - -export async function createMainApp() { - const routePath = config.context?.route - const initialPath = routePath - ? (routePath.startsWith('/') ? routePath : '/' + routePath) - : '/' - - if (config.theming.global?.icons) { - addCollection({ - prefix: 'custom', - icons: config.theming.global.icons, - }) - } - for (const [prefix, icons] of Object.entries(config.theming.global?.iconSets ?? {})) { - addCollection({ prefix, icons }) - } - - const app = createApp(App) - - const preloaded = await preloadWippyState() - const pinia = createPinia() - pinia.use(createWippyPersist(preloaded)) - app.use(pinia) - app.use(VueQueryPlugin) - app.use(PrimeVuePlugin) - - app.provide(HOST_API, host) - app.provide(AXIOS_INSTANCE, api) - app.provide(WIPPY_INSTANCE, { on }) - - const router = createAppRouter(routes, { initialPath }) - app.use(router) - - return app -} - -export async function mountApp(elementId: string = '#app') { - const app = await createMainApp() - app.mount(elementId) - return app -} - -mountApp() -``` - -> `config`, `host`, `api`, and `on` are **synchronous** getters from `@wippy-fe/proxy` — the host injects the child config before the runtime loads, so they resolve the moment your code runs. You never `await` to *obtain* them (the only `await` left is `preloadWippyState()`, an actual async op). Providing them via `app.provide(...)` is an ergonomics choice so the rest of the app can `inject(...)`; a component can equally `import { host, api, on } from '@wippy-fe/proxy'` at its own call site. See [Proxy API](./proxy-api.md). - -Rules: -- MUST obtain `config`, `host`, `api`, `on` via `import { ... } from '@wippy-fe/proxy'` (sync getters — no `await` to obtain them; never `window.$W` / `getWippyApi`). -- MUST resolve initial path from `config.context?.route`, then fall back to `'/'`. -- MUST normalize the resolved path to start with `/`. -- MUST `app.provide(HOST_API, ...)`, `app.provide(AXIOS_INSTANCE, ...)`, `app.provide(WIPPY_INSTANCE, ...)`. -- MUST `app.mount('#app')` (or whatever id matches the `<div id>` in app.html). -- MUST register the PrimeVue plugin if you use any PrimeVue component. -- SHOULD register `createWippyPersist(preloaded)` on pinia for state persistence across iframe destructions. -- SHOULD register `VueQueryPlugin` if you use TanStack Query. -- MUST register `config.theming.global?.icons` and `config.theming.global?.iconSets` during bootstrap. -- MUST NOT `console.log` boot diagnostics in production (`console.warn`/`console.error` allowed). - -### 3.5 `src/router/index.ts` - -Reference: `gold:main/src/router/index.ts`. - -**Canonical pattern** — `app.ts` uses the package factory directly; the local -router module exports route records only: - -```ts -import type { RouteRecordRaw } from 'vue-router' - -export const routes: RouteRecordRaw[] = [ - { path: '/', name: 'home', component: () => import('../pages/home.vue') }, - { path: '/users', name: 'users', component: () => import('../pages/users.vue') }, - { path: '/:pathMatch(.*)*', name: 'not-found', redirect: '/' }, -] -``` - -Rules: -- MUST use `createAppRouter()` for any page that can run in iframe or `auto` mode. A direct `createWebHistory()` router is allowed only for an explicitly Fragment-only page and makes that artifact non-portable. -- MUST source the initial host route from `config.context?.route`; if it is absent, use `/` or an application-owned default. -- MUST NOT fall back to `window.location` or `window.parent.location`. -- MUST NOT reproduce the factory's memory-history, `afterEach`, `@history`, `navId`, or `setLocalRouter` protocol in application code. -- MUST include catch-all route `/:pathMatch(.*)*` with `name: 'not-found'`. - -**Use the current coherent `@wippy-fe/router` family (`0.0.46` at publication).** The package is the canonical home of portable memory routing, local-router registration, and `@history` synchronization. Do not hand-roll this protocol. - -### 3.6 `src/constants.ts` and `src/types.ts` - -Reference: `gold:main/src/constants.ts`, `gold:main/src/types.ts`. - -```ts -// src/constants.ts -import type { InjectionKey } from 'vue' -import type { HostApi, ProxyApiInstance } from './types' - -export const HOST_API = Symbol('host_api') as InjectionKey<HostApi> -export const AXIOS_INSTANCE = Symbol('axios') as InjectionKey<ProxyApiInstance['api']> -export const WIPPY_INSTANCE = Symbol('proxy') as InjectionKey<ProxyApiInstance> -``` - -```ts -// src/types.ts -// HostApi / ProxyApiInstance / AppConfig are not named exports of any @wippy-fe package. -// Derive them at the type level from $W (typeof only — no runtime access to the internal -// global). The $W typings ship with @wippy-fe/types-global-proxy (add it to tsconfig "types"). -export type HostApi = Awaited<ReturnType<typeof window.$W.host>> -export type ProxyApiInstance = Awaited<ReturnType<typeof window.$W.instance>> -export type WippyConfig = Awaited<ReturnType<typeof window.$W.config>> -``` - -Both files are tiny and stable. Copy verbatim into new apps. - -### 3.7 Styling - -`src/styles.css` — 9-line boilerplate (`gold:main/src/styles.css`): - -```css -html, body { - height: 100%; - margin: 0; - background: transparent; -} - -#app { - height: 100%; -} -``` - -Rules: -- MUST set `background: transparent` so the host's iframe styles win. -- MUST NOT set padding/margin on `html, body, #app`. -- MUST NOT redefine `--p-surface-N`, `--p-content-background`, `--p-text-color`, `--p-primary-color`, etc. at module scope. Host owns them. -- MUST NOT put raw PrimeVue component selectors (`.p-dialog`, `.p-button`, etc.) in module-local source CSS. Shared facade `custom_css` and per-page YAML `config_overrides.customization.customCSS` may intentionally use global `.p-*` selectors as part of the shared PrimeVue theme. -- MUST NOT write raw Tailwind color classes (`text-red-500`, `bg-green-100`, etc.) for colors that have semantic meaning. Use severity classes (`text-danger-500`, `bg-success-100`) instead. (`docs:theming.md`) -- DO put per-app theming in YAML `meta.config_overrides` (or the package.json `wippy.configOverrides` mirror) — not in source CSS. - -`src/tailwind.css`: -```css -@tailwind base; -@tailwind components; -@tailwind utilities; -``` - -`tailwind.config.ts` (`gold:main/tailwind.config.ts`): -```ts -import themePreset from '@wippy-fe/theme/tailwind.config' - -export default { - presets: [themePreset], - content: ['./src/**/*.{vue,ts}', './app.html'], -} -``` - -Note: `themePreset` is a **default import**, not a named import. - -`postcss.config.js` (CRITICAL): -```js -module.exports = { - plugins: { tailwindcss: {}, autoprefixer: {} }, + ] } ``` -### 3.8 Vue / TypeScript hygiene +The checker expands the applicability cross-product and fails if any declared +theme, viewport, or state has no unique scenario. When `overlay` is true, every +scenario also requires the `full-page` capture scope. The final build commit and +hash must match every scenario's candidate and +`recapturedAfterBuild` must be true. -Reference: `gold:main/tsconfig.json`. +Each scenario manifest records hashes rather than trusting filenames: ```json { - "compilerOptions": { - "target": "ES2020", - "module": "ESNext", - "moduleResolution": "bundler", - "strict": true, - "jsx": "preserve", - "resolveJsonModule": true, - "isolatedModules": true, - "esModuleInterop": true, - "allowSyntheticDefaultImports": true, - "lib": ["ES2020", "DOM", "DOM.Iterable"], - "skipLibCheck": true, - "noEmit": true, - "types": ["vite/client", "@wippy-fe/types-global-proxy"] + "schemaVersion": "1.0.0", + "scenarioId": "module.component.light.default", + "componentId": "module.component", + "state": { + "theme": "light", + "viewport": { "width": 1440, "height": 900 }, + "interaction": "default" }, - "include": ["src/**/*.ts", "src/**/*.vue", "vite.config.ts"] -} -``` - -Rules: -- MUST `target: "ES2020"` (canonical). `lib` MUST include `"ES2020"`, `"DOM"`, `"DOM.Iterable"`. ES2022+ is allowed but ES2020 is the gold-standard floor. -- MUST `module: "ESNext"`, `moduleResolution: "bundler"`, `strict: true`, `noEmit: true`. -- MUST include `vite/client` and `@wippy-fe/types-global-proxy` in `types`. -- MUST include `src/**/*.ts`, `src/**/*.vue`, and `vite.config.ts` in `include`. -- ALL `.vue` files MUST use `<script setup lang="ts">` at the top. Place the `<script setup>` block before `<template>`. -- MUST type props with TS interface or generic: `defineProps<{ foo: string }>()`. Untyped object syntax is REJECT. -- MUST use Composition API. Use `ref` over `reactive` where the semantics are equivalent. -- Use `computed` properties for derived state; prefer early returns to reduce nesting. -- File names SHOULD be kebab-case. -- MUST avoid `any`; prefer `unknown` + guards. Each retained `any` MUST have a justifying comment. Aim ≤ 50 across an entire app. -- `pages/<x>.vue` MUST be lazy-loaded in router: `() => import('../pages/x.vue')`. -- MUST NOT use `console.log` in production code. `console.warn` and `console.error` are allowed for error reporting. -- `npm run type-check` MUST exit 0. -- Tests SHOULD exist for non-trivial logic and MUST pass. -- Handle all states in UI components: loading, error, empty, success. -- Prefix event handlers with `handle` (e.g., `handleClick`, `handleSubmit`). - -### 3.9 Subscription cleanup (the leak avoidance pattern) - -`instance.on(pattern, cb)` returns an unsubscribe function. ALWAYS store it. ALWAYS call it in `onUnmounted`. (`incident:3A-3I`, `kb:subscription-cleanup`.) - -Canonical pattern: -```ts -import { onMounted, onUnmounted, inject } from 'vue' -import { WIPPY_INSTANCE } from '../constants' - -const instance = inject(WIPPY_INSTANCE)! - -let unsub: (() => void) | null = null -onMounted(() => { - unsub = instance.on('keeper.task', () => load()) -}) -onUnmounted(() => { - unsub?.() -}) -``` - -For multiple subscriptions: -```ts -let unsubs: Array<() => void> = [] -onMounted(() => { - unsubs.push(instance.on('keeper.session:message', onMessage)) - unsubs.push(instance.on('keeper.session:status', onStatus)) -}) -onUnmounted(() => { - unsubs.forEach(u => u?.()) - unsubs = [] -}) -``` - -Anti-patterns (REJECT): -- `instance.on(...)` at module top-level (outside `onMounted`) — leaks for app lifetime (`incident:3A`). -- `instance.on(...)` with return value discarded — silent leak (`incident:3B-3G`). -- `instance.off(...)` — that method does NOT exist; `// @ts-ignore` won't save you (`incident:3D`). -- Loop-creating subscriptions without storing all unsubs (`incident:3C`). -- `window.addEventListener('message', ...)` without matching `removeEventListener` in `onUnmounted` (`incident:3I`). -- Raw `new EventSource(...)` — bypasses host auth bridge; use `instance.on(...)` for an equivalent server-side topic (`incident:3J`). -- `window.addEventListener('error' | 'unhandledrejection', ...)` / `window.onerror` — installing **window-global error handlers**. The host owns global error capture (the host shell's error handler + the iframe proxy's `errorCapture` injection); a child app or web component adding its own duplicates them. This bites web components especially: multiple instances share one realm, so a single error fires every instance's handler → doubled error reporting and toasts. For your own reporting use `instance.logger.captureException(...)`; for component-scoped failures use Vue's `onErrorCaptured`. **MUST NOT** install global `error`/`unhandledrejection` handlers from a WC. - ---- - -## 4. Web components — manifest, build, runtime - -### 4.1 `package.json` - -Reference: `gold:mermaid/package.json`. - -```json -{ - "name": "@example/mermaid", - "version": "1.0.0", - "specification": "wippy-component-1.0", - "title": "Mermaid Diagram", - "description": "...", - "browser": "dist/index.js", - "files": ["dist/", "src/", "package.json"], - "dependencies": { - "@wippy-fe/theme": "^0.0.46", - "@wippy-fe/webcomponent-core": "^0.0.46", - "@wippy-fe/webcomponent-vue": "^0.0.46", - "mermaid": "^11" + "runtime": { + "browserVersion": "pinned-browser-version", + "devicePixelRatio": 1, + "fontsHash": "sha256:generated-font-set-hash", + "fixtureHash": "sha256:generated-fixture-hash" }, - "devDependencies": { - "@vitejs/plugin-vue": "^5.0.0", - "@wippy-fe/proxy": "^0.0.46", - "@wippy-fe/vite-plugin": "^0.0.46", - "typescript": "^5.0.0", - "vite": "^6.0.0", - "vue": "^3.5.0", - "vue-tsc": "^2.0.0" + "baseline": { + "commit": "generated-baseline-commit", + "buildHash": "sha256:generated-baseline-build-hash" }, - "peerDependencies": { - "@wippy-fe/proxy": "^0.0.46", - "vue": "^3.5.0" + "candidate": { + "commit": "generated-candidate-commit", + "buildHash": "sha256:generated-candidate-build-hash", + "recapturedAfterBuild": true }, - "wippy": { - "tagName": "example-mermaid", - "type": "widget", - "description": "...", - "props": { - "type": "object", - "properties": { - "definition": { "type": "string", "default": "", "description": "..." }, - "transparent": { "type": "boolean", "default": true, "description": "..." } + "requiredScopes": ["component", "context"], + "captures": [ + { + "scope": "component", + "before": { + "artifactId": "component-before", + "path": "screenshots/component-before.png", + "sha256": "sha256:generated-before-hash" + }, + "after": { + "artifactId": "component-after", + "path": "screenshots/component-after.png", + "sha256": "sha256:generated-after-hash" + }, + "diff": { + "artifactId": "component-diff", + "path": "screenshots/component-diff.png", + "sha256": "sha256:generated-diff-hash" } }, - "scripts": { - "build": "build", - "debug": "build:debug", - "test": "lint" - } - }, - "scripts": { - "build": "vite build", - "build:debug": "vite build --mode development", - "dev": "vite build --watch", - "lint": "eslint src --ext .ts,.vue", - "lint:fix": "eslint src --ext .ts,.vue --fix" - } -} -``` - -**Specification & metadata**: -- MUST `"specification": "wippy-component-1.0"`. -- MUST `name` follow `@<org>/<short>` (e.g. `@example/mermaid`). -- MUST set top-level `"title"` and `"description"`. -- MUST set `"browser": "dist/index.js"` pointing at the built entry. -- MUST list `dist/`, `src/`, `package.json` in `files`. - -**`wippy` block (WC-specific)**: -- MUST `wippy.type: "widget"` OR `"component"` (NOT `"page"` or `"web-component"`). -- MUST `wippy.tagName` (camelCase) — the custom element tag. Must contain a hyphen. -- MUST `wippy.description` — a **verbose AI/human-readable usage explanation** (not a one-line label). Must explain HOW to use the WC: the expected call shape (which props vs children), supported input forms, fallback paths, notable perf characteristics. -- MUST `wippy.props` JSON Schema. Every property MUST have `type`, `default`, `description`. -- MAY `wippy.events` JSON Schema (omit if no custom events). -- MUST NOT have `wippy.path` (no HTML entry). -- MUST NOT have `wippy.icon` (no nav presence). -- MUST NOT have `wippy.proxy` block (WCs run in host doc, not iframe). -- MUST set `wippy.scripts.build`. MAY set `debug` and `test`. - -**Dependency hygiene**: -- `dependencies`: bundled-into-WC packages. Canonical: `@wippy-fe/theme`, `@wippy-fe/webcomponent-core`, `@wippy-fe/webcomponent-vue`. Plus the WC's domain libs (e.g. `mermaid`, `chart.js`). -- `devDependencies`: build toolchain. Canonical: `vite`, `@vitejs/plugin-vue`, `typescript`, `vue-tsc`, `vue` for build-time, `eslint*`, `@wippy-fe/proxy` (build-time type imports). -- `peerDependencies`: only imported npm package roots expected from the host. - Rollup externals separately contain every key from the fetched import map. - -### 4.2 `vite.config.ts` (web component, library mode) - -Reference: `gold:mermaid/vite.config.ts`. - -```ts -import { readFileSync } from 'node:fs' -import { resolve } from 'node:path' -import vue from '@vitejs/plugin-vue' -import { wippyComponentPlugin } from '@wippy-fe/vite-plugin' -import { defineConfig } from 'vite' - -const hostImportMap = JSON.parse( - readFileSync(new URL('./import-map.json', import.meta.url), 'utf8'), -) - -export default defineConfig({ - plugins: [vue(), wippyComponentPlugin()], - build: { - target: 'esnext', - lib: { - entry: resolve(__dirname, 'src/index.ts'), - name: 'MermaidDiagram', - fileName: 'index', - formats: ['es'], - }, - rollupOptions: { - input: { index: resolve(__dirname, 'src/index.ts') }, - external: Object.keys(hostImportMap.imports), - output: { - entryFileNames: '[name].js', - chunkFileNames: '[name]-[hash].js', - assetFileNames: '[name]-[hash][extname]', + { + "scope": "context", + "before": { + "artifactId": "context-before", + "path": "screenshots/context-before.png", + "sha256": "sha256:generated-before-hash" }, - // preserveEntrySignatures: false ensures define(import.meta.url, …) stays in - // the entry chunk — required so the ?declare-tag= query the host appends reaches - // import.meta.url and registration doesn't silently no-op. - preserveEntrySignatures: false, - }, - sourcemap: true, - }, -}) -``` - -Rules: -- MUST set `build.target: 'esnext'`. -- MUST use `build.lib` library mode with `formats: ['es']` (ESM only). -- MUST set `entry` (and `input.index`) to your `src/index.ts`. -- MUST set `preserveEntrySignatures: false`. -- MUST set entry/chunk/asset file names: `[name].js`, `[name]-[hash].js`, `[name]-[hash][extname]`. -- MUST include `wippyComponentPlugin()` from `@wippy-fe/vite-plugin` in `plugins` so the build emits `wippy-meta.json` in the actual output directory (see [§9.3a](#93a-wippycomponentplugin-web-components)). -- MUST externalize every key in the fetched target-host import map. -- MUST bundle an imported exact specifier that is absent from that map. This - rule applies independently to `@wippy-fe/*` and every `primevue/*` subpath. -- DO NOT set `base` (no HTML entry, base is irrelevant). -- DO NOT set `cssCodeSplit` (CSS is `?inline`-imported into the JS, see §4.3). - -### 4.3 `src/index.ts` (entry) - -Reference: `gold:mermaid/src/index.ts`. - -```ts -import { WippyVueElement, define } from '@wippy-fe/webcomponent-vue' -import type { WippyElementConfig, WippyPropsSchema } from '@wippy-fe/webcomponent-vue' -import type { ComponentProps } from './types.ts' -import type { Events } from './constants.ts' -import MermaidDiagram from './app/mermaid-diagram.vue' -import stylesText from './styles.css?inline' -import pkg from '../package.json' - -class MermaidElement extends WippyVueElement<ComponentProps, Events> { - static get wippyConfig(): WippyElementConfig<ComponentProps> { - return { - propsSchema: pkg.wippy.props as WippyPropsSchema, - hostCssKeys: ['themeConfigUrl'] as const, - inlineCss: stylesText, - contentTemplate: 'text/vnd.mermaid', // optional; reads text from a child <template data-type="text/vnd.mermaid"> element - } - } - - static get vueConfig() { - return { - rootComponent: MermaidDiagram, + "after": { + "artifactId": "context-after", + "path": "screenshots/context-after.png", + "sha256": "sha256:generated-after-hash" + }, + "diff": { + "artifactId": "context-diff", + "path": "screenshots/context-diff.png", + "sha256": "sha256:generated-diff-hash" + } } - } -} - -export async function webComponent() { - return MermaidElement -} - -define(import.meta.url, MermaidElement) -``` - -Rules: -- MUST extend `WippyVueElement<ComponentProps, Events>` (Vue) or `WippyElement` (vanilla). -- MUST implement `static get wippyConfig()` returning: - - `propsSchema: pkg.wippy.props as WippyPropsSchema` — single source of truth from package.json. - - `hostCssKeys: [...]` — which host-provided CSS bundles to inject into the shadow root. Use the const names from `@wippy-fe/webcomponent-core`: `themeConfigUrl` (theme tokens), `iframeCssUrl` (historically named default themed scrollbar styling), `primeVueCssUrl` (PrimeVue components), `markdownCssUrl` (markdown). Pick the minimal set you need. (`preflightCssUrl` is **not** a member of the `HostCssKey` union — Tailwind v3 preflight is reachable only imperatively via `loadCss(hostCss.preflightCssUrl)`.) - - `inlineCss: stylesText` — your WC-specific CSS imported via `?inline`. - - `contentTemplate?: 'text/vnd.foo'` — optional MIME type; when set, the WC reads text from a child `<template data-type="<mime>">` element, e.g. `<example-mermaid><template data-type="text/vnd.mermaid">graph TD; A --> B</template></example-mermaid>` (rare). -- MUST implement `static get vueConfig()` returning `{ rootComponent }`. Add `plugins: [PrimeVuePlugin, ...]` if you use PrimeVue components. -- MUST export async `webComponent()` factory function so the host loader can call it. -- MUST `define(import.meta.url, ElementClass)` at module level. - -### 4.4 Theme compatibility - -- WC root element MUST NOT have padding or margin. Host controls outer spacing. -- WC MUST use semantic CSS vars for theme-dependent colors: `--p-text-color`, `--p-content-background`, `--p-content-border-color`, `--p-text-muted-color`, `--p-content-hover-background`, `--p-primary-color`. -- WC MUST NOT use raw `--p-surface-N` for theme-dependent purposes — that scale is fixed. -- For derived shades, use `color-mix(in srgb, var(--semantic) X%, transparent)`. -- For severity colors, use `--p-danger-*`, `--p-success-*`, `--p-warn-*`, `--p-info-*`, `--p-help-*`, `--p-accent-*` — never raw Tailwind color names. -- Use `<Icon icon="tabler:icon-name" />` from `@iconify/vue` for all icons — never inline `<svg>` for reusable iconography. -- Use semantic HTML elements where possible; include proper ARIA roles and attributes on interactive elements. - -### 4.5 `src/styles.css` - -```css -@import "@wippy-fe/theme/theme-config.css"; - -.my-container { - display: flex; - align-items: center; - justify-content: center; - width: 100%; - height: 100%; - box-sizing: border-box; -} -``` - -The `?inline` import in `index.ts` reads this file as a string and bakes it into the bundle. Combined with `hostCssKeys`, the shadow root gets host CSS + your WC-specific CSS. - -### 4.6 `src/constants.ts` (events typing) - -Reference: `gold:mermaid/src/constants.ts`. - -```ts -import { useProps, useEvents, usePropsErrors } from '@wippy-fe/webcomponent-vue' -import type { ComponentProps } from './types.ts' - -export interface Events { - load: undefined - unload: undefined - error: { message: string, error: unknown } - invalid: { message: string } + ], + "diff": { + "changedPixels": 0, + "totalPixels": 1296000, + "changedRatio": 0, + "pixelDeltaThreshold": 8, + "changedRatioThreshold": 0.001, + "disposition": "within-threshold", + "result": "passed", + "waiver": null + }, + "console": { "unexpectedErrors": [] }, + "fixtureCleanup": { "temporaryArtifactsRemaining": [], "verified": true } } - -export const useComponentProps = () => useProps<ComponentProps>() -export const useComponentEvents = () => useEvents<Events>() -export const useComponentPropsErrors = usePropsErrors ``` -Use `useComponentProps()` and `useComponentEvents()` in your Vue components instead of plain `defineProps` / `defineEmits` — they integrate with the WC's prop/event marshalling. - -### 4.7 `tsconfig.json` (WC variant) - -Reference: `gold:mermaid/tsconfig.json`. - -```json -{ - "compilerOptions": { - "target": "ES2020", - "useDefineForClassFields": true, - "lib": ["ES2020", "DOM", "DOM.Iterable"], - "module": "ESNext", - "skipLibCheck": true, - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "preserve", - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true, - "types": ["vite/client", "@wippy-fe/proxy"], - "allowSyntheticDefaultImports": true, - "esModuleInterop": true - }, - "include": ["src/**/*.ts", "src/**/*.vue"], - "references": [{ "path": "./tsconfig.node.json" }] -} -``` - -Differences vs micro frontend apps: -- `types` uses `@wippy-fe/proxy` (not `@wippy-fe/types-global-proxy`). -- Adds `useDefineForClassFields`, `noUnusedLocals`, `noUnusedParameters`, `noFallthroughCasesInSwitch`, `allowImportingTsExtensions`. -- `references` to a `tsconfig.node.json`. - -### 4.8 Runtime caching / state persistence - -For state that must survive WC unmount or iframe destruction, use `@wippy-fe/pinia-persist`: -- `persist-key` prop values MUST be globally unique across the app. -- Bundle `@wippy-fe/pinia-persist` when its exact specifier is absent from the pinned target-host import map; externalize it when the exact key is present. - ---- - -## 5. Theming - -### 5.0 Visual-matching escalation (HEAVILY recommended) - -To match a visual design, escalate in this strict order. Do not skip ahead — most "I want it to look like X" work fits at level 1 or 2. - -| Level | What | Where | -|---|---|---| -| **1 — CSS variables** | Override existing `--p-*` semantic vars (primary/content/text/severity) and override the surface scale if the brand needs a different neutral palette. Use Playwright + DevTools `getComputedStyle(document.documentElement)` to enumerate every `--p-*` already defined; pick from that menu first. | Facade `theming.global` / `theming.children`, or per-page YAML `config_overrides.customization.cssVariables` for isolation. NEVER `:root` in `.css` files. | -| **2 — facade `custom_css` / frontend `customCSS` for PrimeVue components** | Add design-token overrides (`--p-button-border-radius`, `--p-dialog-shadow`, etc.) and selector tweaks (`.p-button.p-button-xs { … }`, `.p-accordionheader::before { … }`) when level 1 vars don't reach. | Facade `theming.global` / `theming.children`, or per-page YAML `config_overrides.customization.customCSS`. NEVER raw `.p-*` rules in `.css` files. | -| **3 — Custom Vue components** | Build your own component. Reserved for things PrimeVue genuinely doesn't offer: novel visualizations (force graph, custom chart), domain-specific layouts, interactions outside PrimeVue's catalog. | Vue source in your app. | - -**REJECT level-3 work that could have been done at level 1 or 2.** Examples of "should have been level 1/2": -- Custom dropdown when `<Select>` exists. -- Custom modal when `<Dialog>` + `useDialog` exists. -- Custom toast when `<Toast>` + `useToast` exists (or `host.toast()`). -- Custom confirm prompt when `<ConfirmDialog>` + `useConfirm` exists (or `host.confirm()`). -- Custom tooltip when the `v-tooltip` directive exists. -- Custom inline button styled to look like a primary button when a styled `<Button>` exists. - -Examples where level 3 IS legitimate: -- Force graph for dataflow visualization (no PrimeVue equivalent). -- Token-bar charts (Chart.js wrapper). -- Markdown/rich-text renderers (markdown-it / shiki wrappers). -- Code editor (Monaco WC). -- Domain-specific shell components in managed-layout panels. - -### 5.1 Facade-first: the main way to theme a Wippy app - -A Wippy module composes itself from `ns.dependency` entries. **One of those is `wippy/facade`** — the dependency that parameterises the host shell (top bar, nav, login page, layout) and ALL the global theming. The facade is where the main customization lives. - -**Set theming on the facade dependency, not on individual pages.** Parameters of interest: - -| `wippy/facade` parameter | Purpose | -|---|---| -| `app_title`, `app_name`, `app_icon` | brand identity | -| `custom_css` | shared facade-theme CSS — reaches the host chrome, `view.page` documents, and `view.component` shadow roots on supported hosts. Put shared PrimeVue appearance here; keep necessary domain layout and novel structure in module CSS. | -| `css_variables` | JSON map of CSS variable overrides (`--p-primary`, `--p-surface-*`, brand-specific `--k-*` tokens, etc.); custom properties inherit into every surface, shadow roots included. | -| `host_custom_css` | host-chrome-only CSS (not delivered to children — scope class rules to `.wippy-host-app`). Use `children_custom_css` for CSS that should reach those children but not the host chrome. | -| `hide_nav_bar`, `show_admin`, `history_mode`, `session_type`, `login_path` | UX shell behaviour | -| `fe_mode`, `host_config_layout` | managed-layout mode + layout declaration | - -### 5.1.1 Three levels of override (priority, low → high) - -1. **Facade global** — set in the host's `wippy/facade` `ns.dependency` parameters. Affects the whole user shell + every page inheriting from the facade. Shared PrimeVue appearance and brand theming belong here. -2. **Page overrides** — registry YAML uses `meta.config_overrides.customization.cssVariables` / `customCSS`; frontend `package.json` uses `wippy.configOverrides.customization.cssVariables` / `customCSS` as the host-less mirror. The projected frontend values replace the inherited page theme and cascade to its nested subtree. Backend nested `icons` / `iconSets` remain frontend `icons` / `iconSets` and merge additively. -3. **Runtime overlay** — `window.__WIPPY_CONFIG_OVERRIDES__` set BEFORE proxy.js loads. Rare; for query-string or feature-flag theming. - -See [theming.md](./theming.md) for the full three-level guide with examples, escalation criteria, and anti-patterns. - -### 5.1.2 Where each override lives — STRICT placement rule - -Mismatched placement is the #1 source of theme drift. The rule: - -| Override target | Where it goes | Where it MUST NOT go | -|---|---|---| -| Existing host var (`--p-*`) — change its value | Facade theming, or YAML `config_overrides.customization.cssVariables` for per-page isolation | NEVER `:root { --p-* }` in `src/styles.css` | -| New derived var your project owns — needed for project use | Same place as above; compute via `color-mix()` or `var()` referencing host vars | NEVER `:root { --my-* }` in `src/styles.css` | -| HOST-owned selector override (`.p-button`, `.p-dialog`, `.p-inputtext`, etc.) | Facade `custom_css`, or YAML `config_overrides.customization.customCSS` for per-page isolation | NEVER raw `.p-*` rules in `src/styles.css` | -| Domain layout or genuinely novel structure (`.search-layout`, `.diagram-node`) | `src/styles.css` | facade theme unless the rule is intentionally shared | -| Project-scoped non-theme constant (chart bar color, fixed spacing tag) | `src/styles.css` with a clear project prefix (e.g., `--keeper-chart-bar-*`) | n/a | - -**Rationale**: theme is a host concern; the host's CSS pipeline composes facade global + per-page customization in a defined order. CSS files inside the bundle ship AFTER the host's pipeline and shadow it, breaking the override semantics. - -**REJECT 42b**: any `:root { --p-* }` (or `:root { --<other-host-var> }`) redefinition in a child app's `.css` file. Move to facade theming or per-page YAML `config_overrides.customization.cssVariables`. - -**REJECT 43a**: any raw `.p-<component>` rule in a child app's `.css` file. Move to facade `custom_css` or per-page YAML `config_overrides.customization.customCSS`. - -### 5.2 Semantic vs fixed CSS variables - -| Variable | Flips in dark mode? | Use for | -|---|---|---| -| `--p-text-color` | yes | body text | -| `--p-content-background` | yes | container / page background | -| `--p-content-border-color` | yes | borders | -| `--p-text-muted-color` | yes | secondary text | -| `--p-content-hover-background` | yes | hover states | -| `--p-primary-color` | yes | primary action color | -| `--p-surface-0` … `--p-surface-950` | NO (fixed scale) | only as anchors for color-mix(); avoid for theme-dependent UI | -| `--p-primary-500` … `--p-primary-950` | NO (fixed scale) | only when you need a specific primary shade | -| `--p-danger-color`, `--p-success-color`, `--p-warn-color`, `--p-info-color`, `--p-help-color`, `--p-accent-color` | yes | severity colors. Use these, NOT raw Tailwind names. | - -Anti-pattern (REJECT): -```css -.card { background: var(--p-surface-100); } /* fixed; doesn't flip */ -.card { background: var(--p-primary); } /* invalid token; --p-primary-color is the right one */ -``` - -Canonical: -```css -.card { - background: var(--p-content-background); - border: 1px solid var(--p-content-border-color); - color: var(--p-text-color); -} -.muted-card { - background: color-mix(in srgb, var(--p-content-background) 92%, var(--p-text-color) 8%); -} -.danger-banner { background: var(--p-danger-color); } -``` - -### 5.3 REPLACE vs MERGE per field - -| Field in `customization` | Per-page semantics | -|---|---| -| `cssVariables` | REPLACE — your map fully replaces parent's | -| `customCSS` | REPLACE — your string fully replaces parent's | -| `icons` | MERGE shallow — additive | -| `iconSets` | MERGE per-prefix — additive | - -`AppConfigOverrides` top-level: - -| Field | Semantics | -|---|---| -| `customization` | merged via `mergeChildCustomization` (above) | -| `axiosDefaults` | MERGE shallow | -| `routePrefix` | REPLACE | -| `apiRoutes` | REPLACE | - -### 5.4 `@light` / `@dark` blocks - -The host SUPPORTS `@light` and `@dark` keys in `cssVariables` maps — they compile to `@media (prefers-color-scheme: light/dark) { :root { ... } }` blocks ONLY. They are NOT a `[data-theme]` attribute and do not emit any attribute-scoped selector at injection time — binding is solely on the OS color-scheme preference (see `createCssVariables` in `src/shared/util/createStyle.ts`). - -Example: -```yaml -css_variables: - --p-primary-color: var(--p-primary-500) - --kp-bg: var(--p-content-background) - '@light': - --p-content-background: '#ffffff' - --p-text-color: '#18181b' - '@dark': - --p-content-background: '#1c1a19' - --p-text-color: '#fafafa' -``` - -An app that toggles themes via `document.documentElement.setAttribute('data-theme', ...)` will NOT trigger these overrides; the host injects no `[data-theme]` CSS. To support a manual toggle, document it as a project-specific extension and emit your own `[data-theme]`-scoped variable block. See also [micro-frontend-app-theming.md](./micro-frontend-app-theming.md) and [host-less-mode.md](./host-less-mode.md). - -### 5.5 `customCSS` scoping - -- Scope selectors to `.wippy-host-app` only when they target host chrome. Shared facade-theme selectors such as `.p-drawer-content` may intentionally remain unscoped so the same PrimeVue theme reaches pages and injected component shadow roots. -- Scope to a specific child/page boundary only when isolation is intentional. Per-page overrides can use top-level selectors within their delivered page scope. - -### 5.6 Iconify discipline - -Icons in Wippy apps follow a single workflow: - -1. **Use `@iconify/vue` `<Icon>` for ALL icons.** Don't inline `<svg>` for reusable iconography. Don't ship icon-font CSS (Tabler-icons-font, Material Icons font). The proxy and the build assume Iconify; mixing systems creates a/b drift. -2. **Prefer permissive packs.** All free for commercial use, all available via Iconify: - - `tabler` (MIT, ~5,400 icons) — broad UI coverage; the gold-standard default for keeper-class apps. - - `lucide` (ISC, ~1,500 icons) — clean line style. - - `phosphor` (MIT, ~7,000 icons) — six weight variants. - - `material-symbols` (Apache 2.0, ~3,000+ icons) — Google's modern set. - - `mdi` (Apache 2.0, ~7,000 icons) — Material Design Icons community pack. - - `heroicons` (MIT, ~300 icons) — Tailwind team's set, outline + solid. -3. **Don't use commercial-licensed packs** (FontAwesome Pro, etc.) without licence verification per developer seat. Iconify hosts MIT/CC-BY subsets of FontAwesome (`fa6-solid`/`fa6-regular`/`fa6-brands`) — use those instead. -4. **Custom icons** — when no permissive pack covers a symbol: - - Declare them in `theming.global.icons` / `iconSets` when shared, or `config_overrides.customization.icons` for per-page additions — safe because `icons` MERGES, not replaces. - - The bootstrap path (`config.theming.global?.icons → addCollection({ prefix: 'custom', icons })` plus `iconSets`) wires them automatically. - - **NEVER call `addCollection()` from arbitrary application code.** The bootstrap path is canonical; everything else fragments the registry. - - Mint custom icons sparingly. If you find yourself adding more than a dozen, consider whether a permissive pack already has the symbol. -5. **At call sites**, prefer `<Icon icon="tabler:home" />` over hardcoded SVG. Use Iconify's pack:name format consistently. Use `aria-hidden="true"` for decorative icons, `aria-label` for meaningful ones. - -REJECT (5.6.r): any `.vue` file that registers icons via `addCollection()` outside `app.ts`'s canonical bootstrap. REJECT raw `<svg>` for reusable iconography (one-off illustrations are OK). - ---- - -## 6. Proxy API & subscriptions - -### 6.1 Injection keys (apps) - -In `src/constants.ts` (micro frontend apps): - -| Key | Provides | Use | -|---|---|---| -| `HOST_API` | `HostApi` | `inject(HOST_API)` | -| `WIPPY_INSTANCE` | `ProxyApiInstance` | `inject(WIPPY_INSTANCE)` | -| `AXIOS_INSTANCE` | pre-configured `axios` (auth + baseURL) | `inject(AXIOS_INSTANCE)` | - -For web components, import from `@wippy-fe/proxy` directly: -```ts -import { host, api, on } from '@wippy-fe/proxy' -``` - -### 6.2 `host.*` methods (full reference: Appendix B) - -| Method | Use | -|---|---| -| `host.toast` | replaces PrimeVue ToastService | -| `host.confirm` | replaces `window.confirm` | -| `host.startChat` | open a new chat | -| `host.openSession` | navigate to session | -| `host.openArtifact` | open artifact | -| `host.setContext` | set chat context | -| `host.navigate` | host-side navigation | -| `host.onRouteChanged` | report router change | -| `host.handleError` | report error | -| `host.formatUrl` | prepend `routePrefix` | -| `host.classifyLink` | classify nav target | -| `host.layout` | managed-layout API (always present; `host.layout.snapshot` is null outside managed mode) | -| `host.logout` | sign out | - -Rules: -- MUST use `host.toast` not PrimeVue ToastService. -- MUST use `host.confirm` not `window.confirm`. -- MUST use injected `useApi()` / `AXIOS_INSTANCE` not raw `axios.create()`. -- MUST NOT call `sendIframeMessage()` directly — go through `host.*` methods. - -**VERIFY**: -```bash -grep -r "axios.create" src/ # should = 0 -grep -r "window.confirm" src/ # should = 0 -``` - -### 6.3 `instance.on(pattern, cb)` reserved patterns - -| Pattern | Payload | Meaning | -|---|---|---| -| `@history` | `{ path?, navId? }` | host pushed a route | -| `@visibility` | boolean | iframe visibility changed | -| `@layout-change` | `LayoutSnapshot` | layout tree changed | -| `@layout-panel-changed` | `{ panelId, ... }` | single panel changed | -| `@layout-breakpoint` | `{ name, width }` | breakpoint changed (`name` = new breakpoint, `width` = threshold px) | -| `@message` | wildcard | catch all WebSocket messages | -| `@state-error` | `{ error, key }` | state save failed | - -Custom topics use colon-separated parts; `*` is wildcard. - -### 6.4 Layout API - -`host.layout` is always present (a `LayoutApi` object). Outside managed-layout mode, `host.layout.snapshot` is `null` and all mutation/bus calls are silent no-ops — gate on `host.layout.snapshot` (or `isManaged` from `useWippyLayout`) before mutating, not a null-check on `host.layout`. - ---- - -## 7. Router & host integration - -(Source body in §3.5; this section is verification-focused.) - -| # | Rule | REJECT? | -|---|---|---| -| 7-1 | imports and uses `createAppRouter` from `@wippy-fe/router` | yes | -| 7-2 | initial path = `config.context?.route ?? '/'` | yes | -| 7-3 | no `window.location` / `window.parent.location` host-route fallback | yes | -| 7-4 | no application-owned `createMemoryHistory`, `afterEach`, `@history`, `navId`, or `setLocalRouter` synchronization protocol | yes | -| 7-5 | catch-all `/:pathMatch(.*)*` route with `name: 'not-found'` | yes | -| 7-6 | direct `createWebHistory` appears only in a documented Fragment-only artifact | yes | - -If you persist last-route to localStorage, EXCLUDE ID-bearing routes (`/session/:id`, `/changes/:id`, etc.) — reload-after-delete lands on stale 404s otherwise (`incident:2H`). - -Prefer the proxy, AppConfig, and router APIs over raw cross-frame messaging. A documented low-level embedding integration that truly needs `message` MUST validate both `event.origin` against an explicit configured allowlist and `event.source` against the expected window, then remove the listener on unmount. A source comparison alone is not an origin check. - ---- - -## 8. Build pipeline & Makefile - -### 8.1 Canonical Makefile recipe - -```make -build-<app>-frontend: - cd <path-to-app> && npm install --no-audit --no-fund --prefer-offline && npm run build -- --outDir <dest> --emptyOutDir -``` - -Rules: -- MUST identify the registry owner, module/static mount, build owner, output directory, and emitted entry before rebuilding. -- MUST pass both `--outDir` and `--emptyOutDir`; resolve and verify the exact - target before running the build. -- MUST NOT use the `rm + mkdir + cp` dance — pollutes source tree with `dist/` and is not atomic. -- MUST `cd` into the app dir. -- The output directory MUST match the path actually mounted by the deployment; `static/<embed-name>` is a common layout, not a universal contract. - -Each module that publishes a frontend MUST have its own `build-<app>-frontend` target. Add to `publish-*` chains. - -### 8.2 `make.bat` + `make.ps1` are required (every module, every time) - -Every module that ships a `Makefile` MUST also ship `make.bat` + `make.ps1` next to it. **No "if your team runs on Windows" carve-out** — Wippy modules are written by mixed teams and audited on mixed machines, and the wrapper is small enough that there is no reason not to have it. - -- `make.bat` is a thin shim that invokes `make.ps1` via `powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass`. -- `make.ps1` mirrors every Makefile target one-for-one — `build-*`, `lint*`, `publish*`, `dev`, `clean`, etc. — so the same workflow runs on Linux, macOS, and Windows. -- The exact `npm run build -- --outDir <abs-or-relative> --emptyOutDir` - command belongs in both `Makefile` and `make.ps1`; `make.bat` contains no - duplicated build logic. See [Build System](./build-system.md) for complete - runnable examples. -- Keep `make.ps1` pure ASCII (no em-dashes, smart quotes) — Windows PowerShell 5.1 reads BOM-less files as Windows-1252 and corrupts non-ASCII chars on read. - -REJECT a module that ships `Makefile` without matching `make.bat` + `make.ps1`. - -### 8.3 Fetched import-map snapshot and dependency roles - -Fetch `<fe_facade_url>/import-map.json` once during development: - -```bash -curl.exe -fsS "https://web-host.wippy.ai/<version-tag>/import-map.json" -o import-map.json -``` - -- Vite externals MUST be `Object.keys(hostImportMap.imports)`. -- Host-less `app.html` MUST contain the same complete `imports` object. -- `peerDependencies` contain only actually imported npm package roots expected - from the host; they cannot mirror import-map subpath keys. -- Re-fetch when the Web Host tag changes or when adding a dependency to check - whether its exact specifier can be external. -- Bundle a used exact specifier when it is absent from the fetched map. - -Mismatch symptom: `Failed to resolve module specifier 'pinia'` (`incident:8A`). - -### 8.4 Pre-publish gates - -- `npm run type-check` MUST exit 0. -- `npm test` MUST pass if any tests exist. -- `npm run build -- --outDir <abs-or-relative> --emptyOutDir` MUST succeed. -- `npm run lint` SHOULD exit 0 if you have eslint configured. - ---- - -## 9. Host-less mode - -Host-less = boot the SPA via a static HTTP server with no real Wippy host running. `dev-proxy.js` provides a host shim plus a "dev overlay" UI for accepting/editing the config. Host-less mode is the **default supported workflow** for new apps; the `wippyPagePlugin()` and importmap+`<wippy-loading>` patterns described below should be present unless a team has a very good reason to opt out (rare). (See [host-less-mode.md](./host-less-mode.md) for full detail.) - -### 9.1 Complete pinned Web Host import map - -Use the complete, valid JSON script shown in §3.3. It is a verified -`webcomponents-1.0.44` example, not a hand-maintained canonical package list. -For another tag, replace its complete JSON body with the fetched response; -never put comments inside the JSON itself. - -Rules: -- MUST exist in `app.html`. -- MUST copy the complete `imports` object from the same pinned Web Host release, - including `@wippy-fe/proxy` and unused keys. -- MUST bundle an imported exact specifier that is absent from the fetched map. -- Re-fetch only when the Web Host tag changes or when adding a dependency. - -### 9.2 dev-proxy.js + `@wippy/scripts` data-role - -```html -<script - src="https://web-host.wippy.ai/<release-tag>/dev-proxy.js" - data-role="@wippy/scripts" -></script> -``` - -Production CDN form: -```html -<script - src="https://web-host.wippy.ai/<release-tag>/dev-proxy.js" - data-role="@wippy/scripts" -></script> -``` - -Rules: -- MUST have `data-role="@wippy/scripts"` so the host (when running) can find and replace it. -- MUST have `src=` set; raw `<script data-role="@wippy/scripts"></script>` placeholder is acceptable only when a real host injects the src at boot. - -### 9.3 wippyPagePlugin (default) - -```ts -// vite.config.ts -import { wippyPagePlugin } from '@wippy-fe/vite-plugin' - -export default defineConfig({ - plugins: [vue(), wippyPagePlugin(), /* … */], - /* … */ -}) -``` - -The plugin's `transformIndexHtml` hook injects the package.json `wippy` block into the built HTML at the top of `<head>`: - -```html -<script type="application/json" data-role="@wippy/package"> -{ - "name": "...", - "wippy": { "proxy": { "injections": { ... } }, "configOverrides": { ... } } -} -</script> -``` - -Dev-proxy reads the JSON synchronously at boot and seeds: -- proxy injection defaults from `wippy.proxy.injections` -- per-page customization from `wippy.configOverrides.customization` - -so the dev-overlay shows the correct values pre-populated. - -Rules: -- SHOULD include `wippyPagePlugin()` in `vite.config.ts`. This is the **default** for new apps; opt out only with a very good reason (e.g. shipping a host-only bundle that explicitly does not support host-less dev), and record the reason in maintained project guidance. -- MUST install the coherent current `@wippy-fe/vite-plugin` family (`0.0.46` at publication) in devDependencies. -- The plugin is harmless under a real host (the host ignores the `@wippy/package` script tag). - -**VERIFY** the script is in the built HTML: -```bash -OUTPUT_DIR="${OUTPUT_DIR:-dist}" -npm run build -- --outDir "$OUTPUT_DIR" --emptyOutDir && - grep -c 'data-role="@wippy/package"' "$OUTPUT_DIR/app.html" # SHOULD = 1 -``` - -### 9.3a wippyComponentPlugin (web components) - -`view.component` packages have no HTML entry to inject into, so the page plugin doesn't apply. Use `wippyComponentPlugin()` from the same `@wippy-fe/vite-plugin` package — it's emit-only: - -```ts -// vite.config.ts (web component) -import { wippyComponentPlugin } from '@wippy-fe/vite-plugin' - -export default defineConfig({ - plugins: [vue(), wippyComponentPlugin(), /* … */], - /* … */ -}) -``` - -The component plugin emits `wippy-meta.json` in the actual Vite output directory (the resolved `wippy` block) only — no HTML transform, no inline script tag. - -Rules: -- MUST be present in every current `view.component` build. -- MUST install the coherent current `@wippy-fe/vite-plugin` family. - -**VERIFY** the meta file is in the dist: -```bash -OUTPUT_DIR="${OUTPUT_DIR:-dist}" -npm run build -- --outDir "$OUTPUT_DIR" --emptyOutDir && - test -f "$OUTPUT_DIR/wippy-meta.json" && - echo "OK: wippy-meta emitted" || echo "MISSING: wippy-meta.json" -``` - -### 9.3b The `wippy-meta.json` contract + version correlation - -The presence of `wippy-meta.json` next to the served entry is a **hard requirement** for the current contract (`wippy/views` 1.0.31 or newer with the coherent `@wippy-fe/vite-plugin` family). The output directory is deployment-defined; verify the file beside the artifact that the registry actually serves. The file is the resolved `wippy` block from `package.json`, with every `"file://<rel>"` string replaced by the referenced file's UTF-8 contents at build time. - -Two endpoints read it: - -| Endpoint | What it serves | -|---|---| -| `GET /api/public/pages/content/{id}` | resolved `wippy-meta.json` next to the served `app.html` (view.page) | -| `GET /api/public/components/list` + `/components/by-tag/{tag}` | resolved `wippy-meta.json` next to each `index.js` (view.component). `{tag}` is the WC's custom-element tag name (e.g. `example-mermaid`), resolved via `loadByTagName()` — not the CDN git release tag. | - -**YAML-first priority**: the operator's `_index.yaml` registry entry overlays the bundled meta per-field. If `meta.tag_name`, `meta.title`, `meta.description`, `meta.props`, `meta.events`, or `meta.entry_point` is set in YAML, that wins. Otherwise the bundled meta fills in. - -**Fallback when missing**: if `wippy-meta.json` is absent next to the entry, views falls back to a deprecated YAML-synthesis path AND emits a per-process deprecation warning. Treat the warning as a release-blocker. - -#### Current compatibility contract - -Use `wippy/views` 1.0.31 or newer with one coherent current `@wippy-fe/*` package family (`0.0.46` at publication). The Vite plugin validates package metadata and emits `wippy-meta.json`; the registry entry may overlay deployment fields. Historical migration combinations are not an authoring target. - -**VERIFY** the meta file is in the dist and contains resolved content (no `file://` strings): -```bash -OUTPUT_DIR="${OUTPUT_DIR:-dist}" -npm run build -- --outDir "$OUTPUT_DIR" --emptyOutDir -test -f "$OUTPUT_DIR/wippy-meta.json" && echo "OK: emitted" || echo "REJECT: missing" -grep -c 'file://' "$OUTPUT_DIR/wippy-meta.json" | { read n; [ "$n" = "0" ] && echo "OK: all file:// resolved" || echo "REJECT: $n unresolved file:// refs"; } -``` - -### 9.4 wippy-loading - -```html -<div id="app"> - <wippy-loading title="Loading..."></wippy-loading> -</div> -``` - -The `<wippy-loading>` element is auto-registered by dev-proxy (and by the real host) before the body parses. - -REJECT custom hand-rolled spinners. - -### 9.5 base: '' (relative paths) — REJECT if hardcoded - -`base: ''` in vite.config produces relative `./app.js`, `./assets/...` paths in the built HTML/JS. The bundle is portable to any URL prefix and any mount point — host-managed, host-less dev, or moved between projects. - -A hardcoded absolute base (e.g. `base: '/app/keeper/'`) ties the bundle to a specific mount point and breaks portability. **This is a 100% REJECT — there is no acceptable "documented exception".** A child app must not assume its own URL prefix; the prefix is a host-side `meta.url` + `meta.base_path` concern, and the host injects the appropriate `<base>` into the HTML at serve time. Set `base: ''` and let the host do its job. - -### 9.6 Dev-overlay accept flow - -- `<wippy-dev-overlay>` shadow-DOM web component, FAB in bottom-right. -- Manual mode blocks boot until "Accept config" clicked. -- Auto-accept via `localStorage['@wippy-dev/auto-accept'] === 'true'`. -- Stored config: `localStorage['@wippy-dev/config']`, `localStorage['@wippy-dev/proxy-config']`. -- Reset clears all `@wippy-dev/*` keys + reloads. - ---- - -## 10. Verification recipes - -Run these before submitting. Each maps to a section. - -### 10.1 Bootstrap & build - -```bash -# 10.1.1 — wippy.specification + wippy.type -node -e 'const p=require("./package.json"); if(p.specification!=="wippy-component-1.0") throw new Error("bad specification"); const t=p.wippy?.type; if(t!=="page" && t!=="widget" && t!=="component") throw new Error("bad wippy.type"); console.log("OK")' - -# 10.1.2 — markdown injection if app uses markdown (micro frontend apps only) -grep -A 10 'wippy.proxy.injections.css' package.json | grep -c '"markdown": true' # 1 if uses markdown, 0 otherwise - -# 10.1.3 — base relative -grep -E "base:\s*['\"]" vite.config.ts # must show: base: '' - -# 10.1.4 — wippyPagePlugin present (host-less default) -grep -c "wippyPagePlugin" vite.config.ts # SHOULD = 1 - -# 10.1.5 — type-check -npx vue-tsc --build --force || npx vue-tsc --noEmit # exit 0 -``` - -### 10.2 Router & host integration (micro frontend apps) - -```bash -# 10.2.1 — package factory is the only portable routing implementation -grep -c "from '@wippy-fe/router'" src/app.ts # >= 1 -grep -c "createAppRouter(routes, { initialPath })" src/app.ts # >= 1 - -# 10.2.2 — portable app code does not reproduce the package protocol -grep -E "createMemoryHistory|router\.afterEach|on\(['\"]@history|setLocalRouter|window(?:\.parent)?\.location" src/{app.ts,router/index.ts} # empty -grep -c "createWebHistory" src/router/index.ts # 0 unless explicitly Fragment-only - -# 10.2.3 — catch-all + name -grep -E "pathMatch.*not-found|name:.*not-found" src/router/index.ts # >= 1 -``` - -### 10.3 Proxy API & subscription cleanup - -```bash -# 10.3.1 — no module-scope instance.on -grep -n "^instance\.on" src/**/*.{ts,vue} # should be empty - -# 10.3.2 — every instance.on has matching onUnmounted in same file -for f in $(grep -rl "instance\.on(" src --include="*.vue"); do - o=$(grep -c "instance\.on(" "$f"); u=$(grep -c "onUnmounted" "$f") - [ "$o" -gt 0 ] && [ "$u" -eq 0 ] && echo "FAIL: $f" -done - -# 10.3.3 — no instance.off -grep -r "instance\.off" src # should be empty - -# 10.3.4 — no raw axios.create -grep -r "axios.create" src # should be empty - -# 10.3.5 — no raw EventSource -grep -r "new EventSource" src # should be empty - -# 10.3.6 — no window.confirm -grep -r "window\.confirm" src # should be empty - -# 10.3.7 — addEventListener pairs with removeEventListener -for f in $(grep -rl "addEventListener" src --include="*.vue"); do - a=$(grep -c "addEventListener" "$f"); r=$(grep -c "removeEventListener" "$f") - [ "$a" -ne "$r" ] && echo "FAIL: $f add=$a remove=$r" -done -``` - -### 10.4 Styling & theming - -```bash -# 10.4.1 — no theme-dependent --p-surface-N use (informational; document exceptions) -grep -r "var(--p-surface-[0-9]" src/**/*.vue | wc -l # aim for 0; project minimum acceptable - -# 10.4.2 — no invalid --p-primary token -grep -rE "var\(--p-primary\)[^-]" src/**/*.vue | wc -l # must = 0 - -# 10.4.3 — no module-level redefinition of host tokens -grep -rE ":root\s*\{[^}]*--p-(content-background|text-color|primary)" src/styles.css # must = 0 -``` - -### 10.5 Vue/TS hygiene - -```bash -# 10.5.1 — every .vue starts with <script setup lang="ts"> -find src -name "*.vue" | while read f; do - grep -q '<script setup lang="ts">' "$f" || echo "FAIL: $f" -done - -# 10.5.2 — count any-casts (informational; aim ≤ 50) -grep -rE ":\s*any|as\s*any" src | wc -l - -# 10.5.3 — no console.log -grep -rE "console\.log" src # should be empty -``` - -### 10.6 Host-less boot - -```bash -# dist is Vite's default; set OUTPUT_DIR to the build owner's verified output. -OUTPUT_DIR="${OUTPUT_DIR:-dist}" -npm run build -- --outDir "$OUTPUT_DIR" --emptyOutDir -grep -c 'data-role="@wippy/scripts"' "$OUTPUT_DIR/app.html" # must = 1 -grep -c 'data-role="@wippy/package"' "$OUTPUT_DIR/app.html" # SHOULD = 1 -grep -c '<script type="importmap">' "$OUTPUT_DIR/app.html" # must = 1 -grep -c '<wippy-loading' "$OUTPUT_DIR/app.html" # must >= 1 -grep 'src="./app.js"' "$OUTPUT_DIR/app.html" # match - -# live boot test: serve "$OUTPUT_DIR/" with dev-proxy.js from the target Web Host release. -# Browser: see <wippy-loading>, then dev-overlay FAB → Accept → app boots. -``` - -### 10.7 Browser-emulator dark/light + contrast check (recommended) - -Static checks catch token misuse but not actual rendering. Before shipping any non-trivial visual change, **verify the app in a browser emulator (Playwright or equivalent) under both dark and light theme**, and check contrast on both. - -Recommended Playwright recipe: - -```js -// dark + light snapshot pair -for (const scheme of ['dark', 'light']) { - await page.emulateMedia({ colorScheme: scheme }) - await page.goto('http://localhost:<port>/<route>') - await page.waitForLoadState('networkidle') - await page.screenshot({ path: `.local/snap-${scheme}.png`, fullPage: true }) -} - -// contrast smoke — flag any element with computed text vs background -// contrast ratio < 4.5 (WCAG AA body) or < 3 (WCAG AA large text). -// Use `axe-core`, `@axe-core/playwright`, or `pa11y` for a real audit. -``` - -Verify visually: text legibility on both schemes, no light-only assumptions (white-on-white panels), severity colours readable on both, hover/active states visible in both. - -REJECT a page that renders correctly in dark mode but is broken in light mode (or vice versa). - -### 10.8 Final gates - -```bash -npx vue-tsc --build --force && \ -npm test --if-present -- --run && \ -npm run build -- --outDir dist --emptyOutDir && \ -grep -c 'data-role="@wippy/scripts"' dist/app.html && \ -grep -c '<wippy-loading' dist/app.html && \ -echo "ALL GATES PASS" -``` - ---- - -## 11. Acceptance criteria (REJECT rules) - -REJECT a submission if any of the following are true. - -### Manifest (§3.1, §4.1) -1. `package.json.specification` is not `"wippy-component-1.0"`. -2. `wippy.type` is not `"page"` (micro frontend apps), `"widget"` (web components, historical), or `"component"` (web components, newer alias accepted by the vite plugin validator). -3. Micro Frontend App: `wippy.path` does not point to the actual built artifact (e.g. `dist/app.html`). -4. WC: `wippy.tagName` is missing or does not contain a hyphen. -5. WC: `wippy.props` is missing OR has properties without `type`/`default`/`description`. -5a. WC: `wippy.description` is missing OR is a one-line label. It MUST be a verbose usage explanation — see §4.1 / §2.2. -6. `peerDependencies` omits an npm package root that the artifact actually - imports and expects the host to provide, or includes import-map subpaths as - if they were separate npm packages. -7. A dependency is classified from a remembered package list instead of the - pinned target-host import map. -8. WC: `dependencies` is missing `@wippy-fe/webcomponent-core` or `@wippy-fe/webcomponent-vue`. - -### vite.config.ts (§3.2, §4.2) -9. Micro Frontend App: `base` is not `''`. Hardcoded absolute base (e.g. `/app/keeper/`) is REJECT with no documented-exception escape hatch — see §9.5. -10. `build.rollupOptions.external` is not exactly every key in the fetched - target-host `imports` object, including currently unused keys. -11. WC: `build.lib` library mode is missing OR `formats: ['es']` is missing. -12. WC: `build.rollupOptions.preserveEntrySignatures` is not `false`. -13. A used exact specifier present in the pinned map is bundled instead of - externalized. -14. A used exact specifier absent from the pinned map is externalized instead - of bundled. This applies to all `@wippy-fe/*` and `primevue/*` subpaths. - -### tsconfig.json (§3.8, §4.7) -15. `strict` is not `true`. -16. `target` is older than `ES2020`. -17. Micro Frontend App: `types` is missing `vite/client` or `@wippy-fe/types-global-proxy`. -18. WC: `types` is missing `vite/client` or `@wippy-fe/proxy`. -19. `vue-tsc` does not exit 0. - -### app.html (§3.3) -20. No `<script data-role="@wippy/scripts">`. -21. Host-less `app.html` does not contain the complete `imports` object fetched - from the same pinned Web Host release. -22. No `<div id="app">` mount. -23. No `<wippy-loading>` (uses custom spinner instead). - -### Bootstrap (§3.4) -24. `app.ts` obtains `config` / `host` / `api` / `on` from anything other than sync `@wippy-fe/proxy` imports (e.g. reaches for `window.$W` / `getWippyApi`, or `await`s to *obtain* a getter). -25. `app.ts` does not provide `HOST_API`, `AXIOS_INSTANCE`, `WIPPY_INSTANCE` injections. -26. `app.ts` resolves initial path from a non-canonical source (must be `config.context?.route ?? '/'`, with documented project-specific extensions). - -### Router (§3.5, §7) -27. An iframe-capable or `auto` page bypasses `@wippy-fe/router` / portable memory routing. Direct `createWebHistory` is permitted only for a documented Fragment-only artifact. -28. Application code reproduces the package-owned memory-history, `afterEach`, `@history`, `navId`, or `setLocalRouter` synchronization protocol. -29. Initial host route comes from browser location instead of `config.context?.route`. -30. No catch-all `/:pathMatch(.*)*` route OR catch-all has no `name`. - -### Proxy & subscriptions (§3.9, §6) -33. Any `instance.on(...)` at module scope (outside `onMounted`). -34. Any `instance.on(...)` whose return value is discarded. -35. Any `onUnmounted` block missing the matching unsubscribe call(s). -36. Any reference to `instance.off(...)` (the method does not exist). -37. Any `window.addEventListener('message', ...)` without matching `removeEventListener` in `onUnmounted`. -38. Any raw `new EventSource(...)`. -39. Any raw `axios.create(...)`. -40. Any `window.confirm(...)`. - -### Styling (§3.7, §5, §4.4) -41. `html, body, #app` set non-zero padding/margin. -42. `styles.css` redefines `--p-content-background`, `--p-text-color`, `--p-content-border-color`, `--p-primary-color`, or `--p-surface-*` at module scope. -42b. ANY child-app `.css` file contains `:root { --p-* … }` or `:root { --<other-host-var> … }` redefinition (§5.1.2). Move to facade theming or per-page YAML `config_overrides.customization.cssVariables`. -43. PrimeVue component tokens are restyled with `!important` in `styles.css`. -43a. A child module duplicates shared PrimeVue appearance in local `.p-*` rules. Move shared appearance to facade `custom_css`; retain module CSS only for justified domain layout or novel structure. -44. Any Vue file uses `var(--p-primary)` (invalid token; must be `--p-primary-color`). -45. Any Vue file uses raw Tailwind color names (`bg-red-*`, `bg-sky-*`, etc.) for semantic meaning. -46. Any hardcoded hex/rgb in Vue source for semantic colors (use `--p-danger-*` etc., or `color-mix()`). -46a. Page renders correctly in only one of `prefers-color-scheme: dark` / `light`. Verify in a browser emulator before claiming the page is shippable (§10.7). -46b. Custom Vue component reimplements something PrimeVue already ships (e.g. custom dropdown when `<Select>` exists, custom modal when `<Dialog>` exists, custom toast when `<Toast>` exists, custom confirm when `<ConfirmDialog>` exists). Use the PrimeVue component, possibly with §5.0 level-1 / level-2 customization. - -### Iconography (§5.6) -46c. Reusable iconography uses raw `<svg>` instead of `<Icon>` from `@iconify/vue`. -46d. Custom icon collection registered via `addCollection()` outside the canonical `app.ts` bootstrap path. -46e. Icon font CSS (Tabler-icons-font, Material Icons font, FontAwesome CSS, etc.) shipped alongside Iconify. - -### Vue/TS hygiene (§3.8) -47. `.vue` file does not use `<script setup lang="ts">`. -48. `defineProps` uses untyped object syntax instead of TS generic. -49. Production code contains `console.log`. - -### Web components (§4) -50. WC root element has padding or margin. -51. WC does not extend `WippyVueElement` or `WippyElement`. -52. WC `static get wippyConfig()` is missing OR doesn't return `propsSchema`/`hostCssKeys`/`inlineCss`. -53. WC `static get vueConfig()` is missing OR doesn't return `rootComponent`. -54. WC entry does not call `define(import.meta.url, ElementClass)` at module level. -55. WC has `wippy.path` (page-only field) or `wippy.proxy` (page-only block). - -### Build pipeline (§8) -55a. Module ships `Makefile` without matching `make.bat` + `make.ps1` wrappers (§8.2). - -### Accessibility (§3.8) -56. An icon-only native button or PrimeVue `<Button>` lacks a stable accessible name such as `aria-label`. -57. A clickable non-interactive element is used as a control. In shipped Vue product UI use the applicable PrimeVue control with keyboard semantics and a stable accessible name; native controls are reserved for explicitly static/non-Vue examples or a documented semantic gap. - ---- - -## 12. Known intentional deviations - -When you knowingly diverge from canonical, record the reason in the project's maintained engineering guidance or audit record. Do not assume a specific agent-instruction filename. Real examples: - -| Deviation | Reason | Acceptable? | -|---|---|---| -| `createPinia()` registered but no `defineStore` yet | Reserved for upcoming stores | BORDERLINE — clean up if no stores planned | -| Native control in a shipped Vue product surface | PrimeVue lacks the required semantics and the exception documents accessibility and design impact | RARELY YES | -| Custom `inlineCssPlugin` in vite.config | Single-file deployment | YES | -| Raw `localStorage.*` for ad-hoc persistence keys | Avoid pinia overhead for one or two keys | DISCOURAGED. Prefer the canonical stack: facade module owns theme; `@wippy-fe/router` factory owns route restoration; `@wippy-fe/pinia-persist` owns durable state. Raw `localStorage` should be a measured exception in a leaf component, not the default. | -| Skip `wippyPagePlugin()` | Want a very-good-reason: e.g. shipping a host-only bundle that explicitly does not support host-less dev | RARELY YES. Default is to include it. Record the reason in maintained project guidance. | - ---- - -## 13. Tooling gotchas - -### 13.1 Wippy CLI port already in use (`:8080`, `:5173`) - -Symptom: `EADDRINUSE` when starting `./wippy.exe run -c`. - -Fix: override the gateway port via the `-o` flag. Examples: - -```bash -# Pick a different port for the wippy gateway: -./wippy.exe run -c -o app:gateway:addr=:8086 - -# Combine multiple overrides — gateway port + facade fe_facade_url default: -./wippy.exe run -c -o app:gateway:addr=:9000 -o wippy.facade:fe_facade_url:default=http://localhost:5173 -``` - -The `-o <module>:<entry>:<property>=<value>` form patches the registry entry's property at boot — no source edits required. To set a *requirement default* (rather than overriding a configured value), use the `:default` suffix on the property name. - -For Vite (`5173`), kill the existing process or choose a different port via `vite --port <n>`. - -### 13.2 Persistent `app.db` - -Symptom: migration on first run succeeds, on second run fails with "table already exists". - -Fix: delete `.wippy/app.db*` between fresh runs. For test harnesses, prefer `:memory:`. - -### 13.3 npm ERESOLVE after `@wippy-fe/*` bump - -Symptom: `npm install` fails with ERESOLVE after bumping one package in the `@wippy-fe/*` family without aligning its peers. - -Fix: delete `node_modules/` AND `package-lock.json`, then `npm install`. - -### 13.4 Importmap drift - -Symptom: `Failed to resolve module specifier 'pinia'`. - -Fix: refresh the pinned target-host import map, use all of its keys as Vite -externals, copy its complete `imports` object into host-less `app.html`, and -bundle any imported exact specifier that is absent. Keep peer dependencies to -the imported npm roots actually expected from the host. See §8.3. - ---- - -## 14. Template validation policy - -Repository templates are examples, not an authority that can override this -contract. Validate each template against the pinned Web Host import map and the -current checklist before calling it compliant. A stale template must be fixed -or explicitly marked non-compliant; the checklist must not grant it an -automatic "gold standard" exception. - ---- - -## Appendix A — Window globals & DOM markers - -Constants exported from `@wippy-fe/shared`: - -| Constant | Value | Who writes | Who reads | -|---|---|---|---| -| `GLOBAL_CONFIG_VAR` | `__WIPPY_APP_CONFIG__` | host entry point | child app, dev-proxy | -| `GLOBAL_PROXY_CONFIG_VAR` | `__WIPPY_PROXY_CONFIG__` | host | dev-proxy boot | -| `GLOBAL_API_PROVIDER` | `__WIPPY_APP_API__` | host | child app | -| `GLOBAL_WEB_COMPONENT_CACHE` | `__WIPPY_WEB_COMPONENT_CACHE__` | wc loader | wc loader | -| `WIPPY_SCRIPTS_DATA_ROLE` | `@wippy/scripts` | author (in `app.html`) | host injects scripts adjacent to it | -| `WIPPY_PACKAGE_DATA_ROLE` | `@wippy/package` | `@wippy-fe/vite-plugin` (build time) | dev-proxy boot | - -Authors should NEVER reference `window.__WIPPY_*` directly — always import from `@wippy-fe/shared` (constants) or use `@wippy-fe/proxy` API helpers. - ---- - -## Appendix B — HostApi method signatures - -```ts -interface HostApi { - toast(opts: ToastMessageOptions): void - confirm(opts: LimitedConfirmationOptions): Promise<boolean> - startChat(token: string, opts?: { sidebar?: boolean }): void - openSession(uuid: string, opts?: { sidebar?: boolean }): void - openArtifact(uuid: string, opts?: { target: 'modal' | 'sidebar' }): void - setContext( - context: Record<string, unknown>, - sessionUUID?: string, - source?: { type: string; uuid: string; instanceUUID?: string }, - ): void - navigate(url: string): void - onRouteChanged(internalRoute: string, navId?: number): void - handleError(code: 'auth-expired' | 'other', error: Record<string, unknown>): void - formatUrl(relativeUrl: string): string - classifyLink(href: string | null | undefined): LinkClassification - layout: LayoutApi - logout(): void -} -``` - -LayoutApi: - -```ts -interface LayoutApi { - readonly snapshot: LayoutSnapshot | null - - resizePanel(panelId: string, size: SizeValue): void - collapsePanel(panelId: string): void - expandPanel(panelId: string): void - openDrawer(panelId: string): void - closeDrawer(panelId: string): void - toggleDrawer(panelId: string): void - movePanel(panelId: string, target: PanelTarget): void - removePanel(panelId: string): void - updatePanel(panelId: string, def: Partial<HostPanelDef>): void - openModal(id: string, def: HostModalDef): void - closeModal(modalId: string): void - addFloating(id: string, def: HostFloatingDef): void - removeFloating(floatingId: string): void - - broadcast(channel: string, payload: unknown): void - send(target: string, channel: string, payload: unknown): void - on(channel: string, handler: (env: BroadcastEnvelope) => void): () => void -} -``` - -`host.layout` is always present (a `LayoutApi` object). Outside managed-layout mode, `host.layout.snapshot` is `null` and all mutation/bus calls are silent no-ops — gate on `host.layout.snapshot` (or `isManaged` from `useWippyLayout`) before mutating, not a null-check on `host.layout`. - ---- - -## Appendix C — `ProxyConfig.injections` reference - -```ts -interface ProxyConfig { - enabled: boolean - injections: { - css: { - themeConfig: boolean // semantic CSS vars - iframe: boolean // default themed scrollbar consistency - primevue: boolean // PrimeVue component CSS - markdown: boolean // markdown typography - customCss: boolean // theming.global.customCSS - customVariables: boolean // theming.global.cssVariables → :root - } - tailwindConfig: boolean // window.tailwind.config - resizeObserver: boolean // report iframe size - preventLinkClicks: boolean // intercept <a> clicks - iconifyIcons: boolean // register iconify-icon WC + icons - refreshWhenVisible: boolean // reload on @visibility(true) - historyPolyfill: boolean // history() stub (always installed) - errorCapture: boolean // unhandledrejection + onerror → host - } -} -``` - -The YAML registry-entry uses top-level `meta.proxy`, while nested keys under -the `injections` wrapper retain lower camelCase, matching frontend -`package.json` and runtime AppConfig. The host deep-merges the YAML over -bundled `wippy.proxy`: -```yaml -meta: - type: view.page - # ... - proxy: - enabled: true - injections: - css: - themeConfig: true - iframe: true - primevue: true - customCss: true - customVariables: true - tailwindConfig: true - iconifyIcons: true -``` - -CSS is applied in this logical order: `themeConfig → primevue/tailwind → iframe → markdown → customVariables → customCss`. The custom variable and CSS layers use the runtime's override mechanism and win over ordinary document stylesheets; do not depend on a particular `<head>` insertion position. - ---- - -## Cross-references - -- [micro-frontend-app.md](./micro-frontend-app.md) — detailed micro-frontend-app authoring guide -- [web-component.md](./web-component.md) — web-component authoring guide -- [proxy-api.md](./proxy-api.md) — full HostApi + instance.on() reference -- [host-less-mode.md](./host-less-mode.md) — host-less boot in detail -- [theming.md](./theming.md) — three-level theming guide -- [build-system.md](./build-system.md) — build pipeline details +The values above show the required shape, not valid evidence. Publication fails +when a changed component or required state has no scenario, a required capture +scope is absent, a referenced image or hash is missing, builds are stale, +unexpected console errors remain, temporary fixture code remains, or the diff +exceeds tolerance without a reviewed design waiver. A waiver records the exact +changed pixels, design reason, reviewer, and affected scenario; it cannot waive +missing captures, console errors, or fixture cleanup. diff --git a/en/frontend/micro-frontends/configuration-casing.md b/en/frontend/micro-frontends/configuration-casing.md new file mode 100644 index 00000000..7ba76769 --- /dev/null +++ b/en/frontend/micro-frontends/configuration-casing.md @@ -0,0 +1,38 @@ +--- +title: "Configuration and Casing" +description: "Casing rules at backend facade, registry, and frontend configuration boundaries." +--- + +# Configuration and Casing + +Casing follows the schema boundary. Never recursively convert a configuration object. + +| Boundary | Rule | Examples | +|---|---|---| +| Backend facade requirement names | top-level `lower_case_with_underscore` | `custom_css`, `css_variables` | +| Registry fields | each field follows its documented registry schema | `base_path`, `entry_point`, `tag_name` | +| Nested frontend configuration carried by backend YAML | preserve lower camelCase | `customCSS`, `themeConfig`, `iconifyIcons` | +| Frontend AppConfig and package metadata | lower camelCase | `configOverrides`, `hostCssKeys` | + +```yaml +config_overrides: + customization: + customCSS: "" + cssVariables: {} + routePrefix: /admin + +proxy: + injections: + css: + themeConfig: true + customCss: true + iframe: true +``` + +Only the backend wrapper keys are snake case in this example. Nested frontend objects are passed through and retain their defined casing. + +## Temporary mountRoute exception + +`meta.mountRoute` is a current backend compatibility bug. The intended backend field is `meta.mount_route`, but existing deployments require `mountRoute` until the backend correction ships. Treat it as one explicit exception, not evidence that registry or backend fields are generally camelCase. + +Compliance must version this exception so it can be removed when the backend schema changes. diff --git a/en/frontend/micro-frontends/custom-composites.md b/en/frontend/micro-frontends/custom-composites.md new file mode 100644 index 00000000..9d91d60d --- /dev/null +++ b/en/frontend/micro-frontends/custom-composites.md @@ -0,0 +1,133 @@ +--- +title: "Custom Composites" +description: "Contract-first exceptions for controls whose required affordance cannot be provided by PrimeVue." +--- + +# Custom Composites + +Custom controls are exceptions, not an alternative component library. + +## Admission test + +A custom control is accepted only when: + +1. PrimeVue cannot provide or compose the intended semantics, interaction, and affordance. +2. The exception records rejected PrimeVue compositions. +3. It names an exact generated PrimeVue sibling contract and contract hash. +4. Every property that sibling contract classifies `shared-runtime` has an exact source mapping. +5. A fixed utility is accepted only when the sibling contract classifies that exact property `platform-invariant`. +6. Novel geometry and behavior are isolated and documented. +7. Accessibility and visual evidence pass. + +Data-shape equivalence is not affordance equivalence. A multi-option `SelectButton` may represent three values but does not look or behave like a sliding three-position toggle. Conversely, do not invent a `positions` prop for `ToggleSwitch`. Build a reviewed custom sibling only when the affordance requirement is real. + +## Module contract + +Store a reviewed exception in module-root `wippy-fe.contract.json`: + +```json +{ + "schemaVersion": "generated-by-selected-contract-tool", + "exceptions": [ + { + "id": "module.control.example", + "source": "src/components/ExampleControl.vue", + "sourceSha256": "generated-from-source", + "semanticRole": "documented-role", + "requiredAffordance": "documented-affordance", + "rejectedPrimeVueCompositions": [ + { + "components": ["SelectButton"], + "reason": "The reviewed sliding affordance cannot be preserved." + } + ], + "visualSibling": { + "component": "ToggleSwitch", + "contractId": "primevue.toggleswitch.portable-appearance", + "contractHash": "generated-from-selected-theme-contract" + }, + "sharedAppearanceMappings": [ + { + "contractProperty": "root.width", + "part": "root", + "selector": ".example-control", + "source": { + "kind": "css-variable", + "name": "--p-toggleswitch-width" + } + } + ], + "platformInvariantUtilities": [], + "moduleLocalProperties": [], + "accessibilityEvidence": { + "manifest": ".local/evidence/accessibility-manifest.json", + "scenarioId": "module.control.example.keyboard", + "resultId": "module.control.example.keyboard.passed", + "build": { + "head": "generated-candidate-commit", + "trackedFrontendDiffSha256": "generated-diff-hash" + } + }, + "visualEvidence": { + "manifest": ".local/evidence/visual-manifest.json", + "scenarioId": "module.control.example.light.default", + "captureId": "module.control.example.light.default.component", + "build": { + "head": "generated-candidate-commit", + "trackedFrontendDiffSha256": "generated-diff-hash" + } + } + } + ] +} +``` + +The values shown are schema placeholders, not valid evidence. The complete +mapping is generated from the selected sibling contract; the one-row excerpt is +not a valid exception by itself. Tooling generates the source and contract +hashes. A changed source hash or sibling-contract hash invalidates review. + +This page defines the normative fields; it is not a JSON Schema and the +documentation checker only proves that this example retains the required +shape. `wippy-fe-compliance` validates a real module contract against the +selected theme manifest, verifies the hashes and complete property set, and +checks that every evidence reference resolves to the named passing result or +capture from the same candidate build. Accessibility evidence binds the +component `sourceSha256`, hashed files, zero unexpected console errors, and a +passed result. Visual evidence binds canonical before/after/diff files, hashes, +recomputed metrics and disposition, and the matching candidate build. A +string, missing file, missing scenario/result/capture, stale build hash, +`pending`, or unreviewed result does not satisfy the evidence requirement. + +`platformInvariantUtilities` and `moduleLocalProperties` may be empty. Never +invent `gap-2`, `w-10`, `rounded-md`, or another fixed utility merely to make a +contract field nonempty. In particular, a ToggleSwitch sibling cannot relabel +width, height, radius, focus geometry, or motion as invariant when its selected +sibling contract classifies those properties `shared-runtime`. + +The sibling manifest classifies properties as: + +- `shared-runtime`: every custom sibling maps and consumes the published token + or runtime-backed semantic utility. +- `platform-invariant`: a fixed value is permitted only for this exact + property. +- `implementation-private`: internal PrimeVue mechanics do not become + requirements for a custom sibling. + +If the required runtime semantic does not exist, fix the shared theme contract first. Never copy the current sibling dimensions or invent a token name. + +`sharedAppearanceMappings` is exhaustive, not illustrative: it contains exactly +one mapping for every `shared-runtime` property in the selected sibling +contract, no additional property IDs, the contract part, a stable module +selector, and the exact published source kind and name. Compliance tooling uses +the selector, part, CSS property, and published source to prove the mapping +structurally with PostCSS; a token name in a comment or unrelated selector does +not count. A Tailwind-backed mapping also records unique, exact +`utilityClasses`; after normalization that set must equal the selected sibling +contract source set. `platformInvariantUtilities` contains +`{ "contractProperty": "...", "utility": "..." }` records whose utility equals +the selected sibling contract source. `moduleLocalProperties`, when nonempty, +contains structured property IDs and review reasons rather than a free-form CSS +bag. + +No shared `@wippy-fe/ui` package is created for a single exception. Promotion becomes eligible only after a second independent consumer proves the same behavior and portability requirements. diff --git a/en/frontend/micro-frontends/host-less-mode.md b/en/frontend/micro-frontends/host-less-mode.md index f0da7166..bd59653f 100644 --- a/en/frontend/micro-frontends/host-less-mode.md +++ b/en/frontend/micro-frontends/host-less-mode.md @@ -58,8 +58,8 @@ This isn't an accident or an afterthought. It is what makes: Every canonical app's `app.html` ships with **one** script tag that decides the boot path at load time: This is an abbreviated body/boot example. Insert the complete valid import-map -script from [Compliance Checklist §3.3](./compliance-checklist.md#33-apphtml), -updated from the fetched response when the pinned Web Host tag changes. +response described by the [Import-map snapshot algorithm](./build-system.md#import-map-snapshot-algorithm), +updated when the pinned Web Host tag changes. ```html <!-- URL MUST include a release-tag segment: https://web-host.wippy.ai/<release-tag>/dev-proxy.js --> @@ -100,8 +100,9 @@ curl.exe -fsS "https://web-host.wippy.ai/<release-tag>/import-map.json" -o impor Set the text of the `app.html` `<script type="importmap">` element to the fetched JSON response verbatim. Do not put comments, ellipsis placeholders, or hand-written substitutions inside that JSON. The -[compliance checklist §3.3](./compliance-checklist.md#33-apphtml) contains a -valid example for the currently verified release. +The [Build and Dependency Contract](./build-system.md#import-map-snapshot-algorithm) +defines the snapshot and provenance requirements; the fetched release response +provides the exact `imports` object. Conventions: - Put **every fetched key** in Rollup externals, including currently unused keys. diff --git a/en/frontend/micro-frontends/micro-frontend-app.md b/en/frontend/micro-frontends/micro-frontend-app.md index b2219199..1a22f2d7 100644 --- a/en/frontend/micro-frontends/micro-frontend-app.md +++ b/en/frontend/micro-frontends/micro-frontend-app.md @@ -1,624 +1,56 @@ --- -title: "Micro Frontend App (`view.page`)" -description: "A Wippy micro frontend app is a Vue 3 SPA bundled into a standalone HTML artifact and loaded by the host inside an iframe. The iframe has no knowledge…" +title: "Page Recipe" +description: "A portable view.page recipe with supported routing, theme delivery, dependencies, and build ownership." --- -# Micro Frontend App (`view.page`) +# Page Recipe -A Wippy micro frontend app is a Vue 3 SPA bundled into a standalone HTML artifact and loaded by the host inside an iframe. The iframe has no knowledge of the surrounding page — it communicates with the host exclusively through `@wippy-fe/proxy`. +A page is a Vite-built application rendered in an `about:srcdoc` iframe. Its route and host context come from Wippy AppConfig and packages, not from browser location. -> **Isolation is mandatory.** The bundle has zero hardcoded assumptions about where it is served. `vite.config.ts` sets `base: ''`, no `outDir` is hardcoded in config, and the serving path is declared in the BE-side `view.page` registry entry — not in the package itself. The same built artifact ships unchanged to any Wippy instance. +## Required setup -## Project structure +1. Register a `view.page` and its serving filesystem/router entries. +2. Enable required CSS delivery. Keep the `iframe` CSS block enabled for default scrollbar consistency. +3. Use `@wippy-fe/router` for Vue routing. +4. Install PrimeVue and the Wippy PrimeVue plugin when the page renders any PrimeVue-like control. +5. Use the shared Wippy Tailwind preset when the page authors Tailwind utilities. +6. Generate externals from the pinned Web Host import-map snapshot. +7. Build into the deployment-selected output directory. -``` -my-app/ -├── package.json -├── app.html # HTML entry point (Vite input) -├── vite.config.ts -├── tsconfig.json -├── tailwind.config.ts # If using Tailwind -├── postcss.config.js # Required when using Tailwind -└── src/ - ├── app.ts # Bootstrap — @wippy-fe/proxy, Vue setup, mount - ├── constants.ts # InjectionKey symbols - ├── types.ts # HostApi / ProxyApiInstance type aliases - ├── styles.css # Base styles (html, body, #app) - ├── tailwind.css # @tailwind directives (if using Tailwind) - ├── app/ - │ └── app.vue # Root component (layout, router-view) - ├── router/ - │ └── index.ts # createAppRouter factory - ├── pages/ # Route-level components - ├── components/ # Shared/reusable components - ├── composables/ # useHost(), useApi() (or import from @wippy-fe/proxy directly) - ├── stores/ # Pinia stores - └── types/ # Additional TypeScript types -``` - -Use kebab-case for all file names (`recent-sessions.vue`, `user-profile.vue`). - -## `package.json` — the `wippy` block - -```json -{ - "name": "@myorg/app-my-dashboard", - "version": "1.0.0", - "specification": "wippy-component-1.0", - "title": "My Dashboard", - "description": "Dashboard application", - "files": ["dist/", "src/", "package.json"], - "dependencies": { - "@wippy-fe/pinia-persist": "^0.0.46", - "@wippy-fe/router": "^0.0.46", - "@wippy-fe/theme": "^0.0.46" - }, - "devDependencies": { - "@wippy-fe/shared": "^0.0.46", - "@wippy-fe/vite-plugin": "^0.0.46", - "@wippy-fe/types-global-proxy": "^0.0.46", - "@vitejs/plugin-vue": "^5.0.0", - "autoprefixer": "^10.4.0", - "postcss": "^8.4.0", - "primevue": "^4.3.3", - "tailwindcss": "3", - "typescript": "^5.0.0", - "vite": "^6.0.0", - "vue": "^3.5.0", - "vue-router": "^4.0.0", - "vue-tsc": "^2.0.0" - }, - "peerDependencies": { - "@iconify/vue": "^5.0.0", - "@wippy-fe/proxy": "^0.0.46", - "axios": "^1.0.0", - "luxon": "^3.5.0", - "pinia": "^2.1.0", - "vue": "^3.5.0", - "vue-router": "^4.0.0" - }, - "wippy": { - "type": "page", - "title": "My Dashboard", - "icon": "tabler:chart-bar", - "order": 200, - "path": "dist/app.html", - "proxy": { - "enabled": true, - "injections": { - "css": { - "themeConfig": true, - "iframe": true, - "primevue": true, - "markdown": true, - "customCss": true, - "customVariables": true - }, - "tailwindConfig": false, - "resizeObserver": false, - "preventLinkClicks": false, - "iconifyIcons": false, - "refreshWhenVisible": false - } - }, - "scripts": { - "build": "build", - "debug": "build:debug" - } - }, - "scripts": { - "build": "vite build", - "build:debug": "vite build --mode development", - "dev": "vite build --watch" - } -} -``` - -### Field reference - -| Field | Required | Description | -|---|---|---| -| `specification` | Yes | Must be `"wippy-component-1.0"`. Tells the platform this is a Wippy package. | -| `wippy.type` | Yes | Must be `"page"` for micro frontend apps. | -| `wippy.title` | Recommended | Display name shown in the host navigation menu. | -| `wippy.icon` | Recommended | Tabler icon name (e.g. `"tabler:chart-bar"`). Used in navigation. | -| `wippy.order` | Optional | Sort position in the navigation menu (lower = earlier). | -| `wippy.path` | Yes | Path to the built HTML entry file, relative to the package root. Typically `"dist/app.html"`. | -| `wippy.proxy.enabled` | Yes | Must be `true` for the host's proxy system to activate for this iframe. | -| `wippy.proxy.injections` | Yes | Controls which CSS and behaviours the host injects into the iframe. | -| `wippy.scripts.build` | Yes | Maps to the npm script name for production builds. | -| `wippy.scripts.debug` | Recommended | Maps to the npm script name for development builds (with source maps). | - -**Package naming convention:** `@<namespace>/<type>-<description>` where type is `app` for pages. Examples: `@acme/app-analytics-dashboard`, `@myorg/app-user-settings`. - -**Externalization:** fetch `<fe_facade_url>/import-map.json` once during development and put every `imports` key in Rollup externals. Re-fetch when the tag changes or a new dependency is added. `peerDependencies` contain only imported npm package roots that the pinned map supplies; absent imports remain regular dependencies and are bundled. - -### Proxy injections - -The iframe proxy enables most injections when a package omits explicit settings. Page packages should still declare the values below deliberately; the table shows recommended explicit values for a Vite micro frontend app, not the runtime fallback defaults. - -| Key | Effect | Recommended explicit value | -|---|---|---| -| `css.themeConfig` | Injects CSS custom properties (`--p-primary-*`, `--p-surface-*`, etc.) | `true` | -| `css.iframe` | Required default themed scrollbar styling; `iframe` is a historical name | `true` | -| `css.primevue` | PrimeVue component styles and Tailwind utilities | `true` for this full-UI template; disable only for an artifact with no PrimeVue-like UI | -| `css.markdown` | Styles for rendered markdown | `true` | -| `css.customCss` | Host-level custom CSS overrides | `true` | -| `css.customVariables` | Host-level CSS variable overrides | `true` | -| `tailwindConfig` | Tailwind Play CDN runtime config | `false` | -| `resizeObserver` | Reports body-size changes to the parent frame | `false` | -| `preventLinkClicks` | Intercept `<a>` clicks and route through host | `false` — enable if you don't implement a custom router | -| `iconifyIcons` | Iconify icon data from host | `false` — set `true` if using Iconify CDN web component | - -## `app.html` — the entry point - -Vite takes `app.html` as its build input. The file serves two purposes: it is the production iframe document after build, and it boots the app standalone during local development via `dev-proxy.js`. - -This abbreviated shell omits the import-map body. Copy the complete valid -`<script type="importmap">` block from -[Compliance Checklist §3.3](./compliance-checklist.md#33-apphtml), or replace -that block with the complete response fetched for your pinned Web Host tag. - -```html -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset="UTF-8"> - <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <title>My App - - - - - -
- -
- - - -``` - -**The `data-role="@wippy/scripts"` attribute is the switchpoint.** When the host loads this page, it strips the ` -``` - -## Host API — common calls - -The `host` object exposes platform-level actions. Use these in preference to browser APIs or PrimeVue service equivalents: - -```typescript -// Show a toast notification (preferred over PrimeVue ToastService — -// toast renders in the parent frame, not clipped by the iframe bounds) -host.toast({ severity: 'success', summary: 'Saved', detail: 'Changes saved.' }) - -// Confirmation dialog (preferred over PrimeVue ConfirmationService) -const confirmed = await host.confirm({ - message: 'Delete this item?', - header: 'Confirm', - icon: 'tabler:trash', -}) - -// Navigate to a different host-level page (outside this app's router) -host.navigate('/c/other-page-id') - -// Open a chat session in the sidebar -host.startChat(agentToken, { sidebar: true }) - -// Associate context data with the current or a specific chat session -host.setContext({ currentPage: 'dashboard', selectedItems: [1, 2] }, sessionUUID) - -// Sign the user out -host.logout() -``` - -## Pinia and state persistence - -Install Pinia in `app.ts` as shown above. To persist store state across iframe reloads (the iframe is destroyed and recreated on navigation in some host configurations), use `@wippy-fe/pinia-persist`. - -Bundle `@wippy-fe/pinia-persist` unless the exact target host import map explicitly supplies that specifier. - -```typescript -// src/stores/my-store.ts -import { defineStore } from 'pinia' -import { ref } from 'vue' - -export const useMyStore = defineStore('my-store', () => { - const items = ref([]) - const selectedId = ref(null) - return { items, selectedId } -}, { - wippyPersist: true, // Persist all state, scoped to this page's UUID -}) -``` - -Options for `wippyPersist`: - -| Value | Behaviour | -|---|---| -| `true` | Persist all state keys, scoped to the current page UUID | -| `{ pick: ['key1', 'key2'] }` | Persist only the listed keys | -| `{ debounce: 500 }` | Debounce saves by 500 ms (useful for high-frequency updates) | -| `{ scope: 'my-key' }` | Override the scope key (auto-prefixed with `@custom:`) | - -State is saved on store mutation (debounced), on `@visibility:false`, and on `window.unload`. It is hydrated asynchronously on store creation via `preloadWippyState()` called in `app.ts`. - -## Listening to platform events +Verify the exact exported signatures against the selected package version. Do not create a local router synchronization layer. -Import `on` from `@wippy-fe/proxy` and call `on(pattern, callback)` to subscribe to platform events. The return value is an unsubscribe function — always call it in `onUnmounted`. +## Theme injection -```vue - +```text +npm run build -- --outDir --emptyOutDir ``` -## Example page component - -A minimal page that fetches data from the backend and renders a list: - -```vue - - - - -``` - -`` and `` are custom elements registered by the host's `loading.js` script. They render themed fullscreen states and require no import. - -## `src/app/app.vue` — root component - -The root component provides the application shell. `` renders the active page component. - -```vue - - - -``` - -Note that micro frontend apps control their full viewport — root-level padding on `
` is acceptable here, unlike web components where the host controls outer spacing. - -## `src/styles.css` - -```css -html, body { - height: 100%; - margin: 0; - background: transparent; -} - -#app { - height: 100%; -} - -/* Iconify inline icon fallback size */ -svg.iconify { - display: inline-block; - width: 1em; - height: 1em; -} -``` - -## `wippy-meta.json` - -`wippyPagePlugin()` in `vite.config.ts` emits `wippy-meta.json` beside `app.html` in the actual Vite output directory. This file is the canonical source of identity and presentation metadata for the views API. Do not hand-author it — let the plugin generate it. - -For the current contract (`wippy/views` 1.0.31 or newer with the coherent `@wippy-fe/vite-plugin` family), this file is required in the served output. Do not rely on historical synthesis fallbacks. - -## Testing without the host +`vite.config.ts` keeps relative asset behavior and does not hardcode deployment `outDir`. -To develop and test the app without a running Wippy instance, use host-less mode. The `dev-proxy.js` script (referenced in `app.html`) installs the proxy runtime so `@wippy-fe/proxy` imports resolve, letting the app boot normally in a plain browser tab. +Do not invoke the underlying package-manager or Vite build command directly. +On Windows, invoke `make.bat`; it delegates to the target's `make.ps1` +implementation. -See [host-less-mode.md](./host-less-mode.md) for setup, the dev-proxy stub contract, and patterns for testing components in isolation. +See [Build and Dependency Contract](./build-system.md), [Platform Topology](../platform-topology.md), and [Configuration and Casing](./configuration-casing.md). diff --git a/en/frontend/micro-frontends/overview.md b/en/frontend/micro-frontends/overview.md index 7f89403e..049c5df8 100644 --- a/en/frontend/micro-frontends/overview.md +++ b/en/frontend/micro-frontends/overview.md @@ -42,11 +42,12 @@ Build a web component: Both: - [Host-less Mode](./host-less-mode.md) — develop and test without running the full Web Host -- [Compliance Checklist](./compliance-checklist.md) — MUST/SHOULD rules before shipping +- [Compliance Rule Index](./compliance-checklist.md) — canonical rule owners and deterministic gates - [Debugging](./debugging.md) — symptom-first guide for the most common failure scenarios ## Prerequisites - Wippy backend module with `wippy/views` declared as a dependency (see [Views](../../framework/views.md)) - `wippy/facade` for the Web Host entry point (see [Facade Entry Point](../web-host/entry-point.md)) -- Node.js 20+, pnpm or npm, Vite 6 +- Node.js 22 or newer and Vite 7, as declared by the selected Web Host source; + re-check its package when the target release changes diff --git a/en/frontend/micro-frontends/proxy-api.md b/en/frontend/micro-frontends/proxy-api.md index fff91969..4263dcd5 100644 --- a/en/frontend/micro-frontends/proxy-api.md +++ b/en/frontend/micro-frontends/proxy-api.md @@ -105,6 +105,7 @@ interface ChildAppConfig { axiosDefaults?: Partial routePrefix?: string apiRoutes?: Record + themeMode?: 'auto' | 'light' | 'dark' theming: { global?: { customCSS?: string @@ -142,6 +143,59 @@ import { host } from '@wippy-fe/proxy' --- +### `host.setThemeMode(mode)` and `host.getThemeMode()` + +Theme mode is host state carried by AppConfig. Switch it only through the +public proxy API: + +```typescript +import { host, on } from '@wippy-fe/proxy' + +async function setThemeMode(mode: 'auto' | 'light' | 'dark') { + await new Promise((resolve, reject) => { + const unsubscribe = on('@theme', (appliedMode) => { + if (appliedMode !== mode) return + unsubscribe() + const currentMode = host.getThemeMode() + if (currentMode !== mode) { + reject(new Error(`Theme propagation mismatch: ${currentMode}`)) + return + } + resolve() + }) + + // Subscribe before the command so a fast propagation event cannot be lost. + host.setThemeMode(mode) + }) +} + +await setThemeMode('dark') +``` + +The accepted modes are `auto`, `light`, and `dark`. `auto` follows the +operating-system preference. A change is applied to the host, written back to +AppConfig, broadcast to live page iframes and web components, and forwarded +through nested Wippy containers. Subscribe to `@theme` when code needs to wait +for the applied child state. Release the subscription during component +unmount. + +The host does not own persistence. The embedding facade listens for the host +theme-change event and persists the user choice as described in +[Theme Persistence](../web-host/theme-persistence.md). + +Do not add or remove `w-theme-dark` / `w-theme-light` classes, call the internal +`applyThemeMode`, mutate AppConfig stores, synthesize proxy messages, or use +`window.getWippyApi`. Those are Web Host implementation details, not application +or browser-test APIs. Runtime tests must exercise `host.setThemeMode()`, wait +for the propagated `@theme` event, and verify `host.getThemeMode()` before +capturing appearance. AppConfig is the host-to-child transport; do not mutate +its internal store or rely on an earlier imported config snapshot as the +completion signal. + +There is no `host.applyTheme()` method. + +--- + ### `host.startChat(agentToken, options?)` Opens a new chat session using the provided agent start token. diff --git a/en/frontend/micro-frontends/quickstart.md b/en/frontend/micro-frontends/quickstart.md index d8b1e907..603a4309 100644 --- a/en/frontend/micro-frontends/quickstart.md +++ b/en/frontend/micro-frontends/quickstart.md @@ -7,7 +7,7 @@ description: "Two end-to-end examples — a Micro Frontend App (Vue) and a Web C Two end-to-end examples — a **Micro Frontend App** (Vue) and a **Web Component** (Vue) — taken from the public [`wippyai/app`](https://github.com/wippyai/app) repository. Each shows the minimal files, how to register the artifact with the backend, and how to build it. Follow the links to the repo for the complete, runnable source, and to the deep-dive docs for every option. -**Prerequisites:** a Wippy backend with the [`wippy/views`](../../framework/views.md) and [`wippy/facade`](../../framework/facade.md) modules wired up, Node 20+, Vite 6, and the current coherent `@wippy-fe/*` package family. Fetch the target Web Host `import-map.json`, externalize every listed key including unused ones, and bundle an imported exact specifier only when it is absent. See [Build System](./build-system.md) for the toolchain. +**Prerequisites:** a Wippy backend with the [`wippy/views`](../../framework/views.md) and [`wippy/facade`](../../framework/facade.md) modules wired up, Node.js 22 or newer, Vite 7, and the current coherent `@wippy-fe/*` package family selected for the target Web Host. These toolchain requirements come from the selected Web Host package; verify them again when that package changes. Fetch the target Web Host `import-map.json`, externalize every listed key including unused ones, and bundle an imported exact specifier only when it is absent. See [Build System](./build-system.md) for the toolchain. --- @@ -71,7 +71,12 @@ export function createMainApp() { mountRoute: /admin/:part(.*)* ``` -Build it into the served directory with `npm run build -- --outDir --emptyOutDir`, serve the output where `url + base_path` points, and the host renders it at `/admin`. The module's `Makefile` and `make.ps1` must run that exact build shape; `make.bat` is only a shim that invokes `make.ps1`. Full walkthrough: [Micro Frontend App](./micro-frontend-app.md). +Invoke the module's Make target to build into the served directory, then serve +the output where `url + base_path` points; the host renders it at `/admin`. +The Makefile recipe uses +`npm run build -- --outDir --emptyOutDir`; `make.ps1` +implements the same target for Windows, and `make.bat` only invokes +`make.ps1`. Full walkthrough: [Micro Frontend App](./micro-frontend-app.md). --- diff --git a/en/frontend/micro-frontends/tailwind-contract.md b/en/frontend/micro-frontends/tailwind-contract.md new file mode 100644 index 00000000..2c265983 --- /dev/null +++ b/en/frontend/micro-frontends/tailwind-contract.md @@ -0,0 +1,842 @@ +--- +title: "Tailwind Contract" +description: "The difference between utility names, compiled values, runtime-backed utilities, and the portable public contract." +--- + +# Tailwind Contract + +“Tailwind token” is ambiguous. Use these four terms instead. + +| Layer | Example | Theme behavior | +|---|---|---| +| Utility name | `px-3`, `rounded-md`, `bg-primary` | Source vocabulary only | +| Compile-time Tailwind value | `px-3` emits a fixed spacing value | Embedded in the module bundle | +| Runtime-backed utility | `bg-primary` emits a reference to a public `--p-*` variable | Responds to facade runtime theme changes | +| Public portable contract | A deliberately documented Wippy token or semantic utility | Stable for supported portable consumers | + +Tailwind 3 is a zero-runtime compiler. Do not infer runtime behavior from a utility name; inspect the emitted declaration. + +## Runtime semantic utilities + +The generated utility catalogue is the authority for exact mappings. It classifies current primary, surface, severity, text, content, highlight, and radius utilities by their emitted CSS and public variable dependency. + +Examples of the intended categories include semantic colors, content borders, muted text, and `rounded-border` when the generated source confirms its mapping. An entry appears here only when generated from the selected preset and package versions. + +## Compile-time baseline + +The generated catalogue separately records spacing, sizing, default radii, font sizes, shadows, transition durations, and timing functions that compile to constants. + +> Build-time baseline. This value is embedded in the module bundle and does not react to a facade theme change. + +Compile-time values are valid for properties classified `platform-invariant`. They are insufficient for a property required to track a PrimeVue sibling under another facade theme. + +`rounded-md` and `rounded-border` are not equivalent contracts even if they currently resolve to the same number: one is a compiled default and the other is runtime-backed. Equal current values also do not prove equal semantic roles. + +## Protected mappings + +Modules may extend the shared preset. They must not redefine protected Wippy meanings for: + +- Primary and surface families. +- Severity families. +- Text, content, and highlight semantics. +- Published portable-control semantics. + +Compliance resolves the actual module Tailwind configuration and rejects incompatible replacement of protected mappings. + +## Custom siblings + +A portable custom sibling may use: + +- Runtime-backed semantic utilities listed in the generated catalogue. +- Direct public variables listed in the selected token manifest. +- Compile-time utilities for properties explicitly classified `platform-invariant`. +- Module-local utilities for genuinely novel structure. + +It may not copy fixed dimensions, radii, or durations for properties expected to track its PrimeVue sibling. If no public runtime semantic exists, record a theme-contract gap; do not invent a utility or token. + +## Generated utility catalogue + +The checked-in snapshot is generated from: + +- The exact Tailwind version selected by `@wippy-fe/theme`. +- The exact `tailwindcss-primeui` version. +- Wippy’s shared `tailwind.config.ts`. +- Wippy extensions. + +Each generated row contains the utility, emitted property, resolved value, runtime dependency, intended use, allowed consumer, stability, package compatibility tuple, and source hashes. + + +Generated from @wippy-fe/theme 0.0.46. Every representative mapping below is checked against CSS compiled by Tailwind 3.4.19 with tailwindcss-primeui 0.6.1. + +Source hashes: theme contract `853a01257988861e208b6f7523de25cd329717763d064e4f2c5920cff7f7778a`; theme config `129f1591fd657416b75e913f554329924bade319c38e62f5b72dcc5f72bd8295`; Tailwind config `f1e862105254f082a78823ea685e3c6dc3ff5822516b7434a1e1141c976adc1d`; reference theme sources `aura/index.mjs=d1a1a574cf1a15aad8aee4cb3fa169aa97bf4029e9f858b84245e7f0b933d5ca; aura/base/index.mjs=9fec80a7ffbd5fb0229da666c1472c27c9a0a6a7ef3bb0a84bd7b070601e4198; aura/inputtext/index.mjs=5c5a4af9bacf0d585120b119bb7bfb02c7deedd9714b131d7009ff6e95f818e8; aura/toggleswitch/index.mjs=1e068fd0ede48eeeca4d10571940d65dadb3450b2ee51a39d09b33dda9da6e66; aura/button/index.mjs=44d8fd7f7ae163ce2653de8c6eb8af097fc453b4c60f702fcf76845be6ec9393`. + +### Runtime-backed semantic utilities + +| Utility | CSS property | Resolved value | Runtime dependency | Classification | Allowed consumer | Stability | Intended use | +|---|---|---|---|---|---|---|---| +| `bg-danger-500` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-danger-500) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-danger-500 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `bg-emphasis` | background / color | `var(--p-content-hover-background) / var(--p-content-hover-color)` | --p-content-hover-background, --p-content-hover-color | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | public | Hovered or emphasized content | +| `bg-help-500` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-help-500) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-help-500 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `bg-highlight` | background / color | `var(--p-highlight-background) / var(--p-highlight-color)` | --p-highlight-background, --p-highlight-color | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | public | Selected or highlighted content | +| `bg-highlight-emphasis` | background / color | `var(--p-highlight-focus-background) / var(--p-highlight-focus-color)` | --p-highlight-focus-background, --p-highlight-focus-color | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | public | Focused highlighted content | +| `bg-info-500` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-info-500) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-info-500 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `bg-primary` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-primary-color) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-primary-color | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | public | Default primary action and emphasis color | +| `bg-primary-emphasis` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-primary-hover-color) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-primary-hover-color | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | public | Primary hover or emphasis state | +| `bg-success-500` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-success-500) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-success-500 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `bg-surface-0` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-surface-0) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-surface-0 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `bg-surface-100` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-surface-100) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-surface-100 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `bg-surface-200` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-surface-200) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-surface-200 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `bg-surface-300` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-surface-300) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-surface-300 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `bg-surface-400` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-surface-400) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-surface-400 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `bg-surface-50` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-surface-50) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-surface-50 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `bg-surface-700` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-surface-700) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-surface-700 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `bg-surface-950` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-surface-950) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-surface-950 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `bg-warn-500` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-warn-500) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-warn-500 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `border-danger-200` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-danger-200) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-danger-200 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `border-danger-400` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-danger-400) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-danger-400 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `border-danger-500` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-danger-500) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-danger-500 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `border-help-200` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-help-200) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-help-200 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `border-help-500` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-help-500) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-help-500 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `border-info-200` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-info-200) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-info-200 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `border-info-500` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-info-500) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-info-500 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `border-primary` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-primary-color) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-primary-color | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `border-primary-200` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-primary-200) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-primary-200 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `border-success-200` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-success-200) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-success-200 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `border-success-500` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-success-500) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-success-500 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `border-surface` | border-color | `var(--p-content-border-color)` | --p-content-border-color | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | public | Shared content and control borders | +| `border-surface-100` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-surface-100) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-surface-100 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `border-surface-200` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-surface-200) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-surface-200 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `border-surface-300` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-surface-300) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-surface-300 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `border-surface-700` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-surface-700) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-surface-700 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `border-surface-950` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-surface-950) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-surface-950 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `border-warn-200` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-warn-200) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-warn-200 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `border-warn-500` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-warn-500) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-warn-500 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:bg-danger-400` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-danger-400) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-danger-400 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:bg-help-400` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-help-400) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-help-400 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:bg-info-400` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-info-400) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-info-400 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:bg-success-400` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-success-400) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-success-400 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:bg-surface-0` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-surface-0) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-surface-0 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:bg-surface-300` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-surface-300) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-surface-300 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:bg-surface-400` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-surface-400) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-surface-400 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:bg-surface-600` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-surface-600) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-surface-600 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:bg-surface-700` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-surface-700) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-surface-700 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:bg-surface-800` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-surface-800) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-surface-800 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:bg-surface-900` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-surface-900) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-surface-900 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:bg-warn-400` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-warn-400) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-warn-400 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:border-danger-300` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-danger-300) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-danger-300 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:border-danger-400` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-danger-400) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-danger-400 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:border-danger-700` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-danger-700) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-danger-700 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:border-help-400` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-help-400) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-help-400 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:border-help-700` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-help-700) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-help-700 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:border-info-400` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-info-400) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-info-400 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:border-info-700` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-info-700) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-info-700 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:border-primary-700` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-primary-700) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-primary-700 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:border-success-400` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-success-400) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-success-400 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:border-success-700` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-success-700) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-success-700 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:border-surface-100` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-surface-100) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-surface-100 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:border-surface-500` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-surface-500) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-surface-500 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:border-surface-600` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-surface-600) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-surface-600 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:border-surface-700` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-surface-700) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-surface-700 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:border-surface-800` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-surface-800) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-surface-800 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:border-warn-400` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-warn-400) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-warn-400 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:border-warn-700` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-warn-700) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-warn-700 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:disabled:bg-surface-700` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-surface-700) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-surface-700 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:disabled:text-surface-400` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-surface-400) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-surface-400 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:active:bg-danger-200` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-danger-200) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-danger-200 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:active:bg-danger-400/15` | background-color | `color-mix(in srgb, var(--p-danger-400) calc(100% * 0.15), transparent)` | --p-danger-400 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:active:bg-help-200` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-help-200) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-help-200 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:active:bg-help-400/15` | background-color | `color-mix(in srgb, var(--p-help-400) calc(100% * 0.15), transparent)` | --p-help-400 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:active:bg-info-200` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-info-200) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-info-200 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:active:bg-info-400/15` | background-color | `color-mix(in srgb, var(--p-info-400) calc(100% * 0.15), transparent)` | --p-info-400 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:active:bg-primary/15` | background-color | `color-mix(in srgb, var(--p-primary-color) calc(100% * 0.15), transparent)` | --p-primary-color | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:active:bg-success-200` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-success-200) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-success-200 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:active:bg-success-400/15` | background-color | `color-mix(in srgb, var(--p-success-400) calc(100% * 0.15), transparent)` | --p-success-400 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:active:bg-surface-200` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-surface-200) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-surface-200 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:active:bg-surface-600` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-surface-600) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-surface-600 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:active:bg-surface-700` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-surface-700) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-surface-700 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:active:bg-warn-200` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-warn-200) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-warn-200 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:active:bg-warn-400/15` | background-color | `color-mix(in srgb, var(--p-warn-400) calc(100% * 0.15), transparent)` | --p-warn-400 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:active:border-danger-200` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-danger-200) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-danger-200 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:active:border-danger-700` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-danger-700) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-danger-700 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:active:border-help-200` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-help-200) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-help-200 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:active:border-help-700` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-help-700) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-help-700 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:active:border-info-200` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-info-200) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-info-200 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:active:border-info-700` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-info-700) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-info-700 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:active:border-primary-700` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-primary-700) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-primary-700 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:active:border-success-200` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-success-200) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-success-200 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:active:border-success-700` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-success-700) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-success-700 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:active:border-surface-300` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-surface-300) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-surface-300 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:active:border-surface-500` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-surface-500) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-surface-500 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:active:border-surface-600` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-surface-600) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-surface-600 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:active:border-surface-700` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-surface-700) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-surface-700 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:active:border-warn-200` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-warn-200) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-warn-200 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:active:border-warn-700` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-warn-700) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-warn-700 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:active:text-danger-400` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-danger-400) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-danger-400 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:active:text-danger-950` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-danger-950) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-danger-950 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:active:text-help-400` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-help-400) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-help-400 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:active:text-help-950` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-help-950) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-help-950 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:active:text-info-400` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-info-400) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-info-400 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:active:text-info-950` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-info-950) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-info-950 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:active:text-primary` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-primary-color) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-primary-color | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:active:text-success-400` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-success-400) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-success-400 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:active:text-success-950` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-success-950) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-success-950 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:active:text-surface-0` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-surface-0) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-surface-0 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:active:text-surface-100` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-surface-100) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-surface-100 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:active:text-surface-400` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-surface-400) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-surface-400 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:active:text-surface-950` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-surface-950) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-surface-950 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:active:text-warn-400` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-warn-400) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-warn-400 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:active:text-warn-950` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-warn-950) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-warn-950 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:focus:border-primary` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-primary-color) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-primary-color | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:hover:bg-danger-300` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-danger-300) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-danger-300 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:hover:bg-danger-400/5` | background-color | `color-mix(in srgb, var(--p-danger-400) calc(100% * 0.05), transparent)` | --p-danger-400 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:hover:bg-help-300` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-help-300) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-help-300 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:hover:bg-help-400/5` | background-color | `color-mix(in srgb, var(--p-help-400) calc(100% * 0.05), transparent)` | --p-help-400 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:hover:bg-info-300` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-info-300) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-info-300 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:hover:bg-info-400/5` | background-color | `color-mix(in srgb, var(--p-info-400) calc(100% * 0.05), transparent)` | --p-info-400 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:hover:bg-primary/5` | background-color | `color-mix(in srgb, var(--p-primary-color) calc(100% * 0.05), transparent)` | --p-primary-color | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:hover:bg-success-300` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-success-300) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-success-300 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:hover:bg-success-400/5` | background-color | `color-mix(in srgb, var(--p-success-400) calc(100% * 0.05), transparent)` | --p-success-400 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:hover:bg-surface-100` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-surface-100) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-surface-100 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:hover:bg-surface-700` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-surface-700) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-surface-700 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:hover:bg-surface-800` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-surface-800) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-surface-800 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:hover:bg-warn-300` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-warn-300) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-warn-300 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:hover:bg-warn-400/5` | background-color | `color-mix(in srgb, var(--p-warn-400) calc(100% * 0.05), transparent)` | --p-warn-400 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:hover:border-danger-300` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-danger-300) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-danger-300 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:hover:border-danger-700` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-danger-700) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-danger-700 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:hover:border-help-300` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-help-300) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-help-300 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:hover:border-help-700` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-help-700) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-help-700 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:hover:border-info-300` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-info-300) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-info-300 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:hover:border-info-700` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-info-700) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-info-700 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:hover:border-primary-700` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-primary-700) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-primary-700 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:hover:border-success-300` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-success-300) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-success-300 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:hover:border-success-700` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-success-700) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-success-700 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:hover:border-surface-200` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-surface-200) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-surface-200 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:hover:border-surface-500` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-surface-500) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-surface-500 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:hover:border-surface-600` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-surface-600) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-surface-600 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:hover:border-surface-700` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-surface-700) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-surface-700 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:hover:border-warn-300` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-warn-300) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-warn-300 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:hover:border-warn-700` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-warn-700) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-warn-700 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:hover:text-danger-400` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-danger-400) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-danger-400 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:hover:text-danger-950` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-danger-950) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-danger-950 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:hover:text-help-400` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-help-400) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-help-400 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:hover:text-help-950` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-help-950) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-help-950 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:hover:text-info-400` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-info-400) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-info-400 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:hover:text-info-950` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-info-950) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-info-950 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:hover:text-primary` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-primary-color) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-primary-color | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:hover:text-success-400` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-success-400) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-success-400 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:hover:text-success-950` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-success-950) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-success-950 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:hover:text-surface-0` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-surface-0) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-surface-0 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:hover:text-surface-200` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-surface-200) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-surface-200 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:hover:text-surface-400` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-surface-400) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-surface-400 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:hover:text-surface-950` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-surface-950) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-surface-950 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:hover:text-warn-400` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-warn-400) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-warn-400 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:hover:text-warn-950` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-warn-950) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-warn-950 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:focus-visible:outline-danger-400` | outline-color | `color-mix(in srgb, var(--p-danger-400) calc(100% * 1), transparent)` | --p-danger-400 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:focus-visible:outline-help-400` | outline-color | `color-mix(in srgb, var(--p-help-400) calc(100% * 1), transparent)` | --p-help-400 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:focus-visible:outline-info-400` | outline-color | `color-mix(in srgb, var(--p-info-400) calc(100% * 1), transparent)` | --p-info-400 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:focus-visible:outline-success-400` | outline-color | `color-mix(in srgb, var(--p-success-400) calc(100% * 1), transparent)` | --p-success-400 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:focus-visible:outline-surface-0` | outline-color | `color-mix(in srgb, var(--p-surface-0) calc(100% * 1), transparent)` | --p-surface-0 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:focus-visible:outline-surface-300` | outline-color | `color-mix(in srgb, var(--p-surface-300) calc(100% * 1), transparent)` | --p-surface-300 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:focus-visible:outline-warn-400` | outline-color | `color-mix(in srgb, var(--p-warn-400) calc(100% * 1), transparent)` | --p-warn-400 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:placeholder:text-surface-400` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-surface-400) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-surface-400 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:text-danger-400` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-danger-400) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-danger-400 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:text-danger-950` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-danger-950) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-danger-950 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:text-help-400` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-help-400) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-help-400 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:text-help-950` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-help-950) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-help-950 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:text-info-400` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-info-400) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-info-400 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:text-info-950` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-info-950) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-info-950 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:text-primary` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-primary-color) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-primary-color | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:text-success-400` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-success-400) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-success-400 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:text-success-950` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-success-950) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-success-950 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:text-surface-0` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-surface-0) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-surface-0 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:text-surface-300` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-surface-300) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-surface-300 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:text-surface-400` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-surface-400) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-surface-400 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:text-surface-800` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-surface-800) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-surface-800 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:text-surface-900` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-surface-900) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-surface-900 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:text-surface-950` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-surface-950) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-surface-950 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:text-warn-400` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-warn-400) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-warn-400 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:text-warn-950` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-warn-950) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-warn-950 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `disabled:bg-surface-200` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-surface-200) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-surface-200 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `disabled:text-surface-500` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-surface-500) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-surface-500 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:active:bg-danger-100` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-danger-100) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-danger-100 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:active:bg-danger-700` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-danger-700) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-danger-700 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:active:bg-help-100` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-help-100) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-help-100 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:active:bg-help-700` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-help-700) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-help-700 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:active:bg-info-100` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-info-100) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-info-100 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:active:bg-info-700` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-info-700) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-info-700 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:active:bg-primary-100` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-primary-100) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-primary-100 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:active:bg-primary-emphasis-alt` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-primary-active-color) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-primary-active-color | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:active:bg-success-100` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-success-100) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-success-100 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:active:bg-success-700` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-success-700) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-success-700 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:active:bg-surface-100` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-surface-100) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-surface-100 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:active:bg-surface-300` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-surface-300) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-surface-300 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:active:bg-surface-800` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-surface-800) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-surface-800 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:active:bg-warn-100` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-warn-100) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-warn-100 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:active:bg-warn-700` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-warn-700) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-warn-700 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:active:border-danger-200` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-danger-200) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-danger-200 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:active:border-danger-700` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-danger-700) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-danger-700 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:active:border-help-200` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-help-200) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-help-200 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:active:border-help-700` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-help-700) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-help-700 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:active:border-info-200` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-info-200) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-info-200 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:active:border-info-700` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-info-700) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-info-700 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:active:border-primary-200` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-primary-200) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-primary-200 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:active:border-primary-emphasis-alt` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-primary-active-color) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-primary-active-color | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:active:border-success-200` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-success-200) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-success-200 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:active:border-success-700` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-success-700) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-success-700 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:active:border-surface-200` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-surface-200) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-surface-200 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:active:border-surface-300` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-surface-300) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-surface-300 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:active:border-surface-700` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-surface-700) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-surface-700 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:active:border-surface-800` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-surface-800) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-surface-800 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:active:border-warn-200` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-warn-200) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-warn-200 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:active:border-warn-700` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-warn-700) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-warn-700 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:active:text-danger-500` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-danger-500) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-danger-500 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:active:text-help-500` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-help-500) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-help-500 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:active:text-info-500` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-info-500) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-info-500 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:active:text-primary` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-primary-color) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-primary-color | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:active:text-success-500` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-success-500) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-success-500 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:active:text-surface-500` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-surface-500) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-surface-500 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:active:text-surface-700` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-surface-700) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-surface-700 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:active:text-surface-800` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-surface-800) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-surface-800 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:active:text-surface-950` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-surface-950) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-surface-950 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:active:text-warn-500` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-warn-500) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-warn-500 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:focus:border-primary` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-primary-color) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-primary-color | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:hover:bg-danger-50` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-danger-50) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-danger-50 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:hover:bg-danger-600` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-danger-600) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-danger-600 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:hover:bg-help-50` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-help-50) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-help-50 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:hover:bg-help-600` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-help-600) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-help-600 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:hover:bg-info-50` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-info-50) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-info-50 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:hover:bg-info-600` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-info-600) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-info-600 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:hover:bg-primary-50` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-primary-50) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-primary-50 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:hover:bg-primary-emphasis` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-primary-hover-color) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-primary-hover-color | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:hover:bg-success-50` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-success-50) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-success-50 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:hover:bg-success-600` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-success-600) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-success-600 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:hover:bg-surface-200` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-surface-200) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-surface-200 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:hover:bg-surface-50` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-surface-50) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-surface-50 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:hover:bg-surface-900` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-surface-900) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-surface-900 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:hover:bg-warn-50` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-warn-50) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-warn-50 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:hover:bg-warn-600` | --tw-bg-opacity / background-color | `1 / color-mix(in srgb, var(--p-warn-600) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | --p-warn-600 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:hover:border-danger-200` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-danger-200) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-danger-200 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:hover:border-danger-600` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-danger-600) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-danger-600 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:hover:border-help-200` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-help-200) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-help-200 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:hover:border-help-600` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-help-600) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-help-600 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:hover:border-info-200` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-info-200) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-info-200 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:hover:border-info-600` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-info-600) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-info-600 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:hover:border-primary-200` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-primary-200) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-primary-200 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:hover:border-primary-emphasis` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-primary-hover-color) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-primary-hover-color | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:hover:border-success-200` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-success-200) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-success-200 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:hover:border-success-600` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-success-600) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-success-600 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:hover:border-surface-200` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-surface-200) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-surface-200 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:hover:border-surface-400` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-surface-400) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-surface-400 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:hover:border-surface-700` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-surface-700) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-surface-700 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:hover:border-surface-900` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-surface-900) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-surface-900 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:hover:border-warn-200` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-warn-200) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-warn-200 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:hover:border-warn-600` | --tw-border-opacity / border-color | `1 / color-mix(in srgb, var(--p-warn-600) calc(100% * var(--tw-border-opacity, 1)), transparent)` | --p-warn-600 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:hover:text-danger-500` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-danger-500) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-danger-500 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:hover:text-help-500` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-help-500) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-help-500 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:hover:text-info-500` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-info-500) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-info-500 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:hover:text-primary` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-primary-color) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-primary-color | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:hover:text-success-500` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-success-500) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-success-500 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:hover:text-surface-500` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-surface-500) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-surface-500 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:hover:text-surface-700` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-surface-700) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-surface-700 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:hover:text-surface-950` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-surface-950) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-surface-950 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:hover:text-warn-500` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-warn-500) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-warn-500 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `focus-visible:outline-danger-500` | outline-color | `color-mix(in srgb, var(--p-danger-500) calc(100% * 1), transparent)` | --p-danger-500 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `focus-visible:outline-help-500` | outline-color | `color-mix(in srgb, var(--p-help-500) calc(100% * 1), transparent)` | --p-help-500 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `focus-visible:outline-info-500` | outline-color | `color-mix(in srgb, var(--p-info-500) calc(100% * 1), transparent)` | --p-info-500 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `focus-visible:outline-primary` | outline-color | `color-mix(in srgb, var(--p-primary-color) calc(100% * 1), transparent)` | --p-primary-color | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `focus-visible:outline-success-500` | outline-color | `color-mix(in srgb, var(--p-success-500) calc(100% * 1), transparent)` | --p-success-500 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `focus-visible:outline-surface-600` | outline-color | `color-mix(in srgb, var(--p-surface-600) calc(100% * 1), transparent)` | --p-surface-600 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `focus-visible:outline-surface-950` | outline-color | `color-mix(in srgb, var(--p-surface-950) calc(100% * 1), transparent)` | --p-surface-950 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `focus-visible:outline-warn-500` | outline-color | `color-mix(in srgb, var(--p-warn-500) calc(100% * 1), transparent)` | --p-warn-500 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `placeholder:text-surface-500` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-surface-500) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-surface-500 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `rounded-border` | border-radius | `var(--p-content-border-radius)` | --p-content-border-radius | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | public | Generic content radius, not an automatic form-control radius | +| `text-color` | color | `var(--p-text-color)` | --p-text-color | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | public | Primary content text | +| `text-color-emphasis` | color | `var(--p-text-hover-color)` | --p-text-hover-color | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | public | Emphasized content text | +| `text-danger-500` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-danger-500) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-danger-500 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `text-help-500` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-help-500) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-help-500 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `text-info-500` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-info-500) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-info-500 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `text-muted-color` | color | `var(--p-text-muted-color)` | --p-text-muted-color | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | public | Secondary content text | +| `text-muted-color-emphasis` | color | `var(--p-text-hover-muted-color)` | --p-text-hover-muted-color | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | public | Emphasized secondary text | +| `text-primary` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-primary-color) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-primary-color | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | public | Default primary action and emphasis color | +| `text-primary-contrast` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-primary-contrast-color) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-primary-contrast-color | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | public | Foreground paired with the primary background | +| `text-primary-emphasis` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-primary-hover-color) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-primary-hover-color | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `text-success-500` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-success-500) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-success-500 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `text-surface-500` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-surface-500) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-surface-500 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `text-surface-600` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-surface-600) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-surface-600 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `text-surface-700` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-surface-700) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-surface-700 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `text-surface-950` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-surface-950) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-surface-950 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `text-warn-500` | --tw-text-opacity / color | `1 / color-mix(in srgb, var(--p-warn-500) calc(100% * var(--tw-text-opacity, 1)), transparent)` | --p-warn-500 | runtime-variable | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | + +### Compile-time baselines + +> Build-time baseline. This value is embedded in the module bundle and does not react to a facade theme change. + +| Utility | CSS property | Resolved value | Runtime dependency | Classification | Allowed consumer | Stability | Intended use | +|---|---|---|---|---|---|---|---| +| `absolute` | position | `absolute` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `appearance-none` | appearance | `none` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `bg-transparent` | background-color | `transparent` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `border` | border-width | `1px` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `border-transparent` | border-color | `transparent` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `cursor-pointer` | cursor | `pointer` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:bg-transparent` | background-color | `transparent` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:border-transparent` | border-color | `transparent` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:active:bg-white/15` | background-color | `rgb(255 255 255 / 0.15)` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:active:border-transparent` | border-color | `transparent` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:hover:bg-white/5` | background-color | `rgb(255 255 255 / 0.05)` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `dark:enabled:hover:border-transparent` | border-color | `transparent` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `disabled:cursor-default` | cursor | `default` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `disabled:opacity-100` | opacity | `1` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `duration-200` | transition-duration | `200ms` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | platform-invariant-only | Static Tailwind motion baseline | +| `ease-in-out` | transition-timing-function | `cubic-bezier(0.4, 0, 0.2, 1)` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | platform-invariant-only | Static Tailwind timing baseline | +| `enabled:active:bg-transparent` | background-color | `transparent` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:active:border-transparent` | border-color | `transparent` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:active:text-white` | --tw-text-opacity / color | `1 / rgb(255 255 255 / var(--tw-text-opacity, 1))` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:hover:bg-transparent` | background-color | `transparent` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:hover:border-transparent` | border-color | `transparent` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `enabled:hover:text-white` | --tw-text-opacity / color | `1 / rgb(255 255 255 / var(--tw-text-opacity, 1))` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `flex` | display | `flex` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `flex-col` | flex-direction | `column` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `focus-visible:outline` | outline-style | `solid` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `focus-visible:outline-1` | outline-width | `1px` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `focus-visible:outline-offset-2` | outline-offset | `2px` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `font-medium` | font-weight | `500` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `gap-0` | gap | `0px` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `gap-2` | gap | `0.5rem` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | platform-invariant-only | Static Tailwind spacing baseline | +| `h-10` | height | `2.5rem` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `h-4` | height | `1rem` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `h-6` | height | `1.5rem` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | platform-invariant-only | Static Tailwind sizing baseline | +| `h-full` | height | `100%` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `inline-block` | display | `inline-block` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `inline-flex` | display | `inline-flex` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `invisible` | visibility | `hidden` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `items-center` | align-items | `center` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `justify-center` | justify-content | `center` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `leading-4` | line-height | `1rem` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `m-0` | margin | `0px` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `min-w-4` | min-width | `1rem` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `opacity-0` | opacity | `0` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `opacity-100` | opacity | `1` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `order-1` | order | `1` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `order-2` | order | `2` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `order-[-1]` | order | `-1` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `outline-1` | outline-width | `1px` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | platform-invariant-only | Static Tailwind focus geometry baseline | +| `outline-none` | outline / outline-offset | `2px solid transparent / 2px` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `outline-offset-2` | outline-offset | `2px` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | platform-invariant-only | Static Tailwind focus geometry baseline | +| `overflow-hidden` | overflow | `hidden` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `p-0` | padding | `0px` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `px-0` | padding-left / padding-right | `0px / 0px` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `px-2` | padding-left / padding-right | `0.5rem / 0.5rem` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `px-3` | padding-left / padding-right | `0.75rem / 0.75rem` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | platform-invariant-only | Static Tailwind spacing baseline | +| `px-[0.625rem]` | padding-left / padding-right | `0.625rem / 0.625rem` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `px-[0.875rem]` | padding-left / padding-right | `0.875rem / 0.875rem` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `py-1` | padding-top / padding-bottom | `0.25rem / 0.25rem` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `py-2` | padding-top / padding-bottom | `0.5rem / 0.5rem` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | platform-invariant-only | Static Tailwind spacing baseline | +| `py-[0.375rem]` | padding-top / padding-bottom | `0.375rem / 0.375rem` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `py-[0.625rem]` | padding-top / padding-bottom | `0.625rem / 0.625rem` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `relative` | position | `relative` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `rounded-[2rem]` | border-radius | `2rem` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `rounded-full` | border-radius | `9999px` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `rounded-md` | border-radius | `0.375rem` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | platform-invariant-only | Static Tailwind radius baseline | +| `select-none` | user-select | `none` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `shadow-[0_3px_1px_-2px_rgba(0,0,0,0.2),0_2px_2px_0_rgba(0,0,0,0.14),0_1px_5px_0_rgba(0,0,0,0.12)]` | --tw-shadow / --tw-shadow-colored / box-shadow | `0 3px 1px -2px rgba(0,0,0,0.2),0 2px 2px 0 rgba(0,0,0,0.14),0 1px 5px 0 rgba(0,0,0,0.12) / 0 3px 1px -2px var(--tw-shadow-color), 0 2px 2px 0 var(--tw-shadow-color), 0 1px 5px 0 var(--tw-shadow-color) / var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow)` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `start-0` | inset-inline-start | `0px` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `text-[1.125rem]` | font-size | `1.125rem` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `text-lg` | font-size / line-height | `1.125rem / 1.75rem` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `text-sm` | font-size / line-height | `0.875rem / 1.25rem` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | platform-invariant-only | Static Tailwind typography baseline | +| `text-white` | --tw-text-opacity / color | `1 / rgb(255 255 255 / var(--tw-text-opacity, 1))` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `text-xs` | font-size / line-height | `0.75rem / 1rem` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `top-0` | top | `0px` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `top-1/2` | top | `50%` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `transition-[background,color,left]` | transition-property / transition-timing-function / transition-duration | `background,color,left / cubic-bezier(0.4, 0, 0.2, 1) / 150ms` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `transition-colors` | transition-property / transition-timing-function / transition-duration | `color, background-color, border-color, text-decoration-color, fill, stroke / cubic-bezier(0.4, 0, 0.2, 1) / 150ms` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `underline` | text-decoration-line | `underline` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `w-0` | width | `0px` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `w-10` | width | `2.5rem` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | platform-invariant-only | Static Tailwind sizing baseline | +| `w-full` | width | `100%` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | +| `z-10` | z-index | `10` | none | compile-time-constant | portable module when semantic use matches; fixed values only after invariant review | generated-representative | Representative utility; review semantic use at the consumer | + +### Internal or transient utilities + +| Utility | CSS property | Resolved value | Runtime dependency | Classification | Allowed consumer | Stability | Intended use | +|---|---|---|---|---|---|---|---| + +### Compiled representative probes + +| Utility | Emitted declarations | +|---|---| +| `absolute` | `position: absolute` | +| `appearance-none` | `appearance: none` | +| `bg-danger-500` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-danger-500) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `bg-emphasis` | `background: var(--p-content-hover-background); color: var(--p-content-hover-color)` | +| `bg-help-500` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-help-500) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `bg-highlight` | `background: var(--p-highlight-background); color: var(--p-highlight-color)` | +| `bg-highlight-emphasis` | `background: var(--p-highlight-focus-background); color: var(--p-highlight-focus-color)` | +| `bg-info-500` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-info-500) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `bg-primary` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-primary-color) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `bg-primary-emphasis` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-primary-hover-color) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `bg-success-500` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-success-500) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `bg-surface-0` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-surface-0) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `bg-surface-100` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-surface-100) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `bg-surface-200` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-surface-200) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `bg-surface-300` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-surface-300) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `bg-surface-400` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-surface-400) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `bg-surface-50` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-surface-50) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `bg-surface-700` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-surface-700) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `bg-surface-950` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-surface-950) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `bg-transparent` | `background-color: transparent` | +| `bg-warn-500` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-warn-500) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `border` | `border-width: 1px` | +| `border-danger-200` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-danger-200) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `border-danger-400` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-danger-400) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `border-danger-500` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-danger-500) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `border-help-200` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-help-200) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `border-help-500` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-help-500) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `border-info-200` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-info-200) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `border-info-500` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-info-500) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `border-primary` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-primary-color) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `border-primary-200` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-primary-200) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `border-success-200` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-success-200) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `border-success-500` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-success-500) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `border-surface` | `border-color: var(--p-content-border-color)` | +| `border-surface-100` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-surface-100) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `border-surface-200` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-surface-200) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `border-surface-300` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-surface-300) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `border-surface-700` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-surface-700) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `border-surface-950` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-surface-950) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `border-transparent` | `border-color: transparent` | +| `border-warn-200` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-warn-200) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `border-warn-500` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-warn-500) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `cursor-pointer` | `cursor: pointer` | +| `dark:bg-danger-400` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-danger-400) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `dark:bg-help-400` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-help-400) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `dark:bg-info-400` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-info-400) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `dark:bg-success-400` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-success-400) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `dark:bg-surface-0` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-surface-0) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `dark:bg-surface-300` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-surface-300) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `dark:bg-surface-400` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-surface-400) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `dark:bg-surface-600` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-surface-600) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `dark:bg-surface-700` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-surface-700) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `dark:bg-surface-800` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-surface-800) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `dark:bg-surface-900` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-surface-900) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `dark:bg-transparent` | `background-color: transparent` | +| `dark:bg-warn-400` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-warn-400) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `dark:border-danger-300` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-danger-300) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `dark:border-danger-400` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-danger-400) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `dark:border-danger-700` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-danger-700) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `dark:border-help-400` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-help-400) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `dark:border-help-700` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-help-700) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `dark:border-info-400` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-info-400) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `dark:border-info-700` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-info-700) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `dark:border-primary-700` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-primary-700) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `dark:border-success-400` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-success-400) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `dark:border-success-700` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-success-700) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `dark:border-surface-100` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-surface-100) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `dark:border-surface-500` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-surface-500) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `dark:border-surface-600` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-surface-600) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `dark:border-surface-700` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-surface-700) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `dark:border-surface-800` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-surface-800) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `dark:border-transparent` | `border-color: transparent` | +| `dark:border-warn-400` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-warn-400) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `dark:border-warn-700` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-warn-700) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `dark:disabled:bg-surface-700` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-surface-700) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `dark:disabled:text-surface-400` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-surface-400) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `dark:enabled:active:bg-danger-200` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-danger-200) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `dark:enabled:active:bg-danger-400/15` | `background-color: color-mix(in srgb, var(--p-danger-400) calc(100% * 0.15), transparent)` | +| `dark:enabled:active:bg-help-200` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-help-200) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `dark:enabled:active:bg-help-400/15` | `background-color: color-mix(in srgb, var(--p-help-400) calc(100% * 0.15), transparent)` | +| `dark:enabled:active:bg-info-200` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-info-200) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `dark:enabled:active:bg-info-400/15` | `background-color: color-mix(in srgb, var(--p-info-400) calc(100% * 0.15), transparent)` | +| `dark:enabled:active:bg-primary/15` | `background-color: color-mix(in srgb, var(--p-primary-color) calc(100% * 0.15), transparent)` | +| `dark:enabled:active:bg-success-200` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-success-200) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `dark:enabled:active:bg-success-400/15` | `background-color: color-mix(in srgb, var(--p-success-400) calc(100% * 0.15), transparent)` | +| `dark:enabled:active:bg-surface-200` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-surface-200) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `dark:enabled:active:bg-surface-600` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-surface-600) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `dark:enabled:active:bg-surface-700` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-surface-700) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `dark:enabled:active:bg-warn-200` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-warn-200) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `dark:enabled:active:bg-warn-400/15` | `background-color: color-mix(in srgb, var(--p-warn-400) calc(100% * 0.15), transparent)` | +| `dark:enabled:active:bg-white/15` | `background-color: rgb(255 255 255 / 0.15)` | +| `dark:enabled:active:border-danger-200` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-danger-200) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `dark:enabled:active:border-danger-700` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-danger-700) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `dark:enabled:active:border-help-200` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-help-200) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `dark:enabled:active:border-help-700` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-help-700) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `dark:enabled:active:border-info-200` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-info-200) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `dark:enabled:active:border-info-700` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-info-700) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `dark:enabled:active:border-primary-700` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-primary-700) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `dark:enabled:active:border-success-200` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-success-200) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `dark:enabled:active:border-success-700` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-success-700) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `dark:enabled:active:border-surface-300` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-surface-300) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `dark:enabled:active:border-surface-500` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-surface-500) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `dark:enabled:active:border-surface-600` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-surface-600) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `dark:enabled:active:border-surface-700` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-surface-700) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `dark:enabled:active:border-transparent` | `border-color: transparent` | +| `dark:enabled:active:border-warn-200` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-warn-200) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `dark:enabled:active:border-warn-700` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-warn-700) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `dark:enabled:active:text-danger-400` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-danger-400) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `dark:enabled:active:text-danger-950` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-danger-950) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `dark:enabled:active:text-help-400` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-help-400) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `dark:enabled:active:text-help-950` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-help-950) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `dark:enabled:active:text-info-400` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-info-400) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `dark:enabled:active:text-info-950` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-info-950) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `dark:enabled:active:text-primary` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-primary-color) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `dark:enabled:active:text-success-400` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-success-400) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `dark:enabled:active:text-success-950` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-success-950) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `dark:enabled:active:text-surface-0` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-surface-0) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `dark:enabled:active:text-surface-100` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-surface-100) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `dark:enabled:active:text-surface-400` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-surface-400) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `dark:enabled:active:text-surface-950` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-surface-950) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `dark:enabled:active:text-warn-400` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-warn-400) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `dark:enabled:active:text-warn-950` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-warn-950) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `dark:enabled:focus:border-primary` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-primary-color) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `dark:enabled:hover:bg-danger-300` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-danger-300) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `dark:enabled:hover:bg-danger-400/5` | `background-color: color-mix(in srgb, var(--p-danger-400) calc(100% * 0.05), transparent)` | +| `dark:enabled:hover:bg-help-300` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-help-300) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `dark:enabled:hover:bg-help-400/5` | `background-color: color-mix(in srgb, var(--p-help-400) calc(100% * 0.05), transparent)` | +| `dark:enabled:hover:bg-info-300` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-info-300) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `dark:enabled:hover:bg-info-400/5` | `background-color: color-mix(in srgb, var(--p-info-400) calc(100% * 0.05), transparent)` | +| `dark:enabled:hover:bg-primary/5` | `background-color: color-mix(in srgb, var(--p-primary-color) calc(100% * 0.05), transparent)` | +| `dark:enabled:hover:bg-success-300` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-success-300) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `dark:enabled:hover:bg-success-400/5` | `background-color: color-mix(in srgb, var(--p-success-400) calc(100% * 0.05), transparent)` | +| `dark:enabled:hover:bg-surface-100` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-surface-100) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `dark:enabled:hover:bg-surface-700` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-surface-700) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `dark:enabled:hover:bg-surface-800` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-surface-800) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `dark:enabled:hover:bg-warn-300` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-warn-300) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `dark:enabled:hover:bg-warn-400/5` | `background-color: color-mix(in srgb, var(--p-warn-400) calc(100% * 0.05), transparent)` | +| `dark:enabled:hover:bg-white/5` | `background-color: rgb(255 255 255 / 0.05)` | +| `dark:enabled:hover:border-danger-300` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-danger-300) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `dark:enabled:hover:border-danger-700` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-danger-700) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `dark:enabled:hover:border-help-300` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-help-300) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `dark:enabled:hover:border-help-700` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-help-700) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `dark:enabled:hover:border-info-300` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-info-300) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `dark:enabled:hover:border-info-700` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-info-700) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `dark:enabled:hover:border-primary-700` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-primary-700) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `dark:enabled:hover:border-success-300` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-success-300) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `dark:enabled:hover:border-success-700` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-success-700) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `dark:enabled:hover:border-surface-200` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-surface-200) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `dark:enabled:hover:border-surface-500` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-surface-500) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `dark:enabled:hover:border-surface-600` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-surface-600) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `dark:enabled:hover:border-surface-700` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-surface-700) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `dark:enabled:hover:border-transparent` | `border-color: transparent` | +| `dark:enabled:hover:border-warn-300` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-warn-300) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `dark:enabled:hover:border-warn-700` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-warn-700) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `dark:enabled:hover:text-danger-400` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-danger-400) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `dark:enabled:hover:text-danger-950` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-danger-950) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `dark:enabled:hover:text-help-400` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-help-400) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `dark:enabled:hover:text-help-950` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-help-950) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `dark:enabled:hover:text-info-400` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-info-400) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `dark:enabled:hover:text-info-950` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-info-950) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `dark:enabled:hover:text-primary` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-primary-color) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `dark:enabled:hover:text-success-400` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-success-400) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `dark:enabled:hover:text-success-950` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-success-950) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `dark:enabled:hover:text-surface-0` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-surface-0) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `dark:enabled:hover:text-surface-200` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-surface-200) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `dark:enabled:hover:text-surface-400` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-surface-400) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `dark:enabled:hover:text-surface-950` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-surface-950) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `dark:enabled:hover:text-warn-400` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-warn-400) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `dark:enabled:hover:text-warn-950` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-warn-950) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `dark:focus-visible:outline-danger-400` | `outline-color: color-mix(in srgb, var(--p-danger-400) calc(100% * 1), transparent)` | +| `dark:focus-visible:outline-help-400` | `outline-color: color-mix(in srgb, var(--p-help-400) calc(100% * 1), transparent)` | +| `dark:focus-visible:outline-info-400` | `outline-color: color-mix(in srgb, var(--p-info-400) calc(100% * 1), transparent)` | +| `dark:focus-visible:outline-success-400` | `outline-color: color-mix(in srgb, var(--p-success-400) calc(100% * 1), transparent)` | +| `dark:focus-visible:outline-surface-0` | `outline-color: color-mix(in srgb, var(--p-surface-0) calc(100% * 1), transparent)` | +| `dark:focus-visible:outline-surface-300` | `outline-color: color-mix(in srgb, var(--p-surface-300) calc(100% * 1), transparent)` | +| `dark:focus-visible:outline-warn-400` | `outline-color: color-mix(in srgb, var(--p-warn-400) calc(100% * 1), transparent)` | +| `dark:placeholder:text-surface-400` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-surface-400) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `dark:text-danger-400` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-danger-400) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `dark:text-danger-950` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-danger-950) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `dark:text-help-400` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-help-400) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `dark:text-help-950` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-help-950) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `dark:text-info-400` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-info-400) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `dark:text-info-950` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-info-950) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `dark:text-primary` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-primary-color) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `dark:text-success-400` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-success-400) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `dark:text-success-950` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-success-950) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `dark:text-surface-0` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-surface-0) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `dark:text-surface-300` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-surface-300) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `dark:text-surface-400` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-surface-400) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `dark:text-surface-800` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-surface-800) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `dark:text-surface-900` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-surface-900) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `dark:text-surface-950` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-surface-950) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `dark:text-warn-400` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-warn-400) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `dark:text-warn-950` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-warn-950) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `disabled:bg-surface-200` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-surface-200) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `disabled:cursor-default` | `cursor: default` | +| `disabled:opacity-100` | `opacity: 1` | +| `disabled:text-surface-500` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-surface-500) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `duration-200` | `transition-duration: 200ms` | +| `ease-in-out` | `transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1)` | +| `enabled:active:bg-danger-100` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-danger-100) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `enabled:active:bg-danger-700` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-danger-700) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `enabled:active:bg-help-100` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-help-100) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `enabled:active:bg-help-700` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-help-700) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `enabled:active:bg-info-100` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-info-100) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `enabled:active:bg-info-700` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-info-700) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `enabled:active:bg-primary-100` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-primary-100) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `enabled:active:bg-primary-emphasis-alt` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-primary-active-color) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `enabled:active:bg-success-100` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-success-100) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `enabled:active:bg-success-700` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-success-700) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `enabled:active:bg-surface-100` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-surface-100) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `enabled:active:bg-surface-300` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-surface-300) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `enabled:active:bg-surface-800` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-surface-800) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `enabled:active:bg-transparent` | `background-color: transparent` | +| `enabled:active:bg-warn-100` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-warn-100) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `enabled:active:bg-warn-700` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-warn-700) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `enabled:active:border-danger-200` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-danger-200) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `enabled:active:border-danger-700` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-danger-700) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `enabled:active:border-help-200` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-help-200) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `enabled:active:border-help-700` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-help-700) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `enabled:active:border-info-200` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-info-200) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `enabled:active:border-info-700` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-info-700) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `enabled:active:border-primary-200` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-primary-200) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `enabled:active:border-primary-emphasis-alt` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-primary-active-color) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `enabled:active:border-success-200` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-success-200) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `enabled:active:border-success-700` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-success-700) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `enabled:active:border-surface-200` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-surface-200) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `enabled:active:border-surface-300` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-surface-300) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `enabled:active:border-surface-700` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-surface-700) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `enabled:active:border-surface-800` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-surface-800) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `enabled:active:border-transparent` | `border-color: transparent` | +| `enabled:active:border-warn-200` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-warn-200) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `enabled:active:border-warn-700` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-warn-700) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `enabled:active:text-danger-500` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-danger-500) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `enabled:active:text-help-500` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-help-500) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `enabled:active:text-info-500` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-info-500) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `enabled:active:text-primary` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-primary-color) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `enabled:active:text-success-500` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-success-500) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `enabled:active:text-surface-500` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-surface-500) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `enabled:active:text-surface-700` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-surface-700) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `enabled:active:text-surface-800` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-surface-800) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `enabled:active:text-surface-950` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-surface-950) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `enabled:active:text-warn-500` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-warn-500) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `enabled:active:text-white` | `--tw-text-opacity: 1; color: rgb(255 255 255 / var(--tw-text-opacity, 1))` | +| `enabled:focus:border-primary` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-primary-color) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `enabled:hover:bg-danger-50` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-danger-50) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `enabled:hover:bg-danger-600` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-danger-600) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `enabled:hover:bg-help-50` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-help-50) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `enabled:hover:bg-help-600` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-help-600) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `enabled:hover:bg-info-50` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-info-50) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `enabled:hover:bg-info-600` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-info-600) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `enabled:hover:bg-primary-50` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-primary-50) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `enabled:hover:bg-primary-emphasis` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-primary-hover-color) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `enabled:hover:bg-success-50` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-success-50) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `enabled:hover:bg-success-600` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-success-600) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `enabled:hover:bg-surface-200` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-surface-200) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `enabled:hover:bg-surface-50` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-surface-50) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `enabled:hover:bg-surface-900` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-surface-900) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `enabled:hover:bg-transparent` | `background-color: transparent` | +| `enabled:hover:bg-warn-50` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-warn-50) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `enabled:hover:bg-warn-600` | `--tw-bg-opacity: 1; background-color: color-mix(in srgb, var(--p-warn-600) calc(100% * var(--tw-bg-opacity, 1)), transparent)` | +| `enabled:hover:border-danger-200` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-danger-200) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `enabled:hover:border-danger-600` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-danger-600) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `enabled:hover:border-help-200` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-help-200) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `enabled:hover:border-help-600` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-help-600) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `enabled:hover:border-info-200` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-info-200) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `enabled:hover:border-info-600` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-info-600) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `enabled:hover:border-primary-200` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-primary-200) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `enabled:hover:border-primary-emphasis` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-primary-hover-color) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `enabled:hover:border-success-200` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-success-200) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `enabled:hover:border-success-600` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-success-600) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `enabled:hover:border-surface-200` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-surface-200) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `enabled:hover:border-surface-400` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-surface-400) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `enabled:hover:border-surface-700` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-surface-700) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `enabled:hover:border-surface-900` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-surface-900) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `enabled:hover:border-transparent` | `border-color: transparent` | +| `enabled:hover:border-warn-200` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-warn-200) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `enabled:hover:border-warn-600` | `--tw-border-opacity: 1; border-color: color-mix(in srgb, var(--p-warn-600) calc(100% * var(--tw-border-opacity, 1)), transparent)` | +| `enabled:hover:text-danger-500` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-danger-500) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `enabled:hover:text-help-500` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-help-500) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `enabled:hover:text-info-500` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-info-500) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `enabled:hover:text-primary` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-primary-color) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `enabled:hover:text-success-500` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-success-500) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `enabled:hover:text-surface-500` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-surface-500) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `enabled:hover:text-surface-700` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-surface-700) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `enabled:hover:text-surface-950` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-surface-950) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `enabled:hover:text-warn-500` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-warn-500) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `enabled:hover:text-white` | `--tw-text-opacity: 1; color: rgb(255 255 255 / var(--tw-text-opacity, 1))` | +| `flex` | `display: flex` | +| `flex-col` | `flex-direction: column` | +| `focus-visible:outline` | `outline-style: solid` | +| `focus-visible:outline-1` | `outline-width: 1px` | +| `focus-visible:outline-danger-500` | `outline-color: color-mix(in srgb, var(--p-danger-500) calc(100% * 1), transparent)` | +| `focus-visible:outline-help-500` | `outline-color: color-mix(in srgb, var(--p-help-500) calc(100% * 1), transparent)` | +| `focus-visible:outline-info-500` | `outline-color: color-mix(in srgb, var(--p-info-500) calc(100% * 1), transparent)` | +| `focus-visible:outline-offset-2` | `outline-offset: 2px` | +| `focus-visible:outline-primary` | `outline-color: color-mix(in srgb, var(--p-primary-color) calc(100% * 1), transparent)` | +| `focus-visible:outline-success-500` | `outline-color: color-mix(in srgb, var(--p-success-500) calc(100% * 1), transparent)` | +| `focus-visible:outline-surface-600` | `outline-color: color-mix(in srgb, var(--p-surface-600) calc(100% * 1), transparent)` | +| `focus-visible:outline-surface-950` | `outline-color: color-mix(in srgb, var(--p-surface-950) calc(100% * 1), transparent)` | +| `focus-visible:outline-warn-500` | `outline-color: color-mix(in srgb, var(--p-warn-500) calc(100% * 1), transparent)` | +| `font-medium` | `font-weight: 500` | +| `gap-0` | `gap: 0px` | +| `gap-2` | `gap: 0.5rem` | +| `h-10` | `height: 2.5rem` | +| `h-4` | `height: 1rem` | +| `h-6` | `height: 1.5rem` | +| `h-full` | `height: 100%` | +| `inline-block` | `display: inline-block` | +| `inline-flex` | `display: inline-flex` | +| `invisible` | `visibility: hidden` | +| `items-center` | `align-items: center` | +| `justify-center` | `justify-content: center` | +| `leading-4` | `line-height: 1rem` | +| `m-0` | `margin: 0px` | +| `min-w-4` | `min-width: 1rem` | +| `opacity-0` | `opacity: 0` | +| `opacity-100` | `opacity: 1` | +| `order-1` | `order: 1` | +| `order-2` | `order: 2` | +| `order-[-1]` | `order: -1` | +| `outline-1` | `outline-width: 1px` | +| `outline-none` | `outline: 2px solid transparent; outline-offset: 2px` | +| `outline-offset-2` | `outline-offset: 2px` | +| `overflow-hidden` | `overflow: hidden` | +| `p-0` | `padding: 0px` | +| `placeholder:text-surface-500` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-surface-500) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `px-0` | `padding-left: 0px; padding-right: 0px` | +| `px-2` | `padding-left: 0.5rem; padding-right: 0.5rem` | +| `px-3` | `padding-left: 0.75rem; padding-right: 0.75rem` | +| `px-[0.625rem]` | `padding-left: 0.625rem; padding-right: 0.625rem` | +| `px-[0.875rem]` | `padding-left: 0.875rem; padding-right: 0.875rem` | +| `py-1` | `padding-top: 0.25rem; padding-bottom: 0.25rem` | +| `py-2` | `padding-top: 0.5rem; padding-bottom: 0.5rem` | +| `py-[0.375rem]` | `padding-top: 0.375rem; padding-bottom: 0.375rem` | +| `py-[0.625rem]` | `padding-top: 0.625rem; padding-bottom: 0.625rem` | +| `relative` | `position: relative` | +| `rounded-[2rem]` | `border-radius: 2rem` | +| `rounded-border` | `border-radius: var(--p-content-border-radius)` | +| `rounded-full` | `border-radius: 9999px` | +| `rounded-md` | `border-radius: 0.375rem` | +| `select-none` | `user-select: none` | +| `shadow-[0_3px_1px_-2px_rgba(0,0,0,0.2),0_2px_2px_0_rgba(0,0,0,0.14),0_1px_5px_0_rgba(0,0,0,0.12)]` | `--tw-shadow: 0 3px 1px -2px rgba(0,0,0,0.2),0 2px 2px 0 rgba(0,0,0,0.14),0 1px 5px 0 rgba(0,0,0,0.12); --tw-shadow-colored: 0 3px 1px -2px var(--tw-shadow-color), 0 2px 2px 0 var(--tw-shadow-color), 0 1px 5px 0 var(--tw-shadow-color); box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow)` | +| `start-0` | `inset-inline-start: 0px` | +| `text-[1.125rem]` | `font-size: 1.125rem` | +| `text-color` | `color: var(--p-text-color)` | +| `text-color-emphasis` | `color: var(--p-text-hover-color)` | +| `text-danger-500` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-danger-500) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `text-help-500` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-help-500) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `text-info-500` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-info-500) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `text-lg` | `font-size: 1.125rem; line-height: 1.75rem` | +| `text-muted-color` | `color: var(--p-text-muted-color)` | +| `text-muted-color-emphasis` | `color: var(--p-text-hover-muted-color)` | +| `text-primary` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-primary-color) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `text-primary-contrast` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-primary-contrast-color) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `text-primary-emphasis` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-primary-hover-color) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `text-sm` | `font-size: 0.875rem; line-height: 1.25rem` | +| `text-success-500` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-success-500) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `text-surface-500` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-surface-500) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `text-surface-600` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-surface-600) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `text-surface-700` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-surface-700) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `text-surface-950` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-surface-950) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `text-warn-500` | `--tw-text-opacity: 1; color: color-mix(in srgb, var(--p-warn-500) calc(100% * var(--tw-text-opacity, 1)), transparent)` | +| `text-white` | `--tw-text-opacity: 1; color: rgb(255 255 255 / var(--tw-text-opacity, 1))` | +| `text-xs` | `font-size: 0.75rem; line-height: 1rem` | +| `top-0` | `top: 0px` | +| `top-1/2` | `top: 50%` | +| `transition-[background,color,left]` | `transition-property: background,color,left; transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); transition-duration: 150ms` | +| `transition-colors` | `transition-property: color, background-color, border-color, text-decoration-color, fill, stroke; transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); transition-duration: 150ms` | +| `underline` | `text-decoration-line: underline` | +| `w-0` | `width: 0px` | +| `w-10` | `width: 2.5rem` | +| `w-full` | `width: 100%` | +| `z-10` | `z-index: 10` | + diff --git a/en/frontend/micro-frontends/theming.md b/en/frontend/micro-frontends/theming.md index 0ba98bee..b7d4ed4e 100644 --- a/en/frontend/micro-frontends/theming.md +++ b/en/frontend/micro-frontends/theming.md @@ -1,384 +1,80 @@ --- -title: "Theming Reference" -description: "The host (wippy/facade) provides the theme. Both micro frontend apps and web components consume it. The variable catalog below is the shared vocabulary…" +title: "Theme Authoring" +description: "How the facade authors a PrimeVue theme and how modules remain portable." --- -# Theming Reference +# Theme Authoring -The host (wippy/facade) provides the theme. Both micro frontend apps and web components consume it. The variable catalog below is the shared vocabulary — delivery specifics are in [Theming: Micro Frontend Apps](./micro-frontend-app-theming.md) and [Theming: Web Components](./web-component-theming.md). +The facade authors a PrimeVue theme. Modules consume that theme; they do not create parallel mini design systems. -YAML always wins. CSS custom properties (`*_css_variables`) set by the facade/host cascade to child iframes and inherit into shadow DOM. Facade selector rules (`*_custom_css`) do not *cascade* across the shadow boundary, but the Web Host **injects** them into `view.component` shadow roots as of Web Host 1.0.43 (opt-out via the component's `customCss` flag). See the [CSS Delivery Matrix](../web-host/css-injection.md#css-delivery-matrix). +Wippy currently runs PrimeVue with `theme: 'none'`. Component appearance is supplied by Wippy’s Tailwind-authored PrimeVue CSS, public runtime variables, and facade customization. -Configuration casing identifies the layer: +## Where styling belongs -| Layer | Naming | CSS example | -|---|---|---| -| Backend facade requirement names | documented snake_case | `custom_css`, `children_custom_css`, `host_custom_css`, `css_variables` | -| Registry metadata | backend schema, with one temporary casing bug | `config_overrides`; current `mountRoute` is planned to become `mount_route` | -| Nested registry configuration | frontend schema casing, preserved exactly | `proxy.injections.css.customCss`, `config_overrides.customization.customCSS` | -| Frontend AppConfig/runtime | lower camelCase | `theming.global.customCSS`, `theming.global.cssVariables` | -| Page frontend metadata | lower camelCase | `configOverrides.customization.customCSS` | -| Web-component frontend config | lower camelCase | `wippyConfig.customCss`, `hostCssKeys` | - -Do not use `customCSS` when naming a facade backend parameter, and do not use `custom_css` in frontend JavaScript or package metadata. - ---- - -## Reference — CSS variables - -All variables are defined in `theme-config.css` and set on `:root`. At runtime, the host injects the real theme — these serve as the dev-time fallback and contract. - -### Primary palette (11 vars) - -Base: `--p-primary` (default: `rgb(0, 95, 178)`) - -| Variable | Value | +| Styling concern | Owner | |---|---| -| `--p-primary-50` | `color-mix(in srgb, var(--p-primary) 5%, white)` | -| `--p-primary-100` | `color-mix(in srgb, var(--p-primary) 10%, white)` | -| `--p-primary-200` | `color-mix(in srgb, var(--p-primary) 20%, white)` | -| `--p-primary-300` | `color-mix(in srgb, var(--p-primary) 30%, white)` | -| `--p-primary-400` | `color-mix(in srgb, var(--p-primary) 40%, white)` | -| `--p-primary-500` | `var(--p-primary)` (base) | -| `--p-primary-600` | `color-mix(in srgb, var(--p-primary) 80%, black)` | -| `--p-primary-700` | `color-mix(in srgb, var(--p-primary) 70%, black)` | -| `--p-primary-800` | `color-mix(in srgb, var(--p-primary) 60%, black)` | -| `--p-primary-900` | `color-mix(in srgb, var(--p-primary) 50%, black)` | -| `--p-primary-950` | `color-mix(in srgb, var(--p-primary) 40%, black)` | - -### Secondary palette (11 vars) - -Base: `--p-secondary` (default: `#6f7385`) - -Same 50–950 structure as primary, derived via `color-mix` on `--p-secondary`, but with its own percentage ladder — steps 300/400/700/800/950 use different mix percentages than primary, so do not assume primary's exact step values when overriding the secondary base. +| PrimeVue component appearance shared across the product | Facade PrimeVue theme in `custom_css` and public theme variables | +| Host shell chrome only | Facade CSS scoped to `.wippy-host-app` | +| A shared `.p-*` rule intended for host and child roots | Global facade `custom_css`; no host scope required | +| Page-only theme override | Page configuration using supported frontend casing | +| Domain layout or novel structure | Module CSS or Tailwind | +| A necessary non-PrimeVue custom part | Module CSS, reusing public tokens and documented invariant utilities | +| An arbitrary class expected from one facade | Not portable; prohibited by FE-STYLE-001 | -### Surface palette (13 vars) +A global `.p-drawer-content` rule is valid theme implementation when it is intended for every Drawer in host and child roots. `.wippy-host-app .p-drawer-content` is appropriate only when the rule is host-specific. -**Fixed light-to-dark scale** — 0 is always lightest, 950 always darkest. The scale does NOT flip with dark mode. The light scale is the neutral Tailwind gray (no warm cast); the dark scale is a separate warm-toned ramp, so most levels differ between modes — only `0` and `50` are shared. Dark levels 600–950 carry the warmest undertones. +Moving duplicated module CSS into facade CSS does not eliminate the dependency. If the selector is not part of the shared PrimeVue theme vocabulary, it creates a private facade contract. -| Variable | Light value | Dark value | -|---|---|---| -| `--p-surface-0` | `#ffffff` | `#fff` | -| `--p-surface-50` | `#fafafa` | `#fafafa` | -| `--p-surface-100` | `#f5f5f5` | `#f4f4f5` | -| `--p-surface-200` | `#e5e5e5` | `#e4e4e7` | -| `--p-surface-300` | `#d4d4d4` | `#d4d4d8` | -| `--p-surface-400` | `#a3a3a3` | `#a1a1aa` | -| `--p-surface-500` | `#737373` | `#71717a` | -| `--p-surface-600` | `#525252` | `#545250` (warm) | -| `--p-surface-700` | `#404040` | `#403e3c` (warm) | -| `--p-surface-800` | `#262626` | `#2b2927` (warm) | -| `--p-surface-850` | `color-mix(in srgb, var(--p-surface-800) 50%, var(--p-surface-900))` | `color-mix(in srgb, var(--p-surface-800) 50%, var(--p-surface-900))` | -| `--p-surface-900` | `#171717` | `#1c1a19` (warm) | -| `--p-surface-950` | `#0a0a0a` | `#0f0e0d` (warm) | +## Semantic equality -### Danger / Warn / Success / Info / Help / Accent palettes +Semantically equivalent controls should look equivalent. Prefer PrimeVue components directly. When a genuinely custom control is needed, identify its PrimeVue visual sibling and use the same public runtime properties for color, border, focus, state, and any geometry classified theme-variable. -Each has a base var and an 11-step scale (50–950) derived via `color-mix`, same pattern as primary. +The custom part may own only the novel structure that the sibling does not provide. Reuse documented theme padding, dimensions, typography, radius, shadow, focus, and motion contracts wherever they exist. Do not copy a current literal from generated component CSS and call it inheritance. -| Family | Base variable | Default color | Purpose | -|--------|--------------|---------------|---------| -| `danger` | `--p-danger` | `rgb(239, 68, 68)` (red-500) | Errors, destructive actions | -| `success` | `--p-success` | `rgb(34, 197, 94)` (green-500) | Success states, confirmations | -| `warn` | `--p-warn` | `rgb(249, 115, 22)` (orange-500) | Warnings, caution | -| `info` | `--p-info` | `rgb(14, 165, 233)` (sky-500) | Informational messages | -| `help` | `--p-help` | `rgb(168, 85, 247)` (purple-500) | Help, hints | -| `accent` | `--p-accent` | `rgb(20, 184, 166)` (teal-500) | Highlights, special callouts | +## Runtime versus invariant properties -Override the base var to retheme the full scale — the 50–950 range auto-derives via `color-mix`. No dark-mode override block is needed. +Each shared appearance property has one policy: -### The token grammar (predictable naming) +- `theme-variable`: it must resolve through a documented public runtime variable. +- `platform-invariant`: the shared compiled Tailwind value is deliberately stable across every compliant theme. -The `--p-*` set follows one small, exceptionless grammar, so a human — or an AI agent generating styles — can *predict* a token name instead of looking it up. Two layers with a hard contract: +Do not add runtime tokens for theoretical flexibility. Add or adopt a token only after the effective-contract ledger proves a real runtime gap, an exact supported path, a real consumer, and mutation evidence. -- **Numeric scale** — `--p--{50..950}` and `--p-surface-{0..950}`: the fixed-hue anchor, **never theme-switchable** (identical in light and dark). Use it only when you explicitly do *not* want the value to flip. -- **Semantic aliases** — `--p--color` / `-contrast-color` / `-hover-color` / `-active-color`: the theme-switchable layer. `-color` always ships with its `-contrast-color` (the color to place on top of it) plus hover/active states, so no `dark:` pairing is needed. +## CSS transport is not permission -Those four aliases exist for **all eight** families (`primary`, `secondary`, `danger`, `success`, `warn`, `info`, `help`, `accent`) — zero per-family exceptions. Typography follows the same shape (`--p-font--`, below). The generated `tokens.json` manifest shipped in `@wippy-fe/theme` (name, layer, light/dark value, flip flag) is the machine-readable ground truth an agent can load. +Pages receive styles in an iframe. Web components may receive styles inside a shadow root. This explains where CSS can take effect; it does not authorize a module to depend on arbitrary facade selectors. -### Semantic variables (mode-aware) +## Runtime mode switching -These **flip with dark mode** — use these for theme-dependent styling. Do not use numbered surface vars (`--p-surface-N`) for semantic colors. - -| Variable | Light | Dark | -|---|---|---| -| `--p-primary-color` | `primary-500` | `primary-400` | -| `--p-primary-contrast-color` | `surface-0` | `surface-900` | -| `--p-primary-hover-color` | `primary-600` | `primary-300` | -| `--p-primary-active-color` | `primary-700` | `primary-200` | -| `--p-text-color` | `surface-700` | `surface-0` | -| `--p-text-hover-color` | `surface-800` | `surface-0` | -| `--p-text-muted-color` | `surface-500` | `surface-400` | -| `--p-text-hover-muted-color` | `surface-600` | `surface-300` | -| `--p-content-background` | `surface-0` | `surface-900` | -| `--p-content-border-color` | `surface-200` | `surface-700` | -| `--p-content-hover-background` | `surface-100` | `surface-800` | -| `--p-content-hover-color` | `surface-800` | `surface-0` | -| `--p-highlight-background` | `primary-50` | `primary-400 @ 16%` | -| `--p-highlight-color` | `primary-700` | `white @ 87%` | -| `--p-highlight-focus-background` | `primary-100` | `primary-400 @ 24%` | -| `--p-highlight-focus-color` | `primary-800` | `white @ 87%` | -| `--p-content-border-radius` | `6px` | `6px` | - -### Family aliases (all families) - -The four `--p-primary-*` alias rows above exist identically for every family, remapped the same way per mode: - -| Alias | Light | Dark | -|---|---|---| -| `--p--color` | `-500` | `-400` | -| `--p--contrast-color` | `surface-0` | `surface-900` | -| `--p--hover-color` | `-600` | `-300` | -| `--p--active-color` | `-700` | `-200` | - -`var(--p-success-color)` + `var(--p-success-contrast-color)` is a mode-correct fill/on-color pair; `--p-success-500` is the fixed anchor that never flips. - -### Typography tokens - -`--p-font--` — roles `heading` / `body` / `mono`, props `family`, `scale` (size multiplier), `line-height`, `letter-spacing`, `stretch`, `variation-settings`. Mode-independent; defaults are visually inert. They steer rendered-content typography (markdown headings/body, code blocks), not host chrome. - -| Token | Default | Effect | -|---|---|---| -| `--p-font-heading-family` | `--v-font-family-head, Arial` | Heading font | -| `--p-font-heading-scale` | `1` | Multiplies heading sizes | -| `--p-font-body-scale` | `1` | Multiplies body sizes | -| `--p-font--line-height` | `1.5` | Per-role line-height | -| `--p-font--letter-spacing` | `normal` | Per-role tracking | -| `--p-font--stretch` | `normal` | Variable-font width | -| `--p-font--variation-settings` | `normal` | Variable-font axes | -| `--p-font-mono-family` | `ui-monospace` | Code / markdown mono | - -Which font *files* load (families, weights, `size-adjust`, ascent/descent overrides) is declared via the facade `fonts` theming param and compiled to distributed CSS by the host. - ---- +The public theme-mode contract is AppConfig plus `@wippy-fe/proxy`: -## Reference — Dark mode +```typescript +import { host, on } from '@wippy-fe/proxy' -Variables switch at `@media (prefers-color-scheme: dark)`. Key changes: - -- `--p-primary` base shifts from `rgb(0, 95, 178)` to `rgb(0, 125, 178)` (brighter) -- `--p-primary-color` shifts from `primary-500` to `primary-400` -- `--p-content-background` shifts from `surface-0` to `surface-900` -- `--p-text-color` shifts from `surface-700` to `surface-0` -- Surface levels diverge between modes — light uses neutral gray, dark uses a separate warm ramp (only `0` and `50` are shared); levels 600–950 carry the warmest undertones - -**Universal rule:** every level of custom CSS must produce a sensible result in both light and dark modes. - -```css -.my-thing { - background: var(--p-content-background); - color: var(--p-text-color); +async function setThemeMode(mode: 'auto' | 'light' | 'dark') { + await new Promise((resolve, reject) => { + const stop = on('@theme', (appliedMode) => { + if (appliedMode !== mode) return + stop() + const currentMode = host.getThemeMode() + if (currentMode !== mode) { + reject(new Error(`Theme propagation mismatch: ${currentMode}`)) + return + } + resolve() + }) + host.setThemeMode(mode) + }) } -``` - -If the brand requires explicit light and dark palette values, define them in facade or page-level `cssVariables`, not in module CSS. - -In backend `css_variables` YAML, use `@light` / `@dark` keys: -```yaml -css_variables: - "--p-primary": "#005fb2" - "@light": - "--p-content-background": "#fafafa" - "@dark": - "--p-content-background": "#1c1a19" +await setThemeMode('dark') ``` ---- - -## Reference — Tailwind severity utility classes - -Provided by `tailwindcss-primeui` plugin (included in the shared Tailwind preset). Work with `bg-`, `text-`, `border-`, `outline-`, `ring-` prefixes. - -**Rule: always use semantic severity classes over raw Tailwind color names when the color conveys meaning.** Never `text-red-500` for danger, `bg-green-100` for success, etc. - -| Class suffix | Maps to | -|---|---| -| `primary` | `--p-primary-color` | -| `primary-{0,50,100,...,950}` | Full primary shade range | -| `surface-{0,50,100,...,950}` | Full surface shade range | -| `danger-{50..950}` | `--p-danger` scale | -| `success-{50..950}` | `--p-success` scale | -| `warn-{50..950}` | `--p-warn` scale | -| `info-{50..950}` | `--p-info` scale | -| `help-{50..950}` | `--p-help` scale | -| `accent-{50..950}` | `--p-accent` scale | -| `secondary-{50..950}` | `--p-secondary` scale | - -Semantic color utilities: - -| Class | Maps to | -|---|---| -| `.text-color` | `--p-text-color` | -| `.text-muted-color` | `--p-text-muted-color` | -| `.bg-highlight` | Highlighted state (selected items, active rows) | -| `.border-surface` | `--p-content-border-color` | -| `.rounded-border` | `--p-content-border-radius` | - -Animation utilities: `.animate-fadein`, `.animate-fadeout`, `.animate-slidedown`, `.animate-slideup`, `.animate-scalein`, `.animate-fadeinleft`, `.animate-fadeinright`, `.animate-fadeinup`, `.animate-fadeindown`, `.animate-duration-{ms}`, `.animate-delay-{ms}`, `.animate-ease-*`. - ---- - -## Reference — Host UI customization (`--wippy-host-*` + BEM classes) - -Override host chrome through `AppConfig.theming.host.cssVariables` / `customCSS`. Shared brand theme belongs in `AppConfig.theming.global`; child-only overrides belong in `AppConfig.theming.children` and are projected into each child iframe as `config.theming.global`. - -Scope host-chrome-only class overrides to `.wippy-host-app`. Intentionally shared facade selectors, including global `.p-*` PrimeVue rules, remain unscoped when they must apply to both host and child roots. Use `children_custom_css` for child-only selectors. - -### Layout & sidebar - -| Variable | Default | Description | -|---|---|---| -| `--wippy-host-sidebar-width-open` | `16rem` | Sidebar width when expanded | -| `--wippy-host-sidebar-width-closed` | `3.5rem` | Sidebar width when collapsed | - -**BEM classes** (scope with `.wippy-host-app`): - -| Class | Element | -|---|---| -| `.layout` | Root layout wrapper | -| `.layout__sidebar` | Sidebar container | -| `.layout__sidebar-header` | Sidebar header (logo + toggle) | -| `.layout__sidebar-nav` | Navigation list area | -| `.layout__main` | Main content area (right of sidebar) | - -### Splitter gutter - -| Variable | Default | Description | -|---|---|---| -| `--wippy-host-splitter-width` | `1px` | Visible line width | -| `--wippy-host-splitter-hit-area` | `10px` | Draggable hit area width (transparent) | -| `--wippy-host-splitter-color` | `var(--p-surface-200)` (light) / `var(--p-surface-600)` (dark) | Line color | - -### Chat messages - -| Variable | Default | Description | -|---|---|---| -| `--wippy-host-message-radius` | `1rem` | Message bubble border radius | -| `--wippy-host-message-padding-x` | `1rem` | Message horizontal padding | -| `--wippy-host-message-padding-y` | `0.5rem` | Message vertical padding | -| `--wippy-host-message-user-bg` | `var(--p-primary-50)` | User message background | -| `--wippy-host-message-agent-bg` | `var(--p-warn-50)` (light) / `var(--p-surface-800)` (dark) | Agent message background | -| `--wippy-host-tool-bg` | `var(--p-help-50)` | Tool call background | -| `--wippy-host-tool-border` | `var(--p-help-300)` | Tool call left border | -| `--wippy-host-avatar-size` | `2rem` | Message avatar diameter | - -**BEM classes** (scope with `.wippy-host-app`): - -| Class | Element | -|---|---| -| `.chat-message` | Message row container | -| `.chat-message--user` | User message modifier | -| `.chat-message--agent-message` | Agent message modifier | -| `.chat-message--tool` | Tool call message modifier | -| `.chat-message--error` | Error message modifier | -| `.chat-message__avatar` | Avatar wrapper | -| `.chat-message__avatar-icon` | Avatar icon circle | -| `.chat-message__content` | Message bubble | -| `.chat-message__body` | Message text content | -| `.chat-message__footer` | Timestamp row | -| `.chat-message__tool-name` | Tool name label | -| `.chat-message__tool-icon` | Tool icon | -| `.chat-message__agent-content` | Agent name system line | -| `.chat-message__model-content` | Model name system line | -| `.chat-message__files` | Attached files row | -| `.chat-tool-group` | Inline tool call badge group | -| `.chat-tool-group__badge` | Individual tool badge | -| `.chat-tool-group__badge--success` | Completed tool badge | -| `.chat-tool-group__badge--error` | Failed tool badge | -| `.chat-tool-group__badge--processing` | In-progress tool badge | -| `.chat-tool-group__icon` | Badge icon | - -### Chat input - -**BEM classes** (scope with `.wippy-host-app`): - -| Class | Element | -|---|---| -| `.chat-input` | Input bar container | -| `.chat-input__group` | Input field + buttons wrapper | -| `.chat-input__textarea` | Message textarea | -| `.chat-input__attach-button` | Attachment button | -| `.chat-input__send-button` | Send button | -| `.chat-input__stop-button` | Stop generation button | -| `.chat-input__upload-list` | Upload queue list | -| `.chat-input__prompts` | Suggested prompts area | - -### Chat container - -**BEM classes** (scope with `.wippy-host-app`): - -| Class | Element | -|---|---| -| `.chat-container` | Outer chat wrapper | -| `.chat-container--selected` | Has active session | -| `.chat-container--non-selected` | No session selected | -| `.chat-container__empty-state` | Empty state wrapper | -| `.chat-container__empty-state-icon` | Empty state icon | -| `.chat-container__empty-state-title` | Empty state heading | -| `.chat-container__empty-state-description` | Empty state text | -| `.chat-container__drop-zone` | File drag-and-drop overlay | -| `.chat-container__drop-zone-icon` | Drop zone icon | - -### Session selector - -**BEM classes** (scope with `.wippy-host-app`): - -| Class | Element | -|---|---| -| `.session-selector` | Selector wrapper | -| `.session-selector__dropdown` | Dropdown component | -| `.session-selector__option` | Session option row | -| `.session-selector__active-dot` | Active session indicator | - -### Root - -| Class | Element | -|---|---| -| `.wippy-host-app` | Application root element — scope all host-only CSS overrides to this | - ---- - -## Anti-patterns (REJECT list) - -These apply to both micro frontend apps and web components. - -### Color / semantic vars - -- Hardcoded hex/rgb for semantic colors: `color: #ef4444`, `background: rgb(34, 197, 94)`. Always use `var(--p-danger-*)` / `var(--p-success-*)` / `var(--p-warn-*)` / etc. -- Raw Tailwind color classes for semantic meaning: `text-red-500`, `bg-green-100`, `border-yellow-300`. Use severity classes: `text-danger-500`, `bg-success-100`, `border-warn-300`. -- Numbered `--p-surface-N` for theme-dependent semantic colors (e.g. `color: var(--p-surface-700)` for "muted text"). Use semantic aliases: `var(--p-text-color)`, `var(--p-text-muted-color)`, `var(--p-content-background)`, `var(--p-content-border-color)`. -- Component-level custom color palettes (`--my-app-red`, `--feature-blue`) declared without a documented design reason. -- Using severity tokens in decorative contexts (`--p-danger-500` for "red chart category") — that implies meaning the element doesn't have. - -### Placement / scope - -- `:root { --p-* }` overrides inside a child app's `src/styles.css`. Put shared values in facade `css_variables`, or page-specific values in frontend `configOverrides.customization.cssVariables`. -- Raw `.p-button { … }` / `.p-dialog { … }` selectors inside a child app's `src/styles.css`. Put shared PrimeVue selector overrides in facade `custom_css`, or page-specific overrides in frontend `configOverrides.customization.customCSS`. -- App-side `