@imfusion/web-ui 0.10.1-dev.6.gf715361a → 0.10.1-dev.60.g7891a716

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 (71) hide show
  1. package/CHANGELOG.md +159 -0
  2. package/README.md +8 -32
  3. package/THIRD_PARTY_NOTICES.md +110 -0
  4. package/dist/code-NTcddiYk.js +139 -0
  5. package/dist/components/account-card/account-card.d.ts +9 -26
  6. package/dist/components/app-shell/app-shell.d.ts +48 -26
  7. package/dist/components/callout/callout.d.ts +1 -1
  8. package/dist/components/chip/chip.d.ts +1 -1
  9. package/dist/components/color-scheme-toggle/color-scheme-preference.d.ts +3 -0
  10. package/dist/components/color-scheme-toggle/color-scheme-toggle.d.ts +17 -0
  11. package/dist/components/color-scheme-toggle/color-scheme-toggle.meta.d.ts +2 -0
  12. package/dist/components/color-scheme-toggle/index.d.ts +2 -0
  13. package/dist/components/dialog/dialog.d.ts +151 -0
  14. package/dist/components/dialog/dialog.meta.d.ts +2 -0
  15. package/dist/components/dialog/index.d.ts +2 -0
  16. package/dist/components/drawer/drawer.d.ts +1 -1
  17. package/dist/components/logo/index.d.ts +1 -0
  18. package/dist/components/logo/product/index.d.ts +1 -0
  19. package/dist/components/logo/product/marks.d.ts +11 -0
  20. package/dist/components/logo/product/product.d.ts +17 -0
  21. package/dist/components/scroll-area/index.d.ts +2 -0
  22. package/dist/components/scroll-area/scroll-area.d.ts +49 -0
  23. package/dist/components/scroll-area/scroll-area.meta.d.ts +2 -0
  24. package/dist/components/select/select.d.ts +18 -4
  25. package/dist/components/separator/separator.d.ts +4 -2
  26. package/dist/components/skeleton/skeleton.d.ts +17 -2
  27. package/dist/components/table/table.d.ts +12 -4
  28. package/dist/components/toggle/toggle.d.ts +8 -2
  29. package/dist/components/toggle-group/toggle-group-size.d.ts +3 -0
  30. package/dist/components/toggle-group/toggle-group.d.ts +7 -3
  31. package/dist/hooks/index.d.ts +2 -0
  32. package/dist/hooks/use-disclosure.d.ts +17 -0
  33. package/dist/icons.js +1 -2
  34. package/dist/index.d.ts +7 -3
  35. package/dist/index.js +2705 -2093
  36. package/dist/integrations/code-highlight.js +2 -2
  37. package/dist/integrations/frame-selection.js +2 -2
  38. package/dist/integrations/image-display-options.js +123 -113
  39. package/dist/{rolldown-runtime-Dy4uBu1J.js → resolve-state-props-DQf9WiGv.js} +12 -1
  40. package/dist/scroll-area-z9Lf9uIk.js +89 -0
  41. package/dist/{separator-lzz4A3x_.js → separator-BV2TR8U0.js} +16 -8
  42. package/dist/slider-Bf274cMe.js +106 -0
  43. package/dist/style.css +1 -1
  44. package/dist/{tabs-DRyqtS04.js → tabs-CU46aGud.js} +106 -88
  45. package/dist/vite/local-web-ui-link.d.ts +17 -4
  46. package/dist/vite.js +25 -20
  47. package/dist/web-ui-cli.js +11 -11
  48. package/docs/user-guide/UsagePatterns.mdx +4 -0
  49. package/package.json +11 -9
  50. package/src/docgen/doc.gen.json +2292 -1071
  51. package/src/llms/install-templates/hooks/baseline-staleness.sh +2 -0
  52. package/src/llms/install-templates/hooks/stop.sh +7 -2
  53. package/src/llms/llms.gen.txt +18 -1
  54. package/src/llms/skills/imf-web-ui-audit/SKILL.md +24 -11
  55. package/src/llms/skills/imf-web-ui-conventions/templates/AUDIT_CHECKLIST.md +4 -2
  56. package/src/llms/skills/imf-web-ui-conventions/topics/data.md +17 -2
  57. package/src/llms/skills/imf-web-ui-conventions/topics/git.md +5 -2
  58. package/src/llms/skills/imf-web-ui-conventions/topics/library-setup.md +3 -0
  59. package/src/llms/skills/imf-web-ui-conventions/topics/project-structure.md +5 -2
  60. package/src/llms/skills/imf-web-ui-conventions/topics/styling.md +3 -1
  61. package/src/llms/skills/imf-web-ui-conventions/topics/tooling.md +47 -8
  62. package/src/llms/skills/imf-web-ui-conventions/topics/typescript.md +2 -0
  63. package/src/llms/skills/imf-web-ui-setup/SKILL.md +10 -1
  64. package/src/llms/skills/imf-web-ui-update/SKILL.md +7 -0
  65. package/src/llms/skills/imf-web-ui-ux/SKILL.md +47 -26
  66. package/src/llms/skills/imf-web-ui-ux/references/forms.md +6 -6
  67. package/src/llms/skills/imf-web-ui-ux/references/usability-heuristics.md +3 -3
  68. package/src/llms/skills/imf-web-ui-ux/references/visual-design.md +3 -3
  69. package/src/llms/tokens.gen.json +13 -5
  70. package/dist/code-BYkr5k55.js +0 -134
  71. package/dist/slider-Cqpreglu.js +0 -116
@@ -4,6 +4,8 @@
4
4
  # and warns on drift. Always exits 0 — staleness never blocks a commit.
5
5
  PKG=node_modules/@imfusion/web-ui/package.json
