Commit graph

1312 commits

Author SHA1 Message Date
9ced2fe212 release v5.2.9
All checks were successful
Actions probe / probe (push) Successful in 0s
Release APK / Build & publish release (push) Successful in 8m10s
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-07 07:32:16 +02:00
3fb393166f Fix tag directory sorting and follow Client-Integrity.
Paginate tag (and ASC) lists via GQL with integrity retry; mint follow
integrity from a WebView because Dio /integrity tokens are bad_bot.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-07 07:32:16 +02:00
383e786479 release v5.2.8
All checks were successful
Actions probe / probe (push) Successful in 0s
Release APK / Build & publish release (push) Successful in 7m45s
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-07 03:06:09 +02:00
30848e6fe8 Add follow and unfollow for live and offline channels.
Long-press actions use Helix for follow status and web GQL mutations via the linked session.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-07 03:06:05 +02:00
318dc84dc5 Fix tag infinite scroll after GQL ASC first page.
All checks were successful
Actions probe / probe (push) Successful in 0s
Release APK / Build & publish release (push) Successful in 7m37s
GQL page-2 fails integrity, so fewest starts with GQL then continues via Helix tag filter with dedupe; empty Helix windows no longer end the list while a cursor remains.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-01 12:23:15 +02:00
5a787e0134 Fix fewest-viewers sort via GQL VIEWER_COUNT_ASC.
All checks were successful
Actions probe / probe (push) Successful in 0s
Release APK / Build & publish release (push) Successful in 8m16s
Helix directories are viewer-desc only; local reverse of top pages wrongly showed high-viewer streams for ascending sort.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-01 12:03:12 +02:00
47150d0ab3 Fix tag pagination and persist viewer sort.
All checks were successful
Actions probe / probe (push) Successful in 0s
Release APK / Build & publish release (push) Successful in 7m59s
Load tag (and language) directories via Helix with client-side tag matching so scroll can continue past the GQL integrity wall. Re-sort lists locally after each fetch and store streamViewerSort in Settings.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-01 11:41:25 +02:00
9d392d8d6a Add stream language filter and fix directory infinite scroll.
All checks were successful
Actions probe / probe (push) Successful in 0s
Release APK / Build & publish release (push) Successful in 7m41s
Settings multi-select filters Top/category/tag lists via Helix (with over-fetch for multiple languages). Larger Helix pages, safer cursors, and scroll near-end loading restore pagination; GQL page-2 integrity failures end the list cleanly.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-01 10:51:10 +02:00
cff29232c7 Fix NativeVideoStore isBlockingAds for release builds.
All checks were successful
Actions probe / probe (push) Successful in 0s
Release APK / Build & publish release (push) Successful in 8m20s
build_runner had stripped a hand-patched override from the generated store; implement the VideoPlayerInterface getter on the hand-written base class so assembleRelease succeeds.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-30 23:25:33 +02:00
d8247843c4 Add freeform tag search and viewer sort for live lists.
Some checks failed
Actions probe / probe (push) Successful in 0s
Release APK / Build & publish release (push) Failing after 24s
Helix cannot filter by tags or sort ascending; use Twitch GQL for Search→Tags→streams and Most/Fewest viewers on Top, category, and tag screens.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-30 23:22:56 +02:00
7863935fff ci: add temporary Actions probe workflow
All checks were successful
Actions probe / probe (push) Successful in 0s
Verify Forgejo schedules jobs for this repo before Obtainium release builds.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-30 11:36:53 +02:00
2775847c6d release v5.2.1
All checks were successful
Release APK / Build & publish release (push) Successful in 7m19s
Ship the first Obtainium build of this Forgejo fork with TwitchAdSolutions ad blocking.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-30 11:36:03 +02:00
a1b9a5e46d Add script to mint and upload Forgejo Obtainium release secrets.
Generates a local upload keystore and pushes Actions secrets without printing values, so Obtainium APK builds can be signed on desktop-release.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-30 11:34:44 +02:00
cdbccc23a8 Add Forgejo Actions release workflow for Obtainium APK updates.
Build a signed Flutter APK on desktop-release and publish versioned assets so private-repo Obtainium can track frosty-arm64-v8a-v*.apk.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-30 10:59:23 +02:00
1aae7438bb Add TwitchAdSolutions-style ad blocking for native and WebView players.
Some checks failed
CI / Analyze, test, and build Android (push) Has been cancelled
CI / Build iOS (push) Has been cancelled
Request playback with non-site player types and swap to backup Usher streams on stitched ad breaks; inject vendored vaft into the legacy WebView and expose a Settings toggle.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-30 10:55:50 +02:00
Tommy Chow
bb150ce42a chore(mcp): share dart server across grok, claude, and cursor
Keep Next DevTools in both project MCP files so Cursor matches Claude's .mcp.json.
2026-08-23 11:08:03 -07:00
Tommy Chow
c50b3c3c69 fix: correct typos and grammar in comments and login copy
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-17 19:07:42 -07:00
Tommy Chow
a6d7ae6916 chore(store): drop brand names from listing title and short copy
Play rejected Twitch, 7TV, BTTV, and FFZ in the visible listing fields. Keep iOS aligned with the same card.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-17 18:52:05 -07:00
Tommy Chow
96b3eca604 release v5.2.0
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-16 12:36:33 -07:00
Tommy Chow
70de25b29b build(deps): pick up compatible lockfile bumps
webview_flutter_android 4.14.0 is the only direct-ish change. Held
secure_storage / plus packages / wakelock 1.7 for the win32 pin.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-16 12:33:59 -07:00
Tommy Chow
1db8b95ba4 docs(agents): keep in-app notes to three consecutive versions
Drop from the oldest end so the file stays short without holes in
the version list.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-16 12:26:57 -07:00
Tommy Chow
fcd7a304e8 feat(video): make the native player the default
5.1.0 already serialized useNativePlayer: false, so existing installs
need a one-shot migrate. WebView stays as a legacy opt-out.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-16 12:21:44 -07:00
Tommy Chow
dc444810b1 docs(agents): split the release ritual into a repo-local playbook
The full release procedure was loading in every Frosty session. Same split
as the dotfiles resync playbook: always-on file stays gotchas, the ritual
lives in docs/release.md.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-16 11:06:26 -07:00
Tommy Chow
0937a1c5ad
build(deps): bump Flutter to 3.47.0 and Android Gradle stack (#590)
* build(deps): bump Flutter pin to 3.47.0 and Android Gradle stack

Pin Flutter 3.47.0 (Dart 3.13) as the CI source of truth and move the
Android toolchain to the 3.47 template versions: Gradle 9.3.1, AGP 9.1.0,
Kotlin 2.4.0.

App Gradle script matches the 3.47 template (drop kotlin-android /
kotlinOptions, use compilerOptions). Keep android.builtInKotlin=false and
android.newDsl=false so plugins that still apply KGP keep building.

AGP 9 requires libraries to compile against their dependencies' compileSdk;
force plugin modules to 36 via finalizeDsl. Ignore android/build/, which
AGP 9 now writes a problems report into.

analyze, test, and a release APK all passed. 16 KB zip alignment verified.

iOS UIScene is not in this commit: AppDelegate is customized
(CookieExtractorPlugin), so Flutter will not auto-migrate.

* ci: drop unused Claude Code GitHub Actions

Remove the PR review workflow and the @claude mention bot. Neither is
used anymore; the review job just failed this PR on an expired OAuth
token.

* feat(ios): adopt UIScene lifecycle for Flutter 3.47

Adopt UIApplicationSceneManifest and FlutterImplicitEngineDelegate in AppDelegate to support the UIScene lifecycle requirement on iOS.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-15 14:51:05 -07:00
Tommy Chow
0e26aefeb3 build(ios): refresh CocoaPods checksums and Firebase SPM pins
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-09 15:34:01 -07:00
Tommy Chow
cc772a7c90 fix(auth): request moderator OAuth scopes only on opt-in
Stop asking for mod scopes at normal login so existing core-only tokens
never hit a missing-permissions dialog. Gate moderated-channels Helix,
offer an Enable flow for mods/broadcasters, and upgrade atomically.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-09 15:33:06 -07:00
Tommy Chow
2d65aeec29 fix(settings): limit web-session dialog to the profile status icon
Tapping the display name or surrounding whitespace was opening the web-session dialog; only the checkmark/info icon should.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-09 15:18:06 -07:00
Tommy Chow
9888985deb fix(auth): list missing Twitch scopes in the re-login dialog
Existing tokens lack the newer mod scopes, so Helix 401s looked like a vague permissions failure. Cache scopes from /oauth2/validate and show which ones need re-authorization.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-09 14:48:55 -07:00
Tommy Chow
11cd05e6a8 docs: drop removed build_runner flag from AGENTS.md
`--delete-conflicting-outputs` was removed in build_runner 2.15 and is
now ignored with a warning; conflicting outputs are handled
automatically. Documenting it kept sending agents down a no-op path.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 13:18:10 -07:00
Tommy Chow
40733756ab build(deps): bump flutter pin to 3.44.9 and upgrade dependencies
The pubspec pinned Flutter 3.44.5 while the toolchain had moved to
3.44.9, so `flutter pub upgrade` failed version solving outright and no
dependency had been upgradable for a while. Bumping the pin unblocks
resolution (CI reads the same field via flutter-version-file).

Notable bumps: dio 5.9.2 -> 5.11.0, firebase_core 4.10 -> 4.13 (plus
crashlytics/analytics/performance), app_links 7.2.1, flutter_markdown_plus
1.0.12, intl, path_provider, webview_flutter, flutter_cache_manager.

dio 5.11 adds DioExceptionType.transformTimeout, which broke the
exhaustive switch in BaseApiClient._handleError. Grouped it with the
other timeout types and covered all four mappings with tests, which
were previously untested.

Raised the json_annotation floor to ^4.12.0 to silence a
json_serializable build warning; the resolved version was already 4.12.0.

flutter_secure_storage stays on v9 deliberately. v10.0.0 shipped in 5.0.0
and was reverted in 5.0.1 (ba28e09) while chasing the #484 startup freeze
on low-end Android 14, and upstream #1084 (unrecoverable NPE in
initialize() on Android 13) is still open. v9 pins win32 ^5, so
device_info_plus, package_info_plus and wakelock_plus are held at their
current majors too - their only breaking change was a win32 6.0.0 bump
that affects Windows only.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 13:17:46 -07:00
Tommy Chow
a21cf192eb ci: drop the dry-run failure notifier and run it monthly
Reverts the tracking-issue job from 601088b: a single maintainer watching
the Actions tab doesn't need a bot filing issues at them, and GitHub
already emails on scheduled workflow failure.

