Skill Tracer
Skills › gstack › office-hours › visual-design.md

visual-design.md

283 lines · office-hours/sections/visual-design.md at 3f79d81

Open on GitHub

Most marks come from matching words in the file, so treat them as pointers.

1<!-- AUTO-GENERATED from visual-design.md.tmpl — do not edit directly -->
2<!-- Regenerate: bun run gen:skill-docs -->

<!-- AUTO-GENERATED from visual-design.md.tmpl — do not edit directly --> <!-- Regenerate: bun run gen:skill-docs -->

Raw lines 1 to 2
3## Visual Design Exploration
4
5```bash
6_ROOT=$(git rev-parse --show-toplevel 2>/dev/null)
7D=""
8[ -n "$_ROOT" ] && [ -x "$_ROOT/.claude/skills/gstack/design/dist/design" ] && D="$_ROOT/.claude/skills/gstack/design/dist/design"
9[ -z "$D" ] && D="$HOME/.claude/skills/gstack/design/dist/design"
10_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)
11_dt() { if command -v gtimeout >/dev/null; then gtimeout 10 "$@"; elif command -v timeout >/dev/null; then timeout 10 "$@"
12elif command -v perl >/dev/null; then perl -e 'alarm(shift);exec(@ARGV)' 10 "$@"; else return 125; fi; }
13_RC=0; _F="Fix: cd ${D%/design/dist/design} && ./setup"
14if [ ! -x "$D" ]; then _RC=missing
15elif [ ! "$_DC" -nt "$D" ] || [ "$(cat "$_DC")" != "$_DK" ]; then _dt "$D" --version >/dev/null 2>&1 </dev/null || _RC=$?
16fi
17case "$_RC" in
18 0) echo "DESIGN_READY: $D"; [ -n "$_DC" ] && echo "$_DK" > "$_DC" 2>/dev/null ;;
19 missing) echo "DESIGN_NOT_AVAILABLE: $D is not installed. $_F" ;;
20 124|142) echo "DESIGN_NOT_AVAILABLE: $D --version timed out after 10s" ;;
21 125) echo "DESIGN_NOT_AVAILABLE: no timeout/gtimeout/perl to bound $D" ;;
22 137) echo "DESIGN_NOT_AVAILABLE: $D --version exited 137 (killed at launch; on macOS usually an invalid code signature). $_F" ;;
23 *) echo "DESIGN_NOT_AVAILABLE: $D --version exited $_RC" ;;
24esac
25```
27**If `DESIGN_NOT_AVAILABLE`:** Fall back to the HTML wireframe approach below
28(the existing DESIGN_SKETCH section). Visual mockups require the design binary.
30**If `DESIGN_READY`:** Generate visual mockup explorations for the user.
32Generating visual mockups of the proposed design... (say "skip" if you don't need visuals)
34**Step 1: Set up the design directory**
36```bash
37SLUG=$(~/.claude/skills/gstack/bin/gstack-slug --get SLUG 2>/dev/null)
38GSTACK_STATE_ROOT=$(~/.claude/skills/gstack/bin/gstack-paths --get GSTACK_STATE_ROOT); : "${GSTACK_STATE_ROOT:?gstack-paths failed; reinstall with ./setup or /gstack-upgrade}"
39_DESIGN_DIR="$GSTACK_STATE_ROOT/projects/$SLUG/designs/mockup-$(date +%Y%m%d)"
40mkdir -p "$_DESIGN_DIR"
41echo "DESIGN_DIR: $_DESIGN_DIR"
42```
44**Step 2: Construct the design brief**
46Read DESIGN.md if it exists — use it to constrain the visual style. If no DESIGN.md,
47explore wide across diverse directions. The brief, and the user's feedback in Step 6, go in private files:
49```bash
50_GT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)/.gstack/tmp"
51mkdir -p "$_GT" && chmod 700 "$_GT" || { echo "Not sent: cannot create $_GT for the text file." >&2; exit 1; }
52_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"; }
53BRIEF_FILE=$(mktemp "${_GT:?}/brief.XXXXXX") || { echo "Not sent: mktemp failed in $_GT." >&2; exit 1; }; echo "BRIEF_FILE: $BRIEF_FILE (name: ${BRIEF_FILE##*/})"
54FEEDBACK_FILE=$(mktemp "${_GT:?}/feedback.XXXXXX") || { echo "Not sent: mktemp failed in $_GT." >&2; exit 1; }; echo "FEEDBACK_FILE: $FEEDBACK_FILE (name: ${FEEDBACK_FILE##*/})"
55```
57Write 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.
59**Step 3: Generate 3 variants** (substitute the printed name for `<brief-file-name>`)
61```bash
62BRIEF_FILE="$(git rev-parse --show-toplevel 2>/dev/null || pwd)/.gstack/tmp/<brief-file-name>"
63[ -s "$BRIEF_FILE" ] || { echo "Not run: $BRIEF_FILE is missing or empty. Write the brief, then rerun this block." >&2; exit 1; }
64_OUT=$($D variants --brief "$(cat "$BRIEF_FILE")" --count 3 --output-dir "$_DESIGN_DIR/"); _RC=$?
65printf '%s\n' "$_OUT"; echo "EXIT: $_RC"
66```
68This generates 3 style variations of the same brief (~40 seconds total).
70<!-- design:round-accounting -->
71**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`.
73**Step 4: Show variants inline, then open comparison board**
75Show each saved image to the user inline first (read the printed paths with Read tool), then
76create and serve the comparison board:
78<!-- design:board -->
79Write 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:
81```bash
82[ -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"
83$D compare --images-file "$_DESIGN_DIR/board-images.json" --output "$_DESIGN_DIR/design-board.html" --serve
84```
86This publishes the board to the design daemon, opens it in the user's default
87browser, and exits; it does not wait for feedback. Read captured stderr for the
88`BOARD_URL: http://127.0.0.1:N/boards/<id>/` line, then wait with AskUserQuestion:
89"Review <BOARD_URL>, click Submit or Regenerate, then tell me (or paste your
90preferences here)." After the answer, read `feedback.json` or
91`feedback-pending.json` next to the board HTML.
93If the command exits nonzero or prints no `BOARD_URL`, show each variant inline
94with Read and fall back to AskUserQuestion: "Which variant do you prefer? Any feedback?"
96**Step 5: Handle feedback**
98If the JSON contains `"regenerated": true`:
991. Read `regenerateAction` (or `remixSpec` for remix requests)
1002. 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)
1013. Rebuild the board with Step 4's board block, without `--serve`
1024. POST the new HTML to the running board. Parse the board URL from stderr
103 (`BOARD_URL: http://127.0.0.1:N/boards/<id>/` — the daemon path) or fall
104 back to the legacy port (`SERVE_STARTED: port=N` — only emitted under
105 `--no-daemon`, hits `/api/reload` root). Daemon path:
106 `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 @-`
1075. Board auto-refreshes in the same tab
109If `"regenerated": false`: proceed with the approved variant.
111**Step 6: Save approved choice**
113Write 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 `<feedback-file-name>`:
115```bash
116_IMG=$(jq -r --arg v "<VARIANT>" '.[($v | explode[0]) - 65] // empty' "$_DESIGN_DIR/board-images.json")
117if [ -n "$_IMG" ]; then
118 FEEDBACK_FILE="$(git rev-parse --show-toplevel 2>/dev/null || pwd)/.gstack/tmp/<feedback-file-name>"
119 [ -s "$FEEDBACK_FILE" ] || { echo "Not saved: $FEEDBACK_FILE is missing or empty. Write the feedback, then rerun this block." >&2; exit 1; }
120 jq -n --arg approved_variant "<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"
121 echo "APPROVED_IMAGE: $_IMG"
122else
123 echo "NO_BOARD_IMAGE: <VARIANT> is not on this board; reselect from the board"
124fi
125```
127Reference the printed `APPROVED_IMAGE` in the design doc or plan.

