fix(flomni,bec_widgets): treat incomplete jobs as a reorder floor, like running

Sort mode and tomo_queue_move() let a pending job be dragged/moved
visually ahead of an incomplete one. Harmless to actual run order --
tomo_queue_execute()'s pick-next loop always resumes the first
running/incomplete job it finds regardless of list position -- but
confusing to read, since list order looked like run order and wasn't.
Narrows the GUI's _MOVABLE_STATUSES to ("pending",) and the CLI's
floor check to the highest index among all running/incomplete jobs,
not just the running one. Found while testing the reorder feature
against a real interrupted scan.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
x01dc
2026-07-13 16:22:47 +02:00
co-authored by Claude Sonnet 5
parent b3330e6bce
commit b8a567a9f8
2 changed files with 30 additions and 20 deletions
@@ -3861,13 +3861,16 @@ class Flomni(
print(f"Deleted tomo queue job #{index} ({job['label']}).")
def tomo_queue_move(self, index: int, new_index: int) -> None:
"""Move a pending/incomplete tomo queue job to a new position.
"""Move a pending tomo queue job to a new position.
Only ``pending``/``incomplete`` jobs can be reordered -- a
``running`` or ``done`` job stays put. A move is also refused if it
would place the job at or above the position of the job currently
``running`` (that job is a hard floor: nothing may be reordered
ahead of the thing already in progress). Takes effect at the next
Only ``pending`` jobs can be reordered -- a ``running``,
``incomplete``, or ``done`` job stays put. ``incomplete`` is a floor
exactly like ``running``: tomo_queue_execute()'s pick-next loop
always resumes whichever of the two it finds first, regardless of
list order, so a pending job moved "ahead" of one in the list would
visually suggest a run order that isn't real. A move is also
refused if it would place the job at or above the position of any
currently ``running``/``incomplete`` job. Takes effect at the next
job boundary if the queue is executing (see tomo_queue_execute()'s
pick-next model).
@@ -3882,16 +3885,19 @@ class Flomni(
if not 0 <= new_index < len(jobs):
raise ValueError(f"tomo_queue_move: new_index {new_index} out of range.")
job = jobs[index]
if job.get("status", "pending") not in ("pending", "incomplete"):
if job.get("status", "pending") != "pending":
raise ValueError(
f"tomo_queue_move: job #{index} is '{job.get('status')}' -- only "
"pending/incomplete jobs can be reordered."
"pending jobs can be reordered."
)
running_idx = next((i for i, j in enumerate(jobs) if j.get("status") == "running"), None)
if running_idx is not None and new_index <= running_idx:
floor_indices = [
i for i, j in enumerate(jobs) if j.get("status") in ("running", "incomplete")
]
if floor_indices and new_index <= max(floor_indices):
raise ValueError(
f"tomo_queue_move: can't move to position {new_index} -- job "
f"#{running_idx} is currently running and is a hard floor for reordering."
f"#{max(floor_indices)} is running/incomplete and is a hard floor "
"for reordering."
)
self._tomo_queue_proxy.move(index, new_index)
print(f"Moved tomo queue job #{index} -> #{new_index} ({job['label']}).")
@@ -1043,10 +1043,14 @@ class TomoQueueDialog(QDialog):
_POLL_MS = 2000
# Statuses a job must have to be reorderable in sort mode. "running" and
# "done" are excluded: running is a hard floor (plan section 6.4), done
# is pinned for readability (plan section 6.3) even though moving it
# would be harmless.
_MOVABLE_STATUSES = ("pending", "incomplete")
# "incomplete" are both hard floors: tomo_queue_execute()'s pick-next
# loop always resumes whichever of them it finds first, regardless of
# list position (see TOMO_QUEUE_TESTING.md's resume-before-fresh
# rationale) -- so a pending job dragged "ahead" of one in the list
# would visually suggest a run order that isn't real. "done" is pinned
# for readability (plan section 6.3) even though moving it would be
# harmless.
_MOVABLE_STATUSES = ("pending",)
def __init__(self, client, parent=None):
super().__init__(parent)
@@ -1125,7 +1129,7 @@ class TomoQueueDialog(QDialog):
self._btn_sort = QPushButton("Sort queue…")
self._btn_sort.setCheckable(True)
self._btn_sort.setToolTip(
"Reorder pending/incomplete jobs. The running job (if any) and "
"Reorder pending jobs. The running/incomplete job (if any) and "
"done jobs stay put."
)
self._btn_move_up = QPushButton("▲ Move up")
@@ -1279,10 +1283,10 @@ class TomoQueueDialog(QDialog):
def _move_selected(self, direction: int) -> None:
"""Swap the selected row with the adjacent one. Only ever swaps two
movable (pending/incomplete) rows -- a running or done neighbour
blocks the swap, which is what keeps the running job as a hard
floor (plan section 6.4) and done jobs pinned (section 6.3) without
needing separate index-range bookkeeping.
movable (pending) rows -- a running, incomplete, or done neighbour
blocks the swap, which is what keeps the running/incomplete job as
a hard floor (plan section 6.4) and done jobs pinned (section 6.3)
without needing separate index-range bookkeeping.
"""
row = self._selected_single_row()
jobs = self._load_queue()