← All docs Games for Group · Come-back note

LAN mode is paused — how to bring it back

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.

🔌 Nothing was deleted. One constant is 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.

Why it is paused — two iPhone limits, not two bugs

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.

🚫 Limit 1 · The internet-less hotspot

Guest phones route around the host

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.

🚫 Limit 2 · Airplane mode

The flight scenario cannot exist on iPhone

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.

✅ When it does still work Limit 1 disappears if the guest turns their own mobile data off (a fix we would have to teach, with a help card we never built), or if the phone with the data plan is the one hosting the hotspot. Limit 2 disappears if someone in the group has an Android (Android hotspots keep working in airplane mode) or the plane sells wifi that does not isolate its clients from each other. Either way the phone making the wifi is just the router — any other phone, including an iPhone, can still be the one hosting the game. A shared home or café wifi with no internet outage was never the problem — the mode works there today.
🔧 The one repair that is ours to make A guest who has the app installed could be fixed in code: bind the connection to the wifi network interface explicitly, so the phone stops preferring mobile data. That is a real, bounded piece of work — it was never built. It does not help browser guests, who are most of the room, because a browser gives a page no say in which network it uses.
❌ What is not the answer Phone-to-phone radio (Multipeer Connectivity, Bluetooth) sidesteps both limits and was rejected: browsers cannot speak it. The mode's whole promise is that friends join with nothing installed, so a transport only the app can use would be a different product wearing the same name.

What "hidden" means, exactly

One constant. Everything else is untouched code sitting behind it. It says false today.

FlagFileWhat it turns off
LAN_MODE_ENABLEDapps/app/src/modes/launchModes.tsThe 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 website follows automatically The website is the App's own web export, so it reads the same constant. There is no second flag to keep in step.

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.

Screens that carry a guard

⚠️ The trap to remember (it nearly shipped) The home screen bounces a browser served from a private address straight to /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.

What is still true, and still tested

Untouched code

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.

Untouched native

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.

Untouched backend

Nothing in convex/ changed. The online referee never knew about LAN mode in the first place; the two modes share only the pure rulebook.

One stale link path

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.

Resuming — the checklist

  1. First, prove the diagnosis on real phones — before building anything. The five-minute test was never actually run: guest phone, mobile data off, joined to an internet-less hotspot, open the game page. It should load. Limit 1's mechanism is high-confidence but was never confirmed on our exact devices; every plan below leans on it.
  2. Flip the constant to true. The app and the website both read it.
  3. Rebuild the frozen browser-guest copy. 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).
  4. Take the answer lists out of the browser-guest copy. On a hotspot the host's phone is the referee, and for a game whose content is a secret (a quiz and its answers) it deals from the list the app ships with, as one shared phone does. On one phone that is safe: everybody looks at one screen. On a hotspot every guest holds a phone of their own, and the guest copy is the app, so it carries that same list: a guest could look an answer up in their own browser. A guest never deals, so the guest copy needs none of those lists. Build it without them, then search the packed copy for the lists' items the way the site build searches for the server's (scripts/build-site.mjs). This comes before the mode returns, not after. Why the lists exist: decisions, 2026-10-08.
  5. Rebuild the native apps (iOS re-pods, so the packed copy is snapshotted fresh) and verify the shipped artifact, not the repo: the local-network permission survived the archive, and the packed guest copy is the new one. A development tool has deleted that permission from a release build before — see the LAN page.
  6. Restore the copy the pause reworded. Read the home and game-page lines in copy.html for anything that stopped promising offline play, and check the App Home still reads well with the hotspot door back.
  7. Play it before believing it. Both limits above were found at a real table, by real phones, and neither shows up in any test suite.

The open product questions

These are the decisions the pause is really waiting on. Answer them before writing code, not after.

Question 1

Is a help card an acceptable ask?

"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?

Question 2

Do we build the wifi-binding fix?

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.

Question 3

Is there an all-iPhone case big enough for radio?

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.

Question 4

What do we actually promise?

"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.

Last updated: 2026-09-30 · Paused 2026-08-11, expected back in 2–3 months. Delete this page on the day the flag goes true and the mode ships — the reasoning lives on in decisions.html.