diff --git a/.claude/skills/frame0-wireframe/SKILL.md b/.claude/skills/frame0-wireframe/SKILL.md new file mode 100644 index 000000000..6c4125c09 --- /dev/null +++ b/.claude/skills/frame0-wireframe/SKILL.md @@ -0,0 +1,161 @@ +--- +name: frame0-wireframe +description: > + Create and export UI wireframes using Frame0 (local desktop wireframing + app with HTTP API). Use when the user says "create wireframe", "wireframe + this", "mock up the UI", "draw a screen", "UI layout", "wireframe the HUD", + "Frame0", "frame0", "export wireframe", or invokes /frame0-wireframe. + Drives Frame0 via its local HTTP API to create shapes, connectors, and + export pages. Also use when asked to update existing wireframes or export + pages. Requires Frame0 to be running locally. +--- + +# Frame0 Wireframe Generation + +Create UI wireframes by driving Frame0's local HTTP API via bash+curl scripts. +No MCP dependency — the scripts replace the MCP server entirely. + +**Prerequisite:** Frame0 desktop app must be running. The API has no headless +mode. If Frame0 is not available, stop and inform the user. + +## Health Check + +Always check first: + +```bash +.claude/skills/frame0-wireframe/scripts/frame0-cmd.sh health +``` + +If Frame0 is not running, report the failure and point to +`references/setup-guide.md`. Do not attempt to proceed without a healthy +connection. + +## API Pattern + +All Frame0 interaction goes through the bundled scripts. Never call curl +directly. + +- **`frame0-cmd.sh`** — Low-level API wrapper. Maps subcommands to Frame0's + HTTP endpoint at `POST localhost:{port}/execute_command`. +- **`frame0-wireframe.sh`** — High-level composition helpers with project + styling defaults (colors from visual-grammar-v01.md). + +Port default: **58320** (override: `FRAME0_PORT` env var or `--port` flag). + +## Single Wireframe Workflow + +1. **Health check** — `frame0-cmd.sh health` +2. **Create page** — `frame0-wireframe.sh new-page "Screen Name"` +3. **Add components** — use high-level helpers for standard elements: + ```bash + CMD=".claude/skills/frame0-wireframe/scripts/frame0-wireframe.sh" + $CMD button "Confirm" 100 200 + $CMD text-field "Search..." 100 250 300 + $CMD container "Panel Title" 50 50 400 300 + $CMD label "Description text" 60 80 + $CMD divider 50 120 400 + ``` +4. **Fine-grained control** — use `frame0-cmd.sh` for operations not covered + by helpers (connectors, icons, grouping): + ```bash + CMD=".claude/skills/frame0-wireframe/scripts/frame0-cmd.sh" + $CMD create-connector "shape-id-1" "shape-id-2" + $CMD group "shape-id-1" "shape-id-2" "shape-id-3" + $CMD create-icon "search" '{"left":100,"top":100,"width":24,"height":24}' + ``` +5. **Export** — `frame0-wireframe.sh export png docs/design/wireframes/hud/layout-v1.png` + +## High-Level Helpers + +| Command | Default size | Description | +|---------|-------------|-------------| +| `new-page ` | — | Create page with title label | +| `button <label> <x> <y> [w] [h]` | 120x36 | Styled button with accent border | +| `text-field <placeholder> <x> <y> [w] [h]` | 200x32 | Input field with dark fill | +| `container <label> <x> <y> <w> <h>` | — | Labeled panel with border | +| `label <text> <x> <y> [size]` | font 14 | Text label | +| `divider <x> <y> <length> [h\|v]` | horizontal | Separator line | +| `export <format> <output-path>` | — | Export current page (png/svg) | + +## Low-Level Commands + +| Command | Purpose | +|---------|---------| +| `health` | Check if Frame0 is running | +| `exec <namespace:action> <json>` | Execute any raw API command | +| `create-shape <type> <json-props>` | Create rectangle, ellipse, text, etc. | +| `update-shape <id> <json-props>` | Modify shape properties | +| `delete <id...>` | Delete shapes | +| `move <id> <dx> <dy>` | Move shape by pixel offset | +| `group <id...>` | Group shapes | +| `ungroup <id...>` | Ungroup shapes | +| `create-connector <tail> <head>` | Connect two shapes | +| `create-icon <name> <json-props>` | Add icon | +| `add-page <json-props>` | Add page | +| `get-page [id]` | Get current/specific page | +| `list-pages` | List all pages | +| `set-page <id>` | Set current page | +| `export <page-id> <format>` | Export as PNG/SVG | + +## Project Styling Defaults + +Colors from `docs/design/visual-grammar-v01.md`: + +| Role | Hex | Source | +|------|-----|--------| +| Background | `#1a1e24` | Zone 1 floor | +| Stroke | `#333340` | Outline standard | +| Fill | `#2a3040` | Zone 1 wall | +| Text | `#c8d0e0` | Insert chrome | +| Accent | `#c8d8f0` | Zone 1 fixture light | + +## Export Convention + +``` +docs/design/wireframes/ + hud/ # HUD layout wireframes + menus/ # Menu screen wireframes + dialogue/ # Dialogue box wireframes + insert/ # Neural insert wireframes +``` + +## Composition Workflow + +For multi-element wireframes: + +1. Read `references/component-library.md` for the matching UI pattern +2. Adapt dimensions and positions to the wireframe layout +3. Group related elements: `frame0-cmd.sh group <id1> <id2> ...` +4. Add connectors for navigation flow: `frame0-cmd.sh create-connector` +5. Export the finished wireframe + +## Multi-Page Wireframes + +Create wireframe sets showing multiple states or screens: + +```bash +CMD=".claude/skills/frame0-wireframe/scripts/frame0-wireframe.sh" +$CMD new-page "HUD - Normal" +# ... add components ... +$CMD new-page "HUD - Alert" +# ... add components ... +$CMD new-page "HUD - Combat" +# ... add components ... +``` + +## Agent Guidance + +- **Araminta** — Primary user. Full wireframe creation, layout iteration, + visual consistency. Uses all component library patterns. +- **Tyre** — Interface architecture wireframes. System boundary diagrams + showing where UI connects to game systems. +- **Qatux** — Export wireframes for UI decision records and documentation. + +## References + +- `references/api-reference.md` — Full Frame0 HTTP API command and property + reference. Read for fine-grained control beyond helpers. +- `references/component-library.md` — Pre-built wireframe patterns (HUD, + dialogue, menus, modals, lists, inventory). Read when starting a wireframe. +- `references/setup-guide.md` — Frame0 installation and startup for Fedora. + Read if Frame0 is not installed or not running. diff --git a/.claude/skills/frame0-wireframe/references/api-reference.md b/.claude/skills/frame0-wireframe/references/api-reference.md new file mode 100644 index 000000000..38546271c --- /dev/null +++ b/.claude/skills/frame0-wireframe/references/api-reference.md @@ -0,0 +1,285 @@ +# Frame0 HTTP API Reference + +Frame0 exposes a local HTTP API when the desktop app is running. + +## Endpoint + +``` +POST http://localhost:{port}/execute_command +Content-Type: application/json +``` + +Default port: **58320** (override via `FRAME0_PORT` env var or `--port` flag). + +## Request Format + +```json +{ + "command": "namespace:action", + "args": { ... } +} +``` + +## Response Format + +```json +{ + "success": true, + "data": { ... } +} +``` + +On error: + +```json +{ + "success": false, + "error": "Error description" +} +``` + +--- + +## Commands by Namespace + +### shape — Shape Creation and Manipulation + +#### shape:create-shape + +Create a new shape on the current page. + +```json +{ + "command": "shape:create-shape", + "args": { + "type": "rectangle", + "shapeProps": { + "name": "my-button", + "left": 100, + "top": 200, + "width": 120, + "height": 36, + "fillColor": "#2a3040", + "strokeColor": "#333340", + "fontColor": "#c8d0e0", + "fontSize": 14, + "text": "Button Label", + "corners": [4, 4, 4, 4] + } + } +} +``` + +**Shape types:** `rectangle`, `ellipse`, `triangle`, `diamond`, `line`, `text`, `image`, `icon` + +#### shape:update-shape + +```json +{ + "command": "shape:update-shape", + "args": { + "shapeId": "shape-id", + "shapeProps": { + "fillColor": "#1a1e24", + "text": "Updated Label" + } + } +} +``` + +#### shape:move + +```json +{ + "command": "shape:move", + "args": { + "shapeId": "shape-id", + "dx": 50, + "dy": -20 + } +} +``` + +#### shape:group / shape:ungroup + +```json +{ + "command": "shape:group", + "args": { + "shapeIdArray": ["id-1", "id-2", "id-3"] + } +} +``` + +#### shape:create-connector + +```json +{ + "command": "shape:create-connector", + "args": { + "tailId": "source-shape-id", + "headId": "target-shape-id", + "shapeProps": { + "strokeColor": "#c8d8f0" + } + } +} +``` + +#### shape:create-icon + +```json +{ + "command": "shape:create-icon", + "args": { + "iconName": "search", + "shapeProps": { + "left": 100, + "top": 100, + "width": 24, + "height": 24, + "strokeColor": "#c8d0e0" + } + } +} +``` + +Icon sizes: 16, 24, 32, 48 pixels. + +#### shape:create-image + +```json +{ + "command": "shape:create-image", + "args": { + "mimeType": "image/png", + "imageData": "base64-encoded-data", + "shapeProps": { + "left": 100, + "top": 100, + "width": 200, + "height": 150 + } + } +} +``` + +### edit — Editing Operations + +#### edit:delete + +```json +{ + "command": "edit:delete", + "args": { + "shapeIdArray": ["id-1", "id-2"] + } +} +``` + +### page — Page Management + +#### page:add + +```json +{ + "command": "page:add", + "args": { + "pageProps": { + "name": "HUD Layout" + } + } +} +``` + +#### page:get + +```json +{ + "command": "page:get", + "args": { + "pageId": "page-id" + } +} +``` + +Omit `pageId` to get current page. + +#### page:get-all + +```json +{ + "command": "page:get-all", + "args": {} +} +``` + +#### page:set-current + +```json +{ + "command": "page:set-current", + "args": { + "pageId": "page-id" + } +} +``` + +#### page:delete + +```json +{ + "command": "page:delete", + "args": { + "pageId": "page-id" + } +} +``` + +### file — Export + +#### file:export-image + +```json +{ + "command": "file:export-image", + "args": { + "pageId": "page-id", + "format": "png", + "fillBackground": true + } +} +``` + +Formats: `png`, `svg` + +Optional: `"shapeIdArray": ["id-1"]` to export specific shapes only. + +--- + +## Shape Properties Reference + +| Property | Type | Notes | +|----------|------|-------| +| `name` | string | Shape identifier/label | +| `left` | number | X position in pixels (origin: top-left) | +| `top` | number | Y position in pixels | +| `width` | number | Width in pixels | +| `height` | number | Height in pixels | +| `fillColor` | string | Hex color (e.g., `"#2a3040"`) | +| `strokeColor` | string | Hex color for border | +| `fontColor` | string | Hex color for text | +| `fontSize` | number | Font size in pixels | +| `text` | string | Text content (for shapes with text) | +| `wordWrap` | boolean | Enable text word wrapping | +| `corners` | number[4] | Border radius [topLeft, topRight, bottomRight, bottomLeft] | +| `path` | array | Coordinate pairs for lines/polygons | +| `lineType` | string | Line style | +| `tailEndType` | string | Arrow tail type | +| `headEndType` | string | Arrow head type | + +## Coordinate System + +- Origin: top-left corner of the canvas +- Units: pixels +- X increases rightward, Y increases downward diff --git a/.claude/skills/frame0-wireframe/references/component-library.md b/.claude/skills/frame0-wireframe/references/component-library.md new file mode 100644 index 000000000..24151f5e2 --- /dev/null +++ b/.claude/skills/frame0-wireframe/references/component-library.md @@ -0,0 +1,193 @@ +# Component Library + +Pre-built wireframe patterns for The Settled Reach UI. Each pattern provides +`frame0-wireframe.sh` command sequences. Colors from visual-grammar-v01.md. + +--- + +## 1. HUD Layout + +The main gameplay overlay. Minimap top-right, monologue bottom-center, +insert display bottom-left. + +```bash +CMD=".claude/skills/frame0-wireframe/scripts/frame0-wireframe.sh" + +$CMD new-page "HUD Layout" + +# Minimap (top-right) +$CMD container "Minimap" 880 20 240 240 + +# Monologue panel (bottom-center) +$CMD container "Monologue" 300 680 520 80 +$CMD label "Internal monologue text appears here..." 310 700 + +# Insert display (bottom-left) +$CMD container "Insert Display" 20 600 260 160 +$CMD label "Neural Insert Data" 30 620 + +# Action hints (bottom-right) +$CMD container "Action Hints" 880 700 240 60 +$CMD label "[E] Interact [TAB] Insert" 890 720 12 +``` + +**Viewport assumption:** 1140x780 (matching Godot project viewport). + +--- + +## 2. Dialogue Box + +Speaker panel with response options. Anchored bottom-center during dialogue mode. + +```bash +CMD=".claude/skills/frame0-wireframe/scripts/frame0-wireframe.sh" + +$CMD new-page "Dialogue Box" + +# Dialogue container +$CMD container "Dialogue" 170 500 800 260 + +# Speaker name +$CMD label "LERA KONSTANTIN" 190 520 16 + +# Dialogue text area +$CMD container "Text Area" 190 550 760 100 +$CMD label "You look like you could use a drink. First time on the station?" 200 560 + +# Response options +$CMD button "[1] Ask about the station" 190 670 370 30 +$CMD button "[2] Ask about recent events" 190 710 370 30 +$CMD button "[3] Leave" 580 670 180 30 +``` + +--- + +## 3. Menu Screen + +Full-screen menu with sidebar navigation and content area. + +```bash +CMD=".claude/skills/frame0-wireframe/scripts/frame0-wireframe.sh" + +$CMD new-page "Pause Menu" + +# Background overlay +$CMD container "Menu Background" 0 0 1140 780 + +# Sidebar navigation +$CMD container "Navigation" 20 20 200 740 +$CMD button "Inventory" 30 40 180 36 +$CMD button "Journal" 30 86 180 36 +$CMD button "Map" 30 132 180 36 +$CMD button "Settings" 30 178 180 36 +$CMD button "Resume" 30 720 180 36 + +# Content area +$CMD container "Content" 240 20 880 740 +$CMD label "Content area" 260 40 +``` + +--- + +## 4. Modal Dialog + +Centered overlay for confirmations, alerts, choices. + +```bash +CMD=".claude/skills/frame0-wireframe/scripts/frame0-wireframe.sh" + +$CMD new-page "Modal Dialog" + +# Overlay background (semi-transparent implied) +$CMD container "Overlay" 0 0 1140 780 + +# Modal panel (centered) +$CMD container "Modal" 320 240 500 300 + +# Title +$CMD label "Confirm Action" 340 260 18 + +# Divider +$CMD divider 340 290 460 + +# Body text +$CMD label "Are you sure you want to proceed?" 340 310 +$CMD label "This action cannot be undone." 340 340 + +# Action buttons +$CMD button "Cancel" 480 480 120 36 +$CMD button "Confirm" 620 480 120 36 +``` + +--- + +## 5. List View + +Scrollable list with item selection and detail panel. + +```bash +CMD=".claude/skills/frame0-wireframe/scripts/frame0-wireframe.sh" + +$CMD new-page "List View" + +# List panel +$CMD container "Item List" 20 20 400 740 + +# List items (repeating pattern) +$CMD button "Item Alpha" 30 30 380 40 +$CMD button "Item Beta" 30 80 380 40 +$CMD button "Item Gamma" 30 130 380 40 +$CMD button "Item Delta" 30 180 380 40 + +# Detail panel +$CMD container "Detail" 440 20 680 740 +$CMD label "Item Alpha" 460 40 18 +$CMD divider 460 70 640 +$CMD label "Description and properties appear here." 460 90 +``` + +--- + +## 6. Inventory Grid + +Grid of cells for item management. + +```bash +CMD=".claude/skills/frame0-wireframe/scripts/frame0-wireframe.sh" + +$CMD new-page "Inventory Grid" + +# Grid container +$CMD container "Inventory" 240 100 660 580 + +# Grid header +$CMD label "INVENTORY" 260 120 18 + +# Grid cells (4x4 example, 64px each with 8px gaps) +# Row 1 +$CMD container "" 260 160 64 64 +$CMD container "" 332 160 64 64 +$CMD container "" 404 160 64 64 +$CMD container "" 476 160 64 64 + +# Row 2 +$CMD container "" 260 232 64 64 +$CMD container "" 332 232 64 64 +$CMD container "" 404 232 64 64 +$CMD container "" 476 232 64 64 + +# Selected item detail +$CMD container "Item Detail" 580 160 300 400 +$CMD label "Selected Item Name" 600 180 16 +$CMD divider 600 210 260 +$CMD label "Item description and stats" 600 230 +``` + +--- + +## Viewport and Grid + +- **Project viewport:** 1140x780 (from Godot project settings) +- **Grid unit:** 8px (for consistent spacing) +- **Minimum touch target:** 36px height for interactive elements +- **Font sizes:** 12 (small/label), 14 (body), 16 (subtitle), 18 (heading), 24 (title) diff --git a/.claude/skills/frame0-wireframe/references/setup-guide.md b/.claude/skills/frame0-wireframe/references/setup-guide.md new file mode 100644 index 000000000..c0f56abd7 --- /dev/null +++ b/.claude/skills/frame0-wireframe/references/setup-guide.md @@ -0,0 +1,53 @@ +# Frame0 Setup Guide + +## Installation (Fedora) + +Download from https://frame0.app/download and install the RPM: + +```bash +sudo dnf install ./frame0-*.x86_64.rpm +``` + +Requires: Fedora 40 or later (x86_64). + +## Starting Frame0 + +Launch the desktop application: + +```bash +frame0 & +``` + +Frame0 exposes an HTTP API at `localhost:58320` when running. + +## Verify API Access + +```bash +.claude/skills/frame0-wireframe/scripts/frame0-cmd.sh health +``` + +Expected output: `Frame0 is running on port 58320` + +## Port Configuration + +Default port: **58320** + +To use a different port, set the environment variable: + +```bash +export FRAME0_PORT=58321 +``` + +Or pass `--port` to any script: + +```bash +.claude/skills/frame0-wireframe/scripts/frame0-cmd.sh --port 58321 health +``` + +## Troubleshooting + +| Symptom | Cause | Fix | +|---------|-------|-----| +| "Connection refused" | Frame0 not running | Start the desktop app | +| "Port in use" | Another instance running | Close duplicate or use different port | +| Script hangs | API unresponsive | Restart Frame0 | diff --git a/.claude/skills/frame0-wireframe/scripts/frame0-cmd.sh b/.claude/skills/frame0-wireframe/scripts/frame0-cmd.sh new file mode 100755 index 000000000..ab2186226 --- /dev/null +++ b/.claude/skills/frame0-wireframe/scripts/frame0-cmd.sh @@ -0,0 +1,210 @@ +#!/bin/bash +set -euo pipefail + +PORT="${FRAME0_PORT:-58320}" +ENDPOINT="http://localhost:${PORT}/execute_command" + +usage() { + cat <<EOF +Usage: $(basename "$0") <command> [args...] [--port N] + +Low-level Frame0 HTTP API wrapper. Replaces the MCP server with direct +curl calls. Requires Frame0 desktop app to be running. + +Commands: + health Check if Frame0 is running + exec <namespace:action> <json> Execute a raw API command + create-shape <type> <json-props> Create a shape (rectangle, ellipse, text, etc.) + update-shape <id> <json-props> Update shape properties + delete <id> [id...] Delete shapes by ID + move <id> <dx> <dy> Move a shape by pixel offset + group <id> [id...] Group shapes + ungroup <id> [id...] Ungroup shapes + create-connector <tail-id> <head-id> [json-props] Connect two shapes + create-icon <name> <json-props> Create an icon shape + add-page <json-props> Add a new page + get-page [page-id] Get current or specific page info + list-pages List all pages + set-page <page-id> Set current page + export <page-id> <format> Export page (png/svg) + +Options: + --port N Frame0 API port (default: $PORT, env: FRAME0_PORT) + +Examples: + $(basename "$0") health + $(basename "$0") create-shape rectangle '{"name":"btn","left":100,"top":100,"width":120,"height":36}' + $(basename "$0") list-pages + $(basename "$0") export page-id png +EOF + exit 1 +} + +# Parse --port from anywhere in args +ARGS=() +while [[ $# -gt 0 ]]; do + case "$1" in + --port) PORT="$2"; ENDPOINT="http://localhost:${PORT}/execute_command"; shift 2 ;; + *) ARGS+=("$1"); shift ;; + esac +done +set -- "${ARGS[@]+"${ARGS[@]}"}" + +[[ $# -lt 1 ]] && usage + +# Execute a Frame0 API command, return data or error +frame0_exec() { + local command="$1" + local args="${2:-\{\}}" + + local response + response=$(curl -s -w "\n%{http_code}" -X POST "$ENDPOINT" \ + -H "Content-Type: application/json" \ + -d "{\"command\": \"$command\", \"args\": $args}" 2>&1) || { + echo "ERROR: Cannot connect to Frame0 at localhost:$PORT" >&2 + echo "Is Frame0 running? See: .claude/skills/frame0-wireframe/references/setup-guide.md" >&2 + return 1 + } + + local http_code body + http_code=$(echo "$response" | tail -1) + body=$(echo "$response" | sed '$d') + + if [[ "$http_code" != 2* ]]; then + echo "ERROR: HTTP $http_code from Frame0" >&2 + echo "$body" >&2 + return 1 + fi + + # Parse success/error from response + python3 -c " +import sys, json +try: + r = json.loads(sys.stdin.read()) + if r.get('success'): + d = r.get('data') + if d is not None: + print(json.dumps(d, indent=2)) + else: + print('ERROR: ' + str(r.get('error', 'Unknown error')), file=sys.stderr) + sys.exit(1) +except json.JSONDecodeError as e: + print(f'ERROR: Invalid JSON response: {e}', file=sys.stderr) + sys.exit(1) +" <<< "$body" +} + +# Build JSON array from remaining args +ids_to_json_array() { + local arr="[" + local first=true + for id in "$@"; do + [[ "$first" == true ]] && first=false || arr+="," + arr+="\"$id\"" + done + arr+="]" + echo "$arr" +} + +CMD="${1:-}" +shift || true + +case "$CMD" in + health) + if curl -s -o /dev/null -w "%{http_code}" "http://localhost:${PORT}/" 2>/dev/null | grep -q "^[23]"; then + echo "Frame0 is running on port $PORT" + else + echo "Frame0 is NOT running on port $PORT" >&2 + echo "Start Frame0 desktop app, then retry." >&2 + echo "See: .claude/skills/frame0-wireframe/references/setup-guide.md" >&2 + exit 1 + fi + ;; + + exec) + [[ $# -lt 2 ]] && { echo "Usage: exec <command> <json-args>" >&2; exit 1; } + frame0_exec "$1" "$2" + ;; + + create-shape) + [[ $# -lt 2 ]] && { echo "Usage: create-shape <type> <json-props>" >&2; exit 1; } + local_type="$1" + local_props="$2" + frame0_exec "shape:create-shape" "{\"type\": \"$local_type\", \"shapeProps\": $local_props}" + ;; + + update-shape) + [[ $# -lt 2 ]] && { echo "Usage: update-shape <id> <json-props>" >&2; exit 1; } + frame0_exec "shape:update-shape" "{\"shapeId\": \"$1\", \"shapeProps\": $2}" + ;; + + delete) + [[ $# -lt 1 ]] && { echo "Usage: delete <id> [id...]" >&2; exit 1; } + local_arr=$(ids_to_json_array "$@") + frame0_exec "edit:delete" "{\"shapeIdArray\": $local_arr}" + ;; + + move) + [[ $# -lt 3 ]] && { echo "Usage: move <id> <dx> <dy>" >&2; exit 1; } + frame0_exec "shape:move" "{\"shapeId\": \"$1\", \"dx\": $2, \"dy\": $3}" + ;; + + group) + [[ $# -lt 1 ]] && { echo "Usage: group <id> [id...]" >&2; exit 1; } + local_arr=$(ids_to_json_array "$@") + frame0_exec "shape:group" "{\"shapeIdArray\": $local_arr}" + ;; + + ungroup) + [[ $# -lt 1 ]] && { echo "Usage: ungroup <id> [id...]" >&2; exit 1; } + local_arr=$(ids_to_json_array "$@") + frame0_exec "shape:ungroup" "{\"shapeIdArray\": $local_arr}" + ;; + + create-connector) + [[ $# -lt 2 ]] && { echo "Usage: create-connector <tail-id> <head-id> [json-props]" >&2; exit 1; } + local_props="${3:-\{\}}" + frame0_exec "shape:create-connector" "{\"tailId\": \"$1\", \"headId\": \"$2\", \"shapeProps\": $local_props}" + ;; + + create-icon) + [[ $# -lt 2 ]] && { echo "Usage: create-icon <name> <json-props>" >&2; exit 1; } + frame0_exec "shape:create-icon" "{\"iconName\": \"$1\", \"shapeProps\": $2}" + ;; + + add-page) + [[ $# -lt 1 ]] && { echo "Usage: add-page <json-props>" >&2; exit 1; } + frame0_exec "page:add" "{\"pageProps\": $1}" + ;; + + get-page) + if [[ $# -ge 1 ]]; then + frame0_exec "page:get" "{\"pageId\": \"$1\"}" + else + frame0_exec "page:get" "{}" + fi + ;; + + list-pages) + frame0_exec "page:get-all" "{}" + ;; + + set-page) + [[ $# -lt 1 ]] && { echo "Usage: set-page <page-id>" >&2; exit 1; } + frame0_exec "page:set-current" "{\"pageId\": \"$1\"}" + ;; + + export) + [[ $# -lt 2 ]] && { echo "Usage: export <page-id> <format>" >&2; exit 1; } + frame0_exec "file:export-image" "{\"pageId\": \"$1\", \"format\": \"$2\", \"fillBackground\": true}" + ;; + + --help|-h|help) + usage + ;; + + *) + echo "Unknown command: $CMD" >&2 + usage + ;; +esac diff --git a/.claude/skills/frame0-wireframe/scripts/frame0-wireframe.sh b/.claude/skills/frame0-wireframe/scripts/frame0-wireframe.sh new file mode 100755 index 000000000..e6a319c82 --- /dev/null +++ b/.claude/skills/frame0-wireframe/scripts/frame0-wireframe.sh @@ -0,0 +1,144 @@ +#!/bin/bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +CMD="$SCRIPT_DIR/frame0-cmd.sh" + +# Project wireframe defaults from docs/design/visual-grammar-v01.md +BG="#1a1e24" # Zone 1 floor +STROKE="#333340" # Outline standard +FILL="#2a3040" # Zone 1 wall +TEXT="#c8d0e0" # Insert chrome +ACCENT="#c8d8f0" # Zone 1 fixture light +FONT_SIZE=14 +TITLE_SIZE=24 + +usage() { + cat <<EOF +Usage: $(basename "$0") <command> [args...] + +High-level Frame0 wireframe composition helpers. Builds on frame0-cmd.sh +with project-appropriate defaults (colors from visual-grammar-v01.md). + +Commands: + new-page <title> Create a new page with title label + button <label> <x> <y> [w] [h] Labeled button (default 120x36) + text-field <placeholder> <x> <y> [w] [h] Text input field (default 200x32) + container <label> <x> <y> <w> <h> Labeled panel/container + label <text> <x> <y> [font-size] Text label + divider <x> <y> <length> [h|v] Divider line (default horizontal) + export <format> <output-path> Export current page (png/svg) + +Environment: + FRAME0_PORT Frame0 API port (default: 58320) + +Examples: + $(basename "$0") new-page "HUD Layout v1" + $(basename "$0") button "Confirm" 100 200 + $(basename "$0") container "Inventory Panel" 50 50 300 400 + $(basename "$0") export png docs/design/wireframes/hud/hud-v1.png +EOF + exit 1 +} + +[[ $# -lt 1 ]] && usage + +# Ensure Frame0 is running before any operation +check_health() { + "$CMD" health > /dev/null 2>&1 || { + echo "ERROR: Frame0 is not running. Start the desktop app first." >&2 + exit 1 + } +} + +SUBCMD="$1" +shift + +case "$SUBCMD" in + new-page) + [[ $# -lt 1 ]] && { echo "Usage: new-page <title>" >&2; exit 1; } + check_health + local_title="$1" + echo "Creating page: $local_title" + "$CMD" add-page "{\"name\": \"$local_title\"}" + # Add title label at top of page + "$CMD" create-shape text "{\"name\": \"page-title\", \"left\": 20, \"top\": 10, \"width\": 500, \"height\": 40, \"text\": \"$local_title\", \"fontSize\": $TITLE_SIZE, \"fontColor\": \"$TEXT\"}" + echo "Page created: $local_title" + ;; + + button) + [[ $# -lt 3 ]] && { echo "Usage: button <label> <x> <y> [w] [h]" >&2; exit 1; } + check_health + local_label="$1" local_x="$2" local_y="$3" + local_w="${4:-120}" local_h="${5:-36}" + "$CMD" create-shape rectangle "{\"name\": \"$local_label\", \"left\": $local_x, \"top\": $local_y, \"width\": $local_w, \"height\": $local_h, \"fillColor\": \"$FILL\", \"strokeColor\": \"$ACCENT\", \"fontColor\": \"$TEXT\", \"fontSize\": $FONT_SIZE, \"text\": \"$local_label\", \"corners\": [4,4,4,4]}" + ;; + + text-field) + [[ $# -lt 3 ]] && { echo "Usage: text-field <placeholder> <x> <y> [w] [h]" >&2; exit 1; } + check_health + local_label="$1" local_x="$2" local_y="$3" + local_w="${4:-200}" local_h="${5:-32}" + "$CMD" create-shape rectangle "{\"name\": \"$local_label\", \"left\": $local_x, \"top\": $local_y, \"width\": $local_w, \"height\": $local_h, \"fillColor\": \"$BG\", \"strokeColor\": \"$STROKE\", \"fontColor\": \"$TEXT\", \"fontSize\": $FONT_SIZE, \"text\": \"$local_label\", \"corners\": [2,2,2,2]}" + ;; + + container) + [[ $# -lt 5 ]] && { echo "Usage: container <label> <x> <y> <w> <h>" >&2; exit 1; } + check_health + local_label="$1" local_x="$2" local_y="$3" local_w="$4" local_h="$5" + # Container frame + "$CMD" create-shape rectangle "{\"name\": \"$local_label\", \"left\": $local_x, \"top\": $local_y, \"width\": $local_w, \"height\": $local_h, \"fillColor\": \"$BG\", \"strokeColor\": \"$STROKE\", \"fontSize\": 12, \"fontColor\": \"$TEXT\"}" + # Container label at top-left inside + label_x=$((local_x + 8)) + label_y=$((local_y + 4)) + "$CMD" create-shape text "{\"name\": \"${local_label}-label\", \"left\": $label_x, \"top\": $label_y, \"width\": 200, \"height\": 20, \"text\": \"$local_label\", \"fontSize\": 12, \"fontColor\": \"$STROKE\"}" + ;; + + label) + [[ $# -lt 3 ]] && { echo "Usage: label <text> <x> <y> [font-size]" >&2; exit 1; } + check_health + local_text="$1" local_x="$2" local_y="$3" + local_size="${4:-$FONT_SIZE}" + "$CMD" create-shape text "{\"name\": \"$local_text\", \"left\": $local_x, \"top\": $local_y, \"width\": 300, \"height\": 24, \"text\": \"$local_text\", \"fontSize\": $local_size, \"fontColor\": \"$TEXT\"}" + ;; + + divider) + [[ $# -lt 3 ]] && { echo "Usage: divider <x> <y> <length> [h|v]" >&2; exit 1; } + check_health + local_x="$1" local_y="$2" local_len="$3" + local_dir="${4:-h}" + if [[ "$local_dir" == "v" ]]; then + "$CMD" create-shape line "{\"name\": \"divider\", \"left\": $local_x, \"top\": $local_y, \"width\": 1, \"height\": $local_len, \"strokeColor\": \"$STROKE\"}" + else + "$CMD" create-shape line "{\"name\": \"divider\", \"left\": $local_x, \"top\": $local_y, \"width\": $local_len, \"height\": 1, \"strokeColor\": \"$STROKE\"}" + fi + ;; + + export) + [[ $# -lt 2 ]] && { echo "Usage: export <format> <output-path>" >&2; exit 1; } + check_health + local_format="$1" local_output="$2" + + # Get current page ID + local_page_id=$("$CMD" get-page | python3 -c "import sys,json; print(json.load(sys.stdin).get('id',''))" 2>/dev/null) + if [[ -z "$local_page_id" ]]; then + echo "ERROR: Could not determine current page ID" >&2 + exit 1 + fi + + # Ensure output directory exists + mkdir -p "$(dirname "$local_output")" + + "$CMD" export "$local_page_id" "$local_format" + echo "Exported: $local_output" + ;; + + --help|-h|help) + usage + ;; + + *) + echo "Unknown command: $SUBCMD" >&2 + usage + ;; +esac