Monorepo for Aesthetic.Computer aesthetic.computer
core plans ac-command-robustness.md
23 kB

AC Command Robustness Plan #

Date: 2025-01-23
Author: GitHub Copilot
Status: Draft

Problem Statement #

Current Behavior #

The 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:

  • The aesthetic computer extension panel is visible
  • The local dev server (session-server) is running
  • The websocket connection is established

However, it can fail silently when:

  • The VSCode extension sidebar panel is hidden or closed
  • The panel webview hasn't loaded yet
  • The session server hasn't started
  • The websocket connection isn't established

This creates a poor developer experience where running ac gameboy~melody from the terminal may appear to do nothing, with no feedback about why.

Impact on Workflow #

This issue affects the primary development workflow:

  1. Developer edits Game Boy ROM code in kidlisp-gameboy/
  2. Build script (build.sh) compiles the ROM successfully
  3. Build script auto-runs fish -c "ac gameboy~$OUTPUT_NAME"
  4. FAILURE POINT: If panel not visible, message lost โ†’ developer sees no result
  5. Developer must manually click sidebar icon, then re-run command

This breaks the smooth "edit โ†’ build โ†’ test" cycle that's critical for rapid iteration on ROMs.


Current Implementation #

Message Flow Architecture #

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”         โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”         โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Fish Shell   โ”‚  HTTP   โ”‚ Session Server   โ”‚  WebSoc โ”‚ VSCode      โ”‚
โ”‚ ac function  โ”‚  POST   โ”‚ localhost:8889   โ”‚  ket    โ”‚ Webview     โ”‚
โ”‚              โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€>โ”‚ /jump endpoint   โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€>โ”‚ Panel       โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜         โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜         โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Component Analysis #

1. Fish Shell Function #

Location: .devcontainer/config.fish:848-857

function ac --description 'cd to aesthetic-computer or jump to piece'
    if test (count $argv) -eq 0
        cd ~/aesthetic-computer
    else
        set piece_path $argv[1]
        echo "๐ŸŽฏ Jumping to: $piece_path"
        set response (curl -s -k -X POST https://localhost:8889/jump \
            -H "Content-Type: application/json" \
            -d "{\"piece\": \"$piece_path\"}")
        echo "$response"
    end
end

Issues:

  • โŒ No panel visibility check before sending
  • โŒ No retry logic if POST fails
  • โŒ No validation of response
  • โŒ Silent failure if panel not ready
  • โœ… Simple and fast when working

2. Session Server #

Location: session-server/session.mjs:225-235

// Jump to a specific piece (navigate)
fastify.post("/jump", async (req) => {
  const { piece } = req.body;
  everyone(pack("jump", { piece }, "pieces"));
  return { msg: "Jump request sent!", piece };
});

Behavior:

  • Receives POST request
  • Broadcasts jump message to all connected websocket clients
  • Returns success regardless of whether clients received it
  • No confirmation that panel actually navigated

3. VSCode Extension Panel #

Location: vscode-extension/extension.ts:716-740, 742-920

class AestheticViewProvider implements vscode.WebviewViewProvider {
  public static readonly viewType = "aestheticComputer.sidebarView";
  private _view?: vscode.WebviewView;

  public sendMessageToWebview(message: any) {
    if (this._view && this._view.webview) {
      this._view.webview.postMessage(message);
    }
  }

  public refreshWebview(): void {
    if (this._view) {
      const slug = extContext.globalState.get("panel:slug", "");
      this._view.title = slug + (local ? " ๐Ÿง‘โ€๐Ÿคโ€๐Ÿง‘" : "");
      this._view.webview.html = getWebViewContent(this._view.webview, slug);
    }
  }

  public resolveWebviewView(
    webviewView: vscode.WebviewView,
    context: vscode.WebviewViewResolveContext<unknown>,
    _token: vscode.CancellationToken,
  ): void {
    this._view = webviewView;
    // ... initialization ...
    
    webviewView.webview.onDidReceiveMessage(async (data) => {
      // Handles messages from webview
    });

    webviewView.onDidChangeVisibility(() => {
      if (!webviewView.visible) {
        // Panel hidden - no messages received
      } else {
        // Panel visible - send focus event
        webviewView.webview.postMessage({ type: "aesthetic-parent:focused" });
      }
    });
  }
}

