The row said '1.0.0 (dev)' on a build stamped 2026081502.
Constants.nativeBuildVersion is deprecated in SDK 57 and returns nothing — not an
error, not a throw, just undefined, so the fallback fired and the screen reported the
opposite of the truth with complete confidence. A deprecation that quietly returns
undefined is the worst kind: everything looks like it is working.
expo-application is the documented replacement and reads the value from the bundle at
runtime, which is the number OAS stamps before signing.
Also removes the last use of expo-constants here, and with it the '1.0.0' half — the
marketing string that has never changed and cannot tell two builds apart.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The row read '1.0.0 (2026081501)'. The first half has said 1.0.0 for every build ever
made here and conveys nothing — nobody maintains it, and it cannot distinguish two
builds. What identifies a build is the date-stamped number OAS assigns and writes into
CFBundleVersion before signing.
So the row now shows exactly that, dotted for reading: 2026.08.15.01. The same string
the install page shows, which is the point — 'is the thing on my phone the thing on the
site?' becomes a question you answer by looking rather than by remembering which link
you tapped.
A build that never went through OAS has no such number. Anything that is not exactly
ten digits is shown verbatim as 'dev (…)' rather than sliced into a fake date.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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>
Extracted from the officer-suite monorepo into its own public repo. The app is
open source and free; the rest of the suite is not, so it could not stay in a
tree that ships an account layer.
What moved:
- apps/offscale/* -> repo root (App shell, app.json, assets)
- packages/officer-net/ -> modules/officer-net/, a `file:` dependency
- packages/core VPN closure -> src/vpn/ (screen, hook, native bridge, invite)
- packages/core shared primitives -> src/ui/ (theme, cards, tabs, backdrop, splash)
- scripts/build-{ios,android}.sh -> scripts/, cut down to this one app
What deliberately did NOT move: the platform account layer. VpnScreen used to
render core's AppSettingsScreen, which drags in ServersSection -> servers.ts,
useAuth, api.ts, shared-session, install-guard, api-keys. None of that exists
here. src/settings/SettingsScreen.tsx is a fresh replacement rendering exactly
what VpnScreen passed it — the app's own sections plus About — with no Servers
tab, no Appearance control (the app is pinned to dark) and no sign-out.
The one platform reference left in the closure was a dead `import { request }
from './api'` in officer-net.ts, unused since enrollVpn() was removed. Gone. The
app now contacts nothing but the control server you point it at, its peers, and
the enrolment sidecar named inside an invite link you tapped.
Dependencies are pinned to the exact versions the monorepo has installed, not to
ranges: this repo starts from versions already proven to build, and an unpinned
install is a supply-chain decision nobody made. react-query, react-dom,
react-native-screens, expo-secure-store, track-player, expo-audio and
expo-file-system were all declared by the old app and imported by none of this
code, so they are not here.
NOTICE carries Tailscale's BSD-3 text, names the built artifacts that contain
their compiled code, and treats the trademark question separately — including
that src/ui/TailscaleMark.tsx reproduces their logo in the attribution strip.