@imfusion/web-ui 0.8.1-dev.9.gda40b9aa → 0.9.0

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 (94) hide show
  1. package/CHANGELOG.md +252 -7
  2. package/README.md +29 -18
  3. package/THIRD_PARTY_NOTICES.md +34 -0
  4. package/dist/{code-D2lwxHgd.js → code-BYkr5k55.js} +13 -13
  5. package/dist/codegen/gen-icons.d.ts +29 -4
  6. package/dist/codegen/sharpen-icon.d.ts +11 -0
  7. package/dist/components/account-card/account-card.d.ts +9 -9
  8. package/dist/components/app-shell/app-shell.d.ts +59 -34
  9. package/dist/components/avatar/avatar.d.ts +1 -1
  10. package/dist/components/button/button.d.ts +14 -7
  11. package/dist/components/callout/callout.d.ts +4 -4
  12. package/dist/components/card/card.d.ts +21 -7
  13. package/dist/components/chip/chip.cva.d.ts +1 -1
  14. package/dist/components/chip/chip.d.ts +1 -1
  15. package/dist/components/chip-link/chip-link.d.ts +1 -1
  16. package/dist/components/code/code.d.ts +1 -1
  17. package/dist/components/icon/icon.d.ts +1 -1
  18. package/dist/components/icon-button/icon-button.d.ts +19 -0
  19. package/dist/components/icon-button/icon-button.meta.d.ts +2 -0
  20. package/dist/components/icon-button/index.d.ts +2 -0
  21. package/dist/components/kbd/aria-key-shortcuts.d.ts +5 -0
  22. package/dist/components/kbd/index.d.ts +2 -0
  23. package/dist/components/kbd/kbd.d.ts +14 -0
  24. package/dist/components/kbd/kbd.meta.d.ts +2 -0
  25. package/dist/components/logo/logo.d.ts +3 -3
  26. package/dist/components/meter/index.d.ts +2 -0
  27. package/dist/components/meter/meter.d.ts +33 -0
  28. package/dist/components/meter/meter.meta.d.ts +2 -0
  29. package/dist/components/navigation-menu/subs/link.d.ts +1 -1
  30. package/dist/components/popover/popover.d.ts +3 -3
  31. package/dist/components/prose/prose.d.ts +1 -1
  32. package/dist/components/row/row.d.ts +3 -3
  33. package/dist/components/skeleton/index.d.ts +2 -0
  34. package/dist/components/skeleton/skeleton.d.ts +47 -0
  35. package/dist/components/skeleton/skeleton.meta.d.ts +2 -0
  36. package/dist/components/slider/slider.d.ts +11 -1
  37. package/dist/components/spinner/spinner.d.ts +1 -1
  38. package/dist/components/stack/stack.d.ts +3 -3
  39. package/dist/components/table/table.d.ts +16 -7
  40. package/dist/components/tabs/tabs.d.ts +1 -1
  41. package/dist/components/toggle/toggle.d.ts +7 -1
  42. package/dist/components/tooltip/tooltip.d.ts +2 -2
  43. package/dist/components/typo/typo.d.ts +21 -12
  44. package/dist/icons/catalog.gen.d.ts +3804 -0
  45. package/dist/icons/create-icon.d.ts +3 -0
  46. package/dist/icons/icon-config-context.d.ts +3 -0
  47. package/dist/icons/icon-config.d.ts +2 -2
  48. package/dist/icons/icons.gen.d.ts +1737 -1672
  49. package/dist/icons-BsK_o-Cb.js +36358 -0
  50. package/dist/icons.js +2 -2
  51. package/dist/index.d.ts +5 -0
  52. package/dist/index.js +2079 -1607
  53. package/dist/integrations/code-highlight.js +6 -4
  54. package/dist/integrations/frame-selection/frame-selection-view.d.ts +24 -0
  55. package/dist/integrations/frame-selection/frame-selection.d.ts +21 -0
  56. package/dist/integrations/frame-selection/frame-selection.meta.d.ts +2 -0
  57. package/dist/integrations/frame-selection/index.d.ts +2 -0
  58. package/dist/integrations/frame-selection.js +121 -0
  59. package/dist/integrations/image-display-options/image-display-options-view.d.ts +1 -1
  60. package/dist/integrations/image-display-options/image-display-options.d.ts +1 -1
  61. package/dist/integrations/image-display-options.js +56 -55
  62. package/dist/llms/evals/runner/config.d.ts +1 -0
  63. package/dist/llms/gen-tokens.d.ts +1 -1
  64. package/dist/{paths-Oi0v8x-w.js → paths-B5Ht3ETc.js} +2 -2
  65. package/dist/separator-lzz4A3x_.js +26 -0
  66. package/dist/slider-Cqpreglu.js +116 -0
  67. package/dist/style.css +1 -1
  68. package/dist/tabs-DRyqtS04.js +358 -0
  69. package/dist/utils/email.d.ts +7 -0
  70. package/dist/utils/status-glyphs.d.ts +4 -0
  71. package/dist/vite.js +2 -2
  72. package/dist/web-ui-cli.js +1 -1
  73. package/docs/user-guide/AgentTooling.mdx +8 -3
  74. package/package.json +38 -38
  75. package/src/docgen/doc.gen.json +1376 -214
  76. package/src/llms/icon-catalog.gen.json +5541 -1671
  77. package/src/llms/llms.gen.txt +29 -2
  78. package/src/llms/skills/imf-web-ui/SKILL.md +3 -1
  79. package/src/llms/skills/imf-web-ui-audit/SKILL.md +18 -4
  80. package/src/llms/skills/imf-web-ui-components/SKILL.md +3 -1
  81. package/src/llms/skills/imf-web-ui-conventions/templates/AUDIT_CHECKLIST.md +3 -2
  82. package/src/llms/skills/imf-web-ui-conventions/topics/git.md +2 -3
  83. package/src/llms/skills/imf-web-ui-conventions/topics/npm-project.md +3 -4
  84. package/src/llms/skills/imf-web-ui-conventions/topics/styling.md +4 -2
  85. package/src/llms/skills/imf-web-ui-conventions/topics/tooling.md +83 -42
  86. package/src/llms/skills/imf-web-ui-setup/SKILL.md +2 -0
  87. package/src/llms/skills/imf-web-ui-update/SKILL.md +42 -7
  88. package/src/llms/skills/imf-web-ui-user-profile/SKILL.md +95 -0
  89. package/src/llms/skills/imf-web-ui-ux/SKILL.md +2 -0
  90. package/src/llms/tokens.gen.json +3 -3
  91. package/dist/icons-CaGCCwG-.js +0 -78
  92. package/dist/tabs-BS6rqrrA.js +0 -398
  93. /package/dist/{meta-j-7HGrWv.js → meta-V_KyLeGn.js} +0 -0
  94. /package/dist/{chunk-DmhlhrBa.js → rolldown-runtime-Dy4uBu1J.js} +0 -0
package/CHANGELOG.md CHANGED
@@ -1,27 +1,272 @@
1
1
  # Changelog
2
2
 
3
- ## [Unreleased]
3
+ ## `0.9.0`          2026.10.14
4
+
5
+ ### Highlights
6
+
7
+ #### A reworked AppShell
8
+
9
+ The navbar runs the full height of the screen on the support background, and Header and Footer start beside it.
10
+ `AppShell.Main` is the one scroll container, with a thin scrollbar, so the header, navbar, and
11
+ aside stay put while the content scrolls. Sections drop their dividers and are set apart by their backgrounds
12
+ ([WEBSDK-243](https://imfusion.atlassian.net/browse/WEBSDK-243)).
13
+
14
+ - `variant="branded"` fills the navbar with the primary color and switches its text to on-color.
15
+ - `AppShell.NavItem` is a navbar link with an icon, a label, and a description, in the navbar's text color.
16
+ - `AppShell.Burger` opens the navbar as a drawer below its breakpoint. The aside opens as a bottom sheet on phones and a side
17
+ drawer on tablets, and both report through `onOpenChange`.
18
+ - The docked aside has a drag handle that resizes it; a double-click or Home resets it. `resizable`, `onWidthChange`, and
19
+ `resizeLabel` configure it.
20
+ - `AppShell.Root` is an inline-size container. The navbar, the aside, and Main size against it, and Main keeps at least 45%
21
+ of the shell.
22
+
23
+ ![Resizing from desktop to phone: the shell reflows smoothly, and the navbar opens as a drawer](docs/assets/changelog/0.9.0-appshell-resize.mp4)
24
+ ![Desktop: the navbar runs the full height, and Main scrolls between the fixed sections](docs/assets/changelog/0.9.0-appshell-desktop.webp)
25
+ ![Branded navbar filled with the primary color](docs/assets/changelog/0.9.0-appshell-branded.webp)
26
+ ![Dark color scheme](docs/assets/changelog/0.9.0-appshell-dark.webp)
27
+ ![Phone: the navbar opens as a drawer](docs/assets/changelog/0.9.0-appshell-phone.webp)
28
+ ![Phone: the aside opens as a bottom sheet](docs/assets/changelog/0.9.0-appshell-phone-sheet.webp)
29
+
30
+ #### Medical imaging icons
31
+
32
+ `@imfusion/web-ui/icons` has 65 Medical Imaging icons from the ImFusion design team, such as `ToolBrush`, `ToolWindowLevel`,
33
+ and `DefineRoiAction`. Every icon now has square corners, line caps, and joins to match them; letters, digits, stars, and
34
+ badges keep Iconoir's round shapes. The package generates its icons from Iconoir's SVG source, so `iconoir-react` is no longer
35
+ a dependency, and `strokeWidth` reaches every part of an icon, circles and rectangles included
36
+ ([WEBSDK-267](https://imfusion.atlassian.net/browse/WEBSDK-267)).
37
+
38
+ The Iconography catalog filters by origin, `iconoir` or `imfusion`, and gives every icon its own page with a shareable link
39
+ and an SVG download. `imf-web-ui-update` proposes catalog icons for project-local icons they now cover.
40
+
41
+ #### Built with Vite+
42
+
43
+ Web UI builds, tests, lints, and formats with [Vite+](https://viteplus.dev/), a single `vp` toolchain over Vite, Vitest,
44
+ Oxlint, and Oxfmt. Oxlint and Oxfmt replace ESLint and Prettier, so their config files are gone: one `vite.config.ts`
45
+ holds the build, lint, format, and staged-check settings, and only the test projects keep their own `vitest.config.ts`.
46
+ The lint check drops from about 9 seconds to 1, and the format check from 2 seconds to half a second. The pre-commit
47
+ hook checks the staged state, every generated file included, and never rewrites anything.
48
+
49
+ `imf-web-ui-conventions` recommends the same setup for ImFusion frontends, so `imf-web-ui-audit` reports a project that still
50
+ runs Vite, Vitest, ESLint, and Prettier as separate tools. `vp migrate --no-hooks` moves one over.
51
+
52
+ #### Skills that fit the person
53
+
54
+ On the first run, the agent asks a few short questions, one at a time: your name, your technical background, how much you
55
+ have built for the web, how comfortable you are with npm and the terminal, how much explanation you want, and how you like to work.
56
+ You can decline any question, and you see the result before anything is saved.
57
+
58
+ The answers go to `~/.config/imfusion/web-ui/consumer-profile.md`, outside the project. Every skill you run reads that file.
59
+ Someone new to npm gets a plain sentence before each command and a short gloss for a term like lockfile; a seasoned developer
60
+ gets the short version. You can ask for a pause after each step or after each finished piece, and the approvals the
61
+ workflows already ask for stay as they are. Say "change my profile" to answer again.
62
+
63
+ #### New and updated components
64
+
65
+ - `Avatar` is new. It shows a person's photo, their initials, or a person glyph, in three sizes and an on-color context.
66
+
67
+ ![Avatar sizes, light](docs/assets/changelog/0.9.0-avatar-light.webp)
68
+ ![Avatar sizes, dark](docs/assets/changelog/0.9.0-avatar-dark.webp)
69
+
70
+ - `AccountCard` is new. It links to the signed-in person's account, with an action such as Log out in its top row, and
71
+ `AccountCard.Group` lists several cards with the current one marked. `splitEmail` and `initialsFromEmail` fill an
72
+ `AccountCard` or `Avatar` before the person's name is known.
73
+
74
+ ![AccountCard group, light](docs/assets/changelog/0.9.0-account-card-light.webp)
75
+ ![AccountCard group, dark](docs/assets/changelog/0.9.0-account-card-dark.webp)
76
+
77
+ - `Skeleton` is new. It holds the place of loading data, one text line or a sized block at a time.
78
+
79
+ ![Skeleton, light](docs/assets/changelog/0.9.0-skeleton-light.webp)
80
+ ![Skeleton, dark](docs/assets/changelog/0.9.0-skeleton-dark.webp)
81
+
82
+ - `Meter` is new. It shows a measured amount within a range, such as seats in use, as a read-only bar
83
+ ([WEBSDK-250](https://imfusion.atlassian.net/browse/WEBSDK-250)).
84
+
85
+ ![Meter, light](docs/assets/changelog/0.9.0-meter-light.webp)
86
+ ![Meter, dark](docs/assets/changelog/0.9.0-meter-dark.webp)
87
+
88
+ - `IconButton` is new. It is a square `Button` with a single icon, named by its `label`.
89
+
90
+ ![IconButton variants and sizes, light](docs/assets/changelog/0.9.0-icon-button-light.webp)
91
+ ![IconButton variants and sizes, dark](docs/assets/changelog/0.9.0-icon-button-dark.webp)
92
+
93
+ - `Kbd` is new. It shows a keyboard key as a keycap, alone, as a combination such as `Ctrl Z`, or with a `Keyboard` glyph
94
+ in running text. `@imfusion/web-ui/icons` adds that `Keyboard` icon.
95
+
96
+ ![Kbd keys in running text, light](docs/assets/changelog/0.9.0-kbd-light.webp)
97
+ ![Kbd keys in running text, dark](docs/assets/changelog/0.9.0-kbd-dark.webp)
98
+
99
+ - `Button`, `IconButton`, and `Toggle` take a `shortcut` prop that shows the activating key in their top corner and sets
100
+ `aria-keyshortcuts`. The app binds the key, for example with TanStack Hotkeys, which `imf-web-ui-conventions` now
101
+ recommends.
102
+
103
+ ![Pressing P, E, D, and S drives a toolbar through its shortcut hints](docs/assets/changelog/0.9.0-shortcut-hotkeys.mp4)
104
+ ![Shortcut hints on a ToggleGroup and a Button, light](docs/assets/changelog/0.9.0-shortcut-light.webp)
105
+ ![Shortcut hints on a ToggleGroup and a Button, dark](docs/assets/changelog/0.9.0-shortcut-dark.webp)
106
+
107
+ - `FrameSelection` is new, from `@imfusion/web-ui/integrations/frame-selection`. It steps through the frames of ImFusion Web
108
+ SDK image sets with a position slider and a current-of-total readout.
109
+
110
+ ![FrameSelection, light](docs/assets/changelog/0.9.0-frame-selection-light.webp)
111
+ ![FrameSelection, dark](docs/assets/changelog/0.9.0-frame-selection-dark.webp)
112
+
113
+ - `Slider.Root` has `variant="position"`, a scrollbar-like trough and thumb for picking one position in a range.
114
+
115
+ ![Slider position variant, light](docs/assets/changelog/0.9.0-slider-position-light.webp)
116
+ ![Slider position variant, dark](docs/assets/changelog/0.9.0-slider-position-dark.webp)
117
+
118
+ - `Card.Status` marks a card's outcome with a tinted column or band and the matching `Callout` glyph.
119
+
120
+ ![Card statuses, light](docs/assets/changelog/0.9.0-card-status-light.webp)
121
+ ![Card statuses, dark](docs/assets/changelog/0.9.0-card-status-dark.webp)
122
+
123
+ - `Table.RowLink` links a row to its detail page from inside a cell and takes `render` for router links.
124
+
125
+ ![Table with row links, light](docs/assets/changelog/0.9.0-table-row-link-light.webp)
126
+ ![Table with row links, dark](docs/assets/changelog/0.9.0-table-row-link-dark.webp)
127
+
128
+ - `Typo.Eyebrow` is new. It sets a short mono label above a heading, such as a study name over a page title.
129
+
130
+ ![Typo.Eyebrow over a page heading and a card title, light](docs/assets/changelog/0.9.0-typo-eyebrow-light.webp)
131
+ ![Typo.Eyebrow over a page heading and a card title, dark](docs/assets/changelog/0.9.0-typo-eyebrow-dark.webp)
132
+
133
+ - `Typo` body members take a `weight` of `regular`, `medium`, or `strong`, for short text on cards that reads thin at the
134
+ regular weight.
135
+
136
+ ![Typo weights, light](docs/assets/changelog/0.9.0-typo-weight-light.webp)
137
+ ![Typo weights, dark](docs/assets/changelog/0.9.0-typo-weight-dark.webp)
138
+
139
+ - `Button` centers its icons and label when a container stretches it; `justify="spread"` pins the icons to the edges.
140
+
141
+ ![Button justify, light](docs/assets/changelog/0.9.0-button-justify-light.webp)
142
+ ![Button justify, dark](docs/assets/changelog/0.9.0-button-justify-dark.webp)
143
+
144
+ #### Important fixes
145
+
146
+ - Screen readers announce `Field.Error` when its message appears.
147
+ - `WebUIProvider` and the components under it render on React 18.
148
+ - `data-imf-ui-color-scheme` on a nested element switches the semantic colors for that subtree.
149
+ - The `@imfusion/sdk` and `@imfusion/sdk-react` peers accept any version, SDK dev builds included.
150
+
151
+ #### Migrating from 0.8
152
+
153
+ Web UI needs Safari 17.5, Chrome 123, or Firefox 120. Older browsers show no semantic colors.
154
+
155
+ The document no longer scrolls inside an `AppShell`. Move window scroll listeners onto `AppShell.Main`:
156
+
157
+ ```diff
158
+ - window.addEventListener("scroll", onScroll);
159
+ - <AppShell.Main>…</AppShell.Main>
160
+ + <AppShell.Main onScroll={onScroll}>…</AppShell.Main>
161
+ ```
162
+
163
+ Scroll restoration and `scrollTo` take Main's element through a `ref`:
164
+
165
+ ```diff
166
+ - window.scrollTo({ top: 0 });
167
+ + mainRef.current?.scrollTo({ top: 0 });
168
+ + <AppShell.Main ref={mainRef}>…</AppShell.Main>
169
+ ```
170
+
171
+ AppShell sections draw no dividers. Delete overrides of the border properties, which no longer apply:
172
+
173
+ ```diff
174
+ - --imf-ui-appshell-border-color: var(--imf-ui-color-bg-brand);
175
+ - --imf-ui-appshell-border-width: var(--imf-ui-border-size-2);
176
+ ```
177
+
178
+ ### Breaking
179
+
180
+ - **Breaking:** The library supports Safari 17.5, Chrome 123, and Firefox 120 or newer. — Nikolaus Brunner <brunner@imfusion.com>
181
+ - The stylesheet ships native `light-dark()`; older browsers get no semantic colors.
182
+ - The JavaScript build targets the same versions.
183
+ - **Breaking:** AppShell sections draw no dividers, and the `--imf-ui-appshell-border-color`, `--imf-ui-appshell-border-width`, and `--imf-ui-appshell-border-style` properties are removed. ([WEBSDK-243](https://imfusion.atlassian.net/browse/WEBSDK-243)) — Nikolaus Brunner <brunner@imfusion.com>
184
+ - **Breaking:** `AppShell.Main` is the scroll container and the document does not scroll; window scroll listeners, scroll restoration, and `scrollTo` target `AppShell.Main`. ([WEBSDK-243](https://imfusion.atlassian.net/browse/WEBSDK-243)) — Nikolaus Brunner <brunner@imfusion.com>
4
185
 
5
186
  ### Added
6
187
 
7
- - Components — Nikolaus Brunner <brunner@imfusion.com>
8
- - `Avatar` shows a person's photo, their initials, or a person glyph, in three sizes and an on-color context.
9
- - `AccountCard` links to the signed-in person's account: a large title over a quiet subtitle, then the organization and a
10
- mono role under a rule, with an action such as Log out in its top row. Emails wrap at the `@` and at dots, and
11
- `AccountCard.Group` lists several cards with the current one marked.
188
+ - Agent skills — Nikolaus Brunner <brunner@imfusion.com>
189
+ - `web-ui-dev-user-profile` keeps a contributor's profile in `dev-profile.md` beside the consumer one;
190
+ `web-ui-dev-start` and `web-ui-dev-getting-started` read it before any other step.
191
+ - `web-ui-dev-new-integration` builds a component around a package only some apps install. It runs
192
+ `web-ui-dev-new-component` with the integration contract, which `docs/component-definition.md` now spells out.
193
+ - `web-ui-dev-design-system-sync` builds the design system and publishes it to its Claude Design System artifact, with
194
+ resolved light and dark tokens, a live preview per component, the design language and a cover
195
+ ([CP-202](https://imfusion.atlassian.net/browse/CP-202)).
196
+ - `web-ui-dev-new-icon` adds custom icons from an SVG, a Figma link, a Claude Design project, or a description. It checks the icon catalog
197
+ first, and codegen validates the SVGs in `src/icons/svg/` and exports them beside the Iconoir set ([WEBSDK-267](https://imfusion.atlassian.net/browse/WEBSDK-267)).
198
+ - `imf-web-ui-conventions` recommends TanStack Hotkeys for keyboard shortcuts, and its tooling map names each tool's
199
+ documentation source: `npx @tanstack/cli` with the library id for TanStack, the official site for the rest.
200
+ - AppShell ([WEBSDK-243](https://imfusion.atlassian.net/browse/WEBSDK-243)) — Nikolaus Brunner <brunner@imfusion.com>
201
+ - `--imf-ui-appshell-aside-min-width` (`max(20rem, 20cqi)`) is the narrowest the docked aside gets, dragged or not.
202
+ - `--imf-ui-appshell-main-min-width` (`max(24rem, 45cqi)`) caps the docked aside so Main keeps at least 45% of the shell.
203
+ - `--imf-ui-appshell-main-max-width` caps Main's content at `100rem` on wide screens, aligned to the start.
204
+ - `--imf-ui-appshell-navbar-padding` sets one gutter for the navbar's sections. Nav items and an `AccountCard` widen
205
+ their surface past it, so the logo, icons, and avatar share one edge, and the first section is a header-height row.
206
+ - Storybook — Nikolaus Brunner <brunner@imfusion.com>
207
+ - The toolbar has Full HD, QHD, and Ultrawide viewports for previews past `xl`.
208
+ - The floating token button is hidden by default; `parameters.tokenDrawer.show` shows it for a story, and a toolbar toggle overrides either way.
209
+ - Sidebar labels stay in the sidebar text color when a story has test warnings or errors; the status icon carries the state.
210
+ - `All` matrices and stories with a fixed render hide the controls panel; stories that log actions keep it without the Controls tab.
12
211
 
13
212
  ### Changed
14
213
 
214
+ - Icons ([WEBSDK-267](https://imfusion.atlassian.net/browse/WEBSDK-267)) — Nikolaus Brunner <brunner@imfusion.com>
215
+ - Every record in the icon catalog names its `origin`, `iconoir` or `imfusion`, and its source `file`. While no category
216
+ filter is set, each Iconography tile names the icon's category.
217
+ - Components — Nikolaus Brunner <brunner@imfusion.com>
218
+ - Parts that render a plain element, such as `AppShell.Main`, `Stack`, and `Card.Root`, accept a `ref`.
219
+ - `Typo.Link` sets its text in the strong weight, so a link stands out from body text of the same color.
220
+ - Documentation — Nikolaus Brunner <brunner@imfusion.com>
221
+ - The `CodeHighlight` install section names TanStack Highlight 0.1.x and the 0.1.0 build it is tested against.
222
+ - The consumer agent tooling diagram groups the skills you run in one lane, so the router and the profile each reach them
223
+ with a single edge.
15
224
  - Agent skills — Nikolaus Brunner <brunner@imfusion.com>
16
225
  - `imf-web-ui-conventions` declares named functions, local component helpers included, with `function`, and keeps
17
226
  inline callbacks as arrows.
18
227
  - `imf-web-ui-conventions` loads route data with `queryClient.query({ ...options, staleTime: "static" })` in place of the
19
228
  deprecated `ensureQueryData`.
229
+ - `imf-web-ui-update` opens with the route and its decision points, and names the optional `imf-web-ui-audit full` in
230
+ the report and its closing offer.
231
+ - `web-ui-dev-teardown-worktree merge` rebases onto the fetched `origin` base, runs `verify:full`, fast-forwards, and
232
+ pushes; it never creates a merge commit. `web-ui-dev-commit` routes landing a worktree branch there.
233
+ - `web-ui-dev-changelog` gives every new component, and every visibly changed one, a light and a dark screenshot of its
234
+ story in the release highlights.
235
+ - `imf-web-ui-update` checks for a running npm process by PID and working directory, and shows a command line only for
236
+ a process in the package being updated.
237
+ - `web-ui-dev-eval-consumer-skills` records every run's benchmark, and skips the record only when the user asks for it.
238
+ - `web-ui-dev-claude-design-sync-legacy` uploads to the standalone Claude Design site until it closes on 2026-12-14
239
+ ([CP-202](https://imfusion.atlassian.net/browse/CP-202)).
240
+ - `web-ui install` formats the files it writes, skill version markers and hook settings, with the project's `vp fmt` when
241
+ the project has it, and with its Prettier otherwise.
242
+ - `imf-web-ui-audit` searches the frontend for `TODO`, `FIXME`, `HACK`, and similar markers. A marker that admits a
243
+ convention gap backs that topic's finding, and the rest go under `Suggestions`.
244
+ - CI and verification — Nikolaus Brunner <brunner@imfusion.com>
245
+ - `verify:generated` checks every committed `*.gen.*` file against the sources that produce it. It runs in the pre-commit hook, in `verify:full`, and as a TeamCity step.
246
+ - The pre-push hook runs `verify:fast`, the `verify:full` suite without the builds and tests.
247
+ - AppShell ([WEBSDK-243](https://imfusion.atlassian.net/browse/WEBSDK-243)) — Nikolaus Brunner <brunner@imfusion.com>
248
+ - Below their breakpoints the navbar opens as a drawer from the inline end and the aside as a bottom sheet on phones or a side drawer on tablets.
249
+ - `AppShell.Root` is an inline-size container; the navbar is `clamp(16rem, 18cqi, 20rem)` and the aside `clamp(20rem, 30cqi, 40rem)` wide.
250
+ - Main and aside padding grow with the shell, from `1.25rem` up to `3rem` and `2rem`, and the header is `4rem` tall.
20
251
 
21
252
  ### Fixed
22
253
 
23
254
  - Components — Nikolaus Brunner <brunner@imfusion.com>
24
- - Screen readers announce `Field.Error` when its message appears; it has `role="alert"` by default.
255
+ - On-color `ghost` and `outline` Buttons invert to a white fill on hover instead of near-black.
256
+ - `Button`'s `startIcon` sits level with the label.
257
+ - Peer dependencies — Nikolaus Brunner <brunner@imfusion.com>
258
+ - The `FrameSelection` and `ImageDisplayOptions` install sections name SDK 1.x and the 1.0.0 build they are tested against.
259
+ - CI and verification — Nikolaus Brunner <brunner@imfusion.com>
260
+ - The dev skill eval report scores the dev scenarios and lists only the `web-ui-dev-*` skills.
261
+ - `verify:icons` fails when a one-icon import bundles any other icon ([WEBSDK-267](https://imfusion.atlassian.net/browse/WEBSDK-267)).
262
+ - The dev skill eval builds its workspace from a checkout without uncommitted changes.
263
+ - The repository's Chrome DevTools MCP server runs with an isolated profile, so several agent sessions can drive Chrome
264
+ at once.
265
+ - The Iconography interaction test covers the catalog's core behavior and finishes well within the CI timeout.
266
+ - Storybook — Nikolaus Brunner <brunner@imfusion.com>
267
+ - The Iconography catalog shows solid icons as drawn, and its Copy JSX snippet adds no fill or stroke override.
268
+ - Long icon names in the Iconography catalog break between words instead of being cut off.
269
+ - Button's controls list `variant`, `size`, and its other variant props before the inherited Base UI props.
25
270
 
26
271
  ---
27
272
 
package/README.md CHANGED
@@ -65,28 +65,39 @@ npm install
65
65
  npm run git:config
66
66
  ```
67
67
 
68
- `git:config` sets the tracked hooks path, enables rebase pulls, and requires fast-forward merges. The
68
+ `git:config` sets the tracked hooks path, enables rebase pulls, and requires fast-forward merges. Every checkout and worktree
69
+ needs its own `node_modules`; the hooks stop with "run npm ci" without one.
70
+
71
+ The pre-commit hook runs `vp staged` against the staged state: it lints and format-checks the repository, checks that every
72
+ generated `*.gen.*` file matches its sources (`npm run verify:generated`), and runs the pinned-dependency, Knip, type-check,
73
+ and TeamCity DSL checks when their inputs are staged. It never rewrites or stages files; on failure it restores the working
74
+ tree and prints what to fix. The pre-push hook runs `npm run verify:fast`, the CI suite without the builds and tests; the
75
+ agent commit and landing skills run `npm run verify:full`. The
69
76
  [`web-ui-dev-getting-started`](./.agents/skills/web-ui-dev-getting-started/SKILL.md) skill can route a first task.
70
77
 
71
78
  The main development skills are:
72
79
 
73
- | Skill | Use it for |
74
- | ---------------------------------- | ------------------------------------------------ |
75
- | `/web-ui-dev-getting-started` | Route a first task based on experience and goal. |
76
- | `/web-ui-dev-start` | Establish task context and working location. |
77
- | `/web-ui-dev-new-component` | Add a primitive. |
78
- | `/web-ui-dev-update-component` | Change an existing primitive. |
79
- | `/web-ui-dev-design-component` | Apply the ImFusion brand to a component. |
80
- | `/web-ui-dev-story` | Author a Storybook story. |
81
- | `/web-ui-dev-commit` | Verify and commit a change. |
82
- | `/web-ui-dev-changelog` | Write the changelog entry or curate a release. |
83
- | `/web-ui-dev-release` | Prepare and ship a release. |
84
- | `/web-ui-dev-audit-docs` | Check docs for drift before a commit. |
85
- | `/web-ui-dev-llm-framework` | Keep the LLM framework diagrams in sync. |
86
- | `/web-ui-dev-eval-consumer-skills` | Evaluate the shipped consumer skills. |
87
- | `/web-ui-dev-claude-design-sync` | Update the design system in Claude Design. |
88
- | `/web-ui-dev-teardown-worktree` | Remove a finished worktree. |
89
- | `/web-ui-dev-mcp` | Recover one optional MCP capability. |
80
+ | Skill | Use it for |
81
+ | --------------------------------------- | ---------------------------------------------- |
82
+ | `/web-ui-dev-getting-started` | Route a first task based on profile and goal. |
83
+ | `/web-ui-dev-start` | Establish task context and working location. |
84
+ | `/web-ui-dev-new-component` | Add a primitive. |
85
+ | `/web-ui-dev-new-integration` | Add an integration with an optional peer. |
86
+ | `/web-ui-dev-new-icon` | Add custom icons from a design source. |
87
+ | `/web-ui-dev-update-component` | Change an existing primitive. |
88
+ | `/web-ui-dev-design-component` | Apply the ImFusion brand to a component. |
89
+ | `/web-ui-dev-story` | Author a Storybook story. |
90
+ | `/web-ui-dev-commit` | Verify and commit a change. |
91
+ | `/web-ui-dev-changelog` | Write the changelog entry or curate a release. |
92
+ | `/web-ui-dev-record-clip` | Record a story as a video or looping clip. |
93
+ | `/web-ui-dev-release` | Prepare and ship a release. |
94
+ | `/web-ui-dev-audit-docs` | Check docs for drift before a commit. |
95
+ | `/web-ui-dev-llm-framework` | Keep the LLM framework diagrams in sync. |
96
+ | `/web-ui-dev-eval-consumer-skills` | Evaluate the shipped consumer skills. |
97
+ | `/web-ui-dev-design-system-sync` | Publish the design system to Claude. |
98
+ | `/web-ui-dev-claude-design-sync-legacy` | Upload to the standalone Claude Design site. |
99
+ | `/web-ui-dev-teardown-worktree` | Remove a finished worktree. |
100
+ | `/web-ui-dev-mcp` | Recover one optional MCP capability. |
90
101
 
91
102
  ### Work on it from a consumer app
92
103
 
@@ -32,3 +32,37 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
32
32
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
33
33
  SOFTWARE.
34
34
  ```
35
+
36
+ ## Iconoir
37
+
38
+ - Project: [Iconoir](https://github.com/iconoir-icons/iconoir)
39
+ - License: [MIT](https://github.com/iconoir-icons/iconoir/blob/main/LICENSE)
40
+
41
+ The icons exported from `@imfusion/web-ui/icons` are generated from Iconoir SVG source, with modified corners, line caps, and
42
+ line joins.
43
+
44
+ The following license text applies only to those Iconoir-derived icons and is reproduced verbatim:
45
+
46
+ ```text
47
+ MIT License
48
+
49
+ Copyright (c) 2021 Luca Burgio
50
+
51
+ Permission is hereby granted, free of charge, to any person obtaining a copy
52
+ of this software and associated documentation files (the "Software"), to deal
53
+ in the Software without restriction, including without limitation the rights
54
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
55
+ copies of the Software, and to permit persons to whom the Software is
56
+ furnished to do so, subject to the following conditions:
57
+
58
+ The above copyright notice and this permission notice shall be included in all
59
+ copies or substantial portions of the Software.
60
+
61
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
62
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
63
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
64
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
65
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
66
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
67
+ SOFTWARE.
68
+ ```
@@ -1,6 +1,6 @@
1
- import { t as e } from "./chunk-DmhlhrBa.js";
2
- import { t } from "./meta-j-7HGrWv.js";
3
- import { Ma as n, Xo as r, r as i } from "./icons-CaGCCwG-.js";
1
+ import { t as e } from "./rolldown-runtime-Dy4uBu1J.js";
2
+ import { Xo as t, ja as n, pM as r } from "./icons-BsK_o-Cb.js";
3
+ import { t as i } from "./meta-V_KyLeGn.js";
4
4
  import { cx as a } from "class-variance-authority";
5
5
  import { Fragment as o, jsx as s, jsxs as c } from "react/jsx-runtime";
6
6
  import { useCallback as l, useEffect as u, useRef as d, useState as f } from "react";
@@ -32,8 +32,8 @@ var g = {
32
32
  Inline: () => v,
33
33
  InlineLink: () => y
34
34
  });
35
- function v({ copyable: e = !1, children: t, render: a, ...l }) {
36
- let { copied: u, copy: f } = h(), _ = d(null), v = u ? n : r;
35
+ function v({ copyable: e = !1, children: i, render: a, ...l }) {
36
+ let { copied: u, copy: f } = h(), _ = d(null), v = u ? n : t;
37
37
  return m({
38
38
  defaultTagName: "code",
39
39
  render: a,
@@ -43,7 +43,7 @@ function v({ copyable: e = !1, children: t, render: a, ...l }) {
43
43
  children: /* @__PURE__ */ c(o, { children: [
44
44
  /* @__PURE__ */ s("span", {
45
45
  ref: _,
46
- children: t
46
+ children: i
47
47
  }),
48
48
  e && /* @__PURE__ */ s("span", { className: g.separator }),
49
49
  e && /* @__PURE__ */ s("button", {
@@ -52,7 +52,7 @@ function v({ copyable: e = !1, children: t, render: a, ...l }) {
52
52
  "data-copied": u || void 0,
53
53
  "aria-label": u ? "Copied" : "Copy",
54
54
  onClick: () => f(_.current?.textContent ?? ""),
55
- children: /* @__PURE__ */ s(i, {
55
+ children: /* @__PURE__ */ s(r, {
56
56
  glyph: v,
57
57
  size: 12,
58
58
  strokeWidth: 2,
@@ -85,15 +85,15 @@ function y({ kind: e = "external", ...t }) {
85
85
  });
86
86
  }
87
87
  y.displayName = "Code.InlineLink";
88
- function b({ className: e, language: t, hideCopy: o = !1, children: l, ...u }) {
89
- let { copied: f, copy: p } = h(), m = d(null), _ = f ? n : r;
88
+ function b({ className: e, language: i, hideCopy: o = !1, children: l, ...u }) {
89
+ let { copied: f, copy: p } = h(), m = d(null), _ = f ? n : t;
90
90
  return /* @__PURE__ */ c("div", {
91
91
  "data-imf-ui-component": "Code.Block",
92
92
  className: a(g.block, e),
93
93
  children: [
94
- t != null && /* @__PURE__ */ s("span", {
94
+ i != null && /* @__PURE__ */ s("span", {
95
95
  className: g.language,
96
- children: t
96
+ children: i
97
97
  }),
98
98
  !o && /* @__PURE__ */ s("button", {
99
99
  type: "button",
@@ -101,7 +101,7 @@ function b({ className: e, language: t, hideCopy: o = !1, children: l, ...u }) {
101
101
  "data-copied": f || void 0,
102
102
  "aria-label": f ? "Copied" : "Copy",
103
103
  onClick: () => p(m.current?.textContent ?? ""),
104
- children: /* @__PURE__ */ s(i, {
104
+ children: /* @__PURE__ */ s(r, {
105
105
  glyph: _,
106
106
  size: 12,
107
107
  strokeWidth: 2,
@@ -124,7 +124,7 @@ b.displayName = "Code.Block";
124
124
  //#region src/components/code/code.meta.ts
125
125
  var x = {
126
126
  name: "Code",
127
- category: t.display,
127
+ category: i.display,
128
128
  status: "stable",
129
129
  namespace: !0,
130
130
  description: "Displays source code inline or in a block, with optional language labels and copying. Also called a code snippet or code block.",
@@ -1,3 +1,13 @@
1
+ import { IconNode } from '../icons/create-icon';
2
+ export type IconOrigin = "iconoir" | "imfusion";
3
+ export type IconSource = {
4
+ name: string;
5
+ style: "regular" | "solid";
6
+ origin: IconOrigin;
7
+ /** Path of the source SVG inside its icon set, such as `regular/tool-3d-rotate.svg`. */
8
+ file: string;
9
+ nodes: IconNode[];
10
+ };
1
11
  export type IconMetadata = {
2
12
  filename: string;
3
13
  category: string;
@@ -8,6 +18,8 @@ export type IconCatalogRecord = {
8
18
  category: string;
9
19
  tags: string[];
10
20
  style: "regular" | "solid";
21
+ origin: IconOrigin;
22
+ file: string;
11
23
  };
12
24
  type IconMetadataSource = {
13
25
  commit: string;
@@ -15,10 +27,23 @@ type IconMetadataSource = {
15
27
  };
16
28
  export declare function parseIconMetadata(csv: string): IconMetadata[];
17
29
  export declare function validateIconMetadataSource(source: string): IconMetadataSource;
18
- export declare function buildIconCatalog(iconNames: readonly string[], metadata: readonly IconMetadata[]): IconCatalogRecord[];
19
- export declare function renderIconAliases(iconNames: readonly string[]): string;
30
+ export declare function buildIconCatalog(icons: readonly Pick<IconSource, "name" | "origin" | "file">[], metadata: readonly IconMetadata[]): IconCatalogRecord[];
31
+ export declare function parseIconSvg(source: string): IconNode[];
32
+ export declare function iconName(filename: string, style: IconSource["style"]): string;
33
+ /** Problems that keep an SVG from following the Web UI icon contract; empty when it does. */
34
+ export declare function validateIconSvg(source: string): string[];
35
+ export declare function readIconSet(directory: string, origin: IconOrigin): IconSource[];
36
+ export declare function mergeIconSets(iconoir: readonly IconSource[], custom: readonly IconSource[]): IconSource[];
37
+ export declare function iconoirDirectory(): string;
38
+ export declare function parseSharpenExclusions(source: string, icons: readonly IconSource[]): Set<string>;
39
+ export declare function sharpenIconSet(icons: readonly IconSource[], exclusions: ReadonlySet<string>): {
40
+ icons: IconSource[];
41
+ corners: number;
42
+ changed: number;
43
+ };
44
+ export declare function renderIconModule(icons: readonly IconSource[]): string;
20
45
  export declare function renderIconCatalogJson(records: readonly IconCatalogRecord[]): string;
21
46
  export declare function renderIconCatalog(records: readonly IconCatalogRecord[]): string;
22
- export declare function writeIconAliases(): void;
23
- export declare function writeIconCatalog(): void;
47
+ export declare function writeIconModule(icons: readonly IconSource[]): void;
48
+ export declare function writeIconCatalog(icons: readonly IconSource[]): void;
24
49
  export {};
@@ -0,0 +1,11 @@
1
+ import { IconNode } from '../icons/create-icon';
2
+ /** Replaces curves that round a corner or a line end between straight edges with the corner itself. */
3
+ export declare function sharpenPath(d: string, maxLeg: number): {
4
+ d: string;
5
+ corners: number;
6
+ };
7
+ /** Squares the rounded corners, line caps, and line joins of an icon. */
8
+ export declare function sharpenIcon(nodes: readonly IconNode[], maxLeg: number): {
9
+ nodes: IconNode[];
10
+ corners: number;
11
+ };
@@ -2,9 +2,9 @@ import { useRender } from '@base-ui/react/use-render';
2
2
  import { VariantProps } from 'class-variance-authority';
3
3
  import * as AvatarParts from '../avatar/avatar';
4
4
  declare const root: (props?: ({
5
- context?: "default" | "oncolor" | null | undefined;
5
+ context?: "oncolor" | "default" | null | undefined;
6
6
  } & import('class-variance-authority/types').ClassProp) | undefined) => string;
7
- type RootProps = React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof root>;
7
+ type RootProps = React.ComponentProps<"div"> & VariantProps<typeof root>;
8
8
  /**
9
9
  * AccountCard.Root — the signed-in person's card: `AccountCard.Link` and, in its top row, `AccountCard.Action`. The
10
10
  * action sits beside the link, not inside it, since a link cannot contain a button.
@@ -24,7 +24,7 @@ declare function Link({ render, ...props }: useRender.ComponentProps<"a">): impo
24
24
  declare namespace Link {
25
25
  var displayName: string;
26
26
  }
27
- type AvatarProps = React.HTMLAttributes<HTMLSpanElement> & {
27
+ type AvatarProps = React.ComponentProps<"span"> & {
28
28
  /** The person's photo. Until it loads, and when it fails, the initials or the person glyph show instead. */
29
29
  src?: string;
30
30
  /** The avatar size. */
@@ -42,7 +42,7 @@ declare namespace Avatar {
42
42
  * AccountCard.Title — the account's name, or the email's local part until a name exists. Set large; text wraps before
43
43
  * an `@` and after a dot.
44
44
  */
45
- declare function Title({ children, ...props }: React.HTMLAttributes<HTMLSpanElement>): import("react/jsx-runtime").JSX.Element;
45
+ declare function Title({ children, ...props }: React.ComponentProps<"span">): import("react/jsx-runtime").JSX.Element;
46
46
  declare namespace Title {
47
47
  var displayName: string;
48
48
  }
@@ -50,17 +50,17 @@ declare namespace Title {
50
50
  * AccountCard.Subtitle — the quiet line under the title: the email's domain, or the whole email once the title is a
51
51
  * name. Text wraps before an `@` and after a dot.
52
52
  */
53
- declare function Subtitle({ children, ...props }: React.HTMLAttributes<HTMLSpanElement>): import("react/jsx-runtime").JSX.Element;
53
+ declare function Subtitle({ children, ...props }: React.ComponentProps<"span">): import("react/jsx-runtime").JSX.Element;
54
54
  declare namespace Subtitle {
55
55
  var displayName: string;
56
56
  }
57
57
  /** AccountCard.Description — the organization, under a rule at the foot of the card. */
58
- declare function Description(props: React.HTMLAttributes<HTMLSpanElement>): import("react/jsx-runtime").JSX.Element;
58
+ declare function Description(props: React.ComponentProps<"span">): import("react/jsx-runtime").JSX.Element;
59
59
  declare namespace Description {
60
60
  var displayName: string;
61
61
  }
62
62
  /** AccountCard.Role — a mono uppercase label beside the description, such as the person's role. */
63
- declare function Role(props: React.HTMLAttributes<HTMLSpanElement>): import("react/jsx-runtime").JSX.Element;
63
+ declare function Role(props: React.ComponentProps<"span">): import("react/jsx-runtime").JSX.Element;
64
64
  declare namespace Role {
65
65
  var displayName: string;
66
66
  }
@@ -68,7 +68,7 @@ declare namespace Role {
68
68
  * AccountCard.Action — a slot in the card's top row, beside the link, such as a ghost icon `Button` that logs out. The
69
69
  * button carries its own `aria-label` and its own `context`.
70
70
  */
71
- declare function Action(props: React.HTMLAttributes<HTMLDivElement>): import("react/jsx-runtime").JSX.Element;
71
+ declare function Action(props: React.ComponentProps<"div">): import("react/jsx-runtime").JSX.Element;
72
72
  declare namespace Action {
73
73
  var displayName: string;
74
74
  }
@@ -76,7 +76,7 @@ declare namespace Action {
76
76
  * AccountCard.Group — a list of cards, such as accounts to switch between. Inside it a link with `aria-current="page"`
77
77
  * shows as current; a standalone card is always the signed-in account and shows no current state.
78
78
  */
79
- declare function Group(props: React.HTMLAttributes<HTMLDivElement>): import("react/jsx-runtime").JSX.Element;
79
+ declare function Group(props: React.ComponentProps<"div">): import("react/jsx-runtime").JSX.Element;
80
80
  declare namespace Group {
81
81
  var displayName: string;
82
82
  }