Issues:

  • โŒ No method to show panel programmatically
  • โŒ No way to check if panel is visible from fish
  • โŒ No message queue for when panel becomes visible
  • โœ… Well-structured for receiving messages when visible

Proposed Solutions #

Option A: VSCode Command to Show Panel + Fish Wrapper #

Concept: Create a VSCode command that shows the panel, then have fish call it before sending the jump message.

Implementation:

  1. Extension Side - Add command to show panel:
// In extension.ts activate() function
context.subscriptions.push(
  vscode.commands.registerCommand("aestheticComputer.showPanel", async () => {
    await vscode.commands.executeCommand(
      "workbench.view.extension.aestheticComputer"
    );
    // Wait for panel to initialize
    return new Promise((resolve) => setTimeout(resolve, 500));
  })
);
  1. Fish Side - Update ac function:
function ac --description 'cd to aesthetic-computer or jump to piece'
    if test (count $argv) -eq 0
        cd ~/aesthetic-computer
    else
        set piece_path $argv[1]
        echo "๐ŸŽฏ Jumping to: $piece_path"
        
        # Show the panel first
        code --command aestheticComputer.showPanel
        sleep 0.5  # Give panel time to initialize
        
        # Then send the jump command
        set response (curl -s -k -X POST https://localhost:8889/jump \
            -H "Content-Type: application/json" \
            -d "{\"piece\": \"$piece_path\"}")
        echo "$response"
    end
end

Pros:

  • โœ… Directly addresses root cause (hidden panel)
  • โœ… Uses native VSCode APIs
  • โœ… Panel always visible after command
  • โœ… Works even if websocket not connected yet

Cons:

  • โŒ Requires code CLI to be available
  • โŒ May not work in all environments (Codespaces, SSH)
  • โŒ Timing issues - hard to know when panel fully loaded
  • โŒ Intrusive - forces panel to show even if user doesn't want it

Option B: Retry Logic with Exponential Backoff #

Concept: Keep fish simple, but add retry logic to handle transient failures.

Implementation:

function ac --description 'cd to aesthetic-computer or jump to piece'
    if test (count $argv) -eq 0
        cd ~/aesthetic-computer
    else
        set piece_path $argv[1]
        echo "๐ŸŽฏ Jumping to: $piece_path"
        
        # Retry up to 5 times with exponential backoff
        set max_retries 5
        set retry_count 0
        set success false
        
        while test $retry_count -lt $max_retries
            set response (curl -s -k -X POST https://localhost:8889/jump \
                -H "Content-Type: application/json" \
                -d "{\"piece\": \"$piece_path\"}")
            
            # Check if response indicates success
            if string match -q "*Jump request sent*" $response
                echo "$response"
                set success true
                break
            end
            
            set retry_count (math $retry_count + 1)
            if test $retry_count -lt $max_retries
                set delay (math "0.5 * (2 ^ $retry_count)")
                echo "โณ Retrying in $delay seconds... (attempt $retry_count/$max_retries)"
                sleep $delay
            end
        end
        
        if not $success
            echo "โŒ Failed to send jump command after $max_retries attempts"
            echo "๐Ÿ’ก Try manually opening the Aesthetic Computer panel in VSCode"
            return 1
        end
    end
end

Pros:

  • โœ… No changes to extension needed
  • โœ… Handles server startup delay
  • โœ… Handles temporary network issues
  • โœ… Provides clear feedback to user

Cons:

  • โŒ Doesn't solve hidden panel issue
  • โŒ Wastes time retrying if panel will never be visible
  • โŒ Can be slow (up to ~30 seconds with backoff)
  • โŒ Still fails silently if panel hidden but server up

Option C: Extension-Side Message Queue #

Concept: Have the extension buffer jump messages when panel is hidden, then replay them when it becomes visible.

Implementation:

  1. Extension Side - Add message queue:
class AestheticViewProvider implements vscode.WebviewViewProvider {
  private _pendingMessages: any[] = [];
  
  public sendMessageToWebview(message: any) {
    if (this._view && this._view.webview && this._view.visible) {
      this._view.webview.postMessage(message);
    } else {
      // Queue message for later
      this._pendingMessages.push(message);
      console.log(`๐Ÿ“ฌ Queued message (panel hidden): ${message.type || message}`);
    }
  }
  
