Skip to content

Keep the chat at the bottom when a NEEDS YOU card arrives or retires - #786

Merged
alexeyzimarev merged 2 commits into
mainfrom
alexeyzimarev/chat-follow-tail-gestures
Sep 6, 2026
Merged

alexeyzimarev merged 2 commits into
mainfrom
alexeyzimarev/chat-follow-tail-gestures

Conversation

@alexeyzimarev

@alexeyzimarev alexeyzimarev commented Sep 5, 2026 •

Copy link
Copy Markdown
Member

Closes #788 — AI-2532

What & why

Answering a card in the desktop app scrolled the chat to its beginning. Marking the awaiting tool row changes its height, which makes Avalonia's virtualizing panel drop its anchor and re-place every row from the average realized height; with tall prose above short rows the estimate is far off, the extent collapses, and retiring the card grows the viewport and clamps the offset down by the same amount. Follow-tail read that offset drop as the reader scrolling up and stopped correcting. It now keys on the reader's input: a change landing above the bottom is followed unless a pointer, wheel, key or scrollbar gesture inside the list landed in the same layout pass, and a change landing on the bottom re-arms it. The expansion hold is a case of the same rule. The same re-anchoring still moves a reader who has scrolled up; that is #789 (AI-2533).

Where to look

A click's command runs on the pointer release, a dispatcher turn after the press, so both events arm the gesture flag; arming on the press alone let the expansion click scroll the summary away.

Verification

Headless repro against a real 13-row session cut after its AskUserQuestion call: on the old code the list landed on rows 0..3 at offset 390 with extent 3801 once the card retired, and stayed there; with the fix it converges on the last rows (offset 1871, extent 2651) and the appended transcript keeps it there. The new test fails on the old code at the card's arrival and passes now; ChatTabViewSmokeTests 29/29, Capacitor.App.Tests.Unit 1415/1415.

🤖 Generated with Claude Code

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 5, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-05T16:55:43.699197Z a824e69 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Keep chat pinned when NEEDS YOU cards arrive or retire

🐞 Bug fix 🧪 Tests 📝 Documentation 🕐 20-40 Minutes

Grey Divider

AI Description

• Preserve follow-tail through virtualized layout shifts when NEEDS YOU cards appear or retire.
• Suspend following only for reader gestures, then re-arm upon returning to the bottom.
• Add headless regression coverage and document the gesture-based behavior.
Diagram

sequenceDiagram
    actor Reader
    participant List as Chat List
    participant Gesture as Gesture Handler
    participant Panel as Virtual Panel
    participant Scroll as Scroll Viewer
    participant Tail as Follow Tail
    Reader->>List: Pointer, wheel, or key
    List->>Gesture: Route input
    Gesture->>Gesture: Mark reader gesture
    Panel->>Scroll: Change extent or offset
    Scroll->>Tail: ScrollChanged
    alt Reader gesture
        Tail->>Tail: Preserve reader position
    else Layout change while following
        Tail->>Scroll: Scroll to end
    end
    Scroll-->>Tail: Bottom position re-arms
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Custom virtualization anchoring
  • ➕ Could preserve an exact visual anchor across row-height changes.
  • ➕ Would address the layout behavior at its source.
  • ➖ Requires deeper Avalonia panel customization and maintenance.
  • ➖ Risks regressions across virtualization, realization, and framework upgrades.
2. Debounced scroll correction
  • ➕ Could wait for extent estimates to stabilize before scrolling.
  • ➕ Requires fewer routed input handlers.
  • ➖ Introduces visible lag and timing sensitivity.
  • ➖ May override a reader gesture occurring during the debounce window.

Recommendation: Keep the PR’s gesture-based approach. Scroll deltas cannot reliably distinguish reader movement from Avalonia’s anchor and extent corrections, while routed input provides a direct intent signal without replacing the virtualizing panel. Custom anchoring is substantially more invasive, and debouncing remains timing-dependent.

Files changed (4) +116 / -51

Bug fix (1) +30 / -22
ChatTabView.axaml.csDistinguish reader gestures from virtualized layout shifts +30/-22

Distinguish reader gestures from virtualized layout shifts

• Replaces offset-delta inference and the expansion-specific hold with persistent follow-tail state. Routed pointer, wheel, scroll, keyboard, and scrollbar gestures suspend following for their layout pass, while reaching the bottom re-arms it.

src/Capacitor.App/Views/ChatTabView.axaml.cs

Tests (1) +68 / -16
ChatTabViewSmokeTests.csCover gesture-aware scrolling and question-card lifecycle +68/-16

Cover gesture-aware scrolling and question-card lifecycle

• Uses real wheel input instead of direct offset mutation when simulating reader scrolling and updates expansion expectations. Adds a tall-row regression reproducing NEEDS YOU question-card arrival and retirement under Avalonia virtualization.

test/Capacitor.App.Tests.Unit/ChatTabViewSmokeTests.cs

Documentation (2) +18 / -13
CHANGES.mdClarify follow-tail behavior during group expansion +2/-1

Clarify follow-tail behavior during group expansion

• Reframes group expansion as a reader gesture that suspends follow-tail until the reader returns to the bottom.

docs/CHANGES.md

