Files
hermes-krates-connector/README.md
2026-06-16 08:31:55 -04:00

27 KiB
Raw Permalink Blame History

Handoff: Krates — Kubernetes Visual Cluster Navigator

Overview

Krates is a keyboard-first, search-driven Kubernetes cluster navigator. It gives operators and developers a fast, spatial way to explore any cluster: search for any object, open exactly the views you need (logs, shell, describe, YAML), and keep them organized as named krates on an infinite zoomable canvas. Multiple users share the same cluster workspace (the yard), and an admin view shows all users' live queries and open krates.

The product lives at krates.io.


About the Design Files

Krates.dc.html is a high-fidelity interactive prototype — it runs in a browser and is fully interactive. It is not production code. Its purpose is to demonstrate the intended look, behavior, keyboard model, and data shapes precisely enough that a developer can implement the real app without guessing.

Your task: Recreate this UI in a real production stack (recommended: React + TypeScript + a WebSocket/CRDT backend for real-time sync). Use the prototype as the authoritative reference for interactions, visual design, and UX decisions.


Fidelity

High-fidelity. Colors, typography, spacing, animations, and interaction behavior are all final. Implement pixel-accurately. The prototype uses real mock cluster data; the real app will use a Kubernetes API server (kubectl proxy, or a Go/Rust backend aggregating the k8s API).


Core Mental Model

Term Meaning
Yard The shared cluster workspace — one per connected cluster
Krate A named group of windows for one object (or a collection); sits on the canvas
Window A single view (Logs / Shell / Describe / YAML) for one object, inside a krate
Collection window A category or namespace overview (all Pods, all Services, ns/payments…) with a filterable list
Spotlight The global search overlay — opens by clicking anywhere or typing any key

Screens / Views

1. Canvas (main surface)

Purpose: Infinite zoomable/pannable workspace where krates live. The default starting point is empty — no pre-placed content.

Layout:

  • Full viewport, position: fixed; inset: 0
  • Background: #0b0e13
  • CAD grid overlay: two layers — fine (34px, rgba(125,145,175,.04)) and coarse (170px, rgba(125,145,175,.075)), both in X and Y. Rendered via CSS background-image repeating linear gradients.
  • World layer: a large div (12000×8000px) transformed via translate(camX, camY) scale(zoom). All krates and windows are absolutely positioned children of this layer.
  • Top bar: position: absolute; top: 0; left: 0; right: 0; height ~56px; z-index: 10
  • Bottom hint bar: position: absolute; right: 18px; bottom: 18px
  • Zoom pill: position: absolute; left: 18px; bottom: 18px
  • Minimap: position: absolute; right: 18px; bottom: 64px; width: 180px; height: 120px

Navigation:

  • Scroll wheel = pan (default)
  • Ctrl/⌘ + scroll = zoom (intercepted via wheel event, e.ctrlKey || e.metaKey)
  • Space + drag = pan. Critical: space panning must work even when a shell window has keyboard focus. Implement by capturing keydown for space globally with capture: true, setting a "panning" flag, preventing default on the body, and forwarding mousemove to the canvas pan handler. Release on keyup for space.
  • Click on empty canvas = open spotlight
  • Type any key = open spotlight with that character pre-seeded

Zoom levels / LOD (Level of Detail):

  • zoom > 0.5: normal view — krates expanded, all windows visible, draggable
  • zoom < 0.4: collapsed view — krates become small overview cards (230px wide), no windows rendered. Hysteresis: collapse at 0.4, re-expand at 0.5 to prevent flickering during a single scroll gesture.
  • The camera uses CSS transform with a transition only during programmatic "fly" animations (.52s cubic-bezier(.22,.8,.28,1)). During user scroll/drag, transition is none for immediacy.

2. Top Bar

Layout: display: flex; align-items: center; padding: 13px 18px
Background: none (transparent over canvas)

Left side (left→right):

  • Logo pill: krates / yardbackground: rgba(14,18,25,.82); border: 1px solid rgba(140,165,200,.18); border-radius: 9px; padding: 7px 12px; backdrop-filter: blur(6px). The diamond glyph is clip-path: polygon(50% 0,100% 50%,50% 100%,0 50%) in accent color.
  • Cluster pill: cluster name + green health dot (#4ad07a, box-shadow: 0 0 8px #4ad07a)
  • Krate count pill: only shown when krates exist

Right side:

  • Synced pill: pulsing accent dot + "synced" text
  • Admin button: ◉ admin — accent-outlined when active, muted when inactive
  • Roster avatars: overlapping circles (margin-left: -7px), each 30×30px, colored per user, showing 2-letter initials (★ for self)

Purpose: Global search overlay. Fuzzy-ranked results, Tab type-filter, view shortcuts on selected result.

Trigger: Click anywhere on empty canvas, or type any printable character (pre-seeds the query).

Layout:

  • Full-screen backdrop: rgba(7,9,13,.55), backdrop-filter: blur(2px)
  • Search panel: position: absolute; left: 50%; top: 15%; transform: translateX(-50%); width: min(660px, 93vw)
  • Panel: background: rgba(16,20,28,.97); border: 1px solid rgba(140,165,200,.26); border-radius: 14px; overflow: hidden
  • Input row height: ~52px, with font-size: 18px, IBM Plex Sans

Sections (top→bottom):

  1. Input row: glyph (accent color) + optional type-filter pill + input field + optional Tab ghost hint
  2. Type chips row: scrollable chip row for all/deploy/svc/pods/secrets/config/sts/crd/namespace/ns. Scrollable horizontally on small screens.
  3. Results list (max-height: 48vh, overflow: auto): each row = shape glyph + name + subtext (type · ns/namespace) + CRD badge if applicable + type badge
  4. View chips (expanded on the selected row only): ⌥L logs · ⌥S shell · ⌥D describe · ⌥Y yaml. Only available views shown — e.g. YAML and Describe are always shown; Logs and Shell only for pods/deployments/daemonsets/statefulsets.
  5. Footer: ↑↓ pick · ⌥L/S/D/Y open views · ⏎ open default · ⇥ filter type · esc done

Fuzzy search algorithm:

  • Score = character-match score × 10 + type boost
  • Type boost: 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 with bonuses for consecutive matches (+3) and word-boundary starts (+5)
  • Results capped at 8

Tab quick-filter:

  • Typing a type alias (pod, svc, deploy, secret, cm, sts…) shows a ghost hint: ⇥ Pods
  • Tab locks the filter as a pill; Backspace removes it
  • Tab also cycles through all type filters (recency-ordered, most-recently-opened first)

View shortcut keys:

  • All use ⌥ (Alt/Option). Do NOT use Ctrl — it must stay free for terminal use.
  • Keys: Alt+L = logs, Alt+S = shell, Alt+D = describe, Alt+Y = yaml
  • Use event.code (layout-safe, e.g. KeyL) not event.key (which varies by OS/keyboard layout)
  • Multiple views can be opened before closing spotlight — they stack as windows in one krate
  • On Enter: opens the default view (logs for pods, describe for workloads, yaml for config/secrets/pvcs) and closes

Auto-close: after 500ms of keyboard idle (no more keypresses), spotlight closes automatically and the camera flies to the newly created krate.

Search also returns:

  • Existing krates (jump to working set, camera flies there)
  • Namespace results (ns/payments) → opens a collection window
  • Category results (All Pods, All Services…) → opens a collection window

4. Krate (window group)

Purpose: A named group of detail windows for a single query/object. Lives on the canvas.

Visual container:

  • Dashed border frame (rounded rect) around all its windows: border: 1px dashed rgba(color, .3); border-radius: 18px; background: rgba(color, .04)
  • Frame auto-fits to the bounding box of its windows + 30px padding
  • Header label bar above the frame: shows object name, status badge, window count, drag handle to move the entire krate, × to dismiss

Layout of windows inside a krate:

  • Windows are tiled in a 2-column grid (col 0 = dx:0, col 1 = dx:412; row height = 416px)
  • When one window is resized, the others resize proportionally within the grid column/row
  • Windows never overlap (snap-to-grid on release)

Krate interactions:

  • Drag header = move entire krate
  • Double-click header = collapse/expand (toggle)
  • Minimize button () in header = collapse to overview card
  • Collapsed state: 230px-wide card showing name, view-letter badges (Y/D/L/S), status dot, window count, ns. Double-click to re-expand and fly camera to it.
  • Non-overlapping placement: on mouse release after drag, check all other krate bounding boxes and nudge to avoid overlap. Gentle snap, not strict grid.

Camera fly-to on open: after summon, camera animates to center the krate at zoom: 0.92.

5. Detail Window

Purpose: Shows one view (YAML / Describe / Logs / Shell) for one Kubernetes object.

Size: Default 380×362px. Resizable by dragging the bottom-right corner handle (resize one window in a krate → sibling windows resize proportionally in the same column/row).

Header (36px):

  • Shape glyph (8px, color-coded by object type) + view label (uppercase, accent-colored) + object name + status badge + × close
  • cursor: grab — drag to reposition within the canvas
  • Double-click header = zoom window to fill available space (below top bar, above bottom bar, side margins 22px). Press z or double-click again to restore.

Tab bar: YAML · Describe · Logs · Shell. Shell tab is disabled (muted, opacity 0.45) for non-exec object types.

Content area: <pre> with overflow: auto; height: 286px; font-family: 'IBM Plex Mono'; font-size: 12px; line-height: 1.65. Scrollbar styled (8px, rgba(140,165,200,.18)).

Keyboard focus:

  • Mouse-over a window → start typing = focuses that window's content (specifically the shell input if it's a shell view)
  • Mouse-over detection: track mouseenter/mouseleave per window, set a hoveredWindowId ref
  • In the global keydown capture handler: if hoveredWindowId refers to a shell, route the keypress there; if it's any other window, do nothing (let it fall through to canvas shortcuts)

