Skip to content

Develop - #123

Open
ucswift wants to merge 2 commits into
masterfrom
develop
Open

Develop#123
ucswift wants to merge 2 commits into
masterfrom
develop

Conversation

@ucswift

@ucswift ucswift commented Aug 8, 2026

Copy link
Copy Markdown
Member

Pull Request Description

This PR introduces a department-scoped feature flag system to the Resgrid Dispatch mobile app, with the Chat system as the first feature gated behind it.

What was added

  1. Feature Flags API Client (src/api/feature-flags/feature-flags.ts): New API integration to evaluate feature toggles from the v4 FeatureToggles API, supporting both bulk retrieval (GetAll) and single-flag state checks (GetState).

  2. Feature Flags Store (src/stores/feature-flags/store.ts): A persisted Zustand store that fetches and caches feature flags. It retains flags on fetch failure to keep gating stable while offline, and unknown flags default to disabled until the server confirms them.

  3. Chat Feature Gating: The Chat.System flag controls all chat surfaces:

    • App initialization (_layout.tsx): Feature flags are fetched after security rights load; the SignalR chat hub only connects when chat is enabled.
    • All chat screens (chat.tsx, chatbot.tsx, chat/[channelId].tsx, chat/thread/[messageId].tsx): Redirect to home when chat is disabled, and skip data-fetching effects.
    • Side menu (side-menu.tsx): Chat and Assistant menu items are hidden when the flag is off.
  4. Documentation: Updated audio stream refactoring docs with Expo SDK 56 migration guidance for expo-audio/expo-av.

@Resgrid-Bot

Resgrid-Bot commented Aug 8, 2026

Copy link
Copy Markdown

Code Review Completed! 🔥

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

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

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

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

Current Kody Configuration
Review Options

The following review options are enabled or disabled:

Options Enabled
Bug
Performance
Security
Business Logic

Access your configuration settings here.

@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Added API-backed feature flags with persisted Zustand state. The ChatSystem flag now controls chat hub initialization, chat screens, chatbot loading, thread and channel requests, redirects, and sidebar visibility. Added Expo audio migration guidance.

Changes

Feature flag chat gating

Layer / File(s) Summary
Feature flag API and store
src/api/feature-flags/feature-flags.ts, src/stores/feature-flags/store.ts
Added typed API helpers and a persisted store with loading, errors, default-disabled checks, and chat-specific hooks.
Chat initialization gate
src/app/(app)/_layout.tsx
Loads feature flags after security rights and connects the chat hub only when ChatSystem is enabled.
Chat surface and menu gating
src/app/(app)/chat.tsx, src/app/(app)/chatbot.tsx, src/app/chat/..., src/components/sidebar/side-menu.tsx
Guards chat operations and data loading, redirects disabled screens to /home, and hides Chat and Assistant menu entries when disabled.

Audio migration guidance

Layer / File(s) Summary
Expo audio migration requirements
docs/audio-stream-refactoring.md
Documents the Expo SDK 56 expo-audio migration, expo-av removal, device testing, and SDK 54 compatibility constraints.

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

Sequence Diagram(s)

sequenceDiagram
  participant TabLayout
  participant FeatureFlagStore
  participant FeatureFlagAPI
  participant ChatHub
  participant ChatScreen

  TabLayout->>FeatureFlagStore: fetchFlags()
  FeatureFlagStore->>FeatureFlagAPI: getAllFeatureFlags()
  FeatureFlagAPI-->>FeatureFlagStore: ChatSystem state
  alt ChatSystem enabled
    TabLayout->>ChatHub: connect
    ChatScreen->>FeatureFlagStore: useIsChatEnabled()
    ChatScreen->>ChatHub: fetch chat data
  else ChatSystem disabled
    TabLayout-->>TabLayout: skip chat connection
    ChatScreen-->>ChatScreen: redirect to /home
  end
Loading

Possibly related PRs

Suggested reviewers: github-actions

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.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 "Develop" is too generic and does not identify the feature-flag and chat-gating changes in the pull request. Replace "Develop" with a concise title that describes the primary change, such as "Add chat feature-flag support".
✅ 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 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🛠️ Fix failing CI checks 💡
  • 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
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

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

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

69-78: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Gate the remaining channel requests.

Lines 69-78 skip channel activation, but the presence request at Lines 82-94 and markChannelRead at Lines 97-101 still run when chat is disabled. A disabled deep link can therefore read presence or update chat state before navigation completes.

