feat(design): catalog never-lines in the mockup prompt

Ten catalog ids carry `mockupNever` (kicker-above-heading, icon-tile-stack,
gradient-text, ai-color-palette, cream-palette, nested-cards, dark-glow,
pulsing-dot, identical-cards, hero-metrics) and lib/design-catalog.ts exports
their deduped plain-English names as MOCKUP_NEVER_NAMES. briefToPrompt() in
the design binary appends "Never: <names>." before its fixed tail, so `$D
generate | variants | evolve` stop reaching for purple gradients, icon tiles,
and cream defaults before the comparison board opens. The binary still
bundles (`bun build --compile design/src/cli.ts`); ./setup rebuilds it.

design-html's Never-include list now covers every mockupNever id (kicker /
icon tile, hero metric rows, gradient text, cream palette, nested and
identical cards, glow and pulsing dots), each line tagged with its catalog
ids; test/design-catalog.test.ts pins the exact ten flags, the deduped names,
and that the template list is a superset. New design/test/brief.test.ts pins
the prompt shape.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-09-08 16:15:57 +00:00
co-authored by Claude Fable 5.1
parent 3522073ef0
commit d3c3ea27b9
6 changed files with 125 additions and 13 deletions
+40 -11
View File
@@ -12,7 +12,7 @@ import * as fs from 'fs';
import * as path from 'path';
import { spawnSync } from 'child_process';
import {
DESIGN_SLOP_CATALOG, HANDOFF_COMMANDS, OVERUSED_FONTS_DISPLAY, BANNED_FONTS,
DESIGN_SLOP_CATALOG, HANDOFF_COMMANDS, OVERUSED_FONTS_DISPLAY, BANNED_FONTS, MOCKUP_NEVER_NAMES,
FONTS_BODY_UI_OK, FONTS_MONO_OK, FONTS_VERIFIED_FREE,
catalogEntry, entryForImpeccableId, renderCatalog, selectCatalog,
} from '../lib/design-catalog';
@@ -178,18 +178,47 @@ describe('renderCatalog', () => {
});
});
const MOCKUP_NEVER_IDS = ['kicker-above-heading', 'icon-tile-stack', 'gradient-text', 'ai-color-palette', 'cream-palette', 'nested-cards', 'dark-glow', 'pulsing-dot', 'identical-cards', 'hero-metrics'];
function designHtmlNeverIds(): string[] {
const tmpl = fs.readFileSync(path.join(ROOT, 'design-html', 'SKILL.md.tmpl'), 'utf-8');
const start = tmpl.indexOf('**Never include (AI slop blacklist):**');
expect(start).toBeGreaterThan(0);
const block = tmpl.slice(start, tmpl.indexOf('\n\n', start + 10));
const lines = block.split('\n').filter(l => l.startsWith('- '));
expect(lines.length).toBeGreaterThanOrEqual(10);
const ids: string[] = [];
for (const line of lines) {
const found = [...line.matchAll(/<!-- ([a-z0-9-]+) -->/g)].map(m => m[1]);
expect(found.length, line).toBeGreaterThan(0);
ids.push(...found);
}
return ids;
}
describe('design-html blacklist is derived-by-test (decision 31)', () => {
test('every <!-- id --> on the Never-include list names a catalog entry', () => {
const tmpl = fs.readFileSync(path.join(ROOT, 'design-html', 'SKILL.md.tmpl'), 'utf-8');
const start = tmpl.indexOf('**Never include (AI slop blacklist):**');
expect(start).toBeGreaterThan(0);
const block = tmpl.slice(start, tmpl.indexOf('\n\n', start + 10));
const lines = block.split('\n').filter(l => l.startsWith('- '));
expect(lines.length).toBeGreaterThanOrEqual(10);
for (const line of lines) {
const m = line.match(/<!-- ([a-z0-9-]+) -->$/);
expect(m, line).not.toBeNull();
expect(catalogEntry(m![1]), m![1]).toBeDefined();
for (const id of designHtmlNeverIds()) expect(catalogEntry(id), id).toBeDefined();
});
test('every mockupNever entry appears on the Never-include list', () => {
const ids = new Set(designHtmlNeverIds());
for (const id of MOCKUP_NEVER_IDS) expect(ids.has(id), id).toBe(true);
});
});
describe('mockupNever → MOCKUP_NEVER_NAMES (generation-time slop guard)', () => {
test('exactly the ten agreed ids carry the flag', () => {
const flagged = DESIGN_SLOP_CATALOG.filter(e => e.mockupNever).map(e => e.id).sort();
expect(flagged).toEqual([...MOCKUP_NEVER_IDS].sort());
});
test('names are deduped plain English with no hyphenated ids', () => {
expect(new Set(MOCKUP_NEVER_NAMES).size).toBe(MOCKUP_NEVER_NAMES.length);
expect(MOCKUP_NEVER_NAMES.length).toBe(10);
for (const n of MOCKUP_NEVER_NAMES) {
expect(n).not.toMatch(/^[a-z0-9]+(-[a-z0-9]+)+$/);
expect(n[0]).toMatch(/[A-Z"0-9]/);
}
});
});