12 Commits
Author SHA1 Message Date
FrankandClaude Opus 5.5 7a927e21a3 Characters: seed the current party sheets, add skill expertise
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>
2026-10-04 12:50:24 +02:00
FrankandClaude Sonnet 5 9052218927 Characters: build the page around Darragh Oakwanderer's sheet
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>
2026-09-22 08:55:28 +02:00
FrankandClaude Sonnet 5 8b8f67c1d0 Art: drag pieces into a new order
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>
2026-09-15 17:18:02 +02:00
FrankandClaude Sonnet 5 5c821bd7ad Storyline: per-chapter DM notes
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>
2026-09-08 18:51:53 +02:00
FrankandClaude Sonnet 5 cb23d5b9ac Show AC on each tracker row
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>
2026-09-06 19:34:24 +02:00
FrankandClaude Sonnet 5 cbe4b0a0e1 Combat tracker: multiple named encounters
- 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>
2026-09-06 19:24:37 +02:00
FrankandClaude Sonnet 5 9854e6c034 Add combat HP tracker
- 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>
2026-09-06 19:16:12 +02:00
FrankandClaude Sonnet 5 ed9390319b Add Art section: upload named images, gallery, delete
- 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>
2026-09-06 16:41:33 +02:00
FrankandClaude Sonnet 5 7870be9dfb Add Enemies section: D&D stat-block table + detail/edit pages
- 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>
2026-09-06 16:36:17 +02:00
FrankandClaude Sonnet 5 5bf25d1802 Add father/mother links to NPCs, derived from the relationships text
- 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>
2026-09-06 15:21:03 +02:00
FrankandClaude Sonnet 5 1ccab6ed68 Add NPC database table with server-side DataTable and per-NPC game notes
- 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>
2026-09-06 15:04:39 +02:00
FrankandClaude Sonnet 5 9f6763040e Scaffold Symfony 8 app with Bootstrap/DataTables and Docker stack
- Symfony 8.1 webapp skeleton on PHP 8.5 (AssetMapper/importmap, no Node)
- Form-login gate: User entity, security.yaml, SecurityController,
  app:user:create command, initial migration (user + messenger_messages)
- Bootstrap 5 + DataTables layout (base/login/home templates); any
  table.datatable is auto-initialised
- trusted_proxies/headers wired for the shared Nginx Proxy Manager
- Docker (servers only): docker-compose.yml (php:8.5-fpm-alpine,
  nginx:1.30-alpine, mariadb:12.3; host ports 8085/3310; external
  docker_default network) + docker/{Dockerfile,php.ini,nginx,setup.sh,restart.sh}
- .env with CHANGEME placeholders (no secrets), .gitattributes enforces
  LF so the deploy scripts stay runnable on Linux

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 14:11:36 +02:00