- 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>
Save activeSessionId to localStorage on tab switch and restore it in
handleInit so reloading the page jumps back to the session you were
viewing instead of always selecting the first tab.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Added IS_TEST_MODE (process.env.VITEST) guards to every method in TmuxManager and
ScreenManager that touches real tmux/screen sessions. Tests can never create, kill,
discover, or send input to real sessions. Removed broken E2E test suite entirely.
Rewrote test/setup.ts from 459 lines to minimal cleanup. Rewrote tmux-related tests
to verify test-mode safety behavior.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Enable tmux mouse mode on session creation for click-to-select panes
- Auto-discover teammate panes via tmux list-panes when config.json lacks paneIds
- Enable mouse mode dynamically when team pane streams start (restored sessions)
- Suppress teammate subagent popup windows (false info); teammates use main tmux panes
- Remove standalone teammate terminal windows in favor of native tmux interaction
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
feat: interactive teammate tmux pane windows with xterm.js terminals
feat: auto-cleanup teams/subagents/pane windows on session delete
fix: only show agent/teammate windows when matching Claudeman tab exists
fix: UTF-8 encoding in teammate pane terminal output (Uint8Array)
fix: standalone pane window cleanup via subagentParentMap lookup
fix: xterm.js dimensions crash with deferred init + null-safe dispose
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The optimization introduced a duplicate 'session' variable declaration
that caused a SyntaxError, breaking the web UI entirely.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Increase chunked write size from 64KB to 128KB (halves write time)
- Start buffer fetch immediately before other setup work
- Show "Loading session..." indicator during fetch
- Parallelize session attach with buffer fetch
- Fire-and-forget resize call (don't block on it)
Reduces perceived tab switch latency by ~50-100ms for large buffers.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add Case button to mobile toolbar (between Run Claude and Run Shell)
- Add bottom sheet modal for case selection on mobile
- Show current case name on button (truncated)
- Allow creating new cases from mobile picker
- Sync selection with desktop dropdown
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Fix Run Claude button being squeezed on mobile (use fit-content width)
- Remove Send button from accessory bar
- Add /init, /clear, /compact buttons with confirmation dialogs
- Center toolbar buttons instead of stretching
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Added relativePath field to ImageDetectedEvent
- Compute relative path from working directory in image-watcher
- Use relativePath in frontend URL (fixes 'Failed to load image' for subdirs)
- Minor CLAUDE.md improvements
chore: bump version to 0.1461
- Terminal has hidden textarea that was triggering scrollIntoView
- Exclude elements inside .xterm or .terminal-container
- Fixes scroll being broken when tapping terminal
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove all complex viewport tracking (was causing scroll issues)
- Remove keyboard-visible class and CSS (not needed)
- Remove --keyboard-height CSS variable (not needed)
- Simple approach: on focusin, wait 400ms, scrollIntoView
- Keep scroll-margin on phone inputs as CSS fallback
- Much more reliable and no side effects
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Revert interactive-widget viewport meta (caused iOS layout shifts)
- Delay initial viewport height capture to let viewport stabilize
- Update initialViewportHeight on window resize (address bar changes)
- Remove visualViewport scroll handler (not needed, caused issues)
- Scope keyboard CSS to .modal.active only
- Use vh instead of dvh for more stable layout
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove top/bottom positioning from modal-content when keyboard visible
- Reset --viewport-offset-top to 0 when keyboard closes
- Add focusout handler to help reset keyboard state on iOS
- Simplify modal keyboard CSS to only adjust max-height
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add KeyboardHandler module using visualViewport API for keyboard detection
- Set --keyboard-height CSS variable and toggle .keyboard-visible class
- Auto-scroll focused inputs into view after keyboard appears (350ms delay)
- Update viewport meta with interactive-widget=resizes-content for Android
- Add scroll-margin on inputs for tablet/phone breakpoints
- Resize modal-content and modal-body when keyboard visible on mobile
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Rearranged bottom toolbar: Run Claude + Run Shell left, + case selector center
- Made Run Claude button gray instead of blue gradient
- Fixed mobile CSS that was hiding toolbar-center with display:none
- Reduced toolbar height 20%, increased header height
- Tighter session tab styling (smaller padding, font, border-radius)
- Added version display to header next to logo
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
xterm.js has known limited mobile touch support because the viewport
sits underneath the screen rows. Fixed by:
- Setting xterm-viewport z-index: 10 on mobile to receive touch events
- Enabling native browser scrolling with touch-action: pan-y
- Adding -webkit-overflow-scrolling: touch for iOS momentum
- Skipping custom JS scroll handlers on mobile devices
- Moving wizard drag listeners to only attach during actual drag
See: https://github.com/xtermjs/xterm.js/issues/5377
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The imagePopups Map was not being cleaned up during SSE reconnects,
causing memory leaks from orphaned drag event listeners on document.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Backend fixes:
- Session: clear _recentTaskDescriptions Map in stop() and clearBuffers()
- Session: null promise callbacks after rejection in runPrompt() catch block
- Server: store subagent/image watcher listener refs and remove on shutdown
Frontend fixes:
- Plan file windows: store drag/resize handlers and clean up on close
- Plan file manager: store drag handler and clean up on close
- cleanupAllFloatingWindows: now cleans up plan file windows
These prevent unbounded memory growth in long-running sessions.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add --session-id flag to Claude CLI startup so Claudeman and Claude share the same session ID
- Set claudeSessionId immediately on session start (no waiting for JSON messages)
- Add persistent subagentParentMap to store agent-to-tab associations permanently
- Add /api/subagent-parents endpoints for server-side persistence
- Add fallback: use active session when claudeSessionId matching fails
- Connection lines now reliably connect agent windows to their parent tabs
- Survives page refresh, reconnect, and server restart
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>