feat(approval-ux): inline draft CTA + reviewer diff summary + tightened drawer

Acts on the two "build now" items from the design review of the approval
workflow UX (other five went to TODOS.md). Plus the TODOS.md file itself,
authored during the same review.

1. **Inline "Создать черновик с моими изменениями" CTA**
   (DictionaryEditorDialog + CreateSchemaDraftModal)

   Was: when a maker edited an approval-required dict, Save disabled and a
   warning told them to "close the editor and click 'Создать черновик схемы'
   on the dict page." Three friction steps and a hunt for a button.

   Now: the warning Alert carries an action button — "Создать черновик с
   моими изменениями" — that opens CreateSchemaDraftModal in-place with the
   maker's in-progress schemaJson pre-loaded. They add a reason and submit.
   No retyping, no hunt.

   Implementation:
   - CreateSchemaDraftModal accepts optional `initialProposedSchema` prop.
     Seed order: edit-mode draft → caller pre-fill → live HEAD.
   - DictionaryEditorDialog mounts the modal inline and triggers it from the
     Alert action. Closes the editor on successful create.

2. **Diff summary chips in ReviewDrawer**

   Was: two raw JSON dumps side by side. Reviewer had to eyeball-diff long
   records to find the actual edit.

   Now: a row of summary chips above the panes — "+N added, −M removed,
   ~K changed" with the field names inline. Computed shallow client-side
   (shallowDiffSummary helper) — ordinis records are flat-ish at the top
   level, no need for a backend diff endpoint. Skipped for CLOSE op (no
   proposed) and when both sides are null.

3. **Tightened drawer header + trimmed empty-state copy**

   Was: single flex-wrap line with four spans (badge + bk + maker + ts) —
   visually busy. Plus a verbose empty-state description: "Когда maker
   отправит schema draft на ревью, он появится здесь."

   Now: two-row layout (identity row on top, provenance row below) so the
   eye lands on Badge + BK first. Empty-state copy shortened to "Очередь
   пуста" / "Схемы на ревью появятся здесь."

