Skip to content

Conversation

@ucswift
Copy link
Member

@ucswift ucswift commented Jan 25, 2026

Summary by CodeRabbit

  • New Features

    • Multi-target deployments: Docker multi-arch images with runtime env injection, Electron desktop apps (Win/macOS/Linux), and web static hosting with nginx.
    • Web service worker and push notification support for web and Electron.
    • Platform-aware, cross-platform map library (web + native).
  • Documentation

    • Detailed Docker guide, CI/CD build system docs, build quick reference, and expanded deployment section in README.
  • Build & Infrastructure

    • Expanded CI to build web, Docker, Electron, and mobile; local Docker Compose added.
    • Added .dockerignore and runtime entrypoint for env injection.
  • Refactor

    • Improved type safety for timers and environment handling; added platform detection utilities.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 25, 2026

📝 Walkthrough

Walkthrough

Expands multi-platform delivery: adds Docker runtime injection and compose, introduces Electron desktop app and web build artifacts, extends CI/CD with web/docker/electron jobs and release-notes handling, implements a unified platform-aware Mapbox wrapper (web + native), web shims for native modules, and platform-specific push notification services and service worker.

Changes

Cohort / File(s) Summary
Docker Infra & Runtime
\.dockerignore, Dockerfile, docker-compose.yml, nginx.conf, docker/docker-entrypoint.sh
Multi-stage Node 20 → Nginx image, entrypoint generates env-config.js and injects into index.html, healthcheck and compose mapping, runtime env injection and port/exposure changes.
Docker Docs & Types
docker/README.md, types/docker-env.d.ts
New Docker deployment guide and TypeScript ambient types for window.__ENV__.
CI/CD Workflow
.github/workflows/react-native-cicd.yml
Large workflow overhaul: build-mobile rename, added build-web, build-docker, build-electron, release-notes extraction, multi-arch Docker pushes, Electron multi-OS artifacts, and expanded permissions/env declarations.
Electron Platform
electron-builder.config.js, electron/entitlements.mac.plist, electron/main.js, electron/preload.js
New Electron config, macOS entitlements, main process (window/menu/IPC), and secure preload API exposing notifications/window controls/platform info.
Web Service Worker & Push
public/service-worker.js, src/services/push-notification.web.ts, src/services/push-notification.electron.ts
New service worker handling push and client-ready handshake; web push service using VAPID and SW; Electron push service delegating to window.electronAPI.
Map Platform Abstraction
src/components/maps/mapbox.ts, src/components/maps/map-view.web.tsx, src/components/maps/map-view.native.tsx
Unified Mapbox entry that selects web (mapbox-gl) or native (@rnmapbox/maps) implementations at runtime and re-exports a consistent API surface.
Map Consumers Updated
src/app/(app)/index.tsx, src/components/maps/* (full-screen-location-picker.tsx, location-picker.tsx, map-pins.tsx, pin-marker.tsx, static-map.tsx)
Switched imports to local Mapbox wrapper and updated ref typings to React.ElementRef<typeof Mapbox.*> with camera refs typed as any where needed.
Native Module Shims & Resolver
src/lib/native-module-shims.web.ts, metro.config.js, src/lib/platform.ts
Large web shims for many native modules (no-ops/mocks), Metro resolver override to redirect native imports on web, and new platform detection utilities (isWeb/isElectron/isDesktop/etc.).
Runtime Env Handling
src/lib/env.js, env.js, README.md
Runtime Docker/web env detection via window.__ENV__, selection between runtime vs build-time config; README updated with "Deployment Options".
Docker Compose & Health
docker-compose.yml
New compose service for web with long list of UNIT_* env vars and a wget-based healthcheck.
Bluetooth / BLE Improvements
src/services/bluetooth-audio.service.ts
Threaded PeripheralInfo through discovery and monitoring, added hasCharacteristic guard and signature changes to related methods; timer type adjustments.
Map/Timer Type Safety
src/hooks/*, src/services/offline-event-manager.service.ts
Timer handles standardized to `ReturnType<typeof setTimeout
Electron Build & Scripts
package.json, electron/*
New build/dev scripts for web/electron, added electron and electron-builder deps, and related scripts.
Docs: CI/Build Quick Reference
docs/cicd-build-system.md, docs/build-quick-reference.md, docs/android-ptt-foreground-service-fix.md
New documentation covering CI/CD, build quick reference, and Android PTT foreground service type fix.
Formatting / Minor Changes
src/services/media-button.service.ts, src/stores/app/*
Minor import formatting and small Android notification config update in livekit-store.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Container Start
    participant Entrypoint as docker-entrypoint.sh
    participant Nginx as Nginx Server
    participant Browser as Browser/Client

    Client->>Entrypoint: Container starts (ENV vars present)
    Entrypoint->>Entrypoint: Generate /usr/share/nginx/html/env-config.js
    Entrypoint->>Entrypoint: Inject script tag into index.html if missing
    Entrypoint->>Nginx: exec nginx (foreground)
    Browser->>Nginx: HTTP GET /
    Nginx->>Browser: Serve index.html + env-config.js
    Browser->>Browser: window.__ENV__ available (runtime config)
Loading
sequenceDiagram
    participant PushServer as Push Server
    participant SW as Service Worker
    participant OS as System Notification
    participant Client as Web App (Window)
    participant User

    PushServer->>SW: Push event (payload)
    SW->>SW: Parse payload, build notification options
    SW->>OS: showNotification(...)
    OS->>User: Display notification
    User->>OS: Click notification
    OS->>SW: notificationclick event
    SW->>Client: PostMessage NOTIFICATION_CLICK (open/focus)
    Client->>Client: Handle payload (navigate/show modal)
Loading
sequenceDiagram
    participant AppCode as App (renderer)
    participant MapboxModule as `@/components/maps/mapbox`
    participant PlatformDetect as Platform.OS check
    participant WebImpl as map-view.web.tsx
    participant NativeImpl as map-view.native.tsx

    AppCode->>MapboxModule: import Mapbox
    MapboxModule->>PlatformDetect: detect platform
    alt isWeb
        PlatformDetect->>WebImpl: require web impl
        WebImpl->>MapboxModule: export MapView/Camera/.. (mapbox-gl)
    else
        PlatformDetect->>NativeImpl: require native impl
        NativeImpl->>MapboxModule: export MapView/Camera/.. (`@rnmapbox/maps`)
    end
    MapboxModule->>AppCode: return unified Mapbox API
    AppCode->>AppCode: use Mapbox.* with consistent refs/types
Loading
sequenceDiagram
    participant Renderer as Electron Renderer
    participant Preload as Preload Script
    participant Main as Main Process
    participant System as OS Notification
    participant User

    Renderer->>Preload: electronAPI.showNotification({title,body})
    Preload->>Main: ipcRenderer.invoke('show-notification', payload)
    Main->>System: create Notification
    System->>User: display
    User->>System: click
    System->>Main: notification click event
    Main->>Renderer: ipc message 'notification-clicked' with data
    Renderer->>Renderer: handle click (focus/navigate)
Loading

Estimated Code Review Effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Poem

🐰 I stitched the web, desktop, and dock,
env-config whispers at runtime tick-tock,
maps that choose lanes, native or web,
notifications hop, awake from their bed,
build jobs hum — a multi-platform clock! 🥕✨

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The pull request title 'Develop' is vague and non-descriptive, using a generic branch name rather than summarizing the actual changes in the PR. Replace 'Develop' with a specific, concise title that summarizes the main changes, such as 'Add web, Docker, and Electron build support with CI/CD expansion' or similar.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 16

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/components/maps/full-screen-location-picker.tsx (2)

14-15: Missing null check for Mapbox access token.

Unlike location-picker.tsx which validates the token before calling setAccessToken, this file calls it unconditionally. If UNIT_MAPBOX_PUBKEY is undefined, this could cause issues.

📝 Suggested fix
 // Ensure Mapbox access token is set before using any Mapbox components
+if (!Env.UNIT_MAPBOX_PUBKEY) {
+  console.error('Mapbox access token is not configured. Please set UNIT_MAPBOX_PUBKEY in your environment.');
+} else {
+  Mapbox.setAccessToken(Env.UNIT_MAPBOX_PUBKEY);
+}
-Mapbox.setAccessToken(Env.UNIT_MAPBOX_PUBKEY);

184-184: Hardcoded string should use translation function.

Per coding guidelines, all user-facing text should be wrapped in t() from react-i18next. The title prop uses a hardcoded string "Selected Location".

📝 Suggested fix
-        <Mapbox.PointAnnotation id="selectedLocation" coordinate={[currentLocation.longitude, currentLocation.latitude]} title="Selected Location">
+        <Mapbox.PointAnnotation id="selectedLocation" coordinate={[currentLocation.longitude, currentLocation.latitude]} title={t('common.selected_location')}>
🤖 Fix all issues with AI agents
In @.github/workflows/react-native-cicd.yml:
- Around line 531-536: The step "🐳 Log in to Docker Hub" currently uses an if:
expression referencing the secrets context which is not available in step-level
ifs; change the workflow to compute a boolean flag at the job level or via a
preceding step and reference that flag instead: add a job-level env like
HAS_DOCKER_CREDS computed from the secrets (or create a setup step that sets an
output HAS_DOCKER_CREDS) and then update the "🐳 Log in to Docker Hub" step's if
to check that env/output flag before invoking docker/login-action@v3 to avoid
using secrets in the step-level if.

In `@docker/docker-entrypoint.sh`:
- Around line 7-28: The heredoc writes raw environment values into env-config.js
which can break JS or allow injection; add a shell escaper (e.g., a function
named js_escape or escape_js) that JSON-escapes quotes, backslashes and newlines
(or use jq -Rn --arg to produce safe JSON strings), and call it for every string
field in the window.__ENV__ block (replace "${APP_ENV:-...}" with "$(js_escape
"${APP_ENV:-production}")", do likewise for NAME, SCHEME, VERSION, BASE_API_URL,
API_VERSION, RESGRID_API_URL, CHANNEL_HUB_NAME, REALTIME_GEO_HUB_NAME,
LOGGING_KEY, APP_KEY, UNIT_MAPBOX_PUBKEY, SENTRY_DSN, COUNTLY_APP_KEY,
COUNTLY_SERVER_URL) so the generated env-config.js contains valid, escaped JS
string literals.

In `@docs/build-quick-reference.md`:
- Around line 37-41: Update the "Electron Builds" Windows entry to match README
wording: replace the ambiguous ".exe, .msi" with a clear phrasing like "Portable
.exe and NSIS installer (.exe)" (or "Portable .exe, NSIS .exe") in the "Electron
Builds" section so it no longer implies NSIS produces .msi; edit the Windows
list item in that section accordingly.

In `@electron/main.js`:
- Around line 27-28: The frame option currently uses a tautological expression
(process.platform !== 'darwin' || true) so it always evaluates to true; change
the logic in the window creation options (the frame property) to reflect the
intended behavior—use a boolean expression that is false on macOS and true
elsewhere (e.g., frame: process.platform !== 'darwin') or invert it if you
intended to hide the frame on macOS (e.g., frame: process.platform === 'darwin'
? false : true); update the frame assignment where the BrowserWindow is
constructed to remove the stray "|| true" and use the correct platform check.
- Around line 5-8: The code uses require('electron-squirrel-startup') in main.js
to handle Windows installer shortcuts but the package is missing from
dependencies; add "electron-squirrel-startup" to package.json dependencies (not
devDependencies) so require('electron-squirrel-startup') resolves at runtime, or
delete the require/app.quit() block if you are not shipping Squirrel.Windows
installers; ensure package.json is updated and install (npm/yarn/pnpm) so the
app.quit() branch works correctly on Windows.

In `@electron/preload.js`:
- Around line 12-18: The current onNotificationClicked registration uses
ipcRenderer.on and returns a cleanup that calls removeAllListeners which will
unregister other subscribers; instead register the listener as a named function
(e.g., const listener = (event, data) => callback(data)) when calling
ipcRenderer.on inside onNotificationClicked and return a cleanup that calls
ipcRenderer.removeListener('notification-clicked', listener) so only that
specific handler is removed; update the onNotificationClicked implementation to
use this specific listener reference rather than removeAllListeners.

In `@nginx.conf`:
- Around line 38-48: The security headers declared with add_header in the server
block (X-Frame-Options, X-Content-Type-Options, X-XSS-Protection) are not
inherited by the static assets location; update the location ~*
\.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ block to explicitly
include the same add_header directives (with always) so static files are served
with the same security headers as the parent.

In `@public/service-worker.js`:
- Around line 84-91: The arbitrary 1s setTimeout before calling
client.postMessage({type: 'NOTIFICATION_CLICK', ...}) is fragile; implement a
handshake instead: after clients.openWindow(...) store the pending notification
data keyed to the returned client (or its id) and remove the timeout; add a
service worker message listener that waits for a readiness signal (e.g.
message.type === 'SW_READY' or a handshake response) from that client and only
then call client.postMessage with 'NOTIFICATION_CLICK'; alternatively implement
a short poll/handshake (send PING and wait for PONG) using the same
client.postMessage/message event flow so the notification is sent reliably when
the page is initialized.

In `@src/components/maps/map-view.web.tsx`:
- Around line 246-294: PointAnnotation currently ignores React children and the
anchor prop: instead of writing a hardcoded innerHTML it should render actual
React children into the marker container (use ReactDOM.createPortal or
ReactDOM.render into containerRef.current inside the effect, referencing
PointAnnotation's containerRef / markerRef) and it should apply the anchor prop
when creating the mapbox marker (if anchor is a string/valid mapbox anchor pass
it to new mapboxgl.Marker's options; if anchor is an {x,y} object convert to
pixel offset and call marker.setOffset([xOffset, yOffset]) after creation).
Ensure you also attach the click listener to containerRef.current and clean up /
unmount the portal on effect teardown.
- Around line 306-327: The useEffect that creates a mapboxgl.GeolocateControl
(geolocate) currently only calls geolocate.trigger() inside map.on('load'),
which misses the case where map is already loaded; update the effect (in the
useEffect that references map, visible, showsUserHeadingIndicator) to: add
geolocate to the map as before, then if map.loaded() is true call
geolocate.trigger() immediately, otherwise attach a named load handler (e.g.
onMapLoad) that calls geolocate.trigger(), and remove that handler in the
cleanup along with removing the control; this ensures geolocate.trigger() runs
whether the map was already loaded or loads later.

In `@src/lib/native-module-shims.web.ts`:
- Around line 63-82: The current export defines messaging as a function and then
assigns messaging.AuthorizationStatus which breaks TypeScript; change messaging
to a callable object with a proper type and attach AuthorizationStatus via
Object.assign. Define an interface/type (e.g., Messaging) that declares a call
signature returning the shim methods and a static AuthorizationStatus property,
then replace the export with something like: const messaging =
Object.assign((/*...*/) => ({ ... }), { AuthorizationStatus: { NOT_DETERMINED:
-1, DENIED: 0, AUTHORIZED: 1, PROVISIONAL: 2 } }) and export that const so
TypeScript understands both the callable behavior and the static
AuthorizationStatus property.

