mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-16 09:55:29 +02:00
fix(design): run the DOM dump in the page on both engines; align doctrine with the catalog
The DOM-dump script is an arrow function, not a self-calling IIFE: Aside's `pg.evaluate($_DUMP)` receives the function and runs it in the page (the IIFE form executed in the repl sandbox, where `document` does not exist), and the fallback engine calls it with `$B js "($_DUMP)()" --out --raw`. Hygiene widens to every URL-bearing attribute (src, srcset per candidate, poster, action, formaction, data, ping, cite lose their query strings and fragments) and to data: URLs inside existing <style> nodes. The persist and scan blocks restate REPORT_DIR and RUN_ID literally instead of relying on a shell variable from an earlier block; the baseline's targetSet is defined per mode (repo-relative paths in source mode, page slugs in DOM mode) so DOM-mode deltas can match; the PR-body Detector line lists the states the probe can actually print. The DOM fixture is re-captured with the new script from outside the repo (the engine walks up from cwd for DESIGN.md, which the metadata now records). Doctrine contradictions the design specialist found: the landing-page motion rule matches the one-authored-moment reflex; the background rule names the catalog's halo/spotlight/stripe/grid slop instead of asking for gradients; the universal font rule is scoped to the display voice with the body/UI exceptions; "two typefaces max" allows the mono; the methodology's banned-font line renders BANNED_FONTS; Courier New is banned outright; the Brutalist, Retro-Futuristic, and Playful menu entries stop recommending system stacks, glow, and bounce; the coherence nudge uses the decoration vocabulary; Path A's gate names the display voice; font-loading prose points at the source the procedure verified; centered-everything is MEDIUM (an aggregate heuristic); the mockup guard reads "Never by default (unless the brief above asks for it)". The checklist's AUTO-FIX list renders the catalog's auto-fix rules; category 9 and the Hard Rules pointer count from the same partition helpers (detectorSlopEntries, judgmentTellEntries); the handoff list renders from HANDOFF_COMMANDS; a missing catalog id fails gen-skill-docs by name. gstack's own DESIGN.md gains border tokens and Decisions Log rows for its live-feed pulse and 11px mono labels. frontend-scope is case-sensitive like the bash arm. gen-skill-docs shares one emitGenerated helper for sections and lib-derived assets; renderCatalog keeps the one style with a caller. Tests: shared sliceBetween that fails on a missing end marker, the slop-gate fixture's real end marker, an isolated browse daemon for the DOM-mode E2E, the DOM hygiene test gated to CI or opt-in, docs notes for the two superseded plan sentences. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
b4d88a0126
commit
da6f0ff2f6
+19
-25
@@ -14,7 +14,7 @@ import { spawnSync } from 'child_process';
|
||||
import {
|
||||
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,
|
||||
catalogEntry, catalogEntries, entryForImpeccableId, renderCatalog, selectCatalog, detectorSlopEntries, judgmentTellEntries,
|
||||
} from '../lib/design-catalog';
|
||||
import { AI_SLOP_BLACKLIST } from '../scripts/resolvers/constants';
|
||||
|
||||
@@ -138,44 +138,38 @@ describe('fonts', () => {
|
||||
});
|
||||
|
||||
test('banned fonts and overused fonts do not overlap; mono list is mono', () => {
|
||||
for (const f of BANNED_FONTS) expect(OVERUSED_FONTS_DISPLAY).not.toContain(f);
|
||||
for (const f of BANNED_FONTS) { expect(OVERUSED_FONTS_DISPLAY).not.toContain(f); expect(f).not.toMatch(/\(/); } // no role qualifiers: banned means every role
|
||||
for (const f of FONTS_MONO_OK) expect(f).toMatch(/Mono|Code/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('renderCatalog', () => {
|
||||
test('ids style brackets only detector-known ids', () => {
|
||||
const out = renderCatalog({ kind: 'slop', style: 'ids' });
|
||||
expect(out).toContain('- [nested-cards] ');
|
||||
expect(out).toContain('- [side-tab] ');
|
||||
for (const e of DESIGN_SLOP_CATALOG.filter(x => !x.impeccableId)) {
|
||||
expect(out).not.toContain(`[${e.id}]`);
|
||||
}
|
||||
// gstack-only prose still renders, unbracketed
|
||||
expect(out).toContain('- ' + catalogEntry('hero-metrics')!.prose);
|
||||
});
|
||||
|
||||
describe('renderCatalog + partitions', () => {
|
||||
test('bullets style renders prose only, no ids anywhere', () => {
|
||||
const out = renderCatalog({ kind: 'slop', style: 'bullets' });
|
||||
const out = renderCatalog({ kind: 'slop' });
|
||||
expect(out).not.toMatch(/^- \[/m);
|
||||
expect(out.split('\n').length).toBe(selectCatalog({ kind: 'slop' }).length);
|
||||
for (const line of AI_SLOP_BLACKLIST) expect(out).toContain(`- ${line}`);
|
||||
});
|
||||
|
||||
test('compact style is one line of id: name pairs', () => {
|
||||
const out = renderCatalog({ kind: 'quality', style: 'compact' });
|
||||
expect(out.includes('\n')).toBe(false);
|
||||
expect(out).toContain('low-contrast: Low contrast text');
|
||||
expect(out.split('; ').length).toBe(selectCatalog({ kind: 'quality' }).length);
|
||||
});
|
||||
|
||||
test('filters compose: category and omitImpact', () => {
|
||||
const copy = selectCatalog({ kind: 'slop', category: 'copy' });
|
||||
expect(copy.every(e => e.category === 'copy')).toBe(true);
|
||||
test('omitImpact filters', () => {
|
||||
const noPolish = selectCatalog({ kind: 'slop', omitImpact: ['polish'] });
|
||||
expect(noPolish.some(e => e.impact === 'polish')).toBe(false);
|
||||
expect(noPolish.length).toBeLessThan(selectCatalog({ kind: 'slop' }).length);
|
||||
});
|
||||
|
||||
test('detector-known slop and judgment tells partition the non-legacy slop entries', () => {
|
||||
const detector = detectorSlopEntries();
|
||||
const tells = judgmentTellEntries();
|
||||
expect(detector.every(e => e.impeccableId && !e.legacyBlacklist && e.kind === 'slop')).toBe(true);
|
||||
expect(tells.every(e => !e.impeccableId && !e.legacyBlacklist && e.kind === 'slop')).toBe(true);
|
||||
expect(detector.length + tells.length + 11).toBe(selectCatalog({ kind: 'slop' }).length);
|
||||
expect(detectorSlopEntries({ omitPolish: true }).every(e => e.impact !== 'polish')).toBe(true);
|
||||
});
|
||||
|
||||
test('catalogEntries throws with the missing id', () => {
|
||||
expect(() => catalogEntries(['nested-cards', 'no-such-id'])).toThrow('no-such-id');
|
||||
expect(catalogEntries(['nested-cards'])[0].name).toBe('Nested cards');
|
||||
});
|
||||
});
|
||||
|
||||
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'];
|
||||
|
||||
Reference in New Issue
Block a user