@imfusion/web-ui 0.5.1-dev.3.g8e0a047a → 0.5.1-dev.30.gf9b152db
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 +148 -55
- package/bin/install.js +374 -0
- package/bin/install.test.ts +243 -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/field/field.d.ts +104 -0
- package/dist/components/field/field.meta.d.ts +2 -0
- package/dist/components/field/index.d.ts +2 -0
- package/dist/components/fieldset/fieldset.d.ts +29 -0
- package/dist/components/fieldset/fieldset.meta.d.ts +2 -0
- package/dist/components/fieldset/index.d.ts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4956 -4317
- package/dist/integrations/image-display-options.js +1 -1
- package/dist/style.css +1 -1
- package/dist/{tabs-DqBFSqq6.js → tabs-CVp_SgBl.js} +1 -1
- package/package.json +35 -24
- package/src/docgen/doc.gen.json +327 -0
- package/src/llms/llms.gen.txt +12 -0
- package/src/llms/skills/imf-web-ui/SKILL.md +15 -11
- package/src/llms/skills/imf-web-ui-agent-setup/SKILL.md +82 -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 +33 -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 +2 -1
- package/src/llms/skills/imf-web-ui-frontend-conventions/SKILL.md +46 -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 +201 -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} +28 -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 +18 -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 +46 -0
- package/src/llms/skills/imf-web-ui-frontend-conventions/references/validation.md +88 -0
- package/src/llms/skills/imf-web-ui-frontend-setup/SKILL.md +66 -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/FRONTEND_SETUP_REPORT.md +45 -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-update/SKILL.md +166 -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,166 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: imf-web-ui-update
|
|
3
|
+
description:
|
|
4
|
+
"Update @imfusion/web-ui in a consumer repository: refresh the package and vendored skills, audit and optionally install
|
|
5
|
+
lifecycle hooks, verify the base update, then audit and optionally migrate affected or custom consumer components in a
|
|
6
|
+
separate approved commit."
|
|
7
|
+
argument-hint: "[--dry-run] [optional version or reason]"
|
|
8
|
+
allowed-tools: Bash Read Grep
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# imf-web-ui-update
|
|
12
|
+
|
|
13
|
+
Update a repository that consumes `@imfusion/web-ui`. Preserve existing project choices and user work. The workflow has two
|
|
14
|
+
commits: the dependency/tooling update first, then an optional consumer migration. Never commit without a fresh explicit
|
|
15
|
+
approval.
|
|
16
|
+
|
|
17
|
+
## Rules
|
|
18
|
+
|
|
19
|
+
- Work from the frontend package root. In a monorepo, inspect package manifests and `git worktree list` first, then choose
|
|
20
|
+
the frontend package in the active worktree that uses web-ui. Read repository and component instructions before changing
|
|
21
|
+
files.
|
|
22
|
+
- Detect the package manager from lockfiles. Use npm for `package-lock.json`, pnpm for `pnpm-lock.yaml`, Yarn for
|
|
23
|
+
`yarn.lock`, and Bun for `bun.lock*`. If multiple lockfiles exist, stop and ask which is authoritative.
|
|
24
|
+
- A dry run is read-only. Do not install, run the installer, format, stage, commit, reset, or clean. Report what would run.
|
|
25
|
+
- Record the initial `git status --short`. Existing changes are held back, never staged or overwritten.
|
|
26
|
+
- A dirty file that the workflow needs to modify is a conflict. Stop and report it. This includes package files, installed
|
|
27
|
+
skill directories, the `AGENTS.md` installer fence, hook files, and hook settings.
|
|
28
|
+
- Do not hand-edit installer-owned skills, the `AGENTS.md` fenced block, or installer-owned hook files.
|
|
29
|
+
- Keep the base update and consumer migration in separate commits. Do not begin the migration audit until the base update
|
|
30
|
+
commit is complete.
|
|
31
|
+
- Treat the installed package version before the update as the comparison baseline. There is no assumed changelog.
|
|
32
|
+
- A migration candidate is a custom consumer implementation whose behavior overlaps with a current web-ui component, or an
|
|
33
|
+
existing web-ui usage affected by changed types or documented API. Do not infer a replacement from a name alone; inspect
|
|
34
|
+
the implementation and the current component documentation/types.
|
|
35
|
+
|
|
36
|
+
## Preflight
|
|
37
|
+
|
|
38
|
+
1. Select the frontend package in the active worktree and read its instructions, `package.json`, and lockfile.
|
|
39
|
+
2. Check whether `@imfusion/web-ui` is already declared and record its installed/version-marker version.
|
|
40
|
+
3. Record the worktree status and identify files the update would touch.
|
|
41
|
+
4. Inspect `.agents/skills/`, `.claude/skills/`, the `AGENTS.md` fence, `.claude/settings.json`, and `.agents/hooks/`.
|
|
42
|
+
5. Check for another package-manager process. Stop on any conflict or ambiguous target.
|
|
43
|
+
|
|
44
|
+
## Base update
|
|
45
|
+
|
|
46
|
+
In a non-dry run, update the dependency with the detected manager:
|
|
47
|
+
|
|
48
|
+
```sh
|
|
49
|
+
npm install @imfusion/web-ui
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Use the equivalent command for another detected manager. Pass an explicit version only when the user supplied one. Do not use
|
|
53
|
+
`--force` or `--legacy-peer-deps` to make installation pass. Stop on install failure.
|
|
54
|
+
|
|
55
|
+
Then refresh the existing web-ui skill target:
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
npx web-ui-install --skills
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Use the detected manager's runner for non-npm projects. Preserve the existing target (`.agents`, `.claude`, or both); do not
|
|
62
|
+
silently choose a new one. Stop on malformed settings or installer conflicts.
|
|
63
|
+
|
|
64
|
+
## Hook audit
|
|
65
|
+
|
|
66
|
+
Audit these responsibilities separately: session start, prompt submit, and post-edit verification.
|
|
67
|
+
|
|
68
|
+
- If current web-ui hooks cover a responsibility, do not reinstall it.
|
|
69
|
+
- If no equivalent hook exists and the installer-owned destination is clean, ask whether to run:
|
|
70
|
+
|
|
71
|
+
```sh
|
|
72
|
+
npx web-ui-install --hooks
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Run it only after the user accepts. Use the detected runner otherwise.
|
|
76
|
+
|
|
77
|
+
- If another hook covers a responsibility, do not stack a duplicate. Report the overlap and ask whether to leave hooks alone
|
|
78
|
+
or adapt the existing hook in a separate approved change.
|
|
79
|
+
- Invalid settings or dirty installer-owned hook files are conflicts. Stop and ask for resolution.
|
|
80
|
+
|
|
81
|
+
A declined hook offer is valid. Report `hooks: skipped by user` and continue.
|
|
82
|
+
|
|
83
|
+
## Verification
|
|
84
|
+
|
|
85
|
+
After each mutating command, compare `git status --short` with the preflight snapshot.
|
|
86
|
+
|
|
87
|
+
Classify paths as:
|
|
88
|
+
|
|
89
|
+
- **Update-owned:** package files and installer-owned files changed by this workflow.
|
|
90
|
+
- **Held back:** pre-existing user changes, excluded from the commit.
|
|
91
|
+
- **Conflict:** an overlapping pre-existing change, ambiguous target, or unexpected changed path.
|
|
92
|
+
|
|
93
|
+
On conflict, stop. Never use `git restore`, `git reset`, `git clean`, or broad staging to hide it.
|
|
94
|
+
|
|
95
|
+
Run the frontend package's documented full verification after the base update. Read its `package.json` scripts and choose
|
|
96
|
+
commands that cover typechecking, linting, tests, and building. Prefer one documented aggregate `verify`/`check` script when
|
|
97
|
+
it covers those responsibilities; otherwise run the documented non-watch scripts for the responsibilities that exist. Do not
|
|
98
|
+
invent script names or run watch/dev scripts. A type or build failure is a base-update conflict to resolve before the base
|
|
99
|
+
commit, not a migration finding to defer.
|
|
100
|
+
|
|
101
|
+
Verify the dependency/lockfile, current skill markers, the `AGENTS.md` fence, hook coverage, and the final path list. In
|
|
102
|
+
dry-run mode, say that mutation-dependent checks were skipped.
|
|
103
|
+
|
|
104
|
+
## Base update report and approval
|
|
105
|
+
|
|
106
|
+
Before the base commit, show this concise report:
|
|
107
|
+
|
|
108
|
+
```text
|
|
109
|
+
@imfusion/web-ui update report
|
|
110
|
+
Package : <old> -> <new> / would update
|
|
111
|
+
Skills : <status>
|
|
112
|
+
Hooks : <status>
|
|
113
|
+
Verification : <status>
|
|
114
|
+
Update files : <paths>
|
|
115
|
+
Held back : <paths or none>
|
|
116
|
+
Conflicts : <paths or none>
|
|
117
|
+
Commit : <imperative message or not proposed>
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Normal mode: ask exactly, **“Approve these update-owned changes and commit them?”** Do not commit without an affirmative
|
|
121
|
+
answer. If denied, leave the changes uncommitted.
|
|
122
|
+
|
|
123
|
+
Dry-run mode: state **“Dry run complete; no changes or commit were made.”** Do not mutate or ask for commit approval.
|
|
124
|
+
|
|
125
|
+
## Consumer migration audit
|
|
126
|
+
|
|
127
|
+
Begin this phase only after the approved base update has been committed. Record the old and new package versions from the
|
|
128
|
+
pre-update and current manifests/lockfile. Use the current package's exported types, generated documentation, and component
|
|
129
|
+
examples as the source of truth. Use the old version's package metadata or repository history when available to identify what
|
|
130
|
+
changed; do not pretend a changelog exists.
|
|
131
|
+
|
|
132
|
+
Run the same documented full verification again after the base commit. Separate findings into:
|
|
133
|
+
|
|
134
|
+
- **Compatibility fixes:** existing web-ui imports, props, or usage patterns that no longer typecheck, build, lint, or pass
|
|
135
|
+
tests.
|
|
136
|
+
- **Changed component usages:** existing components whose current API, behavior, or documented contract changed between the
|
|
137
|
+
two versions and may need a consumer update, even when the typecheck passes.
|
|
138
|
+
- **Replacement candidates:** custom consumer components, wrappers, or field implementations that overlap with a component
|
|
139
|
+
now exported by `@imfusion/web-ui`. Inspect their code, styles, and call sites before suggesting a replacement. Include the
|
|
140
|
+
current custom implementation, the proposed web-ui component, the relevant API/type evidence, and any behavior or styling
|
|
141
|
+
that still needs to be preserved.
|
|
142
|
+
|
|
143
|
+
Report these findings separately from the base update. Ask whether to apply the proposed migration. If the user declines,
|
|
144
|
+
leave the consumer code unchanged. If accepted, make only the approved migration changes, run the documented full
|
|
145
|
+
verification again, and show a second report with the migration paths, held-back paths, conflicts, and verification result.
|
|
146
|
+
|
|
147
|
+
If no findings exist, report that the installed update has no detected compatibility fixes, changed usages, or replacement
|
|
148
|
+
candidates. Do not create an empty migration commit.
|
|
149
|
+
|
|
150
|
+
## Migration approval
|
|
151
|
+
|
|
152
|
+
Ask exactly, **“Approve these consumer migration changes and commit them separately?”** Do not commit without an affirmative
|
|
153
|
+
answer. The migration approval is not implied by approval of the base update.
|
|
154
|
+
|
|
155
|
+
## Commit
|
|
156
|
+
|
|
157
|
+
For the base update, after approval:
|
|
158
|
+
|
|
159
|
+
1. Recheck status and exclude held-back paths.
|
|
160
|
+
2. Stage base update-owned paths selectively. Never use `git add .` or `git add -A`.
|
|
161
|
+
3. Run the repository's commit workflow if it has one, including its required verification and documentation audit. Otherwise
|
|
162
|
+
run documented verification and follow the repository's commit convention.
|
|
163
|
+
4. Commit with a concise imperative message and confirm the commit and final status.
|
|
164
|
+
|
|
165
|
+
For an approved migration, repeat the same selective staging and documented commit workflow with a separate imperative
|
|
166
|
+
message. Never bypass hooks or amend an unrelated commit after a hook failure.
|
|
@@ -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.
|
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
# Code conventions
|
|
2
|
-
|
|
3
|
-
The ImFusion defaults for everyday code around `@imfusion/web-ui` — the parts that aren't React-specific. TypeScript, file
|
|
4
|
-
organisation, naming, and testing. React component structure lives in [react-patterns.md](react-patterns.md); the two are
|
|
5
|
-
read together when starting new code.
|
|
6
|
-
|
|
7
|
-
These fill vacuums. Where the host project has already decided, the project wins.
|
|
8
|
-
|
|
9
|
-
## TypeScript
|
|
10
|
-
|
|
11
|
-
- **`any` is forbidden.** `unknown` at a boundary you genuinely can't type, narrowed before use. An `any` that silences an
|
|
12
|
-
error moves the failure from compile time to runtime, which is the opposite of the trade you wanted.
|
|
13
|
-
- **Lean on inference for locals; annotate the contract.** Restating a type the compiler already knows inside a function body
|
|
14
|
-
is a second thing to keep in sync. An **explicit return type on an exported function is worth writing**: it's the promise
|
|
15
|
-
the module makes, it stops an internal refactor silently widening the public shape, and it makes the error surface at the
|
|
16
|
-
function rather than at every call site.
|
|
17
|
-
- **No temporal coupling.** Don't initialise to `null` and fill the value in later — model the states instead, so "not loaded
|
|
18
|
-
yet" and "loaded, empty" aren't the same value.
|
|
19
|
-
- **Avoid `as`.** A type assertion tells the compiler to stop checking exactly where checking is worth most. Fix the type.
|
|
20
|
-
Assertions at an untyped third-party boundary are the honest exception; keep them at the boundary, not spread through call
|
|
21
|
-
sites.
|
|
22
|
-
|
|
23
|
-
**Derive types, don't duplicate them.** One source of truth, everything else follows from it:
|
|
24
|
-
|
|
25
|
-
```ts
|
|
26
|
-
const sizes = ["sm", "md", "lg"] as const;
|
|
27
|
-
type Size = (typeof sizes)[number];
|
|
28
|
-
|
|
29
|
-
const labels: Record<Size, string> = { sm: "S", md: "M", lg: "L" }; // compiler breaks if `sizes` changes
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
The same rule crosses the library boundary: prop types come from the components themselves
|
|
33
|
-
(`React.ComponentProps<typeof Button>`), never re-declared by hand.
|
|
34
|
-
|
|
35
|
-
**Function signatures.** One or two positional arguments read fine. At three or more, take a single object and destructure —
|
|
36
|
-
call sites stop depending on argument order, and adding a parameter stops being a breaking change.
|
|
37
|
-
|
|
38
|
-
## Expressions over statements
|
|
39
|
-
|
|
40
|
-
Reach for the array methods before the loop. `map`, `filter`, `find`, `some`, `every`, `flatMap`, `reduce` — each names what
|
|
41
|
-
it's doing, where a `for` loop makes you read the body to find out.
|
|
42
|
-
|
|
43
|
-
```ts
|
|
44
|
-
// The name is the documentation
|
|
45
|
-
const activeNames = users.filter(u => u.isActive).map(u => u.name);
|
|
46
|
-
|
|
47
|
-
// vs. a loop you have to read to understand
|
|
48
|
-
const activeNames = [];
|
|
49
|
-
for (const u of users) {
|
|
50
|
-
if (u.isActive) activeNames.push(u.name);
|
|
51
|
-
}
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
The deeper reason is mutation: the method chain produces a new value, so nothing else can observe a half-built array. Prefer
|
|
55
|
-
spreads and `structuredClone` over in-place edits, and `toSorted`/`toReversed` over `sort`/`reverse`, which mutate their
|
|
56
|
-
receiver and have surprised everyone at least once.
|
|
57
|
-
|
|
58
|
-
Two honest exceptions: a genuine early exit (`for` with `break` beats `find` returning a sentinel) and a hot loop over
|
|
59
|
-
thousands of items where the intermediate arrays actually measure. Neither is the common case, so reach for the method first
|
|
60
|
-
and justify the loop.
|
|
61
|
-
|
|
62
|
-
Keep the chain flat. Three or four steps read well; ten want intermediate named constants, and a `reduce` doing four things
|
|
63
|
-
at once wants to be a loop after all.
|
|
64
|
-
|
|
65
|
-
## Naming
|
|
66
|
-
|
|
67
|
-
- Say what it is, not what it is made of. `useUserQuery`, not `useUserHook`. `retryDelay`, not `num`.
|
|
68
|
-
- Booleans read as assertions: `isOpen`, `hasAccess`, `canSubmit`. A boolean called `status` will end up holding a string.
|
|
69
|
-
- Handlers are `onX` as props, `handleX` as implementations — the prop names the event, the function names the response.
|
|
70
|
-
- Match the vocabulary the product and the API already use. Inventing a synonym for a term the backend already named costs a
|
|
71
|
-
translation step on every read.
|
|
72
|
-
|
|
73
|
-
## File and folder organisation
|
|
74
|
-
|
|
75
|
-
Kebab-case throughout, folders and files.
|
|
76
|
-
|
|
77
|
-
```
|
|
78
|
-
src/
|
|
79
|
-
routes/ # TanStack Router file-based routes; routing only
|
|
80
|
-
api/<topic>/ # <topic>.ts (queries/mutations), query-key.ts, types.ts
|
|
81
|
-
components/ # grouped by kind of component — layouts/, primitives/, or a domain name
|
|
82
|
-
http/ # client, error normalisation
|
|
83
|
-
lib/ # framework-free helpers
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
`api/` groups by topic: a query lives next to its key factory and its types, so a query key is never spelled out at a call
|
|
87
|
-
site. Routes compose and don't fetch inline. Transport concerns live in `http/` and nowhere else.
|
|
88
|
-
|
|
89
|
-
`components/` groups by kind — a layout component under `layouts/`, not beside a domain widget. Flat is fine while there are
|
|
90
|
-
few; let the grouping follow what the project has rather than imposing it up front. The kinds worth separating are the
|
|
91
|
-
component roles in [react-patterns.md](react-patterns.md): dumb components, layout components, smart containers.
|
|
92
|
-
|
|
93
|
-
A component gets a folder once it has more than one file, with an `index.ts` that only re-exports:
|
|
94
|
-
|
|
95
|
-
```
|
|
96
|
-
components/data-table/
|
|
97
|
-
data-table.tsx
|
|
98
|
-
data-table-row.tsx
|
|
99
|
-
data-table.module.css
|
|
100
|
-
index.ts
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
Colocate tests, styles, and types with their subject. A file you have to hunt for in a parallel tree gets edited less
|
|
104
|
-
carefully.
|
|
105
|
-
|
|
106
|
-
## Styling
|
|
107
|
-
|
|
108
|
-
**CSS Modules by default**, colocated as `<component>.module.css`. No CSS-in-JS, no utility-class framework. Compose from
|
|
109
|
-
`--imf-ui-*` tokens so custom UI stays consistent with library components and follows the theme; the override contract (CSS
|
|
110
|
-
layers, `data-imf-ui-component`, never the library's generated class names) is in the parent skill.
|
|
111
|
-
|
|
112
|
-
`imf-web-ui-imfusion-frontend-setup` sets up or audits this structure on an ImFusion project.
|
|
113
|
-
|
|
114
|
-
## Testing
|
|
115
|
-
|
|
116
|
-
Test the **decisions**, not the rendering.
|
|
117
|
-
|
|
118
|
-
- **Pure logic gets unit tests** — pricing, permissions, date math, parsing, reducers. These are cheap, fast, and the place
|
|
119
|
-
bugs actually hide.
|
|
120
|
-
- **Presentational components generally don't.** A component that maps props onto web-ui primitives has no logic of its own;
|
|
121
|
-
asserting that it rendered a `<Button>` tests React, not your code.
|
|
122
|
-
- **Behaviour a user performs gets an interaction test** — a form that validates, a flow with steps. Test it through the
|
|
123
|
-
interface the user has (roles, labels, visible text), not through internals.
|
|
124
|
-
- **Extract the decision out of a hook and test that.** A hook whose interesting part is a plain function is easier to test
|
|
125
|
-
as a plain function than through a render harness. A hook that only wraps a browser API has no decision to extract.
|
|
126
|
-
|
|
127
|
-
The measure isn't coverage percentage. It's whether a test failing tells you something you didn't already know.
|
|
128
|
-
|
|
129
|
-
## Formatting and linting
|
|
130
|
-
|
|
131
|
-
Don't argue about it in review — the tooling decides, and it runs before the commit lands. A formatter, a linter, and a
|
|
132
|
-
pre-commit hook wired so none of them is optional. On an ImFusion project, `imf-web-ui-imfusion-frontend-setup` carries the
|
|
133
|
-
baseline and the setup steps.
|