- 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
- 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
Instead of sending stty commands through the user's terminal session
(which causes 'inappropriate ioctl' errors), send SIGWINCH signal to
the docker exec process on the host. Docker exec should forward this
to the container process, causing the shell to re-read its terminal size.
This avoids:
- Visible stty commands in the terminal
- ioctl errors from stty
- Interference with user's shell session
Reverted docker exec back to -it (required for interactive bash).
Instead, sends stty command with \r to hide it from the terminal display:
- \r moves cursor to start of line (overwrites prompt)
- stty command executes silently (no output on success)
- \r moves cursor back to start, hiding echoed command
This sends stty on EVERY resize so the container shell always matches
frontend dimensions.
Removes -t flag from docker exec so it uses our PTY slave directly instead
of creating its own PTY inside the container. This allows TIOCSWINSZ on the
host PTY master to propagate naturally to the container shell via SIGWINCH.
Also removes all stty command injection logic since resize now works natively.
When Docker starts a container, it creates network interfaces which
triggers Chrome's ERR_NETWORK_CHANGED error, aborting the request.
The backend successfully starts the container but the frontend never
gets the response, showing 'failed to create session' even though
the session is up.
Fix: Add retry with exponential backoff for startInstance and
restartInstance when network errors occur (no HTTP response).
Retries up to 2 times with 1.5s delay between attempts.
Fixes: False 'failed to create session' errors when launching tools.
When FastAPI parses the request body and model_dump() is called,
nested MountItem models are already serialized to plain dicts.
The update handler was unconditionally calling model_dump() again,
causing AttributeError on dict objects.
Fix: Check if mount items are already dicts before calling model_dump().
Fixes: 422 error when updating profiles with mounts.
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
- Fix React key stability in env vars, files, and mount file inputs
to prevent focus loss on every keystroke
- Improve validation error messages to explain Files vs Mounts
- Add helper text in UI clarifying relative vs absolute paths
Fixes focus loss bug and improves UX for path validation errors.
- 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
The _stty_sent guard prevented the container shell from updating its
terminal size after the first resize. This caused visual mismatches
where xterm.js displayed at the new size but the shell still wrapped
output at the old size.
Remove the guard so stty is sent on every resize event.
- 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 terminal page uses height: 100% but parent .shell-content didn't
have explicit height, so the terminal couldn't fill the viewport.
Changes:
- .shell-content: added height: 100%
- .shell-body: added flex: 1 to fill flex parent
- Mobile .shell-content: added height: 100%
This ensures the terminal wrapper can properly calculate and fill
the available viewport space.
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.
Previously the stty command was only sent on the first resize. Now it
is sent every time the terminal dimensions change, so resizing the
browser window or rotating the device properly updates the container
shell size. Added a check to skip when dimensions haven't changed.
Docker exec doesn't forward PTY resize to the container process,
so the container bash stays at 80x24 regardless of frontend resize.
Work around this by sending a stty command through the terminal
on first resize to set the correct dimensions inside the container.
The write loop was crashing with 'name instance_id is not defined' when
processing resize messages. This caused the connection to drop with 1006
and the frontend to reconnect in a loop. Fixed by passing instance_id
as a parameter to _write_loop. Also cleaned up debug logging.