diff --git a/TODO.md b/TODO.md index 088f122..239e0b4 100644 --- a/TODO.md +++ b/TODO.md @@ -5,6 +5,15 @@ REWORK_PLAN.md. ## Feature backlog +### FEAT-M6: Transactional undo (moved from REWORK_PLAN) +- Every mutating action writes event: `(type, payload, undo_payload, undone, ts)`. +- Undo = apply `undo_payload` in same SQLite tx, flip `undone`. Transactional, + no stale clobber. +- Replaces fragile `/logs` snapshot-write undo. +- Migration: keep old undo working for existing entries until cleared; new + format for new entries. +- Related: BUG-7 (reorder no undo). + ### FEAT-1: Dead participants stay in turn order — DONE - Fixed: `applyHpChange` no longer flips `isActive` or touches `turnOrderIds` on death/revive. Dead stay in rotation, `nextTurn` visits them, PCs get @@ -78,18 +87,10 @@ REWORK_PLAN.md. - Test: render App + DisplayView, toggle hide-HP, assert display still shows encounter (not paused). -### BUG-5: mid-round addParticipant/revive corrupts rotation -- Test: `shared/tests/turn.combat.test.js` (jest, seeded RNG, RED). -- 13 rotation-dupes / 100 rounds. First at round 4 (Cleric twice). -- Pattern: Reinforce/Summon added mid-round → appears in rotation same round - → round wrap re-sorts by initiative → currentTurnParticipantId pointer - stale → nextTurn revisits. -- Root cause: `computeTurnOrderAfterAddition` appends id to turnOrderIds - end. Round wrap re-sorts by initiative. currentTurn pointer stale after - sort → drifts → nextTurn revisits. -- This is the test audit should have been. Mirrors replay-combat.js op - sequence exactly (damage, heal, conditions, toggleActive, deathSave, - remove, add, edit, pause/resume, reorder, revive-between-rounds). +### BUG-5: mid-round addParticipant/revive corrupts rotation — FIXED +- Fixed (commit `494327f`). Slot-array turn order + DRY advance core + `nextActiveAfter`. Both nextTurn + computeTurnOrderAfterRemoval delegate. +- 500-round replay: 0 skips, 0 double-acts. ### BUG-6: reorderParticipants doesn't update turnOrderIds - Test: `shared/tests/turn.reorder.test.js` 'reorder updates turnOrderIds' (RED). @@ -114,8 +115,27 @@ REWORK_PLAN.md. - Display frozen until full reload. - Fix: `onclose` → reconnect + re-subscribe existing paths. +### BUG-10: deact+reactivate same round double-acts participant +- Discovered in 500-round replay (3 occurrences). DISTINCT from BUG-5. +- Pattern: participant acts → DM deactivates them → DM reactivates them + same round → `computeTurnOrderAfterAddition` re-inserts by initiative + (front) → acts AGAIN before round ends. +- No "acted-this-round" guard. Slot-array model has no per-round-acted set. +- Edge case (DM deact+reactivate same participant same round). +- Fix candidate: track actedThisRound set, skip re-acted; OR insertion + places reactivate AFTER current position (not by initiative). +- Parser now discounts deact-current advances, so this surfaced real. + +### BUG-11: FE Combat.scenario test crashes (pre-existing) +- `src/tests/Combat.scenario.test.js:254` deathSave query helper throws + (button not found). +- Baseline (my changes removed) also exit=1. Pre-existing, not regression. +- Crashes whole FE test run (process dies). + ## Pipeline (bugs only --- milestones live in REWORK_PLAN.md) - [ ] BUG-4: fix setDoc→updateDoc for all 5 activeDisplay sites -- [ ] BUG-5: fix computeTurnOrderAfterAddition currentTurn re-anchor +- [x] BUG-5: fixed (commit 494327f) - [ ] BUG-6: reorderParticipants update turnOrderIds - [ ] BUG-8: ws adapter reconnect +- [ ] BUG-10: deact+reactivate double-act +- [ ] BUG-11: FE Combat.scenario crash diff --git a/docs/REWORK_PLAN.md b/docs/REWORK_PLAN.md index 08df892..23ef7e8 100644 --- a/docs/REWORK_PLAN.md +++ b/docs/REWORK_PLAN.md @@ -121,8 +121,9 @@ Each milestone = independently mergeable PR upstream (unless marked ❌). | 3 | characterization tests lock current behavior | yes | | 4 | resolve initiative rotation corruption (BUG-5) | yes | | 5 | docker compose in-house | smoke | -| 6 | undo rework (tx events) | unit | +| 6 | _moved to TODO backlog (feature work)_ | - | | 7 | playwright multi-window e2e (deferred) | e2e | +| 8 | (future) public exposure | - | ### Milestone 0 — Repo + branch setup ✅ - Fresh branch off `main` (not `dsr-rework`). Name: `rework-backend`. @@ -159,33 +160,29 @@ Each milestone = independently mergeable PR upstream (unless marked ❌). - **Exit criteria:** characterization suite green. Baseline locked. ✅ DONE. - **Upstream-PRable:** ✅ if kept storage-agnostic (tests target turn logic shape). -### Milestone 4 — Resolve initiative rotation corruption (BUG-5) -- **Real bug.** Mid-round add/revive corrupts rotation. -- 13 dupes / 100 rounds (deterministic seeded test). -- Root cause: `computeTurnOrderAfterAddition` appends id to turnOrderIds - end. Round wrap re-sorts by initiative. `currentTurnParticipantId` - pointer stale → nextTurn revisits. -- RED test locked: `shared/tests/turn.combat.test.js`. -- Detail in `TODO.md` BUG-5. -- **Exit criteria:** RED green. Rotation invariant holds across - add/remove/revive. +### Milestone 4 — Resolve initiative rotation corruption (BUG-5) ✅ +- **Fixed** (commit `494327f`). +- Slot-array turn order model + DRY advance core (`nextActiveAfter`). + Both `nextTurn` + `computeTurnOrderAfterRemoval` delegate → one advance + path, no drift. +- 500-round replay: 0 skips, 0 double-acts. +- Tests: `turn.skip.test.js`, `turn.dry.test.js` (advance parity lock). - **Upstream-PRable:** ✅ bug fix. ### Milestone 5 — Docker compose - `docker-compose.yml`: - `backend` service (Node + sqlite volume) - - `nginx` service (static frontend + reverse proxy + http basic auth) + - `frontend` service (static build served via **Caddy**) + - Caddy reverse-proxies `/api` + `/ws` → backend, auto WS upgrade, HTTP basic auth +- Caddy chosen over nginx: simpler config, native WS, one file `Caddyfile`. - Profiles: `firebase` (frontend only, current behavior) vs `backend` (full stack). +- Run: OrbStack local now; remote docker context later. - **Exit criteria:** `docker compose up` runs full stack in-house. - **Upstream-PRable:** ❌ divergence. -### Milestone 6 — Undo rework -- Events table: every mutating action writes `(type, payload, undo_payload, undone, ts)`. -- Undo = apply `undo_payload` in same SQLite tx, flip `undone`. Transactional, no stale clobber. -- Replaces current fragile `/logs` snapshot-write undo. -- Migration: keep old undo working for existing entries until cleared; new format for new entries. -- **Exit criteria:** undo works transactionally; interleaved undos don't corrupt. -- **Upstream-PRable:** ⚠️ partial. Turn-logic-level undo = ✅. Backend events table = ❌. +### Milestone 6 — Undo rework — _MOVED to TODO backlog_ +- Moved: feature work (transactional undo), not infra. Lives in `TODO.md` now. +- Scope: events table `(type, payload, undo_payload, undone, ts)`; undo = apply undo_payload in tx. ### Milestone 7 — Playwright E2E (deferred) - Multi-window E2E: DM view + display + player view in separate browser contexts against running backend. @@ -234,7 +231,7 @@ Each milestone = independently mergeable PR upstream (unless marked ❌). | 3 characterization tests | ✅ | if storage-agnostic | | 4 BUG-5 rotation fix | ✅ | bug fix | | 5 docker compose | ❌ | divergence | -| 6 undo rework | ⚠️ partial | turn-logic-level ✅, events table ❌ | +| 6 undo (moved to TODO) | - | - | | 7 playwright | ✅ | if test infra shared | Default `STORAGE=firebase` + `AUTH_MODE=none` (unset) = upstream sees literally zero change. @@ -264,4 +261,4 @@ Default `STORAGE=firebase` + `AUTH_MODE=none` (unset) = upstream sees literally - Backend live: port 4001, db `./data/tracker.sqlite` - Frontend: port 3999 with `REACT_APP_STORAGE=ws` - Test suite: ~160 tests (shared + server + FE). Bugs tracked in `TODO.md`. -- Next milestones: M5 docker-compose, M6 undo rework. +- Next milestones: M5 docker-compose. Undo moved to TODO backlog.