# Handoff: Ordinis MDM — UI redesign

## Overview

Full UI redesign for the **Ordinis MDM admin** (`ordinis-admin-ui` — React 19 + TanStack Router + Vite + Tailwind v4 + currently `@nstart/ui@0.1.3`). Covers:

1. **Design system** — earthy / woody light theme + Claude-style warm dark theme. Inter + JetBrains Mono + Tektur. Custom token palette.
2. **Dictionary catalog** — bundle-grouped grid with FK chips, scope/bundle filters, search.
3. **Dictionary editor** — tabs (Records / Relations / Fields / JSON / Events / History), keyboard-driven table, record drawer with sectioned form, time-travel viewer, status banner for draft/review/published flows.
4. **Modals** — Create dictionary, Confirm delete, Toast notifications.
5. **Theme switcher** — Light (earthy) / Dark (Claude-warm), persisted to `localStorage`.

## About the design files

`design/compact.html` and `design/ui-kit.html` are **single-file React prototypes** using inline Babel + CDN React 18. They are **design references**, not production code.

**Your task:** recreate the screens in the existing `ordinis-admin-ui` codebase using its real patterns:
- TanStack Router routes
- TanStack Query for data
- Tailwind v4 utility classes (project already uses `@tailwindcss/vite`)
- Component primitives from `@nstart/ui` (or progressively replace with **shadcn/ui** behind a barrel — see "Stack migration" below)
- React Hook Form + AJV for forms
- Phosphor Icons

**Do not** ship the prototype HTML to production. **Do not** import the prototype as a module — it uses `window.*` globals and inline Babel that won't survive the Vite build.

## Fidelity

**High-fidelity.** Colors, typography, spacing, and most interactions are decided. Pixel-perfect recreation is the goal, with these adaptations:
- Inline styles → Tailwind classes + `@theme` tokens in `globals.css`.
- Mock data (`SATS`, `DICTS`, `HISTORY`) → real TanStack Query.
- Hand-built `<button>`/`<div>` chrome → `@nstart/ui` (short-term) or `shadcn/ui` primitives (long-term).

## Files in this bundle

| Path | What it is |
|---|---|
| `design/compact.html` | The main prototype — catalog, editor, drawer, modals, time-travel, theme switcher |
| `design/ui-kit.html` | Design-system reference — tokens, type scale, components in isolation |
| `screenshots/01-catalog-light.png` | Catalog (Earth theme) |
| `screenshots/02-catalog-dark.png` | Catalog (Claude-warm dark) |
| `screenshots/03-uikit-light.png` | Design-system reference (Earth) |
| `screenshots/04-uikit-dark.png` | Design-system reference (Dark) |
| `review.md` | Original code review of `ordinis-admin-ui` — bugs and improvements that drove this redesign |

Open both HTML files in any modern browser. Toggle Earth / Dark in the top-right corner. Editor screen and record drawer are accessed by clicking any dictionary row in the catalog.


---

## Design tokens

Both themes share token names, just different values. Apply in `globals.css` via Tailwind v4's `@theme` directive.

### Light theme (Earth — default, `:root`)

```css
@theme {
  /* text */
  --color-ink:        #1a1714;   /* body, headings */
  --color-ink-2:      #52483d;   /* secondary text, labels */
  --color-mute:       #8c8276;   /* placeholders, captions, mono labels */

  /* surfaces */
  --color-bg:         #fbf8ee;   /* page background */
  --color-surface:    #ffffff;   /* cards, modals, drawer */
  --color-surface-2:  #f4eedc;   /* hovers, table-row stripe */

  /* lines */
  --color-line:       #e3dccb;   /* hairlines, card borders */
  --color-line-2:     #efe9d8;   /* subtle dividers */

  /* brand + actions */
  --color-navy:       #3a2818;   /* primary button bg (deep coffee) */
  --color-accent:     #b05a2e;   /* terracotta — links, focus, FK chips */
  --color-accent-bg:  rgb(176 90 46 / 11%);
  --color-on-accent:  #ffffff;

  /* semantic */
  --color-green:      #4f7038;   /* "ok" / live */
  --color-green-bg:   #e4ead0;
  --color-warn:       #a8762a;   /* warning / draft */
  --color-warn-bg:    #f6e8bc;
  --color-pink:       #a64636;   /* error / destructive */
  --color-pink-bg:    #f4d9cd;
}
```

### Dark theme (Claude-warm, `[data-theme="dark"]`)

