Campaign export carries prep, arcs and the shelf, and what is in the bin stays out (#830, #990) #998

Merged
claude-bot merged 8 commits from fix/990-binned-not-exported into main 2026-10-01 19:18:14 +00:00
Collaborator

Closes #830. Closes #990. Two changes to the campaign export and import, in one PR because the second is built on the first.

#830: prep sheets, story arcs and the shelf travel

The export carried none of them, so a GM moving a campaign lost their prep and table setup. Schema version 15, additive; v2–14 files import as before, and a missing, empty or malformed section is skipped row by row.

  • story_arcs: live arcs.
  • prep: one sheet per session with a sheet or beats (including sessions.beat_notes), its plan, and its items, "For later" included.
  • shelf: scenes (empty ones too), pins with their whole span, cards (including ones taken off a shelf, which still stand for their pin), a rules creature's HP and conditions, and reveals.

New module app/services/prep_shelf_archive.py, in the shape of discussion_archive.py.

References and audiences

  • Rows are named by file keys, never ids; every imported row gets a fresh id. A key that resolves to nothing becomes no link. Members are never named: authorship becomes the importer, as elsewhere.
  • Each new section is marked GM-only. A reveal carries its own audience and never comes back wider: the table and the main party come back as they were; chosen people, one person, or a second party come back GMs-only (the wiki's and discussions' precedent, question X2). A reveal that was taken back comes back taken back.
  • Left behind on purpose: Discord message and channel ids, Workbench run history, content-pack record links (rule cards read from their snapshot).

#990: what is in the bin does not travel

The export selected wiki pages with no deleted_at filter and the import never set it, so a page a GM binned came back live, readable by players of the new campaign.

  • Binned pages are left out, with everything keyed on them: aliases, owners, appearances, relationships with either end binned, name targets, the zip's wiki/*.md and media/lore/.
  • A binned page's shelf card, pin and prep item are dropped too, since their snapshot is a copy of the page.
  • The import skips any row a file marks deleted, and a post marked hidden comes in hidden.
  • A guard test enumerates every table with a soft-delete column and requires a ledger line saying what the export does with it. A new soft-deletable table, a removed filter, or a newly exported soft-deletable model fails it.

A file exported before this fix may still hold binned pages, which nothing can detect; the docs say so and recommend exporting again. Whether the bin should instead travel into the new campaign's bin is question X1.

Also

  • tests/wiki/test_title_readers.py (from #877) flagged the new module's two .title reads on merge; both are now classified (an arc's own title; a reveal's, already the table's).
  • docs/API.md: the export's section now describes the new sections and the bin rule, and its stale "schema version 13" is corrected.

Follow-ups filed

  • #991: quest details, character sheets, maps and handout files are still not exported.
  • #996: binning a page leaves what was shown from its card still showing at the source (question X3).

Checked

  • Full backend suite on this tree before the last two merges of main: 6590 passed, 14 skipped. After them: tests/export and tests/wiki, 596 passed.
  • All six #990 tests and the #830 mutation check fail against the unfixed code.
  • ruff 0.4.4 format and check clean; changelog check OK (97 fragments).
  • No migration, no bot API change.

🤖 Generated with Claude Code

Closes #830. Closes #990. Two changes to the campaign export and import, in one PR because the second is built on the first. ## #830: prep sheets, story arcs and the shelf travel The export carried none of them, so a GM moving a campaign lost their prep and table setup. Schema version 15, additive; v2–14 files import as before, and a missing, empty or malformed section is skipped row by row. - **`story_arcs`**: live arcs. - **`prep`**: one sheet per session with a sheet or beats (including `sessions.beat_notes`), its plan, and its items, "For later" included. - **`shelf`**: scenes (empty ones too), pins with their whole span, cards (including ones taken off a shelf, which still stand for their pin), a rules creature's HP and conditions, and reveals. New module `app/services/prep_shelf_archive.py`, in the shape of `discussion_archive.py`. **References and audiences** - Rows are named by file keys, never ids; every imported row gets a fresh id. A key that resolves to nothing becomes no link. Members are never named: authorship becomes the importer, as elsewhere. - Each new section is marked GM-only. A reveal carries its own audience and never comes back wider: the table and the main party come back as they were; chosen people, one person, or a second party come back GMs-only (the wiki's and discussions' precedent, question X2). A reveal that was taken back comes back taken back. - Left behind on purpose: Discord message and channel ids, Workbench run history, content-pack record links (rule cards read from their snapshot). ## #990: what is in the bin does not travel The export selected wiki pages with no `deleted_at` filter and the import never set it, so a page a GM binned came back live, readable by players of the new campaign. - Binned pages are left out, with everything keyed on them: aliases, owners, appearances, relationships with either end binned, name targets, the zip's `wiki/*.md` and `media/lore/`. - A binned page's shelf card, pin and prep item are dropped too, since their snapshot is a copy of the page. - The import skips any row a file marks deleted, and a post marked hidden comes in hidden. - A guard test enumerates every table with a soft-delete column and requires a ledger line saying what the export does with it. A new soft-deletable table, a removed filter, or a newly exported soft-deletable model fails it. A file exported before this fix may still hold binned pages, which nothing can detect; the docs say so and recommend exporting again. Whether the bin should instead travel into the new campaign's bin is question X1. ## Also - `tests/wiki/test_title_readers.py` (from #877) flagged the new module's two `.title` reads on merge; both are now classified (an arc's own title; a reveal's, already the table's). - `docs/API.md`: the export's section now describes the new sections and the bin rule, and its stale "schema version 13" is corrected. ## Follow-ups filed - #991: quest details, character sheets, maps and handout files are still not exported. - #996: binning a page leaves what was shown from its card still showing at the source (question X3). ## Checked - Full backend suite on this tree before the last two merges of main: 6590 passed, 14 skipped. After them: `tests/export` and `tests/wiki`, 596 passed. - All six #990 tests and the #830 mutation check fail against the unfixed code. - ruff 0.4.4 format and check clean; changelog check OK (97 fragments). - No migration, no bot API change. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Export schema 15 adds three sections to data.json, each marked as the GMs':
story_arcs, prep (each session's sheet, beats, plan and items, For later
included) and shelf (scenes, pins, cards and reveals). The import puts them
back on the new campaign's sessions and pages with new ids.

A reveal comes back no wider than it was shown (named people, one person or
another party become the GMs') and one taken back comes back taken back.
Discord message ids and Workbench runs do not travel; a generated or rules
card reads from its snapshot. A card taken off a shelf travels as taken off,
so its pin does not put it back. Older archives import as before.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The export wrote a wiki page in the bin like any other, and the import has
no way to set deleted_at, so the page came back live in the new campaign
with the audience it last had: a player there could read what the GM had
taken away.

A soft-deleted row no longer travels, and neither does what hangs off it:
the page's Markdown file and picture, its aliases, owners, appearances,
name targets and relationships (either end), any reveal of it, and the wiki
cards, pins and prep items whose snapshot is a copy of it. A row that
pointed at it keeps its own words and is written with no key.

The import skips any row a file marks deleted (deleted_at, deleted;
removed_at on a prep item or table rule) instead of importing it live, a
post with hidden_at comes in hidden whether or not hidden is set, and
answer_post counts posts as the file lists them.

What is only ended keeps travelling: a cancelled session, a hidden post, a
reveal taken back, a card taken off a shelf, and a card's reveal the table
was shown.

tests/export/test_export_bin.py holds a ledger of every soft-deletable
table and fails when a new one appears without a line saying what the
export does with it. No schema bump: the file's shape is unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Merge remote-tracking branch 'origin/main' into fix/990-binned-not-exported
All checks were successful
CI / Backend lint (ruff) (pull_request) Successful in 34s
CI / Does this change touch the recording path? (pull_request) Successful in 29s
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Has been skipped
CI / Feedback site tests (pull_request) Successful in 49s
CI / Bot/backend version sync (pull_request) Successful in 1m26s
CI / Bot tests and audit (pull_request) Successful in 2m7s
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 3m8s
CI / Docker image build (pull_request) Successful in 4m30s
CI / Frontend (shard 2/2) (pull_request) Successful in 6m46s
CI / End-to-end journeys (phone) (pull_request) Successful in 10m2s
CI / End-to-end journeys (keyboard) (pull_request) Successful in 10m54s
CI / End-to-end journeys (desktop) (pull_request) Successful in 11m6s
CI / Backend migration, tests, and audit (pull_request) Successful in 20m39s
CI / Frontend (shard 1/2) (pull_request) Successful in 22m5s
f344f79fca
Sign in to join this conversation.
No description provided.