fix: reclassify transient errors as non-fatal and fix top crashes

- main: record transient I/O, plugin-teardown, and non-crashing UI errors
  (network/websocket/timeout, MissingPluginException, image-load, layout
  overflow) as non-fatal, and add a grouping reason to async errors so
  distinct errors stop collapsing into one catch-all Crashlytics issue
- irc: guard IRCMessage.fromString against malformed/truncated lines that
  threw RangeError, with regression tests
- chat: wrap the emote menu in OverflowBox so it no longer overflows
  (RenderFlex) while the panel collapses
- chat: fix onBadgeError returning a List instead of a Map, which threw a
  Future.catchError type error during channel asset loads
- video: skip setAutoPipMode on Android < 12 where it isn't implemented

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Tommy Chow 2026-06-21 15:04:11 -04:00
parent 6c969b0d97
commit ef1b1ded5d
6 changed files with 156 additions and 70 deletions

View file

@ -974,6 +974,12 @@ class IRCMessage {
// Obtain the index to break off the tags.
final tagAndIrcMessageDivider = whole.indexOf(' ');
// Without a separating space there are no tags to slice and no message body
// to parse — bail out rather than throw on the substring/index calls below.
if (tagAndIrcMessageDivider == -1) {
return IRCMessage(raw: whole, command: Command.none, tags: const {});
}
// Parse the IRCv3 tags into a map. The tags section is everything between
// the leading '@' and the first space. We split on the *raw* string: a
// literal ';' is always a separator because escaped semicolons inside a
@ -1006,6 +1012,14 @@ class IRCMessage {
// Index 3 and beyond is the :message (word by word) that is sent by the user or empty depending on the command
final splitMessage = ircMessage.split(' ');
// A well-formed message is at least `<prefix> <COMMAND>`. A malformed or
// truncated line (e.g. from the recent-messages history API) can split into
// a single token, which would otherwise throw a RangeError on the
// splitMessage[0]/[1] accesses below.
if (splitMessage.length < 2) {
return IRCMessage(raw: whole, command: Command.none, tags: mappedTags);
}
// If the username exists, set it.
// tmi.twitch.tv means the message was sent by Twitch rather than a user, so will be irrelevant.
final prefix = splitMessage[0];