-
Notifications
You must be signed in to change notification settings - Fork 157
Fix mobile terminal resize loop #305
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
🔍 Code Quality ReportThis comment is automatically updated with linting results from CI. Node.js Biome Formatting ✅ Status: PassedNode.js Biome Linting ✅ Status: PassedNode.js Test Coverage ✅ Status: PassedClient Coverage: Server Coverage: Mac Formatting (SwiftFormat) ✅ Status: PassedMac Linting (SwiftLint) ✅ Status: PassedmacOS Test Coverage ❌ Status: Failed• Coverage: 0% |
Claude review from 7/11/2025, 4:51:01 AM (outdated - click to expand)Claude review from 7/11/2025, 4:51:01 AM (outdated - click to expand)Claude review from 7/11/2025, 4:51:01 AM (outdated - click to expand)Claude review from 7/11/2025, 4:51:01 AM (outdated - click to expand)Claude review from 7/11/2025, 4:51:01 AM (outdated - click to expand)Claude finished @steipete's task —— View job Todo List:
📋 SummaryThis PR addresses mobile terminal resize loops by removing the complex ✅ Positive Aspects
🔍 Areas for ImprovementCode Quality
Potential Issues
Performance
Testing
💡 Suggestions
// In terminal.ts
private static readonly MOBILE_RESIZE_DELAY_MS = 200;
private static readonly MOBILE_BREAKPOINT_WIDTH = 768;
// In session-view.ts
private static readonly MOBILE_TERMINAL_HEIGHT = '85vh';
// utils/device-detection.ts
export function isMobileDevice(): boolean {
return window.innerWidth < 768 && 'ontouchstart' in window;
}
// In setupResize()
setTimeout(() => {
if (!this.terminal || !this.container) {
logger.warn('[Terminal] Mobile resize aborted - terminal not ready');
return;
}
logger.debug('[Terminal] Mobile: Executing single resize now');
this.fitTerminal('initial-mobile-only');
}, MOBILE_RESIZE_DELAY_MS);
const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent) ||
(window.innerWidth < 768 && navigator.maxTouchPoints > 0);🎯 Priority Items
Overall, this is a solid pragmatic fix for a complex problem. The simplification of the resize logic should improve stability significantly. The main improvements would be around code organization and edge case handling. Reviewed commit: 91b40f9 |
❌ CI FailedFailed Jobs:
|
Claude review from 7/11/2025, 5:02:46 AM (outdated - click to expand)Claude review from 7/11/2025, 5:02:46 AM (outdated - click to expand)Claude review from 7/11/2025, 5:02:46 AM (outdated - click to expand)Claude review from 7/11/2025, 5:02:46 AM (outdated - click to expand)Claude finished @steipete's task —— View job 📋 SummaryThis PR effectively addresses the mobile terminal resize loop issue by removing the complex ✅ Positive Aspects
🔍 Areas for ImprovementCode Quality Issues
Potential Issues
💡 SuggestionsPriority Fix - Mobile Detection:// In terminal.ts setupResize() - line 568
const isMobile = window.innerWidth < 768 && 'ontouchstart' in window;
this.isMobile = isMobile; // ADD THIS LINE to fix the bug
// Also store the timeout for cleanup
if (isMobile) {
this.mobileResizeTimeout = setTimeout(() => {
if (!this.terminal || !this.container) {
logger.warn('[Terminal] Mobile resize aborted - terminal not ready');
return;
}
logger.debug('[Terminal] Mobile: Executing single resize now');
this.fitTerminal('initial-mobile-only');
}, 200);
}Extract Constants:// At top of terminal.ts
private static readonly MOBILE_RESIZE_DELAY_MS = 200;
private static readonly MOBILE_BREAKPOINT_WIDTH = 768;
// In session-view.ts
private static readonly MOBILE_TERMINAL_HEIGHT = '85vh';Unified Mobile Detection:// utils/device-detection.ts
export function isMobileDevice(): boolean {
const hasTouch = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
const isNarrow = window.innerWidth < 768;
const isMobileUA = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
return (hasTouch && isNarrow) || isMobileUA;
}Cleanup Improvements:// Add to terminal.ts class properties
private mobileResizeTimeout: NodeJS.Timeout | null = null;
// In cleanup() method
if (this.mobileResizeTimeout) {
clearTimeout(this.mobileResizeTimeout);
this.mobileResizeTimeout = null;
}🎯 Priority Items
Additional NotesThe PR successfully addresses the resize loop issue identified in the debugging session. The approach of limiting mobile to a single resize is reasonable given the constraints of mobile browsers and their dynamic viewport behavior. However, the critical bug where Consider adding a manual "refresh layout" button for mobile users who need to adjust after orientation changes, as suggested in the original review. Reviewed commit: 91b40f9 |
Claude review from 7/11/2025, 5:26:18 AM (outdated - click to expand)Claude review from 7/11/2025, 5:26:18 AM (outdated - click to expand)Claude review from 7/11/2025, 5:26:18 AM (outdated - click to expand)Claude finished @steipete's task —— View job 📋 SummaryThis PR addresses the critical mobile terminal resize loop issue identified in Mario's debugging session. The solution removes the complex ✅ Positive Aspects
🔍 Areas for ImprovementCode Quality
Potential Issues
Performance
SecurityNo security issues identified. Testing
💡 Suggestions1. Extract Constants// terminal.ts
private static readonly MOBILE_RESIZE_DELAY_MS = 200;
private static readonly MOBILE_BREAKPOINT_WIDTH = 768;
// session-view.ts
private static readonly MOBILE_TERMINAL_HEIGHT = '85vh';2. Fix Mobile State Management// In setupResize() around line 586
if (this.isMobile) {
logger.debug('[Terminal] Mobile detected - scheduling single resize in 200ms');
this.mobileResizeTimeout = setTimeout(() => {
if (!this.terminal || !this.container) {
logger.warn('[Terminal] Mobile resize aborted - terminal not ready');
return;
}
logger.debug('[Terminal] Mobile: Executing single resize now');
// Ensure we go through requestResize to set this.isMobile properly
this.requestResize('initial-mobile-only');
}, 200);
}3. Cleanup Memory Leaks// Add to terminal.ts class properties
private mobileResizeTimeout: NodeJS.Timeout | null = null;
// In cleanup() method
if (this.mobileResizeTimeout) {
clearTimeout(this.mobileResizeTimeout);
this.mobileResizeTimeout = null;
}4. Cache Mobile Detectionprivate cachedIsMobile: boolean | null = null;
private detectMobile(): boolean {
if (this.cachedIsMobile !== null) {
return this.cachedIsMobile;
}
this.cachedIsMobile = (
/iPhone|iPad|iPod|Android/i.test(navigator.userAgent) ||
(navigator.maxTouchPoints !== undefined && navigator.maxTouchPoints > 1)
);
return this.cachedIsMobile;
}5. Consider Manual Resize OptionAdd a button in the mobile UI to manually trigger a resize for orientation changes or layout issues. 🎯 Priority Items
Overall, this is a solid pragmatic fix for a complex problem. The simplification of the resize logic should significantly improve mobile stability. The main concern is ensuring the mobile state is properly managed so the resize blocking actually works as intended. Reviewed commit: c815168 |
- Remove ResizeCoordinator entirely for simpler implementation - Mobile now gets exactly ONE resize 200ms after initialization - All subsequent resize attempts on mobile are blocked - Desktop retains normal resize behavior with observers - Fixes continuous scrolling/re-rendering issue on mobile devices The issue was that multiple resize triggers (viewport changes, keyboard, orientation) caused cascading resize events on mobile, leading to performance problems and visual glitches.
- Update action plan section with quotes from transcript - Add emphasis on immediate priority to fix endless scrolling - Improve formatting for better readability
- Update tests to expect fixed 85vh height on mobile - Update tests to expect NO fitTerminal calls on mobile - Mobile resize prevention is working as designed
- Unify mobile detection logic between index.html and terminal.ts - Use consistent User Agent + maxTouchPoints detection everywhere - Fix setupResize() to properly set this.isMobile class property - Ensure mobile resize blocking works correctly on initial load The AI was correct - there was a mismatch in mobile detection that could cause unpredictable resize behavior. Now using the same detection logic everywhere for consistency.
- Changed terminal resize logic to only block WIDTH changes on mobile - Renamed mobileResizeComplete to mobileWidthResizeComplete for clarity - Allow HEIGHT changes on mobile to support keyboard show/hide - Updated session-view to allow dynamic height calculations on mobile - Fixed tests to reflect new mobile resize behavior - Terminal now properly handles keyboard appearance without WIDTH resize loops
- Added isMobile and isHeightOnlyChange flags to terminal-resize event - Terminal lifecycle manager now skips sending height-only resizes on mobile - Prevents server resize commands when keyboard appears/disappears - Server only gets resize commands for actual WIDTH changes on mobile
c07d963 to
3b55b7c
Compare
Claude review from 7/11/2025, 5:40:27 AM (outdated - click to expand)Claude review from 7/11/2025, 5:40:27 AM (outdated - click to expand)Claude finished @steipete's task —— View job 📋 SummaryThis PR addresses the critical mobile terminal resize loop issue by removing the complex ✅ Positive Aspects
🔍 Areas for ImprovementCode Quality
Potential Issues
Performance
SecurityNo security issues identified. Testing
💡 Suggestions1. Fix Memory Leak// In terminal.ts, add class property:
private mobileResizeTimeout: NodeJS.Timeout | null = null;
// In setupResize():
if (this.isMobile) {
logger.debug('[Terminal] Mobile detected - scheduling initial resize in 200ms');
this.mobileResizeTimeout = setTimeout(() => {
if (!this.terminal || !this.container) {
logger.warn('[Terminal] Mobile resize aborted - terminal not ready');
return;
}
logger.debug('[Terminal] Mobile: Executing initial resize');
this.fitTerminal('initial-mobile-only');
logger.debug('[Terminal] Mobile: Initial width set, future WIDTH resizes blocked');
}, 200);
}
// In disconnectedCallback():
if (this.mobileResizeTimeout) {
clearTimeout(this.mobileResizeTimeout);
this.mobileResizeTimeout = null;
}2. Extract Constants// At top of terminal.ts
private static readonly MOBILE_RESIZE_DELAY_MS = 200;
private static readonly RESIZE_DEBOUNCE_MS = 250; // for terminal-lifecycle-manager3. Cache Mobile Detectionprivate cachedIsMobile: boolean | null = null;
private detectMobile(): boolean {
if (this.cachedIsMobile !== null) {
return this.cachedIsMobile;
}
this.cachedIsMobile = (
/iPhone|iPad|iPod|Android/i.test(navigator.userAgent) ||
(navigator.maxTouchPoints !== undefined && navigator.maxTouchPoints > 1)
);
return this.cachedIsMobile;
}4. Consider Manual Resize OptionFor mobile users who need to adjust after orientation changes, consider adding a button or gesture to manually trigger a resize. 🎯 Priority Items
Overall, this is a solid pragmatic fix that should significantly improve mobile stability by eliminating the resize loop issue. The simplification by removing ResizeCoordinator is a good architectural decision. The main concerns are around the memory leak and missing test coverage for the mobile-specific behavior. Reviewed commit: c815168 |
…-pty - Rename vendored-pty directory to node-pty for consistency - Add built-in SEA (Single Executable Application) support to unixTerminal.ts - Remove patching logic from build-native.js as SEA support is now native - Update macOS deployment target to 14.0 in binding.gyp - Fix native executable build issues by using process.dlopen in SEA mode
CI needs the compiled JavaScript files since it doesn't build TypeScript for node_modules. Remove lib/ from .gitignore and commit compiled files.
Claude error from 7/11/2025, 6:08:13 AM (outdated - click to expand)Claude error from 7/11/2025, 6:08:13 AM (outdated - click to expand)Claude encountered an error —— View job Claude finished @steipete's task —— View job Todo List:
📋 SummaryThis PR addresses the critical mobile terminal resize loop issue by removing the complex ✅ Positive Aspects
🔍 Areas for ImprovementCode Quality
Potential Issues
Performance
SecurityNo security issues identified. Testing
💡 Suggestions1. Fix Memory Leak// In terminal.ts, add class property:
private mobileResizeTimeout: NodeJS.Timeout | null = null;
// In setupResize():
if (this.isMobile) {
logger.debug('[Terminal] Mobile detected - scheduling initial resize in 200ms');
this.mobileResizeTimeout = setTimeout(() => {
if (!this.terminal || !this.container) {
logger.warn('[Terminal] Mobile resize aborted - terminal not ready');
return;
}
logger.debug('[Terminal] Mobile: Executing initial resize');
this.fitTerminal('initial-mobile-only');
logger.debug('[Terminal] Mobile: Initial width set, future WIDTH resizes blocked');
}, 200);
}
// In disconnectedCallback():
if (this.mobileResizeTimeout) {
clearTimeout(this.mobileResizeTimeout);
this.mobileResizeTimeout = null;
}2. Extract Constants// At top of terminal.ts
private static readonly MOBILE_RESIZE_DELAY_MS = 200;
private static readonly RESIZE_DEBOUNCE_MS = 250; // for terminal-lifecycle-manager3. Cache Mobile Detectionprivate cachedIsMobile: boolean | null = null;
private detectMobile(): boolean {
if (this.cachedIsMobile !== null) {
return this.cachedIsMobile;
}
this.cachedIsMobile = (
/iPhone|iPad|iPod|Android/i.test(navigator.userAgent) ||
(navigator.maxTouchPoints !== undefined && navigator.maxTouchPoints > 1)
);
return this.cachedIsMobile;
}4. Consider Manual Resize OptionFor mobile users who need to adjust after orientation changes, consider adding a button or gesture to manually trigger a resize. 🎯 Priority Items
Overall, this is a solid pragmatic fix that should significantly improve mobile stability by eliminating the resize loop issue. The simplification by removing ResizeCoordinator is a good architectural decision. The main concerns are around the memory leak and missing test coverage for the mobile-specific behavior. Reviewed commit: 8e3d2de |
|
Claude finished @steipete's task —— View job Todo List:
📋 SummaryThis PR addresses the critical mobile terminal resize loop issue identified in Mario's debugging session by removing the complex ✅ Positive Aspects
🔍 Areas for ImprovementCode Quality
Potential Issues
Performance
SecurityNo security issues identified. Testing
💡 Suggestions1. Fix Memory Leak// In terminal.ts, add class property:
private mobileResizeTimeout: NodeJS.Timeout | null = null;
// In setupResize():
if (this.isMobile) {
logger.debug('[Terminal] Mobile detected - scheduling initial resize in 200ms');
this.mobileResizeTimeout = setTimeout(() => {
if (!this.terminal || !this.container) {
logger.warn('[Terminal] Mobile resize aborted - terminal not ready');
return;
}
logger.debug('[Terminal] Mobile: Executing initial resize');
this.fitTerminal('initial-mobile-only');
logger.debug('[Terminal] Mobile: Initial width set, future WIDTH resizes blocked');
}, 200);
}
// In disconnectedCallback():
if (this.mobileResizeTimeout) {
clearTimeout(this.mobileResizeTimeout);
this.mobileResizeTimeout = null;
}2. Extract Constants// At top of terminal.ts
private static readonly MOBILE_RESIZE_DELAY_MS = 200;
private static readonly RESIZE_DEBOUNCE_MS = 250; // for terminal-lifecycle-manager3. Cache Mobile Detectionprivate cachedIsMobile: boolean | null = null;
private detectMobile(): boolean {
if (this.cachedIsMobile !== null) {
return this.cachedIsMobile;
}
this.cachedIsMobile = (
/iPhone|iPad|iPod|Android/i.test(navigator.userAgent) ||
(navigator.maxTouchPoints !== undefined && navigator.maxTouchPoints > 1)
);
return this.cachedIsMobile;
}4. Fix State Synchronization// In shouldResize() method, update tracking even when rejecting:
if (widthChanged) {
logger.debug(`[Terminal] Preventing WIDTH resize on mobile (width already set)`);
// Update lastCols to prevent repeated detection
this.lastCols = cols;
return false;
}5. Consider Manual Resize OptionFor mobile users who need to adjust after orientation changes, consider adding a button or gesture to manually trigger a resize. 🎯 Priority Items
Overall, this is a solid pragmatic fix that should significantly improve mobile stability by eliminating the resize loop issue. The simplification by removing ResizeCoordinator is a good architectural decision. The main concerns are around the memory leak, state synchronization, and missing test coverage for the mobile-specific behavior. Reviewed commit: fef322a |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: Stale State Causes Incorrect Resize Blocking
The mobile terminal resize logic in shouldResize suffers from inconsistent state management and incorrect blocking. When a width change is detected and prevented (after initial setup), this.lastCols is not updated to the new calculated width. This leaves the internal state stale, causing subsequent calls with the same dimensions to repeatedly detect a "width change" and incorrectly block legitimate height-only resizes (e.g., from keyboard events) if a width change is also present. The fix requires consistently updating this.lastCols and this.lastRows to the current calculated dimensions within shouldResize, regardless of whether the terminal's resize() method is ultimately called.
web/src/client/components/terminal.ts#L271-L312
vibetunnel/web/src/client/components/terminal.ts
Lines 271 to 312 in fef322a
| private shouldResize(cols: number, rows: number): boolean { | |
| // On mobile, prevent WIDTH changes after initial setup, but allow HEIGHT changes | |
| if (this.isMobile && this.mobileWidthResizeComplete) { | |
| // Check if only height changed (allow keyboard resizes) | |
| const widthChanged = this.lastCols !== cols; | |
| const heightChanged = this.lastRows !== rows; | |
| if (widthChanged) { | |
| logger.debug(`[Terminal] Preventing WIDTH resize on mobile (width already set)`); | |
| return false; | |
| } | |
| if (heightChanged) { | |
| logger.debug( | |
| `[Terminal] Allowing HEIGHT resize on mobile: ${this.lastRows} → ${rows} rows` | |
| ); | |
| this.lastRows = rows; | |
| return true; | |
| } | |
| return false; | |
| } | |
| // Check if dimensions actually changed | |
| const changed = this.lastCols !== cols || this.lastRows !== rows; | |
| if (changed) { | |
| logger.debug( | |
| `[Terminal] Dimensions changed: ${this.lastCols}x${this.lastRows} → ${cols}x${rows}` | |
| ); | |
| this.lastCols = cols; | |
| this.lastRows = rows; | |
| // Mark mobile WIDTH resize as complete after first resize | |
| if (this.isMobile && !this.mobileWidthResizeComplete) { | |
| this.mobileWidthResizeComplete = true; | |
| logger.debug(`[Terminal] Mobile WIDTH resize complete - blocking future width changes`); | |
| } | |
| } | |
| return changed; | |
| } |
Was this report helpful? Give feedback by reacting with 👍 or 👎
Summary
Problem
Based on debugging session findings, mobile devices were experiencing continuous resize loops due to:
Solution
Test Plan