Skip to content

Latest commit

 

History

History
477 lines (434 loc) · 74.8 KB

File metadata and controls

477 lines (434 loc) · 74.8 KB

VibeSpace

HOW TO USE (tiered knowledge, 2026-08-13): CLAUDE.md is the auto-loaded INDEX + LAWS. The detailed operating essays live in docs/kb-*.md and are read ON DEMAND: kb-file-structure.md (per-file essays — read BEFORE modifying a file) · kb-design-lessons.md (§1–§17 + review invariants; §-references resolve there) · kb-features.md (shipped-behavior reference) · kb-patterns.md (cross-cutting coding patterns) · kb-api.md (REST/WS reference) · kb-bugfix-invariants.md (incident essays) · docs/history-archive.md (ancient chronicle). Contract: before changing a subsystem, read its kb entries; when your change alters behavior, update the kb file IN THE SAME COMMIT — the kb files ARE the operating manual, this file only indexes them.

Project Overview

A backend-agnostic web workspace for coding agents — manage many concurrent agent CLI sessions with a tiling window manager, file explorer, and code editor. Each backend (Claude Code, Codex, …) is driven through a BackendAdapter (src/adapters/); the UI, window manager, chat view, and session layer stay backend-neutral. Sessions run via native agent CLIs inside dtach for persistence across server restarts. (Project was "Claude Code WebUI" before the 2026-06-22 rename to VibeSpace — references to the claude/Claude Code CLI tool itself are unchanged.)

How to Build and Run

# One-line install (checks deps, clones, builds):
curl -fsSL https://raw.githubusercontent.com/ProblemFactory/vibespace/master/install.sh | bash

# Or manually:
npm install
npm run build   # esbuild bundles src/client.js → public/bundle.js (~1.8MB)
node server.js  # starts on port 3456 (or PORT env var)
# ./run.sh      # supervised: build once, then respawn on exit (survives an OOM kill)

# PRODUCTION (this machine runs it this way since 2.73.0): systemd USER service
./scripts/install-service.sh        # installs+enables ~/.config/systemd/user/vibespace.service (linger on)
systemctl --user restart vibespace  # THE way to restart after a build — do NOT kill+nohup by hand
./scripts/update.sh                 # pull+install+build+restart in one step (⚙ → Update VibeSpace… runs this in a shell terminal; dtach sessions survive)
journalctl --user -u vibespace -f   # logs (no more /tmp/vibespace-server.log)
# Unit: Restart=always/RestartSec=5 (survives OOM/crash — verified via SIGKILL), OOMScoreAdjust=-500,
# StartLimitIntervalSec=0 (retries forever while the NFS workspace mounts late), **KillMode=process**
# (CRITICAL — the default control-group KillMode killed every dtach session spawned AFTER the service
# migration on each restart, while pre-migration sessions survived; real incident, one session died on
# every restart. Only the node main process may be killed on stop), and baked PATH (node dir +
# ~/.local/bin — systemd's minimal env broke every CLI spawn; real incident). The service does NOT
# build — build at deploy, then restart. dtach sessions survive restarts by design.

Architecture

⚑ THE THREE-TIER FINAL FORM (2.319.0) — where a NEW CHANGE goes, and which test gates it

The 2026-08 campaign (docs/design-three-tier.md, R0–R6 + session-brain + closure marathon) reached its terminal shape. Before writing code, route the change:

The change touches… It belongs in… Gated by (run BEFORE commit)
Facts about a machine (discovery, sysinfo, probes, usage walk, transcript parse, ctx sync, writer sweep, shell prelude) The SHARED module (src/discovery-facts, sysinfo, machine-probes, usage-walker, transcript-service, ctx-sync, writer-sweep, remote-shell) — the daemon bundles it, one implementation runs where the facts live; ssh scripts are FALLBACK RUNGS only The module's parity suite: test-discovery-interpret / test-sysinfo-op / test-usage-walk-parity / test-transcript-parity / test-ctx-sync / test-writer-sweep / test-remote-shell — a one-sided edit FAILS them
A new device op src/agentd/agentd.js handler + capability in the hello-ack + client method. THREE-TOUCH RULE (2.300.0, bit twice): reply carries op in the client's id-keyed routing set; unsolicited pushes get their own branch BEFORE prevControl; watches re-arm on reconnect. Reply via mux.control, never an invented helper A real-daemon test (test-sysinfo-op is the template); capability-gate assert (old daemons are never asked — unknown ops HANG)
Live session stdout consumers (served model, usage odometer, banners, todos…) claudeSideEffects in server.js — ONE implementation fed by the parse AND the device stream through sbSeenFirst (parse registers, device fills gaps). Never a new inline consumer in the parse block test-session-brain-dark (mid parity is suffix-wise — every normalizer prefixes its own session id)
Session creation/supervision The daemon pipe-session path (R6, agentd.localPipeSessions) is the FINAL form; dtach is the legacy path that dies by attrition. New spawn features go through buildRemoteExec (remote) / the r6Argv seam (local) test-remote-shell drift guard; test-agentd-session
A one-shot data migration (reshaping stores on old→new update) src/server/migrations.js (this instance) or the DEVICE_MIGRATIONS registry in agentd.js (devices, runs at daemon boot/self-upgrade) — BOTH through the shared runner src/migration-runner.js: ledger-keyed run-at-most-once, failed = retry next boot never block, archive-never-destroy, append-only dated ids. Pre-framework one-shots keep their own markers — do not re-register. Wire-compat residue (410s, capability gates, permanent aliases) is NOT migratable — it retires by fleet-telemetry condition, except device-local state which a DEVICE_MIGRATIONS entry can rewrite scripts/test-migrations.mjs (11)
Quota/vendor calls NOWHERE new. Exactly two files may construct vendor requests; the quota-refresh op (human-gated, on the login-holding machine) is the ONE device-side vendor surface. Passive capture (statusline/rate_limit_event/banners) is always preferred. auto-cli (2.329.0, owner-approved reversal for THIS channel only): a burn-aware background loop may spawn claude -p /usage (official binary makes the fetch; zero vendor HTTP from us; idle accounts get a SLOW rung since 2.334.0, owner-directed: per-tick-randomized 30–60min staleness threshold — wandering, never metronomic — + failure exponential backoff; never-read accounts bootstrap through it) — decision=decideCliRefresh (PURE), refresher=usage-routes refreshViaCliPanel, wiring=server.js loop. Do NOT generalize this to raw-token polling test-vendor-whitelist (23) FAILS any new vendor call until deliberately allowlisted with its gates
Pool/billing decisions src/account-pool-auto.js (pure) + the engine in server.js; per-session links (plan C) resolve via poolCurrentFor(pool, webuiId). Every blocked outcome must SPEAK with named buckets test-pool-auto (56) + test-account-pool (31) + test-account-verdicts (45)
A server cache a client also caches The invalidation entry point must NOTIFY (the 2.309.0 rule) — hook on the ONE entry point, never per call site test-remote-discovery-dirty's drift guard pattern

