## Visual Design Exploration ```bash _ROOT=$(git rev-parse --show-toplevel 2>/dev/null) D="" [ -n "$_ROOT" ] && [ -x "$_ROOT/.claude/skills/gstack/design/dist/design" ] && D="$_ROOT/.claude/skills/gstack/design/dist/design" [ -z "$D" ] && D="$HOME/.claude/skills/gstack/design/dist/design" _DS=$("$HOME/.claude/skills/gstack/bin/gstack-paths" --get GSTACK_STATE_ROOT 2>/dev/null); _DC=${_DS:+$_DS/design-ready}; _DK=$(ls -diL "$D" 2>/dev/null) _dt() { if command -v gtimeout >/dev/null; then gtimeout 10 "$@"; elif command -v timeout >/dev/null; then timeout 10 "$@" elif command -v perl >/dev/null; then perl -e 'alarm(shift);exec(@ARGV)' 10 "$@"; else return 125; fi; } _RC=0; _F="Fix: cd ${D%/design/dist/design} && ./setup" if [ ! -x "$D" ]; then _RC=missing elif [ ! "$_DC" -nt "$D" ] || [ "$(cat "$_DC")" != "$_DK" ]; then _dt "$D" --version >/dev/null 2>&1 "$_DC" 2>/dev/null ;; missing) echo "DESIGN_NOT_AVAILABLE: $D is not installed. $_F" ;; 124|142) echo "DESIGN_NOT_AVAILABLE: $D --version timed out after 10s" ;; 125) echo "DESIGN_NOT_AVAILABLE: no timeout/gtimeout/perl to bound $D" ;; 137) echo "DESIGN_NOT_AVAILABLE: $D --version exited 137 (killed at launch; on macOS usually an invalid code signature). $_F" ;; *) echo "DESIGN_NOT_AVAILABLE: $D --version exited $_RC" ;; esac ``` **If `DESIGN_NOT_AVAILABLE`:** Fall back to the HTML wireframe approach below (the existing DESIGN_SKETCH section). Visual mockups require the design binary. **If `DESIGN_READY`:** Generate visual mockup explorations for the user. Generating visual mockups of the proposed design... (say "skip" if you don't need visuals) **Step 1: Set up the design directory** ```bash SLUG=$(~/.claude/skills/gstack/bin/gstack-slug --get SLUG 2>/dev/null) GSTACK_STATE_ROOT=$(~/.claude/skills/gstack/bin/gstack-paths --get GSTACK_STATE_ROOT); : "${GSTACK_STATE_ROOT:?gstack-paths failed; reinstall with ./setup or /gstack-upgrade}" _DESIGN_DIR="$GSTACK_STATE_ROOT/projects/$SLUG/designs/mockup-$(date +%Y%m%d)" mkdir -p "$_DESIGN_DIR" echo "DESIGN_DIR: $_DESIGN_DIR" ``` **Step 2: Construct the design brief** Read DESIGN.md if it exists — use it to constrain the visual style. If no DESIGN.md, explore wide across diverse directions. The brief, and the user's feedback in Step 6, go in private files: ```bash _GT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)/.gstack/tmp" mkdir -p "$_GT" && chmod 700 "$_GT" || { echo "Not sent: cannot create $_GT for the text file." >&2; exit 1; } _EX=$(git rev-parse --git-path info/exclude 2>/dev/null) && mkdir -p "$(dirname "$_EX")" && { grep -qxF '/.gstack/tmp/' "$_EX" 2>/dev/null || echo '/.gstack/tmp/' >> "$_EX"; } BRIEF_FILE=$(mktemp "${_GT:?}/brief.XXXXXX") || { echo "Not sent: mktemp failed in $_GT." >&2; exit 1; }; echo "BRIEF_FILE: $BRIEF_FILE (name: ${BRIEF_FILE##*/})" FEEDBACK_FILE=$(mktemp "${_GT:?}/feedback.XXXXXX") || { echo "Not sent: mktemp failed in $_GT." >&2; exit 1; }; echo "FEEDBACK_FILE: $FEEDBACK_FILE (name: ${FEEDBACK_FILE##*/})" ``` Write the text into each printed file with your file-write tool (Claude Code's Write tool needs a Read of the empty file first), exactly as it should appear. The text never goes into a shell command, heredoc or quoted argument. If a write fails or is refused, do not send: print the cause, the file path and the command below for sending by hand. **Step 3: Generate 3 variants** (substitute the printed name for ``) ```bash BRIEF_FILE="$(git rev-parse --show-toplevel 2>/dev/null || pwd)/.gstack/tmp/" [ -s "$BRIEF_FILE" ] || { echo "Not run: $BRIEF_FILE is missing or empty. Write the brief, then rerun this block." >&2; exit 1; } _OUT=$($D variants --brief "$(cat "$BRIEF_FILE")" --count 3 --output-dir "$_DESIGN_DIR/"); _RC=$? printf '%s\n' "$_OUT"; echo "EXIT: $_RC" ``` This generates 3 style variations of the same brief (~40 seconds total). **Round accounting (before any board, check or inline view):** `$D` never overwrites; a taken name is bumped (for example `-2`), so use only the paths its JSON printed and carry them as literal paths into later blocks (bash blocks do not share variables). Tell the user how many of `requested` paid images were saved (`saved`) and name each `failures` entry. Route on the exit code: 0 continue with `saved`; 3 continue with `saved` and say the run stopped early; 2 nothing was saved, so report `failures` and stop: do not run `$D compare` or `$D check`. **Step 4: Show variants inline, then open comparison board** Show each saved image to the user inline first (read the printed paths with Read tool), then create and serve the comparison board: Write this round's board images (printed paths that passed checks, in order) as a JSON array to `$_DESIGN_DIR/board-images.json` with the Write tool; board letters A, B, C follow that order. Then archive any earlier Submit so it cannot approve these images, and build the board: ```bash [ -f "${_DESIGN_DIR:?set _DESIGN_DIR to the design dir printed above}/feedback.json" ] && mv "${_DESIGN_DIR:?}/feedback.json" "${_DESIGN_DIR:?}/feedback-$(date -u +%Y%m%dT%H%M%SZ).json" $D compare --images-file "$_DESIGN_DIR/board-images.json" --output "$_DESIGN_DIR/design-board.html" --serve ``` This publishes the board to the design daemon, opens it in the user's default browser, and exits; it does not wait for feedback. Read captured stderr for the `BOARD_URL: http://127.0.0.1:N/boards//` line, then wait with AskUserQuestion: "Review , click Submit or Regenerate, then tell me (or paste your preferences here)." After the answer, read `feedback.json` or `feedback-pending.json` next to the board HTML. If the command exits nonzero or prints no `BOARD_URL`, show each variant inline with Read and fall back to AskUserQuestion: "Which variant do you prefer? Any feedback?" **Step 5: Handle feedback** If the JSON contains `"regenerated": true`: 1. Read `regenerateAction` (or `remixSpec` for remix requests) 2. Generate new variants with `$D iterate` or `$D variants` using the updated brief, rewritten into the brief file (capture and round accounting as in Step 3) 3. Rebuild the board with Step 4's board block, without `--serve` 4. POST the new HTML to the running board. Parse the board URL from stderr (`BOARD_URL: http://127.0.0.1:N/boards//` — the daemon path) or fall back to the legacy port (`SERVE_STARTED: port=N` — only emitted under `--no-daemon`, hits `/api/reload` root). Daemon path: `jq -nc --arg html "$_DESIGN_DIR/design-board.html" '{html: $html}' | curl -sS -X POST "${BOARD_URL}api/reload" -H 'Content-Type: application/json' --data-binary @-` 5. Board auto-refreshes in the same tab If `"regenerated": false`: proceed with the approved variant. **Step 6: Save approved choice** Write the user's feedback (`none` when they gave none) into the feedback file from Step 2, then map the confirmed letter through this board's `board-images.json` (never the directory listing) and save it, substituting the printed name for ``: ```bash _IMG=$(jq -r --arg v "" '.[($v | explode[0]) - 65] // empty' "$_DESIGN_DIR/board-images.json") if [ -n "$_IMG" ]; then FEEDBACK_FILE="$(git rev-parse --show-toplevel 2>/dev/null || pwd)/.gstack/tmp/" [ -s "$FEEDBACK_FILE" ] || { echo "Not saved: $FEEDBACK_FILE is missing or empty. Write the feedback, then rerun this block." >&2; exit 1; } jq -n --arg approved_variant "" --arg approved_path "$(basename "$_IMG")" --rawfile feedback "$FEEDBACK_FILE" --arg date "$(date -u +%Y-%m-%dT%H:%M:%SZ)" --arg screen "mockup" --arg branch "$(git branch --show-current 2>/dev/null)" '$ARGS.named | .feedback |= rtrimstr("\n")' > "$_DESIGN_DIR/approved.json" && rm -f "$FEEDBACK_FILE" echo "APPROVED_IMAGE: $_IMG" else echo "NO_BOARD_IMAGE: is not on this board; reselect from the board" fi ``` Reference the printed `APPROVED_IMAGE` in the design doc or plan. ## Visual Sketch (UI ideas only) If the chosen approach involves user-facing UI (screens, pages, forms, dashboards, or interactive elements), generate a rough wireframe to help the user visualize it. If the idea is backend-only, infrastructure, or has no UI component — skip this section silently. **Step 1: Gather design context** 1. Check if `DESIGN.md` exists in the repo root. If it does, read it for design system constraints (colors, typography, spacing, component patterns). Use these constraints in the wireframe. 2. Apply core design principles: - **Information hierarchy** — what does the user see first, second, third? - **Interaction states** — loading, empty, error, success, partial - **Edge case paranoia** — what if the name is 47 chars? Zero results? Network fails? - **Subtraction default** — "as little design as possible" (Rams). Every element earns its pixels. - **Design for trust** — every interface element builds or erodes user trust. **Step 2: Generate wireframe HTML** Generate a single-page HTML file with these constraints: - **Intentionally rough aesthetic** — use system fonts, thin gray borders, no color, hand-drawn-style elements. This is a sketch, not a polished mockup. - Self-contained — no external dependencies, no CDN links, inline CSS only - Show the core interaction flow (1-3 screens/states max) - Include realistic placeholder content (not "Lorem ipsum" — use content that matches the actual use case) - Add HTML comments explaining design decisions 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 mktemp -d "${TMPDIR:-/tmp}/gstack-sketch.XXXXXX" ``` Write the sketch to `/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 bun --no-env-file --config=/dev/null run $HOME/.claude/skills/gstack/bin/gstack-render.ts /sketch.html --screenshot /sketch.png --width 1280 ``` 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** Show the screenshot to the user. Ask: "Does this feel right? Want to iterate on the layout?" If they want changes, regenerate the HTML with their feedback and re-render. 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 `/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) After the wireframe is approved, offer outside design perspectives: ```bash if ! _OUTSIDE_CFG=$("$HOME/.claude/skills/gstack/bin/gstack-config" get --stored codex_reviews) || [ "$_OUTSIDE_CFG" = disabled ]; then echo 'CODEX_MODE: disabled' elif ( # GSTACK_ACTIVE_HOST names the harness, never the model. if { [ -n "${CODEX_THREAD_ID:-}" ] || [ -n "${CODEX_SANDBOX:-}" ] || [ "${GSTACK_ACTIVE_HOST:-}" = codex ]; }; then echo 'Codex outside review unavailable: harness mismatch; no outside process started. Missing coverage.' >&2 if { [ -n "${CLAUDECODE:-}" ] || [ "${GSTACK_ACTIVE_HOST:-}" = claude ]; } && { [ -n "${CODEX_THREAD_ID:-}" ] || [ -n "${CODEX_SANDBOX:-}" ] || [ "${GSTACK_ACTIVE_HOST:-}" = codex ]; }; then echo 'Inherited harness markers conflict. Run setup --host (claude or codex); do not guess a replacement provider.' >&2 else echo 'Repair installed skills: run setup --host codex from your gstack checkout.' >&2 fi exit 78 fi ); then if command -v codex >/dev/null 2>&1; then echo 'CODEX_MODE: ready'; else echo 'CODEX_MODE: not_installed'; fi else echo 'CODEX_MODE: under_current_harness' fi ``` The historical `CODEX_MODE` variable describes **Codex** availability here. `CODEX_MODE: disabled` or a send's `consent_missing` line: `codex_reviews` is off or unreadable; do not ask; record `outside_status: disabled`. Authentication and configured model validity are checked by the actual invocation, without overriding either. Missing/broken CLI: install or repair Codex; authentication failure: run `codex login`. Otherwise honor this caller’s opt-in/skip choice. Any non-ready outcome is missing outside coverage; follow the caller’s existing fallback. Never substitute another external provider. If Codex is available, use AskUserQuestion: > "Want outside design perspectives on the chosen approach? Codex proposes a visual thesis, content plan, and interaction ideas. A Claude subagent proposes an alternative aesthetic direction." > > A) Yes — get outside design voices > B) No — proceed without If user chooses A, run both independent voices below and wait for both results before synthesis. They may overlap when the host supports parallel tool calls; the native subagent call remains blocking. 1. **Codex** (via Bash, `model_reasoning_effort="medium"`): Prompt: "For this product approach, provide: a visual thesis (one sentence — mood, material, energy), a content plan (hero → support → detail → CTA), and 2 interaction ideas that change page feel. Apply beautiful defaults: composition-first, brand-first, cardless, poster not document. Be opinionated." Include the approved product approach and wireframe source in the prepared prompt. Write the **complete prompt and context**, including actual plan/spec/source, to a private file. Substitute its shell-quoted path for ``; never interpolate user text into shell source. Request a complete design proposal ending with Recommendation: because . ```bash # GSTACK_ACTIVE_HOST names the harness, never the model. if { [ -n "${CODEX_THREAD_ID:-}" ] || [ -n "${CODEX_SANDBOX:-}" ] || [ "${GSTACK_ACTIVE_HOST:-}" = codex ]; }; then echo 'Codex outside review unavailable: harness mismatch; no outside process started. Missing coverage.' >&2 if { [ -n "${CLAUDECODE:-}" ] || [ "${GSTACK_ACTIVE_HOST:-}" = claude ]; } && { [ -n "${CODEX_THREAD_ID:-}" ] || [ -n "${CODEX_SANDBOX:-}" ] || [ "${GSTACK_ACTIVE_HOST:-}" = codex ]; }; then echo 'Inherited harness markers conflict. Run setup --host (claude or codex); do not guess a replacement provider.' >&2 else echo 'Repair installed skills: run setup --host codex from your gstack checkout.' >&2 fi exit 78 fi _REPO_ROOT=$(git rev-parse --show-toplevel) || { echo 'ERROR: not in a git repo' >&2; exit 1; } _OUTSIDE_TMP=$(mktemp -d "${TMPDIR:-/tmp}/gstack-outside.XXXXXXXX") || exit 1 trap 'rm -rf "$_OUTSIDE_TMP"' EXIT _OUTSIDE_INPUT="$_OUTSIDE_TMP/prompt" cat -- '' >"$_OUTSIDE_INPUT" || exit 1 _CODEX_PROBE="$HOME/.claude/skills/gstack/bin/gstack-codex-probe" _CODEX_OUT=$("$_CODEX_PROBE" select-model exec) || exit 1 _CODEX_SEL=$(echo "$_CODEX_OUT" | sed -n 's/^CODEX_SEL: //p') _CODEX_SANDBOX_MODE=$(echo "$_CODEX_OUT" | sed -n 's/^CODEX_SANDBOX: //p') "$_CODEX_PROBE" check-sandbox || exit 1 "$_CODEX_PROBE" show-first-use-notice _OUTSIDE_EXIT=0 "$_CODEX_PROBE" run-with-timeout --effort medium 300 codex exec - -C "$_REPO_ROOT" -s "${_CODEX_SANDBOX_MODE:?}" -c "model=\"${_CODEX_SEL:?}\"" -c skills.include_instructions=false -c 'web_search="cached"' --json -o "$_OUTSIDE_TMP/text" <"$_OUTSIDE_INPUT" >"$_OUTSIDE_TMP/events" 2>"$_OUTSIDE_TMP/stderr" || _OUTSIDE_EXIT=$? cat "$_OUTSIDE_TMP/text" 2>/dev/null || tail -n 20 "$_OUTSIDE_TMP/events" cat "$_OUTSIDE_TMP/stderr" >&2 || { [ "$_OUTSIDE_EXIT" -ne 0 ] || _OUTSIDE_EXIT=1; } _OUTSIDE_RC=0 bun --no-env-file --config=/dev/null "$HOME/.claude/skills/gstack/lib/outside-review-result.ts" --label 'Codex outside review' --exit "$_OUTSIDE_EXIT" --stderr "$_OUTSIDE_TMP/stderr" --events "$_OUTSIDE_TMP/events" proposal "$_OUTSIDE_TMP/text" || _OUTSIDE_RC=$? case "$_OUTSIDE_RC" in 0|3) ;; 4) echo 'OUTSIDE_STATUS: unverified provider=codex host=claude'; exit 4 ;; *) [ "$_OUTSIDE_EXIT" -ne 0 ] && exit "$_OUTSIDE_EXIT"; exit 1 ;; esac echo 'OUTSIDE_STATUS: completed provider=codex host=claude' ``` Use Bash `timeout: 360000`; show the full response in a `tool-output` fence. Require successful execution and valid markers. Refusal, empty/malformed output, missing Recommendation markers, timeout or CLI failure means `outside_status: unavailable`. P0/P1 findings block like native ones; `OUTSIDE_STATUS: unverified` is missing coverage. Continue completed proposals; native completion does not count as outside coverage. After either outcome, delete only your private prompt; scratch cleanup is automatic. Retain the historical review-log skill ID; add `"host":"claude","outside_provider":"codex","outside_status":"completed|unavailable|disabled|skipped","phase":"design-sketch"`. Record differing attempt outcomes separately. `source:"codex"` requires completed CLI output; native uses `source:"in-host"` (historical `source:"claude"`: native Claude). Availability/native fallback is not outside completion. Preserve all reported modelUsage; unknown model identity stays unknown. Under `GSTACK_CODEX_NO_SANDBOX=1` add `"codex_sandbox":"danger-full-access"`. 2. **Claude subagent** (via Agent tool, `run_in_background: false` when available — subagents default to background since Claude Code v2.1.198; A launch receipt means it went background: await its completion notice.): "For this product approach, what design direction would you recommend? What aesthetic, typography, and interaction patterns fit? What would make this approach feel inevitable to the user? Be specific — font names, hex colors, spacing values. Do not fall back on these defaults: a cream ground with a high-contrast serif and terracotta accent; near-black with one neon accent and glowing edges; italic accent words inside headlines; numbered 01 / 02 / 03 section labels; tiny tracked monospace labels; pill-shaped buttons. If your first idea is one of these, name it and choose again." Present Codex output under `CODEX SAYS (design sketch):` and subagent output under `CLAUDE SUBAGENT (design direction):`. Error handling: all non-blocking. On failure, skip and continue.