Add isChatEnabled guards and dependencies to both effects.

Proposed guard changes
 useEffect(() => {
+  if (!isChatEnabled) return;
   const ids = (members ?? []).map((m) => m.UserId).filter((id): id is string => !!id && id !== currentUserId);
   if (ids.length === 0) return;
   // ...
-}, [members, currentUserId]);
+}, [members, currentUserId, isChatEnabled]);

 useEffect(() => {
-  if (channelId && inverted.length > 0) {
+  if (isChatEnabled && channelId && inverted.length > 0) {
     void useChatStore.getState().markChannelRead(channelId);
   }
-}, [channelId, inverted.length]);
+}, [channelId, inverted.length, isChatEnabled]);
🤖 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/chat/`[channelId].tsx around lines 69 - 78, Update the
presence-request effect and the markChannelRead effect in the chat channel
component to return early when isChatEnabled is false, matching the existing
guard around setActiveChannel and channel loading. Add isChatEnabled to both
effects’ dependency arrays while preserving their current behavior when chat is
enabled.
🤖 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 `@docs/audio-stream-refactoring.md`:
- Around line 9-10: Update the later Overview, Key Changes, and Installation
sections in the audio-stream refactoring document to consistently describe the
expo-audio SDK 56 migration using createAudioPlayer, setAudioModeAsync,
AudioPlayer, and playbackStatusUpdate; remove or explicitly label any remaining
expo-av guidance as an SDK 54 baseline.
- Around line 3-5: Update the “Expo SDK 56 migration requirement” guidance to
sequence the expo-audio dependency update during the SDK 56 upgrade rather than
before it, and describe alignment through the SDK upgrade process. If specifying
a package version, use the tested SDK 56-compatible expo-audio version;
otherwise remove the unsupported prerequisite-version wording while retaining
the expo-av replacement requirement.

In `@src/stores/feature-flags/store.ts`:
- Around line 62-66: Separate unresolved feature flags from disabled flags by
adding non-persisted current-department resolution state alongside the enabled
value in useFeatureFlag within src/stores/feature-flags/store.ts#L62-L66. Update
the redirect/rendering logic in src/app/(app)/chat.tsx#L109-L112,
src/app/(app)/chatbot.tsx#L68-L71, src/app/chat/[channelId].tsx#L239-L242, and
src/app/chat/thread/[messageId].tsx#L101-L104 to defer redirects while
unresolved, using ternary rendering for pending, enabled, and disabled states;
the store change is the root fix and all listed route sites require
corresponding updates.
- Around line 55-58: Scope feature-flag persistence to the current
DepartmentCode, or clear the feature-flags store before logout and department
changes. Update the feature-flags store around the feature-flags-storage
configuration and ensure stale flags cannot survive identity changes or fetch
failures for a new department.

---

Outside diff comments:
In `@src/app/chat/`[channelId].tsx:
- Around line 69-78: Update the presence-request effect and the markChannelRead
effect in the chat channel component to return early when isChatEnabled is
false, matching the existing guard around setActiveChannel and channel loading.
Add isChatEnabled to both effects’ dependency arrays while preserving their
current behavior when chat is enabled.
🪄 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: 0aaa6653-a6c7-4828-bf10-32dd7eb6935c

📥 Commits

Reviewing files that changed from the base of the PR and between f66174d and bb6b160.

📒 Files selected for processing (9)
  • docs/audio-stream-refactoring.md
  • src/api/feature-flags/feature-flags.ts
  • src/app/(app)/_layout.tsx
  • src/app/(app)/chat.tsx
  • src/app/(app)/chatbot.tsx
  • src/app/chat/[channelId].tsx
  • src/app/chat/thread/[messageId].tsx
  • src/components/sidebar/side-menu.tsx
  • src/stores/feature-flags/store.ts

Comment on lines +3 to +5
## Expo SDK 56 migration requirement

Before upgrading Dispatch to Expo SDK 56, upgrade `expo-audio` to the SDK 56-compatible version and replace all remaining `expo-av` audio usage with `expo-audio`. SDK 56 no longer provides the legacy Expo Modules Core header required by `expo-av` 16, so leaving `expo-av` installed can break the iOS archive build.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Locate document and relevant docs/config:"
fd -a 'audio-stream-refactoring\.md|app\.json|app\.config\.(js|ts)|package\.json|expo\.config\.(js|ts)|babel\.config' . | sed 's#^\./##' | head -200

echo
echo "Show audio-stream-reffactoring.md context:"
if [ -f docs/audio-stream-refactoring.md ]; then
  nl -ba docs/audio-stream-refactoring.md | sed -n '1,220p'
fi

echo
echo "Find expo versions and audio packages:"
rg -n '"expo"|"react-native"|"expo-audio"|"expo-av"|"babel-preset-expo"|SDK 56|SDK 54' -S --glob '!node_modules' --glob '!dist' --glob '!build' .

Repository: Resgrid/Dispatch

Length of output: 433


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Audio-refactoring context:"
awk '{ printf "%6d	%s\n", NR, $0 }' docs/audio-stream-refactoring.md | sed -n '1,220p'

echo
echo "Package/Expo config snippets:"
for f in app.config.ts babel.config.js package.json; do
  echo "--- $f ---"
  awk '{ printf "%6d	%s\n", NR, $0 }' "$f" | sed -n '1,220p'
done

echo
echo "Search relevant strings:"
rg -n '"expo"|"react-native"|"expo-audio"|"expo-av"|"babel-preset-expo"|SDK 56|SDK 54' -S --glob '!node_modules' --glob '!dist' --glob '!build' .

Repository: Resgrid/Dispatch

Length of output: 27844


🌐 Web query:

Expo SDK 54 expo-audio 1.1 installable expo-audio 16 expo-audio SDK 56 compatibility expo doc

💡 Result:

The expo-audio library is fully compatible with and designed for modern Expo SDKs, including SDK 54 and SDK 56. 1. Expo SDK 54: expo-audio 1.1 is compatible and was the standard for audio in SDK 54 [1][2]. During the SDK 54 lifecycle, users were encouraged to migrate to expo-audio from the deprecated expo-av [3][4]. You can install the version appropriate for your SDK using the command: npx expo install expo-audio [5]. 2. Expo SDK 56: expo-audio version 56.x is the version associated with SDK 56 [6][7]. The library received significant updates in SDK 56, including a new useAudioStream hook and various live-streaming improvements [8][9]. Important Note on Versions: In the Expo ecosystem, you should not manually choose a version number like 16 for expo-audio unless it specifically aligns with your SDK. Instead, always use the command npx expo install expo-audio [5]. This command ensures that the Expo CLI automatically installs the specific version of the package that is tested and compatible with your current Expo SDK version [5]. Using version 16 with SDK 56 is likely incorrect, as the package versions for expo-audio follow the SDK release numbering (e.g., version 56.x for SDK 56) [6][7]. Always verify your dependencies using npx expo-doctor after installation [5].

Citations:


🌐 Web query:

docs.expo.dev "expo-audio" "Expo SDK 56"

💡 Result:

In Expo SDK 56, the expo-audio library is officially supported and includes several key updates [1][2]. Key features and improvements for expo-audio in SDK 56 include: - New useAudioStream Hook: This hook provides real-time access to microphone buffer data, enabling new possibilities for audio processing [1][3]. - Live-Stream Enhancements: - An isLiveStream option is now available for the lock-screen on iOS [1][2]. - A playsInSilentMode option has been added for Android [1][3]. - The AudioStatus object has been updated to include new fields: isLive, currentOffsetFromLive, and error [1][2]. You can access the official documentation for this version at https://docs.expo.dev/versions/v56.0.0/sdk/audio/ [1][2].

Citations:


🌐 Web query:

Expo docs "Before upgrading" "During the upgrade" package compatibility SDK 56 expo install

💡 Result:

To upgrade your project to Expo SDK 56, follow these recommended practices regarding preparation, package compatibility, and the installation process [1][2][3]. Preparation and Best Practices Before starting the upgrade, it is strongly recommended that you read the official SDK 56 changelog and breaking changes documentation [1][4]. This allows you to identify necessary configuration adjustments specific to your application before beginning the actual upgrade process [1]. Additionally, use a dedicated git branch for the upgrade to isolate changes [2][3]. If you are still using Expo Go for development, it is recommended to migrate to development builds first, as this provides a more stable baseline for testing and debugging during and after the upgrade [5]. It is also advised to upgrade one major change at a time (e.g., upgrading the SDK version first, then enabling the New Architecture separately) to make it easier to isolate and troubleshoot any issues [5][4][6]. Package Compatibility and Installation During the upgrade, you should prioritize ensuring that your dependencies are compatible with the new SDK version [2][3]. The primary command for updating your Expo-related packages is [2][3]: npx expo install expo@^56.0.0 After updating the core expo package, run the package-aware installer to automatically align your remaining dependencies with the requirements of SDK 56 [2][3]: npx expo install --fix Post-Upgrade Verification Once the installation is complete, it is crucial to verify your project's health. The recommended next step is to run Expo Doctor, which is designed to detect mismatched versions, incompatible dependencies, and configuration issues [2][3]: npx expo-doctor If your project utilizes Expo Router, be aware that SDK 56 includes internal migration work; if you encounter issues, you may need to use the provided codemod to handle transitions between file-based routing and React Navigation imports [2][3]: npx expo-codemod sdk-56-expo-router-react-navigation-replace src Finally, rebuild your development builds and test thoroughly before deploying to production [2][3].

Citations:


Sequence the SDK 56 package update with the SDK 56 upgrade.

Expo’s SDK upgrade process updates Expo packages with the SDK and uses npx expo install --fix to align dependencies. Keep this guidance focused on the SDK upgrade, or change “Before upgrading” to “During the SDK 56 upgrade” and add the tested expo-audio package version.

🤖 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 `@docs/audio-stream-refactoring.md` around lines 3 - 5, Update the “Expo SDK 56
migration requirement” guidance to sequence the expo-audio dependency update
during the SDK 56 upgrade rather than before it, and describe alignment through
the SDK upgrade process. If specifying a package version, use the tested SDK
56-compatible expo-audio version; otherwise remove the unsupported
prerequisite-version wording while retaining the expo-av replacement
requirement.

Source: MCP tools

Comment on lines +9 to +10
- Migrate `src/hooks/use-ptt.ts`, `src/components/calls/call-audio-modal.tsx`, `src/stores/app/audio-stream-store.ts`, and `src/services/audio.service.ts` to `createAudioPlayer`, `setAudioModeAsync`, `AudioPlayer`, and `playbackStatusUpdate`.
- Remove `expo-av` from `package.json`, the lockfile, tests/mocks, and the Expo Doctor exclusion after no imports remain.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== file excerpt =="
if [ -f docs/audio-stream-refactoring.md ]; then
  wc -l docs/audio-stream-refactoring.md
  sed -n '1,220p' docs/audio-stream-refactoring.md
else
  echo "docs/audio-stream-refactoring.md not found"
fi

echo
echo "== expo-av mentions in docs/audio-stream-refactoring.md =="
rg -n "expo-av|expo-audio|audio-av|createAudioPlayer|setAudioModeAsync|AudioPlayer|playbackStatusUpdate|Migrate|Remove" docs/audio-stream-refactoring.md || true

Repository: Resgrid/Dispatch

Length of output: 7247


🌐 Web query:

Expo SDK 56 expo-av expo-audio expo-audio SDK 56 createAudioPlayer AudioPlayer

💡 Result:

In Expo SDK 56, the expo-audio library is the standard tool for audio playback and recording, having replaced the deprecated expo-av package [1][2]. To work with audio, you primarily use the useAudioPlayer hook or, in advanced scenarios where the player must persist beyond a component's lifecycle, the createAudioPlayer function [3][4]. 1. useAudioPlayer(source, options): This is the recommended approach for most use cases [3]. It is a React hook that automatically manages the lifecycle of the AudioPlayer instance, ensuring that resources are properly disposed of when the component unmounts [5][6]. 2. createAudioPlayer(source, options): This function allows you to create an AudioPlayer instance manually [3][5]. Because this instance is not automatically managed by a component's lifecycle, it is your responsibility to call the release method on the player when it is no longer needed to prevent memory leaks [3][4]. Key differences when migrating from expo-av include: - Time is measured in seconds rather than milliseconds [7]. - Audio loads immediately upon creation [7]. - There is no automatic reset to the beginning when playback finishes; if you wish to replay, you must manually call player.seekTo(0) before calling play [7][8]. For additional functionality, expo-audio also provides the useAudioRecorder hook for recording [7][3].

Citations:


Align expo-av references with the migration goal.

Lines 9-10 remove expo-av, but later sections describe expo-av, the Overview, the Key Changes, and the Installation steps still instruct using expo-av. Rewrite those sections to describe the expo-audio SDK 56 implementation, or label them as an SDK 54 baseline 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 `@docs/audio-stream-refactoring.md` around lines 9 - 10, Update the later
Overview, Key Changes, and Installation sections in the audio-stream refactoring
document to consistently describe the expo-audio SDK 56 migration using
createAudioPlayer, setAudioModeAsync, AudioPlayer, and playbackStatusUpdate;
remove or explicitly label any remaining expo-av guidance as an SDK 54 baseline.

Source: MCP tools

Comment on lines +55 to +58
{
name: 'feature-flags-storage',
storage: createJSONStorage(() => zustandStorage),
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate persistence reset or rehydration calls for this store.
rg -n -C 3 'feature-flags-storage|featureFlagsStore\.(persist\.)?(clearStorage|rehydrate)|featureFlagsStore\.setState' src

# Inspect authentication and department-transition paths for a feature-flag reset.
rg -n -C 3 '\b(signOut|logout|signedOut|DepartmentCode)\b' src

Repository: Resgrid/Dispatch

Length of output: 24927


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== feature-flags store outline =="
ast-grep outline src/stores/feature-flags/store.ts --view expanded || true

echo "== feature-flags store contents =="
cat -n src/stores/feature-flags/store.ts

echo "== feature-flags imports/usages =="
rg -n -C 4 'featureFlagsStore|feature.*flags|Flags|Chat\.System' src --glob '!src/translations/**'

echo "== auth store relevant logout path =="
sed -n '120,175p' src/stores/auth/store.tsx | cat -n

echo "== any clearStorage/rehydrate / storage reset =="
rg -n 'clearStorage|rehydrate\(\)|setPersist|persist\.clearStorage|name:\s*feature-flags-storage' src --glob '!src/translations/**'

Repository: Resgrid/Dispatch

Length of output: 21012


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== app/_layout.tsx relevant initialization and gating =="
sed -n '330,410p' src/app/(' '(app))/_layout.tsx | cat -n
sed -n '520,555p' src/app/(' '(app))/_layout.tsx | cat -n

echo "== security/store identity change helpers and DepartmentCode state =="
cat -n src/stores/security/store.ts

echo "== all DepartmentCode identifiers =="
rg -n -C 3 'DepartmentCode|departmentCode|setDepartment|Department' src/stores src/services src/app src/components \
  --glob '!src/translations/**' --glob '!src/__tests__/**' \
  | head -n 220

echo "== imports/features around auth/store and security =="
rg -n -C 3 'useSecurityStore|securityStore|useAuthStore|useAuth|logout\(|signOut' src/app src/hooks src/stores src/components \
  --glob '!src/translations/**' --glob '!src/__tests__/**' \
  | head -n 260

echo "== targeted reset calls =="
rg -n 'clearStorage|featureFlagsStore\.(persist\.)?(clearStorage|rehydrate)|featureFlagsStore\.setState\(|logout\(\)\s*$|signOut\(\)\s*$' src --glob '!src/translations/**'

Repository: Resgrid/Dispatch

Length of output: 264


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== app/_layout.tsx relevant initialization and gating =="
sed -n '330,410p' 'src/app/(app)/_layout.tsx' | cat -n
sed -n '520,555p' 'src/app/(app)/_layout.tsx' | cat -n

echo "== security/store identity change helpers and DepartmentCode state =="
cat -n src/stores/security/store.ts

echo "== all DepartmentCode identifiers =="
rg -n -C 3 'DepartmentCode|departmentCode|setDepartment|Department' src/stores src/services src/app src/components \
  --glob '!src/translations/**' --glob '!src/__tests__/**' \
  | head -n 220

echo "== imports/features around auth/store and security =="
rg -n -C 3 'useSecurityStore|securityStore|useAuthStore|useAuth|logout\(|signOut' src/app src/hooks src/stores src/components \
  --glob '!src/translations/**' --glob '!src/__tests__/**' \
  | head -n 260

echo "== targeted reset calls =="
rg -n 'clearStorage|featureFlagsStore\.(persist\.)?(clearStorage|rehydrate)|featureFlagsStore\.setState\(|logout\(\)\s*$|signOut\(\)\s*$' src --glob '!src/translations/**'

Repository: Resgrid/Dispatch

Length of output: 44059


Reset or scope feature-flag persistence on sign-out and department changes.

feature-flags-storage is device-wide, while getAllFeatureFlags() evaluates flags for the current department. src/app/(app)/_layout.tsx fetches flags after rights, but saveFlags preserves the previous flags on fetch failure and src/stores/auth/store.tsx logout does not clear them. A prior department can leave Chat.System: true for the next department. Use a DepartmentCode-scoped storage key or clear this store before identity changes.

🤖 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/feature-flags/store.ts` around lines 55 - 58, Scope feature-flag
persistence to the current DepartmentCode, or clear the feature-flags store
before logout and department changes. Update the feature-flags store around the
feature-flags-storage configuration and ensure stale flags cannot survive
identity changes or fetch failures for a new department.

