Skip to content

RC-T40 Chat Fixes - #267

Merged
ucswift merged 2 commits into
masterfrom
develop
Aug 13, 2026
Merged

RC-T40 Chat Fixes#267
ucswift merged 2 commits into
masterfrom
develop

Conversation

@ucswift

@ucswift ucswift commented Aug 13, 2026

Copy link
Copy Markdown
Member

Summary by CodeRabbit

  • New Features

    • Added support for unit dispatch chat channels and improved incident-channel grouping.
    • Incident command updates now refresh automatically after reconnection and during active sessions.
    • Added shared light and dark theme support across web and native platforms.
  • Bug Fixes

    • Improved vertical text alignment in input and selection fields on iOS and Android.
    • Preserved current incident information when background refreshes fail.
  • Tests

    • Added coverage for channel grouping and realtime incident-command updates.

@coderabbitai

coderabbitai Bot commented Aug 13, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@ucswift, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 66 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: cdc2335b-a7c7-42dd-89e5-665bf68ee4bb

📥 Commits

Reviewing files that changed from the base of the PR and between 51d5968 and 81be781.

📒 Files selected for processing (7)
  • gluestack-ui.config.json
  • src/components/ui/gluestack-ui-provider/index.tsx
  • src/components/ui/input/index.tsx
  • src/components/ui/select/index.tsx
  • src/components/ui/text-field-metrics.ts
  • src/hooks/use-signalr-lifecycle.ts
  • tsconfig.json
📝 Walkthrough

Walkthrough

The PR centralizes theme tokens with platform-specific loading, adds chat channel classifications, corrects native input alignment, and refreshes incident-command state after SignalR updates and reconnections.

Changes

Platform theme handling

Layer / File(s) Summary
Shared theme tokens
theme-tokens.css
Adds shared color, typography, shadow, utility, and animation tokens.
Platform theme wiring
global.css, global.web.css, src/lib/theme-styles.ts, src/lib/theme-styles.web.ts, src/app/_layout.tsx, src/components/ui/gluestack-ui-provider/index.tsx
Uses system dark mode on native and .dark class handling on web. The app loads the platform-specific stylesheet entry.

Chat channel classification

Layer / File(s) Summary
Chat channel classification and validation
src/models/v4/chat/chatEnums.ts, src/components/chat/chat-utils.ts, src/app/(app)/chat.tsx, src/components/chat/__tests__/chat-utils.test.ts
Adds UnitDispatch and groups IncidentLeads and IncidentDispatch under incidents. Tests cover grouping and archived-channel exclusion.

Native input alignment

Layer / File(s) Summary
Native text metrics
src/components/ui/input/index.tsx, src/components/ui/select/index.tsx
Applies platform-specific vertical metrics and preserves caller-provided styles for input and select controls.

Incident command realtime updates

Layer / File(s) Summary
Incident command refresh state
src/stores/calls/incident-command-store.ts, src/stores/calls/__tests__/incident-command-store.test.ts
Tracks the active call and refreshes matching updates without clearing the current view or enabling loading. Tests cover matching, unrelated, and failed refreshes.
SignalR event dispatch
src/stores/signalr/signalr-store.ts
Recognizes incidentCommandUpdated, normalizes supported payloads, and dispatches valid call IDs.
Reconnect backfill
src/hooks/use-signalr-lifecycle.ts
Refreshes the open incident command view after a successful update-hub reconnection.

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

Mergeability Score: 🟡 Moderate · up to 51d59

The PR adds new theme styles, but the current lint configuration rejects the new CSS syntax and font-value casing, so merge readiness is moderate until the lint rules are updated or the check is explicitly waived.

Sequence Diagram(s)

sequenceDiagram
  participant SignalR
  participant IncidentCommandStore
  participant IncidentCommandAPI
  participant LifecycleHook
  SignalR->>IncidentCommandStore: Dispatch incidentCommandUpdated(callId)
  IncidentCommandStore->>IncidentCommandAPI: Refresh matching incident view
  IncidentCommandAPI-->>IncidentCommandStore: Return updated view
  LifecycleHook->>IncidentCommandStore: Refresh open call after reconnection
