Five independently-shippable items surfaced by the 2026-07-28 register audit. Each was verified against origin/main with file:line evidence; each is small and self-contained. Listed together for tracking — they should land as separate PRs, not one.
Ordered by value-per-line.
1. D3-43 — diff rendering has no perf guardrails (TRIVIAL)
render_unified_diff (ui/modern/diffview.rs:206) has no byte cap, no line cap and no per-line length cap. Its only guard is display truncation applied after all the work: if !expanded && out.len() > cap { out.truncate(cap) } (:305). Every line has already been syntect-highlighted, and paired runs word-diffed via TextDiff::from_words (:49), before anything is thrown away.
The single call site passes a hard-coded cap of 24 (layout.rs:511) with no size test beforehand. So a multi-megabyte edit result is fully highlighted on every cache miss — and again on every width change, since LayoutCache::sync clears all blocks when the width changes (layout.rs:80).
Fix: byte/line/line-length thresholds at the top of render_unified_diff that fall back to plain tinted lines, and short-circuit before rendering rather than after.
2. D5-30 — mouse capture cannot be relinquished (TRIVIAL, and a prerequisite)
EnableMouseCapture is unconditional at run.rs:649 and again at :711; DisableMouseCapture appears only on teardown. There is no toggle, no slash command, no config key. Native click-drag copy is therefore unavailable for the whole session, and the UI advertises a line-granular substitute instead (render.rs:170).
Fix: an App flag, a chord plus a slash command that execute Disable/EnableMouseCapture against the backend, and a sticky hint. All four call sites already exist.
Do this before #558 — adding hover means capturing motion events, which makes native selection worse. Ship the escape hatch first.
3. D6-13 — the status-line template engine is built, tested, and never called (SMALL)
StatusLineConfig (config/schema.rs:558) and render_statusline_template (:597) implement {model} {turn} {tokens} {cost} {cwd} {session_id} substitution with {{/}} escaping, backed by ~8 unit tests. git grep finds zero call sites outside schema.rs. Nothing reads cfg.ui.statusline; there is no /statusline command and no preview.
Fix: wire it into the modern status bar, add the command, render the candidate template against live vars as the user types.
This is the fifth instance of built-but-never-wired found in this codebase (after notifications, hyperlinks, keybindings.rs, /vim, and the theme picker) — worth noting as a pattern, not just a bug.
4. D3-26 — streaming re-highlights the whole block on every flush (SMALL)
emit_code_block (markdown.rs:418) constructs HighlightLines::new fresh on every invocation, and render_markdown re-parses the entire accumulated block. The layout cache re-renders a block whenever its content hash changes, and the streaming tail's hash changes on every flush (~10/s). So each flush re-runs pulldown-cmark plus syntect over the whole block including every previously-seen line — quadratic in block length.
Fix: cache per-block (ParseState, HighlightState, lines_consumed) and highlight only newly-appended lines, resetting when the fence closes or the prefix changes.
5. D8-12 — an unknown theme id in config fails silently (TRIVIAL)
/color <name> rejects properly with a list (commands/mod.rs:1949), but a bad id in the config file does not: resolve_theme returns unknown strings unchanged (theme_runtime.rs:241) and Theme::from_name has _ => DEFAULT_DARK with no log (theme.rs:790). Startup calls init_with_options without checking. /doctor has no theme check.
Fix: a catalog_ids().contains() check in resolve_theme (or a diagnostics.rs check) emitting unknown theme 'x'; using one-dark. Run /color to list.
Five independently-shippable items surfaced by the 2026-07-28 register audit. Each was verified against
origin/mainwith file:line evidence; each is small and self-contained. Listed together for tracking — they should land as separate PRs, not one.Ordered by value-per-line.
1. D3-43 — diff rendering has no perf guardrails (TRIVIAL)
render_unified_diff(ui/modern/diffview.rs:206) has no byte cap, no line cap and no per-line length cap. Its only guard is display truncation applied after all the work:if !expanded && out.len() > cap { out.truncate(cap) }(:305). Every line has already been syntect-highlighted, and paired runs word-diffed viaTextDiff::from_words(:49), before anything is thrown away.The single call site passes a hard-coded cap of 24 (
layout.rs:511) with no size test beforehand. So a multi-megabyte edit result is fully highlighted on every cache miss — and again on every width change, sinceLayoutCache::syncclears all blocks when the width changes (layout.rs:80).Fix: byte/line/line-length thresholds at the top of
render_unified_diffthat fall back to plain tinted lines, and short-circuit before rendering rather than after.2. D5-30 — mouse capture cannot be relinquished (TRIVIAL, and a prerequisite)
EnableMouseCaptureis unconditional atrun.rs:649and again at:711;DisableMouseCaptureappears only on teardown. There is no toggle, no slash command, no config key. Native click-drag copy is therefore unavailable for the whole session, and the UI advertises a line-granular substitute instead (render.rs:170).Fix: an
Appflag, a chord plus a slash command that executeDisable/EnableMouseCaptureagainst the backend, and a sticky hint. All four call sites already exist.Do this before #558 — adding hover means capturing motion events, which makes native selection worse. Ship the escape hatch first.
3. D6-13 — the status-line template engine is built, tested, and never called (SMALL)
StatusLineConfig(config/schema.rs:558) andrender_statusline_template(:597) implement{model}{turn}{tokens}{cost}{cwd}{session_id}substitution with{{/}}escaping, backed by ~8 unit tests.git grepfinds zero call sites outsideschema.rs. Nothing readscfg.ui.statusline; there is no/statuslinecommand and no preview.Fix: wire it into the modern status bar, add the command, render the candidate template against live vars as the user types.
This is the fifth instance of built-but-never-wired found in this codebase (after notifications, hyperlinks,
keybindings.rs,/vim, and the theme picker) — worth noting as a pattern, not just a bug.4. D3-26 — streaming re-highlights the whole block on every flush (SMALL)
emit_code_block(markdown.rs:418) constructsHighlightLines::newfresh on every invocation, andrender_markdownre-parses the entire accumulated block. The layout cache re-renders a block whenever its content hash changes, and the streaming tail's hash changes on every flush (~10/s). So each flush re-runs pulldown-cmark plus syntect over the whole block including every previously-seen line — quadratic in block length.Fix: cache per-block
(ParseState, HighlightState, lines_consumed)and highlight only newly-appended lines, resetting when the fence closes or the prefix changes.5. D8-12 — an unknown theme id in config fails silently (TRIVIAL)
/color <name>rejects properly with a list (commands/mod.rs:1949), but a bad id in the config file does not:resolve_themereturns unknown strings unchanged (theme_runtime.rs:241) andTheme::from_namehas_ => DEFAULT_DARKwith no log (theme.rs:790). Startup callsinit_with_optionswithout checking./doctorhas no theme check.Fix: a
catalog_ids().contains()check inresolve_theme(or adiagnostics.rscheck) emittingunknown theme 'x'; using one-dark. Run /color to list.