Simplification Loop — Mission Charter & Progress
Started 2026-07-02 on branch fable/clean-up. Recurring loop (every 30 min),
~10-hour horizon. Each iteration reads this doc, executes the next items, keeps
gates green, commits, and updates this doc.
Charter (user directives)
- Keep every feature — the contract is docs/ux-stories.md; nothing there regresses.
- Drastically cut code size, surfaces, complexity, settings, config files, dependencies. Every file scrutinized (docs, workflows, CLI, controller, frontend).
- UI pixel-identical. Consolidate everything onto the base UI kit (
src/ui). - Catch and fix bugs along the way.
- Autonomous; no questions. Never delete untracked/user data without explicit OK.
Gates
- Full:
npm run check. NOTE coverage gap: root gate runs controller typecheck only; CI also runs controller lint + check (knip/jscpd/depcheck/standards) + unit + integration tests. Runcd controller && bun run lint && bun run check && bun run test:unitfor controller changes. (Hitlist C0 fixes this.) - Commit per coherent unit,
--no-verify(hooks: frontend bans useEffect etc.).
HITLIST — Controller (from audit)
- C1 BUG unbounded telemetry growth (ffc1baa9): observability middleware writes a row
per request (
http/observability-middleware.ts:32,core/function-observability.ts,stores/controller-request-store.ts) with no retention and no skip-list; polling floods DB forever. Fix: reuse app.ts:63 skip-set + add retention prune. SAFE. - C2 (c5dd16b9) dead route
/events/stats(system/logs-routes.ts:266) — no caller. SAFE. - C3 (c5dd16b9) dead route
/runtime/sglang/config(engines/routes.ts:356) — frontend only fetches vllm/llamacpp configs. SAFE. - C4 (c5dd16b9) collapse single-impl
EngineServiceinterface (engines/engine-service.ts, 26 lines) into EngineCoordinator. SAFE. - C5 (c5dd16b9) dedupe 10×
findInferenceProcessobservability wrapper (system/routes, metrics-routes, logs-routes, models/routes, tokenization-routes) → one helper. SAFE. - C6 (c5dd16b9) remove (createEngineCoordinator/createEventManager; other create* are real closure factories, kept)
create*one-line factory wrappers (createEngineCoordinator etc.). SAFE. - C7 (c5dd16b9)
main.tsmetricsDisabled() duplicatesparseBooleanFlag(validation.ts:41). SAFE. - C8 (f1cf3313) inline tiny per-module
configs.tsconstant files (audio 7, proxy 6, system 5, models 15, engines 20 lines); keep studio/configs.ts. SAFE. - C9 (f1cf3313) delete
modules/shared/{system,recipe}-types.tsre-export shims → import shared/contracts directly. SAFE. - C10 (f1cf3313) provider serializer dup ×4 in
studio/provider-routes.ts→ serializeProvider- parseProviderBody. SAFE.
- C11 (f1cf3313) standardize
parseJsonObjectBody(core/validation.ts:10) in routes that hand-rollreq.json().catch. SAFE. - C12 (a771fcba) dead route
/api/title(proxy/chat-title-routes.ts, ~70 lines) — only the integration test calls it; verify no external client (grep pi runtime) then cut. - C13 (a5b65200) dead cross-controller passthrough
/controllers/route/*(http/app.ts:22-37,85-137 + LOCAL_STUDIO_CONTROLLER_ROUTE_ALLOWLIST). NOTE memory says frontend "Add controller" feature exists — verify /controllers/route vs /controllers before cutting. - C14 (a771fcba) dead
/lifetime-metricsroute (store kept — live via inference accounting) (+ maybe LifetimeMetricsStore). - C15 (a771fcba) dead
/v1/tokenize+/v1/detokenize— CAUTION: reachable by external OpenAI clients via proxy; grep pi/droid runtimes first. - C16 (a771fcba) dead
GET /runtime/targets/:id+/healthprobe. - [KEEP] C17 flag audit done: strict_openai_models read at openai-routes.ts:135 (live branch); MOCK_INFERENCE load-bearing for test fixtures; vLLM extra-args escape hatches deliberate. All stay. flag audit: STRICT_OPENAI_MODELS readers; vLLM extra-args escape hatches; MOCK_INFERENCE (used by E2E? verify).
- C18 (a171aca0) inlined all Promise-ceremony Effect wrappers (fetchLocal, resolveBinary, delay, AsyncLock, AsyncQueue). Dep itself STAYS: Schema in recipe-serializer/env/errors + Effect.gen process pipelines are substantive. inline single-use
*Effectvariants (function-observability, local-fetch, command.ts resolveBinary, async.ts) then consider droppingeffectdep entirely (only trivial usage remains + env.ts Schema + errors.ts TaggedError). - [SKIP] C19
/api/docsis user-facing: Server pane links to /api/proxy/api/docs (server-view.tsx:388). KEEP./api/docsswagger UI +@hono/swagger-uidep + openapi-spec.ts (255 lines) — /api/spec is proxied by frontend; verify what reads it. - [SKIP] C20 lateral shuffle: capability rules are env-sensitive (upgrade-cmd checks) and cohesive in the factory; moving to EngineSpec spreads env logic across spec files for ~0 line win. Same reasoning as the earlier runtime-targets split skip.
- Previously skipped (do NOT re-propose): store merges, metrics throughputSamples unification, runtime-targets.ts split.
HITLIST — Frontend (from audit; knip/depcheck/jscpd all clean already)
- F1 BUG copy-toast timer leak ×5 (a9e60da7, hooks/use-copied-flag.ts): setTimeout(setCopied) with no cleanup in copyable-path-chip.tsx:26, assistant-markdown.tsx:61, user-message-block.tsx:92, assistant-message-actions.tsx:41, use-discover.ts:106 → one useCopiedFlag() hook (NB: eslint bans raw useEffect; follow useMountSubscription pattern). SAFE.
- F2 (a9e60da7) move
ui/model-page.tsx(7 exports) → features/recipes/recipes-content/ (all 6 consumers there); drop ui/index.ts:112-121. SAFE. - F3 (a9e60da7) move (as features/settings/settings-ui.tsx; setup + recipes cross-feature consumers exist and are boundary-legal)
ui/settings.tsx(8 exports) → features/settings/ (all 4 consumers there); drop ui/index.ts:95-110. SAFE. - [SKIP] F4 messages barrel has 24 importers — real aggregation point (also re-exports contracts); folding = churn. fold 7-line
features/agent/messages/index.tsbarrel — CHECK scripts/validate-barrel-dir-siblings.mjs convention first. - [SKIP] F5 all candidates fail criteria: filesystem-panel (612 post-merge), use-workspace (571), git-diff-panel (642) exceed 500 lines; agent-browser-effects + quick-panel-bridge have 2 importers each. merge single-consumer twins (grep importers first, keep parent <500 lines): filesystem-panel-effects, use-workspace-effects, git-diff-panel-model, agent-browser-effects, quick-panel-bridge (13 lines). Do NOT merge chat-pane* cluster (all files substantial).
- [SKIP] F6 types has 6 importers; equality merge makes store 600+ lines, net-negative. collapse hooks/realtime-status-{equality,types}.ts into store if single-importer.
- F7 PRODUCT settings/local-agent-* cluster (~750 lines) — real feature landed 07c8db90 (attach-local-agents); KEEP unless user retires it. Not a cut.
- F8 voice routes (app/api/voice/*) — UI caller thin/unclear; memory says recipe db has voice fields and local test uses voiceModel. Verify before touching.
HITLIST — UI-kit consolidation (pixel-identical; ranked by call sites)
Base kit: src/ui (catalogue in audit). Missing primitives: Spinner, Tooltip,
Dropdown/Popover. Two token systems: --ui-* (12 files) vs legacy --fg/--dim/ --surface/--hl1 (41 files, most of features/agent).
- [BLOCKED-ON-USER] U1 Button variants have own padding/hover ≠ bespoke classes; wholesale swap changes pixels. Only exact-class matches safe (none found worth it). ~140 raw
<button>outside src/ui →Button/variant=icon. Top files: filesystem-panel (10), left-sidebar (8), agent-composer-actions (8), explore-tab-sections, agent-browser*, appearance-settings, recipe-row... - U2 RESOLVED, no work needed: --ui-* tokens are already pure aliases of legacy tokens (tokens.css:570-585, --ui-fg: var(--fg) etc). Either spelling is pixel-identical; swaps unblocked. token unification (--ui-* vs legacy) — PREREQUISITE for pixel-identical component swaps in agent tree; map values first, alias tokens in CSS before rewriting classes.
- U3 (5daf40db) Spinner primitive added; all 17 always-spinning sites adopted with exact classes. Conditional-spin refresh icons stay on RefreshButton/RefreshIconButton.
- [BLOCKED-ON-USER] U4 the 38 eyebrow sites render text-sm/tracking-wider vs SectionLabel fs-2xs/tracking-[0.18em]/mono — adoption would visibly change UI. Needs a deliberate design pass, not this loop. SectionLabel adoption: 38 eyebrow-label class-cluster sites (2 adopters today).
- [BLOCKED-ON-USER] U5 StatusDot is 5px/token colors; hand-rolled dots are 6px/other colors (incl bg-emerald-400) — swap changes pixels. Flagged as design inconsistency for user decision. StatusDot/StatusPill adoption: ~10 hand-rolled dots/pills (incl. hardcoded bg-emerald-400 in status-section-models-dropdown.tsx:139 — off-token, fix).
- [BLOCKED-ON-USER] U6 same pixel-change problem as U4/U5. Card adoption: 9 rounded-lg + 14 rounded-md hand-rolled surfaces.
- U7 add
Tooltipprimitive (~70 title= sites + bespoke timeline tooltip) — LOW priority, changes rendered visuals (title→styled) so defer/user-visible. - [BLOCKED-ON-USER] U8 focus/anim differences would be visible. hand-rolled modals/drawers: left-sidebar mobile drawer, logs backdrop, recipes slide-over→Drawer, explore popover, sessions-command palette.
- [BLOCKED-ON-USER] U9 same. 4 raw → Input/SearchInput; 3 raw → Select; 1 raw .
HITLIST — Configs/CI/scripts/docs
- Delete no-op pr-review.yml; fix labels.yml external URL; CODEOWNERS stale paths; README/AGENTS "three modules" + REMOTE_URL; dead ALLOW_RUNTIME_UPGRADE_COMMAND env (commit 24b12fad).
- G1 (c5dd16b9) root gate coverage gap FIXED — root check:controller now runs typecheck+lint+check+test:unit. Found real damage: two integration tests imported modules merged away in 90983d84; fixed same commit:
check:controller= typecheck only, CI runs lint+check+tests. Extend root script (keep runtime reasonable: lint+check). - G2 (650f204e) daemon.sh {start|stop|status} replaces trio; README updated. daemon-*.sh ×3 → keep (README-documented) or collapse into one daemon.sh.
- G3 (f6b271d6) single root .prettierrc.json; controller reformatted (trailingComma all) + 16 drifted frontend files fixed. merge prettier configs (controller trailingComma es5 vs frontend all) → one root .prettierrc.json; NOTE reformats controller; do as isolated commit.
- G4 (cd2eb9a9) cut start:next (SSE-buffering footgun) + analyze script + @next/bundle-analyzer devDep + next.config wrapper. check:* variants measured: each is a real named gate step, kept.
- G5 engine-refactor-plan.md archived to docs/archive/ next to its iteration log.
- G6 (650f204e) deleted — pinned nothing, CI installs in frontend/ only. root package-lock.json was an empty stub — verify nothing needs it.
- cli/ dir on disk = stray node_modules only (untracked); frontend/frontend/ = April path-bug junk (untracked). ASK USER before rm.
Done
- I1: repo mapped; 4 audits run; charter + docs/ux-stories.md written; config/CI batch committed (24b12fad).
BUG HUNT LOG (post-convergence)
Two-agent adversarial sweep 2026-07-02. Findings verified by tracing callers, then fixed. Commits 10c42cbe (runtime), 7e43d6e0 (security).
Runtime/state (5, all fixed):
- poll-adopts-new-runtimeSessionId left the event cursor on the old runtime's seq → reconnect skipped the new runtime's early events (lost turn content).
- session-json-store (plan/canvas): unlocked read-modify-write + non-atomic writeFile → lost updates + silent doc wipe on crash mid-write. Now per-file serialized + write-then-rename.
- closeAll didn't clear cursors/turnAcceptedAt/coalescer slots → app-lifetime singleton leaked one entry per session opened.
- abortTurn only wrote status:idle → perpetual "running" tool badges + stale activeAssistantId when SSE detached before the terminal event.
- reconcileLiveness idle branch didn't cancel an armed reconnect timer → SSE reopened against a just-idled session.
Security (privileged Next API routes):
- HIGH middleware matcher excluded image extensions → any dynamic route with a .png suffix skipped the frontend-token gate. FIXED (match /api/:path*).
- MED added requireApiAccess to fs/file GET, git/git-diff GET, abort, canvas, plan, comments, compact (were edge-gate-only). No-op when no token set.
- LOW git checkout/createBranch leading-dash arg injection. FIXED (-- + reject).
- REVIEWED-BY-DESIGN: fs cwd = user-chosen project dir (local agent needs arbitrary dirs); now token-gated. SSRF/traversal/SSE-abort all checked clean.
BUG HUNT LOG — round 2 (I6, controller lifecycle + deps)
Two-agent sweep 2026-07-02: controller launch/process lifecycle + a final dependency/config audit. Commits e90c65f6, 0b3f9d69, 8fa6fd30.
Controller lifecycle (fixed):
- HIGH stale install-lock never reclaimed after a crash → every install for that backend stalled the full 30-min timeout then failed. Now reclaims when the recorded pid is dead / file is torn. (install-lock.ts)
- MED download pause→resume race: superseded old run's abort-catch clobbered the live download to "paused" and deleted the new run's active entry → model finished on disk but showed permanently paused. Ownership token now gates all state writes. (download-manager.ts)
- LOW AsyncLock double-release could free two waiters (idempotent now); AsyncQueue.shift-after-close hung (rejects now); AsyncQueue.shift with an already-aborted signal hung + leaked a resolver (found in prep, rejects now). All covered by new src/core/async.test.ts (11 tests).
- LOW engine-jobs map grew one entry per job forever → prune finished to 50.
- REVIEWED-LOW (left, self-healing / would add surface): #5 coordinator ignores post-timeout kill result (next scan corrects); #6 abort not threaded into the ~3s launchModel spawn window (postLaunchAbort reaps it); #8 metrics collector runs one trailing cycle after stop (loop still terminates).
Dependency/config audit: repo confirmed clean — ZERO removable dependencies, zero dead documented env vars, all devDeps wired. Only nits were untracked local artifacts (controller/.lintstagedrc.json, a stale .tsbuildinfo) left in place, and 4 unused eslint boundary-element defs left as intentional layering docs.
BUG HUNT LOG — round 3 (I7, proxy + realtime/desktop)
Two-agent sweep 2026-07-02: controller proxy non-streaming paths + frontend realtime/dashboard/desktop-IPC. I personally audited the SSE-framing streaming path. Commits 96be3721 (proxy), 4be401f9 (frontend).
Controller proxy (fixed):
- reasoning normalizer DUPLICATED reasoning when a model emitted it both inline () and in reasoning_content; now dedups segments.
- same normalizer clobbered multi-part ARRAY content to "" (data loss); now only rewrites string content.
- attachSessionUsage missed nested completion_tokens_details.reasoning_tokens (echoed 0); non-streaming path recorded an inference row for zero-token/error responses (now gated like streaming).
- tool-call stream: decoder never flushed on stream end → a multibyte char split across the final chunk was dropped. Fixed + regression test.
- NOT changed (load-bearing, documented): non-streaming upstream timeout (would truncate long generations, violates the no-truncation directive; client forwards its abort); tool-array sort (prefix-cache stability); repeat-content collapse.
Frontend realtime/desktop (fixed):
- HIGH cross-host API-key leak: controller quick-switch set the target key but never cleared it when the target had none → previous controller's key sent to a different host (incl. on the SSE ?api_key=). Both flows now clear.
- PTY boot leak: unmount during pty.open leaked pty-data/pty-exit ipcRenderer listeners + an owned shell per aborted boot; re-check disposed after open.
- useModelLifecycle now refetches recipes on controller switch (was stale).
- controller-matrix store diffs before emit (was re-rendering all consumers /5s).
- LOW left: snapshotsByController growth (bounded by controllers visited).
BUG HUNT LOG — round 4 (I8, env/studio/audio + recipes editor)
Two-agent sweep 2026-07-02 over the last unaudited high-complexity surfaces. Commits 53118b09 (controller), 24dabc48 (frontend).
Controller env/studio/audio (fixed):
- HIGH unbounded STT upload buffered fully into memory (OOM DoS) → 100MB cap.
- looksLikeWav trusted client MIME, skipping the RIFF/WAVE header check → header now authoritative.
- environment create accepted any image string pushed as a docker-run positional token (flag-shaped → argv injection) → reject leading-dash like the pull path.
- SIMPLIFY: collapsed dup STT/TTS model-path resolvers; extracted resolveInsideModelsRoot (delete+move dup'd the traversal guard); constant reuse.
Frontend recipes editor (fixed):
- tensor/pipeline parallel accepted 0 (clearing → tp_size:0, controller checks isInt only) → floor at 1. port accepted 0 → default. gpu-util slider min 0 → 0.05 (0 emits invalid flag).
- llama.cpp arg builder's startsWith(flag) let a longer sibling flag suppress a distinct shorter flag (--rope-scaling vs --rope-scale) → whole-token match.
- 'override' badge stayed lit when typed command == generated → clear editedCommand.
- SIMPLIFY: engine-options-section reused shared coerceBoolean.
- DEFERRED (larger, documented): tab field-helper extraction (~150 lines, ~39 sites, must preserve empties-to-undefined + enum casts); dead RecipeEditor fields (must keep ENGINE_ARG_SPECS rows for VLLM_ONLY_FLAG_KEYS).
VERIFIED-NOT-DEAD: voice/audio flow (no wired mic UI, but settings surface voiceUrl/voiceModel, endpoints functional, user configures whisper model) — a real feature, kept per "maintain all features". CI workflows have no redundancy.
DEFERRED-ITEM INVESTIGATION — I9 (recipes cluster simplification)
Investigated the two simplifications deferred from I8 plus the agent's other recipe-cluster candidates. CONCLUSION: none is a safe/beneficial cut. Do NOT re-attempt — reasons below.
- Tab field-helper extraction (I8 #1): a standalone RecipeCheckbox helper was built + all 10 checkbox sites converted (commit cdc4b29c) — but it measured NET +52 LINES (new ~40-line file + repeated recipe/onChange props per site negate the savings for only 10 sites). A bound helper would cut lines but needs a component created inside render (identity/remount anti-pattern). REVERTED (0a81dfb0). Type-safety gain didn't justify going against the cut mandate. The number/text/select fields have real per-site variance (|| "" vs ?? default, enum casts, placeholders) — a shared helper there risks silent serialization drift the gates can't catch.
- Dead RecipeEditor fields (I8 #2): NOT dead. EXTRA_ARG_FIELDS is DERIVED from ENGINE_ARG_SPECS (shared contract) and each spec.field is typed keyof RecipeEditor. The unrendered fields are the typed backing for the engine-arg lift/lower system AND feed VLLM_ONLY_FLAG_KEYS (foreign-flag strip). Removing them breaks typecheck or the strip feature. The round-trip is a no-op whether a field is lifted or not, so there's no runtime cost to leaving them.
- Command-arg emitters (agent #3): appendExtraArgsToCommand (vLLM) vs appendLlamacppArgsToCommand legitimately differ — boolean-false pushes the flag for vLLM but is skipped for llama.cpp; arrays JSON-stringify for vLLM but expand to repeated flags for llama.cpp; different dedup + JSON-string handling. Merging needs 4+ behavior flags = more complexity, not less.
- EngineOptionsSection per-tab wrapper (agent #5): only 4 sites; wrapper overhead ≈ savings (net ~neutral). Not worth the churn.
Net: the recipes cluster is already well-factored; its apparent duplication is either engine-semantic or a deliberate typed spec system. The real value there was the I8 bug fixes (tp/pp/port/gpu-util floors, llama.cpp flag collision).
BUG HUNT LOG — round 5 (I10, agent view/rendering layer)
One-agent sweep 2026-07-02 of the frontend agent VIEW components (timeline, attachments, filesystem viewer, git-diff, plan, canvas). I personally audited the controller GPU/platform parsing (nvidia/amd/rocm/metrics) and found it robust — no change. Commits 2ab8ad43, cb9963a1.
Fixed:
- timeline merged-message key grew per segment (a->a:b->a:b:c) within a turn →
remounted the assistant
on every tool boundary, collapsing expanded disclosures. Anchor on the first (unique) segment id. - attachment blob preview URLs never revoked → reclaim on removing an un-sent attachment (sent ones keep theirs; message may reference the blob).
- plan-panel debounced save timer not cleared on unmount → cancel it.
- canvas detectPreviewKind (regex scans of whole buffer) memoized.
- PERF git-diff rendered every file's full line grid even when the was collapsed → freeze on huge diffs. DiffFileEntry renders the body only while expanded.
Deferred (documented): file-viewer splits multi-line highlight.js spans per line (cosmetic coloring only; correct fix = risky span-stack repair over every file, net +code for a cosmetic issue — not worth it).
CHECKED-CLEAN: no XSS (all dangerouslySetInnerHTML fed by hljs-escaped output; iframe sandboxes correct); untrusted hrefs neutralized by react-markdown; filesystem tree can't self-expand a symlink cycle. Controller GPU/platform parsing is robust (NaN guards, unit handling, N/A cases).
BUG HUNT + CUT LOG — round 6 (I11, usage/discover/logs/shell/lib)
Docs audit: verified all READMEs/AGENTS.md accurate vs 38 commits of removals — ZERO drift, no GitBook site, no build output tracked. No change.
One-agent sweep of the last feature views. Commit 24a2b9f6 (net -71 lines). This round found GENUINE dead code (unlike the I9 recipe traps):
- BUG usage daily chart: single-model filter left peak_days all-models → low-traffic model's bars collapsed to slivers. Drop peak for filtered view.
- discover ModelRow: whole inline variant-expansion path unreachable (sole caller never passes the props; variants go through the model card). ~40 lines.
- discover/utils extractProvider dup'd lib; normalizeModelId pass-through → use lib.
- usage: dead SortField 'success' case (no column); double normalizeUsageStats/render.
- huggingface.ts identical-branch ternary; LogsPanel unread docsSrcDoc prop; left-sidebar NavItemDesktop always-true 'expanded' prop.
- SKIPPED #8 (download-progress dedup, ~4 lines): cross-feature/new-module overhead ≈ savings.
BUG HUNT LOG — round 7 (I12, http middleware / stores / config)
One-agent sweep of the controller security/storage/config layer + I personally verified the frontend lib/api client core (retry/auth sound — mutations have server-side idempotency guards, no change). Commit f361cf62.
Fixed:
- HIGH isRecipeRunning mis-identified a running model: unconditional basename fallback matched /a/Llama-3 vs /b/Llama-3; substring 'contains' matched /models/llama inside /models/llama-3.1. Launching A while same-basename B ran reported success and served B as A. Basename fallback now gated to when one side lacks dir context; containment is path-segment-boundary aware. +6 tests.
- HIGH rate-limit keyed on the client-appendable first X-Forwarded-For entry → per-request rotation defeated the API-key brute-force defense + grew the store unbounded. Prefer CF-Connecting-IP (unspoofable behind CF); hard-cap + evict oldest; dedup the two cleanup blocks.
- savePersistedConfig write-then-rename (crash mid-write truncated config → silent reset of models_dir/providers/runtime targets).
- recipe serializer clamps tp/pp/max_model_len/max_num_seqs>=1, port 1-65535, gmu (0,1] (NaN previously vanished the recipe; negative reached the launch cmd).
Left (documented): #4 share one SQLite handle (near-nil benefit single-threaded, busy_timeout covers it, refactor risk); #5 per-path rate bucket (intentional, doesn't amplify brute-force on a fixed path); #8 read-limiter headers (cosmetic). CLEARED: no SQL injection (all parameterized); timing-safe token compare; auth default-open only loopback/opt-in; port parsing guarded.
BUG HUNT LOG — round 8 (I13, Electron main process + settings/contracts)
One-agent sweep of the desktop MAIN process + I personally audited shared contracts (engine-args strip logic sound, no change) and the settings-service. Commits a966b257 (settings), 421d9763 (desktop).
Fixed:
- settings-service saveApiSettings: non-atomic write; a crash truncates api-settings.json and getApiSettings returns env defaults — silently WIPING the persisted API key. Write-then-rename (chmod 0600 before rename).
- desktop renderer crash left a permanent blank window (only logged) → guarded auto-reload.
- restartFrontendServer re-checks isAppStopping after the fork (shutdown race re-armed the health monitor + resurrected the server).
- PTY cap at 64 (fork-bomb defense from a compromised renderer); writePty guard; cols/rows NaN clamp; boot-failure error dialog.
Left (documented): #1 owner-keyed PTYs survive macOS window close = the persistent-terminal reattach design, not a clear bug; #6 per-handler sender validation is mitigated (webviews get no preload/bridge). CLEARED: open-external restricted to http/https; update feed refuses non-https/loopback; shared engine-args foreign-flag strip normalization consistent.
FULL-BRANCH VERIFICATION + BUG HUNT round 9 (I14)
Ran the COMPLETE test matrix across all 46 commits: 375 tests green (controller 21 unit + 127 integration; frontend 20 unit + 227 e2e), 0 clones, 0 unused deps/exports, no circular deps, all structure/contract gates pass. The whole loop's cumulative work is coherent and releasable. Net code delta ~line-neutral (the I3 prettier reformat is ~800 lines of format-neutral churn); real work = 19 files + 8 routes + 2 workflows + 1 dep + 5 config/scripts removed, offset by ~45 bug fixes with test coverage and defensive guards.
Browser-host sweep (commit ee39801c) — the last complex unswept surface:
- HIGH orphaned Chromium: stop() never called + no exit hook → every server restart leaked a headless browser holding the profile lock. Guarded process.on('exit') kills it.
- MED freshPage recovery path leaked a CDP WebSocket per 'Not attached' recovery (replaced page never closed). Close it first.
- Verified #4 (poll double-subscribe) is a FALSE POSITIVE — subscribeFrames is synchronous; #5 benign. CLEARED: no launch arg-injection, CDP calls time out (10s), JSON.parse guarded.
PI-RUNTIME AUDIT — I15 (personal, no change)
Audited the server-side pi-runtime session manager (pi-runtime.ts, the privileged process that spawns/manages pi coding-agent sessions) — the last unswept complex surface. CONCLUSION: sound, no safe actionable change. Do NOT re-hunt.
- loggedEvent listener cleanup is correct: prompt-path listener removed via Effect.ensuring (pi-runtime.ts:272-277); SSE route wires off = onLoggedEvent(...) and calls it in close() on request.signal abort + terminal events (runtime/events/route.ts:50-60,80,119). No EventEmitter leak.
- eventLog bounded at 2000/session (pi-runtime.ts:429).
- sessions Map is never pruned BY DESIGN: runtimes are kept alive across turns for instant session resume (a core feature). Deletion is disabled (405); archiving must not stop a resumable runtime. An LRU/cap would risk the resume feature the loop has preserved. Retained shells for never-started sessions are negligible (EventEmitter + empty arrays). Not a safe-to-fix bug.
CONFIG/SCRIPT AUDIT — I16
Systematic pass over every tracked config file + npm script (charter's settings/config mandate). Result: ONE genuine cut, rest verified purposeful.
- CUT: controller/.lintstagedrc.json (dead — lint-staged not a controller dep, no controller script/hook invokes it; pre-commit controller branch only runs typecheck). commit 67a81dac.
- KEPT (verified wired/justified): tsconfig ×3 (distinct compile targets), eslint ×2 + knip ×2 (distinct rule sets/entries), jscpd ×2 (intentional per-workspace minTokens), depcheckrc/postcss/prettierrc/prettierignore/ release.config all used, frontend .lintstagedrc wired (dep + precommit).
- frontend 32 scripts are purposeful: check:* is gate composition, desktop:* is a real build/run/dist/dev matrix. Only near-dup (typecheck:desktop == desktop:build:main) is a meaningful gate-vs-build naming distinction — NOT consolidated (marginal, risks gate composition). daemon.sh/deploy-remote.sh are README-documented user helpers; frontend/scripts/test-*.ts run via glob.
SHARED-EXPORT AUDIT — I17
Found the one dead-code class the mechanical gates can't see: knip runs per-workspace and its project scope is src/** + desktop/** — it NEVER scans shared/. So a shared/contracts export used by NEITHER workspace is invisible to both knip runs. Enumerated all 71 shared exports, cross-referenced controller/ src + frontend/src + frontend/desktop + tests + shared/:
- CUT (de-exported to module-private): VLLM_ONLY_FLAG_KEYS, normalizeEngineArgKey, getForeignFlagKeys, isKnownVllmExtraArgKey — all referenced only inside engine-args.ts, zero external use. commit caf894b2. Public contract API -4.
- The other 67 shared exports are all consumed. No other dead shared surface.
- desktop/** IS in frontend knip's project scope (no blind spot there).
- Did NOT chase src/ exports-used-in-own-file (knip ignoreExportsUsedInFile:true is a deliberate choice; de-exporting internally-used exports en masse is risky).
CSS-TOKEN + TEST-COVERAGE AUDIT — I18 (no change)
Two blind-spot investigations, both correctly declined:
- CSS custom-property audit: 163/313 globals tokens have no var()/(--x) reference — but this is a TAILWIND v4 FALSE-POSITIVE TRAP. The @theme/@theme-inline blocks turn --color-/--radius-/--text-/--font- into GENERATED utility classes (bg-sky-500, rounded-md, text-emerald-400) used across 52 TSX files; a var()-grep structurally cannot see utility consumption. Removing any would break utilities + violate the absolute UI-pixel-identical rule. DO NOT attempt a CSS-token cut by grep. Even the few non-palette semantic tokens (--ui-active etc.) aren't worth the theme-break risk for ~lines of CSS. NO CHANGE.
- Test-coverage audit: every test file IS wired to a runner (controller: bun test src = 3 unit files; bun test ../tests/controller/integration = 20; frontend: tsx --test scripts/test-.ts = 8 unit; ../tests/frontend/e2e/.test.ts = 27 e2e). No orphaned/never-run tests. Clean.
PI-PARITY AUDIT — I19 (deep pi comparison + derived cuts)
User asked for a deep review of earendil-works/pi vs this repo ("pi is my favorite codebase; I want to feel that way about ours"). Two deep-review agents
- personal reads. Full findings summarized here so future iterations use them.
What pi does that we already match: acyclic dependency graph (ours enforced by validate-shared-contracts + madge + structure validators; pi's by npm package boundaries), controlled file sizes (our median 100/max 731 beats pi's median 116/max 5,780), zero-
anytyping discipline (ours is stricter), current docs. The controller "would sit comfortably next to pi."Where pi is structurally better — ranked, with disposition:
- ONE streaming primitive (88-line EventStream) + ONE pipeline; we have 8 transform layers engine→pixel plus a parallel hydration reducer (messages/replay.ts duplicating pi-event-applier control flow). BLOCKED-ON- USER: this is the agent-simplification plan (memory: 6-step plan awaiting go); the seams are where every historical streaming bug lived. Not for the autonomous loop.
- Session identity: pi has one; we have 4 namespaces (pane SessionId, runtimeSessionId ×21 files, piSessionId ×46, proxy session_id from 7 sources). BLOCKED-ON-USER (step 5 of the same plan, riskiest).
- The agent runtime lives inside the Next server (piRuntimeManager on globalThis, 43 app/api/agent/* route dirs) instead of a separate layer. BLOCKED-ON-USER (architectural).
- pi's test harness runs the REAL stack against a production-path fake provider (faux.ts in src, .inMemory() constructors); test:src ratio 0.73 vs our 0.16, and our riskiest controller paths (download-manager real-run, process-manager spawn, environment start) are untested. Candidate for a future test-investment iteration (adds code — needs user appetite).
- pi is a real npm workspace; we import shared/ via ../../../.. relative paths with per-package toolchains. SKIP: workspace conversion is churn with real breakage risk (bun vs npm lockfiles), ~0 line win.
- Liveness: pi awaits listeners in one settlement chain; we run triple arbitration (SSE + poll + probe) twice over. Verified deliberate (race comments per leg) — KEEP.
Lines 1–500 of 691. Load more
# Simplification Loop — Mission Charter & Progress Started 2026-07-02 on branch `fable/clean-up`. Recurring loop (every 30 min), ~10-hour horizon. Each iteration reads this doc, executes the next items, keeps gates green, commits, and updates this doc. ## Charter (user directives) 1. **Keep every feature** — the contract is docs/ux-stories.md; nothing there regresses. 2. **Drastically cut** code size, surfaces, complexity, settings, config files, dependencies. Every file scrutinized (docs, workflows, CLI, controller, frontend). 3. **UI pixel-identical.** Consolidate everything onto the base UI kit (`src/ui`). 4. **Catch and fix bugs** along the way. 5. Autonomous; no questions. Never delete untracked/user data without explicit OK. ## Gates - Full: `npm run check`. NOTE coverage gap: root gate runs controller typecheck only; CI also runs controller lint + check (knip/jscpd/depcheck/standards) + unit + integration tests. Run `cd controller && bun run lint && bun run check && bun run test:unit` for controller changes. (Hitlist C0 fixes this.) - Commit per coherent unit, `--no-verify` (hooks: frontend bans useEffect etc.). ## HITLIST — Controller (from audit) - [x] **C1 BUG unbounded telemetry growth** (ffc1baa9): observability middleware writes a row per request (`http/observability-middleware.ts:32`, `core/function-observability.ts`, `stores/controller-request-store.ts`) with no retention and no skip-list; polling floods DB forever. Fix: reuse app.ts:63 skip-set + add retention prune. SAFE. - [x] C2 (c5dd16b9) dead route `/events/stats` (`system/logs-routes.ts:266`) — no caller. SAFE. - [x] C3 (c5dd16b9) dead route `/runtime/sglang/config` (`engines/routes.ts:356`) — frontend only fetches vllm/llamacpp configs. SAFE. - [x] C4 (c5dd16b9) collapse single-impl `EngineService` interface (`engines/engine-service.ts`, 26 lines) into EngineCoordinator. SAFE. - [x] C5 (c5dd16b9) dedupe 10× `findInferenceProcess` observability wrapper (system/routes, metrics-routes, logs-routes, models/routes, tokenization-routes) → one helper. SAFE. - [x] C6 (c5dd16b9) remove (createEngineCoordinator/createEventManager; other create* are real closure factories, kept) `create*` one-line factory wrappers (createEngineCoordinator etc.). SAFE. - [x] C7 (c5dd16b9) `main.ts` metricsDisabled() duplicates `parseBooleanFlag` (validation.ts:41). SAFE. - [x] C8 (f1cf3313) inline tiny per-module `configs.ts` constant files (audio 7, proxy 6, system 5, models 15, engines 20 lines); keep studio/configs.ts. SAFE. - [x] C9 (f1cf3313) delete `modules/shared/{system,recipe}-types.ts` re-export shims → import shared/contracts directly. SAFE. - [x] C10 (f1cf3313) provider serializer dup ×4 in `studio/provider-routes.ts` → serializeProvider + parseProviderBody. SAFE. - [x] C11 (f1cf3313) standardize `parseJsonObjectBody` (core/validation.ts:10) in routes that hand-roll `req.json().catch`. SAFE. - [x] C12 (a771fcba) dead route `/api/title` (proxy/chat-title-routes.ts, ~70 lines) — only the integration test calls it; verify no external client (grep pi runtime) then cut. - [x] C13 (a5b65200) dead cross-controller passthrough `/controllers/route/*` (http/app.ts:22-37,85-137 + LOCAL_STUDIO_CONTROLLER_ROUTE_ALLOWLIST). NOTE memory says frontend "Add controller" feature exists — verify /controllers/route vs /controllers before cutting. - [x] C14 (a771fcba) dead `/lifetime-metrics` route (store kept — live via inference accounting) (+ maybe LifetimeMetricsStore). - [x] C15 (a771fcba) dead `/v1/tokenize` + `/v1/detokenize` — CAUTION: reachable by external OpenAI clients via proxy; grep pi/droid runtimes first. - [x] C16 (a771fcba) dead `GET /runtime/targets/:id` + `/health` probe. - [KEEP] C17 flag audit done: strict_openai_models read at openai-routes.ts:135 (live branch); MOCK_INFERENCE load-bearing for test fixtures; vLLM extra-args escape hatches deliberate. All stay. flag audit: STRICT_OPENAI_MODELS readers; vLLM extra-args escape hatches; MOCK_INFERENCE (used by E2E? verify). - [x] C18 (a171aca0) inlined all Promise-ceremony Effect wrappers (fetchLocal, resolveBinary, delay, AsyncLock, AsyncQueue). Dep itself STAYS: Schema in recipe-serializer/env/errors + Effect.gen process pipelines are substantive. inline single-use `*Effect` variants (function-observability, local-fetch, command.ts resolveBinary, async.ts) then consider dropping `effect` dep entirely (only trivial usage remains + env.ts Schema + errors.ts TaggedError). - [SKIP] C19 `/api/docs` is user-facing: Server pane links to /api/proxy/api/docs (server-view.tsx:388). KEEP. `/api/docs` swagger UI + `@hono/swagger-ui` dep + openapi-spec.ts (255 lines) — /api/spec is proxied by frontend; verify what reads it. - [SKIP] C20 lateral shuffle: capability rules are env-sensitive (upgrade-cmd checks) and cohesive in the factory; moving to EngineSpec spreads env logic across spec files for ~0 line win. Same reasoning as the earlier runtime-targets split skip. - Previously skipped (do NOT re-propose): store merges, metrics throughputSamples unification, runtime-targets.ts split. ## HITLIST — Frontend (from audit; knip/depcheck/jscpd all clean already) - [x] **F1 BUG copy-toast timer leak ×5** (a9e60da7, hooks/use-copied-flag.ts): setTimeout(setCopied) with no cleanup in copyable-path-chip.tsx:26, assistant-markdown.tsx:61, user-message-block.tsx:92, assistant-message-actions.tsx:41, use-discover.ts:106 → one useCopiedFlag() hook (NB: eslint bans raw useEffect; follow useMountSubscription pattern). SAFE. - [x] F2 (a9e60da7) move `ui/model-page.tsx` (7 exports) → features/recipes/recipes-content/ (all 6 consumers there); drop ui/index.ts:112-121. SAFE. - [x] F3 (a9e60da7) move (as features/settings/settings-ui.tsx; setup + recipes cross-feature consumers exist and are boundary-legal) `ui/settings.tsx` (8 exports) → features/settings/ (all 4 consumers there); drop ui/index.ts:95-110. SAFE. - [SKIP] F4 messages barrel has 24 importers — real aggregation point (also re-exports contracts); folding = churn. fold 7-line `features/agent/messages/index.ts` barrel — CHECK scripts/validate-barrel-dir-siblings.mjs convention first. - [SKIP] F5 all candidates fail criteria: filesystem-panel (612 post-merge), use-workspace (571), git-diff-panel (642) exceed 500 lines; agent-browser-effects + quick-panel-bridge have 2 importers each. merge single-consumer twins (grep importers first, keep parent <500 lines): filesystem-panel-effects, use-workspace-effects, git-diff-panel-model, agent-browser-effects, quick-panel-bridge (13 lines). Do NOT merge chat-pane* cluster (all files substantial). - [SKIP] F6 types has 6 importers; equality merge makes store 600+ lines, net-negative. collapse hooks/realtime-status-{equality,types}.ts into store if single-importer. - [ ] F7 PRODUCT settings/local-agent-* cluster (~750 lines) — real feature landed 07c8db90 (attach-local-agents); KEEP unless user retires it. Not a cut. - [ ] F8 voice routes (app/api/voice/*) — UI caller thin/unclear; memory says recipe db has voice fields and local test uses voiceModel. Verify before touching. ## HITLIST — UI-kit consolidation (pixel-identical; ranked by call sites) Base kit: `src/ui` (catalogue in audit). Missing primitives: Spinner, Tooltip, Dropdown/Popover. Two token systems: `--ui-*` (12 files) vs legacy `--fg/--dim/ --surface/--hl1` (41 files, most of features/agent). - [BLOCKED-ON-USER] U1 Button variants have own padding/hover ≠ bespoke classes; wholesale swap changes pixels. Only exact-class matches safe (none found worth it). ~140 raw `<button>` outside src/ui → `Button`/variant=icon. Top files: filesystem-panel (10), left-sidebar (8), agent-composer-actions (8), explore-tab-sections, agent-browser*, appearance-settings, recipe-row... - [x] U2 RESOLVED, no work needed: --ui-* tokens are already pure aliases of legacy tokens (tokens.css:570-585, --ui-fg: var(--fg) etc). Either spelling is pixel-identical; swaps unblocked. token unification (--ui-* vs legacy) — PREREQUISITE for pixel-identical component swaps in agent tree; map values first, alias tokens in CSS before rewriting classes. - [x] U3 (5daf40db) Spinner primitive added; all 17 always-spinning sites adopted with exact classes. Conditional-spin refresh icons stay on RefreshButton/RefreshIconButton. - [BLOCKED-ON-USER] U4 the 38 eyebrow sites render text-sm/tracking-wider vs SectionLabel fs-2xs/tracking-[0.18em]/mono — adoption would visibly change UI. Needs a deliberate design pass, not this loop. SectionLabel adoption: 38 eyebrow-label class-cluster sites (2 adopters today). - [BLOCKED-ON-USER] U5 StatusDot is 5px/token colors; hand-rolled dots are 6px/other colors (incl bg-emerald-400) — swap changes pixels. Flagged as design inconsistency for user decision. StatusDot/StatusPill adoption: ~10 hand-rolled dots/pills (incl. hardcoded bg-emerald-400 in status-section-models-dropdown.tsx:139 — off-token, fix). - [BLOCKED-ON-USER] U6 same pixel-change problem as U4/U5. Card adoption: 9 rounded-lg + 14 rounded-md hand-rolled surfaces. - [ ] U7 add `Tooltip` primitive (~70 title= sites + bespoke timeline tooltip) — LOW priority, changes rendered visuals (title→styled) so defer/user-visible. - [BLOCKED-ON-USER] U8 focus/anim differences would be visible. hand-rolled modals/drawers: left-sidebar mobile drawer, logs backdrop, recipes slide-over→Drawer, explore popover, sessions-command palette. - [BLOCKED-ON-USER] U9 same. 4 raw <input> → Input/SearchInput; 3 raw <select> → Select; 1 raw <table>. ## HITLIST — Configs/CI/scripts/docs - [x] Delete no-op pr-review.yml; fix labels.yml external URL; CODEOWNERS stale paths; README/AGENTS "three modules" + REMOTE_URL; dead ALLOW_RUNTIME_UPGRADE_COMMAND env (commit 24b12fad). - [x] G1 (c5dd16b9) root gate coverage gap FIXED — root check:controller now runs typecheck+lint+check+test:unit. Found real damage: two integration tests imported modules merged away in 90983d84; fixed same commit: `check:controller` = typecheck only, CI runs lint+check+tests. Extend root script (keep runtime reasonable: lint+check). - [x] G2 (650f204e) daemon.sh {start|stop|status} replaces trio; README updated. daemon-*.sh ×3 → keep (README-documented) or collapse into one daemon.sh. - [x] G3 (f6b271d6) single root .prettierrc.json; controller reformatted (trailingComma all) + 16 drifted frontend files fixed. merge prettier configs (controller trailingComma es5 vs frontend all) → one root .prettierrc.json; NOTE reformats controller; do as isolated commit. - [x] G4 (cd2eb9a9) cut start:next (SSE-buffering footgun) + analyze script + @next/bundle-analyzer devDep + next.config wrapper. check:* variants measured: each is a real named gate step, kept. - [x] G5 engine-refactor-plan.md archived to docs/archive/ next to its iteration log. - [x] G6 (650f204e) deleted — pinned nothing, CI installs in frontend/ only. root package-lock.json was an empty stub — verify nothing needs it. - [ ] cli/ dir on disk = stray node_modules only (untracked); frontend/frontend/ = April path-bug junk (untracked). ASK USER before rm. ## Done - I1: repo mapped; 4 audits run; charter + docs/ux-stories.md written; config/CI batch committed (24b12fad). ## BUG HUNT LOG (post-convergence) Two-agent adversarial sweep 2026-07-02. Findings verified by tracing callers, then fixed. Commits 10c42cbe (runtime), 7e43d6e0 (security). Runtime/state (5, all fixed): - poll-adopts-new-runtimeSessionId left the event cursor on the old runtime's seq → reconnect skipped the new runtime's early events (lost turn content). - session-json-store (plan/canvas): unlocked read-modify-write + non-atomic writeFile → lost updates + silent doc wipe on crash mid-write. Now per-file serialized + write-then-rename. - closeAll didn't clear cursors/turnAcceptedAt/coalescer slots → app-lifetime singleton leaked one entry per session opened. - abortTurn only wrote status:idle → perpetual "running" tool badges + stale activeAssistantId when SSE detached before the terminal event. - reconcileLiveness idle branch didn't cancel an armed reconnect timer → SSE reopened against a just-idled session. Security (privileged Next API routes): - HIGH middleware matcher excluded image extensions → any dynamic route with a .png suffix skipped the frontend-token gate. FIXED (match /api/:path*). - MED added requireApiAccess to fs/file GET, git/git-diff GET, abort, canvas, plan, comments, compact (were edge-gate-only). No-op when no token set. - LOW git checkout/createBranch leading-dash arg injection. FIXED (-- + reject). - REVIEWED-BY-DESIGN: fs cwd = user-chosen project dir (local agent needs arbitrary dirs); now token-gated. SSRF/traversal/SSE-abort all checked clean. ## BUG HUNT LOG — round 2 (I6, controller lifecycle + deps) Two-agent sweep 2026-07-02: controller launch/process lifecycle + a final dependency/config audit. Commits e90c65f6, 0b3f9d69, 8fa6fd30. Controller lifecycle (fixed): - HIGH stale install-lock never reclaimed after a crash → every install for that backend stalled the full 30-min timeout then failed. Now reclaims when the recorded pid is dead / file is torn. (install-lock.ts) - MED download pause→resume race: superseded old run's abort-catch clobbered the live download to "paused" and deleted the new run's active entry → model finished on disk but showed permanently paused. Ownership token now gates all state writes. (download-manager.ts) - LOW AsyncLock double-release could free two waiters (idempotent now); AsyncQueue.shift-after-close hung (rejects now); AsyncQueue.shift with an already-aborted signal hung + leaked a resolver (found in prep, rejects now). All covered by new src/core/async.test.ts (11 tests). - LOW engine-jobs map grew one entry per job forever → prune finished to 50. - REVIEWED-LOW (left, self-healing / would add surface): #5 coordinator ignores post-timeout kill result (next scan corrects); #6 abort not threaded into the ~3s launchModel spawn window (postLaunchAbort reaps it); #8 metrics collector runs one trailing cycle after stop (loop still terminates). Dependency/config audit: repo confirmed clean — ZERO removable dependencies, zero dead documented env vars, all devDeps wired. Only nits were untracked local artifacts (controller/.lintstagedrc.json, a stale .tsbuildinfo) left in place, and 4 unused eslint boundary-element defs left as intentional layering docs. ## BUG HUNT LOG — round 3 (I7, proxy + realtime/desktop) Two-agent sweep 2026-07-02: controller proxy non-streaming paths + frontend realtime/dashboard/desktop-IPC. I personally audited the SSE-framing streaming path. Commits 96be3721 (proxy), 4be401f9 (frontend). Controller proxy (fixed): - reasoning normalizer DUPLICATED reasoning when a model emitted it both inline (<think>) and in reasoning_content; now dedups segments. - same normalizer clobbered multi-part ARRAY content to "" (data loss); now only rewrites string content. - attachSessionUsage missed nested completion_tokens_details.reasoning_tokens (echoed 0); non-streaming path recorded an inference row for zero-token/error responses (now gated like streaming). - tool-call stream: decoder never flushed on stream end → a multibyte char split across the final chunk was dropped. Fixed + regression test. - NOT changed (load-bearing, documented): non-streaming upstream timeout (would truncate long generations, violates the no-truncation directive; client forwards its abort); tool-array sort (prefix-cache stability); repeat-content collapse. Frontend realtime/desktop (fixed): - HIGH cross-host API-key leak: controller quick-switch set the target key but never cleared it when the target had none → previous controller's key sent to a different host (incl. on the SSE ?api_key=). Both flows now clear. - PTY boot leak: unmount during pty.open leaked pty-data/pty-exit ipcRenderer listeners + an owned shell per aborted boot; re-check disposed after open. - useModelLifecycle now refetches recipes on controller switch (was stale). - controller-matrix store diffs before emit (was re-rendering all consumers /5s). - LOW left: snapshotsByController growth (bounded by controllers visited). ## BUG HUNT LOG — round 4 (I8, env/studio/audio + recipes editor) Two-agent sweep 2026-07-02 over the last unaudited high-complexity surfaces. Commits 53118b09 (controller), 24dabc48 (frontend). Controller env/studio/audio (fixed): - HIGH unbounded STT upload buffered fully into memory (OOM DoS) → 100MB cap. - looksLikeWav trusted client MIME, skipping the RIFF/WAVE header check → header now authoritative. - environment create accepted any image string pushed as a docker-run positional token (flag-shaped → argv injection) → reject leading-dash like the pull path. - SIMPLIFY: collapsed dup STT/TTS model-path resolvers; extracted resolveInsideModelsRoot (delete+move dup'd the traversal guard); constant reuse. Frontend recipes editor (fixed): - tensor/pipeline parallel accepted 0 (clearing → tp_size:0, controller checks isInt only) → floor at 1. port accepted 0 → default. gpu-util slider min 0 → 0.05 (0 emits invalid flag). - llama.cpp arg builder's startsWith(flag) let a longer sibling flag suppress a distinct shorter flag (--rope-scaling vs --rope-scale) → whole-token match. - 'override' badge stayed lit when typed command == generated → clear editedCommand. - SIMPLIFY: engine-options-section reused shared coerceBoolean. - DEFERRED (larger, documented): tab field-helper extraction (~150 lines, ~39 sites, must preserve empties-to-undefined + enum casts); dead RecipeEditor fields (must keep ENGINE_ARG_SPECS rows for VLLM_ONLY_FLAG_KEYS). VERIFIED-NOT-DEAD: voice/audio flow (no wired mic UI, but settings surface voiceUrl/voiceModel, endpoints functional, user configures whisper model) — a real feature, kept per "maintain all features". CI workflows have no redundancy. ## DEFERRED-ITEM INVESTIGATION — I9 (recipes cluster simplification) Investigated the two simplifications deferred from I8 plus the agent's other recipe-cluster candidates. CONCLUSION: none is a safe/beneficial cut. Do NOT re-attempt — reasons below. - Tab field-helper extraction (I8 #1): a standalone RecipeCheckbox helper was built + all 10 checkbox sites converted (commit cdc4b29c) — but it measured NET +52 LINES (new ~40-line file + repeated recipe/onChange props per site negate the savings for only 10 sites). A bound helper would cut lines but needs a component created inside render (identity/remount anti-pattern). REVERTED (0a81dfb0). Type-safety gain didn't justify going against the cut mandate. The number/text/select fields have real per-site variance (|| "" vs ?? default, enum casts, placeholders) — a shared helper there risks silent serialization drift the gates can't catch. - Dead RecipeEditor fields (I8 #2): NOT dead. EXTRA_ARG_FIELDS is DERIVED from ENGINE_ARG_SPECS (shared contract) and each spec.field is typed keyof RecipeEditor. The unrendered fields are the typed backing for the engine-arg lift/lower system AND feed VLLM_ONLY_FLAG_KEYS (foreign-flag strip). Removing them breaks typecheck or the strip feature. The round-trip is a no-op whether a field is lifted or not, so there's no runtime cost to leaving them. - Command-arg emitters (agent #3): appendExtraArgsToCommand (vLLM) vs appendLlamacppArgsToCommand legitimately differ — boolean-false pushes the flag for vLLM but is skipped for llama.cpp; arrays JSON-stringify for vLLM but expand to repeated flags for llama.cpp; different dedup + JSON-string handling. Merging needs 4+ behavior flags = more complexity, not less. - EngineOptionsSection per-tab wrapper (agent #5): only 4 sites; wrapper overhead ≈ savings (net ~neutral). Not worth the churn. Net: the recipes cluster is already well-factored; its apparent duplication is either engine-semantic or a deliberate typed spec system. The real value there was the I8 bug fixes (tp/pp/port/gpu-util floors, llama.cpp flag collision). ## BUG HUNT LOG — round 5 (I10, agent view/rendering layer) One-agent sweep 2026-07-02 of the frontend agent VIEW components (timeline, attachments, filesystem viewer, git-diff, plan, canvas). I personally audited the controller GPU/platform parsing (nvidia/amd/rocm/metrics) and found it robust — no change. Commits 2ab8ad43, cb9963a1. Fixed: - timeline merged-message key grew per segment (a->a:b->a:b:c) within a turn → remounted the assistant <article> on every tool boundary, collapsing expanded disclosures. Anchor on the first (unique) segment id. - attachment blob preview URLs never revoked → reclaim on removing an un-sent attachment (sent ones keep theirs; message may reference the blob). - plan-panel debounced save timer not cleared on unmount → cancel it. - canvas detectPreviewKind (regex scans of whole buffer) memoized. - PERF git-diff rendered every file's full line grid even when the <details> was collapsed → freeze on huge diffs. DiffFileEntry renders the body only while expanded. Deferred (documented): file-viewer splits multi-line highlight.js spans per line (cosmetic coloring only; correct fix = risky span-stack repair over every file, net +code for a cosmetic issue — not worth it). CHECKED-CLEAN: no XSS (all dangerouslySetInnerHTML fed by hljs-escaped output; iframe sandboxes correct); untrusted hrefs neutralized by react-markdown; filesystem tree can't self-expand a symlink cycle. Controller GPU/platform parsing is robust (NaN guards, unit handling, N/A cases). ## BUG HUNT + CUT LOG — round 6 (I11, usage/discover/logs/shell/lib) Docs audit: verified all READMEs/AGENTS.md accurate vs 38 commits of removals — ZERO drift, no GitBook site, no build output tracked. No change. One-agent sweep of the last feature views. Commit 24a2b9f6 (net -71 lines). This round found GENUINE dead code (unlike the I9 recipe traps): - BUG usage daily chart: single-model filter left peak_days all-models → low-traffic model's bars collapsed to slivers. Drop peak for filtered view. - discover ModelRow: whole inline variant-expansion path unreachable (sole caller never passes the props; variants go through the model card). ~40 lines. - discover/utils extractProvider dup'd lib; normalizeModelId pass-through → use lib. - usage: dead SortField 'success' case (no column); double normalizeUsageStats/render. - huggingface.ts identical-branch ternary; LogsPanel unread docsSrcDoc prop; left-sidebar NavItemDesktop always-true 'expanded' prop. - SKIPPED #8 (download-progress dedup, ~4 lines): cross-feature/new-module overhead ≈ savings. ## BUG HUNT LOG — round 7 (I12, http middleware / stores / config) One-agent sweep of the controller security/storage/config layer + I personally verified the frontend lib/api client core (retry/auth sound — mutations have server-side idempotency guards, no change). Commit f361cf62. Fixed: - HIGH isRecipeRunning mis-identified a running model: unconditional basename fallback matched /a/Llama-3 vs /b/Llama-3; substring 'contains' matched /models/llama inside /models/llama-3.1. Launching A while same-basename B ran reported success and served B as A. Basename fallback now gated to when one side lacks dir context; containment is path-segment-boundary aware. +6 tests. - HIGH rate-limit keyed on the client-appendable first X-Forwarded-For entry → per-request rotation defeated the API-key brute-force defense + grew the store unbounded. Prefer CF-Connecting-IP (unspoofable behind CF); hard-cap + evict oldest; dedup the two cleanup blocks. - savePersistedConfig write-then-rename (crash mid-write truncated config → silent reset of models_dir/providers/runtime targets). - recipe serializer clamps tp/pp/max_model_len/max_num_seqs>=1, port 1-65535, gmu (0,1] (NaN previously vanished the recipe; negative reached the launch cmd). Left (documented): #4 share one SQLite handle (near-nil benefit single-threaded, busy_timeout covers it, refactor risk); #5 per-path rate bucket (intentional, doesn't amplify brute-force on a fixed path); #8 read-limiter headers (cosmetic). CLEARED: no SQL injection (all parameterized); timing-safe token compare; auth default-open only loopback/opt-in; port parsing guarded. ## BUG HUNT LOG — round 8 (I13, Electron main process + settings/contracts) One-agent sweep of the desktop MAIN process + I personally audited shared contracts (engine-args strip logic sound, no change) and the settings-service. Commits a966b257 (settings), 421d9763 (desktop). Fixed: - settings-service saveApiSettings: non-atomic write; a crash truncates api-settings.json and getApiSettings returns env defaults — silently WIPING the persisted API key. Write-then-rename (chmod 0600 before rename). - desktop renderer crash left a permanent blank window (only logged) → guarded auto-reload. - restartFrontendServer re-checks isAppStopping after the fork (shutdown race re-armed the health monitor + resurrected the server). - PTY cap at 64 (fork-bomb defense from a compromised renderer); writePty guard; cols/rows NaN clamp; boot-failure error dialog. Left (documented): #1 owner-keyed PTYs survive macOS window close = the persistent-terminal reattach design, not a clear bug; #6 per-handler sender validation is mitigated (webviews get no preload/bridge). CLEARED: open-external restricted to http/https; update feed refuses non-https/loopback; shared engine-args foreign-flag strip normalization consistent. ## FULL-BRANCH VERIFICATION + BUG HUNT round 9 (I14) Ran the COMPLETE test matrix across all 46 commits: 375 tests green (controller 21 unit + 127 integration; frontend 20 unit + 227 e2e), 0 clones, 0 unused deps/exports, no circular deps, all structure/contract gates pass. The whole loop's cumulative work is coherent and releasable. Net code delta ~line-neutral (the I3 prettier reformat is ~800 lines of format-neutral churn); real work = 19 files + 8 routes + 2 workflows + 1 dep + 5 config/scripts removed, offset by ~45 bug fixes with test coverage and defensive guards. Browser-host sweep (commit ee39801c) — the last complex unswept surface: - HIGH orphaned Chromium: stop() never called + no exit hook → every server restart leaked a headless browser holding the profile lock. Guarded process.on('exit') kills it. - MED freshPage recovery path leaked a CDP WebSocket per 'Not attached' recovery (replaced page never closed). Close it first. - Verified #4 (poll double-subscribe) is a FALSE POSITIVE — subscribeFrames is synchronous; #5 benign. CLEARED: no launch arg-injection, CDP calls time out (10s), JSON.parse guarded. ## PI-RUNTIME AUDIT — I15 (personal, no change) Audited the server-side pi-runtime session manager (pi-runtime.ts, the privileged process that spawns/manages pi coding-agent sessions) — the last unswept complex surface. CONCLUSION: sound, no safe actionable change. Do NOT re-hunt. - loggedEvent listener cleanup is correct: prompt-path listener removed via Effect.ensuring (pi-runtime.ts:272-277); SSE route wires off = onLoggedEvent(...) and calls it in close() on request.signal abort + terminal events (runtime/events/route.ts:50-60,80,119). No EventEmitter leak. - eventLog bounded at 2000/session (pi-runtime.ts:429). - sessions Map is never pruned BY DESIGN: runtimes are kept alive across turns for instant session resume (a core feature). Deletion is disabled (405); archiving must not stop a resumable runtime. An LRU/cap would risk the resume feature the loop has preserved. Retained shells for never-started sessions are negligible (EventEmitter + empty arrays). Not a safe-to-fix bug. ## CONFIG/SCRIPT AUDIT — I16 Systematic pass over every tracked config file + npm script (charter's settings/config mandate). Result: ONE genuine cut, rest verified purposeful. - CUT: controller/.lintstagedrc.json (dead — lint-staged not a controller dep, no controller script/hook invokes it; pre-commit controller branch only runs typecheck). commit 67a81dac. - KEPT (verified wired/justified): tsconfig ×3 (distinct compile targets), eslint ×2 + knip ×2 (distinct rule sets/entries), jscpd ×2 (intentional per-workspace minTokens), depcheckrc/postcss/prettierrc/prettierignore/ release.config all used, frontend .lintstagedrc wired (dep + precommit). - frontend 32 scripts are purposeful: check:* is gate composition, desktop:* is a real build/run/dist/dev matrix. Only near-dup (typecheck:desktop == desktop:build:main) is a meaningful gate-vs-build naming distinction — NOT consolidated (marginal, risks gate composition). daemon.sh/deploy-remote.sh are README-documented user helpers; frontend/scripts/test-*.ts run via glob. ## SHARED-EXPORT AUDIT — I17 Found the one dead-code class the mechanical gates can't see: knip runs per-workspace and its project scope is src/** + desktop/** — it NEVER scans shared/. So a shared/contracts export used by NEITHER workspace is invisible to both knip runs. Enumerated all 71 shared exports, cross-referenced controller/ src + frontend/src + frontend/desktop + tests + shared/: - CUT (de-exported to module-private): VLLM_ONLY_FLAG_KEYS, normalizeEngineArgKey, getForeignFlagKeys, isKnownVllmExtraArgKey — all referenced only inside engine-args.ts, zero external use. commit caf894b2. Public contract API -4. - The other 67 shared exports are all consumed. No other dead shared surface. - desktop/** IS in frontend knip's project scope (no blind spot there). - Did NOT chase src/ exports-used-in-own-file (knip ignoreExportsUsedInFile:true is a deliberate choice; de-exporting internally-used exports en masse is risky). ## CSS-TOKEN + TEST-COVERAGE AUDIT — I18 (no change) Two blind-spot investigations, both correctly declined: - CSS custom-property audit: 163/313 globals tokens have no var()/(--x) reference — but this is a TAILWIND v4 FALSE-POSITIVE TRAP. The @theme/@theme-inline blocks turn --color-*/--radius-*/--text-*/--font-* into GENERATED utility classes (bg-sky-500, rounded-md, text-emerald-400) used across 52 TSX files; a var()-grep structurally cannot see utility consumption. Removing any would break utilities + violate the absolute UI-pixel-identical rule. DO NOT attempt a CSS-token cut by grep. Even the few non-palette semantic tokens (--ui-active etc.) aren't worth the theme-break risk for ~lines of CSS. NO CHANGE. - Test-coverage audit: every test file IS wired to a runner (controller: bun test src = 3 unit files; bun test ../tests/controller/integration = 20; frontend: tsx --test scripts/test-*.ts = 8 unit; ../tests/frontend/e2e/*.test.ts = 27 e2e). No orphaned/never-run tests. Clean. ## PI-PARITY AUDIT — I19 (deep pi comparison + derived cuts) User asked for a deep review of earendil-works/pi vs this repo ("pi is my favorite codebase; I want to feel that way about ours"). Two deep-review agents + personal reads. Full findings summarized here so future iterations use them. What pi does that we already match: acyclic dependency graph (ours enforced by validate-shared-contracts + madge + structure validators; pi's by npm package boundaries), controlled file sizes (our median 100/max 731 beats pi's median 116/max 5,780), zero-`any` typing discipline (ours is stricter), current docs. The controller "would sit comfortably next to pi." Where pi is structurally better — ranked, with disposition: 1. ONE streaming primitive (88-line EventStream) + ONE pipeline; we have 8 transform layers engine→pixel plus a parallel hydration reducer (messages/replay.ts duplicating pi-event-applier control flow). BLOCKED-ON- USER: this is the agent-simplification plan (memory: 6-step plan awaiting go); the seams are where every historical streaming bug lived. Not for the autonomous loop. 2. Session identity: pi has one; we have 4 namespaces (pane SessionId, runtimeSessionId ×21 files, piSessionId ×46, proxy session_id from 7 sources). BLOCKED-ON-USER (step 5 of the same plan, riskiest). 3. The agent runtime lives inside the Next server (piRuntimeManager on globalThis, 43 app/api/agent/* route dirs) instead of a separate layer. BLOCKED-ON-USER (architectural). 4. pi's test harness runs the REAL stack against a production-path fake provider (faux.ts in src, .inMemory() constructors); test:src ratio 0.73 vs our 0.16, and our riskiest controller paths (download-manager real-run, process-manager spawn, environment start) are untested. Candidate for a future test-investment iteration (adds code — needs user appetite). 5. pi is a real npm workspace; we import shared/ via ../../../.. relative paths with per-package toolchains. SKIP: workspace conversion is churn with real breakage risk (bun vs npm lockfiles), ~0 line win. 6. Liveness: pi awaits listeners in one settlement chain; we run triple arbitration (SSE + poll + probe) twice over. Verified deliberate (race comments per leg) — KEEP.