Fix shared spec references in session; clarify undoLast contract

commitAssignment and moveEntry stored the same spec object in both
stack.spec and entry.spec. A later, more complete scan enriching
stack.spec would retroactively mutate the entry that first created the
stack, making it appear as if it had been scanned with data it never
actually had. Both places now store independent shallow copies so the
entries log and the stacks spec (comparison baseline) can't leak into
each other.

Also documents that undoLast intentionally reverts the last *recorded*
entry, not the last *action* - a subsequent moveEntry/removeEntry on a
different entry does not change what undoLast will take back - and
adds a test pinning that contract after a reorder.
This commit is contained in:
vchuser
2026-07-28 15:07:15 +02:00
parent f8e3a06d44
commit a90ec05550
2 changed files with 57 additions and 4 deletions
+12 -4
View File
@@ -42,7 +42,7 @@ export function proposeAssignment(session, spec) {
export function commitAssignment(session, spec, source, stackId) {
let stack = session.stacks.find((candidate) => candidate.id === stackId);
if (!stack) {
stack = { id: stackId, spec, count: 0 };
stack = { id: stackId, spec: { ...spec }, count: 0 };
session.stacks.push(stack);
session.stacks.sort((a, b) => a.id.localeCompare(b.id));
} else {
@@ -55,7 +55,9 @@ export function commitAssignment(session, spec, source, stackId) {
}
stack.count += 1;
const entry = { entryId: session.nextEntryId++, spec, stackId, source };
// entry.spec ist das Protokoll dessen, was tatsaechlich gescannt wurde, und muss
// unabhaengig vom Stapel-Spec bleiben - eigene Kopie, kein geteiltes Objekt.
const entry = { entryId: session.nextEntryId++, spec: { ...spec }, stackId, source };
session.entries.push(entry);
return entry;
}
@@ -64,7 +66,13 @@ function dropEmptyStacks(session) {
session.stacks = session.stacks.filter((stack) => stack.count > 0);
}
/** Nimmt den zuletzt erfassten Eintrag zurueck. */
/**
* Nimmt den zuletzt erfassten Eintrag zurueck - nicht die zuletzt ausgefuehrte
* Handlung. Die Oberflaeche zeigt neben der Rueckgaengig-Flaeche stets den
* zuletzt erfassten Eintrag an, und genau der wird hier zurueckgenommen. Ein
* zwischenzeitliches Umsortieren (moveEntry) oder Entfernen (removeEntry)
* eines anderen Eintrags bleibt davon unberuehrt.
*/
export function undoLast(session) {
const entry = session.entries.pop();
if (!entry) return null;
@@ -84,7 +92,7 @@ export function moveEntry(session, entryId, stackId) {
let to = session.stacks.find((candidate) => candidate.id === stackId);
if (!to) {
to = { id: stackId, spec: entry.spec, count: 0 };
to = { id: stackId, spec: { ...entry.spec }, count: 0 };
session.stacks.push(to);
session.stacks.sort((a, b) => a.id.localeCompare(b.id));
}