Monorepo for Aesthetic.Computer aesthetic.computer
at main 693 lines 23 kB view raw view rendered
1# AC Command Robustness Plan 2 3**Date**: 2025-01-23 4**Author**: GitHub Copilot 5**Status**: Draft 6 7## Problem Statement 8 9### Current Behavior 10The `ac` fish shell function sends HTTP POST requests to `https://localhost:8889/jump` to navigate the VSCode extension's webview panel to different pieces. The command works as expected when: 11- The aesthetic computer extension panel is visible 12- The local dev server (session-server) is running 13- The websocket connection is established 14 15However, it can fail silently when: 16- The VSCode extension sidebar panel is hidden or closed 17- The panel webview hasn't loaded yet 18- The session server hasn't started 19- The websocket connection isn't established 20 21This creates a poor developer experience where running `ac gameboy~melody` from the terminal may appear to do nothing, with no feedback about why. 22 23### Impact on Workflow 24This issue affects the primary development workflow: 251. Developer edits Game Boy ROM code in `kidlisp-gameboy/` 262. Build script (`build.sh`) compiles the ROM successfully 273. Build script auto-runs `fish -c "ac gameboy~$OUTPUT_NAME"` 284. **FAILURE POINT**: If panel not visible, message lost → developer sees no result 295. Developer must manually click sidebar icon, then re-run command 30 31This breaks the smooth "edit → build → test" cycle that's critical for rapid iteration on ROMs. 32 33--- 34 35## Current Implementation 36 37### Message Flow Architecture 38 39``` 40┌──────────────┐ ┌──────────────────┐ ┌─────────────┐ 41│ Fish Shell │ HTTP │ Session Server │ WebSoc │ VSCode │ 42│ ac function │ POST │ localhost:8889 │ ket │ Webview │ 43│ │────────>│ /jump endpoint │────────>│ Panel │ 44└──────────────┘ └──────────────────┘ └─────────────┘ 45``` 46 47### Component Analysis 48 49#### 1. Fish Shell Function 50**Location**: `.devcontainer/config.fish:848-857` 51 52```fish 53function ac --description 'cd to aesthetic-computer or jump to piece' 54 if test (count $argv) -eq 0 55 cd ~/aesthetic-computer 56 else 57 set piece_path $argv[1] 58 echo "🎯 Jumping to: $piece_path" 59 set response (curl -s -k -X POST https://localhost:8889/jump \ 60 -H "Content-Type: application/json" \ 61 -d "{\"piece\": \"$piece_path\"}") 62 echo "$response" 63 end 64end 65``` 66 67**Issues**: 68- ❌ No panel visibility check before sending 69- ❌ No retry logic if POST fails 70- ❌ No validation of response 71- ❌ Silent failure if panel not ready 72- ✅ Simple and fast when working 73 74#### 2. Session Server 75**Location**: `session-server/session.mjs:225-235` 76 77```javascript 78// Jump to a specific piece (navigate) 79fastify.post("/jump", async (req) => { 80 const { piece } = req.body; 81 everyone(pack("jump", { piece }, "pieces")); 82 return { msg: "Jump request sent!", piece }; 83}); 84``` 85 86**Behavior**: 87- Receives POST request 88- Broadcasts `jump` message to all connected websocket clients 89- Returns success regardless of whether clients received it 90- No confirmation that panel actually navigated 91 92#### 3. VSCode Extension Panel 93**Location**: `vscode-extension/extension.ts:716-740, 742-920` 94 95```typescript 96class AestheticViewProvider implements vscode.WebviewViewProvider { 97 public static readonly viewType = "aestheticComputer.sidebarView"; 98 private _view?: vscode.WebviewView; 99 100 public sendMessageToWebview(message: any) { 101 if (this._view && this._view.webview) { 102 this._view.webview.postMessage(message); 103 } 104 } 105 106 public refreshWebview(): void { 107 if (this._view) { 108 const slug = extContext.globalState.get("panel:slug", ""); 109 this._view.title = slug + (local ? " 🧑‍🤝‍🧑" : ""); 110 this._view.webview.html = getWebViewContent(this._view.webview, slug); 111 } 112 } 113 114 public resolveWebviewView( 115 webviewView: vscode.WebviewView, 116 context: vscode.WebviewViewResolveContext<unknown>, 117 _token: vscode.CancellationToken, 118 ): void { 119 this._view = webviewView; 120 // ... initialization ... 121 122 webviewView.webview.onDidReceiveMessage(async (data) => { 123 // Handles messages from webview 124 }); 125 126 webviewView.onDidChangeVisibility(() => { 127 if (!webviewView.visible) { 128 // Panel hidden - no messages received 129 } else { 130 // Panel visible - send focus event 131 webviewView.webview.postMessage({ type: "aesthetic-parent:focused" }); 132 } 133 }); 134 } 135} 136``` 137 138**Issues**: 139- ❌ No method to show panel programmatically 140- ❌ No way to check if panel is visible from fish 141- ❌ No message queue for when panel becomes visible 142- ✅ Well-structured for receiving messages when visible 143 144--- 145 146## Proposed Solutions 147 148### Option A: VSCode Command to Show Panel + Fish Wrapper 149 150**Concept**: Create a VSCode command that shows the panel, then have fish call it before sending the jump message. 151 152**Implementation**: 153 1541. **Extension Side** - Add command to show panel: 155```typescript 156// In extension.ts activate() function 157context.subscriptions.push( 158 vscode.commands.registerCommand("aestheticComputer.showPanel", async () => { 159 await vscode.commands.executeCommand( 160 "workbench.view.extension.aestheticComputer" 161 ); 162 // Wait for panel to initialize 163 return new Promise((resolve) => setTimeout(resolve, 500)); 164 }) 165); 166``` 167 1682. **Fish Side** - Update ac function: 169```fish 170function ac --description 'cd to aesthetic-computer or jump to piece' 171 if test (count $argv) -eq 0 172 cd ~/aesthetic-computer 173 else 174 set piece_path $argv[1] 175 echo "🎯 Jumping to: $piece_path" 176 177 # Show the panel first 178 code --command aestheticComputer.showPanel 179 sleep 0.5 # Give panel time to initialize 180 181 # Then send the jump command 182 set response (curl -s -k -X POST https://localhost:8889/jump \ 183 -H "Content-Type: application/json" \ 184 -d "{\"piece\": \"$piece_path\"}") 185 echo "$response" 186 end 187end 188``` 189 190**Pros**: 191- ✅ Directly addresses root cause (hidden panel) 192- ✅ Uses native VSCode APIs 193- ✅ Panel always visible after command 194- ✅ Works even if websocket not connected yet 195 196**Cons**: 197- ❌ Requires `code` CLI to be available 198- ❌ May not work in all environments (Codespaces, SSH) 199- ❌ Timing issues - hard to know when panel fully loaded 200- ❌ Intrusive - forces panel to show even if user doesn't want it 201 202### Option B: Retry Logic with Exponential Backoff 203 204**Concept**: Keep fish simple, but add retry logic to handle transient failures. 205 206**Implementation**: 207 208```fish 209function ac --description 'cd to aesthetic-computer or jump to piece' 210 if test (count $argv) -eq 0 211 cd ~/aesthetic-computer 212 else 213 set piece_path $argv[1] 214 echo "🎯 Jumping to: $piece_path" 215 216 # Retry up to 5 times with exponential backoff 217 set max_retries 5 218 set retry_count 0 219 set success false 220 221 while test $retry_count -lt $max_retries 222 set response (curl -s -k -X POST https://localhost:8889/jump \ 223 -H "Content-Type: application/json" \ 224 -d "{\"piece\": \"$piece_path\"}") 225 226 # Check if response indicates success 227 if string match -q "*Jump request sent*" $response 228 echo "$response" 229 set success true 230 break 231 end 232 233 set retry_count (math $retry_count + 1) 234 if test $retry_count -lt $max_retries 235 set delay (math "0.5 * (2 ^ $retry_count)") 236 echo "⏳ Retrying in $delay seconds... (attempt $retry_count/$max_retries)" 237 sleep $delay 238 end 239 end 240 241 if not $success 242 echo "❌ Failed to send jump command after $max_retries attempts" 243 echo "💡 Try manually opening the Aesthetic Computer panel in VSCode" 244 return 1 245 end 246 end 247end 248``` 249 250**Pros**: 251- ✅ No changes to extension needed 252- ✅ Handles server startup delay 253- ✅ Handles temporary network issues 254- ✅ Provides clear feedback to user 255 256**Cons**: 257- ❌ Doesn't solve hidden panel issue 258- ❌ Wastes time retrying if panel will never be visible 259- ❌ Can be slow (up to ~30 seconds with backoff) 260- ❌ Still fails silently if panel hidden but server up 261 262### Option C: Extension-Side Message Queue 263 264**Concept**: Have the extension buffer jump messages when panel is hidden, then replay them when it becomes visible. 265 266**Implementation**: 267 2681. **Extension Side** - Add message queue: 269```typescript 270class AestheticViewProvider implements vscode.WebviewViewProvider { 271 private _pendingMessages: any[] = []; 272 273 public sendMessageToWebview(message: any) { 274 if (this._view && this._view.webview && this._view.visible) { 275 this._view.webview.postMessage(message); 276 } else { 277 // Queue message for later 278 this._pendingMessages.push(message); 279 console.log(`📬 Queued message (panel hidden): ${message.type || message}`); 280 } 281 } 282 283 public resolveWebviewView(...) { 284 // ... existing code ... 285 286 webviewView.onDidChangeVisibility(() => { 287 if (webviewView.visible) { 288 // Panel now visible - send queued messages 289 console.log(`📤 Panel visible, sending ${this._pendingMessages.length} queued messages`); 290 this._pendingMessages.forEach(msg => { 291 webviewView.webview.postMessage(msg); 292 }); 293 this._pendingMessages = []; 294 295 webviewView.webview.postMessage({ type: "aesthetic-parent:focused" }); 296 } 297 }); 298 } 299} 300``` 301 3022. **Session Server Side** - Forward to extension: 303```javascript 304// Store reference to extension provider (would need to be set up) 305let extensionProvider; 306 307fastify.post("/jump", async (req) => { 308 const { piece } = req.body; 309 310 // Broadcast via websocket 311 everyone(pack("jump", { piece }, "pieces")); 312 313 // Also send directly to extension if available 314 if (extensionProvider) { 315 extensionProvider.sendMessageToWebview({ 316 type: "jump", 317 piece: piece 318 }); 319 } 320 321 return { msg: "Jump request sent!", piece }; 322}); 323``` 324 325**Pros**: 326- ✅ No changes to fish function needed 327- ✅ Messages never lost - always queued 328- ✅ Works automatically when panel opened 329- ✅ Transparent to user 330 331**Cons**: 332- ❌ Complex to implement - requires IPC between session server and extension 333- ❌ Queue could grow unbounded if panel never opened 334- ❌ User may not realize panel needs to be opened 335- ❌ Messages could arrive out of order if queue processes slowly 336 337### Option D: Hybrid Approach 338 339**Concept**: Combine panel visibility check with smart retry logic. 340 341**Implementation**: Use `code --command` CLI to check panel visibility before sending. 342 343**Pros**: 344- ✅ Best effort panel visibility 345- ✅ Fast retries (< 2 seconds total) 346 347**Cons**: 348-**`code --command` not available in devcontainer/Codespaces** 349- ❌ Creates OS/environment dependencies 350- ❌ Still requires retry logic 351- ❌ Not a complete solution 352 353**Status**: Ruled out - tested and confirmed `code --command` unavailable in devcontainer. 354 355### Option E: Extension as Session Server Client (RECOMMENDED) 356 357**Concept**: The VSCode extension connects directly to the session server as a WebSocket client, receives jump messages directly, and controls its own panel visibility. 358 359**Architecture**: 360``` 361Terminal: Session Server: VSCode Extension: 362┌─────────┐ ┌──────────────┐ ┌────────────────┐ 363│ ac cmd │─── HTTP ────▶│ POST /jump │ │ WebSocket │ 364│ │ POST │ │ │ Client │ 365└─────────┘ │ Broadcast │─── WS ─────▶│ │ 366 │ to all │ "vscode: │ Show Panel │ 367 │ clients │ jump" │ + Navigate │ 368 │ │ │ │ 369Browser: │ │ └────────────────┘ 370┌─────────┐ │ │ 371│ Client │◀─── WS ──────│ │ 372│ │ "jump" │ │ 373└─────────┘ └──────────────┘ 374``` 375 376**Implementation**: 377 3781. **Session Server** (`session-server/session.mjs`): 379```javascript 380const vscodeClients = new Set(); 381 382io.on("connection", (socket) => { 383 socket.on("identify", (data) => { 384 if (data.type === "vscode") { 385 vscodeClients.add(socket); 386 socket.on("disconnect", () => vscodeClients.delete(socket)); 387 console.log("✓ VSCode extension connected"); 388 } 389 }); 390}); 391 392fastify.post("/jump", async (req) => { 393 const { piece } = req.body; 394 395 // Broadcast to browser clients 396 everyone(pack("jump", { piece }, "pieces")); 397 398 // Direct message to VSCode extension clients 399 vscodeClients.forEach(client => { 400 client.emit("vscode:jump", { piece }); 401 }); 402 403 return { 404 msg: "Jump request sent!", 405 piece, 406 vscodeConnected: vscodeClients.size > 0 407 }; 408}); 409``` 410 4112. **Extension** (`vscode-extension/extension.ts`): 412```typescript 413import io from "socket.io-client"; 414 415class AestheticViewProvider { 416 private socket?: any; 417 418 async connectToSessionServer() { 419 const url = local ? "https://localhost:8889" : "https://aesthetic.computer"; 420 this.socket = io(url, { 421 rejectUnauthorized: false, 422 transports: ["websocket"] 423 }); 424 425 this.socket.on("connect", () => { 426 console.log("Connected to session server"); 427 this.socket.emit("identify", { type: "vscode" }); 428 }); 429 430 this.socket.on("vscode:jump", async (data: { piece: string }) => { 431 console.log("Received jump command:", data.piece); 432 433 // Show panel if hidden 434 if (!this._view?.visible) { 435 await vscode.commands.executeCommand( 436 "workbench.view.extension.aestheticComputer" 437 ); 438 // Brief delay for panel to initialize 439 await new Promise(r => setTimeout(r, 300)); 440 } 441 442 // Send jump message to webview 443 this.sendMessageToWebview({ 444 type: "jump", 445 piece: data.piece 446 }); 447 }); 448 449 this.socket.on("disconnect", () => { 450 console.log("Disconnected from session server"); 451 }); 452 } 453} 454 455// In activate(): 456export function activate(context: vscode.ExtensionContext) { 457 const provider = new AestheticViewProvider(context.extensionUri); 458 459 // Connect to session server 460 provider.connectToSessionServer(); 461 462 // ... rest of activation 463} 464``` 465 4663. **Fish Function** (minimal changes): 467```fish 468function ac --description 'cd to aesthetic-computer or jump to piece' 469 if test (count $argv) -eq 0 470 cd ~/aesthetic-computer 471 else 472 set piece_path $argv[1] 473 echo "🎯 Jumping to: $piece_path" 474 475 set response (curl -s -k -X POST https://localhost:8889/jump \ 476 -H "Content-Type: application/json" \ 477 -d "{\"piece\": \"$piece_path\"}") 478 479 if string match -q "*vscodeConnected*true*" $response 480 echo "✅ Sent to VSCode extension" 481 else if string match -q "*Jump request sent*" $response 482 echo "✅ Sent to browser clients" 483 else 484 echo "$response" 485 end 486 end 487end 488``` 489 490**Pros**: 491-**Works in ALL environments** (devcontainer, Codespaces, desktop) 492-**Direct communication** - no polling or retry needed 493-**Extension controls its own UI** - can show panel programmatically 494-**Uses existing infrastructure** - session server already has WebSocket 495-**Clean architecture** - extension is proper session client 496-**Instant feedback** - knows immediately if extension connected 497-**No terminal dependencies** - no `code` CLI required 498-**Supports both targets** - browser clients AND extension 499 500**Cons**: 501- ❌ Requires extension version update (1.191.0) 502- ❌ Users need to update extension 503- ❌ More moving parts (but all well-tested infrastructure) 504 505**Why this is best**: 506This solves the root problem architecturally instead of working around it. The extension becomes a first-class citizen of the session server ecosystem, receiving messages directly instead of relying on WebView visibility. 507 508--- 509 510## Recommendation 511 512**Implement Option E: Extension as Session Server Client** 513 514### Rationale 515 516After thorough analysis and testing, Option E is the clear winner: 517 5181. **Universal compatibility**: Works in devcontainer, Codespaces, and desktop VSCode 5192. **Architectural cleanliness**: Extension is a proper session server client, not a workaround 5203. **Leverages existing infrastructure**: Session server already manages WebSocket connections 5214. **No retry logic needed**: Direct communication with instant feedback 5225. **Extension controls its own UI**: Can show panel programmatically before navigating 5236. **Supports hybrid usage**: Works for both browser clients AND VSCode extension simultaneously 524 525**Why other options don't work**: 526- **Option A/D**: `code --command` unavailable in devcontainer/Codespaces (tested and confirmed) 527- **Option B**: Retry logic can't solve root problem (panel visibility) 528- **Option C**: Queue complexity without solving user experience issue 529 530**The key insight**: Instead of making the terminal try to control VSCode, make VSCode a first-class participant in the session server ecosystem. 531 532### Implementation Plan 533 534**Phase 1: Session Server Updates** (15 minutes) 535 5361. Add VSCode client tracking to `session-server/session.mjs`: 537 - Create `vscodeClients` Set to track extension connections 538 - Add "identify" event handler to recognize VSCode clients 539 - Update `/jump` endpoint to emit "vscode:jump" to extension clients 540 - Return `vscodeConnected` flag in response 541 5422. Test with curl: 543 ```fish 544 curl -k -X POST https://localhost:8889/jump \ 545 -H "Content-Type: application/json" \ 546 -d '{"piece": "gameboy~melody"}' 547 ``` 548 549**Phase 2: Extension Implementation** (30 minutes) 550 5511. Add dependencies to `vscode-extension/package.json`: 552 ```json 553 "dependencies": { 554 "socket.io-client": "^4.7.2" 555 } 556 ``` 557 5582. Update `vscode-extension/extension.ts`: 559 - Import socket.io-client 560 - Add `connectToSessionServer()` method to `AestheticViewProvider` 561 - Emit "identify" with type "vscode" on connection 562 - Listen for "vscode:jump" messages 563 - Show panel if hidden before navigating 564 - Handle connection/disconnection gracefully 565 5663. Update extension version to 1.191.0 in `package.json` 567 5684. Build and test in devcontainer: 569 ```fish 570 cd vscode-extension 571 npm install 572 npm run compile 573 # Test with F5 (Extension Development Host) 574 ``` 575 576**Phase 3: Fish Function Update** (5 minutes) 577 5781. Update `.devcontainer/config.fish` ac function: 579 - Parse response for `vscodeConnected` flag 580 - Show appropriate success message 581 - Minimal changes - no retry logic needed 582 5832. Test scenarios: 584 - `ac gameboy~melody` with panel hidden → should show panel and navigate 585 - `ac gameboy~melody` with panel visible → should navigate immediately 586 - `ac gameboy~melody` with extension not running → should still send to browsers 587 588**Phase 4: Testing & Publishing** (20 minutes) 589 5901. Comprehensive testing: 591 - Panel hidden → jump command → verify panel shows 592 - Panel visible → jump command → verify immediate navigation 593 - Extension not running → verify graceful fallback 594 - Build script auto-launch → verify works in automation 595 - Browser client → verify still receives messages 596 5972. Extension packaging and publishing: 598 ```fish 599 cd vscode-extension 600 vsce package 601 vsce publish 602 ``` 603 6043. Update documentation in README 605 606**Total estimated time**: ~70 minutes 607 608--- 609 610## Alternative Considerations 611 612### Why this beats the CLI approach (Option D)? 613 614The `code --command` approach seemed ideal but: 615- ❌ Not available in VSCode devcontainers 616- ❌ Not available in Codespaces 617- ❌ Creates OS/environment dependencies 618- ❌ Still requires polling/retry logic 619 620The WebSocket approach: 621- ✅ Works in all VSCode environments 622- ✅ Direct, instant communication 623- ✅ Extension can control its own UI 624- ✅ No polling or retry needed 625- ✅ Uses existing session server infrastructure 626 627### Why not just rely on browser clients? 628 629The session server already broadcasts to browser clients, but: 630- Browser clients can't show the VSCode sidebar panel 631- User has to manually open panel before it works 632- No way to provide feedback that panel needs opening 633 634### Why not use VSCode Extension IPC? 635 636VSCode extensions can't directly communicate with terminal processes. The session server acts as the perfect intermediary: 637- Terminal → HTTP POST → Session server → WebSocket → Extension 638- Clean separation of concerns 639- Session server already manages websocket connections 640 641--- 642 643## Success Criteria 644 645After implementation, the `ac` command should: 6461. ✅ Work immediately when panel already visible 6472. ✅ **Show panel automatically if hidden** (extension controls this) 6483. ✅ Work in all VSCode environments (devcontainer, Codespaces, desktop) 6494. ✅ Provide instant feedback about VSCode extension connection status 6505. ✅ Complete navigation within 500ms in success case 6516. ✅ Work in build script auto-launch scenario 6527. ✅ Work in manual terminal usage scenario 6538. ✅ Continue to work with browser clients simultaneously 654 655--- 656 657## Future Improvements 658 659### Extension enhancements 660- Add status bar indicator showing session server connection 661- Add notification when jump command received 662- Add `ac status` command to check connection health 663- Add reconnection logic with exponential backoff 664 665### Session server enhancements 666- Add `/status` endpoint returning connected client counts 667- Add message delivery confirmations 668- Add client presence tracking (last seen timestamp) 669 670### User-facing improvements 671- Add `ac doctor` command to diagnose setup issues 672- Show VSCode notification when jump received while coding elsewhere 673- Add keyboard shortcut to toggle panel visibility 674 675--- 676 677## Appendix: Code Locations 678 679| Component | File | Lines | 680|-----------|------|-------| 681| Fish ac function | `.devcontainer/config.fish` | 848-857 | 682| Session server /jump | `session-server/session.mjs` | 225-235 | 683| Extension provider | `vscode-extension/extension.ts` | 716-920 | 684| Build script auto-launch | `kidlisp-gameboy/build.sh` | 68-74 | 685 686--- 687 688**Next Steps**: 6891. Review this plan with user 6902. Begin Phase 1: Session server implementation 6913. Phase 2: Extension WebSocket client 6924. Phase 3: Fish function update 6935. Phase 4: Testing and publishing extension v1.191.0