feat(#93): IMP-55 u1~u12 frontend manual section swap detection (manual_section_assignment bool axis + drag-only marker gate + dual-axis persistence + backend manual-true gate)
Some checks failed
Multi-MDX Regression (IMP-91) / multi-mdx-regression (push) Failing after 9s
Some checks failed
Multi-MDX Regression (IMP-91) / multi-mdx-regression (push) Failing after 9s
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -54,6 +54,11 @@ function makeSelection(overrides?: Partial<UserSelection["overrides"]>): UserSel
|
||||
// axis declared on `UserSelection.overrides`. Empty by default so the
|
||||
// existing IMP-52 cases remain unchanged in shape.
|
||||
image_overrides: {},
|
||||
// IMP-55 (#93) u3 — bool intent marker is REQUIRED on
|
||||
// `UserSelection.overrides` (not optional). Default to `false` so every
|
||||
// pre-existing fixture matches the `createInitialUserSelection` seed
|
||||
// and stays compile-clean after u3 widened the type.
|
||||
manual_section_assignment: false,
|
||||
...overrides,
|
||||
},
|
||||
};
|
||||
@@ -460,3 +465,88 @@ describe("image_overrides axis — saveImageOverride (IMP-51 u11)", () => {
|
||||
expect(sel.overrides.image_overrides).toEqual(before);
|
||||
});
|
||||
});
|
||||
|
||||
// ─── IMP-55 (#93) u3 — manual_section_assignment bool axis ──────────────────
|
||||
// Restore-on-reopen / seed coverage for the bool intent marker. Production
|
||||
// branch lives at `slidePlanUtils.ts` — `applyPersistedNonFrameOverrides`
|
||||
// guards with `typeof persisted.manual_section_assignment === "boolean"`,
|
||||
// and `createInitialUserSelection` seeds the axis to `false`. The marker
|
||||
// gates whether `handleGenerate` (u7) forwards `overrides.zoneSections`
|
||||
// to the backend; the pipeline (u9) consumes persisted `zone_sections`
|
||||
// only when the marker is exactly `true`, so any non-boolean payload MUST
|
||||
// end up `false` in memory (fail-closed).
|
||||
|
||||
describe("manual_section_assignment axis — applyPersistedNonFrameOverrides (IMP-55 #93 u3)", () => {
|
||||
it("restores literal true verbatim", () => {
|
||||
const sel = makeSelection();
|
||||
const next = applyPersistedNonFrameOverrides(sel, {
|
||||
manual_section_assignment: true,
|
||||
});
|
||||
expect(next.overrides.manual_section_assignment).toBe(true);
|
||||
});
|
||||
|
||||
it("restores literal false verbatim (u12 apply/cancel write must survive reopen)", () => {
|
||||
// Seed `true` so the assertion proves `false` overwrites; a truthiness
|
||||
// check instead of `typeof === \"boolean\"` would silently keep `true`
|
||||
// and resurrect stale auto-carry assignments as user intent.
|
||||
const sel = makeSelection({ manual_section_assignment: true });
|
||||
const next = applyPersistedNonFrameOverrides(sel, {
|
||||
manual_section_assignment: false,
|
||||
});
|
||||
expect(next.overrides.manual_section_assignment).toBe(false);
|
||||
});
|
||||
|
||||
it("leaves the in-memory marker unchanged when the persisted axis is absent", () => {
|
||||
const sel = makeSelection({ manual_section_assignment: true });
|
||||
const next = applyPersistedNonFrameOverrides(sel, { layout: "horizontal-2" });
|
||||
expect(next.overrides.manual_section_assignment).toBe(true);
|
||||
expect(next.overrides.layout_preset).toBe("horizontal-2");
|
||||
});
|
||||
|
||||
it.each([
|
||||
["null clear sentinel", null],
|
||||
['string "true"', "true"],
|
||||
['string "false"', "false"],
|
||||
["number 1", 1],
|
||||
["number 0", 0],
|
||||
["object {}", {}],
|
||||
["array []", []],
|
||||
])("ignores non-boolean payload (%s) — keeps prior in-memory value", (_label, payload) => {
|
||||
const sel = makeSelection({ manual_section_assignment: true });
|
||||
const next = applyPersistedNonFrameOverrides(sel, {
|
||||
manual_section_assignment: payload as unknown as boolean,
|
||||
});
|
||||
expect(next.overrides.manual_section_assignment).toBe(true);
|
||||
});
|
||||
|
||||
it("seeds an empty selection with manual_section_assignment=false (createInitialUserSelection)", () => {
|
||||
const sel = createInitialUserSelection();
|
||||
expect(sel.overrides.manual_section_assignment).toBe(false);
|
||||
});
|
||||
|
||||
it("returns a NEW selection object (no input mutation) when restoring the marker", () => {
|
||||
const sel = makeSelection({ manual_section_assignment: false });
|
||||
const next = applyPersistedNonFrameOverrides(sel, {
|
||||
manual_section_assignment: true,
|
||||
});
|
||||
expect(next).not.toBe(sel);
|
||||
expect(next.overrides).not.toBe(sel.overrides);
|
||||
// Input still pristine — proves the helper does not flip the fixture.
|
||||
expect(sel.overrides.manual_section_assignment).toBe(false);
|
||||
});
|
||||
|
||||
it("layers the bool axis alongside other persisted axes in a single call", () => {
|
||||
const sel = makeSelection();
|
||||
const next = applyPersistedNonFrameOverrides(sel, {
|
||||
layout: "vertical-2",
|
||||
zone_sections: { top: ["03-1"], bottom: ["03-2"] },
|
||||
manual_section_assignment: true,
|
||||
});
|
||||
expect(next.overrides.layout_preset).toBe("vertical-2");
|
||||
expect(next.overrides.zone_sections).toEqual({
|
||||
top: ["03-1"],
|
||||
bottom: ["03-2"],
|
||||
});
|
||||
expect(next.overrides.manual_section_assignment).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user