Skip to content

fix(input): prevent ghost-text suggestion from swallowing pasted content on Enter#337

Merged
chadbyte merged 1 commit intochadbyte:mainfrom
akuehner:fix/ghost-text-enter-swallows-paste
Apr 24, 2026
Merged

fix(input): prevent ghost-text suggestion from swallowing pasted content on Enter#337
chadbyte merged 1 commit intochadbyte:mainfrom
akuehner:fix/ghost-text-enter-swallows-paste

Conversation

@akuehner
Copy link
Copy Markdown
Contributor

Summary

After 0753833 (ghost-text suggestion pattern), pressing Enter with only a pasted image, paste block, or file attached (textarea empty) sends the ghost-text suggestion instead of the user's actual attached content.

The Enter handler checked !ctx.inputEl.value.trim() alone, which ignores pendingImages / pendingPastes / pendingFiles. The click handler was already correct (used hasSendableContent()). This PR aligns Enter with click and also suppresses ghost display entirely when attachments are present.

Changes

  • input.js Enter handler: use hasSendableContent() instead of raw textarea value check
  • app-rendering.js showSuggestionChips: use hasSendableContent() guard (so suggestion doesn't appear when attachments are already queued)
  • input.js renderInputPreviews: hide any active ghost as soon as attachments appear (defensive — in case a suggestion arrives via WS after an attachment was added)

Repro

  1. Wait for a ghost suggestion to appear with an empty textarea
  2. Paste an image or a long text block (goes into pending pastes/images)
  3. Press Enter

Before: suggestion is sent, paste/image is retained in the input bar.
After: paste/image is sent, suggestion is dismissed.

Test plan

  • Verified fix locally on 2.34.0
  • Regression check: ghost-text adoption still works when textarea is fully empty (no attachments)
  • Regression check: typing into textarea still clears ghost (unchanged input-event path)

…ent on Enter

After 0753833 (ghost-text suggestion pattern), pressing Enter with only
a pasted image, paste block, or file attached (textarea empty) sends the
ghost-text suggestion instead of the user's actual attached content.

The Enter handler checked `!ctx.inputEl.value.trim()` alone, which
ignores pendingImages / pendingPastes / pendingFiles. The click handler
was already correct (used hasSendableContent()). This aligns Enter with
click, and also suppresses ghost display entirely when attachments are
present so the user never sees a suggestion they can't safely send.

Changes:
- input.js Enter handler: use hasSendableContent() instead of raw textarea
- app-rendering.js showSuggestionChips: use hasSendableContent() guard
- input.js renderInputPreviews: hide ghost as soon as attachments appear

Repro:
1. Wait for a ghost suggestion to appear with empty textarea
2. Paste an image or a long text block (goes into pending pastes/images)
3. Press Enter
Before: suggestion is sent, paste/image is retained.
After: paste/image is sent, suggestion is dismissed.
@chadbyte chadbyte merged commit a657a09 into chadbyte:main Apr 24, 2026
@github-actions
Copy link
Copy Markdown
Contributor

This issue has been resolved in version 2.34.1-beta.2 (main).

To update, run:

npx clay-server@2.34.1-beta.2

-- Clay Deploy Bot

Build anything, with anyone, in one place.

@github-actions
Copy link
Copy Markdown
Contributor

This issue has been resolved in version 2.35.0 (stable).

To update, run:

npx clay-server@2.35.0

-- Clay Deploy Bot

Build anything, with anyone, in one place.

akuehner added a commit to akuehner/clay that referenced this pull request Apr 27, 2026
…on on stop click (lr-e6b5) (#9)

Regression after chadbyte#337 / 0753833 (ghost-text suggestion pattern). The sendBtn
click handler checked hasSendableContent() and ghost suggestion BEFORE the
processing-stop branch, so clicking the visible Stop button while a ghost
suggestion was queued sent the stale suggestion instead of stopping the SDK
loop. Same shape on the Enter handler: pressing Enter while waiting on Claude
adopted the ghost.

Trust the visible button mode. The "stop" CSS class is the source of truth
for what the user clicked: if the icon is the stop square, the click is a
Stop, never a send. Apply the same guard to the Enter key path so Enter
while processing+empty is a no-op rather than a ghost adoption.

Also document the upstream fork relationship in CLAUDE.md so future sessions
know to search chadbyte/clay issues+PRs by author akuehner before grepping
local code.
akuehner added a commit to akuehner/clay that referenced this pull request Apr 27, 2026
…st suggestion on stop click

Regression after chadbyte#337 / 0753833 (ghost-text suggestion pattern). The
sendBtn click handler checked hasSendableContent() and the ghost
suggestion before the processing-stop branch, so clicking the visible
Stop button while a ghost suggestion was queued sent the stale
suggestion instead of stopping the SDK loop. Same shape on the Enter
handler: pressing Enter while waiting on Claude adopted the ghost.

Both paths now check (ctx.processing && !hasSendableContent()) up front:
in that state the click/Enter is a Stop (or no-op for Enter) and never
adopts a ghost suggestion. Otherwise the original send paths run
unchanged.

Predicating on live state instead of the button's "stop" CSS class
avoids tying user-facing send behavior to the button-state machine in
app-connection.js, which can drift on mobile (backgrounding, ws
reconnect) and would wedge the input if its class got stuck.
chadbyte pushed a commit that referenced this pull request Apr 29, 2026
…st suggestion on stop click

Regression after #337 / 0753833 (ghost-text suggestion pattern). The
sendBtn click handler checked hasSendableContent() and the ghost
suggestion before the processing-stop branch, so clicking the visible
Stop button while a ghost suggestion was queued sent the stale
suggestion instead of stopping the SDK loop. Same shape on the Enter
handler: pressing Enter while waiting on Claude adopted the ghost.

Both paths now check (ctx.processing && !hasSendableContent()) up front:
in that state the click/Enter is a Stop (or no-op for Enter) and never
adopts a ghost suggestion. Otherwise the original send paths run
unchanged.

Predicating on live state instead of the button's "stop" CSS class
avoids tying user-facing send behavior to the button-state machine in
app-connection.js, which can drift on mobile (backgrounding, ws
reconnect) and would wedge the input if its class got stuck.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants