Skip to content

Show booking host working-hours status and manage shared availability - #4999

Merged
JasonYangCIS merged 36 commits into
mainfrom
ai_main_60a606a93fe947758482
Sep 15, 2026
Merged

JasonYangCIS merged 36 commits into
mainfrom
ai_main_60a606a93fe947758482

Conversation

@JasonYangCIS

@JasonYangCIS JasonYangCIS commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a per-host indicator in the booking-link editor showing whether a host's real working hours (vs. just free/busy) are being used, with a one-click request flow to fix it, and moves peer-relationship management into a dedicated "Shared availability" tab on the booking-links page.

Clip:
https://clips.agent-native.com/share/o440oSgx9XZG?ref=clip_share

Problem

Booking links silently degrade to free/busy-only checking when a co-host hasn't reciprocally added the link owner to their own calendar, or hasn't saved a working-hours schedule. Owners had no visibility into this degradation and no way to act on it. Separately, peer/overlay management was buried in a collapsible "People" section of the sidebar, mixed in with unrelated calendar visibility controls, and duplicated with a hover-only remove action that risked accidentally severing a peer relationship.

Solution

  • Added server helpers and read/write actions that report, per host, whether the relationship is reciprocal and whether a schedule has been saved, always resolving identity from the booking link's persisted owner (never the caller) and scoping non-owner callers to the link's own host list.
  • Added a rate-limited (send-overlay-request) action that emails a peer asking them to add the owner back, guarded by an overlay-list-only check, per-peer cooldown, per-owner daily cap, and an atomic reservation to prevent duplicate sends.
  • Added a status icon component with four states (applied, pending schedule, not reciprocal, manual host) rendered in the booking-link host chips.
  • Added a deep-link path (navigate's addPersonEmail) and a dedicated /shared-availability/add landing page so the request email's CTA works reliably for a signed-out recipient clicking from their inbox.
  • Moved peer management out of the sidebar's "People" group into a new "Shared availability" tab on the booking-links page, leaving the sidebar with only color/visibility controls, merged with any directly-shared Google calendar for the same person.

Key Changes

  • server/lib/booking-host-availability.ts: extracted resolvePeerScheduleAndTimezone, added getHostOverlayStatuses and getOverlayReciprocity.
  • New actions: get-host-overlay-status, get-overlay-reciprocity, send-overlay-request, each with extensive access-control and rate-limit tests.
  • New server/lib/overlay-request-emails.ts and overlay-request-reservation.ts, plus a registered transactional email.
  • New client hooks use-host-overlay-status.ts and component HostOverlayStatusIcon.tsx (four visual states, new --success theme token in global.css).
  • New SharedAvailabilityPanel.tsx component and AddSharedAvailability.tsx page/route.
  • BookingLinksPage.tsx: added a "shared" tab with URL deep-linking, wired host status icons into BookingHostsEditor.
  • Sidebar.tsx: removed the "People" collapsible, merged overlay peers with matching shared Google calendars into single rows, dropped the hover-only remove button.
  • AddCalendarDialog.tsx / AppLayout.tsx / use-navigation-state.ts: added prefillPersonEmail / addPersonEmail support to open the add-people dialog prefilled without auto-adding.
  • use-events.ts / use-google-calendars.ts: batched overlay email requests past the 10-email server cap and avoided optimistic-mutation cross-contamination between own and overlay events.
  • Localization: added ~15 new keys across all supported locales for the new copy.
  • Documentation: updated .agents/skills/availability-booking/SKILL.md and added implementation plan docs under docs/plans/.
  • packages/core: corrected the renderEmail paragraphs doc comment to clarify strings are injected verbatim (not escaped), with a changeset.

Edit in Builder  Preview


To clone this PR locally use the Github CLI with command gh pr checkout 4999

You can tag me at @BuilderIO for anything you want me to fix or change

Adds get-host-overlay-status and get-overlay-reciprocity actions, the
shared overlay-request reservation state module, and person-calendar
helpers used to detect whether a booking-link host has added the link
owner back to their own calendar and has real working hours configured.
Adds the send-overlay-request action with an atomic, reservation-based
rate limiter (per-peer cooldown, per-owner daily cap tracked as a
fixed-window counter, and stale-reservation recovery) plus the email
template it sends. Fixes email-template.ts paragraph escaping so
plain-text email bodies render correctly.
Adds HostOverlayStatusIcon and SharedAvailabilityPanel to surface, per
booking-link host, whether they've added the link owner back and
whether their real working hours are applied, with loading/error/empty
states and a keyboard-accessible tooltip. Wires the send-overlay-request
action into BookingLinksPage.
Sidebar previously toggled a merged person/Google calendar row's two
underlying visibility flags independently, which could require two
clicks to actually show/hide it, and CalendarView filtered overlay
events without accounting for a hidden merged Google source. Both now
resolve to a single merged visible/hidden state, and the sidebar
alphabetizes and dedupes shared/overlay calendar entries.
formatRelativeTimeFromNow used a hardcoded 'en' locale and returned the
literal string 'just now' instead of an Intl.RelativeTimeFormat value;
it now takes the active locale and lets the formatter produce the
localized string. Adds the overlayRequestInProgress string across all
locale catalogs and shortens shared-availability copy.
… design

Adds the design/plan docs for the shared-availability tab and the
booking-host working-hours status feature, the user-facing changelog
entry, and updates the availability-booking skill notes.
Sidebar status checks and the main events query both send overlayEmails
to list-events, which caps the array at 10. Beyond that, the sidebar
status query 400'd outright and event loading silently dropped events
for peers past the 10th.

Both now split overlayEmails into batches of 10: the sidebar status
check runs one inventory query per batch, and useEvents keeps the
primary list-events query on the first batch while fetching extra
batches as separate, source-scoped, read-only queries merged into the
final event list. Mutation invalidation, prefetch, loading/placeholder
state, and error surfacing all account for the extra batches.
@builder-io-integration builder-io-integration Bot changed the title Update from the Builder.io agent Show booking host working-hours status and manage shared availability Sep 14, 2026
builder-io-integration[bot]

This comment was marked as outdated.

@JasonYangCIS

Copy link
Copy Markdown
Contributor Author

@builderio-bot review the latest PR feedback (any commenter) and fix what you agree with — be skeptical, and reply on each comment thread with what you did and why (or why not). Also flag accessibility (WCAG 2.1 AA), performance, and security issues in the changed code; fix the easy ones, flag anything that needs a judgment call.

@builder-io-integration

Copy link
Copy Markdown
Contributor

Addressed the automated review feedback:

  • 🟡 Deep-link prefill clobbered by open-reset effect — fixed in AddCalendarDialog.tsx (merged the two effects; see inline reply).
  • 🟡 Stale in-flight reservation permits duplicate emails — fixed in send-overlay-request.ts by bounding sendEmail below PENDING_STALE_MS (see inline reply).
  • 🟡 Free-busy-only Google sources hide overlay events — fixed in CalendarView.tsx (see inline reply).
  • 🟢 Feeds sidebar label bypasses locale catalog — fixed. The sidebar.feedsGroup key already exists (and is already translated) in every locale in i18n-data.ts; the label in Sidebar.tsx just wasn't using it. Swapped the hardcoded "Feeds" string for t("sidebar.feedsGroup").

Additional pass for accessibility/performance/security in the changed code, per the request:

  • Accessibility: HostOverlayStatusIcon and SharedAvailabilityPanel already pair every status icon/color indicator with an aria-label and a text tooltip (not color alone), use real <button>s with focus-visible rings, and the peer-color dot is aria-hidden with a visible name/email alongside it. Destructive removal goes through a confirm popover, not confirm(). No changes needed.
  • Performance: useHostOverlayStatus batches host emails (cap 50/request) and status/reciprocity reads are gated behind enabled so they don't fire on every navigation; getHostOverlayStatuses/getEligibleHostAvailability skip the (up to) two extra settings reads + Google timezone lookup for any peer that isn't reciprocal. No issues found.
  • Security: get-host-overlay-status and send-overlay-request deliberately require editor (not viewer) access on the booking link and scope every read/send to the owner's own overlay list, with comments calling out why (a public link resolving to viewer would otherwise make this an oracle over the owner's peers). The overlay-request email CRLF-strips and HTML-escapes (emailStrong) the user-editable name before it reaches the subject line or body. Nothing found that needs a fix; no judgment calls to flag.

Ran the calendar template's full test suite (743 tests, all passing), pnpm typecheck (clean, aside from unrelated local dev-environment production-config warnings), and pnpm guards (73/73 passing, including i18n catalog checks) after these changes.

@builder-io-integration

Copy link
Copy Markdown
Contributor

Pushed to the PR branch. Here's a summary of what was done on PR #4999:

Fixed (3 medium + 1 low from the automated review):

  1. Deep-link prefill clobbered (AddCalendarDialog.tsx) — merged the prefill effect and the open-reset effect so the reset can no longer fire after and wipe the prefilled email.
  2. Duplicate-email race (send-overlay-request.ts) — bounded the sendEmail call with timeoutMs below PENDING_STALE_MS, so a call can't still be sending once its own reservation goes stale enough for another call to reclaim it.
  3. Free-busy-only sources hiding overlay events (CalendarView.tsx) — excluded freeBusyReader sources from googleHiddenPersonEmails, matching the exclusion enabledGoogleSources already applies.
  4. Hardcoded "Feeds" label (Sidebar.tsx) — swapped for t("sidebar.feedsGroup"), a key that was already translated in every locale but just wasn't wired up.

Reviewed and found clean (a11y/perf/security pass across the diff): status icons pair color with text/aria-labels, destructive actions use confirm popovers not confirm(), host-status queries are batched and gated, and the access-control/oracle-prevention checks and email header-injection/escaping in the new actions were already solid.

