Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
168d020
docs(mobile-ui): core-loop UI buildout design spec
Quantumlyy May 17, 2026
e14b810
feat(mobile-ui): vendor @constructor/protocol @ a7b968f (pure Hermes-…
Quantumlyy May 17, 2026
2039ce1
feat(mobile-ui): Phase 0 foundation — ui wrapper, mock gateway seam, …
Quantumlyy May 17, 2026
4e6b432
feat(mobile-ui): Phase 1 screens — list, create, detail/stream, sign-…
Quantumlyy May 17, 2026
bbebeae
feat(mobile-ui): persist profiles (kv-store), EAS Update, Router v55 …
Quantumlyy May 17, 2026
c9d79d6
fix(mobile-ui): drop redundant native Stack header (double title bar …
Quantumlyy May 17, 2026
8ebc0d4
chore(repo): pin pnpm via packageManager so EAS Build resolves the wo…
Quantumlyy May 17, 2026
0a51119
docs(mobile-ui): exact iOS device EAS build steps + owner/projectId c…
Quantumlyy May 17, 2026
514fd41
feat(mobile-ui): TestFlight-ready (bundle id, store profile) + TestFl…
Quantumlyy May 17, 2026
28a8d7b
chore(mobile-ui): personal EAS owner (drop refrakts project), gitigno…
Quantumlyy May 17, 2026
6209c8c
fix(mobile-ui): runtimeVersion appVersion (fingerprint failed local-v…
Quantumlyy May 17, 2026
5450685
chore(mobile-ui): link personal EAS project
Quantumlyy May 17, 2026
599376e
chore(repo): harden root .gitignore with repo-wide secret/credential …
Quantumlyy May 17, 2026
8b7c424
feat(mobile-ui): Phase A — native UIKit headers (AppBar bridges to St…
Quantumlyy May 17, 2026
f6349f7
fix(mobile-ui): drop fragile headerLargeTitle + de-nest list SafeArea…
Quantumlyy May 17, 2026
0c9b675
feat(mobile-ui): integrate EAS Insights (expo-insights; cold-start an…
Quantumlyy May 17, 2026
8b76c00
docs(gateway): Terraform (Cloudflare IaC) design spec
Quantumlyy May 17, 2026
17ec310
feat(gateway): Terraform IaC — CF worker+KV, R2 state backend, secret…
Quantumlyy May 17, 2026
f6b93d2
chore: scope this PR to mobile app + protocol (gateway-terraform is a…
Quantumlyy May 17, 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
43 changes: 43 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,46 @@ dist/
.wrangler/
*.log
.DS_Store

# upstream protocol clone (vendor source; never committed)
.upstream/

# local Claude Code permission overrides (not committed)
.claude/settings.local.json

# --- secrets & credentials (repo-wide; never commit) ---
.env
.env.*
!.env.example
.dev.vars
.dev.vars.*
!.dev.vars.example
*.pem
*.key
*.p8
*.p12
*.pfx
*.cer
*.certSigningRequest
*.mobileprovision
*.keystore
*.jks
credentials.json
secrets.json
*.secret
google-services.json
GoogleService-Info.plist

# --- terraform (state + tfvars hold secrets; never commit) ---
*.tfvars
*.tfvars.json
!*.tfvars.example
*.tfstate
*.tfstate.*
.terraform/
crash.log
crash.*.log
override.tf
override.tf.json
*_override.tf
*_override.tf.json
4 changes: 3 additions & 1 deletion apps/mobile/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,16 @@ dist/
web-build/
expo-env.d.ts

# Native
# Native / EAS credentials (EAS-managed creds live on Expo servers, not here;
# these patterns are belt-and-suspenders for local-credential or exported keys)
.kotlin/
*.orig.*
*.jks
*.p8
*.p12
*.key
*.mobileprovision
credentials.json

# Metro
.metro-health-check*
Expand Down
19 changes: 15 additions & 4 deletions apps/mobile/app.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
{
"expo": {
"name": "mobile",
"name": "Constructor",
"slug": "mobile",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/images/icon.png",
"scheme": "mobile",
"userInterfaceStyle": "automatic",
"runtimeVersion": {
"policy": "appVersion"
},
"ios": {
"icon": "./assets/expo.icon"
"icon": "./assets/expo.icon",
"bundleIdentifier": "dev.nejc.constructor",
"infoPlist": {
"ITSAppUsesNonExemptEncryption": false
}
},
"android": {
"adaptiveIcon": {
Expand Down Expand Up @@ -36,7 +43,8 @@
}
],
"expo-secure-store",
"expo-web-browser"
"expo-web-browser",
"expo-sqlite"
],
"experiments": {
"typedRoutes": true,
Expand All @@ -48,6 +56,9 @@
"projectId": "00d6ac0f-2366-4d7e-843c-20cf79f4ea7d"
}
},
"owner": "refrakts"
"owner": "refrakts",
"updates": {
"url": "https://u.expo.dev/00d6ac0f-2366-4d7e-843c-20cf79f4ea7d"
}
}
}
26 changes: 26 additions & 0 deletions apps/mobile/eas.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"cli": {
"version": ">= 5.0.0",
"appVersionSource": "remote"
},
"build": {
"development": {
"developmentClient": true,
"distribution": "internal",
"channel": "development",
"ios": { "simulator": false }
},
"preview": {
"distribution": "internal",
"channel": "preview"
},
"production": {
"distribution": "store",
"channel": "production",
"autoIncrement": true
}
},
"submit": {
"production": {}
}
}
6 changes: 6 additions & 0 deletions apps/mobile/metro.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,10 @@ config.resolver.nodeModulesPaths = [
path.resolve(workspaceRoot, "node_modules"),
];
config.resolver.disableHierarchicalLookup = true;
// markdown-it (via react-native-markdown-display) requires Node's "punycode";
// alias it to the userland package so Metro can resolve it in RN/Hermes.
config.resolver.extraNodeModules = {
...config.resolver.extraNodeModules,
punycode: require.resolve("punycode/"),
};
module.exports = config;
5 changes: 5 additions & 0 deletions apps/mobile/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"lint": "expo lint"
},
"dependencies": {
"@constructor/protocol": "workspace:*",
"@expo/ui": "~55.0.16",
"@react-navigation/bottom-tabs": "^7.15.5",
"@react-navigation/elements": "^2.9.10",
Expand All @@ -25,15 +26,19 @@
"expo-font": "~55.0.7",
"expo-glass-effect": "~55.0.11",
"expo-image": "~55.0.10",
"expo-insights": "~55.0.17",
"expo-linking": "~55.0.15",
"expo-notifications": "~55.0.23",
"expo-router": "~55.0.14",
"expo-secure-store": "~55.0.14",
"expo-splash-screen": "~55.0.21",
"expo-sqlite": "~55.0.16",
"expo-status-bar": "~55.0.6",
"expo-symbols": "~55.0.8",
"expo-system-ui": "~55.0.18",
"expo-updates": "~55.0.22",
"expo-web-browser": "~55.0.16",
"punycode": "^2.3.1",
"react": "19.2.0",
"react-dom": "19.2.0",
"react-native": "0.83.6",
Expand Down
62 changes: 51 additions & 11 deletions apps/mobile/src/app/_layout.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,56 @@
import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native';
import React from 'react';
import { useColorScheme } from 'react-native';
import { Stack } from 'expo-router';

import { AnimatedSplashOverlay } from '@/components/animated-icon';
import AppTabs from '@/components/app-tabs';
import { useAuth } from '@/data/auth';
import { AppProviders } from '@/data/provider';
import { useThemeColors } from '@/ui';

export default function TabLayout() {
const colorScheme = useColorScheme();
/** Fallback route Expo Router resolves to when a guard redirects. */
export const unstable_settings = { anchor: 'index' };

/** Native iOS bottom-sheet presentation (Expo Router v55 / react-native-screens). */
const SHEET = {
presentation: 'formSheet' as const,
sheetGrabberVisible: true,
sheetCornerRadius: 20,
sheetAllowedDetents: [0.6, 1] as number[],
sheetInitialDetentIndex: 0,
};

function StackNav() {
const c = useThemeColors();
const { signedIn } = useAuth();
return (
<Stack
screenOptions={{
// Native UIKit header (react-native-screens): large titles, blur,
// native back-swipe. `AppBar` bridges title/actions into it.
headerShown: true,
headerStyle: { backgroundColor: c.background },
headerTintColor: c.text,
headerShadowVisible: false,
contentStyle: { backgroundColor: c.background },
}}
>
<Stack.Protected guard={signedIn}>
<Stack.Screen name="index" options={{ title: 'Sessions' }} />
<Stack.Screen name="new" options={{ title: 'New session', ...SHEET }} />
<Stack.Screen name="s/[id]" options={{ title: 'Session' }} />
<Stack.Screen name="settings" options={{ title: 'Settings' }} />
</Stack.Protected>
<Stack.Protected guard={!signedIn}>
<Stack.Screen
name="sign-in"
options={{ title: 'Sign in', headerShown: false, ...SHEET }}
/>
</Stack.Protected>
</Stack>
);
}

export default function RootLayout() {
return (
<ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
<AnimatedSplashOverlay />
<AppTabs />
</ThemeProvider>
<AppProviders>
<StackNav />
</AppProviders>
);
}
Loading