Disable WebGL renderer during large buffer loads (>32KB) and fall back to
canvas, which handles bulk ANSI writes without synchronous GPU ReadPixels
calls. Re-enable WebGL after the buffer load completes so live terminal
streaming still benefits from GPU acceleration.
Also reduce chunk size from 128KB to 32KB and use chunked writes for
cached buffer restores instead of synchronous terminal.write().
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add charCellWidth/stringCellWidth helpers for Unicode-aware width detection,
fix makeLine to use for...of iteration with visual column positioning, and
fix line splitting in _render to use visual column widths instead of string
length. CJK/fullwidth characters now correctly occupy 2 cell widths.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Reduce terminal chunk size from 128KB to 32KB and use double-RAF between
chunks to give the WebGL renderer time to flush GPU operations. Also switch
cached buffer restore from direct terminal.write() to chunked writes —
the synchronous 256KB write was blocking the main thread for 5+ seconds
via synchronous ReadPixels calls.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The @rollup/rollup-linux-x64-gnu native binding was a hard dependency,
causing npm install to fail on arm64 and macOS platforms. Nothing in the
codebase uses rollup directly (build uses esbuild); rollup is only
pulled in transitively by @remotion/cli and manages its own
platform-specific bindings via its own optionalDependencies.
Closes#28
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Two performance fixes for browser hanging during active agent work:
1. SSE padding (8KB per event) now only applied when Cloudflare tunnel is
active — direct/Tailscale connections skip the padding entirely. Previously
every broadcast event got 8KB of comment padding even on local connections,
causing 40-160KB/s of wasted bandwidth during active subagent work.
2. Subagent window content renders (tool_call, progress, message, tool_result)
now debounced at 100ms per agent via scheduleSubagentWindowRender(). Previously
each SSE event triggered an immediate DOM rewrite, causing 10-30+ rewrites/sec
that starved the terminal rendering pipeline.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Cloudflare quick tunnels buffer small SSE responses, causing tab creation
and other UI events to arrive late on mobile. Adds ~8KB SSE comment padding
(ignored by EventSource) to force the proxy to flush immediately.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
require() is not defined in ESM modules. The production build (tsc)
outputs ESM, causing 'require is not defined' → 500 → 'QR unavailable'.
Vitest/tsx shimmed require() so tests passed but production was broken.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The QR SVG endpoint had zero test coverage for success paths — only
the 404 (tunnel not running) case was tested. This adds tests for
auth/no-auth SVG generation, the 500 error when token rotation isn't
started, SVG caching consistency, and cache invalidation on regeneration.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace hardcoded per-IP rate limit (10) and cookie maxAge (86400) in
system-routes.ts with QR_AUTH_FAILURE_MAX and AUTH_SESSION_TTL_MS/1000
so both auth paths stay in sync if constants change.
Add 16 new tests: grace period boundary precision, base62 charset
validation, current+previous token during grace, stopTokenRotation
state cleanup, rate limit reset, consumed token eviction, full
end-to-end QR flow, per-IP 429, cookie attributes, concurrent race,
regenerate invalidation, URL encoding, path traversal, /q without
param, and session record method:qr.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add path traversal protection to GET /api/cases/:name and fix-plan
- Use safePathSchema for LinkCaseSchema.path
- Fix QR auth test timer leak (afterAll → afterEach) and env var try/finally
- Remove dead terminal size check after Zod validation in resize route
- Remove no-op sampleCount guard in adaptive timing
- Replace hardcoded values with constants in notification-manager and subagent-windows
- Add Zod validation to POST /api/auth/revoke
- Use _apiPut instead of raw fetch in subagent-windows
- Add SwipeHandler.cleanup() for consistency with other mobile handlers
- Move NiceConfig/ProcessStats from types/plan.ts to types/common.ts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove tests for createSuccessResponse and ErrorMessages which were
removed/made private during the type system refactoring (15 failures)
- Fix RalphConfigSchema to accept 'full' string for reset field,
matching the route handler's fullReset() code path
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix Prettier formatting in ralph-tracker.ts and respawn-controller.ts
(whitespace drift from Phase 2/4 refactoring)
- Add missing `await` to writeHooksConfig() calls in hooks-config.test.ts
(async function was called without await, causing ENOENT race condition)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
New test files for utilities that previously had no dedicated coverage,
plus migration tests validating the ralph-tracker and respawn-controller
timer refactorings work correctly.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Overlay renderer (xterm-zerolag-input):
- Add charTop/charHeight to CellDimensions and RenderParams for precise
vertical text positioning matching xterm's canvas renderer
- Convert device.char dimensions to CSS pixels via devicePixelRatio
- Extend line div background 1px past cell boundary to cover compositing
seam between overlay layer (z-index:7) and canvas layer below
- Remove -webkit-font-smoothing/text-rendering overrides that made overlay
text thinner than canvas text
- Add per-span height/lineHeight for natural CSS vertical centering
- Add setPrompt() method for runtime prompt strategy switching (fixes tab
switching crash with "setPrompt is not a function")
app.js duplicate class members:
- Remove dead formatTokens duplicate (line ~5590 shadowed precise version)
- Remove fire-and-forget resetCircuitBreaker duplicate (shadowed notification version)
- Rename mux-panel killAllSessions to killAllMuxSessions (was shadowing
Codeman session killer, breaking Ctrl+K)
Other:
- Update index.html onclick to use killAllMuxSessions
- Add getTeamTasks mock to test route context
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Document the ephemeral single-use QR token system — how it works,
security design informed by USENIX Security 2025 research (6 flaws
addressed), timing-safe lookup, dual-layer rate limiting, QR version
optimization, desktop experience, threat coverage, and comparison
with Discord/WhatsApp/Signal QR auth models.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Phase 2: respawn-controller.ts and ralph-tracker.ts were never migrated
to CleanupManager/Debouncer despite being marked complete. Corrected
status to reflect actual codebase state (6 of 8 files migrated).
Phase 7: All 12 route test files now exist, updated from "9 remaining".
Scorecard adjusted: Resource Cleanup 9→8/10, Test Coverage 7→7.5/10.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
navigator.mediaDevices is undefined in insecure contexts, causing
"undefined is not an object" error. Now shows actionable message.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Update scorecard with before/after scores, mark phases 3-7 as
complete with verified line counts and file inventories. Fix 3
CLAUDE.md inaccuracies: static cache 1h→1y, route count ~160→~113,
remove stale src/tui reference.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Consolidate duplicated MockSession/MockStateStore into test/mocks/,
migrate respawn tests to shared mocks, and add 58 route tests for
session, system, and respawn endpoints using Fastify app.inject().
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Route modules (13→12), type domain files (14→13), SSE events (~80→~100),
total route handlers (~110→~160), and all per-group API route counts.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds ephemeral single-use QR tokens for passwordless tunnel login.
Scanning the QR auto-authenticates; bare tunnel URL requires Basic Auth.
Backend:
- TunnelManager: 60s token rotation, 90s grace, rejection-sampled 6-char
base62 short codes, Map-based O(1) lookup, SVG caching, global rate limit
- Auth middleware: /q/ bypass, separate qrAuthFailures counter, enhanced
AuthSessionRecord with device context (ip, ua, createdAt, method)
- Routes: GET /q/:code (consume + cookie + redirect), POST /api/tunnel/qr/
regenerate, POST /api/auth/revoke, updated GET /api/tunnel/qr with cache
- SSE: tunnel:qrRotated, tunnel:qrRegenerated, tunnel:qrAuthUsed events
- Audit: qr_auth lifecycle log entries
Frontend:
- Auto-refresh QR via inline SVG in SSE (fallback fetch if absent)
- 60s countdown indicator on QR badge
- Regenerate QR button
- QRLjacking detection toast with [Revoke All] action button (10s duration)
- showToast enhanced with optional duration and action button support
Fixes:
- /api/logout now invalidates server-side session token (was only clearing
browser cookie, leaving token valid for replay)
Tests: 20 new tests in test/qr-auth.test.ts covering token lifecycle,
bias check, rate limiting, SVG caching, and full server integration.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Split 1,443-line types.ts into 14 focused domain files under src/types/:
common.ts, session.ts, task.ts, app-state.ts, respawn.ts, ralph.ts,
api.ts, lifecycle.ts, run-summary.ts, tools.ts, teams.ts, push.ts,
plan.ts, and index.ts barrel.
Moved PlanItem interface from plan-orchestrator.ts into types/plan.ts
to break circular dependency. Original types.ts replaced with barrel
re-export — zero changes to 36 import sites.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Split 6,710-line server.ts into focused route modules using port
interfaces for dependency injection. 107/109 routes extracted into
12 domain files with auth middleware, 5 port interfaces, and shared
helpers. Server.ts retains orchestration (SSE, lifecycle, state).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
npm package stays as aicodeman (name taken), but GitHub releases
now show as codeman@x.y.z via a post-publish retag step.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
setup-node registry-url creates .npmrc with auth token reference,
and NODE_AUTH_TOKEN maps the NPM_TOKEN secret for changesets publish.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@manypkg/get-packages only includes workspace members in its packages list,
not the workspace root. Adding "." to workspaces makes the root package
discoverable, fixing "not in the workspace" errors from changeset version.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Root cause: toggleTunnelFromWelcome() sent the entire settings object
back to PUT /api/settings, but the Zod schema uses .strict() which
rejects unknown fields (lastUsedCase, localEchoEnabled, etc.). The PUT
silently failed, so the tunnel never started.
Fix: send only {tunnelEnabled: true/false} instead of the full blob.
Also added polling fallback for tunnel status and server-side re-broadcast
when tunnel is already running.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Re-broadcast tunnel:started SSE event when tunnel is already active
and user toggles the setting, so the client receives the URL.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Both are local state files. plan.json was already gitignored,
skills-lock.json now added to .gitignore.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove remotion-best-practices skill files from tracking.
Local files preserved, just hidden from the repo.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add _isStopped guard to OpenCode 3s readiness timeout (session.ts)
- Block respawn for opencode sessions on interactive-respawn and
respawn/enable routes (server.ts)
- Fail fast in direct PTY fallback for OpenCode mode (session.ts)
- Validate configContent as JSON at schema level (schemas.ts)
- Update JSDoc example for createSession options API (tmux-manager.ts)
- Un-hide Context tab for OpenCode sessions (index.html)
- Add OpenCode UI tests (opencode-resize.test.ts)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When running the dev server inside a tmux session, $TMUX is inherited
and tmux refuses to create new sessions ("sessions should be nested
with care, unset $TMUX to force"). Production (systemd) has a clean
env so this only affects dev/test. Strip $TMUX from the execSync env
when calling tmux new-session.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace dead .btn-claude selectors in mobile.css with proper styling
for the new split run button (.btn-run + .btn-run-gear). Add mobile
touch-friendly dropdown menu options (10px padding, 35px height).
Include mode-specific colors for both Claude (blue) and OpenCode
(green) on mobile. Also guard Claude-specific features (Ralph,
Respawn) from running on OpenCode sessions in server.ts.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
After the 3s TUI stabilization timeout, emit needsRefresh so the
client fetches the full terminal buffer. Without this, the terminal
appears empty until the user manually refreshes or switches tabs.
Wire needsRefresh as a proper session listener in server.ts with
cleanup in both removal paths to prevent memory leaks.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace separate "Run Claude" and "Run OC" buttons with a single
split button: [Run ▾] where the chevron opens a dropdown to switch
between Claude Code and OpenCode modes.
- Run button label shows "Run" (Claude) or "Run OC" (OpenCode)
- Button color reflects selected mode (blue=Claude, green=OpenCode)
- Mode persisted in localStorage across sessions
- Ctrl+Enter uses the selected mode
- Welcome screen simplified to single "Run" button
- Removed standalone btn-claude, btn-opencode, welcome-btn-opencode CSS
- Updated welcome tagline: "Manage AI in persistent tmux sessions"
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use a cursor-based prompt finder for OpenCode's Bubble Tea TUI:
- Custom finder locates ┃ (U+2503) border on the cursor's row
- Offset 3 skips "┃ " to reach the text input start position
- Prompt finder is swapped dynamically when switching between
Claude (❯ character) and OpenCode (┃ border) sessions
- Added setPrompt() method to ZerolagInputAddon for runtime updates
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The local echo overlay buffers keystrokes locally and renders them
in a DOM overlay anchored to the '>' prompt character. OpenCode's
Bubble Tea TUI uses a different prompt (┃), so the overlay can't
find it — keystrokes buffer invisibly and nothing appears on screen.
Disable local echo for OpenCode sessions so keystrokes flow directly
to the PTY via the normal input path.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>