@imfusion/web-ui 0.7.2-dev.10.gaae39295 → 0.7.2-dev.14.gb40820c1

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/CHANGELOG.md CHANGED
@@ -4,6 +4,8 @@
4
4
 
5
5
  ### Added
6
6
 
7
+ - Vite plugins — Nikolaus Brunner <brunner@imfusion.com>
8
+ - `localWebUILink` dedupes the library's peer dependencies in an app that links a local checkout.
7
9
  - Storybook — Nikolaus Brunner <brunner@imfusion.com>
8
10
  - The token drawer offers Kode Mono as a mono font.
9
11
 
@@ -16,6 +18,10 @@
16
18
  - `NavigationMenu.InlineSubmenu` spaces its rail and detail pane with one flyout padding.
17
19
  - Agent skills — Nikolaus Brunner <brunner@imfusion.com>
18
20
  - `imf-web-ui-update` measures the delta from a prerelease baseline by diffing the old package's types and styles.
21
+ - The component and story dev skills name the Storybook CLI command for each lookup and check, and route only pixels, DOM,
22
+ focus, and console checks to Chrome DevTools.
23
+ - `web-ui-dev-new-component` checks existing primitives before any upstream lookup, for a URL or a description, and routes
24
+ a partial match to `web-ui-dev-update-component`.
19
25
 
20
26
  ### Fixed
21
27
 
@@ -30,6 +36,7 @@
30
36
  - The preview stays dark while a page loads in dark mode.
31
37
  - Agent skills — Nikolaus Brunner <brunner@imfusion.com>
32
38
  - `npx web-ui install skills` formats the skill version markers with the project's Prettier.
39
+ - `imf-web-ui-audit` has a checklist box for every tooling section, including Vite plugins.
33
40
 
34
41
  ---
35
42
 
package/README.md CHANGED
@@ -87,3 +87,35 @@ The main development skills are:
87
87
  | `/web-ui-dev-claude-design-sync` | Update the design system in Claude Design. |
88
88
  | `/web-ui-dev-teardown-worktree` | Remove a finished worktree. |
89
89
  | `/web-ui-dev-mcp` | Recover one optional MCP capability. |
90
+
91
+ ### Work on it from a consumer app
92
+
93
+ To change the library while running an app that uses it, point the app's dependency at your checkout:
94
+
95
+ ```json
96
+ "@imfusion/web-ui": "file:../web-ui"
97
+ ```
98
+
99
+ Run `npm install` in the app, then keep `npm run dev:lib` running here. The package resolves to `dist/`, so the app resolves
100
+ nothing until the first watch build has finished, and sees each later change once the rebuild has written it.
101
+
102
+ npm links the checkout instead of copying it. Without help, the app then loads two copies of React, its own and the one in
103
+ this checkout's `node_modules`, and every hook fails with "Invalid hook call". Register `localWebUILink` in the app's Vite
104
+ config to prevent that:
105
+
106
+ ```ts
107
+ import { localWebUILink } from "@imfusion/web-ui/vite";
108
+
109
+ export default defineConfig({
110
+ plugins: [react(), localWebUILink()]
111
+ });
112
+ ```
113
+
114
+ It dedupes every peer dependency of the library, including `@imfusion/sdk` and `@imfusion/sdk-react`. For an installed
115
+ package it does nothing, so it can stay in the config.
116
+
117
+ It covers the browser in development and in the build. Server rendering, including React Router prerendering, still loads
118
+ dependencies through Node, which picks the checkout's React. Base UI's `use-sync-external-store` dependency is CommonJS, so
119
+ Popover, Select, and other stateful parts can still fail on the server.
120
+
121
+ Change the dependency back before you commit the app.
@@ -0,0 +1,26 @@
1
+ import { existsSync as e } from "node:fs";
2
+ import { dirname as t, resolve as n } from "node:path";
3
+ import { fileURLToPath as r } from "node:url";
4
+ import { spawnSync as i } from "node:child_process";
5
+ //#region src/cli/paths.ts
6
+ function a(r) {
7
+ let i = r;
8
+ for (; !e(n(i, "package.json"));) {
9
+ let e = t(i);
10
+ if (e === i) throw Error(`Could not find package.json above ${r}`);
11
+ i = e;
12
+ }
13
+ return i;
14
+ }
15
+ var o = a(t(r(import.meta.url))), s = process.cwd();
16
+ function c(r) {
17
+ let a = s;
18
+ for (; a !== t(a) && !e(n(a, "node_modules", ".bin"));) a = t(a);
19
+ let o = n(a, "node_modules", ".bin", "prettier");
20
+ e(o) && i(o, ["--write", ...r], {
21
+ cwd: s,
22
+ stdio: "ignore"
23
+ });
24
+ }
25
+ //#endregion
26
+ export { o as n, s as r, c as t };
@@ -1,2 +1,3 @@
1
1
  export { imfusionBrandAssets } from './brand-assets.ts';
2
+ export { localWebUILink } from './local-web-ui-link.ts';
2
3
  export { readableCssModuleNames, type ReadableCssModuleNamesOptions } from './readable-css-module-names.ts';
@@ -0,0 +1,8 @@
1
+ import { Plugin, UserConfig } from 'vite';
2
+ /** Returns `undefined` for an installed package; only a checkout outside `node_modules` needs the config. */
3
+ export declare function localWebUILinkConfig(root: string): UserConfig | undefined;
4
+ /**
5
+ * Lets a consumer link a local checkout (`"@imfusion/web-ui": "file:../web-ui"`) without loading two copies of React in
6
+ * client dev and build. SSR is not covered. Does nothing for an installed package, so it can stay registered.
7
+ */
8
+ export declare function localWebUILink(): Plugin;
package/dist/vite.js CHANGED
@@ -1,33 +1,34 @@
1
- import { readFileSync as e, readdirSync as t } from "node:fs";
2
- import { dirname as n, join as r } from "node:path";
3
- import { fileURLToPath as i } from "node:url";
1
+ import { n as e } from "./paths-Oi0v8x-w.js";
2
+ import { readFileSync as t, readdirSync as n, realpathSync as r } from "node:fs";
3
+ import { dirname as i, join as a, resolve as o, sep as s } from "node:path";
4
+ import { fileURLToPath as c } from "node:url";
4
5
  //#region src/vite/brand-assets.ts
5
- var a = n(i(import.meta.resolve("@imfusion/web-ui/assets/favicon/favicon.svg"))), o = t(a).map((t) => ({
6
- name: t,
7
- source: e(r(a, t))
8
- })), s = {
6
+ var l = i(c(import.meta.resolve("@imfusion/web-ui/assets/favicon/favicon.svg"))), u = n(l).map((e) => ({
7
+ name: e,
8
+ source: t(a(l, e))
9
+ })), d = {
9
10
  ".ico": "image/x-icon",
10
11
  ".png": "image/png",
11
12
  ".svg": "image/svg+xml"
12
13
  };
13
- function c(e) {
14
- return s[e.slice(e.lastIndexOf("."))] ?? "application/octet-stream";
14
+ function f(e) {
15
+ return d[e.slice(e.lastIndexOf("."))] ?? "application/octet-stream";
15
16
  }
16
- function l() {
17
+ function p() {
17
18
  return {
18
19
  name: "imf-ui:brand-assets",
19
20
  configureServer(e) {
20
21
  e.middlewares.use((e, t, n) => {
21
- let r = o.find(({ name: t }) => e.url?.split("?")[0] === `/${t}`);
22
+ let r = u.find(({ name: t }) => e.url?.split("?")[0] === `/${t}`);
22
23
  if (!r) {
23
24
  n();
24
25
  return;
25
26
  }
26
- t.setHeader("Content-Type", c(r.name)), t.end(r.source);
27
+ t.setHeader("Content-Type", f(r.name)), t.end(r.source);
27
28
  });
28
29
  },
29
30
  generateBundle() {
30
- for (let e of o) this.emitFile({
31
+ for (let e of u) this.emitFile({
31
32
  fileName: e.name,
32
33
  source: e.source,
33
34
  type: "asset"
@@ -36,20 +37,34 @@ function l() {
36
37
  };
37
38
  }
38
39
  //#endregion
40
+ //#region src/vite/local-web-ui-link.ts
41
+ function m(e) {
42
+ if (r(e).split(s).includes("node_modules")) return;
43
+ let n = JSON.parse(t(o(e, "package.json"), "utf-8")), i = typeof n == "object" && n ? Reflect.get(n, "peerDependencies") : {};
44
+ if (typeof i != "object" || !i) throw Error(`${e}/package.json has no peerDependencies`);
45
+ return { resolve: { dedupe: Object.keys(i) } };
46
+ }
47
+ function h() {
48
+ return {
49
+ name: "imf-ui:local-web-ui-link",
50
+ config: () => m(e)
51
+ };
52
+ }
53
+ //#endregion
39
54
  //#region src/vite/readable-css-module-names.ts
40
- function u(e, t) {
55
+ function g(e, t) {
41
56
  let { lightningcss: n, modules: r, transformer: i } = e.css ?? {}, a = n?.cssModules;
42
57
  if (!(typeof a == "boolean" || r === !1) && !a?.pattern && !(typeof r == "object" && r.generateScopedName)) return i === "postcss" ? { css: { modules: { generateScopedName: t } } } : { css: {
43
58
  transformer: "lightningcss",
44
59
  lightningcss: { cssModules: { pattern: t } }
45
60
  } };
46
61
  }
47
- function d({ prefix: e }) {
62
+ function _({ prefix: e }) {
48
63
  let t = `${e}-[name]-[local]`;
49
64
  return {
50
65
  name: "imf-ui:readable-css-module-names",
51
- config: (e) => u(e, t)
66
+ config: (e) => g(e, t)
52
67
  };
53
68
  }
54
69
  //#endregion
55
- export { l as imfusionBrandAssets, d as readableCssModuleNames };
70
+ export { p as imfusionBrandAssets, h as localWebUILink, _ as readableCssModuleNames };
@@ -1,11 +1,10 @@
1
1
  #!/usr/bin/env node
2
- import { chmodSync as e, cpSync as t, existsSync as n, lstatSync as r, mkdirSync as i, readFileSync as a, readdirSync as o, readlinkSync as s, realpathSync as c, rmSync as l, symlinkSync as u, writeFileSync as d } from "node:fs";
3
- import { dirname as f, relative as p, resolve as m } from "node:path";
4
- import { fileURLToPath as ee } from "node:url";
5
- import { Command as te, Option as ne } from "commander";
6
- import * as h from "@clack/prompts";
7
- import { spawnSync as g } from "node:child_process";
8
- var _ = {
2
+ import { n as e, r as t, t as n } from "./paths-Oi0v8x-w.js";
3
+ import { chmodSync as r, cpSync as i, existsSync as a, lstatSync as o, mkdirSync as s, readFileSync as c, readdirSync as l, readlinkSync as u, realpathSync as d, rmSync as f, symlinkSync as p, writeFileSync as m } from "node:fs";
4
+ import { dirname as h, relative as g, resolve as _ } from "node:path";
5
+ import { Command as ee, Option as te } from "commander";
6
+ import * as v from "@clack/prompts";
7
+ var y = {
9
8
  harnesses: {
10
9
  claude: {
11
10
  label: "Claude Code",
@@ -25,124 +24,100 @@ var _ = {
25
24
  skillsDir: ".agents/skills",
26
25
  linkDir: ".claude/skills",
27
26
  versionMarker: ".imf-web-ui-skill-version.json"
28
- };
29
- //#endregion
30
- //#region src/cli/paths.ts
31
- function v(e) {
32
- let t = e;
33
- for (; !n(m(t, "package.json"));) {
34
- let n = f(t);
35
- if (n === t) throw Error(`Could not find package.json above ${e}`);
36
- t = n;
37
- }
38
- return t;
39
- }
40
- var y = v(f(ee(import.meta.url))), b = process.cwd();
41
- function x(e) {
42
- let t = b;
43
- for (; t !== f(t) && !n(m(t, "node_modules", ".bin"));) t = f(t);
44
- let r = m(t, "node_modules", ".bin", "prettier");
45
- n(r) && g(r, ["--write", ...e], {
46
- cwd: b,
47
- stdio: "ignore"
48
- });
49
- }
50
- //#endregion
51
- //#region src/cli/agents-md.ts
52
- var re = `<!-- ${_.namespace}:begin`, S = `<!-- ${_.namespace}:end -->`, ie = m(y, _.templatesDir, "AGENTS.md"), C = m(b, "AGENTS.md");
53
- function w(e) {
54
- let t = e.indexOf(re), n = e.indexOf(S);
55
- return t === -1 || n === -1 ? null : e.slice(t, n + S.length);
27
+ }, ne = `<!-- ${y.namespace}:begin`, b = `<!-- ${y.namespace}:end -->`, re = _(e, y.templatesDir, "AGENTS.md"), x = _(t, "AGENTS.md");
28
+ function S(e) {
29
+ let t = e.indexOf(ne), n = e.indexOf(b);
30
+ return t === -1 || n === -1 ? null : e.slice(t, n + b.length);
56
31
  }
57
- function ae() {
58
- if (!n(C)) return "absent";
59
- let e = w(a(ie, "utf-8"));
32
+ function ie() {
33
+ if (!a(x)) return "absent";
34
+ let e = S(c(re, "utf-8"));
60
35
  if (!e) return "absent";
61
- let t = a(C, "utf-8"), r = w(t), i = r ? t.replace(r, e) : `${t.trimEnd()}\n\n${e}\n`;
62
- return i === t ? "unchanged" : (d(C, i), r ? "updated" : "added");
36
+ let t = c(x, "utf-8"), n = S(t), r = n ? t.replace(n, e) : `${t.trimEnd()}\n\n${e}\n`;
37
+ return r === t ? "unchanged" : (m(x, r), n ? "updated" : "added");
63
38
  }
64
39
  //#endregion
65
40
  //#region src/cli/skills.ts
66
- var T = Object.keys(_.harnesses).filter((e) => e in _.harnesses), oe = [...T, "all"];
67
- function E(e) {
68
- return e === "all" ? T : [e];
41
+ var C = Object.keys(y.harnesses).filter((e) => e in y.harnesses), w = [...C, "all"];
42
+ function T(e) {
43
+ return e === "all" ? C : [e];
69
44
  }
70
- function D(e) {
71
- return `./${p(b, e)}`;
45
+ function E(e) {
46
+ return `./${g(t, e)}`;
72
47
  }
73
- var O = _.namespace, k = _.versionMarker, A = m(y, _.skillsSource), j = m(b, _.skillsDir), M = m(b, _.linkDir), N = class extends Error {}, P = JSON.parse(a(m(y, "package.json"), "utf-8"));
74
- if (typeof P != "object" || !P || !("version" in P) || typeof P.version != "string") throw Error(`${m(y, "package.json")} has no string "version" field`);
75
- var F = P.version;
76
- function I(e) {
77
- return e === O || e.startsWith(`${O}-`);
48
+ var D = y.namespace, O = y.versionMarker, k = _(e, y.skillsSource), A = _(t, y.skillsDir), j = _(t, y.linkDir), M = class extends Error {}, N = JSON.parse(c(_(e, "package.json"), "utf-8"));
49
+ if (typeof N != "object" || !N || !("version" in N) || typeof N.version != "string") throw Error(`${_(e, "package.json")} has no string "version" field`);
50
+ var P = N.version;
51
+ function F(e) {
52
+ return e === D || e.startsWith(`${D}-`);
78
53
  }
79
- function L() {
80
- return n(A) ? o(A, { withFileTypes: !0 }).filter((e) => e.isDirectory() && I(e.name)).map((e) => e.name).sort() : [];
54
+ function I() {
55
+ return a(k) ? l(k, { withFileTypes: !0 }).filter((e) => e.isDirectory() && F(e.name)).map((e) => e.name).sort() : [];
81
56
  }
82
- function se(e) {
83
- let t = m(j, e, k);
84
- if (!n(t)) return null;
57
+ function ae(e) {
58
+ let t = _(A, e, O);
59
+ if (!a(t)) return null;
85
60
  try {
86
- let e = JSON.parse(a(t, "utf-8"));
61
+ let e = JSON.parse(c(t, "utf-8"));
87
62
  if (typeof e != "object" || !e) return null;
88
63
  let n = "harnesses" in e && Array.isArray(e.harnesses) ? e.harnesses : null;
89
- return n ? T.filter((e) => n.includes(e)) : null;
64
+ return n ? C.filter((e) => n.includes(e)) : null;
90
65
  } catch {
91
66
  return null;
92
67
  }
93
68
  }
94
- function R(e) {
69
+ function L(e) {
95
70
  for (let t of e) {
96
- let e = se(t);
71
+ let e = ae(t);
97
72
  if (e && e.length > 0) return e;
98
73
  }
99
74
  return null;
100
75
  }
101
- function z(e, t, r) {
102
- if (n(e)) for (let n of o(e).filter(I)) t.has(n) || (l(m(e, n), {
76
+ function R(e, t, n) {
77
+ if (a(e)) for (let r of l(e).filter(F)) t.has(r) || (f(_(e, r), {
103
78
  recursive: !0,
104
79
  force: !0
105
- }), h.log.info(`Removed skill: ${n} (${r})`));
80
+ }), v.log.info(`Removed skill: ${r} (${n})`));
106
81
  }
107
- function ce() {
108
- return n(M) && n(j) && c(M) === c(j);
82
+ function oe() {
83
+ return a(j) && a(A) && d(j) === d(A);
109
84
  }
110
- function le(e) {
111
- let t = m(M, e), n = p(M, m(j, e)), a = r(t, { throwIfNoEntry: !1 });
112
- a?.isSymbolicLink() && s(t) === n || (a && l(t, {
85
+ function z(e) {
86
+ let t = _(j, e), n = g(j, _(A, e)), r = o(t, { throwIfNoEntry: !1 });
87
+ r?.isSymbolicLink() && u(t) === n || (r && f(t, {
113
88
  recursive: !0,
114
89
  force: !0
115
- }), i(M, { recursive: !0 }), u(n, t));
90
+ }), s(j, { recursive: !0 }), p(n, t));
116
91
  }
117
- function ue(e, n) {
92
+ function se(e, t) {
118
93
  try {
119
- i(j, { recursive: !0 });
94
+ s(A, { recursive: !0 });
120
95
  let r = [];
121
- for (let i of e) {
122
- let e = m(j, i);
123
- l(e, {
96
+ for (let n of e) {
97
+ let e = _(A, n);
98
+ f(e, {
124
99
  recursive: !0,
125
100
  force: !0
126
- }), t(m(A, i), e, { recursive: !0 });
127
- let a = m(e, k);
128
- d(a, JSON.stringify({
129
- version: F,
130
- harnesses: n
101
+ }), i(_(k, n), e, { recursive: !0 });
102
+ let a = _(e, O);
103
+ m(a, JSON.stringify({
104
+ version: P,
105
+ harnesses: t
131
106
  }, null, 2) + "\n"), r.push(a);
132
107
  }
133
- if (x(r), z(j, new Set(e), "no longer in this bundle"), h.log.success(`Skills -> ${D(j)}/${O}*`), ce()) return;
134
- if (n.includes("claude")) {
135
- for (let t of e) le(t);
136
- z(M, new Set(e), "no longer in this bundle"), h.log.success(`Claude Code -> ${D(M)}/${O}* (symlinks -> ${D(j)})`);
137
- } else z(M, /* @__PURE__ */ new Set(), "claude not selected");
108
+ if (n(r), R(A, new Set(e), "no longer in this bundle"), v.log.success(`Skills -> ${E(A)}/${D}*`), oe()) return;
109
+ if (t.includes("claude")) {
110
+ for (let t of e) z(t);
111
+ R(j, new Set(e), "no longer in this bundle"), v.log.success(`Claude Code -> ${E(j)}/${D}* (symlinks -> ${E(A)})`);
112
+ } else R(j, /* @__PURE__ */ new Set(), "claude not selected");
138
113
  } catch (e) {
139
- throw e instanceof Error ? new N(e.message) : e;
114
+ throw e instanceof Error ? new M(e.message) : e;
140
115
  }
141
116
  }
142
117
  //#endregion
143
118
  //#region src/cli/hooks.ts
144
- var B = m(y, _.templatesDir), V = m(B, "hooks"), H = m(b, _.hooksDir), U = class extends Error {};
145
- function de(e, t) {
119
+ var B = _(e, y.templatesDir), V = _(B, "hooks"), H = _(t, y.hooksDir), U = class extends Error {};
120
+ function ce(e, t) {
146
121
  if (typeof e != "object" || !e || Array.isArray(e)) throw new U(`${t} is not a JSON object.`);
147
122
  let n = { ...e };
148
123
  if ("hooks" in n) {
@@ -154,27 +129,27 @@ function de(e, t) {
154
129
  function W(e) {
155
130
  let t;
156
131
  try {
157
- t = JSON.parse(a(e, "utf-8"));
132
+ t = JSON.parse(c(e, "utf-8"));
158
133
  } catch {
159
134
  throw new U(`${e} is not valid JSON.`);
160
135
  }
161
- return de(t, e);
136
+ return ce(t, e);
162
137
  }
163
- function fe() {
164
- let n = o(V).filter((e) => e.endsWith(".sh"));
165
- i(H, { recursive: !0 });
166
- for (let r of n) {
167
- let n = m(H, r);
168
- t(m(V, r), n, { force: !0 }), e(n, 493);
138
+ function le() {
139
+ let e = l(V).filter((e) => e.endsWith(".sh"));
140
+ s(H, { recursive: !0 });
141
+ for (let t of e) {
142
+ let e = _(H, t);
143
+ i(_(V, t), e, { force: !0 }), r(e, 493);
169
144
  }
170
- let r = o(H).filter((e) => e.endsWith(".sh") && !n.includes(e));
171
- for (let e of r) l(m(H, e), { force: !0 });
145
+ let t = l(H).filter((t) => t.endsWith(".sh") && !e.includes(t));
146
+ for (let e of t) f(_(H, e), { force: !0 });
172
147
  return {
173
- installed: n,
174
- removed: r
148
+ installed: e,
149
+ removed: t
175
150
  };
176
151
  }
177
- var G = `${_.hooksDir}/`;
152
+ var G = `${y.hooksDir}/`;
178
153
  function K(e) {
179
154
  if (typeof e != "string") return null;
180
155
  let t = e.lastIndexOf(G);
@@ -195,7 +170,7 @@ function X(e) {
195
170
  let t = J(e);
196
171
  return t ? t.map((e) => K(Y(e))).filter((e) => e !== null) : [];
197
172
  }
198
- function pe(e, t, n) {
173
+ function ue(e, t, n) {
199
174
  e.hooks ??= {};
200
175
  let r = 0, i = 0, a = [];
201
176
  for (let [r, o] of Object.entries(e.hooks)) {
@@ -228,73 +203,73 @@ function pe(e, t, n) {
228
203
  removed: i
229
204
  };
230
205
  }
231
- function me(e) {
232
- let t = /* @__PURE__ */ new Map(), r = /* @__PURE__ */ new Map();
233
- for (let i of T) {
234
- let a = m(b, _.harnesses[i].hookSettings);
235
- if (n(a) && t.set(i, W(a)), e.includes(i)) {
236
- let e = m(B, _.harnesses[i].hookTemplate);
237
- r.set(i, W(e).hooks ?? {});
206
+ function de(e) {
207
+ let r = /* @__PURE__ */ new Map(), i = /* @__PURE__ */ new Map();
208
+ for (let n of C) {
209
+ let o = _(t, y.harnesses[n].hookSettings);
210
+ if (a(o) && r.set(n, W(o)), e.includes(n)) {
211
+ let e = _(B, y.harnesses[n].hookTemplate);
212
+ i.set(n, W(e).hooks ?? {});
238
213
  }
239
214
  }
240
- let { installed: a, removed: o } = fe();
241
- h.log.success(`Agent hooks -> ${D(H)} (${String(a.length)} scripts)`), o.length > 0 && h.log.info(`Removed stale hook script(s): ${o.join(", ")} (no longer shipped)`);
242
- for (let n of T) {
243
- let o = e.includes(n);
244
- if (!o && !t.has(n)) continue;
245
- let s = m(b, _.harnesses[n].hookSettings), c = r.get(n) ?? {}, l = t.get(n) ?? {}, u = pe(l, c, o ? a : []);
246
- if (u.added === 0 && u.removed === 0) {
247
- o && h.log.info(`Hook registrations already present in ${D(s)}`);
215
+ let { installed: o, removed: c } = le();
216
+ v.log.success(`Agent hooks -> ${E(H)} (${String(o.length)} scripts)`), c.length > 0 && v.log.info(`Removed stale hook script(s): ${c.join(", ")} (no longer shipped)`);
217
+ for (let a of C) {
218
+ let c = e.includes(a);
219
+ if (!c && !r.has(a)) continue;
220
+ let l = _(t, y.harnesses[a].hookSettings), u = i.get(a) ?? {}, d = r.get(a) ?? {}, f = ue(d, u, c ? o : []);
221
+ if (f.added === 0 && f.removed === 0) {
222
+ c && v.log.info(`Hook registrations already present in ${E(l)}`);
248
223
  continue;
249
224
  }
250
- i(f(s), { recursive: !0 }), d(s, JSON.stringify(l, null, 2) + "\n"), x([s]);
251
- let p = [u.added > 0 && `registered ${String(u.added)}`, u.removed > 0 && `removed ${String(u.removed)} stale`].filter(Boolean);
252
- h.log.success(`${p.join(", ")} hook registration(s) in ${D(s)}`);
225
+ s(h(l), { recursive: !0 }), m(l, JSON.stringify(d, null, 2) + "\n"), n([l]);
226
+ let p = [f.added > 0 && `registered ${String(f.added)}`, f.removed > 0 && `removed ${String(f.removed)} stale`].filter(Boolean);
227
+ v.log.success(`${p.join(", ")} hook registration(s) in ${E(l)}`);
253
228
  }
254
229
  }
255
230
  //#endregion
256
231
  //#region src/cli/index.ts
257
232
  function Z() {
258
- return new ne("--harness <harness>", "claude, codex, or all").choices(oe);
233
+ return new te("--harness <harness>", "claude, codex, or all").choices(w);
259
234
  }
260
- async function he(e, t, n) {
261
- if (t) return h.log.info(`Harness from --harness flag: ${t.join(", ")}`), t;
262
- let r = R(e);
263
- if (r && !n) return h.log.info(`Refreshing the existing install: ${r.join(", ")} — pass --reconfigure to choose differently.`), r;
235
+ async function fe(e, t, n) {
236
+ if (t) return v.log.info(`Harness from --harness flag: ${t.join(", ")}`), t;
237
+ let r = L(e);
238
+ if (r && !n) return v.log.info(`Refreshing the existing install: ${r.join(", ")} — pass --reconfigure to choose differently.`), r;
264
239
  if (!process.stdin.isTTY) return console.error("Pass --harness=claude|codex|all — no terminal to prompt in."), process.exitCode = 1, null;
265
- let i = await h.multiselect({
240
+ let i = await v.multiselect({
266
241
  message: "Install into which harness (or harnesses)?",
267
- options: T.map((e) => ({
242
+ options: C.map((e) => ({
268
243
  value: e,
269
- label: _.harnesses[e].label
244
+ label: y.harnesses[e].label
270
245
  })),
271
246
  required: !0
272
247
  });
273
- return h.isCancel(i) ? (h.cancel("Cancelled — nothing installed."), null) : i;
248
+ return v.isCancel(i) ? (v.cancel("Cancelled — nothing installed."), null) : i;
274
249
  }
275
- async function ge(e) {
276
- let t = e.harness ? E(e.harness) : null, n = L();
277
- if (h.intro(`@imfusion/web-ui install skills — ${String(n.length)} skills`), n.length === 0) {
278
- h.log.error("No skills found. Reinstall @imfusion/web-ui and try again."), h.outro("Nothing installed."), process.exitCode = 1;
250
+ async function pe(e) {
251
+ let t = e.harness ? T(e.harness) : null, n = I();
252
+ if (v.intro(`@imfusion/web-ui install skills — ${String(n.length)} skills`), n.length === 0) {
253
+ v.log.error("No skills found. Reinstall @imfusion/web-ui and try again."), v.outro("Nothing installed."), process.exitCode = 1;
279
254
  return;
280
255
  }
281
- let r = await he(n, t, e.reconfigure === !0);
256
+ let r = await fe(n, t, e.reconfigure === !0);
282
257
  if (!r) return;
283
- h.log.message(`Skills in this bundle:\n${n.map((e) => ` - ${e}`).join("\n")}`);
258
+ v.log.message(`Skills in this bundle:\n${n.map((e) => ` - ${e}`).join("\n")}`);
284
259
  try {
285
- ue(n, r);
260
+ se(n, r);
286
261
  } catch (e) {
287
- if (e instanceof N) {
288
- h.log.error(e.message), h.outro("Install failed."), process.exitCode = 1;
262
+ if (e instanceof M) {
263
+ v.log.error(e.message), v.outro("Install failed."), process.exitCode = 1;
289
264
  return;
290
265
  }
291
266
  throw e;
292
267
  }
293
- let i = ae();
294
- (i === "updated" || i === "added") && h.log.success(`Refreshed the ${_.namespace} block in ./AGENTS.md`), h.outro(`Done — ${String(n.length)} skills installed.`);
268
+ let i = ie();
269
+ (i === "updated" || i === "added") && v.log.success(`Refreshed the ${y.namespace} block in ./AGENTS.md`), v.outro(`Done — ${String(n.length)} skills installed.`);
295
270
  }
296
- function _e(e) {
297
- let t = e.harness ? E(e.harness) : null, n = R(L());
271
+ function me(e) {
272
+ let t = e.harness ? T(e.harness) : null, n = L(I());
298
273
  if (!t && !n) {
299
274
  console.error("No harness recorded. Pass --harness=claude|codex|all."), process.exitCode = 1;
300
275
  return;
@@ -305,9 +280,9 @@ function _e(e) {
305
280
  process.exitCode = 1;
306
281
  return;
307
282
  }
308
- h.intro("@imfusion/web-ui install hooks");
283
+ v.intro("@imfusion/web-ui install hooks");
309
284
  try {
310
- me(r);
285
+ de(r);
311
286
  } catch (e) {
312
287
  if (e instanceof U) {
313
288
  console.error(e.message), process.exitCode = 1;
@@ -315,12 +290,12 @@ function _e(e) {
315
290
  }
316
291
  throw e;
317
292
  }
318
- h.outro("Done — agent hooks installed.");
293
+ v.outro("Done — agent hooks installed.");
319
294
  }
320
- var Q = new te();
321
- Q.name("web-ui").description("Command-line tools for @imfusion/web-ui").version(F);
295
+ var Q = new ee();
296
+ Q.name("web-ui").description("Command-line tools for @imfusion/web-ui").version(P);
322
297
  var $ = Q.command("install").description("Install @imfusion/web-ui agent skills or hooks into a project");
323
- $.command("skills").description("Install skills for the chosen harness").addOption(Z()).option("--reconfigure", "Re-open the harness selection prompt on an existing install").action(ge), $.command("hooks").description("Install agent lifecycle hooks (requires skills already installed for the harness)").addOption(Z()).action(_e), $.action(() => {
298
+ $.command("skills").description("Install skills for the chosen harness").addOption(Z()).option("--reconfigure", "Re-open the harness selection prompt on an existing install").action(pe), $.command("hooks").description("Install agent lifecycle hooks (requires skills already installed for the harness)").addOption(Z()).action(me), $.action(() => {
324
299
  $.help();
325
300
  }), Q.action(() => {
326
301
  Q.help();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@imfusion/web-ui",
3
- "version": "0.7.2-dev.10.gaae39295",
3
+ "version": "0.7.2-dev.14.gb40820c1",
4
4
  "description": "The official Web UI component library for ImFusion web apps",
5
5
  "author": "ImFusion GmbH",
6
6
  "homepage": "https://imfusion.com",
@@ -101,10 +101,9 @@ sections one to one — change a topic's sections and this file follows in the s
101
101
  - [ ] Devtools
102
102
  - [ ] Docs over memory
103
103
  - [ ] Prettier
104
- - [ ] ESLint
105
- - [ ] tsconfig
106
- - [ ] Staged files
107
- - [ ] CSS class names
104
+ - [ ] ESLint and TypeScript
105
+ - [ ] Pre-commit checks
106
+ - [ ] Vite plugins
108
107
 
109
108
  ## npm project — `npm-project`
110
109
 
@@ -88,3 +88,6 @@ export default defineConfig({
88
88
  `imfusionBrandAssets` serves the complete favicon set during development and emits it at stable root URLs during builds.
89
89
  Register `readableCssModuleNames` in every compiler that processes the app's CSS, including development, Storybook, and
90
90
  production. A compiler left out produces different class names and styles that appear to work only in some environments.
91
+
92
+ `localWebUILink` dedupes the library's peer dependencies when `@imfusion/web-ui` is linked from a local checkout
93
+ (`file:../web-ui`), so the app loads one React. It does nothing for an installed package and does not cover server rendering.