Replied on each of the three inline review threads, posted a summary comment on the PR, verified with the full calendar test suite (743 passing), typecheck, and pnpm guards (73/73), then pushed the fix commit.



🟢 Fixed and pushed all agreed-on PR review findings, replied on each thread, verified with tests/typecheck/guards.

builder-io-integration[bot]

This comment was marked as outdated.

@JasonYangCIS

Copy link
Copy Markdown
Contributor Author

@builderio-bot review the latest PR feedback (any commenter) and fix what you agree with — be skeptical, and reply on each comment thread with what you did and why (or why not). Also flag accessibility (WCAG 2.1 AA), performance, and security issues in the changed code; fix the easy ones, flag anything that needs a judgment call.

@builder-io-integration

Copy link
Copy Markdown
Contributor

No merge conflicts (mergeable: MERGEABLE); BLOCKED just reflects CI re-running against the fresh commit, which will need a few minutes to complete.

Summary: Fixed a real concurrency bug — the overlay-people add/remove/color-change mutations read-then-fully-replaced the list client-side, which is a classic lost-update race across tabs or UI surfaces. Fixed it the same way this PR already fixes an analogous problem elsewhere (send-overlay-request.ts's mutateUserSetting compare-and-swap): added three small atomic server actions and pointed the client hooks at them instead. Verified in the live preview, full test suite (743 passing), typecheck, and guards (73/73), with no merge conflicts.



🟡 Fixed the concurrency race and pushed; CI checks are still running against the new commit (no failures seen yet, no conflicts) — worth a final gh pr checks 4999 glance once they finish.

@builder-io-integration builder-io-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Builder reviewed your changes and found 2 potential issues 🟡

Review Details

Incremental Code Review Summary

The previously open concurrent overlay mutation issue is fixed. Add, remove, and color updates now use dedicated server actions with mutateUserSetting compare-and-swap semantics instead of client-derived full-list replacements; that comment was verified and resolved.

Two new correctness issues remain in the new server action layer. The PR remains high risk because it combines shared availability settings, agent-callable mutations, and rate-limited transactional email behavior.

New Findings

  • 🟡 MEDIUM — Pruning stale pending reservations removes the per-peer marker but does not decrement the daily count that was reserved before delivery. Repeated abandoned sends can exhaust the owner’s daily quota without delivering emails.
  • 🟡 MEDIUM — The atomic add action compares email strings case-sensitively, allowing case variants of the same address to become duplicate overlay peers despite case-insensitive behavior elsewhere.

Previously reported email-delivery uncertainty concerns were not reposted under incremental-review deduplication rules.

🧪 Browser testing: Will run after this review (PR touches UI code)

Comment thread templates/calendar/actions/send-overlay-request.ts Outdated
Comment thread templates/calendar/actions/add-overlay-person.ts Outdated
…ation quota leak

- add/remove/update-overlay-person-color now compare emails case-insensitively, matching the rest of the codebase's normalization convention. Previously Peer@Example.com and peer@example.com could exist as duplicate rows.
- send-overlay-request now releases the daily-cap slot when pruning an abandoned pending: reservation, so a crashed/timed-out send no longer permanently consumes one of the owner's 20 daily request slots.
@builder-io-integration

Copy link
Copy Markdown
Contributor

Addressed the latest incremental review (2 new findings):

Fixed (2):

  • 🟡 Stale reservations permanently consumed the daily send quota — agreed. send-overlay-request's pruning loop dropped an abandoned pending: reservation from perPeer but never decremented the dailyCounts slot it had reserved, so a crashed/timed-out send permanently burned one of the owner's 20 daily slots. Fixed by decrementing dailyCounts[dayFromReservationTimestamp] in the same atomic update that prunes the stale entry.
  • 🟡 Case variants could create duplicate overlay peers — agreed. The new add-overlay-person/remove-overlay-person/update-overlay-person-color actions compared emails byte-for-byte, while the rest of the codebase (and the UI) treats them case-insensitively. All three now normalize with .trim().toLowerCase() before comparing.

Verified: 743 tests pass, typecheck clean, all 73 guard checks pass, oxfmt clean.

No merge conflicts (mergeable: MERGEABLE); BLOCKED just reflects required checks re-running against the fresh commit (b68ec3458c).

I also re-scanned the diff for accessibility/performance/security issues — the new server actions have no UI surface (a11y n/a), only mutate the calling user's own settings (no cross-user access concern), and are covered by the existing case-insensitivity/rate-limit fixes above. No new issues found.

@github-actions
github-actions Bot temporarily deployed to pr-4999-content September 15, 2026 01:33 Destroyed
@github-actions
github-actions Bot temporarily deployed to pr-4999-analytics September 15, 2026 01:33 Destroyed
@github-actions
github-actions Bot temporarily deployed to pr-4999-fw September 15, 2026 01:34 Destroyed
@github-actions
github-actions Bot temporarily deployed to pr-4999-clips September 15, 2026 01:35 Destroyed
@github-actions
github-actions Bot temporarily deployed to pr-4999-dispatch September 15, 2026 01:35 Destroyed
@github-actions
github-actions Bot temporarily deployed to pr-4999-slides September 15, 2026 01:35 Destroyed
@github-actions
github-actions Bot temporarily deployed to pr-4999-forms September 15, 2026 01:35 Destroyed
@github-actions
github-actions Bot temporarily deployed to pr-4999-plan September 15, 2026 01:35 Destroyed
@github-actions
github-actions Bot temporarily deployed to pr-4999-assets September 15, 2026 01:36 Destroyed
@github-actions
github-actions Bot temporarily deployed to pr-4999-starter September 15, 2026 01:36 Destroyed
@github-actions
github-actions Bot temporarily deployed to pr-4999-calendar September 15, 2026 01:37 Destroyed
@github-actions
github-actions Bot temporarily deployed to pr-4999-design September 15, 2026 01:38 Destroyed
@github-actions
github-actions Bot temporarily deployed to pr-4999-mail September 15, 2026 01:38 Destroyed
…60a606a93fe947758482

# Conflicts:
#	templates/calendar/app/hooks/use-events.ts
#	templates/calendar/app/pages/CalendarView.tsx
@JasonYangCIS
JasonYangCIS merged commit 326d4cf into main Sep 15, 2026
62 of 76 checks passed
@JasonYangCIS
JasonYangCIS deleted the ai_main_60a606a93fe947758482 branch September 15, 2026 21:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants