Move src/features/annotations/classColors.ts to its own component directory src/class-colors/ with a proper barrel; update the 4 consumer imports to go through the barrel; remove the F3-pending exemption from STC-ARCH-01 and from the architecture test fixture; clean up the 5 coupled doc/script touchpoints. Closes baseline finding F3 and retires the 5-coupled-places carry-over surface logged in LESSONS.md 2026-05-12. - Add `class-colors` to scripts/check-arch-imports.mjs COMPONENT_DIRS so deep imports past the new barrel are caught symmetric to every other component. - Replace the architecture test "exemption WORKS" fixture with the stronger "deep import into class-colors NOW FAILS" assertion (Risk 4 mitigation). - module-layout.md: Layout Rules + Per-Component Mapping (11_class-colors, 06_annotations, 03_shared-ui) + Verification Needed #1 + shared/class-colors block all updated to reflect the new home. - 11_class-colors/description.md: Caveats §7 + Module Inventory updated. - architecture_compliance_baseline.md: F3 marked CLOSED with full pre-resolution context preserved (mirrors AZ-485/F4 + AZ-486/F7 pattern); F4 carry-forward exemption note retired. - 04_verification_log.md: open questions #1 + #8 marked RESOLVED. - Build passes with no circular-import warnings (AC-4); fast suite 231/13 skipped green (AC-5); static profile green (AC-3 — zero exemptions remain). Batch report: _docs/03_implementation/batch_14_cycle3_report.md Co-authored-by: Cursor <cursoragent@cursor.com>
23 KiB
Architecture Compliance Baseline
Scope: full existing codebase (src/ deployed tree + mission-planner/ port-source).
Date: 2026-05-10
Mode: code-review baseline (Phase 1 + Phase 7 only).
Verdict: FAIL — 1 Critical, 4 High, 2 Medium, 2 Low. Verdict drives Step 2 → Step 4 / Step 8 routing per flows/existing-code.md; it does NOT block any pipeline.
Inputs read in Phase 1:
_docs/02_document/architecture.md(incl. Architecture Vision P1–P12, ADR-001..010, Mission-planner convergence plan)._docs/02_document/module-layout.md(per-component file ownership, Layer table, Verification Needed #1–#8)._docs/00_problem/restrictions.md,_docs/01_solution/solution.md(project context).
Detection approach: TypeScript import ... from '...' parsing across all .ts / .tsx files; layer resolved via module-layout.md "Per-Component Mapping"; layer comparison against the "Allowed Dependencies (layering)" table.
Findings
| # | Severity | Category | Location | Title |
|---|---|---|---|---|
| F1 | Critical | Architecture | mission-planner/** vs src/features/flights/** |
Mission-planner duplicates 13+ modules of the deployed flights tree |
| F2 | High | Architecture | src/features/dataset/DatasetPage.tsx:9 → ../annotations/CanvasEditor |
Cross-feature same-layer edge — 07_dataset reaches into 06_annotations |
| F3 | High | Architecture | src/features/annotations/classColors.ts |
Physical / logical owner split — 11_class-colors file lives inside 06_annotations |
| F4 | High | Architecture | every component | No Public API barrels — every internal file is de-facto public — CLOSED 2026-05-11 by AZ-485 (23746ec) |
| F5 | High | Architecture | mission-planner/src/flightPlanning/MapView.tsx ↔ MiniMap.tsx |
Pre-existing import cycle inside port-source |
| F6 | Medium | Architecture | (codebase-wide) | No src/shared/ infrastructure for cross-cutting concerns |
| F7 | Medium | Architecture | every api.* / createSSE call site |
Hardcoded /api/<service>/... paths instead of env-driven endpoints — CLOSED 2026-05-11 by AZ-486 (8a461a2) |
| F8 | Low | Architecture | _docs/02_document/module-layout.md |
Layering-table inconsistency — Header → useAuth is unannotated |
| F9 | Low | Architecture | mission-planner/src/{main,App,setupTests,vite-env}.tsx |
Inert second Vite entry tree at port-source root |
Finding Details
F1: Mission-planner duplicates 13+ modules of the deployed flights tree (Critical / Architecture)
-
Location:
mission-planner/src/**vssrc/features/flights/**. -
Description: Component
05_flightshas two physical trees (per ADR-009). The port-source duplicates almost every module the deployed tree exposes today AND carries behaviors the deployed tree does not yet have (camera-config side panel, mission JSON I/O, satellite tile provider, richer waypoint-altitude UX). Duplication enumerated below; until convergence completes, the same logic lives in two places and silently drifts. This is the work-list the Architecture Vision (Mission-planner convergence plan) asked Step 2 to surface.Duplicated by name (deployed → port-source):
Deployed ( src/features/flights/)Port-source ( mission-planner/src/.../)Notes AltitudeChart.tsxflightPlanning/AltitudeChart.tsxBoth consumed by their respective panels. AltitudeDialog.tsxflightPlanning/AltitudeDialog.tsxPort-source version uses useLanguage(custom i18n); deployed uses Tailwind.DrawControl.tsxflightPlanning/DrawControl.tsxDeployed uses newGuidfromflightPlanUtils; port-source fromutils.ts.JsonEditorDialog.tsxflightPlanning/JsonEditorDialog.tsxMission JSON I/O — deployed version is a stub vs. port-source's full editor. MapPoint.tsxflightPlanning/MapPoint.tsxBoth import the local pointIcon*set.MiniMap.tsxflightPlanning/MiniMap.tsxPort-source version is half of the F5 cycle. WindEffect.tsxflightPlanning/WindEffect.tsxPort-source pulls useLanguage; deployed usesi18next.WaypointList.tsxflightPlanning/PointsList.tsx(renamed)DnD reorder; same intent, different libs. FlightMap.tsxflightPlanning/MapView.tsx(renamed)The cycle partner. FlightParamsPanel.tsxflightPlanning/LeftBoard.tsx(renamed)Side-panel composition root. FlightsPage.tsxflightPlanning/flightPlan.tsx(renamed)Route component. mapIcons.tsicons/PointIcons.tsx,icons/MapIcons.tsx,icons/SidebarIcons.tsxPort-source uses inline SVG components; deployed uses L.icon.flightPlanUtils.ts(newGuid,calculateDistance,calculateAllPoints,parseCoordinates,getMockAircraftParams)utils.ts(newGuid);services/calculateDistance.ts;services/calculateBatteryUsage.ts;services/AircraftService.ts(mockGetAirplaneParams)Same five symbols, scattered across four files in the port-source. types.ts(FlightPoint,CalculatedPointInfo,MapRectangle,ActionMode,WindParams,AircraftParams,PURPOSES,COORDINATE_PRECISION,TILE_URLS)types/index.ts+constants/{actionModes,purposes,tileUrls,maptypes,languages,translations}.tsPort-source has richer constants tables (mapTypes, languages, translations) the deployed tree lacks. Port-source-only (no deployed counterpart yet — these are the behaviors that must port across Phase B):
flightPlanning/LanguageContext.tsx+LanguageSwitcher.tsx— mission-planner's standalone i18n (deployed usesi18next). DO NOT port; consolidate oni18next.flightPlanning/TotalDistance.tsx— distance + battery readout in the left panel. Behavior the deployed tree does not yet show in the same form.icons/PhoneIcon.tsx(rotate-phone hint),icons/SidebarIcons.tsx(DashedArea / HideSidebar / ShowSidebar).services/WeatherService.ts— wind compute (already partially in deployedflightPlanUtils.ts:60with the hardcoded key — P10 violation).flightPlanning/Aircraft.ts— aircraft entity helpers.config.ts—COORDINATE_PRECISION,GOOGLE_GEOCODE_KEY(second hardcoded-key risk — verify).
-
Suggestion: This is the convergence work-list. Per Architecture Vision (Mission-planner convergence plan):
- NOT a Step 4 testability item — these are behavior ports, not testability surgery.
- NOT a Step 8 refactor item alone — too large for one mechanical refactor; behavior changes break tests.
- Primary home: Phase B feature cycles (one cycle per port group: i18n consolidation; flight-planning UI parity; waypoint-altitude UX; mission JSON I/O; satellite tile provider; weather/battery service; camera-config side panel). Each cycle: New Task → Implement → Run Tests → Test-Spec Sync → Update Docs → Deploy → Retrospective.
- Final Phase B cycle: delete
mission-planner/(its only consumer becomes zero).
-
Task / Epic: feeds
05_flightsStep 3 (Test Spec) — every port target must have an AC for the converged behavior before its Phase B cycle starts.
F2: Cross-feature same-layer edge — 07_dataset reaches into 06_annotations (High / Architecture)
- Location:
src/features/dataset/DatasetPage.tsx:9→import CanvasEditor from '../annotations/CanvasEditor'. - Description:
07_datasetand06_annotationsare both Layer 3 features. Same-layer imports are forbidden except for explicit grandfathered edges. This edge IS grandfathered (module-layout.mdAllowed-Dependencies table footnote †, Verification Needed #2). It is the only Layer-3 ↔ Layer-3 import in the codebase. Until a proper home is chosen, the implement skill must keep treatingCanvasEditor.tsxas READ-ONLY for07_datasettasks. - Suggestion: Lift
CanvasEditor.tsxtosrc/components/canvas/CanvasEditor.tsx(under03_shared-ui) OR to a new06b_canvascomponent. Both options drop the same-layer edge. Decision should ride a Phase B cycle that already touchesCanvasEditor— folding the move into a behavior change is cheaper than a standalone refactor. - Task / Epic: defer to Phase B (when a
CanvasEditor-touching feature lands) or Step 8 refactor (optional).
F3: Physical / logical owner split for classColors.ts (High / Architecture) — CLOSED 2026-05-13 by AZ-511 (cycle 3 batch 14)
-
Resolution: File moved from
src/features/annotations/classColors.tsto its own component directorysrc/class-colors/classColors.tswith a proper barrelsrc/class-colors/index.tsre-exportinggetClassColor,getClassNameFallback,getPhotoModeSuffix,FALLBACK_CLASS_NAMES. All 4 consumer imports updated to use the barrel ('../class-colors'/'../../class-colors'). The STC-ARCH-01EXEMPT_REforfeatures/annotations/classColorswas removed fromscripts/check-arch-imports.mjs;class-colorswas added toCOMPONENT_DIRSso future deep imports into the new component are caught. The architecture test fixture intests/architecture_imports.test.tswas reshaped from "exemption WORKS" to "synthetic deep import into class-colors NOW FAILS" (Risk 4 mitigation). The 5-coupled-places carry-over surface logged in_docs/LESSONS.md2026-05-12 is fully retired. Module-layout Per-Component Mapping for11_class-colorsand06_annotationsupdated; Verification Needed #1 marked RESOLVED. Build passes with no circular-import warnings (AC-4); fast suite 231 / 13 skipped green (AC-5). -
Pre-resolution context (preserved for trace):
- Location:
src/features/annotations/classColors.ts. - Description: The file was under
06_annotations's owns-glob (src/features/annotations/**) but the component spec assigned it to11_class-colors(Layer 0 shared kernel) — four external consumers depended on it (03_shared-ui/DetectionClasses,06_annotations/{CanvasEditor,AnnotationsPage,AnnotationsSidebar}, future07_datasetclass-distribution chart). Module-layout Verification #1 recorded the workaround:READ-ONLYfor06_annotationstasks. The workaround scaled poorly — a new06_annotationscontributor reading only the directory glob would not know the file is off-limits. - Suggestion (executed): Move physical file to its own component directory
src/class-colors/and add a barrel. - Task / Epic: AZ-511 (Epic AZ-509) — cycle 3 batch 14, 3 points.
- Location:
F4: No Public API barrels — every internal file is de-facto public (High / Architecture) — CLOSED 2026-05-11 by AZ-485 (commit 23746ec)
-
Resolution: 11 component barrels (
src/<component>/index.ts) added — one per component except10_app-shell(top-level file collection, never imported as a unit). Every cross-component import insrc/,tests/, ande2e/now goes through the barrel. TheSTC-ARCH-01static gate (scripts/check-arch-imports.mjs --mode=arch-imports, wired intoscripts/run-tests.sh --static) fails the build on any deep-import regression. The architecture testtests/architecture_imports.test.tsexercises the gate with synthetic fixtures (AC-4 fail-on-synthetic, AC-5 pass-on-migrated). Module-layout Layout Rule #3 records the convention. -
Carried-forward exemption:
— CLOSED by AZ-511 (cycle 3 batch 14). The file moved tosrc/features/annotations/classColorssrc/class-colors/with its own barrel; theEXEMPT_REwas removed fromscripts/check-arch-imports.mjs. STC-ARCH-01 has zero exemptions today. -
Pre-resolution context (preserved for trace):
- Location: every component root (no
src/<component>/index.tsexisted before AZ-485; onlysrc/types/index.tsandmission-planner/src/types/index.tswere barrels and those are re-export hubs, not component facades). - Description: Cross-component imports used file-name granularity (
import { api } from '../api/client',import { useFlight } from '../../components/FlightContext', etc.). Consequence: there was no enforceable Public API surface. Any internal refactor inside a component (split a file, rename an export) was a breaking change to every importer. Phase 7 Check #2 ("Public API respect") could not meaningfully fail in this codebase because everything was public. - Suggestion (executed): add
src/<component>/index.tsfor every component, re-exporting only the symbols listed in module-layout's "Public API" line. - Task / Epic: Step 4 testability — moved to Phase B cycle 1 batch 9 / AZ-485 / Epic AZ-447.
- Location: every component root (no
F5: Pre-existing import cycle inside port-source (High / Architecture)
- Location:
mission-planner/src/flightPlanning/MapView.tsx(importsMiniMapon line 16) ↔mission-planner/src/flightPlanning/MiniMap.tsx(importsUpdateMapCenterfrom./MapViewon line 2). - Description: A named-handle cycle internal to
mission-planner/. Module-layout Verification #5 +00_discovery.md §7already record it. Not introduced by this scan. The cycle does NOT cross component boundaries (both files belong to05_flightsport-source). The cycle disappears at the end of the convergence (whenmission-planner/is deleted). - Suggestion: No action. Track-only finding. Reason: fixing the cycle inside port-source means moving
UpdateMapCenterto a third file — wasted work given the eventual delete. If the deployed tree gains the same cycle when porting, fix it there. - Task / Epic: (none — closed by mission-planner deletion in the final Phase B cycle).
F6: No src/shared/ infrastructure for cross-cutting concerns (Medium / Architecture)
- Location: codebase-wide. No
src/shared/directory exists today (module-layout Layout Rules #4). - Description: There is no shared logger, no central error envelope, no config loader, no telemetry hook. Each feature page does ad-hoc
console.error+ silent catches (multiple module findings — annotations sidebar AI-detect silent catches, dataset silent catches, settings save withouttry/finally). Onboarding observability or a global error boundary today touches every feature. - Suggestion: Introduce
src/shared/(orsrc/components/util/) at Phase B kickoff with:shared/logger.ts— wrapsconsole.*, adds revision + user context; replace ad-hocconsole.error.shared/config.ts— typedimport.meta.env.*accessor (resolves P10 OpenWeatherMap key + the future env-base-URL refactor).shared/errorBoundary.tsx— application-level boundary (today the SPA has noErrorBoundary— recorded insrc__App-and-main.mdfindings).shared/endpoints.ts— typed endpoint constants (closes F7).
- Task / Epic: Phase B candidate (one cycle for shared infrastructure) OR fold into Step 8 refactor if user picks A on the Step 8 gate.
F7: Hardcoded /api/<service>/... paths instead of env-driven endpoints (Medium / Architecture) — CLOSED 2026-05-11 by AZ-486 (commit 8a461a2)
-
Resolution:
src/api/endpoints.tsintroduced as the single source of truth — 25 typed builders covering every/api/<service>/<path>URL the UI talks to today. Re-exported through the F4 barrelsrc/api/index.ts; consumers import{ endpoints } from '../api'(or../../api). Every production callsite ofapi.*andcreateSSE()migrated toendpoints.*— 13 source files (admin, annotations × 5, flights, settings, dataset, auth, client, FlightContext, DetectionClasses). TheSTC-ARCH-02static gate (scripts/check-arch-imports.mjs --mode=api-literals, wired intoscripts/run-tests.sh --static) fails the build on any new/api/<service>/literal insrc/outside the contract owner (endpoints.ts) and*.test.tsx?files. The colocatedsrc/api/endpoints.test.ts(36 assertions, character-identical to pre-refactor URL strings) serves as the wire-contract documentation permodule-layout.md's "code-derived documentation" pattern. Module-layout Verification Needed item #3a records the convention. -
F6 interaction:
endpoints.tslives under01_api-transport(notsrc/shared/) — F6 is explicitly deferred. When/if F6 lands and moves the file, onlysrc/api/index.tsflips the re-export source; consumers do not change. This is exactly the protection F4 was built to provide. -
Pre-resolution context (preserved for trace):
- Location: every call site of
api.*()andcreateSSE()acrosssrc/features/**andsrc/auth/,src/components/FlightContext.tsx,src/components/DetectionClasses.tsx. Approximately 30 call sites. - Description: Consequence of ADR-006 (nginx prefix-strip). Each call site repeated
/api/<service>/<path>as a string literal. Testability suffered — every test fixture had to duplicate paths; any nginx-route change touched every feature. Architecture intent (ADR-006 Consequences) explicitly flagged this: "The SPA hardcodes /api//... paths in source instead of an env-driven base URL — testability is poor (finding tracked)." - Suggestion (executed): introduce a typed endpoints module exposing builders like
endpoints.auth.login(),endpoints.flights.byId(id),endpoints.annotations.media(query), etc. - Task / Epic: Step 4 testability — moved to Phase B cycle 1 batch 10 / AZ-486 / Epic AZ-447.
- Location: every call site of
F8: Layering-table inconsistency — Header → useAuth is unannotated (Low / Architecture)
- Location:
_docs/02_document/module-layout.md— "Allowed Dependencies (layering)" table vs "Per-Component Mapping"03_shared-uirow. - Description: The layering table says Layer 2 may import only from layers 0+1, no same-layer edges. The per-component table explicitly lists
03_shared-ui Imports from: ..., 02_auth. The actual importsrc/components/Header.tsx:3 → ../auth/AuthContextis by design — every header needs the current user. This is not a code violation (the per-component table is authoritative for specific edges); it's a doc bug. - Suggestion: Add a layering-table footnote for
03_shared-uisimilar to the existing07_datasetfootnote: "03_shared-ui importsuseAuthfrom02_auth'sAuthContext— same-layer cross-component edge, permitted as it is the only cross-cutting state-context dependency." Update during Step 4 doc-touchup or fold into Step 13 docs cycle. - Task / Epic: documentation only — no code change.
F9: Inert second Vite entry tree at port-source root (Low / Architecture)
- Location:
mission-planner/src/{main.tsx,App.tsx,setupTests.ts,vite-env.d.ts}. - Description:
mission-planner/src/carries its own Vite entrypoint (main.tsx+App.tsx), test setup file, and env shim. ADR-009 says the deployed Vite build does NOT compile this tree, but there is nothing in the codebase that prevents a future contributor from addingmission-planner/to a Vitebuild.rollupOptions.inputand shipping two SPAs. Architecture intent is "vendored port-source, NOT a deployed component". - Suggestion: Step 4 testability or Step 8 refactor — verify
vite.config.tshas nomission-planner/entry; if Vite ever adds workspace-aware builds, add explicit exclusion. Defer until the convergence retires the directory entirely. - Task / Epic: closed by F1's final delete cycle.
Routing — what feeds where
Per flows/existing-code.md Step 2 → Step 4 / Step 8 rule, High and Critical Architecture findings must either (a) be appended to the testability list-of-changes.md input for Step 4, or (b) be deferred to Step 8 (optional Refactor) via Choose. Recommended routing:
| Finding | Recommended target step | Reason |
|---|---|---|
| F1 | Phase B cycles (NOT Step 4, NOT Step 8) | Behavior ports — need test safety net + per-feature deploy. |
| F2 | Phase B (when a CanvasEditor-touching task lands) |
Fold the lift into a behavior change cycle. |
| F3 | Step 4 testability | Pure file move + import-path update — fits the "smallest fix" rule. |
| F4 | Step 4 testability | 11 new index.ts files + cohort of import-path edits — mechanical. |
| F5 | No action (closed by F1 final delete) | Fixing inside port-source is wasted work. |
| F6 | Phase B (one infra cycle) OR Step 8 refactor | Shared logger / config / endpoints / error boundary — design choice. |
| F7 | Step 4 testability | Endpoint extraction enables tests; depends on F6 if src/shared/ is path. |
| F8 | Documentation touch-up (no step) | Doc-only. |
| F9 | Defer | Closed by F1 final delete. |
The 4 High and 1 Critical findings that should drive a Choose decision now:
- F1 — Phase B work-list (not Step 4, not Step 8).
- F2 — Phase B side-quest (not Step 4, not Step 8).
- F3 — Step 4 candidate.
- F4 — Step 4 candidate.
- F5 — track-only (no decision needed).
Baseline Delta
(N/A — this is the first baseline; no prior report to delta against. Future code-review invocations in cumulative or full mode will emit a Baseline Delta section against this file.)