mirror of
https://github.com/nexu-io/open-design.git
synced 2026-06-01 03:14:35 +07:00
* Add Claude-style design system workflow * Merge design system workflow into main * Restore design system workflow UI styles * Fix design system setup scrolling * Fix design system setup connector button * Preserve connector auth link after popup block * Simplify connected GitHub setup state * Open generated design system workspace project * Summarize design system auto prompt in chat * Add bounded GitHub connector design intake * Prefer path-scoped GitHub intake tools * Restore branch GitHub design context intake * Restore design system review workspace * Restore design system manager tab * Let design system workflow routes own details * Open editable design systems as projects * Restore design system workspace coverage * Fix bounded GitHub connector intake * Hide design system review while generating * Suppress design system generation questions * Constrain GitHub design intake to bounded command * Tolerate oversized GitHub metadata during intake * Rebuild daemon CLI when sources change * Fallback when GitHub connector snapshots are rate limited * Allow GitHub intake without Composio * Use native GitHub auth for design intake * Remove design system review group heading * Improve design system extraction evidence * Align design system scaffold with Claude output * Add evidence inventory for design system intake * Add local design system evidence intake * Add design system package audit gate * Allow auditing Claude Design reference packages * Audit design system package content quality * Migrate legacy design system artifacts * Clean migrated design system artifacts * Require modular design system UI kits * Reject thin design system UI kits * Prioritize core design evidence intake * Require role-based design system UI kits * Clean stale design system manifest references * Require representative preserved design assets * Warn on generic design system visuals * Enforce design system quality warnings * Audit connected design system UI kits * Require mounted design system UI kits * Require composed design system app shells * Require runnable JSX design system kits * Require browser globals for design system components * Infer design system names from source URLs * Require source examples in design system packages * Bind preserved fonts in design system tokens * Require skill frontmatter in design system packages * Preserve build icons in design system packages * Require real assets in brand previews * Require substantive source examples * Require product overview in design system README * Require reusable UI kit README * Require reusable design system skill docs * Seed Claude-style UI kit entry contract * Preserve runtime build assets in design packages * Audit design system packages after generation * Audit design system first-run output * Audit source-backed preview cards * Align design system UI kit scaffolds * Materialize design evidence package artifacts * Show project chat during design system setup * Hand off design system setup to project chat * Auto-repair design system audit failures * Harden design system evidence preservation * Tighten design system package guidance * Add targeted design system repair guidance * Bound design system audit auto repair * Use connector statuses in design system setup * Audit design system preview manifests * Require README preview manifests for design systems * Fix design system GitHub intake handoff * Fix daemon prompt CI assertions
350 lines
13 KiB
TypeScript
350 lines
13 KiB
TypeScript
import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
import { tmpdir } from 'node:os';
|
|
import path from 'node:path';
|
|
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
|
|
import {
|
|
createUserDesignSystem,
|
|
deleteUserDesignSystem,
|
|
linkUserDesignSystemProject,
|
|
listDesignSystems,
|
|
listUserDesignSystemFiles,
|
|
readDesignSystem,
|
|
readUserDesignSystemFile,
|
|
updateUserDesignSystem,
|
|
} from '../src/design-systems.js';
|
|
|
|
describe('design systems registry', () => {
|
|
let root: string;
|
|
|
|
beforeEach(async () => {
|
|
root = await mkdtemp(path.join(tmpdir(), 'od-design-systems-'));
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await rm(root, { recursive: true, force: true });
|
|
});
|
|
|
|
it('lists bundled design systems as published and non-editable', async () => {
|
|
await mkdir(path.join(root, 'acme'), { recursive: true });
|
|
await writeFile(
|
|
path.join(root, 'acme', 'DESIGN.md'),
|
|
'# Acme\n\n> Category: Custom\n> Surface: web\n\nAcme brand.\n',
|
|
);
|
|
|
|
const systems = await listDesignSystems(root);
|
|
|
|
expect(systems).toMatchObject([
|
|
{
|
|
id: 'acme',
|
|
title: 'Acme',
|
|
category: 'Custom',
|
|
status: 'published',
|
|
source: 'built-in',
|
|
isEditable: false,
|
|
},
|
|
]);
|
|
});
|
|
|
|
it('creates, updates, reads, and deletes user design systems with prefixed ids', async () => {
|
|
const created = await createUserDesignSystem(root, {
|
|
title: 'Acme Product',
|
|
summary: 'Dense product UI.',
|
|
category: 'Custom',
|
|
status: 'draft',
|
|
provenance: {
|
|
companyBlurb: 'Acme builds dense product UI.',
|
|
githubUrls: ['https://github.com/acme/product'],
|
|
localCodeFiles: ['src/components/Button.tsx'],
|
|
figFiles: ['brand.fig'],
|
|
assetFiles: ['logo.svg'],
|
|
notes: 'Use compact review flows.',
|
|
},
|
|
});
|
|
|
|
expect(created.id).toBe('user:acme-product');
|
|
expect(created.source).toBe('user');
|
|
expect(created.isEditable).toBe(true);
|
|
expect(created.status).toBe('draft');
|
|
expect(created.provenance).toMatchObject({
|
|
companyBlurb: 'Acme builds dense product UI.',
|
|
githubUrls: ['https://github.com/acme/product'],
|
|
localCodeFiles: ['src/components/Button.tsx'],
|
|
figFiles: ['brand.fig'],
|
|
assetFiles: ['logo.svg'],
|
|
notes: 'Use compact review flows.',
|
|
});
|
|
const files = await listUserDesignSystemFiles(root, created.id);
|
|
expect(files?.map((file) => file.path)).toEqual(
|
|
expect.arrayContaining([
|
|
'DESIGN.md',
|
|
'README.md',
|
|
'SKILL.md',
|
|
'context/provenance.json',
|
|
'context/provenance.md',
|
|
'colors_and_type.css',
|
|
'preview/colors-primary.html',
|
|
'preview/typography-specimens.html',
|
|
'assets/logo.svg',
|
|
'ui_kits/app/index.html',
|
|
'ui_kits/app/README.md',
|
|
'ui_kits/app/components/App.jsx',
|
|
'ui_kits/app/components/Sidebar.jsx',
|
|
'ui_kits/app/components/AssistantsList.jsx',
|
|
'ui_kits/app/components/ChatArea.jsx',
|
|
'ui_kits/app/components/InputBar.jsx',
|
|
'ui_kits/app/components/MessageBubble.jsx',
|
|
]),
|
|
);
|
|
await expect(readUserDesignSystemFile(root, created.id, 'ui_kits/app/index.html'))
|
|
.resolves
|
|
.toMatchObject({
|
|
content: expect.stringContaining('ReactDOM.createRoot'),
|
|
});
|
|
await expect(readUserDesignSystemFile(root, created.id, 'ui_kits/app/index.html'))
|
|
.resolves
|
|
.toMatchObject({
|
|
content: expect.stringContaining('components/App.jsx'),
|
|
});
|
|
await expect(readUserDesignSystemFile(root, created.id, 'ui_kits/app/components/App.jsx'))
|
|
.resolves
|
|
.toMatchObject({
|
|
content: expect.stringContaining('<Sidebar'),
|
|
});
|
|
await expect(readUserDesignSystemFile(root, created.id, 'ui_kits/app/components/App.jsx'))
|
|
.resolves
|
|
.toMatchObject({
|
|
content: expect.stringContaining('window.App = App'),
|
|
});
|
|
await expect(readUserDesignSystemFile(root, created.id, 'README.md'))
|
|
.resolves
|
|
.toMatchObject({
|
|
path: 'README.md',
|
|
kind: 'document',
|
|
content: expect.stringContaining('Acme Product'),
|
|
});
|
|
await expect(readUserDesignSystemFile(root, created.id, 'context/provenance.json'))
|
|
.resolves
|
|
.toMatchObject({
|
|
path: 'context/provenance.json',
|
|
kind: 'data',
|
|
content: expect.stringContaining('https://github.com/acme/product'),
|
|
});
|
|
await expect(readUserDesignSystemFile(root, created.id, 'context/provenance.md'))
|
|
.resolves
|
|
.toMatchObject({
|
|
path: 'context/provenance.md',
|
|
kind: 'document',
|
|
content: expect.stringContaining('Acme builds dense product UI.'),
|
|
});
|
|
await expect(readUserDesignSystemFile(root, created.id, '../metadata.json'))
|
|
.resolves
|
|
.toBeNull();
|
|
|
|
const linked = await linkUserDesignSystemProject(root, created.id, 'ds-acme-product');
|
|
expect(linked?.projectId).toBe('ds-acme-product');
|
|
await expect(listDesignSystems(root, { idPrefix: 'user:' }))
|
|
.resolves
|
|
.toEqual(expect.arrayContaining([
|
|
expect.objectContaining({ id: created.id, projectId: 'ds-acme-product' }),
|
|
]));
|
|
|
|
const updated = await updateUserDesignSystem(root, created.id, {
|
|
title: 'Acme Product System',
|
|
status: 'published',
|
|
body: '# Acme Product System\n\n> Category: Custom\n> Surface: web\n\nPublished.\n',
|
|
});
|
|
|
|
expect(updated?.status).toBe('published');
|
|
expect(updated?.title).toBe('Acme Product System');
|
|
expect(updated?.projectId).toBe('ds-acme-product');
|
|
await expect(readDesignSystem(root, created.id, { idPrefix: 'user:' }))
|
|
.resolves
|
|
.toContain('Published.');
|
|
|
|
await expect(deleteUserDesignSystem(root, created.id)).resolves.toBe(true);
|
|
await expect(listDesignSystems(root, { idPrefix: 'user:' })).resolves.toEqual([]);
|
|
});
|
|
|
|
it('rejects traversal ids when reading design systems', async () => {
|
|
await expect(readDesignSystem(root, '../package')).resolves.toBeNull();
|
|
await expect(readDesignSystem(root, 'user:../package', { idPrefix: 'user:' }))
|
|
.resolves
|
|
.toBeNull();
|
|
});
|
|
|
|
it('backfills generated files for older user design systems', async () => {
|
|
await mkdir(path.join(root, 'legacy'), { recursive: true });
|
|
await writeFile(
|
|
path.join(root, 'legacy', 'DESIGN.md'),
|
|
'# Legacy System\n\n> Category: Custom\n> Surface: web\n\nLegacy body.\n',
|
|
);
|
|
|
|
const files = await listUserDesignSystemFiles(root, 'user:legacy');
|
|
|
|
expect(files?.map((file) => file.path)).toEqual(
|
|
expect.arrayContaining([
|
|
'README.md',
|
|
'SKILL.md',
|
|
'context/provenance.json',
|
|
'colors_and_type.css',
|
|
'preview/colors-primary.html',
|
|
'ui_kits/app/components/App.jsx',
|
|
'ui_kits/app/components/Sidebar.jsx',
|
|
'ui_kits/app/components/AssistantsList.jsx',
|
|
'ui_kits/app/components/ChatArea.jsx',
|
|
'ui_kits/app/components/InputBar.jsx',
|
|
'ui_kits/app/components/MessageBubble.jsx',
|
|
]),
|
|
);
|
|
});
|
|
|
|
it('migrates older review artifact names into the Claude-style package structure', async () => {
|
|
await mkdir(path.join(root, 'legacy', 'preview'), { recursive: true });
|
|
await mkdir(path.join(root, 'legacy', 'ui_kits', 'generated_interface'), { recursive: true });
|
|
await writeFile(
|
|
path.join(root, 'legacy', 'DESIGN.md'),
|
|
'# Legacy System\n\n> Category: Custom\n> Surface: web\n\nLegacy body.\n',
|
|
);
|
|
await writeFile(
|
|
path.join(root, 'legacy', 'README.md'),
|
|
'# Legacy\n\nReview preview/typography-scale.html and ui_kits/generated_interface/index.html first.\n',
|
|
);
|
|
await writeFile(
|
|
path.join(root, 'legacy', 'SKILL.md'),
|
|
'# Legacy Skill\n\nUse preview/colors-ui-palette.html, preview/spacing-system.html, and ui_kits/generated_interface/.\n',
|
|
);
|
|
await writeFile(path.join(root, 'legacy', 'preview', 'colors-ui-palette.html'), '<!doctype html><html><body>colors</body></html>');
|
|
await writeFile(path.join(root, 'legacy', 'preview', 'colors-node-types.html'), '<!doctype html><html><body>nodes</body></html>');
|
|
await writeFile(path.join(root, 'legacy', 'preview', 'typography-scale.html'), '<!doctype html><html><body>type</body></html>');
|
|
await writeFile(path.join(root, 'legacy', 'preview', 'spacing-system.html'), '<!doctype html><html><body>spacing</body></html>');
|
|
await writeFile(path.join(root, 'legacy', 'preview', 'logo-variants.html'), '<!doctype html><html><body>logo</body></html>');
|
|
await writeFile(
|
|
path.join(root, 'legacy', 'ui_kits', 'generated_interface', 'index.html'),
|
|
'<!doctype html><html><body>legacy app kit</body></html>',
|
|
);
|
|
|
|
const files = await listUserDesignSystemFiles(root, 'user:legacy');
|
|
|
|
expect(files?.map((file) => file.path)).toEqual(
|
|
expect.arrayContaining([
|
|
'preview/colors-primary.html',
|
|
'preview/colors-theme-light.html',
|
|
'preview/colors-theme-dark.html',
|
|
'preview/typography-specimens.html',
|
|
'preview/spacing-tokens.html',
|
|
'preview/spacing-radius.html',
|
|
'preview/spacing-shadows.html',
|
|
'preview/components-buttons.html',
|
|
'preview/components-inputs.html',
|
|
'preview/brand-assets.html',
|
|
'ui_kits/app/index.html',
|
|
'ui_kits/app/README.md',
|
|
'ui_kits/app/components/App.jsx',
|
|
'ui_kits/app/components/Sidebar.jsx',
|
|
'ui_kits/app/components/AssistantsList.jsx',
|
|
'ui_kits/app/components/ChatArea.jsx',
|
|
'ui_kits/app/components/InputBar.jsx',
|
|
'ui_kits/app/components/MessageBubble.jsx',
|
|
]),
|
|
);
|
|
expect(files?.map((file) => file.path)).not.toEqual(
|
|
expect.arrayContaining([
|
|
'preview/colors-ui-palette.html',
|
|
'preview/colors-node-types.html',
|
|
'preview/typography-scale.html',
|
|
'preview/spacing-system.html',
|
|
'preview/logo-variants.html',
|
|
'ui_kits/generated_interface/index.html',
|
|
]),
|
|
);
|
|
await expect(readUserDesignSystemFile(root, 'user:legacy', 'ui_kits/app/index.html'))
|
|
.resolves
|
|
.toMatchObject({
|
|
content: expect.stringContaining('legacy app kit'),
|
|
});
|
|
await expect(readUserDesignSystemFile(root, 'user:legacy', 'README.md'))
|
|
.resolves
|
|
.toMatchObject({
|
|
content: expect.not.stringContaining('ui_kits/generated_interface'),
|
|
});
|
|
await expect(readUserDesignSystemFile(root, 'user:legacy', 'README.md'))
|
|
.resolves
|
|
.toMatchObject({
|
|
content: expect.stringContaining('ui_kits/app/index.html'),
|
|
});
|
|
await expect(readUserDesignSystemFile(root, 'user:legacy', 'SKILL.md'))
|
|
.resolves
|
|
.toMatchObject({
|
|
content: expect.not.stringContaining('preview/colors-ui-palette.html'),
|
|
});
|
|
});
|
|
|
|
it('adds modular UI-kit components to existing app kits', async () => {
|
|
await mkdir(path.join(root, 'legacy', 'ui_kits', 'app'), { recursive: true });
|
|
await writeFile(
|
|
path.join(root, 'legacy', 'DESIGN.md'),
|
|
'# Legacy System\n\n> Category: Custom\n> Surface: web\n\nLegacy body.\n',
|
|
);
|
|
await writeFile(path.join(root, 'legacy', 'README.md'), '# Legacy\n');
|
|
await writeFile(path.join(root, 'legacy', 'ui_kits', 'app', 'index.html'), '<!doctype html><html><body>app kit</body></html>');
|
|
|
|
const files = await listUserDesignSystemFiles(root, 'user:legacy');
|
|
|
|
expect(files?.map((file) => file.path)).toEqual(
|
|
expect.arrayContaining([
|
|
'ui_kits/app/components/App.jsx',
|
|
'ui_kits/app/components/Sidebar.jsx',
|
|
'ui_kits/app/components/AssistantsList.jsx',
|
|
'ui_kits/app/components/ChatArea.jsx',
|
|
'ui_kits/app/components/InputBar.jsx',
|
|
'ui_kits/app/components/MessageBubble.jsx',
|
|
]),
|
|
);
|
|
await expect(readUserDesignSystemFile(root, 'user:legacy', 'ui_kits/app/components/App.jsx'))
|
|
.resolves
|
|
.toMatchObject({
|
|
content: expect.stringContaining('<Sidebar'),
|
|
});
|
|
await expect(readUserDesignSystemFile(root, 'user:legacy', 'ui_kits/app/components/App.jsx'))
|
|
.resolves
|
|
.toMatchObject({
|
|
content: expect.stringContaining('window.App = App'),
|
|
});
|
|
});
|
|
|
|
it('does not backfill agent-managed review artifacts before the agent writes them', async () => {
|
|
const created = await createUserDesignSystem(root, {
|
|
title: 'Agent Managed',
|
|
summary: 'The agent will create review artifacts in the workspace.',
|
|
status: 'draft',
|
|
artifactMode: 'agent-managed',
|
|
});
|
|
|
|
const initialFiles = await listUserDesignSystemFiles(root, created.id);
|
|
|
|
expect(initialFiles?.map((file) => file.path)).toEqual(['DESIGN.md']);
|
|
expect(initialFiles?.map((file) => file.path)).not.toEqual(expect.arrayContaining(['README.md', 'preview/colors-primary.html']));
|
|
await expect(readUserDesignSystemFile(root, created.id, 'README.md'))
|
|
.resolves
|
|
.toBeNull();
|
|
|
|
const contextDir = path.join(root, created.id.slice('user:'.length), 'context');
|
|
await mkdir(contextDir, { recursive: true });
|
|
await writeFile(
|
|
path.join(contextDir, 'source-context.md'),
|
|
'# Source Context\n\nConnector evidence remains available as project context.\n',
|
|
'utf8',
|
|
);
|
|
|
|
const generatedFiles = await listUserDesignSystemFiles(root, created.id);
|
|
|
|
expect(generatedFiles?.map((file) => file.path)).toEqual(
|
|
expect.arrayContaining([
|
|
'DESIGN.md',
|
|
'context/source-context.md',
|
|
]),
|
|
);
|
|
expect(generatedFiles?.map((file) => file.path)).not.toEqual(expect.arrayContaining(['README.md']));
|
|
});
|
|
});
|