Chapters now carry an eyebrow ("Overview", "Prologue", ...) and a short
tag for the list/sidebar badge, instead of a bare running number, so
non-numbered entries read correctly.
New chapter: "Prologue - The Long Road to Brackenford", the session
outline formatted as a runnable document -- an at-a-glance metadata band
(levels, duration, locations, hook), the in-world courier notice as a
posted handout, numbered scenes, GM callouts for the "ask the players"
prompts, label/body rows for the skill-check clues and travel-day
prompts, a three-stage encounter block, dramatic beat lines, and a
before/after panel for the legend the party's own retelling becomes.
Cross-links the Hearthburrow to Enemies and the closing line to the
overview's "Five and the Five".
Tests: render every chapter partial headless (no HTTP/DB) so a broken
variable or path() fails loudly, check each registered chapter has its
template file, and assert the /storyline routes sit behind the login gate.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The Storyline section is now a set of hand-written chapters. /storyline
lists them; /storyline/{slug} renders one inside a shell with a sticky
chapter nav and prev/next pager. Chapters are declared in
StorylineController::CHAPTERS, each pointing at a Twig partial under
templates/storyline/chapters/.
First chapter is the Brackenford Campaign Overview: the campaign-bible
text laid out as a long-form reading page -- numbered section headers,
a "by the numbers" / "the cast" quick-reference strip, pull-quotes for
in-world lines, GM callouts for the ticking clocks and the red herring,
and the escalation ladder. Storyline styles added to app.css.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
CSRF-protected POST to /enemies/{id}/delete with a confirm. Any
combatants referencing the enemy keep their snapshotted stats (the FK
sets enemy_id null), so deleting a monster type won't disturb a fight.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Replaces the leftover scaffold DataTable with a grid of tiles linking to
Characters, NPC's, Enemies, Tracker, Art and Storyline.
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>
- extract the 5e stat-block markup into enemy/_statblock.html.twig
(sb.block(enemy) macro); the enemy detail page now uses it too
- each tracker combatant gets a "stat block" toggle that expands the
block in place (Bootstrap collapse), so you don't have to leave the
encounter to check AC / attacks
- open blocks are remembered in localStorage and re-opened after each
damage/heal reload, and the caret rotates when expanded
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>
- gallery thumbnails now object-fit: contain (whole image, letterboxed),
and a card links to the new detail page instead of the raw file
- /art/{id} detail page: large uncropped image, "Open full size",
"Delete" (moved here from the gallery), and "Copy image"
- clipboard Stimulus controller copies the image to the OS clipboard
(converts non-PNG via canvas) for pasting into WhatsApp etc.
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>
- /npcs/family/{family} (app_npc_family) renders a parent->child tree for
everyone sharing that family name; co-parents who are both in the family
are paired into one couple node, elders first
- FamilyTreeBuilder does the flat-list -> tree assembly
- the NPC detail page's family name (subtitle + Details row) now links there
- .family-tree CSS: nested list with connector lines, small portrait avatars
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- /npcs/{id} (app_npc_show): portrait, all fields shown read-only, and
the only editable control is the game-notes textarea. Father/Mother
link through to their own pages.
- /npcs/{id}/edit (app_npc_edit): the full structured form, now WITHOUT
game notes; saving returns to the detail page.
- NpcType drops gameNotes; new NpcGameNotesType holds just that field.
- The DataTable row link (/npcs/{id}) now lands on the detail page.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Npc::getPortraitPath() maps the name to image/npc-pictures/<NameNoSpaces>.png;
the edit page renders it beside the heading (with family · occupation), and
drops the <img> via onerror when a portrait is missing.
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>
Still stored on the entity and editable on the per-NPC form; just no
longer shown in the /npcs list.
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>
Navbar (collapsible) with active-state highlighting driven by a nav list
in base.html.twig. Three routed, auth-gated controllers each rendering a
shared placeholder partial - data models still to be designed.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>