@1agh/maude 0.17.1 → 0.18.0
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/cli/cli-wrapper.cjs +0 -0
- package/cli/commands/design.mjs +248 -43
- package/package.json +8 -8
- package/plugins/design/dev-server/bin/screenshot.sh +12 -0
- package/plugins/design/dev-server/boot-self-heal.ts +90 -0
- package/plugins/design/dev-server/build.ts +136 -8
- package/plugins/design/dev-server/canvas-pipeline.ts +5 -0
- package/plugins/design/dev-server/config.schema.json +12 -0
- package/plugins/design/dev-server/dist/client.bundle.js +3 -3
- package/plugins/design/dev-server/runtime-bundle.ts +29 -1
- package/plugins/design/dev-server/server.ts +6 -0
- package/plugins/design/dev-server/test/boot-self-heal.test.ts +112 -0
- package/plugins/design/dev-server/test/compile-entry.test.ts +134 -0
- package/plugins/design/dev-server/test/runtime-bundle-error-mapping.test.ts +43 -0
- package/plugins/design/templates/canvas.tsx.template +7 -7
- package/plugins/design/templates/design-system-inspiration/audience-pro/colors-presence.html +1 -1
- package/plugins/design/templates/design-system-inspiration/core/README.philosophy.md.tpl +11 -7
- package/plugins/design/templates/design-system-inspiration/core/SKILL.md.tpl +4 -3
- package/plugins/design/templates/design-system-inspiration/core/colors_and_type.css.tpl +61 -57
- package/plugins/design/templates/design-system-inspiration/core/config.json.tpl +2 -0
- package/plugins/design/templates/design-system-inspiration/core/preview/colors-accent.html +4 -4
- package/plugins/design/templates/design-system-inspiration/meta/presence-multiplayer.html +1 -1
- package/plugins/design/templates/design-system-inspiration/platform-desktop/ui_kits-desktop-showcase.html +3 -0
- package/plugins/design/templates/design-system-inspiration/platform-mobile/ui_kits-mobile-showcase.html +1 -1
- package/plugins/design/templates/design-system-inspiration/theme-both/colors-themes-side-by-side.html +6 -6
- package/plugins/design/templates/design-system-inspiration/universal/components-dialogs.html +3 -2
- package/plugins/design/templates/design-system-inspiration/universal/logo.html +2 -1
|
@@ -194,7 +194,12 @@ export async function getRuntimeBundle(pkg: RuntimePackage): Promise<BundleCache
|
|
|
194
194
|
return `[${lvl}] ${l.message}`;
|
|
195
195
|
})
|
|
196
196
|
.join('\n');
|
|
197
|
-
|
|
197
|
+
const remediation = bunCacheRemediation(pkg, msg);
|
|
198
|
+
throw new Error(
|
|
199
|
+
`Failed to build runtime bundle for "${pkg}":\n${msg || '(no log messages)'}${
|
|
200
|
+
remediation ? `\n\n${remediation}` : ''
|
|
201
|
+
}`
|
|
202
|
+
);
|
|
198
203
|
}
|
|
199
204
|
|
|
200
205
|
const out = built.outputs[0];
|
|
@@ -206,6 +211,29 @@ export async function getRuntimeBundle(pkg: RuntimePackage): Promise<BundleCache
|
|
|
206
211
|
return entry;
|
|
207
212
|
}
|
|
208
213
|
|
|
214
|
+
/**
|
|
215
|
+
* Detect the "Bun's global install cache is in a bad state" failure mode and
|
|
216
|
+
* return a one-paragraph remediation message. Returns null when the build
|
|
217
|
+
* failure has a different shape (real syntax error, missing package, etc.) —
|
|
218
|
+
* the original log is enough then.
|
|
219
|
+
*
|
|
220
|
+
* Symptoms: log messages like `EISDIR reading '/Users/foo/.bun/install/cache/
|
|
221
|
+
* react@19.2.6@@@1 @@1/index.js'` or `ENOENT … .bun/install/cache/<pkg>@…`.
|
|
222
|
+
* Surfacing the cache path + the exact `bun pm cache rm <pkg>` command saves
|
|
223
|
+
* the user from grepping the error to figure out what to do. Phase 19 / DDR-044.
|
|
224
|
+
*/
|
|
225
|
+
export function bunCacheRemediation(pkg: string, log: string): string | null {
|
|
226
|
+
const cacheHit = /(EISDIR|ENOENT).*\.bun\/install\/cache\/([\w@/.-]+?)(?:@@@|\/)/i.test(log);
|
|
227
|
+
if (!cacheHit) return null;
|
|
228
|
+
const basePkg = pkg.split('/')[0] ?? pkg;
|
|
229
|
+
return [
|
|
230
|
+
` ⚠ Bun's global package cache for "${basePkg}" appears to be in a bad state`,
|
|
231
|
+
` (truncated install, EISDIR/ENOENT on an index file).`,
|
|
232
|
+
``,
|
|
233
|
+
` Fix: run \`bun pm cache rm ${basePkg}\` then reload the page.`,
|
|
234
|
+
].join('\n');
|
|
235
|
+
}
|
|
236
|
+
|
|
209
237
|
function escapeRegex(s: string): string {
|
|
210
238
|
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
211
239
|
}
|
|
@@ -23,6 +23,12 @@ import { createHttp } from './http.ts';
|
|
|
23
23
|
import { createInspect } from './inspect.ts';
|
|
24
24
|
import { startHeapWatch } from './mem.ts';
|
|
25
25
|
import { createWs } from './ws.ts';
|
|
26
|
+
import { bootSelfHeal } from './boot-self-heal.ts';
|
|
27
|
+
|
|
28
|
+
// Phase 19 / DDR-044 — covers the marketplace-cache-install gap where
|
|
29
|
+
// node_modules/ ships empty (git clone honors .gitignore). Auto-installs +
|
|
30
|
+
// builds on first boot; opt out with MAUDE_NO_AUTOBUILD=1.
|
|
31
|
+
await bootSelfHeal();
|
|
26
32
|
|
|
27
33
|
const ctx = createContext();
|
|
28
34
|
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
// Boot self-heal logic — Phase 19 / DDR-044.
|
|
2
|
+
// Covers the marketplace-cache-install gap: if dist/ or node_modules/ is
|
|
3
|
+
// missing on boot, self-heal runs bun install + build. Opt out with
|
|
4
|
+
// MAUDE_NO_AUTOBUILD=1.
|
|
5
|
+
|
|
6
|
+
import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
|
|
7
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
|
8
|
+
import { tmpdir } from 'node:os';
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
|
|
11
|
+
import { bootSelfHeal, type SelfHealOptions } from '../boot-self-heal.ts';
|
|
12
|
+
|
|
13
|
+
let TMP: string;
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
TMP = mkdtempSync(join(tmpdir(), 'maude-self-heal-'));
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
afterEach(() => {
|
|
20
|
+
rmSync(TMP, { recursive: true, force: true });
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
function harness(extra: Partial<SelfHealOptions> = {}) {
|
|
24
|
+
const calls: { cmd: readonly string[]; cwd: string }[] = [];
|
|
25
|
+
const logs: string[] = [];
|
|
26
|
+
let exited: number | null = null;
|
|
27
|
+
const opts: SelfHealOptions = {
|
|
28
|
+
here: TMP,
|
|
29
|
+
optOut: false,
|
|
30
|
+
spawn: async (cmd, cwd) => {
|
|
31
|
+
calls.push({ cmd, cwd });
|
|
32
|
+
return { code: 0 };
|
|
33
|
+
},
|
|
34
|
+
log: (m) => logs.push(m),
|
|
35
|
+
exit: ((code: number) => {
|
|
36
|
+
exited = code;
|
|
37
|
+
throw new Error(`__exit:${code}`);
|
|
38
|
+
}) as never,
|
|
39
|
+
...extra,
|
|
40
|
+
};
|
|
41
|
+
return { opts, calls, logs, getExited: () => exited };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function seedDist() {
|
|
45
|
+
mkdirSync(join(TMP, 'dist'), { recursive: true });
|
|
46
|
+
writeFileSync(join(TMP, 'dist', 'client.bundle.js'), '/* stub */');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function seedDeps() {
|
|
50
|
+
mkdirSync(join(TMP, 'node_modules', 'react'), { recursive: true });
|
|
51
|
+
writeFileSync(join(TMP, 'node_modules', 'react', 'package.json'), '{}');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
describe('bootSelfHeal', () => {
|
|
55
|
+
test('skips when dist + node_modules both present', async () => {
|
|
56
|
+
seedDist();
|
|
57
|
+
seedDeps();
|
|
58
|
+
const { opts, calls } = harness();
|
|
59
|
+
const result = await bootSelfHeal(opts);
|
|
60
|
+
expect(result.skipped).toBe('all-present');
|
|
61
|
+
expect(result.ran).toEqual([]);
|
|
62
|
+
expect(calls).toEqual([]);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('runs `bun install --production` when node_modules/react is missing', async () => {
|
|
66
|
+
seedDist();
|
|
67
|
+
const { opts, calls } = harness();
|
|
68
|
+
const result = await bootSelfHeal(opts);
|
|
69
|
+
expect(result.ran).toEqual(['install']);
|
|
70
|
+
expect(calls).toHaveLength(1);
|
|
71
|
+
expect(calls[0]?.cmd).toEqual(['bun', 'install', '--production']);
|
|
72
|
+
expect(calls[0]?.cwd).toBe(TMP);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('runs `bun run build.ts` when dist/client.bundle.js is missing', async () => {
|
|
76
|
+
seedDeps();
|
|
77
|
+
const { opts, calls } = harness();
|
|
78
|
+
const result = await bootSelfHeal(opts);
|
|
79
|
+
expect(result.ran).toEqual(['build']);
|
|
80
|
+
expect(calls).toHaveLength(1);
|
|
81
|
+
expect(calls[0]?.cmd).toEqual(['bun', 'run', 'build.ts']);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test('runs install BEFORE build when both missing (build needs deps)', async () => {
|
|
85
|
+
const { opts, calls } = harness();
|
|
86
|
+
const result = await bootSelfHeal(opts);
|
|
87
|
+
expect(result.ran).toEqual(['install', 'build']);
|
|
88
|
+
expect(calls[0]?.cmd).toEqual(['bun', 'install', '--production']);
|
|
89
|
+
expect(calls[1]?.cmd).toEqual(['bun', 'run', 'build.ts']);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test('MAUDE_NO_AUTOBUILD=1: exits 1 with remediation message; no spawn', async () => {
|
|
93
|
+
const { opts, calls, logs, getExited } = harness({ optOut: true });
|
|
94
|
+
await expect(bootSelfHeal(opts)).rejects.toThrow('__exit:1');
|
|
95
|
+
expect(getExited()).toBe(1);
|
|
96
|
+
expect(calls).toEqual([]);
|
|
97
|
+
expect(logs.join('\n')).toMatch(/MAUDE_NO_AUTOBUILD=1/);
|
|
98
|
+
expect(logs.join('\n')).toMatch(/dist\/client\.bundle\.js/);
|
|
99
|
+
expect(logs.join('\n')).toMatch(/node_modules\/react/);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test('spawn failure aborts with exit 1 + remediation hint', async () => {
|
|
103
|
+
seedDist(); // only deps missing
|
|
104
|
+
const { opts, logs, getExited } = harness({
|
|
105
|
+
spawn: async () => ({ code: 42 }),
|
|
106
|
+
});
|
|
107
|
+
await expect(bootSelfHeal(opts)).rejects.toThrow('__exit:1');
|
|
108
|
+
expect(getExited()).toBe(1);
|
|
109
|
+
expect(logs.join('\n')).toMatch(/exited 42/);
|
|
110
|
+
expect(logs.join('\n')).toMatch(/MAUDE_NO_AUTOBUILD=1/);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
// Regression test for writeCompileEntry — the per-target build helper that
|
|
2
|
+
// works around Bun 1.3.4+ --compile NAPI native-binding embedding (DDR-NNN-
|
|
3
|
+
// oxc-parser-bun-compile-workaround). The helper itself is pure (no compile,
|
|
4
|
+
// no spawn): given a target, it writes two files (`init-oxc-<slug>.ts` +
|
|
5
|
+
// `server-<slug>.ts`) under dist/.compile-entries/ and returns the entry path.
|
|
6
|
+
|
|
7
|
+
import { existsSync, readFileSync, rmSync } from 'node:fs';
|
|
8
|
+
import { dirname, join } from 'node:path';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
|
|
11
|
+
import { describe, expect, test } from 'bun:test';
|
|
12
|
+
|
|
13
|
+
import { writeCompileEntry } from '../build.ts';
|
|
14
|
+
|
|
15
|
+
const ROOT = dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
const DEV_SERVER_ROOT = join(ROOT, '..');
|
|
17
|
+
const ENTRY_DIR = join(DEV_SERVER_ROOT, 'dist', '.compile-entries');
|
|
18
|
+
|
|
19
|
+
// PlatformTarget union mirrored from build.ts. Kept inline so the test fails
|
|
20
|
+
// loudly if build.ts adds/removes a target without updating the test.
|
|
21
|
+
const ALL_TARGETS = [
|
|
22
|
+
'bun-darwin-arm64',
|
|
23
|
+
'bun-darwin-x64',
|
|
24
|
+
'bun-linux-x64',
|
|
25
|
+
'bun-linux-arm64',
|
|
26
|
+
'bun-linux-x64-musl',
|
|
27
|
+
'bun-linux-arm64-musl',
|
|
28
|
+
'bun-windows-x64',
|
|
29
|
+
] as const;
|
|
30
|
+
|
|
31
|
+
// Maude-slug → @oxc-parser/binding-<X> NAPI slug. Mirrored from
|
|
32
|
+
// build.ts:oxcBindingSlug — kept in sync intentionally.
|
|
33
|
+
const EXPECTED_OXC_SLUG: Record<string, string> = {
|
|
34
|
+
'darwin-arm64': 'darwin-arm64',
|
|
35
|
+
'darwin-x64': 'darwin-x64',
|
|
36
|
+
'linux-x64': 'linux-x64-gnu',
|
|
37
|
+
'linux-arm64': 'linux-arm64-gnu',
|
|
38
|
+
'linux-x64-musl': 'linux-x64-musl',
|
|
39
|
+
'linux-arm64-musl': 'linux-arm64-musl',
|
|
40
|
+
'win32-x64': 'win32-x64-msvc',
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
function maudeSlug(target: string): string {
|
|
44
|
+
const s = target.replace(/^bun-/, '');
|
|
45
|
+
return s === 'windows-x64' ? 'win32-x64' : s;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
describe('writeCompileEntry', () => {
|
|
49
|
+
test('produces init + entry files for every supported target', () => {
|
|
50
|
+
for (const target of ALL_TARGETS) {
|
|
51
|
+
const slug = maudeSlug(target);
|
|
52
|
+
const initPath = join(ENTRY_DIR, `init-oxc-${slug}.ts`);
|
|
53
|
+
const entryPath = join(ENTRY_DIR, `server-${slug}.ts`);
|
|
54
|
+
|
|
55
|
+
const returned = writeCompileEntry(target);
|
|
56
|
+
expect(returned).toBe(entryPath);
|
|
57
|
+
expect(existsSync(initPath)).toBe(true);
|
|
58
|
+
expect(existsSync(entryPath)).toBe(true);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('init file embeds the matching oxc binding via with-type-file', () => {
|
|
63
|
+
for (const target of ALL_TARGETS) {
|
|
64
|
+
const slug = maudeSlug(target);
|
|
65
|
+
const oxcSlug = EXPECTED_OXC_SLUG[slug];
|
|
66
|
+
const initPath = join(ENTRY_DIR, `init-oxc-${slug}.ts`);
|
|
67
|
+
|
|
68
|
+
writeCompileEntry(target);
|
|
69
|
+
const content = readFileSync(initPath, 'utf8');
|
|
70
|
+
|
|
71
|
+
// Asset embed via Bun's with-type-file syntax — load-bearing for the
|
|
72
|
+
// workaround. Without `with { type: 'file' }` the import resolves to
|
|
73
|
+
// the .node's exports rather than its filesystem path.
|
|
74
|
+
expect(content).toContain(
|
|
75
|
+
`import bindingPath from "@oxc-parser/binding-${oxcSlug}/parser.${oxcSlug}.node" with { type: 'file' };`
|
|
76
|
+
);
|
|
77
|
+
// Env var must be set so oxc-parser's NAPI-RS loader (bindings.js)
|
|
78
|
+
// skips its broken platform-detection switch.
|
|
79
|
+
expect(content).toContain('process.env.NAPI_RS_NATIVE_LIBRARY_PATH = bindingPath;');
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test('entry file imports init BEFORE server.ts (ESM evaluation order matters)', () => {
|
|
84
|
+
for (const target of ALL_TARGETS) {
|
|
85
|
+
const slug = maudeSlug(target);
|
|
86
|
+
const entryPath = join(ENTRY_DIR, `server-${slug}.ts`);
|
|
87
|
+
|
|
88
|
+
writeCompileEntry(target);
|
|
89
|
+
const content = readFileSync(entryPath, 'utf8');
|
|
90
|
+
|
|
91
|
+
const initIdx = content.indexOf(`./init-oxc-${slug}.ts`);
|
|
92
|
+
const serverIdx = content.indexOf('server.ts');
|
|
93
|
+
expect(initIdx).toBeGreaterThanOrEqual(0);
|
|
94
|
+
expect(serverIdx).toBeGreaterThanOrEqual(0);
|
|
95
|
+
// If server.ts is imported before init-oxc, oxc-parser evaluates first
|
|
96
|
+
// and reads NAPI_RS_NATIVE_LIBRARY_PATH before our env-var setter runs.
|
|
97
|
+
expect(initIdx).toBeLessThan(serverIdx);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test('entry uses POSIX path separators in the server.ts import', () => {
|
|
102
|
+
writeCompileEntry('bun-windows-x64');
|
|
103
|
+
const content = readFileSync(join(ENTRY_DIR, 'server-win32-x64.ts'), 'utf8');
|
|
104
|
+
// Even on a Windows host the generated import specifier must use forward
|
|
105
|
+
// slashes — ESM specifiers are not OS paths.
|
|
106
|
+
expect(content).not.toMatch(/\\/);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test('idempotent — calling twice yields identical content', () => {
|
|
110
|
+
const target = 'bun-darwin-arm64';
|
|
111
|
+
const slug = maudeSlug(target);
|
|
112
|
+
|
|
113
|
+
writeCompileEntry(target);
|
|
114
|
+
const a1 = readFileSync(join(ENTRY_DIR, `init-oxc-${slug}.ts`), 'utf8');
|
|
115
|
+
const e1 = readFileSync(join(ENTRY_DIR, `server-${slug}.ts`), 'utf8');
|
|
116
|
+
|
|
117
|
+
writeCompileEntry(target);
|
|
118
|
+
const a2 = readFileSync(join(ENTRY_DIR, `init-oxc-${slug}.ts`), 'utf8');
|
|
119
|
+
const e2 = readFileSync(join(ENTRY_DIR, `server-${slug}.ts`), 'utf8');
|
|
120
|
+
|
|
121
|
+
expect(a2).toBe(a1);
|
|
122
|
+
expect(e2).toBe(e1);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test('cleanup — generated files live under dist/.compile-entries/', () => {
|
|
126
|
+
// Sanity: the helper writes only to the expected directory; nothing
|
|
127
|
+
// leaks elsewhere. Sweep a stale prior dir, regenerate, verify scope.
|
|
128
|
+
rmSync(ENTRY_DIR, { recursive: true, force: true });
|
|
129
|
+
writeCompileEntry('bun-darwin-arm64');
|
|
130
|
+
expect(existsSync(ENTRY_DIR)).toBe(true);
|
|
131
|
+
expect(existsSync(join(ENTRY_DIR, 'init-oxc-darwin-arm64.ts'))).toBe(true);
|
|
132
|
+
expect(existsSync(join(ENTRY_DIR, 'server-darwin-arm64.ts'))).toBe(true);
|
|
133
|
+
});
|
|
134
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Phase 19 / DDR-044 — runtime-bundle error-message remediation.
|
|
2
|
+
// When Bun.build fails because the global install cache is corrupted
|
|
3
|
+
// (EISDIR/ENOENT on a cached package's entry file), surface an actionable
|
|
4
|
+
// `bun pm cache rm <pkg>` hint instead of just relaying the raw log.
|
|
5
|
+
|
|
6
|
+
import { describe, expect, test } from 'bun:test';
|
|
7
|
+
|
|
8
|
+
import { bunCacheRemediation } from '../runtime-bundle.ts';
|
|
9
|
+
|
|
10
|
+
describe('bunCacheRemediation', () => {
|
|
11
|
+
test('returns null on unrelated build failures (real syntax errors etc.)', () => {
|
|
12
|
+
expect(bunCacheRemediation('react', '[error] Unexpected token <')).toBeNull();
|
|
13
|
+
expect(bunCacheRemediation('react-dom', '[error] Could not resolve "missing-pkg"')).toBeNull();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test('matches EISDIR on .bun/install/cache + names the base package', () => {
|
|
17
|
+
const log =
|
|
18
|
+
"[error] EISDIR reading '/Users/iagh/.bun/install/cache/react@19.2.6@@@1 @@1/index.js'";
|
|
19
|
+
const out = bunCacheRemediation('react', log);
|
|
20
|
+
expect(out).not.toBeNull();
|
|
21
|
+
expect(out).toMatch(/bun pm cache rm react/);
|
|
22
|
+
expect(out).toMatch(/bad state/);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test('matches ENOENT on .bun/install/cache too', () => {
|
|
26
|
+
const log = "[error] ENOENT: '/Users/x/.bun/install/cache/react-dom@19.0.0/index.js'";
|
|
27
|
+
const out = bunCacheRemediation('react-dom', log);
|
|
28
|
+
expect(out).toMatch(/bun pm cache rm react-dom/);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('subpath specifiers strip to the base package for the cache rm command', () => {
|
|
32
|
+
// bun pm cache works on base package names — "react/jsx-runtime" → "react".
|
|
33
|
+
const log = "[error] EISDIR '/Users/x/.bun/install/cache/react@19.2.6/jsx-runtime.js'";
|
|
34
|
+
const out = bunCacheRemediation('react/jsx-runtime', log);
|
|
35
|
+
expect(out).toMatch(/bun pm cache rm react\b/);
|
|
36
|
+
expect(out).not.toMatch(/bun pm cache rm react\//);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('case-insensitive match (Bun has bounced casing across versions)', () => {
|
|
40
|
+
const log = '[error] eisdir reading /Users/x/.bun/install/cache/react@19/index.js';
|
|
41
|
+
expect(bunCacheRemediation('react', log)).not.toBeNull();
|
|
42
|
+
});
|
|
43
|
+
});
|
|
@@ -11,11 +11,11 @@
|
|
|
11
11
|
*
|
|
12
12
|
* Authored under {{DS_NAME}}. Tokens + shared component classes load via the
|
|
13
13
|
* dev-server's _shell.html harness — link tags arrive automatically from the
|
|
14
|
-
* iframe's ?tokens= / ?components= query. Class names
|
|
15
|
-
* `_components.css`
|
|
16
|
-
* into a sibling .module.css iff
|
|
17
|
-
*
|
|
18
|
-
* one-off values.
|
|
14
|
+
* iframe's ?tokens= / ?components= query. Class names come from the DS's
|
|
15
|
+
* own `_components.css` — see `system/{{DS_NAME}}/preview/` for the available
|
|
16
|
+
* class shapes. Bespoke classes go into a sibling .module.css iff
|
|
17
|
+
* `css_mode === "modules"`; for `inline` prefer existing classes + `style={{}}`
|
|
18
|
+
* for arbitrary one-off values.
|
|
19
19
|
*
|
|
20
20
|
* The envelope primitives (`DesignCanvas`, `DCSection`, `DCArtboard`) and any
|
|
21
21
|
* specimen helpers come from the dev-server-bundled canvas library via the
|
|
@@ -40,11 +40,11 @@ export default function {{COMPONENT_NAME}}() {
|
|
|
40
40
|
<DesignCanvas>
|
|
41
41
|
<DCSection id="overview" title="{{SUBTITLE}}">
|
|
42
42
|
<DCArtboard id="primary" label="A · primary" width={1280} height={800}>
|
|
43
|
-
<div className="
|
|
43
|
+
<div className="{{ROOT_CLASS}}" data-theme="{{THEME_DEFAULT}}">
|
|
44
44
|
{/* Replace this scaffold with the actual canvas content. The
|
|
45
45
|
envelope this template was rendered against is in
|
|
46
46
|
{{HISTORY_DIR}}/000-envelope.md. */}
|
|
47
|
-
<h1
|
|
47
|
+
<h1>{{NAME}}</h1>
|
|
48
48
|
<p>{{BRIEF}}</p>
|
|
49
49
|
</div>
|
|
50
50
|
</DCArtboard>
|
package/plugins/design/templates/design-system-inspiration/audience-pro/colors-presence.html
CHANGED
|
@@ -4,7 +4,7 @@ DEMONSTRATES: --presence-online, --presence-away, --presence-offline; presence d
|
|
|
4
4
|
COMPOSITION: 4 avatars with presence dots (online / away / offline / idle) + a "live cursors" mockup with 3 simultaneous users
|
|
5
5
|
COPY VOICE: real names, real timestamps
|
|
6
6
|
WHEN SCAFFOLDED: presence family (IF "presence" ∈ activeFamilies — typically pro tools with multiplayer)
|
|
7
|
-
NOTES: Presence dot is 8px circle bottom-right of 32-40px avatars (anchor on the visual bottom-right, not on a math grid). Cursor labels are subtle (pill at 11px, low opacity bg). Don't blink presence — too noisy in a busy view.
|
|
7
|
+
NOTES: Presence dot is 8px circle bottom-right of 32-40px avatars (anchor on the visual bottom-right, not on a math grid). Per-user cursor colors below are illustrative only — the inline `oklch(...)` values are NOT part of the project's design system. A real implementation should source these from a `--presence-user-*` family or derive from `--accent` deterministically. Cursor labels are subtle (pill at 11px, low opacity bg). Don't blink presence — too noisy in a busy view.
|
|
8
8
|
-->
|
|
9
9
|
<!doctype html>
|
|
10
10
|
<html lang="en">
|
|
@@ -19,13 +19,15 @@
|
|
|
19
19
|
|
|
20
20
|
## Foundations
|
|
21
21
|
|
|
22
|
-
###
|
|
22
|
+
### Token contract
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
All visuals reference `var(--*)` tokens declared in `colors_and_type.css`. Adding a new visual concept means **extending the tokens CSS first**, never inventing values inline in a canvas.
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
{{color_space_block}}
|
|
27
|
+
|
|
28
|
+
### Accent strategy
|
|
27
29
|
|
|
28
|
-
|
|
30
|
+
{{accent_rules_block}}
|
|
29
31
|
|
|
30
32
|
### Active token families
|
|
31
33
|
|
|
@@ -33,8 +35,9 @@ All visuals reference `var(--*)` tokens declared in `colors_and_type.css`. No ha
|
|
|
33
35
|
|
|
34
36
|
## Hard rules (non-negotiable)
|
|
35
37
|
|
|
36
|
-
- **Accessibility:** WCAG 2.1 AA contrast at every theme. Focus-visible always rendered.
|
|
37
|
-
- **
|
|
38
|
+
- **Accessibility:** WCAG 2.1 AA contrast at every theme. Focus-visible always rendered. `prefers-reduced-motion: reduce` respected.
|
|
39
|
+
- **Touch targets:** {{touch_target_rule}}
|
|
40
|
+
- **No off-token values** in canvases. Extend tokens; don't inline.
|
|
38
41
|
- **No placeholder copy.** Real product strings only — no "Lorem Solutions Inc.", no "Click here".
|
|
39
42
|
- **Type ladder:** {{type_scale_summary}}
|
|
40
43
|
- **Motion:** every animation uses a `var(--dur-*)` and `var(--ease-*)` token. No magic numbers.
|
|
@@ -52,7 +55,8 @@ All visuals reference `var(--*)` tokens declared in `colors_and_type.css`. No ha
|
|
|
52
55
|
## Hard-stops the completeness-critic enforces
|
|
53
56
|
|
|
54
57
|
- Core tokens present (`--accent`, `--bg-0..4`, `--fg-0..3`, `--dur-flip`)
|
|
55
|
-
-
|
|
58
|
+
- Accent family count matches the declared strategy ({{accent_strategy_summary}})
|
|
59
|
+
- Color space matches the declared choice ({{color_space_summary}})
|
|
56
60
|
- `system/{{ds_dirname}}/preview/` populated with at least 8 specimens
|
|
57
61
|
- `colors_and_type.css` linked from every specimen
|
|
58
62
|
- `prefers-reduced-motion: reduce` guard present in tokens CSS
|
|
@@ -24,8 +24,9 @@ It is **not** auto-invoked by `/design:edit` or `/design:new` on a project with
|
|
|
24
24
|
|
|
25
25
|
## What the agent should remember
|
|
26
26
|
|
|
27
|
-
- **
|
|
28
|
-
- **All visuals reference `var(--*)` tokens.** No
|
|
27
|
+
- **Accent strategy:** {{accent_strategy_summary}}. {{accent_rules_summary}}
|
|
28
|
+
- **All visuals reference `var(--*)` tokens.** No off-token values in canvases.
|
|
29
|
+
- **Color space:** {{color_space_summary}}.
|
|
29
30
|
- **Voice:** {{voice_tone_summary}}
|
|
30
31
|
- **Iconography:** {{iconography_summary}}
|
|
31
32
|
- **Theme default:** `{{theme_default}}`. {{theme_extra}}
|
|
@@ -47,4 +48,4 @@ It is **not** auto-invoked by `/design:edit` or `/design:new` on a project with
|
|
|
47
48
|
|
|
48
49
|
## How to extend
|
|
49
50
|
|
|
50
|
-
If a canvas iteration needs a value not currently in the system, **extend `colors_and_type.css` first**. Adding a new variant of an existing token (e.g. `--accent-tertiary`) is fine
|
|
51
|
+
If a canvas iteration needs a value not currently in the system, **extend `colors_and_type.css` first**. Adding a new variant of an existing token (e.g. `--accent-tertiary`) is fine. Adding a parallel accent family (e.g. `--accent2`) is only allowed if the project's declared `accentStrategy` in `config.json` permits it (e.g. `paired` or `chromatic-N`) — the completeness-critic enforces the declared strategy.
|
|
@@ -4,55 +4,59 @@
|
|
|
4
4
|
* Authoritative token file. Every canvas in <designRoot>/ui/ links to this.
|
|
5
5
|
* Production code should consume the same values (compiled to TS/JS or kept as CSS vars).
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* -
|
|
11
|
-
*
|
|
12
|
-
*
|
|
7
|
+
* This file is the single source of truth for the project's visual language.
|
|
8
|
+
* Every concrete value below is supplied by the discovery payload — there are
|
|
9
|
+
* NO universal defaults baked into this template. Spacing, type, easing,
|
|
10
|
+
* shadows, max-width, accent strategy, and color space are all project-flavored.
|
|
11
|
+
*
|
|
12
|
+
* Two invariants this template does enforce:
|
|
13
|
+
* 1. `prefers-reduced-motion: reduce` collapses every duration to 1ms (a11y).
|
|
14
|
+
* 2. Tokens used by canvases live under one of the documented family prefixes
|
|
15
|
+
* (--bg-*, --fg-*, --accent*, --border-*, --status-*, --space-*, --type-*,
|
|
16
|
+
* --lh-*, --radius-*, --shadow-*, --dur-*, --ease-*, --layout-*, --font-*).
|
|
17
|
+
*
|
|
18
|
+
* Everything else — palette structure, type ladder, motion personality — is
|
|
19
|
+
* a project decision recorded during /design:setup-ds.
|
|
13
20
|
*/
|
|
14
21
|
|
|
15
22
|
:root,
|
|
16
23
|
.{{root_class}}[data-theme="{{theme_default}}"] {
|
|
17
24
|
/* ─── Surfaces (deepest → highest) ─────────────────────────────────── */
|
|
18
|
-
--bg-0: {{
|
|
19
|
-
--bg-1: {{
|
|
20
|
-
--bg-2: {{
|
|
21
|
-
--bg-3: {{
|
|
22
|
-
--bg-4: {{
|
|
25
|
+
--bg-0: {{bg_0}}; /* page bg */
|
|
26
|
+
--bg-1: {{bg_1}}; /* card / panel bg */
|
|
27
|
+
--bg-2: {{bg_2}}; /* nested panel / popover */
|
|
28
|
+
--bg-3: {{bg_3}}; /* input bg / subtle row hover */
|
|
29
|
+
--bg-4: {{bg_4}}; /* hover / pressed state */
|
|
23
30
|
|
|
24
31
|
/* ─── Borders ──────────────────────────────────────────────────────── */
|
|
25
|
-
--border-subtle:
|
|
26
|
-
--border-default:
|
|
27
|
-
--border-strong:
|
|
32
|
+
--border-subtle: {{border_subtle}};
|
|
33
|
+
--border-default: {{border_default}};
|
|
34
|
+
--border-strong: {{border_strong}};
|
|
28
35
|
|
|
29
36
|
/* ─── Text ─────────────────────────────────────────────────────────── */
|
|
30
|
-
--fg-0: {{
|
|
31
|
-
--fg-1: {{
|
|
32
|
-
--fg-2: {{
|
|
33
|
-
--fg-3: {{
|
|
37
|
+
--fg-0: {{fg_0}}; /* primary text */
|
|
38
|
+
--fg-1: {{fg_1}}; /* secondary text */
|
|
39
|
+
--fg-2: {{fg_2}}; /* tertiary / muted */
|
|
40
|
+
--fg-3: {{fg_3}}; /* disabled */
|
|
34
41
|
|
|
35
|
-
/* ─── Accent (
|
|
36
|
-
|
|
37
|
-
--accent-hover: oklch(from var(--accent) calc(l - 0.04) c h);
|
|
38
|
-
--accent-active: oklch(from var(--accent) calc(l - 0.08) c h);
|
|
39
|
-
--accent-fg: {{accent_fg_oklch}};
|
|
42
|
+
/* ─── Accent ({{accent_strategy_summary}}) ─────────────────────────── */
|
|
43
|
+
{{accent_block}}
|
|
40
44
|
|
|
41
45
|
/* ─── Status (only if "status" ∈ activeFamilies) ───────────────────── */
|
|
42
|
-
--status-success:
|
|
43
|
-
--status-warn:
|
|
44
|
-
--status-error:
|
|
45
|
-
--status-info:
|
|
46
|
+
--status-success: {{status_success}};
|
|
47
|
+
--status-warn: {{status_warn}};
|
|
48
|
+
--status-error: {{status_error}};
|
|
49
|
+
--status-info: {{status_info}};
|
|
46
50
|
|
|
47
51
|
/* ─── Presence (only if "presence" ∈ activeFamilies) ───────────────── */
|
|
48
|
-
--presence-online:
|
|
49
|
-
--presence-away:
|
|
50
|
-
--presence-offline:
|
|
52
|
+
--presence-online: {{presence_online}};
|
|
53
|
+
--presence-away: {{presence_away}};
|
|
54
|
+
--presence-offline: {{presence_offline}};
|
|
51
55
|
|
|
52
56
|
/* ─── Shadows / elevation ──────────────────────────────────────────── */
|
|
53
|
-
--shadow-sm:
|
|
54
|
-
--shadow-md:
|
|
55
|
-
--shadow-lg:
|
|
57
|
+
--shadow-sm: {{shadow_sm}};
|
|
58
|
+
--shadow-md: {{shadow_md}};
|
|
59
|
+
--shadow-lg: {{shadow_lg}};
|
|
56
60
|
|
|
57
61
|
/* ─── Radii ────────────────────────────────────────────────────────── */
|
|
58
62
|
--radius-xs: {{radius_xs}};
|
|
@@ -60,45 +64,45 @@
|
|
|
60
64
|
--radius-md: {{radius_md}};
|
|
61
65
|
--radius-lg: {{radius_lg}};
|
|
62
66
|
--radius-xl: {{radius_xl}};
|
|
63
|
-
--radius-pill:
|
|
67
|
+
--radius-pill: {{radius_pill}};
|
|
64
68
|
|
|
65
|
-
/* ─── Spacing
|
|
66
|
-
--space-0:
|
|
67
|
-
--space-1:
|
|
68
|
-
--space-2:
|
|
69
|
-
--space-3:
|
|
70
|
-
--space-4:
|
|
71
|
-
--space-5:
|
|
72
|
-
--space-6:
|
|
73
|
-
--space-7:
|
|
74
|
-
--space-8:
|
|
69
|
+
/* ─── Spacing ──────────────────────────────────────────────────────── */
|
|
70
|
+
--space-0: {{space_0}};
|
|
71
|
+
--space-1: {{space_1}};
|
|
72
|
+
--space-2: {{space_2}};
|
|
73
|
+
--space-3: {{space_3}};
|
|
74
|
+
--space-4: {{space_4}};
|
|
75
|
+
--space-5: {{space_5}};
|
|
76
|
+
--space-6: {{space_6}};
|
|
77
|
+
--space-7: {{space_7}};
|
|
78
|
+
--space-8: {{space_8}};
|
|
75
79
|
|
|
76
80
|
/* ─── Typography ───────────────────────────────────────────────────── */
|
|
77
81
|
--font-display: {{font_display}};
|
|
78
82
|
--font-body: {{font_body}};
|
|
79
83
|
--font-mono: {{font_mono}};
|
|
80
84
|
|
|
81
|
-
/*
|
|
82
|
-
--type-xs:
|
|
83
|
-
--type-sm:
|
|
84
|
-
--type-base:
|
|
85
|
-
--type-md:
|
|
86
|
-
--type-lg:
|
|
87
|
-
--type-xl:
|
|
88
|
-
--type-2xl:
|
|
89
|
-
--type-3xl:
|
|
85
|
+
/* Type scale */
|
|
86
|
+
--type-xs: {{type_xs}}; --lh-xs: {{lh_xs}};
|
|
87
|
+
--type-sm: {{type_sm}}; --lh-sm: {{lh_sm}};
|
|
88
|
+
--type-base: {{type_base}}; --lh-base: {{lh_base}};
|
|
89
|
+
--type-md: {{type_md}}; --lh-md: {{lh_md}};
|
|
90
|
+
--type-lg: {{type_lg}}; --lh-lg: {{lh_lg}};
|
|
91
|
+
--type-xl: {{type_xl}}; --lh-xl: {{lh_xl}};
|
|
92
|
+
--type-2xl: {{type_2xl}}; --lh-2xl: {{lh_2xl}};
|
|
93
|
+
--type-3xl: {{type_3xl}}; --lh-3xl: {{lh_3xl}};
|
|
90
94
|
|
|
91
95
|
/* ─── Motion ───────────────────────────────────────────────────────── */
|
|
92
96
|
--dur-flip: {{dur_flip}};
|
|
93
97
|
--dur-panel: {{dur_panel}};
|
|
94
98
|
--dur-route: {{dur_route}};
|
|
95
99
|
--dur-soft: {{dur_soft}};
|
|
96
|
-
--ease-out:
|
|
97
|
-
--ease-in-out:
|
|
100
|
+
--ease-out: {{ease_out_curve}};
|
|
101
|
+
--ease-in-out: {{ease_in_out_curve}};
|
|
98
102
|
|
|
99
103
|
/* ─── Layout ───────────────────────────────────────────────────────── */
|
|
100
|
-
--layout-max-w:
|
|
101
|
-
--layout-gutter:
|
|
104
|
+
--layout-max-w: {{layout_max_w}};
|
|
105
|
+
--layout-gutter: {{layout_gutter}};
|
|
102
106
|
}
|
|
103
107
|
|
|
104
108
|
@media (prefers-reduced-motion: reduce) {
|