docs(design): DESIGN.md v1 — resolve Section 12 open items
This commit is contained in:
@@ -327,21 +327,188 @@ because empty space looks empty.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 12. Open Items (gaps to address in /design-consultation sprint)
|
## 12. Responsive Breakpoints
|
||||||
|
|
||||||
- Responsive breakpoint policy (mobile 375, tablet 768, desktop 1024,
|
Tailwind defaults — `sm=640 / md=768 / lg=1024 / xl=1280 / 2xl=1536`. Ordinis
|
||||||
wide 1440 — confirm + lint)
|
is an admin tool — desktop primary. Policy:
|
||||||
- Touch target enforcement (P1 in TODO 6 a11y findings)
|
|
||||||
- Loading state taxonomy (skeleton vs spinner vs LoadingBlock vs Suspense
|
- **Target primary:** `lg` (1024) and up — full layout fidelity
|
||||||
— when to use which)
|
- **Tolerate:** `sm`–`md` (640-1023) — degraded but functional (sidebar
|
||||||
- Motion duration policy (currently ad-hoc — set 150ms quick / 300ms medium
|
collapses to hamburger; tables horizontally scroll; tabs may wrap)
|
||||||
/ 500ms entrance)
|
- **Minimum viable:** 375px (iPhone SE narrow). No layout breaks; some
|
||||||
- Long-content overflow rules (truncate vs wrap vs ellipsis vs scroll —
|
density compromises accepted.
|
||||||
per surface type)
|
- **Avoid breakpoint ping-pong:** don't redefine layout at every step.
|
||||||
- Dark mode parity audit (some tokens have alpha inconsistency between
|
Standardize page padding: `px-4 sm:px-6 lg:px-8`. Big shifts only at `lg`
|
||||||
themes)
|
(sidebar collapse) and `xl` (max-width caps).
|
||||||
- Notifications channel UI policy (in-app vs email vs both — currently
|
|
||||||
every event goes to all enabled channels; should be user-configurable)
|
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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -351,3 +518,9 @@ because empty space looks empty.
|
|||||||
semantics, term glossary, drawer-vs-modal rules from approval workflow
|
semantics, term glossary, drawer-vs-modal rules from approval workflow
|
||||||
saga codebase. Marked open items для full `/design-consultation`
|
saga codebase. Marked open items для full `/design-consultation`
|
||||||
sprint. Authored as part of TODO 5 closeout.
|
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.
|
||||||
|
|||||||
Reference in New Issue
Block a user