Visual Design Exploration

_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 </dev/null || _RC=$?
fi
case "$_RC" in
  0) echo "DESIGN_READY: $D"; [ -n "$_DC" ] && echo "$_DK" > "$_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

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:

_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 <brief-file-name>)

BRIEF_FILE="$(git rev-parse --show-toplevel 2>/dev/null || pwd)/.gstack/tmp/<brief-file-name>"
[ -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).

<!-- design:round-accounting --> 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:

<!-- design: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:

[ -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/<id>/ line, then wait with AskUserQuestion: "Review <BOARD_URL>, 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/<id>/ — 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 <feedback-file-name>:

_IMG=$(jq -r --arg v "<VARIANT>" '.[($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/<feedback-file-name>"
  [ -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 "<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: <VARIANT> is not on this board; reselect from the board"
fi

Reference the printed APPROVED_IMAGE in the design doc or plan.

Raw lines 3 to 128
Runs code bin/gstack-paths :10
Runs code bin/gstack-slug :37
Involves a person AskUserQuestion :88
129## Visual Sketch (UI ideas only)
131If the chosen approach involves user-facing UI (screens, pages, forms, dashboards,
132or interactive elements), generate a rough wireframe to help the user visualize it.
133If the idea is backend-only, infrastructure, or has no UI component — skip this
134section silently.
136**Step 1: Gather design context**
1381. Check if `DESIGN.md` exists in the repo root. If it does, read it for design
139 system constraints (colors, typography, spacing, component patterns). Use these
140 constraints in the wireframe.
1412. Apply core design principles:
142 - **Information hierarchy** — what does the user see first, second, third?
143 - **Interaction states** — loading, empty, error, success, partial
144 - **Edge case paranoia** — what if the name is 47 chars? Zero results? Network fails?
145 - **Subtraction default** — "as little design as possible" (Rams). Every element earns its pixels.
146 - **Design for trust** — every interface element builds or erodes user trust.
148**Step 2: Generate wireframe HTML**
150Generate a single-page HTML file with these constraints:
151- **Intentionally rough aesthetic** — use system fonts, thin gray borders, no color,
152 hand-drawn-style elements. This is a sketch, not a polished mockup.
153- Self-contained — no external dependencies, no CDN links, inline CSS only
154- Show the core interaction flow (1-3 screens/states max)
155- Include realistic placeholder content (not "Lorem ipsum" — use content that
156 matches the actual use case)
157- Add HTML comments explaining design decisions
159Create a private directory for it first — the renderer serves that whole directory
160over loopback, so it must be yours alone and hold nothing else (never a fixed,
161shared /tmp name another user could pre-create):
162```bash
163mktemp -d "${TMPDIR:-/tmp}/gstack-sketch.XXXXXX"
164```
165Write the sketch to `<that directory>/sketch.html` (Write tool).
167**Step 3: Render and capture**
169`gstack-render` opens the sketch in the Aside browser when it is running — otherwise
170in gstack's own headless browser (its first line says which: `ENGINE=aside` or
171`ENGINE=browse`) — and screenshots it:
173```bash
174bun --no-env-file --config=/dev/null run $HOME/.claude/skills/gstack/bin/gstack-render.ts <sketch-dir>/sketch.html --screenshot <sketch-dir>/sketch.png --width 1280
175```
177Only if it prints `NEEDS_ASIDE` or `ASIDE_NOT_RUNNING` followed by `ERROR: no browser
178available` (Aside is not open AND gstack's own browser is not built), skip the render
179step. Tell the user: "The visual sketch renders through the Aside browser (macOS 15+,
180aside.com) or gstack's own browser. Open Aside, or run ./setup in the gstack repo, and
181I'll render the wireframe." Never install either for them.
183**Step 4: Present and iterate**
185Show the screenshot to the user. Ask: "Does this feel right? Want to iterate on the layout?"
187If they want changes, regenerate the HTML with their feedback and re-render.
188If they approve or say "good enough," proceed.
190**Step 5: Include in design doc**
192Reference the wireframe screenshot in the design doc's "Recommended Approach" section.
193The screenshot file at `<sketch-dir>/sketch.png` (name the full path in the doc) can be referenced by downstream skills
194(`/plan-design-review`, `/design-review`) to see what was originally envisioned.
196**Step 6: Outside design voices** (optional)
198After the wireframe is approved, offer outside design perspectives:
200```bash
202if ! _OUTSIDE_CFG=$("$HOME/.claude/skills/gstack/bin/gstack-config" get --stored codex_reviews) || [ "$_OUTSIDE_CFG" = disabled ]; then
203 echo 'CODEX_MODE: disabled'
204elif ( # GSTACK_ACTIVE_HOST names the harness, never the model.
205if { [ -n "${CODEX_THREAD_ID:-}" ] || [ -n "${CODEX_SANDBOX:-}" ] || [ "${GSTACK_ACTIVE_HOST:-}" = codex ]; }; then
206 echo 'Codex outside review unavailable: harness mismatch; no outside process started. Missing coverage.' >&2
207 if { [ -n "${CLAUDECODE:-}" ] || [ "${GSTACK_ACTIVE_HOST:-}" = claude ]; } && { [ -n "${CODEX_THREAD_ID:-}" ] || [ -n "${CODEX_SANDBOX:-}" ] || [ "${GSTACK_ACTIVE_HOST:-}" = codex ]; }; then
208 echo 'Inherited harness markers conflict. Run setup --host <actual-harness> (claude or codex); do not guess a replacement provider.' >&2
209 else
210 echo 'Repair installed skills: run setup --host codex from your gstack checkout.' >&2
211 fi
212 exit 78
213fi
214); then
215 if command -v codex >/dev/null 2>&1; then echo 'CODEX_MODE: ready'; else echo 'CODEX_MODE: not_installed'; fi
216else
217 echo 'CODEX_MODE: under_current_harness'
218fi
219```
221The 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.
223If Codex is available, use AskUserQuestion:
224> "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."
226> A) Yes — get outside design voices
227> B) No — proceed without
229If 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.
2311. **Codex** (via Bash, `model_reasoning_effort="medium"`):
232Prompt: "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.
234Write the **complete prompt and context**, including actual plan/spec/source, to a private file. Substitute its shell-quoted path for `<prepared-prompt-file>`; never interpolate user text into shell source. Request a complete design proposal ending with Recommendation: <direction> because <product-specific reason>.
236```bash
237# GSTACK_ACTIVE_HOST names the harness, never the model.
238if { [ -n "${CODEX_THREAD_ID:-}" ] || [ -n "${CODEX_SANDBOX:-}" ] || [ "${GSTACK_ACTIVE_HOST:-}" = codex ]; }; then
239 echo 'Codex outside review unavailable: harness mismatch; no outside process started. Missing coverage.' >&2
240 if { [ -n "${CLAUDECODE:-}" ] || [ "${GSTACK_ACTIVE_HOST:-}" = claude ]; } && { [ -n "${CODEX_THREAD_ID:-}" ] || [ -n "${CODEX_SANDBOX:-}" ] || [ "${GSTACK_ACTIVE_HOST:-}" = codex ]; }; then
241 echo 'Inherited harness markers conflict. Run setup --host <actual-harness> (claude or codex); do not guess a replacement provider.' >&2
242 else
243 echo 'Repair installed skills: run setup --host codex from your gstack checkout.' >&2
244 fi
245 exit 78
246fi
248_REPO_ROOT=$(git rev-parse --show-toplevel) || { echo 'ERROR: not in a git repo' >&2; exit 1; }
249_OUTSIDE_TMP=$(mktemp -d "${TMPDIR:-/tmp}/gstack-outside.XXXXXXXX") || exit 1
250trap 'rm -rf "$_OUTSIDE_TMP"' EXIT
251_OUTSIDE_INPUT="$_OUTSIDE_TMP/prompt"
252cat -- '<prepared-prompt-file>' >"$_OUTSIDE_INPUT" || exit 1
254_CODEX_PROBE="$HOME/.claude/skills/gstack/bin/gstack-codex-probe"
255_CODEX_OUT=$("$_CODEX_PROBE" select-model exec) || exit 1
256_CODEX_SEL=$(echo "$_CODEX_OUT" | sed -n 's/^CODEX_SEL: //p')
257_CODEX_SANDBOX_MODE=$(echo "$_CODEX_OUT" | sed -n 's/^CODEX_SANDBOX: //p')
258"$_CODEX_PROBE" check-sandbox || exit 1
259"$_CODEX_PROBE" show-first-use-notice
260_OUTSIDE_EXIT=0
261"$_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=$?
262cat "$_OUTSIDE_TMP/text" 2>/dev/null || tail -n 20 "$_OUTSIDE_TMP/events"
264cat "$_OUTSIDE_TMP/stderr" >&2 || { [ "$_OUTSIDE_EXIT" -ne 0 ] || _OUTSIDE_EXIT=1; }
265_OUTSIDE_RC=0
266bun --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=$?
267case "$_OUTSIDE_RC" in
268 0|3) ;;
269 4) echo 'OUTSIDE_STATUS: unverified provider=codex host=claude'; exit 4 ;;
270 *) [ "$_OUTSIDE_EXIT" -ne 0 ] && exit "$_OUTSIDE_EXIT"; exit 1 ;;
271esac
272echo 'OUTSIDE_STATUS: completed provider=codex host=claude'
273```
275Use 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.
277Retain 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"`.
2792. **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.):
280"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."
282Present Codex output under `CODEX SAYS (design sketch):` and subagent output under `CLAUDE SUBAGENT (design direction):`.
283Error handling: all non-blocking. On failure, skip and continue.

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:
  3. Information hierarchy — what does the user see first, second, third?
  4. Interaction states — loading, empty, error, success, partial
  5. Edge case paranoia — what if the name is 47 chars? Zero results? Network fails?
  6. Subtraction default — "as little design as possible" (Rams). Every element earns its pixels.
  7. 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):

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:

bun --no-env-file --config=/dev/null run $HOME/.claude/skills/gstack/bin/gstack-render.ts <sketch-dir>/sketch.html --screenshot <sketch-dir>/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-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)

After the wireframe is approved, offer outside design perspectives:


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 <actual-harness> (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 <prepared-prompt-file>; never interpolate user text into shell source. Request a complete design proposal ending with Recommendation: <direction> because <product-specific reason>.

# 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 <actual-harness> (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 -- '<prepared-prompt-file>' >"$_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".

  1. 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.

Raw lines 129 to 283
Runs code bin/gstack-render.ts :174
Names a skill plan-design-review :194
Names a skill design-review :194
Runs code bin/gstack-config :202
Involves a person AskUserQuestion :223
Starts an agent subagent :224
Involves a person and wait :229
Runs code bin/gstack-codex-probe :254
Starts an agent Agent tool :279
Starts an agent subagents :279
Starts an agent SUBAGENT :282