Feat/pro feature registry#386
Conversation
There was a problem hiding this comment.
Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.
There was a problem hiding this comment.
Code Review
This pull request introduces a Pro feature integration system using a git submodule, runtime registries for screens and settings sections, and a Metro configuration fallback. It also adds new built-in tools for sending emails and managing calendar events, alongside integrating tool extensions (such as MCP tools) into the generation loop. Feedback on these changes highlights a duplicate rendering bug of Pro settings sections in SettingsScreen.tsx, potential runtime crashes due to missing date validation in the calendar tool handlers, potential duplicate registrations in the screen and settings registries, and a lack of error handling when opening the mail client via Linking.openURL.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| {/* Pro feature sections registered at runtime by @offgrid/pro */} | ||
| {getSettingsSections().map((Section, i) => <Section key={i} />)} | ||
|
|
There was a problem hiding this comment.
| const result = await RNAddCalendarEvent.presentEventCreatingDialog({ | ||
| title, | ||
| startDate: new Date(startDate).toISOString(), | ||
| endDate: new Date(endDate).toISOString(), | ||
| ...(location ? { location } : {}), | ||
| ...(notes ? { notes } : {}), | ||
| }); |
There was a problem hiding this comment.
If the model provides an invalid date string, calling new Date(startDate).toISOString() or new Date(endDate).toISOString() will throw a RangeError: Invalid time value and crash the execution. Validate the parsed dates before calling .toISOString().
const start = new Date(startDate);
const end = new Date(endDate);
if (isNaN(start.getTime()) || isNaN(end.getTime())) {
throw new Error('Invalid date format. Please provide valid ISO 8601 dates.');
}
const result = await RNAddCalendarEvent.presentEventCreatingDialog({
title,
startDate: start.toISOString(),
endDate: end.toISOString(),
...(location ? { location } : {}),
...(notes ? { notes } : {}),
});| const startDt = startDateStr ? new Date(startDateStr) : new Date(); | ||
| const endDt = endDateStr ? new Date(endDateStr) : new Date(startDt.getTime() + 7 * 24 * 60 * 60 * 1000); | ||
| const events = await RNCalendarEvents.fetchAllEvents(startDt.toISOString(), endDt.toISOString()); |
There was a problem hiding this comment.
If startDateStr or endDateStr are invalid date strings, calling .toISOString() on the resulting Date objects will throw a RangeError. Validate the dates defensively before converting them to ISO strings.
const startDt = startDateStr ? new Date(startDateStr) : new Date();
if (isNaN(startDt.getTime())) {
throw new Error('Invalid start date format.');
}
const endDt = endDateStr ? new Date(endDateStr) : new Date(startDt.getTime() + 7 * 24 * 60 * 60 * 1000);
if (isNaN(endDt.getTime())) {
throw new Error('Invalid end date format.');
}
const events = await RNCalendarEvents.fetchAllEvents(startDt.toISOString(), endDt.toISOString());| export function registerScreen(screen: RegisteredScreen): void { | ||
| screens.push(screen); | ||
| } |
There was a problem hiding this comment.
Since loadProFeatures registers screens on status changes, calling registerScreen multiple times will append duplicate screens to the registry. Add a check to prevent duplicate screen registrations.
export function registerScreen(screen: RegisteredScreen): void {
if (!screens.some(s => s.name === screen.name)) {
screens.push(screen);
}
}| export function registerSettingsSection(component: ComponentType<any>): void { | ||
| sections.push(component); | ||
| } |
There was a problem hiding this comment.
Since loadProFeatures registers settings sections on status changes, calling registerSettingsSection multiple times will append duplicate sections to the registry. Add a check to prevent duplicate section registrations.
export function registerSettingsSection(component: ComponentType<any>): void {
if (!sections.includes(component)) {
sections.push(component);
}
}| if (subject) parts.push(`subject=${encodeURIComponent(subject)}`); | ||
| if (body) parts.push(`body=${encodeURIComponent(body)}`); | ||
| const url = `mailto:${encodeURIComponent(to)}${parts.length ? `?${parts.join('&')}` : ''}`; | ||
| await Linking.openURL(url); |
There was a problem hiding this comment.
Linking.openURL can throw an error if no mail client is installed or configured on the device (especially on simulators or devices without email apps). Wrap the call in a try-catch block to handle this gracefully, log the error to prevent silent failures, and return a meaningful error message.
try {
await Linking.openURL(url);
} catch (err) {
console.error('Failed to open mail client:', err);
throw new Error('Could not open the mail app. Please ensure a mail client is configured on your device.');
}References
- When catching errors, log them instead of swallowing them to ensure failures are visible and to aid in debugging.
94b4a03 to
76cec8d
Compare
Codecov Report❌ Patch coverage is ❌ Your patch check has failed because the patch coverage (73.39%) is below the target coverage (80.00%). You can increase the patch coverage or adjust the target coverage. Additional details and impacted files@@ Coverage Diff @@
## main #386 +/- ##
==========================================
- Coverage 81.82% 81.78% -0.04%
==========================================
Files 241 245 +4
Lines 12835 12954 +119
Branches 3535 3574 +39
==========================================
+ Hits 10502 10595 +93
- Misses 1403 1416 +13
- Partials 930 943 +13
🚀 New features to boost your workflow:
|
Co-Authored-By: Dishit Karia <hanmadishit74@gmail.com>
Co-Authored-By: Dishit Karia <hanmadishit74@gmail.com>
Co-Authored-By: Dishit Karia <hanmadishit74@gmail.com>
Co-Authored-By: Dishit Karia <hanmadishit74@gmail.com>
Co-Authored-By: Dishit Karia <hanmadishit74@gmail.com>
Public placeholder config so the app compiles; real iOS/Android keys stay in gitignored revenueCatKeys.local.ts and are never committed. Co-Authored-By: Dishit Karia <hanmadishit74@gmail.com>
The EmailCalendarExtension test imports the pro module by path, pulling it into tsc's program past the pro/** exclude. Without a paths mapping for @offgrid/core/* (which jest.config.js already has), tsc could not resolve the pro module's core type imports, cascading into TS18046/TS7006 errors. Mirror the jest moduleNameMapper so tsc and jest agree. Co-Authored-By: Dishit Karia <hanmadishit74@gmail.com>
98b32bc to
18ee7f1
Compare
Two CI-only failures (both pass locally where the pro submodule is checked out and revenueCatKeys.ts has the full local copy): - revenueCatKeys.ts committed an older placeholder missing RC_WEB_PURCHASE_URL, which proLicenseService imports. Add the (public, safe-to-commit) web purchase URL placeholder so the committed config exports every symbol the app uses. - EmailCalendarExtension.test.ts imported the pro module by a static path that tsc/jest cannot resolve when pro/ is absent (open-core CI does not check out the private submodule). Load it via a computed-path require and skip the suite when missing; it still runs locally and in the pro repo CI. Co-Authored-By: Dishit Karia <hanmadishit74@gmail.com>
|
- Success card gets a 'Got it' button (and onRequestClose) so the user can read the activation message and dismiss it, instead of being trapped with no way out but force-killing the app. - Card now has a visible border (colors.border) so it reads in dark mode, where the shadow alone disappeared against the background. - CTA (Continue to payment / Verify and unlock) is enabled only once non-whitespace text is entered; no format validation. Whitespace is stripped before use, matching the service-layer trim/lowercase. - Drop the stale RNRestart comment in ProDetailScreen (the lib was removed); Pro now loads on next launch via checkProStatus. - Tests updated for the disabled-until-typed CTA, whitespace handling, and the success-card dismiss. Co-Authored-By: Dishit Karia <hanmadishit74@gmail.com>
Points the pro gitlink at af5aa10 (offgrid-pro feat/email-calendar-tools) which includes the MCP servers screen header inset fix, removal of the demo-servers link, and the now-required server name field. Co-Authored-By: Dishit Karia <hanmadishit74@gmail.com>



