baldart 4.75.0 → 4.76.1
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 +29 -0
- package/VERSION +1 -1
- package/bin/baldart.js +26 -0
- package/framework/.claude/agents/code-reviewer.md +4 -1
- package/framework/.claude/commands/design-review.md +1 -1
- package/framework/.claude/skills/design-sync/SKILL.md +174 -0
- package/framework/.claude/skills/design-system-init/SKILL.md +43 -1
- package/framework/.claude/skills/design-system-init/scripts/compile-ds-cards.mjs +119 -0
- package/framework/.claude/skills/design-system-init/scripts/render-manifest.mjs +187 -0
- package/framework/.claude/skills/ds-render/SKILL.md +79 -0
- package/framework/.claude/skills/e2e-review/SKILL.md +16 -3
- package/framework/.claude/skills/new/references/implement.md +9 -1
- package/framework/.claude/skills/prd/references/discovery-phase.md +1 -0
- package/framework/.claude/skills/prd/references/ui-design-phase.md +5 -0
- package/framework/.claude/skills/ui-design/SKILL.md +11 -0
- package/framework/agents/design-system-protocol.md +25 -3
- package/framework/routines/ds-drift.routine.yml +13 -1
- package/package.json +1 -1
- package/src/commands/render.js +120 -0
- package/src/utils/design-sync-state.js +162 -0
- package/src/utils/render-adapters/claude-design-seed.js +51 -0
- package/src/utils/render-adapters/index.js +59 -0
- package/src/utils/render-adapters/storybook.js +104 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
const StorybookAdapter = require('./storybook');
|
|
2
|
+
const ClaudeDesignSeedAdapter = require('./claude-design-seed');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Render-harness adapter registry (Stage C, since v4.76.0).
|
|
6
|
+
*
|
|
7
|
+
* Mirrors the lsp/toolchain/routine adapter-registry pattern (REGISTRY +
|
|
8
|
+
* getAdapter + detectAll). An adapter turns a `render-manifest.json` into a real
|
|
9
|
+
* render surface (mounted primitives) per ONE strategy. The harness exists to
|
|
10
|
+
* render registry primitives in isolation — today BALDART documents components but
|
|
11
|
+
* never renders them isolated.
|
|
12
|
+
*
|
|
13
|
+
* Adding a render strategy:
|
|
14
|
+
* 1. Create `src/utils/render-adapters/<name>.js` with the shape of
|
|
15
|
+
* StorybookAdapter (static get name, get label, static detect(cwd),
|
|
16
|
+
* generate({manifest,outDir})).
|
|
17
|
+
* 2. Add it to REGISTRY below, in PRIORITY order (detectAll returns the first
|
|
18
|
+
* that fires — Storybook is canonical when present).
|
|
19
|
+
*
|
|
20
|
+
* REFUTED (do NOT add): a per-stack framework-native preview-route generator +
|
|
21
|
+
* an auto-provider-shim. Those are code-generators with unbounded surface that
|
|
22
|
+
* mount providers out-of-tree → wrong-but-green renders (false fidelity). Use
|
|
23
|
+
* Storybook where present; the seed-gallery only for the one-time seed case.
|
|
24
|
+
*/
|
|
25
|
+
const REGISTRY = {
|
|
26
|
+
storybook: StorybookAdapter,
|
|
27
|
+
'claude-design-seed': ClaudeDesignSeedAdapter,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// Priority order for detection: Storybook is the canonical render path when
|
|
31
|
+
// present; the seed gallery is a conditional one-time fallback.
|
|
32
|
+
const PRIORITY = ['storybook', 'claude-design-seed'];
|
|
33
|
+
|
|
34
|
+
function listAdapters() { return Object.keys(REGISTRY); }
|
|
35
|
+
|
|
36
|
+
function getAdapter(name, cwd) {
|
|
37
|
+
const Cls = REGISTRY[name];
|
|
38
|
+
if (!Cls) throw new Error(`Unknown render adapter: ${name}. Available: ${listAdapters().join(', ')}`);
|
|
39
|
+
return new Cls(cwd);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Return the FIRST adapter (by priority) whose static detect() fires, or null
|
|
44
|
+
* when none does (→ the harness is a no-op for this consumer, which is fine:
|
|
45
|
+
* Stage C degrades cleanly without Storybook). Pure: no side effects.
|
|
46
|
+
*/
|
|
47
|
+
function detect(cwd = process.cwd()) {
|
|
48
|
+
for (const name of PRIORITY) {
|
|
49
|
+
try { if (REGISTRY[name].detect(cwd)) return name; } catch (_) { /* keep scanning */ }
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** All adapters whose detect() fires (diagnostic). */
|
|
55
|
+
function detectAll(cwd = process.cwd()) {
|
|
56
|
+
return PRIORITY.filter((name) => { try { return REGISTRY[name].detect(cwd); } catch { return false; } });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
module.exports = { REGISTRY, listAdapters, getAdapter, detect, detectAll };
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Storybook render adapter (Stage C, v1 — the ONLY render path the adversarial
|
|
6
|
+
* pass kept). When the consumer already has Storybook, we generate EPHEMERAL CSF
|
|
7
|
+
* stories from the render-manifest and let Storybook's OWN runtime do the
|
|
8
|
+
* context-shimming (ThemeProvider/router/i18n via the project's real
|
|
9
|
+
* `.storybook/preview` decorators). We never re-grep a provider chain and never
|
|
10
|
+
* mount out-of-tree — that refuted "auto-provider-shim" is exactly what produces
|
|
11
|
+
* wrong-but-green renders. Storybook exists to solve isolated mounting; reuse it.
|
|
12
|
+
*
|
|
13
|
+
* The generated stories carry a marker so they are identifiable + safe to delete;
|
|
14
|
+
* co-located hand-authored stories are NEVER touched.
|
|
15
|
+
*/
|
|
16
|
+
const MARKER = '// @baldart-render-harness — generated from render-manifest.json, safe to delete';
|
|
17
|
+
|
|
18
|
+
class StorybookAdapter {
|
|
19
|
+
constructor(cwd = process.cwd()) { this.cwd = cwd; }
|
|
20
|
+
|
|
21
|
+
static get name() { return 'storybook'; }
|
|
22
|
+
get label() { return 'Storybook'; }
|
|
23
|
+
|
|
24
|
+
/** Detect: a `.storybook/` config dir at the repo root (matches configure.js:234). */
|
|
25
|
+
static detect(cwd = process.cwd()) {
|
|
26
|
+
return fs.existsSync(path.join(cwd, '.storybook'));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Generate one ephemeral `.stories.tsx` per component into outDir.
|
|
31
|
+
* Returns { written: [paths], skipped: [names] }.
|
|
32
|
+
* i18n note (M6): the project's own preview decorators supply the i18n provider
|
|
33
|
+
* if it has one; if it doesn't, the story still renders but text shows raw keys —
|
|
34
|
+
* such PNGs are flagged i18n-incomplete by `render shot`, never fed to a fidelity diff.
|
|
35
|
+
*/
|
|
36
|
+
generate({ manifest, outDir }) {
|
|
37
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
38
|
+
const written = [];
|
|
39
|
+
const skipped = [];
|
|
40
|
+
for (const comp of (manifest.components || [])) {
|
|
41
|
+
if (!comp.source) { skipped.push(comp.name); continue; } // no source → can't import → skip honestly
|
|
42
|
+
const file = path.join(outDir, `${comp.name}.baldart.stories.tsx`);
|
|
43
|
+
fs.writeFileSync(file, this._story(comp, outDir));
|
|
44
|
+
written.push(file);
|
|
45
|
+
}
|
|
46
|
+
return { written, skipped };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
_story(comp, outDir) {
|
|
50
|
+
const abs = path.join(this.cwd, comp.source);
|
|
51
|
+
let imp = path.relative(outDir, abs).replace(/\.(tsx?|jsx?)$/, '');
|
|
52
|
+
if (!imp.startsWith('.')) imp = './' + imp;
|
|
53
|
+
imp = imp.split(path.sep).join('/');
|
|
54
|
+
const lines = [
|
|
55
|
+
MARKER,
|
|
56
|
+
`import { ${comp.name} } from '${imp}';`,
|
|
57
|
+
``,
|
|
58
|
+
`export default { title: 'Baldart Harness/${comp.name}', component: ${comp.name} };`,
|
|
59
|
+
``,
|
|
60
|
+
];
|
|
61
|
+
const usedNames = new Set();
|
|
62
|
+
for (const entry of (comp.entries || [])) {
|
|
63
|
+
if (entry.truncated) continue;
|
|
64
|
+
let exp = this._exportName(entry, comp.name);
|
|
65
|
+
while (usedNames.has(exp)) exp += '_';
|
|
66
|
+
usedNames.add(exp);
|
|
67
|
+
lines.push(`export const ${exp} = { args: ${this._args(entry.props)} };`);
|
|
68
|
+
}
|
|
69
|
+
return lines.join('\n') + '\n';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
_exportName(entry, compName) {
|
|
73
|
+
let tail = entry.id.startsWith(compName + '--') ? entry.id.slice(compName.length + 2) : entry.id;
|
|
74
|
+
const parts = tail.split('--').map((p) => p.replace(/[^A-Za-z0-9]+/g, ' ').trim());
|
|
75
|
+
let name = parts.map((p) => p.split(' ').map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join('')).join('');
|
|
76
|
+
if (!name || /^\d/.test(name)) name = 'Story' + name;
|
|
77
|
+
return name;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
_args(props) {
|
|
81
|
+
const out = Object.entries(props || {}).map(([k, v]) => `${JSON.stringify(k)}: ${this._coerce(v)}`);
|
|
82
|
+
return `{ ${out.join(', ')} }`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
_coerce(v) {
|
|
86
|
+
if (v === 'true' || v === true) return 'true';
|
|
87
|
+
if (v === 'false' || v === false) return 'false';
|
|
88
|
+
if (typeof v === 'string' && /^-?\d+(\.\d+)?$/.test(v)) return v;
|
|
89
|
+
return JSON.stringify(v);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** How `render shot` builds a static render surface (consumer-run, needs deps). */
|
|
93
|
+
buildSurfaceCommand() {
|
|
94
|
+
return { cmd: 'npx', args: ['storybook', 'build', '-o', '.harness/storybook-static'] };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** The iframe URL pattern for a story id in a static Storybook build. */
|
|
98
|
+
storyUrl(baseUrl, storyId) {
|
|
99
|
+
return `${baseUrl}/iframe.html?id=${storyId}&viewMode=story`;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
module.exports = StorybookAdapter;
|
|
104
|
+
module.exports.MARKER = MARKER;
|