Files
opencode-krates-connector/design/shell-logs.md
Hermes Agent 78f19cde7d feat: Add comprehensive design documentation for Krates
- Canvas: Infinite zoomable workspace with LOD and navigation
- Spotlight: Fuzzy search with type filters and view shortcuts
- Krate: Window group container with non-overlapping placement
- Detail Window: YAML/Describe/Logs/Shell views with maximize
- Top Bar: Cluster info, user presence, admin toggle
- Admin Drawer: Multi-user presence and spectate functionality
- Minimap: Browse and navigate canvas overview
- Collection Window: List/tree views with filtering and sorting
- Shell/Logs: Real-time terminal and log streaming
- Backend: Go service with K8s API, WebSocket handlers, CRDT sync
- Architecture: Full project structure and tech stack
2026-06-16 08:32:47 -04:00

8.5 KiB

Shell & Logs Windows Feature Specification

Overview

Shell and Logs windows are specialized detail windows that stream real-time data from Kubernetes pods. Shell provides an interactive terminal, while Logs shows container output.


Shell Window

Purpose

Interactive pseudo-terminal session inside the application, connected to a pod via kubectl exec.

Implementation

Client-Side (xterm.js)

// Initialization
import { Terminal } from 'xterm';
import { FitAddon } from 'xterm-addon-fit';

const term = new Terminal({
  rows: 24,
  cols: 80,
  fontFamily: 'IBM Plex Mono',
  fontSize: 12,
  theme: {
    background: '#0b0e13',
    foreground: '#e6edf6',
    cursor: '#4dd6e8'
  }
});

const fitAddon = new FitAddon();
term.loadAddon(fitAddon);
fitAddon.fit();

WebSocket Connection

// On krate creation with shell tab
const ws = new WebSocket(`/ws/shell?pod=${pod}&ns=${ns}`);

ws.onopen = () => {
  term.focus();
};

ws.onmessage = (event) => {
  term.write(event.data);  // stdout/stderr
};

term.onData((data) => {
  ws.send(data);  // stdin (keypresses, etc.)
});

term.onResize(({ rows, cols }) => {
  ws.send(JSON.stringify({ type: 'resize', rows, cols }));
});

Keyboard Routing

Mouse-Over Focus

  • Track mouseenter/mouseleave per window
  • Set hoveredWindowId on shell windows
  • Global keydown handler routes to shell when focused

Critical: Space Key

  • Must NOT open spotlight when shell has focus
  • Shell window handles space as input (terminal command)
  • Implementation:
    document.addEventListener('keydown', (e) => {
      if (hoveredWindowId && isShellWindow(hoveredWindowId)) {
        // Space goes to shell
        return;  // Don't trigger canvas shortcuts
      }
      // Handle canvas shortcuts (space pan, etc.)
    }, { capture: true });
    

Scroll Wheel Behavior

  • On shell content: Scroll terminal (not canvas)
  • On empty canvas: Pan
  • Ctrl/⌘ + scroll over shell: Zoom canvas (not terminal zoom)
    • Use { capture: true } on canvas wheel handler
    • Check e.ctrlKey || e.metaKey
    • If true: zoom canvas; otherwise: let event propagate

Terminal Features

  • Full PTY emulation: Support all ANSI escapes
  • Resize: Send new size on container resize
  • Paste: Support Ctrl+Shift+V or Shift+Insert
  • Copy: Ctrl+Shift+C or Ctrl+C (custom)
  • Search: / to search, n/N for next/prev
  • Font scaling: Ctrl+wheel for terminal font size

Backend Implementation (Go)

// WebSocket connection
func (s *Server) handleShell(w http.ResponseWriter, r *http.Request) {
    ws, err := upgrader.Upgrade(w, r, nil)
    if err != nil {
        return
    }
    defer ws.Close()

    pod := r.URL.Query().Get("pod")
    ns := r.URL.Query().Get("ns")

    // Create k8s exec request
    req := s.client.CoreV1().RESTClient().Post().
        Name(pod).Namespace(ns).Resource("pods").SubResource("exec")
    req.VersionedParams(&v1.ExecOptions{
        Stdin:     true,
        Stdout:    true,
        Stderr:    true,
        Terminal:  true,
    }, scheme.ParameterCodec)

    exec, err := remotecommand.NewSPDYExecutor(s.config, "POST", req.URL())
    if err != nil {
        ws.Close()
        return
    }

    // Stream between WebSocket and exec
    exec.Stream(remotecommand.StreamOptions{
        Stdin: ws,
        Stdout: ws,
        Stderr: ws,
        TermSize: &remotecommand.TerminalSize{
            Width:  80,
            Height: 24,
        },
    })
}

Logs Window

Purpose

Live-tailing container logs, showing output in real-time.

Streaming

// WebSocket connection
const ws = new WebSocket(`/ws/logs?pod=${pod}&ns=${ns}`);

ws.onmessage = (event) => {
  const line = event.data;
  addLogLine(line);
};

function addLogLine(line: string) {
  const entry = parseLogLine(line);
  logBuffer.push(entry);
  
  // Auto-scroll if at bottom
  if (atBottom()) {
    scrollToBottom();
  }
}

Auto-Scroll Behavior

  • New lines arrive: Auto-scroll to bottom
  • User scrolls up: Stop auto-scrolling
  • Detection:
    function atBottom(): boolean {
      const threshold = 50;  // px
      return (scrollTop + clientHeight + threshold) >= scrollHeight;
    }
    

Color Coding

Parse lines and color-code by pattern:

function parseLogLine(line: string): LogEntry {
  // Pattern matching for common log levels
  if (line.includes('ERROR') || line.includes('ERR')) {
    return { text: line, color: '#ef6f6f' };  // Red
  } else if (line.includes('WARN') || line.includes('WARNING')) {
    return { text: line, color: '#e8b54a' };  // Amber
  } else if (line.includes('INFO') || line.includes('I ')) {
    return { text: line, color: '#b9c6d8' };  // Default
  } else {
    return { text: line, color: '#c7d2e0' };  // Muted
  }
}

Timestamps

  • Default: Include timestamps
  • Toggle: Optional button to show/hide
  • Format: ISO 8601 or container format

Scrollbar Styling

  • Width: 8px
  • Track: rgba(140,165,200,.1)
  • Thumb: rgba(140,165,200,.3)
  • Hover thumb: rgba(140,165,200,.5)

Content Rendering

<pre style={{ overflow: 'auto', height: '286px', fontFamily: 'IBM Plex Mono' }}>
  {logs.map((line, i) => (
    <div key={i} style={{ color: line.color }}>
      {line.timestamp && <span className="timestamp">{line.timestamp} </span>}
      {line.text}
    </div>
  ))}
</pre>

View-Specific Behavior

Shell Tab

  • Enabled for: Pods, and workloads that support exec
  • Disabled for: Deployments, Services, etc.
  • Visual: opacity: 0.45 for disabled
  • Label: Show "shell" but don't route to it

Logs Tab

  • Enabled for: Pods and workloads ( Deployments, StatefulSets, DaemonSets)
  • Disabled for: ConfigMaps, Secrets, PVCs, etc.
  • Visual: Same as shell

Keyboard Shortcuts

Key Context Action
↑ / ↓ Logs shell focused Navigate scroll (logs) / line history (shell)
PageUp / PageDown Logs focused Page scroll
Ctrl+L Logs focused Clear screen (only in shell)
Ctrl+C Shell focused Send interrupt to shell
Ctrl+Z Shell focused Send suspend (if supported)
Ctrl+Shift+F Logs focused Find (future enhancement)
Ctrl+Shift+V Shell focused Paste
Ctrl+Shift+C Shell focused Copy selection

Error Handling

Connection Errors

  • Shell: Show error border, keep terminal buffer, retry button
  • Logs: Show error indicator, auto-reconnect with backoff

Reconnection Strategy

let retries = 0;
const maxRetries = 5;

function reconnect() {
  if (retries >= maxRetries) {
    showFatalError('Connection failed');
    return;
  }
  
  setTimeout(() => {
    retries++;
    connect();
  }, Math.min(1000 * 2^retries, 10000));  // Exponential backoff
}

Buffer Management

  • Shell: Keep last 1000 lines in buffer
  • Logs: Keep last 2000 lines
  • Overflow: Drop oldest lines, mark with "..." indicator

State Management

Shell State

interface ShellState {
  connected: boolean;
  rows: number;
  cols: number;
  buffer: string[];  // Last N lines
  cursor: { x, y };
}

Logs State

interface LogsState {
  lines: LogEntry[];
  scrollTop: number;
  autoScroll: boolean;
  timestamp: boolean;
}

Gotchas

Critical Issues

  1. Space key routing: Must distinguish between shell space (input) and canvas space-pan
  2. Ctrl/⌘+scroll: Intercept on canvas with { capture: true }, check modifier
  3. Terminal resize: Send TermSize on window resize events
  4. Paste security: Sanitize pasted content, avoid escape sequences
  5. Cursor position: xterm.js tracks cursor; keep in sync with server
  6. Buffer overflow: Implement LRU buffer, don't memory leak

Performance

  1. Throttle updates: Don't render on every log line, batch
  2. Virtual scrolling: For logs with many lines
  3. Web Worker: Offload parsing to worker for large logs

Animation

  1. Blinking cursor: CSS animation or xterm.js built-in
  2. Auto-scroll indicator: Small arrow when scrolled up
  3. Connection status: Green/amber/red dot

Testing Checklist

  • Shell key press → terminal receives it
  • Terminal output → WebSocket → UI
  • Resize → terminal resizes correctly
  • Space in shell ≠ spotlight open
  • Ctrl+scroll over shell ≠ zoom canvas
  • Logs auto-scroll works (and stops on manual scroll)
  • Connection loss → retry with backoff
  • Buffer overflow → old lines dropped
  • Shell interrupt (Ctrl+C) handled
  • Paste works (Ctrl+Shift+V)