Skip to content

feat(sidebar): Add conversation sections and sort options#17368

Open
Rikdekker wants to merge 12 commits intonextcloud:mainfrom
Rikdekker:feat/conversation-sections-and-sort
Open

feat(sidebar): Add conversation sections and sort options#17368
Rikdekker wants to merge 12 commits intonextcloud:mainfrom
Rikdekker:feat/conversation-sections-and-sort

Conversation

@Rikdekker
Copy link

@Rikdekker Rikdekker commented Mar 14, 2026

Summary

Resolves #12025

  • Custom sections: Users can create named sections to organize conversations in the sidebar. Sections can be created, renamed, deleted, and reordered via drag-and-drop in the "Manage sections" dialog.
  • Sort options: Conversations can be sorted by recent activity (default), alphabetical order, or groups first. Sorting applies within each section independently.
  • Section behavior: Favorites are always pinned at the top, unsectioned conversations appear in an "Other" group at the bottom. Sections are collapsible with unread count badges. Favorite and archived conversations cannot be assigned to a section.

Backend

  • New talk_conversation_sections table with id, user_id, name, sort_order, collapsed columns
  • New ConversationSectionController with full CRUD + reorder API (/api/v4/sections)
  • New sectionId field on Attendee model to assign conversations to sections
  • New capability: conversation-sections

Frontend

  • New ConversationSectionHeader component for collapsible section headers
  • New ManageSectionsDialog with drag-and-drop reordering
  • New conversationSections Pinia store
  • Sort mode selector with 3 options (activity, alphabetical, groups first)
  • Sort mode and collapsed state persisted in browser storage

Screenshots

Talk-Sections-demo

Talk-Collapsible-sections drop screenshots h Talk-Sort Talk-Manage-sections

Test plan

  • Create, rename, and delete sections via "Manage sections" dialog
  • Assign conversations to sections via conversation context menu
  • Reorder sections via drag-and-drop in "Manage sections"
  • Collapse/expand sections, verify unread count badge shows on collapsed sections
  • Switch between sort modes (activity, alphabetical, groups first)
  • Verify favorites always appear at top, unsectioned in "Other" at bottom
  • Verify sort mode persists across page refresh
  • Verify section order persists across page refresh
  • Verify favorite/archived conversations cannot be assigned to sections

🤖 Generated with Claude Code

@Rikdekker Rikdekker force-pushed the feat/conversation-sections-and-sort branch from 19bd9ad to ba23a61 Compare March 14, 2026 11:54
Rikdekker and others added 6 commits March 14, 2026 13:37
Allow users to organize conversations into custom sections with
drag-and-drop reordering, and sort conversations by recent activity,
alphabetical order, or groups first.

Resolves nextcloud#12025

Signed-off-by: Rikdekker <rik@shalution.nl>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Rikdekker <rik@shalution.nl>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add type assertions in template to satisfy TypeScript type checker
for union type ListItem (Conversation | SectionHeaderItem).

Signed-off-by: Rikdekker <rik@shalution.nl>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fix import ordering, member delimiter style, missing JSDoc comments,
unnecessary parentheses, and sorted named exports. Add mock for
conversationSectionsService in LeftSidebar tests to prevent network
errors during test execution.

Signed-off-by: Rikdekker <rik@shalution.nl>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Rikdekker <Rikdekker@users.noreply.github.com>
Revert version back to 24.0.0-dev.2 (matching main branch) to fix
"App not compatible with this version" error in integration tests.
Add missing section_id key to ChatManagerTest attendee data arrays
to fix "Undefined array key" PHP warning.

Signed-off-by: Rikdekker <rik@shalution.nl>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Rikdekker <Rikdekker@users.noreply.github.com>
Update min-version and max-version to 34 to match the main branch
target, fixing integration test failures.

Signed-off-by: Rikdekker <rik@shalution.nl>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Rikdekker <Rikdekker@users.noreply.github.com>
@Rikdekker Rikdekker force-pushed the feat/conversation-sections-and-sort branch from 293f1aa to 224cbd8 Compare March 14, 2026 12:39
Copy link
Member

@nickvergessen nickvergessen left a comment

Choose a reason for hiding this comment

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

This is a huge step 😎

However it will prevent a conversation from being in multiple sections, which we had planned. Not sure if it's better to merge like this already, or if we should try a second PR based on the current one that tries to make it possible to have 1toN mapping available

