Telegram github commits and releases
4.54K subscribers
601 files
20.3K links
Broadcast from the most important Telegram clients' repositories
Download Telegram
UnigramDev/Unigram/develop4425c241 files, +13/-5
Recompute the message index after removing it from the collection (#3352)

InsertMessageInOrder moves a message by removing it and reinserting it at the
index NextIndexOf computed against the collection as it was *before* the
removal, compensating for the single row that goes away. MessageCollection
removes more than one row: RemoveItem also drops the date or topic separator
that the removal orphans, so a message that was the only one of its day takes
its header with it and the collection shrinks by two. If the message is not the
last row -- a sponsored message below it, say -- the adjusted index is then past
the end and Insert throws ArgumentOutOfRangeException.

Reported by crash telemetry on 12.9.1, reached when a locally pending message
completes: PendingMessage_Completed -> InsertMessage -> InsertMessageInOrder.

Ask NextIndexOf again after the removal rather than trusting the pre-removal
index; clamping would have put the message in the wrong place instead. The
force branch had the same defect -- it reinserted at oldIndex, which is out of
bounds once the header ahead of the message is gone -- so both paths now share
one helper.

Not built: the file parses with Roslyn, nothing more.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

#unigram
UnigramDev/Unigram/chat-preview-teardown158f9424 files, +3/-16
Drop the idempotence flag from Deactivate

The handler detaches from both Closed and Unloaded before it tears anything
down, so whichever event arrives first makes the other unreachable and
Deactivate cannot be entered twice from here. The flag was dead, and it
applied to every ChatView rather than the preview - a second Deactivate
elsewhere would have become a silent no-op.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

#unigram
UnigramDev/Unigram/developde282631 files, +39/-6
Stop the gallery from taking input while it closes (#3354)

Closing the gallery is animated: OnBackRequestedOverride calls Unload()
synchronously, which drops the data context, while Hide() only runs when the
closing animation completes. In between the window is still visible and still
hit-testable, so a click on a caption hyperlink ran Caption_TextEntityClick
with a null ViewModel and crashed in the MessageDelegate constructor
(NullReferenceException, reported by crash telemetry on 12.9.1). The caption
sits outside ScrollingHost, so the tap-to-close handler never consumes it.

Unload() now clears IsHitTestVisible, which closes the whole "input after
unload" family instead of one handler at a time; Caption_TextEntityClick is
guarded as well, because a focused hyperlink can still be invoked from the
keyboard or by automation, neither of which hit tests.

Also fix the same method leaving the window on screen forever: the connected
animation's Completed was subscribed before TryStart, and when TryStart failed
translate became false, so neither Hide() was reachable. Hide() now runs off
the fade batch, which is started on every path, and Completed is subscribed
only when the animation actually started.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

UnigramDev/Unigram/develop162781d3 files, +37/-1
Detach the emoji and sticker drawers from their collections (#3355)

InvalidComObjectException — "COM object that has been separated from its
underlying RCW cannot be used" — reported by crash telemetry on 12.9.1,
raised from MvxObservableCollection.ReplaceWith inside
EmojiDrawerViewModel.Update.

EmojiDrawer's CollectionViewSource and its two toolbars bind straight to the
view model's collections, so their native peers subscribe to CollectionChanged.
Deactivate() only called Bindings.StopTracking(), which stops the bindings from
updating but leaves the value they already pushed in place, and it runs right
before XamlMarkupHelper.UnloadObject destroys those peers. The view model
subscribes to the aggregator in its constructor and Handle queues Update on the
UI thread, so a queued update outlives the drawer and raises into the dead
peers.

Clear the collection sources in Deactivate so nothing native is still attached
once the peers go away. StickerDrawer has the same shape and the same terminal
Deactivate contract, so it gets the same treatment.

EmojiMenuFlyout never deactivated its drawer at all, and only one of its three
constructors subscribed to Popup.Closed; move that subscription into Initialize
and tear the drawer down there, matching what StickerPanel does when it unloads
a drawer.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

#unigram
UnigramDev/Unigram/highlight-quote-mismatchae11bc52 files, +14/-1
Only locate a quote in the text the bubble actually rendered

MessageBubble.Highlight walks message.Text.Paragraphs and asks Message for the
block rendering each one, pairing an index into the model with an index into
the view. Nothing guaranteed the two describe the same text, and GetBlock
returns null when no range covers the index, so the paragraph dereference a few
lines later threw.

MessageAnimatedEmoji is the deterministic case. The message is a plain text
message that TDLib re-types in the API layer, so it is quotable everywhere and
the quote rides on the replying message; searchQuote compares text only and
GetCaption returns the emoji, so the quote always matches. But UpdateMessageText
excludes it from SetText and Recycle clears the ranges, so every GetBlock is
null. A translation or a summary reaches the same state differently: it is an
independent StyledText, and since entities are dropped in translation a
blockquote that forced a paragraph break in the original no longer does, so the
rendered text can have fewer paragraphs than the model.

Compare the two by reference before entering the branch, rather than guarding
the null it produces. The null only appears when the paragraph counts differ;
when they happen to match but the content does not, the old code drew the
highlight in the wrong places instead of crashing, and that is silent. Falling
back to the whole-bubble rect is also the right visual: content that never
reaches SetText is drawn by Media, which the fallback already targets.

Reported by crash telemetry on 12.9.1.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

#unigram
UnigramDev/Unigram/localised-error-messages33f31b31 files, +75/-0
Translate more localised system error messages

Windows returns system error text in the user's language, so one fault
arrives at the crash backend as a different message per locale and splits
into a separate group for each. TranslateText maps those back onto the
canonical English wording before the report is sent, and it only ever
covers the languages someone has already looked at.

This adds the languages seen in recent reports for the faults it already
handles, and the faults it did not handle at all: file sharing violations,
disk full, paging file too small, insufficient system resources, service
disabled, data area too small, invalid operation identifier, out of range
access, interface not registered, invalid registry value and missing
Unicode mapping.

Every string maps onto the wording the same fault produces on an English
system, or onto the documented Windows text. The handful that could not
be established that way are left untranslated.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

#unigram
UnigramDev/Unigram/develop06e29f03 files, +25/-7
Tear down the chat preview from the flyout's Closed event (#3353)

* Tear down the chat preview from the flyout's Closed event

ChatCell.ShowPreview hosts a second ChatView inside a MenuFlyout and hung its only
teardown on chatView.Unloaded, which never fires for a flyout-hosted view: the log
tail of the crash runs for another 109 seconds without the handler's "Unloaded"
line ever appearing. ChatView.OnCollectionChanged therefore stays subscribed to the
view model's Items after the flyout's XAML peers are gone, and the next collection
change reads Messages.ItemsPanelRoot through a separated RCW, throwing
InvalidComObjectException.

Teardown now hangs off the flyout's own Closed event, with Unloaded kept as a
secondary trigger, and ChatView.Deactivate ignores a second call so the order of
the two doesn't matter. The two forum topic cells already tore down from
flyout.Closing and so never had the hole; they move to Closed for a single pattern.

Reported by crash telemetry on 12.9.1.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

#unigram
UnigramDev/Unigram/localised-error-messages75089061 files, +34/-0
Normalize the ASTA "application is busy" message

RPC_E_SERVERCALL_RETRYLATER comes with a detail sentence naming the ASTA
thread that was timed out. The id is different on every hang, and the
sentence is localized, so one fault reports under a new group each time
and splits again per language.

Rewrite it to a canonical English sentence keeping only the IID and the
method index, which are the parts that say which call hung. The GUID and
the number after it are matched structurally, since the labels around
them are translated. Anything the pattern does not match is left alone.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

#unigram
UnigramDev/Unigram/capture-popup-focus88966271 files, +15/-13
Track focus rather than window activation in ChooseCapturePopup

The popup refreshes its list of capturable windows when the user comes back
from arranging the one they want to share. It reached that signal through
WindowContext.ForXamlRoot(XamlRoot).Activated, subscribed in Loaded and
detached in Unloaded, and both halves are unsafe:

- ForXamlRoot is a lookup in a dictionary the window is removed from when its
view is consolidated, even though the XAML tree is still alive, so it returns
null and the subscribe throws.
- XamlRoot is null by the time the popup unloads, and can throw on access, so
the detach throws too - and when it does, the handler is left attached to a
WindowContext that outlives the popup.

Focus tracks window activation just as reliably and needs neither. GotFocus is
raised when the window is activated and LostFocus when it is deactivated, so a
lost-then-got pair is the round trip worth refreshing on; a bare GotFocus is
not, since one arrives simply from opening the popup and enumerating every
window on the system is not free.

This is also the only ForXamlRoot call site in the app that dereferenced the
result directly - every other one assigns to a local and checks it.

Reported by crash telemetry on 12.9.1.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

#unigram
UnigramDev/Unigram/developd78de991 files, +15/-13
Track focus rather than window activation in ChooseCapturePopup (#3359)

The popup refreshes its list of capturable windows when the user comes back
from arranging the one they want to share. It reached that signal through
WindowContext.ForXamlRoot(XamlRoot).Activated, subscribed in Loaded and
detached in Unloaded, and both halves are unsafe:

- ForXamlRoot is a lookup in a dictionary the window is removed from when its
view is consolidated, even though the XAML tree is still alive, so it returns
null and the subscribe throws.
- XamlRoot is null by the time the popup unloads, and can throw on access, so
the detach throws too - and when it does, the handler is left attached to a
WindowContext that outlives the popup.

Focus tracks window activation just as reliably and needs neither. GotFocus is
raised when the window is activated and LostFocus when it is deactivated, so a
lost-then-got pair is the round trip worth refreshing on; a bare GotFocus is
not, since one arrives simply from opening the popup and enumerating every
window on the system is not free.

This is also the only ForXamlRoot call site in the app that dereferenced the
result directly - every other one assigns to a local and checks it.

Reported by crash telemetry on 12.9.1.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

#unigram
UnigramDev/Unigram/localised-error-messages23f2cb71 files, +11/-4
Match the HRESULT suffix instead of the first parenthesis

TranslateMessage cut each line at the first '(' so that the HRESULT .NET
appends to a COM message would not stop the sentence in front of it matching.
The sentence can contain parentheses of its own, though, and across the
recorded reports that boundary lands mid-sentence far more often than it lands
on the suffix - in TDLib assert text, in EETypeRva casts, in captured stack
traces. It only ever worked because a head that had been truncated matched
nothing and was concatenated back unchanged.

Match the suffix itself, anchored at the end. Nothing that previously reached
TranslateText stops reaching it, and the Portuguese RPC_E_WRONG_THREAD message
can now be mapped: its text reads "empacotada (marshall) para outro módulo",
so the first parenthesis was inside the sentence.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

#unigram
UnigramDev/Unigram/hresult-message-lookup5b288a11 files, +133/-9
Key localised error messages on the HRESULT, not on the sentence

Windows returns system error text in the user's language, so a single fault
lands in a different crash group in every locale. Matching those sentences one
by one only ever covers the languages somebody has already noticed, and only
after the group has fragmented. The HRESULT behind them is the same number
everywhere.

ProcessException now passes the exception's type name and HResult into
TranslateMessage, and the first line is rebuilt from a code lookup when the
type is Exception or COMException. The first line is the only one that can be
system text - a WinRT error puts its originating description on a line of its
own after it - and it is also the only part the group hash reads.

Any other type keeps the sentence match. Managed exceptions carry generic
codes: NullReferenceException is E_POINTER, ArgumentException is E_INVALIDARG,
InvalidCastException is E_NOINTERFACE. Rewriting those from the code would
replace .NET's own text with system wording and merge unrelated groups.

FatalError has no HResult of its own, so there the code is read back out of the
"(Exception from HRESULT: 0x...)" suffix when .NET appended one, and otherwise
nothing changes.

The sentence table stays. It is the fallback for every code the lookup does not
list, and it is the only cover for the DirectWrite and Direct2D families:
CoreApplication.UnhandledErrorDetected flattens those to E_FAIL while the
message keeps the original wording, so the code cannot be taken at its word.
E_FAIL and the COR_E_* codes are left out of the lookup for that reason.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

#unigram
UnigramDev/Unigram/localised-error-messagesff66f601 files, +1/-1
Map ERROR_NOT_ENOUGH_MEMORY to its own English text

"Zur Verarbeitung dieses Befehls..." is 0x80070008, whose English ends "to
process this command"; it was returning 0x8007000E's "to complete this
operation" and so merged two distinct codes into one group.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

#unigram
morethanwords/tweb/masterc7f79a41 files, +3/-2
Resolve min peers by username

morethanwords/tweb/masterce0600f4 files, +6/-4
Fix PWA manifest ID for Telegram link scopes

morethanwords/tweb/masteree72f012 files, +119/-10
Fix folder recovery after state clear

morethanwords/tweb/master05424f9300 files, +615/-615
Build

#webk
🫡3
UnigramDev/Unigram/searchcollection-diff-snapshotd6fac752 files, +28/-5
Snapshot the collections before diffing them off-thread

SearchCollection computes its diff on the thread pool:

await incremental.LoadMoreItemsAsync(0);
var diff = await Task.Run(() => DiffUtil.CalculateDiff(this, source, ...));

Both arguments are collections the UI thread owns and is still appending to,
and CalculateDiff starts by copying them. So the pool thread runs Array.Copy
over a list whose backing array and count are being replaced underneath it, and
the reported ArgumentException comes straight out of Array.CopyImpl.

The guard that was meant to prevent this is a plain bool. UpdateImpl sets
_loading before awaiting, and LoadMoreItemsAsync returns early while it is set
- but LoadMoreItemsAsync also clears it when it finishes, so one that was
already in flight when UpdateImpl set it hands the door back while the diff is
still running. The next scroll then appends to the very collection being
copied. That interleaving needs a load to be in flight at the moment an update
starts, which is why it is rare and why the report shows half a minute of
continuous scrolling behind it.

Snapshot both on the UI thread and diff the snapshots. The diff only ever
needed the sequences, the indices it produces stay valid because items are
appended rather than inserted, and nothing else has to be synchronised.

The same lines were copied into ChatTextBox's autocomplete, so all four sites
are changed; only the first is evidenced by a crash.

Reported by crash telemetry on 12.9.1.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

#unigram
UnigramDev/Unigram/develop7a026d53 files, +14/-5
Read the name, not FileType, to know what a file is

A file dragged straight out of a ZIP archive arrives with an empty FileType,
though its Name still carries the extension. HasExtension compared FileType and
so answered false for everything in one.

That decided more than it looks. StorageMedia.CreateAsync dispatches on it
before probing, so a photo out of a ZIP fell through to StorageDocument and was
sent as a file: no thumbnail, no album, no compression. GetItemsView uses it for
the WEBP split that works around the server ignoring force_file inside an album,
so a WEBP out of a ZIP would have stayed in its album and failed the whole
upload -- the exact failure that workaround exists to prevent. GenerationService
uses it to pick the audio generation mode.

HasExtension now falls back to Name when FileType is empty, rather than always
reading Name, which keeps the exact-match behaviour everywhere FileType is
populated. It cannot turn a correct false into a true either: a file with no
extension has none in its name.

Two more places read FileType directly and had the same blind spot.
MessageFactory decides whether a WEBP might be a sticker and whether a TGS
might be an animation. EditMediaPopup decides whether to open the trim toolbar
or the crop one, which meant a video out of a ZIP got the photo editor and
InitializeVideo never ran. Both go through HasExtension now.

EditMediaPopup's matters twice over: ImageHelper.OpenReadAsync already carries a
comment saying it and EditMediaPopup must agree about what a video is, and
fixing only HasExtension would have made them disagree for exactly these files.

The remaining FileType reads are TDLib's StorageStatisticsByFileType and
NetworkStatisticsEntryFile, which are unrelated types.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

UnigramDev/Unigram/developb5cdb0e1 files, +4/-3
Say what the trim strip scales to

Math.Max(ratioY, ratioY) compares a value with itself and ratioX above it was
computed and never used, which reads like a typo for Math.Max(ratioX, ratioY).
It is not one, and making that change would break the strip.

The 40 is the height of the filmstrip, not a box to fit inside. Each frame is a
Border in a row of grid columns filling 296 pixels, so it has to be 40 tall
with its width following the aspect -- which is what dividing by pixelHeight
gives. Taking the larger ratio would pin a portrait video's width to 40 instead
and leave every frame taller than the strip.

So the scale is written as what it is, and the dead ratioX goes with the
no-op Max.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

#unigram
UnigramDev/Unigram/develop6df8bf538 files, +269/-1546
Consume libvlc and webrtc as prebuilt binaries

Both were built from source as part of setting up a checkout. libvlc meant an 8.8 GB submodule,
Docker and the VideoLAN contrib toolchain; webrtc meant depot_tools, a ~1.5 hour sync, ~20 GB of
disk and a multi-hour build, ending in absolute paths to C:\webrtc hardcoded in two project
files. Both change about once a year.

They are vcpkg overlay ports now, downloading an archive for the architecture being built and
verifying it against a recorded SHA512. The sources, the scripts that produce the archives and
the patches they need moved to UnigramDev/deps, and the archives are published as releases there,
each naming the fork commit it was built from.

The libvlc port also has to place the plugin tree, which cannot be flattened: the plugins' paths
are recorded in the generated plugins.dat cache, so they reach the package as
plugins\<category>\<name>.dll through Content items rather than the flat copy the other
dependencies use.

VcpkgAutoLink is off. It put every .lib in the installed tree on every link line, which since
webrtc arrived meant Telegram.Native scanning a 339 MB static library it has no reference into.
Each project now names what it links, derived from what the binaries actually import.

tgcalls and Telegram.Native.Calls include libyuv by its full path. webrtc carries a patched copy
whose ConvertToI420 takes three extra parameters, and vcpkg's libyuv port supplies the stock one,
so a plain <libyuv.h> resolved to whichever include directory came first -- which was decided by
a hardcoded path that no longer exists. The three compiled files that used the ambiguous form now
say which library they mean.

Libraries/vlc and Libraries/webrtc are gone, submodule included. The VLC checkout the former held
is an ordinary clone of the fork now, kept outside the repository.

The instructions for building without WebRTC go too. Removing the project reference and the
ENABLE_CALLS directive still compiles, but webrtc is listed in vcpkg.json, so the restore installs
it for the triplet either way -- the download that section existed to avoid is spent before any
project is built. Making it work again means a manifest feature, not a set of manual edits.

duplicated-libraries.md records what the app now carries more than one copy of -- two ffmpeg
builds, and opus and libyuv both as DLLs and inside webrtc.lib -- with the measurements behind it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

UnigramDev/Unigram/develop51f817c4 files, +40/-20
Merge remote-tracking branch 'origin/develop' into develop

#unigram
UnigramDev/Unigram/develop74966861 files, +121/-5
Stop system error messages from fragmenting crash groups (#3357)

* Translate more localised system error messages

Windows returns system error text in the user's language, so one fault
arrives at the crash backend as a different message per locale and splits
into a separate group for each. TranslateText maps those back onto the
canonical English wording before the report is sent, and it only ever
covers the languages someone has already looked at.

This adds the languages seen in recent reports for the faults it already
handles, and the faults it did not handle at all: file sharing violations,
disk full, paging file too small, insufficient system resources, service
disabled, data area too small, invalid operation identifier, out of range
access, interface not registered, invalid registry value and missing
Unicode mapping.

Every string maps onto the wording the same fault produces on an English
system, or onto the documented Windows text. The handful that could not
be established that way are left untranslated.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Normalize the ASTA "application is busy" message

RPC_E_SERVERCALL_RETRYLATER comes with a detail sentence naming the ASTA
thread that was timed out. The id is different on every hang, and the
sentence is localized, so one fault reports under a new group each time
and splits again per language.

Rewrite it to a canonical English sentence keeping only the IID and the
method index, which are the parts that say which call hung. The GUID and
the number after it are matched structurally, since the labels around
them are translated. Anything the pattern does not match is left alone.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Match the HRESULT suffix instead of the first parenthesis

TranslateMessage cut each line at the first '(' so that the HRESULT .NET
appends to a COM message would not stop the sentence in front of it matching.
The sentence can contain parentheses of its own, though, and across the
recorded reports that boundary lands mid-sentence far more often than it lands
on the suffix - in TDLib assert text, in EETypeRva casts, in captured stack
traces. It only ever worked because a head that had been truncated matched
nothing and was concatenated back unchanged.

Match the suffix itself, anchored at the end. Nothing that previously reached
TranslateText stops reaching it, and the Portuguese RPC_E_WRONG_THREAD message
can now be mapped: its text reads "empacotada (marshall) para outro módulo",
so the first parenthesis was inside the sentence.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Map ERROR_NOT_ENOUGH_MEMORY to its own English text

"Zur Verarbeitung dieses Befehls..." is 0x80070008, whose English ends "to
process this command"; it was returning 0x8007000E's "to complete this
operation" and so merged two distinct codes into one group.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

#unigram
UnigramDev/Unigram/develop7d92ca71 files, +133/-9
Key localised error messages on the HRESULT, not on the sentence (#3360)

Windows returns system error text in the user's language, so a single fault
lands in a different crash group in every locale. Matching those sentences one
by one only ever covers the languages somebody has already noticed, and only
after the group has fragmented. The HRESULT behind them is the same number
everywhere.

ProcessException now passes the exception's type name and HResult into
TranslateMessage, and the first line is rebuilt from a code lookup when the
type is Exception or COMException. The first line is the only one that can be
system text - a WinRT error puts its originating description on a line of its
own after it - and it is also the only part the group hash reads.

Any other type keeps the sentence match. Managed exceptions carry generic
codes: NullReferenceException is E_POINTER, ArgumentException is E_INVALIDARG,
InvalidCastException is E_NOINTERFACE. Rewriting those from the code would
replace .NET's own text with system wording and merge unrelated groups.

FatalError has no HResult of its own, so there the code is read back out of the
"(Exception from HRESULT: 0x...)" suffix when .NET appended one, and otherwise
nothing changes.

The sentence table stays. It is the fallback for every code the lookup does not
list, and it is the only cover for the DirectWrite and Direct2D families:
CoreApplication.UnhandledErrorDetected flattens those to E_FAIL while the
message keeps the original wording, so the code cannot be taken at its word.
E_FAIL and the COR_E_* codes are left out of the lookup for that reason.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

#unigram
UnigramDev/Unigram/collection-adapters-reviewf17fd301 files, +162/-0
Write up the collection adapters

SearchCollection and IncrementalCollectionView are two implementations of the
same adapter, differing by one real feature - the search query driver - and
duplicating everything else. Reading them after the crash fixed in #3361 turned
up a list of defects in both, plus a few in MediaCollection, none of which is
worth losing.

Records what the adapters are for, who uses which, what is wrong with each, and
a direction: converge on IncrementalCollectionView's per-index morph and leave
SearchCollection owning only the query. Open decisions are listed as open, and
the one performance claim that has not been measured says so.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

#unigram
morethanwords/tweb/master267f5393 files, +212/-4
Fix false pin limit from archived pins leaking into the main folder

Folder-scoped messages.getDialogs / messages.getPinnedDialogs answers come
without dialog.folder_id, so archived dialogs were filed into folder 0 and their
pins piled up in pinnedOrders[0]. Nothing ever pruned them and the order is
persisted, so toggleDialogPin reported the limit as reached with only a few
chats actually pinned.

Pass the requested folder to saveDialog again (as an argument, so a known dialog
keeps its cached folder), thread it through applyDialogs, and drop foreign
folder entries from pinnedOrders on load to repair existing state.

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

morethanwords/tweb/master79b177c3 files, +71/-1
Don't count folded Community members against the pin limit

A member chat of a folded Community is hidden from the real folders, so its pin
holds a slot the user can neither see nor unpin there — the limit popup fires
with fewer rows on screen than the limit. tdesktop drops such a history from the
folder's local pinned list when the Community folds, so its limit ignores them
too. Count only the visible pins, leaving pinnedOrders as server truth so the
pins survive unfolding; the server still answers the actual request.

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

morethanwords/tweb/master41848437 files, +230/-267
Fix inline bluff spoilers in Safari

morethanwords/tweb/master08e3a633 files, +5/-5
Rename getPinnedOrdersCount to getVisiblePinnedCount

Sitting next to getPinnedOrders, it read as that array's length — the one thing
it is not.

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

morethanwords/tweb/master2a06488300 files, +789/-785
Build

#webk
🫡3
telegramdesktop/tdesktop/nightly50be7b21 files, +8/-0
[iv-editor] Fixed caret loss after undo of structural change.

telegramdesktop/tdesktop/nightly3b5470c1 files, +2/-2
[iv-editor] Fixed last line skip for vertical caret move in field.

telegramdesktop/tdesktop/nightly18a5e801 files, +11/-6
[iv-editor] Replaced item start with last line for caret moving up.

telegramdesktop/tdesktop/nightlyf429af81 files, +2/-1
[iv-editor] Fixed missing redraw for ordered list turned into bullet.

telegramdesktop/tdesktop/nightlyc28679b3 files, +18/-18
[iv-editor] Added subscript, superscript and marked to selected text.

telegramdesktop/tdesktop/nightlyd0fca682 files, +19/-4
[iv-editor] Added mutual exclusion for subscript and superscript.

telegramdesktop/tdesktop/nightlyb7a09671 files, +4/-7
[iv-editor] Replaced next cell with cell below on Enter inside table.

telegramdesktop/tdesktop/nightly994d2092 files, +73/-5
[iv-editor] Fixed loss of list items wrapped into quote.

telegramdesktop/tdesktop/nightlyff2d8ca3 files, +11/-1
[iv-editor] Fixed Enter with emoji suggestion having no selection.

telegramdesktop/tdesktop/nightlyba487621 files, +2/-1
Added discard of article draft without confirmation with pressed Ctrl.

telegramdesktop/tdesktop/nightly8ee340a2 files, +7/-0
Added discard of rich draft without confirmation with pressed Ctrl.

telegramdesktop/tdesktop/nightly4ce8b541 files, +28/-0
Fixed checkbox colors of rich text messages for dark chat themes.

telegramdesktop/tdesktop/nightly9408ac92 files, +63/-4
Added ability to replace of rich message media from clipboard.

#tdesktop
🫡2