01 · Executive summary

The verticals are already a filter. The product just doesn’t know it yet.

Calendars has one dataset, two presentations, four configuration domains and two different app shells. The fastest route to a coherent architecture is not to add structure — it is to stop modelling the same idea in three incompatible ways.

The decisive evidence is in your own screenshots. Both Calendar view and Appointment list view carry a “Services” dropdown in the toolbar. That control is a vertical selector. Meanwhile, in Calendar settings, those same verticals appear as top-level tabs (Meetings, Services, Rentals, Connections).

So the product already treats a vertical as a filter in the operational plane and a destination in the configuration plane. The unified-calendar mechanism you are proposing to build largely exists — it is just implemented as a single-select scope switch instead of a multi-select facet. That reframes the work substantially: this is less a new feature than the correction of a modelling error, which materially lowers the risk of shipping it to a surface with 1.5m monthly actives.

What I agree with

  • Appointments as the primary workspace, with Calendar and List as view types of one dataset. Correct, and supported by near-universal industry convergence.
  • Saved Views should belong to the dataset, not the table. Correct diagnosis. Your proposed model needs one structural fix (below).
  • A unified calendar is right, and “All” should be the default.

Where I disagree

  • “Meetings” is the wrong name, but renaming the vertical is the wrong fix. The collision is one level down: the word “calendar” currently means four different things. Fix the children, keep the parent.
  • Statuses are not platform-wide. They encode an appointment lifecycle (no-show, showed) that is meaningless for other objects. Keep them in Calendars.
  • Connections are neither purely platform nor purely calendar. They must be split: account identity is platform, sync behaviour is Calendars.
  • Verticals as flat top-level nav items will not scale — not because of nav length, but because each vertical drags a duplicate copy of Staff, Locations and Resources behind it. This is the most expensive latent problem in your proposed tree.
  • The vertical should not be the headline filter in the unified calendar. Operators think staff-first and day-first. Type is one facet among several, not the marquee control.

The one structural change I would insist on

Extract shared resources before adding verticals

Staff, Locations and Resources are shared objects that every vertical references. Today, Services owns its own Staff / Resources / Locations tabs. If Rentals and Field Services each grow their own, Jane Doe gets configured four times, availability conflicts become unresolvable, and the unified calendar cannot compute a trustworthy busy state. Extract them once, reference them everywhere.

FactWe have Pendo instrumentation showing scale (251k accounts, 1.5m MAU), mid-range NPS (~47) with a recent detractor spike, 10+ distinct booking entry points, active mobile usage on iOS and Android, and a live A/B test on feature discoverability.

AssumptionWe have no user interviews and no usability testing for this problem space. Every mental-model claim in this document is reasoned from role incentives, the product’s own telemetry and comparable-product evidence — not from research with users. Section 22 proposes how to close that gap before the expensive phases ship.


02 · Current-state analysis

What the screenshots actually reveal

Reading the six screens as an architecture rather than as a set of layouts.

The three-tab header is not a hierarchy

The Calendars header presents three peers: Calendar view, Appointment list view, Calendar settings. The first two are presentations of the same records. The third is a portal into a completely different app shell — a Settings left nav with a “← Go Back” escape hatch. One tab bar is doing two incompatible jobs: switching a view, and teleporting between shells. Users cannot form a stable model of “where am I” because the answer changes category depending on which tab they press.

“Calendar” means four things

UsageWhere it appearsWhat it actually is
CalendarsPlatform left navThe product
Calendar viewHeader tabA time-grid presentation
Calendars (122)Settings › Calendars › Meetings › CalendarsBooking configurations — types Event, Personal, Round robin, Class booking
Connected calendarsSettings › Calendars › ConnectionsExternal Google/Outlook accounts synced per staff member

Four referents, one word, all reachable within three clicks of each other. This is the single largest terminology defect and it compounds every other navigation problem.

The scope selector is hidden in plain sight

In Calendar view the “Services” dropdown sits between Week view and the display-options button — visually grouped with view controls. In List view the same dropdown sits immediately beside the Appointments heading — visually grouped with the page title. Identical function, two different semantic placements, so users learn it twice. And because it is single-select, the product structurally cannot answer “what is happening today across my whole business?”

Configuration outweighs operations 4:2

The product gives four top-level tabs to configuration domains (Meetings, Services, Rentals, Connections) and two to daily work. HypothesisTask frequency is inverted relative to this allocation: seeing today’s schedule and finding, creating or editing an appointment happen many times per day, while creating a service or connecting a calendar happens at setup and occasionally after. Every comparable product we examined (Square, GoDaddy, Acuity, Wix, Time2book) places the operational calendar first and pushes catalogue configuration below it.

Capability asymmetry between the two views

CapabilityCalendar viewList view
Saved, named configurations—Smart Lists
Persisted stateLast-used only, unnamedNamed + last-used
Status segmentationVia filter panelUpcoming / Cancelled / All tabs
Column / field control— (n/a)Manage columns, Customize list
Sort— (implicit: time)Explicit, multi-key
Filter entry point“Manage view” right panel“Advanced filters” chip
Buffer-time displayToggle—
Unified service viewToggle—

Two different filter entry points, two different vocabularies (“Manage view” vs “Advanced filters”), and a saved-configuration concept that exists on only one side. A user who builds a careful filter in Calendar view cannot carry it to List, and vice versa — despite both querying the same records.

Notable details worth preserving

  • “Unified service view” already exists as a toggle in Manage view. The concept of collapsing multiple bookable entities into one lane is established — we should build on this vocabulary rather than inventing new terms.
  • Buffer-time visibility as a display toggle is a genuinely domain-specific control with no analogue in generic SaaS. Keep it.
  • “New (action required)” rendered as a red-outlined editable dropdown in the list is excellent: status is both the alarm and the control. This should become the model for status everywhere.
  • Groups on the calendars list (105 ungrouped of 122) shows real-world scale and a grouping mechanism users are already under-using — a discoverability signal consistent with the live Pendo experiment.

03 · Core UX problems

Ranked by severity

P0 = fundamental, blocks a coherent model. P1 = major, causes daily friction or blocks scale. P2 = moderate. P3 = minor.

P0

One dataset is modelled as two destinations

Calendar view and Appointment list view are presentations, not places. Making them navigation peers forces every dataset-level concept — filters, saved configurations, search, bulk actions, status — to be built, learned and maintained twice, and guarantees they drift apart. They already have.

P0

“Calendar” has four distinct referents

Product, view, booking configuration, and external synced account all share one word within three clicks of each other. No amount of layout improvement compensates for a collapsed vocabulary; users cannot describe to each other where they are.

P0

A tab bar that switches app shells

“Calendar settings” presents as a peer of two view tabs but transports the user into a different navigation frame requiring “Go Back” to escape. Navigation must answer “where am I” consistently; here the same control class produces two categories of outcome.

P1

Verticals are simultaneously a filter and a destination

Single-select scope dropdown in the operational plane; top-level tabs in the configuration plane. Users must hold two contradictory models of what a vertical is, and the business question “what is happening across everything today?” is structurally unanswerable.

P1

Shared objects are owned by verticals

Staff, Locations and Resources live inside Services. Adding Rentals and Field Services on the same pattern produces N duplicate copies of the same real-world entities, breaks cross-vertical conflict detection, and makes a trustworthy unified busy-state impossible to compute.

P1

Saved configurations exist on only one side

Smart Lists are bound to the table representation. The most valuable personalisation in the product is unavailable in the view most people open first, and cannot be shared, defaulted or governed consistently.

P1

Configuration outranks operations in the IA

Four configuration tabs, two operational ones. Frequent tasks pay a navigation cost to subsidise infrequent ones — the opposite of what task-frequency analysis and every comparable product suggests.

P2

Two filter vocabularies and two entry points

“Manage view” (right panel, mixes filters with display options and type scope) versus “Advanced filters” (chip, count badge). The Calendar panel also conflates three distinct concepts — view-by-type, display toggles, and entity filters — in one undifferentiated stack.

P2

“Meetings” under-describes its contents

The vertical contains Event, Personal, Round robin and Class booking types. A 7am yoga class is not a meeting. The label narrows a general booking-calendar capability and will keep drifting as types are added.

P2

“Service calendars (v1)” leaks versioning into the UI

Migration state is exposed as a permanent navigation label. Users must understand the product’s release history to choose a destination.

P3

Undifferentiated “Busy” blocks

The grid is dominated by identical grey “Busy” blocks carrying only a time range. High ink, low information — and it pre-empts the visual budget the unified calendar will need for type and status encoding.

P3

Grouping is under-adopted

105 of 122 calendars are ungrouped. The mechanism exists but is not discovered or not worth the effort — consistent with the discoverability problem the live Pendo experiment is measuring.


04 · Mental models

Five roles, five different questions

AssumptionDerived from role incentives and the personas recorded in our product principles, not from interviews. These are the hypotheses usability testing should target first.

RolePrimary questionHow they think about the domainDefault they need
Front desk / receptionist “Who is coming in today, and who hasn’t confirmed?” Thinks in people and rooms on a timeline. Has almost no concept of “vertical” — a haircut and a room rental are both just things on today’s board. Cares intensely about exceptions: unconfirmed, no-show risk, double-booked. Calendar · Day · all types · grouped by staff
Business owner “How full is my week and is money coming in?” Thinks in utilisation and revenue. Genuinely wants the cross-vertical view — they are the only role for whom “all types together” maps to an existing mental object: the business. Calendar · Week · all types
Staff member / provider “What am I doing?” Thinks in my own day. Everything outside their own column is noise, occasionally sensitive. Vertical is irrelevant; the boundary that matters is mine vs not mine. Calendar · Day · scoped to self
Scheduler / ops admin “Which records need action?” Thinks in queues and batches. Wants a table, sorting, bulk operations. The only role that naturally reaches for List first. List · status-filtered · sorted by time
Agency admin “Is this sub-account configured correctly?” Thinks in configuration and templates across many accounts. Verticals are real objects to this role — they are the only role for whom vertical-first navigation matches intent. Configuration surfaces, not the calendar
The load-bearing conclusion

Four of five roles do not think in verticals at all. Only the agency admin does — and that role spends its time in configuration, not the calendar. This is the strongest argument for the unified calendar, and simultaneously the strongest argument against making vertical the headline filter control. The vertical is an artefact of how the business was configured, not how the day is experienced.

How each role reads the current vocabulary

  • “Appointments” — understood consistently by everyone. The safest anchor word in the product.
  • “Calendar” — read as the grid by operators, as a bookable configuration by admins. This split is the terminology defect made visible.
  • “Meetings” — read as a scheduled conversation with a person. Operators of class-based businesses will not look here for their yoga schedule.
  • “Services” — strong and unambiguous. Keep.
  • “Rentals” — strong and unambiguous. Keep.
  • “Views” — Assumptionnot a concept most SMB operators hold spontaneously. Slack ships an in-product explainer for exactly this reason; we will need one too.

05 · Entity model

One transaction object, four bookable subtypes

The data model is what makes the unified calendar either coherent or arbitrary. It is coherent.

Appointment — the single operational record; everything users "see" on a calendar │ id, start, end, timezone, status, notes, source, created_by │ recurrence?, buffer_before?, buffer_after? │ ├── bookable → Bookable (polymorphic — this is the keystone) ├── customer → Contact ├── assignees → Staff[] (round-robin, collective, class instructor) ├── location → Location? (fixed, virtual, or customer address) └── resources → Resource[] (room, chair, equipment, unit of stock) Bookable — abstract supertype. Four concrete subtypes today: ├── BookingCalendar 1:1 · round robin · collective · class · event ├── Service duration, price band, category, add-ons, staff eligibility ├── RentalListing rate unit (hour/day/week/month), stock count, category └── HomeService service area, travel time, on-site duration Shared objects — referenced by every Bookable subtype ├── Staff availability, eligibility, per-staff calendar connection ├── Location physical / virtual / customer-site ├── Resource finite capacity, bookable in parallel with staff ├── Category merchandising + filter grouping, per Bookable type └── Status appointment lifecycle state machine Platform objects — owned outside Calendars ├── Contact the customer record ├── User/Role permissions └── Connection OAuth identity (see §16 — this one splits)

Grey text is annotation, not structure.

Why this validates the unified calendar

Every vertical produces the same object: an Appointment with a bookable reference and a bookable.type. There is no schema difference between a haircut, a discovery call and a villa booking at the operational layer — only a difference in which subtype the bookable points at. A unified calendar is therefore not a merge of dissimilar things; it is a query over one table with the type constraint removed. That is a strong architectural signal that the unified view is the natural state and the current per-vertical scoping is the imposed one.

Actions, mapped by frequency

