[AZ-485] Add Public API barrels + STC-ARCH-01 (F4 close)

Closes architecture baseline finding F4. Every component now exposes
its Public API through `src/<component>/index.ts`; cross-component
imports go through the barrel. `scripts/check-arch-imports.mjs` plus
`STC-ARCH-01` in the static profile enforce the rule; tests in
`tests/architecture_imports.test.ts` cover AC-4/AC-5 + 2 exemption
cases. One F3-pending exemption (`classColors`) is documented in 5
places (barrel, consumer, script, doc, test) to avoid a circular
import.

Phase B cycle 1 batch 1 of 2 (epic AZ-447). Batch 2 is AZ-486
(endpoint builders) — blocked on this commit landing.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-05-11 10:33:30 +03:00
parent 2071a24391
commit 23746ec61d
56 changed files with 455 additions and 101 deletions
@@ -0,0 +1,133 @@
# Public API barrels per component + deep-import migration
**Task**: AZ-485_refactor_public_api_barrels
**Name**: Add Public API barrels and migrate cross-component imports
**Description**: Introduce `index.ts` barrels for every component, narrow each component's Public API to the symbols listed in `module-layout.md`, replace every cross-component deep import with a barrel import, and add a static check that flags future deep imports. Closes architecture baseline finding **F4**.
**Complexity**: 5 points
**Dependencies**: None
**Component**: cross-cutting (0010) — coordinated edit across `src/api/`, `src/auth/`, `src/components/`, `src/features/**/`, `src/hooks/`, `src/i18n/`, `src/App.tsx`, plus every test importer
**Tracker**: AZ-485
**Epic**: AZ-447
## Problem
`_docs/02_document/architecture_compliance_baseline.md` Finding **F4** (High / Architecture): no component currently exposes a barrel `index.ts` (the sole barrel today is `src/types/index.ts`, owned by `00_foundation`). Cross-component imports use file-name granularity (`import { api } from '../api/client'`, `import { useFlight } from '../components/FlightContext'`, …). Consequence:
1. There is no enforceable Public API surface — every internal file is de-facto public.
2. Any internal split / rename inside a component is a breaking change to ~10 importers.
3. Phase 7 architecture compliance ("Public API respect") cannot fail in this codebase because everything is public.
4. The next time `module-layout.md` flags a Public-API drift, no static gate exists to catch it.
`module-layout.md` Layout Rules #3 records the same observation and lists this as a Step 4 testability candidate; Step 4 deferred it to Phase B (`_autodev_state.md::step_2_baseline_routing: per-finding-recommended`).
## Outcome
- Every component listed in `module-layout.md`'s "Per-Component Mapping" exposes its Public API through a barrel `index.ts` at the component root (10 new files; `src/types/index.ts` is unchanged).
- Every cross-component import in `src/**` and `tests/**` resolves through a component barrel — no remaining deep imports of another component's internal files. `mission-planner/**` is exempt (untouched per F1's deferred convergence plan).
- A static check (added to `scripts/run-tests.sh`) fails the static profile if any new `src/**` or `tests/**` file imports a non-barrel path from another component.
- `_docs/02_document/module-layout.md` Layout Rules #3 is rewritten to describe the post-change state ("Each component exposes its Public API via `src/<component>/index.ts`. Cross-component imports MUST use the barrel. The static gate `STC-ARCH-01` enforces this.").
- All existing fast + static profiles remain green after the migration.
## Scope
### Included
- Create 10 new barrels (`src/api/index.ts`, `src/auth/index.ts`, `src/components/index.ts`, `src/features/{login,flights,annotations,dataset,admin,settings}/index.ts`, `src/hooks/index.ts`, `src/i18n/index.ts`). Each barrel re-exports ONLY the symbols listed for that component in `module-layout.md`'s "Per-Component Mapping" → "Public API (de-facto)".
- Replace every cross-component deep import (~30 sites across `src/App.tsx`, every feature page that imports from another component, and every `tests/**` and colocated `*.test.tsx` that imports a production symbol from another component) with a barrel import.
- Add a new static check `STC-ARCH-01` to `scripts/run-tests.sh` that fails the static profile if any `src/**` or `tests/**` file (excluding the barrel itself, `mission-planner/**`, and intra-component imports) imports a non-barrel path from a different component.
- Update `_docs/02_document/module-layout.md` Layout Rules #3 to reflect the post-change state and add `STC-ARCH-01` to the Static Checks inventory (if such a list exists; otherwise document inline).
### Excluded
- `src/types/index.ts` is already a barrel — left unchanged.
- `mission-planner/**` — untouched (F1's deferred convergence plan; will be deleted in the final Phase B port cycle per the baseline).
- F2 (`07_dataset → 06_annotations` cross-feature edge for `CanvasEditor`) — `CanvasEditor` STAYS in the `06_annotations` barrel's Public API list (the cross-feature edge is grandfathered in `module-layout.md` and is closed by F2, not F4).
- F3 (`classColors.ts` physical/logical owner split) — the file remains physically under `src/features/annotations/`; F4 lists it in the `06_annotations` barrel for now. Physical move is F3's own task.
- New runtime behavior — this is a structural refactor only.
## Acceptance Criteria
**AC-1: Every component has a barrel exposing only its Public API**
Given the post-change repo,
When `src/<component>/index.ts` is read for every component listed in `module-layout.md`,
Then the file exists, every named export matches a symbol in that component's "Public API (de-facto)" line in `module-layout.md`, and no internal-only file's symbol is re-exported.
**AC-2: No cross-component deep imports remain in production code**
Given the post-change repo,
When `ripgrep "^(import|export).*from\s+['\"]\.\.\/[a-z][^'\"]*\/[A-Za-z][^'\"]+['\"]" src/` is run, excluding intra-component paths (paths that resolve to the same component's owned directory),
Then no match is found.
**AC-3: No cross-component deep imports remain in tests**
Given the post-change repo,
When the same ripgrep is run across `tests/**`, `e2e/**`, and colocated `**/*.test.{ts,tsx}` files,
Then no match is found OUTSIDE the documented testability exemptions in `module-layout.md` "Blackbox Tests" entry (test infrastructure may import testability accessors like `setToken`, `setNavigateToLogin`, `AuthProvider`, and i18n directly per the existing exemption — those continue to use barrel paths now that the barrels re-export them).
**AC-4: Static gate STC-ARCH-01 fails on a newly-introduced deep import**
Given the post-change static profile,
When a synthetic test file is added that imports `'../api/client'` instead of `'../api'` (or equivalent for another component),
Then `bash scripts/run-tests.sh --static` exits non-zero with `STC-ARCH-01` named in the failure line.
**AC-5: Static gate STC-ARCH-01 passes on the migrated codebase**
Given the post-change repo,
When `bash scripts/run-tests.sh --static` runs,
Then it exits zero and the static report shows `STC-ARCH-01` as PASS.
**AC-6: Fast profile remains green**
Given the post-change repo,
When `bash scripts/run-tests.sh --fast` runs,
Then it reports the same PASS / SKIP / FAIL counts as the pre-change baseline (163 PASS / 13 SKIP / 0 FAIL per `_docs/03_implementation/test_run_report.md`), with zero new failures and zero regressions in skip-classification.
**AC-7: module-layout.md reflects the new convention**
Given the post-change repo,
When `_docs/02_document/module-layout.md` Layout Rules #3 is read,
Then it states "Each component exposes its Public API via `src/<component>/index.ts`. Cross-component imports MUST use the barrel. The static gate `STC-ARCH-01` enforces this." and the Verification Needed item referencing the missing barrels is removed (or marked closed by this task's tracker ID).
## Non-Functional Requirements
**Performance**
- Initial JS bundle gzipped size MUST remain ≤ 2 MB (existing `STC-PERF01`). Barrel re-exports tree-shake under Vite's production rollup, so no regression expected.
**Compatibility**
- No runtime behavior change. The fast + e2e suites are the contract; both stay green.
**Maintainability**
- Future internal renames inside a component MUST not require import-path edits outside that component (validated by AC-2/AC-3 + STC-ARCH-01).
## Unit Tests
| AC Ref | What to Test | Required Outcome |
|--------|--------------|------------------|
| AC-1 | Each barrel file re-exports only documented symbols | Re-export list matches `module-layout.md`'s Public API line for that component (test reads both files and compares) |
| AC-4 | Synthetic deep-import detection | `STC-ARCH-01` fails when a fixture file with a deep import is added |
| AC-5 | Static check on the real codebase | `STC-ARCH-01` passes |
## Blackbox Tests
| AC Ref | Initial Data/Conditions | What to Test | Expected Behavior | NFR References |
|--------|-------------------------|--------------|-------------------|----------------|
| AC-2 | Repo after migration | `ripgrep` for cross-component deep imports in `src/` | Zero matches | Maintainability |
| AC-3 | Repo after migration | `ripgrep` for cross-component deep imports in `tests/`, `e2e/`, colocated tests | Zero matches outside documented exemptions | Maintainability |
| AC-6 | Fast profile | `scripts/run-tests.sh --fast` | 163 PASS / 13 SKIP / 0 FAIL (matches `_docs/03_implementation/test_run_report.md`) | Compat |
## Constraints
- All 10 barrels + import migration + static check MUST land in ONE commit to keep mid-state green (partial migration breaks the static gate on intermediate commits). If commit size is impractical, split per-component but always under one PR atomic to merge.
- The barrel files are OWNED by each respective component (e.g. `src/api/index.ts` is OWNED by `01_api-transport` tasks); the static check addition to `scripts/run-tests.sh` is OWNED by `Blackbox Tests` per `module-layout.md`.
- No new dependencies. The static check uses `ripgrep` (already used elsewhere in `scripts/run-tests.sh`).
- `mission-planner/**` MUST be untouched.
## Risks & Mitigation
**Risk 1: A cohort import-path edit misses a transitive import path → fast suite goes red on TypeScript "module not found"**
- *Risk*: ~30 import statements across many files; mechanical edit can miss one.
- *Mitigation*: Run `bun tsc -b --noEmit` (or the project's `lint:tests` script) after every per-component batch; commit only when type-check is green. AC-6 (full fast profile) is the final gate.
**Risk 2: Vite tree-shaking regression — barrel re-exports drag in optional sub-modules**
- *Risk*: A barrel that re-exports rarely-used symbols can defeat tree-shaking and inflate the bundle.
- *Mitigation*: STC-PERF01 already caps gzipped bundle at 2 MB and runs in the static profile. The migration must keep that gate green. If bundle regresses, split the barrel into eager + lazy re-export blocks per Vite's recommendations.
**Risk 3: `CanvasEditor` cross-feature edge (F2) confuses the static check**
- *Risk*: `src/features/dataset/DatasetPage.tsx` legitimately imports `CanvasEditor` from `src/features/annotations/`. STC-ARCH-01 must allow this when it goes through the `06_annotations` barrel but flag the legacy direct path.
- *Mitigation*: After migration, the dataset import becomes `import { CanvasEditor } from '../annotations'` — passes the barrel-path check. F2's eventual `CanvasEditor` lift is independent of this task.
## Contract
This task produces the Public API contract for 10 components — the barrel re-export lists ARE the contract. The contract surface for each component is documented inline at `_docs/02_document/module-layout.md` "Per-Component Mapping" → "Public API (de-facto)"; this task does not create a new contract file but DOES update Layout Rules #3 to declare the barrel files as the canonical Public API surface (no longer "de-facto").