Also moves the heartbeat from weekly to monthly. Nothing it watches --
cert expiry, secret rotation, dependency drift -- moves on a weekly
timescale, and a dispatch run before cutting a release is the check that
actually matters.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 16:15:52 -07:00
Tommy Chow
601088b5c6 ci: bump ruby to 3.3 and report scheduled dry-run failures
fastlane warns on every run that it will soon require Ruby 3.3.0 or
newer; both release workflows pinned 3.2. Bumping now means that break
lands on a quiet commit instead of mid-release. fastlane 2.234 already
supports 3.3 and no gem in either lockfile is version-sensitive, so this
stands alone — the lockfiles are unchanged.

Also gives the weekly dry-run somewhere to report. It broke on the
release after 5.1.0 and stayed red for weeks without surfacing, which is
the failure mode of any heartbeat nobody reads: it looks green by
default. Scheduled failures now open a tracking issue, or comment on the
existing one for each week it stays red. Dispatched runs are excluded —
those are already in front of whoever started them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 16:04:15 -07:00
Tommy Chow
c939dc2790 ci: give the release dry-run an unused android version code
Play rejects a version code it has already seen, and between releases
pubspec's code is by definition already published — so every scheduled
dry-run died on "Version code 69 has already been used" before reaching
any of the credential, signing, or store validation it exists to check.
It has been failing on that since 5.1.0 shipped.

