Lane 3 completions: the recap line, never-expiring invites, chapter rules, import errors, the bin's real grace period (D91, D92, #773, #781, #729) #802

Merged
claude-bot merged 6 commits from build/lane3-completions into main 2026-09-24 04:07:33 +00:00
Collaborator

The owner-decided completions to lane 3's pages (#729). Closes #773. Closes #781.

What changed

  • D91, the recap line. A played session's row on the sessions list now shows its recap. It is the same first-sentence cut Home's card uses, computed once in home_service and reused, and the list's filter matches it. Before this, the row always said "No recap yet", because the list never sent the summary.

  • D92, invites that never expire. InviteCreate.expires_in_days accepts null, and create_invite then leaves expires_at empty, which every expiry check already treats as live for ever. The invite form gains a Never choice beside 1, 7 and 30 days. No migration was needed: campaign_invites.expires_at was already nullable.

  • #773, the story's chapters. chapters() (CampaignStoryline.jsx) and the wiki front's body renderer drop a paragraph that is only a rule, rather than printing a literal --- between chapters.

  • #781, import errors. An importer's own AdapterError message is written for a GM, so it passes through unchanged ("Record 1 has no name."). The raw library text is fixed where it's created:

    • fetch_pack's httpx error.
    • The orcbrew file-read and EDN-parse errors.
    • The foundry bad-zip and bad-entry errors.
    • The fivetools file-read error.

    Each now says what's wrong with the file in plain words and logs the real exception on the server.

  • The bin's grace period comes from the server. CampaignResponse carries trash_retention_days (from settings_service.get_trash_retention_days, floored at 7), and Archive, export & delete reads it instead of a hard-coded 30.

Checks, on the tree merged with main (a028f55)

  • Backend: 4101 passed, 13 skipped. Ruff 0.4.4 format and check are clean.
    • Ten synthetic-session guard tests errored during that run. The machine was out of memory while it ran three suites at once. The same file passes on its own (10/10), and this branch doesn't touch the harness.
  • Frontend: 142 files, 2000 tests passing. Lint: 0 errors, and the warning count is unchanged at 228.

🤖 Generated with Claude Code

The owner-decided completions to lane 3's pages (#729). Closes #773. Closes #781. ## What changed - **D91, the recap line.** A played session's row on the sessions list now shows its recap. It is the same first-sentence cut Home's card uses, computed once in `home_service` and reused, and the list's filter matches it. Before this, the row always said "No recap yet", because the list never sent the summary. - **D92, invites that never expire.** `InviteCreate.expires_in_days` accepts `null`, and `create_invite` then leaves `expires_at` empty, which every expiry check already treats as live for ever. The invite form gains a **Never** choice beside 1, 7 and 30 days. No migration was needed: `campaign_invites.expires_at` was already nullable. - **#773, the story's chapters.** `chapters()` (`CampaignStoryline.jsx`) and the wiki front's body renderer drop a paragraph that is only a rule, rather than printing a literal `---` between chapters. - **#781, import errors.** An importer's own `AdapterError` message is written for a GM, so it passes through unchanged ("Record 1 has no name."). The raw library text is fixed where it's created: - `fetch_pack`'s httpx error. - The `orcbrew` file-read and EDN-parse errors. - The `foundry` bad-zip and bad-entry errors. - The `fivetools` file-read error. Each now says what's wrong with the file in plain words and logs the real exception on the server. - **The bin's grace period comes from the server.** `CampaignResponse` carries `trash_retention_days` (from `settings_service.get_trash_retention_days`, floored at 7), and Archive, export & delete reads it instead of a hard-coded 30. ## Checks, on the tree merged with main (a028f55) - **Backend:** 4101 passed, 13 skipped. Ruff 0.4.4 format and check are clean. - Ten synthetic-session guard tests errored during that run. The machine was out of memory while it ran three suites at once. The same file passes on its own (10/10), and this branch doesn't touch the harness. - **Frontend:** 142 files, 2000 tests passing. Lint: 0 errors, and the warning count is unchanged at 228. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Five small, owner-decided completions to lane 3's pages:

- D91: a played session's row carries `recap_line`, the same first-sentence
  cut Home's card shows, computed once in home_service and reused; the list's
  filter matches it. The row previously always said "No recap yet" because
  the list never sent `summary` at all.
