From 438e5af0ad8826bca655401afacef9c02a634859 Mon Sep 17 00:00:00 2001 From: pavel444-byte Date: Sat, 15 Aug 2026 17:37:57 +0500 Subject: [PATCH] Important Fixes, New mechanics, and many more --- .env.example | 3 + README.md | 23 +- apps/web/components/AppShell.vue | 18 +- apps/web/components/CoauthorConversation.vue | 52 + apps/web/components/CoauthorWorldPreview.vue | 32 + apps/web/composables/useDngApi.ts | 33 + apps/web/composables/useDngAuth.ts | 188 ++ apps/web/pages/auth/callback.vue | 29 + apps/web/pages/campaign/[id].vue | 292 +++ apps/web/pages/dashboard.vue | 41 +- apps/web/pages/index.vue | 59 +- apps/web/pages/join/[token].vue | 38 + apps/web/pages/worlds/new.vue | 216 +- apps/web/server/api/v1/campaigns/[id].get.ts | 56 + .../api/v1/campaigns/[id]/characters.post.ts | 53 + .../api/v1/campaigns/[id]/invites.post.ts | 32 + .../[id]/members/[memberId].patch.ts | 26 + .../[id]/rounds/[roundId]/force.post.ts | 22 + .../[id]/rounds/[roundId]/intent.put.ts | 34 + .../[id]/rounds/[roundId]/ready.post.ts | 36 + .../[id]/rounds/[roundId]/retry.post.ts | 23 + apps/web/server/api/v1/campaigns/index.get.ts | 17 + .../web/server/api/v1/campaigns/index.post.ts | 19 + .../api/v1/coauthor/sessions/[id].get.ts | 15 + .../v1/coauthor/sessions/[id]/confirm.post.ts | 30 + .../coauthor/sessions/[id]/generate.post.ts | 15 + .../coauthor/sessions/[id]/messages.post.ts | 19 + .../v1/coauthor/sessions/[id]/respond.post.ts | 54 + .../api/v1/coauthor/sessions/index.get.ts | 13 + .../api/v1/coauthor/sessions/index.post.ts | 24 + apps/web/server/api/v1/invites/join.post.ts | 19 + apps/web/server/utils/runtime-config.test.ts | 8 + apps/web/server/utils/runtime-config.ts | 6 + .../server/utils/stage-two-supabase.test.ts | 21 + apps/web/server/utils/stage-two-supabase.ts | 126 + apps/worker/src/ai.test.ts | 2 +- apps/worker/src/ai.ts | 19 +- apps/worker/src/index.ts | 89 +- apps/worker/src/orchestration.test.ts | 56 + apps/worker/src/orchestration.ts | 110 + package.json | 14 +- packages/game-engine/src/index.test.ts | 1 + packages/game-engine/src/index.ts | 9 +- packages/shared/src/index.test.ts | 19 + packages/shared/src/index.ts | 39 +- pnpm-lock.yaml | 10 + scripts/build-supabase-bootstrap.mjs | 89 + scripts/ensure-supabase-schema.mjs | 183 ++ scripts/smoke-multiplayer.mjs | 170 ++ supabase/bootstrap.sql | 2064 +++++++++++++++++ supabase/migrations/0001_alpha_schema.sql | 7 +- .../migrations/0003_stage_two_multiplayer.sql | 384 +++ .../0004_anonymous_alpha_access.sql | 100 + .../migrations/0005_failed_round_retry.sql | 83 + .../0006_retry_job_compatibility.sql | 63 + vitest.config.ts | 6 + 56 files changed, 5090 insertions(+), 119 deletions(-) create mode 100644 apps/web/components/CoauthorConversation.vue create mode 100644 apps/web/components/CoauthorWorldPreview.vue create mode 100644 apps/web/composables/useDngApi.ts create mode 100644 apps/web/composables/useDngAuth.ts create mode 100644 apps/web/pages/auth/callback.vue create mode 100644 apps/web/pages/campaign/[id].vue create mode 100644 apps/web/pages/join/[token].vue create mode 100644 apps/web/server/api/v1/campaigns/[id].get.ts create mode 100644 apps/web/server/api/v1/campaigns/[id]/characters.post.ts create mode 100644 apps/web/server/api/v1/campaigns/[id]/invites.post.ts create mode 100644 apps/web/server/api/v1/campaigns/[id]/members/[memberId].patch.ts create mode 100644 apps/web/server/api/v1/campaigns/[id]/rounds/[roundId]/force.post.ts create mode 100644 apps/web/server/api/v1/campaigns/[id]/rounds/[roundId]/intent.put.ts create mode 100644 apps/web/server/api/v1/campaigns/[id]/rounds/[roundId]/ready.post.ts create mode 100644 apps/web/server/api/v1/campaigns/[id]/rounds/[roundId]/retry.post.ts create mode 100644 apps/web/server/api/v1/campaigns/index.get.ts create mode 100644 apps/web/server/api/v1/campaigns/index.post.ts create mode 100644 apps/web/server/api/v1/coauthor/sessions/[id].get.ts create mode 100644 apps/web/server/api/v1/coauthor/sessions/[id]/confirm.post.ts create mode 100644 apps/web/server/api/v1/coauthor/sessions/[id]/generate.post.ts create mode 100644 apps/web/server/api/v1/coauthor/sessions/[id]/messages.post.ts create mode 100644 apps/web/server/api/v1/coauthor/sessions/[id]/respond.post.ts create mode 100644 apps/web/server/api/v1/coauthor/sessions/index.get.ts create mode 100644 apps/web/server/api/v1/coauthor/sessions/index.post.ts create mode 100644 apps/web/server/api/v1/invites/join.post.ts create mode 100644 apps/web/server/utils/stage-two-supabase.test.ts create mode 100644 apps/web/server/utils/stage-two-supabase.ts create mode 100644 apps/worker/src/orchestration.test.ts create mode 100644 apps/worker/src/orchestration.ts create mode 100644 packages/shared/src/index.test.ts create mode 100644 scripts/build-supabase-bootstrap.mjs create mode 100644 scripts/ensure-supabase-schema.mjs create mode 100644 scripts/smoke-multiplayer.mjs create mode 100644 supabase/bootstrap.sql create mode 100644 supabase/migrations/0003_stage_two_multiplayer.sql create mode 100644 supabase/migrations/0004_anonymous_alpha_access.sql create mode 100644 supabase/migrations/0005_failed_round_retry.sql create mode 100644 supabase/migrations/0006_retry_job_compatibility.sql diff --git a/.env.example b/.env.example index e636fea..1723519 100644 --- a/.env.example +++ b/.env.example @@ -3,6 +3,9 @@ NUXT_PUBLIC_SUPABASE_URL= SUPABASE_URL= NUXT_PUBLIC_SUPABASE_ANON_KEY= SUPABASE_SERVICE_ROLE_KEY= +# Used only by db:ensure to create a missing schema. Copy the Session pooler +# URI from Supabase Dashboard → Connect and replace its password placeholder. +SUPABASE_DB_URL= # AI provider (choose openrouter or deepseek) AI_PROVIDER=openrouter diff --git a/README.md b/README.md index 5ca2e90..67d9764 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Dungeons & Ground -An English-first, invite-only web alpha for asynchronous text TTRPG campaigns. Players create private worlds with an AI coauthor, submit actions into shared rounds, and play alongside persistent AI companions. The rules engine—not the language model—owns dice and mechanical state. +An English-first private web alpha for asynchronous text TTRPG campaigns. Players create private worlds with an AI coauthor, submit actions into shared rounds, and play alongside persistent AI companions. The rules engine—not the language model—owns dice and mechanical state. ## Local start @@ -12,9 +12,13 @@ pnpm dev:all Open `http://localhost:3000`. `pnpm dev:all` starts the Nuxt site and the AI worker in one terminal. To run them separately, use `pnpm dev` in the first terminal and `pnpm dev:worker` in the second. +To verify the complete hosted-Supabase multiplayer path (two temporary guests, invite, shared readiness, AI resolution, and the next round), keep `dev:all` running and execute `pnpm smoke:multiplayer` in another terminal. + +Before starting either process, the root commands check the Supabase schema. If it is missing and `SUPABASE_DB_URL` is configured, they transactionally create all D&G tables, functions, triggers, indexes, and policies from `supabase/bootstrap.sql`. Copy the **Session pooler** URI from the Supabase Dashboard **Connect** panel into the root `.env` and replace the password placeholder with the URL-encoded database password. The database URL is server-only and must never use a `NUXT_PUBLIC_` prefix. + The web and worker commands both load this root `.env` file explicitly. Environment variables supplied by Railway or CI take precedence over values in the file. -The static demo UI can be built and tested without credentials. A production Nitro server and every worker process require `SUPABASE_URL`, `SUPABASE_SERVICE_ROLE_KEY`, `NUXT_PUBLIC_SUPABASE_URL`, and `NUXT_PUBLIC_SUPABASE_ANON_KEY`; startup fails with a clear error when required server credentials are missing. Redis is optional: when `REDIS_URL` is absent, the worker safely claims jobs from the Supabase `ai_jobs` outbox using short database leases and retries failed jobs up to three times. +The UI can be built without credentials. A running Nitro server and every worker process require `SUPABASE_URL`, `SUPABASE_SERVICE_ROLE_KEY`, `NUXT_PUBLIC_SUPABASE_URL`, and `NUXT_PUBLIC_SUPABASE_ANON_KEY`; startup fails with a clear error when required server credentials are missing. Redis is optional: when `REDIS_URL` is absent, the worker safely claims jobs from the Supabase `ai_jobs` outbox using short database leases and retries failed jobs up to three times. Select OpenRouter or the official DeepSeek API with `AI_PROVIDER=openrouter|deepseek` and provide the matching API key. OpenRouter retains JSON Schema structured output; DeepSeek uses its official JSON mode, followed by the same Zod validation. @@ -38,11 +42,18 @@ Use Node.js 18.20.5 or newer. The lockfile pins the web toolchain to the Node 18 ## Production services -1. Create a Supabase project and apply `supabase/migrations/0001_alpha_schema.sql`, then `supabase/migrations/0002_supabase_ai_queue.sql`, and finally the seed file. If `0001` was already applied earlier, apply only `0002`; it adds the Supabase-backed worker queue and reloads the REST API schema cache. -2. Add invited tester emails to `public.allowlist`. +1. Copy the Supabase **Session pooler** connection URI from **Connect** into `SUPABASE_DB_URL`. `pnpm dev:all` now detects an empty project and creates the complete schema automatically. `pnpm db:ensure` runs the same guarded bootstrap without starting the app. Manual SQL Editor installation remains available through `supabase/bootstrap.sql`. +2. In Supabase Auth settings, enable **Allow anonymous sign-ins**. In Auth URL Configuration, set the Site URL to `http://localhost:3000` and add `http://localhost:3000/auth/callback` as a Redirect URL. Email magic-link accounts remain optional and must be added to `public.allowlist`. Before external testing, enable CAPTCHA and review Supabase's anonymous sign-in rate limits so disposable guests cannot be used to evade AI quotas. 3. Run `pnpm dev:worker` alongside the web process. Provision Redis only when BullMQ delivery is desired; otherwise the worker polls the Supabase outbox. 4. Configure either `OPENROUTER_API_KEY` (default model `deepseek/deepseek-v4-flash`) or set `AI_PROVIDER=deepseek` with `DEEPSEEK_API_KEY` (default model `deepseek-v4-flash`, endpoint `https://api.deepseek.com/chat/completions`). -If the worker reports `POST /rest/v1/rpc/claim_ai_job 404`, the database is missing migration `0002_supabase_ai_queue.sql`. Run that file in the Supabase SQL Editor and restart `pnpm dev:all`. +If the worker reports `POST /rest/v1/rpc/claim_ai_job 404` and `/rest/v1/profiles` also returns 404, the base schema was never installed. Run the generated `supabase/bootstrap.sql` in the SQL Editor and restart `pnpm dev:all`. Do not run only `0002`: it depends on the tables created by `0001`. -The current UI intentionally includes a complete local vertical slice. Production auth/session binding and queue-enqueue endpoints should be connected to the supplied schema before inviting external users. +## Stage two flow + +1. Click **Enter the Alpha** to create a persistent guest session without email. Do not sign out or clear site storage unless you are prepared to lose that guest account. An allowlisted email magic link is still available as an alternative. +2. Create a world in the coauthor chat, review the editable result, and confirm it. +3. Create human characters and optional AI companions in the campaign lobby. +4. The owner creates an expiring private invite link. Guest or allowlisted-email users join through `/join/:token`. +5. Players save actions and mark them ready. The final ready action queues the round automatically; the owner can also continue without waiting. +6. The worker resolves server-owned rolls and state, publishes narration, and opens the next round. Visible campaign tabs synchronize every two seconds and immediately when the tab regains focus. diff --git a/apps/web/components/AppShell.vue b/apps/web/components/AppShell.vue index 20cf163..49919fb 100644 --- a/apps/web/components/AppShell.vue +++ b/apps/web/components/AppShell.vue @@ -1,5 +1,18 @@ diff --git a/apps/web/components/CoauthorConversation.vue b/apps/web/components/CoauthorConversation.vue new file mode 100644 index 0000000..fd1c96d --- /dev/null +++ b/apps/web/components/CoauthorConversation.vue @@ -0,0 +1,52 @@ + + + + + diff --git a/apps/web/components/CoauthorWorldPreview.vue b/apps/web/components/CoauthorWorldPreview.vue new file mode 100644 index 0000000..7c8a8ef --- /dev/null +++ b/apps/web/components/CoauthorWorldPreview.vue @@ -0,0 +1,32 @@ + + +