Overrides the build number with epoch seconds, which is always unused and
always increasing. The bundle is validate_only, never published, so the
value is discarded. Only downside is the dry-run can no longer catch a
forgotten version bump, which the real release catches anyway.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 15:43:51 -07:00
Tommy Chow
36aa5e7368 fix(video): regenerate video store bindings
880a9c9 added the handleAndroidPipChanged and _setPipActive actions to
VideoStoreBase without regenerating the mixin, so neither was wrapped by
the MobX action controller. Both still ran, but their observable writes
landed as separate notifications instead of one batched transaction —
extra rebuilds on every PiP transition. Analyze and the test suite pass
either way; only CI's generated-file diff check catches it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 15:43:35 -07:00
Tommy Chow
14591b3bf8 build(video): pull in native player low-latency HLS
Bumps better_native_video_player to the frosty-patches tip, which now
carries the Twitch low-latency work: #EXT-X-TWITCH-PREFETCH segments are
promoted into real segments, EXT-X-TARGETDURATION is rewritten to the true
~2s cadence so media3 stops starving on reloads, and the LoadControl no
longer parks playback ~5s behind after a rebuffer.

Takes native live latency on Android from ~9s to ~3s. iOS is unaffected —
AVFoundation parses HLS internally, so the same rewrite needs an
AVAssetResourceLoaderDelegate to intercept the playlist.

