Skip to content

Highlight a clicked user message even with the log panes closed - #486

Merged
iceljc merged 1 commit into
SciSharp:mainfrom
iceljc:features/add-rule-criteria
Sep 2, 2026
Merged

Highlight a clicked user message even with the log panes closed#486
iceljc merged 1 commit into
SciSharp:mainfrom
iceljc:features/add-rule-criteria

Conversation

@iceljc

@iceljc iceljc commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

Clicking a user message bailed out entirely when the persistent log was not open, so the click produced no feedback at all. The highlight now always lands - it marks the message being inspected - while pointing the panes at its entry stays conditional on them being open.

Also stops the panes' settling pin from overriding that jump: a scroll aimed at a specific entry now cancels the pin, which otherwise dragged the pane back to the tail moments after the click.

Clicking a user message bailed out entirely when the persistent log was not
open, so the click produced no feedback at all. The highlight now always
lands - it marks the message being inspected - while pointing the panes at
its entry stays conditional on them being open.

Also stops the panes' settling pin from overriding that jump: a scroll aimed
at a specific entry now cancels the pin, which otherwise dragged the pane
back to the tail moments after the click.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@iceljc
iceljc merged commit 76b453c into SciSharp:main Sep 2, 2026
1 of 2 checks passed
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Highlight selected messages when persistent logs are closed

🐞 Bug fix 🕐 10-20 Minutes

Grey Divider

AI Description

• Always highlights clicked user messages, even when persistent logs are closed.
• Cancels initial log pinning before scrolling open panes to matching entries.
Diagram

sequenceDiagram
    actor User
    participant Bubble as User Message
    participant ChatBox as Chat Box
    participant PersistLog as Persistent Log
    participant Pane as Log Pane
    User->>Bubble: Click message
    Bubble->>ChatBox: Select message ID
    ChatBox-->>Bubble: Apply highlight
    alt Logs are open
        ChatBox->>PersistLog: Cancel settling pin
        PersistLog->>PersistLog: Stop pinning
        ChatBox->>Pane: Scroll to entry
    else Logs are closed
        ChatBox-->>User: Keep feedback visible
    end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Declarative log target prop
  • ➕ Keeps scrolling and pin cancellation inside the persistent-log component
  • ➕ Avoids a global browser event and duplicated event-name string
  • ➕ Makes selected-entry navigation explicit component state
  • ➖ Requires refactoring existing DOM-based scrolling ownership
  • ➖ Introduces more coordination for conditionally mounted panes
  • ➖ Expands the regression surface for a narrowly scoped bug fix

Recommendation: Keep the PR's targeted event-based approach for this fix because it preserves existing scrolling behavior while resolving both symptoms with limited scope. If log navigation grows more complex, move the target message ID into a declarative PersistLog prop so that component owns pin cancellation and scrolling.

Files changed (2) +27 / -2

Bug fix (2) +27 / -2
chat-box.svelteHighlight message selection independently of log visibility +13/-2

Highlight message selection independently of log visibility

• Moves the selected-message highlight ahead of the persistent-log availability check, preserving click feedback when panes are closed. Dispatches a cancellation event before scrolling open panes to the selected log entry.

src/routes/chat/[agentId]/[conversationId]/chat-box.svelte

persist-log.svelteCancel settling pin for targeted log navigation +14/-0

Cancel settling pin for targeted log navigation

• Listens for targeted-scroll cancellation while log panes are initially pinned to the tail. The event stops active resize observers, timers, and interaction listeners so they cannot override the requested entry jump.

src/routes/chat/[agentId]/[conversationId]/persist-log/persist-log.svelte

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Cancel event lost during load 🐞 Bug ≡ Correctness
Description
autoScrollToTargetLog dispatches cancellation synchronously, but the persist-log listener is
installed only after both asynchronous log requests and a DOM tick complete. A click during that
interval can scroll an already-rendered target, then the later settling pin starts and drags the
pane back to the tail.
Code

src/routes/chat/[agentId]/[conversationId]/chat-box.svelte[R1693-1695]

+		// Tell a freshly opened log pane to stop pinning itself to the tail, or it
+		// would pull straight back down from the entry we are about to show.
+		window.dispatchEvent(new CustomEvent('persist-log:cancel-pin'));
Evidence
PersistLog begins asynchronous loading at lines 86-97 and only registers the new listener inside
pinToBottomWhileSettling at lines 182-192. Meanwhile, chat-box marks the pane loaded immediately
at lines 1487-1491 and dispatches the event before its one-time target lookup at lines 1692-1713,
allowing cancellation to be missed before the delayed pin begins.

src/routes/chat/[agentId]/[conversationId]/persist-log/persist-log.svelte[86-97]
src/routes/chat/[agentId]/[conversationId]/persist-log/persist-log.svelte[182-203]
src/routes/chat/[agentId]/[conversationId]/chat-box.svelte[1487-1491]
src/routes/chat/[agentId]/[conversationId]/chat-box.svelte[1692-1713]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The cancel-pin event can fire before the persist-log pane finishes asynchronous initialization and registers its listener. The later settling pin then starts despite the cancellation and can override the message-targeted scroll.

## Issue Context
PersistLog waits for both log requests and `tick()` before calling `pinToBottomWhileSettling()`, which currently owns listener registration. Register cancellation synchronously during component mounting and retain a cancellation flag so delayed initialization does not start the settling pin after an earlier event.

## Fix Focus Areas
- src/routes/chat/[agentId]/[conversationId]/chat-box.svelte[1692-1695]
- src/routes/chat/[agentId]/[conversationId]/persist-log/persist-log.svelte[86-101]
- src/routes/chat/[agentId]/[conversationId]/persist-log/persist-log.svelte[182-192]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Cancel listener leaks after pin 🐞 Bug ☼ Reliability
Description
pinToBottomWhileSettling registers a window listener whose removal callback runs only when a
future cancel event iterates stops; normal timeouts, user interaction, and component teardown
never remove it. Repeatedly opening and closing logs therefore retains stale component and scrollbar
closures indefinitely.
Code

src/routes/chat/[agentId]/[conversationId]/persist-log/persist-log.svelte[R190-192]

+        const cancelPin = () => stops.forEach(stop => stop());
+        window.addEventListener(CANCEL_PIN_EVENT, cancelPin);
+        stops.push(() => window.removeEventListener(CANCEL_PIN_EVENT, cancelPin));
Evidence
The removal callback is stored in stops at lines 190-192, but only cancelPin iterates that
array. Each timeout invokes its local stop at lines 205-218, while the mount cleanup at lines
99-101 only calls cleanLogs, which merely empties arrays at lines 269-272; neither path removes
the window listener.

src/routes/chat/[agentId]/[conversationId]/persist-log/persist-log.svelte[182-192]
src/routes/chat/[agentId]/[conversationId]/persist-log/persist-log.svelte[205-218]
src/routes/chat/[agentId]/[conversationId]/persist-log/persist-log.svelte[86-101]
src/routes/chat/[agentId]/[conversationId]/persist-log/persist-log.svelte[269-272]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The global `persist-log:cancel-pin` listener remains registered after the settling period ends and after PersistLog is destroyed. Each pane mount can therefore leak another listener and its captured DOM and scrollbar state.

## Issue Context
Local `stop` callbacks disconnect observers and clear timers, but they do not remove the window listener. Ensure listener removal occurs when all pins stop, when cancellation occurs, and during component teardown, including teardown before asynchronous initialization completes.

## Fix Focus Areas
- src/routes/chat/[agentId]/[conversationId]/persist-log/persist-log.svelte[86-101]
- src/routes/chat/[agentId]/[conversationId]/persist-log/persist-log.svelte[182-192]
- src/routes/chat/[agentId]/[conversationId]/persist-log/persist-log.svelte[205-218]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context sources
Review mode: ⚖️ Balanced: This is a localized behavioral change affecting message selection and cross-component scroll/event coordination, so it carries enough UI state and timing risk for a complete single-pass review.

Grey Divider

Tip of the day
💡 Did you know, you can turn on the rule miner and Qodo learns your standards from review history

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment on lines +1693 to +1695
// Tell a freshly opened log pane to stop pinning itself to the tail, or it
// would pull straight back down from the entry we are about to show.
window.dispatchEvent(new CustomEvent('persist-log:cancel-pin'));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. Cancel event lost during load 🐞 Bug ≡ Correctness

autoScrollToTargetLog dispatches cancellation synchronously, but the persist-log listener is
installed only after both asynchronous log requests and a DOM tick complete. A click during that
interval can scroll an already-rendered target, then the later settling pin starts and drags the
pane back to the tail.
Agent Prompt
## Issue description
The cancel-pin event can fire before the persist-log pane finishes asynchronous initialization and registers its listener. The later settling pin then starts despite the cancellation and can override the message-targeted scroll.

## Issue Context
PersistLog waits for both log requests and `tick()` before calling `pinToBottomWhileSettling()`, which currently owns listener registration. Register cancellation synchronously during component mounting and retain a cancellation flag so delayed initialization does not start the settling pin after an earlier event.

## Fix Focus Areas
- src/routes/chat/[agentId]/[conversationId]/chat-box.svelte[1692-1695]
- src/routes/chat/[agentId]/[conversationId]/persist-log/persist-log.svelte[86-101]
- src/routes/chat/[agentId]/[conversationId]/persist-log/persist-log.svelte[182-192]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +190 to +192
const cancelPin = () => stops.forEach(stop => stop());
window.addEventListener(CANCEL_PIN_EVENT, cancelPin);
stops.push(() => window.removeEventListener(CANCEL_PIN_EVENT, cancelPin));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

2. Cancel listener leaks after pin 🐞 Bug ☼ Reliability

pinToBottomWhileSettling registers a window listener whose removal callback runs only when a
future cancel event iterates stops; normal timeouts, user interaction, and component teardown
never remove it. Repeatedly opening and closing logs therefore retains stale component and scrollbar
closures indefinitely.
Agent Prompt
## Issue description
The global `persist-log:cancel-pin` listener remains registered after the settling period ends and after PersistLog is destroyed. Each pane mount can therefore leak another listener and its captured DOM and scrollbar state.

## Issue Context
Local `stop` callbacks disconnect observers and clear timers, but they do not remove the window listener. Ensure listener removal occurs when all pins stop, when cancellation occurs, and during component teardown, including teardown before asynchronous initialization completes.

## Fix Focus Areas
- src/routes/chat/[agentId]/[conversationId]/persist-log/persist-log.svelte[86-101]
- src/routes/chat/[agentId]/[conversationId]/persist-log/persist-log.svelte[182-192]
- src/routes/chat/[agentId]/[conversationId]/persist-log/persist-log.svelte[205-218]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

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