Files
mdm-ordinis/DESIGN.md
T
2026-05-14 14:40:35 +00:00

354 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.