mirror of
https://github.com/azaion/ui.git
synced 2026-06-21 10:51:11 +00:00
[AZ-447] autodev Steps 1-4 baseline: docs, tests, refactor specs
Captures the full output of autodev existing-code Phase A through Step 4 (Code Testability Revision) for the Azaion UI workspace: - Step 1 Document: _docs/02_document/ (FINAL_report, architecture, glossary, components/, modules/, diagrams/, system-flows, module-layout) plus _docs/00_problem/ + _docs/01_solution/ + _docs/legacy/ + _docs/how_to_test + README. - Step 2 Architecture Baseline: architecture_compliance_baseline.md. - Step 3 Test Spec: _docs/02_document/tests/ (environment, test-data, blackbox/performance/resilience/security/ resource-limit tests, traceability-matrix), enum_spec_snapshot, expected_results/results_report.md (98 rows), plus the run-tests.sh + run-performance-tests.sh runners. - Step 4 Code Testability Revision: 01-testability-refactoring/ run dir (list-of-changes C01-C07, deferred_to_refactor, analysis/research_findings + refactoring_roadmap) and the 7 child task specs AZ-448..AZ-454 under _docs/02_tasks/todo/ plus _dependencies_table.md. - _docs/_autodev_state.md pins the cursor at Step 4 / refactor Phase 4 entry so /autodev resumes cleanly. Epic AZ-447 (UI testability gates) tracks the 7 child tasks that will land in subsequent commits. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
# 00 — Foundation
|
||||
|
||||
## 1. High-Level Overview
|
||||
|
||||
**Purpose**: Pure leaf modules every other component depends on — TypeScript domain types, framework-agnostic React hooks, and i18next setup. No HTTP, no React tree, no business logic.
|
||||
|
||||
**Architectural Pattern**: Foundation / shared kernel.
|
||||
|
||||
**Upstream dependencies**: none (intra-repo). External: `i18next`, `react-i18next`, `react`.
|
||||
|
||||
**Downstream consumers**: every other `src/` component.
|
||||
|
||||
## 2. Internal Interfaces
|
||||
|
||||
### `src/types/index.ts`
|
||||
|
||||
| Export | Kind | Purpose |
|
||||
|--------|------|---------|
|
||||
| `DetectionClass`, `Annotation`, `Affiliation`, `CombatReadiness`, `AnnotationStatus`, `AnnotationSource`, `MediaStatus`, `Flight`, `Waypoint`, `DatasetItem`, `User`, `Permission`, etc. | type / enum | Shared domain shape used by `api/`, providers, and every feature page. |
|
||||
|
||||
> **CAVEAT — cross-cutting Step 4 work**. State.json records 4 enum-drift findings against the parent suite spec (`AnnotationStatus`, `Affiliation`, `CombatReadiness`, `MediaStatus`) plus a `Waypoint` shape mismatch. Owner of fix: this single file. The full target shapes live in `_docs/02_document/modules/src__features__annotations.md` findings #27–34 and `src__features__flights.md` finding #20.
|
||||
|
||||
### `src/hooks/useDebounce.ts`
|
||||
|
||||
| Export | Signature | Purpose |
|
||||
|--------|-----------|---------|
|
||||
| `useDebounce<T>(value: T, delay: number): T` | hook | Debounced value mirror. Used by Annotations search and Dataset filters. |
|
||||
|
||||
### `src/hooks/useResizablePanel.ts`
|
||||
|
||||
| Export | Signature | Purpose |
|
||||
|--------|-----------|---------|
|
||||
| `useResizablePanel(initialWidth: number, opts): { width, dragHandleProps }` | hook | Mouse-drag-resizable side panel. Used by Annotations and Dataset pages. Width is **not** persisted (finding #11 in `src__features__annotations.md`). |
|
||||
|
||||
### `src/i18n/i18n.ts`
|
||||
|
||||
| Export | Kind | Purpose |
|
||||
|--------|------|---------|
|
||||
| (default side-effect) | i18next init | Loads `en.json` + `ua.json`, wires `react-i18next`. Imported once by `main.tsx` for its side effect. |
|
||||
|
||||
> **CAVEAT**. `lng: 'en'` is hardcoded; no language detector or persistence. `ua.json` exists as a translation target but is not selectable at runtime (finding #1 in `src__i18n__i18n.md`). This is a Step 4 testability/configurability fix.
|
||||
|
||||
## 5. Implementation Details
|
||||
|
||||
| Module | Notes |
|
||||
|--------|-------|
|
||||
| `types/index.ts` | Plain TypeScript. No runtime code. |
|
||||
| `useDebounce` | `setTimeout` + `clearTimeout` in `useEffect`. |
|
||||
| `useResizablePanel` | `mousemove` listener attached to `window` while dragging; min/max width clamped. |
|
||||
| `i18n/i18n.ts` | i18next + ICU plurals. Bundles loaded synchronously (small JSONs, ~100 keys each). |
|
||||
|
||||
**State Management**: Stateless apart from the local hook state inside `useDebounce` / `useResizablePanel`. i18next's instance is module-level.
|
||||
|
||||
**Key Dependencies**:
|
||||
|
||||
| Library | Version | Purpose |
|
||||
|---------|---------|---------|
|
||||
| `i18next` | (per `package.json`) | Translation engine |
|
||||
| `react-i18next` | (per `package.json`) | React bindings; consumed via `useTranslation` in features |
|
||||
| `react` | 19 | Hooks runtime |
|
||||
|
||||
## 7. Caveats & Edge Cases
|
||||
|
||||
- Enum drift findings (cross-cutting). See state.json notes 2026-05-10 02:01Z and 02:13Z. Step 4 must reconcile against .NET service before patching `types/index.ts`.
|
||||
- `i18n` init is synchronous; if either bundle fails to load the app crashes at startup. No fallback.
|
||||
- `useResizablePanel` does not persist user-chosen width; resets every page nav.
|
||||
|
||||
## 8. Dependency Graph
|
||||
|
||||
**Must be implemented after**: nothing (Layer 0).
|
||||
|
||||
**Can be implemented in parallel with**: itself (the 4 modules are independent).
|
||||
|
||||
**Blocks**: every other component in this workspace.
|
||||
|
||||
## 6. Extensions and Helpers
|
||||
|
||||
`features/annotations/classColors.ts` was originally drafted as part of `06_annotations`, but per the Step 2 BLOCKING gate it has been lifted into its own component, **`11_class-colors`** (sibling shared kernel — see `components/11_class-colors/description.md`). The physical file location is unchanged — only the conceptual ownership moved. The proper physical home (a `src/shared/` or `src/components/detection/` directory) is deferred to Step 2.5 / Step 4 / Step 8.
|
||||
|
||||
## Module Inventory
|
||||
|
||||
| Path | Module Doc |
|
||||
|------|------------|
|
||||
| `src/types/index.ts` | `_docs/02_document/modules/src__types__index.md` |
|
||||
| `src/hooks/useDebounce.ts` | `_docs/02_document/modules/src__hooks__useDebounce.md` |
|
||||
| `src/hooks/useResizablePanel.ts` | `_docs/02_document/modules/src__hooks__useResizablePanel.md` |
|
||||
| `src/i18n/i18n.ts` | `_docs/02_document/modules/src__i18n__i18n.md` |
|
||||
@@ -0,0 +1,78 @@
|
||||
# 01 — API Transport
|
||||
|
||||
## 1. High-Level Overview
|
||||
|
||||
**Purpose**: Thin wrappers over the browser's `fetch` and `EventSource` that every feature uses to talk to the suite backend. Sole owners of cookie / bearer / refresh-token plumbing on the wire.
|
||||
|
||||
**Architectural Pattern**: HTTP/SSE facade. No service-specific clients — every feature passes string URLs directly.
|
||||
|
||||
**Upstream dependencies**: `00_foundation` (types).
|
||||
|
||||
**Downstream consumers**: `02_auth`, `03_shared-ui` (FlightContext, DetectionClasses), `05_flights`, `06_annotations`, `07_dataset`, `08_admin`, `09_settings`.
|
||||
|
||||
## 2. Internal Interfaces
|
||||
|
||||
### `src/api/client.ts`
|
||||
|
||||
| Export | Signature | Notes |
|
||||
|--------|-----------|-------|
|
||||
| `api.get<T>(url, opts?): Promise<T>` | thin `fetch` wrapper | Adds `credentials: 'include'`, parses JSON, throws on non-2xx |
|
||||
| `api.post<T>(url, body?, opts?): Promise<T>` | same | |
|
||||
| `api.put<T>(url, body?, opts?): Promise<T>` | same | |
|
||||
| `api.del<T>(url, opts?): Promise<T>` | same | |
|
||||
| `ApiError` | error class | Thrown with `{ status, body }` on non-2xx |
|
||||
|
||||
### `src/api/sse.ts`
|
||||
|
||||
| Export | Signature | Notes |
|
||||
|--------|-----------|-------|
|
||||
| `subscribe<T>(url, onMessage, onError?): { close }` | factory | Creates `EventSource` with the **bearer token in the query string** (browser `EventSource` can't set headers). Returns a `close()` handle. |
|
||||
|
||||
## 3. External API Specification
|
||||
|
||||
This component does not *expose* an API; it consumes the suite's. The set of consumed endpoints (collected from feature module docs):
|
||||
|
||||
| Service | Path prefix (after nginx strip) | Used by |
|
||||
|---------|---------------------------------|---------|
|
||||
| `admin/` auth | `/api/admin/auth/{login,refresh,logout,me,...}` | `02_auth`, `08_admin` |
|
||||
| `flights/` | `/api/flights/...` | `03_shared-ui` (FlightContext), `05_flights` |
|
||||
| `annotations/` | `/api/annotations/...` | `06_annotations`, `07_dataset`, `08_admin` (read) |
|
||||
| `detect/` | `/api/detect/...` | `06_annotations` |
|
||||
| `loader/`, `resource/`, `gps-denied-*`, `autopilot/` | `/api/{loader,resource,gps-denied-desktop,gps-denied-onboard,autopilot}/...` | various features |
|
||||
|
||||
**No service-specific client modules exist**. URL strings are inlined at every call site (testability finding from autodev Step 4).
|
||||
|
||||
## 5. Implementation Details
|
||||
|
||||
| Concern | Behavior |
|
||||
|---------|----------|
|
||||
| Auth bootstrap | `client.ts` does NOT auto-attach `credentials: 'include'` on the very first call from `AuthContext` startup — finding B3 (`src__auth__AuthContext.md`). Cookie-based session bootstrap therefore fails on first refresh. **PRIORITY for Step 4.** |
|
||||
| Refresh-token rotation | Server rotates access tokens via `X-Refresh-Token` header. `client.ts` handles refresh on 401 for fetch; `sse.ts` does **NOT** — `EventSource` holds the bearer captured at create time and dies after rotation (finding in `src__api__sse.md`). |
|
||||
| Timeouts | None — no `AbortController` wired up. Long requests (e.g., dataset bulk export) can hang indefinitely. Step 4. |
|
||||
| Error reporting | `ApiError` thrown to caller. Most features `catch` and call `alert()` or `console.error` silently — uneven across features. |
|
||||
|
||||
**State Management**: Module-level only — `sse.ts` keeps no registry; each subscription is independent.
|
||||
|
||||
**Key Dependencies**: native `fetch`, native `EventSource`. No third-party HTTP library.
|
||||
|
||||
## 7. Caveats & Edge Cases
|
||||
|
||||
- **No timeout / cancellation**. (Step 4.)
|
||||
- **Bearer in SSE query string**. Accepted trade-off; document in `security_approach.md` (Step 6).
|
||||
- **No reconnect-on-token-rotate** for SSE consumers — every feature that uses SSE will silently stop receiving events after the first refresh (Step 8 hardening).
|
||||
- **No service-specific clients** → URL strings duplicated across features. Risk of typos surfacing as 404s only at runtime (Step 4).
|
||||
|
||||
## 8. Dependency Graph
|
||||
|
||||
**Must be implemented after**: `00_foundation`.
|
||||
|
||||
**Can be implemented in parallel with**: `00_foundation` (it has no internal deps beyond types).
|
||||
|
||||
**Blocks**: `02_auth`, every feature page, `03_shared-ui` (FlightContext, DetectionClasses).
|
||||
|
||||
## Module Inventory
|
||||
|
||||
| Path | Module Doc |
|
||||
|------|------------|
|
||||
| `src/api/client.ts` | `_docs/02_document/modules/src__api__client.md` |
|
||||
| `src/api/sse.ts` | `_docs/02_document/modules/src__api__sse.md` |
|
||||
@@ -0,0 +1,84 @@
|
||||
# 02 — Auth
|
||||
|
||||
## 1. High-Level Overview
|
||||
|
||||
**Purpose**: Authentication state, login/logout/refresh plumbing, and the `<ProtectedRoute>` gate that wraps every non-public route.
|
||||
|
||||
**Architectural Pattern**: React Context provider + route-guard component.
|
||||
|
||||
**Upstream dependencies**: `00_foundation` (types), `01_api-transport`.
|
||||
|
||||
**Downstream consumers**: `04_login`, `10_app-shell`, every authenticated page (indirectly via the route gate).
|
||||
|
||||
## 2. Internal Interfaces
|
||||
|
||||
### `src/auth/AuthContext.tsx`
|
||||
|
||||
| Export | Signature | Notes |
|
||||
|--------|-----------|-------|
|
||||
| `AuthProvider({ children })` | React component | Wraps the app below `BrowserRouter`. Bootstraps via `GET /api/admin/auth/refresh` on mount. |
|
||||
| `useAuth(): AuthContextValue` | hook | Read-only access to `{ user, permissions, login, logout, refresh, loading }`. |
|
||||
|
||||
**`AuthContextValue`** (output DTO):
|
||||
|
||||
```
|
||||
user: User | null
|
||||
permissions: Permission[] ← from server, used by route guards & UI
|
||||
loading: boolean ← true during initial bootstrap and active refresh
|
||||
login(c): Promise<User> ← POST /api/admin/auth/login
|
||||
logout(): Promise<void> ← POST /api/admin/auth/logout
|
||||
refresh(): Promise<void> ← POST /api/admin/auth/refresh
|
||||
```
|
||||
|
||||
### `src/auth/ProtectedRoute.tsx`
|
||||
|
||||
| Export | Signature | Notes |
|
||||
|--------|-----------|-------|
|
||||
| `ProtectedRoute({ children, requirePermission? })` | React component | Renders children if authenticated; otherwise navigates to `/login`. Optional `requirePermission` is checked against `useAuth().permissions` and renders a 403 placeholder on miss. |
|
||||
|
||||
## 3. External API Specification
|
||||
|
||||
Consumes only — does not expose. Endpoint set (from `_docs/02_document/modules/src__auth__AuthContext.md`):
|
||||
|
||||
| Method | Path | Auth | When |
|
||||
|--------|------|------|------|
|
||||
| POST | `/api/admin/auth/login` | public | Login form submit |
|
||||
| POST | `/api/admin/auth/refresh` | cookie | Bootstrap + on 401 retry |
|
||||
| POST | `/api/admin/auth/logout` | cookie | Header → Logout |
|
||||
| GET | `/api/admin/auth/me` | cookie | (post-login profile fetch, if implemented) |
|
||||
|
||||
## 5. Implementation Details
|
||||
|
||||
**State Management**: Single React context. Token lives in an HTTP-only cookie (server-managed); the React state holds only the parsed user + permissions. No `localStorage`.
|
||||
|
||||
**Bootstrap sequence**:
|
||||
1. Mount → set `loading: true`.
|
||||
2. `api.post('/api/admin/auth/refresh')` to ask the server "do I have a valid session?".
|
||||
3. On 200 → store user + permissions, `loading: false`.
|
||||
4. On 4xx → user stays `null`, `loading: false`. `ProtectedRoute` then redirects.
|
||||
|
||||
> **PRIORITY finding (B3, copied from state.json)**: the bootstrap call inside `AuthContext.tsx` does not pass `credentials: 'include'` consistently — the cookie is therefore not sent on the very first request and bootstrap silently fails on a fresh page load. Confirmed real bug; Step 4 fix.
|
||||
|
||||
**Spinner UX**: `ProtectedRoute` renders a centered spinner during `loading`. The spinner has **no** `role="status"` / no accessible label / no timeout. (Findings B4, joint with Step 4 client.ts timeout flag.)
|
||||
|
||||
## 7. Caveats & Edge Cases
|
||||
|
||||
- **Bootstrap missing `credentials: 'include'`** → users land on `/login` even with a valid cookie session. PRIORITY Step 4.
|
||||
- **Spinner accessibility** — Step 4.
|
||||
- **Token-rotation interaction with SSE** — see `01_api-transport`. Auth refresh works for fetch but breaks every active EventSource.
|
||||
- **No idle-timeout / inactivity logout** — server-side concern; UI tolerates whatever the server enforces.
|
||||
|
||||
## 8. Dependency Graph
|
||||
|
||||
**Must be implemented after**: `00_foundation`, `01_api-transport`.
|
||||
|
||||
**Can be implemented in parallel with**: nothing inside this workspace's gate path.
|
||||
|
||||
**Blocks**: `03_shared-ui` (Header reads `useAuth`), `04_login`, `10_app-shell`, indirectly every authenticated page.
|
||||
|
||||
## Module Inventory
|
||||
|
||||
| Path | Module Doc |
|
||||
|------|------------|
|
||||
| `src/auth/AuthContext.tsx` | `_docs/02_document/modules/src__auth__AuthContext.md` |
|
||||
| `src/auth/ProtectedRoute.tsx` | `_docs/02_document/modules/src__auth__ProtectedRoute.md` |
|
||||
@@ -0,0 +1,90 @@
|
||||
# 03 — Shared UI & Cross-Cutting Context
|
||||
|
||||
## 1. High-Level Overview
|
||||
|
||||
**Purpose**: Reusable presentational components and the **flight selection context** that every authenticated page reads from. This is the "page chrome + flight scope" layer — the navbar, the help modal, the confirmation dialog, the detection-class picker, and the `FlightContext` provider that holds "which flight the user is currently working in".
|
||||
|
||||
**Architectural Pattern**: Mix of presentational components + one cross-cutting Context provider. `FlightContext` lives in `components/` (not `features/flights/`) because it is read by every feature page (Annotations, Dataset, Admin, Settings, Header).
|
||||
|
||||
**Upstream dependencies**: `00_foundation` (types), `01_api-transport`, `02_auth` (Header reads `useAuth`), `11_class-colors` (DetectionClasses fallback colors / names).
|
||||
|
||||
**Downstream consumers**: `10_app-shell` (mounts Header + FlightProvider), every feature page (consumes `useFlight()` and uses ConfirmDialog), `06_annotations` and `07_dataset` (use DetectionClasses + ConfirmDialog).
|
||||
|
||||
## 2. Internal Interfaces
|
||||
|
||||
### `src/components/Header.tsx`
|
||||
|
||||
| Export | Notes |
|
||||
|--------|-------|
|
||||
| `Header()` | Top bar: app logo, page nav links (`/flights`, `/annotations`, `/dataset`, `/admin`, `/settings`), flight-picker dropdown, language switch, user menu (logout, help). Mobile bottom-nav variant rendered conditionally (lines 113–129 per state.json correction). |
|
||||
|
||||
### `src/components/HelpModal.tsx`
|
||||
|
||||
| Export | Notes |
|
||||
|--------|-------|
|
||||
| `HelpModal({ open, onClose })` | Renders an in-page modal with `GUIDELINES` (currently a hardcoded string — finding: should be in i18n bundle). Esc does NOT close (inconsistent with `ConfirmDialog`). |
|
||||
|
||||
### `src/components/ConfirmDialog.tsx`
|
||||
|
||||
| Export | Notes |
|
||||
|--------|-------|
|
||||
| `ConfirmDialog({ open, title, message, confirmLabel, onConfirm, onCancel })` | Reusable confirm modal. Esc closes. **Missing `aria-modal` and `role="dialog"`** — finding flagged for Step 4 vs `ui_design/README.md` confirmation-dialogs spec. |
|
||||
|
||||
### `src/components/DetectionClasses.tsx`
|
||||
|
||||
| Export | Notes |
|
||||
|--------|-------|
|
||||
| `DetectionClasses({ value, onChange, ... })` | Detection-class picker grid. Loads classes from `GET /api/annotations/classes`. Number-key shortcuts 1–9 select `classes[idx + photoMode]` — ordering against the annotations service contract is unverified (Step 4). Imports from `11_class-colors` for fallback color and name (current physical path: `src/features/annotations/classColors.ts` — file location is misplaced; see `11_class-colors` §7 for refactor target). |
|
||||
|
||||
### `src/components/FlightContext.tsx`
|
||||
|
||||
| Export | Notes |
|
||||
|--------|-------|
|
||||
| `FlightProvider({ children })` | Loads flight list (paged, **`pageSize=1000` ceiling hardcoded** — finding B3) on mount. |
|
||||
| `useFlight(): FlightContextValue` | hook returning `{ flights, selectedFlight, selectFlight, refresh }`. `selectFlight` is **fire-and-forget** PUT to `/api/flights/select` — no error UI. |
|
||||
|
||||
## 5. Implementation Details
|
||||
|
||||
**State Management**:
|
||||
- Local component state for modals (open/closed).
|
||||
- One Context provider (`FlightProvider`) holding the cached flight list and current selection.
|
||||
- No global event bus.
|
||||
|
||||
**Routing awareness**: `Header` reads `useLocation()` to highlight the active link. `FlightProvider` does **not** persist selection across reloads.
|
||||
|
||||
**Accessibility** (cross-cutting findings, Step 4):
|
||||
- `ConfirmDialog` — no `aria-modal` / `role="dialog"` / focus trap.
|
||||
- `Header` flight dropdown — no `role="combobox"`, no `aria-expanded`, no Esc-to-close, no focus trap; outside-click handler always attached.
|
||||
- `HelpModal` — Esc does NOT close.
|
||||
|
||||
**Key Dependencies**: `react-router-dom` 7 (Header), `react-i18next`.
|
||||
|
||||
## 6. Extensions and Helpers
|
||||
|
||||
Class color / fallback name / PhotoMode suffix logic lives in `11_class-colors`. This component depends on it; it is no longer treated as a cross-layer leak — the *physical* file is misplaced (still in `src/features/annotations/`) but the *logical* dependency is a normal shared-layer dependency.
|
||||
|
||||
## 7. Caveats & Edge Cases
|
||||
|
||||
- **`classColors.ts` physical location** is `src/features/annotations/` even though it is logically a `11_class-colors` shared module. Step 4 testability candidate (file move) — does not break this component's dependency graph.
|
||||
- **`FlightContext.pageSize=1000`** — silent ceiling; >1000 flights are invisible.
|
||||
- **`selectFlight` fire-and-forget PUT** — server failures are invisible to the UI.
|
||||
- **`Header` flight dropdown a11y gaps** — Step 4 + Step 8.
|
||||
- **`HelpModal` GUIDELINES hardcoded** — Step 4 i18n.
|
||||
|
||||
## 8. Dependency Graph
|
||||
|
||||
**Must be implemented after**: `00_foundation`, `01_api-transport`, `02_auth`, `11_class-colors`.
|
||||
|
||||
**Can be implemented in parallel with**: nothing critical inside the workspace.
|
||||
|
||||
**Blocks**: `10_app-shell`, every feature page (they import `ConfirmDialog`, `useFlight`, and read `Header` from the shell).
|
||||
|
||||
## Module Inventory
|
||||
|
||||
| Path | Module Doc |
|
||||
|------|------------|
|
||||
| `src/components/Header.tsx` | `_docs/02_document/modules/src__components__Header.md` |
|
||||
| `src/components/HelpModal.tsx` | `_docs/02_document/modules/src__components__HelpModal.md` |
|
||||
| `src/components/ConfirmDialog.tsx` | `_docs/02_document/modules/src__components__ConfirmDialog.md` |
|
||||
| `src/components/DetectionClasses.tsx` | `_docs/02_document/modules/src__components__DetectionClasses.md` |
|
||||
| `src/components/FlightContext.tsx` | `_docs/02_document/modules/src__components__FlightContext.md` |
|
||||
@@ -0,0 +1,48 @@
|
||||
# 04 — Login
|
||||
|
||||
## 1. High-Level Overview
|
||||
|
||||
**Purpose**: The single public route `/login`. Renders the credential form and triggers `useAuth().login(...)`.
|
||||
|
||||
**Architectural Pattern**: Single-page feature module with one entry component.
|
||||
|
||||
**Upstream dependencies**: `02_auth` (`useAuth().login`).
|
||||
|
||||
**Downstream consumers**: `10_app-shell` (routed at `/login`).
|
||||
|
||||
## 2. Internal Interfaces
|
||||
|
||||
| Export | Notes |
|
||||
|--------|-------|
|
||||
| `LoginPage()` | Form: username + password + submit. Calls `useAuth().login({ username, password })` and on success navigates to `/flights`. Error state shown inline. |
|
||||
|
||||
## 5. Implementation Details
|
||||
|
||||
- "Theatrical" unlock animation (`runUnlockSequence`, 4 × 600 ms) plays on success before navigation. Documented in Step 5 solution.md as a UX choice, not a bug.
|
||||
- No "remember me" / persistent-session toggle.
|
||||
- No SSO integration.
|
||||
- No password-strength feedback or recovery link.
|
||||
|
||||
**State Management**: Local component state only.
|
||||
|
||||
**Key Dependencies**: `react-router-dom` (`useNavigate`), `react-i18next`.
|
||||
|
||||
## 7. Caveats & Edge Cases
|
||||
|
||||
- **2.4 s artificial delay** post-login (Step 5 doc note).
|
||||
- **No CSRF token** in the login POST body — server is expected to validate the same-site cookie pattern; document in `security_approach.md` (Step 6).
|
||||
- **No rate-limit feedback** — server returns `429` with no specific UI handling beyond a generic `alert`.
|
||||
|
||||
## 8. Dependency Graph
|
||||
|
||||
**Must be implemented after**: `02_auth`.
|
||||
|
||||
**Can be implemented in parallel with**: every feature page (`05_flights` … `09_settings`).
|
||||
|
||||
**Blocks**: nothing.
|
||||
|
||||
## Module Inventory
|
||||
|
||||
| Path | Module Doc |
|
||||
|------|------------|
|
||||
| `src/features/login/LoginPage.tsx` | `_docs/02_document/modules/src__features__login__LoginPage.md` |
|
||||
@@ -0,0 +1,189 @@
|
||||
# 05 — Flights & Mission Planning
|
||||
|
||||
## 1. High-Level Overview
|
||||
|
||||
**Purpose**: One logical component covering everything mission/flight related — flight CRUD, waypoint editing, altitude profile, wind/battery calc, raw mission JSON I/O, and **GPS-Denied** operations (incl. an end-to-end **test mode** that simulates a real flight from a tlog + video pair). It is currently **physically split** across two codebases that the project intends to converge.
|
||||
|
||||
**Architectural Pattern**: Page composition (`FlightsPage`) wiring three sibling panels — `FlightListSidebar`, `FlightParamsPanel`, `FlightMap` — plus modals (`AltitudeDialog`, `JsonEditorDialog`) and a GPS-Denied sub-page.
|
||||
|
||||
**Implementation status — two trees, one component**:
|
||||
|
||||
| Tree | What it is | Deployed? |
|
||||
|------|------------|-----------|
|
||||
| `src/features/flights/` (15 modules, React 19 + Tailwind) | The **target** implementation. Mostly a mechanical port-in-progress of the tree below. | **Yes** — Dockerfile builds `src/` only. |
|
||||
| `mission-planner/` (37 modules, React 18 + MUI 5) | The **port source / reference**. The richer, more battle-tested mission planner that the new SPA is being adapted from. | **No.** Disjoint dependency island. Deletion candidate after parity. |
|
||||
|
||||
The two trees are intentionally disjoint at the file level (no cross-imports — `00_discovery.md` §1) but they are **one component in the design**: same domain, same data model, same intent. Findings, port plan, and architecture decisions are tracked in this single component spec. Mission-planner files are listed in §"Module Inventory" below alongside the new SPA files; per-finding origin is preserved.
|
||||
|
||||
**Upstream dependencies** (target tree): `00_foundation`, `01_api-transport`, `03_shared-ui` (FlightContext, ConfirmDialog).
|
||||
|
||||
**Downstream consumers**: `10_app-shell` (routes `/flights` and the GPS-Denied sub-page).
|
||||
|
||||
## 2. Internal Interfaces
|
||||
|
||||
### Page entries (target tree, `src/features/flights/`)
|
||||
|
||||
| Export | Notes |
|
||||
|--------|-------|
|
||||
| `FlightsPage()` | Top-level route component for `/flights`. Uses `useFlight()`, fetches flight detail by id, exposes save/delete/duplicate. Wires `react-leaflet`, `leaflet-draw`, and `chart.js`. |
|
||||
| `GpsDeniedPage()` *(planned route, see §6)* | Sub-page for GPS-denied operations and test mode. Today a partial inline panel inside `FlightsPage` (finding #25) — slated to become its own route. |
|
||||
|
||||
### Internal modules — target tree (`src/features/flights/`)
|
||||
|
||||
| Module | Role |
|
||||
|--------|------|
|
||||
| `FlightsPage.tsx` | Orchestrator (route component) |
|
||||
| `FlightMap.tsx` | Leaflet map + draw control + waypoint markers + minimap |
|
||||
| `FlightListSidebar.tsx` | Left panel: flight list, search, new/duplicate/delete |
|
||||
| `FlightParamsPanel.tsx` | Right panel: name, aircraft, takeoff/landing, altitude chart, waypoint list, wind effect |
|
||||
| `WaypointList.tsx` | DnD-sortable waypoints (`@hello-pangea/dnd`) |
|
||||
| `AltitudeChart.tsx` | `chart.js` altitude profile |
|
||||
| `WindEffect.tsx` | Wind-vector visualisation |
|
||||
| `MiniMap.tsx` | Inline overview map |
|
||||
| `MapPoint.tsx` | Single waypoint marker |
|
||||
| `DrawControl.tsx` | `leaflet-draw` integration |
|
||||
| `AltitudeDialog.tsx` | Per-waypoint altitude/purpose modal |
|
||||
| `JsonEditorDialog.tsx` | Raw mission-JSON editor |
|
||||
| `flightPlanUtils.ts` | Distance / battery / weather computation helpers |
|
||||
| `mapIcons.ts` | Leaflet icon factory |
|
||||
| `types.ts` | Local feature types (waypoint shape, mission JSON shape) |
|
||||
|
||||
### Internal modules — port source (`mission-planner/`)
|
||||
|
||||
| Group | Modules | Role / what the target should learn |
|
||||
|-------|---------|-------------------------------------|
|
||||
| Entry | `main.tsx`, `App.tsx` (vestigial CRA stub) | Composition root + LanguageProvider. The CRA stub `App.tsx` is a deletion candidate post-port. |
|
||||
| Page composition | `flightPlanning/flightPlan.tsx`, `LeftBoard.tsx` | Canonical page shape (sidebar + map). |
|
||||
| Map | `flightPlanning/MapView.tsx` (cycle-paired with `MiniMap.tsx`), `MiniMap.tsx`, `DrawControl.tsx`, `MapPoint.tsx` | Reference Leaflet integration. **Cycle**: `MiniMap` imports the *named* helper `UpdateMapCenter` from `MapView`; `MapView` imports `MiniMap` as JSX child. Document the contract precisely if porting both at once. |
|
||||
| Panels | `flightPlanning/PointsList.tsx`, `AltitudeChart.tsx`, `AltitudeDialog.tsx`, `WindEffect.tsx`, `TotalDistance.tsx`, `JsonEditorDialog.tsx`, `LanguageSwitcher.tsx`, `Aircraft.ts` | Reference panel shapes. Several have richer behaviour than the current SPA siblings. |
|
||||
| Services | `services/calculateBatteryUsage.ts`, `AircraftService.ts`, `WeatherService.ts`, `calculateDistance.ts` | **Authoritative** battery / weather / distance logic. The target's `flightPlanUtils.ts` is currently an inferior port (silent errors, sequential `await`, hardcoded API key). |
|
||||
| i18n | `flightPlanning/LanguageContext.tsx`, `constants/translations.ts`, `constants/languages.ts` | Local translation pattern. The port should converge to `00_foundation/i18n` instead. |
|
||||
| Constants | `constants/{actionModes,maptypes,tileUrls,purposes}.ts` | Reference constant tables. |
|
||||
| Icons | `icons/{MapIcons,PointIcons,SidebarIcons,PhoneIcon}.tsx` | Reference icon factory. |
|
||||
| Utilities | `utils.ts`, `config.ts`, `types/index.ts` | Reference helpers + types. |
|
||||
| Test (vestigial) | `test/jsonImport.test.ts`, `setupTests.ts` | One Jest test that **cannot run today** (Jest not in `package.json`). Out of scope for the live SPA test plan. |
|
||||
|
||||
## 3. External API Specification
|
||||
|
||||
Endpoints consumed (target tree + planned for GPS-Denied):
|
||||
|
||||
| Method | Path | Purpose |
|
||||
|--------|------|---------|
|
||||
| GET | `/api/flights` | List + page (`pageSize=1000` ceiling from `FlightContext`) |
|
||||
| GET | `/api/flights/{id}` | Detail + waypoints |
|
||||
| POST | `/api/flights` | Create |
|
||||
| PUT | `/api/flights/{id}` | Update flight metadata |
|
||||
| DELETE | `/api/flights/{id}` | Delete |
|
||||
| POST/DELETE | `/api/flights/{id}/waypoints` | Add / remove waypoints |
|
||||
| POST | `/api/gps-denied-desktop/...` | GPS-denied desktop service (mission setup, plan upload) — partial today |
|
||||
| POST | `/api/gps-denied-onboard/...` | GPS-denied onboard service (frame + IMU consumer) — target of test-mode SITL output |
|
||||
| GET | OpenWeatherMap (third-party, **direct from browser**) | Wind/temperature lookup. **Will be proxied via suite** as part of Step 4 (hardcoded key removal). |
|
||||
|
||||
Concrete GPS-Denied endpoint shapes are **not yet finalised** in the suite spec — flagged for confirmation in autodev Step 3 (Test Spec) and Step 6 (Problem Extraction).
|
||||
|
||||
## 5. Implementation Details
|
||||
|
||||
**State Management**: Page-local React state for the active flight; `FlightContext` (in `03_shared-ui`) for the list of flights.
|
||||
|
||||
**Save model — current shape, target tree** (finding #19): on Save, the UI deletes all existing waypoints and POSTs each one again. N delete + M POST round-trips. **Lossy** — concurrent edits race; if a POST fails halfway through, the saved flight is left with truncated waypoints. The `mission-planner/` reference does this differently (single mission-JSON PUT) and is the recommended target shape.
|
||||
|
||||
**Waypoint POST shape mismatch (PRIORITY, finding #20)**: target tree sends `{name, latitude, longitude, order}`; suite spec wants `{Geopoint:{Lat,Lon,MGRS}, Source, Objective, OrderNum, Height}`. Strict server returns 400. Owner of fix: this component + `00_foundation/types/index.ts::Waypoint`.
|
||||
|
||||
**Battery / weather calc** (target tree `flightPlanUtils.ts`):
|
||||
- **Hardcoded OpenWeather API key in source** (`'335799082893fad97fa36118b131f919'`) — committed secret. Step 4 fix.
|
||||
- Sequential `await` per segment — perf trap on long missions.
|
||||
- Silent `try/catch` swallows weather errors.
|
||||
- Mixes km / m altitudes; ambiguous battery-capacity unit (Wh vs Ws).
|
||||
- Port source (`mission-planner/services/`) has a cleaner, more correct implementation — use it as the reference.
|
||||
|
||||
**Map / icons**:
|
||||
- Target tree `mapIcons.ts::defaultIcon` CDN URL pinned to `leaflet@1.7.1` while `package.json` has 1.9.4.
|
||||
- `MiniMap` map-tile licence attribution missing in some configurations.
|
||||
|
||||
**Modal a11y** (`AltitudeDialog`, `JsonEditorDialog`) — no `aria-modal` / `role="dialog"` / focus trap. Step 4. Same gap on the port-source counterparts.
|
||||
|
||||
**Tech-stack divergence between the two trees** (carry into the port plan):
|
||||
|
||||
| Concern | `mission-planner/` | `src/features/flights/` (target) |
|
||||
|---------|-------------------|----------------------------------|
|
||||
| React | 18 | 19 |
|
||||
| UI library | MUI 5 | Tailwind 4 + custom `az-*` tokens |
|
||||
| i18n | local `LanguageContext` | `react-i18next` (Foundation) |
|
||||
| `react-leaflet` | 4.2 | 5 |
|
||||
| `@hello-pangea/dnd` | 16 | 18 |
|
||||
|
||||
**Key Dependencies** (target tree): `leaflet` 1.9, `react-leaflet` 5, `leaflet-draw`, `leaflet-polylinedecorator`, `chart.js` 4, `@hello-pangea/dnd` 18.
|
||||
|
||||
## 6. GPS-Denied sub-feature
|
||||
|
||||
Today a **partial** UI panel exists inside `FlightsPage` (target tree finding #25). The component design promotes this to a first-class sub-page with two tabs.
|
||||
|
||||
### 6a. Operations tab — current intent (already present, partial)
|
||||
|
||||
- Upload / select a mission plan.
|
||||
- Configure GPS-denied desktop parameters.
|
||||
- Hand off to onboard.
|
||||
|
||||
The exact endpoints are partially wired today and tracked under "External API Specification" above.
|
||||
|
||||
### 6b. **Test mode** — new subitem (per `_docs/how_to_test.md`)
|
||||
|
||||
> **Source of truth**: `_docs/how_to_test.md`. Architecture Vision artifact (`/document` Step 4.5) MUST surface this verbatim.
|
||||
|
||||
**Goal**: enable end-to-end testing of the GPS-denied onboard system **without a real flight**, using a recorded telemetry log + video pair.
|
||||
|
||||
**User journey**:
|
||||
|
||||
1. **Upload tlog file** (MAVLink telemetry log).
|
||||
2. **Upload video** synced with the tlog (the two are **usually not** time-aligned at the file level — the system aligns them).
|
||||
3. The system:
|
||||
- Extracts **timestamps, IMU, and GPS** samples from the tlog.
|
||||
- **Auto-syncs** video to tlog by detecting the take-off moment in the IMU stream and matching it to the corresponding frame in the video. Most test sessions are quadcopter "ground-to-ground" flights; the start-on-ground signature in the IMU is the alignment anchor.
|
||||
- Spawns a **SITL** (Software-In-The-Loop) instance.
|
||||
- **Feeds IMU samples + video frames** into the GPS-denied **onboard** system in lockstep.
|
||||
4. The user observes the onboard system's outputs (position estimate, drift, GPS-recovery moments) on the same map / chart components used for live flights — reusing `FlightMap`, `AltitudeChart`, and a results overlay.
|
||||
|
||||
**Scope notes for downstream skills**:
|
||||
|
||||
- This sub-feature is **not implemented today**. It is a planned addition that this component will own.
|
||||
- The tlog parser, IMU/video sync, and SITL controller are **suite-side services**; this component contributes the upload UI, the run controller, and the result overlay.
|
||||
- Test-mode I/O endpoints will live under `/api/gps-denied-desktop/test/...` (proposed; confirm in `autodev` Step 3 / Step 6).
|
||||
- The existing `/document` Step 1 finding "GPS-Denied panel partial" remains valid for the Operations tab and is broadened to track the Test mode tab as well.
|
||||
|
||||
## 7. Caveats & Edge Cases
|
||||
|
||||
26 numbered findings consolidated in `src__features__flights.md`. Highest-priority Step 4 items:
|
||||
|
||||
1. **Hardcoded OpenWeather API key** — rotate + remove + proxy via suite.
|
||||
2. **Waypoint POST shape mismatch + delete-then-recreate save model** — likely-broken on a strict suite server. Reference port-source's PUT-mission-JSON model.
|
||||
3. **`flightPlanUtils.ts` units / silent errors / sequential awaits.** Replace with port-source services.
|
||||
4. **Modal a11y** + **flight dropdown a11y** (overlap with Shared UI).
|
||||
5. **Leaflet 1.7.1 CDN URL drift.**
|
||||
|
||||
Mission-planner-side caveats (port-only):
|
||||
|
||||
- `mission-planner/src/test/jsonImport.test.ts` cannot run (Jest absent). **Do not** add Jest just for this one test.
|
||||
- `mission-planner/src/App.tsx` is a CRA stub. Delete only after parity.
|
||||
- `MapView.tsx ↔ MiniMap.tsx` named-handle cycle. Document precisely.
|
||||
|
||||
GPS-Denied-side caveats:
|
||||
|
||||
- Operations tab is partial (finding #25 unchanged).
|
||||
- **Test mode** is unimplemented — no risk of regression today, but the design must accommodate the eventual test-mode entry point in routing and in `FlightContext`.
|
||||
|
||||
## 8. Dependency Graph
|
||||
|
||||
**Must be implemented after**: `00_foundation`, `01_api-transport`, `03_shared-ui`.
|
||||
|
||||
**Can be implemented in parallel with**: every other feature page.
|
||||
|
||||
**Blocks**: `10_app-shell` (routes to `/flights` as default authenticated landing; will also host the GPS-Denied sub-route).
|
||||
|
||||
**Internal port order** (within this component): port `services/` (battery/weather/distance) → port `flightPlanning/` panels (`AltitudeChart`, `WindEffect`, `PointsList`) → port `MapView/MiniMap` cycle group → fold the `mission-planner/` tree out as the target reaches parity.
|
||||
|
||||
## Module Inventory
|
||||
|
||||
| Tree | Module Doc |
|
||||
|------|------------|
|
||||
| `src/features/flights/*` (15 modules) | `_docs/02_document/modules/src__features__flights.md` |
|
||||
| `mission-planner/*` (37 modules) | `_docs/02_document/modules/mission-planner.md` |
|
||||
@@ -0,0 +1,132 @@
|
||||
# 06 — Annotations
|
||||
|
||||
## 1. High-Level Overview
|
||||
|
||||
**Purpose**: Image / video annotation editor — bounding boxes, classes, affiliation, combat readiness, AI detection (sync + async via SSE), and the media-list browser scoped to the active flight.
|
||||
|
||||
**Architectural Pattern**: Page composition (`AnnotationsPage`) wiring `MediaList` + `VideoPlayer` (or static image) + `CanvasEditor` + `AnnotationsSidebar` + `DetectionClasses`.
|
||||
|
||||
**Upstream dependencies**: `00_foundation`, `01_api-transport`, `03_shared-ui` (FlightContext, ConfirmDialog, DetectionClasses), `11_class-colors`.
|
||||
|
||||
**Downstream consumers**: `10_app-shell` (routed at `/annotations`); `07_dataset` imports `CanvasEditor` directly (cross-feature edge — see baseline scan).
|
||||
|
||||
## 2. Internal Interfaces
|
||||
|
||||
| Export | Notes |
|
||||
|--------|-------|
|
||||
| `AnnotationsPage()` | Top-level route component. Manages selected media, panel widths (via `useResizablePanel`), and the open annotation under edit. |
|
||||
|
||||
Internal modules:
|
||||
|
||||
| Module | Role |
|
||||
|--------|------|
|
||||
| `AnnotationsPage.tsx` | Orchestrator (route component) |
|
||||
| `MediaList.tsx` | Left panel: thumbnail browser + search/filter; consumes `useFlight()` |
|
||||
| `VideoPlayer.tsx` | HTML5 video + frame seek + per-frame annotation overlays |
|
||||
| `CanvasEditor.tsx` | Bounding-box draw / move / resize layer (also used by `07_dataset`) |
|
||||
| `AnnotationsSidebar.tsx` | Right panel: annotation list, AI-detect controls |
|
||||
|
||||
## 3. External API Specification
|
||||
|
||||
| Method | Path | Purpose |
|
||||
|--------|------|---------|
|
||||
| GET | `/api/annotations/media?flightId=...` | List media |
|
||||
| GET | `/api/annotations/media/{id}` | Media metadata |
|
||||
| GET | `/api/annotations/media/{id}/annotations` | Bbox list |
|
||||
| POST | `/api/annotations` | Create one |
|
||||
| PUT | `/api/annotations/{id}` | Update one |
|
||||
| DELETE | `/api/annotations/{id}` | Delete one |
|
||||
| POST | `/api/detect/{mediaId}` | Sync image AI-detect |
|
||||
| POST | `/api/detect/video/{mediaId}` | Async video AI-detect — should send `X-Refresh-Token`, currently does not (finding #30) |
|
||||
| GET | `/api/detect/classes` | Detection class list |
|
||||
| SSE | `/api/detect/stream/{jobId}` | Async detect progress (subscribed via `01_api-transport/sse.ts`) — but **`AnnotationsPage` does not subscribe today** (finding #10) |
|
||||
|
||||
## 5. Implementation Details
|
||||
|
||||
**State Management**: Page-local for the open annotation + selection; `useResizablePanel` for panel widths (not persisted — finding #11).
|
||||
|
||||
**Time-window math** (`CanvasEditor`, finding #6, post-cross-check correction): implementation is symmetric ±200 ms (400 ms total). Spec wants asymmetric 50 ms before + 150 ms after (200 ms total). UI is 4× too wide and not centred per spec.
|
||||
|
||||
**Gradient cap** (finding #9, post-cross-check correction): annotation row alpha gradient maxes at `0x28 = 16 %` opacity due to a `* 40` literal that is decimal, not hex. Wireframe demands `0x40 = 25 %`.
|
||||
|
||||
**`handleSave` body** (finding #32): currently `{ classNum, x, y, w, h, time }`. Suite spec requires `{ classNum, x, y, w, h, videoTime, Source, WaypointId }`. Owner: this component, also touches Foundation types.
|
||||
|
||||
**handleDownload (image export)** — risks tainting the `<canvas>` if the video is from a `blob:` URL with cross-origin frames; finding #12.
|
||||
|
||||
**Missing affordances** (findings #13–18):
|
||||
- Affiliation icons absent (spec mandates per-bbox).
|
||||
- Combat-readiness indicator absent.
|
||||
- `AFFILIATION_COLORS` defined but unused (dead).
|
||||
- No keyboard shortcuts R / V / PageUp / PageDown.
|
||||
- No camera-config side panel.
|
||||
- No tile zoom indicator for `splitTile` media.
|
||||
|
||||
**AI-detect controls** (`AnnotationsSidebar`, findings #21–23):
|
||||
- Async progress is not streamed (no SSE subscription).
|
||||
- Errors silently `console.error`'d, no UI feedback.
|
||||
|
||||
**MediaList** (findings #24–26):
|
||||
- Uses `alert()` for "no media" state.
|
||||
- `blob:` local previews ignore the search filter.
|
||||
- No virtualisation — long flights render all thumbnails.
|
||||
|
||||
**Cross-feature leak**: `07_dataset` imports `CanvasEditor` directly (caveat from `00_discovery.md` §8). The right home is a shared `components/canvas/` directory; not done in this step.
|
||||
|
||||
**Enum drift cross-link**: findings #27–34 capture the enum drift (`AnnotationStatus`, `Affiliation`, `CombatReadiness`, `MediaStatus`, `AnnotationSource`) — wire payloads using current `src/types/index.ts` values are wrong. Owner of fix: `00_foundation/types/index.ts`. Two findings (#31, #33) are **NO UI CHANGE — PARENT-DOC FIX** and have already been applied to the suite docs (state.json 02:18Z note); the rest are Step 4.
|
||||
|
||||
**Key Dependencies**: HTML5 `<canvas>` + `<video>`, `react-dropzone` (upload).
|
||||
|
||||
## 6b. WPF gap analysis (vs `suite/annotations-research`)
|
||||
|
||||
> Output of the Step 2 BLOCKING-gate cross-check (2026-05-10). The legacy WPF `Azaion.Annotator` window is the design source for this component; the file `_docs/legacy/wpf-era.md` §10 calls out which features must be ported. The list below is the delta — features that exist in the WPF source and are NOT yet present in the React port. Each entry names the WPF anchor and the React owner; numeric findings (#) come from `_docs/02_document/modules/src__features__annotations.md`.
|
||||
|
||||
| WPF feature | Anchor in `annotations-research` | React status | Owner |
|
||||
|------------|----------------------------------|--------------|-------|
|
||||
| **Time window asymmetric 50/150 ms** (interval-tree `_thresholdBefore=50ms`, `_thresholdAfter=150ms`) | `Azaion.Annotator/Annotator.xaml.cs:53-54` | Wrong (symmetric ±200 ms). Finding #6. | `CanvasEditor.tsx` |
|
||||
| **Keyboard shortcut `[Space]` = pause / resume** | `Annotator.xaml` `PauseClick` button tooltip "[Пробіл]" | Missing (no global key listener) | `VideoPlayer` + `AnnotationsPage` |
|
||||
| **Keyboard `[Left]` / `[Right]` = prev / next frame; `+Ctrl` = ±5 sec** | `PreviousFrameClick` / `NextFrameClick` tooltips | Missing | `VideoPlayer` |
|
||||
| **Keyboard `[Enter]` = save annotations and continue** | `SaveAnnotationsClick` tooltip "[Ентер]" | Missing. Finding #16 broadens this. | `AnnotationsPage` |
|
||||
| **Keyboard `[Del]` = delete selected annotations (with confirm)** | `Annotator.xaml.cs:204-222` (`DgAnnotations.KeyUp`) | Missing | `AnnotationsSidebar` + `ConfirmDialog` |
|
||||
| **Keyboard `[X]` = delete ALL annotations** | `RemoveAllClick` tooltip "[X]" | Missing | `AnnotationsPage` |
|
||||
| **Keyboard `[M]` = mute volume; also toggles GPS panel (context-sensitive)** | `TurnOffVolume` + `SwitchGpsPanel` tooltips both "[M]" | Missing | `VideoPlayer` |
|
||||
| **Keyboard `[R]` = AI Detect** | `AIDetectBtn_OnClick` tooltip "[R]" | Missing. Finding #16. | `AnnotationsSidebar` |
|
||||
| **Keyboard `[Ctrl+click]` = multi-select; `[Ctrl+drag]` = pan; `[Ctrl+wheel]` = zoom** | `Azaion.Common/Controls/CanvasEditor.cs` | Pan / zoom missing (finding listed); multi-select unverified | `CanvasEditor.tsx` |
|
||||
| **Volume slider** (`UpdatableProgressBar Volume`, range 0–100, mediator `VolumeChangedEvent`) | `Annotator.xaml:500-507` | Missing entirely | `VideoPlayer` |
|
||||
| **Status bar — clock `mm:ss / mm:ss`** | `Annotator.xaml.cs:237` `StatusClock.Text = ...` | Missing | `VideoPlayer` (display) + `AnnotationsPage` (slot) |
|
||||
| **Status bar — contextual help text** (`HelpTextEnum.{Initial, PlayVideo, PauseForAnnotations, AnnotationHelp}`, `BlinkHelp` flicker pattern) | `Azaion.Annotator/HelpTexts.cs` + `Annotator.xaml.cs:144-158` | Missing | `AnnotationsPage` (or a global toast) |
|
||||
| **Status bar — generic status text** (`StatusBarItem Status`) | `Annotator.xaml:653` | Missing | shared chrome (could live in App Shell or `AnnotationsPage`) |
|
||||
| **Sound Detections feature** ("Show objects detected by audio analysis", own button, distinct from AI Detect) | `Annotator.xaml:565-617` `SoundDetections` button + handler | **Entirely missing**. Not mentioned anywhere in `_docs/legacy/wpf-era.md §10` "What survived" — needs a user decision: port or drop. | TBD |
|
||||
| **Drone Maintenance feature** ("Аналіз стану БПЛА" — UAV state analysis, [K]) | `Annotator.xaml:618-630` `RunDroneMaintenance` button + handler | **Entirely missing**. Same status as above — port-or-drop decision required. | TBD |
|
||||
| **Resizable panel widths persisted** (left + right panels) | `Annotator.xaml.cs:243-252` `SaveUserSettings` writes `UIConfig.LeftPanelWidth` / `RightPanelWidth` | Missing — `useResizablePanel` does not persist (finding #11). | `00_foundation/useResizablePanel` + Settings backend |
|
||||
| **Camera config side panel** (altitude / focal / sensor → GSD) | `Annotator.xaml:196-203` `CameraConfigControl` | Missing (finding #17) | `AnnotationsPage` |
|
||||
| **Affiliation icons + Combat readiness indicator on bbox label** | `Azaion.Common/DTO/AffiliationEnum`, `_docs/ui_design/README.md` | Missing (findings #14–15) | `CanvasEditor.tsx` + types |
|
||||
| **Annotation list seek + zoom on double-click** | `Annotator.xaml.cs:197-202`, `OpenAnnotationResult` (seek + ZoomTo for split tiles) | Partial — seek probably works, "open split-tile zoom" not verified | `AnnotationsSidebar` |
|
||||
| **Help window "Як анотувати" (6 quality rules)** | `HelpWindow.xaml`, `_docs/legacy/wpf-era.md §10` "Help window" | `HelpModal` exists but GUIDELINES is a hardcoded string in source — not moved to i18n bundle. Step 4. | `03_shared-ui/HelpModal` |
|
||||
|
||||
### Decisions required at Step 4.5 (Architecture Vision)
|
||||
|
||||
- **Sound Detections** — port, drop, or move to a different module (e.g., a future audio-pipeline service)?
|
||||
- **Drone Maintenance** — same.
|
||||
- **Camera config** persistence — was per-`AppConfig` in WPF; in the React port should it be per-user-settings, per-flight, or per-detect-job?
|
||||
- **Status bar / help-text blinking** — keep the WPF "blink twice" UX, replace with toasts, or drop?
|
||||
|
||||
These are Architecture-Vision-level questions, not Step 2 component-graph decisions. Recorded here so Step 4.5 can pick them up.
|
||||
|
||||
## 7. Caveats & Edge Cases
|
||||
|
||||
- 26 findings in `src__features__annotations.md`. Cross-cutting blockers cluster on enum drift + `handleSave` body shape + missing `X-Refresh-Token`.
|
||||
- **Time-window** and **gradient** math wrong against spec.
|
||||
- **Video AI-detect not wired to SSE** — long-running jobs appear to hang from the user's POV.
|
||||
- **WPF gap analysis above** lists ~17 missing affordances. Highest user-impact: keyboard shortcuts, volume slider, status bar with clock + help text. Highest design-impact: Sound Detections + Drone Maintenance (both require a port-or-drop decision).
|
||||
|
||||
## 8. Dependency Graph
|
||||
|
||||
**Must be implemented after**: `00_foundation`, `01_api-transport`, `03_shared-ui`, `11_class-colors`.
|
||||
|
||||
**Can be implemented in parallel with**: every other feature page (note: `07_dataset` imports `CanvasEditor`, so a `CanvasEditor` extraction must coordinate).
|
||||
|
||||
**Blocks**: `07_dataset` (direct import dependency on `CanvasEditor`), `10_app-shell`.
|
||||
|
||||
## Module Inventory
|
||||
|
||||
Single consolidated module doc: `_docs/02_document/modules/src__features__annotations.md`. `classColors.ts` is moved into a separate component (`11_class-colors`) — its module doc is referenced from there, not here.
|
||||
@@ -0,0 +1,98 @@
|
||||
# 07 — Dataset Explorer
|
||||
|
||||
## 1. High-Level Overview
|
||||
|
||||
**Purpose**: Browse, filter, edit, split, and export the dataset. Reuses `CanvasEditor` from `06_annotations` for in-place bbox editing on dataset thumbnails.
|
||||
|
||||
**Architectural Pattern**: Single-page feature with one route component composing local panels.
|
||||
|
||||
**Upstream dependencies**: `00_foundation`, `01_api-transport`, `03_shared-ui` (FlightContext, ConfirmDialog, DetectionClasses), `06_annotations` (`CanvasEditor` — cross-feature edge).
|
||||
|
||||
**Downstream consumers**: `10_app-shell` (routed at `/dataset`).
|
||||
|
||||
## 2. Internal Interfaces
|
||||
|
||||
| Export | Notes |
|
||||
|--------|-------|
|
||||
| `DatasetPage()` | Top-level route component. Loads paged dataset items, applies filters (class, affiliation, status, flight), renders thumbnail grid + edit pane. |
|
||||
|
||||
## 3. External API Specification
|
||||
|
||||
| Method | Path | Purpose |
|
||||
|--------|------|---------|
|
||||
| GET | `/api/annotations/dataset` | Paged list with filters |
|
||||
| GET | `/api/annotations/dataset/{id}` | Detail |
|
||||
| PUT | `/api/annotations/dataset/{id}` | Update (class, status, bbox) |
|
||||
| DELETE | `/api/annotations/dataset/{id}` | Delete |
|
||||
| POST | `/api/annotations/dataset/bulk-status` | Bulk status update (numeric per finding cross-check) |
|
||||
| POST | `/api/annotations/dataset/{id}/split` | Split tile |
|
||||
|
||||
## 5. Implementation Details
|
||||
|
||||
**State Management**: Page-local. Uses `useDebounce` for filter inputs and `useResizablePanel` for the editor pane.
|
||||
|
||||
**Findings** (13 numbered, from `src__features__dataset__DatasetPage.md`):
|
||||
|
||||
1. **No keyboard shortcuts** at all.
|
||||
2. **No "Refresh thumbnails"** action.
|
||||
3. **No virtualisation** — long lists render all thumbnails.
|
||||
4. **Editor tab does not save** — confirmed regression.
|
||||
5. **Magic `mediaType=1`** literal — should be the typed enum.
|
||||
6. **Dead `ConfirmDialog` import** — never used.
|
||||
7. **Silent `try/catch`** in delete handler.
|
||||
8. **Status filter conflates `None` with `All`** — depends on the `AnnotationStatus` enum drift fix (`00_foundation/types/index.ts`).
|
||||
9. **`classNum=0` sentinel collides with real class 0** — needs explicit "all classes" sentinel.
|
||||
10. **`mediaType=1` again** — appears twice.
|
||||
11. **Bulk-status request** uses string status names; spec wants numerics — already retagged for parent-doc fix (state.json 02:18Z note covered the spec side).
|
||||
12. **DatasetItem.isSplit** missing in the parent-doc response schema — cross-repo PARENT-DOC FIX applied.
|
||||
13. **Cross-feature `CanvasEditor` import** — finding #14 (cross-link to enum drift + isSplit gap).
|
||||
|
||||
**Key Dependencies**: `react-dropzone` (export trigger), `@hello-pangea/dnd` (potentially, for reordering — verify in Step 3).
|
||||
|
||||
## 6b. WPF gap analysis (vs `suite/annotations-research`)
|
||||
|
||||
> Cross-check of the legacy `Azaion.Dataset.DatasetExplorer` window (`Azaion.Dataset/DatasetExplorer.xaml`) against the current React `DatasetPage`. **Step 4 correction**: an earlier draft of this table claimed several WPF features were missing that are in fact already implemented. Re-read of `src/features/dataset/DatasetPage.tsx` corrected the table below.
|
||||
|
||||
| WPF feature | Anchor in `annotations-research` | React status | Owner |
|
||||
|------------|----------------------------------|--------------|-------|
|
||||
| **Class Distribution chart tab** (3rd tab — horizontal bars per `DetectionClass`, bar tinted with the class color) | `Azaion.Dataset/Controls/ClassDistribution.xaml` + `DatasetExplorer.xaml:146-148` | **Implemented** — `DatasetPage.tsx:151` has three tabs (`annotations`, `editor`, `distribution`); `loadDistribution()` calls `GET /api/annotations/dataset/class-distribution`. Step-4 verify the bar tint matches `classColors`. | — |
|
||||
| **"Show only annotations with objects" checkbox** in left filter pane | `DatasetExplorer.xaml:89-95` `ShowWithObjectsOnlyChBox` | **Implemented** — `DatasetPage.tsx:110-114`, state name `objectsOnly`. | — |
|
||||
| **Validate button (bulk-validate selected annotations to `AnnotationStatus.Validated`)** | `DatasetExplorer.xaml:177-200` `ValidateBtn` + `ValidateAnnotationsClick` | **Implemented** — `DatasetPage.tsx:142-146` Validate button appears when `selectedIds.size > 0`; `handleValidate()` posts to `/api/annotations/dataset/bulk-status`. **Gap is the `[V]` keyboard shortcut**, not the button. | `DatasetPage` (shortcut only) |
|
||||
| **Refresh thumbnails button + progress bar** | `DatasetExplorer.xaml:205-247` `RefreshThumbnailsButtonItem` + `RefreshProgressBarItem` | Button missing (finding #2); progress UI also missing | `DatasetPage` + an as-yet-undefined refresh service endpoint |
|
||||
| **`SelectedAnnotationName` status indicator** (bottom-right of status bar) | `DatasetExplorer.xaml:252-254` | Missing | `DatasetPage` |
|
||||
| **Generic `StatusText` slot** | `DatasetExplorer.xaml:249-251` | Missing | `DatasetPage` |
|
||||
| **Seed annotation highlight** (`IsSeed=true` thumbnails get an 8 px IndianRed border) | `DatasetExplorer.xaml:15-29` thumbnail template | Missing — `DatasetItem.isSeed` shape unverified against suite spec (cross-link to `00_foundation/types/index.ts`). | `DatasetPage` + types |
|
||||
| **Thumbnail caption** (image name + `CreatedDate: CreatedEmail`) | `DatasetExplorer.xaml:42-56` | Likely missing or simplified — verify in Step 4 against current React render. | `DatasetPage` |
|
||||
| **Keyboard shortcuts `[1]–[9]`, `[Enter]`, `[Del]`, `[X]`, `[V]`, arrows, PgUp/PgDn, `[Esc]`** for inline editor | `DatasetExplorer.xaml.cs` (listed in `_docs/legacy/wpf-era.md §5`) | Missing entirely (finding #1) | `DatasetPage` |
|
||||
| **Editor tab actually saves** | WPF wires `ExplorerEditor` → `AnnotationService.OnAnnotationCreated` etc. | **Broken in React** (finding #4 — Editor tab does not save). PRIORITY Step 4. | `DatasetPage` + `06_annotations/CanvasEditor` |
|
||||
| **`DetectionClasses` strip in left pane** (same control reused from Annotator) | `DatasetExplorer.xaml:85-88` | Present (via `03_shared-ui/DetectionClasses`) | — |
|
||||
| **Filter `TextBox`** | `DatasetExplorer.xaml:112-115` `TbSearch` with `TextChanged` debounce | Present (uses `00_foundation/useDebounce`) | — |
|
||||
| **Virtualised thumbnail grid** (`vwp:GridView` from `WpfToolkit.VirtualizingWrapPanel`) | `DatasetExplorer.xaml:126-135` | **Missing virtualisation** (finding #3) — long lists render all thumbnails. | `DatasetPage` |
|
||||
|
||||
### Decisions required at Step 4.5 (Architecture Vision)
|
||||
|
||||
- **Refresh-thumbnails action** — is the existing thumbnail refresh strategy (server-side on annotation save) acceptable, or do we need a manual "Refresh" affordance like the WPF era?
|
||||
- **Status-bar surfaces** (`StatusText`, `SelectedAnnotationName`) — port the WPF status bar verbatim, or rely on existing toasts and selection counters?
|
||||
- **Seed annotation concept** (`IsSeed=true` highlight) — does the modern API still expose `IsSeed`, and is the visual still desired?
|
||||
- **Inline editor save** — is the broken save (#4) a regression to fix or a feature to be redesigned?
|
||||
|
||||
## 7. Caveats & Edge Cases
|
||||
|
||||
- **Cross-feature import** of `CanvasEditor` (`06_annotations`). Either lift to a shared `components/canvas/` or accept the edge — record in module-layout / baseline scan.
|
||||
- **Editor tab broken** (#4) — PRIORITY Step 4.
|
||||
- **Filter sentinels colliding** (#8, #9) — wire-format consistency depends on enum drift fix.
|
||||
- **WPF gap analysis above** lists ~12 missing affordances. Highest user-impact: virtualisation, Refresh thumbnails, keyboard shortcuts, broken editor save. Highest design-impact: Class Distribution chart (entirely missing third tab).
|
||||
|
||||
## 8. Dependency Graph
|
||||
|
||||
**Must be implemented after**: `00_foundation`, `01_api-transport`, `03_shared-ui`, `06_annotations` (`CanvasEditor`).
|
||||
|
||||
**Can be implemented in parallel with**: `04_login`, `05_flights`, `08_admin`, `09_settings`.
|
||||
|
||||
**Blocks**: `10_app-shell` only.
|
||||
|
||||
## Module Inventory
|
||||
|
||||
| Path | Module Doc |
|
||||
|------|------------|
|
||||
| `src/features/dataset/DatasetPage.tsx` | `_docs/02_document/modules/src__features__dataset__DatasetPage.md` |
|
||||
@@ -0,0 +1,64 @@
|
||||
# 08 — Admin
|
||||
|
||||
## 1. High-Level Overview
|
||||
|
||||
**Purpose**: Operator-only configuration page. User management, detection-class management, AI Settings, GPS Settings, aircraft default.
|
||||
|
||||
**Architectural Pattern**: Single-page feature, large monolithic component (~215 lines pre-consolidation per state.json).
|
||||
|
||||
**Upstream dependencies**: `00_foundation`, `01_api-transport`, `03_shared-ui` (ConfirmDialog).
|
||||
|
||||
**Downstream consumers**: `10_app-shell` (routed at `/admin`, **currently with no role-based guard** — see #1 caveat below).
|
||||
|
||||
## 2. Internal Interfaces
|
||||
|
||||
| Export | Notes |
|
||||
|--------|-------|
|
||||
| `AdminPage()` | Top-level route component. Sub-sections: Users, Detection Classes, AI Settings, GPS Settings, Aircraft default. |
|
||||
|
||||
## 3. External API Specification
|
||||
|
||||
| Method | Path | Purpose |
|
||||
|--------|------|---------|
|
||||
| GET / POST / PUT / DELETE | `/api/admin/users` | User CRUD |
|
||||
| GET | `/api/annotations/classes` | Read class list (note: read uses `annotations/`, write uses `admin/`) |
|
||||
| POST / PUT / DELETE | `/api/admin/classes` | Class CRUD |
|
||||
| GET / PUT | `/api/admin/settings/ai` | AI service config |
|
||||
| GET / PUT | `/api/admin/settings/gps` | GPS device config |
|
||||
| GET / PUT | `/api/admin/settings/aircraft-default` | Aircraft default |
|
||||
|
||||
## 5. Implementation Details
|
||||
|
||||
**State Management**: Page-local React state per sub-form. No global form library.
|
||||
|
||||
**Findings (B4, copied from state.json):**
|
||||
|
||||
1. **PRIORITY (security): no role-based route guard on `/admin`** — anyone authenticated can access. Server-enforced 403 protects the data, but UI does not gate. Surface in Step 6 problem-extraction. Cross-link with `10_app-shell`.
|
||||
2. **AI Settings & GPS Settings forms render with `defaultValue` only — NO state, NO submit handler, the Save button does nothing.** PRIORITY surface in Step 6.
|
||||
3. **Hardcoded GPS device default `'192.168.1.100'` / port `'5535'`** shipped in production bundle. Step 4.
|
||||
4. **`handleDeleteClass` has NO `ConfirmDialog`** despite being destructive. Step 4 vs `ui_design/README.md`.
|
||||
5. **Service split mismatch**: detection-class read uses `/api/annotations/classes` (annotations service) but write uses `/api/admin/classes` (admin service). Verify with suite ADRs in Step 3a.
|
||||
6. **`handleToggleDefault` duplicated** between AdminPage and SettingsPage; aircraft default is global config but page exists in both `/admin` and `/settings` — surface intent in Step 6.
|
||||
7. **Many hardcoded English strings.** Step 4 i18n.
|
||||
|
||||
**Key Dependencies**: `03_shared-ui/ConfirmDialog` (used for some destructive actions; missing on `handleDeleteClass`).
|
||||
|
||||
## 7. Caveats & Edge Cases
|
||||
|
||||
- The **broken Save button** is the most user-visible bug.
|
||||
- The **annotations/admin service split** for class CRUD looks like a copy-paste residue but may be deliberate; verify in Step 3a.
|
||||
- **No optimistic concurrency / version check** for any settings — last writer wins.
|
||||
|
||||
## 8. Dependency Graph
|
||||
|
||||
**Must be implemented after**: `00_foundation`, `01_api-transport`, `03_shared-ui`.
|
||||
|
||||
**Can be implemented in parallel with**: every other feature page.
|
||||
|
||||
**Blocks**: `10_app-shell`.
|
||||
|
||||
## Module Inventory
|
||||
|
||||
| Path | Module Doc |
|
||||
|------|------------|
|
||||
| `src/features/admin/AdminPage.tsx` | `_docs/02_document/modules/src__features__admin__AdminPage.md` |
|
||||
@@ -0,0 +1,60 @@
|
||||
# 09 — Settings
|
||||
|
||||
## 1. High-Level Overview
|
||||
|
||||
**Purpose**: User-scoped + system settings: language, theme, system params, directory paths, aircraft default (duplicated with Admin).
|
||||
|
||||
**Architectural Pattern**: Single-page feature, ~181 lines pre-consolidation.
|
||||
|
||||
**Upstream dependencies**: `00_foundation`, `01_api-transport`, `03_shared-ui`.
|
||||
|
||||
**Downstream consumers**: `10_app-shell` (routed at `/settings`).
|
||||
|
||||
## 2. Internal Interfaces
|
||||
|
||||
| Export | Notes |
|
||||
|--------|-------|
|
||||
| `SettingsPage()` | Top-level route component. Sub-sections: Personal (language, theme), System (params), Directories, Aircraft default. |
|
||||
|
||||
## 3. External API Specification
|
||||
|
||||
| Method | Path | Purpose |
|
||||
|--------|------|---------|
|
||||
| GET / PUT | `/api/annotations/settings/user` | Per-user UI preferences |
|
||||
| GET / PUT | `/api/admin/settings/system` | System params (saveSystem) |
|
||||
| GET / PUT | `/api/admin/settings/directories` | Storage paths (saveDirs) |
|
||||
| GET / PUT | `/api/admin/settings/aircraft-default` | Aircraft default (duplicated with Admin) |
|
||||
|
||||
## 5. Implementation Details
|
||||
|
||||
**State Management**: Page-local React state per form section.
|
||||
|
||||
**Findings (B4, copied from state.json):**
|
||||
|
||||
1. **`saveSystem` / `saveDirs` lack `try/finally`** — PUT failure leaves `saving:true` permanently and the spinner never stops. Step 4.
|
||||
2. **Numeric inputs use `parseInt(v) || 0`** — clearing a field silently writes 0. Step 4.
|
||||
3. **No optimistic concurrency** (no version field, no ETag) — Step 6 problem-extraction.
|
||||
4. **`handleToggleDefault` duplicated with AdminPage** — same global config behind two different pages. Surface intent in Step 6.
|
||||
5. **Possibly should be guarded by a permission like `SETTINGS`** — spec doesn't have such a code; server-enforces via 403. Less clear-cut than the `/admin` gap. Surface in Step 6.
|
||||
|
||||
**Key Dependencies**: `react-i18next` (language switch).
|
||||
|
||||
## 7. Caveats & Edge Cases
|
||||
|
||||
- **Stuck-saving spinner** on PUT failure (#1).
|
||||
- **Silent zero on cleared numeric input** (#2) — corrupts settings.
|
||||
- **Aircraft default duplicated** with Admin — eventually one page should win.
|
||||
|
||||
## 8. Dependency Graph
|
||||
|
||||
**Must be implemented after**: `00_foundation`, `01_api-transport`, `03_shared-ui`.
|
||||
|
||||
**Can be implemented in parallel with**: every other feature page.
|
||||
|
||||
**Blocks**: `10_app-shell`.
|
||||
|
||||
## Module Inventory
|
||||
|
||||
| Path | Module Doc |
|
||||
|------|------------|
|
||||
| `src/features/settings/SettingsPage.tsx` | `_docs/02_document/modules/src__features__settings__SettingsPage.md` |
|
||||
@@ -0,0 +1,68 @@
|
||||
# 10 — App Shell
|
||||
|
||||
## 1. High-Level Overview
|
||||
|
||||
**Purpose**: Application bootstrap. `main.tsx` mounts React + StrictMode + BrowserRouter; `App.tsx` defines the top-level routing tree and provider stack.
|
||||
|
||||
**Architectural Pattern**: Composition root.
|
||||
|
||||
**Upstream dependencies**: every other component (this is the wiring root).
|
||||
|
||||
**Downstream consumers**: none (top of the tree).
|
||||
|
||||
## 2. Internal Interfaces
|
||||
|
||||
### `src/main.tsx`
|
||||
|
||||
- Imports `./i18n/i18n` for side-effect (`00_foundation`).
|
||||
- Imports `./index.css`.
|
||||
- Mounts `<StrictMode><BrowserRouter><App /></BrowserRouter></StrictMode>` into `#root`.
|
||||
|
||||
### `src/App.tsx`
|
||||
|
||||
Routes:
|
||||
|
||||
| Route | Wrapping | Component |
|
||||
|-------|----------|-----------|
|
||||
| `/login` | (public) | `04_login/LoginPage` |
|
||||
| `/flights` (default authenticated) | `AuthProvider → ProtectedRoute → FlightProvider → Header` | `05_flights/FlightsPage` |
|
||||
| `/annotations` | same | `06_annotations/AnnotationsPage` |
|
||||
| `/dataset` | same | `07_dataset/DatasetPage` |
|
||||
| `/admin` | same — **no role guard** | `08_admin/AdminPage` |
|
||||
| `/settings` | same | `09_settings/SettingsPage` |
|
||||
| `*` | same | redirect → `/flights` |
|
||||
|
||||
## 5. Implementation Details
|
||||
|
||||
**State Management**: provider stack only (`AuthProvider`, `FlightProvider`).
|
||||
|
||||
**Findings (5 items from `src__App-and-main.md`):**
|
||||
|
||||
1. **No role-based route guards** on `/admin` (PRIORITY — security). `/settings` is more nuanced (no `SETTINGS` permission code in spec; server-enforced via 403).
|
||||
2. **Mobile bottom-nav** route layout — confirmed present (Header.tsx:113–129). Earlier draft incorrectly listed this as missing; corrected per state.json 02:01Z.
|
||||
3. **No `ErrorBoundary`** — any uncaught render throw crashes the whole app to a white screen.
|
||||
4. **No lazy chunks / code-splitting** — every route is in the initial bundle. Compounds the `chart.js` bloat from `05_flights`.
|
||||
5. **`/flights` is the default landing for everyone** — user-specific landing per role would require `00_foundation` permission types + `02_auth.permissions`.
|
||||
|
||||
**Key Dependencies**: `react-router-dom` 7.
|
||||
|
||||
## 7. Caveats & Edge Cases
|
||||
|
||||
- **No `ErrorBoundary`** is the highest-impact gap; even one runtime null deref in any feature kills the app.
|
||||
- **`/admin` open to any authenticated user** at the UI level (PRIORITY).
|
||||
- **No lazy loading** — initial bundle is ~all of the SPA.
|
||||
|
||||
## 8. Dependency Graph
|
||||
|
||||
**Must be implemented after**: every other component (composition root).
|
||||
|
||||
**Can be implemented in parallel with**: nothing.
|
||||
|
||||
**Blocks**: nothing.
|
||||
|
||||
## Module Inventory
|
||||
|
||||
| Path | Module Doc |
|
||||
|------|------------|
|
||||
| `src/App.tsx` | `_docs/02_document/modules/src__App-and-main.md` |
|
||||
| `src/main.tsx` | `_docs/02_document/modules/src__App-and-main.md` |
|
||||
@@ -0,0 +1,85 @@
|
||||
# 11 — Class Colors (Detection Class Theme)
|
||||
|
||||
## 1. High-Level Overview
|
||||
|
||||
**Purpose**: The single source of fallback color, fallback name, and `PhotoMode` suffix for any detection class number — used whenever the live `DetectionClass[]` from the admin API is unavailable, partial, or being rendered next to UI chrome that cannot wait for it (initial paint, gradient stops, label tints, sidebar swatches).
|
||||
|
||||
**Architectural Pattern**: Pure-function shared kernel. Stateless, no React, no HTTP, no DOM.
|
||||
|
||||
**Layer**: shared / Layer 1 (above Foundation, below every UI component that names a detection class).
|
||||
|
||||
**Upstream dependencies**: none (no internal imports; no external libraries).
|
||||
|
||||
**Downstream consumers**:
|
||||
- `03_shared-ui/DetectionClasses` (fallback name + color when admin classes haven't loaded)
|
||||
- `06_annotations/CanvasEditor` (bbox label color + crosshair tint)
|
||||
- `06_annotations/AnnotationsPage` (active-class indicator)
|
||||
- `06_annotations/AnnotationsSidebar` (annotation-row gradient stops)
|
||||
- `07_dataset/DatasetPage` (class-filter chip color, class-distribution chart) — when those features are wired up
|
||||
|
||||
## 2. Internal Interfaces
|
||||
|
||||
```ts
|
||||
export const FALLBACK_CLASS_NAMES: string[]; // 12 generic English labels
|
||||
export function getClassColor(classNum: number): string; // hex string, no '#'-alpha
|
||||
export function getPhotoModeSuffix(classNum: number): string; // '' | ' (winter)' | ' (night)'
|
||||
export function getClassNameFallback(classNum: number): string; // FALLBACK_CLASS_NAMES[base] or '#<n>'
|
||||
```
|
||||
|
||||
A 12-color palette `CLASS_COLORS` is module-private and exposed only via `getClassColor`.
|
||||
|
||||
### PhotoMode contract (from legacy WPF)
|
||||
|
||||
`yoloId = classId + photoModeOffset`. Three offsets:
|
||||
|
||||
| `mode = floor(classNum / 20)` | Suffix | Meaning |
|
||||
|------------------------------|--------|---------|
|
||||
| 0 | (empty) | Regular |
|
||||
| 1 | `' (winter)'` | Winter |
|
||||
| 2 | `' (night)'` | Night |
|
||||
|
||||
`base = classNum % 20` is the index into both `CLASS_COLORS` and `FALLBACK_CLASS_NAMES`.
|
||||
|
||||
## 5. Implementation Details
|
||||
|
||||
```
|
||||
base = classNum % 20
|
||||
mode = floor(classNum / 20)
|
||||
color = CLASS_COLORS[base % CLASS_COLORS.length]
|
||||
name = FALLBACK_CLASS_NAMES[base % FALLBACK_CLASS_NAMES.length] ?? `#${classNum}`
|
||||
suffix = mode === 1 ? ' (winter)' : mode === 2 ? ' (night)' : ''
|
||||
```
|
||||
|
||||
**Open question** (carried forward from `src__features__annotations__classColors.md`): the `??` guard is dead because `base % length` already brings the index back into range. Either the array is wrong (the legacy palette had >12 entries?) or the guard is dead code. Step 4 verification.
|
||||
|
||||
**Redundancy with `DetectionClass.photoMode`** (also in module doc): the live admin DTO carries an explicit `photoMode` field on `DetectionClass`. Computing the suffix from `classNum / 20` here risks disagreeing with the admin-defined value. Step 4 testability candidate: keep `getPhotoModeSuffix` only as a fallback when the admin DTO is missing.
|
||||
|
||||
**State Management**: stateless module. Calls are pure.
|
||||
|
||||
**Key Dependencies**: none.
|
||||
|
||||
## 6. Extensions and Helpers
|
||||
|
||||
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.
|
||||
- **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.
|
||||
|
||||
## 8. Dependency Graph
|
||||
|
||||
**Must be implemented after**: nothing (no internal deps).
|
||||
|
||||
**Can be implemented in parallel with**: `00_foundation`, `01_api-transport`.
|
||||
|
||||
**Blocks**: `03_shared-ui` (DetectionClasses), `06_annotations`, `07_dataset` (when class-distribution chart is added).
|
||||
|
||||
## Module Inventory
|
||||
|
||||
| Path | Module Doc |
|
||||
|------|------------|
|
||||
| `src/features/annotations/classColors.ts` *(physical location pending refactor)* | `_docs/02_document/modules/src__features__annotations__classColors.md` |
|
||||
Reference in New Issue
Block a user