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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/README.md +175 -40
  2. package/bin/install.js +319 -0
  3. package/bin/install.test.ts +139 -0
  4. package/dist/components/logo/logo.d.ts +1 -1
  5. package/dist/index.js +3 -1
  6. package/dist/style.css +1 -1
  7. package/package.json +30 -22
  8. package/src/docgen/doc.gen.json +1 -1
  9. package/src/llms/skills/imf-web-ui/SKILL.md +17 -8
  10. package/src/llms/skills/imf-web-ui-agent-setup/SKILL.md +46 -0
  11. package/src/llms/skills/imf-web-ui-agent-setup/templates/hooks/baseline-staleness.sh +17 -0
  12. package/src/llms/skills/imf-web-ui-agent-setup/templates/hooks/post-tool-use.sh +21 -0
  13. package/src/llms/skills/imf-web-ui-agent-setup/templates/hooks/session-start.sh +4 -0
  14. package/src/llms/skills/imf-web-ui-agent-setup/templates/hooks/user-prompt-submit.sh +4 -0
  15. package/src/llms/skills/imf-web-ui-agent-setup/templates/settings.json +37 -0
  16. package/src/llms/skills/imf-web-ui-components/SKILL.md +1 -1
  17. package/src/llms/skills/imf-web-ui-frontend-conventions/SKILL.md +44 -0
  18. package/src/llms/skills/imf-web-ui-frontend-conventions/references/assets.md +23 -0
  19. package/src/llms/skills/imf-web-ui-frontend-conventions/references/components.md +63 -0
  20. package/src/llms/skills/imf-web-ui-frontend-conventions/references/data.md +130 -0
  21. package/src/llms/skills/imf-web-ui-frontend-conventions/references/docs-structure.md +44 -0
  22. package/src/llms/skills/imf-web-ui-frontend-conventions/references/git.md +33 -0
  23. package/src/llms/skills/imf-web-ui-frontend-conventions/references/library-boundary.md +37 -0
  24. package/src/llms/skills/imf-web-ui-frontend-conventions/references/npm-project.md +57 -0
  25. package/src/llms/skills/imf-web-ui-frontend-conventions/references/project-structure.md +21 -0
  26. package/src/llms/skills/{imf-web-ui-frontend-patterns/references/react-patterns.md → imf-web-ui-frontend-conventions/references/react.md} +26 -12
  27. package/src/llms/skills/imf-web-ui-frontend-conventions/references/stack.md +39 -0
  28. package/src/llms/skills/imf-web-ui-frontend-conventions/references/styling.md +91 -0
  29. package/src/llms/skills/imf-web-ui-frontend-conventions/references/testing.md +16 -0
  30. package/src/llms/skills/imf-web-ui-frontend-conventions/references/tooling.md +65 -0
  31. package/src/llms/skills/imf-web-ui-frontend-conventions/references/typescript.md +45 -0
  32. package/src/llms/skills/imf-web-ui-frontend-setup/SKILL.md +83 -0
  33. package/src/llms/skills/imf-web-ui-frontend-setup/templates/AGENTS.md +34 -0
  34. package/src/llms/skills/imf-web-ui-frontend-setup/templates/README.md +27 -0
  35. package/src/llms/skills/imf-web-ui-library-setup/SKILL.md +56 -0
  36. package/src/llms/skills/imf-web-ui-ux/SKILL.md +4 -4
  37. package/src/llms/skills/imf-web-ui-ux/references/forms.md +4 -2
  38. package/bin/install-skill.js +0 -180
  39. package/src/llms/skills/imf-web-ui-frontend-patterns/SKILL.md +0 -67
  40. package/src/llms/skills/imf-web-ui-setup/SKILL.md +0 -30
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@imfusion/web-ui",
3
- "version": "0.5.0",
3
+ "version": "0.5.1-dev.11.g2e949f6d",
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-skills": "./bin/install-skill.js"
22
+ "web-ui-install": "bin/install.js"
16
23
  },
17
24
  "exports": {
18
25
  ".": {
@@ -36,39 +43,40 @@
36
43
  "bin"
37
44
  ],
38
45
  "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
46
  "dev": "tsx scripts/dev.ts",
46
47
  "dev:host": "tsx scripts/dev.ts --host",
47
48
  "dev:lib": "concurrently -n codegen,lib -c yellow,blue \"npm run codegen:watch\" \"vite build --watch\"",
48
- "typecheck": "npm run codegen && tsc -p tsconfig.lib.json --noEmit && tsc -p .storybook/tsconfig.json --noEmit",
49
+ "build": "npm run codegen && rm -rf dist && vite build && npm run codegen:docgen && npm run codegen:llms",
50
+ "build:storybook": "npm run codegen:storybook && storybook build",
51
+ "storybook": "npm run codegen:storybook && storybook dev -p 6006 --no-open",
52
+ "storybook:free-ports": "tsx scripts/free-storybook-ports.ts",
53
+ "codegen": "tsx src/codegen/run.ts",
54
+ "codegen:watch": "tsx watch src/codegen/run.ts",
55
+ "codegen:storybook": "tsx src/codegen/run.ts --storybook",
56
+ "codegen:docgen": "tsx src/docgen/gen-docgen.ts",
57
+ "codegen:llms": "tsx src/llms/gen-llms.ts",
58
+ "verify:format": "prettier --check .",
59
+ "verify:lint": "eslint . --cache --max-warnings=0",
60
+ "verify:typecheck": "npm run codegen && tsc -p tsconfig.lib.json --noEmit && tsc -p .storybook/tsconfig.json --noEmit",
61
+ "verify:tests": "vitest run",
62
+ "verify:deps": "tsx scripts/check-pinned-dependencies.ts",
63
+ "verify:staged": "tsx scripts/verify/staged.ts",
64
+ "verify:full": "tsx scripts/verify/full.ts && npm run verify:tests",
49
65
  "format": "prettier --write .",
50
- "format:check": "prettier --check .",
51
- "lint": "eslint . --cache --max-warnings=0",
52
- "lint:fix": "eslint . --cache --fix",
66
+ "lint": "eslint . --cache --fix",
53
67
  "lint:inspect": "npx @eslint/config-inspector@latest",
68
+ "test:unit": "vitest run --project unit",
69
+ "test:stories": "vitest run --project storybook",
70
+ "test:watch": "vitest",
54
71
  "skills:eval": "node src/llms/evals/run.mjs",
55
72
  "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
73
  "skills:eval:compare": "node src/llms/evals/compare.mjs",
57
74
  "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
75
  "worktree:create": "tsx scripts/worktrees.ts create",
68
76
  "worktree:close": "tsx scripts/worktrees.ts close",
69
77
  "worktree:discard": "tsx scripts/worktrees.ts discard",
70
78
  "knip": "knip",
71
- "prepare": "git config core.hooksPath .githooks && git config pull.rebase true && git config merge.ff only"
79
+ "git:config": "git config core.hooksPath .githooks && git config pull.rebase true && git config merge.ff only"
72
80
  },
