zulip / zulip-mobile

Zulip mobile apps for Android and iOS.
https://zulip.com/apps/
Apache License 2.0
1.29k stars 649 forks source link

Use updated stream/sender names/avatars on messages #5208

Open gnprice opened 2 years ago

gnprice commented 2 years ago

When we show a message in the message list, we show its sender's name and avatar based on the sender_full_name and avatar_url properties on the message object itself. (See src/webview/html/message.js.) If we show a recipient header with a stream name (because it's a stream message and we're in an interleaved narrow: all messages, @-mentions, starred messages, or search), the stream name comes from the message object too. (*) (See src/webview/html/header.js; the streamNameOfStreamMessage accessor just reads the display_recipient property.)

(*) This isn't true right now in main, but it was until recently, and will be again after #5207.

And if the person who sent the message changes their name or avatar, or if the stream gets renamed, we don't go and update all the message objects. As a result, we'll keep showing the message with the old name, avatar, or stream name.

This bug is somewhat parallel to #3408. We do look at most of these events (users changing avatar since 7296551876e91674330624212dd3a41bb8c30128 / #4230; streams changing name since basically forever, with a gap fixed in 152b06e25 / #2451; users changing name not yet), and apply them to our central user and stream/subscription data structures respectively; we just don't apply those updates to the messages, and we don't consult that updated central data for this purpose. Like #3408, this issue gets largely papered over by our frequently refreshing the server data from scratch. As explained in #4841, we'd like to stop refreshing so frequently because it's a conspicuous drag on the user experience; when we do, this issue will become more visible.

There are two basic approaches we can choose from to solve this problem:

For sender avatars, the web app takes a hybrid strategy:

(References to the web app are as of https://github.com/zulip/zulip/tree/410281624 .)

gnprice commented 2 years ago

One annoying thing about the normalized approach, given the fact that we'll sometimes see messages in streams (and probably from senders) that we don't know about, is that it'd mean having two types of stream/user records in our central stream/user data: full records for the ones we can see in the usual way, and skeleton records for the ones we know about only through messages.

The web app goes ahead and puts skeleton user objects, with only a handful of the usual properties, into the same place as normal user objects go:

        _add_user({
            email: person.email,
            user_id,
            full_name: person.full_name,
            is_admin: person.is_realm_admin || false,
            is_bot: person.is_bot || false,
        });

(That's from extract_people_from_message; the local person is either an element of a PM's display_recipient, or synthesized from the sender_* properties.)

As that excerpt already demonstrates, that approach leads rapidly down the path of making up bogus data. On receiving a stream message from an unknown sender, that bit of code goes and runs, and because messages don't carry any information about whether the sender is an admin or a bot, it just assumes (really, lies) that they're neither.

If doing that with a type-checker, as in our codebase, the type-checker would immediately complain that the types don't match, and rightly so. This object is missing a bunch of the properties expected on a user; and if OTOH one made the data structure's type accept such skeleton records, then the type-checker would flag every place we looked up a user and then tried to use one of the properties not present here.

I believe in the web app there currently isn't a live bug caused by this. That's true only because of an invariant which isn't expressed:

So if we were to pursue this strategy, we'd want to express a similar invariant in a way the type-checker can see and check for us. One way that could look is:

chrisbobbe commented 2 years ago

It may be simpler to discuss after #5188 is settled, but: should the choice between normalized/denormalized be influenced by how we want MessageListElement objects to be put together, e.g., questions like https://github.com/zulip/zulip-mobile/pull/5188#discussion_r793016697 ? If we normalize aggressively, can we still construct each MessageListElement with all the data belonging to the element, such that a function like doElementsDifferInterestingly (in that draft PR) can have the nice property of just taking two MessageListElement objects and no "background data"? That might be good if so.

gnprice commented 2 years ago

Yeah, we certainly could do that.

It might come at some performance cost: we'd be adding a pile of extra properties to each of these MessageListElement objects. We construct potentially a lot of those, in a pretty performance-sensitive path, so making them each have like 8 properties instead of 4 could be meaningful additional work.

That points toward one advantage of the denormalized strategy: it could help us more easily make use of the usual React-style "===, therefore nothing changed" short-circuit optimization.