Skip to content

chat: pin conversations to the top of the inbox - #29634

Open
chrisnojima wants to merge 7 commits into
masterfrom
nojima/HOTPOT-pin-inbox
Open

chrisnojima wants to merge 7 commits into
masterfrom
nojima/HOTPOT-pin-inbox

Conversation

@chrisnojima

Copy link
Copy Markdown
Contributor

Summary

Lets users pin small conversations (1:1, group chats, and single-channel small teams) so they stay at the top of the chat inbox. Big-team channels can't be pinned.

  • Storage: the pin list is a synced per-user gregor category, chatPinnedConvs, holding a JSON array of conversation IDs with the top pin first. It follows the user across devices and survives logout. It's capped at 25 pins.
  • Ordering (Go): buildLayout puts pinned small conversations first, in list order, before the small-team bound truncation, so pins are never cut off. Rows carry a new isPinned field. Pinned IDs that aren't current small conversations are ignored.
  • New-message skip check: UpdateLayoutFromNewMessage now compares against the first unpinned row, since a new message can't move a pinned row.
  • Sync:
    • A new gregor in-band handler rebuilds the layout when the category changes.
    • The GUI also requests a layout when the pins in the pushed gregor state change. This covers pins that arrive in the connect-time sync, which never reach in-band handlers.
  • UI:
    • The inbox-row menu gets Pin to top, Move to top (hidden on the top pin) and Unpin. At the limit, "Pin to top" is disabled with an explanation.
    • Pinned rows show a small pin in the row's top-left corner.
    • On mobile, long-pressing an inbox row opens the same menu as the desktop gear. Swipe actions are unchanged.
  • Writes: writes are serialized, and each one reads the current list from the service first, so quick successive pin changes don't overwrite each other. Stale IDs are dropped on the next edit.

Design choices

  • Reselection: when the selected conversation leaves the layout, or a forced reselect happens, the inbox reselects the first row as displayed. When pins exist, that is the top pinned conversation rather than the most recently active one. This is intentional for now.
  • Pins count toward "Show more": pinned rows count toward the small-rows limit like any other row.
  • Last write wins: the whole list is one value, so if two devices change pins at the same moment, the last save wins.
  • Mixed versions: older clients ignore the category and show their usual newest-first order.

Testing

  • yarn lint:all clean (0 bailouts, 0 whole-props deps)
  • yarn jest chat/inbox constants/init passes, including new tests for parsing, reordering, pruning and the pin limit
  • Go unit tests for pin parsing, row ordering and the first-unpinned skip check pass
  • gofmt and golangci-lint --new-from-rev master are clean on the touched Go packages

Adds isPinned to UIInboxSmallTeamRow, a synced chatPinnedConvs gregor
category that orders pinned small conversations first in the inbox
layout, a gregor handler that rebuilds the layout when pins change on
any device, and TS helpers to read/write the pinned list.
Read the pinned list fresh from the service, prune only IDs missing from
the layout, and serialize writes. Base Move to top visibility on the
layout instead of the debounced gregor push state.
Go reads pins from gregor state while building the layout, but that
state only exists once the service connects, and items delivered in the
connect-time sync never reach the in-band handler. Watch the gregor
state pushed to the GUI and request a layout when the pin list in it
changes.
@chrisnojima
chrisnojima requested a balanced review from Copilot September 16, 2026 14:41

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@chrisnojima
chrisnojima requested a review from zoom-ua September 16, 2026 15:02
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.

2 participants