73
81
  "peerDependencies": {
74
82
  "@imfusion/sdk": "^1.0.0",
@@ -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 patterns. Load when adding or editing
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,25 @@ 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 | Open |
28
- | ---------------------------------------------------------------------------------------------------- | ------------------------------ |
29
- | Using a specific component; checking props, sub-components, or defaults | `imf-web-ui-components` |
30
- | First-time setup, or components rendering unstyled/broken | `imf-web-ui-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 around the library; styling beyond defaults; state or code structure | `imf-web-ui-frontend-patterns` |
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
+
35
+ The last row is narrow on purpose. It's for "what is this project missing?" — a question about the repo as a whole. Being
36
+ asked to add one config file is just that edit; make it, and don't open a skill to do so.
33
37
 
34
38
  Tasks routinely span two: building a screen usually means `imf-web-ui-ux` for the shape and `imf-web-ui-components` for the
35
39
  APIs. That's normal — open both, in that order.
36
40
 
41
+ ## The stack
42
+
43
+ TanStack is the recommended tooling library — routing, server state, forms, tables. When a task needs one of those and the
44
+ project has no incumbent, propose it, and read the library's own docs (`npx @tanstack/cli`) rather than working from memory.
45
+
37
46
  ## When to interview the human
38
47
 
39
48
  `imf-web-ui-ux` contains a short per-feature interview. Run it **only** when both hold:
@@ -0,0 +1,46 @@
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. Wraps npx web-ui-install and adds the judgment the
6
+ binary can't have — reading what the repo already registers before adding anything. Load when setting up agent tooling in a
7
+ consumer app, when skills are stale, or when hooks should be installed or adapted. Not for project tooling
8
+ (imf-web-ui-frontend-setup) or library wiring (imf-web-ui-library-setup)."
9
+ ---
10
+
11
+ # imf-web-ui-agent-setup
12
+
13
+ Sets up or updates the agent side of a consumer repo. The binary does the mechanics; this skill does the judgment.
14
+
15
+ ## Skills
16
+
17
+ `npx web-ui-install` installs or refreshes the vendored `imf-web-ui-*` skills (interactive target choice on first run,
18
+ remembered after; `--reconfigure` re-opens it). It also refreshes the `<!-- imf-web-ui:begin/end -->` fence in an existing
19
+ `AGENTS.md`. Stale check: `.imf-web-ui-skill-version.json` in each installed skill vs. the installed package version — older
20
+ marker means re-run the binary, never hand-diff skill contents.
21
+
22
+ ## Hooks
23
+
24
+ Three agent lifecycle hooks ship as templates in [`templates/hooks/`](templates/hooks/), registration in
25
+ [`templates/settings.json`](templates/settings.json):
26
+
27
+ - **SessionStart** — once per session: the conventions baseline is vendored, load it before writing.
28
+ - **UserPromptSubmit** — a one-line conventions reminder per prompt. Per-prompt, not per-tool-call: a PreToolUse reminder
29
+ would re-inject the same text on every edit.
30
+ - **PostToolUse** (advisory) — file-scope checks on the touched file after every Edit/Write, failures fed straight back.
31
+ Never exits non-zero.
32
+ - **`baseline-staleness.sh`** — not registered as an agent hook; the repo's pre-commit calls it
33
+ ([`git.md`](../imf-web-ui-frontend-conventions/references/git.md), Staleness at commit time).
34
+
35
+ **Read `.claude/settings.json` before installing.** Then pick per event:
36
+
37
+ - **Nothing registered** → install as shipped: `npx web-ui-install --hooks` copies the scripts to `.agents/hooks/imf-web-ui/`
38
+ (installer-owned, refreshed wholesale) and merges the registrations without touching existing entries.
39
+ - **The repo already covers an event** (its own session reminder, its own post-edit verify) → don't stack a second hook on
40
+ it. Use the template as a starting point instead: fold the missing behaviour into the repo's existing script, or adapt the
41
+ template and register that. Surface the situation and let the human pick.
42
+
43
+ ## Not this skill
44
+
45
+ - Project tooling, docs structure, the audit checklist → `imf-web-ui-frontend-setup` (which delegates agent tooling here)
46
+ - 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,4 @@
1
+ #!/usr/bin/env sh
2
+ # UserPromptSubmit: one-line conventions reminder per prompt.
3
+ echo "Conventions check: if this task touches code, styles, data fetching, or docs, consult the matching imf-web-ui-* skill in .agents/skills/ before acting."
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,44 @@
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
+ | [data.md](references/data.md) | `api/`+`http/` shape, Zod boundary, query/mutation patterns | adding an API topic, a fetch, or a mutation |
35
+ | [project-structure.md](references/project-structure.md) | the `src/` tree, file naming, imports | adding files rather than editing existing ones |
36
+ | [testing.md](references/testing.md) | what's worth testing and what isn't | writing or reviewing tests |
37
+ | [stack.md](references/stack.md) | the topic→tool map, when a library owns a layer | choosing or adding any dependency |
38
+ | [npm-project.md](references/npm-project.md) | `package.json`, the `verify:*` script set, dependency pinning | running or adding a script, adding a dependency |
39
+ | [tooling.md](references/tooling.md) | Prettier, ESLint, tsconfig, staged-files config baselines | touching tool config |
40
+ | [git.md](references/git.md) | `git:config`, verify scopes, staleness at commit time | wiring hooks or the commit path |
41
+ | [assets.md](references/assets.md) | image formats, the WebP recipe | adding images or other static assets |
42
+ | [docs-structure.md](references/docs-structure.md) | what a repo documents, where, how it's written | writing or restructuring repo docs |
43
+
44
+ `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,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.
@@ -0,0 +1,130 @@
1
+ # Data
2
+
3
+ How data enters, moves through, and leaves an ImFusion frontend. This is the in-house pattern **for TanStack Query + Router**
4
+ — the default stack ([stack.md](stack.md)). A project on a different data layer keeps the boundary principles (validate at
5
+ the edge, errors as values) but not necessarily this file layout.
6
+
7
+ ## The shape
8
+
9
+ ```
10
+ src/
11
+ api/<topic>/ # one folder per API topic
12
+ <topic>.ts # queryOptions / mutationOptions
13
+ query-key.ts # key factory, topic as the first segment
14
+ types.ts # Zod schemas + z.infer types
15
+ use-<thing>.ts # hooks wrapping the options for components
16
+ http/ # transport: client, error normalisation — the only transport-aware place
17
+ ```
18
+
19
+ A query lives next to its key factory and its schemas, so a query key is never spelled out at a call site. Anything
20
+ transport-level (base client, error normalisation) lives in `http/` and nowhere else.
21
+
22
+ ## The network boundary validates
23
+
24
+ The network is the only place untyped data enters the app, so it is the only place that validates. Everything a backend sends
25
+ is parsed against a Zod schema at that edge; the TypeScript types are derived from the schemas with `z.infer`, never
26
+ hand-written. A hand-written response type is a claim about the backend that nothing checks.
27
+
28
+ The transport in `http/` is deliberately untyped: it returns `unknown` and leaves the parse to the caller, so no call site
29
+ can accidentally skip validation.
30
+
31
+ ## Errors are values
32
+
33
+ A failed request becomes an `ApiError` carrying a machine-readable code — it crosses the boundary the same way values do, not
34
+ as an exception caught ad hoc. The frontend owns the mapping from codes to what the user reads.
35
+
36
+ ## The pattern, end to end
37
+
38
+ The key factory — topic as the first segment, one function per key shape:
39
+
40
+ ```ts
41
+ // api/user/query-key.ts
42
+ const TOPIC = "user" as const;
43
+
44
+ export const userKeys = {
45
+ all: [TOPIC] as const,
46
+ me: () => [TOPIC, "me"] as const,
47
+ detail: (id: string) => [TOPIC, "detail", id] as const
48
+ };
49
+ ```
50
+
51
+ Query options — key from the factory, parse at the boundary:
52
+
53
+ ```ts
54
+ // api/user/user.ts
55
+ import { queryOptions } from "@tanstack/react-query";
56
+
57
+ import { bffClient } from "#/http/bff-client";
58
+ import { userKeys } from "./query-key";
59
+ import { userSchema } from "./types";
60
+
61
+ export const userQueryOptions = () =>
62
+ queryOptions({
63
+ queryKey: userKeys.me(),
64
+ queryFn: async () => userSchema.parse(await bffClient("/me"))
65
+ });
66
+ ```
67
+
68
+ The hook a component consumes:
69
+
70
+ ```ts
71
+ // api/user/use-user.ts
72
+ import { useSuspenseQuery } from "@tanstack/react-query";
73
+
74
+ import { userQueryOptions } from "./user";
75
+
76
+ export const useUser = () => useSuspenseQuery(userQueryOptions());
77
+ ```
78
+
79
+ The options and their hook can start colocated in one file — options right beside the hook that consumes them — and split
80
+ into separate files when the topic's complexity demands it.
81
+
82
+ Mutations follow the same shape with `mutationOptions`, the dirtied topic first in the `mutationKey`:
83
+
84
+ ```ts
85
+ // api/user/user.ts
86
+ export const updateUserMutationOptions = () =>
87
+ mutationOptions({
88
+ mutationKey: [...userKeys.all, "update"],
89
+ mutationFn: async (patch: UserPatch) => userSchema.parse(await bffClient("/me", { method: "PATCH", body: patch }))
90
+ });
91
+ ```
92
+
93
+ Routes prefetch in the `loader` with `context.queryClient.ensureQueryData(userQueryOptions())`, components read with the
94
+ `useSuspenseQuery` hook. Give the route a `pendingComponent`; let errors bubble to the nearest `errorComponent`.
95
+
96
+ ## Automatic invalidation
97
+
98
+ Mutations don't invalidate by hand at every call site — the query client does it centrally, keyed off the mutation's topic. A
99
+ `MutationCache` `onSuccess` invalidates `mutationKey[0]`, on by default via `meta.autoInvalidate`; that's why every
100
+ mutation's key leads with the topic it dirties:
101
+
102
+ ```ts
103
+ // query-client.ts
104
+ export function createQueryClient() {
105
+ const queryClient = new QueryClient({
106
+ defaultOptions: {
107
+ mutations: { meta: { autoInvalidate: true } }
108
+ },
109
+ mutationCache: new MutationCache({
110
+ onSuccess: async (_data, _variables, _context, mutation) => {
111
+ if (!mutation.meta?.autoInvalidate) return;
112
+ const topic = mutation.options.mutationKey?.[0];
113
+ if (topic !== undefined) await queryClient.invalidateQueries({ queryKey: [topic] });
114
+ }
115
+ })
116
+ });
117
+ return queryClient;
118
+ }
119
+ ```
120
+
121
+ Opt out per mutation with `meta: { autoInvalidate: false }` when a coarse topic-wide refetch is wrong (a huge list, a
122
+ targeted optimistic update), and invalidate precisely through the key factory instead. Background reading:
123
+ [query invalidation](https://tanstack.com/query/latest/docs/framework/react/guides/query-invalidation) and
124
+ [automatic invalidation after mutations](https://tkdodo.eu/blog/automatic-query-invalidation-after-mutations).
125
+
126
+ ## Testing the data layer
127
+
128
+ Stub the network at the `fetch` boundary and let the real query client run: the test then exercises the same parse and error
129
+ path production does. Schemas, clients, and query options are where behaviour worth asserting lives — the general philosophy
130
+ is [testing.md](testing.md).
@@ -0,0 +1,44 @@
1
+ # Documentation structure
2
+
3
+ What a repo documents, where, and how it's written. `imf-web-ui-frontend-setup` scaffolds this shape from its `templates/`
4
+ and audits it.
5
+
6
+ ## The shape
7
+
8
+ ```
9
+ <app>/
10
+ README.md # humans: what the app is, setup, scripts
11
+ AGENTS.md # agents: stack, pointers into docs/ — restates nothing
12
+ docs/
13
+ index.md # registers every doc with a one-line "covers" summary
14
+ <topic>.md # repo-unique content only
15
+ ```
16
+
17
+ ## Repo docs hold only what is unique to the repo
18
+
19
+ The litmus, applied sentence by sentence: **would this be true in every ImFusion frontend? Then it's baseline — it lives in
20
+ the installed skill references, don't restate it.** The baseline is already in the repo, vendored under
21
+ `.agents/skills/imf-web-ui-frontend-conventions/`, human-readable and versioned.
22
+
23
+ Where the repo deviates from the baseline, the doc says so as a **named deviation** — what the baseline prescribes, what this
24
+ repo does instead, and why. A deviation written as freestanding convention gets copied into the next repo as if it were house
25
+ style.
26
+
27
+ ## How docs are written
28
+
29
+ - **Docs explain concepts; code is the source of truth for facts.** A doc captures the why — invariants, rationale,
30
+ decisions. It never restates a fact that lives in code (a script definition, a type shape, a config value): point at the
31
+ file instead. A copied fact rots the moment the code changes.
32
+ - **State what is — no decision residue.** Positive, present-tense statements about the current state. Never narrate the
33
+ delta from a past decision or refute alternatives nobody raised ("there is no X mode", "Y was dropped") — that history
34
+ belongs in commits and tickets. A negation earns its place only as a guardrail or to preempt a wrong assumption a present
35
+ reader would actually arrive at.
36
+ - **Boy Scout rule.** Discovered rot — a stale pointer, a doc contradicting the code — is always your responsibility: fix it
37
+ in place if trivial, otherwise report it. The two failure modes are equal: stepping over rot, and cramming unrelated
38
+ cleanup into an unrelated change.
39
+
40
+ ## Staleness at commit time
41
+
42
+ Docs are checked when they can go stale: at the commit. A staged change that invalidates a doc — a renamed script, a moved
43
+ folder, a changed flow — updates that doc **in the same commit**, not in a follow-up. Pre-commit carries the advisory
44
+ staleness checks (vendored baseline, docs) — the mechanics are in [git.md](git.md).
@@ -0,0 +1,33 @@
1
+ # Git
2
+
3
+ ## git:config
4
+
5
+ One script holds the repo's git configuration, run by hand once per clone (the README names it):
6
+
7
+ ```
8
+ git config core.hooksPath .githooks && git config pull.rebase true && git config merge.ff only
9
+ ```
10
+
11
+ Hooks live in a tracked directory; history strategy doesn't depend on personal git config. Two silent failure modes to check
12
+ for: `git:config` never run (hooks exist only on the machine that configured by hand), and `core.hooksPath` pointing at a
13
+ directory that doesn't exist. Config **and** directory.
14
+
15
+ ## Verify scopes
16
+
17
+ Two blocking, one advisory:
18
+
19
+ - **staged** — `verify:staged`, called by the pre-commit hook: lint, format, restage. Fast; a passing commit is not CI green.
20
+ - **full** — `verify:full`: the build plus every `verify:*` check. What CI runs.
21
+ - **files** — post-edit agent hook. Advisory, never exits non-zero, so a mid-flight refactor can't trap the agent.
22
+
23
+ One script owns each scope's step list; npm scripts and hooks only launch them. Name by depth, not by occasion — a
24
+ `preflight` needs explaining and invites a near-identical sibling, and two of those drift into "passes locally, fails in CI".
25
+
26
+ ## Staleness at commit time
27
+
28
+ Pre-commit also runs the advisory staleness checks — warn, never block:
29
+
30
+ - **Vendored baseline** — `.agents/hooks/imf-web-ui/baseline-staleness.sh` compares the installed `imf-web-ui-*` skill
31
+ markers against the installed package version; the fix it names is `npx web-ui-install`.
32
+ - **Docs** — a staged change that invalidates a doc updates that doc in the same commit
33
+ ([docs-structure.md](docs-structure.md)). Where the repo has an agent commit workflow, a staged docs audit belongs in it.