Skip to content

fix(react-ui/chat): stop wiping selection on every /api/operations poll (#9904)#9917

Merged
mudler merged 1 commit into
masterfrom
fix/9904-chat-polling-selection-and-copy
May 21, 2026
Merged

fix(react-ui/chat): stop wiping selection on every /api/operations poll (#9904)#9917
mudler merged 1 commit into
masterfrom
fix/9904-chat-polling-selection-and-copy

Conversation

@localai-bot
Copy link
Copy Markdown
Collaborator

Summary

Fixes the two issues reported in #9904: text selection in chat being wiped every second, and the per-message copy button silently failing over plain HTTP.

Root cause 1 — selection wiped on every poll

useOperations() polls /api/operations every 1s and called setOperations(ops) with a fresh array reference every time, even when the payload was identical. That forced Chat to re-render. The catch is that React 19 dropped the old lastHtml === nextHtml short-circuit in its DOM diff, so on every Chat re-render React re-assigns innerHTML on the <div dangerouslySetInnerHTML=...> for each assistant message — collapsing any text the user had selected.

Fix: JSON-compare the incoming operations payload against the last one written and skip setOperations when it didn't change. Functional setters for loading/error so they short-circuit at the source too.

Root cause 2 — copy button broken on plain HTTP

navigator.clipboard is undefined in non-secure contexts (which is exactly what the user has: LXC -> Docker -> remote HTTP). The previous code called navigator.clipboard.writeText(text) unconditionally and showed a success toast regardless of what actually happened.

Fix: new src/utils/clipboard.js helper with navigator.clipboard.writeText as the primary path (gated on window.isSecureContext) and a hidden-textarea + document.execCommand('copy') fallback that browsers still honour outside secure contexts. Preserves the user's existing selection during the fallback. Wired through Chat, AgentChat, and CanvasPanel (all three had the same bug). New toasts.copyFailed string in all five existing locales.

Test plan

Regression coverage in core/http/react-ui/e2e/chat-polling-selection.spec.js:

  • Selection test: mocks /api/operations (empty payload), opens a chat, attaches a MutationObserver to the assistant content node over 3s of polling. Without the fix the observer records ~1 mutation per poll; with the fix it records 0. Also verifies the programmatic selection survives 2.5s of polling.
  • Copy test: stubs out navigator.clipboard + window.isSecureContext = false, spies on document.execCommand, clicks the copy button and asserts the fallback path runs.
  • Full chat-related Playwright suite: 114 tests passing.

Both new tests fail on master and pass with this change.

Closes #9904

…ll (#9904)

useOperations() was calling setOperations() with a fresh array on every
1s poll, even when the payload was identical. In React 19 the DOM diff
no longer short-circuits dangerouslySetInnerHTML on equal __html, so the
forced Chat re-render re-assigned innerHTML on every assistant message
once per second — wiping any text the user had selected.

Skip the state update when the serialised operations payload is
unchanged, and switch loading/error to functional setters so they also
short-circuit at the source.

Also fixes the chat copy button on plain HTTP: navigator.clipboard is
undefined in non-secure contexts (a common LXC+Docker deployment), but
the previous code called it unconditionally and showed a success toast
regardless. Routed Chat, AgentChat and CanvasPanel through a new
copyToClipboard() helper that uses navigator.clipboard when available
and falls back to a hidden-textarea + execCommand('copy') trick that
browsers still honour outside secure contexts. The fallback preserves
the user's existing selection.

Regression coverage in e2e/chat-polling-selection.spec.js: a
MutationObserver counts mutations on the assistant content node across
3s of polling (must be 0); the copy test stubs out navigator.clipboard
and asserts that execCommand('copy') is invoked.

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Assisted-by: claude-opus-4-7-1m
@mudler mudler merged commit 11d5bd0 into master May 21, 2026
57 checks passed
@mudler mudler deleted the fix/9904-chat-polling-selection-and-copy branch May 21, 2026 10:17
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.

Frontend Chat: Chat copy not possible, endless updating

2 participants