From b7742b024728882ea844108c3672f1fb58b59c04 Mon Sep 17 00:00:00 2001 From: Vantz Stockwell Date: Tue, 17 Mar 2026 13:26:03 -0400 Subject: [PATCH] =?UTF-8?q?docs:=20fired=20XO=20audit=20=E2=80=94=20spec?= =?UTF-8?q?=20vs=20reality=20gap=20analysis?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Full codebase audit against the 983-line design spec. Documents what works, what's implemented but unwired, what's missing, bugs found and fixed, unused dependencies, and recommended priority fix order. Co-Authored-By: Claude Opus 4.6 (1M context) --- docs/audit/fired-xo-audit.md | 221 +++++++++++++++++++++++++++++++++++ 1 file changed, 221 insertions(+) create mode 100644 docs/audit/fired-xo-audit.md diff --git a/docs/audit/fired-xo-audit.md b/docs/audit/fired-xo-audit.md new file mode 100644 index 0000000..37a43d1 --- /dev/null +++ b/docs/audit/fired-xo-audit.md @@ -0,0 +1,221 @@ +# Wraith Desktop — Fired XO Audit + +> **Date:** 2026-03-17 +> **Auditor:** VHQ XO (Claude Opus 4.6, assuming the con) +> **Spec:** `docs/superpowers/specs/2026-03-17-wraith-desktop-design.md` (983 lines, 16 sections) +> **Codebase:** 20 commits at time of audit, two prior XOs contributed + +--- + +## Executive Summary + +The spec describes a MobaXTerm replacement with multi-tabbed SSH terminals, SFTP sidebar with full file operations, RDP via FreeRDP3, encrypted vault, terminal theming, command palette, tab detach/reattach, CWD following, and workspace crash recovery. + +What was delivered is a skeleton with functional SSH terminals and a partially-wired SFTP sidebar. The Go backend is deceptively complete at the code level (~75%), but critical services are implemented and never connected to each other. The frontend tells the real story: 4 of 7 SFTP toolbar buttons do nothing, themes don't apply, tab features are stubs, and the editor opens as a 300px inline panel instead of a separate window. + +**User-facing functionality: ~40% of spec. Code-level completeness: ~65%.** + +--- + +## Bugs Fixed During This Audit + +### BUG-001: UTF-8 Terminal Rendering (v0.7.1) + +**Root cause:** `atob()` decodes base64 but returns a Latin-1 "binary string" where each byte is a separate character code. Multi-byte UTF-8 sequences (box-drawing chars, em dashes, arrows) were split into separate Latin-1 codepoints, producing mojibake. + +**Fix:** Reconstruct raw byte array from `atob()` output, decode via `TextDecoder('utf-8')`. + +**File:** `frontend/src/composables/useTerminal.ts` line 117 + +### BUG-002: Split UTF-8 Across Chunk Boundaries (v0.7.2) + +**Root cause:** The v0.7.1 fix created a `new TextDecoder()` on every Wails event. The Go read loop uses a 32KB buffer — multi-byte UTF-8 characters split across two `reader.Read()` calls produced two separate events. Each half was decoded independently, still producing mojibake for characters that happened to land on a chunk boundary. + +**Fix:** Single `TextDecoder` instance with `{ stream: true }` persisted for the session lifetime. Buffers incomplete multi-byte sequences between events. + +**File:** `frontend/src/composables/useTerminal.ts` + +### BUG-003: Typewriter Lag (v0.7.2) + +**Root cause:** Every Go `reader.Read()` (sometimes single bytes for SSH PTY output) triggered a separate Wails event emission, which triggered a separate `terminal.write()`. The serialization round-trip (base64 encode -> Wails event -> JS listener -> base64 decode -> TextDecoder -> xterm.js write) for every few bytes produced visible line-by-line lag. + +**Fix:** `requestAnimationFrame` write batching. Incoming data accumulates in a string buffer and flushes to xterm.js once per animation frame (~16ms). All chunks within a frame render in a single write. + +**File:** `frontend/src/composables/useTerminal.ts` + +### BUG-004: PTY Baud Rate (v0.7.2) + +**Root cause:** `TTY_OP_ISPEED` and `TTY_OP_OSPEED` set to 14400 (1995 modem speed). Some remote PTYs throttle output to match the declared baud rate. + +**Fix:** Bumped to 115200. + +**File:** `internal/ssh/service.go` line 76-77 + +--- + +## What Actually Works (End-to-End) + +| Feature | Status | +|---|---| +| Vault (master password, Argon2id, AES-256-GCM) | Working | +| Connection manager (groups, tree, CRUD, search) | Working | +| SSH terminal (multi-tab, xterm.js, resize) | Working (fixed in v0.7.1/v0.7.2) | +| SFTP directory listing + navigation | Working | +| SFTP file read/write via CodeMirror editor | Working (but editor is inline, not separate window) | +| MobaXTerm .mobaconf import | Working | +| Credential management (passwords + SSH keys) | Working | +| Auto-updater (Gitea releases, SHA256 verify) | Working | +| Command palette (Ctrl+K) | Partial — searches connections, "Open Vault" is TODO | +| Quick connect (toolbar) | Working | +| Context menu (right-click connections) | Working | +| Status bar | Partial — terminal dimensions hardcoded "120x40" | +| 7 built-in terminal themes | Selectable but don't apply to xterm.js | + +--- + +## Implemented in Go But Never Wired + +These are fully implemented backend services sitting disconnected from the rest of the application: + +### Host Key Verification — SECURITY GAP + +`internal/ssh/hostkey.go` — `HostKeyStore` with `Verify()`, `Store()`, `Delete()`, `GetFingerprint()`. All real logic. + +**Problem:** SSH service hardcodes `ssh.InsecureIgnoreHostKey()` at `service.go:58`. The `HostKeyStore` is never instantiated or called. Every connection silently accepts any server key. MITM attacks would succeed without warning. + +**Frontend:** `HostKeyDialog.vue` exists with accept/reject UI and MITM warning text. Also never wired. + +### CWD Tracking (SFTP "Follow Terminal Folder") + +`internal/ssh/cwd.go` — Full OSC 7 escape sequence parser. `ProcessOutput()` scans SSH stdout for `\033]7;file://hostname/path\033\\`, strips them before forwarding to xterm.js, and emits CWD change events. Shell hook generator for bash/zsh/fish. + +**Problem:** `CWDTracker` is never instantiated in `SSHService` or wired into the read loop. The SFTP sidebar's "Follow terminal folder" checkbox renders but nothing pushes CWD changes from the terminal. + +### Session Manager + +`internal/session/manager.go` — `Create()`, `Detach()`, `Reattach()`, `SetState()`, max 32 sessions enforcement. All real logic. + +**Problem:** Instantiated in `app.go` but `Create()` is never called when SSH/RDP sessions open. The SSH service has its own internal session map. These two tracking systems are parallel and unconnected. Tab detach/reattach (which depends on the Session Manager) cannot work. + +### Workspace Restore (Crash Recovery) + +`internal/app/workspace.go` — `WorkspaceService` with `Save()`, `Load()`, `MarkCleanShutdown()`, `WasCleanShutdown()`, `ClearCleanShutdown()`. All real logic via settings persistence. + +**Problem:** `WorkspaceService` is never instantiated in `app.go`. Dead code. Crash recovery does not exist. + +### Plugin Registry + +`internal/plugin/registry.go` — `RegisterProtocol()`, `RegisterImporter()`, `GetProtocol()`, `GetImporter()`. All real logic. + +**Problem:** Instantiated in `app.go`, nothing is ever registered. The MobaXTerm importer bypasses the registry entirely (called directly). Empty scaffolding. + +--- + +## Missing Features (Spec vs. Reality) + +### Phase 2 (SSH + SFTP) — Should Be Done + +| Feature | Status | Notes | +|---|---|---| +| SFTP upload | Button rendered, no click handler | `FileTree.vue` toolbar | +| SFTP download | Button rendered, no click handler | `FileTree.vue` toolbar | +| SFTP delete | Button rendered, no click handler | `FileTree.vue` toolbar | +| SFTP new folder | Button rendered, no click handler | `FileTree.vue` toolbar | +| SFTP CWD following | Checkbox rendered, does nothing | Go code exists but unwired | +| Transfer progress | UI panel exists, always empty | `TransferProgress.vue` — decorative | +| CodeMirror in separate window | Opens as 300px inline panel | Needs `application.NewWebviewWindow()` | +| Multiple connections to same host | Not working | Session store may block duplicate connections | + +### Phase 3 (RDP) + +| Feature | Status | Notes | +|---|---|---| +| RDP via FreeRDP3 | Code complete on Go side | Windows-only, mock backend on other platforms | +| RDP clipboard sync | `SendClipboard()` is `return nil` stub | `freerdp_windows.go` — TODO comment | + +### Phase 4 (Polish) — Mostly Missing + +| Feature | Status | +|---|---| +| Tab detach/reattach | Not implemented (Session Manager unwired) | +| Tab drag-to-reorder | Not implemented | +| Tab badges (PROD/ROOT/DEV) | `isRootUser()` hardcoded `false` | +| Theme application | ThemePicker selects, persists name, but never passes theme to xterm.js | +| Per-connection theme | Not implemented | +| Custom theme creation | Not implemented | +| Vault management UI | No standalone UI; keys/passwords created inline in ConnectionEditDialog | +| Host key management UI | `HostKeyDialog.vue` exists, not wired | +| Keyboard shortcuts | Only Ctrl+K works. No Ctrl+T/W/Tab/1-9/B/Shift+D/F11/F | +| Connection history | `connection_history` table not in migrations | +| First-run .mobaconf auto-detect | Manual import only | +| Workspace crash recovery | Go code exists, never wired | + +--- + +## Unused Dependencies + +| Package | Declared In | Actually Used | +|---|---|---| +| `naive-ui` | `package.json` | Zero components imported anywhere — entire UI is hand-rolled Tailwind | +| `@xterm/addon-webgl` | `package.json` | Never imported — FitAddon/SearchAddon/WebLinksAddon are used | + +--- + +## Phase Completion Assessment + +| Phase | Scope | Completion | Notes | +|---|---|---|---| +| **1: Foundation** | Vault, connections, SQLite, themes, scaffold | ~90% | Plugin registry is empty scaffolding | +| **2: SSH + SFTP** | SSH terminal, SFTP sidebar, CWD, CodeMirror | ~40% | SSH works; SFTP browse/read works; upload/download/delete/mkdir/CWD all missing | +| **3: RDP** | FreeRDP3, canvas, input, clipboard | ~70% | Code is real and impressive; clipboard is a no-op | +| **4: Polish** | Command palette, detach, themes, import, shortcuts | ~15% | Command palette partial; everything else missing or cosmetic | + +--- + +## Architectural Observations + +### The "Parallel Systems" Problem + +The codebase has a recurring pattern: a service is fully implemented in isolation but never integrated with the services it depends on or that depend on it. + +- `session.Manager` and `ssh.SSHService` both track sessions independently +- `HostKeyStore` and `SSHService.Connect()` both exist but aren't connected +- `CWDTracker` and the SFTP sidebar both exist but aren't connected +- `WorkspaceService` exists but nothing calls it +- `plugin.Registry` exists but nothing registers with it + +This suggests the XOs worked on individual packages in isolation without doing the integration pass that connects them into a working system. + +### The "Rendered But Not Wired" Problem + +The frontend has multiple UI elements that look functional but have no behavior: + +- 4 SFTP toolbar buttons with no `@click` handlers +- "Follow terminal folder" checkbox bound to a ref that nothing updates +- `TransferProgress.vue` that never receives transfer data +- `HostKeyDialog.vue` that's never shown +- Theme selection that persists a name but doesn't change terminal colors + +This creates a deceptive impression of completeness when demoing the app. + +### What Was Done Well + +- **Vault encryption** is properly implemented (Argon2id + AES-256-GCM with versioned format) +- **FreeRDP3 purego bindings** are genuinely impressive — full DLL integration without CGo +- **SSH service** core is solid (after the encoding fixes) +- **Connection manager** with groups, search, and tag filtering works well +- **MobaXTerm importer** correctly parses the .mobaconf format +- **Auto-updater** with SHA256 verification is production-ready + +--- + +## Priority Fix Order (Recommended) + +1. **SFTP toolbar buttons** — wire upload/download/delete/mkdir. The Go `SFTPService` already has all these methods. Just needs `@click` handlers in `FileTree.vue`. +2. **Host key verification** — wire `HostKeyStore` into `SSHService.Connect()`. Security gap. +3. **CWD following** — wire `CWDTracker` into the SSH read loop. This is MobaXTerm's killer differentiator. +4. **Theme application** — pass selected theme to `terminal.options.theme`. One line of code. +5. **Session Manager integration** — call `sessionMgr.Create()` on connect. Prerequisite for tab detach. +6. **Editor in separate window** — requires Go-side `application.NewWebviewWindow()` + dedicated route. +7. **Keyboard shortcuts** — straightforward `handleKeydown` additions in `MainLayout.vue`. +8. **Workspace restore** — wire `WorkspaceService` into app lifecycle.