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
This commit is contained in:
Hermes Agent
2026-06-16 08:32:47 -04:00
parent b31fa3cde5
commit 78f19cde7d
12 changed files with 2709 additions and 2 deletions

188
design/admin-drawer.md Normal file
View File

@@ -0,0 +1,188 @@
# Admin Drawer Feature Specification
## 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.
## Trigger
### Activation
- Button: `◉ admin` in top bar
- Toggle: Click to open/close
- Close on: clicking backdrop, pressing Esc
### Modal Overlay
- Backdrop: `rgba(7,9,13,.4)`
- Click backdrop: Close drawer
- Click outside: Close drawer
## Drawer Layout
### Container
- Width: 380px
- Position: `absolute; right: 0; top: 0; bottom: 0; z-index: 56`
- Background: `rgba(13,17,24,.98)`
- Border: `border-left: 1px solid rgba(140,165,200,.2)`
- Animation: Slide-in from right
### Content Structure
- Header (fixed, sticky)
- Scrollable user list
- Footer (optional, for actions)
## User Card Structure
For each connected user (including self):
### Avatar Section
- Circle: 40px直径
- Background: User color from palette
- Content:
- For self: `★` (star)
- For others: 2-letter initials
- Hover: Show full name + status tooltip
### Identity
- Name (bold)
- Status:
- Active: `active · ns/payments`
- Idle: `idle 5m` (or `idle Nm`)
### Activity Info
- Current spotlight query (if open)
- Display: `🔍 pods in payments`
- "typing…" pulsing indicator (if currently typing)
- Krate count: `N krates open`
### Open Krate List
Collapsible section showing each active krate:
- Clickable row
- Content:
- Krate name
- View-letter badges: `Y`, `D`, `L`, `S`
- Status dot (health indicator)
- **Spectate**: Click any krate row to:
- Close admin panel
- Fly camera to that krate
- Zoom to center the krate at `zoom: 0.92`
### Self User Special Case
- Real-time state updates
- Shows actual spotlight query
- Lists user's actual open krates
- Updates as user interacts
## Real-Time Updates
### Presence Protocol
Each client publishes to awareness channel:
```
{
userId: string,
name: string,
color: string,
lastActive: timestamp,
query: string | null, // spotlight query
krateIds: string[], // list of open krate IDs
cursor: { x, y } | null // optional cursor position
}
```
### Update Frequency
- On connection/disconnection: immediate
- On spotlight open/close: immediate
- On krate create/delete: immediate
- Periodic heartbeat: every 5 seconds
## Keyboard Navigation
| Key | Context | Action |
|---|---|---|
| Esc | Admin drawer open | Close drawer |
| ↑ / ↓ | Drawer focused | Navigate user cards |
| Click user card | User list | Expand/collapse krate list |
| Click krate row | Krate list | Spectate (fly to krate) |
| Click backdrop | Anywhere | Close drawer |
## User Sorting
- Self user: Always first
- Active users: Ordered by last active (most recent first)
- Idle users: Ordered by last active
- Collapsed by default: Minimize height
## Visual Feedback
### Active/Idle Indicators
- Active: Solid color avatar, no dimming
- Idle: Dimmed avatar (80% opacity), `idle Nm` label
### Typing Indicator
- Text: `typing…`
- Color: Muted accent
- Animation: Pulsing opacity or small bounce
- Show when user has spotlight open AND hasn't typed in <1s
### Status Dots
- Krate status: Green/amber/red dot (matching window content)
- Pod status dot: Already in krate view (reuse same color logic)
## Admin Features (Optional Future)
### Control Actions
- Notify user: Send notification to specific user
- Suspend: Temporarily disconnect user (for debugging)
- View logs: See admin-level logs per user
### Spectate Controls
- Step forward/backward: Next/previous krate
- Cycle through users: Left/right arrow
- Lock view: Prevent camera from moving (spectator mode)
## State Management
### Drawer State
```typescript
interface AdminState {
open: boolean;
users: {
id: string;
name: string;
color: string;
self: boolean;
status: 'active' | 'idle';
idleMinutes?: number;
query?: string;
typing: boolean;
krateIds: string[];
}[];
focusedUserId: string | null;
}
```
### User State
```typescript
interface UserPresence {
userId: string;
name: string;
color: string;
lastActive: number; // timestamp
lastTyping: number; // timestamp
spotlightQuery: string | null;
openKrates: KratePresence[];
}
interface KratePresence {
krateId: string;
label: string;
viewCount: number;
views: string[]; // ['Y', 'D', 'L', 'S']
status: string; // from first window
}
```
## Gotchas
1. **Self-updates**: User's own card must show real-time state, not cached data
2. **Focus management**: Keep focus on drawer when open, trap Tab cycle
3. **Idle detection**: 60 seconds of no activity = idle
4. **Typing indicator**: Debounce by 1000ms after last keypress
5. **Spectate behavior**: Close admin panel first, THEN fly camera
6. **Roster overflow**: Handle >10 users gracefully (scroll, paginate, or truncate)

312
design/architecture.md Normal file
View File

