feat(auth): guest login with random telegraph operator name

No email required — players pick male/female/mystery, get assigned
a humorous period-appropriate operator name (e.g. "Dusty Clicksworth",
"Ada Dottsworth", "The Phantom Sender"), can reroll, then hit
"Begin Transmitting!" for instant play. Email/magic link moved to
a collapsible section for cross-device access only.

- server: POST /api/auth/guest creates guest profile + session
- server/db: profiles.email now nullable, is_guest column added,
  migration recreates table for existing installs
- client: names.ts with 15 names per gender category
- client: LoginPage redesigned — gender picker → name display → play
- client: useAuth gains setAuth() for direct session injection
- 55 server + 44 client tests pass

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-30 04:22:05 +00:00
parent 8232e75fa9
commit fe5cbaec2b
7 changed files with 341 additions and 39 deletions
+31 -1
View File
@@ -8,11 +8,33 @@ function initDb(dbPath) {
instance.pragma('journal_mode = WAL')
instance.pragma('foreign_keys = ON')
// Migration: add is_guest + make email nullable on existing installs
const profileCols = instance.pragma('table_info(profiles)').map(c => c.name)
if (profileCols.length > 0 && !profileCols.includes('is_guest')) {
instance.pragma('foreign_keys = OFF')
instance.exec(`
CREATE TABLE profiles_v2 (
id INTEGER PRIMARY KEY AUTOINCREMENT,
email TEXT UNIQUE,
display_name TEXT,
is_guest INTEGER NOT NULL DEFAULT 0,
created_at INTEGER NOT NULL,
last_seen INTEGER NOT NULL
);
INSERT INTO profiles_v2 (id, email, display_name, is_guest, created_at, last_seen)
SELECT id, email, display_name, 0, created_at, last_seen FROM profiles;
DROP TABLE profiles;
ALTER TABLE profiles_v2 RENAME TO profiles;
`)
instance.pragma('foreign_keys = ON')
}
instance.exec(`
CREATE TABLE IF NOT EXISTS profiles (
id INTEGER PRIMARY KEY AUTOINCREMENT,
email TEXT UNIQUE NOT NULL,
email TEXT UNIQUE,
display_name TEXT,
is_guest INTEGER NOT NULL DEFAULT 0,
created_at INTEGER NOT NULL,
last_seen INTEGER NOT NULL
);
@@ -56,6 +78,13 @@ function initDb(dbPath) {
);
`)
function createGuestProfile(displayName) {
const now = Date.now()
return instance.prepare(
'INSERT INTO profiles (email, display_name, is_guest, created_at, last_seen) VALUES (NULL,?,1,?,?) RETURNING *'
).get(displayName, now, now)
}
function createProfile(email, displayName) {
const now = Date.now()
const stmt = instance.prepare(
@@ -230,6 +259,7 @@ function initDb(dbPath) {
return {
_db: instance,
createGuestProfile,
createProfile,
findProfileByEmail,
findProfileById,