chore(mobile): phase 1 quality-review fixups
Three small follow-ups from the post-Phase-1 quality review:
* authContext.tsx — drop the dead `userId` extraction + the `void
userId` suppressor that masked an unused-locals warning. The session
is already implicitly threaded through the supabase client, so no
consumer of ensureDevice needed the value.
* authContext.tsx — switch the device-name string concat to a
template literal for consistency with the rest of the codebase.
* ErrorBoundary.tsx — replace the four inline hex literals with their
`theme/colors.ts` constants. The boundary was authored in Phase 0
before the theme module existed; this brings it in line with every
Phase 1 screen.
* apps/mobile/README.md — drop the stale Phase-0 paragraph about the
`lib/sharedSmoke.ts` canary (deleted in Phase 1) and add a short
pointer to the env-var setup.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+17
-10
@@ -55,16 +55,23 @@ pnpm mobile:typecheck # tsc --noEmit
|
|||||||
## Architecture notes
|
## Architecture notes
|
||||||
|
|
||||||
- Expo Router (file-based) — screens live under `app/`.
|
- Expo Router (file-based) — screens live under `app/`.
|
||||||
- `expo-secure-store` — Keychain / Keystore-backed secret store (used by
|
- `expo-secure-store` — Keychain / Keystore-backed secret store, wrapped
|
||||||
the mobile `SecretStore` adapter in Phase 1).
|
by `lib/secretStore.ts` to implement `@chat-app/shared`'s `SecretStore`.
|
||||||
- `expo-sqlite` — local encrypted history (Phase 1).
|
- `@react-native-async-storage/async-storage` — Supabase session-token
|
||||||
- `react-native-libsodium` — crypto primitives (Phase 1's
|
storage, surfaced via `lib/sessionStorage.ts`.
|
||||||
`CryptoBackend` adapter wraps this).
|
- `expo-sqlite` — local encrypted history (slated for a later phase).
|
||||||
- `@chat-app/shared` — business logic shared with the desktop, including
|
- `react-native-libsodium` — crypto primitives, wrapped by
|
||||||
the `CryptoBackend` and `SecretStore` interfaces the mobile adapters
|
`lib/cryptoBackend.ts` to implement `@chat-app/shared`'s `CryptoBackend`.
|
||||||
plug into.
|
Registered once at boot in `app/_layout.tsx`.
|
||||||
- `lib/sharedSmoke.ts` exists only to prove Metro can resolve the
|
- `@chat-app/shared` — business logic shared with the desktop; the mobile
|
||||||
workspace package; it's deleted in Phase 1.
|
adapters plug into its `CryptoBackend` + `SecretStore` interfaces, and
|
||||||
|
every chat / auth call goes through the namespace exports.
|
||||||
|
|
||||||
|
## Env vars
|
||||||
|
|
||||||
|
Copy `.env.example` to `.env.local` and fill in the same Supabase host +
|
||||||
|
anon key the desktop uses. Expo bundles only `EXPO_PUBLIC_*`-prefixed
|
||||||
|
vars into the JS, which is what the three required values use.
|
||||||
|
|
||||||
## Roadmap
|
## Roadmap
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Pressable, StyleSheet, Text, View } from 'react-native';
|
import { Pressable, StyleSheet, Text, View } from 'react-native';
|
||||||
|
|
||||||
|
import { colors } from '../theme/colors';
|
||||||
|
|
||||||
// Catch render errors anywhere below this boundary and show a readable
|
// Catch render errors anywhere below this boundary and show a readable
|
||||||
// fallback. Without it, a thrown error during render produces a white
|
// fallback. Without it, a thrown error during render produces a white
|
||||||
// screen on TestFlight / production builds with no way for the user to
|
// screen on TestFlight / production builds with no way for the user to
|
||||||
@@ -53,16 +55,16 @@ const styles = StyleSheet.create({
|
|||||||
flex: 1,
|
flex: 1,
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
backgroundColor: '#0b0b0f',
|
backgroundColor: colors.bg,
|
||||||
padding: 24,
|
padding: 24,
|
||||||
},
|
},
|
||||||
title: { color: '#fff', fontSize: 22, fontWeight: '600', marginBottom: 8 },
|
title: { color: colors.text, fontSize: 22, fontWeight: '600', marginBottom: 8 },
|
||||||
message: { color: '#9ca3af', textAlign: 'center', marginBottom: 24 },
|
message: { color: colors.textMuted, textAlign: 'center', marginBottom: 24 },
|
||||||
button: {
|
button: {
|
||||||
backgroundColor: '#5865f2',
|
backgroundColor: colors.accent,
|
||||||
paddingHorizontal: 20,
|
paddingHorizontal: 20,
|
||||||
paddingVertical: 12,
|
paddingVertical: 12,
|
||||||
borderRadius: 10,
|
borderRadius: 10,
|
||||||
},
|
},
|
||||||
buttonText: { color: '#fff', fontWeight: '600' },
|
buttonText: { color: colors.text, fontWeight: '600' },
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -37,8 +37,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|||||||
|
|
||||||
// Resolve or create the device record for this install given an active
|
// Resolve or create the device record for this install given an active
|
||||||
// session. Stores the private key in expo-secure-store on first run.
|
// session. Stores the private key in expo-secure-store on first run.
|
||||||
const ensureDevice = useCallback(async (currentSession: Session): Promise<void> => {
|
const ensureDevice = useCallback(async (_currentSession: Session): Promise<void> => {
|
||||||
const userId = currentSession.user.id;
|
|
||||||
const savedDeviceId = await secretStore.getSecret(KEY_DEVICE_ID);
|
const savedDeviceId = await secretStore.getSecret(KEY_DEVICE_ID);
|
||||||
const savedPrivKey = await secretStore.getSecret(KEY_DEVICE_PRIVKEY);
|
const savedPrivKey = await secretStore.getSecret(KEY_DEVICE_PRIVKEY);
|
||||||
|
|
||||||
@@ -59,7 +58,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|||||||
const kp = backend.generateKeyPair();
|
const kp = backend.generateKeyPair();
|
||||||
const platform = Platform.OS === 'ios' ? 'ios' : Platform.OS === 'android' ? 'android' : 'linux';
|
const platform = Platform.OS === 'ios' ? 'ios' : Platform.OS === 'android' ? 'android' : 'linux';
|
||||||
const record = await auth.registerDevice(supabase, {
|
const record = await auth.registerDevice(supabase, {
|
||||||
name: 'Netralax Mobile (' + Platform.OS + ')',
|
name: `Netralax Mobile (${Platform.OS})`,
|
||||||
platform,
|
platform,
|
||||||
publicKey: kp.publicKey,
|
publicKey: kp.publicKey,
|
||||||
});
|
});
|
||||||
@@ -67,7 +66,6 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|||||||
await secretStore.setSecret(KEY_DEVICE_PRIVKEY, kp.privateKey);
|
await secretStore.setSecret(KEY_DEVICE_PRIVKEY, kp.privateKey);
|
||||||
setDevice(record);
|
setDevice(record);
|
||||||
setOwnPrivateKey(kp.privateKey);
|
setOwnPrivateKey(kp.privateKey);
|
||||||
void userId;
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user