Skip to content

Story → composition playbook

How to lift a Dazzle example (or product app) from entity CRUD tables to job workspaces that match declared stories and personas.

Companion stem: examples/support_tickets/stems/story-driven-jobs.md. HM gallery blueprints: ops-queue, triage-drawer, manager-sla-strip.

Rule

Story → persona → default_workspace → Hyperpart composition. If the primary region is a generic list of the whole entity, the story probably wants a queue, metrics strip, kanban, or scoped list.

Steps

1. Inventory stories by persona

Group stories.dsl (or product jobs) by persona:. Flag:

  • CRUD stories (create/edit entity) → surfaces, not workspace primaries.
  • Job stories (“views open tickets”, “reviews team performance”) → workspaces.

2. Map each job story to a display mode

Signal in the story Prefer
Open / unacked / unassigned / “work the pile” display: queue
Counts, SLA, team health display: metrics (+ tones:)
Readiness / checklist / policy strip display: status_list
Move through states on a board display: kanban
Lifecycle pressure bar_chart grouped by state / pipeline_steps
“What needs me” multi-source one queue per source, primary first
My scoped history filtered list or queue

Dogfood: examples/support_tickets (agent queue, manager_ops).

3. Freeze the composition before editing DSL

Write a short table (region → display → story ids). Keep secondaries (kanban, timeline) below the job primary. Do not add regions “because they exist in the catalogue.”

The first desktop viewport is a limited decision surface. Ask which item the persona needs next, what state or comparison makes that item understandable, and what action follows. Prefer enough of each to make the decision over a complete first region that pushes the related state away. A count is useful when it reveals hidden work; it can be redundant when every row is visible. Check the same seeded screenshot and task journey after changing a region's order, limit, or density. See stems/example-apps-as-probes.md.

4. Wire personas honestly

  • default_workspace: must be the composition that matches the persona’s first job, not the densest demo surface.
  • If two personas share a queue, set access: persona(...) on that workspace.
  • Align stories.dsl given: lines with the real workspace name (see ST-027).

5. Implement in DSL

workspace ticket_queue "Ticket Queue":
  purpose: "Agent workspace for open support work"
  stage: "scanner_table"
  access: persona(agent, manager)

  queue_metrics:
    source: Ticket
    display: metrics
    aggregate:
      total_open: count(Ticket where status = open)
      critical: count(Ticket where priority = critical and status != closed)
    tones:
      critical: destructive

  open_queue:
    source: Ticket
    filter: status != closed
    sort: priority desc, created_at asc
    display: queue
    action: ticket_edit
    empty: "No open tickets"

Validate from the app root (CLI uses the current directory):

cd examples/<app> && uv run dazzle validate

6. Mirror in HM when the motif is reusable

If the composition is a page-scale motif (not app-specific data), add a Blueprint in packages/hatchi-maxchi/site/blueprints.py composed only of published Hyperparts — then rebuild the gallery site. Prefer Blueprint over a new Hyperpart (invention ladder).

7. Mutation feedback

Do not invent client toast state. Use framework with_toast / mutation response chrome (titled toast, optional View action). Opt-in sound stays off unless the app sets cue meta (chrome-cue-opt-in stem).

Anti-patterns

Smell Fix
Persona lands on empty “my assigned” when seed assigns to others Metrics/team queue first (TR-52 class)
Story says “queue” but region is list Switch primary to display: queue
Manager story names workspace A, default_workspace is B Rename story or default — one truth
Every entity gets a list surface as the home Home is a workspace, lists are CRUD routes
New CSS for “ops page” Compose Hyperparts / Blueprint

Checklist

  • [ ] Job stories mapped to displays
  • [ ] Composition freeze written
  • [ ] default_workspace + story given: agree
  • [ ] access: covers shared surfaces
  • [ ] dazzle validate clean
  • [ ] Optional: HM Blueprint + gallery rebuild
  • [ ] Optional: trial scenario for the job persona

Worked examples

support_tickets (keystone):

  • Agent → ticket_queue = metrics + queue + kanban
  • Manager → manager_ops = metrics + status_list + critical/unassigned queues + funnel
  • Customer → my_tickets = metrics + open queue + history list

simple_task:

  • Admin → admin_dashboard = metrics + urgent/overdue queues
  • Manager → team_overview = metrics + review/unassigned/WIP queues
  • Member → my_work = personal metrics + WIP/todo queues

hr_records:

  • HR/manager → staff_directory = headcount metrics + directory lists
  • Finance → compensation_review = compensation metrics + salary list

invoice_ops:

  • Approver/finance → finance_ops = metrics + awaiting_approval / ready_to_pay / disputed queues + funnel + payment chart