[morethanwords/tweb/master](https://github.com/morethanwords/tweb/tree/master) • [fcdcb04](https://github.com/morethanwords/tweb/commit/fcdcb048724d4ba12841d01f97abed905984897d) • _3 files, +338/-0_
Refresh chat-folder membership on unread-mark toggle and message deletion

Chat-folder membership is driven by a cached index (dialog[index_N] /
folder.dialogs) that is written only by dialogsStorage.processDialogForFilters
— separate from the folder unread counters. Two update handlers mutated a
dialog's unread state without re-processing filters, so a dialog could go stale
in an exclude_read ("Unread") folder: it showed in the list with no badge (the
badge reads the live unread_count, already 0) and only vanished when opened
(open -> readHistory -> processDialogForFilters).

- onUpdateDialogUnreadMark: marking a read chat unread didn't add it to an
  exclude_read folder, and clearing the mark (e.g. from another client) left it
  stranded there.
- onUpdateDeleteMessages: deleting a channel's unread posts drops unread_count
  to 0 but left the channel stuck in the folder.

Both fixed by calling processDialogForFilters after releaseUnreadCount, matching
the readHistory ordering (release -> processDialogForFilters -> dispatch). No
counter double-count: the unread*PeerIds sets are idempotent and the additive
unreadMessagesCount nets to zero (release covers folders that matched before the
change; the membership-change modify uses the post-mutate count).

Adds synthetic regression tests:
- unreadMarkFilterStale.test.ts: mark/clear maintains the index, counters net-zero.
- channelMuteFilter.test.ts: muting alone is correct in isolation; deleting a
  channel's unread posts must drop it from exclude_read.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

[morethanwords/tweb/master](https://github.com/morethanwords/tweb/tree/master) • [f9eb2de](https://github.com/morethanwords/tweb/commit/f9eb2dea635fa865f7eeb7da8e46424e230ac331) • _8 files, +350/-12_
Fix forum topic open, invite-tab, and cross-account propagation bugs

- Opening a just-created topic in a cold (not-yet-warm) forum dropped the
  threadId and showed the forum root: Chat.setPeer set isForum only in the
  async onChangePeer, so a chat_update mid-transition compared against a stale
  isForum and reset the peer. Set isForum synchronously in setPeer.

- Joining a forum via an invite link didn't open the topics tab. Route both
  PopupJoinChatInvite.openChat and internalLinkProcessor's already-joined
  (chatInviteAlready/Peek) path through [appImManager.open](http://appImManager.open/)() instead of
  setInnerPeer(), so forums go through op() and open the sidebar topics tab.

- A topic created on another account never appeared in an open/cached topic
  list, and stayed hidden even after reopening or receiving messages, until a
  full reload. Replication-lag race: the topic-create arrives before the topic
  is queryable by id, messages.getForumTopicsByID returns forumTopicDeleted,
  and getForumTopicById blacklisted it permanently in deletedTopics. Build the
  topic locally from the messageActionTopicCreate action (it carries title,
  icon and id) instead of fetching it, and only blacklist a topic when the
  server explicitly returns forumTopicDeleted (not on errors/omissions).

- Guard canManageTopic and the generic-animations sticker preload against null.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

[#webk](?q=%23webk)