Troubleshooting Feature Revamp — PRD

Overview

The troubleshooting feature allows admins/hosts to understand why specific time slots or entire days are unavailable for booking. It is accessible via a "Start troubleshooting" toggle on the public booking page and is only visible to logged-in admins/hosts — clients never see it.

When troubleshooting is active:

  • All time slots for the day are shown (not just available ones)

  • Each slot shows "Available" or "Unavailable" with a help icon to see reasons

  • Days that are entirely unavailable show a "Whole day unavailable" section with reasons

  • Actionable reasons include a "Change" or "View" link to the relevant admin page

  • Available slots may show info icons explaining why they're available

Key Changes From Current Implementation

Change

Details

Show ALL reasons per slot

Currently only one reason is shown. Revamp shows every applicable reason.

Change/View links

Each reason links to the admin page where the issue can be resolved.

"No slots available? Troubleshoot" link

When a day has no available slots, hosts/admins see a link to enter troubleshoot mode.

Whole day blockers

Day-level blockers shown in a summary card: "Whole day unavailable because..."

Booking limit separation

Separate messages for scheduling-link-level vs host-level limits.

Internal vs external meetings

Internal NeetoCal bookings show a "View" button. External calendar events show calendar name.

Show why slots ARE available

Info icon on available slots when they're available due to an override.

Duration exceeds availability

New reason when meeting duration extends past the availability window.

Group meeting filled

Updated copy: "Maximum group size of {n} has been reached."

Date range exceeded

New reason for slots outside the meeting's configured date range.

Revamped precedence order

Reasons displayed in a defined priority order (see below).

Round-robin / multihost per-host breakdown

Common reasons shown once at top; host-specific reasons shown per host with avatars.

Timezone warning

Modal when page timezone differs from admin's profile timezone.

Color coding days

Fully unavailable days will be grey

Future Considerations

  • Shareable troubleshoot link — a "Copy link" button to share the troubleshoot URL with customer support.

  • NeetoChat fallback — Under "Questions about slots?", show "Still not able to figure out the issue? Chat with us." which opens NeetoChat. Only if NeetoChat is enabled for the organization.

  • Slot flooding solution — When availability is 9–5, showing midnight-to-midnight slots creates noise. Options: collapse outside-availability slots into a band, or show only working-hours slots by default with a "Show all 24h" toggle.

  • Expanded layout — Replace narrow slot-list + popover with a split panel view (calendar left, full-width reason panel right) where clicking a slot shows its breakdown inline.

  • Outside availability as a distinct visual state — Dim/fade outside-availability slots instead of showing them as regular "unavailable" — they were never in play, which is different from "blocked by something."


Timezone Warning Modal

When troubleshooting starts, if the booking page timezone differs from the admin's profile timezone, a modal overlay appears.

Field

Value

Title

You're viewing slots in a different time zone

Description

You're viewing slots in {pageTimezone}, but your profile is set to {profileTimezone}. We recommend switching to your profile time zone to avoid confusion during troubleshooting.

Action

Continue anyway — dismisses the modal

The modal appears as a blurred backdrop overlay over the calendar. If the admin manually changes the timezone to match their profile, the modal auto-dismisses. No auto-switch button — admins who want to switch timezone do so manually via the timezone picker.


Calendar Day Styling

Day States

State

Background

Text

Hover

Border

Available

Theme secondary

Theme secondary text

Theme primary + primary text (0.15s ease)

None

Selected

Theme primary

Theme primary text

No change

None

Unavailable (troubleshoot)

gray-200

gray-600

gray-500 + white (0.15s ease)

None

Selected + Unavailable

gray-500

White

No change

None

Disabled (past, non-troubleshoot)

Transparent

gray-600

No hover (pointer-events: none)

None

When a Day Turns Grey

A day turns grey in troubleshoot mode when ANY of:

  • The date is in the past

  • The backend marks it as fullyUnavailable

  • The date has no slots in the API response

  • All slots on the date have isAvailable: false

Grey days remain clickable in troubleshoot mode so admins can see why.


Day-Level Display: "Whole Day Unavailable"

When all slots on a day are unavailable, a summary card appears above the slot list.

  • Card header: "Whole day unavailable because"

  • Card body: Lists the day-level reasons (see table below).

Day-Level Reasons

These are reasons that block the entire day, not just individual slots.

[CHANGE] Added "Past Time" as day-level reason #0. This was discussed in the walkthrough video — when the entire day is in the past, a banner should appear rather than forcing users to click each slot.

#

Reason

Title

Message

Change Link

0

Past time

Past Time

This day has already passed.

None

1

Holiday

Holiday

This day is marked as a holiday for {holidayName} in NeetoCal.

[Change] -> Holidays settings

2

Date range exceeded

Date Range

This slot falls outside the date range set for this meeting.

[Change] -> Scheduling link settings (/where)

3

Lead time

Lead Time

Lead time of {leadTime} prevents this day from being available.

[Change] -> Scheduling link settings (/where)

4

Daily limit (scheduling link)

Daily Limit

Daily booking limit of {limitNo} has been reached for this scheduling link.

[Change] -> Scheduling link limit settings

5

Weekly limit (scheduling link)

Weekly Limit

Weekly booking limit of {limitNo} has been reached for this scheduling link.

[Change] -> Scheduling link limit settings

6

Monthly limit (scheduling link)

Monthly Limit

Monthly booking limit of {limitNo} has been reached for this scheduling link.

[Change] -> Scheduling link limit settings

7

Daily limit (host)

Daily Limit

Daily booking limit of {limitNo} has been reached for {hostName}.

[Change] -> Host booking limits

8

Weekly limit (host)

Weekly Limit

Weekly booking limit of {limitNo} has been reached for {hostName}.

[Change] -> Host booking limits

9

Monthly limit (host)

Monthly Limit

Monthly booking limit of {limitNo} has been reached for {hostName}.

[Change] -> Host booking limits

10

Host availability (no hours)

Host Availability

{hostName} is not available on this day as per {availabilityName}.

[Change] -> Host availability page

Day-Level Detection

A day is marked fully unavailable when:

  • The date is in the past

  • All slots are blocked by a common reason (lead time, holiday, booking limits, date range)

  • All members are outside their working hours (round-robin/multihost)

  • All members have day-level blockers (round-robin/multihost)


Slot-Level Reasons — Complete Reference

Display Precedence Order

When multiple reasons apply to the same slot, they are displayed in this order (top to bottom):

Order

Reason

Rationale

1

Past time

Obvious, no action needed

2

Date range exceeded

Event-level setting — slot was never meant to be bookable

3

Host Unavailable

Fundamental — if hours aren't set, nothing else matters

4

Holiday

Explicit intent to be unavailable

5

Availability override

Explicit override for specific date

6

Lead time

Time-based rule blocking near-term slots

7

Meeting limit (scheduling link)

Day/week/month limits on the scheduling link

7

Meeting limit (host)

Per-host day/week/month limits

7

Duration exceeds availability

Gap exists but isn't long enough

7

Buffer time

Adjacent event's buffer eating into slot

7

Overlapping meeting (internal)

Another NeetoCal booking

7

Calendar conflict (external)

External calendar blocking

8

Group event full

Capacity reached

All reasons are shown (not just the highest priority). This order only determines the display sequence within the popover.

Merging Priority (Backend)

When the backend merges conflicting reasons for the same slot, it keeps the higher-priority reason as the "primary":

Priority

Reasons

4 (highest)

booking, booking_limit_per_slot_reached

3

google_calendar_event, outlook_event, icloud_event

2

booking_before_buffer, booking_after_buffer

1

calendar_event_before_buffer, calendar_event_after_buffer

0 (lowest)

slot_before_buffer, slot_after_buffer


Complete Slot-Level Reason Table

Each reason shows: Title (bold, text-xs font-bold), Message (grey, text-xs), and optionally an inline Change/View link after the message text.

1. Past Time

Slot is in the past. Grey out all past slots but show them as they were — whether available or unavailable. If unavailable, show the original reasons.

Field

Value

Title

Past

Message (was available)

Slot is in the past, but was available.

Message (was unavailable)

Slot is in the past, but was unavailable.

Change link

None

2. Date Range Exceeded

Slot falls outside the meeting's configured date range. Grey out all slots on the day, mark all as unavailable.

Field

Value

Title

Date Range

Message

This slot falls outside the date range set for this meeting.

Change link

[Change] -> Scheduling link settings (/where)

3. Outside Host's Availability

Slot is outside the host's configured working hours. Grey out slots outside availability. These slots are not shown in normal mode.

Field

Value

Title

Host Unavailable

Message

Slot is outside {hostName}'s availability {availabilityName}.

Change link

[Change] -> Host availability page (/host/self/availability/{id})

