mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-11 15:39:04 +02:00
fix(design): Aside dump script stays single-quoted; redaction gate sized to the dump cap; doctrine made consistent
- The DOM-dump Aside block was the only double-quoted `aside repl` script in
the tree (to splice the function text), which put the agent-filled <url>
inside a double-quoted bash string: a same-origin href carrying $(...) would
run in the reviewer's shell when Phase 3 opened that page. The script is
single-quoted like every other Aside script and the function text enters
through a closed-quote segment ('"$_DUMP"'); the fallback line is
`$B js '('"$_DUMP"')()'`. A free test pins that no rendered Aside script
opens with a double quote.
- The persist block capped dumps at 10 MiB but ran gstack-redact with its
1 MiB default, so every real page between the two was deleted as
DOM_DUMP_REDACTION_BLOCKED; the gate passes --max-bytes at the dump cap and
blocks on any exit other than clean (0) or MEDIUM (2), so a redaction tool
that fails to run can no longer fall through to "persist".
- Dump hygiene removes <template> and <noscript> subtrees (invisible to the
attribute walk), inline on* handlers, and the cross-origin <link> nodes
already named in the note, so the file handed to the engine references no
remote stylesheet.
- Doctrine: the Codex design-voice prompts said "2-3 intentional motions"
against the one-authored-moment rule; the overused-display heading scoped
its ban to Persuade/Experience while the catalog and hard rules ban it
everywhere; design-consultation's Important Rule 4 still said "as primary";
design-html's blacklist header is now "Never include by default" with the
mockup/DESIGN.md/user-ask override the catalog grants; the slop gate honors
Decisions Log and Do's and Don'ts blessings like /review does; the landing
"poster" line says poster in stance, not type size; the design binary's
variant dials no longer flip light/dark for variety; gstack's DESIGN.md
rows name data labels (UI labels stay the DM Sans token) and call the
skill-bar fill and hovers functional transitions.
- design-review names how the base branch is found (gh pr view, then the
repo default; never main) for the source-mode scan and the diff-aware mode.
- frontend-scope matches the config globs at the repo root only, like the
bash arm; the parity test carries nested samples.
- Cleanups: renderCatalog's stale style option, an unused import, the
identity-map bannedFontNames, the checklist header's "same entries" claim,
the catalog header's consumer list, the orphaned main() docstring, the
plan doc's IIFE bullet. design-html's skeleton ceiling is re-measured
(54,184) for the two doctrine sentences.
Tests: AUTO-FIX rendering from the catalog, the E2E slice markers checked in
the free suite, the hygiene cases for templates/noscript/handlers/remote
links, and the review E2E counting detector rows separately from the seven
checklist plants.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
ae5a5298e0
commit
982a738663
@@ -49,12 +49,12 @@ Delivery: ONE PR (user decision 2026-09-08, overriding the review's two-PR sugge
|
||||
|
||||
## 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.
|
||||
**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(", ") + "."` (shipped as `Never by default (unless the brief above asks for it): …`) 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.
|
||||
- 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 (superseded before shipping: the script is an arrow function called in the page, spliced into the single-quoted Aside script and into `$B js '('"$_DUMP"')()'` through a closed-quote segment, never a double-quoted block; see lib/dom-dump-script.ts) (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".
|
||||
|
||||
Reference in New Issue
Block a user