6
6
  [ -f "$PKG" ] || exit 0
7
+ # A symlinked checkout reports its own dev version.
8
+ [ -L node_modules/@imfusion/web-ui ] && exit 0
7
9
  CURRENT=$(node -p "require('./$PKG').version" 2>/dev/null) || exit 0
8
10
 
9
11
  # .claude/skills may be a symlink to .agents/skills; skip markers already read.
@@ -10,9 +10,14 @@ git rev-parse --is-inside-work-tree >/dev/null 2>&1 || exit 0
10
10
  [ -n "$(git status --porcelain -- '*.ts' '*.tsx' '*.js' '*.jsx' '*.css' 2>/dev/null)" ] || exit 0
11
11
  # Gate only turns that actually edited files — a tree left dirty by an earlier
12
12
  # session must not block a question-answering turn. Edit evidence: Write/Edit
13
- # tool calls (Claude transcript) or apply_patch calls (Codex rollout).
13
+ # tool calls (Claude transcript) or apply_patch calls (Codex rollout), searched
14
+ # only after the turn's opening entry: the last real user prompt (not a tool
15
+ # result or injected meta message) or the last Codex task_started event. Claude
16
+ # matches only tool_use calls: its transcript also stores the tool definitions,
17
+ # whose "name":"Edit" entries are not edits.
14
18
  TRANSCRIPT=$(printf '%s' "$PAYLOAD" | sed -n 's/.*"transcript_path"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -n 1)
15
19
  [ -f "$TRANSCRIPT" ] || exit 0
16
- grep -q '"name":"Write"\|"name":"Edit"\|"name":"MultiEdit"\|"name":"NotebookEdit"\|apply_patch' "$TRANSCRIPT" || exit 0
20
+ TURN_START=$(grep -En '"type":"user"|"type":"task_started"' "$TRANSCRIPT" | grep -Ev '"type":"tool_result"|"isMeta":true' | tail -n 1 | cut -d: -f1)
21
+ tail -n +$(( ${TURN_START:-0} + 1 )) "$TRANSCRIPT" | grep -Eq '"type":"tool_use","id":"[^"]*","name":"(Write|Edit|MultiEdit|NotebookEdit)"|"name":"apply_patch"' || exit 0
17
22
  printf '{"decision":"block","reason":"web-ui stop gate: this turn changed source files. Run the project verification (verify:typecheck, verify:lint, and verify:format at minimum), fix what it reports, then finish. If it already ran after the last change, say so and finish."}\n'
18
23
  exit 0
@@ -11,7 +11,7 @@ props, which always live in the docgen file above.
11
11
 
12
12
  ## AccountCard
13
13
  - category: Display, status: experimental
14
- Card linking to the signed-in person's account: avatar, a large name or email, a quiet line under it, and organization and role at its foot, with a trailing action such as Log out. Also called an ID card or user row; for a bare person badge, use Avatar.
14
+ The signed-in person's account row: the avatar in a lead column, the name or email, a quiet line under it, and organization and role below, with a trailing action such as Log out. Also called an ID card or user row; for a bare person badge, use Avatar.
15
15
  - props: node_modules/@imfusion/web-ui/src/docgen/doc.gen.json -> .account-card (jq: jq '.account-card' node_modules/@imfusion/web-ui/src/docgen/doc.gen.json)
16
16
 
17
17
  ## AppShell
@@ -73,11 +73,22 @@ Show-and-hide region for secondary content such as FAQs, settings, or details. A
73
73
  - props: node_modules/@imfusion/web-ui/src/docgen/doc.gen.json -> .collapsible (jq: jq '.collapsible' node_modules/@imfusion/web-ui/src/docgen/doc.gen.json)
74
74
  - further reading (usage/anatomy, not props): https://base-ui.com/react/components/collapsible.md
75
75
 
76
+ ## ColorSchemeToggle
77
+ - category: Inputs, status: experimental
78
+ Light, System, and Dark segments that set and remember the page's color scheme. Also called an appearance or theme switch; built on ToggleGroup, which is the choice for any other segmented selection.
79
+ - props: node_modules/@imfusion/web-ui/src/docgen/doc.gen.json -> .color-scheme-toggle (jq: jq '.color-scheme-toggle' node_modules/@imfusion/web-ui/src/docgen/doc.gen.json)
80
+
76
81
  ## CopyButton
77
82
  - category: Buttons, status: stable
78
83
  Copies a value to the clipboard and briefly shows confirmation. Also called a copy-to-clipboard button; commonly used with Code.Block.
79
84
  - props: node_modules/@imfusion/web-ui/src/docgen/doc.gen.json -> .copy-button (jq: jq '.copy-button' node_modules/@imfusion/web-ui/src/docgen/doc.gen.json)
80
85
 
86
+ ## Dialog
87
+ - category: Layout, status: experimental
88
+ Modal window over a dimmed page for focused tasks, forms, confirmations, or viewers. Also called a modal; use Drawer for a panel from the screen edge and Popover for content anchored to a trigger.
89
+ - props: node_modules/@imfusion/web-ui/src/docgen/doc.gen.json -> .dialog (jq: jq '.dialog' node_modules/@imfusion/web-ui/src/docgen/doc.gen.json)
90
+ - further reading (usage/anatomy, not props): https://base-ui.com/react/components/dialog.md
91
+
81
92
  ## Drawer
82
93
  - category: Layout, status: stable
83
94
  Off-canvas panel that slides in from a screen edge for navigation, settings, filters, or focused tasks. Also called a sidebar, side panel, or sheet.
@@ -173,6 +184,12 @@ Link as a whole row: optional leading icon, label, optional description line, an
173
184
  Horizontal layout primitive for spacing, aligning, and wrapping children. Also called a horizontal stack or flex row.
174
185
  - props: node_modules/@imfusion/web-ui/src/docgen/doc.gen.json -> .row (jq: jq '.row' node_modules/@imfusion/web-ui/src/docgen/doc.gen.json)
175
186
 
187
+ ## ScrollArea
188
+ - category: Layout, status: experimental
189
+ Scrollable region with the library's overlay scrollbar and edge fades, for lists, panels, and dialog bodies. Also called a scroll container; use it instead of a bare overflow: auto.
190
+ - props: node_modules/@imfusion/web-ui/src/docgen/doc.gen.json -> .scroll-area (jq: jq '.scroll-area' node_modules/@imfusion/web-ui/src/docgen/doc.gen.json)
191
+ - further reading (usage/anatomy, not props): https://base-ui.com/react/components/scroll-area.md
192
+
176
193
  ## Select
177
194
  - category: Inputs, status: stable
178
195
  Dropdown for choosing a value from a known list. Also called a picker; use it for a labeled, keyboard-accessible option list.
@@ -23,15 +23,20 @@ it low, and do not let it crowd the sections a consumer has to act on.
23
23
 
24
24
  Invoke `imf-web-ui-user-profile` first and explain at its levels throughout.
25
25
 
26
- 1. Resolve the argument. Bare means `full`; an unknown topic is an error, not a reason to widen the scope.
26
+ 1. Resolve the argument. Bare means `full`; an unknown topic is an error, not a reason to widen the scope. Then check that
27
+ `node_modules/@imfusion/web-ui/package.json` exists. Without it, token, installed-version, and installed-type checks can
28
+ only come back Unverified, so tell the human before dispatch and ask whether to install dependencies first or continue
29
+ with those checks unverified.
27
30
  2. Enter plan mode unless this audit is being called as verification by another skill or the host has no plan mode.
28
31
  3. Read the report template and existing reviewer notes, then dispatch one investigator per in-scope topic. Tell each
29
- investigator to use only `Read`, `Glob`, and `Grep`, with no shell commands or writes. An investigator checks every
30
- section in that topic and writes nothing. Once dispatch starts, the host performs no repository inspection: the returned
31
- reports are the evidence, and the host merges them without reading or searching the files again. Gathering the evidence
32
- twice spends context on the second pass and reports what the host still remembers. Inspect a topic inline only after its
33
- dispatch has been tried and refused, and say which attempt failed. A `full` audit also dispatches one marker investigator;
34
- a named-topic audit gives that search to the topic's investigator. See [Deferred-work markers](#deferred-work-markers).
32
+ investigator to inspect statically: `Read` plus the host's file search (`Glob` and `Grep`, or a read-only search tool when
33
+ the host has no `Glob` or `Grep`), with no shell commands or writes. An investigator checks every section in that topic
34
+ across the whole frontend tree and writes nothing; one without any search tool says so and stops instead of auditing only
35
+ the paths it already knows. Once dispatch starts, the host performs no repository inspection: the returned reports are the
36
+ evidence, and the host merges them without reading or searching the files again. Gathering the evidence twice spends
37
+ context on the second pass and reports what the host still remembers. Inspect a topic inline only after its dispatch has
38
+ been tried and refused, and say which attempt failed. A `full` audit also dispatches one marker investigator; a
39
+ named-topic audit gives that search to the topic's investigator. See [Deferred-work markers](#deferred-work-markers).
35
40
  4. Merge the evidence into the shared report format in
