fix(avocet): undo — commit-then-clear order, empty-records guard, skip dedup, stronger test

This commit is contained in:
pyr0ball 2026-03-03 15:41:58 -08:00
parent 80a8195899
commit 4a76f6ba41
2 changed files with 22 additions and 15 deletions

View file

@ -134,28 +134,32 @@ def delete_undo():
if not _last_action: if not _last_action:
raise HTTPException(404, "No action to undo") raise HTTPException(404, "No action to undo")
action = _last_action action = _last_action
_last_action = None
item = action["item"] # always the original clean queue item item = action["item"] # always the original clean queue item
# Perform file operations FIRST — only clear _last_action on success
if action["type"] == "label": if action["type"] == "label":
# Remove last entry from score file
records = _read_jsonl(_score_file()) records = _read_jsonl(_score_file())
if not records:
raise HTTPException(409, "Score file is empty — cannot undo label")
_write_jsonl(_score_file(), records[:-1]) _write_jsonl(_score_file(), records[:-1])
elif action["type"] == "discard":
# Remove last entry from discarded file
records = _read_jsonl(_discarded_file())
_write_jsonl(_discarded_file(), records[:-1])
elif action["type"] == "skip":
# Item is at back of queue — move it to front
items = _read_jsonl(_queue_file()) items = _read_jsonl(_queue_file())
reordered = [item] + [x for x in items if x["id"] != item["id"]] _write_jsonl(_queue_file(), [item] + items)
_write_jsonl(_queue_file(), reordered) elif action["type"] == "discard":
return {"undone": {"type": action["type"], "item": item}} records = _read_jsonl(_discarded_file())
if not records:
raise HTTPException(409, "Discarded file is empty — cannot undo discard")
_write_jsonl(_discarded_file(), records[:-1])
items = _read_jsonl(_queue_file())
_write_jsonl(_queue_file(), [item] + items)
elif action["type"] == "skip":
items = _read_jsonl(_queue_file())
# Remove the item wherever it sits (guards against duplicate insertion),
# then prepend it to the front — restoring it to position 0.
items = [item] + [x for x in items if x["id"] != item["id"]]
_write_jsonl(_queue_file(), items)
# For label and discard: restore item to front of queue # Clear AFTER all file operations succeed
items = _read_jsonl(_queue_file()) _last_action = None
_write_jsonl(_queue_file(), [item] + items)
return {"undone": {"type": action["type"], "item": item}} return {"undone": {"type": action["type"], "item": item}}

View file

@ -116,6 +116,9 @@ def test_undo_label_removes_from_score(client, queue_with_items):
assert data["undone"]["type"] == "label" assert data["undone"]["type"] == "label"
score = api_module._read_jsonl(api_module._score_file()) score = api_module._read_jsonl(api_module._score_file())
assert score == [] assert score == []
# Item should be restored to front of queue
queue = api_module._read_jsonl(api_module._queue_file())
assert queue[0]["id"] == "id0"
def test_undo_discard_removes_from_discarded(client, queue_with_items): def test_undo_discard_removes_from_discarded(client, queue_with_items):
from app import api as api_module from app import api as api_module