Single source of truth for every /api/<service>/... URL the UI talks to: src/api/endpoints.ts (25 typed builders) re-exported via the F4 barrel. Migrates 13 production callsites in admin / annotations / flights / settings / dataset / auth / api-client / FlightContext / DetectionClasses to endpoints.* . Adds the STC-ARCH-02 static gate (--mode=api-literals in scripts/check-arch-imports.mjs, wired into scripts/run-tests.sh) that fails any new hardcoded /api/<service>/ literal in src/ outside endpoints.ts and *.test.tsx? files. Tests: +36 contract assertions in src/api/endpoints.test.ts (every builder, character-identical), +6 STC-ARCH-02 architecture cases in tests/architecture_imports.test.ts (single / double / template literal fail paths, *.test.* exemption, line-comment skip, migrated codebase pass). Fast profile 167 -> 209 PASS / 13 SKIP / 0 FAIL, +42 new, 0 regressions. Static profile 31 / 31 PASS. Closes architecture baseline finding F7. Cycle 1 of Phase B closed. Co-authored-by: Cursor <cursoragent@cursor.com>
22 KiB
Module Layout
Status: derived-from-code
Language: typescript (React 19 + Vite + Tailwind)
Layout Convention: custom (flat-features under src/; per-component barrels at src/<component>/index.ts since AZ-485)
Root: src/
Last Updated: 2026-05-11
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/implementStep 4 (file ownership),/code-reviewPhase 7 (architecture violations), and/refactordiscovery.
Layout Rules
- Each component owns ONE OR MORE top-level directories (or top-level files) under
src/. The mapping is NOT 1:1 —00_foundationowns three sibling directories (src/types/,src/hooks/,src/i18n/),05_flightsspanssrc/features/flights/AND a separatemission-planner/port-source root, and10_app-shellowns top-level files (App.tsx,main.tsx,index.css,vite-env.d.ts). - Shared code does not live under
src/shared/today — there is noshared/directory. Two helper modules (11_class-colors/classColors.tsand06_annotations/CanvasEditor.tsx) are physically misplaced and consumed across components; both are flagged in the## Verification Neededblock. Asrc/shared/directory is a Step 4 testability candidate. - Public API per component is the barrel
src/<component>/index.ts(AZ-485 / F4). Every component except10_app-shell(which is a top-level file collection —App.tsx,main.tsx, etc., never imported as a unit) exposes its Public API through a root barrel. Cross-component imports MUST go through the barrel —import { api } from '../api', notfrom '../api/client'. TheSTC-ARCH-01static gate (scripts/check-arch-imports.mjs, wired intoscripts/run-tests.sh --static-only) fails the build on cross-component deep imports. Intra-component imports (relative./) remain free. One F3-pending exemption:src/features/annotations/classColorsis imported directly because the file is logically owned by11_class-colorsbut physically lives under06_annotations; re-exporting it through the06_annotationsbarrel creates a circular import (AnnotationsPage → DetectionClasses → 06_annotations barrel → AnnotationsPage). The exemption disappears when F3 moves the file. - Cross-cutting concerns (logging, config, error handling, telemetry): no dedicated infrastructure today.
console.error/ silent catches are the closest thing — recorded in module findings. - Tests: there are zero tests under
src/. The only test file ismission-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; suggestsrc/<component>/__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 (no
src/<component>/index.tsbarrel —00_foundationspans three sibling directories; the existingsrc/types/index.tsis the type-alias barrel andsrc/hooks/+src/i18n/are imported directly per file):src/types/index.ts— every exported type alias (Detection,Flight,MediaItem,User, etc.)src/hooks/useDebounce.ts—useDebouncesrc/hooks/useResizablePanel.ts—useResizablePanelsrc/i18n/i18n.ts— default export (i18n instance)
- Internal:
src/i18n/en.json,src/i18n/ua.json(data; consumed only byi18n.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 by06_annotationson disk). Logical owner is this component; physical move tosrc/shared/classColors.ts(orsrc/components/detection/classColors.ts) is a Step 4 testability task. - Public API:
getClassColor,getClassNameFallback,getPhotoModeSuffix,FALLBACK_CLASS_NAMES— exported fromsrc/features/annotations/classColors.ts. No barrel today because the file is physically inside06_annotations; consumers import the path directly under the F3-pending exemption documented in Layout Rule #3 and enforced by STC-ARCH-01. When F3 moves the file to its own component directory, asrc/<new-home>/index.tsbarrel will replace the direct path import and the STC-ARCH-01 exemption will be removed. - Internal: module-private
CLASS_COLORSconstant. - 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 (via
src/api/index.tsbarrel):api,setToken,getToken,getApiBase,setNavigateToLogin,createSSE,endpoints(the typed URL-builder object that is the single source of truth for every/api/<service>/...path the UI talks to today — AZ-486 / F7;STC-ARCH-02enforces it). - Internal: none (every file is externally consumed; the colocated
endpoints.test.tsIS the wire-contract documentation permodule-layout.md's "code-derived documentation" pattern). - 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 (via
src/auth/index.tsbarrel):AuthProvider,useAuth,ProtectedRoute. - Internal: none
- Owns:
src/auth/** - Imports from:
00_foundation,01_api-transport - Consumed by:
03_shared-ui(Header readsuseAuth),04_login,10_app-shell(mountsAuthProvider+ProtectedRoute)
Component: 03_shared-ui
- Epic: TBD
- Directory:
src/components/ - Public API (via
src/components/index.tsbarrel — all symbols externally consumed):Header.tsx→HeaderHelpModal.tsx→HelpModalConfirmDialog.tsx→ConfirmDialogDetectionClasses.tsx→DetectionClassesFlightContext.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(mountsHeader+FlightProvider), every feature page (consumesuseFlight,ConfirmDialog,DetectionClasses)
Component: 04_login
- Epic: TBD
- Directory:
src/features/login/ - Public API (via
src/features/login/index.tsbarrel):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 undermission-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 ismission-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, via
src/features/flights/index.tsbarrel):FlightsPage(route component). Internal sub-components (FlightMap,FlightParamsPanel,FlightListSidebar,WaypointList,AltitudeChart,AltitudeDialog,WindEffect,MiniMap,MapPoint,DrawControl,JsonEditorDialog,mapIcons,flightPlanUtils,types) are NOT re-exported through the barrel. - Public API (port-source
mission-planner/): not consumed at all bysrc/today (separate Vite entrypoint,main.tsxof its own). Effectively a private vendored sibling. - Internal (target tree): every file under
src/features/flights/exceptFlightsPage.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(viaProtectedRoutefrom shell),03_shared-ui(usesConfirmDialog,useFlight) - Consumed by:
10_app-shell(route)
Component: 06_annotations
- Epic: TBD
- Directory:
src/features/annotations/ - Public API (via
src/features/annotations/index.tsbarrel):AnnotationsPage(route component)CanvasEditor— also imported by07_dataset(cross-feature edge, seearchitecture_compliance_baseline.mdF2). The barrel re-exportsCanvasEditorto keep the consumer compliant with STC-ARCH-01 until F2 closes the edge.- NOT re-exported through this barrel:
classColorssymbols (getClassColor,getClassNameFallback,getPhotoModeSuffix,FALLBACK_CLASS_NAMES). Re-exporting them would create a circular barrel import (AnnotationsPage → DetectionClasses → 06_annotations barrel → AnnotationsPage). Consumers importsrc/features/annotations/classColorsdirectly under the F3-pending exemption recorded in Layout Rule #3 and in STC-ARCH-01.
- Internal:
MediaList.tsx,VideoPlayer.tsx,AnnotationsSidebar.tsx - Owns:
src/features/annotations/**EXCEPTclassColors.ts(logically owned by11_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(importsCanvasEditordirectly — see Verification Needed)
Component: 07_dataset
- Epic: TBD
- Directory:
src/features/dataset/ - Public API (via
src/features/dataset/index.tsbarrel):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 (via
src/features/admin/index.tsbarrel):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 (via
src/features/settings/index.tsbarrel):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.tsxis the Vite entrypoint (no symbols are externally imported).App.tsxexportsApp. No barrel — the component is a top-level file collection, never imported as a unit. STC-ARCH-01's component allowlist intentionally omits10_app-shell. - 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'sOwnsglob for that filename pattern only.vitest.config.ts,tsconfig.test.jsonscripts/run-tests.sh,scripts/run-performance-tests.sh(extension only — the files were created in autodev Step 4 as Step 6 placeholders).package.jsontest-scoped sections only:scripts.test*,scripts.lint:tests,devDependenciesfor runners (vitest,@vitest/*,@playwright/test,msw,@testing-library/*), and any test-onlypeerDependenciesoverrides.- ESLint test-override blocks (
overridesentries scoped totests/**,e2e/**,**/*.test.{ts,tsx}).
- Imports from:
- Test bodies (files matching
**/*.test.{ts,tsx}/**/*.spec.{ts,tsx}and any e2e spec undere2e/tests/):00_foundationonly (and onlysrc/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/**ande2e/**—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/setNavigateToLoginon01_api-transport'sclient.ts,AuthProvideron02_auth,i18non00_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).
- Test bodies (files matching
- Consumed by: (none — tests are not part of production runtime).
- Notes:
- Every test task spec under epic AZ-455 carries
**Component**: Blackbox Testsand 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.jsonedits (devDependencies, test scripts) are OWNED here. Productiondependenciesand non-testscriptsare FORBIDDEN — those remain owned by the production component whose runtime they affect (typically10_app-shell). mission-planner/test files (e.g.,mission-planner/src/test/jsonImport.test.ts) are OWNED here for the same reason; themission-planner/**production glob remains owned by05_flights.
- Every test task spec under epic AZ-455 carries
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, treatclassColors.tsasOWNEDby tasks targeting11_class-colorsandREAD-ONLYto all other tasks (including those targeting06_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_annotationsfor 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 aREAD-ONLYfor07_datasettasks.
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.
-
Physical home of
11_class-colors. The component is logically Layer 0/1 shared kernel, but its physical file lives inside06_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 undershared/class-colorsabove (READ-ONLY for06_annotationstasks 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? -
Physical home of
CanvasEditor.tsx. Same shape: it lives under06_annotationsand is consumed cross-feature by07_dataset. Proposed:src/components/canvas/CanvasEditor.tsx(or a new06b_canvascomponent). Decision needed: keep the same-layer cross-feature edge, or schedule the lift? -
No barrel exports anywhere— resolved by AZ-485 (F4). Every component now exposes asrc/<component>/index.tsbarrel; cross-component imports go through it;STC-ARCH-01enforces it. One F3-pending exemption (classColors) remains documented in Layout Rule #3 above and inarchitecture_compliance_baseline.md.
3a. Hardcoded — resolved by AZ-486 (F7). The single source of truth is /api/<service>/ URLs scattered across callsitessrc/api/endpoints.ts (re-exported via the 01_api-transport barrel from rule #3). Every production callsite of api.* and createSSE() uses an endpoints.* builder; the colocated src/api/endpoints.test.ts pins every URL string and serves as the wire-contract documentation. The STC-ARCH-02 static gate (scripts/check-arch-imports.mjs --mode=api-literals, wired into scripts/run-tests.sh --static-only) fails the build on any new hardcoded /api/<service>/ literal under src/. Exemptions: src/api/endpoints.ts (the contract owner) and any *.test.ts / *.test.tsx under src/ (test files are exempt because tests legitimately assert URL strings — MSW handlers, contract tests, etc.).
-
mission-planner/is owned by05_flightsbut lives at the repo root (not undersrc/). Layout rule #1 says one component owns one or more top-level directories — this satisfies the rule (it owns two:src/features/flights/ANDmission-planner/). Implement-skill consumers must includemission-planner/**in05_flights's OWNED glob. Decision needed: confirm the implement skill should treatmission-planner/**as OWNED by 05_flights (otherwise it's FORBIDDEN by default). -
05_flightscycle inside the port-source.mission-planner/src/flightPlanning/MapView.tsx ↔ MiniMap.tsxform a circular import (named-handle, see00_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. -
00_foundationowns three sibling directories (types/,hooks/,i18n/). Layout rule #1 permits this. Future option: split into00a_types,00b_hooks,00c_i18nif the directories grow. Decision needed: keep the multi-directory component, or split now? -
10_app-shellowns 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. -
Test layout is undefined (no tests exist). When Steps 5–6 of autodev produce tests, recommended layout is
src/<component-dir>/__tests__/per React convention; for05_flightscross-tree tests, prefersrc/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; present for every component except 10_app-shell — see Layout Rule #3) |
src/<component>/__tests__/ (none exist today) |