# Tasks: Repository Restructuring and Modularization ## Overview 9 reviewable PRs (all ≤ 400 lines changed) implementing the full restructure. Each task is a standalone merge to `main`. Dependencies are explicit. Review workload is protected. **Conventions:** - `+N/-M` = lines added / removed in the PR - `Files: N` = number of files touched - `Deps:` = must-merge tasks before this one --- ## Phase 1: Safe Foundations ### Task 1.1: Centralize Types and Extract Seed Data **PR label:** `refactor: centralize types and extract seed data` **Estimated:** +180 / −120 lines across 15 files **Deps:** None **What:** - Create `types/` directory with domain type files - Move types out of `api/sessions.ts`, `api/tool-types.ts`, `api/git-repositories.ts`, `api/config-folders.ts` - Move `Session` definition from `state/sessions.tsx` to `types/session.ts` - Move `ToolInstance` definition from `api/sessions.ts` to `types/tool-instance.ts` - Move hardcoded seed data from `main.py` to `seeds/builtin_tool_types.py` - Create `types/index.ts` barrel export - Update all consumers to import from `types/` **Files:** ``` NEW: types/session.ts (from api/sessions.ts + state/sessions.tsx) NEW: types/tool-instance.ts (from api/sessions.ts) NEW: types/tool-type.ts (from api/tool-types.ts) NEW: types/git-repository.ts (from api/git-repositories.ts) NEW: types/config-folder.ts (from api/config-folders.ts) NEW: types/project.ts (from types.ts) NEW: types/user.ts (from types.ts) NEW: types/api-response.ts (new generic types) NEW: types/index.ts (barrel) NEW: seeds/builtin_tool_types.py (from main.py) MOD: api/sessions.ts (remove inline types, import from types/) MOD: api/tool-types.ts (remove inline types, import from types/) MOD: api/git-repositories.ts (remove inline types, import from types/) MOD: api/config-folders.ts (remove inline types, import from types/) MOD: state/sessions.tsx (import Session from types/) MOD: types.ts (remove moved types) MOD: main.py (import seed data from seeds/) ``` **Acceptance criteria:** - [ ] `grep -n "interface Session" apps/web/src` returns exactly 1 result (in `types/session.ts`) - [ ] `grep -n "interface ToolInstance" apps/web/src` returns exactly 1 result - [ ] `tsc --noEmit` passes with zero errors - [ ] `pytest` passes - [ ] No behavior changes --- ### Task 1.2: Extract FileBrowser and Shared UI Components **PR label:** `refactor: extract FileBrowser and shared UI primitives` **Estimated:** +150 / −80 lines across 8 files **Deps:** 1.1 **What:** - Extract `FileBrowser` component from inline definition in `repo-workspace.tsx` - Create `components/features/git/FileBrowser.tsx` - Create `components/ui/LoadingState.tsx` (reusable loading pattern) - Create `components/ui/ErrorState.tsx` (reusable error+retry pattern) - Create `components/ui/index.ts` barrel - Update `repo-workspace.tsx` to import `FileBrowser` - Update pages that use loading/error patterns to use new components **Files:** ``` NEW: components/features/git/FileBrowser.tsx (from repo-workspace.tsx) NEW: components/ui/LoadingState.tsx NEW: components/ui/ErrorState.tsx NEW: components/ui/StatusBadge.tsx NEW: components/ui/index.ts (barrel) MOD: pages/repo-workspace.tsx (remove inline FileBrowser, import) MOD: pages/dashboard.tsx (use LoadingState, ErrorState) MOD: pages/sessions.tsx (use LoadingState, ErrorState) ``` **Acceptance criteria:** - [ ] `grep -n "const FileBrowser" pages/repo-workspace.tsx` returns zero results - [ ] FileBrowser renders correctly in repo workspace - [ ] `tsc --noEmit` passes - [ ] `eslint` passes --- ## Phase 2: Style System ### Task 2.1: Extract Global and Token Styles **PR label:** `refactor: split styles.css — global styles and tokens` **Estimated:** +120 / −50 lines across 5 files **Deps:** 1.2 **What:** - Create `styles/tokens.css` — CSS variables + dark theme - Create `styles/global.css` — reset, body, shell layout - Create `styles/utilities.css` — .stack, .row, .truncate, etc. - Create `styles/syntax-highlight.css` — Prism.js overrides - Update `main.tsx` to import the 4 new files - Do NOT delete `styles.css` yet **Files:** ``` NEW: styles/tokens.css (from styles.css lines 1–80) NEW: styles/global.css (from styles.css: body, .shell, .shell-header, etc.) NEW: styles/utilities.css (from styles.css: .stack, .row, .truncate, etc.) NEW: styles/syntax-highlight.css (from styles.css: Prism overrides) MOD: main.tsx (add imports for new style files) ``` **Acceptance criteria:** - [ ] All 4 new CSS files exist and contain only their concern - [ ] `npm run build` succeeds - [ ] No visual regressions on shell layout - [ ] `styles.css` still exists (deleted in Task 2.3) --- ### Task 2.2: Extract Component CSS Modules (Part 1 — Terminal + Git) **PR label:** `refactor: extract CSS modules for terminal and git components` **Estimated:** +280 / −200 lines across 14 files **Deps:** 2.1 **What:** - Create `.module.css` files for terminal and git components - Extract styles from `styles.css` for: Terminal, GitToolbar, FileBrowser, FileEditor, CommitPanel, CommitDialog, MergeDialog - Update components to import their `.module.css` - Convert global class names to camelCase module classes **Files:** ``` NEW: components/features/terminal/TerminalComponent.module.css NEW: components/features/git/GitToolbar.module.css NEW: components/features/git/FileBrowser.module.css NEW: components/features/git/FileEditor.module.css NEW: components/features/git/CommitPanel.module.css NEW: components/features/git/CommitDialog.module.css NEW: components/features/git/MergeDialog.module.css MOD: components/terminal.tsx (import module, use styles.*) MOD: components/git-toolbar.tsx (import module, use styles.*) MOD: components/features/git/FileBrowser.tsx MOD: components/file-editor.tsx MOD: styles.css (remove extracted sections) ``` **Acceptance criteria:** - [ ] Terminal renders identically - [ ] Git toolbar, file browser, file editor render identically - [ ] Commit panel and dialogs render identically - [ ] `npm run build` succeeds - [ ] `eslint` passes --- ### Task 2.3: Extract Component CSS Modules (Part 2 — Session + Settings + Layout) + Delete styles.css **PR label:** `refactor: extract CSS modules for session/settings + delete monolithic styles.css` **Estimated:** +250 / −2,500 lines across 12 files **Deps:** 2.2 **What:** - Create `.module.css` files for: InstanceList, AppShell, Navigation, SettingsTabLayout - Create `styles/pages/sessions.css`, `styles/pages/repo-workspace.css`, `styles/pages/tool-workshop.css` - Extract remaining component styles from `styles.css` - Update components to import modules - **Delete `styles.css`** - Verify no remaining references to `styles.css` **Files:** ``` NEW: components/features/session/InstanceList.module.css NEW: components/layout/AppShell.module.css NEW: components/layout/Navigation.module.css NEW: components/features/settings/SettingsTabLayout.module.css NEW: styles/pages/sessions.css NEW: styles/pages/repo-workspace.css NEW: styles/pages/tool-workshop.css MOD: components/instance-list.tsx MOD: components/app-shell.tsx MOD: components/settings-tab-layout.tsx MOD: pages/sessions.tsx MOD: pages/repo-workspace.tsx DEL: styles.css ``` **Acceptance criteria:** - [ ] `test -f styles.css` fails (file deleted) - [ ] All pages render identically - [ ] `npm run build` succeeds - [ ] No unstyled components - [ ] `eslint` passes --- ## Phase 3: Backend Decomposition ### Task 3.1: Extract Shared Auth Dependencies **PR label:** `refactor: extract shared auth dependencies` **Estimated:** +90 / −150 lines across 10 files **Deps:** 1.1 **What:** - Create `auth/dependencies.py` with `get_current_user()`, `get_owned_project()`, `get_owned_repository()` - Find and remove duplicated `_get_user()` / `_get_owned_project()` helpers from all routers - Update routers to import from `auth.dependencies` - Ensure dependency signatures match across all routers **Files:** ``` NEW: auth/dependencies.py (consolidated from router files) MOD: api/tool_instances.py (remove inline helpers, import) MOD: api/git_repositories.py (remove inline helpers, import) MOD: api/config_profiles.py (remove inline helpers, import) MOD: api/ssh_keys.py (remove inline helpers, import) MOD: api/projects.py (remove inline helpers, import) MOD: api/tool_configs.py (remove inline helpers, import) MOD: api/config_folders.py (remove inline helpers, import) MOD: api/terminal.py (remove inline helpers, import) ``` **Acceptance criteria:** - [ ] `grep -rn "def _get_user" apps/api/src/api/` returns zero results - [ ] `grep -rn "def _get_owned_project" apps/api/src/api/` returns zero results - [ ] All integration tests pass - [ ] `pytest` passes --- ### Task 3.2: Create Pydantic Schemas Directory **PR label:** `refactor: extract pydantic schemas from routers` **Estimated:** +200 / −100 lines across 8 files **Deps:** 3.1 **What:** - Create `schemas/` directory - Extract request/response models from `api/tool_instances.py` → `schemas/tool_instance.py` - Extract from `api/git_repositories.py` → `schemas/git_repository.py` - Extract from `api/config_profiles.py` → `schemas/config_profile.py` - Extract from `api/tool_types.py` → `schemas/tool_type.py` - Update routers to import schemas - Keep schema imports backward-compatible (routers still work) **Files:** ``` NEW: schemas/tool_instance.py NEW: schemas/git_repository.py NEW: schemas/config_profile.py NEW: schemas/tool_type.py NEW: schemas/ssh_key.py NEW: schemas/project.py MOD: api/tool_instances.py (remove inline schemas, import) MOD: api/git_repositories.py (remove inline schemas, import) MOD: api/config_profiles.py (remove inline schemas, import) MOD: api/tool_types.py (remove inline schemas, import) ``` **Acceptance criteria:** - [ ] No Pydantic `BaseModel` definitions in router files - [ ] `pytest` passes - [ ] All API endpoints return correct response shapes --- ### Task 3.3: Split services/docker.py into Focused Modules **PR label:** `refactor: split services/docker.py into focused modules` **Estimated:** +350 / −300 lines across 6 files **Deps:** 3.2 **What:** - Create `services/docker/compose.py` — compose file generation + modification - Create `services/docker/container.py` — container lifecycle (create, start, stop, restart, remove) - Create `services/docker/tunnel.py` — Cloudflare tunnel create/recreate/health - Create `services/docker/config_staging.py` — config folder file writing - Create `services/docker/__init__.py` barrel - Delete `services/docker.py` - Update `api/tool_instances.py` to import from `services.docker` **Files:** ``` NEW: services/docker/__init__.py NEW: services/docker/compose.py NEW: services/docker/container.py NEW: services/docker/tunnel.py NEW: services/docker/config_staging.py MOD: api/tool_instances.py (update imports) DEL: services/docker.py ``` **Acceptance criteria:** - [ ] `test -f services/docker.py` fails (deleted) - [ ] Each new module ≤ 300 lines - [ ] `pytest` passes - [ ] Tool instance create/start/stop/restart still works --- ### Task 3.4: Slim tool_instances.py Router **PR label:** `refactor: slim tool_instances router to HTTP-only concerns` **Estimated:** +80 / −700 lines across 3 files **Deps:** 3.3 **What:** - Remove all business logic from `api/tool_instances.py` - Move compose generation calls to `services.docker.compose` - Move container lifecycle calls to `services.docker.container` - Move tunnel calls to `services.docker.tunnel` - Move config staging calls to `services.docker.config_staging` - Router should only: validate input, call service, return response - Target: ~250 lines **Files:** ``` MOD: api/tool_instances.py (remove ~700 lines of logic, keep ~250 of routing) MOD: services/docker/compose.py (may need minor adjustments) MOD: services/docker/container.py (may need minor adjustments) ``` **Acceptance criteria:** - [ ] `api/tool_instances.py` ≤ 300 lines - [ ] `grep -n "subprocess" api/tool_instances.py` returns zero results - [ ] `grep -n "docker" api/tool_instances.py` returns only import lines - [ ] `pytest` passes, especially tool instance integration tests --- ### Task 3.5: Slim git_repositories.py and config_profiles.py Routers **PR label:** `refactor: slim git_repositories and config_profiles routers` **Estimated:** +100 / −600 lines across 6 files **Deps:** 3.4 **What:** - Extract git control logic from `api/git_repositories.py` to `services/git/control.py` (already exists, use more) - Extract file browsing logic to thin handlers delegating to `services/git/files.py` - Extract config profile resolution logic to `services/profile_resolver.py` - Slim both routers to ~250 lines each - Ensure routers contain only route definitions and thin handlers **Files:** ``` MOD: api/git_repositories.py (remove business logic, delegate) MOD: api/config_profiles.py (remove business logic, delegate) MOD: services/git/control.py (may expand) MOD: services/git/files.py (may expand) MOD: services/profile_resolver.py (may expand) ``` **Acceptance criteria:** - [ ] `api/git_repositories.py` ≤ 300 lines - [ ] `api/config_profiles.py` ≤ 300 lines - [ ] `pytest` passes - [ ] Git operations (branch, commit, push, pull) still work --- ## Phase 4: Frontend Page Decomposition ### Task 4.1: Split tool-workshop.tsx into Tab Components **PR label:** `refactor: split tool-workshop page into tab components` **Estimated:** +280 / −450 lines across 6 files **Deps:** 2.3 **What:** - Create `components/features/tool-workshop/ToolTypesTab.tsx` - Create `components/features/tool-workshop/ToolConfigsTab.tsx` - Create `components/features/tool-workshop/ConfigFoldersTab.tsx` - Create `components/features/tool-workshop/index.ts` barrel - Slim `pages/tool-workshop.tsx` to tab switcher + layout only (~100 lines) - Each tab manages its own form state and API calls **Files:** ``` NEW: components/features/tool-workshop/ToolTypesTab.tsx NEW: components/features/tool-workshop/ToolConfigsTab.tsx NEW: components/features/tool-workshop/ConfigFoldersTab.tsx NEW: components/features/tool-workshop/index.ts MOD: pages/tool-workshop.tsx (remove inline tabs, compose imports) ``` **Acceptance criteria:** - [ ] `pages/tool-workshop.tsx` ≤ 150 lines - [ ] All 3 tabs function identically - [ ] `tsc --noEmit` passes - [ ] `eslint` passes --- ### Task 4.2: Extract SessionsPage Components **PR label:** `refactor: extract sessions page components` **Estimated:** +220 / −350 lines across 7 files **Deps:** 4.1 **What:** - Create `components/features/session/SessionList.tsx` - Create `components/features/session/SessionCard.tsx` - Create `components/features/session/CreateSessionForm.tsx` - Create `components/features/session/index.ts` barrel - Slim `pages/sessions.tsx` to layout + composition - Extract inline stop/delete confirmation into reusable `ConfirmDialog` in `components/ui/` **Files:** ``` NEW: components/features/session/SessionList.tsx NEW: components/features/session/SessionCard.tsx NEW: components/features/session/CreateSessionForm.tsx NEW: components/features/session/index.ts NEW: components/ui/ConfirmDialog.tsx MOD: pages/sessions.tsx (remove inline lists/forms, compose) ``` **Acceptance criteria:** - [ ] `pages/sessions.tsx` ≤ 200 lines - [ ] Session list, create form, and cards work identically - [ ] `tsc --noEmit` passes --- ### Task 4.3: Extract Dashboard and RepoWorkspace Components **PR label:** `refactor: extract dashboard and repo-workspace components` **Estimated:** +200 / −300 lines across 8 files **Deps:** 4.2 **What:** - Create `components/features/dashboard/DashboardSummary.tsx` - Create `components/features/dashboard/QuickActions.tsx` - Create `components/features/dashboard/ActiveSessionsList.tsx` - Create `components/features/dashboard/index.ts` barrel - Slim `pages/dashboard.tsx` to layout + composition - Slim `pages/repo-workspace.tsx` further (FileBrowser already extracted in 1.2) - Extract `InstanceList` inline create dialog to `components/features/session/CreateInstanceDialog.tsx` **Files:** ``` NEW: components/features/dashboard/DashboardSummary.tsx NEW: components/features/dashboard/QuickActions.tsx NEW: components/features/dashboard/ActiveSessionsList.tsx NEW: components/features/dashboard/index.ts NEW: components/features/session/CreateInstanceDialog.tsx MOD: pages/dashboard.tsx (slim to ~120 lines) MOD: pages/repo-workspace.tsx (slim further) MOD: components/instance-list.tsx (extract create dialog) ``` **Acceptance criteria:** - [ ] `pages/dashboard.tsx` ≤ 150 lines - [ ] Dashboard renders identically - [ ] `tsc --noEmit` passes --- ### Task 4.4: Rename All Files to Naming Convention **PR label:** `refactor: rename files to PascalCase components and kebab-case APIs` **Estimated:** +30 / −0 lines across 40 files (mostly `git mv`) **Deps:** 4.3 **What:** - Rename component files to PascalCase matching exported name: - `app-shell.tsx` → `AppShell.tsx` - `git-toolbar.tsx` → `GitToolbar.tsx` - `file-editor.tsx` → `FileEditor.tsx` - `instance-list.tsx` → `InstanceList.tsx` - `terminal.tsx` → `TerminalComponent.tsx` - etc. - Rename page files to PascalCase: - `dashboard.tsx` → `DashboardPage.tsx` - `git-history.tsx` → `GitHistoryPage.tsx` - `repo-workspace.tsx` → `RepoWorkspacePage.tsx` - etc. - Rename API files to kebab-case: - `tool_types.ts` → `tool-types.ts` - `git_repositories.ts` → `git-repositories.ts` - `config_folders.ts` → `config-folders.ts` - etc. - Update `router.tsx` to import new page paths - Update all imports across the codebase **Files:** ``` # Component renames (git mv) components/app-shell.tsx → components/layout/AppShell.tsx components/git-toolbar.tsx → components/features/git/GitToolbar.tsx components/file-editor.tsx → components/features/git/FileEditor.tsx components/instance-list.tsx → components/features/session/InstanceList.tsx components/terminal.tsx → components/features/terminal/TerminalComponent.tsx components/code-editor.tsx → components/ui/CodeEditor.tsx components/commit-dialog.tsx → components/features/git/CommitDialog.tsx components/commit-panel.tsx → components/features/git/CommitPanel.tsx components/merge-dialog.tsx → components/features/git/MergeDialog.tsx components/protected-route.tsx → components/ProtectedRoute.tsx components/repositories-settings-tab.tsx → components/features/project/RepositoriesSettingsTab.tsx components/repository-create-dialog.tsx → components/features/project/RepositoryCreateDialog.tsx components/settings-tab-layout.tsx → components/features/settings/SettingsTabLayout.tsx components/syntax-highlighter.tsx → components/features/git/SyntaxHighlighter.tsx components/workspace-header.tsx → components/features/workspace/WorkspaceHeader.tsx components/icon.tsx → components/ui/Icon.tsx # Page renames (git mv) pages/dashboard.tsx → pages/DashboardPage.tsx pages/git-history.tsx → pages/GitHistoryPage.tsx pages/git-repositories.tsx → pages/GitRepositoriesPage.tsx pages/profile.tsx → pages/ProfilePage.tsx pages/project-settings.tsx → pages/ProjectSettingsPage.tsx pages/projects.tsx → pages/ProjectsPage.tsx pages/repo-workspace.tsx → pages/RepoWorkspacePage.tsx pages/sessions.tsx → pages/SessionsPage.tsx pages/settings.tsx → pages/SettingsPage.tsx pages/ssh-keys.tsx → pages/SshKeysPage.tsx pages/terminal.tsx → pages/TerminalPage.tsx pages/tool-configs.tsx → pages/ToolConfigsPage.tsx pages/tool-types.tsx → pages/ToolTypesPage.tsx pages/tool-workshop.tsx → pages/ToolWorkshopPage.tsx pages/placeholder.tsx → pages/PlaceholderPage.tsx # API renames (git mv) api/tool_types.ts → api/tool-types.ts api/git_repositories.ts → api/git-repositories.ts api/config_folders.ts → api/config-folders.ts api/tool_configs.ts → api/tool-configs.ts api/ssh_keys.ts → api/ssh-keys.ts api/user_config.ts → api/user-config.ts # Updated imports MOD: router.tsx MOD: all page files (update relative imports) MOD: all component files (update relative imports) MOD: all test files (update imports) ``` **Acceptance criteria:** - [ ] All component files match exported component name (case-insensitive) - [ ] All page files end with `Page.tsx` - [ ] All API files use kebab-case - [ ] `tsc --noEmit` passes - [ ] `eslint` passes - [ ] `vitest run` passes - [ ] Router resolves all routes --- ## Phase 5: Testing and Polish ### Task 5.1: Add Tests for Extracted Components **PR label:** `test: add tests for extracted components` **Estimated:** +250 / −0 lines across 8 files **Deps:** 4.4 **What:** - Add `components/features/git/FileBrowser.test.tsx` - Add `components/ui/LoadingState.test.tsx` - Add `components/ui/ErrorState.test.tsx` - Add `components/features/tool-workshop/ToolTypesTab.test.tsx` - Add `components/features/session/SessionList.test.tsx` - Add `pages/DashboardPage.test.tsx` (replace failing `projects.test.tsx` pattern) - Ensure tests use `MemoryRouter` where needed - Mock API calls consistently **Files:** ``` NEW: components/features/git/FileBrowser.test.tsx NEW: components/ui/LoadingState.test.tsx NEW: components/ui/ErrorState.test.tsx NEW: components/features/tool-workshop/ToolTypesTab.test.tsx NEW: components/features/session/SessionList.test.tsx NEW: pages/DashboardPage.test.tsx ``` **Acceptance criteria:** - [ ] All new tests pass (`vitest run`) - [ ] No test file exceeds 200 lines - [ ] Tests cover render, basic interaction, and error states --- ### Task 5.2: Documentation and Cleanup **PR label:** `docs: add naming conventions doc and final cleanup` **Estimated:** +120 / −50 lines across 6 files **Deps:** 5.1 **What:** - Write `docs/development/naming.md` with full naming convention table - Remove dead CSS classes (verified by grep for unused selectors) - Remove unused exports (check `eslint` `report-unused-disable-directives`) - Add verification script to `package.json`: `"check-structure": "node scripts/check-structure.js"` - Final quality gate run **Files:** ``` NEW: docs/development/naming.md NEW: scripts/check-structure.js (verifies file sizes, naming, barrels) MOD: package.json (add check-structure script) MOD: styles/global.css (remove dead rules if any) MOD: various files (remove unused exports) ``` **Acceptance criteria:** - [ ] `docs/development/naming.md` exists and is complete - [ ] `npm run check-structure` passes - [ ] No file in `src/` exceeds 300 lines - [ ] `tsc --noEmit` passes - [ ] `eslint` passes - [ ] `vitest run` passes - [ ] `pytest` passes --- ## Task Dependency Graph ``` 1.1 (Types + Seeds) ──┐ ├──→ 1.2 (FileBrowser + UI) ──→ 2.1 (Global Styles) │ 3.1 (Auth deps) ──→ 3.2 (Schemas) ─┤ │ │ └──→ 3.3 (Docker split) ──→ 3.4 (tool_instances slim) │ └──→ 3.5 (git + profiles slim) │ 2.2 (Terminal/Git CSS) ──→ 2.3 (Session/Settings CSS + delete styles.css) ────────────────┘ │ │ 4.1 (tool-workshop split) ──→ 4.2 (sessions split) ──→ 4.3 (dashboard/workspace split) ──→ 4.4 (rename files) │ 5.1 (tests) ──→ 5.2 (docs + cleanup) ────────────────────────────────────────────────────────────────────────┘ ``` --- ## Review Workload Summary | Task | Est. Lines | Status | |------|-----------|--------| | 1.1 | +180 / −120 | ✅ Under 400 | | 1.2 | +150 / −80 | ✅ Under 400 | | 2.1 | +120 / −50 | ✅ Under 400 | | 2.2 | +280 / −200 | ✅ Under 400 | | 2.3 | +250 / −2,500 | ✅ Under 400 (mostly deletions) | | 3.1 | +90 / −150 | ✅ Under 400 | | 3.2 | +200 / −100 | ✅ Under 400 | | 3.3 | +350 / −300 | ✅ Under 400 | | 3.4 | +80 / −700 | ✅ Under 400 | | 3.5 | +100 / −600 | ✅ Under 400 | | 4.1 | +280 / −450 | ✅ Under 400 | | 4.2 | +220 / −350 | ✅ Under 400 | | 4.3 | +200 / −300 | ✅ Under 400 | | 4.4 | +30 / −0 | ✅ Under 400 (git mv mostly) | | 5.1 | +250 / −0 | ✅ Under 400 | | 5.2 | +120 / −50 | ✅ Under 400 | **All 16 tasks are under the 400-line review budget.** --- ## Quality Gates (Per Task) Every task MUST pass: 1. `npm run typecheck` (frontend) — zero errors 2. `npm run lint` (frontend) — zero warnings 3. `pytest` (backend) — zero failures 4. File size check — no file > 300 lines 5. For frontend tasks: visual sanity check (build succeeds) 6. Commit with conventional format: `refactor: phase N — description` --- ## Task Execution Notes - **Use `git mv`** for all file renames to preserve history - **Update imports with IDE refactor** when possible (VS Code "Move to new file", PyCharm refactor) - **No logic changes** — pure cut-paste-reorganize - **Merge to `main` immediately** after each task passes quality gates - **Pause between phases** (after Tasks 1.2, 2.3, 3.5, 4.4) to verify stability