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.
6.6 KiB
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.pyintoservices/docker/package - Extract instance lifecycle logic from
api/tool_instances.pyintoservices/instance_lifecycle.py - Extract config profile business logic from
api/config_profiles.pyintoservices/config_profiles.py - Add
get_current_userauth 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
ConfigProfilemounts 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 injectioncontainer.py— container status, IP lookup, network connect, logsconfig_staging.py— staging config files into instance directoriestunnel.py— extracting tunnel URLs from cloudflared output Rationale: Each module has a single reason to change.docker.pymixed 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
- Phase 1: Schemas — Extract all Pydantic models into
schemas/. Update imports in API routers. No logic changes. - Phase 2: Services — Split
docker.py, extractinstance_lifecycle.py, extractconfig_profiles.py. Update imports. - Phase 3: Auth — Add
get_current_user, migrate routers that need the full user object. - Phase 4: Frontend — Rename files, move components, update imports.
- Phase 5: Verification — Run full test suite, typecheck, build.