Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
206 commits
Select commit Hold shift + click to select a range
e0d6ed0
Initial plan
Copilot Aug 20, 2025
dc6af59
Initial plan
Copilot Aug 20, 2025
5c11fc8
Initial plan
Copilot Aug 20, 2025
4cf6421
Initial plan
Copilot Aug 20, 2025
852bd4d
Initial plan
Copilot Aug 22, 2025
79469f7
Initial plan
Copilot Aug 22, 2025
66b2096
Initial plan
Copilot Aug 23, 2025
7a04f87
Initial plan
Copilot Aug 23, 2025
f31465d
Initial plan
Copilot Aug 20, 2025
bd607fd
Initial plan
Copilot Aug 23, 2025
6afacd7
Initial plan
Copilot Aug 24, 2025
bb8892b
Initial plan
Copilot Aug 23, 2025
cdd67b7
Initial plan
Copilot Aug 23, 2025
41de9b1
Initial plan
Copilot Sep 9, 2025
48e0fac
Initial plan
Copilot Sep 11, 2025
af245b2
Initial plan
Copilot Sep 10, 2025
e000a2d
Initial plan
Copilot Sep 11, 2025
731e12e
Initial plan
Copilot Sep 12, 2025
948d66a
Initial plan
Copilot Oct 4, 2025
322c666
Initial plan
Copilot Oct 17, 2025
411f9bb
Initial plan
Copilot Oct 17, 2025
ff91046
Initial plan
Copilot Oct 18, 2025
52a6d05
Initial plan
Copilot Oct 20, 2025
5191d1c
Initial plan
Copilot Oct 25, 2025
bb310ad
Initial plan
Copilot Oct 26, 2025
90dee1e
Initial plan
Copilot Nov 18, 2025
10052e9
Initial plan
Copilot Nov 6, 2025
868d45b
Initial plan
Copilot Nov 6, 2025
ab4e908
Initial plan
Copilot Nov 8, 2025
2179f53
Initial plan
Copilot Nov 8, 2025
8da2d77
Initial plan
Copilot Nov 9, 2025
8e9055a
Initial plan
Copilot Nov 9, 2025
de81a6a
Initial plan
Copilot Nov 11, 2025
8d266a5
Initial plan
Copilot Nov 11, 2025
82f0211
Initial plan
Copilot Nov 11, 2025
1e5d24a
Initial plan
Copilot Nov 11, 2025
a32109d
Initial plan
Copilot Nov 18, 2025
65b7dce
Initial plan
Copilot Nov 18, 2025
466a639
Initial plan
Copilot Nov 18, 2025
7529e0a
Initial plan
Copilot Nov 18, 2025
7f946d5
Initial plan
Copilot Nov 18, 2025
869c92a
Initial plan
Copilot Nov 18, 2025
eb74652
Initial plan
Copilot Nov 18, 2025
7b361d4
Initial plan
Copilot Nov 18, 2025
371f052
Initial plan
Copilot Nov 18, 2025
cab38cd
Initial plan
Copilot Nov 18, 2025
f6b0590
Initial plan
Copilot Nov 18, 2025
44b8121
Initial plan
Copilot Nov 18, 2025
1970159
Initial plan
Copilot Nov 18, 2025
36ac0ed
Initial plan
Copilot Dec 2, 2025
ee6c11e
Initial plan
Copilot Dec 13, 2025
093bd5e
Initial plan
Copilot Mar 9, 2026
56cb5f1
Initial plan
Copilot Mar 9, 2026
f259bfe
Initial plan
Copilot Mar 9, 2026
e1392cb
Initial plan
Copilot Mar 10, 2026
f8f371f
Initial plan
Copilot Mar 9, 2026
8c44672
Initial plan
Copilot Mar 10, 2026
c7e92bf
Initial plan
Copilot Feb 21, 2026
15b8eaa
Initial plan
Copilot Feb 27, 2026
a3bc1be
Initial plan
Copilot Mar 10, 2026
aa27073
Initial plan
Copilot Mar 7, 2026
5b534a5
Initial plan
Copilot Mar 10, 2026
a6bbfb0
Initial plan
Copilot Mar 11, 2026
73750ce
Initial plan
Copilot Mar 9, 2026
a785003
Initial plan
Copilot Mar 9, 2026
f65c574
Initial plan
Copilot Mar 13, 2026
88a703f
Initial plan
Copilot Mar 13, 2026
97aad3d
Initial plan
Copilot Mar 13, 2026
7732bb7
Initial plan
Copilot Mar 13, 2026
986ee97
fix(expo/CreatePackItemForm): default quantity to 1 instead of 0
mikib0 Mar 13, 2026
cc775c7
Initial plan
Copilot Mar 13, 2026
80e2b82
Initial plan
Copilot Mar 16, 2026
0296b39
Initial plan
Copilot Mar 9, 2026
fab0b0a
Initial plan
Copilot Mar 13, 2026
649f245
Initial plan
Copilot Mar 13, 2026
cfeff1e
Initial plan
Copilot Mar 9, 2026
985bac8
Initial plan
Copilot Mar 9, 2026
aa2ee75
Initial plan
Copilot Apr 1, 2026
7de6a58
Initial plan
Copilot Mar 21, 2026
d455979
Initial plan
Copilot Mar 9, 2026
7014149
Initial plan
Copilot Mar 9, 2026
2567058
Initial plan
Copilot Mar 9, 2026
1b0f657
Initial plan
Copilot Mar 9, 2026
7d1bc13
Initial plan
Copilot Mar 9, 2026
5bf077f
Initial plan
Copilot Mar 9, 2026
03e1d84
Initial plan
Copilot Mar 9, 2026
7cee6f9
Initial plan
Copilot Mar 9, 2026
3938af1
Initial plan
Copilot Mar 9, 2026
bb4ce47
Initial plan
Copilot Mar 9, 2026
d344845
Initial plan
Copilot Aug 22, 2025
49eacf7
Initial plan
Copilot Feb 27, 2026
31d4de1
chore: reopen trigger (no-op commit to restore PR state)
andrew-bierman Apr 11, 2026
9512302
ci: trigger biome check
andrew-bierman Apr 11, 2026
8f7c7e1
ci: retrigger CI after biome fixes
andrew-bierman Apr 11, 2026
5b57c02
Initial plan
Copilot Mar 9, 2026
0610422
ci: trigger checks for dependabot merges
andrew-bierman Apr 13, 2026
53ecc0d
Initial plan
Copilot Apr 13, 2026
7251017
ci: retrigger checks after copilot merge-conflict fix
andrew-bierman Apr 14, 2026
c70bc56
ci: trigger CI on Copilot bot's expo-symbols type fixes
andrew-bierman Apr 14, 2026
1eab201
Initial plan
Copilot Apr 14, 2026
8dcc228
trigger: retrigger CI after node_modules clean install verified vites…
andrew-bierman Apr 15, 2026
aca97fc
Initial plan
Copilot Apr 14, 2026
2980aa0
trigger: retrigger CI after node_modules clean install verified vites…
andrew-bierman Apr 15, 2026
c98fd6c
ci: re-trigger checks after @types/react alignment fix
claude Apr 16, 2026
7bb6abb
Initial plan
Copilot Apr 14, 2026
3bcb8d7
Initial plan
Copilot Apr 16, 2026
4a78d26
ci: trigger CI run on updated branch
claude Apr 16, 2026
21d9e93
Initial plan
Copilot Sep 22, 2025
a16b922
Initial plan
Copilot Sep 22, 2025
9105ec9
ci: trigger workflows
andrew-bierman Apr 14, 2026
bbb46ae
ci: retrigger CI after suspected transient runner failure
claude Apr 26, 2026
894a0b6
ci: retrigger workflows
andrew-bierman Apr 29, 2026
97771d5
feat(web): web support MVP — platform shims, Playwright e2e, OTP fix
andrew-bierman May 1, 2026
486fbd6
chore(web): remove unnecessary web layout overrides
andrew-bierman May 1, 2026
495418e
fix(web-e2e): drive login via UI instead of direct API calls
andrew-bierman May 1, 2026
03ca1b0
fix: use __DEV__ instead of process.env.NODE_ENV in _layout.tsx
andrew-bierman May 1, 2026
7b01fae
ci(web-e2e): drop DB seed step — E2E user is a permanent dev account
andrew-bierman May 1, 2026
4a6a732
fix(web-e2e): navigate directly to /auth in globalSetup, increase tim…
andrew-bierman May 1, 2026
730bc94
debug(web-e2e): screenshot + console logs in globalSetup to diagnose …
andrew-bierman May 1, 2026
8f1ed4e
fix(web-e2e): navigate directly to /auth/(login) instead of clicking …
andrew-bierman May 1, 2026
a5e2677
debug(web-e2e): screenshot + testID dump to diagnose CI auth render
andrew-bierman May 1, 2026
c524b97
fix(web-e2e): fix env var validation crash in CI static export
andrew-bierman May 1, 2026
539a021
debug(web-e2e): capture post-login screenshot and network log for aut…
andrew-bierman May 1, 2026
2961860
fix(web-e2e): replace stale EXPO_PUBLIC_API_URL secret with dev/prod …
andrew-bierman May 1, 2026
c8bca67
fix(web-e2e): use E2E_EXPO_PUBLIC_API_URL secret (E2E_ prefix convent…
andrew-bierman May 1, 2026
0eb5eba
fix(web): constrain auth logo Image size on web
andrew-bierman May 1, 2026
74778a0
chore(e2e): remove debug instrumentation from globalSetup
andrew-bierman May 1, 2026
b8411bd
fix(web): add Platform.select style fallback to all Image components
andrew-bierman May 1, 2026
7709b36
fix(web): proper BackHandler stub + UI auth flow in globalSetup
andrew-bierman May 1, 2026
e48fe4f
fix(web): drop ts-ignore from BackHandler patch — not needed in .js file
andrew-bierman May 1, 2026
591efe0
refactor(web): convert polyfills.js → polyfills.ts
andrew-bierman May 1, 2026
02e615f
suppress RNW dev-mode text node false positives in polyfills
andrew-bierman May 1, 2026
90a8c7e
fix(e2e): add testIDs and fix all failing web E2E tests
andrew-bierman May 1, 2026
c7a7bb7
fix(e2e): fix add-from-catalog, item delete, and web alert
andrew-bierman May 1, 2026
3e2e7cd
fix(e2e): resolve remaining 9 failing web E2E tests
andrew-bierman May 1, 2026
19a24b7
fix(e2e): update packs:name-input → pack-name-input after testIds merge
andrew-bierman May 1, 2026
713a276
fix(types): add missing TestIds import to CatalogItemDetailScreen and…
andrew-bierman May 1, 2026
a411610
fix(types): remove searchBar testID prop not in published nativewindu…
andrew-bierman May 1, 2026
7f2968a
fix(types): restore catalog:search-btn testID via safe-cast pending n…
andrew-bierman May 1, 2026
e63d539
fix(e2e): fix waitForResponse timing, add missing test timeouts, patc…
andrew-bierman May 1, 2026
7d4cfc2
fix: remove location guard blocking trip sync, fix test selectors and…
andrew-bierman May 1, 2026
01e3410
fix(e2e): fix Alert imperative API and Dates section selector for web…
andrew-bierman May 1, 2026
5dbe945
chore: remove Alert patch (fix moves to nativewindui PR #14)
andrew-bierman May 1, 2026
93720ad
fix(web-e2e): use Alert.alert (window.confirm on web) for item and tr…
andrew-bierman May 1, 2026
477af3d
chore: upgrade @packrat-ai/nativewindui to 2.0.6, remove 2.0.5 patch
andrew-bierman May 1, 2026
aec4099
fix(web-e2e): use window.confirm on web for item and trip delete
andrew-bierman May 2, 2026
07985a9
fix(web-e2e): register dialog handler before trip delete button click
andrew-bierman May 2, 2026
2835da4
fix(trips): add delete handler to syncedCrud and fix delete test
andrew-bierman May 2, 2026
cd82252
fix(trips): soft-delete via PUT with deleted:true, intercept PUT in test
andrew-bierman May 2, 2026
4a12476
fix(web-e2e): await PUT before page.goto for delete trip assertion
andrew-bierman May 2, 2026
8485dd2
fix(web-e2e): scope delete PUT filter to specific tripId, check respo…
andrew-bierman May 2, 2026
1769e18
fix(trips): hard-delete via DELETE endpoint in useDeleteTrip, interce…
andrew-bierman May 2, 2026
ec003eb
fix(web-e2e): navigate directly to trip detail before delete to avoid…
andrew-bierman May 2, 2026
a8415c5
fix(trips): sort list newest-first and use replace when no back history
andrew-bierman May 2, 2026
1d0d499
fix(trips): add safe-cast annotation for expo-router Href
andrew-bierman May 2, 2026
4e5b790
fix(web-e2e): gate email-input wrapper testID to non-web to fix Playw…
andrew-bierman May 7, 2026
f4d2d93
fix(web-e2e): restore pack-name-input/pack-description-input testIDs
andrew-bierman May 7, 2026
015d05e
fix(web-e2e): make catalog search assertion resilient to live data
andrew-bierman May 7, 2026
4f0ebca
fix(biome): remove unused imports and duplicate testID prop
andrew-bierman May 12, 2026
7fede0a
fix(biome): organize imports in useAuthActions
andrew-bierman May 13, 2026
e489a31
fix(web-e2e): use waitForResponse + explicit goto to fix globalSetup …
andrew-bierman May 13, 2026
474f7ae
fix(web-e2e): submit login via Enter on password field, not button click
andrew-bierman May 13, 2026
ff337ff
fix(web-e2e): target input element directly and use page.keyboard for…
andrew-bierman May 13, 2026
1e821c1
fix(e2e): remove locator('input') wrapper and guard web button submit
andrew-bierman May 13, 2026
083b540
fix(e2e): use native value-setter to update controlled form fields
andrew-bierman May 13, 2026
edf4cdb
fix(e2e): debug form state and fix aria-disabled check for RNW Pressable
andrew-bierman May 13, 2026
24285c8
fix(e2e): catch ANY POST and log all network requests + page errors
andrew-bierman May 13, 2026
72dd0b2
fix(auth+e2e): web localStorage fallback for expo-secure-store + fill…
andrew-bierman May 13, 2026
41c3b02
fix(e2e): add React-props diagnostic + locator.press('Enter') for rel…
andrew-bierman May 13, 2026
559a002
fix(e2e): disable CORS in test browser to fix cross-origin API calls …
andrew-bierman May 13, 2026
248e470
fix(e2e): strip Origin header via context.route() to bypass Better Au…
andrew-bierman May 13, 2026
f7e0a79
fix(e2e): guard route.fetch against context-disposed error on browser…
andrew-bierman May 13, 2026
fe92de3
fix(patch): apply testID to LargeTitleHeader search button on web
andrew-bierman May 13, 2026
8f46a1f
fix(patch): add testID to LargeTitleHeader searchBar type definition
andrew-bierman May 13, 2026
c38f308
fix(e2e): wait for trips list GET before asserting deletion in trips …
andrew-bierman May 13, 2026
9432539
fix(e2e): navigate to trip detail via SPA click to avoid bfcache
andrew-bierman May 13, 2026
4bbc4e6
fix(maestro): tap trips:start/end-date-btn to open the native date pi…
andrew-bierman May 14, 2026
6fcdc46
fix(auth): clear isLoadingAtom when auth screen mounts after sign-out
andrew-bierman May 14, 2026
1301319
fix(maestro): update iOS Pack Name field selector to use folder icon
andrew-bierman May 14, 2026
1127196
fix(maestro): use containerTestID for iOS pack name field instead of …
andrew-bierman May 14, 2026
4c82684
ci(e2e): drop bun.lock from CocoaPods cache key
andrew-bierman May 14, 2026
0495b4a
fix(maestro): wait for keyboard animation before typing pack name on iOS
andrew-bierman May 14, 2026
0fa1a22
fix(packs): sort usePacks newest-first so E2E can find newly created …
andrew-bierman May 14, 2026
9214031
ci: trigger CF Pages rebuild for packrat-guides
andrew-bierman May 14, 2026
f532ce2
fix(checks): exclude guides/lib/content.ts from cast scan
andrew-bierman May 14, 2026
8ef6a38
fix(upload): guard against null userId and malformed data URLs
andrew-bierman May 14, 2026
6590daf
fix(upload): narrow arr[1] to satisfy noUncheckedIndexedAccess
andrew-bierman May 14, 2026
aac0449
fix(auth): remove unused onFocus from OTPField
andrew-bierman May 14, 2026
48ac31a
Merge branch 'development' into feat/web-support-mvp
andrew-bierman May 16, 2026
fc441aa
Merge branch 'development' into feat/web-support-mvp (after #2363 lan…
andrew-bierman May 17, 2026
19aeb09
chore(ci): retrigger CF Pages after transient build failure
andrew-bierman May 17, 2026
35eab22
Merge branch 'development' into feat/web-support-mvp (after #2423/#23…
andrew-bierman May 17, 2026
984e2f8
chore: pin Bun version via .bun-version
andrew-bierman May 17, 2026
5c45f40
Merge branch 'development' into feat/web-support-mvp
andrew-bierman May 20, 2026
9e85a92
fix(test): align timingSafeEqual mock with object-arg signature
andrew-bierman May 30, 2026
58a6ee0
Merge remote-tracking branch 'origin/development' into feat/web-suppo…
andrew-bierman May 30, 2026
e549bd2
fix(web): sync NativeWind dark mode class to <html> in web layout
andrew-bierman May 31, 2026
e85008e
fix(e2e): remove placeholder DB URL fallback, fail loud if unset
andrew-bierman May 31, 2026
5bb9216
Merge remote-tracking branch 'origin/development' into feat/web-suppo…
andrew-bierman May 31, 2026
d08bc9d
🐛 fix(web-e2e): make Better Auth work cross-origin + repair e2e auth …
andrew-bierman Jun 1, 2026
16797be
🐛 fix(web): authenticate api + auth clients on web via cookie + lib/s…
andrew-bierman Jun 1, 2026
397011f
🔧 chore(api): local-only Hyperdrive env for e2e DB + short-lived pg c…
andrew-bierman Jun 1, 2026
e7ad043
🔧 fix(api): use local Neon HTTP proxy for e2e DB instead of raw pg/Hy…
andrew-bierman Jun 1, 2026
1589f1c
📝 docs(solutions): capture web cross-origin auth + local neon-proxy D…
andrew-bierman Jun 1, 2026
f873e9f
Merge remote-tracking branch 'origin/development' into feat/web-suppo…
andrew-bierman Jun 1, 2026
f1509a0
🐛 fix(lint): exempt secureStore.web.ts from no-owned-max-params
andrew-bierman Jun 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/e2e-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ jobs:
path: |
~/Library/Caches/CocoaPods
~/.cocoapods
key: cocoapods-${{ runner.os }}-${{ hashFiles('apps/expo/package.json', 'apps/expo/app.config.ts', 'bun.lock') }}
key: cocoapods-${{ runner.os }}-${{ hashFiles('apps/expo/package.json', 'apps/expo/app.config.ts') }}
restore-keys: |
cocoapods-${{ runner.os }}-

Expand Down
6 changes: 4 additions & 2 deletions .maestro/flows/auth/logout-flow.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,5 +43,7 @@ appId: ${APP_ID}
- waitForAnimationToEnd

# Assert we are back on the auth screen
- assertVisible:
text: "Sign In"
- extendedWaitUntil:
visible:
text: "Sign In"
timeout: 15000
11 changes: 8 additions & 3 deletions .maestro/flows/packs/create-pack-flow.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,16 @@ appId: ${APP_ID}
- waitForAnimationToEnd

# Fill in Pack Name
# iOS: tap the container Pressable (pack-name-container) which is the
# accessibility leaf and focuses the inner TextInput on press.
# Android: match by placeholder text (no container wrapper needed).
- runFlow:
when:
platform: iOS
commands:
- tapOn:
text: "move, Pack Name"
id: "pack-name-container"
- waitForAnimationToEnd
- inputText: ${PACK_NAME}
- runFlow:
when:
Expand Down Expand Up @@ -70,11 +74,12 @@ appId: ${APP_ID}
# Also confirm we are on the packs list by checking for the create button
- assertVisible:
id: "create-pack-button"
- waitForAnimationToEnd
- scrollUntilVisible:
element:
id: "packs:list-item-${PACK_NAME}"
direction: DOWN
timeout: 60000
speed: 99
timeout: 30000
speed: 40
- assertVisible:
id: "packs:list-item-${PACK_NAME}"
4 changes: 2 additions & 2 deletions .maestro/flows/trips/create-trip-flow.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ appId: ${APP_ID}

# --- Start Date ---
- tapOn:
id: "trips:start-date-input"
id: "trips:start-date-btn"
- waitForAnimationToEnd
- runFlow:
when:
Expand Down Expand Up @@ -80,7 +80,7 @@ appId: ${APP_ID}

# --- End Date ---
- tapOn:
id: "trips:end-date-input"
id: "trips:end-date-btn"
- waitForAnimationToEnd
- runFlow:
when:
Expand Down
4 changes: 4 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -259,3 +259,7 @@ Defined in root `tsconfig.json`:
- **Next.js build failures**: `apps/guides` and `apps/landing` may fail without internet (fetches remote data)
- **Type errors after NativeWindUI update**: Check for renamed refs — v2.0.0 renamed `AlertRef` → `AlertMethods`, `LargeTitleSearchBarRef` → `LargeTitleSearchBarMethods`
- **Bun install hangs**: Normal — takes 120+ seconds. Never cancel mid-install.

## Documented Solutions

`docs/solutions/` — documented solutions to past problems (bugs, best practices, workflow patterns), organized by category with YAML frontmatter (`module`, `tags`, `problem_type`). Relevant when implementing or debugging in documented areas.
4 changes: 3 additions & 1 deletion apps/expo/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,10 @@ android
.env.*
!.env.example

# Playwright E2E — cached auth tokens (written by globalSetup, contain real credentials)
# Playwright E2E — cached auth state (written by globalSetup, contains real credentials)
playwright/.auth/
playwright/.auth-tokens.json
playwright/.auth-state.json
playwright/playwright-report/
playwright/test-results/
playwright-report/
Expand Down
2 changes: 1 addition & 1 deletion apps/expo/app/(app)/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { getTripDetailOptions } from 'expo-app/features/trips/utils/getTripDetai
import { useTranslation } from 'expo-app/lib/hooks/useTranslation';
import type { TranslationFunction } from 'expo-app/lib/i18n/types';
import { testIds } from 'expo-app/lib/testIds';
import 'expo-dev-client';
import 'expo-app/lib/devClient';
import { type Href, router, Stack, useRouter } from 'expo-router';
import { useAtomValue } from 'jotai';
import { useEffect, useRef } from 'react';
Expand Down
9 changes: 7 additions & 2 deletions apps/expo/app/(app)/recent-packs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { useRecentPacks } from 'expo-app/features/packs/hooks/useRecentPacks';
import { useColorScheme } from 'expo-app/lib/hooks/useColorScheme';
import { useTranslation } from 'expo-app/lib/hooks/useTranslation';
import { getRelativeTime } from 'expo-app/lib/utils/getRelativeTime';
import { Image, ScrollView, View } from 'react-native';
import { Image, Platform, ScrollView, View } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';

function RecentPackCard({ pack }: { pack: Pack }) {
Expand All @@ -15,7 +15,12 @@ function RecentPackCard({ pack }: { pack: Pack }) {
return (
<View className="mx-4 mb-3 overflow-hidden rounded-xl bg-card shadow-sm">
{pack.image && (
<Image source={{ uri: pack.image }} className="h-40 w-full bg-red-950" resizeMode="cover" />
<Image
source={{ uri: pack.image }}
className="h-40 w-full bg-red-950"
resizeMode="cover"
style={Platform.select({ web: { width: '100%', height: 160 } })}
/>
)}
<View className="p-4">
<View className="flex-row items-start justify-between">
Expand Down
9 changes: 8 additions & 1 deletion apps/expo/app/_layout.web.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { Alert, type AlertMethods } from '@packrat-ai/nativewindui';
import { useColorScheme, useInitialAndroidBarSync } from 'expo-app/lib/hooks/useColorScheme';
import { Providers } from 'expo-app/providers';
import { NAV_THEME } from 'expo-app/theme';
import { type RefObject, useRef } from 'react';
import { type RefObject, useEffect, useRef } from 'react';

/**
* Web version of the root layout.
Expand All @@ -30,6 +30,13 @@ function RootLayout() {

const { colorScheme, isDarkColorScheme } = useColorScheme();

// Sync NativeWind dark mode class to <html> on web (darkMode: 'class' requires it)
useEffect(() => {
if (typeof document !== 'undefined') {
document.documentElement.classList.toggle('dark', isDarkColorScheme);
}
}, [isDarkColorScheme]);

return (
<Providers>
<StatusBar
Expand Down
5 changes: 3 additions & 2 deletions apps/expo/app/auth/(login)/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ export default function LoginScreen() {
source={LOGO_SOURCE}
className="ios:h-12 ios:w-12 web:h-8 web:w-8 h-8 w-8 rounded-md"
resizeMode="contain"
style={Platform.select({ web: { width: 32, height: 32 } })}
/>
<Text variant="title1" className="ios:font-bold pb-1 pt-4 text-center">
{Platform.select({
Expand All @@ -113,7 +114,7 @@ export default function LoginScreen() {
<Form className="gap-2">
<FormSection className="ios:bg-background">
<FormItem>
<View testID={testIds.auth.emailInput}>
<View testID={Platform.OS === 'web' ? undefined : testIds.auth.emailInput}>
<form.Field name="email">
{(field) => (
<TextField
Expand Down Expand Up @@ -233,7 +234,7 @@ export default function LoginScreen() {
accessible={true}
disabled={!canSubmit || loading}
onPress={() => {
if (focusedTextField === 'email') {
if (Platform.OS !== 'web' && focusedTextField === 'email') {
KeyboardController.setFocusTo('next');
return;
}
Expand Down
16 changes: 15 additions & 1 deletion apps/expo/app/auth/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ import type { AlertMethods } from '@packrat/ui/nativewindui';
import { ActivityIndicator, AlertAnchor, Button, Text } from '@packrat/ui/nativewindui';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { featureFlags } from 'expo-app/config';
import { needsReauthAtom, redirectToAtom } from 'expo-app/features/auth/atoms/authAtoms';
import {
isLoadingAtom,
needsReauthAtom,
redirectToAtom,
} from 'expo-app/features/auth/atoms/authAtoms';
import { useAuth } from 'expo-app/features/auth/hooks/useAuth';
import { useTranslation } from 'expo-app/lib/hooks/useTranslation';
import { testIds } from 'expo-app/lib/testIds';
Expand Down Expand Up @@ -41,11 +45,20 @@ export default function AuthIndexScreen() {
};

const setRedirectTo = useSetAtom(redirectToAtom);
const setIsLoadingGlobal = useSetAtom(isLoadingAtom);

React.useEffect(() => {
setRedirectTo(redirectTo as string);
}, [redirectTo, setRedirectTo]);

// After a sign-out via "Sign-in again", isLoadingAtom is left true by the
// profile screen so AppLayout can detect the unauthenticated state and
// navigate here. Once this screen mounts, loading is done — clear the atom
// so the Sign In button renders instead of the spinner.
React.useEffect(() => {
setIsLoadingGlobal(false);
}, [setIsLoadingGlobal]);

if (isLoading) {
return (
<SafeAreaView style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
Expand All @@ -62,6 +75,7 @@ export default function AuthIndexScreen() {
<Image
source={LOGO_SOURCE}
className="ios:h-12 ios:w-12 web:h-8 web:w-8 h-8 w-8 rounded-md"
style={Platform.select({ web: { width: 32, height: 32 } })}
resizeMode="contain"
/>
</View>
Expand Down
7 changes: 6 additions & 1 deletion apps/expo/app/auth/one-time-password.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,12 @@ export default function OneTimePasswordScreen() {
>
<View className="flex-1 justify-center gap-3">
<View className="items-center pb-1">
<Image source={LOGO_SOURCE} className="h-10 w-10 rounded-md" resizeMode="contain" />
<Image
source={LOGO_SOURCE}
className="h-10 w-10 rounded-md"
resizeMode="contain"
style={Platform.select({ web: { width: 40, height: 40 } })}
/>
</View>
<View className="gap-1">
<Text variant="title1" className="text-center font-semibold">
Expand Down
2 changes: 1 addition & 1 deletion apps/expo/atoms/atomWithSecureStorage.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { isFunction } from '@packrat/guards';
import * as SecureStore from 'expo-secure-store';
import * as SecureStore from 'expo-app/lib/secureStore';
import { atom } from 'jotai';

export const atomWithSecureStorage = <T>({
Expand Down
9 changes: 7 additions & 2 deletions apps/expo/components/initial/UserAvatar.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { MockUser } from 'expo-app/data/mockData';
import { Image, Text, View } from 'react-native';
import { Image, Platform, Text, View } from 'react-native';

type UserAvatarProps = {
user: Pick<MockUser, 'name' | 'avatarUrl'>;
Expand All @@ -26,7 +26,12 @@ export function UserAvatar({ user, size = 'md', showName = false }: UserAvatarPr
<View className="flex-row items-center">
<View className={`${sizeClass} overflow-hidden rounded-full bg-gray-200`}>
{avatarUri ? (
<Image source={{ uri: avatarUri }} className="h-full w-full" resizeMode="cover" />
<Image
source={{ uri: avatarUri }}
className="h-full w-full"
resizeMode="cover"
style={Platform.select({ web: { width: '100%', height: '100%' } })}
/>
) : (
<View className="h-full w-full items-center justify-center bg-blue-500">
<Text className="font-bold text-white">{user.name.substring(0, 2).toUpperCase()}</Text>
Expand Down
4 changes: 2 additions & 2 deletions apps/expo/features/auth/hooks/useAuthActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ import * as Sentry from '@sentry/react-native';
import { AuthClientError, toAuthError } from 'expo-app/features/auth/lib/authErrors';
import { userStore } from 'expo-app/features/auth/store';
import type { User } from 'expo-app/features/profile/types';
import * as AppleAuthentication from 'expo-app/lib/appleAuthentication';
import { authClient } from 'expo-app/lib/auth-client';
import { t } from 'expo-app/lib/i18n';
import * as Updates from 'expo-app/lib/updates';
import ImageCacheManager from 'expo-app/lib/utils/ImageCacheManager';
import { queryClient } from 'expo-app/providers/TanstackProvider';
import * as AppleAuthentication from 'expo-apple-authentication';
import { type Href, router } from 'expo-router';
import Storage from 'expo-sqlite/kv-store';
import * as Updates from 'expo-updates';
import { useAtomValue, useSetAtom } from 'jotai';
import {
isLoadingAtom,
Expand Down
14 changes: 12 additions & 2 deletions apps/expo/features/auth/hooks/useAuthInit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,16 @@ export function useAuthInit() {
}, []);

useEffect(() => {
const navigate = (target: Parameters<typeof router.replace>[0]) => {
// On web, expo-router's navigationRef may not be ready yet during
// Strict Mode double-mount — defer by one event loop tick.
if (Platform.OS === 'web') {
setTimeout(() => router.replace(target), 0);
} else {
router.replace(target);
}
};

const initializeAuth = async () => {
await runVersionGateMigration();

Expand Down Expand Up @@ -140,14 +150,14 @@ export function useAuthInit() {
return;
}

router.replace({
navigate({
pathname: '/auth',
params: { showSkipLoginBtn: 'true', redirectTo: '/' },
});
} catch (error) {
Sentry.captureException(error, { tags: { auth_action: 'init' } });
console.error('Failed to initialize auth:', error);
router.replace('/auth');
navigate('/auth');
} finally {
setIsLoading(false);
}
Expand Down
8 changes: 6 additions & 2 deletions apps/expo/features/catalog/components/ItemReviews.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Icon } from 'expo-app/components/Icon';
import { useColorScheme } from 'expo-app/lib/hooks/useColorScheme';
import { useTranslation } from 'expo-app/lib/hooks/useTranslation';
import { useState } from 'react';
import { Image, TouchableOpacity, View } from 'react-native';
import { Image, Platform, TouchableOpacity, View } from 'react-native';
import type { CatalogItem } from '../types';

type ItemReviewsProps = {
Expand Down Expand Up @@ -54,7 +54,11 @@ export function ItemReviews({ reviews }: ItemReviewsProps) {
<View className="flex-row items-center justify-between">
<View className="flex-row items-center">
{review.user_avatar ? (
<Image source={{ uri: review.user_avatar }} className="h-8 w-8 rounded-full" />
<Image
source={{ uri: review.user_avatar }}
className="h-8 w-8 rounded-full"
style={Platform.select({ web: { width: 32, height: 32 } })}
/>
) : (
<View className="h-8 w-8 items-center justify-center rounded-full bg-muted">
<Icon name="person-outline" size={16} color="text-muted-foreground" />
Expand Down
Loading
Loading