Conversation
- Add notifications schema to storeCollections for per-store notifications
- Create subscriber fingerprinting service with format: {site.domain}:{store.id}:{wpCredentials.uuid}
- Add NovuProvider context with subscriber metadata for targeting
- Create useNovuNotifications hook with RxDB sync and unread/unseen counts
- Add Badge component for notification count display
- Add NotificationBell component in header with unread badge
- Add NotificationPanel modal for viewing and managing notifications
- Integrate NotificationBell into header between Online and UserMenu
… notification sync - Add NovuApiClient for API communication - Initialize session on app load (registers/identifies subscriber) - Fetch notifications and sync to RxDB - Update mark as read/seen to sync with both Novu and RxDB
- Cache session tokens globally (survives component re-renders) - Use refs to track initialization state (prevents duplicate calls) - Only recreate API client when subscriberId changes, not metadata - Add retry limit to prevent infinite 401 loops
- Fix session response parsing (data.data.token) - Fix notifications response structure detection - Fix payload must be object not undefined - Map Novu's isRead/isSeen to our schema fields - Add diagnostic logging throughout - Add 30-second polling interval for new notifications
- Add @novu/js SDK dependency - Create new client.ts using official SDK with WebSocket connection - Replace polling with real-time event listeners - Listen for notifications.notification_received events - Listen for notifications.unread_count_changed events - Remove old custom API implementation - Bell icon now updates automatically when new notifications arrive
|
Caution Review failedThe pull request is closed. 📝 WalkthroughWalkthroughAdds a Novu-backed in-app notifications system (client, subscriber utils, behaviors, DB schema, RxDB sync hook, UI components), a Badge UI, virtualized-list refactor, package/config updates, docs edits (one deletion), and reorganized database public exports. Changes
Sequence Diagram(s)sequenceDiagram
participant App as Client App
participant NovuSvc as Novu Client (singleton)
participant NovuAPI as Novu Server (API & WS)
participant LocalDB as RxDB (notifications)
participant UI as Notification UI
App->>NovuSvc: initialize(subscriberId, metadata)
NovuSvc->>NovuAPI: connect WS / authenticate
NovuSvc->>NovuAPI: fetch initial notifications
NovuAPI-->>NovuSvc: notifications list
NovuSvc->>LocalDB: upsert notifications
LocalDB-->>UI: observable updates (notifications, counts)
UI->>LocalDB: markAsRead / markAllAsRead (local update)
LocalDB->>NovuSvc: sync changes
NovuSvc->>NovuAPI: update notification state
NovuAPI-->>NovuSvc: WS push (new notification)
NovuSvc->>LocalDB: upsert pushed notification
LocalDB-->>UI: updated observable (real-time)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
🤖 Fix all issues with AI agents
In `@packages/core/package.json`:
- Line 36: Update the dependency entry for "@novu/js" in package.json from
"^2.0.0" to a 3.x release (e.g. "^3.12.0"), run your package manager
(npm/yarn/pnpm install) to refresh lockfiles, then run the test suite and
linting to surface any breaking API changes and fix import or usage sites
referencing `@novu/js` as needed.
In `@packages/core/src/screens/main/components/header/notification-bell.tsx`:
- Around line 29-30: The Pressable button containing only an Icon (Pressable and
Icon in notification-bell.tsx) lacks a readable label for screen readers; add an
accessibilityLabel (e.g., accessibilityLabel="Notifications" or use your
i18n/localization string) to the Pressable (and ensure accessible={true} if
needed) while keeping accessibilityRole="button" so screen-reader users can
discover and understand the control.
- Around line 19-24: The handler currently calls a non-existent markAllAsSeen;
change it to call the exported markAllAsRead from the hook (update any
destructuring that referenced markAllAsSeen to markAllAsRead) and guard the call
so you only invoke/await markAllAsRead when unreadCount > 0 (await the call to
let the hook's internal error handling run). Also add an accessibilityLabel
(e.g., accessibilityLabel="Notifications") to the Pressable used for the bell so
screen readers have a descriptive label.
In `@packages/core/src/screens/main/components/header/notification-panel.tsx`:
- Around line 108-118: The ScrollView in the NotificationPanel component
currently uses className="max-h-80" which can be ignored by React Native's flex
layout; update the ScrollView element (the one rendering NotificationItem and
using markAsRead) to enforce the max height by adding flex-grow-0 (e.g.,
className="max-h-80 flex-grow-0"), or alternatively wrap the ScrollView in a
bounded View with className="max-h-80" and set the ScrollView to flex-1, or move
the constraint into contentContainerClassName="max-h-80" — pick one approach and
apply it to the ScrollView in notification-panel.tsx.
In `@packages/core/src/services/novu/client.ts`:
- Around line 59-64: The cleanup currently just nulls the novuClient reference
which doesn't close the socket; update the cleanup block that checks novuClient
to call novuClient.unmountComponent() (the SDK method) before setting novuClient
= null so the UI and WebSocket are properly disconnected; ensure you call
unmountComponent() safely (e.g., check method exists or wrap in try/catch) on
the novuClient instance.
- Around line 92-105: The current handlers register events with novuClient.on
but push empty placeholders into unsubscribers, preventing actual unsubscribe
and causing memory leaks; update each registration
(handlers.onNotificationReceived, handlers.onUnreadCountChanged,
handlers.onUnseenCountChanged) to capture the return value of novuClient.on into
a const (e.g., const unsubscribe = novuClient.on(...)) and push that unsubscribe
function into the unsubscribers array so the existing cleanup function can call
them to remove the listeners; maintain existing logging and handler invocations
(e.g., casting to NovuNotification) but ensure the real unsubscribe functions
are stored.
- Around line 192-226: The markAsSeen and markAllAsSeen functions use the wrong
Novu SDK methods (they call novuClient.notifications.read and
novuClient.notifications.readAll which set isRead); update markAsSeen to call
the individual seen method on the notification API
(novuClient.notification.seen({ notificationId })) and update markAllAsSeen to
call the bulk seen method (novuClient.notifications.seenAll({})); keep the
existing error handling/logging but change the method names accordingly so the
code updates isSeen rather than isRead.
🧹 Nitpick comments (8)
packages/database/src/collections/schemas/notifications.ts (1)
1-57: Consider specifying required fields.The schema is well-structured with good documentation. A few observations:
Missing
requiredarray: RxDB treats all properties as optional by default. Consider adding arequiredarray for essential fields likeid,subscriberId, andcreatedAtto ensure data integrity.Unbounded
titleandbody: These string fields have nomaxLength. If Novu could potentially send very long content, consider adding reasonable limits to prevent storage bloat.💡 Suggested improvement
export const notificationsLiteral = { title: 'Notification schema', version: 0, description: 'In-app notifications from Novu', type: 'object', primaryKey: 'id', + required: ['id', 'subscriberId', 'createdAt'], properties: { // ... existing properties title: { description: 'Notification title', type: 'string', + maxLength: 500, }, body: { description: 'Notification body/message', type: 'string', + maxLength: 5000, },packages/components/src/badge/index.tsx (1)
89-92: Dot mode ignores thevariantprop.The dot indicator is hardcoded to use
bg-destructive, while the count badge respects thevariantprop. If this is intentional (dots should always be red), consider documenting this behavior. Otherwise, consider applying the variant:♻️ Optional: Apply variant to dot mode
// Dot mode - just show a small indicator if (dot) { - return <View className={cn('bg-destructive h-2.5 w-2.5 rounded-full', className)} {...props} />; + return <View className={cn(badgeVariants({ variant: variant ?? 'destructive', size: 'sm' }), 'h-2.5 w-2.5 p-0', className)} {...props} />; }packages/core/src/services/novu/subscriber.ts (1)
32-43: Potential for malformed subscriber ID if nested properties are missing.If
store.id,store.localID, orwpCredentials.uuidare undefined, the subscriber ID will contain the literal string"undefined". While the NovuProvider checks for the existence of parent objects, it doesn't validate these nested properties.Consider adding validation or fallbacks:
♻️ Proposed validation
export function generateSubscriberId( site: SiteDocument, store: StoreDocument, wpCredentials: WPCredentialsDocument ): string { // Extract domain from site URL (remove protocol) const domain = extractDomain(site.url || ''); const storeId = store.id || store.localID; const userUuid = wpCredentials.uuid; + if (!domain || !storeId || !userUuid) { + throw new Error('Missing required fields for subscriber ID generation'); + } + return `${domain}:${storeId}:${userUuid}`; }packages/core/src/services/novu/client.ts (1)
50-81: The_metadataparameter is unused.The
_metadataparameter is passed but never used in client initialization. Either remove it or implement metadata registration with Novu (if the SDK supports it).packages/core/src/contexts/novu/index.tsx (1)
14-18: DuplicateNOVU_CONFIGdefinition.This configuration is also defined in
packages/core/src/services/novu/client.ts. Consider extracting to a shared constants file to avoid inconsistencies if URLs need to change.♻️ Extract to shared config
Create a shared config file:
// packages/core/src/services/novu/config.ts export const NOVU_CONFIG = { applicationIdentifier: '64qzhASJJNnb', backendUrl: 'https://api.notifications.wcpos.com', socketUrl: 'wss://ws.notifications.wcpos.com', };Then import from both files.
packages/core/src/hooks/use-novu-notifications.ts (3)
149-156: Sequential notification sync could be slow for large lists.Processing notifications one-by-one with
awaitinside a loop is O(n) sequential operations. Consider usingPromise.allfor parallel processing or RxDB's bulk operations if available.♻️ Parallel sync option
const syncToRxDB = React.useCallback( async (novuNotifications: NovuNotification[]) => { - for (const notification of novuNotifications) { - await syncNotificationToRxDB(notification); - } + await Promise.all( + novuNotifications.map((notification) => syncNotificationToRxDB(notification)) + ); }, [syncNotificationToRxDB] );
209-228: No rollback if server update fails after local update.The optimistic update pattern updates RxDB first for immediate UI feedback, but if
novuMarkAsReadfails, the local state becomes inconsistent with the server. Consider reverting the local change on failure:♻️ Add rollback on failure
const markAsRead = React.useCallback( async (notificationId: string) => { if (!notificationsCollection) return; try { // Update RxDB first for immediate UI feedback const doc = await notificationsCollection.findOne(notificationId).exec(); + const previousStatus = doc?.status; + const previousSeen = doc?.seen; if (doc) { await doc.patch({ status: 'read', seen: true }); } // Then update Novu - await novuMarkAsRead(notificationId); + const success = await novuMarkAsRead(notificationId); + if (!success && doc && previousStatus !== undefined) { + // Rollback on failure + await doc.patch({ status: previousStatus, seen: previousSeen ?? false }); + log.warn('Novu: Rolled back local state after server failure'); + } } catch (error) { log.error('Novu: Failed to mark notification as read', { context: { error } }); } }, [notificationsCollection] );
188-199: Missing error handling for initial fetch.The
.then()chain doesn't have a.catch()handler. WhilefetchNotificationslogs errors internally and returns an empty array, any error insyncToRxDBwould be unhandled.♻️ Add catch handler
// Initial fetch of notifications setIsLoading(true); fetchNotifications() .then((notifications) => { log.info('Novu: Initial notifications loaded', { context: { count: notifications.length }, }); return syncToRxDB(notifications); }) + .catch((error) => { + log.error('Novu: Failed during initial sync', { context: { error } }); + }) .finally(() => { setIsLoading(false); });
| const handleOpenChange = (open: boolean) => { | ||
| setIsOpen(open); | ||
| if (open) { | ||
| markAllAsSeen(); | ||
| } | ||
| }; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Find the notification-bell.tsx file and examine its contents
find . -name "notification-bell.tsx" -type fRepository: wcpos/monorepo
Length of output: 130
🏁 Script executed:
#!/bin/bash
# Search for useNovuNotifications hook to understand markAllAsSeen
rg -n "useNovuNotifications" --type ts --type tsx -lRepository: wcpos/monorepo
Length of output: 85
🏁 Script executed:
#!/bin/bash
# Search for markAllAsSeen implementation
rg -n "markAllAsSeen" --type ts --type tsx -B2 -A5Repository: wcpos/monorepo
Length of output: 85
🏁 Script executed:
#!/bin/bash
# Read the notification-bell component file
cat -n ./packages/core/src/screens/main/components/header/notification-bell.tsxRepository: wcpos/monorepo
Length of output: 1751
🏁 Script executed:
#!/bin/bash
# Search for markAllAsSeen without file type restriction
rg -n "markAllAsSeen" -B2 -A5Repository: wcpos/monorepo
Length of output: 1996
🏁 Script executed:
#!/bin/bash
# Search for useNovuNotifications hook
rg -n "useNovuNotifications" -B2 -A5 | head -100Repository: wcpos/monorepo
Length of output: 4358
🏁 Script executed:
#!/bin/bash
# Read the useNovuNotifications hook implementation
cat -n ./packages/core/src/hooks/use-novu-notifications.tsRepository: wcpos/monorepo
Length of output: 10033
🏁 Script executed:
#!/bin/bash
# Check if there are any accessibilityLabel/accessibilityHint props used elsewhere
rg -n "accessibilityLabel|accessibilityHint" packages/core/src/screens/main/components/header/ -B1 -A1Repository: wcpos/monorepo
Length of output: 40
🏁 Script executed:
#!/bin/bash
# Search for all markAllAsSeen usages to verify if it's a widespread issue
rg -n "markAllAsSeen" --type tsRepository: wcpos/monorepo
Length of output: 393
🏁 Script executed:
#!/bin/bash
# Check if markAllAsSeen should be exported from the hook
rg -n "markAllAsRead" --type ts | grep -E "(export|return|markAllAsRead)" | head -30Repository: wcpos/monorepo
Length of output: 878
Fix function name: use markAllAsRead instead of non-existent markAllAsSeen.
The component destructures markAllAsSeen from the hook, but the hook only exports markAllAsRead. This will cause a runtime error. Additionally, the function should be awaited or wrapped to ensure errors are handled and avoid unnecessary calls when unreadCount is 0.
♻️ Recommended fix
const handleOpenChange = (open: boolean) => {
setIsOpen(open);
- if (open) {
- markAllAsSeen();
- }
+ if (open && unreadCount > 0) {
+ markAllAsRead();
+ }
};Note: The hook already wraps markAllAsRead with error handling, so no additional error wrapping is needed. However, the guard for unreadCount > 0 prevents unnecessary API calls when there are no unread notifications.
Additionally, the Pressable button is missing an accessibilityLabel (line 29), which limits usability for screen reader users. Consider adding a descriptive label like accessibilityLabel="Notifications".
🤖 Prompt for AI Agents
In `@packages/core/src/screens/main/components/header/notification-bell.tsx`
around lines 19 - 24, The handler currently calls a non-existent markAllAsSeen;
change it to call the exported markAllAsRead from the hook (update any
destructuring that referenced markAllAsSeen to markAllAsRead) and guard the call
so you only invoke/await markAllAsRead when unreadCount > 0 (await the call to
let the hook's internal error handling run). Also add an accessibilityLabel
(e.g., accessibilityLabel="Notifications") to the Pressable used for the bell so
screen readers have a descriptive label.
packages/core/src/screens/main/components/header/notification-bell.tsx
Outdated
Show resolved
Hide resolved
packages/core/src/screens/main/components/header/notification-panel.tsx
Outdated
Show resolved
Hide resolved
- Export getNovuEnvironment() to determine dev/prod from config - Subscriber sync now sends environment to server - Environment auto-detected from NOVU_APPLICATION_ID - Uses production by default, dev if custom app ID is set - Improved logging to include environment context
- No more EXPO_PUBLIC_NOVU_APPLICATION_ID env var needed - Auto-detects: __DEV__ for Expo, NODE_ENV for Electron - Both Application IDs hardcoded (dev: 64qzhASJJNnb, prod: Wu5i9hEUNMO2) - Can still override with NOVU_ENV env var if needed
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In `@apps/main/package.json`:
- Line 27: Package.json pins "expo": "54.0.31" which targets RN 0.81.x and may
require RN 0.81 breaking-change workarounds; verify and remedy JSC and
autolinking issues by (1) auditing code for direct JSC usage and if present add
either "expo-modules-core" or a community JSC package and update iOS pods, (2)
running npx expo-modules-autolinking verify -v and, if failures occur, adjust
app.json autolinking settings (add searchPaths or legacy flags) so unified
Expo+RN autolinking works, and (3) inspect native dependencies for
XCFramework/header expectations and update their peer deps or iOS Podfile as
needed.
- Line 65: The project requires dotenv at Metro build-time (metro.config.js
calls require('dotenv').config()), so move the "dotenv" entry from
devDependencies into dependencies in package.json: remove the "dotenv":
"^16.4.5" line from devDependencies and add the same version string under
dependencies so Metro always has dotenv available during initialization.
In `@packages/core/src/services/novu/subscriber.ts`:
- Around line 44-46: In generateSubscriberId and generateSubscriberMetadata,
replace the use of logical OR when computing storeId (currently "store.id ||
store.localID") with nullish coalescing so a legitimate 0 store.id is preserved
(use "store.id ?? store.localID"); locate the storeId allocation in
generateSubscriberId (around the lines that set domain, storeId, userUuid) and
the return object in generateSubscriberMetadata and update both occurrences to
use ??.
♻️ Duplicate comments (2)
packages/core/src/services/novu/client.ts (2)
97-102: Previous review feedback not addressed: proper client cleanup is needed.Simply nulling the
novuClientreference does not properly close the WebSocket connection. As noted in a previous review, the@novu/jsSDK may require an explicit cleanup method (e.g.,unmountComponent()or similar) to disconnect the socket and prevent memory leaks.`@novu/js` SDK v3 disconnect cleanup method
| "@shopify/react-native-skia": "2.2.12", | ||
| "@tailwindcss/postcss": "^4.1.17", | ||
| "expo": "54.0.30", | ||
| "expo": "54.0.31", |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat apps/main/package.json | head -80Repository: wcpos/monorepo
Length of output: 2510
🌐 Web query:
Expo SDK 54.0.31 release notes breaking changes React Native 0.81.x compatibility
💡 Result:
Short summary of Expo SDK 54.0.31 / SDK 54 breaking changes and React Native 0.81 compatibility
-
SDK 54 ships with React Native 0.81 and introduces precompiled iOS React Native XCFrameworks (affects native pods and packages that expect source-built RN). If a dependency expects source headers (or you use use_frameworks!), you may need to opt out or adjust pods. [1][3]
-
First‑party JSC removed in RN 0.81 — if you need JSC you must add the community javascriptcore package and handle native setup (no config plugin yet). [1][2][3]
-
React Native’s built‑in is deprecated — switch to react-native-safe-area-context. [1][3]
-
Autolinking behavior changed (unified Expo + RN autolinking; monorepo/hoisting edge cases). You may need to add node_modules searchPaths or legacy autolinking flags in app.json to restore previous behavior. Run npx expo-modules-autolinking verify -v to debug. [2]
-
SDK 54 is the last Expo release to support RN Legacy Architecture; RN 0.82+ will force the New Architecture — prepare native modules accordingly. [2]
-
Reanimated / animation ecosystem: Reanimated v4 (shipped/supported with RN 0.81) requires the New Architecture and introduces worklets/migration steps — check Reanimated migration notes (some Expo tooling will adjust babel config automatically). [2]
-
Misc: RN 0.81 bumps minimum Node/Xcode and has other RN-level breaking changes (see RN 0.81 release notes for full list). [3]
If you want, I can:
- fetch the exact SDK 54.0.31 changelog entry and link the lines that mention each item, or
- list concrete migration steps (pods, config entries, or sample app.json changes) tailored to your project.
Sources: Expo SDK 54 changelog and beta notes, React Native 0.81 release notes. [1] [2] [3]
🌐 Web query:
Expo SDK 54 React Native 0.81 React 19 compatibility matrix
💡 Result:
Short answer — Expo SDK 54 targets React Native 0.81 and React 19 (19.1.0). Additional packaged versions: React Native Web 0.21.0 and minimum Node 20.19.x. Sources: Expo docs and SDK 54 changelog. [1][2]
Compatibility summary (SDK 54)
- Expo SDK: 54.0.0
- React Native: 0.81.0 (SDK 54 target). [2][3]
- React: 19.1.0. [1][2]
- React Native Web: 0.21.0. [1]
- Minimum Node.js: 20.19.x. [1]
Notes
- SDK 54 introduced precompiled RN for iOS (XCFrameworks) and RN 0.81 removed built‑in JSC — see changelog for breaking changes and migration notes. [2]
- Beta notes and timeline available in the SDK 54 beta post. [3]
Sources
- Expo docs — SDK/versions table. [1]
- Expo SDK 54 changelog/release notes. [2]
- Expo SDK 54 beta announcement. [3]
Expo 54.0.31 is compatible with React Native 0.81.5 and React 19.1.0, but address RN 0.81 breaking changes.
Your versions align with SDK 54's official support matrix. However, RN 0.81 introduced breaking changes that require attention:
-
JavaScript Core (JSC) — First-party JSC is removed in RN 0.81. You don't currently have an explicit JSC package; verify your app doesn't rely on it. If needed, add
expo-modules-coreor the community JSC package and update native pods. -
Autolinking behavior changed — Run
npx expo-modules-autolinking verify -vto confirm your setup works with unified Expo + RN autolinking. If you hit issues, you may need to addsearchPathsor legacy flags inapp.json. -
SafeAreaView deprecated — You're already using
react-native-safe-area-context, so this is handled. -
iOS XCFrameworks — If any native dependencies expect source-built React Native headers, they may fail. Check peer dependencies of native packages.
🤖 Prompt for AI Agents
In `@apps/main/package.json` at line 27, Package.json pins "expo": "54.0.31" which
targets RN 0.81.x and may require RN 0.81 breaking-change workarounds; verify
and remedy JSC and autolinking issues by (1) auditing code for direct JSC usage
and if present add either "expo-modules-core" or a community JSC package and
update iOS pods, (2) running npx expo-modules-autolinking verify -v and, if
failures occur, adjust app.json autolinking settings (add searchPaths or legacy
flags) so unified Expo+RN autolinking works, and (3) inspect native dependencies
for XCFramework/header expectations and update their peer deps or iOS Podfile as
needed.
| "victory-native": "^41.20.2" | ||
| }, | ||
| "devDependencies": { | ||
| "dotenv": "^16.4.5", |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, confirm metro.config.js requires dotenv
find . -name "metro.config.js" -type f | head -20Repository: wcpos/monorepo
Length of output: 85
🏁 Script executed:
# Check the actual metro config file content
cat -n apps/main/metro.config.js 2>/dev/null | head -100Repository: wcpos/monorepo
Length of output: 1729
🏁 Script executed:
# Verify dotenv is currently in devDependencies
cat apps/main/package.json | grep -A 30 '"devDependencies"' | grep -B 5 -A 5 'dotenv'Repository: wcpos/monorepo
Length of output: 246
🏁 Script executed:
# Run the provided verification to check build pipeline
#!/bin/bash
# Look for prod-only installs or NODE_ENV=production usage
rg -n "pnpm install|npm ci|yarn install" -g '!**/node_modules/**' --max-count=20
echo "---"
rg -n "NODE_ENV=production|--prod|--production" -g '!**/node_modules/**' --max-count=20Repository: wcpos/monorepo
Length of output: 129
🏁 Script executed:
# Check for any EAS build configuration that might affect dependencies
find . -name "eas.json" -o -name "app.json" | head -10 | xargs cat 2>/dev/nullRepository: wcpos/monorepo
Length of output: 1119
🏁 Script executed:
# Check if dotenv is actually used at runtime or just config
rg -n "require.*dotenv|import.*dotenv" apps/main/ --type js --type jsonRepository: wcpos/monorepo
Length of output: 160
🏁 Script executed:
# Check build scripts in package.json
cat apps/main/package.json | grep -A 50 '"scripts"'Repository: wcpos/monorepo
Length of output: 1894
🏁 Script executed:
# Check if there's a root package.json with monorepo scripts
cat package.json | grep -A 30 '"scripts"' 2>/dev/null | head -50Repository: wcpos/monorepo
Length of output: 1311
🏁 Script executed:
# Check workspace configuration that might affect dependency resolution
cat package.json | grep -A 10 '"workspaces"' 2>/dev/nullRepository: wcpos/monorepo
Length of output: 40
Move dotenv to dependencies (required at Metro build-time, not runtime).
metro.config.js line 4 calls require('dotenv').config() during Metro initialization. While the current pnpm setup installs devDependencies by default, moving dotenv to dependencies makes this build-time requirement explicit and protects against future CI changes or package manager switches that might skip devDependencies during build.
Suggested change
@@
"dependencies": {
+ "dotenv": "^16.4.5",
@@
"devDependencies": {
- "dotenv": "^16.4.5",🤖 Prompt for AI Agents
In `@apps/main/package.json` at line 65, The project requires dotenv at Metro
build-time (metro.config.js calls require('dotenv').config()), so move the
"dotenv" entry from devDependencies into dependencies in package.json: remove
the "dotenv": "^16.4.5" line from devDependencies and add the same version
string under dependencies so Metro always has dotenv available during
initialization.
- Add accessibilityLabel to notification bell for screen readers - Fix ScrollView max-h-80 constraint with grow-0 class - Fix event listener cleanup by capturing unsubscribe functions from on() - Call socket.disconnect() before nulling client reference - Parallelize notification sync for better performance - Use nullish coalescing for storeId to preserve 0 values
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@packages/core/src/hooks/use-novu-notifications.ts`:
- Around line 115-148: The upsert can write NaN for createdAt when
notification.createdAt is missing/malformed; inside syncNotificationToRxDB
compute a numeric createdAt before calling notificationsCollection.upsert (e.g.,
parse/convert notification.createdAt, check isNaN) and if invalid replace it
with a safe fallback (such as Date.now() or 0) so the createdAt passed to upsert
is always a valid number; update the createdAt field in the upsert call to use
this validated value and keep the logic inside the syncNotificationToRxDB
callback that references notificationsCollection and subscriberId.
♻️ Duplicate comments (1)
packages/core/src/services/novu/client.ts (1)
97-103: Use the SDK’s explicit unmount/cleanup API (don’t rely on socket.disconnect alone).
If the Novu SDK exposesunmountComponent()(or equivalent), it should be called to fully release UI/socket resources before nulling the client.🔧 Proposed fix (verify SDK API first)
- novuClient.socket.disconnect(); + novuClient.unmountComponent?.(); + novuClient.socket?.disconnect?.();Does the `@novu/js` SDK provide an unmount/cleanup method (e.g., unmountComponent) that should be called to fully disconnect the UI/WebSocket?Also applies to: 352-357
- Replace ScrollView with VirtualizedList in notification panel for better performance with large notification lists - Add auto-height sizing to VirtualizedList.Root based on content size using useLayoutEffect (fixes sizing in Popovers) - Replace useEffect with ref callback for scrollElement setup - Add i18n-aware date formatting via useDateFormat hook - Add comprehensive documentation explaining when to use flex-1 vs not - Add max-h-96 constraint to notification PopoverContent
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@packages/components/src/virtualized-list/virtualized-list.web.tsx`:
- Around line 142-150: The padding calculation in the React.useLayoutEffect for
virtualized-list.web.tsx is fragile: update the logic in the effect that uses
rootRef, totalSize and parentProps to compute vertical padding by (1) checking
explicit vertical properties (paddingTop, paddingBottom) on parentProps.style in
addition to shorthand padding, (2) handling string values like "10px" by parsing
units to numbers (e.g., parseFloat) and defaulting to 0 when parsing fails, and
(3) as a fallback, obtaining computed styles from the actual parent DOM node
(via rootRef.current.parentElement and getComputedStyle) to reliably read
paddingTop/paddingBottom; then sum top+bottom and add to totalSize when setting
rootRef.current.style.height.
♻️ Duplicate comments (1)
packages/core/src/screens/main/components/header/notification-bell.tsx (1)
16-23: Use the hook’s exported function name (likelymarkAllAsRead) to avoid runtime errors.This component calls
markAllAsSeen, but earlier review found onlymarkAllAsReadexported from the hook. If that mismatch still exists, opening the popover will throw. Please verify the hook’s return shape and align the call (and consider guarding onunreadCount).🛠️ Proposed fix
-export function NotificationBell() { - const { unreadCount, markAllAsSeen } = useNovuNotifications(); +export function NotificationBell() { + const { unreadCount, markAllAsRead } = useNovuNotifications(); const [isOpen, setIsOpen] = React.useState(false); const handleOpenChange = (open: boolean) => { setIsOpen(open); - if (open) { - markAllAsSeen(); + if (open && unreadCount > 0) { + markAllAsRead(); } };Run this to confirm the hook’s exported field name:
#!/bin/bash # Verify which mark-all function is returned by the hook rg -nP "markAllAs(Read|Seen)" packages/core/src/hooks/use-novu-notifications.ts rg -nP "return\s*{[^}]*markAllAs(Read|Seen)" packages/core/src/hooks/use-novu-notifications.ts
| React.useLayoutEffect(() => { | ||
| if (rootRef.current && totalSize > 0) { | ||
| // Add parentProps padding to totalSize | ||
| const paddingStyle = (parentProps as any)?.style; | ||
| const padding = paddingStyle?.padding ?? 0; | ||
| const paddingVertical = padding * 2; | ||
| rootRef.current.style.height = `${totalSize + paddingVertical}px`; | ||
| } | ||
| }, [totalSize, rootRef, parentProps]); |
There was a problem hiding this comment.
Fragile padding calculation.
The padding extraction only handles the shorthand padding property and assumes a numeric value. This will break if:
- Parent uses
paddingTop/paddingBottomorpaddingVerticalinstead - A CSS string value like
"10px"is passed (would produceNaN)
🔧 Suggested improvement
React.useLayoutEffect(() => {
if (rootRef.current && totalSize > 0) {
- // Add parentProps padding to totalSize
const paddingStyle = (parentProps as any)?.style;
- const padding = paddingStyle?.padding ?? 0;
- const paddingVertical = padding * 2;
+ const paddingTop = paddingStyle?.paddingTop ?? paddingStyle?.paddingVertical ?? paddingStyle?.padding ?? 0;
+ const paddingBottom = paddingStyle?.paddingBottom ?? paddingStyle?.paddingVertical ?? paddingStyle?.padding ?? 0;
+ const paddingVertical = (typeof paddingTop === 'number' ? paddingTop : 0)
+ + (typeof paddingBottom === 'number' ? paddingBottom : 0);
rootRef.current.style.height = `${totalSize + paddingVertical}px`;
}
}, [totalSize, rootRef, parentProps]);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| React.useLayoutEffect(() => { | |
| if (rootRef.current && totalSize > 0) { | |
| // Add parentProps padding to totalSize | |
| const paddingStyle = (parentProps as any)?.style; | |
| const padding = paddingStyle?.padding ?? 0; | |
| const paddingVertical = padding * 2; | |
| rootRef.current.style.height = `${totalSize + paddingVertical}px`; | |
| } | |
| }, [totalSize, rootRef, parentProps]); | |
| React.useLayoutEffect(() => { | |
| if (rootRef.current && totalSize > 0) { | |
| const paddingStyle = (parentProps as any)?.style; | |
| const paddingTop = paddingStyle?.paddingTop ?? paddingStyle?.paddingVertical ?? paddingStyle?.padding ?? 0; | |
| const paddingBottom = paddingStyle?.paddingBottom ?? paddingStyle?.paddingVertical ?? paddingStyle?.padding ?? 0; | |
| const paddingVertical = (typeof paddingTop === 'number' ? paddingTop : 0) | |
| (typeof paddingBottom === 'number' ? paddingBottom : 0); | |
| rootRef.current.style.height = `${totalSize + paddingVertical}px`; | |
| } | |
| }, [totalSize, rootRef, parentProps]); |
🤖 Prompt for AI Agents
In `@packages/components/src/virtualized-list/virtualized-list.web.tsx` around
lines 142 - 150, The padding calculation in the React.useLayoutEffect for
virtualized-list.web.tsx is fragile: update the logic in the effect that uses
rootRef, totalSize and parentProps to compute vertical padding by (1) checking
explicit vertical properties (paddingTop, paddingBottom) on parentProps.style in
addition to shorthand padding, (2) handling string values like "10px" by parsing
units to numbers (e.g., parseFloat) and defaulting to 0 when parsing fails, and
(3) as a fallback, obtaining computed styles from the actual parent DOM node
(via rootRef.current.parentElement and getComputedStyle) to reliably read
paddingTop/paddingBottom; then sum top+bottom and add to totalSize when setting
rootRef.current.style.height.
Add NotificationPayload interface that integrates with the logger infrastructure: - showToast: triggers toast notification on receive - saveToDb: saves to logs database - level: controls toast color (info/success/warn/error) - category: for filtering/grouping (welcome/update/alert/etc) - toast.action: action button with URL support Only new notifications (via WebSocket) trigger toasts, not initial load.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@packages/core/src/hooks/use-novu-notifications.ts`:
- Around line 179-221: processNotificationPayload may call log[level] with an
unvalidated payload.level causing a TypeError; inside the
processNotificationPayload callback validate payload.level against allowed
methods on the log object (e.g., 'info','warn','error','debug') and coerce to a
safe fallback (such as 'info') before calling log[validatedLevel](...).
Specifically, replace direct use of log[level] with a small guard that checks
typeof log[level] === 'function' or maps unknown values to 'info' (or call
log.info) when constructing the toast logging branch so unknown payload.level
values cannot break the flow.
- Around line 316-327: The initial fetchNotifications() call can reject and
cause an unhandled promise rejection and skip logging; update the promise chain
in use-novu-notifications (around setIsLoading, fetchNotifications, log.info,
syncToRxDB) to add a .catch(handler) that logs the error (use log.error with
context) and optionally handle fallback behavior, then keep the .finally(() =>
setIsLoading(false)) so loading state always clears; ensure syncToRxDB only runs
on success (inside the .then) and errors are not swallowed.
♻️ Duplicate comments (1)
packages/core/src/hooks/use-novu-notifications.ts (1)
233-260: PreventcreatedAtfrom becomingNaNbefore upsert.
notification.createdAtcan be missing/malformed, andnew Date(...).getTime()can yieldNaN, which breaks RxDB sorting/indexing. (Duplicate of prior review note.)🛠️ Suggested defensive conversion
- try { - await notificationsCollection.upsert({ + try { + const createdAtMs = notification.createdAt + ? new Date(notification.createdAt).getTime() + : 0; + await notificationsCollection.upsert({ id: String(notificationId), subscriberId, title: notification.subject || '', body: notification.body || '', status: notification.isRead ? 'read' : 'unread', seen: notification.isSeen ?? false, - createdAt: new Date(notification.createdAt).getTime(), + createdAt: Number.isFinite(createdAtMs) ? createdAtMs : 0, // v3 uses `data` instead of `payload` payload: (notification.data as NotificationPayload) || {}, channel: notification.channelType || 'in_app', });
🧹 Nitpick comments (1)
packages/core/src/hooks/use-novu-notifications.ts (1)
337-405: Consider reconciling RxDB state when Novu mutations fail.
Local optimistic updates can diverge from server state ifnovuMarkAsRead/Seenfails. Consider rollback or a follow-up refresh to realign local state.
| const processNotificationPayload = React.useCallback( | ||
| (notification: NovuNotification, isNewNotification: boolean) => { | ||
| const payload = (notification.data as NotificationPayload) || {}; | ||
| const title = notification.subject || payload.subject || ''; | ||
| const body = notification.body || payload.body || ''; | ||
|
|
||
| // Only show toast for NEW notifications (not on initial load/refresh) | ||
| if (isNewNotification && payload.showToast) { | ||
| const level = payload.level || 'info'; | ||
|
|
||
| log[level](title, { | ||
| showToast: true, | ||
| saveToDb: payload.saveToDb, | ||
| context: { | ||
| notificationId: notification.id, | ||
| category: payload.category, | ||
| }, | ||
| toast: { | ||
| text2: payload.toast?.text2 || body, | ||
| dismissable: payload.toast?.dismissable ?? true, | ||
| action: payload.toast?.action | ||
| ? { | ||
| label: payload.toast.action.label, | ||
| onClick: () => { | ||
| if (payload.toast?.action?.url) { | ||
| handleActionUrl(payload.toast.action.url); | ||
| } | ||
| }, | ||
| } | ||
| : undefined, | ||
| }, | ||
| }); | ||
| } else if (payload.saveToDb) { | ||
| // Save to logs DB without showing toast | ||
| log.info(title, { | ||
| saveToDb: true, | ||
| context: { | ||
| notificationId: notification.id, | ||
| body, | ||
| category: payload.category, | ||
| }, | ||
| }); | ||
| } |
There was a problem hiding this comment.
Guard against unknown payload.level values before calling log[level].
Runtime payloads can contain unexpected values, which would throw TypeError: log[level] is not a function and abort the sync flow. Add a safe fallback.
🛠️ Suggested hardening
- if (isNewNotification && payload.showToast) {
- const level = payload.level || 'info';
+ if (isNewNotification && payload.showToast) {
+ const level = payload.level;
+ const logLevel =
+ level === 'info' || level === 'success' || level === 'warn' || level === 'error'
+ ? level
+ : 'info';
- log[level](title, {
+ log[logLevel](title, {
showToast: true,
saveToDb: payload.saveToDb,
context: {
notificationId: notification.id,
category: payload.category,
},
toast: {🤖 Prompt for AI Agents
In `@packages/core/src/hooks/use-novu-notifications.ts` around lines 179 - 221,
processNotificationPayload may call log[level] with an unvalidated payload.level
causing a TypeError; inside the processNotificationPayload callback validate
payload.level against allowed methods on the log object (e.g.,
'info','warn','error','debug') and coerce to a safe fallback (such as 'info')
before calling log[validatedLevel](...). Specifically, replace direct use of
log[level] with a small guard that checks typeof log[level] === 'function' or
maps unknown values to 'info' (or call log.info) when constructing the toast
logging branch so unknown payload.level values cannot break the flow.
- Move WebSocket notification deduplication to client.ts (global level) to handle multiple hook instances from Expo Router keeping pages mounted - Fix saveToDb by wrapping context in object (RxDB schema requires object) - Only process notification behaviors for new notifications, not on refresh - Add notification-behaviors.ts for client-side workflow configuration
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@packages/core/src/contexts/novu/index.tsx`:
- Around line 20-33: The NOVU_CONFIG in
packages/core/src/contexts/novu/index.tsx is inconsistent with the config built
in client.ts: it hardcodes the production app id (Wu5i9hEUNMO2) instead of using
the same environment detection (__DEV__ / NODE_ENV) and uses
backendUrl/socketUrl keys while client.ts uses apiUrl; fix by centralizing the
config (export the canonical config from client.ts and import it into the
context) or align NOVU_CONFIG to mirror client.ts (use the same app id selection
logic and property names—applicationIdentifier/apiUrl/socketUrl) and remove any
unused exported NOVU_CONFIG to avoid confusion; refer to NOVU_CONFIG in the
context and the config creation logic in client.ts when making the change.
In `@packages/core/src/services/novu/client.ts`:
- Around line 128-134: Replace the undocumented direct socket access and add
listener cleanup: in the getNovuClient/novu client teardown where the code calls
novuClient.socket.disconnect(), wrap any socket shutdown in a try/catch and fail
silently with a debug/error log if the socket property is missing (or remove the
explicit disconnect if you confirm the SDK cleans up on GC), and capture the
unsubscribe function returned when adding the session.initialize.resolved
listener (the callback added near where session.initialize.resolved is used) so
you can call that unsubscribe during cleanup (when nulling novuClient) to avoid
accumulating listeners; update references to novuClient, getNovuClient and the
session.initialize.resolved listener to use the captured unsubscribe and safe
shutdown logic.
♻️ Duplicate comments (3)
packages/core/src/hooks/use-novu-notifications.ts (3)
157-180: Guard against invalidlevelvalues before callinglog[level].While
behavior.levelis typed as'info' | 'success' | 'warn' | 'error', runtime data could contain unexpected values. The previous review recommended validating the level before using it as a dynamic property access:if (behavior.showToast) { - const level = behavior.level || 'info'; + const rawLevel = behavior.level || 'info'; + const level = ['info', 'success', 'warn', 'error'].includes(rawLevel) ? rawLevel : 'info'; log[level](title, {This prevents
TypeError: log[level] is not a functionif an unexpected value slips through.
217-229: Guard against invalidcreatedAtto prevent NaN in RxDB.The
notification.createdAtfield from Novu is optional and could be missing or malformed.new Date(undefined).getTime()returnsNaN, which breaks RxDB sorting on the indexedcreatedAtfield.Add defensive conversion:
+const createdAtMs = notification.createdAt + ? new Date(notification.createdAt).getTime() + : Date.now(); +const validCreatedAt = Number.isFinite(createdAtMs) ? createdAtMs : Date.now(); + await notificationsCollection.upsert({ id: String(notificationId), subscriberId, title: notification.subject || '', body: notification.body || '', status: notification.isRead ? 'read' : 'unread', seen: notification.isSeen ?? false, - createdAt: new Date(notification.createdAt).getTime(), + createdAt: validCreatedAt, workflowId, channel: notification.channelType || 'in_app', });
330-341: Handle initial fetch failures to avoid unhandled promise rejections.The promise chain for
fetchNotifications()lacks a.catch()handler. If the fetch fails, the rejection could surface as an unhandled promise rejection.fetchNotifications() .then((notifications) => { log.info('Novu: Initial notifications loaded', { context: { count: notifications.length }, }); return syncToRxDB(notifications); }) + .catch((error) => { + log.error('Novu: Failed to load initial notifications', { + context: { error: error instanceof Error ? error.message : String(error) }, + }); + }) .finally(() => { setIsLoading(false); });
🧹 Nitpick comments (2)
packages/core/src/services/novu/notification-behaviors.ts (1)
60-118: Consider stricter typing for workflow behavior keys.The
WORKFLOW_BEHAVIORSusesRecord<string, NotificationBehavior>which allows any string key. Consider using a stricter type to ensure only known workflow IDs are used:const WORKFLOW_BEHAVIORS: Partial<Record<typeof WORKFLOW_IDS[keyof typeof WORKFLOW_IDS], NotificationBehavior>> = {This would catch typos at compile time. However, the current approach works fine given the controlled access via
WORKFLOW_IDSconstants.packages/core/src/hooks/use-novu-notifications.ts (1)
271-274:isConnectedset totruebefore WebSocket is actually ready.
setIsConnected(true)is called immediately aftergetNovuClient(), but the WebSocket connection is established asynchronously. Consider settingisConnectedtotrueonly afterwaitForNovuReady()resolves successfully:getNovuClient(subscriberId, subscriberMetadata); -setIsConnected(true); +// isConnected will be set after WebSocket is readyThen in the
waitForNovuReadycallback:waitForNovuReady(5000).then((connected) => { setIsConnected(connected); // ... });This provides more accurate connection state to consumers.
| const NOVU_CONFIG = { | ||
| applicationIdentifier: | ||
| process.env.EXPO_PUBLIC_NOVU_APPLICATION_ID || | ||
| process.env.NOVU_APPLICATION_ID || | ||
| 'Wu5i9hEUNMO2', | ||
| backendUrl: | ||
| process.env.EXPO_PUBLIC_NOVU_API_URL || | ||
| process.env.NOVU_API_URL || | ||
| 'https://api.notifications.wcpos.com', | ||
| socketUrl: | ||
| process.env.EXPO_PUBLIC_NOVU_SOCKET_URL || | ||
| process.env.NOVU_SOCKET_URL || | ||
| 'wss://ws.notifications.wcpos.com', | ||
| }; |
There was a problem hiding this comment.
Config inconsistency with client.ts.
This NOVU_CONFIG differs from the one in client.ts:
- Environment detection:
client.tsdynamically selects app ID based on__DEV__orNODE_ENV, but this config always defaults to the production app ID (Wu5i9hEUNMO2). - Property naming: Uses
backendUrlhere vsapiUrlinclient.ts.
This could cause confusion if both configs are used. Consider:
- Either centralizing the config in one place and importing it
- Or ensuring this context config isn't actually used (it's exposed but may not be consumed)
#!/bin/bash
# Check if NOVU_CONFIG from context is actually used anywhere
rg -n "config\." packages/core/src --type ts --type tsx -A 2 | grep -E "applicationIdentifier|backendUrl|socketUrl" | head -20🤖 Prompt for AI Agents
In `@packages/core/src/contexts/novu/index.tsx` around lines 20 - 33, The
NOVU_CONFIG in packages/core/src/contexts/novu/index.tsx is inconsistent with
the config built in client.ts: it hardcodes the production app id (Wu5i9hEUNMO2)
instead of using the same environment detection (__DEV__ / NODE_ENV) and uses
backendUrl/socketUrl keys while client.ts uses apiUrl; fix by centralizing the
config (export the canonical config from client.ts and import it into the
context) or align NOVU_CONFIG to mirror client.ts (use the same app id selection
logic and property names—applicationIdentifier/apiUrl/socketUrl) and remove any
unused exported NOVU_CONFIG to avoid confusion; refer to NOVU_CONFIG in the
context and the config creation logic in client.ts when making the change.
| // Clean up existing client | ||
| if (novuClient) { | ||
| log.debug('Novu: Disconnecting previous client'); | ||
| // Disconnect the WebSocket before releasing the reference | ||
| novuClient.socket.disconnect(); | ||
| novuClient = null; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
@novu/js SDK v3 socket disconnect cleanup API documentation
💡 Result:
I couldn't find any documented public "disconnect" / "cleanup" API on the @novu/js v3 reference. The official @novu/js docs list the SDK surface and real-time events but do not expose a documented method for explicitly closing the underlying socket connection. [1][2]
If you need to stop socket activity in v3 you have two options used by integrators today:
- Unmount/remove the Inbox/component that created the Novu instance so the SDK's internal connection is no longer used (recommended if you're using
@novu/react/@novu/headless). [1] - If you must forcibly close the socket, inspect the Novu instance at runtime for an internal socket property (not part of the public API) and call its close/disconnect — but this is unsupported and may break across versions. (No public API documented for this.) [1]
References
@novu/jsSDK API reference (no disconnect/cleanup method listed). [1]- Novu changelog / repo notes referencing socket management and durable socket changes (implementation-level, not a public client API). [2]
Sources:
[1] https://docs.novu.co/platform/sdks/javascript
[2] https://docs.novu.co/platform/sdks/react/changelog
If you want, I can (a) search the novuhq GitHub code for internal socket properties/method names in the exact @novu/js version you're using, or (b) open an issue/ask Novu support for a supported cleanup API — which would let me fetch concrete code pointers. Which would you prefer?
🏁 Script executed:
find . -name "package.json" -type f | head -5 | xargs grep -l "@novu/js" 2>/dev/nullRepository: wcpos/monorepo
Length of output: 86
🏁 Script executed:
cat package.json | grep -A2 -B2 "@novu/js" || cat packages/*/package.json | grep -A2 -B2 "@novu/js"Repository: wcpos/monorepo
Length of output: 227
🏁 Script executed:
# Search for other uses of novuClient.socket in the codebase
rg "novuClient\.socket" --type ts --type jsRepository: wcpos/monorepo
Length of output: 209
🏁 Script executed:
# Look at the imports and how Novu client is being used
head -50 packages/core/src/services/novu/client.ts | cat -nRepository: wcpos/monorepo
Length of output: 2149
🏁 Script executed:
# Get more context around the socket.disconnect() calls
sed -n '120,140p' packages/core/src/services/novu/client.ts | cat -nRepository: wcpos/monorepo
Length of output: 910
🏁 Script executed:
# Check if there's any try-catch or error handling
rg "socket\.disconnect" -B5 -A5 packages/core/src/services/novu/client.tsRepository: wcpos/monorepo
Length of output: 614
🏁 Script executed:
# Search for cleanup or destroy patterns in novu-related files
rg "disconnect|cleanup|destroy|unmount" packages/core/src/services/novu/ --type tsRepository: wcpos/monorepo
Length of output: 716
🏁 Script executed:
# Look at the entire getNovuClient function
sed -n '122,180p' packages/core/src/services/novu/client.ts | cat -nRepository: wcpos/monorepo
Length of output: 2403
Replace novuClient.socket.disconnect() with a documented cleanup approach or add error handling; also clean up the session.initialize.resolved listener to prevent memory leaks.
The @novu/js v3 SDK does not expose a documented public API for disconnecting the socket. Accessing novuClient.socket.disconnect() relies on undocumented internal implementation that may break in future versions. If the socket property doesn't exist or changes, the code will fail silently or throw a runtime error with no error handling.
Additionally, the session.initialize.resolved listener added at line 48 is never cleaned up. If getNovuClient() is called multiple times (e.g., when subscriber changes), old listeners accumulate without removal, causing a memory leak. The codebase elsewhere acknowledges this pattern—see the comment "on() returns an unsubscribe function - capture it for proper cleanup"—but doesn't apply it here.
Consider either: (1) contacting Novu support for a stable cleanup API in v3, (2) wrapping the disconnect call in try-catch to handle missing socket gracefully, or (3) removing the disconnect call if the SDK handles cleanup internally when the instance is dereferenced.
🤖 Prompt for AI Agents
In `@packages/core/src/services/novu/client.ts` around lines 128 - 134, Replace
the undocumented direct socket access and add listener cleanup: in the
getNovuClient/novu client teardown where the code calls
novuClient.socket.disconnect(), wrap any socket shutdown in a try/catch and fail
silently with a debug/error log if the socket property is missing (or remove the
explicit disconnect if you confirm the SDK cleans up on GC), and capture the
unsubscribe function returned when adding the session.initialize.resolved
listener (the callback added near where session.initialize.resolved is used) so
you can call that unsubscribe during cleanup (when nulling novuClient) to avoid
accumulating listeners; update references to novuClient, getNovuClient and the
session.initialize.resolved listener to use the captured unsubscribe and safe
shutdown logic.
- Fix notification panel to properly scroll within popover constraints - Match Combobox pattern: Root with flex-1, List with flex styles - Fix VirtualizedList.List parentProps spread order bug that was overwriting critical container styles (position, height, width) - Simplify VirtualizedList documentation to match actual working usage - Style notification items to match Combobox appearance
Notification improvements (CodeRabbit review): - Add defensive guard for invalid createdAt timestamps in RxDB sync - Add .catch() handler for initial notification fetch to prevent unhandled rejections - Clean up session.initialize.resolved listener to prevent memory leaks Database schema fixes: - Change sortable_price from integer to number to avoid 32-bit overflow for prices > $2,147 - Add empty string to tax_status enum to handle WooCommerce returning invalid values - Replace payload with workflowId in notifications schema - Bump products schema to v4, variations schema to v5
Summary by CodeRabbit
New Features
Updates
Documentation
✏️ Tip: You can customize this high-level summary in your review settings.