UnigramDev/Unigram/develop • 3503a0d • 1 files, +11/-2
Register the theme before reading its settings (#3343)
Theme.Current is thread-static and assigned in exactly one place, and that
assignment sat behind a call that can fail:
try
{
_isolatedStore = ApplicationData.Current.LocalSettings.CreateContainer(...);
Current ??= this;
...
}
catch { }
When CreateContainer throws, the catch is silent and the theme never registers
itself. Every later Theme.Current dereference - and they are unguarded across
the message tree, FormattedTextBlock, MessageBubblePanel, ChatBackgroundControl
- is then a NullReferenceException on that view thread.
Reported by crash telemetry on 12.9.1, through OnShareTargetActivated:
SharePage's constructor calls ChatBackgroundControl.Update, which reads
Theme.Current.ChatBackground and dies. The share target view is driven before
it is initialized - the same race as #3320, which moved this crash one frame
later rather than removing it - and ApplicationData is evidently not always
reachable that early.
That the constructor ran at all is not in doubt: SharePage.xaml resolves
StaticResource EmptyHyperlinkButtonStyle from CommonStyles.xaml, a merged
dictionary of Application.Resources that follows <common:Theme /> in the merge
list, so InitializeComponent could not have returned unless the resources of
this view - the theme among them - had been inflated.
Assign Current before the try. It depends on nothing inside it, and losing a
persisted preference should not cost the view its theme. The catch now logs,
since the whole diagnosis rests on inferring which call threw, and an empty
catch is what made that necessary.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • 3409687 • 1 files, +12/-1
Guard the geolocation access request in SendLocationPopup (#3344)
Geolocator.RequestAccessAsync is a remote procedure call and throws when the
Windows Geolocation Service (lfsvc) is disabled, which privacy/debloat scripts
routinely do. In FindLocation the call was the only one not wrapped, so the
throw escaped an async void method and reached the unhandled exception handler.
Treat a failed request as "not allowed": the popup keeps the map shimmer, the
same as an explicit denial.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • 5316a20 • 1 files, +19/-8
Fix the Topics tab context menu casting to the wrong item type (#3345)
ProfileTopicsTabPage.OnContextRequested was an unadapted copy of the one in
ProfileSavedChatsTabPage: it cast the right-clicked item to SavedMessagesTopic,
which is always null here because this list holds ForumTopic, and it invoked
the saved chats tab's commands instead of the topics tab's own. Right-clicking
any row threw a NullReferenceException, reported by crash telemetry on 12.9.1.
Cast to ForumTopic, route pin/delete to TopicsTab, and gate them on
CanManageTopics the way ForumView's own topic menu does.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
Register the theme before reading its settings (#3343)
Theme.Current is thread-static and assigned in exactly one place, and that
assignment sat behind a call that can fail:
try
{
_isolatedStore = ApplicationData.Current.LocalSettings.CreateContainer(...);
Current ??= this;
...
}
catch { }
When CreateContainer throws, the catch is silent and the theme never registers
itself. Every later Theme.Current dereference - and they are unguarded across
the message tree, FormattedTextBlock, MessageBubblePanel, ChatBackgroundControl
- is then a NullReferenceException on that view thread.
Reported by crash telemetry on 12.9.1, through OnShareTargetActivated:
SharePage's constructor calls ChatBackgroundControl.Update, which reads
Theme.Current.ChatBackground and dies. The share target view is driven before
it is initialized - the same race as #3320, which moved this crash one frame
later rather than removing it - and ApplicationData is evidently not always
reachable that early.
That the constructor ran at all is not in doubt: SharePage.xaml resolves
StaticResource EmptyHyperlinkButtonStyle from CommonStyles.xaml, a merged
dictionary of Application.Resources that follows <common:Theme /> in the merge
list, so InitializeComponent could not have returned unless the resources of
this view - the theme among them - had been inflated.
Assign Current before the try. It depends on nothing inside it, and losing a
persisted preference should not cost the view its theme. The catch now logs,
since the whole diagnosis rests on inferring which call threw, and an empty
catch is what made that necessary.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • 3409687 • 1 files, +12/-1
Guard the geolocation access request in SendLocationPopup (#3344)
Geolocator.RequestAccessAsync is a remote procedure call and throws when the
Windows Geolocation Service (lfsvc) is disabled, which privacy/debloat scripts
routinely do. In FindLocation the call was the only one not wrapped, so the
throw escaped an async void method and reached the unhandled exception handler.
Treat a failed request as "not allowed": the popup keeps the map shimmer, the
same as an explicit denial.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • 5316a20 • 1 files, +19/-8
Fix the Topics tab context menu casting to the wrong item type (#3345)
ProfileTopicsTabPage.OnContextRequested was an unadapted copy of the one in
ProfileSavedChatsTabPage: it cast the right-clicked item to SavedMessagesTopic,
which is always null here because this list holds ForumTopic, and it invoked
the saved chats tab's commands instead of the topics tab's own. Right-clicking
any row threw a NullReferenceException, reported by crash telemetry on 12.9.1.
Cast to ForumTopic, route pin/delete to TopicsTab, and gate them on
CanManageTopics the way ForumView's own topic menu does.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
👍1
UnigramDev/Unigram/develop • f994e66 • 2 files, +6/-2
Fix InvalidCastException opening the gift variants popup on Windows 10 (#3346)
ListViewItemPresenter.SelectedBorderBrush, SelectedPointerOverBorderBrush and
SelectedPressedBorderBrush are declared on IListViewItemPresenter4, introduced
in UniversalApiContract 13.0 (build 22621). On older builds the QueryInterface
behind the setter fails and the projection throws, so GiftVariantsPopup crashed
as soon as the first container was realized.
Guard the three assignments on the property actually being present. Where it is
not, the selection border keeps its default brush.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • 5b2a1e1 • 3 files, +18/-2
Let the invite link actually arrive on ChatInviteLinksPage (#3347)
ChatInviteLinksViewModel declares _supergroupId and _basicGroupId but never
assigns them, so Handle(UpdateSupergroupFullInfo) and
Handle(UpdateBasicGroupFullInfo) compared against 0 and never matched. The
"full info will arrive by push" fallback in OnNavigatedToAsync was therefore
dead, and InviteLink stayed null for every chat whose full info wasn't already
cached — which made the header's copy and share buttons dereference null.
Assign both ids, and fix the basic group branch, which asked for
GetBasicGroupFullInfo(supergroup.Id) inside the branch where TryGetSupergroup
had just failed: supergroup is null there, and it was passing a supergroup id
where a basic group id belongs.
InviteLink is also legitimately null for anyone who isn't the creator, since
TDLib only populates the primary link for them, so the header buttons are now
disabled while it is null instead of acting on nothing.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • 711f79a • 1 files, +64/-20
Recover from a damaged OCR model archive (#3348)
Extracting Ocr_x64.zip crashed the app with an InvalidDataException,
"The archive entry was compressed using an unsupported compression
method". The message is misleading: Inflater maps zlib's Z_DATA_ERROR
onto that string, and the failure came from mid-entry writes in
DeflateStream.CopyToAsyncStream rather than from ZipArchiveEntry.Open,
so the deflate data was corrupt or truncated, not compressed with a
method the reader doesn't support. Why the local copy was damaged is
not established.
ExtractModelAsync had no try/catch and RecognizeText is async void, so
the failure reached the app unhandled. Catch it, delete the archive so
that the next attempt downloads a fresh copy, delete the partially
written model so that the readiness check in EnsureReadyAsync doesn't
accept it, and report the extraction as unavailable.
Separately, _extractLock.Release() sat outside any finally, so a throw
during extraction leaked the semaphore for the rest of the session:
every later extraction then returned early at Wait(0), leaving OCR
broken until restart.
Reported by crash telemetry on 12.9.1.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
Fix InvalidCastException opening the gift variants popup on Windows 10 (#3346)
ListViewItemPresenter.SelectedBorderBrush, SelectedPointerOverBorderBrush and
SelectedPressedBorderBrush are declared on IListViewItemPresenter4, introduced
in UniversalApiContract 13.0 (build 22621). On older builds the QueryInterface
behind the setter fails and the projection throws, so GiftVariantsPopup crashed
as soon as the first container was realized.
Guard the three assignments on the property actually being present. Where it is
not, the selection border keeps its default brush.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • 5b2a1e1 • 3 files, +18/-2
Let the invite link actually arrive on ChatInviteLinksPage (#3347)
ChatInviteLinksViewModel declares _supergroupId and _basicGroupId but never
assigns them, so Handle(UpdateSupergroupFullInfo) and
Handle(UpdateBasicGroupFullInfo) compared against 0 and never matched. The
"full info will arrive by push" fallback in OnNavigatedToAsync was therefore
dead, and InviteLink stayed null for every chat whose full info wasn't already
cached — which made the header's copy and share buttons dereference null.
Assign both ids, and fix the basic group branch, which asked for
GetBasicGroupFullInfo(supergroup.Id) inside the branch where TryGetSupergroup
had just failed: supergroup is null there, and it was passing a supergroup id
where a basic group id belongs.
InviteLink is also legitimately null for anyone who isn't the creator, since
TDLib only populates the primary link for them, so the header buttons are now
disabled while it is null instead of acting on nothing.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • 711f79a • 1 files, +64/-20
Recover from a damaged OCR model archive (#3348)
Extracting Ocr_x64.zip crashed the app with an InvalidDataException,
"The archive entry was compressed using an unsupported compression
method". The message is misleading: Inflater maps zlib's Z_DATA_ERROR
onto that string, and the failure came from mid-entry writes in
DeflateStream.CopyToAsyncStream rather than from ZipArchiveEntry.Open,
so the deflate data was corrupt or truncated, not compressed with a
method the reader doesn't support. Why the local copy was damaged is
not established.
ExtractModelAsync had no try/catch and RecognizeText is async void, so
the failure reached the app unhandled. Catch it, delete the archive so
that the next attempt downloads a fresh copy, delete the partially
written model so that the readiness check in EnsureReadyAsync doesn't
accept it, and report the extraction as unavailable.
Separately, _extractLock.Release() sat outside any finally, so a throw
during extraction leaked the semaphore for the rest of the session:
every later extraction then returned early at Wait(0), leaving OCR
broken until restart.
Reported by crash telemetry on 12.9.1.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
UnigramDev/Unigram/develop • 27d3ebc • 2 files, +98/-9
Implement BusinessHoursViewModel.ContinueImpl (#3349)
The Done button on Settings > Telegram Business > Opening Hours is bound to
BusinessFeatureViewModelBase.Continue, which calls the abstract ContinueImpl.
BusinessHoursViewModel never implemented it and threw NotImplementedException,
so pressing Done crashed the app every time.
Build a BusinessOpeningHours from the per-day ranges and send it with
SetBusinessOpeningHours, following the other business feature view models:
early-out when nothing changed, toast an error, otherwise go back. Override
HasChanged so the unsaved-changes prompt the base class already carries a
string for actually fires.
The time zone is now resolved on every navigation rather than only when the
user already had opening hours saved, falling back to the first zone at the
current UTC offset, since otherwise a first-time user would have none to send.
Reported by crash telemetry on 12.9.1.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • 4b1d459 • 2 files, +40/-11
Give the DWrite custom font collection a valid key (#3351)
CreateCustomFontCollection takes the key size in bytes, but it was given
path1.size() + path2.size(), the summed character length of the two package
paths (~240) — while the key itself is a two-element array of pointers, 16
bytes. DWrite copied ~240 bytes out of a 16-byte stack array on every
PlaceholderImageHelper construction, so the key it stored was partly unrelated
stack memory, and how much varied with the install path length.
The key also pointed at two locals that die when the function returns, so the
pointers DWrite kept were dangling by the time it could hand them back to
CreateEnumeratorFromKey. The paths and the pointer array now live inside the
CustomFontLoader, which stays alive as long as it is registered, and the size
passed is sizeof that array. CustomFontFileEnumerator honours collectionKeySize
instead of assuming two entries.
Close() now unregisters the loader it registered on the shared DWrite factory,
after releasing the collection built from it. The factory is process-wide, so
without this every window thread left a loader and a custom collection on it for
the rest of the session.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
Implement BusinessHoursViewModel.ContinueImpl (#3349)
The Done button on Settings > Telegram Business > Opening Hours is bound to
BusinessFeatureViewModelBase.Continue, which calls the abstract ContinueImpl.
BusinessHoursViewModel never implemented it and threw NotImplementedException,
so pressing Done crashed the app every time.
Build a BusinessOpeningHours from the per-day ranges and send it with
SetBusinessOpeningHours, following the other business feature view models:
early-out when nothing changed, toast an error, otherwise go back. Override
HasChanged so the unsaved-changes prompt the base class already carries a
string for actually fires.
The time zone is now resolved on every navigation rather than only when the
user already had opening hours saved, falling back to the first zone at the
current UTC offset, since otherwise a first-time user would have none to send.
Reported by crash telemetry on 12.9.1.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • 4b1d459 • 2 files, +40/-11
Give the DWrite custom font collection a valid key (#3351)
CreateCustomFontCollection takes the key size in bytes, but it was given
path1.size() + path2.size(), the summed character length of the two package
paths (~240) — while the key itself is a two-element array of pointers, 16
bytes. DWrite copied ~240 bytes out of a 16-byte stack array on every
PlaceholderImageHelper construction, so the key it stored was partly unrelated
stack memory, and how much varied with the install path length.
The key also pointed at two locals that die when the function returns, so the
pointers DWrite kept were dangling by the time it could hand them back to
CreateEnumeratorFromKey. The paths and the pointer array now live inside the
CustomFontLoader, which stays alive as long as it is registered, and the size
passed is sizeof that array. CustomFontFileEnumerator honours collectionKeySize
instead of assuming two entries.
Close() now unregisters the loader it registered on the shared DWrite factory,
after releasing the collection built from it. The factory is process-wide, so
without this every window thread left a loader and a custom collection on it for
the rest of the session.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
UnigramDev/Unigram/develop • 4425c24 • 1 files, +13/-5
Recompute the message index after removing it from the collection (#3352)
InsertMessageInOrder moves a message by removing it and reinserting it at the
index NextIndexOf computed against the collection as it was *before* the
removal, compensating for the single row that goes away. MessageCollection
removes more than one row: RemoveItem also drops the date or topic separator
that the removal orphans, so a message that was the only one of its day takes
its header with it and the collection shrinks by two. If the message is not the
last row -- a sponsored message below it, say -- the adjusted index is then past
the end and Insert throws ArgumentOutOfRangeException.
Reported by crash telemetry on 12.9.1, reached when a locally pending message
completes: PendingMessage_Completed -> InsertMessage -> InsertMessageInOrder.
Ask NextIndexOf again after the removal rather than trusting the pre-removal
index; clamping would have put the message in the wrong place instead. The
force branch had the same defect -- it reinserted at oldIndex, which is out of
bounds once the header ahead of the message is gone -- so both paths now share
one helper.
Not built: the file parses with Roslyn, nothing more.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
Recompute the message index after removing it from the collection (#3352)
InsertMessageInOrder moves a message by removing it and reinserting it at the
index NextIndexOf computed against the collection as it was *before* the
removal, compensating for the single row that goes away. MessageCollection
removes more than one row: RemoveItem also drops the date or topic separator
that the removal orphans, so a message that was the only one of its day takes
its header with it and the collection shrinks by two. If the message is not the
last row -- a sponsored message below it, say -- the adjusted index is then past
the end and Insert throws ArgumentOutOfRangeException.
Reported by crash telemetry on 12.9.1, reached when a locally pending message
completes: PendingMessage_Completed -> InsertMessage -> InsertMessageInOrder.
Ask NextIndexOf again after the removal rather than trusting the pre-removal
index; clamping would have put the message in the wrong place instead. The
force branch had the same defect -- it reinserted at oldIndex, which is out of
bounds once the header ahead of the message is gone -- so both paths now share
one helper.
Not built: the file parses with Roslyn, nothing more.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
UnigramDev/Unigram/chat-preview-teardown • 158f942 • 4 files, +3/-16
Drop the idempotence flag from Deactivate
The handler detaches from both Closed and Unloaded before it tears anything
down, so whichever event arrives first makes the other unreachable and
Deactivate cannot be entered twice from here. The flag was dead, and it
applied to every ChatView rather than the preview - a second Deactivate
elsewhere would have become a silent no-op.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
Drop the idempotence flag from Deactivate
The handler detaches from both Closed and Unloaded before it tears anything
down, so whichever event arrives first makes the other unreachable and
Deactivate cannot be entered twice from here. The flag was dead, and it
applied to every ChatView rather than the preview - a second Deactivate
elsewhere would have become a silent no-op.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
UnigramDev/Unigram/develop • de28263 • 1 files, +39/-6
Stop the gallery from taking input while it closes (#3354)
Closing the gallery is animated: OnBackRequestedOverride calls Unload()
synchronously, which drops the data context, while Hide() only runs when the
closing animation completes. In between the window is still visible and still
hit-testable, so a click on a caption hyperlink ran Caption_TextEntityClick
with a null ViewModel and crashed in the MessageDelegate constructor
(NullReferenceException, reported by crash telemetry on 12.9.1). The caption
sits outside ScrollingHost, so the tap-to-close handler never consumes it.
Unload() now clears IsHitTestVisible, which closes the whole "input after
unload" family instead of one handler at a time; Caption_TextEntityClick is
guarded as well, because a focused hyperlink can still be invoked from the
keyboard or by automation, neither of which hit tests.
Also fix the same method leaving the window on screen forever: the connected
animation's Completed was subscribed before TryStart, and when TryStart failed
translate became false, so neither Hide() was reachable. Hide() now runs off
the fade batch, which is started on every path, and Completed is subscribed
only when the animation actually started.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • 162781d • 3 files, +37/-1
Detach the emoji and sticker drawers from their collections (#3355)
InvalidComObjectException — "COM object that has been separated from its
underlying RCW cannot be used" — reported by crash telemetry on 12.9.1,
raised from MvxObservableCollection.ReplaceWith inside
EmojiDrawerViewModel.Update.
EmojiDrawer's CollectionViewSource and its two toolbars bind straight to the
view model's collections, so their native peers subscribe to CollectionChanged.
Deactivate() only called Bindings.StopTracking(), which stops the bindings from
updating but leaves the value they already pushed in place, and it runs right
before XamlMarkupHelper.UnloadObject destroys those peers. The view model
subscribes to the aggregator in its constructor and Handle queues Update on the
UI thread, so a queued update outlives the drawer and raises into the dead
peers.
Clear the collection sources in Deactivate so nothing native is still attached
once the peers go away. StickerDrawer has the same shape and the same terminal
Deactivate contract, so it gets the same treatment.
EmojiMenuFlyout never deactivated its drawer at all, and only one of its three
constructors subscribed to Popup.Closed; move that subscription into Initialize
and tear the drawer down there, matching what StickerPanel does when it unloads
a drawer.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
Stop the gallery from taking input while it closes (#3354)
Closing the gallery is animated: OnBackRequestedOverride calls Unload()
synchronously, which drops the data context, while Hide() only runs when the
closing animation completes. In between the window is still visible and still
hit-testable, so a click on a caption hyperlink ran Caption_TextEntityClick
with a null ViewModel and crashed in the MessageDelegate constructor
(NullReferenceException, reported by crash telemetry on 12.9.1). The caption
sits outside ScrollingHost, so the tap-to-close handler never consumes it.
Unload() now clears IsHitTestVisible, which closes the whole "input after
unload" family instead of one handler at a time; Caption_TextEntityClick is
guarded as well, because a focused hyperlink can still be invoked from the
keyboard or by automation, neither of which hit tests.
Also fix the same method leaving the window on screen forever: the connected
animation's Completed was subscribed before TryStart, and when TryStart failed
translate became false, so neither Hide() was reachable. Hide() now runs off
the fade batch, which is started on every path, and Completed is subscribed
only when the animation actually started.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • 162781d • 3 files, +37/-1
Detach the emoji and sticker drawers from their collections (#3355)
InvalidComObjectException — "COM object that has been separated from its
underlying RCW cannot be used" — reported by crash telemetry on 12.9.1,
raised from MvxObservableCollection.ReplaceWith inside
EmojiDrawerViewModel.Update.
EmojiDrawer's CollectionViewSource and its two toolbars bind straight to the
view model's collections, so their native peers subscribe to CollectionChanged.
Deactivate() only called Bindings.StopTracking(), which stops the bindings from
updating but leaves the value they already pushed in place, and it runs right
before XamlMarkupHelper.UnloadObject destroys those peers. The view model
subscribes to the aggregator in its constructor and Handle queues Update on the
UI thread, so a queued update outlives the drawer and raises into the dead
peers.
Clear the collection sources in Deactivate so nothing native is still attached
once the peers go away. StickerDrawer has the same shape and the same terminal
Deactivate contract, so it gets the same treatment.
EmojiMenuFlyout never deactivated its drawer at all, and only one of its three
constructors subscribed to Popup.Closed; move that subscription into Initialize
and tear the drawer down there, matching what StickerPanel does when it unloads
a drawer.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
UnigramDev/Unigram/highlight-quote-mismatch • ae11bc5 • 2 files, +14/-1
Only locate a quote in the text the bubble actually rendered
MessageBubble.Highlight walks message.Text.Paragraphs and asks Message for the
block rendering each one, pairing an index into the model with an index into
the view. Nothing guaranteed the two describe the same text, and GetBlock
returns null when no range covers the index, so the paragraph dereference a few
lines later threw.
MessageAnimatedEmoji is the deterministic case. The message is a plain text
message that TDLib re-types in the API layer, so it is quotable everywhere and
the quote rides on the replying message; searchQuote compares text only and
GetCaption returns the emoji, so the quote always matches. But UpdateMessageText
excludes it from SetText and Recycle clears the ranges, so every GetBlock is
null. A translation or a summary reaches the same state differently: it is an
independent StyledText, and since entities are dropped in translation a
blockquote that forced a paragraph break in the original no longer does, so the
rendered text can have fewer paragraphs than the model.
Compare the two by reference before entering the branch, rather than guarding
the null it produces. The null only appears when the paragraph counts differ;
when they happen to match but the content does not, the old code drew the
highlight in the wrong places instead of crashing, and that is silent. Falling
back to the whole-bubble rect is also the right visual: content that never
reaches SetText is drawn by Media, which the fallback already targets.
Reported by crash telemetry on 12.9.1.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
Only locate a quote in the text the bubble actually rendered
MessageBubble.Highlight walks message.Text.Paragraphs and asks Message for the
block rendering each one, pairing an index into the model with an index into
the view. Nothing guaranteed the two describe the same text, and GetBlock
returns null when no range covers the index, so the paragraph dereference a few
lines later threw.
MessageAnimatedEmoji is the deterministic case. The message is a plain text
message that TDLib re-types in the API layer, so it is quotable everywhere and
the quote rides on the replying message; searchQuote compares text only and
GetCaption returns the emoji, so the quote always matches. But UpdateMessageText
excludes it from SetText and Recycle clears the ranges, so every GetBlock is
null. A translation or a summary reaches the same state differently: it is an
independent StyledText, and since entities are dropped in translation a
blockquote that forced a paragraph break in the original no longer does, so the
rendered text can have fewer paragraphs than the model.
Compare the two by reference before entering the branch, rather than guarding
the null it produces. The null only appears when the paragraph counts differ;
when they happen to match but the content does not, the old code drew the
highlight in the wrong places instead of crashing, and that is silent. Falling
back to the whole-bubble rect is also the right visual: content that never
reaches SetText is drawn by Media, which the fallback already targets.
Reported by crash telemetry on 12.9.1.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
UnigramDev/Unigram/localised-error-messages • 33f31b3 • 1 files, +75/-0
Translate more localised system error messages
Windows returns system error text in the user's language, so one fault
arrives at the crash backend as a different message per locale and splits
into a separate group for each. TranslateText maps those back onto the
canonical English wording before the report is sent, and it only ever
covers the languages someone has already looked at.
This adds the languages seen in recent reports for the faults it already
handles, and the faults it did not handle at all: file sharing violations,
disk full, paging file too small, insufficient system resources, service
disabled, data area too small, invalid operation identifier, out of range
access, interface not registered, invalid registry value and missing
Unicode mapping.
Every string maps onto the wording the same fault produces on an English
system, or onto the documented Windows text. The handful that could not
be established that way are left untranslated.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
Translate more localised system error messages
Windows returns system error text in the user's language, so one fault
arrives at the crash backend as a different message per locale and splits
into a separate group for each. TranslateText maps those back onto the
canonical English wording before the report is sent, and it only ever
covers the languages someone has already looked at.
This adds the languages seen in recent reports for the faults it already
handles, and the faults it did not handle at all: file sharing violations,
disk full, paging file too small, insufficient system resources, service
disabled, data area too small, invalid operation identifier, out of range
access, interface not registered, invalid registry value and missing
Unicode mapping.
Every string maps onto the wording the same fault produces on an English
system, or onto the documented Windows text. The handful that could not
be established that way are left untranslated.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
UnigramDev/Unigram/develop • 06e29f0 • 3 files, +25/-7
Tear down the chat preview from the flyout's Closed event (#3353)
* Tear down the chat preview from the flyout's Closed event
ChatCell.ShowPreview hosts a second ChatView inside a MenuFlyout and hung its only
teardown on chatView.Unloaded, which never fires for a flyout-hosted view: the log
tail of the crash runs for another 109 seconds without the handler's "Unloaded"
line ever appearing. ChatView.OnCollectionChanged therefore stays subscribed to the
view model's Items after the flyout's XAML peers are gone, and the next collection
change reads Messages.ItemsPanelRoot through a separated RCW, throwing
InvalidComObjectException.
Teardown now hangs off the flyout's own Closed event, with Unloaded kept as a
secondary trigger, and ChatView.Deactivate ignores a second call so the order of
the two doesn't matter. The two forum topic cells already tore down from
flyout.Closing and so never had the hole; they move to Closed for a single pattern.
Reported by crash telemetry on 12.9.1.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
Tear down the chat preview from the flyout's Closed event (#3353)
* Tear down the chat preview from the flyout's Closed event
ChatCell.ShowPreview hosts a second ChatView inside a MenuFlyout and hung its only
teardown on chatView.Unloaded, which never fires for a flyout-hosted view: the log
tail of the crash runs for another 109 seconds without the handler's "Unloaded"
line ever appearing. ChatView.OnCollectionChanged therefore stays subscribed to the
view model's Items after the flyout's XAML peers are gone, and the next collection
change reads Messages.ItemsPanelRoot through a separated RCW, throwing
InvalidComObjectException.
Teardown now hangs off the flyout's own Closed event, with Unloaded kept as a
secondary trigger, and ChatView.Deactivate ignores a second call so the order of
the two doesn't matter. The two forum topic cells already tore down from
flyout.Closing and so never had the hole; they move to Closed for a single pattern.
Reported by crash telemetry on 12.9.1.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
UnigramDev/Unigram/localised-error-messages • 7508906 • 1 files, +34/-0
Normalize the ASTA "application is busy" message
RPC_E_SERVERCALL_RETRYLATER comes with a detail sentence naming the ASTA
thread that was timed out. The id is different on every hang, and the
sentence is localized, so one fault reports under a new group each time
and splits again per language.
Rewrite it to a canonical English sentence keeping only the IID and the
method index, which are the parts that say which call hung. The GUID and
the number after it are matched structurally, since the labels around
them are translated. Anything the pattern does not match is left alone.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
Normalize the ASTA "application is busy" message
RPC_E_SERVERCALL_RETRYLATER comes with a detail sentence naming the ASTA
thread that was timed out. The id is different on every hang, and the
sentence is localized, so one fault reports under a new group each time
and splits again per language.
Rewrite it to a canonical English sentence keeping only the IID and the
method index, which are the parts that say which call hung. The GUID and
the number after it are matched structurally, since the labels around
them are translated. Anything the pattern does not match is left alone.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
UnigramDev/Unigram/capture-popup-focus • 8896627 • 1 files, +15/-13
Track focus rather than window activation in ChooseCapturePopup
The popup refreshes its list of capturable windows when the user comes back
from arranging the one they want to share. It reached that signal through
WindowContext.ForXamlRoot(XamlRoot).Activated, subscribed in Loaded and
detached in Unloaded, and both halves are unsafe:
- ForXamlRoot is a lookup in a dictionary the window is removed from when its
view is consolidated, even though the XAML tree is still alive, so it returns
null and the subscribe throws.
- XamlRoot is null by the time the popup unloads, and can throw on access, so
the detach throws too - and when it does, the handler is left attached to a
WindowContext that outlives the popup.
Focus tracks window activation just as reliably and needs neither. GotFocus is
raised when the window is activated and LostFocus when it is deactivated, so a
lost-then-got pair is the round trip worth refreshing on; a bare GotFocus is
not, since one arrives simply from opening the popup and enumerating every
window on the system is not free.
This is also the only ForXamlRoot call site in the app that dereferenced the
result directly - every other one assigns to a local and checks it.
Reported by crash telemetry on 12.9.1.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
Track focus rather than window activation in ChooseCapturePopup
The popup refreshes its list of capturable windows when the user comes back
from arranging the one they want to share. It reached that signal through
WindowContext.ForXamlRoot(XamlRoot).Activated, subscribed in Loaded and
detached in Unloaded, and both halves are unsafe:
- ForXamlRoot is a lookup in a dictionary the window is removed from when its
view is consolidated, even though the XAML tree is still alive, so it returns
null and the subscribe throws.
- XamlRoot is null by the time the popup unloads, and can throw on access, so
the detach throws too - and when it does, the handler is left attached to a
WindowContext that outlives the popup.
Focus tracks window activation just as reliably and needs neither. GotFocus is
raised when the window is activated and LostFocus when it is deactivated, so a
lost-then-got pair is the round trip worth refreshing on; a bare GotFocus is
not, since one arrives simply from opening the popup and enumerating every
window on the system is not free.
This is also the only ForXamlRoot call site in the app that dereferenced the
result directly - every other one assigns to a local and checks it.
Reported by crash telemetry on 12.9.1.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
UnigramDev/Unigram/develop • d78de99 • 1 files, +15/-13
Track focus rather than window activation in ChooseCapturePopup (#3359)
The popup refreshes its list of capturable windows when the user comes back
from arranging the one they want to share. It reached that signal through
WindowContext.ForXamlRoot(XamlRoot).Activated, subscribed in Loaded and
detached in Unloaded, and both halves are unsafe:
- ForXamlRoot is a lookup in a dictionary the window is removed from when its
view is consolidated, even though the XAML tree is still alive, so it returns
null and the subscribe throws.
- XamlRoot is null by the time the popup unloads, and can throw on access, so
the detach throws too - and when it does, the handler is left attached to a
WindowContext that outlives the popup.
Focus tracks window activation just as reliably and needs neither. GotFocus is
raised when the window is activated and LostFocus when it is deactivated, so a
lost-then-got pair is the round trip worth refreshing on; a bare GotFocus is
not, since one arrives simply from opening the popup and enumerating every
window on the system is not free.
This is also the only ForXamlRoot call site in the app that dereferenced the
result directly - every other one assigns to a local and checks it.
Reported by crash telemetry on 12.9.1.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
Track focus rather than window activation in ChooseCapturePopup (#3359)
The popup refreshes its list of capturable windows when the user comes back
from arranging the one they want to share. It reached that signal through
WindowContext.ForXamlRoot(XamlRoot).Activated, subscribed in Loaded and
detached in Unloaded, and both halves are unsafe:
- ForXamlRoot is a lookup in a dictionary the window is removed from when its
view is consolidated, even though the XAML tree is still alive, so it returns
null and the subscribe throws.
- XamlRoot is null by the time the popup unloads, and can throw on access, so
the detach throws too - and when it does, the handler is left attached to a
WindowContext that outlives the popup.
Focus tracks window activation just as reliably and needs neither. GotFocus is
raised when the window is activated and LostFocus when it is deactivated, so a
lost-then-got pair is the round trip worth refreshing on; a bare GotFocus is
not, since one arrives simply from opening the popup and enumerating every
window on the system is not free.
This is also the only ForXamlRoot call site in the app that dereferenced the
result directly - every other one assigns to a local and checks it.
Reported by crash telemetry on 12.9.1.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
UnigramDev/Unigram/localised-error-messages • 23f2cb7 • 1 files, +11/-4
Match the HRESULT suffix instead of the first parenthesis
TranslateMessage cut each line at the first '(' so that the HRESULT .NET
appends to a COM message would not stop the sentence in front of it matching.
The sentence can contain parentheses of its own, though, and across the
recorded reports that boundary lands mid-sentence far more often than it lands
on the suffix - in TDLib assert text, in EETypeRva casts, in captured stack
traces. It only ever worked because a head that had been truncated matched
nothing and was concatenated back unchanged.
Match the suffix itself, anchored at the end. Nothing that previously reached
TranslateText stops reaching it, and the Portuguese RPC_E_WRONG_THREAD message
can now be mapped: its text reads "empacotada (marshall) para outro módulo",
so the first parenthesis was inside the sentence.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
Match the HRESULT suffix instead of the first parenthesis
TranslateMessage cut each line at the first '(' so that the HRESULT .NET
appends to a COM message would not stop the sentence in front of it matching.
The sentence can contain parentheses of its own, though, and across the
recorded reports that boundary lands mid-sentence far more often than it lands
on the suffix - in TDLib assert text, in EETypeRva casts, in captured stack
traces. It only ever worked because a head that had been truncated matched
nothing and was concatenated back unchanged.
Match the suffix itself, anchored at the end. Nothing that previously reached
TranslateText stops reaching it, and the Portuguese RPC_E_WRONG_THREAD message
can now be mapped: its text reads "empacotada (marshall) para outro módulo",
so the first parenthesis was inside the sentence.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
UnigramDev/Unigram/hresult-message-lookup • 5b288a1 • 1 files, +133/-9
Key localised error messages on the HRESULT, not on the sentence
Windows returns system error text in the user's language, so a single fault
lands in a different crash group in every locale. Matching those sentences one
by one only ever covers the languages somebody has already noticed, and only
after the group has fragmented. The HRESULT behind them is the same number
everywhere.
ProcessException now passes the exception's type name and HResult into
TranslateMessage, and the first line is rebuilt from a code lookup when the
type is Exception or COMException. The first line is the only one that can be
system text - a WinRT error puts its originating description on a line of its
own after it - and it is also the only part the group hash reads.
Any other type keeps the sentence match. Managed exceptions carry generic
codes: NullReferenceException is E_POINTER, ArgumentException is E_INVALIDARG,
InvalidCastException is E_NOINTERFACE. Rewriting those from the code would
replace .NET's own text with system wording and merge unrelated groups.
FatalError has no HResult of its own, so there the code is read back out of the
"(Exception from HRESULT: 0x...)" suffix when .NET appended one, and otherwise
nothing changes.
The sentence table stays. It is the fallback for every code the lookup does not
list, and it is the only cover for the DirectWrite and Direct2D families:
CoreApplication.UnhandledErrorDetected flattens those to E_FAIL while the
message keeps the original wording, so the code cannot be taken at its word.
E_FAIL and the COR_E_* codes are left out of the lookup for that reason.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
Key localised error messages on the HRESULT, not on the sentence
Windows returns system error text in the user's language, so a single fault
lands in a different crash group in every locale. Matching those sentences one
by one only ever covers the languages somebody has already noticed, and only
after the group has fragmented. The HRESULT behind them is the same number
everywhere.
ProcessException now passes the exception's type name and HResult into
TranslateMessage, and the first line is rebuilt from a code lookup when the
type is Exception or COMException. The first line is the only one that can be
system text - a WinRT error puts its originating description on a line of its
own after it - and it is also the only part the group hash reads.
Any other type keeps the sentence match. Managed exceptions carry generic
codes: NullReferenceException is E_POINTER, ArgumentException is E_INVALIDARG,
InvalidCastException is E_NOINTERFACE. Rewriting those from the code would
replace .NET's own text with system wording and merge unrelated groups.
FatalError has no HResult of its own, so there the code is read back out of the
"(Exception from HRESULT: 0x...)" suffix when .NET appended one, and otherwise
nothing changes.
The sentence table stays. It is the fallback for every code the lookup does not
list, and it is the only cover for the DirectWrite and Direct2D families:
CoreApplication.UnhandledErrorDetected flattens those to E_FAIL while the
message keeps the original wording, so the code cannot be taken at its word.
E_FAIL and the COR_E_* codes are left out of the lookup for that reason.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
UnigramDev/Unigram/localised-error-messages • ff66f60 • 1 files, +1/-1
Map ERROR_NOT_ENOUGH_MEMORY to its own English text
"Zur Verarbeitung dieses Befehls..." is 0x80070008, whose English ends "to
process this command"; it was returning 0x8007000E's "to complete this
operation" and so merged two distinct codes into one group.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
Map ERROR_NOT_ENOUGH_MEMORY to its own English text
"Zur Verarbeitung dieses Befehls..." is 0x80070008, whose English ends "to
process this command"; it was returning 0x8007000E's "to complete this
operation" and so merged two distinct codes into one group.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
morethanwords/tweb/master • c7f79a4 • 1 files, +3/-2
Resolve min peers by username
morethanwords/tweb/master • ce0600f • 4 files, +6/-4
Fix PWA manifest ID for Telegram link scopes
morethanwords/tweb/master • ee72f01 • 2 files, +119/-10
Fix folder recovery after state clear
morethanwords/tweb/master • 05424f9 • 300 files, +615/-615
Build
#webk
Resolve min peers by username
morethanwords/tweb/master • ce0600f • 4 files, +6/-4
Fix PWA manifest ID for Telegram link scopes
morethanwords/tweb/master • ee72f01 • 2 files, +119/-10
Fix folder recovery after state clear
morethanwords/tweb/master • 05424f9 • 300 files, +615/-615
Build
#webk
🫡3
UnigramDev/Unigram/searchcollection-diff-snapshot • d6fac75 • 2 files, +28/-5
Snapshot the collections before diffing them off-thread
SearchCollection computes its diff on the thread pool:
await incremental.LoadMoreItemsAsync(0);
var diff = await Task.Run(() => DiffUtil.CalculateDiff(this, source, ...));
Both arguments are collections the UI thread owns and is still appending to,
and CalculateDiff starts by copying them. So the pool thread runs Array.Copy
over a list whose backing array and count are being replaced underneath it, and
the reported ArgumentException comes straight out of Array.CopyImpl.
The guard that was meant to prevent this is a plain bool. UpdateImpl sets
_loading before awaiting, and LoadMoreItemsAsync returns early while it is set
- but LoadMoreItemsAsync also clears it when it finishes, so one that was
already in flight when UpdateImpl set it hands the door back while the diff is
still running. The next scroll then appends to the very collection being
copied. That interleaving needs a load to be in flight at the moment an update
starts, which is why it is rare and why the report shows half a minute of
continuous scrolling behind it.
Snapshot both on the UI thread and diff the snapshots. The diff only ever
needed the sequences, the indices it produces stay valid because items are
appended rather than inserted, and nothing else has to be synchronised.
The same lines were copied into ChatTextBox's autocomplete, so all four sites
are changed; only the first is evidenced by a crash.
Reported by crash telemetry on 12.9.1.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
Snapshot the collections before diffing them off-thread
SearchCollection computes its diff on the thread pool:
await incremental.LoadMoreItemsAsync(0);
var diff = await Task.Run(() => DiffUtil.CalculateDiff(this, source, ...));
Both arguments are collections the UI thread owns and is still appending to,
and CalculateDiff starts by copying them. So the pool thread runs Array.Copy
over a list whose backing array and count are being replaced underneath it, and
the reported ArgumentException comes straight out of Array.CopyImpl.
The guard that was meant to prevent this is a plain bool. UpdateImpl sets
_loading before awaiting, and LoadMoreItemsAsync returns early while it is set
- but LoadMoreItemsAsync also clears it when it finishes, so one that was
already in flight when UpdateImpl set it hands the door back while the diff is
still running. The next scroll then appends to the very collection being
copied. That interleaving needs a load to be in flight at the moment an update
starts, which is why it is rare and why the report shows half a minute of
continuous scrolling behind it.
Snapshot both on the UI thread and diff the snapshots. The diff only ever
needed the sequences, the indices it produces stay valid because items are
appended rather than inserted, and nothing else has to be synchronised.
The same lines were copied into ChatTextBox's autocomplete, so all four sites
are changed; only the first is evidenced by a crash.
Reported by crash telemetry on 12.9.1.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
UnigramDev/Unigram/develop • 7a026d5 • 3 files, +14/-5
Read the name, not FileType, to know what a file is
A file dragged straight out of a ZIP archive arrives with an empty FileType,
though its Name still carries the extension. HasExtension compared FileType and
so answered false for everything in one.
That decided more than it looks. StorageMedia.CreateAsync dispatches on it
before probing, so a photo out of a ZIP fell through to StorageDocument and was
sent as a file: no thumbnail, no album, no compression. GetItemsView uses it for
the WEBP split that works around the server ignoring force_file inside an album,
so a WEBP out of a ZIP would have stayed in its album and failed the whole
upload -- the exact failure that workaround exists to prevent. GenerationService
uses it to pick the audio generation mode.
HasExtension now falls back to Name when FileType is empty, rather than always
reading Name, which keeps the exact-match behaviour everywhere FileType is
populated. It cannot turn a correct false into a true either: a file with no
extension has none in its name.
Two more places read FileType directly and had the same blind spot.
MessageFactory decides whether a WEBP might be a sticker and whether a TGS
might be an animation. EditMediaPopup decides whether to open the trim toolbar
or the crop one, which meant a video out of a ZIP got the photo editor and
InitializeVideo never ran. Both go through HasExtension now.
EditMediaPopup's matters twice over: ImageHelper.OpenReadAsync already carries a
comment saying it and EditMediaPopup must agree about what a video is, and
fixing only HasExtension would have made them disagree for exactly these files.
The remaining FileType reads are TDLib's StorageStatisticsByFileType and
NetworkStatisticsEntryFile, which are unrelated types.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • b5cdb0e • 1 files, +4/-3
Say what the trim strip scales to
Math.Max(ratioY, ratioY) compares a value with itself and ratioX above it was
computed and never used, which reads like a typo for Math.Max(ratioX, ratioY).
It is not one, and making that change would break the strip.
The 40 is the height of the filmstrip, not a box to fit inside. Each frame is a
Border in a row of grid columns filling 296 pixels, so it has to be 40 tall
with its width following the aspect -- which is what dividing by pixelHeight
gives. Taking the larger ratio would pin a portrait video's width to 40 instead
and leave every frame taller than the strip.
So the scale is written as what it is, and the dead ratioX goes with the
no-op Max.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
Read the name, not FileType, to know what a file is
A file dragged straight out of a ZIP archive arrives with an empty FileType,
though its Name still carries the extension. HasExtension compared FileType and
so answered false for everything in one.
That decided more than it looks. StorageMedia.CreateAsync dispatches on it
before probing, so a photo out of a ZIP fell through to StorageDocument and was
sent as a file: no thumbnail, no album, no compression. GetItemsView uses it for
the WEBP split that works around the server ignoring force_file inside an album,
so a WEBP out of a ZIP would have stayed in its album and failed the whole
upload -- the exact failure that workaround exists to prevent. GenerationService
uses it to pick the audio generation mode.
HasExtension now falls back to Name when FileType is empty, rather than always
reading Name, which keeps the exact-match behaviour everywhere FileType is
populated. It cannot turn a correct false into a true either: a file with no
extension has none in its name.
Two more places read FileType directly and had the same blind spot.
MessageFactory decides whether a WEBP might be a sticker and whether a TGS
might be an animation. EditMediaPopup decides whether to open the trim toolbar
or the crop one, which meant a video out of a ZIP got the photo editor and
InitializeVideo never ran. Both go through HasExtension now.
EditMediaPopup's matters twice over: ImageHelper.OpenReadAsync already carries a
comment saying it and EditMediaPopup must agree about what a video is, and
fixing only HasExtension would have made them disagree for exactly these files.
The remaining FileType reads are TDLib's StorageStatisticsByFileType and
NetworkStatisticsEntryFile, which are unrelated types.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • b5cdb0e • 1 files, +4/-3
Say what the trim strip scales to
Math.Max(ratioY, ratioY) compares a value with itself and ratioX above it was
computed and never used, which reads like a typo for Math.Max(ratioX, ratioY).
It is not one, and making that change would break the strip.
The 40 is the height of the filmstrip, not a box to fit inside. Each frame is a
Border in a row of grid columns filling 296 pixels, so it has to be 40 tall
with its width following the aspect -- which is what dividing by pixelHeight
gives. Taking the larger ratio would pin a portrait video's width to 40 instead
and leave every frame taller than the strip.
So the scale is written as what it is, and the dead ratioX goes with the
no-op Max.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
UnigramDev/Unigram/develop • 6df8bf5 • 38 files, +269/-1546
Consume libvlc and webrtc as prebuilt binaries
Both were built from source as part of setting up a checkout. libvlc meant an 8.8 GB submodule,
Docker and the VideoLAN contrib toolchain; webrtc meant depot_tools, a ~1.5 hour sync, ~20 GB of
disk and a multi-hour build, ending in absolute paths to C:\webrtc hardcoded in two project
files. Both change about once a year.
They are vcpkg overlay ports now, downloading an archive for the architecture being built and
verifying it against a recorded SHA512. The sources, the scripts that produce the archives and
the patches they need moved to UnigramDev/deps, and the archives are published as releases there,
each naming the fork commit it was built from.
The libvlc port also has to place the plugin tree, which cannot be flattened: the plugins' paths
are recorded in the generated plugins.dat cache, so they reach the package as
plugins\<category>\<name>.dll through Content items rather than the flat copy the other
dependencies use.
VcpkgAutoLink is off. It put every .lib in the installed tree on every link line, which since
webrtc arrived meant Telegram.Native scanning a 339 MB static library it has no reference into.
Each project now names what it links, derived from what the binaries actually import.
tgcalls and Telegram.Native.Calls include libyuv by its full path. webrtc carries a patched copy
whose ConvertToI420 takes three extra parameters, and vcpkg's libyuv port supplies the stock one,
so a plain <libyuv.h> resolved to whichever include directory came first -- which was decided by
a hardcoded path that no longer exists. The three compiled files that used the ambiguous form now
say which library they mean.
Libraries/vlc and Libraries/webrtc are gone, submodule included. The VLC checkout the former held
is an ordinary clone of the fork now, kept outside the repository.
The instructions for building without WebRTC go too. Removing the project reference and the
ENABLE_CALLS directive still compiles, but webrtc is listed in vcpkg.json, so the restore installs
it for the triplet either way -- the download that section existed to avoid is spent before any
project is built. Making it work again means a manifest feature, not a set of manual edits.
duplicated-libraries.md records what the app now carries more than one copy of -- two ffmpeg
builds, and opus and libyuv both as DLLs and inside webrtc.lib -- with the measurements behind it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • 51f817c • 4 files, +40/-20
Merge remote-tracking branch 'origin/develop' into develop
#unigram
Consume libvlc and webrtc as prebuilt binaries
Both were built from source as part of setting up a checkout. libvlc meant an 8.8 GB submodule,
Docker and the VideoLAN contrib toolchain; webrtc meant depot_tools, a ~1.5 hour sync, ~20 GB of
disk and a multi-hour build, ending in absolute paths to C:\webrtc hardcoded in two project
files. Both change about once a year.
They are vcpkg overlay ports now, downloading an archive for the architecture being built and
verifying it against a recorded SHA512. The sources, the scripts that produce the archives and
the patches they need moved to UnigramDev/deps, and the archives are published as releases there,
each naming the fork commit it was built from.
The libvlc port also has to place the plugin tree, which cannot be flattened: the plugins' paths
are recorded in the generated plugins.dat cache, so they reach the package as
plugins\<category>\<name>.dll through Content items rather than the flat copy the other
dependencies use.
VcpkgAutoLink is off. It put every .lib in the installed tree on every link line, which since
webrtc arrived meant Telegram.Native scanning a 339 MB static library it has no reference into.
Each project now names what it links, derived from what the binaries actually import.
tgcalls and Telegram.Native.Calls include libyuv by its full path. webrtc carries a patched copy
whose ConvertToI420 takes three extra parameters, and vcpkg's libyuv port supplies the stock one,
so a plain <libyuv.h> resolved to whichever include directory came first -- which was decided by
a hardcoded path that no longer exists. The three compiled files that used the ambiguous form now
say which library they mean.
Libraries/vlc and Libraries/webrtc are gone, submodule included. The VLC checkout the former held
is an ordinary clone of the fork now, kept outside the repository.
The instructions for building without WebRTC go too. Removing the project reference and the
ENABLE_CALLS directive still compiles, but webrtc is listed in vcpkg.json, so the restore installs
it for the triplet either way -- the download that section existed to avoid is spent before any
project is built. Making it work again means a manifest feature, not a set of manual edits.
duplicated-libraries.md records what the app now carries more than one copy of -- two ffmpeg
builds, and opus and libyuv both as DLLs and inside webrtc.lib -- with the measurements behind it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
UnigramDev/Unigram/develop • 51f817c • 4 files, +40/-20
Merge remote-tracking branch 'origin/develop' into develop
#unigram
UnigramDev/Unigram/develop • 7496686 • 1 files, +121/-5
Stop system error messages from fragmenting crash groups (#3357)
* Translate more localised system error messages
Windows returns system error text in the user's language, so one fault
arrives at the crash backend as a different message per locale and splits
into a separate group for each. TranslateText maps those back onto the
canonical English wording before the report is sent, and it only ever
covers the languages someone has already looked at.
This adds the languages seen in recent reports for the faults it already
handles, and the faults it did not handle at all: file sharing violations,
disk full, paging file too small, insufficient system resources, service
disabled, data area too small, invalid operation identifier, out of range
access, interface not registered, invalid registry value and missing
Unicode mapping.
Every string maps onto the wording the same fault produces on an English
system, or onto the documented Windows text. The handful that could not
be established that way are left untranslated.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Normalize the ASTA "application is busy" message
RPC_E_SERVERCALL_RETRYLATER comes with a detail sentence naming the ASTA
thread that was timed out. The id is different on every hang, and the
sentence is localized, so one fault reports under a new group each time
and splits again per language.
Rewrite it to a canonical English sentence keeping only the IID and the
method index, which are the parts that say which call hung. The GUID and
the number after it are matched structurally, since the labels around
them are translated. Anything the pattern does not match is left alone.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Match the HRESULT suffix instead of the first parenthesis
TranslateMessage cut each line at the first '(' so that the HRESULT .NET
appends to a COM message would not stop the sentence in front of it matching.
The sentence can contain parentheses of its own, though, and across the
recorded reports that boundary lands mid-sentence far more often than it lands
on the suffix - in TDLib assert text, in EETypeRva casts, in captured stack
traces. It only ever worked because a head that had been truncated matched
nothing and was concatenated back unchanged.
Match the suffix itself, anchored at the end. Nothing that previously reached
TranslateText stops reaching it, and the Portuguese RPC_E_WRONG_THREAD message
can now be mapped: its text reads "empacotada (marshall) para outro módulo",
so the first parenthesis was inside the sentence.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Map ERROR_NOT_ENOUGH_MEMORY to its own English text
"Zur Verarbeitung dieses Befehls..." is 0x80070008, whose English ends "to
process this command"; it was returning 0x8007000E's "to complete this
operation" and so merged two distinct codes into one group.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
Stop system error messages from fragmenting crash groups (#3357)
* Translate more localised system error messages
Windows returns system error text in the user's language, so one fault
arrives at the crash backend as a different message per locale and splits
into a separate group for each. TranslateText maps those back onto the
canonical English wording before the report is sent, and it only ever
covers the languages someone has already looked at.
This adds the languages seen in recent reports for the faults it already
handles, and the faults it did not handle at all: file sharing violations,
disk full, paging file too small, insufficient system resources, service
disabled, data area too small, invalid operation identifier, out of range
access, interface not registered, invalid registry value and missing
Unicode mapping.
Every string maps onto the wording the same fault produces on an English
system, or onto the documented Windows text. The handful that could not
be established that way are left untranslated.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Normalize the ASTA "application is busy" message
RPC_E_SERVERCALL_RETRYLATER comes with a detail sentence naming the ASTA
thread that was timed out. The id is different on every hang, and the
sentence is localized, so one fault reports under a new group each time
and splits again per language.
Rewrite it to a canonical English sentence keeping only the IID and the
method index, which are the parts that say which call hung. The GUID and
the number after it are matched structurally, since the labels around
them are translated. Anything the pattern does not match is left alone.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Match the HRESULT suffix instead of the first parenthesis
TranslateMessage cut each line at the first '(' so that the HRESULT .NET
appends to a COM message would not stop the sentence in front of it matching.
The sentence can contain parentheses of its own, though, and across the
recorded reports that boundary lands mid-sentence far more often than it lands
on the suffix - in TDLib assert text, in EETypeRva casts, in captured stack
traces. It only ever worked because a head that had been truncated matched
nothing and was concatenated back unchanged.
Match the suffix itself, anchored at the end. Nothing that previously reached
TranslateText stops reaching it, and the Portuguese RPC_E_WRONG_THREAD message
can now be mapped: its text reads "empacotada (marshall) para outro módulo",
so the first parenthesis was inside the sentence.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Map ERROR_NOT_ENOUGH_MEMORY to its own English text
"Zur Verarbeitung dieses Befehls..." is 0x80070008, whose English ends "to
process this command"; it was returning 0x8007000E's "to complete this
operation" and so merged two distinct codes into one group.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
UnigramDev/Unigram/develop • 7d92ca7 • 1 files, +133/-9
Key localised error messages on the HRESULT, not on the sentence (#3360)
Windows returns system error text in the user's language, so a single fault
lands in a different crash group in every locale. Matching those sentences one
by one only ever covers the languages somebody has already noticed, and only
after the group has fragmented. The HRESULT behind them is the same number
everywhere.
ProcessException now passes the exception's type name and HResult into
TranslateMessage, and the first line is rebuilt from a code lookup when the
type is Exception or COMException. The first line is the only one that can be
system text - a WinRT error puts its originating description on a line of its
own after it - and it is also the only part the group hash reads.
Any other type keeps the sentence match. Managed exceptions carry generic
codes: NullReferenceException is E_POINTER, ArgumentException is E_INVALIDARG,
InvalidCastException is E_NOINTERFACE. Rewriting those from the code would
replace .NET's own text with system wording and merge unrelated groups.
FatalError has no HResult of its own, so there the code is read back out of the
"(Exception from HRESULT: 0x...)" suffix when .NET appended one, and otherwise
nothing changes.
The sentence table stays. It is the fallback for every code the lookup does not
list, and it is the only cover for the DirectWrite and Direct2D families:
CoreApplication.UnhandledErrorDetected flattens those to E_FAIL while the
message keeps the original wording, so the code cannot be taken at its word.
E_FAIL and the COR_E_* codes are left out of the lookup for that reason.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram
Key localised error messages on the HRESULT, not on the sentence (#3360)
Windows returns system error text in the user's language, so a single fault
lands in a different crash group in every locale. Matching those sentences one
by one only ever covers the languages somebody has already noticed, and only
after the group has fragmented. The HRESULT behind them is the same number
everywhere.
ProcessException now passes the exception's type name and HResult into
TranslateMessage, and the first line is rebuilt from a code lookup when the
type is Exception or COMException. The first line is the only one that can be
system text - a WinRT error puts its originating description on a line of its
own after it - and it is also the only part the group hash reads.
Any other type keeps the sentence match. Managed exceptions carry generic
codes: NullReferenceException is E_POINTER, ArgumentException is E_INVALIDARG,
InvalidCastException is E_NOINTERFACE. Rewriting those from the code would
replace .NET's own text with system wording and merge unrelated groups.
FatalError has no HResult of its own, so there the code is read back out of the
"(Exception from HRESULT: 0x...)" suffix when .NET appended one, and otherwise
nothing changes.
The sentence table stays. It is the fallback for every code the lookup does not
list, and it is the only cover for the DirectWrite and Direct2D families:
CoreApplication.UnhandledErrorDetected flattens those to E_FAIL while the
message keeps the original wording, so the code cannot be taken at its word.
E_FAIL and the COR_E_* codes are left out of the lookup for that reason.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
#unigram