Skip to content

RU-T52 Expo 56, Livekit 2.10 and Gluestack v5 Update - #259

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

RU-T52 Expo 56, Livekit 2.10 and Gluestack v5 Update#259
ucswift merged 2 commits into
masterfrom
develop

Conversation

@ucswift

@ucswift ucswift commented Jul 31, 2026

Copy link
Copy Markdown
Member

PR Description: RU-T52 Expo 56, Livekit 2.10 and Gluestack v5 Update

Summary

This PR upgrades the Resgrid Unit app to Expo SDK 56, LiveKit 2.10, and Gluestack UI v5 / NativeWind v5 / Tailwind v4, and includes several bug fixes and compatibility improvements that were required to complete the migration.


1. Framework & Platform Upgrades

  • Expo SDK 56: Bumps Android targetSdkVersion to 36, removes the manual newArchEnabled flag (now default), and adds expo-image, expo-sharing, and expo-status-bar plugins.
  • LiveKit 2.10: Dependency bump (per title).
  • React Native Reanimated v4: Jest resolver configured (react-native-worklets/jest/resolver.js) and EXPO_PUBLIC_USE_RN_FETCH env var set for test runtime.

2. Gluestack UI v5 + NativeWind v5 + Tailwind v4 Migration

This is the largest portion of the change set — every Gluestack UI component wrapper (src/components/ui/*) was rewritten:

Import path consolidation

  • Individual @gluestack-ui/<component> packages → unified @gluestack-ui/core/<component>/creator
  • @gluestack-ui/nativewind-utils/*@gluestack-ui/utils/nativewind-utils
  • Shared PrimitiveIcon boilerplate removed; components now use the core UIIcon wrapped with NativeWind's styled().

Styling bridge

  • cssInterop(Component, …) calls replaced with styled(Component, { className: 'style' }).
  • withStates / withStyleContextAndStates (platform-conditional) removed in favor of a single withStyleContext path.
  • Babel preset for NativeWind removed (v5 uses the Metro/CSS transform); metro.config.js switched to withNativewind; postcss.config.mjs added with @tailwindcss/postcss.

Tailwind v4

  • global.css rewritten from @tailwind directives to @import + @theme blocks, with all Gluestack design tokens expressed as CSS custom properties for light/dark (via prefers-color-scheme and .dark/.light class overrides).
  • tailwind.config.js dropped in favor of inline @theme declarations.
  • Utility renames applied across the app: shadow-smshadow-xs, rounded-smrounded-xs.

Animation library swap

  • @legendapp/motion (Motion.View, AnimatePresence, initial/animate/exit) replaced with react-native-reanimated (entering/exiting with FadeIn, ZoomIn, SlideIn*, etc.) in Modal, AlertDialog, Drawer, Menu, Popover, Toast, and Actionsheet.

Theme handling

  • GluestackUIProvider simplified — no longer injects JS config objects; instead toggles a light/dark className and uses Appearance.setColorScheme.
  • useColorScheme is now imported from nativewind (tracks manual overrides on web), and use-selected-theme drives appearance via Appearance.setColorScheme (native) and <html> class manipulation (web).
  • @custom-variant dark wired up so dark: variants work with the class-driven approach.

3. Navigation Consolidation

  • useFocusEffect, useIsFocused, DarkTheme, DefaultTheme, and ThemeProvider are now imported from expo-router instead of @react-navigation/native.
  • The standalone navigationRef (createNavigationContainerRef) export was removed.
  • FocusAwareStatusBar simplified for SDK 56 edge-to-edge (only visibility APIs remain) and no longer requires a NavigationContext guard.

4. Bug Fixes & Improvements

  • Web map globe crash: Mapbox GL JS v3 defaults to globe projection, whose ray-casting crashes on certain clicks. The web MapView now forces projection: 'mercator' at construction, on load, on style.load, and patches _updateProjection to swallow errors.
  • Static map aspect ratio: StaticMap now sizes the static image request to the actual viewport width (capped at 1280) so auto viewport pins stay centered in landscape.
  • Status bottom sheet tab snapping: The destination tab auto-selection now runs only when the sheet opens or the status changes, preventing the view from snapping back to the Calls tab after a manual selection.
  • Status detail backward compatibility: statusDetailAllowsStations/statusDetailAllowsPois now fall back to statusDetailAllowsCalls so legacy departments using older Detail values still surface destination options.
  • Sentinel filtering: The status store filters out server-side "No Call" and "No Station" sentinel entries from selectable destinations.
  • Actionsheet landscape height: A patch (patches/@gluestack-ui+core+5.0.15.patch) swaps the static window-height calculation for useWindowDimensions so the actionsheet reacts to orientation changes.
  • Web prop leakage: New toDomProps utility strips RN-only props (testID, numberOfLines, accessibility props, responder handlers, etc.) and maps testIDdata-testid for all web (*.web.tsx) UI wrappers, eliminating React "unknown prop" warnings.
  • Notification screens: NotificationDetail and NotificationInbox now read the color scheme via Appearance.getColorScheme() instead of the removed nativewind/colorScheme.get().
  • Web location marker pulse: Inline animationName style (rejected by react-native-web) replaced with a .pulse-ring CSS class in global.css.
  • Grid layout: Rewritten to compute item widths from a CSS-grid-equivalent formula and parse gap-* classes directly, removing the previous cssInterop-based nativeStyleToProp mapping.

5. Test Updates

All test files were updated to match the new architecture:

  • nativewind mocks now include styled and vars.
  • Focus/navigation mocks moved from @react-navigation/native to expo-router.
  • Additional RN component stubs (TurboModuleRegistry, Text, Modal, etc.) added where needed for the new Gluestack creator imports.
  • test-utils no longer wraps renders in a NavigationContainer.

Risk areas to verify: all UI components (modals, drawers, actionsheets, menus, popovers, toasts), dark/light theme switching on both native and web, and the web map interaction after the projection fix.

@Resgrid-Bot

This comment has been minimized.

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Important

Review skipped

Too many files!

This PR contains 151 files, which is 51 over the limit of 100.

To get a review, narrow the scope:
• coderabbit review --committed # exclude uncommitted changes
• coderabbit review --dir # limit to a subdirectory
• coderabbit review --base # compare against a closer base

Upgrade to a paid plan to raise the limit.

This review couldn't start because sufficient usage credits or metered capacity aren't available. Add credits or update usage-based reviews in the billing tab, then retry.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 9e0edef8-513e-480d-abea-00ff94ab7431

📥 Commits

Reviewing files that changed from the base of the PR and between a410c56 and 89c7bff.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (151)
  • .gitignore
  • .vscode/settings.json
  • app.config.ts
  • babel.config.js
  • global.css
  • jest-setup.ts
  • jest.config.js
  • metro.config.js
  • package.json
  • patches/@gluestack-ui+actionsheet+0.2.53.patch
  • patches/@gluestack-ui+core+5.0.15.patch
  • postcss.config.mjs
  • react-native-css-env.d.ts
  • src/app/(app)/__tests__/calls.test.tsx
  • src/app/(app)/__tests__/index.test.tsx
  • src/app/(app)/_layout.tsx
  • src/app/(app)/calls.tsx
  • src/app/(app)/index.tsx
  • src/app/(app)/weather-alerts.tsx
  • src/app/_layout.tsx
  • src/app/call/[id].tsx
  • src/app/call/__tests__/[id].security.test.tsx
  • src/app/call/__tests__/[id].test.tsx
  • src/app/call/new/__tests__/what3words.test.tsx
  • src/app/login/__tests__/login-form.test.tsx
  • src/app/maps/search.tsx
  • src/app/routes/active.tsx
  • src/app/routes/history/[planId].tsx
  • src/app/routes/history/instance/[id].tsx
  • src/app/routes/poi/[id].tsx
  • src/app/routes/start.tsx
  • src/app/routes/stop/[id].tsx
  • src/components/call-video-feeds/__tests__/video-feed-card.test.tsx
  • src/components/call-video-feeds/__tests__/video-feed-tab-content.test.tsx
  • src/components/calls/__tests__/call-notes-modal-new.test.tsx
  • src/components/calls/__tests__/close-call-bottom-sheet.test.tsx
  • src/components/calls/__tests__/dispatch-selection-basic.test.tsx
  • src/components/calls/__tests__/dispatch-selection-modal.test.tsx
  • src/components/calls/call-card.tsx
  • src/components/calls/call-notes-modal.tsx
  • src/components/check-in-timers/__tests__/check-in-bottom-sheet.test.tsx
  • src/components/check-in-timers/__tests__/check-in-timer-card.test.tsx
  • src/components/common/loading.tsx
  • src/components/contacts/contact-card.tsx
  • src/components/contacts/contact-details-sheet.tsx
  • src/components/incident-command/__tests__/incident-command-tab-panel.test.tsx
  • src/components/incident-command/incident-command-tab-panel.tsx
  • src/components/livekit/__tests__/livekit-bottom-sheet.test.tsx
  • src/components/maps/__tests__/full-screen-map.test.tsx
  • src/components/maps/__tests__/pin-actions.test.tsx
  • src/components/maps/map-view.web.tsx
  • src/components/maps/static-map.tsx
  • src/components/notes/__tests__/note-details-sheet.test.tsx
  • src/components/notes/note-card.tsx
  • src/components/notifications/NotificationDetail.tsx
  • src/components/notifications/NotificationInbox.tsx
  • src/components/protocols/__tests__/protocol-details-sheet.test.tsx
  • src/components/protocols/protocol-card.tsx
  • src/components/roles/__tests__/role-assignment-item.test.tsx
  • src/components/roles/__tests__/role-user-selection-modal.test.tsx
  • src/components/roles/__tests__/roles-bottom-sheet.test.tsx
  • src/components/routes/poi-card.tsx
  • src/components/routes/route-card.tsx
  • src/components/routes/stop-card.tsx
  • src/components/settings/__tests__/login-info-bottom-sheet-simple.test.tsx
  • src/components/settings/__tests__/login-info-bottom-sheet.test.tsx
  • src/components/settings/__tests__/server-url-bottom-sheet-simple.test.tsx
  • src/components/status/__tests__/status-bottom-sheet.test.tsx
  • src/components/status/status-bottom-sheet.tsx
  • src/components/ui/__tests__/actionsheet.test.tsx
  • src/components/ui/__tests__/bottom-sheet.test.tsx
  • src/components/ui/__tests__/shared-tabs.test.tsx
  • src/components/ui/accordion/index.tsx
  • src/components/ui/actionsheet/index.tsx
  • src/components/ui/alert-dialog/index.tsx
  • src/components/ui/alert/index.tsx
  • src/components/ui/avatar/index.tsx
  • src/components/ui/badge/index.tsx
  • src/components/ui/bottomsheet/index.tsx
  • src/components/ui/box/index.tsx
  • src/components/ui/box/index.web.tsx
  • src/components/ui/box/styles.tsx
  • src/components/ui/button/index.tsx
  • src/components/ui/card/index.tsx
  • src/components/ui/card/index.web.tsx
  • src/components/ui/card/styles.tsx
  • src/components/ui/center/index.tsx
  • src/components/ui/center/index.web.tsx
  • src/components/ui/center/styles.tsx
  • src/components/ui/checkbox/index.tsx
  • src/components/ui/divider/index.tsx
  • src/components/ui/drawer/index.tsx
  • src/components/ui/fab/index.tsx
  • src/components/ui/focus-aware-status-bar.tsx
  • src/components/ui/form-control/index.tsx
  • src/components/ui/gluestack-ui-provider/config.ts
  • src/components/ui/gluestack-ui-provider/index.tsx
  • src/components/ui/gluestack-ui-provider/index.web.tsx
  • src/components/ui/grid/index.tsx
  • src/components/ui/grid/index.web.tsx
  • src/components/ui/grid/styles.tsx
  • src/components/ui/heading/index.tsx
  • src/components/ui/heading/index.web.tsx
  • src/components/ui/heading/styles.tsx
  • src/components/ui/hstack/index.tsx
  • src/components/ui/hstack/index.web.tsx
  • src/components/ui/hstack/styles.tsx
  • src/components/ui/html-renderer/__tests__/html-renderer.test.tsx
  • src/components/ui/html-renderer/index.web.tsx
  • src/components/ui/icon/index.tsx
  • src/components/ui/icon/index.web.tsx
  • src/components/ui/image-background/index.tsx
  • src/components/ui/image.tsx
  • src/components/ui/image/index.tsx
  • src/components/ui/index.tsx
  • src/components/ui/input/index.tsx
  • src/components/ui/link/index.tsx
  • src/components/ui/menu/index.tsx
  • src/components/ui/modal/index.tsx
  • src/components/ui/popover/index.tsx
  • src/components/ui/portal/index.tsx
  • src/components/ui/pressable/index.tsx
  • src/components/ui/progress/index.tsx
  • src/components/ui/radio/index.tsx
  • src/components/ui/select/index.tsx
  • src/components/ui/select/select-actionsheet.tsx
  • src/components/ui/skeleton/index.tsx
  • src/components/ui/skeleton/index.web.tsx
  • src/components/ui/skeleton/styles.tsx
  • src/components/ui/slider/index.tsx
  • src/components/ui/spinner/index.tsx
  • src/components/ui/switch/index.tsx
  • src/components/ui/text/index.tsx
  • src/components/ui/text/index.web.tsx
  • src/components/ui/text/styles.tsx
  • src/components/ui/textarea/index.tsx
  • src/components/ui/toast/index.tsx
  • src/components/ui/tooltip/index.tsx
  • src/components/ui/utils/dom-props.ts
  • src/components/ui/utils/use-break-point-value.ts
  • src/components/ui/vstack/index.tsx
  • src/components/ui/vstack/index.web.tsx
  • src/components/ui/vstack/styles.tsx
  • src/components/weather-alerts/weather-alert-card.tsx
  • src/lib/hooks/__tests__/use-selected-theme.test.ts
  • src/lib/hooks/use-selected-theme.tsx
  • src/lib/test-utils.tsx
  • src/models/v4/customStatuses/customStateDetailTypes.ts
  • src/stores/status/store.ts
  • tailwind.config.js
  • tsconfig.json

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.


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.

Comment thread postcss.config.mjs
@@ -0,0 +1,5 @@
export default {

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

Default export in postcss.config.mjs violates Rule [72] favoring named exports for clarity and safer refactoring. Convert to a named constant export, verifying PostCSS loader compatibility before converting.

Kody rule violation: Avoid default exports

Prompt for LLM

File postcss.config.mjs:

Line 1:

Default export in `postcss.config.mjs` violates Rule [72] favoring named exports for clarity and safer refactoring. Convert to a named constant export, verifying PostCSS loader compatibility before converting.

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)/index.tsx
<Mapbox.PointAnnotation id="userLocation" coordinate={[locationLongitude, locationLatitude]} anchor={{ x: 0.5, y: 0.5 }}>
<Animated.View style={[styles.markerContainer, Platform.OS === 'web' ? styles.markerPulseWeb : { transform: [{ scale: pulseAnim }] }]}>
<View style={[styles.markerOuterRing, Platform.OS === 'web' && styles.markerOuterRingPulseWeb]} />
<View style={styles.markerOuterRing} className={Platform.OS === 'web' ? 'pulse-ring' : 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.

kody code-review Kody Rules high

Global .pulse-ring CSS class violates Rule [16] requiring component-scoped styling for all non-top-level components. Move the animation into a CSS Module or BEM-style class name scoped to the component.

Kody rule violation: Use component-scoped styling

Prompt for LLM

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

Line 573:

Global `.pulse-ring` CSS class violates Rule [16] requiring component-scoped styling for all non-top-level components. Move the animation into a CSS Module or BEM-style class name scoped to the component.

Talk to Kody by mentioning @kody

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

}));

jest.mock('nativewind', () => ({
styled: jest.fn((Component: any) => Component),

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

Parameter Component uses PascalCase, violating Rule [7] requiring camelCase for locals and parameters. Rename to component.

Kody rule violation: Use proper naming conventions

Prompt for LLM

File src/app/call/new/__tests__/what3words.test.tsx:

Line 118:

Parameter `Component` uses PascalCase, violating Rule [7] requiring camelCase for locals and parameters. Rename to `component`.

Talk to Kody by mentioning @kody

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

Comment thread src/app/maps/search.tsx
const isActive = segment.value === activeFilter;
return (
<Pressable key={segment.value} onPress={() => handleFilterChange(segment.value)} className={`flex-1 items-center rounded-md px-3 py-2 ${isActive ? 'bg-white shadow-sm dark:bg-gray-600' : ''}`}>
<Pressable key={segment.value} onPress={() => handleFilterChange(segment.value)} className={`flex-1 items-center rounded-md px-3 py-2 ${isActive ? 'bg-white shadow-xs dark:bg-gray-600' : ''}`}>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

Inline arrow function in the onPress prop of Pressable creates a new function on every render, degrading performance. Move the function definition outside the render method.

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

Prompt for LLM

File src/app/maps/search.tsx:

Line 176:

Inline arrow function in the `onPress` prop of `Pressable` creates a new function on every render, degrading performance. Move the function definition outside the render method.

Talk to Kody by mentioning @kody

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

Comment on lines +349 to +351
} catch {
return this;
}

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

Swallowed exception in the _updateProjection catch block returns this without logging, violating Rule [29]. Log the caught error with structured context (operation name, error message) before returning the fallback value so failures are traceable.

Kody rule violation: Avoid empty catch blocks

Prompt for LLM

File src/components/maps/map-view.web.tsx:

Line 349 to 351:

Swallowed exception in the `_updateProjection` catch block returns `this` without logging, violating Rule [29]. Log the caught error with structured context (operation name, error message) before returning the fallback value so failures are traceable.

Talk to Kody by mentioning @kody

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


const cancelButton = screen.getByText('common.cancel').parent;
fireEvent.press(cancelButton);
fireEvent.press(cancelButton!);

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

Non-null assertion (!) on cancelButton suppresses the compiler instead of handling the nullable .parent result per Rule [2]. Add an explicit null guard with a clear error message or use optional chaining with an early return.

Kody rule violation: Add null checks before accessing properties

Prompt for LLM

File src/components/settings/__tests__/login-info-bottom-sheet-simple.test.tsx:

Line 219:

Non-null assertion (`!`) on `cancelButton` suppresses the compiler instead of handling the nullable `.parent` result per Rule [2]. Add an explicit null guard with a clear error message or use optional chaining with an early return.

Talk to Kody by mentioning @kody

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


const cancelButton = screen.getByText('common.cancel').parent;
fireEvent.press(cancelButton);
fireEvent.press(cancelButton!);

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

Potentially-null cancelButton derived from .parent is dereferenced via fireEvent.press with only a non-null assertion, violating Rule [26]. Add an explicit null check (e.g., expect(cancelButton).not.toBeNull()) or branch on the null case deterministically before the call.

Kody rule violation: Add null checks to prevent NullReferenceException

Prompt for LLM

File src/components/settings/__tests__/login-info-bottom-sheet-simple.test.tsx:

Line 219:

Potentially-null `cancelButton` derived from `.parent` is dereferenced via `fireEvent.press` with only a non-null assertion, violating Rule [26]. Add an explicit null check (e.g., `expect(cancelButton).not.toBeNull()`) or branch on the null case deterministically before the call.

Talk to Kody by mentioning @kody

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

Comment on lines +362 to 367
const ActionsheetFlatList = React.forwardRef<React.ComponentRef<typeof UIActionsheet.FlatList>, IActionsheetFlatListProps>(function ActionsheetFlatList({ className, style, ...props }, ref) {
return (
<View className={actionsheetFlatListStyle({ class: className })} style={style}>
<UIActionsheet.FlatList ref={ref} estimatedItemSize={94} {...props} />
<UIActionsheet.FlatList ref={ref} {...props} />
</View>
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Performance high

Missing estimatedItemSize on UIActionsheet.FlatList (a @shopify/flash-list) triggers dynamic item measurement, causing extra layout passes and scroll jank. Restore estimatedItemSize={94} or accept it as a forwarded prop.

<UIActionsheet.FlatList ref={ref} estimatedItemSize={94} {...props} />
Prompt for LLM

File src/components/ui/actionsheet/index.tsx:

Line 362 to 367:

Missing `estimatedItemSize` on `UIActionsheet.FlatList` (a `@shopify/flash-list`) triggers dynamic item measurement, causing extra layout passes and scroll jank. Restore `estimatedItemSize={94}` or accept it as a forwarded prop.

Suggested Code:

<UIActionsheet.FlatList ref={ref} estimatedItemSize={94} {...props} />

Talk to Kody by mentioning @kody

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


type IAnimatedPressableProps = React.ComponentProps<typeof Pressable> & MotionComponentProps<typeof Pressable, ViewStyle, unknown, unknown, unknown>;

const AnimatedPressable = createMotionAnimatedComponent(Pressable) as React.ComponentType<IAnimatedPressableProps>;

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

Unsafe type cast on createMotionAnimatedComponent(Pressable) bypasses compile-time type safety. Use the as operator with proper type guards or pattern matching to ensure null safety before usage.

Kody rule violation: Use safe type casting with as operator

Prompt for LLM

File src/components/ui/actionsheet/index.tsx:

Line 23:

Unsafe type cast on `createMotionAnimatedComponent(Pressable)` bypasses compile-time type safety. Use the `as` operator with proper type guards or pattern matching to ensure null safety before usage.

Talk to Kody by mentioning @kody

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

// drives the class-based `dark:` variant (see @custom-variant in global.css).
const osScheme = useColorScheme();
const colorSchemeName: 'light' | 'dark' = mode === 'system' ? (osScheme === 'dark' ? 'dark' : 'light') : mode === 'dark' ? 'dark' : 'light';
const resolvedScheme: 'light' | 'dark' = mode === 'system' ? (osScheme === 'dark' ? 'dark' : 'light') : mode;

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

Repeated inline string literals 'light', 'dark', 'system' and the inline 'light' | 'dark' type duplicate the canonical ModeType union, risking drift. Derive from a single source (e.g., const MODES = ['light','dark','system'] as const) and reference those constants everywhere.

Kody rule violation: Use enums instead of magic strings

Prompt for LLM

File src/components/ui/gluestack-ui-provider/index.tsx:

Line 14:

Repeated inline string literals `'light'`, `'dark'`, `'system'` and the inline `'light' | 'dark'` type duplicate the canonical `ModeType` union, risking drift. Derive from a single source (e.g., `const MODES = ['light','dark','system'] as const`) and reference those constants everywhere.

Talk to Kody by mentioning @kody

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

duration: 100,
}}
})}
exiting={FadeOut.duration(150)}

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

Inline magic number 150 duplicates the value on line 119 and invites drift if one changes without the other. Extract a shared named constant such as MENU_ANIM_DURATION = 150 and use it for both entering and exiting durations.

Kody rule violation: Replace magic numbers with named constants

Prompt for LLM

File src/components/ui/menu/index.tsx:

Line 123:

Inline magic number `150` duplicates the value on line 119 and invites drift if one changes without the other. Extract a shared named constant such as `MENU_ANIM_DURATION = 150` and use it for both entering and exiting durations.

Talk to Kody by mentioning @kody

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

// react-native-web ships no Appearance.setColorScheme — drive the <html>
// class directly (same mechanism as GluestackUIProvider's script.ts).
if (typeof document !== 'undefined') {
const resolved = t === 'system' ? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') : t;

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

Hardcoded 'dark' and 'light' literals repeated across files violate Rule [6] requiring shared string literals to be centralized. Extract named constants or reuse existing ColorSchemeType members.

Kody rule violation: Centralize string constants

Prompt for LLM

File src/lib/hooks/use-selected-theme.tsx:

Line 15:

Hardcoded `'dark'` and `'light'` literals repeated across files violate Rule [6] requiring shared string literals to be centralized. Extract named constants or reuse existing `ColorSchemeType` members.

Talk to Kody by mentioning @kody

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

@@ -14,9 +14,21 @@ export const statusDetailAllowsCalls = (detail: number): boolean => {
};

export const statusDetailAllowsStations = (detail: number): boolean => {

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

Unverified destination-type fallback in statusDetailAllowsStations and statusDetailAllowsPois permits handleSubmit to submit a RespondingToType the backend's Detail flag never authorized, risking rejection or silent misrouting. Confirm the backend accepts every RespondingToType the fallback now permits, or gate the fallback behind an explicit opt-in config/feature flag rather than applying it unconditionally to every department.

// Verify the backend honors these destinations for the legacy Detail values
// before applying the fallback unconditionally; otherwise restrict to departments
// that opt in.
export const statusDetailAllowsStations = (detail: number, legacyFallback = false): boolean => {
  if (detail === CustomStateDetailTypes.Stations || detail === CustomStateDetailTypes.CallsAndStations || detail === CustomStateDetailTypes.StationsAndPois || detail === CustomStateDetailTypes.CallsStationsAndPois) {
    return true;
  }
  return legacyFallback ? statusDetailAllowsCalls(detail) : false;
};
Prompt for LLM

File src/models/v4/customStatuses/customStateDetailTypes.ts:

Line 16:

Unverified destination-type fallback in `statusDetailAllowsStations` and `statusDetailAllowsPois` permits `handleSubmit` to submit a `RespondingToType` the backend's `Detail` flag never authorized, risking rejection or silent misrouting. Confirm the backend accepts every `RespondingToType` the fallback now permits, or gate the fallback behind an explicit opt-in config/feature flag rather than applying it unconditionally to every department.

Suggested Code:

// Verify the backend honors these destinations for the legacy Detail values
// before applying the fallback unconditionally; otherwise restrict to departments
// that opt in.
export const statusDetailAllowsStations = (detail: number, legacyFallback = false): boolean => {
  if (detail === CustomStateDetailTypes.Stations || detail === CustomStateDetailTypes.CallsAndStations || detail === CustomStateDetailTypes.StationsAndPois || detail === CustomStateDetailTypes.CallsStationsAndPois) {
    return true;
  }
  return legacyFallback ? statusDetailAllowsCalls(detail) : false;
};

Talk to Kody by mentioning @kody

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

Comment thread src/stores/status/store.ts Outdated
Comment on lines +69 to +73
const isNoStationSentinel = (station: GroupResultData): boolean => {
const id = station.GroupId?.trim() ?? '';
const name = station.Name?.trim().toLowerCase() ?? '';
return id === '' || id === '0' || name === 'no station';
};

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 normalize-and-compare logic in isNoStationSentinel mirrors isNoCallSentinel, risking drift when sentinel rules change. Extract a shared isSentinel(id, name, sentinelName) helper and call it from both functions.

Kody rule violation: Extract duplicated logic into functions

Prompt for LLM

File src/stores/status/store.ts:

Line 69 to 73:

Duplicated normalize-and-compare logic in `isNoStationSentinel` mirrors `isNoCallSentinel`, risking drift when sentinel rules change. Extract a shared `isSentinel(id, name, sentinelName)` helper and call it from both functions.

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

// Verified safe against the backend: SaveUnitStatus (UnitStatusController)
// validates the destination via IsValidDestinationAsync (entity exists in the
// same department) and never checks the status Detail flag, and
// GetSetUnitStatusData (DispatchController) serves these stations/pois as the

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

Unsafe type casting violates team rules. Use the as operator or pattern matching for safe casts and guard null results before usage.

Kody rule violation: Use safe type casting with as operator

Prompt for LLM

File src/models/v4/customStatuses/customStateDetailTypes.ts:

Line 26:

Unsafe type casting violates team rules. Use the `as` operator or pattern matching for safe casts and guard null results before usage.

Talk to Kody by mentioning @kody

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

return normalizedId === '' || normalizedId === '0' || normalizedName === sentinelName;
};

const isNoCallSentinel = (call: CallResultData): boolean => isSentinel(call.CallId, call.Name, 'no call');

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

Scattered string literal for the sentinel name 'no call' duplicates values, violating Rule [6]. Centralize sentinel names into a constant object, such as const SENTINEL_NAMES = { NoCall: 'no call', NoStation: 'no station' } as const;, and pass SENTINEL_NAMES.NoCall.

Kody rule violation: Centralize string constants

Prompt for LLM

File src/stores/status/store.ts:

Line 67:

Scattered string literal for the sentinel name 'no call' duplicates values, violating Rule [6]. Centralize sentinel names into a constant object, such as `const SENTINEL_NAMES = { NoCall: 'no call', NoStation: 'no station' } as const;`, and pass `SENTINEL_NAMES.NoCall`.

Talk to Kody by mentioning @kody

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

return normalizedId === '' || normalizedId === '0' || normalizedName === sentinelName;
};

const isNoCallSentinel = (call: CallResultData): boolean => isSentinel(call.CallId, call.Name, 'no call');

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 for the finite sentinel-name set 'no call' risks typos and reduces discoverability, violating Rule [8]. Define a const tuple like const SENTINEL_NAMES = ['no call', 'no station'] as const; with type SentinelName = typeof SENTINEL_NAMES[number]; and reference the named member.

Kody rule violation: Use enums instead of magic strings

Prompt for LLM

File src/stores/status/store.ts:

Line 67:

Magic string for the finite sentinel-name set 'no call' risks typos and reduces discoverability, violating Rule [8]. Define a const tuple like `const SENTINEL_NAMES = ['no call', 'no station'] as const;` with `type SentinelName = typeof SENTINEL_NAMES[number];` and reference the named member.

Talk to Kody by mentioning @kody

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

@ucswift

ucswift commented Jul 31, 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 6ccf245 into master Jul 31, 2026
19 of 20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants