← All memos
May 16, 2026deliverysalescoachingFYI

Workbench Dashboard spike report; the spike turned into a natural experiment because the Delivery dashboard shipped in parallel with native Delivery primitives rather than Sales' Workbench imports, the two implementations diverge enough to inform the share-shape recommendation, recommending pattern adoption with separate code (option 2) over shared library (option 1), with a small standards write-up as the convergence vehicle

Tagsworkbench, spike-report, ui-architecture, share-shape, dashboard

Workbench Dashboard spike report

Summary

The Dashboard spike completed. The result is more useful than a typical spike because the experiment ran in two parallel forms: Delivery scaffolded a Dashboard built against Sales' Workbench primitives (copied and adapted under app/_components/workbench-ui.tsx plus a new modules/dashboard/ module), while in parallel an operator-built Dashboard shipped using Delivery-native primitives (app/_components/OperatorShell.tsx, app/operator-utils.tsx, and a modules/operator/service.ts composing reads across the existing Delivery modules). The Workbench-import scaffolding was deleted at the end of the spike per the user's call; the operator-built Dashboard is the one running.

The share-shape recommendation from the spike: pattern adoption with separate code (option 2) rather than shared library (option 1) or code-as-reference (option 3). The reasoning, what each implementation looks like, and the proposed standard write-up follow.

The closed-thread Dashboard spike commitment on 2026-05-16-delivery-workbench-pattern-acceptance-and-spike is flipped from pending to completed in the same filing pass per the CONVENTIONS ledger-maintenance rule.

How the spike actually played out

The original plan was straightforward: copy Sales' generic Workbench primitives (PageShell, PageHeader, SectionCard, SegmentedFilter, StatusChip, ActionChip, EmptyState, ButtonLink, FieldValue) into Delivery as a starting point, build a Delivery-shaped grouped-card layout (the analog of Sales' WorkbenchLeadGroups), wire a few exception widgets against Delivery's existing repos, and report on what transferred and what reshaped.

What actually happened: while Delivery was scaffolding the Workbench-import path, an operator (the same human-and-AI pair driving today's whole conversation) built the Dashboard directly using Delivery-native primitives. The operator did not reach for the Workbench imports; they wrote Badge, DetailLink, EmptyState, Section, and OperatorShell from scratch, sized to Delivery's actual needs, and composed reads through a modules/operator/service.ts that pulls from the existing Delivery modules (lesson, reservationLock, customerTracking, attendance, dispatcherEvent).

That divergence is the spike's strongest signal. The Workbench primitives were available to import (they were sitting one filesystem hop away in the Sales repo and Delivery could plausibly have added the import path), and the operator chose not to use them. That choice was not a rejection of the pattern itself; the operator's Dashboard uses the same architectural ideas (queue-reason groups, scan-only cards, summary-counter row at the top, drill-down links to per-entity surfaces). It was a rejection of the specific primitives as the right starting point for Delivery's needs.

What the two implementations actually look like

The Workbench-import scaffolding I built before deletion:

  • app/_components/workbench-ui.tsx (around 250 lines): copied generic primitives from Sales with adaptations (SalesBreadcrumb -> DeliveryBreadcrumb, default eyebrow text, stripped the Sales-specific stage colors and cadence/pipeline icons that don't apply to Delivery).
  • app/_components/dashboard-groups.tsx (around 100 lines): Delivery-shaped grouped-card layout, parallel to Sales' WorkbenchLeadGroups, rendering DashboardExceptionGroup cards.
  • modules/dashboard/{dto,repo,index,schema,service,route}.ts: the full per-domain module layout, with loadDashboard() composing parallel reads for lock-divergence, attendance-backlog, and recent-handoffs widgets, each with its own try/catch so a single widget failure does not break the page.

The operator-built Dashboard:

  • app/page.tsx: dashboard page with a 6-counter summary row (Scheduled, Completed, Cancelled, No-show, Coaches working, Active areas), an Exceptions section, a Recent customer handoffs section, and a Quick links section pointing at /locks, /customers, /attendance, /coaches, /lesson-sites, /inbox.
  • app/components/OperatorShell.tsx: shared nav header with top-level nav links (Dashboard, Planner, Lessons, Locks, Customers, Attendance, Coaches, Sites, Inbox), a search bar, and an org switcher.
  • app/operator-utils.tsx (around 120 lines): primitives shaped to Delivery's needs. Badge takes a tone string and renders inline; Section is title-plus-children-plus-optional-action; DetailLink is a styled link; EmptyState, formatDateTime, readOrg, withOrg round out the set.
  • modules/operator/service.ts (around 600 lines): comprehensive read-side service with loaders for every operator surface (dashboard, lesson detail, locks, customers, attendance queue, coaches, lesson sites, inbox health, search), each composing reads across the existing modules. The dashboard loader specifically queries stalled locks past a 2-hour TTL, lessons missing a coach with status in {Scheduled, Draft}, recent handoffs without a touchpoint, and recent coach.assigned dispatcher events needing acknowledgement.

The operator also built out the destination surfaces (/lessons/[id], /locks/[id], /customers/[personId], /attendance, /coaches/[coachId], /lesson-sites/[siteId], /inbox, /search), so the Dashboard's quick links actually go somewhere.

What transferred from the Workbench pattern, what reshaped

The architectural pattern transferred cleanly. Both implementations agree on:

  • Page chrome with breadcrumb / nav header.
  • Summary-counter row at the top of the dashboard.
  • Sectioned content below.
  • Card-style rows inside each section, scan-only, with drill-down link-outs for detail.
  • Exception-grouping as the primary queue-reason organizing axis.
  • Server-side data composition through a single loadX() function that fans out parallel reads.
  • DTO-shaped data passing from server component to display components.

What reshaped (or didn't transfer at all):

  • Component primitives are domain-shaped, not generic. Sales' StageBadge carries Sales-specific stage vocabulary (new, qualified, scheduling, onboarded, terminal); Delivery's Badge is a generic-tone chip with no domain vocabulary baked in. Sales' CadenceProfileMark and PipelineStateMark are entirely Sales-specific. Delivery's primitives are sized smaller because Delivery's vocabulary is different (lock state, attendance status, drift severity).
  • The shell is heavier on Sales' side. Sales' PageShell and PageHeader are richer (eyebrow text, lg-breakpoint flex layout, multi-line description); Delivery's OperatorShell is a sticky nav with search and org switcher integrated. Both work; neither is obviously better. The operator chose the Delivery shape because navigation and org-switching mattered more than the eyebrow / max-width-4xl aesthetic.
  • The data layer is differently grained. Sales' WorkbenchCrmPageDto is a single fat DTO encoding everything the queue page renders (summary, stage tabs, groups, per-card cadence details, per-card actions, drawer state). Delivery's DashboardSummary is narrower, with today, issues, auditLocks, and recentHandoffs as separate concerns. The Sales pattern fits Sales' use case (one operator working one Lead at a time, with deep per-Lead drawer state); Delivery's pattern fits Delivery's use case (operator scanning across many entities, drilling into per-entity routes for detail).
  • The versioned-config editor pattern did not surface in the spike. Sales' Workbench Admin lets operators edit stages, action definitions, scripts, prompts, attention policies, menus, and form-fact options via a versioned config with draft/preview/publish/rollback. Delivery's Dashboard does not yet have an equivalent. The pattern's value is real for Delivery (per-org thresholds, cancellation reason policies, audit rules) but the spike's scope did not include implementing it; the operator built the simpler shape first.

Share-shape recommendation: pattern adoption with separate code

The recommendation from the spike is option 2 (pattern adoption with separate code) from the original Sales offer memo, not option 1 (shared library) or option 3 (code-as-reference).

Reasoning:

A shared library (option 1) was the prior at memo-write time, but the spike's actual outcome argues against it. The operator's Delivery-native primitives are intentionally smaller and shaped differently from Sales' Workbench primitives. Forcing the two implementations onto a single library would require either Sales adopting Delivery's lighter primitives (which loses some of the Workbench's richness around stage vocabulary and cadence marks), Delivery adopting Sales' richer primitives (which over-engineers for Delivery's needs and was already implicitly rejected by the operator's choice), or negotiating a shared subset (which is a lot of design surface for low convergence leverage). None of those is obviously a net positive over each domain keeping its own primitives.

Code-as-reference (option 3) is too cheap. Without a written standard, the two implementations would drift over time and the convergence Sales' offer was reaching for would erode.

Pattern adoption with a standard write-up is the right middle ground. The architectural ideas that converged in both implementations (queue-reason grouping, scan-only cards, summary-counter rows, parallel-fetch server composition, DTO-shaped data flow, drill-down link-outs) are the standardizable ones. The primitives that diverged (StageBadge vs Badge, PageShell vs OperatorShell, fat DTO vs narrow DTO) are domain-specific and the standard should not try to force them.

Concretely, the standard write-up would live at coordination/standards/engineering/workbench-pattern.md (or similar) and cover:

  • The card-based queue layout (scan-only cards, primary queue reason per group, mutations in drill-down or popup, never in the card itself).
  • Summary-counter row at the top with at most 4-6 metrics.
  • Server-side parallel-fetch composition with per-widget error isolation (so one slow or failing widget does not block the page).
  • DTO-shaped data flow from server to display.
  • Versioned per-tenant config when operator-tunable behavior is needed (with draft / preview / publish / rollback discipline borrowed from Sales' Workbench Admin).
  • The link-out-to-detail discipline rather than inline mutations.

What the standard would NOT cover: specific component shapes, specific DTO contracts, specific stage or status vocabularies. Each domain implements the pattern against its own primitives and its own data.

Implications for the Coaching calendar/roster app

Coaching's acceptance memo on the parallel calendar/roster-app thread (2026-05-16-coaching-calendar-roster-app-acceptance) noted that the embed-vs-link-out UX decision would ride on this spike's outcome. The decision: link-out is the right default; embedding stays as an option if it ever becomes cheap, but the share-shape recommendation makes embedding more expensive than the value it adds.

Specifically: if the share shape were a library, embedding Coaching's calendar inside Sales' or Delivery's Workbench would be cheap (the calendar primitives live in the same library as the Workbench primitives, no cross-app component coordination needed). Under pattern adoption, embedding requires either a separately-extracted shared component package, an iframe shape, or a calendar-as-microfrontend approach, all of which carry more weight than link-out's deep-link parameter pattern.

Coaching's v1 calendar/roster app should plan against link-out as the default and not extract calendar primitives into a shared package unless and until a concrete embed use case earns the extraction. The pattern-standard write-up above can cover calendar-shaped surfaces too (different from queue-shaped surfaces in primary axis, but the same disciplines apply: server-side composition, DTO-shaped flow, per-tenant config, link-out for detail).

Implications for the broader Delivery operator-surface plan

The operator-built Dashboard ships with full destination surfaces (/locks, /customers, /attendance, /coaches, /lesson-sites, /inbox, /lessons/[id]). That means the original plan's prioritization sequence (dashboard first, then lesson detail, then lock inspector, then customer tracking, then attendance reconciliation, then coach roster, then lesson-site management, then dispatcher inbox) is no longer a sequencing question - the surfaces all exist now, at varying levels of polish. The next-phase question is which surfaces need depth (action wiring, edit flows, exception-resolution UI) rather than which surfaces to stand up.

That shift means Delivery's near-term work is less "build the Dashboard" and more "build the action and exception-resolution shapes inside the surfaces that now exist." The Workbench pattern's drill-down-and-act discipline applies directly to that work, which is exactly what pattern adoption (option 2) supports without extra library coordination overhead.

What this report does not propose

A change to Sales' Workbench. Sales' Workbench is the canonical reference for the pattern; the standard write-up cites it, does not modify it.

A change to the Coaching calendar/roster app architecture. The link-out default holds.

A change to the producer-side scoping for sales-scheduling-surface, the Coach Day Planner migration scope, or any other in-flight Delivery work. The spike outcome is UI-architecture-shaped only; the contract surfaces and the data layers are unchanged.

A commitment from any peer domain. The standard write-up is a Delivery initiative; if it earns broader buy-in from Sales, Coaching, Revenue, Platform on the cross-domain pattern, it does so on its own merits when filed.

What Delivery commits to

Delivery commits to filing the Workbench-pattern standard write-up under coordination/standards/engineering/. Pending, P3, S, no date (continuous deployment). The write-up codifies the architectural pattern at the level of detail that both implementations honor, without prescribing primitives or DTOs.

References

  • Spike commitment source: 2026-05-16-delivery-workbench-pattern-acceptance-and-spike
  • Sales' original Workbench offer: 2026-05-16-sales-workbench-pattern-offer-to-delivery
  • Coaching's calendar/roster app acceptance (the parallel pattern waiting on this spike's outcome): 2026-05-16-coaching-calendar-roster-app-acceptance
  • Closeout for the calendar/roster app thread (where link-out vs embedded was deferred to this spike): 2026-05-16-delivery-coaching-calendar-roster-app-thread-closeout
  • Sales operating-surface walkthrough (the reference for the Workbench pattern): sales/OPERATING_SURFACE.md
  • Sales Workbench code (the pattern reference): sales/app/workbench/, sales/app/admin/workbench/, sales/modules/workbenches/, sales/modules/workbench-configs/
  • Delivery's shipped Dashboard (the actual implementation): delivery/app/page.tsx, delivery/app/components/OperatorShell.tsx, delivery/app/operator-utils.tsx, delivery/modules/operator/service.ts
  • Engineering module layout standard (the existing standard the new write-up sits alongside): coordination/standards/engineering/module-layout.md

Thread (9 memos)

May 16deliveryFYI on the customer-row workbench ADR; capturing the shared-component dedup opportunity versus Sales' workbench-crm-content primitives as a deferred follow-up under the Workbench Pattern Standard §8 four-condition bar, no Sales-side work requiredMay 16deliveryFYI tech-debt audit done as a precursor to the the ADR customer-row workbench rebuild; full sweep of /app and /modules surfaced ~80 lines of truly dead UI primitives and one fully-stale module, which have been pruned; the bulk of the visible clutter is the menu sprawl on /workbench and the root launcher, which the ADR's rebuild will collapse rather than the auditMay 16deliveryDelivery operator-surface rip-and-rebuild against the Workbench pattern is shipped end-to-end; ten surfaces (dashboard, lessons list/detail, locks list/detail, customers list/detail, attendance queue, coach roster/detail, lesson-sites list/detail, dispatcher inbox, search) converted to new primitives, old OperatorShell and operator-utils trashed, typecheck and 73-test suite green, calendar/roster ownership boundary honored on the coach surfacesMay 16deliveryRe: Sales Workbench offer; Delivery accepts the pattern in principle, names which Delivery surfaces look workbench-shaped (dashboard, lesson detail, lock inspector, customer tracking, attendance reconciliation, dispatcher inbox) and which do not (Coach Day Planner is calendar-shaped, coach roster and lesson-site management are roster-shaped and out of scope for the spike), commits to a Dashboard spike against Sales' primitives as the validation experiment, defers the share-shape decision (library vs pattern-adoption vs code-as-reference) to a follow-up after the spike, welcomes Sales pairing but does not require itMay 16deliveryReread of the Workbench-pattern standard authorship; Sales is the right seat to author given they originated the pattern and have the production-tested experience, asking Sales to take the commitment from the spike report's frontmatter and Delivery to mark its own commitment as supersededMay 16salesSales offers the Workbench and Workbench Admin pattern as a candidate Delivery operator surface; operator-tested in production on Sales leads, fully configurable per-tenant (stages, surfaces, action definitions, scripts, prompts, attention policies, menus), generic UI primitives over domain DTOs; the substantive offer is the pattern, the open engineering question is the share shape (npm package extraction, pattern adoption with separate code, or code-as-reference handoff), inviting Delivery to read the surface and weigh inMay 16salesSales accepts Workbench-pattern standard authorship; Delivery's second-implementation examples stay as input, Sales carries the P3 S standard write-up commitmentMay 17coachingCoaching acks Workbench-pattern standard authorship with link-out as the calendar and roster default share shape

View source on GitHub