Repository navigation
Rulebook editor: Directors edit their campaign's book, with house rules - #969
Merged
Merged
Conversation
A Director (the owner, or a member with a co-DM grant) can now edit a game system's Rulebook from the Rules page: change any page, add pages, and write house-rules chapters. Edits are stored as the campaign's own copy of each page (new tables campaign_book_chapters and campaign_book_pages), so the installed package is never changed, pages nobody edited keep following package updates, and a page the package changed after it was edited is flagged with Compare / Keep mine / Use the package's. The edition can be downloaded as package files. The merge runs before the Player/Director filter, so Directors-only flags on edited pages are honoured, and stored pages are re-checked by the same rules as package files on every read. Fixes #889 Fixes #821 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cce6X7rscrMeCUBituVyNe
A new house-rules page, or a title typed before any text, was refused because its empty text box failed the book checks. Empty text blocks are now kept in the stored page and show nothing to readers; every other block is still checked, and error block numbers still match the editor. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cce6X7rscrMeCUBituVyNe
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Cce6X7rscrMeCUBituVyNe
8 of 10 tasks
keyxmakerx
marked this pull request as ready for review
October 3, 2026 04:27
This was referenced Oct 3, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #889
Fixes #821
Security implication: New write routes. Every editor route (page, source, export and all changes) first checks the person is the owner or has a co-DM grant (
bookEditorAllowed→cc.CanAuthorDmOnly()), then 403. Every query is scoped by campaign and system. Stored pages are re-checked by the same strict rules as package files on every read, and the Player/Director filter runs after the merge.Consumer-verified:
GET /book/sourceentries are read atstatic/js/widgets/rulebook_editor.js:303and:324(key, state, problem, page, theirs).canEditonGET /bookis read bystatic/js/widgets/rulebook.js.Mockup: Signed on the Sign-offs page (card mock-rulebook-editor: approved; owner and co-Directors edit; house rules in this editor). Mockup: https://claude.ai/artifact/4BzsRuAcAEPpbQzkaJGH34
What this changes
Before: a game system's Rulebook could only be changed by editing the package's YAML files, so a table couldn't fix a page, add a house rule or leave a note.
After: a Director opens the Rulebook and presses Edit. A three-pane editor (contents, page form, live page with a Director/Player switch) changes any page, adds pages, and writes house-rules chapters, which appear in a "House rules" part at the end of the book. "Download as package files" gives the edition back as
book/YAML a package author can ship.Edits are the campaign's own copy of each page. The installed package is never touched. Pages nobody edited keep following package updates. If the package changes a page after it was edited, the page says so and offers Compare / Keep mine / Use the package's.
How: two core tables (
campaign_book_chapters,campaign_book_pages). A copy of a package page is keyed by the page's position, with a sha256 of the package page it was copied from.ApplyBookEditsmerges these over the loaded package book beforeFilterBook. Request pages go through the same strict decoder andbuildBookPagechecks as package files. The editor's live page is drawn byrulebook.js's own renderer (window.ChronicleRulebook.renderPage).Load-bearing lines
internal/systems/book_edit_handler.gobookEditor()is the permission check that every editor route passes first.internal/systems/handler.goBookAPIruns the merge first, thenFilterBook. If the edits can't be loaded it fails loudly rather than quietly serving the package book.internal/systems/book_edit_service.gocheckRequestPage/buildStoredPagehold the validation and the drafts rule.checkRoomcaps everything one campaign stores for one book at 8 MB.db/migrations/000036_campaign_book_edits.up.sql: idempotent, references only the corecampaignstable,ON DELETE CASCADE.Honest deviations from the signed mockup
Each of these is needed for the editor to work:
Other differences:
Why
#889: Key Maker asked for a Rulebook "somone who isn't even a programmer can work with". #821: house rules, which Key Maker chose to fold into this editor.
Test plan
go build ./...,go vet ./internal/systems/: cleango test ./... -short -count=1: no failuresCHRONICLE_TEST_DB_DSN='root@tcp(127.0.0.1:13306)/' go test ./internal/systems/ -count=1 -v: 259 passed, 0 failed, 0 skipped. This includes the newTestBookEditRepository_Integration, which runs the SQL against real MariaDB: the nullable unique key, upsert, promotion, house chapters, cross-campaign scoping and cascade on campaign delete.go test ./internal/wire/... ./internal/app/... ./internal/patch/... -short -count=1 -v: 120 passed, 0 failed. Routes snapshot regenerated with 10 new lines.make test-js: 291 pass, 0 failtools/check-*.sh: passLive check for Key Maker, after deploy and the Draw Steel update:
Tenet self-check
docs/system-rulebook-book.mdandinternal/systems/.ai.mdupdated🤖 Generated with Claude Code
https://claude.ai/code/session_01Cce6X7rscrMeCUBituVyNe
Generated by Claude Code