Skip to content

fix(tui): expand paste placeholders at submit time#149

Merged
BunsDev merged 1 commit into
mainfrom
fix/tui-paste-placeholder-expansion
Jul 12, 2026
Merged

fix(tui): expand paste placeholders at submit time#149
BunsDev merged 1 commit into
mainfrom
fix/tui-paste-placeholder-expansion

Conversation

@BunsDev

@BunsDev BunsDev commented Jul 12, 2026

Copy link
Copy Markdown
Member

Summary

Large pastes into the TUI composer (>=3 lines or >150 chars) are replaced with a [Pasted ~N lines #K] placeholder and the real content is stored in paste_contents "for retrieval at submit time" — but no retrieval existed. PromptInputState::take() returned the composer text verbatim, so submitted messages carried the literal placeholder and the pasted content was silently dropped. Observed in practice: multiple user pastes arriving at the model as [Pasted ~1 lines #N].

Changes

  • PromptInputState::take() expands every placeholder back to its stored content before returning; the submitted text and prompt history now carry the real paste.
  • Placeholder format extracted into paste_placeholder(), shared by handle_paste and take(), so the insert and expand sides cannot drift.
  • clear() drops paste_contents (once text is cleared the placeholders are unreachable); paste_counter stays monotonic so a stale placeholder restored via undo can never collide with a later paste's id.
  • Tests: expansion of single and multiple pastes, deleted-placeholder content does not leak, clear() drops the store.

Verification

From src-rust/:

  • cargo check --workspace — clean
  • cargo clippy --workspace --all-targets -- -D warnings — clean
  • cargo test --package claurst-tui prompt_input — 5 new tests pass alongside existing paste tests
  • cargo fmt --all — applied (unrelated drift in claude_cli.rs excluded from this branch)

🤖 Generated with Claude Code

handle_paste stores large pastes in paste_contents and inserts a
[Pasted ~N lines #K] placeholder, but nothing ever read the store back:
PromptInputState::take() returned the composer text verbatim, so the
model received the literal placeholder instead of the pasted content.

take() now rebuilds each placeholder from its stored content and
expands it before returning; the placeholder format lives in one
helper shared with handle_paste so the two cannot drift. clear()
drops stored contents (placeholders live in text, so once text is
cleared they are unreachable) while paste_counter stays monotonic to
keep stale placeholder ids from colliding after undo.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 12, 2026 02:58
@BunsDev BunsDev added the crate:tui Terminal UI crate (src-rust/crates/tui) label Jul 12, 2026
@vercel

vercel Bot commented Jul 12, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
docs Ready Ready Preview Jul 12, 2026 2:58am

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Fixes a TUI prompt-composer correctness issue where large clipboard pastes were replaced by [Pasted ~N lines #K] markers and the real content was stored, but submission (PromptInputState::take()) returned the marker text verbatim—silently dropping the pasted content.

Changes:

  • Extracts placeholder formatting into paste_placeholder() so insertion and later expansion share the exact same format.
  • Updates PromptInputState::take() to expand placeholders back to their stored paste content before clearing input state.
  • Adds tests for placeholder expansion (single/multiple), deletion behavior, and clearing paste storage.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +3161 to 3166
// Placeholders live in `text`, so once that is gone the stored paste
// contents are unreachable. `paste_counter` stays monotonic so a
// stale placeholder (e.g. restored via undo) can never collide with
// a later paste's id.
self.paste_contents.clear();
}
Comment on lines +3174 to 3183
let mut text = std::mem::take(&mut self.text);
for (id, content) in &self.paste_contents {
let placeholder = paste_placeholder(content.lines().count(), *id);
if text.contains(&placeholder) {
text = text.replace(&placeholder, content);
}
}
self.clear();
text
}
@BunsDev
BunsDev merged commit 6e85b1e into main Jul 12, 2026
3 of 4 checks passed
@BunsDev
BunsDev deleted the fix/tui-paste-placeholder-expansion branch July 12, 2026 03:04
BunsDev added a commit that referenced this pull request Jul 15, 2026
…urn (#166)

* fix(query): recover announce-then-stop stalls instead of ending the turn

The model sometimes announces work and then ends the turn with
stop_reason end_turn and zero tool calls ("Writing the CLI uninstall
module now." — then nothing). Both provider paths treated end_turn as
authoritative, so the loop fired Stop hooks and returned EndTurn; the
issue #149 placeholder made the empty variant visible but nothing made
either variant recoverable. Users had to keep typing "continue".

Mirror the max_tokens recovery pattern: when a round ends end_turn with
no tool calls and its text is empty or closes on an imminent-action
announcement, inject a continuation nudge as a user message and keep
looping. Bounded at STALL_RECOVERY_LIMIT (2) per user turn; the counter
resets whenever a tool round actually executes. The intermediate
TurnComplete is suppressed while a nudge is pending, exactly like an
in-budget max_tokens recovery. Deliberate handoffs (questions,
"let me know…", "I'll wait for your review.") never trigger recovery,
and COVEN_CODE_DISABLE_STALL_RECOVERY=1 turns the whole mechanism off.

The announcement heuristic works on the final sentence-like chunk,
skipping punctuation debris from dotted identifiers (`main.rs`.), and
is deliberately cheap to false-positive: the nudge tells the model to
state the final outcome if the work is already done, so a wrong guess
costs one bounded round-trip.

Observed transcripts from the stalled session are pinned as unit tests
for both the stall and non-stall (completion/handoff) shapes.

fixes #165

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

* fix(query): honor documented =1 contract for the stall-recovery kill switch

Review follow-up: stall_recovery_enabled() checked env presence, so
COVEN_CODE_DISABLE_STALL_RECOVERY=0 also disabled recovery. Parse the
value with feature_gates::is_env_truthy so only 1/true/yes/on disable,
matching the documented contract and the repo's gate convention.

Refs #165

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

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Uberration pushed a commit to Uberration/meatycoven-code that referenced this pull request Jul 18, 2026
…en#125)

Adds a discoverable `/steer <message>` command, gated behind a new off-by-
default `steer` Cargo feature (build with `--features steer`).

Behavior:
- While a turn is in flight: queue the message; it auto-runs after the current
  turn, mirroring the existing Enter-while-busy queueing (issue OpenCoven#149).
- When idle: submit the message immediately via the auto-submit path.

Implementation reuses the proven queued_messages + pending_auto_submit
mechanisms, so no new turn-dispatch logic is introduced. The command is
registered in PROMPT_SLASH_COMMANDS (feature-gated) for palette/help
discoverability, handled in the CLI main loop, and added to the
prompt_slash_commands_covers_registry allow-list (like /handoff, /stats).

Verified: builds with and without the feature; the coverage test passes in
both configurations; /steer appears in the command palette when enabled.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

crate:tui Terminal UI crate (src-rust/crates/tui)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants