ADR 0046 — Use-case-first pages + rendered action dialogs
- Status: accepted
- Date: 2026-10-05
- Slice: P46 (builder-roadmap: UI-first — the app reads as a set of use cases)
Context
P45 demoted the CQRS model pages to a collapsed "Developer" console, so the
shell no longer starts from CQRS. But the list pages themselves still
read as CQRS: every projection's list page rendered a card titled rows with
the description "live read model — replayed from the event log". And the
"trigger / create / update" surface (the per-page actions introduced in P36)
was never actually rendered in any committed app — no golden example carried
a table-view action (the flagship full-stack actions were on a kanban
projection, which does not render action buttons). The action/dialog codegen
was therefore never compiled, and a set of latent bugs shipped.
Goal: make a generated app read as a set of use cases — pages that list and present things and let the caller create / update / trigger them — framed in business terms, with the CQRS vocabulary confined to the developer console.
Decision
1. The list page is a use-case page, not a "read model" (compiler)
The list-page card no longer injects CQRS language:
- The
CardTitleis the projection'sui.pagebusiness label (falling back to the projection name) — e.g.Documents,Tickets— threaded from the page hint intorows_card/rows_view_card. - The
CardDescriptionis the projection'sabout(a business sentence the author writes), shown only when present. The hardcoded "live read model — replayed from the event log" is gone.ProjPlannow carriesaboutfor this.
So the page title + description are the author's business framing; the compiler adds none of its own.
2. Neutral command-form submit (compiler)
The default command-form submit label was submit <Command> (the raw command
name in a button). It is now a neutral Submit. (The command page title
already defaulted to the command's about — business — so the create/update
form reads as an action, not a CQRS command.)
3. The reference app is a set of named use-case pages (mosaic-pages)
The pages app (eugeis/mosaic-pages, a docs portal) now declares its two
projections as use-case pages:
DocList→ Documents (ui.pagelabel, businessabout) with the create/update surface: a toolbar New document action (a dialog overDoc.Publish: id / app / title / body) and a row Retire action (two-step confirm overDoc.Retire).LogList→ Activity (the tenant's audit trail, read-only).
These are the app's front pages (above the Developer console, per P45).
4. The rendered action / dialog codegen (compiler — bug fixes)
Because no golden exercised a rendered table-view action, the action dialog
codegen was broken in four independent ways. All are fixed and now pinned in
the full-stack golden (a new Open ticket toolbar action on the split
Tickets page):
- The dialog cancel button emitted unquoted text (
>cancel</button>with a stray"), which broke theview!token stream and left the component with an unclosed delimiter. It is now>"cancel"</button>. - The dialog component was named snake_case (
doc_list_0_dlg); a lowercase tag inview!resolves as an HTML element. It is now PascalCase (DocList0Dlg), definition and reference in lockstep. - The dialog field signals used
create_signal("")→&str, which does not satisfybind:value'sIntoSplitSignal. They are nowcreate_rw_signal(String)/create_rw_signal(bool)(read-writeString/boolsignals). - The dialog's
rowprop is a plain value; it is now namedrow(a bare_rowis not getter-wrapped by#[component]) and passed as a value (the dialog is created only while itsShowis open, so it is current then). Alet _ = &row;suppresses the unused warning for toolbar dialogs. - The per-action execute block is now a
;-terminated statement (not a bare tail block), and required-field guards are;-terminated —view!'s syn parser rejects a bare block / bareifwhen another statement follows it in anon:clickclosure.
Consequences
- Generated apps read as use cases: business-titled pages with a business description and an in-context create/update surface; CQRS confined to the Developer console.
- The
full-stackgolden now pins a rendered table-view action, so a web-rendering regression in the action/dialog codegen fails conformance (and the example web is compiled in CI / local verification). - The
mosaic-pagesreference app ships named use-case pages (Documents, Activity) with a working publish/retire surface. ProjPlan.aboutis new public surface (plan); the list-page card title now derives from theui.pagelabel, so a projection with a page hint shows that label on its list page (not just in the nav).