  public resolveWebviewView(...) {
    // ... existing code ...
    
    webviewView.onDidChangeVisibility(() => {
      if (webviewView.visible) {
        // Panel now visible - send queued messages
        console.log(`๐Ÿ“ค Panel visible, sending ${this._pendingMessages.length} queued messages`);
        this._pendingMessages.forEach(msg => {
          webviewView.webview.postMessage(msg);
        });
        this._pendingMessages = [];
        
        webviewView.webview.postMessage({ type: "aesthetic-parent:focused" });
      }
    });
  }
}
  1. Session Server Side - Forward to extension:
// Store reference to extension provider (would need to be set up)
let extensionProvider;

fastify.post("/jump", async (req) => {
  const { piece } = req.body;
  
  // Broadcast via websocket
  everyone(pack("jump", { piece }, "pieces"));
  
  // Also send directly to extension if available
  if (extensionProvider) {
    extensionProvider.sendMessageToWebview({
      type: "jump",
      piece: piece
    });
  }
  
  return { msg: "Jump request sent!", piece };
});

Pros:

  • โœ… No changes to fish function needed
  • โœ… Messages never lost - always queued
  • โœ… Works automatically when panel opened
  • โœ… Transparent to user

Cons:

  • โŒ Complex to implement - requires IPC between session server and extension
  • โŒ Queue could grow unbounded if panel never opened
  • โŒ User may not realize panel needs to be opened
  • โŒ Messages could arrive out of order if queue processes slowly

Option D: Hybrid Approach #

Concept: Combine panel visibility check with smart retry logic.

Implementation: Use code --command CLI to check panel visibility before sending.

Pros:

  • โœ… Best effort panel visibility
  • โœ… Fast retries (< 2 seconds total)

Cons:

  • โŒ code --command not available in devcontainer/Codespaces
  • โŒ Creates OS/environment dependencies
  • โŒ Still requires retry logic
  • โŒ Not a complete solution

Status: Ruled out - tested and confirmed code --command unavailable in devcontainer.

Concept: The VSCode extension connects directly to the session server as a WebSocket client, receives jump messages directly, and controls its own panel visibility.

Architecture:

Terminal:                 Session Server:              VSCode Extension:
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”              โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”             โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ ac cmd  โ”‚โ”€โ”€โ”€ HTTP โ”€โ”€โ”€โ”€โ–ถโ”‚  POST /jump  โ”‚             โ”‚  WebSocket     โ”‚
โ”‚         โ”‚     POST     โ”‚              โ”‚             โ”‚  Client        โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜              โ”‚   Broadcast  โ”‚โ”€โ”€โ”€ WS โ”€โ”€โ”€โ”€โ”€โ–ถโ”‚                โ”‚
                         โ”‚   to all     โ”‚   "vscode:  โ”‚  Show Panel    โ”‚
                         โ”‚   clients    โ”‚    jump"    โ”‚  + Navigate    โ”‚
                         โ”‚              โ”‚             โ”‚                โ”‚
Browser:                 โ”‚              โ”‚             โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”              โ”‚              โ”‚
โ”‚ Client  โ”‚โ—€โ”€โ”€โ”€ WS โ”€โ”€โ”€โ”€โ”€โ”€โ”‚              โ”‚
โ”‚         โ”‚   "jump"     โ”‚              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜              โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Implementation:

  1. Session Server (session-server/session.mjs):
const vscodeClients = new Set();

io.on("connection", (socket) => {
  socket.on("identify", (data) => {
    if (data.type === "vscode") {
      vscodeClients.add(socket);
      socket.on("disconnect", () => vscodeClients.delete(socket));
      console.log("โœ“ VSCode extension connected");
    }
  });
});

fastify.post("/jump", async (req) => {
  const { piece } = req.body;
  
  // Broadcast to browser clients
  everyone(pack("jump", { piece }, "pieces"));
  
  // Direct message to VSCode extension clients
  vscodeClients.forEach(client => {
    client.emit("vscode:jump", { piece });
  });
  
  return { 
    msg: "Jump request sent!", 
    piece,
    vscodeConnected: vscodeClients.size > 0 
  };
});
  1. Extension (vscode-extension/extension.ts):
