diff --git a/office-hours/SKILL.md b/office-hours/SKILL.md index 83161b8ca..3ba0a0e6e 100644 --- a/office-hours/SKILL.md +++ b/office-hours/SKILL.md @@ -1555,12 +1555,32 @@ SKETCH_FILE="/tmp/gstack-sketch-$(date +%s).html" **Step 3: Render and capture** ```bash -$B goto "file://$SKETCH_FILE" -$B screenshot /tmp/gstack-sketch.png +if [ -n "$B" ] && [ -x "$B" ] && command -v python3 >/dev/null 2>&1; then + SKETCH_DIR=$(dirname "$SKETCH_FILE") + SKETCH_NAME=$(basename "$SKETCH_FILE") + SKETCH_PORT=$(python3 - <<'PY' +import socket +s = socket.socket() +s.bind(("127.0.0.1", 0)) +print(s.getsockname()[1]) +s.close() +PY +) + (cd "$SKETCH_DIR" && python3 -m http.server "$SKETCH_PORT" --bind 127.0.0.1 >/tmp/gstack-sketch-http.log 2>&1) & + SKETCH_HTTP_PID=$! + sleep 1 + if ! $B goto "http://127.0.0.1:$SKETCH_PORT/$SKETCH_NAME" || ! $B screenshot /tmp/gstack-sketch.png; then + echo "Visual sketch render failed while serving $SKETCH_FILE over localhost. The HTML file is still available for manual inspection." + fi + kill "$SKETCH_HTTP_PID" 2>/dev/null || true + wait "$SKETCH_HTTP_PID" 2>/dev/null || true +else + echo "Visual sketch rendering requires the browse binary and python3 for the local HTTP server. Run setup for browse, or install python3, then retry." +fi ``` -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." +Do not use `file://` for the render path. The browse binary blocks local file +navigation, so serve the generated HTML over `127.0.0.1` as shown above. **Step 4: Present and iterate** diff --git a/scripts/resolvers/design.ts b/scripts/resolvers/design.ts index 9f31b3619..88146ae60 100644 --- a/scripts/resolvers/design.ts +++ b/scripts/resolvers/design.ts @@ -487,12 +487,32 @@ SKETCH_FILE="/tmp/gstack-sketch-$(date +%s).html" **Step 3: Render and capture** \`\`\`bash -$B goto "file://$SKETCH_FILE" -$B screenshot /tmp/gstack-sketch.png +if [ -n "$B" ] && [ -x "$B" ] && command -v python3 >/dev/null 2>&1; then + SKETCH_DIR=$(dirname "$SKETCH_FILE") + SKETCH_NAME=$(basename "$SKETCH_FILE") + SKETCH_PORT=$(python3 - <<'PY' +import socket +s = socket.socket() +s.bind(("127.0.0.1", 0)) +print(s.getsockname()[1]) +s.close() +PY +) + (cd "$SKETCH_DIR" && python3 -m http.server "$SKETCH_PORT" --bind 127.0.0.1 >/tmp/gstack-sketch-http.log 2>&1) & + SKETCH_HTTP_PID=$! + sleep 1 + if ! $B goto "http://127.0.0.1:$SKETCH_PORT/$SKETCH_NAME" || ! $B screenshot /tmp/gstack-sketch.png; then + echo "Visual sketch render failed while serving $SKETCH_FILE over localhost. The HTML file is still available for manual inspection." + fi + kill "$SKETCH_HTTP_PID" 2>/dev/null || true + wait "$SKETCH_HTTP_PID" 2>/dev/null || true +else + echo "Visual sketch rendering requires the browse binary and python3 for the local HTTP server. Run setup for browse, or install python3, then retry." +fi \`\`\` -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." +Do not use \`file://\` for the render path. The browse binary blocks local file +navigation, so serve the generated HTML over \`127.0.0.1\` as shown above. **Step 4: Present and iterate** @@ -1154,4 +1174,3 @@ Flat design can strip away useful visual information that signals interactivity. Prioritize ruthlessly: things needed in a hurry go close at hand, everything else a few taps away with an obvious path to get there.`; } - diff --git a/test/skill-validation.test.ts b/test/skill-validation.test.ts index 99d4eb83b..7c448567e 100644 --- a/test/skill-validation.test.ts +++ b/test/skill-validation.test.ts @@ -759,6 +759,12 @@ describe('office-hours skill structure', () => { expect(content).toContain('$B screenshot'); }); + test('renders sketch through localhost instead of file URLs', () => { + expect(content).toContain('python3 -m http.server'); + expect(content).toContain('http://127.0.0.1:$SKETCH_PORT/$SKETCH_NAME'); + expect(content).not.toContain('file://$SKETCH_FILE'); + }); + test('contains rough aesthetic instruction', () => { expect(content).toMatch(/rough|hand-drawn/i); });