Fix drag-out losing audio and FX-container drops losing the capture; re-home ingest under shell/actions

This commit is contained in:
2026-07-29 23:49:53 -04:00
parent a689fb75eb
commit 0800760833
16 changed files with 269 additions and 45 deletions
+72
View File
@@ -234,6 +234,72 @@ static void testEmbedStripIsNotHotspot() {
CHECK(!infoNamesFxHotspot("mcp.fxembed extra"));
}
// --- Drop surface parity: container vs FX button ------------------------------
//
// The container-drop regression (instance loads, capture does not): the two surfaces must be
// one code path carrying one payload. The payload is a function of the capture alone, so the
// surface cannot influence it — and both surfaces must classify as hotspots so both reach it.
// Every surface a drop can land on admits the SAME capture and yields a payload that decodes
// back to it. The payload takes the capture and nothing else, so the surface cannot influence
// it — walking the surfaces here is what pins that the container family is not second-class.
static void testEveryDropSurfaceCarriesTheCapture() {
const char* surfaces[] = {
"fx_chain", // FX chain window / container list — the reported failing surface
"fx_0", // floating FX window
"tcp.fx", // TCP FX button — the reference path
"mcp.fx", // MCP FX button
};
const std::string sampleId = "cap-7f3a";
for (const char* info : surfaces) {
CHECK(infoNamesFxHotspot(info));
const ParsedPreset p = parsePreset(buildInstrumentDropPreset(sampleId));
CHECK(p.ok);
CHECK(p.classId == vstClassIdHex());
const ComponentState cs = deserializeComponentState(p.compChunk, kRate);
CHECK(cs.selectionId == sampleId);
}
}
// --- All-or-nothing rollback --------------------------------------------------
// The add failed: nothing was created, so there is nothing to delete and nothing loaded.
static void testAddFailureLeavesNothingToRollBack() {
const DropOutcome out = decideDropOutcome(DropAttempt{-1, false});
CHECK(!out.loaded);
CHECK(out.rollbackFxIndex < 0);
}
// The instance was created but the preset did not apply: the caller MUST delete that exact
// index — an instance without its capture is the orphan the contract forbids.
static void testPresetFailureRollsBackTheCreatedIndex() {
const DropOutcome zero = decideDropOutcome(DropAttempt{0, false});
CHECK(!zero.loaded);
CHECK(zero.rollbackFxIndex == 0);
const DropOutcome later = decideDropOutcome(DropAttempt{4, false});
CHECK(!later.loaded);
CHECK(later.rollbackFxIndex == 4);
// Container-addressed indices (0x2000000-flagged) roll back by the same rule — the
// obligation follows the index REAPER handed back, whatever space it names.
const DropOutcome inContainer = decideDropOutcome(DropAttempt{0x2000000 + 5, false});
CHECK(!inContainer.loaded);
CHECK(inContainer.rollbackFxIndex == 0x2000000 + 5);
}
// Both halves succeeded: loaded, and nothing to undo.
static void testSuccessKeepsTheInstance() {
const DropOutcome out = decideDropOutcome(DropAttempt{2, true});
CHECK(out.loaded);
CHECK(out.rollbackFxIndex < 0);
}
// A "preset applied" report with no instance behind it can never read as loaded.
static void testNoInstanceIsNeverLoaded() {
const DropOutcome out = decideDropOutcome(DropAttempt{-1, true});
CHECK(!out.loaded);
CHECK(out.rollbackFxIndex < 0);
}
int main() {
testClassIdHexPinnedPerChannel();
testPresetRoundTripsThroughInstrumentReader();
@@ -248,6 +314,12 @@ int main() {
testNonFxSurfacesAreNotHotspot();
testEmbedStripIsNotHotspot();
testEveryDropSurfaceCarriesTheCapture();
testAddFailureLeavesNothingToRollBack();
testPresetFailureRollsBackTheCreatedIndex();
testSuccessKeepsTheInstance();
testNoInstanceIsNeverLoaded();
if (g_fail == 0) std::printf("All tests passed.\n");
return g_fail ? 1 : 0;
}