Skip to content

Sync upstream Silo-Server/silo-android (30 commits) - #46

Merged
JonahMMay merged 37 commits into
mainfrom
sync/upstream-2026-10-18
Oct 19, 2026
Merged

JonahMMay merged 37 commits into
mainfrom
sync/upstream-2026-10-18

Conversation

@JonahMMay

Copy link
Copy Markdown

Rebrand-aware merge of Silo-Server/silo-android at b1e5774f3a (30 upstream commits).

Land with "Create a merge commit". Never squash. The second parent is the real upstream commit; that is what keeps the next sync small.

3 files auto-resolved (hunks: theirs 6, ours 0, same 0, superset 1, import-union 1; delete-over-noise 0).

The audit comment below lists likely compile breaks and lost Prairie lines. CI must be green, including Prairie invariants; a failing test after a sync usually means Prairie code was lost, not that the test is stale.

Quick104 and others added 30 commits October 12, 2026 20:45
…-Server#556)

FCM pushes that name a server and profile (silo_server_id, silo_profile_id) are read with that saved server's own login and profile, whatever is active. A pair that no saved server claims, or that more than one claims, is not fetched; it posts generic and doesn't navigate. Each saved server's push commands run in order under its own lock. A new FCM token reaches every saved server, either within the 8 s window or on the next foreground. Sign-out and server removal unregister first. They are pinned to the server and login they began on, and if they stop, they restore what they removed. Pushes without the fields keep the active-server path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Silo-Server#561)

On Android TV, typing a search query and then dismissing the on-screen
keyboard with Back left the viewer stuck on the search field: D-pad Down
did nothing, so the results and the "Available to request" row could only
be reached by going up into the top menu and back down.

The search screen tracked the keyboard with its own isKeyboardOpen flag,
set on Select and cleared only by the keyboard's Search action, by focus
leaving the field, or by the screen's BackHandler. Back pressed while the
IME is up never reaches that BackHandler: the IME consumes the key to hide
itself. The flag stayed true, the field stayed editable, and the text
field swallowed Down as a caret move before the Down-to-chips handler
(gated on the flag being false) could run.

The screen now follows WindowInsets.isImeVisible: once the IME has
appeared after Select and then goes away, the field returns to read-only
and Down moves to the filter chips and results again. The flag ignores
the frames between show() and the IME surfacing, so opening the keyboard
and typing are unchanged.
…ilo-Server#558)

* fix(requests): remove request moderation from the phone and TV apps

The 1.0 Requests acceptance criteria keep request administration
(approve, decline, retry, limits, auto-approve) in web admin, and the
native validation tasks say not to expose it on native clients. The
Android apps still offered it: an Approvals screen and summary cards on
phone, approval and failed-request rows on TV, and Approve, Decline, and
Retry on the request detail pages of both.

This removes that surface and the code behind it: the admin request
endpoints in RequestsApi and their models, the admin capability probe
and canModerate in RequestsFeatureStore, RequestApprovalsViewModel, the
moderation state in RequestDetailViewModel and RequestDetailCache, and
the requests/approvals route. Users keep requesting titles, cancelling
their own pending requests, and following request status.

Without the moderation lookup, the detail page's own create or cancel
broadcast could reach its listener after the action finished and trigger
a second read; the page now ignores the broadcast of its own action.

A source guard test fails if Android code calls the admin request
endpoints or brings the moderation surface back. AGENTS.md, FEATURES.md,
and README.md now say request administration stays in web admin.

* fix(requests): redirect the removed approvals route to Requests

Removing the requests/approvals destination left no destination for a
back stack saved by an older build, and Navigation throws when it
restores an entry whose destination is missing. Register a hidden
redirect to Requests, like the existing admin and card overlays
aliases, so such a restore lands on Requests. It renders nothing and
offers no moderation.

The source guard allows only the alias's two lines in AppNavigation.kt.
…pickers (Silo-Server#544)

* fix(player): tell AI translations from AI transcriptions in subtitle pickers

The phone track sheet marked every AI subtitle with the same "AI" chip and
the shared label called both kinds "AI Translation", so a transcript of
the audio and a translation could not be told apart. Android TV's subtitle
rows showed no AI marker, and its player Subtitles tab chipped only
translations.

All of them now read the kind from the server's label ("(transcribed)" or
"(translated)" provider suffix, or the "(AI transcribed)" release name)
and show "AI Translation" or "AI Transcription".

* fix(player): require the server AI marker and keep labels of AI tracks without a language

* fix(player): strip only AI markers from the source label of AI tracks
…ed (Silo-Server#545)

* fix(player): show why an AI subtitle job failed after its dialog closed

A failed AI subtitle job reported its reason only inside the AI sheet
(phone) or dialog (TV). Closing it while the job ran, which the sheet
invites, meant the failure was never seen: the phone cleared the error on
close, and TV reset the dialog to an empty form when it was reopened.

The phone now shows the reason in the player's notice, which fits a long
reason in landscape; TV shows it in the player's message toast for ten
seconds; and reopening the TV dialog keeps the failure and its reason.
Loading other content clears the TV phase, so another episode never
inherits the previous job's progress or failure.

* fix(player): keep AI failure notices through PiP and behind session notices
)

* fix(tv): keep long values in TV dialog rows on one line

Choosing an audio track with a long label, such as "German AAC Stereo
(Deutsch)", in the TV AI subtitles dialog wrapped the value over many
lines. The row grew until the target language and Transcribe rows below it
were off screen and out of D-pad reach, so a German-audio transcription
could not be started with the remote.

Cycler row values now stay on one line, take the space the title leaves
and end with an ellipsis.

* fix(tv): keep both cycler chevrons visible when the value truncates
…ilo-Server#548)

* fix(player): leave playback when an administrator ended the session

When an administrator stops a session while the player cannot be reached over
its control socket, the server now ends the attempt and answers progress and
replan with 410 playback_session_ended. The phone, tablet and TV players kept
playing: the sequenced progress journal retried the sample forever and showed a
pending-progress warning, and a recovery replan surfaced an error over the
player.

The playback lifecycle now reports such a session, and both player view models
leave playback on it, or on a replan that gets the same answer, through the
same path as a stop delivered over the control socket.

* fix(player): leave on a session-ended subtitle replan on phones

