Files
headquarter/openspec/changes/backend-frontend-refactoring/design.md
T
alex 2757ef3b4f docs: add OpenSpec change for backend-frontend refactoring
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.
2026-06-04 09:42:03 +02:00

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.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 extractionMitigation: Extract schemas first (no service dependencies), then services, then thin routers last. Use TYPE_CHECKING guards.

[Risk] Merge conflicts with in-flight featuresMitigation: 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 importsMitigation: 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 consumersMitigation: 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.