feat(tui): show user pasted content#2784
Conversation
|
@joshbarrington thanks! Could you show me some screenshots? I won't have the time to check out your branch |
|
/review |
docker-agent
left a comment
There was a problem hiding this comment.
Assessment: 🟡 NEEDS ATTENTION
This PR renders pasted content in the TUI with collapsible messages for large pastes. The implementation is solid overall — the sort-by-length trick for placeholder disambiguation is clever, the cache invalidation on toggle is correct, and the toggleableView interface dispatch is sound.
One medium-severity issue was found in the paste expansion loop.
| }) | ||
|
|
||
| for _, att := range pastes { | ||
| content = strings.ReplaceAll(content, "@"+att.Name, att.Content) |
There was a problem hiding this comment.
[MEDIUM] Paste content expansion can corrupt messages if pasted content contains @paste-N strings
The loop in resetAndSend replaces each @paste-N placeholder with the paste's content using strings.ReplaceAll. Sorting by name-length descending correctly prevents @paste-1 from partially matching @paste-10 in the original input — but it doesn't guard against a later paste's ReplaceAll matching an @paste-N string that was introduced by a previous paste's content.
Trigger scenario:
- User pastes two items:
paste-1andpaste-2 - The content of
paste-1happens to contain the literal string@paste-2(e.g., a shell script, documentation, or any text referencing another paste) - After
paste-1is expanded (longer name goes first),contentnow contains@paste-2embedded in the replacement text - When the loop reaches
paste-2,strings.ReplaceAllfinds that injected@paste-2and replaces it withpaste-2's content — double-expanding and corrupting the message
Suggested fix: Replace only the first occurrence in the original content position, or process on a copy of the original content string (tracking expansions separately), or escape @paste-N references in the source string before looping:
// Option: replace on a snapshot so injected tokens aren't re-processed
original := content
for _, att := range pastes {
content = strings.ReplaceAll(original, "@"+att.Name, att.Content)
original = content // or accumulate replacements on original only
}A cleaner approach is to do all replacements against the original content value and accumulate them without re-scanning previously expanded text.




Summary
This PR enhances the TUI to render pasted content back to the user - automatically collapsing large pasted file contents (exceeding 30 lines) into a toggleable view, improving overall chat readability and scrolling performance.
Key Changes
@paste-1for example. This shows the pasted content back to the user to allow for better readability.messages.goandmessage.go).toggleableViewinterface inmessages.goto handle click-to-toggle interactions on message views (now supporting both reasoning blocks and long user messages).editor.goto sort paste attachments by length descending before inline string replacement (@paste-1vs@paste-10), preventing partial match corruption, while fully retaining proper order of standard metadata file attachments.strings.Countandstrings.SplitN) instead of heavy array allocations (strings.Split) during truncation calculations.TestUserMessageCollapsibleandTestUserMessageNotCollapsibleunit tests to secure boundary math and UI layout mechanics.