- 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)
26 KiB
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 PRFiles: N= number of files touchedDeps:= 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
Sessiondefinition fromstate/sessions.tsxtotypes/session.ts - Move
ToolInstancedefinition fromapi/sessions.tstotypes/tool-instance.ts - Move hardcoded seed data from
main.pytoseeds/builtin_tool_types.py - Create
types/index.tsbarrel 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/srcreturns exactly 1 result (intypes/session.ts)grep -n "interface ToolInstance" apps/web/srcreturns exactly 1 resulttsc --noEmitpasses with zero errorspytestpasses- 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
FileBrowsercomponent from inline definition inrepo-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.tsbarrel - Update
repo-workspace.tsxto importFileBrowser - 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.tsxreturns zero results- FileBrowser renders correctly in repo workspace
tsc --noEmitpasseseslintpasses
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.tsxto import the 4 new files - Do NOT delete
styles.cssyet
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 buildsucceeds- No visual regressions on shell layout
styles.cssstill 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.cssfiles for terminal and git components - Extract styles from
styles.cssfor: 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 buildsucceedseslintpasses
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.cssfiles 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.cssfails (file deleted)- All pages render identically
npm run buildsucceeds- No unstyled components
eslintpasses
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.pywithget_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 resultsgrep -rn "def _get_owned_project" apps/api/src/api/returns zero results- All integration tests pass
pytestpasses
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
BaseModeldefinitions in router files pytestpasses- 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__.pybarrel - Delete
services/docker.py - Update
api/tool_instances.pyto import fromservices.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.pyfails (deleted)- Each new module ≤ 300 lines
pytestpasses- 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 linesgrep -n "subprocess" api/tool_instances.pyreturns zero resultsgrep -n "docker" api/tool_instances.pyreturns only import linespytestpasses, 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.pytoservices/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 linesapi/config_profiles.py≤ 300 linespytestpasses- 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.tsbarrel - Slim
pages/tool-workshop.tsxto 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 --noEmitpasseseslintpasses
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.tsbarrel - Slim
pages/sessions.tsxto layout + composition - Extract inline stop/delete confirmation into reusable
ConfirmDialogincomponents/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 --noEmitpasses
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.tsbarrel - Slim
pages/dashboard.tsxto layout + composition - Slim
pages/repo-workspace.tsxfurther (FileBrowser already extracted in 1.2) - Extract
InstanceListinline create dialog tocomponents/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 --noEmitpasses
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.tsxgit-toolbar.tsx→GitToolbar.tsxfile-editor.tsx→FileEditor.tsxinstance-list.tsx→InstanceList.tsxterminal.tsx→TerminalComponent.tsx- etc.
- Rename page files to PascalCase:
dashboard.tsx→DashboardPage.tsxgit-history.tsx→GitHistoryPage.tsxrepo-workspace.tsx→RepoWorkspacePage.tsx- etc.
- Rename API files to kebab-case:
tool_types.ts→tool-types.tsgit_repositories.ts→git-repositories.tsconfig_folders.ts→config-folders.ts- etc.
- Update
router.tsxto 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 --noEmitpasseseslintpassesvitest runpasses- 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 failingprojects.test.tsxpattern) - Ensure tests use
MemoryRouterwhere 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.mdwith full naming convention table - Remove dead CSS classes (verified by grep for unused selectors)
- Remove unused exports (check
eslintreport-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.mdexists and is completenpm run check-structurepasses- No file in
src/exceeds 300 lines tsc --noEmitpasseseslintpassesvitest runpassespytestpasses
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:
npm run typecheck(frontend) — zero errorsnpm run lint(frontend) — zero warningspytest(backend) — zero failures- File size check — no file > 300 lines
- For frontend tasks: visual sanity check (build succeeds)
- Commit with conventional format:
refactor: phase N — description
Task Execution Notes
- Use
git mvfor 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
mainimmediately after each task passes quality gates - Pause between phases (after Tasks 1.2, 2.3, 3.5, 4.4) to verify stability