Skip to content

Conversation

@sm-sayedi
Copy link
Collaborator

@sm-sayedi sm-sayedi commented Jan 14, 2026

Fixes: #1476

In addition to addressing the main problem stated in #1476's description, which is about removing messages in some narrows after becoming muted by a message move, this PR also addresses a closely related problem: showing messages in the same narrows after becoming unmuted by a message move.

@sm-sayedi sm-sayedi added the maintainer review PR ready for review by Zulip maintainers label Jan 14, 2026
@sm-sayedi sm-sayedi requested a review from chrisbobbe January 14, 2026 20:29
@sm-sayedi sm-sayedi force-pushed the 1476-msgs-moved-to-muted-narrow branch 4 times, most recently from 90e1fd0 to 01e3432 Compare January 15, 2026 19:27
Copy link
Collaborator

@chrisbobbe chrisbobbe left a comment

Choose a reason for hiding this comment

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

Thanks! Comments below, from reading the implementation but not yet the tests.

Also a small commit-message nit, for the first two commits: "keyword" has a special meaning, so I'd avoid saying '"channel" keyword'.

Comment on lines 1221 to 1223
/// The messages are moved into this narrow from another narrow, or are moved
/// internally but got unmuted by the move.
void _messagesMovedIntoNarrow() {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I appreciate not wanting to use a method against its interface—I see the added dartdoc—but this change causes confusion in the code. The method's name is also part of its interface, and in the new case (messages moved internally but got unmuted) it's very confusing that the name contradicts what's actually happening. For what it means for a message to be in a narrow, see Narrow.containsMessage; in particular:

  /// This does not necessarily mean the message list would show this message
  /// when navigated to this narrow; in particular it does not address the
  /// question of whether the stream or topic […] is muted.

Some options to fix:

  • Make the name, dartdoc if needed, and callers agree on what the method is for.
  • Make a new method just for the new case.
  • Just write the code inline, as we do for the corresponding code handling MutedUsersEvents.

Similarly for _messagesMovedFromNarrow.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Agree. TBH, I was hesitant to reuse these methods, and in fact, the first revisions had the code inlined. 🙂 Anyway, renamed these methods to _messagesMoved(From/Into)MessageList. I think the callers can now agree on the new names.

Comment on lines 1274 to 1280
_handleMessagesMovedInternally(messageMove, messageIds);

case MentionsNarrow():
case StarredMessagesNarrow():
// The messages didn't enter or leave this narrow.
// TODO(#1255): … except they may have become muted or not.
// We'll handle that at the same time as we handle muting itself changing.
// Recipient headers, and downstream of those, may change, though.
_messagesMovedInternally(messageIds);
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm looking at the two methods _handleMessagesMovedInternally and _messagesMovedInternally. Those names are very similar, and the difference ("handle" in the first, not in the second) doesn't effectively carry meaning for me as a reader. This invites confusion and wasted time, when readers either

  • don't notice that two different methods are being called, or
  • feel pressure to load both implementations into their heads, because they're not satisfied with some aspect of the system—even an aspect unrelated to the feature you're implementing—and there aren't any clues that it's correct to call one method instead of the other, except by understanding the implementations.

Copy link
Collaborator

Choose a reason for hiding this comment

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

here aren't any clues that it's correct to call one method instead of the other, except by understanding the implementations

In fact here's a concrete example: _messagesMovedInternally is called in the MentionsNarrow, StarredMessagesNarrow, and KeywordSearchNarrow cases, but _handleMessagesMovedInternally isn't. This difference isn't explained by the fact that your method's name starts with _handle and the other one doesn't, so I have to go read the implementations.

Copy link
Collaborator

Choose a reason for hiding this comment

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

_messagesMovedInternally is called in the MentionsNarrow, StarredMessagesNarrow, and KeywordSearchNarrow cases, but _handleMessagesMovedInternally isn't.

I see from _messageVisible that MentionsNarrow, StarredMessagesNarrow, and KeywordSearchNarrow don't filter out messages by channel/topic muting, so there would be nothing for _handleMessagesMovedInternally to do in those cases, and I guess that's what led to the apparent inconsistency.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Does the new method _handleMessagesMovedInternally, and its helper _messageInternalMoveCanAffectVisibility, need to exist? What if we just cut those out, inlining the switches on MessageMoveVisibilityEffect right there in messagesMoved?

      case CombinedFeedNarrow():
        switch (store.mightAffectIsTopicVisible(messageMove)) {
          case .unmutedToMuted:       // etc.
          case .mutedToUnmuted:       // etc.
          case .unmutedToUnmuted:     _messagesMovedInternally(messageIds);
          case .mutedToMuted: return;
        }

(and the appropriate thing for the ChannelNarrow case below)

Comment on lines 182 to 191
/// Whether the given message move will change the result of
/// [isTopicVisibleInChannel] for the channel and topic of its moved messages,
/// compared to the current state.
MessageMoveVisibilityEffect mightAffectIsTopicVisibleInChannel(UpdateMessageMoveData messageMove) {
final UpdateMessageMoveData(
:origStreamId, :newStreamId, :origTopic, :newTopic) = messageMove;
return MessageMoveVisibilityEffect._fromBeforeAfter(
isTopicVisibleInChannel(origStreamId, origTopic),
isTopicVisibleInChannel(newStreamId, newTopic));
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

The dartdoc is incoherent: "moved messages" don't have a "channel and topic"; they have an original channel/topic and a new channel/topic. I worry about bugs caused by calling this with a messageMove where the channel changes.

Let's focus this method on what we need it to do, making it impossible to call except in the case we care about: messages having just their topic changed, in the context of a specific channel focused in the UI. Here's a suggestion:

  /// following a topic rename in the given channel.
  MessageMoveVisibilityEffect mightAffectIsTopicVisibleInChannel({
    required int channelId,
    required TopicName origTopic,
    required TopicName newTopic,
  }) {
    return MessageMoveVisibilityEffect._fromBeforeAfter(
      isTopicVisibleInChannel(channelId, origTopic),
      isTopicVisibleInChannel(channelId, newTopic));
  }

Alternatively, instead of origTopic and newTopic, it could take the whole UpdateMessageMoveData, but with asserts and dartdoc saying it can't be a move between channels.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Looking carefully at this, I think we can allow this method to be used with a message move where the channel changes. I agree that this method is related to a specific channel being focused in the UI, but a message can be moved from/into the current focused channel. In fact, this method is used for such cases in a commit that I recently added:

1be11a3 msglist: Optimize cross-channel message moves in channel narrow

/// Whether the given message move will change the result of
/// [isTopicVisibleInChannel] for the channel and topic of its moved messages,
/// compared to the current state.
MessageMoveVisibilityEffect mightAffectIsTopicVisibleInChannel(UpdateMessageMoveData messageMove) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

We have these names:

  • ChannelStore.willChangeIfTopicVisibleInChannel
  • ChannelStore.willChangeIfTopicVisible
  • UserStore.mightChangeShouldMuteDmConversation

and now we have these new names:

  • ChannelStore.mightAffectIsTopicVisibleInChannel
  • ChannelStore.mightAffectIsTopicVisible

I'd like to settle on a consistent pattern, since all of these have similar jobs, rather than varying between "will" and "might", and between "change" and "affect". Could you add a prep commit that makes the existing names consistent, then change your commit to follow that pattern? You'll need to disambiguate your new methods from the existing ones, and we might as well do that semantically. For example:

  • ChannelStore.muteEventWillAffectIfTopicVisibleInChannel
  • ChannelStore.muteEventWillAffectIfTopicVisible
  • ChannelStore.messageMoveWillAffectIfTopicVisibleInChannel
  • ChannelStore.messageMoveWillAffectIfTopicVisible

This includes renaming the method(s), renaming their
parameters/local variables, modifying their docs, and modifying
their test names to replace the legacy "stream" term with "channel".

This will make it consistent with other similar methods that we'll add
in the next commits which will use the "channel" term.
… methods

This includes renaming the method(s), renaming their local variables,
modifying their docs, and modifying their test names to replace the
legacy "stream" term with "channel".

This will make it consistent with other similar methods that we'll add
in the next commits which will use the "channel" term.
Rename them to topicEventWillAffectIfTopicVisible/InChannel to set up
a consistent pattern for other similar methods that will be added
in the next commits (and in future).
@sm-sayedi sm-sayedi force-pushed the 1476-msgs-moved-to-muted-narrow branch from 01e3432 to e5ccc07 Compare January 16, 2026 20:21
@sm-sayedi
Copy link
Collaborator Author

Thanks for the review @chrisbobbe. Pushed new changes, PTAL.

@sm-sayedi sm-sayedi requested a review from chrisbobbe January 16, 2026 20:39
Rename it to willAffectShouldMuteDmConversation to match the pattern
used by similar methods in ChannelStore, such as topicEventWillAffectIfTopicVisible.
Rename them to _messagesMoved(From/Into)MessageList.

I think "message list" is a better term here compared to "narrow" as in
the model code we actually deal with the messages being moved from/into
the message list. This will become more accurate in the next commit(s)
where a message move will mute/unmute some messages in a narrow,
in which case, they're not moved from/into the narrow, but instead
moved from/into the message list.
This optimization handles cases where moved messages become muted/unmuted:
  - If messages are moved into the narrow but end up muted by the move,
    they will never appear in the message list, so no refetch is needed.
  - If messages are moved out of the narrow but were already muted,
    there is no need to look them up and try to remove them from the
    message list, since they could not have appeared there in the first place.
    This avoids an unnecessary lookup with time complexity `O(m log n)`,
    where `m` is the number of moved messages and `n` is the number of
    messages currently in the message list.
@sm-sayedi sm-sayedi force-pushed the 1476-msgs-moved-to-muted-narrow branch from e5ccc07 to 1a31979 Compare January 16, 2026 21:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

maintainer review PR ready for review by Zulip maintainers

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Messages can show up in feed when moved to muted narrow

2 participants