@imfusion/web-ui 0.10.1-dev.6.gf715361a → 0.10.1-dev.61.g9d1000b8

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 (74) hide show
  1. package/CHANGELOG.md +167 -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 +155 -141
  48. package/docs/user-guide/AgentTooling.mdx +4 -4
  49. package/docs/user-guide/UsagePatterns.mdx +4 -0
  50. package/package.json +11 -9
  51. package/src/docgen/doc.gen.json +2292 -1071
  52. package/src/llms/install-templates/hooks/session-start.sh +14 -0
  53. package/src/llms/install-templates/hooks/stop.sh +7 -2
  54. package/src/llms/llms.gen.txt +18 -1
  55. package/src/llms/skills/imf-web-ui-audit/SKILL.md +28 -15
  56. package/src/llms/skills/imf-web-ui-conventions/templates/AUDIT_CHECKLIST.md +4 -2
  57. package/src/llms/skills/imf-web-ui-conventions/topics/agent-tooling.md +8 -9
  58. package/src/llms/skills/imf-web-ui-conventions/topics/data.md +17 -2
  59. package/src/llms/skills/imf-web-ui-conventions/topics/git.md +8 -5
  60. package/src/llms/skills/imf-web-ui-conventions/topics/library-setup.md +3 -0
  61. package/src/llms/skills/imf-web-ui-conventions/topics/project-structure.md +5 -2
  62. package/src/llms/skills/imf-web-ui-conventions/topics/styling.md +3 -1
  63. package/src/llms/skills/imf-web-ui-conventions/topics/tooling.md +47 -8
  64. package/src/llms/skills/imf-web-ui-conventions/topics/typescript.md +2 -0
  65. package/src/llms/skills/imf-web-ui-setup/SKILL.md +10 -1
  66. package/src/llms/skills/imf-web-ui-update/SKILL.md +12 -5
  67. package/src/llms/skills/imf-web-ui-ux/SKILL.md +47 -26
  68. package/src/llms/skills/imf-web-ui-ux/references/forms.md +6 -6
  69. package/src/llms/skills/imf-web-ui-ux/references/usability-heuristics.md +3 -3
  70. package/src/llms/skills/imf-web-ui-ux/references/visual-design.md +3 -3
  71. package/src/llms/tokens.gen.json +13 -5
  72. package/dist/code-BYkr5k55.js +0 -134
  73. package/dist/slider-Cqpreglu.js +0 -116
  74. package/src/llms/install-templates/hooks/baseline-staleness.sh +0 -24
@@ -2,4 +2,18 @@
2
2
  # SessionStart: inject the web-ui router pointer once per session. Fixed string — the
3
3
  # hook-injection eval asserts on it verbatim.
4
4
  echo "This project depends on @imfusion/web-ui. Start at the imf-web-ui skill: it decides whether guidance is needed and routes to the right companion. Repo docs carry only what is unique to this repo."
5
+
6
+ # Installed skills that differ from the package's copy are stale. A symlinked checkout
7
+ # carries work-in-progress skills, so it never counts.
8
+ ROOT=$(cd "$(dirname "$0")/../../.." && pwd) || exit 0
9
+ PACKAGE="$ROOT/node_modules/@imfusion/web-ui"
10
+ [ -d "$PACKAGE/src/llms/skills" ] && [ ! -L "$PACKAGE" ] || exit 0
11
+ for dir in "$PACKAGE"/src/llms/skills/imf-web-ui* "$ROOT"/.agents/skills/imf-web-ui*; do
12
+ [ -d "$dir" ] || continue
13
+ name=$(basename "$dir")
14
+ if ! diff -rq "$PACKAGE/src/llms/skills/$name" "$ROOT/.agents/skills/$name" >/dev/null 2>&1; then
15
+ echo "The installed imf-web-ui skills differ from the @imfusion/web-ui package in node_modules. Tell the user and offer the imf-web-ui-update skill, or \`npx web-ui install skills\`."
16
+ break
17
+ fi
18
+ done
5
19
  exit 0
@@ -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
 
@@ -83,13 +96,13 @@ UI component or a hard-coded color waiting for a token, is evidence for that top
83
96
  ## Agent tooling checks
84
97
 
85
98
  For `agent-tooling`, compare the declared version in `package.json`, the installed
86
- `node_modules/@imfusion/web-ui/package.json`, every `.imf-web-ui-skill-version.json` marker, the `AGENTS.md` fence, hook
87
- scripts, and both host registrations with the shipped topic. Version drift, extra hooks, stale markers, and missing
88
- registrations are findings; point version drift to `imf-web-ui-update`. Registry availability is unverified. Read files as
89
- text; do not run installers or hooks.
99
+ `node_modules/@imfusion/web-ui/package.json`, the installed skills against `node_modules/@imfusion/web-ui/src/llms/skills`,
100
+ the `AGENTS.md` fence, hook scripts, and both host registrations with the shipped topic. Version drift, extra hooks, skills
101
+ that differ from the package copy, and missing registrations are findings; point version drift to `imf-web-ui-update`.
102
+ Registry availability is unverified. Read files as 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
 
@@ -12,12 +12,13 @@ npx web-ui install skills --harness=claude
12
12
 
13
13
  It installs or refreshes the `imf-web-ui-*` skills, updates the managed `AGENTS.md` fence, and removes skills that the
