Monorepo for Aesthetic.Computer
aesthetic.computer
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