A 410 playback_session_ended answer to a subtitle_embedded_failed replan was
treated as a failed subtitle fallback, so the phone said playback continues.
It now leaves playback like the TV does.
…ation is off (Silo-Server#547)

A server can offer AI transcription without AI translation. The phone
sheet and TV dialog still let the viewer pick any target language for an
audio job, and every choice other than the spoken language was sent as a
transcribe-and-translate job the server refused with "AI subtitle
processing is not configured".

Without translation, audio mode now hides the target language, explains
that subtitles are written in the spoken language, and sends a plain
transcription.
…becomes available (Silo-Server#559)

* fix(requests): remove request moderation from the phone and TV apps

The 1.0 Requests acceptance criteria keep request administration
(approve, decline, retry, limits, auto-approve) in web admin, and the
native validation tasks say not to expose it on native clients. The
Android apps still offered it: an Approvals screen and summary cards on
phone, approval and failed-request rows on TV, and Approve, Decline, and
Retry on the request detail pages of both.

This removes that surface and the code behind it: the admin request
endpoints in RequestsApi and their models, the admin capability probe
and canModerate in RequestsFeatureStore, RequestApprovalsViewModel, the
moderation state in RequestDetailViewModel and RequestDetailCache, and
the requests/approvals route. Users keep requesting titles, cancelling
their own pending requests, and following request status.

Without the moderation lookup, the detail page's own create or cancel
broadcast could reach its listener after the action finished and trigger
a second read; the page now ignores the broadcast of its own action.

A source guard test fails if Android code calls the admin request
endpoints or brings the moderation surface back. AGENTS.md, FEATURES.md,
and README.md now say request administration stays in web admin.

* fix(requests): redirect the removed approvals route to Requests

Removing the requests/approvals destination left no destination for a
back stack saved by an older build, and Navigation throws when it
restores an entry whose destination is missing. Register a hidden
redirect to Requests, like the existing admin and card overlays
aliases, so such a restore lands on Requests. It renders nothing and
offers no moderation.

The source guard allows only the alias's two lines in AppNavigation.kt.

* feat(requests): let viewers get notified when someone else's request becomes available

Silo 1.0 Requests AC1 says a user who finds a title already requested can
ask to be notified when it becomes available instead of requesting it
again. The server and web already support this; Android had no way to
do it.

The request detail page on phone and TV now offers "Notify me when
available" / "Stop notifying me" when someone else's request for the
title is still open and the server reports follow_supported. It is not
offered to the requester, or for a title that can be requested or is
in the library. It calls
PUT/DELETE /api/v2/requests/follows/{media_type}/{tmdb_id}, confirms
with the web's copy, and broadcasts the change so search, discover, and
cached detail pages agree. A capability_* refusal re-reads the requests
status so the action hides; a conflict re-reads the title.

On TV, the follow pill keeps one node across toggles so focus stays on
it, and a focused pill that leaves the action row (Cancel after a
cancel, Notify me once the title changes) hands focus back to the
primary action. Compose would otherwise move that focus to the
synopsis above the row, where Select opens the overview.

On tablets the hero beside the poster grows when its content does not
fit. Before, the text and actions were held to the poster's height, so
on someone else's request the title and facts disappeared and the
status card was cut off. Now a small overflow is still absorbed by
shrinking the title, as before, so library detail pages lay out
exactly as they did; a larger one makes the hero taller than the
poster so the title, facts, Notify me and full status card all show.

* fix(requests): keep a follow when an older detail read lands after it

A detail read sent before a follow or unfollow could answer after it and
replace the page's state and the cached detail with the old `following`.
The page flipped back to "Notify me when available" while the server
had the follow, and the next tap sent another follow instead of an
unfollow. A prefetch had the same gap.

The repository now keeps each title's latest follow event for the
session. Detail reads and prefetches reapply any event that landed while
they were out, to the title and its recommendations, before they store
or return the detail.

The first frame built from the viewer's own request record also marks
it as theirs (`requested_by_viewer`), so the page no longer offers
Notify me on the viewer's own request before the read lands.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Quick104 <31828688+Quick104@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…o-Server#574)

* fix(android): open detail pages without popping or layout shifts

Opening an episode from Home showed the episode still for about 2.4s
while the app loaded the episode page, which only redirects to its
series, and then swapped in a different picture. The hero backdrop cut
in over its blurred placeholder in one frame, the backdrop downloaded
twice, the series page switched its overview and selectors after it
appeared, and the loading skeleton was about 32dp off from the page.

- Home episode cards open their series page directly, with the series
  artwork (or none) as the loading picture. Next Up series are warmed
  along with Continue Watching.
- The series page starts with the requested episode selected.
- Gated artwork fades in over its placeholder unless it resolves within
  100ms of the request.
- Concurrent image requests for one URL share a single download.
- The loading skeleton matches the hero's metadata rows and five action
  columns.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(android): fall back to the episode when its series cannot open

Home episode cards now open the series page directly. If that series
cannot load (deleted, or no longer accessible while a stale Home row
still lists the episode), the page now shows the episode instead of an
error, as the episode-to-series redirect already does.

Also drop the shared-download interceptor's memory-cache shortcut. Only
Coil's engine knows whether a cached bitmap is large enough for a
request, so a small cached copy let a larger request skip the shared
download.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(android): keep the season row's episode description after early selection

A series opened for an episode selects it before the season's episodes
load, so the episode's detail can arrive first and keep its own overview
instead of the row's (newer or machine-translated) description. Apply
the row's description when the season's episodes arrive.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(android): keep series pages opened from unwarmed Home rows fresh

A series page opened for an episode reads its seasons, episodes and
episode detail cache-first, which only Home's warm-up keeps fresh.
Recently Added and other rows Home does not warm could open on an older
cached copy indefinitely.

- Home starts the same live reads when such a row is tapped.
- Cache-first reads join a warm-up still in flight before using the
  cache.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(android): share image downloads in the fetcher, after the memory cache

Coordinating in an interceptor made a request that Coil could serve from
memory wait for another same-URL request's whole fetch and decode,
because the interceptor runs before Coil's memory-cache check. Wrap the
network fetcher instead: Coil creates it only after a memory miss, and a
waiter now waits only for the download.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(android): fall back to the episode when its series cannot load offline

A series opened for an episode with no cached series detail showed the
network error page offline, although the episode itself may be cached.
The episode route kept the episode in that case; apply the same fallback
to network errors.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…ilo-Server#563)

* ci(release): tag tester builds and promote the tested build to Play

Release (Run workflow, bump = build/minor/patch) now picks the version and
build number from the tags, tags the commit vX.Y.Z-beta.B, runs the unit
tests, uploads both App Bundles to the Play beta track, and publishes the
APKs on a GitHub prerelease with generated notes. Promote copies a tester
build to Play production as a draft and drafts the vX.Y.Z GitHub release
without rebuilding; publishing that release moves the Downloader and
latest-APK links.

Replaces the tag-push trigger, the hand-typed per-version build number, the
bundle provenance manifest, and the structural workflow tests. Play builds
report channel "release" instead of their track, and TV shows the same
"1.1.0 (22)" label as phone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci(release): count only exact vX.Y.Z tags as store releases

Tags with build metadata (v1.0.0+21) were tester builds, so they no longer
mark their version as released. A Release run with no tester build yet now
asks for an explicit version instead of suggesting a bump.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci(release): publish one APK per app and one latest copy per store release

Tester builds now carry silo-android-X.Y.Z-B.apk and silo-android-tv-X.Y.Z-B.apk
(the universal APKs) instead of eight per-ABI and universal files. Store
releases add silo-android{,-tv}-latest-universal-release.apk for the Downloader
codes, README and user manual; the duplicate -latest-universal-debug.apk names
are no longer written. The docs use 1.0.0, the first store version.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci(release): fail when draft lookup fails; keep tags that reached Play

Reading the draft releases into a variable first makes a failed gh call stop
the job instead of skipping the pending-release check. The docs and the
error now say to delete a failed run's tag only if it never uploaded to
Play, since Play rejects a reused build number.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(release): keep a failed run's tag unless Play never received it

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci(release): continue the newest unreleased version after a hotfix

bump: build picked the version of the newest tester build, so after a hotfix
shipped while a newer version was still in testing it refused to continue
that version. It now picks the newest version that has not been released.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci(release): stop Promote when reading the commit or previous release fails

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(release): rebuild a rejected version with an explicit version

Once the next version has started, bump: build continues that one, so a
fix for a rejected store build needs version: X.Y.Z.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci(release): read every page of releases when looking for drafts

gh release list --limit 100 returns only the newest 100 releases, so an
older draft store release could be missed and its version picked again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci(release): summarize Play's What's new with Claude

Play's beta What's new is capped at 500 characters, so the plain list kept
about eight bullets. It is now a short summary of the merged pull request
titles written by Claude, with the same guardrails as silo-apple: titles only,
the key in one step of a dispatch-only workflow and passed to curl through a
file descriptor, and the plain list whenever the summary fails, is too long,
or contains a link.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…t dispatcher (Silo-Server#575)

realtimeSignalsDuringARefreshRunOneMoreRefreshAfterIt returned as soon as
its follow-up request entered MockEngine. MockEngine had no dispatcher, so it
answered on a real IO thread after tearDown had reset Dispatchers.Main. The
late response resumed HomeViewModel on the missing Main dispatcher, and
kotlinx-coroutines-test reported that against the next runTest in the JVM,
usually PersonalListViewModelGenerationTest.

The file's Home clients now run MockEngine on the test's dispatcher, so no
request outlives the test on another thread.

Closes Silo-Server#562

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…-Server#576)

GitHub's releases/latest links skip prereleases, so no Downloader code could
follow tester builds. Each Release run now moves a beta-latest tag to its
commit and replaces silo-android-latest-beta.apk and
silo-android-tv-latest-beta.apk on a "Latest tester build" prerelease, unless
a newer tester build already exists.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…ilo-Server#577)

* ci(release): make publishing re-runnable and swap beta APKs safely

Re-running the publish job used to stop at creating a prerelease that already
existed, so a failure in the beta-latest step could never be retried. It now
re-uploads the APKs to the existing prerelease instead.

The beta-latest APKs are uploaded under staging names and swapped in, so a
failed upload no longer leaves the Downloader links without a file (gh's
--clobber deletes before uploading). The tag and title move to the new build
only after its files are live.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci(release): publish a draft left by a failed upload and keep the old beta APK until the swap succeeds

- A re-run uploads only the APKs the prerelease lacks and publishes it, since
  gh release create leaves a draft when an upload fails.
- The live beta APK steps aside as previous-<name> and is renamed back if the
  staged file can't take its name; a re-run finishes the swap and removes the
  leftover.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci(release): upload both beta APKs before swapping either

A failed upload now leaves the phone and TV links on the same build instead of
moving only the phone link.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The old codes 1051382 and 1636227 pointed at the -latest-universal-debug.apk
names, which are gone. The store codes are now 1141108 (phones) and 2249835
(Android TV), and the new beta codes 2988950 and 6033910 install the newest
tester build.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…pple (Silo-Server#584)

* fix(watch-party): converge drift by small rate changes like web and Apple

Room members corrected drift at 0.9-1.25x, seeked whenever a target
was buffered, and only measured drift when a command executed. Phone
room seeks also passed no mounted range, so a small correction on an
open-ended stream could rebuild it.

- Follow the last applied command as the room reference and nudge
  0.4-5 s of drift with a 1-4 percent rate every half second; seek only
  beyond 5 s, the same rule the web and Apple clients use.
- Apply rate and play state when a command executes, never on receipt.
- Fall back to in-buffer seeks when the audio path ignores speed
  (tunneled or passthrough output).
- Route a member that wants to play but is still buffering through the
  reload budget, and stop a stale reload from blocking the drift loop.
- Ping every 2 s until four clock samples arrive, then every 15 s.
- Remove the unused RoomSyncEngine.

Refs Silo-Server/silo-server#2481

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(watch-party): keep rate corrections honest across remounts and pending commands

Review fixes for the drift correction:

- Expire a reload that never landed without adding backoff, so the next
  correction over 5 s can load again at once.
- Do not judge whether the player honors the rate while a command is
  pending, so a 1x reading cannot reset the rate before execute_at.
- Re-check rate support after any remount, so a same-session remount
  that supports speed is not left in seek-only correction.
- Ignore engine samples taken before the latest mount on TV, and publish
  the playback speed only with a sample that is used on phone and TV.
- Fix the plan table's rate formula, whose pipes split the row.

Refs Silo-Server#583

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(watch-party): describe the server's correction thresholds after Silo-Server/silo-server#2481

Refs Silo-Server#583

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(watch-party): acknowledge a stalled member's seek that lands short

The server sends a stalled guest its own seek to the paused room and
accepts its readiness within 5 s when it names that seek. The binding
only acknowledged within 1 s, so a rebuilt stream landing short waited
out the deadline. A guest now reports within 5 s, with the seek's
command id, and the server applies the tolerance that fits the seek.

Refs Silo-Server#583

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(watch-party): keep seek-only correction across the fallback's own seeks

Rate support was re-tested after every pending seek, including the
in-place seeks the unsupported-rate fallback makes itself, so a mount
that ignores speed kept retrying an ignored nudge. Phone and TV now
report a mount generation that changes only when new media is mounted,
and rate support is re-tested only then.

Refs Silo-Server#583

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(watch-party): report only what the player shows and correct members ahead

Second review pass on the Android clients:

- Hold routine reports after a command until the player shows its play
  state, so a stale sample can no longer reverse a host's pause.
- Treat a target behind the playhead in a seekable item as reachable in
  place, as Apple and web do, so a member ahead of a paused room seeks
  back.
- Aim fallback seeks ahead by the drift the previous one left and back
  off on a miss, so slow seeks on tunneled output stop repeating.
- Shift clock samples by a wall-clock step instead of discarding them,
  so a step no longer pauses a playing member.
- Accept round trips just below zero and prefer the newer sample on a
  tie.

Refs Silo-Server#583

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(watch-party): keep naming commands after a slow player and keep fallback seeks in place

A player slower than the 3 s report hold lost the tracker that names
its command, so reports kept naming the previous one. The hold now only
releases reports at its deadline and still names the command once the
player shows it. A fallback seek also uses its lead only when the led
target is reachable in place, and otherwise seeks to the room position.

Refs Silo-Server#583

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(watch-party): build repeated skips on the requested room seek

A room seek runs a lead after the request, and each skip pressed in
between was computed from the live position, so a burst of D-pad
presses repeated or undid the previous seek while the timeline snapped
back. The binding now keeps the requested target until the room's seek
runs (or 5 s pass); phone and TV skips build on it and the timeline
shows it. Media-session seeks stay exact.

Refs Silo-Server#583

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(watch-party): clear a pending skip only when its room seek runs

The room's seek for an earlier skip in a burst cleared the newer pending
target, so the next skip built on the older one. Only the room's seek to
the pending target clears it now. The TV scrubber also starts from the
pending target, where the overlay already shows the playhead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(watch-party): clear a pending skip whose room seek was replaced

A newer command can replace the room's seek for the pending target before
it runs, and the seek never executes here. The pending target now also
clears when a command that arrived after that seek runs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(watch-party): drop clock samples on a clock step instead of shifting them

Shifting every stored offset by a wall-minus-monotonic step assumed the
monotonic clock kept running. On Android it stops in deep sleep, so after
N seconds asleep every sample moved by N and server time stayed N off
until the samples aged out. Drop the samples on a step, as before, and
restart the ping warm-up.

Also port the web and Apple rule: a new sample whose offset differs from
the best one's by more than half their round trips plus 50 ms means a
clock stepped, so the older samples are dropped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(watch-party): reload a rate-incapable member a few seconds off the room

A playing member whose mount ignores rate (tunneling, passthrough audio)
got no correction for a Play 1-5 s off when the target was not reachable
in place, so a member ahead of the room on a non-seekable mount never
converged, and server corrections landed in the same branch. Seek through
the reload budget instead. A held correction now re-runs the Play rule
when the budget allows, so it is not dropped as nudge-sized drift.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(watch-party): seek the host above 2 s when it executes a command

A playing member executing a Play command used to seek above 2 s; it now
nudges up to 5 s. A released server (host authority drift 2 s) takes a
host report more than 2 s off the room as the host's decision and
re-anchors everyone to the lagging host. The host now executes commands,
and retries held corrections, with a 2 s seek threshold. Guests and the
drift loop keep 5 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(watch-party): run a held reload for a member still buffering toward play

A member that wants to play while buffering is routed to a budgeted
reload, and the budget may hold it. The held retry ran only while the
player was actually playing, so a buffering member's correction never
ran. Allow the retry when the player intends to play; the suspension,
pending-seek, reference, and budget checks stay.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(watch-party): treat a player that cannot keep up with a nudge as rate-unsupported

On an emulator that could not decode in real time the phone reported
1.04x but advanced at 0.99-1.02x and stayed 1.5-6 s behind for minutes,
because rate support was judged only from the reported speed. While a
nudge runs, also measure the playhead's advance from engine samples and
their timestamps. Once a window of at least 5 s should have gained 0.25 s
over 1x and the playhead gained less than half of that, mark the mount
rate-unsupported so the in-place fallback seeks take over. The window
restarts on a seek, a stall, a sample gap, a command, a new nudge, and
wherever rate support is already re-measured.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(watch-party): measure rate support again after an audio track switch

Rate support was cleared only on a new mount, epoch, quality change, or
reload. Switching audio track within the mounted stream (the phone's and
TV's in-place path, with no replan) can move output from passthrough to
PCM, yet the mount stayed rate-unsupported for the rest of the title.
Both players now tell the binding, which measures rate support afresh.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(watch-party): build phone double-tap skips on the live room seek target

The double-tap detector is created once, so it kept calling skip actions
from an old composition, and those actions used the position captured
when they were built. In a room, two double-taps before the first room
seek ran sent the same target, which the server folds into one; with
controls hidden for a while a double-tap could ask for a much older
position. The gesture handler now reads the latest skip actions, and a
room skip reads the binding's pending seek target and the live position
when pressed, as the TV already does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(watch-party): measure rate support again once the new audio track is mounted

The reset was sent when an audio track switch was requested, before the player
had mounted the new track. A switch that took longer than the rate check would
retest the old passthrough path, mark the mount rate-unsupported again, and
never be reset when the PCM track landed. It is now sent when the player
confirms the wanted track is mounted and selected, on phone and TV.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Quick104 <31828688+Quick104@users.noreply.github.com>
…ilo-Server#598)

* feat(network): use the server's local address automatically at home

A signed-in phone or TV now sends its requests to the server's address on
the local network whenever that address proves it is the server, and goes
back to the saved address as soon as it doesn't. The app reads the key the
server presents on its API port from GET /api/v2/system/connections, over
the saved HTTPS address and without following redirects, and uses a private
IPv4 address (the admin-set local address, or the one that last worked) only
when TLS there presents that key and the identity check answers the saved
server.

Only the transport moves: an OkHttp interceptor rewrites scheme, host and
port and relabels the response, so auth checks, caches, URL resolution and
stored URLs keep the saved address. API requests, artwork and video move.
A request that fails on the route drops it at once and goes again to the
saved address when it can't have been sent or is a GET or HEAD. A stream
keeps the route it started on, and continues from the saved address when
that route goes. Sign-in and refresh, sockets, Watch Party, downloads,
sidecar subtitles, audiobooks, the reader and casting stay on the saved
address.

Checks run in the foreground on Wi-Fi or Ethernet only, after every network
change and foreground return, and once more after a failure. The active
server's row in the phone and TV server lists reads "Connected on your local
network" while the route is in use.

Closes Silo-Server#591

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(network): keep the local route's probes on HTTP/1.1

The probe clients keep no idle connections. Over HTTP/2, a multiplexed
connection could be evicted from that empty pool before its answer was
read, so a valid candidate sometimes failed its check. This flaked
LocalRouteTransportTest in CI (11 of 200 local runs). The server's LAN
TLS listener offers only HTTP/1.1, so the probes now ask for it alone:
0 of 600 runs failed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(playback): admit streams on the saved address unless their media takes the route

The server picks a playback session's local or remote bitrate cap from
the network its `POST /api/v2/playback/start` arrives through, stores
it, and reuses it for every replan. Admission went over the local route
for every stream, including Watch Party, casting and audiobooks, whose
media stays on the saved address. Those sessions got the local cap and
counted as local while streaming over a tunnel or public address.

Admission now takes the route only when the stream's media may: the
phone and TV video starters pass the same rule their media gets, and
every other caller admits on the saved address.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(network): re-read a rotated pin at once and check known local addresses first

- A candidate that presents a key other than the pin now makes the app
  read the connections document again in the same check. Before, a
  SECRET_KEY rotation within 15 minutes of the last read left the old
  pin in place, and the route off, until a later trigger.
- An admin-set host name is resolved only after the local address in
  use, the one that worked last and an IP-literal local address have
  failed, so a slow lookup no longer delays one that works.
- The starter publishes the active server only if it is still the same
  after the sign-in lookup; a switch or sign-out during the lookup
  publishes itself next.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…lo-Server#604)

* fix(player): say when an administrator ended playback

An administrator's stop or terminate closed the Android phone and TV
player with no message, so viewers saw playback quit at random. The
control-socket decoder dropped `issued_by`, and stop/terminate mapped to
a bare Stop.

Decode `issued_by.kind` and carry a notice on Stop when an administrator
issued it ("An administrator ended/stopped this playback session.", the
Apple and web text). Both players show it as a toast as they leave, and
the 410 playback_session_ended path shows the "ended" text. The toast
sits behind the existing exit guard, so a socket stop followed by a 410
shows one message. SiloCast stops and the viewer's own Back stay silent.

Closes Silo-Server#602

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(player): keep a stop's party leave and match Apple's issuer rules

Guard only the toast with exitRequested, so a stop that arrives while a
phone is already leaving for the Watch Party lobby still leaves the room
as before. Compare the issuer case-insensitively and prefer an
administrator's own message, as Apple does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(player): show the admin notice on the player's display

Build the toast from the screen's context instead of the application
context, so it lands on the display the player was on (an external or
desktop display) rather than the default one. Text toasts are drawn by
the system and stay up after the activity finishes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Silo-Server#606)

* docs(contributing): require evidence on phones, tablets, and foldables

Name an Android phone, an Android tablet, the Galaxy Z Fold8 and Fold8 Ultra (folded and open), and Android TV in the evidence rules and the pull request template, and ask contributors to say why they skip a device.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(agents): list the evidence devices in the agent instructions

Repeat the device list from CONTRIBUTING.md in AGENTS.md, as silo-server does for desktop and mobile web, and keep the template's "Surface and build:" label so it matches the other Silo repositories.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci(macroscope): check that visible changes show evidence on each device

Add the visible-change evidence check from silo-server, adapted to trace which of an Android phone, an Android tablet, the Galaxy Z Fold8 and Fold8 Ultra, and Android TV a change reaches and to flag devices the Evidence section leaves out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…rver#607)

Ask for an Android foldable, folded and open, instead of naming the Galaxy Z Fold8 and Fold8 Ultra. A Galaxy Z Fold or Pixel Fold both count.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
… setup (Silo-Server#597)

* feat(onboarding): list Silo servers found on the local network during setup

Phone server setup and both Android TV setup pages now list the Silo
servers advertised on the local network (`_silo._tcp`), like the Apple
apps. Each advertisement is confirmed with GET /api/v2/system/identity at
its resolved address before it is listed, results with one id share a
row, and the row is named from the server's branding. Choosing one runs
the same probes and plain-HTTP consent as typing its address.

Discovery runs only while the setup screen is started, on its own HTTP
client so no credentials or device metadata reach an address that only
advertised itself, and it reads at most 64 KiB per answer.

Refs Silo-Server#594

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(onboarding): give every found server a turn and drop stale resolves

On API 34+, once six services held callbacks a seventh was never followed,
so a real server could stay hidden behind replicas or advertisements that
never confirm. Each refresh now hands the longest-held slots to services
that are waiting. Callbacks also check they still own their key.

On API 24-33, a resolve that finishes after its service was lost and found
again under the same key is dropped instead of reporting the old address.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(onboarding): keep discovery probes on advertised addresses and bounded

- The probe client no longer follows redirects, so an advertiser can't
  send the automatic identity request to an address discovery refuses,
  such as the device's own loopback.
- At most four advertised addresses are probed at once, however many
  instances the network advertises.
- Events a restarted browse had already queued are dropped, so they can't
  remove a result the new browse reported.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…ilo-Server#596)

* feat(profiles): set a child's rating and libraries on phone and TV

The Android profile forms couldn't restrict a child's libraries, and the
TV form had no rating picker: a child silently got PG. Turning Child on
now shows the rating ceiling and a Restrict libraries switch with the
account's libraries, on by default, and asks for at least one library,
as on iOS and the web. Editing sends only the limits that changed, so a
rating from another client's list (such as the web's "12") stays as
stored.

Closes Silo-Server#593

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(profiles): check the saved allowlist and keep TV retry focus

A changed allowlist made only of deleted libraries passed the "choose a
library" check, then saved as an empty list. The check now counts only
libraries the account still has, and only for an allowlist the user
changed.

On TV, removing the focused Try Again after a successful retry reported
a focus loss that cancelled the hand-off to the library list. Only a
focus loss while the failure is still shown cancels it now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(profiles): group a child's limits in one panel with a rating ladder

The child limits read as three levels of identical switches, with nothing
tying them to Child Profile. On TV they sat in a three-row strip under the
avatar grid, with 14sp rating chips and a clipped Create button.

Both apps now show the limits in one panel under the Child switch:

- Maximum rating is a connected scale. The ceiling is solid, the ratings
  under it are tinted as also allowed, and a caption states the rule.
- Libraries is an "All libraries / Choose libraries" choice, then a
  checklist with each library's type icon.

On TV the whole form column scrolls as one, so moving down into the limits
gives them the screen. The d-pad links and the Try Again focus hand-off
are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(profiles): let any profile limit its rating and libraries

The rating ceiling and library allowlist showed only for a child, so a
household manager couldn't limit a teen or a guest to some libraries
without also marking them a child. The web editor shows these limits on
every profile.

The limits panel now shows on every profile, on phone and TV, as on the
web:

- Child only fills in kid-safe defaults: PG when no rating was chosen in
  the form, and Choose libraries when libraries weren't touched. Turning
  it off clears the limits, as the web does.
- The rating scale ends in an Any step for no ceiling.
- Create sends the rating and allowlist for any profile, and any
  restricted profile must allow at least one library.

On TV, Child always moves down into the rating scale, landing on the
selected step.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…erver#595)

The phone and TV pickers showed Manage and the Add profile tile only to
admin accounts. Since silo-server#2019 the server lets the account's
verified primary profile manage the household whatever the role, and
Manage already acts as the primary (Silo-Server#493), so the role check left
regular households with no way to add, edit or delete a profile.

Show both whenever the household has a primary profile. The manage gate
tests become ManageGate tests covering a regular account, a failed
account lookup and a household without a primary.

Closes Silo-Server#592

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: rxwatcher <rxwatcher@users.noreply.github.com>
…ilo-Server#614)

* fix(player): steady volume swipe with on-screen and haptic feedback

The right-edge volume swipe applied each touch event separately and
truncated it, so slow drags did nothing and fast ones jumped several
steps, with nothing on screen. Swipe-down-to-close gave no sign it was
about to close the player.

Volume now follows the whole drag from the level at touch-down and shows
a level pill at the top center, clear of the thumb. Swipe-down shows a
"Pull down to close" hint that turns into "Release to close" once letting
go would close. Volume steps, skips, hold-for-2x, pinch zoom, and the
close threshold each answer with a haptic.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(player): keep pinch gravity calls the source test checks for

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…-Server#612)

* test(fixtures): vendor the image capability fixture

The capability response now advertises per-slot artwork widths
(slot_width_params), and the client decodes it, so the contract test
needs the server's body. Add get_image_capabilities_ok to the vendored
subset and re-vendor from the server checkout that carries it.

The bodies come from an unmerged silo-server change (branch
feat/episode-image-original-quality); SOURCE records snapshot=uncommitted.
Re-vendor from the merged server commit before relying on them. The
other fixture diffs are drift between the previously vendored commit
and that checkout.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(artwork): request artwork at the size each screen draws it

Artwork came back at the server's default sizes however large a screen
drew it. On Android TV that left detail logos (drawn up to 650px from a
500px image) and episode stills soft, and the hero preload assumed 2x
density. On phones the detail hero, episode cards and grid posters were
upscaled.

Servers that list slot_width_params in /api/v2/images/capabilities take
poster_width, backdrop_width, logo_width, still_width and photo_width on
every v2 read that returns artwork. Each app now describes how wide it
draws each surface (dp x the device's density), and the repositories
send those widths when the active server advertises them:

- Android TV: Home, For You and library shelves send poster 88dp,
  backdrop 672dp (fills the 614x378dp hero) and logo 440dp; detail
  sends the same backdrop with logo 325dp and photo 100dp, so opening
  an item reuses the hero image Home loaded. Episodes send still 180dp
  on the series page and 230dp elsewhere, the similar rail poster
  130dp, catalog grids poster 138dp. The hero and startup preloads use
  density-derived sizes instead of 1229x756px.
- Phone and tablet: the detail hero, episodes and catalog grids send
  widths from their layouts, taking the larger of both orientations so
  rotating keeps a listing's widths. Rails (Home, For You, similar) ask
  for their cards at the largest card size, since that preference loads
  after launch. Opening a Home card warms its backdrop at full-screen
  size only on servers without widths, where detail uses the same URL.

Requests wait up to 2s for the capability probe and otherwise go out
as today, as they do on older servers, which reject width parameters.
Widths are bound to the server that advertised them, so a read issued
during a server switch can't carry them to the next server, and a
probe that fails on the network is retried with backoff. Catalog
cursors keep the widths of their first page, and the Room caches key
detail, episodes, library pages and Home by the widths the device draws
them at.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(artwork): size cards by the card-size preference and keep cache keys stable

Review follow-ups on the per-slot artwork widths:

- Cards and grid cells grow with the card-size preference: TV cards,
  episode stills and grids scale with cardScaled(), and phone grids
  drop columns for larger posters. Requests sized them at the standard
  card size, so Large posters got an image a size too small (a phone
  grid cell of 551px got a 500px poster). Each app now reads the active
  preference when it builds a request. Cache keys stay at the standard
  size, so they don't change when the preference loads after launch.
- Cache writes rebuilt their key from the live layout, so a fold while
  a read was in flight filed one layout's artwork under the other's key.
  Detail, episodes, library pages, library sections and Home now capture
  the key before the read and cache the answer under it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(fixtures): re-vendor the API fixtures from server main

Silo-Server/silo-server#2627 merged, so the vendored bodies now come
from main at 2fe4518e9 instead of an uncommitted server checkout. The
bodies are unchanged; SOURCE now names that commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(artwork): keep pre-update caches and skip caching unsized reads

- Rows cached before artwork widths existed are keyed without them, so
  after an update offline Home, detail, episodes, library pages and
  library sections found nothing. A read now falls back to the old row
  until that item is cached again, which removes it.
- A read that went without widths only because the capability check
  hadn't answered was still cached under the layout's key, and
  prefetch then reused it after the check succeeded. Such reads are no
  longer cached; reads to a server that doesn't take widths still are.
  The cache key is captured with the widths, after the check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(artwork): load the saved card size before the first reads

The startup Home warm-up ran before the card-size preference loaded, so
a user with Large cards got Home sized for Standard (a 420px request for
a 504px card at 3.5x). Cache keys also used the standard size while
requests used the active one, so after a card-size change cached Home,
library and episode reads reused smaller images.

The layouts now seed the preference from its local cache, without the
network, the first time a read needs it, and cache keys use the active
card size again. A card-size change starts a new cache entry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(artwork): decide cacheability with the request and retire only the writer's legacy row

A read that went without widths because the capability check timed out
could still be cached if the check answered before the response: the
write consulted live probe state. ArtworkSizing.sizedRead now returns
the widths and whether the answer may be cached together, before the
read goes out, and replaces mayCache.

Retiring a pre-update cache row re-read the active identity after the
write, so a profile switch during Room's upsert could delete the next
profile's row. put() now returns the owner it wrote for, and only that
owner's legacy row is deleted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…tting (Silo-Server#613)

Playback settings now offer separate Resolution and Maximum bitrate
pickers, matching the web app, and phones add a Cellular quality preset
(default 720p at 2 Mbps) that applies on cellular and metered networks.
A saved Original reads as Auto. During playback, quality follows network
changes with bounded retries, and a pick made in the player lasts for the
current title. The app still works against servers without the new
cellular settings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ion (Silo-Server#611)

Advertise hls_subtitle_renditions_v1 when the HLS delivery is Media3's own
pipeline (native_hls_playback_v1), so Cast keeps its sidecars. A
server_transcode_hls plan with subtitle.hls_rendition is validated, and its
selected inventory row is projected with the rendition and no URL, so the
whole-file extraction is never mounted.

The rendition is an exact server selection, modelled on the native embedded
decision: phone and TV select the manifest track by Media3's GROUP-ID:NAME
format id (falling back to a single non-sidecar track labelled NAME), commit
only after it is selected, and on a failed mount replan failure_recovery with
subtitle_hls_rendition_failed so the server returns the sidecar plan without
tearing down video. Replan and seek-reanchor checks accept and compare the
rendition.

Vendor the server's hls-subtitle-rendition-decision_response.json fixture.
…#626)

* fix(tv): stop re-reading Home while the player covers it

Android TV keeps one Home for the whole shell, alive under the player, and
every realtime or metadata-language signal re-read the full Home. With a
large Home on a 192 MB heap, where the player holds up to half, one of those
reads ran the app out of memory mid-episode.

Home's rows now report when they are on screen (TV Home and the Settings
Home sections editor). While they are hidden, realtime and language
refreshes wait and run once when Home is shown, after the shell restores
focus on a detail return and after a first load still filling an empty
Home. Hiding drops a realtime read still in flight. The phone app keeps its
behavior.

Closes Silo-Server#623

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(home): run a waiting refresh after a signed-out first load

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(home): keep hidden Home from cancelling or starting reads

A metadata-language change while Home is hidden no longer cancels the
language read already running, which may be the one filling Home. A
watched write that finishes while Home is hidden re-reads on show instead
of beside the player.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The Libraries picker sheet laid out every library in a plain Column, so
on a server with more libraries than fit on screen the rows past the
bottom edge could not be reached. Keep the "Libraries" title fixed and
put the rows in a LazyColumn, matching iOS's LibraryPickerSheet.

Fixes Silo-Server#522

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
JonahMMay and others added 2 commits October 18, 2026 16:25
30 upstream commits since 726684d. Rebrand-aware merge by
Prairie-Server/.github upstream-sync/prairie_sync.py (base=rebrand(merge-base), theirs=rebrand(upstream)).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Conflicts resolved by hand:
	AGENTS.md
	androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/di/AndroidModule.kt
	androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/requests/MyRequestsScreen.kt
	androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/servers/ServerListScreen.kt
	androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/servers/ServerListViewModel.kt
	androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/player/TvPlayerViewModel.kt
	androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/servers/TvServerListViewModel.kt
	shared/src/commonTest/kotlin/org/prairieserver/prairie/model/profile/ProfileQualityPreferenceTest.kt
	shared/src/commonTest/kotlin/org/prairieserver/prairie/viewmodel/RequestsViewModelTest.kt
…d playerQualityLabel

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 18, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Too many files!

This PR contains 350 files, which is 250 over the limit of 100.

To get a review, reduce the PR to 100 files or fewer by splitting it into smaller PRs or changing its base branch.

Upgrade to a paid plan to raise the limit.

Usage-priced reviews support at most 300 files.

⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 9beb3468-0960-42e3-80ce-f33a4e493018
📥 Commits

Reviewing files that changed from the base of the PR and between 7d842b3 and 722631d.

📒 Files selected for processing: 350
  • .github/release.yml
  • .macroscope/check-run-agents/visible-change-evidence.md
  • AGENTS.md
  • FEATURES.md
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/data/repository/RoomCatalogCacheRepository.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/data/repository/RoomHomeCacheRepository.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/discovery/LanServerDiscovery.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/discovery/NsdLanServiceBrowser.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/downloads/DownloadWorker.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/downloads/OfflineTrackAssetFetcher.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/images/PrairieImageLoader.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/images/SharedDownloadFetcherFactory.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/network/ClientBuildIdentity.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/network/LocalRouteStarter.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/player/PlaybackNetworkMonitor.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/player/PlaybackQualityPair.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/player/PlaybackSessionLifecycle.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/player/PlaybackSessionManager.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/player/PlaybackV3Session.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/player/PlayerOkHttpClient.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/player/PrairiePlayerFactory.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/player/SubtitleMountResolver.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/player/VideoPlayerMediaMounter.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/player/VideoPlayerMediaSpec.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/player/video/VideoPlayerRouteArgs.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/player/watchparty/WatchPartyPlayback.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/requests/RequestRouter.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/requests/RequestUi.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/settings/AndroidPlayerSettingsStore.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/settings/CardPresentationStore.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/settings/PlayerSettingsStore.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/settings/ServerSettingsFlusher.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/startup/StartupWarmup.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/ui/components/ThumbhashImage.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/data/repository/RoomCatalogCacheRepositoryTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/data/repository/RoomHomeCacheRepositoryTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/discovery/LanServerDiscoveryTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/images/SharedDownloadFetcherFactoryTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/network/LocalRouteTransportTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/player/HlsSubtitleRenditionTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/player/NetworkQualityFollowerTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/player/PlaybackCapabilityDetectorDolbyVisionTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/player/PlaybackNetworkSnapshotTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/player/PlaybackSessionLifecycleTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/player/PlaybackSessionManagerSeekReanchorTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/player/PlaybackSessionManagerStagedReplanTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/player/cast/CastPlaybackPreparerTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/settings/AndroidPlayerSettingsStoreTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/settings/ServerDrivenConfigRefresherTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/settings/ServerSettingsFlusherTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/startup/StartupHomeV2Test.kt
  • androidApp/build.gradle.kts
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/PrairieApplication.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/auth/SignOutTeardown.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/di/AndroidModule.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/push/AndroidPushRegistrar.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/push/AndroidPushRegistrationStarter.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/push/PrairieFirebaseMessagingService.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/push/PushInstallationStore.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/push/PushNotificationPresenter.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/push/PushTargetResolver.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/components/MediaRow.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/navigation/AppNavigation.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/navigation/Routes.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/MainScreen.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/auth/ServerSetupScreen.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/auth/ServerSetupViewModel.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/detail/DetailLoadingSkeleton.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/detail/DetailSharedComponents.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/detail/ItemDetailScreen.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/detail/ItemDetailViewModel.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/home/HomeScreen.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/libraries/LibrariesScreen.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/player/AiTranslateSheet.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/player/MobileRecoveryReplan.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/player/MobileSubtitleTransactionAdapter.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/player/MobileVideoPlaybackStarter.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/player/PicksDuringNetworkMove.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/player/PlaybackQualitySheet.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/player/PlaybackRealtimeController.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/player/PlayerGestureHandler.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/player/PlayerOverlay.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/player/PlayerScreen.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/player/PlayerTrackLabels.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/player/PlayerViewModel.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/player/SubtitleAiSupport.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/player/WatchPartyPlayerSupport.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/profiles/CreateProfileScreen.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/profiles/CreateProfileViewModel.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/profiles/EditProfileScreen.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/profiles/EditProfileViewModel.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/profiles/ProfileAccessControls.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/profiles/ProfileSelectionScreen.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/profiles/ProfileSelectionViewModel.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/requests/MyRequestsScreen.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/requests/RequestApprovalsScreen.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/requests/RequestComponents.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/requests/RequestDetailScreen.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/requests/RequestRows.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/requests/RequestsScreen.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/servers/ServerListScreen.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/servers/ServerListViewModel.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/settings/PlaybackSettings.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/settings/SettingsPages.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/settings/SettingsScreen.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/settings/SettingsViewModel.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/util/PhoneArtworkLayout.kt
  • androidApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/android/auth/SignOutTeardownTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/android/push/AndroidPushRegistrarTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/android/push/PushMessageHandlerTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/android/push/PushTargetFixtures.kt
  • androidApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/android/push/PushTargetResolverTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/android/ui/navigation/ContentDeepLinkRoutesTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/android/ui/navigation/ExternalRouteNavigationTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/android/ui/screens/auth/ServerSetupPersistenceTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/android/ui/screens/player/MobileQualityMenuTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/android/ui/screens/player/MobileRecoveryReplanTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/android/ui/screens/player/MobileSubtitleTransactionAdapterTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/android/ui/screens/player/NetworkQualityReplanTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/android/ui/screens/player/PlayerVerticalDragModeTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/android/ui/screens/player/PlayerViewModelLoadOwnershipIntegrationTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/android/ui/screens/player/SubtitleTrackDisplayLabelTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/android/ui/screens/player/WatchPartyPlayerSupportTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/android/ui/screens/profiles/ProfileSelectionManageGateTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/android/ui/screens/servers/ServerListViewModelRemovalTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/android/ui/util/PhoneArtworkLayoutTest.kt
  • androidTvApp/build.gradle.kts
  • androidTvApp/gradle.lockfile
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/MainTvActivity.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/PrairieTvApplication.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/data/preferences/TvSettingsEnums.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/di/AndroidTvModule.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/components/TvSkylineSectionFeed.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/auth/TvServerSetupScreen.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/auth/TvServerSetupViewModel.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/detail/TvItemDetailViewModel.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/home/TvHomeScreen.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/player/TvAiTranslateDialog.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/player/TvPlaybackRealtimeController.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/player/TvPlayerHud.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/player/TvPlayerScreen.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/player/TvPlayerViewModel.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/player/TvSubtitleChoiceLabels.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/player/TvSubtitleRemountReselection.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/player/TvSubtitleSearchDialog.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/player/TvSubtitleTransactionAdapter.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/player/TvVideoPlaybackStarter.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/player/TvWatchPartyPlayer.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/profiles/TvCreateProfileScreen.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/profiles/TvCreateProfileViewModel.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/profiles/TvEditProfileScreen.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/profiles/TvEditProfileViewModel.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/profiles/TvProfileAccessControls.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/profiles/TvProfileForm.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/profiles/TvProfileSelectionScreen.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/profiles/TvProfileSelectionViewModel.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/requests/TvRequestComponents.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/requests/TvRequestDetailScreen.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/requests/TvRequestsPage.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/search/TvSearchInputRules.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/search/TvSearchScreen.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/servers/TvServerListScreen.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/servers/TvServerListViewModel.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/settings/TvHomeSectionsEditor.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/settings/TvSettingsScreen.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/settings/TvSettingsViewModel.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/theme/TvArtworkLayout.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/data/preferences/LegacyTvPrefsMigrationTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/testing/FakePlayerSettingsStore.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/navigation/TvAudiobookRoutingTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/screens/auth/TvServerSetupPersistenceTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/screens/player/SubtitleTransactionIntegrationTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/screens/player/TvFireTvRcFeedbackOwnershipTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/screens/player/TvPlaybackQualityOptionsTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/screens/player/TvStartupMetadataOwnerTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/screens/player/TvSubtitleChoiceLabelsTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/screens/player/TvSubtitleTransactionAdapterTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/screens/player/TvWatchPartyPlayerTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/screens/profiles/TvProfileSelectionManageGateTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/screens/requests/TvRequestActionFocusFallbackTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/screens/search/TvSearchKeyboardStateTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/theme/TvArtworkLayoutTest.kt
  • baselineprofile-tv/gradle.lockfile
  • docs/README.md
  • docs/playback/01-media3-only-player-architecture.md
  • docs/playback/README.md
  • docs/releasing.md
  • docs/requests-api-v2.md
  • docs/watch-party-implementation-plan.md
  • fastlane/Appfile
  • fastlane/Fastfile
  • fastlane/play_bundle_manifest.rb
  • proguard-rules.pro
  • scripts/decode-release-secrets.sh
  • scripts/release-version.py
  • scripts/summarize-release-notes.sh
  • scripts/sync-apiv2-fixtures.sh
  • scripts/test-ci-cache-invalidation.py
  • scripts/test-fastlane-lanes.rb
  • scripts/test-play-bundle-lanes.rb
  • scripts/test-release-version.py
  • scripts/test-release-workflow.sh
  • scripts/test-summarize-release-notes.sh
  • shared/build.gradle.kts
  • shared/src/androidMain/kotlin/org/prairieserver/prairie/network/EncryptedTokenManagerImpl.kt
  • shared/src/androidMain/kotlin/org/prairieserver/prairie/network/PrairieHttpClient.android.kt
  • shared/src/androidMain/kotlin/org/prairieserver/prairie/network/PrairieOkHttp.kt
  • shared/src/androidMain/kotlin/org/prairieserver/prairie/network/localroute/LocalRouteEnvironment.kt
  • shared/src/androidMain/kotlin/org/prairieserver/prairie/network/localroute/LocalRouteInterceptor.kt
  • shared/src/androidMain/kotlin/org/prairieserver/prairie/network/localroute/LocalRouteProbe.kt
  • shared/src/androidMain/kotlin/org/prairieserver/prairie/network/localroute/LocalRouteTls.kt
  • shared/src/androidMain/kotlin/org/prairieserver/prairie/player/SubtitleTrackDisplayLabel.kt
  • shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/model/playback/PlaybackProtocolV3ConformanceTest.kt
  • shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/model/settings/PlaybackSettingsKeysContractTest.kt
  • shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/network/EncryptedTokenManagerScopeGenerationTest.kt
  • shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/network/api/NoNativeRequestModerationSourceTest.kt
  • shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/network/localroute/LocalRouteInterceptorTest.kt
  • shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/network/localroute/LocalRouteTlsTest.kt
  • shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/network/localroute/TestCerts.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/RoomSyncEngine.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/di/NetworkModule.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/di/RepositoryModule.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/model/feature/RequestsFeatureStore.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/model/notifications/PushRegistrationModels.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/model/playback/PlaybackModels.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/model/playback/PlaybackProtocolV3.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/model/playback/PlaybackQualityMenu.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/model/playback/SubtitleTransition.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/model/profile/ProfileAccessLimits.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/model/profile/ProfileQualityPreference.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/model/request/RequestDisplayState.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/model/request/RequestModels.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/model/request/RequestMutations.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/model/settings/PlaybackSettingsKeys.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/model/settings/QualityChoices.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/model/settings/QualityPresets.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/model/settings/SettingKeys.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/model/watchtogether/WatchTogetherModels.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/network/ArtworkWidths.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/network/AuthScopeSnapshot.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/network/DeviceMetadataProvider.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/network/DurableLoginAuthority.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/network/PlaybackRealtimeClient.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/network/PlaybackRealtimeModels.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/network/TokenManager.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/network/WatchTogetherRealtimeClient.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/network/api/AuthApi.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/network/api/CatalogApi.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/network/api/ExternalSignInApi.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/network/api/ImageCapabilitiesApi.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/network/api/ProfileApi.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/network/api/PushRegistrationApi.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/network/api/RecommendationApi.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/network/api/RequestsApi.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/network/api/SectionApi.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/network/api/ServerIdentityApi.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/network/api/WatchTogetherApi.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/network/apiv2/CatalogV2Api.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/network/apiv2/CatalogV2Models.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/network/apiv2/DiscoverV2Api.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/network/apiv2/DownloadCreationV2Api.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/network/apiv2/DownloadRegistryV2Api.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/network/apiv2/HomeSectionsV2Api.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/network/apiv2/LibrarySectionItemsV2Api.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/network/apiv2/NotificationsV2Api.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/network/apiv2/PlaybackV2Api.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/network/apiv2/SimilarCardsV2Api.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/network/localroute/LocalRoute.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/network/localroute/LocalRouteController.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/network/localroute/LocalRouteHeaders.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/network/localroute/LocalRouteOffers.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/network/localroute/LocalRouteState.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/playback/PlaybackCommandDispatch.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/playback/PlaybackSubtitleIdentity.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/playback/PlaybackVersionSelector.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/repository/ArtworkSizing.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/repository/AuthRepository.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/repository/CatalogRepository.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/repository/ImageCapabilitiesSession.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/repository/NotificationsRepository.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/repository/PlaybackRepository.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/repository/ProfileRepository.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/repository/RecommendationRepository.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/repository/RequestDetailCache.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/repository/RequestsRepository.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/repository/SectionRepository.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/repository/SequencedPlayback.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/repository/WatchTogetherRepository.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/repository/port/CatalogCachePort.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/repository/port/HomeCachePort.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/viewmodel/HomeViewModel.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/viewmodel/RequestApprovalsViewModel.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/viewmodel/RequestDetailViewModel.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/viewmodel/RequestsViewModels.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/watchtogether/RoomCatchup.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/watchtogether/RoomClock.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/watchtogether/RoomPlaybackBinding.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/RoomSyncEngineTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/model/feature/RequestsFeatureStoreTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/model/playback/HlsSubtitleRenditionProtocolTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/model/playback/PlaybackProtocolV3Test.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/model/playback/PlaybackQualityMenuTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/model/profile/ProfileAccessLimitsTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/model/profile/ProfileQualityPreferenceTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/model/settings/QualityChoicesTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/model/settings/QualityPresetsTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/network/ImageCapabilitiesSessionTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/network/PlaybackRealtimeDecodeTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/network/api/PushRegistrationApiTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/network/apiv2/ApiV2ContractTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/network/apiv2/ArtworkWidthsV2Test.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/network/apiv2/LibrarySectionListV2Test.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/network/apiv2/NotificationsV2Test.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/network/apiv2/ProfileHouseholdV2Test.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/network/apiv2/RequestFollowV2Test.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/network/apiv2/RequestsV2Test.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/network/localroute/LocalRouteControllerTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/network/localroute/LocalRouteTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/playback/PlaybackCommandDispatchTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/repository/AuthRepositorySignOutTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/repository/CatalogLibraryScopeTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/repository/CatalogRepositoryDetailCacheTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/repository/RequestsRepositoryTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/repository/SectionRepositoryCacheTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/repository/SequencedPlaybackTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/repository/WatchTogetherRepositoryTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/viewmodel/HomeScreenV2Test.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/viewmodel/HomeViewModelCacheIdentityTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/viewmodel/RequestDetailFollowTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/viewmodel/RequestDetailStatusTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/viewmodel/RequestsViewModelTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/watchtogether/RoomCatchupTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/watchtogether/RoomPlaybackBindingTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/watchtogether/WatchPartyWireTest.kt
  • shared/src/commonTest/resources/api/v2/fixtures/SOURCE
  • shared/src/commonTest/resources/api/v2/fixtures/complete_account_identity_link_invalid_grant.json
  • shared/src/commonTest/resources/api/v2/fixtures/complete_oauth_login_invalid_grant.json
  • shared/src/commonTest/resources/api/v2/fixtures/create_account_identity_link_ticket_wrong_password.json
  • shared/src/commonTest/resources/api/v2/fixtures/delete_account_identity_last_sign_in_method.json
  • shared/src/commonTest/resources/api/v2/fixtures/get_device_login_capability_ok.json
  • shared/src/commonTest/resources/api/v2/fixtures/get_image_capabilities_ok.json
  • shared/src/commonTest/resources/api/v2/fixtures/index.json
  • shared/src/commonTest/resources/api/v2/fixtures/refresh_session_provider_unavailable.json
  • shared/src/commonTest/resources/api/v2/fixtures/server_connections_ok.json
  • shared/src/commonTest/resources/api/v2/fixtures/sign_in_with_network_identity_off_overlay.json
  • shared/src/commonTest/resources/playback/v3/SOURCE
  • shared/src/commonTest/resources/playback/v3/hls-subtitle-rendition-decision_response.json
  • shared/src/commonTest/resources/settings/v1/SOURCE
  • shared/src/commonTest/resources/settings/v1/conformance.json
  • shared/src/commonTest/resources/settings/v1/manifest.json

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@JonahMMay

JonahMMay commented Oct 18, 2026 •

Copy link
Copy Markdown
Author

Upstream sync audit

check count
conflict markers 0
likely compile breaks: names used but declared nowhere ⚠️ 1
likely compile breaks: names declared twice 0
files with Prairie lines no longer present 8
Silo tokens added by the sync 0

Names still used but declared nowhere. Upstream renamed/removed them, or a resolution dropped the definition. Port the caller to upstream's replacement:

  • Approval: androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/requests/RequestDetailScreen.kt:450, shared/src/commonMain/kotlin/org/prairieserver/prairie/model/request/RequestDisplayState.kt:161, shared/src/commonMain/kotlin/org/prairieserver/prairie/model/request/RequestDisplayState.kt:197

Prairie lines no longer present. Each is either intended (upstream rewrote that code) or a lost Prairie change:

androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/di/AndroidModule.kt (1)
viewModel { ServerListViewModel(get(), get(), get(), get(), get()) }
androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/player/PlayerOverlay.kt (2)
preservesSource = option.isOriginal,
qualityPreference = playerQualityLabel(qualityOptions, activeQualityId),
androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/requests/MyRequestsScreen.kt (3)
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.Spacer
import androidx.compose.material.icons.filled.Close
androidTvApp/build.gradle.kts (3)
val siloDisplayVersion = providers
.gradleProperty("siloDisplayVersion")
buildConfigField("String", "DISPLAY_VERSION", "\"${siloDisplayVersion.get()}\"")
androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/player/TvPlayerViewModel.kt (3)
// Menu ids may be full ladder rung ids (`1080p-high`); V3 replan uses
qualityPreference = org.prairieserver.prairie.playback.toV3QualityPreference(
return authoritativePlaybackQualityOptions(planQualities, selectedWire, planDelivered)
fastlane/Fastfile (6)
# structure of silo-apple's Fastfile:
# Local path: read ~/.playstore/silo-play-publisher.json from disk.
# Same CI/local duality as silo-apple's setup_api_key.
# (same threat model as silo-apple's marketing_version_xcarg).
keystore_path = File.join(Dir.mktmpdir("silo-release-keystore"), "silo-release.jks")
# commit's subject — same pattern as silo-apple's TESTFLIGHT_CHANGELOG.
shared/src/commonTest/kotlin/org/prairieserver/prairie/model/profile/ProfileQualityPreferenceTest.kt (2)
assertEquals("720p", displayProfileQualityPreference("720p"))
assertEquals("480p", displayProfileQualityPreference("480p"))
shared/src/commonTest/kotlin/org/prairieserver/prairie/model/settings/QualityPresetsTest.kt (4)
fun `byId and describe cover uncapped original and compound 4k`() {
assertEquals("original", QualityPresets.byId("original")?.id)
assertEquals("Original", QualityPresets.describe("original", null))
assertEquals("Original at 12 Mbps", QualityPresets.describe("original", 12000))

JonahMMay and others added 4 commits October 18, 2026 19:21
…kQualityOption.preservesSource removal

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…age gate

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…un connect merge

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Add JVM unit tests (shared androidUnitTest, self-signed keytool certs, no
network) for PinningTrustManager, PinOnlyTrustManager, PinningHostnameVerifier,
LocalRouteTls, HandshakeTimeoutSocketFactory, LocalRouteInterceptor,
LocalRouteConnectionCheck, LocalRouteAttempt and RouteFailureBody, and drop
their Kover excludes. Only LocalRouteProbe (real sockets) stays excluded.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@JonahMMay
JonahMMay merged commit 385d965 into main Oct 19, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants