# Module Layout **Status**: derived-from-code **Language**: typescript (React 19 + Vite + Tailwind) **Layout Convention**: custom (flat-features under `src/`; no per-component barrels) **Root**: `src/` **Last Updated**: 2026-05-10 > Authoritative file-ownership map for the React UI workspace. Derived from > `_docs/02_document/00_discovery.md` (dependency graph) and the Step 2 > component specs at `_docs/02_document/components/`. Consumed by > `/implement` Step 4 (file ownership), `/code-review` Phase 7 > (architecture violations), and `/refactor` discovery. ## Layout Rules 1. Each component owns ONE OR MORE top-level directories (or top-level files) under `src/`. The mapping is NOT 1:1 — `00_foundation` owns three sibling directories (`src/types/`, `src/hooks/`, `src/i18n/`), `05_flights` spans `src/features/flights/` AND a separate `mission-planner/` port-source root, and `10_app-shell` owns top-level files (`App.tsx`, `main.tsx`, `index.css`, `vite-env.d.ts`). 2. Shared code does **not** live under `src/shared/` today — there is no `shared/` directory. Two helper modules (`11_class-colors/classColors.ts` and `06_annotations/CanvasEditor.tsx`) are physically misplaced and consumed across components; both are flagged in the `## Verification Needed` block. A `src/shared/` directory is a Step 4 testability candidate. 3. Public API per component: NO barrel `index.ts` exists at any component root. The only `index.ts` files are `src/types/index.ts` (a re-export hub for type aliases — used as the de-facto public API for `00_foundation` types) and `mission-planner/src/types/index.ts`. Until Step 4 introduces barrels, Public API is approximated as "every named export from any file under the component's owned directories". Cross-component imports ARE happening at file-name granularity (`import { api } from '../api/client'`, `import { CanvasEditor } from '../annotations/CanvasEditor'`). 4. Cross-cutting concerns (logging, config, error handling, telemetry): no dedicated infrastructure today. `console.error` / silent catches are the closest thing — recorded in module findings. 5. Tests: there are **zero tests** under `src/`. The only test file is `mission-planner/src/test/jsonImport.test.ts`, which can't run because Jest isn't installed (00_discovery.md §11.5). Test layout is therefore TBD; suggest `src//__tests__/` per the standard React convention when tests are added (autodev Step 5–6). ## Per-Component Mapping ### Component: `00_foundation` - **Epic**: TBD (set during autodev Step 4 / Decompose) - **Directories**: `src/types/`, `src/hooks/`, `src/i18n/` - **Public API** (de-facto, no barrel): - `src/types/index.ts` — every exported type alias (`Detection`, `Flight`, `MediaItem`, `User`, etc.) - `src/hooks/useDebounce.ts` — `useDebounce` - `src/hooks/useResizablePanel.ts` — `useResizablePanel` - `src/i18n/i18n.ts` — default export (i18n instance) - **Internal**: `src/i18n/en.json`, `src/i18n/ua.json` (data; consumed only by `i18n.ts`) - **Owns** (exclusive write): `src/types/**`, `src/hooks/**`, `src/i18n/**` - **Imports from**: (none — Layer 0) - **Consumed by**: every other component ### Component: `11_class-colors` - **Epic**: TBD - **Directories**: (none today — physical file lives at `src/features/annotations/classColors.ts`, which is owned by `06_annotations` on disk). Logical owner is this component; physical move to `src/shared/classColors.ts` (or `src/components/detection/classColors.ts`) is a Step 4 testability task. - **Public API**: `src/features/annotations/classColors.ts` exports `getClassColor`, `getClassNameFallback`, `getPhotoModeSuffix`, `FALLBACK_CLASS_NAMES`. - **Internal**: module-private `CLASS_COLORS` constant. - **Owns**: pending — see Verification Needed item #1. - **Imports from**: (none — Layer 0/1, no internal imports) - **Consumed by**: `03_shared-ui` (DetectionClasses), `06_annotations` (CanvasEditor, AnnotationsPage, AnnotationsSidebar) ### Component: `01_api-transport` - **Epic**: TBD - **Directory**: `src/api/` - **Public API** (de-facto): `src/api/client.ts` exports `api` (fetch wrapper); `src/api/sse.ts` exports `subscribeSSE` / equivalent helper. - **Internal**: none (both files are externally consumed) - **Owns**: `src/api/**` - **Imports from**: `00_foundation` (types) - **Consumed by**: `02_auth`, `03_shared-ui`, every feature page (04, 05, 06, 07, 08, 09) ### Component: `02_auth` - **Epic**: TBD - **Directory**: `src/auth/` - **Public API**: `src/auth/AuthContext.tsx` exports `AuthProvider`, `useAuth`. `src/auth/ProtectedRoute.tsx` exports `ProtectedRoute`. - **Internal**: none - **Owns**: `src/auth/**` - **Imports from**: `00_foundation`, `01_api-transport` - **Consumed by**: `03_shared-ui` (Header reads `useAuth`), `04_login`, `10_app-shell` (mounts `AuthProvider` + `ProtectedRoute`) ### Component: `03_shared-ui` - **Epic**: TBD - **Directory**: `src/components/` - **Public API** (de-facto, all are externally consumed): - `Header.tsx` → `Header` - `HelpModal.tsx` → `HelpModal` - `ConfirmDialog.tsx` → `ConfirmDialog` - `DetectionClasses.tsx` → `DetectionClasses` - `FlightContext.tsx` → `FlightProvider`, `useFlight` - **Internal**: none — every file in `src/components/` is consumed externally today - **Owns**: `src/components/**` - **Imports from**: `00_foundation`, `11_class-colors` (physical: `../features/annotations/classColors`), `01_api-transport`, `02_auth` - **Consumed by**: `10_app-shell` (mounts `Header` + `FlightProvider`), every feature page (consumes `useFlight`, `ConfirmDialog`, `DetectionClasses`) ### Component: `04_login` - **Epic**: TBD - **Directory**: `src/features/login/` - **Public API**: `LoginPage.tsx` → `LoginPage` - **Internal**: none (single-page component) - **Owns**: `src/features/login/**` - **Imports from**: `00_foundation`, `01_api-transport`, `02_auth` - **Consumed by**: `10_app-shell` (route) ### Component: `05_flights` - **Epic**: TBD (this is the merged Flights & Mission Planning component) - **Directories** (TWO physical roots): - `src/features/flights/` — deployed target tree (15 modules) - `mission-planner/` — port-source, NOT deployed (37 modules under `mission-planner/src/`). Documented inside this component per the user's Step 2 BLOCKING-gate decision (`_docs/02_document/state.json::component_05_flights_merge_2026-05-10`). The port direction is `mission-planner/` → `src/features/flights/`; module-layout treats both trees as owned by this component but only the target tree is in the layering table below. - **Public API** (target tree, de-facto): `FlightsPage.tsx` → `FlightsPage` (route component). Internal sub-components (`FlightMap`, `FlightParamsPanel`, `FlightListSidebar`, `WaypointList`, `AltitudeChart`, `AltitudeDialog`, `WindEffect`, `MiniMap`, `MapPoint`, `DrawControl`, `JsonEditorDialog`, `mapIcons`, `flightPlanUtils`, `types`) are NOT consumed outside the component. - **Public API** (port-source `mission-planner/`): not consumed at all by `src/` today (separate Vite entrypoint, `main.tsx` of its own). Effectively a private vendored sibling. - **Internal** (target tree): every file under `src/features/flights/` except `FlightsPage.tsx` - **Internal** (port-source): every file under `mission-planner/` - **Owns**: `src/features/flights/**`, `mission-planner/**` - **Imports from** (target tree): `00_foundation`, `01_api-transport`, `02_auth` (via `ProtectedRoute` from shell), `03_shared-ui` (uses `ConfirmDialog`, `useFlight`) - **Consumed by**: `10_app-shell` (route) ### Component: `06_annotations` - **Epic**: TBD - **Directory**: `src/features/annotations/` - **Public API** (de-facto): - `AnnotationsPage.tsx` → `AnnotationsPage` (route component) - `CanvasEditor.tsx` → `CanvasEditor` — **also imported by `07_dataset`** (cross-feature edge, see Verification Needed #3) - **Internal**: `MediaList.tsx`, `VideoPlayer.tsx`, `AnnotationsSidebar.tsx` - **Owns**: `src/features/annotations/**` EXCEPT `classColors.ts` (logically owned by `11_class-colors`; physical home pending refactor) - **Imports from**: `00_foundation`, `11_class-colors`, `01_api-transport`, `03_shared-ui` - **Consumed by**: `10_app-shell` (route); `07_dataset` (imports `CanvasEditor` directly — see Verification Needed) ### Component: `07_dataset` - **Epic**: TBD - **Directory**: `src/features/dataset/` - **Public API**: `DatasetPage.tsx` → `DatasetPage` - **Internal**: none (single-page) - **Owns**: `src/features/dataset/**` - **Imports from**: `00_foundation`, `11_class-colors` (only when class-distribution chart is added — not in code yet), `01_api-transport`, `03_shared-ui`, **`06_annotations` (CanvasEditor cross-feature edge)** - **Consumed by**: `10_app-shell` (route) ### Component: `08_admin` - **Epic**: TBD - **Directory**: `src/features/admin/` - **Public API**: `AdminPage.tsx` → `AdminPage` - **Internal**: none (single-page) - **Owns**: `src/features/admin/**` - **Imports from**: `00_foundation`, `01_api-transport`, `03_shared-ui` - **Consumed by**: `10_app-shell` (route) ### Component: `09_settings` - **Epic**: TBD - **Directory**: `src/features/settings/` - **Public API**: `SettingsPage.tsx` → `SettingsPage` - **Internal**: none (single-page) - **Owns**: `src/features/settings/**` - **Imports from**: `00_foundation`, `01_api-transport`, `03_shared-ui` - **Consumed by**: `10_app-shell` (route) ### Component: `10_app-shell` - **Epic**: TBD - **Files** (no dedicated directory): `src/App.tsx`, `src/main.tsx`, `src/index.css`, `src/vite-env.d.ts` - **Public API**: `main.tsx` is the Vite entrypoint (no symbols are externally imported). `App.tsx` exports `App`. - **Internal**: `index.css` (global Tailwind base + `az-*` design-token CSS variables), `vite-env.d.ts` (type shim) - **Owns**: `src/App.tsx`, `src/main.tsx`, `src/index.css`, `src/vite-env.d.ts` - **Imports from**: every other component (it is the composition root) - **Consumed by**: (none — top of the graph; bundled by Vite) ### Component: `Blackbox Tests` (cross-cutting) - **Epic**: AZ-455 - **Directories**: `tests/` (fast-profile shared helpers, MSW, fixtures, setup), `e2e/` (Playwright config, suite-e2e docker-compose, stubs, runner, e2e specs, e2e fixtures), plus colocated `*.test.{ts,tsx}` and `*.spec.{ts,tsx}` files under any production component directory. - **Public API**: none (tests are not consumed by production code). - **Internal**: every file under owned paths. - **Owns** (exclusive write): - `tests/**` - `e2e/**` - `**/*.test.{ts,tsx}` and `**/*.spec.{ts,tsx}` — colocated test files (Vitest convention) override every production component's `Owns` glob for that filename pattern only. - `vitest.config.ts`, `tsconfig.test.json` - `scripts/run-tests.sh`, `scripts/run-performance-tests.sh` (extension only — the files were created in autodev Step 4 as Step 6 placeholders). - `package.json` test-scoped sections only: `scripts.test*`, `scripts.lint:tests`, `devDependencies` for runners (`vitest`, `@vitest/*`, `@playwright/test`, `msw`, `@testing-library/*`), and any test-only `peerDependencies` overrides. - ESLint test-override blocks (`overrides` entries scoped to `tests/**`, `e2e/**`, `**/*.test.{ts,tsx}`). - **Imports from**: `00_foundation` only (and only `src/types/index.ts` — typed wire-contract enums per `_docs/02_document/tests/environment.md` § Black-box discipline / `P9`). NEVER any other production component's internal files. The static profile enforces this via ripgrep. - **Consumed by**: (none — tests are not part of production runtime). - **Notes**: - Every test task spec under epic AZ-455 carries `**Component**: Blackbox Tests` and resolves its file ownership through this entry. - Colocated test files are OWNED by the test task that creates them, even when the parent directory belongs to a production component. The production files in that same directory remain READ-ONLY for the test task (compile-time imports of the production module under test are permitted; modifications are not). - Test-related `package.json` edits (devDependencies, test scripts) are OWNED here. Production `dependencies` and non-test `scripts` are FORBIDDEN — those remain owned by the production component whose runtime they affect (typically `10_app-shell`). - `mission-planner/` test files (e.g., `mission-planner/src/test/jsonImport.test.ts`) are OWNED here for the same reason; the `mission-planner/**` production glob remains owned by `05_flights`. ## Shared / Cross-Cutting > No `src/shared/` directory exists today. Two cross-cutting concerns are tracked here as **proposed** shared modules; they require a physical file move scheduled for Step 4 (testability) or Step 8 (refactor). ### shared/class-colors (proposed; current physical location: `src/features/annotations/classColors.ts`) - **Owner component**: `11_class-colors` - **Purpose**: Detection-class fallback color, fallback name, PhotoMode suffix. - **Owned by**: pending move task — current physical file is under `06_annotations`'s owns-glob, which makes it ambiguous. Workaround: until moved, treat `classColors.ts` as `OWNED` by tasks targeting `11_class-colors` and `READ-ONLY` to all other tasks (including those targeting `06_annotations`). - **Consumed by**: `03_shared-ui/DetectionClasses`, `06_annotations` (CanvasEditor, AnnotationsPage, AnnotationsSidebar) ### shared/canvas-editor (proposed; current physical location: `src/features/annotations/CanvasEditor.tsx`) - **Owner component**: still `06_annotations` for now (it's the dominant consumer) - **Purpose**: Bounding-box draw / move / resize layer; reused by Dataset's inline editor. - **Status**: cross-feature edge (07_dataset imports it). The proper home is a future `src/components/canvas/` directory. Decision deferred to Step 2 architecture baseline scan; the implement skill should treat this as a `READ-ONLY` for `07_dataset` tasks. ## Allowed Dependencies (layering) Read top-to-bottom; an upper layer may import from a lower layer but NEVER the reverse. Same-layer imports are permitted only for explicit cross-feature edges listed in the table footnotes. | Layer | Components | May import from | |-------|------------|-----------------| | 4. App Shell / Entry | `10_app-shell` | 0, 1, 2, 3 (and any feature in Layer 3) | | 3. Application / Features | `04_login`, `05_flights`, `06_annotations`, `07_dataset`, `08_admin`, `09_settings` | 0, 1, 2, 3 | | 2. Composition | `02_auth`, `03_shared-ui` | 0, 1 | | 1. Transport | `01_api-transport` | 0 | | 0. Foundation / Shared kernel | `00_foundation`, `11_class-colors` | (none) | `07_dataset` imports `06_annotations/CanvasEditor.tsx` — same-layer cross-feature edge. Permitted today; flagged as a refactor target. The implement skill grants `07_dataset` tasks **READ-ONLY** access to that one file specifically. Violations of this table are **Architecture** findings in code-review Phase 7 and are High severity. The `Blackbox Tests` cross-cutting component sits **outside** this table. It imports from `00_foundation` only (specifically `src/types/index.ts` for typed wire-contract enums) and is consumed by no production component. The static-profile ripgrep checks enforce that no test imports from any other production component's internal files. ## Verification Needed The following inferences could not be made cleanly from code alone. They are surfaced for the user to confirm or override at the Step 2.5 BLOCKING gate. 1. **Physical home of `11_class-colors`**. The component is logically Layer 0/1 shared kernel, but its physical file lives inside `06_annotations`'s owns-glob (`src/features/annotations/classColors.ts`). Until the file is moved (proposed: `src/shared/classColors.ts`), the implement skill must apply the special-case rule documented under `shared/class-colors` above (READ-ONLY for `06_annotations` tasks even though the file is inside that component's directory). **Decision needed**: schedule the file move at Step 4 / Step 8, or accept the special-case rule indefinitely? 2. **Physical home of `CanvasEditor.tsx`**. Same shape: it lives under `06_annotations` and is consumed cross-feature by `07_dataset`. Proposed: `src/components/canvas/CanvasEditor.tsx` (or a new `06b_canvas` component). **Decision needed**: keep the same-layer cross-feature edge, or schedule the lift? 3. **No barrel exports anywhere**. The codebase imports cross-component at file-name granularity (`import { api } from '../api/client'`). This means every internal file is *de-facto* Public API. Recommendation: Step 4 testability task to add `src//index.ts` barrels per component, locking the public surface. **Decision needed**: add barrels now or stay file-import? 4. **`mission-planner/` is owned by `05_flights` but lives at the repo root** (not under `src/`). Layout rule #1 says one component owns one or more top-level directories — this satisfies the rule (it owns two: `src/features/flights/` AND `mission-planner/`). Implement-skill consumers must include `mission-planner/**` in `05_flights`'s OWNED glob. **Decision needed**: confirm the implement skill should treat `mission-planner/**` as OWNED by 05_flights (otherwise it's FORBIDDEN by default). 5. **`05_flights` cycle inside the port-source**. `mission-planner/src/flightPlanning/MapView.tsx ↔ MiniMap.tsx` form a circular import (named-handle, see `00_discovery.md` §7 footnote). They were analyzed together in batch MP-B6. The cycle is internal to the component and does not cross component boundaries; flagged here for completeness. 6. **`00_foundation` owns three sibling directories** (`types/`, `hooks/`, `i18n/`). Layout rule #1 permits this. Future option: split into `00a_types`, `00b_hooks`, `00c_i18n` if the directories grow. **Decision needed**: keep the multi-directory component, or split now? 7. **`10_app-shell` owns top-level files instead of a directory** (`src/App.tsx`, `src/main.tsx`, etc.). Layout rule #1 permits this. The implement-skill OWNED glob for app-shell tasks must therefore be the explicit file list, not a directory glob. 8. **Test layout is undefined** (no tests exist). When Steps 5–6 of autodev produce tests, recommended layout is `src//__tests__/` per React convention; for `05_flights` cross-tree tests, prefer `src/features/flights/__tests__/` (target tree only). ## Layout Conventions (reference) | Language | Root | Per-component path | Public API file | Test path | |----------|------|-------------------|-----------------|-----------| | TypeScript / React | `src/` | `src//` (this codebase deviates: features under `src/features//`, shared chrome under `src/components/`) | `src//index.ts` (barrel — none exist today) | `src//__tests__/` (none exist today) |