Commit Graph

72 Commits

Author SHA1 Message Date
17160f9d9b Add version number + About dialog with changelog (v1)
Start tracking a version number and a player-facing changelog, surfaced from
the ☰ menu via a new About item.

- frontend/src/lib/changelog.js: VERSION (bump every commit) + CHANGELOG.
- AboutModal.svelte: shows the version and changelog using the shared modal CSS;
  closes via ×, backdrop click, or Escape.
- CornerMenu.svelte: new 'ℹ️ About' menu item, modal rendered outside the menu's
  stacking context so it overlays correctly on every page.
- AGENTS.md: documents the bump-every-commit / log-only-user-facing convention.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 09:20:47 -07:00
44f7fd939b Home: list Rejoin a Crew above Muster, with a gap between
Returning players are the common case, so surface their saved crews first.
The two glass panels had no margin between them; add spacing on the
Rejoin box so it sits clear of the Muster box below it.

Also tidies the TODO 'Polish' section (renamed from 'UI Polish'; drops the
now-committed Rank-3 bonus 'Gameplay gaps' entry).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 09:14:27 -07:00
5b5087ac23 Track files left untracked by recent commits
RankBonusModal.svelte, scene/ObstacleItem.svelte, and lib/tooltip.js were created during the 2026-06-15 scene-dashboard and Rank-3-bonus work and are imported by already-committed components (Dashboard, ChallengePanel, ObstacleBoard, Card, components.css), but were never git-added — so HEAD did not build on a clean checkout.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 09:10:10 -07:00
daeac91d07 Dead code cleanups 2026-06-15 07:46:32 -07:00
045857a8fd Refine crew column: names, Captain label, slide-out objectives
Follow-up tweaks to the scene dashboard:
- Bubbles now show the human player name alongside the Pi-Rat name.
- The Captain is named "Captain <name>" (the "Recruit" honorific swapped,
  or "Captain" prepended once Named) instead of a 🛞 icon — applied on
  bubbles and the character-sheet/duel headings via new crewLabel/
  captainName helpers in lib/cards.js.
- "Objectives" → "Crew Objectives" moved to the end of the roster as a
  left-aligned toggle with a right-aligned X/3 completed counter; it now
  slides the detailed list open in place (svelte/transition slide)
  instead of opening a modal.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 20:12:44 -07:00
2ea91c066a Refresh the scene dashboard around a crew bubble column
Replace the roster banner and the standalone character-sheet panel with
a left-hand column of crew bubbles. Each bubble shows the player's role
icon, name, rank, current hand size, and a 🛞 for the Captain; clicking
one pops out that player's character sheet as a modal.

- New CrewColumn.svelte: bubbles (Pi-Rats first, Deep last) + an
  Objectives button that opens a crew-objectives popup.
- CharacterSheet.svelte is now a target-driven modal: description,
  likes/hates, and personal objectives for anyone; secret J/Q/K only on
  your own sheet; the Duel UI (no "Challenge whom?" — target is fixed)
  only when a living Pi-Rat views another living Pi-Rat.
- ScenePhase.svelte → 3-column layout (crew | obstacles | hand/deep);
  crew column collapses to a horizontal strip on narrow screens.
- Shared .modal-backdrop/.modal-box/.modal-close moved into components.css;
  removed the now-dead roster-banner and captain-badge styles.

Backend unchanged — the state blob already carries hands, roles, ranks,
objectives, and the captain.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 20:03:41 -07:00
8614a6c820 Collapse between-scenes upkeep into a single panel
During between_scenes there's now just one centered panel with the
voting controls, nomination progress, and Crew Rank Ledger. The separate
"Resting Deep Upkeep" panel (which only said hand refresh would happen
later) is rendered solely during deep_upkeep, where the actual discard/
redraw happens. Moved the rank ledger into the voting panel and added a
.between-grid.single layout.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 10:57:55 -07:00
44bbb88836 Add gat-description modal when a Pi-Rat earns a Gat
Mirrors the name modal: earning the Gat objective sets
Player.needs_gat_description, and GatModal.svelte (rendered by Dashboard
across all phases) prompts for a one-of-a-kind Gat description, stored in
Player.gat_description and shown on the character sheet. Like a stolen
Name, the Gat's description travels with it through Gat Tax transfers.

- Player.gat_description / needs_gat_description (+ migration)
- toggle_objective personal_1 sets/clears the prompt
- crud_challenge gat-tax paths move the description with the Gat
- set-gat-description route; CharacterSheet displays it

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 09:46:02 -07:00
7825ad2cca Toast new events into the Event Log button
When the Event Log is collapsed, a freshly-arrived event now flashes as
a toast anchored above the button, then animates down and fades into it.
Suppressed on the initial state load and on rollback reseeds so it only
fires for genuinely new events; clicking the toast opens the log.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 09:39:54 -07:00
473fd74a00 Point Admin back button at the active game, not the join screen
The Admin page's back button now returns to this browser's own Pi-Rat
dashboard for the game (looked up from the saved sessions), labelled
"← Back to Game". Falls back to the join page only when this browser has
no saved session for the game.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 09:33:53 -07:00
ceaf88ef22 Stop auto-assigning the Captain when a ship is stolen
Completing Crew Objective 1 (Steal a Ship) no longer anoints the
highest-ranked Pi-Rat as Captain (nor vacates the seat when the ship is
lost). Captaincy is now earned: the crew chooses one via the Captaincy
UI / set_captain and Crew Objective 2. Decoupled captaincy from crew_1
in toggle_objective, dropped the now-unused random import, and updated
test_captaincy_and_hand_sizes accordingly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 09:30:46 -07:00
39dc07d6b6 Split Deep challenge stakes into success and failure fields
The Deep now states what happens on success and on failure as two
separate fields when calling a Challenge, instead of one free-text
stakes blob. ChallengePanel shows them as " On success" / " On
failure"; the event log lists both.

- Challenge.stakes_success / stakes_failure (+ migration); the old
  single `stakes` column is kept (vestigial) so in-scene rollback
  snapshots taken before the upgrade still deserialize, and ChallengePanel
  falls back to it for any pre-existing challenge.
- create_challenge / route / DeepControlPanel updated; tests adjusted.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 09:28:38 -07:00
5a8919d967 Display rank-up vote winner on the post-voting upkeep screen
The post-voting (deep_upkeep) screen now names who won the rank-up vote
and their new rank, instead of just "Voting complete!". Adds
Game.last_rank_up_player_id (+ migration), set when votes are tallied
and cleared when the next scene begins; falls back to a "no one ranked
up" message when there were no votes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 09:23:18 -07:00
b37b655118 Confirm hand refresh visually in Deep Upkeep
After a resting Deep player confirms their hand refresh, replace the
discard/keep drag editor with a confirmation panel showing the freshly
drawn hand and a "Waiting for the rest of the crew" message, instead of
re-rendering the editable drag UI (which made it look like nothing
happened). Also stops nagging "please complete your Hand Refresh" once
they're done. Visible whenever they remain in deep_upkeep waiting on
other resting Deep players.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 09:19:58 -07:00
bfe982251e Add teaching mode: admin can seed themselves as scene-1 Deep
An admin can now opt into "teaching mode" from the lobby, seeding
themselves as the Rank-3 player who must play the Deep in the first
scene (instead of leaving it to the random starting-rank roll), so they
can run the table for new crews.

- Game.teaching_deep_player_id (+ Alembic migration)
- maybe_finish_assign_techniques honors it before rolling ranks, so both
  the normal flow and the dev-mode skip path respect it
