Implements the cycle-3-deferred AZ-512 task under the user-authorized
Option B path (MSW-stubbed; live deploy gates at Step 16 on AZ-513).
Code:
- src/features/admin/AdminPage.tsx — inline edit affordance:
editingId/editForm state, handleStartEdit/Cancel/Update, Enter+Escape
keyboard handling, colspan row swap when editing, pencil (✎) button
per row. Full-body PATCH (Risk 2). Single editingId enforces the
one-row-at-a-time invariant (Risk 3). Disabled buttons during the
in-flight PATCH (Risk 4). Inline role="alert" on validation/server
errors (no alert() per Finding B4 anti-pattern).
- src/i18n/{en,ua}.json — `admin.classes` flat → nested with `title`
+ 6 new keys (edit, save, cancel, nameRequired,
maxSizeMustBePositive, updateFailed). Parity gate FT-P-22 PASS.
Test infrastructure:
- tests/msw/handlers/admin.ts — PATCH /api/admin/classes/:id
partial-merge handler.
- tests/admin_class_edit.test.tsx — 12 tests covering AC-1..AC-6
+ AC-8 (AC-7 satisfied by static FT-P-22 gate).
- tests/destructive_ux.test.tsx — adjacent-hygiene selector fix
at 3 call sites: the new ✎ button moved the first-button
position; targeting × explicitly preserves the existing
it.fails()/control semantics.
Docs:
- _docs/02_document/components/08_admin/description.md — recorded
edit affordance + PATCH wiring + AZ-513 cross-workspace note.
- _docs/03_implementation/batch_16_cycle4_report.md
- _docs/03_implementation/implementation_report_admin_class_edit_cycle4.md
- _docs/02_tasks/todo → done — AZ-512 archived.
Quality gates: 32 files / 243 tests / 13 quarantined skips PASS;
all 35 static checks PASS (FT-P-22/23, STC-ARCH-01/02, STC-SEC*,
banned-deps incl. SEC1B/C/D).
Cross-workspace dependency: admin/ AZ-513 (POST + PATCH + DELETE
/classes routes) NOT yet shipped. Step 11 (Run Tests) passes on
stubs; Step 16 (Deploy) holds until AZ-513 lands live. Leftover
record at _docs/_process_leftovers/2026-05-13_az-512-admin-
classes-prereq.md stays open.
Discovered pre-existing bug (NOT bundled): tests/msw/handlers/
admin.ts returns paginate(seedUsers) for GET /api/admin/users,
but AdminPage consumes as flat User[] → users.map crash. Test
files use the same flat-array workaround
destructive_ux.test.tsx documented. Flagged in batch + impl
reports for separate triage.
Co-authored-by: Cursor <cursoragent@cursor.com>
3.8 KiB
08 — Admin
1. High-Level Overview
Purpose: Operator-only configuration page. User management, detection-class management, AI Settings, GPS Settings, aircraft default.
Architectural Pattern: Single-page feature, large monolithic component (~215 lines pre-consolidation per state.json).
Upstream dependencies: 00_foundation, 01_api-transport, 03_shared-ui (ConfirmDialog).
Downstream consumers: 10_app-shell (routed at /admin, currently with no role-based guard — see #1 caveat below).
2. Internal Interfaces
| Export | Notes |
|---|---|
AdminPage() |
Top-level route component. Sub-sections: Users, Detection Classes, AI Settings, GPS Settings, Aircraft default. Detection Classes table supports the full CRUD surface — add, edit (AZ-512 inline form on row click of the ✎ button; PATCH /api/admin/classes/{id} with full body per Risk-2 mitigation; Enter saves, Escape cancels; inline validation for empty name and non-positive maxSizeM; closes Architecture Vision P12), delete. |
3. External API Specification
| Method | Path | Purpose |
|---|---|---|
| GET / POST / PUT / DELETE | /api/admin/users |
User CRUD |
| GET | /api/annotations/classes |
Read class list (note: read uses annotations/, write uses admin/) |
| POST / PATCH / DELETE | /api/admin/classes |
Class CRUD. PATCH /api/admin/classes/{id} powers the inline edit affordance (AZ-512) and accepts a full or partial body of { name?, shortName?, color?, maxSizeM? }. Cross-workspace note: as of AZ-512 ship, the live admin/ service still owes the write routes (POST + PATCH + DELETE) per AZ-513 on admin/; UI ships against MSW stubs until that lands. |
| GET / PUT | /api/admin/settings/ai |
AI service config |
| GET / PUT | /api/admin/settings/gps |
GPS device config |
| GET / PUT | /api/admin/settings/aircraft-default |
Aircraft default |
5. Implementation Details
State Management: Page-local React state per sub-form. No global form library.
Findings (B4, copied from state.json):
- PRIORITY (security): no role-based route guard on
/admin— anyone authenticated can access. Server-enforced 403 protects the data, but UI does not gate. Surface in Step 6 problem-extraction. Cross-link with10_app-shell. - AI Settings & GPS Settings forms render with
defaultValueonly — NO state, NO submit handler, the Save button does nothing. PRIORITY surface in Step 6. - Hardcoded GPS device default
'192.168.1.100'/ port'5535'shipped in production bundle. Step 4. handleDeleteClasshas NOConfirmDialogdespite being destructive. Step 4 vsui_design/README.md.- Service split mismatch: detection-class read uses
/api/annotations/classes(annotations service) but write uses/api/admin/classes(admin service). Verify with suite ADRs in Step 3a. handleToggleDefaultduplicated between AdminPage and SettingsPage; aircraft default is global config but page exists in both/adminand/settings— surface intent in Step 6.- Many hardcoded English strings. Step 4 i18n.
Key Dependencies: 03_shared-ui/ConfirmDialog (used for some destructive actions; missing on handleDeleteClass).
7. Caveats & Edge Cases
- The broken Save button is the most user-visible bug.
- The annotations/admin service split for class CRUD looks like a copy-paste residue but may be deliberate; verify in Step 3a.
- No optimistic concurrency / version check for any settings — last writer wins.
8. Dependency Graph
Must be implemented after: 00_foundation, 01_api-transport, 03_shared-ui.
Can be implemented in parallel with: every other feature page.
Blocks: 10_app-shell.
Module Inventory
| Path | Module Doc |
|---|---|
src/features/admin/AdminPage.tsx |
_docs/02_document/modules/src__features__admin__AdminPage.md |