@imfusion/web-ui 0.6.1-dev.9.g317bd6f2 → 0.6.2-dev.1.gf73fc5d3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/LICENSE.txt +30 -0
  2. package/README.md +99 -173
  3. package/THIRD_PARTY_NOTICES.md +34 -0
  4. package/bin/install.js +28 -10
  5. package/dist/code-BFMQnmu9.js +147 -0
  6. package/dist/codegen/gen-code-highlight-theme.d.ts +1 -0
  7. package/dist/components/code/code.d.ts +5 -4
  8. package/dist/components/stack/stack.d.ts +1 -1
  9. package/dist/components/toast/index.d.ts +2 -0
  10. package/dist/components/toast/toast.d.ts +200 -0
  11. package/dist/components/toast/toast.meta.d.ts +2 -0
  12. package/dist/components/typo/typo.d.ts +23 -22
  13. package/dist/icons/icon-config.d.ts +12 -0
  14. package/dist/{icons-wBmF0U2x.js → icons-Cy1HAosO.js} +1 -1
  15. package/dist/icons.js +1 -1
  16. package/dist/index.d.ts +1 -0
  17. package/dist/index.js +1278 -1069
  18. package/dist/integrations/code-highlight/highlighter.d.ts +24 -0
  19. package/dist/integrations/code-highlight.js +80 -47
  20. package/dist/integrations/image-display-options.js +2 -2
  21. package/dist/provider/web-ui-provider.d.ts +3 -3
  22. package/dist/style.css +1 -1
  23. package/dist/{tabs-CMKvMF4E.js → tabs-DIe1Utiy.js} +2 -0
  24. package/docs/assets/imfusion-banner.svg +16 -0
  25. package/package.json +10 -8
  26. package/src/docgen/doc.gen.json +515 -1
  27. package/src/llms/install-templates/AGENTS.md +15 -18
  28. package/src/llms/llms.gen.txt +39 -33
  29. package/src/llms/skills/imf-web-ui/SKILL.md +30 -39
  30. package/src/llms/skills/imf-web-ui-audit/SKILL.md +50 -102
  31. package/src/llms/skills/imf-web-ui-components/SKILL.md +47 -104
  32. package/src/llms/skills/imf-web-ui-conventions/SKILL.md +44 -52
  33. package/src/llms/skills/imf-web-ui-conventions/templates/AUDIT_CHECKLIST.md +1 -0
  34. package/src/llms/skills/imf-web-ui-conventions/topics/agent-tooling.md +40 -62
  35. package/src/llms/skills/imf-web-ui-conventions/topics/assets.md +11 -12
  36. package/src/llms/skills/imf-web-ui-conventions/topics/authentication.md +31 -46
  37. package/src/llms/skills/imf-web-ui-conventions/topics/class-names.md +18 -23
  38. package/src/llms/skills/imf-web-ui-conventions/topics/components.md +20 -69
  39. package/src/llms/skills/imf-web-ui-conventions/topics/data.md +50 -146
  40. package/src/llms/skills/imf-web-ui-conventions/topics/docs-structure.md +17 -23
  41. package/src/llms/skills/imf-web-ui-conventions/topics/git.md +15 -20
  42. package/src/llms/skills/imf-web-ui-conventions/topics/library-boundary.md +28 -19
  43. package/src/llms/skills/imf-web-ui-conventions/topics/library-setup.md +20 -16
  44. package/src/llms/skills/imf-web-ui-conventions/topics/npm-project.md +28 -42
  45. package/src/llms/skills/imf-web-ui-conventions/topics/project-structure.md +28 -30
  46. package/src/llms/skills/imf-web-ui-conventions/topics/react.md +28 -74
  47. package/src/llms/skills/imf-web-ui-conventions/topics/styling.md +65 -62
  48. package/src/llms/skills/imf-web-ui-conventions/topics/testing.md +12 -14
  49. package/src/llms/skills/imf-web-ui-conventions/topics/tokens.md +9 -4
  50. package/src/llms/skills/imf-web-ui-conventions/topics/tooling.md +40 -68
  51. package/src/llms/skills/imf-web-ui-conventions/topics/typescript.md +26 -50
  52. package/src/llms/skills/imf-web-ui-conventions/topics/validation.md +19 -25
  53. package/src/llms/skills/imf-web-ui-setup/SKILL.md +45 -64
  54. package/src/llms/skills/imf-web-ui-update/SKILL.md +48 -114
  55. package/src/llms/skills/imf-web-ui-ux/SKILL.md +64 -92
  56. package/src/llms/skills/imf-web-ui-ux/references/forms.md +16 -36
  57. package/src/llms/skills/imf-web-ui-ux/references/usability-heuristics.md +14 -27
  58. package/src/llms/skills/imf-web-ui-ux/references/visual-design.md +22 -38
  59. package/src/llms/tokens.gen.json +5 -5
  60. package/bin/install.test.ts +0 -329
  61. package/dist/code-Blo48PGr.js +0 -136
  62. package/dist/icons/icon-config-provider.d.ts +0 -8
  63. package/dist/icons/icon-context.d.ts +0 -4
