Files
ui/_docs/02_document/system-flows.md
T
Oleksandr Bezdieniezhnykh 510df68bcf [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>
2026-05-11 00:38:49 +03:00

32 KiB
Raw Blame History

Azaion UI — System Flows

Synthesis output of /document Step 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 F4F11 (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:

  1. Bootstrap pathAuthContext.tsx:24 calls api.get('/api/admin/auth/refresh') on app mount. This does NOT have credentials:'include' because api/client.ts doesn'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.
  2. 401-retry pathapi/client.ts:44 calls fetch('/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 19 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 (with X-Refresh-Token header, 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 .tlog and 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

  1. Where does the IMU-based sync analysis live — in gps-denied-desktop/, in a new Cython worker, or client-side?
  2. Is SITL state persisted across page reloads (= server-side session), or is it in-memory client-side only?
  3. 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.