Cleanup: pf2e subclasses + all native dialogs → themed Modals

- pf2e classes now show the defining 1st-level choice (instinct / doctrine /
  bloodline / mystery / racket / implement / thesis / …) in the wizard's
  subclass picker, like 5e. Curated PF2E_SUBCLASSES in pf2e/progression.ts,
  merged into the class during the wizard's pf2e enrich step.
- replaced every remaining native dialog with a themed Modal:
  - PlayerView session-password prompt → password Modal
  - Settings cloud-conflict confirm → "Use cloud / Keep this device" Modal
  - SessionControl host-password prompt → host Modal (optional password)
  - MapEditor text-label prompt → label Modal (captures the click point)
  No window.confirm / window.prompt / window.alert remain in the app.

Realtime e2e updated for the host Modal (Host → Start hosting).
Gate: 293 unit + 35 e2e + 2 realtime; tsc + app/server build clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-09 15:50:10 +02:00
parent 895807d6c9
commit 45d74ce5d7
8 changed files with 252 additions and 22 deletions
@@ -15,6 +15,7 @@ import { newId } from '@/lib/ids';
import { formatModifier } from '@/lib/format';
import { getClassTip, raceSynergyNote } from '@/lib/assistant/builder';
import { briefOverview, dedupeByName } from './overview';
import { PF2E_SUBCLASSES } from '@/lib/rules/pf2e/progression';
import { Modal } from '@/components/ui/Modal';
import { Button } from '@/components/ui/Button';
import { Badge } from '@/components/ui/Codex';
@@ -69,12 +70,15 @@ export function CreationWizard({ campaign, onClose }: { campaign: Campaign; onCl
let on = true;
void loadClasses(system).then((c) => {
if (!on) return;
// The pf2e class data loads with caster:'none'; merge the curated caster
// ability so pf2e spellcasters get the Spells step + "Caster" tag like 5e.
// The pf2e class data loads with caster:'none' and no subclasses; merge the
// curated caster ability (so pf2e spellcasters get the Spells step + "Caster"
// tag) and the defining 1st-level choice list (instinct/doctrine/bloodline…).
const enriched = system === 'pf2e'
? c.map((rc) => {
const def = getClassDef('pf2e', rc.name);
return def && def.caster !== 'none' && def.spellAbility ? { ...rc, caster: def.spellAbility } : rc;
const subclasses = PF2E_SUBCLASSES[rc.name] ?? rc.subclasses;
const caster = def && def.caster !== 'none' && def.spellAbility ? def.spellAbility : rc.caster;
return { ...rc, caster, subclasses };
})
: c;
setClasses([...enriched].sort((a, b) => a.name.localeCompare(b.name)));