Conversation
📝 WalkthroughWalkthroughThe incident command panel now displays authorized incident chat channels and ICS role contacts. Contacts with Resgrid accounts can start direct messages. New models, channel types, role labels, translations, and icon test mocks support the feature. ChangesIncident command chat
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant IncidentContact
participant IncidentChatSection
participant useDirectMessage
participant ChatRoute
IncidentContact->>IncidentChatSection: Select contact with UserId
IncidentChatSection->>useDirectMessage: openDirectMessage(UserId)
useDirectMessage->>ChatRoute: Navigate to direct-message channel
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (2)
src/components/incident-command/incident-chat-section.tsx (1)
37-37: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUse a semantic theme token for chat icons.
The hardcoded
#3b82f6value bypasses the app color system. Use the project semantic icon color token and verify its light- and dark-mode values.
src/components/incident-command/incident-chat-section.tsx#L37-L37: Replace the hardcoded channel icon color.src/components/incident-command/incident-chat-section.tsx#L65-L65: Replace the hardcoded direct-message icon color.As per coding guidelines, use semantic color tokens from Tailwind config, not hardcoded hex values, and support dark and light mode.
🤖 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/incident-chat-section.tsx` at line 37, Replace the hardcoded `#3b82f6` colors on the channel and direct-message IconComponent instances in src/components/incident-command/incident-chat-section.tsx at lines 37-37 and 65-65 with the project’s semantic icon color token, verifying the token provides appropriate light- and dark-mode values.Source: Coding guidelines
src/components/incident-command/incident-command-tab-panel.tsx (1)
133-133: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExtract the message handler from JSX.
Line 133 creates an anonymous event handler. Define a named handler and pass it to
onPress. This follows the component performance guidance and keeps pending-state handling in one place.As per coding guidelines, avoid anonymous functions in event handlers to prevent re-renders.
🤖 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/incident-command-tab-panel.tsx` at line 133, Extract the inline onPress callback in the incident command tab panel into a named message handler that invokes openDirectMessage with contact.UserId, then pass that handler to Pressable. Keep the existing testID and pending-state behavior unchanged.Source: Coding guidelines
🤖 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/__tests__/incident-command-tab-panel.test.tsx`:
- Around line 29-45: Add incident-chat branch coverage in the incident command
tab panel tests: mock the missing Radio icon before dispatch-channel visibility
tests, then cover server-provided channel visibility, rendering the IsFrozen
banner, and message-button behavior for contacts both with and without UserId.
In `@src/components/incident-command/incident-command-tab-panel.tsx`:
- Line 135: Update the MessageCircle icon in the incident command tab panel to
use the project’s semantic theme color token instead of hardcoded `#3B82F6`,
resolving it through the existing light/dark theme mechanism while preserving
the icon’s current appearance and sizing.
- Line 21: Update the IncidentChatSection import in the incident command tab
panel to use the project-configured tsconfig path alias instead of a relative
path, preserving the existing symbol and behavior.
In `@src/components/incident-command/incident-role-names.ts`:
- Around line 10-45: The ICS_ROLE_NAMES values are user-visible English strings
and must be localized. Replace each role title with its corresponding
translation key, update getIncidentRoleName to resolve known keys through the
provided TFunction t, and add every key to all locale translation files,
explicitly supplying the standard English title where applicable; preserve the
existing generic fallback for unknown role types.
In `@src/hooks/use-direct-message.ts`:
- Around line 12-30: Make useDirectMessage’s opening flow single-flight across
all hook instances by using a shared React Query v5 mutation or module-level
lock, and guard calls while one request is pending. Update every direct-message
Pressable, including ContactRow consumers, to disable while the shared operation
is pending; preserve the existing unavailable-user handling and only allow
router.push after the guarded request completes.
In `@src/translations/ar.json`:
- Around line 568-588: Translate the newly added incident-command and messaging
keys, including chat labels, hints, errors, contact text, reach_directly,
role_generic, and send_message, in every affected locale. Update
src/translations/ar.json lines 568-588 and 626-630 in Arabic;
src/translations/de.json lines 568-588 and 626-630 in German;
src/translations/es.json lines 568-588 and 626-630 in Spanish;
src/translations/fr.json lines 568-588 and 626-630 in French; and
src/translations/it.json lines 568-588 and 626-630 in Italian, preserving the
existing keys and interpolation placeholders.
In `@src/translations/pl.json`:
- Around line 568-588: Translate all English user-facing incident-chat values
while preserving the existing keys and interpolation placeholders: update
src/translations/pl.json lines 568-588 and 626-630, src/translations/sv.json
lines 568-588 and 626-630, and src/translations/uk.json lines 568-588 and
626-630. Localize the channel labels, hints, frozen-state and direct-message
text, direct-contact text, generic role, and message action in each language.
---
Nitpick comments:
In `@src/components/incident-command/incident-chat-section.tsx`:
- Line 37: Replace the hardcoded `#3b82f6` colors on the channel and
direct-message IconComponent instances in
src/components/incident-command/incident-chat-section.tsx at lines 37-37 and
65-65 with the project’s semantic icon color token, verifying the token provides
appropriate light- and dark-mode values.
In `@src/components/incident-command/incident-command-tab-panel.tsx`:
- Line 133: Extract the inline onPress callback in the incident command tab
panel into a named message handler that invokes openDirectMessage with
contact.UserId, then pass that handler to Pressable. Keep the existing testID
and pending-state behavior unchanged.
🪄 Autofix
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 Plus
Run ID: 72b647e5-3c7c-4044-9481-fdce17b8d4b7
📒 Files selected for processing (16)
src/components/incident-command/__tests__/incident-command-tab-panel.test.tsxsrc/components/incident-command/incident-chat-section.tsxsrc/components/incident-command/incident-command-tab-panel.tsxsrc/components/incident-command/incident-role-names.tssrc/hooks/use-direct-message.tssrc/models/v4/chat/chatEnums.tssrc/models/v4/incidentCommand/resourceIncidentView.tssrc/translations/ar.jsonsrc/translations/de.jsonsrc/translations/en.jsonsrc/translations/es.jsonsrc/translations/fr.jsonsrc/translations/it.jsonsrc/translations/pl.jsonsrc/translations/sv.jsonsrc/translations/uk.json
| // Icons used by the incident chat section rendered inside the panel. | ||
| MessageCircle: () => { | ||
| const { View } = require('react-native'); | ||
| return <View testID="message-icon" />; | ||
| }, | ||
| MessagesSquare: () => { | ||
| const { View } = require('react-native'); | ||
| return <View testID="messages-icon" />; | ||
| }, | ||
| ShieldCheck: () => { | ||
| const { View } = require('react-native'); | ||
| return <View testID="shield-icon" />; | ||
| }, | ||
| Users: () => { | ||
| const { View } = require('react-native'); | ||
| return <View testID="users-icon" />; | ||
| }, |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -eu
file="src/components/incident-command/__tests__/incident-command-tab-panel.test.tsx"
printf '%s\n' '--- file outline ---'
ast-grep outline "$file" || true
printf '%s\n' '--- test file ---'
cat -n "$file"
printf '%s\n' '--- related files ---'
fd -i 'incident-command' src | sort
printf '%s\n' '--- relevant symbols and test identifiers ---'
rg -n "authorized|frozen|UserId|message|IncidentCommandTabPanel|incident chat|channel" src/components/incident-command src 2>/dev/null | head -300Repository: Resgrid/Unit
Length of output: 50368
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- tab panel chat integration ---'
sed -n '1,340p' src/components/incident-command/incident-command-tab-panel.tsx
printf '%s\n' '--- incident chat section ---'
cat -n src/components/incident-command/incident-chat-section.tsx
printf '%s\n' '--- incident chat models ---'
cat -n src/models/v4/chat/chatModels.ts
printf '%s\n' '--- incident view chat-related model fields ---'
rg -n -C 8 "Chat|Frozen|Contact|Role|ChannelId|IncidentChat" src/models/v4/incidentCommand src/components/incident-command
printf '%s\n' '--- direct message hook ---'
cat -n src/hooks/use-direct-message.tsRepository: Resgrid/Unit
Length of output: 50368
🏁 Script executed:
#!/bin/bash
set -eu
python3 - <<'PY'
from pathlib import Path
import re
impl = Path("src/components/incident-command/incident-chat-section.tsx").read_text()
test = Path("src/components/incident-command/__tests__/incident-command-tab-panel.test.tsx").read_text()
channel_ids = re.findall(r"channelId=\{chat\?\.(\w+)\}", impl)
channel_test_ids = re.findall(r'testID=\`\$\{testID\}-(\w+)\`', impl)
branch_markers = {
"authorized_channel_visibility": "if (!channelId)" in impl and "channelId={chat?.IncidentChannelId}" in impl,
"frozen_banner": "chat?.IsFrozen" in impl and "${testID}-frozen" in impl,
"message_requires_user_id": "{contact.UserId ?" in impl and "${testID}-message" in impl,
}
mocked_icons = set(re.findall(r"^\s{2}(\w+):\s*\(\)", test, re.MULTILINE))
used_icons = set(re.findall(r"icon=\{(\w+)\}", impl))
print("channel fields:", channel_ids)
print("channel test-id suffixes:", channel_test_ids)
print("branch markers:", branch_markers)
print("incident-chat test references:", sorted(set(re.findall(r"incident-command-chat(?:-\w+)*", test))))
print("used chat icons:", sorted(used_icons))
print("mocked icons:", sorted(mocked_icons))
print("unmocked used icons:", sorted(used_icons - mocked_icons))
PYRepository: Resgrid/Unit
Length of output: 675
Add incident-chat branch coverage.
Add tests for server-provided channel visibility, the IsFrozen banner, and message-button rendering for contacts with and without UserId. Add the missing Radio mock before testing dispatch-channel visibility.
🤖 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 29 - 45, Add incident-chat branch coverage in the incident command
tab panel tests: mock the missing Radio icon before dispatch-channel visibility
tests, then cover server-provided channel visibility, rendering the IsFrozen
banner, and message-button behavior for contacts both with and without UserId.
Source: Coding guidelines
| import { type IncidentContactInfo, IncidentNeedStatus, type TacticalObjective, TacticalObjectiveStatus } from '@/models/v4/incidentCommand/resourceIncidentView'; | ||
| import { useIncidentCommandStore } from '@/stores/calls/incident-command-store'; | ||
|
|
||
| import { IncidentChatSection } from './incident-chat-section'; |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Use the configured path alias.
Line 21 adds a relative import for IncidentChatSection. Use the project alias instead.
Proposed fix
-import { IncidentChatSection } from './incident-chat-section';
+import { IncidentChatSection } from '`@/components/incident-command/incident-chat-section`';As per coding guidelines, source files must use path aliases from tsconfig.json instead of relative paths.
📝 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.
| import { IncidentChatSection } from './incident-chat-section'; | |
| import { IncidentChatSection } from '`@/components/incident-command/incident-chat-section`'; |
🤖 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/incident-command-tab-panel.tsx` at line 21,
Update the IncidentChatSection import in the incident command tab panel to use
the project-configured tsconfig path alias instead of a relative path,
preserving the existing symbol and behavior.
Source: Coding guidelines
| {contact.UserId ? ( | ||
| <Pressable onPress={() => void openDirectMessage(contact.UserId)} testID={`${testID}-message`}> | ||
| <HStack className="mt-1 items-center"> | ||
| <MessageCircle size={14} color="#3B82F6" /> |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Use a semantic theme color for the new icon.
Line 135 introduces hardcoded #3B82F6. Resolve the icon color from a semantic theme token so the action supports the light and dark palettes and the project contrast rules.
As per coding guidelines, use semantic color tokens instead of hardcoded hex values and support both light and dark mode.
🤖 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/incident-command-tab-panel.tsx` at line 135,
Update the MessageCircle icon in the incident command tab panel to use the
project’s semantic theme color token instead of hardcoded `#3B82F6`, resolving it
through the existing light/dark theme mechanism while preserving the icon’s
current appearance and sizing.
Source: Coding guidelines
| const ICS_ROLE_NAMES: Record<number, string> = { | ||
| 0: 'Incident Commander', | ||
| 1: 'Deputy Incident Commander', | ||
| 2: 'Unified Command Member', | ||
| 3: 'Operations Section Chief', | ||
| 4: 'Planning Section Chief', | ||
| 5: 'Logistics Section Chief', | ||
| 6: 'Finance/Admin Section Chief', | ||
| 7: 'Safety Officer', | ||
| 8: 'Liaison Officer', | ||
| 9: 'Public Information Officer', | ||
| 10: 'Staging Area Manager', | ||
| 11: 'Resources Unit Leader', | ||
| 12: 'Situation Unit Leader', | ||
| 13: 'Documentation Unit Leader', | ||
| 14: 'Communications Unit Leader', | ||
| 15: 'Division/Group Supervisor', | ||
| 16: 'Branch Director', | ||
| 17: 'Strike Team/Task Force Leader', | ||
| 18: 'Medical Unit Leader', | ||
| 19: 'Rehab Officer', | ||
| 20: 'Medical Branch Director', | ||
| 21: 'Triage Officer', | ||
| 22: 'Treatment Officer', | ||
| 23: 'Transport Officer', | ||
| 24: 'HazMat Group Supervisor', | ||
| 25: 'Decon Officer', | ||
| 26: 'Entry Team Leader', | ||
| 27: 'Search Group Supervisor', | ||
| 28: 'Air Operations Branch Director', | ||
| 29: 'Shelter/Mass Care Coordinator', | ||
| 30: 'Damage Assessment Lead', | ||
| }; | ||
|
|
||
| /** Display name for an ICS position; falls back to a generic label for a value we don't know yet. */ | ||
| export const getIncidentRoleName = (t: TFunction, roleType: number): string => ICS_ROLE_NAMES[roleType] ?? t('incident_command.role_generic', { role: roleType }); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Localize the ICS role names.
getIncidentRoleName returns these strings as user-visible labels. Non-English users therefore receive English role titles. Map each role type to a translation key, then add that key to every locale. If a locale uses the standard English title, set that title explicitly in that locale.
As per coding guidelines, all user-visible text must be wrapped in t() from react-i18next.
🤖 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/incident-role-names.ts` around lines 10 - 45,
The ICS_ROLE_NAMES values are user-visible English strings and must be
localized. Replace each role title with its corresponding translation key,
update getIncidentRoleName to resolve known keys through the provided TFunction
t, and add every key to all locale translation files, explicitly supplying the
standard English title where applicable; preserve the existing generic fallback
for unknown role types.
Source: Coding guidelines
| * The server dedups on a normalized participant key, so calling this repeatedly for the same person | ||
| * reuses the existing conversation rather than starting a new one — which is what makes it safe to | ||
| * hang a "message" button off every contact on an incident. | ||
| */ | ||
| export const useDirectMessage = () => { | ||
| const { t } = useTranslation(); | ||
| const [isOpening, setIsOpening] = useState(false); | ||
|
|
||
| const openDirectMessage = useCallback( | ||
| async (targetUserId?: string | null) => { | ||
| if (!targetUserId) { | ||
| // External lane leads have a name and phone but no Resgrid account to message. | ||
| useToastStore.getState().showToast('info', t('incident_command.dm_unavailable')); | ||
| return; | ||
| } | ||
|
|
||
| setIsOpening(true); | ||
| try { | ||
| const channel = await createDirectMessage({ TargetUserId: targetUserId }); |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- candidate files ---'
fd -i 'use-direct-message|incident-command-tab-panel|incident-chat-section|chat' src | head -80
printf '%s\n' '--- package/react-query references ---'
rg -n --glob 'package.json' --glob 'src/**/*.{ts,tsx}' '`@tanstack/react-query`|useMutation|createDirectMessage|CreateDirectMessage|isOpening|openDirectMessage' .
printf '%s\n' '--- hook outline ---'
ast-grep outline src/hooks/use-direct-message.ts
printf '%s\n' '--- hook ---'
cat -n src/hooks/use-direct-message.ts
printf '%s\n' '--- caller sections ---'
cat -n src/components/incident-command/incident-command-tab-panel.tsx | sed -n '85,150p'
cat -n src/components/incident-command/incident-chat-section.tsx | sed -n '60,155p'
printf '%s\n' '--- chat API ---'
cat -n src/api/chat/chat.ts | sed -n '1,100p'
printf '%s\n' '--- direct-message-related files ---'
rg -n -i --glob '!node_modules' 'direct.?message|CreateDirectMessage|TargetUserId|participant' src app server api .github 2>/dev/null | head -200Repository: Resgrid/Unit
Length of output: 41358
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- tracked endpoint/idempotency evidence ---'
git ls-files | xargs -r rg -n -i 'CreateDirectMessage|create.?direct.?message|normalized participant|participant.?key|dedup|idempot' -- 2>/dev/null | head -300
printf '%s\n' '--- component declarations and relevant caller context ---'
cat -n src/components/incident-command/incident-command-tab-panel.tsx | sed -n '1,145p'
cat -n src/components/incident-command/incident-chat-section.tsx | sed -n '1,90p'
printf '%s\n' '--- React Query usage ---'
rg -n --glob 'src/**/*.{ts,tsx}' 'useMutation|mutationFn|mutateAsync|isPending|QueryClient|useQuery' src | head -240
printf '%s\n' '--- tests and test setup related to these components ---'
rg -n --glob 'src/components/incident-command/**/*.{ts,tsx}' 'useDirectMessage|message|Pressable|disabled|CreateDirectMessage|router.push' src/components/incident-command
cat -n package.json | sed -n '65,90p'
printf '%s\n' '--- state timing model: two invocations before a React state commit ---'
node - <<'JS'
let isOpening = false;
let requests = 0;
const pendingState = [];
function setIsOpening(value) {
pendingState.push(value);
}
function openDirectMessage() {
if (!isOpening) {
setIsOpening(true);
requests += 1;
}
}
openDirectMessage();
openDirectMessage();
console.log(JSON.stringify({ requests, committedStateBeforeFlush: isOpening, queuedStateUpdates: pendingState }));
JSRepository: Resgrid/Unit
Length of output: 19945
🌐 Web query:
Resgrid CreateDirectMessage endpoint server dedup normalized participant key
💡 Result:
The Resgrid API (v4) architecture utilizes standard REST endpoints and Swagger documentation [1][2]. There is no public, documented API endpoint named CreateDirectMessage that utilizes a "server dedup normalized participant key" in the official Resgrid documentation or public GitHub repositories [1][3][4]. Resgrid's messaging and dispatch systems are generally managed through the Calls, Personnel, and Workflows API areas [2][5]. In the context of Resgrid's internal architecture, developers often work with the Resgrid.Core repository, where services and providers handle logic such as messaging, caching, and worker queue processing [4][6]. The platform employs a Cache-Aside pattern using Redis for performance optimization [6], but specific deduplication logic involving a "normalized participant key" for a "CreateDirectMessage" operation is not an exposed public API feature [1][2]. If you are developing against the Resgrid platform, you should refer to the official Resgrid API Swagger documentation (typically available at https://api.resgrid.com/index.html) to identify the currently supported endpoints for your version [2][7]. For self-hosted deployments, you may inspect the Resgrid.Core source code directly to understand how internal messaging and participant handling are implemented within your specific environment version [4][8].
Citations:
- 1: https://docs.resgrid.com/category/api/
- 2: https://docs.resgrid.com/api/information/
- 3: https://docs.resgrid.com/
- 4: https://resgrid-core.readthedocs.io/
- 5: https://docs.resgrid.com/category/api
- 6: https://github.com/Resgrid/Core/blob/master/CLAUDE.md
- 7: https://github.com/Resgrid/ngxresgridlib
- 8: https://resgrid.com/open-source
Make direct-message opening single-flight.
isOpening is never checked, so two taps can issue two requests and call router.push twice. ContactRow creates a separate hook instance for each row, so a per-hook guard will not cover all buttons. Use a shared React Query v5 mutation or lock, guard pending calls, and disable every direct-message Pressable. Do not rely on server deduplication without a CreateDirectMessage idempotency guarantee.
🤖 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/hooks/use-direct-message.ts` around lines 12 - 30, Make
useDirectMessage’s opening flow single-flight across all hook instances by using
a shared React Query v5 mutation or module-level lock, and guard calls while one
request is pending. Update every direct-message Pressable, including ContactRow
consumers, to disable while the shared operation is pending; preserve the
existing unavailable-user handling and only allow router.push after the guarded
request completes.
Source: Coding guidelines
| "chat": "Chat", | ||
| "chat_frozen": "This incident is closed. Conversations are kept as a point-in-time record — no new messages.", | ||
| "command_channel": "Command chat", | ||
| "command_channel_hint": "Command staff only", | ||
| "commander": "القائد", | ||
| "dispatch_channel": "Dispatch", | ||
| "dispatch_channel_hint": "Reach the dispatch desk", | ||
| "dm_failed": "Couldn't open that conversation.", | ||
| "dm_unavailable": "That contact has no Resgrid account to message.", | ||
| "error": "فشل في تحميل معلومات قيادة الحادث", | ||
| "established": "تم التأسيس", | ||
| "estimated_end": "النهاية المتوقعة", | ||
| "ics_positions": "ICS positions", | ||
| "important_information": "معلومات مهمة", | ||
| "incident_channel": "Incident chat", | ||
| "incident_info": "معلومات الحادث", | ||
| "lane_channel": "Lane chat", | ||
| "leads_channel": "All Leads", | ||
| "leads_channel_hint": "IC and lane leads", | ||
| "linked_need": "احتياج مرتبط", | ||
| "message_person": "Message {{name}}", |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Translate the new incident-command strings in every non-English locale.
The same newly added keys remain in English across these locale files. This creates a mixed-language incident chat and direct-message UI.
src/translations/ar.json#L568-L588: translate the new Arabic incident-command labels, errors, and contact text.src/translations/ar.json#L626-L630: translatereach_directly,role_generic, andsend_messageinto Arabic.src/translations/de.json#L568-L588: translate the new German incident-command labels, errors, and contact text.src/translations/de.json#L626-L630: translatereach_directly,role_generic, andsend_messageinto German.src/translations/es.json#L568-L588: translate the new Spanish incident-command labels, errors, and contact text.src/translations/es.json#L626-L630: translatereach_directly,role_generic, andsend_messageinto Spanish.src/translations/fr.json#L568-L588: translate the new French incident-command labels, errors, and contact text.src/translations/fr.json#L626-L630: translatereach_directly,role_generic, andsend_messageinto French.src/translations/it.json#L568-L588: translate the new Italian incident-command labels, errors, and contact text.src/translations/it.json#L626-L630: translatereach_directly,role_generic, andsend_messageinto Italian.
📍 Affects 5 files
src/translations/ar.json#L568-L588(this comment)src/translations/ar.json#L626-L630src/translations/de.json#L568-L588src/translations/de.json#L626-L630src/translations/es.json#L568-L588src/translations/es.json#L626-L630src/translations/fr.json#L568-L588src/translations/fr.json#L626-L630src/translations/it.json#L568-L588src/translations/it.json#L626-L630
🤖 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/translations/ar.json` around lines 568 - 588, Translate the newly added
incident-command and messaging keys, including chat labels, hints, errors,
contact text, reach_directly, role_generic, and send_message, in every affected
locale. Update src/translations/ar.json lines 568-588 and 626-630 in Arabic;
src/translations/de.json lines 568-588 and 626-630 in German;
src/translations/es.json lines 568-588 and 626-630 in Spanish;
src/translations/fr.json lines 568-588 and 626-630 in French; and
src/translations/it.json lines 568-588 and 626-630 in Italian, preserving the
existing keys and interpolation placeholders.
| "chat": "Chat", | ||
| "chat_frozen": "This incident is closed. Conversations are kept as a point-in-time record — no new messages.", | ||
| "command_channel": "Command chat", | ||
| "command_channel_hint": "Command staff only", | ||
| "commander": "Dowódca", | ||
| "dispatch_channel": "Dispatch", | ||
| "dispatch_channel_hint": "Reach the dispatch desk", | ||
| "dm_failed": "Couldn't open that conversation.", | ||
| "dm_unavailable": "That contact has no Resgrid account to message.", | ||
| "error": "Nie udało się załadować informacji o dowodzeniu incydentem", | ||
| "established": "Ustanowiono", | ||
| "estimated_end": "Szacowane zakończenie", | ||
| "ics_positions": "ICS positions", | ||
| "important_information": "Ważne informacje", | ||
| "incident_channel": "Incident chat", | ||
| "incident_info": "Informacje o incydencie", | ||
| "lane_channel": "Lane chat", | ||
| "leads_channel": "All Leads", | ||
| "leads_channel_hint": "IC and lane leads", | ||
| "linked_need": "Powiązana potrzeba", | ||
| "message_person": "Message {{name}}", |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Translate the new incident-chat strings.
The Polish, Swedish, and Ukrainian dictionaries contain English user-visible strings for the new incident-chat flow. Translate these values before release.
src/translations/pl.json#L568-L588: Translate the channel labels, hints, frozen-state text, and direct-message text.src/translations/pl.json#L626-L630: Translate the direct-contact, generic-role, and message-action text.src/translations/sv.json#L568-L588: Translate the channel labels, hints, frozen-state text, and direct-message text.src/translations/sv.json#L626-L630: Translate the direct-contact, generic-role, and message-action text.src/translations/uk.json#L568-L588: Translate the channel labels, hints, frozen-state text, and direct-message text.src/translations/uk.json#L626-L630: Translate the direct-contact, generic-role, and message-action text.
As per coding guidelines, translation dictionaries must provide localized user-facing text with identical keys across supported languages.
📍 Affects 3 files
src/translations/pl.json#L568-L588(this comment)src/translations/pl.json#L626-L630src/translations/sv.json#L568-L588src/translations/sv.json#L626-L630src/translations/uk.json#L568-L588src/translations/uk.json#L626-L630
🤖 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/translations/pl.json` around lines 568 - 588, Translate all English
user-facing incident-chat values while preserving the existing keys and
interpolation placeholders: update src/translations/pl.json lines 568-588 and
626-630, src/translations/sv.json lines 568-588 and 626-630, and
src/translations/uk.json lines 568-588 and 626-630. Localize the channel labels,
hints, frozen-state and direct-message text, direct-contact text, generic role,
and message action in each language.
Source: Coding guidelines
Code Review Completed! 🔥The code review was successfully completed based on your current configurations. Kody Guide: Usage and ConfigurationInteracting with Kody
Current Kody ConfigurationReview OptionsThe following review options are enabled or disabled:
|
|
Approve |
| const assignment = view.MyAssignment; | ||
| const roles = view.Roles ?? []; | ||
|
|
||
| const handleMessage = useCallback((userId?: string | null) => void openDirectMessage(userId), [openDirectMessage]); |
There was a problem hiding this comment.
Unhandled rejection risk: void openDirectMessage(userId) suppresses the returned Promise, leaving potential rejections unhandled. Replace with await inside try/catch or chain .catch() to surface errors via the logger.
Kody rule violation: Handle async operations with proper error handling
Prompt for LLM
File src/components/incident-command/incident-chat-section.tsx:
Line 88:
Unhandled rejection risk: `void openDirectMessage(userId)` suppresses the returned Promise, leaving potential rejections unhandled. Replace with `await` inside try/catch or chain `.catch()` to surface errors via the logger.
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.
| const hasContacts = roles.length > 0; | ||
|
|
||
| if (!hasChannels && !hasContacts) { | ||
| return null; | ||
| } |
There was a problem hiding this comment.
Orphaned UI element: hasContacts evaluates true when roles contains entries with a null Contact, causing the 'ICS Positions' header to render with zero rows beneath it. Derive hasContacts from roles.some((role) => !!role.Contact) so the header only renders when at least one role has a valid Contact.
const hasContacts = roles.some((role) => !!role.Contact);
if (!hasChannels && !hasContacts) {
return null;
}Prompt for LLM
File src/components/incident-command/incident-chat-section.tsx:
Line 93 to 97:
Orphaned UI element: `hasContacts` evaluates true when `roles` contains entries with a null `Contact`, causing the 'ICS Positions' header to render with zero rows beneath it. Derive `hasContacts` from `roles.some((role) => !!role.Contact)` so the header only renders when at least one role has a valid `Contact`.
Suggested Code:
const hasContacts = roles.some((role) => !!role.Contact);
if (!hasChannels && !hasContacts) {
return null;
}
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.
| const ChannelRow: React.FC<{ label: string; hint?: string | null; icon: React.ElementType; channelId?: string | null; testID: string }> = ({ label, hint, icon: IconComponent, channelId, testID }) => { | ||
| const onPress = useCallback(() => { | ||
| if (channelId) { | ||
| router.push(`/chat/${channelId}`); |
There was a problem hiding this comment.
Inline route path /chat/${channelId} should be centralized for consistency and single-point maintenance. Define a route constant (e.g., ROUTES.CHAT_CHANNEL = (id: string) => \/chat/${id}``) in a central routes module and reference it here.
Kody rule violation: Centralize string constants
Prompt for LLM
File src/components/incident-command/incident-chat-section.tsx:
Line 26:
Inline route path `/chat/${channelId}` should be centralized for consistency and single-point maintenance. Define a route constant (e.g., `ROUTES.CHAT_CHANNEL = (id: string) => \`/chat/${id}\``) in a central routes module and reference it here.
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.
| </VStack> | ||
| {/* External contacts have a name and phone but no Resgrid account, so no 1:1 to open. */} | ||
| {contact.UserId ? ( | ||
| <Pressable onPress={handlePress} className="p-3" hitSlop={8} accessibilityLabel={t('incident_command.message_person', { name: contact.Name })} testID={`${testID}-message`}> |
There was a problem hiding this comment.
Inline numeric literal hitSlop={8} obscures intent and prevents reuse across touch targets. Extract it to a named constant such as DEFAULT_HIT_SLOP = 8.
Kody rule violation: Replace magic numbers with named constants
Prompt for LLM
File src/components/incident-command/incident-chat-section.tsx:
Line 64:
Inline numeric literal `hitSlop={8}` obscures intent and prevents reuse across touch targets. Extract it to a named constant such as `DEFAULT_HIT_SLOP = 8`.
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.
| ); | ||
| }; | ||
|
|
||
| export default IncidentChatSection; |
There was a problem hiding this comment.
Default export reduces clarity and refactoring ergonomics. Remove the default export and import the named export IncidentChatSection directly where needed.
Kody rule violation: Avoid default exports
Prompt for LLM
File src/components/incident-command/incident-chat-section.tsx:
Line 139:
Default export reduces clarity and refactoring ergonomics. Remove the default export and import the named export `IncidentChatSection` directly where needed.
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.
| ) : null} | ||
| {/* External contacts carry a name and phone but no Resgrid account, so there is nobody to message. */} | ||
| {contact.UserId ? ( | ||
| <Pressable onPress={() => void openDirectMessage(contact.UserId)} testID={`${testID}-message`}> |
There was a problem hiding this comment.
Inline arrow function in the onPress JSX prop creates a new function on every render. Move the function definition outside the render method or wrap it with useCallback to avoid unnecessary re-renders.
Kody rule violation: Avoid using .bind() or arrow functions in JSX props
Prompt for LLM
File src/components/incident-command/incident-command-tab-panel.tsx:
Line 133:
Inline arrow function in the `onPress` JSX prop creates a new function on every render. Move the function definition outside the render method or wrap it with `useCallback` to avoid unnecessary re-renders.
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.
| const openDirectMessage = useCallback( | ||
| async (targetUserId?: string | null) => { |
There was a problem hiding this comment.
Missing JSDoc: async function openDirectMessage returns a Promise but lacks documentation for its resolve value and rejection conditions. Add @returns {Promise<void>} and note that rejections are caught internally and surfaced via toast, so no rejection propagates to the caller.
Kody rule violation: Document async/Promise behavior and errors
Prompt for LLM
File src/hooks/use-direct-message.ts:
Line 20 to 21:
Missing JSDoc: async function `openDirectMessage` returns a Promise but lacks documentation for its resolve value and rejection conditions. Add `@returns {Promise<void>}` and note that rejections are caught internally and surfaced via toast, so no rejection propagates to the caller.
Talk to Kody by mentioning @kody
Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.
This PR adds incident chat and direct messaging capabilities to the Incident Command panel, allowing responders to communicate through incident-related channels and reach key personnel directly.
Key changes:
Incident Chat Section: A new component (
IncidentChatSection) displays tappable chat channels on the incident, including the all-call incident channel, the responder's lane channel, the command channel, the leads channel, and the dispatch channel. Each channel only appears when the server includes its ID in the payload, meaning access is fully server-controlled. A frozen state is shown when the incident is closed (readable but no longer active).ICS Role Contacts: Responders can now see who holds ICS positions on the incident (Incident Commander, Safety Officer, Operations Section Chief, etc.) and start a 1:1 conversation with them directly from the panel. Standard NIMS/ICS position titles are provided via a new role-name mapping.
Direct Messaging: A new
useDirectMessagehook handles creating or reusing a 1:1 conversation with another user and navigating to it. It gracefully handles external contacts who have a name and phone but no Resgrid account.Contact Card Messaging: Existing contact rows (e.g., incident commander, lane leads) now include a "send message" button when the contact has a Resgrid account.
New Chat Channel Types: Added
IncidentLeads(IC + lane leads) andIncidentDispatch(incident personnel + authorized dispatchers) channel types to the chat enums.Data Model Updates: The
ResourceIncidentViewmodel now includesRoles(ICS position holders) andChat(authorized channel IDs with frozen state), both populated by the server.