Skip to content

RC-T40 Fixes - #26

Merged
ucswift merged 2 commits into
masterfrom
develop
Jul 28, 2026
Merged

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

Conversation

@ucswift

@ucswift ucswift commented Jul 28, 2026

Copy link
Copy Markdown
Member

PR Description

This pull request includes fixes and enhancements across three main areas:

1. Per-Lane Work-Time (Crew Fatigue) Thresholds

Adds configurable amber and red work-time thresholds at the lane/division level in the command structure board. Previously, work-time indicators used fixed defaults (amber at 20 minutes, red at 40 minutes). Now, users can set custom thresholds per lane when creating or editing a lane. A value of 0 disables that specific color indicator. If left unset, the defaults of 20/40 minutes apply. This includes updates to the data models, offline queue, command store, and both the add-lane and lane-details UIs.

2. iOS Microphone Permission Deadlock Fix

Resolves an app freeze on iOS caused by expo-audio's requestRecordingPermissionsAsync, which activates AVAudioSession in record mode and deadlocks against expo-av's active session. The LiveKit store now only performs a non-activating permission check on iOS and allows WebRTC to prompt the user natively when a microphone track is published.

3. Multi-Orientation Support for Modals

Adds supportedOrientations to all modal components (bottom sheets, side drawers, call modals, video player, command details, and location picker) to prevent iOS from forcing landscape iPads into portrait orientation when a modal opens.

@Resgrid-Bot

This comment has been minimized.

@coderabbitai

coderabbitai Bot commented Jul 28, 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: 17 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

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: 0c53012b-1e4c-4bb9-bf89-8a6bb515e59c

📥 Commits

Reviewing files that changed from the base of the PR and between fbc4e39 and ed11cbb.

📒 Files selected for processing (4)
  • src/components/command/landscape-structure-board.tsx
  • src/components/command/lane-details-sheet.tsx
  • src/stores/app/__tests__/livekit-store.test.ts
  • src/stores/app/livekit-store.ts
📝 Walkthrough

Walkthrough

The change adds configurable amber and red work-time thresholds across lane editing, persistence, offline replay, and indicators; enables explicit orientation lists on several modals; and changes iOS microphone handling to check permission status without requesting it directly.

Changes

Lane work-time thresholds

