Resolved: light

Page recipes

The macro skeletons a route lives in — the vehicle for the app-wide page sweep. Where the workflow family is the chrome (TitleBar, SaveBar) and surfaces are the regions, recipes are whole-PAGE templates: each owns the layout + region hierarchy and takes ReactNode SLOTS (header/breadcrumbs/toolbar/content/aside/footer) so the app supplies the data. They compose the @phauna/ds page recipes (TablePage, RecordPage) with the STD workflow chrome over the Surface primitive. Index/Detail/Settings are server-safe; only ResourceEditPage is a client component (it owns the useUnsavedChanges route-leave guard). Flip the theme in the top bar to confirm every region re-skins from the tokens.

ResourceIndexPage
List/index template. Composes @phauna/ds TablePage: a full-width TitleBar (title + breadcrumbs slot + a 'New' primaryAction + secondaryActions) above a toolbar/filter band, then the table/grid slot (a ds/grid DataGrid or a passed table), an optional facet rail, and a pagination footer. Server-safe.
ResourceDetailPage
Record/detail template over @phauna/ds RecordPage: TitleBar header → a main content slot + an optional metadata aside (collapses beneath on narrow) → a provenance footer. maxWidth opts into a centered reading column. Server-safe.
ResourceEditPage
The canonical create/edit shell (client). RecordPage header + form slot (+ optional aside) closed by a dirty-aware workflow SaveBar; wires useUnsavedChanges(dirty) for the hard- + soft-nav leave guard. Parent owns form state and passes dirty/saving + onSave/onDiscard.
SettingsPage
Sectioned settings template: TitleBar over a responsive [nav rail | section stack] grid (the rail sticks on lg+, collapses on narrow), closed by a prop-driven SaveBar. Server-safe — drive dirty from your form; reach for ResourceEditPage if you want the route guard too.
slots + RecipeHeader
All four share RecipeHeaderProps (title / as / breadcrumbs / status / primaryAction / secondaryActions / actions, or a full `header` override) and the SaveBar-shaped props (dirty / saving / onSave / onDiscard / saveMessage / saveBar). Every region is a ReactNode — the template owns layout, the app owns content.

ResourceIndexPage

The 'here are all the X' route. A full-width TitleBar (breadcrumbs + a count badge + a 'New volunteer' primary and Export/Import secondaries that fold into a ⋮ on mobile) sits above a token-spaced toolbar (search + filters), the table, and a pagination footer. Pass a facets node to split a facet rail beside the table; pass your ds/grid DataGrid (or any table) into the table slot.

Volunteers

128
NameTeamZIPStatus
Ada OkoroMarch Organizers11215Active
Ben CastilloPhonebank10027Active
Carla DeviMarch Organizers11238Paused
Dan WhitmoreCanvass11201Active
Esi MensahPhonebank10001Active
Farid KhanCanvass11217Pending
Showing 1–6 of 128 volunteers
ResourceIndexPage — TitleBar (+ New) · toolbar/filter band · table · pagination footer
table (required)
The data region — a ds/grid <DataGrid/>, a passed <table>, an <EmptyState/> or a <Skeleton/>. Lives in TablePage's table slot.
toolbar / facets
toolbar is the band above the table (search, bulk actions); facets is an optional rail (e.g. a <FacetRail/>) — when set the body splits at facetWidth (default 240px) and collapses on narrow screens.
footer
Pagination / counts, in a space-between foot band (small + muted). header props (title/breadcrumbs/primaryAction…) build the TitleBar above the recipe.

ResourceDetailPage

The 'view one X' route. The TitleBar carries the record's name + a status badge and the record actions (Edit primary; Message/Archive/Delete secondaries). The main body holds the record's sections; the metadata aside sits beside it (340px, collapsing beneath on narrow); a provenance line closes the page. Set maxWidth to cap it to a centered reading column.

Ada Okoro

Active

About

Email
ada.okoro@example.org
Phone
(718) 555-0142
Neighborhood
Park Slope, Brooklyn
Joined
Mar 4, 2026

Recent activity

  • Checked in — Spring Adoption Drive · 2d ago
  • Joined team — March Organizers · 1w ago
  • Completed onboarding · 3w ago

Record #4821 · last updated 2 days ago by Rebekah M.

ResourceDetailPage — TitleBar · main body + metadata aside (collapses on narrow) · provenance footer
content (required) / aside
content is the main body (RecordPage main); aside is the metadata/related rail — when set the body splits at asideWidth (default 340px) and stacks on narrow. footer is the provenance closer.
maxWidth
true → centers to var(--page-max); a CSS string → a custom reading width; omit for full-bleed. Same knob as the underlying RecordPage.

ResourceEditPage

The canonical create/edit shell. TitleBar (with a live Saved/Unsaved badge) over the form, an optional help aside, and the sticky SaveBar that rises from the bottom the moment the form is dirty — useUnsavedChanges is armed throughout, so reloading the tab while dirty prompts. The parent owns the form state and passes dirty / saving + onSave / onDiscard; the shell owns the chrome and the guard. (Demo: edit a field, then try to reload.)

Ada Okoro

Saved

Edit any field to dirty the form and raise the SaveBar.

ResourceEditPage — TitleBar · form (+ help aside) · dirty-aware SaveBar + useUnsavedChanges guard
form (required) / aside
form is the field stack (RecordPage main); aside is an optional help/metadata rail. maxWidth defaults to true (a centered reading column) — pass false (as the demo does) when an aside needs the room.
dirty + SaveBar
dirty drives the SaveBar visibility AND arms the route-leave guard. saving locks the bar + spins Save. The bar renders only when onSave/onDiscard (or a custom saveBar node) is given; saveMessage / saveBarChildren / saveLabel customise it.
useUnsavedChanges
Wired internally on `dirty` (this is why the shell is a client component). unsavedMessage sets the soft-nav confirm copy; guardEnabled is the master off-switch (e.g. flip false right after a successful save).

SettingsPage

The sectioned settings screen. A section-nav rail (sticky on lg+, collapsing above the sections on narrow) sits beside a stack of grouped settings cards, all under the TitleBar and closed by a SaveBar. The SaveBar is prop-driven here (the template stays server-safe) — drive dirty from your form; if you also want the route-leave guard, use ResourceEditPage or call useUnsavedChanges in your client parent.

Settings

Profile

Notifications

Privacy

Change any field to dirty the form and raise the SaveBar.

SettingsPage — TitleBar · section-nav rail + grouped sections · prop-driven SaveBar
nav / sections (required)
nav is the section-rail (links/buttons); sections is the stacked groups. The grid splits at navWidth (default 240px); navEnd moves the rail to the trailing edge. Omit nav to render the sections full-width.
SaveBar (prop-driven)
Same SaveBar-shaped props as the edit shell (dirty / saving / onSave / onDiscard / saveMessage / saveBar) but WITHOUT the route guard, so SettingsPage stays server-safe. The bar renders only when there's a save path.