Change link visibility: Only show if the troubleshooting user is the host themselves or an admin.

Special rendering for host availability (single range):

{hostName} is only available from {start} to {end} as per {availabilityName}. [Change]

Special rendering for host availability (multiple ranges):

{hostName} is only available at the following times:

  • 09:00 AM to 10:00 AM

  • 11:15 AM to 12:15 PM

  • 01:15 PM to 02:15 PM

As per {availabilityName}. [Change]

No working hours on the day:

{hostName} is not available on this day as per {availabilityName}. [Change]

4. Holiday

Day is marked as a holiday. Grey out all slots, mark all as unavailable.

Field

Value

Title

Holiday

Message

Holiday for {holidayName}.

Change link

[Change] -> Holidays settings (/admin-panel/general/holidays)

5. Availability Override

Explicit override for a specific date.

Field

Value

Title

Availability Override

Message

See below

Change link

[Change] -> Host availability page (/host/self/availability/{id})

Change link visibility: Only show if the troubleshooting user is the host themselves or an admin.

Special rendering (single range):

{hostName} is only available from {start} to {end} due to an availability override in {availabilityName}. [Change]

Special rendering (multiple ranges):

Same vertical list format, with "As per availability override in {availabilityName}." [Change]

For available slots (for all available slots):

Show help popover with message. This slot is available because of an availability override in {availabilityName}. [Change]

6. Lead Time

Lead time prevents near-term slots from being bookable.

Field

Value

Title

Lead Time

Message

Lead time of {leadTime} prevents this slot from being available.

Change link

[Change] -> Scheduling link settings (/where)

Booking limit reached at the scheduling link level.

Field

Value

Title (daily)

Daily Limit

Message (daily)

Daily booking limit of {limitNo} has been reached for this scheduling link.

Title (weekly)

Weekly Limit

Message (weekly)

Weekly booking limit of {limitNo} has been reached for this scheduling link.

Title (monthly)

Monthly Limit

Message (monthly)

Monthly booking limit of {limitNo} has been reached for this scheduling link.

Change link

[Change] -> Scheduling link limit settings (/settings/limit)

7b. Meeting Limit — Host

Booking limit reached at the host level.

Field

Value

Title (daily)

Daily Limit

Message (daily)

Daily booking limit of {limitNo} has been reached for {hostName}.

Title (weekly)

Weekly Limit

Message (weekly)

Weekly booking limit of {limitNo} has been reached for {hostName}.

Title (monthly)

Monthly Limit

Message (monthly)

Monthly booking limit of {limitNo} has been reached for {hostName}.

Change link

[Change] -> Host booking limits (/host/self/booking-limits)

Change link visibility: Only show if the troubleshooting user is the host themselves (v1).

7c. Duration Exceeds Availability

Meeting duration extends past the end of the host's availability window. These slots are not shown in normal mode.

Field

Value

Title

Duration Exceeds

Message

Meeting duration extends past {hostName}'s availability {availabilityName}.

Change link

[Change] -> Host availability page (/host/self/availability/{id})

Change link visibility: Only show if the troubleshooting user is the host themselves (v1).

Implementation note: This reason must be generated by the backend. The prototype computes it on the frontend, but the production implementation should move this to time_frames_to_slots_service.rb.

7d. Overlapping Meeting — Internal (NeetoCal Booking)

Conflict with another booking made through NeetoCal. These are treated differently from external events because we can link directly to the booking.

Field

Value

Title

Conflict

Message

Conflicts with meeting {meetingName} with {clientName}.

Link

[View] -> Booking detail page

Note: "View" link (not "Change") because this is an existing booking, not a setting.

7e. Overlapping Meeting — External Calendar Event

Conflict with an event from a connected external calendar.

Field

Value

Title

Conflict

Message (Google)

Conflicts with Google Calendar event {eventName} on calendar {calendarName}.

Message (Outlook)

Conflicts with Outlook Calendar event {eventName} on calendar {calendarName}.

Message (iCloud)

Conflicts with iCloud Calendar event {eventName} on calendar {calendarName}.

Change link

None

iCloud Travel Time variant:

Field

Value

Title

iCloud Travel Time

Message

Conflicts with travel time of {travelTime} minutes before iCloud Calendar event {eventName} on calendar {calendarName}.

Change link

None

7f. Buffer Time

Buffer time from adjacent events eating into this slot. Two distinct cases:

Case 1: External event has its own buffer configured