@nickvergessen
Copy link
Member

Also to be able to review easier, we'd appreciate if you address comments in individual commits going forward. we can rebase and combine them at the end, but it avoids having to recheck the full diff all the time :)

@nickvergessen nickvergessen added feature: api 🛠️ OCS API for conversations, chats and participants feature: frontend 🖌️ "Web UI" client feature: conversations 👥 labels Mar 14, 2026
@nickvergessen nickvergessen added this to the 🏖️ Next Major (34) milestone Mar 14, 2026
@Rikdekker
Copy link
Author

Rikdekker commented Mar 14, 2026

Thanks for the review! 🙏 Will address all comments in individual commits.

Regarding 1:1 vs 1:N section mapping:

I believe 1:1 is the right model for sections — they behave like folders, not labels. A few observations:

  • UX clarity: Showing the same conversation in multiple sidebar sections would be confusing. Users naturally think "where is this?" (folder mental model), not "which labels does this have?" (tag mental model).
  • Unread count ambiguity: With 1:N, the same unread message would inflate counts across multiple sections, making the sidebar misleading.
  • Industry consensus: Slack, Discord, and Microsoft Teams all use 1:1 mapping for channel categories. None of them allow a channel to appear in multiple categories.
  • Nextcloud own design confirms this: Favorites and sections are already mutually exclusive, the UI uses radio buttons (single select), and the sidebar assumes each conversation appears in one place.

If cross-cutting organization is ever needed, a separate tagging system would be more appropriate than multi-section — tags and folders solve fundamentally different problems.

Happy to discuss further if you see use cases I'm missing!

Rikdekker and others added 3 commits March 14, 2026 14:21
Add userId to findById() and clearSectionFromAttendees() to ensure
sections can only be accessed/modified by their owner at the query
level, preventing insecure direct object reference vulnerabilities.

Signed-off-by: Rikdekker <rik@shalution.nl>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Rikdekker <Rikdekker@users.noreply.github.com>
Replace autoincrement with Snowflake ID pattern for the
talk_conversation_sections table, matching the scheduled messages
table design. Rename index to tcs_user_id.

Signed-off-by: Rikdekker <rik@shalution.nl>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Rikdekker <Rikdekker@users.noreply.github.com>
Remove docs/conversation-sections.md (documentation is in OpenAPI specs).
Revert CHANGELOG.md changes (done manually on release).
Move capabilities entries from 23.x to section 24.

Signed-off-by: Rikdekker <rik@shalution.nl>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Rikdekker <Rikdekker@users.noreply.github.com>
@nickvergessen
Copy link
Member

after broken app update before the log level stayed on debug again

Well that is basically exactly the problem 😅
The label thinking comes from our very own company instance, where multiple members are in multiple groups, e.g. "Groupware team" and "Security experts". it would be good to be able to sort people into both at the same time, so you easily find them when checking for groupware as well as when checking for security.

a separate tagging system would be more appropriate than multi-section — tags and folders solve fundamentally different problems.

yeah, but you can have "folder behaviour" with tag API, but not the other way around.
And we will not have both, tags and folders, as that is too confusing UX-wise.

the sidebar assumes each conversation appears in one place.

This can be adjusted of course.


I'll discuss it with the team next week

Copy link
Member

@nickvergessen nickvergessen left a comment

Choose a reason for hiding this comment

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

Frontend

Design

  • Having a separate "Manage sections" entry at the bottom is lessening the already reduced vertical space, instead we can have a 3 dot menu button that appears on hover, with edit/reorder/delete which leads to the modal with that section in an editable state
    Sample from mail app: https://github.com/user-attachments/assets/41903e40-116c-4871-a288-e3442ef9749c

  • It seems the headers are not using our components

  • In the "Manage sections" modal
    Image

    • each item seems to be taller than usual? Possibly it's 44px instead of default clickable area.
    • To create a new section feels not accessible and inconsistent with how it's usually done in NC (lack of a button, only a text field with a placeholder, etc), we should add an explicit button, and then a text field
    • There should also be "move up" and "move down" buttons in the 3 dot menu for a11y
  • Wording should be category / tag / label instead of "section"


We can also take over the Pull Request to address the feedback ourselves in case it's too much for you.


