# Design: Repository Restructuring and Modularization ## Overview This design document defines the exact target file layout, import patterns, barrel export structure, and per-phase migration mechanics for the repo restructuring. Every old file is mapped to its new location. All decisions from the spec are implemented concretely. **Key decisions:** - CSS Modules for component-scoped styles - Flat `api/` backend structure (no versioning yet) - Feature components at `components/features/{domain}/` - Barrel exports for `components/ui/`, `components/features/{domain}/`, `types/` - Merge each phase to `main` immediately --- ## 1. Target Directory Structure ### 1.1 Frontend (`apps/web/src/`) ``` src/ ├── api/ # API clients — NO types, NO barrel exports │ ├── client.ts │ ├── config-folders.ts # renamed: config_folders.ts → kebab-case │ ├── config-profiles.ts │ ├── dashboard.ts │ ├── git-repositories.ts │ ├── profile.ts │ ├── projects.ts │ ├── sessions.ts │ ├── settings.ts │ ├── ssh-keys.ts │ ├── tool-configs.ts │ ├── tool-types.ts │ └── user-config.ts │ ├── components/ │ ├── layout/ # App-level layout │ │ ├── AppShell.tsx # renamed: app-shell.tsx │ │ ├── AppShell.module.css │ │ ├── Navigation.tsx │ │ ├── Navigation.module.css │ │ ├── UserChip.tsx │ │ └── index.ts # barrel: export { AppShell, Navigation } │ │ │ ├── ui/ # Primitive UI components │ │ ├── Button.tsx │ │ ├── Button.module.css │ │ ├── Card.tsx │ │ ├── Card.module.css │ │ ├── Dialog.tsx │ │ ├── Dialog.module.css │ │ ├── Input.tsx │ │ ├── Input.module.css │ │ ├── LoadingState.tsx │ │ ├── ErrorState.tsx │ │ ├── StatusBadge.tsx │ │ └── index.ts # barrel │ │ │ └── features/ # Domain-specific components │ ├── git/ │ │ ├── FileBrowser.tsx # extracted from repo-workspace.tsx │ │ ├── FileBrowser.module.css │ │ ├── GitToolbar.tsx # renamed: git-toolbar.tsx │ │ ├── GitToolbar.module.css │ │ ├── CommitPanel.tsx │ │ ├── CommitPanel.module.css │ │ ├── CommitDialog.tsx │ │ ├── CommitDialog.module.css │ │ ├── MergeDialog.tsx │ │ ├── MergeDialog.module.css │ │ ├── FileEditor.tsx # renamed: file-editor.tsx │ │ ├── FileEditor.module.css │ │ ├── SyntaxHighlighter.tsx │ │ └── index.ts # barrel │ │ │ ├── project/ │ │ ├── ProjectCard.tsx │ │ ├── ProjectCard.module.css │ │ ├── ProjectList.tsx │ │ ├── CreateProjectDialog.tsx │ │ ├── DeleteConfirmDialog.tsx │ │ ├── RepositoryCard.tsx │ │ ├── RepositoryCreateDialog.tsx │ │ ├── RepositoriesSettingsTab.tsx │ │ └── index.ts # barrel │ │ │ ├── session/ │ │ ├── InstanceList.tsx # renamed: instance-list.tsx │ │ ├── InstanceList.module.css │ │ ├── InstanceCard.tsx │ │ ├── CreateInstanceDialog.tsx │ │ ├── SessionCard.tsx │ │ ├── SessionList.tsx │ │ ├── CreateSessionForm.tsx │ │ └── index.ts # barrel │ │ │ ├── settings/ │ │ ├── SettingsTabLayout.tsx │ │ ├── GeneralSettingsTab.tsx │ │ └── index.ts # barrel │ │ │ ├── terminal/ │ │ ├── TerminalComponent.tsx # renamed: terminal.tsx │ │ ├── TerminalComponent.module.css │ │ └── index.ts │ │ │ └── workspace/ │ ├── WorkspaceHeader.tsx │ └── index.ts │ ├── hooks/ │ ├── use-theme.ts │ ├── use-auth.ts # extracted from state/auth.tsx? No — keep in state/ │ ├── use-api-query.ts # NEW: reusable data fetching │ ├── use-local-storage.ts # NEW │ └── use-debounce.ts # NEW: extracted from use-terminal-connection │ ├── pages/ # Route entry points ONLY │ ├── DashboardPage.tsx # renamed: dashboard.tsx │ ├── DashboardPage.module.css │ ├── GitHistoryPage.tsx # renamed: git-history.tsx │ ├── GitRepositoriesPage.tsx # renamed: git-repositories.tsx │ ├── ProfilePage.tsx # renamed: profile.tsx │ ├── ProjectSettingsPage.tsx # renamed: project-settings.tsx │ ├── ProjectsPage.tsx # renamed: projects.tsx │ ├── RepoWorkspacePage.tsx # renamed: repo-workspace.tsx │ ├── SessionsPage.tsx # renamed: sessions.tsx │ ├── SettingsPage.tsx # renamed: settings.tsx │ ├── SshKeysPage.tsx # renamed: ssh-keys.tsx │ ├── TerminalPage.tsx # renamed: terminal.tsx │ ├── ToolConfigsPage.tsx # renamed: tool-configs.tsx │ ├── ToolTypesPage.tsx # renamed: tool-types.tsx │ ├── ToolWorkshopPage.tsx # renamed: tool-workshop.tsx │ └── PlaceholderPage.tsx # renamed: placeholder.tsx │ ├── router.tsx # unchanged │ ├── state/ │ ├── auth.tsx # keep — context is state layer │ └── sessions.tsx # keep — imports from types/session.ts │ ├── styles/ │ ├── tokens.css # CSS variables / design tokens │ ├── global.css # reset, body, shell layout grid │ ├── utilities.css # .truncate, .stack, .row, etc. │ ├── pages/ │ │ ├── sessions.css # page-specific layout only │ │ ├── repo-workspace.css │ │ └── tool-workshop.css │ └── syntax-highlight.css # Prism.js overrides │ ├── types/ # ALL domain types centralized │ ├── index.ts # barrel: re-exports all │ ├── api-response.ts # generic ApiResponse, PaginatedResponse │ ├── config-folder.ts │ ├── config-profile.ts │ ├── git-repository.ts │ ├── project.ts │ ├── session.ts # canonical Session definition │ ├── ssh-key.ts │ ├── terminal.ts # merged from types/terminal.ts │ ├── tool-config.ts │ ├── tool-instance.ts # canonical ToolInstance definition │ ├── tool-type.ts │ ├── user.ts │ └── user-config.ts │ ├── utils/ │ ├── icons.ts │ ├── language.ts │ └── terminal-protocol.ts │ ├── main.tsx # import entry point for styles └── test/ └── setup.ts ``` ### 1.2 Backend (`apps/api/src/`) ``` src/ ├── main.py # router mounting + middleware ONLY (target: <100 lines) ├── config.py # unchanged ├── database.py # unchanged ├── logging_config.py # unchanged │ ├── auth/ │ ├── __init__.py │ ├── cookies.py │ ├── dependencies.py # shared: get_current_user, get_owned_project │ ├── oidc.py │ └── session.py │ ├── api/ # flat — no v1/ yet │ ├── __init__.py │ ├── auth.py # ~200 lines (target) │ ├── config_folders.py # ~200 lines (target) │ ├── config_profiles.py # ~250 lines (target) — CRUD only │ ├── dashboard.py # ~65 lines (unchanged) │ ├── git_repositories.py # ~250 lines (target) — CRUD only │ ├── health.py # ~150 lines (unchanged) │ ├── instance_proxy.py # ~125 lines (unchanged) │ ├── projects.py # ~200 lines (target) │ ├── ssh_keys.py # ~170 lines (target) │ ├── terminal.py # ~158 lines (unchanged) │ ├── tool_configs.py # ~200 lines (target) │ ├── tool_instances.py # ~250 lines (target) — CRUD + lifecycle endpoints only │ ├── tool_types.py # ~250 lines (target) │ ├── user_config.py # ~121 lines (unchanged) │ └── users.py # ~156 lines (unchanged) │ ├── models/ # unchanged — already well-organized │ ├── schemas/ # NEW: Pydantic request/response schemas │ ├── __init__.py │ ├── config_folder.py │ ├── config_profile.py │ ├── git_repository.py │ ├── project.py │ ├── ssh_key.py │ ├── tool_config.py │ ├── tool_instance.py │ ├── tool_type.py │ ├── user.py │ └── user_config.py │ ├── services/ │ ├── __init__.py │ ├── docker/ │ │ ├── __init__.py │ │ ├── compose.py # compose file generation (≤300 lines) │ │ ├── container.py # container lifecycle (≤300 lines) │ │ ├── tunnel.py # Cloudflare tunnel (≤200 lines) │ │ └── config_staging.py # config folder file writing (≤200 lines) │ ├── docker_build.py # unchanged (~69 lines) │ ├── git/ │ │ ├── __init__.py │ │ ├── control.py # renamed: git_control.py │ │ ├── files.py # renamed: git_files.py │ │ └── history.py # renamed: git_history.py │ ├── profile_resolver.py # unchanged (~251 lines) │ ├── readiness_probe.py # unchanged (~66 lines) │ ├── terminal_manager.py # unchanged (~193 lines) │ └── terminal_session.py # unchanged (~162 lines) │ ├── seeds/ │ ├── __init__.py │ └── builtin_tool_types.py # extracted from main.py │ ├── utils/ │ ├── git_url_parser.py # unchanged │ └── ... # keep existing │ └── scripts/ └── seed.py # unchanged ``` --- ## 2. Barrel Export Patterns ### 2.1 Frontend Barrels **`components/ui/index.ts`:** ```typescript export { Button } from "./Button"; export { Card } from "./Card"; export { Dialog } from "./Dialog"; export { Input } from "./Input"; export { LoadingState } from "./LoadingState"; export { ErrorState } from "./ErrorState"; export { StatusBadge } from "./StatusBadge"; ``` **`components/features/git/index.ts`:** ```typescript export { FileBrowser } from "./FileBrowser"; export { GitToolbar } from "./GitToolbar"; export { CommitPanel } from "./CommitPanel"; export { CommitDialog } from "./CommitDialog"; export { MergeDialog } from "./MergeDialog"; export { FileEditor } from "./FileEditor"; export { SyntaxHighlighter } from "./SyntaxHighlighter"; ``` **`types/index.ts`:** ```typescript export type { ApiResponse, PaginatedResponse } from "./api-response"; export type { ConfigFolder } from "./config-folder"; export type { ConfigProfile } from "./config-profile"; export type { GitRepository } from "./git-repository"; export type { Project } from "./project"; export type { Session } from "./session"; export type { SshKey } from "./ssh-key"; export type { TerminalConnectionState, ClientControlMessage, ServerControlMessage } from "./terminal"; export type { ToolConfig } from "./tool-config"; export type { ToolInstance } from "./tool-instance"; export type { ToolType } from "./tool-type"; export type { User } from "./user"; export type { UserConfig } from "./user-config"; ``` ### 2.2 Backend Barrels **`services/docker/__init__.py`:** ```python from .compose import generate_compose, modify_compose from .container import create_container, start_container, stop_container, remove_container from .tunnel import create_tunnel, recreate_tunnel, check_tunnel_health from .config_staging import stage_config_files __all__ = [ "generate_compose", "modify_compose", "create_container", "start_container", "stop_container", "remove_container", "create_tunnel", "recreate_tunnel", "check_tunnel_health", "stage_config_files", ] ``` **`services/git/__init__.py`:** ```python from .control import branch, checkout, commit, fetch, pull, push, merge from .files import list_files, read_file, write_file from .history import get_history, get_commit_detail, get_diff __all__ = [ "branch", "checkout", "commit", "fetch", "pull", "push", "merge", "list_files", "read_file", "write_file", "get_history", "get_commit_detail", "get_diff", ] ``` --- ## 3. Import Pattern Examples ### 3.1 Frontend Imports (After Refactor) **Page component — orchestration only:** ```typescript // pages/RepoWorkspacePage.tsx import { useParams, useSearchParams } from "react-router-dom"; import { WorkspaceHeader } from "@/components/features/workspace"; import { FileBrowser, GitToolbar, CommitPanel } from "@/components/features/git"; import { InstanceList } from "@/components/features/session"; import { FileEditor } from "@/components/features/git"; import { useApiQuery } from "@/hooks/use-api-query"; import type { Project, GitRepository } from "@/types"; ``` **Feature component — self-contained:** ```typescript // components/features/git/FileBrowser.tsx import { useCallback, useEffect, useState } from "react"; import { Icon } from "@/components/ui"; import { apiClient } from "@/api/client"; import type { FileTreeEntry, GitStatus } from "@/types"; import styles from "./FileBrowser.module.css"; ``` **API module — pure functions, no types:** ```typescript // api/git-repositories.ts import { apiClient } from "./client"; import type { GitRepository, GitStatus, FileTreeEntry } from "@/types"; export async function listRepositories(projectId: string): Promise { ... } ``` ### 3.2 Backend Imports (After Refactor) **Router — thin, delegates to services:** ```python # api/tool_instances.py from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.ext.asyncio import AsyncSession from ..auth.dependencies import get_current_user, get_owned_project from ..database import get_db from ..models import User, Project from ..schemas.tool_instance import CreateInstanceRequest, InstanceResponse from ..services.docker import container, tunnel, compose from ..services.profile_resolver import resolve_profile router = APIRouter(prefix="/projects/{project_id}/repositories/{repo_id}/instances") @router.post("", response_model=InstanceResponse) async def create_instance( project_id: str, repo_id: str, request: CreateInstanceRequest, user: User = Depends(get_current_user), project: Project = Depends(get_owned_project), db: AsyncSession = Depends(get_db), ): compose_content = compose.generate_compose(...) await container.create_container(...) return InstanceResponse(...) ``` **Service — pure business logic:** ```python # services/docker/container.py import subprocess from pathlib import Path from .compose import generate_compose from .config_staging import stage_config_files def create_container(instance_id: str, project_id: str, compose_path: Path) -> dict: stage_config_files(instance_id) result = subprocess.run( ["docker", "compose", "-f", str(compose_path), "up", "-d"], capture_output=True, text=True, ) ... ``` --- ## 4. CSS Modules Migration Strategy ### 4.1 How It Works Vite has built-in CSS Modules support. Naming a file `{name}.module.css` makes Vite: 1. Scope all class names to that component 2. Export a mapping object from the import ```typescript import styles from "./Button.module.css"; // In JSX: // → renders as: ``` ### 4.2 Migration Mechanics **Step 1: Extract component styles from `styles.css`** For each component, find its CSS rules in `styles.css` and move them to `{Component}.module.css`. Example — `FileBrowser`: ```css /* components/features/git/FileBrowser.module.css */ .fileBrowser { padding: 0.5rem; overflow: auto; } .treeEntry { display: block; padding: 0.375rem 0.5rem; ... } .treeDirectory { font-weight: 500; } /* etc. */ ``` **Step 2: Convert global class names to camelCase in the module** Original: `.file-tree`, `.tree-entry`, `.tree-directory` Module: `.fileBrowser`, `.treeEntry`, `.treeDirectory` **Step 3: Update component to import the module** ```typescript import styles from "./FileBrowser.module.css"; // Before:
// After:
``` ### 4.3 Global Styles That Stay Global These rules remain in `styles/global.css` or `styles/utilities.css`: ```css /* styles/global.css */ :root { /* CSS variables */ } * { box-sizing: border-box; } body { margin: 0; background: var(--bg); } /* Shell layout — used by AppShell only */ .shell { min-height: 100vh; display: flex; flex-direction: column; } .shell-body { display: grid; grid-template-columns: 230px 1fr; } ``` ```css /* styles/utilities.css */ .stack { display: flex; flex-direction: column; gap: 1rem; } .row { display: flex; flex-wrap: wrap; gap: 1rem; align-items: center; } .truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } ``` ### 4.4 Page-Specific Layout Styles Some pages need layout rules that don't belong to any single component: ```css /* styles/pages/repo-workspace.css */ .repo-workspace { display: flex; flex-direction: column; height: calc(100vh - 60px); } .workspace-layout { display: flex; flex: 1; overflow: hidden; } .workspace-sidebar { width: 280px; min-width: 280px; ... } ``` These are imported by the page component: ```typescript import "@/styles/pages/repo-workspace.css"; ``` --- ## 5. Per-Phase Migration Mechanics ### Phase 1: Safe Foundations **Goal:** Low-risk extractions that establish the new patterns without touching many files. | Action | Old Location | New Location | Technique | |--------|-------------|--------------|-----------| | Extract `FileBrowser` | `pages/repo-workspace.tsx` (inline) | `components/features/git/FileBrowser.tsx` | Cut-paste + import rewrite | | Create `types/session.ts` | `api/sessions.ts` + `state/sessions.tsx` | `types/session.ts` | Extract shared interface | | Create `types/tool-instance.ts` | `api/sessions.ts` | `types/tool-instance.ts` | Extract interface | | Create `types/tool-type.ts` | `api/tool-types.ts` | `types/tool-type.ts` | Extract interface | | Create `types/git-repository.ts` | `api/git-repositories.ts` | `types/git-repository.ts` | Extract interface | | Create `types/project.ts` | `types.ts` + scattered | `types/project.ts` | Extract from types.ts | | Create `types/user.ts` | `types.ts` + `api/auth.ts` | `types/user.ts` | Extract from types.ts | | Create `types/api-response.ts` | Nowhere (new) | `types/api-response.ts` | New file for generic types | | Update `api/sessions.ts` | inline types | imports from `types/` | Import rewrite | | Update `state/sessions.tsx` | inline `Session` | imports from `types/session.ts` | Import rewrite | | Move seed data | `main.py` (hardcoded) | `seeds/builtin_tool_types.py` | Cut-paste + import | | Extract auth deps | Duplicated in routers | `auth/dependencies.py` + `api/` imports | Cut-paste + import rewrite | | Create barrel | `types/` | `types/index.ts` | New file | **Quality gate:** `tsc --noEmit`, `pytest`, verify `repo-workspace.tsx` still works ### Phase 2: Style System **Goal:** Replace `styles.css` with modular styles. This is the largest diff but lowest risk (no JS logic changes). | Action | Old | New | Technique | |--------|-----|-----|-----------| | Create `styles/tokens.css` | `styles.css` (variables section) | New file | Extract `:root` and `[data-theme="dark"]` | | Create `styles/global.css` | `styles.css` (reset + layout) | New file | Extract `*`, `body`, `.shell-*` | | Create `styles/utilities.css` | `styles.css` (utility classes) | New file | Extract `.stack`, `.row`, `.truncate`, etc. | | Create `styles/syntax-highlight.css` | `styles.css` (Prism overrides) | New file | Extract all `code[class*="language-"]` rules | | Create component `.module.css` files | `styles.css` (component sections) | Per-component files | Extract `.terminal-*`, `.git-toolbar`, `.file-editor`, etc. | | Create page layout CSS files | `styles.css` (page sections) | `styles/pages/*.css` | Extract `.repo-workspace`, `.sessions-page`, etc. | | Delete `styles.css` | `styles.css` | — | `git rm` | | Update `main.tsx` | imports `styles.css` | imports `styles/global.css`, `styles/tokens.css`, etc. | Edit import | | Update components | use global class names | import `.module.css` and use `styles.className` | Edit JSX + add CSS file | **Migration order within Phase 2:** 1. Extract tokens + global + utilities + syntax-highlight (safe, no component changes) 2. Extract component styles one domain at a time: terminal → git → session → settings 3. Extract page layout styles 4. Delete `styles.css` 5. Run full visual check **Quality gate:** `npm run build` succeeds, `npm run lint` passes, manual visual verification of all pages ### Phase 3a: Backend Shared Dependencies **Goal:** Extract duplicated auth helpers so later router splits don't duplicate them. | Action | Old | New | Technique | |--------|-----|-----|-----------| | Extract `get_current_user` | `api/tool_instances.py`, `api/git_repositories.py`, etc. | `auth/dependencies.py` | Find all `_get_user` functions, unify, move | | Extract `get_owned_project` | Same routers | `auth/dependencies.py` | Same | | Extract `get_owned_repository` | Same routers | `auth/dependencies.py` | Same | | Update router imports | inline helper | `from ..auth.dependencies import get_current_user` | Import rewrite | **Quality gate:** `pytest` passes, all integration tests pass ### Phase 3b: `tool_instances.py` Decomposition **Goal:** Split the 1,463-line monster into router + services + schemas. | Action | Old | New | Technique | |--------|-----|-----|-----------| | Create schemas | Inline Pydantic models in router | `schemas/tool_instance.py` | Extract `CreateInstanceRequest`, `InstanceResponse`, etc. | | Extract compose logic | `tool_instances.py` `_modify_compose_file` | `services/docker/compose.py` | Cut-paste + tests | | Extract container lifecycle | `tool_instances.py` start/stop/restart | `services/docker/container.py` | Cut-paste | | Extract tunnel logic | `tool_instances.py` recreate-tunnel, health | `services/docker/tunnel.py` | Cut-paste | | Extract config staging | `tool_instances.py` config folder writing | `services/docker/config_staging.py` | Cut-paste | | Extract instance name gen | `tool_instances.py` `_generate_instance_name` | `services/docker/compose.py` or new `services/instances/naming.py` | Cut-paste | | Slim router | 1,463 lines | ~250 lines (endpoints + thin handlers) | Delete moved code, add imports | **Quality gate:** `pytest`, especially integration tests for tool instances ### Phase 3c: `git_repositories.py` + `config_profiles.py` Decomposition | Action | Old | New | Technique | |--------|-----|-----|-----------| | Create `schemas/git_repository.py` | Inline in router | New file | Extract | | Create `schemas/config_profile.py` | Inline in router | New file | Extract | | Extract file browsing endpoints | `git_repositories.py` | `api/git_files.py` (or keep in router but delegate) | Move endpoint handlers | | Extract git control endpoints | `git_repositories.py` | Keep in router but delegate to `services/git/control.py` | Thin handlers | | Extract config profile resolution | `config_profiles.py` | `services/profile_resolver.py` (already exists, use it more) | Refactor to use existing service | | Slim routers | 900 + 877 lines | ~250 lines each | Delete moved code | **Quality gate:** `pytest`, git-related integration tests ### Phase 4a: `tool-workshop` Page Split **Goal:** Split the 700-line page into tab components. | Action | Old | New | Technique | |--------|-----|-----|-----------| | Extract `ToolTypesTab` | `pages/tool-workshop.tsx` (inline state + JSX) | `components/features/tool-workshop/ToolTypesTab.tsx` | Cut-paste | | Extract `ToolConfigsTab` | Same | `components/features/tool-workshop/ToolConfigsTab.tsx` | Cut-paste | | Extract `ConfigFoldersTab` | Same | `components/features/tool-workshop/ConfigFoldersTab.tsx` | Cut-paste | | Slim page | ~700 lines | ~100 lines (tab switcher + layout) | Compose tabs | | Create barrel | — | `components/features/tool-workshop/index.ts` | New | **Quality gate:** `tsc`, `eslint`, manual test of all 3 tabs ### Phase 4b: Pages Split | Action | Old | New | Technique | |--------|-----|-----|-----------| | Extract `SessionList`, `SessionCard`, `CreateSessionForm` | `pages/sessions.tsx` | `components/features/session/` | Cut-paste | | Extract `DashboardSummary`, `QuickActions` | `pages/dashboard.tsx` | `components/features/dashboard/` | Cut-paste | | Rename pages | `dashboard.tsx` | `DashboardPage.tsx` | `git mv` | | Rename pages | `git-history.tsx` | `GitHistoryPage.tsx` | `git mv` | | Rename pages | `repo-workspace.tsx` | `RepoWorkspacePage.tsx` | `git mv` | | etc. | all pages | PascalCase matching component | `git mv` | **Quality gate:** `tsc`, `eslint`, router still resolves all routes ### Phase 4c: Naming Consistency | Action | Old | New | Technique | |--------|-----|-----|-----------| | Rename component files | `app-shell.tsx` | `AppShell.tsx` | `git mv` | | Rename component files | `git-toolbar.tsx` | `GitToolbar.tsx` | `git mv` | | Rename component files | `file-editor.tsx` | `FileEditor.tsx` | `git mv` | | Rename component files | `instance-list.tsx` | `InstanceList.tsx` | `git mv` | | Rename component files | `terminal.tsx` | `TerminalComponent.tsx` | `git mv` | | Rename API files | `tool_types.ts` | `tool-types.ts` | `git mv` | | Rename API files | `git_repositories.ts` | `git-repositories.ts` | `git mv` | | Rename API files | `config_folders.ts` | `config-folders.ts` | `git mv` | | Update all imports | old paths | new paths | IDE refactor / sed | | Update router | old page paths | new page paths | Edit `router.tsx` | **Quality gate:** `tsc`, `eslint`, all tests pass ### Phase 5: Tests + Docs | Action | Description | |--------|-------------| | Add tests for `FileBrowser` | Basic render + interaction tests | | Add tests for `LoadingState`, `ErrorState` | Render tests | | Add tests for extracted tabs | `ToolTypesTab`, `ToolConfigsTab`, `ConfigFoldersTab` | | Write `docs/development/naming.md` | Document all naming conventions | | Dead code cleanup | Remove unused CSS classes, unused exports | | Final quality gate | Full `tsc`, `eslint`, `pytest`, build, visual check | --- ## 6. Risk Mitigation by Phase ### Phase 1 (Safe Foundations) - **Risk:** Type extraction breaks consumers - **Mitigation:** Update ALL consumers in the same commit; run `tsc` before commit ### Phase 2 (Style System) - **Risk:** Visual regressions from CSS split - **Mitigation:** Keep original `styles.css` until all extractions are verified; delete only at phase end ### Phase 3 (Backend Decomposition) - **Risk:** Endpoint behavior changes during router slimming - **Mitigation:** Pure cut-paste with zero logic changes; integration tests verify behavior ### Phase 4 (Frontend Pages) - **Risk:** Router breaks from file renames - **Mitigation:** Update `router.tsx` in the same commit as renames; `git mv` preserves history ### Phase 5 (Tests + Docs) - **Risk:** Low — additive only --- ## 7. Tooling Recommendations ### Import Rewriting Use VS Code / Vite path aliases to minimize import churn: ```json // tsconfig.json (already configured) "paths": { "@/*": ["src/*"] } ``` ### Automated Refactoring - **File moves:** `git mv` (preserves git history) - **Import updates:** VS Code "Move to new file" or find-replace with path patterns - **Dead CSS detection:** `purgecss` or manual grep — run after Phase 2 ### Verification Scripts ```bash # File size check find apps/web/src apps/api/src -type f \( -name "*.ts" -o -name "*.tsx" -o -name "*.py" -o -name "*.css" \) -exec sh -c 'lines=$(wc -l < "$1"); if [ "$lines" -gt 300 ]; then echo "OVERSIZED ($lines): $1"; fi' _ {} \; # Inner component check grep -rn "const [A-Z].*=" apps/web/src/pages/ || echo "No inner components found" # CSS module check find apps/web/src/components -name "*.module.css" | wc -l # Barrel export check test -f apps/web/src/types/index.ts && echo "types barrel exists" test -f apps/web/src/components/ui/index.ts && echo "ui barrel exists" ``` --- ## 8. Definition of Done (Per Phase) Each phase is done when: 1. All files in the phase are ≤ 300 lines 2. `tsc --noEmit` passes 3. `eslint` passes 4. `pytest` passes (backend phases) or `vitest run` passes (frontend phases) 5. No visual regressions (frontend phases) 6. Commit uses `git mv` for moves (preserves history) 7. Commit message references this SDD change: `refactor: phase N — description` --- *Design prepared for SDD tasks phase. Next: break into reviewable implementation tasks with line-count forecasts.*