- Switch apply_resolved_profile from per-file bind mounts to one
directory-level bind mount per ResolvedMount target.
- Stage all configured files under instance_dir/mounts/<sanitized_target>
and bind-mount that directory, so Docker no longer creates a root-owned
parent directory such as ~/.config.
- Propagate read-only mode ('ro') as the 'readonly' flag on volume entries.
- Update unit tests to expect directory-level mounts and add coverage for
readonly/writable flags.
Quality gates: python3 -m py_compile, pytest (313 passed, 34 skipped),
npm run typecheck, npm run lint.
- Remove pi_state and pi_config mounts from the pi-agent manifest.
- Add Alembic data migration to strip those mounts from existing DB rows.
- Remove opencode_home:/tmp volume and HOME=/tmp override from the opencode
built-in compose template; config/state now belongs in config profiles.
- Workspace and SSH key mounts remain unchanged.
Quality gates: python3 -m py_compile, pytest (311 passed, 34 skipped),
npm run typecheck, npm run lint
Add a reusable LoadingOverlay component that dims and disables the
container owning an in-flight action, with a spinning indicator and
label. Apply it to:
- SessionCard (when actionBusyId matches)
- InstanceList cards (per busyInstanceId with action-specific labels)
- CreateSessionForm (while submitting)
- ToolStarter (while starting)
Also add .icon-spin animation and position:relative to the relevant
containers.
Quality gates: npm run typecheck, npm run lint, npm test -- --run
(87 passed).
The bottom-right progress panel duplicated feedback already shown by
toasts. Remove it and the operation-tracking state to simplify the UI:
- Delete state/session-operations.tsx and session-progress-panel.tsx.
- Remove SessionOperationsProvider/SessionProgressPanel from AppShell.
- Remove startOperation/completeOperation calls from useInstanceActions
and ToolStarter.
- Remove SessionOperationsProvider wrapper from DashboardPage.test.tsx.
- Remove .session-progress-panel CSS rules.
- Format use-events.test.ts mock to match project lint rules.
Quality gates: npm run typecheck, npm run lint, npm test -- --run
(87 passed).
The backend sends named lifecycle events (event: instance.health_changed),
but useEvents only set es.onmessage, which only receives unnamed message
events. Add explicit addEventListener registrations for all lifecycle
event types so the progress panel receives updates and completes.
Quality gates: npm run typecheck, npm run lint, npm test -- --run
(87 passed).
The workspace-first cleanup removed clone_mode from the creation flow,
so the API now inserts NULL. Align the database with the model by
making clone_mode nullable.
Apply with: cd apps/api && alembic upgrade head
Config-profile and git mounts staged under instance_dir were created
by the API process (root), so when bind-mounted over ~/.config the
container user could not write. Recursively chown staged sources to
the resolved container uid/gid before compose up.
Quality gates: python3 -m py_compile passed; ruff/pytest skipped
(test tooling not available in this shell, helper smoke tested
with a temporary directory).
Move the following completed changes from openspec/changes/ to
openspec/changes/archive/2026-06-12-completed-changes-archive/:
- multi-session-terminal-ux
- reorganize-long-files
- working-copies
- workspace-first-ui
Update parent and archive .pi-map*.md indexes to reflect the move and
remove the transient active-changes-archive grouping.
openspec/changes/ now contains only the archive/ directory.
Regenerate .pi-map*.md artifacts for source changes in:
- apps/api/src/api/tool/tool_types_validation.py
- apps/api/src/schemas/tool/tool_type.py
- apps/api/tests/integration/test_tool_types_api_extended.py
- and all affected test files from backend-frontend refactoring cleanup
- Extract tool instance lifecycle endpoints (start/stop/restart/delete) from
api/tool/tool_instances.py into new api/tool/tool_lifecycle.py.
- Register tool_lifecycle_router in main.py and api/tool/__init__.py.
- Extract inline WorkspaceDetailPage components into
components/features/workspace/: detail header, tab bars, file/git/tools/settings
panels. Slim page from ~446 to ~62 lines.
- Update OpenSpec reorganize-long-files tasks to reflect completed work and
current source state; mark change completed.
- Regenerate project maps.
Quality gates: python3 -m py_compile (backend clean), npm run typecheck,
npm run lint, npm test -- --run (87 passed), pytest workspace integration
and unit tests (27 passed, 1 skipped).
- Delete dead repo-workspace code: RepoWorkspacePage, useRepoWorkspace,
WorkspaceLayout, FileBrowser, old git components (git-toolbar, file-editor,
commit-panel), and repo-workspace.css.
- Fix stale backend test imports for moved models/services.
- Add GitOperations unit tests.
- Add integration tests for workspace files, git, and instances endpoints.
- Add frontend tests for WorkspaceDetailPage and ProjectCard.
- Update OpenSpec workspace-first-ui tasks and mark change completed.
- Regenerate project maps.
Quality gates: npm run typecheck, npm run lint, npm test -- --run (87 passed),
python3 -m py_compile on changed backend files, pytest backend workspace tests.
- Remove clone_mode/branch from API responses and make DB columns nullable
- Remove legacy clone-mode branches from create_tool_instance
- Add WORKSPACE_PATH compose variable alongside REPO_PATH
- Add workspace migration helpers in WorkspaceManager
Remaining: POST /workspaces/:id/instances, frontend clone_mode cleanup, tests
- Fill empty apply-pr2.md with backend API + frontend client apply report
- Mark all 12 tasks as completed in tasks.md
- Update .openspec.yaml status from exploring to completed
The implementation was already merged to dev across PR 1, PR 2, and PR 3.
This commit only synchronizes the OpenSpec change metadata.
Add generated .pi-map.md and .pi-map.index.md files across the repository
so the project navigation maps are shared and versioned. These artifacts
are maintained by project_map_init/patch/validate and must be kept in
sync with source edits.
Note: .cache/ remains ignored (added in previous commit).
- Add SessionOperationsContext + SessionProgressPanel for global,
non-blocking lifecycle progress (create/start/stop/restart/delete/
recreate-tunnel) driven by SSE events.
- Promote SessionsContext to authoritative shared session state with
refresh, addOrUpdateSession, and removeSession helpers.
- Wire AppShell, DashboardPage, SessionsPage, useInstanceActions,
ToolStarter, and InstanceList into shared state so lists update
immediately after create/delete without manual refresh.
- Remove legacy blocking overlays from CreateSessionForm, SessionCard,
and InstanceList; keep disabled states and inline spinners only.
- Update DashboardPage tests to wrap with SessionsProvider and
SessionOperationsProvider.
- Add .cache/ to .gitignore.
Quality gates: npm run typecheck, npm run lint, npm test -- --run
(82 passed).
The session options dropdown used var(--surface) which was never
defined anywhere in the stylesheet, causing a transparent background.
Changed to var(--panel) which is defined in tokens.css.
Quality gates: tsc --noEmit pass, npm run build pass, 82/82 tests pass
Root cause: manifest-based Dockerfile created the home directory and
chowned only the home root. Files/directories copied from /etc/skel by
useradd -m (or created later by root) remained root-owned, so apps like
ranger failed when writing to ~/.config.
Changes:
- manifest_compiler.py: recursive chown of the home directory after
useradd so /etc/skel contents are owned by the container user
- Pre-create .config, .local/share, .cache and chown them to the user
so first-run apps have writable directories immediately
- Add unit test verifying the Dockerfile emits the expected user/home
setup and config directory creation
Quality gates: py_compile all backend files pass, test file compiles,
tsc --noEmit pass, npm run build pass, 82/82 web tests pass
Note: pytest not available in this shell; backend unit test was not
executed but follows existing project conventions.
Instead of relying on display_name (which for old instances is just the
workspace name), fetch the full session info and build the title from
individual fields:
- Base: '{workspace_name} {tool_type_name}'
- With multiple terminal sessions: '{workspace_name} {tool_type_name} {session_name}'
- Fallback: 'Instance {id}' if session lookup fails
This gives meaningful titles like 'MyWorkspace code-server Session 1'
instead of just 'MyWorkspace — Terminal'.
Quality gates: tsc --noEmit pass, npm run build pass, 82/82 tests pass