Maximize (z key or double-click header):

  • Resize the window to width = viewport.width - 44px, height = viewport.height - topBarHeight(68px) - bottomBarHeight(66px)
  • Set camera so camX = 22 - krate.wx - window.dx, camY = 68 - krate.wy - window.dy, zoom = 1
  • z again or double-click to restore previous size

Secret YAML auto-decode:

  • When rendering YAML for a Secret object, base64-decode all data values and show plaintext with a ⊙ secret values auto-decoded banner

6. Shell Window

Purpose: Interactive pseudo-terminal session inside the app.

Implementation:

  • Use xterm.js (or equivalent) for the real terminal
  • Backend: WebSocket → kubectl exec into the pod
  • Keyboard routing: the window captures keypresses when the mouse is over it (see above). Space must NOT open the spotlight when the cursor is over a shell — instead pass the space character to the terminal.
  • Scroll wheel over shell: scroll the terminal content, do NOT pan the canvas. Only Ctrl/⌘+scroll over a shell should zoom the canvas.

Mock in prototype: Shows static text with a blinking cursor character. The real implementation connects via kubectl exec -it <pod> -n <ns> -- sh through the backend.

7. Logs Window

Purpose: Live-tailing container logs.

Implementation:

  • Streams via kubectl logs --follow through the backend WebSocket
  • Auto-scroll to bottom when new lines arrive, unless the user has scrolled up (detect by comparing scrollTop + clientHeight vs scrollHeight; if not at bottom, stop auto-scroll)
  • Color-code: ERROR lines in #ef6f6f, WARN in #e8b54a, INFO in #b9c6d8

8. Collection Window

Purpose: Overview of a category (All Pods, All Services…) or namespace. Status-at-a-glance list.

Size: Default 540×480px.

Header:

  • Title (e.g. "pods · ns/payments"), status summary badges (✓ N / ⚠ N / ✗ N)
  • Filter input (auto-focused on open): font-family: IBM Plex Mono; font-size: 12px
  • Keyboard hint: ↑↓ ⌥L/S/D/Y (always visible, muted)
  • List / Tree toggle buttons

List mode:

  • Each row: shape glyph + name + relation tag (muted, for tree mode) + health metric + type badge + view buttons (⌥L/S/D/Y) + 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 each group

