fix: same-slot reorder is a no-op; WM_DESTROY clears card drag state; fix card_meta.h ms-rounding comment

This commit is contained in:
2026-07-27 02:47:29 -04:00
parent 439bb44e0d
commit 1585f0bbe6
4 changed files with 29 additions and 1 deletions
+1
View File
@@ -76,6 +76,7 @@ bool SlotMap::remove(const std::string& id) {
bool SlotMap::reorder(const std::string& id, int targetSlot) {
if (slotOf(id) < 0) return false; // not mapped -> no mutation
if (targetSlot < 0) targetSlot = 0;
if (slotOf(id) == targetSlot) return false; // already there — true no-op
// Detach the moving id first so the occupancy test below sees the post-move world.
remove(id);
+4
View File
@@ -2922,6 +2922,7 @@ void onMouseMove(int x, int y) {
g_panel.dropBankId.clear();
g_panel.cardGesture = CardGesture::None;
g_panel.dragTargetSlot = -1;
g_panel.dragPrimaryId.clear();
invalidatePanel();
// Empty path list -> nothing draggable (all stale/missing); do not start a drag.
@@ -3117,6 +3118,9 @@ WDL_DLGRET dlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
stopAudition();
g_panel.selection = Selection{};
g_panel.dragArmed = g_panel.dragging = false;
g_panel.cardGesture = CardGesture::None;
g_panel.dragTargetSlot = -1;
g_panel.dragPrimaryId.clear();
g_panel.hovered = Hover{};
g_panel.tooltipShown = false;
g_panel.hwnd = nullptr;
+1 -1
View File
@@ -47,7 +47,7 @@ struct MusicalLength {
std::string formatBarsBeats(const MusicalLength& m);
// seconds.milliseconds from a wall-clock length (always derivable, meter-independent).
// * "S.mmm" — integer seconds, a dot, zero-padded 3-digit milliseconds (floored).
// * "S.mmm" — integer seconds, a dot, zero-padded 3-digit milliseconds (rounded to nearest ms).
// e.g. 0.0 -> "0.000", 1.5 -> "1.500", 62.037 -> "62.037".
// * negative length is clamped to "0.000" (a length is never negative; defensive).
std::string formatSecondsMs(double lengthSeconds);
+23
View File
@@ -973,6 +973,28 @@ static void testReorderSampleRejectsUnknown() {
CHECK(book.pool().slots.slotOf("id-r1") == 0); // unchanged
}
// Same-slot reorder is a true no-op: returns false, no undo point triggered,
// JSON byte-identical before/after (the invariant that blocks spurious dirty-state).
static void testReorderSampleSameSlotIsNoOp() {
BankBook book;
CHECK(book.pool().index.add(sampleWith("a")) == AddResult::Added);
CHECK(book.pool().index.add(sampleWith("b")) == AddResult::Added);
CHECK(book.pool().index.add(sampleWith("c")) == AddResult::Added);
book.reconcileSlots(); // a@0, b@1, c@2
const std::string jsonBefore = book.serialize();
// Drop each card onto its own current slot — must return false every time.
CHECK(!book.reorderSample("id-a", kPoolBankId, 0));
CHECK(!book.reorderSample("id-b", kPoolBankId, 1));
CHECK(!book.reorderSample("id-c", kPoolBankId, 2));
// Slots and JSON are byte-identical — no mutation occurred.
CHECK(book.pool().slots.slotOf("id-a") == 0);
CHECK(book.pool().slots.slotOf("id-b") == 1);
CHECK(book.pool().slots.slotOf("id-c") == 2);
CHECK(book.serialize() == jsonBefore);
}
// --- BankBook L7: Alt-replace mutator ----------------------------------------
static void testReplaceSampleTakesSlotAndRemovesOccupant() {
@@ -1090,6 +1112,7 @@ int main() {
testMigrationDefaultsToInsertionOrderDense();
testOrderedSampleIdsReconcilesLazily();
testReorderSampleRejectsUnknown();
testReorderSampleSameSlotIsNoOp();
testReplaceSampleTakesSlotAndRemovesOccupant();
testReplaceSampleNonDestructiveFileStays();
testReplaceSampleRejectionsNoMutation();