Loading

Possibly related PRs

  • Resgrid/Unit#253: Introduced the incident-command store and related incident channel integration.
  • Resgrid/Unit#260: Introduced chat functionality extended by the channel classification changes.

Suggested reviewers: github-actions

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately identifies the pull request as a set of chat-related fixes, which matches the changeset.
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 💡 1
📝 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: 3

🧹 Nitpick comments (6)
src/stores/signalr/signalr-store.ts (1)

9-9: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use the configured source alias.

Replace the relative import with @/stores/calls/incident-command-store. This keeps imports stable when this store moves.

As per coding guidelines: “Use path aliases from tsconfig.json (@/*, @env, @assets/*) instead of relative paths.”

🤖 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/signalr/signalr-store.ts` at line 9, Update the import of
useIncidentCommandStore in signalr-store.ts to use the configured
`@/stores/calls/incident-command-store` alias instead of the relative path,
preserving the imported symbol and behavior.

Source: Coding guidelines

src/app/(app)/chat.tsx (1)

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

Add component coverage for the incident icon branch.

Render ChannelRow with ChatChannelType.IncidentLeads and ChatChannelType.IncidentDispatch, then assert that Network is rendered. The existing groupChannels test covers classification only.

🤖 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/`(app)/chat.tsx around lines 38 - 43, Add component tests for the
incident icon branch in ChannelRow, rendering it with
ChatChannelType.IncidentLeads and ChatChannelType.IncidentDispatch and asserting
that Network is present for both cases. Keep the existing groupChannels
classification test unchanged.

Source: Coding guidelines

src/components/ui/input/index.tsx (1)

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

Centralize the platform metric styles.

ANDROID_FIELD_METRICS and useTextFieldVerticalFix are identical in both wrappers. A future size change can make Input and SelectInput render differently. Move the shared metrics into one UI utility. Use StyleSheet.create() for the static native styles.

  • src/components/ui/input/index.tsx#L26-L43: import the shared metric helper and remove the local definitions.
  • src/components/ui/select/index.tsx#L75-L92: import the shared metric helper and remove the local definitions.

As per coding guidelines, use StyleSheet.create() for styles without a Gluestack UI equivalent.

🤖 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/ui/input/index.tsx` around lines 26 - 43, Centralize the
duplicated Android metric styles and useTextFieldVerticalFix logic in one shared
UI utility, implemented with StyleSheet.create() for the static native styles.
In src/components/ui/input/index.tsx lines 26-43, import the shared helper and
remove the local ANDROID_FIELD_METRICS and useTextFieldVerticalFix definitions;
make the same change in src/components/ui/select/index.tsx lines 75-92 so both
wrappers use identical shared metrics.

Source: Coding guidelines

src/app/_layout.tsx (1)

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

Use the @/ path alias for this import.

The target lives under src/, so the alias applies.

♻️ Proposed fix
 // Import global CSS (platform-specific entry: global.css on native, global.web.css on web)
-import '../lib/theme-styles';
+import '`@/lib/theme-styles`';

As per coding guidelines: "Use path aliases from tsconfig.json (@/*, @env, @assets/*) instead of relative paths".

🤖 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/_layout.tsx` around lines 1 - 2, Update the global stylesheet import
in the layout module to use the configured `@/` alias instead of a relative path,
preserving the existing lib/theme-styles target.

Source: Coding guidelines

theme-tokens.css (2)

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

Add a parity check for the duplicated dark token blocks.

Lines 146-277 and lines 279-408 declare the same ~130 dark tokens twice. The values currently match. The file header states that the shared file exists so the token set never drifts, but these two blocks can drift silently because nothing enforces equality.

Add a small script or Jest test that parses theme-tokens.css, extracts the two dark blocks, and asserts identical key/value maps. Run it in CI.

🤖 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 `@theme-tokens.css` around lines 146 - 408, Update the validation setup for
theme-tokens.css by adding a small script or Jest test that parses the
prefers-color-scheme dark :root block and the :root.dark block, extracts their
CSS custom-property key/value maps, and asserts they are identical. Ensure this
check is included in the CI test or validation command so future token drift
fails automatically.

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

Stylelint does not know the Tailwind v4 at-rules used by these stylesheets. The scss/at-rule-no-unknown rule rejects @theme and @custom-variant, and value-keyword-case rejects the font family name. The single root cause is the Stylelint configuration, not the CSS. Add theme, custom-variant, and the other Tailwind v4 at-rules to ignoreAtRules, and disable value-keyword-case for font-family values.

  • theme-tokens.css#L543-L543: allow the @theme inline at-rule in the Stylelint configuration.
  • theme-tokens.css#L675-L676: allow the @theme at-rule and keep Roboto capitalized by disabling value-keyword-case for font values.
  • global.css#L17-L17: allow the @custom-variant at-rule.
  • global.web.css#L12-L12: allow the @custom-variant at-rule.
🤖 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 `@theme-tokens.css` at line 543, Update the Stylelint configuration, not the
CSS, to add Tailwind v4 at-rules including theme and custom-variant to
scss/at-rule-no-unknown.ignoreAtRules, and disable value-keyword-case for
font-family values so Roboto remains capitalized. This applies to
theme-tokens.css lines 543 and 675-676, global.css line 17, and global.web.css
line 12; no direct stylesheet changes are required.

Source: Linters/SAST tools

🤖 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/ui/input/index.tsx`:
- Around line 33-43: Add co-located Jest regression tests for
src/components/ui/input/index.tsx lines 33-43 and
src/components/ui/select/index.tsx lines 82-92, mocking native modules before
imports and using the jest-expo preset with `@testing-library/react-native`. Cover
iOS metrics, Android metrics for every supported size, and caller style
precedence for both InputField and SelectInput; call unmount() after each
render.

In `@src/stores/calls/__tests__/incident-command-store.test.ts`:
- Around line 337-346: Replace the mocked response objects in the incident view
tests around fetchIncidentView with typed ResourceIncidentViewResult fixtures,
creating each via new ResourceIncidentViewResult() and assigning Data and
Status. Apply this consistently to the responses at the four referenced setup
points and remove the as any casts.

In `@src/stores/signalr/signalr-store.ts`:
- Around line 164-182: Add focused tests in
src/stores/signalr/signalr-store.ts:164-182 covering incidentCommandUpdated
payload normalization via extractCommandCallId, timestamp recording, dispatch
for valid IDs, and ignoring malformed payloads. Add a test at
src/stores/signalr/signalr-store.ts:392-400 verifying handleAppResume refreshes
the open incident command after a successful update-hub reconnect. Add the
corresponding lifecycle coverage at src/hooks/use-signalr-lifecycle.ts:139-145,
or explicitly confirm no direct change is needed there if the store-level tests
fully cover the behavior.

---

Nitpick comments:
In `@src/app/_layout.tsx`:
- Around line 1-2: Update the global stylesheet import in the layout module to
use the configured `@/` alias instead of a relative path, preserving the existing
lib/theme-styles target.

In `@src/app/`(app)/chat.tsx:
- Around line 38-43: Add component tests for the incident icon branch in
ChannelRow, rendering it with ChatChannelType.IncidentLeads and
ChatChannelType.IncidentDispatch and asserting that Network is present for both
cases. Keep the existing groupChannels classification test unchanged.

In `@src/components/ui/input/index.tsx`:
- Around line 26-43: Centralize the duplicated Android metric styles and
useTextFieldVerticalFix logic in one shared UI utility, implemented with
StyleSheet.create() for the static native styles. In
src/components/ui/input/index.tsx lines 26-43, import the shared helper and
remove the local ANDROID_FIELD_METRICS and useTextFieldVerticalFix definitions;
make the same change in src/components/ui/select/index.tsx lines 75-92 so both
wrappers use identical shared metrics.

In `@src/stores/signalr/signalr-store.ts`:
- Line 9: Update the import of useIncidentCommandStore in signalr-store.ts to
use the configured `@/stores/calls/incident-command-store` alias instead of the
relative path, preserving the imported symbol and behavior.

In `@theme-tokens.css`:
- Around line 146-408: Update the validation setup for theme-tokens.css by
adding a small script or Jest test that parses the prefers-color-scheme dark
:root block and the :root.dark block, extracts their CSS custom-property
key/value maps, and asserts they are identical. Ensure this check is included in
the CI test or validation command so future token drift fails automatically.
- Line 543: Update the Stylelint configuration, not the CSS, to add Tailwind v4
at-rules including theme and custom-variant to
scss/at-rule-no-unknown.ignoreAtRules, and disable value-keyword-case for
font-family values so Roboto remains capitalized. This applies to
theme-tokens.css lines 543 and 675-676, global.css line 17, and global.web.css
line 12; no direct stylesheet changes are required.
🪄 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: 6b08902d-b479-4635-8170-8952c74c6e96

📥 Commits

Reviewing files that changed from the base of the PR and between 7e1ddc0 and 51d5968.

📒 Files selected for processing (17)
  • global.css
  • global.web.css
  • src/app/(app)/chat.tsx
  • src/app/_layout.tsx
  • src/components/chat/__tests__/chat-utils.test.ts
  • src/components/chat/chat-utils.ts
  • src/components/ui/gluestack-ui-provider/index.tsx
  • src/components/ui/input/index.tsx
  • src/components/ui/select/index.tsx
  • src/hooks/use-signalr-lifecycle.ts
  • src/lib/theme-styles.ts
  • src/lib/theme-styles.web.ts
  • src/models/v4/chat/chatEnums.ts
  • src/stores/calls/__tests__/incident-command-store.test.ts
  • src/stores/calls/incident-command-store.ts
  • src/stores/signalr/signalr-store.ts
  • theme-tokens.css

Comment thread src/components/ui/input/index.tsx Outdated
Comment on lines +33 to +43
const useTextFieldVerticalFix = (size: string | undefined) =>
React.useMemo(() => {
if (Platform.OS === 'ios') {
return { lineHeight: 0 } as const;
}
if (Platform.OS === 'android') {
const metrics = ANDROID_FIELD_METRICS[size ?? 'md'] ?? ANDROID_FIELD_METRICS.md;
return { height: metrics.height, lineHeight: metrics.lineHeight, includeFontPadding: false, textAlignVertical: 'center' } as const;
}
return undefined;
}, [size]);

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

Add regression tests for platform text metrics.

The new behavior depends on Platform.OS, field size, and style merge order. Add co-located Jest tests for both wrappers. Mock native modules before imports. Assert iOS metrics, Android metrics for each supported size, and caller style precedence. Call unmount() after each render.

  • src/components/ui/input/index.tsx#L33-L43: test InputField metrics on iOS and Android.
  • src/components/ui/select/index.tsx#L82-L92: test SelectInput metrics on iOS and Android.

As per coding guidelines, use Jest with the jest-expo preset and @testing-library/react-native utilities for component tests.

📍 Affects 2 files
  • src/components/ui/input/index.tsx#L33-L43 (this comment)
  • src/components/ui/select/index.tsx#L82-L92
🤖 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/ui/input/index.tsx` around lines 33 - 43, Add co-located Jest
regression tests for src/components/ui/input/index.tsx lines 33-43 and
src/components/ui/select/index.tsx lines 82-92, mocking native modules before
imports and using the jest-expo preset with `@testing-library/react-native`. Cover
iOS metrics, Android metrics for every supported size, and caller style
precedence for both InputField and SelectInput; call unmount() after each
render.

Source: Coding guidelines

Comment on lines +337 to +346
mockGetResourceIncidentView.mockResolvedValue({ Data: mockView, Status: 'Ok' } as any);

const { result, unmount } = renderHook(() => useIncidentCommandStore());

await act(async () => {
await result.current.fetchIncidentView('call123');
});

const updated = { ...mockView, ImportantInformation: 'Structure now unstable' };
mockGetResourceIncidentView.mockResolvedValue({ Data: updated, Status: 'Ok' } as any);

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 -eu

test_file='src/stores/calls/__tests__/incident-command-store.test.ts'

printf '%s\n' '--- relevant test lines ---'
sed -n '1,430p' "$test_file" | nl -ba | sed -n '300,410p'

printf '%s\n' '--- incident-view symbols and result types ---'
rg -n -C 3 'getResourceIncidentView|ResourceIncidentView(Result)?|mockGetResourceIncidentView' src

printf '%s\n' '--- project TypeScript/test configuration ---'
rg -n -C 2 'strict|noImplicitAny|jest-expo|ts-jest|isolatedModules' tsconfig*.json package.json

Repository: Resgrid/Unit

Length of output: 26294


🏁 Script executed:

#!/bin/bash
set -eu

test_file='src/stores/calls/__tests__/incident-command-store.test.ts'

printf '%s\n' '--- test imports and fixtures ---'
sed -n '1,75p' "$test_file"
printf '%s\n' '--- relevant refresh tests ---'
sed -n '325,410p' "$test_file"
printf '%s\n' '--- model declarations ---'
sed -n '1,35p' src/models/v4/baseV4Request.ts
sed -n '145,230p' src/models/v4/incidentCommand/resourceIncidentView.ts
sed -n '1,25p' src/models/v4/incidentCommand/resourceIncidentViewResult.ts
printf '%s\n' '--- available TypeScript tooling ---'
command -v tsc || true
find . -maxdepth 3 -type f \( -name 'typescript.js' -o -name 'tsc' \) -print

Repository: Resgrid/Unit

Length of output: 7121


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- nested model declarations used by createMockView ---'
rg -n -A 18 -B 3 'interface (IncidentContactInfo|TacticalObjective|IncidentNeed|IncidentNote|IncidentAttachment)|class (TacticalObjective|IncidentNeed|IncidentNote|IncidentAttachment)' src/models/v4/incidentCommand/resourceIncidentView.ts

repo_root="$PWD"
cat > /tmp/incident-view-type-check.ts <<TS
import { ResourceIncidentViewResult } from '${repo_root}/src/models/v4/incidentCommand/resourceIncidentViewResult';

const view = {
  IncidentCommandId: 'ic-1',
  CallId: 123,
  Status: 0,
  EstablishedOn: '2026-07-01T10:00:00Z',
  EstimatedEndOn: null,
  ClosedOn: null,
  ImportantInformation: 'Watch for downed lines',
  IncidentActionPlan: 'Attack from the north side',
  Commander: { UserId: 'user-1', Name: 'Chief Smith', Phone: '555-1234', Email: 'chief@example.com' },
  Objectives: [],
  Needs: [],
  Notes: [],
  Attachments: [],
  MyAssignment: null,
};

const directResult = { Data: view, Status: 'Ok' };
// `@ts-expect-error`: the direct partial object must not satisfy the class result contract.
const invalidResult: ResourceIncidentViewResult = directResult;

const typedResult = Object.assign(new ResourceIncidentViewResult(), directResult);
const validResult: ResourceIncidentViewResult = typedResult;
void invalidResult;
void validResult;
TS

printf '%s\n' '--- temporary type-check ---'
tsc --noEmit --strict --skipLibCheck --target es2022 --module commonjs /tmp/incident-view-type-check.ts
rm -f /tmp/incident-view-type-check.ts

Repository: Resgrid/Unit

Length of output: 3320


🏁 Script executed:

#!/bin/bash
set -eu

repo_root="$PWD"
cat > /tmp/incident-view-type-check.ts <<TS
import { ResourceIncidentViewResult } from '${repo_root}/src/models/v4/incidentCommand/resourceIncidentViewResult';

const view = {
  IncidentCommandId: 'ic-1',
  CallId: 123,
  Status: 0,
  EstablishedOn: '2026-07-01T10:00:00Z',
  EstimatedEndOn: null,
  ClosedOn: null,
  ImportantInformation: 'Watch for downed lines',
  IncidentActionPlan: 'Attack from the north side',
  Commander: { UserId: 'user-1', Name: 'Chief Smith', Phone: '555-1234', Email: 'chief@example.com' },
  Objectives: [],
  Needs: [],
  Notes: [],
  Attachments: [],
  MyAssignment: null,
};

const directResult = { Data: view, Status: 'Ok' };
// `@ts-expect-error`: the direct partial object must not satisfy the class result contract.
const invalidResult: ResourceIncidentViewResult = directResult;

const typedResult = Object.assign(new ResourceIncidentViewResult(), directResult);
const validResult: ResourceIncidentViewResult = typedResult;
void invalidResult;
void validResult;
TS

tsc --ignoreConfig --noEmit --strict --skipLibCheck --target es2022 --module commonjs /tmp/incident-view-type-check.ts
rm -f /tmp/incident-view-type-check.ts

Repository: Resgrid/Unit

Length of output: 150


Use a typed ResourceIncidentViewResult fixture for these mocked responses.

Construct the result with new ResourceIncidentViewResult() and assign Data and Status. This preserves validation of the inherited BaseV4Request fields and the Data type instead of bypassing them with as any. Apply this at lines 337, 346, 363, and 383.

🤖 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 337 -
346, Replace the mocked response objects in the incident view tests around
fetchIncidentView with typed ResourceIncidentViewResult fixtures, creating each
via new ResourceIncidentViewResult() and assigning Data and Status. Apply this
consistently to the responses at the four referenced setup points and remove the
as any casts.

Source: Coding guidelines

Comment on lines +164 to +182
/**
* The affected incident's call id. Core sends it as a bare string — the eventing worker forwards the
* topic's ItemId, which is CallId.ToString() — with object payloads tolerated for safety.
*/
function extractCommandCallId(message: unknown): string | undefined {
if (typeof message === 'string') {
const trimmed = message.trim();
return trimmed.length > 0 ? trimmed : undefined;
}
if (typeof message === 'number' && Number.isFinite(message)) {
return String(message);
}
if (message !== null && typeof message === 'object') {
const m = message as { CallId?: string | number; callId?: string | number };
const id = m.CallId ?? m.callId;
return id !== undefined && id !== null && String(id).trim().length > 0 ? String(id).trim() : undefined;
}
return undefined;
}

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 -e
printf '%s\n' '--- candidate files ---'
git ls-files | rg '(^|/)(signalr-store|use-signalr-lifecycle)(\.[^.]+)?$|(^|/).*signalr.*\.(test|spec)\.(ts|tsx)$|(^|/).*incident-command.*\.(test|spec)\.(ts|tsx)$'
printf '%s\n' '--- affected implementation sections ---'
sed -n '130,205p' src/stores/signalr/signalr-store.ts
sed -n '365,420p' src/stores/signalr/signalr-store.ts
sed -n '115,165p' src/hooks/use-signalr-lifecycle.ts
printf '%s\n' '--- related test references ---'
rg -n --glob '*.{test,spec}.{ts,tsx}' 'extractCommandCallId|incidentCommandUpdated|useSignalrLifecycle|use-signalr-lifecycle|handleIncidentCommandUpdated|recordEvent' . || true
printf '%s\n' '--- test configuration ---'
rg -n --glob 'package.json' --glob '*config*' --glob 'vitest*' --glob 'jest*' --glob 'setup*' '"(test|jest|vitest)|testing"' . || true

Repository: Resgrid/Unit

Length of output: 9929


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- SignalR store test structure ---'
ast-grep outline src/stores/signalr/__tests__/signalr-store.test.ts
printf '%s\n' '--- relevant SignalR store tests ---'
rg -n -C 8 'incidentCommand|UPDATE_HUB_EVENTS|recordEvent|connectUpdateHub|signalRService\.on|handleIncidentCommandUpdated' src/stores/signalr/__tests__/signalr-store.test.ts
printf '%s\n' '--- lifecycle test structure ---'
ast-grep outline src/hooks/__tests__/use-signalr-lifecycle.test.tsx
printf '%s\n' '--- lifecycle test contents ---'
cat -n src/hooks/__tests__/use-signalr-lifecycle.test.tsx
printf '%s\n' '--- incident-command store realtime tests ---'
sed -n '315,415p' src/stores/calls/__tests__/incident-command-store.test.ts
printf '%s\n' '--- imports and exports in affected modules ---'
sed -n '1,135p' src/stores/signalr/signalr-store.ts
sed -n '1,120p' src/hooks/use-signalr-lifecycle.ts

Repository: Resgrid/Unit

Length of output: 33319


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- all incident-command SignalR references in tests ---'
rg -n -C 5 --glob '*.{test,spec}.{ts,tsx}' 'incidentCommandUpdated|extractCommandCallId|openCallId|handleIncidentCommandUpdated|recordEvent' src || true
printf '%s\n' '--- lifecycle integration test ---'
cat -n src/app/\(app\)/__tests__/signalr-lifecycle.test.tsx
printf '%s\n' '--- focused SignalR store test setup and event assertions ---'
sed -n '1,125p' src/stores/signalr/__tests__/signalr-store.test.ts
sed -n '260,300p' src/stores/signalr/__tests__/signalr-store.test.ts
printf '%s\n' '--- source-level coverage probe ---'
python3 - <<'PY'
from pathlib import Path
tests = list(Path('src').rglob('*.test.ts')) + list(Path('src').rglob('*.test.tsx')) + list(Path('src').rglob('*.spec.ts')) + list(Path('src').rglob('*.spec.tsx'))
needles = {
    'event_name': 'incidentCommandUpdated',
    'extractor_name': 'extractCommandCallId',
    'dispatch_action': 'handleIncidentCommandUpdated',
    'open_call_backfill': 'openCallId',
    'event_record_assertion': 'recordEvent',
}
for name, needle in needles.items():
    hits = []
    for path in tests:
        text = path.read_text(errors='replace')
        if needle in text:
            hits.append(str(path))
    print(f'{name}: {hits or "NONE"}')
PY

Repository: Resgrid/Unit

Length of output: 24089


Add focused tests for the new realtime entry points.

  • Test incidentCommandUpdated payload normalization, timestamp recording, valid-ID dispatch, and malformed-payload handling.
  • Test that handleAppResume refreshes the open incident command after a successful update-hub reconnect.
📍 Affects 2 files
  • src/stores/signalr/signalr-store.ts#L164-L182 (this comment)
  • src/stores/signalr/signalr-store.ts#L392-L400
  • src/hooks/use-signalr-lifecycle.ts#L139-L145
🤖 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/signalr/signalr-store.ts` around lines 164 - 182, Add focused
tests in src/stores/signalr/signalr-store.ts:164-182 covering
incidentCommandUpdated payload normalization via extractCommandCallId, timestamp
recording, dispatch for valid IDs, and ignoring malformed payloads. Add a test
at src/stores/signalr/signalr-store.ts:392-400 verifying handleAppResume
refreshes the open incident command after a successful update-hub reconnect. Add
the corresponding lifecycle coverage at
src/hooks/use-signalr-lifecycle.ts:139-145, or explicitly confirm no direct
change is needed there if the store-level tests fully cover the behavior.

Source: Coding guidelines

@ucswift

ucswift commented Aug 13, 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 ed729b5 into master Aug 13, 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.

1 participant