Coming back to the feed shows it as it was left

Leaving the feed keeps it in Services/FeedMemory.cs (posts, waiting posts, cursors and scroll; ten minutes, for the same
feed of the same persona), so coming back from a thread or a profile shows it again at once, older pages included,
instead of reading it anew; newer posts come behind the "new" button and through the stream as before.
tests/e2e FeedMemoryTests goes to a thread and back without a new read of the home timeline; the whole suite passes
(123, 5 skipped).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LsXgEaXee4GCU1hwYgPJXw
This commit is contained in:
thepraandClaude Opus 5.5 committed 2026-10-06 01:21:41 +02:00
1 parent 5a7e1804a9
commit 23dbc68fb4
6 files changed
+164 -1

No files matched your search

+51
View File
@@ -110,6 +110,7 @@
[Inject] StreamingService Streaming { get; set; }
[Inject] InstanceService InstanceService { get; set; }
[Inject] RequestStats RequestStats { get; set; }
[Inject] FeedMemory Memory { get; set; }
FeedFilter Filter { get; set; } = new();
List<FeedPost> posts = [];
@@ -129,6 +130,8 @@
FeedQuery lastQuery;
DateTime? lastPollAt;
TimeSpan? lastLoadTook;
// the scroll a recalled feed goes back to, once rendered
double? recalledScroll;
GlobePanel globe;
IJSObjectReference feedModule;
@@ -158,9 +161,36 @@
return;
}
if (await Recall())
return;
await Reload();
}
/// <summary>Shows the feed again as the reader left it, when it is the same feed of the same persona and recent.</summary>
async Task<bool> Recall()
{
if (!Memory.Holds(CascadingState.PersonaId))
return false;
groups = Memory.Groups;
lists = Memory.Lists;
if (Query != Memory.Query)
return false;
loadCancellation = new();
posts = Memory.Posts;
pending = Memory.Pending;
minId = Memory.MinId;
maxId = Memory.MaxId;
recalledScroll = Memory.ScrollTop;
loadedPersonaId = CascadingState.PersonaId;
lastQuery = Query;
IsLoading = false;
Memory.Forget();
await Streaming.Follow(StreamOf(Filter), loadCancellation.Token);
await ReadNotifications(loadCancellation.Token);
return true;
}
// The persona changed (NavMenu's switcher): everything on the page belongs to the old one.
protected override async Task OnParametersSetAsync()
{
@@ -177,6 +207,8 @@
reference = DotNetObjectReference.Create(this);
feedModule = await JS.InvokeAsync<IJSObjectReference>("import", "./js/feed.js");
if (recalledScroll is { } top)
await feedModule.InvokeVoidAsync("scrollToOffset", ScrollId, top);
await feedModule.InvokeVoidAsync("observeEnd", reference, SentinelId, ScrollId);
pollTimer = new(PollInterval);
_ = PollNewer(pageCancellation.Token);
@@ -511,6 +543,7 @@
public override async ValueTask DisposeAsync()
{
Streaming.Received -= OnStream;
await Remember();
await Streaming.Stop();
await pageCancellation.CancelAsync();
pageCancellation.Dispose();
@@ -531,6 +564,24 @@
await base.DisposeAsync();
}
/// <summary>Keeps the feed for the way back (FeedMemory), when it shows something worth keeping.</summary>
async Task Remember()
{
if (IsLoading || loadError is { Length: > 0 } || posts.Count == 0 || CascadingState.PersonaId is null
|| Filter.TimelineType is TimelineType.Nearby)
return;
var scrollTop = 0d;
try
{
if (feedModule is not null)
scrollTop = await feedModule.InvokeAsync<double>("scrollOffset", ScrollId);
}
catch (JSDisconnectedException)
{
}
Memory.Save(Query, posts, pending, groups, lists, minId, maxId, scrollTop);
}
sealed class GeoAnswer
{
public double? Latitude { get; set; }