- Remove chicken-and-egg check that prevented fit() when cols/rows were 0
- Add console logging for container dimensions and fit results
- Add retry limit (50 attempts) for initial fit to prevent infinite loops
- Add includes section to profile editor with drag-and-drop reordering
- Display included profiles with scope badges (Global, Project, Tool)
- Add 'Add Include' dropdown filtered by compatibility and cycle prevention
- Add remove button per include row
- Save includes together with profile form
- Add include count badges to profile list sidebar
- Add drag icon to Icon component
Implements config-profile-includes-ui tasks 1.1-4.3
- Add per-item busy overlay to SessionCard component
- Remove full-screen loading overlay from sessions page
- Remove loadingAction state, use per-item busy state only
- Add handleStart to sessions page for consistency
- Add session-card CSS for busy overlay positioning
- Both home and sessions pages now use same per-item loading pattern
- Add retry logic for transient network errors in API client
- Retry up to 2 times with exponential backoff on network errors
- Reduce session polling from 10s to 30s to decrease error frequency
- Handle 502/503/504 gateway errors with retries as well
- Add position: relative to create-session-form-wrapper so overlay fills only the form
- Remove text from instance busy overlay, show only spinner
- Delete progress indicator now fills only the target card
- Open terminal before calling fitTerminal() to avoid race conditions
- Add container dimension checks before fitting
- Add guards to prevent fit/refresh with 0x0 dimensions
- Only send resize messages when dimensions are valid
- Prevent xterm.js internal errors from invalid dimension access
- Replace full-screen loading with per-instance busy state
- Add busy overlay with spinner to instance cards
- Disable action buttons while instance is busy
- Add CSS for visual dimming and overlay positioning
Fixes add-config-profiles: instance UI polish
Frontend:
- Clear xterm.js screen when receiving 'connected' status after reset
- Send resize message after clearing to ensure proper dimensions
- Fixes terminal artifacts after reset
Backend:
- Fix data loss bug: text starting with '{' but not valid JSON was silently dropped
- Now writes such text to session as regular input
- Fixes missing characters when user types '{'
Frontend:
- Fix term.onData to use wsRef.current instead of captured ws variable
- Fix fitTerminal to use wsRef.current for resize messages
- Fix sendData callback to use wsRef.current
- This fixes 'cannot type' after WebSocket reconnect
Backend:
- Add SessionRef class for mutable session reference
- Update _read_loop and _write_loop to use SessionRef
- Reset now updates session_ref.session instead of returning
- This keeps the WebSocket alive after reset instead of closing it
Backend:
- Remove _stty_sent guard to send stty on EVERY resize
- Use stty -echo to hide command, then delete the command line with ANSI escapes
- Change log level from info to debug
Frontend:
- Add window resize listener as fallback to ResizeObserver
- 250ms debounce to avoid excessive refits
- Proper cleanup on unmount
- Move Config Profiles from settings to top-level navigation
- Implement split-pane layout: profile list on left, editor on right
- Add project and tool type dropdowns with live data
- Keep form open after save with success feedback
- Add sticky save bar at bottom of editor
- Remove Config Profiles tab from Settings page
OpenSpec: add-config-profiles
Reverted terminal.tsx, terminal_session.py, and terminal.py to clean state
from before the resize debugging saga. Removed:
- Debug console.log statements
- Explicit term.resize() calls that broke xterm.js
- position: relative CSS overrides on .xterm
- stty -echo wrapper and asyncio.sleep delay
- Extra requestAnimationFrame refresh calls
Kept:
- Mobile terminal features (special keys, modifiers, font size)
- ResizeObserver for container resize detection
- Basic fit() and WebSocket resize messaging
- Add ConfigProfile and ConfigProfileInclude data models with migrations
- Implement profile resolver service with ordered includes and merge rules
- Add profile CRUD API with validation, compatibility, and cycle detection
- Add instance API plumbing for profile selection on create/start/restart
- Add resolved profile preview and default resolution APIs
- Add frontend config profile API client and management UI
- Add launch/restart profile selection UI
- Add backend integration and unit tests (31 passing)
OpenSpec: add-config-profiles
Quality gates: ruff, TypeScript compile, 31 tests passing
- Add window resize listener as fallback for ResizeObserver
- Use 250ms debounce to avoid early layout reads
- Delay term.refresh() to next animation frame so renderer
can process resize before we force redraw
- Clean up window resize listener on unmount
- Add window resize listener to complement ResizeObserver
- Clear window resize timeout on cleanup
- Force term.refresh() after font size changes
- Send resize message after font size change
- Frontend: Add ResizeObserver with dimension tracking for accurate resize detection
- Frontend: Fix cleanup function to properly disconnect ResizeObserver
- Frontend: Use CSS grid for terminal wrapper layout
- Backend: Add duplicate dimension check to avoid unnecessary resizes
- Backend: Ensure stty command is sent correctly to container shell
Issues fixed:
1. Terminal container now has explicit width: 100% and height: 100%
2. Added term.refresh() after fit() to force redraw when dimensions change
3. Changed shell-body from min-height to height for definite sizing
4. Added .xterm-viewport width: 100% to ensure proper filling
This ensures the terminal properly fills the viewport and redraws
content when the window is resized.
When window resize fires, CSS layout hasn't settled yet. Adding
requestAnimationFrame ensures the browser has calculated new sizes
before xterm.js fit() reads the container dimensions. Reduced
debounce from 250ms to 100ms since rAF handles the layout timing.
The ResizeObserver detected size changes caused by the stty command
output appearing in the terminal, creating an infinite resize loop:
1. Resize detected -> fit() -> send resize to backend
2. Backend sends stty command through PTY
3. stty text appears in terminal output
4. ResizeObserver detects content height change
5. fit() calculates new rows -> send resize
6. Loop continues forever
Reverted to:
- Window resize event instead of ResizeObserver
- stty command only sent once on first resize
This means the container shell stays at the initial size and won't
dynamically resize when the browser window changes, but prevents
the infinite loop.
Window resize events fire before CSS layout settles, so FitAddon
was reading stale container dimensions. ResizeObserver fires after
the element actually changes size, ensuring fit() gets correct
dimensions. Reduced debounce from 250ms to 100ms for snappier response.
- Backend PTY starts with default 80x24 dimensions
- Previous code only sent resize during layout changes
- Now sends current terminal size immediately when WebSocket opens
- Ensures PTY is properly sized before shell starts rendering
- Simplified fit logic: just fit after open, after fonts load, and on resize
- Added console logging to debug what FitAddon calculates
- Single fitTerminal() function used everywhere
- Removed complex retry logic that wasn't working
- Wait for document.fonts.ready before fitting (ensures correct cell metrics)
- Retry fit every 100ms if rows <= 1 or cols <= 10 (layout still settling)
- Up to 30 retries (3 seconds) for layout to stabilize
- Remove redundant delayed fits, keep only header auto-hide fit at 4s
- Container-level ResizeObserver created feedback loop with fitAddon.fit()
- Removed it, kept initialization-time dimension check only
- Rely on window resize listener for viewport changes
- xterm docs require parent to have dimensions when open() is called
- Added ResizeObserver to wait for non-zero dimensions before initializing
- Added container ResizeObserver to handle resizes (header hide, keyboard)
- Fixed cleanup to properly disconnect observers and handle uninitialized ws
- Don't reconnect when server closes old connection with code 4000
- Code 4000 means new connection was established, not an error
- Prevents infinite reconnection loop between old/new connections
Refs: terminal switching between 4000 error and connected
- Measure parent dimensions and set them on container before term.open()
- Ensures FitAddon gets correct dimensions on initialization
- Prevents 1-row/1-col calculation that breaks scrolling and sizing
- Backend: Send ping every 30s from WebSocket endpoint
- Frontend: Respond to pings with pongs, detect missed pings (60s timeout)
- Update type definitions to include 'resetting' status
Refs: persistent-terminal-sessions task 6.4
- Terminal sessions now persist across WebSocket disconnections
- Added circular output buffer (10KB) for replay on reconnect
- Added idle timeout cleanup (30 minutes)
- Added reset functionality via WebSocket message and HTTP endpoint
- Concurrent connections close old WebSocket when new one connects
- Frontend: Added reset button with confirmation dialog
- Frontend: Handle resetting status and reconnection
Refs: persistent-terminal-sessions