UnigramDev/Unigram/crash-vlc-dangling-audio-device • 3bd0033 • 1 files, +9/-1
Fix crash when the default audio device changes during playback
OnDefaultAudioRenderDeviceChanged handed libvlc a pointer to a string that
had already been destroyed:
Set(libvlc_audio_output_set, winrt::to_string(args.Id()).c_str());
Set forwards its arguments into a lambda that Write queues for the player's
worker thread, and captures them by value — which for a const char* copies the
pointer, not the characters. The temporary std::string returned by to_string
dies at the end of that statement, so by the time the work item is dequeued
libvlc reads freed memory.
The crash lands in strcmp, called from module_exists by
libvlc_audio_output_set as it compares the device name against known module
names, which is exactly what reading a freed string looks like.
Own the string in the capture instead, matching the Write call sites that
already exist in this file.
This fires whenever the system's default audio render device changes while a
player is alive — headphones plugged in or removed, a Bluetooth device
connecting, a call starting — which is why it is common enough to matter
despite needing a device change to trigger. Reported by crash telemetry on
12.8.1; the code is unchanged on develop.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
Fix crash when the default audio device changes during playback
OnDefaultAudioRenderDeviceChanged handed libvlc a pointer to a string that
had already been destroyed:
Set(libvlc_audio_output_set, winrt::to_string(args.Id()).c_str());
Set forwards its arguments into a lambda that Write queues for the player's
worker thread, and captures them by value — which for a const char* copies the
pointer, not the characters. The temporary std::string returned by to_string
dies at the end of that statement, so by the time the work item is dequeued
libvlc reads freed memory.
The crash lands in strcmp, called from module_exists by
libvlc_audio_output_set as it compares the device name against known module
names, which is exactly what reading a freed string looks like.
Own the string in the capture instead, matching the Write call sites that
already exist in this file.
This fires whenever the system's default audio render device changes while a
player is alive — headphones plugged in or removed, a Bluetooth device
connecting, a call starting — which is why it is common enough to matter
despite needing a device change to trigger. Reported by crash telemetry on
12.8.1; the code is unchanged on develop.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
🫡3
UnigramDev/Unigram/develop • 6ea41c1 • 1 files, +9/-1
Fix crash when the default audio device changes during playback (#3321)
OnDefaultAudioRenderDeviceChanged handed libvlc a pointer to a string that
had already been destroyed:
Set(libvlc_audio_output_set, winrt::to_string(args.Id()).c_str());
Set forwards its arguments into a lambda that Write queues for the player's
worker thread, and captures them by value — which for a const char* copies the
pointer, not the characters. The temporary std::string returned by to_string
dies at the end of that statement, so by the time the work item is dequeued
libvlc reads freed memory.
The crash lands in strcmp, called from module_exists by
libvlc_audio_output_set as it compares the device name against known module
names, which is exactly what reading a freed string looks like.
Own the string in the capture instead, matching the Write call sites that
already exist in this file.
This fires whenever the system's default audio render device changes while a
player is alive — headphones plugged in or removed, a Bluetooth device
connecting, a call starting — which is why it is common enough to matter
despite needing a device change to trigger. Reported by crash telemetry on
12.8.1; the code is unchanged on develop.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
Fix crash when the default audio device changes during playback (#3321)
OnDefaultAudioRenderDeviceChanged handed libvlc a pointer to a string that
had already been destroyed:
Set(libvlc_audio_output_set, winrt::to_string(args.Id()).c_str());
Set forwards its arguments into a lambda that Write queues for the player's
worker thread, and captures them by value — which for a const char* copies the
pointer, not the characters. The temporary std::string returned by to_string
dies at the end of that statement, so by the time the work item is dequeued
libvlc reads freed memory.
The crash lands in strcmp, called from module_exists by
libvlc_audio_output_set as it compares the device name against known module
names, which is exactly what reading a freed string looks like.
Own the string in the capture instead, matching the Write call sites that
already exist in this file.
This fires whenever the system's default audio render device changes while a
player is alive — headphones plugged in or removed, a Bluetooth device
connecting, a call starting — which is why it is common enough to matter
despite needing a device change to trigger. Reported by crash telemetry on
12.8.1; the code is unchanged on develop.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
🫡2
UnigramDev/Unigram/develop • 7bb3a02 • 3 files, +390/-390
Update maximize icon
UnigramDev/Unigram/develop • a4f390f • 4 files, +140/-206
Untangle ImageHelper scaling and crop duplication
The "fit inside N" calculation was spelled out inline seven times. Replace it
with three named primitives — FitRatio, Fit and Clamp — so the difference
between a target and a ceiling is visible at the call site rather than implied
by a parameter named requestedMinSide that holds a maximum.
Drop both Scale overloads. They had no callers, and Scale(this Size, double)
was an exact signature match for SizeEx.Scale in Telegram.Controls, which
multiplies by a factor rather than fitting to a box. Which of the two a file
got depended only on the namespace it sat in.
CropAsync and CropAndPreviewAsync carried a copy each of the same 45 lines,
including the same TODO verbatim. Both now call ComputeCropTransform.
Fix the minimum-size branch, which took the smaller of the two ratios: that
brings the longer edge down to the floor and leaves the shorter one further
below it than it started, so a 100x400 crop with a minimum of 160 came back
40x160. No effect on the only live caller, whose crop is always square.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • 59b9050 • 3 files, +34/-17
Fix HEVC 10-bit video hanging VideoAnimation
decode_frame gated on a whitelist of pixel formats before the swscale path was
considered, so yuv420p10le was rejected outright and every frame returned -1.
The whitelist describes what libyuv can convert unaided, not what is supported;
it now gates only that fast path, and anything else goes through the scaler.
The alignment test likewise only chooses swscale in preference to libyuv, so it
applies only when a libyuv path exists. Without one the scaler is the only way
to convert the frame, and the scaling path already handles an unaligned
destination through an intermediate buffer.
RenderSync then spun. has_decoded_frames is set as soon as a frame is received,
before conversion, which freezes the try budget; the read-EOF branch does not
reset it. With conversion failing on every frame the loop decoded the file end
to end, seeked back to zero and repeated with no exit condition. A failed
conversion now spends a try, which bounds the loop regardless of the format.
RenderSync returns Boolean rather than Int32. It only ever returned 1 or 0, and
both internal callers already treated the result as a boolean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
Update maximize icon
UnigramDev/Unigram/develop • a4f390f • 4 files, +140/-206
Untangle ImageHelper scaling and crop duplication
The "fit inside N" calculation was spelled out inline seven times. Replace it
with three named primitives — FitRatio, Fit and Clamp — so the difference
between a target and a ceiling is visible at the call site rather than implied
by a parameter named requestedMinSide that holds a maximum.
Drop both Scale overloads. They had no callers, and Scale(this Size, double)
was an exact signature match for SizeEx.Scale in Telegram.Controls, which
multiplies by a factor rather than fitting to a box. Which of the two a file
got depended only on the namespace it sat in.
CropAsync and CropAndPreviewAsync carried a copy each of the same 45 lines,
including the same TODO verbatim. Both now call ComputeCropTransform.
Fix the minimum-size branch, which took the smaller of the two ratios: that
brings the longer edge down to the floor and leaves the shorter one further
below it than it started, so a 100x400 crop with a minimum of 160 came back
40x160. No effect on the only live caller, whose crop is always square.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • 59b9050 • 3 files, +34/-17
Fix HEVC 10-bit video hanging VideoAnimation
decode_frame gated on a whitelist of pixel formats before the swscale path was
considered, so yuv420p10le was rejected outright and every frame returned -1.
The whitelist describes what libyuv can convert unaided, not what is supported;
it now gates only that fast path, and anything else goes through the scaler.
The alignment test likewise only chooses swscale in preference to libyuv, so it
applies only when a libyuv path exists. Without one the scaler is the only way
to convert the frame, and the scaling path already handles an unaligned
destination through an intermediate buffer.
RenderSync then spun. has_decoded_frames is set as soon as a frame is received,
before conversion, which freezes the try budget; the read-EOF branch does not
reset it. With conversion failing on every frame the loop decoded the file end
to end, seeked back to zero and repeated with no exit condition. A failed
conversion now spends a try, which bounds the loop regardless of the format.
RenderSync returns Boolean rather than Int32. It only ever returned 1 or 0, and
both internal callers already treated the result as a boolean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
🫡2
UnigramDev/Unigram/develop • 27022ab • 1 files, +25/-6
Propagate video render failures out of ImageHelper
RenderSync answers whether it wrote a frame, and three call sites discarded it,
passing a BufferSurface that still held uninitialised memory to Encode or to a
WriteableBitmap.
Each needs a different answer. GetPreviewBitmapAsync returns null, which is
already its contract and which every caller substitutes a placeholder for.
CropAndPreviewAsync throws, because StorageMedia.Refresh recovers by falling
back to the unedited preview and does so from a catch, which a null would slip
past. OpenReadAsync throws, because callers pass the result straight to
BitmapDecoder and there is no null that does not simply move the failure; both
GenerationService and ImageCropper.SetSourceAsync catch it.
The blanket catch in GetPreviewBitmapAsync now logs. A codec the decoder cannot
handle was indistinguishable from a file with no frames.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • d4ac632 • 4 files, +208/-86
Remove per-frame allocations from message layout
Four allocations on paths that run for every message on every layout pass.
ReactionsPanel.ArrangeOverride built a List of rows and a List of rects per row
purely to centre them. A row is a contiguous run of children, complete as soon
as the next child does not fit, at which point its right edge is known, so each
run is now arranged in place. Both overrides also index Children rather than
enumerating it, since foreach over UIElementCollection allocates an
IEnumerator<UIElement> per pass.
LoadingTextBlock rebuilt its skeleton clip on every arrange: a native text
measurement, one D2D geometry per line, a group, a path, a path geometry and a
clip. It is now cached on the inputs it derives from.
ReplyMarkupInlinePanel rebuilt the rect buffers it hands to the native clip
builder, plus the clip itself, on every arrange. The buffers are reused and the
rects compared in place, so the clip is rebuilt only when one moves.
Discern gains an IList<UnreadReaction> overload over a shared core, so
ReactionsPanel no longer projects through Select on a list that is usually
empty.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • a2c8668 • 5 files, +28/-7
Release chart geometries built inside the draw pass
DrawChart and DrawPickerChart called CanvasGeometry.CreatePath once per line on
every frame and never disposed the result, leaving the native D2D geometry to
the finalizer. Seven sites now go through DrawGeometry and FillGeometry on
BaseChartView, which wrap the geometry in a using. The drawing session consumes
it before the call returns, so nothing outlives the scope.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
Propagate video render failures out of ImageHelper
RenderSync answers whether it wrote a frame, and three call sites discarded it,
passing a BufferSurface that still held uninitialised memory to Encode or to a
WriteableBitmap.
Each needs a different answer. GetPreviewBitmapAsync returns null, which is
already its contract and which every caller substitutes a placeholder for.
CropAndPreviewAsync throws, because StorageMedia.Refresh recovers by falling
back to the unedited preview and does so from a catch, which a null would slip
past. OpenReadAsync throws, because callers pass the result straight to
BitmapDecoder and there is no null that does not simply move the failure; both
GenerationService and ImageCropper.SetSourceAsync catch it.
The blanket catch in GetPreviewBitmapAsync now logs. A codec the decoder cannot
handle was indistinguishable from a file with no frames.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • d4ac632 • 4 files, +208/-86
Remove per-frame allocations from message layout
Four allocations on paths that run for every message on every layout pass.
ReactionsPanel.ArrangeOverride built a List of rows and a List of rects per row
purely to centre them. A row is a contiguous run of children, complete as soon
as the next child does not fit, at which point its right edge is known, so each
run is now arranged in place. Both overrides also index Children rather than
enumerating it, since foreach over UIElementCollection allocates an
IEnumerator<UIElement> per pass.
LoadingTextBlock rebuilt its skeleton clip on every arrange: a native text
measurement, one D2D geometry per line, a group, a path, a path geometry and a
clip. It is now cached on the inputs it derives from.
ReplyMarkupInlinePanel rebuilt the rect buffers it hands to the native clip
builder, plus the clip itself, on every arrange. The buffers are reused and the
rects compared in place, so the clip is rebuilt only when one moves.
Discern gains an IList<UnreadReaction> overload over a shared core, so
ReactionsPanel no longer projects through Select on a list that is usually
empty.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • a2c8668 • 5 files, +28/-7
Release chart geometries built inside the draw pass
DrawChart and DrawPickerChart called CanvasGeometry.CreatePath once per line on
every frame and never disposed the result, leaving the native D2D geometry to
the finalizer. Seven sites now go through DrawGeometry and FillGeometry on
BaseChartView, which wrap the geometry in a using. The drawing session consumes
it before the call returns, so nothing outlives the scope.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
🫡2
UnigramDev/Unigram/develop • ba4ca6b • 1 files, +54/-18
Fix resource ownership and leaks in DrawStrokes
DrawStrokes is public, took a CanvasBitmap as a parameter and disposed it, so
any caller outside this file lost the bitmap it passed in. The caller now owns
both the bitmap it supplies and the render target it receives, and the summary
says so.
Nothing was scoped, so an exception between the two CanvasRenderTarget
constructions and the final dispose leaked all three objects. The body moves to
a private overload, leaving the public one to own the targets: the overlay is a
using and the returned target is disposed on the way out of a catch.
DrawStrokesAsync(SoftwareBitmap) leaked the InMemoryRandomAccessStream it
returns when SaveAsync threw. DrawStrokesAsync(StorageFile) relied on
DrawStrokes disposing the bitmap before the file was reopened for writing,
since it is loaded from that same path; the release is now explicit rather than
a side effect, and scoped to keep that order.
canvas1 and canvas2 become overlay and target.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • 532b8cf • 1 files, +20/-12
Drop dead parameters and stale docs from ImageHelper
CropAsync took a quality parameter that nothing read: the property set it was
written for is commented out. No caller passed it, and none passes more than
five positional arguments, so removing it does not disturb them.
CropAsync also took trimStart and never forwarded it to OpenReadAsync, so a
trimmed video would have been cropped at position zero. ScaleAsync already
forwards its own; this one now does too. No caller passes it yet, so nothing
changes today.
The doc block above ScaleAsync described a quality parameter the method has
never had, and omitted the ones it does. Both commented-out property sets
referred to that parameter by name.
RotateArea computed width - (width - area.X), which is area.X, through an
intermediate Point.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • bfb6d0d • 1 files, +3/-1
Accept every video container in OpenReadAsync
The frame extraction path tested for the literal ".mp4", so .mov and .m4v took
the image branch and failed inside BitmapDecoder. Constants.VideoTypes is the
set EditMediaPopup already uses to decide whether a file is a video, and the two
disagreed.
Uses the existing HasExtension helper, which compares ordinal and
case-insensitive against the array without copying it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
Fix resource ownership and leaks in DrawStrokes
DrawStrokes is public, took a CanvasBitmap as a parameter and disposed it, so
any caller outside this file lost the bitmap it passed in. The caller now owns
both the bitmap it supplies and the render target it receives, and the summary
says so.
Nothing was scoped, so an exception between the two CanvasRenderTarget
constructions and the final dispose leaked all three objects. The body moves to
a private overload, leaving the public one to own the targets: the overlay is a
using and the returned target is disposed on the way out of a catch.
DrawStrokesAsync(SoftwareBitmap) leaked the InMemoryRandomAccessStream it
returns when SaveAsync threw. DrawStrokesAsync(StorageFile) relied on
DrawStrokes disposing the bitmap before the file was reopened for writing,
since it is loaded from that same path; the release is now explicit rather than
a side effect, and scoped to keep that order.
canvas1 and canvas2 become overlay and target.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • 532b8cf • 1 files, +20/-12
Drop dead parameters and stale docs from ImageHelper
CropAsync took a quality parameter that nothing read: the property set it was
written for is commented out. No caller passed it, and none passes more than
five positional arguments, so removing it does not disturb them.
CropAsync also took trimStart and never forwarded it to OpenReadAsync, so a
trimmed video would have been cropped at position zero. ScaleAsync already
forwards its own; this one now does too. No caller passes it yet, so nothing
changes today.
The doc block above ScaleAsync described a quality parameter the method has
never had, and omitted the ones it does. Both commented-out property sets
referred to that parameter by name.
RotateArea computed width - (width - area.X), which is area.X, through an
intermediate Point.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • bfb6d0d • 1 files, +3/-1
Accept every video container in OpenReadAsync
The frame extraction path tested for the literal ".mp4", so .mov and .m4v took
the image branch and failed inside BitmapDecoder. Constants.VideoTypes is the
set EditMediaPopup already uses to decide whether a file is a video, and the two
disagreed.
Uses the existing HasExtension helper, which compares ordinal and
case-insensitive against the array without copying it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
🫡2
UnigramDev/Unigram/develop • a7f0db0 • 1 files, +34/-26
Honour requestedMinSide for every format in GetPreviewBitmapAsync
The method asked the decoder for its own BitmapPixelFormat, which
SoftwareBitmapSource accepts only when it happens to be Bgra8. The guard was
therefore testing the input rather than the output, and everything else fell
through to a BitmapImage decoded at full size, dropping requestedMinSide on the
path where it matters most.
Requesting Bgra8 premultiplied directly lets the decoder convert, which removes
the reason for the branch. One path, always the format SoftwareBitmapSource
needs, always bounded.
SoftwareBitmapSource holds a single frame, so an animated GIF would come back
static. Nothing reaches this with a GIF today, since StorageMedia previews
photos through a BitmapImage and only calls the overload above for video; the
summary records what a future caller would need.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • 8700dde • 9 files, +27/-36
Update nuget packages
UnigramDev/Unigram/develop • ec916c0 • 3 files, +3/-1
Update round font
UnigramDev/Unigram/develop • 3be5e09 • 2 files, +3/-3
Fix ephemeral messages icon
UnigramDev/Unigram/develop • af506e6 • 2 files, +4/-4
Update property name
UnigramDev/Unigram/develop • e8560fb • 1 files, +1/-1
Update MicroTeX
UnigramDev/Unigram/develop • ce8637e • 5 files, +72/-7
New unsupported message template
#unigram
Honour requestedMinSide for every format in GetPreviewBitmapAsync
The method asked the decoder for its own BitmapPixelFormat, which
SoftwareBitmapSource accepts only when it happens to be Bgra8. The guard was
therefore testing the input rather than the output, and everything else fell
through to a BitmapImage decoded at full size, dropping requestedMinSide on the
path where it matters most.
Requesting Bgra8 premultiplied directly lets the decoder convert, which removes
the reason for the branch. One path, always the format SoftwareBitmapSource
needs, always bounded.
SoftwareBitmapSource holds a single frame, so an animated GIF would come back
static. Nothing reaches this with a GIF today, since StorageMedia previews
photos through a BitmapImage and only calls the overload above for video; the
summary records what a future caller would need.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • 8700dde • 9 files, +27/-36
Update nuget packages
UnigramDev/Unigram/develop • ec916c0 • 3 files, +3/-1
Update round font
UnigramDev/Unigram/develop • 3be5e09 • 2 files, +3/-3
Fix ephemeral messages icon
UnigramDev/Unigram/develop • af506e6 • 2 files, +4/-4
Update property name
UnigramDev/Unigram/develop • e8560fb • 1 files, +1/-1
Update MicroTeX
UnigramDev/Unigram/develop • ce8637e • 5 files, +72/-7
New unsupported message template
#unigram
🫡2
UnigramDev/Unigram/develop • c883cf7 • 1 files, +72/-3
Queue call transitions instead of blocking on the system call
TransitionToRequestingOrRinging blocked on InitializeSystemCallAsync, which
awaits the WinRT call reservation. VoipCoordinator.Handle calls Update from
inside its _activeLock on the TdReceive thread, and the VoipCall constructor
reaches the same transition, so the lock was held for the whole reservation —
including RequestNewIncomingCall and its 120 second ringing timeout. The UI
thread takes that lock through VoipCoordinator.ActiveCall, read from MainPage
and from the CallAccept and CallReject shortcuts, so it froze while a call was
being set up.
Awaiting alone would have introduced a worse problem. The block was also
ordering: a discard arriving while the reservation was in flight would reach
DisposeImpl with _systemCall still null, so nothing released it, and the
reservation completing afterwards would leave Windows showing an incoming call
that could no longer be ended.
Update now enqueues and returns, and a single pump applies transitions one at a
time in the order TDLib reported them. That keeps the ordering the block
provided while holding _activeLock only until the first await. The failure
handling matters: the catch sits inside the drain loop so one bad transition
cannot strand the rest, and _transitioning is cleared on the only exit, since a
call that stops draining would never reach Discarded.
UpdateImpl is otherwise the previous body unchanged, including its unlocked
read of _state, which races with OnStateUpdated and is left for its own change.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • 2180a5e • 1 files, +68/-19
Reserve the group call system call outside the constructor
All three VoipGroupCall constructors blocked on InitializeSystemCallAsync, and
VoipCoordinator runs each of them inside its _activeLock. The UI thread takes
that lock through VoipCoordinator.ActiveCall, read from MainPage and from
ChatGroupCallHeader, so it froze for the length of the reservation. The
ThreadPool.QueueUserWorkItem already wrapping those constructions kept the block
off the UI thread but did nothing about the lock.
Constructors cannot await, so the tail of each one moves into JoinAsync or
JoinConferenceAsync, started by the constructor and running after it returns.
The lock is then held only for field initialization. Ordering within the tail is
unchanged: reserve, create the window, then rejoin.
Leaving before the reservation completes needs handling that blocking made
unnecessary. Dispose would run with _systemCall still null, release nothing, and
the reservation completing afterwards would leave Windows holding a call that
can no longer be ended. Both paths check _isClosed once they have it and release
it themselves. _isClosed becomes volatile, since it is now read after an await
on a different thread from the one that sets it.
Dispose's system call teardown is extracted to DisposeSystemCall so the normal
and abandoned paths release it identically.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
Queue call transitions instead of blocking on the system call
TransitionToRequestingOrRinging blocked on InitializeSystemCallAsync, which
awaits the WinRT call reservation. VoipCoordinator.Handle calls Update from
inside its _activeLock on the TdReceive thread, and the VoipCall constructor
reaches the same transition, so the lock was held for the whole reservation —
including RequestNewIncomingCall and its 120 second ringing timeout. The UI
thread takes that lock through VoipCoordinator.ActiveCall, read from MainPage
and from the CallAccept and CallReject shortcuts, so it froze while a call was
being set up.
Awaiting alone would have introduced a worse problem. The block was also
ordering: a discard arriving while the reservation was in flight would reach
DisposeImpl with _systemCall still null, so nothing released it, and the
reservation completing afterwards would leave Windows showing an incoming call
that could no longer be ended.
Update now enqueues and returns, and a single pump applies transitions one at a
time in the order TDLib reported them. That keeps the ordering the block
provided while holding _activeLock only until the first await. The failure
handling matters: the catch sits inside the drain loop so one bad transition
cannot strand the rest, and _transitioning is cleared on the only exit, since a
call that stops draining would never reach Discarded.
UpdateImpl is otherwise the previous body unchanged, including its unlocked
read of _state, which races with OnStateUpdated and is left for its own change.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • 2180a5e • 1 files, +68/-19
Reserve the group call system call outside the constructor
All three VoipGroupCall constructors blocked on InitializeSystemCallAsync, and
VoipCoordinator runs each of them inside its _activeLock. The UI thread takes
that lock through VoipCoordinator.ActiveCall, read from MainPage and from
ChatGroupCallHeader, so it froze for the length of the reservation. The
ThreadPool.QueueUserWorkItem already wrapping those constructions kept the block
off the UI thread but did nothing about the lock.
Constructors cannot await, so the tail of each one moves into JoinAsync or
JoinConferenceAsync, started by the constructor and running after it returns.
The lock is then held only for field initialization. Ordering within the tail is
unchanged: reserve, create the window, then rejoin.
Leaving before the reservation completes needs handling that blocking made
unnecessary. Dispose would run with _systemCall still null, release nothing, and
the reservation completing afterwards would leave Windows holding a call that
can no longer be ended. Both paths check _isClosed once they have it and release
it themselves. _isClosed becomes volatile, since it is now read after an await
on a different thread from the one that sets it.
Dispose's system call teardown is extracted to DisposeSystemCall so the normal
and abandoned paths release it identically.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
🫡2
UnigramDev/Unigram/develop • 5d1c657 • 1 files, +20/-5
Notify the system call of the mute state on the conference path
Both conference constructors called TryNotifyMutedChanged before reserving the
system call, when _coordinator is still null, so the null-conditional swallowed
it and the initial mute state was never handed over. The video chat constructor
called it after, where it works.
Both now go through NotifyMutedChanged after the reservation. That reads the
manager into a local first: it runs after an await, and Dispose clears the field
from whichever thread ends the call.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • 196c8df • 1 files, +1/-1
Fix leak on voip coordinator exception
UnigramDev/Unigram/develop • e8922b2 • 1 files, +152/-0
Add crash-triage skill
Encodes the workflow for turning a production crash from the dashboard into a
fix: rank groups by affected users, symbolicate with crashctl, map the
crashing version back to its source, diagnose, and open a PR against develop.
The version mapping is the part worth writing down. Crash line numbers refer
to the released build, so reading them against a working tree silently points
at the wrong code — on the crash checked while writing this, the reported line
2341 lands exactly on the reporting function at the 12.8.1 bump commit, and is
past the end of the file in the current tree. The skill resolves the version
via its tag, falls back to the "Bump version to X" commit when the tag was
never pushed (v12.8.1 is one such case), and then requires verifying that the
reported line actually falls inside the reported function before any code is
read for diagnosis.
Work happens in a local clone, never in the user's working tree. The skill
also records the limits: never close a crash group (only a shipped release
proves a fix), never push to develop/main, and treat crash records as user
data.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • cb35007 • 8 files, +122/-380
Extract the duplicated skeleton placeholder builder
The same shimmer placeholder was written out seven times, in ReactionButton,
InteractionsView, SearchPostsTabPage, ChatView, GiftCraftChoosePopup,
ResoldGiftsPopup and StoryInteractionsPopup. Each built a list of rounded rects,
grouped them into a path geometry, and then repeated an identical gradient,
sprite shapes, clip and sweep animation, differing only in the rectangles.
VisualUtilities.SetSkeleton takes the element, the sweep extent and the shapes.
Two things fell out of merging them: every copy kept separate foreground and
background colours that were always assigned the same value, now one shimmer;
and StoryInteractionsPopup was the only one not consulting the theme for
MenuFlyoutItemBackgroundPointerOver, so it now matches the other six rather than
always using the six percent white fallback.
Shapes are params, since two call sites pass a fixed pair and the rest pass an
array that is forwarded without copying. That makes a zero-shape call
expressible, which is worth guarding anyway: the loop-driven callers already
reach it whenever their row count is zero, and an empty group clips everything
away, attaching a skeleton that renders as nothing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
Notify the system call of the mute state on the conference path
Both conference constructors called TryNotifyMutedChanged before reserving the
system call, when _coordinator is still null, so the null-conditional swallowed
it and the initial mute state was never handed over. The video chat constructor
called it after, where it works.
Both now go through NotifyMutedChanged after the reservation. That reads the
manager into a local first: it runs after an await, and Dispose clears the field
from whichever thread ends the call.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • 196c8df • 1 files, +1/-1
Fix leak on voip coordinator exception
UnigramDev/Unigram/develop • e8922b2 • 1 files, +152/-0
Add crash-triage skill
Encodes the workflow for turning a production crash from the dashboard into a
fix: rank groups by affected users, symbolicate with crashctl, map the
crashing version back to its source, diagnose, and open a PR against develop.
The version mapping is the part worth writing down. Crash line numbers refer
to the released build, so reading them against a working tree silently points
at the wrong code — on the crash checked while writing this, the reported line
2341 lands exactly on the reporting function at the 12.8.1 bump commit, and is
past the end of the file in the current tree. The skill resolves the version
via its tag, falls back to the "Bump version to X" commit when the tag was
never pushed (v12.8.1 is one such case), and then requires verifying that the
reported line actually falls inside the reported function before any code is
read for diagnosis.
Work happens in a local clone, never in the user's working tree. The skill
also records the limits: never close a crash group (only a shipped release
proves a fix), never push to develop/main, and treat crash records as user
data.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • cb35007 • 8 files, +122/-380
Extract the duplicated skeleton placeholder builder
The same shimmer placeholder was written out seven times, in ReactionButton,
InteractionsView, SearchPostsTabPage, ChatView, GiftCraftChoosePopup,
ResoldGiftsPopup and StoryInteractionsPopup. Each built a list of rounded rects,
grouped them into a path geometry, and then repeated an identical gradient,
sprite shapes, clip and sweep animation, differing only in the rectangles.
VisualUtilities.SetSkeleton takes the element, the sweep extent and the shapes.
Two things fell out of merging them: every copy kept separate foreground and
background colours that were always assigned the same value, now one shimmer;
and StoryInteractionsPopup was the only one not consulting the theme for
MenuFlyoutItemBackgroundPointerOver, so it now matches the other six rather than
always using the six percent white fallback.
Shapes are params, since two call sites pass a fixed pair and the rest pass an
array that is forwarded without copying. That makes a zero-shape call
expressible, which is worth guarding anyway: the loop-driven callers already
reach it whenever their row count is zero, and an empty group clips everything
away, attaching a skeleton that renders as nothing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
🫡2
UnigramDev/Unigram/develop • 5082051 • 1 files, +39/-4
crash-triage: make the app log tail a first-class diagnosis step
The log tail is the most useful part of a crash record and was only mentioned
as a flag. The stack says where the app died; the log tail says what it was
doing, which is usually what explains the crash.
Documents both halves: the state header (memory used/available, window size,
scaling, active calls, raw HRESULT) which is worth checking against the crash's
device spread, and the timestamped [File.cs:line][Method] trace, which carries
its own source locations and so corroborates the symbolicated stack
independently.
Diagnosis now requires reconciling stack, log tail and source at the reference
commit, rather than reasoning from the stack alone. Also notes that app logging
is currently sparse, so a missing log line is a legitimate follow-up to raise
in the PR.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • d36dec9 • 2 files, +48/-0
Handle clicks in gallery
UnigramDev/Unigram/develop • cfff648 • 11 files, +305/-23
Extend the orphan analysis to the gallery and mini apps
Analyze reports whatever is registered but unreachable from the roots it is
given, so it only works as a single call over the union of every area's roots:
running it per area marks every other area's live objects as leaked. ChatView
therefore stops calling it and exposes DebugRoots instead, MainPage assembles
the union, and each area contributes roots and a descent that return nothing for
types they do not own.
The gallery registers what a leak there actually costs: the window, its content
and transport controls, the compact overlay that outlives it on purpose, and the
player base, whose decoder and surfaces are the reason any of this matters. The
window stops being a root once nothing legitimately holds it, so anything still
referencing it past that point is the leak rather than the owner.
WebAppPage joins the same analysis, and its event subscriptions move with it.
Both constructors subscribed Window.Current.Activated, CloseRequested,
Consolidated and VisibleBoundsChanged, and neither unsubscribed, so the page
stayed reachable from the view for as long as the view lived. They now hook and
unhook in the UserControlEx OnLoaded and OnUnloaded pair, which is guaranteed to
alternate; the raw Loaded and Unloaded events fire again on every reparenting,
and CloseRequested and Consolidated take deferrals, so a second handler would
mean a second confirmation dialog rather than merely wasted work.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • b239b87 • 15 files, +154/-159
Code cleanup
UnigramDev/Unigram/develop • eb9411e • 7 files, +264/-15979
Improve rich editor
UnigramDev/Unigram/develop • bf24e59 • 2 files, +129/-13
Add buttons support in rich messages
#unigram
crash-triage: make the app log tail a first-class diagnosis step
The log tail is the most useful part of a crash record and was only mentioned
as a flag. The stack says where the app died; the log tail says what it was
doing, which is usually what explains the crash.
Documents both halves: the state header (memory used/available, window size,
scaling, active calls, raw HRESULT) which is worth checking against the crash's
device spread, and the timestamped [File.cs:line][Method] trace, which carries
its own source locations and so corroborates the symbolicated stack
independently.
Diagnosis now requires reconciling stack, log tail and source at the reference
commit, rather than reasoning from the stack alone. Also notes that app logging
is currently sparse, so a missing log line is a legitimate follow-up to raise
in the PR.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • d36dec9 • 2 files, +48/-0
Handle clicks in gallery
UnigramDev/Unigram/develop • cfff648 • 11 files, +305/-23
Extend the orphan analysis to the gallery and mini apps
Analyze reports whatever is registered but unreachable from the roots it is
given, so it only works as a single call over the union of every area's roots:
running it per area marks every other area's live objects as leaked. ChatView
therefore stops calling it and exposes DebugRoots instead, MainPage assembles
the union, and each area contributes roots and a descent that return nothing for
types they do not own.
The gallery registers what a leak there actually costs: the window, its content
and transport controls, the compact overlay that outlives it on purpose, and the
player base, whose decoder and surfaces are the reason any of this matters. The
window stops being a root once nothing legitimately holds it, so anything still
referencing it past that point is the leak rather than the owner.
WebAppPage joins the same analysis, and its event subscriptions move with it.
Both constructors subscribed Window.Current.Activated, CloseRequested,
Consolidated and VisibleBoundsChanged, and neither unsubscribed, so the page
stayed reachable from the view for as long as the view lived. They now hook and
unhook in the UserControlEx OnLoaded and OnUnloaded pair, which is guaranteed to
alternate; the raw Loaded and Unloaded events fire again on every reparenting,
and CloseRequested and Consolidated take deferrals, so a second handler would
mean a second confirmation dialog rather than merely wasted work.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • b239b87 • 15 files, +154/-159
Code cleanup
UnigramDev/Unigram/develop • eb9411e • 7 files, +264/-15979
Improve rich editor
UnigramDev/Unigram/develop • bf24e59 • 2 files, +129/-13
Add buttons support in rich messages
#unigram
🫡2
UnigramDev/Unigram/develop • a7d3657 • 1 files, +8/-4
Improve quote style
UnigramDev/Unigram/develop • e068bd3 • 1 files, +22/-5
crash-triage: correct the PR base and branch naming
Two things went wrong on the first real run of this skill.
The PR must be based on GitHub's develop, not on origin/develop inside a
clone of the user's repo. That ref is their remote-tracking branch: it was
both stale and carrying unpushed local commits, so it had diverged from the
branch the PR actually merges into, and the file being patched differed
between the two. Add the GitHub remote explicitly and branch from there.
Branches cannot be prefixed with fix/, because a branch literally named "fix"
exists on the remote and git rejects the push with "directory file conflict".
Also requires stating whether the change was built and run. A UWP/.NET Native
build is usually unavailable here, and a reviewer should not have to guess.
Roslyn parsing is offered as the weaker check it is, with its limits spelled
out.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • 79e90af • 1 files, +20/-7
crash-triage: never publish crash impact numbers
The repository is public, so affected-user counts, device counts and per-OS
breakdowns are private telemetry being published to the world. The PR
template asked for exactly that, and the first real run put "46 crashes / 32
users" into both a PR body and a pushed commit message.
Impact figures are now explicitly for choosing what to work on, never for
justifying it in public. PRs say "reported by crash telemetry on <version>"
and nothing more; group hashes and device breakdowns stay out too.
Calls out that the commit message is as public as the PR body and must be
checked before pushing, since scrubbing it afterwards needs a force-push.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • 6a24748 • 1 files, +12/-3
crash-triage: generate public text with crashctl --public
Stripping impact figures by hand already failed once, so point the skill at
the mechanical path instead: crashctl show --public emits the exception, the
frames and the missing-symbol list with impact figures, device/OS breakdown,
group hash and log tail withheld.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
Improve quote style
UnigramDev/Unigram/develop • e068bd3 • 1 files, +22/-5
crash-triage: correct the PR base and branch naming
Two things went wrong on the first real run of this skill.
The PR must be based on GitHub's develop, not on origin/develop inside a
clone of the user's repo. That ref is their remote-tracking branch: it was
both stale and carrying unpushed local commits, so it had diverged from the
branch the PR actually merges into, and the file being patched differed
between the two. Add the GitHub remote explicitly and branch from there.
Branches cannot be prefixed with fix/, because a branch literally named "fix"
exists on the remote and git rejects the push with "directory file conflict".
Also requires stating whether the change was built and run. A UWP/.NET Native
build is usually unavailable here, and a reviewer should not have to guess.
Roslyn parsing is offered as the weaker check it is, with its limits spelled
out.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • 79e90af • 1 files, +20/-7
crash-triage: never publish crash impact numbers
The repository is public, so affected-user counts, device counts and per-OS
breakdowns are private telemetry being published to the world. The PR
template asked for exactly that, and the first real run put "46 crashes / 32
users" into both a PR body and a pushed commit message.
Impact figures are now explicitly for choosing what to work on, never for
justifying it in public. PRs say "reported by crash telemetry on <version>"
and nothing more; group hashes and device breakdowns stay out too.
Calls out that the commit message is as public as the PR body and must be
checked before pushing, since scrubbing it afterwards needs a force-push.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • 6a24748 • 1 files, +12/-3
crash-triage: generate public text with crashctl --public
Stripping impact figures by hand already failed once, so point the skill at
the mechanical path instead: crashctl show --public emits the exception, the
frames and the missing-symbol list with impact figures, device/OS breakdown,
group hash and log tail withheld.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
🫡2
UnigramDev/Unigram/develop • 73fcda9 • 1 files, +26/-4
crash-triage: close the crash group when the PR merges
Merging is what marks a crash fixed, so the loop now ends by closing the
group rather than leaving it to be done by hand.
Because the group hash cannot appear in the PR, the pr-number -> group-hash
mapping has to be recorded locally when the PR is opened, or the link is lost
as soon as the PR is the only artefact left.
Also records the PR-watching gotcha found this session: gh posts comments
under the user's own account, so a watcher cannot use author to tell the
agent's comments from the user's, and unfiltered it reads its own words back
as instructions. Agent comments carry an HTML-comment marker and the watcher
skips them.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • 4caca6d • 1 files, +17/-0
crash-triage: verify the mechanism instead of inferring it
A rejected PR's worth of lesson. The autocomplete crash was diagnosed as a
recycled container carrying the previous item type's template, a story that
fitted the stack and was never checked. The actual cause was
AutocompleteTemplateSelector returning CommandTemplate for both UserCommand
and QuickReplyShortcut, so the tree never matched what the code-behind read —
one file would have shown it, and it was never opened.
Requires naming and opening the file that would confirm a hypothesis before
writing code, and adds two checks that would have caught this one:
- Does the crash frequency match the story? A race is intermittent; a
mismatched template fails every time that path runs. Rare-but-deterministic
contradicts a race.
- For a visual-tree mismatch, read the DataTemplateSelector before blaming
recycling. Guarding the symptom there turns a crash into a silently blank
row and leaves the real bug in place.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • e66dbd0 • 1 files, +14/-0
crash-triage: don't construct singletons to silence a null
Second rejected PR, same root habit as the first: fixing the symptom at the
call site without establishing who owns the state.
InitializeFrame was patched to build a WindowContext when Current was null.
WindowContext's constructor assigns the thread-static _current, and
OnWindowCreated constructs one on its own schedule, so the patch produced two:
the later assignment wins and the frame attached to the first is silently
discarded. That is worse than the NullReferenceException it removed, because
it corrupts startup state instead of aborting it.
Adds two rules:
- Before constructing anything to make a null go away, grep every construction
site and work out the ordering. If it is a not-yet-initialised singleton,
creating one here means two exist and one loses.
- When the missing state is a race, neither inventing it nor returning early
is the fix — returning leaves the feature silently dead, which is the same
trap as guarding a template mismatch. Carrying the input forward until the
state arrives is a sequencing decision about startup: propose it, don't
guess at it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • ecb532a • 1 files, +2/-2
Fix MediaDeviceList release
#unigram
crash-triage: close the crash group when the PR merges
Merging is what marks a crash fixed, so the loop now ends by closing the
group rather than leaving it to be done by hand.
Because the group hash cannot appear in the PR, the pr-number -> group-hash
mapping has to be recorded locally when the PR is opened, or the link is lost
as soon as the PR is the only artefact left.
Also records the PR-watching gotcha found this session: gh posts comments
under the user's own account, so a watcher cannot use author to tell the
agent's comments from the user's, and unfiltered it reads its own words back
as instructions. Agent comments carry an HTML-comment marker and the watcher
skips them.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • 4caca6d • 1 files, +17/-0
crash-triage: verify the mechanism instead of inferring it
A rejected PR's worth of lesson. The autocomplete crash was diagnosed as a
recycled container carrying the previous item type's template, a story that
fitted the stack and was never checked. The actual cause was
AutocompleteTemplateSelector returning CommandTemplate for both UserCommand
and QuickReplyShortcut, so the tree never matched what the code-behind read —
one file would have shown it, and it was never opened.
Requires naming and opening the file that would confirm a hypothesis before
writing code, and adds two checks that would have caught this one:
- Does the crash frequency match the story? A race is intermittent; a
mismatched template fails every time that path runs. Rare-but-deterministic
contradicts a race.
- For a visual-tree mismatch, read the DataTemplateSelector before blaming
recycling. Guarding the symptom there turns a crash into a silently blank
row and leaves the real bug in place.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • e66dbd0 • 1 files, +14/-0
crash-triage: don't construct singletons to silence a null
Second rejected PR, same root habit as the first: fixing the symptom at the
call site without establishing who owns the state.
InitializeFrame was patched to build a WindowContext when Current was null.
WindowContext's constructor assigns the thread-static _current, and
OnWindowCreated constructs one on its own schedule, so the patch produced two:
the later assignment wins and the frame attached to the first is silently
discarded. That is worse than the NullReferenceException it removed, because
it corrupts startup state instead of aborting it.
Adds two rules:
- Before constructing anything to make a null go away, grep every construction
site and work out the ordering. If it is a not-yet-initialised singleton,
creating one here means two exist and one loses.
- When the missing state is a race, neither inventing it nor returning early
is the fix — returning leaves the feature silently dead, which is the same
trap as guarding a template mismatch. Carrying the input forward until the
state arrives is a sequencing decision about startup: propose it, don't
guess at it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • ecb532a • 1 files, +2/-2
Fix MediaDeviceList release
#unigram
🫡2
UnigramDev/Unigram/develop • c5beb02 • 3 files, +30/-8
Fix file id packing
UnigramDev/Unigram/develop • b9e38a2 • 10 files, +674/-265
Improve media streaming
UnigramDev/Unigram/develop • e18b2ba • 1 files, +3/-1
Fix conditional check
UnigramDev/Unigram/develop • aabf3a0 • 1 files, +104/-9
Calculate can be forwarded/replied locally in MessageSelector
UnigramDev/Unigram/develop • 1ae3795 • 1 files, +70/-0
Support buttons in text
UnigramDev/Unigram/develop • 7979820 • 2 files, +10/-3
Fix recycle paths
UnigramDev/Unigram/develop • 9a61aba • 2 files, +2/-0
Add handled true where missing
UnigramDev/Unigram/develop • 333170e • 7 files, +14/-0
Add tlottie test
UnigramDev/Unigram/develop • 2a2fd79 • 1 files, +8/-0
Handle unsupported message click
UnigramDev/Unigram/develop • d4268bb • 1 files, +5/-4
Fix quick reply shortcut crash
UnigramDev/Unigram/develop • ee0d49d • 1 files, +5/-0
Handle force reply
UnigramDev/Unigram/develop • 16acb43 • 1 files, +2/-2
Fix rich draft
UnigramDev/Unigram/develop • f2305b4 • 3 files, +80/-100
Text editor improvements
UnigramDev/Unigram/develop • 8bde885 • 1 files, +2/-2
Fix csproj
UnigramDev/Unigram/develop • f046915 • 1 files, +3/-3
Fix AddFileToDownloads calls
#unigram
Fix file id packing
UnigramDev/Unigram/develop • b9e38a2 • 10 files, +674/-265
Improve media streaming
UnigramDev/Unigram/develop • e18b2ba • 1 files, +3/-1
Fix conditional check
UnigramDev/Unigram/develop • aabf3a0 • 1 files, +104/-9
Calculate can be forwarded/replied locally in MessageSelector
UnigramDev/Unigram/develop • 1ae3795 • 1 files, +70/-0
Support buttons in text
UnigramDev/Unigram/develop • 7979820 • 2 files, +10/-3
Fix recycle paths
UnigramDev/Unigram/develop • 9a61aba • 2 files, +2/-0
Add handled true where missing
UnigramDev/Unigram/develop • 333170e • 7 files, +14/-0
Add tlottie test
UnigramDev/Unigram/develop • 2a2fd79 • 1 files, +8/-0
Handle unsupported message click
UnigramDev/Unigram/develop • d4268bb • 1 files, +5/-4
Fix quick reply shortcut crash
UnigramDev/Unigram/develop • ee0d49d • 1 files, +5/-0
Handle force reply
UnigramDev/Unigram/develop • 16acb43 • 1 files, +2/-2
Fix rich draft
UnigramDev/Unigram/develop • f2305b4 • 3 files, +80/-100
Text editor improvements
UnigramDev/Unigram/develop • 8bde885 • 1 files, +2/-2
Fix csproj
UnigramDev/Unigram/develop • f046915 • 1 files, +3/-3
Fix AddFileToDownloads calls
#unigram
🫡2
UnigramDev/Unigram/develop • 765bc86 • 5 files, +127/-22
Merge branch 'develop' of https://github.com/UnigramDev/Unigram into develop
UnigramDev/Unigram/develop • 440776a • 197 files, +399978/-6501
Merge branch 'layer228' into develop
#unigram
Merge branch 'develop' of https://github.com/UnigramDev/Unigram into develop
UnigramDev/Unigram/develop • 440776a • 197 files, +399978/-6501
Merge branch 'layer228' into develop
#unigram
🫡2
morethanwords/tweb/master • b1f87b5 • 6 files, +224/-7
Fix XSS via forged Electron helpers in message links
Link hrefs were built by interpolating the URL into a javascript: string
literal, so an apostrophe in a URL escaped into code. The Electron check
guarding that path read the ambient electronHelpers global, which any
element id shadows — and Temml turns a message's \label{} into a raw id,
so message content could forge it in an ordinary browser.
Keep the URL as data and hand it to openExternal on click, require a
callable bridge, and namespace ids Temml renders from message content.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
morethanwords/tweb/master • c4618cc • 3 files, +63/-23
Fix Instant View math never rendering
Temml's lexer builds its token regex by concatenating string literals
holding raw lone surrogates (the surrogate-pair alternative,
"|[\uD800-\uDBFF][\uDC00-\uDFFF]"). Both the esbuild dev dep-optimizer
and the rolldown production build re-print that folded string with each
lone surrogate written as U+FFFD, degrading the class to
[<FFFD>d800-<FFFD>dbff]. The 0-<FFFD> range that falls out of it
swallows a backslash, so every control word lexed as a one-letter
command: \frac died with "Unsupported function name: \f", nothing but
bare symbols parsed, and renderLatexInto silently fell back to showing
the raw source. This was never dev-only — production shipped the same
broken regex.
Load the library as an asset no bundler re-prints: ?url plus a runtime
script load. Take the package's own terser-minified build, which escapes
the lone surrogates correctly and is 49KB gzip against the .mjs bundle's
115KB — lighter than the broken bundled copy's 59KB. The comment
claiming Temml PR #128 made a plain dynamic import safe was wrong.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
morethanwords/tweb/master • 4c5a237 • 2 files, +277/-10
Handle postMessage clone failures in SuperMessagePort
postMessage errors were swallowed with a log line, so a payload that could
not be structured-cloned left the caller's invoke promise pending forever
and dropped the task for every port.
Route every send through sendTask, which now retries a failed batch
per-inner-task, answers a result/ack that could not be cloned with an
explicit DATA_CLONE_ERROR, and rejects an invoke that reached no port at
all. Also record the invoke's payload name/method in `awaiting` so a
timed-out task can be identified from the log.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
morethanwords/tweb/master • 701e811 • 1 files, +3/-3
Fix join button label for channels with join requests
A broadcast channel that requires approval showed "Subscribe" instead of
"Request to Join": isBroadcast was tested before the request type, so the
request branch was unreachable for channels. Test the request type first.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
#webk
Fix XSS via forged Electron helpers in message links
Link hrefs were built by interpolating the URL into a javascript: string
literal, so an apostrophe in a URL escaped into code. The Electron check
guarding that path read the ambient electronHelpers global, which any
element id shadows — and Temml turns a message's \label{} into a raw id,
so message content could forge it in an ordinary browser.
Keep the URL as data and hand it to openExternal on click, require a
callable bridge, and namespace ids Temml renders from message content.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
morethanwords/tweb/master • c4618cc • 3 files, +63/-23
Fix Instant View math never rendering
Temml's lexer builds its token regex by concatenating string literals
holding raw lone surrogates (the surrogate-pair alternative,
"|[\uD800-\uDBFF][\uDC00-\uDFFF]"). Both the esbuild dev dep-optimizer
and the rolldown production build re-print that folded string with each
lone surrogate written as U+FFFD, degrading the class to
[<FFFD>d800-<FFFD>dbff]. The 0-<FFFD> range that falls out of it
swallows a backslash, so every control word lexed as a one-letter
command: \frac died with "Unsupported function name: \f", nothing but
bare symbols parsed, and renderLatexInto silently fell back to showing
the raw source. This was never dev-only — production shipped the same
broken regex.
Load the library as an asset no bundler re-prints: ?url plus a runtime
script load. Take the package's own terser-minified build, which escapes
the lone surrogates correctly and is 49KB gzip against the .mjs bundle's
115KB — lighter than the broken bundled copy's 59KB. The comment
claiming Temml PR #128 made a plain dynamic import safe was wrong.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
morethanwords/tweb/master • 4c5a237 • 2 files, +277/-10
Handle postMessage clone failures in SuperMessagePort
postMessage errors were swallowed with a log line, so a payload that could
not be structured-cloned left the caller's invoke promise pending forever
and dropped the task for every port.
Route every send through sendTask, which now retries a failed batch
per-inner-task, answers a result/ack that could not be cloned with an
explicit DATA_CLONE_ERROR, and rejects an invoke that reached no port at
all. Also record the invoke's payload name/method in `awaiting` so a
timed-out task can be identified from the log.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
morethanwords/tweb/master • 701e811 • 1 files, +3/-3
Fix join button label for channels with join requests
A broadcast channel that requires approval showed "Subscribe" instead of
"Request to Join": isBroadcast was tested before the request type, so the
request branch was unreachable for channels. Test the request type first.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
#webk
🫡3
morethanwords/tweb/master • 088d690 • 7 files, +334/-53
Support guard-bot joins through their WebView (layer 228)
Layer 227 turned the join result into a union, and tweb handled the
web-view-gated branch by caching the bot and reporting no updates — the
user got nothing at all. Layer 228 splits that flow into an initial query
plus a separate messages.requestChatJoinWebView, so it can be driven.
processChatInviteJoinResult now returns a chatInviteJoinWebView descriptor
instead of undefined, and openWebApp grows a joinChat mode that requests
the guard bot's web view, tracks the query ids, and follows
updateJoinChatWebViewDecision: a webView result reloads the iframe with the
new url, while approved/declined/queued closes it and toasts the outcome,
opening the chat when the join went through.
Also make WebApp.destroy idempotent and bail out of the async init steps
once destroyed — the join flow can tear the popup down mid-init.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
morethanwords/tweb/master • 2d2f188 • 206 files, +30012/-2291
Implement Telegram Communities
morethanwords/tweb/master • 6619fdb • 14 files, +944/-73
Support guard bots end to end
Layer 228 hands a join off to a guard bot, which greets the newcomer in its
own mini app and answers the query. tweb only had the joining half, and its
outcome toast interpolated the chat title — which an invite-link join never
has, so a declined or queued join showed a hole where the name should be.
Joining side: the toast now uses the wording the official clients use and
tells a group from a channel without needing the title; an approved join
opens the chat through `open` so a forum lands on its topics tab; the
disclaimer names both the chat and the bot, ported from iOS. `loadExternal`
and the iframe show/hide it shared with `createWebView` no longer duplicate
each other.
Admin side, which was missing entirely: `toggleJoinRequest` carries the guard
bot (and clears it with `inputUserEmpty`), the bot's admin rights grow a
Process Join Requests switch with the enable and replace confirmations, and
the chat type screen names the guard bot by @username with a link to its
rights. Channels get the approval section they never had, so a guard bot set
there can also be seen and removed.
The ephemeral bot fixture gains Guard Mode: it answers join request queries
with approve, decline or queue, optionally showing a Mini App first. A query
it can no longer answer is logged and skipped rather than fatal — the same
update is redelivered until the batch is acknowledged, so dying on it wedges
the fixture forever on that one update.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
morethanwords/tweb/master • 62c3ad6 • 9 files, +99/-7
Support stars_spend_topup_invoice_disabled
Don't offer the Stars topup when the spend is attributed to a bot or a
channel and the app config forbids it — show a toast instead, like other
clients do.
The spend purpose peer is now resolved the same way as in tdesktop (bots
and channels only, monoforums resolve to their broadcast) and is used
both for inputStorePaymentStarsTopup.spend_purpose_peer and for the new
check. Paid messages, suggested posts and the "Buy Stars" service
buttons pass their peer as well now.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
#webk
Support guard-bot joins through their WebView (layer 228)
Layer 227 turned the join result into a union, and tweb handled the
web-view-gated branch by caching the bot and reporting no updates — the
user got nothing at all. Layer 228 splits that flow into an initial query
plus a separate messages.requestChatJoinWebView, so it can be driven.
processChatInviteJoinResult now returns a chatInviteJoinWebView descriptor
instead of undefined, and openWebApp grows a joinChat mode that requests
the guard bot's web view, tracks the query ids, and follows
updateJoinChatWebViewDecision: a webView result reloads the iframe with the
new url, while approved/declined/queued closes it and toasts the outcome,
opening the chat when the join went through.
Also make WebApp.destroy idempotent and bail out of the async init steps
once destroyed — the join flow can tear the popup down mid-init.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
morethanwords/tweb/master • 2d2f188 • 206 files, +30012/-2291
Implement Telegram Communities
morethanwords/tweb/master • 6619fdb • 14 files, +944/-73
Support guard bots end to end
Layer 228 hands a join off to a guard bot, which greets the newcomer in its
own mini app and answers the query. tweb only had the joining half, and its
outcome toast interpolated the chat title — which an invite-link join never
has, so a declined or queued join showed a hole where the name should be.
Joining side: the toast now uses the wording the official clients use and
tells a group from a channel without needing the title; an approved join
opens the chat through `open` so a forum lands on its topics tab; the
disclaimer names both the chat and the bot, ported from iOS. `loadExternal`
and the iframe show/hide it shared with `createWebView` no longer duplicate
each other.
Admin side, which was missing entirely: `toggleJoinRequest` carries the guard
bot (and clears it with `inputUserEmpty`), the bot's admin rights grow a
Process Join Requests switch with the enable and replace confirmations, and
the chat type screen names the guard bot by @username with a link to its
rights. Channels get the approval section they never had, so a guard bot set
there can also be seen and removed.
The ephemeral bot fixture gains Guard Mode: it answers join request queries
with approve, decline or queue, optionally showing a Mini App first. A query
it can no longer answer is logged and skipped rather than fatal — the same
update is redelivered until the batch is acknowledged, so dying on it wedges
the fixture forever on that one update.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
morethanwords/tweb/master • 62c3ad6 • 9 files, +99/-7
Support stars_spend_topup_invoice_disabled
Don't offer the Stars topup when the spend is attributed to a bot or a
channel and the app config forbids it — show a toast instead, like other
clients do.
The spend purpose peer is now resolved the same way as in tdesktop (bots
and channels only, monoforums resolve to their broadcast) and is used
both for inputStorePaymentStarsTopup.spend_purpose_peer and for the new
check. Paid messages, suggested posts and the "Buy Stars" service
buttons pass their peer as well now.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
#webk
🫡3
🫡3
UnigramDev/Unigram/develop • 70a965e • 2 files, +72/-17
Reuse the text selection manager across recycled containers
MessageSelector built a TextSelectionManager on load and dropped it on unload,
so a virtualized history allocated one per container per scroll-through, each
registering four routed pointer handlers and two owner subscriptions on the way
in and removing them on the way out.
The manager now takes its content element through Attach rather than only in the
constructor, so one instance can follow a container through its whole life while
the element it watches changes underneath. The four PointerEventHandler
instances move to the constructor: AddHandler and RemoveHandler match by delegate
identity, so they always had to be stable, and creating them once per manager
rather than once per attach costs nothing.
Detach now clears the selection and empties _items. That list holds the
selectable controls the selection was made of, and a manager that outlives its
content would otherwise keep them alive and highlight them again the next time it
was asked. It is also idempotent and leaves _root null, which the two detach
paths on the selector, unload and content change, both rely on. RebuildItems
guards against the detached state because SelectAll is public.
An active selection is therefore dropped when content detaches, where before it
was abandoned with the manager. Nothing observes the difference:
TextSelectionCoordinator holds a weak reference and SelectionChanged has no
subscribers.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • 55e0be3 • 2 files, +16/-10
Wire up button click in text
UnigramDev/Unigram/develop • 1785402 • 1 files, +34/-1
crash-triage: diagnose why the value was wrong, not just that it was
Both rejected PRs this session shared a habit: reaching for a guard as soon as
the faulting value was identified, without establishing how it came to be that
way. "It was null" is where the investigation starts.
Requires tracing back to whatever should have set the value and being able to
name the state that produced it in one sentence, pointing at the code that
creates it. "Somehow it was null" means the diagnosis is unfinished.
Where that lands decides the fix, and the three outcomes want different things:
- the API legitimately returns null and the caller assumed otherwise, where a
guard at the call site is correct;
- something upstream is wired wrong, where a guard hides a permanent bug and
degrades silently instead;
- two things race and the state is merely not there yet, which needs
sequencing rather than a default or an early return.
All three occurred here within a day, so the table names the real examples.
Also notes that a guard shipped without knowing which case applies is still a
guess even when it demonstrably stops the crash — it costs the next person the
evidence, since the reports stop while the bug remains — and that the PR should
state the cause, because that is what a reviewer can actually check.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
Reuse the text selection manager across recycled containers
MessageSelector built a TextSelectionManager on load and dropped it on unload,
so a virtualized history allocated one per container per scroll-through, each
registering four routed pointer handlers and two owner subscriptions on the way
in and removing them on the way out.
The manager now takes its content element through Attach rather than only in the
constructor, so one instance can follow a container through its whole life while
the element it watches changes underneath. The four PointerEventHandler
instances move to the constructor: AddHandler and RemoveHandler match by delegate
identity, so they always had to be stable, and creating them once per manager
rather than once per attach costs nothing.
Detach now clears the selection and empties _items. That list holds the
selectable controls the selection was made of, and a manager that outlives its
content would otherwise keep them alive and highlight them again the next time it
was asked. It is also idempotent and leaves _root null, which the two detach
paths on the selector, unload and content change, both rely on. RebuildItems
guards against the detached state because SelectAll is public.
An active selection is therefore dropped when content detaches, where before it
was abandoned with the manager. Nothing observes the difference:
TextSelectionCoordinator holds a weak reference and SelectionChanged has no
subscribers.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • 55e0be3 • 2 files, +16/-10
Wire up button click in text
UnigramDev/Unigram/develop • 1785402 • 1 files, +34/-1
crash-triage: diagnose why the value was wrong, not just that it was
Both rejected PRs this session shared a habit: reaching for a guard as soon as
the faulting value was identified, without establishing how it came to be that
way. "It was null" is where the investigation starts.
Requires tracing back to whatever should have set the value and being able to
name the state that produced it in one sentence, pointing at the code that
creates it. "Somehow it was null" means the diagnosis is unfinished.
Where that lands decides the fix, and the three outcomes want different things:
- the API legitimately returns null and the caller assumed otherwise, where a
guard at the call site is correct;
- something upstream is wired wrong, where a guard hides a permanent bug and
degrades silently instead;
- two things race and the state is merely not there yet, which needs
sequencing rather than a default or an early return.
All three occurred here within a day, so the table names the real examples.
Also notes that a guard shipped without knowing which case applies is still a
guess even when it demonstrably stops the crash — it costs the next person the
evidence, since the reports stop while the bug remains — and that the PR should
state the cause, because that is what a reviewer can actually check.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
🫡2
UnigramDev/Unigram/develop • 1e841d6 • 7 files, +658/-308
Get the VLC build working again, and generate the plugin cache
The build failed while fetching contribs, so nothing downstream had been
exercised in a while. Fixing that surfaced several more problems.
The submodule fix is carried as 0005: $(SF) pointed at a SourceForge mirror
that no longer resolves, and the libvlc-only build tried to package an NSIS
installer around a vlc.exe that -z never builds.
build.ps1:
- dropped -it from docker run. It requires a terminal on stdin, so the script
could not run from CI or any non-interactive shell.
- check the exit code. Previously a failed build fell straight through to
packing, turning a broken build into a stale package rather than an error.
- pass -D as a separate argument. getopts captured the '=' from -D=<path> into
OPTARG, which reached -fdebug-prefix-map and put a literal '=' in front of
every source path recorded in the PDBs. Crash reports showed frames as
"=C:\...\win64-uwp\modules\=C:\...\modules\audio_output\wasapi.c", and they
now resolve normally.
The plugin cache was never generated: Makefile.am only runs vlc-cache-gen when
build == host, so cross-compiling always skipped it, and the committed
plugins.dat was left over from 3.0.22-rc1. cache.c compares the version with
memcmp, so it was being rejected outright and every launch rescanned all the
plugins.
plugins-cache.ps1 generates it. The UWP build loads plugins with
LoadPackagedLibrary, which only resolves inside the calling process's package,
so cache-gen finds nothing when run from a plain directory. It therefore stages
the plugins the nuspec ships, registers that as a loose package, and runs
cache-gen inside it.
ARM64 cannot execute on an x64 host, so its cache is derived rather than
generated. bank.c validates each entry by mtime and size only, and the module
descriptors are architecture-independent, so the x64 cache is re-stamped with
the ARM64 files' metadata. Verified independently: all 36 entries carry the
right mtime and size for their architecture, and 31 of them genuinely differ
between the two.
That also means one plugins.dat cannot serve both architectures, so the nuspec
now ships one per architecture instead of the same file twice.
The nuspec referred to the install directory as vlc-3.0.23-2 in 284 places
while the build produces vlc-3.0.23, so packing could never succeed. The
version is now substituted via -Properties. A wildcard was simpler but NuGet
then treats each target as a directory and produces
build/win10-x64/libvlc.dll/libvlc.dll.
Verified end to end: both architectures build, the caches are generated and
checked, and nuget pack produces a package with 36 plugins and a distinct
cache per architecture.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • 07ab82d • 2 files, +0/-0
Latest tlottie fixes
UnigramDev/Unigram/develop • 942b8a3 • 3 files, +15/-8
Update libvlc
UnigramDev/Unigram/develop • 0525c26 • 3 files, +4/-33
Display voice notes in rich messages properly
UnigramDev/Unigram/develop • 3418495 • 1 files, +20/-33
Fix instant view styles
#unigram
Get the VLC build working again, and generate the plugin cache
The build failed while fetching contribs, so nothing downstream had been
exercised in a while. Fixing that surfaced several more problems.
The submodule fix is carried as 0005: $(SF) pointed at a SourceForge mirror
that no longer resolves, and the libvlc-only build tried to package an NSIS
installer around a vlc.exe that -z never builds.
build.ps1:
- dropped -it from docker run. It requires a terminal on stdin, so the script
could not run from CI or any non-interactive shell.
- check the exit code. Previously a failed build fell straight through to
packing, turning a broken build into a stale package rather than an error.
- pass -D as a separate argument. getopts captured the '=' from -D=<path> into
OPTARG, which reached -fdebug-prefix-map and put a literal '=' in front of
every source path recorded in the PDBs. Crash reports showed frames as
"=C:\...\win64-uwp\modules\=C:\...\modules\audio_output\wasapi.c", and they
now resolve normally.
The plugin cache was never generated: Makefile.am only runs vlc-cache-gen when
build == host, so cross-compiling always skipped it, and the committed
plugins.dat was left over from 3.0.22-rc1. cache.c compares the version with
memcmp, so it was being rejected outright and every launch rescanned all the
plugins.
plugins-cache.ps1 generates it. The UWP build loads plugins with
LoadPackagedLibrary, which only resolves inside the calling process's package,
so cache-gen finds nothing when run from a plain directory. It therefore stages
the plugins the nuspec ships, registers that as a loose package, and runs
cache-gen inside it.
ARM64 cannot execute on an x64 host, so its cache is derived rather than
generated. bank.c validates each entry by mtime and size only, and the module
descriptors are architecture-independent, so the x64 cache is re-stamped with
the ARM64 files' metadata. Verified independently: all 36 entries carry the
right mtime and size for their architecture, and 31 of them genuinely differ
between the two.
That also means one plugins.dat cannot serve both architectures, so the nuspec
now ships one per architecture instead of the same file twice.
The nuspec referred to the install directory as vlc-3.0.23-2 in 284 places
while the build produces vlc-3.0.23, so packing could never succeed. The
version is now substituted via -Properties. A wildcard was simpler but NuGet
then treats each target as a directory and produces
build/win10-x64/libvlc.dll/libvlc.dll.
Verified end to end: both architectures build, the caches are generated and
checked, and nuget pack produces a package with 36 plugins and a distinct
cache per architecture.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • 07ab82d • 2 files, +0/-0
Latest tlottie fixes
UnigramDev/Unigram/develop • 942b8a3 • 3 files, +15/-8
Update libvlc
UnigramDev/Unigram/develop • 0525c26 • 3 files, +4/-33
Display voice notes in rich messages properly
UnigramDev/Unigram/develop • 3418495 • 1 files, +20/-33
Fix instant view styles
#unigram
🫡2
UnigramDev/Unigram/develop • 9aa0fea • 1 files, +4/-4
Fix variable name
UnigramDev/Unigram/develop • 11b83a7 • 1 files, +7/-6
Comment out broken code
UnigramDev/Unigram/develop • b28a53d • 2 files, +43/-3
WIP changes
UnigramDev/Unigram/develop • 2e74d91 • 1 files, +2/-2
Fix menu flyout style
UnigramDev/Unigram/develop • 7db4c04 • 1 files, +3/-3
Fix markup button clip
UnigramDev/Unigram/develop • c2805dc • 1 files, +11/-1
Handle UpdateChatHasWelcomeMessages
UnigramDev/Unigram/develop • e35125c • 1 files, +10/-6
Support multiple qr code sizes
UnigramDev/Unigram/develop • 4d506bf • 1 files, +0/-34
Remove dead code
UnigramDev/Unigram/develop • 654bae0 • 1 files, +1/-1
Make field static
UnigramDev/Unigram/develop • 0a33466 • 1 files, +1/-1
Fix poll document type check
UnigramDev/Unigram/develop • 1ec2c2b • 1 files, +12/-0
WriteableBitmap span extension
UnigramDev/Unigram/develop • 257d8b1 • 1 files, +13/-2
Support color and offset in shadow helper
UnigramDev/Unigram/develop • d43f2ca • 2 files, +521/-0
Tilt effect (wip)
UnigramDev/Unigram/develop • b85a83e • 1 files, +1/-0
Fix libvlc_audio_output_device_set
UnigramDev/Unigram/develop • 89d879b • 2 files, +66/-12
Add more logs to media player
UnigramDev/Unigram/develop • c6a778f • 1 files, +7/-0
Add missing has_decoded_frames case
#unigram
Fix variable name
UnigramDev/Unigram/develop • 11b83a7 • 1 files, +7/-6
Comment out broken code
UnigramDev/Unigram/develop • b28a53d • 2 files, +43/-3
WIP changes
UnigramDev/Unigram/develop • 2e74d91 • 1 files, +2/-2
Fix menu flyout style
UnigramDev/Unigram/develop • 7db4c04 • 1 files, +3/-3
Fix markup button clip
UnigramDev/Unigram/develop • c2805dc • 1 files, +11/-1
Handle UpdateChatHasWelcomeMessages
UnigramDev/Unigram/develop • e35125c • 1 files, +10/-6
Support multiple qr code sizes
UnigramDev/Unigram/develop • 4d506bf • 1 files, +0/-34
Remove dead code
UnigramDev/Unigram/develop • 654bae0 • 1 files, +1/-1
Make field static
UnigramDev/Unigram/develop • 0a33466 • 1 files, +1/-1
Fix poll document type check
UnigramDev/Unigram/develop • 1ec2c2b • 1 files, +12/-0
WriteableBitmap span extension
UnigramDev/Unigram/develop • 257d8b1 • 1 files, +13/-2
Support color and offset in shadow helper
UnigramDev/Unigram/develop • d43f2ca • 2 files, +521/-0
Tilt effect (wip)
UnigramDev/Unigram/develop • b85a83e • 1 files, +1/-0
Fix libvlc_audio_output_device_set
UnigramDev/Unigram/develop • 89d879b • 2 files, +66/-12
Add more logs to media player
UnigramDev/Unigram/develop • c6a778f • 1 files, +7/-0
Add missing has_decoded_frames case
#unigram
🫡2
UnigramDev/Unigram/develop • aaf2a06 • 3 files, +314/-0
Unused code
UnigramDev/Unigram/develop • ba351eb • 4 files, +189/-9363
Temp changes
UnigramDev/Unigram/develop • 1601a27 • 1 files, +1/-1
Update libvlc in main project too
UnigramDev/Unigram/develop • b1e62d2 • 2 files, +2/-1
Maintenance
#unigram
Unused code
UnigramDev/Unigram/develop • ba351eb • 4 files, +189/-9363
Temp changes
UnigramDev/Unigram/develop • 1601a27 • 1 files, +1/-1
Update libvlc in main project too
UnigramDev/Unigram/develop • b1e62d2 • 2 files, +2/-1
Maintenance
#unigram
🫡2