527 lines
21 KiB
Markdown
527 lines
21 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. Responsive Breakpoints
|
||
|
||
Tailwind defaults — `sm=640 / md=768 / lg=1024 / xl=1280 / 2xl=1536`. Ordinis
|
||
is an admin tool — desktop primary. Policy:
|
||
|
||
- **Target primary:** `lg` (1024) and up — full layout fidelity
|
||
- **Tolerate:** `sm`–`md` (640-1023) — degraded but functional (sidebar
|
||
collapses to hamburger; tables horizontally scroll; tabs may wrap)
|
||
- **Minimum viable:** 375px (iPhone SE narrow). No layout breaks; some
|
||
density compromises accepted.
|
||
- **Avoid breakpoint ping-pong:** don't redefine layout at every step.
|
||
Standardize page padding: `px-4 sm:px-6 lg:px-8`. Big shifts only at `lg`
|
||
(sidebar collapse) and `xl` (max-width caps).
|
||
|
||
Explicit non-goal: native mobile-app feel. Users on phones are doing
|
||
inspection / approval triage, not maker-flow data entry. See Section 19
|
||
disclosure.
|
||
|
||
---
|
||
|
||
## 13. Touch Targets
|
||
|
||
WCAG AAA recommends 44×44 minimum. Ordinis policy:
|
||
|
||
- **All interactive elements** (`button`, `a` with `href`, `[role=button]`,
|
||
inputs) — `min-h-[44px]` enforced.
|
||
- **Icon-only buttons** (Bell, theme switch, language switch, hamburger) —
|
||
`size-11` (44×44) wrapper instead of current `size-7`. Visual icon stays
|
||
small; hit area is the padded wrapper.
|
||
- **Tabs** — `h-11` instead of `h-10`. Lose 4px header height, gain WCAG
|
||
compliance.
|
||
- **Sidebar nav rows** — full row click area (already correct);
|
||
`py-2.5` ensures ≥ 44px tall.
|
||
- **Table action links** ("Open →") — wrap in `<Button variant="ghost" size="sm">`
|
||
which already has 44px hit area, despite small visual text.
|
||
- **Exception:** icons inside larger clickable containers (e.g. unread dot
|
||
inside a notification row that's clickable as a whole) — hit area belongs
|
||
to parent, not the icon itself.
|
||
|
||
Enforcement: a follow-up MR sweeps current 28–32px buttons → 44px wrappers.
|
||
Tracked in TODOS.md TODO 6 punch list.
|
||
|
||
---
|
||
|
||
## 14. Loading State Taxonomy
|
||
|
||
| Surface | Pattern | Rationale |
|
||
|---|---|---|
|
||
| Inline cell / quick value | `<LoadingBlock size="sm">` | Local replacement, no layout shift |
|
||
| Panel / table / drawer body | `<LoadingBlock size="md">` | Replaces whole section, matches Panel padding |
|
||
| Whole route mount | Skeleton matching real shape (TanStack Router pending state) | First paint should hint at content layout |
|
||
| Suspense fallback | `<LoadingBlock size="md">` | Same as panel — Suspense boundaries wrap panels |
|
||
| Inflight mutation | Button `loading` prop (replaces label with spinner) | Local feedback at the trigger |
|
||
| Optimistic updates | No loading affordance — use `useOptimistic` | Most modern pattern |
|
||
|
||
Never use a bare `<Spinner>` outside `<Button loading>`. Spinners detached
|
||
from context are anxiety-inducing — replace with a skeleton or LoadingBlock
|
||
sized to the missing content.
|
||
|
||
---
|
||
|
||
## 15. Motion Durations
|
||
|
||
Four-step scale. Implemented via Tailwind utility `duration-{step}` mapped
|
||
to CSS custom properties so the same scale applies to bespoke transitions.
|
||
|
||
| Step | Range | Use |
|
||
|---|---|---|
|
||
| `micro` | 50–100ms | Hover, focus rings, toggle thumb, button press |
|
||
| `short` | 150–250ms | Dropdown / popover open, success/error flash on form input, badge appearance |
|
||
| `medium` | 300–400ms | Drawer slide-in, modal fade, ReviewDrawer decision flash, tab content fade |
|
||
| `long` | 500–700ms | Route entrance choreography, hero reveals, complex sequence |
|
||
|
||
### Easing
|
||
|
||
- **Entrance** (hidden → visible, off-screen → on-screen): `ease-out` —
|
||
start fast, settle in
|
||
- **Exit** (visible → hidden): `ease-in` — start gentle, accelerate away
|
||
- **Move** (element relocates / resizes in-place): `ease-in-out`
|
||
- **Linear**: only for indeterminate loops (spinner rotation, progress
|
||
bar bounce)
|
||
|
||
### Rules
|
||
|
||
- Animate `transform` and `opacity` only — never width/height/top/left
|
||
(triggers layout reflow on every frame)
|
||
- `transition: all` is banned — list properties explicitly
|
||
- Respect `prefers-reduced-motion`: replace entrance animation with instant
|
||
state swap; spinner becomes pulsing dot
|
||
- No animation > 700ms without user-controlled trigger (clicking expand, etc.)
|
||
|
||
---
|
||
|
||
## 16. Long-Content Overflow Rules
|
||
|
||
| Surface | Pattern |
|
||
|---|---|
|
||
| Table cells (string columns) | `truncate` + native `title={fullValue}` tooltip |
|
||
| Drawer / Modal title | `truncate` (single line, no wrap) |
|
||
| Body description text | Wrap naturally; `max-w-prose` on wide screens |
|
||
| Code blocks, JSON diff panes | `overflow-x-auto whitespace-pre font-mono` |
|
||
| Comments (makerComment / reviewComment) | `line-clamp-2` on mobile, full on `md+` |
|
||
| Notification preview text | `line-clamp-2` always (drawer list is dense) |
|
||
| Search results / lists | Wrap; `break-words` for long URLs/identifiers |
|
||
| Tab labels | Never truncate — if label is long, label is wrong |
|
||
|
||
Tooltip + truncation rule: whenever applying `truncate`, ALWAYS pair with
|
||
native `title` attribute (or `<Tooltip>` primitive for rich content). Don't
|
||
let truncation hide information without a recovery path.
|
||
|
||
---
|
||
|
||
## 17. Dark Mode Parity
|
||
|
||
Audited 2026-05-14 — found `*-bg` alpha tokens differ between themes (11%
|
||
light vs 16% dark). Drift from independent token tweaks, not intentional.
|
||
|
||
### Policy
|
||
|
||
- **Surface elevation** via opacity / shadow, NOT lightness inversion. Light
|
||
→ dark = different palette, not flipped lightness. Already implemented;
|
||
keep.
|
||
- **Accent saturation:** reduce 10–20% in dark mode to avoid vibration on
|
||
dark surface. Already implemented (`--color-accent: #b05a2e` light vs
|
||
`#d97757` dark which is brighter but compensated for darker bg).
|
||
- **`*-bg` alpha consistency:** normalize to 12% in both themes via new
|
||
`--alpha-bg-tint` token. One-time MR. Visual diff acceptable (slight
|
||
saturation shift on info badges).
|
||
- **Text on accent (`--color-on-accent`):** verified ≥ 4.5:1 ratio both
|
||
themes. Light = white (#fff) on `#b05a2e` ≈ 4.8:1 OK. Dark = `#1c1a17`
|
||
on `#d97757` ≈ 7.2:1 OK.
|
||
- **`text-mute` on `bg-surface`:** light ≈ 4.6:1 (just passes), dark ≈ 4.8:1.
|
||
Borderline — re-verify after Section 13 button rework.
|
||
|
||
---
|
||
|
||
## 18. Notifications Channel UX
|
||
|
||
Phase A (current, MR !190): every fired event dispatches to all enabled
|
||
channels at backend level. EmailChannel is the only Phase A channel.
|
||
Reviewer-pool address hardcoded in `application.yml`. Bell + drawer + toast
|
||
already shipped on frontend.
|
||
|
||
### Phase B (deferred — TODO follow-up)
|
||
|
||
User-configurable preferences via new route `/me/notifications/preferences`.
|
||
|
||
Surface design when shipped:
|
||
- Table: rows = event types, columns = channels (Email / Express / Telegram /
|
||
In-App). Toggle per cell.
|
||
- Save button bottom-right; optimistic update.
|
||
- "Mute all" + "Reset to defaults" quick actions in header.
|
||
- Defaults: all on; per-user state in `user_notification_prefs` table
|
||
(migration 0021 already has it).
|
||
|
||
Anti-pattern: don't bury preferences inside profile settings. Notifications
|
||
preferences live one click from the bell — make the bell drawer footer have
|
||
a settings cog.
|
||
|
||
### Future channel ordering (Phase C, "Don't Disturb")
|
||
|
||
Per-channel priority for DND mode. Email always lowest (durable). Express/Slack
|
||
mid. Telegram highest urgency. Bell + in-app toast always on.
|
||
|
||
---
|
||
|
||
## 19. Disclosure: Known Tradeoffs
|
||
|
||
This admin UI **deliberately deprioritizes mobile-first design**. Affordances:
|
||
|
||
- Native touch targets bigger than 44px not enforced on data-cell click
|
||
areas (would explode row heights). Cells with `<UserCell>` or row-level
|
||
click handlers rely on the parent row hit area.
|
||
- Tables don't reformat into card-stacks on narrow screens — they scroll
|
||
horizontally.
|
||
- Forms don't shift to one-column layouts under 768px — they compress.
|
||
- Server-side rendering not used; client-only SPA means full-app JS bundle
|
||
required for any page.
|
||
|
||
Users on phones doing emergency approvals are supported. Users doing
|
||
maker-flow data entry on phones are not. Don't ship to a context where the
|
||
latter matters without prior architecture work.
|
||
|
||
---
|
||
|
||
## 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.
|
||
- **2026-05-14 v1** — Section 12 (open items) resolved via `/design-consultation`.
|
||
Added Sections 12–19 with concrete policies: responsive breakpoints, touch
|
||
targets, loading taxonomy, motion durations, overflow rules, dark mode
|
||
parity, notifications channel UX, known-tradeoffs disclosure. Touch target
|
||
follow-up MR pending; dark mode alpha normalization MR pending; Phase B
|
||
notifications prefs TODO.
|