Virtualize AnchorMode.End does not position at the end after the initial ItemsProvider result
- Dominant language
- C#
- Stars
- 38.4k
- Forks
- 10.9k
- Avg merge
- 2d 10h
- Merged PRs (30d)
- 281
Description
### Is there an existing issue for this?
- [x] I have searched the existing issues
Related but distinct:
- #65742 added anchor modes.
- #66799 is the `AnchorMode` API proposal.
- #68099 covers an `InitialItemIndex` startup race.
### Describe the bug
A `Virtualize` using an `ItemsProvider` and `AnchorMode="VirtualizeAnchorMode.End"` remains at the beginning of the list after its initial provider result is rendered.
This affects chat and log-viewer scenarios. If the provider initially returns enough existing items to overflow the viewport, the user sees the oldest items instead of the newest items at the end.
End anchoring works for subsequent appends after the viewport has reached the bottom. The problem is specifically the initial transition from an empty virtualizer to its first populated provider result.
### Expected behavior
An initially empty viewport is also at its end. When its first provider result changes the item count from zero to a nonzero value, `AnchorMode.End` should position the viewport at the end.
This matches the chat/log-viewer use case described for `AnchorMode.End`.
### Actual behavior
The first provider result is displayed at the beginning of the list. The user must manually scroll to the bottom before End following becomes established.
### Minimal reproduction
```razor
@page "/"
@rendermode InteractiveServer
@using Microsoft.AspNetCore.Components.Web.Virtualization
@code {
private readonly Message[] _messages =
[.. Enumerable.Range(1, 100).Select(i => new Message(i, $"Message {i}"))];
private readonly IEqualityComparer _messageComparer =
EqualityComparer.Default;
private ValueTask> LoadMessagesAsync(
ItemsProviderRequest request)
{
var items = _messages
.Skip(request.StartIndex)
.Take(request.Count);
return ValueTask.FromResult(
new ItemsProviderResult(items, _messages.Length));
}
private sealed record Message(int Id, string Text);
}
```
Load the page and wait for the initial provider result. The viewport remains at `Message 1` instead of being positioned at `Message 100`.
### Possible cause
In `Virtualize.RefreshDataCoreAsync`, append handling is gated by:
```csharp
var previousItemCount = _itemCount;
var countDelta = result.TotalItemCount - previousItemCount;
var itemsAdded = countDelta > 0 && previousItemCount > 0;
```
For the initial provider result, `previousItemCount` is zero, so neither `ShouldFollowAppendedTailAsync` nor `AdvanceWindowToAppendedTailAsync` runs.
On the JavaScript side, End mode initializes `bottomTracking.following` to `true`, but `bottomTracking.reached` and `bottomTracking.wasAtBottomLastRender` are initially false. `refreshObservedElements` therefore does not pin the first populated render:
```ts
if ((anchorModeIs.end || bottomTracking.following) &&
(bottomTracking.wasAtBottomLastRender || bottomTracking.reached)) {
scrollElement.scrollTop = scrollElement.scrollHeight;
}
```
The empty initial viewport should arguably establish the at-bottom state, or the first `0 -> N` provider result should enter the End-following path.
### Additional context
This was reproduced in two independent Aspire Dashboard views backed by `FluentDataGrid`, which forwards its `AnchorMode` to `Virtualize`:
- Structured Logs
- Traces
Both remained away from the end after initial loading. Subsequent appended content followed correctly once the viewport had manually reached the bottom.
### Version
- .NET SDK: `11.0.100-rc.1.26425.128`
- Microsoft.AspNetCore.App: `11.0.0-rc.1.26425.128`
- OS: Windows 11, `win-x64`
- Browser: Chromium
Contributor guide
Research direction
Start with Virtualize.RefreshDataCoreAsync and trace the initial 0-to-N ItemsProvider result, then inspect the JavaScript refreshObservedElements logic and bottomTracking state. Run the minimal reproduction in the issue and verify that AnchorMode.End initially displays the newest item, such as Message 100, while subsequent appends still follow the end.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp, javascript
- Domain
- frontend, web-dev
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 74/100