handleInit() called KeyboardHandler.cleanup() and MobileDetection.cleanup()
on every SSE connect but never re-initialized them, breaking keyboard
scroll-into-view and the /init /clear accessory bar on mobile.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Renamed "Two-Row Tabs" to "Tall Tabs (Name + Folder)" for clarity
- Tab folder path now only rendered in JS when setting is ON (not just CSS hidden)
- CSS specificity fix: mobile.css now matches .tabs-two-rows specificity (0,2,0)
- Server settings sync: display keys seed from server on fresh localStorage
- Bumped cache buster versions to 0.1534 to force browser reload
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Send SIGWINCH to server on keyboard open/close so Ink renders at the
correct terminal dimensions. Previously only local xterm was resized
while server resize was suppressed, causing Ink to redraw at the old
(larger) row count on each keystroke — pushing content off screen.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove confirm() dialog which dismissed keyboard and broke Enter delivery
- Send command text and Enter as separate requests with 120ms delay so Ink processes them as distinct events
- Add double-tap confirmation for /clear and /compact (amber "Tap again" state, 2s timeout)
- /init sends immediately (non-destructive)
- Refocus terminal after first tap on confirm buttons to keep keyboard open
- Match accessory button font size to toolbar buttons (0.65rem)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Three bugs fixed in KeyboardHandler:
1. onKeyboardShow() used sessionData.terminal (always undefined) instead of app.terminal
2. Keyboard dismiss threshold (50px) too tight for iOS address bar drift — widened to 100px
3. No terminal refit on keyboard hide — caused black gap below terminal
Also added: dynamic baseline update for initialViewportHeight, safety check in
updateLayoutForKeyboard() that forces dismiss when keyboardOffset <= 0.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Clear teams/teamTasks maps in handleInit() on SSE reconnect
- Fix saveNowAsync() race condition with in-flight promise guard
- Add setMaxListeners() on Session, WebServer, SubagentWatcher,
ImageWatcher, TmuxManager to prevent MaxListenersExceeded warnings
- Debounce persistSessionState() per-session (100ms) to reduce
redundant toState() serialization across 25+ call sites
- Cache getLightSessionsState() with 1s TTL to avoid re-serializing
all sessions on every SSE connect and /api/sessions request
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Convert sync readFileSync/statSync to async in subagent-watcher.ts to
unblock event loop on hot paths (transcript reads, liveness checks)
- Fix Map mutation during iteration in closeSessionLogViewerWindows and
closeSessionImagePopups (collect IDs first, then iterate to close)
- Unify session cleanup into shared _cleanupSessionData() method called
from both closeSession() and session:deleted handler to prevent leaks
- Add Zod validation schemas for 20+ API routes that used raw type casts
- Add consecutive error tracking (5 errors/60s triggers exit for systemd
restart) and SIGHUP handler for SSH disconnect safety
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1. handleInit crash: commit cdc822d removed Map initializations for
teammateTerminals, teammatePanesByName, teams, teamTasks, teammateMap
from the constructor but left cleanup code that iterates them.
cleanupAllFloatingWindows() crashed on "not iterable", preventing
ALL frontend data (sessions, subagents) from loading.
2. claudeSessionId null on recovered sessions: only set inside
startInteractive(), never in constructor or persisted. After server
restart, recovered sessions had null claudeSessionId, so the
hasMatchingTab check always failed → no subagent windows.
Fixes:
- Re-add all 5 missing Map initializations in app.js constructor
- Set _claudeSessionId = this.id in Session constructor (Claudeman
always passes --session-id to Claude, so they always match)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
handleInit is called twice on page load (loadState + SSE init). The second
call cleared terminal state but skipped selectSession because activeSessionId
was still set from the first call. Now reset activeSessionId before
re-selecting so the terminal buffer always gets reloaded.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>