Skip to content

fix(dashboard): prevent supervisor queue poll from overwriting just-saved order - #656

Merged
xsovad06 merged 1 commit into
mainfrom
feat/issue-632
Aug 14, 2026
Merged

fix(dashboard): prevent supervisor queue poll from overwriting just-saved order#656
xsovad06 merged 1 commit into
mainfrom
feat/issue-632

Conversation

@xsovad06

Copy link
Copy Markdown
Owner

Summary

  • Fix race condition where the 30-second polling loop overwrites a just-saved queue order by reading stale config from disk, causing the queue to visually "snap back" to its previous state.
  • Improve drag-and-drop with directional drop indicators (top/bottom border highlight based on cursor position).
  • Guard _specs_dir() against None project dir to prevent crashes in multi-project mode.

Closes #632

Changes

Queue poll-overwrite guard (supervisor.html):

  • Add _queueSaveInFlight flag (blocks poll during in-flight saves) and _queueLastSaved timestamp (5-second grace period after any queue mutation).
  • Applied to all five queue-mutating functions: saveQueue, addToQueue, removeFromQueue, clearQueue, and implicitly via saveQueue for moveQueueItem/queueDrop.

Drag-and-drop polish (supervisor.html):

  • queueDragOver calculates cursor position relative to element midpoint and highlights top or bottom border accordingly.
  • queueDrop inserts above or below the target based on the last drag-over position.
  • All border cleanup functions clear both borderTopColor and borderBottomColor.

spec_service None guard (spec_service.py):

  • _specs_dir() returns None when both project_dir arg and get_project_dir() are None.
  • Both callers (find_spec_file, _iter_all_specs) handle None gracefully.
  • dependency_graph.py already had its own guard at line 84 (unchanged).

Review guidance

  • The JS changes are straightforward state guards. The key correctness point is that _queueLastSaved is set in ALL queue-mutating paths, not just saveQueue.
  • The drag-and-drop insertion index arithmetic (insertIdx adjustment when _dragIdx < targetIdx) handles the splice-then-insert offset correctly.

Test plan

  • 5 new tests in TestSpecsDirNoneGuard covering _specs_dir, find_spec_file, read_spec, list_all_specs, and list_pending_specs with None project dir
  • make check passes (5896 passed, 1 pre-existing env-dependent failure in test_scheduler.py)
  • Manual verification: arrow reorder and drag-and-drop persist after 30-second poll cycle

…aved order

Add save-in-flight flag and 5-second grace period so the 30-second
polling loop skips queue fetches immediately after a save, preventing
the snap-back race condition. Also improve drag-and-drop with
directional drop indicators (top/bottom border) and guard _specs_dir
against None project_dir in multi-project mode.

Closes #632
@xsovad06 xsovad06 self-assigned this Aug 14, 2026
@coderabbitai

coderabbitai Bot commented Aug 14, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@xsovad06, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 105 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: fd55370d-ba51-4128-9415-cb277120a2aa

📥 Commits

Reviewing files that changed from the base of the PR and between 1960c25 and f51edf9.

📒 Files selected for processing (3)
  • sova/dashboard/services/spec_service.py
  • sova/dashboard/templates/supervisor.html
  • tests/test_spec.py

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@sonarqubecloud

Copy link
Copy Markdown

@xsovad06 xsovad06 left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR Summary

Single-commit PR by xsovad06 fixing three related issues: (1) a race condition where the supervisor queue polling loop overwrites just-saved reorder data, (2) drag-and-drop UX improvement with directional drop indicators, and (3) a crash in _specs_dir() when project_dir is None in multi-project mode. 3 files changed (+65/-7), 1 commit.

Findings

No blocking or actionable findings. The changes are well-scoped and correct.

The insertion index arithmetic in queueDrop (insertIdx adjustment for _dragIdx < targetIdx) correctly compensates for the splice-then-insert offset. The _queueSaveInFlight flag with finally cleanup and the 5-second _queueLastSaved grace period together form a two-layer guard against the poll-overwrite race. All five queue-mutating paths (saveQueue, addToQueue, removeFromQueue, clearQueue, and moveQueueItem/queueDrop via saveQueue) correctly set _queueLastSaved.

The _specs_dir None guard provides defense-in-depth alongside the existing guard in dependency_graph.py:84.

Verdict

Approve. Clean, focused fix with good test coverage.

What's Done Well

  1. The two-layer poll guard (_queueSaveInFlight + _queueLastSaved grace period) is a robust pattern. The flag catches in-flight saves, and the timestamp catches the window between save completion and the next poll cycle. Either alone would have gaps.

  2. The spec_service None guard is placed at exactly the right level (_specs_dir itself), making every downstream caller safe without requiring changes in each one. The 5 new tests cover all public entry points.

  3. The drag-and-drop _dropPosition logic with midpoint calculation gives users clear visual feedback about where the drop will land, which was missing before. The border cleanup in queueDragEnd and queueDragLeave covers all exit paths.

(Posted as comment: GitHub does not allow self-reviews with formal approval/rejection state.)

@xsovad06
xsovad06 merged commit 85368fd into main Aug 14, 2026
8 checks passed
@xsovad06
xsovad06 deleted the feat/issue-632 branch August 14, 2026 08:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(dashboard): supervisor task queue reordering broken by poll overwrite and drag/drop race

1 participant