diff --git a/ROADMAP.md b/ROADMAP.md new file mode 100644 index 0000000..1106b4a --- /dev/null +++ b/ROADMAP.md @@ -0,0 +1,181 @@ +# ROADMAP — aex + +A D&D 5e character sheet and campaign dashboard. One page per concern: character, +inventory, quests, notes, contacts, item reference. Built with Leptos 0.8 (SSR + +hydration), actix-web, SCSS. + +## MVP definition + +The MVP is done when a player can run a real session with the app: + +- the app opens with last session's state (survives tab close / reload) +- the player can track HP, spell slots, hit dice, death saves, money, inventory + (add, remove, move items — including from item search) +- the player can record quests, notes, and contacts as the session happens +- the player can look up any item's stats and add it to inventory +- the player can back up the dashboard to a JSON file and restore it + +## Current state (Feb 2026) + +| Area | Status | +|---|---| +| Header: campaign name, image, in-game date, session steppers | Works | +| Header: Save / Load / Clear buttons | Dead (no persistence) | +| Sidebar: image, names, class/level/XP, HP, hit dice, death saves, spell slots | Works | +| Character page `/` | Stub — renders literal text "character" | +| Inventory page `/inv`: personal + common, money, item rows | Works | +| Item search (nucleo over ~5k bundled 5e.tools items) | Component exists, wired to no page | +| Item "+" (add) and "…" (details) buttons | Dead | +| Quests / Notes / Contacts | Models + mock data exist, zero UI, no routes | +| Prepared spells | Model exists, list is empty; `Spell` is a `@TODO` stub | +| Attunements | Model exists, no UI | +| Settings | Empty struct | +| Persistence | None — hardcoded `Dashboard::mock()` on every load | +| e2e tests | Example Playwright spec only | +| README | Stock Leptos template | + +## Non-goals (explicitly out of MVP) + +- accounts, multi-user, or sync across devices +- server-side database +- dice rolling, character builder, full rulebook +- multiple campaigns / dashboards switching +- images hosted by the app (remote URLs are fine) + +## Effort legend + +- S — ~0.5 day +- M — ~1-2 days +- L — ~3-5 days + +--- + +## Phase 0 — Build hygiene (S, ~0.5-1 day) + +Cheap, unblocks the rest. Can happen any time; do it first. + +- [ ] rewrite README (currently the stock template) +- [ ] deduplicate `foundry.json` load in `src/statics/mod.rs` (loaded twice) +- [ ] clear compiler warnings (unused imports, unused `qty` in `item.rs`) +- [ ] decide Cargo.lock policy — currently gitignored, builds are non-reproducible; commit it +- [ ] drop unused dependencies (`leptos-use`, `dotenvy`, `pretty_env_logger` if confirmed unused) +- [ ] replace `end2end/tests/example.spec.ts` with a smoke test (app boots, nav renders, no console errors) + +## Phase 1 — Persistence: localStorage + JSON export/import (L, ~3-5 days) + +The foundation. Every later phase depends on it. + +- [ ] derive `serde::Serialize` / `Deserialize` on all entities in `src/entities/mod.rs` + (Dashboard, PlayerData, Balance, Inventory, InventoryEntry, InventoryItem, + QuestBook, Quest, NoteBook, Note, ContactBook, Contact, SpellSlots, Attunements, + PreparedSpells, Spell, HitDice, DeathSaveThrows, Name, Settings; enums: + InventoryKind, ContactStatus, PreparedSpell) +- [ ] verify `Store` derive and `serde` derive coexist on the same structs (prototype first) +- [ ] add a `version` field to `Settings`; add a migration hook for future format changes +- [ ] serialize Dashboard to JSON; save to localStorage via `web_sys::Storage` + (feature-gate to `hydrate`/`csr`; SSR renders mock/empty) +- [ ] auto-save on change — debounced effect over the `Store` — plus manual Save button +- [ ] Load button: restore from localStorage with an overwrite confirm +- [ ] Clear button: wipe localStorage with a confirm +- [ ] export: download `dashboard.json` as a blob +- [ ] import: file input → parse → validate version → replace state +- [ ] on startup, hydrate from localStorage; keep `mock()` as the fallback / "New dashboard" +- **Acceptance:** close the tab, reopen — state is intact. Export → import into a + fresh browser — identical state. + +## Phase 2 — Character page (M, ~1-2 days) + +`/` currently renders the literal string "character". + +- [ ] player card: portrait, name, class, level, XP (data already in sidebar) +- [ ] HP block: temp / current / max, `Numput`-style editing +- [ ] prepared spells UI: render Vacant/Occupied slots; free-text spell name entry + (upgrade the `Spell` stub from `name`-only to name + optional level/description) +- [ ] attunements UI: 3 slots, free text, clear button (model exists) +- **Acceptance:** player can set prepared spells and attunements; they persist. + +## Phase 3 — Inventory & item search end-to-end (L, ~3-5 days) + +Rows are editable and move/remove works. Search exists but is orphaned; "+" and +"…" buttons are dead. + +- [ ] host `ItemSearchField` on a page (Wiki page is the natural home — see Phase 7) +- [ ] "+" adds the `BookItem` to the personal inventory: merge into a row with the + same name, else new row, quantity 1 +- [ ] "…" opens an item detail view: description, rarity, weight, tags, source data + (`BookItem.rest`) +- [ ] free-form add-entry UI: add a row (name/quantity/weight) without search +- [ ] edit a row: name, quantity, weight, description +- [ ] stretch: carrying capacity — sum row weights per inventory, show total vs limit +- **Acceptance:** search "potion" → click + → a row appears in the personal + inventory with the correct weight; state persists. + +## Phase 4 — Quests page (M, ~1-2 days) + +New route `/quests`. Model and mock data exist; zero UI. + +- [ ] add a completion state to `Quest` (the model lacks one today) +- [ ] quest list: title, giver (Name), taken/task locations, dates, reward (Balance, + reuse `Numput`), description +- [ ] add / update / delete quest; toggle complete +- [ ] sort by deadline; highlight overdue +- **Acceptance:** create a quest, reload — it is still there; mark it complete. + +## Phase 5 — Notes page (M, ~1-2 days) + +New route `/notes`. Model and mock data exist; zero UI. + +- [ ] note list: content, in-game date, session, tags; add / edit / delete +- [ ] tag chips; filter by tag or by session +- [ ] quick-add defaults to the header's current session and in-game date +- **Acceptance:** log a note mid-session, filter by tag, reload — still there. + +## Phase 6 — Contacts page (M, ~1-2 days) + +New route `/contacts`. Model and mock data exist; zero UI. + +- [ ] contact cards: name, alias, status, location, notes, optional image +- [ ] status filter: Alive / Dead / DeadBygone / Unknown +- [ ] add / edit / delete contact +- **Acceptance:** find a contact by status filter; edit its notes; persists. + +## Phase 7 — Wiki page: item reference (M, ~1-2 days) + +`/wiki` currently renders "books". Search infrastructure already exists. + +- [ ] host `ItemSearchField` here with results list +- [ ] item detail panel: rarity, weight, full entry text from the JSON `rest` field +- [ ] filters: rarity / category tags; lazy list or pagination — never render 5k rows +- [ ] "Add to inventory" shortcut (reuses the Phase 3 path) +- **Acceptance:** browse or search items, open a detail, add to inventory. + +## Phase 8 — Polish, tests, release (L, ~2-4 days) + +- [ ] responsive SCSS pass (sidebar and header on mobile widths) +- [ ] Playwright e2e: persistence round-trip, add item from search, quest CRUD, + notes filter +- [ ] rewrite README: usage, dev workflow, deploy +- [ ] release build via cargo-leptos; verify SSR + hydration; pick a deploy target +- **Acceptance:** fresh clone → `cargo leptos build` → app runs; e2e green. + +--- + +## Sequencing notes + +- Phase 1 before Phases 2-7: every new page must persist. Building pages first + against mock data and persisting later costs a data-migration pass instead. +- Phases 2 and 3 can run in parallel after Phase 1 (different components). +- Phases 4-6 are independent of each other and share one CRUD-list pattern — + build the pattern once (e.g. a generic list editor) and reuse it. +- Phase 7 reuses the Phase 3 add-to-inventory path; keep that path shared. + +## Known debt and risks + +- **Cargo.lock is gitignored** — builds are not reproducible. Decide in Phase 0. +- **serde × Store derive** on the same structs — unverified combination; + prototype before committing to it (Phase 1). +- **~3.9 MB of item JSON** bundled via `include_str!` — parses on startup in the + browser. Fine for MVP; moving to a server function is a post-MVP option. +- **localStorage** has a ~5 MB budget — plenty for text JSON state. +- **Browser APIs on SSR** — localStorage access must be gated to `hydrate`/`csr`; + SSR renders from mock or empty state.