What this does
Introduces the Pro feature layer for Off Grid and moves the email/calendar tools out of the open core into the private pro submodule, behind the Pro entitlement.
The branch history was rebuilt into 7 focused commits so that no commit in the open repo ever contains the email/calendar handler implementation or any RevenueCat API keys.
Change set
Pro feature registry + license seam (
d300dbb0)@offgrid/prosubmodule and the registry seam (screenRegistry,sectionRegistry,loadProFeatures) so pro features register themselves at boot through anactivate(registry)bag, with a no-op stub when the submodule is absent.Tool extension seam + MCP wiring (
26db7c0d)ToolExtension(with optionalgetToolDefinitions()) and wires extension-provided tools into the generation tool loop, the chat input popovers, and the chat screen.RevenueCat pro license service + unlock modal (
3d5613bf)proLicenseServiceconfigures RevenueCat with Trusted Entitlements (INFORMATIONALverification,FAILEDsignatures blocked), caches the license in the keychain, and revalidates online at launch so access can be revoked from the RC dashboard.ProUnlockModalcollects the email, opens the RC web checkout, and verifies the purchase. Replaces the auto-restart library with a manual "close and reopen" prompt.RevenueCat config placeholders (
66ff0c44)revenueCatKeys.local.tsand are never committed.In-app debug log viewer (
99718d62)DebugLogsScreen+debugLogsStorefor on-device log inspection.Pro-gated email/calendar tools (
fcbc9c99)send_email,create_calendar_event,read_calendar_eventsnow live in the pro submodule as aToolExtension. The core picker renders core tools plus extension-provided ones, deduped.handlers.ts/registry.tsmatchmainexactly — the handler code exists only in the pro package.tsconfig path mapping (
18ee7f1d)@offgrid/core/*→src/*for tsc, mirroring the existing jestmoduleNameMapper, so tsc resolves the pro extension's core type imports.Verification
tsc --noEmitclean.handleSendEmail,handleCreateCalendarEvent,ensureCalendarPermission,RNCalendarEvents) in any source file across the branch history.handlers.ts/registry.tsare byte-identical tomain.Notes
ed9b93b.