TabbedPage
A report/analytics shell: a PageTabs rail below the header and the active tab's panel as children (composes the @phauna/ds ReportLayout).
Analytics
Overview
Signups are up week over week, driven by the Bay Area meet-up campaign. Activation held steady; churn ticked down after the new onboarding checklist shipped.
- tabs (required)
- The tab rail rendered into the toolbar — typically a thin client island that resolves the active tab from usePathname; the TabbedPage shell stays server-safe.
- children / toolbarExtra
- children is the active tab's panel; toolbarExtra adds controls beside the tabs (e.g. a date range).
- maxWidth / footer
- maxWidth centers the body to a reading column (true → var(--page-max), or a CSS width); footer is the page footer slot.
SideTabPage
A 'BeagleBot console': a route-driven <SideTabs> rail | the active section's content | an optional right <InspectorPanel> — the shape for settings-style and section-switcher consoles.
BeagleBot console
- tabs / children
- tabs is the grouped SideTabGroup[] rail model (route-driven highlight); children is the active section's content pane.
- inspector / inspectorWidth
- Optional right InspectorPanel rail for the selected item; the body splits when set.
- SideTabs is client
- The rail resolves the active tab from the current route (DS Tabs state), so wrap selection in a client island; the SideTabPage shell stays server-safe.
WorkspacePage
The resizable, persisted split shell: a primary work region with optional leading (nav/tree) and trailing (inspector) panes that drag, collapse and remember their sizes.
Mission control
Objectives
Foster network
Recruit 25 fosters
Owner · Dana K
Status
Due
Jul 12
Progress
Notes
18 of 25 onboarded. Bay Area meet-up booked for Jul 6 to close the gap.
Objective O1 · last updated Jun 26, 2026
- primary (required)
- The main work region — never collapsible, fills the remainder (PaneLayout.primary). primaryTitle is its a11y label / mobile-stack header.
- start / end
- Leading (left/top) and trailing (right/bottom) PaneRegion auxiliary panes — a nav/tree/files rail and an inspector/details rail.
- direction / persistKey
- direction lays panes 'horizontal' (default) or 'vertical'; persistKey forwards to SplitView to persist sizes + collapse across reloads. Server-safe wrapper over the client SplitView.
TriplePanePage
This is WorkspacePage configured as a three-pane IDE workspace — start navigator + primary canvas + end inspector, all resizable/persisted via PaneLayout.
import { WorkspacePage } from "@/components/ds/recipes";
import { TreeView } from "@/components/ds/tree";
import { InspectorPanel } from "@/components/ds/surface";
<WorkspacePage
title="Workspace"
start={{ title: "Navigator", content: <TreeView framed nodes={nodes} />, defaultSize: 22 }}
primary={<EditorCanvas doc={doc} />}
end={{ title: "Inspector", content: <InspectorPanel title={sel?.name}>{props}</InspectorPanel>, defaultSize: 26 }}
persistKey="workspace:panes"
/>SplitViewPage
The master/detail (inbox) shell: a list pane beside a detail pane that collapses to one column on narrow screens (composes the @phauna/ds ListDetailLayout). TreeExplorerPage and InboxPage below are configured presets of it.
Inbox
- Dana K9:14 AMFoster meet-up recap18 of 25 onboarded — booking a follow-up for Jul 6…
- Intake bot8:02 AM18 new rescuers from NVDA formNightly sync added 18 rescuers across 4 regions.
- Priya NYesterdayVet capacity blocking 10 intakesEscalated to ops — need an extra weekend slot.
Inbox
Foster meet-up recap
Dana K · 9:14 AM
Status
Message
18 of 25 fosters onboarded after the Bay Area meet-up. Booking a follow-up for Jul 6 to close the gap — can you confirm the venue is still available and loop in the volunteer coordinators?
Thread m1 · 1 of 3
- list / detail (required)
- list is the index pane; detail is the selected item's pane. Selection wiring rides the caller's own client island.
- listWidth / listEnd / fullHeight
- listWidth sizes the list pane; listEnd moves it to the trailing edge; fullHeight fills the viewport below the 44px admin nav (inbox-style).
- maxWidth / detailMaxWidth
- Cap the whole two-pane block, or just the detail content, to a centered reading column (true → var(--page-max), or a CSS width).
TriageQueuePage
A list/triage SPLIT preset over SplitViewPage: a selectable queue rail (a sticky filter bar + a stats band + the list of work items) beside an InspectorPanel detail whose footer is the action rail (assign / decline). Selection is route-driven; the rapid-review shape for onboarding / organizers / memberships.
Onboarding
- Dana Kerrigan3d agoOrganizingAustin, TX
- Priya Natarajan5d agoPolitical actionDallas, TX
- Marcus Webb1w agoMarchesHouston, TX
Dana Kerrigan
dana@example.org
Contact
dana@example.org · phone restricted for your role
Location
Profile
she/her · Long-time foster, wants to help canvass.
- queue / detail (required)
- queue is the selectable list (rows link to a per-item subroute); detail is the InspectorPanel for the routed selection. Selection is route-driven, so the recipe holds no state.
- filterBar / stats
- filterBar pins a search/facets/sort strip atop the rail; stats is an at-a-glance counts band (SummaryBar/StatGrid) beneath it.
- listWidth / fullHeight
- listWidth sizes the queue rail (default 380px); fullHeight fills the viewport below the 44px admin nav (default on).
Triage session
The shared behavior layer that rides TriageQueuePage (Admin Hi-fi 6c): keyboard j/k/a/h with auto-advance to the next unhandled row, the session-local handled ledger (approved/held/declined rows stay visible, dimmed with a status pill, instead of vanishing on revalidate), and the queue-clear panel once every row is handled. One recipe — pages register rows and a primary action; they never fork the keyboard/triage semantics.
Live session — focus the panel, then a approves, h holds (keys scoped to the panel; real queues use the window-global listener).
- Dana Kerrigan3d agoOrganizing
- Priya Natarajan5d agoPolitical action
- Marcus Webb1w agoMarches
j / k move · a approve · h hold · 0/0 handled
Dana Kerrigan
Organizing · requested 3d ago
Approve / hold / decline below — or focus this panel and press a / h. Each action dims the row with its status pill and auto-advances to the next unhandled one.
- Alma ReyesOrganizing
- Jon ParkMarches
- Lena FisherPolitical action
- Omar HaddadOrganizing
j / k move · a approve · h hold
- TriageSessionProvider
- Mounts in the queue's LAYOUT (not the page) so the ledger survives index ↔ [id] navigation. Owns the one window keydown listener (j/k move, a = primary, h = hold; dead in inputs / contenteditable / open modals) and the handled ledger.
- useTriageRows / useTriagePrimaryAction
- The queue island registers its VISIBLE ordered rows ({key, href, data}) + the routed selection + the index href; the inspector registers the fn that `a` fires. Selection stays route-driven — moving/advancing is a router.push to the row's href.
- mergeHandledRows / statusOf
- Merge the server's still-pending rows with the session ledger so approved/declined rows re-insert from their snapshot after revalidate (a hold is session-local — no server write). Style handled rows dimmed with the TRIAGE_STATUS_LABELS / TRIAGE_STATUS_TONES pill.
- TriageQueueClear
- Wraps the empty-detail placeholder; once every visible row is handled (or nothing was pending — serverRowCount settles the pre-hydration frame) it renders the dashed ✓ queue-clear panel with the session tally and a link to the next queue with pending items.
TreeExplorerPage
This is SplitViewPage configured as a tree explorer — list is a framed TreeView hierarchy, detail shows the selected node (an InspectorPanel).
import { SplitViewPage } from "@/components/ds/recipes";
import { TreeView } from "@/components/ds/tree";
import { InspectorPanel } from "@/components/ds/surface";
<SplitViewPage
title="Territory explorer"
fullHeight
listWidth="20rem"
list={<TreeView framed nodes={nodes} selectedId={sel} onSelect={(n) => setSel(String(n.id))} defaultExpanded={["us"]} />}
detail={<InspectorPanel eyebrow="Team" title={node?.label}>{detailBody}</InspectorPanel>}
/>InboxPage
This is SplitViewPage configured as a mail/notification inbox — list is a message Feed, detail is an InspectorPanel reader, fullHeight on (mirrors the email-triage route SplitViewPage already serves).
import { SplitViewPage } from "@/components/ds/recipes";
import { InspectorPanel } from "@/components/ds/surface";
<SplitViewPage
title="Inbox"
fullHeight
listWidth="22rem"
list={<MessageList items={messages} selectedId={sel} onSelect={setSel} />}
detail={<InspectorPanel title={open.subject} subtitle={open.from} actions={replyActions} footer={<ReplyBox />}>{open.body}</InspectorPanel>}
/>