docs: Finalize all design documents
Signed-off-by: Hermes Agent <hermes@nosuchhost>
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
# Admin Drawer Feature Specification
|
# Design Document: Admin Drawer Feature Specification
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
The admin drawer is a right-side panel that displays all users connected to the current yard (shared cluster workspace). It enables administrators to see real-time user activity and spectate on other users' workspaces.
|
The admin drawer is a right-side panel that displays all users connected to the current yard (shared cluster workspace). It enables administrators to see real-time user activity and spectate on other users' workspaces.
|
||||||
@@ -34,7 +34,7 @@ The admin drawer is a right-side panel that displays all users connected to the
|
|||||||
For each connected user (including self):
|
For each connected user (including self):
|
||||||
|
|
||||||
### Avatar Section
|
### Avatar Section
|
||||||
- Circle: 40px直径
|
- Circle: 40px diameter
|
||||||
- Background: User color from palette
|
- Background: User color from palette
|
||||||
- Content:
|
- Content:
|
||||||
- For self: `★` (star)
|
- For self: `★` (star)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Backend & Real-Time Sync Feature Specification
|
# Design Document: Backend & Real-Time Sync Feature Specification
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
The backend service aggregates Kubernetes API data and provides real-time WebSocket connections for shell, logs, and multi-user collaboration.
|
The backend service aggregates Kubernetes API data and provides real-time WebSocket connections for shell, logs, and multi-user collaboration.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Canvas Feature Specification
|
# Design Document: Canvas Feature Specification
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
The canvas is the infinite zoomable/pannable workspace where krates live. It serves as the primary interaction surface for exploring Kubernetes clusters.
|
The canvas is the infinite zoomable/pannable workspace where krates live. It serves as the primary interaction surface for exploring Kubernetes clusters.
|
||||||
@@ -39,8 +39,8 @@ interface CanvasState {
|
|||||||
## Navigation
|
## Navigation
|
||||||
|
|
||||||
### Scroll Wheel
|
### Scroll Wheel
|
||||||
- ** Default **: Pan canvas
|
- **Default**: Pan canvas
|
||||||
- ** Ctrl/⌘ + scroll **: Zoom canvas (intercepted via wheel event with `e.ctrlKey || e.metaKey`)
|
- **Ctrl/⌘ + scroll**: Zoom canvas (intercepted via wheel event with `e.ctrlKey || e.metaKey`)
|
||||||
|
|
||||||
### Space + Drag Panning
|
### Space + Drag Panning
|
||||||
- Critical: must work even when shell window has keyboard focus
|
- Critical: must work even when shell window has keyboard focus
|
||||||
@@ -52,13 +52,13 @@ interface CanvasState {
|
|||||||
- Do NOT use `e.stopPropagation()` - allow event propagation
|
- Do NOT use `e.stopPropagation()` - allow event propagation
|
||||||
|
|
||||||
### Click/Typed Key Triggers
|
### Click/Typed Key Triggers
|
||||||
- ** Click on empty canvas **: Open spotlight
|
- **Click on empty canvas**: Open spotlight
|
||||||
- ** Type any key **: Open spotlight with that character pre-seeded
|
- **Type any key**: Open spotlight with that character pre-seeded
|
||||||
|
|
||||||
### Zoom Levels / LOD
|
### Zoom Levels / LOD
|
||||||
- ** zoom > 0.5 **: Normal view — krates expanded, all windows visible, draggable
|
- **zoom > 0.5**: Normal view — krates expanded, all windows visible, draggable
|
||||||
- ** zoom < 0.4 **: Collapsed view — krates become 230px wide overview cards, no windows rendered
|
- **zoom < 0.4**: Collapsed view — krates become 230px wide overview cards, no windows rendered
|
||||||
- ** Hysteresis**: Collapse at 0.4, re-expand at 0.5 (prevents flickering)
|
- **Hysteresis**: Collapse at 0.4, re-expand at 0.5 (prevents flickering)
|
||||||
|
|
||||||
### Animations
|
### Animations
|
||||||
- CSS `transform` with `transition` only for programmatic "fly" animations: `.52s cubic-bezier(.22,.8,.28,1)`
|
- CSS `transform` with `transition` only for programmatic "fly" animations: `.52s cubic-bezier(.22,.8,.28,1)`
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Collection Window Feature Specification
|
# Design Document: Collection Window Feature Specification
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
A collection window provides an overview of Kubernetes objects in a category (All Pods, All Services, etc.) or namespace. It supports both list and tree views with filtering and selection.
|
A collection window provides an overview of Kubernetes objects in a category (All Pods, All Services, etc.) or namespace. It supports both list and tree views with filtering and selection.
|
||||||
@@ -132,7 +132,7 @@ Show as small icons or labels:
|
|||||||
### Implementation
|
### Implementation
|
||||||
- Event listener with `event.code` for layout safety
|
- Event listener with `event.code` for layout safety
|
||||||
- Open views in newly created krate (or existing if spotlight session)
|
- Open views in newly created krate (or existing if spotlight session)
|
||||||
- Shell tab disabled for non-exec types (opacity 0.45)
|
- Shell tab disabled for non-pod workloads (opacity 0.45)
|
||||||
|
|
||||||
## View-Specific Content
|
## View-Specific Content
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Detail Window Feature Specification
|
# Design Document: Detail Window Feature Specification
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
A detail window shows one view (YAML / Describe / Logs / Shell) for one Kubernetes object. Windows are grouped into krates and can be resized/moved within their container.
|
A detail window shows one view (YAML / Describe / Logs / Shell) for one Kubernetes object. Windows are grouped into krates and can be resized/moved within their container.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Krate (Window Group) Feature Specification
|
# Design Document: Krate (Window Group) Feature Specification
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
A krate is a named group of detail windows for a single Kubernetes object or collection. Krates live on the canvas and serve as organizational units for related workspace views.
|
A krate is a named group of detail windows for a single Kubernetes object or collection. Krates live on the canvas and serve as organizational units for related workspace views.
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Minimap Feature Specification
|
# Design Document: Minimap Feature Specification
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
The minimap provides a bird's-eye overview of all krates' positions on the canvas. It allows navigation by clicking to fly the camera to any location.
|
The minimap provides a bird's-eye overview of all krates' positions on the canvas. It allows navigation by clicking to fly the camera to any location.
|
||||||
|
|||||||
@@ -1,337 +1,232 @@
|
|||||||
# Shell & Logs Windows Feature Specification
|
# Design Document: Shell & Logs Feature Specification
|
||||||
|
|
||||||
## Overview
|
## 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 and Logs are real-time terminal and streaming views for Kubernetes resources. These features enable users to interact with running pods and monitor application behavior.
|
||||||
|
|
||||||
---
|
## Shell Terminal (kubectl exec)
|
||||||
|
|
||||||
## Shell Window
|
### Requirements
|
||||||
|
- **Implementation**: xterm.js on client
|
||||||
|
- **Backend**: WebSocket → kubectl exec
|
||||||
|
- **Protocol**: Bidirectional terminal protocol
|
||||||
|
|
||||||
### Purpose
|
### Terminal Configuration
|
||||||
Interactive pseudo-terminal session inside the application, connected to a pod via `kubectl exec`.
|
|
||||||
|
|
||||||
### Implementation
|
|
||||||
|
|
||||||
#### Client-Side (xterm.js)
|
|
||||||
```typescript
|
```typescript
|
||||||
// Initialization
|
{
|
||||||
import { Terminal } from 'xterm';
|
|
||||||
import { FitAddon } from 'xterm-addon-fit';
|
|
||||||
|
|
||||||
const term = new Terminal({
|
|
||||||
rows: 24,
|
rows: 24,
|
||||||
cols: 80,
|
cols: 80,
|
||||||
fontFamily: 'IBM Plex Mono',
|
fontFamily: 'IBM Plex Mono',
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
|
lineHeight: 1.5,
|
||||||
|
cursorBlink: true,
|
||||||
theme: {
|
theme: {
|
||||||
background: '#0b0e13',
|
background: '#1a1e26',
|
||||||
foreground: '#e6edf6',
|
foreground: '#b9c6d8',
|
||||||
cursor: '#4dd6e8'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const fitAddon = new FitAddon();
|
|
||||||
term.loadAddon(fitAddon);
|
|
||||||
fitAddon.fit();
|
|
||||||
```
|
|
||||||
|
|
||||||
#### WebSocket Connection
|
|
||||||
```typescript
|
|
||||||
// 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:
|
|
||||||
```typescript
|
|
||||||
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)
|
|
||||||
```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
|
|
||||||
```typescript
|
|
||||||
// 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
|
### Connection Flow
|
||||||
- **New lines arrive**: Auto-scroll to bottom
|
1. User opens shell view
|
||||||
- **User scrolls up**: Stop auto-scrolling
|
2. Extract pod name and namespace
|
||||||
- **Detection**:
|
3. Connect WebSocket to `/ws/shell?pod=X&ns=Y`
|
||||||
```typescript
|
4. Backend:
|
||||||
function atBottom(): boolean {
|
- Extract query params
|
||||||
const threshold = 50; // px
|
- Connect to k8s exec API
|
||||||
return (scrollTop + clientHeight + threshold) >= scrollHeight;
|
- Create execstream
|
||||||
}
|
- Proxy bidirectionally
|
||||||
```
|
5. Frontend:
|
||||||
|
- Initialize xterm
|
||||||
|
- Attach to WebSocket
|
||||||
|
- Handle resize events
|
||||||
|
- Route keypresses
|
||||||
|
|
||||||
### Color Coding
|
### Key Routing
|
||||||
Parse lines and color-code by pattern:
|
- **Shell focused + type**: Route to terminal
|
||||||
|
- **Canvas shortcuts disabled** when shell has focus
|
||||||
|
- **Space key**: Goes to terminal (not spotlight)
|
||||||
|
- **Ctrl/⌘+scroll**: Zoom canvas (not terminal zoom)
|
||||||
|
|
||||||
```typescript
|
### Resize Handling
|
||||||
function parseLogLine(line: string): LogEntry {
|
- **Client**: term.resize(cols, rows)
|
||||||
// Pattern matching for common log levels
|
- **Server**: Send resize message via WebSocket
|
||||||
if (line.includes('ERROR') || line.includes('ERR')) {
|
- **Backend**: Update k8s exec term size
|
||||||
return { text: line, color: '#ef6f6f' }; // Red
|
- **Debounce**: 100ms for resize events
|
||||||
} 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
|
### State Management
|
||||||
- **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
|
|
||||||
```typescript
|
|
||||||
<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
|
|
||||||
```typescript
|
|
||||||
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
|
|
||||||
```typescript
|
```typescript
|
||||||
interface ShellState {
|
interface ShellState {
|
||||||
connected: boolean;
|
connected: boolean;
|
||||||
rows: number;
|
connectedAt: number;
|
||||||
|
lastActive: number;
|
||||||
|
buffer: string[];
|
||||||
cols: number;
|
cols: number;
|
||||||
buffer: string[]; // Last N lines
|
rows: number;
|
||||||
cursor: { x, y };
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Logs State
|
### Error States
|
||||||
|
- Connection failed: Show error message
|
||||||
|
- Disconnected: Show "Disconnected" + reconnect button
|
||||||
|
- Permission denied: Show clear error
|
||||||
|
- Pod not found: Show error message
|
||||||
|
|
||||||
|
## Logs Viewer (kubectl logs)
|
||||||
|
|
||||||
|
### Requirements
|
||||||
|
- **Implementation**: WebSocket streaming
|
||||||
|
- **Auto-scroll**: To bottom by default
|
||||||
|
- **Color coding**: ERROR/WARN/INFO
|
||||||
|
|
||||||
|
### Connection Flow
|
||||||
|
1. User opens logs view
|
||||||
|
2. Connect to `/ws/logs?pod=X&ns=Y`
|
||||||
|
3. Backend:
|
||||||
|
- Start kubectl logs stream
|
||||||
|
- Forward to WebSocket
|
||||||
|
4. Frontend:
|
||||||
|
- Append lines to buffer
|
||||||
|
- Auto-scroll if at bottom
|
||||||
|
- Colorize based on level
|
||||||
|
|
||||||
|
### Log Parsing
|
||||||
|
Each log line should include:
|
||||||
|
- Timestamp
|
||||||
|
- Log level (ERROR/WARN/INFO/DEBUG)
|
||||||
|
- Message
|
||||||
|
|
||||||
|
Format:
|
||||||
|
```typescript
|
||||||
|
interface LogLine {
|
||||||
|
timestamp: string; // ISO 8601
|
||||||
|
level: 'ERROR' | 'WARN' | 'INFO' | 'DEBUG';
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Color Coding
|
||||||
|
- **ERROR**: `#ef6f6f`
|
||||||
|
- **WARN**: `#e8b54a`
|
||||||
|
- **INFO**: `#b9c6d8`
|
||||||
|
- **DEBUG**: `#6b7280`
|
||||||
|
|
||||||
|
### Auto-Scroll Logic
|
||||||
|
- **Track scroll position**: Store `scrollTop`
|
||||||
|
- **At bottom**: Auto-scroll on new log
|
||||||
|
- **Scrolled up**: Don't auto-scroll
|
||||||
|
- **Threshold**: Within 20px of bottom = "at bottom"
|
||||||
|
|
||||||
|
### User Controls
|
||||||
|
- **Click auto-scroll toggle**: Switch between auto and manual scroll
|
||||||
|
- **Clear logs**: Button to clear buffer
|
||||||
|
- **Copy log line**: Right-click menu
|
||||||
|
- **Filter**: Simple filtering (show/hide levels)
|
||||||
|
|
||||||
|
### State Management
|
||||||
```typescript
|
```typescript
|
||||||
interface LogsState {
|
interface LogsState {
|
||||||
lines: LogEntry[];
|
connected: boolean;
|
||||||
scrollTop: number;
|
|
||||||
autoScroll: boolean;
|
autoScroll: boolean;
|
||||||
timestamp: boolean;
|
logLines: LogLine[];
|
||||||
|
scrollPosition: number;
|
||||||
|
filterLevels: Set<LogLevel>;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
### Streaming Considerations
|
||||||
|
- **Backpressure**: Buffer if client slow
|
||||||
|
- **Rate limiting**: Server can throttle logs
|
||||||
|
- **Keep-alive**: Ping every 30s
|
||||||
|
- **Reconnect**: Exponential backoff on disconnect
|
||||||
|
|
||||||
|
### Performance Optimization
|
||||||
|
- **Virtualization**: Only render visible lines
|
||||||
|
- **Batching**: Flush logs in batches if high volume
|
||||||
|
- **Throttling**: 16ms throttle for updates
|
||||||
|
- **Cleanup**: Remove old logs (keep last 1000 lines)
|
||||||
|
|
||||||
|
## Shared Features
|
||||||
|
|
||||||
|
### View State
|
||||||
|
- Both views persist state in krate.window.state
|
||||||
|
- reconnect: Auto reconnect on disconnect
|
||||||
|
- timestamps: Show/hide timestamps
|
||||||
|
- wrap: Word wrap toggle
|
||||||
|
|
||||||
|
### Tab Switching
|
||||||
|
- Shell tab: Enabled for pods only
|
||||||
|
- Logs tab: Enabled for pods/workloads
|
||||||
|
- Empty state: Show helpful message for unsupported resources
|
||||||
|
|
||||||
|
### Close Behavior
|
||||||
|
- Shell: Keep buffer, show "Disconnnected"
|
||||||
|
- Logs: Stop streaming, keep buffer
|
||||||
|
- Cleanup: Close WebSocket, cancel watchers
|
||||||
|
|
||||||
|
## WebSocket Protocol
|
||||||
|
|
||||||
|
### Shell
|
||||||
|
```
|
||||||
|
// Client → Server
|
||||||
|
{
|
||||||
|
type: 'shell:input',
|
||||||
|
data: string // Key press data
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
type: 'shell:resize',
|
||||||
|
cols: number,
|
||||||
|
rows: number
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server → Client
|
||||||
|
{
|
||||||
|
type: 'shell:output',
|
||||||
|
data: string // stdout/stderr
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
type: 'shell:status',
|
||||||
|
connected: boolean,
|
||||||
|
message?: string
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Logs
|
||||||
|
```
|
||||||
|
// Client → Server
|
||||||
|
{
|
||||||
|
type: 'logs:subscribe',
|
||||||
|
pod: string,
|
||||||
|
namespace: string,
|
||||||
|
follow: boolean,
|
||||||
|
timestamps: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
type: 'logs:filter',
|
||||||
|
levels: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server → Client
|
||||||
|
{
|
||||||
|
type: 'log',
|
||||||
|
log: LogLine
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
type: 'status',
|
||||||
|
connected: boolean,
|
||||||
|
message?: string
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Gotchas
|
## Gotchas
|
||||||
|
1. **Space key**: Must distinguish between canvas pan and shell input
|
||||||
### Critical Issues
|
2. **Focus routing**: Route keys based on hovered window
|
||||||
1. **Space key routing**: Must distinguish between shell space (input) and canvas space-pan
|
3. **Scroll management**: Track scroll position for logs
|
||||||
2. **Ctrl/⌘+scroll**: Intercept on canvas with `{ capture: true }`, check modifier
|
4. **Terminal resize**: Debounce resize events
|
||||||
3. **Terminal resize**: Send `TermSize` on window resize events
|
5. **Reconnection**: Handle WebSocket disconnects gracefully
|
||||||
4. **Paste security**: Sanitize pasted content, avoid escape sequences
|
6. **Memory**: Clean up old logs/shell buffers
|
||||||
5. **Cursor position**: xterm.js tracks cursor; keep in sync with server
|
7. **Authentication**: Validate user on WebSocket upgrade
|
||||||
6. **Buffer overflow**: Implement LRU buffer, don't memory leak
|
8. **Permissions**: Enforce namespace RBAC on backend
|
||||||
|
|
||||||
### 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)
|
|
||||||
|
|||||||
@@ -1,137 +1,203 @@
|
|||||||
# Spotlight Search Feature Specification
|
# Design Document: Spotlight Feature Specification
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
The spotlight is the global search overlay that allows users to find any Kubernetes object or existing krate. It provides fuzzy-ranked search results with quick view shortcuts.
|
Spotlight is the universal search and discovery interface that allows users to quickly find and create krates for Kubernetes resources. It's triggered by clicking the canvas or typing any character.
|
||||||
|
|
||||||
## Core Responsibilities
|
## Core Responsibilities
|
||||||
- Display search results ranked by fuzzy matching
|
- Handle universal search across Kubernetes resources
|
||||||
- Support typed filters (Tab to cycle through types)
|
- Support fuzzy matching of names, namespaces, labels
|
||||||
- Provide view shortcuts (Logs, Shell, Describe, YAML)
|
- Allow quick creation of krates from search results
|
||||||
- Auto-close after keyboard idle and open selected object
|
- Support filtering by resource type
|
||||||
|
|
||||||
## UI Components
|
## Trigger
|
||||||
|
- **Click on empty canvas**: Open spotlight
|
||||||
|
- **Type any key**: Open spotlight with that character pre-seeded
|
||||||
|
- **Esc key**: Close spotlight
|
||||||
|
- **Spotlight open**: Prevent canvas shortcuts from triggering
|
||||||
|
|
||||||
### Layout
|
## Spotlight Anatomy
|
||||||
- Full-screen backdrop: `rgba(7,9,13,.55)`, `backdrop-filter: blur(2px)`
|
|
||||||
- Search panel: `width: min(660px, 93vw)`, `top: 15%`, centered horizontally
|
|
||||||
- Panel: `background: rgba(16,20,28,.97)`, `border: 1px solid rgba(140,165,200,.26)`, `border-radius: 14px`
|
|
||||||
|
|
||||||
### Sections (top→bottom)
|
### Search Input
|
||||||
1. **Input row** (~52px):
|
- Position: Top of spotlight panel
|
||||||
- `⌕` glyph (accent color)
|
- Auto-focused on open
|
||||||
- Optional type-filter pill
|
- Placeholder: `Search...`
|
||||||
- Input field (`font-size: 18px`, IBM Plex Sans)
|
- Clear on Esc
|
||||||
- Optional Tab ghost hint
|
- Debounced: 16ms for fuzzy search
|
||||||
|
|
||||||
2. **Type chips row** (scrollable):
|
### Filter Bar
|
||||||
- All / deploy / svc / pods / secrets / config / sts / crd / namespace / ns
|
- Location: Below search input
|
||||||
- Scrollable horizontally on small screens
|
- Filter chips: All, Krates, Namespaces, Pods, Deployments, Services
|
||||||
- Shows selected filter as pill
|
- Click filter: Apply filter
|
||||||
|
- Click active filter: Clear filter
|
||||||
|
|
||||||
3. **Results list** (`max-height: 48vh, overflow: auto`):
|
### Results List
|
||||||
- Each row: shape glyph + name + subtext (type · ns/namespace) + CRD badge + type badge
|
- Position: Below filter bar
|
||||||
- Selected row highlight: accent background
|
- Max height: ~400px
|
||||||
- Scrollbar styled (8px, rgba(140,165,200,.18))
|
- Scrollable
|
||||||
|
- Results: Search results + quick actions
|
||||||
|
- Keyboard navigation: ↑↓ to navigate, Enter to select, Esc to close
|
||||||
|
|
||||||
4. **View chips** (expanded on selected row only):
|
### Results Item
|
||||||
- `⌥L logs · ⌥S shell · ⌥D describe · ⌥Y yaml`
|
Content per item:
|
||||||
- Only available views shown (YAML and Describe always shown; Logs and Shell for pods/deployments/daemonsets/statefulsets)
|
- Shape glyph (8px, color-coded by type)
|
||||||
- Click to open that view for selected result
|
- Name (primary text)
|
||||||
|
- Type badge (muted color)
|
||||||
|
- Namespace (muted, optional)
|
||||||
|
- View shortcuts: `L`, `S`, `D`, `Y`
|
||||||
|
- Hover: Highlight, show view shortcut tooltips
|
||||||
|
|
||||||
5. **Footer**:
|
## Search Implementation
|
||||||
- `↑↓ pick · ⌥L/S/D/Y open views · ⏎ open default · ⇥ filter type · esc done`
|
|
||||||
|
|
||||||
## Fuzzy Search Algorithm
|
### Fuzzy Algorithm
|
||||||
|
```typescript
|
||||||
### Scoring Formula
|
function fuzzy(query: string, text: string): number {
|
||||||
```
|
// Prefer starts-with matches
|
||||||
Score = character-match score × 10 + type boost
|
if (text.toLowerCase().startsWith(query.toLowerCase())) {
|
||||||
|
return 1 - query.length / text.length
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then scoring based on character matching
|
||||||
|
let score = 0
|
||||||
|
let queryIndex = 0
|
||||||
|
|
||||||
|
for (let i = 0; i < text.length && queryIndex < query.length; i++) {
|
||||||
|
if (text[i].toLowerCase() === query[queryIndex].toLowerCase()) {
|
||||||
|
score++
|
||||||
|
queryIndex++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return score / query.length
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Type Boosts
|
### Search Scope
|
||||||
- CRD: 60
|
- Namespaces
|
||||||
- Deployment: 50
|
- Pods
|
||||||
- StatefulSet: 48
|
- Deployments
|
||||||
- Service: 46
|
- Services
|
||||||
- DaemonSet: 44
|
- ConfigMaps
|
||||||
- Ingress: 42
|
- Secrets
|
||||||
- Secret: 24
|
- Krates (created by user)
|
||||||
- ConfigMap: 22
|
|
||||||
- PVC: 18
|
|
||||||
- Pod: 16
|
|
||||||
|
|
||||||
### Character Matching
|
### Resource Discovery
|
||||||
- Sequential character match
|
1. Initial load: Fetch all resources from `/api/resources`
|
||||||
- Bonus for consecutive matches: +3
|
2. Real-time updates: WebSocket `/ws/watch` for changes
|
||||||
- Bonus for word-boundary starts: +5
|
3. Local cache: Store in Zustand krateStore
|
||||||
|
4. Filtering: Apply fuzzy search + type filter
|
||||||
|
|
||||||
### Results
|
## Results Organization
|
||||||
- Capped at 8 results
|
|
||||||
- Debounce: 16ms (one animation frame) for performance with large datasets
|
|
||||||
|
|
||||||
## Type Filter
|
### Search vs Quick Actions
|
||||||
|
- Search results: Resource matches (top section)
|
||||||
|
- Quick actions: Create new krate, open specific view (bottom section)
|
||||||
|
|
||||||
### Tab Quick-Filter
|
### Quick Actions
|
||||||
- Typing type alias (e.g., `pod`, `svc`) shows ghost hint: `⇥ Pods`
|
- `Create krate for ns/{namespace}`
|
||||||
- Tab locks filter as pill
|
- `All pods`
|
||||||
- Backspace removes filter (only when empty query and filter active)
|
- `All services`
|
||||||
- Tab cycles through type filters (most-recently-opened first)
|
- `All deployments`
|
||||||
|
- `Search again for...`
|
||||||
## View Shortcuts
|
|
||||||
|
|
||||||
### Key Mapping
|
|
||||||
All use **⌥ (Alt/Option)**, NOT Ctrl (reserved for terminal):
|
|
||||||
- `⌥L` = logs
|
|
||||||
- `⌥S` = shell
|
|
||||||
- `⌥D` = describe
|
|
||||||
- `⌥Y` = yaml
|
|
||||||
|
|
||||||
### Implementation
|
|
||||||
- Use `event.code` (layout-safe, e.g., `KeyL`), not `event.key`
|
|
||||||
- Multiple views can be opened before closing spotlight
|
|
||||||
- Views stack as windows in one krate
|
|
||||||
|
|
||||||
## Keyboard Navigation
|
## Keyboard Navigation
|
||||||
|
|
||||||
| Key | Context | Action |
|
| Key | Context | Action |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| ↑ / ↓ | Spotlight | Navigate results |
|
| ↑ / ↓ | Spotlight open | Move selection |
|
||||||
| Tab | Spotlight | Apply type filter / cycle filters |
|
| PageUp / PageDown | Spotlight open | Page navigation |
|
||||||
| Backspace | Spotlight (empty query, filter active) | Clear type filter |
|
| Home / End | Spotlight open | First / last item |
|
||||||
| Enter | Spotlight (result selected) | Open default view + close |
|
| Enter | Item selected | Create krate / open resource |
|
||||||
| ⌥L/S/D/Y | Spotlight | Open that view for selected result |
|
| Esc | Spotlight open | Close spotlight |
|
||||||
| Esc | Spotlight | Close spotlight |
|
| Space | Input focused | Search space character |
|
||||||
|
| Ctrl+K | Anywhere | Open spotlight |
|
||||||
|
| / | Anywhere | Open spotlight |
|
||||||
|
|
||||||
## Auto-Close Behavior
|
## View Shortcuts
|
||||||
- After 500ms of keyboard idle (no more keypresses)
|
All use **Option/Alt** key:
|
||||||
- Spotlight closes automatically
|
- `⌥L`: Open logs view (if supported)
|
||||||
- Camera flies to the newly created krate
|
- `⌥S`: Open shell view (if supported)
|
||||||
- Debounce timer resets on each keypress
|
- `⌥D`: Open describe view
|
||||||
|
- `⌥Y`: Open YAML view
|
||||||
|
|
||||||
## Search Results Include
|
## Krate Creation Flow
|
||||||
- **Existing krates** (jump to working set, camera flies there)
|
1. User selects resource from spotlight
|
||||||
- **Namespace results** (`ns/payments`) → opens collection window
|
2. Create new krate with:
|
||||||
- **Category results** (`All Pods`, `All Services`) → opens collection window
|
- Title: Resource name
|
||||||
- **Kubernetes objects**: pods, deployments, services, secrets, configmaps, etc.
|
- Color: Namespace color palette
|
||||||
|
- Position: Find non-overlapping location
|
||||||
|
- Windows: Create detail windows for selected views
|
||||||
|
3. Fly camera to new krate
|
||||||
|
4. Zoom to 0.92
|
||||||
|
|
||||||
## State
|
## State Management
|
||||||
|
|
||||||
|
### Spotlight State
|
||||||
```typescript
|
```typescript
|
||||||
interface SpotlightState {
|
interface SpotlightState {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
query: string;
|
query: string;
|
||||||
filterType: string | null;
|
filterType: string | null;
|
||||||
sel: number; // selected result index
|
sel: number;
|
||||||
navigated: boolean; // true after ↑/↓ press
|
navigated: boolean;
|
||||||
session: { // tracks views opened before closing
|
}
|
||||||
kid: string;
|
|
||||||
objId: string;
|
type FilterType = 'all' | 'krate' | 'namespace' | 'pod' | 'deployment' | 'service'
|
||||||
views: string[];
|
```
|
||||||
} | null;
|
|
||||||
|
### Search State
|
||||||
|
```typescript
|
||||||
|
interface SearchState {
|
||||||
|
results: SearchResult[];
|
||||||
|
loading: boolean;
|
||||||
|
error: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SearchResult {
|
||||||
|
id: string;
|
||||||
|
type: string;
|
||||||
|
name: string;
|
||||||
|
namespace?: string;
|
||||||
|
score: number;
|
||||||
|
icon: string;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Visual Design
|
||||||
|
|
||||||
|
### Spotlight Panel
|
||||||
|
- Position: Fixed, centered horizontally at top of canvas
|
||||||
|
- Width: 600px
|
||||||
|
- Max height: 500px
|
||||||
|
- Background: `#1a1e26`
|
||||||
|
- Border: `1px solid rgba(140,165,200,0.18)`
|
||||||
|
- Border-radius: 12px
|
||||||
|
- Shadow: `0 10px 40px rgba(0,0,0,0.5)`
|
||||||
|
|
||||||
|
### Results Item
|
||||||
|
- Hover background: `rgba(255,255,255,0.04)`
|
||||||
|
- Selected: `accentDim` (subtle accent color)
|
||||||
|
- Left border: 2px solid accent when selected
|
||||||
|
- Padding: 8px 12px
|
||||||
|
- Font-size: 14px
|
||||||
|
|
||||||
|
### Color Palette (by namespace)
|
||||||
|
- `default`: `#6fb1ff`
|
||||||
|
- `kube-system`: `#9c88ff`
|
||||||
|
- `kube-public`: `#4dd6e8`
|
||||||
|
- Custom namespaces: Pick from palette
|
||||||
|
|
||||||
## Gotchas
|
## Gotchas
|
||||||
1. **Fuzzy search speed**: For real clusters with thousands of objects, debounce by 16ms or run in Web Worker
|
1. **Space character**: When spotlight open, space goes to search (not canvas pan)
|
||||||
2. **View shortcuts**: Must use `event.code` for layout safety across OS/keyboard layouts
|
2. **Esc handling**: Must be handled in spotlight, not propagate to canvas
|
||||||
3. **Auto-close timer**: Reset on each keypress, don't close during active typing
|
3. **Fuzzy search**: Use debounced search for large datasets
|
||||||
4. **Pre-seeding**: All printable characters (not just letters) should pre-seed spotlight
|
4. **Keyboard routing**: When spotlight open, route keys to spotlight, not canvas
|
||||||
|
5. **Focus management**: Auto-focus input, trap Tab cycle when open
|
||||||
|
6. **Selection reset**: Reset selection on query change
|
||||||
|
|
||||||
|
## Future Enhancements
|
||||||
|
- Quick pick recent resources
|
||||||
|
- Star/favorite resources
|
||||||
|
- Saved searches
|
||||||
|
- Search history
|
||||||
|
- Resource previews
|
||||||
|
- Multi-resource selection
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Top Bar Feature Specification
|
# Design Document: Top Bar Feature Specification
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
The top bar is the primary navigation and status bar for the application. It provides quick access to cluster information, user presence, and global actions.
|
The top bar is the primary navigation and status bar for the application. It provides quick access to cluster information, user presence, and global actions.
|
||||||
|
|||||||
Reference in New Issue
Block a user