Files
ss-tools/.opencode/command/speckit.ux.md
busya d874a4dca6 feat(speckit): workflow architecture upgrades + rework 038 spec per new flow
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.
2026-07-31 11:25:43 +03:00

458 lines
22 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.

---
description: 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.
handoffs:
- label: Generate HTML Prototype
agent: speckit.prototype
prompt: Build an interactive HTML prototype from the UX contracts and state matrix
send: true
- label: Generate OpenAPI Spec
agent: speckit.openapi
prompt: Derive OpenAPI 3.1 from the UX contracts and API shapes
send: true
- label: Build Technical Plan
agent: speckit.plan
prompt: Create a Python/Svelte implementation plan using the UX contracts
send: true
---
## User Input
```text
$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
1. **Setup**: Run `.specify/scripts/bash/check-prerequisites.sh --json``FEATURE_DIR`.
2. **Load**:
- `FEATURE_DIR/spec.md` — user stories, acceptance criteria
- `FEATURE_DIR/ux_reference.md` — high-level narrative (if exists)
- `.opencode/skills/semantics-svelte/SKILL.md` — §VI canonical template, §VII design tokens
- `frontend/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
1. **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").
2. **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.
3. **Test ownership**: L1 = Screen Model unit test (no render, fast). L2 = component/browser UX test (with render). If both are marked, write L1 first.
4. **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 OpenAPI `components/responses/` section.
- The state matrix feeds into `speckit.plan` test ownership (L1/L2) drives task decomposition in `speckit.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:
```markdown
#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.openapi` reads API shapes for `openapi.yaml`
- `<screen>-ux.md` state tables `/speckit.prototype` reads states for prototype state switcher
- `screen-models.md` `/speckit.plan` reads models for contract generation
- Edge/failure matrix coverage `/speckit.tasks` generates test tasks per test ownership (L1/L2)
1. **`contracts/ux/screen-models.md`** Model inventory from Phase 1-2 decisions
2. **`contracts/ux/api-ux.md`** API shapes from Phase 4
3. **`contracts/ux/<screen>-ux.md`** × N per-screen UX contracts from Phase 2-3
4. **`contracts/ux/design-tokens.md`** token application from Phase 3
5. **`contracts/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.implement` will translate it into the real source file.
6. **`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.implement` will create the actual file in `frontend/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`
```markdown
#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`.
```typescript
// 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`