Skip to content

Develop#253

Merged
ucswift merged 4 commits into
masterfrom
develop
Jul 22, 2026
Merged

Develop#253
ucswift merged 4 commits into
masterfrom
develop

Conversation

@ucswift

@ucswift ucswift commented Jul 21, 2026

Copy link
Copy Markdown
Member

Pull Request Description

This PR introduces several new features, bug fixes, and improvements:

New: Incident Command Tab

Adds a resource-facing incident command view to the call detail screen, displaying:

  • Unit assignment — lane name, primary/secondary leads (with tap-to-call and tap-to-email), assigned objectives with progress, and linked needs
  • Incident info — commander contact, established/estimated end times, important information, and action plan
  • Objectives, needs, notes, and attachments — with status badges, quantity tracking, and empty states

Includes the API endpoint, data models, a Zustand store (with stale-request and reset guards), and the tab panel UI component.

New: Destination POI Routing

  • The call detail map can now toggle between the call/dispatch location and the destination POI when a call has one
  • A "destination" route button opens native maps directions to the destination coordinates (or falls back to the address)
  • The sidebar call card gains a dedicated destination navigation button alongside the existing call location button

Fix: Duplicate Push Notification Modal on Cold Start

Resolves an issue where the same cold-start notification tap could trigger the notification modal twice — once from the notification response listener and again from getLastNotificationResponseAsync. Both paths now flow through a shared handler that deduplicates by request identifier while still allowing distinct notifications to be processed.

Other Fixes

  • Weather alerts: GetWeatherAlert now uses a path parameter instead of a query parameter; the detail store also clears stale alert data before refetching
  • Drawer layout: Replaced an unsafe as any props cast with proper size/anchor props and adjusted the sidebar width
  • Severity filter tabs: Prevented the horizontal scroll view from expanding unnecessarily with grow-0

Summary by CodeRabbit

  • New Features
    • Added an Incident Command tab with assignments, objectives, needs, notes, attachments, commander details, and progress information.
    • Added destination-aware map viewing and navigation from call details and call cards.
    • Added support for loading incident command information and localized labels across supported languages.
  • Bug Fixes
    • Prevented duplicate notification modals after cold-start taps.
    • Improved handling of stale incident data and weather alert requests.
  • UI Improvements
    • Adjusted drawer sizing and weather-alert filter layout.

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The pull request adds incident command models, API/state handling, a command tab, destination POI routing and map selection, notification tap deduplication, localized strings, and related weather-alert and layout adjustments with corresponding tests.

Changes

Incident command