Layer / File(s) Summary
Threshold contracts and editing flows
src/models/..., src/components/command/add-lane-sheet.tsx, src/components/command/lane-details-sheet.tsx, src/translations/*, src/components/command/__tests__/add-lane-sheet.test.tsx
Lane models and limits support amber/red work-time minutes. Add and edit forms expose numeric threshold inputs, parse defaults and cleared values, and include localized labels and help text.
Threshold indicator propagation
src/components/command/landscape-structure-board.tsx, src/components/command/structure-section.tsx, src/components/command/__tests__/work-time-light.test.tsx
Work-time colors accept lane-specific thresholds, including disabled values, and pass them through board resource components into WorkTimeLight.
Threshold persistence and offline replay
src/stores/command/store.ts, src/services/offline-event-manager.service.ts
Optimistic updates, server saves, and offline event replay persist the new threshold fields.

Modal orientation support

Layer / File(s) Summary
Modal orientation allow-lists
src/components/call-video-feeds/video-player-modal.tsx, src/components/calls/*, src/components/command/command-details-sheet.tsx, src/components/ui/*
Call, location-picker, bottom-sheet, and side-drawer modals explicitly allow portrait and landscape orientations.

iOS microphone permission handling

Layer / File(s) Summary
Non-activating iOS permission checks
src/stores/app/livekit-store.ts, src/stores/app/__tests__/livekit-store.test.ts
iOS permission flows stop calling requestRecordingPermissionsAsync, log when permission is not granted, and continue returning success while tests verify the new behavior.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant AddLaneSheet
  participant CommandStore
  participant saveCommandNode
  participant LandscapeStructureBoard
  participant WorkTimeLight

  AddLaneSheet->>CommandStore: submit threshold values
  CommandStore->>saveCommandNode: persist WorkTimeAmberMinutes and WorkTimeRedMinutes
  CommandStore-->>LandscapeStructureBoard: update lane node
  LandscapeStructureBoard->>WorkTimeLight: pass lane-specific thresholds
  WorkTimeLight-->>LandscapeStructureBoard: render threshold-based color
Loading
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title is too generic to describe the main changes in this PR. Use a concise title that names the primary change, such as adding work-time threshold support and orientation fixes.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
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 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: 5

🧹 Nitpick comments (3)
src/stores/app/__tests__/livekit-store.test.ts (1)

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

Remove the redundant any casts from the iOS permission fixtures.

mockGetRecordingPermissionsAsync is already jest.MockedFunction<typeof getRecordingPermissionsAsync>, so the fixture object is checked against getRecordingPermissionsAsync’s return type.

🤖 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/app/__tests__/livekit-store.test.ts` around lines 263 - 268,
Remove the redundant any cast from the object passed to
mockGetRecordingPermissionsAsync in the iOS permission fixtures, relying on the
existing jest.MockedFunction typing to validate the fixture against
getRecordingPermissionsAsync’s return type.

Source: Coding guidelines

src/components/command/landscape-structure-board.tsx (1)

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

Extract the WorkTimeLight props into an interface.

The updated component uses an inline object type instead of a named props interface. Define WorkTimeLightProps and use React.FC<WorkTimeLightProps>.

As per coding guidelines, use interfaces for props and type components as React.FC<Props>.

🤖 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/command/landscape-structure-board.tsx` around lines 41 - 47,
Define a named WorkTimeLightProps interface containing the existing
WorkTimeLight properties, then update WorkTimeLight to use
React.FC<WorkTimeLightProps> instead of the inline object type; preserve all
optional fields and defaults.

Source: Coding guidelines

src/stores/command/store.ts (1)

640-641: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Add end-to-end regression coverage for threshold persistence and replay.

The new fields cross optimistic state, online saving, and offline replay, but the supplied tests only cover the form payload.

  • src/stores/command/store.ts#L640-L641: assert optimistic nodes retain amber/red values and disabled 0 values.
  • src/stores/command/store.ts#L670-L671: assert the online saveCommandNode payload contains both values.
  • src/services/offline-event-manager.service.ts#L388-L389: assert queued replay forwards both values correctly.

As per coding guidelines, new logic and persistence paths should have tests.

🤖 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/command/store.ts` around lines 640 - 641, Extend regression
coverage for threshold persistence and replay: in src/stores/command/store.ts at
lines 640-641, verify optimistic nodes retain amber/red values and disabled
values as 0; at lines 670-671, verify the online saveCommandNode payload
includes both fields; and in src/services/offline-event-manager.service.ts at
lines 388-389, verify queued replay forwards both values correctly.

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/command/__tests__/add-lane-sheet.test.tsx`:
- Around line 57-67: Call the destructured unmount function from the render
result at the end of the test after the onSave assertion, ensuring the
AddLaneSheet render is cleaned up before subsequent tests.

In `@src/components/command/add-lane-sheet.tsx`:
- Around line 194-214: Add accessibilityLabel to the workTimeAmberMinutes and
workTimeRedMinutes InputField elements, using the corresponding translated
threshold labels from command.limit_worktime_amber and
command.limit_worktime_red. Preserve the existing values, handlers,
placeholders, and test IDs.

In `@src/components/command/lane-details-sheet.tsx`:
- Around line 58-60: Update the lane-details form state initialization and save
handling around workTimeAmber and workTimeRed so unset thresholds remain
distinguishable from an explicit 0. Seed empty fields with the shared
WorkTimeLight defaults (20 and 40 minutes) when the lane has no configured
values, and avoid parseInt(... ) || 0 converting unset values into disabled
indicators; preserve explicit 0 as disabled when saving.
- Around line 301-319: Add translated accessibilityLabel values to the amber and
red InputField elements in the worktime thresholds section, using the
corresponding threshold label translations so VoiceOver/TalkBack can distinguish
them after values are entered. Keep the existing input behavior and testIDs
unchanged.

In `@src/stores/app/livekit-store.ts`:
- Around line 407-414: Update the iOS microphone permission handling in
livekit-store.ts at lines 407-414 and 940-946 to treat !granted && !canAskAgain
as failure, guide the user to Settings, and stop before allowing WebRTC to
prompt; preserve the existing behavior for permissions that can still be
requested. Update the corresponding test in
src/stores/app/__tests__/livekit-store.test.ts lines 282-296 so permanently
denied access no longer returns true.

---

Nitpick comments:
In `@src/components/command/landscape-structure-board.tsx`:
- Around line 41-47: Define a named WorkTimeLightProps interface containing the
existing WorkTimeLight properties, then update WorkTimeLight to use
React.FC<WorkTimeLightProps> instead of the inline object type; preserve all
optional fields and defaults.

In `@src/stores/app/__tests__/livekit-store.test.ts`:
- Around line 263-268: Remove the redundant any cast from the object passed to
mockGetRecordingPermissionsAsync in the iOS permission fixtures, relying on the
existing jest.MockedFunction typing to validate the fixture against
getRecordingPermissionsAsync’s return type.

In `@src/stores/command/store.ts`:
- Around line 640-641: Extend regression coverage for threshold persistence and
replay: in src/stores/command/store.ts at lines 640-641, verify optimistic nodes
retain amber/red values and disabled values as 0; at lines 670-671, verify the
online saveCommandNode payload includes both fields; and in
src/services/offline-event-manager.service.ts at lines 388-389, verify queued
replay forwards both values correctly.
🪄 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 Plus

Run ID: 86382e9c-321e-4dc0-b918-ffff500a72f9

📥 Commits

Reviewing files that changed from the base of the PR and between 5fd1386 and fbc4e39.

📒 Files selected for processing (30)
  • src/components/call-video-feeds/video-player-modal.tsx
  • src/components/calls/call-detail-menu.tsx
  • src/components/calls/call-images-modal.tsx
  • src/components/calls/call-notes-modal.tsx
  • src/components/calls/close-call-bottom-sheet.tsx
  • src/components/command/__tests__/add-lane-sheet.test.tsx
  • src/components/command/__tests__/work-time-light.test.tsx
  • src/components/command/add-lane-sheet.tsx
  • src/components/command/command-details-sheet.tsx
  • src/components/command/landscape-structure-board.tsx
  • src/components/command/lane-details-sheet.tsx
  • src/components/command/structure-section.tsx
  • src/components/ui/bottom-sheet.tsx
  • src/components/ui/side-drawer.tsx
  • src/models/offline-queue/queued-event.ts
  • src/models/v4/incidentCommand/commandStructureNode.ts
  • src/models/v4/incidentCommand/incidentCommandModels.ts
  • src/services/offline-event-manager.service.ts
  • src/stores/app/__tests__/livekit-store.test.ts
  • src/stores/app/livekit-store.ts
  • src/stores/command/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 on lines +57 to +67
it('saves custom work-time thresholds and omits cleared ones', () => {
const onSave = jest.fn();
const { getByTestId, unmount } = render(<AddLaneSheet isOpen={true} onClose={jest.fn()} onSave={onSave} />);

fireEvent.changeText(getByTestId('lane-name-input'), 'Rehab');
fireEvent.press(getByTestId('lane-limits-toggle'));
fireEvent.changeText(getByTestId('limit-worktime-amber'), '15');
fireEvent.changeText(getByTestId('limit-worktime-red'), '');
fireEvent.press(getByTestId('lane-save'));

expect(onSave).toHaveBeenCalledWith('Rehab', CommandNodeType.Division, undefined, { workTimeAmberMinutes: 15 });

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

Unmount the new render during cleanup.

The test destructures unmount on Line 59 but never calls it after the assertion, allowing the rendered tree to leak into later tests.

As per coding guidelines, tests must always call unmount() during cleanup.

Proposed fix
    expect(onSave).toHaveBeenCalledWith('Rehab', CommandNodeType.Division, undefined, { workTimeAmberMinutes: 15 });
+   unmount();
📝 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
it('saves custom work-time thresholds and omits cleared ones', () => {
const onSave = jest.fn();
const { getByTestId, unmount } = render(<AddLaneSheet isOpen={true} onClose={jest.fn()} onSave={onSave} />);
fireEvent.changeText(getByTestId('lane-name-input'), 'Rehab');
fireEvent.press(getByTestId('lane-limits-toggle'));
fireEvent.changeText(getByTestId('limit-worktime-amber'), '15');
fireEvent.changeText(getByTestId('limit-worktime-red'), '');
fireEvent.press(getByTestId('lane-save'));
expect(onSave).toHaveBeenCalledWith('Rehab', CommandNodeType.Division, undefined, { workTimeAmberMinutes: 15 });
it('saves custom work-time thresholds and omits cleared ones', () => {
const onSave = jest.fn();
const { getByTestId, unmount } = render(<AddLaneSheet isOpen={true} onClose={jest.fn()} onSave={onSave} />);
fireEvent.changeText(getByTestId('lane-name-input'), 'Rehab');
fireEvent.press(getByTestId('lane-limits-toggle'));
fireEvent.changeText(getByTestId('limit-worktime-amber'), '15');
fireEvent.changeText(getByTestId('limit-worktime-red'), '');
fireEvent.press(getByTestId('lane-save'));
expect(onSave).toHaveBeenCalledWith('Rehab', CommandNodeType.Division, undefined, { workTimeAmberMinutes: 15 });
unmount();
🤖 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/command/__tests__/add-lane-sheet.test.tsx` around lines 57 -
67, Call the destructured unmount function from the render result at the end of
the test after the onSave assertion, ensuring the AddLaneSheet render is cleaned
up before subsequent tests.

Source: Coding guidelines

Comment on lines +194 to +214
<HStack space="sm">
<VStack className="flex-1" space="xs">
<Text className="text-xs font-medium text-gray-600 dark:text-gray-300">{t('command.limit_worktime_amber')}</Text>
<Input size="sm" variant="outline">
<InputField
keyboardType="number-pad"
placeholder={t('command.limit_none')}
value={limits.workTimeAmberMinutes}
onChangeText={(v) => setLimit('workTimeAmberMinutes', v)}
testID="limit-worktime-amber"
/>
</Input>
</VStack>
<VStack className="flex-1" space="xs">
<Text className="text-xs font-medium text-gray-600 dark:text-gray-300">{t('command.limit_worktime_red')}</Text>
<Input size="sm" variant="outline">
<InputField keyboardType="number-pad" placeholder={t('command.limit_none')} value={limits.workTimeRedMinutes} onChangeText={(v) => setLimit('workTimeRedMinutes', v)} testID="limit-worktime-red" />
</Input>
</VStack>
</HStack>
<Text className="text-xs text-gray-500 dark:text-gray-400">{t('command.limit_worktime_help')}</Text>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Files matching add-lane-sheet:"
fd -a 'add-lane-sheet\.tsx$' . || true

if [ -f src/components/command/add-lane-sheet.tsx ]; then
  echo
  echo "Outline:"
  ast-grep outline src/components/command/add-lane-sheet.tsx || true
  echo
  echo "Relevant lines 160-230:"
  sed -n '160,230p' src/components/command/add-lane-sheet.tsx | nl -ba -v160
  echo
  echo "Imports/labels pattern:"
  rg -n "InputField|accessibilityLabel|accessibilityLabelledBy|limitWorktimeAmber|workTimeAmberMinutes|limit_worktime" src/components/command/add-lane-sheet.tsx
fi

Repository: Resgrid/IC

Length of output: 895


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Imports and relevant input/limit sections:"
sed -n '1,45p' src/components/command/add-lane-sheet.tsx
printf '\n---\n'
sed -n '170,230p' src/components/command/add-lane-sheet.tsx

echo
echo "Search for gluestack accessibilityLabel APIs nearby:"
rg -n "InputField|accessibilityLabel|accessibilityLabelledBy|limit_worktime|workTimeAmberMinutes|workTimeRedMinutes" src/components src/components/command/add-lane-sheet.tsx || true

Repository: Resgrid/IC

Length of output: 25660


Add accessible labels to the work-time threshold inputs.

The nearby <Text> labels are not programmatic labels for the native text fields, so screen readers can expose both inputs only via identical placeholders. Add accessibilityLabel to both <InputField> elements using t('command.limit_worktime_amber') and t('command.limit_worktime_red').

[low_effort_and_highreward]

🤖 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/command/add-lane-sheet.tsx` around lines 194 - 214, Add
accessibilityLabel to the workTimeAmberMinutes and workTimeRedMinutes InputField
elements, using the corresponding translated threshold labels from
command.limit_worktime_amber and command.limit_worktime_red. Preserve the
existing values, handlers, placeholders, and test IDs.

Source: Coding guidelines

Comment thread src/components/command/lane-details-sheet.tsx
Comment on lines +301 to +319
<VStack space="xs">
<Text className="text-sm font-medium text-gray-600 dark:text-gray-300">{t('command.worktime_thresholds_label')}</Text>
<HStack space="sm">
<VStack className="flex-1" space="xs">
<Text className="text-xs font-medium text-gray-600 dark:text-gray-300">{t('command.limit_worktime_amber')}</Text>
<Input size="sm" variant="outline">
<InputField keyboardType="number-pad" placeholder={t('command.limit_none')} value={workTimeAmber} onChangeText={(v) => setWorkTimeAmber(v.replace(/[^0-9]/g, ''))} testID="lane-worktime-amber" />
</Input>
</VStack>
<VStack className="flex-1" space="xs">
<Text className="text-xs font-medium text-gray-600 dark:text-gray-300">{t('command.limit_worktime_red')}</Text>
<Input size="sm" variant="outline">
<InputField keyboardType="number-pad" placeholder={t('command.limit_none')} value={workTimeRed} onChangeText={(v) => setWorkTimeRed(v.replace(/[^0-9]/g, ''))} testID="lane-worktime-red" />
</Input>
</VStack>
</HStack>
<Text className="text-xs text-gray-500 dark:text-gray-400">{t('command.limit_worktime_help')}</Text>
</VStack>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Add accessible labels to the threshold inputs.

The amber/red labels are separate Text elements, while each InputField only has the generic “none” placeholder. VoiceOver/TalkBack may therefore expose two indistinguishable fields, especially after values are entered. Add translated accessibilityLabel values for both inputs.

Proposed accessibility fix
-<InputField keyboardType="number-pad" placeholder={t('command.limit_none')} value={workTimeAmber} ...
+<InputField accessibilityLabel={t('command.limit_worktime_amber')} keyboardType="number-pad" placeholder={t('command.limit_none')} value={workTimeAmber} ...

-<InputField keyboardType="number-pad" placeholder={t('command.limit_none')} value={workTimeRed} ...
+<InputField accessibilityLabel={t('command.limit_worktime_red')} keyboardType="number-pad" placeholder={t('command.limit_none')} value={workTimeRed} ...

As per coding guidelines, follow WCAG mobile accessibility practices, including accessible labels.

📝 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
<VStack space="xs">
<Text className="text-sm font-medium text-gray-600 dark:text-gray-300">{t('command.worktime_thresholds_label')}</Text>
<HStack space="sm">
<VStack className="flex-1" space="xs">
<Text className="text-xs font-medium text-gray-600 dark:text-gray-300">{t('command.limit_worktime_amber')}</Text>
<Input size="sm" variant="outline">
<InputField keyboardType="number-pad" placeholder={t('command.limit_none')} value={workTimeAmber} onChangeText={(v) => setWorkTimeAmber(v.replace(/[^0-9]/g, ''))} testID="lane-worktime-amber" />
</Input>
</VStack>
<VStack className="flex-1" space="xs">
<Text className="text-xs font-medium text-gray-600 dark:text-gray-300">{t('command.limit_worktime_red')}</Text>
<Input size="sm" variant="outline">
<InputField keyboardType="number-pad" placeholder={t('command.limit_none')} value={workTimeRed} onChangeText={(v) => setWorkTimeRed(v.replace(/[^0-9]/g, ''))} testID="lane-worktime-red" />
</Input>
</VStack>
</HStack>
<Text className="text-xs text-gray-500 dark:text-gray-400">{t('command.limit_worktime_help')}</Text>
</VStack>
<VStack space="xs">
<Text className="text-sm font-medium text-gray-600 dark:text-gray-300">{t('command.worktime_thresholds_label')}</Text>
<HStack space="sm">
<VStack className="flex-1" space="xs">
<Text className="text-xs font-medium text-gray-600 dark:text-gray-300">{t('command.limit_worktime_amber')}</Text>
<Input size="sm" variant="outline">
<InputField accessibilityLabel={t('command.limit_worktime_amber')} keyboardType="number-pad" placeholder={t('command.limit_none')} value={workTimeAmber} onChangeText={(v) => setWorkTimeAmber(v.replace(/[^0-9]/g, ''))} testID="lane-worktime-amber" />
</Input>
</VStack>
<VStack className="flex-1" space="xs">
<Text className="text-xs font-medium text-gray-600 dark:text-gray-300">{t('command.limit_worktime_red')}</Text>
<Input size="sm" variant="outline">
<InputField accessibilityLabel={t('command.limit_worktime_red')} keyboardType="number-pad" placeholder={t('command.limit_none')} value={workTimeRed} onChangeText={(v) => setWorkTimeRed(v.replace(/[^0-9]/g, ''))} testID="lane-worktime-red" />
</Input>
</VStack>
</HStack>
<Text className="text-xs text-gray-500 dark:text-gray-400">{t('command.limit_worktime_help')}</Text>
</VStack>
🤖 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/command/lane-details-sheet.tsx` around lines 301 - 319, Add
translated accessibilityLabel values to the amber and red InputField elements in
the worktime thresholds section, using the corresponding threshold label
translations so VoiceOver/TalkBack can distinguish them after values are
entered. Keep the existing input behavior and testIDs unchanged.

Source: Coding guidelines

Comment thread src/stores/app/livekit-store.ts
keyboardType="number-pad"
placeholder={t('command.limit_none')}
value={limits.workTimeAmberMinutes}
onChangeText={(v) => setLimit('workTimeAmberMinutes', v)}

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 functions in JSX props create new function instances on every render, impacting performance. Move these handlers outside the render method or memoize them.

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

Prompt for LLM

File src/components/command/add-lane-sheet.tsx:

Line 202:

Inline arrow functions in JSX props create new function instances on every render, impacting performance. Move these handlers outside the render method or memoize them.

Talk to Kody by mentioning @kody

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

minTimeInRole: '',
maxTimeInRole: '',
workTimeAmberMinutes: '20',
workTimeRedMinutes: '40',

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 number '40' is duplicated as the work-time red threshold in both the useState initializer and reset logic. Extract a named constant such as DEFAULT_WORK_TIME_RED_MINUTES = 40 and reuse it in both locations to prevent drift.

Kody rule violation: Replace magic numbers with named constants

Prompt for LLM

File src/components/command/add-lane-sheet.tsx:

Line 67:

Magic number `'40'` is duplicated as the work-time red threshold in both the `useState` initializer and reset logic. Extract a named constant such as `DEFAULT_WORK_TIME_RED_MINUTES = 40` and reuse it in both locations to prevent drift.

Talk to Kody by mentioning @kody

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


export const workTimeColor = (minutes: number) => (minutes < WORK_TIME_AMBER_MINUTES ? '#22c55e' : minutes < WORK_TIME_RED_MINUTES ? '#f59e0b' : '#ef4444');
export const workTimeColor = (minutes: number, amberAfterMinutes: number = DEFAULT_WORK_TIME_AMBER_MINUTES, redAfterMinutes: number = DEFAULT_WORK_TIME_RED_MINUTES) =>
redAfterMinutes > 0 && minutes >= redAfterMinutes ? '#ef4444' : amberAfterMinutes > 0 && minutes >= amberAfterMinutes ? '#f59e0b' : '#22c55e';

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

Hex color literals '#ef4444', '#f59e0b', and '#22c55e' are inlined in multiple locations within the file, risking palette drift. Define them once as named constants such as WORK_TIME_RED, WORK_TIME_AMBER, and WORK_TIME_GREEN and reference those everywhere.

Kody rule violation: Centralize string constants

Prompt for LLM

File src/components/command/landscape-structure-board.tsx:

Line 34:

Hex color literals `'#ef4444'`, `'#f59e0b'`, and `'#22c55e'` are inlined in multiple locations within the file, risking palette drift. Define them once as named constants such as `WORK_TIME_RED`, `WORK_TIME_AMBER`, and `WORK_TIME_GREEN` and reference those everywhere.

Talk to Kody by mentioning @kody

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

Comment on lines +96 to +97
WorkTimeAmberMinutes: parseInt(workTimeAmber, 10) || 0,
WorkTimeRedMinutes: parseInt(workTimeRed, 10) || 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 Bug high

LaneDetailsSheet persists 0 instead of undefined for blank work-time threshold fields, permanently disabling the default 20/40 fatigue indicator for any edited lane. The expression parseInt('', 10) || 0 produces 0, which fails the > 0 guard in workTimeColor and silently turns the indicator green. Omit the field from the patch when blank so undefined preserves default behavior.

WorkTimeAmberMinutes: workTimeAmber ? parseInt(workTimeAmber, 10) : undefined,
      WorkTimeRedMinutes: workTimeRed ? parseInt(workTimeRed, 10) : undefined,
Prompt for LLM

File src/components/command/lane-details-sheet.tsx:

Line 96 to 97:

LaneDetailsSheet persists `0` instead of `undefined` for blank work-time threshold fields, permanently disabling the default 20/40 fatigue indicator for any edited lane. The expression `parseInt('', 10) || 0` produces `0`, which fails the `> 0` guard in `workTimeColor` and silently turns the indicator green. Omit the field from the patch when blank so `undefined` preserves default behavior.

Suggested Code:

WorkTimeAmberMinutes: workTimeAmber ? parseInt(workTimeAmber, 10) : undefined,
      WorkTimeRedMinutes: workTimeRed ? parseInt(workTimeRed, 10) : undefined,

Talk to Kody by mentioning @kody

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

<VStack className="flex-1" space="xs">
<Text className="text-xs font-medium text-gray-600 dark:text-gray-300">{t('command.limit_worktime_amber')}</Text>
<Input size="sm" variant="outline">
<InputField keyboardType="number-pad" placeholder={t('command.limit_none')} value={workTimeAmber} onChangeText={(v) => setWorkTimeAmber(v.replace(/[^0-9]/g, ''))} testID="lane-worktime-amber" />

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

The digit-only sanitization v.replace(/[^0-9]/g, '') is duplicated across both work-time inputs. Extract a shared helper such as const sanitizeMinutes = (v: string) => v.replace(/[^0-9]/g, ''); and reuse it for the amber and red fields.

Kody rule violation: Extract duplicated logic into functions

Prompt for LLM

File src/components/command/lane-details-sheet.tsx:

Line 307:

The digit-only sanitization `v.replace(/[^0-9]/g, '')` is duplicated across both work-time inputs. Extract a shared helper such as `const sanitizeMinutes = (v: string) => v.replace(/[^0-9]/g, '');` and reuse it for the amber and red fields.

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 28, 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.

// prompt itself when a track is published.
const micPermission = await getRecordingPermissionsAsync();

if (!micPermission.granted && !micPermission.canAskAgain) {

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 logic at lines 407-414 and 949-956 checks permanently-denied permissions, logs warnings, and returns early with only the variable name differing. Extract a helper such as handlePermanentlyDeniedPermission(mic, Platform.OS) to encapsulate the !granted && !canAskAgain check and return false from both sites.

Kody rule violation: Extract duplicated logic into functions

Prompt for LLM

File src/stores/app/livekit-store.ts:

Line 407:

Duplicated logic at lines 407-414 and 949-956 checks permanently-denied permissions, logs warnings, and returns early with only the variable name differing. Extract a helper such as `handlePermanentlyDeniedPermission(mic, Platform.OS)` to encapsulate the `!granted && !canAskAgain` check and return false from both sites.

Talk to Kody by mentioning @kody

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

@ucswift
ucswift merged commit c103fe4 into master Jul 28, 2026
9 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