36
41
  [`templates/REPORT.md`](../imf-web-ui-conventions/templates/REPORT.md). Before presenting or writing it, check that every
37
42
  Broken, Missing, Deviation, and Unverified entry has a severity, evidence, impact, and next action; do not compact
@@ -55,7 +60,15 @@ Classify every result as one of these:
55
60
 
56
61
  Use the report contract's severity, `path:line` evidence, concrete impact, and smallest next action. Unverified entries use
57
62
  that same structure: unavailable runtime evidence, the impact of the unknown, and the check that resolves it. Do not promote
58
- an optional tool or a working alternative to a missing finding.
63
+ an optional tool or a working alternative to a missing finding. A `vite.config.ts` without `localWebUILink()` registered from
64
+ `@imfusion/web-ui/vite` is Missing. A `tsconfig.json` without the `paths` block from the `tooling` topic is Missing too. Each
65
+ next action is the add from that topic. A Vite config that reads tsconfig `paths` (`resolve.tsconfigPaths` or
66
+ `vite-tsconfig-paths`) is Broken: it loads the React types package as React once Web UI comes from the registry. The fix is
67
+ to drop it and give the `#/` alias through `imports` in `package.json`. A major or minor mismatch between the project and Web
68
+ UI on a package listed under Aligned runtime packages in the `tooling` topic is a Deviation. Report both versions, with
69
+ `npm install <package>@<Web UI's version>` as the fix. A route-local file that the inline rule in the `project-structure`
70
+ topic says belongs in its route file is a Deviation, and so is a route file over 250 lines that inlines parts the rule would
71
+ keep in its `-` folder. Report the line counts, with the move as the fix.
59
72
 
60
73
  ## Topics
61
74
 
@@ -90,6 +103,6 @@ text; do not run installers or hooks.
90
103
 
91
104
  ## Safety
92
105
 
93
- Use only `Read`, `Glob`, `Grep`, and equivalent static inspection. Do not run Git, shells, Node, npm, npx, package scripts,
94
- linters, tests, builds, hooks, or imported config. Report runtime state as unverified. Investigators inherit this
95
- restriction.
106
+ Use only `Read`, the host's read-only file search, and equivalent static inspection. Do not run Git, shells, Node, npm, npx,
107
+ package scripts, linters, tests, builds, hooks, or imported config. Report runtime state as unverified. Investigators inherit
108
+ this restriction.
@@ -14,7 +14,7 @@ sections one to one — change a topic's sections and this file follows in the s
14
14
  - [ ] Stay behind the library
15
15
  - [ ] Wrap primitives when the app has a reason to
16
16
  - [ ] Lock a field: read-only or disabled
17
- - [ ] Derive types, don't import them
17
+ - [ ] Derive types, don't copy them
18
18
  - [ ] Integrations own their peers
19
19
  - [ ] Styling crosses the boundary through seams
20
20
 
@@ -89,6 +89,7 @@ sections one to one — change a topic's sections and this file follows in the s
89
89
  - [ ] Topic registry and key factory
90
90
  - [ ] Options factories and naming
91
91
  - [ ] Router context access
92
+ - [ ] Loading states
92
93
  - [ ] Automatic invalidation
93
94
  - [ ] Testing the data layer
94
95
 
@@ -108,7 +109,8 @@ sections one to one — change a topic's sections and this file follows in the s
108
109
  - [ ] Oxfmt
109
110
  - [ ] Oxlint and TypeScript
110
111
  - [ ] Pre-commit checks
111
- - [ ] Vite plugins
112
+ - [ ] Vite plugins, including `localWebUILink()`
113
+ - [ ] Aligned runtime packages
112
114
 
113
115
  ## npm project — `npm-project`
114
116
 
@@ -70,8 +70,10 @@ Cross-topic invalidation can then use `keys.topic` instead of rebuilding a prefi
70
70
 
71
71
  ## Options factories and naming
72
72
 
73
- Queries use `get*` names such as `getAll`, `getById`, and `getDetails`. Mutations use bare verbs such as `update` or
74
- `delete`. Do not add a topic prefix or `Options` suffix; `context.api.user.getDetails()` already supplies the context.
73
+ Queries use `get*` names such as `getAll`, `getById`, and `getDetails`. Mutations use verbs such as `update` or `delete`. A
74
+ name never repeats its topic or adds an `Options` suffix; `context.api.user.getDetails()` already supplies the context. When
75
+ a topic has more than one mutation target, add the sub-resource to the verb: `api.projects.removeMember(projectId)` and
76
+ `api.projects.setMemberPermissions(projectId)`, where a bare `remove` would read as deleting the project.
75
77
 
76
78
  A query factory owns its key and parsing:
77
79
 
@@ -102,6 +104,19 @@ not inline those options at the call site.
102
104
  Put the API object and `QueryClient` in router context. Loaders use `context.api.<topic>.<fn>()`; route components read the
103
105
  same context with `Route.useRouteContext()`. Direct imports are for plain utilities and tests outside the route tree.
104
106
 
107
+ ## Loading states
108
+
109
+ Pick the Skeleton part by how the data loads:
110
+
111
+ - Data read with `useSuspenseQuery` inside a Suspense boundary uses `Skeleton.Suspense`, with the bones as its `fallback`.
112
+ Content fades in when the fallback showed; cached data mounts without a fade.
113
+ - Secondary data read with `useQuery` that may arrive later uses `Skeleton.Root` with `loading={isPending}` around the real
114
+ content, which cross-fades when the data lands.
115
+ - A route whose loader blocks navigation until the data is cached shows its `pendingComponent`. The router unmounts it, so
116
+ nothing reveals.
117
+
118
+ A layout that reads suspending data needs its own boundary. Otherwise a suspension blanks the whole layout.
119
+
105
120
  ## Automatic invalidation
106
121
 
107
122
  Mutation keys start with the topic they change. The shared `MutationCache` invalidates that topic after success by default.
@@ -7,12 +7,15 @@ Use the repository's tracked configuration and keep verification scopes clear.
7
7
  Run once in a clone:
8
8
 
9
9
  ```sh
10
- git config core.hooksPath .githooks
10
+ git config core.hooksPath .vite-hooks/_
11
+ vp config --no-agent
11
12
  git config pull.rebase true
12
13
  git config merge.ff only
13
14
  ```
14
15
 
15
- The hooks are tracked in `.githooks/`. Check both the Git setting and the directory; a missing hook path fails silently.
16
+ Vite+ manages the hooks. The hook scripts are tracked in `.vite-hooks/`; `vp config` installs the dispatcher into the
17
+ git-ignored `.vite-hooks/_/`. `vp config` leaves a `core.hooksPath` that points elsewhere alone, so set it first. Every clone
18
+ and worktree needs its own dispatcher. Check both with `vp hooks status`; a missing dispatcher skips the hooks silently.
16
19
 
17
20
  ## Verify scopes
18
21
 
@@ -29,6 +29,9 @@ instead, so component colors cannot resolve against the opposite scheme:
29
29
  <WebUIProvider colorScheme="dark">
30
30
  ```
31
31
 
32
+ To let the user pick Light, System, or Dark, place a `ColorSchemeToggle`. It stores the choice and works only while the
33
+ provider's `colorScheme` is unset.
34
+
32
35
  ## Symptoms of broken setup
33
36
 
34
37
  - Unstyled components usually mean the `styles.css` import is missing.
@@ -35,8 +35,11 @@ A route file holds its search schema and `validateSearch`, its loaders, and its
35
35
  Route-local code that outgrows small in-file components (views, forms, hooks, tests) goes in a sibling folder named `-` plus
36
36
  the route file's name: `route.tsx` gets `-route/`, `products.tsx` gets `-products/`. TanStack Router ignores files and
37
37
  folders with a leading `-` when it builds the route tree, so the folder never becomes a route. The folder holds the route's
38
- views with their props types, hooks, `.module.css` files, and tests. A component small enough to sit in the route file stays
39
- there.
38
+ views with their props types, hooks, `.module.css` files, and tests.
39
+
40
+ A part used by one route file is inlined into it when it is a component under 50 lines, calls no `use*` hook, has no
41
+ stylesheet or test of its own, and leaves that route file under 250 lines. Every other single-route part goes in the `-`
42
+ folder. The thresholds keep tiny wrappers out of their own files without letting a route file grow into a page of views.
40
43
 
41
44
  `partials/` holds only parts that more than one route uses.
42
45
 
@@ -9,7 +9,9 @@ Use native CSS and Web UI's public seams. The library's CSS Modules and token na
9
9
  - Nest states, pseudo-elements, and child selectors under the root.
10
10
  - Write declarations before nested rules; browsers before Chrome 130, Firefox 132, and Safari 18 move later ones above them.
11
11
  - Do not nest under a selector list whose selectors differ in specificity. The nested rule takes the highest one.
12
- - Lift repeated values into custom properties, especially when a state or variant changes several descendants.
12
+ - Lift a value into a custom property on the component's root when it repeats within that stylesheet, or when a state or
13
+ variant changes several descendants. A value that only recurs across files stays a literal in each; when those files show
14
+ one design element, a shared component owns it. Never define component values on `:root`.
13
15
  - Keep structurally different rules explicit; not every repeated value needs an abstraction.
14
16
 
15
17
  ```css
@@ -49,7 +49,7 @@ from `vite-plus/test`. It needs Node `^22.22.1 || ^24.11 || >=26` and Git 2.32 o
49
49
  `vp <command>` is a built-in; `vp run <script>` runs a package script.
50
50
 
51
51
  Move an existing Vite, Vitest, ESLint, or Prettier project with `vp migrate --no-hooks` (`vp help migrate` lists its
52
- options), then check the result against this topic. The hook lives in `.githooks/`, as `git.md` describes.
52
+ options), then check the result against this topic. The hook lives in `.vite-hooks/`, as `git.md` describes.
53
53
 
54
54
  ## Oxfmt
55
55
 
@@ -101,12 +101,19 @@ export default defineConfig({
101
101
  });
102
102
  ```
103
103
 
104
- Use strict TypeScript with bundler module resolution, `verbatimModuleSyntax`, unused-value checks, and `#/` paths. Keep
105
- `import type` for type-only imports.
104
+ Use strict TypeScript with bundler module resolution, `verbatimModuleSyntax`, unused-value checks, and `import type` for
105
+ type-only imports. The `#/` alias is a subpath import in `package.json`, which Vite and TypeScript both read without a
106
+ plugin:
107
+
108
+ ```json
109
+ "imports": {
110
+ "#/*": "./src/*"
111
+ }
112
+ ```
106
113
 
107
114
  ## Pre-commit checks
108
115
 
109
- `.githooks/pre-commit` runs `vp staged --hide-unstaged --fail-on-changes --revert --diff-filter=ACMRD` with `set -e`. Those
116
+ `.vite-hooks/pre-commit` runs `vp staged --hide-unstaged --fail-on-changes --revert --diff-filter=ACMRD` with `set -e`. Those
110
117
  flags make the checks see only the staged state, include deletion-only commits, fail the commit when a check would change a
111
118
  file, and restore the tree afterwards, so the hook never rewrites or stages files. The `staged` block in `vite.config.ts` is
112
119
  a function returning path-free commands such as `npm run --silent verify:lint`, so each project-wide check runs once. Use
@@ -119,10 +126,10 @@ Register Web UI's Vite plugins in the application configuration:
119
126
  ```ts
120
127
  import { defineConfig } from "vite-plus";
121
128
  import react from "@vitejs/plugin-react";
122
- import { imfusionBrandAssets, readableCssModuleNames } from "@imfusion/web-ui/vite";
129
+ import { imfusionBrandAssets, localWebUILink, readableCssModuleNames } from "@imfusion/web-ui/vite";
123
130
 
124
131
  export default defineConfig({
125
- plugins: [react(), imfusionBrandAssets(), readableCssModuleNames({ prefix: "app" })]
132
+ plugins: [react(), imfusionBrandAssets(), readableCssModuleNames({ prefix: "app" }), localWebUILink()]
126
133
  });
127
134
  ```
128
135
 
