## Context Current `dev` has all behavioral features from the overwritten `main` merge, but the code structure is pre-refactor: - Monolithic `api/tool_instances.py` (~3000 lines) - Monolithic `api/config_profiles.py` (~1000 lines) - Monolithic `services/docker.py` - No `schemas/` directory - Flat frontend component structure with inconsistent naming The `b6f89f9` merge from `main` had a clean refactoring that we need to redo, but adapted to our current reality. ## Goals / Non-Goals **Goals:** - Extract Pydantic schemas from API routers into `src/schemas/` - Split `services/docker.py` into `services/docker/` package - Extract instance lifecycle logic from `api/tool_instances.py` into `services/instance_lifecycle.py` - Extract config profile business logic from `api/config_profiles.py` into `services/config_profiles.py` - Add `get_current_user` auth dependency and migrate routers that need the full user object - Reorganize frontend components into `features/` directories - Standardize frontend API file naming to kebab-case - Standardize frontend page naming to `*Page.tsx` **Non-Goals:** - Changing any API request/response shapes - Changing any database schemas - Adding new features - Modifying frontend component behavior or styling - Converting `ConfigProfile` mounts from JSON to relation tables (out of scope — would require migration) ## Decisions ### 1. Schema Extraction: One File Per Domain **Decision:** Each domain gets its own schema file: `schemas/tool_type.py`, `schemas/tool_instance.py`, etc. **Rationale:** Keeps schemas close to their domain. Avoids a giant `schemas.py`. ### 2. Docker Service Split: Functional Boundaries **Decision:** Split by responsibility: - `compose.py` — compose file generation, modification, port injection, network injection - `container.py` — container status, IP lookup, network connect, logs - `config_staging.py` — staging config files into instance directories - `tunnel.py` — extracting tunnel URLs from cloudflared output **Rationale:** Each module has a single reason to change. `docker.py` mixed compose logic with container runtime queries. ### 3. Instance Lifecycle: Service Receives Raw Params, Not Request Objects **Decision:** Service functions receive model instances and primitive parameters, not FastAPI request objects. **Example:** `create_instance(session, user, project, repo, tool_type, data: CreateInstanceRequest)` → service extracts fields. **Rationale:** Keeps service layer independent of HTTP framework. Easier to test. ### 4. Auth Pattern: Gradual Migration, Not Big Bang **Decision:** Add `get_current_user` alongside existing `get_current_user_id`. Migrate routers incrementally. **Rationale:** Reduces risk. Endpoints that only need the ID can keep the old pattern. ### 5. Frontend Naming: Align with `b6f89f9` Conventions **Decision:** Use kebab-case for API files, PascalCase for page files with `Page` suffix, `features/` for component directories. **Rationale:** Matches the `b6f89f9` structure that was already reviewed and accepted. ## Module Map ### Backend — Before ``` api/ tool_instances.py (~3000 lines) — HTTP + Docker + Git + Lifecycle config_profiles.py (~1000 lines) — HTTP + Validation + Defaults tool_types.py (~500 lines) — HTTP + Schemas health.py (~150 lines) — HTTP + Schemas users.py (~100 lines) — HTTP + Schemas ... services/ docker.py (~600 lines) — Compose + Container + Tunnel ``` ### Backend — After ``` schemas/ tool_type.py (~200 lines) tool_instance.py (~40 lines) config_profile.py (~130 lines) health.py (~50 lines) user.py (~20 lines) ... api/ tool_instances.py (~300 lines) — HTTP routing only config_profiles.py (~200 lines) — HTTP routing only tool_types.py (~250 lines) — HTTP + validation endpoints health.py (~80 lines) — HTTP only users.py (~60 lines) — HTTP only ... services/ instance_lifecycle.py (~420 lines) — Create/Start/Stop/Restart/Delete config_profiles.py (~300 lines) — CRUD + Defaults + Validation docker/ __init__.py (~40 lines) — Re-exports compose.py (~240 lines) — Compose generation container.py (~120 lines) — Container queries config_staging.py (~80 lines) — File staging tunnel.py (~150 lines) — Tunnel URL extraction ``` ### Frontend — Before ``` src/ api/ tool_types.ts ssh_keys.ts git_repositories.ts sessions.ts components/ git-toolbar.tsx file-editor.tsx commit-dialog.tsx ... pages/ dashboard.tsx projects.tsx sessions.tsx ... ``` ### Frontend — After ``` src/ api/ tool-types.ts ssh-keys.ts git-repositories.ts sessions.ts components/ features/ git/ GitToolbar.tsx FileBrowser.tsx FileEditor.tsx CommitDialog.tsx MergeDialog.tsx WorkspaceSidebar.tsx dashboard/ DashboardSummary.tsx ActiveSessionsList.tsx ProjectsSection.tsx QuickCreateForm.tsx RecentSessionsSection.tsx project/ RepositoriesSettingsTab.tsx tool-workshop/ ToolTypesTab.tsx ProtectedRoute.tsx AppShell.tsx pages/ DashboardPage.tsx ProjectsPage.tsx SessionsPage.tsx ... ``` ## Risks / Trade-offs **[Risk] Import cycles during extraction** → **Mitigation:** Extract schemas first (no service dependencies), then services, then thin routers last. Use TYPE_CHECKING guards. **[Risk] Merge conflicts with in-flight features** → **Mitigation:** Coordinate timing. This refactor should be the only large change on `dev` while it's in progress. Freeze other backend work. **[Risk] Frontend renaming breaks imports** → **Mitigation:** Use `git mv` for renames so git tracks history. Update all imports in a single commit. **[Risk] Missing re-export in docker/__init__.py breaks consumers** → **Mitigation:** After splitting, run a full import test across all backend files. Add any missing re-exports. ## Migration Plan 1. **Phase 1: Schemas** — Extract all Pydantic models into `schemas/`. Update imports in API routers. No logic changes. 2. **Phase 2: Services** — Split `docker.py`, extract `instance_lifecycle.py`, extract `config_profiles.py`. Update imports. 3. **Phase 3: Auth** — Add `get_current_user`, migrate routers that need the full user object. 4. **Phase 4: Frontend** — Rename files, move components, update imports. 5. **Phase 5: Verification** — Run full test suite, typecheck, build.