Skip to content

Use channel folders in the "Inbox" view#2186

Open
chrisbobbe wants to merge 12 commits intozulip:mainfrom
chrisbobbe:pr-inbox-channel-folders
Open

Use channel folders in the "Inbox" view#2186
chrisbobbe wants to merge 12 commits intozulip:mainfrom
chrisbobbe:pr-inbox-channel-folders

Conversation

@chrisbobbe
Copy link
Collaborator

Fixes #1765.

cc @alya

Note on the screenshots: I think the view looks "busy" because the channel-header and unread-marker backgrounds are so bold. I'd like to follow up with a PR to finish updating the "Inbox" design to follow Figma, which will make these elements more subtle:

https://www.figma.com/design/1JTNtYo9memgW7vV6d0ygq/Zulip-Mobile?node-id=13188-51396&m=dev

image

Screenshots

Before After
image image
image image

As discussed at #mobile-design > channel folders in inbox: sticky headers @ 💬, here's a proposal for showing the channel folder in a channel header's name, but only in the sticky-header position. As discussed, we'd ideally make folder headers and channel headers both be sticky headers, but we haven't really defined what that means, and it's probably nontrivial to implement.

We also discussed that this part isn't crucial, so I've made a separate commit for it that we can drop if desired.

Screenshots of this detail:

Light Dark
image image

@chrisbobbe chrisbobbe added the maintainer review PR ready for review by Zulip maintainers label Feb 28, 2026
@alya
Copy link
Collaborator

alya commented Feb 28, 2026

I'd like to follow up with a PR to finish updating the "Inbox" design to follow Figma, which will make these elements more subtle:

Mmm, let's make sure to discuss once more in #mobile-design before you do that work, since it's been a while since the design was proposed, to make sure we're still happy with all the ideas.

Moving the buttons to collapse/expand channels to the right will be nice for getting more space for topic names (less left-side margin), and matching web better.

@alya
Copy link
Collaborator

alya commented Feb 28, 2026

Left a comment on the extra folder name commit at #mobile-design > channel folders in inbox: sticky headers @ 💬 (I'm worried it won't work well).

@alya
Copy link
Collaborator

alya commented Feb 28, 2026

I think the top DM row should be vertically closer to its "folder" label. Currently, it looks to be equidistant, which I don't think feels right semantically.

@alya
Copy link
Collaborator

alya commented Feb 28, 2026

I guess folders aren't collapsible at this point? We should probably track making them collapsible as a follow-up.

@alya
Copy link
Collaborator

alya commented Feb 28, 2026

Overall, the screenshots look reasonable to me -- I'm excited for this feature!

Copy link
Member

@rajveermalviya rajveermalviya left a comment

Choose a reason for hiding this comment

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

Thanks @chrisbobbe! Sorry for the delay! All LGTM, moving over to Greg's review.

@rajveermalviya rajveermalviya added integration review Added by maintainers when PR may be ready for integration and removed maintainer review PR ready for review by Zulip maintainers labels Mar 11, 2026
@rajveermalviya rajveermalviya requested a review from gnprice March 11, 2026 19:57
Copy link
Member

@gnprice gnprice left a comment

Choose a reason for hiding this comment

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

Thanks for building this! Generally this looks great; mostly small comments below.

The two parts I haven't read are the last commit (mentioned below) and the tests of the main, second-to-last commit:
e601570 inbox: Group channels by folder, including realm-level folders

Comment on lines 300 to 301
return Semantics(container: true,
child: result);
Copy link
Member

Choose a reason for hiding this comment

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

This Semantics widget seems to disappear from InboxChannelHeaderItem in the _HeaderItem refactor:
3d92f97 inbox [nfc]: Merge _HeaderItem base class into its only remaining subclass

A second one appears in an earlier commit on InboxFolderHeaderItem. Is it intended to move from one to the other, or should it be on both?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Ah sounds like I misresolved a conflict with #2120 :) thanks for spotting!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

(It should be on both.)

Comment on lines +238 to -213
final DmNarrow narrow;
final int count;
final bool hasMention;
final List<(DmNarrow, int, bool)> items;
Copy link
Member

Choose a reason for hiding this comment

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

The count and the mention icon effectively get removed from the DMs header in this commit:
a6bb747 inbox: Use folder-style header for all-DMs, too

I see in the screenshots that's intentional, and I guess it's implied by "folder-style" since we don't put those on folders. Would be good to briefly mention in the commit message.

Comment on lines +208 to +209
case _InboxListItemDmConversation(:final narrow, :final count, :final hasMention):
return InboxDmItem(narrow: narrow, count: count, hasMention: hasMention);
Copy link
Member

Choose a reason for hiding this comment

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

nit: This way is fine, but it can make things a bit simpler to pass the whole item object down instead of each of its fields separately:

Suggested change
case _InboxListItemDmConversation(:final narrow, :final count, :final hasMention):
return InboxDmItem(narrow: narrow, count: count, hasMention: hasMention);
case _InboxListItemDmConversation():
return InboxDmItem(data: item);

In particular that makes fewer places that need to be updated when we add or alter a field on the data class.

(This is the same pattern seen in our content widgets, which generally take a whole node object of some relevant subclass of ContentNode.)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Ah yeah, agreed. I'd like to defer this for now if that's OK.

Comment on lines 158 to +159
Finder findChannelHeader(int channelId) => find.byWidgetPredicate((widget) =>
widget is InboxChannelHeaderItem && widget.channelId == channelId).first;
widget is InboxChannelHeaderItem && widget.subscription.streamId == channelId).first;
Copy link
Member

Choose a reason for hiding this comment

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

nit: this seems to happen in the commit before the one it's relevant to:
a6bb747 inbox: Use folder-style header for all-DMs, too
3d92f97 inbox [nfc]: Merge _HeaderItem base class into its only remaining subclass

Comment on lines +1238 to +1240
"unknownChannelFolderName": "(unknown channel folder)",
"@unknownChannelFolderName": {
"description": "Name placeholder to use for a channel folder when we don't know its name."
Copy link
Member

Choose a reason for hiding this comment

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

Huh, can this situation happen?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Mmm nope!

Comment on lines +116 to +117
final sorted = unsorted.toList()
.sorted((a, b) => store.compareUiChannelFolders(a, b));
Copy link
Member

Choose a reason for hiding this comment

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

nit: simplify a bit with a tearoff

Suggested change
final sorted = unsorted.toList()
.sorted((a, b) => store.compareUiChannelFolders(a, b));
final sorted = unsorted.toList()
.sorted(store.compareUiChannelFolders);

(which might then fit on one line?)

Comment on lines +183 to +184
final channelSections = channelSectionsByFolder[folder]!.toList();
channelSections.sort((a, b) {
Copy link
Member

Choose a reason for hiding this comment

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

No need for the toList() copy; we can sort the existing list. (Just like we sort the pinnedChannelSections and otherChannelSections lists as of the commit before this one.)

Comment on lines +167 to +168
final uiChannelFolder = store.uiChannelFolder(streamId);
(channelSectionsByFolder[uiChannelFolder] ??= [])
Copy link
Member

Choose a reason for hiding this comment

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

There's a user setting to control whether folders apply to their inbox, right? Do we want to implement that setting before we ship this?

Probably isn't hard to add.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yyyes, but it's marked as "web/desktop" in the API doc:

https://zulip.com/api/register-queue

web_inbox_show_channel_folders: boolean

Determines whether channel folders are used to organize how conversations with unread messages are displayed in the web/desktop application's Inbox view.

Changes: New in Zulip 12.0 (feature level 431).

Comment on lines +390 to +395
// TODO(design) this is kind of a hack; we'd really like to keep the
// folder name onscreen by making it sticky too, i.e. make
// both folder headers and channel headers sticky headers.
// See discussion for defining and implementing that behavior:
// https://chat.zulip.org/#narrow/channel/530-mobile-design/topic/channel.20folders.20in.20inbox.3A.20sticky.20headers/near/2367795
final bool showChannelFolderName;
Copy link
Member

Choose a reason for hiding this comment

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

I'm skipping reading the code in this last commit:
7f1ada4 inbox: Show channel-folder name in channel header, only in "sticky" position

because it sounds like Alya's view above is that we don't want it product/design-wise.

Comment on lines +304 to +314
testWidgets('only pinned channels: shows pinned header, no other header', (tester) async {
final channel = eg.stream();
await setupPage(tester,
streams: [channel],
subscriptions: [eg.subscription(channel, pinToTop: true)],
unreadMessages: [eg.streamMessage(stream: channel)]);
checkFolderHeader('Pinned channels');
checkNoFolderHeader('Other channels');
});

testWidgets('only unpinned channels: shows other header, no pinned header', (tester) async {
Copy link
Member

Choose a reason for hiding this comment

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

These two test cases would be sharper if in each of them a channel in the other situation existed but didn't have any messages. That shows the logic for which headers to show is based on the (unread) messages appearing in the inbox, not on the full roster of channels.

chrisbobbe and others added 12 commits March 13, 2026 00:20
This is equivalent except that it puts channels that start with an
emoji first; see implementation comment in the method.

Test written with help from Claude.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This more generic semantics can accommodate list items that aren't
whole sections, such as folder headers (coming soon) and eventually
topic items, for zulip#389 .
Tests written with Claude.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This removes the unread count and @-mention icon from the all-DMs
header, which the channel-folder headers will also leave out for
now.
…class

This _HeaderItem class had two subclasses before:
(a) one for the all-DMs section header, InboxAllDmsHeaderItem
(b) one for channel section headers, InboxChannelHeaderItem

We recently restyled the all-DMs section header as a folder-style
header, dropping (a). Now, simplify InboxChannelHeaderItem by having
it absorb all that _HeaderItem was doing.
Clients are actually expected to show "PINNED" and "OTHER" sections
in the Inbox, with the same styling as channel folders that admins
can create for the org with Zulip Server 11+. We can think of these
two entities as "pseudo" channel folders. "UI channel folder" seems
like a reasonable umbrella term for these "pseudo" channel folders
and the realm/org-level channel folders.

Also replace the existing compareChannelFolders function, which
acted only on realm-level folders, with a new function
compareUiChannelFolders that's actually what we'll want for the inbox.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fixes zulip#1765.

Tests written with help from Claude.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

integration review Added by maintainers when PR may be ready for integration

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Use channel folders in inbox view

4 participants