ActionFrequencyObjectBelongs in
See today’s / this week’s scheduleMany times dailyAppointmentAppointments › Calendar
Find a specific appointmentMany times dailyAppointmentAppointments (search, both views)
Create / reschedule / cancelMany times dailyAppointmentAppointments (either view)
Change status, chase confirmationDailyAppointmentAppointments › List (bulk) + inline
Block out timeDailyAppointment (busy)Appointments › Calendar
Adjust my availabilityWeeklyStaffStaff & resources › Staff
Create / edit a serviceMonthlyServiceServices
Create a booking calendarMonthlyBookingCalendarBooking calendars
Add a rental listingMonthlyRentalListingRentals
Configure staff / locations / resourcesSetup, rare afterSharedStaff & resources
Connect an external calendarOnce per staff memberConnectionSettings › Calendar sync (+ platform identity)
Define statusesOnce, rarely revisitedStatusSettings › Appointment statuses

The frequency column is the IA. Everything in rows 1–5 must be reachable without leaving one workspace; everything from row 6 down can afford a click.


06 · Current IA

The tree as it stands

Calendars (platform left nav) ├── Calendar view ← presentation of Appointments │ ├── [Services ▾] vertical scope, single-select │ ├── [Today] [date nav] [Week view ▾] │ ├── [Manage view] → panel: │ │ ├── View by type: All / Appointments / Blocked slots ← a filter │ │ ├── Show buffer time ← a display option │ │ ├── Unified service view ← a display option │ │ ├── All locations ▾ ← a filter │ │ └── Filters: Staff / Services / Categories ← filters │ └── [+ New] │ ├── Appointment list view ← same Appointments, as a table │ ├── [Services ▾] same vertical scope, different placement │ ├── Upcoming / Cancelled / All / New List / + Smart list │ ├── [Advanced filters (1)] [Sort by (1)] [Search] [Customize list] [Manage columns] │ └── [+ New appointment] │ └── Calendar settings ← NOT a view. Switches to the Settings app shell. │ Settings › Business Services › Calendars ├── Meetings │ ├── Calendars (122) Groups · types: Event/Personal/Round robin/Class booking │ ├── Service calendars (v1) ← versioning leaked into navigation │ ├── Preferences │ └── My availability ├── Services (New) │ ├── Services (15 categories) · Staff · Resources · Add-ons │ ├── Locations · Booking pages · Global settings │ └── [View booking page] [Onboarding – start guide] ├── Rentals (New) │ └── Listings (3 categories) · Global settings └── Connections └── per-staff: Calendars / Video conferencing / Google organic booking └── Connected calendars · Linked calendar · Conflict calendars

Red marks a structural defect. Underlined Staff, Resources and Locations inside Services are the duplication risk described in P1.

What the tree tells us

  • Depth to a daily task is 1–2. Good, and worth protecting.
  • Depth to a configuration task is 4–5, across a shell boundary: Calendars → Calendar settings → Services → Staff. Each hop changes the navigation frame’s meaning.
  • Four concepts are interleaved in one panel. Manage view mixes filters, display options and type scope with no visual separation — the clearest single instance of the concept-mixing this project set out to fix.
  • The Settings shell hosts a second, contradictory model of verticals. Same nouns, different grammar.

07 · Mobbin evidence board

Design evidence, grouped by the decision it informs

Six searches across web, ~50 screens and 4 multi-step flows reviewed visually. Product names link to the source screen on Mobbin. Organised by the question each reference answers, not by product.

A · Navigation & IA

ProductPatternObservation & why it mattersBorrowAvoid
Square Appointments product nav: Overview / Calendar / Waitlist / Online booking / Library / Team Members / Settings The closest real-world analogue to our product. Note what is absent: no list-view sibling and no verticals. Navigation is organised by object class (Library = catalogue, Team Members = staff), with the operational calendar first. Operations-first ordering; object-class grouping; a single Calendar destination. “Library” as a label — opaque for a scheduling context.
GoDaddy Appointments → Overview / Calendar / Services / Categories / Customers / Coupons / Staff / Settings A literal product→subproduct→context tree, and again no verticals: catalogue objects (Services, Categories) sit as peers of Staff. Confirms shared objects want to be top-level, not nested inside a vertical. Flat sibling treatment for Services and Staff. Categories as a separate nav item — it is a property of Services, not a peer.
Wix Booking Calendar → Calendar + Booking List + Work Schedule as nav siblings The counter-example, and the most useful one: Wix made exactly our mistake. Two nav destinations over one dataset, plus a separate “Filter by” panel listing Services / Staff / Location / Session availability. The filter panel has to be rebuilt on both sides. The filter dimension list — it is the right set for this domain. Calendar and List as nav siblings. Direct evidence the pattern does not resolve itself at scale.
Squarespace / Acuity Overview: Calendar / Clients / Invoices / Reports, then Client scheduling page, then Business settings Sidebar is grouped by audience: what I operate, what my client sees, how the business is configured. An elegant alternative axis to object-class grouping. Grouping configuration by audience — useful for the Booking pages surface. Splitting Availability away from Staff into “Business settings”.

B · Calendar

ProductPatternObservation & why it mattersBorrowAvoid
Fresha Staff-column day grid; stacked side panels for contact then service selection Staff-as-columns is the default in real service businesses, not a power feature. Service picker is grouped by category with duration and price on every row — the operator books by category → service, never by scrolling a flat list. Staff columns as a first-class calendar grouping; category-grouped service picker with duration + price. Stacked overlay panels — three layers deep loses the calendar context.
Front “Week ▾” menu containing granularity and “Show weekends” Granularity and closely-related display toggles share one control. Reduces toolbar count without mixing filters into it. Fold weekend/hours display into the granularity menu. Nothing significant.
Clockwise Explicit conflict surfacing: “Conflicts · 1 attendee”, “Outside of working hours”, “Inconveniences” Conflicts are named and quantified rather than implied by overlapping blocks. Directly applicable to our double-booking and buffer edge cases. Named, counted conflict states in the appointment editor. “Inconveniences” — invented vocabulary users must learn.
Motion Calendar grid and agenda list on screen simultaneously A serious alternative to view switching: show both. Works because the list is a narrow ranked agenda, not a full table. Worth testing for the front-desk role, who needs “the board” and “what’s next” at once. An optional agenda rail alongside the grid. Treating it as a replacement for the List view — it cannot do bulk work.

C · Unified & multi-entity views

ProductPatternObservation & why it mattersBorrowAvoid
HoneyBook Sidebar in two groups: entity types (Booked Projects, Meetings, Payments, Tentative Projects) then Session Type The single most relevant reference in this board. A genuinely multi-entity calendar showing projects, meetings and payments together, with type and sub-type as two separate filter dimensions. Critically, tentative bookings use a hatched fill while confirmed use solid — differentiation that survives greyscale and colour-blindness. Two-tier type filtering; hatched vs solid fill to encode certainty; a “Go to scheduler” link from filter to configuration. Mixing Payments (not a time-bound booking) into the same list as bookings without visual separation.
Amie Top-bar pill “7 calendars” with colour-dot cluster, opening a searchable list of 11 Visibility controls live in a toolbar pill that states the count, not a permanent sidebar — reclaiming ~200px for the grid. The list has its own search, so it scales past a dozen entries. Disconnected sources are labelled inline. Count-bearing toolbar pill; searchable option list; inline disconnected state. Colour dots as the only type signal in the pill.
Confluence Type checkboxes each carrying a distinct icon (Birthdays, Events, Leave, Travel) Types are identified by glyph, not hue. Icons scale to arbitrarily many types; a distinguishable colour palette does not survive past roughly seven. A per-type glyph as the primary type signal. Nothing significant.
Hootsuite Right-hand Filters drawer with drill-in breadcrumb, search, checkboxes, Clear / Apply footer Structurally the closest thing to our existing “Manage view” panel. The drill-in keeps a long dimension list navigable. Note the explicit Apply — a deliberate choice to batch expensive queries. Drill-in for long dimensions; keep our panel position. Clear/Apply for cheap filters — live-apply is better; reserve Apply for costly cross-vertical queries.

D · Saved views

ProductPatternObservation & why it mattersBorrowAvoid
Height “This view has unsaved changes — Saving will update the view for everyone.”  Revert · Save as… · Save Directly solves our biggest saved-view problem. Ad-hoc exploration and durable saved state coexist because dirty state is explicit and offers three exits. Crucially the warning names the blast radius: “for everyone”. The entire dirty-state contract, including the shared-view warning copy. Nothing significant.
Linear A Views index page split into Personal views · Only visible to you and Workspace, with an Owner column Answers the governance question cleanly: two scopes, visible ownership, and a place to manage views that is not a dropdown. Matters for us because org-level views need an admin surface. Personal / workspace split with explicit ownership. A separate Views page as the only entry — too far from the work for SMB operators.
Slack Lists In-menu explainer: “Views are saved sets of filters, sorting and layout changes…” + per-view item counts Slack judged the concept unfamiliar enough to teach in situ. Given our live discoverability experiment and non-technical operator base, the same is very likely true here. Item counts make each view’s value legible before selection. The dismissible in-menu explainer and result counts per view. Nothing significant.
ClickUp Customize view panel: fields, filter, group, sort + Autosave for me · Pin · Private · Protect · Set as default · Copy link · Sharing The complete governance surface, and simultaneously a warning: eleven view tabs plus “1 more…” in the header. Unlimited view types produce a tab bar nobody can scan. Set as default, Copy link to view, Protect view. Unbounded view tabs. Cap our view types at two (Calendar, List).
GitHub Projects Saved views as tabs with an unsaved-change dot; Discard / Save in the toolbar Tabs work at 3–4 views and degrade past that. The per-tab dirty dot is a cheap, legible signal. The dirty dot. Views as tabs — our users will create many more than four.
Todoist One “Display: 3” button containing Layout, Sort, Filter + “Save for everyone” / “Reset all” The count badge tells you how far you have drifted from default without opening anything. Extremely economical for a dense toolbar. Numeric drift badge on the display control; a single Reset. Burying Filter inside a “Display” menu — conflates §6’s two concepts.
Motion “All Tasks” + List / Kanban / Gantt tabs + “Default View” chip + independent Group / Workspace / Filters chips Cleanest separation in the board: dataset title, then view type, then named saved view, then filter chips — four distinct concepts, four distinct control shapes. This exact four-part header grammar. Nothing significant.
Airtable Left panel of saved views + “Find a view” search + view-type picker on create Views in a searchable panel scale to dozens where tabs and dropdowns fail. View type is chosen at creation, which is precisely the model I recommend in §13. Searchable view list; type chosen at creation. A permanent left view panel — too much chrome for our density.

E · Filtering

ProductPatternObservation & why it mattersBorrowAvoid
Airtable Toolbar chip reads “Filtered by Duration”; panel has Where / and rows, Add condition, Add condition group, plus a natural-language input The chip names the active filter rather than showing a count, so filter state is legible without opening anything — a direct fix for silently-filtered views, which is how users lose trust in a calendar. Name the active filter in the chip. Condition groups for power users. Natural-language filter input as the primary affordance — unpredictable for a booking system of record.
Flodesk “Matching all | any of these” segmented control per group, And/Or between groups Boolean logic expressed in plain words at the group level instead of per-row operators. Far more legible for non-technical operators. The all/any segmented control. Nested groups beyond one level.
Vercel Named rule (Name + Description) wrapping If/Then conditions A filter set becomes a durable, describable object once it has a name and description — the conceptual bridge from “filter” to “Saved View”. Optional description on a Saved View. Nothing significant.
beehiiv Group-level “All (AND) / At least one (OR)” + Delete All Conditions Confirms the plain-language boolean convention and the need for a single escape hatch out of a complex filter. “Clear all” always visible — which our List view already has. Nothing significant.

F · Tables

ProductPatternObservation & why it mattersBorrowAvoid
Zapier Tables “1 hidden field” chip and a “Save as View” action appearing beside it in the toolbar The save-as-view affordance surfaces next to the evidence that state has changed. This is how a user discovers Saved Views without being taught — the highest-leverage detail in this board for our discoverability problem. Contextual “Save as view” adjacent to the dirty-state chip. Nothing significant.
Vanta One popover with Density (Dense / Regular / Comfortable) + column toggles + Reset to default Row density belongs with column config, not with filters. Density matters a great deal for schedulers scanning hundreds of rows. Density control; Reset to default. Nothing significant.
Remote “Edit table” panel split into Shown in table / Hidden, with column search Two explicit buckets beat a long checkbox list once column count passes ~15. Ours will, given appointment metadata plus custom fields. Two-bucket column manager with search. Nothing significant.
Deel “Customize columns” modal, “6 / 21 selected”, first column locked and greyed Locking the identity column prevents users creating an unreadable table. Our equivalent is the appointment title / customer column. Lock the identity column; show selected/total. A modal — a popover keeps the table visible while adjusting.
Dovetail Fields popover with drag handles and per-field toggles Reorder and visibility in one control; drag handles read as reorderable without instruction. Drag-to-reorder in the same popover. Nothing significant.

