645 lines
19 KiB
Text
645 lines
19 KiB
Text
PHASE 2 DESIGN: RICH PROMPTS AND OUTPUT ENHANCEMENTS
|
|
======================================================
|
|
|
|
Overview
|
|
--------
|
|
|
|
Phase 1 delivered basic remote control: Claude Code session wrapping, SSE streaming
|
|
to mobile dashboard, binary prompt approval (y/n only). Phase 2 addresses the most
|
|
critical gaps between claude-remote and the crabigator reference implementation:
|
|
|
|
1. Rich prompt system - multi-option choices, text input, tab instructions
|
|
2. ANSI color parsing in output
|
|
3. Session statistics extraction and display
|
|
4. Git status widget
|
|
5. UI polish - style controls, filtering, auto-scroll
|
|
|
|
This phase transforms claude-remote from a proof-of-concept into a genuinely useful
|
|
remote approval tool. Priority is on prompt system (the blocker for real usage)
|
|
followed by output quality and UX improvements.
|
|
|
|
|
|
PROMPT SYSTEM REDESIGN
|
|
======================
|
|
|
|
The Current Problem
|
|
-------------------
|
|
|
|
Claude Code presents three types of interactive prompts:
|
|
|
|
1. Permission prompts - tool access requests with options like:
|
|
- Yes (allow once)
|
|
- Yes, and allow all future requests for this tool
|
|
- No (deny)
|
|
- Tab to add additional instructions
|
|
|
|
2. Question prompts - AskUserQuestion with multiple choice:
|
|
- Numbered options (1, 2, 3...)
|
|
- Optional "Other" text input
|
|
- Sometimes multiple questions in sequence
|
|
|
|
3. Plan mode exit prompts - after plan generation:
|
|
- Continue with plan
|
|
- Modify plan
|
|
- Cancel
|
|
|
|
Current implementation only sends "y\n" or "n\n" - unusable for real workflows.
|
|
|
|
|
|
Message Types Claude Code Emits
|
|
--------------------------------
|
|
|
|
Based on crabigator's events.rs, Claude Code hooks provide structured prompt data:
|
|
|
|
Permission prompt:
|
|
{
|
|
"type": "prompt",
|
|
"prompt": {
|
|
"prompt_type": "permission",
|
|
"tool_name": "Write",
|
|
"tool_input": { "file_path": "/path/to/file.txt", "content": "..." },
|
|
"options": [
|
|
{ "label": "Yes", "value": "1", "description": "Allow once" },
|
|
{ "label": "Yes, always", "value": "2", "description": "Allow all future" },
|
|
{ "label": "No", "value": "3", "description": "Deny" }
|
|
],
|
|
"allows_tab_instructions": true,
|
|
"selected_option": 1
|
|
}
|
|
}
|
|
|
|
Question prompt:
|
|
{
|
|
"type": "prompt",
|
|
"prompt": {
|
|
"prompt_type": "question",
|
|
"questions": [
|
|
{
|
|
"question": "Which approach should I take?",
|
|
"header": "Implementation choice",
|
|
"options": [
|
|
{ "label": "Use SQLite", "value": "1" },
|
|
{ "label": "Use JSON files", "value": "2" }
|
|
],
|
|
"multi_select": false,
|
|
"allows_other": true
|
|
}
|
|
]
|
|
}
|
|
}
|
|
|
|
Exit plan prompt:
|
|
{
|
|
"type": "prompt",
|
|
"prompt": {
|
|
"prompt_type": "exit_plan",
|
|
"options": [
|
|
{ "label": "Continue with plan", "value": "1" },
|
|
{ "label": "Modify plan", "value": "2" },
|
|
{ "label": "Cancel", "value": "3" }
|
|
]
|
|
}
|
|
}
|
|
|
|
Prompt cleared:
|
|
{
|
|
"type": "prompt",
|
|
"prompt": null
|
|
}
|
|
|
|
|
|
UI for Each Prompt Type
|
|
------------------------
|
|
|
|
Permission prompts:
|
|
- Show tool name and relevant input (file path, command, etc.)
|
|
- Radio button list of options (Yes / Yes always / No)
|
|
- Show which option is currently selected (from selected_option field)
|
|
- If allows_tab_instructions is true, show "Add instructions" button
|
|
- Instructions UI: textarea + submit button, sends key sequence:
|
|
- Navigate to correct option if needed (up/down arrow keys)
|
|
- Press Tab
|
|
- Type instruction text
|
|
- Press Enter
|
|
|
|
Question prompts:
|
|
- Show question header if present
|
|
- Show question text
|
|
- Radio button list for single-select, checkboxes for multi-select
|
|
- If allows_other is true, show "Other" option with text input
|
|
- Submit button sends option number(s) or text input
|
|
|
|
Exit plan prompts:
|
|
- Show plan context (last few lines of output mentioning the plan)
|
|
- Radio button list of options
|
|
- Submit button sends option number
|
|
|
|
General prompt UI patterns:
|
|
- Large touch targets for mobile (min 44px height)
|
|
- Clear visual distinction from regular output
|
|
- Show prompt at top of session card (sticky while scrolling output)
|
|
- Disable interaction if desktop disconnected
|
|
- Show loading state after submission
|
|
- Clear prompt UI when prompt clears (state change from permission/question to ready)
|
|
|
|
|
|
Response Protocol Changes
|
|
--------------------------
|
|
|
|
Current: POST /api/sessions/:id/approve with { approved: boolean }
|
|
Sends "y\n" or "n\n" to PTY
|
|
|
|
New: POST /api/sessions/:id/answer with { response: AnswerResponse }
|
|
|
|
Where AnswerResponse is one of:
|
|
|
|
Simple option selection:
|
|
{
|
|
"type": "option",
|
|
"value": "2" // option number as string
|
|
}
|
|
|
|
Text input (for "Other" in questions):
|
|
{
|
|
"type": "text",
|
|
"value": "User's custom answer here"
|
|
}
|
|
|
|
Tab instructions (for permission prompts):
|
|
{
|
|
"type": "tab_instructions",
|
|
"selected_option": 1, // which option to select first
|
|
"instruction": "Only process files in the src/ directory"
|
|
}
|
|
|
|
Server processing:
|
|
|
|
- Option selection: write "${value}\n" to PTY (e.g., "2\n")
|
|
- Text input: write "${value}\n" to PTY
|
|
- Tab instructions: execute key sequence:
|
|
1. Navigate to selected_option (send up/down arrows)
|
|
2. Wait 100ms
|
|
3. Send Tab key
|
|
4. Wait 100ms
|
|
5. Write instruction text
|
|
6. Send Enter key
|
|
|
|
Key sequence execution needs pty.write() for text and special key codes:
|
|
- Up arrow: "\x1b[A"
|
|
- Down arrow: "\x1b[B"
|
|
- Tab: "\t"
|
|
- Enter: "\n"
|
|
|
|
|
|
OUTPUT IMPROVEMENTS
|
|
===================
|
|
|
|
ANSI Color Parsing
|
|
------------------
|
|
|
|
Current: plain text output, no colors
|
|
Needed: parse ANSI escape codes and render as HTML with colored spans
|
|
|
|
Crabigator uses ansi-to-html conversion in dashboard/js/ansi.ts. For Bun stack,
|
|
options:
|
|
|
|
1. Use ansi-to-html npm package (simple, proven)
|
|
2. Implement minimal parser (only need basic colors, not full xterm)
|
|
3. Use ansi-escapes to strip codes, add classes for common patterns
|
|
|
|
Recommendation: ansi-to-html package for phase 2, optimization later if needed
|
|
|
|
Implementation:
|
|
- Parse ANSI in server before sending via SSE
|
|
- Send HTML with spans: <span class="ansi-red">text</span>
|
|
- CSS classes map to terminal color palette
|
|
- Store raw ANSI in database, parse on read
|
|
|
|
CSS terminal colors:
|
|
.ansi-black { color: #000000; }
|
|
.ansi-red { color: #cd3131; }
|
|
.ansi-green { color: #0dbc79; }
|
|
.ansi-yellow { color: #e5e510; }
|
|
.ansi-blue { color: #2472c8; }
|
|
.ansi-magenta { color: #bc3fbc; }
|
|
.ansi-cyan { color: #11a8cd; }
|
|
.ansi-white { color: #e5e5e5; }
|
|
.ansi-bright-black { color: #666666; }
|
|
(etc. for full 16-color palette plus bold/italic/underline)
|
|
|
|
|
|
Session Stats Extraction
|
|
-------------------------
|
|
|
|
Claude Code emits stats via hooks. Need to parse and display:
|
|
|
|
- Prompts count (user interactions)
|
|
- Completions count (LLM responses)
|
|
- Tools count (tool invocations)
|
|
- Compressions count (context compaction events)
|
|
- Thinking time (cumulative seconds in thinking state)
|
|
- Work time (cumulative seconds in ready/thinking/busy states)
|
|
- Idle time (time since last activity)
|
|
- Current mode (normal / auto_accept / plan)
|
|
- Current model name (claude-opus-4-5-20251101, etc.)
|
|
|
|
Stats arrive via "stats" event type (see crabigator events.rs lines 199-275):
|
|
|
|
{
|
|
"type": "stats",
|
|
"prompts": 5,
|
|
"completions": 3,
|
|
"tools": 12,
|
|
"compressions": 1,
|
|
"thinking_seconds": 45,
|
|
"work_seconds": 180,
|
|
"mode": "normal",
|
|
"model": "claude-opus-4-5-20251101",
|
|
"prompts_changed_at": 1706472000.0,
|
|
"completions_changed_at": 1706471980.0,
|
|
"tool_timestamps": [1706471900.0, 1706471920.0, ...],
|
|
"session_start": 1706471800.0,
|
|
"idle_since": 1706472100.0
|
|
}
|
|
|
|
Stats UI (widget in session card):
|
|
- Single line: "5 prompts | 3 completions | 12 tools | 1 compression"
|
|
- Timing line: "45s thinking | 3m idle"
|
|
- Mode badge: "PLAN MODE" or "AUTO-ACCEPT" if not normal
|
|
- Model name: "opus-4-5" (shortened)
|
|
- Sparkline for tool activity using tool_timestamps (optional phase 2.5)
|
|
|
|
Stats storage:
|
|
- Add columns to sessions table: prompts, completions, tools, compressions,
|
|
thinking_seconds, work_seconds, mode, model, idle_since
|
|
- Update on stats event (high frequency, throttle to every 5s)
|
|
- Show in dashboard session list (for quick scanning)
|
|
|
|
|
|
Git Status Extraction
|
|
----------------------
|
|
|
|
Git status widget shows current branch and file changes. Crabigator extracts this
|
|
by parsing git status output in the PTY stream. For claude-remote, two approaches:
|
|
|
|
Approach A (simple): Git hook in Claude Code
|
|
- Claude Code has git hooks that emit structured git events
|
|
- Listen for git events, store in session state
|
|
- Display in widget
|
|
|
|
Approach B (parse output): Pattern matching
|
|
- Watch PTY output for git commands (git status, git diff)
|
|
- Parse output to extract branch and file list
|
|
- Fragile but works without hook support
|
|
|
|
Recommendation: Approach A if hooks available, Approach B as fallback
|
|
|
|
Git event structure (from crabigator events.rs lines 95-121):
|
|
{
|
|
"type": "git",
|
|
"branch": "main",
|
|
"files": [
|
|
{ "path": "src/index.ts", "status": "M ", "additions": 5, "deletions": 2 },
|
|
{ "path": "README.md", "status": "??", "additions": 0, "deletions": 0 }
|
|
]
|
|
}
|
|
|
|
Git widget UI:
|
|
- Collapsible card below stats, above output
|
|
- Header: branch name with git icon
|
|
- File list: path + status indicator (M/A/D/??) + diff count
|
|
- Color code: M=yellow, A=green, D=red, ??=gray
|
|
- Max 10 files shown, "and N more" if truncated
|
|
- No git data: hide widget entirely
|
|
|
|
|
|
SESSION STATE BADGES
|
|
====================
|
|
|
|
Claude Code sessions have explicit states (see crabigator events.rs lines 13-20):
|
|
- ready: idle, waiting for user input
|
|
- thinking: LLM processing request
|
|
- permission: waiting for tool permission
|
|
- question: waiting for question answer
|
|
- complete: session finished
|
|
- interrupted: user cancelled
|
|
|
|
Current: implicit state inference from prompt presence
|
|
Needed: explicit state tracking and visual badges
|
|
|
|
State event:
|
|
{
|
|
"type": "state",
|
|
"state": "thinking",
|
|
"timestamp": 1706472000000
|
|
}
|
|
|
|
State badge UI (in session card header):
|
|
- ready: gray "READY"
|
|
- thinking: blue "THINKING" with animated dots
|
|
- permission: yellow "PERMISSION REQUIRED"
|
|
- question: yellow "QUESTION"
|
|
- complete: green "COMPLETE"
|
|
- interrupted: red "INTERRUPTED"
|
|
|
|
Badge behavior:
|
|
- Replace connection status indicator
|
|
- Connection status moves to header icon (filled=connected, outline=disconnected)
|
|
- State badge is primary status indicator
|
|
- Animate state transitions (fade between states)
|
|
|
|
|
|
UI ENHANCEMENTS
|
|
===============
|
|
|
|
Style Controls
|
|
--------------
|
|
|
|
Crabigator has comprehensive style popover (see dashboard.ts lines 69-132).
|
|
Priority features for phase 2:
|
|
|
|
High priority:
|
|
- Column layout: 1 / 2 / 3 / Fit (auto-columns based on viewport width)
|
|
- Text size: +/- buttons to scale font size (90% to 140% in 10% steps)
|
|
- Terminal height: +/- buttons to adjust output area (250px to 550px in 50px steps)
|
|
- Text wrap: toggle between wrap and horizontal scroll
|
|
|
|
Medium priority:
|
|
- Widget collapse: toggle for collapsing stats/git widgets
|
|
- Grouping: all sessions vs. by project (group by cwd)
|
|
|
|
Implementation:
|
|
- Store preferences in localStorage (key: claude-remote-prefs)
|
|
- Apply via CSS classes on container element
|
|
- Layout: data-layout="1" | "2" | "3" | "fit"
|
|
- Font size: CSS variable --font-scale
|
|
- Terminal height: CSS variable --terminal-height
|
|
- Wrap: class="terminal-wrap" vs. "terminal-scroll"
|
|
|
|
|
|
Session Filtering
|
|
-----------------
|
|
|
|
Focus mode: view single session, hide others. Useful for multi-session workspaces.
|
|
|
|
UI:
|
|
- Session count button in header (e.g., "3 sessions")
|
|
- Click to open popover with session list
|
|
- Each session: command + cwd + state badge
|
|
- Click session to filter to that session only
|
|
- Filter indicator shows in header: "Viewing 1 session" with X to clear
|
|
- Clear filter button returns to all sessions
|
|
|
|
Implementation:
|
|
- Store filter in component state (not localStorage - session-specific)
|
|
- Filter by session ID in render loop
|
|
- Popover positioned below sessions button
|
|
|
|
|
|
Auto-scroll Control
|
|
-------------------
|
|
|
|
Output auto-scrolls to bottom on new content. User scrolling up should pause
|
|
auto-scroll, with button to re-enable.
|
|
|
|
UI:
|
|
- Pin/unpin button in output footer
|
|
- Unpinned (auto-scroll on): pin icon, tooltip "Pin scroll position"
|
|
- Pinned (auto-scroll off): unpin icon, tooltip "Follow output"
|
|
- Auto-unpin when user scrolls up
|
|
- Auto-pin when user scrolls to bottom
|
|
|
|
Implementation:
|
|
- Track scroll position in output container
|
|
- On scroll event: if scrollTop + clientHeight < scrollHeight - 50, unpin
|
|
- On new content: if unpinned, scroll to bottom
|
|
- Pin button click: toggle unpinned state + scroll to bottom
|
|
|
|
|
|
DATA MODEL CHANGES
|
|
==================
|
|
|
|
Sessions Table Schema
|
|
---------------------
|
|
|
|
Current columns:
|
|
- id (primary key)
|
|
- command
|
|
- cwd
|
|
- started_at
|
|
- ended_at
|
|
- is_active
|
|
|
|
New columns for phase 2:
|
|
|
|
Stats:
|
|
- prompts INTEGER DEFAULT 0
|
|
- completions INTEGER DEFAULT 0
|
|
- tools INTEGER DEFAULT 0
|
|
- compressions INTEGER DEFAULT 0
|
|
- thinking_seconds INTEGER DEFAULT 0
|
|
- work_seconds INTEGER DEFAULT 0
|
|
- mode TEXT DEFAULT 'normal'
|
|
- model TEXT
|
|
- idle_since INTEGER -- unix timestamp
|
|
|
|
State:
|
|
- state TEXT DEFAULT 'ready' -- ready|thinking|permission|question|complete|interrupted
|
|
|
|
Git:
|
|
- git_branch TEXT
|
|
- git_files_json TEXT -- JSON array of file objects
|
|
|
|
Prompt:
|
|
- current_prompt_json TEXT -- serialized CloudPromptData or null
|
|
|
|
Migration:
|
|
- Add columns with ALTER TABLE
|
|
- Default values for existing rows
|
|
- No data loss for old sessions
|
|
|
|
|
|
Stats Storage Approach
|
|
----------------------
|
|
|
|
Stats events arrive frequently (every time a tool runs, every state change).
|
|
Two storage strategies:
|
|
|
|
Strategy A: Update on every event
|
|
- Simple: just UPDATE sessions SET ... WHERE id = ?
|
|
- High write volume, may cause SQLite lock contention
|
|
|
|
Strategy B: Throttle updates
|
|
- Update in-memory session state immediately (for SSE streaming)
|
|
- Persist to SQLite every 5 seconds if changed
|
|
- Trade-off: 5s data loss window on server crash
|
|
- Much lower write volume
|
|
|
|
Recommendation: Strategy B (throttled updates) for phase 2
|
|
|
|
Implementation:
|
|
- Session manager holds in-memory state map: Map<sessionId, SessionState>
|
|
- On stats event: update in-memory state, mark dirty
|
|
- Background interval (5s): persist all dirty sessions
|
|
- On session end: persist immediately
|
|
|
|
|
|
IMPLEMENTATION PHASES
|
|
=====================
|
|
|
|
Break implementation into atomic deliverables. Each phase should be independently
|
|
testable and useful.
|
|
|
|
Phase 2.1: Rich Prompt Foundation
|
|
----------------------------------
|
|
|
|
Goal: Support multi-option prompts without tab instructions yet
|
|
|
|
Tasks:
|
|
1. Define prompt types and response types (TypeScript interfaces)
|
|
2. Update session state to track current_prompt_json
|
|
3. Extend PTY wrapper to emit structured prompt events
|
|
4. Change /approve endpoint to /answer endpoint with response types
|
|
5. Render permission prompts with option list (no tab instructions yet)
|
|
6. Render question prompts with option list (no "Other" yet)
|
|
7. Test: permission prompt with 3 options (yes/yes always/no)
|
|
|
|
Deliverable: Can approve tool permissions by selecting specific option
|
|
|
|
Phase 2.2: Text Input and Tab Instructions
|
|
-------------------------------------------
|
|
|
|
Goal: Complete prompt system with all input types
|
|
|
|
Tasks:
|
|
1. Add "Other" text input for question prompts
|
|
2. Add "Add instructions" button for permission prompts
|
|
3. Implement key sequence generation for tab instructions
|
|
4. Implement key sequence execution in PTY wrapper
|
|
5. Test: question prompt with "Other" option
|
|
6. Test: permission prompt with tab instructions
|
|
|
|
Deliverable: Full prompt interactivity matches desktop Claude Code
|
|
|
|
Phase 2.3: Session States and Stats
|
|
------------------------------------
|
|
|
|
Goal: Track and display session state and statistics
|
|
|
|
Tasks:
|
|
1. Add state/stats columns to sessions table (migration)
|
|
2. Parse and emit state events from PTY output
|
|
3. Parse and emit stats events from PTY output
|
|
4. Update session state in database (throttled writes)
|
|
5. Add state badges to session card headers
|
|
6. Add stats widget to session cards
|
|
7. Test: state transitions visible in UI
|
|
8. Test: stats update as session progresses
|
|
|
|
Deliverable: Rich session metadata visible in dashboard
|
|
|
|
Phase 2.4: ANSI Colors
|
|
----------------------
|
|
|
|
Goal: Colored terminal output
|
|
|
|
Tasks:
|
|
1. Add ansi-to-html dependency
|
|
2. Parse ANSI codes in output before SSE transmission
|
|
3. Add terminal color CSS classes
|
|
4. Update output rendering to preserve HTML spans
|
|
5. Test: colored tool output (git diff, test results)
|
|
|
|
Deliverable: Output looks like terminal, not plain text
|
|
|
|
Phase 2.5: Git Widget
|
|
---------------------
|
|
|
|
Goal: Show git status for sessions in git repositories
|
|
|
|
Tasks:
|
|
1. Add git columns to sessions table (migration)
|
|
2. Parse git events from PTY output (pattern matching or hooks)
|
|
3. Add git widget component to session cards
|
|
4. Update widget on git events
|
|
5. Test: git status visible after git commands
|
|
|
|
Deliverable: Git branch and file changes visible in dashboard
|
|
|
|
Phase 2.6: UI Polish
|
|
--------------------
|
|
|
|
Goal: Style controls and UX improvements
|
|
|
|
Tasks:
|
|
1. Add style popover with column layout options
|
|
2. Add text size +/- controls
|
|
3. Add terminal height +/- controls
|
|
4. Add text wrap toggle
|
|
5. Add session filtering (focus mode)
|
|
6. Add auto-scroll control (pin/unpin)
|
|
7. Persist preferences to localStorage
|
|
8. Test: style changes apply correctly
|
|
9. Test: filter to single session works
|
|
|
|
Deliverable: Dashboard matches crabigator's UX quality
|
|
|
|
|
|
TESTING STRATEGY
|
|
================
|
|
|
|
Each phase needs:
|
|
- Unit tests for new functions (prompt parsing, key sequence generation)
|
|
- Integration tests for API endpoints (answer endpoint with different response types)
|
|
- Manual testing with real Claude Code sessions
|
|
|
|
Key test scenarios:
|
|
- Permission prompt: approve with each option (yes / yes always / no)
|
|
- Permission prompt: add tab instructions
|
|
- Question prompt: select numbered option
|
|
- Question prompt: enter "Other" text
|
|
- State transitions: ready -> thinking -> permission -> ready
|
|
- Stats updates: watch counts increment during session
|
|
- Git status: run git commands, verify widget updates
|
|
- ANSI colors: run command with colored output (git status, test runner)
|
|
- Style controls: change layout, font size, terminal height
|
|
- Session filter: filter to one session, clear filter
|
|
- Auto-scroll: scroll up to pause, scroll down to resume
|
|
|
|
|
|
OPEN QUESTIONS
|
|
==============
|
|
|
|
1. Hook availability: Does Claude Code expose hooks for git/stats/prompts?
|
|
- If yes: use structured events
|
|
- If no: parse PTY output (fragile but workable)
|
|
|
|
2. Key sequence timing: What delays needed between key presses?
|
|
- Start with 100ms, adjust based on testing
|
|
- May need longer delays for slow terminal responses
|
|
|
|
3. Prompt detection: How to reliably detect prompt boundaries in output?
|
|
- Look for specific patterns in PTY output
|
|
- May need escape sequence markers
|
|
- Fallback: timeout-based detection (no output for 500ms = prompt)
|
|
|
|
4. Stats persistence frequency: 5s throttle good enough?
|
|
- Depends on write volume and SQLite performance
|
|
- Monitor DB file size growth
|
|
- May need adaptive throttling (1s when active, 10s when idle)
|
|
|
|
|
|
REFERENCES
|
|
==========
|
|
|
|
Crabigator patterns to steal from:
|
|
- workers/crabigator-api/src/durable-objects/SessionDO.ts (lines 330-419)
|
|
State management, event handling, persistence strategy
|
|
- src/cloud/events.rs (lines 1-741)
|
|
Event type definitions, prompt structures, key sequences
|
|
- workers/crabigator-api/src/dashboard.ts (lines 69-132)
|
|
Style controls UI patterns
|
|
|
|
Claude Code documentation:
|
|
- Prompt hooks: check official docs for hook event structures
|
|
- PTY escape codes: standard ANSI/VT100 sequences
|