diff --git a/_docs/02_document/module-layout.md b/_docs/02_document/module-layout.md index 90c8f3e..2277181 100644 --- a/_docs/02_document/module-layout.md +++ b/_docs/02_document/module-layout.md @@ -50,8 +50,8 @@ - **Epic**: TBD - **Directory**: `src/api/` -- **Public API** (via `src/api/index.ts` barrel): `api`, `setToken`, `getToken`, `getApiBase`, `setNavigateToLogin`, `createSSE`. -- **Internal**: none (both files are externally consumed) +- **Public API** (via `src/api/index.ts` barrel): `api`, `setToken`, `getToken`, `getApiBase`, `setNavigateToLogin`, `createSSE`, `endpoints` (the typed URL-builder object that is the single source of truth for every `/api//...` path the UI talks to today — AZ-486 / F7; `STC-ARCH-02` enforces it). +- **Internal**: none (every file is externally consumed; the colocated `endpoints.test.ts` IS the wire-contract documentation per `module-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) @@ -227,6 +227,8 @@ The following inferences could not be made cleanly from code alone. They are sur 3. ~~No barrel exports anywhere~~ — **resolved by AZ-485 (F4)**. Every component now exposes a `src//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`. +3a. ~~Hardcoded `/api//` 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//` 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.). + 4. **`mission-planner/` is owned by `05_flights` but lives at the repo root** (not under `src/`). Layout rule #1 says one component owns one or more top-level directories — this satisfies the rule (it owns two: `src/features/flights/` AND `mission-planner/`). Implement-skill consumers must include `mission-planner/**` in `05_flights`'s OWNED glob. **Decision needed**: confirm the implement skill should treat `mission-planner/**` as OWNED by 05_flights (otherwise it's FORBIDDEN by default). 5. **`05_flights` cycle inside the port-source**. `mission-planner/src/flightPlanning/MapView.tsx ↔ MiniMap.tsx` form a circular import (named-handle, see `00_discovery.md` §7 footnote). They were analyzed together in batch MP-B6. The cycle is internal to the component and does not cross component boundaries; flagged here for completeness. diff --git a/_docs/02_tasks/todo/AZ-486_refactor_endpoint_builders.md b/_docs/02_tasks/done/AZ-486_refactor_endpoint_builders.md similarity index 100% rename from _docs/02_tasks/todo/AZ-486_refactor_endpoint_builders.md rename to _docs/02_tasks/done/AZ-486_refactor_endpoint_builders.md diff --git a/_docs/03_implementation/batch_10_report.md b/_docs/03_implementation/batch_10_report.md new file mode 100644 index 0000000..a423350 --- /dev/null +++ b/_docs/03_implementation/batch_10_report.md @@ -0,0 +1,100 @@ +# Batch Report + +**Batch**: 10 (Phase B cycle 1, batch 2 of 2 — cycle close) +**Tasks**: AZ-486 (Endpoint builders + STC-ARCH-02) +**Date**: 2026-05-11 +**Cycle**: Phase B feature cycle, Step 10 — Implement +**Total complexity**: 5 pts +**Epic**: AZ-447 (`01-testability-refactoring`) +**Closes**: architecture baseline finding **F7** (`_docs/02_document/architecture_compliance_baseline.md`) +**Depends on**: AZ-485 (F4 barrels) — committed in `23746ec`, AC-6 verified barrel re-export + +## Task Results + +| Task | Status | Files Modified / Added | Tests | AC Coverage | Issues | +|------|--------|------------------------|-------|-------------|--------| +| AZ-486_refactor_endpoint_builders | Done | **2 new files** (`src/api/endpoints.ts` — 25 builders; `src/api/endpoints.test.ts` — 36 contract assertions); **1 barrel update** (`src/api/index.ts` re-exports `endpoints`); **13 production files migrated** (`src/api/client.ts`, `src/auth/AuthContext.tsx`, `src/components/{FlightContext,DetectionClasses}.tsx`, `src/features/{admin/AdminPage,annotations/{AnnotationsPage,AnnotationsSidebar,CanvasEditor,MediaList,VideoPlayer},dataset/DatasetPage,flights/FlightsPage,settings/SettingsPage}.tsx`); **1 static-check script extended** (`scripts/check-arch-imports.mjs` — added `--mode=api-literals` for STC-ARCH-02 alongside existing `--mode=arch-imports` for STC-ARCH-01); **1 runner wired** (`scripts/run-tests.sh` — STC-ARCH-02 row added; STC-ARCH-01 invocation made explicit with `--mode=arch-imports`); **1 test file extended** (`tests/architecture_imports.test.ts` — 6 new STC-ARCH-02 cases covering single-/double-/template-literal fail paths, *.test.* exemption, line-comment skip, and migrated-codebase pass); **1 doc updated** (`_docs/02_document/module-layout.md` — `01_api-transport` Public API now lists `endpoints`; Verification Needed item #3a records F7 resolution + STC-ARCH-02 inventory + exemptions) | 36 new contract tests in `src/api/endpoints.test.ts` + 6 new architecture tests in `tests/architecture_imports.test.ts` = **+42 fast tests**; fast profile re-baselined from 167 → 209 passes (0 regressions); 31/31 static checks PASS including new `STC-ARCH-02` | 7 / 7 ACs covered | None | + +## AC Test Coverage: All 7 ACs covered + +| AC | Where | Profile | Status | +|----|-------|---------|--------| +| AC-1 — Every current path has a builder; URL strings character-identical | `src/api/endpoints.test.ts` (36 `expect(...).toBe('...')` cases — one per builder × every realistic input shape) | fast | PASS — every URL literal that lived in source before this refactor is reproduced exactly. Parameter interpolation (id strings, query strings, two-segment composites like `flightWaypoint(flightId, waypointId)`) covered explicitly. The test file IS the wire contract per `module-layout.md`'s "code-derived documentation" pattern. | +| AC-2 — No `/api//` literals remain in production | `node scripts/check-arch-imports.mjs --mode=api-literals` (exit 0) over `src/` excluding `endpoints.ts` and `*.test.tsx?`; cross-checked with workspace-wide grep showing only `endpoints.ts` retains the literals | static (`STC-ARCH-02`) | PASS — 0 hits outside the documented exemptions | +| AC-3 — Static gate fails on a newly-introduced literal | `tests/architecture_imports.test.ts` — 3 fail-on-synthetic-fixture cases (single-quoted, double-quoted, template-literal) all assert `status != 0` and `stderr` mentions `STC-ARCH-02` + fixture filename | fast | PASS — every quote style trips the gate. Single quote and template literal cases shown in the run log; double-quote case implicitly verified (same regex branch) | +| AC-4 — Static gate passes on the migrated codebase | `tests/architecture_imports.test.ts` `AC-4: passes on the migrated codebase` + `STC-ARCH-02` row in the static profile | fast + static | PASS — exit code 0, stderr empty | +| AC-5 — Fast profile remains green | `bash scripts/run-tests.sh` (static + fast); fast went 167 / 13 / 0 → 209 / 13 / 0 (+36 endpoint contract + 6 architecture STC-ARCH-02 = +42 new passes) | static + fast | PASS — 0 regressions; only adds new tests | +| AC-6 — `endpoints` is re-exported from `src/api/index.ts` (the F4 barrel) | `src/api/endpoints.test.ts` `AC-6: barrel re-export` (`endpoints === endpointsViaBarrel`); 13 production consumers import `endpoints` via `'../api'` or `'../../api'` — verified by STC-ARCH-01 still PASS | fast + static | PASS — same object identity; no deep imports reintroduced | +| AC-7 — MSW handlers and e2e stubs continue to match | Pre-existing MSW handlers across the fast suite still intercept correctly (no NEW "intercepted unhandled" errors introduced by the refactor); URL strings character-identical per AC-1; e2e profile not run in this batch (per project's batch-level testing strategy — handed off to Step 11 / Step 16 full run) | fast | PASS — observed MSW unhandled-warning lines under `ConfirmDialog.test.tsx` are pre-existing noise (AuthProvider boot triggers `/api/admin/auth/refresh` which that test file deliberately leaves unhandled; the auth-refresh URL is character-identical to pre-refactor); no new failure modes | + +## Design Decisions + +1. **Single shared static-check script with `--mode` flag, not a second `check-api-literals.mjs`.** Mirrors AZ-485's "single source of truth" decision (batch 9 / Design Decision #1). Both gates walk the same codebase, use the same `IGNORED_DIRS` / `SOURCE_EXT` / `walkSourceFiles` machinery, and skip the same single-line comments. Forking the script would have duplicated the walker and the comment-skip rule in two places, which is exactly the kind of drift STC-ARCH-* gates exist to prevent. The `--mode` flag is a one-line dispatch in `main()`. + +2. **STC-ARCH-02 regex matches all three quote styles** (`'`, `"`, `` ` ``), not just single quotes as the task spec's illustrative `ripgrep "'/api/[a-z-]+/"` suggested. Quote style is not a meaningful difference for "no hardcoded URLs in production" — a developer could regress the gate by switching quote styles otherwise. The regex `[\`'"]/api/[a-z][a-z-]*/` requires the path to be preceded by a string-opener, which avoids false positives on comment text that mentions `/api//` as documentation. Three fail-on-synthetic test cases (one per quote style) lock this behavior in. + +3. **`*.test.{ts,tsx}` files under `src/` are exempted from STC-ARCH-02.** Tests legitimately assert URL strings — MSW handlers, the `endpoints.test.ts` contract itself, and existing colocated tests (`src/api/client.test.ts`, `src/auth/{AuthContext,ProtectedRoute}.test.tsx`, `src/components/Header.test.tsx`) all reference `/api/...` literals. The exemption is documented in five places: the static-check script's `API_LITERAL_EXEMPT_FILES_RE` comment, the bash wrapper in `run-tests.sh`, `module-layout.md` Verification Needed item #3a, the architecture test (`AC-3: still PASSES when an offending literal lives in a *.test.ts file under src/`), and the in-code header comment at the top of `endpoints.ts`. Same exemption discipline as the AZ-485 F3-pending exemption (5 places). + +4. **Function-form builders everywhere (not constants).** Pinned by the task spec's "Why function form" comment in `endpoints.ts`. Allows parameter interpolation without callsites re-introducing template literals (and re-introducing STC-ARCH-02 violations), keeps tree-shaking per-builder under Vite's production rollup, and lets builders that take a query string own the `?` boundary so the wire contract stays identical (e.g., `endpoints.annotations.dataset(queryString)` returns `` `/api/annotations/dataset?${queryString}` `` — caller passes `params.toString()`, not a literal `?`-prefixed string). + +5. **`endpoints.flights.collection(queryString?)` accepts an optional query string.** Today's callsites are split: `FlightContext` reads `'/api/flights?pageSize=1000'` (GET with query), `FlightsPage` writes `'/api/flights'` (POST without query). One builder with an optional arg keeps the wire-contract surface coherent; passing `undefined` returns the bare path. Validated by two test cases (`flights.collection() without query` and `flights.collection(queryString) appends ?queryString`). Same shape used for `annotations.dataset(queryString)` and `annotations.media(queryString)`. + +6. **`endpoints.annotations.annotationsByMedia(mediaId, pageSize?=1000)` exposes the page-size constant.** Every current callsite uses `pageSize=1000`; the optional arg lets future tuning be a single-file change (per task spec NFR / Maintainability). Two test cases pin both the default and the override path. + +7. **`endpoints.admin.class(id: string | number)` widens the ID type.** `DetectionClass.id` is `number` in the type system today (per `AdminPage` line 51 cast), but the rest of the admin builders take `string` because user/aircraft IDs are GUIDs. Widening to `string | number` at the builder accepts today's number-typed call from `AdminPage.handleDeleteClass` without an explicit cast and stays forward-compatible if the backend later switches `DetectionClass.id` to UUID. Two test cases (`'cls-7'` and `42`) pin both arms. + +8. **`endpoints.detect.media(mediaId)` is the only entry under a non-annotations namespace.** The `/api/detect/` path is a single-segment service path (no further segments today) consumed only by `AnnotationsSidebar`. Keeping it under its own `detect` namespace mirrors the URL's first segment and leaves room for future detect-service endpoints without renaming. + +9. **`src/api/endpoints.ts` lives under `01_api-transport` — F6 explicitly out of scope.** Per the AZ-486 spec's `Excluded` section, the future `src/shared/` move (F6) is deferred. The barrel-re-export pattern means consumers import `{ endpoints } from '../api'` — when F6 lands and moves the file, only `src/api/index.ts` needs to flip the re-export source; consumers do not change. This is exactly the protection F4 / AZ-485 was built to provide. + +## Code Review Verdict: PASS + +Self-review (implement skill Step 9 / 10) applied to the 2 new + 13 modified + 1 script-extended + 1 runner-wired + 1 doc-updated + 1 test-extended files: + +- **0 Critical, 0 High, 0 Medium, 0 Low findings.** +- **Scope discipline**: every modified file is one of (contract author, deep-literal consumer, static-check author, runner wirer, contract documentor, gate test author). The spec's listed files are all migrated; two additional files outside the spec's explicit list (`CanvasEditor.tsx`, `VideoPlayer.tsx`) were also migrated because they contain `/api//` literals as `` / `