diff --git a/openspec/changes/reorganize-long-files/.openspec.yaml b/openspec/changes/reorganize-long-files/.openspec.yaml new file mode 100644 index 0000000..f617bd1 --- /dev/null +++ b/openspec/changes/reorganize-long-files/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-06-04 diff --git a/openspec/changes/reorganize-long-files/proposal.md b/openspec/changes/reorganize-long-files/proposal.md new file mode 100644 index 0000000..0ec7731 --- /dev/null +++ b/openspec/changes/reorganize-long-files/proposal.md @@ -0,0 +1,73 @@ +## Why + +After the backend-frontend refactoring (commits `0591b00` through `8c7affc`), the codebase gained proper directory structure but several files grew into monoliths. The `main` branch (pre-refactor baseline at `5ed5e1c`) kept pages thin by delegating to extracted components. On `dev`, new features were added inline, causing pages and routers to absorb responsibilities that belong in components or services. + +### Problem Files (Frontend) + +| File | Lines | Problem | +|------|-------|---------| +| `pages/ToolWorkshopPage.tsx` | **1,269** | Merged 3 tab components inline (ToolTypes, ToolConfigs, ConfigFolders) | +| `pages/ConfigProfilesPage.tsx` | **1,611** | List, detail, edit, create, and mobile views all in one file | +| `pages/TerminalPage.tsx` | **571** | Session tabs, keyboard shortcuts, fullscreen, mobile overlay, special keys all inline | +| `pages/RepoWorkspacePage.tsx` | **505** | File editor, git toolbar, workspace header, sidebar logic inline | +| `pages/SettingsPage.tsx` | **284** | Settings nav + multiple setting sections inline | +| `pages/SshKeysPage.tsx` | **277** | List and create inline | + +### Problem Files (Backend) + +| File | Lines | Problem | +|------|-------|---------| +| `api/tool/tool_instances.py` | **2,900** | CRUD, Docker lifecycle, WebSocket proxy, terminal sessions, instance proxy all in one router | +| `api/project/git_repositories.py` | **1,588** | HTTP endpoints mixed with git command orchestration | +| `api/config/config_profiles.py` | **842** | CRUD + validation + resolver + mount/include management | + +### What `main` Did Differently + +`main` at `5ed5e1c`: +- `ToolWorkshopPage.tsx` = **77 lines** (just a tab switcher, tabs imported from `features/tool-workshop/`) +- `TerminalPage.tsx` = **38 lines** (just a wrapper around `TerminalComponent`) +- `api/tool_instances.py` = **284 lines** (HTTP endpoints only) +- `api/terminal.py` = **158 lines** (separate WebSocket router) + +## What Changes + +Restore the **thin-page / thin-router / fat-component** pattern from `main`, adapted to current `dev` features: + +1. **Frontend page extraction** — Split monolithic pages into: + - Page shell (orchestrator, 50-150 lines) + - Tab components (for tabbed pages) + - List / Detail / Edit / Create components (for CRUD pages) + - Mobile-specific views (extracted, not inline) + +2. **Backend router slimming** — Split `tool_instances.py` into: + - `tool_instances.py` — CRUD endpoints only + - `tool_lifecycle.py` — Start/stop/restart/delete logic + - Move terminal WebSocket back to dedicated `terminal.py` + +3. **Git repository router** — Extract git command orchestration into `services/git/` + +## Capabilities + +### New Capabilities +- None (pure structural refactor) + +### Modified Capabilities +- `frontend-structure`: Pages become orchestrators; components carry the UI logic +- `backend-structure`: Routers become HTTP-only; services carry business logic + +## Impact + +- **Frontend**: New `features/tool-workshop/` tab components, new `features/config-profiles/` components, `features/terminal/` session manager, etc. +- **Backend**: New `api/tool/tool_lifecycle.py`, `api/tool/terminal.py`, slimmer `api/tool/tool_instances.py` +- **Tests**: Test files may need import path updates (component moved → test follows) + +## Exclusions (Already Done / Out of Scope) + +- Directory structure already exists (`features/`, `services/`, etc.) +- Schema extraction already done (`schemas/` subpackages) +- Model subpackages already done (`models/` subpackages) +- API router subpackages already done (`api/tool/`, `api/project/`, etc.) +- File naming already done (kebab-case APIs, PascalCase pages) +- No behavioral changes to any endpoint or UI flow +- No database schema changes +- No new features diff --git a/openspec/changes/reorganize-long-files/spec.md b/openspec/changes/reorganize-long-files/spec.md new file mode 100644 index 0000000..813b312 --- /dev/null +++ b/openspec/changes/reorganize-long-files/spec.md @@ -0,0 +1,94 @@ +## Scope + +This change is a **pure structural refactoring** to split monolithic pages and routers into focused components and services. No API contracts, database schemas, or user-facing behaviors change. + +### In Scope + +#### 1. Frontend Page Extraction + +Split the following pages into a thin page shell + extracted components: + +**`pages/ToolWorkshopPage.tsx` (1,269 → ~80 lines)** +- Extract `ToolTypesTab` → `components/features/tool-workshop/ToolTypesTab.tsx` +- Extract `ToolConfigsTab` → `components/features/tool-workshop/ToolConfigsTab.tsx` +- Extract `ConfigFoldersTab` → `components/features/tool-workshop/ConfigFoldersTab.tsx` +- Page becomes: tab switcher only, imports the 3 tabs + +**`pages/ConfigProfilesPage.tsx` (1,611 → ~80 lines)** +- Extract `ConfigProfileListView` → list view + mobile list view +- Extract `ConfigProfileDetailView` → detail view with edit toggle +- Extract `ConfigProfileEditForm` → edit/create form +- Extract `ConfigProfileMobileView` → mobile view state machine wrapper +- Page becomes: router between list/detail/edit views + +**`pages/TerminalPage.tsx` (571 → ~80 lines)** +- Extract `TerminalSessionManager` → session tabs + auto-create logic +- Extract `TerminalKeyboardShortcuts` → shortcut handler hook (already exists, just use it) +- Extract `MobileTerminalOverlay` → mobile overlay toolbar + tabs +- Page becomes: choose between desktop (`TerminalComponent` + `TerminalSessionTabs`) and mobile (`MobileTerminalOverlay` + `TerminalComponent`) wrappers + +**`pages/SettingsPage.tsx` (284 → ~80 lines)** +- Extract `SettingsNavigation` → settings nav sidebar +- Extract `GeneralSettingsTab`, `SSHKeysTab` (already separate pages, but move sections into components if inline) +- Page becomes: nav + `` for nested routes + +**`pages/SshKeysPage.tsx` (277 → ~80 lines)** +- Extract `SSHKeyList` → list with actions +- Extract `SSHKeyCreateForm` → create form +- Page becomes: layout wrapper + conditionally render list or form + +**`pages/RepoWorkspacePage.tsx` (505 → ~150 lines)** +- Extract `WorkspaceLayout` → sidebar + main content layout +- Page becomes: data loader + layout wrapper + +**`pages/ProjectsPage.tsx` (433 → ~100 lines)** +- Extract `ProjectList` → list with cards +- Extract `ProjectCreateDialog` → create form in dialog +- Extract `ProjectEditDialog` → edit form in dialog +- Page becomes: data loader + layout + dialog state manager + +#### 2. Backend Router Slimming + +**`api/tool/tool_instances.py` (2,900 → ~300 lines)** +- Extract terminal WebSocket handlers → `api/tool/terminal.py` (~400 lines) +- Extract instance lifecycle (create/start/stop/delete/restart) → `api/tool/tool_lifecycle.py` (~600 lines) +- Keep in `tool_instances.py`: CRUD endpoints (GET list, GET detail, POST, PATCH, DELETE) + instance proxy endpoint + +**`api/project/git_repositories.py` (1,588 → ~300 lines)** +- Extract git command orchestration into `services/git/operations.py` +- Router keeps: auth, parameter validation, response building, error handling +- Service functions: `clone_repo`, `fetch_repo`, `pull_repo`, `push_repo`, `merge_repo`, etc. + +**`api/config/config_profiles.py` (842 → ~200 lines)** +- Extract resolver orchestration into `services/config/resolver_service.py` +- Extract CRUD helpers into `services/config/crud_service.py` +- Router keeps: endpoint definitions, auth, input validation + +### Out of Scope + +- Any new features or behavioral changes +- Database schema changes (no migrations) +- API contract changes (same endpoints, same request/response shapes) +- Frontend UI behavior changes (same components, same interactions) +- Moving existing `features/` components (already organized) +- Renaming files (naming already done) +- CSS changes (styles already work) + +## Acceptance Criteria + +1. All pages ≤ 150 lines (except `RepoWorkspacePage` which may stay at ~150) +2. All API routers ≤ 400 lines +3. All existing tests pass without modification (behavior unchanged) +4. All existing API endpoints return identical responses +5. Frontend `npm run typecheck` passes +6. Frontend `npm run build` passes +7. Backend `py_compile` passes on all files +8. No import errors in browser console +9. File count increases (more files, smaller files) + +## Preconditions + +- `dev` branch is stable (all fixes from this session are committed) +- Backend compiles (`py_compile` pass) +- Frontend typechecks and builds (`tsc`, `vite build` pass) +- Current tests pass (or known failures are documented) diff --git a/openspec/changes/reorganize-long-files/tasks.md b/openspec/changes/reorganize-long-files/tasks.md new file mode 100644 index 0000000..1be5bde --- /dev/null +++ b/openspec/changes/reorganize-long-files/tasks.md @@ -0,0 +1,97 @@ +## Phase 0: Preparation + +- [ ] 0.1 Verify `dev` builds cleanly (backend `py_compile`, frontend `tsc` + `vite build`) +- [ ] 0.2 Document current file sizes for before/after comparison +- [ ] 0.3 Create component directory stubs if missing: + - `apps/web/src/components/features/tool-workshop/` + - `apps/web/src/components/features/config-profiles/` + - `apps/web/src/components/features/terminal/` + - `apps/web/src/components/features/settings/` + - `apps/web/src/components/features/ssh-keys/` + - `apps/api/src/services/git/operations.py` (extract from router) + - `apps/api/src/services/config/crud_service.py` + - `apps/api/src/services/config/resolver_service.py` + +## Phase 1: Backend — Router Slimming + +### 1.1 Terminal WebSocket Extraction +- [ ] 1.1.1 Create `api/tool/terminal.py` from terminal WebSocket handlers in `api/tool/tool_instances.py` +- [ ] 1.1.2 Move `_handle_terminal_websocket`, `_get_user_from_websocket`, `SessionRef` class +- [ ] 1.1.3 Update `main.py` to include `terminal_router` from `api.tool.terminal` +- [ ] 1.1.4 Remove terminal routes from `api/tool/tool_instances.py` +- [ ] 1.1.5 Verify `py_compile` passes + +### 1.2 Instance Lifecycle Extraction +- [ ] 1.2.1 Create `api/tool/tool_lifecycle.py` for start/stop/restart/delete endpoints +- [ ] 1.2.2 Extract lifecycle endpoints from `api/tool/tool_instances.py` +- [ ] 1.2.3 Update `main.py` to include lifecycle router +- [ ] 1.2.4 Verify `py_compile` passes + +### 1.3 Git Repository Router +- [ ] 1.3.1 Create `services/git/operations.py` for git command orchestration +- [ ] 1.3.2 Extract `clone_repo`, `fetch_repo`, `pull_repo`, `push_repo`, `merge_repo`, `commit_repo` helpers +- [ ] 1.3.3 Update `api/project/git_repositories.py` to call service functions +- [ ] 1.3.4 Verify `py_compile` passes + +### 1.4 Config Profile Router +- [ ] 1.4.1 Extract CRUD helpers into `services/config/crud_service.py` +- [ ] 1.4.2 Extract resolver helpers into `services/config/resolver_service.py` +- [ ] 1.4.3 Update `api/config/config_profiles.py` to call services +- [ ] 1.4.4 Verify `py_compile` passes + +## Phase 2: Frontend — Tool Workshop Page + +- [ ] 2.1 Extract `ToolTypesTab` from `pages/ToolWorkshopPage.tsx` into `components/features/tool-workshop/ToolTypesTab.tsx` +- [ ] 2.2 Extract `ToolConfigsTab` into `components/features/tool-workshop/ToolConfigsTab.tsx` +- [ ] 2.3 Extract `ConfigFoldersTab` into `components/features/tool-workshop/ConfigFoldersTab.tsx` +- [ ] 2.4 Slim `pages/ToolWorkshopPage.tsx` to ~80 lines (tab switcher only) +- [ ] 2.5 Update imports in all consumers +- [ ] 2.6 Verify `tsc --noEmit` and `npm run build` pass + +## Phase 3: Frontend — Config Profiles Page + +- [ ] 3.1 Extract `ConfigProfileListView` into `components/features/config-profiles/ConfigProfileListView.tsx` +- [ ] 3.2 Extract `ConfigProfileDetailView` into `components/features/config-profiles/ConfigProfileDetailView.tsx` +- [ ] 3.3 Extract `ConfigProfileEditForm` into `components/features/config-profiles/ConfigProfileEditForm.tsx` +- [ ] 3.4 Extract `ConfigProfileMobileView` into `components/features/config-profiles/ConfigProfileMobileView.tsx` +- [ ] 3.5 Slim `pages/ConfigProfilesPage.tsx` to ~80 lines +- [ ] 3.6 Update imports +- [ ] 3.7 Verify `tsc --noEmit` and `npm run build` pass + +## Phase 4: Frontend — Terminal Page + +- [ ] 4.1 Extract `TerminalSessionManager` (tabs + auto-create) into `components/features/terminal/TerminalSessionManager.tsx` +- [ ] 4.2 Extract `MobileTerminalOverlay` into `components/features/terminal/MobileTerminalOverlay.tsx` +- [ ] 4.3 Extract fullscreen keyboard shortcut handler into `hooks/use-terminal-shortcuts.ts` +- [ ] 4.4 Slim `pages/TerminalPage.tsx` to ~80 lines +- [ ] 4.5 Update imports +- [ ] 4.6 Verify `tsc --noEmit` and `npm run build` pass + +## Phase 5: Frontend — Settings & SSH Keys Pages + +- [ ] 5.1 Extract `SettingsNavigation` into `components/features/settings/SettingsNavigation.tsx` +- [ ] 5.2 Slim `pages/SettingsPage.tsx` to ~80 lines +- [ ] 5.3 Extract `SSHKeyList` into `components/features/ssh-keys/SSHKeyList.tsx` +- [ ] 5.4 Extract `SSHKeyCreateForm` into `components/features/ssh-keys/SSHKeyCreateForm.tsx` +- [ ] 5.5 Slim `pages/SshKeysPage.tsx` to ~80 lines +- [ ] 5.6 Verify `tsc --noEmit` and `npm run build` pass + +## Phase 6: Frontend — Projects & Repo Workspace Pages + +- [ ] 6.1 Extract `ProjectList` into `components/features/project/ProjectList.tsx` +- [ ] 6.2 Extract `ProjectCreateDialog` into `components/features/project/ProjectCreateDialog.tsx` +- [ ] 6.3 Extract `ProjectEditDialog` into `components/features/project/ProjectEditDialog.tsx` +- [ ] 6.4 Slim `pages/ProjectsPage.tsx` to ~100 lines +- [ ] 6.5 Extract `WorkspaceLayout` into `components/features/workspace/WorkspaceLayout.tsx` +- [ ] 6.6 Slim `pages/RepoWorkspacePage.tsx` to ~150 lines +- [ ] 6.7 Verify `tsc --noEmit` and `npm run build` pass + +## Phase 7: Integration and Verification + +- [ ] 7.1 Run backend `py_compile` on all files +- [ ] 7.2 Run frontend `npm run typecheck` +- [ ] 7.3 Run frontend `npm run build` +- [ ] 7.4 Run frontend tests: `npm test` +- [ ] 7.5 Verify file size targets met (pages ≤ 150, routers ≤ 400) +- [ ] 7.6 Verify no 404s or import errors in browser console +- [ ] 7.7 Manual smoke test: create project, start terminal, open config profiles