```css
[data-theme="dark"] {
  --color-ink:        #f0ece4;
  --color-ink-2:      #c8c1b3;
  --color-mute:       #8a8474;
  --color-bg:         #1c1a17;
  --color-surface:    #262421;
  --color-surface-2:  #2e2b27;
  --color-line:       #3a3733;
  --color-line-2:     #2d2a27;
  --color-navy:       #d97757;   /* primary becomes Claude-orange in dark */
  --color-accent:     #d97757;
  --color-accent-bg:  rgb(217 119 87 / 16%);
  --color-on-accent:  #1c1a17;
  --color-green:      #6fa97d;
  --color-green-bg:   rgb(111 169 125 / 18%);
  --color-warn:       #d4a653;
  --color-warn-bg:    rgb(212 166 83 / 18%);
  --color-pink:       #d18272;
  --color-pink-bg:    rgb(209 130 114 / 20%);
}
```

### Scope colors (dictionary visibility)

| Scope | Light fg | Light bg | Dark behavior |
|---|---|---|---|
| `PUBLIC` | `--color-accent` (`#b05a2e`) | `--color-accent-bg` | inherits accent |
| `INTERNAL` | `--color-warn` (`#a8762a`) | `--color-warn-bg` | inherits warn |
| `TENANT` | `--color-pink` (`#a64636`) | `--color-pink-bg` | inherits pink |

Always pair with a 6-8px square swatch prefix (`<span class="scope-dot">`).

### Typography

| Font | Source | Used for |
|---|---|---|
| **Inter** 400/500/600/700 | Google Fonts | All UI text, headings, body |
| **JetBrains Mono** 400/500/600 | Google Fonts | IDs, FK refs, dates, numerics |
| **Tektur** 500/600/700 | Google Fonts | UPPERCASE captions (`.cap`), logo, theme switch labels |

`font-feature-settings: "ss01","cv11","cv02","cv03","cv04"` on body for Inter's stylistic alternates.

**Scale:**
- 22px / 600 — section header (rare, e.g. modal title)
- 17px / 600 — page title in editor
- 15px / 600 — dictionary card title
- 13px / 400-600 — body text, buttons, tabs (this is the workhorse)
- 12.5px / 500 — table cell text
- 11px Mono / 500 — IDs, dates
- 10.5px Tektur uppercase, `letter-spacing: .10em` — captions / section labels

### Spacing

Tight, density-first. The whole UI optimizes for showing many records at once.

- Card padding: `12px 14px`
- Drawer body padding: `4px 18px 24px`
- Drawer section grid gap: `14px`
- Modal padding: `20px 22px`
- Toolbar height: `40px`, with `0 14px` horizontal padding
- Page gutter: `18px` horizontal

### Radius / shadow

| Element | Value |
|---|---|
| Buttons, inputs | `border-radius: 6px` |
| Cards | `border-radius: 8px` |
| Modal, drawer | `border-radius: 8px` (drawer only on inner edge) |
| Toast | `border-radius: 8px` |
| Chip (badge, FK) | `border-radius: 4-5px` |

Shadows are minimal in light, almost absent in dark. Drawer: `-20px 0 60px -20px rgba(15,17,21,.35)`. Modal: `0 24px 48px -16px rgba(15,17,21,.30)`. Toast: `0 12px 30px -10px rgba(40,25,10,.38)`.


---

## Screens

The prototype is **one page** that toggles between catalog and editor via a state machine (`view: "catalog" | "editor"`). In production these become two TanStack Router routes.

### Screen 1 — Catalog (`/dictionaries`)

**Layout (1280px+):**

```
┌─ sidebar 220px ──┬─ main ─────────────────────────────────────┐
│ logo (ORDINIS)   │ header (h=56): breadcrumb · ⌕ search · usr │
│ nav rail         ├────────────────────────────────────────────┤
│ ─ Главная        │ toolbar (h=44): filter chips · "+ Создать" │
│ ▣ Справочники 40 ├────────────────────────────────────────────┤
│   Мои черновики  │ bundle group: cuod (10)                    │
│   На ревью    7  │   ┌── card ──┐ ┌── card ──┐                │
│   Outbox      2  │   │ title    │ │ title    │  …             │
│   Webhooks       │   │ desc     │ │ desc     │                │
│   Аудит          │   │ FK chips │ │ FK chips │                │
│   Поиск          │   └──────────┘ └──────────┘                │
│                  │ bundle group: geo (5)                      │
│ user · profile   │   …                                        │
└──────────────────┴────────────────────────────────────────────┘
```

