Lane 8 PR 4: quests as pages (#734) #890

Merged
claude-bot merged 5 commits from build/lane8-4-quests into main 2026-09-28 03:19:57 +00:00
Collaborator

Part of #734 (lane 8), §3.7. Stacked on #884 (2b).

Migrations

8e5f6a7b8c9d_loretype_quest adds quest to loretype. It chains on 8c3d.

8f6a7b8c9d0e_quests_as_pages creates quest_details, quest_objectives and objective audiences, then moves the data. Each plot_threads row becomes a quest page, one to one:

  • GM notes go into an escaped :::gm block;
  • player_visible becomes a table audience, otherwise a GM audience;
  • the start and end sessions carry over;
  • linked_lore_entry_id becomes a "concerns" relationship;
  • rumour threads are repointed in one UPDATE.

It's reversible on real data.

  • quest_details.legacy_plot_thread_id records where each page came from. The move skips threads that are already done.
  • downgrade() truly reverses it: threads point back, the "concerns" rows and quest pages are deleted, and the tables are dropped.
  • Quest pages made after the upgrade are deleted on downgrade. That's the accepted cost, and the docstring says so.
  • plot_threads stays, unread, until PR 9.
  • Verified with seeded data: up → down → up, both in pytest and directly with Alembic on Postgres 16. Each time there's exactly one page per thread, the rumour moves correctly, and no rows are left orphaned.

Server

  • quest_service.py is the one place quests are read. Every raw body read renders through render_for and is on the allowlist, and the module is in the discussions no-AI list.
  • Readers moved over:
    • the bot's /quests (identical response shape, so the contract stays v2);
    • the reminder recap (table-audience only);
    • generation context;
    • the shelf's open loops;
    • Planning.
  • Routes:
    • new: GET and POST .../lore/quests. They're placed before /lore/{entry_id}, and a test caught the ordering.
    • removed: .../planning/threads and .../quests.
  • W10/D196: a rumour can exist before its quest does. Confirming one can create the quest page.

Frontend (only what removing the routes forced)

  • The API client, QuestLog, the Quests drawer quick-add, and Improv's "Add as plot thread".
  • NewThread has a "No quest yet" option. It no longer blocks a rumour when there are no quests.
  • J3's seed now posts to the new route. It hasn't been run locally; CI will run it.

Departures

  1. The shelf's players' open loops use table-audience quests only. The shelf has no viewer context, so this narrows what players see rather than widening it.
  2. QuestLog and Planning no longer show the old "linked wiki entry". It's now a relationship, and P5 replaces both surfaces.

Checks

  • Full backend suite: 5562 passed, 3 failed. The 3 failures happen locally only, because the harness mounts webapp/backend alone and they need repo paths.
  • Targeted modules: all green.
  • ruff: clean. Version sync: OK.
  • Frontend touched tests: 161 passed. eslint: 0 errors.
  • The full vitest run timed out under machine load; CI will confirm it.

🤖 Generated with Claude Code

Part of #734 (lane 8), §3.7. **Stacked on #884 (2b).** ## Migrations **`8e5f6a7b8c9d_loretype_quest`** adds `quest` to `loretype`. It chains on `8c3d`. **`8f6a7b8c9d0e_quests_as_pages`** creates `quest_details`, `quest_objectives` and objective audiences, then moves the data. Each `plot_threads` row becomes a quest page, one to one: - GM notes go into an escaped `:::gm` block; - `player_visible` becomes a table audience, otherwise a GM audience; - the start and end sessions carry over; - `linked_lore_entry_id` becomes a "concerns" relationship; - rumour threads are repointed in one UPDATE. **It's reversible on real data.** - `quest_details.legacy_plot_thread_id` records where each page came from. The move skips threads that are already done. - `downgrade()` truly reverses it: threads point back, the "concerns" rows and quest pages are deleted, and the tables are dropped. - Quest pages made *after* the upgrade are deleted on downgrade. That's the accepted cost, and the docstring says so. - `plot_threads` stays, unread, until PR 9. - **Verified with seeded data:** up → down → up, both in pytest and directly with Alembic on Postgres 16. Each time there's exactly one page per thread, the rumour moves correctly, and no rows are left orphaned. ## Server - **`quest_service.py`** is the one place quests are read. Every raw body read renders through `render_for` and is on the allowlist, and the module is in the discussions no-AI list. - **Readers moved over:** - the bot's `/quests` (identical response shape, so the contract stays v2); - the reminder recap (table-audience only); - generation context; - the shelf's open loops; - Planning. - **Routes:** - new: `GET` and `POST .../lore/quests`. They're placed before `/lore/{entry_id}`, and a test caught the ordering. - removed: `.../planning/threads` and `.../quests`. - **W10/D196: a rumour can exist before its quest does.** Confirming one can create the quest page. ## Frontend (only what removing the routes forced) - The API client, `QuestLog`, the Quests drawer quick-add, and Improv's "Add as plot thread". - `NewThread` has a "No quest yet" option. It no longer blocks a rumour when there are no quests. - **J3's seed** now posts to the new route. It hasn't been run locally; CI will run it. ## Departures 1. **The shelf's players' open loops use table-audience quests only.** The shelf has no viewer context, so this narrows what players see rather than widening it. 2. **`QuestLog` and Planning no longer show the old "linked wiki entry".** It's now a relationship, and P5 replaces both surfaces. ## Checks - **Full backend suite:** 5562 passed, 3 failed. The 3 failures happen locally only, because the harness mounts `webapp/backend` alone and they need repo paths. - **Targeted modules:** all green. - **ruff:** clean. **Version sync:** OK. - **Frontend touched tests:** 161 passed. **eslint:** 0 errors. - The full vitest run timed out under machine load; CI will confirm it. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
A quest is a wiki page of kind `quest` now (app.services.quest_service),
not a `plot_threads` row. Migrations 8e5f6a7b8c9d (the `quest` enum
value, in its own transaction) and 8f6a7b8c9d0e (quest_details,
quest_objectives, the data move, and the rumour repoint) move every
existing quest to a page: the GM's notes become a `:::gm` block,
`player_visible` becomes the page's own audience (D166), and a
`linked_lore_entry_id` becomes a "concerns" relationship.

Repoints every reader: the bot's `/quests` (same response shape, no
contract bump), the pre-session reminder's open threads, Prep's context
(rendered through wiki_text.GM_VIEWER, never raw), the shelf's open
loops, and the Planning page's quest window. Removes the old
GET/POST/PATCH/DELETE .../planning/threads and GET .../quests routes,
replaced by GET/POST .../lore/quests.

