@imfusion/web-ui 0.5.1-dev.49.gd1a7230f → 0.5.1-dev.5.gb4de52d7
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 +55 -156
- package/bin/install-skill.js +180 -0
- package/dist/index.d.ts +0 -2
- package/dist/index.js +4317 -4956
- package/dist/integrations/code-highlight/highlighter.d.ts +3 -32
- package/dist/integrations/code-highlight.js +48 -188
- package/dist/integrations/image-display-options.js +1 -1
- package/dist/style.css +1 -1
- package/dist/{tabs-CVp_SgBl.js → tabs-DqBFSqq6.js} +1 -1
- package/package.json +25 -39
- package/src/docgen/doc.gen.json +0 -327
- package/src/llms/llms.gen.txt +0 -12
- package/src/llms/skills/imf-web-ui/SKILL.md +12 -13
- package/src/llms/skills/imf-web-ui-components/SKILL.md +1 -2
- package/src/llms/skills/imf-web-ui-frontend-patterns/SKILL.md +93 -0
- package/src/llms/skills/imf-web-ui-frontend-patterns/references/code-conventions.md +133 -0
- package/src/llms/skills/imf-web-ui-frontend-patterns/references/react-patterns.md +94 -0
- package/src/llms/skills/imf-web-ui-imfusion-frontend-setup/SKILL.md +201 -0
- package/src/llms/skills/imf-web-ui-setup/SKILL.md +37 -67
- package/src/llms/skills/imf-web-ui-ux/SKILL.md +4 -4
- package/src/llms/skills/imf-web-ui-ux/references/forms.md +2 -2
- package/bin/install.js +0 -428
- package/bin/install.test.ts +0 -329
- package/dist/build/vite-css-module-names/index.d.ts +0 -20
- package/dist/build/vite-css-module-names.js +0 -17
- package/dist/components/field/field.d.ts +0 -104
- package/dist/components/field/field.meta.d.ts +0 -2
- package/dist/components/field/index.d.ts +0 -2
- package/dist/components/fieldset/fieldset.d.ts +0 -29
- package/dist/components/fieldset/fieldset.meta.d.ts +0 -2
- package/dist/components/fieldset/index.d.ts +0 -2
- package/dist/integrations/code-highlight/language-patterns.d.ts +0 -7
- package/dist/integrations/code-highlight/languages/cmake.d.ts +0 -1
- package/dist/integrations/code-highlight/languages/cpp.d.ts +0 -1
- package/dist/integrations/code-highlight/languages/python.d.ts +0 -1
- package/dist/llms/gen-tokens.d.ts +0 -7
- package/src/llms/install-templates/AGENTS.md +0 -34
- package/src/llms/install-templates/codex-hooks.json +0 -44
- package/src/llms/install-templates/hooks/baseline-staleness.sh +0 -17
- package/src/llms/install-templates/hooks/session-start.sh +0 -5
- package/src/llms/install-templates/hooks/stop.sh +0 -18
- package/src/llms/install-templates/hooks/subagent-start.sh +0 -5
- package/src/llms/install-templates/hooks/user-prompt-submit.sh +0 -5
- package/src/llms/install-templates/settings.json +0 -45
- package/src/llms/skills/imf-web-ui-audit/SKILL.md +0 -119
- package/src/llms/skills/imf-web-ui-conventions/SKILL.md +0 -57
- package/src/llms/skills/imf-web-ui-conventions/templates/AUDIT_CHECKLIST.md +0 -141
- package/src/llms/skills/imf-web-ui-conventions/templates/REPORT.md +0 -45
- package/src/llms/skills/imf-web-ui-conventions/topics/agent-tooling.md +0 -82
- package/src/llms/skills/imf-web-ui-conventions/topics/assets.md +0 -27
- package/src/llms/skills/imf-web-ui-conventions/topics/authentication.md +0 -65
- package/src/llms/skills/imf-web-ui-conventions/topics/class-names.md +0 -50
- package/src/llms/skills/imf-web-ui-conventions/topics/components.md +0 -101
- package/src/llms/skills/imf-web-ui-conventions/topics/data.md +0 -221
- package/src/llms/skills/imf-web-ui-conventions/topics/docs-structure.md +0 -40
- package/src/llms/skills/imf-web-ui-conventions/topics/git.md +0 -34
- package/src/llms/skills/imf-web-ui-conventions/topics/library-boundary.md +0 -33
- package/src/llms/skills/imf-web-ui-conventions/topics/library-setup.md +0 -26
- package/src/llms/skills/imf-web-ui-conventions/topics/npm-project.md +0 -53
- package/src/llms/skills/imf-web-ui-conventions/topics/project-structure.md +0 -44
- package/src/llms/skills/imf-web-ui-conventions/topics/react.md +0 -109
- package/src/llms/skills/imf-web-ui-conventions/topics/styling.md +0 -88
- package/src/llms/skills/imf-web-ui-conventions/topics/testing.md +0 -25
- package/src/llms/skills/imf-web-ui-conventions/topics/tokens.md +0 -7
- package/src/llms/skills/imf-web-ui-conventions/topics/tooling.md +0 -116
- package/src/llms/skills/imf-web-ui-conventions/topics/typescript.md +0 -73
- package/src/llms/skills/imf-web-ui-conventions/topics/validation.md +0 -62
- package/src/llms/skills/imf-web-ui-update/SKILL.md +0 -157
- package/src/llms/tokens.gen.json +0 -887
package/bin/install.test.ts
DELETED
|
@@ -1,329 +0,0 @@
|
|
|
1
|
-
import { beforeEach, describe, expect, it } from "vitest";
|
|
2
|
-
import { spawnSync } from "node:child_process";
|
|
3
|
-
import {
|
|
4
|
-
existsSync,
|
|
5
|
-
lstatSync,
|
|
6
|
-
mkdirSync,
|
|
7
|
-
mkdtempSync,
|
|
8
|
-
readdirSync,
|
|
9
|
-
readFileSync,
|
|
10
|
-
statSync,
|
|
11
|
-
symlinkSync,
|
|
12
|
-
writeFileSync
|
|
13
|
-
} from "node:fs";
|
|
14
|
-
import { tmpdir } from "node:os";
|
|
15
|
-
import { join, resolve } from "node:path";
|
|
16
|
-
import { fileURLToPath } from "node:url";
|
|
17
|
-
|
|
18
|
-
const installer = resolve(fileURLToPath(new URL(".", import.meta.url)), "install.js");
|
|
19
|
-
|
|
20
|
-
function runInstall(cwd: string, ...args: string[]) {
|
|
21
|
-
const result = spawnSync("node", [installer, ...args], { cwd, encoding: "utf-8" });
|
|
22
|
-
expect(result.status).toBe(0);
|
|
23
|
-
return result;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function readSettings(cwd: string) {
|
|
27
|
-
return JSON.parse(readFileSync(join(cwd, ".claude", "settings.json"), "utf-8")) as {
|
|
28
|
-
permissions?: { allow: string[] };
|
|
29
|
-
hooks: Record<string, { hooks: { command?: string }[] }[]>;
|
|
30
|
-
};
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
let consumer: string;
|
|
34
|
-
const retiredSkillNames = [
|
|
35
|
-
["agent", "setup"],
|
|
36
|
-
["library", "setup"]
|
|
37
|
-
].map(parts => `imf-web-ui-${parts.join("-")}`);
|
|
38
|
-
|
|
39
|
-
beforeEach(() => {
|
|
40
|
-
consumer = mkdtempSync(join(tmpdir(), "web-ui-install-"));
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
describe("web-ui-install", () => {
|
|
44
|
-
it("installs skills into both targets with version markers", () => {
|
|
45
|
-
runInstall(consumer, "--skills", "--target", "agents", "--target", "claude");
|
|
46
|
-
|
|
47
|
-
const installed = readdirSync(join(consumer, ".agents", "skills"));
|
|
48
|
-
expect(installed).toContain("imf-web-ui");
|
|
49
|
-
expect(installed).toContain("imf-web-ui-conventions");
|
|
50
|
-
expect(installed).toContain("imf-web-ui-setup");
|
|
51
|
-
expect(installed).toContain("imf-web-ui-audit");
|
|
52
|
-
expect(installed).toHaveLength(7);
|
|
53
|
-
for (const name of retiredSkillNames) expect(installed).not.toContain(name);
|
|
54
|
-
|
|
55
|
-
for (const name of installed) {
|
|
56
|
-
expect(existsSync(join(consumer, ".agents", "skills", name, ".imf-web-ui-skill-version.json"))).toBe(true);
|
|
57
|
-
expect(lstatSync(join(consumer, ".claude", "skills", name)).isSymbolicLink()).toBe(true);
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it("removes skills that are no longer included in the bundle", () => {
|
|
62
|
-
const orphan = "imf-web-ui-retired-skill";
|
|
63
|
-
const agentsOrphan = join(consumer, ".agents", "skills", orphan);
|
|
64
|
-
mkdirSync(agentsOrphan, { recursive: true });
|
|
65
|
-
writeFileSync(join(agentsOrphan, "SKILL.md"), "# Retired skill\n");
|
|
66
|
-
const claudeOrphan = join(consumer, ".claude", "skills", orphan);
|
|
67
|
-
mkdirSync(join(consumer, ".claude", "skills"), { recursive: true });
|
|
68
|
-
symlinkSync(agentsOrphan, claudeOrphan);
|
|
69
|
-
expect(lstatSync(claudeOrphan).isSymbolicLink()).toBe(true);
|
|
70
|
-
|
|
71
|
-
const result = runInstall(consumer, "--skills", "--target", "agents", "--target", "claude");
|
|
72
|
-
|
|
73
|
-
expect(existsSync(agentsOrphan)).toBe(false);
|
|
74
|
-
expect(lstatSync(claudeOrphan, { throwIfNoEntry: false })).toBeUndefined();
|
|
75
|
-
expect(result.stdout).toContain(`Removed orphaned skill: ${orphan} (no longer in this bundle)`);
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it("installs hooks into an installer-owned directory and registers them for both hosts", () => {
|
|
79
|
-
runInstall(consumer, "--hooks", "--target", "agents");
|
|
80
|
-
|
|
81
|
-
const hookDir = join(consumer, ".agents", "hooks", "imf-web-ui");
|
|
82
|
-
const scripts = readdirSync(hookDir);
|
|
83
|
-
expect(scripts).toEqual(expect.arrayContaining(["session-start.sh", "subagent-start.sh", "user-prompt-submit.sh"]));
|
|
84
|
-
for (const script of scripts) {
|
|
85
|
-
expect(statSync(join(hookDir, script)).mode & 0o100).toBeTruthy();
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const settings = readSettings(consumer);
|
|
89
|
-
expect(Object.keys(settings.hooks)).toEqual(
|
|
90
|
-
expect.arrayContaining(["SessionStart", "SubagentStart", "UserPromptSubmit"])
|
|
91
|
-
);
|
|
92
|
-
|
|
93
|
-
const codexHooks = JSON.parse(readFileSync(join(consumer, ".codex", "hooks.json"), "utf-8")) as {
|
|
94
|
-
hooks: Record<string, { hooks: { command?: string }[] }[]>;
|
|
95
|
-
};
|
|
96
|
-
expect(Object.keys(codexHooks.hooks)).toEqual(Object.keys(settings.hooks));
|
|
97
|
-
const codexCommands = Object.values(codexHooks.hooks).flatMap(entries =>
|
|
98
|
-
entries.flatMap(entry => entry.hooks.map(hook => hook.command))
|
|
99
|
-
);
|
|
100
|
-
for (const command of codexCommands) {
|
|
101
|
-
expect(command).toContain("$(git rev-parse --show-toplevel)/.agents/hooks/imf-web-ui/");
|
|
102
|
-
expect(command).not.toContain("CLAUDE_PROJECT_DIR");
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
expect(readdirSync(join(consumer, ".agents", "skills"))).toHaveLength(7);
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
it("prunes a stale hook script and its registrations on refresh", () => {
|
|
109
|
-
runInstall(consumer, "--hooks", "--target", "agents");
|
|
110
|
-
const hookDir = join(consumer, ".agents", "hooks", "imf-web-ui");
|
|
111
|
-
writeFileSync(join(hookDir, "retired-hook.sh"), "#!/usr/bin/env sh\nexit 0\n");
|
|
112
|
-
const settingsFile = join(consumer, ".claude", "settings.json");
|
|
113
|
-
const settings = readSettings(consumer);
|
|
114
|
-
settings.hooks.PostToolUse = [
|
|
115
|
-
{
|
|
116
|
-
hooks: [{ type: "command", command: '"$CLAUDE_PROJECT_DIR/.agents/hooks/imf-web-ui/retired-hook.sh"' }]
|
|
117
|
-
} as never
|
|
118
|
-
];
|
|
119
|
-
writeFileSync(settingsFile, JSON.stringify(settings));
|
|
120
|
-
|
|
121
|
-
runInstall(consumer, "--hooks", "--target", "agents");
|
|
122
|
-
|
|
123
|
-
expect(existsSync(join(hookDir, "retired-hook.sh"))).toBe(false);
|
|
124
|
-
const refreshed = readSettings(consumer);
|
|
125
|
-
expect(refreshed.hooks.PostToolUse).toBeUndefined();
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
it("prunes per hook, keeping consumer hooks and customized commands in the same entry", () => {
|
|
129
|
-
runInstall(consumer, "--hooks", "--target", "agents");
|
|
130
|
-
const hookDir = join(consumer, ".agents", "hooks", "imf-web-ui");
|
|
131
|
-
writeFileSync(join(hookDir, "retired-hook.sh"), "#!/usr/bin/env sh\nexit 0\n");
|
|
132
|
-
const settingsFile = join(consumer, ".claude", "settings.json");
|
|
133
|
-
const settings = readSettings(consumer);
|
|
134
|
-
const customized = '"$CLAUDE_PROJECT_DIR/.agents/hooks/imf-web-ui/session-start.sh" 2>/dev/null';
|
|
135
|
-
settings.hooks.PostToolUse = [
|
|
136
|
-
{
|
|
137
|
-
hooks: [
|
|
138
|
-
{ type: "command", command: '"$CLAUDE_PROJECT_DIR/.agents/hooks/imf-web-ui/retired-hook.sh"' },
|
|
139
|
-
{ type: "command", command: "npm run my-check" },
|
|
140
|
-
{ type: "command", command: customized }
|
|
141
|
-
]
|
|
142
|
-
} as never
|
|
143
|
-
];
|
|
144
|
-
writeFileSync(settingsFile, JSON.stringify(settings));
|
|
145
|
-
|
|
146
|
-
runInstall(consumer, "--hooks", "--target", "agents");
|
|
147
|
-
|
|
148
|
-
const commands = readSettings(consumer).hooks.PostToolUse.flatMap(entry => entry.hooks.map(hook => hook.command));
|
|
149
|
-
expect(commands).toEqual(["npm run my-check", customized]);
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
it("keeps a consumer-adjusted codex command across re-runs, without duplicating it", () => {
|
|
153
|
-
runInstall(consumer, "--hooks", "--target", "agents");
|
|
154
|
-
const codexPath = join(consumer, ".codex", "hooks.json");
|
|
155
|
-
const adjusted = '"$(git rev-parse --show-toplevel)/frontend/.agents/hooks/imf-web-ui/session-start.sh"';
|
|
156
|
-
const codexHooks = JSON.parse(readFileSync(codexPath, "utf-8")) as {
|
|
157
|
-
hooks: Record<string, { hooks: { command?: string }[] }[]>;
|
|
158
|
-
};
|
|
159
|
-
codexHooks.hooks.SessionStart[0].hooks[0].command = adjusted;
|
|
160
|
-
writeFileSync(codexPath, JSON.stringify(codexHooks));
|
|
161
|
-
|
|
162
|
-
runInstall(consumer, "--hooks", "--target", "agents");
|
|
163
|
-
|
|
164
|
-
const refreshed = JSON.parse(readFileSync(codexPath, "utf-8")) as {
|
|
165
|
-
hooks: Record<string, { hooks: { command?: string }[] }[]>;
|
|
166
|
-
};
|
|
167
|
-
const commands = refreshed.hooks.SessionStart.flatMap(entry => entry.hooks.map(hook => hook.command));
|
|
168
|
-
expect(commands).toEqual([adjusted]);
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
it("merges registrations without clobbering existing settings, idempotently", () => {
|
|
172
|
-
mkdirSync(join(consumer, ".claude"), { recursive: true });
|
|
173
|
-
writeFileSync(
|
|
174
|
-
join(consumer, ".claude", "settings.json"),
|
|
175
|
-
JSON.stringify({
|
|
176
|
-
permissions: { allow: ["Bash(git *)"] },
|
|
177
|
-
hooks: { PostToolUse: [{ matcher: "Edit", hooks: [{ type: "command", command: "echo existing" }] }] }
|
|
178
|
-
})
|
|
179
|
-
);
|
|
180
|
-
|
|
181
|
-
runInstall(consumer, "--hooks", "--target", "agents");
|
|
182
|
-
runInstall(consumer, "--hooks", "--target", "agents");
|
|
183
|
-
|
|
184
|
-
const settings = readSettings(consumer);
|
|
185
|
-
expect(settings.permissions?.allow).toEqual(["Bash(git *)"]);
|
|
186
|
-
const foreign = settings.hooks.PostToolUse.flatMap(entry => entry.hooks.map(hook => hook.command));
|
|
187
|
-
expect(foreign).toContain("echo existing");
|
|
188
|
-
// the shipped registration is added exactly once across both runs
|
|
189
|
-
const commands = settings.hooks.UserPromptSubmit.flatMap(entry => entry.hooks.map(hook => hook.command));
|
|
190
|
-
expect(commands.filter(command => command?.includes("user-prompt-submit.sh"))).toHaveLength(1);
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
it("recognises registrations written with braced ${CLAUDE_PROJECT_DIR}", () => {
|
|
194
|
-
mkdirSync(join(consumer, ".claude"), { recursive: true });
|
|
195
|
-
writeFileSync(
|
|
196
|
-
join(consumer, ".claude", "settings.json"),
|
|
197
|
-
JSON.stringify({
|
|
198
|
-
hooks: {
|
|
199
|
-
UserPromptSubmit: [
|
|
200
|
-
{
|
|
201
|
-
hooks: [
|
|
202
|
-
{
|
|
203
|
-
type: "command",
|
|
204
|
-
command: '"${CLAUDE_PROJECT_DIR}/.agents/hooks/imf-web-ui/user-prompt-submit.sh"'
|
|
205
|
-
}
|
|
206
|
-
]
|
|
207
|
-
},
|
|
208
|
-
// a foreign entry carrying no `command` at all must not throw
|
|
209
|
-
{ matcher: "Bash", hooks: [{ type: "command", args: ["${CLAUDE_PROJECT_DIR}/other/gate.mjs"] }] }
|
|
210
|
-
]
|
|
211
|
-
}
|
|
212
|
-
})
|
|
213
|
-
);
|
|
214
|
-
|
|
215
|
-
runInstall(consumer, "--hooks", "--target", "agents");
|
|
216
|
-
|
|
217
|
-
const settings = readSettings(consumer);
|
|
218
|
-
const commands = settings.hooks.UserPromptSubmit.flatMap(entry => entry.hooks.map(hook => hook.command));
|
|
219
|
-
expect(commands.filter(command => command?.includes("user-prompt-submit.sh"))).toHaveLength(1);
|
|
220
|
-
expect(commands).toContain('"${CLAUDE_PROJECT_DIR}/.agents/hooks/imf-web-ui/user-prompt-submit.sh"');
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
it("does not touch settings.json when every registration is already present", () => {
|
|
224
|
-
runInstall(consumer, "--hooks", "--target", "agents");
|
|
225
|
-
const settingsFile = join(consumer, ".claude", "settings.json");
|
|
226
|
-
const settings = readSettings(consumer);
|
|
227
|
-
settings.hooks.PreToolUse = [{ hooks: [{ command: "./other/gate.mjs" }] }];
|
|
228
|
-
const before = JSON.stringify(settings, null, 4) + "\n";
|
|
229
|
-
writeFileSync(settingsFile, before);
|
|
230
|
-
|
|
231
|
-
const result = runInstall(consumer, "--hooks", "--target", "agents");
|
|
232
|
-
|
|
233
|
-
expect(readFileSync(settingsFile, "utf-8")).toBe(before);
|
|
234
|
-
expect(result.stdout).toContain("Hook registrations already present");
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
it("upserts the fenced AGENTS.md block and leaves the rest untouched", () => {
|
|
238
|
-
const agentsPath = join(consumer, "AGENTS.md");
|
|
239
|
-
writeFileSync(
|
|
240
|
-
agentsPath,
|
|
241
|
-
"# AGENTS.md\n\nRepo-specific intro.\n\n<!-- imf-web-ui:begin stale -->\nold content\n<!-- imf-web-ui:end -->\n\nRepo-specific outro.\n"
|
|
242
|
-
);
|
|
243
|
-
|
|
244
|
-
runInstall(consumer, "--skills", "--target", "agents");
|
|
245
|
-
const updated = readFileSync(agentsPath, "utf-8");
|
|
246
|
-
expect(updated).toContain("Repo-specific intro.");
|
|
247
|
-
expect(updated).toContain("Repo-specific outro.");
|
|
248
|
-
expect(updated).not.toContain("old content");
|
|
249
|
-
expect(updated).toContain("resynced with `npx web-ui-install`");
|
|
250
|
-
|
|
251
|
-
// no fence yet → block is appended; missing AGENTS.md entirely → left alone
|
|
252
|
-
writeFileSync(agentsPath, "# AGENTS.md\n\nNo fence here.\n");
|
|
253
|
-
runInstall(consumer, "--skills", "--target", "agents");
|
|
254
|
-
const appended = readFileSync(agentsPath, "utf-8");
|
|
255
|
-
expect(appended).toContain("No fence here.");
|
|
256
|
-
expect(appended).toContain("<!-- imf-web-ui:begin");
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
it("ships injection hooks that exit 0 and echo their fixed one-liners", () => {
|
|
260
|
-
runInstall(consumer, "--hooks", "--target", "agents");
|
|
261
|
-
const hookDir = join(consumer, ".agents", "hooks", "imf-web-ui");
|
|
262
|
-
|
|
263
|
-
const sessionStart = spawnSync("sh", [join(hookDir, "session-start.sh")], { encoding: "utf-8" });
|
|
264
|
-
expect(sessionStart.status).toBe(0);
|
|
265
|
-
expect(sessionStart.stdout).toContain("imf-web-ui skill");
|
|
266
|
-
|
|
267
|
-
// Subagents get the same router pointer, byte for byte.
|
|
268
|
-
const subagentStart = spawnSync("sh", [join(hookDir, "subagent-start.sh")], { encoding: "utf-8" });
|
|
269
|
-
expect(subagentStart.status).toBe(0);
|
|
270
|
-
expect(subagentStart.stdout).toBe(sessionStart.stdout);
|
|
271
|
-
|
|
272
|
-
const promptSubmit = spawnSync("sh", [join(hookDir, "user-prompt-submit.sh")], { encoding: "utf-8" });
|
|
273
|
-
expect(promptSubmit.status).toBe(0);
|
|
274
|
-
expect(promptSubmit.stdout).toContain("imf-web-ui-components");
|
|
275
|
-
|
|
276
|
-
// no node_modules in the consumer → silent success
|
|
277
|
-
const staleness = spawnSync("sh", [join(hookDir, "baseline-staleness.sh")], { cwd: consumer, encoding: "utf-8" });
|
|
278
|
-
expect(staleness.status).toBe(0);
|
|
279
|
-
expect(staleness.stdout).toBe("");
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
it("gates the turn end only after in-session edits, and never loops", () => {
|
|
283
|
-
runInstall(consumer, "--hooks", "--target", "agents");
|
|
284
|
-
const stopHook = join(consumer, ".agents", "hooks", "imf-web-ui", "stop.sh");
|
|
285
|
-
const transcript = join(consumer, "transcript.jsonl");
|
|
286
|
-
const runStop = (input: string) => spawnSync("sh", [stopHook], { cwd: consumer, input, encoding: "utf-8" });
|
|
287
|
-
|
|
288
|
-
// outside a git repo → silent pass
|
|
289
|
-
const noGit = runStop("{}");
|
|
290
|
-
expect(noGit.status).toBe(0);
|
|
291
|
-
expect(noGit.stdout).toBe("");
|
|
292
|
-
|
|
293
|
-
spawnSync("git", ["init", "--quiet", consumer]);
|
|
294
|
-
mkdirSync(join(consumer, "src"), { recursive: true });
|
|
295
|
-
writeFileSync(join(consumer, "src", "app.ts"), "export const x = 1;\n");
|
|
296
|
-
writeFileSync(transcript, '{"type":"assistant","message":{"content":[{"type":"tool_use","name":"Write"}]}}\n');
|
|
297
|
-
const payload = JSON.stringify({ transcript_path: transcript, stop_hook_active: false });
|
|
298
|
-
|
|
299
|
-
const blocked = runStop(payload);
|
|
300
|
-
expect(blocked.status).toBe(0);
|
|
301
|
-
expect(blocked.stdout).toContain('"decision":"block"');
|
|
302
|
-
expect(blocked.stdout).toContain("web-ui stop gate:");
|
|
303
|
-
|
|
304
|
-
// re-entry after a block → silent pass
|
|
305
|
-
const reentry = runStop(JSON.stringify({ transcript_path: transcript, stop_hook_active: true }));
|
|
306
|
-
expect(reentry.status).toBe(0);
|
|
307
|
-
expect(reentry.stdout).toBe("");
|
|
308
|
-
|
|
309
|
-
// dirty tree, but this session's transcript shows no edits → silent pass
|
|
310
|
-
writeFileSync(transcript, '{"type":"user","message":{"content":"a question"}}\n');
|
|
311
|
-
const questionTurn = runStop(payload);
|
|
312
|
-
expect(questionTurn.status).toBe(0);
|
|
313
|
-
expect(questionTurn.stdout).toBe("");
|
|
314
|
-
});
|
|
315
|
-
|
|
316
|
-
it("warns from the staleness script when skill markers lag the package", () => {
|
|
317
|
-
runInstall(consumer, "--skills", "--hooks", "--target", "agents");
|
|
318
|
-
const pkgDir = join(consumer, "node_modules", "@imfusion", "web-ui");
|
|
319
|
-
mkdirSync(pkgDir, { recursive: true });
|
|
320
|
-
writeFileSync(join(pkgDir, "package.json"), JSON.stringify({ version: "99.0.0" }));
|
|
321
|
-
|
|
322
|
-
const result = spawnSync("sh", [join(consumer, ".agents", "hooks", "imf-web-ui", "baseline-staleness.sh")], {
|
|
323
|
-
cwd: consumer,
|
|
324
|
-
encoding: "utf-8"
|
|
325
|
-
});
|
|
326
|
-
expect(result.status).toBe(0);
|
|
327
|
-
expect(result.stdout).toContain("npx web-ui-install");
|
|
328
|
-
});
|
|
329
|
-
});
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { Plugin } from 'vite';
|
|
2
|
-
export interface ReadableCssModuleNamesOptions {
|
|
3
|
-
/** Namespace for every generated class name. Short, stable, app-scoped (`imf-ui`, `acme`). */
|
|
4
|
-
prefix: string;
|
|
5
|
-
}
|
|
6
|
-
/**
|
|
7
|
-
* Generates readable CSS Module class names instead of the default hash, so the DOM
|
|
8
|
-
* stays legible in devtools and browser automation. `button.module.css` yields
|
|
9
|
-
* `{prefix}-button-module-root` — `[name]` is the filename, which keeps its `.module`
|
|
10
|
-
* suffix.
|
|
11
|
-
*
|
|
12
|
-
* Register it in every tool that compiles the CSS (the app build AND Storybook). A
|
|
13
|
-
* compiler left out generates different class names for the same source file and its
|
|
14
|
-
* styles silently don't apply.
|
|
15
|
-
*
|
|
16
|
-
* Defaults to opting into Lightning CSS, which drops `css.modules` options it has no
|
|
17
|
-
* equivalent for (`localsConvention`). Set `css.transformer: "postcss"` to stay on
|
|
18
|
-
* the PostCSS pipeline.
|
|
19
|
-
*/
|
|
20
|
-
export declare function readableCssModuleNames({ prefix }: ReadableCssModuleNamesOptions): Plugin;
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
//#region src/build/vite-css-module-names/index.ts
|
|
2
|
-
function e(e, t) {
|
|
3
|
-
let { lightningcss: n, modules: r, transformer: i } = e.css ?? {}, a = n?.cssModules;
|
|
4
|
-
if (!(typeof a == "boolean" || r === !1) && !a?.pattern && !(typeof r == "object" && r.generateScopedName)) return i === "postcss" ? { css: { modules: { generateScopedName: t } } } : { css: {
|
|
5
|
-
transformer: "lightningcss",
|
|
6
|
-
lightningcss: { cssModules: { pattern: t } }
|
|
7
|
-
} };
|
|
8
|
-
}
|
|
9
|
-
function t({ prefix: t }) {
|
|
10
|
-
let n = `${t}-[name]-[local]`;
|
|
11
|
-
return {
|
|
12
|
-
name: "imf-ui:readable-css-module-names",
|
|
13
|
-
config: (t) => e(t, n)
|
|
14
|
-
};
|
|
15
|
-
}
|
|
16
|
-
//#endregion
|
|
17
|
-
export { t as readableCssModuleNames };
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import { Field as FieldPrimitive } from '@base-ui/react/field';
|
|
2
|
-
type UpstreamRoot = React.ComponentProps<typeof FieldPrimitive.Root>;
|
|
3
|
-
type UpstreamItem = React.ComponentProps<typeof FieldPrimitive.Item>;
|
|
4
|
-
type UpstreamLabel = React.ComponentProps<typeof FieldPrimitive.Label>;
|
|
5
|
-
type UpstreamDescription = React.ComponentProps<typeof FieldPrimitive.Description>;
|
|
6
|
-
type UpstreamControl = React.ComponentProps<typeof FieldPrimitive.Control>;
|
|
7
|
-
type UpstreamError = React.ComponentProps<typeof FieldPrimitive.Error>;
|
|
8
|
-
type UpstreamValidity = React.ComponentProps<typeof FieldPrimitive.Validity>;
|
|
9
|
-
type RootProps = Omit<React.HTMLAttributes<HTMLDivElement>, "className" | "style"> & {
|
|
10
|
-
name?: string;
|
|
11
|
-
actionsRef?: UpstreamRoot["actionsRef"];
|
|
12
|
-
dirty?: boolean;
|
|
13
|
-
touched?: boolean;
|
|
14
|
-
/** @default false */
|
|
15
|
-
disabled?: boolean;
|
|
16
|
-
invalid?: boolean;
|
|
17
|
-
validate?: UpstreamRoot["validate"];
|
|
18
|
-
/** @default "onSubmit" */
|
|
19
|
-
validationMode?: UpstreamRoot["validationMode"];
|
|
20
|
-
/** @default 0 */
|
|
21
|
-
validationDebounceTime?: number;
|
|
22
|
-
className?: UpstreamRoot["className"];
|
|
23
|
-
style?: UpstreamRoot["style"];
|
|
24
|
-
render?: UpstreamRoot["render"];
|
|
25
|
-
};
|
|
26
|
-
/**
|
|
27
|
-
* Field.Root — groups a label, control, validation state, and supporting copy.
|
|
28
|
-
*
|
|
29
|
-
* @doc https://base-ui.com/react/components/field
|
|
30
|
-
* @llm https://base-ui.com/react/components/field.md
|
|
31
|
-
*/
|
|
32
|
-
export declare function Root({ className, ...props }: RootProps): import("react/jsx-runtime").JSX.Element;
|
|
33
|
-
export declare namespace Root {
|
|
34
|
-
var displayName: string;
|
|
35
|
-
}
|
|
36
|
-
type ItemProps = Omit<React.HTMLAttributes<HTMLDivElement>, "className" | "style"> & {
|
|
37
|
-
/** @default false */
|
|
38
|
-
disabled?: boolean;
|
|
39
|
-
className?: UpstreamItem["className"];
|
|
40
|
-
style?: UpstreamItem["style"];
|
|
41
|
-
render?: UpstreamItem["render"];
|
|
42
|
-
};
|
|
43
|
-
/** Field.Item — groups one checkable option with its label and description. */
|
|
44
|
-
export declare function Item({ className, ...props }: ItemProps): import("react/jsx-runtime").JSX.Element;
|
|
45
|
-
export declare namespace Item {
|
|
46
|
-
var displayName: string;
|
|
47
|
-
}
|
|
48
|
-
type LabelProps = Omit<React.LabelHTMLAttributes<HTMLLabelElement>, "className" | "style"> & {
|
|
49
|
-
/** @default true */
|
|
50
|
-
nativeLabel?: boolean;
|
|
51
|
-
className?: UpstreamLabel["className"];
|
|
52
|
-
style?: UpstreamLabel["style"];
|
|
53
|
-
render?: UpstreamLabel["render"];
|
|
54
|
-
};
|
|
55
|
-
/** Field.Label — an accessible label automatically associated with the control. */
|
|
56
|
-
export declare function Label({ className, ...props }: LabelProps): import("react/jsx-runtime").JSX.Element;
|
|
57
|
-
export declare namespace Label {
|
|
58
|
-
var displayName: string;
|
|
59
|
-
}
|
|
60
|
-
type DescriptionProps = Omit<React.HTMLAttributes<HTMLParagraphElement>, "className" | "style"> & {
|
|
61
|
-
className?: UpstreamDescription["className"];
|
|
62
|
-
style?: UpstreamDescription["style"];
|
|
63
|
-
render?: UpstreamDescription["render"];
|
|
64
|
-
};
|
|
65
|
-
/** Field.Description — supporting information for a field. */
|
|
66
|
-
export declare function Description({ className, ...props }: DescriptionProps): import("react/jsx-runtime").JSX.Element;
|
|
67
|
-
export declare namespace Description {
|
|
68
|
-
var displayName: string;
|
|
69
|
-
}
|
|
70
|
-
type ControlProps = Omit<React.InputHTMLAttributes<HTMLInputElement>, "className" | "style" | "defaultValue"> & {
|
|
71
|
-
defaultValue?: UpstreamControl["defaultValue"];
|
|
72
|
-
onValueChange?: UpstreamControl["onValueChange"];
|
|
73
|
-
className?: UpstreamControl["className"];
|
|
74
|
-
style?: UpstreamControl["style"];
|
|
75
|
-
render?: UpstreamControl["render"];
|
|
76
|
-
};
|
|
77
|
-
/** Field.Control — a native input control with Field labeling and validation. */
|
|
78
|
-
export declare function Control({ className, ...props }: ControlProps): import("react/jsx-runtime").JSX.Element;
|
|
79
|
-
export declare namespace Control {
|
|
80
|
-
var displayName: string;
|
|
81
|
-
}
|
|
82
|
-
type ErrorProps = Omit<React.HTMLAttributes<HTMLDivElement>, "className" | "style"> & {
|
|
83
|
-
match?: UpstreamError["match"];
|
|
84
|
-
className?: UpstreamError["className"];
|
|
85
|
-
style?: UpstreamError["style"];
|
|
86
|
-
render?: UpstreamError["render"];
|
|
87
|
-
};
|
|
88
|
-
/** Field.Error — an inline validation message shown when its match condition fails. */
|
|
89
|
-
export declare function Error({ className, ...props }: ErrorProps): import("react/jsx-runtime").JSX.Element;
|
|
90
|
-
export declare namespace Error {
|
|
91
|
-
var displayName: string;
|
|
92
|
-
}
|
|
93
|
-
type ValidityProps = {
|
|
94
|
-
children: UpstreamValidity["children"];
|
|
95
|
-
};
|
|
96
|
-
/**
|
|
97
|
-
* Field.Validity — renderless access to the field validity data for custom messages.
|
|
98
|
-
* Its function child owns the rendered element and therefore its identity and styling.
|
|
99
|
-
*/
|
|
100
|
-
export declare function Validity({ children }: ValidityProps): import("react/jsx-runtime").JSX.Element;
|
|
101
|
-
export declare namespace Validity {
|
|
102
|
-
var displayName: string;
|
|
103
|
-
}
|
|
104
|
-
export {};
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { Fieldset as FieldsetPrimitive } from '@base-ui/react/fieldset';
|
|
2
|
-
type UpstreamRoot = React.ComponentProps<typeof FieldsetPrimitive.Root>;
|
|
3
|
-
type UpstreamLegend = React.ComponentProps<typeof FieldsetPrimitive.Legend>;
|
|
4
|
-
type RootProps = Omit<React.FieldsetHTMLAttributes<HTMLFieldSetElement>, "className" | "style"> & {
|
|
5
|
-
className?: UpstreamRoot["className"];
|
|
6
|
-
style?: UpstreamRoot["style"];
|
|
7
|
-
render?: UpstreamRoot["render"];
|
|
8
|
-
};
|
|
9
|
-
/**
|
|
10
|
-
* Fieldset.Root — semantic grouping for related form controls and a shared legend.
|
|
11
|
-
*
|
|
12
|
-
* @doc https://base-ui.com/react/components/fieldset
|
|
13
|
-
* @llm https://base-ui.com/react/components/fieldset.md
|
|
14
|
-
*/
|
|
15
|
-
export declare function Root({ className, ...props }: RootProps): import("react/jsx-runtime").JSX.Element;
|
|
16
|
-
export declare namespace Root {
|
|
17
|
-
var displayName: string;
|
|
18
|
-
}
|
|
19
|
-
type LegendProps = Omit<React.HTMLAttributes<HTMLDivElement>, "className" | "style"> & {
|
|
20
|
-
className?: UpstreamLegend["className"];
|
|
21
|
-
style?: UpstreamLegend["style"];
|
|
22
|
-
render?: UpstreamLegend["render"];
|
|
23
|
-
};
|
|
24
|
-
/** Fieldset.Legend — the accessible label for its related controls. */
|
|
25
|
-
export declare function Legend({ className, ...props }: LegendProps): import("react/jsx-runtime").JSX.Element;
|
|
26
|
-
export declare namespace Legend {
|
|
27
|
-
var displayName: string;
|
|
28
|
-
}
|
|
29
|
-
export {};
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { HighlightTokenClass, TokenRange } from '@tanstack/highlight/core';
|
|
2
|
-
export type LanguagePattern = {
|
|
3
|
-
className: HighlightTokenClass;
|
|
4
|
-
group?: number;
|
|
5
|
-
regex: RegExp;
|
|
6
|
-
};
|
|
7
|
-
export declare function collectPatternRanges(code: string, patterns: readonly LanguagePattern[], initialRanges?: readonly TokenRange[]): TokenRange[];
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare const cmake: import('@tanstack/highlight/core').LanguageDefinition<"cmake">;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare const cpp: import('@tanstack/highlight/core').LanguageDefinition<"cpp">;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare const python: import('@tanstack/highlight/core').LanguageDefinition<"python">;
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
export type TokenEntry = {
|
|
2
|
-
name: string;
|
|
3
|
-
value: string;
|
|
4
|
-
};
|
|
5
|
-
export declare function collectTokenEntries(files: readonly string[]): TokenEntry[];
|
|
6
|
-
export declare function renderTokenIndex(tokens: readonly TokenEntry[]): string;
|
|
7
|
-
export declare function generateTokenIndex(): Promise<number>;
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
# AGENTS.md
|
|
2
|
-
|
|
3
|
-
<Keep this file lean — target ~50 lines. Decision test for every line: would the agent make a costly mistake without it? If
|
|
4
|
-
it would just need to read a file first, cut it. Dev commands, path aliases, and tool config are discoverable from the files
|
|
5
|
-
themselves.>
|
|
6
|
-
|
|
7
|
-
<One paragraph: what the app is and the stack in one line.>
|
|
8
|
-
|
|
9
|
-
Scripts, deps, and setup: [`README.md`](./README.md) and [`package.json`](./package.json) are the source of truth. Check the
|
|
10
|
-
`package.json` scripts before running or suggesting a command — don't infer one exists by pattern-matching a sibling.
|
|
11
|
-
|
|
12
|
-
## Read before you write
|
|
13
|
-
|
|
14
|
-
<One bullet per doc in docs/, each with when to read it, e.g.:>
|
|
15
|
-
|
|
16
|
-
- [`docs/<topic>.md`](./docs/<topic>.md) — <what it covers>. Read before <the change it governs>.
|
|
17
|
-
|
|
18
|
-
[`docs/index.md`](./docs/index.md) registers all of them.
|
|
19
|
-
|
|
20
|
-
## Working here
|
|
21
|
-
|
|
22
|
-
<The fenced block below is the only part of this file `npx web-ui-install` touches: its content comes from this template and
|
|
23
|
-
is refreshed on every skills install. Everything else in the file is scaffolded once by the setup skill and then owned by the
|
|
24
|
-
repo.>
|
|
25
|
-
|
|
26
|
-
<!-- imf-web-ui:begin — managed by `npx web-ui-install`; edits inside the fence are overwritten -->
|
|
27
|
-
|
|
28
|
-
`.agents/skills/imf-web-ui-*` is vendored from `@imfusion/web-ui` and resynced with `npx web-ui-install`. Don't edit it and
|
|
29
|
-
don't put repo conventions there. Load the matching `imf-web-ui-*` skill before writing code, styles, data fetching, or docs;
|
|
30
|
-
repo docs hold only what is unique to this repo.
|
|
31
|
-
|
|
32
|
-
<!-- imf-web-ui:end -->
|
|
33
|
-
|
|
34
|
-
<Repo-specific agent guidance: generated files that are committed, tools to verify APIs against, things never to touch.>
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"hooks": {
|
|
3
|
-
"SessionStart": [
|
|
4
|
-
{
|
|
5
|
-
"hooks": [
|
|
6
|
-
{
|
|
7
|
-
"type": "command",
|
|
8
|
-
"command": "\"$(git rev-parse --show-toplevel)/.agents/hooks/imf-web-ui/session-start.sh\""
|
|
9
|
-
}
|
|
10
|
-
]
|
|
11
|
-
}
|
|
12
|
-
],
|
|
13
|
-
"Stop": [
|
|
14
|
-
{
|
|
15
|
-
"hooks": [
|
|
16
|
-
{
|
|
17
|
-
"type": "command",
|
|
18
|
-
"command": "\"$(git rev-parse --show-toplevel)/.agents/hooks/imf-web-ui/stop.sh\""
|
|
19
|
-
}
|
|
20
|
-
]
|
|
21
|
-
}
|
|
22
|
-
],
|
|
23
|
-
"SubagentStart": [
|
|
24
|
-
{
|
|
25
|
-
"hooks": [
|
|
26
|
-
{
|
|
27
|
-
"type": "command",
|
|
28
|
-
"command": "\"$(git rev-parse --show-toplevel)/.agents/hooks/imf-web-ui/subagent-start.sh\""
|
|
29
|
-
}
|
|
30
|
-
]
|
|
31
|
-
}
|
|
32
|
-
],
|
|
33
|
-
"UserPromptSubmit": [
|
|
34
|
-
{
|
|
35
|
-
"hooks": [
|
|
36
|
-
{
|
|
37
|
-
"type": "command",
|
|
38
|
-
"command": "\"$(git rev-parse --show-toplevel)/.agents/hooks/imf-web-ui/user-prompt-submit.sh\""
|
|
39
|
-
}
|
|
40
|
-
]
|
|
41
|
-
}
|
|
42
|
-
]
|
|
43
|
-
}
|
|
44
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
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
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env sh
|
|
2
|
-
# SessionStart: inject the web-ui router pointer once per session. Fixed string — the
|
|
3
|
-
# hook-injection eval asserts on it verbatim.
|
|
4
|
-
echo "This project depends on @imfusion/web-ui. Start at the imf-web-ui skill: it decides whether guidance is needed and routes to the right companion. Repo docs carry only what is unique to this repo."
|
|
5
|
-
exit 0
|