Polish sprint 1/2: restore crash fix, pf2e parity, full glyph purge
Crash fix: - restoreBackup (file + cloud pull) now validates each row through its Zod schema, backfilling new fields — an older backup no longer lands characters missing feats/concentration/etc and crashing the sheet. + test. pf2e parity (closing feature gaps vs 5e): - +9 missing classes (Animist, Exemplar, Gunslinger, Inventor, Kineticist, Magus, Psychic, Summoner, Thaumaturge) with data + class tips. - the wizard now enriches pf2e classes with their caster ability, so pf2e spellcasters get the Spells step + "Caster" tag like 5e. - editable Perception rank and spellcasting proficiency on the pf2e sheet (fixes wrong initiative / spell DC at higher levels); rank-10 spell slots. - pf2e monster resistances/weaknesses/immunities now apply in combat (flat amounts: resistance subtracts, weakness adds, 'all' matches any type). + tests. Glyph purge (finishing the emoji→Lucide migration): replaced ~40 leftover text-glyphs (✕ − + ✦ 🤫 ⬆ ⬇ ☑ ☐ ▶ ◀ ‹ › ★ ↻ ▸ ↑ ●) with Lucide icons across Modal (every dialog), the sheet sections, dice, settings, player panels, world pages, map editor, roll tray, and session UI. Gate: 293 unit + e2e green; tsc + build clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
|
||||
import { useState } from 'react';
|
||||
import { X, Minus, Plus } from 'lucide-react';
|
||||
import { newId } from '@/lib/ids';
|
||||
import { getSystem, applyRest } from '@/lib/rules';
|
||||
import { spendResource, regainResource } from '@/lib/mechanics';
|
||||
@@ -62,19 +64,19 @@ export function ResourcesSection({ c, update }: SectionProps) {
|
||||
{c.resources.map((r) => (
|
||||
<li key={r.id} className="flex items-center gap-2 rounded-md border border-line bg-panel px-3 py-2">
|
||||
<Input className="h-8 flex-1" value={r.name} onChange={(e) => patch(r.id, { name: e.target.value })} aria-label="Resource name" />
|
||||
<Button size="icon" variant="ghost" onClick={() => { const res = spendResource(c, r.id); if (res.ok) update(res.patch); }} aria-label="Spend">−</Button>
|
||||
<Button size="icon" variant="ghost" onClick={() => { const res = spendResource(c, r.id); if (res.ok) update(res.patch); }} aria-label="Spend"><Minus size={14} aria-hidden /></Button>
|
||||
<span className="w-12 text-center text-sm">
|
||||
<span className="font-medium text-ink">{r.current}</span>
|
||||
<span className="text-muted">/{r.max}</span>
|
||||
</span>
|
||||
<Button size="icon" variant="ghost" onClick={() => { const res = regainResource(c, r.id); if (res.ok) update(res.patch); }} aria-label="Regain">+</Button>
|
||||
<Button size="icon" variant="ghost" onClick={() => { const res = regainResource(c, r.id); if (res.ok) update(res.patch); }} aria-label="Regain"><Plus size={14} aria-hidden /></Button>
|
||||
<NumberField className="w-14" value={r.max} min={0} onChange={(v) => patch(r.id, { max: v, current: Math.min(v, r.current) })} aria-label="Max" />
|
||||
<Select className="w-auto py-1 text-xs" value={r.recovery} onChange={(e) => patch(r.id, { recovery: e.target.value as CharacterResource['recovery'] })}>
|
||||
{(['short', 'long', 'daily', 'none'] as const).map((k) => (
|
||||
<option key={k} value={k}>{RECOVERY_LABEL[k]}</option>
|
||||
))}
|
||||
</Select>
|
||||
<Button size="icon" variant="ghost" className="text-danger" onClick={() => remove(r.id)} aria-label={`Remove ${r.name}`}>✕</Button>
|
||||
<Button size="icon" variant="ghost" className="text-danger" onClick={() => remove(r.id)} aria-label={`Remove ${r.name}`}><X size={14} aria-hidden /></Button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
Reference in New Issue
Block a user