Note the readout: getLatencyToLive() measures distance to the playlist's
live edge, while Twitch's own "Latency To Broadcaster" stat adds ingest and
packaging on top, so the two aren't directly comparable.

Refs #573

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 15:20:52 -07:00
Tommy Chow
880a9c92aa fix(video): sync android pip state and playback across both players
Three related PiP gaps on Android:

`handleAndroidPipChanged` lived on `NativeVideoPlayerInterface`, and
`notifyPip` type-checked against it, so the WebView store never learned it
was in PiP — `_isInPipMode` stayed false for the whole session and the
overlay kept auto-hiding behind the PiP window. Moved to the shared
`VideoPlayerInterface` and implemented in both stores. The WebView store's
two JS PiP channel bodies collapse into one `_setPipActive` state machine,
now shared with the Android callback.

`NativeVideoStore` never called `_pip.setIsPlaying()`, so with the
`mediaOnlyPause` layout the PiP window's button was stuck on whatever glyph
it started with, and `onPipAction` — which toggles blind — could do the
opposite of what the button showed.

`VideoStore.handleAppResume()` did nothing in video mode, while the native
store calls `play()`. Backgrounding can suspend the webview's playback with
no way for the page to restart itself, leaving it dead until a manual
refresh. Resumes now unless the user was the one who paused.

Not fixed: PiP with the WebView player. Twitch's embed refuses to play
below 400x300 CSS pixels and a PiP window is far under that, Android
resizes the activity before firing onPictureInPictureModeChanged so any
correction arrives too late, and the embed latches once tripped. The native
player has no such constraint. Refs #563.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 11:47:54 -07:00
Tommy Chow
2a203aa0d1 fix(video): re-run player init on every webview page load
`_needsInit` replaced the JS-side `window._injected` guard in 9cec89a, but
the two have different lifetimes. `window._injected` died with the page, so
any page load re-ran `initVideo()`. `_needsInit` lives on the store and was
only re-armed by `handleRefresh()`.

The store outlives the Video widget, which loads `videoUrl` on every mount.
Those reloads hit `onPageFinished` with `_needsInit` already false and
returned early, so the player came back with no play/pause listeners, no
cached `_videoEl`, no autoplay call and no overlay CSS — visibly stuck
paused with a play button that did nothing, unrecoverable without an app
restart.

