Add prototype/openapi/validate/resume commands, wire edge-failure matrix into UX, enforce traceability + validation gates, mandate C4/C5 belief-runtime verification. Rework 038-dashboard-scenario-model artifacts: applicability, structured edge cases, 24-class UX state matrix, interactive HTML prototype, standardized OpenAPI 3.1 (7 ops), full RTM with coverage gate, 56-task backlog, and PASS validation report.
22 KiB
description, handoffs
| description | handoffs | |||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Interactive UX design session — asks questions, presents alternatives, exhaustively designs every screen state (systematic edge/failure matrix), then generates Screen Model code and UX contracts. |
|
User Input
$ARGUMENTS
You MUST consider the user input before proceeding (if not empty).
Principle
You are a UX designer, not a contract generator. Your job is to ask questions the spec didn't answer, present visual and interaction alternatives, and work through every screen state exhaustively before writing a single contract. Contracts are the OUTPUT of design decisions, not the input.
Outline
Phase 0: Load Context
- Setup: Run
.specify/scripts/bash/check-prerequisites.sh --json→FEATURE_DIR. - Load:
FEATURE_DIR/spec.md— user stories, acceptance criteriaFEATURE_DIR/ux_reference.md— high-level narrative (if exists).opencode/skills/semantics-svelte/SKILL.md— §VI canonical template, §VII design tokensfrontend/src/lib/ui/— available atoms (Button, Card, Input, Select, PageHeader...)frontend/src/lib/components/— available widgets (MultiSelect, SearchableMultiSelect...)frontend/src/lib/models/— existing Screen Models (reuse or extend)
Phase 1: Screen Decomposition — ASK, don't assume
For EACH user story in spec.md that has a UI surface, ask:
## Screen: [Story Title]
**1. Navigation structure**
How does the user reach this screen?
A) Separate route: /feature-name
B) Modal/drawer over existing page
C) Tab/section within existing page: /existing#feature
D) Other: [describe]
**2. Layout strategy**
A) Single column, full width — simple CRUD
B) Two-column: list + detail panel
C) Wizard: multi-step with progress indicator
D) Dashboard: cards/grid with filters
E) Other: [describe]
**3. Data density**
How much data does the user see at once?
A) Few items (<20): simple list, no pagination
B) Medium (20-200): paginated table with search
C) Large (200+): paginated table + filters + search
D) Real-time stream: WebSocket updates, auto-scroll
Present 2-3 concrete alternatives with tradeoffs. Wait for user response before continuing to the next question.
Phase 2: State Exhaustion — Systematic Edge & Failure Matrix
For each screen, work through ALL states exhaustively. This is where most UX bugs hide — the states between "loading" and "loaded". Use the systematic edge/failure state matrix below to ensure NO state class is missed.
Edge & Failure State Matrix (Systematic)
Every screen MUST evaluate each of these state classes. Mark each as Applicable (with concrete UX) or Not Applicable (with rationale). Never blanket-reject a state class without evidence.
| # | State Class | Probability | Trigger | Visual/Feedback | Recovery | Test Ownership |
|---|---|---|---|---|---|---|
| NET_01 | Network offline | Medium | navigator.onLine == false |
Offline banner at top, disabled actions | Auto-retry on reconnect (online event); manual "Retry" button |
L2 |
| NET_02 | Timeout (>30s no response) | Medium | AbortController timeout | Toast: "Request timed out" + progress bar retry countdown | Retry with exponential backoff (3 attempts); "Cancel" button | L1+L2 |
| NET_03 | Retry exhaustion | Low | 3 failed retries | Persistent error banner: "Could not reach server. Check your connection." + manual retry button | Manual retry; "Contact support" link if persists 5min | L1+L2 |
| VAL_01 | Field validation error | High | On blur / on submit | Inline red border + error message below field | Re-type and re-submit; clear error on field focus | L1+L2 |
| VAL_02 | Form-level validation (cross-field) | Medium | On submit | Toast or summary banner listing all errors + scroll to first error | Fix all fields and re-submit | L1+L2 |
| AUTH_01 | 401 Unauthorized | Medium | Expired/no token | Redirect to login; preserve intended destination | Login → redirect back to original page | L1 |
| AUTH_02 | 403 Forbidden | Medium | Wrong role | Full-page 403 with explanation: "You don't have permission. Contact admin@example.com." | Navigate to dashboard; request access flow if applicable | L1+L2 |
| NF_01 | 404 Not Found | Medium | Deleted/moved resource | Full-page 404: "Resource not found. It may have been deleted." + link to list | Navigate to parent list | L1+L2 |
| CONF_01 | 409 Conflict (concurrent edit) | Low | If-Match / version check fails | Modal: "This item was modified by [user] at [time]. Reload and try again?" | "Reload" button → re-fetch; "Discard my changes" → navigate away | L1+L2 |
| CONF_02 | 409 Duplicate (idempotency) | Low | POST with duplicate idempotency key | Return the existing resource (200 OK) — NOT an error | Transparent to user; log event | L1 |
| 422 | 422 Unprocessable (server validation) | Medium | Business rule violation | Toast with server error detail: "[detail]" | Correct input and re-submit | L1+L2 |
| 429 | 429 Rate Limited + Retry-After | Low | Too many requests | Toast: "Too many requests. Please wait [N]s." + countdown timer on action button | Wait for Retry-After; disable action during countdown | L1+L2 |
| 5XX | 500/502/503 Server Error | Low | Backend failure | Full-page or section error: "Something went wrong. Our team has been notified." + "Try again" button | Retry button; auto-refresh suggestion after 30s | L1+L2 |
| STALE | Stale data (background update) | Medium | WebSocket / polling detects newer version | Subtle banner: "Data updated. Refresh to see changes." with refresh button | User clicks "Refresh" → re-fetch | L2 |
| PARTIAL | Partial data load | Low | Some rows failed, some loaded | Section loads; failed rows show "⚠ Failed to load" placeholder | Per-row retry button; "Reload all" button | L1+L2 |
| DUP_01 | Duplicate submit prevention | Medium | Rapid double-click | Button disabled + spinner immediately on first click; subsequent clicks ignored | Normal completion; no special recovery needed | L2 |
| DUP_02 | Navigation interruption (unsaved changes) | Medium | Route change with dirty form | Browser beforeunload event + custom confirm: "You have unsaved changes. Discard?" |
"Stay" → remain on page; "Discard" → navigate away | L2 |
| LARGE | Large dataset (>1000 items) | Low | Response > render capacity | Virtual scrolling; "Showing 100 of 1523. Refine your search." | Pagination; search/filter refinement; no "load all" button | L2 |
| EMPTY | Empty result (no data) | High | No items match criteria | Empty state component with illustration + guidance | CTA to create first item or clear filters | L1+L2 |
| MALFORMED | Malformed response body | Very Low | Backend bug / middleware error | Toast: "Unexpected response. Please try again or contact support." + error ID for debugging | Retry; note error ID for support | L1 |
| A11Y | Screen reader state announcements | N/A (always) | State change (loading, error, loaded) | aria-live="polite" region announces: "Loading results", "[N] results loaded", "Error: [message]" |
Built into state transitions — not user-initiated | L2 |
| RESP | Responsive breakpoint collapse | N/A (always) | Viewport < 768px | Columns stack; sidebar collapses to hamburger; touch targets ≥44×44px | Built into responsive layout — not user-initiated | L2 |
State Evaluation Rules
- No blanket "Not Applicable": For each state class, either define the concrete UX or state explicitly WHY this feature cannot hit this state (e.g., "No network for offline CLI tool", "Read-only view — no submit", "Single-user system — no concurrent edits").
- Probability must be grounded: Use High (>10% of sessions), Medium (1-10%), Low (<1%), Very Low (<0.1%). Do not mark everything "Low" to skip design. The probability drives test priority, not whether to design.
- Test ownership: L1 = Screen Model unit test (no render, fast). L2 = component/browser UX test (with render). If both are marked, write L1 first.
- Recovery must be testable: Every recovery action must produce a verifiable state transition (e.g., "Retry → loading → loaded OR error").
Interaction with Prototype and OpenAPI
- The state matrix feeds directly into
speckit.prototype— every state class marked "Applicable" MUST be represented in the prototype's state switcher. - The state matrix feeds into
speckit.openapi— error response classes (401, 403, 404, 409, 422, 429, 5xx) drive the OpenAPIcomponents/responses/section. - The state matrix feeds into
speckit.plan— test ownership (L1/L2) drives task decomposition inspeckit.tasks.
Per-Screen State Exhaustion
For each screen, work through ALL states from the matrix. Present:
## States for: [Screen]
For each state, define: Visual → ARIA → User can...
**Happy path:**
- **idle** → [what user sees before any action]
- **loading** → skeleton? spinner? progress bar? partial data?
- **loaded** → data visible, actions available
**Empty states:**
- **empty (first use)** → guided onboarding or empty state with CTA?
- **empty (filtered)** → "No results match" + clear filters?
- **empty (no permissions)** → 403 with explanation?
**Error states (from matrix):**
- **NET_01 (offline)** → offline banner; disabled actions; auto-retry on reconnect
- **NET_02 (timeout)** → toast + retry countdown
- **NET_03 (retry exhausted)** → persistent banner + manual retry
- **AUTH_01 (401)** → redirect to login, preserve intent
- **AUTH_02 (403)** → full-page explanation
- **NF_01 (404)** → "not found" + link to list
- **CONF_01 (409 concurrent)** → modal with reload option
- **CONF_02 (409 duplicate)** → transparent return existing
- **422 (validation)** → toast with server detail
- **429 (rate limited)** → countdown timer
- **5XX (server error)** → error section + retry
**Edge states (from matrix):**
- **STALE** → refresh banner
- **PARTIAL** → per-row retry
- **DUP_01 (double submit)** → button disabled immediately
- **DUP_02 (navigation interruption)** → confirm dialog
- **LARGE** → virtual scroll + refinement prompt
- **MALFORMED** → error ID + retry
Mark each state as: ✅ Applicable (define UX) or ⛔ Not Applicable (give reason).
For EACH applicable state, ask: "What does the user see? How do they recover?"
Coverage Gate: Before leaving Phase 2, verify:
- Every state class in the matrix is either ✅ Applicable or ⛔ Not Applicable with rationale
- Every ✅ state has Visual + ARIA + User Can + Recovery defined
- No state class was skipped without explicit rationale
- Test ownership is assigned (L1 / L2)
Phase 3: Interaction Design — choices with tradeoffs
For each user action, present alternatives:
## Interaction: [Action Name]
**1. Trigger**
A) Button (primary, visible immediately)
B) Button in toolbar (secondary, contextual)
C) Inline action (icon per row, hover reveal)
D) Keyboard shortcut (power users)
E) Context menu (right-click)
**2. Feedback**
A) Optimistic update (UI changes before API confirms)
B) Loading state on element (button spinner, row skeleton)
C) Full page overlay (block all interactions)
D) Background (toast on completion)
**3. Confirmation**
A) No confirmation (action is safe/undoable)
B) `confirm()` dialog (simple yes/no)
C) Custom modal (shows affected items, requires explicit confirm)
D) Undo toast (action executes, toast offers undo for 5s)
**4. Multi-select**
If user can act on multiple items:
A) Checkbox per row + bulk action bar
B) Shift-click range selection
C) Select-all + deselect individually
Present the tradeoff for each alternative — don't just list options. E.g.: "Optimistic update feels faster but requires rollback logic on failure. Loading spinner is simpler but adds perceived latency."
Phase 4: API UX Design
For each endpoint this feature touches:
## API: [METHOD] /api/[endpoint]
**Request:**
- Shape: { field: Type, ... }
- Validation errors → HTTP 422, inline per-field messages
**Response shapes — ALL variants:**
- Success (200/201): { data: {...}, meta?: {...} }
- Empty (200): { data: [], meta: { total: 0 } }
- Not found (404): { error: { code: "NOT_FOUND", detail: "..." } }
- Permission denied (403): { error: { code: "FORBIDDEN", detail: "..." } }
- Validation (422): { error: { code: "VALIDATION", fields: { field: "message" } } }
- Conflict (409): { error: { code: "CONFLICT", detail: "..." } }
- Server error (500): { error: { code: "INTERNAL", detail: "..." } }
**Loading UX:**
- Debounce before showing loader? (ms)
- Skeleton or spinner?
- Partial data during load or blank?
**Sequence (Mermaid — for complex multi-step flows):**
```mermaid
sequenceDiagram
User->>+Frontend: Click "[Action]"
Frontend->>+Backend: POST /api/...
Backend->>+External: [call]
External-->>-Backend: [response]
Backend-->>-Frontend: { status: "ok", data: {...} }
Frontend->>User: [feedback]
Use ONLY for flows with 3+ participants or async callbacks. Skip for simple CRUD.
WebSocket (if applicable):
- Channel: task.{id}.progress
- Payload shape
- How does UI react to each message type?
### Phase 5: Mobile & Accessibility
Mobile behavior:
- Responsive breakpoint strategy?
- Stacked layout on mobile? Which columns collapse?
- Touch targets: minimum 44×44px per WCAG
Accessibility:
- Screen reader flow for each state
- Focus management: where does focus go after modal opens/closes?
- Keyboard navigation: Tab order, Enter/Space for actions
- Color contrast: semantic tokens guarantee WCAG AA? Check destructive/success on surface.
### Phase 6: Record Decisions & Alternatives
After all questions are answered, create TWO artifacts:
**`contracts/ux/alternatives.md`** — all options considered, BEFORE final choice:
```markdown
#region Std.Opencode.UxAlternatives [C:3] [TYPE ADR] [SEMANTICS ux,alternatives,[DOMAIN]]
@defgroup Ux Design alternatives explored for [FEATURE].
## Screen: [Name]
### Navigation
- ✅ CHOSEN: Separate route /feature — clean URL, direct linkable, full focus
- ❌ Rejected: Modal over dashboard — loses context when modal closes, can't deep-link
- ❌ Rejected: Tab within settings — buried, users won't discover
### Layout
- ✅ CHOSEN: Two-column (list + detail) — best scanability for 20+ items
- ❌ Rejected: Single table — no preview without navigation, repetitive clicks
- ❌ Rejected: Cards grid — doesn't scale past 12 items, inconsistent card heights
### Data Loading
- ✅ CHOSEN: Paginated table (20 per page) + search — predictable, fast
- ❌ Rejected: Infinite scroll — breaks "select all", hard to find specific item
- ❌ Rejected: Load all at once — 200+ items freeze UI
### Action Feedback (for destructive actions)
- ✅ CHOSEN: Undo toast (5s) — feels instant, recoverable
- ❌ Rejected: Confirm dialog — extra click on every action, annoying at scale
- ❌ Rejected: No confirmation — dangerous for delete/migrate
#endregion Std.Opencode.UxAlternatives
contracts/ux/decisions.md — only the final choices:
#region Std.Opencode.UxDecisions [C:3] [TYPE ADR] [SEMANTICS ux,decisions,[DOMAIN]]
@defgroup Ux Final UX design decisions for [FEATURE].
## Screen: [Name]
- Navigation: Separate route /feature
- Layout: Two-column (list + detail)
- Data: Paginated (20/page) + search
- Feedback: Undo toast (5s) for destructive actions
#endregion Std.Opencode.UxDecisions
Rule: alternatives.md shows the DESIGN SPACE — agent can see WHY each path was rejected. decisions.md is the compact reference for /speckit.plan.
Phase 7: Generate Artifacts
ONLY after all design decisions are made. The edge/failure state matrix from Phase 2 is complete — every state class has been evaluated.
ALL artifacts go into FEATURE_DIR/contracts/ux/ — NEVER into frontend/src/lib/. The UX phase produces design contracts, not implementation. Actual source files are written by /speckit.implement.
Artifacts feed downstream:
api-ux.md→/speckit.openapireads API shapes foropenapi.yaml<screen>-ux.mdstate tables →/speckit.prototypereads states for prototype state switcherscreen-models.md→/speckit.planreads models for contract generation- Edge/failure matrix coverage →
/speckit.tasksgenerates test tasks per test ownership (L1/L2)
contracts/ux/screen-models.md— Model inventory from Phase 1-2 decisionscontracts/ux/api-ux.md— API shapes from Phase 4contracts/ux/<screen>-ux.md× N — per-screen UX contracts from Phase 2-3contracts/ux/design-tokens.md— token application from Phase 3contracts/ux/model-changes.md— precise edit instructions for existing models (atoms, derived, actions to add; exact file paths and line insertions). For NEW models, include the full reference model code in this file —/speckit.implementwill translate it into the real source file.contracts/ux/model-<domain>.svelte.ts(optional) — ONLY for NEW Screen Models that don't exist yet. This is a reference copy in the spec folder —/speckit.implementwill create the actual file infrontend/src/lib/models/.
For artifacts 3-6, use the templates defined below. Do NOT generate contracts before design decisions are recorded.
Phase 8: Confirmation Gate
Before writing any contract files, present:
| # | File | Location | Type | Summary |
|---|---|---|---|---|
| 1 | contracts/ux/screen-models.md |
FEATURE_DIR/contracts/ux/ |
Inventory | Models touched, new atoms, component changes |
| 2 | contracts/ux/api-ux.md |
FEATURE_DIR/contracts/ux/ |
API shapes | Endpoints, SSE events, sequences |
| 3 | contracts/ux/<screen>-ux.md |
FEATURE_DIR/contracts/ux/ |
Per-screen FSM | States, feedback, recovery, UX tests |
| 4 | contracts/ux/design-tokens.md |
FEATURE_DIR/contracts/ux/ |
Token map | Semantic token → state mapping |
| 5 | contracts/ux/model-changes.md |
FEATURE_DIR/contracts/ux/ |
Edit diff | Exact additions to existing source files |
| 6 | contracts/ux/model-<domain>.svelte.ts |
FEATURE_DIR/contracts/ux/ |
Ref model (NEW only) | Full model code — /speckit.implement copies to frontend/src/lib/models/ |
Rule: Items 1-5 are mandatory. Item 6 only when creating a NEW Screen Model that doesn't exist in frontend/src/lib/models/.
Ask: "Write these UX contracts to FEATURE_DIR/contracts/ux/? (yes/no)"
Artifact Templates
<screen>-ux.md
#region <Screen>Ux [C:3] [TYPE ADR] [SEMANTICS ux,<domain>,<screen>]
@defgroup Ux UX contract for <Screen>.
## FSM (from Phase 2 decisions)
idle → [trigger] → loading → [success] → loaded
→ [empty] → empty
→ [failure] → error → [retry] → loading
## State Mappings (from Phase 2-3 decisions)
| @UX_STATE | Visual | ARIA | User Can |
|-----------|--------|------|----------|
## Feedback (from Phase 3 decisions)
| Trigger | Feedback | Rationale |
## Recovery (from Phase 2 edge states)
| From | Action | To |
## Reactivity (from Phase 1-2 decisions)
- Model atoms → Component props → DOM
- Store subscriptions → $effect (browser-side only)
## UX Tests (minimum: happy, empty, error, edge)
| @UX_TEST | Given | When | Then |
model-<domain>.svelte.ts — reference model code (spec folder only)
ONLY for NEW Screen Models. This file lives in FEATURE_DIR/contracts/ux/. /speckit.implement will create the actual file at frontend/src/lib/models/<Domain>Model.svelte.ts.
// REFERENCE MODEL — will be created at frontend/src/lib/models/<Domain>Model.svelte.ts by /speckit.implement
// #region <Domain>.Model [C:4] [TYPE Model] [SEMANTICS <domain>,<feature>,screen-model]
// @defgroup <Domain> <One-line from decisions>.
// @INVARIANT <from Phase 2-3 decisions>
// @STATE <FSM states from Phase 2>
// @ACTION <from Phase 3 interaction decisions>
// @RELATION DEPENDS_ON -> [api]
// @RATIONALE Model-first: extracted to enable L1 testing without DOM.
// @REJECTED Inline state rejected — scatters logic across event handlers.
import { requestApi } from "$lib/api";
import { log } from "$lib/cot-logger";
// ── Types (from Phase 2-4 decisions) ──
type ScreenState = "idle" | "loading" | "loaded" | "empty" | "error";
interface Entity { id: string; /* from spec + API shape */ }
interface ListResponse { data: Entity[]; meta: { total: number }; }
export class <Domain>Model {
// ── Atoms ──
items: Entity[] = $state([]);
screenState: ScreenState = $state("idle");
error: string | null = $state(null);
// ── Derived ──
isEmpty = $derived(this.items.length === 0 && this.screenState === "loaded");
// ── Actions ──
async load(): Promise<void> {
this.screenState = "loading";
this.error = null;
log("<Domain>.Model", "REASON", "Loading items");
try {
const res: ListResponse = await requestApi("/api/...");
this.items = res.data;
this.screenState = this.items.length === 0 ? "empty" : "loaded";
log("<Domain>.Model", "REFLECT", "Items loaded", { count: this.items.length });
} catch (e: unknown) {
this.error = e instanceof Error ? e.message : "Load failed";
this.screenState = "error";
log("<Domain>.Model", "EXPLORE", "Load failed", {}, this.error);
}
}
async retry(): Promise<void> { await this.load(); }
// TODO: implement remaining actions from Phase 3 decisions
// Each action throws until implemented — L1-testable immediately
}
// #endregion <Domain>.Model
Stop & Report
After Phase 8, report:
- Screens designed: N
- Design decisions recorded: N
- UX contracts generated: N files
- Model files generated: N (if confirmed)
- Total @UX_STATE mappings: N
- Total @UX_TEST scenarios: N
- Every screen state from Phase 2 covered: yes/no
- Every API response variant from Phase 4 covered: yes/no
- Edge/failure matrix: N of 24 state classes applicable, N not applicable (with rationale), 0 skipped without rationale
- State test ownership: N L1, N L2
- Readiness for
/speckit.prototype(if UI): yes/no - Readiness for
/speckit.openapi(if API surface): yes/no - Readiness for
/speckit.plan