The party sheets in assets/characters/ changed: Darrah_Oakwanderer.pdf is
gone, replaced by Darragh Fermwyr (same halfling ranger, renamed and
updated), Owayn Fermwyr (stout halfling fighter, BJ) and Dunnchadh
(ghostwise halfling druid, Circle of the Blighted, Rick).
- app:character:seed now carries those three sheets, read from the PDFs'
form fields. Darragh's portrait is renamed to match his new name.
- New skill expertise field (double proficiency bonus), since Darragh's
Deft Explorer gives him +7 Stealth and the sheet had nowhere to show
it. Expertise implies proficiency; the sheet marks it with two dots,
the edit form gets a checkbox list next to skill proficiencies.
- Migration adds skill_expertise, backfilling existing rows with [] so
the NOT NULL JSON column works on tables that already have characters.
Tests: expertise math on the entity, and a catalog test that every seed
sheet hydrates into a Character that passes validation.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replaces the Characters placeholder with a real 5e character sheet
feature, following the same read-only-detail/edit-split/game-notes
pattern as NPCs and Enemies:
- Character entity: identity, combat vitals, six ability scores, saving
throw and skill proficiencies (computed modifiers, not stored), a
small attacks table, and the sheet's freeform boxes (features,
equipment, personality, appearance, backstory, allies, treasure).
- Detail page shows the portrait uncropped, then the sheet - genuine
<table>s for ability scores, skills and attacks, since that's the one
part of a character sheet that actually is a table; everything else
reuses the prose helpers from the enemy stat block. Game notes are
the only editable field here, same as Enemy/NPC.
- Edit page covers every field via CharacterType; the attack rows are
six plain inputs handled by hand (not a Symfony collection field),
since it's a short fixed-shape table, not a dynamic list.
- Portraits follow the NPC convention: assets/image/characters/<NameNo
Spaces>.png, computed path, not a DB column.
- Imported the example sheet from assets/characters/ (Darragh
Oakwanderer, halfling ranger) directly into the database.
Tests: entity math (ability/save/skill modifiers, passive perception,
portrait path), template rendering for show/edit/index (catches broken
Twig/macro/path()/asset() calls), and the login-gate redirect. 21/21
passing; schema validate clean.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Each gallery tile gets a small drag handle (top-left, ✱). Grab it and
drop it anywhere among the others - the tile moves live as you drag
over neighbours, then the new order is saved automatically on drop.
- Artwork gets a `position` column; the gallery lists by position (was
upload date). New uploads land after everything else.
- New POST /art/reorder endpoint takes an ordered list of ids (one
shared CSRF token, "reorder-art") and rewrites positions 1..n.
- Migration backfills position from the existing upload-date order via
ROW_NUMBER(), then drops the bootstrap default so it matches the
Doctrine mapping.
- New Stimulus controller (art-reorder): native HTML5 drag/drop, no
library. dragover live-reorders the DOM by comparing cursor x to the
hovered tile's midpoint; dragend posts the resulting id order and
shows a small "Saving order… / Order saved" status pill.
Verified: schema validate clean: a throwaway script exercised
persist+reload of a reordered set against the real DB, and a live
browser check dispatched real DragEvents through the actual Stimulus
controller and confirmed the exact POST payload it sent. phpunit 9/9.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Each chapter page gets a collapsible "Chapter notes" card at the top — a
free-text log the DM fills during play (the story the party tells after
the Crooked Goose, names given to NPCs, threads for next time). Saved
per chapter slug with a "saved <timestamp>" marker, same shape as the
Enemy/NPC game-notes field.
New: ChapterNote entity (slug-keyed, unique) + repository findOrCreate(),
ChapterNoteType form, chapter_note table migration (ENGINE = InnoDB
pinned). The storyline chapter route now also accepts POST to save.
Chapters stay hand-written Twig; only the mutable notes live in the DB.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Combatant snapshots armor_class from the enemy when added (like maxHp),
shown as an "AC n" pill next to the name for quick hit checks. Migration
backfills existing combatants from their enemy.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Encounter entity; Combatant now belongs to an encounter (ON DELETE
CASCADE). Migration adds encounter_id, rehoming any existing combatants.
- /tracker lists encounters (name, count, last updated) with a "New
encounter" form; /tracker/{id} is the HP tracker for one encounter.
- Encounters can be renamed, have their combatants cleared, or be deleted.
- The Enemies list gains an encounter picker next to "Add to tracker"
(existing encounter or "— New encounter —", which prompts for a name);
an encounter's "Add enemies" link pre-selects it via ?encounter=.
- Routes for combatant HP/remove moved under /tracker/combatant/ to keep
the numeric {id} for encounters.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- one running tracker (Combatant rows); no encounters to manage
- Enemies list gets a "Track" column (checkbox + count) and an
"Add to tracker" button; picking N of an enemy makes "Goblin 1..N",
numbering continues if some are already on the tracker
- /tracker: per-combatant current/max HP with an HP bar, Damage / Heal
by amount, remove one, "Clear all". HP is clamped to [0, max];
0 HP shows a "Down" badge. Stats are snapshotted from the Enemy on add.
- "Tracker" nav item; tracker_picker Stimulus controller; app.js gains a
data-format="pick" cell renderer
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Artwork entity (name, stored filename, original name, mime, size, date)
- /art gallery grid; /art/upload (name + image file, PNG/JPEG/GIF/WebP,
20M cap); /art/{id}/file streams the image behind auth; /art/{id}/delete
(CSRF) removes row + file
- files stored under var/uploads/art/ (git-ignored, on the php_var volume
in Docker), served through a controller so they stay login-gated
- "Art" nav item
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Enemy entity: full 5e stat block (size/type/subtype/alignment, AC, HP,
speed, six ability scores, CR/XP/prof, saves/skills/senses/languages,
damage & condition modifiers, traits/actions/bonus/reactions/legendary,
description, source) plus a DM game-notes field and updated_at.
- CreatureSize / CreatureType enums.
- /enemies: server-side DataTable (name, size, type, CR, AC, HP, notes),
"Add enemy" button.
- /enemies/{id}: stat block rendered read-only + editable game notes.
- /enemies/new and /enemies/{id}/edit: the full stat-block form (no notes).
- "Enemies" nav item between NPC's and Storyline.
- Table left empty on purpose.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Npc gains self-referencing father/mother (nullable, ON DELETE SET NULL);
editable as dropdowns on the per-NPC form
- app:npc:link-parents parses "son/daughter/child of X [and Y]" from the
Relationships / Notes column, resolves the names within the same family,
and works out which parent is father vs mother from husband/wife/widow(er)
and son/daughter/sister/brother cues (plus a small name-gender table for
the few the text can't disambiguate). --dry-run / --force. 92/180 linked,
none ambiguous.
- migrations now pin ENGINE=InnoDB - the local WAMP MariaDB defaults to
MyISAM, which silently drops foreign keys
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Npc entity (village spreadsheet columns) + game_notes text field for
the DM's live play notes, plus updated_at touched on every change
- Race / VillageStatus / BloodCartInvolvement / MysteryImportance as
string-backed PHP enums, rendered as dropdowns on the edit form
- /npcs: server-side DataTables (paging, global search and column sort
all run in the backend via NpcRepository::findForDataTable); name cell
links to the edit page, game notes shown truncated
- /npcs/{id}: Bootstrap-themed edit form (all fields + a large game
notes textarea); Symfony forms now use the bootstrap_5 theme
- app.js: generic server-side DataTable wiring driven by data-* on the
<table>/<th> (data-datatable-src, data-row-url, data-format)
- app:npc:import console command - upserts from data/npcs.csv by
(family, name), never overwrites game_notes; --purge / --dry-run
- data/npcs.csv: 180 villagers exported from the working spreadsheet
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>