UX Requirements — AI Software Factory
Interaction, layout, and usability requirements. Requirement IDs referenced here are canonical in product-requirements.md; functional detail in functional-requirements.md; tokens and component visuals in ../ui/design-system.md.
1. Application layout
1.1 Shell (desktop ≥ 1024 px)
┌────────────────────────────────────────────────────────────────────────────┐
│ ▣ Acme Inc. ⌕ Search… (⌘K) + New ⬡ Share ⋯ [👤 Ana ▾] 40 px │ ← top bar
├──────────────┬─────────────────────────────────────────────────────────────┤
│ ⌂ Home │ 📁 Website Redesign ●●● ⌕ ⋯ 240 px │
│ 📥 Inbox (3) │ ────────────────────────────────────────────────────────── │
│ ⭐ Favorites │ │
│ │ Title block (H1, inline editable) │
│ ▸ Engineering│ Description (muted, below title) │
│ ⌂ Home │ │
│ ▾ Projects │ [Table view ▾ | Board | Calendar] [+ View] [⚙] │
│ Website… │ ┌─────────────────────────────────────────────────────┐ │
│ Mobile… │ │ Name │ Status │ Owner │ Due │ ⋯ │ │
│ ▸ Product │ │ Website Redesign│ ● In Pro… │ Ana │ Sep 30 │ ⋯ │ │
│ │ │ Mobile App │ ○ To Do │ Ben │ Dec 15 │ ⋯ │ │
│ Private │ │ Data Platform │ ◐ Review │ Ana │ Oct 31 │ ⋯ │ │
│ Shared │ │ + New row ⋯ │ │
│ 🗑 Trash │ └─────────────────────────────────────────────────────┘ │
│ │ │
│ │ Blocks (editor) │
│ │ ┊ ⌫ Write something, type "/" for commands… │
└──────────────┴─────────────────────────────────────────────────────────────┘
| Zone | Rules |
|---|---|
Top bar (40 px, z-50) |
Left: workspace switcher (icon + name + chevron → workspace list, create). Center-left: global search field (read-only input; click or ⌘K opens palette). Right: + New menu, Share (when page open), ⋯ page menu, avatar menu (profile, theme, settings, sign out). Offline/realtime status chip appears center when degraded. |
| Sidebar (240 px default, 56–320 px resizable, collapsible to 56 px icon rail) | Sections in fixed order: Home, Inbox (unread badge), Search, Favorites, Teamspaces (expandable), Private, Shared, Trash. Each page row: icon, title, hover actions (⋯ menu, + child). Active row highlighted (bg-accent). Drag to reorder/reparent (§ DnD). "More" footer → settings, help. |
| Content | Max content width 900 px (pages) / 960 px (databases) centered; scroll container is the content pane only (top bar + sidebar fixed). Scrollbar: 12 px, auto-hiding on desktop. |
1.2 Page anatomy (top → bottom)
┌──────────────────────────────────────────────────┐
│ [cover image — full-bleed, 150–400 px, drag to │
│ resize, hover: ⚙ change / 🗑 remove] │
│ │
│ 🚀 (icon — emoji or file, 48 px; click │
│ to open icon picker) │
│ Website Redesign ← title (H1, inline) │
│ Redesign the marketing site…← description (muted│
│ , ≤ 2 lines, inline, collapsible) │
│ ─────────────────────────────────────────────── │
│ [properties bar — if page is a record: chips/ │
│ inline property editor row, "Show more"] │
│ │
│ Block 1 │
│ Block 2 ← hover gutter: ⋮⋮ + "+" │
│ Block 3 │
└──────────────────────────────────────────────────┘
| Element | UX rules |
|---|---|
| Cover | Click cover → upload / pick from files / remove; height drag handle at bottom edge (snaps 150/250/400 px); lazy-loaded below the fold. |
| Icon | Click (or + when absent) → emoji grid (searchable) or "upload image"; keyboard: Enter opens, Esc closes. |
| Title | Placeholder "Untitled"; Enter in title → move to first block; Backspace on empty title does nothing (never deletes page); max 200 chars (soft warning at 180). |
| Description | Collapses to one line + "…" expander; editable inline; max 500. |
| Properties bar (record pages) | Horizontal wrap of property chips (icon + label + value); chip click → inline editor for that type; "Show more (n)" → popover with all properties; order follows property position. |
2. Editor UX
2.1 Caret & selection
- Caret is the native text caret inside Tiptap; the block is never outlined except when selected (multi-select →
bg-accent/10+ left 2 pxborder-accent). - Empty block placeholder is
text-tertiary, disappears on focus with text; placeholder for the last block on a page is "Type '/' for commands…", for other empty blocks "Empty — press Enter to continue". - Select-all ladder:
⌘A×1 = text in block; ×2 = block (including children); ×3 = all blocks in page. - Copy a selected block → clipboard holds (a) rendered markdown for external apps and (b) internal JSON for cross-block moves; paste prefers internal JSON, falls back to markdown/HTML/RTF/plain.
- Focus restoration: after any dialog (icon, color, share), caret returns to its exact offset.
2.2 Block hover controls
┊⌫ Write something, type "/" for commands… ← normal text
[⋮⋮] [+] ← appear in the 40 px left gutter on hover (≥150 ms)
- ⋮⋮ handle: drag to move (see functional §4.3); click → block context menu; keyboard:
Spaceon focused handle lifts the block (arrow keys move it,Spacedrops,Esccancels) — the non-drag alternative (PR-056). - + button: inserts a new empty paragraph below (or opens the slash menu directly).
- Selected blocks show a floating selection toolbar (top-right of the selection): duplicate, copy, delete, color, move-to; appears < 100 ms.
2.3 Slash menu UX
┌──────────────────────────────────────┐
│ /hea ⌕ (fuzzy) │
│ ─ Text ───────────────────────────── │
│ ▸ Heading 1 H1 (⌘⇧1) │
│ ▸ Heading 2 H2 (⌘⇧2) │
│ ▸ Heading 3 H3 (⌘⇧3) │
│ ─ Data ───────────────────────────── │
│ ▸ Table ⌗ │
│ ▸ Database 🗃 new properties│
│ ─ Media ──────────────────────────── │
│ ▸ Image 🖼 │
└──────────────────────────────────────┘
↑↓ navigate · Enter select · Esc close · Tab filter group
- Opens at ≥ 1 result (150 ms debounce on the query); groups in fixed order: Text, Lists, Media, Code, Data, Layout, Embeds; each row: icon, name, optional shortcut hint, optional "new" badge for database.
- Database-family commands open the property picker first: table of default properties (Name/title, Status select) with add-row, type dropdown (22 types), "Create 'Projects'" button.
- Results are keyboard-first: arrows + Enter; mouse hover previews (no click-then-click).
2.4 Context menu structure
Menu = flat list with dividers and submenus, 260 px wide, max 14 visible rows (scroll).
Block menu (exact order — functional §4.3): Turn into ▸ · Duplicate · Copy · Copy link · Move to ▸ · Delete · ⎯ · Comment · Ask AI ▸ · ⎯ · Color ▸ · Background ▸ · ⎯ · Convert to ▸ · ⎯ · More ▸.
Page menu (sidebar row hover → ⋯, or top-bar ⋯): Open · Open in new tab · Duplicate · Rename · Move ▸ · Copy link · Favorite (✓ when on) · Archive · Delete · ⎯ · Export ▸ · Share · Permissions.
Rules: items the caller cannot perform are hidden (not disabled) for guests; destructive items (Delete, Archive) get text-danger; "Delete" opens a confirm dialog for pages, executes immediately for blocks (undo-able via ⌘Z + "Undo delete" toast action).
2.5 Undo/redo & copy/paste UX
- Undo/redo stack covers: text edits, block create/delete/move/convert, property edits, view-setting changes. History survives page navigation within the session (50 entries per page).
- Destructive ops that are not undo-able (delete page → trash, purge) show a toast with Undo (5 s) where a safe inverse exists.
- Paste from external: detection order internal JSON → HTML → markdown → RTF → plain text; pasted tables/checkboxes convert to native blocks; images are uploaded (toast "Uploading 2 images…") and appear as placeholders until
READY.
3. Command palette (⌘K / Ctrl+K)
Single overlay (centered, 640 px, z-100), fuzzy over actions + pages + databases + commands, groups:
| Group | Items |
|---|---|
| Create | New page, New page in… (submenu of top-level pages), New database, New team, New template, New dashboard, New form |
| Go to | (pages/databases from fuzzy search, recent first, max 20) |
| Actions | Invite people, Copy link to current page, Duplicate page, Move page, Export current page, Save as template, Toggle dark mode, Toggle sidebar, Go to Inbox, Go to Search, Go to Settings ▸ (Profile / Members / Security / Billing) |
| Workspace | Switch workspace (list + "Manage workspaces"), Create workspace |
| AI | Ask AI (opens chat), Summarize page, Improve selection, Generate database from selection |
| Data | Import, Reindex search (admin), Rebuild cache (admin) |
Behavior: type filters within + across groups; ↑↓ + Enter; Esc closes; palette remembers last-used group; every action has the same permission gating as its menu counterpart.
4. Database view UX
Common rules: view switcher tabs (left) + + (type picker); active tab border-b-2 border-accent text-primary; per-view settings gear (right) opens a right-side drawer (320 px) with tabs Filter · Sort · Group · Properties · Display; all drawer edits apply immediately (no Save button — 400 ms debounced autosave, "Saved" chip).
4.1 Table
- Virtualized: rows (64 px default / 48 px compact) and columns; first column (title) sticky + frozen at horizontal scroll start.
- Cell editing: click → inline widget (functional §5.1);
Entercommits + moves down;Tabcommits + right;Escreverts; multi-line types (RICH_TEXT) expand the row. - Row hover: left gutter
+(add below) and right⋯(open page, duplicate, archive, add comment, copy link); selected rows → bulk bar (n selected: Archive · Duplicate · Assign ▸ · Add comment · Delete). - New row: always-visible "+ New row" footer row;
Enteron it creates + focuses title cell. - Column: header click = sort (cycle none → asc → desc, arrow indicator); header drag = reorder; right edge drag = resize (min 80, max 500, persisted); header menu: hide, property settings (type, options, default), delete property (confirm; blocked for TITLE).
- Empty database: illustration + "Add your first record" primary button + "Add a property" secondary.
4.2 Board
- Columns from the group property (option order); column header: name, count,
⋯(add card, column settings, hide column); "Empty" column for nulls always last. - Card: cover (if set), icon, title, up to 3 configured property rows (pills/dates/avatars); click → record page; card drag: within column = reorder, across = property update (optimistic, 300 ms settle, snap-back on error + toast).
- Add card: "+" per column (pre-sets group value) and floating card at column bottom.
4.3 Calendar
- Toolbar: month name + prev/today/next · view switch (Day/Week/Month) · "New record" (opens with the visible day prefilled).
- Month: 7-col grid, 6 rows; day cells show ≤ 3 record chips (title, color by group/status) + "+n more" → day popover list; today = accent ring.
- Week/Day: time-less list rows; records sorted by start date; ranges show a span badge "→ 5 days".
- Empty day cell: ghost "+ Add" on hover.
4.4 Gallery
- Grid: responsive
auto-fill, minmax(220 px, 1fr); card: cover (aspect 16:9, fallback icon onbg-secondary), title, ≤ 3 property rows; hover: lift (shadow-md, 150 ms). - Card settings (drawer → Display): cover on/off, card size (S/M/L adjusts minmax), property rows picker (≤ 3), title line-clamp 1/2.
4.5 Timeline
- Toolbar: zoom (Day/Week/Month/Quarter) + "New record" (spans visible range).
- Lane per group value (horizontal bands); bars: rounded 8 px, label inside when width > 120 px else right side; drag bar = move (updates start+end), drag right edge = resize end; snap to day boundary at current zoom; today = vertical accent line; bars with missing dates render in "No date" lane (muted).
- Axis: month ticks (or day ticks at Day zoom), sticky on horizontal scroll.
4.6 Chart / Dashboard / Form views
- Chart view: renders the database's chart; empty state = "Add a chart" wizard (type → axes → aggregation → done).
- Dashboard view: widget grid (§12 functional); add-widget picker; read-only editing (open-source actions).
- Form view: form builder (draft) or public form render (published); builder is a two-pane: question list (drag-reorder) + live preview.
5. States: empty, loading, error
| State | Rule |
|---|---|
| Loading (initial) | Skeletons, never spinners, for: page body (3 line blocks), database table (12 ghost rows), sidebar tree (5 ghost rows), search (3 result ghosts). Shown only when a fetch exceeds 150 ms (avoid flash). |
| Loading (mutation) | The exact control spins/disables (button opacity-60 pointer-events-none + 14 px spinner); row-level: cell keeps its value + subtle pulse overlay. Optimistic UI for: todo toggle, checkbox, board drag, comment send (sent state, then confirm). |
| Loading (background) | Top-bar progress bar (2 px, accent, indeterminate) for bulk ops (import, reindex, export). |
| Empty | Every list/table has a dedicated empty state: illustration (monochrome, 96 px) + one-line headline + primary CTA. Databases: "Add your first record". Search: no query → recent searches; no results → "No results for 'x'" + filter-reset. Inbox: "You're all caught up". Trash: "Trash is empty" + "Restore all" (when non-empty) / "Permanently delete" (admin). |
| Error (fetch) | Inline error panel in the content area: icon + message + Retry button. For search: "Search is unavailable — try again" (degraded mode). |
| Error (permission) | Page-level: 403 screen "You don't have access" + "Request access" (sends notification to owner) — never a blank page. Cell-level: value hidden, "Hidden" chip. |
| Error (validation) | Inline field errors (below field, text-danger, 12 px) + field border-danger; focus first invalid field on submit; aria-live polite. |
| 410 / deleted | "This page was deleted" + (if owner) "Restore" / "View trash". |
Toast rules (z-110, stack max 4, bottom-right desktop / bottom-center mobile, 4 000 ms default / 7 000 ms with action): kinds = info / success / warning / error; icon + message + optional action (Undo, Retry, View) + close; errors persist until dismissed unless they have a retry action (then 7 s); toasts are role=status (info/success) or role=alert (error) for screen readers.
6. Onboarding flow (first run, personal workspace)
| Step | Content |
|---|---|
| 0 | Post-login landing: "Welcome to <Name>" screen (no page yet) with 3 cards: Create your first page, Invite your team, Try a template. |
| 1 | "Create your first page" → creates Getting started page pre-filled with: welcome text, a todo list (type /, drag a block, try the slash menu — 3 checked-as-you-go items), a mini 3-row database ("Try a database"), a callout with tips. |
| 2 | Progress: each interaction (type in editor, open slash menu, drag a block, add a record) checks its todo in real time; all done → confetti-free success toast "You're ready 🎉" + button "Explore templates". |
| 3 | Team-workspace variant (invite-accept flow): skip step 1 (owner's Getting started visible if shared) → "Invite your team" card primary. |
| Skip | "Skip for now" always visible; onboarding state persisted per user (user.settings.onboardedAt); re-openable via Help → "Getting started". |
7. Responsive rules
| Breakpoint | Width | Rules |
|---|---|---|
| Desktop | ≥ 1024 px | Full shell (§1). Columns render side-by-side. Context menus right-click + handle. |
| Laptop | 768–1023 px | Sidebar auto-collapses to 56 px icon rail (hover-expands, non-modal); database: table scrolls horizontally (sticky title col); board: horizontal scroll; calendar: week view default; columns: 2-up max. |
| Tablet | 640–767 px | Sidebar is an overlay drawer (hamburger, z-90, swipe to close); top bar search → icon (opens full-screen search); command palette full-screen; columns stack; context menu = long-press or ⋯ button on each block; DnD: drag handles enlarged (44 px hit area), drop targets snap to nearest edge. |
| Mobile | < 640 px | Single-column everything; editor: full-width blocks, ⋮⋮ always visible as ⋯ in the right gutter; database: table → list view forced (cards), board keeps horizontal scroll with 80 % viewport cards, calendar → day list; toasts bottom-full-width; modals → full-screen sheets (slide up, 24 px top grab handle). |
Rules of thumb: minimum touch target 44 × 44 px below 1024 px; no hover-only functionality without a tap equivalent; text never scales below 14 px body; 1 px borders become border tokens that render at 1.5 px on coarse pointers.
8. Theming (UX contract — token detail in design-system.md)
- Three modes: light / dark / system (user setting, persisted; default system).
- App code uses token names only (
bg-surface,text-primary,border-default,accent) — a lint rule (no-restricted-syntaxfor hex literals inapps/web) enforces PR-164. - Brand accent is fixed (not user-configurable in v1); semantic colors:
success(green),warning(amber),danger(red),info(blue) — each with abg-softvariant for chips. - Database status/select option colors: fixed 28-color palette named by hue (
gray, brown, orange, yellow, green, blue, purple, pink, red+bgvariants) — stable across themes, contrast-checked in both themes. - Motion: 150 ms standard transitions (opacity/transform), 200 ms drawers, 120 ms hover; reduced-motion mode (OS setting or manual) disables all non-essential motion (no auto-scroll animations, no card lift, instant panel swaps) per PR-165.
9. High-contrast & accessibility
- High-contrast mode (WCAG Enhanced prep): a
hcclass on<html>swaps the token set to pure black/white + 2 px borders + no soft backgrounds; shipped in v1.1 (flagged behindFEATURE_HC), required for enterprise. - See §12 for the WCAG 2.2 AA checklist.
10. Keyboard shortcut master table
Mac ⌘ / Windows Ctrl shown as ⌘/Ctrl. Esc always: closes top-most overlay.
| Category | Shortcut | Action |
|---|---|---|
| Global | ⌘/Ctrl+K |
Command palette |
⌘/Ctrl+P |
Quick open page (alias of palette, "Go to" group) | |
⌘/Ctrl+Shift+F |
Global search (opens search page) | |
g h / g i / g f / g t |
Go to Home / Inbox / Favorites / Trash (Vim-style, 1 s window) | |
n |
New page (in current location) | |
⌘/Ctrl+Shift+I |
Invite people | |
⌘/Ctrl+Shift+L |
Link (selection) / share current page (no selection) | |
? (in editor) |
Shortcuts help overlay | |
| Editing | Enter / Shift+Enter |
New block / soft break |
Backspace / Delete |
Merge-prev / merge-next | |
Tab / Shift+Tab |
Indent / outdent | |
↑ ↓ ← → |
Caret & block navigation (with scrolling) | |
⌘/Ctrl+A (×3) |
Text → block → all blocks | |
⌘/Ctrl+C / X / V |
Copy / cut / paste (blocks) | |
⌘/Ctrl+Z / ⌘/Ctrl+Shift+Z / ⌘/Ctrl+Y |
Undo / redo / redo | |
⌘/Ctrl+B / I / U |
Bold / italic / underline | |
⌘/Ctrl+Shift+S |
Strikethrough | |
⌘/Ctrl+E |
Inline code | |
⌘/Ctrl+Shift+1/2/3/0 |
H1 / H2 / H3 / paragraph | |
⌘/Ctrl+Shift+8 |
Toggle block | |
/ |
Slash menu (at block start) | |
| Blocks | ⌘/Ctrl+Shift+D |
Duplicate selection |
⌘/Ctrl+Shift+C |
Copy link to block | |
⌘/Ctrl+Shift+M |
Move block (opens "Move to") | |
| Database (cell focus) | Enter / Tab / Shift+Tab |
Commit+down / commit+right / commit+left |
Esc |
Revert cell edit | |
⌘/Ctrl+Enter |
Open record page | |
⌘/Ctrl+Shift+A |
Select all rows (in view) | |
| Views | 1…9 (view focus) |
Switch to view # |
f |
Focus filter drawer (in view) | |
| Palette | ↑ ↓ + Enter |
Navigate / select |
Tab |
Cycle result groups | |
| Non-drag DnD | Space (on focused ⋮⋮) |
Lift block → ↑↓←→ move → Space drop, Esc cancel |
All shortcuts are remappable (Settings → Shortcuts, v1.1) and none collide with browser defaults beyond the ⌘/Ctrl+letter set listed. Every shortcut has a menu equivalent (a11y).
11. Focus & overlays (dialog/menu contract)
- Focus trap: every modal/menu/palette traps Tab (first focusable on open,
Escor confirm returns focus to the invoker). - Layering (z-index tokens): content 0 → block toolbar 30 → drawer 40 → top bar 50 → sidebar drawer (mobile) 90 → palette 100 → toasts 110 → confirm dialog 120.
- Only one overlay may own the mouse at a time; nested menus (submenus) are allowed and close on blur.
- Confirm dialogs: headline + body +
Cancel(secondary, default) +Confirm(primary;dangervariant for destructive — typed confirmation for delete-workspace/purge).
12. Accessibility requirements (WCAG 2.2 AA)
Global: all interactive elements reachable by keyboard in a logical order; visible focus ring (2 px accent, 2 px offset) on every focusable; landmarks (banner, navigation, main, complementary, contentinfo); skip to content link first in tab order; lang attribute set from user locale; color contrast ≥ 4.5:1 (text) / 3:1 (large text, UI components) verified for every token pair in both themes.
| Component type | Requirements |
|---|---|
| Block editor | Each block exposes ARIA: contenteditable region with role=textbox semantics per block; headings are real h1–h3; lists role=list/listitem; todo = role=checkbox with aria-checked; toggle = aria-expanded; drag alternative via Space lift (§10); "Move to" context-menu path for every drag; block selection announced (<n> blocks selected, role=status); paste results announced. |
| Slash menu / context menu / dropdowns | role=menu + role=menuitem (or combobox pattern for slash), arrow-key navigation, aria-activedescendant or roving tabindex, aria-haspopup, aria-expanded on trigger, Esc closes & returns focus, items have accessible names (icon + text). |
| Command palette | role=combobox input + role=listbox results; announces result count; results navigable by arrow; aria-controls links input → listbox. |
| Dialogs / drawers | role=dialog aria-modal=true with aria-labelledby (headline) + aria-describedby (body); focus trap; Esc = cancel; focus restored to trigger on close. |
| Toasts | role=status (info/success) / role=alert (error), auto-announced, dismissible, timer paused on hover/focus (2.5.8 Pointer Lock / timeout control). |
| Database table | role=grid with role=row/columnheader/gridcell; cell editing exposes role=combobox/spinbutton/switch per widget type; column headers aria-sort; row selection announced; virtualized rows keep a stable aria-rowindex; keyboard: arrows move cells, Enter edits, Space selects row. |
| Board / kanban | Cards focusable; Space lifts card (arrows move between columns, announce "Column: In Progress, 3 of 7"); drop announced with new column + position. |
| Calendar | Day cells = role=gridcell with date label; "n more" is a button; today announced ("Today"); navigation keys (PageUp/PageDown month, Shift+PageUp/Down week). |
| Sidebar tree | role=tree / treeitem, aria-expanded on parents, arrow-key tree navigation (up/down select, right expand, left collapse), Home key = first, End = last. |
| Forms (public + builder) | Labels always associated (for/aria-labelledby), required marked (aria-required + visible *), errors aria-describedby + aria-invalid, live region for submit progress, file input has visible name + remove button. |
| Comments | Thread = role=feed (aria-busy while loading); each comment labeled "Name, 2h ago, resolved"; reaction buttons have aria-pressed; mention popup = listbox. |
| Realtime presence | Cursors: decorative (aria-hidden) — presence list is a role=status live region updated ≤ 1/s ("Ana is editing this page"); no reliance on color alone for cursor names (name label on cursor). |
| Media | Images: alt required at upload (prompted, skippable → "Decorative" sets alt=""); video/audio have native controls + text alternatives; embeds get aria-label from the URL title. |
| Motion | prefers-reduced-motion honored (auto) + manual toggle; no content flashes > 3×/s; auto-scroll during drag disabled in reduced motion (jump instead). |
| Touch targets | ≥ 44 × 44 px at all breakpoints below 1024 px; adjacent targets ≥ 8 px apart (2.5.8). |
| Target size / hover (2.2) | Hoverable controls (hover-only gutter buttons) have a tap/keyboard equivalent; hover-intent menus close on pointer exit with 300 ms grace (1.4.13). |
Verification: axe-core scan in the E2E suite (0 critical/serious per MVP flow), manual screen-reader pass (VoiceOver + NVDA) on the 13 MVP flows before each release (checklist in ../development/testing.md).