D196/W10: a rumour may be about no quest yet. Confirming one offers
"Make it a quest" (VerdictIn.make_quest_title), which creates the page
and posts it under whoever started the rumour. NewThread.jsx's quest
picker gains a "No quest yet" choice and no longer blocks posting.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
8f6a7b8c9d0e's downgrade dropped the three new tables but left the
migrated quest pages and the repointed discussion_threads behind, so
upgrade -> downgrade -> upgrade on a campaign with plot threads doubled
every quest page. Fixes review feedback on the lane 8 PR 4 commit.

* quest_details gains legacy_plot_thread_id (nullable, no FK --
  plot_threads outlives this migration until PR 9), set by
  migrate_quests, which now skips any plot_threads row already named by
  one -- idempotent across a down+up.
* downgrade() is a true reverse: it repoints discussion_threads back to
  plot_thread_id (reading legacy_plot_thread_id, in one UPDATE per
  ck_discussion_threads_about), deletes the "concerns" relationships
  the migration made, and deletes every quest page -- including one
  made since the upgrade with no legacy thread to return to, which is
  the accepted, documented cost of downgrading past this point.
* tests/wiki/test_quest_migration.py: a new up -> down -> up test on
  seeded data (a player_visible thread with notes, a GM-only thread
  with a linked lore entry, and a rumour about the first) asserting
  exactly one page per thread each time, the rumour's audience moving
  the right way each time, and no orphaned concerns rows.

Verified directly against a fresh Postgres 16 outside the pytest
harness too: seed -> downgrade -> upgrade -> assert -> downgrade ->
assert -> upgrade -> assert, all green, including that the second
upgrade's pages get fresh ids rather than reusing the first run's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The one conflict was test_notifications' migration-history docstring:
main's lane 10 entries, then 8b2c, 8d4e, 8c3d and 8e5f/8f6a in chain order.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The one conflict was GET /api/bot/guilds/{guild_id}/quests. #885 (O9) made
it resolve one campaign from channel_id/campaign_id (409 when a guild's
campaigns are ambiguous); lane 8 PR 4 moved quests from plot_threads to wiki
pages. Kept both: the route resolves the campaign as main does, then reads
that campaign's active, table-audience quest pages through quest_service,
whose guild-wide list_active_table_quests_for_guild becomes the
campaign-scoped list_active_table_quests. Response shape unchanged, so no
contract bump.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude-bot changed target branch from build/lane8-2b-feeds to main 2026-09-28 01:55:35 +00:00
Merge main into build/lane8-4-quests
All checks were successful
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 57s
CI / Backend lint (ruff) (pull_request) Successful in 31s
CI / Bot tests and audit (pull_request) Successful in 1m57s
CI / Feedback site tests (pull_request) Successful in 44s
CI / Does this change touch the recording path? (pull_request) Successful in 18s
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Has been skipped
CI / Bot/backend version sync (pull_request) Successful in 1m7s
CI / Docker image build (pull_request) Successful in 1m9s
CI / Frontend tests, audit, and build (pull_request) Successful in 22m46s
CI / Backend migration, tests, and audit (pull_request) Successful in 33m16s
CI / End-to-end journeys (desktop, phone, keyboard-only) (pull_request) Successful in 40m32s
fec864b29b
Sign in to join this conversation.
No description provided.