- D92: an invite link can be set never to expire. `InviteCreate.expires_in_days`
  is now `int | None`; `create_invite` leaves `expires_at` null for "never",
  which `invite_is_live` and every other expiry check already treated as live
  forever. The frontend adds a "Never" choice beside 1/7/30 days.
- #773: `chapters()` (CampaignStoryline.jsx) and WikiFront's body renderer both
  drop a paragraph that is only rule characters (a markdown thematic break),
  instead of printing a literal `---` between chapters.
- #781: three sites in import_service.py that built a 400 detail from
  `str(exc)` now say what went wrong with the file in plain, library-agnostic
  words and log the real exception server-side.
- The bin's 30-day retention is now read from the server: `CampaignResponse`
  carries `trash_retention_days` (settings_service.get_trash_retention_days,
  floored at 7), and Archive, export & delete reads it instead of a hardcoded
  constant.

No migration needed: `campaign_invites.expires_at` was already nullable
(migration d3e4f5a6b7c8), with NULL meaning "never expires" already wired
through invite_is_live — only create_invite needed to start writing it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Revert the genericising at import_service.py's two AdapterError boundary
sites (read_pack, import_from_path): AdapterError's message is written for
the importer (per its own docstring) and now passes through unchanged again,
e.g. "Record 1 has no name." — exactly what a GM needs to fix their file.
The logger.warning() calls added at both sites stay, so the exception is
still on record.

Keep the fetch_pack fix (httpx.HTTPError's str() is genuinely library text,
never meant for a person) — unchanged from the first commit.

Fixed the real leak at its source instead: five AdapterError raises across
three adapters embedded a raw caught exception's text directly in their
message (searched every adapter for `{exc}`/`str(exc)`/`repr(`/`%s` with an
exception):

- orcbrew.py: OSError reading the file, and edn_format's own exception
  (several possible types) parsing EDN.
- foundry.py: zipfile.BadZipFile opening the archive, and
  (BadZipFile, OSError, EOFError) reading one entry from it.
- fivetools.py: OSError reading the file.

Each now says which file/entry and what to do, and logs the real exception.

Tests: reverted the five existing assertions this touched back to their
original specific wording (test_content_pack_import.py x3,
test_content_pack_task.py); adjusted the two import_service boundary tests
to assert the adapter's own message passes through and is logged, instead of
asserting a generic message; kept the fetch_pack no-library-text test
unchanged; added one no-library-text-and-logged test per fixed adapter
(two for orcbrew, two for foundry — one per site — one for fivetools).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The previous entry described genericising at import_service.py's boundary
sites, which the follow-up commit reverted in favour of fixing the five
adapter call sites that actually embedded raw exception text, while leaving
every reader-facing AdapterError message unchanged.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Merge remote-tracking branch 'origin/main' into build/lane3-completions
Some checks failed
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 1m49s
CI / Bot tests and audit (pull_request) Successful in 1m46s
CI / Backend lint (ruff) (pull_request) Successful in 51s
CI / Feedback site tests (pull_request) Successful in 41s
CI / Bot/backend version sync (pull_request) Successful in 44s
CI / Does this change touch the recording path? (pull_request) Successful in 15s
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Has been skipped
CI / Docker image build (pull_request) Successful in 55s
CI / Frontend tests, audit, and build (pull_request) Successful in 6m11s
CI / End-to-end journeys (desktop, phone, keyboard-only) (pull_request) Successful in 13m51s
CI / Backend migration, tests, and audit (pull_request) Has been cancelled
541e9217d8
# Conflicts:
#	CHANGELOG.md
Merge remote-tracking branch 'origin/main' into build/lane3-completions
Some checks failed
CI / Frontend tests, audit, and build (pull_request) Has been cancelled
CI / Backend migration, tests, and audit (pull_request) Has been cancelled
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Has been cancelled
CI / Bot tests and audit (pull_request) Has been cancelled
CI / Backend lint (ruff) (pull_request) Has been cancelled
CI / Feedback site tests (pull_request) Has been cancelled
CI / Bot/backend version sync (pull_request) Has been cancelled
CI / Docker image build (pull_request) Has been cancelled
CI / Does this change touch the recording path? (pull_request) Has been cancelled
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Has been cancelled
CI / End-to-end journeys (desktop, phone, keyboard-only) (pull_request) Has been cancelled
a09acc9edb
# Conflicts:
#	CHANGELOG.md
Follows #793's sweep, for colours outside that PR's chip/badge scope:

- About twenty full-width error/success/warning banners in Admin,
  CampaignDetail and WikiFront had no light-mode pair (`bg-red-900/40 …
  text-red-300` and similar `/20`-`/60` variants), so light mode showed pale
  text on a translucent dark wash. Each now has a light pair (e.g. `bg-red-50
  border-red-200 text-red-800`) with the original dark look kept behind
  `dark:`.
- Bare status words with no light pair (`text-green-400`, `text-red-400`)
  in QuestLog, CampaignPlanning's arc/thread status legends, LootLedger's
  gold amounts, the wiki's entryTypes.js colour map, and VotingGrid's
  winning-slot score now have one too. WikiArticle.jsx's inline wiki-link
  colour picks its class from entryTypes.js and used `.split(" ")[0]`,
  which would have silently dropped the new dark: token; it now grabs both
  text tokens instead.
- VotingGrid's base grey for an unvoted cell, `text-gray-500
  dark:text-gray-600`, measured 2.66:1 in dark mode; it's now
  `dark:text-gray-400` (7.73:1), the same pair #793 established elsewhere.

Every pair was checked by computing WCAG contrast from Tailwind v4's oklch
palette in both modes, against the surface it actually sits on; translucent
banners were composited over the page background first. Colour meaning is
unchanged throughout — no behaviour or layout changes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
# Conflicts:
#	CHANGELOG.md
#	webapp/frontend/src/components/VotingGrid.jsx
fix(frontend): the wiki front's and the Loot Ledger's red and amber text get a light pair (#794)
All checks were successful
CI / Backend lint (ruff) (pull_request) Successful in 1m9s
CI / Bot tests and audit (pull_request) Successful in 2m8s
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 2m44s
CI / Bot/backend version sync (pull_request) Successful in 29s
CI / Does this change touch the recording path? (pull_request) Successful in 21s
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Has been skipped
CI / Docker image build (pull_request) Successful in 1m0s
CI / Feedback site tests (pull_request) Successful in 1m57s
CI / Frontend tests, audit, and build (pull_request) Successful in 7m5s
CI / End-to-end journeys (desktop, phone, keyboard-only) (pull_request) Successful in 7m23s
CI / Backend migration, tests, and audit (pull_request) Successful in 28m13s
8c71c2c260
Bare text-red-400 on white is about 3:1. The error lines and delete
buttons become text-red-700 dark:text-red-400 (6.5:1 light, 7.0:1
dark). The wiki front's amber-600 text and its white-on-amber-600 count
badges move to amber-700 (4.9:1 even on amber-50).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merge remote-tracking branch 'origin/main' into build/794-dark-only
All checks were successful
CI / Backend lint (ruff) (pull_request) Successful in 34s
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 1m3s
CI / Feedback site tests (pull_request) Successful in 43s
CI / Does this change touch the recording path? (pull_request) Successful in 12s
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Has been skipped
CI / Bot/backend version sync (pull_request) Successful in 58s
CI / Bot tests and audit (pull_request) Successful in 1m58s
CI / Docker image build (pull_request) Successful in 3m49s
CI / Frontend tests, audit, and build (pull_request) Successful in 6m52s
CI / End-to-end journeys (desktop, phone, keyboard-only) (pull_request) Successful in 14m5s
CI / Backend migration, tests, and audit (pull_request) Successful in 24m54s
8951994425
# Conflicts:
#	CHANGELOG.md
Merge remote-tracking branch 'origin/build/794-dark-only' into build/lane3-completions
All checks were successful
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 58s
CI / Bot tests and audit (pull_request) Successful in 2m0s
CI / Bot/backend version sync (pull_request) Successful in 28s
CI / Backend lint (ruff) (pull_request) Successful in 50s
CI / Feedback site tests (pull_request) Successful in 50s
CI / Does this change touch the recording path? (pull_request) Successful in 15s
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Has been skipped
CI / Frontend tests, audit, and build (pull_request) Successful in 5m11s
CI / Docker image build (pull_request) Successful in 3m28s
CI / End-to-end journeys (desktop, phone, keyboard-only) (pull_request) Successful in 13m56s
CI / Backend migration, tests, and audit (pull_request) Successful in 24m41s
5e192e30e5
# Conflicts:
#	CHANGELOG.md
Sign in to join this conversation.
No description provided.