feat(companion): add upcoming bookings widget for iOS and Android#27199
feat(companion): add upcoming bookings widget for iOS and Android#27199dhairyashiil merged 12 commits intomainfrom
Conversation
- 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 EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
…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>
|
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. |
…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>
|
merged #27564 into this one updates:
Screen.Recording.2026-02-03.at.8.20.17.PM.mov |
There was a problem hiding this comment.
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.
companion/utils/widgetStorage.ts
Outdated
|
|
||
| try { | ||
| const { requestWidgetUpdate } = await import("react-native-android-widget"); | ||
| const { UpcomingBookingsWidget } = await import("@/widgets/UpcomingBookingsWidget"); |
There was a problem hiding this comment.
Why are we using dynamic imports?
volnei
left a comment
There was a problem hiding this comment.
Nit comment: Dynamic import can lead into runtime errors, if we can avoid it is better.
Pull request was converted to draft
Paragon: tests updated3 new tests generated for this PR. New Tests
DetailsNew Tests
|
* 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>
|
added widgets for android: Screen.Recording.2026-02-06.at.12.59.28.PM.mov |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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>
| }} | ||
| /> | ||
| <TextWidget | ||
| text="Upcoming" |
There was a problem hiding this comment.
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>
Devin AI is addressing Cubic AI's review feedbackA Devin session has been created to address the issues identified by Cubic AI. |
|
Reviewed the two open Cubic AI findings from the latest review:
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. |


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:
@bacons/apple-targetsExtensionStorageAndroid Implementation:
react-native-android-widgetwith FlexWidget/TextWidget componentsData Sync:
ExtensionStorage.reloadWidget()to refresh widget after data updateUpdates since last revision
useWidgetSync.tsto move conditional logic outside of try/catch blocks (React Compiler limitation)react-native-shared-group-preferenceswithExtensionStoragefrom@bacons/apple-targets:ExtensionStorageis the recommended approach for Expo widget data sharingExtensionStorage.reloadWidget()call to trigger immediate widget refresh after data updateMandatory Tasks (DO NOT REMOVE)
How should this be tested?
iOS Testing:
npx expo prebuildthen build the iOS appAndroid Testing:
npx expo prebuildthen build the Android appImportant Notes:
group.com.cal.companion) must be configured in Apple Developer portal for production buildsChecklist
Human Review Focus Areas
ExtensionStoragefrom@bacons/apple-targetsstores data in a format the Swift widget can read (JSON string viauserDefaults.string(forKey:))widgets.swift(lines 45-48) - reads as string first, then converts to Data for JSON decodinguseWidgetSync.ts(lines 22-36) correctly finds cached bookings regardless of filter params usedExtensionStorage.reloadWidget()properly triggers widget timeline refreshLink to Devin run: https://app.devin.ai/sessions/cc55e9ded9034cb29f71c1ba50c6dd72
Requested by: @PeerRich