Skip to content

feat(companion): add upcoming bookings widget for iOS and Android#27199

Merged
dhairyashiil merged 12 commits intomainfrom
devin/1769185918-companion-upcoming-bookings-widget
Feb 6, 2026
Merged

feat(companion): add upcoming bookings widget for iOS and Android#27199
dhairyashiil merged 12 commits intomainfrom
devin/1769185918-companion-upcoming-bookings-widget

Conversation

@PeerRich
Copy link
Member

@PeerRich PeerRich commented Jan 23, 2026

What does this PR do?

Adds a home screen widget for the Cal.com companion app that displays upcoming bookings. The widget is implemented for both iOS and Android platforms.

iOS Implementation:

  • SwiftUI-based widget using @bacons/apple-targets
  • Uses App Groups for data sharing between app and widget via ExtensionStorage
  • Supports small (2 bookings) and medium (4 bookings) widget sizes
  • 15-minute automatic refresh interval

Android Implementation:

  • Uses react-native-android-widget with FlexWidget/TextWidget components
  • Stores widget data in AsyncStorage
  • Configurable widget size (3x2 cells default)

Data Sync:

  • Widget data syncs when app goes to background
  • Uses cached booking data from React Query when available
  • Falls back to API fetch if cache is empty
  • Triggers ExtensionStorage.reloadWidget() to refresh widget after data update

Updates since last revision

  • Fixed React Compiler build error by refactoring useWidgetSync.ts to move conditional logic outside of try/catch blocks (React Compiler limitation)
  • Fixed widget showing "No upcoming bookings" despite bookings existing in app:
    • Swift widget now reads data as JSON string with fallback to raw Data
    • Fixed query key mismatch by checking multiple common filter combinations used by the app
  • Latest fix: Replaced react-native-shared-group-preferences with ExtensionStorage from @bacons/apple-targets:
    • ExtensionStorage is the recommended approach for Expo widget data sharing
    • Added ExtensionStorage.reloadWidget() call to trigger immediate widget refresh after data update

Mandatory Tasks (DO NOT REMOVE)

  • I have self-reviewed the code (A decent size PR without self-review might be rejected).
  • I have updated the developer docs in /docs if this PR makes changes that would require a documentation change. N/A - companion app internal feature
  • I confirm automated tests are in place that prove my fix is effective or that my feature works.

How should this be tested?

  1. iOS Testing:

    • Run npx expo prebuild then build the iOS app
    • Add the "Upcoming Bookings" widget to home screen via long-press
    • Log in to the app and verify bookings appear in widget
    • Verify widget updates when app goes to background
  2. Android Testing:

    • Run npx expo prebuild then build the Android app
    • Add the "Upcoming Bookings" widget from widget picker
    • Verify bookings display correctly
  3. Important Notes:

    • iOS App Groups entitlement (group.com.cal.companion) must be configured in Apple Developer portal for production builds
    • Widget requires native build (not Expo Go)

Checklist

  • My code follows the style guidelines of this project
  • I have checked if my changes generate no new warnings
  • My PR is not too large - this PR adds ~700 lines across 16 files for a new feature

Human Review Focus Areas

  • Verify iOS App Groups data sharing works correctly between SwiftUI widget and React Native app
  • Verify ExtensionStorage from @bacons/apple-targets stores data in a format the Swift widget can read (JSON string via userDefaults.string(forKey:))
  • Review SwiftUI widget JSON decoding in widgets.swift (lines 45-48) - reads as string first, then converts to Data for JSON decoding
  • Verify the query key matching logic in useWidgetSync.ts (lines 22-36) correctly finds cached bookings regardless of filter params used
  • Confirm ExtensionStorage.reloadWidget() properly triggers widget timeline refresh

Link to Devin run: https://app.devin.ai/sessions/cc55e9ded9034cb29f71c1ba50c6dd72
Requested by: @PeerRich

- Add iOS widget using @bacons/apple-targets with SwiftUI
- Add Android widget using react-native-android-widget
- Configure App Groups for iOS data sharing between app and widget
- Create widget sync hook to update widget data when bookings change
- Widget displays up to 5 upcoming bookings with title, time, and attendee
- Widget refreshes every 15 minutes and when app goes to background

Co-Authored-By: peer@cal.com <peer@cal.com>
@devin-ai-integration
Copy link
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR that start with 'DevinAI' or '@devin'.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

devin-ai-integration bot and others added 4 commits January 23, 2026 16:45
…tion

Move conditional logic outside of try/catch block to fix React Compiler
error: 'Support value blocks (conditional, logical, optional chaining,
etc) within a try/catch statement'

Co-Authored-By: peer@cal.com <peer@cal.com>
- Fix Swift widget to read data as JSON string (format used by react-native-shared-group-preferences)
- Add fallback to read as raw Data for compatibility
- Fix query key mismatch by checking multiple common filter combinations
- Ensure widget finds cached bookings regardless of which filter the app used

Co-Authored-By: peer@cal.com <peer@cal.com>
…idget data

- Replace react-native-shared-group-preferences with ExtensionStorage
- ExtensionStorage properly stores data in iOS App Groups
- Add ExtensionStorage.reloadWidget() call to trigger widget refresh
- This ensures the widget picks up new data immediately

Co-Authored-By: peer@cal.com <peer@cal.com>
@github-actions
Copy link
Contributor

github-actions bot commented Feb 3, 2026

This PR has been marked as stale due to inactivity. If you're still working on it or need any help, please let us know or update the PR to keep it active.

@github-actions github-actions bot added the Stale label Feb 3, 2026
@keithwillcode keithwillcode added the core area: core, team members only label Feb 3, 2026
@dhairyashiil dhairyashiil changed the base branch from main to feat/companion-add-upcoming-bookings-widget-for-ios February 3, 2026 13:41
@dhairyashiil dhairyashiil changed the base branch from feat/companion-add-upcoming-bookings-widget-for-ios to main February 3, 2026 13:41
…27564)

* ios working widget

* fix(companion): remove sensitive data logging from widget storage

Remove console.log statements that were logging full widgetData payload
and formatted widget bookings containing attendee names and booking
details. This addresses security concerns identified by Cubic AI review
(confidence 9/10).

Co-Authored-By: unknown <>

* dark mode

* new look

* fix deeplink

* fix biome lint

* fix(companion): remove booking titles from console logs to avoid PII exposure

Co-Authored-By: unknown <>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@dhairyashiil dhairyashiil marked this pull request as ready for review February 3, 2026 15:14
@dhairyashiil dhairyashiil requested a review from a team as a code owner February 3, 2026 15:14
@dhairyashiil
Copy link
Member

merged #27564 into this one

updates:

  1. added new look
  2. dark mode support
  3. deeplink
Screen.Recording.2026-02-03.at.8.20.17.PM.mov

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

2 issues found across 34 files

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="companion/hooks/useWidgetSync.ts">

<violation number="1" location="companion/hooks/useWidgetSync.ts:16">
P2: Avoid shipping verbose console debugging that logs booking details; guard these logs behind a dev-only check or remove them to prevent leaking booking metadata in production logs.</violation>
</file>

<file name="companion/package.json">

<violation number="1" location="companion/package.json:95">
P2: The PR adds react-native-shared-group-preferences but there are no code references to it (only package.json/bun.lock). If the widget now uses ExtensionStorage, this dependency is unused and should be removed to avoid unnecessary native modules/bundle size.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

@dhairyashiil dhairyashiil enabled auto-merge (squash) February 3, 2026 15:44
dhairyashiil
dhairyashiil previously approved these changes Feb 3, 2026
@github-actions github-actions bot removed the Stale label Feb 4, 2026