**Toolbar:**
- Search input — `width: 280px`, placeholder "Поиск по названию, коду…", searches `id + title + desc`.
- Scope chip group — three toggleable buttons with colored dot prefix.
- Bundle filter — single-select chips after a small "BUNDLE" Tektur label.
- Right side: `"+ Создать справочник"` primary button → opens modal.

**Card:**
- 3px left strip in scope color.
- Title (15px / 600 / `--color-navy`) + id below in Mono 11px gray.
- Description (12px) clamped to 2 lines.
- Relations row — `→ ссылается` label + chips for `fk[]`; `← N использ.` + chips for `refBy[]` (dimmed dashed border style).
- Right column: ScopeBadge + version mono.
- Hover: `translateY(-1px)` + soft shadow; entire card is the click target → opens editor.

**FK chip:** clickable button, `padding: 2px 7px`, `border-radius: 4px`, mono 11px. Active variant: `bg: --color-accent-bg`, `color: --color-accent`. Dimmed: transparent bg + dashed border. `onClick` stops propagation and navigates to that dictionary.

### Screen 2 — Editor (`/dictionaries/$name`)

**Layout:**

```
┌─ sidebar (collapsed to 56px in editor) ─┬─ main ─────────────────┐
│ ⌂  ▣ ✎ ◐ ↗ ⇆ ☷ ⌕                       │ breadcrumb + actions   │
│                                          │ title row              │
│                                          │ status banner          │
│                                          ├────────────────────────┤
│                                          │ tabs (h=40)            │
│                                          ├────────────────────────┤
│                                          │ tab body               │
│                                          │   - records (table)    │
│                                          │   - relations (FK list)│
│                                          │   - fields (schema)    │
│                                          │   - json (raw schema)  │
│                                          │   - events (audit)     │
│                                          │   - history (timeline) │
│                                          ├────────────────────────┤
│                                          │ footer toolbar         │
└──────────────────────────────────────────┴────────────────────────┘
```

**Breadcrumb row:** "Справочники / **Космические аппараты**" + right-aligned: `[Export]` `[Compare]` `[⋯ menu]`.

**Title row:** large title (17px / 600) + scope badge + version mono + edit pencil.

**Status banner (sits above tabs):** 1-line card showing current workflow state.
- Draft: `--color-warn-bg` background, left edge `--color-warn`, copy "Черновик · v1.8.0+draft · 3 правки · открыт {user}".
- Review: `--color-accent-bg`, copy "На ревью · ждёт {reviewer} · открыто {time}".
- Live: `--color-green-bg`, "Опубликовано · v1.8.0 · {time}".
- Buttons on the right adapt to state: `Запросить ревью` (draft), `Approve / Request changes` (review), `Создать черновик` (live).

**Tab bar:** horizontal scrollable on narrow viewports; active tab has `--color-accent` bottom border, weight 600. Right side of bar has utility buttons (`Time-travel ◷` opens a popover with date picker).

**Records tab** — the main view:
- Compact table, row height 32px, monospace IDs in first column.
- Sticky header with column sort arrows and a search icon per column (filter).
- Row hover: `--color-surface-2` background.
- Click anywhere in a row → opens **RecordDrawer** on the right.
- Bulk-select via leftmost checkbox column.
- Footer toolbar: "N из M записей" · paging · "+ Запись" (right-aligned, primary).

**Relations tab** — two sections:
- "Исходящие FK" — table: field · target dict · cardinality · critical / soft.
- "Входящие ссылки" — table: source dict · field · count of records using it.

**Fields tab** — full JSON-schema view as an editable grid:
- Columns: name · type · required · unique · i18n · index · description.
- Inline edit; "+ Поле" at bottom.

**JSON tab** — Monaco editor with the raw `schema.json`; read-only on Live records.

**Events tab** — audit log filtered to this dict:
- Filter pills: webhook · publish · review · import · retried.
- One row per event with mono timestamp, actor avatar, target record, status badge.

**History tab** — vertical timeline:
- One node per commit, current `HEAD` marked.
- Each node shows: short SHA, message, author, diff counters (`+N -M`).
- Click → opens diff in a slide-over.

### Screen 3 — RecordDrawer (right slide-over)

Opens on row click. **Two widths**: 520px (default) and 880px (toggle via `‹/›` button).

**Header (sticky):** breadcrumb "редактирование записи · satellites" caption, then `id` mono + `· name`, then `[‹/›]` `[История]` `[×]`.

**Toolbar (sticky):** search field for filtering schema fields by name + "только заполненные" toggle + counter "N / M".

**Section anchor chips:** horizontally scrolling row of section names with field-count badge; click to scroll-to-section. Active chip is filled navy/accent.

**Body:** sections in order with sticky section headers (`.cap` style). Each section is a 2-column grid (or 4 in wide mode). Section list — see **Record schema** below.

**Wide mode (`880px`):** adds a left ToC rail (180px) with the same sections as a vertical list, mirroring the chip strip.

**Footer (sticky):** `[Удалить]` (pink) · "не сохранено · N полей" caption · `[Отмена]` · `[Сохранить]` (primary).

### Screen 4 — Modals

**Create dictionary** — centered dialog, 560px:
- "НОВЫЙ СПРАВОЧНИК" cap header.
- Fields: Название (text), id (mono, slug-validated, auto-derived from title), Bundle (select), Scope (radio with colored dots), Поддерживаемые локали (multi-chip — `ru / en / zh / es / fr / de / ar`).
- Footer: `[Отмена]` · `[Создать]`.

**Confirm delete** — centered dialog, 440px:
- Pink left-accent stripe (4px).
- "ПОДТВЕРЖДЕНИЕ" cap header.
- Copy explains consequences (e.g. "12 справочников ссылаются на эту запись через FK").
- Footer: `[Отмена]` · `[Удалить]` (pink danger button).

**Toast** — bottom-right slide-up:
- Navy (or Claude-orange in dark) `--color-navy` background.
- "OK" cap header in toast-cap color (orange-cream), then short message, then `×`.
- Auto-dismiss after 4s.

### Screen 5 — Time-travel

A popover from the time-travel button in the editor tab bar. Two parts:
- **Date picker** — calendar with 30 day range; selecting a date snapshots the dict and editor to that point.
- **Quick presets** — "сейчас" · "1ч назад" · "вчера 18:00" · "перед последним публикованием".
- "Применить" / "Сбросить" footer.

When active, all editor surfaces gain a thin amber top border and a banner: "Просмотр на {timestamp} · только чтение · [Сбросить]".

---

## Record schema (sections in drawer)

The drawer's section list is the canonical "what fields a record can have" definition. The prototype encodes this in `RECORD_SCHEMA` (see `design/compact.html` ~line 220).

| Section | Fields | Notes |
|---|---|---|
| **Основное** | id, active toggle, name_ru/en/zh/es, description | name_ru is the only required loc; others optional |
| **Связи (FK)** | operator, status (enum), orbit_type, mission, launch_site, launch_vehicle, manufacturer, data_center | Each is a FK to another dict |
| **Геометрия орбиты** | altitude_km, inclination, period_min, eccentricity, raan, arg_perigee | numerics with units |
| **Физические параметры** | mass_kg, power_w, design_life, dimensions | |
| **Инструменты и продукты** | instruments[], products[], ground_stations[] | tag inputs |
| **Внешние идентификаторы** | cospar_id, norad_id, sat_cat, itu | mono text |
| **Даты и события** | launch_date, decom_date, first_light, last_contact | |
| **Флаги** | public, deprecated, sensitive, draft_only | toggles |
| **Произвольные данные** | raw_json | JSON editor (Monaco) |
| **Системные** (readonly) | created_at, updated_at, created_by, version | |

In production these sections must come from the dictionary's JSON-schema metadata (`x-section` per property), not be hard-coded.


---

## Components inventory

Map each prototype piece to a target component file. Where possible reuse `@nstart/ui`; where it's broken, replace.

### Atomic

| Prototype | Target | Notes |
|---|---|---|
| `Cap` (uppercase Tektur label) | `<Cap>` in `components/ui/Cap.tsx` | Plain `<span class="cap">`, Tektur 10.5px |
| `ScopeBadge` | `components/catalog/ScopeBadge.tsx` | Pill with colored dot + uppercase label |
| `ScopeDot` | `components/catalog/ScopeDot.tsx` | 8×8 colored square |
| `FkChip` | `components/catalog/FkChip.tsx` | Active + dimmed variants |
| `StatusBadge` (DRAFT / LIVE / DEPRECATED) | `components/ui/StatusBadge.tsx` | Background = `--color-warn-bg` / `--color-green-bg` / `--color-pink-bg` |
| `Btn` / `BtnPrimary` / `BtnDanger` / `BtnGhost` | `@nstart/ui` `Button` (variant) | Patch theme tokens; danger uses `--color-pink` |
| `IconBtn` (28x28 with icon glyph) | `components/ui/IconButton.tsx` | |
| `Toggle` (switch) | `@nstart/ui` `Toggle` or shadcn `Switch` | Native indeterminate not needed |
| `TagInput` | replace `@nstart/ui` MultiSelect → **shadcn Combobox + cmdk** | See "Stack migration" |

### Composite

| Prototype piece | Target component | File |
|---|---|---|
| Sidebar (nav rail + collapse) | `<Sidebar>` | `components/layout/Sidebar.tsx` |
| Header (breadcrumb + search + user) | `<TopBar>` | `components/layout/TopBar.tsx` |
| Catalog toolbar | `<CatalogToolbar>` | `components/catalog/CatalogToolbar.tsx` |
| Catalog card | `<DictCard>` | `components/catalog/DictCard.tsx` |
| Catalog bundle group | `<BundleGroup>` | `components/catalog/BundleGroup.tsx` |
| Editor tab bar | `<EditorTabs>` | `components/editor/EditorTabs.tsx` |
| Editor status banner | `<WorkflowBanner>` | `components/editor/WorkflowBanner.tsx` (variant = draft/review/live) |
| Records table | `<RecordsTable>` | `components/editor/RecordsTable.tsx` (TanStack Table) |
| Record drawer | `<RecordDrawer>` | `components/editor/RecordDrawer.tsx` |
| Drawer field renderers | `<DrawerField>` | `components/editor/DrawerField.tsx` (kind-switch: text / mono / num / date / fk / enum / toggle / tags / textarea / json) |
| Create-dict modal | `<CreateDictModal>` | `components/modals/CreateDictModal.tsx` |
| Confirm modal | `<ConfirmModal>` | `components/modals/ConfirmModal.tsx` |
| Toast | `<Toast>` | use `sonner` (recommended) or `@nstart/ui` Toast |
| Time-travel popover | `<TimeTravelPopover>` | `components/editor/TimeTravelPopover.tsx` |
| Theme switcher | `<ThemeSwitch>` | `components/layout/ThemeSwitch.tsx`, writes `data-theme` attr + `localStorage` |

---

## Stack migration plan (`@nstart/ui` → `shadcn/ui`)

Three-phase migration. Don't do it all at once. Phase 1 unblocks the redesign; phases 2-3 happen over the following weeks.

### Phase 1 — Patch & ship (1 day)

1. `pnpm patch @nstart/ui` to fix three things in `dist/index.js`:
   - `Select` / `MultiSelect` / `DatePicker` popover → `createPortal(node, document.body)` with `position: fixed`, computed top/left from `triggerRef.getBoundingClientRect()`, `z-index: 9999`, listens to scroll/resize.
   - Outside-click detection: check both `triggerRef.contains(target)` and `popoverRef.contains(target)`.
   - `Checkbox`: forward ref to inner `<input>`, sync `indeterminate` prop via `useEffect`.
2. Add `@theme` block to `globals.css` with both palettes (above).
3. Add `<ThemeSwitch>` component → toggles `data-theme` on `<html>`.
4. Wire `@nstart/ui` theme tokens to our `--color-*` variables (one CSS file, override their CSS vars).

### Phase 2 — Barrel + Radix replacements for broken components (3-4 days)

Create a barrel at `src/ui/index.ts`:

```ts
// re-export anything from @nstart/ui that works
export { Button, Alert, Badge, Tabs, TextInput, Drawer } from '@nstart/ui';

// override the broken ones with our wrappers
export { Select, MultiSelect } from './Select';     // Radix Select + cmdk
export { Modal } from './Modal';                    // Radix Dialog
export { Checkbox } from './Checkbox';              // Radix Checkbox
export { Combobox } from './Combobox';              // cmdk-based
```

