Recovers and adapts the structural refactoring from overwritten
main merge (b6f89f9) to current dev reality.
Scope:
- Schema extraction into apps/api/src/schemas/
- Docker service split into services/docker/ package
- Instance lifecycle extraction from api/tool_instances.py
- Config profile service extraction from api/config_profiles.py
- Auth dependency refactor (get_current_user)
- Frontend reorganization into features/ dirs + kebab-case naming
Exclusions (already in dev): seeding, defaults, unique constraint,
SSH key mounting, terminal backend, tunnel regex, session auto-numbering.
- Add ssh_key_id column to ConfigProfile model and migration
- Update config profile API to accept/return ssh_key_id
- Include ssh_key_id in ResolvedProfile and resolver logic
- Mount selected SSH key into container home dir at start_instance
- Frontend config profile form with SSH key selector dropdown
- Git mount URL validation defaults to profile's SSH key
Quality gates: pytest (231 passed, 6 pre-existing), tsc --noEmit clean
- Add POST /config-profiles/validate-git-url endpoint:
- Parses URL using existing parse_git_url utility
- Suggests corrected URL for browser URLs
- Runs git ls-remote --heads to verify reachability
- Lists available branches from remote
- Supports SSH key for private repos
- Returns structured response: valid, suggested_url, branches,
default_branch, error, error_code
- Update frontend GitMountEditor:
- Add Check button next to URL field with loading state
- Show validation result: valid (green), suggestion (yellow),
invalid (red)
- Suggestion includes Use this button to apply corrected URL
- Branch field becomes dropdown when URL is validated,
populated with remote branches
- Mappings section disabled until URL is validated
- Shows hint: Validate the URL first
- Quality gates: pytest (218 passed, 6 pre-existing),
tsc --noEmit (clean)
- Add notifications table with Alembic migration
- Notification model with user-scoped indexing and partial index on unread
- NotificationService singleton with create/list/count/mark-read/dismiss
- FastAPI router: GET /notifications, GET /unread, PATCH /{id}/read,
POST /mark-all-read, DELETE /{id}
- Mute categories filtering from UserConfig
- 13 unit tests for NotificationService
- 10 integration tests for API endpoints
- Updated test_models.py with new table registration
Quality gates: pytest 23 new passed, ruff clean
The previous sorting fix exposed a deeper bug: ResolvedMount always
mounted its staging directory as a single bind mount. When a config
profile mount targeted /workspace/x/y and contained a single file
z.json, the staging directory (containing only z.json) replaced the
ENTIRE /workspace/x/y directory, hiding all sibling files from git
repo mounts.
- Change apply_resolved_profile to mount each file individually:
- source: staging_dir/relative_path
- target: expanded_target/relative_path
- Sibling files from other mounts are preserved.
- Empty mounts produce no volume entries.
- Keep volume sorting (parent paths before child paths) which is
still necessary for directory mounts and ensures parent dirs exist
before file mounts inside them.
- Add 4 unit tests for file-level mount behavior.
Quality gates: pytest (218 passed, 6 pre-existing), tsc --noEmit (clean)
When switching terminal sessions in fullscreen mode, the viewport
shrank cumulatively because .terminal-wrapper uses
grid-template-rows: auto 1fr. With showControls=false, the single
child (.terminal-container) landed in the auto track instead of 1fr,
creating a feedback loop with xterm fit().
- Add .terminal-wrapper.no-controls with grid-template-rows: 1fr
so the container fills the wrapper when the header is hidden.
- Apply no-controls class in TerminalComponent when showControls=false.
- Replace setTimeout(50) with double requestAnimationFrame in
TerminalPage for more reliable fit() timing after tab switches.
Quality gates: tsc --noEmit (clean), pytest (208 passed, 6 pre-existing)
- Add instance_events and health_checks tables with Alembic migration
- InstanceEventBus: typed pub/sub singleton with wildcard support
- HealthMonitor: async background loop polling containers every 15s
- SSE endpoint GET /events/stream with auth and connection limits
- Lifecycle hooks in tool_instances.py (create/start/stop/restart/delete)
- Structured JSON logging with correlation IDs
- 15 new unit tests (EventBus, HealthMonitor, MonitoringModels)
Quality gates: pytest 15 new passed, ruff clean
- Add expand_container_path() helper that resolves ~/ and $HOME/ prefixes
- Add get_manifest_home_dir() to compute /home/{user.name} or /root from manifest
- Set ENV HOME=... and ENV USER=... in generated Dockerfile for runtime compatibility
- Pass home_dir through instance creation and startup pipeline
- Expand mount targets in apply_resolved_profile() for regular profile mounts
- Expand mapping targets in _resolve_git_mount_mappings() for git mounts
- Expand working_directory and volume targets in _modify_compose_file()
- Update _prepare_manifest_instance to return home_dir alongside image tag
- Fetch tool_type early in start_instance to determine home_dir before profile application
Quality gates: pytest 188 passed, frontend typecheck clean
Addresses: home-path-expansion
- 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
- Add test_tool_instances_legacy.py with 8 unit tests:
- dockerfile definition type builds from template
- dockerfile build failure raises HTTP 500
- compose definition type renders template
- manifest compiler is NOT called for legacy types
- start_instance legacy/compose/dockerfile types all skip manifest flow
- start_instance manifest type correctly invokes compiler
- Mark T3.2 and T3.3 tasks complete in OpenSpec
- Add openspec/docs/tool-workshop-guide.md with user guide covering
definition types, manifest creation workflow, base definitions,
migration path, and permissions
- Add ToolDefinitionManifest model with base image versioning
- Add manifest compiler: Dockerfile + Compose generation from JSON manifests
- Add permission fixer: post-start chown/chmod for mount policies
- Add tool definition CRUD API with live compile preview endpoint
- Integrate manifest-based startup flow in start_instance
- Add Alembic migration with data conversion for pi-agent
- Add 48 unit tests for manifest compiler, permission fixer, docker service
- Keep backward compatibility with legacy dockerfile_template/compose_template
Migration: applied successfully. Pi-agent converted to manifest.
Quality gates: pytest (146 passed, 4 pre-existing unrelated 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
- Update validation rules to check URL format instead of repo existence
- Update cloning scenarios to clone directly from URL
- Update UI scenarios to show URL input instead of repo selector
- Remove references to internal/existing repositories
- 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 POST /repositories endpoint for external repos (no project_id)
- Update GitRepositoryResponse to allow nullable project_id
- Update list_repositories to support listing all user repos
- Add _pull_repository_updates for auto-pull on container creation
- Update git mount validation to allow external repos
- Frontend: Update listRepositories to support optional projectId
- Spec updates: external repos, auto-clone, per-instance isolation
- Make project_id nullable in git_repositories table (migration)
- Allow external repos not tied to any project
- Update validation to allow user-owned external repos in git mounts
- Add /projects/repositories endpoint to list all user repos
- Update frontend to fetch all user repos for git mount selector
- TypeScript and build pass
- API documentation for config profiles with git mounts endpoint details
- User guide for using git repositories in config profiles
- Document branch pinning, glob patterns, error handling, and best practices
- Update API README to link to new config-profiles documentation
- Restored original desktop CSS that was accidentally overwritten
- Added back all mobile-specific styles
- CSS file now 3945 lines (original + mobile styles)
- Build passes successfully
- Add mobile viewport detection to RepoWorkspace
- Implement bottom tab navigation (Files, Editor, Git, Terminal)
- Add repository and branch selectors for mobile
- Create mobile workspace layout with tab bar
- Add CSS styles for mobile workspace components
- Desktop layout remains unchanged
- Add mobile list view showing all config profiles
- Add mobile detail view with profile information display
- Add mobile edit/create view with full form
- Implement list→detail→edit navigation
- Fix TypeScript errors and build issues
- 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
- Add startup_command field to ToolType model and API
- Execute startup command before interactive shell in terminal sessions
- Add tmux and ranger to OpenCode container spec
- Update Tool Workshop UI with startup_command input for terminal types
- Add backend tests for startup_command CRUD operations
- Sync specs: tool-terminal, tool-types-definition, opencode-web-server
- New spec: tool-terminal-startup-command
Quality gates: Frontend typecheck/lint passed. Backend tests blocked by environment (Python/Docker not available).
OpenSpec: terminal-startup-and-container-tools
- 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