The first launch ships two ways to play: Same Device and Online. The Local Network mode is finished, tested and still in the codebase — it is simply not offered to players, because on an all-iPhone group it cannot keep its promise. This page is the whole story, written so a session months from now can resume without archaeology.
false in the app (the website
is the app, so it follows). Flip it, rebuild the browser-guest copy, rebuild the native app — the
mode is back.Read alongside: architecture/lan-mode.html still describes how the mode works — it is accurate, and none of it changed. decisions.html (2026-08-11) carries the reasoning and what would reverse it.
Both were found by playing it, not by reading code. Neither is fixable in our app alone, and both hit the exact groups the mode was built for.
The host's hotspot has no internet. Every guest iPhone notices that, marks the wifi as "no internet", and quietly sends its traffic out over its own mobile data instead. The host's phone is on the wifi and nowhere else, so it becomes unreachable: a browser guest sees "no internet" and a blank page, having done nothing wrong.
Turning on airplane mode disables Personal Hotspot on iPhone, by design — iOS treats a hotspot as "sharing my cellular data", so a phone with no active data plan may not offer one at all, and no iOS API lets an app create a wifi network itself. There is no setting and no permission that changes this, and no app can override it. The "play on a plane" story — the single most quotable reason the mode exists — is impossible when the host is an iPhone.
One constant. Everything else is untouched code sitting behind it. It says
false today.
| Flag | File | What it turns off |
|---|---|---|
LAN_MODE_ENABLED | apps/app/src/modes/launchModes.ts | The hotspot door under the play doors on the App Home, the private-address redirect for browser guests, and the LAN routes (each answers with a redirect home) |
The constant is typed boolean, not the literal false, on purpose:
TypeScript therefore keeps both branches of every check alive, so nothing behind the flag
rots into unreachable code while it waits.
apps/app/src/app/(tabs)/index.tsx — hands the home's play doors
(src/home/crew/CrewDoors.tsx, through CrewPage) their hotspot entry only
while the flag is on, and gates the browser-guest bounce described below.apps/app/src/app/lan/index.tsx, lan/host.tsx,
lan/join.tsx, apps/app/src/app/lan-guest.tsx — each redirects home
while the flag is off, so an old bookmark or a stale link lands somewhere sensible./lan-guest. Guarding /lan-guest without guarding that bounce makes the
two redirect at each other forever. Both sides sit behind the same flag — keep it
that way.apps/app/src/modes/lan/, packages/match-host, the
modules/expo-lan-server native module and the whole engine are exactly as they
were. Every one of their tests still runs and still passes on every gate.
The local-network permission and the LAN module's config plugin stay in place. Removing either breaks the native build, and the permission prompt is only reachable by starting the server — which no screen can now do.
Nothing in convex/ changed. The online referee never knew about LAN mode in
the first place; the two modes share only the pure rulebook.
The site still serves the /lan screens (/lan, /lan/host, /lan/join are real pages in the web export),
where the app's own guard sends the visitor home. Any other /lan/… path gets the not-found page. Deliberate: a stale bookmark fails softly,
and there is one less thing to restore later.
true. The app and the website both read it.bun run export:lan-guest. Games
added during the pause do not exist for browser guests until this runs, and the freshness test
is what tells you. The copy frozen today also predates the twelve player colours: the host deals
all twelve, but its guests know only eight and wrap the rest round, so a ninth player shows up in
the first player's colour. This rebuild, and the native rebuild below that packs it, are
required before LAN returns, not optional. A guest export made straight after the repo's own
site export keeps its guest flag. The trap is the other way round: a plain web export made
after the guest export, with no options of its own, reuses the guest's compiled files and
believes it is a guest page, and nothing says so. No script here makes that plain export; one
made by hand needs an empty bundler cache (learnings,
2026-10-05).scripts/build-site.mjs). This comes before the mode
returns, not after. Why the lists exist: decisions, 2026-10-08.These are the decisions the pause is really waiting on. Answer them before writing code, not after.
"Turn your mobile data off to join" is a real instruction we could show every guest, on the screen where they get stuck. Does that read as helpful, or as the app admitting it does not work?
It rescues app-installed guests only. Worth it if enough of a group installs the app — which is precisely the assumption the browser-guest design was built to avoid.
Phone-to-phone radio would make the flight scenario real, for app-installed players only. It needs evidence that all-iPhone groups are asking for it — not a hunch.
"Play with no internet" was the headline. If it returns as "play on shared wifi, no internet needed", that is a smaller and more honest claim — and it works on every phone today.
true and the mode ships — the reasoning lives on in
decisions.html.