- PlayerCard: read-only portrait, name, class, level, XP (the sidebar
holds the editors for this data)
- PreparedSpells: growable slot list; a vacant slot has a free-text
input and '+' to fill it, an occupied slot shows name/level/
description and '×' to empty it; '+ слот' appends a vacant slot
- Attunements: three slots, free-text entry and per-slot clear
- rows are plain functions called from For's let(...) children —
component tags are not allowed directly inside them (view macro)
- Field<...> conversions for the Subfield prop types, state-in-key
keys so changed rows re-render (same convention as sidebar pips)
- SCSS: _character.scss with card, slot rows, buttons (mixins)
- App provides HistoryHandle as context (both build modes; stacks stay
empty on the server) and passes it to persist::init_hydrated
- persist: init_hydrated starts undo tracking after the localStorage
load (first captured state = saved state, not mock); clear_all and
apply_import call undo::before_replace so they are undoable
- header: Отменить/Вернуть buttons with disabled states driven by the
reactive stack depths; Ctrl+Z / Ctrl+Shift+Z / Ctrl+Y via a window
keydown listener that skips editable targets (browser owns those)
- SCSS: disabled control buttons dim out
ModalState carries title/message/action as RwSignals; the action is
read at click time so a late confirm wins. Overlay click and Cancel
close; the confirm button runs the stored action. Edition-2024 note:
method calls on non-Copy captures move by value inside nested move
closures, so handlers touch state.open/state.action fields directly.