Files
gstack/docs/designs/IMPECCABLE_INTEROP.md
T
Garry TanandClaude Fable 5.1 da6f0ff2f6 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>
2026-09-08 17:31:30 +00:00

13 KiB

impeccable interop — gstack's design skills and the impeccable engine, catalog, and open DESIGN.md format

Status: Shipped on tehran-v1 (one PR, 13 bisected commits). Promoted from the CEO plan at ~/.gstack/projects/garrytan-gstack/ceo-plans/2026-09-08-impeccable-design-interop.md. Last updated: 2026-09-08 Authors: garrytan (with /plan-ceo-review, /plan-eng-review, and two Codex outside-voice passes)

What shipped

  • Detector pre-pass. bin/gstack-design-detect.ts probes for an impeccable engine the user installed (never installs, downloads, or runs anything that could download) and scans local files with it. /design-review (source mode on a diff, DOM mode on a URL through a rendered-DOM dump), /ship review-lite, /review's Design specialist, and /design-html's one-pass slop gate run it when IMPECCABLE_READY. Sentinel vocabulary: lib/design-detect-contract.ts. Off switch: gstack-config set design_detector off.
  • One typed catalog. lib/design-catalog.ts replaced four drifting slop lists. Every one of impeccable's 61 rule ids is mapped with gstack prose, tier, impact, confidence, and /impeccable handoff; gstack-only judgment tells have no brackets. review/design-checklist.md and lib/dom-dump.js are generated from lib/.
  • Doctrine. Persuade / Operate / Read / Experience modes, the craft-floor reflexes, the three-looks calibration, a font procedure with role-scoped overused lists, Restrained / Committed / Full palette / Drenched color strategies.
  • Open DESIGN.md format. lib/design-md.ts + bin/gstack-design-md.ts read, convert, and write the google-labs-code/design.md format; the conversion answer is persisted in the file's marker line. gstack's own DESIGN.md is converted.
  • Generation-time guard. The design binary's image prompt carries a "Never:" line built from ten catalog ids.
  • Attribution. NOTICE.md + licenses/Apache-2.0.txt.

CEO plan (promoted)

CEO Plan: impeccable.style interop for gstack's design skills

Generated by /plan-ceo-review on 2026-09-08 Branch: tehran-v1 | Mode: SELECTIVE EXPANSION Repo: garrytan/gstack

Vision

10x Check

Every AI design skill on the market is a prose vocabulary, and two vocabularies loaded together cancel out (impeccable's own docs say so). The 10x version of "incorporate impeccable" is not a fifth vocabulary. It is gstack as the design orchestrator: it runs every deterministic check the ecosystem offers (today: impeccable's 61-rule engine, when the user installed it), speaks the shared rule ids in its own voice, reads and writes the open DESIGN.md interchange format that Google Stitch and impeccable both use, and keeps the three things nobody else has: a live-site design audit through the user's real browser (Aside), image mockups with a comparison board, and a plan-stage design review before a line of UI is written.

What the user feels: /design-review opens with the machine's findings already on the table, each tagged [side-tab] or [nested-cards], then spends its judgment on hierarchy, trust, and copy. /design-consultation writes a DESIGN.md that impeccable, Stitch, and any future tool read without translation. The mockup board stops showing purple gradients because the generator prompt was told not to reach for them. And when the user has impeccable installed, every deferred finding ends with the command that fixes it.

Platonic Ideal

Not run (SELECTIVE EXPANSION mode).

Scope Decisions

# Proposal Effort Decision Reasoning
1 "Never:" line in design/src/brief.ts briefToPrompt() built from a pinned MOCKUP_NEVER_NAMES export (ten catalog ids, plain-English names, deduped) S (human ~2 h / CC ~10 min) ACCEPTED Cheapest win in the plan: slop stopped at mockup generation; prompt text only; design/test/brief.test.ts added (no brief test exists today)
2 Detector on live URLs via a rendered-DOM dump (one shared dump script for both engines; Aside writes under pwd, browse engine writes under a temp dir via $B eval --out --raw; bash copies both into $REPORT_DIR/dom/), scanned by the static engine; wrapper allow-lists ${GSTACK_HOME:-$HOME/.gstack}/projects/*/designs/ as a scan root M (human ~1.5 days / CC ~40 min) ACCEPTED Resolves the tension between Phase 0 scanning source and design-review Rule 4 ("never read source"); works on deployed sites; the wrapper root check, the dump script, and the $REPORT_DIR path fix are the real work
3 detector: {mode, engine, total, byRule} in design-baseline.json; regression mode reports ids that appeared and disappeared when mode matches, with an engine changed caveat when the engine differs S (human ~4 h / CC ~15 min) ACCEPTED The number is already computed; tracked per rule id across runs; the mode gate and the engine caveat keep source-vs-DOM and engine upgrades from reading as regressions
4 Slop rubric in $D check (GPT-4o vision) M (human ~2 days / CC ~40 min) DEFERRED Paid call per variant; vision misjudges cream palettes and nested cards; revisit once catalog LLM-tells are exercised
5 Taste-profile interplay for overused-font hits S (human ~4 h / CC ~15 min) DEFERRED .impeccable/config.json ignore-value already covers the case without coupling two schemas
6 Catalog bullets in plan-ceo-review Section 11 S DEFERRED plan-ceo-review skeleton has ~555 B of carve headroom; sequence after the doctrine-carve TODO

Implementation approach decision (D1): B, the reviewed 13-commit plan, over A (detector only, 5/10) and C (B plus generation-time enforcement, 10/10). C's free half (proposal 1) accepted as a cherry-pick; its paid half (proposal 4) deferred.

Delivery: ONE PR (user decision 2026-09-08, overriding the review's two-PR suggestion). Commits stay bisected; goldens regenerated at commits 4, 7, and 9; commits 12 and 13 are whole.

Accepted Scope (added to this plan)

1. Generation-time slop guard (commit 7b). lib/design-catalog.ts sets mockupNever: true on exactly these ten ids: kicker-above-heading, icon-tile-stack, gradient-text, ai-color-palette, cream-palette, nested-cards, dark-glow, pulsing-dot, and the gstack-only identical-cards and hero-metrics. It exports MOCKUP_NEVER_NAMES: string[] (their name fields, deduped, plain English, no ids). design/src/brief.ts briefToPrompt() pushes "Never: " + MOCKUP_NEVER_NAMES.join(", ") + "." before its fixed tail. Import path ../../lib/design-catalog (precedent: design/src/receipted-fetch.ts imports ../../lib/egress-receipt; scripts/build.sh bundles relative TS into the binary; setup already treats lib/ mtime as a rebuild trigger). lib/design-catalog.ts imports nothing from scripts/. The catalog invariant test checks the ten ids; the new design/test/brief.test.ts asserts the "Never:" line is present, precedes the fixed tail, and lists MOCKUP_NEVER_NAMES.length names. Binary rebuilt by ./setup; design/dist/ is never committed.

2. Detector on live URLs via a rendered-DOM dump (commit 7).

  • Mode rule: the target is a URL (including an explicit http://localhost:3000 on a feature branch) → DOM mode, no source scan. Diff-aware mode with no URL → source mode over the changed frontend dirs. Stated in the Phase 0 prose so nobody scans source "helpfully".
  • Hook rule (superseded before shipping, eng review decision 35): IMPECCABLE_HOOK: present never skips a scan; the prose tells the agent to use gstack's rows and ignore the hook's vocabulary. DOM mode scans the dump regardless, because the hook cannot see a rendered page.
  • One dump script for both engines, rendered once by the resolver as a fenced JS block, written as an IIFE expression with no single-quote characters (every aside repl '...' script is a single-quoted bash string, and $B eval wraps an expression): serialize the stylesheets whose ownerNode is a <link> (inline <style> nodes are already in the markup; serializing them too would double-count), catch cross-origin sheets and list them in a trailing HTML comment as unresolved, inject the text as <style data-gstack-dom-css> in <head>, return document.documentElement.outerHTML. Aside: the Phase 3 page script evaluates it, does fs.writeFile(path.join(pwd, "{page}.dom.html"), html) (the sandbox fs writes only under pwd), prints ASIDE_DIR=, and bash copies the file to $REPORT_DIR/dom/. Browse fallback: _TMP=$(mktemp -d), write the same JS to $_TMP/dom-dump.js, run $B eval "$_TMP/dom-dump.js" --out "$_TMP/{page}.dom.html" --raw (--out accepts only temp dirs or cwd, browse/src/path-security.ts:26), then cp into $REPORT_DIR/dom/. $B html is not used: reading commands wrap stdout in untrusted-content markers (browse/src/server.ts:1249-1255). The row lives in design-review's own DOM-mode prose in scripts/resolvers/design.ts, not in the shared {{BROWSE_FALLBACK}} table (that table renders into ten skills). Same script, same serialization on both engines; page content may still differ by session. {page} reuses the slug the Phase 3 screenshots already use. In DOM mode, findings' file:line point into {page}.dom.html; the agent confirms the element in the rendered page (Rule 4), never by hunting a source line.
  • $REPORT_DIR in design-review/SKILL.md.tmpl:95 changes from $HOME/.gstack/... to ${GSTACK_HOME:-$HOME/.gstack}/... (as bin/gstack-slug already does) so the allow-list and the report dir agree when GSTACK_HOME is set. The Output Structure tree at tmpl:119 and the mkdir at design.ts:419 keep their ~/.gstack shorthand; neither feeds the allow-list.
  • Sequencing: in DOM mode the scan runs once, after the last Phase 3 page script has produced its dump; in source mode the scan runs in Setup. Phase 9 recomputes (DOM mode re-dumps the affected pages after reload and rescans; source mode rescans the touched files); Phase 10 reports Detector: N → M.
  • Scan: gstack-design-detect.ts scan --format gstack "$REPORT_DIR/dom"; the wrapper's target validation accepts paths under the repo root OR under ${GSTACK_HOME:-$HOME/.gstack}/projects/*/designs/ (explicit allow-list, tested with a designs path accepted and a non-designs sibling refused). Report line once: "static scan of the rendered DOM; cross-origin CSS not resolved".
  • The load-bearing assumption (impeccable's static engine reads inline <style> in a .html file) is pinned in commit 1, not commit 13: the fixture set includes review-eval-design-slop.html served locally, dumped with $B eval dom-dump.js --out --raw, and scanned. DOM mode ships in commit 7 on top of that fixture.

3. Detector counts in the regression baseline (commit 7). The design-baseline.json template and regression prose live in scripts/resolvers/design.ts (DESIGN_METHODOLOGY Phase 6, the JSON block and "Regression Output"), not in the skill template; edit there. Field: detector: { mode: "dom"|"source", engine, total, byRule: {id: n} }. engine is the version parsed from the READY path when it is the ~/.impeccable/bin/<version>/ cache, or from a sibling VERSION file, otherwise sha256:<12 hex> of the binary (superseded before shipping: never a filesystem path); a version field in detect --json output wins if the fixture shows one. Previous baseline = newest ${GSTACK_HOME:-$HOME/.gstack}/projects/$SLUG/designs/design-audit-*/design-baseline.json older than today's $REPORT_DIR (a second run on the same day overwrites today's baseline and compares against the most recent prior day's, by design). Diff only when mode matches; otherwise say "detector modes differ (dom vs source), no delta". When engine differs, print the delta with the caveat engine changed X → Y; rule set may differ. Absent field → "no detector baseline (first scan)", never +N. Output: ids that appeared, ids that disappeared, totals, one example row in the prose (+ kicker-above-heading (2) - gradient-text (1) total 14 → 9). Known limitation stated: live DOM dumps jitter (ads, timestamps, A/B), so count deltas are advisory and id appear/disappear is the signal.

Deferred to TODOS.md

  • $D check slop rubric (P3, M): add catalog LLM-only tells to design/src/check.ts once the catalog's LLM-tell entries have been exercised in reviews; cost per variant and vision false positives are the open questions.
  • Taste-profile interplay (P3, S): downgrade a detector overused-font hit to polish when the value is in the user's approved taste profile; today impeccable hooks ignore-value covers it.
  • plan-ceo-review Section 11 catalog bullets (P3, S): blocked on the plan-ceo-review doctrine carve (TODOS.md line ~1100).

Reviewer Concerns

Three adversarial review rounds (scores 6 → 8 → 7 out of 10; 26 issues raised, 26 fixed). The nine fixes from round 3 were applied after the last round and are therefore unreviewed: temp-dir --out then cp for the browse fallback; hook-skip scoped to source mode; <link>-only stylesheet inlining; engine changed caveat in the diff rule; $B eval row kept out of the shared fallback table; the IIFE/no-single-quote constraint on the shared script; DOM-dump fixture captured in commit 1; Phase 9 computes and Phase 10 reports; wording (session-dependent dumps, prior-day baseline, {page} placeholder, brief test asserts MOCKUP_NEVER_NAMES.length). Residual risk the reviewers named and this plan accepts: design-review's eager budget will need its fixture refreshed in commit 7 (expected, not conditional).