chore(skills): add frame0-wireframe skill for UI wireframing

Replaces Frame0 MCP server with portable bash+curl scripts that call
Frame0's local HTTP API directly. Includes low-level API wrapper,
high-level composition helpers with project styling, component library
patterns, and setup guide. Requires Frame0 desktop app running locally.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-23 11:57:00 +01:00
co-authored by Claude Opus 4.6
parent 1d4b9cf299
commit 08dfadb863
6 changed files with 1046 additions and 0 deletions
+161
View File
@@ -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 <title>` | — | 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.
@@ -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
@@ -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)
@@ -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 |
+210
View File
@@ -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
+144
View File
@@ -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