Repository navigation
Sync upstream Silo-Server/silo-android (30 commits) - #46
Conversation
…-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>
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>
|
Important Review skippedToo 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
📒 Files selected for processing: 350
You can disable this status message by setting the
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. Comment |
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Upstream sync audit
Names still used but declared nowhere. Upstream renamed/removed them, or a resolution dropped the definition. Port the caller to upstream's replacement:
Prairie lines no longer present. Each is either intended (upstream rewrote that code) or a lost Prairie change:
|
…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>
Rebrand-aware merge of
Silo-Server/silo-androidatb1e5774f3a(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.