Layer / File(s) Summary
Incident command contracts
src/models/v4/incidentCommand/*
Incident command enums, resource view models, and nullable result payloads are added.
Incident command data flow
src/api/calls/incidentCommand.ts, src/stores/calls/incident-command-store.ts, src/stores/calls/__tests__/*
A typed API helper and Zustand store fetch resource incident views, pass the active unit, handle NotFound, loading, errors, stale requests, and reset behavior.
Incident command presentation
src/components/incident-command/*, src/translations/*.json
The command panel renders assignments, incident details, objectives, needs, notes, attachments, contacts, formatted values, and localized states.
Call-detail command integration
src/app/call/[id].tsx, src/app/call/__tests__/*
Call details expose the incident command panel through a new tab and update related test mocks.

Destination routing

Layer / File(s) Summary
Sidebar destination routing
src/components/sidebar/call-sidebar.tsx, src/components/sidebar/__tests__/call-sidebar.test.tsx
Sidebar calls display destination routing when destination coordinates or an address exists, using coordinate-first and address-fallback navigation.
Call-detail destination map
src/app/call/[id].tsx, src/app/call/__tests__/[id].test.tsx
Call details switch between call and destination locations on the map and provide destination routing with validation and toast error handling.

Notification tap deduplication

Layer / File(s) Summary
Unified notification response routing
src/services/push-notification.ts, src/services/__tests__/push-notification.test.ts
Listener and killed-state notification responses share identifier-based deduplication before modal presentation.

Related API and UI adjustments

Layer / File(s) Summary
API and layout adjustments
src/api/weather-alerts/weather-alerts.ts, src/stores/weather-alerts/store.ts, src/app/(app)/_layout.tsx, src/components/weather-alerts/severity-filter-tabs.tsx
Weather-alert requests use encoded path IDs, detail loading clears the selected alert, and drawer and severity-tab layout properties are updated.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CallDetail
  participant IncidentCommandTabPanel
  participant useIncidentCommandStore
  participant getResourceIncidentView
  CallDetail->>IncidentCommandTabPanel: render command tab with callId
  IncidentCommandTabPanel->>useIncidentCommandStore: fetch incident view
  useIncidentCommandStore->>getResourceIncidentView: request resource incident view
  getResourceIncidentView-->>useIncidentCommandStore: return typed result
  useIncidentCommandStore-->>IncidentCommandTabPanel: provide view or state
Loading
sequenceDiagram
  participant CallDetail
  participant SidebarCallCard
  participant Maps
  CallDetail->>Maps: open destination directions
  SidebarCallCard->>Maps: open coordinate directions
  SidebarCallCard->>Maps: open address directions when coordinates are absent
Loading

Possibly related PRs

  • Resgrid/Unit#184: Updates the push-notification flow and related tap-handling behavior.
  • Resgrid/Unit#235: Adds related weather-alert API endpoint construction and retrieval changes.

Suggested reviewers: github-actions

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title is too generic and does not convey the main change in the pull request. Rename the PR to a concise, specific summary of the main change, such as adding incident command and destination routing features.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch develop

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

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

⚠️ Outside diff range comments (1)
src/app/call/[id].tsx (1)

515-574: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

"Call Location" map toggle is a dead control when the call has no valid call/dispatch coordinates.

showingDestination = hasDestinationCoordinates && (mapTarget === 'destination' || !hasCallCoordinates) forces showingDestination to true whenever hasCallCoordinates is false, regardless of mapTarget. But the toggle button pair (lines 563-574) is rendered based solely on hasDestinationCoordinates, so when a call has a valid destination POI but no valid dispatch coordinates, the "Call Location" button is shown, yet pressing it can never flip the map or its own variant styling — a visible no-op control.

🐛 Suggested fix: only show the toggle when there's an actual choice
-            {hasDestinationCoordinates ? (
+            {hasDestinationCoordinates && hasCallCoordinates ? (
               <HStack className="w-full">
                 <Button onPress={() => setMapTarget('call')} variant={showingDestination ? 'outline' : 'solid'} size="sm" className="flex-1 rounded-none" testID="call-detail-map-toggle-call">
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/call/`[id].tsx around lines 515 - 574, Only render the map toggle
buttons when both valid destination coordinates and valid call coordinates
exist, so the controls appear only when switching locations is possible. Update
the condition around the toggle pair near StaticMap, while preserving
destination-only map rendering and existing toggle behavior when both coordinate
sets are available.
🧹 Nitpick comments (8)
src/components/sidebar/__tests__/call-sidebar.test.tsx (1)

128-131: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove the new explicit any types from test mocks.

  • src/components/sidebar/__tests__/call-sidebar.test.tsx#L128-L131: remove the unused props parameter from Navigation.
  • src/components/sidebar/__tests__/call-sidebar.test.tsx#L569-L577: infer or explicitly type the Zustand selector.
  • src/components/sidebar/__tests__/call-sidebar.test.tsx#L585-L593: infer or explicitly type the Zustand selector.
  • src/components/sidebar/__tests__/call-sidebar.test.tsx#L601-L609: infer or explicitly type the Zustand selector.
  • src/components/sidebar/__tests__/call-sidebar.test.tsx#L629-L637: infer or explicitly type the Zustand selector.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/sidebar/__tests__/call-sidebar.test.tsx` around lines 128 -
131, Remove the unused props parameter from the Navigation test mock in
src/components/sidebar/__tests__/call-sidebar.test.tsx:128-131. At
src/components/sidebar/__tests__/call-sidebar.test.tsx:569-577, 585-593,
601-609, and 629-637, remove explicit any usage from the Zustand selector mocks
by allowing inference or applying the appropriate concrete selector type, while
preserving their existing behavior.

Source: Coding guidelines

src/models/v4/incidentCommand/resourceIncidentView.ts (2)

1-1: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use lowercase-hyphenated paths for the new incident-command modules.

  • src/models/v4/incidentCommand/resourceIncidentView.ts#L1-L1: rename the directory to incident-command and the file to resource-incident-view.ts.
  • src/models/v4/incidentCommand/resourceIncidentViewResult.ts#L1-L1: rename the file to resource-incident-view-result.ts in the same renamed directory.
  • src/api/calls/incidentCommand.ts#L1-L1: rename the file to incident-command.ts.
  • Update all affected alias imports consistently.

As per coding guidelines, “Directory and file names should be lowercase and hyphenated.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/models/v4/incidentCommand/resourceIncidentView.ts` at line 1, Rename the
incident-command modules to lowercase-hyphenated paths: change
src/models/v4/incidentCommand/resourceIncidentView.ts to
src/models/v4/incident-command/resource-incident-view.ts,
resourceIncidentViewResult.ts to resource-incident-view-result.ts in the same
directory, and src/api/calls/incidentCommand.ts to incident-command.ts. Update
every affected alias import and reference consistently.

Source: Coding guidelines


45-46: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use the declared enum types for enum-backed fields.

number accepts invalid status/category values despite the explicit wire enums above. Type these as TacticalObjectiveType, TacticalObjectiveStatus, IncidentNeedCategory, and IncidentNeedStatus, retaining their corresponding zero-value defaults.

As per coding guidelines, “Never use any type; use precise types and interfaces with TypeScript strict mode enabled.”

Also applies to: 65-66

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/models/v4/incidentCommand/resourceIncidentView.ts` around lines 45 - 46,
Update the enum-backed fields in the relevant resource view classes to use their
declared types: TacticalObjectiveType and TacticalObjectiveStatus for the
objective fields, and IncidentNeedCategory and IncidentNeedStatus for the
incident-need fields. Preserve each field’s existing zero-value default while
replacing the broad number annotations with these precise enums.

Source: Coding guidelines

src/app/call/__tests__/[id].security.test.tsx (1)

322-322: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Type the icon mock props.

any bypasses the lucide icon prop contract. Use the library’s exported icon-props type, or a narrow local interface containing only the props this mock supports.

As per coding guidelines, “Never use any type; use precise types and interfaces with TypeScript strict mode enabled.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/call/__tests__/`[id].security.test.tsx at line 322, Replace the any
annotation on the ClipboardListIcon mock with the library’s exported icon-props
type, or a narrow interface covering size and the forwarded div props. Preserve
the existing size-based style and data-testid behavior while ensuring the mock
conforms to TypeScript strict typing.

Source: Coding guidelines

src/stores/calls/__tests__/incident-command-store.test.ts (1)

5-5: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Keep store and API fixtures type-safe.

These any casts allow mocked core state and API payloads to drift from their real contracts. Define typed fixture factories for the core-store state and ResourceIncidentViewResult, then reuse them for each scenario.

As per coding guidelines, “Never use any type; use precise types and interfaces with TypeScript strict mode enabled.”

Also applies to: 67-76, 100-104, 133-136, 178-181, 200-201

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/stores/calls/__tests__/incident-command-store.test.ts` at line 5, Replace
the any-based fixtures and mock selectors in the incident command store tests
with typed factory helpers for core-store state and ResourceIncidentViewResult.
Reuse those factories across the scenarios identified in the comment, preserving
each test’s overrides while ensuring mocked state, API payloads, and the select
callback conform to their real contracts.

Source: Coding guidelines

src/stores/calls/incident-command-store.ts (1)

43-48: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Log failed incident-command fetches.

The error is reduced to local UI state but never reported through the shared logger, so request failures are absent from Sentry/operational diagnostics.

As per coding guidelines, “All async operations must have proper try/catch with logging.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/stores/calls/incident-command-store.ts` around lines 43 - 48, Update the
catch block in the incident-command fetch method to log the caught error through
the shared logger before updating state. Preserve the existing view,
error-message, and isLoading assignments while ensuring failed requests are
reported with useful error details.

Source: Coding guidelines

src/app/call/__tests__/[id].test.tsx (1)

1313-1324: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Type the mocked call-detail store.

call: unknown and selector: any let this helper bypass the store selector contract. Define a narrow typed fixture/state interface so routing tests fail at compile time when the call-detail API changes.

As per coding guidelines, “Never use any type; use precise types and interfaces with TypeScript strict mode enabled.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/call/__tests__/`[id].test.tsx around lines 1313 - 1324, Update
mockDetailStore to use the call-detail store’s precise state/interface type
instead of an untyped object, and type its selector parameter against that store
contract rather than any. Replace call: unknown with the appropriate call
fixture type while preserving the existing selector-or-store behavior, so API
changes are caught by TypeScript.

Source: Coding guidelines

src/components/incident-command/__tests__/incident-command-tab-panel.test.tsx (1)

359-383: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider adding a failure-path test for openContactUrl.

Current tests only cover the happy path (openURL resolving). Add a case where Linking.openURL rejects to confirm logger.error is invoked and the app doesn't crash — this also gives a regression hook for the PII-in-context concern raised in the source file.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/components/incident-command/__tests__/incident-command-tab-panel.test.tsx`
around lines 359 - 383, Add a failure-path test alongside the existing dialer
and mail tests for openContactUrl: mock Linking.openURL to reject, trigger a
contact action through IncidentCommandTabPanel, and assert logger.error is
called while the interaction does not throw. Reuse the existing test setup and
restore mocks and unmount the rendered panel afterward.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/incident-command/incident-command-tab-panel.tsx`:
- Around line 81-90: The openContactUrl error logging currently exposes the full
contact URL. Update the logger.error context in openContactUrl to omit url or
include only its scheme, while preserving the existing error logging and failure
handling.

In `@src/components/sidebar/call-sidebar.tsx`:
- Around line 129-140: Update the map-launch handling around
openMapsWithDirections and openMapsWithAddress to inspect each awaited boolean
result and call showLocationAlert() when it is false, while retaining
catch-based alerts for thrown errors. Add regression coverage for both the
coordinate and address branches, verifying alerts occur when the helpers return
false.

In `@src/services/__tests__/push-notification.test.ts`:
- Around line 430-438: Replace the any type on pushNotificationService in the
test setup with the exported type from ../push-notification, while preserving
the dynamically reloaded singleton assignment in beforeEach. Ensure the
declaration remains type-checked against the module’s pushNotificationService
export.

In `@src/stores/calls/incident-command-store.ts`:
- Around line 19-50: Update the incident-command store’s fetchIncidentView and
reset flow to track a request sequence or current call ID. Invalidate the active
request in reset(), and before applying success or error state from
fetchIncidentView, verify that the request is still the latest; ignore
superseded or reset-invalidated results so they cannot overwrite the current
view.

---

Outside diff comments:
In `@src/app/call/`[id].tsx:
- Around line 515-574: Only render the map toggle buttons when both valid
destination coordinates and valid call coordinates exist, so the controls appear
only when switching locations is possible. Update the condition around the
toggle pair near StaticMap, while preserving destination-only map rendering and
existing toggle behavior when both coordinate sets are available.

---

Nitpick comments:
In `@src/app/call/__tests__/`[id].security.test.tsx:
- Line 322: Replace the any annotation on the ClipboardListIcon mock with the
library’s exported icon-props type, or a narrow interface covering size and the
forwarded div props. Preserve the existing size-based style and data-testid
behavior while ensuring the mock conforms to TypeScript strict typing.

In `@src/app/call/__tests__/`[id].test.tsx:
- Around line 1313-1324: Update mockDetailStore to use the call-detail store’s
precise state/interface type instead of an untyped object, and type its selector
parameter against that store contract rather than any. Replace call: unknown
with the appropriate call fixture type while preserving the existing
selector-or-store behavior, so API changes are caught by TypeScript.

In
`@src/components/incident-command/__tests__/incident-command-tab-panel.test.tsx`:
- Around line 359-383: Add a failure-path test alongside the existing dialer and
mail tests for openContactUrl: mock Linking.openURL to reject, trigger a contact
action through IncidentCommandTabPanel, and assert logger.error is called while
the interaction does not throw. Reuse the existing test setup and restore mocks
and unmount the rendered panel afterward.

In `@src/components/sidebar/__tests__/call-sidebar.test.tsx`:
- Around line 128-131: Remove the unused props parameter from the Navigation
test mock in src/components/sidebar/__tests__/call-sidebar.test.tsx:128-131. At
src/components/sidebar/__tests__/call-sidebar.test.tsx:569-577, 585-593,
601-609, and 629-637, remove explicit any usage from the Zustand selector mocks
by allowing inference or applying the appropriate concrete selector type, while
preserving their existing behavior.

In `@src/models/v4/incidentCommand/resourceIncidentView.ts`:
- Line 1: Rename the incident-command modules to lowercase-hyphenated paths:
change src/models/v4/incidentCommand/resourceIncidentView.ts to
src/models/v4/incident-command/resource-incident-view.ts,
resourceIncidentViewResult.ts to resource-incident-view-result.ts in the same
directory, and src/api/calls/incidentCommand.ts to incident-command.ts. Update
every affected alias import and reference consistently.
- Around line 45-46: Update the enum-backed fields in the relevant resource view
classes to use their declared types: TacticalObjectiveType and
TacticalObjectiveStatus for the objective fields, and IncidentNeedCategory and
IncidentNeedStatus for the incident-need fields. Preserve each field’s existing
zero-value default while replacing the broad number annotations with these
precise enums.

In `@src/stores/calls/__tests__/incident-command-store.test.ts`:
- Line 5: Replace the any-based fixtures and mock selectors in the incident
command store tests with typed factory helpers for core-store state and
ResourceIncidentViewResult. Reuse those factories across the scenarios
identified in the comment, preserving each test’s overrides while ensuring
mocked state, API payloads, and the select callback conform to their real
contracts.

In `@src/stores/calls/incident-command-store.ts`:
- Around line 43-48: Update the catch block in the incident-command fetch method
to log the caught error through the shared logger before updating state.
Preserve the existing view, error-message, and isLoading assignments while
ensuring failed requests are reported with useful error details.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: aa1198a9-c506-4805-84d2-0bebf48f1595

📥 Commits

Reviewing files that changed from the base of the PR and between 8496b32 and 345d05e.

📒 Files selected for processing (27)
  • src/api/calls/incidentCommand.ts
  • src/api/weather-alerts/weather-alerts.ts
  • src/app/(app)/_layout.tsx
  • src/app/call/[id].tsx
  • src/app/call/__tests__/[id].security.test.tsx
  • src/app/call/__tests__/[id].test.tsx
  • src/components/incident-command/__tests__/incident-command-tab-panel.test.tsx
  • src/components/incident-command/incident-command-tab-panel.tsx
  • src/components/sidebar/__tests__/call-sidebar.test.tsx
  • src/components/sidebar/call-sidebar.tsx
  • src/components/weather-alerts/severity-filter-tabs.tsx
  • src/models/v4/incidentCommand/resourceIncidentView.ts
  • src/models/v4/incidentCommand/resourceIncidentViewResult.ts
  • src/services/__tests__/push-notification.test.ts
  • src/services/push-notification.ts
  • src/stores/calls/__tests__/incident-command-store.test.ts
  • src/stores/calls/incident-command-store.ts
  • src/stores/weather-alerts/store.ts
  • src/translations/ar.json
  • src/translations/de.json
  • src/translations/en.json
  • src/translations/es.json
  • src/translations/fr.json
  • src/translations/it.json
  • src/translations/pl.json
  • src/translations/sv.json
  • src/translations/uk.json

Comment thread src/components/incident-command/incident-command-tab-panel.tsx
Comment thread src/components/sidebar/call-sidebar.tsx Outdated
Comment on lines +430 to +438
let pushNotificationService: any;

beforeEach(() => {
jest.clearAllMocks();
jest.resetModules();

jest.unmock('../push-notification');
const module = require('../push-notification');
pushNotificationService = module.pushNotificationService;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

file='src/services/__tests__/push-notification.test.ts'

# Show the relevant region with line numbers.
sed -n '410,470p' "$file" | cat -n

# Find the module's exported shape for typing.
rg -n "export (const|function|class|let|type|interface)|module\.exports|export default" src/services/push-notification.ts src/services/__tests__/push-notification.test.ts

Repository: Resgrid/Unit

Length of output: 2911


Remove the any from the reloaded singleton.
Type pushNotificationService from ../push-notification so the test stays type-checked.

Proposed fix
-    let pushNotificationService: any;
+    let pushNotificationService: typeof import('../push-notification').pushNotificationService;
...
-      const module = require('../push-notification');
-      pushNotificationService = module.pushNotificationService;
+      const reloadedModule = require('../push-notification') as typeof import('../push-notification');
+      pushNotificationService = reloadedModule.pushNotificationService;
📝 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
let pushNotificationService: any;
beforeEach(() => {
jest.clearAllMocks();
jest.resetModules();
jest.unmock('../push-notification');
const module = require('../push-notification');
pushNotificationService = module.pushNotificationService;
let pushNotificationService: typeof import('../push-notification').pushNotificationService;
beforeEach(() => {
jest.clearAllMocks();
jest.resetModules();
jest.unmock('../push-notification');
const reloadedModule = require('../push-notification') as typeof import('../push-notification');
pushNotificationService = reloadedModule.pushNotificationService;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/__tests__/push-notification.test.ts` around lines 430 - 438,
Replace the any type on pushNotificationService in the test setup with the
exported type from ../push-notification, while preserving the dynamically
reloaded singleton assignment in beforeEach. Ensure the declaration remains
type-checked against the module’s pushNotificationService export.

Source: Coding guidelines

Comment thread src/stores/calls/incident-command-store.ts Outdated
@Resgrid-Bot

This comment has been minimized.

export const getResourceIncidentView = async (callId: string | number, unitId?: string | number | null) => {
const getResourceIncidentViewApi = createApiEndpoint(`/IncidentCommand/GetResourceIncidentView/${encodeURIComponent(String(callId))}`);
const params = unitId !== undefined && unitId !== null && unitId !== '' ? { unitId } : undefined;
const response = await getResourceIncidentViewApi.get<ResourceIncidentViewResult>(params);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

Unhandled rejection in getResourceIncidentView — the awaited getResourceIncidentViewApi.get call has no try/catch, violating Rule 1 which requires every awaited async operation to be guarded. Wrap the await in a try/catch, log the failure with context (callId, err), and rethrow.

Kody rule violation: Handle async operations with proper error handling

Prompt for LLM

File src/api/calls/incidentCommand.ts:

Line 16:

Unhandled rejection in `getResourceIncidentView` — the awaited `getResourceIncidentViewApi.get` call has no try/catch, violating Rule 1 which requires every awaited async operation to be guarded. Wrap the await in a try/catch, log the failure with context (`callId`, `err`), and rethrow.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

export const getResourceIncidentView = async (callId: string | number, unitId?: string | number | null) => {
const getResourceIncidentViewApi = createApiEndpoint(`/IncidentCommand/GetResourceIncidentView/${encodeURIComponent(String(callId))}`);
const params = unitId !== undefined && unitId !== null && unitId !== '' ? { unitId } : undefined;
const response = await getResourceIncidentViewApi.get<ResourceIncidentViewResult>(params);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

Missing error handling around the network call — Rule 30 requires external/network calls to be wrapped in try/catch with context added and mapped to application-level errors. Wrap the GET in try/catch, log the operation name and identifiers (callId, unitId), and rethrow or map to a domain error such as ApiError.

Kody rule violation: Add try-catch blocks for external calls

Prompt for LLM

File src/api/calls/incidentCommand.ts:

Line 16:

Missing error handling around the network call — Rule 30 requires external/network calls to be wrapped in try/catch with context added and mapped to application-level errors. Wrap the GET in try/catch, log the operation name and identifiers (`callId`, `unitId`), and rethrow or map to a domain error such as `ApiError`.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

* assignment (MyAssignment). When no unit id is available the endpoint is
* called without it and the server resolves no assignment.
*/
export const getResourceIncidentView = async (callId: string | number, unitId?: string | number | null) => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules low

