Add React Native + Expo mobile app (bare workflow) - #2
Conversation
Sets up the complete React Native + Expo (SDK 54) project in native/ alongside
the existing web app. Covers all infrastructure, configuration, and business
logic layers so parallel screen-conversion agents can build on a clean base.
What's in this commit
─────────────────────
• MOBILE_MIGRATION_PLAN.md — full route→screen mapping doc (Phase 1)
• native/app.json — bundle IDs, permissions, dark splash, scheme
• native/eas.json — development / preview / production EAS Build profiles
• native/babel.config.js — NativeWind + module-resolver + Reanimated (last)
• native/metro.config.js — withNativeWind wrapper
• native/tailwind.config.js — brand palette (#0E0E12, #00E5FF, #9B59B6)
• native/tsconfig.json — @/ path alias → ./src/
• native/.env.example — EXPO_PUBLIC_* variable template
• src/integrations/supabase/client.ts — AsyncStorage auth (replaces localStorage)
• src/integrations/supabase/types.ts — verbatim copy of generated DB types
• src/constants/, src/types/ — copied as-is from web app
• src/lib/{utils,toast,geo,device,ble}.ts — native rewrites
• src/hooks/ — 9 hooks (5 verbatim, useBiometrics env-var swap,
useBlePeripheral→ble-advertiser, useBleScanner NEW central scan)
• src/navigation/ — RootNavigator (auth-aware), AuthStack, StudentTabs,
LecturerTabs, typed ParamLists
• src/theme/colors.ts — brand color tokens
• src/components/ui/ — Button, Card, Input, Badge (RN primitives)
• src/components/ — BlurCard (expo-blur), PresenceLoader (Reanimated),
CameraCapture (expo-camera, replaces MediaPipe LivenessScanner)
• src/components/dashboard/ — LiveSessionCard, DashboardHeader,
AttendancePinger, CourseManagementDialog, LogoutConfirmDialog
• src/components/live-session/ — SessionHeader, SessionStats
• src/screens/ — LandingScreen, LoginScreen (auth screens in progress)
• App.tsx — GestureHandlerRootView → SafeAreaProvider → QueryClient → RootNavigator
Screens still being written by parallel agents (follow-up commit pending):
register wizard, StudentDashboard, AttendanceVerification, AttendanceLedger,
LecturerDashboard, CreateSession, LiveSession, remaining live-session components.
https://claude.ai/code/session_013FrbpAh4Sg7nG88Y62p3yE
Written by parallel agents: - src/screens/StudentDashboardScreen.tsx - src/screens/register/CourseSelectScreen.tsx - src/screens/register/FaceEnrollScreen.tsx - src/components/live-session/AttendanceFeed.tsx - src/components/live-session/EngagementChart.tsx - src/components/live-session/SessionStats.tsx (updated) https://claude.ai/code/session_013FrbpAh4Sg7nG88Y62p3yE
…tion Full React Native + Expo (SDK 54, bare workflow) project in native/ alongside the untouched web app. All infrastructure, business logic, and UI layers are complete; remaining screens being written by parallel agents will follow. See MOBILE_MIGRATION_PLAN.md for the full route→screen mapping. https://claude.ai/code/session_013FrbpAh4Sg7nG88Y62p3yE
Add the remaining lecturer/student screen implementations (Attendance
Verification, Ledger, Create Session, Live Session, Lecturer Dashboard,
Lecturer Register, Manual Entry, Analytics, Not Found) plus a stub
EngagementChart that renders a sparkline via react-native-svg instead of
victory-native v41 (which requires Skia).
Resolve outstanding TypeScript errors:
- Toast shim accepts both string and `{ description }` second-argument
shapes and exposes `warning` so existing call sites compile.
- Button accepts an optional `style` prop merged into its Pressable.
- NativeWind type reference points at `nativewind/types` so `className`
is recognised on RN intrinsic elements.
- BLE peripheral broadcast encodes the token prefix as a manufacturer-
data byte array (matches `react-native-ble-advertiser` v0 signature).
- BLE scanner uses the v12 `ScanOptions` object form for `scan()`.
- Strongly type checklist colours and add `day_number` to session
inserts so the Supabase row matches the schema.
Install the missing native runtime deps (lucide-react-native, victory-
native, react-native-svg, react-native-toast-message, react-native-ble-
manager/advertiser, expo-camera/location/device/secure-store/blur/
haptics) so `tsc --noEmit` and module resolution both succeed.
https://claude.ai/code/session_013FrbpAh4Sg7nG88Y62p3yE
Replaces the old Capacitor Gradle workflow with an EAS Build job that: - Triggers on pushes to main / the native migration branch and via workflow_dispatch (so you can trigger a build manually from the GitHub Actions tab with a choice of preview or production profile). - Runs tsc --noEmit first so the build only proceeds when the TypeScript check is green. - Uses the existing eas.json `preview` profile which outputs a side-loadable APK (not an AAB) via `android.buildType: apk`. - Passes Supabase + biometric server env vars from repo secrets. - Writes the EAS build dashboard URL to the job summary so the APK download link is one click away after the ~10-min cloud build. Required repo secrets to set before triggering: EXPO_TOKEN — from expo.dev/settings/access-tokens EXPO_PUBLIC_SUPABASE_URL EXPO_PUBLIC_SUPABASE_PUBLISHABLE_KEY EXPO_PUBLIC_BIOMETRIC_SERVER_URL https://claude.ai/code/session_013FrbpAh4Sg7nG88Y62p3yE
Replaces the EAS cloud build workflow with a direct Gradle build that runs entirely on the ubuntu-latest runner (which ships with the Android SDK). No EXPO_TOKEN or Expo account required. Steps: 1. npm ci + tsc --noEmit — fail fast on type errors 2. expo prebuild --platform android -- generates android/ from app.json 3. gradlew assembleDebug -- compiles the debug APK 4. upload-artifact -- makes app-debug.apk downloadable directly from the Actions run page (retained 14 days) Env vars for Supabase / biometric server fall back to placeholder strings during prebuild (only needed at runtime, not compile time). Real values can be added as repo secrets to bake them into the bundle. https://claude.ai/code/session_013FrbpAh4Sg7nG88Y62p3yE
actions/checkout, setup-node, setup-java, cache, and upload-artifact all updated from v4 → v5 so the action runners use Node.js 22. https://claude.ai/code/session_013FrbpAh4Sg7nG88Y62p3yE
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Analysis CompleteGenerated ECC bundle from 7 commits | Confidence: 70% View Pull Request #3Repository Profile
Changed Files (80)
Top hotspots
Top directories
Analysis Depth Readiness (evidence-backed, 43%)ECC Tools uses this to decide whether recommendations should stay at commit-history/setup guidance or expand into CI, security, harness, reference-set, AI-routing, and team backlog work.
Reference Set Readiness (0/7, 0%)
Likely Future Issues (5)
Suggested Follow-up Work (5)
Copy-ready bodies test: add regression coverage for native/App.tsx + native/babel.config.js ## Summary
- Add regression coverage for the recently touched code paths before more changes stack on top.
## Why
- Backfill regression coverage before another change set lands on the touched code paths.
## Touched paths
- `native/App.tsx`
- `native/babel.config.js`
## Validation
- Add or extend focused tests that exercise the touched paths.
- Run the affected test suite and verify the new coverage closes the gap.chore: sync config templates for native/.claude/settings.json + native/babel.config.js ## Summary
- Update the example env files, sample configs, or deployment templates that should mirror the changed runtime configuration surface.
## Why
- Backfill example env files or config templates before a fresh setup drifts from the shipped runtime surface.
## Touched paths
- `native/.claude/settings.json`
- `native/babel.config.js`
## Validation
- Update the repo example env file or config template that should reflect the new runtime settings.
- Run the setup, boot, or deployment validation flow that depends on the changed config surface.test: add browser coverage for native/global.css + native/src/components/BlurCard.tsx ## Summary
- Add browser or end-to-end coverage for the recently changed user-facing surface.
## Why
- Backfill browser coverage before another user-facing UI change lands on the touched surface.
## Touched paths
- `native/global.css`
- `native/src/components/BlurCard.tsx`
## Validation
- Add or extend browser / e2e coverage for the changed component, page, or flow.
- Exercise the visible user journey that depends on the touched UI surface.security: add scanner evidence for native/src/hooks/use-toast.ts + native/src/hooks/useAttendanceStats.ts ## Summary
- Add security scanner or code-scanning evidence for the recently changed security-sensitive surface.
## Why
- Backfill explicit scanner or code-scanning evidence before another security-sensitive change lands on the touched surface.
## Touched paths
- `native/src/hooks/use-toast.ts`
- `native/src/hooks/useAttendanceStats.ts`
## Validation
- Run or add the relevant security scanner, code scanning, secret scanning, or dependency/security review check for the touched surface.
- Attach the scanner output, SARIF/code-scanning result, or focused security regression test to the follow-up PR.
- Confirm the changed auth, billing, webhook, secret-handling, agent, or CI surface has an explicit pass/fail gate.ci: add failure-mode evidence for .github/workflows/build-android.yml ## Summary
- Add CI failure-mode evidence for the recently changed workflow or test-runner surface.
## Why
- Backfill CI failure-mode evidence before another workflow or test-runner change lands on the touched surface.
## Touched paths
- `.github/workflows/build-android.yml`
## Validation
- Add or update a CI failure fixture, captured failing log, troubleshooting note, workflow dry-run evidence, or regression test for the changed CI/test-runner behavior.
- Run the affected workflow or test-runner entrypoint locally or in CI and record pass/fail evidence.Detected Workflows (3)
Generated Instincts (29)
After merging, import with: Files
|
There was a problem hiding this comment.
Pull request overview
This PR introduces a new React Native + Expo (bare workflow) mobile app under /native to mirror the existing Smart Campus Presence attendance features, including Supabase-backed auth/data flows and student/lecturer navigation.
Changes:
- Added a full Expo workspace (
/native) with build configuration, NativeWind styling, Supabase client integration, and navigation scaffolding. - Implemented core auth + registration screens and lecturer/student flows (live session monitoring, ledger/history, dashboards/components).
- Added GitHub Actions workflow updates to build an Android debug APK from the new native app.
Reviewed changes
Copilot reviewed 74 out of 80 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
| native/tsconfig.json | Expo TS config + path aliases |
| native/tailwind.config.js | NativeWind/Tailwind theme tokens |
| native/src/types/index.ts | Supabase-derived domain types |
| native/src/theme/colors.ts | Central color system |
| native/src/screens/register/CourseSelectScreen.tsx | Registration step: course selection |
| native/src/screens/register/BasicInfoScreen.tsx | Registration step: basic info |
| native/src/screens/NotFoundScreen.tsx | Fallback screen |
| native/src/screens/LoginScreen.tsx | Supabase password login |
| native/src/screens/LiveSessionScreen.tsx | Lecturer live session monitoring UI |
| native/src/screens/LecturerRegisterScreen.tsx | Lecturer registration flow |
| native/src/screens/LandingScreen.tsx | Unauthenticated landing/CTA screen |
| native/src/screens/AttendanceLedgerScreen.tsx | Ledger/history screen |
| native/src/screens/AnalyticsScreen.tsx | Lecturer analytics placeholder |
| native/src/navigation/types.ts | Navigation param typing |
| native/src/navigation/StudentTabs.tsx | Student bottom tabs + nested stack |
| native/src/navigation/RootNavigator.tsx | Auth/role-based root switching |
| native/src/navigation/LecturerTabs.tsx | Lecturer bottom tabs + nested stack |
| native/src/navigation/AuthStack.tsx | Unauthenticated navigation + register stack |
| native/src/lib/utils.ts | Utility helpers (cn, percentage) |
| native/src/lib/toast.ts | Toast wrapper API |
| native/src/lib/geo.ts | Location permission + haversine distance |
| native/src/lib/device.ts | SecureStore-based device ID + metadata |
| native/src/lib/ble.ts | BLE advertiser wrapper |
| native/src/integrations/supabase/types.ts | Generated Supabase database types |
| native/src/integrations/supabase/client.ts | RN Supabase client with AsyncStorage auth |
| native/src/hooks/useSessionData.ts | Session details + realtime records hook |
| native/src/hooks/useProfile.ts | Current user profile hook |
| native/src/hooks/useLiveSessions.ts | Student live sessions hook |
| native/src/hooks/useLecturerData.ts | Lecturer dashboard data hook |
| native/src/hooks/useBleScanner.ts | Student BLE scanning hook |
| native/src/hooks/useBlePeripheral.ts | Lecturer BLE broadcast hook |
| native/src/hooks/useBiometrics.ts | Biometric enroll/verify API hook |
| native/src/hooks/useAttendanceStats.ts | Student attendance stats hook |
| native/src/hooks/use-toast.ts | Re-export of toast API |
| native/src/constants/index.ts | Shared constants (statuses, BLE UUIDs, etc.) |
| native/src/components/ui/Input.tsx | Input UI component |
| native/src/components/ui/index.ts | UI barrel exports |
| native/src/components/ui/Card.tsx | Card UI component |
| native/src/components/ui/Button.tsx | Button UI component |
| native/src/components/ui/Badge.tsx | Badge UI component |
| native/src/components/PresenceLoader.tsx | Animated loader component |
| native/src/components/OfflineStatus.tsx | Connectivity banner component |
| native/src/components/LogoutConfirmDialog.tsx | Logout confirmation modal |
| native/src/components/live-session/SessionStats.tsx | Live session stats widget |
| native/src/components/live-session/SessionHeader.tsx | Live session header widget |
| native/src/components/live-session/ManualEntry.tsx | Manual attendance entry modal |
| native/src/components/live-session/EngagementChart.tsx | Lightweight sparkline chart |
| native/src/components/live-session/AttendanceFeed.tsx | Live attendance feed list |
| native/src/components/dashboard/LiveSessionCard.tsx | Student “mark attendance” session card |
| native/src/components/dashboard/DashboardHeader.tsx | Lecturer dashboard header |
| native/src/components/dashboard/CourseManagementDialog.tsx | Course enrollment management modal |
| native/src/components/dashboard/AttendanceScoreboard.tsx | Attendance ring + breakdown UI |
| native/src/components/dashboard/AttendancePinger.tsx | “Live/no sessions” indicator |
| native/src/components/BlurCard.tsx | Blur/glassmorphism card wrapper |
| native/package.json | Native app dependencies/scripts |
| native/nativewind-env.d.ts | NativeWind type reference |
| native/metro.config.js | Metro + NativeWind config |
| native/index.ts | Expo root registration |
| native/global.css | Tailwind directives for NativeWind |
| native/eas.json | EAS build profiles |
| native/CLAUDE.md | Agent config reference |
| native/babel.config.js | Babel config (NativeWind + module resolver + reanimated) |
| native/App.tsx | App root providers + RootNavigator + Toast |
| native/app.json | Expo app config + permissions |
| native/AGENTS.md | Agent guidance note |
| native/.gitignore | Native workspace gitignore |
| native/.claude/settings.json | Claude plugin settings |
| MOBILE_MIGRATION_PLAN.md | Migration plan documentation |
| .github/workflows/build-android.yml | Android APK build workflow for /native |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| Toast.show({ type: "info", text1, text2: typeof text2 === "object" ? text2?.description : text2 }), | ||
| warning: (text1: string, text2?: string | { description?: string }) => | ||
| Toast.show({ type: "info", text1, text2: typeof text2 === "object" ? text2?.description : text2 }), |
| const { data, error } = await supabase | ||
| .from('courses') | ||
| .select('*') | ||
| .eq('level', params.level) | ||
| .eq('semester', params.semester); | ||
|
|
| useEffect(() => { | ||
| if (!sessionId) return; | ||
| fetchLedgerData(); | ||
| }, [sessionId]); |
| try { | ||
| // Encode first 4 chars of token as ASCII bytes in manufacturer data so | ||
| // student scanners can match the broadcast without GATT service discovery. | ||
| const tokenPrefix = token.slice(0, 4); | ||
| const manufData: number[] = []; | ||
| for (let i = 0; i < tokenPrefix.length; i++) { | ||
| manufData.push(tokenPrefix.charCodeAt(i)); | ||
| } | ||
|
|
||
| await BLEAdvertiser.broadcast( | ||
| SERVICE_UUID, | ||
| manufData, | ||
| { | ||
| includeDeviceName: false, | ||
| connectable: false, | ||
| } | ||
| ); |
| foundRef.current = false; | ||
| resolveRef.current = resolve; | ||
|
|
||
| const expectedName = 'Session-' + targetToken.slice(0, 4); | ||
|
|
||
| setIsScanning(true); | ||
|
|
||
| try { | ||
| // Ensure the BLE module is started before scanning | ||
| await BleManager.start({ showAlert: false }); | ||
| } catch (err) { | ||
| // start() throws if already initialized — safe to ignore | ||
| console.log('[useBleScanner] BleManager.start() (may already be running):', err); | ||
| } | ||
|
|
||
| // Register the peripheral discovery listener before starting the scan | ||
| // so we don't miss advertisements that arrive immediately. | ||
| subscriptionRef.current = bleEmitter.addListener( | ||
| 'BleManagerDiscoverPeripheral', | ||
| (peripheral: Peripheral) => { | ||
| if (foundRef.current) return; // already resolved | ||
|
|
||
| const name = peripheral.name ?? ''; | ||
|
|
||
| if (name === expectedName) { | ||
| foundRef.current = true; |
| const { data: records } = await supabase | ||
| .from("attendance_records") | ||
| .select("session_id") | ||
| .eq("student_id", studentId); | ||
|
|
| if (sessionsData) { | ||
| const sessionsWithStats = await Promise.all( | ||
| sessionsData.map(async (s: any) => { | ||
| const { count: totalEnrolled } = await supabase | ||
| .from("enrollments") | ||
| .select("*", { count: "exact", head: true }) | ||
| .eq("course_id", s.course_id); | ||
|
|
||
| const present = | ||
| s.attendance_records?.filter( | ||
| (r: any) => r.status === "verified" | ||
| ).length || 0; | ||
| return { ...s, present, total: totalEnrolled || 0 }; | ||
| }) |
| function CourseRow({ course }: { course: CourseBreakdown }) { | ||
| const barWidth = useSharedValue(0); | ||
|
|
||
| useEffect(() => { | ||
| barWidth.value = withTiming(course.percentage, { | ||
| duration: 1000, | ||
| easing: Easing.out(Easing.ease), | ||
| }); | ||
| }, [course.percentage]); | ||
|
|
||
| const animatedBarStyle = { | ||
| width: `${course.percentage}%` as `${number}%`, | ||
| }; | ||
|
|
||
| return ( |
| branches: [main, claude/review-deployment-readiness-3tEAH] | ||
| paths: | ||
| - 'native/**' | ||
| - '.github/workflows/build-android.yml' |
| const [authState, setAuthState] = useState<AuthState>('loading'); | ||
| // Keep a stable ref so the auth-state-change listener can read it without | ||
| // becoming a stale closure dependency. | ||
| const authStateRef = useRef<AuthState>('loading'); | ||
|
|
actions/cache and actions/upload-artifact do not have v5 releases yet, so resolving them at parse time was failing the workflow silently (no run appeared on the latest commit). Reverting just those two to v4 while keeping checkout/setup-node/setup-java on v5 (which is what was actually flagged by the Node 20 deprecation warning). Also guard the job summary step with `if: success()` and a file-exists check so it doesn't crash when an earlier step fails. https://claude.ai/code/session_013FrbpAh4Sg7nG88Y62p3yE
CI: - Revert all build-android.yml actions to @v4 (actions/setup-java@v5, cache@v5, upload-artifact@v5 do not have v5 tags yet, which caused the workflow to fail at parse time in 9s with no run logs). - Add a pull_request trigger and drop the hard-coded feature-branch name from on.push.branches (per Copilot review). Critical BLE fix: - useBleScanner previously matched `peripheral.name === "Session-XXXX"`, but the broadcaster (after the type-fix earlier in this branch) no longer advertises a local name and instead encodes the token prefix as ASCII bytes in manufacturer data. That mismatch meant the scanner would never resolve `found: true`. The scanner now walks the raw `advertising.manufacturerData.bytes` array and looks for the contiguous token-prefix subsequence — aligning student-side discovery with what the lecturer is actually broadcasting. Review-comment fixes (small, low-risk): - toast.warning now maps to the "error" type so warnings are visually distinct from info; docstring explains how to wire a true "warning" renderer if needed. - AttendanceLedgerScreen no longer hangs on the loading spinner when `sessionId` is undefined (history-tab use case) — it sets loading=false and renders the empty state. - LiveSessionScreen presentCount now includes ATTENDANCE_STATUS.MANUAL so manual entries don't undercount lecturer stats. Remaining Copilot review items deferred for follow-up (need more context / are larger refactors): CourseSelectScreen department filter, useAttendanceStats status filter, useLecturerData N+1 query, AttendanceScoreboard dead Reanimated value, RootNavigator authStateRef. https://claude.ai/code/session_013FrbpAh4Sg7nG88Y62p3yE
The native workspace's lockfile was generated under --legacy-peer-deps because react-native-screens@4.25.1 declares a peer dep on RN >= 0.82 while Expo 54 ships RN 0.81.5. Without this .npmrc, GitHub Actions' strict `npm ci` aborts immediately with ERESOLVE — which is why the Android build failed at ~14 seconds before reaching any real work. A future cleanup would be to pin react-native-screens to Expo SDK 54's recommended version (~4.4.x) and drop the override, but adding .npmrc is the minimal change to get CI green now and keep local installs consistent with package-lock.json. https://claude.ai/code/session_013FrbpAh4Sg7nG88Y62p3yE
Three runs in a row have failed at 30s with no visible logs through MCP. Adding a Diagnostics step that prints node/npm versions and the resolved `legacy-peer-deps` setting, plus `--verbose 2>&1 | tail -40` on `npm ci`, so the next run's job output makes the failure point obvious. Will revert once the underlying issue is identified. https://claude.ai/code/session_013FrbpAh4Sg7nG88Y62p3yE
Once `expo prebuild` has generated the native android/ and ios/ folders, the project is on the bare workflow and `expo start --android` will not actually build/install the local native code. `expo run:*` does the right thing. (Side-effect of running prebuild locally during CI debugging — the scripts were auto-updated by expo and are worth keeping.) https://claude.ai/code/session_013FrbpAh4Sg7nG88Y62p3yE
The TypeScript check in CI failed with: src/lib/utils.ts(1,39): error TS2307: Cannot find module 'clsx' src/lib/utils.ts(2,25): error TS2307: Cannot find module 'tailwind-merge' These are used by the `cn()` helper but were never added to the native workspace's package.json (carried over from the web project without their deps). Local installs picked them up via npm's lookup through the parent web project; CI's `npm ci` runs in isolation and strictly enforces the lockfile, so the missing deps surfaced. Also reverts the diagnostic step / verbose npm ci added in 1d4c1d2 — no longer needed now that the failure is pinpointed. https://claude.ai/code/session_013FrbpAh4Sg7nG88Y62p3yE
Gradle failed at react-native-reanimated/android/build.gradle:310 with a Groovy script error. Root cause: we had 4.3.1 installed, but Expo 54's bundledNativeModules.json specifies ~4.1.1. The patch release bump introduced a Gradle-script regression incompatible with the AGP version expo prebuild generates. Pinning to ~4.1.1 so we stay on the Expo 54 tested version. https://claude.ai/code/session_013FrbpAh4Sg7nG88Y62p3yE
…ch req) reanimated v4.x has two hard requirements that don't fit our setup: 1. Gradle build.gradle has an `assertNewArchitectureEnabledTask` that fails the build unless `newArchEnabled=true`. Our app.json explicitly opts out of the new architecture because several deps in this app (BLE manager/advertiser, etc.) haven't been validated under Fabric. 2. v4 split the worklets runtime into a separate `react-native-worklets` package, which isn't installed. Its build.gradle calls `node ./../scripts/validate-worklets-build.js` which exits 1 when the package isn't there — that's the actual error we were seeing. v3.16 keeps worklets in-tree, supports both architectures, and works with RN 0.81. Our reanimated usage (shared values, useAnimatedStyle, withTiming/withSpring) is unchanged between v3 and v4, so no source changes are needed — `tsc --noEmit` and `expo prebuild` both pass. https://claude.ai/code/session_013FrbpAh4Sg7nG88Y62p3yE
Full diff against bundledNativeModules.json — all packages were ahead of Expo 54's tested/pinned versions, which caused Gradle to fail during dependency resolution for :react-native-async-storage_async-storage (v3.0.3 → needs KSP/Kotlin ≥2.1 which wasn't present; Expo wants 2.2.0). Packages downgraded to Expo 54 recommendations: @react-native-async-storage/async-storage 3.0.3 → 2.2.0 expo-blur 55.0.x → 15.0.8 expo-camera 55.0.x → 17.0.10 expo-device 55.0.x → 8.0.10 expo-haptics 55.0.x → 15.0.8 expo-location 55.1.x → 19.0.8 expo-secure-store 55.0.x → 15.0.8 react-native-gesture-handler 2.31.x → 2.28.0 react-native-screens 4.25.x → 4.16.0 react-native-safe-area-context 5.8.x → 5.6.0 react-native-svg 15.15.x→ 15.12.1 react-native-reanimated stays at ~3.16.0 (v4.x requires newArchEnabled which we keep disabled due to BLE libs not being validated on Fabric). tsc --noEmit and expo prebuild both pass after the downgrade. https://claude.ai/code/session_013FrbpAh4Sg7nG88Y62p3yE
- Add web bundling deps: react-native-web, react-dom, @expo/metro-runtime - Pin babel-preset-expo to ~54.0.10 (matches Expo SDK 54) and add babel-plugin-module-resolver as a devDep (was implicitly required by babel.config.js but missing — could break native builds too) - Add react-native-worklets (required by nativewind v4's babel preset) - Move nativewind/babel to presets[] (was incorrectly in plugins[]) - Add .web.ts/.web.tsx to module-resolver extensions - Stub BLE modules for web (lib/ble.web.ts, hooks/useBleScanner.web.ts) so the bundler doesn't pull native-only packages on web - Add web-only RootNavigator.web.tsx that bypasses Supabase auth and exposes every screen at a URL path for headless screenshotting https://claude.ai/code/session_013FrbpAh4Sg7nG88Y62p3yE
The previous commit added react-native-worklets as a babel plugin
dependency for nativewind/babel preset. But worklets ships its own
Android native module (build.gradle + CMakeLists.txt + codegenConfig),
so Expo autolinks it during prebuild → Gradle tries to build it →
conflicts with the pinned reanimated v3.16.
Drop nativewind/babel from the babel preset chain entirely —
babel-preset-expo with { jsxImportSource: "nativewind" } already
provides the NativeWind transform. Verified web export still works
(4.04 MB bundle) and tsc --noEmit is clean.
https://claude.ai/code/session_013FrbpAh4Sg7nG88Y62p3yE
…etric endpoint URL
Summary
This PR introduces a complete React Native + Expo implementation of the Smart Campus Presence attendance system, running in bare workflow mode. The native app mirrors the web application's core functionality while leveraging platform-specific APIs for GPS, BLE, and biometric verification.
Key Changes
New Mobile App Structure (
/native)App.tsx,index.ts, configuration files (app.json,eas.json,tsconfig.json,babel.config.js,metro.config.js)RootNavigator,AuthStack,StudentTabs,LecturerTabs, andRegisterStackButton,Input,Badge,Card,BlurCard)Core Screens Implemented
LandingScreen,LoginScreen,LecturerRegisterScreenBasicInfoScreen,CourseSelectScreen,FaceEnrollScreenStudentDashboardScreenwith live sessions list and attendance statsAttendanceVerificationScreen— multi-step verification (init → GPS → BLE → face → upload) with animated checklist and haptic feedbackLecturerDashboardScreenwith stat cards and session managementCreateSessionScreen(form to launch sessions),LiveSessionScreen(real-time attendance feed)AttendanceLedgerScreenwith search and filteringVerification & Biometrics
CameraCapture.tsx: Expo Camera integration for face enrollment and verification (base64 JPEG capture)useBiometricshook: Communicates with remote InsightFace API for face vectorization and similarity scoringuseBleScannerhook: BLE proximity scanning with RSSI threshold validation (> -80 dB)Dashboard Components
AttendanceScoreboard: Animated progress ring (Reanimated) showing overall attendance % and per-course breakdownLiveSessionCard: Session card with course info, lecturer name, and "Mark Attendance" CTAAttendanceFeed: Real-time list of student check-ins with status badgesSessionStats: Present/total enrollment countersSessionHeader: Course code, name, topic, and date displayEngagementChart: Cumulative attendance sparkline by 5-minute intervalsHooks & Utilities
useProfile: Fetch authenticated user profile from SupabaseuseLiveSessions: Query active sessions for studentsuseAttendanceStats: Calculate overall and per-course attendance metricsuseSessionData: Fetch session details and attendance records with real-time subscriptionsuseLecturerData: Dashboard stats (total students, course count, avg attendance)useBlePeripheral: BLE broadcast control for lecturer sessionsgetUniqueDeviceId: Secure device binding viaexpo-secure-storegetCurrentPosition/calculateDistance: Geolocation helpers usingexpo-locationDatabase Types
native/src/integrations/supabase/types.ts: Auto-generated TypeScript types for all Supabase tables (attendance_records, profiles, courses, sessions, enrollments, face_embeddings, etc.)Configuration & Build
react-native-toast-messageintegrationDocumentation
https://claude.ai/code/session_013FrbpAh4Sg7nG88Y62p3yE