Derive the pinned bar's topic root from the thread

Task: 2026/08/19/align-replies-thread-shared-media-attribution
This commit is contained in:
John Preston
2026-08-19 11:04:15 +04:00
committed by 23rd
parent c5e7edef8b
commit e44457662a
2 changed files with 60 additions and 6 deletions

View File

@@ -320,7 +320,7 @@ void TopControls::updatePinnedViewer() {
const auto fullHistory = !_repliesRootId && !_topic && !_sublist;
_minPinnedId = Data::ResolveMinPinnedId(
_history->peer,
fullHistory ? MsgId(0) : _repliesRootId,
activeThread()->topicRootId(),
fullHistory ? PeerId(0) : _monoforumPeerId,
migrated);
}
@@ -737,12 +737,13 @@ void TopControls::setupPinnedTracker() {
checkPinnedBarState();
return;
}
const auto topicRootId = thread->topicRootId();
SharedMediaViewer(
&_history->session(),
Storage::SharedMediaKey(
_history->peer->id,
_repliesRootId,
topicRootId,
_monoforumPeerId,
Storage::SharedMediaType::Pinned,
ServerMaxMsgId - 1),
@@ -757,13 +758,13 @@ void TopControls::setupPinnedTracker() {
const auto peerId = _history->peer->id;
const auto hiddenId = settings.hiddenPinnedMessageId(
peerId,
_repliesRootId,
topicRootId,
_monoforumPeerId);
const auto last = result.size() ? result[result.size() - 1] : 0;
if (hiddenId && hiddenId != last) {
settings.setHiddenPinnedMessageId(
peerId,
_repliesRootId,
topicRootId,
_monoforumPeerId,
0);
_history->session().saveSettingsDelayed();
@@ -778,7 +779,7 @@ void TopControls::checkPinnedBarState() {
const auto fullHistory = !_repliesRootId && !_topic && !_sublist;
const auto migrated = migratedPeer();
const auto topicRootId = fullHistory ? MsgId(0) : _repliesRootId;
const auto topicRootId = activeThread()->topicRootId();
const auto monoforumPeerId = fullHistory ? PeerId(0) : _monoforumPeerId;
const auto hiddenId = _history->peer->canPinMessages()
? MsgId(0)
@@ -1061,7 +1062,7 @@ void TopControls::hidePinnedMessage() {
Window::HidePinnedBar(
_controller,
_history->peer,
fullHistory ? MsgId(0) : _repliesRootId,
activeThread()->topicRootId(),
fullHistory ? PeerId(0) : _monoforumPeerId,
crl::guard(_wrap.get(), [=] {
if (_pinnedTracker) {

View File

@@ -0,0 +1,53 @@
# The pinned bar's topic root comes from the active thread
`HistoryView::Controls::TopControls` built the `Storage::SharedMediaType::Pinned`
key triple from its own `_repliesRootId` field. For a channel-comments thread —
`ChatWidget` in `Mode::Replies` with no `Data::ForumTopic` — that names
`(discussionGroup, repliesRootId, 0)`, a key no `Storage::SharedMedia` list can
ever hold, while the messages of that thread are indexed under the whole-peer
`(discussionGroup, 0, 0)` list. This takes the root from `activeThread()`
instead, the same expression `HistoryView::PinnedTracker` — constructed two lines
above — and `HistoryView::PinnedWidget` already use.
The change is behaviour-neutral for every mode that can reach it today.
`Data::Thread::topicRootId()` returns `_topic->rootId()` for a forum topic (equal
to `_repliesRootId`, which `ChatWidget::lookupTopic()` resolves the topic from),
and zero for a plain `History` or a `Data::SavedSublist` (every `ChatViewId` that
sets `.sublist` leaves `repliesRootId` at zero). The monoforum half of the key
keeps using `_monoforumPeerId`: that field is
`(_sublist && _sublist->parentChat()) ? _sublist->sublistPeer()->id : PeerId()`,
which deliberately differs from `Data::Thread::monoforumPeerId()` for a Saved
Messages sublist, and shared-media storage supports only the monoforum form —
`ApiWrap::sharedMediaDone` discards any slice whose `monoforumPeerId` does not
resolve through `PeerData::monoforumSublistFor`, and the self peer is not a
channel.
Two things were established while making this change and are worth recording,
because both contradict how the defect was originally described.
The replies-root list is not stale, it is absent. `Storage::SharedMedia` creates
a per-thread list only from `SharedMediaAddExisting` and `SharedMediaAddSlice`;
`SharedMediaAddNew` and `SharedMediaRemoveOne` only `find` one. Every writer that
can pass a non-zero root takes it from `HistoryItem::topic()` or
`ForumTopic::rootId()`, and `ApiWrap::sharedMediaDone` returns before storing
when `peer->forumTopicFor(topicRootId)` is null. A non-forum discussion group has
no forum, so nothing ever creates that list and no removal can decrement it.
A channel-comments thread has no pinned bar at all. `setupPinnedTracker()` runs
only from `subscribeToPinnedMessages()`, which `ChatWidget` calls only for
`Mode::History`, for a topic, and for a sublist — never for `Mode::Replies`
without a topic. `_pinnedTracker` therefore stays null there for the widget's
whole life, and the three other functions that build this key all require it.
That matches the pre-migration code, where `ChatWidget::setupPinnedTracker()`
opened with `Expects(_topic || _sublist)`.
The alternative repair — making `HistoryItem::topicRootId()` report the replies
root for a reply in a discussion thread — was rejected. It would not have fixed
anything, since nothing creates the list either way, and `topicRootId()` also
feeds `FullReplyTo`, the serialized `Data::DraftKey`, and the outgoing
`top_msg_id` and `f_forum_topic` fields, so it cannot move without changing
persisted and protocol behaviour. The repository already carries
`HistoryItem::replyToTop()` for the replies root and discriminates between the
two in `ApiWrap::exportDirectMessageLink`.
Rationale, measurements and follow-ups live in the AI task.