Implements 22 blackbox test scenarios across the four batch-2 tasks:
AZ-457 - Auth & token handling (11 scenarios, fast + e2e):
- src/api/client.test.ts: FT-P-02, NFT-SEC-04, NFT-PERF-02, NFT-RES-01,
NFT-RES-08 (apiClient surface)
- src/auth/AuthContext.test.tsx: FT-P-01 (it.fails - Step 4 drift),
FT-P-03, NFT-SEC-01, NFT-SEC-02
- src/auth/ProtectedRoute.test.tsx: FT-N-04, NFT-RES-08 (router half)
- e2e/tests/auth.e2e.ts: FT-P-02 e2e, NFT-SEC-01/02/03 (cookie attrs
via Playwright context.cookies(), gated by suite stack)
AZ-459 - Wire-contract enums (4 scenarios):
- tests/wire_contract.test.ts: FT-P-04 (AnnotationStatus, it.fails),
FT-P-05 (MediaStatus + Affiliation it.fails; CombatReadiness skip
per verification_pending), FT-P-06 (AnnotationSource control +
spec value-set membership), FT-N-15 (typed-enum shape + skip for
value-set verification)
- e2e/tests/wire_contract.e2e.ts: FT-P-06 against real annotations/
service, drift-gated via AZAION_RUN_DRIFT_E2E
- scripts/run-tests.sh STC-FN15: ripgrep static for MediaType
magic-literal hygiene
AZ-465 - i18n (4 scenarios, all static + quarantined fast):
- scripts/check-i18n-coverage.mjs: FT-P-22 (en vs ua key parity) +
FT-P-23 (no raw user strings outside t() in src/**/*.tsx); refined
JSX text-node regex with negative lookbehind to drop TS generics
+ arrow-function false positives
- tests/i18n-allowlist.json: snapshot of current pre-existing raw
strings (CI gates growth per AZ-465 Constraints)
- tests/i18n.test.tsx: FT-P-24 + FT-P-25 it.skip (QUARANTINE - i18n
detector + persistence not wired today; control tests assert the
gap so the skip flips to a real test once Step 4 lands)
AZ-481 - CI image labels (3 scenarios, static against
.woodpecker/build-arm.yml):
- scripts/check-ci-image-labels.mjs: NFT-RES-LIM-11 (tag scheme
${CI_COMMIT_BRANCH}-arm), NFT-RES-LIM-12 (revision/created/source
PASS, image.title reported as DRIFT - foundation/CI-CD owns the
fix), NFT-RES-LIM-13 (revision = $CI_COMMIT_SHA)
Cross-cutting:
- scripts/run-tests.sh: src_grep now excludes *.test.{ts,tsx} +
*.spec.{ts,tsx} so production-source static checks (STC-SEC4,
STC-FN15, etc.) don't false-positive on test prose
- tsconfig.json: exclude src/**/*.{test,spec}.{ts,tsx} so production
tsc -b doesn't see jest-dom matchers
- _docs/03_implementation/batch_02_report.md: full per-task AC
coverage matrix + drift inventory + verification run
- _docs/_autodev_state.md: 22 tasks remain after batch 2
Verification (host):
fast : 7 files, 38 passed | 4 skipped (quarantined)
static : 19/19 checks PASS (was 13 in batch 1; +6 from batch 2)
e2e : not run on host (Risk 4 - requires suite docker stack)
Co-authored-by: Cursor <cursoragent@cursor.com>
azaion-ui
React SPA that serves as the single front-end for the entire Azaion suite.
It does not own data or business logic; it is the operator's window onto
every backend service that lives as a sibling submodule in the
azaion/suite monorepo.
suite/
├── annotations/ .NET 10 — Annotations / Media / Datasets / Settings
├── flights/ .NET 10 — Flights, waypoints, aircrafts, GPS SSE
├── detections/ Cython — YOLO inference (ONNX / TensorRT)
├── detections-semantic/ Cython — Semantic detection
├── loader/ Cython — Encrypted resource loader
├── gps-denied-onboard/ Python — GPS-denied positioning (UAV side)
├── gps-denied-desktop/ Python — GPS-denied positioning (operator side)
├── autopilot/ Python — UAV control via MAVLink
├── admin/ .NET 10 — Users, roles, detection classes, model versions
├── ai-training/ Python — YOLO training, ONNX export
├── satellite-provider/ .NET 10 — Google Maps tile cache
└── ui/ React 19 — ◄ this repository
For the suite-wide architecture, deployment topology, database design, and the legacy WPF predecessor see:
suite/_docs/— system-level architecture and per-service feature docs (canonical source of truth)._docs/legacy/wpf-era.md— what the pre-rewrite Windows desktop application looked like, why the React port exists, and which behaviours are intentionally being preserved._docs/ui_design/— page-level wireframes (HTML mockups + a design README) inherited from the WPF UI; the authoritative reference for layout, keyboard shortcuts, color scheme, affiliation icons, annotation row gradient, etc.
Status. The code currently in
src/is a rudimentary first cut mechanically translated from the legacy WPF / XAML UI. It is not yet fully wired to the new suite APIs and has no test coverage. Formal bottom-up documentation, a test specification, a testability pass, and the initial test suite are being produced via the/autodevexisting-code flow. When the documentation + safety net are in place, feature work resumes through the same flow.
What this repo is — and is not
this repo (ui/) |
the suite services | |
|---|---|---|
| Owns | rendering, routing, client-side state, i18n, drag-and-drop, the Leaflet map, <video> playback, optimistic UI, keyboard shortcuts, layout persistence |
data, persistence, queues, inference, model delivery, telemetry, RBAC, tile caching, autopilot |
| Talks to | every backend submodule via REST + SSE (HTTP) | each other via REST / SSE / RabbitMQ; no shared source code |
| Builds to | a static bundle served by nginx (Dockerfile) |
individual service Docker images |
| Runs on | operator station (laptop / tablet / mini-PC); browser of choice | edge device or remote server, per service tier (see suite/_docs/00_top_level_architecture.md) |
| Reaches the network | only through the operator's browser, with the user's JWT | service-to-service inside the docker network and to admin/ over the internet |
The React app must remain stateless beyond what localStorage /
UserSettings (Annotations API) can hold. No business rules in the UI.
Pages → backend submodules
| Page | Route | Primary submodule(s) consumed |
|---|---|---|
| Login | /login |
admin/ (auth + user) |
| Flights | /flights |
flights/, gps-denied-desktop/, gps-denied-onboard/ (live GPS SSE), satellite-provider/ (orthophoto reference tiles), autopilot/ (mission status) |
| Annotations | /annotations |
annotations/ (media, annotations, settings), detections/ + detections-semantic/ (AI detect), loader/ (model availability status only) |
| Dataset Explorer | /dataset |
annotations/ (queries, validation), ai-training/ (class distribution, dataset health), admin/ (detection classes seed) |
| Admin | /admin |
admin/ (users, detection classes, AI recognition + GPS device + model-version settings) |
| Settings | /settings |
annotations/ (tenant / directories / aircrafts), admin/ (connection check) |
Header exposes a global flight selector — the selected flight is the
application context and most other pages auto-filter by it. The
selection is persisted server-side via UserSettings in the
annotations/ API.
For the full UX spec (wireframes, interactions, keyboard shortcuts, color
tokens, affiliation icons, combat-readiness indicator, gradient annotation
list, time-window video annotation overlay, drawer behaviour, etc.) see
_docs/ui_design/README.md.
Tech stack
- React 19 + TypeScript 5.7
- Vite 6 (
bun run dev/bun run build) - Bun 1.3.11 as package manager and runtime (
packageManagerfield) - Tailwind CSS 4 via
@tailwindcss/vite - react-router-dom 7 for routing
- react-i18next for UA/EN localization (see
src/i18n/) - leaflet + react-leaflet + leaflet-draw + leaflet-polylinedecorator for the Flights map
- chart.js + react-chartjs-2 for telemetry / class distribution
- @hello-pangea/dnd for waypoint reordering
- react-icons for icon set
- react-dropzone for orthophoto upload
There is no client-side state library — local component state and
Context (AuthContext, FlightContext) are sufficient at this scale.
Server data is fetched on demand and not cached beyond component
lifetime; introduce TanStack Query when the test suite is in place if
the lack of caching becomes a real problem.
Repository layout
src/
├── main.tsx app entry point (creates the React root)
├── App.tsx routes, AuthProvider, FlightProvider
├── index.css tailwind base + custom CSS variables
├── api/
│ ├── client.ts fetch wrapper, JWT injection, error normalization
│ └── sse.ts EventSource helper used by flights + detections
├── auth/
│ ├── AuthContext.tsx JWT lifecycle, user identity, role
│ └── ProtectedRoute.tsx route guard
├── components/ shared UI (Header, FlightContext, ConfirmDialog,
│ DetectionClasses, HelpModal …)
├── features/
│ ├── login/ LoginPage
│ ├── flights/ FlightsPage + map, sidebar, params panel,
│ │ waypoint list, altitude chart, mini-map,
│ │ wind effect, JSON editor, draw control
│ ├── annotations/ AnnotationsPage + canvas editor, video player,
│ │ media list, annotations sidebar, class colors
│ ├── dataset/ DatasetPage
│ ├── admin/ AdminPage
│ └── settings/ SettingsPage
├── hooks/ useDebounce, useResizablePanel
├── i18n/ i18n.ts + en.json + ua.json
└── types/ shared TS types
_docs/
├── legacy/ history of the WPF predecessor
└── ui_design/ page-level wireframes inherited from the WPF UI
.cursor/ workspace-scoped agents/rules/skills
.woodpecker/ CI pipeline (Woodpecker, ARM build target)
Dockerfile multi-stage: bun build → nginx static
nginx.conf production nginx config (SPA fallback to /index.html)
vite.config.ts vite + react + tailwind plugin
tsconfig.json TS config
The skeleton mirrors the page-per-feature, control-per-domain
decomposition of the legacy WPF app:
Azaion.Annotator → features/annotations/,
Azaion.Dataset → features/dataset/,
Azaion.Suite.MainSuite → components/Header.tsx, etc. See
_docs/legacy/wpf-era.md §10 for the per-feature mapping and §11 for
the parts that are intentionally NOT being ported.
Local development
Prerequisites:
- Bun 1.3.11+
- A reachable suite (or at least the
admin/andannotations/services). For an all-in-one local stack, seesuite/_infra/dev/README.md.
# from suite/ui/
bun install --frozen-lockfile
bun run dev # http://localhost:5173 (Vite)
bun run build # tsc -b + vite build → dist/
bun run preview # serve the production build locally
API base URL is configured via Vite environment variables. Copy
.env.example (when introduced — currently the wiring is hardcoded;
this is one of the testability fixes scheduled for /autodev Step 4)
to .env.local before bun run dev. Until then, see
src/api/client.ts for the current base-URL strategy.
Production build
The production image is a static bundle behind nginx:
docker build -t azaion-ui:dev .
docker run --rm -p 8080:80 azaion-ui:dev
# open http://localhost:8080/
The image expects the suite's reverse-proxy / ingress to terminate TLS
and forward /api/* to the appropriate backend service. See
suite/_infra/deploy/ for per-target compose files.
CI
CI runs in self-hosted Woodpecker, building the multi-arch Docker
image and pushing it to the suite's Harbor registry. Pipeline: see
.woodpecker/build-arm.yml. Suite-wide CI
infra: suite/_infra/ci/README.md.
The build is triggered on push to dev, stage, and main. Image tags
are branch-tagged; no latest tag is published.
How this repo is being matured
- Document the existing code — bottom-up, via the
/documentskill:_docs/02_document/will hold module docs, component specs, architecture, and a final report. Triggered automatically as Step 1 of the existing-code flow. - Architecture baseline scan of the produced architecture against
the codebase (
_docs/02_document/architecture_compliance_baseline.md). - Test specifications — produced from the documentation, not from
the code, so the tests describe intended behaviour. Output:
_docs/02_document/tests/. - Testability revision — the smallest possible refactor that lets tests run (replace hardcoded API URLs with env vars, etc.). Strictly minimal; deeper refactoring is deferred to step 8.
- Decompose tests into individual tasks and implement them.
- Run tests — green tests become the safety net.
- (Optional) Refactor the codebase against the safety net.
- From there, the project enters the existing-code feature cycle: new task → implement → run tests → sync test specs → update docs → security audit → performance test → deploy → retrospective, and loops.
The state machine for this is in _docs/_autodev_state.md.
The orchestration definition is in
.cursor/skills/autodev/SKILL.md.
License
See the parent suite repository.