Skip to content

anchors n/n: Open at first unread; open /near/ links at message #1517

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 30 commits into
base: main
Choose a base branch
from

Conversation

gnprice
Copy link
Member

@gnprice gnprice commented May 20, 2025

Fixes #82.
Fixes #80 (given projected revisions; see below).

This is stacked atop #1515. It will probably become two separate PRs before it's ready to merge.

The main remaining work this branch needs is tests. There are also some directly user-visible aspects still to do:

  • When opening a /near/ link, this PR ensures the feed opens with the target message in view. But it'd be good to visibly highlight the specific message, e.g. by having its background blink once or twice.

    That will probably be a post-launch follow-up — the lack of it isn't a regression relative to either zulip-flutter main, or the legacy app.

  • When opening a /near/ link, this doesn't implement following where the message has been moved to.

    That's Follow /near/ links through message moves #683. Currently M6 but arguably should be M5b; either way, won't block this, and most likely won't happen before launch (which is coming soon).

  • When fetching at first unread, it turns out the resulting /api/get-messages request can be quite slow, if the user's first unread is far back in history (e.g. in combined feed or a busy channel). This seems to be on the server side — probably it's just a naturally slower query for the database to handle.

    In retrospect this is probably a major contributor (though far from the only one) to why the legacy RN app has always felt slow to me.

    In this branch, fetching at first unread is therefore kept behind an experimental flag. With that flag enabled, opening my combined feed on chat.zulip.org takes multiple seconds. But then if I tap the "go to bottom" button, it loads the newest messages like usual, and that is zippy like zulip-flutter has always been. (Also if I scroll around in history, even way back in the vicinity of the first unread, it remains as zippy as always.)

    This draft won't actually fix Open message list at first unread, rather than latest message #80 until it fetches at first unread without that experimental flag. Given the stark difference in performance, though, I think I'll want a setting for it — akin to the "automatically mark as read on scroll" setting. Without that setting, this would be a substantial regression relative to zulip-flutter main for users who have old unreads. So I plan to add such a setting.

gnprice added 27 commits May 28, 2025 23:45
This will give us a natural home for logic that makes these depend
on whether we have the newest messages, once that becomes something
that varies.
This is NFC ultimately because we currently only ever fetch, or
show loading indicators, at one end of the message list, namely
the start.

When we do start supporting a message list in the middle of history,
though (zulip#82), and consequently loading newer as well as older
messages, my conclusion after thinking it through is that we'll want
a "busy fetching" state at one end to mean we show a loading
indicator at the other end too, if it still has more to be fetched.

This would look weird if the user actually saw both at the same time
-- but that shouldn't happen, because if both ends (or even either
end) is still open then the original fetch should have found plenty of
messages to separate them, many screenfuls' worth.

And conversely, if the user does kick off a fetch at one end and then
scroll swiftly to the other end and witness how that appears, we want
to show them a "loading" sign.  The situation is exactly like if
they'd had a fetch attempt on that same end and we were backing off
from failure: there's no fetch right now, but there won't be one yet,
so effectively the loading is busy.
Generally this is helpful because it means that viewing references
to the field will highlight specifically the places that set it.

Here it's also helpful because we're about to replace the field
with an enum shared across several getters.
This makes the relationships between these flags clearer.

It will also simplify some upcoming refactors that change their
semantics.
Now the distinction between these two states exists only for asserts.
If a fetch in one direction has recently failed, we'll want the
backoff to apply to any attempt to fetch in the other direction too;
after all, it's the same server.

We can also drop the term "cooldown" here, which is effectively
redundant with "backoff".
This matches the symmetry expressed in the description of
busyFetchingMore and at the latter's call site in widgets code:
whichever direction (older or newer) we might have a fetch request
active in, the consequences we draw are the same in both directions.
This tightens up a bit the logic for maintaining the fetching status,
and hopefully makes it a bit easier to read.
This is NFC with a correctly-behaved server: we set `anchor=newest`,
so the server always sets `found_newest` to true.

Conversely, this will be helpful as we generalize `fetchInitial` to
work with other anchor values; we'll use the `found_newest` value
given by the server, without trying to predict it from the anchor.

The server behavior that makes this effectively NFC isn't quite
explicit in the API docs.  Those say:

  found_newest: boolean

  Whether the server promises that the messages list includes the
  very newest messages matching the narrow (used by clients that
  paginate their requests to decide whether there may be more
  messages to fetch).

  https://zulip.com/api/get-messages#response

But with `anchor=newest`, the response does need to include the very
newest messages in the narrow -- that's the meaning of that `anchor`
value.  So the server is in fact promising the list includes those,
and `found_newest` is therefore required to be true.

(And indeed in practice the server does set `found_newest` to true
when `anchor=newest`; it has specific logic to do so.)
Also expand a bit of docs to reflect what happens on a
request using AnchorCode.firstUnread.
In particular this causes the handful of places where each field
of MessageListView needs to appear to all be next to each other.
Even if the reader is already sure that the field doesn't get mutated
from outside this file, giving it a different name from the getter is
useful for seeing exactly where it does get mutated: now one can look
at the references to `_narrow`, and see the mutation sites without
having them intermingled with all the sites that just read it.
This is effectively NFC given normal server behavior.  In particular,
the Zulip server is smart enough to skip doing any actual work to
fetch later messages when the anchor is already `newest`.

When we start passing anchors other than `newest`, we'll need this.
This is NFC as to the live app, because we continue to always set
the anchor to AnchorCode.newest there.
There's no value that's a natural default for this at a model level:
different UI scenarios will use different values.  So require callers
to be explicit.
This completes the model layer of zulip#82 and zulip#80: the message list can
start at an arbitrary anchor, including a numeric message-ID anchor
or AnchorCode.firstUnread, and can fetch more history from there
in both directions.

Still to do is to work that into the widgets layer.  This change is
therefore NFC as to the live app: nothing calls this method yet.
TODO test: call even if also near top
  (because if haveOldest but not haveNewest, then want that fetch)
… typing-status and mark-as-read; TODO revisit realism of data
TODO test: on jump and then new store, preserve jumped anchor
When the message list is truly far back in history -- for example,
at first unread in the combined feed or a busy channel, for a user
who has some old unreads going back months and years -- trying to
scroll smoothly to the bottom is hopeless.  The only way to get to
the newest messages in any reasonable amount of time is to jump there.

So, do that.
This will give us room to start returning additional information
from parsing the URL, in particular the /near/ operand.
gnprice added 3 commits May 28, 2025 23:46
…eal setting

TODO: for me, in combined feed, this is a lot slower than fetching
  newest -- seems like the server request is a lot slower,
  which makes sense.
  So I want to just always start at newest in, I guess, interleaved views.

Fixes 80.
@gnprice gnprice force-pushed the pr-first-unread branch from 1182dcb to 3d75129 Compare May 29, 2025 06:48
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.

Open a message list in the middle of history, when requested Open message list at first unread, rather than latest message
1 participant