@@ -0,0 +1,312 @@
# Top-Level Application Architecture
## Tech Stack Recommendations
### Frontend
- **Framework**: React 18 + TypeScript
- **Styling**: CSS-in-JS (styled-components or vanilla-extract) or Tailwind CSS
- **Terminal**: xterm.js
- **Real-Time**: Yjs + y-websocket (CRDT collaboration)
- **Build Tool**: Vite
- **Fonts**: IBM Plex Sans + IBM Plex Mono (Google Fonts or self-hosted)
- **State Management**: Zustand or React Context (keep it simple)
### Backend
- **Language**: Go
- **Kubernetes Client**: kubernetes/client-go
- **WebSockets**: gorilla/websocket
- **CRDT Backend**: Redis (single-node) or y-leveldb/y-mongodb (multi-node)
- **Build/Deployment**: Docker, Kubernetes
## Project Structure
```
krates/
├── client/ # Frontend application
│ ├── src/
│ │ ├── components/ # React components
│ │ │ ├── canvas/ # Canvas and world layer
│ │ │ │ ├── WorldLayer.tsx
│ │ │ │ ├── GridOverlay.tsx
│ │ │ │ └── Minimap.tsx
│ │ │ ├── krate/ # Krate and windows
│ │ │ │ ├── Krate.tsx
│ │ │ │ ├── KrateHeader.tsx
│ │ │ │ ├── DetailWindow.tsx
│ │ │ │ └── CollectionWindow.tsx
│ │ │ ├── Spotlight.tsx
│ │ │ ├── TopBar.tsx
│ │ │ ├── AdminDrawer.tsx
│ │ │ ├── ShellTerminal.tsx # xterm.js wrapper
│ │ │ └── LogsViewer.tsx
│ │ ├── hooks/ # Custom React hooks
│ │ │ ├── useCanvas.ts
│ │ │ ├── useSpotlight.ts
│ │ │ ├── useWebSocket.ts
│ │ │ └── useKubernetes.ts
│ │ ├── state/ # State management
│ │ │ ├── canvasStore.ts
│ │ │ ├── krateStore.ts
│ │ │ └── spotlightStore.ts
│ │ ├── utils/
│ │ │ ├── fuzzy.ts # Fuzzy search algorithm
│ │ │ ├── crdt.ts # Yjs setup
│ │ │ ├── keyboard.ts # Keyboard handling
│ │ │ └── math.ts # Canvas math helpers
│ │ ├── App.tsx
│ │ └── main.tsx
│ ├── public/ # Static assets
│ └── package.json
├── server/ # Backend service
│ ├── cmd/
│ │ └── server/
│ │ └── main.go
│ ├── internal/
│ │ ├── api/ # HTTP endpoints
│ │ │ ├── handlers/
│ │ │ └── routes.go
│ │ ├── ws/ # WebSocket handlers
│ │ │ ├── shell.go
│ │ │ ├── logs.go
│ │ │ └── watch.go
│ │ ├── k8s/ # Kubernetes client wrappers
│ │ │ ├── client.go
│ │ │ ├── resources.go
│ │ │ └── watch.go
│ │ ├── crdt/ # Yjs integration
│ │ │ └── provider.go
│ │ └── auth/ # Authentication/authorization
│ │ └── token.go
│ ├── pkg/ # Reusable packages
│ └── go.mod
├── design/ # This folder
│ ├── canvas.md
│ ├── spotlight.md
│ ├── krate.md
│ ├── detail-window.md
│ ├── top-bar.md
│ ├── admin-drawer.md
│ ├── minimap.md
│ ├── backend.md
│ └── architecture.md
└── README.md
```
## State Management Strategy
### Global State (Zustand)
```typescript
// canvasStore.ts
interface CanvasState {
camX: number;
camY: number;
zoom: number;
flying: boolean;
dragging: boolean;
collapsed: boolean;
spacePanning: boolean;
}
// krateStore.ts
interface KrateStore {
krates: Map<string, Krate>;
selectedKrateId: string | null;
}
// spotlightStore.ts
interface SpotlightStore {
open: boolean;
query: string;
filterType: string | null;
sel: number;
navigated: boolean;
}
// userStore.ts
interface UserStore {
currentUser: User;
presence: Map<string, UserPresence>;
}
```
### Collection State (Per-Window)
```typescript
// stored in krate.windows[].state or separate map
interface CollectionState {
search: string;
view: 'list' | 'tree';
sel: number;
}
```
## WebSocket Architecture
### Connection Setup
```
1. Connect to /ws/sync (CRDT + awareness)
2. Connect to /ws/watch (resource updates)
3. On-demand connections:
- /ws/shell (when shell window opened)
- /ws/logs (when logs window opened)
```
### Message Format
```
// CRDT sync
{
type: 'sync',
payload: Uint8Array // Yjs update encoded
}
// Awareness presence
{
type: 'awareness',
updates: UserPresence[]
}
// Resource watch
{
type: 'resource',
action: 'add' | 'update' | 'delete',
kind: string, // 'Pod', 'Deployment', etc.
resource: K8sObject
}
// Shell output
{
type: 'shell:output',
data: string // stdout/stderr
}
// Shell input
{
type: 'shell:input',
data: string // keypresses, etc.
}
```
## Build & Deployment
### Development
```
# Frontend
cd client
npm install
npm run dev # Vite dev server
# Backend
cd server
go run cmd/server/main.go
```
### Production
```
# Backend
cd server
go build -o krates-server
./krates-server --config config.yaml
# Frontend
cd client
npm run build
# Serve static files with nginx or similar
```
### Docker
```dockerfile
# Backend Dockerfile
FROM golang:1.21-alpine
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN go build -o /krates ./cmd/server
CMD ["/krates"]
# Frontend Dockerfile
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
CMD ["npm", "start"]
```
## Performance Optimization
### Frontend
1. **Virtualization**: Don't render off-screen krates if world is large
2. **Debounce**: 16ms debouncing on all frequent events
3. **Web Workers**: Fuzzy search in worker for large datasets
4. **Memoization**: Reuse expensive calculations (transforms, layouts)
5. **CSS optimization**: Use transforms, avoid layout thrashing
### Backend
1. **Caching**: Cache static resource lists (with watch for updates)
2. **Connection pooling**: Reuse k8s API connections
3. **Rate limiting**: Per-user limits on WebSocket connections
4. **Compression**: WebSocket message compression
5. **Batching**: Batch resource updates when possible
## Testing Strategy
### Unit Tests
- Fuzzy search algorithm
- Coordinate transformations
- CRDT conflict resolution
- WebSocket message formatting
### Integration Tests
- Kubernetes API integration
- WebSocket connection lifecycle
- Multi-user presence sync
### E2E Tests
- User flows: search → spotlight → krate creation
- Multi-user: user A creates krate, user B sees it
- Drag/drop and resize interactions
## Security Checklist
### Authentication
- [ ] JWT or session-based auth on all WebSocket connections
- [ ] Token validation on every message
- [ ] Expire inactive sessions
### Authorization
- [ ] Namespace-level RBAC enforcement
- [ ] Block access to unauthorized resources
- [ ] Audit logging
### Data Protection
- [ ] Never log sensitive data (especially decoded secrets)
- [ ] Sanitize user inputs
- [ ] Escape HTML in text display
- [ ] CSP headers on frontend
### Network
- [ ] TLS/HTTPS for all connections
- [ ] WSS for WebSocket connections
- [ ] Origin validation on WebSocket upgrade
## Future Enhancements (Backlog)
### Already Identified
- Tile layout presets (logs hero, split, grid)
- Port-forwarding window
- Events window
- Diff view (compare YAMLs)
- Multi-cluster support
### Additional Ideas
- Keyboard shortcuts customization
- Workspace presets (save/restore layouts)
- Annotations/markers on canvas
- Shared annotations (multi-user notes)
- History/undo stack
- Keyboard macro recording
- Export workspace (screenshots, layouts)

264
design/backend.md Normal file
View File

@@ -0,0 +1,264 @@
# Backend & Real-Time Sync Feature Specification
## Overview
The backend service aggregates Kubernetes API data and provides real-time WebSocket connections for shell, logs, and multi-user collaboration.
## Backend Architecture
### Recommended Stack
- **Language**: Go (for Kubernetes client integration)
- **Kubernetes Client**: kubernetes/client-go
- **WebSockets**: gorilla/websocket
- **CRDT Layer**: Yjs + y-websocket
- **Storage**: Redis or in-memory (single-node), y-leveldb/y-mongodb (multi-node)
### Service Responsibilities
1. Wrap kubectl API access
2. Provide WebSocket multiplexer for:
- Shell sessions
- Log streaming
- Watch API updates
3. Handle CRDT sync for shared workspace state
4. Broadcast user presence/awareness
## Kubernetes Data Sources
### Required Resources
- Namespaces
- Deployments
- StatefulSets
- DaemonSets
- Services
- Ingresses
- ConfigMaps
- Secrets (with base64 decoding server-side)
- PVCs
- Events
- CRDs
### Live Updates
Implementation: Kubernetes Watch API
```
GET /api/v1/namespaces/{ns}/pods?watch=true
GET /api/v1/namespaces/{ns}/services?watch=true
GET /apis/apps/v1/namespaces/{ns}/deployments?watch=true
# etc for each resource type
```
### Aggregation Strategy
- Single endpoint: `/api/watch` that multiplexes all watch streams
- Or: Resource-specific endpoints (`/api/watch/pods`, `/api/watch/services`, etc.)
- Include namespace filtering in requests
### Secret Decoding
- Option 1: Server-side decode before sending
- Option 2: Client-side decode (safer, never log)
- If client-side: Include decoded flag in response
- **Never log decoded values**
## WebSocket Endpoints
### Shell Endpoint
```
/ws/shell
```
- **Upstream**: `kubectl exec -it <pod> -n <ns> -- sh`
- **Protocol**: Bidirectional WebSocket
- Client → Server: Keycodes, terminal resize
- Server → Client: stdout, stderr
- **Terminal**: xterm.js on client
- **Backend**: Use k8s Go client's Exec API with streaming
### Logs Endpoint
```
/ws/logs
```
- **Upstream**: `kubectl logs --follow <pod> -n <ns>`
- **Protocol**: WebSocket text frames
- **Streaming**: Each line as separate message
- **Client**: Auto-scroll unless user scrolled up
### Watch Endpoint
```
/ws/watch
```
- **Purpose**: Broadcast resource changes to all connected clients
- **Payload**: JSON Patch or full object update
- **Filtering**: Per-client namespace/resource filters
## Multi-User Sync (The Yard)
### CRDT Strategy
- **Library**: Yjs (proven CRDT implementation)
- **Provider**:
- Single-node: in-memory or Redis
- Multi-node: y-leveldb, y-mongodb, or y-webrtc
- **WebSocket**: y-websocket for real-time sync
### Synced State
Only sync the *structure* of the workspace, not content:
- ✅ Krate positions (wx, wy)
- ✅ Krate existence (create/delete)
- ✅ Krate minimize state
- ✅ Window layout (grid positions, sizes)
- ✅ Room/canvas camera state (optional)
- ❌ Window content (logs, YAML, shell) — each client streams independently
### Presence/Awareness
Broadcast ephemeral user state:
```
{
userId: string,
name: string,
color: string,
cursorPosition: { x, y } | null,
activeKrateId: string | null,
spotlightQuery: string | null,
timestamp: number
}
```
Implementation:
- Each client publishes to Yjs Awareness channel
- Admin panel subscribes to awareness updates
- Update interval: every 5 seconds or on significant change
## Real-Time Shell Implementation
### Client-Side (xterm.js)
```
1. Connect WebSocket
2. Initialize xterm with {
rows: 24,
cols: 80,
fontFamily: 'IBM Plex Mono',
fontSize: 12
}
3. Attach xterm to WebSocket
- term.write() → send to server
- WebSocket.onmessage → term.write()
4. Handle resize: term.resize(cols, rows)
5. Attach to DOM
```
### Server-Side (Go + k8s client)
```
1. Extract pod/ns from URL: /ws/shell?pod=xxx&ns=yyy
2. Use k8s-exec Go library:
req := clientset.CoreV1().RESTClient().Post()
req.Name(pod).Namespace(ns).Resource("pods").SubResource("exec")
req.VersionedParams(&v1.ExecOptions{
Stdin: true,
Stdout: true,
Stderr: true,
Terminal: true,
}, scheme.ParameterCodec)
// Create exec stream
3. Duplex: WebSocket ↔ Exec stream
4. Handle close gracefully
```
## Real-Time Logs Implementation
### Client-Side
```
1. Connect WebSocket
2. Maintain scroll position state
3. On new message:
- Append to log buffer
- If wasAtBottom: scroll to bottom
- Else: keep existing scroll position
4. Colorize lines on receive (ERROR/WARN/INFO patterns)
```
### Server-Side
```
1. kubectl logs --follow logic via exec stream
2. Stream lines as WebSocket messages
3. Add keep-alive ping every 30s
4. Reconnect on disconnect (client-side)
```
## Backend API Endpoints
### HTTP Endpoints (REST)
```
GET /api/cluster # Cluster metadata
GET /api/resources # All resources (cached, for initial load)
GET /api/resources/pods # Filtered by namespace (optional)
GET /api/resources/deployments
GET /api/resources/services
GET /api/resources/secrets
GET /api/resources/configmaps
GET /api/resources/namespaces
GET /api/resources/crds
GET /api/resource/{kind}/{name}?ns={namespace}
GET /api/health # Backend health check
```
### WebSocket Endpoints
```
/ws/shell # Shell session
/ws/logs # Log streaming
/ws/watch # Resource watch updates
/ws/sync # CRDT sync + awareness
```
## Error Handling
### WebSocket Disconnects
- Shell: Notify user, keep terminal buffer (don't clear)
- Logs: Auto-reconnect with exponential backoff
- Watch: Auto-reconnect, fetch delta on reconnect
### Backpressure
- Shell: Buffer output if client slow, drop old lines if buffer full
- Logs: Same strategy
- Watch: Batch updates if high frequency
### Connection Limits
- Per-user limits: max concurrent shells/logs per namespace
- Global limits: max connections
- Reject with 429 if limit exceeded
## Security Considerations
### Authentication
- JWT or session cookie
- Validate token on WebSocket upgrade
- Bind connections to user ID
### Authorization
- Namespace-level RBAC
- User can only access resources they have permission for
- Backend must enforce (not just client-side filtering)
### Secret Handling
- Never log decoded secrets
- Sanitize before display
- Consider explicit user action to reveal (future enhancement)
## Scalability
### Single-Node (Development)
- In-memory Yjs provider
- Redis for presence
- Direct k8s API calls
### Multi-Node (Production)
- Shared Redis for Yjs CRDT state
- WebSocket leader election (one node handles all CRDT updates)
- or: y-webrtc for peer-to-peer (simpler, less reliable)
## Health Monitoring
- WebSocket connection count
- Active shell sessions
- API latency (p50, p95, p99)
- CRDT sync lag
- Error rates by endpoint
## Testing Strategy
1. Unit tests for Kubernetes API wrappers
2. Integration tests with kind (Kubernetes in Docker)
3. Load tests for WebSocket connections
4. CRDT conflict resolution tests

105
design/canvas.md Normal file
View File

@@ -0,0 +1,105 @@
# Canvas Feature Specification
## Overview
The canvas is the infinite zoomable/pannable workspace where krates live. It serves as the primary interaction surface for exploring Kubernetes clusters.
## Core Responsibilities
- Render an infinite 2D workspace (12000×8000px world)
- Handle camera navigation (pan/zoom)
- Manage Level of Detail (LOD) based on zoom level
- Display a CAD grid overlay
- Coordinate global interactions (space panning, spotlight trigger)
## Technical Requirements
### Viewport & World Layer
- Full viewport (`position: fixed; inset: 0`)
- Background color: `#0b0e13`
- World layer: 12000×8000px div transformed via `translate(camX, camY) scale(zoom)`
- All krates/windows are absolutely positioned children
### Grid Overlay (CSS background-image)
- Fine grid: 34px spacing, rgba(125,145,175,.04)
- Coarse grid: 170px spacing, rgba(125,145,175,.075)
- Both X and Y repeating linear gradients
### Camera State
```typescript
interface CanvasState {
camX: number; // world-to-screen X offset
camY: number; // world-to-screen Y offset
zoom: number; // 0.16 2.2; default 0.92
flying: boolean; // true during animated camera transitions
dragging: boolean; // true during pan drag
collapsed: boolean; // LOD: true when zoom < 0.4
spacePanning: boolean; // true while space is held
}
```
## Navigation
### Scroll Wheel
- ** Default **: Pan canvas
- ** Ctrl/⌘ + scroll **: Zoom canvas (intercepted via wheel event with `e.ctrlKey || e.metaKey`)
### Space + Drag Panning
- Critical: must work even when shell window has keyboard focus
- Implementation:
- `document.addEventListener('keydown', handler, { capture: true })` on space
- Set "panning" flag, prevent default on body
- Forward `mousemove` to canvas pan handler
- Release on `keyup` for space
- Do NOT use `e.stopPropagation()` - allow event propagation
### Click/Typed Key Triggers
- ** Click on empty canvas **: Open spotlight
- ** Type any key **: Open spotlight with that character pre-seeded
### Zoom Levels / LOD
- ** 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
- ** Hysteresis**: Collapse at 0.4, re-expand at 0.5 (prevents flickering)
### Animations
- CSS `transform` with `transition` only for programmatic "fly" animations: `.52s cubic-bezier(.22,.8,.28,1)`
- During user scroll/drag: `transition: none` for immediacy
## UI Components
### Top Bar
- Position: `absolute; top: 0; left: 0; right: 0; z-index: 10; height ~56px`
- Layout: `display: flex; align-items: center; padding: 13px 18px`
- Background: none (transparent)
- Left side: Logo pill, cluster pill, krate count pill
- Right side: Synced pill, admin button, roster avatars
### Bottom Hint Bar
- Position: `absolute; right: 18px; bottom: 18px`
- Shows keyboard hints (status bar)
### Zoom Pill
- Position: `absolute; left: 18px; bottom: 18px`
- Displays current zoom percentage
### Minimap
- Position: `absolute; right: 18px; bottom: 64px`
- Size: 180×120px
- Shows all krates as small rectangles
- Viewport indicator rectangle showing current view frustum
- Click anywhere to fly camera to that world position
## Interaction Summary
| Interaction | Behavior |
|---|---|
| Scroll wheel | Pan canvas |
| Ctrl/⌘ + scroll | Zoom canvas |
| Space + drag | Pan canvas (even over windows/shells) |
| Click empty canvas | Open spotlight |
| Type any key | Open spotlight pre-seeded |
| Minimap click | Fly camera to position |
## Gotchas
1. **Space pan over shells**: Must intercept with `{ capture: true }`, prevent default, but don't stop propagation
2. **Ctrl/⌘+scroll**: Use `{ capture: true }` on canvas wheel handler to intercept before window scroll containers
3. **Camera transitions**: Only animate during programmatic fly, not user gestures
4. **LOD state**: Use stable boolean `collapsed` in state, not derived from `zoom` on every render

243
design/collection-window.md Normal file
View File

@@ -0,0 +1,243 @@
# Collection Window Feature Specification
## 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.
## Default Size
- Width: 540px
- Height: 480px (larger than detail windows for better data visibility)
## Header
### Title
- Format: `pods · ns/payments` or `All Pods`
- Bold styling
### Status Summary
- Health badges: `✓ N / ⚠ N / ✗ N`
- Colors: OK=green, Warn=amber, Error=red
- Order: Ready/Warn/Error counts
### Filter Input
- Auto-focused on open
- Styling:
- `font-family: IBM Plex Mono`
- `font-size: 12px`
- Placeholder: `Filter...`
- Instant filtering (no debounce needed for <1000 items)
### View Toggle
- **List mode** button: `list icon`
- **Tree mode** button: `tree icon`
- Toggles between list and tree display
- Keyboard shortcut: `t` or click toggle button
### Keyboard Hint
- Always visible, muted text: `↑↓ ⌥L/S/D/Y`
- Shows navigation + view shortcuts
## List Mode
### Row Structure
Each row displays:
- Shape glyph (8px, color-coded by object type)
- Name (primary text)
- Relation tag (muted, for tree mode)
- Health metric (icon or text)
- Type badge (small, muted)
- View buttons: `⌥L`, `⌥S`, `⌥D`, `⌥Y` (hover show tooltips)
- Status dot (green/amber/red)
### Selection
- Selected row:
- Background: `accentDim` (subtle accent color)
- Left border: `2px solid accent`
- Hover row:
- Background: `rgba(255,255,255,.04)`
- Border-radius: 4px
### Sorting
1. Primary: Health status
- Degraded/failed first
- Then pending
- Then ready/running
2. Secondary: Alphabetical within each group
## Tree Mode (k9s xray-style)
### Hierarchy
- **Workloads expand to pods**:
- Deployment → pods
- StatefulSet → pods
- DaemonSet → pods
- **Pods expand to resources**:
- Pods show configMaps, secrets, PVCs
- **Services/Ingresses expand to targets**:
- Show selectors
- Show endpoints
### Relation Tags
Show as small icons or labels:
- `pod` (attached to pod rows)
- `configMap` (mounted volumes)
- `secret` (mounted secrets)
- `volume` (PVC)
- `selects` (service → pods)
- `routes` (ingress → service)
- `used by` (reverse relationships)
### Indentation
- Per depth: `20px` indentation
- Connector: L-shaped border element (vertical + horizontal)
- Visual hierarchy clear at a glance
### Expand/Collapse
- Click chevron/arrow: Toggle children
- Double-click name: Toggle
- Click anywhere else on row: Select row
### Filtering in Tree Mode
- Matching nodes + **all their ancestors** must stay visible
- Ancestors shown even if not matching, but with different styling
- Children hidden if no match
- Preserves hierarchy readability
## Keyboard Navigation
### List/Tree Mode
| Key | Context | Action |
|---|---|---|
| ↑ / ↓ | Collection focused | Move row selection |
| PageUp / PageDown | Collection focused | Move selection by page |
| Home / End | Collection focused | First / last row |
| Enter | Row selected | Open default view for selected row |
| ⌥L / ⌥S / ⌥D / ⌥Y | Row selected | Open view for selected row |
| Esc | Filter focused | Blur filter input |
| Esc | Collection focused | No-op (handle in krate) |
| t | Anywhere | Toggle list/tree view |
### Mouse + Type Routing
- Mouse-over collection window + type = focus filter input
- Similar to shell routing but for filter input
- Use `hoveredWindowId` ref to determine routing
## View Shortcuts
### Key Mapping (All use ⌥ Alt)
- `⌥L`: Open logs view (if supported)
- `⌥S`: Open shell view (if supported)
- `⌥D`: Open describe view
- `⌥Y`: Open YAML view
### Implementation
- Event listener with `event.code` for layout safety
- Open views in newly created krate (or existing if spotlight session)
- Shell tab disabled for non-exec types (opacity 0.45)
## View-Specific Content
### Logs View (Pods/Workloads Only)
- **Streaming**: WebSocket → `kubectl logs --follow`
- **Auto-scroll**: To bottom unless user scrolled up
- **Color coding**:
- ERROR: `#ef6f6f`
- WARN: `#e8b54a`
- INFO: `#b9c6d8`
- **Timestamps**: Optional toggle (hidden by default)
### Shell View (Pods Only)
- **Implementation**: xterm.js
- **Disabled**: For non-pod workloads, show disabled (muted)
- **Cursor**: Blinking block cursor
- **Key routing**: Route to shell when collected window hovered
### Describe View
- Human-readable object description
- All metadata sections
- Formatted for easy scanning
### YAML View
- Full object YAML
- **Secrets**: Decode data values
- Banner: `⊙ secret values auto-decoded`
- Monospace font, line numbers optional
## Filter Behavior
### Instant Filtering
- No debounce (fast for <1000 items)
- Debounce if dataset grows: 16ms
### Filter Logic
- Case-insensitive substring match
- Match against: name, labels, annotations, namespace
- For tree mode: Match name only (not ancestors)
### Clear Filter
- Esc key (when filter focused)
- Clear button (×) in input
- Empty string clears filter
### Keyboard Focus
1. Click collection window + type = focus filter
2. Click filter input = focus
3. Click anywhere else = blur filter, focus canvas shortcuts
## State Management
### Per-Window State
```typescript
interface CollectionState {
wid: string; // window ID
search: string; // filter text
view: 'list' | 'tree';
sel: number; // selected row index
expanded: Set<string>; // tree expansion state (node IDs)
}
```
### Data Loading
```typescript
interface CollectionData {
kind: string; // 'Pod', 'Service', etc.
namespace?: string; // undefined for "All Pods"
items: K8sObject[];
filtered: K8sObject[]; // after filter
treeData?: TreeNode[]; // tree mode intermediate
}
```
## Tree Node Structure
```typescript
interface TreeNode {
id: string; // unique within collection
name: string;
kind: string; // 'Pod', 'Deployment', etc.
obj: K8sObject | null; // null for synthetic nodes (e.g., "pods")
depth: number;
parentId: string | null;
children: string[]; // node IDs
relation: string; // 'pod', 'configMap', 'selects', etc.
hasChildren: boolean;
}
```
## Performance Considerations
### Large Datasets
- List virtualization: Only render visible rows (window height / row height)
- Tree optimization: Lazy expand (fetch children on expand, not preload)
- Filtering: Use efficient algorithms (no regex, simple string.match)
### Updates
- Throttle tree rebuild: 100ms
- Only rebuild if filter or data changed
- Keep expansion state on update (if node still exists)
## Gotchas
1. **Tree expansion**: Keep ancestors visible when filtering
2. **Auto-focus**: Focus filter input on window open (use ref + useEffect)
3. **View shortcuts**: Use `event.code` (layout-safe)
4. **Sorting priority**: Health first, then alphabetical
5. **Shell vs other types**: Disable shell tab for non-pod workloads
6. **Filter scope**: In tree mode, filter only on names, not on ancestors

176
design/detail-window.md Normal file
View File

@@ -0,0 +1,176 @@
# Detail Window Feature Specification
## 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.
## Core Responsibilities
- Display object data in one of four view types
- Allow window dragging within krate
- Support window resizing with proportional sibling adjustment
- Implement maximize/restore functionality
- Route keyboard focus on mouse-over
## Window Types
### Detail View (default)
- **Purpose**: Display static object information
- **Content types**: YAML, Describe, Logs (for pods/workloads)
### Collection Window
- **Purpose**: List/Tree view of multiple objects
- **Special features**: Filter input, selection highlighting, view shortcuts
## Window anatomy (380×362px default)
### Header (36px height)
- Left: Shape glyph (8px, color-coded by object type)
- Middle: View label (uppercase, accent-colored) + object name + status badge
- Right: × button (close window)
- **Cursor**: `grab` — drag to reposition within canvas
- **Double-click header**: Maximize / restore window
- **Drag**: Move window within krate
### Tab Bar
- Tabs: YAML · Describe · Logs · Shell
- Shell tab: disabled (opacity 0.45) for non-exec object types
- Active tab: accent underline/highlight
- Click tab: Switch view content
### Content Area (286px height)
- Element: `<pre>` (preformatted text)
- Styling:
- `overflow: auto`
- `height: 286px`
- `font-family: 'IBM Plex Mono'`
- `font-size: 12px`
- `line-height: 1.65`
- Scrollbar:
- Width: 8px
- Color: rgba(140,165,200,.18)
## Resize Behavior
### Resize Handle
- Positioned at bottom-right corner
- Drag to resize
- **Proportional adjustment**: When one window resized, siblings in same column/row resize proportionally
- Storage: Use `rowHeights` and `colWidths` arrays per krate (future enhancement)
### Window Grid
- **Column pitch**: 412px
- **Row pitch**: 416px
- **Window default**: 380×362px (leaves room for grip/padding)
## Maximize Functionality
### Maximize State
- Width: `viewport.width - 44px` (22px margins)
- Height: `viewport.height - topBarHeight(68px) - bottomBarHeight(66px)`
- Camera adjusted: `camX = 22 - krate.wx - window.dx`, `camY = 68 - krate.wy - window.dy`, `zoom = 1`
### Trigger
- **Key**: `z` (when mouse is over window)
- **Mouse**: Double-click header
- Restore: Same trigger again
## Keyboard Focus Routing
### Mouse-Over Detection
- Track `mouseenter`/`mouseleave` per window
- Set `hoveredWindowId` ref
- Clear on `mouseleave`
### Key Event Routing
Global `keydown` handler with `{ capture: true }`:
```
if (hoveredWindowId is shell) {
route keypress to shell
} else if (hoveredWindowId is collection) {
route keypress to filter input
} else {
let it fall through to canvas shortcuts
}
```
### Shell Special Case
- Space character goes to terminal (NOT spotlight)
- Must distinguish between canvas space-pan and shell space-input
## View-Specific Content
### YAML View
- Display full object YAML
- **Secret objects**: Base64-decode all data values
- Show banner: `⊙ secret values auto-decoded`
- Security: Handle carefully, never log decoded values
### Describe View
- Human-readable object description
- Formatted for readability
- All metadata sections included
### Logs View
- **Pods/workloads only**
- **Live-tailing**: Stream via WebSocket
- Auto-scroll to bottom on new lines (unless user scrolled up)
- Color-code:
- ERROR: `#ef6f6f`
- WARN: `#e8b54a`
- INFO: `#b9c6d8`
### Shell View
- **Implementation**: xterm.js (real terminal)
- **Backend**: WebSocket → `kubectl exec -it <pod> -n <ns> -- sh`
- **Keyboard routing**: Route keypresses to terminal, passthrough terminal output
- **Scroll wheel**: Scroll terminal content (NOT canvas pan)
- **Ctrl/⌘+scroll**: Zoom canvas (not terminal zoom)
## Collection Window Specifics
### Size
- Default: 540×480px (larger than detail windows)
### Header
- Title: `pods · ns/payments`
- Status summary badges: `✓ N / ⚠ N / ✗ N`
- Filter input (auto-focused on open)
- List / Tree toggle buttons
- Keyboard hint: `↑↓ ⌥L/S/D/Y`
### List Mode
- Rows: shape glyph + name + health metric + type badge + view buttons + status dot
- Selected row: `background: accentDim; border-left: 2px solid accent`
- Hover row: `background: rgba(255,255,255,.04)`
- Sorted: degraded/failed first, then pending, then ready; alpha within groups
### Tree Mode (k9s xray)
- Workloads expand to show pods
- Pods expand to show configMaps, secrets, PVCs
- Services/ingresses expand to show targets
- Relation tags: `pod`, `configMap`, `secret`, `volume`, `selects`, `routes`, `used by`
- Indent: `20px * depth`
- Filtering: Keep matching nodes + all ancestors for hierarchy
## Window State
```typescript
interface Window {
wid: string; // unique window ID
kind: 'detail' | 'collection';
tab: 'logs' | 'shell' | 'describe' | 'yaml';
dx: number; // offset within krate (column * 412)
dy: number; // offset within krate (row * 416)
w: number; // width
h: number; // height
isMaximized: boolean; // current state
prevW?: number; // previous width (for restore)
prevH?: number; // previous height (for restore)
}
```
## Gotchas
1. **Space key**: Must distinguish between canvas space-pan (capture with `{ capture: true }`) and shell space-input
2. **Ctrl/⌘+scroll**: Use `{ capture: true }` on canvas to intercept before window scroll containers
3. **Secret decoding**: Do server-side or careful client-side; never log decoded values
4. **Window resizing**: Proportional siblings requires storing `rowHeights`/`colWidths` arrays (not just individual `dx/dy`)
5. **Focus routing**: Mouse-over + type should focus shell/filter, not trigger canvas shortcuts
6. **Collection auto-focus**: On creation, focus filter input in next tick using ref + `useEffect`

101
design/krate.md Normal file
View File

@@ -0,0 +1,101 @@
# Krate (Window Group) Feature Specification
## 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.
## Core Responsibilities
- Group related windows together visually and logically
- Auto-fit frame around contained windows
- Manage window layout within krate boundaries
- Handle krate-level interactions (move, drag, collapse, expand)
## Visual Container
### Krate Frame
- **Dashed border**: `border: 1px dashed rgba(color, .3)`, `border-radius: 18px`
- **Background**: `rgba(color, .04)` (subtle color tint)
- **Auto-fit**: Frame adjusts to bounding box of all windows + 30px padding
- **Color**: Matches namespace palette (e.g., payments: `#6fb1ff`)
### Header Bar
Above the frame:
- Shows object name + status badge + window count
- **Drag handle**: Move entire krate
- **× button**: Dismiss krate
- **Minimize button** (`—`): Collapse to overview card
- **Double-click header**: Collapse/expand toggle
## Window Layout
### Grid System
- Windows tiled in 2-column grid
- Column 0: `dx: 0`
- Column 1: `dx: 412`
- Row height: 416px
- **Window default size**: 380×362px
### Properties
- Windows never overlap (snap-to-grid on release)
- When one window resized, siblings resize proportionally in same column/row
- Storage:
```typescript
interface Window {
wid: string;
kind: 'detail' | 'collection';
tab: 'logs' | 'shell' | 'describe' | 'yaml';
dx: number; // offset within krate (column * 412)
dy: number; // offset within krate (row * 416)
w: number;
h: number;
}
```
## Krate Interactions
### Dragging
- **Drag header** = move entire krate
- **Non-overlapping placement**: On drag release, nudge to avoid overlap with other krates
- **Gentle snap, not strict grid** (allows flexibility while preventing overlap)
### Collapse/Expand
- **Minimized state**: 230px-wide card showing:
- Name
- View-letter badges (Y/D/L/S)
- Status dot
- Window count
- Namespace
- **Double-click collapsed card**: Re-expand and fly camera to krate
### Camera Fly
- On krate creation: animate camera to center krate at `zoom: 0.92`
- Duration: `.52s cubic-bezier(.22,.8,.28,1)`
- Position: center of krate's bounding box
## Krate State
```typescript
interface Krate {
id: string;
objId: string | null; // null for collection krates
collScope?: { kind: 'namespace' | 'category', value: string };
label: string; // display name
status: string; // status badge text
color: string; // hex, from namespace palette
wx: number; wy: number; // world position (top-left of frame)
minimized: boolean;
windows: Window[];
seq: number; // for generating unique window IDs
}
```
## Non-Overlapping Placement Algorithm
1. Try computed default position
2. Check bounding boxes against all existing krates
3. If overlap, nudge by fixed increments (e.g., 20px right/down)
4. Repeat until no overlap
5. **Same algorithm** used on creation AND drag release
## Gotchas
1. **Auto-fit frame**: Re-compute bounding box whenever windows are added/removed/moved/resized
2. **Non-overlapping**: Use AABB (Axis-Aligned Bounding Box) collision detection
3. **Window grid**: Store `colWidths` and `rowHeights` arrays for proper resize relationships (future enhancement)
4. **Krate ID**: `objId` is null for collections; otherwise references the Kubernetes object ID

149
design/minimap.md Normal file
View File

@@ -0,0 +1,149 @@
# Minimap Feature Specification
## 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.
## Position & Size
### Placement
- Position: `absolute; right: 18px; bottom: 64px` (above zoom pill)
- Size: 180×120px
- Z-index: Must be above canvas but below spotlight (z-index: 20-30 range)
### Background
- Base: `rgba(16,20,28,.97)`
- Border: `1px solid rgba(140,165,200,.2)`
- Border-radius: `4px`
- Padding: None (full content area)
## Viewport Indicator
### Rectangle
- Shows current camera view frustum
- Position: Relative to minimap content (proportionally mapped)
- Size: Proportional to zoom level
- Color: Accent (e.g., `#4dd6e8` cyan) with 40% opacity
- Stroke: Solid line, 1px
### Mapping
- World position (wx, wy) → Minimap coordinates
- Formula:
```
mx = (wx + camX) / zoom scaling factor
my = (wy + camY) / zoom scaling factor
```
- Zoom mapping: Larger viewport indicator = lower zoom level
## Krate Visualization
### Representation
- Each krate = small rectangle (8×6px default)
- Color: Matches krate's namespace color
- Position: Relative to minimap origin
- Scaled down: ~1/60th of world space
### Content
- Krate positions only (not internal windows)
- All active krates on canvas
- Update on: krate create, delete, move
### Performance Optimization
- Only render visible krates (cull distant ones)
- Batch updates (throttle minimap render)
- Use CSS transform, not re-layout
## Interaction
### Click Navigation
- Click anywhere → fly camera to world position
- Calculate world coordinates from click position:
```
worldX = (clickX / minimapWidth) * worldWidth - viewportWidth/2
worldY = (clickY / minimapHeight) * worldHeight - viewportHeight/2
```
- Animate camera fly: `.52s cubic-bezier(.22,.8,.28,1)`
- Zoom: Set to `0.92` or user's preferred zoom
### Hover Effects (Optional)
- Hover krate rectangle → highlight preview on canvas
- Show tooltip: krate name, window count
## Canvas Synchronization
### Updates Triggered By
1. New krate creation
2. Krate movement (drag)
3. Krate deletion
4. Canvas resize (re-calculate scale)
5. Camera change (update viewport indicator)
### Throttling
- Debounce minimap render: 100ms
- Don't update on every pixel of drag
- Batch: Collect all krate changes, render once
## Visual Styling
### Krate Rectangles
- Size: 8×6px (small but visible)
- Color: Krate's namespace color
- Fill: Solid
- Border: None (or very subtle: rgba(255,255,255,.1))
### Viewport Indicator
- Fill: None
- Stroke: `#4dd6e8` (accent), 1px
- Opacity: 0.4
- Rounded corners: 2px
### Background Patterns (Optional)
- Subtle grid overlay (same as main canvas, fainter)
- Stars or dots for visual interest
- Very low opacity: rgba(255,255,255,.05)
## Coordinate Mapping
### World to Minimap
```
minimapScale = minimapWidth / worldWidth (e.g., 180 / 12000 = 0.015)
minimapX = (worldX + camX) * minimapScale
minimapY = (worldY + camY) * minimapScale
```
### Minimap to World (for click)
```
worldX = (minimapX / minimapScale) - camX
worldY = (minimapY / minimapScale) - camY
```
### Viewport Rect
```
viewportWidthWorld = viewportWidth / zoom
viewportHeightWorld = viewportHeight / zoom
viewportMinimapX = (camX * minimapScale)
viewportMinimapY = (camY * minimapScale)
viewportMinimapW = (viewportWidthWorld * minimapScale)
viewportMinimapH = (viewportHeightWorld * minimapScale)
```
## Animation
### Camera Fly
- Duration: `.52s`
- Easing: `cubic-bezier(.22,.8,.28,1)`
- Target: Center minimap-clicked position
- Zoom: Set to `0.92` (or user preference)
### Smooth Updates
- Smooth viewport indicator movement: CSS transition
- Avoid jitter during drag
- Use `requestAnimationFrame` for updates
## Gotchas
1. **Coordinate precision**: Use floating-point for accurate mapping
2. **Zoom handling**: Viewport indicator must scale inversely with zoom
3. **Performance**: Minimap should never block main thread
4. **Culling**: Don't render krates far outside view
5. **Click handling**: Ensure click z-index > canvas but < spotlight
6. **Responsiveness**: Recalculate mapping on window resize

337
design/shell-logs.md Normal file
View File

@@ -0,0 +1,337 @@
# 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)
```typescript
// 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
```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
- **New lines arrive**: Auto-scroll to bottom
- **User scrolls up**: Stop auto-scrolling
- **Detection**:
```typescript
function atBottom(): boolean {
const threshold = 50; // px
return (scrollTop + clientHeight + threshold) >= scrollHeight;
}
```
### Color Coding
Parse lines and color-code by pattern:
```typescript
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
```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
interface ShellState {
connected: boolean;
rows: number;
cols: number;
buffer: string[]; // Last N lines
cursor: { x, y };
}
```
### Logs State
```typescript
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)