Then a codemod / find-and-replace across the codebase: `from '@nstart/ui'` → `from '@/ui'`. Adopt **shadcn/ui** primitives inside the wrappers:
- `npx shadcn@latest init` (Tailwind v4 mode, slate base, CSS variables = yes).
- `npx shadcn@latest add select combobox dialog checkbox dropdown-menu popover tooltip command`.
- Edit the generated files to use our `--color-*` tokens directly (replace shadcn's `primary` / `destructive` etc. with our names).

### Phase 3 — Full shadcn for new screens, gradual replace for old (2-3 weeks, parallel to feature work)

- All new components use shadcn directly.
- Old `@nstart/ui` Button / Alert / Badge / Tabs / TextInput / Drawer get migrated one at a time as touched.
- End state: `@nstart/ui` removed from `package.json`, patch deleted.

---

## Interactions & behavior

### Tables (all 5 — catalog list, records, fields, events, history-preview)
- Row hover background uses `--color-surface-2`; default is `--color-surface`. **Never hardcode `#fff` / `#fafbfc`** in `onMouseEnter`/`Leave` handlers — those break dark mode (rows go white-on-white). Either use CSS `tr:hover { background: var(--color-surface-2) }` or pass theme tokens to the inline handler.
- Row height: 32px in production; 28px in compact density mode.
- IDs in the first column are `mono` + `--color-accent`; clickable but `stopPropagation` is not needed (row click and id click both navigate to the same place).

### Catalog
- Search debounced 150ms.
- Filter chips are URL-synced via TanStack Router `validateSearch` (`?q&scopes&bundle&onlyWithRefs`).
- Card click → `navigate({ to: '/dictionaries/$name', params: { name: id } })`.

### Editor
- Tab change → `?tab=records|relations|fields|json|events|history`.
- Status banner → mutations (request-review, approve, publish, create-draft) hit existing API and invalidate the dict query.
- Records table sort/filter are URL-synced.
- Pressing `j`/`k` moves selected row; `Enter` opens drawer; `Esc` closes; `n` opens new-record drawer; `/` focuses search.

### Drawer
- Width toggle persists per-user to localStorage (`ord-drawer-wide`).
- "Только заполненные" filter only hides fields with no value (excluding active toggles set to false — those still show because the field is "configured").
- "История" button opens the History tab of the current dict filtered to this record.
- Save → optimistic update + toast; conflict → modal with diff.

### Time-travel
- Opens via tab-bar button.
- Sets `?asOf=2026-04-30T09:00:00Z` in search.
- All API queries scope to that timestamp; mutations disabled (UI goes read-only with amber border).
- "Сбросить" clears `asOf`.

### Theme
- Click switch → `document.documentElement.setAttribute('data-theme', val)` + `localStorage.setItem('ord-theme', val)`.
- Initial load reads localStorage; falls back to `prefers-color-scheme`.

---

## Responsive

Desktop-first (1280px+ baseline). Three breakpoints:

| Breakpoint | Changes |
|---|---|
| `≤ 1024px` | Sidebar becomes overlay drawer; hamburger in TopBar; main grid `0 1fr` |
| `≤ 880px` | Drawer and modals go fullscreen (`100vw`, `100dvh`, `border-radius: 0`); editor body is single column |
| `≤ 560px` | Table columns 7+ hidden via `nth-child`; toolbar wraps; sidebar always overlay |

---

## Accessibility checklist

- All buttons have visible focus rings — use `--color-accent` 2px outline-offset 2px.
- Drawer + modal are focus-trapped (shadcn Dialog gives this free).
- Drawer + modal close on `Esc`.
- Theme switch has `aria-label`; current value is `aria-pressed`.
- Tables: `<th scope="col">`; sortable headers are `<button>` with `aria-sort`.
- Status badges include text, never color-only.
- All FK chips have a `title` with the full target id.

---

## Open questions for the team

1. **JSON-schema FK convention** — is it `x-ref`, `$ref`, or `x-dict`? `extractFks` must match.
2. **Backend graph endpoint** — willing to add `GET /api/v1/dictionaries/graph` so the catalog doesn't recompute on every load?
3. **Workflow state machine** — confirm exact transitions (draft → review → live → archived?). Current banner assumes draft / review / live.
4. **Time-travel granularity** — minute? second? day? Affects calendar UX.
5. **Sections in record drawer** — confirm `x-section` will be added to schema metadata, or do we need a separate ordering file?
6. **Are sensitive fields really record-level** or are they field-level (mask in UI based on user permission)?

---

## Out of scope

- Mobile-first redesign — admin remains desktop-primary.
- Bulk import / export UX — separate work.
- Permission management UI — separate work.
- Webhook subscription manager — separate work.
- The form-validation bugs in `review.md` (#4 AJV errors, #12 token storage) — covered in Phase 1 patch + ongoing.
