Use canonical profile files and writable Git working copies so editor and container changes share one source. Require confirmation before destructive Git refreshes and overlay profile files without composite snapshots.
- Use profile-scoped canonical Git clone sources with locked refreshes
- Mount shared Git configuration read-only and isolate profile content
- Add API and desktop/mobile actions for live Git mount refresh
Quality gates: frontend build and backend py_compile passed.
Skipped: backend pytest/Ruff unavailable; Docker/manual checks not approved.
- Add a geometric evergreen Headquarter mark for browser tabs\n- Register the SVG favicon and matching browser theme color\n\nOpenSpec: add-project-favicon\nQuality gates: npm run build
- Add dynamic viewport bounds and scrollable body regions to dialogs and modals\n- Make tool-launch popups use the shared scrollable body pattern\n- Keep mobile sheets, action sheets, and notification popups scroll-contained\n- Restore the ProjectsPage test setup required for the frontend suite\n\nOpenSpec: fix-dialog-scroll-containment\nQuality gates: npm run typecheck, npm run lint, npm test (88 passed), npm run build
Recreate the terminal when responsive classification changes so mobile scrollback and touch listeners are installed.\n\nOpenSpec: fix-mobile-terminal-scrolling\nQuality gates: npm run typecheck, npm run lint, npm test (89 passed), npm run build
- Retain xterm normal-buffer history on mobile while preserving desktop zero-scrollback behavior\n- Repair ProjectsPage tests for session context and current project list markup\n- Add focused terminal scrollback coverage\n\nOpenSpec: fix-mobile-terminal-scrolling\nQuality gates: npm run typecheck, npm run lint, npm test (89 passed), npm run build
- Remove compose-level user: 0:0 override from manifest_compiler.py so the
entrypoint can start as root, fix mount ownership, and drop privileges to
the container user internally.
- Add get_manifest_container_user() helper to resolve the manifest-declared
container user (with uid:gid fallback).
- Pass container user through TerminalSession, TerminalManager, and the
terminal WebSocket handler so docker exec is invoked with --user <user>.
- Update and add unit tests for the manifest compiler and terminal session.
- Record the additional root-user fix in the fix-pi-container-mount-permissions
OpenSpec change/tasks.
Quality gates: pytest tests/unit/ (226 passed), pytest tests/services/test_terminal_manager_multi.py (7 passed), ruff check on changed files (clean), mypy on changed files (clean)
- Add ProjectListItem component with project header actions
- Show repositories horizontally with branch labels
- List workspaces vertically under each repo
- Show running tools per workspace from useSessions
- Link workspace names to /workspaces/:id
- Link tool names to web URL or terminal page
- Keep existing mobile view and dialogs unchanged
Working copies were stored as /data/working-copies/{repo_id}/{workspace_name}/,
so git clone was forced into a user-named directory. That meant the container
mount basename was the workspace name (e.g. main) instead of the repo name.
- Generate the workspace UUID before cloning and clone into
/data/working-copies/{workspace_id}/ so git creates {repo_name}/ naturally
- Set workspace.path to /data/working-copies/{workspace_id}/{repo_name}/
- Update _migrate_clone_into_workspace() to use the same layout
- _get_repository_mount_name() now prefers workspace.path basename and only
falls back to remote URL / repo.name for legacy repo-only instances
- Update unit tests to assert workspace path basename is used for mounts
Quality gates:
- pytest tests/unit: 219 passed
- ruff: clean on changed files
- mypy: clean on changed files
The pi-agent manifest still declared an explicit repo mount with
{{WORKSPACE_NAME}}, making the mount target dependent on tool config. The
instance service now synthesizes the repo mount, so the manifest no longer
needs the explicit mount.
- Add Alembic migration 2026_06_15_090500 to remove the source_type: repo
mount from the built-in pi-agent manifest
- Add _get_repository_mount_name() helper to derive the workspace directory
name from the repository remote URL (matching git clone behavior) and
fall back to the user-provided repository name
- Use the helper for WORKSPACE_NAME/REPO_NAME in manifest, legacy dockerfile,
and legacy compose template paths
- Update unit tests for the new migration and helper
Quality gates:
- pytest tests/unit: 218 passed
- ruff: clean on changed files
- mypy: clean on changed files
- alembic heads: single head
WORKSPACE_NAME was computed from os.path.basename(repo_path), so when a
workspace path ended in a directory like 'main', the container mount target
became /home/user/main instead of /home/user/{repo-name}.
- Use GitRepository.name for WORKSPACE_NAME/REPO_NAME in manifest and
legacy dockerfile flows
- Add unit test verifying prepare_manifest_instance uses repo.name even
when the workspace path basename differs
Quality gates:
- pytest tests/unit: 213 passed
- ruff: clean on changed files
- mypy: clean on changed files
Older cached images still contain a literal /home/user/{{WORKSPACE_NAME}}
directory baked in by the previous Dockerfile generation. Even though new
images no longer create it, existing images leave the placeholder folder
alongside the real repo-named mount.
- Add entrypoint cleanup that removes /{{WORKSPACE_NAME}} if it
exists before creating the real workspace target and /workspace symlink
- Update unit tests to assert the stale placeholder removal
Quality gates:
- pytest tests/unit: 212 passed
- ruff: clean on changed files
- mypy: clean on changed files
- Add final get_container_status check in start_tool_instance before
writing status=running; mark as error and return logs if container stopped
- Treat restarting as error in HealthMonitor when DB status was already
running, so crash loops are surfaced instead of preserved
- Disable auto-restart (restart: unless-stopped -> restart: no) for tool
instances in manifest compiler, legacy dockerfile path, and built-in seeds
Quality gates:
- pytest tests/unit: 210 passed
- ruff: clean on changed files
- mypy: clean on changed files
The previous commit moved the pi-agent repo mount from /workspace to
/home/user/{repo_name}. This exposed a permission bug: the Dockerfile
creates /workspace as a root-owned symlink in the image, and the
non-root entrypoint could not replace it because / is owned by root.
- Update compile_entrypoint to recreate /workspace via sudo when running
as the container user, or directly when running as root
- Add unit test covering sudo/root symlink creation
- Update OpenSpec change docs with the additional root cause
Quality gates:
- pytest tests/unit: 208 passed
- ruff: clean on changed files
- mypy: clean on changed files
- alembic heads: single head
- Add Alembic migration to update built-in pi-agent manifest:
* repo mount target from /workspace to ~/{{WORKSPACE_NAME}}
* keep /workspace as compatibility symlink via working_dir
* update startup chown target to $HOME/$WORKSPACE_NAME
- Pass REPO_NAME and WORKSPACE_NAME to compile_compose from instance_service
- Substitute {{WORKSPACE_NAME}} in manifest mount targets and expose it as
a container env var so the entrypoint can create the /workspace symlink
- Generate entrypoint workspace symlink from runtime WORKSPACE_NAME env var
- Install npm_global packages into {home_dir}/.npm-global with PATH so the
non-root container user can update global packages
- Update manifest compiler unit tests for the new behavior
Quality gates:
- pytest tests/unit: 207 passed
- ruff: clean on changed files
- mypy: clean on changed files
- alembic heads: single head
- Change mobile terminal CSS to use touch-action: none and
overscroll-behavior: none so the custom touch handler owns swipes
- Archive completed/partial OpenSpec specs to
openspec/changes/archive/2026-06-14-completed-specs-archive/
- Regenerate project maps
Quality gates: npm run typecheck, npm run lint (apps/web)
- Add design doc / ADR for configurable /home/user home directory
- Add implementation plan with phased rollout
- Add test plan / QA checklist
- Update OpenSpec task for home-path-expansion
The desktop terminal page sometimes grew an outer scrollbar because the
terminal instance/wrapper/container chain lacked height constraints.
Without min/max-height enforcement, xterm.js's internal viewport could
expand its parent flex/grid track past the available space.
- Add overflow: hidden to .terminal-page.
- Add max-height: 100% and overflow: hidden to .terminal-instance.
- Add max-height: 100% to .terminal-wrapper.
- Add min-height: 0 to .terminal-container.
- Constrain .xterm-viewport to max-height/width 100% so it fills but
never exceeds its container.
Quality gates: npm run typecheck, npm run lint, npm test -- --run (87 passed).
Refs: openspec/changes/fix-terminal-container-overflow
The Pi Agent dockerfile templates created ~/.tmux.conf with a literal
\n because the RUN command used single-quoted echo. Tmux never parsed
the malformed line, so mouse mode stayed off. Without tmux mouse mode,
mouse-wheel events in xterm.js fell back to Up/Down arrow keys and
cycled shell command history instead of scrolling the terminal buffer.
- Use printf '%s\n' to write real newlines in .tmux.conf.
- Apply the same fix to the ranger rc.conf where the same bug existed.
- Update tool-images/pi-agent.dockerfile and both affected alembic
migration dockerfile strings.
Quality gates: npm run typecheck, npm run lint, npm test -- --run (87 passed),
py_compile on changed migrations.
Refs: openspec/changes/fix-tmux-mouse-config
- Wire MobileListView onItemDelete/onItemDuplicate callbacks to render
action buttons in each list row.
- Pass onItemDelete in ToolWorkshopMobileView list view.
- Add CSS for mobile-list-item-action buttons.
- Fix MobileEditView sticky bottom action bar that was hidden behind
the 64px mobile navigation bar; raise to bottom: 64px and z-index 110.
Quality gates: npm run typecheck, npm run lint, npm test -- --run (87 passed)
Refs: openspec/changes/mobile-list-delete-button
- Update MobileEditView to render Save and optional Delete in a
sticky bottom action bar; header now shows Cancel + title only.
- Make ConfigProfilesMobileView open edit view on profile tap.
- Make ToolWorkshopMobileView open edit view on tool type tap.
- Wire delete into MobileEditView for existing profiles and tool
types.
- Stay on edit view after saving an existing item; create flow
returns to list as before.
- Update ToolWorkshopPage cancel to return to list.
- Add mobile-edit-actions and mobile-edit-delete CSS.
Quality gates: npm run typecheck, npm run lint, npm test -- --run (87 passed)
Refs: openspec/changes/mobile-edit-default-bottom-actions
- Rewrite ConfigProfilesMobileView to match desktop functionality:
full detail view with all fields, preview action showing resolved
profile, and edit view with project/tool selects, includes,
environment variables, runtime hints, files, mounts with nested
files, and git mounts.
- Update useConfigProfiles.handleSubmit to return boolean success.
- Update ConfigProfilesPage to pass required state and callbacks.
- Add mobile-specific CSS for config profile forms, includes,
mount/file cards, and preview panels.
- Allow MobileDetailView to render extra children.
Quality gates: npm run typecheck, npm run lint, npm test -- --run (87 passed)
Refs: openspec/changes/mobile-config-profiles-ui
- Rework ToolWorkshopMobileView to support full desktop functionality:
definition type selection (Compose/Dockerfile/Manifest), manifest editor,
conditional port, startup command, readiness probe, required variables,
and validation feedback.
- Add ProfileMobileView and wire ProfilePage to render it on mobile.
- Update useToolWorkshop hook to return boolean success from submit.
- Add responsive CSS for mobile forms, edit views, and manifest editor.
- Update project maps.
Quality gates: npm run typecheck, npm run lint, npm test -- --run (87 passed)
Refs: openspec/changes/mobile-tool-profile-ui
Move the following audited-and-implemented changes into
openspec/changes/archive/2026-06-12-completed-changes-archive/:
- tool-config-mount-cleanup
- config-profile-directory-mounts
Update archive README count and project map index/files accordingly.
openspec/changes/ now contains only the archive directory.
Quality gates: pytest 313 passed/34 skipped, npm run typecheck/lint clean,
npm test -- --run 87 passed
- 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
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.
- 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.
- 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.