Files
ss-tools/.opencode/command/speckit.ux.md
2026-06-09 09:43:34 +03:00

12 KiB
Raw Blame History

description, handoffs
description handoffs
Interactive UX design session — asks questions, presents alternatives, exhaustively designs every screen state, then generates Screen Model code and UX contracts.
label agent prompt send
Build Technical Plan speckit.plan Create a Python/Svelte implementation plan using the UX contracts true
label agent prompt
Create Tasks speckit.tasks Break the plan into executable tasks referencing 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

  1. Setup: Run .specify/scripts/bash/check-prerequisites.sh --jsonFEATURE_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 — EVERY screen state

For each screen, work through ALL states exhaustively. This is where most UX bugs hide — the states between "loading" and "loaded".

## 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:**
- **error (network)** → toast + retry? full error page? degraded mode?
- **error (validation)** → inline field errors? modal? which fields?
- **error (timeout)** → retry with countdown? cancel?
- **error (server 500)** → generic message? retry? contact support?

**Edge states:**
- **stale data** → show cached with "refresh" indicator?
- **partial data** → some rows loaded, some failed?
- **background update** → data changed by another user? WebSocket notification?
- **rate limited** → "Too many requests" + countdown?

For EACH state, ask: "Is this state possible? If yes, what does the user see?"

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 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 UxAlternatives

contracts/ux/decisions.md — only the final choices:

#region 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 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:

  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. frontend/src/lib/models/<Domain>Model.svelte.ts — generated model code

For artifacts 3-5, use the templates defined below. Do NOT generate contracts before design decisions are recorded.

Phase 8: Confirmation Gate

Before writing model files to frontend/src/lib/models/, present:

File Path Atoms Actions Dependencies

Ask: "Write these model files? (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 |

<Domain>Model.svelte.ts — generated code

// frontend/src/lib/models/<Domain>Model.svelte.ts
// #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
  • Readiness for /speckit.plan