ACTIVATION SWITCHES (code complete, soak-gated — flip deliberately, in order): ① watch sb-parity-hit/miss in Diagnostics (step-2/3 live parity) → ② agentd.localDiscovery on (after local-disc-snap-ms looks sane on YOUR storage) → ③ agentd.localPipeSessions on (R6 — only after ①② soak; the design's sequencing law). Rollback = flip off; every path falls back structurally.

ENFORCEMENT (2.323.0; PHYSICAL since 2.325-2.326): the tier rules above are a RED TEST, not prose — scripts/test-architecture.mjs (47 asserts, runs INSIDE npm run build AFTER esbuild — it validates the just-built daemon bundle, and running it first broke every fresh checkout's first build) walks the real require/import graph: PURE imports nothing, SHARED never reaches up, DEVICE pulls only shared+pure, CLIENT crosses only the wire, the daemon bundle carries no orchestrator markers, and server.js stays ≤2100 lines with ≥14 modules under src/server/ (the SIZE RATCHET). Cross-tier edges need an allowlist entry WITH a reason; dead allowlist entries fail the suite. Adding a module = add it to the right tier set in that file (path-based defaults cover src/lib/, src/routes/, src/server/). 拆分P1-P3 (2.325.0-2.326.0) made the separation PHYSICAL: server.js = bootstrap + wiring stanzas only (6423→~1910 lines; 14 create(deps) factory modules under src/server/ — usage-pool-engine, session-stdout, boot-restore, session-brain, account-usage-routes, mounts-plugins-wiring, cli-env, dial-pairing, exit-routes, ops-routes, incident-wiring, sysinfo-wiring, goal-sync, agent-tool-generators); the ws 'create' case family lives in src/ws-create.js behind the VALIDATED WS_CTX_CONTRACT (missing dep = loud boot error; scripts/test-ws-contract.mjs pins destructures + call site); every live-session _field is registered with an owner in src/session-schema.js (scripts/test-session-schema.mjs fails anonymous new fields). Extraction discipline lives in memory feedback_no_hacks (verbatim create(deps) factories, lazy getters for late singletons, mutable let crosses as explicit get*() never a Proxy, worktree-only boot smokes — the repo dir attaches to PRODUCTION dtach sessions, #127 class); scripts/test-restore-smoke.mjs (create→SIGKILL→reboot→reconnect, worktree-isolated) is the cross-seam gate that caught the campaign's one real wiring bug. Since 2.333.0 it also fires a 28-route GET battery at the live worktree server (any 5xx = fail): a factory function used by server.js but never EXPORTED throws only when the route RUNS — no boot log, no build-gate signal (the /api/agent-hooks 500; third lost-export incident).

STANDING SWEEP (the sysinfo lesson, 2.314.0): "twin-sets = 0" is not a state, it's a metric to re-measure. A user caught a missed local/remote twin AFTER closure. When touching any "how much X" reader, grep for its sibling on the other transport first.

Stack

  • Backend: Node.js + Express + WebSocket (ws) + node-pty + dtach
  • Frontend: Vanilla JS (ES modules) bundled with esbuild, xterm.js for terminals, CodeMirror 6 for editor
  • Session persistence: dtach — claude processes run in dtach sessions, survive server restarts
  • Data persistence: data/layouts.json, data/session-meta/*.json, data/sockets/cw-*

Data Flow

Terminal mode:
  Browser (xterm.js) ←→ WebSocket ←→ node-pty (dtach -a) ←→ dtach socket ←→ pty-wrapper.js ←→ claude CLI
                                                                                  ↓
                                                                            buffer file (raw PTY)

Chat mode:
  Browser (ChatView) ←→ WebSocket (msg create/edit/meta) ←→ MessageManager ←→ server ←→ chat-wrapper.js ←→ claude --stream-json
                                                                   ↓
                                                          BackendAdapter (swappable)

Cross-cutting laws (the always-loaded digest — full text lives in the kb files)

  • §ban-safety: NEVER call Anthropic on a timer with a subscription token; vendor requests live in EXACTLY two files (scripts/test-vendor-whitelist.mjs enforces); passive capture is the default; the ONE owner-approved exception is auto-cli (claude -p /usage: burn-aware fast rung + owner-directed slow idle rung at a WANDERING 30–60min threshold, never a fixed cadence) — do NOT generalize it. Full: kb-design-lessons §9 + the routing table above.
  • Program-use billing: sessions run interactive PTY mode — never -p/--print/Agent SDK for inference (moves usage to metered programmatic billing).
  • Public repo hygiene: ZERO company/cluster detail, ZERO personal identifiers (users are userL/userW/userN in docs); secret-scan CI + pre-push guard.
  • Worktree-only smokes: NEVER run server.js/boot smokes from the repo dir — repo data/ is PRODUCTION (#127 class); git worktree + own data/ + symlinked node_modules; harnesses set VIBESPACE_SKIP_AGENT_HOOKS=1.
  • Release discipline: every change = version bump + CHANGELOG + commit + push; git push runs the MANDATORY release gate (npm run ci — build + gate suites + ONE real haiku chat turn (oat slot ~/.config/vibespace/ci-oat locally + VIBESPACE_CI_OAT repo secret in Actions — the officially documented setup-token CI channel) + headless-chrome CLIENT boot smoke + worktree server boot smoke w/ route battery, ~1.5min, tracked pre-push hook + GitHub Actions mirror; docs-only pushes skip; VIBESPACE_SKIP_CI=1 = emergency bypass, use never). Daemon changes also npm run build:agentd; user self-updates — no agent-initiated restarts.
  • CS separation: hostId is a PARAMETER, never a branch — one implementation against a machine handle; new facts-about-a-machine code goes in the SHARED modules with a parity suite (routing table above).
  • Multi-client: every persistent state change broadcasts to other clients live; UI actions chained after a store write must NOT wait for the broadcast echo.
  • Cache invalidation must NOTIFY: a server cache a client also caches → the invalidation entry point pushes the recomputed RESULT (one dirty signal = one computation).
  • i18n quick rules: t() with ENGLISH-STRING-AS-KEY on human-visible chrome ONLY (never protocol values/stored strings/agent-facing text); keep escHtml() around t(); sidebar cluster imports t as tr; same-spelling-different-meaning ⇒ tc(ctx,str); new keys need zh+ja entries (i18n-check runs in build). Full: kb-design-lessons §16.
  • UI conventions: theme vars only (no literal colors), --radius/--radius-sm, createModalShell for dialogs, NO native prompt/alert/confirm, SVG icons only (never emoji), data-popover Esc protocol, no global .hidden. Full: kb-design-lessons §17.
  • Injection budgets: hook additionalContext wraps into at exactly 10 KiB — tools/rules first, logs byte-budgeted last, 9600B hard cap (agent-routes).
  • Spawn hygiene: agent sessions get agentEnv() (sanitized env, never raw process.env); display strings (host-labeled cwds) never reach a spawn; secrets ride env/files, NEVER argv.
  • Danger constants (memorize): NEVER delete data/sockets/cw-* (the socket IS the session). Production restart = systemctl --user restart vibespace ONLY (KillMode=process is load-bearing — the default killed every dtach session). Restarting the server from inside a Claude session is safe ONLY because of the CLAUDE_CODE_* env sanitizer in server.js (never remove it — silent transcript loss). Buffer/meta sweeps are age-based; kill-path teardown runs BEFORE activeSessions delete.
  • XSS: NEVER interpolate user/peer-controlled strings into innerHTML unescaped (they sync to ALL clients — one miss = stored XSS fleet-wide); markdown goes through DOMPurify.sanitize(marked.parse()); build image overlays via .src property, never innerHTML.
  • Atomic persistence: every data/*.json write goes through writeJsonAtomic (tmp+rename); stores flush on SIGINT/SIGTERM — a bare writeFileSync is silent data loss on the exact crash path the product exists to survive.
  • Listener lifecycle: window-scoped document listeners bind to winInfo._listenerCtl.signal (AbortController, aborted on close); per-drag listeners get a per-drag controller — never a per-render one (tears down MID-DRAG).
  • Never block the event loop: no sync fs/exec against mountpoints, /dav-sharing processes, or discovery sweeps — child processes/workers with timeouts only (three whole-instance outages: FUSE threadpool, execFileSync sweep freeze, device-mount self-deadlock).
  • No silent failures: a failed USER action must reach the user (toast/dialog/server-notice) — telemetry having it ≠ reported; fetchJson never throws, so callers check r?.error.
  • Working mode: route every change through the three-tier table above (it names the module AND the gating test); fixes go to git only — the user self-updates; bookkeeping via vibespace-status/-task/-ask each turn.

File Structure (index — essays in docs/kb-file-structure.md)

Long entries are truncated here; the ⇒ marker means the full essay (invariants, incidents) is in kb-file-structure.md under that filename. READ IT before editing the file.

server.js — BOOTSTRAP + WIRING ONLY (~1910 lines since the 2.325.0 拆分; the arch suite RATCHETS it ≤2100). ⇒ kb-file-structure.md
src/ws-create.js       — the ws 'create' case family (1633 lines, 拆分P2): new/resume/fork + local/ssh/dial/daemon-pipe spawn + account resolution + breakers + writer sweep + adoption; body wrapped `do{}while(0)` so case-level `break;` semantics survived the move. Deps arrive via the VALIDATED WS_CTX_CONTRACT (ws-handler.js) — missing key = loud boot error; scripts/test-ws-contract.mjs pins contract⇄destructures⇄server.js call site.
src/session-schema.js  — PURE registry of all ~47 live-session `_fields` (owner module + persistence home + note, 拆分P3); scripts/test-session-schema.mjs fails any unregistered new field write and any dead row. New session state ⇒ add the row FIRST.
editor-helper.sh       — Ctrl+G editor helper (overwritten by server on startup)
data/bin/editor/code — Fake "code" script for Ctrl+G (generated by server; moved OUT of data/bin in 2.267.0 — data/bin is PATH-prepended into every local session and the fake `code` shadowed real VS Code there; EDITOR= stay… ⇒ kb-file-structure.md
data/bin/pty-wrapper.js — PTY wrapper for terminal mode (runs inside dtach, survives server restarts)
data/bin/vibespace-hook.mjs — Dual-harness hook (SessionStart→task context OR a baseline tools intro for no-task sessions, UserPromptSubmit→first-prompt context + status-override notices + per-turn micro-reminder, Stop→bookkeeping… ⇒ kb-file-structure.md
data/bin/vibespace-task — Agent task-reporting CLI (STATIC tracked file, not generated — inline template-in-template escaping was unmaintainable): show [--full]/progress "summary" [--detail]/backlog/backlog-add/backlog-done/backlog-drop → /api/agent/task* (scoped to the session's own context task). ⇒ kb-file-structure.md
data/bin/vibespace-ask — Agent CLI for the GLOBAL USER-TODO inbox (2.65.0, STATIC tracked): files items the USER must act on (decision/input/review) → POST /api/agent/user-todo (vsst_ scoped to own session). ⇒ kb-file-structure.md
data/bin/vibespace-status — Agent status tool (GENERATED at startup like data/bin/code — NEVER git-track it: tracking it dirtied the tree every boot and blocked `git pull --ff-only` in self-update (real incident 2.111.26); gitig… ⇒ kb-file-structure.md
data/bin/vibespace-job — Background Work agent CLI (STATIC tracked, 2.342.0): run/--keep-up/--every/--cron/--at/poll(--wait cap 100s)/logs/stop/start/rm/access/progress/ask/answers; auth vsst_ session token OR jbt_ job token (job may act on itself only). In AGENT_TOOLS (ships to remote hosts).
data/bin/job-wrapper.js — Background Work detached wrapper (STATIC tracked): first act = atomic pid.json stamp {pid,starttime,bootId,argvHash}; tees child stdout→current.log (50MB rotate, never unlink live fd); exit.json on child exit; answers.jsonl→stdin tail when stdinOpen.
data/bin/vibespace-msg — agent互聊CLI (Channels v1, 2.362.0, STATIC tracked, in AGENT_TOOLS): list/send by 名称或conversation id; 组ACL服务器端执行; idle接收方=计费turn(工具明教); 手册vibespace-docs msg
data/bin/vibespace-page — 页面发布+design kit CLI(2.366.0, STATIC tracked, in AGENT_TOOLS): publish <file.html> --title [--public](上传快照→/p/<id>分享链接, 同路径同URL) · kit(准备/镜像design工具包并打印base directory) · list; vsst_或jbt_. 手册vibespace-docs pages
data/bin/vibespace-docs — full-manual reader for every agent CLI (2.351.0, STATIC tracked, in AGENT_TOOLS): `vibespace-docs [status|ask|task|jobs|msg|pages]`, no topic = global index; fetches GET /api/agent/docs/:topic (vsst_ or jbt_) which serves docs/agent/*-manual.md from the running server's checkout — manuals never drift from the running version; budgeted context teaching carries one pointer line per surface.
data/bin/vibespace-channel.js — EXPERIMENTAL Claude Code channel server (2.344.0, STATIC tracked, dependency-free hand-rolled MCP stdio): declares claude/channel capability; VibeSpace pushes {content,meta} lines to its per-session unix socket (VIBESPACE_CHANNEL_SOCK, data/channel-socks/<webuiId>.sock) → notifications/claude/channel → <channel source="vibespace"> events in the session. Spawned by the CLI itself when agents.vibespaceChannel is ON (per-spawn --mcp-config + --dangerously-load-development-channels server:vibespace — research preview keeps custom channels on the dev flag). Foundation for external chat bridges; NOT the primary notify lane.
data/bin/vibespace-usage — PASSIVE usage capture (STATIC tracked file, not generated; 2.60.0). ⇒ kb-file-structure.md
data/bin/chat-wrapper.js — Chat wrapper for stream-json mode (runs inside dtach, parses JSON lines)
data/bin/codex-chat-wrapper.js — Codex chat wrapper (JSON-RPC protocol, thread management)
data/bin/vibespace-usage-scan — REMOTE-side usage ledger scanner (2.127.0, STATIC tracked; PARITY-GUARDED since 2.275.0: it is a shipped SINGLE FILE (a host has no checkout ⇒ it cannot require the local walker), so it silently lagged its twin twice — the 2.265.0 subagents/workflows walk took 6 weeks to reach it. ⇒ kb-file-structure.md
data/bin/vibespace-remote-keeper — REMOTE-side persistence for remote CHAT sessions (2.124.0, STATIC tracked; distributed to remote ~/.vibespace/bin with the other tools): claude runs setsid-DETACHED on the host under a keeper daemon (… ⇒ kb-file-structure.md
data/sockets/cw-*      — dtach socket files (session anchors)
data/session-buffers/  — Per-session output buffer files (written by pty-wrapper)
data/session-meta/     — Per-session metadata JSON
data/layouts.json      — Persisted layouts and autosave state
data/drafts.json       — Chat input drafts (SyncStore, versioned, multi-client synced)
data/settings-sync.json — Settings SyncStore (future migration target)
src/
  transcript-service.js — ONE interface over the transcript parse stack (R3 of docs/design-three-tier.md; 2.283.0 step 1 = server-hosted, HTTP read family rerouted through it). ⇒ kb-file-structure.md
  client.js            — Entry point (2 lines): imports App and initializes
  ws-handler.js        — WebSocket protocol handler (all WS message cases, extracted from server.js)
  sync-store.js        — SyncStore class (versioned state sync with diff broadcast)
  session-store.js — SessionMessages + JSONL parsing + session discovery helpers. ⇒ kb-file-structure.md
  message-manager.js — MessageManager (Claude stream-json → normalized messages with stable IDs). ⇒ kb-file-structure.md
  codex-message-manager.js — CodexMessageManager (Codex JSON-RPC → normalized messages)
  codex-session-store.js — Codex session discovery (thread listing, JSONL parsing, forkedFrom chain merge)
  normalizers.js       — createMessageManager(backend, id) factory for backend-agnostic normalization
  mounts.js — MountManager (rclone mounts, MULTI-SOURCE: typed records s3/drive/onedrive/gmail/webdav/sftp/vibespace/rclone via _rcloneFor; detached + boot adoption; one-click rclone install (data/bin, pinned 1.65.… ⇒ kb-file-structure.md
  gmail-sync.js — GmailSync (2.134.0, backlog B-64db): Gmail-as-a-folder — a 'gmail' mount = local dir of .eml files synced READ-ONLY from the Gmail API (GYB-style; NOT FUSE). ⇒ kb-file-structure.md
  transcript-worker.js — heavy JSONL transcript work OFF the main thread (2.235.0, the userL degradation follow-up): a persistent SafeFs-hosted worker pool (SafeFs gained opts.workerPath/inlineRun generalization + in-flight r… ⇒ kb-file-structure.md
  agentd/ — CS refactor (B-5052) machine agent — ALL milestones' protocol+device side COMPLETE and acceptance-tested (2.140.0–2.146.0; docs/design-remote-cs.md + M0 addendum + milestone record). ⇒ kb-file-structure.md
  machine-mounts.js — MachineMounts (B-f3e8 2.160.0 — HostMounts 2.147.0 + DeviceMounts 2.153.0 collapsed into ONE manager; records carry dir:'push'|'pull', keyed by hostId, store data/machine-mounts.json, both legacy stor… ⇒ kb-file-structure.md
  exit-proxy.js — ExitProxyManager (task #164, 2.186.0): ON-DEMAND egress — an agent borrows a paired machine's network for a SINGLE command (a region / internal-VPN / fixed source IP), NOT the whole session. ⇒ kb-file-structure.md
  port-forward.js — PortForwardManager (B-0b60 tunnel path, 2.165.0): vscode-style port forwarding over the agentd data plane — NO frps/public exposure. ⇒ kb-file-structure.md
  plugins.js — PluginManager (2.140.0, B-2d44): generic host-capability plugins — install + PERSISTENT state (~/.vibespace/plugins/<id>/, home=PVC in fleet) + boot replay (enabled+desiredUp → restart with server) + guided setup + status. ⇒ kb-file-structure.md
  accounts.js — AccountManager (per-session billing-identity switching, backend-scoped via a `backend` field on each account — legacy records = 'claude'. ⇒ kb-file-structure.md
  (LONG-LIVED TOKENS oat, B-211a 2.253.0 — `claude setup-token` integration: env channel, expiry honesty, ambient strip, review-hardened invariant list) ⇒ kb-file-structure.md (under accounts.js)
  account-pool-auto.js — Pooled pseudo-account (B-6217, 2.251.0-2.252.0): account type 'pooled' — ONE switchable billing identity over the logged-in Claude subscriptions. ⇒ kb-file-structure.md
  rate-limit-capture.js — PASSIVE quota capture from the CLI's own `rate_limit_event` stream-json records (B-e5c9, 2.289.0; discovered by 2.1.226 binary forensics — a first-class stdout record "emitted when rate limit info changes" VibeSpace dropped as unknown forever). ⇒ kb-file-structure.md
  usage-anchors.js — Quota dead-reckoning DATA FOUNDATION (2.261.0, user-designed 惯性导航 step 1): every ground-truth usage reading (statusline/⟳/get_usage/limit-banner — any usage-cache write) becomes an ANCHOR paired with … ⇒ kb-file-structure.md
  usage-estimator.js — Quota dead-reckoning ESTIMATOR (B-fcff v2, 2.263.0): est_u = anchor_u + learned_rate × ledger_cost_since_anchor, per identity per bucket. ⇒ kb-file-structure.md
  usage-walker.js — THE ledger walk, ONE module (2.297.0 twin-killer): UsageHistory.scan() consumes it IN-PROCESS (cursors injected, onEvent enrichment orchestrator-side), the daemon runs it as the usage-scan op child, and data/bin/vibespace-usage-scan mirrors it for checkout-less ssh hosts (parity-pinned). ⇒ kb-file-structure.md
  conversation-index.js — Conversation-location index (2.297.0, R3 tail): data/conversation-index.json records which machine owns each conversation from BOTH discovery listings and transcript fetches — resume host-inference lo… ⇒ kb-file-structure.md
  account-material.js  — Device-tier credential-MATERIAL mechanics (2.298.0, data/subs formalized as device #0's account store): repointPoolSymlink (the 2.251.0 hot-swap primitive verbatim) shared by accounts.setPoolTarget AND the daemon's sealed-orders reflex (bundled). Decisions stay orchestrator-side; material acts are one implementation per machine.
  msg-acl.js — PURE reach ACL for agent messaging (Channels v1, 2.362.0): 组内互通 / 组externalVisibility / 会话override只放宽 / 多组并集 / 无组singleton. ⇒ kb-file-structure.md
  server/design-kit.js — /design画布工具包(2.366.0): 从已安装CLI提取helper+payload(CLI自解压目录优先, 否则二进制流式扫描)+技能文本, 写data/design-kit/<ver>/ 并把第4步改写成vibespace-page publish(全有或全无, 缺锚点=明确不可用), 每次用helper自己--check验证; 状态栏design芯片→可见的design request→agent走kit→发布到本实例. ⇒ kb-file-structure.md
  server/published-pages.js — 发布页(2.364.0 接管artifact; 2.366.0 publishContent上传路径+srcKey=host:path+会话归属+onPublished唯一通知点+浏览器origin启发): /p/<id>自托管可分享HTML, 默认私有+public锁, srcPath upsert稳定URL, CSP sandbox=同源XSS防线(勿加allow-same-origin). ⇒ kb-file-structure.md
  server/wrapper-files.js — chat-wrapper文件解析(2.339.2 resolveWrapperFiles: 碰撞会话经/proc argv定位真sidecar) + wrapperCaps(2.364.1: 能力=wrapper SIDECAR data/session-buffers/<id>.json的caps, 唯一读者, 无状态; 严禁从data/session-meta读caps). ⇒ kb-file-structure.md
  server/conversation-deliver.js — THE conversation delivery ladder (2.362.0): channel socket→local inbox→owning machine's agentd peer-post op→durable stash drained at injection; jobs通知与agent消息同一实现; 2.363.0投递点即渲染peer卡片(emitPeerCard→normalizer.injectPeerCard — server-posted在CLI侧是body-less origin, 只有poster能渲染). ⇒ kb-file-structure.md
  job-model.js — PURE Background Work model (2.342.0): schedule math (cron parse/next-fire/jitter/15min agent floor), permission predicates (conversation-lineage owner + view/control scopes, NO existence oracle), vendor-pattern vet, digest/update renderers (600B budget, never trips the 10240 wrap), panel schema validation. Gated by scripts/test-job-model.mjs (36).
  jobs.js — JobManager engine (2.342.0, docs/design-background-work.md): registry data/jobs.json + detached-wrapper spawns (identity pid+starttime+bootId, adopt-first boot, verified handle-kills, single-engine lock, park-after-6 crash loops, until-output cursor scan, cron ticks, GC-never-on-live, secret literal-redaction). Per-machine only (cross-machine parked by owner). 2.344.0: owner auto-notify (_notifyOwner at terminal/park/missed/ask/answered; toggles job>group>global default ON; 30s rate floor; stash data/job-notifications.json drained at resume injection; notifyPreview honesty). Gated by scripts/test-jobs-engine.mjs (27, real spawns).
  peer-messaging.js — THE delivery primitive for jobs→conversation notifications (2.344.0, B-0bf4): scans the CLI's own session registry (~/.claude/sessions/<pid>.json + published <pid>.<sha>.key) and posts the documented two-frame injection (auth + stream-json user message) onto the session's cross-session-messaging inbox socket — the CLI queues mid-turn / opens a billed turn when idle / applies its own inbound controls (never bypassed; automation red line intact: we never write session stdin). Also postChannelEvent (experimental vibespace-channel lane). Local machine only; every failure returns {ok:false,reason} so the engine stashes. Gated by scripts/test-peer-messaging.mjs (10, real unix-socket inbox).
  otel-truth.js — PURE OTLP-logs parser (2.361.0, B-345b): the CLI's OpenTelemetry api_request events are the ONLY per-request channel that NAMES the billing org (transcript/statusline/rate_limit_event carry values, never identity). ⇒ kb-file-structure.md
  server/otel-ingest.js — loopback OTLP TRUTH receiver (2.361.0): local claude sessions push their own api_request telemetry here (zero vendor calls); observed org overrides link-intent attribution at ledger bake time + corrective attribution records + append-only stash. ⇒ kb-file-structure.md
  usage-history.js — UsageHistory (2.61.0, codex 2.63.0): PERMANENT per-request token LEDGER for the Usage window. ⇒ kb-file-structure.md
  session-status.js    — SessionStatusManager (session-level state/urgency set by the AGENT via vibespace-status CLI or by the user; user overrides of agent-set values record a pendingNotice injected as <system-reminder> into the NEXT chat-input; data/session-status.json — memory + session-status-updated broadcast are IMMEDIATE, disk write is DEBOUNCED 500ms + content-compared + flushed on exit (2.38.0), single-process so no race; independent of any task/context folder)
  user-todos.js — UserTodoManager (2.65.0, the GLOBAL user-facing TODO list / "For you" inbox): discrete items an AGENT filed that need the USER (decision/input/review) — the inverse of the agent's own TodoWrite. ⇒ kb-file-structure.md
  task-groups.js — TaskGroupManager (岗位/Task-Group store; renamed from tasks.js in 2.39.0 — data/task-groups.json authoritative, migrated once from legacy data/tasks.json; ⊃ legacy groups, CRUD + bind/unbind + progress,… ⇒ kb-file-structure.md
  webdav.js            — MountTokens (scoped bearer mount tokens, sha256-hashed, per-token chroot root + ro/rw) + registerWebdav (/dav WebDAV subset for VibeSpace↔VibeSpace mounting; auth = Bearer vsmt_ token, bypasses cookie auth, registered before json body parser)
  vnc.js — VncManager (2.104.0, in-container desktop): LAZY lifecycle — POST /api/vnc/start spawns Xtigervnc (-localhost -SecurityTypes None, safe: the cookie-authed /api/vnc WS bridge is the ONLY route in) + XF… ⇒ kb-file-structure.md
  ssh-key-format.js — Private-key FORMAT classifier, PURE + shared server/browser (CJS pulled into the bundle like task-color-seq.js). ⇒ kb-file-structure.md
  ssh-key.js — Server-side private-key UNLOCK at import (2.246.0). ⇒ kb-file-structure.md
  ctx-sync.js — THE ctx-folder sync, ONE implementation for every machine (2.277.0). The old split — ssh=bidirectional rsync NO caps, dial=hashed sync with a SILENT ≤400-files/≤2MB cap — meant a 3MB context file reached every ssh host and never reached a dial device, invisibly. ⇒ kb-file-structure.md
  discovery-facts.js — Shared discovery INTERPRETATION (2.278.0) — deliberately tiny so the esbuild agentd bundle carries it (unlike the ssh one-file scanner, the daemon CAN share code). ⇒ kb-file-structure.md
  writer-sweep.js — THE pre-resume writer sweep, ONE implementation for every machine (CS separation, 2.276.0). The invariant: before resuming, no OTHER process may still be writing that conversation's transcript (two writers on one JSONL = the B-4058 corruption class). ⇒ kb-file-structure.md
  remote-shell.js — ALSO buildRemoteExec (2.279.0): the five remote spawn command builders (ssh terminal/ssh keeper chat/ssh agentd pipe/dial pty/dial pipe) are ONE composition — `cd → pre → resolve → AMBIENT_OAT_UNS…` ⇒ kb-file-structure.md
  **CS SEPARATION RULE (2.276.0): `hostId` is a PARAMETER, never a branch.** Server-side logic is written ONCE against a machine handle; the only difference between this machine, an ssh host and a dialed-in device is the transport underneath it. `hosts.device(falsy)` returns device #0 (the local daemon has been a full DeviceManager since 2.158.0 — same binary/mux/op surface — but was unreachable through the API, so every feature was written twice BY CONSTRUCTION). Migration table: docs/design-cs-unification.md — the metric is PARALLEL IMPLEMENTATIONS (9 twin-sets → 0 unjustified as of 2.279.1), not branch count (branches that select a transport for ONE shared implementation are fine). Live keystone proof: scripts/test-local-device.mjs (real daemon; absence THROWS). Documented exceptions require a table row + a drift/parity guard: the shipped single-file usage scanner (checkout-less host) and local file ops (SafeFs FUSE-hang isolation is load-bearing; the daemon carries live session pipes — revisit only with daemon-side worker isolation).
  hosts.js — HostManager (ssh host registry, connectivity test, keygen, remote discovery + first-user-message names, bootstrap, backend-status, dir-complete, fetchSessionJsonl = remote transcript → data/remote-jso… ⇒ kb-file-structure.md
  remote-fs.js         — RemoteFs (ssh-per-op remote filesystem: list/read/write/mkdir/rename/rm/stat/copy/move/download/archive/tar-stream folder transfer — mirrors /api/file* shapes; files.js dispatches on ?host=, cross-host copy = server relay)
  auth.js              — Optional password auth (scrypt, server-side cookie tokens in data/auth.json, per-IP rate limit; VIBESPACE_PASSWORD / VIBESPACE_GENERATE_PASSWORD=1). 2.103.0: optional Clerk SSO rides on top — `passwordEnabled` vs `enabled` split (Clerk alone enables auth), POST /api/clerk-login verifies the Clerk session JWT vs JWKS and issues the SAME cookie token; loginHtml() renders password and/or SSO per config
  clerk-auth.js — ClerkAuth (2.103.0, env-gated: VIBESPACE_CLERK_PUBLISHABLE_KEY = on-switch, frontend-API host + JWKS URL DERIVED from the key's base64 payload; VIBESPACE_CLERK_ALLOWED_EMAILS comma list, @domain = whole domain, EMPTY = reject all). ⇒ kb-file-structure.md
  agent-routes.js — setupAgentRoutes() (2.93.0 split): every agent-facing endpoint — vibespace-ask/status/task routes, task-context/prompt-context injection (incl. ⇒ kb-file-structure.md
  usage-routes.js — setupUsage() (2.92.0 split): the ENTIRE usage/rate-limit cluster from server.js — passive statusline ingest, read-only OAuth token, opt-in active poll, on-demand quota refresh, codex rollout-tail summarizer (§ban-safety rules apply — see kb-design-lessons §9). ⇒ kb-file-structure.md
  opslog.js — Optional persistent ops log (2.115.0, env-gated no-op): VIBESPACE_OPSLOG_DIR → console tee into daily-rotated files + boot/exit/crash markers (VIBESPACE_OPSLOG_CEPHFS_* = server kernel-mounts the path-scoped subtree first, same mechanism as My storage). ⇒ kb-file-structure.md
  incident.js — FREEZE THE SCENE for a panic-button capture (2.239.0, the admin's correction: users troubleshoot their own problems — ssh/resume/kill — and destroy the evidence before anyone looks). ⇒ kb-file-structure.md
  routes/persistence.js — …also LAYOUT ROLLBACK POINTS (2.296.0, after a bug flattened a user's whole desktop layout): `writeLayouts` is the ONE choke point every layout write passes through, so it snapshots the PREVIOUS s… ⇒ kb-file-structure.md
  lib/incident-recorder.js — "Report a problem" panic button (2.238.0, the timezone-gap fix): ALWAYS-ON client ring buffers (clicks/coarse keys — typed TEXT never recorded, only a per-burst marker; ws message TYPES both direction… ⇒ kb-file-structure.md
  telemetry.js — Telemetry (2.84.0, local-first observability): monthly ndjson shards data/telemetry/events-YYYY-MM.ndjson of {ts,kind:error|event|boot|server-error|metric,name,detail,stack,version,ua,value}; summary(… ⇒ kb-file-structure.md
  adapters/
    base.js            — BackendAdapter + SessionHandle (abstract interface for AI backends)
    claude-code.js     — ClaudeCodeAdapter (Claude Code CLI: flags, JSONL, control protocol, format methods)
    codex.js           — CodexAdapter (Codex CLI: app-server mode, JSON-RPC, permission mapping)
    shell.js           — ShellAdapter (plain login-shell terminals — no AI; toolbar Terminal button, "Open Terminal Here")
    index.js           — createAdapterRegistry() factory
  routes/
    files.js           — File system API routes (browse, read, write, upload, download, clipboard paste, CSV streaming, format)
    persistence.js     — Persistence routes (layouts, bookmarks, themes, settings, user state, groups)
    sessions.js        — Session API routes (discovery, messages, subagents, kill)
  lib/
    app.js             — App controller / mediator (~2000 lines; 2.82.0 split three prototype-mixin clusters out — installed at module tail, BEFORE client.js instantiates App)
    manage-agents.js — installManageAgents(App): Manage-Agents dialog + Anthropic/ChatGPT account rosters/wizard (~790 lines, split from app.js). ⇒ kb-file-structure.md
    usage-meter.js     — installUsageMeter(App): taskbar quota pies + usage popup + on-demand refresh (~370 lines, split from app.js)
    session-lifecycle.js — installSessionLifecycle(App): create/attach/resume/fork/view/kill + billing switcher + replayOpenSpec (~680 lines, split from app.js) RESUME PRESERVES THE HOME DESKTOP (2.295.0, a fleet user, inc-ms… ⇒ kb-file-structure.md
    themes.js          — THEMES constant + ThemeManager class. **extractThemeValues probes inside a data-theme="dark" WRAPPER (B-b2d6, 2.268.9): custom properties inherit and only dark defines every var — a bare probe read live-preview inline overrides (and an applied custom theme's stylesheet values) back as a partially-defined theme's "defaults", and Save froze them into new themes. Never regress to a single-layer probe.**
    ws.js              — WsManager (WebSocket with reconnect)
    window.js          — WindowManager (drag/resize/snap/grid)
    tab-group.js       — Tab grouping mixin (chain model, icon drag, tab bar, drag-out)
    terminal.js        — TerminalSession (xterm.js wrapper, per-terminal settings)
    sidebar.js         — Sidebar shell (filter/sort/merge pipeline, tab switching, ~870 lines)
    sidebar-workbench.js — Folders-tab three-zone rendering (installSidebarWorkbench mixin, ~540 lines)
    sidebar-mounts.js — Remote tab: machines (ssh hosts) + storage (mounts) rendering (installSidebarMounts mixin, ~1000 lines; 2.152.0: "Pair a device" action + _showDevicePairDialog — dial-out device pairing, mints via POS… ⇒ kb-file-structure.md
    sidebar-rail.js — Sidebar activity rail (2.176.0, vscode-style, setting `sidebar.activityRail` default ON): installSidebarRail mixin — ~44px icon strip replaces the 3-tab bar; panels Folders/Tasks/Remote reuse the tab … ⇒ kb-file-structure.md
    sidebar-state.js   — Sidebar state mixin (star/archive/archivedFolders/rename/migration)
    sidebar-render.js  — Sidebar rendering mixin (folder groups, drag-drop)
    sidebar-tasks.js   — Sidebar tasks mixin (client task store + tasks-updated sync, task board render desktop+mobile, bind/unbind write-through, bind popover, attention aggregation)
    task-detail.js     — Task detail window (structured editor: objective/plan/progress/sessions/folders/contextDir/color)
    task-log.js — Task Group log viewer (2.85.0, openSpec openTaskLog, window type 'task'): Backlog tab (2.122.0 — status filter all/open/done/dropped, parked/resolved attribution chips, ✎ inline text+detail edit, ✓/⊘/… ⇒ kb-file-structure.md
    workflow-detail.js — Workflow detail window (POST-HOC dynamic-workflow/ultracode viewer: phases → agents w/ state/model, per-agent View Log; reads the terminal wf_<runId>.json snapshot — live progress is TUI-only, unreadable)
    session-props.js     — Session Properties window (2.49.0: identity/state+history timeline/billing+on-resume account/config/Task-Group toggles/agent steps; openSpec openSessionProps, live re-render on broadcasts)
    user-todos-panel.js — "For you" inbox UI (2.65.0, installUserTodos(app)): taskbar button #taskbar-user-todos (count badge, worst-urgency color, blink + click-to-jump toast on NEW items — knownIds starts null so boot doesn'… ⇒ kb-file-structure.md
    usage-window.js — Usage dashboard window (2.61.0, ⚙ → Usage, window type 'usage', openSpec openUsage). ⇒ kb-file-structure.md
  sidebar-render-mobile.js — Mobile sidebar mixin (two-level folder/group navigation)
    mobile-nav.js — MobileNav class (window switcher, close, desktop tabs, gestures; 2.99.0: ⚙ gear → gs-menu + worst-of quota donut chip → usage popup — the taskbar with ALL its entry points is hidden ≤768px, these are … ⇒ kb-file-structure.md
    session-card.js — Session card renderer (SVG icons, composite backend+mode icons). ⇒ kb-file-structure.md
    agent-meta.js      — Backend/agent metadata, SVG icon creation (createBackendIcon, createModeBackendIcon)
    file-explorer.js   — FileExplorer (browse, View menu, resizable columns, preview panel; uploads + ops clusters split out 2.93.0)
    file-explorer-uploads.js — installExplorerUploads: upload popover, batched multipart, inline progress + ring, synced history
    file-explorer-ops.js — installExplorerOps: context/background menus, clipboard copy/cut/paste, rename/delete/duplicate, archive compress/extract, properties
    setup-flows.js     — installSetupFlows(App): onboarding wizard, Backup & migrate, password dialogs, diagnostics report (2.93.0 split from app.js)
    file-viewer.js     — FileViewer (dispatch by type via file-types registry, renderInto shared method; 2.341.0 floating ⟳ reload w/ media cache-bust)
    file-types.js      — File type registry (extension → category, icon, viewer, bypassBinary)
    code-editor.js     — CodeEditor (CodeMirror 6, Prettier format, server-side format, HTML/MD preview; 2.341.0 disk-freshness watch + ⟳ reload — kb-features §File Management)
    chat-view.js       — ChatView controller (virtual scroll, op dispatch, lifecycle; gap-seek mixin split out 2.92.0)
    chat-view-seek.js  — installChatSeek(ChatView): the huge-JSONL continuous-scroll machinery (sentinel, slab loading, teleport, stable-height landings; 17 methods)
    chat-renderers.js  — Message rendering (user/assistant/tool/system, linkify, diffs, permissions)
    chat-input.js      — ChatInput (textarea, send, attachments, drafts, slash commands, TODO display)
    chat-status-bar.js — ChatStatusBar (model, context%, cost, permission mode, task popup)
    chat-search.js     — ChatSearch (Ctrl+F, CSS Custom Highlight API, server-side search)
    chat-minimap.js    — ChatMinimap (semantic scrollbar, turn markers, drag-to-jump)
    highlight.js       — Syntax highlighting (30 hljs languages, code block rendering, line numbers)
    theme-editor.js    — ThemeEditor (floating panel for custom themes, ~50 CSS vars + 16 ANSI colors)
    hex-viewer.js      — HexViewer (binary file viewer with chunked loading)
    resizer.js         — Resizer (reusable drag-to-resize handle)
    layout.js          — LayoutManager (auto-save/restore, multi-client layout sync)
    desktop-manager.js — DesktopManager (virtual desktops, Ubuntu-style previews, window routing) DRAG-REORDER (2.250.0): desktop preview wrappers are draggable(text/desktop-id); dropping onto another preview → reorderDeskt… ⇒ kb-file-structure.md
    stage-manager.js — StageManager (2.112.0 dynamic desktop 'Stage', default OFF via desktop.dynamicEnabled — docs/design-dynamic-desktop.md is the blueprint+progress anchor. ⇒ kb-file-structure.md
    external-editor.js — Ctrl+G split-pane CodeMirror editor (extracted from app.js)
    command-mode.js    — CommandMode (Ctrl+\ prefix key, tmux-style shortcuts, desktop switch)
    session-palette.js — Ctrl+K session palette (fuzzy session switcher; installSessionPalette, desktop only). ⇒ kb-file-structure.md
    icons.js           — Centralized inline SVG icon library (FILE_ICONS + UI_ICONS; imported by file-types/file-explorer/chat-*/etc.)
    customize-mode.js  — CustomizeMode (Firefox-style chrome edit mode: click-to-toggle + drag-to-move elements, zone arrangement, position pills)
    taskbar.js         — Taskbar rendering + window list popup (extracted from app.js)
    browser-window.js  — Embedded browser window (iframe + URL bar + proxy toggle)
    utils.js           — Shared utilities (escHtml, createPopover, showContextMenu, fetchJson, copyText, StateSync)
    i18n.js            — i18n runtime: t(str, params?) with ENGLISH-STRING-AS-KEY, per-device lang (localStorage vibespace.lang), applyI18nToDom for index.html data-i18n/-attr; reload-on-switch (see §16)
    i18n-zh.js / i18n-ja.js — dictionaries ({'English': '翻译'}, 869 entries each; missing key = English fallback)
    autocomplete.js    — Shared directory autocomplete (setupDirAutocomplete)
    settings.js        — SettingsManager (sparse storage, server persist, WS sync, event listeners)
    settings-schema.js — Settings schema (all options with types, defaults, categories)
    settings-ui.js     — SettingsUI (VS Code-style full settings, search + category nav). Opens as a NON-BLOCKING same-level WINDOW (type 'settings', singleton-focus, no openSpec = transient/not persisted) so you can edit a setting and watch the effect live — NOT a modal overlay (2.53.0)
public/
  brand/               — Backend brand SVGs (claude.svg, codex.svg)
  index.html           — HTML structure
  style.css            — CSS: themes, sidebar, toolbar, workspace, windows, taskbar, settings (~2300 lines)
  chat.css             — CSS: chat view, compact mode, role indicators (~600 lines)
  viewers.css          — CSS: file explorer, media viewer, editor, hex viewer (~300 lines)
  theme-editor.css     — CSS: theme editor panel (~100 lines)
  bundle.js            — Built output (do not edit)
CLAUDE.md              — This file
docs/
  README.md            — Documentation index
  getting-started.md   — Installation, first run, quick tour
  terminal.md          — Terminal: dtach, multi-device, clipboard, idle detection, fonts
  window-manager.md    — Windows: grid, snap, command mode, presets, highlight
  sessions.md          — Sessions: discovery, groups, star/archive, drag-drop, filters
  file-explorer.md     — Files: browsing, bookmarks, viewers, right-click, drag-drop
  settings.md          — Settings: global/per-terminal, complete reference table
  mounts.md            — Shared S3 storage: my-storage, share minting, import links
  customize-ui.md      — Customize mode guide: show/hide, drag between bars, springs, alignment, extra rows
  editor.md            — Ctrl+G split-pane CodeMirror editor
  browser.md           — Embedded browser with proxy mode
  keyboard-shortcuts.md — Complete shortcut reference
  screenshot-helper.js — JS sanitizer for privacy-safe screenshots
  screenshots/         — Documentation screenshots (overview, sidebar, settings, grid)

Developer Guide: Where to Find & Modify Code

Common Tasks → File Location Map

Task Primary File(s) Notes
Add/change terminal behavior src/lib/terminal.js xterm.js config, input filtering, cursor, font
Session list / sidebar src/lib/sidebar.js + src/lib/sidebar-state.js + src/lib/sidebar-render.js + src/lib/sidebar-render-mobile.js + src/lib/sidebar-workbench.js (Folders tab) + src/lib/sidebar-mounts.js (Remote tab) + src/lib/session-card.js Sidebar shell + state mixin + render mixin (desktop) + render mixin (mobile) + Folders-tab workbench + Remote-tab mounts + card factory
Backend metadata / icons src/lib/agent-meta.js Backend/agent meta, createBackendIcon, createModeBackendIcon, contrast adaptation
Session discovery (RUNNING/STOPPED) src/routes/sessions.js + src/session-store.js Lock-first algorithm, tmux detection, PID verification
Window tiling / grid / snap src/lib/window.js Drag, resize, grid cells, layout presets, freeform, Alt bypass, overlap switcher, pre-snap size memory, move mode
Tab groups src/lib/tab-group.js Drag icon-to-icon to merge windows into tabs, Chrome-style tab bar, drag-out to split
Custom grid presets src/lib/app.js_addCustomGrid() + src/routes/persistence.js + button adds, right-click removes, persisted in layouts.json
Session starring src/lib/sidebar.jstoggleStar(), isStarred() ★/☆ per session, starred first in sidebar + taskbar
Task system / board src/task-groups.js + src/lib/sidebar-tasks.js + src/lib/task-detail.js tasks.json store, board (Tasks tab), bind-as-tag, detail window, attention — design in docs/design-task-system.md
Workflow detail viewer src/lib/workflow-detail.js + src/routes/sessions.js (/api/workflow) + chat-renderers.js (View Workflow btn) Post-hoc dynamic-workflow/ultracode viewer; reads terminal wf_<runId>.json snapshot; per-agent View Log reuses subagent viewer
Session rename src/lib/sidebar.jsrenameSession() Double-click name in sidebar, syncs to open windows via app.syncSessionName()
Ctrl+G external editor server.jscreateEditorHelper() + src/lib/external-editor.js Fake "code" script + split-pane CodeMirror
File viewer (open file) src/lib/file-viewer.js Dispatch by type, size check, binary detection
Code editor src/lib/code-editor.js CodeMirror 6, language switching, Prettier/server-side format, markdown/HTML preview
Chat mode src/lib/chat-view.js (controller) + src/lib/chat-renderers.js (rendering) Virtual scroll, op dispatch, message rendering, tool cards
Chat input / send src/lib/chat-input.js Textarea, attachments, slash commands, draft persistence, TODO display
Chat status bar src/lib/chat-status-bar.js Model, context%, cost, permission mode dropdown, task popup
Chat search src/lib/chat-search.js Ctrl+F, CSS Custom Highlight API, server-side search
Chat minimap src/lib/chat-minimap.js Semantic scrollbar, turn markers, drag-to-jump
Hex viewer src/lib/hex-viewer.js Binary display with chunked loading
Themes / colors src/lib/themes.js + public/style.css 6 built-in themes, CSS variables [data-theme="..."]
Theme editor src/lib/theme-editor.js + public/theme-editor.css Floating panel: ~50 CSS vars + 16 ANSI, live preview, save/load/delete
Layout save/restore + sync src/lib/layout.js + src/ws-handler.js Auto-save debounce, layout-sync WS protocol, openSpec pattern
Virtual desktops src/lib/desktop-manager.js + src/ws-handler.js Create/delete/rename/switch desktops, Ubuntu-style previews, per-desktop grid, drag-to-move, multi-client sync
Usage / rate limits src/usage-routes.js + data/bin/vibespace-usage + src/lib/usage-meter.js PASSIVE statusline capture by default (§ban-safety); on-demand ⟳ + opt-in active poll + auto-cli — see §9 and the usage-routes.js entry
WebSocket protocol src/ws-handler.js + src/lib/ws.js All WS message types in ws-handler switch/case
State sync / drafts src/sync-store.js + src/lib/utils.jsStateSync class Versioned diff broadcast, reconnect recovery, draft persistence
Syntax highlighting src/lib/highlight.js + src/lib/chat-renderers.js 30 hljs languages, line numbers, language picker
File routes src/routes/files.js Browse, read, write, upload, download, clipboard paste
Persistence routes src/routes/persistence.js Layouts, bookmarks, themes, settings, user state, groups
Taskbar src/lib/taskbar.js Taskbar rendering, window list popup, right-click context menu
Command mode src/lib/command-mode.js Ctrl+\ prefix key, tmux-style window commands
Embedded browser src/lib/browser-window.js iframe + URL bar + proxy toggle
Shared utilities src/lib/utils.js escHtml, createPopover, showContextMenu, fetchJson, copyText, showImageOverlay (the ONE image-zoom overlay — chat thumbs + pending chips), StateSync
i18n / translations src/lib/i18n.js + i18n-zh.js/i18n-ja.js + scripts/i18n-extract.mjs t() wrapping rules + dictionary workflow in §16 — read it BEFORE adding UI strings
CSS / visual styling public/style.css + public/chat.css + public/viewers.css Split by component area
HTML structure public/index.html Sidebar, toolbar, workspace, dialogs

Architecture Patterns — INDEX

Full text: docs/kb-patterns.md (moved verbatim). Cross-cutting coding patterns — the mediator rule, one-time WS handlers, agentEnv() spawn sanitization, no-native-dialogs, drag rAF coalescing, uiScale drag conversions, popover/toast conventions, sidebar poll digest. Read before writing frontend chrome or a new spawn path. Index:

  • All frontend cross-class communication goes through App (mediator pattern). Classes receive a…
  • One-time WebSocket handler pattern: Register via ws.onGlobal(), match on type + sessionId,…
  • Debounced auto-save: LayoutManager.scheduleAutoSave() waits 2s after last change. Blocked by …
  • Resizer: Reusable component with inside: true mode for fixed-position elements (sidebar). Don'…
  • Layout restore: attachSession() returns winInfo synchronously (the DOM element). Position is…
  • Proportional bounds tracking: win.gridBounds stores position {left, top, width, height} as f…
  • Title-bar right-click = full window menu (2.212.0): showWindowContextMenu (taskbar.js, shared wi…
  • WebSocket reconnect re-attach: On WS reconnect, all active sessions are re-attached. Timeout ≠ d…
  • Layout sync (multi-client): State-based — full workspace state broadcast via layout-sync WS me…
  • Atomic openSpec: createWindow({ openSpec }) sets the openSpec before _notify() fires, ensuri…
  • ChatView module split: ChatView is the controller (virtual scroll, op dispatch). Rendering deleg…
  • Shared utilities: createPopover(anchor, className, opts) handles popover positioning/dedup/clo…
  • Agent sessions get a SANITIZED env, never raw process.env (2.227.12, agentEnv() in ws-handle…
  • Server-side settings reads use serverSetting(key) (server.js; ws-handler gets it via deps) — b…
  • No native dialogs: prompt()/alert()/confirm() are banned — use showInputDialog/showConfirmD…
  • Drag mousemove is rAF-coalesced everywhere (window.js titlebar drag + resize, tab-group icon dra…
  • Cursor→window positioning MUST convert viewport→workspace coords (2.100.3, trace-diagnosed real …
  • UI scale (DPI) + UI font size (2.257.0, per-DEVICE like the language): gs-menu rows writing loca…
  • Sidebar session poll (5s) pauses while document.hidden (30s heartbeat + immediate catch-up on …
  • Tab groups (chain model): Windows can be merged into tab groups by dragging one window's icon on…
  • Tab merge hit-test: Shared helper _detectTabMergeTarget(x, y, sourceWinId, hiddenEls) on tab-g…
  • Window type icons: Each window type has an inline SVG icon (TYPE_ICONS in tab-group.js): termi…
  • Move mode: Right-click taskbar → Move. Full-screen overlay blocks all UI interaction. Window res…
  • Loading screen: Inline splash in HTML (no CSS dependency). Fades out after app.ready promise r…
  • Resume-all boot popup (2.250.0): sessions that come back STOPPED restore as read-only history wi…

Server-Side Key Functions

Function Location Purpose
createEditorHelper() server.js Generates data/bin/code script on startup
attachToDtach() server.js Creates PTY bridge to dtach socket
restoreSessions() server.js Reconnects to surviving dtach sessions on startup
setupSessionPty() server.js Wires PTY onData (chat JSON parser or terminal raw) + onExit
refreshRateLimit() server.js OPT-IN active usage poll (accounts.activeUsagePolling, default OFF — default is passive capture, §9)
registerWsHandler() src/ws-handler.js All WebSocket message type handling (create/attach/kill/etc.)
cwdToProjectDir() src/session-store.js cwd.replace(/[/._]/g, '-') — deterministic encoding
recoverCwdFromProjDir() src/session-store.js Greedy reverse: try segments as-is, . prefix, _
SessionMessages src/session-store.js Unified JSONL+buffer access, chatStatus, taskState
SyncStore src/sync-store.js Versioned key-value store with diff broadcast
broadcastActiveSessions() server.js Pushes session list to all WebSocket clients

Key Design Decisions & Lessons Learned — INDEX

Full text: docs/kb-design-lessons.md (moved verbatim; §-numbers unchanged). Read the relevant § BEFORE touching that area; update it in the same commit.

  • §1 dtach for session persistence — dtach NOT tmux (zero rendering layer); dtach -c never -n; NEVER delete socket files (the socket IS the session); pty-wrapper tees output to the buffer file.
  • §2 Mouse/scroll — no middle layer; strip \e[I/\e[O focus events from onData; 6 tmux approaches failed (docs/history-archive.md).
  • §3 Ctrl+G external editor — fake code script (GUI-editor whitelist trick) + HTTP bridge; never print to the terminal from the helper; POST carries the vsst_ Bearer.
  • §4 Session discovery — LOCK-FIRST; claimJsonls exact→tail→mtime (mtime only over no-tail-evidence files); each lock claims ≤1 JSONL; status values tmux/external/stopped.
  • §5 Project path recovery — cwd→dir encoding replace(/[/._]/g,'-') is deterministic; the reverse is ambiguous, prefer forward.
  • §6/6a/6b/6c Layout — autosave/restore + gridBounds fractions; custom grid presets; MULTI-CLIENT sync anti-ping-pong (4 guards: seq, user-dirty w/ 60s expiry, defer-while-interacting, rounding epsilon — do not regress ANY); virtual desktops (visibility:hidden model, purge-vs-preserve on close).
  • §7/7a Modular frontend + mobile — App mediator; isMobile/isTouch; long-press=contextmenu; mobile yield-sidebar is CENTRAL.
  • §8/8a/8b xterm + rendering — WebGL renderer (do not remove), clearTextureAtlas on font change, alt-screen awareness, query-response arbitration, self-hosted fonts (never third-party origin); hljs span-split fix; 交付夜 invariants (self-update dialog, /dav DAV class 2, archive ops, VNC probe retry, mountpoint hygiene ladder).
  • §9 Usage / rate limits — §ban-safety home — DEFAULT = passive statusline capture; active poll is OPT-IN (accounts.activeUsagePolling); read-only token NEVER refreshes; OAuth Bearer + anthropic-beta header; approach history in docs/history-archive.md.
  • §10 Settings — schema-driven; only add settings with working code; serverSetting() server-side (never the SyncStore).
  • §11 Chat mode dual-arch — THE big one: chat-wrapper stream-json, permission stdio protocol, subagent virtual sessions, workflow viewer, goal loop (claude native + codex thread/goal RPC), interrupt delayed-fallback, model/effort/permission mid-session switches, model lock v2, fallback policy, task wakeups. Read §11 in the kb before ANY chat-pipeline change.
  • §12 Font discovery — queryLocalFonts → fc-list fallback; client fonts are what matter.
  • §13 StateSync — versioned diff broadcast + reconnect resync; register stores before set() (writes silently drop otherwise).
  • §14 Syntax highlighting — 30 langs, EXT_TO_LANG, splitHighlightedLines carries the span stack.
  • §15 Frontend optimization — minify+gzip; delivery-stall watchdog; stale-tab reload is PER-TAB (sessionStorage); index.html route injects ?v=mtime cache-busting.
  • §16 i18n — see Laws digest above + kb §16 for the full wrapping rules.
  • §17 UI design conventions — see Laws digest above + kb §17 for the token/radius/type tables.
  • 2026-06-09 review invariants (v2.8.0) + 2026-07-03 review invariants — two do-NOT-regress lists (escHtml quotes, atomic JSON writes, AbortController listeners, XSS rules, one-time WS handler self-guards…). In the kb.

API Reference — INDEX

Full REST + WebSocket reference: docs/kb-api.md (moved verbatim). Adding/changing a route or WS message type ⇒ update kb-api.md in the same commit. Quick shape: REST under /api/* (files, sessions, usage, accounts, hosts, mounts, tasks, agent-facing vsst_ endpoints), WS /ws (create/attach/input/kill + msg/layout-sync/state-sync families), /dav WebDAV, /proxy.

Features Summary — INDEX

Full behavior reference: docs/kb-features.md (moved verbatim). These sections describe SHIPPED behavior with embedded do-not-regress notes — read the relevant one before changing that surface.

  • Deployment & Onboarding — auth (password/Clerk SSO), Docker/K8s/Helm, onboarding wizard, config export/import (4-touch-point gotcha), Manage Agents, account switching + billing identity + verdicts (the accounts mega-essay), chrome customization/springs/zones, toolbar scale, mounts/storage rows, remote hosts + keeper, orphan sweep, kill-path teardown, async discovery sweep.
  • Terminal Management — dtach sessions, per-terminal settings, bell/idle detection, clipboard image paste, CJK, multi-device size, contrast.
  • Chat Mode — message rendering, permission cards, relative-path linkify, metadata popup, virtual scroll, minimap, search, drafts, uploads, resume bar, read-only views.
  • Window Manager — snap/grid/presets, shake-bypass, tab groups, move mode, virtual desktops, taskbar stacking, resume-all boot popup.
  • Session Management — Task Groups (岗位) refactor + backlog claim model + status/urgency + injection contracts + repo task files + remote ctx sync + group admin + New-Session prefills + right-click menu + Session Properties + per-session config. The 2.39.0 concept-refactor block SUPERSEDES older bullets where they conflict.
  • File Management — explorer, multi-select, clipboard ops, archives, viewers (PDF/DOCX/XLSX/PPTX/CSV/hex/eml), uploads, cross-host.
  • UI — themes, theme editor, embedded browser + proxy normalization, window-list popup.

claude CLI 中途文本丢失 (2026-07-13, 上游bug — 产品无辜, 勿再向内排查)

stream-json 下 assistant 的 thinking→text→thinking→tool_use 三明治形状响应, 其 text 块会在长寿会话中被 CLI 从 stdout 流和 JSONL 转录双双丢弃(模型上下文保留 → 永久分叉; 全新会话不复现, 连同形状都不复现; wrapper/normalizer/renderer 逐层验证无辜)。用户视角="agent 没回复"。取证档案+判别矩阵+issue草稿: ~/workspace/AIWorkspace/SharedContext/claude-cli-text-loss.md。Agent 准则: 实质内容只放 turn 末尾消息(其后无工具调用), 中途只发可丢弃状态行。

  • 2.284.0 field-test batch invariants: message-meta billing row is THREE-state honest (host-namespaced h:<id>: rid suffix-match / not-yet-harvested says so + session identity / no-requestId falls back to session-level — never a silently missing row); CLAUDE_KNOWN_MODELS = the GA baseline the dropdown always carries (passive discovery only learns locally-SERVED statusline models — Opus 5 was invisible until served here; noteModelSeen also feeds models.json from EVERY session's served model); customize-mode taskbar pill is BODY-FIXED measured off the live bar rect (a bar-child with negative offsets hides under extra rows when docked top); .cz-overlay dims with theme-agnostic black (a var(--bg) mix is invisible on dark themes — same class as the no-global-.hidden rule); #taskbar-row2 belongs in the pinned-defaults var block (extra rows are NEUTRAL zones — missing it made the same element size differently top vs bottom); extra rows re-dispatch contextmenu onto their parent bar. 2.284.1: per-message billing joins on message.id — live stdout records carry NO requestId (CLI behavior; only the transcript copy has it), so the popup lookup queries rid AND mid; eventForMid joins mid-field/rid-fallback/host-namespaced; the remote scanner emits mid (parity-asserted per event). NO billing data is ever lost by a missing requestId — the ledger mines transcripts. Model baseline labels carry NO context-size claims (a claude-opus-5 session served 222k/1M under a "(200k)" label). 2.284.2: api_retry system records feed the SPINNER LABEL ("API retrying (3/10, HTTP 500)…"), deliberately card-less — a fleet API-500 burst read as "everything stuck on thinking" because the subtype was silently dropped (the 2.227.5 invisible-record class again). Degrade-gracefully catches can swallow their own bugs: the 2.276.0 local writer sweep referenced shq out of scope and its own catch hid the ReferenceError for 6 releases — a degrade path that logs the message VERBATIM is the only reason it was ever found; never catch broader than the failure you designed for.

Bug Fixes Applied — INDEX

Full essays: docs/kb-bugfix-invariants.md (moved verbatim; ancient one-liners in docs/history-archive.md). Each entry is an incident whose FIX carries invariants — search here before re-diagnosing a familiar symptom. Index:

  • CAPABILITY GATE READ THE WRONG FILE (2.364.1, owner三次Terminate+Resume无效): 2.361.1帧文件能力门从data/session-meta(服务器记录, 从无caps)读caps, 真caps在wrapper sidecar data/session-buffers/.json → 所有wrapper都判'旧', 旁路死两版, >1MB粘贴全拒且拒收文案指错方向; 修复=wrapperCaps()唯一读者(sidecar+碰撞解析器, 无状态), 只缓存正向判定, 拒收带证据+进journal; 不变量=进程宣告的能力只从该进程写的文件读, 负判定不缓存, 拒收必须举证, 读者测试要对着真写者的文件+负控, 报告与解释矛盾时解释错…
  • PROXIED POST EATEN BY BODY PARSER (2.363.2, inc-mt2bpw2f userW): express.json在unblocker之上吞掉代理POST的body→目标端等Content-Length到超时, GET无恙所以页面渲染正常但按钮全死; 修复=json parser跳过/proxy/(unblocker保持在auth之后, 严禁open proxy); 不变量=流式中间件必须拿到原始请求, 全局body parser与任何pass-through路由互斥…
  • SEND REFUSAL READ AS ATTACH FAILURE (2.363.1, inc-mt2arppw userW): chat-input拒收被client当attach失败→活窗口翻Resume bar+拒收文本在error字段没人读; 修复=code:'input-rejected'分流+双字段+ring补记error; 不变量=一个error类型多语义必须带code分流…
  • PEER MESSAGE INVISIBLE ON LIVE STREAM (2.362.2+2.363.0, inc-mt27t0bg userW+owner): CLI收站内信stdout从不带user记录(仅JSONL), 会话发的挖result.origin补渲染; SERVER发的(jobs notify/vibespace-msg)在CLI侧连origin都body-less — 投递梯自己渲染卡片(emitPeerCard); 不变量=可见性归属掌握信息的一方…
  • UNSTAGED WIRING (2.355.0, userW复报): 2.331.0纯函数+单测入库但调用点没staged — 修复死了24个版本而单测常绿; 纯函数修复必须配WIRING PIN(套件grep调用点)…
  • MANUAL POOL HOT-SWITCH DEMOTED (2.355.0, userW热切换): plan C per-session links优先级盖过手动切换面 — 新间接层压过旧控制面时, 旧面每个writer都要重审为新层consumer…
  • NOTIFY-CRON NEVER WOKE ITS OWNER AGENT (2.361.5): notify action只投user inbox+被动组事件, _notifyOwner从未被调用; 修复=接上标准owner投递栈+每job notifyLog投递日志环(面板Delivery log可监控)…
  • RUN+ECHO REMINDER = SILENT NO-OP (2.361.4): 定时裸echo因quiet-success law永久静默+创建响应误导; 修复=该形状默认notifyOk+创建响应明说per-fire语义; 不变量=常见直觉形状要么让它工作要么大声拒绝, 绝不accept-and-ignore…
  • AGENT-TIME IS UTC, BARE DATETIMES ARE SERVER-LOCAL (2.361.3): --at裸时间按服务器本地时区解析害agent追查幻影'通知链路坏了'; 修复=相对形式+2m+创建回显resolved fire time…
  • MONTHLY-SPEND-CAP REJECT UNMARKED (2.361.2): seven_day_overage_included被映射成'other'→周桶+月度cap双死成员7d缓存仍0.53被池反复挑中; 修复=映射到sevenDay+按事件resetsAt标死…
  • WRAPPER VERSION SKEW ATE IMAGE PASTES (2.361.1, c1206711): 2.360.0帧文件旁路对旧wrapper会话发指针行→CLI静默丢弃; 修复=wrapper boot meta广播caps.frameFile, 服务器按能力门控…
  • HOT-SWITCH STALE-TOKEN MISATTRIBUTION (2.361.0, B-345b定案): 池热切换对运行中CLI不生效(≥25min) — 链接意图记账双向错位毒化odometer+学习率(爆发层-30~-49%); 修复=OTel每请求真值通道…
  • SCHEMA-TWIN FALSY-0 REVERT (2.360.1, inc-mt0mozsp): Manage Agents instructions tab held a pre-2.210.0 bounds copy + Number(v)||dflt — explicit 0 (every-stop mode) silently…
  • PROXY-SWALLOWED ASSIGNMENT (2.343.0, 7th): lazy.js get-only Proxy ate portForwards.plugins= since 拆分#12 — publish dead 17 releases; set trap + test-lazy gate; 跨seam赋值必须落在真实例…
  • RELATIVE-PATH LOST BINDING (2.341.1, 6th): extraction carried require('./package.json') into src/server/ — call-time throw, all gates blind; test-architecture #8 now statically resolves every re…
  • LATENT LOST BINDING (2.340.2, 5th): free identifier swallowed by its own catch — 'capture failed: X is not defined' logs, dead 3 days; grep journals for is-not-defined after every extraction…
  • STUCK-THINKING AFTER RESTART (2.339.2): socket-derived wrapper-file reads miss counter-collision sessions — resolve via wrapper argv; attach reconciles streaming vs sidecar…
  • TYPING-DRIVEN PAGING SELF-FEED (2.338.0, Windows freeze audit): autosize thrash → anchoring drift → ungated _extendTop paged 50/bounce; every ex…
  • Three-tier closure marathon (2.297.0–2.300.0, owner directive "一次性完全做完"; invariants, do not regress): ① script…
  • PAGING INTENT GATE (2.307.0, inc-msorcsrl — the mechanism two earlier fixes missed): after an upward page, con…
  • PAGING BOUNCE / COLLAPSED GEOMETRY (2.301.0→2.306.0, inc-mso818ry + inc-msor3oax): while content-visibility le…
  • SCOPED-BUCKET MERGE (2.305.0, inc-msof8i22 — the pool refused to leave an account whose Opus was spent): model…
  • COUNTER CAPTURE (2.302.0, proven in fleet production data): a monotonic counter's value is CAPTURED ONCE and r…
  • B-b87b batch (2.267.0 — all 16 confirmed findings from the 31-agent global review; do not regress): ①dial-sess…
  • Display cwd must never reach a spawn (2.225.2, userL h200 real incident): the sidebar merge composes remote we…
  • Restart-survival audit batch (2.218.0-2.219.0, user directive '整体review所有不抗重启处'; full 54-finding report: Share…
  • Dead-session replay = blank window shell (2.217.0, userL ×12 real report; user directive: restart后必须能看read-onl…
  • Resume guard (2.179.0, userW's duplicate-session incident): a plain claude --resume REUSES the conversation …
  • Username migration must also re-encode claude's project dirs (2.236.1, userW's real incident): the 3.5.0 image…
  • Changing the container USER ripples to EVERY kubectl exec (2.162.7 regression): the 3.5.0 personalized-usern…
  • Dial-device (paired) sessions — FULLY FIXED + E2E-verified (B-0d70, 2.163.0–2.164.1; scripts/dbg-dial-session-…
  • #workspace must be overflow:clip, NEVER a scroller (2.161.2, three-iteration live-tracer diagnosis): overflow:…
  • Permission/AskUserQuestion resolution must survive restarts WITHOUT the control_response (2.109.5, real report…
  • SILENT TRANSCRIPT LOSS (2026-07-03, CRITICAL): sessions spawned by a server that was itself started from INSID…
  • Chat status bar empty until first reply after resume/attach: model + contextWindow came only from result.mode…
  • Clipboard image paste failures: (1) paste event never fires — xterm.js v5 uses Clipboard API directly, bypas…
  • Spontaneous terminal shrink + disconnect (mid-use, no user action): resizeSessionToMin min'd over ALL sessi…
  • Dead settings in schema: removed 9 settings defined in settings-schema.js but never read by any code (enable…
  • Tab merge hit-test matched occluded icons: dragging A's titlebar onto A's own position would merge with a stac…
  • /compact leaving chat stuck on 'thinking': stream-json emits user messages (compact summary) but no resul…
  • isStreaming derived from stale normalizer messages: old heuristic scanned normalizer messages for status==='s…
  • Huge sessions (>512MB JSONL) — tail-only load + continuous seek scroll (no seam marker): readFileSync(fp,'utf…
  • Codex thread vanished from session list permanently: a transient IO error in extractCodexThreadMeta was swal…
  • Stale openSpec replay → blank chat window with outdated title: a window whose dtach session died and was resum…
  • Chat drag-upload overlay permanently visible ("won't disappear"): this project has NO global .hidden {display…
  • Claude fork behaved identically to resume in chat: the WebUI never captured Claude's stream-json session_id
  • Minimap/search accuracy (huge sessions, reconnect, gap slabs) — five related fixes + the 2.286.1 sixth, keep A…
  • CodeMirror active-line highlight hid the selection on the first/last selected line: highlightActiveLine (fro…