@@ -1,26 +1,30 @@
1
1
  # Library setup
2
2
 
3
- Library wiring (styles import + `WebUIProvider`) for anyone using `@imfusion/web-ui` — `imf-web-ui-setup library-setup`
4
- proposes a bootstrap change, `imf-web-ui-audit library-setup` is the read-only health check.
3
+ Wire `@imfusion/web-ui` at the app entry point. The setup skill can propose a fix; the audit skill can check it.
5
4
 
6
5
  ## Entry-point wiring
7
6
 
8
- - Every consumer entry point has exactly two lines, in this order:
7
+ Import the stylesheet before the components and mount `WebUIProvider` once around the app:
9
8
 
10
- ```tsx
11
- import "@imfusion/web-ui/styles.css";
12
- import { WebUIProvider, Button } from "@imfusion/web-ui";
13
- ```
9
+ ```tsx
10
+ import "@imfusion/web-ui/styles.css";
11
+ import { Button, WebUIProvider } from "@imfusion/web-ui";
14
12
 
15
- - Wrap the app root in `<WebUIProvider>` once — components rendered outside it lack the theme and CSS-variable context they
16
- expect.
17
- - Never import a Base UI (or other upstream) stylesheet or component directly — everything a web-ui component needs is inside
18
- `styles.css` and the package's own exports. What may be reached for when the library lacks a component:
19
- [library boundary](library-boundary.md).
13
+ export function App() {
14
+ return (
15
+ <WebUIProvider>
16
+ <Button>Save</Button>
17
+ </WebUIProvider>
18
+ );
19
+ }
20
+ ```
21
+
22
+ Components outside the provider do not receive the library's provider context. Keep upstream imports behind the library; see
23
+ [library-boundary.md](library-boundary.md).
20
24
 
21
25
  ## Symptoms of broken setup
22
26
 
23
- - Components render but look unstyled → the `styles.css` import is missing from the entry point.
24
- - Components render but ignore the theme (wrong colors, CSS variables not resolving) → mounted outside `<WebUIProvider>`.
25
- - An integration component throws on import → its optional peer dependency is not installed; check the component's
26
- description in the docgen index (`imf-web-ui-components`) for which peer to add to `package.json`.
27
+ - Unstyled components usually mean the `styles.css` import is missing.
28
+ - Wrong colors or unresolved variables usually mean the component is outside `WebUIProvider`.
29
+ - An integration import error usually means its optional peer is missing. Check the component entry in
30
+ `imf-web-ui-components` for the package to install.
@@ -1,53 +1,39 @@
1
1
  # npm project
2
2
 
3
- The npm side of an ImFusion frontend: `package.json`, scripts, dependencies. `imf-web-ui-setup` audits against this file.
3
+ Keep project metadata, scripts, and dependency policy in `package.json` and the lockfile.
4
4
 
5
5
  ## package.json
6
6
 
7
- ```jsonc
8
- {
9
- "name": "@imfusion/scan-review",
10
- "type": "module",
11
- "private": true, // only when the package is not meant to be published
12
- "engines": { "node": ">=22" },
13
- "imports": { "#/*": "./src/*" }
14
- }
15
- ```
16
-
17
- - `"type": "module"` always.
18
- - `"private": true` only for apps that never publish.
19
- - Pin Node via `engines.node` or `.nvmrc` — never a personal version manager's config.
20
- - Wire the `#/` alias through `imports` ([project-structure.md](project-structure.md)).
7
+ - Use ESM with `"type": "module"`.
8
+ - Set the Node floor with `engines.node` or `.nvmrc`.
9
+ - Use the `#/` source alias when the project has one, and wire it through the package `imports` field.
10
+ - Set `private: true` only for packages that are never published.
21
11
 