G · Configuration & settings

ProductPatternObservation & why it mattersBorrowAvoid
Plane Settings with scope tabs — Account / Workspace / Projects — then a sidebar grouped Administration / Features / Developer. “Project states” sits under Features. The answer to our Settings scope question. Scope is made explicit and selectable rather than inferred from location. And “Project states” — a per-object-type state machine configured at workspace level — is the exact analogue of our appointment Statuses, and it lives with the product, not the platform. Explicit scope tabs; Administration / Features grouping; states-with-the-product precedent. “Developer” grouping — irrelevant to our audience.
Customer.io Banner: “Looking for settings for your entire account? Go to your account settings.” Then product-scoped settings grouped by channel. Resolves the Connections dilemma without forcing a single home. When a setting genuinely spans scopes, a cross-scope pointer beats an arbitrary placement decision. The cross-scope wayfinding banner, verbatim in spirit. Nothing significant.
Render Grouped sidebar + right-hand in-page anchor nav for a long settings page Long settings pages need intra-page navigation. Our Booking pages and Notifications surfaces will be long. In-page anchor rail on long settings pages. Nothing significant.
Slite Sidebar groups: Account / Workspace / Billing / Other Scope-as-group-heading is a cheaper version of Plane’s tabs. Viable fallback if tabs are too heavy. Scope group headings. An “Other” group — a bucket for unresolved IA decisions.

H · Create flows

ProductPatternObservation & why it mattersBorrowAvoid
Square “Create appointment” panel whose first field is “Event type”, then client, then date/time, then a services line-item table with duration, amount and Add a service The template for creating from a unified calendar. Type is resolved by an explicit first field, then the form adapts. The line-item table also handles multi-service appointments, which our Services vertical needs. Type-first create panel; services as editable line items; right-rail client context (notes, cards, history). Nothing significant.
Time2book Single “Add to schedule” drawer with a Service dropdown grouped by type — Appointments vs Classes An elegant alternative to Square: skip the type field entirely and let the grouped picker carry the type. One fewer decision, and the grouping teaches the type taxonomy implicitly. Type-grouped bookable picker — my recommended primary path (§17, Flow 7). Nothing significant.
Squarespace / Acuity “ADD NEW ▾” split button (New Appointment / New Order); “BLOCK OFF TIME” as a separate sibling action; header reads “Week of Oct 1 · 0 appointments” Blocking time is promoted to a peer of creating a booking — correct, because operators do it many times a day. The header states the result count for the current range, which quietly confirms filters are working. Block time as a peer action; result count in the header. An all-caps split button as the primary create control — hides the common case.
GoDaddy Add Booking as a full page: Service, Coupons, Staff, Date/Time, Customer Info, Service Location A full page for a high-frequency action costs the operator their calendar context on every booking. The field inventory. A full page for appointment creation. Use a side panel.

I · Multi-type visual encoding

Only two references in this board differentiate entity types without relying on hue, and both matter disproportionately: HoneyBook’s hatched-vs-solid fill for tentative-vs-confirmed, and Confluence’s per-type icons. Everything else in the board — Amie, Front, Motion, Rise, HoneyBook’s own type dots — encodes with colour alone. HypothesisThis is a genuine gap in the market rather than a solved problem, which means we should not expect to find a pattern to copy and should design the encoding system deliberately (§12).

J · What the board does not support

  • No product in this board exposes business verticals as top-level navigation. Square, GoDaddy, Acuity, Wix and Time2book all organise by object class or audience. That is a meaningful absence given how many multi-offering scheduling products were sampled.
  • No product ships more than three primary view types over a calendar dataset. ClickUp’s eleven tabs are the visible failure mode.
  • Only one product (Wix) splits Calendar and List into nav siblings — and it duplicates its filter panel as a result.

08 · Pattern synthesis

What the evidence converges on

Six conclusions

  1. The four-part header grammar is settled. Dataset title → view type → named saved view → filter chips, each with a distinct control shape. Motion, Height, GitHub, Airtable and Todoist all land within a small variation of this. We should adopt it rather than invent.
  2. Dirty state is the mechanism that makes saved views usable. Without it, users either fear touching a saved view or accidentally overwrite a shared one. Height’s three-exit contract (Revert / Save as / Save) plus Zapier’s contextual placement is the complete solution.
  3. Filter legibility beats filter power. Airtable naming the active filter in the chip is worth more than condition groups. For a system of record, a silently-filtered calendar is a trust failure — the user concludes appointments have vanished.
  4. Type belongs in a toolbar pill, not a sidebar. Amie’s count-bearing pill preserves grid width, scales via search, and states its own state. Our existing right-hand Manage view panel should keep the long-tail dimensions and hand the type facet to the toolbar.
  5. Two view types, hard cap. Calendar and List. ClickUp demonstrates the failure of unbounded types; nothing in the board suggests a third earns its place for scheduling.
  6. Creation resolves type through the bookable, not through a mode switch. Time2book’s grouped picker is better than Square’s explicit type field because it removes a decision while still teaching the taxonomy.

Design principles

Eight principles to govern the redesign. Where they refine the brief’s starting hypothesis, I say so.

Principle 01

Navigation names places; everything else names lenses

A destination is somewhere you can bookmark and describe to a colleague. A view type, a filter, a date range and a saved view are all lenses onto a place. The brief’s starting hypothesis holds — with one refinement: display options are a fifth category, and they belong with the view type, not with filters.

Principle 02

One dataset, one workspace

Any concept that applies to the appointment dataset — search, filters, saved views, status, bulk actions, permissions — is built once and works in every presentation. If a capability exists in only one view, that is a bug, not a roadmap item.

Principle 03

Frequency earns proximity

Daily tasks get zero-click or one-click access; monthly tasks can afford two; setup tasks can afford three. The current 4:2 split favouring configuration inverts this and is the reason configuration feels like the product’s centre of gravity.

Principle 04

Shared objects are configured once

Staff, Locations and Resources are real-world entities, not per-vertical settings. Every vertical references them. This is the principle that most constrains the IA and the one most likely to be violated under delivery pressure.

Principle 05

Never encode meaning in colour alone

Every distinction an operator must make under time pressure — type, status, certainty — must survive greyscale, colour-blindness and a 40%-brightness laptop at a reception desk. Glyph, border style and fill pattern carry the load; colour amplifies.

Principle 06

State is always legible without opening a panel

If a view is filtered, the toolbar says what by. If a saved view has drifted, the toolbar says so. If a range is empty because of a filter rather than because nothing is booked, the empty state says which. Hidden state destroys trust in a system of record.

Principle 07

The default must be correct for the single-vertical account

AssumptionMost accounts run one vertical. The unified calendar must therefore cost them nothing — no extra control to dismiss, no type column consuming space, no empty legend. Multi-vertical capability should be invisible until a second vertical exists.

Principle 08

Preserve domain-specific controls that generic SaaS lacks

Buffer-time visibility, blocked slots, unified service view, capacity and look-busy have no analogue in project-management tools. Their absence from the Mobbin board is not evidence they are wrong — it is evidence our domain is more specific. Do not simplify them away in pursuit of a familiar pattern.


09 · IA alternatives

Four models, evaluated

Option D emerged from the entity model and is not in the brief. It is the one I recommend.

Option A — Appointments-first, verticals as siblings (the brief’s hypothesis)

Calendars ├── Appointments → Calendar | List ├── Meetings ├── Services ├── Field Services ├── Rentals └── Settings (Connections, Statuses)

Mental model: “My schedule, then the things I offer, then how it all works.”

  • Strengths: Correctly separates operations from configuration. Direct access to each vertical. Minimal conceptual leap from today’s Settings tabs, so migration reads as a promotion rather than a restructure.
  • Weaknesses: Nav grows linearly with verticals. Critically, it provides no home for shared objects — Staff, Locations and Resources must either live inside each vertical (duplication) or inside Settings (wrong: they are operational catalogue data, edited weekly, not system configuration). This is the flaw that disqualifies it as-is.
  • Scalability: Poor past ~5 verticals. Discoverability: Good. Ops efficiency: Good. Config efficiency: Poor — configuring Jane Doe means visiting N verticals. Migration: Low. Verdict: Right instinct, incomplete object model.

Option B — Vertical-first

Calendars ├── Meetings → Calendar | List | Setup ├── Services → Calendar | List | Setup ├── Rentals → Calendar | List | Setup └── Settings

Mental model: “Pick the part of my business, then work inside it.”

  • Strengths: Each vertical is self-contained and independently shippable — genuinely attractive from a delivery and team-ownership perspective. Matches the agency admin’s mental model. Permissions map cleanly onto verticals.
  • Weaknesses: Structurally forecloses the unified calendar — the primary business question becomes unanswerable by design. Duplicates Calendar, List, filters and saved views N times. Four of five roles do not think in verticals. Staff duplication is not merely likely but mandatory.
  • Scalability: Poor. Discoverability: Poor — “where do I see my day?” has N answers. Ops efficiency: Poor for multi-vertical, fine for single. Config efficiency: Good within a vertical, bad across. Migration: Lowest — it is roughly today’s model. Verdict: Reject. Optimises for the org chart, not the user.

Option C — Hybrid: Appointments + a single catalogue node

Calendars ├── Appointments → Calendar | List ├── Catalogue → Booking calendars | Services | Rentals | Field services ├── Staff & resources └── Settings

Mental model: “My schedule, my offerings, my people, my rules.”

  • Strengths: Nav stays at four items regardless of vertical count — handles the 10-vertical case cleanly. Shared objects get a proper home. Conceptually the tidiest tree.
  • Weaknesses: Adds a click to every configuration task and introduces an abstraction (“Catalogue”) that no user asked for. Hypothesis“Catalogue” is retail vocabulary that a plumber or a law firm will not adopt; the Mobbin board offers no support for it in a scheduling context (Square’s comparable “Library” is equally opaque). A single-vertical salon now navigates Catalogue → Services instead of Services.
  • Scalability: Excellent. Discoverability: Moderate — depends entirely on the parent label landing. Ops efficiency: Good. Config efficiency: Good. Migration: Moderate. Verdict: Structurally strong, linguistically weak.

Option D — Appointments-first with adaptive verticals and extracted shared objects Recommended

Calendars ├── Appointments operational hub — Calendar | List over one dataset │ ├── Booking calendars shown only if the vertical is enabled ├── Services shown only if enabled ├── Rentals shown only if enabled ├── Field services shown only if enabled ├── + Add booking type discovery surface for verticals not yet enabled │ ├── Staff & resources Staff · Locations · Resources — configured ONCE └── Settings Booking pages · Statuses · Calendar sync · Notifications

Mental model: identical to Option A — which is the point. It preserves the brief’s instinct while fixing its two structural gaps.

Two changes do the work:

  1. Verticals render conditionally. A salon sees Appointments / Services / Staff & resources / Settings — four items. An agency sees Appointments / Booking calendars / Staff & resources / Settings. Nobody sees a vertical they do not use. This dissolves Option A’s scalability problem without Option C’s abstraction, because nav length now tracks what the account actually does rather than what the product supports.
  2. Shared objects are extracted into one destination that every vertical references.

The + Add booking type row is a deliberate second job: it converts navigation into the discoverability surface our live Pendo experiment says we need. A salon that has never noticed Rentals exists now encounters it in the one place it is guaranteed to look.

  • Scalability: Excellent — nav length is a function of account configuration, not product surface area. Discoverability: Excellent, and actively improves it. Ops efficiency: Excellent. Config efficiency: Excellent — shared objects configured once. Migration: Moderate — requires the shared-object extraction, which is the real engineering cost. Future verticals: Adding one is a feature flag plus a catalogue screen, with no impact on Appointments.
Why D beats C on the naming question

Option C must find a word that covers booking calendars, services, rentals and field services. Every candidate is either jargon (Catalogue, Library, Offerings) or a category error (Products — these are time, not goods). Option D avoids the problem entirely: it never needs a collective noun, because it never groups them. The cost C was paying to solve scalability, D solves with conditional rendering instead.


10 · Recommended IA

The tree, and the naming decisions behind it

Calendars PRODUCT — keep this name (see below) │ ├── Appointments SUBPRODUCT — default destination │ ├── Calendar view type │ └── List view type │ Saved Views · Filters · Search · Date range — all shared across both │ ├── Booking calendars CONTEXT — was "Meetings" │ ├── All booking calendars (122) · groups · types │ └── Booking pages customer-facing surface for this type │ ├── Services │ ├── Services (categories, add-ons, pricing, duration) │ └── Booking pages │ ├── Rentals │ ├── Listings (categories, rate units, stock) │ └── Booking pages │ ├── Field services future │ ├── Services (+ service areas, travel time) │ └── Booking pages │ ├── + Add booking type enables a vertical; discovery surface │ ├── Staff & resources SHARED — referenced by every vertical │ ├── Staff availability · eligibility · per-staff calendar sync │ ├── Locations physical · virtual · customer-site │ └── Resources rooms · chairs · equipment │ └── Settings calendar-scoped configuration only ├── Appointment statuses ├── Calendar sync was "Connections" — split, see §16 ├── Notifications & reminders └── Preferences timezone, week start, default view → Account-level integrations live in platform Settings (cross-link)

Naming decisions

TodayRecommendedReasoning
Calendars (product)Calendars — unchangedStrictly, “Scheduling” is the more accurate parent now that Rentals exists. But the four-way collision documented in §2 is caused by the children, not the parent — and renaming a nav item used by 1.5m monthly actives, while a discoverability experiment is live, spends a large amount of user relearning to fix a problem that renaming the children solves for free. Trade-offWe accept a slightly narrow parent name in exchange for zero relearning cost. Revisit only if a genuinely non-time-based vertical arrives.
MeetingsBooking calendarsThe objects inside are already called calendars by users (122 of them) and by the product. Adding the qualifier Booking disambiguates from the grid view and from connected external calendars at near-zero relearning cost, while dropping “Meetings” stops mis-describing Class booking and Event types. I deliberately reject Event types (Calendly/Cal.com’s term) — it is more precise but would invalidate every support document and every user’s vocabulary for a marginal gain.
Calendar view / Appointment list viewCalendar / List, as view types inside AppointmentsThey stop being destinations, so they stop needing the word “view” in their names. The container supplies the noun.
Service calendars (v1)Removed from navigationMigration state is not a place. Surface it as a one-time banner on the Booking calendars list for affected accounts, with a migrate action.
ConnectionsCalendar sync (in Settings)Describes what it does. “Connections” is generic enough to imply platform integrations, which is exactly the confusion §16 resolves.
Manage view / Advanced filtersFilters (one control, both views) + DisplayOne vocabulary, and the split finally separates “what subset” from “how shown”.
Smart ListsSaved Views“Smart” promises automation the feature does not provide, and “List” binds it to one presentation — the precise defect we are fixing. See §21 for how existing Smart Lists migrate without disruption.
—Staff & resourcesNew destination. Named for its contents rather than abstracted (“Team” excludes rooms and equipment; “Resources” alone reads as inventory and hides staff).

How this maps onto the platform left-nav prototype

The prototype’s product → subproduct → context model fits without strain. Calendars is a product row in the main nav; its flyout contains the eight rows above. Two specific recommendations for the prototype’s own mechanisms:

  • Appointments should be dockable. The prototype’s floating dock band holds pinned destinations (Conversations, Contacts, Workflows, Invoices, Dashboard). Appointments is a many-times-daily destination and belongs there for scheduling-led accounts; the verticals belong in the flyout. This uses the prototype’s existing affordance rather than asking it for new capability.
  • The flyout must render conditionally too. If nav hides disabled verticals, the flyout must respect the same rule, or the two surfaces disagree about what exists.
  • Trade-offThe prototype spends 56px on the account rail plus 272px on the nav — 328px of chrome before content. A staff-column calendar for six staff at a usable column width needs roughly 900px. On a 1280px laptop that is tight but workable with the nav collapsed to 64px; the calendar should auto-collapse the nav on first entry at viewport widths under 1400px, which the prototype already supports as a state.

11 · Appointments architecture

One workspace, one header grammar

The brief proposes a specific header. It is close to right; I would change two things and add one.

Screen 2 — Appointments › Calendar · recommended header
Appointments
CalendarList Front desk  view 142 appointments
Today ‹Aug 23 – 29, 2026› Week ▾ Staff ▾ All types 4 Filters ▾ Display ▾ + New appointment ▾

What I changed from the brief’s proposal

  1. Two rows, not one. Row 1 is identity and scope — what dataset, which presentation, which saved view, how many results. Row 2 is manipulation — when, what subset, how displayed, create. This matters because row 1 answers “where am I” and row 2 answers “what am I doing”; collapsing them into one line is why the current toolbars read as an undifferentiated pile of chips. Trade-offCosts ~40px of vertical space. On a day grid that is roughly one 30-minute row — acceptable, and recoverable by collapsing row 2 on scroll.
  2. “Views ▾” becomes a named chip in row 1, not a dropdown in row 2. The active saved view is identity, not a control. Motion does exactly this and it reads unambiguously: the chip states which lens you are looking through, and clicking it opens the picker.
  3. Added a result count. “142 appointments” for the current range and filters, borrowed from Acuity. This is the cheapest possible defence against the trust failure in Principle 06 — an operator who sees an unexpectedly empty week immediately knows whether it is empty or filtered.
  4. “Staff” promoted out of Filters. Four of five roles think staff-first. Making it a peer chip rather than a row inside a panel reflects actual usage frequency; it remains available inside Filters as well.
  5. “+ New appointment” is a split button. Primary action creates an appointment; the caret offers Block time and Add to waitlist. Acuity promotes blocking to a peer for good reason — operators do it many times daily.

Full component inventory

ElementRowBehaviour
Title1Static “Appointments”. Never changes with filters — it names the place.
View switcher1Segmented, two options, hard cap. Preserves date range, filters and saved view across the switch.
Saved view chip1Shows active view name; a dot appears when modified. Click opens picker; the dot area exposes Revert / Save / Save as.
Result count1Live count for range + filters. Clicking it opens Filters, so it doubles as an explanation affordance.
Today2Returns range to now at current granularity. Disabled when already showing today.
Date range2Prev / label / next. Label is clickable and opens a month picker with a range option.
Granularity2Day / Week / Month / Schedule (agenda). Also holds Show weekends and Business hours only, per Front.
Staff2Multi-select with search and “Only me” shortcut. Named in chip when 1–2 selected, counted beyond that.
Types2Multi-select over enabled verticals. Hidden entirely when only one vertical is enabled (Principle 07).
Filters2Everything else: service, category, location, resource, status, source, customer tag, and the condition builder. Chip names the active filter when there is one, counts beyond that.
Display2Calendar: colour-by, group-by, buffer visibility, unified service view, slot height, cancelled visibility. List: columns, density, grouping. Carries a drift count badge, per Todoist.
Search2Icon-only, expands. Searches customer, title, notes, confirmation number. Scoped to current filters with an “also search all appointments” escape.
Create2Split: New appointment / Block time / Add to waitlist.
Bulk actions—Appears as a floating bar on selection in List; in Calendar via marquee or shift-click. Confirm, cancel, reschedule, reassign staff, notify, export.

12 · Unified calendar

Yes — but type is not the headline

1. Is a unified calendar good UX?

Yes. Three independent lines of evidence agree. The entity model (§5) shows all verticals produce the identical object, so unification is a query, not a merge. The mental models (§4) show four of five roles have no concept of vertical, so per-vertical scoping imposes a boundary users do not hold. And the product itself already ships a single-select vertical scope in both toolbars — the mechanism exists; we are removing a constraint, not adding a feature.

The strongest argument, though, is negative: cross-vertical conflicts are currently invisible. If Jane is booked for a Service at 2pm and a Meeting at 2:15pm, no current screen can show both. That is not a convenience gap, it is a correctness gap in a system of record — and it gets worse with every vertical added.

2–3. Mental model, and when it breaks

HypothesisThe model users hold is “my business’s time” — a single physical reality of who is busy when. The vertical is invisible in that model, which is why unification feels natural rather than clever.

It breaks in three specific situations, each of which needs a designed answer rather than a warning:

  • Different time granularities collide. A 30-minute haircut and a 4-day villa rental cannot share a week grid legibly. Answer: multi-day bookings render in the all-day band at the top, never as tall blocks in the time grid — the same treatment calendars already give all-day events. Rentals therefore read as a horizontal bar spanning days, which is also how the mental model works.
  • Different capacity semantics collide. A class holds 12; a room holds 1. Answer: capacity renders as a fraction on the block (8/12), and the block is only visually “full” when capacity is reached — not when one booking exists.
  • Volume asymmetry. A business with 400 service appointments and 3 rentals per week will find rentals lost in the noise. Answer: this is the case for saved views, not for splitting the calendar — “Rentals only” is one click, and the type chip states that 3 of 403 are visible.

4. Visual differentiation — the encoding system

The Mobbin board (§7I) shows only two products differentiate types without hue. So this must be designed rather than borrowed. Four channels, in priority order:

ChannelEncodesRationale
GlyphType — a 12px icon leading the block titleScales to arbitrarily many types; survives greyscale; already how Confluence solves it. Booking calendars = users, Services = sparkle, Rentals = package, Field services = truck.
ColourStatus by default, user-switchableThe contentious call, argued below.
Border & fillCertainty — solid = confirmed, dashed = pending, hatched = blocked/busy, struck = cancelledDirectly borrowed from HoneyBook. Works at 11px, in greyscale, and at a glance from across a reception desk.
PositionStaff or resource via column groupingAlready the domain default (Fresha, Square). The most information-dense channel available and it costs no ink.
Recommendation: colour should encode status, not type

The intuitive choice is colour-by-type, and I would argue against it as the default. An operator’s urgent question during a shift is “what needs action?” — not “which vertical is this?”. Type is stable, low-cardinality and already carried by the glyph; status changes continuously through the day and is the only attribute that can require intervention. If colour is spent on type, status becomes the weakest signal on the busiest screen. Note the current product already agrees: the list view renders “New (action required)” in red outline precisely because status is the alarm.

Trade-offMulti-vertical accounts lose at-a-glance type separation by hue. Mitigated by the glyph, and fully recoverable via Display › Colour by — Type / Status / Staff / Booking calendar. Shipping the choice resolves the debate empirically per account instead of by argument, and both Airtable and ClickUp establish “colour by” as an expected control. Default: Status.

Type & status encoding — week grid, all four verticals, colour‑by‑status
24 Mon
25 Tue
26 Wed
27 Thu
all‑day
▣Coral Bay Villa · M. OkonjoRental · 3 nights · 1/1 unit
9 AM
◎Haircut — J. DoeService · 45m
●Discovery callBooking calendar · unconfirmed
◻Busy — externalGoogle · 9:00–10:30
◎Hair spa — A. RaoService · 1h
11 AM
●Whole Facility — N. KumariAction required · 5h
▲Boiler service — 14 Elm StField service · +25m travel
◎Yoga 11amClass · 8/12 booked
◎Gel overlaysCancelled
Confirmed Pending Action required Busy / blocked Cancelled ● Booking calendar   ◎ Service   ▣ Rental   ▲ Field service

Glyphs stand in for lucide icons (users / sparkles / package / truck). Note the rental in the all-day band, capacity as a fraction on the class, travel time on the home service, and that every distinction survives greyscale.

5. Which dimensions should filter

DimensionPlacementReasoning
StaffToolbar chipHighest-frequency dimension; four of five roles think this way first.
Type / verticalToolbar chip, hidden when 1 verticalNeeded for the unified view to be controllable, but not the marquee control.
StatusToolbar chip in List, Filters in CalendarList is the queue-management surface where status filtering is the primary job (today’s Upcoming / Cancelled / All tabs are exactly this).
LocationToolbar chip if >1 location, else FiltersAdaptive: multi-location businesses filter by it constantly; single-location ones never.
Service / listing / calendarFilters panelHigh cardinality (15 categories, 122 calendars) — needs search, not a chip.
CategoryFilters panelA convenience roll-up over the above.
ResourceFilters panel, or group-by in DisplayMore useful as a grouping axis than a filter for room-based businesses.
SourceFilters panelWe instrument 10+ booking sources; this is a genuinely valuable filter nobody currently exposes.
Customer / tagFilters panelEnables the “VIP customers” saved view the brief anticipates.

6–8. Default, persistence, saveability

  • Should “All” be the default? Yes. For single-vertical accounts — Assumptionthe majority — All and “Services only” are the same set, so All is free. For multi-vertical accounts it is the only way the unified view is ever discovered, and it is the only default that makes cross-vertical conflicts visible. Choosing a single vertical as default would reproduce today’s blind spot.
  • Should the selection persist? Yes, but as a personal preference, not as the default. Last-used type selection persists per user per device. Crucially, “Today” and the saved-view chip both reset it — otherwise a user who once filtered to Rentals silently loses appointments forever, which is the single most likely support-ticket generator in this design.
  • Should configurations be saveable as Views? Yes — that is precisely what §13 is for. Type selection is one facet stored in a Saved View like any other.

10–11. Scale and permissions

Thousands of appointments. Density is a rendering problem before it is an IA problem. Mitigations, in order: the range window caps the query (a week is a week regardless of total volume); overlapping blocks collapse to +4 more beyond three per slot; Month granularity shows counts per day rather than blocks; the List view is virtualised and paginated. AssumptionThe genuine risk is not the grid but the query — a cross-vertical union across four tables with staff, location and status predicates needs an index review before “All” ships as default. This is the main engineering dependency of this proposal.

Permissions. The unified calendar makes a latent problem urgent: it will surface appointments from verticals a user may not be entitled to see. Rules: filter at the query layer, never in the client. A staff member restricted to their own appointments sees only their column, and the type chip shows only types they can access. Where a slot is occupied by an appointment the viewer cannot see, render an opaque busy block with no detail — availability is not confidential, content is. This preserves correct booking behaviour without leaking data, and matches how Google Calendar handles free/busy.

12. Smaller screens

FactPendo shows active iOS and Android usage, so this is not hypothetical. Below 1024px: granularity locks to Day or Schedule (agenda list); staff columns become a horizontally-swipeable set with a sticky staff header; the two toolbar rows collapse into one row plus a single “Filters” sheet containing type, staff and everything else; the saved-view chip stays visible because it is the cheapest way to reach a useful subset on a small screen. Hover-dependent affordances — the dock lift, the edit-nav reveal, tooltips — all need tap equivalents.

13. Creating from the unified calendar

Click-drag a range, or press New appointment. The panel opens with time pre-filled and a single “What are you booking?” picker whose options are grouped by type — Time2book’s pattern, which I prefer to Square’s explicit type field because it removes a decision while still teaching the taxonomy. Choosing a bookable resolves the type, and the form then reveals only that type’s fields: staff and add-ons for a Service, unit and rate period for a Rental, address and travel for a Field service, host and invitees for a Booking calendar. Detail in §17, Flow 7.


13 · Saved Views

Right model, one structural fix

The brief’s instinct — saved configurations belong to the dataset, not to the table — is correct and well supported. But the proposed model, in which a Saved View simply works in both Calendar and List, has a failure mode that will surface within a week of launch.

The failure mode: not every filter set is meaningful in both views

“Today’s appointments” works in both. “Pending appointments” does not. Twelve unconfirmed bookings scattered across three months render as a nearly empty week grid — the user opens their “Pending” view in Calendar, sees nothing, and concludes the view is broken. The mismatch is structural: a status-driven view has no natural date range, while a calendar is defined by one.

Fix: a Saved View stores a preferred view type and each view type’s display config, while the filter set stays shared and canonical. Opening a view lands you in its preferred type; the switcher stays available and remembers your last choice for that view. Airtable establishes exactly this — view type is chosen at creation — and it costs the user nothing.

The object

SavedView ├── id, name, description?, icon? ├── scope personal | shared(workspace) — per Linear ├── owner, created_at, updated_at ├── preferredViewType calendar | list ← the structural fix │ ├── filters SHARED & CANONICAL — identical in both view types │ ├── types[] booking_calendar | service | rental | home_service │ ├── staff[], locations[], resources[] │ ├── bookables[], categories[] │ ├── statuses[], sources[] │ ├── customerTags[] │ ├── dateRange relative (today, this week, next 30d) | fixed | none │ └── conditions[] advanced builder output, all/any │ ├── calendarDisplay applies only in Calendar │ ├── granularity, groupBy (none|staff|location|resource|type) │ ├── colourBy (status|type|staff|bookable) │ ├── showBuffers, unifiedServiceView, showCancelled, slotHeight │ └── listDisplay applies only in List ├── columns[], columnOrder[], density ├── sort[], groupBy └── pageSize

One filter set, two display configs, one preference for where to land. Switching views never loses the filter; it swaps the display config.

Screen 5 — Saved Views picker
ViewsManage
Views save your filters, dates and layout so you can return to them in one click. Got it
Personal
✓Front desk — today18
My appointments6
Needs confirmation12
Shared · workspace
All appointments403
Services only387
Rentals only3
+ Save current view…
Screen 4 — Types dropdown
Appointment typesOnly this
✓All types
✓●Booking calendars9
✓◎Services387
✓▣Rentals3
✓▲Field services4
Also show
✓Blocked & busy time
Cancelled appointments
Field services not enabled · Add booking type

Left: personal / shared split with result counts and a dismissible explainer (Linear + Slack). Right: types with per-type counts, an “Only this” shortcut for solo-ing a type, and blocked time as an explicit facet rather than a hidden default.

Governance

ConcernDecision
Personal vs sharedTwo scopes only. Personal is the default on create — the safe option, and it means an operator experimenting cannot disrupt colleagues. Promotion to shared requires the manage-views permission.
Default viewEach user may set one personal default. Admins may set one workspace default per role, which becomes the landing state for new users — the mechanism that delivers “front desk lands on today, staff land on their own day” without configuration by the end user.
Dirty stateModifying a view shows a dot on the chip and exposes Revert · Save · Save as new. For a shared view, Save warns “This will change the view for everyone using it” — Height’s copy, which is better than anything I would write.
Protected viewsAdmins can lock a shared view so members can open and modify locally but only Save as new. Prevents the “someone broke Front Desk” incident.
DeletionDeleting a shared view in use warns with the count of users who have it as default, and offers to reassign them.
Permissions interactionA shared view is a filter set, not a grant. Two users opening the same view see different result counts if their entitlements differ — and the count in the header makes that visible rather than mysterious.
DiscoveryThree surfaces: the dismissible explainer in the picker (Slack), the contextual “Save as view” beside the dirty chip (Zapier), and three seeded shared views on every account so the concept is demonstrated rather than described.

Seeded views

Every account starts with three shared, protected views so Saved Views is a populated feature rather than an empty mechanism: All appointments (no filters, Calendar/Week), Today (relative range, Calendar/Day, grouped by staff), Needs action (status in pending/unconfirmed, List, sorted by time ascending). The third is deliberately List-preferred — it is the demonstration that view type is a property of the view.


14 · Calendar / List relationship

Same query, two renderers

The contract is narrow enough to state precisely, which is what makes it implementable:

ConceptShared?Behaviour on view switch
Filter set (all dimensions)SharedPreserved exactly.
Date rangeSharedPreserved. Calendar’s Week becomes List’s “Aug 23–29” range filter — the same predicate rendered differently.
Search querySharedPreserved.
SelectionSharedPreserved, so a marquee selection in Calendar can be bulk-acted in List.
Active saved viewSharedPreserved; dirty state preserved.
Granularity, group-by, colour-by, buffersCalendar onlyRetained for return.
Columns, density, sort, paginationList onlyRetained for return.

URL as the source of truth. /calendars/appointments?view=calendar&range=2026-08-23..29&types=all&staff=jane&savedView=front-desk. Switching view changes one parameter. This makes the shared/not-shared contract self-evident, makes every state linkable — which the current product cannot do — and makes the migration redirects in §21 trivial.

Screen 3 — Appointments › List · same filters, same saved view, same range
Appointments
CalendarList Front desk  view 142 appointments
Today Aug 23 – 29, 2026 Staff: Jane ▾ All types 4 Status ▾ Filters ▾ Columns 7 + New appointment ▾
AppointmentTypeCustomerStaffWhenStatusSource
Whole Facility● Booking calendarNeha KumariJane Doe29 Aug, 09:30New · action requiredBooking page
Haircut for Women◎ ServiceAmara RaoJane Doe24 Aug, 09:00ConfirmedBooking page
Coral Bay Villa▣ RentalM. Okonjo—24–27 AugConfirmedThird party
Boiler service — 14 Elm St▲ Field serviceD. WhelanSam Lee25 Aug, 11:00PendingConversation
Discovery call● Booking calendarT. AlvarezJane Doe25 Aug, 09:00UnconfirmedRouting
Type column appears automatically only when more than one vertical is enabled · Status is inline-editable · Source column is new, drawn from the 10+ instrumented booking entry points

List specifics

  • Default columns: Appointment (locked, per Deel), Customer, Staff, When, Status, Service/Bookable, Source. Type is added automatically when >1 vertical is enabled and hidden otherwise — Principle 07 applied to a column.
  • Available columns: location, resource, duration, price, payment status, created, created-by, confirmation number, notes, customer tags, recurrence, buffers, add-ons, capacity, custom fields.
  • Column management: popover, two buckets (shown/hidden) with search, drag-reorder, count in the chip, reset to default.
  • Status editing: inline dropdown in the cell, preserving the existing red-outline treatment for action-required. This is the single best interaction in the current product and should not change.
  • Bulk actions: floating bar on selection — confirm, cancel, reschedule, reassign staff, send reminder, export. Destructive actions confirm with a count and, per our conservative-defaults principle, never fire notifications silently.
  • Scale: virtualised rows, server-side sort and filter, cursor pagination. Row grouping by day is the default because the dataset is inherently temporal.

15 · Vertical configuration

What each vertical owns — and what it must not

The rule: a vertical owns its bookable definitions and its booking rules. It never owns staff, locations or resources.

VerticalOwnsReferences (does not own)
Booking calendars Calendar definitions and their types (1:1, round robin, collective, class, event); per-calendar duration, interval and capacity; assignment and routing rules; booking window, buffers, look-busy; guest rules; invite title and branding; slug; groups; its own booking pages Staff, Locations, Statuses, Notification templates
Services Service definitions; categories and their display order; duration ranges and price bands; add-ons; staff eligibility per service; resource requirements per service; its own booking pages Staff, Locations, Resources, Statuses
Rentals Listings; categories; rate units (hour/day/week/month) and rate cards; stock/unit count; minimum and maximum rental duration; turnaround time between bookings; deposit and damage rules; its own booking pages Locations, Resources, Statuses. Note: rentals often reference no staff at all
Field services Service definitions; service areas (postcode / radius / polygon); travel-time model and whether travel consumes bookable time; on-site duration; per-area surcharge; equipment requirements; its own booking pages Staff, Resources, Statuses

Staff & resources — the extracted destination

Staff & resources ├── Staff │ ├── list: name · role · locations · services eligible · calendar sync status │ └── detail: Availability (working hours, date overrides, time off) │ Eligibility (which bookables, across ALL verticals, in one place) │ Calendar sync (linked + conflict calendars — was Connections) │ Booking rules (max per day, priority for round-robin) ├── Locations │ └── physical (address, hours) · virtual (Zoom/Meet) · customer-site └── Resources └── rooms · chairs · equipment — capacity, location, which bookables need them

The Eligibility tab is the payoff for the extraction. Today, answering “what can Jane be booked for?” means visiting Services, then Meetings, then eventually Rentals. Afterwards it is one screen — and it is the screen that makes cross-vertical conflict detection explicable to the user rather than mysterious.

Migration note on per-staff calendar sync

Today’s Connections screen already selects a staff member first, then configures their calendars — it is already staff-scoped. Moving it to Staff › detail › Calendar sync is therefore a relocation to where its own interaction model already points, not a redesign. Low risk, high coherence.

Booking pages

Each vertical has a customer-facing booking surface. RecommendationKeep each vertical’s booking-page configuration inside that vertical (as Services does today) rather than centralising it, because the content of a booking page is inseparable from the offering it presents — a rental page needs a gallery and rate table, a round-robin page needs host selection. Centralise only the shared theme: brand, colours, logo, domain, and legal copy live in Settings › Booking page appearance, which every vertical page inherits and may override. This mirrors the audience-based grouping Acuity uses and avoids a single overloaded page-builder.


16 · Settings architecture

Two explicit disagreements

Should Settings live inside Calendars or at platform level? Both — and the split must be principled

The test I would apply: would this setting still exist if Calendars were deleted? If yes, it is platform. If no, it belongs to Calendars.

SettingHomeReasoning
Appointment statusesCalendarsSee below — I disagree with the brief here.
Calendar sync (linked + conflict calendars)Calendars, per staffTwo-way sync of bookings and conflict-checking for availability are meaningless outside scheduling.
OAuth account identity (which Google account is connected)PlatformThe same Google connection serves Calendars, Email and Contacts. Duplicating it per product produces multiple consent flows and multiple revocation paths.
Video conferencing (Zoom/Meet)Platform, referenced by CalendarsWould survive Calendars’ deletion — used by Conversations too.
Notification templates & channelsPlatform engine, Calendars triggersEmail/SMS/WhatsApp infrastructure is platform. Which appointment events fire which template, and cancellation/reschedule policy, is Calendars. Customer.io splits it exactly this way.
Permissions & rolesPlatform, with calendar-specific grantsRoles are platform; “can see other staff’s appointments” and “can manage shared views” are calendar grants declared by Calendars.
Timezone, week start, default viewCalendars (+ user override)Business timezone is platform; week-start and default calendar view are scheduling preferences.
Booking page appearance (brand)Calendars, inheriting platform brandInherits logo and colours from platform brand settings, overridable for booking pages.
Disagreement 1 — Statuses are not platform-wide