$attendeesTable = $schema->getTable('talk_attendees');
if (!$attendeesTable->hasColumn('section_id')) {
$attendeesTable->addColumn('section_id', Types::BIGINT, [
Copy link
Member

Choose a reason for hiding this comment

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

Our plan would be instead of int make this a TEXT type column and store a json encoded array with the applicable section ids


## 24
* `conversation-sections` (local) - Whether the user can create custom sections to organize conversations in the sidebar
* `config => conversations => sort-mode` (local) - User selected sort mode for conversations (`activity`, `alphabetical`, or `type-first`)
Copy link
Member

Choose a reason for hiding this comment

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

This capability is just here in the docs but not available in the Capabilities.php
Either we clean it up and keep sorting BrowserStorage-only, or implement it via user preferences like config => chat => style currently works.

<NcActionButton
closeAfterClick
type="radio"
:modelValue="sortMode === 'type-first'"
Copy link
Member

Choose a reason for hiding this comment

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

The team brought up that it feel like activity + alphabetical and type-first are two different categories - sorting and grouping, and users e.g. might want to combine 'groups first' or 'private first' with 'alphabetical' or 'activity'. Maybe we can store them as separate preferences, or a tuple.

Rikdekker added a commit to Rikdekker/spreed that referenced this pull request Mar 21, 2026
Addresses all review feedback from nextcloud#17368:

**Rename & terminology:**
- Rename "sections" to "categories" throughout (backend, frontend, API)
- API endpoints: /sections → /categories

**Backend:**
- IDOR fix: userId parameter on mapper queries
- Snowflake ID for primary key
- category_ids as TEXT/JSON column (single category per conversation)
- Sort order and group mode as server-side user preferences
- Consistent string-based ID handling for Snowflake IDs

**Frontend:**
- Merge filter + sort into single dropdown with NcActionSeparator
- Split sort (activity/alphabetical) and group (type-first) into separate settings
- Migrate sortMode from Vuex to Pinia settings store
- Use NcAppNavigationItem with allowCollapse for category headers
- Inline category management via 3-dot menu (rename, move up/down, delete)
- New category creation via filter/sort dropdown
- Delete confirmation using Nextcloud ConfirmDialog
- Move up/down disabled on first/last category
- Conversations move to "Other" when category is deleted (instant UI update)

**Cleanup:**
- Remove ManageSectionsDialog (replaced by inline management)
- Remove standalone docs (use OpenAPI specs)
- Revert CHANGELOG.md and appinfo/info.xml changes
- Regenerate OpenAPI specs and TypeScript types

Signed-off-by: Rikdekker <rik@shalution.nl>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Rikdekker
Copy link
Author

Rikdekker commented Mar 21, 2026

Thank you for the thorough review @nickvergessen! We've addressed all feedback points in this commit.

Inline comment responses

# Feedback Status
1 IDOR fix$userId parameter on mapper queries ✅ Both findById() and clearCategoryFromAttendees() now require and filter on $userId
2 Snowflake ID for primary key ✅ Following the scheduled messages pattern
3 Index rename to tcs_user_id
4 Remove docs/conversation-sections.md ✅ Documentation via OpenAPI specs
5 Capabilities to v24 ✅ Moved to v24 section
6 Revert appinfo/info.xml
7 Skip CHANGELOG.md ✅ Reverted
8 TEXT column with JSON array category_ids is TEXT with JSON. Single category enforced in application layer (like MS Teams), schema supports multiple if needed later
9 Sort capability not in Capabilities.php ✅ Implemented as server-side user preferences following chat => style pattern: sort-order and group-mode in Config.php, Capabilities.php, BeforePreferenceSetEventListener.php
10 Sort vs Group as separate settings ✅ Split into sortOrder (radio: activity/alphabetical) + groupMode (checkbox: groups first) — independently combinable

Review body responses

# Feedback Status
11 Vuex → Pinia ✅ Migrated to Pinia settings.ts store
12 Merge filter + sort buttons ✅ Single dropdown with FilterCogOutline icon and NcActionSeparator. Includes "New category" at the bottom
13 3-dot menu instead of "Manage sections" ✅ Removed dialog entirely. Inline management via 3-dot menu per header: rename, move up/down, delete. Delete uses ConfirmDialog
14 Headers not using our components ✅ Now uses NcAppNavigationItem with allowCollapse for native Nextcloud chevron
15 Item height / accessibility ✅ Dialog removed. Move up/down in 3-dot menu, disabled on first/last category
16 Explicit button for new category ✅ "New category" button in filter/sort dropdown, shows input field on click
17 Move up/down for a11y ✅ In 3-dot menu, disabled at boundaries
18 Wording → category ✅ Renamed throughout: classes, API (/categories), capability (conversation-categories), UI strings

Screenshots

Creating a new category via the filter/sort dropdown:

New-category

Assigning a conversation to a category and the result:

Move-To-Category

Edit category
Edit-category

Rikdekker added a commit to Rikdekker/spreed that referenced this pull request Mar 21, 2026
Addresses all review feedback from nextcloud#17368:

**Rename & terminology:**
- Rename "sections" to "categories" throughout (backend, frontend, API)
- API endpoints: /sections → /categories

**Backend:**
- IDOR fix: userId parameter on mapper queries
- Snowflake ID for primary key
- category_ids as TEXT/JSON column (single category per conversation)
- Sort order and group mode as server-side user preferences
- Consistent string-based ID handling for Snowflake IDs

**Frontend:**
- Merge filter + sort into single dropdown with NcActionSeparator
- Split sort (activity/alphabetical) and group (type-first) into separate settings
- Migrate sortMode from Vuex to Pinia settings store
- Use NcAppNavigationItem with allowCollapse for category headers
- Inline category management via 3-dot menu (rename, move up/down, delete)
- New category creation via filter/sort dropdown
- Delete confirmation using Nextcloud ConfirmDialog
- Move up/down disabled on first/last category
- Conversations move to "Other" when category is deleted (instant UI update)

**Cleanup:**
- Remove ManageSectionsDialog (replaced by inline management)
- Remove standalone docs (use OpenAPI specs)
- Revert CHANGELOG.md and appinfo/info.xml changes
- Regenerate OpenAPI specs and TypeScript types

Signed-off-by: Rikdekker <Rikdekker@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Rikdekker Rikdekker force-pushed the feat/conversation-sections-and-sort branch from 1ba676a to b500d87 Compare March 21, 2026 10:56
Rikdekker and others added 3 commits March 21, 2026 12:05
Addresses all review feedback from nextcloud#17368:

**Rename & terminology:**
- Rename "sections" to "categories" throughout (backend, frontend, API)
- API endpoints: /sections → /categories

**Backend:**
- IDOR fix: userId parameter on mapper queries
- Snowflake ID for primary key
- category_ids as TEXT/JSON column (single category per conversation)
- Sort order and group mode as server-side user preferences
- Consistent string-based ID handling for Snowflake IDs

**Frontend:**
- Merge filter + sort into single dropdown with NcActionSeparator
- Split sort (activity/alphabetical) and group (type-first) into separate settings
- Migrate sortMode from Vuex to Pinia settings store
- Use NcAppNavigationItem with allowCollapse for category headers
- Inline category management via 3-dot menu (rename, move up/down, delete)
- New category creation via filter/sort dropdown
- Delete confirmation using Nextcloud ConfirmDialog
- Move up/down disabled on first/last category
- Conversations move to "Other" when category is deleted (instant UI update)

**Cleanup:**
- Remove ManageSectionsDialog (replaced by inline management)
- Remove standalone docs (use OpenAPI specs)
- Revert CHANGELOG.md and appinfo/info.xml changes
- Regenerate OpenAPI specs and TypeScript types

Signed-off-by: Rikdekker <rik@shalution.nl>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix arrow function spacing: `fn($id)` → `fn ($id)` (php-cs-fixer)
- Wrap array_filter/array_map in array_values() for psalm list type
- Add sort-order and group-mode to CapabilitiesTest expectations and mocks

Signed-off-by: Rikdekker <rik@shalution.nl>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…arning)

- Add psalm type annotation for json_decode result in ConversationCategoryMapper
- Handle missing category_ids key in AttendeeMapper::createAttendeeFromRow

Signed-off-by: Rikdekker <rik@shalution.nl>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Rikdekker Rikdekker force-pushed the feat/conversation-sections-and-sort branch from b500d87 to b37ee63 Compare March 21, 2026 11:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement feature: api 🛠️ OCS API for conversations, chats and participants feature: conversations 👥 feature: frontend 🖌️ "Web UI" client

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Sorting and grouping of Talk Chats

2 participants