In `@src/lib/platform.ts`:
- Around line 10-11: The current isElectron export uses window.process?.type
which fails with contextIsolation and nodeIntegration disabled; update the
detection in the isElectron constant (in src/lib/platform.ts) to prefer the
preload-exposed electronAPI (e.g., check for typeof window !== 'undefined' &&
!!(window as any).electronAPI) and fall back to the existing
window.process?.type check only if present. Replace the current export with a
combined check that first looks for the preload-exposed electronAPI and then
falls back to the old process.type test so detection works when contextIsolation
is enabled.

In `@src/services/bluetooth-audio.service.ts`:
- Around line 919-931: In setupButtonEventMonitoring, replace the unsafe any in
the peripheralInfo.services map with the proper Service type from
react-native-ble-manager: import { Service } from 'react-native-ble-manager' and
change the map callback signature from (s: any) => s.uuid to (s: Service) =>
s.uuid; ensure the peripheralInfo.services is treated as Service[] (or cast it
where needed) so the logger's availableServices remains a string[] and
TypeScript compiles without using any.

In `@src/services/push-notification.electron.ts`:
- Around line 48-54: Update the notification click registration to capture and
store the unsubscribe function returned by the preload API: change the type
signature of onNotificationClicked to return a () => void, update the call in
the service to assign the returned function to cleanupListener instead of
setting it to null, and ensure cleanup() (and any re-init/HMR logic) invokes
cleanupListener when present; key symbols: onNotificationClicked,
cleanupListener, handleNotificationClick, cleanup().

In `@src/stores/app/livekit-store.ts`:
- Line 1: Replace the magic number [2] used when starting the Android foreground
service with the imported enum value from AndroidForegroundServiceType (use the
appropriate member name for the service type), i.e. update the notifee call that
currently passes [2] to pass AndroidForegroundServiceType.<appropriate_member>
instead; keep the existing import of AndroidForegroundServiceType and consult
the library docs to pick the exact enum member that matches the numeric 2.

In `@types/docker-env.d.ts`:
- Line 19: The declared type for IS_MOBILE_APP in types/docker-env.d.ts is a
boolean but runtime env vars are strings; change the type of IS_MOBILE_APP to
string (update the IS_MOBILE_APP declaration) and ensure callers parse it (e.g.,
where window.__ENV__ or IS_MOBILE_APP is consumed, convert with a strict
equality check against 'true' or run a small parser) so runtime values like
"false" do not evaluate truthily.
🧹 Nitpick comments (34)
docs/android-ptt-foreground-service-fix.md (1)

6-8: Add language specifier to the fenced code block.

The error message code block is missing a language specifier. While this is a plain text error, using a language hint improves rendering.

Suggested fix
-```
+```text
 java.lang.SecurityException: Starting FGS with type microphone callerApp=ProcessRecord{...} targetSDK=35 requires permissions: all of the permissions allOf=true [android.permission.FOREGROUND_SERVICE_MICROPHONE] any of the permissions allOf=false [android.permission.RECORD_AUDIO, ...]
</details>

</blockquote></details>
<details>
<summary>src/services/bluetooth-audio.service.ts (1)</summary><blockquote>

`940-955`: **Multiple `any` type assertions should be replaced with proper types.**

This helper method has good defensive logic, but uses `any` type assertions in multiple places (lines 948, 950, 954), which violates the coding guideline to "avoid using `any`; strive for precise types."



<details>
<summary>Suggested improvement</summary>

Define a local interface for the service/characteristic structure returned by `BleManager.retrieveServices()`:

```typescript
interface BleService {
  uuid: string;
  characteristics?: Array<{
    characteristic: string;
    // add other properties as needed
  }>;
}

private hasCharacteristic(peripheralInfo: PeripheralInfo, serviceUuid: string, characteristicUuid: string): boolean {
  if (!peripheralInfo?.services) {
    return false;
  }

  const services = peripheralInfo.services as BleService[];
  const service = services.find((s) => s.uuid?.toUpperCase() === serviceUuid.toUpperCase());

  if (!service?.characteristics) {
    return false;
  }

  return service.characteristics.some((c) => c.characteristic?.toUpperCase() === characteristicUuid.toUpperCase());
}

As per coding guidelines, strive for precise types instead of any.

.dockerignore (1)

56-62: Duplicate *.pem pattern.

The *.pem pattern appears twice: on line 24 (under Misc) and line 58 (under Secrets and keys). While harmless, consider removing the duplicate for cleaner configuration.

🧹 Suggested fix
 # Misc
 .DS_Store
-*.pem
 .env
 .env.local
package.json (1)

109-109: Move @types/mapbox-gl to devDependencies.

Type definition packages (@types/*) are only needed during development/build time and should be in devDependencies rather than dependencies to avoid shipping unnecessary packages in production.

♻️ Suggested fix

Move from dependencies:

-    "@types/mapbox-gl": "3.4.1",

Add to devDependencies:

+    "@types/mapbox-gl": "3.4.1",
electron/entitlements.mac.plist (1)

22-24: Consider deferring unused entitlements until needed.

The camera entitlement is marked as "future use." Apple's App Store review guidelines recommend requesting only permissions that are actively used. If this app will be distributed via the Mac App Store, preemptively declaring camera access without using it could lead to rejection or user trust concerns.

Consider removing this entitlement until the camera feature is implemented, or ensure there's a clear usage description in your Info.plist.

.github/workflows/react-native-cicd.yml (5)

477-517: Add explicit permissions block for security hardening.

CodeQL flags that the new jobs lack explicit permissions blocks. Following the principle of least privilege, add minimal permissions to each job.

♻️ Proposed fix for build-web job
  build-web:
    needs: test
    if: (github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master')) || github.event_name == 'workflow_dispatch'
    runs-on: ubuntu-latest
    environment: RNBuild
+   permissions:
+     contents: read
    steps:

519-570: Add permissions block for build-docker job.

This job requires additional permissions for pushing to GHCR.

♻️ Proposed fix
  build-docker:
    needs: build-web
    if: (github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master')) || github.event_name == 'workflow_dispatch'
    runs-on: ubuntu-latest
    environment: RNBuild
+   permissions:
+     contents: read
+     packages: write
    steps:

571-600: Add permissions block for build-electron job.

This job creates releases and needs appropriate permissions.

♻️ Proposed fix
  build-electron:
    needs: test
    if: (github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master')) || github.event_name == 'workflow_dispatch'
    strategy:
      matrix:
        os: [windows-latest, macos-15, ubuntu-latest]
    runs-on: ${{ matrix.os }}
    environment: RNBuild
+   permissions:
+     contents: write
    steps:

492-500: Update actions/cache to v4.

Static analysis indicates actions/cache@v3 is outdated. Update to actions/cache@v4 for the latest features and fixes.

♻️ Proposed fix
      - name: 📦 Setup yarn cache
-       uses: actions/cache@v3
+       uses: actions/cache@v4
        with:
          path: |

Also applies to: 589-597


685-806: Duplicated release notes extraction logic.

The extract_release_notes function and surrounding logic is duplicated between build-mobile (lines 296-416) and build-electron (lines 685-806). Consider extracting this into a reusable composite action or a shared script.

You could create a shared script file (e.g., scripts/extract-release-notes.sh) or a reusable composite action to avoid maintaining the same code in two places.

src/lib/platform.ts (2)

53-65: Window augmentation should remove process.type since it won't be available.

Given that nodeIntegration is disabled, window.process won't exist. The type augmentation for process is misleading and could cause confusion.

♻️ Proposed cleanup
 // Augment Window interface for Electron API
 declare global {
   interface Window {
-    process?: {
-      type?: string;
-    };
     electronAPI?: {
       showNotification: (options: { title: string; body: string; data: any }) => Promise<boolean>;
       onNotificationClicked: (callback: (data: any) => void) => void;
       platform: string;
       isElectron: boolean;
     };
   }
 }

59-64: Improve type safety by avoiding any.

Per coding guidelines, avoid using any. Consider defining a proper interface for notification options and callback data.

♻️ Proposed improvement
+interface NotificationOptions {
+  title: string;
+  body: string;
+  data?: Record<string, unknown>;
+}
+
+interface NotificationData {
+  [key: string]: unknown;
+}
+
 declare global {
   interface Window {
     electronAPI?: {
-      showNotification: (options: { title: string; body: string; data: any }) => Promise<boolean>;
-      onNotificationClicked: (callback: (data: any) => void) => void;
+      showNotification: (options: NotificationOptions) => Promise<boolean>;
+      onNotificationClicked: (callback: (data: NotificationData) => void) => void;
       platform: string;
       isElectron: boolean;
     };
   }
 }
electron/preload.js (1)

36-37: Consider removing or conditionalizing the console.log for production.

This log statement will appear in the DevTools console for every app launch. While useful during development, it may be unnecessary noise in production.

♻️ Proposed fix
-// Log that preload script has been loaded
-console.log('Electron preload script loaded');
+// Log that preload script has been loaded (development only)
+if (process.env.NODE_ENV === 'development') {
+  console.log('Electron preload script loaded');
+}

Note: process.env.NODE_ENV may not be available in preload. You might need to check process.env.NODE_ENV before the app is packaged, or simply remove the log.

electron/main.js (1)

55-59: Avoid redundant require inside the handler.

shell is available from the Electron import at the top of the file but wasn't destructured. Add it to the top-level import instead of calling require inline.

♻️ Proposed fix

At line 2, add shell to the imports:

-const { app, BrowserWindow, ipcMain, Notification, nativeTheme, Menu } = require('electron');
+const { app, BrowserWindow, ipcMain, Notification, nativeTheme, Menu, shell } = require('electron');

Then update the handler:

  // Handle external links
  mainWindow.webContents.setWindowOpenHandler(({ url }) => {
-   require('electron').shell.openExternal(url);
+   shell.openExternal(url);
    return { action: 'deny' };
  });
src/services/push-notification.electron.ts (1)

9-17: Replace any with a typed notification payload.

any removes type safety across the Electron API contract and handlers. Define a shared payload interface and reuse it in the API surface and methods.

♻️ Proposed refactor
 import { logger } from '@/lib/logging';
 import { isElectron } from '@/lib/platform';
 import { usePushNotificationModalStore } from '@/stores/push-notification/store';

+interface ElectronNotificationPayload {
+  eventCode?: string;
+  title?: string;
+  body?: string;
+  message?: string;
+  [key: string]: unknown;
+}
+
 // Type declaration for Electron API exposed via preload
 declare global {
   interface Window {
     electronAPI?: {
-      showNotification: (options: { title: string; body: string; data: any }) => Promise<boolean>;
-      onNotificationClicked: (callback: (data: any) => void) => void;
+      showNotification: (options: { title: string; body: string; data: ElectronNotificationPayload }) => Promise<boolean>;
+      onNotificationClicked: (callback: (data: ElectronNotificationPayload) => void) => void;
       platform: string;
       isElectron: boolean;
     };
   }
 }
@@
-  private handleNotificationClick = (data: any): void => {
+  private handleNotificationClick = (data: ElectronNotificationPayload): void => {
@@
-  async showNotification(title: string, body: string, data?: any): Promise<boolean> {
+  async showNotification(title: string, body: string, data?: ElectronNotificationPayload): Promise<boolean> {

As per coding guidelines, avoid any for TypeScript types.

Also applies to: 62-83

src/lib/native-module-shims.web.ts (1)

40-75: Avoid returning fake tokens/permissions in web shims.

Returning 'web-token' and AUTHORIZED can register invalid devices or show push-enabled UI on web. Prefer null/NOT_DETERMINED (or DENIED) so unsupported behavior is explicit.

♻️ Proposed refactor
 export const notifee = {
   createChannel: async () => 'web-channel',
   displayNotification: async () => 'web-notification',
   setBadgeCount: async () => {},
   getBadgeCount: async () => 0,
   incrementBadgeCount: async () => {},
   decrementBadgeCount: async () => {},
-  requestPermission: async () => ({ authorizationStatus: AuthorizationStatus.AUTHORIZED }),
-  getPermissionSettings: async () => ({ authorizationStatus: AuthorizationStatus.AUTHORIZED }),
+  requestPermission: async () => ({ authorizationStatus: AuthorizationStatus.NOT_DETERMINED }),
+  getPermissionSettings: async () => ({ authorizationStatus: AuthorizationStatus.NOT_DETERMINED }),
@@
 export const messaging = () => ({
-  getToken: async () => 'web-token',
+  getToken: async () => null,
   deleteToken: async () => {},
-  hasPermission: async () => 1,
-  requestPermission: async () => 1,
+  hasPermission: async () => AuthorizationStatus.NOT_DETERMINED,
+  requestPermission: async () => AuthorizationStatus.NOT_DETERMINED,
   onMessage: () => () => {},
   onNotificationOpenedApp: () => () => {},
   getInitialNotification: async () => null,
   setBackgroundMessageHandler: () => {},
   subscribeToTopic: async () => {},
   unsubscribeFromTopic: async () => {},
 });
metro.config.js (1)

23-39: Hoist the native module list to module scope.

The array is recreated on every resolve; move it to a top-level Set and use .has() to reduce allocations.

♻️ Proposed refactor
 const config = getSentryExpoConfig(__dirname, {
   isCSSEnabled: true,
 });
@@
-// Web-specific module resolution - redirect native modules to web shims
+// Web-specific module resolution - redirect native modules to web shims
+const nativeModules = new Set([
+  '@notifee/react-native',
+  '@react-native-firebase/messaging',
+  '@react-native-firebase/app',
+  'react-native-callkeep',
+  'react-native-ble-manager',
+  '@livekit/react-native',
+  '@livekit/react-native-webrtc',
+  '@livekit/react-native-expo-plugin',
+]);
+
 const originalResolveRequest = config.resolver.resolveRequest;
 config.resolver.resolveRequest = (context, moduleName, platform) => {
   if (platform === 'web') {
-    // Native modules that need web shims
-    const nativeModules = [
-      '@notifee/react-native',
-      '@react-native-firebase/messaging',
-      '@react-native-firebase/app',
-      'react-native-callkeep',
-      'react-native-ble-manager',
-      '@livekit/react-native',
-      '@livekit/react-native-webrtc',
-      '@livekit/react-native-expo-plugin',
-    ];
-
-    if (nativeModules.includes(moduleName)) {
+    if (nativeModules.has(moduleName)) {
       return {
         filePath: path.resolve(__dirname, 'src/lib/native-module-shims.web.ts'),
         type: 'sourceFile',
       };
     }
docker-compose.yml (1)

21-26: Consider adding placeholder instructions for required secrets.

Several environment variables are left empty (UNIT_LOGGING_KEY, UNIT_APP_KEY, UNIT_MAPBOX_PUBKEY, etc.). While this is fine for a template, consider adding comments indicating which variables are required vs optional for the application to function correctly.

📝 Suggested improvement
      - UNIT_CHANNEL_HUB_NAME=eventingHub
      - UNIT_REALTIME_GEO_HUB_NAME=geolocationHub
-      - UNIT_LOGGING_KEY=
-      - UNIT_APP_KEY=
-      - UNIT_MAPBOX_PUBKEY=
-      - UNIT_SENTRY_DSN=
-      - UNIT_COUNTLY_APP_KEY=
-      - UNIT_COUNTLY_SERVER_URL=
+      - UNIT_LOGGING_KEY=          # Optional: Logging service key
+      - UNIT_APP_KEY=              # Required: Application key
+      - UNIT_MAPBOX_PUBKEY=        # Required: Mapbox public key for maps
+      - UNIT_SENTRY_DSN=           # Optional: Sentry DSN for error tracking
+      - UNIT_COUNTLY_APP_KEY=      # Optional: Countly analytics key
+      - UNIT_COUNTLY_SERVER_URL=   # Optional: Countly server URL
src/components/maps/location-picker.tsx (1)

40-41: Avoid using any for cameraRef - consider a more precise type.

Per coding guidelines, avoid using any and strive for precise types. The comment indicates this is due to imperative handle access, but a more specific type would improve type safety.

📝 Suggested improvement

Consider defining an interface for the camera's imperative API:

interface CameraRef {
  setCamera: (options: {
    centerCoordinate: [number, number];
    zoomLevel: number;
    animationDuration?: number;
  }) => void;
}

const cameraRef = useRef<CameraRef>(null);

Alternatively, if the wrapper exports proper types, use those directly.

src/lib/env.js (1)

32-44: Consider reducing @ts-ignore usage with proper type definitions.

Three @ts-ignore comments suppress type errors. Since this file handles both Docker runtime (window.__ENV__) and native builds (Constants.expoConfig?.extra), consider defining a union type or using proper type guards to maintain type safety.

📝 Suggested approach
/**
 * `@typedef` {import('../../env.js').ClientEnv} EnvConfig
 */

/**
 * `@returns` {EnvConfig}
 */
const getEnvConfig = () => {
  if (isDockerRuntime()) {
    // `@ts-ignore` - window.__ENV__ typed in types/docker-env.d.ts
    return /** `@type` {EnvConfig} */ (window.__ENV__);
  }
  return /** `@type` {EnvConfig} */ (Constants.expoConfig?.extra ?? {});
};

/** `@type` {EnvConfig} */
export const Env = getEnvConfig();
src/components/maps/full-screen-location-picker.tsx (1)

38-39: Avoid using any for cameraRef.

Same issue as in location-picker.tsx - using any bypasses type safety. Consider defining a shared camera ref type that can be reused across map components.

src/components/maps/pin-marker.tsx (2)

18-18: Unused markerRef prop.

The markerRef prop is declared in PinMarkerProps (line 14) but is not destructured or used in the component. If it's intended for parent components to access, it should be forwarded; otherwise, consider removing it from the interface.


25-25: Potential runtime error with non-null assertion on icon.

The non-null assertion icon!.uri assumes the fallback MAP_ICONS['call'] always exists. If MAP_ICONS doesn't contain a 'call' key or the lookup fails, this will throw at runtime.

Proposed defensive fix
-      <Image fadeDuration={0} source={icon!.uri} style={[styles.image, { width: size, height: size }]} />
+      <Image fadeDuration={0} source={icon?.uri} style={[styles.image, { width: size, height: size }]} />

Alternatively, add a guard or ensure MAP_ICONS['call'] is guaranteed to exist via TypeScript strict checks.

nginx.conf (2)

41-41: X-XSS-Protection header is deprecated.

The X-XSS-Protection header is no longer recommended and has been removed from modern browsers. Consider removing it and implementing a Content-Security-Policy header instead for better XSS protection.


56-60: Reorder add_header before return for clarity.

While nginx processes directives correctly, placing add_header after return is unconventional and can confuse readers. Reordering improves readability.

Proposed fix
     # Health check endpoint
     location /health {
         access_log off;
+        add_header Content-Type text/plain;
         return 200 "healthy\n";
-        add_header Content-Type text/plain;
     }
src/app/(app)/index.tsx (2)

34-34: Consider typing cameraRef more precisely.

Using any violates the coding guideline "Avoid using any; strive for precise types." If the imperative handle API is known, consider defining an interface for the expected methods (setCamera, etc.).

Proposed interface for camera ref
interface CameraHandle {
  setCamera: (config: {
    centerCoordinate: [number, number];
    zoomLevel?: number;
    animationDuration?: number;
    heading?: number;
    pitch?: number;
  }) => void;
}

const cameraRef = useRef<CameraHandle>(null);

Based on coding guidelines, avoid using any.


398-399: Use ternary operator for conditional rendering.

Per coding guidelines, use ? : instead of && for conditional rendering. Lines 398 and 411 use &&.

Example fix for line 398-399
-          {location.latitude && location.longitude && (
+          {location.latitude && location.longitude ? (
             <Mapbox.PointAnnotation ...>
               ...
             </Mapbox.PointAnnotation>
-          )}
+          ) : null}

Based on coding guidelines.

Also applies to: 411-411

public/service-worker.js (1)

8-9: Unused CACHE_NAME constant.

CACHE_NAME is declared but never used. Either implement offline caching or remove the constant to avoid confusion.

docs/cicd-build-system.md (2)

188-194: Add language specifier to fenced code block.

The ASCII diagram code block lacks a language specifier. Use text or plaintext to satisfy linting.

Proposed fix
-```
+```text
 test (all platforms)
   ├─→ build-mobile (android, ios)
   ├─→ build-web
   │     └─→ build-docker
   └─→ build-electron (windows, macos, linux)
</details>

---

`242-242`: **Capitalize "GitHub" consistently.**

Minor: The reference should use "GitHub" with capital H for consistency with the official branding.


<details>
<summary>Proposed fix</summary>

```diff
-- Workflow file: [.github/workflows/react-native-cicd.yml](../.github/workflows/react-native-cicd.yml)
+- Workflow file: [GitHub workflow](../.github/workflows/react-native-cicd.yml)

Or keep the path as-is since it's a literal filesystem path (.github is the actual directory name).

docker/README.md (2)

70-72: Consider updating Docker Compose version specification.

The version: '3.8' field is deprecated in modern Docker Compose (v2+). While it still works for backward compatibility, you can simply remove the version line to use the latest Compose specification.