137
design/spotlight.md Normal file
View File

@@ -0,0 +1,137 @@
# Spotlight Search Feature Specification
## 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.
## Core Responsibilities
- Display search results ranked by fuzzy matching
- Support typed filters (Tab to cycle through types)
- Provide view shortcuts (Logs, Shell, Describe, YAML)
- Auto-close after keyboard idle and open selected object
## UI Components
### Layout
- 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)
1. **Input row** (~52px):
- `⌕` glyph (accent color)
- Optional type-filter pill
- Input field (`font-size: 18px`, IBM Plex Sans)
- Optional Tab ghost hint
2. **Type chips row** (scrollable):
- All / deploy / svc / pods / secrets / config / sts / crd / namespace / ns
- Scrollable horizontally on small screens
- Shows selected filter as pill
3. **Results list** (`max-height: 48vh, overflow: auto`):
- Each row: shape glyph + name + subtext (type · ns/namespace) + CRD badge + type badge
- Selected row highlight: accent background
- Scrollbar styled (8px, rgba(140,165,200,.18))
4. **View chips** (expanded on selected row only):
- `⌥L logs · ⌥S shell · ⌥D describe · ⌥Y yaml`
- Only available views shown (YAML and Describe always shown; Logs and Shell for pods/deployments/daemonsets/statefulsets)
- Click to open that view for selected result
5. **Footer**:
- `↑↓ pick · ⌥L/S/D/Y open views · ⏎ open default · ⇥ filter type · esc done`
## Fuzzy Search Algorithm
### Scoring Formula
```
Score = character-match score × 10 + type boost
```
### Type Boosts
- CRD: 60
- Deployment: 50
- StatefulSet: 48
- Service: 46
- DaemonSet: 44
- Ingress: 42
- Secret: 24
- ConfigMap: 22
- PVC: 18
- Pod: 16
### Character Matching
- Sequential character match
- Bonus for consecutive matches: +3
- Bonus for word-boundary starts: +5
### Results
- Capped at 8 results
- Debounce: 16ms (one animation frame) for performance with large datasets
## Type Filter
### Tab Quick-Filter
- Typing type alias (e.g., `pod`, `svc`) shows ghost hint: `⇥ Pods`
- Tab locks filter as pill
- Backspace removes filter (only when empty query and filter active)
- Tab cycles through type filters (most-recently-opened first)
## 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
| Key | Context | Action |
|---|---|---|
| ↑ / ↓ | Spotlight | Navigate results |
| Tab | Spotlight | Apply type filter / cycle filters |
| Backspace | Spotlight (empty query, filter active) | Clear type filter |
| Enter | Spotlight (result selected) | Open default view + close |
| ⌥L/S/D/Y | Spotlight | Open that view for selected result |
| Esc | Spotlight | Close spotlight |
## Auto-Close Behavior
- After 500ms of keyboard idle (no more keypresses)
- Spotlight closes automatically
- Camera flies to the newly created krate
- Debounce timer resets on each keypress
## Search Results Include
- **Existing krates** (jump to working set, camera flies there)
- **Namespace results** (`ns/payments`) → opens collection window
- **Category results** (`All Pods`, `All Services`) → opens collection window
- **Kubernetes objects**: pods, deployments, services, secrets, configmaps, etc.
## State
```typescript
interface SpotlightState {
open: boolean;
query: string;
filterType: string | null;
sel: number; // selected result index
navigated: boolean; // true after ↑/↓ press
session: { // tracks views opened before closing
kid: string;
objId: string;
views: string[];
} | null;
}
```
## Gotchas
1. **Fuzzy search speed**: For real clusters with thousands of objects, debounce by 16ms or run in Web Worker
2. **View shortcuts**: Must use `event.code` for layout safety across OS/keyboard layouts
3. **Auto-close timer**: Reset on each keypress, don't close during active typing
4. **Pre-seeding**: All printable characters (not just letters) should pre-seed spotlight

