Profiles can now carry a list of PresetActivation entries alongside
their per-type / per-instance modifiers. When such a profile is
picked in the Lobby, its bundled presets are unioned with any
layout suggestedPresets (profile config wins on id-collision).
Changes:
- ModifierProfile.presetActivations added (optional readonly array).
Zod schema + server wire schema mirror the field; drift guard
picks up forgotten updates on either side.
- ModifierProfileEditor grows a Presets tab in the right column
sharing space with the Library tab. Each preset row carries a
checkbox, scope radio (both/white/black), and a turns counter
(blank = permanent). Inline diagnostics warn on redundant layout
overlap and on loose-scope incompatibility; hard errors under
overlapping scope disable Save.
- Lobby merges profile.presetActivations into its active preset
set on profile select, on editor close, and on URL deep-link.
- LayoutPicker suggested-preset chips now expose aria-pressed so
the active state is readable by assistive tech + e2e tests.
Tests: library round-trip preserves presetActivations (unit); two
new e2e scenarios (pre-seeded bundled profile auto-activates; full
editor-author loop persists + reflects in the lobby). 1752 unit +
105 playwright all green.
Previously the ModifierProfileEditor's layout picker was editor-only
UI state — it drove per-instance board preview and live validation,
but was dropped on save. This meant per-instance modifiers (square-
bound to their authoring layout) silently became orphans in the Lobby
if the user picked a different layout at apply time, with no signal
about the mismatch.
Changes:
- ModifierProfileEditor now writes the bound layout through to
profile.layoutId (schema already supported the optional field) and
rehydrates boundLayout from profile.layoutId on open / load /
undo / redo.
- Lobby rearranged so the Modifier Profile picker sits ABOVE the
Layout Picker. Picking a profile with a layoutId binding snaps
selectedLayout to match. Same behavior on URL ?modifierProfile
deep-links and after the editor closes with a new save.
- Mismatch banner (amber) surfaces when the user overrides the
layout after picking a bound profile, with a one-click "Switch
to <LayoutName>" restore.
Unit test added for library round-trip of layoutId; new
profile-layout-binding.spec.ts covers the four scenarios: snap,
manual override + mismatch banner, unbound profile leaves layout
alone, and editor-save persists layoutId.