tasks ready 1
This commit is contained in:
@@ -36,6 +36,9 @@ You **MUST** consider the user input before proceeding (if not empty).
|
||||
- `.opencode/skills/semantics-svelte/SKILL.md`
|
||||
- `.opencode/skills/semantics-testing/SKILL.md`
|
||||
- `.specify/templates/plan-template.md`
|
||||
- `FEATURE_DIR/contracts/ux/screen-models.md` (if `/speckit.ux` was run)
|
||||
- `FEATURE_DIR/contracts/ux/api-ux.md` (if `/speckit.ux` was run)
|
||||
- `FEATURE_DIR/contracts/ux/*-ux.md` (per-screen UX contracts)
|
||||
- relevant `docs/adr/*.md`
|
||||
|
||||
3. **Execute the planning workflow** using the template structure:
|
||||
@@ -44,6 +47,7 @@ You **MUST** consider the user input before proceeding (if not empty).
|
||||
- ERROR if a blocking constitutional or semantic conflict is discovered and cannot be justified.
|
||||
- Phase 0: generate `research.md` in `FEATURE_DIR`, resolving all material unknowns.
|
||||
- Phase 1: generate `data-model.md`, `contracts/modules.md`, optional machine-readable contract artifacts, and `quickstart.md` in `FEATURE_DIR`.
|
||||
- Phase 1: if UX contracts exist, generate `traceability.md` — a requirements traceability matrix mapping Story → Model → API → Task → Test.
|
||||
- Materialize blocking ADR references and planning decisions inside the plan and downstream contracts.
|
||||
- Run `.specify/scripts/bash/update-agent-context.sh kilocode` after planning artifacts are written.
|
||||
|
||||
@@ -63,6 +67,12 @@ Research must resolve only implementation-shaping unknowns that matter for this
|
||||
- belief runtime instrumentation for C4/C5 flows
|
||||
- semantic validation boundaries and static verification workflow
|
||||
|
||||
**If `/speckit.ux` was run before plan:**
|
||||
- `screen-models.md` defines Model inventory → use directly, don't re-discover
|
||||
- `api-ux.md` defines API shapes → use as @DATA_CONTRACT source for backend Pydantic schemas
|
||||
- `<screen>-ux.md` defines UX contracts → use as @UX_STATE/@UX_FEEDBACK source for component contracts
|
||||
- Generated `.svelte.ts` model files in `frontend/src/lib/models/` → DO NOT regenerate; reference them via `@RELATION BINDS_TO` from component contracts
|
||||
|
||||
Write `research.md` with concise sections:
|
||||
- Decision
|
||||
- Rationale
|
||||
@@ -230,6 +240,93 @@ Generate its full `#region` header in `contracts/modules.md` under its parent mo
|
||||
|
||||
If a planned contract depends on unknown schema, relation target, or ADR identity, emit `[NEED_CONTEXT: target]` instead of fabricating placeholders.
|
||||
|
||||
### Fixture Generation (MANDATORY for C3+ contracts with @TEST_EDGE)
|
||||
|
||||
For every C3+ contract that declares `@TEST_EDGE`, `@POST`, or `@REJECTED` guardrails, generate **canonical test fixtures** in `FEATURE_DIR/fixtures/`. Canonical fixtures live beside the spec — they are the design-time source of truth. Executable fixtures are materialized into `tests/` later by `/speckit.tasks`.
|
||||
|
||||
**Output structure:**
|
||||
|
||||
```text
|
||||
specs/<feature>/fixtures/
|
||||
├── manifest.md # Fixture index with GRACE contracts
|
||||
├── api/
|
||||
│ ├── <contract>_valid.json
|
||||
│ ├── <contract>_missing_field.json
|
||||
│ ├── <contract>_invalid_type.json
|
||||
│ ├── <contract>_external_fail.json
|
||||
│ └── <contract>_rejected_path.json
|
||||
└── model/
|
||||
├── <model>_valid.json
|
||||
├── <model>_edge_case.json
|
||||
└── <model>_invariant.json
|
||||
```
|
||||
|
||||
**`manifest.md` — fixture index with GRACE contracts:**
|
||||
|
||||
```markdown
|
||||
#region FixtureManifest [C:3] [TYPE ADR] [SEMANTICS test,fixture,[DOMAIN]]
|
||||
@defgroup Fixtures Canonical test fixtures for [FEATURE].
|
||||
|
||||
## @{ Fixture FX_Auth.Login.Valid [C:2] [TYPE Block] [SEMANTICS test,auth,fixture]
|
||||
@BRIEF Valid login request/response pair.
|
||||
@RELATION VERIFIES -> [Api.Auth.Login]
|
||||
@TEST_FIXTURE: valid_login -> fixtures/api/auth_login_valid.json
|
||||
@TEST_INVARIANT: TokenIssued -> VERIFIED_BY: [Test.Api.Auth]
|
||||
## @} Fixture FX_Auth.Login.Valid
|
||||
|
||||
## @{ Fixture FX_Auth.Login.MissingPassword [C:2] [TYPE Block] [SEMANTICS test,auth,fixture]
|
||||
@BRIEF Missing password field — @TEST_EDGE: missing_field.
|
||||
@RELATION VERIFIES -> [Api.Auth.Login]
|
||||
@TEST_EDGE: missing_field -> 422 VALIDATION_ERROR
|
||||
@TEST_FIXTURE: missing_password -> fixtures/api/auth_login_missing_field.json
|
||||
## @} Fixture FX_Auth.Login.MissingPassword
|
||||
|
||||
## @{ Fixture FX_Migration.EnvReset [C:2] [TYPE Block] [SEMANTICS test,migration,fixture]
|
||||
@BRIEF Model invariant: changing source env resets selection.
|
||||
@RELATION VERIFIES -> [Migration.Model]
|
||||
@TEST_INVARIANT: env_reset_selection -> VERIFIED_BY: [Test.Migration.Model]
|
||||
@TEST_FIXTURE: env_reset -> fixtures/model/migration_env_reset.json
|
||||
## @} Fixture FX_Migration.EnvReset
|
||||
```
|
||||
|
||||
**JSON fixture format:**
|
||||
|
||||
```json
|
||||
{
|
||||
"fixture_id": "FX_Auth.Login.MissingPassword",
|
||||
"verifies": "Api.Auth.Login",
|
||||
"edge": "missing_field",
|
||||
"input": {
|
||||
"username": "admin"
|
||||
},
|
||||
"expected": {
|
||||
"status": 422,
|
||||
"error_code": "VALIDATION_ERROR",
|
||||
"error_detail": "Field 'password' is required"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Generation rules:**
|
||||
- **One JSON file per fixture** — named `<contract_snake>_<edge>.json`
|
||||
- **Minimum 5 per C3+ contract**: valid, missing_field, invalid_type, external_fail, rejected_path
|
||||
- **Expected values ALWAYS hardcoded** — never derived from implementation (anti-tautology)
|
||||
- **Input values are concrete** — real strings, numbers, objects, not pseudocode
|
||||
- **Fixture ID format**: `FX_<Domain>.<Name>` — hierarchical, matches contract hierarchy
|
||||
- **@TEST_FIXTURE in manifest** points to the JSON file path
|
||||
- **@RELATION VERIFIES** links fixture to production contract
|
||||
- For `@REJECTED` paths: expected MUST include error/failure, proving the path is unreachable
|
||||
- For model invariants: input = state before action, expected = state after action
|
||||
- Do NOT generate executable test files here — only canonical JSON fixtures
|
||||
|
||||
### Fixture Traceability
|
||||
|
||||
Extend `traceability.md` with a Fixture column:
|
||||
|
||||
| Story | Model | Fixture | Task | Test |
|
||||
|-------|-------|---------|------|------|
|
||||
| US1 | Api.Auth.Login | FX_Auth.Login.Valid | T017 | Test.Api.Auth
|
||||
|
||||
### Quickstart Output
|
||||
|
||||
Generate `quickstart.md` using real repository verification paths:
|
||||
@@ -239,6 +336,41 @@ Generate `quickstart.md` using real repository verification paths:
|
||||
- Frontend lint: `cd frontend && npm run lint`
|
||||
- Docker: `docker compose up --build`
|
||||
|
||||
### Traceability Matrix Output
|
||||
|
||||
If UX contracts exist (`contracts/ux/` was generated by `/speckit.ux`), generate `traceability.md` — a requirements traceability matrix (RTM) mapping every user story through its implementation chain:
|
||||
|
||||
```markdown
|
||||
#region Traceability [C:3] [TYPE ADR] [SEMANTICS traceability,rtm,[DOMAIN]]
|
||||
@defgroup Trace Matrix Requirements → Model → API → Task → Test for [FEATURE].
|
||||
|
||||
## Traceability Matrix
|
||||
|
||||
| Story | Screen | Model | Fixture | API Endpoint | Backend Task | Frontend Task | Test |
|
||||
|-------|--------|-------|---------|-------------|-------------|--------------|------|
|
||||
| US1: [Title] | /route | Domain.Model | FX_Domain.Valid | GET /api/... | T017 | T015 | Test.Domain |
|
||||
| US1: [Title] | /route | Domain.Model | FX_Domain.MissingField | POST /api/... | T018 | T019 | Test.Domain.Edge |
|
||||
|
||||
## Impact Analysis Quick Reference
|
||||
|
||||
| If you change... | These fixtures verify it | These tests verify it | These screens depend |
|
||||
|-----------------|------------------------|----------------------|---------------------|
|
||||
| `GET /api/dashboards` | FX_Dashboards.Hub.* | Test.Dashboards.Hub | /dashboards, /migration |
|
||||
| `Dashboards.Hub` model | FX_Dashboards.EnvReset | Test.Dashboards.Hub | /dashboards |
|
||||
|
||||
#endregion Traceability
|
||||
```
|
||||
|
||||
**Generation rules:**
|
||||
- One row per unique (Story, API Endpoint, Screen) tuple
|
||||
- Model column: `[TYPE Model]` contract ID from `screen-models.md`
|
||||
- API column: endpoint from `api-ux.md` or `contracts/modules.md`
|
||||
- Task columns: task IDs from `tasks.md` (to be filled after `/speckit.tasks` — leave as `T???` if tasks not yet generated)
|
||||
- Test column: test contract ID pattern `Test.<Domain>.<Name>`
|
||||
- Impact table: derived from `@RELATION` edges in contracts — invert the dependency graph
|
||||
- Grep-friendly: `grep "Dashboards.Hub" traceability.md` → all rows for that model
|
||||
- Agent zombie mode: without MCP tools, `grep "<contract>" traceability.md` replaces `impact_analysis`
|
||||
|
||||
## Key Rules
|
||||
|
||||
- Use absolute paths in workflow execution.
|
||||
|
||||
@@ -173,3 +173,29 @@ Before finalizing `tasks.md`, verify that:
|
||||
- no task text schedules a rejected path
|
||||
- story tasks remain executable within the actual Python/Svelte project structure
|
||||
- at least one explicit verification task protects against rejected-path regression
|
||||
|
||||
### Fixture Materialization Tasks
|
||||
|
||||
If `/speckit.plan` generated canonical fixtures in `specs/<feature>/fixtures/`, create materialization tasks that copy them into the repo-native test directories before writing test code:
|
||||
|
||||
**Backend fixtures:**
|
||||
```text
|
||||
- [ ] TXXX [P] [US1] Materialize fixtures from specs/<feature>/fixtures/api/ into backend/tests/fixtures/<domain>/
|
||||
Source: fixtures/api/auth_login_*.json
|
||||
Target: backend/tests/fixtures/auth/
|
||||
Each fixture → one JSON file. Do NOT modify fixture content — copy as-is.
|
||||
```
|
||||
|
||||
**Frontend fixtures:**
|
||||
```text
|
||||
- [ ] TXXX [P] [US1] Materialize fixtures from specs/<feature>/fixtures/model/ into frontend/src/lib/models/__fixtures__/<model>/
|
||||
Source: fixtures/model/migration_*.json
|
||||
Target: frontend/src/lib/models/__fixtures__/migration/
|
||||
```
|
||||
|
||||
**Rules:**
|
||||
- Materialization tasks are [P] (parallel, different directories)
|
||||
- Materialize BEFORE test-writing tasks — tests import fixtures
|
||||
- Fixtures are copied as-is from canonical source — no adaptation at this stage
|
||||
- If canonical fixture shape doesn't match test framework expectations, create a separate adapter task
|
||||
- Every fixture in `manifest.md` gets exactly one materialization task
|
||||
|
||||
366
.opencode/command/speckit.ux.md
Normal file
366
.opencode/command/speckit.ux.md
Normal file
@@ -0,0 +1,366 @@
|
||||
---
|
||||
description: Interactive UX design session — asks questions, presents alternatives, exhaustively designs every screen state, then generates Screen Model code and UX contracts.
|
||||
handoffs:
|
||||
- label: Build Technical Plan
|
||||
agent: speckit.plan
|
||||
prompt: Create a Python/Svelte implementation plan using the UX contracts
|
||||
send: true
|
||||
- label: Create Tasks
|
||||
agent: speckit.tasks
|
||||
prompt: Break the plan into executable tasks referencing UX contracts
|
||||
---
|
||||
|
||||
## 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 — 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:
|
||||
|
||||
```markdown
|
||||
#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`
|
||||
|
||||
```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 |
|
||||
```
|
||||
|
||||
### `<Domain>Model.svelte.ts` — generated code
|
||||
|
||||
```typescript
|
||||
// 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`
|
||||
Reference in New Issue
Block a user