@imfusion/web-ui 0.5.1-dev.2.g0a349a2a → 0.5.1-dev.20.g9170acaf
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 +122 -43
- package/bin/install.js +343 -0
- package/bin/install.test.ts +166 -0
- package/dist/build/vite-css-module-names/index.d.ts +20 -0
- package/dist/build/vite-css-module-names.js +17 -0
- package/dist/components/logo/logo.d.ts +1 -1
- package/dist/index.js +4 -2
- package/dist/style.css +1 -1
- package/package.json +34 -24
- package/src/docgen/doc.gen.json +1 -1
- package/src/llms/skills/imf-web-ui/SKILL.md +15 -11
- package/src/llms/skills/imf-web-ui-agent-setup/SKILL.md +71 -0
- package/src/llms/skills/imf-web-ui-agent-setup/templates/hooks/baseline-staleness.sh +17 -0
- package/src/llms/skills/imf-web-ui-agent-setup/templates/hooks/post-tool-use.sh +21 -0
- package/src/llms/skills/imf-web-ui-agent-setup/templates/hooks/session-start.sh +4 -0
- package/src/llms/skills/imf-web-ui-agent-setup/templates/hooks/user-prompt-submit.sh +4 -0
- package/src/llms/skills/imf-web-ui-agent-setup/templates/settings.json +37 -0
- package/src/llms/skills/imf-web-ui-components/SKILL.md +1 -1
- package/src/llms/skills/imf-web-ui-frontend-conventions/SKILL.md +45 -0
- package/src/llms/skills/imf-web-ui-frontend-conventions/references/assets.md +23 -0
- package/src/llms/skills/imf-web-ui-frontend-conventions/references/class-names.md +42 -0
- package/src/llms/skills/imf-web-ui-frontend-conventions/references/components.md +63 -0
- package/src/llms/skills/imf-web-ui-frontend-conventions/references/data.md +196 -0
- package/src/llms/skills/imf-web-ui-frontend-conventions/references/docs-structure.md +44 -0
- package/src/llms/skills/imf-web-ui-frontend-conventions/references/git.md +33 -0
- package/src/llms/skills/imf-web-ui-frontend-conventions/references/library-boundary.md +37 -0
- package/src/llms/skills/imf-web-ui-frontend-conventions/references/npm-project.md +57 -0
- package/src/llms/skills/imf-web-ui-frontend-conventions/references/project-structure.md +21 -0
- package/src/llms/skills/{imf-web-ui-frontend-patterns/references/react-patterns.md → imf-web-ui-frontend-conventions/references/react.md} +26 -12
- package/src/llms/skills/imf-web-ui-frontend-conventions/references/stack.md +39 -0
- package/src/llms/skills/imf-web-ui-frontend-conventions/references/styling.md +91 -0
- package/src/llms/skills/imf-web-ui-frontend-conventions/references/testing.md +16 -0
- package/src/llms/skills/imf-web-ui-frontend-conventions/references/tooling.md +76 -0
- package/src/llms/skills/imf-web-ui-frontend-conventions/references/typescript.md +45 -0
- package/src/llms/skills/imf-web-ui-frontend-setup/SKILL.md +89 -0
- package/src/llms/skills/imf-web-ui-frontend-setup/templates/AGENTS.md +34 -0
- package/src/llms/skills/imf-web-ui-frontend-setup/templates/README.md +27 -0
- package/src/llms/skills/imf-web-ui-library-setup/SKILL.md +36 -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-skill.js +0 -180
- package/src/llms/skills/imf-web-ui-frontend-patterns/SKILL.md +0 -93
- package/src/llms/skills/imf-web-ui-frontend-patterns/references/code-conventions.md +0 -133
- package/src/llms/skills/imf-web-ui-imfusion-frontend-setup/SKILL.md +0 -201
- package/src/llms/skills/imf-web-ui-setup/SKILL.md +0 -57
|
@@ -0,0 +1,76 @@
|
|
|
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.
|
|
@@ -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,89 @@
|
|
|
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, when asked to align a repo with the baseline, or when asked to check or set up one named topic from it (e.g. CSS
|
|
8
|
+
class names, Prettier). Not for a bare 'add this config file' request — that's just the edit. Not for wiring the library
|
|
9
|
+
itself (imf-web-ui-library-setup)."
|
|
10
|
+
argument-hint: "[new|audit|align|<topic>]"
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# imf-web-ui-frontend-setup
|
|
14
|
+
|
|
15
|
+
First-time setup and audit against the ImFusion frontend baseline. The conventions live in `imf-web-ui-frontend-conventions`
|
|
16
|
+
— this skill is the process that checks a repo against them and wires up what they assume. In-house conventions, not industry
|
|
17
|
+
standards: report findings as "missing against the ImFusion baseline", never "against best practice". Built for ImFusion
|
|
18
|
+
frontends; anyone else who likes the baseline can run it too.
|
|
19
|
+
|
|
20
|
+
Four modes, same checklist:
|
|
21
|
+
|
|
22
|
+
- **New project** — work down the checklist and set each piece up.
|
|
23
|
+
- **Audit** — read the repo (don't ask what it has), report present / missing / broken, change nothing until the human picks.
|
|
24
|
+
An established repo is where a forgotten piece hides. **The project wins:** where the repo already decided, that stands —
|
|
25
|
+
report what's _absent_; a working convention you'd have chosen differently is not a finding.
|
|
26
|
+
- **Align** — when the user asks to _align_ the repo with the baseline ("align"/"alignment" is the flag), the project-wins
|
|
27
|
+
guard lifts: deviations become migration findings, proposed as a plan, still nothing changed until approved.
|
|
28
|
+
- **One topic** — any other argument names a topic instead of a mode (`class names`, `prettier`, `tooling`). Resolve it to
|
|
29
|
+
the checklist rows it touches and run the audit process against those only, reading their references as usual. Say which
|
|
30
|
+
rows you resolved it to before reporting, and if nothing matches, say so and list the rows rather than guessing or sweeping
|
|
31
|
+
everything. Same output as an audit: findings, nothing changed until the human picks.
|
|
32
|
+
|
|
33
|
+
**Producer scope.** The web-ui repo itself produces this baseline; it is not a consumer frontend. Consumer-only rows — the
|
|
34
|
+
AGENTS.md fence, vendored-skill staleness, the app stack and app `src/` tree — don't apply there. Audit it against the shared
|
|
35
|
+
rows only: scripts, tooling, git, docs.
|
|
36
|
+
|
|
37
|
+
## The checklist
|
|
38
|
+
|
|
39
|
+
Each row is a reference in `../imf-web-ui-frontend-conventions/references/` — read it, then check the repo against it. A
|
|
40
|
+
topic argument narrows this table to the rows it names; every other mode works down all of it.
|
|
41
|
+
|
|
42
|
+
| Reference | Set up / audit |
|
|
43
|
+
| ---------------------- | ------------------------------------------------------------------------------------------- |
|
|
44
|
+
| `stack.md` | the dependencies match the topic→tool map; devtools siblings present |
|
|
45
|
+
| `npm-project.md` | script names table, `type`/`private`, exact pins, `.npmrc`, Node pinning |
|
|
46
|
+
| `tooling.md` | Prettier values, ESLint flat config, tsconfig, staged-file runner, readable CSS class names |
|
|
47
|
+
| `git.md` | `git:config` run and hooks directory present, verify scopes, staleness hooks |
|
|
48
|
+
| `project-structure.md` | the `src/` tree, file naming, `#/` alias wiring |
|
|
49
|
+
| `components.md` | component folders and colocation |
|
|
50
|
+
| `styling.md` | CSS Modules, tokens, no CSS-in-JS or utility framework |
|
|
51
|
+
| `docs-structure.md` | docs shape and content rules (see Docs below) |
|
|
52
|
+
| — agent tooling | delegated to `imf-web-ui-agent-setup` (see Agent tooling below) |
|
|
53
|
+
|
|
54
|
+
## Docs
|
|
55
|
+
|
|
56
|
+
`README.md`, `AGENTS.md`, and `docs/` with a `docs/index.md` that registers every doc. Scaffold from
|
|
57
|
+
[`templates/README.md`](templates/README.md) and [`templates/AGENTS.md`](templates/AGENTS.md); missing structure is a
|
|
58
|
+
finding.
|
|
59
|
+
|
|
60
|
+
Keep `AGENTS.md` lean. The decision test for every line: would the agent make a costly mistake without it? If it would just
|
|
61
|
+
need to read a file first, cut it — dev commands, path aliases, and tool config are discoverable from the files themselves.
|
|
62
|
+
|
|
63
|
+
`AGENTS.md` contains one installer-owned section: the `<!-- imf-web-ui:begin -->` … `<!-- imf-web-ui:end -->` fence.
|
|
64
|
+
`npx web-ui-install` refreshes what's inside on every skills install; everything outside the fence is the repo's own. A
|
|
65
|
+
missing fence in an existing `AGENTS.md` is a finding — without it the baseline note can't be kept current.
|
|
66
|
+
|
|
67
|
+
Judge existing docs only against `docs-structure.md`: repo-unique content stays, restated baseline becomes a pointer,
|
|
68
|
+
deviations get named as deviations. Don't rewrite a repo's docs uninvited — report, and let the human pick.
|
|
69
|
+
|
|
70
|
+
## Agent tooling
|
|
71
|
+
|
|
72
|
+
The agent side — vendored skills and their freshness, the lifecycle hooks, the settings registrations — is
|
|
73
|
+
`imf-web-ui-agent-setup`. Delegate to it: in a new project after the docs step, in an audit as one checklist row (skills
|
|
74
|
+
present and current, hooks wired or consciously adapted). Findings it produces report here like any other.
|
|
75
|
+
|
|
76
|
+
## Optional
|
|
77
|
+
|
|
78
|
+
Recommend when the shape calls for it; absence is not a finding.
|
|
79
|
+
|
|
80
|
+
- **knip** — once several people delete things independently.
|
|
81
|
+
- **`eslint-plugin-jsx-a11y`** — anything user-facing.
|
|
82
|
+
|
|
83
|
+
Out of scope, project-specific: CI, env and secrets, error tracking, deploy, dependency updates.
|
|
84
|
+
|
|
85
|
+
## Not this skill
|
|
86
|
+
|
|
87
|
+
- Library wiring (styles import, provider) → `imf-web-ui-library-setup`
|
|
88
|
+
- The conventions themselves → `imf-web-ui-frontend-conventions` and its references — this skill checks the structure exists,
|
|
89
|
+
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,36 @@
|
|
|
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. Load when installing
|
|
5
|
+
the library for the first time, or when components render unstyled or without theme context."
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# imf-web-ui-library-setup
|
|
9
|
+
|
|
10
|
+
This is library wiring: the styles import and the provider. It applies to anyone using `@imfusion/web-ui`.
|
|
11
|
+
|
|
12
|
+
If the project is an **ImFusion** frontend and this is first-time setup, mention once that `imf-web-ui-frontend-setup` sets
|
|
13
|
+
up or audits the repo's tooling (formatting, linting, hooks, scripts) against the ImFusion baseline, and let the human
|
|
14
|
+
decide. Offer it; never run it uninvited, and don't raise it again if they pass — the library works fine without any of it.
|
|
15
|
+
|
|
16
|
+
Every consumer entry point needs exactly two lines, in this order:
|
|
17
|
+
|
|
18
|
+
```tsx
|
|
19
|
+
import "@imfusion/web-ui/styles.css";
|
|
20
|
+
import { WebUIProvider, Button } from "@imfusion/web-ui";
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Wrap the app root in `<WebUIProvider>` once. Components rendered outside it won't have the theme/CSS-variable context they
|
|
24
|
+
expect.
|
|
25
|
+
|
|
26
|
+
Never import a Base UI (or other upstream) stylesheet or component directly — everything a web-ui component needs is already
|
|
27
|
+
inside `styles.css` and the package's own exports; reaching around web-ui to the upstream library is always wrong, even if
|
|
28
|
+
the upstream docs show it that way.
|
|
29
|
+
|
|
30
|
+
## Symptoms of a broken setup
|
|
31
|
+
|
|
32
|
+
- **Components render but look unstyled** — the `styles.css` import is missing from the entry point.
|
|
33
|
+
- **Components render but ignore the theme (wrong colors, no CSS variables resolving)** — they're mounted outside
|
|
34
|
+
`<WebUIProvider>`.
|
|
35
|
+
- **An integration component throws on import** — its optional peer dependency isn't installed; check the component's
|
|
36
|
+
description in the docgen index (`imf-web-ui-components`) for which peer to add to your `package.json`.
|
|
@@ -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-frontend-
|
|
14
|
+
`imf-web-ui-frontend-conventions`; project wiring lives in `imf-web-ui-library-setup`.
|
|
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-frontend-
|
|
85
|
+
`imf-web-ui-frontend-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-frontend-
|
|
91
|
-
file, not forty call sites.
|
|
90
|
+
movement across releases — prefer wrapping them once (see `imf-web-ui-frontend-conventions`) so a breaking change lands in
|
|
91
|
+
one file, not forty call sites.
|
|
92
92
|
|
|
93
93
|
## Deep dives
|
|
94
94
|
|
|
@@ -8,7 +8,7 @@ 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-frontend-
|
|
11
|
+
the same as owning the state: form state and validation belong to a form library (see `imf-web-ui-frontend-conventions`), and
|
|
12
12
|
these rules govern how its errors get presented.
|
|
13
13
|
|
|
14
14
|
## Structure
|
package/bin/install-skill.js
DELETED
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// Installs the consumer skill family (../src/llms/skills/imf-web-ui*/)
|
|
4
|
-
// into the consumer project's agent skill directories. Runs in the
|
|
5
|
-
// CONSUMER's environment, so it may only use this package's real runtime
|
|
6
|
-
// dependencies (@clack/prompts). Never wire this into a postinstall hook:
|
|
7
|
-
// ambient script execution on `npm install` is a live supply-chain attack
|
|
8
|
-
// vector — install stays an explicit, user-run command.
|
|
9
|
-
//
|
|
10
|
-
// The skills cross-reference each other, so they install as one bundle.
|
|
11
|
-
// When both targets are selected, .agents/skills/ holds the real copy and
|
|
12
|
-
// .claude/skills/ symlinks it (this repo's own convention) so the two
|
|
13
|
-
// can't drift apart.
|
|
14
|
-
|
|
15
|
-
import {
|
|
16
|
-
cpSync,
|
|
17
|
-
existsSync,
|
|
18
|
-
lstatSync,
|
|
19
|
-
mkdirSync,
|
|
20
|
-
readdirSync,
|
|
21
|
-
readFileSync,
|
|
22
|
-
rmSync,
|
|
23
|
-
symlinkSync,
|
|
24
|
-
writeFileSync
|
|
25
|
-
} from "node:fs";
|
|
26
|
-
import { dirname, relative, resolve } from "node:path";
|
|
27
|
-
import { fileURLToPath } from "node:url";
|
|
28
|
-
import * as p from "@clack/prompts";
|
|
29
|
-
|
|
30
|
-
const SKILL_PREFIX = "imf-web-ui";
|
|
31
|
-
const VERSION_MARKER = ".imf-web-ui-skill-version.json";
|
|
32
|
-
|
|
33
|
-
const here = dirname(fileURLToPath(import.meta.url));
|
|
34
|
-
// Source is relative to THIS SCRIPT's location (inside node_modules), not
|
|
35
|
-
// the consumer's cwd — the script is invoked from the consumer's project
|
|
36
|
-
// root, but the skills it copies ship alongside this file in the package.
|
|
37
|
-
const skillsRoot = resolve(here, "..", "src", "llms", "skills");
|
|
38
|
-
const packageJson = JSON.parse(readFileSync(resolve(here, "..", "package.json"), "utf-8"));
|
|
39
|
-
const currentVersion = packageJson.version;
|
|
40
|
-
|
|
41
|
-
// Destination is relative to the consumer's project root (cwd), since
|
|
42
|
-
// that's where their `.claude/` or `.agents/` directory lives.
|
|
43
|
-
const projectRoot = process.cwd();
|
|
44
|
-
|
|
45
|
-
const TARGETS = {
|
|
46
|
-
claude: { label: "Claude Code", root: resolve(projectRoot, ".claude", "skills") },
|
|
47
|
-
agents: { label: "Vendor-neutral (.agents/)", root: resolve(projectRoot, ".agents", "skills") }
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
function discoverSkills() {
|
|
51
|
-
if (!existsSync(skillsRoot)) return [];
|
|
52
|
-
return readdirSync(skillsRoot, { withFileTypes: true })
|
|
53
|
-
.filter(entry => entry.isDirectory() && entry.name.startsWith(SKILL_PREFIX))
|
|
54
|
-
.map(entry => entry.name)
|
|
55
|
-
.sort();
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function displayPath(absPath) {
|
|
59
|
-
return `./${relative(projectRoot, absPath)}`;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function readInstalledVersion(dir) {
|
|
63
|
-
const markerPath = resolve(dir, VERSION_MARKER);
|
|
64
|
-
if (!existsSync(markerPath)) return null;
|
|
65
|
-
try {
|
|
66
|
-
return JSON.parse(readFileSync(markerPath, "utf-8")).version ?? null;
|
|
67
|
-
} catch {
|
|
68
|
-
return null;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function writeRealCopy(sourceDir, destDir) {
|
|
73
|
-
mkdirSync(dirname(destDir), { recursive: true });
|
|
74
|
-
// force: true makes re-running after a version bump overwrite cleanly.
|
|
75
|
-
cpSync(sourceDir, destDir, { recursive: true, force: true });
|
|
76
|
-
writeFileSync(resolve(destDir, VERSION_MARKER), JSON.stringify({ version: currentVersion }, null, 2) + "\n");
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function writeSymlink(linkPath, targetPath) {
|
|
80
|
-
mkdirSync(dirname(linkPath), { recursive: true });
|
|
81
|
-
// lstat (not existsSync, which follows symlinks) catches a broken/stale
|
|
82
|
-
// symlink left over from a prior run, not just a real file or directory.
|
|
83
|
-
if (lstatSync(linkPath, { throwIfNoEntry: false })) {
|
|
84
|
-
rmSync(linkPath, { recursive: true, force: true });
|
|
85
|
-
}
|
|
86
|
-
symlinkSync(relative(dirname(linkPath), targetPath), linkPath);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// --target claude|agents (repeatable) selects targets without the
|
|
90
|
-
// interactive prompt — for CI and scripted installs.
|
|
91
|
-
function parseTargetFlags(argv) {
|
|
92
|
-
const targets = [];
|
|
93
|
-
for (let i = 0; i < argv.length; i++) {
|
|
94
|
-
if (argv[i] !== "--target") continue;
|
|
95
|
-
const value = argv[i + 1];
|
|
96
|
-
if (!value || !(value in TARGETS)) {
|
|
97
|
-
console.error(`--target expects one of: ${Object.keys(TARGETS).join(", ")}`);
|
|
98
|
-
process.exit(1);
|
|
99
|
-
}
|
|
100
|
-
targets.push(value);
|
|
101
|
-
i++;
|
|
102
|
-
}
|
|
103
|
-
return targets;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
async function main() {
|
|
107
|
-
const skills = discoverSkills();
|
|
108
|
-
const flagTargets = parseTargetFlags(process.argv.slice(2));
|
|
109
|
-
|
|
110
|
-
p.intro(`@imfusion/web-ui — install the ${SKILL_PREFIX} skills (${skills.length})`);
|
|
111
|
-
|
|
112
|
-
if (skills.length === 0) {
|
|
113
|
-
p.log.error(`No skills found at ${skillsRoot}. Reinstall @imfusion/web-ui and try again.`);
|
|
114
|
-
p.outro("Nothing installed.");
|
|
115
|
-
process.exitCode = 1;
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
const existingVersions = Object.values(TARGETS)
|
|
120
|
-
.flatMap(({ root }) => skills.map(name => readInstalledVersion(resolve(root, name))))
|
|
121
|
-
.filter(Boolean);
|
|
122
|
-
if (existingVersions.length > 0 && existingVersions.every(v => v === currentVersion)) {
|
|
123
|
-
p.log.info(`Already up to date (v${currentVersion}). Re-running will overwrite with the same content.`);
|
|
124
|
-
} else if (existingVersions.some(v => v !== currentVersion)) {
|
|
125
|
-
const from = existingVersions.find(v => v !== currentVersion);
|
|
126
|
-
p.log.info(`Updating installed skills from v${from} to v${currentVersion}.`);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
p.log.message(`Skills in this bundle:\n${skills.map(name => ` - ${name}`).join("\n")}`);
|
|
130
|
-
|
|
131
|
-
let selected;
|
|
132
|
-
if (flagTargets.length > 0) {
|
|
133
|
-
selected = flagTargets;
|
|
134
|
-
p.log.info(`Targets from --target flags: ${selected.join(", ")}`);
|
|
135
|
-
} else {
|
|
136
|
-
selected = await p.multiselect({
|
|
137
|
-
message: "Install into which skill directory (or directories)?",
|
|
138
|
-
options: Object.entries(TARGETS).map(([key, { label, root }]) => ({
|
|
139
|
-
value: key,
|
|
140
|
-
label,
|
|
141
|
-
hint: displayPath(root)
|
|
142
|
-
})),
|
|
143
|
-
required: true
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
if (p.isCancel(selected)) {
|
|
147
|
-
p.cancel("Cancelled — nothing installed.");
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
const both = selected.includes("claude") && selected.includes("agents");
|
|
153
|
-
|
|
154
|
-
for (const name of skills) {
|
|
155
|
-
const sourceDir = resolve(skillsRoot, name);
|
|
156
|
-
if (both) {
|
|
157
|
-
// .agents/ is the canonical real copy; .claude/ aliases it via symlink.
|
|
158
|
-
const realDir = resolve(TARGETS.agents.root, name);
|
|
159
|
-
writeRealCopy(sourceDir, realDir);
|
|
160
|
-
writeSymlink(resolve(TARGETS.claude.root, name), realDir);
|
|
161
|
-
} else {
|
|
162
|
-
for (const key of selected) {
|
|
163
|
-
writeRealCopy(sourceDir, resolve(TARGETS[key].root, name));
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
if (both) {
|
|
169
|
-
p.log.success(`Vendor-neutral (.agents/) -> ${displayPath(TARGETS.agents.root)}/${SKILL_PREFIX}*`);
|
|
170
|
-
p.log.success(`Claude Code -> ${displayPath(TARGETS.claude.root)}/${SKILL_PREFIX}* (symlinks -> .agents/)`);
|
|
171
|
-
} else {
|
|
172
|
-
for (const key of selected) {
|
|
173
|
-
p.log.success(`${TARGETS[key].label} -> ${displayPath(TARGETS[key].root)}/${SKILL_PREFIX}*`);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
p.outro(`Done — ${skills.length} skills installed.`);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
await main();
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: imf-web-ui-frontend-patterns
|
|
3
|
-
description:
|
|
4
|
-
"Raise the quality of frontend code written around @imfusion/web-ui — including quickly vibe-coded frontends. Library
|
|
5
|
-
boundary contract (tokens, CSS layers, type derivation), component roles, state placement, effects discipline, code
|
|
6
|
-
conventions (TypeScript, naming, file organisation, testing), and stack defaults. Load when writing wrapper components,
|
|
7
|
-
custom UI, styling beyond the defaults, adding new files to a consumer app, or choosing a routing, data-fetching, form, or
|
|
8
|
-
table library."
|
|
9
|
-
---
|
|
10
|
-
|
|
11
|
-
# imf-web-ui-frontend-patterns
|
|
12
|
-
|
|
13
|
-
One guard, once: **if the host project already has a convention — a styling system, a state library, a folder shape — the
|
|
14
|
-
project wins.** These defaults fill vacuums. They are not a license to refactor a consumer codebase toward this document.
|
|
15
|
-
|
|
16
|
-
Everything else below is how to build.
|
|
17
|
-
|
|
18
|
-
## Stay behind the library
|
|
19
|
-
|
|
20
|
-
Never import Base UI (or any other upstream this library wraps) directly — no upstream stylesheets, no upstream components,
|
|
21
|
-
even when upstream docs show it that way. Everything a component needs ships in `@imfusion/web-ui`. If the library is missing
|
|
22
|
-
something upstream has, report the gap (see `imf-web-ui-components`); don't reach around it.
|
|
23
|
-
|
|
24
|
-
## Style through the sanctioned seams
|
|
25
|
-
|
|
26
|
-
All library styles live in the `imf-ui.components` CSS layer, so **any plain selector you write wins** — that's the whole
|
|
27
|
-
override contract:
|
|
28
|
-
|
|
29
|
-
- Target the stable hooks: `data-imf-ui-component` attributes and your own classes/wrappers.
|
|
30
|
-
- Never target the library's internal class names — they are generated and change without notice.
|
|
31
|
-
- Never `!important` — if you think you need it, you're targeting the wrong thing.
|
|
32
|
-
|
|
33
|
-
## Build custom UI from tokens
|
|
34
|
-
|
|
35
|
-
Anything you build that the library doesn't cover — a stat widget, a custom panel — uses `--imf-ui-*` variables for color,
|
|
36
|
-
spacing, radius, and type instead of hardcoded values. That's what makes custom UI look native next to library components,
|
|
37
|
-
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
|
|
38
|
-
defect.
|
|
39
|
-
|
|
40
|
-
## Derive types, don't import them
|
|
41
|
-
|
|
42
|
-
Prop types come from the components themselves: `React.ComponentProps<typeof Button>`. The library deliberately exports no
|
|
43
|
-
`Props` types — don't look for them, and don't re-declare prop shapes by hand.
|
|
44
|
-
|
|
45
|
-
## Integrations own their peers
|
|
46
|
-
|
|
47
|
-
Components under `@imfusion/web-ui/integrations/*` depend on optional peers (e.g. `@tanstack/highlight` for `CodeHighlight`).
|
|
48
|
-
Add the peer explicitly to the consumer's `package.json` — never rely on hoisting.
|
|
49
|
-
|
|
50
|
-
## React patterns
|
|
51
|
-
|
|
52
|
-
The full treatment — component roles with an example, state placement, effects discipline, and the react.dev sources to
|
|
53
|
-
consult while building — lives in [references/react-patterns.md](references/react-patterns.md). Read it before writing new
|
|
54
|
-
screens or wrappers. The core in one breath:
|
|
55
|
-
|
|
56
|
-
- **Three roles.** Dumb components own how things look, layout components own arrangement, smart containers own data and
|
|
57
|
-
logic. Styling never lives in containers.
|
|
58
|
-
- **State lives where its truth lives.** URL → query cache → context → store → local state; walk the list, stop at the first
|
|
59
|
-
match.
|
|
60
|
-
- **Effects are a last resort**, and always extracted into purpose-named hooks.
|
|
61
|
-
- **Compose, don't configure.** If a component's prop list reads like a settings page, it wanted to be two or three
|
|
62
|
-
components.
|
|
63
|
-
|
|
64
|
-
## Everything else about the code
|
|
65
|
-
|
|
66
|
-
TypeScript, naming, where files go, and what's worth testing live in
|
|
67
|
-
[references/code-conventions.md](references/code-conventions.md). Read it when you're adding files rather than editing
|
|
68
|
-
existing ones — that's when these choices get made and then inherited by everything after.
|
|
69
|
-
|
|
70
|
-
The split between the two references: `react-patterns.md` covers **writing React** — component roles, where state lives,
|
|
71
|
-
effects discipline, composition. `code-conventions.md` covers **the code around it** — TypeScript, JS style, naming, file
|
|
72
|
-
layout, testing. Starting a new feature usually wants both.
|
|
73
|
-
|
|
74
|
-
## Stack defaults
|
|
75
|
-
|
|
76
|
-
TanStack is the default for the tooling around web-ui, whether the app is greenfield or you're adding one screen to something
|
|
77
|
-
that already exists. The ones you'll reach for most: **Router** (URL state, type-safe search params), **Query** (server
|
|
78
|
-
state), **Form** (form state), and **Table** for data grids, which you pair with web-ui's styled `Table` parts
|
|
79
|
-
(`Table.SortableHeaderCell` carries the sort glue). The suite goes wider than those four — check what exists before adding a
|
|
80
|
-
non-TanStack dependency. This is the stack the state ladder assumes.
|
|
81
|
-
|
|
82
|
-
`useState` is the right tool for local UI state, and most of it is local: whether a panel is open, which tab is active, a
|
|
83
|
-
draft value being typed, a hover flag. Keep those in the component and don't reach for a library.
|
|
84
|
-
|
|
85
|
-
The line is what the state is _for_, not how much of it there is. A library owns the layer once you find yourself rebuilding
|
|
86
|
-
what it does: validation timing and cross-field rules (**Form**), caching and refetching (**Query**), URL as the source of
|
|
87
|
-
truth (**Router**), sorting and pagination over rows (**Table**). web-ui ships none of that logic, and that absence is not an
|
|
88
|
-
argument for writing it yourself. Adding the library mid-project is normal and cheap; unpicking a hand-rolled version of it
|
|
89
|
-
later is not.
|
|
90
|
-
|
|
91
|
-
For best practices and patterns within any of these libraries, go to the library's own guidance rather than working from
|
|
92
|
-
memory: `npx @tanstack/cli` for docs. Where a project has wired up `@tanstack/intent`, use it to reach the Agent Skills its
|
|
93
|
-
TanStack dependencies ship, and read those too.
|