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>
32 KiB
Azaion UI — System Flows
Synthesis output of
/documentStep 3b. Each flow is grounded in component specs (components/*/description.md) and module docs (modules/*.md). When a step references a finding (e.g., #14), the source is one of:_docs/02_document/modules/src__features__annotations.md,..__flights.md,..__dataset__DatasetPage.md, or..__App-and-main.md.
Flow Inventory
| # | Flow Name | Trigger | Primary Components | Criticality |
|---|---|---|---|---|
| F1 | Login | User submits /login form |
04_login, 02_auth, admin/ |
High |
| F2 | Bearer auto-refresh on 401 | Any authenticated fetch returns 401 | 02_auth, 01_api-transport, admin/ |
High |
| F3 | Select active flight | User picks a flight in Header dropdown | 03_shared-ui (FlightContext + Header), flights/ + annotations/settings/user |
High |
| F4 | Create / save flight + waypoints | User clicks Save in FlightsPage |
05_flights, flights/ |
High |
| F5 | Annotate media (manual bbox) | User drags on canvas | 06_annotations, annotations/ |
High |
| F6 | AI Detect — image (sync) | User clicks AI Detect with image selected | 06_annotations, detect/ |
Medium |
| F7 | AI Detect — video (async) — NOT WIRED | User clicks AI Detect with video selected | (target) 06_annotations, detect/, 01_api-transport/sse.ts |
High |
| F8 | Dataset browse + filter | User opens /dataset |
07_dataset, annotations/ |
Medium |
| F9 | Dataset bulk-validate | User selects thumbnails + Validate (planned, missing today) | 07_dataset, annotations/ |
Medium |
| F10 | Admin: detection class CRUD | User edits in /admin |
08_admin, admin/ (write) + annotations/ (read) |
High |
| F11 | Settings: persist user prefs | User saves panel widths / system / camera | 09_settings, annotations/ (system/dirs/user) + flights/ (aircraft) |
Medium |
| F12 | GPS-Denied Test Mode (planned) | User uploads .tlog + video |
05_flights (Test Mode tab), gps-denied-desktop/, gps-denied-onboard/ |
Medium (planned) |
| F13 | Live-GPS aircraft telemetry (SSE) | User opens FlightsPage with selected flight | 05_flights/FlightsPage, flights/ (/api/flights/${id}/live-gps SSE) |
Medium |
| F14 | Annotation-status events (SSE) | User opens AnnotationsPage with selected media | 06_annotations/AnnotationsSidebar, annotations/ (/api/annotations/annotations/events SSE) |
Medium |
Flow Dependencies
| Flow | Depends On | Shares Data With |
|---|---|---|
| F1 | — (entry) | F2 (auth state cycles forever afterwards) |
| F2 | F1 (initial bearer must exist) | every authenticated flow |
| F3 | F1 | F4–F11 (selected flight scopes most lists) |
| F4 | F3 | F5 (annotations are scoped per media → per flight) |
| F5 | F3, media exists | F6, F7 (manual edits coexist with AI runs) |
| F6 | F3, image media exists | F5 |
| F7 | F3, video media exists | F5 |
| F8 | F3, F5/F6/F7 produced data | F9 |
| F9 | F8 | F10 (validation status feeds class metrics) |
| F10 | F1 (admin role) | F5 (class definitions are inputs to drawing) |
| F11 | F1 | F5 (panel widths are read by useResizablePanel, but persistence is missing today — finding #11) |
| F12 | F4 (a flight context for the test) | F7 (output detections may be cross-checked) |
| F13 | F3 (selected flight) | F4 (live-GPS overlays the planned route) |
| F14 | F5 (an annotation must exist for an event to fire) | F8 (dataset reflects status changes pushed via this stream) |
Flow F1: Login
Description
Operator submits credentials at /login. On success, the Admin service issues a JWT bearer (response body) and a Secure; HttpOnly; SameSite=Strict refresh-token cookie. AuthContext stores the bearer in memory; ProtectedRoute lets the user enter the app.
Preconditions
- The Admin service is reachable through the nginx
/api/admin/*proxy (or Vite dev proxy). - The user has not yet authenticated (or their bearer + refresh cookie are both expired).
Sequence Diagram
sequenceDiagram
actor User
participant LoginPage as 04_login/LoginPage
participant AuthCtx as 02_auth/AuthContext
participant ApiClient as 01_api-transport/client
participant AdminApi as admin/ service
User->>LoginPage: Enter email + password, click Submit
LoginPage->>AuthCtx: login(email, password)
AuthCtx->>ApiClient: api.post('/api/admin/auth/login', {email, password})
ApiClient->>AdminApi: POST /admin/auth/login (credentials:'include')
AdminApi-->>ApiClient: 200 {bearer, user} + Set-Cookie: refresh=...
ApiClient-->>AuthCtx: {bearer, user}
AuthCtx-->>LoginPage: ok
LoginPage-->>User: Redirect to '/' (= '/flights' default)
Flowchart
flowchart TD
Start([User submits login form]) --> ValidateForm{Both fields filled?}
ValidateForm -->|No| ShowFormError[Show inline error]
ValidateForm -->|Yes| Theatrical[runUnlockSequence: 4×600 ms theatrical animation — finding B4]
Theatrical --> CallApi[POST /api/admin/auth/login]
CallApi --> Resp{200 OK?}
Resp -->|Yes| StoreBearer[AuthContext stores bearer in memory]
StoreBearer --> Redirect([Navigate to '/'])
Resp -->|401| ShowAuthError[Show 'Invalid credentials']
Resp -->|5xx / network| ShowGenericError[Show 'Try again']
ShowAuthError --> Start
ShowGenericError --> Start
ShowFormError --> Start
Data Flow
| Step | From | To | Data | Format |
|---|---|---|---|---|
| 1 | User | LoginPage | email + password | form input |
| 2 | LoginPage | AuthContext | { email, password } |
function args |
| 3 | AuthContext | admin/ via api/client | POST /api/admin/auth/login |
JSON body |
| 4 | admin/ | AuthContext | { bearer, user: AuthUser } + Set-Cookie |
JSON + cookie |
| 5 | AuthContext | LoginPage | success | promise resolution |
| 6 | LoginPage | router | navigate('/') |
client-side redirect |
Error Scenarios
| Error | Where | Detection | Recovery |
|---|---|---|---|
| Invalid credentials | step 4 | 401 from admin/ | Show inline message; let user retry |
| Network failure | step 3 | fetch reject | Show generic error; let user retry |
| Theatrical animation hides server error | step 2 (theatrical waits 2.4 s) | none | UX cost — finding (B4) suggests removing the animation entirely |
| Refresh cookie blocked by browser | step 4 | next F2 immediately fails | User must log in again — accepted today |
Performance Expectations
| Metric | Target | Notes |
|---|---|---|
| End-to-end latency | < 1 s server-side + 2.4 s artificial delay | Artificial delay is the dominant cost — Step 4 fix |
Flow F2: Bearer auto-refresh on 401 (TWO refresh paths exist in code)
Description
There are two distinct refresh code paths in the source — the verification pass (Step 4) caught both:
- Bootstrap path —
AuthContext.tsx:24callsapi.get('/api/admin/auth/refresh')on app mount. This does NOT havecredentials:'include'becauseapi/client.tsdoesn't add it on GET. Result: the cookie is not sent, the bootstrap silently fails, the user starts unauthenticated even when they have a valid refresh cookie. - 401-retry path —
api/client.ts:44callsfetch('/api/admin/auth/refresh', { method: 'POST', credentials: 'include' })automatically when any authenticated fetch returns 401. This path IS correct.
The bootstrap path is the bug surfaced as finding B3 PRIORITY. The 401-retry path is the silent fallback that does work but only after the user has already hit a 401.
Preconditions
- A valid refresh-cookie exists on the browser.
- The 401 came from a request that should be retryable (idempotent or annotated as retry-safe).
Sequence Diagram (401-retry path inside api/client.ts — works correctly)
sequenceDiagram
participant Page
participant ApiClient as 01_api-transport/client
participant AdminApi as admin/ service
Page->>ApiClient: api.get('/api/...')
ApiClient->>AdminApi: GET /... (with bearer)
AdminApi-->>ApiClient: 401 (bearer expired)
ApiClient->>AdminApi: POST /admin/auth/refresh (credentials:'include')
AdminApi-->>ApiClient: 200 {bearer} + Set-Cookie: refresh=...
ApiClient->>AdminApi: GET /... (retry with new bearer)
AdminApi-->>ApiClient: 200 OK
ApiClient-->>Page: response
Sequence Diagram (Bootstrap path on app mount — broken)
sequenceDiagram
participant App
participant AuthCtx as 02_auth/AuthContext
participant AdminApi as admin/ service
App->>AuthCtx: <AuthProvider> mounts
AuthCtx->>AdminApi: GET /admin/auth/refresh (NO credentials:'include' — finding B3)
AdminApi-->>AuthCtx: 401 (no cookie sent)
AuthCtx->>AuthCtx: setLoading(false), user stays null
AuthCtx-->>App: ProtectedRoute sees null user → redirects to /login
Error Scenarios
| Error | Where | Detection | Recovery |
|---|---|---|---|
Bootstrap GET refresh missing credentials:'include' |
AuthContext.tsx:24 |
server returns 401 because cookie was not sent | Bug today — finding B3 PRIORITY. Symptom: a user with a valid refresh cookie still gets bounced to /login on every fresh tab. Step 4 fix: change to POST with credentials:'include' (matching the 401-retry path), or just delete the bootstrap GET and let the first authenticated fetch's 401 trigger the retry path. |
| 401-retry path | api/client.ts:44 |
works | (no fix needed) |
| Refresh cookie expired or revoked | refresh call | 401 | UI redirects to /login. |
| SSE subscription holds a stale bearer | active EventSource | server closes the SSE stream | No reconnect logic today (Step 8 hardening). |
Flow F3: Select active flight (CORRECTED Step 4)
Description
Most pages are scoped to a "currently selected flight". FlightContext loads the flight list AND the user's stored selection on mount, then lets the user switch via the Header dropdown. The selection is persisted as part of UserSettings via the annotations/ service — there is no /api/flights/select endpoint; this was a Step 3 documentation error caught at Step 4 verification.
Sequence Diagram
sequenceDiagram
actor User
participant Header as 03_shared-ui/Header
participant FlightCtx as 03_shared-ui/FlightContext
participant ApiClient
participant FlightsApi as flights/ service
participant AnnotationsApi as annotations/ service
Note over FlightCtx: On mount
FlightCtx->>FlightsApi: GET /api/flights?pageSize=1000
FlightsApi-->>FlightCtx: { items: Flight[] }
Note over FlightCtx: Flights with index > 1000 are silently dropped (finding B3)
FlightCtx->>AnnotationsApi: GET /api/annotations/settings/user
AnnotationsApi-->>FlightCtx: UserSettings { selectedFlightId }
alt selectedFlightId is set
FlightCtx->>FlightsApi: GET /api/flights/{selectedFlightId}
FlightsApi-->>FlightCtx: Flight
end
User->>Header: Click flight in dropdown
Header->>FlightCtx: selectFlight(flight)
FlightCtx->>FlightCtx: setSelectedFlight(flight) immediately (optimistic)
FlightCtx->>AnnotationsApi: PUT /api/annotations/settings/user { selectedFlightId }
Note over FlightCtx,AnnotationsApi: Fire-and-forget — error swallowed (finding B3)
Error Scenarios
| Error | Where | Detection | Recovery |
|---|---|---|---|
| Flights list > 1000 | initial load | not detected | Silent ceiling — finding B3. Step 4 fix: paginate or stream. |
selectFlight PUT fails |
step | none — fire-and-forget | UI keeps the local selection but the server does not — next reload reverts it. Step 4 fix. |
Flow F4: Create / save flight + waypoints
Description
User edits a flight in FlightsPage: drags waypoints on the map, edits altitudes, types parameters. On Save, the UI sends all changes to the flights/ service.
Sequence Diagram (current implementation — lossy)
sequenceDiagram
actor User
participant FlightsPage as 05_flights/FlightsPage
participant ApiClient
participant FlightsApi as flights/ service
User->>FlightsPage: Drag waypoints, edit fields, click Save
FlightsPage->>ApiClient: api.put('/api/flights/{id}', {name, aircraftId, ...})
ApiClient->>FlightsApi: PUT /flights/{id}
FlightsApi-->>FlightsPage: 200
Note over FlightsPage,FlightsApi: Then a delete-then-recreate cycle for waypoints (finding #19)
FlightsPage->>FlightsApi: DELETE /flights/{id}/waypoints (loop or bulk)
loop for each new waypoint
FlightsPage->>FlightsApi: POST /flights/{id}/waypoints {name, lat, lng, order}
end
Note over FlightsPage,FlightsApi: But the suite spec wants {Geopoint:{Lat,Lon,MGRS}, Source, Objective, OrderNum, Height} — finding #20 (server may return 400)
FlightsApi-->>FlightsPage: per-waypoint result
FlightsPage-->>User: Saved (or partial-failure UI not built today)
Error Scenarios
| Error | Where | Detection | Recovery |
|---|---|---|---|
| Waypoint shape mismatch (UI vs spec, finding #20) | waypoint POST | server 400 | UI does not surface server errors well today — Step 4 priority |
| Partial failure (some waypoints created, some failed) | step (per-waypoint loop) | server returns mixed status | UI does not detect — Step 4 priority |
| Network drop mid-save | any | timeout | UI does not retry — Step 4 priority |
Performance Expectations
| Metric | Target | Notes |
|---|---|---|
| Save latency | < 1 s for ≤ 50 waypoints | Currently degrades with the N+M round-trip pattern (1 PUT + 1 DELETE + N POSTs). Bulk endpoint is a Step 5 solution candidate. |
Flow F5: Annotate media (manual bbox)
Description
User drags on the canvas to draw a bounding box, picks a class via 1–9 keyboard or DetectionClasses strip, and saves. The annotation persists to the annotations/ service.
Sequence Diagram
sequenceDiagram
actor User
participant CanvasEditor as 06_annotations/CanvasEditor
participant Sidebar as 06_annotations/AnnotationsSidebar
participant Page as 06_annotations/AnnotationsPage
participant ApiClient
participant AnnotationsApi as annotations/ service
User->>CanvasEditor: Mouse drag (draw bbox)
User->>CanvasEditor: Press [1]–[9] (pick class)
CanvasEditor-->>Sidebar: New detection added (in-memory)
User->>Page: Click Save
Page->>ApiClient: api.post('/api/annotations/annotations', body)
Note over Page,ApiClient: Endpoint is doubly-prefixed (suite-service + resource path)
Note over Page,ApiClient: handleSave body today: {classNum, x, y, w, h, time} — finding #32
Note over Page,ApiClient: Spec wants: {classNum, x, y, w, h, videoTime, Source, WaypointId} — Step 4
ApiClient->>AnnotationsApi: POST /annotations/annotations
AnnotationsApi-->>Page: 200 {id}
Page-->>User: Saved
Error Scenarios
| Error | Where | Detection | Recovery |
|---|---|---|---|
| Save body shape wrong (finding #32) | save call | server 400 | Today the UI logs console.error and the user sees nothing — Step 4 priority |
| Time-window math (50/150 ms vs implementation 200/200, finding #6) | overlay render | wrong annotations show during playback | Step 4 fix |
| Gradient cap (16 % vs 25 % spec, finding #9) | sidebar render | visual drift only | Step 4 fix |
| Cross-origin video tainted canvas (finding #12) | export image | browser CSP error | Step 4 fix or Step 5 design (server-side render) |
Flow F6: AI Detect — image (sync)
Description
User clicks AI Detect with an image selected. The detect service runs YOLO inference synchronously and returns detections inline.
Sequence Diagram
sequenceDiagram
actor User
participant Sidebar as 06_annotations/AnnotationsSidebar
participant ApiClient
participant DetectApi as detect/ service
participant AnnotationsApi as annotations/ service
User->>Sidebar: Click AI Detect (image selected)
Sidebar->>ApiClient: api.post('/api/detect/{mediaId}')
ApiClient->>DetectApi: POST /detect/{mediaId}
DetectApi-->>ApiClient: 200 {detections[]}
ApiClient-->>Sidebar: detections
Note over Sidebar,AnnotationsApi: Detections may auto-persist (server-side) or require client save — verify in Step 4
Sidebar-->>User: Render detections on canvas
Error Scenarios
| Error | Where | Detection | Recovery |
|---|---|---|---|
| Inference timeout | step | fetch timeout | None today — finding #21 (errors silently console.error'd) |
| Class not in admin list | render | getClassNameFallback falls back to #<n> |
Acceptable; covered by 11_class-colors |
Flow F7: AI Detect — video (async, with SSE) — NOT WIRED TODAY
Description (target — what should exist)
User clicks AI Detect with a video. The detect service kicks off an async job and returns a job ID. The UI subscribes to GET /api/detect/stream/{jobId} (SSE) for progress + final results.
What's actually in code (Step 4 verification)
The async-video flow is completely absent from the codebase. AnnotationsSidebar.tsx:39 only does POST /api/detect/${media.id} — the same endpoint, regardless of whether the media is an image or a video. There are NO calls to /api/detect/video/... and NO EventSource subscriptions to /api/detect/stream/.... The only annotation-related SSE in the codebase is createSSE('/api/annotations/annotations/events', ...) in the same file (line 25), which streams annotation-status events, not detect progress.
The previously cited finding #21 ("AI-detect doesn't stream progress") is therefore stronger than originally documented: the async path does not exist at all. The fix in Step 4 must build:
POST /api/detect/video/${mediaId}request (withX-Refresh-Tokenheader, finding #30)createSSE('/api/detect/stream/${jobId}', ...)subscription- progress UI in AnnotationsSidebar.
Sequence Diagram (target — what should happen)
sequenceDiagram
actor User
participant Sidebar as 06_annotations/AnnotationsSidebar
participant Page as 06_annotations/AnnotationsPage
participant ApiClient
participant SseClient as 01_api-transport/sse
participant DetectApi as detect/ service
User->>Sidebar: Click AI Detect (video selected)
Sidebar->>ApiClient: api.post('/api/detect/video/{mediaId}', {}, headers={X-Refresh-Token})
ApiClient->>DetectApi: POST /detect/video/{mediaId}
DetectApi-->>ApiClient: 202 {jobId}
Page->>SseClient: subscribeSSE(`/api/detect/stream/${jobId}?token=...`)
SseClient-->>DetectApi: GET /detect/stream/{jobId}?token=... (EventSource)
loop progress events
DetectApi-->>SseClient: data: {progress: 0..100}
SseClient-->>Page: onMessage(progress)
Page-->>User: Update progress bar
end
DetectApi-->>SseClient: data: {detections[], status: 'done'}
SseClient-->>Page: onMessage(final)
Page-->>User: Render detections; close stream
Error Scenarios
| Error | Where | Detection | Recovery |
|---|---|---|---|
| No SSE subscription today (finding #10) | step | user sees "AI Detect" go silent forever | Build the subscription — Step 4 PRIORITY |
Bearer expires mid-job (no X-Refresh-Token, finding #30) |
server side | job aborts | Send X-Refresh-Token so server can rotate transparently — Step 4 |
| EventSource holds stale token (finding cross-link) | refresh-rotation occurs while subscribed | server closes stream | Reconnect with new token — Step 8 hardening |
| Network blip | mid-stream | onerror |
Reconnect with backoff — Step 8 |
Flow F8: Dataset browse + filter
Description
User opens /dataset, filters by class / status / search; thumbnails appear in a grid.
Sequence Diagram
sequenceDiagram
actor User
participant DatasetPage as 07_dataset/DatasetPage
participant ApiClient
participant AnnotationsApi as annotations/ service
User->>DatasetPage: Type in filter
Note over DatasetPage: 300 ms debounce via useDebounce
DatasetPage->>ApiClient: api.get('/api/annotations/dataset?status=...&classNum=...&q=...')
ApiClient->>AnnotationsApi: GET ...
AnnotationsApi-->>DatasetPage: PaginatedResponse<DatasetItem>
DatasetPage-->>User: Render thumbnails (NOT virtualised — finding #3)
Error Scenarios
| Error | Where | Detection | Recovery |
|---|---|---|---|
| Status filter sentinels collide (finding #8) | client query build | wrong items returned | Step 4 fix after enum-drift resolution |
classNum=0 collides with real class 0 (finding #9) |
client query build | class 0 selectable but matches "All" | Step 4 fix |
| Long lists render all thumbnails (finding #3) | render | UI lag | Add virtualisation — Step 4 / Step 8 |
Flow F9: Dataset bulk-validate (Step 4 CORRECTED — partially wired)
Description
User multi-selects thumbnails (Ctrl+click) and clicks the Validate button — DatasetPage.tsx:142-146 only renders this button when selectedIds.size > 0, so it's discoverable. On click, the page POSTs /api/annotations/dataset/bulk-status with {annotationIds, status: AnnotationStatus.Validated}. The selection is then cleared and the items re-fetched.
The earlier doc draft claimed this was missing. It's not; only the [V] keyboard shortcut is missing (finding #1 — keyboard shortcuts as a category).
Sequence Diagram (actual)
sequenceDiagram
actor User
participant DatasetPage as 07_dataset/DatasetPage
participant ApiClient
participant AnnotationsApi as annotations/ service
User->>DatasetPage: Ctrl+click thumbnails (build selectedIds)
User->>DatasetPage: Click Validate button (visible when selectedIds.size > 0)
DatasetPage->>ApiClient: api.post('/api/annotations/dataset/bulk-status', {annotationIds, status: AnnotationStatus.Validated})
ApiClient->>AnnotationsApi: POST /dataset/bulk-status
AnnotationsApi-->>DatasetPage: 200
DatasetPage->>DatasetPage: setSelectedIds(new Set()); fetchItems()
DatasetPage-->>User: Refresh visible thumbnails (status badge updated)
Error Scenarios
| Error | Where | Detection | Recovery |
|---|---|---|---|
[V] keyboard shortcut missing |
finding #1 (Dataset) | only mouse click works | Step 4 — add useEffect keydown listener on the page when tab === 'annotations' |
bulk-status request body uses string instead of numeric (finding #11) |
server payload | server may reject | Step 4 — verify the suite spec then fix the client side to match |
| Bulk action fails partway | server | handleValidate has no try/catch |
Step 4 — surface a toast, keep selection so the user can retry |
Flow F10: Admin — detection class CRUD
Description
Admin user opens /admin, edits detection classes (color, name, photoMode, maxSizeM). Deletes are destructive but currently lack a confirm dialog (finding B4). Read uses annotations/classes; write uses admin/classes (finding B4 — verify with suite ADRs).
Sequence Diagram (Step 4 corrected — no PUT/edit endpoint exists)
sequenceDiagram
actor Admin
participant AdminPage as 08_admin/AdminPage
participant ApiClient
participant AnnotationsApi as annotations/ service
participant AdminApi as admin/ service
AdminPage->>ApiClient: api.get('/api/annotations/classes')
ApiClient->>AnnotationsApi: GET /classes
AnnotationsApi-->>AdminPage: DetectionClass[]
Admin->>AdminPage: Click "Add Class"
AdminPage->>ApiClient: api.post('/api/admin/classes', newClass)
ApiClient->>AdminApi: POST /classes
AdminApi-->>AdminPage: 200
AdminPage->>ApiClient: api.get('/api/annotations/classes') (re-read)
Admin->>AdminPage: Click Delete
Note over AdminPage: NO ConfirmDialog today (finding B4) — destructive action goes through immediately
AdminPage->>ApiClient: api.delete('/api/admin/classes/{id}')
ApiClient->>AdminApi: DELETE /classes/{id}
AdminApi-->>AdminPage: 200
Step 4 verification finding: there is no edit-class endpoint in the codebase today. Admins can only add new classes and delete existing ones — they cannot modify an existing class's color / name / maxSizeM / photoMode. This is a real gap, not just a documentation drift; the WPF era supported in-place edits via the same DataGrid. Surface for Step 4 (decide: add a
PATCH /api/admin/classes/{id}and a UI form, or accept add-and-delete as the only mutation path).
Error Scenarios
| Error | Where | Detection | Recovery |
|---|---|---|---|
/admin route lacks RBAC gate |
route entry | non-admin can browse | Security PRIORITY (App+main finding #1) — Step 4 |
| AI Settings + GPS Settings forms don't save (finding B4 PRIORITY) | save click | nothing happens | Step 4 PRIORITY |
Hardcoded GPS device defaults (192.168.1.100:5535) shipped to prod (finding B4) |
bundle | leaks internal network shape | Step 4 |
Flow F11: Settings — persist user prefs (Step 4 CORRECTED)
Description
User edits System / Directory / Camera / User settings, clicks Save. PUTs go to annotations/ (/api/annotations/settings/{system,directories,user}) — NOT admin/ as initially drafted. The aircraft default-toggle hits flights/ (PATCH /api/flights/aircrafts/${id}). The "settings" name is a misnomer — what the UI calls "settings" is split across two services.
Panel widths (annotations / dataset, left / right) are typed fields in UserSettings (00_foundation/types/index.ts) but useResizablePanel does not write them back today — finding #11. The wire endpoint exists; the client wiring is the gap.
Error Scenarios
| Error | Where | Detection | Recovery |
|---|---|---|---|
saveSystem / saveDirs lack try/finally (finding B4) |
PUT failure | saving:true stays forever |
Step 4 fix |
Numeric inputs parseInt(v) ‖ 0 (finding B4) |
input clear | silent zero write | Step 4 fix |
| No optimistic concurrency (finding B4) | concurrent admin edits | last-write-wins, no conflict UI | Step 6 problem-extraction surface |
Flow F12: GPS-Denied Test Mode (planned, not implemented)
Description (target — per _docs/how_to_test.md)
The operator wants to validate the GPS-Denied onboard system without a real flight. They upload a .tlog file + a synced video (or one that the system will auto-sync via IMU analysis). The system feeds the simulated frames + IMU/GPS into the SITL pipeline and the onboard service consumes them as if they were live.
Sequence Diagram (target)
sequenceDiagram
actor Operator
participant TestMode as 05_flights/GPS-Denied/Test Mode (planned)
participant ApiClient
participant GpsDeskApi as gps-denied-desktop/ service
participant GpsBoardApi as gps-denied-onboard/ service
Operator->>TestMode: Upload tlog + video (drag-drop)
TestMode->>ApiClient: api.post('/api/gps-denied-desktop/test/sync', files)
ApiClient->>GpsDeskApi: POST /test/sync (multipart)
GpsDeskApi-->>TestMode: {sessionId, syncOffset, imuChart}
Note over GpsDeskApi: Server-side: extract timestamps + IMU + GPS from tlog,<br/>auto-sync video by detecting takeoff in IMU + video duration
Operator->>TestMode: Confirm sync, click Start SITL
TestMode->>ApiClient: api.post('/api/gps-denied-desktop/test/run', {sessionId})
ApiClient->>GpsDeskApi: POST /test/run
GpsDeskApi->>GpsBoardApi: stream IMU + frames (in-cluster)
GpsBoardApi-->>GpsDeskApi: positioning estimates
GpsDeskApi-->>TestMode: SSE stream of positioning estimates
TestMode-->>Operator: Render trajectory overlay + comparison chart
Preconditions
- A flight context exists (Test Mode is scoped within a flight).
- The
.tlogand video are valid; the IMU chart in the tlog is "drone-on-ground → take-off → land" so auto-sync can find the take-off event.
Open architectural questions for Test Mode
- Where does the IMU-based sync analysis live — in
gps-denied-desktop/, in a new Cython worker, or client-side? - Is SITL state persisted across page reloads (= server-side session), or is it in-memory client-side only?
- Output rendering — overlay on the GPS-Denied tab's existing map, or a dedicated comparison chart view?
These belong in Step 4.5 (Architecture Vision); Test Mode is in this flow inventory only as a planned flow so downstream consumers don't lose it.
Flow F13: Live-GPS aircraft telemetry (SSE) — added at Step 4
Description
When the user has a flight selected and FlightsPage is open, the page subscribes to a per-flight live-GPS event stream so the map can render the aircraft's current position in real time. Discovered at Step 4 verification (FlightsPage.tsx:67).
Sequence Diagram
sequenceDiagram
actor User
participant FlightsPage as 05_flights/FlightsPage
participant SseClient as 01_api-transport/sse
participant FlightsApi as flights/ service
User->>FlightsPage: Select a flight (Header dropdown)
FlightsPage->>SseClient: createSSE(`/api/flights/${flightId}/live-gps`, onEvent)
SseClient-->>FlightsApi: GET /api/flights/{flightId}/live-gps?token=... (EventSource)
loop while flight is selected
FlightsApi-->>SseClient: data: { lat, lon, satellites, status }
SseClient-->>FlightsPage: onEvent(payload)
FlightsPage-->>User: Update aircraft marker on map
end
User->>FlightsPage: Switch flight or navigate away
FlightsPage->>SseClient: source.close()
Error Scenarios
| Error | Where | Detection | Recovery |
|---|---|---|---|
| Bearer expires mid-stream | server | server closes the stream | EventSource auto-reconnects with stale token — same Step 8 hardening as F7 |
| Aircraft offline | data shape | status: 'offline' payload |
UI should grey out the marker — verify in Step 4 |
| User switches flights rapidly | client | new createSSE before old .close() |
Memory leak risk — verify cleanup in Step 4 |
Flow F14: Annotation-status events (SSE) — added at Step 4
Description
When the user opens AnnotationsPage with media selected, AnnotationsSidebar.tsx:25 subscribes to /api/annotations/annotations/events to receive annotation-status events (created / edited / validated). This is the SSE that exists today; it is NOT the detect-progress SSE (F7), which doesn't exist yet.
Sequence Diagram
sequenceDiagram
actor User
participant Sidebar as 06_annotations/AnnotationsSidebar
participant SseClient as 01_api-transport/sse
participant AnnotationsApi as annotations/ service
User->>Sidebar: Open AnnotationsPage (media selected)
Sidebar->>SseClient: createSSE('/api/annotations/annotations/events', onEvent)
SseClient-->>AnnotationsApi: GET /api/annotations/annotations/events?token=... (EventSource)
loop while page mounted
AnnotationsApi-->>SseClient: data: { annotationId, mediaId, status }
SseClient-->>Sidebar: onEvent(payload)
Sidebar->>Sidebar: refetch annotations for current media
end
User->>Sidebar: Navigate away
Sidebar->>SseClient: source.close()
Notes
- The stream is not scoped per media — every connected user receives all annotation-status events. Filtering happens client-side (
event.mediaId === selectedMedia.id). Acceptable for low-volume admin use; flag as a Step 6 problem-extraction surface for scale. - Bearer-rotation issue applies here too (Step 8 hardening).
Mermaid Diagram Conventions
- Participants: matched to component IDs
NN_name(e.g.,04_login,02_auth,01_api-transport). - External services: named after their suite-service folder (
admin/,flights/,annotations/,detect/,gps-denied-desktop/,gps-denied-onboard/). - Decision nodes:
{Question?}. - Start/End:
([label])stadium shape. - No styling — let the renderer theme handle it.