Previously, server.stop() called cleanupSession(killMux=false) for all sessions, which still tore down session state, removed listeners, killed PTY processes via node-pty SIGHUP, and broadcast session:deleted. This caused Claude sessions running inside tmux to be disrupted during COM deploys. Now server shutdown just persists state, removes listeners, and lets the Node.js process exit naturally. The tmux sessions survive independently, and restoreMuxSessions() finds them alive on restart. Also adds defense-in-depth in session.stop(): when killMux=false, skip PTY kill entirely (just null the reference). And adds 'detached' lifecycle event type for accurate audit logging.
27 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Quick Reference
| Task | Command |
|---|---|
| Dev server | npx tsx src/index.ts web |
| Type check | tsc --noEmit |
| Single test | npx vitest run test/<file>.test.ts |
| Production | npm run build && systemctl --user restart claudeman-web |
CRITICAL: Session Safety
You may be running inside a Claudeman-managed tmux session. Before killing ANY tmux or Claude process:
- Check:
echo $CLAUDEMAN_TMUX- if1, you're in a managed session - NEVER run
tmux kill-session,pkill tmux, orpkill claudewithout confirming - Use the web UI or
./scripts/tmux-manager.shinstead of direct kill commands
CRITICAL: Always Test Before Deploying
NEVER COM without verifying your changes actually work. For every fix:
- Backend changes: Hit the API endpoint with
curland verify the response - Frontend changes: Use Playwright to load the page and assert the UI renders correctly. Use
waitUntil: 'domcontentloaded'(notnetworkidle— SSE keeps the connection open). Wait 3-4s for polling/async data to populate, then check element visibility, text content, and CSS values - Only after verification passes, proceed with COM
The production server caches static files for 1 hour (maxAge: '1h' in server.ts). After deploying frontend changes, users may need a hard refresh (Ctrl+Shift+R) to see updates.
COM Shorthand (Deployment)
When user says "COM":
- Increment version in BOTH
package.jsonANDCLAUDE.md(verify they match withgrep version package.json && grep Version CLAUDE.md) - Run:
git add -A && git commit -m "chore: bump version to X.XXXX" && git push && npm run build && systemctl --user restart claudeman-web
Version: 0.1561 (must match package.json for npm publish)
Project Overview
Claudeman is a Claude Code session manager with web interface and autonomous Ralph Loop. Spawns Claude CLI via PTY, streams via SSE, supports respawn cycling for 24+ hour autonomous runs.
Tech Stack: TypeScript (ES2022/NodeNext, strict mode), Node.js, Fastify, node-pty, xterm.js
TypeScript Strictness (see tsconfig.json): noUnusedLocals, noUnusedParameters, noImplicitReturns, noImplicitOverride, noFallthroughCasesInSwitch, allowUnreachableCode: false, allowUnusedLabels: false. Note: src/tui is excluded from compilation (legacy/deprecated code path).
Requirements: Node.js 18+, Claude CLI, tmux
Commands
CRITICAL: npm run dev shows CLI help, NOT the web server.
Default port: 3000 (web UI at http://localhost:3000)
# Setup
npm install # Install dependencies
# Development
npx tsx src/index.ts web # Dev server (RECOMMENDED)
npx tsx src/index.ts web --https # With TLS (only needed for remote access)
npm run typecheck # Type check
tsc --noEmit --watch # Continuous type checking
# Testing (NEVER run full suite from inside Claudeman — kills tmux sessions)
# npx vitest run # ALL tests — DANGEROUS inside Claudeman
npx vitest run test/<file>.test.ts # Single file (SAFE)
npx vitest run -t "pattern" # Tests matching name
npm run test:coverage # With coverage report
# Production
npm run build
systemctl --user restart claudeman-web
journalctl --user -u claudeman-web -f
Common Gotchas
npm run devis NOT the web server — it shows CLI help. Usenpx tsx src/index.ts web- Single-line prompts only —
writeViaMux()sends text and Enter separately; multi-line breaks Ink - Don't kill tmux sessions blindly — Check
$CLAUDEMAN_TMUXfirst; you might be inside one - Never run full test suite —
npx vitest runspawns/kills tmux sessions and will crash your Claudeman session. Run individual test files only. - Global regex
lastIndexsharing —ANSI_ESCAPE_PATTERN_FULL/SIMPLEhavegflag; usecreateAnsiPatternFull/Simple()factory functions for fresh instances in loops - DEC 2026 sync blocks — Never discard incomplete sync blocks (START without END); buffer up to 50ms then flush. See
app.js:extractSyncSegments() - Terminal writes during buffer load — Live SSE writes are queued while
_isLoadingBufferis true to prevent interleaving with historical data - Local echo prompt scanning — Does NOT use
buffer.cursorY(Ink moves it); scans buffer bottom-up for visible>prompt marker
Import Conventions
- Utilities: Import from
./utils(re-exports all):import { LRUMap, stripAnsi } from './utils' - Types: Use type imports:
import type { SessionState } from './types' - Config: Import from specific files:
import { MAX_TERMINAL_BUFFER_SIZE } from './config/buffer-limits'
Architecture
Core Files
| File | Purpose |
|---|---|
src/session.ts |
PTY wrapper: runPrompt(), startInteractive(), startShell() |
src/mux-interface.ts |
TerminalMultiplexer interface + MuxSession type |
src/mux-factory.ts |
Create tmux multiplexer instance |
src/tmux-manager.ts |
tmux session management |
src/session-manager.ts |
Session lifecycle, cleanup |
src/state-store.ts |
State persistence to ~/.claudeman/state.json |
src/respawn-controller.ts |
State machine for autonomous cycling |
src/ralph-tracker.ts |
Detects <promise>PHRASE</promise>, todos |
src/ralph-loop.ts |
Autonomous task execution loop (polls queue, assigns tasks) |
src/ralph-config.ts |
Parses .claude/ralph-loop.local.md plugin config |
src/task.ts |
Task model for prompt execution |
src/task-queue.ts |
Priority queue for tasks with dependencies |
src/task-tracker.ts |
Background task tracker for subagent detection |
src/subagent-watcher.ts |
Monitors Claude Code's Task tool (background agents) |
src/team-watcher.ts |
Polls ~/.claude/teams/ for agent team activity; matches teams to sessions via leadSessionId |
src/run-summary.ts |
Timeline events for "what happened while away" |
src/ai-checker-base.ts |
Base class for AI-powered checkers (shared by idle + plan checkers) |
src/ai-idle-checker.ts |
AI-powered idle detection |
src/ai-plan-checker.ts |
AI-powered plan completion checker |
src/bash-tool-parser.ts |
Parses Claude's bash tool invocations from output |
src/transcript-watcher.ts |
Watches Claude's transcript files for changes |
src/hooks-config.ts |
Manages .claude/settings.local.json hook configuration |
src/session-lifecycle-log.ts |
Append-only JSONL audit log at ~/.claudeman/session-lifecycle.jsonl |
src/image-watcher.ts |
Watches for image file creation (screenshots, etc.) |
src/file-stream-manager.ts |
Manages tail -f processes for live log viewing |
src/plan-orchestrator.ts |
Multi-agent plan generation with research and planning phases |
src/prompts/index.ts |
Barrel export for all agent prompts |
src/prompts/*.ts |
Agent prompts (research-agent, planner) |
src/templates/claude-md.ts |
CLAUDE.md generation for new cases |
src/cli.ts |
Command-line interface handlers |
src/web/server.ts |
Fastify REST API + SSE at /api/events (~99 routes) |
src/web/schemas.ts |
Zod v4 validation schemas with path/env security allowlists |
src/web/public/app.js |
Frontend: xterm.js, tab management, subagent windows, mobile support (~16K lines) |
src/types.ts |
All TypeScript interfaces (~100 types, ~1400 lines) |
Large files (>50KB): app.js, ralph-tracker.ts, respawn-controller.ts, session.ts, subagent-watcher.ts — these contain complex state machines; read docs/respawn-state-machine.md before modifying.
Config Files (src/config/)
| File | Purpose |
|---|---|
buffer-limits.ts |
Terminal/text buffer size limits |
map-limits.ts |
Global limits for Maps, sessions, watchers |
Utility Files (src/utils/)
| File | Purpose |
|---|---|
index.ts |
Re-exports all utilities (standard import point) |
lru-map.ts |
LRU eviction Map for bounded caches |
nice-wrapper.ts |
Wrap commands with nice priority adjustment |
stale-expiration-map.ts |
TTL-based Map with lazy expiration |
claude-cli-resolver.ts |
Resolve Claude CLI binary across install paths |
cleanup-manager.ts |
Centralized resource disposal |
buffer-accumulator.ts |
Chunk accumulator with size limits |
string-similarity.ts |
String matching utilities (fuzzy matching) |
token-validation.ts |
Token count parsing and validation |
regex-patterns.ts |
Shared regex patterns for parsing |
type-safety.ts |
assertNever() for exhaustive switch/case type checking |
Data Flow
- Session spawns
claude --dangerously-skip-permissionsvia node-pty - PTY output buffered, ANSI stripped, parsed for JSON messages
- WebServer broadcasts to SSE clients at
/api/events - State persists to
~/.claudeman/state.jsonvia StateStore
Key Patterns
Input to sessions: Use session.writeViaMux() for programmatic input (respawn, auto-compact). Uses tmux send-keys -l (literal text) + send-keys Enter. All prompts must be single-line.
Terminal multiplexer: TerminalMultiplexer interface (src/mux-interface.ts) abstracts the backend. createMultiplexer() from src/mux-factory.ts creates the tmux backend.
Idle detection: Multi-layer (completion message → AI check → output silence → token stability). See docs/respawn-state-machine.md.
Token tracking: Interactive mode parses status line ("123.4k tokens"), estimates 60/40 input/output split.
Hook events: Claude Code hooks trigger notifications via /api/hook-event. Key events: permission_prompt (tool approval needed), elicitation_dialog (Claude asking question), idle_prompt (waiting for input), stop (response complete), teammate_idle (Agent Teams), task_completed (Agent Teams). See src/hooks-config.ts.
Agent Teams (experimental): TeamWatcher polls ~/.claude/teams/ for team configs and matches teams to sessions via leadSessionId. Teammates are in-process threads (not separate OS processes) and appear as standard subagents. RespawnController checks TeamWatcher.hasActiveTeammates() before triggering respawn. Enable via CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1 env var in settings.local.json. See agent-teams/ for full docs.
Circuit breaker: Prevents respawn thrashing when Claude is stuck. States: CLOSED (normal) → HALF_OPEN (testing) → OPEN (blocked). Tracks consecutive no-progress, same-error-repeated, and tests-failing-too-long. Reset via API at /api/sessions/:id/ralph-circuit-breaker/reset.
Respawn cycle metrics & health scoring: RespawnCycleMetrics tracks per-cycle outcomes (success, stuck_recovery, blocked, error). RalphLoopHealthScore computes 0-100 health with component scores (cycleSuccess, circuitBreaker, iterationProgress, aiChecker, stuckRecovery). Available via respawn status API.
Subagent-session correlation: Session parses Task tool output via BashToolParser → SubagentWatcher discovers new agent → calls session.findTaskDescriptionNear() to match description for window title.
Frontend Architecture (app.js)
The frontend is a single 16K-line vanilla JS file with these key systems:
| System | Key Classes/Functions | Purpose |
|---|---|---|
| Terminal rendering | batchTerminalWrite(), flushPendingWrites(), chunkedTerminalWrite() |
60fps batched writes with DEC 2026 sync |
| Local echo overlay | LocalEchoOverlay class |
DOM overlay for instant mobile keystroke feedback |
| Mobile support | MobileDetection, KeyboardHandler, SwipeHandler, KeyboardAccessoryBar |
Touch input, viewport adaptation, swipe navigation |
| Subagent windows | openSubagentWindow(), closeSubagentWindow(), updateConnectionLines() |
Floating terminal windows with parent connection lines |
| Notifications | NotificationManager class |
4-layer: in-app drawer, tab flash, browser API, audio beep |
| SSE connection | connectSSE(), addListener() |
EventSource with exponential backoff (1-30s), offline queue (64KB) |
| Settings | openAppSettings(), apply*Visibility() |
Server-backed + localStorage persistence |
| Focus management | FocusTrap class |
Modal keyboard navigation with focus restore |
Z-index layers: subagent windows (1000), plan agents (1100), log viewers (2000), image popups (3000), local echo overlay (7).
Built-in respawn presets: solo-work (3s idle, 60min), subagent-workflow (45s idle, 240min), team-lead (90s idle, 480min), overnight-autonomous (10s idle, 480min, full reset).
Keyboard shortcuts: Escape (close panels), Ctrl+? (help), Ctrl+Enter (quick start), Ctrl+W (kill session), Ctrl+Tab (next session), Ctrl+K (kill all), Ctrl+L (clear), Ctrl+Shift+R (restore size), Ctrl/Cmd +/- (font size).
Security
- HTTP Basic Auth: Optional via
CLAUDEMAN_USERNAME/CLAUDEMAN_PASSWORDenv vars - CORS: Restricted to localhost only
- Security headers: X-Content-Type-Options, X-Frame-Options, CSP; HSTS if HTTPS
- Path validation (
schemas.ts): Strict allowlist regex, no shell metacharacters, no traversal, must be absolute - Env var allowlist: Only
CLAUDE_CODE_*prefixes allowed; blocksPATH,LD_PRELOAD,NODE_OPTIONS,CLAUDEMAN_*keys - File streaming TOCTOU protection:
FileStreamManagercallsrealpathSync()twice (at validation and before spawn) to catch symlink swaps
SSE Event Categories
~80+ event types broadcast via broadcast(). Key categories:
| Category | Events | Purpose |
|---|---|---|
| Session | session:created/updated/deleted/working/idle/exit/error/completion |
Lifecycle |
| Terminal | session:terminal, session:clearTerminal, session:needsRefresh |
Output streaming |
| Respawn | respawn:stateChanged/cycleStarted/blocked/aiCheck*/planCheck*/timer* |
Respawn state machine |
| Subagent | subagent:discovered/updated/completed/tool_call/progress |
Background agents |
| Ralph | session:ralphLoopUpdate/ralphTodoUpdate/ralphCompletionDetected |
Ralph tracking |
| Hooks | hook:{eventName} (dynamic) |
Claude Code hook events |
| Plan | plan:started/progress/completed/cancelled/subagent |
Plan orchestration |
| Mux | mux:created/killed/died/statsUpdated |
tmux process monitor |
| Image | image:detected |
Screenshot detection |
API Route Categories
~99 routes in server.ts:buildServer(). Key groups:
| Group | Prefix | Count | Key endpoints |
|---|---|---|---|
| Sessions | /api/sessions |
~20 | CRUD, input, resize, interactive, shell |
| Respawn | /api/sessions/:id/respawn |
5 | start, stop, enable, config |
| Ralph | /api/sessions/:id/ralph-* |
6 | state, status, config, circuit-breaker |
| Plan | /api/sessions/:id/plan/* |
5 | task CRUD, checkpoint, history, rollback |
| Subagents | /api/subagents |
7 | list, transcript, kill, cleanup |
| Cases | /api/cases |
5 | CRUD, link, fix-plan |
| Scheduled | /api/scheduled |
4 | CRUD for scheduled runs |
| System | /api/status, /api/stats, /api/config, /api/settings |
8 | App state, config |
| Files | /api/sessions/:id/file*, tail-file |
5 | Browser, preview, raw, tail stream |
| Mux | /api/mux-sessions |
4 | tmux management, stats |
Adding Features
- API endpoint: Types in
types.ts, route inserver.ts:buildServer(), usecreateErrorResponse(). Validate request bodies with Zod schemas inschemas.ts. - SSE event: Emit via
broadcast(), handle inapp.jsSSE listener section (searchaddListener() - Session setting: Add to
SessionStateintypes.ts, include insession.toState(), callpersistSessionState() - Hook event: Add to
HookEventTypeintypes.ts, add hook command inhooks-config.ts:generateHooksConfig(), updateHookEventSchemainschemas.ts - Mobile feature: Add to relevant mobile singleton (
KeyboardHandler,KeyboardAccessoryBar, etc.), test withMobileDetection.isMobile()guard - New test: Pick unique port (search
const PORT =), add port comment to test file header. Tests use ports 3150+.
Validation: Uses Zod v4 for request validation. Define schemas in schemas.ts and use .parse() or .safeParse(). Note: Zod v4 has different API from v3 (e.g., z.object() options changed, error formatting differs).
State Files
| File | Purpose |
|---|---|
~/.claudeman/state.json |
Sessions, settings, tokens, respawn config |
~/.claudeman/mux-sessions.json |
Tmux session metadata for recovery |
~/.claudeman/settings.json |
User preferences |
Default Settings
UI defaults are set in src/web/public/app.js using ?? fallbacks. To change defaults, edit openAppSettings() and apply*Visibility() functions.
Key defaults: Most panels hidden (monitor, subagents shown), notifications enabled (audio disabled), subagent tracking on, Ralph tracking off.
Testing
CRITICAL: You are running inside a Claudeman-managed tmux session. Never run npx vitest run (full suite) — it spawns/kills tmux sessions and will crash your own session. Instead:
# Safe: run individual test files
npx vitest run test/<specific-file>.test.ts
# Safe: run tests matching a pattern
npx vitest run -t "pattern"
# DANGEROUS from inside Claudeman — will kill your tmux session:
# npx vitest run ← DON'T DO THIS
Ports: Unit tests pick unique ports manually. Search const PORT = before adding new tests.
Config: Vitest with globals: true, fileParallelism: false. Unit timeout 30s.
Safety: test/setup.ts snapshots pre-existing tmux sessions at load time and never kills them. Only sessions registered via registerTestTmuxSession() get cleaned up.
Respawn tests: Use MockSession from test/respawn-test-utils.ts to avoid spawning real Claude processes.
Mobile tests: Separate Playwright-based suite in mobile-test/ with 135 device profiles. Run via npx vitest run --config mobile-test/vitest.config.ts. See mobile-test/README.md.
Screenshots ("sc")
When the user says "check the sc", "screenshot", or "sc", they mean uploaded screenshots from their mobile device. Screenshots are saved to ~/.claudeman/screenshots/ and uploaded via /upload.html on the Claudeman web UI. To view them, use the Read tool on the image files:
ls ~/.claudeman/screenshots/ # List uploaded screenshots
# Then use Read tool on individual files — Claude Code can view images natively
API: GET /api/screenshots (list), GET /api/screenshots/:name (serve), POST /api/screenshots (upload multipart/form-data). Source: src/web/public/upload.html.
Debugging
tmux list-sessions # List tmux sessions
tmux attach-session -t <name> # Attach (Ctrl+B D to detach)
curl localhost:3000/api/sessions # Check sessions
curl localhost:3000/api/status | jq # Full app state
cat ~/.claudeman/state.json | jq # View persisted state
curl localhost:3000/api/subagents # List background agents
curl localhost:3000/api/sessions/:id/run-summary | jq # Session timeline
Troubleshooting
| Problem | Check | Fix |
|---|---|---|
| Session won't start | tmux list-sessions for orphans |
Kill orphaned sessions, check Claude CLI installed |
| Port 3000 in use | lsof -i :3000 |
Kill conflicting process or use --port flag |
| SSE not connecting | Browser console for errors | Check CORS, ensure server running |
| Respawn not triggering | Session settings → Respawn enabled? | Enable respawn, check idle timeout config |
| Terminal blank on tab switch | Network tab for /api/sessions/:id/buffer |
Check session exists, restart server |
| Tests failing on session limits | tmux list-sessions | wc -l |
Clean up: tmux list-sessions | grep test | awk -F: '{print $1}' | xargs -I{} tmux kill-session -t {} |
| State not persisting | cat ~/.claudeman/state.json |
Check file permissions, disk space |
Performance Constraints
The app must stay fast with 20 sessions and 50 agent windows:
- 60fps terminal (16ms batching +
requestAnimationFrame) - Auto-trimming buffers (2MB terminal max)
- Debounced state persistence (500ms)
- SSE adaptive batching: 16ms (normal), 32ms (moderate), 50ms (rapid); immediate flush at 32KB
- SSE backpressure handling: skip writes to backpressured clients, recover via
session:needsRefreshon drain - Cached endpoints:
/api/sessionsand/api/statususe 1s TTL caches to avoid expensive serialization - Frontend buffer loads: 128KB chunks via
requestAnimationFrameto prevent UI jank
Terminal Anti-Flicker System
Claude Code uses Ink (React for terminals), which redraws the screen on every state change. Claudeman implements a 6-layer anti-flicker pipeline for smooth 60fps output:
PTY Output → Server Batching (16-50ms) → DEC 2026 Wrap → SSE → Client rAF → xterm.js
Key functions: server.ts:batchTerminalData(), server.ts:flushTerminalBatches(), app.js:batchTerminalWrite(), app.js:extractSyncSegments()
Typical latency: 16-32ms. Optional per-session flicker filter adds ~50ms for problematic terminals.
See docs/terminal-anti-flicker.md for full implementation details (adaptive batching, DEC 2026 markers, edge cases).
Resource Limits
Limits are centralized in src/config/buffer-limits.ts and src/config/map-limits.ts.
Buffer limits (per session):
| Buffer | Max | Trim To |
|---|---|---|
| Terminal | 2MB | 1.5MB |
| Text output | 1MB | 768KB |
| Messages | 1000 | 800 |
Map limits (global):
| Resource | Max |
|---|---|
| Tracked agents | 500 |
| Concurrent sessions | 50 |
| SSE clients total | 100 |
| File watchers | 500 |
Use LRUMap for bounded caches with eviction, StaleExpirationMap for TTL-based cleanup.
Where to Find More Information
| Topic | Location |
|---|---|
| Respawn state machine | docs/respawn-state-machine.md |
| Ralph Loop guide | docs/ralph-wiggum-guide.md |
| Claude Code hooks | docs/claude-code-hooks-reference.md |
| Terminal anti-flicker | docs/terminal-anti-flicker.md |
| API routes | src/web/server.ts:buildServer() or README.md (full endpoint tables) |
| SSE events | Search broadcast( in server.ts |
| CLI commands | claudeman --help |
| Frontend patterns | src/web/public/app.js (subagent windows, notifications) |
| Session statuses | SessionStatus type in src/types.ts |
| Error codes | createErrorResponse() in src/types.ts |
| Test utilities | test/respawn-test-utils.ts |
| Memory leak patterns | test/memory-leak-prevention.test.ts |
| Keyboard shortcuts | README.md or App Settings in web UI |
| Mobile/SSH access | README.md (Claudeman Sessions / sc command) |
| Plan orchestrator | src/plan-orchestrator.ts file header |
| Agent prompts | src/prompts/ directory |
| Agent Teams (experimental) | agent-teams/README.md, agent-teams/design.md |
| Local echo overlay | docs/local-echo-overlay-plan.md |
| Browser testing | docs/browser-testing-guide.md |
| Mobile testing | docs/mobile-testing-report.md |
| Run summary design | docs/run-summary-plan.md |
| Performance audit | docs/perf-audit-first-load.md |
| First-load optimization | docs/first-load-optimization-plan.md |
| Dead code audit | docs/cleanup-findings.md |
| Mobile test suite | mobile-test/README.md |
Scripts
| Script | Purpose |
|---|---|
scripts/tmux-manager.sh |
Safe tmux session management (use instead of direct kill commands) |
scripts/tmux-chooser.sh |
Mobile-friendly tmux session picker (sc alias) |
scripts/monitor-respawn.sh |
Monitor respawn state machine in real-time |
scripts/postinstall.js |
npm postinstall hook for setup |
scripts/data-generator.sh |
Generate test data for development |
scripts/test-tail-links.sh |
Test clickable file links in tail output |
scripts/capture-subagent-screenshots.mjs |
Capture subagent screenshots/GIFs for README (uses real Claude sessions) |
scripts/mobile-screenshot.mjs |
Capture mobile UI screenshots |
scripts/ralph-wizard-start.mjs |
Automate Ralph Loop startup via headless browser |
scripts/ralph-wizard-prod.mjs |
Production Ralph wizard with HTTPS support |
scripts/browser-comparison.mjs |
Compare Playwright, Puppeteer, and Agent-Browser frameworks |
scripts/ralph-wizard-demo.mjs |
Demo Ralph Loop wizard via visible browser |
scripts/test-links-browser.mjs |
Browser test for clickable terminal file links |
scripts/test-patterns.mjs |
Test file path link detection regex patterns |
scripts/watch-subagents.ts |
Real-time subagent transcript watcher (list, follow by session/agent ID) |
scripts/capture-readme-screenshots.mjs |
Capture screenshots for README |
scripts/claudeman-web.service |
systemd service file for production deployment |
Memory Leak Prevention
Frontend runs long (24+ hour sessions); all Maps/timers must be cleaned up.
Cleanup Patterns
When adding new event listeners or timers:
- Store handler references for later removal
- Add cleanup to appropriate
stop()orcleanup*()method - For singleton watchers, store refs in class properties and remove in server
stop()
Backend: Clear Maps in stop(), null promise callbacks on error, remove watcher listeners on shutdown. Use CleanupManager for centralized disposal — supports timers, intervals, watchers, listeners, streams. Guard async callbacks with if (this.cleanup.isStopped) return.
Frontend: Store drag/resize handlers on elements, clean up in close*() functions. SSE reconnect calls handleInit() which resets state. SSE listeners are tracked in an array and removed on reconnect to prevent accumulation.
Run npx vitest run test/memory-leak-prevention.test.ts to verify patterns.
Common Workflows
Investigating a bug: Start dev server (npx tsx src/index.ts web), reproduce in browser, check terminal output and ~/.claudeman/state.json for clues.
Adding a new API endpoint: Define types in types.ts, add route in server.ts:buildServer(), broadcast SSE events if needed, handle in app.js:handleSSEEvent().
Modifying respawn behavior: Study docs/respawn-state-machine.md first. The state machine is in respawn-controller.ts. Use MockSession from test/respawn-test-utils.ts for testing.
Modifying mobile behavior: Mobile singletons (MobileDetection, KeyboardHandler, SwipeHandler, KeyboardAccessoryBar) all have init()/cleanup() lifecycle. KeyboardHandler uses visualViewport API for iOS keyboard detection (100px threshold for address bar drift). All mobile handlers are re-initialized after SSE reconnect to prevent stale closures.
Adding a file watcher: Use ImageWatcher as a template pattern — chokidar with awaitWriteFinish, burst throttling (max 20/10s), debouncing (200ms), and auto-ignore of node_modules/.git/dist/.