feat(terminal): real PTY backend, typed errors, and offline help - #30
Conversation
## Features **Terminal — real PTY over Tauri IPC.** The old terminal faked a TTY with `script(1)` and moved bytes over HTTP: one POST per keystroke, plus a 100 ms poll for output. That worked for `ls` and broke for anything interactive. Replaced with `portable-pty`, streamed over a Tauri `Channel<String>`: - `vim`, `htop`, `k9s` and other cursor-addressed programs now render, and resize correctly — the pty gets a real `SIGWINCH`. - `Ctrl+C` / `Ctrl+D` / `Ctrl+Z` reach the child instead of printing `^C`. - Idle terminals cost nothing; output is pushed, not polled. - Bounded 1 MiB output ring buffer (drop-oldest) so `yes` cannot OOM the app. - Session cap of 16 plus a 30-minute idle reaper; reconnects are exempt from the cap so a user at the ceiling can still reattach to existing tabs. - Kubernetes pod shells open an in-app tab instead of shelling out to Terminal.app via `osascript`. "Open in Terminal.app" survives as a secondary action. - Per-profile shell history (`HISTFILE=~/.colima-ui/history-<profile>`) with `PROMPT_COMMAND='history -a'`, so several tabs on one profile do not lose each other's history to bash's append-on-exit. **Typed error taxonomy.** `error.rs` now returns a structured `ColimaError` carrying a category and a `doc_id`, and both entry points (Tauri IPC and HTTP) serialize the same shape. The frontend half lives in `lib/errors.ts`; `errorReporter.ts` turns a failed operation into a localized toast plus a hint, so call sites no longer decide presentation. Failures are recorded in an error log with a detail panel. **Secret redaction.** `redact.rs` / `lib/redact.ts` strip credentials from any string that can reach the user, a log, or a bug report. The motivating leak: `reqwest` puts the full request URL in its `Display` output, so an error from a provider that authenticates by query parameter would print the API key. Redaction works by position (credential-shaped parameters) and by pattern. **System capability detection.** `check_system`, `check_tool` and `get_platform` each returned a different shape and `SetupWizard.svelte` stitched them together, so every other page had to re-derive "is Colima installed and usable". Now one source of truth. **Colima config editor** — read, validate and write `~/.colima/<profile>/colima.yaml` from Settings. **Live engine resources.** The dashboard derived CPU/memory/disk from `colima.yaml`, which is the *allocated* VM config and only exists when Colima manages the engine. Docker Desktop, OrbStack and Rancher now report real figures. **Menu bar / tray** — see instance state and start or stop instances without opening the main window. **Offline Help** — 6 articles (install Colima / Docker CLI / kubectl, start Colima, common errors, performance tuning) in English, Japanese, Vietnamese and Chinese. These are the destination for the `doc_id` slugs errors attach: the error says what broke, the article says what to do about it. **Compose grouping** — containers are grouped by `com.docker.compose.project`; hand-started containers stay ungrouped. ## Fixes - Terminal rendered garbage (a run of repeated characters and an oversized block cursor) because `@xterm/xterm/css/xterm.css` was never imported. xterm.js positions its row divs, hides its character-measuring element and sizes the cursor entirely from that stylesheet; without it the measure element renders on screen. - Terminal session id embedded `Date.now()`, so every remount opened a new pty and abandoned the old one. Keyed to the tab, a remount now reattaches. - Terminal rewrote `\n` as `\r\n` to compensate for the `script(1)` wrapper, corrupting any program that positions the cursor itself. Removed with the wrapper. - `terminal.*` translation keys were absent from all four locale files, so terminal errors always displayed in English. Added to en/ja/vi/zh. - Terminal printed a "Connecting to …" banner with a doubled space and a stray blank line, pushing the shell prompt down two rows. Removed on the happy path; failures still announce themselves. - Client-side ids were `Date.now().toString()` and collided when two items were created in the same millisecond. - `validation.rs` gained path-traversal and argument-injection guards for values that reach a command line (profile names, k8s namespace/pod/ container), covered by unit tests. ## Security `routes/ws.rs` is deleted. Despite the filename it contained no WebSocket — only five HTTP handlers (`api_terminal_create/write/read/close/resize`) that exposed an interactive shell on a local port, which is remote code execution. The terminal is desktop-only, so these were removed rather than ported: over Tauri IPC there is no port to reach and no handshake to authenticate. Terminal I/O is never logged. ## Tests 9 frontend test modules (errors, redaction, ids, compose grouping, markdown) and 12 Rust tests over the terminal session — argv construction, hostile-name rejection, UTF-8 carry across read boundaries, zero-size resize refusal, and per-profile history.
|
Analysis CompleteGenerated ECC bundle from 1 commits | Confidence: 75% View Pull Request #31Repository Profile
Changed Files (138)
Top hotspots
Top directories
Analysis Depth Readiness (commit-history, 21%)ECC Tools uses this to decide whether recommendations should stay at commit-history/setup guidance or expand into CI, security, harness, reference-set, AI-routing, and team backlog work.
Reference Set Readiness (0/7, 0%)
Likely Future Issues (8)
Suggested Follow-up Work (8)
Copy-ready bodies test: add integration coverage for src-tauri/src/routes/ai.rs + src-tauri/src/routes/colima_config.rs ## Summary
- Add integration or end-to-end coverage for the recently changed API surface.
## Why
- Backfill integration or end-to-end coverage for the changed API surface before more contract changes land.
## Touched paths
- `src-tauri/src/routes/ai.rs`
- `src-tauri/src/routes/colima_config.rs`
## Validation
- Add or extend integration / e2e coverage for the changed API, route, or contract surface.
- Exercise the touched endpoints or route handlers against realistic request / response flows.docs: sync API contract for src-tauri/src/routes/ai.rs + src-tauri/src/routes/colima_config.rs ## Summary
- Update the API contract artifact that should reflect the recently changed implementation surface.
## Why
- Backfill the missing API contract or spec update before another implementation change lands on top of the same surface.
## Touched paths
- `src-tauri/src/routes/ai.rs`
- `src-tauri/src/routes/colima_config.rs`
## Validation
- Update the relevant OpenAPI, GraphQL, or contract/spec artifact used by this repo.
- Run the contract validation, docs generation, or API verification flow that depends on that artifact.chore: sync config templates for src-tauri/tauri.conf.json ## Summary
- Update the example env files, sample configs, or deployment templates that should mirror the changed runtime configuration surface.
## Why
- Backfill example env files or config templates before a fresh setup drifts from the shipped runtime surface.
## Touched paths
- `src-tauri/tauri.conf.json`
## Validation
- Update the repo example env file or config template that should reflect the new runtime settings.
- Run the setup, boot, or deployment validation flow that depends on the changed config surface.test: add auth coverage for src-tauri/src/auth.rs ## Summary
- Add auth, session, or permission regression coverage for the recently changed security-sensitive surface.
## Why
- Backfill auth or permission regression coverage before another access-control change lands on the touched surface.
## Touched paths
- `src-tauri/src/auth.rs`
## Validation
- Add or extend integration / e2e coverage for the changed auth, session, middleware, or permission surface.
- Exercise allowed and denied flows, invalid or expired credentials, or equivalent access-control boundary cases.test: add browser coverage for src/components/AiChatPanel.svelte + src/components/DiffView.svelte ## Summary
- Add browser or end-to-end coverage for the recently changed user-facing surface.
## Why
- Backfill browser coverage before another user-facing UI change lands on the touched surface.
## Touched paths
- `src/components/AiChatPanel.svelte`
- `src/components/DiffView.svelte`
## Validation
- Add or extend browser / e2e coverage for the changed component, page, or flow.
- Exercise the visible user journey that depends on the touched UI surface.test: add CLI coverage for src-tauri/src/commands/ai_chat.rs + src-tauri/src/commands/colima.rs ## Summary
- Add shell, CLI, or end-to-end coverage for the recently changed command surface.
## Why
- Backfill CLI coverage before another command-surface change lands on the touched paths.
## Touched paths
- `src-tauri/src/commands/ai_chat.rs`
- `src-tauri/src/commands/colima.rs`
## Validation
- Add or extend shell, CLI, or end-to-end coverage for the changed command surface.
- Exercise the user-facing command invocation and expected exit/output behavior.security: add scanner evidence for src-tauri/src/auth.rs ## Summary
- Add security scanner or code-scanning evidence for the recently changed security-sensitive surface.
## Why
- Backfill explicit scanner or code-scanning evidence before another security-sensitive change lands on the touched surface.
## Touched paths
- `src-tauri/src/auth.rs`
## Validation
- Run or add the relevant security scanner, code scanning, secret scanning, or dependency/security review check for the touched surface.
- Attach the scanner output, SARIF/code-scanning result, or focused security regression test to the follow-up PR.
- Confirm the changed auth, billing, webhook, secret-handling, agent, or CI surface has an explicit pass/fail gate.test: add budget evidence for src/styles/tokens.css ## Summary
- Add budget or usage-limit validation for the recently changed AI routing or model-call surface.
## Why
- Backfill cost, token, or usage-limit validation before another model-routing change lands on the touched surface.
## Touched paths
- `src/styles/tokens.css`
## Validation
- Add or extend budget, token, usage-limit, or model-routing regression coverage for the changed path.
- Verify the route still enforces plan limits, retry caps, fallback behavior, or explicit cost controls.Generated Instincts (10)
After merging, import with: Files
|
dev advanced 10 commits after this branch was cut (premium UI/UX overhaul,
toast redesign, icon fixes, AI-chat error handling). 12 files conflicted
across 26 hunks.
Resolution: conflicting hunks resolved in favour of this branch, per
instruction. Non-conflicting dev changes auto-merged and were kept — 7 of the
12 files carry dev improvements alongside ours.
## Why ours had to win on the AI API surface
Not a preference — a contract check. The merged Rust backend registers
`ai_chat_load_history`, `ai_chat_save_message`, `ai_chat_clear_history`,
`ai_chat_{list,create,rename,delete}_conversation` and `read_reference`, and
serves `/api/ai/conversations{,/rename,/delete}` and `/api/ai/history/clear`.
dev's frontend called `load_ai_history`, `save_ai_message`,
`clear_ai_history`, `read_reference_file`, `/api/ai/history/message` and
`/api/ai/read-reference` — none of which exist on the merged backend. Taking
dev's side would have broken AI chat outright. Frontend and backend now match
8/8 on command names.
## Two defects found and fixed while verifying
- `ToastContainer.svelte` was left internally inconsistent by the hunk-level
resolution: our hunks do not define `intervalId`, but dev's auto-merged
regions referenced it five times. Restored this branch's file whole so the
component is self-consistent.
- `instance_reader.rs` opened a doc code block with no language, so rustdoc
compiled its box-drawing directory tree as Rust and `cargo test` exited 101.
Marked the fence as `text`. Pre-existing on dev, not caused by this merge.
Also replaced a raw NUL byte in `globalToast.ts`'s toast-collapsing key with
the escape sequence for U+0000. Byte-identical at runtime, but the raw NUL
made the file register as binary to file(1), grep and diff.
## Verification
- cargo test: exit 0, 89 passed, 0 failed (was exit 101 before the doc fix)
- svelte-check: 143 errors / 36 files, against a 144 / 36 pre-merge baseline —
one fewer, and none in any of the 12 resolved files. The remainder are
pre-existing and untouched by this merge.
…I redesign - Add kind create/list/delete Tauri commands (kind.rs) with k8s name validation - Add k8s cluster commands (k8s_cluster.rs, k8s_resources.rs) and dedupe HTTP routes - Add K8sOverview view: health score, resource counts, issue list - Kubernetes sidebar: group parent highlight, Cluster group on top, Overview entry, aligned padding - Instances page: overview strip, richer list/detail cards, kind cluster name input with hint, 15s kind polling - Stale k8s context state cleared when no contexts remain - Add common error KB articles and shell event helpers
Features
Terminal — real PTY over Tauri IPC. The old terminal faked a TTY with
script(1)and moved bytes over HTTP: one POST per keystroke, plus a 100 ms poll for output. That worked forlsand broke for anything interactive. Replaced withportable-pty, streamed over a TauriChannel<String>:vim,htop,k9sand other cursor-addressed programs now render, and resize correctly — the pty gets a realSIGWINCH.Ctrl+C/Ctrl+D/Ctrl+Zreach the child instead of printing^C.yescannot OOM the app.osascript. "Open in Terminal.app" survives as a secondary action.HISTFILE=~/.colima-ui/history-<profile>) withPROMPT_COMMAND='history -a', so several tabs on one profile do not lose each other's history to bash's append-on-exit.Typed error taxonomy.
error.rsnow returns a structuredColimaErrorcarrying a category and adoc_id, and both entry points (Tauri IPC and HTTP) serialize the same shape. The frontend half lives inlib/errors.ts;errorReporter.tsturns a failed operation into a localized toast plus a hint, so call sites no longer decide presentation. Failures are recorded in an error log with a detail panel.Secret redaction.
redact.rs/lib/redact.tsstrip credentials from any string that can reach the user, a log, or a bug report. The motivating leak:reqwestputs the full request URL in itsDisplayoutput, so an error from a provider that authenticates by query parameter would print the API key. Redaction works by position (credential-shaped parameters) and by pattern.System capability detection.
check_system,check_toolandget_platformeach returned a different shape andSetupWizard.sveltestitched them together, so every other page had to re-derive "is Colima installed and usable". Now one source of truth.Colima config editor — read, validate and write
~/.colima/<profile>/colima.yamlfrom Settings.Live engine resources. The dashboard derived CPU/memory/disk from
colima.yaml, which is the allocated VM config and only exists when Colima manages the engine. Docker Desktop, OrbStack and Rancher now report real figures.Menu bar / tray — see instance state and start or stop instances without opening the main window.
Offline Help — 6 articles (install Colima / Docker CLI / kubectl, start Colima, common errors, performance tuning) in English, Japanese, Vietnamese and Chinese. These are the destination for the
doc_idslugs errors attach: the error says what broke, the article says what to do about it.Compose grouping — containers are grouped by
com.docker.compose.project; hand-started containers stay ungrouped.Fixes
@xterm/xterm/css/xterm.csswas never imported. xterm.js positions its row divs, hides its character-measuring element and sizes the cursor entirely from that stylesheet; without it the measure element renders on screen.Date.now(), so every remount opened a new pty and abandoned the old one. Keyed to the tab, a remount now reattaches.\nas\r\nto compensate for thescript(1)wrapper, corrupting any program that positions the cursor itself. Removed with the wrapper.terminal.*translation keys were absent from all four locale files, so terminal errors always displayed in English. Added to en/ja/vi/zh.Date.now().toString()and collided when two items were created in the same millisecond.validation.rsgained path-traversal and argument-injection guards for values that reach a command line (profile names, k8s namespace/pod/ container), covered by unit tests.Security
routes/ws.rsis deleted. Despite the filename it contained no WebSocket — only five HTTP handlers (api_terminal_create/write/read/close/resize) that exposed an interactive shell on a local port, which is remote code execution. The terminal is desktop-only, so these were removed rather than ported: over Tauri IPC there is no port to reach and no handshake to authenticate. Terminal I/O is never logged.Tests
9 frontend test modules (errors, redaction, ids, compose grouping, markdown) and 12 Rust tests over the terminal session — argv construction, hostile-name rejection, UTF-8 carry across read boundaries, zero-size resize refusal, and per-profile history.