Skip to content

fix(chat): copy what the bubble shows (#269) + keep a sent message pinned - #272

Open
kfaracik wants to merge 2 commits into
mainfrom
fix/269-copy-think-and-send-pin
Open

fix(chat): copy what the bubble shows (#269) + keep a sent message pinned#272
kfaracik wants to merge 2 commits into
mainfrom
fix/269-copy-think-and-send-pin

Conversation

@kfaracik

Copy link
Copy Markdown
Member

Why

Two independent chat bugs.

1. Copying a reply with <think> tags (#269). The Copy button put message.content on the clipboard verbatim — including the <think>…</think> block and the [n] citation markers that are hidden on screen. Pasting that back rendered a nearly empty user bubble: parseThinkingContent ran for every role, and the user branch rendered only the text before <think>, dropping the block and everything after </think>. The DB row was always intact — the loss was purely visual, but the model did receive the raw markup.

2. A sent message sometimes wasn't pinned. From the second message on, the new message occasionally stayed where it was appended instead of moving below the header, forcing a manual scroll. The pin was a single scrollToEnd fired from onContentSizeChange, but the list's maximum offset keeps moving for a few hundred milliseconds after that:

  • blankSpace is applied as the scroll view's bottom contentInset (ScrollViewWithBottomPadding in keyboard-controller), and Reanimated commits it on the UI thread a frame or more after JS sets it;
  • the inset is recomputed again once the new user row and the assistant placeholder report their heights;
  • the keyboard dismissed by the send itself animates out for ~250 ms, and keyboard-controller drives its own per-frame scrollTo the whole time.

Whichever lands last wins, so a lone scroll aims at a stale end and falls short. The first message in a chat was masked by the initial-scroll retry loop; later ones had no safety net. Independently, the Android keyboard-dismiss re-snap was gated on wasAtBottomDuringKeyboard, so sending after scrolling up skipped the last correction.

What changed

  • utils/thinking.ts — one home for the think parser, previously duplicated in MessageItem and messageSources; also normalises an orphan </think>.
  • utils/messageText.tsvisibleMessageText() decides what Copy puts on the clipboard: think blocks out, citation markers out when the reply is grounded in sources, and a reply that is only reasoning falls back to that reasoning instead of an empty string.
  • User bubbles keep every word — only the think markers are dropped — so pasted markup can no longer swallow the bubble. New chat titles are built from the marker-free text.
  • useScrollSettler re-asserts the scroll position across the settling window (animated while the send transition runs, instant afterwards). recomputeBlankSpace re-snaps whenever it moves the inset, a drag cancels the pin so the user is never fought, and a send in flight now outranks the keyboard-dismiss gate. A 300 ms fallback runs the pin even if onContentSizeChange never arrives.

Test plan

  • yarn test — 772 passing, incl. new __tests__/messageText.test.ts (15) and __tests__/useScrollSettler.test.ts (8, fake timers: correction order and animation mode, cancel-on-drag, restart mid-window, unmount cleanup).
  • yarn lint, yarn format:check, npx tsc --noEmit clean for the touched files.
  • Not verified on a device: the settler's root cause is derived from the app code and the keyboard-controller sources, not from a reproduced failure on hardware. Worth a manual pass — send several messages in a row, including from a scrolled-up position and with a multi-line input.

Closes #269

🤖 Generated with Claude Code

kfaracik and others added 2 commits August 1, 2026 01:27
…eadable (#269)

Copying an assistant reply put `message.content` on the clipboard verbatim,
including the `<think>…</think>` block and the `[n]` citation markers that are
hidden on screen. Pasting that back produced a user bubble that looked almost
empty, because `parseThinkingContent` ran for every role and the user branch
rendered only the text *before* `<think>` — dropping the block and everything
after `</think>`.

- Copy now goes through `visibleMessageText`: think blocks stripped, citation
  markers stripped when the reply is grounded in sources, and a reply that is
  nothing but reasoning falls back to that reasoning instead of an empty string.
- User messages keep every word; only the think markers are dropped, so pasted
  markup never swallows the bubble.
- The think parser was duplicated in `MessageItem` and `messageSources`; both
  now share `utils/thinking.ts`, which also handles an orphan `</think>`.
- New chat titles are built from the marker-free text.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sending the second or a later message sometimes left it where it was
appended instead of pinned below the header, forcing a manual scroll.

The pin was a single `scrollToEnd` fired from `onContentSizeChange`, but the
list's maximum offset keeps moving for a few hundred milliseconds afterwards:
`blankSpace` is applied as the scroll view's bottom contentInset and Reanimated
commits it on the UI thread a frame later, the inset is recomputed once the new
rows report their heights, and keyboard-controller drives its own per-frame
scrollTo while the keyboard dismissed by the send animates out. Whichever
lands last wins, so a lone scroll aims at a stale end and falls short.

`useScrollSettler` re-asserts the position across that window (animated while
the send transition is still running, instant afterwards), `recomputeBlankSpace`
re-snaps whenever it moves the inset, a drag cancels the pin so the user is
never fought, and the keyboard-dismiss snap no longer skips a send in flight
just because the list was scrolled up when the message was written.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 31, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
private-mind Error Error Jul 31, 2026 11:30pm

Request Review

@kfaracik kfaracik self-assigned this Jul 31, 2026
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.

Copying a message with <think> tags truncates the pasted user bubble; copied text includes hidden markup

1 participant