Install opolyx on your phone — and sign in without a password
opolyx is now a proper installable app, and there are four ways in: a magic link, an emailed code, Google or Apple. The code is the one that works everywhere.

In plain words
You can add opolyx to your home screen and open it like any other app: its own icon, no browser bars, straight into the lobby. That works today on Android and iOS from the browser menu, and it is the same build we are packaging for the app stores.
- launches full-screen into the lobby rather than the marketing page;
- keeps its own icon and splash screen instead of a browser shortcut;
- shows a proper opolyx page when you are offline, instead of a browser error;
- never caches anything from a live game, so an installed app is exactly as fresh as the site.
Signing in has four routes now, and you can use whichever suits the device you are on:
| Method | How it works |
|---|---|
| Magic link | a link in your email — one tap, no password |
| Email code | an 8-character code you type on the sign-in page |
| the standard account chooser | |
| Apple | including Hide My Email, which we never see behind |
The emailed code is the route that works absolutely everywhere — including inside an app wrapper, where a link opens in a separate browser and cannot hand the session back.
For the technically curious
The service worker is deliberately narrow. It is an allow-list, not a catch-all: navigations are network-first with an offline page as fallback, hashed static assets are stale-while-revalidate, and everything else — the database, the game server, realtime, auth, server-rendered payloads — is bypassed entirely, so it cannot possibly serve a stale board or break a live match.
// public/sw.js — allow-list, not catch-all
self.addEventListener('fetch', (event) => {
const url = new URL(event.request.url);
if (url.origin !== self.location.origin) return; // Supabase, daemon: untouched
if (BYPASS.some((p) => url.pathname.startsWith(p))) return; // /auth, /api, RSC
if (event.request.mode === 'navigate') return event.respondWith(networkFirst(event));
if (HASHED.test(url.pathname)) return event.respondWith(staleWhileRevalidate(event));
});The interesting part was the wrapped iOS build, where no sign-in method completed at all while every browser worked fine. The cause was a WebKit setting the packaging tool writes for you: with app-bound domains enabled, WebKit silently cancels any top-level navigation to a host that is not on the list — and the first hop of an OAuth flow is exactly such a navigation. Google separately refuses embedded webviews as a matter of policy, which no configuration can fix.
The magic link could never have worked there either: it opens in the system browser, whose cookie jar the app’s webview cannot see. That is what makes a typed code the only same-origin email route — it completes in the webview that asked for it.
For Google the sanctioned path is to run the flow in the system browser and hand the session back. The bridge builds the authorize URL deliberately without PKCE, so the flow returns tokens in the callback fragment that the shell can read — an authorization code would only be redeemable by the browser holding the verifier, which is the wrong one.
