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
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.
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
| Usage | Where it appears | What it actually is |
|---|---|---|
| Calendars | Platform left nav | The product |
| Calendar view | Header tab | A time-grid presentation |
| Calendars (122) | Settings › Calendars › Meetings › Calendars | Booking configurations — types Event, Personal, Round robin, Class booking |
| Connected calendars | Settings › Calendars › Connections | External 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
| Capability | Calendar view | List view |
|---|---|---|
| Saved, named configurations | — | Smart Lists |
| Persisted state | Last-used only, unnamed | Named + last-used |
| Status segmentation | Via filter panel | Upcoming / 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 display | Toggle | — |
| Unified service view | Toggle | — |
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.
Ranked by severity
P0 = fundamental, blocks a coherent model. P1 = major, causes daily friction or blocks scale. P2 = moderate. P3 = minor.
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.
“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.
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.
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.
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.
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.
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.
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.
“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.
“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.
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.
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.
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.
| Role | Primary question | How they think about the domain | Default 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 |
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.
One transaction object, four bookable subtypes
The data model is what makes the unified calendar either coherent or arbitrary. It is coherent.
Grey text is annotation, not structure.
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
| Action | Frequency | Object | Belongs in |
|---|---|---|---|
| See today’s / this week’s schedule | Many times daily | Appointment | Appointments › Calendar |
| Find a specific appointment | Many times daily | Appointment | Appointments (search, both views) |
| Create / reschedule / cancel | Many times daily | Appointment | Appointments (either view) |
| Change status, chase confirmation | Daily | Appointment | Appointments › List (bulk) + inline |
| Block out time | Daily | Appointment (busy) | Appointments › Calendar |
| Adjust my availability | Weekly | Staff | Staff & resources › Staff |
| Create / edit a service | Monthly | Service | Services |
| Create a booking calendar | Monthly | BookingCalendar | Booking calendars |
| Add a rental listing | Monthly | RentalListing | Rentals |
| Configure staff / locations / resources | Setup, rare after | Shared | Staff & resources |
| Connect an external calendar | Once per staff member | Connection | Settings › Calendar sync (+ platform identity) |
| Define statuses | Once, rarely revisited | Status | Settings › 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.
The tree as it stands
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.
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
| Product | Pattern | Observation & why it matters | Borrow | Avoid |
|---|---|---|---|---|
| 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
| Product | Pattern | Observation & why it matters | Borrow | Avoid |
|---|---|---|---|---|
| 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
| Product | Pattern | Observation & why it matters | Borrow | Avoid |
|---|---|---|---|---|
| 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
| Product | Pattern | Observation & why it matters | Borrow | Avoid |
|---|---|---|---|---|
| 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
| Product | Pattern | Observation & why it matters | Borrow | Avoid |
|---|---|---|---|---|
| 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
| Product | Pattern | Observation & why it matters | Borrow | Avoid |
|---|---|---|---|---|
| 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
| Product | Pattern | Observation & why it matters | Borrow | Avoid |
|---|---|---|---|---|
| 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
| Product | Pattern | Observation & why it matters | Borrow | Avoid |
|---|---|---|---|---|
| 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.
What the evidence converges on
Six conclusions
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
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.
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.
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.
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.
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.
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.
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.
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.
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)
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
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
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
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:
- 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.
- 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.
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.
The tree, and the naming decisions behind it
Naming decisions
| Today | Recommended | Reasoning |
|---|---|---|
| Calendars (product) | Calendars — unchanged | Strictly, “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. |
| Meetings | Booking calendars | The 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 view | Calendar / List, as view types inside Appointments | They stop being destinations, so they stop needing the word “view” in their names. The container supplies the noun. |
| Service calendars (v1) | Removed from navigation | Migration state is not a place. Surface it as a one-time banner on the Booking calendars list for affected accounts, with a migrate action. |
| Connections | Calendar 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 filters | Filters (one control, both views) + Display | One vocabulary, and the split finally separates “what subset” from “how shown”. |
| Smart Lists | Saved 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 & resources | New 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.
One workspace, one header grammar
The brief proposes a specific header. It is close to right; I would change two things and add one.
What I changed from the brief’s proposal
- 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.
- “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.
- 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.
- “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.
- “+ 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
| Element | Row | Behaviour |
|---|---|---|
| Title | 1 | Static “Appointments”. Never changes with filters — it names the place. |
| View switcher | 1 | Segmented, two options, hard cap. Preserves date range, filters and saved view across the switch. |
| Saved view chip | 1 | Shows active view name; a dot appears when modified. Click opens picker; the dot area exposes Revert / Save / Save as. |
| Result count | 1 | Live count for range + filters. Clicking it opens Filters, so it doubles as an explanation affordance. |
| Today | 2 | Returns range to now at current granularity. Disabled when already showing today. |
| Date range | 2 | Prev / label / next. Label is clickable and opens a month picker with a range option. |
| Granularity | 2 | Day / Week / Month / Schedule (agenda). Also holds Show weekends and Business hours only, per Front. |
| Staff | 2 | Multi-select with search and “Only me” shortcut. Named in chip when 1–2 selected, counted beyond that. |
| Types | 2 | Multi-select over enabled verticals. Hidden entirely when only one vertical is enabled (Principle 07). |
| Filters | 2 | Everything 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. |
| Display | 2 | Calendar: colour-by, group-by, buffer visibility, unified service view, slot height, cancelled visibility. List: columns, density, grouping. Carries a drift count badge, per Todoist. |
| Search | 2 | Icon-only, expands. Searches customer, title, notes, confirmation number. Scoped to current filters with an “also search all appointments” escape. |
| Create | 2 | Split: 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. |
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:
| Channel | Encodes | Rationale |
|---|---|---|
| Glyph | Type — a 12px icon leading the block title | Scales to arbitrarily many types; survives greyscale; already how Confluence solves it. Booking calendars = users, Services = sparkle, Rentals = package, Field services = truck. |
| Colour | Status by default, user-switchable | The contentious call, argued below. |
| Border & fill | Certainty — solid = confirmed, dashed = pending, hatched = blocked/busy, struck = cancelled | Directly borrowed from HoneyBook. Works at 11px, in greyscale, and at a glance from across a reception desk. |
| Position | Staff or resource via column grouping | Already the domain default (Fresha, Square). The most information-dense channel available and it costs no ink. |
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.
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
| Dimension | Placement | Reasoning |
|---|---|---|
| Staff | Toolbar chip | Highest-frequency dimension; four of five roles think this way first. |
| Type / vertical | Toolbar chip, hidden when 1 vertical | Needed for the unified view to be controllable, but not the marquee control. |
| Status | Toolbar chip in List, Filters in Calendar | List is the queue-management surface where status filtering is the primary job (today’s Upcoming / Cancelled / All tabs are exactly this). |
| Location | Toolbar chip if >1 location, else Filters | Adaptive: multi-location businesses filter by it constantly; single-location ones never. |
| Service / listing / calendar | Filters panel | High cardinality (15 categories, 122 calendars) — needs search, not a chip. |
| Category | Filters panel | A convenience roll-up over the above. |
| Resource | Filters panel, or group-by in Display | More useful as a grouping axis than a filter for room-based businesses. |
| Source | Filters panel | We instrument 10+ booking sources; this is a genuinely valuable filter nobody currently exposes. |
| Customer / tag | Filters panel | Enables 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.
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.
“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
One filter set, two display configs, one preference for where to land. Switching views never loses the filter; it swaps the display config.
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
| Concern | Decision |
|---|---|
| Personal vs shared | Two 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 view | Each 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 state | Modifying 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 views | Admins can lock a shared view so members can open and modify locally but only Save as new. Prevents the “someone broke Front Desk” incident. |
| Deletion | Deleting a shared view in use warns with the count of users who have it as default, and offers to reassign them. |
| Permissions interaction | A 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. |
| Discovery | Three 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.
Same query, two renderers
The contract is narrow enough to state precisely, which is what makes it implementable:
| Concept | Shared? | Behaviour on view switch |
|---|---|---|
| Filter set (all dimensions) | Shared | Preserved exactly. |
| Date range | Shared | Preserved. Calendar’s Week becomes List’s “Aug 23–29” range filter — the same predicate rendered differently. |
| Search query | Shared | Preserved. |
| Selection | Shared | Preserved, so a marquee selection in Calendar can be bulk-acted in List. |
| Active saved view | Shared | Preserved; dirty state preserved. |
| Granularity, group-by, colour-by, buffers | Calendar only | Retained for return. |
| Columns, density, sort, pagination | List only | Retained 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.
| Appointment | Type | Customer | Staff | When | Status | Source | |
|---|---|---|---|---|---|---|---|
| Whole Facility | ● Booking calendar | Neha Kumari | Jane Doe | 29 Aug, 09:30 | New · action required | Booking page | |
| Haircut for Women | ◎ Service | Amara Rao | Jane Doe | 24 Aug, 09:00 | Confirmed | Booking page | |
| Coral Bay Villa | ▣ Rental | M. Okonjo | — | 24–27 Aug | Confirmed | Third party | |
| Boiler service — 14 Elm St | ▲ Field service | D. Whelan | Sam Lee | 25 Aug, 11:00 | Pending | Conversation | |
| Discovery call | ● Booking calendar | T. Alvarez | Jane Doe | 25 Aug, 09:00 | Unconfirmed | Routing |
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.
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.
| Vertical | Owns | References (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
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.
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.
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.
| Setting | Home | Reasoning |
|---|---|---|
| Appointment statuses | Calendars | See below — I disagree with the brief here. |
| Calendar sync (linked + conflict calendars) | Calendars, per staff | Two-way sync of bookings and conflict-checking for availability are meaningless outside scheduling. |
| OAuth account identity (which Google account is connected) | Platform | The 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 Calendars | Would survive Calendars’ deletion — used by Conversations too. |
| Notification templates & channels | Platform engine, Calendars triggers | Email/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 & roles | Platform, with calendar-specific grants | Roles are platform; “can see other staff’s appointments” and “can manage shared views” are calendar grants declared by Calendars. |
| Timezone, week start, default view | Calendars (+ user override) | Business timezone is platform; week-start and default calendar view are scheduling preferences. |
| Booking page appearance (brand) | Calendars, inheriting platform brand | Inherits logo and colours from platform brand settings, overridable for booking pages. |
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.
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
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.
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.
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←/→,Tfor today,D/W/Mfor granularity,/for search,Nfor 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
| State | Treatment |
|---|---|
| Loading | Grid 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 enabled | Flow 1’s setup card. |
| Empty — no permission | “You can only see your own appointments.” with a request-access action where the role allows. |
| Partial failure | One 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 sync | External 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. |
| Error | State what failed and the next action. Preserve unsaved panel input on failure — never discard a half-entered booking. |
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.
| Element | Keep as-is | Change |
|---|---|---|
| Type & spacing | Inter; 16px headings, 14px controls/labels, 13px helper, 12px badges; 4px radii on cards; 8px control radii; #EAECF0 borders | Nothing. The existing scale is already disciplined. |
| Colour | Primary blue for primary actions; the HighRise grey ramp; green/amber/red semantics | Reserve saturated colour for status on the calendar (§12). Reduce the visual weight of routine busy blocks so status can carry the alarm. |
| Chips & toolbar | Existing chip shape, height and count-badge treatment | Split into two rows; add named-filter labelling; add drift badge to Display. |
| Right panel | Panel 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. |
| Tables | Header treatment, row height, pill-shaped status, avatar stacks, pagination | Add Type and Source columns (conditionally); lock the identity column; two-bucket column manager. |
| Calendar grid | Time gutter, day headers, all-day band, current-time indicator, grid rules | Add the four-channel encoding; add capacity fractions; add staff-column grouping as a first-class option. |
| Status control | The red-outlined editable status dropdown — keep exactly. | Extend the same treatment into the Calendar block detail popover. |
| Vertical config screens | Category rail with drag-reorder and counts, quick-tip cards, View booking page, onboarding guide entry | Remove 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.
What breaks, and the designed answer
| Case | Answer |
|---|---|
| No verticals configured | Flow 1. Nav shows Appointments, Staff & resources, Settings, + Add booking type. Calendar renders empty but real. |
| Exactly one vertical | Type 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+ verticals | Nav 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 appointments | Range-bounded queries; +N more collapse beyond 3 per slot; Month shows per-day counts; virtualised List. Index review is a hard prerequisite (§12). |
| Many staff | Staff 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 locations | Location becomes a toolbar chip automatically when >1 exists. Location can also be the group-by axis instead of staff. |
| Overlapping appointments | Side-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 appointments | Rendered as instances with a recurrence glyph. Editing prompts This / This and following / All. List groups instances under a parent with a count. |
| Blocked slots & buffers | Blocked 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. |
| Permissions | Query-layer filtering. Unviewable occupied slots render as opaque busy with no detail. Type chip lists only permitted types. |
| Archived / deleted bookable | Existing 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 appointments | Disabling 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 staff | Time off renders as hatched; booking into it warns but permits with an explicit override reason. |
| Conflicting filters | Zero 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 zones | Always 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 views | Two scopes, ownership visible, admin-protected sharing (§13). |
| Shared view deleted while in use | Users 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. |
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.
| # | Phase | What ships | Why here |
|---|---|---|---|
| 0 | Instrument | Event 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. |
| 1 | Merge the two views | Appointments 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. |
| 2 | Saved Views | Saved 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. |
| 3 | Unified types | Type 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. |
| 4 | Nav restructure | Verticals 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. |
| 5 | Extract shared objects | Staff & 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
| Today | Migration |
|---|---|
| Smart Lists | Become 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 state | Becomes 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 selection | Becomes 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 tabs | Become three seeded status views, retaining the tab affordance in List for one release so muscle memory survives, then folding into the Views picker. |
| Bookmarked URLs | 301 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 users | The 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 verticals | Phase 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.
What to measure, and what we do not yet know
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.
| Area | Proposed metric | Baseline | Target / read |
|---|---|---|---|
| Navigation | Time to reach Service configuration from Calendars | Unmeasured; structurally 4 clicks + shell switch | 1 click. Measure in moderated testing, not analytics. |
| Wrong-destination rate (opens Calendar settings when seeking an appointment, or vice versa) | Unmeasured | Establish in Phase 0; expect meaningful reduction after Phase 4. | |
| Backtracking rate (nav item opened then abandoned <3s) | Unmeasured | Directional health signal for the new tree. | |
| Appointments | Time to locate a named appointment | Unmeasured | Moderated task; expect improvement from shared search across views. |
| Time to create an appointment, by entry point | Unmeasured per-entry | Must not regress. The grouped picker adds a decision point — watch this closely in Phase 3. | |
| View-switch rate per session | Unmeasured (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 view | Smart List rate unmeasured | Primary 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 menu | n/a | Tests the Zapier-derived discoverability bet directly. | |
| Unified calendar | % of multi-vertical accounts retaining All | n/a (All does not exist) | High retention validates the default. Heavy soloing suggests density is unmanageable. |
| Type-filter change frequency per session | Vertical-dropdown changes — unmeasured | Phase 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 detected | 0 — not currently detectable | A pure-gain metric and the clearest evidence of correctness value. | |
| Mis-typed appointments (created against the wrong bookable, corrected <5 min) | Unmeasured | The key counter-metric. If unification causes booking errors, this catches it. | |
| Health | Calendar time-to-interactive at p75 / p95, by appointment volume | Unmeasured | Guardrail for Phase 3. Regression here outweighs any IA gain. |
| Scheduling-related NPS verbatims and support-ticket volume by theme | NPS ~47, detractor spike recorded | Rollback trigger, defined before rollout. |
Validation plan before the expensive phases
- 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.
- 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.
- 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.
- First-click test on the type chip vs a sidebar for visibility control, to check the Amie-derived toolbar-pill bet.
Thirteen screens
Screens 2, 3, 4 and 5 are rendered above. The rest are specified for design and engineering pickup.
| # | Screen | Specification |
|---|---|---|
| 1 | Left nav | Rendered 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. |
| 2 | Appointments › Calendar | Rendered in §11 and §12. Two-row header; week grid; four-channel encoding; all-day band for multi-day; capacity fractions; result count. |
| 3 | Appointments › List | Rendered in §14. Identical row 1; conditional Type column; new Source column; inline status editing; locked identity column. |
| 4 | Types dropdown | Rendered in §13. Per-type counts and glyphs; “Only this” solo shortcut; blocked/cancelled as explicit facets; footer pointing at disabled verticals. |
| 5 | Saved Views picker | Rendered in §13. Personal / shared split with ownership; result counts; dismissible explainer; “+ Save current view”; “Manage” to the index. |
| 6 | Create Saved View | Popover, ~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. |
| 7 | Filter builder | Right 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. |
| 8 | New appointment | Right 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. |
| 9 | Services configuration | Preserve 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. |
| 10 | Booking calendars configuration | Preserve 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. |
| 11 | Settings | Index 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.” |
| 12 | Calendar sync | Admin 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. |
| 13 | Appointment statuses | Table: 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.
Where this could go wrong
| Risk | Severity | Mitigation |
|---|---|---|
| Unified query performance | High | The 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 density | High | Four 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 5 | Med-high | Merging 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-adopted | Medium | “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 cost | Low-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 users | Medium | “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 support | Low-med | Two 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 expectation | Low-med | Stakeholders 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 sources | Medium | Changes 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 platform | Low-med | Saved 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. |
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.
| Criterion | Current | A · Appts-first | B · Vertical-first | C · Hybrid | D · 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 | 24 | 45 | 29 | 50 | 55 |
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
- 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.
- 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.
- 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.
- Rename “Meetings” to “Booking calendars”; keep “Calendars” as the product name. Fix the collision at the child level, where it is cheap.
- Keep Statuses in Calendars. Split Connections. Both against the brief’s stated suspicion, for the reasons in §16.
- Sequence by blast radius, not coherence (§21). Instrument first; extract shared objects last.
- Run the tree test before Phase 1. It is the cheapest study available and it tests the naming decisions this whole tree rests on.
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.
What I could not resolve from the available evidence
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.