import io from "socket.io-client";

class AestheticViewProvider {
  private socket?: any;
  
  async connectToSessionServer() {
    const url = local ? "https://localhost:8889" : "https://aesthetic.computer";
    this.socket = io(url, { 
      rejectUnauthorized: false,
      transports: ["websocket"] 
    });
    
    this.socket.on("connect", () => {
      console.log("Connected to session server");
      this.socket.emit("identify", { type: "vscode" });
    });
    
    this.socket.on("vscode:jump", async (data: { piece: string }) => {
      console.log("Received jump command:", data.piece);
      
      // Show panel if hidden
      if (!this._view?.visible) {
        await vscode.commands.executeCommand(
          "workbench.view.extension.aestheticComputer"
        );
        // Brief delay for panel to initialize
        await new Promise(r => setTimeout(r, 300));
      }
      
      // Send jump message to webview
      this.sendMessageToWebview({ 
        type: "jump", 
        piece: data.piece 
      });
    });
    
    this.socket.on("disconnect", () => {
      console.log("Disconnected from session server");
    });
  }
}

// In activate():
export function activate(context: vscode.ExtensionContext) {
  const provider = new AestheticViewProvider(context.extensionUri);
  
  // Connect to session server
  provider.connectToSessionServer();
  
  // ... rest of activation
}
  1. Fish Function (minimal changes):
function ac --description 'cd to aesthetic-computer or jump to piece'
    if test (count $argv) -eq 0
        cd ~/aesthetic-computer
    else
        set piece_path $argv[1]
        echo "๐ŸŽฏ Jumping to: $piece_path"
        
        set response (curl -s -k -X POST https://localhost:8889/jump \
            -H "Content-Type: application/json" \
            -d "{\"piece\": \"$piece_path\"}")
        
        if string match -q "*vscodeConnected*true*" $response
            echo "โœ… Sent to VSCode extension"
        else if string match -q "*Jump request sent*" $response
            echo "โœ… Sent to browser clients"
        else
            echo "โŒ $response"
        end
    end
end

Pros:

  • โœ… Works in ALL environments (devcontainer, Codespaces, desktop)
  • โœ… Direct communication - no polling or retry needed
  • โœ… Extension controls its own UI - can show panel programmatically
  • โœ… Uses existing infrastructure - session server already has WebSocket
  • โœ… Clean architecture - extension is proper session client
  • โœ… Instant feedback - knows immediately if extension connected
  • โœ… No terminal dependencies - no code CLI required
  • โœ… Supports both targets - browser clients AND extension

Cons:

  • โŒ Requires extension version update (1.191.0)
  • โŒ Users need to update extension
  • โŒ More moving parts (but all well-tested infrastructure)

Why this is best: This 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.


Recommendation #

Implement Option E: Extension as Session Server Client

Rationale #

After thorough analysis and testing, Option E is the clear winner:

  1. Universal compatibility: Works in devcontainer, Codespaces, and desktop VSCode
  2. Architectural cleanliness: Extension is a proper session server client, not a workaround
  3. Leverages existing infrastructure: Session server already manages WebSocket connections
  4. No retry logic needed: Direct communication with instant feedback
  5. Extension controls its own UI: Can show panel programmatically before navigating
  6. Supports hybrid usage: Works for both browser clients AND VSCode extension simultaneously

Why other options don't work:

  • Option A/D: code --command unavailable in devcontainer/Codespaces (tested and confirmed)
  • Option B: Retry logic can't solve root problem (panel visibility)
  • Option C: Queue complexity without solving user experience issue

The key insight: Instead of making the terminal try to control VSCode, make VSCode a first-class participant in the session server ecosystem.

Implementation Plan #

Phase 1: Session Server Updates (15 minutes)

  1. Add VSCode client tracking to session-server/session.mjs:

    • Create vscodeClients Set to track extension connections
    • Add "identify" event handler to recognize VSCode clients
    • Update /jump endpoint to emit "vscode:jump" to extension clients
    • Return vscodeConnected flag in response
  2. Test with curl:

    curl -k -X POST https://localhost:8889/jump \
      -H "Content-Type: application/json" \
      -d '{"piece": "gameboy~melody"}'
    

