first pass of merging in warp (doesn't build)
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
# Queued Prompts UI
|
||||
|
||||
## Summary
|
||||
Introduce a collapsible "queued prompts" panel for regular Agent Mode queued prompts that supports multiple prompts, in-place editing, drag-to-reorder, and per-row delete.
|
||||
Queued prompts run sequentially as the agent finishes each preceding exchange.
|
||||
## Problem
|
||||
Today regular Agent Mode queueing only supports a single follow-up prompt at a time.
|
||||
Re-queueing replaces the previous prompt, the user can't reorder or edit what's pending, and there's no way to deal with multiple in-flight ideas without losing work.
|
||||
## Goals
|
||||
- Let users queue any number of follow-up prompts while the agent is responding, and have them auto-fire in order.
|
||||
- Make the queued prompts visible, reorderable, editable, and individually removable from a single panel.
|
||||
- Preserve the existing regular queue trigger surfaces: the auto-queue toggle and `/queue` slash command.
|
||||
- Keep compatibility placeholder flows (`/compact-and`, `/fork-and-compact`, Cloud Mode prompts) on their existing pending-user-query UI instead of broadening the new panel. Expanding those surfaces to also use the queued prompt panel will come in a separate, follow-up PR.
|
||||
- Reuse `QueueSlashCommand` as the regular queue rollout gate for both the trigger surfaces and the visual panel — no new flags introduced.
|
||||
## Non-goals
|
||||
- Persisting the queue across app restarts.
|
||||
- Cross-conversation queueing (the queue belongs to the conversation it was filed against).
|
||||
- Reordering or editing the prompt that's currently executing (only items still pending in the queue are editable).
|
||||
- A "Send now" affordance to interrupt the in-flight exchange (explicitly removed; users cancel via the existing stop button if they want to fire a queued prompt earlier).
|
||||
## Behavior
|
||||
### Queue panel placement and visibility
|
||||
1. The queue panel renders between the warping indicator (status bar) and the agent input box, anchored to the bottom of the conversation area, in the same vertical slot the inline menu uses when it's open.
|
||||
2. The panel is visible whenever the active conversation has at least one queued prompt; otherwise the panel is not rendered (no empty state).
|
||||
3. The panel has a header `"<N> queued"` with a chevron icon. The body of the panel (everything below the header) is what collapses, not the header itself. Clicking the chevron (or anywhere on the header) toggles the panel between expanded (header + rows visible) and collapsed (only the header is visible). Default state is expanded. The collapsed state persists for the lifetime of the queue (across re-orderings, edits, deletions, additions). Adding a new prompt while collapsed does not auto-expand.
|
||||
4. `/queue`, the auto-queue toggle, and the visual queue panel continue to be gated by `QueueSlashCommand`. `/compact-and` continues to be gated by `SummarizationConversationCommand`, and `/fork-and-compact` follows the existing fork-command availability. `PendingUserQueryIndicator` remains compatibility infrastructure for legacy pending-user-query placeholders, not a rollout switch for the regular queue panel.
|
||||
### What gets queued
|
||||
5. The auto-queue toggle in the warping indicator is per-conversation. When on for the active conversation, any prompt the user submits while that conversation is in progress (`InProgress` or `Blocked`) is appended to that conversation's queue rather than sent. When off, regular submits still cancel-and-resend (existing behavior). Toggling the button toggles the state for the active conversation only; switching to a different conversation shows that conversation's own toggle state. New conversations default the toggle to off.
|
||||
6. `/queue <prompt>` appends `<prompt>` to the queue when the active conversation is in progress, and behaves like a normal send when the conversation is idle (existing semantics).
|
||||
7. `/compact-and <prompt>` and `/fork-and-compact <prompt>` do not create queued-prompts panel rows. Their follow-up prompts stay on the legacy pending-user-query UI while summarization or fork-then-summarization runs.
|
||||
8. Cloud Mode prompts (both Oz and third-party harness flows) do not create queued-prompts panel rows. Their placeholders stay on the legacy pending-user-query UI and remain lifecycle-owned by the cloud setup / shared-session flow.
|
||||
9. Submitting in shell mode (input type is Shell, not AI) is never queued — it runs in the terminal as today, regardless of toggle state or in-progress AI status.
|
||||
10. `/queue` with an empty argument shows an error toast and does not modify the queue (existing behavior).
|
||||
11. Queues and the auto-queue toggle state are owned per-conversation app-wide. Each conversation has its own queue and its own toggle state, both of which persist across agent-view exit, switching between conversations, and re-entry into the agent view. Switching to a different conversation shows that conversation's queue and toggle state; switching back restores the prior conversation's state. Queue and toggle state are dropped only when the conversation itself is deleted, or when its owning terminal view's conversations are cleared.
|
||||
### Queue rows
|
||||
12. Each queue row shows, left to right:
|
||||
- A drag handle icon (six-dot grid).
|
||||
- A compact multiline prompt preview, capped by both displayed height and character count so long prompts stay scannable in the queue.
|
||||
- On hover: a pencil (edit) and a trash (delete) icon-button, right-aligned.
|
||||
13. Hovering a row reveals the edit/delete icons. Moving the cursor off the row hides them.
|
||||
14. Every row in the queued-prompts panel is a regular user-managed queued prompt, so the row interactions in (12)–(13) apply uniformly to every visible panel row.
|
||||
15. Rows render in queue order from top (next to fire) to bottom (last to fire).
|
||||
### Edit interaction
|
||||
16. Clicking the pencil icon on a row replaces the row's static preview with an inline multiline editor pre-filled with the current prompt text and selects the entire prompt.
|
||||
17. The editor is visually outlined while editing, grows until it reaches the same visual line cap as the static row preview, then scrolls internally with a visible scrollbar. Pressing Enter commits the edit (the row's prompt is replaced with the editor contents) and exits edit mode. An empty edit restores the original prompt text and exits edit mode.
|
||||
18. Pressing Escape cancels the edit and restores the original prompt text. Clicking outside the row, including focusing the main input, commits the current editor text.
|
||||
19. While a row is in edit mode, that row's drag handle is inert (the row cannot be reordered until the edit is committed or cancelled). Other rows can still be dragged.
|
||||
20. Only one row can be in edit mode at a time. Clicking the pencil on a different row exits edit mode on the previous row without changing that row's last committed text, then enters edit mode on the new one.
|
||||
21. Auto-fire never sends a row that is currently in edit mode. If the active conversation reaches `FinishReason::Complete` while the first queue row is in edit mode, then:
|
||||
- If the main input is empty, that row is removed from the queue and — mirroring the delete behavior in (23)–(24) — the row's prompt text (its last committed value, not any uncommitted text still in the inline editor buffer) is placed in the main input box, and the input is focused.
|
||||
- If the main input is non-empty, that row stays in the queue and the input is not modified.
|
||||
Other queue rows are not affected and resume normal sequential firing on the next completion. If the row being edited is not the first row, auto-fire proceeds normally for the actual first row; the edited row is left in place and can become the next-to-fire after rows ahead of it drain.
|
||||
### Delete interaction
|
||||
22. Clicking the trash icon on a row removes that row from the queue.
|
||||
23. If the input box is empty when a row is deleted, the deleted row's prompt text is placed in the input (replacing the empty buffer); the input gains focus.
|
||||
24. If the input box is non-empty when a row is deleted, the deleted prompt is discarded — the input is not modified.
|
||||
25. Deleting the last visible row in the queue removes the panel (since the queue is now empty); the collapsed/expanded state resets for any future queue.
|
||||
### Drag-to-reorder
|
||||
26. Dragging a row vertically reorders it within the queue. The dragged row is visually highlighted while the queue live-reorders around it.
|
||||
27. Dropping the row commits the new order. The first row of the new order is what will fire next.
|
||||
28. Dragging is constrained to the vertical axis — horizontal motion does not change order.
|
||||
29. Rows reflow as the dragged item crosses the midpoint of a neighboring row, making the tentative new order visible before drop. Reflow live-mutates the queue, so auto-fire (and any other read of the queue head) always sees the current post-drag order rather than the pre-drag order.
|
||||
30. If the queue is mutated by something other than the drag itself while a drag is active, behavior depends on which row was mutated. If a different row is removed (e.g. auto-fire pops the current head while the user is dragging a non-head row) or appended, the drag continues against the updated queue and subsequent reflow uses the new neighbor positions. If the dragged row itself is removed (only reachable when the dragged row is currently the head and auto-fire pops it), the row disappears and is submitted as the next user query, and the drag is cancelled silently: no reorder is committed, no reorder telemetry fires, and any further mouse motion before mouseup is inert.
|
||||
31. Legacy pending-user-query placeholders for Cloud Mode, `/compact-and`, and `/fork-and-compact` are outside this panel, so they do not participate in drag-to-reorder.
|
||||
### Sequential firing
|
||||
32. When the active conversation reaches `FinishReason::Complete`, the first prompt in the queue is removed and submitted as the next user query in the same conversation, routed through the normal submission path so slash, skill, and session-sharing paths are handled correctly.
|
||||
33. While that newly-fired prompt is mid-exchange, the rest of the queue stays intact, the panel updates the count to `<N-1> queued`, and additional prompts can still be queued at the tail.
|
||||
34. The cycle continues until either the queue is empty or one of the abort conditions in (35) fires.
|
||||
### Cancellation and error handling
|
||||
35. When the active conversation finishes for any non-`Complete` reason — `Error`, `Cancelled`, `CancelledDuringRequestedCommandExecution` — auto-fire pauses immediately. The queue is not flushed.
|
||||
36. When auto-fire pauses for one of those reasons, the head-restore behavior depends on whether the user is currently viewing the cancelled conversation in agent view:
|
||||
- If the user is **not** viewing this conversation in agent view (e.g. they triggered the cancel by exiting the agent view, or the conversation was running in the background), the queue is left fully intact — neither the head nor any other row is placed into the input.
|
||||
- If the user **is** viewing this conversation in agent view (e.g. stop button or `Ctrl-C` while in agent view):
|
||||
- If the input is currently empty, the first queued prompt is removed from the queue and its text is placed in the input box. The row is removed in this case so that re-submitting the input does not also re-fire the same prompt from the queue.
|
||||
- If the input is non-empty, the first prompt's text is not placed in the input and the queue is left intact (the first prompt remains in the queue at position 0).
|
||||
- In all cases all queue rows beyond the first remain intact in the panel, so the user can review, edit, reorder, delete, or send further prompts.
|
||||
37. Auto-fire resumes naturally the next time the active conversation reaches `FinishReason::Complete` — i.e. the user re-runs or sends a new prompt that succeeds, and from that completion onward the queue resumes draining from the top.
|
||||
38. Manually cancelling the in-progress agent (stop button or `Ctrl-C` shortcut) is treated as `Cancelled` for the purposes of (35)–(36).
|
||||
### Conversation lifecycle interactions
|
||||
39. Exiting the agent view (Esc to terminal, closing the tab/pane) does not discard the queue for that conversation. Re-entering the agent view for that conversation later restores its queue and auto-queue toggle. This matters especially for cloud agents, whose conversations continue running in the background after the user leaves the agent view; their queues continue to drain when the conversation eventually finishes, even if the user has navigated away.
|
||||
40. Starting a new conversation begins with an empty queue and the auto-queue toggle defaulted off — each conversation has its own queue and toggle, so prior conversations' state is unaffected.
|
||||
41. The queue belongs to a conversation; if the agent splits the conversation (`/fork`, `/fork-and-compact`), regular queued-prompts panel rows do not carry into the new conversation. Any summarize/fork follow-up placeholder behavior remains separate legacy pending-user-query UI, not queue-panel state.
|
||||
### Focus
|
||||
42. The auto-queue toggle keybinding (`Cmd-Shift-J` / `Ctrl-Shift-J`) is unchanged.
|
||||
43. Submitting from the main input always returns focus to the main input, even when the submission appended to the queue.
|
||||
### Telemetry
|
||||
44. Existing `/queue` and auto-queue telemetry events continue to fire. Queue-panel-specific interactions (edit committed, row deleted, row reordered, panel collapsed/expanded) are tracked as new telemetry events so we can measure usage of the new affordances.
|
||||
@@ -0,0 +1,112 @@
|
||||
# Queued Prompts UI — Technical Spec
|
||||
See `specs/REMOTE-1543/PRODUCT.md` for user-visible behavior. This document covers the implementation that supports that behavior.
|
||||
## Context
|
||||
Regular Agent Mode queued prompts are stored in an app-wide singleton keyed by conversation id, so queue state outlives the agent-view session that originated it. The rendered panel sits next to the input that hosts it, while queue data is owned globally.
|
||||
|
||||
The implementation spans four ownership layers:
|
||||
- `app/src/ai/blocklist/queued_query.rs` defines the `QueuedQueryModel` singleton. It owns every conversation's queue rows, edit state, and auto-queue toggle, indexed by `AIConversationId`. It self-manages cleanup by subscribing to `BlocklistAIHistoryModel` lifecycle events.
|
||||
- `app/src/terminal/view.rs` wires the input and panel, subscribes to panel events for input mutation, and drains queued prompts when a conversation finishes — using the `conversation_id` carried on `BlocklistAIControllerEvent::FinishedReceivingOutput`.
|
||||
- `app/src/terminal/view/queued_prompts_panel.rs` owns panel rendering and row-level interactions: collapse, edit, delete, drag reorder, and panel telemetry. The panel looks up the active conversation for its terminal view via `BlocklistAIHistoryModel` rather than duplicating that state.
|
||||
- `app/src/terminal/input.rs` and `app/src/terminal/input/slash_commands/mod.rs` route regular queue trigger surfaces into the singleton, scoped to the conversation each trigger fires against.
|
||||
|
||||
Cloud Mode placeholders and compact follow-up placeholders remain on the legacy pending-user-query path. They still use the rich-content machinery in `app/src/terminal/view/pending_user_query.rs`, `app/src/terminal/view/rich_content.rs`, and related terminal selection plumbing because their lifecycle is driven by cloud setup or summarize/fork workflows rather than by regular Agent Mode queue draining.
|
||||
## Proposed changes
|
||||
### Queue ownership and data model
|
||||
`QueuedQueryModel` is an app-wide singleton (`SingletonEntity`) keyed by `AIConversationId`. It is the source of truth for every regular queued prompt anywhere in the app (`app/src/ai/blocklist/queued_query.rs`):
|
||||
- `queues: HashMap<AIConversationId, ConversationQueueState>` stores each conversation's per-conversation state. Conversations that have never been touched have no entry; reads against an absent key return empty/false, so a missing entry is indistinguishable from a conversation with an empty queue and toggle off.
|
||||
- `ConversationQueueState { queue: Vec<QueuedQuery>, editing: Option<QueuedQueryId>, queue_next_prompt_enabled: bool }` packages all per-conversation state in one struct so a conversation's queue, in-progress edit, and auto-queue toggle live and die together.
|
||||
- `QueuedQueryId` gives each row stable identity across edit, delete, and reorder.
|
||||
- `QueuedQueryOrigin` distinguishes `/queue` rows from auto-queue rows for telemetry without affecting firing semantics.
|
||||
|
||||
Every queue/edit/toggle accessor takes the `conversation_id` it applies to, and the singleton emits `QueuedQueryEvent`s whose payload carries that `conversation_id` so subscribers can filter by the conversation they care about. Panel collapse is panel-local UI state and lives on `QueuedPromptsPanelView`, not on the singleton, because no other consumer cares.
|
||||
|
||||
`QueuedQueryModel` self-manages cleanup by subscribing to `BlocklistAIHistoryModel`:
|
||||
- `RemoveConversation { conversation_id, .. }` and `DeletedConversation { conversation_id, .. }` drop that conversation's `ConversationQueueState`.
|
||||
- `ClearedConversationsInTerminalView { cleared_conversation_ids, .. }` drops every entry whose id appears in the cleared list. The history event is extended to include `cleared_conversation_ids: Vec<AIConversationId>` — `BlocklistAIHistoryModel::clear_conversations_in_terminal_view` already collects those ids; the event just needs to carry them.
|
||||
|
||||
No subscription is added for `ExitedAgentView`. Conversations outlive their visible session — particularly for cloud agents — so the queue and its toggle state must too.
|
||||
### Trigger routing and enqueue flow
|
||||
All regular queue entry points target `QueuedQueryModel::append(conversation_id, ...)` on the singleton, scoped to the conversation the trigger fires against:
|
||||
- The auto-queue path in `Input::maybe_queue_input_for_in_progress_conversation` verifies that the conversation-scoped toggle is enabled, AI input is active, the selected conversation is in progress or blocked, and the prompt is non-empty. It then calls the singleton to append an `AutoQueueToggle` row to that `conversation_id` (`app/src/terminal/input.rs`). The toggle read uses the same conversation id.
|
||||
- `/queue <prompt>` appends a `QueueSlashCommand` row to the in-progress conversation's queue, and otherwise falls back to normal submission (`app/src/terminal/input/slash_commands/mod.rs`). The `conversation_id` is the in-progress conversation derived from the same gating check.
|
||||
- The `ToggleQueueNextPrompt` action handler in `TerminalView` (`app/src/terminal/view.rs`) looks up the view's active conversation id via `BlocklistAIHistoryModel::active_conversation_id(self.view_id)` and calls `QueuedQueryModel::toggle_queue_next_prompt(conversation_id, ctx)`. When the view has no active conversation, the action is a no-op.
|
||||
|
||||
`FeatureFlag::QueueSlashCommand` remains the single gate for the regular queue experience. It covers the trigger surfaces above and the panel attachment/render path; there is no separate panel-specific rollout switch.
|
||||
### Panel composition and interaction ownership
|
||||
`Input::new` constructs `QueuedPromptsPanelView` when the regular queue feature is available, passing the parent terminal view's `EntityId` so the panel can look up the active conversation it should render for (`app/src/terminal/input.rs`). The panel handle is stored on `Input`, and the input render tree places the panel between the status bar and the editor (`app/src/terminal/input/agent.rs`), matching the product placement contract. `Input` subscribes to panel events for cross-component side effects.
|
||||
|
||||
`QueuedPromptsPanelView` intentionally owns only queue-panel concerns (`app/src/terminal/view/queued_prompts_panel.rs`):
|
||||
- It renders the queue header, expanded rows, hover controls, inline edit editor, and drag handles.
|
||||
- Static rows render bounded multiline previews: prompt text is character-trimmed before rendering, then constrained to a compact maximum height so queued prompts remain readable without letting one row dominate the panel.
|
||||
- Edit mode reuses the multiline editor pattern used elsewhere in the client (`EditorOptions` with autogrow + soft wrap), constrains the editor to the same visual height as the static preview, and wraps it in a clipped outlined scroll surface with a scrollbar so larger edits stay bounded inside the row.
|
||||
- It mutates queue state through `QueuedQueryModel` methods scoped to the panel's current active conversation: `enter_edit_mode`, `remove_by_id`, `commit_edit`, `cancel_edit`, and `reorder`.
|
||||
- It emits higher-level `QueuedPromptsPanelEvent`s when the host view must coordinate with input focus or buffer placement.
|
||||
- Panel-only UI state — `collapsed`, `row_states`, drag state — lives on the view itself, not on the singleton, because no other consumer cares.
|
||||
|
||||
The panel resolves "which conversation am I rendering for?" via `BlocklistAIHistoryModel::active_conversation_id(terminal_view_id)`. It subscribes to `BlocklistAIHistoryEvent::SetActiveConversation` filtered to its terminal view so it can re-seed `row_states` and reset `collapsed` when the active conversation changes. It also subscribes to `QueuedQueryModel` events and ignores any whose `conversation_id` does not match the current active conversation.
|
||||
|
||||
`TerminalView::handle_queued_prompts_panel_event` (delegated through `Input`) owns the cross-component consequences the panel should not perform directly: focus restoration and placing deleted text into the main input when the input is empty.
|
||||
### Drain behavior and conversation lifecycle
|
||||
When a conversation finishes, `TerminalView` decides how that conversation's queued prompts advance; the queued prompts panel only renders and edits queued rows. `BlocklistAIControllerEvent::FinishedReceivingOutput` carries the `conversation_id` that just finished, and `TerminalView::handle_ai_controller_event` threads it through `handle_finished_conversation` into `drain_queued_prompts(conversation_id, finish_reason, ctx)` (`app/src/terminal/view.rs`). Explicit threading keeps drain correct when the finishing conversation is not the view's currently-active conversation (e.g. a backgrounded child agent).
|
||||
|
||||
`drain_queued_prompts` branches on `FinishReason`:
|
||||
- `Complete`: pop one queued row from that conversation via `QueuedQueryModel::pop_for_autofire(conversation_id, ctx)`; submit it through `Input::submit_queued_prompt`, or place it into the input if the row was first in queue and in edit mode.
|
||||
- `Error`, `Cancelled`, or `CancelledDuringRequestedCommandExecution`: first gate on `AgentViewController::agent_view_state().active_conversation_id() == Some(conversation_id)` — restore only when the user is currently viewing this conversation in agent view. `AgentViewController::exit_agent_view_internal` flips the state to `Inactive` before emitting `ExitedAgentView`, so cancels driven by agent-view exit reach the drain with the state already cleared and skip the restore. Backgrounded conversations the user isn't viewing also skip on the same check. Once gated past that, if the input is empty, pop the first row of that conversation's queue and place its text into the input; otherwise leave the queue untouched.
|
||||
|
||||
The model owns row-removal details and per-conversation state mutation, while the terminal owns submission and input mutation. This division keeps queue semantics testable in `queued_query_tests.rs` while preserving terminal-specific side effects in `queued_prompts_tests.rs`.
|
||||
|
||||
Queue and toggle state are dropped only by `QueuedQueryModel`'s own subscriptions to `BlocklistAIHistoryModel`:
|
||||
- `RemoveConversation { conversation_id, .. }` and `DeletedConversation { conversation_id, .. }` drop that conversation's `ConversationQueueState` entry.
|
||||
- `ClearedConversationsInTerminalView { cleared_conversation_ids, .. }` drops every entry in the cleared list. The event is extended to carry `cleared_conversation_ids: Vec<AIConversationId>` alongside the existing `active_conversation_id`.
|
||||
- `ExitedAgentView` is intentionally not subscribed to. Conversations and their queues outlive the agent-view session that originated them; re-entering the agent view, or switching to the conversation from anywhere else, restores the same queue and toggle state.
|
||||
### Compatibility boundary for legacy pending placeholders
|
||||
The regular queue subsystem does not absorb placeholder flows whose lifecycle is unrelated to conversation-completion draining:
|
||||
- Cloud Mode initial/follow-up placeholders continue using pending-user-query rich content.
|
||||
- `/compact-and` and `/fork-and-compact` continue using the summarize/fork placeholder path.
|
||||
|
||||
This boundary matters architecturally because these placeholders are owned by cloud or summarize/fork workflows, not by `QueuedQueryModel`. Keeping them separate avoids forcing prompt placeholders into queue APIs whose responsibilities are append, inspect, edit, reorder, and drain regular Agent Mode follow-ups.
|
||||
### Telemetry
|
||||
Panel-only interaction telemetry is emitted from `QueuedPromptsPanelView`, where the interaction actually occurs:
|
||||
- `QueuedPrompt.Edited`
|
||||
- `QueuedPrompt.Deleted`
|
||||
- `QueuedPrompt.Reordered`
|
||||
- `QueuedPrompt.PanelCollapseToggled`
|
||||
|
||||
`app/src/server/telemetry/events.rs (1205-1228, 2947-2971, 5848-5859)` mirrors queue-row origin into telemetry payloads and associates these events with `FeatureFlag::QueueSlashCommand`.
|
||||
## End-to-end flow
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["Input auto-queue or /queue"] --> B["QueuedQueryModel::append(conversation_id, ...)"]
|
||||
B --> E["Panel renders queue for the view's active conversation"]
|
||||
E --> F["Panel edits / deletes / reorders rows"]
|
||||
F --> G["QueuedQueryModel mutates the conversation's state"]
|
||||
G --> E
|
||||
H["FinishedReceivingOutput { conversation_id }"] --> I["TerminalView::drain_queued_prompts(conversation_id, ...)"]
|
||||
I --> J["QueuedQueryModel::pop_for_autofire(conversation_id)"]
|
||||
J --> K["Submit queued prompt via Input"]
|
||||
J --> L["Restore text into input when behavior requires it"]
|
||||
M["RemoveConversation / DeletedConversation / ClearedConversationsInTerminalView"] --> N["QueuedQueryModel drops affected ConversationQueueState entries"]
|
||||
```
|
||||
## Testing and validation
|
||||
Map tests directly to the product behavior in `specs/REMOTE-1543/PRODUCT.md`:
|
||||
- Behaviors 4-11: regular queue gating, `/queue`, auto-queue, shell-mode exclusion, and per-conversation isolation should stay covered by terminal/input-level tests plus slash-command coverage. Add coverage that appending or toggling on one conversation does not affect another conversation's state.
|
||||
- Behaviors 12-31: row rendering, collapse/edit/delete/reorder semantics belong in `app/src/terminal/view/queued_prompts_tests.rs` and `app/src/ai/blocklist/queued_query_tests.rs`. Tests construct queues against explicit conversation ids and assert per-conversation isolation.
|
||||
- Behaviors 32-38: sequential firing, edit-mode drain handling, and cancellation/error restoration belong in `TerminalView::drain_queued_prompts` coverage in `app/src/terminal/view/queued_prompts_tests.rs`. Drain tests construct the queue for a specific conversation id and verify the popped row is from that conversation.
|
||||
- Behaviors 39-41: conversation/terminal/Agent View cleanup belong in queue-model lifecycle tests. Specifically cover (a) `ExitedAgentView` does not touch queue state, (b) `RemoveConversation`/`DeletedConversation` drops only the targeted conversation's entry, and (c) `ClearedConversationsInTerminalView` drops every conversation in `cleared_conversation_ids`. Also cover the auto-queue toggle: it persists across agent-view exit and is dropped together with the queue on cleanup.
|
||||
- Behavior 44: telemetry payload/origin plumbing should be covered where telemetry event serialization or event wiring already has local test patterns.
|
||||
|
||||
Validation for this implementation should use:
|
||||
- `cargo fmt`
|
||||
- Targeted compile/test coverage for queued prompt model and terminal view queue behavior
|
||||
- Full presubmit before PR submission
|
||||
|
||||
Do not run the app as part of this change.
|
||||
## Parallelization
|
||||
Parallel child agents are not especially helpful for implementing this feature because the queue model, panel, input routing, and terminal drain semantics share tight ownership boundaries and must remain consistent across one architectural thread. Review and validation can be parallelized later, but the primary implementation should stay in a single workstream to avoid churn across the same types and event contracts.
|
||||
## Risks and mitigations
|
||||
- **Queue lifecycle drifting from conversation lifecycle**: centralize cleanup inside `QueuedQueryModel`'s own subscriptions to `BlocklistAIHistoryModel` (deletion + clear-conversations). `TerminalView` is no longer responsible for clearing queue state; agent-view exit is intentionally not a cleanup trigger.
|
||||
- **Drain firing against the wrong conversation**: thread `conversation_id` from `BlocklistAIControllerEvent::FinishedReceivingOutput` explicitly into `drain_queued_prompts` rather than relying on the view's "active conversation" being the same as the finished one.
|
||||
- **Panel rendering stale rows when active conversation changes**: re-seed `row_states` and reset `collapsed` in the panel's `BlocklistAIHistoryEvent::SetActiveConversation` subscription, and ignore `QueuedQueryEvent`s whose `conversation_id` does not match the current active conversation.
|
||||
- **Panel owning terminal/input side effects**: keep focus restoration and input-buffer placement in `TerminalView::handle_queued_prompts_panel_event`.
|
||||
- **Drain behavior losing edit-mode or cancellation semantics**: keep firing policy in `TerminalView::drain_queued_prompts` and row-removal mechanics in `QueuedQueryModel`.
|
||||
- **Compatibility placeholders leaking into regular queue abstractions**: keep Cloud Mode and compact follow-up placeholders on their existing pending-user-query path because their ownership and removal semantics differ.
|
||||
Reference in New Issue
Block a user