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
listof 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 | funnel_chart / progress |
| “What needs me” multi-source | task_inbox (ops_dashboard dogfood) |
| My scoped history | filtered list or queue |
Dogfood: examples/ops_dashboard (ack_queue, ops_inbox, health_summary).
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.”
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.dslgiven: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):
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+ storygiven:agree - [ ]
access:covers shared surfaces - [ ]
dazzle validateclean - [ ] 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
project_tracker:
- Admin/manager →
dashboard= portfolio metrics + open task queue + grid + kanban - Member →
project_board= board metrics + kanban + unassigned queue
contact_manager:
- User →
home= directory metrics + favourites queue + sample list - User →
contacts= search + favourites queue + dual-pane list/detail
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
acme_billing:
- Admin/owner/auditor →
billing= portfolio metrics + invoice queue + entity lists
design_studio:
- Designer/admin →
studio_dashboard= portfolio metrics + grids - Reviewer →
asset_gallery= gallery metrics + review queue (status = review)
fieldtest_hub:
- Engineer/manager →
engineering_dashboard= fleet metrics + non-active device attention queue + triage/critical queues + open task queue (kanban/map/tree retained as secondary demos) - Tester →
tester_dashboard= personal metrics + assigned device/issue/task queues
llm_ticket_classifier:
- Supervisor →
support_dashboard= classification metrics + open queue - Agent →
ticket_management= non-closed ticket queue + full list
domain_join_co:
- Admin/member →
home= announcement metrics + join readiness status_list + feed
ops_dashboard (reference dogfood — already job-shaped):
- Ops engineer → command centre with metrics,
ack_queue, charts, status_list, task_inbox