Skip to content

Commit 23ad2dd

Browse files
committed
RU-T49 PR#232 fixes
1 parent 562ae36 commit 23ad2dd

File tree

16 files changed

+296
-26
lines changed

16 files changed

+296
-26
lines changed

CLAUDE.md

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
<!-- dgc-policy-v10 -->
2+
# Dual-Graph Context Policy
3+
4+
This project uses a local dual-graph MCP server for efficient context retrieval.
5+
6+
## MANDATORY: Always follow this order
7+
8+
1. **Call `graph_continue` first** — before any file exploration, grep, or code reading.
9+
10+
2. **If `graph_continue` returns `needs_project=true`**: call `graph_scan` with the
11+
current project directory (`pwd`). Do NOT ask the user.
12+
13+
3. **If `graph_continue` returns `skip=true`**: project has fewer than 5 files.
14+
Do NOT do broad or recursive exploration. Read only specific files if their names
15+
are mentioned, or ask the user what to work on.
16+
17+
4. **Read `recommended_files`** using `graph_read`**one call per file**.
18+
- `graph_read` accepts a single `file` parameter (string). Call it separately for each
19+
recommended file. Do NOT pass an array or batch multiple files into one call.
20+
- `recommended_files` may contain `file::symbol` entries (e.g. `src/auth.ts::handleLogin`).
21+
Pass them verbatim to `graph_read(file: "src/auth.ts::handleLogin")` — it reads only
22+
that symbol's lines, not the full file.
23+
- Example: if `recommended_files` is `["src/auth.ts::handleLogin", "src/db.ts"]`,
24+
call `graph_read(file: "src/auth.ts::handleLogin")` and `graph_read(file: "src/db.ts")`
25+
as two separate calls (they can be parallel).
26+
27+
5. **Check `confidence` and obey the caps strictly:**
28+
- `confidence=high` -> Stop. Do NOT grep or explore further.
29+
- `confidence=medium` -> If recommended files are insufficient, call `fallback_rg`
30+
at most `max_supplementary_greps` time(s) with specific terms, then `graph_read`
31+
at most `max_supplementary_files` additional file(s). Then stop.
32+
- `confidence=low` -> Call `fallback_rg` at most `max_supplementary_greps` time(s),
33+
then `graph_read` at most `max_supplementary_files` file(s). Then stop.
34+
35+
## Token Usage
36+
37+
A `token-counter` MCP is available for tracking live token usage.
38+
39+
- To check how many tokens a large file or text will cost **before** reading it:
40+
`count_tokens({text: "<content>"})`
41+
- To log actual usage after a task completes (if the user asks):
42+
`log_usage({input_tokens: <est>, output_tokens: <est>, description: "<task>"})`
43+
- To show the user their running session cost:
44+
`get_session_stats()`
45+
46+
Live dashboard URL is printed at startup next to "Token usage".
47+
48+
## Rules
49+
50+
- Do NOT use `rg`, `grep`, or bash file exploration before calling `graph_continue`.
51+
- Do NOT do broad/recursive exploration at any confidence level.
52+
- `max_supplementary_greps` and `max_supplementary_files` are hard caps - never exceed them.
53+
- Do NOT dump full chat history.
54+
- Do NOT call `graph_retrieve` more than once per turn.
55+
- After edits, call `graph_register_edit` with the changed files. Use `file::symbol` notation (e.g. `src/auth.ts::handleLogin`) when the edit targets a specific function, class, or hook.
56+
57+
## Context Store
58+
59+
Whenever you make a decision, identify a task, note a next step, fact, or blocker during a conversation, append it to `.dual-graph/context-store.json`.
60+
61+
**Entry format:**
62+
```json
63+
{"type": "decision|task|next|fact|blocker", "content": "one sentence max 15 words", "tags": ["topic"], "files": ["relevant/file.ts"], "date": "YYYY-MM-DD"}
64+
```
65+
66+
**To append:** Read the file → add the new entry to the array → Write it back → call `graph_register_edit` on `.dual-graph/context-store.json`.
67+
68+
**Rules:**
69+
- Only log things worth remembering across sessions (not every minor detail)
70+
- `content` must be under 15 words
71+
- `files` lists the files this decision/task relates to (can be empty)
72+
- Log immediately when the item arises — not at session end
73+
74+
## Session End
75+
76+
When the user signals they are done (e.g. "bye", "done", "wrap up", "end session"), proactively update `CONTEXT.md` in the project root with:
77+
- **Current Task**: one sentence on what was being worked on
78+
- **Key Decisions**: bullet list, max 3 items
79+
- **Next Steps**: bullet list, max 3 items
80+
81+
Keep `CONTEXT.md` under 20 lines total. Do NOT summarize the full conversation — only what's needed to resume next session.
82+
83+
---
84+
85+
# Project: Resgrid Unit (React Native / Expo)
86+
87+
## Tech Stack
88+
89+
TypeScript · React Native · Expo (managed, prebuild) · Zustand · React Query · React Hook Form · react-i18next · react-native-mmkv · Axios · @rnmapbox/maps · gluestack-ui · lucide-react-native
90+
91+
## Code Style
92+
93+
- Write concise, type-safe TypeScript. Avoid `any`; use precise types and interfaces for props/state.
94+
- Use functional components and hooks; never class components. Use `React.FC` for typed components.
95+
- Enable strict mode in `tsconfig.json`.
96+
- Organize files by feature, grouping related components, hooks, and styles.
97+
- All components must be mobile-friendly and responsive, supporting both iOS and Android.
98+
- This is an Expo managed project using prebuild — **do not make native code changes** outside Expo prebuild capabilities.
99+
100+
## Naming Conventions
101+
102+
- Variables and functions: `camelCase` (e.g., `isFetchingData`, `handleUserInput`)
103+
- Components: `PascalCase` (e.g., `UserProfile`, `ChatScreen`)
104+
- Files and directories: `lowercase-hyphenated` (e.g., `user-profile.tsx`, `chat-screen/`)
105+
106+
## Styling
107+
108+
- Use `gluestack-ui` components from `components/ui` when available.
109+
- For anything without a Gluestack component, use `StyleSheet.create()` or Styled Components.
110+
- Support both **dark mode and light mode**.
111+
- Follow WCAG accessibility guidelines for mobile.
112+
113+
## Performance
114+
115+
- Minimize `useEffect`, `useState`, and heavy computation inside render methods.
116+
- Use `React.memo()` for components with static props.
117+
- Optimize `FlatList` with `removeClippedSubviews`, `maxToRenderPerBatch`, `windowSize`, and `getItemLayout` when items have a consistent size.
118+
- Avoid anonymous functions in `renderItem` or event handlers.
119+
120+
## Internationalization
121+
122+
- All user-visible text **must** be wrapped in `t()` from `react-i18next`.
123+
- Translation dictionary files live in `src/translations/`.
124+
125+
## Libraries — use these, not alternatives
126+
127+
| Purpose | Library |
128+
|---|---|
129+
| Package manager | `yarn` |
130+
| State management | `zustand` |
131+
| Data fetching | `react-query` |
132+
| Forms | `react-hook-form` |
133+
| i18n | `react-i18next` |
134+
| Local storage | `react-native-mmkv` |
135+
| Secure storage | Expo SecureStore |
136+
| HTTP | `axios` |
137+
| Maps / navigation | `@rnmapbox/maps` |
138+
| Icons | `lucide-react-native` (use directly in markup, not via gluestack Icon wrapper) |
139+
140+
## Conditional Rendering
141+
142+
Use ternary `? :` for conditional rendering — **never `&&`**.
143+
144+
## Testing
145+
146+
- Use Jest. Generate tests for all new components, services, and logic.
147+
- Ensure tests run without errors before considering a task done.
148+
149+
## Best Practices
150+
151+
- Follow React Native's threading model for smooth UI performance.
152+
- Use React Navigation for navigation and deep linking.
153+
- Handle errors gracefully and provide user feedback.
154+
- Implement proper offline support.
155+
- Optimize for low-end devices.