Tree mode (k9s xray):

  • Each workload expands to show its pods; each pod/workload expands to show its configMaps, secrets, PVCs
  • Services/ingresses expand to show their targets
  • Relation tags shown: pod, configMap, secret, volume, selects, routes, used by
  • Indent per level: 20px * depth; connector shown as an L-shaped border element

Filter behavior in collection window:

  • Typing filters immediately (no mode switching)
  • ↑/↓ moves selection highlight
  • ⌥L/S/D/Y opens that view for the selected row (uses event.code, Alt required)
  • Enter opens default view for selected row
  • Escape blurs the input
  • Mouse-over the collection window + start typing = focuses the filter input and types there (same hover routing as shell windows, but for the filter input)
  • In tree mode, filtering keeps matching nodes plus all their ancestors so the hierarchy stays readable

9. Admin Drawer

Purpose: Right-side panel showing all users connected to the yard.

Trigger: ◉ admin button in top bar (toggle).

Layout:

  • Right-side slide-in panel: width: 380px; top: 0; right: 0; bottom: 0; position: absolute; 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)

Content per user:

  • Avatar circle + name + status (active · namespace / idle Nm)
  • Current search query + "typing…" pulsing indicator if they have spotlight open
  • Krate count
  • Each open krate as a clickable row: name + view-letter badges + status dot

Admin can spectate: clicking a krate row in another user's card (or in "You") closes the admin panel and flies the camera to that krate.

Your own row shows real-time state — your current spotlight query and actual open krates.

10. Minimap

Purpose: Overview of all krates' positions on the canvas.

Position: right: 18px; bottom: 64px
Size: 180×120px, with a viewport indicator rectangle showing the current view frustum.

Interaction: Click anywhere on the minimap to fly the camera to that world position.


Interactions & Behavior Summary

Interaction Behavior
Click empty canvas Open spotlight
Type any key (not in input) Open spotlight pre-seeded with that key
/ key Open spotlight empty
Esc Close spotlight / close spotlight and place krate
Scroll wheel Pan canvas
Ctrl/⌘ + scroll Zoom canvas
Space + drag Pan canvas (even over windows/shells)
z over a window Maximize window to fill available space
z again Restore window size
Double-click window header Maximize / restore
Double-click krate header Collapse / expand krate
Double-click collapsed krate card Expand and fly camera to it
Mouse-over window + type Focus shell / collection filter
⌥L / ⌥S / ⌥D / ⌥Y Open view for selected result (spotlight or collection)
Tab in spotlight Apply type filter / cycle type filters
Backspace in spotlight (empty query, filter active) Clear type filter
↑↓ in spotlight Navigate results
⌥+key in spotlight Open view for selected result
↑↓ in collection filter Move row selection
⌥+key in collection Open view for selected row
Drag window header Move window
Drag krate header Move entire krate
Krate drag release Nudge to avoid overlap with other krates
Resize window handle Resize; siblings resize proportionally
Minimap click Fly camera to position

State Management

Canvas state

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
}

Krate state

interface Krate {
  id: string;
  objId: string | null;        // null for collection krates
  collScope?: { kind: 'namespace' | 'category', value: string };
  label: string;
  status: string;
  color: string;               // hex, from namespace palette
  wx: number; wy: number;      // world position
  minimized: boolean;
  windows: Window[];
  seq: number;                 // for generating window IDs
}

interface Window {
  wid: string;
  kind: 'detail' | 'collection';
  tab: 'logs' | 'shell' | 'describe' | 'yaml';
  dx: number; dy: number;      // offset within krate
  w: number; h: number;
}

Spotlight state

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;
}

Collection window state (per-window ID)

interface CollectionState {
  [wid: string]: {
    search: string;
    view: 'list' | 'tree';
    sel: number;               // selected row index
  }
}

Backend / API Requirements

The prototype uses mock data. The real app needs:

Kubernetes data

  • kubectl proxy or a dedicated Go/Rust aggregation service
  • Watch API for live updates: GET /api/v1/namespaces/{ns}/pods?watch=true etc.
  • Resources needed: Namespaces, Deployments, StatefulSets, DaemonSets, Services, Ingresses, ConfigMaps, Secrets, PVCs, Events, and CRDs
  • Secret values should be base64-decoded server-side (or client-side) before display

