[AZ-512] Admin class inline edit form + PATCH wiring (cy4 batch 16)

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>
This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-05-13 04:35:13 +03:00
parent ef56d9c207
commit ecacfa8b43
10 changed files with 718 additions and 14 deletions
@@ -0,0 +1,89 @@
# Batch Report
**Batch**: 16
**Cycle**: 4 (autodev existing-code Step 10)
**Tasks**: [AZ-512]
**Date**: 2026-05-13
**Reactivation context**: AZ-512 was deferred to backlog at the end of cycle 3 (Cross-Workspace Verification BLOCKING gate failed — `admin/` service does not expose `/classes` write routes). User authorized **Option B** (MSW-stubbed UI ahead of admin/ AZ-513 shipping) at cycle 4 entry. Task moved `backlog/``todo/` in commit `ef56d9c`.
## Task Results
| Task | Status | Files Modified | Tests | AC Coverage | Issues |
|------|--------|---------------|-------|-------------|--------|
| AZ-512_admin_edit_detection_class | Done | 5 production + test + 1 doc | 12 passed | 8/8 ACs covered | 1 noted (pre-existing) |
### Files modified
| Path | Type | Change |
|------|------|--------|
| `src/features/admin/AdminPage.tsx` | OWNED (08_admin) | Added inline edit affordance: `editingId` / `editForm` / `editError` / `editSaving` state; handlers (`handleStartEdit`, `handleCancelEdit`, `handleUpdateClass`, `handleEditKeyDown`); colspan row swap when editing; pencil (✎) button on read-only rows. Updated `t('admin.classes')``t('admin.classes.title')`. |
| `src/i18n/en.json` | spec-authorized (00_foundation) | Restructured `admin.classes` from flat string to nested object (`title` + 6 new keys: `edit`, `save`, `cancel`, `nameRequired`, `maxSizeMustBePositive`, `updateFailed`). |
| `src/i18n/ua.json` | spec-authorized (00_foundation) | Ukrainian mirror of the same 7 keys (FT-P-22 parity gate PASS). |
| `tests/msw/handlers/admin.ts` | test-infra | Added `http.patch('/api/admin/classes/:id', ...)` partial-merge handler; existing PUT handler retained (dead code, not introduced by this task). |
| `tests/admin_class_edit.test.tsx` | new | 12 tests covering AC-1..AC-6, AC-8 (AC-7 covered by static FT-P-22 gate). |
| `tests/destructive_ux.test.tsx` | adjacent hygiene | Fixed `firstRow.querySelector('button')` selector at 3 call sites — my ✎ button became the first button in the row; replaced with `Array.from(querySelectorAll('button')).find(b => b.textContent === '×')` to deliberately target the delete (×) button. Pre-existing `it.fails()` semantics preserved. |
| `_docs/02_document/components/08_admin/description.md` | spec-authorized (per task Scope.Included) | Recorded edit affordance + PATCH wiring in Internal Interfaces table and External API table; cross-referenced AZ-513 prerequisite. |
### Files NOT modified (scope discipline)
| Path | Reason |
|------|--------|
| `src/api/endpoints.ts` | Task constraint: reuse existing `endpoints.admin.class(id)` builder; no new endpoint helper for PATCH (same URL as DELETE). |
| `src/api/client.ts` | `api.patch()` helper already exists. |
| `_docs/02_document/architecture.md` | Architecture-level wire-shape table update belongs in Step 13 (Update Docs), not Step 10. |
| AdminPage delete-confirm wiring | Out of scope (Finding B4 — explicitly excluded per task spec Scope.Excluded). |
| Settings/Users sections | Out of scope (separate concerns per task spec Scope.Excluded). |
## AC Test Coverage: All covered (8 of 8)
| AC | Test name | Notes |
|----|-----------|-------|
| AC-1 | `renders a pencil button per row` | One edit affordance per class row |
| AC-2 | `row 1 enters edit mode with name="class-a"; other rows stay read-only` + `single-row invariant` | Seeded values + Risk 3 mitigation |
| AC-3 | `Save button → one PATCH with full body, row re-renders, form closes` + `Enter key inside form behaves like Save` | Risk 2 mitigation: full-body always |
| AC-4 | `Cancel button → no PATCH; row reverts` + `Escape key inside form behaves like Cancel` | No network in either path |
| AC-5 | `empty name → no PATCH; nameRequired error visible` + `non-positive maxSizeM → no PATCH; maxSizeMustBePositive error visible` | Validation-before-submit |
| AC-6 | `PATCH 500 → form stays open; updateFailed error visible; no alert() called` | Risk 4 mitigation: disabled buttons during PATCH; spy on `window.alert` |
| AC-7 | (static) `FT-P-22 (key parity): PASS` | `scripts/check-i18n-coverage.mjs --parity-only` |
| AC-8 | `Add posts to /api/admin/classes and refetches the list` + `Delete sends DELETE and removes the row optimistically` | Regression guards |
## Code Review Verdict: PASS (inline self-review)
A formal `/code-review` skill run was not invoked for this single-task batch (3 pts, tight scope, all spec ACs verified). The self-review checked: file ownership respected, no silent error swallowing, no `alert()` usage (STC-SEC7 confirms), no banned-deps literals (STC-SEC1B/C/D confirm), i18n parity + coverage (FT-P-22/23 confirm), architecture compliance (STC-ARCH-01/02 confirm), single-responsibility handlers, no spec drift, no dependencies on un-shipped admin/ work in the test layer.
If a cumulative review is required at Step 14.5 (every K=3 batches), this is the 1st batch of cycle 4 — cumulative review fires at batch 18.
## Auto-Fix Attempts: 0
No PASS-with-warnings or FAIL findings during self-review.
## Stuck Agents: None
Single task, ~7 file edits, no rewrites without progress. The one i18n-coverage failure (3 raw English aria-labels) was fixed in a single targeted swap (aria-label → data-field) without regressing the spec's aria-label-on-edit-button NFR.
## Test Suite Result
| Suite | Result |
|-------|--------|
| `bun run test` (full vitest) | **32 files passed, 243 tests passed, 13 quarantined skips** (cycle 3 baseline preserved) |
| `bash scripts/run-tests.sh --static-only` | **All 35 static checks PASS** including FT-P-22, FT-P-23, STC-ARCH-01/02, STC-SEC1/2/3/4/7/8/13/14, STC-SEC1B/C/D, banned-deps, etc. |
## Pre-existing bug noted (NOT fixed this batch)
While writing the new test file, I discovered that `tests/msw/handlers/admin.ts` returns `paginate(seedUsers)` (= `{ items, totalCount, page, pageSize }`) for `GET /api/admin/users`, but `AdminPage.tsx:19` does `api.get<User[]>(...).then(setUsers)` expecting a flat array. The catch swallows fetch errors but NOT the subsequent `users.map is not a function` render error.
- **Impact in tests**: any test that mounts the full `<AdminPage />` without overriding the users handler crashes. Today, `destructive_ux.test.tsx:50-59` already overrides `/api/admin/users` with `jsonResponse([])` and documents the drift with the same comment shape; my new `tests/admin_class_edit.test.tsx` adds the same override (`stubUsersAsPlainArray()`).
- **Impact in production**: depends on what the live `admin/` service actually returns (flat or paginated). If paginated, the Users table is broken end-to-end against the live service — analogous to the pre-existing AZ-513 add/delete situation. If flat, only the test fixture is wrong.
- **Recommendation**: a separate UI-workspace ticket to either (a) align the MSW handler with the live admin/ shape (and fix `AdminPage.users` consumption if needed), or (b) introduce a paginated-response unwrap in the api client. NOT bundled with AZ-512 per scope discipline (`coderule.mdc`).
## Cross-workspace dependency reminder
AZ-512 ships in this batch but the **live admin/ service does not yet expose** `POST | PATCH | DELETE /api/admin/classes(/{id})` (verified 2026-05-13: zero `MapPost|MapPatch|MapDelete` against `classes` in `admin/Azaion.AdminApi/Program.cs`). Per the user-chosen Option B path:
- **Step 11 (Run Tests)** passes on MSW stubs.
- **Step 16 (Deploy)** gates on **AZ-513** landing on the admin/ workspace AND that build being deployed to whichever environment(s) the UI is promoted into. The leftover record at `_docs/_process_leftovers/2026-05-13_az-512-admin-classes-prereq.md` remains open until that point.
- The existing pre-existing-broken Add and Delete affordances on `AdminPage`'s class table also start working end-to-end the moment AZ-513 ships.
## Next Batch
None planned in this cycle (cycle 4 was entered for AZ-512 reactivation only). After Step 11 (Run Tests) confirms the test suite still passes, autodev auto-chains through Steps 12 → 13 → 14 → 15 → 16 → 17. The Deploy gate (Step 16) will surface the admin/ AZ-513 dependency before any prod cutover.
@@ -0,0 +1,97 @@
# Implementation Report — Admin Class Edit (Cycle 4)
**Date**: 2026-05-13
**Cycle**: 4 (autodev existing-code Step 10 → Step 17 loop)
**Epic**: AZ-509 (Phase B cycle 3 carry-over — UI workspace cycle 3 deliverables; AZ-512 was the cycle 3 deferred task brought into cycle 4 under user-authorized Option B)
**Tasks**: [AZ-512]
**Batches**: 1 (batch_16_cycle4)
**Outcome**: PASS — single-task cycle, all ACs covered, full test suite green, all static gates green.
## Summary
Cycle 4 was entered as a small surgical cycle to **reactivate AZ-512** — the "edit existing detection class" affordance that was deferred to backlog at the end of cycle 3 because the `admin/` sibling service does not expose the underlying CRUD routes for detection classes.
At cycle 4 entry the user explicitly chose Option B from the original AZ-512 Cross-Workspace Verification gate: implement the UI inline edit form against MSW-stubbed PATCH semantics while AZ-513 ships in parallel on the admin/ workspace. The UI is therefore complete and tested today; the live deploy gate (Step 16) holds until AZ-513 lands on admin/ and that build deploys to whichever environments the UI is promoted into.
## Tasks Delivered
| Task | Name | Complexity | Status |
|------|------|-----------|--------|
| AZ-512 | Admin — edit existing detection class (inline form + PATCH wiring) | 3 | Done (MSW-stubbed; live wire shape gates at Step 16 on AZ-513) |
**Total complexity delivered**: 3 points.
## Acceptance Criteria Status
8 of 8 ACs covered. See `batch_16_cycle4_report.md` for the per-AC test mapping. Highlights:
- AC-1, AC-2 — edit affordance + single-row invariant verified.
- AC-3 — Save (button + Enter) sends exactly one PATCH with the full editable body (Risk 2 mitigation: full body always sent so backend partial-merge vs full-replace semantics are equivalent for the UI).
- AC-4 — Cancel (button + Escape) emits zero network requests.
- AC-5 — empty name AND non-positive `maxSizeM` both block the PATCH and surface inline `role="alert"` errors.
- AC-6 — 500 response keeps the form open, surfaces an inline error, leaves the user's draft intact, and confirms `window.alert` is NOT called.
- AC-7 — static FT-P-22 i18n parity gate PASS; six new `admin.classes.*` keys exist in both `en.json` and `ua.json`.
- AC-8 — regression guards for the existing Add and Delete affordances both pass.
## Quality Gates
| Gate | Result | Notes |
|------|--------|-------|
| Full vitest suite | PASS — 32 files, 243 tests, 13 quarantined skips | `bun run test` |
| `scripts/run-tests.sh --static-only` | PASS — all 35 static checks | i18n parity + coverage, arch imports, api literals, banned-deps (incl. STC-SEC1B/C/D), destructive UX surface registry, performance regex, etc. |
| ReadLints on touched files | PASS — no introduced lint errors | `AdminPage.tsx`, MSW handler, test file, doc |
| File ownership envelope | PASS — only `08_admin` OWNED files + spec-authorized exceptions (i18n bundles, tests, admin description doc) | |
| AZ-512 Cross-Workspace Verification | DEFERRED — Option B path active (MSW-stubbed) | Live deploy gates at Step 16 on AZ-513 |
## Product Implementation Completeness Gate (Step 15)
| Task | Verdict | Evidence |
|------|---------|----------|
| AZ-512 | **PASS** | Task promises are UI-only and are implemented in production source (`src/features/admin/AdminPage.tsx`). No named external runtime dependency beyond the existing `api.patch()` helper. No unresolved placeholder/stub/TODO/scaffold markers in the touched files. The "cross-workspace prerequisite" is an external system (admin/ workspace) explicitly out-of-scope-from-the-UI per the task spec; the leftover entry tracks it and the Step 16 gate enforces it. No remediation tasks created. |
Final implementation report can therefore be written here (this file) without further gate-driven loops.
## Handoff to Test Run (Step 11)
The full vitest suite was already run during batch verification and passed cleanly. Per `implement` skill Step 16:
> If the next flow step is `Run Tests`, record a handoff in the final implementation report and let `.cursor/skills/test-run/SKILL.md` own the full-suite gate to avoid duplicate full runs.
Step 11 (Run Tests) is the next autodev step. The test-run skill should pick up here and run its own formal gate; the result of my pre-flight run is purely advisory.
## Discovered pre-existing bug (NOT fixed this batch)
`tests/msw/handlers/admin.ts:39` returns `paginate(seedUsers)` for `GET /api/admin/users`, but `AdminPage.tsx:19` consumes the response as a flat `User[]`. The mismatch is silently caught at the fetch layer but surfaces as a `users.map is not a function` render crash when the response is bound to state. The destructive-ux test fixture documents the same drift and overrides the handler with a flat array; my new test file uses the same workaround.
This is logged for the user to triage as a separate UI-workspace ticket — fixing it requires deciding which side (handler shape vs UI consumption) reflects the live admin/ service's behavior, and that determination belongs to the admin/-side conversation, not this batch's scope.
## Cross-workspace coordination point
When **AZ-513** ships on the `admin/` workspace AND that build is deployed to the environments the UI is promoted into:
1. The Step 16 (Deploy) gate in this cycle (or any future cycle re-running it) un-blocks for AZ-512 prod cutover.
2. The existing pre-existing-broken Add and Delete affordances on `AdminPage` ALSO start working end-to-end against the live service for free.
3. The leftover record at `_docs/_process_leftovers/2026-05-13_az-512-admin-classes-prereq.md` becomes deletable.
4. The Step 16 leftovers-replay step should additionally verify the admin/-side `GET /api/admin/users` response shape and, depending on outcome, file the separate UI-workspace ticket flagged above.
## Cycle 4 metrics snapshot
| Metric | Value | Δ vs cycle 3 |
|--------|-------|--------------|
| Tasks attempted | 1 (AZ-512) | 2 |
| Tasks delivered | 1 | 1 |
| Tasks deferred at spec gate | 0 (deferred-at-gate pattern resolved via user Option B authorization) | 1 |
| Total batches | 1 | 2 |
| Total complexity points planned | 3 | 6 |
| Total complexity points delivered | 3 | 3 |
| Source files mutated | 2 production + 2 test + 2 doc/i18n + 1 test-infra = ~7 | n/a (single-task shape) |
## Files Reference
- `src/features/admin/AdminPage.tsx` — inline edit affordance.
- `src/i18n/en.json`, `src/i18n/ua.json``admin.classes` flat → nested with 6 new keys.
- `tests/msw/handlers/admin.ts` — PATCH partial-merge handler.
- `tests/admin_class_edit.test.tsx` — 12 tests covering AC-1..AC-6 + AC-8.
- `tests/destructive_ux.test.tsx` — adjacent-hygiene selector tightening for the existing class-delete `it.fails()` and `control` tests (my ✎ button moved the first-button position).
- `_docs/02_document/components/08_admin/description.md` — recorded edit affordance + PATCH wiring.
- `_docs/03_implementation/batch_16_cycle4_report.md` — per-batch detail.