22
12
  ## Scripts
23
13
 
24
- Same name, same meaning, every repo — "what can I run to check this?" is tab-completing `verify:`.
25
-
26
- | Script | Runs |
27
- | ------------------ | --------------------------------------------------------------------- |
28
- | `dev` | dev server |
29
- | `build` | production build |
30
- | `verify:deps` | exact-pin check over `dependencies` and `devDependencies` |
31
- | `verify:format` | `prettier --check .` |
32
- | `verify:lint` | `eslint . --cache --max-warnings=0` |
33
- | `verify:typecheck` | `tsc --noEmit` (or `tsc -b --noEmit` in a project-references setup) |
34
- | `verify:tests` | `vitest run` |
35
- | `verify:knip` | `knip` — dead-code and unused-export check |
36
- | `verify:staged` | staged-file subset, called by the pre-commit hook |
37
- | `verify:full` | every `verify:*` check plus the build; what CI runs |
38
- | `format` | `prettier --write .` |
39
- | `lint` | `eslint . --cache --fix` |
40
- | `git:config` | see [git.md](git.md); run by hand once per clone, named in the README |
41
-
42
- - **Every check is `verify:*`** — one namespace for everything that reads and reports.
43
- - **Write-mode scripts keep the tool name** (`format`, `lint`): changing files isn't verifying — no prefix, no `:fix` suffix.
44
- - `verify:staged` is fast and partial: staged-file format/lint plus the relevant project-wide checks for staged source or
45
- config changes, including `verify:knip` — a passing commit is not CI green.
46
- - `verify:full` is the CI gate.
14
+ Use the same names for the same checks where the project supports them:
15
+
16
+ | Script | Purpose |
17
+ | ------------------ | --------------------------------------- |
18
+ | `dev` | Start local development. |
19
+ | `build` | Create a production build. |
20
+ | `verify:format` | Check Prettier. |
21
+ | `verify:lint` | Check ESLint. |
22
+ | `verify:typecheck` | Run TypeScript without emitting. |
23
+ | `verify:tests` | Run tests once. |
24
+ | `verify:knip` | Check dead code and unused exports. |
25
+ | `verify:staged` | Run the pre-commit scope. |
26
+ | `verify:full` | Run the CI scope. |
27
+ | `format` | Rewrite formatting. |
28
+ | `lint` | Rewrite fixable lint issues. |
29
+ | `git:config` | Apply the repository Git configuration. |
30
+
31
+ Read the repository's actual `package.json` before running a command. A project may not have every script in the table.
47
32
 
48
33
  ## Dependencies
49
34
 
50
- - **Pin exactly** — no `^`, `~`, or `latest`, in `dependencies` and `devDependencies` alike: `verify:deps` catches drift,
51
- `save-exact=true` in `.npmrc` prevents it.
52
- - **`ignore-scripts=true` in `.npmrc`** — blocks lifecycle scripts on install (the supply-chain vector); setup that matters
53
- is a command someone runs, not a hook that fires on install.
35
+ Pin dependencies exactly. Avoid `^`, `~`, and `latest` in runtime and development dependencies. Set `save-exact=true` in
36
+ `.npmrc` so new installs follow the same rule.
37
+
38
+ Use `ignore-scripts=true` in `.npmrc` when the project relies on explicit setup commands rather than install-time lifecycle
39
+ scripts.
@@ -1,44 +1,42 @@
1
1
  # Project structure
2
2
 
3
- Kebab-case throughout, folders and files alike; exported symbols stay PascalCase — only the filename is kebab.
3
+ Use kebab-case for folders and files. Exported symbols use PascalCase.
4
4
 
5
5
  ## Layout
6
6
 
7
- ```
7
+ ```text
8
8
  src/