The brief suspects statuses may belong to the platform. I would argue firmly against it. Appointment statuses encode a lifecycle specific to time-bound bookings: unconfirmed → confirmed → showed / no-show → cancelled. “No-show” is meaningless for an Opportunity, a Contact or an Invoice. Opportunities already have pipeline stages; invoices have payment states. A platform-wide status registry would be an abstraction over three unrelated state machines — it would serve no user and would force every product to describe its states in a vocabulary general enough to be useless.

Evidence: Plane places “Project states” under Workspace › Features — a per-object-type state machine configured at workspace scope but owned by the product that uses it. That is precisely the shape I recommend.

Home: Calendars › Settings › Appointment statuses. Trade-offIf a future requirement genuinely needs cross-object status reporting, the answer is a reporting-layer mapping, not a shared configuration object.

Disagreement 2 — Connections must be split, not relocated

The brief asks whether Connections are platform-wide. Treating them as a single thing is the error — today’s Connections screen bundles two different concerns:

  • Identity: which Google/Microsoft account is authorised. Platform-owned, shared across products, one consent and one revocation path.
  • Behaviour: which of that account’s calendars receives bookings (linked), which are checked for conflicts, and how sync resolves. Purely scheduling, and already staff-scoped in the current UI.

Moving the whole thing to the platform would strand conflict-calendar configuration — an availability-computation input — in a settings area with no scheduling context. Keeping it all in Calendars means users re-authorise Google separately per product.

Resolution: split, and connect the two with Customer.io’s cross-scope banner: on Calendars › Settings › Calendar sync, show “Connected accounts are managed in your account settings”; on the platform integrations page, show “Choose which calendars receive bookings in Calendars › Calendar sync”. Neither surface pretends to own the whole story.

Settings structure

Calendars › Settings scoped: this product only ├── Appointment statuses state machine, colours, which count as "busy" ├── Calendar sync per-staff linked + conflict calendars │ → banner: connected accounts live in platform account settings ├── Notifications & reminders which appointment events fire which template; policies ├── Booking page appearance inherits platform brand; overridable └── Preferences week start, default view, slot granularity, timezone display NOT here — platform scope, cross-linked: connected accounts · video conferencing · roles & permissions · brand · billing

RecommendationAdopt Plane’s explicit scope tabs at the platform settings level (Account · Workspace · Products) so users stop guessing which scope they are in. This is a platform-level change beyond this project’s remit, but Calendars should be designed assuming it arrives — and should not be blocked on it.


17 · User flows

Eleven flows, end to end

Flow 1 · First entry into Calendars

Entry
Platform left nav → Calendars. No prior configuration.
Intent
“Set this up so people can book me.”
UI
Appointments › Calendar renders the real grid — current week, empty — with a dismissible setup card overlaid on the first two days: “Choose what people can book” and four cards (Booking calendars / Services / Rentals / Field services), each one line on what it is for. Nav shows only Appointments, Staff & resources, Settings, and + Add booking type.
Interaction
Picking a type enables that vertical, adds it to nav with a brief highlight, and lands on its create-first empty state. Dismissing the card leaves the empty calendar with a persistent “+ Add booking type” in nav.
Feedback
Nav item appears; toast “Services enabled”.
Exit
Inside the chosen vertical with one bookable created, and a “View booking page” action — the existing product already gets this right.

Why the empty grid rather than a full-page wizard: the grid teaches where the user will live. A wizard teaches the wizard. Trade-offSlightly lower setup completion than a forced wizard; considerably better orientation. Worth A/B testing given the live discoverability experiment.

Flow 2 · Seeing all appointment types together

Entry
Calendars → lands on Appointments › Calendar.
Intent
“What’s happening this week?”
UI
Week grid, all enabled types, colour by status, glyph by type. Type chip reads “All types 4”. Header reads “142 appointments”.
Interaction
None required — this is the default. Multi-day rentals sit in the all-day band; classes show capacity fractions.
Feedback
The count in row 1 confirms scope. If a saved view is active its name appears; if not, the chip reads “All appointments”.
Exit
Oriented. For single-vertical accounts the type chip is absent entirely and this flow is indistinguishable from today.

Flow 3 · Filtering to Services only

Entry
Type chip in row 2.
Intent
“Just show me the salon side.”
UI
Dropdown with All types, per-type rows with counts, and an “Only this” shortcut on hover of each row.
Interaction
“Only this” on Services deselects the others in one action — the common case is soloing, not deselecting three things individually.
Feedback
Chip becomes “Services” (named, not counted) and takes the active treatment. Count updates to 387. Grid re-renders; the type glyph remains, because a filter should not change encoding.
Exit
Filtered, with the filter legible in the toolbar and a “Save as view” affordance now visible beside the saved-view chip.

Flow 4 · Creating a Saved View

Entry
Either the “Save as view” affordance that appeared in Flow 3, or Views › “+ Save current view…”.
Intent
“I do this every morning — stop making me rebuild it.”
UI
Small popover, not a modal: Name (pre-filled from the active filters, e.g. “Services · this week”), optional description, scope radio (Personal default / Shared), a read-only summary of what will be saved (“4 filters, week view, grouped by staff, 7 columns”), and “Set as my default” checkbox.
Interaction
Save. Nothing navigates.
Feedback
Saved-view chip adopts the new name; toast “View saved · Personal” with an Undo.
Exit
Same screen, now named. The pre-filled name is the detail that makes this feel cheap rather than like a form.

Flow 5 · Switching Calendar → List

Entry
Segmented control, row 1.
Intent
“I need to work through these, not look at them.”
UI
Table renders with the identical filter set, range and saved view. Row 1 is unchanged except the segment; the count is identical — the strongest possible proof it is the same dataset.
Interaction
One click. Any Calendar selection carries over into the List selection.
Feedback
Count unchanged. URL changes only view=.
Exit
In List, able to sort, bulk-select and edit status. Returning to Calendar restores granularity and grouping.

Flow 6 · A Saved View persisting across both

Entry
Views picker → “Needs action” (a List-preferred view).
Intent
“Chase everything unconfirmed.”
UI
Opens in List because that is its preferred type — the structural fix from §13 doing its job. Date range is “none”, so all pending appointments appear regardless of when.
Interaction
Switching to Calendar is permitted and shows the same 12 records spread thinly across the current week, with an inline note: “This view has no date range. Showing Aug 23–29 — 3 of 12 appointments.” plus “Show all in List”.
Feedback
The note prevents the “my view is broken” conclusion — the exact failure mode a naive cross-view model would produce.
Exit
Either view, with the discrepancy explained rather than hidden. Next opening remembers the user’s last choice for this view.

Flow 7 · Creating an appointment from the unified calendar

Entry
Click-drag a range on the grid, or “+ New appointment”.
Intent
“Book this person into this slot.”
UI
Right side panel — never a full page (GoDaddy’s mistake). Time and staff pre-filled from the drag. First field: “What are you booking?” — a searchable picker grouped by type: Services (Haircut 45m £59, Hair spa 30m…), Booking calendars (Discovery call, Sales call…), Rentals (Coral Bay Villa…). Grouping is omitted when one vertical is enabled.
Interaction
Choosing a bookable resolves the type and reveals only that type’s fields. Duration auto-fills and adjusts the block live on the grid behind the panel. Customer picker searches Contacts with inline create. Conflicts appear as a named warning (Clockwise): “Jane has a 2:15pm Service — 15 min overlap”, with Book anyway / Find next slot.
Feedback
Live block on the grid; explicit notification control (“Send confirmation to customer”, on by default) because silent notification is the classic scheduling regret.
Exit
Panel closes, block animates to solid confirmed, toast with Undo. Calendar state, filters and view all unchanged.

Why the grouped picker rather than a type selector first: it removes a decision the user does not think in (“which vertical is a haircut?”) while still teaching the taxonomy through the group headers. Square’s explicit “Event type” field is the fallback if the picker proves too long at scale.

Flow 8 · Configuring a Service

Entry
Nav → Services. One click from anywhere, versus today’s Calendars → Calendar settings → Services → Services across a shell boundary.
Intent
“Change the price of Haircut for Women.”
UI
Services list, preserved from today: category rail with drag-reorder and counts, table of name / duration / price / staff avatars, search, New service. Keep this — it works.
Interaction
Row → detail. Staff shows eligible staff drawn from the shared Staff object, with a link to Staff & resources rather than an editable duplicate.
Feedback
Autosave with a “Saved” indicator, matching the calendar-creation builder pattern already specified for this product.
Exit
Back to the list. Existing appointments referencing the service are unaffected — price is captured at booking time.

Flow 9 · Creating a booking calendar

Entry
Nav → Booking calendars → New calendar.
Intent
“Let clients book a 30-minute intro with my team.”
UI
The chooser modal and split-screen builder with live booking-widget preview already designed for this product: Essentials → Booking page → Rules & limits → Automations, with only Essentials required.
Interaction
Unchanged from that specification. The only IA change is the entry point — one click instead of four, and no shell switch.
Feedback
Live preview; “Saved” toast; slug validated inline.
Exit
Calendar created and immediately bookable; appointments from it appear in Appointments with the booking-calendar glyph.

Flow 10 · Configuring Connections / calendar sync

Entry
Two legitimate entries, both correct: Staff & resources → Staff → Jane → Calendar sync (staff-centric, the common case), or Settings → Calendar sync (admin-centric, showing all staff and their sync status).
Intent
“Stop double-booking me against my personal Google calendar.”
UI
Connected accounts (read-only, with the cross-scope banner to platform account settings), then Linked calendar (receives bookings) and Conflict calendars (checked for availability) — the current screen’s own structure, relocated.
Interaction
If no account is connected, a single “Connect Google” button deep-links to the platform OAuth flow and returns here on completion. Never a second consent screen for an already-connected account.
Feedback
Green check plus “Last synced 2 min ago”. Disconnected state is labelled inline in the picker, per Amie.
Exit
Sync active; external busy time appears as hatched blocks in the unified calendar.

Flow 11 · Configuring Statuses

Entry
Settings → Appointment statuses. Also reachable from any status dropdown via “Manage statuses” at the foot of the menu — the point of need.
Intent
“Add a ‘Deposit paid’ status.”
UI
Table of statuses: name, colour, category (Pending / Confirmed / Completed / Cancelled), “Counts as busy” toggle, “Requires action” toggle, usage count. System statuses are locked with a lock icon and an explanatory tooltip.
Interaction
Custom statuses map onto a system category, so automations and reporting keep working. Deleting a status in use requires choosing a replacement.
Feedback
Usage count makes the blast radius visible before editing.
Exit
New status available in every status dropdown and as a filter facet immediately, in both views.

The “Counts as busy” toggle is the non-obvious requirement: statuses do not merely label, they determine whether a slot blocks availability. That is why they cannot be a generic platform object.


18 · Interaction specifications

The behaviours worth writing down

View switching

  • Preserves filters, range, search, selection, saved view and dirty state. Swaps display config only.
  • Never re-fetches if the underlying query is unchanged — the switch should feel instant, which is also what convinces users it is one dataset.
  • Keyboard: C / L. Range navigation ← / →, T for today, D/W/M for granularity, / for search, N for new.

Filter behaviour

  • Live apply for single-dimension chips (staff, type, status). Batched apply only inside the advanced condition builder, where a partial condition is invalid anyway.
  • Chip labelling: 0 selected → dimension name; 1–2 → the value names; 3+ → “Staff 4”. Naming beats counting whenever it fits, per Airtable.
  • “Clear all” is always present when any filter is active, and does not clear the date range — range is orientation, not a filter.
  • A filter that would produce zero results still applies; the empty state explains which filter is responsible and offers to remove it.

Saved-view dirty state

  • Any change to a stored property marks the view dirty: dot on the chip, tooltip “Modified”.
  • Chip menu when dirty: Revert to saved · Save changes · Save as new view. For shared views, Save is disabled without permission and shows “Ask an admin, or save as a new view” — a dead end with an exit.
  • Navigating away with unsaved changes does not prompt; the dirty state persists in the session and in the URL. Prompting on every navigation in a tool used dozens of times a day would be intolerable.