📝 Suggested update
-version: '3.8'
-
 services:
   resgrid-unit:

148-157: Consider adding a note about external secret management for Kubernetes.

While the example is instructive, it shows secrets in plaintext within stringData. Consider adding a brief note recommending external secret management solutions (e.g., HashiCorp Vault, AWS Secrets Manager, Sealed Secrets) for production deployments.

src/components/maps/mapbox.ts (1)

9-32: Consider simplifying the repeated platform checks and require calls.

Each export performs the same Platform.OS === 'web' check and require() call. Since MapboxImpl is already resolved to the correct platform module, you can destructure from it directly. This improves maintainability and reduces redundancy.

♻️ Proposed simplification
 const MapboxImpl = Platform.OS === 'web' ? require('./map-view.web').default : require('./map-view.native').default;
+const platformModule = Platform.OS === 'web' ? require('./map-view.web') : require('./map-view.native');

 // Re-export all components
 export const MapView = MapboxImpl.MapView || MapboxImpl;
-export const Camera = Platform.OS === 'web' ? require('./map-view.web').Camera : require('./map-view.native').Camera;
-export const PointAnnotation = Platform.OS === 'web' ? require('./map-view.web').PointAnnotation : require('./map-view.native').PointAnnotation;
-export const UserLocation = Platform.OS === 'web' ? require('./map-view.web').UserLocation : require('./map-view.native').UserLocation;
-export const MarkerView = Platform.OS === 'web' ? require('./map-view.web').MarkerView : require('./map-view.native').MarkerView;
-export const ShapeSource = Platform.OS === 'web' ? require('./map-view.web').ShapeSource : require('./map-view.native').ShapeSource;
-export const SymbolLayer = Platform.OS === 'web' ? require('./map-view.web').SymbolLayer : require('./map-view.native').SymbolLayer;
-export const CircleLayer = Platform.OS === 'web' ? require('./map-view.web').CircleLayer : require('./map-view.native').CircleLayer;
-export const LineLayer = Platform.OS === 'web' ? require('./map-view.web').LineLayer : require('./map-view.native').LineLayer;
-export const FillLayer = Platform.OS === 'web' ? require('./map-view.web').FillLayer : require('./map-view.native').FillLayer;
-export const Images = Platform.OS === 'web' ? require('./map-view.web').Images : require('./map-view.native').Images;
-export const Callout = Platform.OS === 'web' ? require('./map-view.web').Callout : require('./map-view.native').Callout;
+export const { Camera, PointAnnotation, UserLocation, MarkerView, ShapeSource, SymbolLayer, CircleLayer, LineLayer, FillLayer, Images, Callout, StyleURL, UserTrackingMode, setAccessToken } = platformModule;

-// Export style URL constants
-export const StyleURL = Platform.OS === 'web' ? require('./map-view.web').StyleURL : require('./map-view.native').StyleURL;
-
-// Export UserTrackingMode
-export const UserTrackingMode = Platform.OS === 'web' ? require('./map-view.web').UserTrackingMode : require('./map-view.native').UserTrackingMode;
-
-// Export setAccessToken
-export const setAccessToken = Platform.OS === 'web' ? require('./map-view.web').setAccessToken : require('./map-view.native').setAccessToken;
-
 // Default export matching Mapbox structure
 export default MapboxImpl;
src/components/maps/map-view.web.tsx (1)

345-351: Placeholder components use any type.

Per coding guidelines, avoid using any. Consider defining minimal prop interfaces for these placeholder components to maintain type safety.

♻️ Suggested typing
-export const ShapeSource: React.FC<any> = ({ children }) => <>{children}</>;
-export const SymbolLayer: React.FC<any> = () => null;
-export const CircleLayer: React.FC<any> = () => null;
-export const LineLayer: React.FC<any> = () => null;
-export const FillLayer: React.FC<any> = () => null;
-export const Images: React.FC<any> = () => null;
-export const Callout: React.FC<any> = ({ children }) => <>{children}</>;
+interface PlaceholderProps {
+  children?: React.ReactNode;
+  [key: string]: unknown;
+}
+export const ShapeSource: React.FC<PlaceholderProps> = ({ children }) => <>{children}</>;
+export const SymbolLayer: React.FC<PlaceholderProps> = () => null;
+export const CircleLayer: React.FC<PlaceholderProps> = () => null;
+export const LineLayer: React.FC<PlaceholderProps> = () => null;
+export const FillLayer: React.FC<PlaceholderProps> = () => null;
+export const Images: React.FC<PlaceholderProps> = () => null;
+export const Callout: React.FC<PlaceholderProps> = ({ children }) => <>{children}</>;

Comment on lines +37 to +41
5.**Electron Builds**
- Windows: .exe, .msi
- macOS: .dmg, .zip (Universal)
- Linux: .AppImage, .deb, .rpm
- All added to GitHub Release
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Minor inconsistency: Windows Electron build output formats.

Line 38 lists .exe, .msi for Windows, but README.md (line 61) mentions "Portable exe and NSIS installer". NSIS typically produces .exe installers, not .msi. Consider aligning the documentation.

📝 Suggested fix
 5. ✅ **Electron Builds**
-   - Windows: .exe, .msi
+   - Windows: .exe (portable and NSIS installer)
    - macOS: .dmg, .zip (Universal)
    - Linux: .AppImage, .deb, .rpm
📝 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.

Suggested change
5.**Electron Builds**
- Windows: .exe, .msi
- macOS: .dmg, .zip (Universal)
- Linux: .AppImage, .deb, .rpm
- All added to GitHub Release
5.**Electron Builds**
- Windows: .exe (portable and NSIS installer)
- macOS: .dmg, .zip (Universal)
- Linux: .AppImage, .deb, .rpm
- All added to GitHub Release
🤖 Prompt for AI Agents
In `@docs/build-quick-reference.md` around lines 37 - 41, Update the "Electron
Builds" Windows entry to match README wording: replace the ambiguous ".exe,
.msi" with a clear phrasing like "Portable .exe and NSIS installer (.exe)" (or
"Portable .exe, NSIS .exe") in the "Electron Builds" section so it no longer
implies NSIS produces .msi; edit the Windows list item in that section
accordingly.

