Files
Developer ee1fa6bee5 refactor: centralize types and extract seed data (Task 1.1)
- Create types/ directory with centralized domain types:
  session, tool-instance, tool-type, git-repository, config-folder,
  tool-config, project, user, api-response
- Remove inline type definitions from API modules;
  re-export from types/ for backward compatibility
- Update state/sessions.tsx to import Session from types/session.ts
- Update all consumer components/pages to import from types/
- Extract seed_builtin_tool_types from main.py to
  seeds/builtin_tool_types.py
- Create types/index.ts barrel export

Quality gates: tsc (pass), eslint (pass), Python syntax (pass)
2026-06-02 18:56:54 +00:00

724 lines
30 KiB
Markdown

# 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<T>, PaginatedResponse<T>
│ ├── 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<GitRepository[]> { ... }
```
### 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:
<button className={styles.primary}>Click</button>
// → renders as: <button class="Button_primary__a3f7b">Click</button>
```
### 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: <div className="file-tree">
// After: <div className={styles.fileBrowser}>
```
### 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.*