Re-arm on `onPageStarted` so every navigation re-initializes, restoring the
page-scoped lifetime while keeping the Dart-side control that fixed the
original race.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 11:47:42 -07:00
Tommy Chow
b2a32d178f build: bump flutter pin to 3.44.5
picks up the Impeller text/icon shadow positioning fix from #188035.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-09 00:13:05 -07:00
Tommy Chow
abb6cbc446
feat(chat): add bubbles for shared chat participant channels (#581)
* feat(chat): add bubbles for shared chat participant channels

Twitch's native shared chat previously had no way to interact with
participant channels the way manually-added chats work. Adds
per-participant bubbles alongside manual tabs: tapping one spotlights
that channel (fading the rest), hiding one filters its messages from
the feed entirely. Defaults to unfaded, resets on rejoin (fresh
ChatStore per connect).

Manual "merged mode" fading is untouched and unaffected.

* refactor(chat): dedup shared-chat tag lookup and extract fade policy

- add IRCMessage.sourceChannelId, replacing three copies of the same
  source-room-id/room-id tag lookup
- move the shared-chat spotlight/merged-mode fade decision out of
  ChatMessage's build into ChatStore.shouldFadeMessage, matching the
  extract-into-store convention already used for SharedChatBubbleState
- factor the tab-bar item's Padding/Center wrapper into a shared
  _wrapItem helper instead of duplicating it per branch

* fix(chat): don't spotlight a hidden shared-chat bubble

toggleFocus didn't check hiddenChannelIds, so tapping a hidden
bubble's chip added it to the spotlight set — fading every other
participant to showcase a channel whose own messages were already
filtered out of the feed.

* fix(chat): stop shared-chat detection flapping on system messages

_isInSharedChatMode was recomputed from every PRIVMSG/NOTICE/USERNOTICE,
but only PRIVMSG reliably carries source-room-id for a session's
duration. A sub/raid/announcement without the tag would flip the flag
off and back on within a single live session. Previously this only
risked a redundant asset refetch (already guarded); now the shared
chat bubble bar reads the flag directly, so a flap would visibly
collapse and reopen it. Scope detection to PRIVMSG only.
2026-07-03 00:16:49 -04:00
Tommy Chow
98c3a031bc docs(agents): drop duplicated never section and checklist meta-advice, flatten layout
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 20:25:11 -04:00
Tommy Chow
fb3508f43e docs(agents): drop pre-push check, CI gates instead
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 20:07:15 -04:00
Tommy Chow
65974ef954 docs(agents): add fork-pin gotcha, test layout, pre-push check
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 20:07:15 -04:00
Tommy Chow
e2dff06f8d docs(agents): fix dart-define names, default to tests, drop lint-enforced style rules
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 20:07:15 -04:00
Tommy Chow
2cdd3e9fda build: pin exact flutter version in pubspec as single source of truth
- add `flutter: 3.44.2` to environment so `flutter pub get` fails locally
  the moment the SDK drifts (was already drifting: local 3.44.2 vs the
  3.44.1 pinned in CI)
- point all 8 workflow pins at `flutter-version-file: pubspec.yaml` so CI
  reads the same field; bumping Flutter is now a one-line pubspec edit
  instead of editing 5 workflow files

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 19:52:09 -04:00
Tommy Chow
03e4198fdd ci: fail on stale generated files
run build_runner in both ci.yml (push to main) and pr-check.yml (PRs),
then fail if git status is dirty. catches forgetting to regenerate a
.g.dart after editing a MobX store or @JsonSerializable model, which
analyze/test/build all miss (as #576 did).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 18:51:11 -04:00
Tommy Chow
64c748d0b6 fix(chat): paginate moderated channels and refresh mod status on entry
- getModeratedChannels now follows the `after` cursor so moderators of
  more than 100 channels aren't silently truncated at the first page
  (mirrors getUserBlockedList's throttled recursion)
- add refreshModeratedChannels() and fire it on channel entry so mod
  status reflects grants/revokes made since login
- regenerate user_store.g.dart, which also restores #576's moderation
  action wrappers and the _moderatedChannels atom (its generated file
  was never regenerated when #576 merged)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 18:51:04 -04:00
Tommy Chow
66932c5d0b
fix(chat): keep long-press from missing in fast-moving chat (#579)
Freeze the message list on a stationary touch-and-hold (pointer-counted,
touch-slop aware) so an incoming message can't shift the row out from
under a long-press, while still allowing scroll to cancel the freeze.

Co-authored-by: Francis Lavoie <lavofr@gmail.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 18:15:42 -04:00
Francis Lavoie
2511ae93fd
feat(chat): moderator actions (delete / timeout / ban / unban) (#576)
Add a reusable ModerationActions widget shown to channel moderators (and
the broadcaster) from both the message long-press menu and the user
panel. Supports delete message, timeout (10 min), ban, and remove
ban/timeout — the recovery action appears only when the target is
currently banned or timed out (tracked via CLEARCHAT). Surfaces Twitch's
own error reason when an action is rejected, and forces an immediate
chat re-render so a deleted/banned message reflects instantly.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 17:43:37 -04:00
Francis Lavoie
b8648426f8
fix(video): keep native player alive across rotation (#575)
Wrap the native player in a GlobalKey so an orientation change re-parents
the existing platform view instead of tearing it down and reloading the
stream.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 17:23:26 -04:00
Tommy Chow
ef1b1ded5d 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>
2026-06-21 15:04:11 -04:00