Comment on lines +919 to +931
private async setupButtonEventMonitoring(device: Device, peripheralInfo: PeripheralInfo): Promise<void> {
try {
const peripheralInfo = await BleManager.getDiscoveredPeripherals();
const deviceInfo = peripheralInfo.find((p) => p.id === device.id);

if (!deviceInfo) {
logger.warn({
message: 'Device not found in discovered peripherals',
context: { deviceId: device.id },
});
return;
}

logger.info({
message: 'Setting up button event monitoring',
context: {
deviceId: device.id,
deviceName: device.name,
availableServices: peripheralInfo?.services?.map((s: any) => s.uuid) || [],
},
});

// Start notifications for known button control characteristics
await this.startNotificationsForButtonControls(device.id);
await this.startNotificationsForButtonControls(device.id, peripheralInfo);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# Find and examine the bluetooth-audio.service.ts file
fd "bluetooth-audio.service.ts" --type f

Repository: Resgrid/Unit

Length of output: 95


🏁 Script executed:

# Check the structure around lines 919-931
cat -n src/services/bluetooth-audio.service.ts | sed -n '915,935p'

Repository: Resgrid/Unit

Length of output: 942


🏁 Script executed:

# Search for PeripheralInfo type definition in the codebase
rg "PeripheralInfo" --type ts --type tsx -A 5 | head -50

Repository: Resgrid/Unit

Length of output: 83


🏁 Script executed:

# Check react-native-ble-manager in package.json or node_modules types
fd "react-native-ble-manager" --type f | head -20

Repository: Resgrid/Unit

Length of output: 93


🏁 Script executed:

# Examine the mock file for type definitions
cat -n __mocks__/react-native-ble-manager.ts

Repository: Resgrid/Unit

Length of output: 3117


🏁 Script executed:

# Search for PeripheralInfo type definition
rg "type PeripheralInfo|interface PeripheralInfo" --type ts -A 10

Repository: Resgrid/Unit

Length of output: 1321


🏁 Script executed:

# Search for the import of PeripheralInfo in bluetooth-audio.service.ts
head -50 src/services/bluetooth-audio.service.ts | grep -n "PeripheralInfo\|import"

Repository: Resgrid/Unit

Length of output: 709


🌐 Web query:

react-native-ble-manager PeripheralInfo type services array definition

💡 Result:

In react-native-ble-manager, PeripheralInfo.services is typed as an optional array of Service objects, where each Service is:

export interface Service {
  uuid: string;
}

export interface PeripheralInfo extends Peripheral {
  serviceUUIDs?: string[];
  characteristics?: Characteristic[];
  services?: Service[];
}

So services is Service[] (objects with a uuid field), not string[]—that’s what serviceUUIDs is for. [1] [2]

Sources:
[1] src/types.ts (v12.4.1) https://app.unpkg.com/react-native-ble-manager@12.4.1/files/src/types.ts
[2] dist/esm/types.d.ts (v12.1.2) https://app.unpkg.com/react-native-ble-manager@12.1.2/files/dist/esm/types.d.ts


Replace any with the proper Service type from react-native-ble-manager.

Line 926 uses (s: any) which violates the coding guideline to avoid using any. Since PeripheralInfo.services is typed as Service[] in the react-native-ble-manager library, the parameter should be explicitly typed as Service instead.

Suggested improvement
-        availableServices: peripheralInfo?.services?.map((s: any) => s.uuid) || [],
+        availableServices: peripheralInfo?.services?.map((s: Service) => s.uuid) || [],

The Service type is exported from react-native-ble-manager and contains the uuid property. Keep the .map() operation as it intentionally extracts UUIDs into a string array for cleaner logging.

📝 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.

Suggested change
private async setupButtonEventMonitoring(device: Device, peripheralInfo: PeripheralInfo): Promise<void> {
try {
const peripheralInfo = await BleManager.getDiscoveredPeripherals();
const deviceInfo = peripheralInfo.find((p) => p.id === device.id);
if (!deviceInfo) {
logger.warn({
message: 'Device not found in discovered peripherals',
context: { deviceId: device.id },
});
return;
}
logger.info({
message: 'Setting up button event monitoring',
context: {
deviceId: device.id,
deviceName: device.name,
availableServices: peripheralInfo?.services?.map((s: any) => s.uuid) || [],
},
});
// Start notifications for known button control characteristics
await this.startNotificationsForButtonControls(device.id);
await this.startNotificationsForButtonControls(device.id, peripheralInfo);
private async setupButtonEventMonitoring(device: Device, peripheralInfo: PeripheralInfo): Promise<void> {
try {
logger.info({
message: 'Setting up button event monitoring',
context: {
deviceId: device.id,
deviceName: device.name,
availableServices: peripheralInfo?.services?.map((s: Service) => s.uuid) || [],
},
});
// Start notifications for known button control characteristics
await this.startNotificationsForButtonControls(device.id, peripheralInfo);
🤖 Prompt for AI Agents
In `@src/services/bluetooth-audio.service.ts` around lines 919 - 931, In
setupButtonEventMonitoring, replace the unsafe any in the
peripheralInfo.services map with the proper Service type from
react-native-ble-manager: import { Service } from 'react-native-ble-manager' and
change the map callback signature from (s: any) => s.uuid to (s: Service) =>
s.uuid; ensure the peripheralInfo.services is treated as Service[] (or cast it
where needed) so the logger's availableServices remains a string[] and
TypeScript compiles without using any.

LOGGING_KEY: string;
APP_KEY: string;
UNIT_MAPBOX_PUBKEY: string;
IS_MOBILE_APP: boolean;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

IS_MOBILE_APP type may not match runtime value.

Environment variables injected via shell scripts are strings. If docker-entrypoint.sh writes IS_MOBILE_APP: "${UNIT_IS_MOBILE_APP:-false}", the runtime value will be the string "false", not the boolean false. This can cause issues like if (window.__ENV__.IS_MOBILE_APP) evaluating to true for the string "false".

Proposed fix: use string type and parse at consumption
-  IS_MOBILE_APP: boolean;
+  IS_MOBILE_APP: string;

Then parse it when consuming: const isMobile = window.__ENV__?.IS_MOBILE_APP === 'true';

📝 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.

Suggested change
IS_MOBILE_APP: boolean;
IS_MOBILE_APP: string;
🤖 Prompt for AI Agents
In `@types/docker-env.d.ts` at line 19, The declared type for IS_MOBILE_APP in
types/docker-env.d.ts is a boolean but runtime env vars are strings; change the
type of IS_MOBILE_APP to string (update the IS_MOBILE_APP declaration) and
ensure callers parse it (e.g., where window.__ENV__ or IS_MOBILE_APP is
consumed, convert with a strict equality check against 'true' or run a small
parser) so runtime values like "false" do not evaluate truthily.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 11

🤖 Fix all issues with AI agents
In @.github/workflows/react-native-cicd.yml:
- Around line 828-834: The artifact download step currently in the matrix can
run before Windows/macOS jobs finish; move the "Download all Electron artifacts"
logic out of the matrix and into a dedicated release job (e.g.,
release-electron) that uses needs: build-electron so it only runs after all
matrix runs complete; in that release job include the checkout step, prepare
release notes step, and then the actions/download-artifact@v4 step (using the
same path/pattern/merge-multiple settings) and guard the job with the same
if-condition used for releases (push to main/master or workflow_dispatch) so
artifact downloads and subsequent release steps always see all built artifacts.
- Around line 836-846: The "Prepare Electron release artifacts" step can
silently produce an empty release-artifacts/ directory because the find ...
-exec cp ... command succeeds even when no files match; update this step to
detect when no Electron artifacts are found before copying (e.g. run find to
count or list matches and fail the step if none are returned) and emit an
explicit error message and non-zero exit to stop the workflow, referencing the
existing find invocation and the release-artifacts directory so you only proceed
to cp/ls when artifacts exist.

In `@package.json`:
- Around line 198-200: The dependency spec for electron-squirrel-startup should
use an exact version like the other Electron deps; update the package.json entry
for "electron-squirrel-startup" to remove the caret and pin it to "1.0.1" so it
matches the exact-version style used by "electron" and "electron-builder".

In `@src/components/maps/map-view.web.tsx`:
- Around line 260-313: Replace deprecated ReactDOM.render and
ReactDOM.unmountComponentAtNode by creating and storing a React root for the
marker container: when mounting (where ReactDOM.render currently is) call
ReactDOM.createRoot(container) and root.render(<>{children}</>), store that root
(e.g., containerRootRef) so it can be used later; on cleanup (where
unmountComponentAtNode is used) call containerRootRef.current?.unmount() instead
of unmountComponentAtNode. Keep the rest of the logic (markerRef, containerRef,
anchor handling, onSelected listener, popup) the same and ensure you only create
the root when children exist and clear containerRootRef after unmount.

In `@src/lib/platform.ts`:
- Around line 12-15: Remove the unnecessary "as any" casts and fix Prettier
formatting: update the isElectron export to use the augmented Window type
directly (no cast) and ensure its expression is formatted correctly, and
likewise replace (window as any).electronAPI?.platform with
window.electronAPI?.platform in the platformName function; keep the same logic
but use the typed window.electronAPI access to satisfy the no-any rule and run
Prettier to normalize spacing/line breaks.

In `@src/services/push-notification.electron.ts`:
- Around line 10-19: Update the declaration of
Window.electronAPI.onNotificationClicked to match the implemented cleanup
pattern: change its type from returning void to returning a cleanup function,
i.e. onNotificationClicked: (callback: (data: any) => void) => () => void; in
the global Window.electronAPI interface so the callback signature and return
type align with the implementation that captures and calls the returned cleanup
function.

In `@src/services/push-notification.web.ts`:
- Around line 171-189: The unsubscribe() method returns early when
this.pushSubscription is null but must still locate any persisted browser
subscription and clear it; update unsubscribe() so that if this.pushSubscription
is falsy it attempts to retrieve the active subscription via the Service Worker
registration (use registration.pushManager.getSubscription() or equivalent) and
assign it to this.pushSubscription before proceeding, then call
this.pushSubscription.unsubscribe(), set this.pushSubscription = null, and clear
any persisted client-side record of the subscription (localStorage/IndexedDB
entry if used). Ensure errors from getSubscription() and unsubscribe() are
caught and logged via logger.error and the method still returns a boolean
indicating success/failure.
- Around line 79-97: The handler handleServiceWorkerMessage currently assumes
event.data.data exists and then accesses data.eventCode, which can throw if the
SW sends a NOTIFICATION_CLICK without a payload; guard against undefined by
reading const data = event.data?.data ?? undefined (or return early if missing)
and only proceed to log and call
usePushNotificationModalStore.getState().showNotificationModal when data is an
object with the expected fields (check data && data.eventCode), ensuring you
don't access data.eventCode, data.title, data.body, or data.message when data is
undefined.
- Around line 250-251: The static isSupported() method in the PushNotification
class (or module) directly accesses navigator and window which breaks in
SSR/Node; update isSupported() to first guard that typeof window !== 'undefined'
and typeof navigator !== 'undefined' before checking 'serviceWorker' in
navigator and 'PushManager'/'Notification' in window (or reuse the existing
platform guard helper used elsewhere) so the method returns false in non‑browser
environments and never throws.
- Around line 65-74: sendClientReadyHandshake currently silently skips sending
CLIENT_READY when navigator.serviceWorker.controller is null (first-install
case); update sendClientReadyHandshake to: if controller exists postMessage and
log; else if this.registration?.active exists postMessage to
this.registration.active and log; additionally add a one-time 'controllerchange'
listener that when navigator.serviceWorker.controller becomes available posts
CLIENT_READY and logs then removes the listener to avoid duplicate sends;
reference the sendClientReadyHandshake method,
navigator.serviceWorker.controller, this.registration?.active and the
'controllerchange' event in your changes.

In `@src/stores/app/livekit-store.ts`:
- Line 1: The build fails because the code uses an invalid enum member
AndroidForegroundServiceType.MICROPHONE; update the usage to the correct Notifee
enum name AndroidForegroundServiceType.FOREGROUND_SERVICE_TYPE_MICROPHONE (or,
if the original numeric intent was the media playback flag, use
AndroidForegroundServiceType.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK) wherever
AndroidForegroundServiceType.MICROPHONE is referenced (e.g., in the livekit
foreground service setup code) so TypeScript compiles cleanly.
♻️ Duplicate comments (2)
.github/workflows/react-native-cicd.yml (1)

609-617: Update actions/cache to v4 for runner compatibility.

Same issue as flagged for other jobs. The actions/cache@v3 action should be updated to v4.

electron/main.js (1)

5-8: Verify electron-squirrel-startup is in package.json dependencies.

As noted in past reviews, require('electron-squirrel-startup') will fail at runtime on Windows if the package is not installed. Ensure it's added to dependencies if shipping with Squirrel.Windows installers.

#!/bin/bash
# Check if electron-squirrel-startup is in package.json
rg "electron-squirrel-startup" package.json
🧹 Nitpick comments (16)
src/lib/native-module-shims.web.ts (1)

182-186: Consider using a type for the permission status.

The hardcoded 'granted' string works for a shim, but for better type safety and alignment with Expo's actual API, consider using a more precise type.

Optional: Add type annotation for permission response
+type PermissionResponse = {
+  granted: boolean;
+  status: 'granted' | 'denied' | 'undetermined';
+};
+
 // Expo modules that may have issues on web
 export const expoAudioShim = {
-  getRecordingPermissionsAsync: async () => ({ granted: true, status: 'granted' }),
-  requestRecordingPermissionsAsync: async () => ({ granted: true, status: 'granted' }),
+  getRecordingPermissionsAsync: async (): Promise<PermissionResponse> => ({ granted: true, status: 'granted' }),
+  requestRecordingPermissionsAsync: async (): Promise<PermissionResponse> => ({ granted: true, status: 'granted' }),
 };
.github/workflows/react-native-cicd.yml (3)

302-423: Significant code duplication with build-electron job.

The extract_release_notes function and surrounding logic (lines 302-423) is nearly identical to lines 705-826 in build-electron. This violates DRY and creates maintenance burden.

Consider extracting this into:

  1. A composite action in .github/actions/prepare-release-notes/action.yml
  2. Or a shared script in the repository that both jobs call
♻️ Example composite action structure
# .github/actions/prepare-release-notes/action.yml
name: 'Prepare Release Notes'
description: 'Extract and prepare release notes from PR body or commits'
inputs:
  version:
    description: 'Version string for the release'
    required: true
outputs:
  notes-file:
    description: 'Path to the generated release notes file'
    value: ${{ steps.prepare.outputs.notes-file }}
runs:
  using: 'composite'
  steps:
    - id: prepare
      shell: bash
      run: |
        # ... shared release notes logic here ...
      env:
        GH_TOKEN: ${{ github.token }}

Then use in both jobs:

- uses: ./.github/actions/prepare-release-notes
  with:
    version: '7.${{ github.run_number }}'

498-506: Update actions/cache to v4.

The static analysis indicates actions/cache@v3 may have runner compatibility issues. This also applies to lines 104, 147, 200, and 610 in other jobs.

♻️ Proposed fix
      - name: 📦 Setup yarn cache
-       uses: actions/cache@v3
+       uses: actions/cache@v4
        with:
          path: |

537-544: Quote secret interpolations to prevent word splitting.

The static analysis flags SC2086 at the secret checks. While the values are unlikely to contain spaces, quoting is a defensive practice.

♻️ Proposed fix
      - name: 🔍 Check Docker Hub credentials availability
        id: docker-creds
        run: |
-         if [[ -n "${{ secrets.DOCKER_USERNAME }}" && -n "${{ secrets.DOCKER_PASSWORD }}" ]]; then
+         if [[ -n "${{ secrets.DOCKER_USERNAME || '' }}" && -n "${{ secrets.DOCKER_PASSWORD || '' }}" ]]; then
            echo "available=true" >> $GITHUB_OUTPUT
          else
            echo "available=false" >> $GITHUB_OUTPUT
          fi

Alternatively, use explicit quoting in the shell:

        run: |
-         if [[ -n "${{ secrets.DOCKER_USERNAME }}" && -n "${{ secrets.DOCKER_PASSWORD }}" ]]; then
+         USERNAME="${{ secrets.DOCKER_USERNAME }}"
+         PASSWORD="${{ secrets.DOCKER_PASSWORD }}"
+         if [[ -n "$USERNAME" && -n "$PASSWORD" ]]; then
            echo "available=true" >> $GITHUB_OUTPUT
package.json (1)

109-109: Move @types/mapbox-gl to devDependencies.

Type definition packages (@types/*) are only needed at compile time and should typically be in devDependencies rather than dependencies. This avoids unnecessarily including them in production builds.

♻️ Suggested change

Move this entry from dependencies to devDependencies:

  "dependencies": {
    ...
-   "@types/mapbox-gl": "3.4.1",
    ...
  },
  "devDependencies": {
    ...
+   "@types/mapbox-gl": "3.4.1",
    ...
  }
src/components/maps/map-view.web.tsx (5)

59-59: Avoid using any for the ref type.

Per coding guidelines, strive for precise types instead of any. Define a proper interface for the imperative handle.

♻️ Suggested fix
+// Define the imperative handle interface
+interface MapViewRef {
+  getMap: () => mapboxgl.Map | null;
+}

-export const MapView = forwardRef<any, MapViewProps>(
+export const MapView = forwardRef<MapViewRef, MapViewProps>(

167-187: Avoid any types in Camera component.

The ref type and flyTo options parameter use any, which violates coding guidelines.

♻️ Suggested types
+interface CameraRef {
+  setCamera: (options: {
+    centerCoordinate?: [number, number];
+    zoomLevel?: number;
+    heading?: number;
+    pitch?: number;
+    animationDuration?: number;
+  }) => void;
+  flyTo: (options: mapboxgl.FlyToOptions) => void;
+}

-export const Camera = forwardRef<any, CameraProps>(({ centerCoordinate, zoomLevel, heading, pitch, animationDuration = 1000, followUserLocation, followZoomLevel }, ref) => {
+export const Camera = forwardRef<CameraRef, CameraProps>(({ centerCoordinate, zoomLevel, heading, pitch, animationDuration = 1000, followUserLocation, followZoomLevel }, ref) => {
-    flyTo: (options: any) => {
+    flyTo: (options: mapboxgl.FlyToOptions) => {

218-221: Arbitrary setTimeout delay is fragile.

Using a fixed 100ms timeout to trigger geolocation is a race condition waiting to happen. The control may not be ready, or it may be ready sooner.

♻️ Consider using the geolocate control's event
-    // Trigger tracking after control is added
-    setTimeout(() => {
-      geolocateControl.current?.trigger();
-    }, 100);
+    // Trigger tracking once the map is idle (control is ready)
+    const triggerOnIdle = () => {
+      geolocateControl.current?.trigger();
+    };
+    
+    if (map.loaded()) {
+      map.once('idle', triggerOnIdle);
+    } else {
+      map.once('load', () => {
+        map.once('idle', triggerOnIdle);
+      });
+    }

349-366: Confusing control flow with multiple conditional returns.

The nested conditional returns make cleanup logic hard to follow. Both branches remove the control, but via different return statements.

♻️ Cleaner structure
+    let onMapLoad: (() => void) | null = null;
+
     // Auto-trigger to show user location
     if (map.loaded()) {
       geolocate.trigger();
     } else {
-      const onMapLoad = () => {
+      onMapLoad = () => {
         geolocate.trigger();
       };
       map.on('load', onMapLoad);
-
-      return () => {
-        map.off('load', onMapLoad);
-        map.removeControl(geolocate);
-      };
     }

     return () => {
+      if (onMapLoad) {
+        map.off('load', onMapLoad);
+      }
       map.removeControl(geolocate);
     };

377-381: Consider more robust ID generation for MarkerView.

Using coordinate.join('-') creates IDs like "-98.5795--39.8283" for negative coordinates, which could be confusing or cause issues if used as element IDs.

♻️ Suggested improvement
-    <PointAnnotation id={`marker-${coordinate.join('-')}`} coordinate={coordinate}>
+    <PointAnnotation id={`marker-${coordinate[0]}_${coordinate[1]}`} coordinate={coordinate}>

Or use a more explicit format: marker-lng${coordinate[0]}-lat${coordinate[1]}

src/services/push-notification.electron.ts (1)

68-83: Consider validating notification data structure before accessing store.

The handleNotificationClick method accesses data?.eventCode but then passes potentially undefined values to the store. Consider adding validation or defaults.

♻️ Suggested improvement
  private handleNotificationClick = (data: any): void => {
    logger.info({
      message: 'Electron notification clicked',
      context: { data },
    });

    // Show the notification modal
-   if (data?.eventCode) {
+   if (data?.eventCode && data?.title) {
      usePushNotificationModalStore.getState().showNotificationModal({
        eventCode: data.eventCode,
        title: data.title,
-       body: data.body || data.message,
+       body: data.body || data.message || '',
        data,
      });
    }
  };
public/service-worker.js (2)

11-12: Consider memory management for pendingNotifications Map.

The pendingNotifications Map stores notification data indefinitely. If a client opens but never sends CLIENT_READY, entries will leak. Consider adding a timeout-based cleanup or maximum size limit.

♻️ Optional: Add cleanup mechanism
// Store pending notification data with timestamp for cleanup
const pendingNotifications = new Map();
const PENDING_TIMEOUT_MS = 30000; // 30 seconds

// Clean up stale pending notifications periodically
setInterval(() => {
  const now = Date.now();
  for (const [clientId, entry] of pendingNotifications) {
    if (now - entry.timestamp > PENDING_TIMEOUT_MS) {
      pendingNotifications.delete(clientId);
      console.log('[Service Worker] Cleaned up stale pending notification:', clientId);
    }
  }
}, 60000);

// When storing:
pendingNotifications.set(client.id, { data, timestamp: Date.now() });

15-28: Avoid logging potentially sensitive notification data in production.

The console.log on line 16 logs the entire push event which may contain sensitive information. Consider removing or reducing verbosity for production.

♻️ Suggested change
 self.addEventListener('push', function (event) {
-  console.log('[Service Worker] Push received:', event);
+  console.log('[Service Worker] Push received');
src/services/push-notification.web.ts (1)

202-231: Replace any with a typed notification payload.
You already expect eventCode, title, and body; using a typed payload keeps this safe (prefer a shared PushNotificationData if one exists). As per coding guidelines, avoid any.

♻️ Suggested refactor
 import { logger } from '@/lib/logging';
 import { usePushNotificationModalStore } from '@/stores/push-notification/store';

+interface PushNotificationData {
+  eventCode?: string;
+  title?: string;
+  body?: string;
+  message?: string;
+  [key: string]: unknown;
+}
+
@@
-  async showLocalNotification(title: string, body: string, data?: any): Promise<void> {
+  async showLocalNotification(title: string, body: string, data?: PushNotificationData): Promise<void> {
src/lib/platform.ts (2)

29-54: Avoid as any when reading electronAPI in platformName.
Window is augmented below, so the cast isn’t necessary. As per coding guidelines, avoid any.

♻️ Suggested fix
-    const electronPlatform = (window as any).electronAPI?.platform;
+    const electronPlatform = window.electronAPI?.platform;

57-68: Prefer a concrete or unknown payload type instead of any.
This keeps Electron interop type-safe across the app. As per coding guidelines, avoid any.

♻️ Suggested fix
-      showNotification: (options: { title: string; body: string; data: any }) => Promise<boolean>;
-      onNotificationClicked: (callback: (data: any) => void) => void;
+      showNotification: (options: { title: string; body: string; data: unknown }) => Promise<boolean>;
+      onNotificationClicked: (callback: (data: unknown) => void) => void;

Comment on lines +828 to +834
- name: � Download all Electron artifacts
if: ${{ matrix.os == 'ubuntu-latest' }}
uses: actions/download-artifact@v4
with:
path: electron-artifacts/
pattern: electron-*
merge-multiple: false
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Artifact download may fail if Windows/macOS matrix jobs haven't completed.

The download-artifact@v4 step attempts to download artifacts from all matrix jobs (electron-windows, electron-macos, electron-linux), but matrix jobs run in parallel. When the ubuntu-latest runner reaches this step, the Windows and macOS builds may still be in progress.

This will cause the release to be created with incomplete artifacts, or the download step to fail.

🐛 Recommended fix: Split into a separate release job

Create a dedicated release job that depends on build-electron completing all matrix runs:

  release-electron:
    needs: build-electron
    if: (github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master')) || github.event_name == 'workflow_dispatch'
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - name: 🏗 Checkout repository
        uses: actions/checkout@v4
        
      - name: 📋 Prepare Release Notes file
        run: |
          # ... release notes logic ...
        env:
          GH_TOKEN: ${{ github.token }}
          
      - name: 📦 Download all Electron artifacts
        uses: actions/download-artifact@v4
        with:
          path: electron-artifacts/
          pattern: electron-*
          merge-multiple: false
          
      # ... rest of release steps ...

This ensures all matrix jobs complete before attempting to download and release artifacts.

🤖 Prompt for AI Agents
In @.github/workflows/react-native-cicd.yml around lines 828 - 834, The artifact
download step currently in the matrix can run before Windows/macOS jobs finish;
move the "Download all Electron artifacts" logic out of the matrix and into a
dedicated release job (e.g., release-electron) that uses needs: build-electron
so it only runs after all matrix runs complete; in that release job include the
checkout step, prepare release notes step, and then the
actions/download-artifact@v4 step (using the same path/pattern/merge-multiple
settings) and guard the job with the same if-condition used for releases (push
to main/master or workflow_dispatch) so artifact downloads and subsequent
release steps always see all built artifacts.

Comment on lines +836 to +846
- name: 📦 Prepare Electron release artifacts
if: ${{ matrix.os == 'ubuntu-latest' }}
run: |
# Create release-artifacts directory
mkdir -p release-artifacts
# Copy all Electron artifacts to a flat directory for release
find electron-artifacts/ -type f \( -name "*.exe" -o -name "*.msi" -o -name "*.dmg" -o -name "*.zip" -o -name "*.AppImage" -o -name "*.deb" -o -name "*.rpm" \) -exec cp {} release-artifacts/ \;
echo "Release artifacts prepared:"
ls -lh release-artifacts/
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add error handling for empty artifact collection.

The find command with -exec cp will succeed silently even if no matching files are found, resulting in an empty release-artifacts/ directory and a release with no downloadable files.

🐛 Proposed fix
      - name: 📦 Prepare Electron release artifacts
        if: ${{ matrix.os == 'ubuntu-latest' }}
        run: |
          # Create release-artifacts directory
          mkdir -p release-artifacts
          
          # Copy all Electron artifacts to a flat directory for release
          find electron-artifacts/ -type f \( -name "*.exe" -o -name "*.msi" -o -name "*.dmg" -o -name "*.zip" -o -name "*.AppImage" -o -name "*.deb" -o -name "*.rpm" \) -exec cp {} release-artifacts/ \;
          
+         # Verify artifacts were collected
+         ARTIFACT_COUNT=$(find release-artifacts/ -type f | wc -l)
+         if [ "$ARTIFACT_COUNT" -eq 0 ]; then
+           echo "Error: No release artifacts found" >&2
+           exit 1
+         fi
+         
          echo "Release artifacts prepared:"
          ls -lh release-artifacts/
📝 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.

Suggested change
- name: 📦 Prepare Electron release artifacts
if: ${{ matrix.os == 'ubuntu-latest' }}
run: |
# Create release-artifacts directory
mkdir -p release-artifacts
# Copy all Electron artifacts to a flat directory for release
find electron-artifacts/ -type f \( -name "*.exe" -o -name "*.msi" -o -name "*.dmg" -o -name "*.zip" -o -name "*.AppImage" -o -name "*.deb" -o -name "*.rpm" \) -exec cp {} release-artifacts/ \;
echo "Release artifacts prepared:"
ls -lh release-artifacts/
- name: 📦 Prepare Electron release artifacts
if: ${{ matrix.os == 'ubuntu-latest' }}
run: |
# Create release-artifacts directory
mkdir -p release-artifacts
# Copy all Electron artifacts to a flat directory for release
find electron-artifacts/ -type f \( -name "*.exe" -o -name "*.msi" -o -name "*.dmg" -o -name "*.zip" -o -name "*.AppImage" -o -name "*.deb" -o -name "*.rpm" \) -exec cp {} release-artifacts/ \;
# Verify artifacts were collected
ARTIFACT_COUNT=$(find release-artifacts/ -type f | wc -l)
if [ "$ARTIFACT_COUNT" -eq 0 ]; then
echo "Error: No release artifacts found" >&2
exit 1
fi
echo "Release artifacts prepared:"
ls -lh release-artifacts/
🤖 Prompt for AI Agents
In @.github/workflows/react-native-cicd.yml around lines 836 - 846, The "Prepare
Electron release artifacts" step can silently produce an empty
release-artifacts/ directory because the find ... -exec cp ... command succeeds
even when no files match; update this step to detect when no Electron artifacts
are found before copying (e.g. run find to count or list matches and fail the
step if none are returned) and emit an explicit error message and non-zero exit
to stop the workflow, referencing the existing find invocation and the
release-artifacts directory so you only proceed to cp/ls when artifacts exist.

Comment on lines +198 to +200
"electron": "40.0.0",
"electron-builder": "26.4.0",
"electron-squirrel-startup": "^1.0.1",
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

Electron version 40.0.0 latest release

💡 Result:

As of January 25, 2026, the latest stable Electron major release is 40.0.0. It was announced as available on January 13, 2026 and is installable via npm install electron@latest. [1][2]

Release automation for the v40.0.0 stable branch completed around January 15, 2026, and Electron’s lifecycle trackers list 40.0.0 as the latest 40.x release (published Jan 16, 2026). [3][4]

Install: npm i electron@latest (or pin: npm i electron@40.0.0). [1]

Sources: [1] [2] [3] [4]


Align version specifier for electron-squirrel-startup.

electron-squirrel-startup uses caret (^1.0.1) while electron and electron-builder use exact versions. For consistency with the other newly added Electron dependencies, consider using an exact version specifier.

🤖 Prompt for AI Agents
In `@package.json` around lines 198 - 200, The dependency spec for
electron-squirrel-startup should use an exact version like the other Electron
deps; update the package.json entry for "electron-squirrel-startup" to remove
the caret and pin it to "1.0.1" so it matches the exact-version style used by
"electron" and "electron-builder".

Comment on lines +260 to +313
// Render React children into the container using createPortal
if (children) {
ReactDOM.render(<>{children}</>, container);
}

// Determine marker options based on anchor prop
const markerOptions: mapboxgl.MarkerOptions = {
element: container,
};

// Handle anchor prop - if it's a string, use it as mapbox anchor
if (typeof anchor === 'string') {
markerOptions.anchor = anchor as mapboxgl.Anchor;
}

markerRef.current = new mapboxgl.Marker(markerOptions)
.setLngLat(coordinate)
.addTo(map);

// If anchor is an {x, y} object, convert to pixel offset
if (typeof anchor === 'object' && anchor !== null && 'x' in anchor && 'y' in anchor) {
// Calculate offset based on container size
// Mapbox expects offset in pixels, anchor is typically 0-1 range
// Convert anchor position to offset (center is anchor {0.5, 0.5})
const rect = container.getBoundingClientRect();
const xOffset = (anchor.x - 0.5) * rect.width;
const yOffset = (anchor.y - 0.5) * rect.height;
markerRef.current.setOffset([xOffset, yOffset]);
}

if (title) {
markerRef.current.setPopup(new mapboxgl.Popup().setText(title));
}

// Attach click listener to container
if (onSelected && containerRef.current) {
containerRef.current.addEventListener('click', onSelected);
}

return () => {
// Clean up click listener
if (onSelected && containerRef.current) {
containerRef.current.removeEventListener('click', onSelected);
}

// Unmount React children from the portal
if (children && containerRef.current) {
ReactDOM.unmountComponentAtNode(containerRef.current);
}

// Remove marker from map
markerRef.current?.remove();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Replace deprecated ReactDOM.render and unmountComponentAtNode with React 18+ createRoot API.

The pipeline is warning that ReactDOM.render (line 262) and ReactDOM.unmountComponentAtNode (line 307) are deprecated since React 18. Given you're using React 19, these must be updated to use the createRoot API.

🔧 Suggested fix using createRoot
 import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react';
-import ReactDOM from 'react-dom';
+import { createRoot, Root } from 'react-dom/client';

 // ... in PointAnnotation component:

 export const PointAnnotation: React.FC<PointAnnotationProps> = ({ id, coordinate, title, children, anchor = { x: 0.5, y: 0.5 }, onSelected }) => {
   const map = React.useContext(MapContext);
   const markerRef = useRef<mapboxgl.Marker | null>(null);
   const containerRef = useRef<HTMLDivElement | null>(null);
+  const rootRef = useRef<Root | null>(null);

   useEffect(() => {
     if (!map || !coordinate) return;

     // Create a container for React children
     const container = document.createElement('div');
     container.style.cursor = 'pointer';
     containerRef.current = container;

     // Render React children into the container using createRoot
     if (children) {
-      ReactDOM.render(<>{children}</>, container);
+      rootRef.current = createRoot(container);
+      rootRef.current.render(<>{children}</>);
     }

     // ... marker creation code ...

     return () => {
       // ... click listener cleanup ...

       // Unmount React children
-      if (children && containerRef.current) {
-        ReactDOM.unmountComponentAtNode(containerRef.current);
+      if (rootRef.current) {
+        rootRef.current.unmount();
+        rootRef.current = null;
       }

       // Remove marker from map
       markerRef.current?.remove();
     };
   }, [map, coordinate, id, children, anchor, onSelected, title]);
🧰 Tools
🪛 GitHub Actions: React Native CI/CD

[warning] 262-262: ReactDOM.render is deprecated since React 18.0.0, use createRoot instead.


[warning] 275-275: prettier/prettier: Replace long chain with .setLngLat(coordinate)


307-307: ReactDOM.unmountComponentAtNode is deprecated since React 18.0.0, use root.unmount instead

🪛 GitHub Check: test

[warning] 307-307:
ReactDOM.unmountComponentAtNode is deprecated since React 18.0.0, use root.unmount instead, see https://reactjs.org/link/switch-to-createroot


[warning] 275-275:
Replace ⏎······.setLngLat(coordinate)⏎······ with .setLngLat(coordinate)


[warning] 262-262:
ReactDOM.render is deprecated since React 18.0.0, use createRoot instead, see https://reactjs.org/link/switch-to-createroot

🤖 Prompt for AI Agents
In `@src/components/maps/map-view.web.tsx` around lines 260 - 313, Replace
deprecated ReactDOM.render and ReactDOM.unmountComponentAtNode by creating and
storing a React root for the marker container: when mounting (where
ReactDOM.render currently is) call ReactDOM.createRoot(container) and
root.render(<>{children}</>), store that root (e.g., containerRootRef) so it can
be used later; on cleanup (where unmountComponentAtNode is used) call
containerRootRef.current?.unmount() instead of unmountComponentAtNode. Keep the
rest of the logic (markerRef, containerRef, anchor handling, onSelected
listener, popup) the same and ensure you only create the root when children
exist and clear containerRootRef after unmount.

Comment on lines +12 to +15
export const isElectron =
typeof window !== 'undefined' &&
(!!(window as any).electronAPI || window.process?.type === 'renderer');

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's look at the actual file content
cat -n src/lib/platform.ts

Repository: Resgrid/Unit

Length of output: 2552


🏁 Script executed:

# Search for Window interface augmentations and type definitions
fd -e "d.ts" -e "ts" -e "tsx" | xargs rg -l "Window\s*\{|interface\s+Window|declare\s+global" | head -20

Repository: Resgrid/Unit

Length of output: 164


🏁 Script executed:

# Look for electronAPI references in the codebase
rg "electronAPI" --type ts --type tsx -B 2 -A 2

Repository: Resgrid/Unit

Length of output: 83


🏁 Script executed:

# Check Prettier configuration
fd -e ".prettierrc" -e "prettier.config" -e "prettierrc.json" -e "prettierrc.js"

Repository: Resgrid/Unit

Length of output: 38


Remove as any cast and fix Prettier formatting in isElectron.
The Window interface is properly augmented for electronAPI, so the as any cast is unnecessary and violates the no-any guideline. Update to use the augmented type directly. Note: there's a similar as any cast at line 32 in platformName() that should also be removed.

♻️ Suggested fix
-export const isElectron = 
-  typeof window !== 'undefined' && 
+export const isElectron =
+  typeof window !== 'undefined' &&
   (!!(window.electronAPI) || window.process?.type === 'renderer');

Additionally, update line 32 from (window as any).electronAPI?.platform to window.electronAPI?.platform to maintain consistency.

🧰 Tools
🪛 GitHub Actions: React Native CI/CD

[warning] 12-12: prettier/prettier: Replace newline+typeof window !== 'undefined' with typeof window !== 'undefined' &&

🪛 GitHub Check: test

[warning] 12-12:
Replace ⏎··typeof·window·!==·'undefined'·&&·⏎· with typeof·window·!==·'undefined'·&&

🤖 Prompt for AI Agents
In `@src/lib/platform.ts` around lines 12 - 15, Remove the unnecessary "as any"
casts and fix Prettier formatting: update the isElectron export to use the
augmented Window type directly (no cast) and ensure its expression is formatted
correctly, and likewise replace (window as any).electronAPI?.platform with
window.electronAPI?.platform in the platformName function; keep the same logic
but use the typed window.electronAPI access to satisfy the no-any rule and run
Prettier to normalize spacing/line breaks.

Comment on lines +65 to +74
private sendClientReadyHandshake(): void {
if (navigator.serviceWorker.controller) {
navigator.serviceWorker.controller.postMessage({
type: 'CLIENT_READY',
});
logger.info({
message: 'Sent CLIENT_READY handshake to service worker',
});
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's confirm the file exists and view the relevant section
cat -n src/services/push-notification.web.ts | head -100

Repository: Resgrid/Unit

Length of output: 3774


🌐 Web query:

service worker controller null after ready resolves first install

💡 Result:

On the very first install/load, it’s normal for navigator.serviceWorker.ready to resolve while navigator.serviceWorker.controller is still null.

  • ready only waits until there is an active service worker for the page’s registration; it does not guarantee the current page is already controlled by it. [1]
  • controller is only set when the current page is actually controlled by an active/activating worker; otherwise it’s null (also null on a force refresh). [2][3]

What to do

  1. Most common/expected: reload or navigate once after install (control is gained on the next navigation).

  2. Take control immediately (recommended for first install UX):

  • In the SW, claim clients on activate (and often also skip waiting):
// sw.js
self.addEventListener("install", () => self.skipWaiting());
self.addEventListener("activate", (event) => event.waitUntil(self.clients.claim()));

This is the standard fix when you want controller to become available without requiring a manual refresh. [4][5]

  1. In the page, wait for control explicitly:
    Listen for controllerchange and proceed when it fires (or when controller is already non-null). [4]

Also verify:

  • the SW scope actually includes the page URL (otherwise controller will stay null). [4]
  • you’re not testing during a hard/force refresh, which can keep controller null for that load. [2][3]

Sources:
[1] MDN: ServiceWorkerContainer.ready [2] MDN: ServiceWorkerContainer.controller [3] W3C Service Workers spec note on force refresh and controller [4] Stack Overflow: controller null until refresh / controllerchange / scope [5] Stack Overflow: clients.claim() making controller available


Guarantee CLIENT_READY delivery by handling the first-install case where controller is null.

After navigator.serviceWorker.ready resolves, the service worker is active but the current page may not yet be controlled by it (especially on first install). This causes navigator.serviceWorker.controller to remain null, and the handshake is skipped silently.

Use this.registration?.active as a fallback to reach the active worker immediately, and listen for controllerchange to handle the case when the page gains control after initialization:

Suggested fix
  private sendClientReadyHandshake(): void {
-    if (navigator.serviceWorker.controller) {
-      navigator.serviceWorker.controller.postMessage({
+    const controller = navigator.serviceWorker.controller ?? this.registration?.active;
+    if (controller) {
+      controller.postMessage({
         type: 'CLIENT_READY',
       });
       logger.info({
         message: 'Sent CLIENT_READY handshake to service worker',
       });
+      return;
     }
+
+    navigator.serviceWorker.addEventListener(
+      'controllerchange',
+      () => {
+        if (navigator.serviceWorker.controller) {
+          navigator.serviceWorker.controller.postMessage({ type: 'CLIENT_READY' });
+          logger.info({ message: 'Sent CLIENT_READY handshake to service worker' });
+        }
+      },
+      { once: true }
+    );
   }
🤖 Prompt for AI Agents
In `@src/services/push-notification.web.ts` around lines 65 - 74,
sendClientReadyHandshake currently silently skips sending CLIENT_READY when
navigator.serviceWorker.controller is null (first-install case); update
sendClientReadyHandshake to: if controller exists postMessage and log; else if
this.registration?.active exists postMessage to this.registration.active and
log; additionally add a one-time 'controllerchange' listener that when
navigator.serviceWorker.controller becomes available posts CLIENT_READY and logs
then removes the listener to avoid duplicate sends; reference the
sendClientReadyHandshake method, navigator.serviceWorker.controller,
this.registration?.active and the 'controllerchange' event in your changes.

Comment on lines +79 to +97
private handleServiceWorkerMessage = (event: MessageEvent): void => {
if (event.data?.type === 'NOTIFICATION_CLICK') {
const data = event.data.data;
logger.info({
message: 'Notification clicked from service worker',
context: { data },
});

// Show the notification modal
if (data.eventCode) {
usePushNotificationModalStore.getState().showNotificationModal({
eventCode: data.eventCode,
title: data.title,
body: data.body || data.message,
data,
});
}
}
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, check if the file exists and view the relevant lines
if [ -f "src/services/push-notification.web.ts" ]; then
  echo "=== File found. Showing lines 75-100 for context ==="
  head -100 "src/services/push-notification.web.ts" | tail -30
else
  echo "File not found at src/services/push-notification.web.ts"
  # Try to locate the file
  find . -name "push-notification*" -type f 2>/dev/null | head -20
fi

Repository: Resgrid/Unit

Length of output: 893


🏁 Script executed:

# Check the entire file for type definitions and context
wc -l "src/services/push-notification.web.ts"

# Look for any MessageEvent type definitions or interfaces in the file
rg -A 5 -B 5 "MessageEvent|event\.data" "src/services/push-notification.web.ts" | head -80

# Check if there are type definitions for the message payload
rg -B 10 -A 10 "NOTIFICATION_CLICK" "src/services/push-notification.web.ts"

Repository: Resgrid/Unit

Length of output: 1058


🏁 Script executed:

# View the full file to understand context and check for type definitions
head -100 "src/services/push-notification.web.ts"

# Check if there's a type guard or interface for the event payload elsewhere
rg "interface.*Message|type.*Message" "src/services/push-notification.web.ts"

# Look for any assertion or type narrowing
ast-grep --pattern 'event.data.data'

Repository: Resgrid/Unit

Length of output: 3151


Guard against undefined event.data.data before accessing properties.
The optional chaining on event.data?.type does not protect event.data.data from being undefined. If the service worker sends a NOTIFICATION_CLICK message without a data payload, accessing data.eventCode will throw a TypeError.

Suggested fix
-      if (data.eventCode) {
+      if (data && data.eventCode) {
         usePushNotificationModalStore.getState().showNotificationModal({
           eventCode: data.eventCode,
           title: data.title,
           body: data.body || data.message,
           data,
         });
       }
🤖 Prompt for AI Agents
In `@src/services/push-notification.web.ts` around lines 79 - 97, The handler
handleServiceWorkerMessage currently assumes event.data.data exists and then
accesses data.eventCode, which can throw if the SW sends a NOTIFICATION_CLICK
without a payload; guard against undefined by reading const data =
event.data?.data ?? undefined (or return early if missing) and only proceed to
log and call usePushNotificationModalStore.getState().showNotificationModal when
data is an object with the expected fields (check data && data.eventCode),
ensuring you don't access data.eventCode, data.title, data.body, or data.message
when data is undefined.

Comment on lines +171 to +189
async unsubscribe(): Promise<boolean> {
if (!this.pushSubscription) {
return true;
}

try {
await this.pushSubscription.unsubscribe();
this.pushSubscription = null;
logger.info({
message: 'Successfully unsubscribed from push notifications',
});
return true;
} catch (error) {
logger.error({
message: 'Failed to unsubscribe from push notifications',
context: { error },
});
return false;
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -name "push-notification.web.ts" -type f

Repository: Resgrid/Unit

Length of output: 95


🏁 Script executed:

wc -l src/services/push-notification.web.ts

Repository: Resgrid/Unit

Length of output: 97


🏁 Script executed:

cat -n src/services/push-notification.web.ts

Repository: Resgrid/Unit

Length of output: 8887


Unsubscribe must retrieve and clear persisted browser subscriptions on every call.

After a page reload, this.pushSubscription becomes null but the browser's push subscription persists. The current code returns true without actually unsubscribing, leaving users subscribed despite successful method return.

Suggested fix
  async unsubscribe(): Promise<boolean> {
-    if (!this.pushSubscription) {
-      return true;
-    }
-
     try {
+      if (!this.registration) {
+        await this.initialize();
+      }
+      if (!this.pushSubscription && this.registration) {
+        this.pushSubscription = await this.registration.pushManager.getSubscription();
+      }
+      if (!this.pushSubscription) {
+        return true;
+      }
       await this.pushSubscription.unsubscribe();
       this.pushSubscription = null;
       logger.info({
         message: 'Successfully unsubscribed from push notifications',
       });
       return true;
📝 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.

Suggested change
async unsubscribe(): Promise<boolean> {
if (!this.pushSubscription) {
return true;
}
try {
await this.pushSubscription.unsubscribe();
this.pushSubscription = null;
logger.info({
message: 'Successfully unsubscribed from push notifications',
});
return true;
} catch (error) {
logger.error({
message: 'Failed to unsubscribe from push notifications',
context: { error },
});
return false;
}
async unsubscribe(): Promise<boolean> {
try {
if (!this.registration) {
await this.initialize();
}
if (!this.pushSubscription && this.registration) {
this.pushSubscription = await this.registration.pushManager.getSubscription();
}
if (!this.pushSubscription) {
return true;
}
await this.pushSubscription.unsubscribe();
this.pushSubscription = null;
logger.info({
message: 'Successfully unsubscribed from push notifications',
});
return true;
} catch (error) {
logger.error({
message: 'Failed to unsubscribe from push notifications',
context: { error },
});
return false;
}
}
🤖 Prompt for AI Agents
In `@src/services/push-notification.web.ts` around lines 171 - 189, The
unsubscribe() method returns early when this.pushSubscription is null but must
still locate any persisted browser subscription and clear it; update
unsubscribe() so that if this.pushSubscription is falsy it attempts to retrieve
the active subscription via the Service Worker registration (use
registration.pushManager.getSubscription() or equivalent) and assign it to
this.pushSubscription before proceeding, then call
this.pushSubscription.unsubscribe(), set this.pushSubscription = null, and clear
any persisted client-side record of the subscription (localStorage/IndexedDB
entry if used). Ensure errors from getSubscription() and unsubscribe() are
caught and logged via logger.error and the method still returns a boolean
indicating success/failure.

Comment on lines +250 to +251
static isSupported(): boolean {
return 'serviceWorker' in navigator && 'PushManager' in window && 'Notification' in window;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

fd "push-notification" --type f

Repository: Resgrid/Unit

Length of output: 495


🏁 Script executed:

wc -l src/services/push-notification.web.ts

Repository: Resgrid/Unit

Length of output: 97


🏁 Script executed:

sed -n '245,255p' src/services/push-notification.web.ts

Repository: Resgrid/Unit

Length of output: 340


🏁 Script executed:

cat package.json | grep -A5 -B5 "next\|ssr"

Repository: Resgrid/Unit

Length of output: 855


🏁 Script executed:

grep -r "push-notification.web" --include="*.ts" --include="*.tsx" --include="*.js"

Repository: Resgrid/Unit

Length of output: 38


🏁 Script executed:

cat -n src/services/push-notification.ts

Repository: Resgrid/Unit

Length of output: 23309


🏁 Script executed:

cat -n src/services/push-notification.web.ts

Repository: Resgrid/Unit

Length of output: 8887


🏁 Script executed:

cat -n src/services/__tests__/push-notification.test.ts

Repository: Resgrid/Unit

Length of output: 21008


🏁 Script executed:

grep -r "webPushNotificationService\|push-notification.web\|WebPushNotificationService" --include="*.ts" --include="*.tsx" --include="*.js" --include="*.json"

Repository: Resgrid/Unit

Length of output: 722


🏁 Script executed:

grep -E "react-native-web|ssr|server" package.json

Repository: Resgrid/Unit

Length of output: 272


🏁 Script executed:

rg "typeof window|typeof navigator" --type ts --type tsx

Repository: Resgrid/Unit

Length of output: 83


🏁 Script executed:

rg "typeof window|typeof navigator" --include="*.ts" --include="*.tsx"

Repository: Resgrid/Unit

Length of output: 497


🏁 Script executed:

grep -r "typeof window\|typeof navigator" src/ --include="*.ts" --include="*.tsx" | head -20

Repository: Resgrid/Unit

Length of output: 356


🏁 Script executed:

cat -n src/lib/platform.ts

Repository: Resgrid/Unit

Length of output: 2552


Guard isSupported for non-browser contexts.
Direct access to navigator and window without typeof checks will throw during SSR or in Node.js test environments. The codebase already uses this pattern in src/lib/platform.ts and src/components/ui/gluestack-ui-provider/index.web.tsx.

🐛 Suggested fix
  static isSupported(): boolean {
-    return 'serviceWorker' in navigator && 'PushManager' in window && 'Notification' in window;
+    return (
+      typeof window !== 'undefined' &&
+      typeof navigator !== 'undefined' &&
+      'serviceWorker' in navigator &&
+      'PushManager' in window &&
+      'Notification' in window
+    );
  }
📝 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.

Suggested change
static isSupported(): boolean {
return 'serviceWorker' in navigator && 'PushManager' in window && 'Notification' in window;
static isSupported(): boolean {
return (
typeof window !== 'undefined' &&
typeof navigator !== 'undefined' &&
'serviceWorker' in navigator &&
'PushManager' in window &&
'Notification' in window
);
}
🤖 Prompt for AI Agents
In `@src/services/push-notification.web.ts` around lines 250 - 251, The static
isSupported() method in the PushNotification class (or module) directly accesses
navigator and window which breaks in SSR/Node; update isSupported() to first
guard that typeof window !== 'undefined' and typeof navigator !== 'undefined'
before checking 'serviceWorker' in navigator and 'PushManager'/'Notification' in
window (or reuse the existing platform guard helper used elsewhere) so the
method returns false in non‑browser environments and never throws.

@@ -1,4 +1,4 @@
import notifee, { AndroidImportance } from '@notifee/react-native';
import notifee, { AndroidForegroundServiceType, AndroidImportance } from '@notifee/react-native';
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# Check if node_modules exists and find notifee type definitions
if [ -d "node_modules" ]; then
  echo "=== Searching for notifee types ==="
  find node_modules/@notifee -name "*.d.ts" -o -name "*.ts" | head -20
fi

# Check package.json for `@notifee/react-native` version
echo ""
echo "=== Package version ==="
if [ -f "package.json" ]; then
  cat package.json | jq '.dependencies["@notifee/react-native"] // .devDependencies["@notifee/react-native"] // "Not found"'
fi

Repository: Resgrid/Unit

Length of output: 199


🌐 Web query:

AndroidForegroundServiceType enum values @notifee/react-native documentation

💡 Result:

AndroidForegroundServiceType enum values in @notifee/react-native are:(docs.page)

  • FOREGROUND_SERVICE_TYPE_MANIFEST = -1
  • FOREGROUND_SERVICE_TYPE_DATA_SYNC = 1
  • FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK = 2
  • FOREGROUND_SERVICE_TYPE_PHONE_CALL = 4
  • FOREGROUND_SERVICE_TYPE_LOCATION = 8
  • FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE = 16
  • FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION = 32
  • FOREGROUND_SERVICE_TYPE_CAMERA = 64
  • FOREGROUND_SERVICE_TYPE_MICROPHONE = 128
  • FOREGROUND_SERVICE_TYPE_HEALTH = 256
  • FOREGROUND_SERVICE_TYPE_REMOTE_MESSAGING = 512
  • FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED = 1024
  • FOREGROUND_SERVICE_TYPE_SHORT_SERVICE = 2048
  • FOREGROUND_SERVICE_TYPE_MEDIA_PROCESSING = 8192
  • FOREGROUND_SERVICE_TYPE_SPECIAL_USE = 1073741824

Citations:


TypeScript build failure: AndroidForegroundServiceType.MICROPHONE is not a valid enum member.

The @notifee/react-native enum uses the naming convention FOREGROUND_SERVICE_TYPE_*. The correct member is FOREGROUND_SERVICE_TYPE_MICROPHONE (value 128), not MICROPHONE.

Update line 229 to use the correct enum member: AndroidForegroundServiceType.FOREGROUND_SERVICE_TYPE_MICROPHONE.

Note: If the original magic number [2] was intentional, it maps to FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK—verify which service type is actually needed.

🤖 Prompt for AI Agents
In `@src/stores/app/livekit-store.ts` at line 1, The build fails because the code
uses an invalid enum member AndroidForegroundServiceType.MICROPHONE; update the
usage to the correct Notifee enum name
AndroidForegroundServiceType.FOREGROUND_SERVICE_TYPE_MICROPHONE (or, if the
original numeric intent was the media playback flag, use
AndroidForegroundServiceType.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK) wherever
AndroidForegroundServiceType.MICROPHONE is referenced (e.g., in the livekit
foreground service setup code) so TypeScript compiles cleanly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants