Files
headquarter/openspec/changes/repo-restructure/spec.md
T
Developer ee1fa6bee5 refactor: centralize types and extract seed data (Task 1.1)
- 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)
2026-06-02 18:56:54 +00:00

330 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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: 100200). 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:** 100200 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** `Session` is defined in `api/sessions.ts` and `state/sessions.tsx`
- **WHEN** the refactor is applied
- **THEN** a single `types/session.ts` defines the canonical `Session` interface
- **AND** both `api/sessions.ts` and `state/sessions.tsx` import from it
- **AND** `api/sessions.ts` no longer exports a `Session` type
#### Scenario: API modules lose inline types
- **GIVEN** `api/tool_types.ts` defines `ToolType` inline
- **WHEN** the refactor is applied
- **THEN** `types/tool-type.ts` defines `ToolType`
- **AND** `api/tool_types.ts` imports 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** `FileBrowser` has 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 `.fileBrowser` and child selectors
- **AND** no global class names leak outside the component
#### Scenario: Global styles remain minimal
- **GIVEN** `styles/global.css` exists
- **WHEN** reading it
- **THEN** it contains only: CSS variables, `* { box-sizing }`, `body` reset, 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 /instances` request
- **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}/start` request
- **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 modification
- `services/docker/container.py` — container lifecycle (create, start, stop, remove)
- `services/docker/tunnel.py` — Cloudflare tunnel management
- `services/docker/config.py` — config folder staging and file writing
- **AND** each module is ≤ 300 lines
- **AND** `services/docker.py` does 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.tsx` contains only: data loading, layout, and sub-component composition
- **AND** `components/features/git/file-browser/FileBrowser.tsx` contains the file tree UI
- **AND** `components/features/git/commit-panel/CommitPanel.tsx` contains 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 layout
- `components/features/tool-workshop/ToolTypesTab.tsx`
- `components/features/tool-workshop/ToolConfigsTab.tsx`
- `components/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 `Button` and `Dialog`
- **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-protocol` utilities
- **WHEN** they write the import
- **THEN** they write `import { encodeControlMessage } from "@/utils/terminal-protocol"`
- **AND** `utils/index.ts` does 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 build` or `vite build` duration
- **Bundle size:** No increase in output bundle size
- **Test runtime:** No regression in `npm run test` or `pytest` duration
- **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.*