Skip to content

wta(tui): queue prompts while busy, Esc to dequeue LIFO#278

Open
yeelam-gordon wants to merge 32 commits into
mainfrom
dev/yeelam/wta-input-queue
Open

wta(tui): queue prompts while busy, Esc to dequeue LIFO#278
yeelam-gordon wants to merge 32 commits into
mainfrom
dev/yeelam/wta-input-queue

Conversation

@yeelam-gordon

@yeelam-gordon yeelam-gordon commented Jun 12, 2026

Copy link
Copy Markdown
Collaborator

Reopens #46 (closed unmerged); rebuilt on top of current main (152 commits behind at revival; merge commit da6802685).

Summary

Adds a per-tab pending-prompt queue to the WTA TUI. Today, pressing Enter while a turn is in flight surfaces a "Agent is busy on this tab — wait for the current prompt to finish." system message and discards the user''s typing. With this change:

  • Enter while busy → push the prompt onto the tab''s pending_prompts queue and clear the input box.
  • FIFO drain → when the turn returns to an accepting state (accepts_new_prompt() && recommendations().is_none() && !loading_session) the front of the queue is dispatched automatically.
  • Esc behavior with an empty input box, in priority order:
    1. Dismiss any visible overlay (help / notifications / recommendation card / suggested-pane indicator).
    2. Pop the back of pending_prompts (LIFO undo) — if the queue is non-empty, Esc rescinds the most-recently queued prompt. The in-flight head (whether autofix or a user turn) keeps running. Repeated Esc presses peel queued prompts off the back one by one.
    3. Cancel the in-flight head like /stop — only fires once the queue is empty.
    4. Fallback → clear input draft (existing behavior).
  • Visible indicator → a 1-row Queued (N): preview… rendered directly above the input box.
  • Bounded at PENDING_PROMPT_QUEUE_CAP = 20. Over-cap keeps the user''s input intact and shows a transient hint instead of silently dropping it.
  • Queue cleared on reset/clear, /new, /restart, tab reset and session-load all funnel through clear_chat_history(), which now drops pending_prompts so stale prompts can''t fire into a fresh session.
image

Design notes

  • Why queue-pop beats cancel-head: in the autofix-with-queue scenario (autofix in flight, user typed a follow-up question), users want a single Esc to rescind their typed question — not tear down the autofix the system started. Queue items represent work the user added; the head may have been added by autofix. Peeling from the back first matches the user''s mental model of "undo my most recent action".
  • cancel_in_flight_turn() helper shared between Ctrl+C and the new Esc branch. Falls back to DEFAULT_TAB_ID when the focused tab has no ACP session id yet — same convention the Enter dispatch path uses, so the local state machine still resets correctly even before SessionAttached.
  • Drain hook lives at the end of every event-handling tick (both ui_rx and event_rx paths) rather than wired into each individual TurnState transition. Error paths (TabError, AgentError), stale-autofix discards, and the autofix Ignore decision are all covered uniformly without scattering callsites.
  • Per-tick at most one dispatch per tabturn_submit_prompt flips state to Submitted, so a second pass would no-op. Per-tab FIFO order is deterministic; cross-tab order is sorted alphabetically for log reproducibility.
  • Card-visible blocks drain — when a recommendation card is staged (Surfaced { end_pending: false }) accepts_new_prompt() returns true, but a queued prompt going through turn_submit_prompt would clear selected_recommendation and wipe the card before the user can Run / Insert / dismiss.
  • Tab-scoped dispatch — walks tab_sessions, resolves session-id per tab. Background tabs without an ACP session_id defer drain (avoids the tab_for_session(DEFAULT_TAB_ID) → focused-tab misroute).
  • Cached collapsed previewQueuedPrompt::new() precomputes the whitespace-collapsed text once at enqueue so the per-frame UI render is allocation-free.

