feat(settings): show which build this is

Two OffScale builds from different source trees were installed on the same phone and
were indistinguishable: both reported 1.0.0 (1), because nothing ever stamped a version.
The only way to answer 'did that actually install?' was to remember which URL it came
from, which is not available to the person holding the phone.

Reads nativeBuildVersion rather than the value in app.json. The config says what the
project asked for; the native plist says what shipped, and it is the second one that
answers the question. OAS stamps its YYYYMMDDNN into CFBundleVersion before signing, so
the row now shows the same number the install page does.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-14 23:24:02 +01:00
co-authored by Claude Opus 5
parent 859a716b5d
commit fe389c9752
3 changed files with 26 additions and 1 deletions
+6 -1
View File
@@ -9,6 +9,7 @@
"@react-native-async-storage/async-storage": "2.2.0",
"expo": "57.0.8",
"expo-clipboard": "57.0.1",
"expo-constants": "57.0.7",
"expo-dev-client": "57.0.9",
"expo-modules-core": "57.0.7",
"expo-status-bar": "57.0.1",
@@ -510,7 +511,7 @@
"expo-clipboard": ["expo-clipboard@57.0.1", "", { "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-HWICri4+1ao7S6QEfcorxVumXDiDnx1guGGewjZgGJWLGxFYs0RgH8ujBs+lkTzBkMmlwADaWSlaesR+nDJt5Q=="],
"expo-constants": ["expo-constants@57.0.11", "", { "dependencies": { "@expo/env": "~2.4.2" }, "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-gpSZ13Nfx0BOc9ALjG1SMhks5csb8Z6ncEVdhDgv0NRrmJcHPRvyUlWaKjRjzwW730XrQ6PIXFRcnNmAxOLj5A=="],
"expo-constants": ["expo-constants@57.0.7", "", { "dependencies": { "@expo/env": "~2.4.2" }, "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-ShDwaKnh3UieCQ/dG0kO8PuicTTatn3WDGmXbq/fukyzPXWhdUxf37DINVDQS6D3DDG7nfqItij+QvnDHSQhTg=="],
"expo-dev-client": ["expo-dev-client@57.0.9", "", { "dependencies": { "expo-dev-launcher": "~57.0.9", "expo-dev-menu": "~57.0.9", "expo-dev-menu-interface": "~57.0.0", "expo-manifests": "~57.0.1", "expo-updates-interface": "~57.0.1" }, "peerDependencies": { "expo": "*" } }, "sha512-DDqinc0EHMsqDUqkfad6c3pc5vr2oCxwAojSGVSBZe5paRIPgQTEWA+x76Qel13F1LO2LSG+DLs5W1vk3JwUow=="],
@@ -1106,6 +1107,10 @@
"css-tree/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
"expo/expo-constants": ["expo-constants@57.0.11", "", { "dependencies": { "@expo/env": "~2.4.2" }, "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-gpSZ13Nfx0BOc9ALjG1SMhks5csb8Z6ncEVdhDgv0NRrmJcHPRvyUlWaKjRjzwW730XrQ6PIXFRcnNmAxOLj5A=="],
"expo-asset/expo-constants": ["expo-constants@57.0.11", "", { "dependencies": { "@expo/env": "~2.4.2" }, "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-gpSZ13Nfx0BOc9ALjG1SMhks5csb8Z6ncEVdhDgv0NRrmJcHPRvyUlWaKjRjzwW730XrQ6PIXFRcnNmAxOLj5A=="],
"expo-modules-autolinking/commander": ["commander@7.2.0", "", {}, "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="],
"fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
+1
View File
@@ -19,6 +19,7 @@
"@react-native-async-storage/async-storage": "2.2.0",
"expo": "57.0.8",
"expo-clipboard": "57.0.1",
"expo-constants": "57.0.7",
"expo-dev-client": "57.0.9",
"expo-modules-core": "57.0.7",
"expo-status-bar": "57.0.1",
+19
View File
@@ -3,6 +3,7 @@ import type { ImageSourcePropType } from 'react-native';
import { View, Text, Pressable, ScrollView, Image } from 'react-native';
import { Ionicons } from '@expo/vector-icons';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import Constants from 'expo-constants';
import { useTheme } from '../ui/useTheme';
import { Card } from '../ui/Card';
@@ -150,6 +151,24 @@ export const SettingsScreen = ({
{children}
{/* About last, because it is the way out of the page. */}
<SettingsSection title="About">
{/*
Which build this is, and it earns its place.
Two OffScale builds from different source trees were installed on the same phone and
were indistinguishable — both said 1.0.0 (1), because nothing stamped the version, so
the answer to "did that actually install?" lived only in the URL it was downloaded
from. OAS now writes its YYYYMMDDNN into CFBundleVersion before signing, and this is
where you read it back.
nativeBuildVersion, not the value from app.json: the config is what the project asked
for, the native plist is what shipped, and it is the second one that answers the
question. Falls back so a Metro run does not show an empty row.
*/}
<SettingsRow label="Version">
<Text style={{ color: palette.textMuted, fontVariant: ['tabular-nums'] }}>
{`${Constants.expoConfig?.version ?? '1.0.0'} (${Constants.nativeBuildVersion ?? 'dev'})`}
</Text>
</SettingsRow>
{onShowCredits ? (
<SettingsRow label="Credits" icon="information-circle-outline" onPress={onShowCredits} />
) : null}