Skip to content

fix: make text selection highlight visible in chat bubbles#697

Open
hankkyy wants to merge 1 commit into
fathah:mainfrom
hankkyy:feat/edit-queued-messages
Open

fix: make text selection highlight visible in chat bubbles#697
hankkyy wants to merge 1 commit into
fathah:mainfrom
hankkyy:feat/edit-queued-messages

Conversation

@hankkyy

@hankkyy hankkyy commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Problem

Text selection highlighting in chat bubbles is nearly invisible. Users can select and copy text, but cannot see which text is selected — the highlight color blends into the background.

Root Cause

Two issues:

  1. Low opacity in --selection CSS variable: Dark themes used 0.3-0.4 alpha. On dark backgrounds (#212121, #2f2f2f), a 40% opacity dark blue composited to nearly the same shade, making the highlight indistinguishable from the background.

  2. User bubble color clash: User bubbles use #003f7a (dark blue) — nearly identical to the --selection blue tone. Even at higher opacity, blue-on-blue produces no visible contrast on user messages.

Before/After (Dark theme, default)

  • Agent bubble (#2f2f2f): old rgba(0,63,122,0.4) composites to ~#1C354D -> invisible. New rgba(0,100,180,0.7) composites to ~#135C8C -> clearly visible.
  • User bubble (#003f7a): old blue-on-blue -> imperceptible. New rgba(120,190,255,0.7) sky-blue override -> high contrast against dark blue.

Changes

File: src/renderer/src/assets/main.css

1. Increased --selection opacity across all 12 themes

Dark themes: 0.3-0.4 -> 0.7. Light themes: 0.2 -> 0.3.

Theme Before After
Dark (default) rgba(0,63,122,0.4) rgba(0,100,180,0.7)
Light rgba(0,63,122,0.2) rgba(0,63,122,0.3)
Dracula rgba(189,147,249,0.35) rgba(189,147,249,0.7)
Nord rgba(136,192,208,0.3) rgba(136,192,208,0.7)
One Dark rgba(97,175,239,0.3) rgba(97,175,239,0.7)
GitHub Dark rgba(56,139,253,0.3) rgba(56,139,253,0.7)
Monokai rgba(102,217,239,0.3) rgba(102,217,239,0.7)
Solarized Dark rgba(38,139,210,0.3) rgba(38,139,210,0.7)
Gruvbox Dark rgba(131,165,152,0.3) rgba(131,165,152,0.7)
Tokyo Night rgba(122,162,247,0.3) rgba(122,162,247,0.7)
GitHub Light rgba(9,105,218,0.2) rgba(9,105,218,0.3)
Solarized Light rgba(38,139,210,0.2) rgba(38,139,210,0.3)

2. Added user-bubble-specific ::selection override

.chat-bubble-user ::selection { background: rgba(120, 190, 255, 0.7); }

User bubbles share the same blue tone as --selection, so even at 0.7 opacity a blue-on-blue highlight is barely visible. This override uses a brighter sky-blue that clearly contrasts with the #003f7a user bubble background.

Verification

  • Dark theme: select text on agent bubble -> visible blue highlight on dark gray background
  • Dark theme: select text on user bubble -> visible sky-blue highlight on dark blue background
  • Switch through all 12 themes -> selection highlight is clearly visible

Add an inline edit button (pencil icon) to each queued message so
users can correct typos or adjust content while the agent is busy.

Changes:
- QueuedMessages: add Pencil button + inline <input> edit mode
  (Enter saves, Escape cancels, blur commits)
- Chat: add handleEditQueued callback that updates queueRef
- CSS: .chat-queue-edit styles (matching .chat-queue-remove) +
  .chat-queue-edit-input for the inline text field
- i18n: add queuedEdit string to all 10 supported locales
@greptile-apps

greptile-apps Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Despite the PR title referencing a text-selection highlight fix, the actual changes implement inline editing of queued messages — none of the described --selection CSS variable changes or ::selection overrides appear in the diff. The real change adds a pencil button to each queued message that opens an inline <input>, commits on Enter/blur, and cancels on Escape, along with the supporting CSS and i18n strings across all locales.

  • QueuedMessages.tsx gains editingIndex/editText state, startEdit/commitEdit/cancelEdit callbacks, and an onEdit prop wired through from Chat.tsx; there is a correctness bug where pressing Escape still commits the edit because onBlur fires after cancelEdit while React's state update is still batched.
  • main.css adds .chat-queue-edit and .chat-queue-edit-input styles; the --selection opacity changes described in the PR description are absent entirely.
  • i18n locale files (all 10 languages) each receive a queuedEdit key.

Confidence Score: 4/5

Safe to merge after fixing the Escape-key cancel flow; the rest of the feature is straightforward and well-contained.

The cancel-edit path is broken: pressing Escape queues state resets but the synchronous blur event fires first, calling commitEdit with the user's modified text and silently saving what they tried to discard. Everything else — the Edit callback in Chat.tsx, the CSS, and the i18n additions — is correct and low-risk. The PR description also completely misidentifies what this change does (claims --selection opacity fixes; actual changes add inline queue editing), which warrants a flag even though it doesn't affect runtime behavior.

src/renderer/src/screens/Chat/QueuedMessages.tsx — specifically the Escape key + onBlur interaction in renderEditInput.

Important Files Changed

Filename Overview
src/renderer/src/screens/Chat/QueuedMessages.tsx Adds inline edit mode for queued messages; contains an Escape-key cancel bug where onBlur fires after cancelEdit and re-commits the edit, and shows the edit button for attachment-only messages without a guard.
src/renderer/src/screens/Chat/Chat.tsx Adds handleEditQueued callback that mutates queueRef and syncs state; straightforward and consistent with the existing handleRemoveQueued pattern.
src/renderer/src/assets/main.css Adds .chat-queue-edit and .chat-queue-edit-input CSS rules for the new edit UI; no changes to --selection variables or ::selection overrides despite the PR description claiming otherwise.
src/shared/i18n/locales/en/chat.ts Adds queuedEdit translation key; value is clear and consistent with existing queuedCancel key.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant User
    participant QueuedMessages
    participant Chat

    User->>QueuedMessages: clicks Pencil button
    QueuedMessages->>QueuedMessages: "startEdit(index, text)<br/>setEditingIndex / setEditText"

    User->>QueuedMessages: types in input

    alt User presses Enter
        QueuedMessages->>QueuedMessages: commitEdit(index)
        QueuedMessages->>Chat: onEdit(index, trimmedText)
        Chat->>Chat: "handleEditQueued → mutates queueRef[index].text<br/>setQueuedMessages([...queueRef])"
    else User presses Escape
        QueuedMessages->>QueuedMessages: cancelEdit() — queues setState(null / "")
        Note over QueuedMessages: ⚠ input unmounts → onBlur fires<br/>before state update takes effect
        QueuedMessages->>QueuedMessages: "commitEdit(index) via onBlur<br/>may call onEdit with modified text"
        QueuedMessages->>Chat: onEdit(index, modifiedText) ← unintended
    else User blurs input
        QueuedMessages->>QueuedMessages: commitEdit(index)
        QueuedMessages->>Chat: onEdit(index, trimmedText)
        Chat->>Chat: "handleEditQueued → mutates queueRef[index].text<br/>setQueuedMessages([...queueRef])"
    end
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant User
    participant QueuedMessages
    participant Chat

    User->>QueuedMessages: clicks Pencil button
    QueuedMessages->>QueuedMessages: "startEdit(index, text)<br/>setEditingIndex / setEditText"

    User->>QueuedMessages: types in input

    alt User presses Enter
        QueuedMessages->>QueuedMessages: commitEdit(index)
        QueuedMessages->>Chat: onEdit(index, trimmedText)
        Chat->>Chat: "handleEditQueued → mutates queueRef[index].text<br/>setQueuedMessages([...queueRef])"
    else User presses Escape
        QueuedMessages->>QueuedMessages: cancelEdit() — queues setState(null / "")
        Note over QueuedMessages: ⚠ input unmounts → onBlur fires<br/>before state update takes effect
        QueuedMessages->>QueuedMessages: "commitEdit(index) via onBlur<br/>may call onEdit with modified text"
        QueuedMessages->>Chat: onEdit(index, modifiedText) ← unintended
    else User blurs input
        QueuedMessages->>QueuedMessages: commitEdit(index)
        QueuedMessages->>Chat: onEdit(index, trimmedText)
        Chat->>Chat: "handleEditQueued → mutates queueRef[index].text<br/>setQueuedMessages([...queueRef])"
    end
Loading

Reviews (1): Last reviewed commit: "feat: allow editing queued messages befo..." | Re-trigger Greptile

Comment on lines +78 to +83
onKeyDown={(e) => {
if (e.key === "Enter") commitEdit(index);
if (e.key === "Escape") cancelEdit();
}}
onBlur={() => commitEdit(index)}
/>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Escape key cancels but onBlur immediately re-commits

When a user presses Escape, cancelEdit() queues its state updates (setEditingIndex(null), setEditText("")) but React batches those updates — they haven't taken effect yet. The input then loses focus (unmounts on re-render), firing onBlurcommitEdit(index). At that point editText still holds the modified value, so the if (trimmed && trimmed !== messages[index].text) check passes and onEdit is called, silently saving changes the user explicitly rejected.

A common fix is a ref flag set synchronously before any state update so commitEdit can bail out early:

const isCancellingRef = useRef(false);

const cancelEdit = useCallback(() => {
  isCancellingRef.current = true;
  setEditingIndex(null);
  setEditText("");
}, []);

const commitEdit = useCallback((index: number) => {
  if (isCancellingRef.current) {
    isCancellingRef.current = false;
    return;
  }
  const trimmed = editText.trim();
  if (trimmed && trimmed !== messages[index].text) {
    onEdit(index, trimmed);
  }
  setEditingIndex(null);
  setEditText("");
}, [editText, messages, onEdit]);

Comment on lines +98 to +106
<button
type="button"
className="chat-queue-edit"
onClick={() => startEdit(0, messages[0].text)}
aria-label={t("chat.queuedEdit")}
title={t("chat.queuedEdit")}
>
<Pencil size={12} />
</button>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Edit button shown for attachment-only messages

When messages[0].text === "" (an attachment-only queued message), startEdit(0, "") is called, opening an empty input. If the user types anything and commits, onEdit is called, converting what was an attachment-only message into one that also has text. The preview continues to show the attachment count (since preview prioritises text), so this silently changes the message type. Consider hiding or disabling the edit button when m.text === "".

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.

1 participant