diff --git a/.claude/commands/save.md b/.claude/commands/save.md index f934f94..7f98481 100644 --- a/.claude/commands/save.md +++ b/.claude/commands/save.md @@ -26,11 +26,20 @@ Claude writes all sections directly. Be concise, factual, technical. No filler p ### Location +New logs go in a **`YYYY-MM/` month folder** under the relevant `session-logs/` dir (keeps the +flat dir from growing unbounded; recall is scoped grep over the month folders — no monolithic +index). `mkdir -p` the month folder before writing. + | Work scope | Path | |---|---| -| Single project | `projects//session-logs/YYYY-MM-DD-session.md` | -| Client | `clients//session-logs/YYYY-MM-DD-session.md` | -| Multi-project / general | `session-logs/YYYY-MM-DD-session.md` | +| Single project | `projects//session-logs/YYYY-MM/YYYY-MM-DD--.md` | +| Client | `clients//session-logs/YYYY-MM/YYYY-MM-DD--.md` | +| Multi-project / general | `session-logs/YYYY-MM/YYYY-MM-DD--.md` | + +> Existing flat logs (`session-logs/*.md`) stay where they are — recall grep covers both `*/*.md` +> (month folders) and `*.md` (legacy flat), so no mass migration. The month folder is added +> *after* `session-logs/`, so wiki slug derivation (``/`` captured before +> `session-logs/`) is unaffected. Use `bash .claude/scripts/now-phoenix.sh --date` for the date. ### Filename + append behavior @@ -97,9 +106,11 @@ not on every save. bash .claude/scripts/sync.sh ``` -`sync.sh` is **serialized by a per-machine lock** (`.git/claudetools-sync.lock`) so concurrent sessions or the scheduled-task sync cannot interleave commits/rebases; if another sync is mid-flight it waits up to ~120s, then **exits 75 (deferred)** rather than racing — the next sync catches up. On a 75, do NOT print a success summary; report "**sync deferred — another sync is running; your session log is written locally and will sync on the next run**". Otherwise it: reconciles this machine's `git config user.name/email` to `.claude/identity.json` (so commit authorship can't drift), stages all changes with `git add -A` (after purging garbled Windows path-as-filename cruft), auto-commits, fetch + rebase, push, then the same flow for the vault repo, then surfaces cross-user `## Note for ` blocks. - -> Note: `git add -A` is still the catch-all sweep, so a save run will also pick up any *other* dirty files in the shared tree. The lock prevents two syncs from racing, and per-session-unique log filenames prevent log overwrites — but the bare-`add -A` capture means full per-session commit isolation is a later step (see the isolation plan: drop blind `add -A` in favour of explicit per-session staging). For now, avoid running `/save` from two sessions at the exact same moment. +Same driver as `/sync` — see that command for the full semantics. The two load-bearing +points for reporting: **exit 75 = deferred** (another sync is running; report "sync deferred +— your session log is written locally and will sync on the next run", NOT a success summary); +and `git add -A` is a catch-all sweep, so avoid running `/save` from two sessions at the exact +same moment (per-session-unique log filenames prevent log overwrites, the lock prevents racing). After sync, emit a **Post-commit Summary**: diff --git a/.claude/commands/sync.md b/.claude/commands/sync.md index 57dbd87..16dfbc1 100644 --- a/.claude/commands/sync.md +++ b/.claude/commands/sync.md @@ -39,18 +39,15 @@ The intent: a `/sync` that finds unsaved work should default toward `/save`. Aut ## What this does -Invokes `bash .claude/scripts/sync.sh`, which: +Run it — the script is the single source of truth for all git ops (both `/sync` and `/save` invoke it): -1. Detects local changes (including untracked-only files) via `git status --porcelain`; stages with `git add -A` and auto-commits with `sync: auto-sync from at ` -2. Fetches from origin, rebases local commits onto remote -3. Pushes to origin -4. Copies `.claude/commands/*.md` → `~/.claude/commands/` so the global Claude CLI commands stay current without a manual copy -5. Repeats steps 1-3 for the **vault** repo (path read from `.claude/identity.json` `vault_path` field) -6. Surfaces any `## Note for ` / `## Message for ` blocks from incoming session logs +```bash +bash .claude/scripts/sync.sh +``` -The script is the single source of truth for git operations. Both `/sync` and `/save` invoke it. +It stages (`git add -A`, submodule gitlinks unstaged unless `--with-submodules`), auto-commits, fetch+rebase+push for this repo then the vault repo, deploys `.claude/commands/*.md` + skills to `~/.claude/`, and surfaces incoming `## Note for ` blocks. Full internals: `.claude/CLAUDE_EXTENDED.md` / the script header. -**Concurrency:** the run is serialized by a per-machine lock (`.git/claudetools-sync.lock`) so two syncs (e.g. interactive + the scheduled-task sync, or two Claude sessions) can't interleave staging/commit/rebase/push. If another sync is already running, this run waits up to ~120s then **exits 75 (EX_TEMPFAIL = deferred, not a failure)** — report it as deferred, not synced; the next run catches up. Stale locks (owner process dead, or older than 10 min) are auto-reclaimed. +**Exit 75 = deferred, not a failure.** The run is serialized by a per-machine lock (`.git/claudetools-sync.lock`); if another sync is mid-flight it waits ~120s then exits 75. On a 75, report "sync deferred — another sync is running; it will catch up next run", NOT a success summary. Stale locks (dead owner, or >10 min) auto-reclaim. --- diff --git a/.claude/harness/CHANGELOG.md b/.claude/harness/CHANGELOG.md index 27cc5db..e174b05 100644 --- a/.claude/harness/CHANGELOG.md +++ b/.claude/harness/CHANGELOG.md @@ -30,3 +30,19 @@ or old harness during a heterogeneous rollout. See (full manual, on-demand). Saves ~3.7k tokens per CLAUDE.md injection; nothing lost. - Task 9 (P2): delegation re-tuned in CORE — act directly by default; delegate only for high-volume output, blast radius >3 files/layers, domain shift, or parallel work. + +## 1.4.0 — 2026-06-08 (P1+P2+P3 complete) +- Task 5: one-line registry descriptions on the 8 biggest skills (remediation-tool, gc-audit, + packetdial, memory-dream, human-flow, self-check, impeccable, mailprotector). Skill-description + injection ~3320 -> ~2123 tokens (~36% cut); keyword triggers preserved; frontmatter valid. +- Task 7: thinned `/save` + `/sync` bodies — they point to `sync.sh` as the single source instead + of re-documenting its internals; load-bearing LLM-judgment parts (Phase 0 save-vs-sync, cross-user + note display, exit-75 reporting) kept verbatim. The mechanical sync never depends on an LLM step. +- Task 10 (P3): `session-logs/YYYY-MM/` adopted as a FORWARD convention for new logs (recall = scoped + grep over month folders, no monolithic index); existing flat logs untouched (grep covers both). + Recall order (wiki -> CONTEXT/log -> coord) already lives in CORE. +- Deterministic Bash fix: `now-phoenix.sh` helper added — fixed UTC-7 epoch math, replaces the + unreliable `TZ=America/Phoenix date` (silently returns UTC on Git-Bash). `--iso/--date/--datetime/ + --fmt` formats. `post-bot-alert.sh` already uses `jq -nc --arg` (verified, no change needed). +- Deferred (unchanged): full Python port = separate spec; Task 8 shard command bodies; promote + guard to FATAL after a clean warn window; schedule memory-dream --apply-safe per-machine. diff --git a/.claude/harness/VERSION b/.claude/harness/VERSION index f0bb29e..88c5fb8 100644 --- a/.claude/harness/VERSION +++ b/.claude/harness/VERSION @@ -1 +1 @@ -1.3.0 +1.4.0 diff --git a/.claude/scripts/now-phoenix.sh b/.claude/scripts/now-phoenix.sh new file mode 100644 index 0000000..fab1d06 --- /dev/null +++ b/.claude/scripts/now-phoenix.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +# now-phoenix.sh — emit the current America/Phoenix timestamp, deterministically. +# +# WHY: `TZ=America/Phoenix date` is unreliable on Git-for-Windows bash (the MSYS +# tz database is often absent, so it silently returns UTC). Arizona does NOT +# observe DST — it is fixed UTC-7 (MST) year-round — so we compute Phoenix time +# as (UTC epoch - 7h) and format it. No tz database, no DST edge cases, identical +# result on Windows / macOS / Linux. +# +# Usage: +# bash now-phoenix.sh -> 2026-06-08 14:32 PT (default, human log line) +# bash now-phoenix.sh --iso -> 2026-06-08T14:32:07-07:00 +# bash now-phoenix.sh --date -> 2026-06-08 +# bash now-phoenix.sh --datetime -> 2026-06-08 14:32:07 +# bash now-phoenix.sh --epoch -> 1749422327 (raw UTC epoch, for arithmetic) +# bash now-phoenix.sh --fmt '+%H:%M' -> 14:32 (custom strftime, applied to Phoenix time) +# +# All output is on stdout, no trailing prose. Soft, dependency-free (coreutils date only). + +set -euo pipefail + +OFFSET=$((7 * 3600)) # Phoenix is UTC-7, fixed +EPOCH_UTC="$(date -u +%s)" +EPOCH_PHX=$((EPOCH_UTC - OFFSET)) + +# Portable "format an epoch as if it were UTC" (so the wall-clock we print is Phoenix local). +fmt_epoch() { + local e="$1" f="$2" + if date -u -d "@${e}" "$f" >/dev/null 2>&1; then + date -u -d "@${e}" "$f" # GNU/Git-Bash + else + date -u -r "${e}" "$f" # BSD/macOS + fi +} + +case "${1:-}" in + --iso) printf '%s-07:00\n' "$(fmt_epoch "$EPOCH_PHX" '+%Y-%m-%dT%H:%M:%S')" ;; + --date) fmt_epoch "$EPOCH_PHX" '+%Y-%m-%d' ;; + --datetime) fmt_epoch "$EPOCH_PHX" '+%Y-%m-%d %H:%M:%S' ;; + --epoch) printf '%s\n' "$EPOCH_UTC" ;; + --fmt) fmt_epoch "$EPOCH_PHX" "${2:?--fmt needs a strftime arg, e.g. --fmt '+%H:%M'}" ;; + ''|--pt) printf '%s PT\n' "$(fmt_epoch "$EPOCH_PHX" '+%Y-%m-%d %H:%M')" ;; + -h|--help) + grep -E '^#( |$)' "$0" | sed 's/^# \{0,1\}//' + ;; + *) + echo "[ERROR] now-phoenix: unknown arg '$1' (try --help)" >&2 + exit 64 + ;; +esac diff --git a/.claude/skills/gc-audit/SKILL.md b/.claude/skills/gc-audit/SKILL.md index 7ceedfd..94ed786 100644 --- a/.claude/skills/gc-audit/SKILL.md +++ b/.claude/skills/gc-audit/SKILL.md @@ -1,669 +1,654 @@ ---- -name: gc-audit -description: | - Periodic end-to-end verification of the GuruConnect codebase and CI/CD - infrastructure. Runs 6 parallel audit passes: (1) API/route & surface - inventory, (2) Rust code quality & standards, (3) TypeScript/dashboard - quality, (4) protocol & wire-format integrity (proto <-> prost <-> manual TS - decode), (5) security & remote-session integrity, (6) docs/roadmap - reconciliation. A 7th sequential pass audits CI/CD pipeline health (Gitea - Actions workflows, runner registration, clippy/audit gates, deploy host). - Produces a timestamped audit report and updates the living docs - (FEATURE_ROADMAP.md, TECHNICAL_DEBT.md). Takes 10-20 minutes. - - Invoke explicitly only — no auto-trigger. Use /gc-audit for a full audit. - Optional arg: --pass= to run a single pass - (api, rust, ts, protocol, security, docs, pipeline). - The docs pass reconciles FEATURE_ROADMAP.md, TECHNICAL_DEBT.md, the docs/specs/SPEC-*.md, - and the specs/*/plan.md task markers against the code; quality passes check code against - the granular .claude/standards/ files. Cleans up stale entries. ---- - -# GuruConnect End-to-End Audit - -Periodic full-stack sanity check for **GuruConnect** — the standalone MSP remote-desktop -product (Rust/Axum relay server + Windows Rust agent + React/TS viewer component library, -Protobuf-over-WebSocket transport). Read-only by default — findings are written to a report -file and living docs are updated. No production code is changed. - -> **This is GuruConnect, not GuruRMM.** GC diverges from the RMM audit in ways that matter — -> do NOT copy RMM assumptions. The biggest traps, called out where they apply below: -> - **GC uses runtime `sqlx::query()`/`query_as()` — NOT compile-time `sqlx::query!` macros** -> (verified 2026-05-29; CLAUDE.md's "compile-time checked queries" line is stale, and v2 keeps runtime -> sqlx). This matches RMM. A *new* `query!`/`query_as!` macro is therefore a deviation worth a `[LOW]` -> (it reintroduces the `.sqlx`-cache-regen footgun + a build-time `DATABASE_URL` requirement), not the norm. -> - **Wire format is Protobuf**, not RMM's JSON `AgentMessage`/`ServerMessage` enums. The -> integrity pass chases drift across four artifacts: `proto/guruconnect.proto` → -> prost-generated agent code → prost-generated server code → **hand-written binary decode in -> `dashboard/src/lib/protobuf.ts` (hardcoded message IDs)**. That manual TS decoder is GC's -> signature failure mode. -> - **The dashboard is a React component library + server-served static HTML** -> (`server/static/{login,dashboard,viewer}.html`), NOT a React Router SPA. There is no -> `App.tsx` route tree and no `dashboard/src/api/client.ts`. The surface pass adapts. -> - **CI is Gitea Actions** (`.gitea/workflows/*.yml`) with a Windows runner on Pluto — not -> RMM's `webhook-handler.py` + `build-shared.sh` pipeline. - ---- - -## Execution Overview - -``` -Phase 0: Context load (coordinator reads key files) -Phase 1: Spawn 6 parallel audit agents (codebase passes A-F) -Phase 2: Run CI/CD pipeline audit (Agent G — sequential; SSH to deploy host) -Phase 3: Collect findings, aggregate, score -Phase 4: Write report + update living docs -Phase 5: Present summary to user -``` - -The audit is orchestrated here (Claude coordinator). All codebase passes run in parallel -subagents. The pipeline pass runs sequentially after (it touches live state via SSH / HTTP). -Each agent returns structured findings; the coordinator aggregates and writes the final report. - -### Model (MANDATORY) - -**Always run every audit pass on Opus.** Spawn each agent with `model: "opus"` (the `opus` -alias resolves to the latest Opus). This overrides default complexity-based routing — do NOT -downgrade any pass to Sonnet or Haiku, even the lower-stakes ones (API surface, TypeScript). -The coordinator orchestration also runs on Opus. - -### Repo root - -All paths below are relative to `projects/msp-tools/guru-connect/` unless prefixed otherwise. -GuruConnect is a **separate Gitea repo** (`azcomputerguru/guru-connect`), not a submodule of -the monorepo in the RMM sense — it lives at `projects/msp-tools/guru-connect/` with its own -`.git`. Deploy host: **172.16.3.30:3002**, systemd service `guruconnect.service`. - ---- - -## Phase 0: Context Load (Coordinator Reads These) - -Before spawning agents, read these yourself: - -1. `CLAUDE.md` — GC project instructions, coding standards, ports, auth model -2. `docs/FEATURE_ROADMAP.md` — planned features (`[ ]`/`[~]`/`[x]` + P1-P3) -3. `TECHNICAL_DEBT.md` — living debt backlog + "Completed Items" section (repo root, NOT docs/) -4. `docs/ARCHITECTURE_DECISIONS.md` — ADR-001 (RMM↔GC contract), ADR-002 (release eng) -5. **All** `docs/specs/SPEC-*.md` (Glob them so new specs are auto-picked-up) — the architecture/feature - specs. SPEC-001 = release-engineering parity; **SPEC-002 = the v2 modernization architecture — read it: - it defines the in-flight rebuild and which v1 weaknesses are *already planned* to be replaced, so passes - can distinguish known/planned from net-new.** -6. `specs/*/` shape-spec folders (`plan.md`/`shape.md`/`references.md`/`standards.md`) — pre-implementation - plans (e.g. `specs/v2-secure-session-core/`, `specs/native-remote-control/`). `plan.md` is the - implementation source-of-truth and tracks progress via `[DONE]` task markers — a reconciliation target - for Agent F. -7. `.claude/CODING_GUIDELINES.md` **+ `.claude/standards/` — read `index.yml`, then the relevant standard - files** (repo root). `.claude/standards/` is the **compliance baseline** the quality passes check code - against (not just the looser CODING_GUIDELINES): `security/credential-handling`, `api/response-format`, - `gururmm/sqlx-migrations`, `gururmm/platform-parity`, `conventions/{naming,no-emojis,output-markers}`, - `git/commit-style`. Pass the relevant standards **and their key rules** to Agents B and C. - -Capture from `server/src/api/mod.rs` (+ `server/src/main.rs` route registration) the complete -route list — every `.route(...)` plus the two WebSocket upgrade endpoints in -`server/src/relay/mod.rs` (`agent_ws_handler`, `viewer_ws_handler`). This becomes the -**authority route list** passed to Agents A and E. - -Also extract every checkbox line from `FEATURE_ROADMAP.md` (with section + priority) into a -**roadmap claims list** — passed to Agent F for reconciliation against the code. - -**During the v2 rebuild (SPEC-002):** extract a **planned-work list** from SPEC-002 and the active -`specs/*/plan.md` files — the known v1 weaknesses already scheduled for replacement (the relay-auth -CRITICALs, the broken web protobuf codec, the deploy stub, etc.). Pass this list to every pass. A finding -that matches already-planned work is still reported, but tagged **`[TRACKED — SPEC-002 / ]`** instead -of presented as net-new, so an audit run mid-rebuild does not drown the report in things already decided. -**Net-new findings (in no spec/plan) are the signal that matters most.** - ---- - -## Phase 1: Parallel Audit Agents - -Spawn all six parallel agents (A, B, C, D, E, F) simultaneously in a single message. Each -agent receives the full context it needs inline — do not assume they share context. (Agent G — -Pipeline — runs sequentially afterward; it SSHes the live deploy host and hits HTTP endpoints.) - ---- - -### Agent A — API Surface & Route Inventory - -**Goal:** Find server endpoints with no UI/consumer exposure, handlers that are dead code, and -references in the dashboard/static HTML to routes that don't exist. - -**Instructions for agent:** - -1. Read `server/src/api/mod.rs` and `server/src/main.rs` — extract every `.route(path, method)` - into a list grouped by resource (auth, users, sessions, downloads, releases, changelog, - health, metrics). Include the two WS endpoints from `server/src/relay/mod.rs`. - -2. **The dashboard is NOT a routed SPA.** Instead cross-reference route usage against: - - `server/static/login.html`, `dashboard.html`, `viewer.html` — grep for `fetch(`, `/api/`, - and `ws`/`wss` URLs the static pages call. - - `dashboard/src/hooks/useRemoteSession.ts` — the WS connection URLs it opens. - - `dashboard/src/components/*.tsx` — any REST calls. - -3. Classify: - - Server route never referenced by any static page / component / hook → **ORPHANED ROUTE** - (dead code vs. intentionally external/integration — distinguish by context, e.g. routes the - RMM integration calls per ADR-001 are not dead). - - A `fetch`/WS URL in the frontend that matches no server route → **BROKEN CALL**. - - Method mismatch (frontend POSTs to a GET route) → **METHOD MISMATCH**. - -4. For each API resource group, assess whether the UI (static pages + viewer component) exposes - the major operations. Flag resources with full server CRUD but no UI surface. - -5. Return structured findings: `[SEVERITY] Description — file:line`. - ---- - -### Agent B — Rust Code Quality & Standards - -**Goal:** Find violations of GC's Rust standards and common quality issues across server + agent. - -**Instructions for agent:** - -Read `CLAUDE.md` (GC standards section), `.claude/CODING_GUIDELINES.md`, **and the relevant -`.claude/standards/` files the coordinator passed you** first. GC standards: -`tracing` for logging (not `println!`/`log`), `anyhow` in binaries, `thiserror` in libraries, -`async`/`await` preferred, clippy clean. **Audit against the concrete `.claude/standards/` rules and -cite the standard each finding violates** — `security/credential-handling` (no hardcoded secrets; -hashed/short-lived tokens; log auth attempts), `api/response-format` (consistent error envelope, no raw -`e.to_string()` to clients, kebab-case segments, idempotent migrations), `gururmm/sqlx-migrations` -(`IF NOT EXISTS`, server-applied, no manual pre-apply), `conventions/naming` (Rust/proto/DB casing), -`conventions/no-emojis`, `git/commit-style`. - -**Compliance checks:** -- `.unwrap()` / `.expect()` outside `#[cfg(test)]` — panic in production. Flag each with context. - `.expect("invariant reason")` on a truly-impossible path is acceptable if the reason is clear. -- `todo!()` / `unimplemented!()` in non-test production paths. -- `println!` / `eprintln!` used for logging instead of `tracing::` macros. -- `format!()` used to build SQL strings (injection risk — parameterize instead). -- **sqlx style:** GC's db layer uses **runtime `sqlx::query()`/`query_as()`** throughout, NOT - compile-time `sqlx::query!` macros (verified 2026-05-29 — CLAUDE.md's "compile-time checked queries" - claim is stale; v2 keeps runtime sqlx, matching RMM). So a **new `sqlx::query!`/`query_as!` macro is a - deviation** worth `[LOW]` (reintroduces the `.sqlx`-cache-regen footgun + build-time `DATABASE_URL`), - not the norm. Still flag `format!()`-built SQL (above) as the real injection risk. - -**Auth coverage (server):** -- Read `server/src/api/mod.rs` + `server/src/auth/mod.rs`. Identify which route groups go through - the `AuthenticatedUser` extractor / auth middleware vs. which are public (login, health, - downloads, metrics may be intentionally public). -- Verify the **agent plane and viewer/admin plane are separated**: the agent WS - (`agent_ws_handler`, auth via `support_code` OR `api_key`) and the viewer WS - (`viewer_ws_handler`, auth via JWT) must not accept each other's credentials. Any non-public - admin route reachable without JWT → `[CRITICAL]`. -- Confirm JWT secret + min-length enforcement at startup (`server/src/main.rs`) and the logout - token blacklist (`server/src/auth/token_blacklist.rs`) is actually consulted on each request. - -**Logging hygiene:** -- Grep `tracing::` calls that include JWT secrets, API keys, support codes, passwords, or PII - (emails, usernames). These must be redacted. - -**Error handling:** -- HTTP handlers returning 500 with raw `e.to_string()` — leaks internals to callers. Log - server-side, return a generic message. - -**Search paths:** `server/src/` and `agent/src/`. Exclude generated proto (`OUT_DIR`) and -`server/src/db/mod.rs` re-exports. - -Return structured findings with file:line references. - ---- - -### Agent C — TypeScript / Dashboard Quality - -**Goal:** Find dashboard quality issues, with special focus on the hand-written protobuf decoder -(GC has no schema library on the TS side — correctness is manual). - -**Instructions for agent:** - -The dashboard (`dashboard/`) is a **React component library** (peer-dep React, no app router, -no bundler in-repo). Main artifacts: `components/RemoteViewer.tsx`, `components/SessionControls.tsx`, -`hooks/useRemoteSession.ts`, `lib/protobuf.ts`, `types/protocol.ts`. - -**Standards baseline:** audit against the relevant `.claude/standards/` files the coordinator passed -(esp. `conventions/no-emojis`, `conventions/naming`, `conventions/output-markers` for any scripts) and -cite the standard each violation breaks. - -**TypeScript quality:** -- `any` annotations in `dashboard/src/` — each is a type-safety gap (the binary/canvas code is - exactly where `any` hides bugs). -- `@ts-ignore` / `@ts-expect-error` — note reason and whether still needed. -- `console.log` / `console.error` left in non-dev paths. -- Hardcoded server URLs instead of a passed `serverUrl` prop / config. - -**Manual protobuf decoder correctness (`lib/protobuf.ts`) — HIGH PRIORITY:** -- The decoder parses binary frames with `DataView` and **hardcoded message IDs** (e.g. - `MSG_VIDEO_FRAME`, `MSG_MOUSE_EVENT`, `MSG_KEY_EVENT`). Verify these IDs and field offsets - match `proto/guruconnect.proto` field numbers / wire layout. A mismatch silently corrupts - frames or events. (Agent D cross-checks the same surface from the proto side — coordinate via - the report; this side checks the TS implementation correctness.) -- Bounds checking: does the decoder validate buffer length before reading offsets? A short/hostile - frame should not throw an unhandled error or read OOB. - -**Component patterns:** -- `RemoteViewer.tsx` canvas rendering: BGRA→RGBA conversion correctness; is the canvas sized to - the frame? Are out-of-order frames (the `sequence` field) handled or dropped? -- `useRemoteSession.ts`: WebSocket lifecycle — reconnect handling, cleanup on unmount, error - surfacing (a dropped session should not silently hang). -- Icon-only buttons without `aria-label`/`title`; inputs without `