Repository navigation
Description
What happened
The app's debug infrastructure has been added to incrementally for months and is now scattered across eight+ directories without a single source of truth. There are 30 files outside those debug homes that contain ad-hoc `console.log` / `console.warn` calls. The result is that there is no canonical answer to "where does debug code live, and how do you add a new debug surface?" — every author makes a local decision, and the decisions disagree.
Current scatter (concrete inventory)
Eight directories that all hold debug-related code:
- `src/main/performance/` — PerformanceService, LocalJsonlSpanExporter, heapWatchdog, instrumentIpc, ProcessTelemetry
- `src/shared/performance/` — shared types
- `src/renderer/src/performance/` — renderer-side perf client
- `src/renderer/src/features/system-perf/` — the header badge + popover
- `src/renderer/src/features/performance/` — `PerformancePanel` (per-pane CPU/mem)
- `src/renderer/src/features/debug/` — DebugPanel, ProxyDebugPanel, FeedDebugPanel, HtmlDebugPanel, AgentInlineTerminal, saveDebugBundle, renderTrace
- `src/shared/work-context/debug.ts` — `summarizeWorktreeActivity` (debug-only)
- `src/renderer/src/workspace/runtime/feedDebug.ts` + `hook/persistence/useFeedDebugPersist.ts`
Plus loose files scattered in non-debug directories:
- `src/main/pasteDebugJournal.ts`, `src/main/ghostJournal.ts`, `src/main/dictationJournal.ts`
- `src/main/storage/debugRetention.ts`, `src/main/storage/feedDebugLog.ts`, `src/main/storage/debugBundle.ts`
- `src/main/ipc/debug.ts`, `src/main/ipc/pasteDebug.ts`
- `src/preload/api/debug.ts`, `src/preload/api/pasteDebug.ts`, `src/preload/api/dictationDebug.ts`
Console-log scatter: 30 source files outside the debug homes carry raw `console.log` / `console.warn` calls. Sample sites (all should route through a typed logger instead):
- `src/renderer/src/app/App.tsx`
- `src/renderer/src/workspace/hook/persistence/{useAutoSave,useBootstrap,rehydrate}.ts`
- `src/renderer/src/workspace/hook/actions/{history,session,initialHistory}.ts`
- `src/renderer/src/workspace/hook/ipc/useIpcSubscriptions.ts`
- `src/renderer/src/workspace/tile-tree/TileLeaf/{useClaudeImagePaste,useComposerKeybinds}.ts`
- `src/providers/{claude,codex}/runtime/{claudeSession,codexSession}.ts`
Where
The scatter itself is the issue — see inventory above.
Output
N/A — this is a structural / maintainability issue, no crash.
State
Fragmentation accumulated over months. Each individual addition was reasonable on its own; the aggregate is the problem.
Sketch
Three layered moves. The first is the load-bearing one; the others fall out of it.
Step 1 — One canonical home for all debug code: `src/debug/`.
A top-level `debug/` directory (peer to `src/main/`, `src/renderer/`, `src/shared/`) that subsumes everything in the inventory above. Internal shape:
```
src/debug/
shared/ # types, the Logger interface, log levels
Logger.ts # the typed logger boundary (see Step 2)
types.ts
main/ # main-process implementations
journals/ # ghost / dictation / paste / feed-debug
performance/ # PerformanceService, watchdog, OTEL exporter
storage/ # debugBundle, debugRetention
ipc/ # debug + pasteDebug IPC handlers
preload/ # preload bridges for debug IPC
renderer/ # renderer-side debug code
panels/ # DebugPanel, ProxyDebugPanel, FeedDebugPanel,
# HtmlDebugPanel, PerformancePanel,
# SystemPerfHeader+popover
devModules/ # the slot for #101's dev debug modules
runtime/ # feedDebug runtime helpers, renderTrace
```
`debug/` is logically a package even if it isn't published — same role headless packages play (cleanly separable, self-contained). The non-debug app code only depends on it through the `Logger` interface (Step 2) and a small number of IPC channels.
Step 2 — A typed Logger boundary.
Replace every `console.log` / `console.warn` in the 30 scatter files with calls to a typed logger:
```ts
// src/debug/shared/Logger.ts
export type LogLevel = 'debug' | 'info' | 'warn' | 'error'
export interface Logger {
debug(area: string, msg: string, data?: Record<string, unknown>): void
info(area: string, msg: string, data?: Record<string, unknown>): void
warn(area: string, msg: string, data?: Record<string, unknown>): void
error(area: string, msg: string, error?: unknown, data?: Record<string, unknown>): void
child(area: string): Logger // returns a logger with area pre-bound
}
```
The implementation routes to:
- console (gated by log level + env var, same idea as `AGENT_CODE_PERF`)
- the existing journal pipeline where appropriate (`PerformanceService`, ghost log, etc.)
- the structured debug surfaces (the `area` is what lets the debug panels filter by subsystem)
Then each scatter site becomes:
```ts
// before
console.warn('[worktree-activity] background refresh failed:', err)
// after
log.warn('worktree-activity', 'background refresh failed', { error: err })
```
Cleaner site, structured data, routable to multiple sinks, filterable in the debug panel.
Step 3 — Enforce the boundary in lint.
Add an ESLint rule (`no-restricted-syntax` or `no-console`) that forbids raw `console.*` calls outside `src/debug/`. The 30 scatter sites get touched by Step 2's migration; the lint rule prevents the scatter from coming back.
Process:
This is too big for one PR. Sequence it as:
- PR α: create `src/debug/shared/Logger.ts` and a minimal implementation that routes to console. No migrations yet. Pure addition.
- PR β: relocate the 8+ existing debug directories under `src/debug/`. Pure move — no logic changes. The diff is mostly import-path updates. CI catches regressions.
- PR γ — δ: migrate the 30 scatter sites to the typed logger. Group by area (renderer-workspace hooks, providers, app shell) so each PR is reviewable. Roughly 4–6 small PRs.
- PR ε: enable the lint rule. Final step — only flips on after every scatter site has been migrated, otherwise the rule fails CI.
- PR ζ: cleanup audit. Walk the new `src/debug/` looking for redundant logger types, overlapping panels, journals that could be consolidated. (Out of scope for the first round; file follow-up issues if structural issues are found.)
The whole thing is several PRs over a couple of sessions. The payoff: one place to look for debug behaviour, structured logs, a discoverable mental model for new contributors, and a lint rule that prevents the scatter from regrowing.
Related: #101 (the dev-debug module slot — its `devModules/` directory becomes part of `src/debug/renderer/devModules/` once this consolidation lands).
Activity
Juliusolsson05 commentedon Sep 27, 2026
Closing (owner decision, 2026-09-27). Moving all debug code into one folder, adding a typed logger for every console call, and a lint gate would be heavy scaffolding with no user-facing gain. Raw console use grew from 30 to 94 files since this was filed, which shows the cost. Reopen / refile when: a concrete need appears, e.g. filtering logs by area in the debug panel. Then add a small logger for just that.