Missing JSDoc Promise and error annotations on getResourceIncidentView — Rule 23 requires async functions to document the resolve value and rejection conditions. Add @returns {Promise<ResourceIncidentViewResult>} and @throws {Error} when the API request fails or the response is invalid. to the existing JSDoc block.

Kody rule violation: Document async/Promise behavior and errors

Prompt for LLM

File src/api/calls/incidentCommand.ts:

Line 13:

Missing JSDoc Promise and error annotations on `getResourceIncidentView` — Rule 23 requires async functions to document the resolve value and rejection conditions. Add `@returns {Promise<ResourceIncidentViewResult>}` and `@throws {Error} when the API request fails or the response is invalid.` to the existing JSDoc block.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

Comment thread src/app/call/[id].tsx Outdated
<ButtonIcon as={MapPinIcon} />
<ButtonText className="text-xs">{t('call_detail.call_location')}</ButtonText>
</Button>
<Button onPress={() => setMapTarget('destination')} variant={showingDestination ? 'solid' : 'outline'} size="sm" className="flex-1 rounded-none" testID="call-detail-map-toggle-destination">

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

Inline arrow function in the JSX onPress prop creates a new function instance on every render, causing unnecessary re-renders of child components. Extract the handler as a stable callback via useCallback and pass it directly as onPress.

Kody rule violation: Avoid using .bind() or arrow functions in JSX props

Prompt for LLM

File src/app/call/[id].tsx:

Line 569:

Inline arrow function in the JSX `onPress` prop creates a new function instance on every render, causing unnecessary re-renders of child components. Extract the handler as a stable callback via `useCallback` and pass it directly as `onPress`.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

} catch (error) {
logger.error({
message: 'Failed to open contact link',
context: { error, url },

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

PII exposure in logs — the url field in the structured log context contains raw tel:${contact.Phone} or mailto:${contact.Email} URLs, leaking phone numbers and email addresses. Redact or hash the personal-data portion of the URL before logging, emitting only the scheme or a hashed identifier.

Kody rule violation: Mask PII and secrets in logs

Prompt for LLM

File src/components/incident-command/incident-command-tab-panel.tsx:

Line 87:

PII exposure in logs — the `url` field in the structured log context contains raw `tel:${contact.Phone}` or `mailto:${contact.Email}` URLs, leaking phone numbers and email addresses. Redact or hash the personal-data portion of the URL before logging, emitting only the scheme or a hashed identifier.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

} catch (error) {
logger.error({
message: 'Failed to open contact link',
context: { error, url },

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

PII exposure in diagnostic output — the url field in the structured log context contains unredacted phone numbers and email addresses via tel:/mailto: URLs, violating GDPR data minimization. Strip or mask the personal-data portion of the URL before logging.

Kody rule violation: Redact PII in logs and metrics by default

Prompt for LLM

File src/components/incident-command/incident-command-tab-panel.tsx:

Line 87:

PII exposure in diagnostic output — the `url` field in the structured log context contains unredacted phone numbers and email addresses via `tel:`/`mailto:` URLs, violating GDPR data minimization. Strip or mask the personal-data portion of the URL before logging.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

{contact.Email ? (
<Pressable onPress={() => openContactUrl(`mailto:${contact.Email}`)} testID={`${testID}-email`}>
<HStack className="mt-1 items-center">
<MailIcon size={14} color="#3B82F6" />

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules low

Duplicated hardcoded hex literal #3B82F6 — the color is inline in both icon declarations and should be defined once as a module-level constant. Extract const LINK_ICON_COLOR = '#3B82F6' and reference it in both places.

Kody rule violation: Centralize string constants

Prompt for LLM

File src/components/incident-command/incident-command-tab-panel.tsx:

Line 114:

Duplicated hardcoded hex literal `#3B82F6` — the color is inline in both icon declarations and should be defined once as a module-level constant. Extract `const LINK_ICON_COLOR = '#3B82F6'` and reference it in both places.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

{contact.Phone ? (
<Pressable onPress={() => openContactUrl(`tel:${contact.Phone}`)} testID={`${testID}-phone`}>
<HStack className="mt-1 items-center">
<PhoneIcon size={14} color="#3B82F6" />

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules low

Unnamed numeric literal 14 used as an icon size degrades maintainability. Extract a module-level constant const ICON_SIZE = 14 and replace the inline value.

Kody rule violation: Replace magic numbers with named constants

Prompt for LLM

File src/components/incident-command/incident-command-tab-panel.tsx:

Line 106:

Unnamed numeric literal `14` used as an icon size degrades maintainability. Extract a module-level constant `const ICON_SIZE = 14` and replace the inline value.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

Comment on lines +582 to +590
mockUseCoreStore.mockImplementation((selector: any) => typeof selector === 'function' ? selector({
activeCall: callWithAddressOnly,
activePriority: mockPriority,
setActiveCall: mockSetActiveCall,
}) : {
activeCall: callWithAddressOnly,
activePriority: mockPriority,
setActiveCall: mockSetActiveCall,
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules low

Duplicated test setup — the mockUseCoreStore.mockImplementation(...) block is repeated seven times (lines 582–590, 620–628, 636–644, 652–660, 680–688, 699–707, 734–742) with only the activeCall value changing. Extract a helper like setupMockStore(call) and call it with the desired call object to reduce drift risk.

Kody rule violation: Extract duplicated logic into functions

Prompt for LLM

File src/components/sidebar/__tests__/call-sidebar.test.tsx:

Line 582 to 590:

Duplicated test setup — the `mockUseCoreStore.mockImplementation(...)` block is repeated seven times (lines 582–590, 620–628, 636–644, 652–660, 680–688, 699–707, 734–742) with only the `activeCall` value changing. Extract a helper like `setupMockStore(call)` and call it with the desired call object to reduce drift risk.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

};

// Check if the call carries a routable destination POI (coordinates or address)
const hasDestinationData = (call: typeof activeCall) => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Bug high

The (0,0) no-data sentinel passes the != null check in hasDestinationData and handleDestinationDirections, causing openMapsWithDirections(0, 0, name) to open navigation to null island (Gulf of Guinea) when the server sends DestinationLatitude=0 / DestinationLongitude=0. Mirror the exclusion already applied at src/app/call/[id].tsx line 519 by adding the (0,0) guard in both functions.

const hasDestinationData = (call: typeof activeCall) => {
    if (!call) return false;
    const hasCoordinates =
      call.DestinationLatitude != null &&
      call.DestinationLongitude != null &&
      (call.DestinationLatitude !== 0 || call.DestinationLongitude !== 0);
    const hasAddress = !!call.DestinationAddress && call.DestinationAddress.trim() !== '';
    return hasCoordinates || hasAddress;
  };

// and in handleDestinationDirections:
// if (latitude != null && longitude != null && (latitude !== 0 || longitude !== 0)) {
Prompt for LLM

File src/components/sidebar/call-sidebar.tsx:

Line 119:

The (0,0) no-data sentinel passes the `!= null` check in `hasDestinationData` and `handleDestinationDirections`, causing `openMapsWithDirections(0, 0, name)` to open navigation to null island (Gulf of Guinea) when the server sends `DestinationLatitude=0` / `DestinationLongitude=0`. Mirror the exclusion already applied at `src/app/call/[id].tsx` line 519 by adding the (0,0) guard in both functions.

Suggested Code:

const hasDestinationData = (call: typeof activeCall) => {
    if (!call) return false;
    const hasCoordinates =
      call.DestinationLatitude != null &&
      call.DestinationLongitude != null &&
      (call.DestinationLatitude !== 0 || call.DestinationLongitude !== 0);
    const hasAddress = !!call.DestinationAddress && call.DestinationAddress.trim() !== '';
    return hasCoordinates || hasAddress;
  };

// and in handleDestinationDirections:
// if (latitude != null && longitude != null && (latitude !== 0 || longitude !== 0)) {

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

Comment on lines +141 to +143
} catch {
showLocationAlert();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

Silent error swallowing — the catch block uses optional catch binding (catch {) without capturing the error object, discarding exception details needed to debug failed map-direction opens. Capture the error variable (catch (error)) and log it with structured context before calling showLocationAlert.

Kody rule violation: Avoid empty catch blocks

Prompt for LLM

File src/components/sidebar/call-sidebar.tsx:

Line 141 to 143:

Silent error swallowing — the catch block uses optional catch binding (`catch {`) without capturing the error object, discarding exception details needed to debug failed map-direction opens. Capture the error variable (`catch (error)`) and log it with structured context before calling `showLocationAlert`.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

public DepartmentId: number = 0;
public CallId: number = 0;
public Name: string = '';
public ObjectiveType: number = 0;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules low

Type mismatch — ObjectiveType is typed as number but a dedicated TacticalObjectiveType enum is defined in the same file (lines 4–8) and already encodes the valid values. Change the type to TacticalObjectiveType to restore compile-time guarantees for downstream consumers.

Kody rule violation: Derive TypeScript types from validation schemas

Prompt for LLM

File src/models/v4/incidentCommand/resourceIncidentView.ts:

Line 45:

Type mismatch — `ObjectiveType` is typed as `number` but a dedicated `TacticalObjectiveType` enum is defined in the same file (lines 4–8) and already encodes the valid values. Change the type to `TacticalObjectiveType` to restore compile-time guarantees for downstream consumers.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

* getLastNotificationResponseAsync — dedupe by identifier so it shows once while
* distinct notifications still each get handled.
*/
private handledResponseIds = new Set<string>();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Performance high

Unbounded memory growth in the PushNotification singleton — the handledResponseIds Set accumulates an entry on every notification tap (line 241) and is never cleared, not even in cleanup() (lines 470–485). Clear handledResponseIds in cleanup() via this.handledResponseIds.clear().

public cleanup(): void {
    if (this.notificationListener) {
      this.notificationListener.remove();
      this.notificationListener = null;
    }

    if (this.responseListener) {
      this.responseListener.remove();
      this.responseListener = null;
    }

    if (this.notifeeForegroundUnsubscribe) {
      this.notifeeForegroundUnsubscribe();
      this.notifeeForegroundUnsubscribe = null;
    }

    this.handledResponseIds.clear();
  }
Prompt for LLM

File src/services/push-notification.ts:

Line 83:

Unbounded memory growth in the `PushNotification` singleton — the `handledResponseIds` Set accumulates an entry on every notification tap (line 241) and is never cleared, not even in `cleanup()` (lines 470–485). Clear `handledResponseIds` in `cleanup()` via `this.handledResponseIds.clear()`.

Suggested Code:

public cleanup(): void {
    if (this.notificationListener) {
      this.notificationListener.remove();
      this.notificationListener = null;
    }

    if (this.responseListener) {
      this.responseListener.remove();
      this.responseListener = null;
    }

    if (this.notifeeForegroundUnsubscribe) {
      this.notifeeForegroundUnsubscribe();
      this.notifeeForegroundUnsubscribe = null;
    }

    this.handledResponseIds.clear();
  }

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

Comment on lines +31 to +33
fetchIncidentView: async (callId: string) => {
const seq = ++requestSeq;
set({ isLoading: true, error: null });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Bug high

Stale view data leaks across calls because fetchIncidentView sets isLoading and error but never clears the previous view in the useIncidentCommandStore singleton, causing IncidentCommandTabPanel to render the previous incident's commander, objectives, needs, important-information, and notes for one committed frame before the spinner appears. Clear view at the start of fetchIncidentView by setting view: null.

fetchIncidentView: async (callId: string) => {
    const seq = ++requestSeq;
    set({ isLoading: true, error: null, view: null });
Prompt for LLM

File src/stores/calls/incident-command-store.ts:

Line 31 to 33:

Stale view data leaks across calls because `fetchIncidentView` sets `isLoading` and `error` but never clears the previous `view` in the `useIncidentCommandStore` singleton, causing `IncidentCommandTabPanel` to render the previous incident's commander, objectives, needs, important-information, and notes for one committed frame before the spinner appears. Clear `view` at the start of `fetchIncidentView` by setting `view: null`.

Suggested Code:

fetchIncidentView: async (callId: string) => {
    const seq = ++requestSeq;
    set({ isLoading: true, error: null, view: null });

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

const result = await getResourceIncidentView(callId, activeUnitId ?? undefined);
if (seq !== requestSeq) return;

if (result && result.Data && result.Status !== 'NotFound') {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules low

Magic string 'NotFound' compared against result.Status — status values represent a finite set that should be expressed as an enum or named constant for type safety and discoverability. Define a shared constant or enum and compare with result.Status !== IncidentStatus.NotFound.

Kody rule violation: Use enums instead of magic strings

Prompt for LLM

File src/stores/calls/incident-command-store.ts:

Line 39:

Magic string `'NotFound'` compared against `result.Status` — status values represent a finite set that should be expressed as an enum or named constant for type safety and discoverability. Define a shared constant or enum and compare with `result.Status !== IncidentStatus.NotFound`.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

@Resgrid-Bot

Resgrid-Bot commented Jul 22, 2026

Copy link
Copy Markdown

Code Review Completed! 🔥

The code review was successfully completed based on your current configurations.

Kody Guide: Usage and Configuration
Interacting with Kody
  • Request a Review: Ask Kody to review your PR manually by adding a comment with the @kody start-review command at the root of your PR.

  • Validate Business Logic: Ask Kody to validate your code against business rules by adding a comment with the @kody -v business-logic command.

  • Provide Feedback: Help Kody learn and improve by reacting to its comments with a 👍 for helpful suggestions or a 👎 if improvements are needed.

Current Kody Configuration
Review Options

The following review options are enabled or disabled:

Options Enabled
Bug
Performance
Security
Business Logic

Access your configuration settings here.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (3)
src/stores/calls/__tests__/incident-command-store.test.ts (1)

199-306: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Test state-transition logic is correct. Each case aligns with the store's requestSeq supersession guard (synchronous clear, superseded-result ignore, post-reset success/error ignore).

One guideline nit: the repeated as any casts on mock views and resolved values (e.g., Lines 200, 216-217, 226, 238, 267) sidestep type checking and won't catch drift in the ResourceIncidentViewResult/ResourceIncidentView shape. Prefer typing createMockView() and the mocked resolutions to the real result type.

As per coding guidelines: "Never use any type; use precise types and interfaces with TypeScript strict mode enabled".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/stores/calls/__tests__/incident-command-store.test.ts` around lines 199 -
306, Replace the any casts throughout the incident-command fetch tests with the
actual ResourceIncidentView and ResourceIncidentViewResult types. Type
createMockView and the mocked promise resolve values used by fetchIncidentView,
including the superseded, reset-success, and reset-error cases, so TypeScript
validates the real response shape without weakening strict checking.

Source: Coding guidelines

src/components/sidebar/__tests__/call-sidebar.test.tsx (2)

556-606: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add unmount() cleanup to new tests.

None of these new test cases call unmount() after their assertions, per the repo's testing guideline for co-located test files.

As per coding guidelines, "Always call unmount() in tests to clean up after assertions."

✅ Example fix
   render(<SidebarCallCard />);
   fireEvent.press(screen.getByTestId('map-pin-icon'));
   await new Promise(resolve => setTimeout(resolve, 0));
   expect(mockAlert.alert).toHaveBeenCalledWith(...);
+  unmount();
 });

Also applies to: 697-809

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/sidebar/__tests__/call-sidebar.test.tsx` around lines 556 -
606, Update the new tests for map directions and address handling, along with
the additional tests in the referenced range, to capture the render result and
call unmount() after assertions. Preserve each test’s existing setup, async
behavior, and expectations while ensuring every rendered SidebarCallCard is
cleaned up.

Source: Coding guidelines


557-572: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use waitFor for these async assertions. Import waitFor from @testing-library/react-native and replace the setTimeout(0) flush in these cases with await waitFor(...) (including the destination routing checks).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/sidebar/__tests__/call-sidebar.test.tsx` around lines 557 -
572, Update the asynchronous assertions in the SidebarCallCard tests, including
the destination routing checks, to import and use waitFor from
`@testing-library/react-native` instead of manually flushing with setTimeout(0).
Place the relevant expectations inside await waitFor callbacks while preserving
their existing assertion behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/components/sidebar/__tests__/call-sidebar.test.tsx`:
- Around line 556-606: Update the new tests for map directions and address
handling, along with the additional tests in the referenced range, to capture
the render result and call unmount() after assertions. Preserve each test’s
existing setup, async behavior, and expectations while ensuring every rendered
SidebarCallCard is cleaned up.
- Around line 557-572: Update the asynchronous assertions in the SidebarCallCard
tests, including the destination routing checks, to import and use waitFor from
`@testing-library/react-native` instead of manually flushing with setTimeout(0).
Place the relevant expectations inside await waitFor callbacks while preserving
their existing assertion behavior.

In `@src/stores/calls/__tests__/incident-command-store.test.ts`:
- Around line 199-306: Replace the any casts throughout the incident-command
fetch tests with the actual ResourceIncidentView and ResourceIncidentViewResult
types. Type createMockView and the mocked promise resolve values used by
fetchIncidentView, including the superseded, reset-success, and reset-error
cases, so TypeScript validates the real response shape without weakening strict
checking.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b15724c2-fec1-4d19-a24e-605f50b84e22

📥 Commits

Reviewing files that changed from the base of the PR and between 345d05e and b850227.

📒 Files selected for processing (9)
  • src/app/call/[id].tsx
  • src/components/incident-command/incident-command-tab-panel.tsx
  • src/components/sidebar/__tests__/call-sidebar.test.tsx
  • src/components/sidebar/call-sidebar.tsx
  • src/models/v4/incidentCommand/resourceIncidentView.ts
  • src/models/v4/incidentCommand/resourceIncidentViewResult.ts
  • src/services/__tests__/push-notification.test.ts
  • src/stores/calls/__tests__/incident-command-store.test.ts
  • src/stores/calls/incident-command-store.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • src/models/v4/incidentCommand/resourceIncidentViewResult.ts
  • src/stores/calls/incident-command-store.ts
  • src/services/tests/push-notification.test.ts
  • src/components/sidebar/call-sidebar.tsx
  • src/app/call/[id].tsx
  • src/components/incident-command/incident-command-tab-panel.tsx

const hasDestinationData = (call: typeof activeCall) => {
if (!call) return false;
// (0,0) is the server's no-data sentinel, not a real destination
const hasCoordinates = call.DestinationLatitude != null && call.DestinationLongitude != null && (call.DestinationLatitude !== 0 || call.DestinationLongitude !== 0);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules low

Duplicate null-plus-(0,0)-sentinel coordinate validation at src/components/sidebar/call-sidebar.tsx:137 risks divergence if the validation logic changes. Extract a shared helper such as isValidCoordinate(lat, lng) to centralize the null and sentinel checks for both hasDestinationData and the navigation handler.

Kody rule violation: Extract duplicated logic into functions

Prompt for LLM

File src/components/sidebar/call-sidebar.tsx:

Line 122:

Duplicate null-plus-(0,0)-sentinel coordinate validation at `src/components/sidebar/call-sidebar.tsx:137` risks divergence if the validation logic changes. Extract a shared helper such as `isValidCoordinate(lat, lng)` to centralize the null and sentinel checks for both `hasDestinationData` and the navigation handler.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

@ucswift

ucswift commented Jul 22, 2026

Copy link
Copy Markdown
Member Author

Approve

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

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 15ae7f1 into master Jul 22, 2026
19 of 20 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