ZaStoGram_desktop/tasks/2026/08/19/align-replies-thread-shared-media-attribution.md
John Preston 68e8e52dcc Derive the pinned bar's topic root from the thread
Task: 2026/08/19/align-replies-thread-shared-media-attribution
2026-09-24 22:22:14 +04:00

53 lines
3.3 KiB
Markdown

# 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.