Files
ui/_docs/02_document/module-layout.md
T
Oleksandr Bezdieniezhnykh 496b089102
ci/woodpecker/push/build-arm Pipeline was successful
[AZ-456] Clarify Blackbox Tests imports rule (helpers vs test bodies)
Split the "Imports from" entry for the Blackbox Tests cross-cutting
component into two cases:

- Test bodies (*.test.{ts,tsx}, *.spec.{ts,tsx}, e2e specs) keep the
  strict "00_foundation only / src/types only" rule per black-box
  discipline (P9 / environment.md).
- Test infrastructure (tests/setup.ts, tests/msw/**, tests/helpers/**,
  tests/fixtures/**, e2e/playwright.config.ts, e2e/stubs/**, etc.) MAY
  import testability-purpose production accessors from any layer
  (e.g. setToken on 01_api-transport, AuthProvider on 02_auth, i18n
  on 00_foundation) — these helpers ARE the production-equivalent
  composition root for tests.

Surfaced during AZ-456 self-review when render.tsx / auth.ts /
navigate.ts had to import production accessors that the task spec
explicitly mandated. The original rule was unambiguous-but-incomplete;
the doc now matches the practical reality without weakening the
black-box discipline for test bodies themselves.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-11 03:01:31 +03:00

244 lines
19 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.
# 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/<component>/__tests__/` per the standard React convention when tests are added (autodev Step 56).
## 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**:
- **Test bodies** (files matching `**/*.test.{ts,tsx}` / `**/*.spec.{ts,tsx}` and any e2e spec under `e2e/tests/`): `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.
- **Test infrastructure** (everything else under `tests/**` and `e2e/**``setup.ts`, MSW handlers, fixtures, helpers/wrappers, Playwright configs, runner Dockerfiles, stub services): MAY import production accessors from any layer when the accessor was created specifically for testability (e.g. `setToken` / `setNavigateToLogin` on `01_api-transport`'s `client.ts`, `AuthProvider` on `02_auth`, `i18n` on `00_foundation`). These helpers ARE the production-equivalent composition root for tests; black-box discipline applies to what test bodies observe, not to how the test environment is wired. Imports MUST still be public-API entry points (no reaching into internal files of other components).
- **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`<sup>†</sup>, `08_admin`, `09_settings` | 0, 1, 2, 3<sup>†</sup> |
| 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) |
<sup>†</sup> `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/<component>/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 56 of autodev produce tests, recommended layout is `src/<component-dir>/__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/<component>/` (this codebase deviates: features under `src/features/<feature>/`, shared chrome under `src/components/`) | `src/<component>/index.ts` (barrel — none exist today) | `src/<component>/__tests__/` (none exist today) |