@imfusion/web-ui 0.5.1-dev.33.gadde98d1 → 0.5.1-dev.39.gfc64049e
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 +18 -10
- package/bin/install.js +96 -42
- package/bin/install.test.ts +150 -64
- package/dist/llms/gen-tokens.d.ts +7 -0
- package/package.json +6 -3
- package/src/llms/install-templates/codex-hooks.json +44 -0
- package/src/llms/install-templates/hooks/session-start.sh +5 -0
- package/src/llms/install-templates/hooks/stop.sh +18 -0
- package/src/llms/install-templates/hooks/subagent-start.sh +5 -0
- package/src/llms/install-templates/hooks/user-prompt-submit.sh +5 -0
- package/src/llms/{skills/imf-web-ui-agent-setup/templates → install-templates}/settings.json +14 -6
- package/src/llms/skills/imf-web-ui/SKILL.md +8 -11
- package/src/llms/skills/imf-web-ui-audit/SKILL.md +87 -0
- package/src/llms/skills/imf-web-ui-components/SKILL.md +1 -1
- package/src/llms/skills/imf-web-ui-conventions/SKILL.md +57 -0
- package/src/llms/skills/imf-web-ui-conventions/templates/AUDIT_CHECKLIST.md +141 -0
- package/src/llms/skills/{imf-web-ui-frontend-setup/templates/FRONTEND_SETUP_REPORT.md → imf-web-ui-conventions/templates/REPORT.md} +4 -4
- package/src/llms/skills/imf-web-ui-conventions/topics/agent-tooling.md +82 -0
- package/src/llms/skills/imf-web-ui-conventions/topics/assets.md +27 -0
- package/src/llms/skills/imf-web-ui-conventions/topics/authentication.md +65 -0
- package/src/llms/skills/imf-web-ui-conventions/topics/class-names.md +50 -0
- package/src/llms/skills/imf-web-ui-conventions/topics/components.md +101 -0
- package/src/llms/skills/imf-web-ui-conventions/topics/data.md +212 -0
- package/src/llms/skills/imf-web-ui-conventions/topics/docs-structure.md +40 -0
- package/src/llms/skills/imf-web-ui-conventions/topics/git.md +34 -0
- package/src/llms/skills/imf-web-ui-conventions/topics/library-boundary.md +33 -0
- package/src/llms/skills/imf-web-ui-conventions/topics/library-setup.md +26 -0
- package/src/llms/skills/{imf-web-ui-frontend-conventions/references → imf-web-ui-conventions/topics}/npm-project.md +14 -18
- package/src/llms/skills/imf-web-ui-conventions/topics/project-structure.md +44 -0
- package/src/llms/skills/imf-web-ui-conventions/topics/react.md +107 -0
- package/src/llms/skills/imf-web-ui-conventions/topics/styling.md +88 -0
- package/src/llms/skills/imf-web-ui-conventions/topics/testing.md +25 -0
- package/src/llms/skills/imf-web-ui-conventions/topics/tokens.md +7 -0
- package/src/llms/skills/imf-web-ui-conventions/topics/tooling.md +116 -0
- package/src/llms/skills/{imf-web-ui-frontend-conventions/references → imf-web-ui-conventions/topics}/typescript.md +3 -4
- package/src/llms/skills/imf-web-ui-conventions/topics/validation.md +62 -0
- package/src/llms/skills/imf-web-ui-setup/SKILL.md +71 -0
- package/src/llms/skills/imf-web-ui-update/SKILL.md +9 -3
- package/src/llms/skills/imf-web-ui-ux/SKILL.md +4 -4
- package/src/llms/skills/imf-web-ui-ux/references/forms.md +2 -2
- package/src/llms/tokens.gen.json +887 -0
- package/src/llms/skills/imf-web-ui-agent-setup/SKILL.md +0 -82
- 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-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/project-structure.md +0 -21
- package/src/llms/skills/imf-web-ui-frontend-conventions/references/react.md +0 -110
- 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/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/README.md +0 -27
- package/src/llms/skills/imf-web-ui-library-setup/SKILL.md +0 -36
- /package/src/llms/{skills/imf-web-ui-frontend-setup/templates → install-templates}/AGENTS.md +0 -0
- /package/src/llms/{skills/imf-web-ui-agent-setup/templates → install-templates}/hooks/baseline-staleness.sh +0 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Project structure
|
|
2
|
+
|
|
3
|
+
Kebab-case throughout, folders and files alike; exported symbols stay PascalCase — only the filename is kebab.
|
|
4
|
+
|
|
5
|
+
## Layout
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
src/
|
|
9
|
+
main.tsx # providers and router bootstrap; one WebUIProvider at the app root
|
|
10
|
+
routes/ # TanStack Router file-based routes; routing only — they compose, they don't fetch inline
|
|
11
|
+
__root.tsx # document-wide error/not-found boundary and Outlet
|
|
12
|
+
_public/ # anonymous route group; resolves the current user when public pages need it
|
|
13
|
+
route.tsx
|
|
14
|
+
_app/ # pathless authenticated route group; guards the subtree before children render
|
|
15
|
+
route.tsx
|
|
16
|
+
api/ # one folder per API topic — layout and behaviour in data.md
|
|
17
|
+
auth/ # one current-user query plus server-owned login/logout URL helpers
|
|
18
|
+
components/ # grouped by kind — anatomy in components.md
|
|
19
|
+
app-shell/ # optional persistent authenticated chrome: brand, navigation, and session action
|
|
20
|
+
http/ # transport: client, error normalisation — the only transport-aware place (data.md)
|
|
21
|
+
lib/ # framework-free helpers, each with a colocated .test.ts when it has decisions to test
|
|
22
|
+
auth/
|
|
23
|
+
login-url.ts
|
|
24
|
+
login-url.test.ts
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
- Each subtree's rules live with its topic: [data.md](data.md) for `api/` and `http/`, [components.md](components.md) for
|
|
28
|
+
`components/`.
|
|
29
|
+
|
|
30
|
+
## Application boundary
|
|
31
|
+
|
|
32
|
+
- Start every frontend with the root providers, a current-user query, and pathless `_public/` and `_app/` route groups —
|
|
33
|
+
contract in [authentication.md](authentication.md); add feature routes only after that boundary exists.
|
|
34
|
+
- Greenfield: `_app/` starts with a minimal `AppShell` — ImFusion logo, route navigation, and a stable session-action area
|
|
35
|
+
around the route `Outlet`.
|
|
36
|
+
- Omit the shell only when the human explicitly says the product has no persistent authenticated navigation; the `_app/`
|
|
37
|
+
guard stays.
|
|
38
|
+
- An established project preserves its working choice; public pages can use a small branded header instead.
|
|
39
|
+
|
|
40
|
+
## Imports
|
|
41
|
+
|
|
42
|
+
- `#/` alias for anything outside the current folder, plain `./` for siblings.
|
|
43
|
+
- The alias is always `#/` → `src/`: `#` is Node's own subpath-import prefix, so it can't collide with an npm scope.
|
|
44
|
+
- Wire it once, through `package.json`'s `imports` field (toolchain-native), with a matching tsconfig `paths` entry.
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# React
|
|
2
|
+
|
|
3
|
+
House defaults for React around `@imfusion/web-ui`. Links are for you, the agent — authoritative when a case here is
|
|
4
|
+
ambiguous; hand them to the human only if asked.
|
|
5
|
+
|
|
6
|
+
## Composition: pages (smart containers), partials, dumb components
|
|
7
|
+
|
|
8
|
+
The architecture principle, top-down — rooted in Dan Abramov's
|
|
9
|
+
[Presentational and Container Components](https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0) and
|
|
10
|
+
[Thinking in React](https://react.dev/learn/thinking-in-react). Styling lives exclusively in dumb and layout components,
|
|
11
|
+
nowhere else:
|
|
12
|
+
|
|
13
|
+
- **Pages are the smart containers.** The route component in `routes/` is the container — don't go looking for `*Container`
|
|
14
|
+
files. It owns how things _work_: loaders prefetch, the component fetches data, orchestrates, composes partials and passes
|
|
15
|
+
data down. Zero styling — a page wanting CSS (two panels side by side) means a layout component, not an inline style. A
|
|
16
|
+
standalone container earns its place only off-route (a modal fetching its own data).
|
|
17
|
+
- **Partials** are pure composition: reusable units of dumb and layout components with light logic and zero styling of their
|
|
18
|
+
own, extracted because they read as a unit. A partial with a CSS file is a red flag — the styled element wants to be its
|
|
19
|
+
own dumb component, or the wrapper wants to be a layout component.
|
|
20
|
+
- **Layout components** own _arrangement_ and nothing else: `Stack`-/`Row`-based wrappers with token gaps, a page grid, a
|
|
21
|
+
section frame.
|
|
22
|
+
- **Dumb components** own how things _look_: style and compose library primitives, take plain data and callbacks as props; no
|
|
23
|
+
fetching, routing, or business logic. All non-layout styling lives here and only here — logic-free dumb components (the
|
|
24
|
+
`@imfusion/web-ui` layer) keep screens restylable, testable with plain props, resilient to library updates.
|
|
25
|
+
- Wrapping library primitives in app-level dumb components: [library-boundary.md](library-boundary.md).
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
// Dumb — renders what it's given
|
|
29
|
+
function UserCard({ name, role, onEdit }: { name: string; role: string; onEdit: () => void }) {
|
|
30
|
+
return (
|
|
31
|
+
<Card.Root>
|
|
32
|
+
<Card.Content>
|
|
33
|
+
<Typo>{name}</Typo>
|
|
34
|
+
<Chip>{role}</Chip>
|
|
35
|
+
</Card.Content>
|
|
36
|
+
<Card.Footer>
|
|
37
|
+
<Button onClick={onEdit}>Edit</Button>
|
|
38
|
+
</Card.Footer>
|
|
39
|
+
</Card.Root>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
```tsx
|
|
45
|
+
// Smart — knows where data comes from, renders the dumb component
|
|
46
|
+
function UserCardContainer({ userId }: { userId: string }) {
|
|
47
|
+
const { data } = useUserQuery(userId);
|
|
48
|
+
const openEditor = useEditorNavigation(userId);
|
|
49
|
+
return <UserCard name={data.name} role={data.role} onEdit={openEditor} />;
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Compose, don't configure
|
|
54
|
+
|
|
55
|
+
- Build screen-level pieces by composing primitives (`Stack`, `Row`, `Card`, dumb components), not one component with a dozen
|
|
56
|
+
boolean props — a prop list that reads like a settings page wanted to be two or three components.
|
|
57
|
+
- Lift state shared between siblings to the nearest common parent
|
|
58
|
+
([Sharing State Between Components](https://react.dev/learn/sharing-state-between-components)); never sync copies.
|
|
59
|
+
|
|
60
|
+
## Put state where its truth lives
|
|
61
|
+
|
|
62
|
+
Work down this list; stop at the first match:
|
|
63
|
+
|
|
64
|
+
1. **URL state** — shareable via the address bar (filters, sort, pagination, active tab) → TanStack Router search params;
|
|
65
|
+
back button and copied links come free. These cross an external boundary — parse per [validation.md](validation.md).
|
|
66
|
+
2. **Server state** — from an API → TanStack Query's cache ([data.md](data.md)); never copy it into `useState` — that's how
|
|
67
|
+
stale-UI bugs are born.
|
|
68
|
+
3. **Subtree state** — resets on leave (wizard progress) → React context.
|
|
69
|
+
4. **Client state** — app-wide and persistent → TanStack Store, and only now: tiers 1–2 usually dissolve the "we need a
|
|
70
|
+
store" instinct.
|
|
71
|
+
5. **Local state** — one component's own (input value, open/closed, a draft, a hover flag) → `useState`. Most state is local;
|
|
72
|
+
keep it in the component, no library.
|
|
73
|
+
|
|
74
|
+
- A form turning drafts into a submitted domain value validates that boundary per [validation.md](validation.md).
|
|
75
|
+
- Shape the state itself per [Choosing the State Structure](https://react.dev/learn/choosing-the-state-structure) —
|
|
76
|
+
especially its rules on redundant and duplicated state.
|
|
77
|
+
|
|
78
|
+
## Effects: last resort, and named
|
|
79
|
+
|
|
80
|
+
- No `useEffect` for: derived values (render or `useMemo`), responses to user actions (the event handler), server sync (the
|
|
81
|
+
data-fetching layer). Effects only synchronize with systems _outside_ React.
|
|
82
|
+
- Read [You Might Not Need an Effect](https://react.dev/learn/you-might-not-need-an-effect) — the definitive misuse catalog —
|
|
83
|
+
before every effect you're tempted to write.
|
|
84
|
+
- Extract a genuine effect into a custom hook named for its purpose (`useSyncedScroll`, `useDocumentTitle`, `useHotkey`),
|
|
85
|
+
never an anonymous inline `useEffect`: the name documents intent, the hook isolates the dependency array. Pattern:
|
|
86
|
+
[Reusing Logic with Custom Hooks](https://react.dev/learn/reusing-logic-with-custom-hooks).
|
|
87
|
+
- The rare effect that stays inline still names its callback, so intent survives without a comment:
|
|
88
|
+
|
|
89
|
+
```tsx
|
|
90
|
+
useEffect(
|
|
91
|
+
function syncDocumentTitle() {
|
|
92
|
+
document.title = title;
|
|
93
|
+
},
|
|
94
|
+
[title]
|
|
95
|
+
);
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Reading list
|
|
99
|
+
|
|
100
|
+
Consult while building; each is the authority for its topic:
|
|
101
|
+
|
|
102
|
+
- [Thinking in React](https://react.dev/learn/thinking-in-react) — decomposition and one-way data flow
|
|
103
|
+
- [Keeping Components Pure](https://react.dev/learn/keeping-components-pure) — why dumb components stay dumb
|
|
104
|
+
- [Choosing the State Structure](https://react.dev/learn/choosing-the-state-structure) — shaping state without duplication
|
|
105
|
+
- [Sharing State Between Components](https://react.dev/learn/sharing-state-between-components) — lifting state
|
|
106
|
+
- [You Might Not Need an Effect](https://react.dev/learn/you-might-not-need-an-effect) — the effect misuse catalog
|
|
107
|
+
- [Reusing Logic with Custom Hooks](https://react.dev/learn/reusing-logic-with-custom-hooks) — named effects live here
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Styling
|
|
2
|
+
|
|
3
|
+
How app CSS is written and how it meets `@imfusion/web-ui`.
|
|
4
|
+
|
|
5
|
+
## CSS authoring
|
|
6
|
+
|
|
7
|
+
- **CSS Modules with native CSS only** — no Sass, no CSS-in-JS, no utility-class framework: nesting and custom properties
|
|
8
|
+
already cover DRY. Stylesheet location and ownership: [components.md](components.md).
|
|
9
|
+
- Nest pseudo-elements, states, and child selectors under the root so the prefix is written once:
|
|
10
|
+
|
|
11
|
+
```css
|
|
12
|
+
.root {
|
|
13
|
+
transition: clip-path var(--ease);
|
|
14
|
+
|
|
15
|
+
&:focus-visible {
|
|
16
|
+
outline: var(--imf-ui-border-size-2) solid var(--imf-ui-color-fg-support);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
&[data-disabled] {
|
|
20
|
+
opacity: 0.5;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
- Lift a repeated literal (an easing, a colour-math result) into a custom property; custom properties also carry
|
|
26
|
+
per-state/per-variant values down the tree — native parameterisation, no mixin needed.
|
|
27
|
+
- Don't merge rules that only look similar — structurally different output (three distinct `clip-path` polygons) isn't
|
|
28
|
+
repetition; keep it explicit.
|
|
29
|
+
|
|
30
|
+
## Build custom UI from tokens
|
|
31
|
+
|
|
32
|
+
- Custom UI the library doesn't cover (a stat widget, a custom panel) uses names from the shipped token index
|
|
33
|
+
([tokens.md](tokens.md)) for color, size, radius, and type — that's what makes it look native and survive theme changes.
|
|
34
|
+
- A hex code or a magic `px` next to a concept the tokens already name is a defect.
|
|
35
|
+
- Geometry that can't be a token (a clip-path percentage, a hairline `1px`) → named custom property at the top of the
|
|
36
|
+
stylesheet, invariant written next to it. Don't silently approximate to the nearest token.
|
|
37
|
+
|
|
38
|
+
## Override through the sanctioned seams
|
|
39
|
+
|
|
40
|
+
- All library styles live in the `imf-ui.components` CSS layer, so any plain selector you write wins — that's the whole
|
|
41
|
+
override contract.
|
|
42
|
+
- Target the stable hooks: `data-imf-ui-component` attributes and your own classes/wrappers.
|
|
43
|
+
- Never target the library's internal class names — they are generated and change without notice.
|
|
44
|
+
- Never `!important` — needing it means you're targeting the wrong thing.
|
|
45
|
+
- Style against state via data attributes (`[data-checked]`, `[data-disabled]`, `[data-popup-open]`) — components expose
|
|
46
|
+
state there, so never maintain your own state classes. `className` is purely a styling surface:
|
|
47
|
+
|
|
48
|
+
```css
|
|
49
|
+
[data-imf-ui-component="Switch"][data-checked] {
|
|
50
|
+
outline: 2px solid var(--imf-ui-color-bg-positive);
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## The color system
|
|
55
|
+
|
|
56
|
+
- Roles: **surfaces** (`main` canvas, `support` panels, `minor` popovers); **brand** (identity, full saturation) vs
|
|
57
|
+
**primary** (contrast-tuned, CTAs) — distinct roles on purpose; **status** (`negative`, `warning`, `positive`, `info`);
|
|
58
|
+
three **accent** slots.
|
|
59
|
+
- **Controls** are the 80/20 customization surface — override one and every derived token shifts:
|
|
60
|
+
|
|
61
|
+
```css
|
|
62
|
+
:root {
|
|
63
|
+
--imf-ui-color-primary-hue: 30; /* every primary semantic token follows */
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
- **Semantic tokens** are what you consume: `--imf-ui-color-bg-{name}` per role, a flat
|
|
68
|
+
`--imf-ui-color-fg-{main|support|minor|oncolor|…}` ladder for text. Borders and rings draw from `fg-*`.
|
|
69
|
+
- Push a hue control into a pale corner → you own overriding the matching `fg-*` token.
|
|
70
|
+
- Color scheme is `<html data-imf-ui-color-scheme="light|dark">`, set by the provider; scheme-specific styling selects via
|
|
71
|
+
that attribute.
|
|
72
|
+
|
|
73
|
+
## Responsive styling
|
|
74
|
+
|
|
75
|
+
- Tune components per breakpoint by overriding their `--imf-ui-*` variables inside your own media queries — custom properties
|
|
76
|
+
cross the `@media` boundary, props don't (why sizing is variables, not a `width` prop):
|
|
77
|
+
|
|
78
|
+
```css
|
|
79
|
+
@media (min-width: 768px) {
|
|
80
|
+
.my-shell {
|
|
81
|
+
--imf-ui-appshell-navbar-width: 22rem;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
- Write literal breakpoint values — the library's `@custom-media` aliases are build-internal and don't ship.
|
|
87
|
+
- JS only for behaviour (render a burger menu on mobile): `useMediaQuery(minWidth("md"))`. Never for styling CSS can do.
|
|
88
|
+
- No responsive props — `size={{ sm: … }}` objects are deliberately not offered; write the media query.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Testing
|
|
2
|
+
|
|
3
|
+
Test the **decisions**, not the rendering.
|
|
4
|
+
|
|
5
|
+
## Naming
|
|
6
|
+
|
|
7
|
+
- Colocated tests use `.test.ts` / `.test.tsx` (e.g. `login-url.test.ts`) — not `.spec.*` or plural `.tests.*`.
|
|
8
|
+
|
|
9
|
+
## What gets a test
|
|
10
|
+
|
|
11
|
+
- **Pure logic gets unit tests** — pricing, permissions, date math, parsing, reducers: cheap, fast, and where bugs actually
|
|
12
|
+
hide.
|
|
13
|
+
- **Presentational components generally don't**: a component mapping props onto web-ui primitives has no logic of its own —
|
|
14
|
+
asserting a `<Button>` rendered tests React, not your code.
|
|
15
|
+
- **Behaviour a user performs gets an interaction test** — a validating form, a stepped flow — through the interface the user
|
|
16
|
+
has (roles, labels, visible text), not through internals.
|
|
17
|
+
- **Extract the decision out of a hook and test it as a plain function** — easier than a render harness; a hook that only
|
|
18
|
+
wraps a browser API has no decision to extract.
|
|
19
|
+
- The measure isn't coverage percentage — it's whether a failing test tells you something you didn't already know.
|
|
20
|
+
|
|
21
|
+
## Layer-specific recipes
|
|
22
|
+
|
|
23
|
+
- Data layer: stub `fetch`, run the real query client — the recipe is in [data.md](data.md).
|
|
24
|
+
- A schema whose constraints or transforms encode product behavior gets focused accepted, rejected, and transformed cases:
|
|
25
|
+
[validation.md](validation.md).
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Tokens
|
|
2
|
+
|
|
3
|
+
## Token index
|
|
4
|
+
|
|
5
|
+
- `@imfusion/web-ui` ships the generated token index at `node_modules/@imfusion/web-ui/src/llms/tokens.gen.json` — the only
|
|
6
|
+
source for consumer token names and authored default values.
|
|
7
|
+
- Look up the exact entry there; never invent, recall, or duplicate a plausible `--imf-ui-*` name.
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Tooling
|
|
2
|
+
|
|
3
|
+
Dependency and configuration baseline for an ImFusion frontend; check before adding a dependency and before writing its
|
|
4
|
+
config.
|
|
5
|
+
|
|
6
|
+
## Topic-to-tool map
|
|
7
|
+
|
|
8
|
+
- Use the mapped tool for each topic; prefer typed config (`.ts` over `.json`) where the tool supports it.
|
|
9
|
+
|
|
10
|
+
| Topic | Tool |
|
|
11
|
+
| --------------------- | ----------------------------------------------------------------------------------------------------- |
|
|
12
|
+
| Build | [Vite](https://vite.dev) |
|
|
13
|
+
| Routing, URL state | [TanStack Router](https://tanstack.com/router) — file-based, type-safe search params |
|
|
14
|
+
| Server state | [TanStack Query](https://tanstack.com/query) — the pattern around it is [data.md](data.md) |
|
|
15
|
+
| Forms | [TanStack Form](https://tanstack.com/form) |
|
|
16
|
+
| Data grids | [TanStack Table](https://tanstack.com/table) + web-ui's styled `Table` parts |
|
|
17
|
+
| App-wide client state | [TanStack Store](https://tanstack.com/store) — last resort in the state ladder ([react.md](react.md)) |
|
|
18
|
+
| Schema validation | [Zod](https://zod.dev) — at the network boundary ([data.md](data.md)) |
|
|
19
|
+
| Styling | CSS Modules ([styling.md](styling.md)) |
|
|
20
|
+
| Test | [Vitest](https://vitest.dev) |
|
|
21
|
+
| Format | [Prettier](https://prettier.io) |
|
|
22
|
+
| Lint | [ESLint](https://eslint.org) flat config |
|
|
23
|
+
| Types | `tsc --noEmit` — own script, own CI step |
|
|
24
|
+
| Staged files | [lint-staged](https://github.com/lint-staged/lint-staged) (or nano-staged, drop-in) |
|
|
25
|
+
| Dead code | [Knip](https://knipjs.dev) via `verify:knip` — needs per-repo config |
|
|
26
|
+
|
|
27
|
+
## When a library owns a layer
|
|
28
|
+
|
|
29
|
+
- Adopt the library once you're rebuilding what it does — validation timing and cross-field rules (Form), caching and
|
|
30
|
+
refetching (Query), URL as source of truth (Router), sorting/pagination over rows (Table): adding it mid-project is cheap,
|
|
31
|
+
unpicking a hand-rolled version later is not.
|
|
32
|
+
|
|
33
|
+
## Devtools
|
|
34
|
+
|
|
35
|
+
- Every TanStack library with a devtools package gets it as a dev dependency, mounted in development only — Router's
|
|
36
|
+
`@tanstack/react-router-devtools` is a given; Query's goes in when Query does.
|
|
37
|
+
- Check for a `-devtools` sibling on every new TanStack dependency — not every library has one yet.
|
|
38
|
+
- Once several are in, host them in one panel via `@tanstack/devtools`.
|
|
39
|
+
|
|
40
|
+
## Docs over memory
|
|
41
|
+
|
|
42
|
+
- `npx @tanstack/cli` for TanStack docs — never work from memory.
|
|
43
|
+
- Where `@tanstack/intent` is wired up, use it to reach and read the Agent Skills the TanStack dependencies ship.
|
|
44
|
+
|
|
45
|
+
## Prettier
|
|
46
|
+
|
|
47
|
+
- Config file shape is free (`.prettierrc`, `prettier.config.ts`); the values are not:
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
import type { Config } from "prettier";
|
|
51
|
+
|
|
52
|
+
const config: Config = {
|
|
53
|
+
printWidth: 125,
|
|
54
|
+
tabWidth: 2,
|
|
55
|
+
useTabs: false,
|
|
56
|
+
trailingComma: "none",
|
|
57
|
+
arrowParens: "avoid",
|
|
58
|
+
semi: true,
|
|
59
|
+
singleQuote: false,
|
|
60
|
+
proseWrap: "always"
|
|
61
|
+
};
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
- Never omit the config file — no file means defaults, and the values silently differ.
|
|
65
|
+
|
|
66
|
+
## ESLint
|
|
67
|
+
|
|
68
|
+
Flat config (`eslint.config.ts`):
|
|
69
|
+
|
|
70
|
+
- `strictTypeChecked` + `stylisticTypeChecked`, `projectService: true`
|
|
71
|
+
- `as` and `!` banned outside tests (`consistent-type-assertions`)
|
|
72
|
+
- `.gitignore` as the ignore source (`includeIgnoreFile` from `@eslint/compat`)
|
|
73
|
+
- `#/` alias enforced via `no-restricted-imports` banning `../*` — parent-relative paths error, siblings (`./`) stay relative
|
|
74
|
+
([project-structure.md](project-structure.md))
|
|
75
|
+
|
|
76
|
+
## tsconfig
|
|
77
|
+
|
|
78
|
+
```jsonc
|
|
79
|
+
{
|
|
80
|
+
"compilerOptions": {
|
|
81
|
+
"strict": true,
|
|
82
|
+
"moduleResolution": "bundler",
|
|
83
|
+
"verbatimModuleSyntax": true, // import type stays import type
|
|
84
|
+
"noUnusedLocals": true,
|
|
85
|
+
"noUnusedParameters": true,
|
|
86
|
+
"noFallthroughCasesInSwitch": true,
|
|
87
|
+
"noUncheckedSideEffectImports": true,
|
|
88
|
+
"skipLibCheck": true,
|
|
89
|
+
"paths": { "#/*": ["./src/*"] }
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
- `#/` → `src/` alias rationale: [project-structure.md](project-structure.md).
|
|
95
|
+
|
|
96
|
+
## Staged files
|
|
97
|
+
|
|
98
|
+
- Runner config (lint-staged or nano-staged) applies eslint `--fix` and prettier `--write` to staged files only.
|
|
99
|
+
- The pre-commit runner also invokes `verify:knip` when staged source or project config can change the reachability graph.
|
|
100
|
+
|
|
101
|
+
## CSS class names
|
|
102
|
+
|
|
103
|
+
- Generated CSS Module class names are readable in the DOM, `{prefix}-{file}-{local}`, never the default hash: a legible DOM
|
|
104
|
+
is what makes devtools and browser automation usable.
|
|
105
|
+
- The library ships the plugin; pass a short, app-scoped prefix:
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
import { readableCssModuleNames } from "@imfusion/web-ui/build/vite-css-module-names";
|
|
109
|
+
|
|
110
|
+
export default defineConfig({
|
|
111
|
+
plugins: [react(), readableCssModuleNames({ prefix: "acme" })]
|
|
112
|
+
});
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
- One pattern for dev, Storybook, and production: register the plugin in **every** tool that compiles the CSS — a compiler
|
|
116
|
+
left out generates different names for the same source, and its styles silently don't apply.
|
|
@@ -18,10 +18,9 @@ type Size = (typeof sizes)[number];
|
|
|
18
18
|
const labels: Record<Size, string> = { sm: "S", md: "M", lg: "L" }; // compiler breaks if `sizes` changes
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
([library-boundary.md](library-boundary.md)), untrusted data via a runtime schema and `z.infer`
|
|
23
|
-
([validation.md](validation.md)).
|
|
24
|
-
|
|
21
|
+
- Same rule across boundaries: 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)).
|
|
25
24
|
- Function signatures: 1–2 positional arguments; at 3+, one destructured object.
|
|
26
25
|
|
|
27
26
|
## Immutability & expressions
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Validation
|
|
2
|
+
|
|
3
|
+
Validate where data crosses from untrusted representation into frontend-owned values; the schema is the single source of
|
|
4
|
+
truth for runtime check and TypeScript type.
|
|
5
|
+
|
|
6
|
+
## Boundaries
|
|
7
|
+
|
|
8
|
+
- Validate once, at the edge: network responses on entry; URL path and search params before business logic; persisted browser
|
|
9
|
+
data on read; user input becoming a submitted domain or request value.
|
|
10
|
+
- Inside the boundary: parsed values only, no repeated defensive shape checks.
|
|
11
|
+
- Outgoing requests built from already parsed domain values are serialized, not re-validated.
|
|
12
|
+
- A typed client's handwritten generic is not validation — it only asserts a type onto an untrusted response.
|
|
13
|
+
|
|
14
|
+
## Schema first, type derived
|
|
15
|
+
|
|
16
|
+
- Zod schema, type via `z.infer`; never a handwritten type beside its schema.
|
|
17
|
+
- Same rule for request and response shapes the frontend owns or consumes at runtime.
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { z } from "zod";
|
|
21
|
+
|
|
22
|
+
export const userSchema = z.object({
|
|
23
|
+
id: z.string(),
|
|
24
|
+
email: z.email()
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
export type User = z.infer<typeof userSchema>;
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## TanStack Router search params
|
|
31
|
+
|
|
32
|
+
- `validateSearch` takes a Zod v4 schema directly (TanStack Router v1) — no adapter or parsing wrapper.
|
|
33
|
+
- `Route.useSearch()` infers its type from `validateSearch`.
|
|
34
|
+
- `.catch()`: malformed URL input falls back without interrupting navigation.
|
|
35
|
+
- `.default()`: only for missing values; malformed values still follow the route's validation error path.
|
|
36
|
+
|
|
37
|
+
```tsx
|
|
38
|
+
import { createFileRoute } from "@tanstack/react-router";
|
|
39
|
+
import { z } from "zod";
|
|
40
|
+
|
|
41
|
+
const searchSchema = z.object({
|
|
42
|
+
page: z.number().int().positive().catch(1),
|
|
43
|
+
filter: z.string().catch("")
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
export const Route = createFileRoute("/users")({
|
|
47
|
+
validateSearch: searchSchema,
|
|
48
|
+
component: Users
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
function Users() {
|
|
52
|
+
const search = Route.useSearch(); // inferred: z.infer<typeof searchSchema>
|
|
53
|
+
return <UserList page={search.page} filter={search.filter} />;
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Failure handling
|
|
58
|
+
|
|
59
|
+
- `schema.parse`: invalid data is a contract failure on the normal error path (malformed backend response → route error
|
|
60
|
+
boundary).
|
|
61
|
+
- `schema.safeParse`: failure is expected and the caller handles the issues (submitted user input).
|
|
62
|
+
- Transforms and coercion live in the boundary schema — never scatter trimming, number conversion, or defaulting downstream.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: imf-web-ui-setup
|
|
3
|
+
description:
|
|
4
|
+
"Plan and, after explicit approval, bootstrap an ImFusion frontend against the conventions baseline: library setup,
|
|
5
|
+
tooling, git, npm project, authentication, project structure, docs structure, data, testing, or agent tooling. Inspect
|
|
6
|
+
statically first and write only the approved files. Use imf-web-ui-audit for read-only health checks or topics with no
|
|
7
|
+
setup action."
|
|
8
|
+
argument-hint: "[full|library-setup|tooling|git|npm-project|authentication|project-structure|docs-structure|data|testing|agent-tooling]"
|
|
9
|
+
allowed-tools: Read Glob Grep Write
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# imf-web-ui-setup
|
|
13
|
+
|
|
14
|
+
You are the frontend bootstrap planner. Inspect the repository statically, read the applicable `imf-web-ui-conventions`
|
|
15
|
+
topics, and prepare a complete proposal for every file you would create or change. The project wins where it already has a
|
|
16
|
+
working convention. This skill is plan-first: enter the host's plan mode before presenting the proposal, and do not create a
|
|
17
|
+
report file as a substitute for the host plan.
|
|
18
|
+
|
|
19
|
+
## Workflow
|
|
20
|
+
|
|
21
|
+
1. Resolve the argument. Bare means `full`; a topic selects one supported setup topic. For an unsupported topic, list the
|
|
22
|
+
available setup topics and point the human to `imf-web-ui-audit` for a read-only report.
|
|
23
|
+
2. Enter the host's plan mode. If it is not already active, use the host plan-mode control before inspecting and planning.
|
|
24
|
+
3. Read the selected convention topics and inspect the repository with `Read`, `Glob`, and `Grep` only.
|
|
25
|
+
4. Build the complete proposal in the host plan from the shared report contract at
|
|
26
|
+
[`../imf-web-ui-conventions/templates/REPORT.md`](../imf-web-ui-conventions/templates/REPORT.md). Include the concrete
|
|
27
|
+
content of every file the approved setup would create or change, with repository evidence for each decision.
|
|
28
|
+
5. For a greenfield project, propose the minimal branded `AppShell` in the authenticated route group by default. If the human
|
|
29
|
+
explicitly says the product has no persistent authenticated navigation, ask whether to omit the shell before finalizing
|
|
30
|
+
the proposal; keep the `_app/` boundary either way. Existing projects keep their working shell choice. This ticket
|
|
31
|
+
documents the starter shape but does not copy a full starter template.
|
|
32
|
+
6. Keep the plan as the approval gate. After the host approves and exits plan mode, write only the listed files. Complete any
|
|
33
|
+
immediately requested bootstrap work covered by that approved plan, then invoke `imf-web-ui-audit full` and review its
|
|
34
|
+
`AUDIT_REPORT.md` before declaring the setup complete.
|
|
35
|
+
|
|
36
|
+
Installer and hook runs stay with the human: an `agent-tooling` proposal lists the `npx web-ui-install` commands as human
|
|
37
|
+
steps, applies the judgment from the topic (read existing registrations first, never stack a hook on a covered event), and
|
|
38
|
+
includes the topic's Codex trust follow-up whenever hooks are part of the plan.
|
|
39
|
+
|
|
40
|
+
## Checklist
|
|
41
|
+
|
|
42
|
+
The post-implementation audit uses the shared
|
|
43
|
+
[convention audit checklist](../imf-web-ui-conventions/templates/AUDIT_CHECKLIST.md) to verify the complete baseline, not
|
|
44
|
+
only the setup topic selected at the start.
|
|
45
|
+
|
|
46
|
+
## Safety
|
|
47
|
+
|
|
48
|
+
Use only static inspection: `Read`, `Glob`, and `Grep`. Do not use a shell or invoke Node, npm, npx, package scripts, hooks,
|
|
49
|
+
config imports, linters, tests, builds, Git commands, project binaries, installers, or package managers. Read config as text
|
|
50
|
+
and report runtime or machine-local state that cannot be established statically as unverified. After approval, `Write` is
|
|
51
|
+
limited to the files listed in the approved report; anything needing execution goes in the plan for the human.
|
|
52
|
+
|
|
53
|
+
## Setup topics
|
|
54
|
+
|
|
55
|
+
`full` covers every topic below. A one-topic run assesses only that topic.
|
|
56
|
+
|
|
57
|
+
| Topic | Assess and plan |
|
|
58
|
+
| ------------------- | ------------------------------------------------------------------------------ |
|
|
59
|
+
| `library-setup` | styles import, `WebUIProvider`, and library package wiring |
|
|
60
|
+
| `tooling` | dependency selection, devtools, Prettier, ESLint, TypeScript, and verification |
|
|
61
|
+
| `git` | tracked hooks, verification scopes, and staleness wiring |
|
|
62
|
+
| `npm-project` | package metadata, scripts, pins, npm, and Node configuration |
|
|
63
|
+
| `authentication` | current-user query, public/app guards, login, and logout |
|
|
64
|
+
| `project-structure` | source tree, route groups, optional app shell, naming, and placement |
|
|
65
|
+
| `docs-structure` | README, AGENTS, docs index, and content boundaries |
|
|
66
|
+
| `data` | transport, schemas, query/mutation options, keys, and invalidation |
|
|
67
|
+
| `testing` | test boundaries and verification coverage |
|
|
68
|
+
| `agent-tooling` | vendored skills, AGENTS fence, lifecycle hooks, registrations, and staleness |
|
|
69
|
+
|
|
70
|
+
Setup has no file-creation action for `react`, `typescript`, `class-names`, `validation`, `components`, `styling`, `assets`,
|
|
71
|
+
`library-boundary`, or `tokens`; select those in `imf-web-ui-audit`.
|
|
@@ -53,7 +53,7 @@ pass. Stop on install failure.
|
|
|
53
53
|
Then refresh the existing web-ui skill target and lifecycle hooks:
|
|
54
54
|
|
|
55
55
|
```sh
|
|
56
|
-
npx web-ui-install
|
|
56
|
+
npx web-ui-install
|
|
57
57
|
npx web-ui-install --hooks
|
|
58
58
|
```
|
|
59
59
|
|
|
@@ -62,8 +62,10 @@ Run these from the consumer package root, the directory holding the `node_module
|
|
|
62
62
|
binary. Preserve the existing target (`.agents`, `.claude`, or both); do not silently choose a new one.
|
|
63
63
|
|
|
64
64
|
Always run both installer commands after the package update. Skills and installer-owned hook scripts may have changed even
|
|
65
|
-
when their existing registrations already cover session start, prompt submit, and
|
|
66
|
-
refreshes those scripts wholesale
|
|
65
|
+
when their existing registrations already cover session start, subagent start, prompt submit, and the stop gate. The hook
|
|
66
|
+
installer refreshes those scripts wholesale, prunes retired scripts and their registrations, and merges registrations
|
|
67
|
+
idempotently into both host files (`.claude/settings.json` and `.codex/hooks.json`). Stop on malformed settings or installer
|
|
68
|
+
conflicts.
|
|
67
69
|
|
|
68
70
|
## Verification
|
|
69
71
|
|
|
@@ -132,6 +134,10 @@ verification again, and show a second report with the migration paths, held-back
|
|
|
132
134
|
If no findings exist, report that the installed update has no detected compatibility fixes, changed usages, or replacement
|
|
133
135
|
candidates. Do not create an empty migration commit.
|
|
134
136
|
|
|
137
|
+
Whatever the migration outcome, close the phase by asking whether to run `imf-web-ui-audit full`: an update can shift the
|
|
138
|
+
baseline (new components, hooks, conventions), and the audit report shows where the project now stands against it. Run it
|
|
139
|
+
only on an explicit yes.
|
|
140
|
+
|
|
135
141
|
## Migration approval
|
|
136
142
|
|
|
137
143
|
Ask exactly, **“Approve these consumer migration changes and commit them separately?”** Do not commit without an affirmative
|
|
@@ -11,7 +11,7 @@ description:
|
|
|
11
11
|
Most teams consuming `@imfusion/web-ui` don't have a designer on call. This skill stands in: it encodes the library authors'
|
|
12
12
|
UX experience — the whole experience of a screen, its visual design, and the usability where both meet. Follow it by default;
|
|
13
13
|
deviate when the product has a real reason to. Code-level patterns (tokens, layers, wrappers) live in
|
|
14
|
-
`imf-web-ui-
|
|
14
|
+
`imf-web-ui-conventions`; project wiring lives in the `library-setup` topic of `imf-web-ui-conventions`.
|
|
15
15
|
|
|
16
16
|
Component names below are real — verify any API against the docgen index (`imf-web-ui-components`) before use. Never invent a
|
|
17
17
|
component this library doesn't ship.
|
|
@@ -82,13 +82,13 @@ Every screen ships four states, not one:
|
|
|
82
82
|
|
|
83
83
|
Custom UI the library doesn't cover should be indistinguishable from library UI: build it from `--imf-ui-*` tokens and
|
|
84
84
|
compose it with library primitives. The goal lives here; the mechanics (tokens, layers, wrappers) live in
|
|
85
|
-
`imf-web-ui-
|
|
85
|
+
`imf-web-ui-conventions`.
|
|
86
86
|
|
|
87
87
|
## Experimental components
|
|
88
88
|
|
|
89
89
|
The identity index marks each component `stable` or `experimental`. Experimental ones are fine to use, but expect API
|
|
90
|
-
movement across releases — prefer wrapping them once (see `imf-web-ui-
|
|
91
|
-
|
|
90
|
+
movement across releases — prefer wrapping them once (see `imf-web-ui-conventions`) so a breaking change lands in one file,
|
|
91
|
+
not forty call sites.
|
|
92
92
|
|
|
93
93
|
## Deep dives
|
|
94
94
|
|
|
@@ -8,8 +8,8 @@ non-compliant ones. Read before building any form beyond two fields.
|
|
|
8
8
|
|
|
9
9
|
The library ships the controls (`Input`, `Select`, `Checkbox`, `Switch`, `Slider`) but no form or field wrapper, so labels,
|
|
10
10
|
grouping, and where errors appear are composed by you. That's exactly where these rules apply. Composing the markup is not
|
|
11
|
-
the same as owning the state: form state and validation belong to a form library (see `imf-web-ui-
|
|
12
|
-
|
|
11
|
+
the same as owning the state: form state and validation belong to a form library (see `imf-web-ui-conventions`), and these
|
|
12
|
+
rules govern how its errors get presented.
|
|
13
13
|
|
|
14
14
|
## Structure
|
|
15
15
|
|