- Admin-gated POST .../teaching-mode toggle (pre-rank phases only)
- Lobby checkbox UI, greyed out if another admin already volunteered

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 09:14:58 -07:00
5a9d712c95 Add Cancel button to character-creation Revise flows
Clicking Revise on basic records, techniques, or J/Q/K assignment now
offers a Cancel button that throws away any edits made since Revise and
restores the previously-saved values. Basic-records cancel is a pure
client-side toggle; techniques/face cancel re-submit a snapshot taken at
Revise time (since Revise immediately unsubmits/unassigns server-side).
Also added Cancel to the recruit-creation basic-records revise.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 09:09:24 -07:00
45fefc0c71 Add modal name popup when a Pi-Rat earns a Name
When a Pi-Rat completes their second objective and ranks up, they now
get a blocking modal popup (NameModal.svelte, rendered by Dashboard
across all phases) to claim their Pirate Name, rather than an optional
box buried in their character sheet. Removed the old inline prompt-box
and its handler from CharacterSheet.svelte.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 09:05:02 -07:00
f40724c9d2 Clean up ruff E701 warnings
Split multi-statement-on-one-line colon constructs in crud_scene.py
and routes_scene.py onto separate lines.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 09:00:44 -07:00
3bb4940df7 Hide deep's hand 2026-06-14 08:57:08 -07:00
55f4085d7a Add likes/hates to character sheet UI 2026-06-13 09:58:16 -07:00
56cdebdeda Fix off-by-one in rollback button placement
A rollback button on event E restores the state just after E. So the
button on the latest event was a no-op (you're already there), while the
scene-start event -- a valid earliest target -- correctly has one. The
visible set was effectively shifted one event late.

Hide the button on whichever event sits at the current position (the
rollback head if set, else the latest checkpoint, now supplied by the
backend as state.latest_checkpoint_id since the frontend's own reduce
over the async-populated accumulator wasn't reliably reactive). In live
play the latest event loses its redundant button; when rolled back, the
"you are here" event has none while earlier events show rollback and
later ones show redo.

Verified in a browser across live, rolled-back, and redo states.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 09:03:52 -07:00
6964aeabb6 Add undoable rollback/redo (Phase 2)
Rolling back is now non-destructive: instead of truncating the future,
it restores the target checkpoint's snapshot and points
Game.rollback_head_checkpoint_id at it. Later checkpoints/events are
kept and rendered greyed; rolling forward to one redoes the rollback
(same endpoint). The abandoned future is discarded only when a new
action is taken from a rolled-back state -- maintain_rollback_history
truncates it, clears the head, and bumps rollback_timeline_version,
now the sole trigger for the frontend log reset.

- Game.rollback_head_checkpoint_id (nullable; in SNAPSHOT_EXCLUDE) +
  Alembic migration.
- crud_rollback: rollback_to_checkpoint sets the head (None at latest);
  _discard_future + head-clear + version-bump moved into the next scene
  action's capture.
- EventLog.svelte: greyed future, redo (forward) affordance, a "rolled
  back" banner; dropped the now-misleading destructive confirm.
- Tests rewritten for head-pointer semantics (+ redo, action-after-
  rollback truncation, scene-end head clear); HTTP integration test
  extended to drive the full cycle over the wire.

Verified end-to-end in a browser: rollback greys the future and reverts
live state (captaincy/ranks), redo restores it, no errors.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 08:25:06 -07:00
0bce291ab9 Ignore Emacs autosave files 2026-06-13 06:04:53 -07:00
a7f174b214 Add game-state rollback (Phase 1)
Full-state JSON snapshots let a scene be rolled back to an earlier
action. All game state lives in game_id-scoped tables, so a checkpoint
is just a serialized dump of the gameplay rows and rollback restores
it -- sidestepping deterministic replay (the deck shuffle is
materialized into state and captured verbatim).

- Checkpoint table + GameEvent.checkpoint_id (tri-state) +
  Game.rollback_timeline_version (models.py, Alembic migration).
- crud_rollback.py: serialize/apply/capture/seal-purge/rollback.
- Capture is driven by the broadcast middleware: snapshot per action
  while in the `scene` phase, seal+purge otherwise. Confined to the
  current scene; older scenes' checkpoints are purged at scene end.
- POST /game/{gid}/player/{pid}/rollback (routes_rollback.py),
  server-enforced for Admins and Deep players.
- EventLog.svelte: per-event rollback buttons + timeline reconciliation.
- Remove the orphaned /scene/rollback per-card-play undo (dead code
  since d7f8483, never wired up; superseded by full-state rollback).

Phase 1 truncates the future immediately; the greyed/undoable redo is
deferred to Phase 2.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 06:02:56 -07:00
e7db19f27e Let players voluntarily leave a game
Adds a "🚪 Leave Game" option to the corner menu for every player, so someone
who's done can remove themselves instead of waiting for an admin to kick them.

leave_game reuses the kick mechanics (cards to discard, reference cleanup,
phase re-check) via a shared _remove_player helper; it logs a friendlier
"left the crew" event and, like kick, refuses the last Admin so the table
isn't orphaned. The self-service route POST /game/{gid}/player/{pid}/leave
lives next to join and authenticates on the player's own id (no admin key).
The handler confirms, forgets the saved session, and routes home.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 21:58:07 -07:00
428af784e3 Add player kicking for admins
Admins can now kick players from the Admin panel so a vanished player can't
stall the table. crud.kick_player removes a player mid-game: it drops their
captaincy, deletes their votes and any Challenge they're part of, and repairs
every other player's pointers to them (pending swap offers are cleared;
delegated Like/Hate questions and recruit technique slots are handed to a
present crewmate, or pool-filled when no one is left). The row is then deleted
— a kicked player's hand simply stops being "in play", so it returns to the
discard automatically. Finally recheck_phase_completion advances the phase if
the kicked player was the last thing it was waiting on.

Auth mirrors set-admin (admin-key on POST /api/game/{gid}/admin/kick). The one
guard is that the last remaining Admin can't be kicked, so the table is never
locked out of its own panel; a vanished creator can still be kicked once a
co-admin exists. The Admin panel gains a per-row Kick button (with a confirm),
shown as "—" for the last admin.

To support re-checking phase completion after a kick, each phase's
"everyone's done -> advance" gate was extracted into an idempotent maybe_*
helper, and recheck_phase_completion dispatches the right one per Game.phase.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 21:48:03 -07:00
d04e5013fa Streamline dead Pi-Rat rank voting
Dead and awaiting-recruit Pi-Rats can no longer be nominated to rank up
(they'd reset to Rank 1 anyway). submit_rank_vote rejects them, and the
upkeep dropdown only lists living, in-play crewmates via the shared
eligible_vote_nominees helper.

When a voter has no eligible nominees (e.g. a lone survivor whose only
crewmates are the previous Deep and a dead rat) they now skip voting
entirely and can ready up directly, instead of deadlocking — which is
why dead players were previously left nominable. The roster shows such
players as "No vote".

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 21:21:38 -07:00
345ef4088f Save active sessions in browser local storage
Joining a game now records the session (game ID, player ID, plus
player/rat/crew names) in localStorage via the new lib/sessions.js, and
the dashboard keeps it enriched as state loads (so the rat name tracks
earned Names). The home page lists saved sessions in a "Rejoin a Crew"
menu, most-recent first, alongside creating a new game.

Deleting an entry only touches localStorage, never the backend, and is
undoable: the row greys out with an Undo button and is dropped from
storage immediately, so a page refresh finalizes the removal.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 21:14:27 -07:00
d68333ec05 Add crew/player/rat name to page title
Each routed page now sets a descriptive document.title so tabs and
browser history entries are distinguishable: the dashboard shows the
Pi-Rat (and human player) name plus crew, the admin panel shows
"Admin · {crew}", the join page is labeled, and home resets to the
app name. New lib/title.js helper composes "{parts} — Rats with Gats".

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 21:09:36 -07:00
fb17c632b0 Add invite link to Admin panel
The Admin panel now shows the crew's join link (origin/#/game/{id}/join)
with Open and Copy buttons, so an admin can invite new players without
returning to the lobby. Matches the existing rejoin-link copy pattern.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 21:07:28 -07:00
e502155af7 More detailed card tooltips
Card tooltips now show what a card represents *as* an Obstacle (the
rulebook obstacle table entry) in addition to its suit theme, which is
relevant when challenging another Pi-Rat. The obstacle tables are served
from the backend via GET /api/obstacles (single source of truth in
cards.py) and cached client-side in cards.js, with a retry so a transient
first-load failure doesn't silently disable the tooltips.

Enriched everywhere cards appear in the challenge flow: hand/board cards
(Card.svelte), PvP defend buttons and the temporary-obstacle line
(ChallengePanel), and the duel card selector, which also shows an
inline description of what the chosen card becomes as an Obstacle.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 21:05:20 -07:00
952b1be872 Uncommit feature in character creation
Players can now unlock and revise any reasonably revisable choice while
character creation is ongoing: basic Rat Records (also in recruit
creation), submitted techniques (until the swap phase begins), J/Q/K
face-card assignments (until everyone is ready), and Like/Hate answers
written for crewmates. New endpoints unsubmit-techniques and
unassign-face-techniques clear the locked-in state server-side so the
phase machine waits for the revision.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 17:05:52 -07:00
ba32c96b7b Light mode update 2026-06-12 16:57:07 -07:00
fa37cffe47 Technique swapping implementation 2026-06-12 16:46:03 -07:00
121f2f7498 Player minimum of 3 to start a game or scene
Enforced server-side in lobby/start and confirm_scene_setup via
crud_base.has_min_players; Dev Mode bypasses it so local single-player
testing still works. Lobby and scene-setup UIs show a player-count
message instead of the start button when below the minimum.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 16:16:49 -07:00
5ad9c9db07 Multiple admins
Adds Player.is_admin (creator starts with it; backfilled by migration).
Admin-key-authenticated POST /admin/set-admin grants/revokes it from the
admin panel; the creator's privileges can't be revoked. Admin-gated
backend routes (dev mode, skip character creation) and frontend controls
(corner menu, lobby start, scene start, end story) now check is_admin
instead of is_creator, and admins get the admin_key in their state blob
so granted players can open the admin panel.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 16:03:14 -07:00
e24e6d3b46 Add rules button to join page 2026-06-12 12:18:41 -07:00
3395b127b4 Dev mode revamp 2026-06-12 11:50:34 -07:00
f61201144f Dead Pi-Rat reroll rework 2026-06-12 11:32:15 -07:00
df7a0cc83f Add Discord link to rules 2026-06-12 10:03:16 -07:00
4e262a5345 Update TODOs 2026-06-12 09:58:54 -07:00
5f647d1940 Commit .claude/launch.json, gitignore local Claude settings
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 07:17:25 -07:00
96eeb442fd Database migrations 2026-06-12 07:14:50 -07:00
0535c68647 Fix flake with new hash 2026-06-12 07:01:55 -07:00
0744893e7b Re-theme the app: token-based CSS and a full face lift
CSS refactor: every color, font, radius, and shadow is now defined once
in theme.css; all other styles derive translucent variants with
color-mix(), so re-theming means editing one file. Removed the
duplicate core.css/style.css import chains, dead styles, and the
Bootstrap/Tailwind orphan classes that were silently unstyled
(the main reason the splash page looked broken).

New "Lantern & Brine" look: brass and parchment on a lantern-lit
ink-navy night, Pirata One wordmark with Alegreya SC/Sans body fonts
(self-hosted via fontsource — they were previously referenced but
never loaded), parchment-faced playing cards, and a rebuilt splash
page. Inline color styles in components were replaced with semantic
classes (prompt-box, notice-banner, status chips, etc.), the rulebook's
embedded palette was updated to match, and the leftover Vite favicon
and "frontend" page title were replaced.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 06:50:00 -07:00
ba6bf35579 Websockets instead of polling 2026-06-12 06:19:22 -07:00
b72aea9747 Combine Rules and Admin corner links into one upper-right menu
Replace the fixed Rules link (upper-left) and the creator-only Admin
link (upper-right) with a single collapsible ☰ Menu in the upper right,
leaving the event log alone in its corner. CornerMenu always lists the
Rules link; pages can contribute context-specific entries through the
extraMenuLinks store, which Dashboard uses for the creator-only Admin
link. This gives future misc sub-pages a home without eating more
screen corners.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 01:47:56 -07:00
4fcd2b693c Make the rulebook's obstacle reference section collapsible
The Obstacle List & Type Charts section is long reference material the
app already implements, so wrap it in a <details> element. A small
script expands it when the TOC link or a deep link targets #obstacles.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 01:42:15 -07:00
683c60fff9 Serve the rulebook as a static HTML page from FastAPI
Replace the SPA Rules route with a self-contained src/pirats/rules.html
served at GET /rules (registered before the SPA mount, included in
package data for the Nix build). Plain anchor links replace the
scrollIntoView workaround since there's no hash router to fight. The
corner Rules link and splash-page link now point at /rules, and the
Vite dev server proxies /rules to the backend like /api.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 01:35:04 -07:00
69e7d24e10 Add HTML rulebook page with always-available Rules link
New /#/rules route renders the full rulebook transcribed from
"Rats with Gats beta.pdf" (intro, character creation, gameplay,
obstacle charts, challenge resolution, example of play, and setting
info), with an in-page table of contents. A fixed corner link in
App.svelte opens it in a new tab from every page, and the splash
page's previously broken /rules link now points at the new route.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 01:30:00 -07:00