Calendar direct manipulation

  • Drag to move; edge-drag to resize; both show a live conflict indicator and snap to the slot granularity. Drop outside working hours warns but permits — operators legitimately override.
  • Drag across staff columns reassigns, and checks eligibility: “Sam is not eligible for Gel overlays” with Reassign anyway / Cancel.
  • Every drag is optimistic with a 5-second Undo toast, and reverts visibly on server rejection.
  • Multi-day and all-day items are not vertically resizable — only their day span changes.

Loading, empty and error states

StateTreatment
LoadingGrid chrome and time rules render immediately; blocks arrive as skeletons in place. Never a full-page spinner — the chrome is stable and should not flicker. List uses skeleton rows at the current density.
Empty — nothing booked“No appointments this week.” + New appointment. Quiet.
Empty — because of filters“No appointments match these filters.” Lists the active filters as removable chips, plus “Clear all filters”. This distinction is non-negotiable — conflating the two is how users conclude data is lost.
Empty — no verticals enabledFlow 1’s setup card.
Empty — no permission“You can only see your own appointments.” with a request-access action where the role allows.
Partial failureOne vertical’s query fails: render the rest and show an inline banner “Rentals couldn’t load · Retry”. Never fail the whole calendar for one subtype — a critical resilience property of the unified query.
Stale syncExternal calendar not synced in >1h: subtle badge on the type chip and a tooltip with last-sync time. Silent staleness in a booking system causes double-bookings.
ErrorState what failed and the next action. Preserve unsaved panel input on failure — never discard a half-entered booking.

19 · UI recommendations

Preserve the visual language; change the structure

Per the brief, the screenshots are the source of truth. The mockups in this document use the existing product’s tokens deliberately.

ElementKeep as-isChange
Type & spacingInter; 16px headings, 14px controls/labels, 13px helper, 12px badges; 4px radii on cards; 8px control radii; #EAECF0 bordersNothing. The existing scale is already disciplined.
ColourPrimary blue for primary actions; the HighRise grey ramp; green/amber/red semanticsReserve saturated colour for status on the calendar (§12). Reduce the visual weight of routine busy blocks so status can carry the alarm.
Chips & toolbarExisting chip shape, height and count-badge treatmentSplit into two rows; add named-filter labelling; add drift badge to Display.
Right panelPanel position, width and close affordance from “Manage view”Rename to Filters and remove display options and type scope from it — they move to Display and the type chip. This is the concrete fix for the concept-mixing in P2.
TablesHeader treatment, row height, pill-shaped status, avatar stacks, paginationAdd Type and Source columns (conditionally); lock the identity column; two-bucket column manager.
Calendar gridTime gutter, day headers, all-day band, current-time indicator, grid rulesAdd the four-channel encoding; add capacity fractions; add staff-column grouping as a first-class option.
Status controlThe red-outlined editable status dropdown — keep exactly.Extend the same treatment into the Calendar block detail popover.
Vertical config screensCategory rail with drag-reorder and counts, quick-tip cards, View booking page, onboarding guide entryRemove per-vertical Staff / Resources / Locations tabs; replace with a read-only summary linking to Staff & resources.

Trade-offThe one place I would spend new visual budget is the calendar block. Today’s blocks are uniform grey with a coloured left edge and carry only “Busy” plus a time. The unified calendar needs them to carry type, status, capacity and customer — roughly four times the information in the same space. That requires tighter type (11px, 1.3 line-height), truncation rules, and a hover/click detail popover doing more work. This is the highest-risk visual change in the proposal and the first thing I would prototype at real density with a real account’s data.


20 · Edge cases

What breaks, and the designed answer

CaseAnswer
No verticals configuredFlow 1. Nav shows Appointments, Staff & resources, Settings, + Add booking type. Calendar renders empty but real.
Exactly one verticalType chip hidden; Type column hidden; type glyph still rendered (cheap, and correct if a second vertical arrives). Experience is indistinguishable from today — the design’s most important non-regression.
10+ verticalsNav lists enabled verticals only; beyond six, collapse to a “Booking types” group in the flyout. Type dropdown gains search (Amie). The unified query needs a cap: beyond ~8 types, All defaults to the six most-used with “+2 more”.
Thousands of appointmentsRange-bounded queries; +N more collapse beyond 3 per slot; Month shows per-day counts; virtualised List. Index review is a hard prerequisite (§12).
Many staffStaff columns beyond ~8 scroll horizontally with a sticky header. Default staff selection is “working today”, not all staff — a 40-person roster should not render 40 empty columns.
Multiple locationsLocation becomes a toolbar chip automatically when >1 exists. Location can also be the group-by axis instead of staff.
Overlapping appointmentsSide-by-side within the slot down to 3; then +N more. Genuine double-bookings get a warning glyph and appear in a “Conflicts” seeded view. Cross-vertical conflicts are now detectable — the main correctness win.
Recurring appointmentsRendered as instances with a recurrence glyph. Editing prompts This / This and following / All. List groups instances under a parent with a count.
Blocked slots & buffersBlocked time is a facet in the type dropdown (“Blocked & busy time”), on by default. Buffers stay a Display toggle, rendered as a hatched extension of the block rather than a separate block.
PermissionsQuery-layer filtering. Unviewable occupied slots render as opaque busy with no detail. Type chip lists only permitted types.
Archived / deleted bookableExisting appointments survive and display the captured name with an “archived” marker. Filters list archived bookables in a collapsed “Archived” group so historical views keep working.
Disabled vertical with existing appointmentsDisabling hides the nav item and configuration but never hides appointments. The type remains a filter facet marked “disabled”. Silently hiding booked revenue would be the worst possible outcome.
Unavailable staffTime off renders as hatched; booking into it warns but permits with an explicit override reason.
Conflicting filtersZero results with an explanation naming the mutually exclusive pair, e.g. “Type: Rentals and Service: Haircut cannot both match.” plus one-click removal of either.
Time zonesAlways render in the business timezone with the offset in the gutter, as today. Where a customer’s timezone differs, the detail panel shows both. Staff with a personal timezone override get a persistent banner naming which zone the grid is in — ambiguity here causes real missed appointments.
Personal vs org saved viewsTwo scopes, ownership visible, admin-protected sharing (§13).
Shared view deleted while in useUsers holding it as default fall back to the workspace default with a one-time notice naming what changed.
Mobile / tablet§12.12. Day/Schedule granularity, swipeable staff columns, single filter sheet, no hover-dependent affordances.

21 · Migration strategy

Six phases, ordered by risk

Fact251k accounts, 1.5m MAU, and a detractor spike already recorded in the last 30 days. The sequencing below is driven by blast radius, not by design coherence — the most satisfying change is deliberately not first.

#PhaseWhat shipsWhy here
0InstrumentEvent coverage on: vertical-scope dropdown usage (how many accounts ever change it), view-tab switching, Smart List creation and reuse, filter dimension usage, Manage-view panel opens, and how many accounts have >1 vertical enabled.Every assumption in this document about multi-vertical prevalence and filter usage is currently unmeasured. This phase is cheap and de-risks all the others. Do not skip it.
1Merge the two viewsAppointments workspace with Calendar/List as view types. Shared filters, shared range, shared search. URL redirects. Both old tabs deep-link into the new workspace.Highest coherence gain, lowest conceptual risk — nothing is removed, two things become one. Delivers the P0 fix immediately.
2Saved ViewsSaved View object, dirty state, personal/shared scopes, seeded views. Smart Lists migrate automatically.Depends on Phase 1’s shared filter model. Delivers the asymmetry fix and the discoverability win.
3Unified typesType becomes a multi-select facet; All becomes default; the four-channel encoding ships.Gated on the query/index work and on Phase 0 data about density. The riskiest visual change, so it follows the structural ones.
4Nav restructureVerticals promoted out of the Settings shell into the product flyout; conditional rendering; renames (Meetings → Booking calendars, Connections → Calendar sync); Settings split.Should land with the platform left-nav rollout so users absorb one navigation change, not two.
5Extract shared objectsStaff & resources as a destination; per-vertical Staff/Locations/Resources tabs become read-only links; eligibility consolidated.The largest data-model change (deduplicating staff records across verticals) and the one with real merge-conflict potential. Last, and behind a per-account migration with review.

What happens to existing artefacts

TodayMigration
Smart ListsBecome Saved Views with preferredViewType = list, preserving name, owner, filters, columns and sort. Scope maps to personal unless already shared. Nothing to redo. A one-time in-product notice: “Your Smart Lists are now Views — and they work in Calendar too.” The rename is the only user-visible change, and it arrives with a capability gain, which is the right trade.
Calendar last-used stateBecomes an implicit personal view named “Last used”, promotable via Save as. Users who never engage with Views keep exactly today’s behaviour — the calendar still remembers where they were.
Vertical scope selectionBecomes the type facet, pre-set to the account’s last-used vertical for one release, with a one-time tooltip: “You can now see all your appointment types together.” Then the default flips to All. Flipping immediately would silently change what every user sees on login — unacceptable at this scale.
Upcoming / Cancelled / All tabsBecome three seeded status views, retaining the tab affordance in List for one release so muscle memory survives, then folding into the Views picker.
Bookmarked URLs301 redirects: /calendars/view → ?view=calendar, /calendars/list → ?view=list, and a mapped table for every settings sub-route. Old query parameters are translated where possible, dropped with a notice where not.
Service calendars (v1)Removed from nav; surfaced as a migrate banner on the Booking calendars list for affected accounts only.
Heavy Appointment-List usersThe population most at risk. Mitigations: List remains one click from the same nav item; their Smart Lists carry over intact; a personal default view can pin them to List permanently so Calendars opens exactly where it used to. RecommendationIdentify this cohort in Phase 0 and recruit from it for usability testing before Phase 1 ships.
Staff records duplicated across verticalsPhase 5. Match on email, then name; present an admin review screen for ambiguous matches rather than auto-merging. Never merge availability silently — take the union of working hours and flag conflicts for review.

Rollout mechanics

  • Per-phase feature flag with account-level targeting; internal, then design partners, then 1% / 10% / 50% / 100%.
  • An explicit “switch back” for Phase 1 for at least one release, with a reason prompt. The reason data is worth more than the engineering cost.
  • Given the recorded detractor spike, add NPS-verbatim monitoring keyed to scheduling terms for the duration of the rollout, and set a rollback trigger before shipping rather than after.
  • Fact10+ booking entry points are instrumented. Every phase needs regression coverage across all of them — routing, conversation, opportunity, embed, third-party — not just the calendar page. A change that looks safe in the UI can break a booking source that never touches it.

22 · Metrics & validation

What to measure, and what we do not yet know

Status of our evidence

FactAvailable from Pendo today: account and MAU scale, NPS and its recent movement, calendar-type event volumes, booking-source distribution, platform split (web / iOS / Android), appointment-creation retention curves, and an active discoverability experiment.

AssumptionNot available: any user interviews, usability testing, task-timing, or session replay for this problem space. Also unmeasured: how many accounts run more than one vertical, how often the vertical scope dropdown is changed, Smart List creation and reuse rates, and which filter dimensions are actually used. No baseline in the table below should be invented — Phase 0 exists to establish them.

AreaProposed metricBaselineTarget / read
NavigationTime to reach Service configuration from CalendarsUnmeasured; structurally 4 clicks + shell switch1 click. Measure in moderated testing, not analytics.
Wrong-destination rate (opens Calendar settings when seeking an appointment, or vice versa)UnmeasuredEstablish in Phase 0; expect meaningful reduction after Phase 4.
Backtracking rate (nav item opened then abandoned <3s)UnmeasuredDirectional health signal for the new tree.
AppointmentsTime to locate a named appointmentUnmeasuredModerated task; expect improvement from shared search across views.
Time to create an appointment, by entry pointUnmeasured per-entryMust not regress. The grouped picker adds a decision point — watch this closely in Phase 3.
View-switch rate per sessionUnmeasured (tab switching is a proxy)A rise indicates the views are complementary; a collapse to one view indicates the other is not pulling weight.
Saved Views% of active accounts with ≥1 saved viewSmart List rate unmeasuredPrimary adoption metric for Phase 2.
Reuse ratio (view opens ÷ views created)Unmeasured>1 means views are used, not just made. The metric that distinguishes real value from a novelty.
% of views created via the contextual “Save as view” vs the Views menun/aTests the Zapier-derived discoverability bet directly.
Unified calendar% of multi-vertical accounts retaining Alln/a (All does not exist)High retention validates the default. Heavy soloing suggests density is unmanageable.
Type-filter change frequency per sessionVertical-dropdown changes — unmeasuredPhase 0 gives the comparison. If accounts rarely change vertical today, the unified view is low-risk; if constantly, density is the risk.
Cross-vertical conflicts detected0 — not currently detectableA pure-gain metric and the clearest evidence of correctness value.
Mis-typed appointments (created against the wrong bookable, corrected <5 min)UnmeasuredThe key counter-metric. If unification causes booking errors, this catches it.
HealthCalendar time-to-interactive at p75 / p95, by appointment volumeUnmeasuredGuardrail for Phase 3. Regression here outweighs any IA gain.
Scheduling-related NPS verbatims and support-ticket volume by themeNPS ~47, detractor spike recordedRollback trigger, defined before rollout.