Note: It is unclear whether this scenario can actually occur in practice. Keeping it documented for completeness, but engineering should verify if this case is possible before implementing.

The external event's buffer time bleeds into this slot.

Field

Value

Title

Buffer

Message (before)

Blocked by {bufferTime} buffer before event {eventName} on calendar {calendarName}.

Message (after)

Blocked by {bufferTime} buffer after event {eventName} on calendar {calendarName}.

Change link

None

Case 2: This scheduling link requires buffer, and an adjacent event prevents it

The scheduling link's buffer requirement can't be satisfied because of a nearby event.

Field

Value

Title

Buffer

Message (before)

External: Cannot satisfy the before buffer of {bufferTime} required due to event {eventName} on calendar {calendarName}. / Internal: Cannot satisfy the before buffer of {bufferTime} required due to meeting {meetingName} with {clientName}.

Message (after)

External: Cannot satisfy the after buffer of {bufferTime} required due to event {eventName} on calendar {calendarName}. / Internal: Cannot satisfy the after buffer of {bufferTime} required due to meeting {meetingName} with {clientName}.

Change link

[Change] -> Host availability page (/host/self/availability/{id})

Change link visibility: Only show if the troubleshooting user is the host themselves (v1).

Buffer from internal NeetoCal bookings:

Field

Value

Title

Buffer

Message (before)

Blocked by {bufferTime} buffer before meeting {meetingName} with {clientName}.

Message (after)

Blocked by {bufferTime} buffer after meeting {meetingName} with {clientName}.

Link

[View] -> Booking detail page

8. Group Event Full

Maximum capacity reached for a group scheduling slot.

Field

Value

Title

Max Group Size

Message

Maximum group size of {groupSize} has been reached.

Change link

[Change] -> Scheduling link settings (/what)

Other Slot-Level Reasons

Reason

Title

Message

Change Link

Members unavailable

{unavailableMemberNames} is unavailable.

None

Note: too_soon and members_unavailable must be generated by the backend. The prototype has translation keys but the reasons are not yet generated — they need to be added in the backend phase.


Available Slot Info

Available slots normally show no help icon. In these specific cases, an info (i) icon appears:

Available Due to Availability Override

When a slot is available because an override expanded hours beyond the original working hours:

Field

Value

Icon

Info (i)

Message

This slot is available because of an availability override in {availabilityName}.

Available But in the Past

When a slot was available but the time has now passed:

Field

Value

Icon

Info (i)

Message

Slot was available, but the time has now passed.

Available Due to Free External Event (Future consideration)

When an external calendar event is marked as "free" (not blocking):

To be specified in a future iteration.


Multi-Member Meetings (Round Robin & Multihost)

Behavior Differences by Meeting Type

Behavior

One-on-One / Group

Round Robin

Multihost

Slot available when

Host is free

ANY host is free

ALL hosts are free

Day unavailable when

All slots blocked

ALL hosts blocked for all slots

ANY host blocked for all slots

Per-host breakdown

Not shown

Shown

Shown

Available slot in round-robin

N/A

Still shows per-host info icon with reasons for unavailable hosts

N/A

Shared vs Host-Specific Reasons

For multi-member meetings, day-level reasons split into:

Shared reasons (shown once at the top, NOT repeated per host): Holiday, Lead time, Date range exceeded, Booking limits (scheduling-link-level: daily/weekly/monthly)

Host-specific reasons (shown per host): Host availability / no hours, Booking limits (host-level: daily/weekly/monthly)

When shared reasons exist (e.g., holiday), ALL hosts are treated as unavailable regardless of backend per-host status.

Per-Host UI

[CHANGE] Updated host section ordering for round-robin meetings. Per the walkthrough video, the order of "Available hosts" and "Unavailable hosts" sections should flip depending on whether the slot itself is available or unavailable. This helps users focus on the most relevant information first.

For unavailable slots (and all multihost slots):

  • Unavailable hosts section (shown first):

    • Heading: "Unavailable hosts" (text error-600, body2, semibold)

    • Red pill badge: {count}/{total} (background error-100, text error-600)

    • Each host: 24px circular avatar + name (text-xs font-medium gray-800) + reasons expanded below

  • Available hosts section (shown second):

    • Heading: "Available hosts" (text success-600, body2, semibold)

    • Green pill badge: {count}/{total} (background success-100, text success-600)

    • Each host: avatar + name only, no reasons