2026-08-26-ai2196-chat-for-pty-harnesses-design.mdDocument gesture-driven follow-tail semantics +16/-12

Document gesture-driven follow-tail semantics

• Updates the design specification to explain why scroll deltas cannot identify reader intent. Documents supported gestures, layout-pass scoping, and bottom-position re-arming.

docs/superpowers/specs/2026-08-26-ai2196-chat-for-pty-harnesses-design.md

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a824e69ee8

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

ChatItems.AddHandler(PointerPressedEvent, OnReaderGesture, RoutingStrategies.Tunnel, handledEventsToo: true);
ChatItems.AddHandler(PointerReleasedEvent, OnReaderGesture, RoutingStrategies.Tunnel, handledEventsToo: true);
ChatItems.AddHandler(PointerWheelChangedEvent, OnReaderGesture, RoutingStrategies.Tunnel, handledEventsToo: true);
ChatItems.AddHandler(ScrollGestureEvent, OnReaderGesture, RoutingStrategies.Tunnel, handledEventsToo: true);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Register scroll gestures on their bubbling route

On touch devices, Avalonia's ScrollGestureEvent is bubble-only, so this tunnel-only handler never arms _readerGesture. After the pointer-press flag has drained, each touch-pan offset change reaches OnScrollChanged while _followTail is still true and is immediately undone by ScrollToEnd(), preventing the reader from scrolling up with touch input. Register this event for RoutingStrategies.Bubble (with handled events enabled) instead.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Confirmed against Avalonia 12.1.1: ScrollGestureEvent is registered Bubble-only, so the tunnel handler never fired. Registered on the bubbling route in 6f165ee, with a headless test that raises a scroll gesture up from a row and checks an append no longer pulls the reader back; it fails on the tunnel registration and passes on bubble.

@qodo-code-review

qodo-code-review Bot commented Sep 5, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

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

Grey Divider


Remediation recommended

1. Selection drag snaps to bottom ✗ Dismissed 🐞 Bug ≡ Correctness
Description
OnReaderGesture clears _readerGesture after the press layout pass even when the pointer remains
held, so later auto-scroll during a long upward text-selection drag is classified as non-reader
movement. With _followTail still armed, OnScrollChanged calls ScrollToEnd() and prevents the
reader from extending the selection upward.
Code

src/Capacitor.App/Views/ChatTabView.axaml.cs[55]

+        Dispatcher.UIThread.Post(() => _readerGesture = false, DispatcherPriority.Background);
Relevance

●●● Strong

Clear held-pointer correctness bug directly contradicting the PR’s selection-gesture intent; recent
UI correctness fixes are accepted.

PR-#766
PR-#740

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The transcript is hosted in the ChatItems scroll viewer and contains SelectableTextBlock rows.
The new handlers pulse gesture state for press and release independently, while the unconditional
background reset can occur before a held-pointer selection finishes; subsequent scroll changes then
preserve _followTail and force ScrollToEnd.

src/Capacitor.App/Views/ChatTabView.axaml[10-31]
src/Capacitor.App/Views/ChatTabView.axaml.cs[35-42]
src/Capacitor.App/Views/ChatTabView.axaml.cs[52-62]

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

## Issue description
Long pointer gestures outlive the background-priority `_readerGesture` pulse. Auto-scroll produced while selecting transcript text is therefore overridden by follow-tail.

## Issue Context
Transcript rows intentionally use selectable text inside the `ChatItems` scroll viewer. Track an active pointer gesture through release or capture loss, while preserving the post-release window needed for button commands and layout.

## Fix Focus Areas
- src/Capacitor.App/Views/ChatTabView.axaml.cs[35-62]
- src/Capacitor.App/Views/ChatTabView.axaml[10-31]
- test/Capacitor.App.Tests.Unit/ChatTabViewSmokeTests.cs[75-82]

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


Grey Divider

Context sources
✅ Compliance rules (platform): 58 rules
Review mode: ⚖️ Balanced: This changes runtime scroll-following behavior and input/event timing in a UI control, with several subtle interaction paths, but the logic remains localized enough for one careful review pass.

Grey Divider

Tip of the day
💡 Did you know, you can show, collapse, or hide each part of a finding: code, evidence, and all

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread src/Capacitor.App/Views/ChatTabView.axaml.cs
…788)

The virtualizing panel re-places every row from the average height whenever a
realized row changes size, and the presenter then clamps or anchor-shifts the
offset by amounts that look exactly like a reader scrolling up. Only the
reader's own input tells the two apart, so follow-tail keys on gestures inside
the list rather than on the change's offset delta.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@alexeyzimarev
alexeyzimarev force-pushed the alexeyzimarev/chat-follow-tail-gestures branch from a824e69 to 3179a96 Compare September 6, 2026 08:24
…ail (#788)

The scroll gesture event routes on the bubbling strategy only, so a tunnel
registration never sees it and every touch pan up was undone.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@alexeyzimarev
alexeyzimarev merged commit fcf600a into main Sep 6, 2026
6 checks passed
@alexeyzimarev
alexeyzimarev deleted the alexeyzimarev/chat-follow-tail-gestures branch September 6, 2026 08:52
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.

Desktop chat scrolls to the beginning after answering a NEEDS YOU card

1 participant