fix(ui): hide engine-internal presets from user authoring surface
The Modifier Profile editor's Presets tab surfaced the plumbing preset '__modifier-profile-integration__' — an internal hook bundle that wires modifier-profile facts (CaptureFlags, DirectionAdditions, DamageResistance, on-damage / on-capture / aura triggers) into the engine runtime. ChessEngine auto-activates it when a profile is supplied; toggling it from a profile has no meaningful effect and confuses users who see 'Modifier Profile Integration' as a bundle-able rule variant. Filter any preset id matching the '__x__' double-underscore naming convention out of: - PresetPanel's allPresets list (the Presets tab in the editor) - LayoutPicker's suggestedPresets chip row (in case a layout ever accidentally lists one) Generalized so future internal presets following the same convention are auto-hidden without maintaining an exclusion list. New unit test (PresetPanel.filter.test.ts) pins the contract: registry contains the internal preset, filter yields a non-empty user-safe list, known public preset (extinction-chess) survives.
This commit is contained in:
parent
ef730eefbe
commit
e5594b0d3c
3 changed files with 71 additions and 2 deletions
|
|
@ -144,7 +144,14 @@ export function LayoutPicker({
|
||||||
value.suggestedPresets.length > 0 && (
|
value.suggestedPresets.length > 0 && (
|
||||||
<div className="pt-2 flex flex-wrap items-center gap-1.5">
|
<div className="pt-2 flex flex-wrap items-center gap-1.5">
|
||||||
<span className="text-xs text-neutral-400 italic mr-1">Suggested rules:</span>
|
<span className="text-xs text-neutral-400 italic mr-1">Suggested rules:</span>
|
||||||
{value.suggestedPresets.map((presetId) => {
|
{value.suggestedPresets
|
||||||
|
// Never surface engine-internal plumbing presets
|
||||||
|
// (__x__ naming convention) even if a layout
|
||||||
|
// accidentally lists one — they auto-activate via
|
||||||
|
// the engine constructor and can't be meaningfully
|
||||||
|
// toggled by the user.
|
||||||
|
.filter((id) => !(id.startsWith('__') && id.endsWith('__')))
|
||||||
|
.map((presetId) => {
|
||||||
const presetDef = PRESET_REGISTRY.get(presetId);
|
const presetDef = PRESET_REGISTRY.get(presetId);
|
||||||
const label = presetDef?.name ?? presetId;
|
const label = presetDef?.name ?? presetId;
|
||||||
const isActive = activations.some(a => a.id === presetId);
|
const isActive = activations.some(a => a.id === presetId);
|
||||||
|
|
|
||||||
46
packages/chess/src/ui/PresetPanel.filter.test.ts
Normal file
46
packages/chess/src/ui/PresetPanel.filter.test.ts
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
import { describe, it, expect } from "vitest";
|
||||||
|
import { PRESET_REGISTRY } from "../presets/registry.js";
|
||||||
|
import "../presets/index.js";
|
||||||
|
// Side-effect import: registers the __modifier-profile-integration__
|
||||||
|
// pseudo-preset (top-level register call in apply.ts). The modifier
|
||||||
|
// barrel doesn't pull this in, so we import directly to mirror the
|
||||||
|
// engine constructor's boot ordering.
|
||||||
|
import "../modifiers/apply.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Regression guard — the Modifier Profile editor's Presets tab used
|
||||||
|
* to surface the engine-internal `__modifier-profile-integration__`
|
||||||
|
* preset because it calls PRESET_REGISTRY.getAll() directly. That
|
||||||
|
* preset is plumbing (it wires modifier facts into move-gen / damage
|
||||||
|
* pipelines) and is auto-activated by the ChessEngine constructor,
|
||||||
|
* so it should never appear as something a user can toggle on a
|
||||||
|
* profile.
|
||||||
|
*
|
||||||
|
* The panel now filters any id matching the `__x__` double-underscore
|
||||||
|
* convention. This test pins that contract.
|
||||||
|
*/
|
||||||
|
describe("Preset authoring surface excludes internal presets", () => {
|
||||||
|
const isInternal = (id: string) => id.startsWith("__") && id.endsWith("__");
|
||||||
|
|
||||||
|
it("registry contains the modifier-profile-integration internal preset", () => {
|
||||||
|
const all = PRESET_REGISTRY.getAll();
|
||||||
|
const internal = all.filter((p) => isInternal(p.id));
|
||||||
|
expect(internal.length).toBeGreaterThan(0);
|
||||||
|
expect(internal.map((p) => p.id)).toContain(
|
||||||
|
"__modifier-profile-integration__",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("filtering internal presets yields a user-authoring-safe list", () => {
|
||||||
|
const all = PRESET_REGISTRY.getAll();
|
||||||
|
const userFacing = all.filter((p) => !isInternal(p.id));
|
||||||
|
expect(userFacing.length).toBeGreaterThan(0);
|
||||||
|
for (const p of userFacing) {
|
||||||
|
expect(isInternal(p.id)).toBe(false);
|
||||||
|
}
|
||||||
|
// Sanity: at least one well-known user-facing preset survives
|
||||||
|
// the filter. extinction-chess is a stable long-standing id so
|
||||||
|
// this assertion won't churn with preset-registry shuffling.
|
||||||
|
expect(userFacing.map((p) => p.id)).toContain("extinction-chess");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -121,7 +121,23 @@ export function PresetPanel({
|
||||||
onChange,
|
onChange,
|
||||||
boundLayout,
|
boundLayout,
|
||||||
}: PresetPanelProps) {
|
}: PresetPanelProps) {
|
||||||
const allPresets = useMemo(() => PRESET_REGISTRY.getAll(), []);
|
// Filter engine-internal presets out of the authoring surface.
|
||||||
|
// Convention: any id wrapped in double-underscores (`__x__`) is
|
||||||
|
// reserved for plumbing presets the engine auto-activates — e.g.
|
||||||
|
// `__modifier-profile-integration__` wires modifier facts into
|
||||||
|
// move-gen / damage pipelines. Those should never appear in a
|
||||||
|
// profile's bundled activations because they can't be meaningfully
|
||||||
|
// toggled; they either flip on via the engine constructor or they
|
||||||
|
// don't. Same guard protects against any future internal presets
|
||||||
|
// that follow the naming convention without our having to
|
||||||
|
// hand-maintain an exclusion list.
|
||||||
|
const allPresets = useMemo(
|
||||||
|
() =>
|
||||||
|
PRESET_REGISTRY.getAll().filter(
|
||||||
|
(p) => !(p.id.startsWith('__') && p.id.endsWith('__')),
|
||||||
|
),
|
||||||
|
[],
|
||||||
|
);
|
||||||
const activeById = useMemo(() => {
|
const activeById = useMemo(() => {
|
||||||
const m = new Map<string, PresetActivation>();
|
const m = new Map<string, PresetActivation>();
|
||||||
for (const a of activations) m.set(a.id, a);
|
for (const a of activations) m.set(a.id, a);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue