aex/ROADMAP.md
yaroslav k a11e86e839 update docs for phase 0 completion
ARCHITECTURE: LazyLock, tracked Cargo.lock, dedup'd foundry.json, warnings
cleared. ROADMAP: phase 0 checkboxes closed; MODERNIZE: lazy_static marked
applied, rest pending.
2026-08-15 14:58:01 +03:00

189 lines
8.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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.
- [x] rewrite README (currently the stock template)
- [x] deduplicate `foundry.json` load in `src/statics/mod.rs` (loaded twice)
- [x] clear compiler warnings (unused imports, unused `qty` in `item.rs`)
- [x] decide Cargo.lock policy — currently gitignored, builds are non-reproducible; commit it
(done: committed 2026-08-04)
- [~] dependency and idiom cleanup — see `MODERNIZE.md` (drop unused deps,
`lazy_static``LazyLock`, local replacements for `num-format`/`num-traits`)
(done: `lazy_static``LazyLock` only; the rest stays pending)
- [~] decide on the under-consideration unused deps from `MODERNIZE.md`
(`seq-macro`, `leptos-use`, `http`, `console_log`, `js-sys`); the rest
(`itertools`, `thiserror`, `anyhow`, `pretty_env_logger`, `dotenvy`) stay
(decision: nothing removed; revisit later)
- [x] 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.