9
- main.tsx # providers and router bootstrap; one WebUIProvider at the app root
10
- routes/ # TanStack Router file-based routes; routing only — they compose, they don't fetch inline
11
- __root.tsx # document-wide error/not-found boundary and Outlet
12
- _public/ # anonymous route group; resolves the current user when public pages need it
13
- route.tsx
14
- _app/ # pathless authenticated route group; guards the subtree before children render
15
- route.tsx
16
- api/ # one folder per API topic — layout and behaviour in data.md
17
- auth/ # one current-user query plus server-owned login/logout URL helpers
18
- components/ # grouped by kind — anatomy in components.md
19
- app-shell/ # optional persistent authenticated chrome: brand, navigation, and session action
20
- http/ # transport: client, error normalisation — the only transport-aware place (data.md)
21
- lib/ # framework-free helpers, each with a colocated .test.ts when it has decisions to test
22
- auth/
23
- login-url.ts
24
- login-url.test.ts
9
+ main.tsx
10
+ routes/
11
+ __root.tsx
12
+ _public/route.tsx
13
+ _app/route.tsx
14
+ api/
15
+ http/
16
+ lib/
17
+ components/
25
18
  ```
26
19
 
27
- - Each subtree's rules live with its topic: [data.md](data.md) for `api/` and `http/`, [components.md](components.md) for
28
- `components/`.
20
+ `main.tsx` owns providers and router setup. Routes compose pages and their route data; API topics own data options and
21
+ schemas; `http/` owns transport; `lib/` holds framework-free helpers; `components/` holds UI.
22
+
23
+ The authenticated route group may contain the persistent `AppShell`. The full data layout is in [data.md](data.md), and
24
+ component folders are in [components.md](components.md).
29
25
 
30
26
  ## Application boundary
31
27
 
32
- - Start every frontend with the root providers, a current-user query, and pathless `_public/` and `_app/` route groups —
33
- contract in [authentication.md](authentication.md); add feature routes only after that boundary exists.
34
- - Greenfield: `_app/` starts with a minimal `AppShell` — ImFusion logo, route navigation, and a stable session-action area
35
- around the route `Outlet`.
36
- - Omit the shell only when the human explicitly says the product has no persistent authenticated navigation; the `_app/`
37
- guard stays.
38
- - An established project preserves its working choice; public pages can use a small branded header instead.
28
+ Create the root providers, current-user boundary, and pathless `_public/` and `_app/` groups before adding feature routes. A
29
+ new app starts `_app/` with a minimal `AppShell` unless it has no persistent authenticated navigation. An established app
30
+ keeps its working shell choice.
39
31
 
40
32
  ## Imports
41
33
 
42
- - `#/` alias for anything outside the current folder, plain `./` for siblings.
43
- - The alias is always `#/` → `src/`: `#` is Node's own subpath-import prefix, so it can't collide with an npm scope.
44
- - Wire it once, through `package.json`'s `imports` field (toolchain-native), with a matching tsconfig `paths` entry.
34
+ Use `./` for siblings and `#/` for imports elsewhere in `src/`:
35
+
36
+ ```ts
37
+ import { Button } from "#/components/button";
38
+ import { formatDate } from "./format-date";
39
+ ```
40
+
41
+ Wire `#/` once through `package.json`'s `imports` field and the matching TypeScript paths. It uses Node's `#` subpath prefix,
42
+ so it cannot conflict with an npm package scope.
@@ -1,90 +1,52 @@
1
1
  # React
2
2
 
3
- House defaults for React around `@imfusion/web-ui`. Links are for you, the agent — authoritative when a case here is
4
- ambiguous; hand them to the human only if asked.
3
+ Keep data flow one-way and components focused. Pages decide how data is loaded; presentational components decide how it
4
+ looks.
5
5
 
6
6
  ## Composition: pages (smart containers), partials, dumb components
7
7
 
