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
fullyUnavailableThe 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) |
7a. Meeting Limit — Scheduling Link
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.
Change Link Destinations Summary
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 |
— |
Change Link Visibility Rules
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)
Change Links Must Use Host-Specific URLs
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.