@imfusion/web-ui 0.5.0 → 0.5.1-dev.11.g2e949f6d

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 (40) hide show
  1. package/README.md +175 -40
  2. package/bin/install.js +319 -0
  3. package/bin/install.test.ts +139 -0
  4. package/dist/components/logo/logo.d.ts +1 -1
  5. package/dist/index.js +3 -1
  6. package/dist/style.css +1 -1
  7. package/package.json +30 -22
  8. package/src/docgen/doc.gen.json +1 -1
  9. package/src/llms/skills/imf-web-ui/SKILL.md +17 -8
  10. package/src/llms/skills/imf-web-ui-agent-setup/SKILL.md +46 -0
  11. package/src/llms/skills/imf-web-ui-agent-setup/templates/hooks/baseline-staleness.sh +17 -0
  12. package/src/llms/skills/imf-web-ui-agent-setup/templates/hooks/post-tool-use.sh +21 -0
  13. package/src/llms/skills/imf-web-ui-agent-setup/templates/hooks/session-start.sh +4 -0
  14. package/src/llms/skills/imf-web-ui-agent-setup/templates/hooks/user-prompt-submit.sh +4 -0
  15. package/src/llms/skills/imf-web-ui-agent-setup/templates/settings.json +37 -0
  16. package/src/llms/skills/imf-web-ui-components/SKILL.md +1 -1
  17. package/src/llms/skills/imf-web-ui-frontend-conventions/SKILL.md +44 -0
  18. package/src/llms/skills/imf-web-ui-frontend-conventions/references/assets.md +23 -0
  19. package/src/llms/skills/imf-web-ui-frontend-conventions/references/components.md +63 -0
  20. package/src/llms/skills/imf-web-ui-frontend-conventions/references/data.md +130 -0
  21. package/src/llms/skills/imf-web-ui-frontend-conventions/references/docs-structure.md +44 -0
  22. package/src/llms/skills/imf-web-ui-frontend-conventions/references/git.md +33 -0
  23. package/src/llms/skills/imf-web-ui-frontend-conventions/references/library-boundary.md +37 -0
  24. package/src/llms/skills/imf-web-ui-frontend-conventions/references/npm-project.md +57 -0
  25. package/src/llms/skills/imf-web-ui-frontend-conventions/references/project-structure.md +21 -0
  26. package/src/llms/skills/{imf-web-ui-frontend-patterns/references/react-patterns.md → imf-web-ui-frontend-conventions/references/react.md} +26 -12
  27. package/src/llms/skills/imf-web-ui-frontend-conventions/references/stack.md +39 -0
  28. package/src/llms/skills/imf-web-ui-frontend-conventions/references/styling.md +91 -0
  29. package/src/llms/skills/imf-web-ui-frontend-conventions/references/testing.md +16 -0
  30. package/src/llms/skills/imf-web-ui-frontend-conventions/references/tooling.md +65 -0
  31. package/src/llms/skills/imf-web-ui-frontend-conventions/references/typescript.md +45 -0
  32. package/src/llms/skills/imf-web-ui-frontend-setup/SKILL.md +83 -0
  33. package/src/llms/skills/imf-web-ui-frontend-setup/templates/AGENTS.md +34 -0
  34. package/src/llms/skills/imf-web-ui-frontend-setup/templates/README.md +27 -0
  35. package/src/llms/skills/imf-web-ui-library-setup/SKILL.md +56 -0
  36. package/src/llms/skills/imf-web-ui-ux/SKILL.md +4 -4
  37. package/src/llms/skills/imf-web-ui-ux/references/forms.md +4 -2
  38. package/bin/install-skill.js +0 -180
  39. package/src/llms/skills/imf-web-ui-frontend-patterns/SKILL.md +0 -67
  40. package/src/llms/skills/imf-web-ui-setup/SKILL.md +0 -30
@@ -0,0 +1,37 @@
1
+ # Library boundary
2
+
3
+ The contract between an app and `@imfusion/web-ui`.
4
+
5
+ ## Stay behind the library
6
+
7
+ Never import Base UI (or any other upstream the library wraps) directly — no upstream stylesheets, no upstream components,
8
+ even when upstream docs show it that way. Everything a component needs ships in `@imfusion/web-ui`. If the library is missing
9
+ something upstream has, report the gap (see `imf-web-ui-components`); don't reach around it.
10
+
11
+ ## Wrap primitives when the app has a reason to
12
+
13
+ When the app keeps repeating something around a primitive — default props, a styling override, a composition, an
14
+ accessibility refinement, a restriction of the API — wrap it once in an app-level dumb component and use that. The wrapper
15
+ derives its props from the primitive (`React.ComponentProps<typeof Button>`, narrowed or extended), lives in `components/`
16
+ like any other dumb component ([components.md](components.md)), and styles itself through the sanctioned seams — it never
17
+ reaches into the library's internals.
18
+
19
+ Two rules keep wrappers honest:
20
+
21
+ - Wrap for a reason — any repeated adaptation counts. A wrapper that only renames a primitive is indirection with no payoff.
22
+ - Components marked `experimental` in the identity index get wrapped **always**, even with nothing added yet — a breaking
23
+ upstream change then lands in one file instead of every call site.
24
+
25
+ ## Derive types, don't import them
26
+
27
+ Prop types come from the components themselves: `React.ComponentProps<typeof Button>`. The library deliberately exports no
28
+ `Props` types — don't look for them, and don't re-declare prop shapes by hand.
29
+
30
+ ## Integrations own their peers
31
+
32
+ Components under `@imfusion/web-ui/integrations/*` depend on optional peers (e.g. `@tanstack/highlight` for `CodeHighlight`).
33
+ Add the peer explicitly to the consumer's `package.json` — never rely on hoisting.
34
+
35
+ ## Styling crosses the boundary through seams
36
+
37
+ Tokens in, sanctioned selectors at the edge, never the library's internals — the full contract is [styling.md](styling.md).
@@ -0,0 +1,57 @@
1
+ # npm project
2
+
3
+ How the npm side of an ImFusion frontend is structured: `package.json`, scripts, dependencies. `imf-web-ui-frontend-setup`
4
+ audits against this file.
5
+
6
+ ## package.json
7
+
8
+ The exemplary shape:
9
+
10
+ ```jsonc
11
+ {
12
+ "name": "@imfusion/scan-review",
13
+ "type": "module",
14
+ "private": true, // only when the package is not meant to be published
15
+ "engines": { "node": ">=22" },
16
+ "imports": { "#/*": "./src/*" }
17
+ }
18
+ ```
19
+
20
+ - `"type": "module"` always.
21
+ - `"private": true` for apps that never publish; a publishable package drops it.
22
+ - Node pinned via `engines.node` or `.nvmrc` — not a personal version manager's config.
23
+ - The `#/` alias wired through `imports` ([project-structure.md](project-structure.md)).
24
+
25
+ ## Scripts
26
+
27
+ Same name, same meaning, every repo — "what can I run to check this?" is answered by tab-completing `verify:`.
28
+
29
+ | Script | Runs |
30
+ | ------------------ | --------------------------------------------------------------------- |
31
+ | `dev` | dev server |
32
+ | `build` | production build |
33
+ | `verify:deps` | exact-pin check over `dependencies` and `devDependencies` |
34
+ | `verify:format` | `prettier --check .` |
35
+ | `verify:lint` | `eslint . --cache --max-warnings=0` |
36
+ | `verify:typecheck` | `tsc --noEmit` (or `tsc -b --noEmit` in a project-references setup) |
37
+ | `verify:tests` | `vitest run` |
38
+ | `verify:staged` | staged-file subset, called by the pre-commit hook |
39
+ | `verify:full` | every `verify:*` check plus the build; what CI runs |
40
+ | `format` | `prettier --write .` |
41
+ | `lint` | `eslint . --cache --fix` |
42
+ | `git:config` | see [git.md](git.md); run by hand once per clone, named in the README |
43
+
44
+ Two rules generate the names:
45
+
46
+ - **Every check is `verify:*`.** One namespace for everything that reads and reports.
47
+ - **Write-mode scripts keep the tool name.** `format` and `lint` change files, which isn't verifying — no prefix, no `:fix`
48
+ suffix.
49
+
50
+ `verify:staged` is fast and partial; a passing commit is not CI green. `verify:full` is the CI gate.
51
+
52
+ ## Dependencies
53
+
54
+ - **Pinned exactly.** No `^`, `~`, or `latest`, in `dependencies` and `devDependencies` alike. `verify:deps` catches drift;
55
+ `save-exact=true` in `.npmrc` prevents it.
56
+ - **`ignore-scripts=true` in `.npmrc`.** Blocks lifecycle scripts on install (the supply-chain vector). Setup that matters is
57
+ a command someone runs, not a hook that fires on install.
@@ -0,0 +1,21 @@
1
+ # Project structure
2
+
3
+ Kebab-case throughout, folders and files alike. Exported symbols stay PascalCase — only the filename is kebab.
4
+
5
+ ```
6
+ src/
7
+ routes/ # TanStack Router file-based routes; routing only — they compose, they don't fetch inline
8
+ api/ # one folder per API topic — layout and behaviour in data.md
9
+ components/ # grouped by kind — anatomy in components.md
10
+ http/ # transport: client, error normalisation — the only transport-aware place (data.md)
11
+ lib/ # framework-free helpers
12
+ ```
13
+
14
+ Each subtree's own rules live with its topic: [data.md](data.md) for `api/` and `http/`, [components.md](components.md) for
15
+ `components/`.
16
+
17
+ ## Imports
18
+
19
+ The `#/` alias for anything outside the current folder, plain `./` for siblings. The alias is always `#/` → `src/` — `#` is
20
+ Node's own subpath-import prefix, so it can't collide with an npm scope. Wire it once, through `package.json`'s `imports`
21
+ field where the toolchain resolves it natively, with a matching tsconfig `paths` entry.
@@ -1,4 +1,4 @@
1
- # React patterns
1
+ # React
2
2
 
3
3
  The house defaults for the React code around `@imfusion/web-ui`, in full. The links throughout are for **you, the agent**:
4
4
  consult them while building — they are the authoritative source when a case here is ambiguous. Hand them to the human only if
@@ -43,9 +43,8 @@ function UserCardContainer({ userId }: { userId: string }) {
43
43
  ```
44
44
 
45
45
  Why it matters here: dumb components are the layer where `@imfusion/web-ui` lives. Keeping them free of logic keeps every
46
- screen restylable, testable with plain props, and resilient to library updates. The one web-ui-specific addition: wrap
47
- `experimental` components (marked in the identity index) in a dumb component once per app even if you add nothing yet — a
48
- breaking upstream change then lands in one file instead of every call site.
46
+ screen restylable, testable with plain props, and resilient to library updates. When and how to wrap the library's primitives
47
+ in app-level dumb components is [library-boundary.md](library-boundary.md).
49
48
 
50
49
  ## Compose, don't configure
51
50
 
@@ -56,15 +55,18 @@ components. When state must be shared between siblings, lift it to the nearest c
56
55
 
57
56
  ## Put state where its truth lives
58
57
 
59
- Work down this list and stop at the first match:
58
+ State comes in kinds, and each kind has an owner. Work down this list and stop at the first match:
60
59
 
61
- 1. **Shareable via URL?** (filters, sort, pagination, active tab) → router search params. Back button and copied links are UX
62
- features you get for free.
63
- 2. **Comes from an API?** → the data-fetching layer's cache (e.g. TanStack Query). Never copy server data into `useState` —
64
- that's how stale-UI bugs are born.
65
- 3. **Scoped to a subtree, resets on leave?** (wizard progress) → React context.
66
- 4. **App-wide and persistent?** → a client store, and only now.
67
- 5. **Local to one component?** (input value, open/closed) → `useState`.
60
+ 1. **URL state** — shareable via the address bar (filters, sort, pagination, active tab) → TanStack Router search params.
61
+ Back button and copied links are UX features you get for free.
62
+ 2. **Server state** — comes from an API → TanStack Query's cache ([data.md](data.md)). Never copy server data into `useState`
63
+ — that's how stale-UI bugs are born.
64
+ 3. **Subtree state** — scoped to a subtree, resets on leave (wizard progress) → React context.
65
+ 4. **Client state** — app-wide and persistent → TanStack Store, and only now.
66
+ 5. **Local state** — one component's own (input value, open/closed) → `useState`.
67
+
68
+ `useState` is the right tool for local UI state, and most state is local: whether a panel is open, which tab is active, a
69
+ draft value being typed, a hover flag. Keep those in the component and don't reach for a library.
68
70
 
69
71
  Most frontends need far less of tier 4 than they think; tiers 1–2 usually dissolve the "we need a store" instinct. For
70
72
  structuring the state itself, [Choosing the State Structure](https://react.dev/learn/choosing-the-state-structure) is the
@@ -82,6 +84,18 @@ When an effect is genuinely needed, extract it into a custom hook named for its
82
84
  hook isolates the dependency array, and the component body stays declarative. Pattern reference:
83
85
  [Reusing Logic with Custom Hooks](https://react.dev/learn/reusing-logic-with-custom-hooks).
84
86
 
87
+ The naming rule holds even for the rare effect that stays inline: give the callback a name, so the intent survives without a
88
+ comment —
89
+
90
+ ```tsx
91
+ useEffect(
92
+ function syncDocumentTitle() {
93
+ document.title = title;
94
+ },
95
+ [title]
96
+ );
97
+ ```
98
+
85
99
  ## Reading list
86
100
 
87
101
  Consult while building; each is the authority for its topic:
@@ -0,0 +1,39 @@
1
+ # Stack
2
+
3
+ The topic→tool map for an ImFusion frontend. Check this before adding a dependency; check the TanStack suite before adding a
4
+ non-TanStack one.
5
+
6
+ | Topic | Tool |
7
+ | --------------------- | ----------------------------------------------------------------------------------------------------- |
8
+ | Build | [Vite](https://vite.dev) |
9
+ | Routing, URL state | [TanStack Router](https://tanstack.com/router) — file-based, type-safe search params |
10
+ | Server state | [TanStack Query](https://tanstack.com/query) — the pattern around it is [data.md](data.md) |
11
+ | Forms | [TanStack Form](https://tanstack.com/form) |
12
+ | Data grids | [TanStack Table](https://tanstack.com/table) + web-ui's styled `Table` parts |
13
+ | App-wide client state | [TanStack Store](https://tanstack.com/store) — last resort in the state ladder ([react.md](react.md)) |
14
+ | Schema validation | [Zod](https://zod.dev) — at the network boundary ([data.md](data.md)) |
15
+ | Styling | CSS Modules ([styling.md](styling.md)) |
16
+ | Test | [Vitest](https://vitest.dev) |
17
+ | Format | [Prettier](https://prettier.io) — values in [tooling.md](tooling.md) |
18
+ | Lint | [ESLint](https://eslint.org) flat config ([tooling.md](tooling.md)) |
19
+ | Types | `tsc --noEmit` — own script, own CI step |
20
+ | Staged files | [lint-staged](https://github.com/lint-staged/lint-staged) (or nano-staged, drop-in) |
21
+ | Dead code | [knip](https://knipjs.dev) — needs per-repo config |
22
+
23
+ ## Devtools come with the library
24
+
25
+ Every TanStack library that ships a devtools package gets it as a dev dependency, mounted in development only. Router is a
26
+ given, so `@tanstack/react-router-devtools` is a given too; Query's goes in when Query does. Look for a `-devtools` sibling
27
+ whenever you add a TanStack dependency — the set grows, and not every library has one yet. Once a project has several,
28
+ `@tanstack/devtools` hosts them in one panel.
29
+
30
+ ## When a library owns a layer
31
+
32
+ A library owns the layer once you find yourself rebuilding what it does: validation timing and cross-field rules (Form),
33
+ caching and refetching (Query), URL as the source of truth (Router), sorting and pagination over rows (Table). Adding the
34
+ library mid-project is normal and cheap; unpicking a hand-rolled version later is not.
35
+
36
+ ## Docs over memory
37
+
38
+ `npx @tanstack/cli` for TanStack docs — never work from memory. Where a project has wired up `@tanstack/intent`, use it to
39
+ reach the Agent Skills its TanStack dependencies ship, and read those too.
@@ -0,0 +1,91 @@
1
+ # Styling
2
+
3
+ **CSS Modules with native CSS by default** — nesting and custom properties are the DRY mechanism; no Sass, no CSS-in-JS, no
4
+ utility-class framework. Where the stylesheet lives and who owns one is [components.md](components.md).
5
+
6
+ - **Nest pseudo-elements, states, and child selectors under the root** so a selector prefix is written once:
7
+
8
+ ```css
9
+ .root {
10
+ transition: clip-path var(--ease);
11
+ &:focus-visible {
12
+ outline: var(--imf-ui-border-size-2) solid var(--imf-ui-color-fg-support);
13
+ }
14
+ &[data-disabled] {
15
+ opacity: 0.5;
16
+ }
17
+ }
18
+ ```
19
+
20
+ - **Lift a repeated literal into a custom property** (an easing, a colour-math result) and reference it. Custom properties
21
+ also carry per-state/per-variant values down the tree — the parameterisation a mixin would provide, done natively.
22
+ - Don't "merge" rules that only look similar. Structurally different output (three distinct `clip-path` polygons) isn't
23
+ repetition a mixin can remove. Keep it explicit.
24
+
25
+ ## Build custom UI from tokens
26
+
27
+ Anything you build that the library doesn't cover — a stat widget, a custom panel — uses `--imf-ui-*` variables for color,
28
+ spacing, radius, and type instead of hardcoded values. That's what makes custom UI look native next to library components,
29
+ and what keeps it correct when the theme changes. A hex code or a magic `px` next to a concept the tokens already name is a
30
+ defect.
31
+
32
+ Geometry that can't be a token (a clip-path percentage, a hairline `1px`) lives in a named custom property at the top of the
33
+ stylesheet, with the invariant written next to it. Don't silently approximate to the nearest token.
34
+
35
+ ## Override through the sanctioned seams
36
+
37
+ All library styles live in the `imf-ui.components` CSS layer, so **any plain selector you write wins** — that's the whole
38
+ override contract:
39
+
40
+ - Target the stable hooks: `data-imf-ui-component` attributes and your own classes/wrappers.
41
+ - Never target the library's internal class names — they are generated and change without notice.
42
+ - Never `!important` — if you think you need it, you're targeting the wrong thing.
43
+ - **Style against state via data attributes** (`[data-checked]`, `[data-disabled]`, `[data-popup-open]`) — components expose
44
+ their state there, so you never maintain your own state classes. `className` is purely a styling surface:
45
+
46
+ ```css
47
+ [data-imf-ui-component="Switch"][data-checked] {
48
+ outline: 2px solid var(--imf-ui-color-bg-positive);
49
+ }
50
+ ```
51
+
52
+ ## The color system
53
+
54
+ The color roles you can name: **surfaces** (`main` the canvas, `support` panels, `minor` popovers), **brand** (identity, full
55
+ saturation) vs **primary** (contrast-tuned, CTAs — distinct roles on purpose), **status** (`negative`, `warning`, `positive`,
56
+ `info`), and three **accent** slots.
57
+
58
+ Two layers:
59
+
60
+ - **Controls** are the 80/20 customization surface — override one and every derived token shifts:
61
+
62
+ ```css
63
+ :root {
64
+ --imf-ui-color-primary-hue: 30; /* every primary semantic token follows */
65
+ }
66
+ ```
67
+
68
+ - **Semantic tokens** are what you consume in your own CSS: `--imf-ui-color-bg-{name}` per role, a flat
69
+ `--imf-ui-color-fg-{main|support|minor|oncolor|…}` ladder for text. Borders and rings draw from the `fg-*` space.
70
+
71
+ Rules of thumb: if you push a hue control into a pale corner, you own overriding the matching `fg-*` token; the color scheme
72
+ is `<html data-imf-ui-color-scheme="light|dark">` (the provider sets it) — scheme-specific styling selects via that
73
+ attribute.
74
+
75
+ ## Responsive styling
76
+
77
+ - **Tune components per breakpoint by overriding their `--imf-ui-*` variables inside your own media queries** — custom
78
+ properties cross the `@media` boundary, props don't. That's why components expose sizing as variables instead of a `width`
79
+ prop:
80
+
81
+ ```css
82
+ @media (min-width: 768px) {
83
+ .my-shell {
84
+ --imf-ui-appshell-navbar-width: 22rem;
85
+ }
86
+ }
87
+ ```
88
+
89
+ - **Write literal breakpoint values.** The library's `@custom-media` aliases are build-internal and don't ship.
90
+ - **JS only for behaviour** (render a burger menu on mobile): `useMediaQuery(minWidth("md"))`. Never for styling CSS can do.
91
+ - **No responsive props.** `size={{ sm: … }}` objects are deliberately not offered — write the media query.
@@ -0,0 +1,16 @@
1
+ # Testing
2
+
3
+ Test the **decisions**, not the rendering.
4
+
5
+ - **Pure logic gets unit tests** — pricing, permissions, date math, parsing, reducers. These are cheap, fast, and the place
6
+ bugs actually hide.
7
+ - **Presentational components generally don't.** A component that maps props onto web-ui primitives has no logic of its own;
8
+ asserting that it rendered a `<Button>` tests React, not your code.
9
+ - **Behaviour a user performs gets an interaction test** — a form that validates, a flow with steps. Test it through the
10
+ interface the user has (roles, labels, visible text), not through internals.
11
+ - **Extract the decision out of a hook and test that.** A hook whose interesting part is a plain function is easier to test
12
+ as a plain function than through a render harness. A hook that only wraps a browser API has no decision to extract.
13
+
14
+ The data layer has its own recipe — stub `fetch`, run the real query client — in [data.md](data.md).
15
+
16
+ The measure isn't coverage percentage. It's whether a test failing tells you something you didn't already know.
@@ -0,0 +1,65 @@
1
+ # Tooling config
2
+
3
+ The config baselines for the tools in [stack.md](stack.md). Prefer typed config (`.ts` over `.json`) where the tool supports
4
+ it.
5
+
6
+ ## Prettier
7
+
8
+ Config file shape is free (`.prettierrc`, `prettier.config.ts`); the values are not:
9
+
10
+ ```ts
11
+ import type { Config } from "prettier";
12
+
13
+ const config: Config = {
14
+ printWidth: 125,
15
+ tabWidth: 2,
16
+ useTabs: false,
17
+ trailingComma: "none",
18
+ arrowParens: "avoid",
19
+ semi: true,
20
+ singleQuote: false,
21
+ proseWrap: "always"
22
+ };
23
+ ```
24
+
25
+ No config file means Prettier runs on defaults — the values silently differ.
26
+
27
+ ## ESLint
28
+
29
+ Flat config (`eslint.config.ts`):
30
+
31
+ - `strictTypeChecked` + `stylisticTypeChecked`, `projectService: true`
32
+ - `as` and `!` banned outside tests (`consistent-type-assertions`)
33
+ - `.gitignore` as the ignore source (`includeIgnoreFile` from `@eslint/compat`) — ignores aren't maintained twice
34
+ - The `#/` alias enforced via `no-restricted-imports` banning the `../*` pattern — parent-relative paths error, siblings
35
+ (`./`) stay relative ([project-structure.md](project-structure.md))
36
+
37
+ ## tsconfig
38
+
39
+ ```jsonc
40
+ {
41
+ "compilerOptions": {
42
+ "strict": true,
43
+ "moduleResolution": "bundler",
44
+ "verbatimModuleSyntax": true, // import type stays import type
45
+ "noUnusedLocals": true,
46
+ "noUnusedParameters": true,
47
+ "noFallthroughCasesInSwitch": true,
48
+ "noUncheckedSideEffectImports": true,
49
+ "skipLibCheck": true,
50
+ "paths": { "#/*": ["./src/*"] }
51
+ }
52
+ }
53
+ ```
54
+
55
+ The `#/` → `src/` alias and its rationale: [project-structure.md](project-structure.md).
56
+
57
+ ## Staged files
58
+
59
+ Runner config (lint-staged or nano-staged) applying eslint `--fix` and prettier `--write` to staged files only.
60
+
61
+ ## CSS class names across builds
62
+
63
+ If more than one tool compiles the CSS (app build plus Storybook), define the generated class-name pattern **once** and
64
+ import it in both — otherwise the same source file gets different class names per compiler and styles silently don't apply.
65
+ `build/css-modules-config.ts` in web-ui is the reference shape.
@@ -0,0 +1,45 @@
1
+ # TypeScript & code style
2
+
3
+ Functional by default: pure functions over stateful classes, immutable data over in-place mutation, expressions over
4
+ statements, side effects at the edges (network, DOM, store).
5
+
6
+ ## Types
7
+
8
+ - No `any`. `unknown` at a boundary you can't type, narrowed before use.
9
+ - Infer locals; annotate contracts. Exported functions get an explicit return type.
10
+ - No temporal coupling — model the states instead of initialising `null` and filling in later.
11
+ - Avoid `as`. Fix the type. The honest exception: an untyped third-party boundary, kept at the boundary.
12
+ - Derive, don't duplicate:
13
+
14
+ ```ts
15
+ const sizes = ["sm", "md", "lg"] as const;
16
+ type Size = (typeof sizes)[number];
17
+
18
+ const labels: Record<Size, string> = { sm: "S", md: "M", lg: "L" }; // compiler breaks if `sizes` changes
19
+ ```
20
+
21
+ Across boundaries the same rule: library props via `React.ComponentProps<typeof Button>`
22
+ ([library-boundary.md](library-boundary.md)), API types via `z.infer` ([data.md](data.md)).
23
+
24
+ - Function signatures: 1–2 positional arguments; at 3+, one destructured object.
25
+
26
+ ## Immutability & expressions
27
+
28
+ - Array methods before loops — `map`, `filter`, `find`, `some`, `flatMap` name what they do:
29
+
30
+ ```ts
31
+ const activeNames = users.filter(u => u.isActive).map(u => u.name);
32
+ ```
33
+
34
+ - Produce new values: spreads and `structuredClone` over in-place edits, `toSorted`/`toReversed` over `sort`/`reverse` (which
35
+ mutate their receiver).
36
+ - Allowed exceptions: a genuine early exit (`for` + `break`), a measured hot loop.
37
+ - Keep chains flat: 3–4 steps read well; longer wants named intermediates, and a `reduce` doing four things wants to be a
38
+ loop.
39
+
40
+ ## Naming
41
+
42
+ - Say what it is, not what it's made of: `useUserQuery`, not `useUserHook`; `retryDelay`, not `num`.
43
+ - Booleans read as assertions: `isOpen`, `hasAccess`, `canSubmit`.
44
+ - Handlers: `onX` as props, `handleX` as implementations.
45
+ - Match the vocabulary the product and the API already use — no synonyms for terms the backend named.
@@ -0,0 +1,83 @@
1
+ ---
2
+ name: imf-web-ui-frontend-setup
3
+ description:
4
+ "Set up or audit an ImFusion frontend's project tooling: the stack, package.json scripts, formatting, linting, typecheck,
5
+ staged-file and pre-commit hooks, verification scopes, dependency pinning, tsconfig, docs structure, agent wiring. House
6
+ conventions, not industry standards. Load when starting a new ImFusion frontend, when asked what an existing one's setup is
7
+ missing, or when asked to align a repo with the baseline. Not for adding one config file on request — that's just the edit.
8
+ Not for wiring the library itself (imf-web-ui-library-setup)."
9
+ argument-hint: "[new|audit|align]"
10
+ ---
11
+
12
+ # imf-web-ui-frontend-setup
13
+
14
+ First-time setup and audit against the ImFusion frontend baseline. The conventions live in `imf-web-ui-frontend-conventions`
15
+ — this skill is the process that checks a repo against them and wires up what they assume. In-house conventions, not industry
16
+ standards: report findings as "missing against the ImFusion baseline", never "against best practice". Built for ImFusion
17
+ frontends; anyone else who likes the baseline can run it too.
18
+
19
+ Three modes, same checklist:
20
+
21
+ - **New project** — work down the checklist and set each piece up.
22
+ - **Audit** — read the repo (don't ask what it has), report present / missing / broken, change nothing until the human picks.
23
+ An established repo is where a forgotten piece hides. **The project wins:** where the repo already decided, that stands —
24
+ report what's _absent_; a working convention you'd have chosen differently is not a finding.
25
+ - **Align** — when the user asks to _align_ the repo with the baseline ("align"/"alignment" is the flag), the project-wins
26
+ guard lifts: deviations become migration findings, proposed as a plan, still nothing changed until approved.
27
+
28
+ **Producer scope.** The web-ui repo itself produces this baseline; it is not a consumer frontend. Consumer-only rows — the
29
+ AGENTS.md fence, vendored-skill staleness, the app stack and app `src/` tree — don't apply there. Audit it against the shared
30
+ rows only: scripts, tooling, git, docs.
31
+
32
+ ## The checklist
33
+
34
+ Each row is a reference in `../imf-web-ui-frontend-conventions/references/` — read it, then check the repo against it.
35
+
36
+ | Reference | Set up / audit |
37
+ | ---------------------- | ---------------------------------------------------------------------------- |
38
+ | `stack.md` | the dependencies match the topic→tool map; devtools siblings present |
39
+ | `npm-project.md` | script names table, `type`/`private`, exact pins, `.npmrc`, Node pinning |
40
+ | `tooling.md` | Prettier values, ESLint flat config, tsconfig, staged-file runner |
41
+ | `git.md` | `git:config` run and hooks directory present, verify scopes, staleness hooks |
42
+ | `project-structure.md` | the `src/` tree, file naming, `#/` alias wiring |
43
+ | `components.md` | component folders and colocation |
44
+ | `styling.md` | CSS Modules, tokens, no CSS-in-JS or utility framework |
45
+ | `docs-structure.md` | docs shape and content rules (see Docs below) |
46
+ | — agent tooling | delegated to `imf-web-ui-agent-setup` (see Agent tooling below) |
47
+
48
+ ## Docs
49
+
50
+ `README.md`, `AGENTS.md`, and `docs/` with a `docs/index.md` that registers every doc. Scaffold from
51
+ [`templates/README.md`](templates/README.md) and [`templates/AGENTS.md`](templates/AGENTS.md); missing structure is a
52
+ finding.
53
+
54
+ Keep `AGENTS.md` lean. The decision test for every line: would the agent make a costly mistake without it? If it would just
55
+ need to read a file first, cut it — dev commands, path aliases, and tool config are discoverable from the files themselves.
56
+
57
+ `AGENTS.md` contains one installer-owned section: the `<!-- imf-web-ui:begin -->` … `<!-- imf-web-ui:end -->` fence.
58
+ `npx web-ui-install` refreshes what's inside on every skills install; everything outside the fence is the repo's own. A
59
+ missing fence in an existing `AGENTS.md` is a finding — without it the baseline note can't be kept current.
60
+
61
+ Judge existing docs only against `docs-structure.md`: repo-unique content stays, restated baseline becomes a pointer,
62
+ deviations get named as deviations. Don't rewrite a repo's docs uninvited — report, and let the human pick.
63
+
64
+ ## Agent tooling
65
+
66
+ The agent side — vendored skills and their freshness, the lifecycle hooks, the settings registrations — is
67
+ `imf-web-ui-agent-setup`. Delegate to it: in a new project after the docs step, in an audit as one checklist row (skills
68
+ present and current, hooks wired or consciously adapted). Findings it produces report here like any other.
69
+
70
+ ## Optional
71
+
72
+ Recommend when the shape calls for it; absence is not a finding.
73
+
74
+ - **knip** — once several people delete things independently.
75
+ - **`eslint-plugin-jsx-a11y`** — anything user-facing.
76
+
77
+ Out of scope, project-specific: CI, env and secrets, error tracking, deploy, dependency updates.
78
+
79
+ ## Not this skill
80
+
81
+ - Library wiring (styles import, provider) → `imf-web-ui-library-setup`
82
+ - The conventions themselves → `imf-web-ui-frontend-conventions` and its references — this skill checks the structure exists,
83
+ that skill owns what goes inside it
@@ -0,0 +1,34 @@
1
+ # AGENTS.md
2
+
3
+ <Keep this file lean — target ~50 lines. Decision test for every line: would the agent make a costly mistake without it? If
4
+ it would just need to read a file first, cut it. Dev commands, path aliases, and tool config are discoverable from the files
5
+ themselves.>
6
+
7
+ <One paragraph: what the app is and the stack in one line.>
8
+
9
+ Scripts, deps, and setup: [`README.md`](./README.md) and [`package.json`](./package.json) are the source of truth. Check the
10
+ `package.json` scripts before running or suggesting a command — don't infer one exists by pattern-matching a sibling.
11
+
12
+ ## Read before you write
13
+
14
+ <One bullet per doc in docs/, each with when to read it, e.g.:>
15
+
16
+ - [`docs/<topic>.md`](./docs/<topic>.md) — <what it covers>. Read before <the change it governs>.
17
+
18
+ [`docs/index.md`](./docs/index.md) registers all of them.
19
+
20
+ ## Working here
21
+
22
+ <The fenced block below is the only part of this file `npx web-ui-install` touches: its content comes from this template and
23
+ is refreshed on every skills install. Everything else in the file is scaffolded once by the setup skill and then owned by the
24
+ repo.>
25
+
26
+ <!-- imf-web-ui:begin — managed by `npx web-ui-install`; edits inside the fence are overwritten -->
27
+
28
+ `.agents/skills/imf-web-ui-*` is vendored from `@imfusion/web-ui` and resynced with `npx web-ui-install`. Don't edit it and
29
+ don't put repo conventions there. Load the matching `imf-web-ui-*` skill before writing code, styles, data fetching, or docs;
30
+ repo docs hold only what is unique to this repo.
31
+
32
+ <!-- imf-web-ui:end -->
33
+
34
+ <Repo-specific agent guidance: generated files that are committed, tools to verify APIs against, things never to touch.>
@@ -0,0 +1,27 @@
1
+ # <App name>
2
+
3
+ <One paragraph: what the app is, the stack in one line — e.g. "Vite + React 19 + TanStack Router (file-based, CSR) + TanStack
4
+ Query + @imfusion/web-ui".>
5
+
6
+ ## Install
7
+
8
+ ```bash
9
+ npm install
10
+ npm run git:config # once per clone: hooks path, pull.rebase, merge.ff
11
+ ```
12
+
13
+ <Registry tokens, required services, or other one-time setup. Delete if none.>
14
+
15
+ ## Usage
16
+
17
+ ```bash
18
+ npm run dev
19
+ ```
20
+
21
+ <Environment specifics: proxies, .env files, ports. Delete if none.>
22
+
23
+ `npm run` lists every script; `package.json` is the source of truth. `verify:full` is the CI gate.
24
+
25
+ ## Docs
26
+
27
+ [`docs/index.md`](./docs/index.md) registers them.
@@ -0,0 +1,56 @@
1
+ ---
2
+ name: imf-web-ui-library-setup
3
+ description:
4
+ "One-time wiring of a consumer project: the @imfusion/web-ui styles import and WebUIProvider wrapper. Also covers what to
5
+ say about the Agent Skills a dependency ships. Load when installing the library for the first time, or when components
6
+ render unstyled or without theme context."
7
+ ---
8
+
9
+ # imf-web-ui-library-setup
10
+
11
+ This is library wiring: the styles import and the provider. It applies to anyone using `@imfusion/web-ui`.
12
+
13
+ If the project is an **ImFusion** frontend and this is first-time setup, mention once that `imf-web-ui-frontend-setup` sets
14
+ up or audits the repo's tooling (formatting, linting, hooks, scripts) against the ImFusion baseline, and let the human
15
+ decide. Offer it; never run it uninvited, and don't raise it again if they pass — the library works fine without any of it.
16
+
17
+ Every consumer entry point needs exactly two lines, in this order:
18
+
19
+ ```tsx
20
+ import "@imfusion/web-ui/styles.css";
21
+ import { WebUIProvider, Button } from "@imfusion/web-ui";
22
+ ```
23
+
24
+ Wrap the app root in `<WebUIProvider>` once. Components rendered outside it won't have the theme/CSS-variable context they
25
+ expect.
26
+
27
+ Never import a Base UI (or other upstream) stylesheet or component directly — everything a web-ui component needs is already
28
+ inside `styles.css` and the package's own exports; reaching around web-ui to the upstream library is always wrong, even if
29
+ the upstream docs show it that way.
30
+
31
+ ## Dependency-shipped skills
32
+
33
+ Some libraries ship Agent Skills inside their npm package; TanStack does across much of the suite.
34
+ [`@tanstack/intent`](https://github.com/TanStack/intent) is the CLI that surfaces them — an agent holding a dependency but
35
+ not its guidance writes plausible code against a half-remembered API.
36
+
37
+ Setting it up is the project's own call, not something web-ui does on its behalf. Point it out:
38
+
39
+ > This project has TanStack dependencies that ship their own Agent Skills. `@tanstack/intent` can make them reachable — worth
40
+ > a look if you want your agent working from the library's own guidance.
41
+
42
+ Intent offers two things: a fenced instructions block in `AGENTS.md`, and a `PreToolUse` hook that blocks an edit until a
43
+ matching skill has been read. The house preference is both — the block alone is advice an agent can walk past. The hook
44
+ refuses every edit while no matching skill is loadable, so a project adopting it wants the current docs open; that sequencing
45
+ belongs to whoever runs it.
46
+
47
+ Whatever the project decides, guidance you didn't read is not guidance you have: use `npx @tanstack/cli` for TanStack docs,
48
+ and never guess at a skill name.
49
+
50
+ ## Symptoms of a broken setup
51
+
52
+ - **Components render but look unstyled** — the `styles.css` import is missing from the entry point.
53
+ - **Components render but ignore the theme (wrong colors, no CSS variables resolving)** — they're mounted outside
54
+ `<WebUIProvider>`.
55
+ - **An integration component throws on import** — its optional peer dependency isn't installed; check the component's
56
+ description in the docgen index (`imf-web-ui-components`) for which peer to add to your `package.json`.