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>