- Update session.sessionJsonDebounceTimer when creating new timers
- Clear timer reference after callback execution
- Remove incorrect initial null assignment
- Fixes memory leak where timers continued after session cleanup
The bug occurred because the timer ID was only stored in a local variable,
preventing proper cleanup during session termination.
- Add --update-title flag to fwd.ts for cleaner session title updates
- Replace complex bash/jq/python/sed logic with single fwd.ts call
- Leverage existing SessionManager for type-safe JSON handling
- Improve error handling and messaging
- Reorganize vt script to locate app path before processing commands
This eliminates the fragile bash JSON manipulation in favor of a
robust TypeScript implementation that reuses existing infrastructure.
- Fix command injection vulnerability by using jq --arg for safe parameter passing
- Add Python fallback for systems without jq for safe JSON manipulation
- Improve sed fallback with proper escaping (last resort)
- Add debouncing (100ms) to file watcher to prevent rapid updates
- Add error handling for file watcher failures
- Clean up debounce timer on session cleanup
- Fix issue where lastSessionName wasn't properly tracked
Security fix prevents arbitrary command execution through malicious title strings.
Debouncing prevents performance issues from rapid file changes.
- Add error handling for 'vt title' when used outside a session
- Create unit tests for session.json watcher in PtyManager
- Add integration tests for vt title command with edge cases
- Test all 4 title modes (none, filter, static, dynamic)
- Test special characters, concurrent updates, and error scenarios
- Ensure proper cleanup of file watchers on session exit
- Add 'vt title' command that updates session names from within terminal sessions
- Implement file watcher in PtyManager to detect session.json changes
- Integrate title updates with all 4 title modes (none, filter, static, dynamic)
- Updates terminal titles in real-time when session name changes
- Works by directly editing session.json using the VIBETUNNEL_SESSION_ID env var
This allows users to easily set descriptive names for their terminal sessions,
making it easier to manage multiple VibeTunnel windows.
* Implement ultra-low-latency WebSocket input system
This change eliminates input lag on slow networks by replacing HTTP requests
with a fire-and-forget WebSocket system for terminal input transmission.
Key optimizations:
- Raw text transmission (no JSON overhead): 1 byte vs 87+ bytes per keystroke
- Fire-and-forget input (no ACK blocking): eliminates 20-50ms roundtrip latency
- Single persistent connection per session: zero connection overhead
- Direct PTY write path: fastest possible server processing
- Graceful HTTP fallback: maintains full backward compatibility
Performance improvements:
- 99% bandwidth reduction per keystroke
- 90% latency reduction on slow networks
- Zero blocking waits for rapid typing
- Eliminates HTTP/1.1 connection overhead
Files changed:
- Add: src/client/services/websocket-input-client.ts (WebSocket client)
- Add: src/server/routes/websocket-input.ts (WebSocket input handler)
- Modify: src/client/components/session-view/input-manager.ts (WebSocket integration)
- Modify: src/server/server.ts (WebSocket routing for /ws/input endpoint)
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Add socket_input URL parameter feature flag
Adds ?socket_input=true/false URL parameter to control WebSocket input behavior:
- socket_input=true (default): Enable WebSocket input with HTTP fallback
- socket_input=false: Force HTTP-only input mode (disable WebSocket)
This feature flag enables:
- A/B testing between WebSocket and HTTP input performance
- Debugging WebSocket connection issues
- Gradual rollout control
- Easy fallback mechanism for production issues
Examples:
- http://localhost:4020/?socket_input=true&session=abc123 (WebSocket enabled)
- http://localhost:4020/?socket_input=false&session=abc123 (HTTP only)
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Fix WebSocket input key mapping for special keys
The server was receiving key names like "enter" and writing them literally
as text instead of interpreting them as actual key presses.
Added proper key mapping:
- "enter" → '\r' (carriage return)
- "escape" → '\x1b' (ESC key)
- "backspace" → '\x7f' (DEL)
- "tab" → '\t' (TAB)
- "arrow_up" → '\x1b[A' (VT100 up arrow)
- "arrow_down" → '\x1b[B' (VT100 down arrow)
- "arrow_left"/"arrow_right" → '\x1b[D'/'\x1b[C' (VT100 arrows)
- Function keys F1-F12 → proper VT100 sequences
- Regular text → sent as-is
Now "enter" actually triggers ENTER key instead of typing "enter" text.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Reuse existing key mapping logic instead of duplicating
Instead of duplicating the key mapping logic (enter → \r, etc.), now properly
reuse the existing PtyManager.sendInput() method which already handles:
- Key mapping via convertSpecialKey() method
- SessionInput type validation
- Special key vs regular text determination
- Error handling and session management
This ensures consistency between HTTP /input and WebSocket /ws/input endpoints
and avoids maintaining duplicate key mapping tables.
Benefits:
- Single source of truth for key mappings
- Consistent behavior across input methods
- Proper type safety with SessionInput/SpecialKey types
- Reuses existing error handling and validation
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Fix special key detection in pasted text using null-byte markers
Problem: Pasting text like "i am entering the world" would incorrectly
interpret "enter" as the ENTER key instead of literal text.
Solution: Use null-byte markers to distinguish special keys from literal text:
- Special keys: "\x00enter\x00" → ENTER key press
- Regular text: "enter" → literal text "enter"
- Pasted text: "i am entering the world" → literal text
This maintains the raw text protocol while solving the ambiguity:
- Single keystroke "enter" → "\x00enter\x00" → ENTER key
- Pasted word "enter" → "enter" → literal text "enter"
- Multi-word paste → always literal text
Benefits:
- Preserves ultra-minimal bandwidth (just 2 null bytes overhead)
- Maintains raw text protocol (no JSON)
- Solves paste ambiguity correctly
- Null bytes rarely appear in normal text input
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Fix paste text ambiguity and control sequence handling
- Modified sendInputText to always treat pasted content as literal text
- Added sendControlSequence method for control characters like Ctrl+R
- Updated direct keyboard manager to use sendControlSequence for control chars
- This ensures pasted text containing words like "enter", "backspace" is sent as literal text
- Control sequences like Ctrl+R (\x12) are properly transmitted via WebSocket with null-byte escaping
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Add debug logging for WebSocket input transmission
- Added detailed logging on client side to show what's being sent
- Added server side logging to show what's being received
- This will help debug the enter key transmission issue
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Add comprehensive input logging to track key processing
- Added logging in WebSocket handler to show parsed input
- Added logging in PtyManager to show key conversion and output
- This will show the complete flow: received key -> parsed input -> converted output
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Add DEBUG environment variable for enabling debug logging
- Added DEBUG=true environment variable option alongside --debug flag
- Makes it easier to enable debug logging during development
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Fix WebSocket input handling for HQ mode and mobile keyboards
- Add WebSocket proxy support for remote sessions in HQ mode
- Fix mobile keyboard special key handling to use sendInput() instead of sendInputText()
- Make InputManager.sendInput() public for use by mobile components
- Update DirectKeyboardManager to correctly send special keys from custom keyboard
- Handle WebSocket data type conversion for native WebSocket API compatibility
This ensures special keys are properly wrapped with null bytes (\x00) when sent
via WebSocket, and enables low-latency input for remote sessions in HQ mode.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
* refactor: eliminate code duplication in input-manager and fix TypeScript typing
- Extract common WebSocket/HTTP fallback logic into sendInputInternal()
- Reduce ~155 lines of duplicate code across sendInput, sendInputText, and sendControlSequence methods
- Add proper WebSocketRequest interface to replace any type usage
- Fix linting issues in server.ts WebSocket handling
* up
* Add comprehensive tests for WebSocket input handler
- Test special key handling with null-byte wrapped keys (\x00enter\x00)
- Test text containing key names ('i enter the world') treated as literal text
- Test HQ mode remote session proxying vs local PTY handling
- Test edge cases: empty messages, malformed keys, binary data, Unicode
- Test error handling and connection lifecycle
- Remove duplicate special key validation logic
- Delegate key conversion to ptyManager for consistency
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Fix TypeScript linting issues in WebSocket input handler tests
- Replace all 'any' types with proper type definitions
- Add MockEventListener type for test event handlers
- Use 'unknown' instead of 'any' for type assertions
- Remove unused SessionInput import
- Fix formatting issues per Biome requirements
All 20 WebSocket input handler tests now pass with proper TypeScript types.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Mario Zechner <badlogicgames@gmail.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
- Tests now expect all Windows drive letters (A-Z) to be replaced with ~
- This matches the updated regex pattern that supports any drive letter
- Tests verify D:, E:, Z:, and lowercase drive letters work correctly
- Update regex pattern to support all drive letters A-Z (not just C:)
- Fix linting warning about unnecessary escape in character class
- Now properly handles paths like D:\Users\, E:\Users\, etc.
The working directory input was displaying formatted paths (~/Documents) but
storing them as-is when edited, causing backend errors since it expects
absolute paths.
Reverted to showing raw paths in the input field. The file browser component
is unaffected as it correctly only formats for display, not for input.
This ensures workingDir always contains absolute paths as expected by the API.
- Add formatPathForDisplay to session-create-form working directory input
- Add formatPathForDisplay to file-browser path display
- Users now see ~/Documents instead of /Users/username/Documents everywhere
- Maintains raw paths for editing and API calls, only formats for display
- The formatPathForDisplay function is extremely cheap (single regex replace)
- LitElement already optimizes renders efficiently
- Removing memoization reduces code complexity and memory overhead
- The performance difference is negligible for such a simple operation
- Optimize path formatting with pre-compiled regex for better performance
- Add memoization to clickable-path component to avoid re-computations
- Add comprehensive tests for usernames with regex special characters
- Simplify clipboard tests for more reliable test execution
- Fix potential issues with special characters in usernames breaking regex patterns
- Handle Windows paths with forward slashes (C:/Users/username)
- Support case-insensitive Windows drive letters (c:\ vs C:\)
- Fix multiple home directory pattern replacement using single regex
- Add comprehensive tests for all edge cases
- Update JSDoc comments with proper formatting
- Keep parallel execution for read-only checks (format:check, lint, typecheck)
- Make check:fix run sequentially to prevent file write conflicts
- Add documentation explaining why sequential execution is needed
- Based on best practices: lint-staged and JavaScript community recommendations
- Add tests for formatPathForDisplay covering all platforms
- Create parallel code quality check script (pnpm run check)
- Add auto-fix command for format and lint issues (pnpm run check:fix)
- Document the improved workflow in DEVELOPMENT.md
- Update CLAUDE.md with the new commands
- Remove all server-side path formatting logic
- Implement simple regex-based client-side formatting
- Support macOS, Linux, Windows, and root user paths
- Simpler implementation with no API changes needed
- Better separation of concerns (UI formatting in UI layer)
- Delete formatPathForDisplay function from path-utils.ts
- Update all components to use displayWorkingDir from server
- Clean up imports in affected components
- Add refactoring philosophy note to CLAUDE.md
- Replace hardcoded home directory with dynamic detection using os.homedir()
- Add server-side path formatting using existing abbreviatePath utility
- Add displayWorkingDir field to Session interface
- Fix mobile header overflow with proper width constraints
- Update clickable-path component to accept pre-formatted display paths
This fixes both the regression where ~ substitution was broken and the
mobile header pushing content off-screen when session paths are long.
Fixes#96
* Add keyboard shortcut highlighter to terminal
- Create keyboard-shortcut-highlighter.ts with comprehensive pattern matching
- Integrate highlighter into terminal component alongside URL highlighting
- Support clickable shortcuts like "Ctrl+R", "Ctrl+A", "esc to interrupt"
- Style shortcuts with greyish color and dotted underlines (not blue links)
- Send actual key sequences when shortcuts are clicked
- Handle overlapping matches and avoid double-processing
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Fix keyboard shortcut input handling - add missing event listener
The keyboard shortcut highlighter was dispatching 'terminal-input' events
but the session-view component wasn't listening for them. Added:
- @terminal-input event listener on vibe-terminal component
- handleTerminalInput method that forwards to inputManager.sendInputText()
Now clicked shortcuts like 'Ctrl+R' properly send input to the terminal.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Add Claude Code interactive prompt support to keyboard shortcut highlighter
Make numbered options in Claude Code prompts clickable:
- Pattern: '❯ 1. Yes' or ' 2. Yes, and don't ask again'
- Clicking sends the number (1, 2, 3, etc.) to terminal
- Handles both selected (❯) and unselected options
- Uses multiline regex matching for line-start patterns
Now users can click numbered options instead of typing numbers.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Fix Claude Code prompt patterns to handle indented options
Previous patterns required line-start anchors which failed for indented
options within bordered terminal output. Updated patterns:
- Remove restrictive line-start anchors (^)
- Add whitespace-flexible patterns for indented options
- Support both cursor-selected (❯) and plain numbered options
- Handle 'Yes, proceed' and 'No, exit' style options
Now correctly matches:
'❯ 1. Yes, proceed' and ' 2. No, exit' patterns.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Simplify Claude Code prompt patterns to be fully generic
Replace keyword-specific patterns with generic numbered option matching:
- ❯ 1. (cursor-selected options)
- 1. xxxxx (any numbered option with text)
Now matches any numbered list item regardless of content:
'1. Yes, proceed', '2. No, exit', '3. Maybe later', etc.
Much cleaner and more flexible than keyword-based matching.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Fix Claude Code prompt patterns to highlight full option lines
Changed from matching only first word to matching entire lines:
- /❯\s*(\d+)\.\s+.*/ - highlights complete cursor-selected option
- /(\d+)\.\s+.*/ - highlights complete numbered option
Now '1. Yes, proceed' and '2. No, exit' are fully underlined
instead of just '1. Yes' and '2. No'.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
Implement WriteQueue to serialize all async write operations and handle backpressure:
- Add shared WriteQueue utility class for serializing async operations
- Move write queue inside AsciinemaWriter to handle all write methods
- Add backpressure handling using stream 'drain' events
- Ensure all AsciinemaWriter methods (writeHeader, writeRawJson) use queue
- Add drain() method to wait for pending writes on close
- Store stdout queue reference in PtySession for cleanup on process exit
This prevents race conditions where concurrent fs.fsync calls could interleave,
causing out-of-order disk flushes and potential data corruption.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
Added comprehensive IME composition support to prevent duplication of Japanese, Chinese, and Korean text input on mobile devices.
Changes:
- DirectKeyboardManager: Added composition event handlers to wait for final text
- MobileInputOverlay: Added composition state tracking to prevent intermediate updates
- Fixed null safety issues and unused parameter warnings
- Comprehensive documentation explaining IME implementation
Bug Fixed:
Previously, intermediate composition characters were sent to terminal during Japanese typing, causing duplicated text. Now properly waits for composition completion before sending final text to terminal.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-authored-by: Claude <noreply@anthropic.com>
* Add Shift+Tab key to mobile QuickKeyboard per PR #105 feedback
Implements Peter's suggestion to add Shift+Tab key to mobile quick keyboard
for enhanced Claude Code planning mode support on mobile devices.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Fix Monaco Editor keyboard input cancellation
Enhanced isKeyboardShortcut detection to properly handle Monaco Editor
elements, preventing keyboard event interference while maintaining
terminal functionality.
- Added contentEditable detection
- Added .monaco-editor container detection
- Added data-keybinding-context detection
- Added .editor-container detection
This resolves the 'Canceled: Canceled' errors when typing in Monaco Editor
while preserving Shift+Tab functionality for Claude Code planning mode.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
Fixed the 'command not found' error for shell aliases like claude command.
Root Cause:
Previously, commands were executed as:
/bin/zsh -c 'source ~/.zshrc 2>/dev/null || true; claude'
Shell alias expansion happens at parse time, before the source command runs,
so aliases defined in .zshrc were not available during command parsing.
Solution:
Use interactive mode with login shell flags:
/bin/zsh -i -l -c 'claude'
The -i flag enables interactive mode (loads aliases)
The -l flag makes it a login shell (sources RC files)
This ensures aliases are properly loaded and expanded before command execution.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-authored-by: Claude <noreply@anthropic.com>
* Prevent recursive VibeTunnel sessions (GitHub #95)
Added detection and prevention of nested VibeTunnel sessions to avoid
recursive 'vt' command execution.
Changes:
- Set INSIDE_VIBETUNNEL and VIBETUNNEL_SESSION environment variables when creating PTY sessions
- Added check in vt script to detect if already inside a VibeTunnel session
- Shows helpful error message when recursive session is attempted
This prevents the confusing behavior where users could run 'vt' inside a
VibeTunnel session, creating nested terminal instances. Now users get a
clear error message explaining they're already in a VibeTunnel session.
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
* Improve recursive session prevention based on PR feedback
- Consolidate to single environment variable VIBETUNNEL_SESSION_ID
- Add session ID for debugging purposes as requested
- Show session ID in error message for better user feedback
- Remove redundant environment variables (INSIDE_VIBETUNNEL, VIBETUNNEL_SESSION)
This addresses feedback from PR #104 to use a single, more informative
environment variable that serves both purposes: preventing recursion
and providing debugging information.
---------
Co-authored-by: Claude <noreply@anthropic.com>