131
design/top-bar.md Normal file
View File

@@ -0,0 +1,131 @@
# Top Bar Feature Specification
## 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.
## Layout
### Container
- Position: `absolute; top: 0; left: 0; right: 0; z-index: 10; height ~56px`
- Layout: `display: flex; align-items: center; padding: 13px 18px`
- Background: None (transparent over canvas)
## Components
### Left Side (left→right)
#### Logo Pill
- **Text**: `krates / yard`
- **Styling**:
- `background: rgba(14,18,25,.82)`
- `border: 1px solid rgba(140,165,200,.18)`
- `border-radius: 9px`
- `padding: 7px 12px`
- `backdrop-filter: blur(6px)`
- **Diamond glyph**: `clip-path: polygon(50% 0,100% 50%,50% 100%,0 50%)`
- **Color**: Accent (e.g., `#4dd6e8` cyan)
#### Cluster Pill
- Content: Cluster name + green health dot
- Health dot:
- Color: `#4ad07a`
- `box-shadow: 0 0 8px #4ad07a` (glow effect)
- Optional: Click to switch clusters (multi-cluster support)
#### Krate Count Pill
- Only shown when krates exist
- Content: `N krates` or similar
- Accent-colored
### Right Side
#### Synced Pill
- Content: `synced` text + pulsing accent dot
- Dots color: `#4dd6e8` (cyan)
- Animation:
- Opacity: 50% at 50%
- Duration: `1.6s`
- Easing: `ease-in-out infinite`
- Visual indicator of WebSocket connection status
#### Admin Button
- Content: `◉ admin`
- Active state: Accent-outlined (e.g., cyan border)
- Inactive state: Muted (gray, subtle)
- Toggle: Opens/closes admin drawer
#### Roster Avatars
- Overlapping circles: `margin-left: -7px`
- Size: 30×30px
- Color: Per-user (from palette)
- Content: 2-letter initials
- **Self indicator**: `★` (star) instead of initials
- Interactive: Click to jump to user's krate (admin mode)
## Admin Drawer Trigger
### Button Position
- Rightmost interactive element
- Click to slide in right-side panel
### Drawer Features
- Width: 380px
- Position: `absolute; right: 0; z-index: 56`
- Backdrop: `rgba(7,9,13,.4)` (click to close)
- Background: `rgba(13,17,24,.98); border-left: 1px solid rgba(140,165,200,.2)`
### User Card Content
For each connected user:
- Avatar circle + name
- Status: `active · namespace` or `idle Nm`
- Current search query + "typing…" indicator (if spotlight open)
- Krate count
- Clickable krate rows: name + view badges + status dot
- **Spectate**: Click user's krate to fly camera to it
### Self User
- Shows real-time state
- Displays current spotlight query
- Lists actual open krates
- Updates live as user interacts
## Keyboard Shortcuts
| Key | Context | Action |
|---|---|---|
| None (top bar is static UI) | - | - |
## State Management
### Top Bar References
```
- clusterName: string
- clusterHealthy: boolean
- krateCount: number
- users: User[]
- currentUser: User
- adminOpen: boolean
```
### User Object
```typescript
interface User {
id: string;
name: string;
color: string;
initials: string;
self: boolean;
status: 'active' | 'idle';
namespace?: string;
idleMinutes?: number;
query?: string;
krateIds: string[];
}
```
## Gotchas
1. **Admin presence**: Requires CRDT awareness or WebSocket presence channel
2. **Roster overflow**: Handle many users gracefully (scroll or truncate with `+N`)
3. **Self identification**: Mark current user with star `★`, not just initials
4. **Health dot**: Use shadow for glow, not just color
5. **Synced animation**: CSS keyframe or React state toggle every 800ms