[Backend][UX] A session has a number (D86): given at creation, frozen, gaps allowed #760

Closed
opened 2026-09-22 21:17:02 +00:00 by claude-bot · 1 comment
Collaborator

Decided by the owner on 2026-09-22 as D86, answering a question lane 1 (#727) raised and left open.

What is missing

There is no session numbering in Quest Board — no column, no field, no concept anywhere. Yet the prototype says Session 14 on the Contents, the breadcrumb, the bell and the live bar, and lane 1 could only put the session's title in all four. The test fiction titles its sessions "Session 13: …" and "Session 14: …" so the journeys read right, which is a workaround, not a feature.

The decision

A number, given when the session is created, frozen, and never reused.

  • Numbering is per campaign, starting at 1.
  • It is assigned at creation, not at completion, because the breadcrumb, the bell and the live bar all show it for a session that has not been played yet.
  • It never changes. A cancelled session keeps its number, so the sequence has gaps: cancel one and a campaign reads 12, 13, 15.

That gap is the cost, and it was chosen deliberately over the alternative. The nth-session-by-date is not stable — cancel one and every later session renumbers — and someone will have written "session 12" in the wiki, a recap or a Discord message by then. A number that skips is honest; a number that shifts is a lie about something already written down.

Why it is worth a column at all

The four places the drawing uses it are tight — a breadcrumb, a bell row, the live bar, a Contents entry — and they chose a short, stable token for exactly that reason. "The Amberwake Gate, Part Two" fits none of them. It also gives the wiki, recaps and Discord posts something stable to cite, which a title (editable at any time) is not.

What the work is

  • A column and a backfill. sessions.number, integer, not null, unique per campaign. Backfill existing sessions in created_at order per campaign — that is the closest thing to "the order they were made" that the data still holds, and it is worth saying in the migration that it is a reconstruction, not a record.
  • Assignment at creation, in the one place sessions are created, and safe against two sessions being created at once (a unique constraint on (campaign_id, number) plus a retry, rather than a read-then-write race).
  • Exposure: the session API, GET /api/me/home, /api/me/attention, the campaign's live endpoint and search results — wherever a session is named to a person.
  • The UI, in the four places the drawing shows it, and anywhere else lane 4 finds when it builds the session surfaces.
  • Import. POST /api/campaigns/import creates sessions; they need numbers too, in the imported order.

Where it lives in the redesign

SH-1 (the app bar's breadcrumb and the live bar), SH-2/SH-3 (the Contents), HOME-1 (Needs you and the campaign cards), SESS-1 to SESS-7 (a session's own pages), TABLE-1 (the GM's face at the table). Admin and GM alike; used on a phone, which is part of why the short token matters.

The catalogue's decision table carries D86; this issue is the work.

Sequencing

Lane 4 (#730, sessions) is the natural home — it owns SESS-1 to SESS-7 and will be rewriting every surface that shows a session's name. Doing the column ahead of it, as its server PR, means lane 4 builds against a number that already exists rather than retrofitting one. Lane 3 (#729, the campaign core) comes first and shows sessions in a list, so if that lane lands before this, its list shows titles until this does.

Nothing is blocked either way: today's behaviour (the title) is correct-looking and stays until the number exists.

🤖 Generated with Claude Code

**Decided by the owner on 2026-09-22 as D86**, answering a question lane 1 (#727) raised and left open. ## What is missing There is **no session numbering in Quest Board** — no column, no field, no concept anywhere. Yet the prototype says *Session 14* on the Contents, the breadcrumb, the bell and the live bar, and lane 1 could only put the session's **title** in all four. The test fiction titles its sessions "Session 13: …" and "Session 14: …" so the journeys read right, which is a workaround, not a feature. ## The decision **A number, given when the session is created, frozen, and never reused.** - Numbering is **per campaign**, starting at 1. - It is assigned **at creation**, not at completion, because the breadcrumb, the bell and the live bar all show it for a session that has not been played yet. - It **never changes**. A cancelled session keeps its number, so the sequence has gaps: cancel one and a campaign reads 12, 13, 15. That gap is the cost, and it was chosen deliberately over the alternative. The *n*th-session-by-date is not stable — cancel one and every later session renumbers — and someone will have written "session 12" in the wiki, a recap or a Discord message by then. **A number that skips is honest; a number that shifts is a lie about something already written down.** ## Why it is worth a column at all The four places the drawing uses it are tight — a breadcrumb, a bell row, the live bar, a Contents entry — and they chose a short, stable token for exactly that reason. "The Amberwake Gate, Part Two" fits none of them. It also gives the wiki, recaps and Discord posts something stable to cite, which a title (editable at any time) is not. ## What the work is - **A column and a backfill.** `sessions.number`, integer, not null, unique per campaign. Backfill existing sessions in `created_at` order per campaign — that is the closest thing to "the order they were made" that the data still holds, and it is worth saying in the migration that it is a reconstruction, not a record. - **Assignment at creation**, in the one place sessions are created, and safe against two sessions being created at once (a unique constraint on `(campaign_id, number)` plus a retry, rather than a read-then-write race). - **Exposure**: the session API, `GET /api/me/home`, `/api/me/attention`, the campaign's live endpoint and search results — wherever a session is named to a person. - **The UI**, in the four places the drawing shows it, and anywhere else lane 4 finds when it builds the session surfaces. - **Import.** `POST /api/campaigns/import` creates sessions; they need numbers too, in the imported order. ## Where it lives in the redesign `SH-1` (the app bar's breadcrumb and the live bar), `SH-2`/`SH-3` (the Contents), `HOME-1` (Needs you and the campaign cards), `SESS-1` to `SESS-7` (a session's own pages), `TABLE-1` (the GM's face at the table). Admin and GM alike; used on a phone, which is part of why the short token matters. The catalogue's decision table carries D86; this issue is the work. ## Sequencing **Lane 4 (#730, sessions) is the natural home** — it owns `SESS-1` to `SESS-7` and will be rewriting every surface that shows a session's name. Doing the column ahead of it, as its server PR, means lane 4 builds against a number that already exists rather than retrofitting one. Lane 3 (#729, the campaign core) comes first and shows sessions in a list, so if that lane lands before this, its list shows titles until this does. Nothing is blocked either way: today's behaviour (the title) is correct-looking and stays until the number exists. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Author
Collaborator

Done in #807 (lane 4's first PR, #730).

  • Migration f3a4b5c6d7e8: sessions.number (NOT NULL, unique per campaign), plus a per-campaign counter, so a number is never reused, even after the newest session is deleted.
    • The backfill orders by created_at and breaks ties by when each session happened, which handles an import's shared timestamp.
    • Every creation path allocates, and a flush hook numbers anything else created through the ORM.
  • Where "Session 14" shows: the breadcrumb, tab title, Contents, live bar, bell, Home, This week and the Sessions list, through sessionLabel(). The title stays the heading.
  • Import and export carry the number. /api/bot/* responses gain it additively, with no contract bump.
  • Fixture titles lost their "Session N: " prefixes, and the journeys now assert the real numbers.
  • Not yet: the Storyline page, the Search panel and MergeNotes' options don't draw the number; lane 4's page PRs pick them up.

🤖 Generated with Claude Code

Done in #807 (lane 4's first PR, #730). - **Migration `f3a4b5c6d7e8`:** `sessions.number` (NOT NULL, unique per campaign), plus a per-campaign counter, so a number is never reused, even after the newest session is deleted. - The backfill orders by `created_at` and breaks ties by when each session happened, which handles an import's shared timestamp. - Every creation path allocates, and a flush hook numbers anything else created through the ORM. - **Where "Session 14" shows:** the breadcrumb, tab title, Contents, live bar, bell, Home, This week and the Sessions list, through `sessionLabel()`. The title stays the heading. - **Import and export** carry the number. `/api/bot/*` responses gain it additively, with no contract bump. - **Fixture titles** lost their "Session N: " prefixes, and the journeys now assert the real numbers. - **Not yet:** the Storyline page, the Search panel and MergeNotes' options don't draw the number; lane 4's page PRs pick them up. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
rbrooks/Quest-Board#760
No description provided.