-
Notifications
You must be signed in to change notification settings - Fork 0
Develop #38
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Develop #38
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -39,6 +39,7 @@ import { FeatureFlagKeys, featureFlagsStore } from '@/stores/feature-flags/store | |
| import { useRolesStore } from '@/stores/roles/store'; | ||
| import { securityStore } from '@/stores/security/store'; | ||
| import { useSignalRStore } from '@/stores/signalr/signalr-store'; | ||
| import { useToastStore } from '@/stores/toast/store'; | ||
| import { useWeatherAlertsStore } from '@/stores/weather-alerts/store'; | ||
|
|
||
| export default function TabLayout() { | ||
|
|
@@ -175,6 +176,18 @@ export default function TabLayout() { | |
| await useCallsStore.getState().init(); | ||
| await useWeatherAlertsStore.getState().init(); | ||
| await securityStore.getState().getRights(); | ||
|
|
||
| // The IC app is for commanders. A member the department has not authorized must not get past | ||
| // initialization — the server refuses them the board endpoints anyway, so signing them straight | ||
| // back out is far clearer than an app that loads and then fails every request. | ||
| if (!isCurrentRun()) return; | ||
| if (securityStore.getState().rights?.CanLoginToCommandApp === false) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Authorization gate fails open. The check for explicit Kody rule violation: Implement RBAC with least privilege and deny-by-default Prompt for LLMTalk to Kody by mentioning @kody Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction. |
||
| logger.warn({ message: 'User is not authorized to use the IC app; signing out', context: { userId } }); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Non-compliant security audit logging. The unauthorized access attempt is logged via Kody rule violation: Emit tamper-evident audit logs with required fields Prompt for LLMTalk to Kody by mentioning @kody Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction. |
||
| useToastStore.getState().showToast('error', t('login.command_not_authorized')); | ||
| await useAuthStore.getState().logout(); | ||
| return; | ||
| } | ||
|
|
||
| await featureFlagsStore.getState().fetchFlags(); | ||
|
|
||
| if (!isCurrentRun()) return; | ||
|
|
@@ -235,7 +248,7 @@ export default function TabLayout() { | |
| setIsInitComplete(true); | ||
| } | ||
| } | ||
| }, [status]); | ||
| }, [status, t, userId]); | ||
|
|
||
| const refreshDataFromBackground = useCallback(async () => { | ||
| if (status !== 'signedIn' || !hasInitialized.current) return; | ||
|
|
@@ -501,24 +514,29 @@ export default function TabLayout() { | |
| [t, headerLeftBack, headerRightNotification] | ||
| ); | ||
|
|
||
| // chat + chatbot are routable (sidebar menu links) but hidden from the tab bar (href: null); | ||
| // each screen renders its own in-screen header/toolbar, so the tab header is disabled. | ||
| // chat + chatbot are routable (sidebar menu links) but hidden from the tab bar (href: null). | ||
| // They keep the app header: it is the only way back out, since neither is on the tab bar and | ||
| // their in-screen toolbars carry actions rather than navigation. | ||
| const chatOptions = useMemo( | ||
| () => ({ | ||
| href: null, | ||
| title: t('chat.title'), | ||
| headerShown: false as const, | ||
| headerShown: true as const, | ||
| headerLeft: headerLeftMap, | ||
| headerRight: headerRightNotification, | ||
| }), | ||
| [t] | ||
| [t, headerLeftMap, headerRightNotification] | ||
| ); | ||
|
|
||
| const chatbotOptions = useMemo( | ||
| () => ({ | ||
| href: null, | ||
| title: t('chatbot.title'), | ||
| headerShown: false as const, | ||
| headerShown: true as const, | ||
| headerLeft: headerLeftMap, | ||
| headerRight: headerRightNotification, | ||
| }), | ||
| [t] | ||
| [t, headerLeftMap, headerRightNotification] | ||
| ); | ||
|
|
||
| // settings stays routable (sidebar menu link) but is hidden from the tab bar. | ||
|
|
@@ -628,23 +646,26 @@ interface CreateDrawerMenuButtonProps { | |
| const CreateDrawerMenuButton = ({ setIsOpen }: CreateDrawerMenuButtonProps) => { | ||
| return ( | ||
| <Pressable | ||
| className="p-2" | ||
| hitSlop={4} | ||
| className="p-3" | ||
| hitSlop={8} | ||
| testID="drawer-menu-button" | ||
| onPress={() => { | ||
| setIsOpen(true); | ||
| }} | ||
| > | ||
| <Menu size={24} color="currentColor" className="text-gray-700 dark:text-gray-300" /> | ||
| {/* Routed through the Icon wrapper, not a bare lucide element: className alone never reaches a | ||
| raw lucide icon (no cssInterop is registered for them), so it falls back to currentColor and | ||
| renders solid black — invisible against a dark header. */} | ||
| <Icon as={Menu} size={24} className="text-gray-700 dark:text-gray-200" /> | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -eu
file='src/app/(app)/_layout.tsx'
printf '%s\n' '--- file outline ---'
if command -v ast-grep >/dev/null 2>&1; then
ast-grep outline "$file" || true
fi
printf '%s\n' '--- relevant source ---'
sed -n '1,90p' "$file"
sed -n '610,700p' "$file"
printf '%s\n' '--- icon imports and usages in target file ---'
rg -n -C 3 'Icon|Menu|ArrowLeft|Pressable|Touchable|accessib' "$file"
printf '%s\n' '--- repository guidance and comparable usage ---'
rg -n -g '*.{ts,tsx,md,json}' 'lucide-react-native|Icon as=|accessibilityLabel|aria-label|<Menu|<ArrowLeft' . | head -250Repository: Resgrid/IC Length of output: 47271 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- Icon and Pressable implementations ---'
icon_file=$(fd -i -t f '^icon\.(tsx|ts)$' src/components/ui | head -1)
pressable_file=$(fd -i -t f '^pressable\.(tsx|ts)$' src/components/ui | head -1)
printf 'icon_file=%s\npressable_file=%s\n' "$icon_file" "$pressable_file"
cat -n "$icon_file"
cat -n "$pressable_file"
printf '%s\n' '--- theme and color scheme usage ---'
rg -n -g '*.{ts,tsx}' 'useColorScheme|colorScheme|resolvedColor|theme.*color|ColorValue|--color|dark:' src | head -250
printf '%s\n' '--- accessible header controls and translation keys ---'
rg -n -C 4 -g '*.{ts,tsx,json}' 'common\.back|common\.menu|accessibilityLabel=.*(back|menu)|header-back|drawer-menu' src | head -250
printf '%s\n' '--- project icon rules ---'
sed -n '1,35p' AGENTS.md
sed -n '360,385p' AGENTS.mdRepository: Resgrid/IC Length of output: 253 🏁 Script executed: #!/bin/bash
set -u
printf '%s\n' '--- tracked UI files ---'
git ls-files 'src/components/ui/*' | head -200
printf '%s\n' '--- Icon definitions ---'
rg -n -C 8 'export.*Icon|const Icon|function Icon|createIcon' src/components/ui src | head -250
printf '%s\n' '--- Pressable definitions ---'
rg -n -C 8 'export.*Pressable|const Pressable|function Pressable|createPressable' src/components/ui src | head -250
printf '%s\n' '--- theme and color scheme usage ---'
rg -n -g '*.{ts,tsx}' 'useColorScheme|colorScheme|resolvedColor|theme.*color|ColorValue|--color' src | head -250
printf '%s\n' '--- accessible header controls and translation keys ---'
rg -n -C 4 -g '*.{ts,tsx,json}' 'common\.back|common\.menu|accessibilityLabel=.*(back|menu)|header-back|drawer-menu' src | head -250
printf '%s\n' '--- project icon rules ---'
sed -n '1,35p' AGENTS.md
sed -n '360,385p' AGENTS.mdRepository: Resgrid/IC Length of output: 50366 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- native Icon wrapper ---'
cat -n src/components/ui/icon/index.tsx | sed -n '1,85p'
printf '%s\n' '--- translation files and relevant keys ---'
git ls-files | rg '(^|/)(locales?|translations?|i18n)(/|\\.)|\\.json$' | head -100
rg -n -C 2 '"(back|menu|drawer|navigation)"|common\.(back|menu)' . \
-g '*.json' -g '*.{ts,tsx}' \
--glob '!node_modules/**' --glob '!dist/**' --glob '!build/**' | head -180
printf '%s\n' '--- direct Lucide color usage ---'
rg -n -C 2 '<(Menu|ArrowLeft|ChevronRight|Search|X)[^>]*(color=|className=)' src -g '*.{ts,tsx}' | head -120Repository: Resgrid/IC Length of output: 23306 Render the header icons directly and label the controls. The project rules require direct 🤖 Prompt for AI AgentsSource: Coding guidelines |
||
| </Pressable> | ||
|
Comment on lines
648
to
660
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -e
file='src/app/(app)/_layout.tsx'
printf '%s\n' '--- target section ---'
sed -n '620,695p' "$file"
printf '%s\n' '--- relevant imports and translation setup ---'
sed -n '1,120p' "$file"
printf '%s\n' '--- Pressable and accessibility usages in this file ---'
rg -n -C 3 'Pressable|accessibility(Label|Role|Hint)' "$file"Repository: Resgrid/IC Length of output: 9224 🏁 Script executed: #!/bin/bash
set -e
printf '%s\n' '--- Pressable implementation ---'
pressable=$(fd -i '^pressable\.(tsx|ts)$' src | head -n 1)
printf 'file=%s\n' "$pressable"
sed -n '1,220p' "$pressable"
printf '%s\n' '--- localized icon-button patterns ---'
rg -n -C 4 'accessibilityLabel|accessibilityRole' src --glob '*.{ts,tsx}' | head -n 240
printf '%s\n' '--- translation keys related to navigation and drawer ---'
rg -n -i 'drawer|menu|back|navigation' src --glob '*.{json,ts,tsx}' | head -n 240Repository: Resgrid/IC Length of output: 231 🏁 Script executed: #!/bin/bash
set -e
printf '%s\n' '--- candidate Pressable files ---'
git ls-files | rg '(^|/)pressable\.(tsx|ts)$|(^|/)Pressable\.(tsx|ts)$'
printf '%s\n' '--- UI component directory ---'
git ls-files 'src/components/ui/*' | rg -i 'pressable|button|icon|text'
printf '%s\n' '--- accessibility API usage ---'
rg -n -C 4 'accessibilityLabel|accessibilityRole' src --glob '*.{ts,tsx}' | head -n 240
printf '%s\n' '--- navigation translation references ---'
rg -n -i -C 3 'drawer|menu|back|navigation' src --glob '*.{json,ts,tsx}' | head -n 240Repository: Resgrid/IC Length of output: 182 🏁 Script executed: #!/bin/bash
printf '%s\n' '--- repository files under the imported UI path ---'
find src/components/ui -maxdepth 3 -type f 2>/dev/null | sort | rg -i 'pressable|button|icon|text' || true
printf '%s\n' '--- Pressable declarations and imports ---'
rg -n -C 5 'export .*Pressable|const Pressable|function Pressable|from .*pressable' src --glob '*.{ts,tsx}' || true
printf '%s\n' '--- accessibility API usage ---'
rg -n -C 4 'accessibilityLabel|accessibilityRole' src --glob '*.{ts,tsx}' | head -n 240 || true
printf '%s\n' '--- navigation translation references ---'
rg -n -i -C 3 'drawer|menu|back|navigation' src --glob '*.{json,ts,tsx}' | head -n 240 || trueRepository: Resgrid/IC Length of output: 50366 🏁 Script executed: #!/bin/bash
set -e
printf '%s\n' '--- Pressable wrapper ---'
cat -n src/components/ui/pressable/index.tsx
printf '%s\n' '--- existing localized accessibility labels ---'
rg -n -C 2 'accessibilityLabel=' src --glob '*.{ts,tsx}' | head -n 160
printf '%s\n' '--- translation resource files ---'
find . -type f \( -name '*.json' -o -name '*.ts' -o -name '*.tsx' \) -not -path './node_modules/*' | rg -i 'locale|locales|translation|i18n|lang' | head -n 120
printf '%s\n' '--- common accessibility/navigation keys ---'
rg -n -i -C 2 'dismiss|drawer|menu|back|accessibility' . --glob '*.json' --glob '*.ts' --glob '*.tsx' --glob '!node_modules/**' | head -n 220Repository: Resgrid/IC Length of output: 50367 🏁 Script executed: #!/bin/bash
set -e
python3 - <<'PY'
import json
from pathlib import Path
for name in ('en', 'fr', 'es', 'de', 'it', 'pl', 'sv', 'uk', 'ar'):
path = Path('src/translations') / f'{name}.json'
data = json.loads(path.read_text())
common = data.get('common', {})
print(name, {
'back': common.get('back'),
'menu': common.get('menu'),
'open_menu': common.get('open_menu'),
'open_drawer': common.get('open_drawer'),
})
PY
printf '%s\n' '--- comparable back-button implementations ---'
rg -n -C 5 "testID=.*back|accessibilityLabel=.*common.back|ArrowLeft" src/app src/components --glob '*.{ts,tsx}' | head -n 180Repository: Resgrid/IC Length of output: 14457 Add accessible names to both navigation buttons. Set 🤖 Prompt for AI AgentsSource: Coding guidelines |
||
| ); | ||
| }; | ||
|
|
||
| const CreateHeaderBackButton = () => { | ||
| return ( | ||
| <Pressable | ||
| className="p-2" | ||
| hitSlop={4} | ||
| className="p-3" | ||
| hitSlop={8} | ||
| testID="header-back-button" | ||
| onPress={() => { | ||
| if (router.canGoBack()) { | ||
|
|
@@ -654,7 +675,7 @@ const CreateHeaderBackButton = () => { | |
| } | ||
| }} | ||
| > | ||
| <ArrowLeft size={24} color="currentColor" className="text-gray-700 dark:text-gray-300" /> | ||
| <Icon as={ArrowLeft} size={24} className="text-gray-700 dark:text-gray-200" /> | ||
| </Pressable> | ||
| ); | ||
| }; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,5 @@ | ||
| import { type Href, Redirect, Stack, useFocusEffect, useRouter } from 'expo-router'; | ||
| import { Bot, MessageCircle, MessagesSquare, Network, Plus, Sparkles, Users } from 'lucide-react-native'; | ||
| import { type Href, Redirect, useFocusEffect, useRouter } from 'expo-router'; | ||
| import { Bot, MessageCircle, Network, Plus, Sparkles, Users } from 'lucide-react-native'; | ||
| import React, { useCallback, useState } from 'react'; | ||
| import { useTranslation } from 'react-i18next'; | ||
| import { RefreshControl, ScrollView } from 'react-native'; | ||
|
|
@@ -119,7 +119,6 @@ export default function ChatScreen() { | |
| if (chatStatus === 'unknown') { | ||
| return ( | ||
| <Box className="size-full flex-1 items-center justify-center bg-background-0"> | ||
| <Stack.Screen options={{ headerShown: false }} /> | ||
| <FocusAwareStatusBar /> | ||
| <Spinner /> | ||
| </Box> | ||
|
|
@@ -133,17 +132,14 @@ export default function ChatScreen() { | |
|
|
||
| return ( | ||
| <Box className="size-full flex-1 bg-background-0"> | ||
| <Stack.Screen options={{ headerShown: false }} /> | ||
| <FocusAwareStatusBar /> | ||
|
|
||
| {/* In-screen toolbar (the app drawer provides the top nav bar). */} | ||
| <HStack className="items-center justify-between border-b border-outline-100 px-4 py-2"> | ||
| <HStack className="items-center" space="sm"> | ||
| <MessagesSquare size={22} color="#2563eb" /> | ||
| <Text className="text-lg font-bold text-typography-900">{t('chat.title')}</Text> | ||
| </HStack> | ||
| <Pressable onPress={() => router.push('/chatbot' as Href)} accessibilityLabel={t('chat.assistant')}> | ||
| <Sparkles size={22} color="#7c3aed" /> | ||
| {/* Shortcut across to the assistant. The app header above carries the title and the way back, | ||
| so this row is actions only. */} | ||
| <HStack className="items-center justify-end border-b border-outline-100 px-4 py-2"> | ||
| <Pressable className="flex-row items-center rounded-full bg-purple-600 px-3 py-2" onPress={() => router.push('/chatbot' as Href)} accessibilityLabel={t('chat.assistant')} testID="chat-open-assistant"> | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Performance regression in Kody rule violation: Avoid using .bind() or arrow functions in JSX props Prompt for LLMTalk to Kody by mentioning @kody Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction. |
||
| <Sparkles size={16} color="#ffffff" /> | ||
| <Text className="ml-1 text-xs font-medium text-white">{t('chat.assistant')}</Text> | ||
| </Pressable> | ||
| </HStack> | ||
|
|
||
|
|
||
There was a problem hiding this comment.
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
Add a denied-access test case.
Every changed fixture sets
CanLoginToCommandApptotrue. The suite can therefore pass while the explicitfalseauthorization path remains broken. Add a case that denies command-app access and verifies the localized toast and logout behavior. Also cover an omitted field if the API contract treats missing values specially.As per coding guidelines: generate tests for new components, services, and logic.
Also applies to: 48-48, 70-70, 91-91, 114-114
🤖 Prompt for AI Agents
Source: Coding guidelines