@imfusion/web-ui 0.5.1-dev.2.gf11bbed1 → 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 +119 -32
- 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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@imfusion/web-ui",
|
|
3
|
-
"version": "0.5.1-dev.
|
|
3
|
+
"version": "0.5.1-dev.20.g9170acaf",
|
|
4
4
|
"description": "The official Web UI component library for ImFusion web apps",
|
|
5
5
|
"author": "ImFusion GmbH",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -9,10 +9,17 @@
|
|
|
9
9
|
"access": "restricted"
|
|
10
10
|
},
|
|
11
11
|
"type": "module",
|
|
12
|
+
"engines": {
|
|
13
|
+
"node": ">=24"
|
|
14
|
+
},
|
|
15
|
+
"imports": {
|
|
16
|
+
"#/*": "./src/*",
|
|
17
|
+
"#/storybook/*": "./.storybook/*"
|
|
18
|
+
},
|
|
12
19
|
"module": "./dist/index.js",
|
|
13
20
|
"types": "./dist/index.d.ts",
|
|
14
21
|
"bin": {
|
|
15
|
-
"web-ui-install
|
|
22
|
+
"web-ui-install": "bin/install.js"
|
|
16
23
|
},
|
|
17
24
|
"exports": {
|
|
18
25
|
".": {
|
|
@@ -23,6 +30,10 @@
|
|
|
23
30
|
"types": "./dist/integrations/*/index.d.ts",
|
|
24
31
|
"import": "./dist/integrations/*.js"
|
|
25
32
|
},
|
|
33
|
+
"./build/*": {
|
|
34
|
+
"types": "./dist/build/*/index.d.ts",
|
|
35
|
+
"import": "./dist/build/*.js"
|
|
36
|
+
},
|
|
26
37
|
"./styles.css": "./dist/style.css",
|
|
27
38
|
"./docgen.json": "./src/docgen/doc.gen.json",
|
|
28
39
|
"./llms.txt": "./src/llms/llms.gen.txt",
|
|
@@ -36,37 +47,35 @@
|
|
|
36
47
|
"bin"
|
|
37
48
|
],
|
|
38
49
|
"scripts": {
|
|
39
|
-
"codegen:docgen": "tsx src/docgen/gen-docgen.ts",
|
|
40
|
-
"codegen:llms": "tsx src/llms/gen-llms.ts",
|
|
41
|
-
"codegen:watch": "tsx watch src/codegen/run.ts",
|
|
42
|
-
"codegen:storybook": "tsx src/codegen/run.ts --storybook",
|
|
43
|
-
"codegen": "tsx src/codegen/run.ts",
|
|
44
|
-
"build": "npm run codegen && rm -rf dist && vite build && npm run codegen:docgen && npm run codegen:llms",
|
|
45
50
|
"dev": "tsx scripts/dev.ts",
|
|
46
51
|
"dev:host": "tsx scripts/dev.ts --host",
|
|
47
52
|
"dev:lib": "concurrently -n codegen,lib -c yellow,blue \"npm run codegen:watch\" \"vite build --watch\"",
|
|
48
|
-
"
|
|
53
|
+
"build": "npm run codegen && rm -rf dist && vite build && npm run codegen:docgen && npm run codegen:llms",
|
|
54
|
+
"build:storybook": "npm run codegen:storybook && storybook build",
|
|
55
|
+
"storybook": "npm run codegen:storybook && storybook dev -p 6006 --no-open",
|
|
56
|
+
"storybook:free-ports": "tsx scripts/free-storybook-ports.ts",
|
|
57
|
+
"codegen": "tsx src/codegen/run.ts",
|
|
58
|
+
"codegen:watch": "tsx watch src/codegen/run.ts",
|
|
59
|
+
"codegen:storybook": "tsx src/codegen/run.ts --storybook",
|
|
60
|
+
"codegen:docgen": "tsx src/docgen/gen-docgen.ts",
|
|
61
|
+
"codegen:llms": "tsx src/llms/gen-llms.ts",
|
|
62
|
+
"verify:format": "prettier --check .",
|
|
63
|
+
"verify:lint": "eslint . --cache --max-warnings=0",
|
|
64
|
+
"verify:typecheck": "npm run codegen && tsc -p tsconfig.lib.json --noEmit && tsc -p .storybook/tsconfig.json --noEmit",
|
|
65
|
+
"verify:tests": "vitest run",
|
|
66
|
+
"verify:deps": "tsx scripts/check-pinned-dependencies.ts",
|
|
67
|
+
"verify:staged": "tsx scripts/verify/staged.ts",
|
|
68
|
+
"verify:full": "tsx scripts/verify/full.ts && npm run verify:tests",
|
|
49
69
|
"format": "prettier --write .",
|
|
50
|
-
"
|
|
51
|
-
"lint": "eslint . --cache --max-warnings=0",
|
|
52
|
-
"lint:fix": "eslint . --cache --fix",
|
|
70
|
+
"lint": "eslint . --cache --fix",
|
|
53
71
|
"lint:inspect": "npx @eslint/config-inspector@latest",
|
|
72
|
+
"test:unit": "vitest run --project unit",
|
|
73
|
+
"test:stories": "vitest run --project storybook",
|
|
74
|
+
"test:watch": "vitest",
|
|
54
75
|
"skills:eval": "node src/llms/evals/run.mjs",
|
|
55
76
|
"skills:eval:dev": "WEB_UI_SKILL_EVAL_ROOT=/private/tmp/web-ui-dev-skill-evals WEB_UI_SKILL_EVAL_RESULTS_DIR=.agents/evals/results WEB_UI_SKILL_EVAL_REPORT_PATH=.agents/evals/REPORT.md WEB_UI_SKILL_EVAL_SKILL_FAMILY=web-ui-dev WEB_UI_SKILL_EVAL_WORKSPACE_LABEL='development repository' node src/llms/evals/run.mjs --scenarios-dir .agents/evals/scenarios --setup .agents/evals/setup-env.sh",
|
|
56
77
|
"skills:eval:compare": "node src/llms/evals/compare.mjs",
|
|
57
78
|
"skills:eval:report": "node src/llms/evals/report.mjs",
|
|
58
|
-
"storybook": "npm run codegen:storybook && storybook dev -p 6006 --no-open",
|
|
59
|
-
"storybook:free-ports": "tsx scripts/free-storybook-ports.ts",
|
|
60
|
-
"build:storybook": "npm run codegen:storybook && storybook build",
|
|
61
|
-
"test": "vitest run",
|
|
62
|
-
"test:unit": "vitest run --project unit",
|
|
63
|
-
"test:stories": "vitest run --project storybook",
|
|
64
|
-
"test:watch": "vitest",
|
|
65
|
-
"verify:preflight": "tsx scripts/verify/full.ts",
|
|
66
|
-
"verify:full": "npm run verify:preflight && npm run test",
|
|
67
|
-
"worktree:create": "tsx scripts/worktrees.ts create",
|
|
68
|
-
"worktree:close": "tsx scripts/worktrees.ts close",
|
|
69
|
-
"worktree:discard": "tsx scripts/worktrees.ts discard",
|
|
70
79
|
"knip": "knip",
|
|
71
80
|
"git:config": "git config core.hooksPath .githooks && git config pull.rebase true && git config merge.ff only"
|
|
72
81
|
},
|
|
@@ -124,6 +133,7 @@
|
|
|
124
133
|
"globals": "15.15.0",
|
|
125
134
|
"jiti": "2.7.0",
|
|
126
135
|
"knip": "6.14.2",
|
|
136
|
+
"lightningcss": "1.32.0",
|
|
127
137
|
"nano-staged": "1.0.2",
|
|
128
138
|
"playwright": "1.60.0",
|
|
129
139
|
"prettier": "3.8.3",
|
package/src/docgen/doc.gen.json
CHANGED
|
@@ -1281,7 +1281,7 @@
|
|
|
1281
1281
|
{
|
|
1282
1282
|
"name": "variant",
|
|
1283
1283
|
"required": false,
|
|
1284
|
-
"type": "| \"inherit\"\n| \"main\"\n| \"support\"\n| \"minor\"\n| \"brand\"\n| null",
|
|
1284
|
+
"type": "| \"inherit\"\n| \"main\"\n| \"support\"\n| \"minor\"\n| \"brand\"\n| \"oncolor\"\n| null",
|
|
1285
1285
|
"defaultValue": "brand",
|
|
1286
1286
|
"description": ""
|
|
1287
1287
|
}
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
name: imf-web-ui
|
|
3
3
|
description:
|
|
4
4
|
"Entry point for UI work in a project that depends on @imfusion/web-ui. Decides whether guidance is needed at all, then
|
|
5
|
-
routes to the right companion skill — component reference, UX guidance, or frontend
|
|
6
|
-
UI in a consumer repo."
|
|
5
|
+
routes to the right companion skill — component reference, UX guidance, or frontend conventions. Load when adding or
|
|
6
|
+
editing UI in a consumer repo."
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# imf-web-ui
|
|
@@ -24,16 +24,17 @@ Guidance skills exist to fill gaps, not to add ceremony to clear tasks.
|
|
|
24
24
|
|
|
25
25
|
## Routing
|
|
26
26
|
|
|
27
|
-
| The task at hand
|
|
28
|
-
|
|
|
29
|
-
| Using a specific component; checking props, sub-components, or defaults
|
|
30
|
-
| First-time setup, adding a library dependency, or components rendering unstyled/broken
|
|
31
|
-
| Building/reshaping a screen or flow; choosing between components; layout, density, hierarchy, states
|
|
32
|
-
| Writing wrappers or custom UI; styling beyond defaults; adding files; TypeScript, naming, testing
|
|
33
|
-
| Setting up or auditing an **ImFusion** repo's tooling: formatting, linting, hooks, scripts, structure
|
|
27
|
+
| The task at hand | Open |
|
|
28
|
+
| --------------------------------------------------------------------------------------------------------- | --------------------------------- |
|
|
29
|
+
| Using a specific component; checking props, sub-components, or defaults | `imf-web-ui-components` |
|
|
30
|
+
| First-time setup, adding a library dependency, or components rendering unstyled/broken | `imf-web-ui-library-setup` |
|
|
31
|
+
| Building/reshaping a screen or flow; choosing between components; layout, density, hierarchy, states | `imf-web-ui-ux` |
|
|
32
|
+
| Writing wrappers or custom UI; styling beyond defaults; adding files; TypeScript, naming, testing | `imf-web-ui-frontend-conventions` |
|
|
33
|
+
| Setting up or auditing an **ImFusion** repo's tooling: formatting, linting, hooks, scripts, structure | `imf-web-ui-frontend-setup` |
|
|
34
|
+
| Setting up or refreshing agent tooling: vendored skills, lifecycle hooks, dependency-shipped Agent Skills | `imf-web-ui-agent-setup` |
|
|
34
35
|
|
|
35
|
-
The
|
|
36
|
-
asked to add one config file is just that edit; make it, and don't open a skill to do so.
|
|
36
|
+
The two setup rows are narrow on purpose. They're for "what is this project missing?" — a question about the repo as a whole.
|
|
37
|
+
Being asked to add one config file is just that edit; make it, and don't open a skill to do so.
|
|
37
38
|
|
|
38
39
|
Tasks routinely span two: building a screen usually means `imf-web-ui-ux` for the shape and `imf-web-ui-components` for the
|
|
39
40
|
APIs. That's normal — open both, in that order.
|
|
@@ -43,6 +44,9 @@ APIs. That's normal — open both, in that order.
|
|
|
43
44
|
TanStack is the recommended tooling library — routing, server state, forms, tables. When a task needs one of those and the
|
|
44
45
|
project has no incumbent, propose it, and read the library's own docs (`npx @tanstack/cli`) rather than working from memory.
|
|
45
46
|
|
|
47
|
+
Much of the suite also ships Agent Skills inside the package, reachable via TanStack Intent. Wiring that up is
|
|
48
|
+
`imf-web-ui-agent-setup`.
|
|
49
|
+
|
|
46
50
|
## When to interview the human
|
|
47
51
|
|
|
48
52
|
`imf-web-ui-ux` contains a short per-feature interview. Run it **only** when both hold:
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: imf-web-ui-agent-setup
|
|
3
|
+
description:
|
|
4
|
+
"Install or update the LLM/agent tooling @imfusion/web-ui ships in a consumer repo: the vendored imf-web-ui-* skills, the
|
|
5
|
+
AGENTS.md fence, the agent lifecycle hooks, and the staleness wiring. Also covers reaching the Agent Skills a dependency
|
|
6
|
+
ships — TanStack Intent (@tanstack/intent) and its allowlist. Wraps npx web-ui-install and adds the judgment the binary
|
|
7
|
+
can't have — reading what the repo already registers before adding anything. Load when setting up agent tooling in a
|
|
8
|
+
consumer app, when skills are stale, when hooks should be installed or adapted, or when asked to set up TanStack Intent or
|
|
9
|
+
dependency-shipped Agent Skills. Not for project tooling (imf-web-ui-frontend-setup) or library wiring
|
|
10
|
+
(imf-web-ui-library-setup)."
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# imf-web-ui-agent-setup
|
|
14
|
+
|
|
15
|
+
Sets up or updates the agent side of a consumer repo. The binary does the mechanics; this skill does the judgment.
|
|
16
|
+
|
|
17
|
+
## Skills
|
|
18
|
+
|
|
19
|
+
`npx web-ui-install` installs or refreshes the vendored `imf-web-ui-*` skills (interactive target choice on first run,
|
|
20
|
+
remembered after; `--reconfigure` re-opens it). It also refreshes the `<!-- imf-web-ui:begin/end -->` fence in an existing
|
|
21
|
+
`AGENTS.md`. Stale check: `.imf-web-ui-skill-version.json` in each installed skill vs. the installed package version — older
|
|
22
|
+
marker means re-run the binary, never hand-diff skill contents.
|
|
23
|
+
|
|
24
|
+
## Hooks
|
|
25
|
+
|
|
26
|
+
Three agent lifecycle hooks ship as templates in [`templates/hooks/`](templates/hooks/), registration in
|
|
27
|
+
[`templates/settings.json`](templates/settings.json):
|
|
28
|
+
|
|
29
|
+
- **SessionStart** — once per session: the conventions baseline is vendored, load it before writing.
|
|
30
|
+
- **UserPromptSubmit** — a one-line conventions reminder per prompt. Per-prompt, not per-tool-call: a PreToolUse reminder
|
|
31
|
+
would re-inject the same text on every edit.
|
|
32
|
+
- **PostToolUse** (advisory) — file-scope checks on the touched file after every Edit/Write, failures fed straight back.
|
|
33
|
+
Never exits non-zero.
|
|
34
|
+
- **`baseline-staleness.sh`** — not registered as an agent hook; the repo's pre-commit calls it
|
|
35
|
+
([`git.md`](../imf-web-ui-frontend-conventions/references/git.md), Staleness at commit time).
|
|
36
|
+
|
|
37
|
+
**Read `.claude/settings.json` before installing.** Then pick per event:
|
|
38
|
+
|
|
39
|
+
- **Nothing registered** → install as shipped: `npx web-ui-install --hooks` copies the scripts to `.agents/hooks/imf-web-ui/`
|
|
40
|
+
(installer-owned, refreshed wholesale) and merges the registrations without touching existing entries.
|
|
41
|
+
- **The repo already covers an event** (its own session reminder, its own post-edit verify) → don't stack a second hook on
|
|
42
|
+
it. Use the template as a starting point instead: fold the missing behaviour into the repo's existing script, or adapt the
|
|
43
|
+
template and register that. Surface the situation and let the human pick.
|
|
44
|
+
|
|
45
|
+
## Dependency-shipped skills
|
|
46
|
+
|
|
47
|
+
Some libraries ship Agent Skills inside their npm package; TanStack does across much of the suite.
|
|
48
|
+
[TanStack Intent](https://github.com/TanStack/intent) (`@tanstack/intent`) is the CLI that surfaces them — an agent holding a
|
|
49
|
+
dependency but not its guidance writes plausible code against a half-remembered API.
|
|
50
|
+
|
|
51
|
+
Setting it up is the project's own call, not something web-ui does on its behalf. Point it out:
|
|
52
|
+
|
|
53
|
+
> This project has TanStack dependencies that ship their own Agent Skills. `@tanstack/intent` can make them reachable — worth
|
|
54
|
+
> a look if you want your agent working from the library's own guidance.
|
|
55
|
+
|
|
56
|
+
Intent offers two things: a fenced instructions block in `AGENTS.md`, and a `PreToolUse` hook that blocks an edit until a
|
|
57
|
+
matching skill has been read. The house preference is both — the block alone is advice an agent can walk past. The hook
|
|
58
|
+
refuses every edit while no matching skill is loadable, so a project adopting it wants the current docs open; that sequencing
|
|
59
|
+
belongs to whoever runs it.
|
|
60
|
+
|
|
61
|
+
**Allowlist the whole scope.** `intent.skills` takes `@tanstack/*`. Per-package entries like `@tanstack/react-query` look
|
|
62
|
+
more precise and silently reach almost nothing, because the skills ship from packages the app never names directly. Exclude
|
|
63
|
+
`@tanstack/devtools-event-client` — it publishes skills that are noise in an app repo.
|
|
64
|
+
|
|
65
|
+
Read Intent's own docs before wiring it; it is young and moves. Whatever the project decides, guidance you didn't read is not
|
|
66
|
+
guidance you have: use `npx @tanstack/cli` for TanStack docs, and never guess at a skill name.
|
|
67
|
+
|
|
68
|
+
## Not this skill
|
|
69
|
+
|
|
70
|
+
- Project tooling, docs structure, the audit checklist → `imf-web-ui-frontend-setup` (which delegates agent tooling here)
|
|
71
|
+
- Library wiring (styles import, provider) → `imf-web-ui-library-setup`
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
# Advisory baseline-staleness check, meant to be called from the repo's pre-commit hook.
|
|
3
|
+
# Compares the installed imf-web-ui skill markers against the installed package version
|
|
4
|
+
# and warns on drift. Always exits 0 — staleness never blocks a commit.
|
|
5
|
+
PKG=node_modules/@imfusion/web-ui/package.json
|
|
6
|
+
[ -f "$PKG" ] || exit 0
|
|
7
|
+
CURRENT=$(node -p "require('./$PKG').version" 2>/dev/null) || exit 0
|
|
8
|
+
|
|
9
|
+
for marker in .agents/skills/imf-web-ui-*/.imf-web-ui-skill-version.json; do
|
|
10
|
+
[ -f "$marker" ] || continue
|
|
11
|
+
INSTALLED=$(node -p "require('./$marker').version" 2>/dev/null) || continue
|
|
12
|
+
if [ "$INSTALLED" != "$CURRENT" ]; then
|
|
13
|
+
echo "imf-web-ui skills are stale ($INSTALLED installed, package is $CURRENT) — run: npx web-ui-install"
|
|
14
|
+
break
|
|
15
|
+
fi
|
|
16
|
+
done
|
|
17
|
+
exit 0
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
# PostToolUse (Edit|Write): advisory file-scope checks on the touched file. Never exits
|
|
3
|
+
# non-zero, so a mid-flight refactor can't trap the agent. Resolve the repo from the edited
|
|
4
|
+
# file, not cwd, which is not guaranteed for hooks.
|
|
5
|
+
FILE=$(jq -r '.tool_input.file_path // empty' 2>/dev/null)
|
|
6
|
+
[ -f "$FILE" ] || exit 0
|
|
7
|
+
|
|
8
|
+
ROOT=$(cd "$(dirname "$FILE")" && git rev-parse --show-toplevel 2>/dev/null) || exit 0
|
|
9
|
+
cd "$ROOT" || exit 0
|
|
10
|
+
|
|
11
|
+
case "$FILE" in
|
|
12
|
+
*.ts | *.tsx | *.js | *.jsx)
|
|
13
|
+
[ -x ./node_modules/.bin/eslint ] && ./node_modules/.bin/eslint --cache "$FILE" 2>&1
|
|
14
|
+
;;
|
|
15
|
+
esac
|
|
16
|
+
case "$FILE" in
|
|
17
|
+
*.ts | *.tsx | *.js | *.jsx | *.css | *.json | *.md)
|
|
18
|
+
[ -x ./node_modules/.bin/prettier ] && ./node_modules/.bin/prettier --check "$FILE" 2>&1
|
|
19
|
+
;;
|
|
20
|
+
esac
|
|
21
|
+
exit 0
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
# SessionStart: point the agent at the vendored conventions baseline once per session.
|
|
3
|
+
echo "The ImFusion frontend conventions baseline is vendored in .agents/skills/ (imf-web-ui-frontend-conventions and siblings). Load the relevant skill before writing code, styles, or docs; repo docs in docs/ carry only what is unique to this repo."
|
|
4
|
+
exit 0
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/claude-code-settings.json",
|
|
3
|
+
"hooks": {
|
|
4
|
+
"SessionStart": [
|
|
5
|
+
{
|
|
6
|
+
"hooks": [
|
|
7
|
+
{
|
|
8
|
+
"type": "command",
|
|
9
|
+
"command": "\"$CLAUDE_PROJECT_DIR/.agents/hooks/imf-web-ui/session-start.sh\""
|
|
10
|
+
}
|
|
11
|
+
]
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"UserPromptSubmit": [
|
|
15
|
+
{
|
|
16
|
+
"hooks": [
|
|
17
|
+
{
|
|
18
|
+
"type": "command",
|
|
19
|
+
"command": "\"$CLAUDE_PROJECT_DIR/.agents/hooks/imf-web-ui/user-prompt-submit.sh\""
|
|
20
|
+
}
|
|
21
|
+
]
|
|
22
|
+
}
|
|
23
|
+
],
|
|
24
|
+
"PostToolUse": [
|
|
25
|
+
{
|
|
26
|
+
"matcher": "Edit|MultiEdit|Write",
|
|
27
|
+
"hooks": [
|
|
28
|
+
{
|
|
29
|
+
"type": "command",
|
|
30
|
+
"command": "\"$CLAUDE_PROJECT_DIR/.agents/hooks/imf-web-ui/post-tool-use.sh\"",
|
|
31
|
+
"statusMessage": "Verifying file"
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
]
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -3,7 +3,7 @@ name: imf-web-ui-components
|
|
|
3
3
|
description:
|
|
4
4
|
"Look up @imfusion/web-ui component APIs without reading their source: the two-hop lookup (identity index -> prop data),
|
|
5
5
|
compound components, and integrations. Load when you need the props, sub-components, or defaults of a specific component —
|
|
6
|
-
not for choosing between components (imf-web-ui-ux) or first-time setup (imf-web-ui-setup)."
|
|
6
|
+
not for choosing between components (imf-web-ui-ux) or first-time setup (imf-web-ui-library-setup)."
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# imf-web-ui-components
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: imf-web-ui-frontend-conventions
|
|
3
|
+
description:
|
|
4
|
+
"The ImFusion frontend conventions baseline — in-house conventions, valid in every ImFusion frontend and usable by anyone
|
|
5
|
+
who likes them. A router over topic references: library boundary, React, components, TypeScript, styling, data layer,
|
|
6
|
+
project structure, testing, stack, npm project, tooling config, git, assets, docs structure. Load when writing wrapper
|
|
7
|
+
components, custom UI, styling beyond the defaults, adding new files to a consumer app, writing repo docs, touching tool
|
|
8
|
+
config, or choosing any dependency."
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# imf-web-ui-frontend-conventions
|
|
12
|
+
|
|
13
|
+
The ImFusion frontend baseline. In-house conventions, not industry claims — they encode how ImFusion frontends are built, and
|
|
14
|
+
anyone else is welcome to them.
|
|
15
|
+
|
|
16
|
+
**The project wins.** These defaults fill vacuums: if the host project already has a convention — a styling system, a state
|
|
17
|
+
library, a folder shape — that stands. They are not a license to refactor a consumer codebase toward this document.
|
|
18
|
+
|
|
19
|
+
**Alignment mode.** The exception is intent: when the user asks to _align_ the repo with the baseline ("align", "alignment"
|
|
20
|
+
is the flag), the project-wins guard lifts. Deviations then become migration findings — proposed as a plan, still nothing
|
|
21
|
+
changed until the human approves.
|
|
22
|
+
|
|
23
|
+
## The references
|
|
24
|
+
|
|
25
|
+
Each topic lives in one reference. Read the one whose moment you're in; starting a new feature usually wants several.
|
|
26
|
+
|
|
27
|
+
| Reference | Covers | Read when |
|
|
28
|
+
| ------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------- |
|
|
29
|
+
| [library-boundary.md](references/library-boundary.md) | staying behind `@imfusion/web-ui`, wrappers, type derivation, peers | touching anything that renders library components |
|
|
30
|
+
| [react.md](references/react.md) | component roles, state kinds and owners, effects discipline | writing new screens, components, or wrappers |
|
|
31
|
+
| [components.md](references/components.md) | component anatomy on disk: grouping, folders, colocation | adding a component file |
|
|
32
|
+
| [typescript.md](references/typescript.md) | functional style, types, naming | writing any code |
|
|
33
|
+
| [styling.md](references/styling.md) | native CSS Modules, tokens, the override contract | writing CSS or styling beyond the defaults |
|
|
34
|
+
| [class-names.md](references/class-names.md) | CVA variants, `cx`, merging the incoming `className` | writing a component with variants or a `className` prop |
|
|
35
|
+
| [data.md](references/data.md) | `api/`+`http/` shape, Zod boundary, query/mutation patterns | adding an API topic, a fetch, or a mutation |
|
|
36
|
+
| [project-structure.md](references/project-structure.md) | the `src/` tree, file naming, imports | adding files rather than editing existing ones |
|
|
37
|
+
| [testing.md](references/testing.md) | what's worth testing and what isn't | writing or reviewing tests |
|
|
38
|
+
| [stack.md](references/stack.md) | the topic→tool map, when a library owns a layer | choosing or adding any dependency |
|
|
39
|
+
| [npm-project.md](references/npm-project.md) | `package.json`, the `verify:*` script set, dependency pinning | running or adding a script, adding a dependency |
|
|
40
|
+
| [tooling.md](references/tooling.md) | Prettier, ESLint, tsconfig, staged files, CSS class names | touching tool config |
|
|
41
|
+
| [git.md](references/git.md) | `git:config`, verify scopes, staleness at commit time | wiring hooks or the commit path |
|
|
42
|
+
| [assets.md](references/assets.md) | image formats, the WebP recipe | adding images or other static assets |
|
|
43
|
+
| [docs-structure.md](references/docs-structure.md) | what a repo documents, where, how it's written | writing or restructuring repo docs |
|
|
44
|
+
|
|
45
|
+
`imf-web-ui-frontend-setup` bootstraps and audits a repo against this baseline.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Assets
|
|
2
|
+
|
|
3
|
+
Import everything from `src/assets/` so the bundler fingerprints and bundles it. Never reference an image by public-path
|
|
4
|
+
string.
|
|
5
|
+
|
|
6
|
+
## Photographs — WebP
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
magick source.png -resize 2000x -quality 80 -define webp:method=6 src/assets/name.webp
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
- Quality 80 is visually lossless on photos and routinely cuts file size by an order of magnitude.
|
|
13
|
+
- `method=6` is the densest encoding. It's a one-off cost at conversion time, so take the smaller file.
|
|
14
|
+
- Cap the long edge at 2000px — nothing on the market resolves more in a content image.
|
|
15
|
+
- No `<picture>` fallback needed; WebP is supported everywhere since 2020.
|
|
16
|
+
|
|
17
|
+
## Other formats
|
|
18
|
+
|
|
19
|
+
- **Alpha, or pixels that must stay exact** → PNG, optimized with `oxipng` or `pngquant`.
|
|
20
|
+
- **Icons, logos, line art** → inline SVG component, so it inherits `currentColor` and follows the theme.
|
|
21
|
+
|
|
22
|
+
These rules apply to assets as they're added or touched. Existing assets in another format are not findings to sweep —
|
|
23
|
+
convert opportunistically, when you're working on them anyway.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Class names in components
|
|
2
|
+
|
|
3
|
+
How a component turns props into a `className` string. What the build does with the result is [tooling.md](tooling.md); what
|
|
4
|
+
goes in the stylesheet is [styling.md](styling.md).
|
|
5
|
+
|
|
6
|
+
**CVA is the only tool.** `class-variance-authority` maps variant props to CSS Module classes. No `clsx`, no
|
|
7
|
+
`tailwind-merge`, no local `cn()` helper. `cx` is CVA's own concatenator and `@imfusion/web-ui` re-exports it, so it comes
|
|
8
|
+
from the library alongside the components.
|
|
9
|
+
|
|
10
|
+
```tsx
|
|
11
|
+
import { cva } from "class-variance-authority";
|
|
12
|
+
import classes from "./chip.module.css";
|
|
13
|
+
|
|
14
|
+
const chip = cva(classes.root, {
|
|
15
|
+
variants: {
|
|
16
|
+
appearance: { outline: classes.appearanceOutline, solid: classes.appearanceSolid },
|
|
17
|
+
inline: { false: null, true: classes.inline }
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
export function Chip({ appearance = "outline", inline = false, className, ...props }: Props) {
|
|
22
|
+
return <span {...props} className={chip({ appearance, inline, className })} />;
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
- **Base class is CVA's first argument**, variant values are CSS Module references, never string literals. A boolean axis
|
|
27
|
+
uses `null` for its off-state.
|
|
28
|
+
- **The incoming `className` goes into CVA's `className` slot**, which appends it last so a caller's class always wins. With
|
|
29
|
+
no variants to map, `cx(classes.inline, className)` does the same job.
|
|
30
|
+
- **Every component that accepts `className` merges it.** A component whose surface is deliberately closed omits the prop
|
|
31
|
+
entirely rather than accepting and ignoring it.
|
|
32
|
+
- **Defaults live in the props destructuring, not CVA's `defaultVariants`.** react-docgen-typescript reads the destructuring,
|
|
33
|
+
so defaults declared in CVA don't reach the generated docs.
|
|
34
|
+
- **Resolve a function-form `className` before merging.** Components built on a library that passes render state
|
|
35
|
+
(`className={state => …}`) receive either shape:
|
|
36
|
+
|
|
37
|
+
```tsx
|
|
38
|
+
className={state => cx(classes.root, typeof className === "function" ? className(state) : className)}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
- **Two components sharing one visual share one CVA module**, a `{name}.cva.ts` next to them, so their variant axes can't
|
|
42
|
+
drift apart.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Components
|
|
2
|
+
|
|
3
|
+
How a component lives on disk and how dumb it stays. Roles, state, and effects in full are [react.md](react.md); styling is
|
|
4
|
+
[styling.md](styling.md).
|
|
5
|
+
|
|
6
|
+
## As dumb as possible
|
|
7
|
+
|
|
8
|
+
Data comes from outside — props-fed, never self-fetching. Presentational logic (formatting a label, deriving a display state)
|
|
9
|
+
is fine; owning data is not:
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
// components/user-card/user-card.tsx
|
|
13
|
+
interface Props {
|
|
14
|
+
name: string;
|
|
15
|
+
role: string;
|
|
16
|
+
onEdit: () => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function UserCard({ name, role, onEdit }: Props) {
|
|
20
|
+
return (
|
|
21
|
+
<Card.Root>
|
|
22
|
+
<Card.Content>
|
|
23
|
+
<Typo>{name}</Typo>
|
|
24
|
+
<Chip>{role}</Chip>
|
|
25
|
+
</Card.Content>
|
|
26
|
+
<Card.Footer>
|
|
27
|
+
<Button onClick={onEdit}>Edit</Button>
|
|
28
|
+
</Card.Footer>
|
|
29
|
+
</Card.Root>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
No query hook, no store access, no route awareness — the component renders what it's given and reports events upward. Local
|
|
35
|
+
UI state (`isOpen`, a draft value) is allowed; anything whose truth lives elsewhere is not ([react.md](react.md)).
|
|
36
|
+
|
|
37
|
+
## Grouping
|
|
38
|
+
|
|
39
|
+
`components/` groups by kind — a layout component under `layouts/`, not beside a domain widget. Flat is fine while there are
|
|
40
|
+
few; let the grouping follow what the project actually has rather than imposing it up front. The kinds worth separating are
|
|
41
|
+
the component roles in [react.md](react.md).
|
|
42
|
+
|
|
43
|
+
## One file or a folder
|
|
44
|
+
|
|
45
|
+
A component gets a folder once it has more than one file — sub-components, styles, tests — with an `index.ts` that only
|
|
46
|
+
re-exports:
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
components/data-table/
|
|
50
|
+
data-table.tsx
|
|
51
|
+
data-table-row.tsx
|
|
52
|
+
data-table.module.css
|
|
53
|
+
index.ts
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Imports then read `#/components/data-table` and the inside can be restructured without touching call sites. Single-file
|
|
57
|
+
components stay single files, directly in their group.
|
|
58
|
+
|
|
59
|
+
## Colocation
|
|
60
|
+
|
|
61
|
+
The stylesheet is colocated as `<component>.module.css` next to the component, and only dumb components have one — a
|
|
62
|
+
container that wants CSS is asking for a layout component instead ([react.md](react.md)). Tests and local types sit next to
|
|
63
|
+
their subject too. A file you have to hunt for in a parallel tree gets edited less carefully.
|