Skip to content

Conversation

@ucswift
Copy link
Member

@ucswift ucswift commented Jan 19, 2026

Summary by CodeRabbit

  • New Features

    • Added calendar functionality for managing calendar items and tracking attendance.
    • Added shift management features for viewing and signing up for shifts.
    • Extended platform support to include web deployments.
  • Improvements

    • Enhanced type safety across navigation and API interactions.
    • Optimized build configuration and linting rules for better code quality.

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

@coderabbitai
Copy link

coderabbitai bot commented Jan 19, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

📝 Walkthrough

Walkthrough

This pull request encompasses a major refactoring of the codebase configuration and type system, alongside new API modules and state management updates. Changes include ESLint/TypeScript configuration migration, GitHub Actions workflow expansion to support web platform builds, new calendar and shifts API endpoints, router navigation type safety improvements, Jest mock updates, substantial test file removals, and store state enhancements including lockscreen lifecycle tracking and SignalR event type definitions.

Changes

Cohort / File(s) Summary
ESLint & Configuration
.eslintignore, .eslintrc.js, .vscode/settings.json, tsconfig.json
Rewired ESLint from Expo/Tailwind/Prettier setup to TypeScript/React-focused config with explicit parser, JSX support, React hooks rules, and import sorting. Expanded ignore patterns and tsconfig excludes to cover node_modules variants and additional directories. Added cSpell configuration.
Build Workflow
.github/workflows/react-native-cicd.yml
Extended CI/CD pipeline to support web platform builds in addition to Android. Replaced RESPOND_* secrets with DISPATCH_* equivalents. Added web export, Docker build/push steps, artifact handling, release notes processing, and Changerawr integration. Updated versioning scheme and release creation.
Jest & Mock Setup
jest-setup.ts, package.json
Updated Jest mocks to remove zod, add @dev-plugins/react-query and @sentry/react-native with multiple exported members. Changed lint script to target src directory.
Calendar API Module
src/api/calendar/calendar.ts
Added new typed API module exporting five functions: getCalendarItems(), getCalendarItem(itemId), getCalendarItemTypes(), getCalendarItemsForDateRange(startDate, endDate), and setCalendarAttending(...).
Shifts API Module
src/api/shifts/shifts.ts
Added new shifts API module with five exported functions: getAllShifts(), getShift(shiftId), getShiftDay(shiftDayId), getTodaysShifts(), and signupForShiftDay(shiftDayId, userId?).
API Client & Utilities
src/api/common/client.tsx, src/api/personnel/personnel.ts
Broadened post/put parameter types from Record<string, unknown> to object. Added getPersonnelFilterOptions alias for backward compatibility.
Router Navigation Type Safety
src/app/(app)/calls.tsx, src/app/(app)/index.tsx, src/app/[...messing].tsx, src/app/call/[id].tsx, src/app/call/new/index.tsx, src/app/lockscreen.tsx, src/app/maintenance.tsx, src/app/onboarding.tsx, src/components/maps/pin-detail-modal.tsx, src/components/push-notification/push-notification-modal.tsx, src/components/sidebar/call-sidebar.tsx
Imported type Href from expo-router and cast navigation targets to Href type across multiple navigation calls for improved type safety.
Layout & Component Updates
src/app/(app)/_layout.tsx, src/app/(app)/home.web.tsx, src/app/_layout.tsx, src/components/calls/dispatch-selection-modal.tsx, src/components/dispatch-console/*
Minor formatting changes to web drawer overflow behavior, component prop destructuring, and memoization dependencies. Updated dependency logic for filtered data memoization.
Test Updates: Navigation & Mocking
src/app/__tests__/lockscreen.test.tsx, src/app/__tests__/maintenance.test.tsx, src/app/login/__tests__/index.test.tsx
Wrapped test-rendered components in NavigationContainer via TestWrapper. Added mockReplace mock for router. Updated assertions to verify mockReplace usage.
Component Test Mocks
src/components/dispatch-console/__tests__/active-calls-panel.test.tsx, src/components/status/__tests__/status-bottom-sheet.test.tsx
Refactored store mocks to support selector functions and expanded state structure with loading/error fields. Updated Statuses array key to Data shape.
Store Implementations
src/stores/calendar/store.ts, src/stores/calendar/__tests__/store.test.ts, src/stores/calls/__tests__/store.test.ts, src/stores/shifts/store.ts, src/stores/shifts/__tests__/store.test.ts
Removed ApiResponse type casting and updated to direct Data property access. Split global error/loading state into per-category fields for call types and priorities. Updated test mock payloads.
Lockscreen Store
src/stores/lockscreen/store.tsx
Introduced comprehensive lock state management with isLocked, lockTimeout, lastActivityTime, and methods: lock(), unlock(), updateActivity(), setLockTimeout(), shouldLock(), getTimeoutMs().
SignalR Store Enhancement
src/stores/signalr/signalr-store.ts
Added new exported type SignalREventType and state fields for per-event timestamps (lastPersonnelUpdateTimestamp, lastUnitsUpdateTimestamp, lastCallsUpdateTimestamp, lastEventType). Updated handlers to populate event type tracking.
Hook & Service Type Fixes
src/hooks/use-map-signalr-updates.ts, src/hooks/use-signalr-lifecycle.ts, src/services/bluetooth-audio/native.service.ts
Changed timer reference types from number | null to ReturnType<typeof setTimeout> | null for cross-environment compatibility.
Bluetooth Audio Service
src/services/bluetooth-audio/index.ts, src/components/bluetooth/bluetooth-audio-modal.tsx
Added public singleton export bluetoothAudioService via factory. Updated import path from .service to without suffix.
Type Definitions
src/types/api.ts
Added ApiResponse<T> and PaginatedApiResponse<T> interfaces supporting both data and Data payload naming, with pagination metadata support.
Test Files Removed
src/components/settings/__tests__/*.test.tsx, src/services/__tests__/*.test.ts, src/stores/app/__tests__/core-store.test.ts, src/stores/status/__tests__/store.test.ts, and others
Deleted comprehensive test suites for AudioDeviceSelection, BluetoothDeviceSelection (both variants), UnitSelectionBottomSheet (both), AppInitializationService, BluetoothAudioService (multiple variants), CallKeepService iOS, LocationService (foreground and general), and StatusStore—totaling ~4,100 lines of test code removed.
Minor Formatting & Cleanup
src/app/map.tsx, src/components/calls/call-card.web.tsx, src/components/dispatch-console/animated-refresh-icon.web.tsx, src/components/dispatch-console/stats-header.tsx, src/components/dispatch-console/units-panel.tsx, src/components/dispatch-console/notes-panel.tsx, src/components/dispatch-console/index.ts, src/components/sidebar/side-menu.web.tsx, src/lib/auth/index.tsx, src/lib/hooks/use-keep-alive.web.tsx, src/lib/i18n/utils.web.tsx, src/lib/storage/*, src/services/audio.service.web.ts, src/services/signalr.service.ts, src/services/app-initialization.service.ts, src/services/aptabase.service.ts, src/stores/app/location-store.ts, src/stores/auth/store.tsx, src/stores/signalr/__tests__/signalr-store.test.ts
Import reordering, whitespace normalization, trailing newlines, end-of-file formatting, and removal of unused properties (e.g., hydrate from useAuth).
Component Props & Exports
src/components/sidebar/side-menu.tsx, src/components/sidebar/__tests__/side-menu.test.tsx
Added SideMenuProps interface with optional onNavigate callback. Updated component signature to React.FC<SideMenuProps> and changed export from named to default (implied by test changes).
Test Mock Refinements
src/features/livekit-call/store/__tests__/useLiveKitCallStore.test.ts, src/stores/app/__tests__/livekit-store.test.ts, src/components/status/__tests__/status-gps-debug.test.tsx, src/lib/__tests__/livekit-platform-init.test.ts
Updated CallKeep service mocks to use { virtual: true } for environment-agnostic module handling. Added clarifying comments. Adjusted test assertions for logging flexibility.
Deleted Test Files (Status & Location)
src/components/status/__tests__/status-gps-integration.test.tsx, src/services/__tests__/signalr.service.enhanced.test.ts, src/services/__tests__/location-foreground-permissions.test.ts, src/services/__tests__/location.test.ts
Removed GPS integration test block and location foreground permission test suite (~400 lines). Updated SignalR service test logging assertions to use flexible pattern matching.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes


🐰 A rabbit's verse on this grand refactor quest:

Types now safe with Href's gentle cast,
API modules built to ever last,
Lockscreens wake with timers set just right,
SignalR events tracked through the night,
Tests retired make the path more bright! 🌟

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.09% 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 title 'RD-T39 Fixing build' is vague and lacks specific information about what build issues were fixed. Replace with a more descriptive title that explains the specific build fixes, such as 'Update ESLint and build configuration for TypeScript and React compliance' or 'Fix build configuration and add calendar/shifts APIs'.
✅ 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.


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.

@ucswift
Copy link
Member Author

ucswift commented Jan 19, 2026

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Jan 19, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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: 6

Caution

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

⚠️ Outside diff range comments (6)
src/features/livekit-call/store/__tests__/useLiveKitCallStore.test.ts (1)

502-549: Duplicate test blocks detected.

There's a nested describe('Error Handling') block (lines 503-536) inside the outer describe('Error Handling') (line 502), and the test 'should handle basic error state management' is duplicated at lines 525-535 and 538-548. This appears to be a merge artifact or copy-paste error.

Consider removing the duplicate nested block:

🔧 Suggested fix
   describe('Error Handling', () => {
-    describe('Error Handling', () => {
     it('should handle room initialization errors', async () => {
       // Make the Room constructor throw an error
       MockedRoom.mockImplementationOnce(() => {
         throw new Error('Failed to initialize room');
       });
       
       const { result } = renderHook(() => useLiveKitCallStore());

       await act(async () => {
         await result.current.actions.connectToRoom('test-room', 'test-participant');
       });

       expect(mockLogger.error).toHaveBeenCalledWith({
         message: 'Failed to connect to LiveKit room',
         context: { error: expect.any(Error), roomId: 'test-room', participantIdentity: 'test-participant' },
       });
       expect(result.current.error).toBe('Failed to initialize room');
       expect(result.current.isConnecting).toBe(false);
       expect(result.current.isConnected).toBe(false);
     });

     it('should handle basic error state management', async () => {
       const { result } = renderHook(() => useLiveKitCallStore());

       // Test basic error clearing functionality since token fetching isn't implemented
       act(() => {
         // Set an error state and then clear it
         result.current.actions._clearError();
       });

       expect(result.current.error).toBeNull();
     });
-  });
-
-    it('should handle basic error state management', async () => {
-      const { result } = renderHook(() => useLiveKitCallStore());
-
-      // Test basic error clearing functionality since token fetching isn't implemented
-      act(() => {
-        // Set an error state and then clear it
-        result.current.actions._clearError();
-      });
-
-      expect(result.current.error).toBeNull();
-    });
   });
src/stores/calendar/store.ts (5)

109-116: Property name mismatch: response.Data will be undefined.

The API function getCalendarItemsForDateRange (in src/api/calendar/calendar.ts, lines 45-51) returns response.data (lowercase). However, this code accesses response.Data (uppercase), which will be undefined, causing the .filter() call on line 113 to throw a runtime error.

🐛 Proposed fix
-      const todayItems = response.Data.filter((item: CalendarItemResultData) => {
+      const todayItems = (response.Data ?? response.data ?? []).filter((item: CalendarItemResultData) => {

Alternatively, if the API consistently returns lowercase data:

-      const todayItems = response.Data.filter((item: CalendarItemResultData) => {
+      const todayItems = response.data.filter((item: CalendarItemResultData) => {

153-162: Same property name mismatch in loadUpcomingCalendarItems.

response.Data on lines 155 and 161 should be response.data to match what the API function returns.

🐛 Proposed fix
       const response = await getCalendarItemsForDateRange(startDate, endDate);
       set({
-        upcomingCalendarItems: response.Data,
+        upcomingCalendarItems: response.data,
         isUpcomingLoading: false,
         updateCalendarItems: false,
       });
       logger.info({
         message: 'Upcoming calendar items loaded successfully',
-        context: { count: response.Data.length, startDate, endDate },
+        context: { count: response.data.length, startDate, endDate },
       });

179-188: Same property name mismatch in loadCalendarItems.

response.Data on lines 181 and 187 should be response.data.


201-210: Same property name mismatch in loadCalendarItemsForDateRange.

response.Data on lines 203 and 209 should be response.data.


285-290: Property name mismatch in fetchItemTypes.

The API function getCalendarItemTypes returns response.data, but lines 286 and 289 access response.Data.

🐛 Proposed fix
       const response = await getCalendarItemTypes();
-      set({ itemTypes: response.Data, isTypesLoading: false });
+      set({ itemTypes: response.data, isTypesLoading: false });
       logger.info({
         message: 'Calendar item types fetched successfully',
-        context: { count: response.Data.length },
+        context: { count: response.data.length },
       });
🤖 Fix all issues with AI agents
In @.github/workflows/react-native-cicd.yml:
- Around line 449-454: The NOTES filtering pipeline can fail under set -eo
pipefail when all lines are removed (causing grep to exit 1); update the
pipeline that builds NOTES (the command that assigns NOTES and uses multiple
grep -v filters) to tolerate no matches by ensuring the grep pipeline does not
cause a non-zero exit (e.g., append a fallback like "|| true" to the pipeline or
capture the pipeline output into a subshell and handle empty result) so the
assignment to NOTES never fails under set -eo pipefail.
- Around line 475-493: The release step ("📦 Create Release" using
ncipollo/release-action@v1) unconditionally references
'./web-artifacts/ResgridDispatch-web.zip' even though the download step ("📦
Download Web Artifacts") can skip/fail (it uses continue-on-error), so the
release can fail when the web artifact is absent; fix by making the release
artifacts list conditional or computed at runtime (e.g., add an earlier step
that checks for './web-artifacts/ResgridDispatch-web.zip' and sets an output/env
var or writes a comma-separated ARTIFACTS string, then reference that variable
in the Create Release step, or split into two Create Release steps guarded by
if: the first includes web-artifacts when present, the second omits it when
absent), and ensure the Create Release step only uses the
'./web-artifacts/ResgridDispatch-web.zip' path when that file actually exists.

In `@src/app/__tests__/lockscreen.test.tsx`:
- Around line 60-100: Add unit tests covering the missing critical flows: add
tests that toggle the password visibility by interacting with the showPassword
toggle control and assert input type changes; test the unlock submission by
filling the password and invoking the onSubmit flow (mock the unlock handler or
API) and assert expected calls and UI changes; cover error handling by mocking a
failing unlock response and asserting the error message is rendered; cover
loading state by forcing isUnlocking=true (or mocking the unlock promise to
delay) and assert a loading indicator / disabled button; and cover logout by
triggering the handleLogout control and asserting the logout handler is called.
Reference the Lockscreen component, its showPassword state, onSubmit handler,
isUnlocking flag, and handleLogout function when locating where to add these
tests.

In `@src/stores/calendar/store.ts`:
- Around line 267-268: In fetchCalendarItem, the code reads response.Data but
the API getCalendarItem returns response.data; update the property access to
response.data when setting viewCalendarItem and ensure set({ viewCalendarItem:
response.data, isItemLoading: false }) is used so fetchCalendarItem,
getCalendarItem, viewCalendarItem and set all reference the correct payload
property.

In `@src/stores/calls/__tests__/store.test.ts`:
- Around line 106-109: The tests are leaking per-category state because setState
merges, so update the test suite's beforeEach (the block that calls setState) to
explicitly reset the types-specific and priorities-specific fields — e.g., set
typesError to null/'' , isLoadingTypes to false, callTypes to [], and likewise
reset prioritiesError, isLoadingPriorities and the priorities list — so each
test starts with a clean per-category state and is order-independent.

In `@src/stores/lockscreen/store.tsx`:
- Around line 53-61: The setLockTimeout handler currently accepts any number and
computes _cachedTimeoutMs directly, which can produce negative or NaN timeouts;
update setLockTimeout to validate the minutes parameter (use
Number.isFinite(minutes) and clamp to a non-negative value, e.g. minutes =
Math.max(0, Number(minutes) || 0)), log a warning via logger.warn if an invalid
value was provided, and then call set with lockTimeout set to the sanitized
minutes and _cachedTimeoutMs computed as minutes * 60 * 1000 (or 0 when clamped)
to ensure no negative/NaN cached timeout values.
🧹 Nitpick comments (16)
src/services/bluetooth-audio/index.ts (1)

11-14: Import statement should be placed before exports for consistency.

While JavaScript hoists imports, placing the import after the re-exports is unconventional. Consider moving it to the top for better readability.

Suggested reordering
 export * from './base.service';
 export * from './factory.service';
 export * from './native.service';
 export * from './web.service';

-// Export a singleton instance for convenience
-import { createBluetoothAudioService } from './factory.service';
-export const bluetoothAudioService = createBluetoothAudioService();
+import { createBluetoothAudioService } from './factory.service';
+
+// Export a singleton instance for convenience
+export const bluetoothAudioService = createBluetoothAudioService();
src/components/status/__tests__/status-gps-integration.test.tsx (2)

51-54: Remove debug console.log statement.

This console.log appears to be a leftover from debugging and will add noise to test output.

🧹 Suggested fix
     mockUseLocationStore.mockImplementation(() => {
-      console.log('useLocationStore called, returning:', mockLocationStore);
       return mockLocationStore;
     });

58-59: Remove duplicate mock setup.

mockUseCoreStore.mockReturnValue(mockCoreStore) is called twice consecutively. Remove the redundant call.

🧹 Suggested fix
     mockUseCoreStore.mockReturnValue(mockCoreStore);
-    mockUseCoreStore.mockReturnValue(mockCoreStore);
     // Also mock getState for the status store logic
.github/workflows/react-native-cicd.yml (3)

277-284: Add directory existence check before zipping.

The step assumes dist directory exists after expo export. If the export fails silently or produces output elsewhere, this step would fail with a confusing error.

Suggested improvement
       - name: 📦 Zip Web Export
         if: (matrix.platform == 'web' && (github.event.inputs.buildType == 'all' || github.event_name == 'push' || github.event.inputs.platform == 'web' || github.event.inputs.platform == 'all'))
         run: |
+          if [ ! -d "dist" ]; then
+            echo "Error: dist directory not found after web export"
+            exit 1
+          fi
           cd dist
           zip -r ../ResgridDispatch-web.zip .
           cd ..
           echo "Web export zipped successfully"
           ls -la ResgridDispatch-web.zip

509-521: Unused variables in JSON payload construction.

The --arg repo, --arg commit, --arg actor, and --arg run_url parameters are defined but not used in the final JSON object. These appear to be leftover from a more detailed payload design.

Either remove unused args or include them in the payload
           # Create JSON payload using --arg for proper escaping
           PAYLOAD=$(jq -n \
             --arg version "$VERSION" \
             --arg date "$RELEASE_DATE" \
             --arg notes "$NOTES_CONTENT" \
-            --arg repo "${{ github.repository }}" \
-            --arg commit "${{ github.sha }}" \
-            --arg actor "${{ github.actor }}" \
-            --arg run_url "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \
             '{
               "version": $version,
               "title": ("Release v" + $version),
               "content": $notes
             }')

528-533: Add timeout to curl to prevent job hangs.

The curl command lacks explicit timeouts. If the Changerawr API is unresponsive, this step could hang indefinitely even with continue-on-error: true.

Add connection and max-time limits
           # Send to Changerawr API
           HTTP_STATUS=$(curl -s -o /tmp/changerawr_response.json -w "%{http_code}" \
+            --connect-timeout 10 \
+            --max-time 30 \
             -X POST \
             -H "Content-Type: application/json" \
             -H "Authorization: Bearer ${CHANGERAWR_API_KEY}" \
             -d "$PAYLOAD" \
             "${CHANGERAWR_API_URL}")
src/app/(app)/_layout.tsx (1)

407-407: Confirm always-on scrollbars are intended on web. overflow: 'scroll' forces scrollbars even when content fits; if this was only to satisfy typing, consider preserving auto.

💡 Optional tweak to preserve auto-scroll behavior
-                <View style={{ flex: 1, overflow: 'scroll' as 'visible' | 'hidden' | 'scroll' }}>
+                <View style={{ flex: 1, overflow: 'auto' as any }}>
package.json (1)

38-38: Consider a separate lint target for non-src files.

Line 38 now limits linting to src, which may skip config/scripts at the repo root. If those files still need linting, consider adding a secondary script (e.g., lint:root) or extending the pattern.

tsconfig.json (1)

32-42: Redundant node_modules exclusion patterns.

The exclude array contains overlapping patterns:

  • "node_modules" already excludes the top-level node_modules
  • "node_modules/**/*" and "**/node_modules/*" are partially redundant

TypeScript excludes node_modules by default when outDir is set or no explicit include is provided for it. Consider simplifying to just "**/node_modules" if you need to exclude nested node_modules in monorepo scenarios.

♻️ Suggested simplification
   "exclude": [
-    "node_modules",
-    "node_modules/**/*",
-    "**/node_modules/*",
+    "**/node_modules",
     "docs",
     "cli",
     "android",
     "lib",
     "ios",
     "storybookDocsComponents"
   ],
src/stores/calendar/store.ts (1)

8-8: Unused import.

CalendarItemsResult is imported but not used in this file.

🧹 Proposed fix
-import { type CalendarItemsResult } from '@/models/v4/calendar/calendarItemsResult';
src/api/common/client.tsx (1)

124-125: Type loosening from Record<string, unknown> to object reduces type safety.

The change from Record<string, unknown> to object allows any non-primitive type, including arrays, class instances, and other objects that may not serialize correctly as JSON payloads. This could mask type errors at compile time.

Consider keeping the stricter type or using a more precise alternative if array payloads are needed:

♻️ Suggested alternatives
-    post: <T,>(data: object, signal?: AbortSignal) => api.post<T>(endpoint, data, { signal }),
-    put: <T,>(data: object, signal?: AbortSignal) => api.put<T>(endpoint, data, { signal }),
+    post: <T,>(data: Record<string, unknown> | unknown[], signal?: AbortSignal) => api.post<T>(endpoint, data, { signal }),
+    put: <T,>(data: Record<string, unknown> | unknown[], signal?: AbortSignal) => api.put<T>(endpoint, data, { signal }),

Or revert to the original stricter typing if arrays aren't needed:

-    post: <T,>(data: object, signal?: AbortSignal) => api.post<T>(endpoint, data, { signal }),
-    put: <T,>(data: object, signal?: AbortSignal) => api.put<T>(endpoint, data, { signal }),
+    post: <T,>(data: Record<string, unknown>, signal?: AbortSignal) => api.post<T>(endpoint, data, { signal }),
+    put: <T,>(data: Record<string, unknown>, signal?: AbortSignal) => api.put<T>(endpoint, data, { signal }),
src/stores/signalr/signalr-store.ts (1)

175-227: Consider reusing a single timestamp per handler for consistency.

Several handlers call Date.now() multiple times; using one now avoids tiny drift between lastUpdateTimestamp and the specific last-* timestamp.

♻️ Example pattern to apply
-      updateHubHandlers.personnelStatusUpdated = (message: unknown) => {
+      updateHubHandlers.personnelStatusUpdated = (message: unknown) => {
+        const now = Date.now();
         logger.info({
           message: 'personnelStatusUpdated',
           context: { message },
         });
-        set({ lastUpdateMessage: JSON.stringify(message), lastUpdateTimestamp: Date.now(), lastEventType: 'personnelStatusUpdated', lastPersonnelUpdateTimestamp: Date.now() });
+        set({ lastUpdateMessage: JSON.stringify(message), lastUpdateTimestamp: now, lastEventType: 'personnelStatusUpdated', lastPersonnelUpdateTimestamp: now });
       };
.eslintrc.js (1)

38-40: Consider re-enabling @typescript-eslint/no-unused-vars.

Disabling this rule entirely can mask dead code and unused imports. If the goal is to fix build errors incrementally, consider using warn severity instead, or narrowing the disable to specific patterns.

-    '@typescript-eslint/no-unused-vars': 'off',
+    '@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }],

This allows prefixing intentionally unused variables with _ while still catching accidental dead code.

src/stores/shifts/__tests__/store.test.ts (1)

103-103: Remove unused mockSignupResult.

This mock data is no longer used since signupForShiftDay now returns void.

🧹 Suggested cleanup
-const mockSignupResult = { ...createBaseMockResult(), Id: 'signup1' };
src/stores/shifts/store.ts (2)

104-111: Consider removing redundant inline type annotations.

The API functions (getAllShifts, getTodaysShifts) already return typed promises (Promise<ShiftsResult>, Promise<ShiftDaysResult>). The inline type annotations add verbosity without additional type safety.

♻️ Suggested simplification
       await Promise.all([
-        getAllShifts().then((response: { Data: ShiftResultData[] }) => {
-          set((state) => ({ ...state, shifts: response.Data }));
+        getAllShifts().then((response) => {
+          set({ shifts: response.Data });
         }),
-        getTodaysShifts().then((response: { Data: ShiftDaysResultData[] }) => {
-          set((state) => ({ ...state, todaysShiftDays: response.Data }));
+        getTodaysShifts().then((response) => {
+          set({ todaysShiftDays: response.Data });
         }),
       ]);

139-140: Replace console.log with logger.

For consistency with the rest of the codebase, use the structured logger instead of console.log.

🧹 Suggested fix
     } catch (error) {
-      console.log('fetchAllShifts error:', error);
       set({
         error: 'Failed to fetch shifts',
         isLoading: false,
       });
+      logger.error({
+        message: 'Failed to fetch all shifts',
+        context: { error },
+      });

Comment on lines 449 to 454
# Remove CodeRabbit auto-generated lines
NOTES="$(printf '%s\n' "$NOTES" \
| grep -v "Summary by CodeRabbit" \
| grep -v "✏️ Tip: You can customize this high-level summary" \
| grep -v "<!-- This is an auto-generated comment: release notes by coderabbit.ai -->" \
| grep -v "<!-- end of auto-generated comment: release notes by coderabbit.ai -->")"
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

Potential pipeline failure when filtering CodeRabbit lines.

With set -eo pipefail enabled (line 437), if the notes contain only CodeRabbit-generated content and all lines are filtered out by grep -v, the command will return exit code 1 and fail the step.

Suggested fix using `grep ... || true` or checking result
             # Remove CodeRabbit auto-generated lines
-            NOTES="$(printf '%s\n' "$NOTES" \
-              | grep -v "Summary by CodeRabbit" \
-              | grep -v "✏️ Tip: You can customize this high-level summary" \
-              | grep -v "<!-- This is an auto-generated comment: release notes by coderabbit.ai -->" \
-              | grep -v "<!-- end of auto-generated comment: release notes by coderabbit.ai -->")"
+            NOTES="$(printf '%s\n' "$NOTES" \
+              | grep -v "Summary by CodeRabbit" \
+              | grep -v "✏️ Tip: You can customize this high-level summary" \
+              | grep -v "<!-- This is an auto-generated comment: release notes by coderabbit.ai -->" \
+              | grep -v "<!-- end of auto-generated comment: release notes by coderabbit.ai -->" || true)"
🤖 Prompt for AI Agents
In @.github/workflows/react-native-cicd.yml around lines 449 - 454, The NOTES
filtering pipeline can fail under set -eo pipefail when all lines are removed
(causing grep to exit 1); update the pipeline that builds NOTES (the command
that assigns NOTES and uses multiple grep -v filters) to tolerate no matches by
ensuring the grep pipeline does not cause a non-zero exit (e.g., append a
fallback like "|| true" to the pipeline or capture the pipeline output into a
subshell and handle empty result) so the assignment to NOTES never fails under
set -eo pipefail.

Comment on lines 475 to 493
- name: 📦 Download Web Artifacts
if: ${{ matrix.platform == 'android' && (github.event.inputs.buildType == 'all' || github.event_name == 'push' || github.event.inputs.buildType == 'prod-apk') }}
uses: actions/download-artifact@v4
with:
name: app-builds-web
path: ./web-artifacts
continue-on-error: true

- name: 📦 Create Release
if: ${{ matrix.platform == 'android' && (github.event.inputs.buildType == 'all' || github.event_name == 'push' || github.event.inputs.buildType == 'prod-apk') }}
uses: ncipollo/release-action@v1
with:
tag: '10.${{ github.run_number }}'
tag: '1.${{ github.run_number }}'
commit: ${{ github.sha }}
makeLatest: true
allowUpdates: true
name: '10.${{ github.run_number }}'
artifacts: './ResgridRespond-prod.apk'
bodyFile: 'RELEASE_NOTES.md'
name: '1.${{ github.run_number }}'
artifacts: './ResgridDispatch-prod.apk,./web-artifacts/ResgridDispatch-web.zip'
bodyFile: 'RELEASE_NOTES.md'
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

Release may fail if web artifacts are unavailable.

The download step uses continue-on-error: true (line 481), but the release step still references ./web-artifacts/ResgridDispatch-web.zip unconditionally. If the web build didn't run or failed, this file won't exist and the release action may fail.

Consider dynamically building the artifacts list or adding a fallback:

Suggested approach
       - name: 📦 Create Release
         if: ${{ matrix.platform == 'android' && (github.event.inputs.buildType == 'all' || github.event_name == 'push' || github.event.inputs.buildType == 'prod-apk') }}
+        id: release
+        run: |
+          ARTIFACTS="./ResgridDispatch-prod.apk"
+          if [ -f "./web-artifacts/ResgridDispatch-web.zip" ]; then
+            ARTIFACTS="${ARTIFACTS},./web-artifacts/ResgridDispatch-web.zip"
+          fi
+          echo "artifacts=${ARTIFACTS}" >> $GITHUB_OUTPUT
+
+      - name: 📦 Create Release
+        if: ${{ matrix.platform == 'android' && (github.event.inputs.buildType == 'all' || github.event_name == 'push' || github.event.inputs.buildType == 'prod-apk') }}
         uses: ncipollo/release-action@v1
         with:
           tag: '1.${{ github.run_number }}'
           commit: ${{ github.sha }}
           makeLatest: true
           allowUpdates: true
           name: '1.${{ github.run_number }}'
-          artifacts: './ResgridDispatch-prod.apk,./web-artifacts/ResgridDispatch-web.zip'
+          artifacts: ${{ steps.release.outputs.artifacts }}
           bodyFile: 'RELEASE_NOTES.md'
🤖 Prompt for AI Agents
In @.github/workflows/react-native-cicd.yml around lines 475 - 493, The release
step ("📦 Create Release" using ncipollo/release-action@v1) unconditionally
references './web-artifacts/ResgridDispatch-web.zip' even though the download
step ("📦 Download Web Artifacts") can skip/fail (it uses continue-on-error), so
the release can fail when the web artifact is absent; fix by making the release
artifacts list conditional or computed at runtime (e.g., add an earlier step
that checks for './web-artifacts/ResgridDispatch-web.zip' and sets an output/env
var or writes a comma-separated ARTIFACTS string, then reference that variable
in the Create Release step, or split into two Create Release steps guarded by
if: the first includes web-artifacts when present, the second omits it when
absent), and ensure the Create Release step only uses the
'./web-artifacts/ResgridDispatch-web.zip' path when that file actually exists.

Comment on lines 60 to 100
it('should render lockscreen correctly', () => {
render(<Lockscreen />);
render(
<TestWrapper>
<Lockscreen />
</TestWrapper>
);

expect(screen.getByText('lockscreen.title')).toBeTruthy();
expect(screen.getByText('lockscreen.message')).toBeTruthy();
expect(screen.getByText('lockscreen.unlock_button')).toBeTruthy();
});

it('should render password input field', () => {
render(<Lockscreen />);
render(
<TestWrapper>
<Lockscreen />
</TestWrapper>
);

expect(screen.getByPlaceholderText('lockscreen.password_placeholder')).toBeTruthy();
});

it('should toggle password visibility', () => {
render(<Lockscreen />);

const passwordInput = screen.getByPlaceholderText('lockscreen.password_placeholder');
expect(passwordInput.props.secureTextEntry).toBe(true);

// Find and click the eye icon button
const eyeButton = screen.getByTestId('password-toggle');
fireEvent.press(eyeButton);
it('should render welcome back message when authenticated', () => {
render(
<TestWrapper>
<Lockscreen />
</TestWrapper>
);

expect(passwordInput.props.secureTextEntry).toBe(false);
expect(screen.getByText('lockscreen.welcome_back')).toBeTruthy();
});

it('should handle unlock submission', async () => {
render(<Lockscreen />);

const passwordInput = screen.getByPlaceholderText('lockscreen.password_placeholder');
const unlockButton = screen.getByText('lockscreen.unlock_button');

fireEvent.changeText(passwordInput, 'testpassword');
fireEvent.press(unlockButton);

await waitFor(() => {
expect(mockUnlock).toHaveBeenCalled();
expect(mockReplace).toHaveBeenCalledWith('/(app)');
});
});

it('should show error for empty password', async () => {
render(<Lockscreen />);

const unlockButton = screen.getByText('lockscreen.unlock_button');
fireEvent.press(unlockButton);

await waitFor(() => {
expect(screen.getByText(/required/i)).toBeTruthy();
});
});

it('should handle logout', async () => {
render(<Lockscreen />);

const logoutLink = screen.getByText('lockscreen.not_you');
fireEvent.press(logoutLink);

await waitFor(() => {
expect(mockUnlock).toHaveBeenCalled();
expect(mockLogout).toHaveBeenCalled();
expect(mockReplace).toHaveBeenCalledWith('/login');
});
});

it('should display loading state while unlocking', async () => {
render(<Lockscreen />);

const passwordInput = screen.getByPlaceholderText('lockscreen.password_placeholder');
const unlockButton = screen.getByText('lockscreen.unlock_button');

fireEvent.changeText(passwordInput, 'testpassword');
fireEvent.press(unlockButton);
it('should display logout link', () => {
render(
<TestWrapper>
<Lockscreen />
</TestWrapper>
);

expect(screen.getByText('lockscreen.unlocking')).toBeTruthy();
expect(screen.getByText('lockscreen.not_you')).toBeTruthy();
});
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

Significant reduction in test coverage.

The current tests only verify basic rendering (title, message, button, password input, welcome message, logout link). Based on the component's functionality described in the relevant code snippets, the following scenarios are no longer covered:

  • Password visibility toggle (showPassword state)
  • Unlock submission flow (onSubmit handler)
  • Error handling and display
  • Loading state (isUnlocking)
  • Logout flow execution (handleLogout)

Consider adding these tests back to maintain coverage of critical user flows.

Would you like me to help generate additional test cases for the missing scenarios?

🤖 Prompt for AI Agents
In `@src/app/__tests__/lockscreen.test.tsx` around lines 60 - 100, Add unit tests
covering the missing critical flows: add tests that toggle the password
visibility by interacting with the showPassword toggle control and assert input
type changes; test the unlock submission by filling the password and invoking
the onSubmit flow (mock the unlock handler or API) and assert expected calls and
UI changes; cover error handling by mocking a failing unlock response and
asserting the error message is rendered; cover loading state by forcing
isUnlocking=true (or mocking the unlock promise to delay) and assert a loading
indicator / disabled button; and cover logout by triggering the handleLogout
control and asserting the logout handler is called. Reference the Lockscreen
component, its showPassword state, onSubmit handler, isUnlocking flag, and
handleLogout function when locating where to add these tests.

Comment on lines +267 to 268
const response = await getCalendarItem(calendarItemId);
set({ viewCalendarItem: response.Data, isItemLoading: 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 | 🔴 Critical

Property name mismatch in fetchCalendarItem.

The API function getCalendarItem returns response.data, but line 268 accesses response.Data.

🐛 Proposed fix
       const response = await getCalendarItem(calendarItemId);
-      set({ viewCalendarItem: response.Data, isItemLoading: false });
+      set({ viewCalendarItem: response.data, isItemLoading: false });
🤖 Prompt for AI Agents
In `@src/stores/calendar/store.ts` around lines 267 - 268, In fetchCalendarItem,
the code reads response.Data but the API getCalendarItem returns response.data;
update the property access to response.data when setting viewCalendarItem and
ensure set({ viewCalendarItem: response.data, isItemLoading: false }) is used so
fetchCalendarItem, getCalendarItem, viewCalendarItem and set all reference the
correct payload property.

Comment on lines 106 to 109
await waitFor(() => {
expect(result.current.error).toBe('Failed to fetch call types');
expect(result.current.isLoading).toBe(false);
expect(result.current.typesError).toBe('API Error');
expect(result.current.isLoadingTypes).toBe(false);
expect(result.current.callTypes).toEqual([]);
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

Reset per-category fields in beforeEach to prevent state leakage.

setState merges, so typesError/isLoadingTypes (and priorities equivalents) can persist across tests, making the suite order‑dependent. Update the reset block accordingly.

🔧 Suggested update
 useCallsStore.setState({
   calls: [],
   callPriorities: [],
   callTypes: [],
   isLoading: false,
   error: null,
+  isLoadingTypes: false,
+  typesError: null,
+  isLoadingPriorities: false,
+  prioritiesError: null,
 });
🤖 Prompt for AI Agents
In `@src/stores/calls/__tests__/store.test.ts` around lines 106 - 109, The tests
are leaking per-category state because setState merges, so update the test
suite's beforeEach (the block that calls setState) to explicitly reset the
types-specific and priorities-specific fields — e.g., set typesError to null/''
, isLoadingTypes to false, callTypes to [], and likewise reset prioritiesError,
isLoadingPriorities and the priorities list — so each test starts with a clean
per-category state and is order-independent.

Comment on lines 53 to 61
setLockTimeout: (minutes: number) => {
logger.info({
message: 'Setting lock timeout',
context: { minutes },
});
set({
lockTimeout: minutes,
_cachedTimeoutMs: minutes * 60 * 1000,
});
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

Guard against negative/NaN lock timeout values.

Without validation, a negative/NaN timeout can yield immediate or undefined locking behavior.

✅ Safer timeout handling
-  setLockTimeout: (minutes: number) => {
+  setLockTimeout: (minutes: number) => {
     logger.info({
       message: 'Setting lock timeout',
       context: { minutes },
     });
+    const safeMinutes = Number.isFinite(minutes) ? Math.max(0, minutes) : 0;
     set({
-      lockTimeout: minutes,
-      _cachedTimeoutMs: minutes * 60 * 1000,
+      lockTimeout: safeMinutes,
+      _cachedTimeoutMs: safeMinutes * 60 * 1000,
     });
   },
📝 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
setLockTimeout: (minutes: number) => {
logger.info({
message: 'Setting lock timeout',
context: { minutes },
});
set({
lockTimeout: minutes,
_cachedTimeoutMs: minutes * 60 * 1000,
});
setLockTimeout: (minutes: number) => {
logger.info({
message: 'Setting lock timeout',
context: { minutes },
});
const safeMinutes = Number.isFinite(minutes) ? Math.max(0, minutes) : 0;
set({
lockTimeout: safeMinutes,
_cachedTimeoutMs: safeMinutes * 60 * 1000,
});
},
🤖 Prompt for AI Agents
In `@src/stores/lockscreen/store.tsx` around lines 53 - 61, The setLockTimeout
handler currently accepts any number and computes _cachedTimeoutMs directly,
which can produce negative or NaN timeouts; update setLockTimeout to validate
the minutes parameter (use Number.isFinite(minutes) and clamp to a non-negative
value, e.g. minutes = Math.max(0, Number(minutes) || 0)), log a warning via
logger.warn if an invalid value was provided, and then call set with lockTimeout
set to the sanitized minutes and _cachedTimeoutMs computed as minutes * 60 *
1000 (or 0 when clamped) to ensure no negative/NaN cached timeout values.

@ucswift
Copy link
Member Author

ucswift commented Jan 19, 2026

Approve

Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

This PR is approved.

@ucswift ucswift merged commit 9211447 into master Jan 19, 2026
9 of 11 checks passed
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