mirror of
https://github.com/azaion/ui.git
synced 2026-06-21 09:21:10 +00:00
[AZ-512] Cycle 4 Steps 12-15: test-spec sync + docs + sec + perf
ci/woodpecker/push/build-arm Pipeline failed
ci/woodpecker/push/build-arm Pipeline failed
Steps 12-15 closure for cycle 4 (AZ-512 admin class inline edit):
- Step 12 (Test-Spec Sync): traceability O9 -> Covered; new FT-P-62
+ FT-N-18 in blackbox-tests.md.
- Step 13 (Update Docs): AdminPage module doc gains the inline-edit
state slots, four new handlers, PATCH integrations row, expanded
i18n key list, tests section. architecture.md row 272 now lists
PATCH /api/admin/classes/{id} with AZ-513 deploy-gate caveat.
- Step 14 (Security Audit): cycle-4 delta report records one new
LOW finding (F-SAST-CY4-1 lost-update / mid-air-collision on
PATCH, by design per spec); verdict carries PASS_WITH_WARNINGS;
bun audit re-run clean.
- Step 15 (Performance Test): NFT-PERF-01 bundle = 291 332 B
(+757 B / +0.26% vs cycle 3; ~13.89% of 2 MB budget); PASS.
Tests 243 passed / 13 skipped / 0 failed (+12 AZ-512 cases).
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -269,7 +269,7 @@ contract beautifully and accessibly".
|
||||
| `06_annotations/AnnotationsSidebar` | `annotations/`, `detect/` | REST + SSE | Request-Response + Event | `POST /api/detect/${mediaId}` (sync detect — used for BOTH images and videos today); `createSSE('/api/annotations/annotations/events', ...)` for **annotation-status SSE** (NOT detect progress). **No `/api/detect/video/${id}` and no `/api/detect/stream/${jobId}` are wired today** — finding #10 / #21 confirmed. |
|
||||
| `06_annotations/CanvasEditor` | `annotations/` | static asset GET | — | `GET /api/annotations/annotations/${id}/image` (annotation thumbnail), `GET /api/annotations/media/${id}/file` (raw media). |
|
||||
| `07_dataset/DatasetPage` | `annotations/` | REST | Request-Response | `GET /api/annotations/dataset?...`, `GET /api/annotations/dataset/${annotationId}`, `POST /api/annotations/dataset/bulk-status`, **`GET /api/annotations/dataset/class-distribution`** (the endpoint **already exists**; the chart UI is what's missing — see `01_legacy_coverage_gaps.md`), `<img src="/api/annotations/annotations/${id}/thumbnail">`. **Editor tab does not save** — finding #4. |
|
||||
| `08_admin/AdminPage` | `annotations/` + `admin/` + `flights/` | REST | Request-Response | `GET /api/annotations/classes` (read), `POST /api/admin/classes` (create), `DELETE /api/admin/classes/${id}` (delete — no ConfirmDialog, finding B4), `POST /api/admin/users`, `PATCH /api/admin/users/${id}` (deactivate), `GET /api/flights/aircrafts`, `PATCH /api/flights/aircrafts/${id}`. **Cross-service reads** — admin page reads aircraft from `flights/` and classes from `annotations/`. |
|
||||
| `08_admin/AdminPage` | `annotations/` + `admin/` + `flights/` | REST | Request-Response | `GET /api/annotations/classes` (read), `POST /api/admin/classes` (create), **`PATCH /api/admin/classes/${id}` (update — AZ-512 inline edit; full body always sent per Risk-2 mitigation; live deploy gates on `admin/` AZ-513)**, `DELETE /api/admin/classes/${id}` (delete — no ConfirmDialog, finding B4), `POST /api/admin/users`, `PATCH /api/admin/users/${id}` (deactivate), `GET /api/flights/aircrafts`, `PATCH /api/flights/aircrafts/${id}`. **Cross-service reads** — admin page reads aircraft from `flights/` and classes from `annotations/`. |
|
||||
| `09_settings/SettingsPage` | `annotations/` + `flights/` | REST | Request-Response | `GET/PUT /api/annotations/settings/system`, `GET/PUT /api/annotations/settings/directories`, `GET /api/flights/aircrafts`, `PATCH /api/flights/aircrafts/${id}`. **Settings endpoints route to `annotations/`**, NOT `admin/` as initially drafted. |
|
||||
| `05_flights/FlightsPage` | `flights/` | REST + SSE | Request-Response + Event | `GET /api/flights/aircrafts`, `GET /api/flights/${id}/waypoints`, **`createSSE('/api/flights/${id}/live-gps', ...)` — live-GPS SSE for aircraft telemetry**, `POST /api/flights`, `DELETE /api/flights/${id}`, `DELETE /api/flights/${id}/waypoints/${wpId}` (loop), `POST /api/flights/${id}/waypoints` (loop, lossy shape — finding #20). |
|
||||
| `05_flights/flightPlanUtils` | OpenWeatherMap (external) | REST | Request-Response | `GET https://api.openweathermap.org/data/2.5/onecall?...` with env-resolved key + base URL since AZ-448 / AZ-449 (closes the original security finding; see AC-20 + AC-42). |
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
# Module: `src/features/admin/AdminPage.tsx`
|
||||
|
||||
> **Source**: `src/features/admin/AdminPage.tsx` (209 lines)
|
||||
> **Source**: `src/features/admin/AdminPage.tsx`
|
||||
> **Topo batch**: B4 (depends on B3: `api/client`, `components/ConfirmDialog`, `types/index`)
|
||||
> **Cycle 4 update (2026-05-13, AZ-512)**: gained an inline "edit detection class" affordance — see the new state slots, the `handleStartEdit / handleCancelEdit / handleUpdateClass / handleEditKeyDown` handlers, the PATCH row in the External integrations table, the new i18n keys consumed, and the FT-P-62 / FT-N-18 entries under Tests. Closes Architecture Vision principle **P12** (Objective O9 in `tests/traceability-matrix.md`). Implementation shipped against MSW stubs under the user-authorized Option B path; the live deploy gate remains until AZ-513 ships on the `admin/` workspace.
|
||||
|
||||
## Purpose
|
||||
|
||||
@@ -37,6 +38,16 @@ No props. Reads everything via `api/client` and local state.
|
||||
'Annotator' }`).
|
||||
- `deactivateId: string | null` — drives the `ConfirmDialog`'s open
|
||||
state for user deactivation.
|
||||
- `editingId: number | null` — id of the detection class currently
|
||||
in inline-edit mode (AZ-512). A single value, not per-row, so
|
||||
opening one row's editor closes any other (AC-2 single-row
|
||||
invariant / Risk 3 mitigation).
|
||||
- `editForm: { name; shortName; color; maxSizeM }` — the inline-edit
|
||||
staging buffer; seeded from the row on edit-start.
|
||||
- `editError: 'nameRequired' | 'maxSizeMustBePositive' | 'updateFailed' | null` —
|
||||
discriminated error kind rendered as an inline `role="alert"`.
|
||||
- `editSaving: boolean` — disables Save + Cancel while the PATCH is
|
||||
in flight (Risk 4 mitigation).
|
||||
- **Bootstrap effect** (`useEffect([])` — runs once at mount):
|
||||
|
||||
```ts
|
||||
@@ -68,6 +79,30 @@ No props. Reads everything via `api/client` and local state.
|
||||
ConfirmDialog** despite this being destructive. Inconsistent with
|
||||
the user-deactivation flow which uses ConfirmDialog. Flag for Step 4
|
||||
against `_docs/ui_design/README.md` confirmation-dialog spec.
|
||||
- **`handleStartEdit(c)`** (AZ-512): sets `editingId = c.id`, seeds
|
||||
`editForm` from `c`, clears `editError`. Triggered by the per-row
|
||||
pencil (✎) affordance.
|
||||
- **`handleCancelEdit()`** (AZ-512): clears `editingId`, `editError`,
|
||||
`editSaving`. No network call. Also fires on **Escape** inside the
|
||||
form (AC-4).
|
||||
- **`handleUpdateClass()`** (AZ-512):
|
||||
1. Guard: `editingId !== null && !editSaving`.
|
||||
2. Validation: `editForm.name.trim()` non-empty (else
|
||||
`setEditError('nameRequired')`); `editForm.maxSizeM > 0` (else
|
||||
`setEditError('maxSizeMustBePositive')`). Both pre-empt the
|
||||
network call (AC-5).
|
||||
3. `setEditSaving(true)`.
|
||||
4. `await api.patch(endpoints.admin.class(editingId), editForm)` —
|
||||
**the complete `editForm` is always sent** (Risk 2 mitigation:
|
||||
the backend's partial-merge vs full-replace semantics become
|
||||
equivalent for the UI).
|
||||
5. On success: `await api.get(endpoints.annotations.classes())`,
|
||||
`setClasses(...)`, `setEditingId(null)`.
|
||||
6. On failure: `setEditError('updateFailed')` — form stays open,
|
||||
edits intact, NO `alert()` (Finding B4 anti-pattern).
|
||||
- **`handleEditKeyDown(e)`** (AZ-512): Enter → `handleUpdateClass`;
|
||||
Escape → `handleCancelEdit`. Wired at the container level so any
|
||||
input in the form respects it.
|
||||
- **`handleAddUser()`** — analogous to `handleAddClass` against
|
||||
`POST endpoints.admin.users()` and `GET endpoints.admin.users()`
|
||||
(both → `/api/admin/users`). Guards on `email && password`.
|
||||
@@ -85,6 +120,11 @@ No props. Reads everything via `api/client` and local state.
|
||||
the UI does not).
|
||||
- **Layout** (left → center → right, all in one horizontal flex):
|
||||
- **Left column** (`w-[340px]`): detection-classes table + add row.
|
||||
Each read-only row carries a pencil (✎) edit button and a `×`
|
||||
delete button (AZ-512). When `c.id === editingId`, that row's
|
||||
cells collapse into a single `colspan=3` form holding name /
|
||||
shortName / color / maxSizeM inputs + Save + Cancel (with an
|
||||
inline `role="alert"` directly below on validation/server error).
|
||||
- **Center column** (`flex-1 max-w-md`): AI settings form, GPS
|
||||
settings form, users table + add row. The AI and GPS forms have
|
||||
`defaultValue` only — there is **no** state, no `Save` handler
|
||||
@@ -115,10 +155,15 @@ backend assigns `id` and other server-managed fields.
|
||||
|
||||
## Configuration
|
||||
|
||||
- **i18n keys consumed**: `admin.classes`, `admin.aiSettings`,
|
||||
- **i18n keys consumed**: `admin.classes.title` (was flat
|
||||
`admin.classes` pre-AZ-512), `admin.classes.edit`,
|
||||
`admin.classes.save`, `admin.classes.cancel`,
|
||||
`admin.classes.nameRequired`, `admin.classes.maxSizeMustBePositive`,
|
||||
`admin.classes.updateFailed`, `admin.aiSettings`,
|
||||
`admin.gpsSettings`, `admin.users`, `admin.aircrafts`,
|
||||
`admin.deactivate`, `common.save`. (Confirmed present in
|
||||
`src/i18n/en.json` admin/common groups.) Plenty of hardcoded
|
||||
`src/i18n/en.json` admin/common groups; ua mirror enforced by the
|
||||
FT-P-22 parity gate.) Plenty of hardcoded
|
||||
English strings — placeholders ("Name", "Email", "Password"), table
|
||||
headers (`#`, `Name`, `Color`, `Email`, `Role`, `Status`), role
|
||||
options (`Annotator`, `Admin`, `Viewer`), the GPS protocol options
|
||||
@@ -143,6 +188,7 @@ backend assigns `id` and other server-managed fields.
|
||||
|---|---|---|
|
||||
| `GET` | `endpoints.annotations.classes()` → `/api/annotations/classes` | List detection classes (read path uses annotations service) |
|
||||
| `POST` | `endpoints.admin.classes()` → `/api/admin/classes` | Create detection class (write path uses admin service) |
|
||||
| `PATCH` | `endpoints.admin.class(id)` → `/api/admin/classes/{id}` | Update detection class (AZ-512 — full body always sent; same URL as DELETE, no new endpoint helper introduced per task constraint) |
|
||||
| `DELETE` | `endpoints.admin.class(id)` → `/api/admin/classes/{id}` | Delete detection class |
|
||||
| `GET` | `endpoints.flights.aircrafts()` → `/api/flights/aircrafts` | List aircraft |
|
||||
| `PATCH` | `endpoints.flights.aircraft(id)` → `/api/flights/aircrafts/{id}` | Toggle `isDefault` |
|
||||
@@ -175,7 +221,19 @@ Path builders live in `src/api/endpoints.ts` (since AZ-486 / F7). Routed by `ngi
|
||||
|
||||
## Tests
|
||||
|
||||
None.
|
||||
- `tests/admin_class_edit.test.tsx` (cycle 4, AZ-512) — 12 cases
|
||||
covering AC-1 through AC-6 + AC-8; AC-7 covered by the static
|
||||
FT-P-22 i18n parity gate. Traces to FT-P-62 + FT-N-18 in
|
||||
`_docs/02_document/tests/blackbox-tests.md`.
|
||||
- `tests/destructive_ux.test.tsx` (cycle 1) — AZ-466 class-delete
|
||||
destructive-UX `it.fails()` + control pair. Updated cycle 4 to
|
||||
target the `×` delete button by text after the AZ-512 ✎ button
|
||||
was added to the same row's action cell.
|
||||
|
||||
No dedicated `AdminPage` happy-path test predates AZ-512; the AC-8
|
||||
regression guard in `admin_class_edit.test.tsx` covers Add and
|
||||
Delete inline. A broader AdminPage test fixture is a Phase B
|
||||
candidate.
|
||||
|
||||
## Notes / open questions
|
||||
|
||||
|
||||
@@ -1649,6 +1649,50 @@ The scenarios below were appended via `/test-spec` cycle-update mode after Phase
|
||||
|
||||
---
|
||||
|
||||
### FT-P-62: AdminPage class edit — inline form + PATCH wire contract + refresh
|
||||
|
||||
**Traces to**: O9 (P12) — landed cycle 4 / 2026-05-13 by AZ-512.
|
||||
**Profile**: fast
|
||||
|
||||
**Input data**: an `<AdminPage>` mount with at least one detection class loaded via `GET /api/annotations/classes`; the user activates the row's edit (✎) affordance.
|
||||
|
||||
**Steps**:
|
||||
|
||||
| Step | Consumer Action | Expected System Response |
|
||||
|------|----------------|------------------------|
|
||||
| 1 | Inspect each rendered row | One edit (✎) button per class row (AC-1) |
|
||||
| 2 | Click the edit (✎) on row N | Row N replaces its read-only cells with editable `name` / `shortName` / `color` / `maxSizeM` inputs seeded with the row's current values; Save + Cancel buttons appear; no other row is in edit mode (AC-2 single-row invariant) |
|
||||
| 3 | Click edit (✎) on row M while row N is editing | Row N reverts to read-only; row M enters edit mode |
|
||||
| 4 | Modify `name` and click **Save** (or press **Enter** inside the form) | Exactly one `PATCH /api/admin/classes/{N}` is observed with body `{ name, shortName, color, maxSizeM }` (full body per Risk-2 mitigation); on 200/2xx `<AdminPage>` re-fetches via `GET /api/annotations/classes` and row N re-renders read-only with the new values (AC-3) |
|
||||
|
||||
**Pass criteria**: zero PATCH calls before step 4; exactly one PATCH in step 4 with the complete editable shape; URL pattern `^/api/admin/classes/\d+$`; success-path refresh observed via the existing `GET /api/annotations/classes` builder (no new endpoint introduced — `endpoints.admin.class(id)` reused per task constraint).
|
||||
**Max execution time**: 5s.
|
||||
**Expected result source**: `_docs/02_tasks/done/AZ-512_admin_edit_detection_class.md` AC-1..AC-3.
|
||||
|
||||
---
|
||||
|
||||
### FT-N-18: AdminPage class edit — error paths (Cancel, validation, 5xx)
|
||||
|
||||
**Traces to**: O9 (P12), O10 (B4 anti-pattern: no `alert()`) — landed cycle 4 / 2026-05-13 by AZ-512.
|
||||
**Profile**: fast
|
||||
|
||||
**Input data**: `<AdminPage>` mounted with at least one class loaded; the row's edit form is open.
|
||||
|
||||
**Steps**:
|
||||
|
||||
| Step | Consumer Action | Expected System Response |
|
||||
|------|----------------|------------------------|
|
||||
| 1 | Modify any field; click **Cancel** (or press **Escape** in the form) | Zero PATCH observed; row reverts to original read-only values (AC-4) |
|
||||
| 2 | Clear `name`; click Save | Zero PATCH observed; inline `role="alert"` element renders `admin.classes.nameRequired` (en / ua localized) (AC-5) |
|
||||
| 3 | Set `maxSizeM ≤ 0` or NaN; click Save | Zero PATCH observed; inline `role="alert"` renders `admin.classes.maxSizeMustBePositive` (AC-5) |
|
||||
| 4 | Stub PATCH to return 500; click Save with valid fields | Exactly one PATCH observed (counterpart to FT-P-62 step 4); form stays open with the user's edits intact; inline `role="alert"` renders `admin.classes.updateFailed`; `window.alert` is NEVER called (AC-6 — Finding B4 anti-pattern enforced) |
|
||||
|
||||
**Pass criteria**: every error path produces exactly the documented network footprint and exactly the documented inline error key; `window.alert` is spied and asserted-zero across the entire scenario (the STC-SEC7 static check independently guards the no-`alert()` invariant in production source).
|
||||
**Max execution time**: 10s.
|
||||
**Expected result source**: `_docs/02_tasks/done/AZ-512_admin_edit_detection_class.md` AC-4 / AC-5 / AC-6.
|
||||
|
||||
---
|
||||
|
||||
## Notes carried into Phase 3
|
||||
|
||||
- All tests tagged `quarantined` correspond to features either pending a Step 4 fix (e.g., AC-13 i18n detector, AC-21 panel persistence, AC-22 role-gate, AC-26/27 form hygiene, AC-39 split surface, AC-40 tile zoom) or pending Phase B implementation (AC-11 bundle gate, AC-24 SSE refresh, AC-25 async video, AC-40 tile zoom). The test is written so it activates the day the implementation lands; Phase 3 will surface them for downgrade or accept.
|
||||
|
||||
@@ -96,7 +96,7 @@ Maps every acceptance criterion and every restriction in `_docs/00_problem/` to
|
||||
| O6 | No hardcoded credentials | NFT-SEC-09 | Covered |
|
||||
| O7 | Spec is source of truth for numeric enums | FT-P-04, FT-P-05, FT-P-06 | Covered |
|
||||
| O8 | Persist what you type (panel widths) | FT-P-37 [Q], FT-P-38 [Q] | Covered (quarantined) |
|
||||
| O9 | Admin can edit existing detection classes (P12) | NOT COVERED — feature missing today (`acceptance_criteria.md` notes P12 violation; PATCH endpoint to re-introduce in Phase B). **Cycle 3 (2026-05-13)**: AZ-512 attempted this, but its spec-defined Cross-Workspace Verification BLOCKING gate failed — admin/ service exposes no /classes routes at all (not even the POST/DELETE that AdminPage already calls today). Task parked in `_docs/02_tasks/backlog/AZ-512_*.md` with leftover `_docs/_process_leftovers/2026-05-13_az-512-admin-classes-prereq.md` until the admin/ workspace ships POST + PATCH + DELETE /classes. | NOT COVERED — Phase B target (deferred; cross-workspace prerequisite outstanding) |
|
||||
| O9 | Admin can edit existing detection classes (P12) | FT-P-62, FT-N-18 — landed cycle 4 / 2026-05-13 by AZ-512 (UI-side; user-authorized Option B path — implementation shipped against MSW stubs). **Live deploy gate remains** until AZ-513 ships on `admin/` and is deployed: `POST | PATCH | DELETE /classes` is verified-missing on the live admin service today; leftover `_docs/_process_leftovers/2026-05-13_az-512-admin-classes-prereq.md` stays open until then. | Covered (UI implementation + stub-tested); cross-workspace deploy gate pending AZ-513 on `admin/` |
|
||||
| O10 | Destructive actions require ConfirmDialog | NFT-SEC-08, FT-P-26, FT-P-27, FT-N-07 | Covered |
|
||||
| O11 | No SSR / RSC | NFT-RES-LIM-03 (no Node in image) + STC-O11 (no `react-dom/server` import) | Partially Covered |
|
||||
| O12 | `mission-planner/` not compiled by production Vite build | NFT-RES-LIM-04 | Covered |
|
||||
|
||||
Reference in New Issue
Block a user