14
14
  package no longer ships. The skills always live in `.agents/skills/`. `--harness` selects `claude`, which also links each
15
- skill into `.claude/skills/`, `codex`, or `all`; without it, the command prompts and remembers the choice. Pass
16
- `--reconfigure` to choose again. On a project that already has an install, re-running `install skills` without `--harness`
17
- reuses the remembered harness instead of narrowing it.
15
+ skill into `.claude/skills/`, `codex`, or `all`; without it, the command prompts on a first install. Pass `--reconfigure` to
16
+ choose again. On a project that already has an install, re-running `install skills` or `install hooks` without `--harness`
17
+ reads the harness back from the installed files: Claude skill links and installer-owned hook registrations, with Codex
18
+ assumed when skills leave no trace.
18
19
 
19
- Skill version markers (`.imf-web-ui-skill-version.json`) show which package version installed each skill. Refresh them with
20
- the installer; do not hand-edit the vendored files.
20
+ Refresh the skills with the installer; do not hand-edit them. Keep `.agents/skills/imf-web-ui*` out of the project's
21
+ formatter: a reformatted skill no longer matches the package copy, and the session-start check reports it as stale.
21
22
 
22
23
  ## The hooks
23
24
 
@@ -36,7 +37,8 @@ harness that is not selected.
36
37
 
37
38
  The shipped events are:
38
39
 
39
- - `SessionStart`: point the agent at the Web UI router once per session.
40
+ - `SessionStart`: point the agent at the Web UI router once per session, and tell it when the installed skills differ from
41
+ the copy in `node_modules/@imfusion/web-ui`.
40
42
  - `SubagentStart`: provide the same pointer to spawned agents.
41
43
  - `UserPromptSubmit`: name the companion skills relevant to the prompt.
42
44
  - `Stop`: ask for project verification after a turn edits source files.
@@ -44,9 +46,6 @@ The shipped events are:
44
46
  Read existing registrations first. If the project already covers an event, do not stack a second hook; adapt the existing
45
47
  registration and keep the missing behavior.
46
48
 
47
- `baseline-staleness.sh` is a pre-commit check, not an agent hook. It compares installed skill markers with the installed
48
- package version.
49
-
50
49
  ## Codex trust
51
50
 
52
51
  Codex runs project hooks only after the project is trusted and each script is approved through `/hooks`. Revisit that review
@@ -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
 
@@ -25,6 +28,6 @@ The `staged` block and the scripts under `scripts/verify/` own the step lists. H
25
28
 
26
29
  ## Staleness at commit time
27
30
 
28
- Pre-commit also runs advisory checks for vendored skill markers and documentation drift. If a staged change makes a doc
29
- stale, update the doc in the same commit. For an outdated installed skill, run `npx web-ui install skills` (also
30
- `npx web-ui install hooks` if the project uses hooks) with the project's existing `--harness` in the consumer project.
31
+ Pre-commit also runs an advisory check for documentation drift. If a staged change makes a doc stale, update the doc in the
32
+ same commit. Outdated installed skills surface at session start instead, as the `agent-tooling` topic describes; refresh them
33
+ with `npx web-ui install skills` (also `npx web-ui install hooks` if the project uses hooks).
@@ -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
@@ -79,8 +79,9 @@ npx web-ui install skills
79
79
  npx web-ui install hooks
80
80
  ```
81
81
 
82
- Both commands reuse the recorded harness. An install from the old `web-ui-install` has no record: pass the project's existing
83
- harness once, e.g. `--harness=all`. Stop on install failure. Do not use `--force` or `--legacy-peer-deps`.
82
+ Both commands read the harness back from the installed files and print it. Codex leaves no trace without hooks, so a Claude
83
+ and Codex install without hooks reads as Claude only; when the printed harness is wrong, pass the project's harness, e.g.
84
+ `--harness=all`. Stop on install failure. Do not use `--force` or `--legacy-peer-deps`.
84
85
 
85
86
  The installer owns the vendored skills, its `AGENTS.md` fence, and hook scripts. Run the installer commands after the package
86
87
  update even when the existing registrations look complete: they refresh scripts, prune retired registrations, and merge
@@ -88,14 +89,20 @@ installer-owned entries idempotently into the registration file for the chosen h
88
89
  `.codex/hooks.json`, or both). Stop on malformed settings or installer conflicts. Read existing host registrations first and
89
90
  do not stack an event the project already covers.
90
91
 
91
- After the installer command, inspect the installed skill diffs and briefly report any guidance changes; version-marker
92
- changes alone do not count.
92
+ After the installer command, inspect the installed skill diffs and briefly report any guidance changes.
93
93
 
94
94
  Then re-read, in full, every changed skill and reference the remaining work uses, starting with this update skill when it
95
95
  changed. A diff shows what moved, not the step order you continue under, so it does not replace the re-read. Continue from
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
@@ -110,7 +117,7 @@ Run the consumer's documented full verification. Read its scripts first; prefer
110
117
  non-watch format, lint, typecheck, test, and build commands that exist.
111
118
 
112
119
  Verify that the installed package, manifest, and lockfile match the recorded target version. A mismatch is a base-update
113
- conflict. Check skill markers, the `AGENTS.md` fence, hook registrations, and the final update-owned path list.
120
+ conflict. Check the `AGENTS.md` fence, hook registrations, and the final update-owned path list.
114
121
 
115
122
  Report before the first commit:
116
123
 
@@ -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.