try {
const { requestWidgetUpdate } = await import("react-native-android-widget");
const { UpcomingBookingsWidget } = await import("@/widgets/UpcomingBookingsWidget");
Copy link
Contributor

Choose a reason for hiding this comment

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

Why are we using dynamic imports?

Copy link
Member

Choose a reason for hiding this comment

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

removed dynamic imports: c8b56e4

volnei
volnei previously requested changes Feb 4, 2026
Copy link
Contributor

@volnei volnei left a comment

Choose a reason for hiding this comment

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

Nit comment: Dynamic import can lead into runtime errors, if we can avoid it is better.

@github-actions github-actions bot marked this pull request as draft February 4, 2026 10:47
auto-merge was automatically disabled February 4, 2026 10:47

Pull request was converted to draft

@paragon-review
Copy link

paragon-review bot commented Feb 4, 2026

Paragon: tests updated

3 new tests generated for this PR.

New Tests

  • widgetStorage utility tests — Tests for widget storage utility functions including formatTime, formatDate, and booking data transformation
  • widgetTaskHandler tests — Tests for Android widget task handler including getStoredBookings function and widget action handling
  • UpcomingBookingsWidget component tests — Tests for the Android UpcomingBookingsWidget React Native component rendering

Accept Changes Open in Paragon

Details

New Tests

  • widgetStorage utility tests (unit)
  • widgetTaskHandler tests (unit)
  • UpcomingBookingsWidget component tests (unit)

* companion-upcoming-bookings-widget-android

* fix(companion): remove stale relative countdowns and fix past-time clamping in Android widget

- Remove relative countdown text (In 15m, In 1h, Now) that becomes stale
  due to Android widget 30+ min update interval (Cubic violation 2)
- Remove Math.max(0, minutes) clamping that caused ended meetings to
  display as 'Now' indefinitely (Cubic violation 1)
- Simplify accent color logic to use startTimeISO directly
- Keep absolute time display (date + startTime) which remains accurate

Co-Authored-By: unknown <>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@dhairyashiil
Copy link
Member

added widgets for android:

Screen.Recording.2026-02-06.at.12.59.28.PM.mov

@dhairyashiil dhairyashiil marked this pull request as ready for review February 6, 2026 08:01
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

2 issues found across 37 files

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="companion/utils/widgetStorage.shared.ts">

<violation number="1" location="companion/utils/widgetStorage.shared.ts:26">
P2: Guard against invalid or empty date strings before calling toLocaleTimeString; `new Date("")` throws when formatting and can break widget updates if a booking has missing start/end times.</violation>
</file>

<file name="companion/widgets/UpcomingBookingsWidget.tsx">

<violation number="1" location="companion/widgets/UpcomingBookingsWidget.tsx:198">
P2: Localize widget labels instead of hardcoding strings. Use the existing `upcoming` translation key and add a new key for “No upcoming bookings” so the widget follows the app’s i18n rules.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

}

export function formatTime(dateString: string): string {
const date = new Date(dateString);
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Feb 6, 2026

Choose a reason for hiding this comment

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

P2: Guard against invalid or empty date strings before calling toLocaleTimeString; new Date("") throws when formatting and can break widget updates if a booking has missing start/end times.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At companion/utils/widgetStorage.shared.ts, line 26:

<comment>Guard against invalid or empty date strings before calling toLocaleTimeString; `new Date("")` throws when formatting and can break widget updates if a booking has missing start/end times.</comment>

<file context>
@@ -0,0 +1,106 @@
+}
+
+export function formatTime(dateString: string): string {
+  const date = new Date(dateString);
+  return date.toLocaleTimeString("en-US", {
+    hour: "numeric",
</file context>
Fix with Cubic

}}
/>
<TextWidget
text="Upcoming"
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Feb 6, 2026

Choose a reason for hiding this comment

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

P2: Localize widget labels instead of hardcoding strings. Use the existing upcoming translation key and add a new key for “No upcoming bookings” so the widget follows the app’s i18n rules.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At companion/widgets/UpcomingBookingsWidget.tsx, line 198:

<comment>Localize widget labels instead of hardcoding strings. Use the existing `upcoming` translation key and add a new key for “No upcoming bookings” so the widget follows the app’s i18n rules.</comment>

<file context>
@@ -0,0 +1,246 @@
+          }}
+        />
+        <TextWidget
+          text="Upcoming"
+          style={{
+            fontSize: 12,
</file context>
Fix with Cubic

@github-actions
Copy link
Contributor

github-actions bot commented Feb 6, 2026

Devin AI is addressing Cubic AI's review feedback

A Devin session has been created to address the issues identified by Cubic AI.

View Devin Session

@devin-ai-integration
Copy link
Contributor

Reviewed the two open Cubic AI findings from the latest review:

  1. companion/utils/widgetStorage.shared.ts:26 — Guard against invalid/empty date strings in formatTime/formatDate. Confidence: 8/10 — below the 9/10 fix threshold, skipping.
  2. companion/widgets/UpcomingBookingsWidget.tsx:198 — Localize hardcoded widget labels. Confidence: 7/10 — below the 9/10 fix threshold, skipping.

Neither issue meets the required confidence threshold (9/10+) for an automated fix. These can be addressed in a follow-up if the team deems them necessary after manual review.

@dhairyashiil dhairyashiil enabled auto-merge (squash) February 6, 2026 12:32
@dhairyashiil dhairyashiil merged commit 8a7cf24 into main Feb 6, 2026
78 checks passed
@dhairyashiil dhairyashiil deleted the devin/1769185918-companion-upcoming-bookings-widget branch February 6, 2026 12:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core area: core, team members only ready-for-e2e size/XXL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants