The Visits tab is where the floor and the book meet. Its views pane is flat — Showroom (default), Calendar, List, Board — no nested presentation switcher inside any one view.
The design is grounded in real usage: reps ignore the calendar tab today, and 54% of past sales appointments end unresolved (27,501 of ~51k in prod Postgres, per the 2026-09-01 engineering feasibility report; 12.7% of all appointments overall — sales appointments being the Board's actual population) — nobody marks them Show or No-Show, and the silent hourly sweep buries them. A multi-store CEO demanded to know who logs each visit, and asked by name for an exportable ups/visits report. Walk-ins were polluting appointment metrics simply by existing on the same surface as scheduled visits. Showroom is today's floor — wait timers, walk-ins. Calendar is a real time grid — Day/Week/Month, fixed 30-minute blocks, business-hours shading. List is that ups/visits report as a table. Board lays the same appointments out as day-column cards. Resolution — marking a visit Show or No-Show — lives on the status chip everywhere it appears, not in a pinned queue: an "outcome unknown" chip opens a quick Show/No-show popover, and the full dialog only when a note is wanted. Every visit still carries an origin mark (DAISI-booked, walk-in, or scheduled) and a logged-by attribution, with a role-gating rule that keeps who books a visit distinct from who completes it.
The tab is named Visits, not Showroom — the tab names the domain, the views name the modes; the titlebar names whichever view is active.
07-28
Walk-ins never appear on the Calendar — a calendar is a schedule of future promises; walk-ins live on the Showroom floor log with a wait timer instead.
07-29
Add follows the active view — Check in a visitor on Showroom, Book an appointment on Calendar/List/Board; the primary action is never generic.
07-29
Showroom stays a Contacts view for now — a dual-home with the Visits tab; removal is an OPEN decision, not a ruling.
07-29
A dedicated Unresponded smart view is PARKED — the idea is good, but not this round.
07-30
Origin trichotomy — DAISI-booked, Walk-in, or Scheduled. DAISI carries the mono origin mark; Walk-in carries an orange tag; Scheduled carries neither.
07-30
Check-out outcome is REQUIRED — Show/No-show is a mandatory step before the destructive check-out confirm, though the model has no field for it yet (PRESCRIPTIVE).
08-25
Direction reset — the resolution-first agenda is retired; Calendar is rebuilt as a time grid on a competitor-inspired model.
08-25
The views pane is flat — Showroom / Calendar / List / Board, no nested switcher; the fourth view is named Board rather than Agenda, because Agenda already names a customer-profile surface.
08-25
Resolution is demoted from a pinned queue to the status chip — an "outcome unknown" chip opens a Show/No-show popover anywhere it appears; the full dialog is for when a note is wanted.
08-25
The manager strip is retired — manager needs are met by List's filters and the Board, not a standing strip of counts.
09-01
The symmetric week board is superseded by the asymmetric hybrid — after the adversarial GSM review and the engineering feasibility report. Day columns stay for capacity; a state position (the pinned Needs-action lane) is added for enforcement. The lane is windowed — the mocks show a trailing 14 days; window length and backlog amnesty await a CPO ruling. Show/No-show map to the existing completed/missed statuses in v1. DAISI follow up is a rescue action, not an outcome.
The views rail flips the whole screen between Showroom, Calendar, List and Board in place — same header, same scope chips, just the content and the primary action swap.
1 · Desktop — working screen (click a views-rail entry to switch)
Search/
Cash MakersSales Manager
Showroom
Appointments 115%
Shown 3of 11 Appts
Avg Visit 2h 32m5%
Sold 115%
Showroom2 Total< 2h2h - 4h> 4h
TB+2
9:05 am4h 10mSales
GreetTest DriveAppraisalManager T.O.Credit App
2026 Land Rover Defender#1324557$96,000LeaseFresh
JM+2
11:42 am1h 15mSales
GreetTest DriveAppraisalManager T.O.Credit App
2025 Land Rover Defender#1324567$26,000CashFinance
Anticipated5 Total311
TB+2
9:00 amSalesMissed
JM+2
10:00 amSalesUnconfirmed
2026 Land Rover Defender#1324557$96,000LeaseFresh
JM+2
12:00 pmSalesConfirmed
2025 Land Rover Defender#1324567$26,000CashFinance
TB+2
1:15 pmSalesConfirmed
JM+2
3:30 pmSalesConfirmed
2025 Land Rover Defender#1324567$26,000CashFinance
2026 Land Rover Defender#1324557$96,000LeaseFresh
Completed2 Total
JM+2
9:30 am2h 32mSales
GreetTest DriveAppraisalManager T.O.Credit App
Needed to go get driver's license
2025 Land Rover Defender#1324567$26,000CashFinance
TB+2
10:15 am1h 48mSales
GreetTest DriveAppraisalManager T.O.Credit App
Bought a car!
2025 Land Rover Defender#1324567$26,000CashClose
Aug 23 – 29, 2026
12 matching · Aug 23 – 29, 2026 · Each location's local time
8 AM
9 AM
10 AM
11 AM
12 PM
1 PM
2 PM
3 PM
4 PM
Sun23
Mon24
Jordan Avery9:00 – 9:30
Tue25
Dana Cole10:00 – 10:30
Priya Nair10:00 – 10:30
Wed26
Thu27
Renee Park12:00 – 12:30
Fri28
Evan Ross8:00 – 8:30
Sat29
121 matching · Aug 1 – Aug 31, 2026
Contact
Type
When
Location
Status
Karla Sellers
sales
Aug 1, 2026, 10:00 AM
Brentwood
outcome unknown
Renee Park
service
Aug 2, 2026, 8:30 AM
Lehi
cancelled
Dana Cole
sales
Aug 3, 2026, 2:00 PM
Bountiful
confirmed
Evan Ross
delivery
Aug 4, 2026, 11:15 AM
Brentwood
confirmed
Todd Bonzalez
sales
Aug 5, 2026, 9:00 AM
Lehi
confirmed
Aug 24 – Sep 1, 2026
Needs action3
outcome unknown6d
Aug 20 — 2:30 PM
Marcus LeeKarla Sellers
outcome unknown4d
Aug 22 — 11:00 AM
Priya NairUnassigned
outcome unknown2d
Aug 24 — 4:15 PM
Todd BonzalezSonya West
8/24Mon
8/25Tue
8/26Wed123
outcome unknown8:30 AM
Sales — 30 min — 8:30 AM
My insurance cards are digital
Karla SellersUnassigned
set1:00 PM
Sales — 30 min — 1:00 PM
Bobson DugnuttUnassigned
confirmed2:00 PM
Sales — 30 min — 2:00 PM
Dana ColeJake Martin
8/27Thu1
cancelled10:00 AM
Service — 30 min — 10:00 AM
Confirmed by phone for Wednesday at 8:30 AM. Customer reports a broken front...
Renee ParkUnassigned
8/28Fri1
confirmed11:15 AM
Delivery — 30 min — 11:15 AM
Evan RossSonya West
8/29Sat1
confirmed9:00 AM
Sales — 30 min — 9:00 AM
Todd BonzalezUnassigned
8/30Sun0
Nothing scheduled
8/31Mon1
confirmed3:30 PM
Sales — 30 min — 3:30 PM
Lena OrtizJake Martin
9/1Tue0
Nothing scheduled
Views rail hosts four flat entries — Showroom (default, on-state; orange location-dot + count pill), Calendar (the time-grid view), List (the exportable ups/visits table), and Board (the day-column kanban) — no nested presentation switcher inside the rail; Day/Week/Month lives inside Calendar's own pagehead. Two saved Calendar views (My Week, Deliveries) sit below the divider as plain .sb-viewitems. Clicking a rail entry swaps the pagehead title, date control, Add's tooltip, and the hosted surface — a single working screen; the scope chips (All/Mine/Unassigned) stay visible across every view.
Add follows the active view: Showroom → "Check in a new visitor"; Calendar / List / Board → "Book a new appointment". The Calendar surface is 1:1 comp-visits-calendar-grid.html (week grid); List is 1:1 comp-visits-list.html; Board is 1:1 comp-visits-board.html — the asymmetric hybrid: pinned Needs-action lane, today ring + two-badge headers, past-day receipts, minmax(180px,1fr) future columns with edge fade and peek.
PRESCRIPTIVE role defaults: a rep's scope chip defaults to Mine on both views (their own book); a manager's defaults to All, plus a Locations chip on multi-location dealerships. The choice persists per user across sessions, mirroring the Contacts shell's own All/Mine convention. The Locations chip renders for the manager persona shown here; visit cards add a conditional location line only when the active scope spans multiple rooftops — single-rooftop scopes never repeat the store name on every card.
The shell — mobile
Mobile carries the Showroom, Calendar (Day mode) and Board (day pager) views as separate phone frames, with a Views sheet that switches between all four.
Showroom phone — titlebar, mselbar (All/Mine/Unassigned, Mine default for a rep), the real .sb-shwm-body content at mobile width (not a placeholder), and the canonical .sb-bn-bar (six items, CRM lit; the list search row stays hidden until Search is tapped).
2 · Calendar phone — Day mode (mobile default; Week/Month are desktop affordances)
9:41
Mon 24
8a
9a
10a
11a
12p
1p
2p
3p
4p
Jordan Avery9:00 – 9:30
Priya Nair10:15 – 10:45
Marcus Lee1:00 – 1:30
Calendar phone renders Day mode by default — Week and Month are desktop-width affordances that don't fit a 375px column; day-to-day nav replaces the range chip. Titlebar reads "Calendar"; Add's tooltip reads "Book a new appointment." The manager strip is retired — not shown here. Same block grammar as the desktop grid (.sb-vgrid, promoted to components.css): fixed 30-min height, business-hours shading, an unresolved past block's orange left edge, a DAISI-booked block's gold origin mark.
3 · Board phone — one day per swipe page, pager dots
9:41
Needs action3
outcome unknown6d
Aug 20 — 2:30 PM
Marcus LeeKarla Sellers
outcome unknown4d
Aug 22 — 11:00 AM
Priya NairUnassigned
8/26Wed123
outcome unknown8:30 AM
Sales — 30 min
Karla SellersUnassigned
set1:00 PM
Sales — 30 min
Bobson DugnuttUnassigned
confirmed2:00 PM
Sales — 30 min
Dana ColeJake Martin
Board on mobile swipes ONE day per page — the same swipe-pager convention as the Tasks mobile board — with dots marking position in the week (filled = current day). The Needs-action lane renders as the first section above the day pager, same calm treatment as desktop (orange only on chip + left edge, neutral age badge, 12-older chip to the List). Swiping to a past day folds that day's receipt into its header — scheduled / shown / no-show / unresolved counts where the card stack would be; resolved visits never render as full cards. Card anatomy (.sb-vcard, promoted to components.css) is identical to the desktop board.
4 · Views sheet — all four entries, over the Showroom screen it was opened from
9:41
Appointments 115%
Showing 35%
Showroom2 Total
TB+2
9:05 am·4h 10m
Views
Views sheet — the canonical .sb-sheet-title (span .t + .x close button) hosting the two-entry .sb-viewlist; Calendar's pill carries the same orange unresolved count as the desktop rail and disappears at zero.
The calendar — a time grid
Day, Week, or Month, drawn as fixed-height blocks against an hour grid — Filters decides which rows load, Show decides how the grid presents them.
Blocks render at a fixed 30-minute height, never scaled to a real duration — our appointment model stores a single timestamp, not a start/end, so drawing anything but a fixed block would fabricate a duration we don't have. Business-hours shading (blue-02, low opacity) covers the working window on weekdays only; Sunday and closed hours stay plain. Today's date numeral (Mon 24) sits in a filled blue-01 circle. Block skins: default = white surface + gray-06 hairline; selected = filled blue-01 with white text; an unresolved past block keeps a 3px orange-01 left edge; a DAISI-booked block carries the small gold origin mark top-right. Tuesday shows two overlapping bookings side-by-side in one slot — the grid's collision-handling case.
1 · Pagehead band — Day / Week / Month + range nav
Aug 23 – 29, 2026
12 matching · Aug 23 – 29, 2026 · Each location's local time
Day / Week / Month is a presentation switch, wired via sb-controls.js (Week is the default on). It shares one range-nav pair and one Today jump across all three modes. The meta line states the match count, the exact range, and that times are each location's LOCAL time — multi-location dealerships never see one blended clock.
The board — the asymmetric hybrid
Three zones on one surface: a pinned Needs-action lane (unresolved past visits, oldest first, orange held to chip + edge), Today under its ring with two-badge headers, and future day columns on minmax(180px,1fr) tracks with a right-edge fade and a peeking last column. Past days compress to tappable day receipts — resolved visits never render as full cards — the assignee truncates before the customer name, and resolution stays one click away on the chip, with DAISI follow up separated below the divider as a non-resolving rescue action and every quick resolve undoable.
Confirmed by phone for Wednesday at 8:30 AM. Customer reports a broken front...
Renee ParkUnassigned
8/28Fri1
confirmed11:15 AM
Delivery — 30 min — 11:15 AM
Evan RossSonya West
8/29Sat1
confirmed9:00 AM
Sales — 30 min — 9:00 AM
Todd BonzalezUnassigned
8/30Sun0
Nothing scheduled
8/31Mon1
confirmed3:30 PM
Sales — 30 min — 3:30 PM
Lena OrtizJake Martin
9/1Tue0
Nothing scheduled
Three zones, one board. Needs action is a pinned ~240px first column holding unresolved past visits from a trailing 14-day window, oldest first — a calm surface, not a queue: orange lives only on the status chip and the thin left edge (the calendar's unresolved-edge convention), never as a banner or a fill. Each lane card carries a neutral age badge, the original date + time, customer, owner, and one next action; the lane scrolls internally and its 12 older footer chip opens the pre-filtered List. Today keeps the grid's ring convention on its date numeral; day headers carry at most two badges — orange unresolved count, neutral-outline unassigned count (a gap is not an alarm). Future days run on minmax(180px,1fr) tracks with horizontal overflow, sticky headers, a soft right-edge fade, and the last column peeking. Past days inside the week compress to day receipts — tappable scheduled / shown / no-show / unresolved counts, each opening the List filtered to that day + status; resolved visits never render as full cards anywhere on the Board. At minimum column width the time leaves row 1 and joins the type row; the assignee truncates before the customer name, full values in the tooltip. Status chips: outcome unknown (blue-01 fill, white text) · set (neutral outline — unconfirmed) · confirmed (blue outline) · cancelled (gray) · completed (green-02/green-01). A DAISI-booked card keeps the small gold origin mark.
The lane at zero is quiet green-01 text and nothing else — no decoration, no celebration graphic. The lane never hides; its presence is the promise that unresolved work always surfaces here first.
Resolution lives on the status chip everywhere it appears — clicking outcome unknown opens this popover directly, no navigation. Show and No-show resolve; DAISI follow up sits below the divider as a secondary action because it does not resolve — it schedules outreach. The full Resolve Visit dialog (comp-visits-resolution.html) opens only when a note is wanted. Every quick resolve fires the snackbar with a ~5-second Undo; the resolver is stamped on the visit either way.
The list
Every appointment as a row — the report a store manager can act on without opening the app.
Columns: Contact · Type (sales / service / delivery / other, gray chip) · When (full timestamp) · Duration · Location — a dealer's "Rooftop" is our Location, named that way in this table on purpose · Assigned ("Unassigned" gray-04 or a name) · Vehicle (VIN, mono; an em-dash when the deal has no vehicle — the same em-dash convention as every other table in the system) · Status (the same chip set as the board) · a trailing edit pencil. Server-sorts on When by default.
This view IS the exportable ups/visits report a CEO asked for by name — every row a visit, every column a fact he can hand to a store manager. An export affordance is PRESCRIPTIVE; no export endpoint exists today.
Resolving a visit
Marking a visit Show or No-Show starts from the status chip everywhere it appears; the full dialog is for when a note is wanted, and every path offers an undo.
The row stays --attn (orange-02) until resolved — it never takes --dim, which is reserved for a settled outcome (Declined/Missed strike, per R-C). The inline pair (.act-complete / .act-missed) opens the full dialog for the note; a future one-tap-only mode is out of scope here.
PRESCRIPTIVE: the outcome-note field has no model support today — it is a design-ahead capture point, not a shipped field (eng ask on the epic list). "Resolve" stays disabled until an outcome is chosen. No-show reveals a secondary "DAISI follow up" affordance; Show does not.
3 · Mobile — full-screen covering sheet (never a pushed pane)
Resolve Visit
Mobile uses .sb-cvrsheet — a full-screen sheet that slides up over the agenda (translateY(100%) → .is-open), not the desktop's 424px split-view panel. Resolution is a modal decision at any width; mobile has no room to push content beside it.
Marked as shownEvery resolve — inline pair or dialog — surfaces this toast. Undo reverts the visit to Unresolved; the status chip goes back to reading "outcome unknown" wherever it appears — no confirm dialog on the way in, only this way back out.
Origin and attribution
Every visit card on the floor carries who booked it and who logged it — two independent facts, not one blended label.
comp-showroom-card.html specs the origin trichotomy (DAISI / Walk-in / Scheduled) and the Logged-by row directly on its specimens; that page carries no standalone nugget to embed here — see the anatomy map link above for the live specimens and captions.
Engineering scope
[CANONICAL] The Board reads POST /dashboard/appointments — fold the showroom fields into that response rather than standing up a second feed; day receipts are a GROUP BY status on the existing summary endpoint.
[NEW] Task-to-appointment linkage (an appointmentId on tasks) so a DAISI rescue is owned by the visit it rescues.
[NEW] createdBy / resolvedBy columns — forward-only; legacy rows render an em-dash rather than a guessed name.
[NEW — URGENT] Preserve scheduledAt before check-in overwrites it with the actual arrival time — flagged urgent before showroom check-in adoption ramps; without it, "was it late" is unanswerable after the fact.
[PRESCRIPTIVE] Appointment origin field (scheduled / walk-in / AI-booked) — today it's derivable from the *VisitedAt stamps, with three known leaks: check-in overwrites appointmentAt, retro-logged visits are ambiguous, and DAISI's create-appointment path misses setVisitedAt.
[PRESCRIPTIVE] An outcome field on resolution, plus the resolve endpoints themselves — in v1, Show/No-show write the existing completed/missed statuses; the dedicated outcome field follows.
[PRESCRIPTIVE] An oldest-unanswered / wait-time sort key, for surfacing unresolved visits first on List and Board.
[PRESCRIPTIVE] A LeadsFilters unresolved field, plus the dashboard date window passed into the unresponded filter.
[DEFERRED] scheduledEndAt (duration) — the grid renders fixed 30-minute blocks until it lands; deferred to the calendar-grid phase.
[DEFERRED — phase 2] The per-appointment DAISI rescue trigger behind the popover's follow-up action.