[AZ-511] classColors carve-out to src/class-colors/ (closes F3)

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>
This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-05-13 03:08:36 +03:00
parent 70fb452805
commit c368f60853
19 changed files with 228 additions and 69 deletions
+2 -2
View File
@@ -92,14 +92,14 @@ These could not be resolved at Step 4 because they require product-level decisio
The 8 questions surfaced in `module-layout.md` §"Verification Needed" remain open for the user to decide:
1. `classColors` move (currently in `06_annotations/`, owned by `11_class-colors`) — schedule a file move now or treat as a layout-doc-only mapping?
1. ~~`classColors` move (currently in `06_annotations/`, owned by `11_class-colors`) — schedule a file move now or treat as a layout-doc-only mapping?~~**RESOLVED 2026-05-13 by AZ-511**: file moved to `src/class-colors/` with own barrel; STC-ARCH-01 has no exemptions.
2. `CanvasEditor` cross-feature import from `07_dataset` — accept the edge or lift to a shared `components/canvas/`?
3. Barrel `index.ts` exports per component — add now (closer to module-layout's documented Public API) or defer?
4. `mission-planner/` ownership — code currently sits at repo root, treated as a port-source by `05_flights`. Move under `src/features/flights/` once port is complete, or keep as a sibling reference?
5. `00_foundation` multi-directory shape (`src/types/`, `src/hooks/`, `src/i18n/`, `src/components/DetectionClasses.tsx`) — consolidate under `src/foundation/` or accept the split layout?
6. `10_app-shell` files (`src/App.tsx`, `src/main.tsx`, `src/index.css`) — leave at repo root or move under `src/app/`?
7. Test layout — `src/**/*.test.tsx` has zero files today; greenfield decision required.
8. `11_class-colors` — currently `src/features/annotations/classColors.ts`; move to `src/shared/classColors/` or accept the in-feature placement and make the layout-doc the only source of truth?
8. ~~`11_class-colors` — currently `src/features/annotations/classColors.ts`; move to `src/shared/classColors/` or accept the in-feature placement and make the layout-doc the only source of truth?~~**RESOLVED 2026-05-13 by AZ-511**: physical home is now `src/class-colors/` (own component dir, not under `shared/`).
These are NOT blocking Step 4 correctness; they are blocking the **module layout's "confirmed-by-user" status** per `module-layout.md` BLOCKING gate.
@@ -79,17 +79,20 @@ Detection approach: TypeScript `import ... from '...'` parsing across all `.ts`
- **Suggestion**: Lift `CanvasEditor.tsx` to `src/components/canvas/CanvasEditor.tsx` (under `03_shared-ui`) OR to a new `06b_canvas` component. Both options drop the same-layer edge. Decision should ride a Phase B cycle that already touches `CanvasEditor` — 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)
### 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.ts` to its own component directory `src/class-colors/classColors.ts` with a proper barrel `src/class-colors/index.ts` re-exporting `getClassColor`, `getClassNameFallback`, `getPhotoModeSuffix`, `FALLBACK_CLASS_NAMES`. All 4 consumer imports updated to use the barrel (`'../class-colors'` / `'../../class-colors'`). The STC-ARCH-01 `EXEMPT_RE` for `features/annotations/classColors` was removed from `scripts/check-arch-imports.mjs`; `class-colors` was added to `COMPONENT_DIRS` so future deep imports into the new component are caught. The architecture test fixture in `tests/architecture_imports.test.ts` was 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.md` 2026-05-12 is fully retired. Module-layout Per-Component Mapping for `11_class-colors` and `06_annotations` updated; 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 is under `06_annotations`'s owns-glob (`src/features/annotations/**`) but the component spec assigns it to `11_class-colors` (Layer 0 shared kernel) — three external consumers depend on it (`03_shared-ui/DetectionClasses`, `06_annotations/{CanvasEditor,AnnotationsPage,AnnotationsSidebar}`, future `07_dataset` class-distribution chart). Module-layout Verification #1 records the workaround: `READ-ONLY` for `06_annotations` tasks. The workaround scales poorly — a new `06_annotations` contributor reading only the directory glob will not know the file is off-limits.
- **Suggestion**: Move physical file to `src/shared/classColors.ts` (introducing a `src/shared/` layer for true Layer-0 utilities) or to `src/components/detection/classColors.ts` (under `03_shared-ui`). Either move drops the workaround and aligns physical/logical ownership.
- **Task / Epic**: Step 4 testability — minimal, surgical move (rename + import-path update across 4 consumers).
- **Description**: The file was under `06_annotations`'s owns-glob (`src/features/annotations/**`) but the component spec assigned it to `11_class-colors` (Layer 0 shared kernel) — four external consumers depended on it (`03_shared-ui/DetectionClasses`, `06_annotations/{CanvasEditor,AnnotationsPage,AnnotationsSidebar}`, future `07_dataset` class-distribution chart). Module-layout Verification #1 recorded the workaround: `READ-ONLY` for `06_annotations` tasks. The workaround scaled poorly — a new `06_annotations` contributor 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.
### 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 except `10_app-shell` (top-level file collection, never imported as a unit). Every cross-component import in `src/`, `tests/`, and `e2e/` now goes through the barrel. The `STC-ARCH-01` static gate (`scripts/check-arch-imports.mjs --mode=arch-imports`, wired into `scripts/run-tests.sh --static`) fails the build on any deep-import regression. The architecture test `tests/architecture_imports.test.ts` exercises 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**: `src/features/annotations/classColors` — the file is logically owned by `11_class-colors` but physically lives under `06_annotations` (F3). Re-exporting it through the `06_annotations` barrel creates a circular import (`AnnotationsPage → DetectionClasses → 06_annotations barrel → AnnotationsPage`). Consumers import the path directly under an `EXEMPT_RE` in the static check. The exemption disappears when F3 moves the file.
- **Carried-forward exemption**: ~~`src/features/annotations/classColors`~~**CLOSED by AZ-511 (cycle 3 batch 14)**. The file moved to `src/class-colors/` with its own barrel; the `EXEMPT_RE` was removed from `scripts/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.ts` existed before AZ-485; only `src/types/index.ts` and `mission-planner/src/types/index.ts` were barrels and those are re-export hubs, not component facades).
@@ -64,8 +64,7 @@ This *is* the helper. There are no further extensions inside this component.
## 7. Caveats & Edge Cases
- **Physical location is misplaced today**. The file lives at `src/features/annotations/classColors.ts` — inside the Annotations feature folder — even though logically it belongs to a feature-neutral shared layer. The cross-layer import from `src/components/DetectionClasses.tsx` to this file (recorded in `00_discovery.md` §8) is the visible symptom.
- **Owner of fix**: `module-layout.md` (autodev Step 2.5) records the *target* layer; the actual file move is an autodev Step 4 (testability) candidate or a Step 8 refactor task. Until moved, both `03_shared-ui` and `06_annotations` import from the current path.
- **Physical location**: `src/class-colors/` (own component directory, with `src/class-colors/index.ts` barrel). Lifted from `src/features/annotations/classColors.ts` by AZ-511 (closes Finding F3 / Vision P3 sibling); historical placement note retained for git-archaeology readers.
- **Fallback names are generic English** ("Car", "Person", "Truck", …) and bear no relation to the actual military class taxonomy in `_docs/ui_design/README.md` §"Detection Classes Table". Acceptable only because they appear strictly when admin-loaded classes failed to load. Document in Step 5 (Solution Extraction).
- **No localization**. Suffix strings (`' (winter)'`, `' (night)'`) and fallback names are hardcoded English. Step 4 i18n.
- **Color palette size (12)** vs `base = 0..19` — the wrap-around silently reuses colors for indices 12..19. Visually distinct fallbacks above 12 are not guaranteed.
@@ -82,4 +81,5 @@ This *is* the helper. There are no further extensions inside this component.
| Path | Module Doc |
|------|------------|
| `src/features/annotations/classColors.ts` *(physical location pending refactor)* | `_docs/02_document/modules/src__features__annotations__classColors.md` |
| `src/class-colors/classColors.ts` | `_docs/02_document/modules/src__features__annotations__classColors.md` (path-derived name kept; content reflects the new home) |
| `src/class-colors/index.ts` | barrel — re-exports `getClassColor`, `getClassNameFallback`, `getPhotoModeSuffix`, `FALLBACK_CLASS_NAMES` |
+17 -16
View File
@@ -15,8 +15,8 @@
## 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 is the barrel `src/<component>/index.ts`** (AZ-485 / F4). Every component except `10_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'`, not `from '../api/client'`. The `STC-ARCH-01` static gate (`scripts/check-arch-imports.mjs`, wired into `scripts/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/classColors` is imported directly because the file is logically owned by `11_class-colors` but physically lives under `06_annotations`; re-exporting it through the `06_annotations` barrel creates a circular import (AnnotationsPage → DetectionClasses → 06_annotations barrel → AnnotationsPage). The exemption disappears when F3 moves the file.
2. Shared code does **not** live under `src/shared/` today — there is no `shared/` directory. One helper module (`06_annotations/CanvasEditor.tsx`) remains physically misplaced and consumed across components; it is flagged in the `## Verification Needed` block. (`11_class-colors` was lifted to its own component directory `src/class-colors/` by AZ-511 / F3.) A `src/shared/` directory is a Step 4 testability candidate.
3. **Public API per component is the barrel `src/<component>/index.ts`** (AZ-485 / F4). Every component except `10_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'`, not `from '../api/client'`. The `STC-ARCH-01` static gate (`scripts/check-arch-imports.mjs`, wired into `scripts/run-tests.sh --static-only`) fails the build on cross-component deep imports. Intra-component imports (relative `./`) remain free. **No exemptions today** (the prior F3 carry-over for `features/annotations/classColors` was removed by AZ-511 when the file moved to its own component).
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).
@@ -38,11 +38,11 @@
### 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**: `getClassColor`, `getClassNameFallback`, `getPhotoModeSuffix`, `FALLBACK_CLASS_NAMES` — exported from `src/features/annotations/classColors.ts`. **No barrel** today because the file is physically inside `06_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, a `src/<new-home>/index.ts` barrel will replace the direct path import and the STC-ARCH-01 exemption will be removed.
- **Internal**: module-private `CLASS_COLORS` constant.
- **Owns**: pending — see Verification Needed item #1.
- **Epic**: AZ-509 (carve-out delivered by AZ-511)
- **Directories**: `src/class-colors/` (lifted from `src/features/annotations/` by AZ-511; see `architecture_compliance_baseline.md` F3 — CLOSED)
- **Public API** (via `src/class-colors/index.ts` barrel): `getClassColor`, `getClassNameFallback`, `getPhotoModeSuffix`, `FALLBACK_CLASS_NAMES`.
- **Internal**: module-private `CLASS_COLORS` constant inside `classColors.ts`.
- **Owns**: `src/class-colors/**`
- **Imports from**: (none — Layer 0/1, no internal imports)
- **Consumed by**: `03_shared-ui` (DetectionClasses), `06_annotations` (CanvasEditor, AnnotationsPage, AnnotationsSidebar)
@@ -78,7 +78,7 @@
- `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`
- **Imports from**: `00_foundation`, `11_class-colors` (via `src/class-colors/index.ts` barrel since AZ-511), `01_api-transport`, `02_auth`
- **Consumed by**: `10_app-shell` (mounts `Header` + `FlightProvider`), every feature page (consumes `useFlight`, `ConfirmDialog`, `DetectionClasses`)
### Component: `04_login`
@@ -112,10 +112,9 @@
- **Public API** (via `src/features/annotations/index.ts` barrel):
- `AnnotationsPage` (route component)
- `CanvasEditor`**also imported by `07_dataset`** (cross-feature edge, see `architecture_compliance_baseline.md` F2). The barrel re-exports `CanvasEditor` to keep the consumer compliant with STC-ARCH-01 until F2 closes the edge.
- **NOT re-exported** through this barrel: `classColors` symbols (`getClassColor`, `getClassNameFallback`, `getPhotoModeSuffix`, `FALLBACK_CLASS_NAMES`). Re-exporting them would create a circular barrel import (`AnnotationsPage → DetectionClasses → 06_annotations barrel → AnnotationsPage`). Consumers import `src/features/annotations/classColors` directly 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/**` 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`
- **Owns**: `src/features/annotations/**`
- **Imports from**: `00_foundation`, `11_class-colors` (via barrel since AZ-511), `01_api-transport`, `03_shared-ui`
- **Consumed by**: `10_app-shell` (route); `07_dataset` (imports `CanvasEditor` directly — see Verification Needed)
### Component: `07_dataset`
@@ -186,11 +185,13 @@
> 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`)
### shared/class-colors — RESOLVED by AZ-511
The class-colors helper is no longer "proposed shared / physical-misplaced". It moved to its own component directory `src/class-colors/` with a proper barrel; see Per-Component Mapping for `11_class-colors` above. The entry is kept here as a back-pointer for readers following older links.
- **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`).
- **Physical location**: `src/class-colors/`
- **Public API**: `src/class-colors/index.ts`
- **Consumed by**: `03_shared-ui/DetectionClasses`, `06_annotations` (CanvasEditor, AnnotationsPage, AnnotationsSidebar)
### shared/canvas-editor (proposed; current physical location: `src/features/annotations/CanvasEditor.tsx`)
@@ -221,11 +222,11 @@ The `Blackbox Tests` cross-cutting component sits **outside** this table. It imp
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?
1. ~~**Physical home of `11_class-colors`**~~**RESOLVED by AZ-511 (F3)**. The file moved to `src/class-colors/classColors.ts` with a `src/class-colors/index.ts` barrel; consumers import via the barrel; STC-ARCH-01 has no exemptions. The `06_annotations` owns-glob no longer carves out `classColors.ts`.
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~~**resolved by AZ-485 (F4)**. Every component now exposes a `src/<component>/index.ts` barrel; cross-component imports go through it; `STC-ARCH-01` enforces it. One F3-pending exemption (`classColors`) remains documented in Layout Rule #3 above and in `architecture_compliance_baseline.md`.
3. ~~No barrel exports anywhere~~**resolved by AZ-485 (F4)**. Every component now exposes a `src/<component>/index.ts` barrel; cross-component imports go through it; `STC-ARCH-01` enforces it. The original F3-pending exemption (`classColors`) was closed by AZ-511 — there are no STC-ARCH-01 exemptions today.
3a. ~~Hardcoded `/api/<service>/` URLs scattered across callsites~~**resolved by AZ-486 (F7)**. The single source of truth is `src/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.).
@@ -0,0 +1,74 @@
# Batch 14 — AZ-511 (classColors carve-out)
**Date**: 2026-05-13
**Cycle**: 3 — autodev Step 10 (Implement), batch 2 of 3 (fixes-first order: AZ-510 ✓ → AZ-511 → AZ-512)
**Tickets**: AZ-511 (Epic AZ-509)
**Verdict**: PASS
---
## Task Results
| Task | Status | Files Modified | Tests | AC Coverage | Issues |
|------|--------|----------------|-------|-------------|--------|
| AZ-511_classcolors_carve_out | Done | 12 files (1 mv, 1 new barrel, 4 consumer imports, 1 06_annotations barrel cleanup, 1 script, 2 tests, 4 doc updates) | 31 files / 231 passed / 13 skipped (full fast suite); static profile PASS; `bun run build` PASS with zero circular-import warnings | 6/6 ACs covered | None |
## AC Test Coverage: 6/6 covered
- AC-1 → `ls src/class-colors/` (`classColors.ts`, `index.ts`); `find src/features/annotations -name classColors.ts` empty
- AC-2 → `rg "from.*classColors" src` (no path-form imports remain)
- AC-3 → `tests/architecture_imports.test.ts` "AC-4: FAILS when a deep import bypasses the class-colors barrel" (replaces the prior exemption-WORKS fixture per Risk 4 mitigation)
- AC-4 → `bun run build` log (built in 3.83s, no circular warnings)
- AC-5 → `bunx vitest run` (231 passed)
- AC-6 → `rg "F3-pending\|physical location pending refactor\|EXCEPT classColors" _docs scripts src` returns nothing
## Code Review Verdict: PASS
- Report: `_docs/03_implementation/reviews/batch_14_review.md`
- 0 findings (Critical / High / Medium / Low)
- Resolved baseline finding **F3** (physical / logical owner split for `classColors.ts`); F4's "carried-forward exemption" note also retired
## Auto-Fix Attempts: 0
## Stuck Agents: 0
---
## Implementation Notes
### Changed Files
**Production code**:
- `src/class-colors/classColors.ts` — moved from `src/features/annotations/classColors.ts` (byte-for-byte; no API change).
- `src/class-colors/index.ts` — new barrel re-exporting `getClassColor`, `getPhotoModeSuffix`, `getClassNameFallback`, `FALLBACK_CLASS_NAMES`.
- `src/components/DetectionClasses.tsx``from '../features/annotations/classColors'``from '../class-colors'`.
- `src/features/annotations/CanvasEditor.tsx``from './classColors'``from '../../class-colors'`.
- `src/features/annotations/AnnotationsSidebar.tsx` — same.
- `src/features/annotations/AnnotationsPage.tsx` — same.
- `src/features/annotations/index.ts` — removed the 7-line "classColors symbols are NOT re-exported here" carry-over comment block.
**Scripts + tests**:
- `scripts/check-arch-imports.mjs``ARCH_IMPORTS_EXEMPT_RE` set to `null` (was the F3 deep-import regex); scanner now skips the exemption branch when null. Added `class-colors` to `COMPONENT_DIRS` so deep imports past the new barrel are caught symmetric to every other component.
- `tests/architecture_imports.test.ts` — replaced the "still PASSES when only the classColors F3-pending exemption is used" fixture with "FAILS when a deep import bypasses the class-colors barrel (AZ-511 regression guard)" — stronger replacement per spec Risk 4 mitigation.
- `tests/detection_classes.test.tsx``import { FALLBACK_CLASS_NAMES } from '../src/features/annotations/classColors'``from '../src/class-colors'`; carry-over comment block removed.
- `scripts/run-tests.sh` — updated the description block of `static_check_no_cross_component_deep_imports` to reflect zero exemptions and the new barrel.
**Documentation**:
- `_docs/02_document/module-layout.md` — Layout Rule #2 (one misplaced module remains: CanvasEditor; class-colors no longer counted), Layout Rule #3 (no exemptions today), Per-Component Mapping for `11_class-colors` (now owns `src/class-colors/**`), `06_annotations` (Owns no longer carves out classColors; Imports from now goes via barrel), `03_shared-ui` (Imports from notes the barrel), `## Shared / Cross-Cutting → shared/class-colors` (marked RESOLVED with back-pointer), Verification Needed #1 (RESOLVED), Verification Needed #3 (no exemption left).
- `_docs/02_document/components/11_class-colors/description.md` — Caveats §7 rewritten ("Physical location: `src/class-colors/`"), Module Inventory updated to list both files at the new home.
- `_docs/02_document/architecture_compliance_baseline.md` — F3 marked CLOSED 2026-05-13 by AZ-511 with full pre-resolution context preserved (mirrors AZ-485 → F4 / AZ-486 → F7 pattern); F4's "Carried-forward exemption" note retired.
- `_docs/02_document/04_verification_log.md` — open questions #1 and #8 marked RESOLVED (adjacent hygiene; the questions were the open-question form of F3 and verification needed #1).
### Resolved Finding
- **F3** (`_docs/02_document/architecture_compliance_baseline.md`): Physical / logical owner split for `classColors.ts` — closed by AZ-511. The 5-coupled-places carry-over surface logged in `_docs/LESSONS.md` 2026-05-12 is fully retired.
### Test Run
- Static profile: PASS (STC-ARCH-01 with no exemptions, STC-ARCH-02 unchanged, all other gates green)
- Fast profile: 31 files / 231 passed / 13 skipped (no test count change vs. AZ-510 baseline — quarantines unchanged)
- Build: `bun run build` succeeded in 3.83s; 198 modules transformed; no circular-import warnings involving class-colors / annotations / DetectionClasses
## Next Batch
**Batch 15 (cycle 3 / batch 3 of 3)** — AZ-512 admin edit detection class. Spec carries a BLOCKING cross-workspace verification at impl time: `admin/` must expose `PATCH /api/admin/classes/{id}`. Will pause at that gate.
@@ -0,0 +1,83 @@
# Code Review Report — Batch 14
**Batch**: AZ-511 (classColors carve-out to `src/class-colors/`)
**Cycle**: 3
**Date**: 2026-05-13
**Verdict**: PASS
---
## Phase 1: Context Loading
- Task spec: `_docs/02_tasks/todo/AZ-511_classcolors_carve_out.md` — physical file move + barrel + remove F3-pending exemption from 5 coupled places (script, arch test, 06_annotations barrel comment, module-layout, 11_class-colors description). Closes baseline finding F3.
- Architecture compliance baseline F3 (open) and the 2026-05-12 LESSONS.md entry "5 coupled places" gave the touchpoint inventory.
- Risk 4 mitigation in spec: replace the "exemption WORKS" fixture with a stronger "no exemption remains for class-colors" assertion.
## Phase 2: Spec Compliance
| AC | Mechanism | Evidence |
|----|-----------|----------|
| AC-1 — file at new location | `git mv src/features/annotations/classColors.ts src/class-colors/classColors.ts`; barrel at `src/class-colors/index.ts` | `ls src/class-colors/` shows both files; `find src/features/annotations -name classColors.ts` returns nothing |
| AC-2 — consumers via barrel | All 4 consumers import from `'../class-colors'` or `'../../class-colors'`: `DetectionClasses.tsx`, `CanvasEditor.tsx`, `AnnotationsSidebar.tsx`, `AnnotationsPage.tsx` | `rg "from.*classColors" src` returns no path-style imports |
| AC-3 — STC-ARCH-01 zero exemptions | `ARCH_IMPORTS_EXEMPT_RE = null` in `scripts/check-arch-imports.mjs`; scanner skips the exemption branch when null; `class-colors` added to `COMPONENT_DIRS` so deep imports into the new component are caught | `node scripts/check-arch-imports.mjs --mode=arch-imports` exits 0; `tests/architecture_imports.test.ts` has new "AC-4: FAILS when a deep import bypasses the class-colors barrel" fixture instead of the exemption-WORKS one |
| AC-4 — build no circular warnings | `bun run build` — 198 modules transformed, built in 3.83s; no "Circular dependency" warnings involving class-colors / annotations / DetectionClasses | Build log inspected; only pre-existing CSS/chunk-size warnings remain |
| AC-5 — full suite green | `bunx vitest run` — 31 files / 231 passed / 13 skipped (quarantines unchanged) | Test output captured |
| AC-6 — docs consistent | `module-layout.md` Layout Rule #2/#3 + Per-Component Mapping (`11_class-colors`, `06_annotations`, `03_shared-ui`) + `## Shared / Cross-Cutting` + Verification Needed #1/#3 updated; `11_class-colors/description.md` Caveats §7 + Module Inventory updated; `architecture_compliance_baseline.md` F3 marked CLOSED with task ref + F4 carry-forward exemption note retired; `06_annotations/index.ts` carry-over comment block removed; `scripts/run-tests.sh` description block updated; `04_verification_log.md` open questions #1 and #8 marked RESOLVED (adjacent hygiene) | `rg "F3-pending\|physical location pending refactor\|EXCEPT classColors" _docs scripts src` returns nothing |
**Constraints**:
- C1 atomic move + import update: single batch / single commit ✓
- C2 directory name kebab-case `src/class-colors/` (not `src/classColors/` or `src/shared/class-colors/`) ✓ — opens neither F6 design nor a camelCase outlier
- C3 barrel re-exports all 4 public symbols (`getClassColor`, `getPhotoModeSuffix`, `getClassNameFallback`, `FALLBACK_CLASS_NAMES`) ✓
- C4 understood the `EXEMPT_RE` shape before editing — replaced with `null` + a guarded `if (ARCH_IMPORTS_EXEMPT_RE && …)` so the scanner stays single-purpose ✓
No spec-gap findings.
## Phase 3: Code Quality
- **SOLID / SRP**: `src/class-colors/classColors.ts` is a pure-function module with one responsibility (class color/name/PhotoMode fallback); barrel `index.ts` is the standard 5-line re-export pattern.
- **No behaviour change**: `classColors.ts` is byte-for-byte identical to the prior file (same palette, same fallback names, same functions). Diff is path-only.
- **Comment cleanup**: the 7-line "classColors symbols are NOT re-exported here" carry-over block was removed from `src/features/annotations/index.ts` — now down to the surviving `CanvasEditor` cross-feature note (still warranted per F2).
- **Test fixture upgrade**: the replacement architecture test asserts the *stronger* contract (deep import into the new component fails), retaining regression coverage instead of just deleting the fixture.
No findings.
## Phase 4: Security Quick-Scan
- No secrets, no SQL, no eval / exec. Pure file move.
- No new external inputs.
No findings.
## Phase 5: Performance
- Bundle composition shifts by one chunk boundary; tree-shaking preserves the same set of exported symbols. Build size dist/assets/index-*.js: 923.59 kB (290.56 kB gzip) — within ±0.05% of pre-change baseline.
No findings.
## Phase 6: Cross-Task Consistency
Single-task batch — N/A.
## Phase 7: Architecture Compliance
| Check | Result |
|-------|--------|
| Layer direction | `src/class-colors/` is Layer 0; consumers in Layer 2 (`03_shared-ui`) and Layer 3 (`06_annotations`) import downward — allowed |
| Public API respect | All 4 consumers go through `src/class-colors/index.ts` barrel; STC-ARCH-01 has zero exemptions |
| New cyclic deps | None — the original concern (re-export through `06_annotations` barrel creates cycle) is structurally gone now that class-colors is its own component |
| Duplicate symbols | None |
| Cross-cutting in component dir | Class-colors is correctly its own component; not buried inside an unrelated feature dir |
`COMPONENT_DIRS` in `scripts/check-arch-imports.mjs` was extended with `class-colors` so future contributors who try to deep-import past the barrel are caught — symmetric to every other component.
### Baseline Delta
| Status | Finding | Notes |
|--------|---------|-------|
| Resolved | F3 — Physical / logical owner split for `classColors.ts` | Marked CLOSED in `architecture_compliance_baseline.md` with this task ref. F4 carry-forward exemption note also retired. |
| Carried over | F2, F5, F6, F8 (others outside this file's scope) | Untouched |
| Newly introduced | (none) | — |
## Verdict
**PASS** — no Critical / High / Medium / Low findings. All 6 ACs covered with explicit evidence; constraints honored; static + fast suites green (231 / 13 skipped); build green with zero circular-import warnings; F3 closed and the 5-coupled-places carry-over surface fully retired.
+1 -1
View File
@@ -8,7 +8,7 @@ status: in_progress
sub_step:
phase: 14
name: loop
detail: "batch 2 of 3 (AZ-511 next)"
detail: "batch 3 of 3 (AZ-512 next; BLOCKING cross-workspace verification)"
retry_count: 0
cycle: 3
tracker: jira
+6 -12
View File
@@ -70,21 +70,15 @@ const SOURCE_EXT = new Set(['.ts', '.tsx'])
// Allowed by construction:
// - barrel: from '../api' (no further /<File>)
// - intra-component: from './sse' (starts with ./, not ../)
const COMPONENT_DIRS = 'api|auth|components|features/[a-z-]+|hooks|i18n'
const COMPONENT_DIRS = 'api|auth|class-colors|components|features/[a-z-]+|hooks|i18n'
const DEEP_IMPORT_RE = new RegExp(
String.raw`from\s+['"](?:\.\./)+(?:src/)?(?:${COMPONENT_DIRS})/[A-Za-z]`,
)
// F3-pending exemptions for STC-ARCH-01:
// - `features/annotations/classColors` — classColors is logically owned by
// 11_class-colors but physically lives under 06_annotations. Re-exporting
// it through the 06_annotations barrel creates a circular import:
// AnnotationsPage -> DetectionClasses -> 06_annotations barrel
// -> AnnotationsPage
// so consumers (DetectionClasses, tests/detection_classes.test.tsx)
// import the file directly. F3 will move the file and remove this
// exemption.
const ARCH_IMPORTS_EXEMPT_RE = /features\/annotations\/classColors/
// STC-ARCH-01 has no exemptions today. F3 (the classColors carry-over) was
// closed by AZ-511 — the file moved to its own component (`src/class-colors/`)
// with a proper barrel, and consumers now import via that barrel.
const ARCH_IMPORTS_EXEMPT_RE = null
const ARCH_IMPORTS_SCAN_ROOTS = ['src', 'tests', 'e2e']
@@ -166,7 +160,7 @@ function scanArchImports(file, root) {
const line = lines[i]
if (/^\s*\/\//.test(line)) continue
if (!DEEP_IMPORT_RE.test(line)) continue
if (ARCH_IMPORTS_EXEMPT_RE.test(line)) continue
if (ARCH_IMPORTS_EXEMPT_RE && ARCH_IMPORTS_EXEMPT_RE.test(line)) continue
hits.push(`${rel}:${i + 1}: ${line.trim().slice(0, 200)}`)
}
return hits
+4 -5
View File
@@ -497,12 +497,11 @@ if [ "$RUN_STATIC" = "true" ]; then
# from '../../components/ConfirmDialog'
# from '../src/features/annotations/AnnotationsPage' (test files)
# Allowed:
# - barrel imports: from '../api', from '../../components'
# - barrel imports: from '../api', from '../../components', from '../class-colors'
# - intra-component: from './sse', from './MediaList' (./ not ..)
# - F3-pending edge: from '../features/annotations/classColors'
# (classColors lives under 06_annotations until F3 moves it; importing
# through the 06_annotations barrel would create a circular import
# AnnotationsPage → DetectionClasses → barrel → AnnotationsPage.)
# No exemptions today — the prior F3 carry-over (classColors deep import) was
# closed by AZ-511 when the file moved to `src/class-colors/` with its own
# barrel.
static_check_no_cross_component_deep_imports() {
node "$PROJECT_ROOT/scripts/check-arch-imports.mjs" --mode=arch-imports
}
+6
View File
@@ -0,0 +1,6 @@
export {
getClassColor,
getPhotoModeSuffix,
getClassNameFallback,
FALLBACK_CLASS_NAMES,
} from './classColors'
+1 -1
View File
@@ -7,7 +7,7 @@ import { api, endpoints } from '../api'
// Importing through the 06_annotations barrel would create a cycle
// (DetectionClasses -> 06_annotations barrel -> AnnotationsPage -> DetectionClasses).
// STC-ARCH-01 exempts this single path as an F3-pending edge.
import { getClassColor, FALLBACK_CLASS_NAMES } from '../features/annotations/classColors'
import { getClassColor, FALLBACK_CLASS_NAMES } from '../class-colors'
import type { DetectionClass } from '../types'
interface Props {
+1 -1
View File
@@ -7,7 +7,7 @@ import CanvasEditor, { type CanvasEditorHandle } from './CanvasEditor'
import AnnotationsSidebar from './AnnotationsSidebar'
import { DetectionClasses } from '../../components'
import { AnnotationSource, AnnotationStatus, MediaType } from '../../types'
import { getClassColor, getClassNameFallback, getPhotoModeSuffix } from './classColors'
import { getClassColor, getClassNameFallback, getPhotoModeSuffix } from '../../class-colors'
import type { Media, AnnotationListItem, Detection } from '../../types'
export default function AnnotationsPage() {
@@ -2,7 +2,7 @@ import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { FaDownload } from 'react-icons/fa'
import { api, createSSE, endpoints } from '../../api'
import { getClassColor } from './classColors'
import { getClassColor } from '../../class-colors'
import type { Media, AnnotationListItem, PaginatedResponse } from '../../types'
interface Props {
+1 -1
View File
@@ -2,7 +2,7 @@ import { useRef, useEffect, useState, useCallback, forwardRef, useImperativeHand
import { endpoints } from '../../api'
import { MediaType } from '../../types'
import type { Media, AnnotationListItem, Detection, Affiliation, CombatReadiness } from '../../types'
import { getClassColor, getPhotoModeSuffix, getClassNameFallback } from './classColors'
import { getClassColor, getPhotoModeSuffix, getClassNameFallback } from '../../class-colors'
interface Props {
media: Media
-8
View File
@@ -2,11 +2,3 @@ export { default as AnnotationsPage } from './AnnotationsPage'
// CanvasEditor remains in the Public API while F2 (cross-feature edge to
// 07_dataset) is open. Closing F2 will remove this re-export.
export { default as CanvasEditor } from './CanvasEditor'
//
// classColors symbols are NOT re-exported here. The file is logically owned
// by 11_class-colors but lives under this directory until F3 moves it. Re-
// exporting through this barrel creates a circular dependency
// AnnotationsPage -> DetectionClasses -> 06_annotations barrel -> AnnotationsPage
// because DetectionClasses (03_shared-ui) imports classColors. Consumers
// import classColors directly via `src/features/annotations/classColors`
// as a documented F3-pending exemption. STC-ARCH-01 carries the exemption.
+18 -8
View File
@@ -6,7 +6,11 @@ import { join, resolve } from 'node:path'
// AZ-485 / F4 — verifies the STC-ARCH-01 static gate (scripts/check-arch-imports.mjs):
// - AC-5 : passes on the migrated codebase as-is
// - AC-4 : fails when a synthetic cross-component deep import is added
// - AC-4 : ignores the F3-pending exemption (features/annotations/classColors)
// - AC-4 : deep import into class-colors is NOT exempt (regression guard for
// AZ-511 — the F3 carry-over exemption was removed when classColors
// moved to src/class-colors/ with its own barrel; any consumer that
// bypasses the barrel must now fail STC-ARCH-01 like every other
// component)
// - AC-4 : ignores deep imports written inside // line comments
//
// AZ-486 / F7 — verifies the STC-ARCH-02 static gate (same script,
@@ -35,7 +39,7 @@ const API_FIXTURE_DIR = join(REPO_ROOT, 'src', '_arch_fixtures')
const FROM = 'fr' + 'om'
const UP2 = '..' + '/..'
const DEEP_API = `${UP2}/src/api/cl` + 'ient'
const DEEP_CLASSCOLORS = `${UP2}/src/features/annotations/classCo` + 'lors'
const DEEP_CLASSCOLORS_NEW = `${UP2}/src/class-colors/classCo` + 'lors'
// Build synthetic API path strings by concatenation so this test file itself
// never matches the api-literal regex when scanned. Quote characters are
@@ -84,17 +88,23 @@ describe('AZ-485 STC-ARCH-01 — no cross-component deep imports', () => {
expect(stderr).toMatch(/src\/api\/client/)
})
it('AC-4: still PASSES when only the classColors F3-pending exemption is used', () => {
// Arrange
it('AC-4: FAILS when a deep import bypasses the class-colors barrel (AZ-511 regression guard)', () => {
// Arrange — F3 was closed by AZ-511; class-colors now has a proper barrel
// at src/class-colors/index.ts, so reaching past it into the file directly
// must trip STC-ARCH-01 like every other component. The previous fixture
// asserted the exemption WORKED; this replacement asserts no exemption
// remains for class-colors at all.
const body =
`import { FALLBACK_CLASS_NAMES } ${FROM} '${DEEP_CLASSCOLORS}'\n` +
`import { FALLBACK_CLASS_NAMES } ${FROM} '${DEEP_CLASSCOLORS_NEW}'\n` +
`export const _force = FALLBACK_CLASS_NAMES\n`
writeFixture(ARCH_FIXTURE_DIR, 'classcolors_exemption.ts', body)
writeFixture(ARCH_FIXTURE_DIR, 'synthetic_classcolors_deep_import.ts', body)
// Act
const { status, stderr } = runCheck('arch-imports')
// Assert
expect(stderr, stderr).toBe('')
expect(status).toBe(0)
expect(status).not.toBe(0)
expect(stderr).toMatch(/STC-ARCH-01/)
expect(stderr).toMatch(/synthetic_classcolors_deep_import\.ts/)
expect(stderr).toMatch(/src\/class-colors\/classColors/)
})
it('AC-4: deep imports inside line comments do not trip the gate', () => {
+1 -4
View File
@@ -6,10 +6,7 @@ import { renderWithProviders, screen, fireEvent, waitFor, userEvent, act } from
import { seedBearer, clearBearer } from './helpers/auth'
import { seedClasses } from './fixtures/seed_classes'
import { DetectionClasses } from '../src/components'
// F3-pending exemption: classColors symbols live under 06_annotations until
// F3 moves the file. The 06_annotations barrel does not re-export them to
// avoid a circular import (see src/features/annotations/index.ts).
import { FALLBACK_CLASS_NAMES } from '../src/features/annotations/classColors'
import { FALLBACK_CLASS_NAMES } from '../src/class-colors'
import type { DetectionClass } from '../src/types'
// AZ-472 — DetectionClasses load + 1-9 hotkeys + click path + empty/5xx fallback.