Skip to content

Rulebook editor: Directors edit their campaign's book, with house rules - #969

Merged
keyxmakerx merged 3 commits into
mainfrom
claude/rules-page-fix-h5b7j8
Oct 3, 2026
Merged

keyxmakerx merged 3 commits into
mainfrom
claude/rules-page-fix-h5b7j8

Conversation

@keyxmakerx

Copy link
Copy Markdown
Owner

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/source entries are read at static/js/widgets/rulebook_editor.js:303 and :324 (key, state, problem, page, theirs). canEdit on GET /book is read by static/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. ApplyBookEdits merges these over the loaded package book before FilterBook. Request pages go through the same strict decoder and buildBookPage checks as package files. The editor's live page is drawn by rulebook.js's own renderer (window.ChronicleRulebook.renderPage).

Load-bearing lines

  • internal/systems/book_edit_handler.go bookEditor() is the permission check that every editor route passes first.
  • internal/systems/handler.go BookAPI runs the merge first, then FilterBook. If the edits can't be loaded it fails loudly rather than quietly serving the package book.
  • internal/systems/book_edit_service.go checkRequestPage / buildStoredPage hold the validation and the drafts rule. checkRoom caps everything one campaign stores for one book at 8 MB.
  • db/migrations/000036_campaign_book_edits.up.sql: idempotent, references only the core campaigns table, ON DELETE CASCADE.

Honest deviations from the signed mockup

Each of these is needed for the editor to work:

  • "Remove this page" on the campaign's own pages, with an inline "Yes, remove it / Keep it" confirm.
  • A title field and "Remove this chapter" on house-rules chapters.
  • Creature tagline, notice lines, label/value number rows and a Director's note field.
  • Rolls get add/remove band and a "Readers can add a bonus" box.
  • Cards get a short-line field.
  • A read-only card for package widgets, which can be moved or removed only.
  • The rule-word menu gets a search box past 12 words.
  • A save error shows "Try again".

Other differences:

  • The changed-page banner says "Draw Steel changed this page" without a version number, since the server can't know which release did it.
  • Chapter order follows the package. House-rules chapters can't be moved between package chapters (not in the mockup either).
  • A text block with no text yet is kept as a draft and shows readers nothing. Without that, a new page couldn't be saved.

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/: clean
  • go test ./... -short -count=1: no failures
  • CHRONICLE_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 new TestBookEditRepository_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 fail
  • Every tools/check-*.sh: pass
  • Editor browser harness (Chromium, contract-following fake server): 107/107. Book viewer harness unchanged: 80/80.
  • Live end-to-end on a local server with the real Draw Steel package and owner and player accounts:
    • the owner sees Edit and the player doesn't;
    • an edit saves, survives a reload and shows in the book;
    • a Directors-only block never reaches the player's JSON;
    • the player gets 403 on all 10 editor routes, with a valid CSRF token;
    • a house chapter shows in the player's "House rules" part;
    • "Go back" restores the original;
    • an on-disk package change shows the banner, and Keep mine clears it;
    • the export zip has 18 chapters that parse;
    • 390px tabs work with no horizontal scroll.
    • The run found one bug: an empty new page was refused. It is fixed in the second commit, with a test.
  • Operator live check after deploy (below)

Live check for Key Maker, after deploy and the Draw Steel update:

  1. As owner, open the Rules page and press Edit, then change a sentence. Working: "All changes saved", and Done shows your sentence. Broken: an error, or the old text.
  2. Tick "Directors only" on a block. In the player view (or as a player) it's gone.
  3. Press "+ Add a house-rules chapter" and type a page. It shows at the end of the book for players.

Tenet self-check

  • T-B1 security: permission check on every route, scoped SQL, strict input decoding, 1 MB request cap, 8 MB per-book storage cap, escaped output, same-origin navigation only
  • T-B2 plugin isolation: no foundry literals, no cross-plugin imports
  • T-B3 production UI: saving / saved / error states, loading and 403 states, labels and aria-pressed, focus kept while typing, no animation
  • T-B4 dual-audience docs: docs/system-rulebook-book.md and internal/systems/.ai.md updated

🤖 Generated with Claude Code

https://claude.ai/code/session_01Cce6X7rscrMeCUBituVyNe


Generated by Claude Code

claude added 2 commits October 3, 2026 03:52
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
Comment thread static/js/widgets/rulebook_editor.js Fixed
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cce6X7rscrMeCUBituVyNe
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Rulebook: form editor so non-programmers can write a system's book Idea (parked): house rules as part of the Rulebook

3 participants