8
- The architecture principle, top-down — rooted in Dan Abramov's
9
- [Presentational and Container Components](https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0) and
10
- [Thinking in React](https://react.dev/learn/thinking-in-react). Styling lives exclusively in dumb and layout components,
11
- nowhere else:
12
-
13
- - **Pages are the smart containers.** The route component in `routes/` is the container — don't go looking for `*Container`
14
- files. It owns how things _work_: loaders prefetch, the component fetches data, orchestrates, composes partials and passes
15
- data down. Zero styling — a page wanting CSS (two panels side by side) means a layout component, not an inline style. A
16
- standalone container earns its place only off-route (a modal fetching its own data).
17
- - **Partials** are pure composition: reusable units of dumb and layout components with light logic and zero styling of their
18
- own, extracted because they read as a unit. A partial with a CSS file is a red flag — the styled element wants to be its
19
- own dumb component, or the wrapper wants to be a layout component.
20
- - **Layout components** own _arrangement_ and nothing else: `Stack`-/`Row`-based wrappers with token gaps, a page grid, a
21
- section frame.
22
- - **Dumb components** own how things _look_: style and compose library primitives, take plain data and callbacks as props; no
23
- fetching, routing, or business logic. All non-layout styling lives here and only here — logic-free dumb components (the
24
- `@imfusion/web-ui` layer) keep screens restylable, testable with plain props, resilient to library updates.
25
- - Wrapping library primitives in app-level dumb components: [library-boundary.md](library-boundary.md).
8
+ - **Pages** live in `routes/`. They load data, read URL state, coordinate actions, and compose the view. They do not own
9
+ component CSS.
10
+ - **Partials** are reusable compositions extracted because they read as a unit. They have light logic and no styling of their
11
+ own.
12
+ - **Layout components** arrange content with `Stack`, `Row`, grids, and token gaps.
13
+ - **Dumb components** receive data and callbacks, render UI, and own their styles. They do not fetch, route, or contain
14
+ business rules.
26
15
 
27
16
  ```tsx
28
- // Dumb — renders what it's given
29
- function UserCard({ name, role, onEdit }: { name: string; role: string; onEdit: () => void }) {
17
+ function UserCard({ name, onEdit }: { name: string; onEdit: () => void }) {
30
18
  return (
31
19
  <Card.Root>
32
- <Card.Content>
33
- <Typo>{name}</Typo>
34
- <Chip>{role}</Chip>
35
- </Card.Content>
36
- <Card.Footer>
37
- <Button onClick={onEdit}>Edit</Button>
38
- </Card.Footer>
20
+ <Typo.P>{name}</Typo.P>
21
+ <Button onClick={onEdit}>Edit</Button>
39
22
  </Card.Root>
40
23
  );
41
24
  }
42
25
  ```
43
26
 
44
- ```tsx
45
- // Smart — knows where data comes from, renders the dumb component
46
- function UserCardContainer({ userId }: { userId: string }) {
47
- const user = useUserQuery(userId);
48
- const openEditor = useEditorNavigation(userId);
49
- return <UserCard name={user.data.name} role={user.data.role} onEdit={openEditor} />;
50
- }
51
- ```
52
-
53
27
  ## Compose, don't configure
54
28
 
55
- - Build screen-level pieces by composing primitives (`Stack`, `Row`, `Card`, dumb components), not one component with a dozen
56
- boolean props — a prop list that reads like a settings page wanted to be two or three components.
57
- - Lift state shared between siblings to the nearest common parent
58
- ([Sharing State Between Components](https://react.dev/learn/sharing-state-between-components)); never sync copies.
29
+ Compose small parts instead of making one component configurable through many boolean props. Lift shared state to the nearest
30
+ common parent.
59
31
 
60
32
  ## Put state where its truth lives
61
33
 
62
- Work down this list; stop at the first match:
34
+ Choose the first suitable level:
63
35
 
64
- 1. **URL state** — shareable via the address bar (filters, sort, pagination, active tab) → TanStack Router search params;
65
- back button and copied links come free. These cross an external boundary — parse per [validation.md](validation.md).
66
- 2. **Server state** — from an API → TanStack Query's cache ([data.md](data.md)); never copy it into `useState` — that's how
67
- stale-UI bugs are born.
68
- 3. **Subtree state** — resets on leave (wizard progress) → React context.
69
- 4. **Client state** — app-wide and persistent → TanStack Store, and only now: tiers 1–2 usually dissolve the "we need a
70
- store" instinct.
71
- 5. **Local state** — one component's own (input value, open/closed, a draft, a hover flag) → `useState`. Most state is local;
72
- keep it in the component, no library.
36
+ 1. **URL state**: filters, sort, pagination, and active tab. Use TanStack Router search params and validate them.
37
+ 2. **Server state**: API data. Use TanStack Query; do not copy it into `useState`.
38
+ 3. **Subtree state**: state shared by a subtree and reset when it leaves. Use React context.
39
+ 4. **Client state**: app-wide persistent state. Use TanStack Store only when the earlier levels do not fit.
40
+ 5. **Local state**: one component's open state, draft, or input value. Use `useState`.
73
41
 
74
- - A form turning drafts into a submitted domain value validates that boundary per [validation.md](validation.md).
75
- - Shape the state itself per [Choosing the State Structure](https://react.dev/learn/choosing-the-state-structure) —
76
- especially its rules on redundant and duplicated state.
42
+ Shape state so it is not redundant or duplicated. Validate a form draft when it becomes a submitted domain value.
77
43
 
78
44
  ## Effects: last resort, and named
79
45
 
80
- - No `useEffect` for: derived values (render or `useMemo`), responses to user actions (the event handler), server sync (the
81
- data-fetching layer). Effects only synchronize with systems _outside_ React.
82
- - Read [You Might Not Need an Effect](https://react.dev/learn/you-might-not-need-an-effect) — the definitive misuse catalog —
83
- before every effect you're tempted to write.
84
- - Extract a genuine effect into a custom hook named for its purpose (`useSyncedScroll`, `useDocumentTitle`, `useHotkey`),
85
- never an anonymous inline `useEffect`: the name documents intent, the hook isolates the dependency array. Pattern:
86
- [Reusing Logic with Custom Hooks](https://react.dev/learn/reusing-logic-with-custom-hooks).
87
- - The rare effect that stays inline still names its callback, so intent survives without a comment:
46
+ Use `useEffect` only to synchronize with something outside React. Do not use it for derived values, event responses, or
47
+ server sync.
48
+
49
+ A genuine effect belongs in a named custom hook. If it stays inline, name the callback:
88
50
 
89
51
  ```tsx
90
52
  useEffect(
@@ -95,15 +57,7 @@ useEffect(
95
57
  );
96
58
  ```
97
59
 
98
- - A dependency sourced from a hook or query return: destructure the primitive first — [typescript.md](typescript.md#naming).
99
-
100
- ## Reading list
101
-
102
- Consult while building; each is the authority for its topic:
60
+ Destructure a primitive before putting it in an effect dependency array. Keep the returned query or route object intact
61
+ elsewhere so its namespace remains visible.
103
62
 
104
- - [Thinking in React](https://react.dev/learn/thinking-in-react) — decomposition and one-way data flow
105
- - [Keeping Components Pure](https://react.dev/learn/keeping-components-pure) — why dumb components stay dumb
106
- - [Choosing the State Structure](https://react.dev/learn/choosing-the-state-structure) — shaping state without duplication
107
- - [Sharing State Between Components](https://react.dev/learn/sharing-state-between-components) — lifting state
108
- - [You Might Not Need an Effect](https://react.dev/learn/you-might-not-need-an-effect) — the effect misuse catalog
109
- - [Reusing Logic with Custom Hooks](https://react.dev/learn/reusing-logic-with-custom-hooks) — named effects live here
63
+ Read the React documentation when a decomposition or effect decision is unclear.
@@ -1,88 +1,91 @@
1
1
  # Styling
2
2
 
3
- How app CSS is written and how it meets `@imfusion/web-ui`.
3
+ Use native CSS and Web UI's public seams. The library's CSS Modules and token names are not consumer implementation details.
4
4
 
5
5
  ## CSS authoring
6
6
 
7
- - **CSS Modules with native CSS only** — no Sass, no CSS-in-JS, no utility-class framework: nesting and custom properties
8
- already cover DRY. Stylesheet location and ownership: [components.md](components.md).
9
- - Nest pseudo-elements, states, and child selectors under the root so the prefix is written once:
7
+ - Use CSS Modules with native nesting. Do not add Sass, CSS-in-JS, or a utility-class framework for ordinary component
8
+ styles.
9
+ - Nest states, pseudo-elements, and child selectors under the root.
10
+ - Lift repeated values into custom properties, especially when a state or variant changes several descendants.
11
+ - Keep structurally different rules explicit; not every repeated value needs an abstraction.
10
12
 
11
- ```css
12
- .root {
13
- transition: clip-path var(--ease);
13
+ ```css
14
+ .root {
15
+ transition: opacity var(--imf-ui-duration-quick-2) var(--imf-ui-ease-2);
14
16
 
15
- &:focus-visible {
16
- outline: var(--imf-ui-border-size-2) solid var(--imf-ui-color-fg-support);
17
- }
18
-
19
- &[data-disabled] {
20
- opacity: 0.5;
21
- }
17
+ &:focus-visible {
18
+ outline: var(--imf-ui-border-size-2) solid var(--imf-ui-color-fg-primary);
22
19
  }
23
- ```
24
20
 
25
- - Lift a repeated literal (an easing, a colour-math result) into a custom property; custom properties also carry
26
- per-state/per-variant values down the tree — native parameterisation, no mixin needed.
27
- - Don't merge rules that only look similar — structurally different output (three distinct `clip-path` polygons) isn't
28
- repetition; keep it explicit.
21
+ &[data-disabled] {
22
+ opacity: 0.5;
23
+ }
24
+ }
25
+ ```
29
26
 
30
27
  ## Build custom UI from tokens
31
28
 
32
- - Custom UI the library doesn't cover (a stat widget, a custom panel) uses names from the shipped token index
33
- ([tokens.md](tokens.md)) for color, size, radius, and type — that's what makes it look native and survive theme changes.
34
- - A hex code or a magic `px` next to a concept the tokens already name is a defect.
35
- - Geometry that can't be a token (a clip-path percentage, a hairline `1px`) → named custom property at the top of the
36
- stylesheet, invariant written next to it. Don't silently approximate to the nearest token.
29
+ Use names from the shipped token index for colors, type, size, radius, and shadow. A literal beside a tokenized concept is a
30
+ defect. Keep a literal only when no token represents it, such as a hairline or a clip-path percentage; give that literal a
31
+ named custom property when it is part of the component's design.
32
+
33
+ Read exact token names from `node_modules/@imfusion/web-ui/src/llms/tokens.gen.json`. Do not invent or copy a token list into
34
+ a consumer project.
37
35
 
38
36
  ## Override through the sanctioned seams
39
37
 
40
- - All library styles live in the `imf-ui.components` CSS layer, so any plain selector you write wins — that's the whole
41
- override contract.
42
- - Target the stable hooks: `data-imf-ui-component` attributes and your own classes/wrappers.
43
- - Never target the library's internal class names — they are generated and change without notice.
44
- - Never `!important` — needing it means you're targeting the wrong thing.
45
- - Style against state via data attributes (`[data-checked]`, `[data-disabled]`, `[data-popup-open]`) — components expose
46
- state there, so never maintain your own state classes. `className` is purely a styling surface:
47
-
48
- ```css
49
- [data-imf-ui-component="Switch"][data-checked] {
50
- outline: 2px solid var(--imf-ui-color-bg-positive);
51
- }
52
- ```
38
+ Library component styles are in `@layer imf-ui.components`. Consumer CSS outside a layer wins. Target:
39
+
40
+ - your own class or wrapper;
41
+ - `data-imf-ui-component` for component identity;
42
+ - the component's documented state attributes.
43
+
44
+ Do not target generated CSS Module class names and do not use `!important` to fight the cascade. `className` is a styling
45
+ hook, not a state flag.
46
+
47
+ ```css
48
+ [data-imf-ui-component="Switch"][data-checked] {
49
+ outline: var(--imf-ui-border-size-2) solid var(--imf-ui-color-fg-positive);
50
+ }
51
+ ```
53
52
 
54
53
  ## The color system
55
54
 
56
- - Roles: **surfaces** (`main` canvas, `support` panels, `minor` popovers); **brand** (identity, full saturation) vs
57
- **primary** (contrast-tuned, CTAs) — distinct roles on purpose; **status** (`negative`, `warning`, `positive`, `info`);
58
- three **accent** slots.
59
- - **Controls** are the 80/20 customization surface — override one and every derived token shifts:
55
+ Use surface roles (`main`, `support`, `minor`), the separate `brand` and `primary` roles, status roles, and the three accent
56
+ slots. Override a high-level control when a whole family should change; consume a semantic token in a component.
60
57
 
61
- ```css
62
- :root {
63
- --imf-ui-color-primary-hue: 30; /* every primary semantic token follows */
64
- }
65
- ```
58
+ Foreground roles are for text, icons, borders, and focus rings. Background roles are for fills. The provider sets
59
+ `data-imf-ui-color-scheme="light" | "dark"` on `<html>`.
60
+
61
+ ## Browser floor
66
62
 
67
- - **Semantic tokens** are what you consume: `--imf-ui-color-bg-{name}` per role, a flat
68
- `--imf-ui-color-fg-{main|support|minor|oncolor|…}` ladder for text. Borders and rings draw from `fg-*`.
69
- - Push a hue control into a pale corner → you own overriding the matching `fg-*` token.
70
- - Color scheme is `<html data-imf-ui-color-scheme="light|dark">`, set by the provider; scheme-specific styling selects via
71
- that attribute.
63
+ The library targets Safari 16.2 or newer. `color-mix(in oklch, …)` is available at that floor. Relative color syntax needs a
64
+ guarded fallback for older Safari:
65
+
66
+ ```css
67
+ .subtle {
68
+ background: color-mix(in oklch, var(--chip-bg) 15%, transparent);
69
+ }
70
+
71
+ @supports (color: oklch(from red l c h)) {
72
+ .subtle {
73
+ background: oklch(from var(--chip-bg) l c h / 0.15);
74
+ }
75
+ }
76
+ ```
72
77
 
73
78
  ## Responsive styling
74
79
 
75
- - Tune components per breakpoint by overriding their `--imf-ui-*` variables inside your own media queries — custom properties
76
- cross the `@media` boundary, props don't (why sizing is variables, not a `width` prop):
80
+ Override component variables inside your own media queries:
77
81
 
78
- ```css
79
- @media (min-width: 768px) {
80
- .my-shell {
81
- --imf-ui-appshell-navbar-width: 22rem;
82
- }
82
+ ```css
83
+ @media (min-width: 768px) {
84
+ .shell {
85
+ --imf-ui-appshell-navbar-width: 22rem;
83
86
  }
84
- ```
87
+ }
88
+ ```
85
89
 
86
- - Write literal breakpoint values — the library's `@custom-media` aliases are build-internal and don't ship.
87
- - JS only for behaviour (render a burger menu on mobile): `useMediaQuery(minWidth("md"))`. Never for styling CSS can do.
88
- - No responsive props — `size={{ sm: … }}` objects are deliberately not offered; write the media query.
90
+ Use literal breakpoint values in consumer CSS; the library's `@custom-media` aliases are build-time only. Use
91
+ `useMediaQuery(minWidth("md"))` for JavaScript behavior, not for styling.
@@ -1,25 +1,23 @@
1
1
  # Testing
2
2
 
3
- Test the **decisions**, not the rendering.
3
+ Test decisions and user behavior, not framework behavior.
4
4
 
5
5
  ## Naming
6
6
 
7
- - Colocated tests use `.test.ts` / `.test.tsx` (e.g. `login-url.test.ts`) — not `.spec.*` or plural `.tests.*`.
7
+ Colocate tests and name them `*.test.ts` or `*.test.tsx`.
8
8
 
9
9
  ## What gets a test
10
10
 
11
- - **Pure logic gets unit tests** — pricing, permissions, date math, parsing, reducers: cheap, fast, and where bugs actually
12
- hide.
13
- - **Presentational components generally don't**: a component mapping props onto web-ui primitives has no logic of its own —
14
- asserting a `<Button>` rendered tests React, not your code.
15
- - **Behaviour a user performs gets an interaction test** — a validating form, a stepped flow — through the interface the user
16
- has (roles, labels, visible text), not through internals.
17
- - **Extract the decision out of a hook and test it as a plain function** — easier than a render harness; a hook that only
18
- wraps a browser API has no decision to extract.
19
- - The measure isn't coverage percentage — it's whether a failing test tells you something you didn't already know.
11
+ - Unit-test pure logic such as parsing, permissions, reducers, and date or token calculations.
12
+ - Use an interaction test for behavior a user performs and a static story cannot show.
13
+ - Do not test a presentational component that only maps props onto Web UI or HTML.
14
+ - Extract a hook's decision into a pure function and test that function. A hook that only wraps a browser API has no decision
15
+ to test.
16
+
17
+ A useful test answers something a reader could not know just by reading the implementation. Coverage percentage is not the
18
+ goal.
20
19
 
21
20
  ## Layer-specific recipes
22
21
 
23
- - Data layer: stub `fetch`, run the real query client — the recipe is in [data.md](data.md).
24
- - A schema whose constraints or transforms encode product behavior gets focused accepted, rejected, and transformed cases:
25
- [validation.md](validation.md).
22
+ For the data layer, stub `fetch` at the transport boundary and run the real query client. For schemas, test accepted,
23
+ rejected, and transformed values when the schema encodes product behavior. Keep both tests next to their source.
@@ -1,7 +1,12 @@
1
1
  # Tokens
2
2
 
3
- ## Token index
3
+ Use the generated token index when choosing a Web UI token.
4
4
 
5
- - `@imfusion/web-ui` ships the generated token index at `node_modules/@imfusion/web-ui/src/llms/tokens.gen.json` — the only
6
- source for consumer token names and authored default values.
7
- - Look up the exact entry there; never invent, recall, or duplicate a plausible `--imf-ui-*` name.
5
+ Read:
6
+
7
+ ```text
8
+ node_modules/@imfusion/web-ui/src/llms/tokens.gen.json
9
+ ```
10
+
11
+ It contains the shipped token names and authored defaults. Look up the exact entry instead of guessing a plausible
12
+ `--imf-ui-*` name or copying a token list into the project.