- 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)
30 KiB
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
mainimmediately
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:
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:
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:
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:
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:
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:
// 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:
// 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:
// 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:
# 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:
# 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:
- Scope all class names to that component
- Export a mapping object from the import
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:
/* 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
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:
/* 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; }
/* 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:
/* 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:
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:
- Extract tokens + global + utilities + syntax-highlight (safe, no component changes)
- Extract component styles one domain at a time: terminal → git → session → settings
- Extract page layout styles
- Delete
styles.css - 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
tscbefore commit
Phase 2 (Style System)
- Risk: Visual regressions from CSS split
- Mitigation: Keep original
styles.cssuntil 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.tsxin the same commit as renames;git mvpreserves history
Phase 5 (Tests + Docs)
- Risk: Low — additive only
7. Tooling Recommendations
Import Rewriting
Use VS Code / Vite path aliases to minimize import churn:
// 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:
purgecssor manual grep — run after Phase 2
Verification Scripts
# 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:
- All files in the phase are ≤ 300 lines
tsc --noEmitpasseseslintpassespytestpasses (backend phases) orvitest runpasses (frontend phases)- No visual regressions (frontend phases)
- Commit uses
git mvfor moves (preserves history) - 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.