@@ -130,5 +137,37 @@ export default defineConfig({
130
137
  Register `readableCssModuleNames` in every compiler that processes the app's CSS, including development, Storybook, and
131
138
  production. A compiler left out produces different class names and styles that appear to work only in some environments.
132
139
 
133
- `localWebUILink` dedupes the library's peer dependencies when `@imfusion/web-ui` is linked from a local checkout
134
- (`file:../web-ui`), so the app loads one React. It does nothing for an installed package and does not cover server rendering.
140
+ ## Aligned runtime packages
141
+
142
+ Web UI and the app run the same versions of these packages. The `peerDependencies` of
143
+ `node_modules/@imfusion/web-ui/package.json` give the accepted ranges, and its `devDependencies` give the version Web UI
144
+ builds and tests with. A matching major and minor is the rule; a patch difference is fine.
145
+
146
+ - `react`, `react-dom`
147
+ - `@types/react`, `@types/react-dom`
148
+ - `@base-ui/react`, which brings `@floating-ui/*` with it
149
+ - `vite-plus`
150
+ - `vite`, the `npm:@voidzero-dev/vite-plus-core` alias that `vite-plus` provides
151
+
152
+ ## Version drift
153
+
154
+ Aligned versions are the primary fix. The plugin and the `paths` block below catch the drift that slips through.
155
+
156
+ `localWebUILink` stays registered for good. It does nothing for an installed package. When `@imfusion/web-ui` is a symlinked
157
+ checkout, it keeps one React in dev, build, and Vitest.
158
+
159
+ Add this to `compilerOptions` in `tsconfig.json`, merged by hand into an existing `paths`. For a symlinked checkout it keeps
160
+ one set of React and Vite types for `tsc`:
161
+
162
+ ```json
163
+ "paths": {
164
+ "react": ["./node_modules/@types/react"],
165
+ "react/*": ["./node_modules/@types/react/*"],
166
+ "vite": ["./node_modules/vite"]
167
+ }
168
+ ```
169
+
170
+ Only TypeScript reads these `paths`. Never let Vite read them: leave `resolve.tsconfigPaths` off and do not add
171
+ `vite-tsconfig-paths`. Either one applies the `react` mapping at runtime, loads the types package as React, and breaks the
172
+ build and tests once `@imfusion/web-ui` is installed from the registry. A symlinked checkout hides the break, because
173
+ `localWebUILink()` dedupes React first.
@@ -6,6 +6,8 @@ Prefer pure functions, immutable values, and side effects at the edges.
6
6
 
7
7
  - Do not use `any`. Accept `unknown` at an untrusted boundary and narrow it.
8
8
  - Infer local values; give exported functions explicit return types.
9
+ - TanStack options factories (`queryOptions`, `mutationOptions`, and similar) return their helper's inferred type. Spelling
10
+ it out loses the typed query key (`DataTag`) or repeats the helper's own generics.
9
11
  - Model states directly instead of filling a nullable value later.
10
12
  - Derive types from their source. Use `z.infer` for schemas and `React.ComponentProps` for Web UI components.
11
13
  - Avoid `as`. Keep an assertion only at an untyped third-party boundary where it is the honest boundary.
@@ -45,10 +45,19 @@ Include these pieces unless the existing project already has them or the user re
45
45
  - one server-backed current-user query at the route boundary;
46
46
  - a minimal `AppShell` in `_app/`, unless the user says the product has no persistent authenticated navigation;
47
47
  - an API topic folder for data shown by the first screen;
48
- - the root provider and library wiring.
48
+ - the root provider and library wiring;
49
+ - the Vite plugins below.
49
50
 
50
51
  The plan describes the exact starter files. It does not copy a generic starter project.
51
52
 
53
+ ## Vite plugins
54
+
55
+ Propose a `vite.config.ts` that registers `imfusionBrandAssets()`, `readableCssModuleNames()`, and `localWebUILink()` from
56
+ `@imfusion/web-ui/vite`, next to the plugins the project already has. Register `localWebUILink()` in every project: without a
57
+ symlinked checkout it does nothing. Also propose the `tsconfig.json` `paths` block. Both keep one React and one set of types
58
+ when the package is a symlinked checkout. Vite must not read those `paths`: never propose `resolve.tsconfigPaths` or
59
+ `vite-tsconfig-paths`, and give the `#/` alias through `imports` in `package.json`. The `tooling` topic has the exact config.
60
+
52
61
  ## Installer steps
53
62
 
54
63
  For `agent-tooling`, list the `npx web-ui install` commands as steps for the human. Read existing registrations first and do
@@ -96,6 +96,13 @@ changed. A diff shows what moved, not the step order you continue under, so it d
96
96
  the current step using the updated guidance, keeping the original baseline, user choices, and completed changes in mind.
97
97
  Check for newly required steps without repeating completed mutations or expanding the approved scope.
98
98
 
99
+ ## Align the runtime packages
100
+
101
+ After the install, compare the app's installed versions of the packages listed under Aligned runtime packages in the
102
+ `tooling` topic with Web UI's own `package.json` in `node_modules/@imfusion/web-ui`. Report every major or minor mismatch and
103
+ propose `npm install <package>@<Web UI's version>` for each. Make the change as its own commit, in this same update, after
104
+ the base commit and with its own approval.
105
+
99
106
  ## Verify the base update
100
107
 
101
108
  After every mutating command, compare the status with the preflight snapshot. Stop on an unexpected path or overlapping
@@ -7,8 +7,8 @@ description:
7
7
 
8
8
  # Shape the screen
9
9
 
10
- Use this skill for screen-level decisions. Verify component APIs with `imf-web-ui-components`; put code and styling rules in
11
- `imf-web-ui-conventions`.
10
+ Use this skill for screen-level decisions. The patterns below name no components: check which component fits a pattern, and
11
+ its API, in `imf-web-ui-components`. Put code and styling rules in `imf-web-ui-conventions`.
12
12
 
13
13
  Invoke `imf-web-ui-user-profile` first and explain at its levels throughout.
14
14
 
@@ -34,33 +34,31 @@ unanswered, make the conservative choice and state the assumption.
34
34
  ## Choose a surface
35
35
 
36
36
  - Use a full page by default.
37
- - Use `Drawer` for a focused task that keeps the current page in context.
38
- - Use `Popover` for small contextual content or a light form.
39
- - Use `Tooltip` for a hint only. It must not contain required or interactive information.
40
- - Use `Collapsible` for secondary content inside the page.
41
- - Use `Tabs` for parallel views of one subject. If users must complete every section, use a flow instead.
42
-
43
- The library has no modal `Dialog` or `Toast`. Raise those gaps rather than hand-rolling a replacement without a product
44
- reason.
37
+ - Use a side panel for a focused task that keeps the current page in context.
38
+ - Use a floating panel anchored to its trigger for small contextual content or a light form.
39
+ - Use a hover or focus hint for a short label only. It must not contain required or interactive information.
40
+ - Use a collapsible region for secondary content inside the page.
41
+ - Use tabs for parallel views of one subject. If users must complete every section, use a flow instead.
42
+ - Use a modal dialog for a decision that must be settled before the page continues, such as a destructive action. If no
43
+ component fits a pattern, raise the gap rather than hand-rolling a replacement without a product reason.
45
44
 
46
45
  ## Choose between similar controls
47
46
 
48
- - `Button` performs an action, `ChipLink` goes somewhere, and `Chip` labels something.
49
- - Use `ChipLink` for a link inside text or a row of chips, and `RichLink` for a link that fills its own row, such as a list
50
- of resources.
51
- - Use plain `Table` parts for static small data. Pair them with a headless table library for sorting, pagination, or column
52
- logic. TanStack Table is the recommended choice.
53
- - Use `Callout` for persistent in-place status. Put transient confirmation near the action until a toast pattern exists.
54
- - Use `Switch` for an immediate setting and `Checkbox` for a submitted choice.
55
- - Use `Select` for a known list, and consider a different control when the list is very short or very large.
56
- - Use `Slider` when relative position matters more than typing an exact value.
47
+ - An action is a button, navigation is a link, and a label is a chip.
48
+ - Use an inline link inside text or in a row of chips, and a full-row link for a list of resources.
49
+ - Use a plain table for static small data. Pair it with a headless table library for sorting, pagination, or column logic.
50
+ TanStack Table is the recommended choice.
51
+ - Use an in-place status message for persistent status. Keep transient confirmation near the action it confirms.
52
+ - Use a switch for an immediate setting and a checkbox for a submitted choice.
53
+ - Use a select for a known list, and consider a different control when the list is very short or very large.
54
+ - Use a slider when relative position matters more than typing an exact value.
57
55
 
58
56
  ## Build the hierarchy
59
57
 
60
- - Frame an application with `AppShell`.
61
- - Compose `Stack` and `Row` for spacing and arrangement, and wrap long-form text in `Prose`. Use `Separator` for a real
58
+ - Frame an application with the library's app shell.
59
+ - Arrange content with stack and row layouts, wrap long-form text in a prose container, and use a separator for a real
62
60
  division.
63
- - Use `Typo` for text hierarchy instead of styling raw headings beside it.
61
+ - Use the library's typography components for text hierarchy instead of styling raw headings beside them.
64
62
  - Give a view one primary action. Make other actions quieter.
65
63
  - Choose density from the user's frequency and data volume: frequent work and large data need compact layouts; occasional
66
64
  work benefits from more explanation and space.
@@ -70,14 +68,37 @@ reason.
70
68
  Plan these states with the loaded view:
71
69
 
72
70
  - **Empty**: say what belongs here and what the user can do next.
73
- - **Loading**: use `Spinner` or a stable skeleton; keep the frame from jumping.
74
- - **Error**: explain what failed and the next action in a `Callout` near the problem.
71
+ - **Loading**: use a spinner or a stable skeleton; keep the frame from jumping. The `imf-web-ui-conventions` data topic says
72
+ which skeleton part fits each way the data loads.
73
+ - **Error**: explain what failed and the next action in an in-place status message near the problem.
75
74
  - **Loaded**: the normal view.
76
75
 
77
76
  ## Keep custom UI native
78
77
 
79
- When the library does not cover a piece, compose its primitives and use `--imf-ui-*` tokens. The implementation details are
80
- in `imf-web-ui-conventions`.
78
+ When no component covers a piece, compose its primitives and use `--imf-ui-*` tokens. The implementation details are in
79
+ `imf-web-ui-conventions`.
81
80
 
82
81
  For a screen review, read the relevant reference under `references/`: `usability-heuristics.md`, `visual-design.md`, or
83
82
  `forms.md`.
83
+
84
+ ## Public backdrop handover
85
+
86
+ Public pages share a backdrop with the Keycloak login page: a light column with an animated line mesh, and a brand-colored
87
+ panel. Both sides draw the same geometry, so the cross-origin navigation between them looks seamless. Read the geometry from
88
+ these tokens and never from literals:
89
+
90
+ - `--imf-ui-size-backdrop-edge`: where the brand panel starts on wide landscape screens.
91
+ - `--imf-ui-size-backdrop-band-height`: the height of the light band on top of narrow or portrait screens, with the panel
92
+ below it.
93
+
94
+ The switch between the two layouts is a media query, which a token cannot carry. Use the same breakpoint as the reference
95
+ implementations.
96
+
97
+ The backdrop also paints before React mounts, so the first frame already matches. Put the same two columns on `body` as a
98
+ hard-stop `linear-gradient`, with `min-block-size: 100dvh`. In a single-page app, a marker on `html`
99
+ (`data-backdrop-pending`), set by an inline script in `index.html` for the routes that show the backdrop, gates that
100
+ gradient. The backdrop component removes the marker when it mounts. Generate that script's route list at build time from the
101
+ router's own source, never as a hand-written copy: with TanStack file routes, a small Vite plugin (`transformIndexHtml`)
102
+ reads the folder of backdrop routes and fills a placeholder in `index.html`, so a new route gets the pre-paint without anyone
103
+ touching the script. The ImFusion Portal's public backdrop component and the Keycloak login theme's copy of it are the
104
+ reference implementations.
@@ -6,9 +6,9 @@ validation. These rules cover structure and feedback.
6
6
  ## Structure
7
7
 
8
8
  - Remove fields that can be derived or asked later.
9
- - Use one column by default. Put short, inseparable values such as city and postal code in a `Row`.
10
- - Group related fields in a `Stack` and separate groups with more space.
11
- - Use a labeled `Fieldset` or heading for each group.
9
+ - Use one column by default. Put short, inseparable values such as city and postal code on one row.
10
+ - Group related fields with tight spacing and separate groups with more space.
11
+ - Use a labeled fieldset or heading for each group.
12
12
  - Match a field's size to the value it expects.
13
13
 
14
14
  ## Labels
@@ -21,10 +21,10 @@ hints near the field before the user starts typing.
21
21
  Validate when the user finishes a field, except where live feedback genuinely helps, such as password requirements. Keep the
22
22
  error next to the field and use more than color to show it.
23
23
 
24
- An error says what is wrong and how to fix it. Keep the user's input. A server or form-level failure belongs in a `Callout`
25
- near the actions; field-level failures stay with their fields.
24
+ An error says what is wrong and how to fix it. Keep the user's input. A server or form-level failure belongs in an in-place
25
+ status message near the actions; field-level failures stay with their fields.
26
26
 
27
27
  ## Submission
28
28
 
29
- Use one primary `Button` whose label names the result, such as `Create project`. While submitting, keep the form visible and
29
+ Use one primary button whose label names the result, such as `Create project`. While submitting, keep the form visible and
30
30
  show progress. A failed submission must leave the user's input on screen.
@@ -5,12 +5,12 @@ user research.
5
5
 
6
6
  1. **Show system status.** Give async actions visible progress and confirmation.
7
7
  2. **Use the user's language.** Labels and ordering should match the domain, not an API's private names.
8
- 3. **Provide an exit.** Drawers and popovers need clear dismissal; destructive actions need confirmation or undo.
8
+ 3. **Provide an exit.** Side panels and floating panels need clear dismissal; destructive actions need confirmation or undo.
9
9
  4. **Be consistent.** Reuse the library's established components and semantics.
10
10
  5. **Prevent errors.** Constrain known choices and disable actions that cannot work.
11
11
  6. **Support recognition.** Keep relevant options and context visible instead of making users remember them.
12
12
  7. **Support frequent users.** Add keyboard shortcuts or dense layouts when the task is frequent and data-heavy.
13
13
  8. **Remove decoration without a job.** Every element should support the action or the data.
14
14
  9. **Make errors recoverable.** Say what failed and what to do next, near the problem.
15
- 10. **Keep help contextual.** Prefer a short hint beside a control to essential information hidden in a tooltip or separate
16
- page.
15
+ 10. **Keep help contextual.** Prefer a short hint beside a control to essential information hidden in a hover hint or
16
+ separate page.
@@ -7,12 +7,12 @@ Use this when a screen works but still looks wrong. Decide the hierarchy before
7
7
  Write the intended order of attention: title, primary action, content, and supporting details. Use contrast and scale to make
8
8
  that order visible. Keep variation limited; if everything is loud, nothing leads.
9
9
 
10
- Reserve saturated status colors for meaning. The primary `Button` should be the strongest action treatment on the screen.
10
+ Reserve saturated status colors for meaning. The primary button should be the strongest action treatment on the screen.
11
11
 
12
12
  ## Grouping
13
13
 
14
- Use proximity first: tight gaps inside a group, larger gaps between groups. `Stack` gap values encode this relationship. Use
15
- a shared `Card` or bordered region only when spacing alone does not make the relationship clear.
14
+ Use proximity first: tight gaps inside a group, larger gaps between groups. Spacing values encode this relationship. Use a
15
+ shared card or bordered region only when spacing alone does not make the relationship clear.
16
16
 
17
17
  Similarity helps related elements stay recognisable across a layout. It supports proximity; it does not replace it.
18
18
 
@@ -477,7 +477,7 @@
477
477
  },
478
478
  {
479
479
  "name": "--imf-ui-font-family-mono",
480
- "value": "\"CoFo Sans Mono\", ui-monospace, monospace"
480
+ "value": "\"Paper Mono\", ui-monospace, monospace"
481
481
  },
482
482
  {
483
483
  "name": "--imf-ui-font-family-sans",
@@ -493,7 +493,7 @@
493
493
  },
494
494
  {
495
495
  "name": "--imf-ui-font-letterspacing-brand",
496
- "value": "-0.02em"
496
+ "value": "clamp(-0.035rem, -0.0081rem - 0.0224vw, -0.0131rem)"
497
497
  },
498
498
  {
499
499
  "name": "--imf-ui-font-letterspacing-display",
@@ -501,7 +501,7 @@
501
501
  },
502
502
  {
503
503
  "name": "--imf-ui-font-letterspacing-hero",
504
- "value": "-0.04em"
504
+ "value": "clamp(-0.225rem, -0.0312rem - 0.1615vw, -0.0675rem)"
505
505
  },
506
506
  {
507
507
  "name": "--imf-ui-font-letterspacing-loose",
@@ -689,7 +689,7 @@
689
689
  },
690
690
  {
691
691
  "name": "--imf-ui-font-weight-strong",
692
- "value": "600"
692
+ "value": "500"
693
693
  }
694
694
  ],
695
695
  "inner-*": [
@@ -913,6 +913,14 @@
913
913
  "name": "--imf-ui-size-9",
914
914
  "value": "4rem"
915
915
  },
916
+ {
917
+ "name": "--imf-ui-size-backdrop-band-height",
918
+ "value": "30dvh"
919
+ },
920
+ {
921
+ "name": "--imf-ui-size-backdrop-edge",
922
+ "value": "38%"
923
+ },
916
924
  {
917
925
  "name": "--imf-ui-size-content-1",
918
926
  "value": "20ch"
@@ -947,5 +955,5 @@
947
955
  }
948
956
  ]
949
957
  },
950
- "count": 231
958
+ "count": 233
951
959
  }