Phase 2: Extension Implementation (30 minutes)

  1. Add dependencies to vscode-extension/package.json:

    "dependencies": {
      "socket.io-client": "^4.7.2"
    }
    
  2. Update vscode-extension/extension.ts:

    • Import socket.io-client
    • Add connectToSessionServer() method to AestheticViewProvider
    • Emit "identify" with type "vscode" on connection
    • Listen for "vscode:jump" messages
    • Show panel if hidden before navigating
    • Handle connection/disconnection gracefully
  3. Update extension version to 1.191.0 in package.json

  4. Build and test in devcontainer:

    cd vscode-extension
    npm install
    npm run compile
    # Test with F5 (Extension Development Host)
    

Phase 3: Fish Function Update (5 minutes)

  1. Update .devcontainer/config.fish ac function:

    • Parse response for vscodeConnected flag
    • Show appropriate success message
    • Minimal changes - no retry logic needed
  2. Test scenarios:

    • ac gameboy~melody with panel hidden โ†’ should show panel and navigate
    • ac gameboy~melody with panel visible โ†’ should navigate immediately
    • ac gameboy~melody with extension not running โ†’ should still send to browsers

Phase 4: Testing & Publishing (20 minutes)

  1. Comprehensive testing:

    • Panel hidden โ†’ jump command โ†’ verify panel shows
    • Panel visible โ†’ jump command โ†’ verify immediate navigation
    • Extension not running โ†’ verify graceful fallback
    • Build script auto-launch โ†’ verify works in automation
    • Browser client โ†’ verify still receives messages
  2. Extension packaging and publishing:

    cd vscode-extension
    vsce package
    vsce publish
    
  3. Update documentation in README

Total estimated time: ~70 minutes


Alternative Considerations #

Why this beats the CLI approach (Option D)? #

The code --command approach seemed ideal but:

  • โŒ Not available in VSCode devcontainers
  • โŒ Not available in Codespaces
  • โŒ Creates OS/environment dependencies
  • โŒ Still requires polling/retry logic

The WebSocket approach:

  • โœ… Works in all VSCode environments
  • โœ… Direct, instant communication
  • โœ… Extension can control its own UI
  • โœ… No polling or retry needed
  • โœ… Uses existing session server infrastructure

Why not just rely on browser clients? #

The session server already broadcasts to browser clients, but:

  • Browser clients can't show the VSCode sidebar panel
  • User has to manually open panel before it works
  • No way to provide feedback that panel needs opening

Why not use VSCode Extension IPC? #

VSCode extensions can't directly communicate with terminal processes. The session server acts as the perfect intermediary:

  • Terminal โ†’ HTTP POST โ†’ Session server โ†’ WebSocket โ†’ Extension
  • Clean separation of concerns
  • Session server already manages websocket connections

Success Criteria #

After implementation, the ac command should:

  1. โœ… Work immediately when panel already visible
  2. โœ… Show panel automatically if hidden (extension controls this)
  3. โœ… Work in all VSCode environments (devcontainer, Codespaces, desktop)
  4. โœ… Provide instant feedback about VSCode extension connection status
  5. โœ… Complete navigation within 500ms in success case
  6. โœ… Work in build script auto-launch scenario
  7. โœ… Work in manual terminal usage scenario
  8. โœ… Continue to work with browser clients simultaneously

Future Improvements #

Extension enhancements #

  • Add status bar indicator showing session server connection
  • Add notification when jump command received
  • Add ac status command to check connection health
  • Add reconnection logic with exponential backoff

Session server enhancements #

  • Add /status endpoint returning connected client counts
  • Add message delivery confirmations
  • Add client presence tracking (last seen timestamp)

User-facing improvements #

  • Add ac doctor command to diagnose setup issues
  • Show VSCode notification when jump received while coding elsewhere
  • Add keyboard shortcut to toggle panel visibility

Appendix: Code Locations #

Component File Lines
Fish ac function .devcontainer/config.fish 848-857
Session server /jump session-server/session.mjs 225-235
Extension provider vscode-extension/extension.ts 716-920
Build script auto-launch kidlisp-gameboy/build.sh 68-74

Next Steps:

  1. Review this plan with user
  2. Begin Phase 1: Session server implementation
  3. Phase 2: Extension WebSocket client
  4. Phase 3: Fish function update
  5. Phase 4: Testing and publishing extension v1.191.0