@imfusion/web-ui 0.5.1-dev.28.g1156ad00 → 0.5.1-dev.3.g8e0a047a
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.
- package/README.md +55 -148
- package/bin/install-skill.js +180 -0
- package/dist/index.d.ts +0 -2
- package/dist/index.js +4317 -4956
- package/dist/integrations/image-display-options.js +1 -1
- package/dist/style.css +1 -1
- package/dist/{tabs-CVp_SgBl.js → tabs-DqBFSqq6.js} +1 -1
- package/package.json +24 -35
- package/src/docgen/doc.gen.json +0 -327
- package/src/llms/llms.gen.txt +0 -12
- package/src/llms/skills/imf-web-ui/SKILL.md +11 -15
- package/src/llms/skills/imf-web-ui-components/SKILL.md +1 -2
- package/src/llms/skills/imf-web-ui-frontend-patterns/SKILL.md +93 -0
- package/src/llms/skills/imf-web-ui-frontend-patterns/references/code-conventions.md +133 -0
- package/src/llms/skills/{imf-web-ui-frontend-conventions/references/react.md → imf-web-ui-frontend-patterns/references/react-patterns.md} +12 -28
- package/src/llms/skills/imf-web-ui-imfusion-frontend-setup/SKILL.md +201 -0
- package/src/llms/skills/imf-web-ui-setup/SKILL.md +57 -0
- package/src/llms/skills/imf-web-ui-ux/SKILL.md +4 -4
- package/src/llms/skills/imf-web-ui-ux/references/forms.md +1 -1
- package/bin/install.js +0 -343
- package/bin/install.test.ts +0 -197
- package/dist/build/vite-css-module-names/index.d.ts +0 -20
- package/dist/build/vite-css-module-names.js +0 -17
- package/dist/components/field/field.d.ts +0 -104
- package/dist/components/field/field.meta.d.ts +0 -2
- package/dist/components/field/index.d.ts +0 -2
- package/dist/components/fieldset/fieldset.d.ts +0 -29
- package/dist/components/fieldset/fieldset.meta.d.ts +0 -2
- package/dist/components/fieldset/index.d.ts +0 -2
- package/src/llms/skills/imf-web-ui-agent-setup/SKILL.md +0 -82
- package/src/llms/skills/imf-web-ui-agent-setup/templates/hooks/baseline-staleness.sh +0 -17
- package/src/llms/skills/imf-web-ui-agent-setup/templates/hooks/post-tool-use.sh +0 -33
- package/src/llms/skills/imf-web-ui-agent-setup/templates/hooks/session-start.sh +0 -4
- package/src/llms/skills/imf-web-ui-agent-setup/templates/hooks/user-prompt-submit.sh +0 -4
- package/src/llms/skills/imf-web-ui-agent-setup/templates/settings.json +0 -37
- package/src/llms/skills/imf-web-ui-frontend-conventions/SKILL.md +0 -46
- package/src/llms/skills/imf-web-ui-frontend-conventions/references/assets.md +0 -23
- package/src/llms/skills/imf-web-ui-frontend-conventions/references/class-names.md +0 -42
- package/src/llms/skills/imf-web-ui-frontend-conventions/references/components.md +0 -63
- package/src/llms/skills/imf-web-ui-frontend-conventions/references/data.md +0 -201
- package/src/llms/skills/imf-web-ui-frontend-conventions/references/docs-structure.md +0 -44
- package/src/llms/skills/imf-web-ui-frontend-conventions/references/git.md +0 -33
- package/src/llms/skills/imf-web-ui-frontend-conventions/references/library-boundary.md +0 -37
- package/src/llms/skills/imf-web-ui-frontend-conventions/references/npm-project.md +0 -57
- package/src/llms/skills/imf-web-ui-frontend-conventions/references/project-structure.md +0 -21
- package/src/llms/skills/imf-web-ui-frontend-conventions/references/stack.md +0 -39
- package/src/llms/skills/imf-web-ui-frontend-conventions/references/styling.md +0 -91
- package/src/llms/skills/imf-web-ui-frontend-conventions/references/testing.md +0 -18
- package/src/llms/skills/imf-web-ui-frontend-conventions/references/tooling.md +0 -76
- package/src/llms/skills/imf-web-ui-frontend-conventions/references/typescript.md +0 -46
- package/src/llms/skills/imf-web-ui-frontend-conventions/references/validation.md +0 -88
- package/src/llms/skills/imf-web-ui-frontend-setup/SKILL.md +0 -66
- package/src/llms/skills/imf-web-ui-frontend-setup/templates/AGENTS.md +0 -34
- package/src/llms/skills/imf-web-ui-frontend-setup/templates/FRONTEND_SETUP_REPORT.md +0 -45
- package/src/llms/skills/imf-web-ui-frontend-setup/templates/README.md +0 -27
- package/src/llms/skills/imf-web-ui-library-setup/SKILL.md +0 -36
- package/src/llms/skills/imf-web-ui-update/SKILL.md +0 -166
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
# Git
|
|
2
|
-
|
|
3
|
-
## git:config
|
|
4
|
-
|
|
5
|
-
One script holds the repo's git configuration, run by hand once per clone (the README names it):
|
|
6
|
-
|
|
7
|
-
```
|
|
8
|
-
git config core.hooksPath .githooks && git config pull.rebase true && git config merge.ff only
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
Hooks live in a tracked directory; history strategy doesn't depend on personal git config. Two silent failure modes to check
|
|
12
|
-
for: `git:config` never run (hooks exist only on the machine that configured by hand), and `core.hooksPath` pointing at a
|
|
13
|
-
directory that doesn't exist. Config **and** directory.
|
|
14
|
-
|
|
15
|
-
## Verify scopes
|
|
16
|
-
|
|
17
|
-
Two blocking, one advisory:
|
|
18
|
-
|
|
19
|
-
- **staged** — `verify:staged`, called by the pre-commit hook: lint, format, restage. Fast; a passing commit is not CI green.
|
|
20
|
-
- **full** — `verify:full`: the build plus every `verify:*` check. What CI runs.
|
|
21
|
-
- **files** — post-edit agent hook. Advisory, never exits non-zero, so a mid-flight refactor can't trap the agent.
|
|
22
|
-
|
|
23
|
-
One script owns each scope's step list; npm scripts and hooks only launch them. Name by depth, not by occasion — a
|
|
24
|
-
`preflight` needs explaining and invites a near-identical sibling, and two of those drift into "passes locally, fails in CI".
|
|
25
|
-
|
|
26
|
-
## Staleness at commit time
|
|
27
|
-
|
|
28
|
-
Pre-commit also runs the advisory staleness checks — warn, never block:
|
|
29
|
-
|
|
30
|
-
- **Vendored baseline** — `.agents/hooks/imf-web-ui/baseline-staleness.sh` compares the installed `imf-web-ui-*` skill
|
|
31
|
-
markers against the installed package version; the fix it names is `npx web-ui-install`.
|
|
32
|
-
- **Docs** — a staged change that invalidates a doc updates that doc in the same commit
|
|
33
|
-
([docs-structure.md](docs-structure.md)). Where the repo has an agent commit workflow, a staged docs audit belongs in it.
|
|
@@ -1,37 +0,0 @@
|
|
|
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).
|
|
@@ -1,57 +0,0 @@
|
|
|
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.
|
|
@@ -1,21 +0,0 @@
|
|
|
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,39 +0,0 @@
|
|
|
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.
|
|
@@ -1,91 +0,0 @@
|
|
|
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.
|
|
@@ -1,18 +0,0 @@
|
|
|
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). A schema whose
|
|
15
|
-
constraints or transforms encode product behavior gets focused accepted, rejected, and transformed cases; see
|
|
16
|
-
[validation.md](validation.md).
|
|
17
|
-
|
|
18
|
-
The measure isn't coverage percentage. It's whether a test failing tells you something you didn't already know.
|
|
@@ -1,76 +0,0 @@
|
|
|
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
|
|
62
|
-
|
|
63
|
-
Generated CSS Module class names are readable in the DOM, `{prefix}-{file}-{local}`, never the default hash. A legible DOM is
|
|
64
|
-
what makes devtools and browser automation usable. The library ships the plugin that produces them; pass your own short,
|
|
65
|
-
app-scoped prefix:
|
|
66
|
-
|
|
67
|
-
```ts
|
|
68
|
-
import { readableCssModuleNames } from "@imfusion/web-ui/build/vite-css-module-names";
|
|
69
|
-
|
|
70
|
-
export default defineConfig({
|
|
71
|
-
plugins: [react(), readableCssModuleNames({ prefix: "acme" })]
|
|
72
|
-
});
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
One pattern for dev, Storybook, and production. Register the plugin in **every** tool that compiles the CSS — a compiler left
|
|
76
|
-
out generates different names for the same source file, and its styles silently don't apply.
|
|
@@ -1,46 +0,0 @@
|
|
|
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)), untrusted data via a runtime schema and `z.infer`
|
|
23
|
-
([validation.md](validation.md)).
|
|
24
|
-
|
|
25
|
-
- Function signatures: 1–2 positional arguments; at 3+, one destructured object.
|
|
26
|
-
|
|
27
|
-
## Immutability & expressions
|
|
28
|
-
|
|
29
|
-
- Array methods before loops — `map`, `filter`, `find`, `some`, `flatMap` name what they do:
|
|
30
|
-
|
|
31
|
-
```ts
|
|
32
|
-
const activeNames = users.filter(u => u.isActive).map(u => u.name);
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
- Produce new values: spreads and `structuredClone` over in-place edits, `toSorted`/`toReversed` over `sort`/`reverse` (which
|
|
36
|
-
mutate their receiver).
|
|
37
|
-
- Allowed exceptions: a genuine early exit (`for` + `break`), a measured hot loop.
|
|
38
|
-
- Keep chains flat: 3–4 steps read well; longer wants named intermediates, and a `reduce` doing four things wants to be a
|
|
39
|
-
loop.
|
|
40
|
-
|
|
41
|
-
## Naming
|
|
42
|
-
|
|
43
|
-
- Say what it is, not what it's made of: `useUserQuery`, not `useUserHook`; `retryDelay`, not `num`.
|
|
44
|
-
- Booleans read as assertions: `isOpen`, `hasAccess`, `canSubmit`.
|
|
45
|
-
- Handlers: `onX` as props, `handleX` as implementations.
|
|
46
|
-
- Match the vocabulary the product and the API already use — no synonyms for terms the backend named.
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
# Validation
|
|
2
|
-
|
|
3
|
-
Runtime validation belongs where data crosses from an untrusted representation into frontend-owned values. The schema is the
|
|
4
|
-
single source of truth for both the runtime check and the TypeScript type.
|
|
5
|
-
|
|
6
|
-
## Boundaries
|
|
7
|
-
|
|
8
|
-
Validate once, at the edge:
|
|
9
|
-
|
|
10
|
-
- network responses when they enter the frontend;
|
|
11
|
-
- URL path and search parameters before business logic uses them;
|
|
12
|
-
- persisted browser data when it is read;
|
|
13
|
-
- user input when it becomes a submitted domain value or request value.
|
|
14
|
-
|
|
15
|
-
Code inside that boundary receives parsed values and does not repeat defensive shape checks. An outgoing request built from
|
|
16
|
-
an already parsed domain value is serialized, not validated a second time. A typed client returning a handwritten generic is
|
|
17
|
-
not validation: it only asserts that an untrusted response has the requested type.
|
|
18
|
-
|
|
19
|
-
## Schema first, type derived
|
|
20
|
-
|
|
21
|
-
Use Zod for the runtime schema and derive the type with `z.infer`:
|
|
22
|
-
|
|
23
|
-
```ts
|
|
24
|
-
import { z } from "zod";
|
|
25
|
-
|
|
26
|
-
export const userSchema = z.object({
|
|
27
|
-
id: z.string(),
|
|
28
|
-
email: z.email()
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
export type User = z.infer<typeof userSchema>;
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
Do not maintain a handwritten `User` beside `userSchema`. Request and response shapes follow the same rule when the frontend
|
|
35
|
-
owns or consumes their runtime representation.
|
|
36
|
-
|
|
37
|
-
## TanStack Router search params
|
|
38
|
-
|
|
39
|
-
TanStack Router v1 accepts a Zod v4 schema directly in `validateSearch`; no adapter or parsing wrapper is needed:
|
|
40
|
-
|
|
41
|
-
```tsx
|
|
42
|
-
import { createFileRoute } from "@tanstack/react-router";
|
|
43
|
-
import { z } from "zod";
|
|
44
|
-
|
|
45
|
-
const searchSchema = z.object({
|
|
46
|
-
page: z.number().int().positive().catch(1),
|
|
47
|
-
filter: z.string().catch("")
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
type UserSearch = z.infer<typeof searchSchema>;
|
|
51
|
-
|
|
52
|
-
export const Route = createFileRoute("/users")({
|
|
53
|
-
validateSearch: searchSchema,
|
|
54
|
-
component: Users
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
function Users() {
|
|
58
|
-
const search = Route.useSearch();
|
|
59
|
-
return <UserList page={search.page} filter={search.filter} />;
|
|
60
|
-
}
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
`Route.useSearch()` returns `UserSearch` by inference from `validateSearch`. Use `.catch()` when malformed URL input should
|
|
64
|
-
fall back without interrupting navigation. Use `.default()` only when a missing value gets a default while malformed values
|
|
65
|
-
should still follow the route's validation error path.
|
|
66
|
-
|
|
67
|
-
## Failure handling
|
|
68
|
-
|
|
69
|
-
Use `schema.parse(value)` when invalid data is a contract failure that should follow the normal error path, such as a
|
|
70
|
-
malformed backend response reaching a route error boundary. Use `schema.safeParse(value)` when failure is expected and the
|
|
71
|
-
caller must render or otherwise handle validation issues, such as submitted user input.
|
|
72
|
-
|
|
73
|
-
Transforms and coercion belong in the boundary schema when they are part of entering the domain. Do not scatter trimming,
|
|
74
|
-
number conversion, or defaulting through downstream components.
|
|
75
|
-
|
|
76
|
-
## Audit
|
|
77
|
-
|
|
78
|
-
A boundary is aligned when:
|
|
79
|
-
|
|
80
|
-
- the untrusted source is represented as `unknown` until parsed;
|
|
81
|
-
- a Zod schema parses it at the point of entry;
|
|
82
|
-
- exported TypeScript types use `z.infer<typeof schema>`;
|
|
83
|
-
- downstream code consumes the parsed value without duplicate checks or assertions;
|
|
84
|
-
- parse failures reach the intended error or user-feedback path;
|
|
85
|
-
- behavior-changing schemas have focused tests for accepted, rejected, and transformed values.
|
|
86
|
-
|
|
87
|
-
TanStack Query placement is in [data.md](data.md), general type derivation in [typescript.md](typescript.md), URL and form
|
|
88
|
-
ownership in [react.md](react.md), and test selection in [testing.md](testing.md).
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: imf-web-ui-frontend-setup
|
|
3
|
-
description:
|
|
4
|
-
"Assess an ImFusion frontend against the project baseline and write a reviewable FRONTEND_SETUP_REPORT.md: new-project
|
|
5
|
-
setup needs, existing-project gaps, alignment migrations, or one named topic (for example data, lifecycle hooks, CSS class
|
|
6
|
-
names, or Prettier). Covers stack, package scripts, tooling, verification, project shape, data, docs, and agent wiring.
|
|
7
|
-
House conventions, not industry standards. This skill inspects and plans; approved implementation is a separate task. Not
|
|
8
|
-
for wiring the library itself (imf-web-ui-library-setup)."
|
|
9
|
-
argument-hint: "[new|audit|align|<topic>]"
|
|
10
|
-
allowed-tools: Read Glob Grep
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
# imf-web-ui-frontend-setup
|
|
14
|
-
|
|
15
|
-
You are the frontend setup auditor. Investigate the repository statically, record every supported conclusion in
|
|
16
|
-
`FRONTEND_SETUP_REPORT.md`, then stop for human review. You must follow the applicable `imf-web-ui-frontend-conventions`
|
|
17
|
-
references, cite repository evidence, distinguish defects from working deviations, and never present the baseline as
|
|
18
|
-
universal best practice.
|
|
19
|
-
|
|
20
|
-
## Workflow
|
|
21
|
-
|
|
22
|
-
1. Resolve the requested mode and scope.
|
|
23
|
-
2. Read every applicable convention reference below. For agent tooling, also read the vendored
|
|
24
|
-
`imf-web-ui-agent-setup/SKILL.md` and its templates.
|
|
25
|
-
3. Inspect the repository and write or refresh `FRONTEND_SETUP_REPORT.md` from
|
|
26
|
-
[`templates/FRONTEND_SETUP_REPORT.md`](templates/FRONTEND_SETUP_REPORT.md). The template is the report contract. Preserve
|
|
27
|
-
everything under `## Reviewer notes` verbatim.
|
|
28
|
-
4. Return the report path and a short verdict. Change nothing else; implementation is a separate, approved task.
|
|
29
|
-
|
|
30
|
-
## Safety
|
|
31
|
-
|
|
32
|
-
Use only static inspection: Read, Glob, Grep, and equivalent non-executing search tools. Do not use a shell or invoke Node,
|
|
33
|
-
npm, npx, package scripts, hooks, config imports, linters, tests, builds, Git commands, or project binaries. Read config as
|
|
34
|
-
text and report runtime or machine-local state that cannot be established statically as unverified.
|
|
35
|
-
|
|
36
|
-
Only `FRONTEND_SETUP_REPORT.md` may be written. Write is intentionally not pre-approved in `allowed-tools`; the report
|
|
37
|
-
follows the host's ordinary write approval. Host-managed hooks may run after that write; the skill neither invokes nor
|
|
38
|
-
suppresses them, but it does report broken or unexpected hook behavior found during static inspection.
|
|
39
|
-
|
|
40
|
-
## Modes
|
|
41
|
-
|
|
42
|
-
- **New** — record what exists and what setup work is needed.
|
|
43
|
-
- **Audit** — report broken and missing pieces. A working project convention wins; differences are deviations, not defects.
|
|
44
|
-
- **Align** — use the same evidence, but make deviations explicit migration proposals.
|
|
45
|
-
- **One topic** — resolve any other argument to matching rows below and assess only those. If none match, list the available
|
|
46
|
-
rows instead of guessing or widening scope.
|
|
47
|
-
|
|
48
|
-
## References
|
|
49
|
-
|
|
50
|
-
| Reference | Assess |
|
|
51
|
-
| --------------------------------- | ------------------------------------------------------------------------- |
|
|
52
|
-
| `stack.md` | dependencies, dead-code detection, and matching devtools |
|
|
53
|
-
| `npm-project.md` | package metadata, scripts, pins, npm and Node config |
|
|
54
|
-
| `tooling.md` | Prettier, ESLint, TypeScript, staged files, CSS class names |
|
|
55
|
-
| `git.md` | tracked hooks, verification scopes, staleness wiring |
|
|
56
|
-
| `project-structure.md` | source tree, naming, imports |
|
|
57
|
-
| `components.md` | component folders and colocation |
|
|
58
|
-
| `styling.md` | CSS Modules, tokens, prohibited styling systems |
|
|
59
|
-
| `validation.md` | runtime schemas, boundary parsing, derived types |
|
|
60
|
-
| `data.md` | transport, query/mutation options, keys, invalidation |
|
|
61
|
-
| `docs-structure.md` | README, AGENTS, docs index and content boundaries |
|
|
62
|
-
| `imf-web-ui-agent-setup/SKILL.md` | installed skills, AGENTS fence, lifecycle hooks, registrations, staleness |
|
|
63
|
-
|
|
64
|
-
Knip is required for dead-code detection. `eslint-plugin-jsx-a11y` remains a recommendation when the project is user-facing;
|
|
65
|
-
its absence is not a finding. CI, env and secrets, error tracking, deploy, dependency updates, and library wiring are out of
|
|
66
|
-
scope.
|
|
@@ -1,34 +0,0 @@
|
|
|
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.>
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
# Frontend Setup Report
|
|
2
|
-
|
|
3
|
-
Mode: `<new|audit|align|topic>` Scope: `<all applicable references or resolved topic references>`
|
|
4
|
-
|
|
5
|
-
<!--
|
|
6
|
-
This template is the report contract. Keep every H2 below once and in this order.
|
|
7
|
-
Use `None.` for an empty section.
|
|
8
|
-
|
|
9
|
-
Broken, Missing, Deviations, and Unverified entries use:
|
|
10
|
-
- **[high|medium|low] reference-or-agent-tooling — Short title**
|
|
11
|
-
- Evidence: `path:line`
|
|
12
|
-
- Impact: concrete consequence
|
|
13
|
-
- Next action: smallest selectable follow-up
|
|
14
|
-
|
|
15
|
-
Present entries name the reference and evidence path. Deviations are selectable follow-up work, not defects; in align mode,
|
|
16
|
-
their next actions are migration proposals. Optional tools are not missing findings.
|
|
17
|
-
-->
|
|
18
|
-
|
|
19
|
-
## Verdict
|
|
20
|
-
|
|
21
|
-
<One short assessment of the current frontend setup.>
|
|
22
|
-
|
|
23
|
-
## Broken
|
|
24
|
-
|
|
25
|
-
None.
|
|
26
|
-
|
|
27
|
-
## Missing
|
|
28
|
-
|
|
29
|
-
None.
|
|
30
|
-
|
|
31
|
-
## Deviations
|
|
32
|
-
|
|
33
|
-
None.
|
|
34
|
-
|
|
35
|
-
## Present
|
|
36
|
-
|
|
37
|
-
None.
|
|
38
|
-
|
|
39
|
-
## Unverified
|
|
40
|
-
|
|
41
|
-
None.
|
|
42
|
-
|
|
43
|
-
## Reviewer notes
|
|
44
|
-
|
|
45
|
-
<!-- Human-owned. Preserve everything under this heading verbatim when refreshing the report. -->
|