Validation plan before the expensive phases

  1. Tree test (unmoderated, n≈40–60, mixed roles): current IA vs Options A, C and D on eight findability tasks. Directly tests the naming decisions in §10, especially “Booking calendars” and “Staff & resources”. Cheapest high-value study available and it should run before Phase 1.
  2. Moderated usability (n≈8–10, recruited to include heavy Appointment-List users and at least three multi-vertical accounts): the unified calendar at real density, Flow 7 creation, and the saved-view dirty-state contract.
  3. Density prototype with real data — the highest-risk unknown. Load a genuine high-volume multi-vertical account into the encoding system and test legibility with front-desk staff on the hardware they actually use.
  4. First-click test on the type chip vs a sidebar for visibility control, to check the Amie-derived toolbar-pill bet.

23 · Screen specifications

Thirteen screens

Screens 2, 3, 4 and 5 are rendered above. The rest are specified for design and engineering pickup.

Screen 1 — Calendars in the platform left nav (product → subproduct → context)
Conversations
Contacts
Calendars ›
Payments
Marketing
Settings
Calendars
Appointments
Services
Rentals
+ Add booking type
Staff & resources
Settings
This account has Services and Rentals enabled. Booking calendars and Field services are not shown — they appear via “Add booking type”.
#ScreenSpecification
1Left navRendered above. Conditional verticals; Appointments visually separated as the operational destination; + Add booking type as the discovery surface; shared objects and settings grouped at the foot. Appointments is dock-pinnable.
2Appointments › CalendarRendered in §11 and §12. Two-row header; week grid; four-channel encoding; all-day band for multi-day; capacity fractions; result count.
3Appointments › ListRendered in §14. Identical row 1; conditional Type column; new Source column; inline status editing; locked identity column.
4Types dropdownRendered in §13. Per-type counts and glyphs; “Only this” solo shortcut; blocked/cancelled as explicit facets; footer pointing at disabled verticals.
5Saved Views pickerRendered in §13. Personal / shared split with ownership; result counts; dismissible explainer; “+ Save current view”; “Manage” to the index.
6Create Saved ViewPopover, ~320px. Name (pre-filled from active filters), optional description, scope radio with Personal default, read-only summary of what is captured, “Set as my default”. Primary Save view. Never a modal — the context behind it is the point.
7Filter builderRight panel replacing Manage view. Top: fast facets (staff, service, category, location, resource, status, source, customer tag) each collapsible with search — preserving today’s panel structure. Bottom: + Add condition opening rows of [field][operator][value] with a group-level all / any segmented control, one nesting level maximum. Footer: active-filter count, Clear all. Live apply for facets; explicit Apply only for conditions.
8New appointmentRight panel, ~440px. Time pre-filled from drag. First field “What are you booking?” — type-grouped searchable picker showing duration and price. Then type-conditional fields, customer picker with inline create, named conflict warnings, notification toggle on by default. Right rail shows customer context (history, notes, cards) per Square.
9Services configurationPreserve today’s screen: category rail with drag-reorder and counts, services table with duration / price / staff avatars, search, New service, Quick tip, View booking page. Remove the Staff / Resources / Locations tabs; add a read-only “Staff & resources” summary card linking out. Tabs reduce to Services and Booking pages.
10Booking calendars configurationPreserve today’s list: groups rail, 122-row table with name / group / duration / type / status / date updated / actions, filters for status / type / owner, New calendar. Tabs reduce to All booking calendars and Booking pages; Preferences and My availability move to Settings and Staff respectively. Create flow is the existing chooser modal plus split-screen builder.
11SettingsIndex page listing the five calendar-scoped areas with one-line descriptions, plus the Customer.io-style cross-scope banner: “Looking for connected accounts, roles or billing? Those live in your account settings.”
12Calendar syncAdmin view: table of staff × sync status × last synced, with per-row Configure. Staff view (from Staff detail): connected account read-only with cross-link, Linked calendar picker, Conflict calendars multi-select, sync direction, last-synced timestamp. Preserves today’s structure and vocabulary.
13Appointment statusesTable: name, colour swatch, system category, Counts as busy toggle, Requires action toggle, usage count, actions. System statuses locked with explanatory tooltip. New status panel maps to a system category. Reachable from any status dropdown via “Manage statuses”.

Key interaction states to design

  • Saved-view chip: default / active / dirty (dot) / dirty-shared-no-permission.
  • Type chip: hidden (1 vertical) / All / named single / counted multiple / permission-limited.
  • Filter chip: empty / named / counted / conflicting.
  • Calendar block: confirmed / pending / action-required / cancelled / busy-external / no-permission / conflicting / recurring / at-capacity / multi-day.
  • Empty states: nothing booked / filtered to zero / no verticals / no permission / partial vertical failure.
  • Create panel: type not chosen / type chosen / conflict detected / eligibility warning / saving / failed-with-input-preserved.

24 · Risks & trade-offs

Where this could go wrong

RiskSeverityMitigation
Unified query performanceHighThe hardest constraint in the proposal. A cross-vertical union with staff, location and status predicates over a large account may not meet interaction latency. Mitigate with an index review before Phase 3, range-bounded queries, per-type parallel fetch with independent failure, and a volume threshold above which All degrades to the most-used types. If this cannot be solved, the unified default must not ship — and that is an acceptable outcome, because Phases 1, 2 and 4 deliver most of the IA value without it.
Calendar block densityHighFour times the information in the same space. Prototype with real high-volume data before committing; keep “Colour by” and a compact/comfortable density toggle as escape valves; be willing to drop capacity fractions from the block to the popover.
Staff deduplication in Phase 5Med-highMerging staff records across verticals can corrupt availability. Admin-reviewed matching, union-not-overwrite for working hours, full rollback per account, and no silent merges.
Saved Views under-adoptedMedium“Views” is not native SMB vocabulary. Three-pronged: seeded views so the feature is populated, contextual save-as-view, in-menu explainer. Measure reuse ratio, not creation count — a feature that is created once and never reopened has failed regardless of adoption numbers.
Two-row header costLow-med~40px of grid height. Collapse row 2 on scroll; validate on a 13-inch laptop with the nav expanded, which is the realistic worst case.
Renames confusing existing usersMedium“Meetings → Booking calendars” and “Smart Lists → Saved Views” both break existing vocabulary and documentation. Ship each with a one-time in-product notice, keep the old term as a search synonym for two releases, and update help content before, not after.
Nav conditional rendering confusing supportLow-medTwo accounts legitimately see different nav, which makes support conversations and documentation harder. Mitigate with a support-tools view of enabled verticals and documentation written around “if you have X enabled”. Trade-offAccepted — the alternative is showing every user four verticals they do not use.
Colour-by-status contradicting expectationLow-medStakeholders will expect colour-by-type. The Display control makes this reversible per account, and Phase 3 can A/B the default. I would rather be wrong reversibly than right by decree.
Regression across 10+ booking sourcesMediumChanges to the appointment model can break routing, embed, conversation and third-party creation paths that never touch this UI. Explicit regression suite per phase across all instrumented sources.
Scope creep into a view-builder platformLow-medSaved Views invites board views, timelines, dashboards and grouping-by-anything. Hard cap at two view types; any third requires evidence from the reuse-ratio metric, not enthusiasm.

25 · Final recommendation

Decision matrix

Scored 1–5, where 5 is best. Scores are my judgement against the evidence in §7 and the reasoning in §9, not measured data.

CriterionCurrentA · Appts-firstB · Vertical-firstC · HybridD · Recommended
Discoverability 2 4 2 3 5
Mental-model fit 2 4 2 3 5
Scalability (verticals) 2 2 1 5 5
Cross-vertical workflows 1 5 1 5 5
Appointment management 2 5 2 5 5
Configuration efficiency 2 2 3 4 5
Saved Views coherence 1 5 2 5 5
Calendar / List consistency 1 5 2 5 5
Cognitive load 2 3 3 3 4
Navigation simplicity 3 3 4 5 4
Migration effort (5 = cheapest) 5 4 5 2 2
Future extensibility 1 3 2 5 5
Total / 60 2445295055

Reading the scores. Option D loses only on migration effort — and it loses there for exactly one reason: the shared-object extraction. That is also the single change that most improves configuration efficiency and future extensibility, so the cost buys the two things the current architecture most lacks. Option C ties D on structure and loses on discoverability and mental-model fit purely on the naming problem (§9). Option A is a genuine improvement over today and is the right thing to ship if the shared-object extraction cannot be funded — note it scores 45, well clear of the current 24, so a partial adoption is still a large win. Option B scores below Option A on almost every user-facing criterion despite being cheapest to build, which is the clearest sign it optimises for delivery convenience.

The recommendation

  1. Adopt Option D. Appointments as the operational hub with Calendar and List as view types; verticals as conditionally-rendered siblings; shared objects extracted; Settings scoped to Calendars with principled cross-links.
  2. Ship the unified calendar with All as default, encoding type by glyph and border, status by colour, with a user-switchable “Colour by”. Gate on the query-performance work.
  3. Build Saved Views as a dataset-level object with a shared filter set, per-view-type display config, and a preferred view type — the one structural correction to the brief’s model.
  4. Rename “Meetings” to “Booking calendars”; keep “Calendars” as the product name. Fix the collision at the child level, where it is cheap.
  5. Keep Statuses in Calendars. Split Connections. Both against the brief’s stated suspicion, for the reasons in §16.
  6. Sequence by blast radius, not coherence (§21). Instrument first; extract shared objects last.
  7. Run the tree test before Phase 1. It is the cheapest study available and it tests the naming decisions this whole tree rests on.
If only one thing ships

Merge Calendar and List into one Appointments workspace with a shared filter model (Phase 1). It fixes two of the three P0 problems, requires no data-model change, makes every subsequent phase cheaper, and is invisible to users who never used more than one view. Everything else in this document is an argument for what to build on top of that foundation.


26 · Open questions

What I could not resolve from the available evidence

  1. How many accounts run more than one vertical? The single most load-bearing unknown in this document. It determines whether the unified calendar is a flagship capability or a niche one, and whether Principle 07’s adaptive defaults are the main experience or an edge case. Answerable from Pendo in Phase 0.
  2. How often is the vertical scope dropdown actually changed? Rarely ⇒ unification is low-risk and mostly invisible. Constantly ⇒ density is the primary design risk and the encoding system needs far more validation.
  3. What is the real Smart List creation and reuse rate? If it is very low today, the Saved Views investment needs its discoverability plan strengthened before Phase 2, not after.
  4. Can the cross-vertical query meet interaction latency on the largest accounts? An engineering question that gates the unified default. It should be answered with a spike, not an estimate.
  5. Is Rentals genuinely time-bookable in the same sense as the others? Multi-day, stock-based, often staff-less. It fits the entity model, but if rental operators do not think of listings as living on a staff-oriented calendar, Rentals may want its own occupancy-grid presentation as a third view type — which would contradict my two-view cap. Worth testing specifically with rental accounts.
  6. Does “Booking calendars” actually test better than “Meetings”? I have argued it from semantics and migration cost, not from evidence. The tree test settles it, and I would change the recommendation if the data disagreed.
  7. Should the platform adopt explicit settings scope tabs? Plane’s pattern would resolve a class of confusion well beyond Calendars, but it is a platform decision outside this project’s remit. Calendars should be designed to work with or without it.
  8. What is causing the recorded NPS detractor spike? Unknown, and it matters for sequencing: if it traces to a recent Calendars change, that regression should be fixed before a restructure lands on top of it.
  9. Where does Waitlist belong? Square treats it as a peer of Calendar. We have no equivalent today. If it arrives, it is a third operational surface — and I would want to know before finalising the Appointments header, since it would compete for row 1.
  10. How do custom objects interact with this? The current left nav shows Pets, Pet Owners and Massage Chairs as top-level items. If custom objects can become bookable, “vertical” stops being a fixed set of four and the adaptive-nav model in Option D becomes essential rather than merely preferable. Worth confirming with the platform team.

Evidence base: six current-product screenshots; the platform left-nav prototype (structure, tokens and motion extracted from its deployed source); Pendo Calendars instrumentation; and 50 screens plus 4 multi-step flows reviewed on Mobbin across 34 products. No user interviews or usability testing were available for this problem space — §22 proposes how to close that gap.