@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
@@ -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-patterns`; project wiring lives in `imf-web-ui-setup`.
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-patterns`.
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-patterns`) so a breaking change lands in one
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
 
@@ -6,8 +6,10 @@ The 80/20 of forms, distilled from NN/g's form-design and error-guidelines artic
6
6
  most users — a cited CHI study found guideline-compliant forms hit 78% one-try error-free submission vs. 42% for
7
7
  non-compliant ones. Read before building any form beyond two fields.
8
8
 
9
- The library ships the controls (`Input`, `Select`, `Checkbox`, `Switch`, `Slider`) but no form or field wrapper — labels,
10
- grouping, and validation display are composed by you, which is exactly where these rules apply.
9
+ The library ships the controls (`Input`, `Select`, `Checkbox`, `Switch`, `Slider`) but no form or field wrapper, so labels,
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-conventions`), and
12
+ these rules govern how its errors get presented.
11
13
 
12
14
  ## Structure
13
15
 
@@ -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,67 +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, and stack
6
- defaults. Load when writing wrapper components, custom UI, or styling beyond the defaults."
7
- ---
8
-
9
- # imf-web-ui-frontend-patterns
10
-
11
- One guard, once: **if the host project already has a convention — a styling system, a state library, a folder shape — the
12
- project wins.** These defaults fill vacuums. They are not a license to refactor a consumer codebase toward this document.
13
-
14
- Everything else below is how to build.
15
-
16
- ## Stay behind the library
17
-
18
- Never import Base UI (or any other upstream this library wraps) directly — no upstream stylesheets, no upstream components,
19
- even when upstream docs show it that way. Everything a component needs ships in `@imfusion/web-ui`. If the library is missing
20
- something upstream has, report the gap (see `imf-web-ui-components`); don't reach around it.
21
-
22
- ## Style through the sanctioned seams
23
-
24
- All library styles live in the `imf-ui.components` CSS layer, so **any plain selector you write wins** — that's the whole
25
- override contract:
26
-
27
- - Target the stable hooks: `data-imf-ui-component` attributes and your own classes/wrappers.
28
- - Never target the library's internal class names — they are generated and change without notice.
29
- - Never `!important` — if you think you need it, you're targeting the wrong thing.
30
-
31
- ## Build custom UI from tokens
32
-
33
- Anything you build that the library doesn't cover — a stat widget, a custom panel — uses `--imf-ui-*` variables for color,
34
- spacing, radius, and type instead of hardcoded values. That's what makes custom UI look native next to library components,
35
- 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
36
- defect.
37
-
38
- ## Derive types, don't import them
39
-
40
- Prop types come from the components themselves: `React.ComponentProps<typeof Button>`. The library deliberately exports no
41
- `Props` types — don't look for them, and don't re-declare prop shapes by hand.
42
-
43
- ## Integrations own their peers
44
-
45
- Components under `@imfusion/web-ui/integrations/*` depend on optional peers (e.g. `@tanstack/highlight` for `CodeHighlight`).
46
- Add the peer explicitly to the consumer's `package.json` — never rely on hoisting.
47
-
48
- ## React patterns
49
-
50
- The full treatment — component roles with an example, state placement, effects discipline, and the react.dev sources to
51
- consult while building — lives in [references/react-patterns.md](references/react-patterns.md). Read it before writing new
52
- screens or wrappers. The core in one breath:
53
-
54
- - **Three roles.** Dumb components own how things look, layout components own arrangement, smart containers own data and
55
- logic. Styling never lives in containers.
56
- - **State lives where its truth lives.** URL → query cache → context → store → local state; walk the list, stop at the first
57
- match.
58
- - **Effects are a last resort**, and always extracted into purpose-named hooks.
59
- - **Compose, don't configure.** If a component's prop list reads like a settings page, it wanted to be two or three
60
- components.
61
-
62
- ## Starting a frontend from scratch
63
-
64
- When the consumer app is greenfield, default to the TanStack suite: **Router** (URL state, type-safe search params),
65
- **Query** (server state), **Form** (form state), and **Table** for data grids, which you pair with web-ui's styled `Table`
66
- parts (`Table.SortableHeaderCell` carries the sort glue). Documentation is available straight from the terminal via
67
- `npx tanstack`. This is the stack the state ladder assumes.
@@ -1,30 +0,0 @@
1
- ---
2
- name: imf-web-ui-setup
3
- description:
4
- "One-time wiring of @imfusion/web-ui into a consumer project: the styles import and the WebUIProvider wrapper. Load when
5
- installing the library for the first time, or when its components render unstyled or without theme context."
6
- ---
7
-
8
- # imf-web-ui-setup
9
-
10
- Every consumer entry point needs exactly two lines, in this order:
11
-
12
- ```tsx
13
- import "@imfusion/web-ui/styles.css";
14
- import { WebUIProvider, Button } from "@imfusion/web-ui";
15
- ```
16
-
17
- Wrap the app root in `<WebUIProvider>` once. Components rendered outside it won't have the theme/CSS-variable context they
18
- expect.
19
-
20
- Never import a Base UI (or other upstream) stylesheet or component directly — everything a web-ui component needs is already
21
- inside `styles.css` and the package's own exports; reaching around web-ui to the upstream library is always wrong, even if
22
- the upstream docs show it that way.
23
-
24
- ## Symptoms of a broken setup
25
-
26
- - **Components render but look unstyled** — the `styles.css` import is missing from the entry point.
27
- - **Components render but ignore the theme (wrong colors, no CSS variables resolving)** — they're mounted outside
28
- `<WebUIProvider>`.
29
- - **An integration component throws on import** — its optional peer dependency isn't installed; check the component's
30
- description in the docgen index (`imf-web-ui-components`) for which peer to add to your `package.json`.