- Add mappings array support to git_mount entries
- Clone repository once per git_mount entry, mount multiple subdirectories
- Normalize legacy source_path+target_path to mappings on read
- Update _merge_git_mounts to dedup by (remote_url, branch) and concatenate mappings
- Add _normalize_git_mount, _clone_git_repo, _resolve_git_mount_mappings helpers
- Update GitMountItem Pydantic model with GitMountMapping and model_validator
- Update frontend GitMountEditor component with mappings UI
- Auto-convert legacy git mount entries to mappings format on load
- Add 15 backend unit tests for normalization, resolution, and glob expansion
- Update existing config profile resolver tests for new merge behavior
Quality gates: pytest 167 passed, frontend typecheck clean
Addresses: config-profile-multi-repo-mounts
Frontend:
- Remove 4 console.log statements from terminal.tsx that flooded the
browser console with WebSocket traffic (open, received X bytes, sending Y,
xterm focused)
Backend:
- Downgrade Dockerfile/entrypoint compilation logs from INFO to DEBUG in
_prepare_manifest_instance
- Remove hex-dump diagnostic logging from docker_build.py (was for
troubleshooting the backslash continuation bug, now fixed)
- Downgrade Dockerfile write log from INFO to DEBUG
The ManifestEditor had a feedback loop:
1. State change → buildManifest changes → onChange notifies parent
2. Parent updates manifestData → new manifest prop
3. Loading effect sets all state from manifest (arrays get new refs even if same content)
4. New array refs → buildManifest changes → onChange fires again → loop
Fix: track the last-sent manifest via a ref and only call onChange when the
serialized built manifest actually differs. This breaks the cycle because after
the loading effect syncs state, the rebuilt manifest is identical in content
so we skip the parent notification.
The instance status may say 'running' but the actual Docker container
may have been removed (e.g. docker prune, host restart). The old code
created a terminal session which immediately died because docker exec
failed with 'No such container'.
- Add get_container_status check in WebSocket handler before session creation
- Return 4004 with clear message if container is missing
- This prevents spawning zombie terminal sessions
- Initialize loading=true in useTerminalSessions to prevent auto-create
from firing before initial load completes
- Remove hasAutoCreated ref from TerminalPage (no longer needed)
- Add focus() to TerminalRef, call on tab switch
- Add term.focus() after term.open() in TerminalComponent
- Add console logging for WebSocket send/receive to debug no-i/o
- Revert backend _read_loop retry logic to original break-on-error
- Add tool_definitions API client with types for manifests
- Add ManifestEditor component: base image selector, package editors
(apt/npm/pip/node), script editors (build/startup), mount schema
designer, runtime config, and live preview panel
- Integrate ManifestEditor into Tool Workshop as 'Manifest (Declarative)'
definition type alongside Compose and Dockerfile
- Update ToolType API types to include manifest_id and 'manifest'
definition_type
- Frontend builds clean, TypeScript typecheck passes
- Add stdin_open: true and tty: true to dockerfile-based compose generation.
Without these, bash (PID 1) exits immediately, causing a container restart
loop that makes the instance invisible to docker ps and triggers 4004.
- Treat WebSocket close codes 4001/4003/4004 as permanent errors in the
frontend. Stop retrying and show the server reason to the user.
- Prevent visibilitychange handler from resetting retry attempts after a
permanent error has occurred.
- Use docker ps -a in get_container_id/get_container_name to find
stopped/exited containers for diagnostics.
Quality gates: tsc --noEmit (pass), pytest (98 passed, 4 pre-existing failures)
- Remove absolute path requirement from target_path validation
- Resolve relative paths against working_directory at instance startup
- Fall back to /home/user if no working_directory is configured
- Update frontend to allow relative target paths
- Update spec to document relative path support
- Update tests to allow relative paths and test path traversal rejection
- Change git mount schema from repo_id to remote_url
- Remove database lookups for git mount resolution
- Clone directly from URL at instance startup
- Simplify frontend UI to text input for Git URL
- Fix route ordering in git_repositories.py to prevent 422 errors
- Update all tests to use remote_url field
Breaking change: Git mounts now use remote_url instead of repo_id
- Add createExternalRepository API function
- Update GitMountEditor with "+ Add new repository..." option
- Show form to enter repo name and remote URL
- Auto-create external repo and refresh list on success
- Update config-profiles page to pass onCreateRepository handler
The calculateFontSize() function was overriding the font size on mobile
based on viewport width (vw/25), causing the terminal to display at ~15px
while the internal state was 8px. When pressing A-, it would jump from
15px to 7px. Now it respects the actual fontSize state consistently.
Quality gates: TypeScript check passed, production build successful
- Reduce MIN_FONT_SIZE from 8 to 4 for maximum text size reduction
- Set default font size to 8px for both mobile and desktop
- Allows very small terminal text for mobile viewport optimization
Quality gates: TypeScript check passed, production build successful
- Reduce MIN_FONT_SIZE from 12 to 8 for smaller text option
- Reduce default font size from 14/12 to 10/10 for mobile/desktop
- Allows users to make terminal text significantly smaller
Quality gates: TypeScript check passed, production build successful
Mobile devices use different monospace fonts (Courier on iOS, Droid Sans Mono
on Android) with larger ascent/descent metrics than desktop fonts. With
lineHeight: 1.0, calculated cell height was smaller than actual glyph height,
causing block characters to render at ~3/4 height. Increasing to 1.2 gives
mobile fonts proper vertical space while maintaining desktop compatibility.
Quality gates: TypeScript check passed, production build successful
- Remove CSS overrides that interfere with xterm.js internal sizing
- Increase MIN_FONT_SIZE from 10 to 12 to prevent broken character rendering
- Increase default font sizes from 10/12 to 12/14 (desktop/mobile)
- Add clamping for stored font size values to prevent old tiny values
- Remove !important rules on xterm-viewport that could cause clipping
The changeFontSize callback passed to MobileTerminalWrapper was capturing
the initial handleFontSizeChange function, so subsequent clicks used stale
fontSize state. Fixed by wrapping handleFontSizeChange in a ref so the
callback always calls the latest version.
- Reduce MIN_FONT_SIZE from 10 to 6
- Reduce MAX_FONT_SIZE from 24 to 20
- Reduce default desktop font size from 14 to 10
- Reduce default mobile font size from 16 to 12
- Add mobile viewport detection to ToolWorkshopPage
- Implement mobile list view with MobileListView component
- Implement mobile detail view with MobileDetailView component
- Implement mobile edit view with MobileEditView component
- Add MobileFAB for creating new tool types
- Fix IconName type issues in mobile components
- TypeScript check passes, build succeeds
Mobile Navigation:
- Add MobileNav component with bottom tab bar
- Show mobile nav on small screens, hide desktop sidebar
- Add session count badge to Sessions tab
- Add safe area padding for notched devices
Session Management:
- Redesign SessionCard for mobile with action menu
- Add MobileActionSheet for session actions
- Keep primary action prominent
Forms & Dialogs:
- Stack form fields vertically on mobile
- Ensure 44px minimum touch targets
- Update dialogs for 320px viewport
Responsive Layout:
- Add MobilePageHeader with back button
- Reduce page padding on mobile
- Stack multi-column grids vertically
Touch & Interaction:
- Add active states to interactive elements
- Ensure 8px spacing between touch targets
Complex Pages:
- Update Repo Workspace for mobile
- Update Tool Workshop and Config Profiles
Build: TypeScript check passes, production build succeeds
- 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