Other five gaps (Мои tab, full state-coverage pass, DESIGN.md, a11y audit,
maker notifications) recorded in TODOS.md.
This commit is contained in:
Andrei Zimin
2026-05-14 15:56:50 +03:00
parent bdae52ff28
commit fe7f9cd01f
4 changed files with 339 additions and 20 deletions
+159
View File
@@ -0,0 +1,159 @@
# TODOS
Design debt and follow-ups surfaced by `/plan-design-review` of the approval
workflow UX (2026-05-14). Each entry is self-contained: pick one up and read
the **Context** to understand the motivation without re-running the review.
---
## 1. `/reviews` "Мои" tab — maker visibility
**What.** Add a third tab "Мои" on the `/reviews` page with three sub-sections:
Pending (drafts awaiting review), Decided (drafts that received a verdict),
WIP (drafts the maker started but did not yet submit).
**Why.** Today the maker has no surface to see "where are my drafts?". MR !170
added the backend (`listByMaker` + analogous schema endpoint), but the UI never
got the entry point. Reviewer's queue mixes records and schemas — symmetric
"my drafts" view is missing.
**Pros.** Reuses `/reviews` page chrome. Backend endpoints already exist. Closes
the loop on the maker journey ("submit → ??? → ..." now has a destination).
**Cons.** Adds two more list queries to the page. Need to think about whether
the existing reviewer "Записи / Схемы" toggle nests inside "Мои" too (so 6 panels
total) or "Мои" is flat.
**Context.** Review pass 1 rated information architecture 6/10 specifically
because maker visibility was missing. User chose "third tab on /reviews"
over a separate `/my-drafts` route — keeps everything in one place.
**Depends on / blocked by.** Nothing — endpoints live (`/admin/dictionaries/*/drafts?makerId=...`
and analogous for schemas).
---
## 2. Interaction state pass — reviews page polish
**What.** One MR covering five small fixes:
- Records empty state: add description + primary action ("Queue is clear, see
/audit for past decisions"). Current `<EmptyState title={t('reviews.empty')} />`
is just a heading.
- Reject reason inline validation: today `handleReject` does
`if (!comment.trim()) return` silently. Button click → nothing happens. Show
inline error or set the TextArea into error state.
- Drawer empty state (`workflow.schemaDraft.empty`): distinguish "not loaded",
"not found" (deleted), "no permission". Today the same blank message covers
all three.
- Approve action: drawer closes immediately on success. Add a 500ms success
state (or toast) so the reviewer gets visual confirmation. Currently feels
like the click might not have registered.
- Post-bulk-result polish: the `bulkResult` Alert lists business keys but the
failed list shows raw `reason` codes (`self_approve_forbidden`,
`draft_not_pending`). Map through `extractReviewError` already in the file.
**Why.** Pass 2 rated state coverage 5/10. These are the visible gaps. Each
is 10-30 LOC; the value is interaction confidence.
**Pros.** All in one place — `reviews.tsx`, `SchemaDraftDrawer.tsx`. Low risk.
**Cons.** Five different surfaces in one MR means review surface is wider than
a single-purpose change. Still worth grouping because they're all "state
polish" semantically.
**Context.** Pass 2 finding. None of these block functionality; they're trust
builders. Reject silent fail is the most user-visible — a reviewer clicks the
button and nothing happens, then notices the TextArea is empty after a beat.
**Depends on / blocked by.** Nothing.
---
## 5. DESIGN.md for the admin UI
**What.** Run `/design-consultation` skill or stub DESIGN.md manually to lock
the implicit design system: drawer-vs-modal usage rules, term glossary
(maker / reviewer / publisher and their Russian counterparts), Badge variant
mapping (CREATE → success, UPDATE → info, CLOSE → warning — already used
consistently but undocumented), code panes pattern (live = `bg-line/30`,
proposed = `bg-accent/4`).
**Why.** Pass 5 rated design system alignment 4/10. The system *exists* — design
tokens are used consistently — but it's tribal knowledge. Next developer building
a feature like "Phase 4 webhook approvals" will guess and either match by luck
or drift.
**Pros.** Half-day work. Pays off on every new feature. Source of truth for
`/plan-design-review` calibration on future plans.
**Cons.** Documentation has to be maintained or it lies. Mitigated by keeping
DESIGN.md to invariants (tokens, primitives, semantic rules), not specific
components.
**Context.** User chose "defer to /design-consultation sprint" instead of
stubbing inline. The decision is to do it properly when the sprint happens,
not piecemeal during this review.
**Depends on / blocked by.** Nothing.
---
## 6. A11y audit post-deploy via `/design-review`
**What.** After the in-flight approval workflow MRs deploy to staging, run
`/design-review` skill against the live `/reviews` page + DictionaryEditorDialog
flow. The skill does Lighthouse, axe, screen reader walk-through, contrast
audit, keyboard navigation testing.
**Why.** Pass 6 rated responsive/a11y 5/10. Specific suspects:
- Button heights not verified at 44px touch target.
- `text-mute` contrast against `bg-surface` — might fail WCAG AA 4.5:1.
- Reject reason TextArea has no specified `minHeight`.
- Bulk action toolbar wrap behavior at 375px.
- Diff `<pre>` panes have no aria-label — screen reader reads JSON literally.
- Keyboard tab order through long diff panes — reviewer may have to tab 50 times
to reach approve buttons.
**Pros.** Necessary for compliance contexts (gov contracts, accessibility laws).
Catches things static review misses (real screen reader, real touch device).
**Cons.** Requires the in-flight MRs to be deployed first. Cannot do in
plan mode.
**Context.** User chose "defer to /design-review post-deploy". The audit will
produce its own punch list with severity ratings.
**Depends on / blocked by.** MRs !179, !180, !182, !183, !184, !185 deployed
to staging.
---
## 7. Notifications service: draft decision toast + email
**What.** Notifications service subscribes to outbox `RecordDraftApproved /
RecordDraftRejected / RecordDraftWithdrawn` events (now fired thanks to
MR !184) and:
1. Fires email to maker with reviewer comment.
2. Sets a flag (DB or Redis) so on next login the admin UI shows a toast:
"Your draft for `test1/BK-42` was approved by reviewer X. The record is
now live."
3. Bell icon in the header gains a badge counter for unread decisions.
**Why.** Maker has no synchronous feedback. They submit, then either:
- Tab "Мои" → Decided (covered by TODO 1) — but only when they come back
- This TODO — push notification so they know without re-visiting
**Pros.** Closes the maker feedback loop. Reuses the outbox infra. Mirrors
existing notification flows in `ordinis-notifications`.
**Cons.** New email template + new in-app toast surface + bell badge. Half-day
to a day of work split between notifications service and admin-ui.
**Context.** Pass 7 unresolved decision. User chose "in-app toast + email via
notifications service" over the lighter "tab badge polling" option. Backend
events already fire (MR !184), so the gate is consumer side.
**Depends on / blocked by.** MR !184 deployed. TODO 1 ("Мои" tab) helps but
is not a hard blocker — the toast and email work without that surface.