RepositoryCreateDialog was only rendered inside the isMobile block.
Desktop 'Add Repository' clicks set state but the dialog never appeared.
Add conditional rendering in the desktop section.
Quality gates: tsc --noEmit pass, npm run build pass, 80/80 tests pass
- ProjectCard.tsx: add onAddRepository prop, show 'Add Repository' button in expanded view
- ProjectCard.tsx: add showBackButton/onBack props for mobile detail reuse
- ProjectsPage.tsx (desktop): wire onAddRepository to open RepositoryCreateDialog
- ProjectsPage.tsx (mobile): reuse ProjectCard for detail view instead of inline duplication
- pages/projects.css: add .project-add-repo style
Both platforms now use the same ProjectCard component and RepositoryCreateDialog
for adding repositories to projects.
Quality gates: tsc --noEmit pass, npm run build pass, 80/80 tests pass
- ProjectsPage.tsx: detect mobile viewport, show MobileListView / custom detail view / MobileFAB
- Mobile list: tap project to view details (name, description, repositories, workspaces)
- Mobile detail: shows repositories with New Workspace buttons, Add Repository button, Edit/Delete project actions
- Mobile FAB: opens inline project creation form
- RepositoryCreateDialog reused for mobile 'Add Repository' flow
- New CSS: .mobile-form-actions, .mobile-form-group for mobile form layouts
Quality gates: tsc --noEmit pass, npm run build pass, 80/80 tests pass
- WorkspacesPage.tsx: detect mobile viewport, show MobileListView / MobileDetailView / MobileFAB
- Mobile list: tap workspace to view details (branch, status, path, instances, sync time)
- Mobile detail: view workspace fields with Edit and Delete actions
- Mobile FAB: opens inline WorkspaceCreateForm
- New styles/pages/workspaces.css with responsive grid and card styles
- Import workspaces.css in main.tsx
Quality gates: tsc --noEmit pass, npm run build pass, 80/80 tests pass
Backend (health_monitor.py):
- Skip health checks for instances with no container_id
- Treat 'not_found' as error only when container was previously running
- Skip duplicate error notifications when already in error state
- Skip 'not_found' notifications for containers that never ran
Frontend (notification-item.tsx):
- Display notification.message (detailed error text)
- Add expandable Details section showing metadata (exit_code, previous_status, etc.)
- New CSS styles for message and metadata display
Quality gates: py_compile, tsc --noEmit, 80/80 tests pass
- Move APIRouter definition from instance_service.py back to tool_instances.py
(service files should not define FastAPI routers)
- Add missing prepare_manifest_instance import in tool_instances.py
- Guard repo.remote_url before clone_repository call
- Guard tool_type.compose_template before render_compose_template call
- Rename subprocess result variable to avoid shadowing SQLAlchemy Result
- Build error message as local string to avoid None/bool type issues
Quality gates: py_compile pass, LSP clean
All styles have been extracted to styles/ directory and component modules.
Build verified without styles.css.
Quality gates: tsc --noEmit passes, npm run build passes
- Extract use-repo-workspace hook for data loading
- Extract WorkspaceLayout and FileBrowser components
- Slim RepoWorkspacePage from 505 to ~80 lines
Quality gates: tsc --noEmit passes, npm run build passes
- Extract use-projects hook for state management
- Extract ProjectCard and ProjectDialog components
- Slim ProjectsPage from 433 to ~100 lines
Quality gates: tsc --noEmit passes, npm run build passes
- Extract use-ssh-keys hook for state management
- Extract SSHKeyCreateForm and SSHKeyList components
- Slim SshKeysPage from 277 to ~80 lines
Quality gates: tsc --noEmit passes, npm run build passes
- Move GeneralSettingsTab to components/features/settings/
- Re-export from page for backward compatibility
- Slim SettingsPage from 284 to ~150 lines
Quality gates: tsc --noEmit passes, npm run build passes
- Extract use-terminal-page hook for terminal state and effects
- Extract MobileTerminalView and DesktopTerminalView components
- Slim TerminalPage from 571 to 112 lines
Quality gates: tsc --noEmit passes, npm run build passes
The xterm.js WebGL addon has known rendering bugs with reverse-video
(inverse color) ANSI sequences — exactly what tmux uses for its status
bar, pane borders, and selected text. On desktop the WebGL addon loaded
successfully, causing characters to render as black-on-black and appear
to 'disappear'. On mobile WebGL typically fails to initialize, so the
terminal silently fell back to the DOM renderer which handles these
color attributes correctly.
- Remove WebGL addon loading and its cleanup logic
- Remove unused xterm-addon-webgl import and dependency
- DOM renderer is the default and correctly handles all ANSI color
attributes including reverse video
Quality gates: tsc --noEmit (pass), build (pass), bundle -100KB
Refs: xterm.js WebGL reverse-video / minimumContrastRatio issues
- Use useMobileViewport to detect mobile and position dropdown
centered with left/right margins instead of right-aligned, which
caused overflow on small screens.
- Add a semi-transparent backdrop overlay on mobile so tapping
outside the dropdown naturally closes it.
- Update mobile CSS: notification-dropdown fills screen width
with 0.75rem margins, max-height capped at 70vh for reachability.
- Remove the 360px max-width cap on mobile so the dropdown uses
available screen space properly.
Quality gates: tsc --noEmit (pass), build (pass)
The notification dropdown was trapped inside .shell-header's stacking
context (created by backdrop-filter). Even with z-index: 9999, it
remained below any element with a higher root-level z-index such as
modal overlays (1000), dialog overlays (1000), and fullscreen
terminals (1000).
- Render the dropdown via ReactDOM.createPortal into document.body
so it escapes all parent stacking contexts.
- Dynamically measure the bell button's bounding rect to position
the dropdown correctly with position: fixed.
- Update click-outside handler to also ignore clicks on the bell
button itself.
- Add window resize listener to keep dropdown aligned.
- Change .notification-dropdown from position: absolute to fixed.
Quality gates: tsc --noEmit (pass), build (pass)
The notification dropdown was at z-index: 100, well below fullscreen
terminals (1000), modal overlays (1000), and dialog overlays (1000).
Because .notification-center creates a stacking context with no explicit
z-index, the dropdown was trapped behind any of those overlays and
became unclickable.
- Set .notification-center z-index to 9999 so it competes above all
other overlays in the root stacking context.
- Set .notification-dropdown z-index to 9999 for consistency.
Quality gates: tsc --noEmit (pass), build (pass)
Terminal sessions are opened in a new tab via window.open with
noopener,noreferrer. In a new tab, window.history has no previous
entry, so navigate(-1) silently does nothing. Both the back arrow
and the X button in the mobile terminal overlay called navigate(-1),
which made them appear broken.
Change both buttons to navigate('/sessions') so they always exit
to a sensible page regardless of how the terminal was opened.
Quality gates: tsc --noEmit (pass), build (pass)
When a workspace is provided, auto-generated display names now use
workspace.name instead of repo.name:
'myworkspace / VS Code Server' # first
'myworkspace / VS Code Server #2' # second
Without a workspace, naming falls back to repo.name:
'myrepo / VS Code Server'
'myrepo / VS Code Server #2'
The counter is scoped to workspace+tool_type (or repo+tool_type),
so different tool types for the same workspace/repo are numbered
independently.
This replaces the old format of 'project / repo / tool #N' which
was always repo-based and included the project name even though
the sidebar already groups by project.
Quality gates: py_compile passed, ruff passed.
services/correlation.py was moved to services/shared/correlation.py,
services/event_bus.py to services/instance/event_bus.py, and
services/health_monitor.py to services/instance/health_monitor.py
but main.py was still importing from the old flat paths.
Updated main.py to import from the new subpackage paths via
__init__.py re-exports.
Quality gates: py_compile passed, ruff passed.
Fixed import paths for 43 components moved into features/ directories.
Key fixes:
- api/, types/, hooks/, state/, utils/ imports need ../../../ from features/*/
- components/ imports need ../../ from features/*/
- Cross-feature imports use relative paths (e.g., ../tool/tools-bottom-sheet)
- app-shell.tsx updated to import from features/ subdirectories
Frontend typecheck now passes except for one pre-existing error:
xterm-addon-webgl missing type declarations.
Quality gates: ruff passed on backend, py_compile passed on all backend files.
The .dockerignore added in 8a0d82f incorrectly excluded wait-for-db.sh,
but the Dockerfile copies it as the container entrypoint. This caused
the Docker build to fail with 'failed to compute cache key: not found'.
Quality gates: verified file exists, py_compile passed.
1. Built-in tool type seeding (apps/api/src/seeds/builtin_tool_types.py):
- Seeds code-server, jupyter-notebook, and opencode on startup.
- Adapts to current dev model: uses interface_type (single string)
instead of interfaces array, and created_by_id=None instead of
is_builtin flag.
- Called from main.py startup event.
2. Config profile default management:
- Adds default_profile_id and default_profiles properties to
UserConfig model for JSON-backed per-tool-type defaults.
- Adds GET /config-profiles/defaults, PUT /config-profiles/defaults,
and GET /config-profiles/defaults/{tool_type_id} endpoints.
- Validates that all profile IDs in default mappings belong to the
authenticated user before persisting.
3. Config profile unique constraint:
- Adds __table_args__ with UniqueConstraint(user_id, name) to
ConfigProfile model. The constraint already exists in the DB
from migration 2026_05_24_add_config_profiles.py; this just
aligns the SQLAlchemy model with the schema.
Quality gates: py_compile passed, ruff passed on all modified files.
From ae02e97 ('fix: tunnel URLs, session naming, git control bar placement'):
1. Tunnel URL regex: exclude api.trycloudflare.com from pattern.
Real tunnel subdomains are 10+ random chars. Prevents matching the
Cloudflare API endpoint instead of the actual tunnel URL.
2. Session auto-numbering: when user doesn't provide a display_name,
auto-generate 'project / repo / tool_type #N' where N increments
for each existing instance with the same project/repo/tool_type.
Prevents confusing duplicate display names in the sidebar.
These fixes were lost when main's merge was overwritten. Ported to
our clean dev codebase.
Quality gates: py_compile passed, ruff passed on tool_instances.py and tunnel.py.
Add .dockerignore to exclude __pycache__, .venv, test artifacts, and
other host-only files from Docker build context. Prevents stale .pyc
cache pollution in container images.
Add read-only bind mount for ./apps/api/src:/app/src in docker-compose.yml
so code changes on the host are reflected in the running container
without requiring image rebuild. This is a dev convenience that resolves
the persistent 'tool_configs' import error from stale container images.
Quality gates: docker-compose.yml syntax valid, .dockerignore parsed.
The production DB was already migrated to 86cec91fdb00 (merge of
0014_add_profile_resolver_fields and 2026_06_01_add_workspaces) during
earlier fixes. The clean base branch lacked these migration files,
causing startup failure: 'Can't locate revision identified by 86cec91fdb00'.
Copy the idempotent 0013/0014 migrations and the no-op merge revision
from the fix commits so the Alembic graph matches the DB state.
Quality gates: alembic heads returns single head (86cec91fdb00),
py_compile and ruff passed on all three files.
Move ToolDefinitionManifest import out of TYPE_CHECKING in tool_type.py
so SQLAlchemy can resolve the string-annotated relationship during mapper
configuration.
Add Workspace to models/__init__.py (before ToolInstance) so the
ToolInstance-Workspace relationship can be resolved.
Quality gates: py_compile passed, ruff passed, all mappers configure OK.
Sidebar SessionItem was only opening web tool URLs in new tabs.
Terminal sessions linked to the project page in the same tab.
SessionCard 'Open' buttons for terminal sessions navigated in-place.
Changes:
- app-shell.tsx: SessionItem now builds terminal URLs
(/instances/:id/terminal) and always uses target=_blank
- session-card.tsx: compute openHref for both web and terminal sessions,
render <a> links with target=_blank instead of callback buttons
- use-instance-actions.ts: handleOpen now uses window.open(..., '_blank')
for terminal sessions and project fallback
All session opening (sidebar, cards, callbacks) now consistently opens
in a new tab.
Quality gates: tsc clean