Namaste Ji by Ayushman Dash

Docs / console/design-patterns.md · mirrored from the repo

BO Console — Object Presentation Patterns

Living catalog of system-design patterns for visualizing data objects in the back-office console. The premise: data objects fall into a small, easy-to-identify taxonomy, and each shape of object should be presented and behave the same way everywhere via reusable patterns — never designed bespoke per page. Identify the object’s shape first, then apply the matching contract below. New object types should slot into these patterns, not invent their own.

Already in practice: canonical routed object pages reused across surfaces (PR-E/PR-F). These patterns generalize that instinct to every object and every surface.

Object taxonomy

Most things on screen are entities with a lifecycle: brief, shot, asset, run, batch, agent, … Each has: an identity, a status/lifecycle, relationships (lineage), and actions. A few are collections (lists of entities) or events (append-only run/audit log lines). Classify first; the contract follows from the class.

Patterns

1. Object presentation contract

Every entity kind declares, in one place: its list renderer, its detail renderer, its status vocabulary, and its actions (single + bulk). UI surfaces compose from these declarations rather than re-implementing per page.

2. One detail component, many hosts

An object’s detail view is a single component reused by every host — routed page, drawer, modal, preview. A routed /briefs/:id page and a ?panel=brief~id drawer must render the same component. (Current: BriefDetail, the *Detail renderers in PipelineDrawer.)

3. Stack-based overlays (drawers/sheets)

Overlays are a push/pop navigation stack, not independent open flags. Opening from within an overlay pushes; cancel/Escape/backdrop pops (reveals previous); close-all clears. Stack serialized to one URL param, backed by browser history, deep-linkable, refresh-safe. Render as a capped cascade. See the drawer-stack item in /TODO.md.

4. Lineage / relationship walking

Related objects (e.g. asset → shot → brief) are navigable without losing context — walking lineage pushes onto the overlay stack (pattern 3), never destroys where you came from.

5. Server-authoritative collections

All search / filter / sort / pagination for collections happens on the server/API. The client never filters a fully-loaded list (counts will bloat fast). Query state (filters, search, sort, page/cursor) lives in the URL (useSearchParams) so it round-trips to the server and survives refresh.

6. Status & lifecycle as data

An object’s lifecycle state is data, rendered by one shared Badge with a consistent status vocabulary and color mapping per object kind — never ad-hoc inline styling.

7. Loading taxonomy

Distinguish two async states and render them differently:

  • Skeleton — data is being fetched (list/detail load). Show shape-matched skeleton placeholders.
  • Progress / activitywork is in flight (generating, vectorizing, rendering, batch running). Show an animated running-step indicator (see the pipeline loaders/animation items in /TODO.md).

8. Consistent actions

Per-object actions (and bulk actions on collections) use the same affordances, confirmation patterns, and permission gating (RBAC) wherever that object appears.

9. Data type → control / visual mapping

The type of a field determines the control and visual used to display and filter it — this is global, not a per-page decision. Identifying the mapping should be obvious from the data’s shape. Canonical mapping:

Field shapeDisplayFilter control
Categorical, multi-value (status set, language, geo, agent, tags)Chips / badgesMulti-select (a row can match any of several selected values)
Categorical, single-valueLabel / badgeSingle-select
BooleanToggle / checkToggle or tri-state (on / off / any)
Free text (title, caption)TextSearch box (server-side, debounced)
Date / timeFormatted / relativeDate range picker
Number / countNumericRange / comparator
Enum lifecycle statusShared Badge (pattern 6)Multi-select over the status vocabulary
Relationship (lineage ref)Link that pushes onto the drawer stack (patterns 3–4)Select by referenced object

Default rule of thumb: if a field can legitimately hold or match more than one value, its filter is multi-select. Filter state still lives in the URL and is applied server-side (patterns 5). Build these as shared filter-control primitives so every collection composes the same controls rather than hand-rolling them.

10. Live updates via server push, not polling

Freshness comes from the server pushing events, not the client polling. One long-lived stream (SSE GET /api/stream) per session replaces all per-page polling loops. A Durable Object event hub in console-api fans out events emitted by producers (agents/pipeline steps already POST run events to the /internal/run-events sink) to all subscribers. Every event carries { type, object: {kind, id}, ts, ... } so the client does targeted revalidation of the affected object (pattern 1/2) — not a blanket refetch. Drives the running-step animation (pattern 7) live. On reconnect, replay via Last-Event-ID or one reconciling refetch. Same stream is the notifications substrate (a notification event type; later fans out to Web Push from the same hub). SSE now; upgrade to WebSocket (Agents SDK/PartySocket, already a dep) only if bidirectional/ presence/low-latency is needed.


Extend this doc whenever a new presentation decision is made — capture it as a pattern so it applies to every object of that shape, not just the screen that prompted it.