Merge-with-main notes (vs. original #46)

Major drift reconciled when bringing this back on top of current main (152 commits later):

  • App::new signature grew — added rename_session_tx, master_tx, Arc<ShellManager>. Test helpers updated.
  • TabSession gained agents_view + pane_open alongside the PR''s new pending_prompts field.
  • input_cursor_position deleted on main (caret is now an inverse buffer cell). Branch''s edit to that function was dropped as dead code; cursor placement no longer needs adjusting around the queue indicator row.
  • View enum hotkey doc changed F2 → Ctrl+Shift+/ (doc-only).
  • Cleaner Esc-on-recommendation path — now routes through turn_cancel(DEFAULT_TAB_ID) (which handles autofix cleanup) instead of duplicating the cleanup inline.

No PR features dropped.

Tests

cargo test --target x86_64-pc-windows-msvc --manifest-path tools/wta/Cargo.toml751 / 751 passing (725 baseline + 26 new tests added by this PR).

Core queue mechanics — tools/wta/src/app.rs

Test Covers
enter_while_busy_queues_prompt_without_user_bubble Enter mid-flight enqueues; no premature User bubble; input cleared
enter_during_session_load_queues_instead_of_dispatching Mid-loadSession Enter queues instead of racing the replay; drain holds while loading; drain dispatches once load completes
drain_dispatches_queued_prompts_fifo_when_turn_completes FIFO drain after turn_cancel
drain_skips_when_recommendation_card_visible Card blocks auto-drain (Surfaced { end_pending: false })
drain_skips_tab_with_pending_permission_request Defensive: tabs with non-empty permission queue are skipped
drain_no_ops_when_disconnected Transport drop defers drain
queue_full_keeps_input_and_emits_hint Cap (PENDING_PROMPT_QUEUE_CAP=20) enforced; input preserved on overflow
clear_chat_history_drops_pending_prompts /clear, /new, /restart, session-load reset all wipe queue

Esc / Ctrl+C / /stop cascade

Test Covers
esc_pops_queue_before_cancelling_in_flight Queue-pop priority over in-flight cancel (LIFO undo first)
esc_pops_back_of_queue_when_idle_and_input_empty Idle + queue → LIFO pop from back
esc_cancels_in_flight_turn_when_input_empty Empty-queue + in-flight Esc behaves like /stop
esc_cancels_autofix_only_when_queue_is_empty Reported autofix-with-queue scenario: user-typed prompts get rescinded first; autofix isn''t torn down
esc_does_not_pop_queue_when_input_is_not_empty Draft preserved; queue + in-flight preserved
esc_cancel_in_flight_with_empty_queue_still_clears_correctly Regression-pin: empty queue stays empty across the cancel
ctrl_c_clears_pending_prompts_queue_and_blocks_auto_dispatch Ctrl+C halts all tab work — no auto-fire of next queued prompt on the post-cancel drain tick
slash_stop_clears_pending_prompts_queue /stop semantics match Ctrl+C (single source of truth via cancel_in_flight_turn)

Per-tab / multi-tab isolation (no cross-tab leakage)

Test Covers
esc_only_pops_focused_tabs_queue_not_background_tabs Esc on Tab A leaves Tab B''s queue + turn state untouched
drain_uses_loop_tab_id_for_pane_context_not_focused_tab Background-tab drain attributes the dispatched prompt to the right tab id, not the focused one
drain_holds_queue_when_tab_has_no_session_id Background tabs without an ACP session_id defer drain rather than misroute via DEFAULT_TAB_ID
drain_dispatches_to_multiple_idle_tabs_in_same_tick Two background tabs both idle + both queued → both drain in one tick with correct per-tab session_ids
drain_dispatches_only_idle_tabs_leaves_busy_tabs_queued Mixed idle/busy: only idle tabs drain; busy tab''s queue stays intact
drain_per_tab_fifo_independent_across_tabs Two tabs with multi-item queues advance heads independently (per-tab FIFO + cross-tab fairness)
clear_chat_history_on_one_tab_preserves_other_tabs_queue Reset on Tab A doesn''t touch Tab B''s pending_prompts

QueuedPrompt data structure

Test Covers
queued_prompt_preview_truncates_and_collapses_whitespace Cached preview collapses runs of whitespace + truncates to budget
queued_prompt_preview_zero_budget_is_empty preview(0) honors the char-budget contract
queued_prompt_caps_collapsed_storage Storage cap prevents unbounded memory for very long prompts

queued_hint width-aware truncation — tools/wta/src/ui/queued_hint.rs

Test Covers
truncate_under_width_keeps_string Fast path: fits as-is
truncate_over_width_inserts_ellipsis Standard truncation appends
truncate_zero_width_returns_empty max_cells=0 → empty string (defensive)
truncate_respects_width_with_combining_marks Width-correct truncation with zero-width chars (e.g. a\u{0301})
truncate_wide_char_with_narrow_budget_emits_ellipsis CJK + max_cells=1 → bare
truncate_wide_char_with_two_cell_budget_emits_ellipsis_only Width contract under tight budget (2 cells = CJK char would overflow with ellipsis → bare )

Files

File Change
tools/wta/src/app.rs QueuedPrompt type with cached collapsed, pending_prompts field, Enter enqueue, Esc cascade (queue-pop → cancel-in-flight → clear-input), cancel_in_flight_turn() helper shared with Ctrl+C, drain_pending_prompts(), drain hooks in run(), 13 new unit tests
tools/wta/src/ui/queued_hint.rs new — renders Queued (N): preview… row with width-correct truncation (handles CJK, combining marks, narrow panes)
tools/wta/src/ui/layout.rs reserve + render queue row above input; cursor positioning replicates render''s chat_estimate.min(chat_max) clamp + welcome-hint reservation
tools/wta/src/ui/mod.rs export queued_hint
tools/wta/locales/*.yml input.queue.indicator/removed/full keys in all 89 locales

Out of scope

  • RTL (Arabic/Hebrew) bidirectional reordering of chat/input rows — separate, larger investigation. (Queue strings ARE translated for RTL locales; only chat-pane bidi reordering is deferred.)

Copilot AI and others added 20 commits May 22, 2026 23:01
While a turn is in flight, pressing Enter on a non-empty input no longer surfaces a 'Agent is busy' system message and discards the user's typing. Instead, the prompt is pushed onto a per-tab VecDeque and dispatched FIFO once the turn returns to an accepting state (and no recommendation card is staged).

Esc pops the back of the queue (LIFO undo) when the input box is empty, so users can cancel queued messages one by one in the order they would un-add them. Esc on a non-empty input keeps the existing 'clear draft' behavior so editing isn't lost.

A 1-row 'Queued (N): preview...' indicator renders directly above the input box whenever the queue is non-empty. The queue is bounded (PENDING_PROMPT_QUEUE_CAP = 20) and is cleared by /clear, /new, /restart, tab reset, and session-load paths (all funneled through clear_chat_history).

Drain hook lives at the end of every event-handling tick rather than wired into individual TurnState transitions, so error paths (TabError/AgentError), autofix 'Ignore' decisions, stale-autofix discards and the natural turn-complete path are all covered uniformly. Card-visible state explicitly blocks drain so a queued prompt can't wipe a surfaced recommendation before the user can act on it.

Tests: 9 new unit tests cover enqueue-on-busy, FIFO dispatch on turn complete, LIFO Esc pop, Esc-with-text preserving draft, card-visible blocking drain, queue cap with input preservation, clear_chat_history clearing the queue, disconnected drain no-op, and preview truncation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1. drain_pending_prompts: use the loop's tab_id for PaneContext.tab_id instead of self.tab_id (focused WT tab). Background-tab drains now correctly attribute the dispatched prompt to the tab whose queue was actually drained.

2. queued_hint::render: subtract HORIZONTAL_PADDING from area.width before truncation so the 2-space left padding doesn't push the localized indicator off the right edge.

Adds drain_uses_loop_tab_id_for_pane_context_not_focused_tab test (281 total).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Revert vcpkg triplet to v143 (the v145 bump was a local build-env workaround and leaked into the PR by accident; this PR doesn't touch the toolchain).

* drain_pending_prompts: sort tab_ids for deterministic cross-tab dispatch order; update doc comment to clarify ordering guarantees.

* layout.rs: update stale cursor-position comment to reflect the new chunk index (7 not 6) after inserting the queued_hint row.

* queued_hint::truncate_to_width: emit a single '...' when the budget is too narrow to fit even one char (e.g. wide-glyph + max_cells=1). Adds truncate_wide_char_with_narrow_budget_emits_ellipsis test.

* clear_chat_history: tighten comment grammar.

* Fix CodeQL spelling: 'pre-empt' -> 'preempt' in test assertion.

282 tests pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Reserve 1 cell upfront for the ellipsis instead of post-trimming after appending. The old algorithm could shave off the ellipsis itself (max_cells==5 with 5 chars left no room) and could yield strings whose display width exceeded max_cells when the last grapheme carried zero-width combining marks.

New algorithm:
1. Fast path: if UnicodeWidthStr::width(text) <= max_cells, return as-is.
2. Otherwise, content_budget = max_cells - 1. Emit chars until budget exceeded, then append the ellipsis. Result width is provably <= max_cells.
3. max_cells == 0 -> empty; max_cells == 1 with any overflow -> bare ellipsis.

Adds 2 new tests covering wide-char and combining-mark cases. 284 tests pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds input.queue.indicator, input.queue.removed, and input.queue.full to all 88 non-English locale files (including pseudo-locales qps-ploc, qps-ploca, qps-plocm). Comment block is translated alongside the values. Placeholders (%{count}, %{preview}, %{cap}) and the Esc keybind label are preserved verbatim per the {Locked="Esc"} directive.

Per .github/instructions/rust-localization.instructions.md, all locale files must include every key from en-US.yml. This closes the localization gap flagged in Copilot review round 3.

284 tests pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* queue_hint_height: doc comment reworded ("for the current tab" instead of "for tab") since the function takes &App and reads current_tab() internally.
* truncate_to_width fast-path comment: clarified that UnicodeWidthStr semantics are correct for the fast path (combining marks add 0 visible columns) and the "zero-width to 1 cell" treatment only matters inside the truncation loop for break-decision budgeting.
* truncate_over_width_inserts_ellipsis test: comment updated to reflect the reserve-1-for-ellipsis algorithm; added a width assertion to pin the contract.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
In terminals where area.width < HORIZONTAL_PADDING (i.e. < 2 cells), the previous code rendered a row of pure padding because truncate_to_width returned an empty string while the format!("  {}", ...) still prepended two literal spaces.

Now the padding shrinks to whatever room is left after the truncated body, so even at width=1 we render the single visible cell from truncate_to_width (a bare ellipsis) instead of silently blanking the indicator.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Three follow-ups to the round-6 Copilot advisories (suppressed low-confidence):

1. Single truncation owner: QueuedPrompt::preview was clipping at char count + ellipsis, then ui/queued_hint::truncate_to_width was clipping again at cell width + ellipsis — risk of doubled "..." on the same string. Split responsibilities: QueuedPrompt::collapsed_text only collapses whitespace; queued_hint owns all width-aware truncation. preview() stays for the transient hint where char-based clipping is fine.

2. Narrow-pane fix: when area.width is 1 or 2 the previous code passed budget=0 to truncate_to_width, which returned "" and the row rendered as pure padding. Now budget is floored at 1 whenever area.width >= 1, so the truncation marker is always visible.

3. drain_pending_prompts early-out: a single any()-scan skips the keys-clone + sort entirely when no tab has anything queued. Runs after every UI tick; common case stays allocation-free.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1. Cache collapsed preview at enqueue time. QueuedPrompt now holds a pre-collapsed `collapsed: String` populated by `QueuedPrompt::new(text)`. `ui/queued_hint::render` runs every frame; the old `split_whitespace().collect::<Vec<_>>().join(" ")` allocated a temporary Vec on every draw. The new `collapse_whitespace` helper streams chars into the output string with no intermediate Vec, allocating only the final result once at enqueue.

2. Gate background-tab drain on session_id. `drain_pending_prompts` was using `DEFAULT_TAB_ID` as a fallback when a tab had no ACP session id yet. That fallback resolves via `tab_for_session` → `self.tab_id` (the focused tab), so a background tab draining before its `SessionAttached` arrived would route `turn_submit_prompt` to the wrong tab. Now the fallback is only allowed when the draining tab IS the focused one; otherwise the queue is held until the tab's `SessionAttached` arrives (next event tick retries).

Adds `drain_holds_queue_when_tab_has_no_session_id` regression test. 285 tests pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1. input_cursor_position now mirrors render's chat_height clamp. Previously render did `chat_estimate.min(chat_max)` accounting for everything reserved below (rec/perm/input/hints/queue), but input_cursor_position used `chat_estimate` raw. When chat overflowed the available area, the input chunk index ended up at a different Y in the two layouts and the cursor landed on the wrong row. Replicates the same `reserved_below` computation and `chat_estimate.min(chat_max)` clamp.

2. input_cursor_position now reserves a hint row for both welcome_hint and transient_hint (was: transient only). Mirrors render's logic so the cursor doesn't drift by 1 row while the first-run welcome hint is visible. Suppressed-low-confidence advisory from the same review.

3. queued_hint::render uses HORIZONTAL_PADDING constant instead of hard-coded 2 for the prefix clamp, so the padding budget stays in lockstep if the constant ever changes.

285 tests pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Per user feedback: "Esc should stop for the 'thinking', i.e. dequeue. Just like the comment /stop, but since we have a queue, that's why I said dequeue."

Esc now acts like /stop on a busy state — cancels the current Thinking… (the head of the dispatch queue). With queued items behind it, the per-tick drain hook promotes the next one on the following event tick, so repeated Esc presses peel work off the head one-by-one.

Priority cascade on Esc (input empty):
1. Existing overlay dismissals (help, notifications, recommendations/autofix, suggested-pane).
2. NEW: in-flight turn (Submitted/Streaming/Surfaced+end_pending) -> cancel via shared cancel_in_flight_turn() helper. Queue preserved; auto-drain promotes the next item on the next tick.
3. Queue non-empty + idle + input empty -> pop LIFO (back of queue, undo).
4. Fallback -> clear input.

Refactor: extracted cancel_in_flight_turn() from the Ctrl+C handler so both entry points share the same cancel logic. Falls back to DEFAULT_TAB_ID when the focused tab has no ACP session id yet — same convention the Enter dispatch path uses, so the local state machine still resets correctly even before SessionAttached.

Test changes:
- esc_pops_back_of_queue_when_input_empty renamed/refocused to esc_pops_back_of_queue_when_idle_and_input_empty (cancels in-flight first so subsequent Esc presses target the queue).
- esc_does_not_pop_queue_when_input_is_not_empty now also asserts the in-flight turn is preserved when the draft is non-empty.
- NEW esc_cancels_in_flight_turn_when_input_empty pins the /stop behavior and the "queue survives one Esc" contract.

286 tests pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
User reported scenario:
1. Made an error (autofix triggers, autofix turn in flight)
2. Typed a follow-up question (queued)
3. Pressed Esc once
Expected: queue.pop() of the user question, autofix continues.
Actual (previous): autofix was cancelled — Esc cancelled the head before the queue was empty.

Root cause: my last change put cancel_in_flight_turn AHEAD of the queue-pop branch, AND the pre-existing recommendations/autofix branch also bundled "autofix in flight" into card-dismiss. Both bypassed the queue.

Fix:
* Reorder the Esc cascade so queue-pop runs BEFORE in-flight cancel. With a non-empty queue, Esc peels the most-recently queued item off the back (LIFO) and leaves the head running. Once the queue is empty, the next Esc cancels the head.
* Split the recommendations branch from the autofix-in-flight branch: card-dismiss still has priority (the card blocks drain), but autofix-in-flight is now handled by the generic in-flight cancel below (which yields to queue-pop).

New cascade on Esc (input empty):
1. selected_completed_turn_idx -> clear selection
2. help_overlay_visible -> hide
3. show_notification_banner -> dismiss
4. recommendations().is_some() -> turn_cancel (card dismiss)
5. suggested_pane_id.is_some() -> clear pane indicator
6. NEW PRIORITY: pending_prompts non-empty -> pop_back (LIFO)
7. in-flight (Submitted/Streaming/Surfaced+end_pending) -> cancel_in_flight_turn (covers autofix and user prompts)
8. fallback -> clear_input

Tests:
* NEW esc_pops_queue_before_cancelling_in_flight — single Esc with queue=1 + in-flight pops queue, head survives.
* NEW esc_cancels_autofix_only_when_queue_is_empty — pins the reported autofix scenario: first Esc pops queue, autofix continues; second Esc cancels autofix.
* esc_cancels_in_flight_turn_when_input_empty tightened to start with an empty queue (otherwise it would test the queue-pop path now).
* Existing esc_pops_back_of_queue_when_idle_and_input_empty unchanged.

288 tests pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1. QueuedPrompt::preview(0) now returns String::new() instead of "…" (1 char), honoring the documented "at most max_chars" contract. Added queued_prompt_preview_zero_budget_is_empty regression test.
2. queued_hint::HORIZONTAL_PADDING: comment reworded to describe the actual behavior (two-cell left indent used by the prefix and subtracted from the truncation budget) — was misleadingly claiming it mirrored layout left/right padding.

289 tests pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Resolves conflict in tools/wta/src/app.rs Esc handler. Main #42 bundled
"autofix in-flight" into the recommendations Esc branch as part of the
per-tab autofix refactor. This branch deliberately keeps that branch
recommendations-only — the in-flight autofix case is handled by the
generic cancel_in_flight_turn below, which yields to queue-pop first so
the user can peel queued prompts they typed during autofix.

Test updated to use the new per-tab autofix state (tab.autofix.pane_id /
tab.autofix.generation) introduced by main #42.

303 tests pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1. cancel_in_flight_turn doc comment now reflects the real Esc cascade: queue-pop runs BEFORE the helper, so Esc only cancels the head when pending_prompts is empty (or the call comes from Ctrl+C, which has no queue-pop short-circuit). Previous comment misleadingly claimed "each Esc cancels the current head; the queue keeps rotating".

2. PR description rewritten to match the actual queue-pop-first behavior. Includes a "Why queue-pop beats cancel-head" rationale explaining the autofix-with-queue scenario.

3. Spelling: renamed drain_noops_when_disconnected -> drain_no_ops_when_disconnected and adjusted the preview test fixture to "  hello  \n\n  world   " (extra spaces around literals) so the spell checker no longer reads "\nworld" as the word "nworld".

303 tests pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Per Copilot review: the no-session-id branch only cleared autofix state but didn't reset the turn, so the card would persist on a brand-new tab whose recommendation surfaced before SessionAttached arrived. Now mirrors cancel_in_flight_turn: when tab.session_id is None, fall back to DEFAULT_TAB_ID which resolves via tab_for_session -> self.tab_id to the focused tab. turn_cancel then dismisses the card consistently in both branches.

303 tests pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1. QueuedPrompt::collapsed is now capped at COLLAPSED_PREVIEW_CAP = 256 chars (well over the ~60-cell visible indicator). collapse_whitespace_capped breaks out of the scan as soon as the cap is reached, so a paste-large prompt no longer costs unbounded allocation/scanning on every UI frame through t!() interpolation. Full text remains intact on QueuedPrompt::text for ACP dispatch — only the rendering cache is capped. Test: queued_prompt_caps_collapsed_storage.

2. Pinning the per-tab isolation contract with esc_only_pops_focused_tabs_queue_not_background_tabs: pressing Esc on the focused tab only touches its own queue + in-flight head; a background tab's queue and in-flight state stay untouched. Confirms the design audit:
   - Esc handler -> current_tab_mut().pending_prompts.pop_back()
   - Esc handler -> cancel_in_flight_turn() (uses current_tab())
   - Enter handler -> current_tab_mut().pending_prompts.push_back(...)
   - clear_chat_history() -> self.pending_prompts.clear() (caller picks tab via current_tab_mut())
   - drain_pending_prompts iterates tabs but resolves each tab's own session_id; never cross-routes.

305 tests pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The truncate_respects_width_with_combining_marks test used "a\u{0301}bcdef"
as the input — the trailing "bcdef" was arbitrary filler to force the
truncation loop past the 1-cell budget, but the spell checker (correctly)
flagged it as an unrecognized word.

Swapped to "a\u{0301}1234". Digits don't trip check-spelling, and the test
contract is unchanged: one grapheme `á` followed by *something* that
pushes past max_cells=1 to verify the combining-mark width handling.
Added a comment explaining why the trailing chars are deliberately
digits so future maintainers don't wonder either.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Removed the reference to a non-existent `PREVIEW_MAX_CHARS` constant (it was
deleted in an earlier review round when we switched to width-based
truncation via `truncate_to_width`). The cap is an O(n) defense against
unbounded per-frame work; the actual visible width is bounded by
`area.width` via the renderer's width-based truncation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…queue

# Conflicts:
#	tools/wta/locales/am-ET.yml
#	tools/wta/src/app.rs
#	tools/wta/src/ui/layout.rs
#	tools/wta/src/ui/mod.rs
Copilot AI review requested due to automatic review settings June 12, 2026 02:00

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds a per-tab pending-prompt queue to the WTA TUI so user input isn’t discarded when a tab is busy, with automatic FIFO draining when the tab becomes ready again and an Esc cascade that dequeues queued prompts (LIFO) before cancelling the in-flight turn. This also introduces a one-row “Queued (N): …” indicator above the input box and localizes the new strings across all WTA locales.

Changes:

  • Add TabSession::pending_prompts plus enqueue/drain/cancel behavior in App (Enter queues while busy; Esc pops queued prompts before cancelling head; per-tick drain).
  • Add a new queued-hint UI row and update layout to reserve/render it above the input box.
  • Add input.queue.* localization keys across all tools/wta/locales/*.yml.

Reviewed changes

Copilot reviewed 93 out of 93 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tools/wta/src/app.rs Implements queued prompt storage, drain hook, and updated Enter/Esc/Ctrl+C behaviors with unit tests.
tools/wta/src/ui/layout.rs Reserves and renders a queued-hint row above the input; updates popup anchoring.
tools/wta/src/ui/mod.rs Wires the new queued_hint UI module.
tools/wta/src/ui/queued_hint.rs New width-aware queued-hint row renderer and truncation tests.
tools/wta/locales/af-ZA.yml Adds input.queue.* localized strings.
tools/wta/locales/am-ET.yml Adds input.queue.* localized strings (also includes broader unrelated string churn).
tools/wta/locales/ar-SA.yml Adds input.queue.* localized strings.
tools/wta/locales/as-IN.yml Adds input.queue.* localized strings.
tools/wta/locales/az-Latn-AZ.yml Adds input.queue.* localized strings.
tools/wta/locales/bg-BG.yml Adds input.queue.* localized strings.
tools/wta/locales/bn-IN.yml Adds input.queue.* localized strings.
tools/wta/locales/bs-Latn-BA.yml Adds input.queue.* localized strings.
tools/wta/locales/ca-Es-VALENCIA.yml Adds input.queue.* localized strings.
tools/wta/locales/ca-ES.yml Adds input.queue.* localized strings.
tools/wta/locales/cs-CZ.yml Adds input.queue.* localized strings.
tools/wta/locales/cy-GB.yml Adds input.queue.* localized strings.
tools/wta/locales/da-DK.yml Adds input.queue.* localized strings.
tools/wta/locales/de-DE.yml Adds input.queue.* localized strings.
tools/wta/locales/el-GR.yml Adds input.queue.* localized strings.
tools/wta/locales/en-GB.yml Adds input.queue.* localized strings.
tools/wta/locales/en-US.yml Adds input.queue.* localized strings.
tools/wta/locales/es-ES.yml Adds input.queue.* localized strings.
tools/wta/locales/es-MX.yml Adds input.queue.* localized strings.
tools/wta/locales/et-EE.yml Adds input.queue.* localized strings.
tools/wta/locales/eu-ES.yml Adds input.queue.* localized strings.
tools/wta/locales/fa-IR.yml Adds input.queue.* localized strings.
tools/wta/locales/fi-FI.yml Adds input.queue.* localized strings.
tools/wta/locales/fil-PH.yml Adds input.queue.* localized strings.
tools/wta/locales/fr-CA.yml Adds input.queue.* localized strings.
tools/wta/locales/fr-FR.yml Adds input.queue.* localized strings.
tools/wta/locales/ga-IE.yml Adds input.queue.* localized strings.
tools/wta/locales/gd-gb.yml Adds input.queue.* localized strings.
tools/wta/locales/gl-ES.yml Adds input.queue.* localized strings.
tools/wta/locales/gu-IN.yml Adds input.queue.* localized strings.
tools/wta/locales/he-IL.yml Adds input.queue.* localized strings.
tools/wta/locales/hi-IN.yml Adds input.queue.* localized strings.
tools/wta/locales/hr-HR.yml Adds input.queue.* localized strings.
tools/wta/locales/hu-HU.yml Adds input.queue.* localized strings.
tools/wta/locales/hy-AM.yml Adds input.queue.* localized strings.
tools/wta/locales/id-ID.yml Adds input.queue.* localized strings.
tools/wta/locales/is-IS.yml Adds input.queue.* localized strings.
tools/wta/locales/it-IT.yml Adds input.queue.* localized strings.
tools/wta/locales/ja-JP.yml Adds input.queue.* localized strings.
tools/wta/locales/ka-GE.yml Adds input.queue.* localized strings.
tools/wta/locales/kk-KZ.yml Adds input.queue.* localized strings.
tools/wta/locales/km-KH.yml Adds input.queue.* localized strings.
tools/wta/locales/kn-IN.yml Adds input.queue.* localized strings.
tools/wta/locales/ko-KR.yml Adds input.queue.* localized strings.
tools/wta/locales/kok-IN.yml Adds input.queue.* localized strings.
tools/wta/locales/lb-LU.yml Adds input.queue.* localized strings.
tools/wta/locales/lo-LA.yml Adds input.queue.* localized strings.
tools/wta/locales/lt-LT.yml Adds input.queue.* localized strings.
tools/wta/locales/lv-LV.yml Adds input.queue.* localized strings.
tools/wta/locales/mi-NZ.yml Adds input.queue.* localized strings.
tools/wta/locales/mk-MK.yml Adds input.queue.* localized strings.
tools/wta/locales/ml-IN.yml Adds input.queue.* localized strings.
tools/wta/locales/mr-IN.yml Adds input.queue.* localized strings.
tools/wta/locales/ms-MY.yml Adds input.queue.* localized strings.
tools/wta/locales/mt-MT.yml Adds input.queue.* localized strings.
tools/wta/locales/nb-NO.yml Adds input.queue.* localized strings.
tools/wta/locales/ne-NP.yml Adds input.queue.* localized strings.
tools/wta/locales/nl-NL.yml Adds input.queue.* localized strings.
tools/wta/locales/nn-NO.yml Adds input.queue.* localized strings.
tools/wta/locales/or-IN.yml Adds input.queue.* localized strings.
tools/wta/locales/pa-IN.yml Adds input.queue.* localized strings.
tools/wta/locales/pl-PL.yml Adds input.queue.* localized strings.
tools/wta/locales/pt-BR.yml Adds input.queue.* localized strings.
tools/wta/locales/pt-PT.yml Adds input.queue.* localized strings.
tools/wta/locales/qps-ploc.yml Adds input.queue.* localized strings.
tools/wta/locales/qps-ploca.yml Adds input.queue.* localized strings.
tools/wta/locales/qps-plocm.yml Adds input.queue.* localized strings.
tools/wta/locales/quz-PE.yml Adds input.queue.* localized strings.
tools/wta/locales/ro-RO.yml Adds input.queue.* localized strings.
tools/wta/locales/ru-RU.yml Adds input.queue.* localized strings.
tools/wta/locales/sk-SK.yml Adds input.queue.* localized strings.
tools/wta/locales/sl-SI.yml Adds input.queue.* localized strings.
tools/wta/locales/sq-AL.yml Adds input.queue.* localized strings.
tools/wta/locales/sr-Cyrl-BA.yml Adds input.queue.* localized strings.
tools/wta/locales/sr-Cyrl-RS.yml Adds input.queue.* localized strings.
tools/wta/locales/sr-Latn-RS.yml Adds input.queue.* localized strings.
tools/wta/locales/sv-SE.yml Adds input.queue.* localized strings.
tools/wta/locales/ta-IN.yml Adds input.queue.* localized strings.
tools/wta/locales/te-IN.yml Adds input.queue.* localized strings.
tools/wta/locales/th-TH.yml Adds input.queue.* localized strings.
tools/wta/locales/tr-TR.yml Adds input.queue.* localized strings.
tools/wta/locales/tt-RU.yml Adds input.queue.* localized strings.
tools/wta/locales/ug-CN.yml Adds input.queue.* localized strings.
tools/wta/locales/uk-UA.yml Adds input.queue.* localized strings.
tools/wta/locales/ur-PK.yml Adds input.queue.* localized strings.
tools/wta/locales/uz-Latn-UZ.yml Adds input.queue.* localized strings.
tools/wta/locales/vi-VN.yml Adds input.queue.* localized strings.
tools/wta/locales/zh-CN.yml Adds input.queue.* localized strings.
tools/wta/locales/zh-TW.yml Adds input.queue.* localized strings.

Comment thread tools/wta/src/ui/layout.rs
Comment thread tools/wta/locales/am-ET.yml Outdated
Comment thread tools/wta/locales/am-ET.yml Outdated
…prompts

Closes coverage gaps in the per-tab queue feature around the cross-tab
iteration path in drain_pending_prompts (the one place where a regression
could mis-route or starve a tab):

- drain_dispatches_to_multiple_idle_tabs_in_same_tick: two idle tabs both
  drain in a single tick, each with their own pane_context.tab_id.
- drain_dispatches_only_idle_tabs_leaves_busy_tabs_queued: a Submitted
  tab in the iteration is skipped without losing its queued prompt.
- drain_per_tab_fifo_independent_across_tabs: per-tab FIFO holds with
  cross-tab fairness — one head per idle tab per tick.
- clear_chat_history_on_one_tab_preserves_other_tabs_queue: pins the
  per-TabSession isolation contract for reset paths.

All 30 queue/drain/esc/clear_chat_history tests pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 93 out of 93 changed files in this pull request and generated 2 comments.

Comment thread tools/wta/src/ui/layout.rs
Comment thread tools/wta/locales/am-ET.yml Outdated
- am-ET.yml: restore main's translations + layout.welcome_hint (Ctrl+Shift+/);
  the original conflict resolution kept the stale branch-side file, dropping
  31 keys (welcome_disclaimer, connection.connecting_activity,
  commands.model.summary, time.*, etc.) that main had added in the 152
  intervening commits. Rebuilt from main + inserted the three input.queue.*
  Amharic translations after input.placeholder.disconnected.
- layout.rs: comment above command_popup said the input box is chunks[6];
  it's now chunks[7] (chunks[6] is the queued-hint row). Comment-only.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Treat loading_session as 'busy' on the Enter-submit path so it queues
instead of dispatching. During session/load replay, tab.turn stays Idle
(no TurnState::Submitted is created for replay) so accepts_new_prompt()
returns true — Enter would dispatch immediately and interleave a new
turn with the in-progress loadSession replay chunks.

drain_pending_prompts already gates on loading_session (app.rs:8141);
this keeps Enter and drain symmetric on what counts as busy.

Adds enter_during_session_load_queues_instead_of_dispatching pinning
the new contract: Enter mid-load queues, drain holds while loading,
and drain dispatches once loading completes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 94 out of 94 changed files in this pull request and generated 1 comment.

Comment thread tools/wta/src/ui/queued_hint.rs Outdated
Match the early-return guard style of other UI renderers — bail when
area.width == 0 as well as area.height == 0, instead of relying on
ratatui's behavior for a zero-width Rect.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 94 out of 94 changed files in this pull request and generated 1 comment.

Comment thread tools/wta/src/ui/queued_hint.rs Outdated
…calc

The early-return at line 30 already short-circuits area.width==0, so the
inner conditional was unreachable post-previous-commit. Simplified to a
straight saturating-sub + max(1).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 94 out of 94 changed files in this pull request and generated 1 comment.

Comment thread tools/wta/src/ui/queued_hint.rs Outdated
Match the convention of every other one-line hint
(recommendations::render_hint, agents_view::render_footer_hint,
auth::render, etc.) so RTL locales right-align the indicator rather
than leaving it pinned to the left edge.

The 2-cell left-padding remains an LTR stylistic indent; full RTL
bidi-reordering of chat/input rows is the deferred work flagged in
the PR description's 'Out of scope' section.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 94 out of 94 changed files in this pull request and generated no new comments.

yeelam-gordon and others added 2 commits June 12, 2026 21:06
Surfaced { end_pending: false } reports accepts_new_prompt() = true, so
the Enter handler at app.rs:6950 was dispatching typed prompts via
turn_submit_prompt instead of queuing them. turn_submit_prompt wipes
selected_recommendation and clears messages, which destroyed the
recommendation card the user was about to Run / Insert / Esc.

drain_pending_prompts already gates on recommendations().is_some()
(app.rs:8137); mirror that gate in the Enter handler so the two sides
agree on what 'busy' means. With this change, typing + Enter while a
card is staged queues the prompt; the card survives until the user
dismisses or executes it, at which point drain promotes the queued
prompt automatically.

Regression test pins the behavior with the exact scenario from the
bug report (card visible + 'Give me command for Ram' + Enter).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
User-reported regression: with a draft typed in the input box and a
recommendation card surfaced, pressing Enter to execute the card did
nothing visible. The card-Enter handler required input.is_empty(), so
Enter fell through to the prompt-dispatch path — which either wiped
the card (pre-queue) or silently queued the draft (post-queue fix
ffa86ed), neither of which is what the user wanted.

Fix: remove the input.is_empty() requirement on the card-Enter branch.
Enter on a visible card always executes the card. The pre-existing
draft stays in the input untouched — once the card runs and
recommendations() returns None, input_has_nav_focus flips back to
true and the user can edit/send/discard the draft normally.

Simpler than the dual-action approach (execute + auto-queue) that
would have widened the queue-card state interaction. Draft preserved
is the right model: the user typed mid-card-interaction, so they
clearly want to be able to revise after the card resolves.

Defensive: card-Enter falls back to DEFAULT_TAB_ID when no real
session_id is bound, matching the convention in the queue branch and
cancel_in_flight_turn.

Tests:
- enter_with_card_visible_and_empty_input_executes_card: baseline
- enter_with_card_visible_and_draft_input_executes_card_preserving_draft:
  pins the bug report contract — draft verbatim, no queue interaction,
  cursor preserved, input regains nav focus post-execute

753/753 tests pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 12, 2026 13:14

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 94 out of 94 changed files in this pull request and generated no new comments.

Comment thread tools/wta/src/app.rs
// This intentionally runs even when the input box has
// typed text. When a card surfaces, `input_has_nav_focus`
// locks the input so the user can't type more, but any
// pre-existing draft stays in the buffer. Pressing Enter
@github-actions

This comment has been minimized.

User-reported regression: with a recommendation card visible, Backspace,
typing, Delete, and Ctrl+Backspace all silently did nothing in the input
box. Cause was input_has_nav_focus() returning false when
turn.recommendations().is_some() — the gate that the rest of the input
key handlers consult.

Original intent of the gate: 'when card is visible, focus belongs to
the card; don't let keystrokes fill an invisible buffer'. But the input
box stays visually present (with caret) when a card is up, so the
'invisible buffer' worry doesn't apply. Locking edits left users with
a draft they couldn't even backspace, with no UI hint why.

Fix: input_has_nav_focus() now only locks on permission cards and
past-turn navigation. Recommendation cards still own Arrow keys (their
own match arms run first), Enter (card-Enter branch executes the card
regardless of input contents — fix 86fbc47), and Esc (dismisses card).
Char/Backspace/Delete/Ctrl+Backspace are free to edit the input draft
while the card is visible.

Regression test: input_editing_works_while_recommendation_card_visible
exercises Backspace, Char insertion, and Ctrl+Backspace with a surfaced
card, and verifies (a) edits land on the draft, (b) the card is NOT
dismissed by typing — only Esc / Enter affect the card.

754/754 tests pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

This comment has been minimized.

…e::Backspace

User-reported regression on PR #278. Diagnosis from
wta-main_helper-*.log: this build's conpty hands Backspace to wta as
the DEL byte (0x7F) wrapped in \Char('\u{7f}')\ rather than as
\KeyCode::Backspace\. Confirmed in helper logs dating to before any
PR #278 deploy today — this is a pre-existing quirk of the dev IT
build's conpty (shipped WindowsTerminal handles it correctly), not
something the queue PR introduced. It only became user-visible now
because the queue PR is the first feature that lets users sit on a
non-empty draft long enough to want to edit it.

Without normalization, the DEL byte fell into the \KeyCode::Char(c)\
arm in app.rs and was inserted into the input buffer as an invisible
control character — backspace appeared dead, and each press silently
bloated the draft.

Fix: at the event-stream boundary in event.rs, rewrite
\Char('\u{7f}')\ and \Char('\u{8}')\ (Ctrl+H BS, used by some
legacy emulators) to \KeyCode::Backspace\ before producing the
AppEvent. Every downstream handler sees a proper Backspace and the
existing \KeyCode::Backspace\ match arms fire as intended.

Same pattern used by many TUI apps targeting cross-platform terminals
where conpty / xterm-variant behavior is inconsistent.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 12, 2026 14:11

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 95 out of 95 changed files in this pull request and generated no new comments.

@github-actions

Copy link
Copy Markdown

@check-spelling-bot Report

⚠️ Dictionary not found

Problems were encountered retrieving check dictionaries (cspell:public-licenses/src/additional-licenses.txt cspell:cpp/src/compiler-clang-attributes.txt cspell:fullstack/dict/fullstack.txt cspell:lua/dict/lua.txt cspell:django/dict/django.txt cspell:clojure/src/clojure.txt cspell:cpp/src/stdlib-cmath.txt cspell:cpp/src/compiler-gcc.txt cspell:public-licenses/src/generated/public-licenses.txt cspell:cpp/src/lang-keywords.txt cspell:rust/dict/rust.txt cspell:java/src/java-terms.txt cspell:cpp/src/stdlib-cerrno.txt cspell:software-terms/dict/webServices.txt cspell:latex/dict/latex.txt cspell:ruby/dict/ruby.txt cspell:python/src/python/python.txt cspell:scala/dict/scala.txt cspell:cpp/src/people.txt cspell:elixir/dict/elixir.txt cspell:html/dict/html.txt cspell:k8s/dict/k8s.txt cspell:monkeyc/src/monkeyc_keywords.txt cspell:haskell/dict/haskell.txt cspell:cpp/src/ecosystem.txt cspell:python/src/common/extra.txt cspell:redis/dict/redis.txt cspell:powershell/dict/powershell.txt cspell:swift/src/swift.txt cspell:sql/src/tsql.txt cspell:node/dict/node.txt cspell:cpp/src/template-strings.txt cspell:cpp/src/stdlib-cpp.txt cspell:css/dict/css.txt cspell:cpp/src/compiler-msvc.txt cspell:cpp/src/stdlib-c.txt cspell:shell/dict/shell-all-words.txt cspell:golang/dict/go.txt cspell:sql/src/sql.txt cspell:java/src/java.txt cspell:dart/src/dart.txt cspell:docker/src/docker-words.txt cspell:php/dict/php.txt cspell:r/src/r.txt cspell:typescript/dict/typescript.txt cspell:software-terms/dict/softwareTerms.txt cspell:ada/dict/ada.txt cspell:npm/dict/npm.txt cspell:python/src/python/python-lib.txt cspell:svelte/dict/svelte.txt cspell:dotnet/dict/dotnet.txt cspell:python/src/additional_words.txt cspell:gaming-terms/dict/gaming-terms.txt cspell:cpp/src/lang-jargon.txt).

⚠️ For more information, see check-dictionary-not-found.

🔴 Please review

See the 📂 files view, the 📜action log, 👼 SARIF report, or 📝 job summary for details.

❌ Errors and Warnings Count
⚠️ check-dictionary-not-found 54
❌ forbidden-pattern 1

See ❌ Event descriptions for more information.

These words are not needed and should be removed Backgrounder CANTCALLOUT Ccc cplusplus ctl Debian dotnet drv endptr EOFs evt Fullwidth gitlab hdr idl IME inbox ININPUTSYNCCALL intelligentterminal Ioctl KVM lbl lld lsb NODEFAULT NONINFRINGEMENT notif oss outdir Podcast pri prioritization rcv segfault SND sourced SWP Tbl testname transitioning unk unparseable unregisters Virt VMs webpage websites WINVER xsi

Available 📚 dictionaries could cover words (expected and unrecognized) not in the 📘 dictionary

This includes both expected items (2064) from .github/actions/spelling/expect/alphabet.txt .github/actions/spelling/expect/expect.txt .github/actions/spelling/expect/web.txt

Dictionary Entries Covers Uniquely
cspell:csharp/csharp.txt 32 2 2
cspell:aws/aws.txt 232 2 2
cspell:fonts/fonts.txt 536 1 1

Consider adding to the extra_dictionaries array (in the .github/actions/spelling/config.json file):

    "cspell:csharp/csharp.txt",
    "cspell:aws/aws.txt",
    "cspell:fonts/fonts.txt",

To stop checking additional dictionaries, put (in the .github/actions/spelling/config.json file):

"check_extra_dictionaries": []

Forbidden patterns 🙅 (1)

In order to address this, you could change the content to not match the forbidden patterns (comments before forbidden patterns may help explain why they're forbidden), add patterns for acceptable instances, or adjust the forbidden patterns themselves.

These forbidden patterns matched content:

Should be preexisting
[Pp]re[- ]existing
✏️ Contributor please read this

By default the command suggestion will generate a file named based on your commit. That's generally ok as long as you add the file to your commit. Someone can reorganize it later.

If the listed items are:

  • ... misspelled, then please correct them instead of using the command.
  • ... names, please add them to .github/actions/spelling/allow/names.txt.
  • ... APIs, you can add them to a file in .github/actions/spelling/allow/.
  • ... just things you're using, please add them to an appropriate file in .github/actions/spelling/expect/.
  • ... tokens you only need in one place and shouldn't generally be used, you can add an item in an appropriate file in .github/actions/spelling/patterns/.

See the README.md in each directory for more information.

🔬 You can test your commits without appending to a PR by creating a new branch with that extra change and pushing it to your fork. The check-spelling action will run in response to your push -- it doesn't require an open pull request. By using such a branch, you can limit the number of typos your peers see you make. 😉

If the flagged items are 🤯 false positives

If items relate to a ...

  • binary file (or some other file you wouldn't want to check at all).

    Please add a file path to the excludes.txt file matching the containing file.

    File paths are Perl 5 Regular Expressions - you can test yours before committing to verify it will match your files.

    ^ refers to the file's path from the root of the repository, so ^README\.md$ would exclude README.md (on whichever branch you're using).

  • well-formed pattern.

    If you can write a pattern that would match it,
    try adding it to the patterns.txt file.

    Patterns are Perl 5 Regular Expressions - you can test yours before committing to verify it will match your lines.

    Note that patterns can't match multiline strings.

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.

4 participants