Comment on lines +62 to +66
// Reactive hook; components re-render when the flag changes. Unknown flags default to disabled
// so gated features stay hidden until the server confirms them.
export const useFeatureFlag = (key: string, defaultValue = false) => featureFlagsStore((state) => state.flags[key]?.enabled ?? defaultValue);

export const useIsChatEnabled = () => useFeatureFlag(FeatureFlagKeys.ChatSystem);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Separate unresolved flags from disabled flags.

useFeatureFlag returns false before the current department flag request completes. Each route then treats an unresolved flag as a confirmed disabled flag and redirects enabled deep links.

  • src/stores/feature-flags/store.ts#L62-L66: expose a non-persisted current-department resolution state with the enabled value.
  • src/app/(app)/chat.tsx#L109-L112: defer the redirect until feature flags resolve.
  • src/app/(app)/chatbot.tsx#L68-L71: defer the redirect until feature flags resolve.
  • src/app/chat/[channelId].tsx#L239-L242: defer the redirect until feature flags resolve.
  • src/app/chat/thread/[messageId].tsx#L101-L104: defer the redirect until feature flags resolve.

Use a ternary to render the pending, enabled, and disabled states.

As per coding guidelines, use ternary operator (? :) for conditional rendering and not && operator.

📍 Affects 5 files
  • src/stores/feature-flags/store.ts#L62-L66 (this comment)
  • src/app/(app)/chat.tsx#L109-L112
  • src/app/(app)/chatbot.tsx#L68-L71
  • src/app/chat/[channelId].tsx#L239-L242
  • src/app/chat/thread/[messageId].tsx#L101-L104
🤖 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/feature-flags/store.ts` around lines 62 - 66, Separate unresolved
feature flags from disabled flags by adding non-persisted current-department
resolution state alongside the enabled value in useFeatureFlag within
src/stores/feature-flags/store.ts#L62-L66. Update the redirect/rendering logic
in src/app/(app)/chat.tsx#L109-L112, src/app/(app)/chatbot.tsx#L68-L71,
src/app/chat/[channelId].tsx#L239-L242, and
src/app/chat/thread/[messageId].tsx#L101-L104 to defer redirects while
unresolved, using ternary rendering for pending, enabled, and disabled states;
the store change is the root fix and all listed route sites require
corresponding updates.

Source: Coding guidelines


/** Evaluates every active flag for the caller's department. */
export const getAllFeatureFlags = async (signal?: AbortSignal) => {
const response = await api.get<FeatureTogglesResult>(`${FEATURE_TOGGLES}/GetAll`, { signal });

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

Unguarded external HTTP call via api.get violates Rule [27] by lacking contextual error mapping. Wrap the call in a try/catch block, attach the operation name and endpoint, and map errors to a feature-flags domain error or safe default.

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

Prompt for LLM

File src/api/feature-flags/feature-flags.ts:

Line 29:

Unguarded external HTTP call via `api.get` violates Rule [27] by lacking contextual error mapping. Wrap the call in a try/catch block, attach the operation name and endpoint, and map errors to a feature-flags domain error or safe default.

Talk to Kody by mentioning @kody

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

Comment thread src/app/(app)/_layout.tsx
// Connect the realtime chat hub (best-effort; chat may be disabled per department)
try {
await useSignalRStore.getState().connectChatHub();
// Connect the realtime chat hub only when the Chat.System feature flag is on for

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

Feature flag bypass occurs in useSignalRLifecycle, which unconditionally reconnects the chat hub on app resume via signalRStore.connectChatHub() at use-signalr-lifecycle.ts:121, defeating the Chat.System gate at _layout.tsx:182. Thread the feature flag into the resume reconnect path in handleAppResume to skip connectChatHub() when featureFlagsStore.getState().isEnabled(FeatureFlagKeys.ChatSystem) is false.

// Gate must also be enforced in use-signalr-lifecycle.ts handleAppResume:
// const hubs = [signalRStore.connectUpdateHub(), signalRStore.connectGeolocationHub()];
// if (featureFlagsStore.getState().isEnabled(FeatureFlagKeys.ChatSystem)) {
//   hubs.push(signalRStore.connectChatHub());
// }
// const results = await Promise.allSettled(hubs);
Prompt for LLM

File src/app/(app)/_layout.tsx:

Line 180:

Feature flag bypass occurs in `useSignalRLifecycle`, which unconditionally reconnects the chat hub on app resume via `signalRStore.connectChatHub()` at `use-signalr-lifecycle.ts:121`, defeating the `Chat.System` gate at `_layout.tsx:182`. Thread the feature flag into the resume reconnect path in `handleAppResume` to skip `connectChatHub()` when `featureFlagsStore.getState().isEnabled(FeatureFlagKeys.ChatSystem)` is false.

Suggested Code:

// Gate must also be enforced in use-signalr-lifecycle.ts handleAppResume:
// const hubs = [signalRStore.connectUpdateHub(), signalRStore.connectGeolocationHub()];
// if (featureFlagsStore.getState().isEnabled(FeatureFlagKeys.ChatSystem)) {
//   hubs.push(signalRStore.connectChatHub());
// }
// const results = await Promise.allSettled(hubs);

Talk to Kody by mentioning @kody

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

Comment thread src/app/(app)/_layout.tsx
context: { platform: Platform.OS },
});

await featureFlagsStore.getState().fetchFlags();

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

Unguarded awaited fetchFlags() call risks unhandled rejections that fail app initialization. Wrap the call in a try/catch block and log the error context to comply with Rule [1].

Kody rule violation: Handle async operations with proper error handling

Prompt for LLM

File src/app/(app)/_layout.tsx:

Line 158:

Unguarded awaited `fetchFlags()` call risks unhandled rejections that fail app initialization. Wrap the call in a try/catch block and log the error context to comply with Rule [1].

Talk to Kody by mentioning @kody

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

Comment thread src/app/(app)/chat.tsx

// Chat.System feature flag off: no chat for this department.
if (!isChatEnabled) {
return <Redirect href={'/home' as Href} />;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules low

Magic string '/home' for a finite application route reduces maintainability and risks typos. Define route names as a Route enum or const tuple instead of using raw strings.

Kody rule violation: Use enums instead of magic strings

Prompt for LLM

File src/app/(app)/chat.tsx:

Line 111:

Magic string '/home' for a finite application route reduces maintainability and risks typos. Define route names as a Route enum or const tuple instead of using raw strings.

Talk to Kody by mentioning @kody

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

const menuItems = getMenuItems(t);
const isChatEnabled = useIsChatEnabled();
// Chat and the assistant are gated by the Chat.System feature flag.
const menuItems = getMenuItems(t).filter((item) => (item.id === 'chat' || item.id === 'assistant' ? isChatEnabled : true));

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 inline string literals 'chat' and 'assistant' as menu-item identifiers risk drift across getMenuItems and other components. Define these shared keys as centralized constants, such as MenuItemIds.Chat and MenuItemIds.Assistant, to ensure consistency.

Kody rule violation: Centralize string constants

Prompt for LLM

File src/components/sidebar/side-menu.tsx:

Line 100:

Duplicated inline string literals 'chat' and 'assistant' as menu-item identifiers risk drift across `getMenuItems` and other components. Define these shared keys as centralized constants, such as `MenuItemIds.Chat` and `MenuItemIds.Assistant`, to ensure consistency.

Talk to Kody by mentioning @kody

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

flags: {},
isLoaded: false,
error: null,
fetchFlags: async () => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules low

Missing JSDoc on the async fetchFlags method hides its failure semantics, specifically that it swallows errors and sets the error state rather than rejecting. Add a JSDoc block to document the resolve value, rejection conditions, and await usage to satisfy Rule [22].

Kody rule violation: Document async/Promise behavior and errors

Prompt for LLM

File src/stores/feature-flags/store.ts:

Line 34:

Missing JSDoc on the async `fetchFlags` method hides its failure semantics, specifically that it swallows errors and sets the `error` state rather than rejecting. Add a JSDoc block to document the resolve value, rejection conditions, and await usage to satisfy Rule [22].

Talk to Kody by mentioning @kody

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

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