Real-time shell

  • WebSocket → kubectl exec -it stream
  • Bidirectional: keypresses → stdin, stdout/stderr → display

Real-time logs

  • WebSocket → kubectl logs --follow
  • Stream new lines; handle reconnection

Multi-user sync (the Yard)

  • CRDTs or operational transforms for shared krate positions, krate existence, window layout
  • Recommended: Yjs (CRDT library) over WebSocket (y-websocket)
  • Each user's cursor/active query broadcast as ephemeral state (Yjs awareness)
  • Admin view reads from the awareness map
  • Go service wrapping the Kubernetes client-go library
  • WebSocket multiplexer for shell, logs, watch streams
  • Redis or in-memory for session/presence if single-node
  • For multi-node: use Yjs with a persistent provider (y-leveldb or y-mongodb)

Design Tokens

Colors

Background:          #0b0e13
World background:    #0b0e13
Surface:             rgba(14,18,25,.97)
Surface elevated:    rgba(20,26,36,.7)
Border default:      rgba(140,165,200,.2)
Border subtle:       rgba(140,165,200,.14)

Accent (cyan):       #4dd6e8
Accent (violet):     #a98cff
Accent (amber):      #f5b454

Text primary:        #e6edf6
Text secondary:      #c7d2e0
Text muted:          #9fb0c8
Text faint:          #7e8aa2
Text dimmed:         #6b7890

Status OK:           #4ad07a
Status warn:         #e8b54a
Status error:        #ef6f6f

Object colors:
  Deployment:        #6fb1ff
  StatefulSet:       #b89cff
  Service:           #5fd0c0
  DaemonSet:         #ffb27a
  Ingress:           #e58fb0
  CRD:               #ffd479
  Pod:               #8aa0bd
  ConfigMap:         #7fb39c
  Secret:            #c79bd6
  PVC:               #9aa7c7

Namespace palettes:
  payments:          #6fb1ff
  platform:          #5fd0c0
  storefront:        #e58fb0

Shape language (CSS clip-path per object type)

Deployment:   border-radius: 50% (circle)
StatefulSet:  polygon(50% 0, 100% 100%, 0 100%) (triangle)
Service:      polygon(50% 0, 100% 50%, 50% 100%, 0 50%) (diamond)
DaemonSet:    ring (transparent fill, colored border)
Ingress:      polygon(50% 0, 100% 100%, 0 100%) (triangle, same as STS)
CRD:          polygon(25% 0, 75% 0, 100% 50%, 75% 100%, 25% 100%, 0 50%) (hexagon)
Pod:          circle
ConfigMap:    square (border-radius: 3px)
Secret:       hexagon (same clip-path as CRD, smaller)
PVC:          square

Typography

UI font:      IBM Plex Sans, 400/500/600
Mono font:    IBM Plex Mono, 400/500

Sizes used:
  9px   — label/badge uppercase legends
  10px  — tiny metadata, status labels
  11px  — chips, hints, secondary metadata
  12px  — mono body text in windows/collections
  13px  — nav bar labels, search result sub-text
  14px  — collection titles, admin panel
  17px  — spotlight search input prefix glyph
  18px  — spotlight search input
  25px  — empty state headline

Spacing

Window default size:   380 × 362px
Collection default:    540 × 480px
Window grid col gap:   412px (column pitch)
Window grid row gap:   416px (row pitch)
Krate frame padding:   30px around bounding box
Top bar height:        ~56px (inset: 68px with padding)
Bottom bar height:     ~40px (inset: 66px with padding)
Minimap:               180 × 120px
Sidebar margin:        18px

Animations

Camera fly:       transform .52s cubic-bezier(.22,.8,.28,1)
Spotlight open:   opacity + translateY(-8px→0) .16s cubic-bezier(.2,.8,.3,1)
Window appear:    opacity + scale(.97→1) .16s ease
Fade backdrop:    opacity .12s ease
Krate logo float: translateY 0→-7px→0, 4s ease-in-out infinite
Synced dot blink: opacity 50% at 50%, 1.6s ease-in-out infinite

Keyboard Shortcut Reference

All shortcuts that act on objects use ⌥ (Alt/Option) — never Ctrl (reserved for terminal), never bare letters (reserved for typing in search/filter).

Key Context Action
Any printable key Canvas focus Open spotlight pre-seeded
/ Canvas focus Open spotlight empty
Esc Spotlight open Close / finish + place krate
↑/↓ Spotlight Navigate results
Tab Spotlight Apply type filter / cycle filters
⌥L/S/D/Y Spotlight (result selected) Open that view
Enter Spotlight Open default view + close
↑/↓ Collection filter focused Move row selection
⌥L/S/D/Y Collection filter focused Open that view for selected row
Enter Collection filter focused Open default view for selected row
Space + drag Canvas Pan (even over windows/shells)
Ctrl/⌘ + scroll Anywhere Zoom canvas
z Mouse over window Maximize / restore window

File Reference

File Purpose
Krates.dc.html Full interactive prototype — the primary design reference. Open in any modern browser.
README.md This document

Implementation Notes & Gotchas

  1. Space pan over shells: The trickiest keyboard interaction. The space key must pan the canvas even when a <textarea> or xterm.js instance has focus. Implement with a document.addEventListener('keydown', handler, { capture: true }) that checks if space is pressed during a drag gesture, sets a flag, and calls e.preventDefault() on the focused element. Do NOT use e.stopPropagation() — you still need the event to reach other handlers.

  2. Ctrl/⌘+scroll over windows: Similarly, wheel events on window content areas will be captured by the scroll container. Use { capture: true } on the canvas wheel handler to intercept before the window, check for e.ctrlKey || e.metaKey, and only zoom then. Otherwise let the event scroll the window content.

  3. LOD hysteresis: Use separate thresholds for collapse (0.4) and expand (0.5) to prevent jitter. Store collapsed as boolean in state, not derived from zoom on every render.

  4. Non-overlapping krate placement: On each new krate creation, try the computed default position, then nudge by fixed increments until no bounding-box overlap exists with any existing krate. On drag release, do the same nudge.

  5. Fuzzy search speed: The prototype scores all objects on every keystroke. For a real cluster with thousands of objects, debounce by 16ms (one animation frame) or run scoring in a Web Worker. The scoring algorithm is simple enough that even 10,000 objects should score in <5ms.

  6. Collection filter auto-focus: When a collection krate is created, focus() its filter <input> in the next tick. Use a ref stored during render and a flag cleared in componentDidUpdate / useEffect.

  7. Secret decoding: The prototype shows decoded secrets inline with a banner. In the real app, ensure that decoded secrets are handled carefully — consider requiring explicit user action to reveal, and never log them.

  8. CRDT sync scope: Sync krate positions, existence, and window layout via CRDT. Do NOT sync window content (logs, YAML, shell) — each client streams independently from the k8s API. Only sync the structure of the workspace.

  9. Resize handles: The prototype does not implement drag-resize yet. When implementing, a resize in one column/row should proportionally adjust sibling windows. Store a colWidths and rowHeights array per krate, not absolute dx/dy per window.

  10. Admin presence data: The prototype uses hardcoded mock users. In the real app, use Yjs awareness or a presence channel: each client publishes { userId, name, color, query, krateIds[] } as ephemeral state; the admin panel subscribes and renders it reactively.


Frontend:      React 18 + TypeScript
Styling:       CSS-in-JS (styled-components or vanilla-extract) or Tailwind
Terminal:      xterm.js
Real-time:     Yjs + y-websocket (CRDT collaboration)
Backend:       Go + kubernetes/client-go + gorilla/websocket
Fonts:         IBM Plex Sans + IBM Plex Mono (Google Fonts or self-hosted)
Build:         Vite

Backlog (Not in Prototype)

  • Tile layout presets per krate: "logs hero" (one large log window, data tiles around it), "split", "grid". Shortcut on krate header.
  • Port-forwarding window: dedicated view type alongside YAML/Logs/Shell/Describe
  • Events window: kubectl get events --watch for a namespace or object
  • Diff view: compare YAML between two versions or two objects
  • Multi-cluster support: multiple yards in one app, switchable from the top bar