354 lines
14 KiB
Markdown
354 lines
14 KiB
Markdown
# DESIGN.md — Ordinis Admin UI Design System
|
||
|
||
**Status:** v0 baseline (2026-05-14). Captured from observed patterns в
|
||
codebase after approval workflow saga (MR !179–!190). To be refined через
|
||
`/design-consultation` skill in a dedicated sprint.
|
||
|
||
**Goal:** Lock the implicit design system как source of truth для будущих
|
||
features (Phase 4 webhook approvals, notifications surfaces, MDM expansion).
|
||
Document invariants (tokens, primitives, semantic rules) — NOT specific
|
||
components.
|
||
|
||
---
|
||
|
||
## 1. Voice & Tone
|
||
|
||
- **Russian-first.** UI primary language ru-RU; en-US is parallel mirror.
|
||
Every user-facing string registered in `src/i18n.ts`.
|
||
- **Operational, not aspirational.** Copy describes what user sees / does
|
||
("Очередь чистая", "Reject требует comment") — no marketing-speak.
|
||
- **Domain-precise.** Use exact role names (maker / reviewer / publisher)
|
||
and operation codes (CREATE / UPDATE / CLOSE) — no generic "user".
|
||
- **Apologize on failure.** Empty states + error messages name what went
|
||
wrong и what to do next ("Refresh page + retry", "Укажите причину").
|
||
|
||
---
|
||
|
||
## 2. Color Tokens
|
||
|
||
CSS variables defined в `src/styles.css`. Three themes: `default` (light
|
||
Anthropic warm), `nstart` (medium contrast brand), `dark` (Anthropic dark).
|
||
|
||
### Semantic roles
|
||
|
||
| Token | Light | Dark | Use |
|
||
|---|---|---|---|
|
||
| `--color-ink` | `#1a1714` | `#f0ece4` | Primary text |
|
||
| `--color-ink-2` | `#52483d` | `#c8c1b3` | Secondary text |
|
||
| `--color-mute` | `#8c8276` | `#8a8474` | Tertiary text / placeholders |
|
||
| `--color-bg` | `#faf9f5` | `#1c1a17` | App background |
|
||
| `--color-surface` | `#ffffff` | `#262421` | Card / panel surface |
|
||
| `--color-surface-2` | `#f5f3ec` | `#2e2b27` | Hover / nested surface |
|
||
| `--color-line` | `#e6e1d4` | `#3a3733` | Dividers / borders |
|
||
| `--color-line-2` | `#efeae0` | `#2d2a27` | Subtle dividers |
|
||
| `--color-accent` | `#b05a2e` | `#d97757` | Primary CTA / links |
|
||
| `--color-accent-bg` | accent @ 11% | accent @ 16% | Accent tint |
|
||
| `--color-on-accent` | `#ffffff` | `#1c1a17` | Text on accent |
|
||
| `--color-green` | `#4f7038` | `#4a7d4f` | Success / APPROVED |
|
||
| `--color-green-bg` | `#e4ead0` | `#dde9da` | Success tint |
|
||
| `--color-warn` | `#a8762a` | `#b97c2f` | Warning / CHANGES_REQUESTED |
|
||
| `--color-warn-bg` | `#f6e8bc` | `#fcecc7` | Warning tint |
|
||
| `--color-pink` | `#a64636` | `#b34d6a` | Danger / REJECTED |
|
||
| `--color-pink-bg` | `#f4d9cd` | `#f8d8de` | Danger tint |
|
||
|
||
### Rules
|
||
|
||
- **Never hard-code hex.** Use `bg-line`, `text-mute`, `border-accent` через
|
||
Tailwind utilities mapped to these tokens.
|
||
- **Status colors are stable across themes.** `success` always green-family;
|
||
`danger` always pink-family.
|
||
- **Tint backgrounds (`*-bg`) use 11-16% alpha** — for badge backgrounds
|
||
and subtle highlights. Never tint at higher opacity (loses contrast).
|
||
|
||
---
|
||
|
||
## 3. Typography
|
||
|
||
### Font families
|
||
|
||
| Token | Stack | Use |
|
||
|---|---|---|
|
||
| `--font-sans` | `Inter, -apple-system, BlinkMacSystemFont, sans-serif` | Body, UI |
|
||
| `--font-mono` | `JetBrains Mono, SF Mono, monospace` | Code, UUIDs, business keys, versions |
|
||
| `--font-display` | `Tektur, Inter, sans-serif` | Caps headers, brand mark |
|
||
|
||
### Type scale (Tailwind utilities)
|
||
|
||
| Utility | Use |
|
||
|---|---|
|
||
| `text-title-md` | Section headings (Panel title) |
|
||
| `text-body` | Body copy |
|
||
| `text-cell` | Table cell, dense info |
|
||
| `text-cap` | Caps tracking-wide labels (column headers) — uses `--font-display` |
|
||
| `text-mono` | Code / monospaced |
|
||
|
||
### Rules
|
||
|
||
- **Mono для identity:** UUIDs (truncated `slice(0,8)…`), business keys,
|
||
version strings (`v1.1.0`). Reads as "this is an exact identifier".
|
||
- **Display caps для column headers:** `text-cap text-mute font-display
|
||
uppercase tracking-wider` — visual hierarchy через caps, not size.
|
||
- **`tabular-nums` для timestamp + count columns** — prevents jitter on
|
||
refresh.
|
||
- **Never use system-ui / -apple-system as primary display font.** Inter
|
||
is fallback only.
|
||
|
||
---
|
||
|
||
## 4. Spacing & Layout
|
||
|
||
### Base scale
|
||
|
||
Tailwind default 4px scale (`gap-1` = 4px, `gap-2` = 8px, `gap-3` = 12px,
|
||
`gap-4` = 16px). Avoid arbitrary spacing values (`mt-[13px]`) — use scale
|
||
or extend tokens.
|
||
|
||
### Layout primitives
|
||
|
||
- **Page chrome:** `<PageHeader title description>` for top of every route
|
||
- **Content area:** `space-y-4` (16px) between top-level sections
|
||
- **Panel:** `<Panel>` wraps tables / forms with `bg-surface border border-line`
|
||
- **Card:** `<Card>` для individual content blocks (`bg-surface-2`)
|
||
- **Drawer:** Right-side slide-in for detail views (review draft, schema
|
||
draft, history)
|
||
- **Modal:** Centered overlay для destructive confirmations + create flows
|
||
|
||
### Drawer-vs-Modal rules
|
||
|
||
| Pattern | Use Drawer | Use Modal |
|
||
|---|---|---|
|
||
| Review existing entity | ✓ | |
|
||
| Read-only history | ✓ | |
|
||
| Diff side-by-side | ✓ (max-w-3xl) | |
|
||
| Create new entity | | ✓ |
|
||
| Destructive confirmation | | ✓ (size=sm) |
|
||
| Maker action requiring focus | | ✓ |
|
||
|
||
Drawer = "look at this thing, maybe act on it". Modal = "do this thing now,
|
||
then dismiss".
|
||
|
||
### Width caps
|
||
|
||
- Drawer: `max-w-md` (notifications), `max-w-xl` (schema draft), `max-w-3xl`
|
||
(record review with diff panes)
|
||
- Modal: `size="sm"` (confirm), `"md"` (form), `"xl"` (JSON editor)
|
||
- Body copy: implicit via Panel padding; for description text apply
|
||
`max-w-prose` если viewport wide
|
||
|
||
---
|
||
|
||
## 5. Component Primitives
|
||
|
||
Locations: `src/ui/components/*.tsx`. Re-exported via `@/ui`.
|
||
|
||
### Stable primitives (use freely)
|
||
|
||
- `<Button variant size>` — `primary | secondary | ghost | danger`, `sm | md | lg`
|
||
- `<Badge variant>` — `info | success | warning | danger | default | primary | outline`
|
||
- `<Panel>` — surface wrapper для table/form blocks
|
||
- `<Card>` — content block (lighter surface than Panel)
|
||
- `<Drawer isOpen onClose title side widthClassName>` — slide-in detail view
|
||
- `<Modal isOpen onClose title size>` — centered overlay
|
||
- `<Table TableHead TableRow TableHeaderCell TableBody TableCell>` — tabular data
|
||
- `<Tabs items value onValueChange>` — Radix-based tabs with badge slot
|
||
- `<EmptyState icon title description action>` — no-data fallback
|
||
- `<Alert variant title>` — banner with `info | success | warning | error`
|
||
- `<LoadingBlock size label>` — skeleton / spinner
|
||
- `<QueryErrorState error onRetry>` — generic 4xx/5xx state с retry
|
||
- `<UserCell uuid>` — resolves sub → display name (three-tier cache)
|
||
- `<TextInput TextArea Checkbox Select MultiSelect>` — form inputs
|
||
|
||
### Domain-specific (in `src/components/`)
|
||
|
||
- `<ReviewDrawer>` — record draft review (records tab + Мои tab)
|
||
- `<SchemaDraftDrawer>` — schema draft review (dict page + Мои tab redirect)
|
||
- `<DictionaryEditorDialog>` — schema-aware editor с approval flow
|
||
- `<MyDraftsPanel>` — Мои tab on /reviews
|
||
- `<NotificationsDrawer>` + `<NotificationsBell>` — TODO 7 notification surfaces
|
||
- `<ChangelogDiffModal>` — historical schema diff viewer
|
||
- `<TimeTravelModal>` — record at point-in-time
|
||
|
||
---
|
||
|
||
## 6. Badge Variant Mapping
|
||
|
||
Visual category по semantic role, applied везде через `<Badge variant=…>`:
|
||
|
||
### Operations (DraftOperation)
|
||
|
||
| Op | Variant | Color | Reason |
|
||
|---|---|---|---|
|
||
| CREATE | success | green | Net-new record — positive change |
|
||
| UPDATE | info | accent | Most common; neutral default |
|
||
| CLOSE | warning | warn | Soft-delete; destructive but reversible |
|
||
|
||
### Record draft status (DraftStatus)
|
||
|
||
| Status | Variant | Reason |
|
||
|---|---|---|
|
||
| PENDING | info | Awaiting reviewer action |
|
||
| APPROVED | success | Terminal positive |
|
||
| REJECTED | danger | Terminal negative |
|
||
| WITHDRAWN | default | Maker-initiated cancel, neutral |
|
||
|
||
### Schema draft status (SchemaDraftStatus)
|
||
|
||
| Status | Variant | Reason |
|
||
|---|---|---|
|
||
| draft | warning | Pre-submit, maker action pending |
|
||
| review_pending | info | Reviewer queue |
|
||
| approved | success | Awaiting publish |
|
||
| changes_requested | warning | Bounced to maker |
|
||
| rejected | danger | Terminal negative |
|
||
| published | primary | Terminal live (strongest signal) |
|
||
| withdrawn | default | Maker cancel |
|
||
|
||
### Notification event type
|
||
|
||
| Pattern in eventType | Variant |
|
||
|---|---|
|
||
| `*Approved`, `*Published` | success |
|
||
| `*Rejected`, `*Withdrawn` | danger |
|
||
| `*Submitted`, `*ChangesRequested` | info |
|
||
| other | default |
|
||
|
||
---
|
||
|
||
## 7. Code / Diff Visual Language
|
||
|
||
### Live vs proposed panes
|
||
|
||
- **Live (current state):** `bg-line/30` background — muted, "established"
|
||
- **Proposed (draft):** `bg-accent/4` background — accent-tinted, "your suggestion"
|
||
- **CLOSE op note:** `text-aurora italic` — call-out пустой proposed pane
|
||
|
||
### Diff summary chips (above panes)
|
||
|
||
- `+N added`: text-aurora (green-family) with `+` prefix
|
||
- `−M removed`: text-danger with `−` prefix
|
||
- `~K changed`: text-warn with `~` prefix
|
||
- Bucket details в `text-mute lowercase` parens
|
||
|
||
Rendered via `shallowDiffSummary()` from `src/lib/diff.ts`.
|
||
|
||
---
|
||
|
||
## 8. Interaction States
|
||
|
||
### Required for all interactive elements
|
||
|
||
- `hover:bg-line/30` или similar hover affordance
|
||
- `focus-visible:ring-2 focus-visible:ring-ring` focus ring (NEVER
|
||
`outline: none` без replacement)
|
||
- `disabled:opacity-50 disabled:cursor-not-allowed` для disabled
|
||
- Touch target ≥ 44×44 — use `min-h-[44px]` for icon buttons (current gap;
|
||
audit findings P1 in TODOS.md TODO 6)
|
||
|
||
### Success / error affordances
|
||
|
||
- **Mutation success:** прокачка через `toast.success()` (sonner) — auto-dismiss 4s
|
||
- **Mutation error:** `toast.error()` с structured message via
|
||
`extractReviewError()` / `humanizeBulkReason()` mappings
|
||
- **In-form validation:** inline error under input с `role="alert"` +
|
||
`aria-invalid` + `aria-describedby`
|
||
- **Approve/reject decision flash:** 600ms bg-green-bg/30 или bg-pink-bg/30
|
||
band на drawer footer перед `onClose()` — ack ack для reviewer
|
||
|
||
### Empty states (warmth required)
|
||
|
||
Every `<EmptyState>` MUST have:
|
||
- `icon` — phosphor duotone, size 28-32
|
||
- `title` — outcome-framed ("Очередь пуста", not "No results")
|
||
- `description` — actionable hint (what to do next, where data appears)
|
||
- `action` (optional) — Link / Button to relevant route
|
||
|
||
---
|
||
|
||
## 9. Term Glossary (canonical)
|
||
|
||
| Code | EN | RU | Definition |
|
||
|---|---|---|---|
|
||
| maker | maker | автор | User who creates / submits a draft |
|
||
| reviewer | reviewer | ревьюер | User who approves / rejects drafts |
|
||
| publisher | publisher | публикатор | User who publishes approved schema drafts |
|
||
| draft | draft | черновик | Pending change awaiting approval |
|
||
| pending | pending | на ревью | Status: awaiting reviewer decision |
|
||
| approved | approved | одобрено | Status: reviewer approved (record published OR schema awaits publish) |
|
||
| rejected | rejected | отклонён | Status: reviewer rejected (terminal) |
|
||
| withdrawn | withdrawn | отозван | Status: maker cancelled (terminal) |
|
||
| WIP | in progress | в работе | Schema draft pre-submit OR changes_requested |
|
||
| decided | decided | закрыты | Any terminal status — APPROVED + REJECTED + WITHDRAWN |
|
||
| changes requested | changes requested | запрошены правки | Reviewer bounced schema draft back to maker |
|
||
| published | published | опубликован | Schema draft → live (terminal positive) |
|
||
|
||
Source: backend domain types (DraftStatus, SchemaDraftStatus, DraftOperation).
|
||
NEVER use synonyms ("submitter" for maker, "validator" for reviewer) —
|
||
breaks consistency with audit log + RBAC roles.
|
||
|
||
---
|
||
|
||
## 10. RBAC & Role Visibility
|
||
|
||
Backend gates (single source of truth). Frontend hide UI elements when
|
||
roles missing — backend still enforces if frontend leaks.
|
||
|
||
| Composite role | Members | UI surface |
|
||
|---|---|---|
|
||
| `ordinis:reviewer` | `schema:reviewer` + `record:reviewer` | /reviews tabs (Records, Schemas) — approve/reject buttons |
|
||
| (maker is implicit) | any authenticated user | /reviews Мои tab, draft create modals, dict editor |
|
||
|
||
Identity check pattern (in components):
|
||
```ts
|
||
const auth = useAuth()
|
||
const currentSub = auth.user?.profile?.sub
|
||
const isMine = draft.makerId === currentSub
|
||
```
|
||
|
||
For role gating use `usePermissions()` (src/auth/usePermissions.ts).
|
||
|
||
---
|
||
|
||
## 11. AI Slop Anti-Patterns (banned)
|
||
|
||
Per /design-review gstack skill plus our own observations:
|
||
|
||
- ❌ Purple/violet/indigo gradient backgrounds
|
||
- ❌ 3-column feature grid with icon-in-circle headers
|
||
- ❌ Decorative blobs / wavy SVG dividers / floating circles
|
||
- ❌ Emoji as design elements (rocket in heading)
|
||
- ❌ Colored left-border-only cards
|
||
- ❌ Generic hero copy ("Welcome to Ordinis", "Unlock the power…")
|
||
- ❌ Bubbly uniform `rounded-full` on every element
|
||
- ❌ Centered everything (`text-align: center` on all body content)
|
||
- ❌ system-ui / -apple-system as PRIMARY display font
|
||
|
||
Pass test: every visual element should be there because it does a job, not
|
||
because empty space looks empty.
|
||
|
||
---
|
||
|
||
## 12. Open Items (gaps to address in /design-consultation sprint)
|
||
|
||
- Responsive breakpoint policy (mobile 375, tablet 768, desktop 1024,
|
||
wide 1440 — confirm + lint)
|
||
- Touch target enforcement (P1 in TODO 6 a11y findings)
|
||
- Loading state taxonomy (skeleton vs spinner vs LoadingBlock vs Suspense
|
||
— when to use which)
|
||
- Motion duration policy (currently ad-hoc — set 150ms quick / 300ms medium
|
||
/ 500ms entrance)
|
||
- Long-content overflow rules (truncate vs wrap vs ellipsis vs scroll —
|
||
per surface type)
|
||
- Dark mode parity audit (some tokens have alpha inconsistency between
|
||
themes)
|
||
- Notifications channel UI policy (in-app vs email vs both — currently
|
||
every event goes to all enabled channels; should be user-configurable)
|
||
|
||
---
|
||
|
||
## Changelog
|
||
|
||
- **2026-05-14 v0** — Initial document. Captured tokens, components, badge
|
||
semantics, term glossary, drawer-vs-modal rules from approval workflow
|
||
saga codebase. Marked open items для full `/design-consultation`
|
||
sprint. Authored as part of TODO 5 closeout.
|