13 KiB
status, system, created, owners
| status | system | created | owners | |
|---|---|---|---|---|
| active | ui | 2026-08-07 |
|
Reorder Queued Messages Requirements
Overview
Pending messages run in strict FIFO order, so a later, more urgent prompt waits behind everything queued before it. Today the only workarounds are remove-and-requeue (destructive) or Send Now (interrupts the active turn). Users need to rearrange pending messages so execution order matches priority without losing content.
Requirements
REQ-UI-MESSAGE-QUEUE-REORDER-001: Reorder Queued Messages
Intent: Pending messages run in strict FIFO order, so a later, more urgent prompt waits behind everything queued before it. Today the only workarounds are remove-and-requeue (destructive) or Send Now (interrupts the active turn). Users need to rearrange pending messages so execution order matches priority without losing content.
Acceptance criteria
- AC-UI-MESSAGE-QUEUE-REORDER-001.1: Every visible pending message in the queue panel offers a grab handle on its left edge. The handle is a small dotted grip (2-column dot grid).
- AC-UI-MESSAGE-QUEUE-REORDER-001.2: The handle is a floating overlay anchored to the row's left edge, vertically centered. It does not participate in layout: no row or panel content moves to make room for it. While shown it may cover the row's
#Nposition label; the label returns when the handle hides. - AC-UI-MESSAGE-QUEUE-REORDER-001.3: On a fine pointer the handle is hidden by default and appears when the pointer hovers the row or keyboard focus reaches it. On a coarse pointer (touch) it is always visible, attached flush to the message box's left edge, and painted behind the row content — no chip surface, so the dotted grip reads as part of the box edge. The position label and sender icon stay painted above it (and pass touches through), and the interactive hit area is at least 44 by 44 CSS pixels.
- AC-UI-MESSAGE-QUEUE-REORDER-001.4: Dragging starts only from the handle. Pointer interaction with the row body (text selection, expand toggle, edit/merge/remove/send-now actions) is unchanged. Dragging requires pointer movement (activation distance), so a click on the handle does nothing by itself.
- AC-UI-MESSAGE-QUEUE-REORDER-001.5: Dropping the handle onto another row commits a new order. The queue panel updates immediately (optimistic) and reconciles to the backend result.
- AC-UI-MESSAGE-QUEUE-REORDER-001.6: The new order is persisted server-side and survives reload, session switch-back, and restart. All connected views reconcile to it.
- AC-UI-MESSAGE-QUEUE-REORDER-001.7: Displayed positions remain compact one-based ordinals (
#1through#N) derived from the visible order, exactly as after remove/merge. - AC-UI-MESSAGE-QUEUE-REORDER-001.8: Reordering applies to every visible pending row regardless of provenance (user, agent, workflow, system), matching the Remove action. Server-reserved rows already in flight stay hidden and are never reordered.
Migrated source detail
Why
Pending messages run in strict FIFO order, so a later, more urgent prompt waits behind everything queued before it. Today the only workarounds are remove-and-requeue (destructive) or Send Now (interrupts the active turn). Users need to rearrange pending messages so execution order matches priority without losing content.
What
- Every visible pending message in the queue panel offers a grab handle on its left edge. The handle is a small dotted grip (2-column dot grid).
- The handle is a floating overlay anchored to the row's left edge,
vertically centered. It does not participate in layout: no row or panel
content moves to make room for it. While shown it may cover the row's
#Nposition label; the label returns when the handle hides. - On a fine pointer the handle is hidden by default and appears when the pointer hovers the row or keyboard focus reaches it. On a coarse pointer (touch) it is always visible, attached flush to the message box's left edge, and painted behind the row content — no chip surface, so the dotted grip reads as part of the box edge. The position label and sender icon stay painted above it (and pass touches through), and the interactive hit area is at least 44 by 44 CSS pixels.
- Dragging starts only from the handle. Pointer interaction with the row body (text selection, expand toggle, edit/merge/remove/send-now actions) is unchanged. Dragging requires pointer movement (activation distance), so a click on the handle does nothing by itself.
- Dropping the handle onto another row commits a new order. The queue panel updates immediately (optimistic) and reconciles to the backend result.
- The new order is persisted server-side and survives reload, session switch-back, and restart. All connected views reconcile to it.
- Displayed positions remain compact one-based ordinals (
#1through#N) derived from the visible order, exactly as after remove/merge. - Reordering applies to every visible pending row regardless of provenance (user, agent, workflow, system), matching the Remove action. Server-reserved rows already in flight stay hidden and are never reordered.
- The handle is disabled while a queue mutation or backend cancellation for
the session is pending (the same
isLoading/cancellationPendinggate as Send Now), and is hidden while the row is being edited. - Keyboard reordering is supported: focusing the handle and using Space/Enter plus the arrow keys moves the row, matching the platform's sortable-list convention.
- Reordering changes nothing else: merge, edit, Remove, Send Now, FIFO Auto-run, and capacity semantics are unchanged. When Auto-run next takes an entry, it uses the new FIFO head. Protocol-level Send Now all and FIFO drain dispatch in the current reordered order.
API Surface
New WebSocket action:
message.queue.reorder
Request:
{
"session_id": "session-id",
"ordered_ids": ["entry-b", "entry-a", "entry-c"]
}
session_id is required. ordered_ids is required, non-empty, and must
contain each visible pending entry id of the session exactly once.
Successful response:
{
"session_id": "session-id",
"reordered": 3
}
reordered is the number of entries in the submitted order — equal to the
visible pending count on success. A no-op reorder (submitting the current
order) reports the same count, since positions are rewritten to the compacted
1..N sequence.
Errors:
validation:session_idmissing,ordered_idsmissing/empty, or duplicate ids inordered_ids.queue_changed: the submitted id set does not match the current visible pending set — an entry was drained, removed, merged, or newly queued since the client's snapshot. The request is rejected atomically with no partial reorder; the client refetches the authoritative queue.- Session access is required; the existing non-enumerating session-not-found response is used before any queue read or mutation.
On success the handler publishes the existing message.queue.status_changed
event so every connected view reconciles.
Data Model
No schema change. Order lives in the existing queued_messages.position
integer column (lower = head). A successful reorder rewrites positions to a
strictly increasing sequence: reserved in-flight rows keep their place in the
sequence, and the visible rows appear in the submitted order. Positions are
compacted (1..N) by the same operation, so subsequent inserts take
MAX(position) + 1 as today.
Permissions
- Any caller with access to the session may reorder its visible pending entries, regardless of entry provenance (same rule as Remove and Send Now).
- The action accepts no caller-supplied content,
user_id,task_id, or identity. Onlysession_idand the entry ids are taken from the caller, and the ids are validated against the persisted visible set.
Failure Modes
- Drain/remove/merge races the drag: the submitted set no longer matches
the visible set; the backend rejects the whole request with
queue_changedand the client refetches silently (self-recovering, no error toast). - Backend mutation fails for another reason: the client refetches to restore the authoritative order and shows a localized error toast.
- Concurrent clients: the last successful reorder wins. A client with a
stale snapshot receives
queue_changedand reconciles on its next refetch. - Reserved in-flight rows: they are invisible to the client and excluded from validation; they are never reordered and keep their delivery reservation.
- Rapid repeated drags: while a reorder request is in flight the handles are disabled, so overlapping reorder requests cannot be issued from one client.
Persistence Guarantees
The reordered sequence persists in queued_messages.position in SQLite and
survives a Kandev restart. It is queue state, not a client preference, and is
shared by every view of the session. A process crash mid-reorder leaves the
transaction uncommitted and the previous order intact.
Responsive and Mobile Behavior
- The existing inline queue panel remains the desktop and phone composition; the queue list stays the single internal scroll owner and the composer stays visible.
- Desktop: the handle is a hover/focus-revealed floating overlay on the row's left edge; nothing shifts in layout.
- Phone/coarse-pointer: the handle is always visible, attached flush to the
box's left edge, and painted behind the row content (background, no chip
surface). The row's position label / sender icon render above it and are
pointer-events: none, so touches reach the handle's 44 by 44 CSS-pixel target while the label stays readable.touch-action: noneon the handle keeps a touch-drag reordering instead of scrolling; vertical scrolling of the queue list is unaffected elsewhere. - The queue hook, backend action, optimistic order, and reconciliation are shared across viewports; only the handle's visibility and size differ.
- Mobile Playwright coverage proves the same reorder result with touch input.
Scenarios
- GIVEN three messages are queued in order A, B, C, WHEN the user
drags C's handle above A, THEN the panel shows
#1C,#2A,#3B and reloading the page keeps that order. - GIVEN a fine-pointer desktop with a queued message, WHEN the pointer is not over the row, THEN no handle is visible, and the row box has the same width and content position as without the feature.
- GIVEN the pointer hovers a queued message row, WHEN the pointer moves onto the left edge, THEN the dotted grab handle appears as a floating overlay and the row content does not shift.
- GIVEN a queued message row, WHEN the user presses and drags on the row body (not the handle), THEN no drag starts; text selection and row actions behave as before.
- GIVEN a row is being edited, WHEN the user looks at it, THEN no grab handle is shown for that row.
- GIVEN a queue mutation or cancellation is in flight, WHEN the user looks at any row, THEN the handles are visible but disabled.
- GIVEN the user starts dragging entry A and another client drains A
before the drop, WHEN the drop commits, THEN the backend returns
queue_changed, the panel silently refetches, and no error toast appears. - GIVEN a queue whose head is a hidden reserved in-flight lifecycle row, WHEN the user reorders the visible entries, THEN the visible entries assume the submitted order around the reserved row and its delivery/acknowledgement is unaffected.
- GIVEN the backend reorder fails for a non-race reason, WHEN the drop commits, THEN the panel refetches the authoritative order and shows the localized "failed to reorder" toast.
- GIVEN a queue containing user, agent, workflow, and system rows, WHEN the user drags any of them, THEN the row moves to the dropped position.
- GIVEN a reordered queue and a protocol client invokes Send Now all, THEN the bulk prompt concatenates the bodies in reordered FIFO order.
- GIVEN a phone viewport with queued messages, WHEN the user opens the queue panel, THEN every row shows an always-visible touch-sized handle attached flush to the box's left edge and painted behind the row content (position labels stay readable), and a touch drag reorders the queue with the same persisted result.
- GIVEN the user focuses a row's handle with the keyboard, WHEN they press Space, Arrow Up, then Space, THEN the row moves one position up and the order is persisted.
Out of Scope
- Bulk selection or multi-row drag.
- Dedicated up/down move buttons as an alternative to dragging (keyboard reordering is covered by the sortable keyboard sensor).
- Reordering hidden reserved in-flight rows.
- Cross-session or cross-task reordering.
- Changing merge, edit, Remove, Send Now, Auto-run, FIFO drain, or capacity semantics.