mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-15 17:35:29 +02:00
v1.81.0.0 feat: Aside is the browser gstack drives first; every browsing skill, the PDF/diagram renderer, and web research; the bundled browser stays the automatic fallback (#2810)
* feat(aside): browser-driver contract, cookbook, research and fallback resolvers
{{ASIDE_SETUP}} (readiness probe + ten rules for driving the user's real browser), {{ASIDE_COOKBOOK}} (script shapes verified live against Aside CLI 1.26: one flow per aside repl script, CDP console hook before navigation, evidence lines, session-directory artifact handoff, GSTACK_STEP_OK sentinel), {{ASIDE_RESEARCH}} (research through aside exec, WebSearch when Aside is absent, knowledge otherwise) and {{BROWSE_FALLBACK}} (the fifteen-row Aside-step to $B-command table plus the rules that differ, so every browsing skill keeps working on gstack's own headless browser). test/aside-driver.test.ts pins the sentences and asserts every browsing skill carries the Aside block followed by the fallback; test/helpers/aside-available.ts is the shared live-Aside probe.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* feat(render): Aside-first local-HTML renderer with the bundled browser as fallback
lib/aside-render.ts serves the HTML's directory on loopback (Aside refuses file:// URLs), opens it with waitUntil load, prints through CDP Page.printToPDF so tagged output, outlines, header/footer templates and page numbers survive, emulates device metrics for sized screenshots, and writes in-page evaluations to files; when Aside is absent it runs the same spec through the browse daemon (newtab, load, js, pdf, screenshot, closetab) and reports ENGINE=aside|browse. bin/gstack-render.ts is the CLI skill templates call. lib/claude-bin.ts and lib/error-handling.ts become the canonical copies (browse/src re-exports them).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* refactor(browse): /browse drives Aside first, with the $B reference behind the fallback
Contract, cookbook, mode choice (aside repl by default, aside exec for reading), report format, the fallback section, and the full command reference carved on demand.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* refactor(qa): /qa and /qa-only drive Aside, fall back to $B
QA_METHODOLOGY runs every phase as Aside scripts (orient, explore, document, re-test, mobile viewport via CDP emulation, links via HEAD fetch); the authenticate phase is 'you are already signed in'; a 13th rule requires consent before mutating actions on non-local targets; the fallback section translates each step onto $B. The qa E2E tests run on whichever engine is present.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* refactor(design): design-review, design-consultation, design-shotgun, plan-design-review, design-html drive Aside
Design-system extraction is one script printing FONTS/COLORS/HEADINGS/TOUCH_TARGETS/NAV; competitor research confirms the exact URLs before opening them in the real browser and runs on the bundled browser when Aside is absent; design-html's viewport screenshots, sketches and comparison boards render through gstack-render.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* refactor(deploy): benchmark, canary, land-and-deploy Step 7, devex-review drive Aside
One aside repl script per page prints NAV/PAINT/LCP/RESOURCES/SCRIPTS/CSS/SUMMARY (benchmark), CONSOLE_ERRORS/NAV/TEXT + screenshot (canary, re-run every 60s), and the post-deploy check reads responseStatus from the navigation entry; each carries the $B fallback.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* feat(third-party-actions): Aside is the recommended driver; gstack's visible browser stays the fallback
The readiness probe is lifted from {{ASIDE_SETUP}} at gen time (byte-identity pinned) and rule 3 points at browse/SKILL.md for how to drive; the consent question offers Aside first and gstack's own visible browser (handoff/resume for sign-in) as the fallback, as v1.72 framed it.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* refactor(scrape): /scrape reads pages through Aside; the browser-skills runtime rides the fallback
Look-then-extract scripts build the JSON inside the page and print it between JSON_START/JSON_END; aside exec for fuzzy intents; on the $B fallback the browser-skills match/prototype flow and /skillify apply as before.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* refactor(make-pdf): print through Aside first, the bundled browser otherwise
asideClient.ts replaces the direct $B client with one render() call per PDF (the exact option mapping the browse pdf command had: paper, margins, header/footer/page numbers, tagged, outline, printBackground, preferCSSPageSize, Paged.js wait); the diagram pre-pass, oversized-image downscale and DOCX rasters each run as one render script with per-fence try/catch; exit 4 now means no browser is available and names both remedies; $P setup reports which engine it found. The e2e gates run on whichever engine is present, so the Linux lane exercises the fallback.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* refactor(diagram): the triplet is one gstack-render call
SVG, PNG and excalidraw from one invocation over the content-addressed bundle staged under /tmp/gstack-render; every diagram type gets an excalidraw export; gstack-render picks the engine and prints ENGINE=; the diagram E2E gates on either engine.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* feat(research): web research runs in Aside first, WebSearch second
The planning, review, design, security and investigate skills research through {{ASIDE_RESEARCH}}; WebSearch stays in allowed-tools as the fallback; testing.ts's bootstrap step follows; skeleton ceilings ratcheted for the research block.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* feat(setup,gen-skill-docs): prune renders of skills that no longer exist
setup gains _prune_stale_generated for every host tree and the doc generator removes gstack-* output dirs it did not write, so a skill removed from the source tree can never linger in an install.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* test: registries, budgets and suite reconciled for Aside-first with the $B fallback
Touchfiles + E2E tiers gain the Aside keys, coverage matrix and eval baselines updated, size budget re-baselined to parity-baseline-v1.80.0.0.json (the contract plus fallback ride in every browsing skill), parity ceilings ratcheted with measured values, LLM-judge prompts and the E2E fixtures speak Aside-first, browse-fallback.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* docs: Aside first, gstack browser fallback
README, BROWSER.md, docs/, CONTRIBUTING, CLAUDE.md, ARCHITECTURE, AGENTS.md, TODOS and the root router describe the one product story: Aside is the browser gstack drives first; the bundled headless browser is the automatic fallback (Linux, Windows, app closed) where cookie import, GStack Browser, pair-agent and browser-skills still apply.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* chore: regenerate SKILL.md docs, llms.txt, agents digest, ship goldens, context-budget fixture
bun run gen:skill-docs over the templates; goldens re-rendered; context-budget ceilings recaptured.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* v1.80.0.0: Aside is the browser gstack drives first; the bundled browser is the fallback
MINOR: new capability across ten skills, the renderer and research; nothing removed. CHANGELOG release summary + itemized changes; VERSION 1.80.0.0; package.json 1.80.0.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* docs(todos): file non-Claude host ownership-gate and version-heading pin follow-ups
Two follow-ups from the /plan-ceo-review + /plan-eng-review pass on merging
PR #2804 with main's v1.80.0.0 ownership gate: bring the Codex/Factory/
OpenCode/Cursor/Kiro copy loops and the stale-render prune under the
.gstack-owned marker rule, and a free test pinning that the CHANGELOG top
heading equals VERSION (the collision that git cannot see).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* fix: pre-landing review fixes for the Aside-first branch
Review army + adversarial passes (Claude and Codex) on the merged branch:
setup
- _prune_stale_generated scans the host dirs too (the generator already
removed the render before setup ran, so the host branch was dead), skips
symlinks in the render tree (rm -rf on a slash-terminated link empties its
target), removes a host symlink only when it resolves into gstack, cleans a
bannered real dir through _cleanup_weak_dir, recognizes frontmatter-renamed
skills, and logs through log. The always-run codex render passes every host
dir that may link to it.
- NEEDS_BUILD checks all three binaries (with $_EXE) and lib/ sources; the
browser hint and the bootstrap summary honor GSTACK_SKIP_ASIDE, treat a
requested skip as a request, and derive one skill list.
lib/aside-render.ts + bin/gstack-render.ts
- The loopback server carries a per-render secret path, checks containment on
the real path (symlink escapes are 403), and rejects malformed encoding.
- Inline eval results are one base64 line, so page text cannot forge
ASIDE_DIR= or the sentinel; the last ASIDE_DIR wins.
- runProc escalates SIGTERM to SIGKILL, bounds every wait, and clears every
timer (an uncleared one kept gstack-render alive after printing OK).
- renderTmpDir refuses a shared /tmp name owned by someone else; the work dir
and server are created inside try; goto's budget follows the render budget.
- probeAside classifies a present-but-failing CLI as ASIDE_NOT_RUNNING like
the skills' bash probe; render() retries on gstack's own browser when Aside
could not start or its private CDP bridge is gone (never on a page error
or a timeout of a running script); the CLI reports the engine that actually
rendered, exits 0 on --help, rejects non-numeric flags, documents
--wait-timeout, fences EVAL/PAGE_ERRORS as untrusted content, and names the
daemon's cookie-import JS lock remedy.
- The browse path passes --scale only when asked (a scale change rebuilds
the daemon context) and restores the viewport after a sized screenshot.
resolvers / templates
- The bash probe honors GSTACK_SKIP_ASIDE and has a perl deadline on stock
macOS; .local is no longer LOCAL (mDNS); same-origin filters compare parsed
origins; link status is HEAD-checked only on LOCAL targets; every
aside exec goes through the receipted _aside_exec prelude
({{ASIDE_EXEC_PRELUDE}}), including nine template blocks that called it
bare; the design sketch and diagram staging use private directories.
- The generator prunes only bannered renders and never a host whose
generation failed.
Docs, stale comments and dead code cleaned; goldens re-rendered; tests
updated and added for every behavior above.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* test: coverage for the render CLI, setup rebuild check, make-pdf exit codes, and prose $B spans
New free tests from the ship coverage audit: test/gstack-render-cli.test.ts
(argv guards, --help, output contract with a fake daemon, failure and
serve-root paths, no-browser case, prompt exit), test/setup-needs-build.test.ts
(every binary and source set flips NEEDS_BUILD, Windows suffixes),
make-pdf/test/cli-exit-codes.test.ts and setup-smoke.test.ts (error to exit
code mapping, runSetup stages, renderPdf's engine), and prose-span cases for
extractBrowseCommands in test/skill-parser.test.ts.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* docs: CHANGELOG and TODOS cover the review fixes (v1.81.0.0)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* docs: sync project docs with the v1.81.0.0 review fixes
BROWSER.md, ARCHITECTURE.md, CONTRIBUTING.md, README.md, CLAUDE.md,
docs/TESTING_INTERNALS.md and docs/PROJECT_STRUCTURE.md now describe the
shipped renderer and setup: the loopback render server's per-render secret
path and real-path containment, ENGINE= naming the engine that actually
rendered (mid-run retry on gstack's own browser), EVAL/PAGE_ERRORS fenced as
untrusted content, --wait-timeout and the CLI's argv guards, the receipted
_aside_exec prelude ({{ASIDE_EXEC_PRELUDE}} in the placeholder table), the
LOCAL host rule without .local, LOCAL-only HEAD checks in the links script,
GSTACK_SKIP_ASIDE across probe/renderer/setup, the ownership-gated
retired-skill prune, the widened NEEDS_BUILD check, and the new free tests
(gstack-render-cli, setup-prune-stale-generated, setup-browser-hint,
setup-needs-build, make-pdf cli-exit-codes and setup-smoke).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* docs: CHANGELOG states the precise mid-run retry rule
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* fix(test): skill-e2e-bws slices the $B setup block from the Browser fallback section
browse/SKILL.md no longer has '## SETUP' / '## Core QA Patterns' (Aside is the
primary driver; the $B block moved under 'Browser fallback'), so the gate test
sliced an empty block and handed the agent nothing to run. Anchor on
'### Find the `$B` binary' up to the next heading. 7/7 pass.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* fix(test): gate POSIX-only fixtures off Windows
windows-free-tests: the gstack-render CLI tests drive a shebang fake browse
that CreateProcess cannot exec, and two NEEDS_BUILD cases assert an execute
bit and a bare-name miss that MSYS bash does not have (test -x ignores mode
bits and resolves design -> design.exe). Those describes and cases now
self-skip on win32; argument guards, --help, the no-browser case, and every
other rebuild-check case still run there.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* fix(render): runProc waits for the exit code until the kill deadline; newtab retries once on a cold daemon
A process whose pipes have reached EOF is exiting, but runProc gave the exit
code only five seconds to arrive and then returned null, which run() reports
as a failed command. Under CI's six-shard load one such render failed with the
artifact already written. The SIGTERM/SIGKILL timers already bound the wait,
so the exit race now runs to the kill deadline.
The first CLI call auto-starts the browse daemon; on a cold start it can
answer 'Unable to connect' once while the server is still coming up. That
single case is retried after 1.5s; every other newtab failure is not.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* test(aside-render): warm the daemon before live fallback cases; failures name the render error
- Live fallback cases run 'goto about:blank' up to twice before asserting and
skip (never fail) when the daemon cannot come up.
- expectOk() puts r.error and the browse transcript into the assertion so a
failed render is diagnosable from the CI log.
- The argv-contract cases dump the fake's log on a miss.
- File default timeout is 30s: the subject is the CLI contract, not latency.
- Two cases pin the cold-daemon newtab retry and that other errors are not
retried.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* docs: CHANGELOG notes the cold-start tolerance of the bundled-browser renderer
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
---------
Co-authored-by: Sina <sdroid674+github@gmail.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
Sina
parent
c241216637
commit
0530392821
+106
-54
@@ -94,8 +94,20 @@ Run full audit, then load previous \`design-baseline.json\`. Compare: per-catego
|
||||
|
||||
The most uniquely designer-like output. Form a gut reaction before analyzing anything.
|
||||
|
||||
1. Navigate to the target URL
|
||||
2. Take a full-page desktop screenshot: \`$B screenshot "$REPORT_DIR/screenshots/first-impression.png"\`
|
||||
1. Open the target URL in Aside and take a full-page desktop screenshot, in one script:
|
||||
|
||||
\`\`\`bash
|
||||
aside repl '
|
||||
const pg = await openTab("<url>");
|
||||
await pg.screenshot({ path: "first-impression.jpg", type: "jpeg", quality: 60, fullPage: true });
|
||||
console.log("URL=" + pg.url());
|
||||
console.log("ASIDE_DIR=" + pwd);
|
||||
await closeTab(pg);
|
||||
console.log("GSTACK_STEP_OK");
|
||||
'
|
||||
\`\`\`
|
||||
|
||||
2. \`cp "<ASIDE_DIR>/first-impression.jpg" "$REPORT_DIR/screenshots/"\` and Read it. Check the \`URL=\` line against Auth Detection (Phase 3) before you critique a login wall by mistake.
|
||||
3. Write the **First Impression** using this structured critique format:
|
||||
- "The site communicates **[what]**." (what it says at a glance — competence? playfulness? confusion?)
|
||||
- "I notice **[observation]**." (what stands out, positive or negative — be specific)
|
||||
@@ -114,21 +126,19 @@ This is the section users read first. Be opinionated. A designer doesn't hedge
|
||||
|
||||
Extract the actual design system the site uses (not what a DESIGN.md says, but what's rendered):
|
||||
|
||||
One Aside script; every probe runs inside the page and returns a JSON string (element scans capped at 500 to stay inside the script budget):
|
||||
|
||||
\`\`\`bash
|
||||
# Fonts in use (capped at 500 elements to avoid timeout)
|
||||
$B js "JSON.stringify([...new Set([...document.querySelectorAll('*')].slice(0,500).map(e => getComputedStyle(e).fontFamily))])"
|
||||
|
||||
# Color palette in use
|
||||
$B js "JSON.stringify([...new Set([...document.querySelectorAll('*')].slice(0,500).flatMap(e => [getComputedStyle(e).color, getComputedStyle(e).backgroundColor]).filter(c => c !== 'rgba(0, 0, 0, 0)'))])"
|
||||
|
||||
# Heading hierarchy
|
||||
$B js "JSON.stringify([...document.querySelectorAll('h1,h2,h3,h4,h5,h6')].map(h => ({tag:h.tagName, text:h.textContent.trim().slice(0,50), size:getComputedStyle(h).fontSize, weight:getComputedStyle(h).fontWeight})))"
|
||||
|
||||
# Touch target audit (find undersized interactive elements)
|
||||
$B js "JSON.stringify([...document.querySelectorAll('a,button,input,[role=button]')].filter(e => {const r=e.getBoundingClientRect(); return r.width>0 && (r.width<44||r.height<44)}).map(e => ({tag:e.tagName, text:(e.textContent||'').trim().slice(0,30), w:Math.round(e.getBoundingClientRect().width), h:Math.round(e.getBoundingClientRect().height)})).slice(0,20))"
|
||||
|
||||
# Performance baseline
|
||||
$B perf
|
||||
aside repl '
|
||||
const pg = await openTab("<url>");
|
||||
console.log("FONTS=" + await pg.evaluate(() => JSON.stringify([...new Set([...document.querySelectorAll("*")].slice(0, 500).map(e => getComputedStyle(e).fontFamily))])));
|
||||
console.log("COLORS=" + await pg.evaluate(() => JSON.stringify([...new Set([...document.querySelectorAll("*")].slice(0, 500).flatMap(e => [getComputedStyle(e).color, getComputedStyle(e).backgroundColor]).filter(c => c !== "rgba(0, 0, 0, 0)"))])));
|
||||
console.log("HEADINGS=" + await pg.evaluate(() => JSON.stringify([...document.querySelectorAll("h1,h2,h3,h4,h5,h6")].map(h => ({ tag: h.tagName, text: h.textContent.trim().slice(0, 50), size: getComputedStyle(h).fontSize, weight: getComputedStyle(h).fontWeight })))));
|
||||
console.log("TOUCH_TARGETS=" + await pg.evaluate(() => JSON.stringify([...document.querySelectorAll("a,button,input,[role=button]")].filter(e => { const r = e.getBoundingClientRect(); return r.width > 0 && (r.width < 44 || r.height < 44); }).map(e => ({ tag: e.tagName, text: (e.textContent || "").trim().slice(0, 30), w: Math.round(e.getBoundingClientRect().width), h: Math.round(e.getBoundingClientRect().height) })).slice(0, 20))));
|
||||
console.log("NAV=" + await pg.evaluate(() => JSON.stringify(performance.getEntriesByType("navigation")[0]))); // stringify IN the page: PerformanceEntry fields are getters and serialize to {} across the bridge
|
||||
await closeTab(pg);
|
||||
console.log("GSTACK_STEP_OK");
|
||||
'
|
||||
\`\`\`
|
||||
|
||||
Structure findings as an **Inferred Design System**:
|
||||
@@ -143,23 +153,47 @@ After extraction, offer: *"Want me to save this as your DESIGN.md? I can lock in
|
||||
|
||||
## Phase 3: Page-by-Page Visual Audit
|
||||
|
||||
For each page in scope:
|
||||
For each page in scope, two Aside scripts. First the read: console hook, interactive snapshot, annotated screenshot, load-time errors, navigation timing:
|
||||
|
||||
\`\`\`bash
|
||||
$B goto <url>
|
||||
$B snapshot -i -a -o "$REPORT_DIR/screenshots/{page}-annotated.png"
|
||||
$B responsive "$REPORT_DIR/screenshots/{page}"
|
||||
$B console --errors
|
||||
$B perf
|
||||
aside repl '
|
||||
const HOOK = \`(() => { window.__gstackErrs = window.__gstackErrs || []; const oe = console.error; console.error = (...a) => { window.__gstackErrs.push(a.map(String).join(" ")); oe.apply(console, a); }; window.addEventListener("error", e => window.__gstackErrs.push("uncaught: " + e.message)); window.addEventListener("unhandledrejection", e => window.__gstackErrs.push("unhandledrejection: " + (e.reason && e.reason.message || e.reason))); })()\`;
|
||||
const pg = await openTab("about:blank");
|
||||
await pg._sendToTarget("Page.addScriptToEvaluateOnNewDocument", { source: HOOK });
|
||||
await pg.goto("<url>");
|
||||
const s = await snapshot(pg, { interactive: true });
|
||||
console.log(s.tree);
|
||||
const a = await annotatedScreenshot(pg);
|
||||
await fs.writeFile(path.join(pwd, "{page}-annotated.png"), Buffer.from(a.base64Image, "base64"));
|
||||
console.log("URL=" + pg.url());
|
||||
console.log("CONSOLE_ERRORS=" + JSON.stringify(await pg.evaluate(() => window.__gstackErrs)));
|
||||
console.log("NAV=" + await pg.evaluate(() => JSON.stringify(performance.getEntriesByType("navigation")[0])));
|
||||
console.log("ASIDE_DIR=" + pwd);
|
||||
await closeTab(pg);
|
||||
console.log("GSTACK_STEP_OK");
|
||||
'
|
||||
\`\`\`
|
||||
|
||||
Then the responsive captures (mobile 375, tablet 768, desktop 1440):
|
||||
|
||||
\`\`\`bash
|
||||
aside repl '
|
||||
const pg = await openTab("<url>");
|
||||
for (const [name, width, height] of [["mobile", 375, 812], ["tablet", 768, 1024], ["desktop", 1440, 900]]) {
|
||||
await pg._sendToTarget("Emulation.setDeviceMetricsOverride", { width, height, deviceScaleFactor: 2, mobile: width < 1024 });
|
||||
await sleep(300);
|
||||
await pg.screenshot({ path: \`{page}-\${name}.jpg\`, type: "jpeg", quality: 60, fullPage: true });
|
||||
}
|
||||
await pg._sendToTarget("Emulation.clearDeviceMetricsOverride", {});
|
||||
console.log("ASIDE_DIR=" + pwd); await closeTab(pg); console.log("GSTACK_STEP_OK");
|
||||
'
|
||||
\`\`\`
|
||||
|
||||
After each script, \`cp\` its files out of the \`ASIDE_DIR\` it printed into \`$REPORT_DIR/screenshots/\` (each script gets its own directory) and Read them.
|
||||
|
||||
### Auth Detection
|
||||
|
||||
After the first navigation, check if the URL changed to a login-like path:
|
||||
\`\`\`bash
|
||||
$B url
|
||||
\`\`\`
|
||||
If URL contains \`/login\`, \`/signin\`, \`/auth\`, or \`/sso\`: the site requires authentication. AskUserQuestion: "This site requires authentication. Want to import cookies from your browser? Run \`/setup-browser-cookies\` first if needed."
|
||||
Check the \`URL=\` line every script prints. If it contains \`/login\`, \`/signin\`, \`/auth\`, or \`/sso\`, the page bounced you to a sign-in wall: follow the credential rule in BROWSER SETUP — tell the user to sign in to that origin in Aside themselves, wait for them to say they're done, then re-run the script. The session now carries their cookies. No cookie import, no typed passwords, ever.
|
||||
|
||||
### Trunk Test (run on every page)
|
||||
|
||||
@@ -197,7 +231,7 @@ Apply these at each page. Each finding gets an impact rating (high/medium/polish
|
||||
- Weight contrast: >=2 weights used for hierarchy
|
||||
- No blacklisted fonts (Papyrus, Comic Sans, Lobster, Impact, Jokerman)
|
||||
- If primary font is Inter/Roboto/Open Sans/Poppins → flag as potentially generic
|
||||
- \`text-wrap: balance\` or \`text-pretty\` on headings (check via \`$B css <heading> text-wrap\`)
|
||||
- \`text-wrap: balance\` or \`text-pretty\` on headings (check via \`await pg.evaluate(() => getComputedStyle(document.querySelector("h1")).textWrap)\`)
|
||||
- Curly quotes used, not straight quotes
|
||||
- Ellipsis character (\`…\`) not three dots (\`...\`)
|
||||
- \`font-variant-numeric: tabular-nums\` on number columns
|
||||
@@ -258,7 +292,7 @@ Apply these at each page. Each finding gets an impact rating (high/medium/polish
|
||||
- Easing: ease-out for entering, ease-in for exiting, ease-in-out for moving
|
||||
- Duration: 50-700ms range (nothing slower unless page transition)
|
||||
- Purpose: every animation communicates something (state change, attention, spatial relationship)
|
||||
- \`prefers-reduced-motion\` respected (check: \`$B js "matchMedia('(prefers-reduced-motion: reduce)').matches"\`)
|
||||
- \`prefers-reduced-motion\` respected (check: \`await pg.evaluate(() => matchMedia("(prefers-reduced-motion: reduce)").matches)\`)
|
||||
- No \`transition: all\` — properties listed explicitly
|
||||
- Only \`transform\` and \`opacity\` animated (not layout properties like width, height, top, left)
|
||||
|
||||
@@ -293,14 +327,31 @@ ${AI_SLOP_BLACKLIST.map(item => `- ${item}`).join('\n')}
|
||||
|
||||
## Phase 4: Interaction Flow Review
|
||||
|
||||
Walk 2-3 key user flows and evaluate the *feel*, not just the function:
|
||||
Walk 2-3 key user flows and evaluate the *feel*, not just the function. One flow per Aside script — open, act, diff, evidence:
|
||||
|
||||
\`\`\`bash
|
||||
$B snapshot -i
|
||||
$B click @e3 # perform action
|
||||
$B snapshot -D # diff to see what changed
|
||||
aside repl '
|
||||
const HOOK = \`(() => { window.__gstackErrs = window.__gstackErrs || []; const oe = console.error; console.error = (...a) => { window.__gstackErrs.push(a.map(String).join(" ")); oe.apply(console, a); }; window.addEventListener("error", e => window.__gstackErrs.push("uncaught: " + e.message)); })()\`;
|
||||
const pg = await openTab("about:blank");
|
||||
await pg._sendToTarget("Page.addScriptToEvaluateOnNewDocument", { source: HOOK });
|
||||
await pg.goto("<url>");
|
||||
await snapshot(pg, { interactive: true }); // baseline for .diff; refs like [ref=e3] name every control
|
||||
await pg.screenshot({ path: "flow-<name>-step-1.jpg", type: "jpeg", quality: 60 });
|
||||
await pg.locator("e3").click(); // perform the action — or pg.getByRole("button", { name: "Sign Up" })
|
||||
await sleep(500); // or: await pg.waitForSelector("<selector>"); await pg.waitForURL(/dashboard/)
|
||||
const s = await snapshot(pg);
|
||||
console.log("DIFF_START"); console.log(s.diff); console.log("DIFF_END"); // what changed since the baseline
|
||||
console.log("URL=" + pg.url());
|
||||
console.log("CONSOLE_ERRORS=" + JSON.stringify(await pg.evaluate(() => window.__gstackErrs)));
|
||||
await pg.screenshot({ path: "flow-<name>-result.jpg", type: "jpeg", quality: 60 });
|
||||
console.log("ASIDE_DIR=" + pwd);
|
||||
await closeTab(pg);
|
||||
console.log("GSTACK_STEP_OK");
|
||||
'
|
||||
\`\`\`
|
||||
|
||||
Chain more steps inside the same script for a longer flow (re-snapshot before clicking by ref again). Forms may be filled but not submitted on a non-local target without the one-time consent in BROWSER SETUP.
|
||||
|
||||
Evaluate:
|
||||
- **Response feel:** Does clicking feel responsive? Any delays or missing loading states?
|
||||
- **Transition quality:** Are transitions intentional or generic/absent?
|
||||
@@ -436,19 +487,19 @@ Tie everything to user goals and product objectives. Always suggest specific imp
|
||||
## Important Rules
|
||||
|
||||
1. **Think like a designer, not a QA engineer.** You care whether things feel right, look intentional, and respect the user. You do NOT just care whether things "work."
|
||||
2. **Screenshots are evidence.** Every finding needs at least one screenshot. Use annotated screenshots (\`snapshot -a\`) to highlight elements.
|
||||
2. **Screenshots are evidence.** Every finding needs at least one screenshot. Use annotated screenshots (\`annotatedScreenshot(pg)\`) to highlight elements.
|
||||
3. **Be specific and actionable.** "Change X to Y because Z" — not "the spacing feels off."
|
||||
4. **Never read source code.** Evaluate the rendered site, not the implementation. (Exception: offer to write DESIGN.md from extracted observations.)
|
||||
5. **AI Slop detection is your superpower.** Most developers can't evaluate whether their site looks AI-generated. You can. Be direct about it.
|
||||
6. **Quick wins matter.** Always include a "Quick Wins" section — the 3-5 highest-impact fixes that take <30 minutes each.
|
||||
7. **Use \`snapshot -C\` for tricky UIs.** Finds clickable divs that the accessibility tree misses.
|
||||
7. **Fall back to \`annotatedScreenshot(pg)\` for tricky UIs.** When the snapshot tree does not surface a control you can plainly see (clickable divs, canvas buttons), take the annotated screenshot, Read it, and drive by CSS selector or \`pg.getByText(...)\` instead of by ref.
|
||||
8. **Responsive is design, not just "not broken."** A stacked desktop layout on mobile is not responsive design — it's lazy. Evaluate whether the mobile layout makes *design* sense.
|
||||
9. **Document incrementally.** Write each finding to the report as you find it. Don't batch.
|
||||
10. **Depth over breadth.** 5-10 well-documented findings with screenshots and specific suggestions > 20 vague observations.
|
||||
11. **Show screenshots to the user.** After every \`$B screenshot\`, \`$B snapshot -a -o\`, or \`$B responsive\` command, use the Read tool on the output file(s) so the user can see them inline. For \`responsive\` (3 files), Read all three. This is critical — without it, screenshots are invisible to the user.`;
|
||||
11. **Show screenshots to the user.** After every script that saves a screenshot, annotated screenshot, or responsive set, \`cp\` the files out of the printed \`ASIDE_DIR\` into \`$REPORT_DIR/screenshots/\` and use the Read tool on each copied file so the user can see them inline. For the responsive set (3 files), Read all three. This is critical — without it, screenshots are invisible to the user.`;
|
||||
}
|
||||
|
||||
export function generateDesignSketch(_ctx: TemplateContext): string {
|
||||
export function generateDesignSketch(ctx: TemplateContext): string {
|
||||
return `## Visual Sketch (UI ideas only)
|
||||
|
||||
If the chosen approach involves user-facing UI (screens, pages, forms, dashboards,
|
||||
@@ -479,20 +530,29 @@ Generate a single-page HTML file with these constraints:
|
||||
matches the actual use case)
|
||||
- Add HTML comments explaining design decisions
|
||||
|
||||
Write to a temp file:
|
||||
Create a private directory for it first — the renderer serves that whole directory
|
||||
over loopback, so it must be yours alone and hold nothing else (never a fixed,
|
||||
shared /tmp name another user could pre-create):
|
||||
\`\`\`bash
|
||||
SKETCH_FILE="/tmp/gstack-sketch-$(date +%s).html"
|
||||
mktemp -d "\${TMPDIR:-/tmp}/gstack-sketch.XXXXXX"
|
||||
\`\`\`
|
||||
Write the sketch to \`<that directory>/sketch.html\` (Write tool).
|
||||
|
||||
**Step 3: Render and capture**
|
||||
|
||||
\`gstack-render\` opens the sketch in the Aside browser when it is running — otherwise
|
||||
in gstack's own headless browser (its first line says which: \`ENGINE=aside\` or
|
||||
\`ENGINE=browse\`) — and screenshots it:
|
||||
|
||||
\`\`\`bash
|
||||
$B goto "file://$SKETCH_FILE"
|
||||
$B screenshot /tmp/gstack-sketch.png
|
||||
bun run ${toShellPath(ctx.paths.binDir)}/gstack-render.ts <sketch-dir>/sketch.html --screenshot <sketch-dir>/sketch.png --width 1280
|
||||
\`\`\`
|
||||
|
||||
If \`$B\` is not available (browse binary not set up), skip the render step. Tell the
|
||||
user: "Visual sketch requires the browse binary. Run the setup script to enable it."
|
||||
Only if it prints \`NEEDS_ASIDE\` or \`ASIDE_NOT_RUNNING\` followed by \`ERROR: no browser
|
||||
available\` (Aside is not open AND gstack's own browser is not built), skip the render
|
||||
step. Tell the user: "The visual sketch renders through the Aside browser (macOS 15+,
|
||||
aside.com) or gstack's own browser. Open Aside, or run ./setup in the gstack repo, and
|
||||
I'll render the wireframe." Never install either for them.
|
||||
|
||||
**Step 4: Present and iterate**
|
||||
|
||||
@@ -504,7 +564,7 @@ If they approve or say "good enough," proceed.
|
||||
**Step 5: Include in design doc**
|
||||
|
||||
Reference the wireframe screenshot in the design doc's "Recommended Approach" section.
|
||||
The screenshot file at \`/tmp/gstack-sketch.png\` can be referenced by downstream skills
|
||||
The screenshot file at \`<sketch-dir>/sketch.png\` (name the full path in the doc) can be referenced by downstream skills
|
||||
(\`/plan-design-review\`, \`/design-review\`) to see what was originally envisioned.
|
||||
|
||||
**Step 6: Outside design voices** (optional)
|
||||
@@ -798,22 +858,14 @@ if [ -x "$D" ]; then
|
||||
else
|
||||
echo "DESIGN_NOT_AVAILABLE"
|
||||
fi
|
||||
B=""
|
||||
[ -n "$_ROOT" ] && [ -x "$_ROOT/${ctx.paths.localSkillRoot}/browse/dist/browse" ] && B="$_ROOT/${ctx.paths.localSkillRoot}/browse/dist/browse"
|
||||
[ -z "$B" ] && B="${toShellPath(ctx.paths.browseDir)}/browse"
|
||||
if [ -x "$B" ]; then
|
||||
echo "BROWSE_READY: $B"
|
||||
else
|
||||
echo "BROWSE_NOT_AVAILABLE (will use 'open' to view comparison boards)"
|
||||
fi
|
||||
\`\`\`
|
||||
|
||||
If \`DESIGN_NOT_AVAILABLE\`: skip visual mockup generation and fall back to the
|
||||
existing HTML wireframe approach (\`DESIGN_SKETCH\`). Design mockups are a
|
||||
progressive enhancement, not a hard requirement.
|
||||
|
||||
If \`BROWSE_NOT_AVAILABLE\`: use \`open file://...\` instead of \`$B goto\` to open
|
||||
comparison boards. The user just needs to see the HTML file in any browser.
|
||||
Comparison boards are local HTML files: open them with \`open file://...\` on macOS
|
||||
(\`xdg-open\` elsewhere). The user just needs to see the file in their default browser.
|
||||
|
||||
If \`DESIGN_READY\`: the design binary is available for visual mockup generation.
|
||||
Commands:
|
||||
|
||||
Reference in New Issue
Block a user