NAI OS
An AI-refereed party word game, Jackbox-style: a shared “console” screen creates a room, phones join via QR as answer pads, and Claude rules each typed answer with a one-line reason. Next.js + Supabase + Vercel + Claude API, its own private repository, live at prestolex.vercel.app. This is the one page describing it.
Prestolex (github.com/udopnink/nai-prestolex) is the third web
application in this portfolio and the first aimed at consumers rather than at Udo's own consulting
practice. It modernizes the mechanic of a classic parlour word race — a category and a
letter are revealed, everyone races to type a fitting word — and replaces the argument the
paper game always ended in with an AI referee that rules every answer instantly, with a reason.
It took over as the primary commercial project on 2026-08-11, inheriting the previous
candidate's build envelope, and shipped its first live milestone within days.
Like nai-analysis and
nai-onto, it is built with GSD's phase-based
discuss→plan→execute→verify loop rather than this workspace's
orchestrator/subagent/tool pattern — a persistent product with a database, a deploy
pipeline, and now a billing system, not a one-shot dispatch that produces a document. Unlike the
other two, an LLM sits inside the product: the referee and the deck generator are
Claude calls at runtime, which is why this app has a model policy and cost guardrails the other
two never needed.
The console reveals a letter and a category; a 25-second timer runs; every player types an answer on their phone; the referee rules each answer as it arrives. All players answered early? The round ends after a short beat instead of waiting out the clock.
Every valid answer scores. The speed bonus goes to the earliest server-side submission timestamp among answers that ultimately validate — async judging latency can never reorder it. Duplicates score less, which keeps answers diverse.
Exact match on the normalized form only — Unicode NFC + casefold + trim + collapsed whitespace. No umlaut folding (Bär ≠ Baer), no fuzzy matching; anything smarter is gated behind the referee eval set.
The signature moment: every answer with the referee's one-line ruling. The argument the paper game always ended in, settled instantly with a reason.
Configurable rounds (default 10) from a chosen deck, a final scoreboard with small awards, and two curated scoring extras — a jackpot rollover and a final-round double.
DE + EN from day one. The deck's language sets the round UI and the judging language.
The shared-screen moment is theatrical by design: the letter lands via a slot-machine spin, the category is drawn as a card off a physical-looking deck, and won cards stack visibly per player. Console-only — phones stay fast answer pads.
Five bundled decade packs — 80s through 20s — each pairing a curated era deck (DE + EN) with a visual theme worn by the console and every joined phone for the whole game. Curated, not generated: no AI cost, IP-safe by construction.
Binding model policy:
claude-haiku-4-5 for all app AI calls — referee, deck generation,
opponent. Sonnet only as an eval-gated escalation; never an Opus-tier model anywhere in the
product. Referee calls are tiny and latency is the UX.
Each ruling comes back as
{valid, display_safe, reason} via a JSON schema, so parsing can never fail
mid-game. The judging rules live in a cached system prompt.
The cost lever: each distinct
(language, category, letter, word) is judged once globally and
memoized in Postgres. Party vocabulary is Zipf-distributed, so repeats are free and instant
and the hit rate climbs fast.
On timeout, refusal, or a missing API key the referee degrades to “unavailable” and the room flips to majority-vote judging by the players — framed as a feature, never an error screen. A referee failure falls back to the letter-check heuristic and refunds the consumed allowance unit.
Free-text answers on a shared screen
pass a synchronous local DE+EN blocklist at submission, then the referee's
display_safe flag. An answer failing either is masked on the recap, scores
invalid, and its raw text never reaches the console DOM — one module is the single
chokepoint for what renders.
Theme in, difficulty-tagged category list out, DE or EN — the freemium product. The generator must refuse protected-IP themes and genericize them; themed decks are their own trademark surface.
REFEREE_MOCK=1 — an
answer is valid iff it starts with the letter — is the deterministic referee the unit
and e2e suites run against.
Every limit is env-configurable, but the point is that each exists and has a name: a per-account monthly judgment allowance (300 free, 8 000 plus, credit packs beyond that), per-room daily caps enforced atomically with the allowance, three deck generations per device per day on the free tier, and a global spend alarm — soft at €10/day, hard stop at €25/day. Exhaustion and the hard stop don't error: they flip judging to majority-vote mode, the same graceful floor the referee falls to when it's unavailable.
Creating a room or generating decks requires a host account (Google or email magic link, via Supabase Auth) so quotas and purchases have a durable identity. Phone players keep the frictionless anonymous QR + nickname join; signing in is optional and only adds a saved nickname and avatar.
3 players free, 13 on the plus tier — stamped into the game state as entitlements and re-resolved at every round start, with a fail-closed-to-free tier resolver.
Phone-side checkout for the plus subscription and for judgment credit packs, an idempotent webhook that reopens failed events, and a realtime console unlock when the upgrade lands mid-party.
The Anthropic and Stripe production keys are deliberately not set until the owner adds them — the live referee and billing degrade gracefully in the meantime, because degrading gracefully is a designed-in mode, not an incident.
%%{init: {'theme':'neutral', 'flowchart':{'htmlLabels':false,'nodeSpacing':45,'rankSpacing':55}}}%%
flowchart TD
H["The console - TV, tablet or laptop<br/>host signed in - the single writer"] --> API
P["Players' phones - anonymous<br/>QR join - submit answers only"] --> API
subgraph L1 ["Next.js server routes - api/rooms/*"]
API["applyActions over the pure reducer<br/>version-guarded on rooms.version"]
REF["Referee module - never throws<br/>memo hit? no API call"]
end
API --> REF
REF --> CL["Claude API<br/>claude-haiku-4-5, structured output"]
subgraph L2 ["Supabase - service role only for writes"]
DB[("rooms · answers (append-only)<br/>decks · subscriptions · allowances")]
MEMO[("judgment memo<br/>each distinct answer judged once")]
RT["Realtime channel per room<br/>broadcast + presence"]
end
API --> DB
REF --> MEMO
DB --> RT
RT --> H
RT --> P
classDef orch fill:#dcefe9,stroke:#0f6f63,stroke-width:2px
classDef tool fill:#f4f1ea,stroke:#8a7a5a
classDef human fill:#fdf3e0,stroke:#8a5a00,stroke-width:2px
class H,P human
class API,REF orch
class DB,MEMO,RT,CL tool
applyActions under optimistic concurrency on rooms.version, and
Supabase Realtime broadcasts the result back to everyone — which is what makes the game
survive flaky party Wi-Fi. Game logic is a pure reducer with no I/O; AI calls are server-side
only, so the API key never reaches a client; the anon database role is SELECT-only and all
writes ride the service-role key inside server routes.Next.js App Router on Vercel, installable as a PWA — web manifest, service worker, offline page.
Postgres + Auth + Realtime; versioned SQL migrations
under supabase/migrations/, twelve so far.
The official TypeScript SDK, server-side only, Haiku-only by binding policy.
Subscription + credit-pack checkout with an idempotent webhook.
tsc --noEmit, a Vitest unit suite on
the pure reducer (62 tests at the first milestone), and Playwright e2e against a local
Supabase stack — the e2e's parallel joins are the standing regression proof for a
join-race fix.
v0.1.0-m1 (the core game, live) and
v0.2.0-m2 (game-flow UX, decade packs, showtime, accounts) are tagged; the
player-cap + paywall work is the current branch.
Like nai-onto, and unlike
nai-analysis, Prestolex has
no seam to this workspace at all — no slash command reads it, writes to
it, or knows it exists. What it shares with the agent systems is only the discipline: pinned
decisions that don't get relitigated, invariants with a named single home in the code, degraded
modes designed as features, and a verify step before anything is called done.
The current focus: what it takes to ship in the Google Play Store and Apple App Store — wrapper paths, review policies for user-generated content on a shared screen, and how in-app-purchase rules interact with the freemium model.
The experimental voice referee is explicitly on hold — no spend on it.
A standing cost comparison for the referee/deck-gen workload (tiny prompts, high volume) across providers; the Haiku-first policy stands until that analysis says otherwise and the owner re-decides.