Files
homelab-topology/.sisyphus/plans/remaining-features.md

42 KiB

Homelab Topology - Remaining Features Implementation

TL;DR

Quick Summary: Implement 11 remaining features for the homelab topology visualizer, including backend API for SSH discovery, real data tabs (Config/Files/Usage), filters, persistence, and UX enhancements.

Deliverables:

  • Express backend API server with SSH discovery endpoints
  • Config Tab showing real docker-compose.yml content
  • Files Tab showing real mounted volumes
  • Usage Tab showing live docker stats
  • Type and Status filters in Header
  • Configurable poll interval UI
  • LocalStorage persistence for settings
  • Command palette (Cmd+K)
  • Stale data warning banner
  • Path highlighting for selected nodes

Estimated Effort: Large Parallel Execution: YES - 3 waves Critical Path: Backend API → SSH Discovery → Real Data Tabs → Filters → UX Enhancements


Context

Original Request

Implement all remaining tasks for the homelab topology visualizer:

  • High Priority: SSH Discovery, Config Tab, Files Tab, Usage Tab
  • Medium Priority: Type Filter, Status Filter, Configurable Poll, LocalStorage
  • Lower Priority: Command Palette, Stale Warning, Path Highlighting

Interview Summary

Key Discussions:

  • SSH Discovery: Requires backend API (browser can't SSH directly)
  • Config/Files/Usage Tabs: Need real data via SSH + docker commands
  • Filters: Toggle buttons in Header for type/status filtering
  • Persistence: Zustand persist middleware for localStorage
  • UX: Command palette, stale warning, path highlighting

Research Findings:

  • Current project uses React + Vite + Zustand + React Flow + dagre
  • sshDiscovery.ts exists but not connected to frontend
  • App.tsx has 30s polling with countdown timer
  • RightPanel tabs show placeholder data

Metis Review

Identified Gaps (addressed):

  • SSH Authentication: Using environment variables/config file, NOT localStorage
  • Error Handling: Graceful degradation for partial failures
  • Security: All SSH operations server-side only
  • Performance: Discovery timeout of 30s per host

Work Objectives

Core Objective

Complete the homelab topology visualizer with real infrastructure data via SSH discovery and enhanced UX features.

Concrete Deliverables

  1. server/ - Express backend with SSH discovery API
  2. server/routes/discover.ts - SSH discovery endpoints
  3. server/routes/config.ts - Config file retrieval
  4. server/routes/files.ts - Volume discovery
  5. server/routes/stats.ts - Docker stats endpoint
  6. src/components/Header.tsx - Updated with filters and poll interval
  7. src/components/RightPanel.tsx - Config/Files/Usage tabs with real data
  8. src/components/CommandPalette.tsx - New Cmd+K modal
  9. src/components/StaleWarning.tsx - New warning banner
  10. src/store/topologyStore.ts - Updated with persist middleware

Definition of Done

  • All 11 features implemented and functional
  • Backend API running on port 3001
  • Frontend connects to backend for discovery
  • All filters work and persist across refresh
  • Command palette functional with Cmd+K
  • Stale warning appears after 3 failed discoveries
  • Path highlighting shows ancestor nodes

Must Have

  • Backend API for SSH operations (security: server-side only)
  • Real data in Config/Files/Usage tabs
  • Type and Status filters functional
  • Settings persist in localStorage

Must NOT Have (Guardrails)

  • SECURITY: NEVER store SSH credentials in localStorage or frontend
  • SECURITY: NEVER expose SSH credentials in API responses
  • SCOPE: No user authentication system (single-user local tool)
  • SCOPE: No WebSocket (polling is sufficient)
  • SCOPE: No historical metrics storage (live data only)
  • SCOPE: No alerting/notification system
  • IMPLEMENTATION: No database (file-based config is sufficient)
  • AI-SLOP: No over-engineering - simple Express server, minimal routes

Verification Strategy

Test Decision

  • Infrastructure exists: YES (frontend has test config)
  • Automated tests: NO (tests-after approach)
  • Framework: bun test
  • Agent-Executed QA: ALWAYS (mandatory for all tasks)

QA Policy

Every task includes agent-executed QA scenarios:

  • Backend API: Use Bash (curl) — Send requests, assert status + response fields
  • Frontend UI: Use Playwright — Navigate, interact, assert DOM, screenshot
  • Integration: Use Playwright + curl combined

Evidence saved to .sisyphus/evidence/task-{N}-{scenario-slug}.{ext}


Execution Strategy

Parallel Execution Waves

Wave 1 (Start Immediately — 5 parallel tasks):
├── Task 1: Express Backend Foundation [quick]
├── Task 2: Backend Types & Config Schema [quick]
├── Task 3: Type Filter UI [quick]
├── Task 4: Status Filter UI [quick]
└── Task 5: LocalStorage Persistence [quick]

Wave 2 (After Wave 1 — SSH discovery, then parallel data tabs):
├── Task 6: SSH Discovery API Endpoint [deep] (blocks 7-10)
├── Task 7: Wire Frontend to Discovery API [quick]
├── Task 8: Config Tab with Real Data [quick]
├── Task 9: Files Tab with Real Data [quick]
└── Task 10: Usage Tab with Real Data [quick]

Wave 3 (After Wave 2 — 4 parallel UX enhancements):
├── Task 11: Configurable Poll Interval [quick]
├── Task 12: Command Palette [visual-engineering]
├── Task 13: Stale Data Warning [quick]
└── Task 14: Path Highlighting [visual-engineering]

Wave FINAL (After ALL tasks — 4 parallel reviews):
├── Task F1: Plan Compliance Audit (oracle)
├── Task F2: Code Quality Review (unspecified-high)
├── Task F3: Real Manual QA (unspecified-high)
└── Task F4: Scope Fidelity Check (deep)

Critical Path: Task 1 → Task 6 → Task 7 → Task 8/9/10 → Task 11/12/13/14 → F1-F4
Parallel Speedup: ~60% faster than sequential
Max Concurrent: 5 (Wave 1)

Dependency Matrix

Task Depends On Blocks
1 6, 8, 9, 10
2 6, 8, 9, 10
3 12
4 12
5
6 1, 2 7, 8, 9, 10
7 6 11, 13, 14
8 6, 7
9 6, 7
10 6, 7
11 7
12 3, 4
13 7
14 7

Agent Dispatch Summary

  • Wave 1: 5 tasks — T1 → quick + system-admin, T2 → quick, T3-T4 → quick + frontend-ui-ux, T5 → quick
  • Wave 2: 5 tasks — T6 → deep + system-admin + ssh, T7-T10 → quick
  • Wave 3: 4 tasks — T11/T13 → quick + frontend-ui-ux, T12/T14 → visual-engineering
  • FINAL: 4 tasks — F1 → oracle, F2-F3 → unspecified-high, F4 → deep

TODOs

  • 1. Create Express Backend Foundation

    What to do:

    • Create server/ directory at project root
    • Create server/index.ts with Express app listening on port 3001
    • Add CORS middleware for frontend communication
    • Create /api/health endpoint returning {"status":"ok"}
    • Add npm script "server": "tsx server/index.ts" to package.json
    • Install dependencies: express, cors, @types/express, @types/cors

    Must NOT do:

    • Do NOT add authentication middleware (out of scope)
    • Do NOT add WebSocket support (polling is sufficient)
    • Do NOT over-engineer with middleware layers

    Recommended Agent Profile:

    • Category: quick
      • Reason: Standard Express server setup, well-documented pattern
    • Skills: [system-admin]
      • system-admin: Server setup and port configuration

    Parallelization:

    • Can Run In Parallel: YES
    • Parallel Group: Wave 1 (with Tasks 2, 3, 4, 5)
    • Blocks: Tasks 6, 8, 9, 10
    • Blocked By: None

    References:

    • package.json:13-22 - Existing dependencies, add express/cors
    • src/services/sshDiscovery.ts:1-44 - SSH client setup pattern to reference
    • Existing Vite config shows project structure

    Acceptance Criteria:

    • server/index.ts exists with Express app
    • npm run server starts server on port 3001
    • curl http://localhost:3001/api/health returns {"status":"ok"}
    • CORS enabled for http://localhost:3000

    QA Scenarios:

    Scenario: Backend health check works
      Tool: Bash (curl)
      Steps:
        1. cd /home/christopher/homelab-topology
        2. bun run server &
        3. sleep 2
        4. curl -s http://localhost:3001/api/health
      Expected Result: {"status":"ok"}
      Evidence: .sisyphus/evidence/task-1-health-check.json
    

    Commit: YES

    • Message: feat(backend): add Express server foundation
    • Files: server/index.ts, package.json

  • 2. Define Backend Types and Config Schema

    What to do:

    • Create server/types.ts with TypeScript interfaces:
      • HostConfig: name, ip, sshUser, sshKeyPath (optional)
      • DiscoveryResponse: hosts, timestamp, errors
      • ConfigResponse: yaml, path, error
      • FilesResponse: volumes array (source, destination, mode)
      • StatsResponse: cpu, memory, network (rx/tx)
    • Create server/config.ts with host configuration loader:
      • Load from environment variables first (SSH_HOSTS, SSH_USER, SSH_KEY)
      • Fallback to server/config.json file
      • Export getHostConfigs(): HostConfig[]
    • Create sample server/config.example.json with structure

    Must NOT do:

    • Do NOT store credentials in code
    • Do NOT create encrypted vault (out of scope)
    • Do NOT add credential rotation logic

    Recommended Agent Profile:

    • Category: quick
      • Reason: Type definitions and simple config loading
    • Skills: []

    Parallelization:

    • Can Run In Parallel: YES
    • Parallel Group: Wave 1 (with Tasks 1, 3, 4, 5)
    • Blocks: Tasks 6, 8, 9, 10
    • Blocked By: None

    References:

    • src/types/index.ts - Existing type patterns to match
    • src/services/sshDiscovery.ts:3-28 - Existing SSH config interface
    • src/data/staticConfig.ts:58-101 - Existing host definitions

    Acceptance Criteria:

    • server/types.ts exists with all interfaces
    • server/config.ts exports getHostConfigs()
    • server/config.example.json shows expected format
    • Types compile without errors: tsc --noEmit

    QA Scenarios:

    Scenario: Config loader returns host configs
      Tool: Bash (node)
      Steps:
        1. cd /home/christopher/homelab-topology
        2. Create test config: echo '{"hosts":[{"name":"test","ip":"127.0.0.1"}]}' > server/config.json
        3. node -e "import('./server/config.ts').then(m => console.log(m.getHostConfigs()))"
      Expected Result: Array with host configs
      Evidence: .sisyphus/evidence/task-2-config-loader.txt
    

    Commit: YES

    • Message: feat(backend): add types and config schema
    • Files: server/types.ts, server/config.ts, server/config.example.json

  • 3. Implement Type Filter UI

    What to do:

    • Add typeFilters: NodeType[] to topologyStore (default: all types)
    • Add toggleTypeFilter(type: NodeType) action to store
    • Update getFilteredNodes() to filter by typeFilters
    • Add filter toggle buttons in Header.tsx after orientation dropdown
    • Each button shows node type icon and is toggleable (active/inactive state)
    • Use existing node type colors for button styling

    Must NOT do:

    • Do NOT add "select all/none" buttons (keep it simple)
    • Do NOT create a dropdown menu (use inline toggles)
    • Do NOT add filter descriptions/tooltips (icons sufficient)

    Recommended Agent Profile:

    • Category: quick
      • Reason: Simple toggle buttons with store integration
    • Skills: [frontend-ui-ux]
      • frontend-ui-ux: Filter button design and styling

    Parallelization:

    • Can Run In Parallel: YES
    • Parallel Group: Wave 1 (with Tasks 1, 2, 4, 5)
    • Blocks: Task 12
    • Blocked By: None

    References:

    • src/store/topologyStore.ts:79-125 - Existing getFilteredNodes() to extend
    • src/types/index.ts:1-11 - NodeType definitions
    • src/utils/colors.ts - Node type colors for styling
    • src/components/Header.tsx:58-73 - Existing button styling pattern

    Acceptance Criteria:

    • typeFilters added to store with all types as default
    • Toggle buttons visible in Header
    • Clicking filter hides corresponding nodes
    • Multiple filters can be active simultaneously
    • Filter state persists (will be tested in Task 5)

    QA Scenarios:

    Scenario: Type filter hides gateway nodes
      Tool: Playwright
      Steps:
        1. Navigate to http://localhost:3000
        2. Click type filter button for "gateway"
        3. Wait for graph to update
        4. Check if gateway node is visible
      Expected Result: Gateway node not in DOM
      Evidence: .sisyphus/evidence/task-3-type-filter.png
    
    Scenario: Multiple type filters work together
      Tool: Playwright
      Steps:
        1. Navigate to http://localhost:3000
        2. Click filter for "vlan" and "wifi"
        3. Count visible nodes
      Expected Result: Only nodes NOT of type vlan/wifi visible
      Evidence: .sisyphus/evidence/task-3-multi-filter.png
    

    Commit: YES

    • Message: feat(ui): add type filter toggles
    • Files: src/store/topologyStore.ts, src/components/Header.tsx

  • 4. Implement Status Filter UI

    What to do:

    • Add statusFilter: 'all' | 'running' | 'stopped' to topologyStore (default: 'all')
    • Add setStatusFilter(status: string) action to store
    • Update getFilteredNodes() to filter by status
    • Add dropdown in Header.tsx after type filters with options: All, Running, Stopped
    • Use existing status colors for visual indication

    Must NOT do:

    • Do NOT add "unknown" status filter (not useful)
    • Do NOT create multi-select (single status at a time)
    • Do NOT add status counts (complexity)

    Recommended Agent Profile:

    • Category: quick
      • Reason: Simple dropdown with store integration
    • Skills: [frontend-ui-ux]
      • frontend-ui-ux: Dropdown styling

    Parallelization:

    • Can Run In Parallel: YES
    • Parallel Group: Wave 1 (with Tasks 1, 2, 3, 5)
    • Blocks: Task 12
    • Blocked By: None

    References:

    • src/store/topologyStore.ts:79-125 - Existing getFilteredNodes() to extend
    • src/types/index.ts:15 - Status type definition
    • src/utils/colors.ts - getStatusColor function
    • src/components/Header.tsx:78-89 - Existing dropdown pattern (orientation)

    Acceptance Criteria:

    • statusFilter added to store with 'all' as default
    • Dropdown visible in Header with 3 options
    • Selecting "Running" shows only running nodes
    • Selecting "Stopped" shows only stopped nodes
    • Filter state persists (will be tested in Task 5)

    QA Scenarios:

    Scenario: Status filter shows only running nodes
      Tool: Playwright
      Steps:
        1. Navigate to http://localhost:3000
        2. Click status dropdown, select "Running"
        3. Check all visible nodes have status "running"
      Expected Result: All visible nodes show running status
      Evidence: .sisyphus/evidence/task-4-running-filter.png
    
    Scenario: Status filter shows only stopped nodes
      Tool: Playwright
      Steps:
        1. Navigate to http://localhost:3000
        2. Click status dropdown, select "Stopped"
        3. Verify no running nodes visible
      Expected Result: Only stopped/unknown nodes visible
      Evidence: .sisyphus/evidence/task-4-stopped-filter.png
    

    Commit: YES

    • Message: feat(ui): add status filter dropdown
    • Files: src/store/topologyStore.ts, src/components/Header.tsx

  • 5. Implement LocalStorage Persistence

    What to do:

    • Add zustand/middleware persist to topologyStore
    • Persist these keys: viewMode, orientation, typeFilters, statusFilter, leftPanelOpen, rightPanelOpen, pollInterval
    • Set storage key as homelab-topology-settings
    • Exclude from persistence: nodes, edges, selectedNodeId, isLoading, networkInfo, hosts
    • Add version number for future migrations

    Must NOT do:

    • Do NOT persist SSH credentials (SECURITY)
    • Do NOT persist topology data (nodes/edges)
    • Do NOT add export/import functionality (out of scope)
    • Do NOT add profile switching (out of scope)

    Recommended Agent Profile:

    • Category: quick
      • Reason: Zustand persist middleware is straightforward
    • Skills: []

    Parallelization:

    • Can Run In Parallel: YES
    • Parallel Group: Wave 1 (with Tasks 1, 2, 3, 4)
    • Blocks: None
    • Blocked By: None

    References:

    Acceptance Criteria:

    • Persist middleware added to store
    • Settings saved to localStorage on change
    • Settings restored on page load
    • Sensitive data NOT persisted
    • Page refresh preserves filters and view mode

    QA Scenarios:

    Scenario: Settings persist across page refresh
      Tool: Playwright
      Steps:
        1. Navigate to http://localhost:3000
        2. Set view mode to "Network"
        3. Set orientation to "Top to Bottom"
        4. Toggle left panel closed
        5. Refresh page (F5)
        6. Check view mode, orientation, panel state
      Expected Result: All settings match pre-refresh state
      Evidence: .sisyphus/evidence/task-5-persistence.png
    
    Scenario: Nodes NOT persisted in localStorage
      Tool: Playwright
      Steps:
        1. Navigate to http://localhost:3000
        2. Open browser DevTools > Application > Local Storage
        3. Check homelab-topology-settings value
      Expected Result: No 'nodes' or 'edges' keys in storage
      Evidence: .sisyphus/evidence/task-5-no-sensitive-data.txt
    

    Commit: YES

    • Message: feat(ui): add localStorage persistence
    • Files: src/store/topologyStore.ts

  • 6. Implement SSH Discovery API Endpoint

    What to do:

    • Create server/routes/discover.ts with Express router
    • Implement POST /api/discover endpoint:
      • Load host configs from getHostConfigs()
      • Call discoverHostViaSSH() from existing sshDiscovery.ts for each host
      • Run discoveries in parallel with Promise.all
      • Handle errors gracefully (partial success)
      • Return DiscoveryResponse with hosts, timestamp, errors array
    • Add 30-second timeout per host
    • Import and use existing sshDiscovery functions

    Must NOT do:

    • Do NOT expose SSH credentials in response
    • Do NOT add retry logic (keep simple)
    • Do NOT cache results (always fresh discovery)
    • Do NOT add WebSocket push (polling is sufficient)

    Recommended Agent Profile:

    • Category: deep
      • Reason: Complex async operations, error handling, SSH integration
    • Skills: [system-admin, ssh]
      • system-admin: Understanding SSH operations
      • ssh: SSH client configuration and error handling

    Parallelization:

    • Can Run In Parallel: NO (depends on Tasks 1, 2)
    • Parallel Group: Wave 2 (must complete before 7-10)
    • Blocks: Tasks 7, 8, 9, 10
    • Blocked By: Tasks 1, 2

    References:

    • src/services/sshDiscovery.ts:61-121 - Existing discoverHostViaSSH function
    • src/services/sshDiscovery.ts:114-132 - discoverAllHostsSSH pattern
    • server/types.ts - HostConfig, DiscoveryResponse interfaces
    • server/config.ts - getHostConfigs() function

    Acceptance Criteria:

    • server/routes/discover.ts exists
    • POST /api/discover endpoint functional
    • Returns JSON with hosts array, timestamp, errors
    • Handles SSH failures gracefully (partial success)
    • No SSH credentials in response

    QA Scenarios:

    Scenario: Discovery API returns host data
      Tool: Bash (curl)
      Steps:
        1. Ensure server running: bun run server
        2. curl -X POST http://localhost:3001/api/discover
        3. Parse JSON response
      Expected Result: Response has hosts array with host objects
      Evidence: .sisyphus/evidence/task-6-discover-success.json
    
    Scenario: Discovery handles SSH failure gracefully
      Tool: Bash (curl)
      Steps:
        1. Add unreachable host to config
        2. curl -X POST http://localhost:3001/api/discover
        3. Check response has errors array
      Expected Result: Response includes error for unreachable host, other hosts succeed
      Evidence: .sisyphus/evidence/task-6-discover-partial.json
    

    Commit: YES

    • Message: feat(api): implement SSH discovery endpoint
    • Files: server/routes/discover.ts, server/index.ts (register router)

  • 7. Wire Frontend to Discovery API

    What to do:

    • Update App.tsx loadData() to call backend API instead of simulated discovery
    • Add API_BASE_URL constant (default: http://localhost:3001)
    • Handle API errors with fallback to simulated data
    • Add loading states per host (show which hosts are still loading)
    • Update Footer to show "Live data" or "Simulated data" indicator

    Must NOT do:

    • Do NOT hardcode API URL (use environment variable)
    • Do NOT add complex error UI (console.error + fallback is enough)
    • Do NOT add retry logic in frontend (keep simple)

    Recommended Agent Profile:

    • Category: quick
      • Reason: Simple fetch call with error handling
    • Skills: []

    Parallelization:

    • Can Run In Parallel: NO (depends on Task 6)
    • Parallel Group: Wave 2 (runs after Task 6)
    • Blocks: Tasks 8, 9, 10, 11, 13, 14
    • Blocked By: Task 6

    References:

    • src/App.tsx:33-73 - Existing loadData() function to modify
    • src/services/discovery.ts:264-340 - Existing discoverHosts (will be fallback)
    • src/services/sshDiscovery.ts - Will be called via API instead

    Acceptance Criteria:

    • Frontend calls backend API for discovery
    • Falls back to simulated data on API error
    • Loading state shows during API call
    • Footer shows data source indicator

    QA Scenarios:

    Scenario: Frontend shows real data from API
      Tool: Playwright
      Steps:
        1. Start backend: bun run server
        2. Start frontend: bun run dev
        3. Navigate to http://localhost:3000
        4. Check footer for "Live data" indicator
      Expected Result: Topology shows real SSH-discovered data
      Evidence: .sisyphus/evidence/task-7-live-data.png
    
    Scenario: Frontend falls back on API error
      Tool: Playwright
      Steps:
        1. Stop backend server
        2. Navigate to http://localhost:3000
        3. Check footer for "Simulated data" indicator
      Expected Result: Topology shows simulated data, no crash
      Evidence: .sisyphus/evidence/task-7-fallback.png
    

    Commit: YES

    • Message: feat(ui): wire frontend to discovery API
    • Files: src/App.tsx

  • 8. Implement Config Tab with Real Data

    What to do:

    • Create server/routes/config.ts with Express router
    • Implement GET /api/config/:host/:container endpoint:
      • SSH to host
      • Find docker-compose.yml location (check common paths)
      • Read file content
      • Parse YAML to find specific service
      • Return ConfigResponse with yaml string, path, error if any
    • Update RightPanel.tsx ConfigTab:
      • Fetch config from API when service node selected
      • Display YAML with syntax highlighting (use <pre> with monospace)
      • Show error message if config not found
      • Show "Loading..." state during fetch

    Must NOT do:

    • Do NOT add YAML editing (read-only)
    • Do NOT show full docker-compose (only selected service section)
    • Do NOT add config validation (just display)

    Recommended Agent Profile:

    • Category: quick
      • Reason: Simple file read and display
    • Skills: []

    Parallelization:

    • Can Run In Parallel: YES
    • Parallel Group: Wave 2 (with Tasks 9, 10, after Task 7)
    • Blocks: None
    • Blocked By: Tasks 6, 7

    References:

    • src/components/RightPanel.tsx:122-141 - Existing ConfigTab to update
    • src/services/sshDiscovery.ts:46-59 - SSH exec pattern
    • Common docker-compose paths: ./docker-compose.yml, /opt/docker-compose.yml

    Acceptance Criteria:

    • GET /api/config/:host/:container endpoint exists
    • ConfigTab fetches and displays YAML
    • Shows error message when config not found
    • Shows "Loading..." during fetch

    QA Scenarios:

    Scenario: Config tab shows docker-compose content
      Tool: Playwright
      Steps:
        1. Navigate to http://localhost:3000
        2. Click on a service node (e.g., traefik)
        3. Click "Config" tab in right panel
      Expected Result: YAML content displayed in monospace font
      Evidence: .sisyphus/evidence/task-8-config-display.png
    
    Scenario: Config tab handles missing file
      Tool: Bash (curl)
      Steps:
        1. curl http://localhost:3001/api/config/nonexistent/container
      Expected Result: Response with error field, not 500
      Evidence: .sisyphus/evidence/task-8-config-error.json
    

    Commit: YES

    • Message: feat(ui): add real config data to Config tab
    • Files: server/routes/config.ts, src/components/RightPanel.tsx, server/index.ts

  • 9. Implement Files Tab with Real Data

    What to do:

    • Create server/routes/files.ts with Express router
    • Implement GET /api/files/:host/:container endpoint:
      • SSH to host
      • Run docker inspect <container> --format '{{json .Mounts}}'
      • Parse JSON array of mounts
      • Return FilesResponse with volumes array (source, destination, mode)
    • Update RightPanel.tsx FilesTab:
      • Fetch files from API when service node selected
      • Display list of volume mounts with Source → Destination
      • Show "No volumes" message if empty
      • Show "Loading..." state during fetch

    Must NOT do:

    • Do NOT browse files (just show mount points)
    • Do NOT add file content preview
    • Do NOT follow symlinks (show as-is)

    Recommended Agent Profile:

    • Category: quick
      • Reason: Simple docker inspect parsing
    • Skills: []

    Parallelization:

    • Can Run In Parallel: YES
    • Parallel Group: Wave 2 (with Tasks 8, 10, after Task 7)
    • Blocks: None
    • Blocked By: Tasks 6, 7

    References:

    • src/components/RightPanel.tsx:143-163 - Existing FilesTab to update
    • src/services/sshDiscovery.ts:46-59 - SSH exec pattern
    • Docker inspect format: {{json .Mounts}}

    Acceptance Criteria:

    • GET /api/files/:host/:container endpoint exists
    • FilesTab fetches and displays volume mounts
    • Shows "No volumes" when container has none
    • Shows "Loading..." during fetch

    QA Scenarios:

    Scenario: Files tab shows volume mounts
      Tool: Playwright
      Steps:
        1. Navigate to http://localhost:3000
        2. Click on a service node with volumes (e.g., jellyfin)
        3. Click "Files" tab in right panel
      Expected Result: List of volume mounts with source and destination
      Evidence: .sisyphus/evidence/task-9-files-display.png
    
    Scenario: Files API returns mount data
      Tool: Bash (curl)
      Steps:
        1. curl http://localhost:3001/api/files/ubuntu/jellyfin
      Expected Result: JSON array with volume objects
      Evidence: .sisyphus/evidence/task-9-files-api.json
    

    Commit: YES

    • Message: feat(ui): add real volume data to Files tab
    • Files: server/routes/files.ts, src/components/RightPanel.tsx, server/index.ts

  • 10. Implement Usage Tab with Real Data

    What to do:

    • Create server/routes/stats.ts with Express router
    • Implement GET /api/stats/:host/:container endpoint:
      • SSH to host
      • Run docker stats <container> --no-stream --format '{"cpu":"{{.CPUPerc}}","mem":"{{.MemPerc}}","net":"{{.NetIO}}"}'
      • Parse output (remove % signs, convert to numbers)
      • Return StatsResponse with cpu, memory, network values
    • Update RightPanel.tsx UsageTab:
      • Fetch stats from API when service node selected
      • Display progress bars for CPU and Memory
      • Show network I/O as text
      • Auto-refresh stats every 5 seconds while tab is visible
      • Show "N/A" for stopped containers

    Must NOT do:

    • Do NOT add historical charts (live only)
    • Do NOT add alerting thresholds
    • Do NOT store stats data

    Recommended Agent Profile:

    • Category: quick
      • Reason: Simple docker stats parsing and display
    • Skills: [system-admin]
      • system-admin: Understanding docker stats format

    Parallelization:

    • Can Run In Parallel: YES
    • Parallel Group: Wave 2 (with Tasks 8, 9, after Task 7)
    • Blocks: None
    • Blocked By: Tasks 6, 7

    References:

    • src/components/RightPanel.tsx:165-209 - Existing UsageTab to update
    • src/services/sshDiscovery.ts:46-59 - SSH exec pattern
    • Docker stats format: --format flag with Go templates

    Acceptance Criteria:

    • GET /api/stats/:host/:container endpoint exists
    • UsageTab fetches and displays live stats
    • Stats auto-refresh every 5 seconds
    • Shows "N/A" for stopped containers

    QA Scenarios:

    Scenario: Usage tab shows live docker stats
      Tool: Playwright
      Steps:
        1. Navigate to http://localhost:3000
        2. Click on a running service node
        3. Click "Usage" tab in right panel
        4. Wait 5 seconds for refresh
      Expected Result: CPU/Memory bars update with real values
      Evidence: .sisyphus/evidence/task-10-usage-live.png
    
    Scenario: Stats API returns numeric data
      Tool: Bash (curl)
      Steps:
        1. curl http://localhost:3001/api/stats/ubuntu/traefik
      Expected Result: JSON with cpu, memory as numbers (0-100)
      Evidence: .sisyphus/evidence/task-10-stats-api.json
    

    Commit: YES

    • Message: feat(ui): add live docker stats to Usage tab
    • Files: server/routes/stats.ts, src/components/RightPanel.tsx, server/index.ts

  • 11. Implement Configurable Poll Interval

    What to do:

    • Add pollInterval: number to topologyStore (default: 30000ms)
    • Add setPollInterval(ms: number) action
    • Update App.tsx to use store's pollInterval instead of hardcoded constant
    • Add settings dropdown in Header (gear icon) with poll interval options:
      • 10 seconds, 30 seconds, 1 minute, 5 minutes
    • Update Footer countdown to reflect new interval
    • Include pollInterval in localStorage persistence

    Must NOT do:

    • Do NOT add freeform input (use preset options only)
    • Do NOT add "pause polling" button (keep simple)
    • Do NOT show interval in seconds (human-friendly format)

    Recommended Agent Profile:

    • Category: quick
      • Reason: Simple dropdown and store integration
    • Skills: [frontend-ui-ux]
      • frontend-ui-ux: Settings UI design

    Parallelization:

    • Can Run In Parallel: YES
    • Parallel Group: Wave 3 (with Tasks 12, 13, 14)
    • Blocks: None
    • Blocked By: Task 7

    References:

    • src/App.tsx:15 - POLLING_INTERVAL_MS constant to replace
    • src/App.tsx:78-81 - setInterval to make dynamic
    • src/components/Header.tsx:78-89 - Dropdown pattern to reference
    • src/store/topologyStore.ts - Add pollInterval state

    Acceptance Criteria:

    • pollInterval in store with 30s default
    • Settings dropdown in Header with 4 options
    • Polling interval updates when changed
    • Footer countdown reflects new interval
    • Setting persists across refresh

    QA Scenarios:

    Scenario: Poll interval can be changed
      Tool: Playwright
      Steps:
        1. Navigate to http://localhost:3000
        2. Click settings icon in Header
        3. Select "5 minutes" option
        4. Check footer countdown shows "299s" → "298s"
      Expected Result: Countdown reflects 5-minute interval
      Evidence: .sisyphus/evidence/task-11-poll-5min.png
    
    Scenario: Poll interval persists
      Tool: Playwright
      Steps:
        1. Set poll interval to "1 minute"
        2. Refresh page
        3. Check footer countdown
      Expected Result: Countdown starts at 59s (1 minute)
      Evidence: .sisyphus/evidence/task-11-poll-persist.png
    

    Commit: YES

    • Message: feat(ui): add configurable poll interval
    • Files: src/store/topologyStore.ts, src/App.tsx, src/components/Header.tsx

  • 12. Implement Command Palette

    What to do:

    • Create src/components/CommandPalette.tsx modal component
    • Add commandPaletteOpen: boolean to topologyStore
    • Implement Cmd+K keyboard shortcut to toggle palette
    • Palette shows searchable list of commands:
      • "Refresh discovery" → triggers refresh
      • "Toggle [type] filter" → toggles each type filter
      • "Set view: Full/Network/Hosts/Services/Files" → changes view
      • "Set orientation: LR/TB" → changes orientation
    • Fuzzy search filters commands as user types
    • Arrow keys navigate, Enter executes, Escape closes
    • Click outside or Escape to close

    Must NOT do:

    • Do NOT add command history
    • Do NOT add custom commands
    • Do NOT add macro recording

    Recommended Agent Profile:

    • Category: visual-engineering
      • Reason: Modal UI with keyboard navigation, fuzzy search
    • Skills: [frontend-ui-ux]
      • frontend-ui-ux: Modal design, keyboard interactions

    Parallelization:

    • Can Run In Parallel: YES
    • Parallel Group: Wave 3 (with Tasks 11, 13, 14)
    • Blocks: None
    • Blocked By: Tasks 3, 4 (needs filter state)

    References:

    • src/components/Header.tsx - View mode and filter actions to wire
    • src/store/topologyStore.ts - Actions to call from commands
    • Cmd+K pattern: Standard in VS Code, Linear, etc.

    Acceptance Criteria:

    • Cmd+K opens command palette modal
    • Fuzzy search filters commands
    • Arrow keys navigate, Enter executes
    • Escape or click-outside closes
    • Commands execute correct actions

    QA Scenarios:

    Scenario: Command palette opens with Cmd+K
      Tool: Playwright
      Steps:
        1. Navigate to http://localhost:3000
        2. Press Meta+K (or Ctrl+K on Windows)
      Expected Result: Modal appears with search input focused
      Evidence: .sisyphus/evidence/task-12-palette-open.png
    
    Scenario: Search and execute command
      Tool: Playwright
      Steps:
        1. Open command palette
        2. Type "network"
        3. Press Enter
      Expected Result: View mode changes to Network
      Evidence: .sisyphus/evidence/task-12-palette-execute.png
    
    Scenario: Escape closes palette
      Tool: Playwright
      Steps:
        1. Open command palette
        2. Press Escape
      Expected Result: Modal closes, focus returns to graph
      Evidence: .sisyphus/evidence/task-12-palette-close.png
    

    Commit: YES

    • Message: feat(ux): add command palette with Cmd+K
    • Files: src/components/CommandPalette.tsx, src/App.tsx, src/store/topologyStore.ts

  • 13. Implement Stale Data Warning

    What to do:

    • Add consecutiveFailures: number and lastSuccessfulDiscovery: Date | null to topologyStore
    • Increment consecutiveFailures on API error, reset to 0 on success
    • Create src/components/StaleWarning.tsx banner component
    • Show banner when consecutiveFailures >= 3
    • Banner shows: "Data may be stale - Last successful: [timestamp]"
    • Banner has dismiss button (X) that temporarily hides it
    • Banner auto-dismisses on successful discovery
    • Style: Yellow/warning background, at top of main content area

    Must NOT do:

    • Do NOT add retry button (use main Refresh button)
    • Do NOT show detailed error messages
    • Do NOT add notification sound

    Recommended Agent Profile:

    • Category: quick
      • Reason: Simple conditional banner with dismiss
    • Skills: [frontend-ui-ux]
      • frontend-ui-ux: Warning banner styling

    Parallelization:

    • Can Run In Parallel: YES
    • Parallel Group: Wave 3 (with Tasks 11, 12, 14)
    • Blocks: None
    • Blocked By: Task 7

    References:

    • src/App.tsx:63-69 - Error handling in loadData to update
    • src/store/topologyStore.ts - Add failure tracking state
    • Warning banner pattern: Common UI pattern

    Acceptance Criteria:

    • consecutiveFailures tracked in store
    • Banner shows after 3 consecutive failures
    • Banner shows last successful timestamp
    • Dismiss button hides banner temporarily
    • Banner auto-dismisses on success

    QA Scenarios:

    Scenario: Warning appears after failures
      Tool: Playwright
      Steps:
        1. Stop backend server
        2. Navigate to http://localhost:3000
        3. Wait for 3 discovery attempts (90 seconds or speed up polling)
      Expected Result: Yellow warning banner visible at top
      Evidence: .sisyphus/evidence/task-13-stale-warning.png
    
    Scenario: Warning dismisses on success
      Tool: Playwright
      Steps:
        1. Show warning banner (3+ failures)
        2. Start backend server
        3. Wait for next discovery
      Expected Result: Banner disappears automatically
      Evidence: .sisyphus/evidence/task-13-warning-dismiss.png
    

    Commit: YES

    • Message: feat(ux): add stale data warning banner
    • Files: src/components/StaleWarning.tsx, src/App.tsx, src/store/topologyStore.ts

  • 14. Implement Path Highlighting

    What to do:

    • Add highlightPath: string[] to topologyStore (array of node IDs from root to selected)
    • Add setHighlightPath(ids: string[]) action
    • When node selected, calculate path from gateway to that node:
      • Walk up parentId chain from selected node to root
      • Store all ancestor IDs in highlightPath
    • Update TopologyGraph.tsx to style highlighted nodes:
      • Highlighted nodes get brighter border (2px solid, accent color)
      • Non-highlighted nodes get dimmed opacity (0.5)
      • Highlighted edges get accent color
    • Clear highlightPath when clicking background (deselect)

    Must NOT do:

    • Do NOT animate the highlighting
    • Do NOT add "highlight path" button (automatic on select)
    • Do NOT highlight children (only ancestors)

    Recommended Agent Profile:

    • Category: visual-engineering
      • Reason: Graph styling with conditional classes
    • Skills: [frontend-ui-ux]
      • frontend-ui-ux: Visual highlighting design

    Parallelization:

    • Can Run In Parallel: YES
    • Parallel Group: Wave 3 (with Tasks 11, 12, 13)
    • Blocks: None
    • Blocked By: Task 7

    References:

    • src/components/Graph/TopologyGraph.tsx - Node styling to update
    • src/store/topologyStore.ts:71-77 - getChildNodes pattern to reference
    • Node data has parentId field for walking ancestors

    Acceptance Criteria:

    • highlightPath calculated on node selection
    • Ancestor nodes have highlighted style
    • Non-ancestor nodes dimmed
    • Edges to ancestors highlighted
    • Clicking background clears highlight

    QA Scenarios:

    Scenario: Path highlighting shows ancestors
      Tool: Playwright
      Steps:
        1. Navigate to http://localhost:3000
        2. Click on a container node (e.g., ubuntu-traefik)
        3. Check visual state of ancestor nodes (ubuntu, vlan-50, gateway)
      Expected Result: Ancestors have bright border, others dimmed
      Evidence: .sisyphus/evidence/task-14-path-highlight.png
    
    Scenario: Clicking background clears highlight
      Tool: Playwright
      Steps:
        1. Select a node (path highlighted)
        2. Click on empty graph background
      Expected Result: All nodes return to normal opacity
      Evidence: .sisyphus/evidence/task-14-highlight-clear.png
    

    Commit: YES

    • Message: feat(ux): add path highlighting for selected nodes
    • Files: src/components/Graph/TopologyGraph.tsx, src/store/topologyStore.ts

Final Verification Wave

  • F1. Plan Compliance Auditoracle Read the plan end-to-end. For each "Must Have": verify implementation exists. For each "Must NOT Have": search codebase for forbidden patterns. Check evidence files exist in .sisyphus/evidence/. Compare deliverables against plan. Output: Must Have [N/N] | Must NOT Have [N/N] | Tasks [N/N] | VERDICT: APPROVE/REJECT

  • F2. Code Quality Reviewunspecified-high Run tsc --noEmit + bun run lint + bun test. Review all changed files for: as any/@ts-ignore, empty catches, console.log in prod, commented-out code, unused imports. Check AI slop: excessive comments, over-abstraction. Output: Build [PASS/FAIL] | Lint [PASS/FAIL] | Tests [N pass/N fail] | Files [N clean/N issues] | VERDICT

  • F3. Real Manual QAunspecified-high (+ playwright skill) Start from clean state. Execute EVERY QA scenario from EVERY task — follow exact steps, capture evidence. Test cross-task integration. Test edge cases: empty state, invalid input. Save to .sisyphus/evidence/final-qa/. Output: Scenarios [N/N pass] | Integration [N/N] | Edge Cases [N tested] | VERDICT

  • F4. Scope Fidelity Checkdeep For each task: read "What to do", read actual diff. Verify 1:1 — everything in spec was built, nothing beyond spec. Check "Must NOT do" compliance. Detect cross-task contamination. Output: Tasks [N/N compliant] | Contamination [CLEAN/N issues] | Unaccounted [CLEAN/N files] | VERDICT


Commit Strategy

  • Wave 1: feat(backend): add Express server foundation with types
  • Wave 1: feat(ui): add type and status filters, localStorage persistence
  • Wave 2: feat(api): implement SSH discovery and data endpoints
  • Wave 2: feat(ui): wire real data to Config/Files/Usage tabs
  • Wave 3: feat(ux): add command palette, stale warning, path highlighting
  • Final: feat: complete remaining features implementation

Success Criteria

Verification Commands

# Backend health check
curl http://localhost:3001/api/health
# Expected: {"status":"ok"}

# SSH discovery
curl http://localhost:3001/api/discover | jq '.hosts | length'
# Expected: 6 (number of configured hosts)

# Config retrieval
curl http://localhost:3001/api/config/ubuntu/traefik
# Expected: YAML content of docker-compose.yml

# Frontend build
cd /home/christopher/homelab-topology && bun run build
# Expected: Build succeeds with no errors

# Type check
bun run tsc --noEmit
# Expected: No type errors

Final Checklist

  • All "Must Have" features present
  • All "Must NOT Have" constraints respected
  • No SSH credentials in localStorage
  • Backend API functional on port 3001
  • Frontend connects to backend
  • All filters functional and persistent
  • Command palette works with Cmd+K
  • Stale warning appears on failures
  • Path highlighting shows ancestors