Architecture deep-dive: the 87 techniques actually in Talkak's codebase — each with the incident behind it
Not the bird's-eye view — the full inventory. A six-agent scan read the entire codebase and extracted every real technique in production, subsystem by subsystem: PTY/process, terminal rendering, layout/keybindings, agent augmentation, the connective graph, and the release pipeline. Almost every entry exists because something concretely broke. It all reduces to three recurring patterns: make silent failure visible, put mechanisms outside the thing that forgets, and demand receipts for every number.
Compiled from a six-agent full-codebase scan (each agent read the actual source; every entry carries the mechanism and, where it applies, the incident that forced it). ★ = most load-bearing. The high-level companion: Talkak's core architecture (2026-06-10).
Part 1 — PTY & process layer (Rust)
★ 1. The bash -c error wrapper. tmux is never exec'd bare; it runs inside /bin/bash -c "<tmux…> 2>&1; on failure echo the code + resolved path; exec $SHELL". A failing spawn prints a readable error into the pane and drops to a shell for debugging. Why: inside a GUI bundle's minimal PATH, a bare spawn dies silently — the user sees a frozen blank pane.
★ 2. A dedicated tmux server socket (-L dalkkak). Every tmux call uses a private, app-owned server — parented to launchd, so it outlives the app: relaunch just re-attaches. Why: panes once landed on the user's default tmux server — a days-old daemon another terminal had started, which lacked macOS Documents (TCC) permission; ls ~/Documents failed inside panes while the app itself could read the repo.
★ 3. new-session -A -D -s dalkkak-<paneId>. Deterministic session name per pane; -A attach-or-create (this IS crash recovery), -D detaches the dead pre-crash client; -c <dir> only when the cwd validates as a real directory. Why: tmux must be the single recovery mechanism — a renderer-side auto-claude --resume once corrupted live sessions that tmux had already recovered.
4. Healing the live tmux server's global env on every spawn. Idempotently unset every npm_config_* from the server's global env + mouse off + status off. Why: the server is long-lived and remembers its first environment — fixing only the spawn-process env never reaches it. (The npm_config_prefix → nvm refuses to load → codex: command not found incident: the code fix "didn't work" until the live server was mutated.)
★ 5. Three-move child env hygiene. Explicitly SET TERM/COLORTERM; FORWARD exactly ten interactive-shell vars; SUBTRACT all npm_config_* (count logged). Why: portable-pty seeds the child env from the current process — launch via pnpm tauri dev and pnpm's vars leak into every pane. Env hygiene is addition and subtraction.
6. PATH augmentation + wrapper-dir prepend. /opt/homebrew/bin:/usr/local/bin prepended (dedup-guarded), and ahead of those, the app's own bin dir holding the claude wrapper — so the wrapper wins only inside Talkak panes.
7. DALKKAK_PANE_ID as tmux per-session env (-e). Every process in the pane — claude, and the hook scripts claude spawns — knows its pane id. This one var powers event attribution and the per-pane gate scoping.
★ 8. Blocking read loop on a dedicated OS thread. 4096-byte reads, from_utf8_lossy (split multibyte never panics), each chunk emitted as a pty-output event. Three logged exit paths — EOF, read error, and emit failure (webview gone) → the loop kills itself instead of pumping a dead window.
9. A visible EOF marker. read() == 0 prints a yellow [pane closed] before the stream ends. A silently frozen pane is the worst UX — you can't tell dead from slow.
10. UTF-8 locale backfill. If inherited LANG isn't UTF-8 (in a Finder-launched app it's usually absent), set LANG/LC_CTYPE=en_US.UTF-8 — but never LC_ALL, which would steamroll a user's own locale. Why: Hangul rendered as __ per syllable until the bytes were allowed through.
11. find_tmux(): absolute candidates before PATH. Probe /opt/homebrew/bin → /usr/local/bin → /usr/bin → /opt/local/bin; PATH lookup is the last resort; the chosen path is logged and printed in error messages.
12. Pathological PTY-size guard. cols/rows of 0 → 80×24.
13. Scroll mode = state-queried copy-mode toggle. Ask tmux #{pane_in_mode}, then cancel or copy-mode -u (enters and pages up in one call — without -u the cursor starts at the bottom and the viewport won't move). Why: mouse on flooded the webview with tracking escapes and broke copy-on-select; wheel scrollback was abandoned for this.
14. pty_kill: two-layer teardown, explicit-close only. Kill the PTY child and tmux kill-session (killing only the client leaves a zombie session). Never called on app quit — that asymmetry is the persistence design.
15. Orphan-session GC: double-guarded, now disabled. It reaped sessions that were detached and absent from every saved layout — but a layout-format change made loads fail, the live-set came back empty, and it nearly killed in-progress work. Policy now in a 20-line comment: a few lingering sessions beat ever auto-killing live work.
Part 2 — Terminal rendering & registry (TypeScript)
★ 16. Module-scoped terminal registry. xterm objects, PTY subscriptions, and buffers live in a plain module Map — outside React. Remount all you want; the terminal loses nothing.
★ 17. Remount re-attach moves the DOM node. If a terminal was opened once, its existing DOM subtree is appendChild-ed into the new container — scrollback, cursor, running TUI physically relocated; term.open() runs only on first mount. Effect cleanup deliberately disposes nothing.
18. PTY subscriptions bound to pane id, torn down only by explicit action. Exactly three callers ever destroy a terminal: ⌘W, Reset, startup delete.
★ 19. Hot-path split. Every chunk hits term.write immediately; parsing is deferred — chunks accumulate and a single 250ms flush feeds the parser once and ships all events in ONE IPC call. Parser exceptions are caught; rendering can't break. Why: per-chunk invoke across many verbose panes was a measured cost.
20. Focus-reporting escape stripping (input path). When a TUI enables DECSET ?1004, xterm emits CSI I/CSI O on focus changes; written to the PTY, the bare ESC reads as interrupt to Claude's TUI. Exactly those two sequences are dropped.
★ 21. Copy-on-select pipeline. Selection auto-copies (no ⌘C), debounced 120ms to the settled selection; the copy is cleaned (frame glyphs stripped) while the on-screen selection is untouched. The cleaning rules live in a pure, test-locked module.
22. Click-a-colored-run-to-copy. Per-cell foreground inspection turns colored runs (commands, paths, hashes) into copy links. Runs containing box-drawing chars are never linkable — the "──── ultracode ─" banner fix, after three wrong attempts on the wrong code path.
23. WebGL focused-only — currently parked. Structurally ≤1 GPU context (follows focus), context-loss falls back to the DOM renderer ("never a blank pane")… and none of it is currently called: WebGL provably broke Korean IME composition in WKWebView, and Korean input outranks GPU smoothness.
24. Resize debounce. ResizeObserver storms (dozens per drag) coalesce into one fit() + pty_resize per 120ms.
25. Idempotent spawn with a pre-await race guard. spawned = true is set before the await — two same-tick callers can't double-spawn; failure resets the flag for retry.
26. Window-focus re-grab with an overlay guard. Returning from another app/Space, xterm's hidden textarea has lost focus and "typing goes nowhere" — a window listener re-grabs it, unless a real input field (an open overlay) is active.
27. Two-tier clipboard + a quiet toast. navigator.clipboard first; WKWebView refusal falls back to a hidden textarea + execCommand (restoring the stolen focus). Success shows a small ✓ pill with a ≤34-char snippet of what was copied.
28. autoResume neutered. tmux persistence IS the recovery; the renderer typing claude --resume into recovered sessions was corruption, not help.
29. Construction defaults. Font fallback includes Apple SD Gothic Neo (no Hangul tofu), ClipboardAddon for OSC52 (tmux set-clipboard reaches the system clipboard through the webview), 10k scrollback.
Part 3 — Layout, keybindings & startups
★ 30. The layoutOwner cross-write guard. The layout tree travels with its owning startup id; the save effect persists only when owner == active. Why: for one commit after every switch, a stale closure held {new id, old tree} and wrote startup A's panes under B's key — quit inside that window and two startups permanently shared the same live terminals (confirmed by two adversarial reviews; the race had been described in a 5/26 post-mortem whose fix covered only the null variant, and its wrong comment protected the bug).
★ 31. Boot sanitizer: a pane id belongs to exactly one startup. Before first render, scan all saved layouts; first claim wins, later layouts get fresh leaves. Already-corrupted installs self-heal on next launch.
★ 32. Pure chord guards + the full-consumption contract. Predicates (isStartupSwitchChord …) extracted to a test-locked module; any claimed chord is consumed unconditionally — preventDefault + stopPropagation even on the out-of-range no-op path. Why: Ctrl+3 is ESC in a terminal; the leak silently interrupted Claude for a full day.
33. Arc-style modifier split. Ctrl = startups (spaces), Cmd = panes (tabs). Ctrl-arrows belong to macOS Spaces, so startup-walk is ⌘⌥-arrows — colliding with a macOS default is an instant kill, not a tradeoff.
34. One capture-phase keydown hub. Registered with capture: true so app chords are evaluated before xterm can serialize them; first line bails on unmodified keys (plain typing costs one boolean). Ordering is explicit (⌘⇧I before ⌘I; ⌘I requires !shiftKey).
35. Per-startup layout persistence with shape-validated loads. dalkkak.layout.<id>.v1; reads accept only a string leaf or a complete {direction, first, second} — anything else (old formats, garbage) returns null instead of poisoning state; saves never accept null.
36. Synchronous layout seeding. Fresh layouts are persisted to localStorage synchronously, before setState in both places that create them — the null variant of the cross-write race, closed.
37. Per-startup folder grants through one Rust chokepoint. Explicit folder picker → PathAllowlist (canonicalized). Never a blanket disk permission.
38. Ownership registration walks ALL stored layouts — background startups' panes keep their graph-push attribution, not just the active one's.
39. No tmux GC — sessions die only by explicit user action. The data-survival rule, written into a long comment where the GC used to be.
40. The mosaic tree as plain recursive data. Three ~12-line pure functions (collectIds / replaceLeaf / removeLeaf) instead of library utilities — which also lets the boot sanitizer manipulate trees without importing React or mosaic. Closing the last pane generates a fresh leaf; the workspace can't go empty.
41. One-shot legacy migration. The pre-multi-startup single layout moves under the first startup's key (skipped if occupied — re-run safe).
42. Startup delete = explicit destructive cascade. Confirm (names the consequence), destroy every pane from the stored layout (background sessions included), remove the key, hand off focus.
43. An in-app InputDialog, because window.prompt returns null in a Tauri webview — a discovered platform gap; the native path was a silent no-op.
Part 4 — Agent augmentation & AI (BYO)
★ 44. The PATH-shadowing claude wrapper — the platform's lever. A script in the app's bin dir execs the real claude with --append-system-prompt "$(cat dk-directive.txt)". One directive file = behavioral change for every agent in every startup; outside Talkak panes, the user's claude is untouched.
★ 45. The four-section DIRECTIVE. Honesty (verified-only, tag inference, quote outputs), English polish (You wrote / Polished), graph push (dk-node — durable facts only, ≤1 per turn, never distort the work), and the inline summary card contract (dk-summary + verify).
46. An append-only, marker-guarded ~/.zshrc function. A shell function beats PATH when the user's rc re-prepends its own bins; marker-guarded against double-install, backed up once, and never read-and-rewritten — only appended. A user's dotfile is the one file you can destroy; safety is structural.
★ 47. Summaries read from the transcript, never the TUI stream. TUI redraws merge and split text unpredictably (a redraw once turned "Your home directory (" into a bogus "Yourhomedirectory(" match). The transcript JSONL on disk is the machine-readable source of truth.
48. A lenient first-JSON parser with graceful degradation. Parse the first complete JSON value and ignore trailing junk (observed in the wild: a stray "</parameter>}"); on failure, fall back to the previous good card. "The parser is the durable floor" — assume the model will occasionally emit malformed output.
★ 49. The BYO headless claude -p pattern. One-shot calls (phrase correction, summaries) run the user's own claude: API-key env vars removed (forcing their auth), neutral cwd, JSON envelope error check, timeouts.
50. Per-call model pinning + MCP cold-start kill switch. Summaries pin haiku (cheap/fast); correction pins opus (quality; slow is fine; 150s). --strict-mcp-config with no config loads ZERO MCP servers — without it every call paid ~30s of the user's MCP cold-starts.
★ 51. The data-not-instruction prompt rule. User text embedded in an LLM prompt is content to transform, never a request to fulfill — with the actual incident ("give me the delete command" → it answered rm -rf) baked into the prompt as the counter-example, and <<<>>> delimiters around all fields.
52. Real-user-prompt detection. Transcript type:"user" lines include tool results; a real human prompt has string content or no tool_result block. Keystone for turn boundaries, usage splits, and the chat view.
53. Session-vs-turn token accounting; no fabricated dollars. Lifetime totals and "since the last real prompt" are split; dollars are never shown — on a subscription there is no per-token bill, so a $ figure would be invented.
54. Transcript → chat log (⌘L). Assistant messages between human prompts merge into one entry (+ tools used); AskUserQuestion blocks become {question, options, chosen} via tool_use-id matching with an anchored label check; dk-summary blocks are extracted into per-turn recaps rather than just stripped.
55. Codex usage with zero network. Read the newest rollout-*.jsonl under ~/.codex/sessions/**, find the last rate_limits snapshot — searched recursively anywhere in the JSON tree, defensive against the undocumented format moving.
56. Claude usage: Keychain OAuth → the same endpoint claude.ai uses; fallback self-calibrates. Token read from the macOS Keychain (first read = an explicit user consent prompt; never logged), then a usage query (zero tokens spent). Any failure falls back to a local 7-day transcript estimate whose 5h gauge calibrates against the user's own peak window — no fabricated official cap.
57. Fail-soft cached usage snapshots. The usage command never errors (unreadable providers come back null — the strip always paints), 60s TTL cache, heavy scans on a blocking-pool thread.
58. StreamParser for live activity. Line-buffered, ANSI-stripped heuristics; spinner lines outrank tool lines (the TUI-merge incident again). Live state only — anything precise comes from hooks + transcripts.
59. The summarizer prompt as a versioned file artifact. include_str!-ed from a maintained markdown file with an ordered kind-selection algorithm ("a waiting AI is what the founder most needs to see") and an explicit transcript-noise ignore list (the source of earlier hallucinated summaries).
Part 5 — Connective layer & hooks
★ 60. PathAllowlist: one canonicalizing chokepoint for every project-path access — symlink-resolved, hard-refusing anything ungranted, never falling back to $HOME.
★ 61. Git→change-node capture (PULL). A 20s read-only poll (git log never takes index.lock) turns new commits into confirmed change nodes whose evidence is the re-verifiable hash.
★ 62. GraphStore: append-only JSONL per startup, O(1) idempotent dedup on a hydrated node_id set. Old records are never rewritten.
63. A locked schema with a joinability policy. schema_version, additive-only fields, readers tolerate unknown fields, version-dispatch on read, node_id as the eternal join key.
64. Dual write-side validation (TypeScript validate.ts mirrored in Rust): empty titles rejected, three provenances only, confirmed requires evidence — "an inference can never masquerade as fact" is mechanical.
★ 65. Agent PUSH = an offset-persisted transcript scanner. A 20s worker maps pane→transcript (hook stream) →startup (renderer-registered), scans incrementally from byte offsets persisted to disk (restarts rescan nothing), consuming only to the last newline.
66. Hand-rolled FNV-1a 64. node_ids must be deterministic forever; std's hasher is seeded per-process. Ten lines, reference constants locked in tests.
67. Agent-node hardening. change stays git-exclusive; evidence.source is force-set to "agent" (spoof overwritten); titles/bodies clamped.
★ 68. Session events travel as a tailed FILE, not a localhost port. Hooks append one JSON line per event; Rust tails new lines every 400ms. No port conflicts, survives restarts, coexists with user hooks. (Pre-history: TUI scraping was proven unworkable — "not a tuning problem.")
69. recover_sessions(): fold to the latest line per pane. The live tail starts at EOF, so restarts would lose pane→transcript mappings (⌘L, ⌘I, usage all need them) — one full-file fold restores them; the same reader feeds the graph-push worker.
70. tauri::async_runtime::spawn, never raw tokio::spawn in setup(). The setup closure has no entered tokio runtime; a raw spawn aborts the app at launch (SIGABRT before any UI). Learned, logged, pinned as a convention.
★ 71. Usage Pulse: zero storage, read-time rollups, honest units. Re-aggregates hook stream + transcripts + graph on open (a persisted pipeline is deferred behind a falsifiable trigger). Output tokens only — counting cache reads would inflate ~200× (a real 305M:1.6M trap). Attribution is longest-prefix cwd matching; ambiguity becomes a visible unassigned row — never guessed, never dropped.
72. Rolling user-data backups outside WebKit storage. Every launch snapshots the personal stores into the app-data dir (never snapshotting emptiness — no overwriting good backups with a wiped state); timestamp filenames make lexicographic = chronological; keep 10.
★ 73. Two Stop-hook gates, fail-open. Honesty: a done-claim regex (KO+EN) with zero evidence markers → block. Summary presence: in a Talkak pane, >200 chars without <dk-summary> → block. Every error path passes (a crashed gate must never lock a session); stop_hook_active prevents re-block loops; the hook itself is under eight tests in the build gate.
74. GraphPanel: a deliberately dumb read surface — one invoke, client-side sort, provenance color badges (confirmed/inferred/hypothesis). Its job is to prove capture → store → read end-to-end; trust level is always visible.
Part 6 — Build, release & quality
★ 75. The test-gated build. build = vitest run && tsc && vite build, and tauri's beforeBuildCommand is pnpm build — the only path to a bundle is through the tests. Proven with a deliberately failing test: exit 1, dist untouched.
★ 76. The regression-tracking loop. One machine-parseable line per regression (caught by: user|me|test|hook) + a weekly stats script with a trend column. Baseline week: 6 regressions, all user-caught. Win condition: user → 0.
★ 77. Incident-pinned test suites. Each file names the dated incident it locks, and fixtures are field artifacts: the literally corrupted layout tree, the literal "──── ultracode ─" banner, an exhaustive sweep of every possible chunk-boundary split of the dk-summary block. Pure modules were extracted and the production call sites switched over — tests lock the real logic, not a copy.
78. Tests live outside tsconfig's include. Production tsc never compiles them; vitest transpiles independently; the node environment lets tests spawn the real hook script as a child process.
★ 79. Minisign-signed updates, end to end. createUpdaterArtifacts emits a signed tar.gz; the public key ships inside the binary; latest.json + artifacts are committed into the landing folder and statically served — git push is the release distribution. Clients verify against the embedded key before installing.
80. A fail-soft, consent-first update check. One check at launch; a newer build asks (with release notes) before download-install-relaunch; any error is swallowed — an update check must never hold app launch hostage.
81. Version & release conventions. The shipping version lives in tauri.conf.json; one release commit renames the artifact, rewrites latest.json, and bumps the version atomically — the release state can't be half-published.
82. INVARIANTS.md — area-indexed durable memory. "Touching keybindings? Check these rows." Every row is something that actually broke once, with the date. Rows that can be automated graduate into tests or hooks; the file covers the rest.
83. Daily-rolling, non-blocking runtime tracing. runtime.log.YYYY-MM-DD, writes never stall the app, the worker guard is deliberately leaked for process lifetime. It earned its keep: a launch SIGABRT was localized in seconds because the log showed init but never "setup complete".
84. The entitlement gate: fail-open, env-testable, swappable. A provider trait (stub → real backend later); DALKKAK_ENTITLEMENT=trial|expired|subscribed exercises every paywall state with no backend; backend errors mean entitled — the worst failure mode must be free usage, never a locked-out paying user; and nothing paywalls before payment actually exists.
85. A minimal capability allowlist — six permissions total. Dialogs, updater, restart, core. No fs, no shell: a webview that renders arbitrary terminal output, if compromised, can only call the audited command set.
86. Info.plist TCC usage descriptions. Declaring Documents/Desktop/Downloads usage turns macOS's silent denial into a one-time prompt — the consumer-appropriate alternative to Full Disk Access.
87. The honest-metrics contract (system pulse). Every figure is obtained "the same way Activity Monitor does" (sysctl + ps RSS over each pane's exact PID subtree), and the limits are documented in the module header: macOS exposes no per-process swap, so swap is reported system-wide and never attributed to a session. A cargo test runs the real collector on the live machine.
The three patterns underneath
All 87 reduce to three recurring moves:
- Make silent failure visible. The bash wrapper, the EOF marker, the
unassignedrow, TCC prompts, three logged exit paths. Silence is the worst failure mode. - Put the mechanism outside the thing that forgets. Hooks outside the model, the test gate outside discipline, the sanitizer outside the race, INVARIANTS outside the context window, signatures outside trust.
- Receipts for every number. Provenance + evidence, output-tokens-only, no fabricated dollars or caps, commit hashes you can re-verify.
Everything else is an application of those three.