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/temp9b6feb41 files, +3/-39
Reuse UpdateMessageTopic from UpdateContent

The MessageHeaderMessageTopic branch was a verbatim copy of it.

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

UnigramDev/Unigram/temp82828492 files, +23/-0
Recycle service message containers

Only MessageSelector was recycled, so a service message leaving the viewport
went back on the queue still holding its view model and its text inlines.

Also gives the per-content subclasses a place to reset the state they set,
which the shared templates need.

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

UnigramDev/Unigram/temp3bd2fe41 files, +16/-0
Fix stale publisher chip on recycled gift messages

MessageGift, MessageGiftedPremium, MessageGiftedStars and MessagePremiumGiftCode
share one template, but only the first one touches the publisher border, so a
recycled container kept showing the previous message's publisher.

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

UnigramDev/Unigram/temp551580a1 files, +7/-9
Start a selection only on a direct hit

ResolvePosition resolves a point to the nearest selectable, clamping when the
pointer is past the end of a line, below the last block or beside a cell. A drag
needs that clamp, but a press was taking it too: pressing on media resolved to a
neighbouring caption, and the capture that follows moved the whole pointer
sequence to the selection root. Nothing else saw it, so a drag out of
PhotoContent never began.

The press now requires ResolvePosition to report a direct hit. Drags still clamp,
so extending a selection past the end of a line is unchanged.

The multi-tap branch tested the same flag to keep gap presses out of the tap
sequence, which the anchor now guarantees.

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

UnigramDev/Unigram/temp34ab6311 files, +24/-6
Fix null dereferences in service message text

- a gift purchase offer in a chat that isn't a user
- a stopped poll event whose message isn't a poll
- a refunded suggested post whose replied-to message has no post info
- an empty task list on a checklist update

All of them dereferenced without checking; they now fall back to no text.

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

UnigramDev/Unigram/tempab2e7141 files, +18/-13
Guard the entity offset in the suggested photo action

The bold entity was placed at the index of {0} in the format string, with no
check: a translation without the placeholder gave a negative offset.

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

#unigram
UnigramDev/Unigram/tempfbc19b71 files, +7/-5
Use ActionRequestedPeerUser when no user was shared

The branch that shows it sat behind a condition identical to the one above it,
so it was unreachable and an unresolved user list rendered as a gap.

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

UnigramDev/Unigram/tempf15da6e1 files, +20/-3
Keep entities aligned across a placeholder substitution

Only entities starting after the placeholder were shifted, so an entity
spanning it (markdown parsed before the substitution) kept its old length and
ended short. ReplaceWithLinks also measured against the last inserted name
rather than the placeholder, leaving anything in between unshifted.

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

UnigramDev/Unigram/temp3dfb83d1 files, +5/-21
Remove dead code in service message text

An if (true) with an unreachable else, a condition on a local just set to 0,
and a local assigned but never read.

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

UnigramDev/Unigram/temp6fa750b2 files, +9/-0
Expose ExecuteServiceMessage on the message delegate

Service message controls moving out of ChatView.xaml can no longer reach the
view's click handler, so they need the action through their own delegate.

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

UnigramDev/Unigram/tempd09f6825 files, +369/-331
Move the gift service message into its own control

126 lines of ChatView.xaml and four branches of UpdateContent, addressed by
string through FindName, become one control with typed fields.

The publisher chip and the ribbon are now reset in one place shared by the
three contents that don't own them, and Recycle releases the sticker.

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

UnigramDev/Unigram/temp40357805 files, +251/-231
Move the upgraded gift service message into its own control

Same shape as the gift one; the dead commented-out layout it carried is gone.

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

UnigramDev/Unigram/tempf88e1275 files, +116/-57
Move the giveaway prize service message into its own control

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

UnigramDev/Unigram/temp67080895 files, +186/-139
Move the suggested birthdate service message into its own control

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

#unigram
UnigramDev/Unigram/temp1f8ad8a5 files, +123/-84
Move the chat background service message into its own control

Covers the event log counterpart too, which shares the template.

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

UnigramDev/Unigram/tempbbce8fa6 files, +205/-165
Move the photo service message into its own control

Chat photo, suggested profile photo and story mention share one template; the
story-opening click moves with them, so ChatView no longer reaches into the
template by name to find the segments to animate from.

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

UnigramDev/Unigram/tempa8e0f548 files, +80/-145
Move the unsupported message into its own control

Unsupported messages have been rendered by the service template for a while, so
the old Content\UnsupportedContent control was only still reachable through the
bubble content factory. Its update check, the one part still in use, moves with
the template.

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

UnigramDev/Unigram/temp9c0ff6d10 files, +342/-243
Move the header service messages into their own controls

The account info and message topic headers were the last two contents
UpdateContent knew about, so the base implementation is now empty and
MessageService no longer reaches into a template it doesn't own.

UpdateServiceWithForumTopic dropped its action parameter with them: it existed
to call UpdateMessageTopic, which is now the header control's own method.

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

UnigramDev/Unigram/temp03f7b191 files, +38/-7
Allow a selection to start from the bubble surface

Requiring a direct hit kept media drags working but took the gap with it: a press
in the padding beside the text, which ResolvePosition clamps to the nearest
block, no longer began a selection.

The press is now filtered by what it landed on rather than by whether the
position was clamped. Walking up from the pressed element to the root, a
selectable starts a gesture, any other IContent refuses it, and reaching the root
without crossing either means the press was on the bubble itself. Testing the
root by identity would not do: a press on the bubble background reports whichever
template element carries the brush, never the root the manager was attached to.

The multi-tap branch tests the clamp again, since clamped presses can begin a
gesture once more and a click beside the text should not turn the next one into a
word selection.

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

UnigramDev/Unigram/temp13b0aa21 files, +1/-1
Update TDLib

#unigram
UnigramDev/Unigram/temp2b6f4ac1 files, +1/-1
Fix crash when sending non-text content with ephemeral bot commands (#3323)

CreateSendMessage maps anything that is not an InputMessageText to a null
caption, then dereferenced it unconditionally. Sending a sticker (or any
other media) in a chat where HasEphemeralBotCommands is set crashed with a
NullReferenceException.

Reported by crash telemetry on 12.9.0.0.

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

UnigramDev/Unigram/tempe53bc6c1 files, +17/-0
Translate font and render target error messages (#3324)

DirectWrite's two font-file errors were missing from TranslateText entirely, so
every locale reported them as its own crash group. The render target message was
already translated, but only for a few locales.

Adds the localized variants observed in crash reports:

- font file not found: de, es, fr, it, pt, ru, tr, zh-Hans
- font file access denied: fr, ru
- wrong render target: es, fr, ru

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

UnigramDev/Unigram/temp3b186d21 files, +6/-0
Fix crash when opening the gallery (#3326)

ConvertOf is an x:Bind function binding over ViewModel.SelectedItem, and
Bindings.Update runs from Load before an item has been selected, so the very
first evaluation dereferenced a null GalleryMedia.

ConvertCaption in the same file already guards its input the same way.

Reported by crash telemetry on 12.9.0.0.

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

UnigramDev/Unigram/temp63a834d1 files, +3/-1
Fix crash when rendering the wallpaper preview bubbles (#3327)

* Fix crash when rendering the wallpaper preview bubbles

The plain-run fast path wrote whatever font size had changed, including 0, and
XAML rejects a FontSize of 0 with E_INVALIDARG. GetOrCreateRun already treats
non-positive as "inherit" and clears the property instead; the fast path now
does the same.

MessageBubble.Mockup reaches this: it calls SetText without a font size, so the
default of 0 replaces the size the bubble was previously rendered with, and the
transition is exactly what triggers the write.

Reported by crash telemetry on 12.9.0.0.

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

* Use the computed font size in the fast path

Per review: _fontSize is the raw value, fontSize is what AutoFontSize resolved
it to, and the slow path already passes the resolved one to GetOrCreateRun. The
fast path writing the raw value is the actual defect, so write the resolved one
instead of clearing the property — clearing would inherit the parent's size
rather than applying the theme size.

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/temp21628b71 files, +5/-1
Never report a negative desired height from SettingsPanel (#3328)

The IsHeader branch subtracts the trailing gap that follows the last child, but
the accumulator starts at 0 when IsFooter is also set, so a panel with nothing
visible reported -16. XAML rejects a negative desired size.

Both flags are set together on ShareGroupCallPopup, ChatInviteLinkInfoPopup and
FolderPage, so the combination is reachable; no crash report is currently tied
to it, this is a latent defect found while reading the layout code.

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

UnigramDev/Unigram/temp26e36721 files, +3/-1
Fix the expander never collapsing its content (#3329)

The scoped batch's Completed handler compares _tracker against a value captured
with a post-increment, so the comparison was (n + 1) == n and never held. The
handler is what applies Visibility.Collapsed after the collapse animation, and
PopupRoot.Visibility is set to Visible unconditionally just above it, so
collapsed content stayed visible and kept participating in layout — hidden only
by PopupHost.Height = 0 and the negative margin.

Pre-increment gives the guard its intended meaning: apply the final visibility
unless a newer toggle has started since this batch began.

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

UnigramDev/Unigram/tempd675e791 files, +8/-0
Fix crash when copying an empty selection or an absent note (#3330)

CopyText(XamlRoot, FormattedText) dereferenced its argument immediately, but its
callers pass values that are legitimately null: TextSelectionManager's
GetSelectedText returns null when nothing is selected, BlockQuote passes the
result of FormattedTextBlock.GetSelectedText, and ProfileHeader passes
UserFullInfo.Note, which is null when the contact has no note.

The method is async void, so the NullReferenceException was posted to the
dispatcher and terminated the app instead of being catchable.

Reported by crash telemetry on 12.9.0.0.

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

UnigramDev/Unigram/temp7d0d7be1 files, +2/-2
Only log the voip ready state when it changes (#3331)

OnStateUpdated logged before the guard that ignores an unchanged state, so a
source repeating the same state filled the whole log window with identical
lines — one crash report held nothing but Failed, every entry stamped in the
same millisecond, which cost us everything that preceded it.

OnSignalBarsUpdated just below already logs from inside its change guard.

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

#unigram
UnigramDev/Unigram/tempea40fcc1 files, +36/-13
Keep the log tail in a ring buffer (#3332)

Three things were wrong with the window that ships in every crash report.

It was a List trimmed with RemoveAt(0), so once full every log call copied all
retained entries down one slot. Logging happens on hot paths; this is now an
overwrite of one slot.

Dump stored its "Bump" marker as an entry, so taking a dump evicted a real line
and a second dump left the first marker behind. It is appended to the output
instead, and Dump no longer mutates anything.

The size was an inline 50, which is under a second of ordinary logging. It is
now a named constant at 200.

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

UnigramDev/Unigram/tempda46bee1 files, +1/-0
Add todo note

UnigramDev/Unigram/temp066672a1 files, +10/-4
Optimize hashset pop

UnigramDev/Unigram/temp320dce97 files, +127/-8
Experimental bubble content recycling

UnigramDev/Unigram/tempd642d862 files, +10/-0
Clear text block pool on navigate

UnigramDev/Unigram/tempe7ffacf8 files, +113/-57
Improve recycling for some contents

UnigramDev/Unigram/temp99d1e141 files, +255/-0
Started to work on WebRTC issues

UnigramDev/Unigram/tempc71aa982 files, +0/-3
Code cleanup

UnigramDev/Unigram/temp74469722 files, +0/-0
Update tlottie to latest dev

UnigramDev/Unigram/temp9090b241 files, +4/-4
Update webrtc todo

UnigramDev/Unigram/tempc533f042 files, +2/-2
Bump version to 12.9.1

#unigram
UnigramDev/Unigram/devicelostba693f92 files, +41/-13
Don't recreate the Direct3D device from the device-lost callback

PlaceholderImageHelper watches its D3D device with RegisterDeviceRemovedEvent
and recreated the device straight from the threadpool wait callback. That runs
while the display driver is still resetting, and D3D11CreateDevice at that
moment faults inside the vendor user-mode driver (reported by crash telemetry
on 12.9, on two different Intel UMDs).

The device is now rebuilt lazily instead: PlaceholderHelper already calls
HandleDeviceLost() on every access to the singleton, so the next use recreates
it from the UI thread. HandleDeviceLost tolerates a null device, which is the
state left behind when creation failed.

Two related lifetime fixes in the same path:

- CreateDeviceResources released nothing before calling D3D11CreateDevice.
com_ptr::put() overwrites without releasing, so recreating over the removed
device stranded it and everything the driver held behind it.

- PlaceholderImageHelper::Close() called StopWatchingCurrentDevice(), which
closes the threadpool wait but does not wait for a callback that is already
running -- the callback then reached RaiseDeviceLostEvent on an object that
was being destroyed. The drain that already existed in ~DeviceLostHelper is
now a Shutdown() method that Close() calls, and it was dead code before
because Close() had already cleared the handle it checks.

Not built: no UWP/.NET Native build environment here.

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

#unigram
UnigramDev/Unigram/instant-empty-table3b632791 files, +7/-0
Handle an instant view table with no rows

ProcessTable computed its column count with table.Cells.Max(...), which throws
InvalidOperationException: Sequence contains no elements when the table has no
rows at all. It reached OnApplyTemplate through InstantContent.UpdateView, so
the whole message failed to render. Reported by crash telemetry on 12.9.1.

There is no grid to build for an empty table, so render just the caption --
which is what the method already falls back to, and callers of ProcessBlock all
handle a null return.

Not built: no UWP/.NET Native build environment here. Verified the file still
parses with Roslyn.

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

#unigram
UnigramDev/Unigram/swapchain-diagnosticsdaaee852 files, +50/-5
Log why the video swap chain failed, and don't crash when it does

AsyncMediaPlayerSwapChain::Create() swallowed every failure and returned false,
so the only trace left was SwapChainOptions() throwing "You must wait for the
VideoView to be loaded before calling GetSwapChainOptions()" -- a message
inherited from LibVLCSharp that describes a lifecycle mistake this code cannot
make. The real cause is that one of the Direct3D calls failed, and nothing
recorded which one. That throw crossed a XAML Loaded handler (gallery video,
stories, the music player all construct AsyncMediaPlayer there) and became an
unhandled exception, reported by crash telemetry on 12.8.1 and 12.9.

Create() now names each stage as it goes and logs the stage, the HRESULT and
the message, so the next report says which driver call failed. The thrown
message says what actually happened.

The player no longer fails construction over it: the Direct3D device can
legitimately be unavailable while the display driver is resetting, and there is
nothing the caller could do about it. When the swap chain is not loaded the
player is built with --no-video instead, so audio still plays and the failure
is in the log rather than in a crash.

Also releases m_d3d11Device/m_deviceContext between adapter attempts --
com_ptr::put() overwrites without releasing, so a failed attempt could strand a
device.

Not built: no UWP/.NET Native build environment here.

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

#unigram
UnigramDev/Unigram/settingsbutton-disabled0b6ba981 files, +4/-5
Don't animate a deferred template part from SettingsButton's Disabled state

SettingsButton's Disabled visual state animated the Foreground of three template
parts, one of which -- DescriptionPresenter -- is x:Load="False" and is only
realized by OnApplyTemplate when Description is non-empty. A Storyboard whose
TargetName cannot be resolved fails with E_INVALIDARG, and because the initial
visual state is applied while the template is being applied, that is inside the
control's measure. The ArgumentException therefore surfaces from the measure of
whatever is above it, which is why the reported stacks anchor on
HeaderedControlPanel and SettingsPanel and name nothing closer to the cause.

It needs the button to be disabled before its template is applied, which is what
happens when the disabled state is inherited: on Settings > Power saving the
HeaderedControl carries IsEnabled, so its SettingsExpanders -- whose ActionButton
has no Description -- are born disabled. Settings > Data and storage has the same
disabled expanders but sets Description, so the part is realized and the target
resolves. Reported by crash telemetry on every version from 12.4.1 to 12.9.1.

Dropping the key frame is enough: DescriptionPresenter is already painted with
SystemControlDisabledChromeDisabledLowBrush, so the disabled appearance is
effectively unchanged.

Checked the rest of the app for the same shape: this is the only
Storyboard.TargetName in any template that points at an x:Load="False" element.
The VisualState.Setters that target deferred parts (RecentChoosers in the message
reaction templates) are left alone -- those resolve differently and do not throw,
as reactions demonstrate every day.

Not built: no UWP/.NET Native build environment here. The file was checked for XML
well-formedness.

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

#unigram
UnigramDev/Unigram/settingsbutton-disabledb852e7b1 files, +10/-5
Keep the disabled description dimming, realize the part instead

Dropping the key frame left the description on its own brush while the title
dimmed, so on pages that do set Description the two no longer matched. Restore
the key frame and take x:Load off DescriptionPresenter instead, so the target
always resolves.

The disabled appearance is now exactly what it was before this branch; the cost
is one collapsed, unmeasured ContentPresenter per SettingsButton. The other two
optional parts keep their deferral -- nothing animates them.

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

UnigramDev/Unigram/settingsbutton-disabled4b571511 files, +6/-4
Defer DescriptionPresenter with Lazy instead of dropping the deferral

x:DeferLoadStrategy="Lazy" realizes the element when something references it,
including a visual state, so the Disabled storyboard resolves without the part
being materialized up front. x:Load does not, which is what made the target
unresolvable. The stock TextBox template deferrs HeaderContentPresenter the same
way for the same reason.

Keeps the deferral the previous commit had given up, and the disabled appearance
is still what it was before this branch.

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

#unigram
UnigramDev/Unigram/develop4eda2af3 files, +26/-52
Probe VoipCaptureBase before get_self

VoipManager::Start cast the descriptor's capture straight to VoipVideoCapture
with get_self, which reinterprets the ABI pointer without checking it. The
capture is whichever one is active when the call turns ready, so sharing the
screen before that point handed a VoipScreenCapture to the cast and m_impl was
read at the wrong offset.

The other three call sites already probed with try_as; move that chain into
GetVideoCaptureImpl and use it everywhere.

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

UnigramDev/Unigram/developd2005801 files, +17/-5
Drop group call frames when the E2E delegates are missing

OnE2EEncryptDecrypt returned the input untouched when the delegate was null,
which puts plaintext on the wire while encrypting and feeds ciphertext to the
decoder as if it were plaintext while decrypting. The window is real: the
delegates are installed after the manager is constructed, and cleared again
before Stop.

tgcalls drops the frame when the transform returns empty, which is what the
managed side already does when TDLib fails, so return empty here too.

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

UnigramDev/Unigram/developfef656e1 files, +2/-5
Fix out-of-bounds write reading VoipDescriptor.PersistentState

The loop indexed into an empty vector. Nothing sets PersistentState today so it
never ran, but it would corrupt the heap the moment something did.

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

UnigramDev/Unigram/developd4f76122 files, +18/-1
Survive a loopback capture that fails to start

OnStopCapture dereferenced m_AudioClient unconditionally, but StopCaptureAsync
accepts the error state and a failed activation never produces a client, so
stopping after a failed start crashed. Process loopback is unavailable on older
builds, which is exactly when that happens.

The MFStartup was also left dangling on that path, since StopCaptureAsync is
what pairs it. Track it and shut down from the destructor if we never got there.

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

UnigramDev/Unigram/developc4bbb772 files, +19/-9
Hold CLoopbackCapture by reference, and only for screencast

It is a ref-counted RuntimeClass whose queued MF work items hold references of
their own, so storing it by value freed its storage out from under them. Hold a
ComPtr instead, and create it only on the screencast path that uses it, which
also drops the StopCaptureAsync every other call was making into a manager that
had never captured anything.

Skip the loopback when it fails to start rather than sharing the screen with an
audio capture that is not running.

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

#unigram
UnigramDev/Unigram/develop5356eba1 files, +8/-4
Clear the group call E2E delegates after Stop, not before

Both teardown paths cleared them first, leaving a window where tgcalls was still
live and could ask us to transform a frame with no delegate installed. They are
only cleared to break the native to managed cycle that would otherwise leak the
call, and that works just as well once the instance is gone.

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

UnigramDev/Unigram/develop668badd6 files, +101/-46
Join requests accessibility

UnigramDev/Unigram/develop30abc2e1 files, +57/-5
Improve toasts accessibility

UnigramDev/Unigram/developb469c6a1 files, +302/-0
Track the VoipManager review findings

Catches the doc up to the P0 fixes already committed; from here it moves in the
commit that closes each item.

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

UnigramDev/Unigram/develop806e4793 files, +35/-9
Stop re-registering the same incoming video sink

tgcalls appends sinks and only drops them once the weak_ptr expires, but the
page re-sets the output on every remote media state change where video is
active, always with the same sink. Each one appended another entry, so a single
frame was rendered once per registration.

Remember the last sink and skip a repeat. Weakly, so that dropping the sink
stays the way the output is detached.

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

UnigramDev/Unigram/developc9247d63 files, +23/-6
Fire each broadcast task completion exactly once

The deferral is handed to managed code, which can raise it more than once, and
nothing stopped the second call from re-entering tgcalls with a completion it
had already consumed. Latch _done after firing, in all three task impls.

OnMediaChannelDescriptionsRequested was the one doing it, deferring on the
null-participants path and then falling through — where it dereferenced the null
and took down an async void before it ever reached the second deferral.

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

UnigramDev/Unigram/develop2d25c442 files, +10/-11
Report an empty broadcast part as NotReady

An empty payload was reported as Success, leaving tgcalls to decode a zero-byte
part instead of asking again. Only a non-null, non-empty part is a success.

While here, drop the second copy of the payload: it was read out through the
IVector iterator and then memcpy'd into another vector.

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

#unigram
UnigramDev/Unigram/develop647b6682 files, +19/-12
Harden the descriptor conversion in VoipManager::Start

Read the encryption key with one GetMany instead of 256 GetAt calls through a
property fetched each time. It also clamps to what is there rather than throwing
hresult_out_of_bounds out of Start, and drops a copy via a stack array.

pushStun skips an empty host but pushTurn did not, so a server without IPv6
contributed a TURN entry pointing at nothing.

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

UnigramDev/Unigram/develop1e7c8952 files, +5/-11
Return the debug info without leaking it

The hand-rolled UTF-8 conversion leaked its buffer on every call and returned it
uninitialized if MultiByteToWideChar failed. The correct one-liner was already
there below it, unreachable — the source of the C4702 warning.

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

UnigramDev/Unigram/develop28996492 files, +36/-23
Guard three loose ends in VoipGroupManager

Screencast unmuted the instance directly, leaving m_isMuted stale so IsMuted
reported the opposite of the truth; route it through the setter. A screencast
with no audio to send stays muted, which is what it means.

The quality switch had no default, so an unrecognised value read an
uninitialised local. AddIncomingVideoOutput took get_self on the sink without
checking it, unlike its VoipManager counterpart.

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

UnigramDev/Unigram/developa1e3ccf2 files, +93/-73
Limits popup accessibility

UnigramDev/Unigram/develop6393fdb1 files, +1/-1
Fix rich messages translation

UnigramDev/Unigram/develop73473171 files, +3/-3
Fix polls/checklists translation

#unigram
UnigramDev/Unigram/developa9bef419 files, +452/-559
Rewrite the loopback capture as VoipLoopbackCapture

It was a near-verbatim copy of the Microsoft ApplicationLoopbackAudio sample and
the one piece of this project still on WRL and WIL. Now on C++/WinRT: implements
instead of RuntimeClass, which brings agility so FtmBase goes too, and com_ptr,
handle and slim_mutex in place of the wil types.

The METHODASYNCCALLBACK macro and its four offsetof-derived callbacks are gone.
Only the sample pump needs the MMCSS work queue, so start, stop and finish are
direct calls now and one IMFAsyncCallback is left. That also removes the
unbounded wait on m_hCaptureStopped: Stop takes the lock the sample callback
holds, so it waits for a packet read at most.

Three bugs in the packet loop came out with it. The copy length was taken from
GetNextPacketSize but used after GetBuffer, which may report fewer frames.
AUDCLNT_BUFFERFLAGS_SILENT was ignored, and a silent buffer's contents are
undefined, so silence went out as whatever was in memory. The samples handler
was called without checking it was set; it is a constructor argument now.

CLSID_YUV420Effect was being defined by whichever translation unit happened to
reach the initguid.h that LoopbackCapture.h dragged in. VoipVideoOutputSink.cpp
includes it deliberately now.

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

UnigramDev/Unigram/developea548fe1 files, +12/-5
Fix crash when searching while no tab is selected (#3325)

Keep SelectedTab in range instead of guarding its readers

Per review: nothing deselects a tab, so the question was where -1 comes from.

TopNavView.SelectedIndex is bound TwoWay, and a selector reports -1 whenever it
holds no selection — which it does while its ItemsSource is being attached.
BindableBase.Set stores the value and raises the notification before returning,
so the existing guard, which ran after Set and only checked the upper bound,
had already let -1 into the field. Tabs[value] on the next line was reading at
-1 too; the reported crash simply landed later, in Query, on the next
keystroke.

Validating before the value is committed keeps the field in range for every
reader — Query, ItemsView, IsTopChatsVisible and the setter itself — so the
guard added to Query in the first commit is no longer needed and is reverted.

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

#unigram
UnigramDev/Unigram/developa10164e2 files, +41/-13
Don't recreate the Direct3D device from the device-lost callback (#3333)

PlaceholderImageHelper watches its D3D device with RegisterDeviceRemovedEvent
and recreated the device straight from the threadpool wait callback. That runs
while the display driver is still resetting, and D3D11CreateDevice at that
moment faults inside the vendor user-mode driver (reported by crash telemetry
on 12.9, on two different Intel UMDs).

The device is now rebuilt lazily instead: PlaceholderHelper already calls
HandleDeviceLost() on every access to the singleton, so the next use recreates
it from the UI thread. HandleDeviceLost tolerates a null device, which is the
state left behind when creation failed.

Two related lifetime fixes in the same path:

- CreateDeviceResources released nothing before calling D3D11CreateDevice.
com_ptr::put() overwrites without releasing, so recreating over the removed
device stranded it and everything the driver held behind it.

- PlaceholderImageHelper::Close() called StopWatchingCurrentDevice(), which
closes the threadpool wait but does not wait for a callback that is already
running -- the callback then reached RaiseDeviceLostEvent on an object that
was being destroyed. The drain that already existed in ~DeviceLostHelper is
now a Shutdown() method that Close() calls, and it was dead code before
because Close() had already cleared the handle it checks.

Not built: no UWP/.NET Native build environment here.

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

UnigramDev/Unigram/develop8b5b9742 files, +50/-5
Log why the video swap chain failed, and don't crash when it does (#3334)

AsyncMediaPlayerSwapChain::Create() swallowed every failure and returned false,
so the only trace left was SwapChainOptions() throwing "You must wait for the
VideoView to be loaded before calling GetSwapChainOptions()" -- a message
inherited from LibVLCSharp that describes a lifecycle mistake this code cannot
make. The real cause is that one of the Direct3D calls failed, and nothing
recorded which one. That throw crossed a XAML Loaded handler (gallery video,
stories, the music player all construct AsyncMediaPlayer there) and became an
unhandled exception, reported by crash telemetry on 12.8.1 and 12.9.

Create() now names each stage as it goes and logs the stage, the HRESULT and
the message, so the next report says which driver call failed. The thrown
message says what actually happened.

The player no longer fails construction over it: the Direct3D device can
legitimately be unavailable while the display driver is resetting, and there is
nothing the caller could do about it. When the swap chain is not loaded the
player is built with --no-video instead, so audio still plays and the failure
is in the log rather than in a crash.

Also releases m_d3d11Device/m_deviceContext between adapter attempts --
com_ptr::put() overwrites without releasing, so a failed attempt could strand a
device.

Not built: no UWP/.NET Native build environment here.

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

#unigram
UnigramDev/Unigram/develop9b8ba301 files, +7/-1
Don't animate a deferred template part from SettingsButton's Disabled state (#3336)

* Defer DescriptionPresenter with Lazy instead of dropping the deferral

x:DeferLoadStrategy="Lazy" realizes the element when something references it,
including a visual state, so the Disabled storyboard resolves without the part
being materialized up front. x:Load does not, which is what made the target
unresolvable. The stock TextBox template deferrs HeaderContentPresenter the same
way for the same reason.

Keeps the deferral the previous commit had given up, and the disabled appearance
is still what it was before this branch.

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

UnigramDev/Unigram/developa04d5d41 files, +7/-0
Handle an instant view table with no rows (#3335)

* Handle an instant view table with no rows

ProcessTable computed its column count with table.Cells.Max(...), which throws
InvalidOperationException: Sequence contains no elements when the table has no
rows at all. It reached OnApplyTemplate through InstantContent.UpdateView, so
the whole message failed to render. Reported by crash telemetry on 12.9.1.

There is no grid to build for an empty table, so render just the caption --
which is what the method already falls back to, and callers of ProcessBlock all
handle a null return.

Not built: no UWP/.NET Native build environment here. Verified the file still
parses with Roslyn.

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

#unigram
Ajaxy/telegram-tt/master383d4323 files, +5/-5
12.0.38

Ajaxy/telegram-tt/masterca9857410 files, +144/-2
Status Picker: Add roll effect (#7115)

Ajaxy/telegram-tt/masterac049361 files, +1/-1
Poll: Fix sending quizes with no solution text (#7116)

Ajaxy/telegram-tt/masteraf03e8330 files, +1916/-463
GramJS: Protocol hardening (#7098)

Ajaxy/telegram-tt/master9a7b7401 files, +7/-1
Albums: Fix separators for albums (#7119)

Ajaxy/telegram-tt/mastera3faeb04 files, +239/-218
[Dev] Bump Vite (#7120)

Ajaxy/telegram-tt/master549ac261 files, +1/-1
Composer: Fix sending forward on Enter (#7117)

Ajaxy/telegram-tt/masterc8049f144 files, +1309/-618
Middle Header Panes: Redesign + Refactoring (#7096)

Co-authored-by: zubiden <19638254+zubiden@users.noreply.github.com>
Co-authored-by: Alexander Zinchuk <alx.zinchuk@gmail.com>

Ajaxy/telegram-tt/masteredaa9321 files, +10/-3
Radial Pattern Background: Fix color (#7113)

Ajaxy/telegram-tt/masterc06de3a22 files, +398/-374
Left Search: Redesign (#7111)

Ajaxy/telegram-tt/master69bb99050 files, +2236/-1028
Support server wallpapers (#7076)

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: zubiden <19638254+zubiden@users.noreply.github.com>
Co-authored-by: Dmitry Kabanov <153344039+dmitrykabanovdev@users.noreply.github.com>

Ajaxy/telegram-tt/master2305f821 files, +6/-1
Audio Player: Fix transition animation (#7123)

Ajaxy/telegram-tt/mastereaa99563 files, +24/-12
Forward Recipient Picker: Support quick forward to saved messages (#7122)

Ajaxy/telegram-tt/master0cc747111 files, +80/-177
Manage Group Members: Fix search data (#7121)

#webz
🫡2
Ajaxy/telegram-tt/master6786a057 files, +49/-7
Wallpapers: Follow up (#7125)

Ajaxy/telegram-tt/masterc770a3877 files, +1922/-1358
Settings: Redesign icons (#7124)

Ajaxy/telegram-tt/master9716ca22 files, +24/-11
UI: Fix blink on long first load (#7129)

Ajaxy/telegram-tt/master37f136424 files, +476/-301
Stickers: Migrate to TLottie (#7128)

Ajaxy/telegram-tt/master911c59c22 files, +221/-122
[chore] Avoid Record<string, unknown> (#7130)

Ajaxy/telegram-tt/master8ae788a3 files, +83/-123
Vite: Fix startup speed (#7131)

Ajaxy/telegram-tt/master1211dd57 files, +1326/-1791
Tauri: Bump dependencies (#7133)

Ajaxy/telegram-tt/master786f1ff8 files, +2/-87
Service Messages: Remove changelog (#7134)

Ajaxy/telegram-tt/masterc5a2bff1 files, +14/-6
Message List: Fix scroll restore (#7135)

Ajaxy/telegram-tt/masterd915b1b300 files, +2984/-2890
[Build]

#webz
🫡2
UnigramDev/Unigram/develop390bfc85 files, +44/-62
Take the manager locks off the event and callback paths

winrt::event synchronises itself, so m_lock only served to serialise callbacks
against subscribe and unsubscribe — at the price of a lock order inversion with
the managed side, which unsubscribes from inside its own lock while a tgcalls
thread holds this one inside managed code that takes that same lock. Nothing
closed the cycle today, but it was there to be closed.

VoipManager keeps no lock at all now. VoipGroupManager keeps one for the only
state that is really shared, the encrypt and decrypt delegates, and copies them
out rather than holding it across the call: the managed side blocks on a TDLib
round trip in there, and it was doing that while holding the same mutex as every
audio level update and every subscribe.

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

UnigramDev/Unigram/developd94416d5 files, +22/-3
Stop both managers from their destructor

Stop is what the managed side calls, but nothing guarantees every path gets
there, and destroying a manager without it skips tgcalls' own teardown and
leaves a screencast still capturing loopback audio. Stop is idempotent, so
calling it again from the destructor costs nothing when it already ran.

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

UnigramDev/Unigram/developd75db132 files, +5/-4
Use std::mutex in the loopback capture

winrt::slim_mutex is not deprecated, but every other mutex in Telegram.Native
and Telegram.Native.Calls is std::mutex and this one is uncontended anyway.

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

UnigramDev/Unigram/developb4767514 files, +57/-41
Cut the per-frame interop out of the group call callbacks

The E2E path rebuilt its result with a push_back loop over an IIterator, which
is two COM calls per byte on every frame of a conference call; one GetMany into
a pre-sized buffer does it instead. Audio levels appended to the IVector one
participant at a time, ten times a second for the length of the call, and are
now filled into a reserved vector and handed over whole.

Same treatment for ReceiveSignalingData. SetRequestedVideoChannels stops
building its vector when there is no instance to give it to, and Protocol's
comparator stops copying both strings on every comparison.

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

#unigram
UnigramDev/Unigram/develop278ca6e4 files, +61/-42
Clear the remaining hygiene items in the call managers

The tgcalls version registrations had internal linkage but lived in a header, so
a second includer would have registered every version twice; they move into
VoipManager.cpp. The #ifndef _WIN32 arm of the config initialiser could never
have compiled and is gone.

A screencast runs a second group manager alongside the main one and both opened
tgcalls_group.txt, interleaving into one file; the screencast writes its own now.

EmitJoinPayload no longer completes when there is no instance to emit from. The
empty payload it used to hand back only got the caller as far as a join the
server would reject, and it did it on the calling thread rather than the one the
success path answers on.

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

UnigramDev/Unigram/develop5fb96f25 files, +309/-1
Base service message for community add/remove

UnigramDev/Unigram/develop03513e42 files, +22/-4
Let a null incoming video sink reach tgcalls

Skipping out early on null was wrong twice. It left m_incomingVideoOutput
pointing at the sink the caller had just asked to detach, and it swallowed the
one thing a null is good for: setIncomingVideoOutput also assigns _currentSink,
which every newly negotiated video channel is handed on creation, so a channel
appearing after the detach re-attached the old sink.

Null flows through as an empty shared_ptr now. The dedupe check handles it
without a special case, since two nulls in a row compare equal.

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

UnigramDev/Unigram/develop16ee8b22 files, +26/-2
Set responseTimestamp on every broadcast part status

NotReady is precisely the status tgcalls reads responseTimestamp on, to decide
where to restart a stream that has not begun yet, and it was only ever set on
the Success path — so it arrived as zero. The caller had been supplying it all
along and we dropped it.

It is also the one timestamp in this API measured in seconds rather than
milliseconds; tgcalls multiplies it back up by 1000. We passed milliseconds
straight through, a thousand times too large. Converting at the tgcalls boundary
keeps the WinRT surface consistently milliseconds, which is what
requestCurrentTime already expects.

Found by re-reading the commit that started reporting empty parts as NotReady,
which is what made this reachable.

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

#unigram