For available slots in round-robin meetings:

  • Available hosts section (shown first):

    • Heading: "Available hosts" (text success-600, body2, semibold)

    • Green pill badge: {count}/{total} (background success-100, text success-600)

    • Each host: avatar + name only, no reasons

  • Unavailable hosts section (shown second):

    • Heading: "Unavailable hosts" (text error-600, body2, semibold)

    • Red pill badge: {count}/{total} (background error-100, text error-600)

    • Each host: 24px circular avatar + name (text-xs font-medium gray-800) + reasons expanded below

No line separators between hosts. Reasons always expanded (no accordion).

Slot-Level Popover for Multi-Member

The popover shows:

  • General reasons (meeting-level blockers) at top with inline links

  • Host sections ordered as per the rules above (unavailable-first for unavailable slots; available-first for available round-robin slots)

Popover: min-width 400px, max-height 320px with vertical scroll.


Admin Page

Reasons Linking Here

Link Label

Scheduling link settings (/where)

Lead time, date range exceeded

Change

Scheduling link limit settings (/settings/limit)

Booking limit daily/weekly/monthly (scheduling-link-level)

Change

Scheduling link settings (/what)

Group event full

Change

Host booking limits (/host/self/booking-limits)

Booking limit daily/weekly/monthly (host-level)

Change

Host availability page (/host/self/availability/{id})

Host availability, availability override, duration mismatch, buffer time (case 2), slot buffer

Change

Holidays settings (/admin-panel/general/holidays)

Holiday

Change

Booking detail page

Internal booking conflict, booking buffer

View

No link

External calendar events, external buffer (case 1), iCloud travel time, past time, members unavailable, group slot overlap

Some change links should only be shown to the relevant host or to admins:

  • Host availability, availability override, duration mismatch, host booking limits, buffer (case 2) -> show only if the troubleshooting user IS the host or is an admin

  • All other change links -> always shown


Reason Item UI Layout

Title (text-xs font-bold)
Description text in grey (text-xs, gray-600). Change <- inline link

Slot hover: theme primary color with 0.15s ease transition.


Whole Day Unavailable — Layout Example

+-- Whole day unavailable because ------------------+
|                                                    |
|  Holiday                                           |
|  This day is marked as a holiday for Holi          |
|  in NeetoCal. Change                               |
|                                                    |
|  -- Multi-member only: -------------------------   |
|                                                    |
|  Unavailable hosts [5/6] <- red pill               |
|                                                    |
|  [avatar] Cypress User                             |
|    Host Availability                               |
|    Cypress User is only available from             |
|    09:00 AM to 05:00 PM as per                     |
|    Working hours. Change                           |
|                                                    |
|  [avatar] Oliver Smith                             |
|    Daily Limit                                     |
|    Daily booking limit of 5 has been reached       |
|    for Oliver Smith. Change                        |
|                                                    |
|  Available hosts [1/6] <- green pill               |
|                                                    |
|  [avatar] Jane Doe                                 |
|                                                    |
+----------------------------------------------------+
                See individual slots v

Known Engineering Considerations

These are edge cases and technical notes the engineering team should be aware of during implementation.

Timezone + Day Boundary

When the page timezone and host timezone differ significantly (e.g., host in IST, page in PST), a slot at 11:30 PM PST is actually the next calendar day in IST. This can cause day-level reasons (like holidays) to be applied incorrectly. The prototype has a known bug with holidays related to this. All day-boundary logic in time_frames_to_slots_service.rb must use the client timezone consistently.

Buffer Time: Two Distinct Cases

The backend already generates different reason keys for these — calendar_event_before/after_buffer (case 1: external event's buffer) vs slot_before/after_buffer (case 2: scheduling link's buffer requirement). These must remain separate because:

  • Case 1 has no change link (admin can't change the external event's buffer from NeetoCal). Note: It is unclear whether this case can actually occur in practice — engineering should verify.

  • Case 2 has a change link to the host availability page (admin can adjust the scheduling link's buffer setting)

When an admin troubleshoots a round-robin/multihost meeting, change links for host-specific reasons (availability, booking limits) must link to that host's settings page, not the admin's own. The URL should use the host's SID, not /host/self/.

Availability Override Detection

The override reason applies in two directions:

  • Override contracted hours (slot was available but override made it unavailable) -> show override reason with "Change" link

  • Override expanded hours (slot is now available because override added hours) -> show info icon: "available because of override"

The backend must track the original (pre-override) availability periods to distinguish which slots fall inside vs outside the original hours.