- 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)
14 KiB
Spec: Repository Restructuring and Modularization
Overview
Restructure the Headquarter monorepo into a modular, maintainable architecture where every source file has a single responsibility and stays within 300 lines (target: 100–200). No behavior changes. No new features. Pure structural reorganization with standardized naming conventions.
Scope: Frontend (apps/web/src/) and backend (apps/api/src/)
Non-goals: Technology swaps, feature additions, database migrations, CI/CD changes
Target file size: 100–200 lines; hard limit 300 lines
Naming Conventions (MUST follow)
| Layer | File Naming | Component/Function Naming | Example |
|---|---|---|---|
| React page components | PascalCase matching exported name | GitHistoryPage |
GitHistoryPage.tsx |
| React feature components | PascalCase matching exported name | FileBrowser |
FileBrowser.tsx + FileBrowser.module.css |
| React UI primitives | PascalCase matching exported name | Button, Dialog |
Button.tsx + Button.module.css |
| React hooks | camelCase | useTheme, useApiQuery |
useTheme.ts |
| Frontend API modules | kebab-case | — | tool-configs.ts |
| Frontend utilities | kebab-case | camelCase functions | terminal-protocol.ts |
| Frontend types | kebab-case | PascalCase interfaces | session.ts |
| CSS modules | kebab-case matching component | — | file-browser.module.css |
| Backend routers | snake_case | snake_case handlers | tool_instances.py |
| Backend services | snake_case | snake_case functions | docker_compose.py |
| Backend models | snake_case | PascalCase classes | tool_instance.py |
| Backend tests | snake_case prefixed with test_ |
— | test_tool_instances.py |
CSS Modules rule: Every React component with significant styling gets its own .module.css file. Global styles live in styles/ and only contain resets, tokens, and layout foundations.
Acceptance Criteria
AC-1: No Monolithic Files Remain
GIVEN the codebase after restructuring
WHEN we count lines in every .ts, .tsx, .py, and .css file under src/
THEN no file exceeds 300 lines
AND the average file size under each domain directory is under 200 lines
Test: Run find src -type f | xargs wc -l | sort -rn — verify top result ≤ 300.
AC-2: Types Are Centralized and Deduplicated
GIVEN a domain type such as Session or ToolInstance
WHEN a developer searches for its definition
THEN exactly one definition exists under types/
AND api/sessions.ts and state/sessions.tsx both import from types/session.ts
AND no API module defines types inline
Test: Search for interface Session — expect 1 result. Search for interface ToolInstance — expect 1 result.
AC-3: Styles Are Modular
GIVEN the frontend build
WHEN styles.css is checked
THEN it does not exist (deleted)
AND global styles live in styles/global.css (resets + tokens + layout)
AND component styles live in .module.css files co-located with components
AND page styles live in styles/pages/{page-name}.css for page-specific layout only
AND syntax highlighting styles live in styles/syntax-highlight.css
Test: test -f src/styles.css fails. find src/styles -name "*.css" | wc -l ≥ 5.
AC-4: Backend Routers Contain Only HTTP Concerns
GIVEN any router file under api/
WHEN reading its contents
THEN it contains only: route definitions, dependency injection, request/response models, and thin handler functions
AND no Docker CLI calls, no Git subprocess calls, no file I/O, no compose file mutation
AND all business logic delegates to services/ modules
Test: grep -n "subprocess\|docker\|compose\|os\." apps/api/src/api/*.py returns zero matches.
AC-5: Backend Services Are Focused
GIVEN the services/ directory
WHEN listing files
THEN each service module has a single responsibility (e.g., container lifecycle, tunnel management, compose generation)
AND services/docker.py does not exist (split into focused modules)
Test: test -f apps/api/src/services/docker.py fails. Each .py in services/ is ≤ 300 lines.
AC-6: Inline Components Are Extracted
GIVEN any page component
WHEN reading its JSX
THEN no inner component definitions exist (no const FileBrowser = () => ... inside a page)
AND all extracted components are importable and testable independently
Test: grep -rn "const [A-Z].*=.*=>" apps/web/src/pages/ returns zero results.
AC-7: Naming Is Consistent
GIVEN any source file
WHEN checking its name against the naming table above
THEN it follows the convention for its layer
AND every exported React component matches its file name (case-insensitive)
Test: Script checks that every .tsx file's default/named export matches its basename.
AC-8: All Quality Gates Pass
GIVEN any phase of the refactor
WHEN running the quality gates
THEN npm run typecheck (frontend) passes with zero errors
AND npm run lint (frontend) passes with zero warnings
AND pytest (backend) passes with zero failures
AND no visual regressions are introduced
Test: Run all gates after each phase. No failures.
AC-9: Barrel Exports for Stable Boundaries
GIVEN components/ui/, components/features/{domain}/, or types/
WHEN importing from those directories
THEN an index.ts barrel export exists
AND consumers import from the directory, not individual files
AND one-off utilities and API modules do NOT have barrel exports
Test: test -f src/components/ui/index.ts passes. test -f src/utils/index.ts fails.
Requirements
REQ-1: Frontend Type System
The system SHALL centralize all shared domain types under apps/web/src/types/.
Rationale: Prevents drift between API types, state types, and component prop types.
Scenario: Centralizing Session types
- GIVEN
Sessionis defined inapi/sessions.tsandstate/sessions.tsx - WHEN the refactor is applied
- THEN a single
types/session.tsdefines the canonicalSessioninterface - AND both
api/sessions.tsandstate/sessions.tsximport from it - AND
api/sessions.tsno longer exports aSessiontype
Scenario: API modules lose inline types
- GIVEN
api/tool_types.tsdefinesToolTypeinline - WHEN the refactor is applied
- THEN
types/tool-type.tsdefinesToolType - AND
api/tool_types.tsimports and re-exports it
REQ-2: Frontend Style Modules
The system SHALL use CSS Modules for component-scoped styles.
Rationale: Eliminates global CSS specificity wars; makes component styles discoverable and deletable.
Scenario: Component with styles
- GIVEN
FileBrowserhas custom styles - WHEN a developer looks for its styles
- THEN they find
components/features/git/file-browser/FileBrowser.module.css - AND the module contains only
.fileBrowserand child selectors - AND no global class names leak outside the component
Scenario: Global styles remain minimal
- GIVEN
styles/global.cssexists - WHEN reading it
- THEN it contains only: CSS variables,
* { box-sizing },bodyreset, and shell layout grid - AND it does not contain component-specific rules (cards, buttons, dialogs, etc.)
REQ-3: Backend Router Separation
The system SHALL separate HTTP routing from business logic.
Rationale: Routers should be thin and testable; business logic should be reusable and independently testable.
Scenario: Creating a tool instance
- GIVEN a
POST /instancesrequest - WHEN the router handles it
- THEN it validates the request body with a Pydantic schema
- AND it calls
services.instances.create_instance(...) - AND it returns the response
- AND it does not call
docker compose up, modify files, or manage tunnels
Scenario: Starting a tool instance
- GIVEN a
POST /instances/{id}/startrequest - WHEN the router handles it
- THEN it calls
services.instances.lifecycle.start_instance(...) - AND it does not contain subprocess calls
REQ-4: Backend Service Focus
The system SHALL split services/docker.py into single-responsibility modules.
Rationale: Docker operations span compose, containers, tunnels, and config staging — too many concerns for one file.
Scenario: Service decomposition
- GIVEN the old
services/docker.py - WHEN the refactor is applied
- THEN the following modules exist:
services/docker/compose.py— compose file generation and modificationservices/docker/container.py— container lifecycle (create, start, stop, remove)services/docker/tunnel.py— Cloudflare tunnel managementservices/docker/config.py— config folder staging and file writing
- AND each module is ≤ 300 lines
- AND
services/docker.pydoes not exist
REQ-5: Page Component Decomposition
The system SHALL split page components into route entry points and feature sub-components.
Rationale: Pages should orchestrate data and routing, not contain inline UI implementations.
Scenario: Repo workspace page
- GIVEN the old
pages/repo-workspace.tsx - WHEN the refactor is applied
- THEN
pages/repo-workspace/page.tsxcontains only: data loading, layout, and sub-component composition - AND
components/features/git/file-browser/FileBrowser.tsxcontains the file tree UI - AND
components/features/git/commit-panel/CommitPanel.tsxcontains the commit form - AND each extracted component is independently importable
Scenario: Tool workshop page
- GIVEN the old
pages/tool-workshop.tsx - WHEN the refactor is applied
- THEN it is split into:
pages/tool-workshop/page.tsx— tab navigation and layoutcomponents/features/tool-workshop/ToolTypesTab.tsxcomponents/features/tool-workshop/ToolConfigsTab.tsxcomponents/features/tool-workshop/ConfigFoldersTab.tsx
- AND each tab component manages its own form state
REQ-6: Inline Component Extraction
The system SHALL not contain inner component definitions.
Rationale: Inner components cannot be tested independently, cause re-creation on every render, and hide complexity.
Scenario: No inner components in pages
- GIVEN any file under
pages/ - WHEN searching for
const [A-Z]followed by a component body - THEN zero matches are found
- AND all previously inner components are moved to
components/
REQ-7: Reusable Loading/Error Patterns
The system SHALL extract repeated loading/error/retry UI into shared components.
Rationale: ~8 pages copy the same status: "loading" | "ready" | "error" pattern with identical retry buttons.
Scenario: Loading state
- GIVEN a page is loading data
- WHEN the UI renders
- THEN it uses
<LoadingState message="Loading sessions..." />instead of inline JSX
Scenario: Error state
- GIVEN a page fails to load data
- WHEN the UI renders
- THEN it uses
<ErrorState message="Failed to load" onRetry={loadData} />instead of inline JSX
REQ-8: Barrel Exports at Stable Boundaries
The system SHALL provide index.ts barrel exports for stable module boundaries.
Rationale: Cleaner imports; encapsulates internal file structure.
Scenario: Importing UI primitives
- GIVEN a developer needs
ButtonandDialog - WHEN they write the import
- THEN they write
import { Button, Dialog } from "@/components/ui" - AND not
import { Button } from "@/components/ui/button/button"
Scenario: No barrel for utilities
- GIVEN a developer needs
terminal-protocolutilities - WHEN they write the import
- THEN they write
import { encodeControlMessage } from "@/utils/terminal-protocol" - AND
utils/index.tsdoes not exist
API / Protocol Changes
None. This is a pure reorganization refactor. All HTTP endpoints, WebSocket protocols, and database schemas remain unchanged.
Dependencies
No new dependencies required. Existing toolchain:
- Frontend: React 18, Vite, TypeScript, ESLint, Vitest
- Backend: FastAPI, SQLAlchemy, Alembic, pytest
Optional consideration: If CSS Modules are adopted (per proposal), Vite has built-in support — no new dependency needed.
Non-Functional Requirements
- Build time: No regression in
npm run buildorvite buildduration - Bundle size: No increase in output bundle size
- Test runtime: No regression in
npm run testorpytestduration - Developer experience: File discovery time (time to locate a component/service) must decrease
Migration Order
| Phase | Concern | Files Touched | Est. Lines |
|---|---|---|---|
| 1 | Types, seeds, shared deps | types/, main.py, repo-workspace.tsx |
~350 |
| 2 | Style system | styles.css → styles/ + .module.css |
~2,750 |
| 3a | Backend shared deps | auth/dependencies.py, router helpers |
~400 |
| 3b | tool_instances split |
api/tool_instances.py → router + services |
~800 |
| 3c | git_repositories + config_profiles split |
Routers + services | ~700 |
| 4a | tool-workshop split |
Page + feature components | ~500 |
| 4b | Pages split | sessions.tsx, dashboard.tsx, repo-workspace.tsx |
~600 |
| 4c | Naming consistency | Rename files/components | ~350 |
| 5 | Tests + docs | Backfill tests, naming doc | ~350 |
Open Questions (Resolved)
| # | Question | Resolution |
|---|---|---|
| 1 | CSS approach | CSS Modules — each component gets its own .module.css |
| 2 | Backend API versioning | Keep flat api/ — version when v2 is actually needed |
| 3 | Feature components location | components/features/{domain}/ |
| 4 | Barrel exports | Yes for stable boundaries (components/ui/, components/features/{domain}/, types/); no for one-off utilities and API modules |
| 5 | Branching strategy | Merge each phase to main immediately |
Spec prepared for SDD design phase. Next: technical design with exact file layout and import patterns.