src/app/maps/custom/[id].tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ export default function CustomMapViewer() {
183183
<Card className="rounded-xl bg-white p-4 shadow-lg dark:bg-gray-800">
184184
<HStack className="items-start justify-between">
185185
<VStack className="flex-1" space="xs">
186-
<Text className="text-base font-bold text-gray-900 dark:text-white">{(selectedFeature.name as string) || (selectedFeature.Name as string) || 'Region'}</Text>
186+
<Text className="text-base font-bold text-gray-900 dark:text-white">{(selectedFeature.name as string) || (selectedFeature.Name as string) || t('maps.region')}</Text>
187187
{selectedFeature.description || selectedFeature.Description ? (
188188
<Text className="text-sm text-gray-600 dark:text-gray-400">{(selectedFeature.description as string) || (selectedFeature.Description as string)}</Text>
189189
) : null}

src/app/maps/indoor/[id].tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ export default function IndoorMapViewer() {
219219
<VStack className="flex-1" space="xs">
220220
<HStack className="items-center" space="sm">
221221
<Icon as={Building2} size="sm" className="text-purple-600 dark:text-purple-400" />
222-
<Text className="text-base font-bold text-gray-900 dark:text-white">{(selectedZone.name as string) || (selectedZone.Name as string) || 'Zone'}</Text>
222+
<Text className="text-base font-bold text-gray-900 dark:text-white">{(selectedZone.name as string) || (selectedZone.Name as string) || t('maps.zone')}</Text>
223223
</HStack>
224224
{selectedZone.type || selectedZone.Type ? (
225225
<Badge action="info" variant="outline" size="sm" className="self-start">

src/app/routes/history/instance/[id].tsx

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,12 @@ const STOP_STATUS_COLORS: Record<number, string> = {
6060
[RouteStopStatus.Skipped]: '#f59e0b',
6161
};
6262

63-
const DEVIATION_TYPE_LABELS: Record<number, string> = {
64-
[RouteDeviationType.OffRoute]: 'Off Route',
65-
[RouteDeviationType.MissedStop]: 'Missed Stop',
66-
[RouteDeviationType.UnexpectedStop]: 'Unexpected Stop',
67-
[RouteDeviationType.SpeedViolation]: 'Speed Violation',
68-
[RouteDeviationType.Other]: 'Other',
63+
const DEVIATION_TYPE_KEYS: Record<number, string> = {
64+
[RouteDeviationType.OffRoute]: 'routes.deviation_type_off_route',
65+
[RouteDeviationType.MissedStop]: 'routes.deviation_type_missed_stop',
66+
[RouteDeviationType.UnexpectedStop]: 'routes.deviation_type_unexpected_stop',
67+
[RouteDeviationType.SpeedViolation]: 'routes.deviation_type_speed_violation',
68+
[RouteDeviationType.Other]: 'routes.deviation_type_other',
6969
};
7070

7171
function formatDuration(seconds: number): string {
@@ -399,6 +399,7 @@ export default function RouteInstanceDetail() {
399399
// --- Sub-components ---
400400

401401
function StopCard({ stop }: { stop: RouteInstanceStopResultData }) {
402+
const { t } = useTranslation();
402403
const statusColor = STOP_STATUS_COLORS[stop.Status] ?? '#9ca3af';
403404
const StopIcon = getStopIcon(stop.Status);
404405

@@ -416,9 +417,9 @@ function StopCard({ stop }: { stop: RouteInstanceStopResultData }) {
416417
</Text>
417418
) : null}
418419
<HStack className="mt-1 space-x-3">
419-
{stop.CheckedInOn ? <Text className="text-xs text-gray-500">In: {formatDate(stop.CheckedInOn)}</Text> : null}
420-
{stop.CheckedOutOn ? <Text className="text-xs text-gray-500">Out: {formatDate(stop.CheckedOutOn)}</Text> : null}
421-
{stop.SkippedOn ? <Text className="text-xs text-gray-500">Skipped: {formatDate(stop.SkippedOn)}</Text> : null}
420+
{stop.CheckedInOn ? <Text className="text-xs text-gray-500">{t('routes.check_in')}: {formatDate(stop.CheckedInOn)}</Text> : null}
421+
{stop.CheckedOutOn ? <Text className="text-xs text-gray-500">{t('routes.check_out')}: {formatDate(stop.CheckedOutOn)}</Text> : null}
422+
{stop.SkippedOn ? <Text className="text-xs text-gray-500">{t('routes.skipped')}: {formatDate(stop.SkippedOn)}</Text> : null}
422423
</HStack>
423424
</VStack>
424425
<Text className="text-xs font-medium" style={{ color: statusColor }}>
@@ -431,7 +432,7 @@ function StopCard({ stop }: { stop: RouteInstanceStopResultData }) {
431432

432433
function DeviationCard({ deviation }: { deviation: RouteDeviationResultData }) {
433434
const { t } = useTranslation();
434-
const typeLabel = DEVIATION_TYPE_LABELS[deviation.Type] ?? t('routes.deviation');
435+
const typeLabel = DEVIATION_TYPE_KEYS[deviation.Type] ? t(DEVIATION_TYPE_KEYS[deviation.Type]) : t('routes.deviation');
435436

436437
return (
437438
<Box className="mb-2 rounded-lg border border-red-200 bg-red-50 p-3 dark:border-red-800 dark:bg-red-900/20">

src/app/routes/stop/contact.tsx

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -178,12 +178,12 @@ export default function StopContactScreen() {
178178

179179
{/* Phone numbers */}
180180
<Box className={`mt-2 ${colorScheme === 'dark' ? 'bg-neutral-900' : 'bg-white'}`}>
181-
<PhoneRow label="Phone" number={contact.Phone} colorScheme={colorScheme ?? 'light'} />
182-
<PhoneRow label="Mobile" number={contact.Mobile} colorScheme={colorScheme ?? 'light'} />
183-
<PhoneRow label="Home" number={contact.HomePhoneNumber} colorScheme={colorScheme ?? 'light'} />
184-
<PhoneRow label="Cell" number={contact.CellPhoneNumber} colorScheme={colorScheme ?? 'light'} />
185-
<PhoneRow label="Office" number={contact.OfficePhoneNumber} colorScheme={colorScheme ?? 'light'} />
186-
<PhoneRow label="Fax" number={contact.FaxPhoneNumber} colorScheme={colorScheme ?? 'light'} />
181+
<PhoneRow label={t('contacts.phone')} number={contact.Phone} colorScheme={colorScheme ?? 'light'} />
182+
<PhoneRow label={t('contacts.mobile')} number={contact.Mobile} colorScheme={colorScheme ?? 'light'} />
183+
<PhoneRow label={t('contacts.homePhone')} number={contact.HomePhoneNumber} colorScheme={colorScheme ?? 'light'} />
184+
<PhoneRow label={t('contacts.cellPhone')} number={contact.CellPhoneNumber} colorScheme={colorScheme ?? 'light'} />
185+
<PhoneRow label={t('contacts.officePhone')} number={contact.OfficePhoneNumber} colorScheme={colorScheme ?? 'light'} />
186+
<PhoneRow label={t('contacts.faxPhone')} number={contact.FaxPhoneNumber} colorScheme={colorScheme ?? 'light'} />
187187
</Box>
188188

189189
{/* Address */}
@@ -234,19 +234,19 @@ export default function StopContactScreen() {
234234
{locationGps && (
235235
<HStack className="items-center gap-1">
236236
<View style={[styles.legendDot, { backgroundColor: '#3b82f6' }]} />
237-
<Text className="text-xs text-typography-500">Location</Text>
237+
<Text className="text-xs text-typography-500">{t('routes.location')}</Text>
238238
</HStack>
239239
)}
240240
{entranceGps && (
241241
<HStack className="items-center gap-1">
242242
<View style={[styles.legendDot, { backgroundColor: '#22c55e' }]} />
243-
<Text className="text-xs text-typography-500">Entrance</Text>
243+
<Text className="text-xs text-typography-500">{t('routes.entrance')}</Text>
244244
</HStack>
245245
)}
246246
{exitGps && (
247247
<HStack className="items-center gap-1">
248248
<View style={[styles.legendDot, { backgroundColor: '#ef4444' }]} />
249-
<Text className="text-xs text-typography-500">Exit</Text>
249+
<Text className="text-xs text-typography-500">{t('routes.exit')}</Text>
250250
</HStack>
251251
)}
252252
</HStack>

src/models/v4/mapping/mappingResults.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { type FeatureCollection } from 'geojson';
22

33
import { BaseV4Request } from '../baseV4Request';
4-
import { type CustomMapResultData } from './customMapResultData';
4+
import { type CustomMapLayerResultData, type CustomMapResultData } from './customMapResultData';
55
import { type IndoorMapFloorResultData, type IndoorMapResultData } from './indoorMapResultData';
66

77
export class GetIndoorMapsResult extends BaseV4Request {
@@ -25,7 +25,7 @@ export class GetCustomMapResult extends BaseV4Request {
2525
}
2626

2727
export class GetCustomMapLayerResult extends BaseV4Request {
28-
public Data: CustomMapResultData = {} as CustomMapResultData;
28+
public Data: CustomMapLayerResultData = {} as CustomMapLayerResultData;
2929
}
3030

3131
export class GetGeoJSONResult extends BaseV4Request {

src/stores/routes/store.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,9 @@ interface RoutesState {
6565
fetchDirections: (instanceId: string) => Promise<void>;
6666

6767
// Actions - Stop Interactions
68-
checkIn: (stopId: string, unitId: string, lat: number, lon: number) => Promise<void>;
69-
checkOut: (stopId: string, unitId: string) => Promise<void>;
70-
skip: (stopId: string, reason: string) => Promise<void>;
68+
checkIn: (stopId: string, unitId: string, lat: number, lon: number) => Promise<boolean>;
69+
checkOut: (stopId: string, unitId: string) => Promise<boolean>;
70+
skip: (stopId: string, reason: string) => Promise<boolean>;
7171
performGeofenceCheckIn: (unitId: string, lat: number, lon: number) => Promise<void>;
7272
updateNotes: (stopId: string, notes: string) => Promise<void>;
7373

@@ -288,8 +288,10 @@ export const useRoutesStore = create<RoutesState>((set, get) => ({
288288
set({
289289
instanceStops: instanceStops.map((s) => (s.RouteInstanceStopId === stopId ? { ...s, Status: 1, CheckedInOn: new Date().toISOString() } : s)),
290290
});
291+
return true;
291292
} catch (error) {
292293
set({ error: 'Failed to check in at stop' });
294+
return false;
293295
}
294296
},
295297

@@ -300,8 +302,10 @@ export const useRoutesStore = create<RoutesState>((set, get) => ({
300302
set({
301303
instanceStops: instanceStops.map((s) => (s.RouteInstanceStopId === stopId ? { ...s, Status: 2, CheckedOutOn: new Date().toISOString() } : s)),
302304
});
305+
return true;
303306
} catch (error) {
304307
set({ error: 'Failed to check out from stop' });
308+
return false;
305309
}
306310
},
307311

@@ -312,8 +316,10 @@ export const useRoutesStore = create<RoutesState>((set, get) => ({
312316
set({
313317
instanceStops: instanceStops.map((s) => (s.RouteInstanceStopId === stopId ? { ...s, Status: 3, SkippedOn: new Date().toISOString() } : s)),
314318
});
319+
return true;
315320
} catch (error) {
316321
set({ error: 'Failed to skip stop' });
322+
return false;
317323
}
318324
},
319325

src/translations/ar.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,7 @@
515515
"feature": "ميزة",
516516
"floor": "طابق",
517517
"floor_count": "{{count}} طوابق",
518+
"floor_count_one": "{{count}} طابق",
518519
"general": "عام",
519520
"indoor": "داخلي",
520521
"indoor_maps": "خرائط داخلية",
@@ -526,10 +527,12 @@
526527
"no_maps_description": "لا توجد خرائط متاحة لقسمك.",
527528
"no_results": "لم يتم العثور على نتائج",
528529
"outdoor": "خارجي",
530+
"region": "منطقة",
529531
"search": "البحث في الخرائط...",
530532
"search_maps": "البحث في الخرائط",
531533
"search_placeholder": "البحث عن مواقع، مناطق...",
532534
"title": "الخرائط",
535+
"zone": "منطقة",
533536
"zone_details": "تفاصيل المنطقة"
534537
},
535538
"notes": {
@@ -609,24 +612,32 @@
609612
"description": "الوصف",
610613
"destination": "وجهة المسار",
611614
"deviation": "انحراف المسار",
615+
"deviation_type_missed_stop": "توقف فائت",
616+
"deviation_type_off_route": "خارج المسار",
617+
"deviation_type_other": "أخرى",
618+
"deviation_type_speed_violation": "مخالفة سرعة",
619+
"deviation_type_unexpected_stop": "توقف غير متوقع",
612620
"deviations": "الانحرافات",
613621
"directions": "الاتجاهات",
614622
"distance": "المسافة",
615623
"duration": "المدة",
616624
"dwell_time": "وقت التوقف",
617625
"end_route": "إنهاء المسار",
618626
"end_route_confirm": "هل أنت متأكد من إنهاء هذا المسار؟",
627+
"entrance": "مدخل",
619628
"estimated_distance": "المسافة المقدرة",
620629
"estimated_duration": "المدة المقدرة",
621630
"eta": "الوقت المتوقع للوصول",
622631
"eta_to_next": "الوقت المتوقع للمحطة التالية",
632+
"exit": "مخرج",
623633
"geofence_radius": "نطاق السياج الجغرافي",
624634
"history": "سجل المسار",
625635
"in_progress": "قيد التنفيذ",
626636
"instance_detail": "تفاصيل المسار",
627637
"loading": "جارٍ تحميل المسارات...",
628638
"loading_directions": "جارٍ تحميل الاتجاهات...",
629639
"loading_stops": "جارٍ تحميل المحطات...",
640+
"location": "الموقع",
630641
"min": "دق",
631642
"my_unit": "وحدتي",
632643
"next_step": "الخطوة التالية",
@@ -669,6 +680,7 @@
669680
"step_of": "الخطوة {{current}} من {{total}}",
670681
"stop_contact": "جهة اتصال المحطة",
671682
"stop_count": "{{count}} محطات",
683+
"stop_count_one": "{{count}} محطة",
672684
"stop_detail": "تفاصيل المحطة",
673685
"stop_type_dropoff": "تسليم",
674686
"stop_type_inspection": "تفتيش",

0 commit comments

Comments
 (0)