Skip to content

chore(sync): merge Silo-Server/silo-android main (238 commits) - #28

Merged
JonahMMay merged 415 commits into
mainfrom
cursor/sync-silo-android-4e8c
Aug 28, 2026
Merged

chore(sync): merge Silo-Server/silo-android main (238 commits)#28
JonahMMay merged 415 commits into
mainfrom
cursor/sync-silo-android-4e8c

Conversation

@JonahMMay

@JonahMMay JonahMMay commented Aug 28, 2026

Copy link
Copy Markdown

Problem

Prairie-android was 406 commits behind Silo-Server/silo-android main (238 beyond the stale draft sync on cursor/sync-silo-android-e10e / PR #27).

Approach

Merged remaining upstream main onto the prior sync branch tip, then rebranded:

  • Remap org.siloserver.siloorg.prairieserver.prairie and Silo*Prairie*
  • Keep Live TV DI bindings and X-Prairie-* / ImageFormats headers
  • Keep Kover coverage gate alongside upstream Robolectric cache + lint jobs
  • Follow upstream removal of admin surfaces (including STATS); docs updated to match
  • Finish leftover skipSiloAuth / cast helper renames for compile

After merge: 0 behind upstream/main.

Supersedes draft PR #27.

Testing

git rev-list --left-right --count HEAD...upstream/main
# 75  0

Android SDK / full Gradle not run in this environment — rely on CI.

AI Disclosure

  • Tool(s): Cursor Cloud Agent
  • Model(s): Composer
  • Involvement: Large upstream merge, conflict automation, Prairie rebrand, Live TV / auth header / Kover preservation
  • Adversarial review: Special-cased Network/Repository/Android(TV) modules, AuthInterceptorImpl, and workflows

Checklist

  • Adversarial review of DI/auth/workflow conflict resolutions
  • Fixed CI configure failure (missing Kover plugin alias)
  • Android CI Builds green
Open in Web Open in Cursor 

Quick104 and others added 30 commits July 30, 2026 18:16
…ar (Silo-Server#156)

* fix(tv): keep the For You list selection, scroll, and a legible top bar

Reported against v1.0.0+3 (83e23da), which already contains the recent TV
focus batch — these are live on current code, not stale-release artifacts.

**Watchlist and Favorites reverted to For You after opening an item.**
`savedListSelection` was a plain `remember`, so opening an item disposed the
composition and the value re-initialised from `entryRequest.selection` on the
way back. Top-level For You entry carries `selection = null`, so returning
from a Watchlist item did not merely forget the list — it actively reselected
the recommendations feed.

`lastAppliedEntrySequence` had to move with it. Left as `remember` it resets
to 0, which makes the entry-request effect treat the unchanged request as new
and re-apply its selection — reintroducing the same jump even once the
selection itself is saved. Both are now `rememberSaveable`.

**Scroll position was lost returning to the feed.** The recommendations
`LazyColumn` created its list state inside the `when` branch, so a refresh
that briefly flipped to loading/empty and back discarded it. Hoisted above
the branch.

**The top bar was unreadable over For You.** `TvTopMenuBar` deliberately has
no background band of its own and documents that "the SHELL draws a fixed top
scrim behind the bar" (QA 2026-07-08) — but the shell drew none, so the
labels sat directly on whatever scrolled underneath. On For You that is a
poster row. Restored as a gradient rather than a solid band, which satisfies
both that contract and the shell's own intent that content stay visible
behind the bar. This fixes every route, not just For You.

Not addressed: the jerky scrolling on For You, and "cannot scroll the
sections" after returning. The latter looks like focus restoration rather
than scroll state (on a D-pad, no focus means no scrolling) and belongs with
TvRecommendationsFocusBridge; both want their own change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(tv): hand focus back into For You after returning from an item

Completes the second half of the report: "I can't scroll the For You sections
anymore" after opening an item and coming back.

The shell already has a detail-return path — a flag set when opening, a
resume claim on the content group, and a restorer fallback pointing at the
launch card — but every part of it was Home-only, and For You was wired to
the raw onOpenItemDetail. Nothing claimed content focus on the way back, so
focus settled wherever Compose's default search landed, and the D-pad no
longer drove the rows the viewer was just in.

Two flags now, deliberately: `restoreContentAfterDetail` means a return is
pending for ANY root and gates the resume claim; `restoreHomeContentAfterDetail`
additionally means it was the Home feed, which is the only root that attaches
homeDetailReturnCardFocusRequester to its launch card. Using that requester
as the restorer fallback for a root that never attached it would aim the
restorer at a detached node, so Home's behaviour is left byte-identical and
For You opts into the claim alone.

The screen's own once-per-entry focus grab had to stop fighting it. Its
guards were plain `remember`, so a detail return reset them, re-fired the
effect and slammed focus onto the Watchlist pill while the feed sat scrolled
where the viewer left it — the exact anti-pattern TvMainShell warns about:
"fired LaunchedEffects in each screen that imperatively re-focused index 0 —
defeating the restorer". Saved, the grab stays genuinely once-per-entry.

Jerky scrolling is NOT addressed. The usual causes are ruled out — TvMediaRow
carries key and contentType, hoists its row state, and memoises its item
mapping on remember(items, showProgress, style, cardLayout) — so what remains
(image decode during scroll, focus-driven recomposition, the absence of the
skyline feed's settled-focus prefetch policy on this plain LazyColumn) needs
a device profile rather than a guess.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(tv): smooth For You focus scrolling

* fix(tv): hide menu scrim on settings

---------

Co-authored-by: rxwatcher <rxwatcher@users.noreply.github.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sync upstream into prairie-android with Silo→Prairie rebrand:
settings contract/language catalogs, playback buffer architecture,
specials-first season order, TV option dialog viewport fixes, For You
list/top bar fixes, and related shared/TV/phone changes.

Preserve Prairie-only Live TV DI bindings, X-Prairie-* + ImageFormats
auth headers, American spelling in settings copy, and pinned Action SHAs.
Move leftover org/siloserver paths to org.prairieserver.prairie and scrub
brand tokens across the tree.

Co-authored-by: Jonah May <JonahMMay@users.noreply.github.com>
* build(android): target API 36

* fix(player): adapt orientation lock for Android 16

* fix(tv): migrate back handling for Android 16
SharedModelsCoverageTest still expected specials last; sortedForDisplay
now places specials before regular seasons (SeasonDisplayOrderTest).

Co-authored-by: Jonah May <JonahMMay@users.noreply.github.com>
Add focused unit tests for newly synced onboarding models/API, invitation
auth routes, downloaded-subtitle URL rebasing, and QualityPresets edge
cases so :shared:koverVerify clears the 95% line floor after the Silo sync.

Co-authored-by: Jonah May <JonahMMay@users.noreply.github.com>
Aligns the shared overlay registry and badge renderer with
web/src/lib/overlays, closing drift found while auditing all clients
against the contract's card-overlays schema:

- standalone resolution badge now uses prettyResolution ("4K", not
  "2160P"), matching the combined badge and web
- show_status recognizes "continuing", "upcoming"/"planned", and drops
  empty strings
- HDR icon picking matches web's exact-match rules, and the combined
  resolution_hdr badge only ever doubles up the Dolby Vision mark
- brand tokens (HDR/HDR10/ATMOS/AV1) suppress a label that says the
  same thing, so icon-preferring presets don't render "HDR10 HDR10"
- original_language renders an English language name ("English"), not
  a raw uppercased tag

Companion to the silo-apple fix for the Discord report of
/settings/card-overlays not applying on native clients.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…-selection-ux

fix(tv): improve Fire TV selection and episode continuity
…-contract

fix(overlays): match web's card-badge rendering rules
RXWatcher and others added 22 commits August 11, 2026 16:59
…r#216)

* fix(tv): observe focus arrival per target, not per screen

requestFocusUntilObserved tests isFocused() BEFORE it ever calls
requestFocus, so an arrival test broader than the thing being asked for
turns the whole claim into a no-op. Five sites got this wrong, in two
directions.

Always-true — the claim never fires. Search and Requests both waited on a
screen-root hasFocus, which is already true the moment you are on the
screen at all:
- Back from a result never returned to the search field, because a result
  having focus is exactly the state that satisfied the test;
- a submitted search never handed you its results, because the field had
  focus;
- return-restoration onto the card you left from was a coin flip on
  whether the root had reacquired focus yet.

Always-false — the claim can never be confirmed. Calendar's
calendarFilterHasFocus was declared and never assigned, so it read false
forever: the claim burned every attempt and reported Exhausted even when
focus had landed, and the reconfirm after Android's delayed focus pass,
the bar-suppression release and onInitialContentFocus() never ran.

Each claim is now observed on the region it actually asked for, so moving
focus WITHIN a screen is a state the test can distinguish.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(tv): clear the calendar flag on exit, and guard search regions by item identity

Two holes in the per-region observation.

The calendar's zone callbacks only fire when a control or shelf GAINS
focus, so moving Up from the filter into the top menu left the flag true
with focus outside the screen entirely. A later shell handoff read that
stale true, skipped both claims, and still reported initial content
focus — and only another calendar zone gaining focus could clear it,
which the skipped handoff could never cause. Clear it when the screen
loses focus.

Search's result-region callbacks are per card, and Compose can deliver
the newly focused card before the outgoing one reports false, so the card
that just lost focus cleared the region the new one had set; a recycled
item has the same shape. Guard the clear on item identity, the way the
neighbouring return-target tracking already does, so a stale false whose
id is no longer current is ignored.

Found by Codex review of Silo-Server#216.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(tv): observe a search return on the card, not the region

A return is a claim on one specific item, so testing 'some result has
focus' is the same too-coarse arrival this change removed everywhere
else: any already-focused card in the region satisfied it, and the saved
card was never requested — precisely the case a return exists to serve.
The identity wait below already knew the right answer; the claim above it
did not.

Found by Codex reviewing this branch against the new main.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: rxwatcher <rxwatcher@users.noreply.github.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…rver#218)

The similar rail fetched inside its own LaunchedEffect, which only ran
once the LazyColumn scrolled the row into view — so scrolling down always
hit a pop-in. Move the fetch into ItemDetailViewModel (started from
loadDetail, like the TV app's loadMoreLikeThis) and make SimilarRail a
pure renderer fed from ui state.

Code written by OpenAI Codex (gpt-5.6-sol) under Claude orchestration.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* ci: cache Robolectric's Android runtimes

Robolectric does not resolve its Android runtimes through Gradle. At test
execution time it fetches android-all-instrumented straight from Maven
Central into ~/.m2, which setup-gradle's cache does not cover, so every
run re-downloaded them: 85 MB for API 24, 145-204 MB each for 34/35/36.

On 2026-08-11 Maven Central answered 403 for one of those fetches and 31
unrelated tests in android-shared failed — sync, PiP, DB migration,
downloads, pairing — on a pull request that touched none of it. The
download time is also a large part of why this job takes 4-5 minutes when
the tests themselves run in seconds.

Keyed on the version catalogue so bumping Robolectric repopulates, with a
restore-key so a bump seeds from the previous cache rather than starting
cold.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* ci: track module SDK selection in the Robolectric cache key

Which runtimes get fetched depends on the Robolectric version AND on the
SDK each test resolves to, so hashing only the version catalogue could
leave a newly-needed runtime outside the key — and actions/cache only
saves on a key miss, so that one would be re-downloaded every run.

Hash the module build files (min/target SDK) alongside the catalogue. A
new @config(sdk=NN) in a test is deliberately not in the key: hashing
test sources would bust the cache on nearly every pull request, which
costs more than it saves, and the residual gap is one runtime until the
key next moves.

Raised by CodeRabbit on Silo-Server#217.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: rxwatcher <rxwatcher@users.noreply.github.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ilo-Server#214)

* fix(tv): make the player controls reachable, and Center mean pause

Two things kept a viewer on the scrub bar from doing the obvious thing.

D-pad Down is consumed by the activity-level remote-key bridge, which
maps it to FocusTransport and asks the overlay to focus the transport
row. That request is retried until observed — but the arrival test was
`idleOverlayHasFocus`, hasFocus on the overlay ROOT, which is already
true whenever any control has focus. With the scrub bar focused the retry
loop concluded focus had arrived and never requested, so Down did
nothing. It also explains the workaround: Back hides the controls,
clearing the flag, so the next Down finally requested. Observe focus per
control row and test the row actually asked for.

Center on the bar entered a scrub mode. The Google TV remote has no
play/pause key, so that spent the viewer's only one-press pause on
something Left/Right already do (skip, and long-press auto-seek). Center
now lands any scrub in flight and then toggles playback: racing forward
it stops on the frame you asked for; hunting a spot while paused it plays
on from it.

Verified on a Google TV Streamer: scrub bar -> Down now focuses
play/pause in one press.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(tv): do not chain play/pause onto a scrub commit inside a room

Center commits the in-flight scrub and then toggles playback. Solo that
applies locally and in order, but in a Watch Together room they are two
independently launched requests and the play/pause carries the LIVE
position rather than the committed one — so it can land after the seek
and pull every participant back to where the scrub started.

Skip the toggle when a room owns transport; Center there commits, as it
did before. The surface is compile-time disabled
(CLIENT_WATCH_TOGETHER_SURFACE_ENABLED = false), so this is latent rather
than reachable, but the ordering hazard is real.

Found by Codex review of Silo-Server#214.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: rxwatcher <rxwatcher@users.noreply.github.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat: add hosted diagnostics uploads

* fix: harden hosted diagnostics delivery

* fix: normalize hosted decoder identifiers

* fix: redact hosted loopback identities

* fix: preserve hosted erasure races

* fix: close Android diagnostics privacy boundaries

* fix: bound Android crash evidence retention

* fix(diagnostics): harden hosted capture and delivery

* fix(diagnostics): escape Android template regex

* fix(diagnostics): preserve release metadata

* fix: harden hosted diagnostics delivery

* fix: close diagnostics review follow-ups
…ilo-Server#223)

The rest-state border on the player transport controls is a 0.5dp
translucent white stroke (22% alpha) drawn on each circular button.
Because it is thinner than a device pixel and translucent, it dithers
against the moving video and aliases into a jagged white fringe around
every button, visible even on 4K panels where the sub-pixel stroke never
lands on a whole device pixel.

Drop the rest-state border and let the translucent fill carry the button
edge. Focus treatment (white-fill inversion) is unchanged.
… server (Silo-Server#226)

* feat(client-identity): report build number and release channel to the server

The apps already sent X-Silo-Client and X-Silo-Client-Version on every
API request, so the server could name the app but not the build. CI
already computed a real build number in release.yml, but consumed it
purely as arithmetic into versionCode — it never reached Gradle as its
own value, so nothing on the device could report it.

Threads the build number through as a first-class value: a validated
siloBuildNumber Gradle provider (-PsiloBuildNumber, then
SILO_BUILD_NUMBER, defaulting to "0") emits BuildConfig.BUILD_NUMBER in
both app modules, and release.yml now passes it. The env block in the
APK build step is appended to rather than restructured, because
TvFireTvRcFeedbackOwnershipTest asserts its literal SILO_DISPLAY_VERSION
line.

Sends X-Silo-Client-Build and X-Silo-Client-Channel from the existing
single header choke point, plus app_build/app_channel on the v3 playback
context and the Cast prepare request. The build number is deliberately
not derived from versionCode: that is the form-factor-doubled release
code (base*2 phone, base*2+1 TV), not this counter, and reversing the
formula would be fragile.

A build CI never stamped reports as absent rather than as build 0 — the
server treats the value as an opaque string, so a placeholder would
surface verbatim as "(build 0)" in admin Activity, and channel=dev
already carries that meaning. normalizedClientBuildNumber is the single
place that knows it, reused by all three carriers and the About row.

Also normalizes two device-login platform spellings to "android-tv":
RemotePlaybackIdentityManager sent "android_tv", which the web
frontend's classifyPlatform bucketed as mobile.

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

* fix(client-identity): one source of truth for build number and channel

Review follow-up on the build-number reporting.

Depth. The build number and channel were threaded through five playback call
sites, so the one shared caller that cannot see BuildConfig — the audiobook
player — reported neither, leaving audio sessions exactly as indistinguishable
as before. Both facts now cross into android-shared once as a DI-provided
SiloClientBuildIdentity, which the metadata provider, the capability detector,
the Cast request and the diagnostics environment all resolve. The five video
call sites revert to their original form; the audiobook one is fixed without
being touched.

Channel. Play bundles and sideload APKs are both assembled from the release
build type, so `if (BuildConfig.DEBUG)` reported "release" for both and the
field said nothing. It is now BuildConfig.RELEASE_CHANNEL, set per build type
off the existing isBuildingBundle signal: bundle -> release, assemble ->
sideload, debug -> dev. Verified against generated sources.

app_build. Diagnostics already sent that field holding the versionCode, so one
install reported two different builds under one name. It now sends the same
counter, matching silo-apple's CFBundleVersion on all three carriers.

Platform. The TV had a third device-login spelling, "Android TV", on the LAN
companion-pairing path, which classifyPlatform buckets as mobile.

Also: conformance fixtures now carry app_build/app_channel with tests pinning
the encoded key names and the omit-when-unstamped rule, since the round-trip
check alone would not catch a name mismatch; build number bounded to 0..999 as
release.yml and the Fastfile do; the About row label is one shared helper in
the "1.0.0 (5)" form the server and Play both render; unread DISPLAY_VERSION
dropped from androidApp.

./gradlew :androidApp:assembleDebug :androidTvApp:assembleDebug test  PASS

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

* fix(client-identity): stamp the real Play track and keep prereleases distinct

Two review findings on the channel and build number.

Channel. Deriving it from the invoked task labelled every bundle "release",
but the Fastfile uploads to internal/alpha/beta/production, so a beta tester
and a production user reported the same thing — the attribution the field
exists to provide. It is now an explicit siloReleaseChannel property that the
Fastfile fills with the track it is actually uploading to, validated against
Play's vocabulary plus sideload/dev so a typo cannot reach the server as a
header. The sideload APK job states its channel outright; a hand-built
artifact defaults to sideload, which is what it is.

Prerelease identity. A -rc.N tag with no +N suffix resolved to build 1, so
v1.2.3-rc.1 and v1.2.3-rc.2 both reported version 1.2.3 / build 1 and stayed
indistinguishable — precisely what the build number was added to fix. The
counter now comes from the suffix (-rc.2 is build 2), canonicalized so -rc.02
and +02 agree, with a numberless suffix still meaning build 1.

Verified by executing the workflow's own setup block over each tag form:
v1.2.3 -> 1, v1.2.3+2 -> 2, -rc.1 -> 1, -rc.2 -> 2, -rc.10 -> 10, -rc.02 -> 2,
-beta -> 1; -rc.1000 and +0 rejected by the existing bound; play_publish still
false for every prerelease. Channel verified against generated sources: debug
-> dev, assembleRelease -> sideload, -PsiloReleaseChannel=beta -> beta,
nightly -> rejected.

./gradlew :androidApp:assembleDebug :androidTvApp:assembleDebug test  PASS
scripts/test-release-workflow.sh, test-check-build-supply-chain.sh,
check-build-supply-chain.sh  PASS

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

* revert: do not derive the build counter from a prerelease suffix

Reverts the -rc.N -> build mapping from 583fa15. Bugbot was right and the
change was a net regression.

The counter is folded into the versionCode, so mapping -rc.2 to build 2 gave
the sideloaded prerelease code base+2 while the official v1.2.3 gets base+1.
Android refuses the lower code, so a QA device on rc.2 could no longer take
the official release of the same version — an upgrade path that worked before,
when both resolved to base+1 and installed as a reinstall.

It did not even buy what it was meant to: -rc.2 and +2 resolve to the same
counter, so those two artifacts still reported an identical
version/build/channel triple.

Confirmed by executing the workflow's setup block before and after: with the
mapping, v1.2.3-rc.2 -> 110203002 against v1.2.3 -> 110203001; after the
revert both are 110203001 and every tag form matches main's behaviour.

Prerelease artifacts stay distinguishable by their tag and GitHub release but
not by reported identity. Fixing that properly means carrying the prerelease
suffix in the reported version rather than the counter, which changes
app_version semantics for suffixed tags and is a call for the release owner,
not a second unilateral guess at the versioning scheme.

./gradlew :androidApp:assembleDebug :androidTvApp:assembleDebug test  PASS
release-workflow / supply-chain self-tests  PASS

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

* fix(client-identity): align fixture channel with the configured vocabulary

The v3 golden fixtures asserted app_channel "release", which stopped being a
value any artifact can emit when the channel became the Play track. Both
request fixtures and the conformance assertions now use "production", so the
corpus matches what a real build reports.

Also renames siloReleaseChannels in both build scripts to match the camelCase
every other val in those files uses.

./gradlew :androidApp:assembleDebug :androidTvApp:assembleDebug test  PASS

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

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…er#227)

* Update launcher icons and TV art to the new vector artwork

Re-render the phone and TV launcher icons, the adaptive foregrounds, both
silo_wordmark drawables and the TV banner from the SVG masters in
Silo-Server/silo-branding.

The adaptive foreground was a fully opaque copy of the whole tile, which hid
the background layer entirely and put the mark well outside the 66/108
keyline, so circular and squircle launchers were cropping the top of the play
triangle and the bottom of the orange bar. The foreground is now transparent
art sized to the keyline circle, which makes silo_icon_background visible for
the first time; it moves from #1718C9 to the brand field #010D9F so the two
layers agree.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012iRtwoxswjkgRqcgAeczVP

* Match the guarded TV launcher icon geometry

androidTvApp already had a correct mark-only adaptive foreground and a legacy
icon that insets the mark on an opaque field, both guarded by
TvLauncherIconAssetsTest. The first pass would have regressed them: it centred
the mark on its minimum enclosing circle, which sits near the mark's left edge
and so pushed the artwork right of centre, and it rendered the legacy icons
from the rounded project icon, which has transparent corners.

Size the safe zone from the largest radius about the bounding-box centre, and
render the legacy TV icon as the flat mark inset on an opaque field at the
217/320 proportion the hand-made asset used. The regenerated foreground lands
at 80x255 +176+88 against the hand-made 79x255 +176+88.

The phone app gets the same treatment; its foreground was still the baked
opaque tile.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012iRtwoxswjkgRqcgAeczVP

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…den + reconnect verbiage change (Silo-Server#222)

* fix(tv): show one buffering indicator, not two

PlayerView's built-in centered spinner was still enabled alongside the
top-right "Buffering" capsule that had been added to replace it, so any
rebuffer drew both at once: a teal spinner mid-screen plus the capsule.
The capsule landed in 2454-2546 with a comment saying it replaced the
full-screen spinner, but setShowBuffering was never turned off.

Behavior change: during playback buffering the centered ExoPlayer spinner
no longer appears; the "Buffering" capsule is the sole indicator. The
centered white Compose spinner is untouched and stays reserved for the
lifecycle Reconnecting state, which the player cannot observe.

* fix(tv): report buffering while the controls are hidden

The "Buffering" capsule and the sleep-timer chip lived inside
TvPlayerIdleOverlay, which is only composed while state.showControls is
true. With the controls auto-hidden, D-pad Left/Right does a discrete
seek (dpadHorizontalSeek gates on !showControls) without revealing the
transport, so the rebuffer that seek triggers had no indicator at all
once PlayerView's spinner was off.

Moved both chips up into TvPlayerOverlays, which renders regardless of
controls visibility, using the same precedent as the intro auto-skip
banner and the transient skip-seek indicator. Rewrote the block's
.align(TopEnd) as a fillMaxSize Box with contentAlignment, since
TvPlayerOverlays' body is not a BoxScope. Both chips moved together;
they share one Column at the same TopEnd offset, so splitting them
would overlap.

Behavior change: buffering and the sleep countdown now appear during
hidden-controls seeks and hold-to-seek sessions, not just when the
transport is up. Gated off in PiP, while the HUD is open, and while Up
Next is showing, all of which provide their own feedback. When the
controls are visible the result is unchanged. No collision with the
Watch Together room indicator (also TopEnd, but still controls-gated)
or the hold-seek chip (TopCenter).

* fix(tv): let the reconnect spinner outrank the buffering capsule

A comment on the outage spinner claimed it only showed when the idle
overlay wasn't already showing the Buffering chip, but
shouldShowReconnectSpinner never checked that, and a player stalled by a
server outage reports STATE_BUFFERING like any other stall. Reconnecting
therefore drew the centered white spinner and the capsule at once. That
already needed controls-visible to happen; moving the capsule out of the
idle overlay widened it to every Reconnecting stall.

Behavior change: the Buffering capsule is suppressed while the centered
reconnect spinner is up. During a server outage the screen shows the
centered spinner plus the "Reconnecting" notice toast, which say strictly
more than "Buffering" does. Ordinary rebuffers are unaffected. The sleep
countdown chip is not gated on this; it is unrelated to either signal.

Also corrects the outage-spinner comment, which still described the
capsule as living inside the idle overlay's statusColumn.

* fix(player): stop blaming the reconnect notice on a server update

beginOutageRecovery fires on isGatewayOrTunnelFailureStatus (502, 503,
504, 520-527, 530) and on NetworkError, so the cause can be a crashed
server, a restarting one, a dead tunnel or reverse proxy, or the
client's own network dropping. The notice asserted one specific cause,
"The server is updating", which is usually wrong and reads as a false
explanation to anyone debugging their own setup. Its paired timeout
message was already cause-agnostic, so this was the odd one out.

Behavior change: the reconnect pill now reads "Reconnecting. Playback
will resume automatically." No claim about why, and no promise about
server readiness that the client cannot verify. Same wording on phone
and TV, since both consume this constant from the shared lifecycle.
Note this now differs from the Apple clients' copy, which lives outside
this repo.

* fix(tv): keep the buffering capsule up wherever video is playing

The capsule replaced PlayerView's spinner (SHOW_BUFFERING_NEVER), but it
was gated on !hudOpen && !showNextUp, so those two surfaces had no
buffering feedback at all. Neither owns a loading state: a HUD quality or
version pick restarts the whole session with the HUD still open
(closeOnSelect closes only the picker), and Up Next plays video behind
the mini-player frame until the credits end.

Keep the capsule up on both, dropping it below the HUD card so they don't
overlap, and let the ambient sleep-timer chip keep yielding the corner.

Gate the block on videoActive as well: it moved out of the streamUrl
branch into TvPlayerOverlays, and fail() sets error without clearing
isBuffering, so a spinning capsule could land on the error screen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Quick <31828688+Quick104@users.noreply.github.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
)

The splash was a 4K h264 file with no source alongside it, and its artwork
predated the vector rebuild — its play triangle used a deeper blue than its
first bar, which the palette no longer has. This is the same animation
regenerated from silo-wordmark-white.svg, so it tracks the masters and matches
the launcher icons and TV art updated in Silo-Server#227.

Motion is unchanged: timings were measured frame-by-frame off the previous file,
and every element holds within 20px at 4K, or 1.3% of the mark's height. The
triangle now reads brighter, being the palette's signal blue, and the new
lockup's mark-to-text gap is fractionally tighter — about 48px at 4K, under a
pixel and a half at phone size.

Same container: 3840x2160, 60fps, 4.0s, h264. 2.9MB down to 490KB, because flat
vector colour compresses far better than the previous render. StartupSplashVideo
keeps loading it as R.raw.startup_splash.

The Lottie cut is the same animation as vector, at an eighth of the size. It is
named startup_splash_lottie.json rather than startup_splash.json because res/raw
names resources by filename without extension, so the latter would collide with
the video on R.raw.startup_splash — aapt2 rejects it with "resource
'raw/startup_splash' has a conflicting value". Nothing loads it yet.

Generated by src/splash.py in the silo-branding repo.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…sted allowlist (Silo-Server#232)

* feat(diagnostics): re-vendor expanded attribute registry and align hosted allowlist

Picks up the attribute keys added to the canonical registry in
silo-server, keeping this client's copy in lockstep with the collector.

Also fixes three gaps found while coordinating this change with the
Apple client:

- REGISTERED_ATTRIBUTES in DiagnosticsValidation.kt had fallen behind the
  vendored fixture. Unknown keys hit a `null -> Unit` branch, so they lost
  type validation silently rather than being rejected.

- Nothing in the test suite read the vendored fixture, so this client had
  no equivalent of the Go and Swift parity gates and could drift by hand
  again. Adds DiagnosticsAttributeRegistryParityTest.

- HOSTED_V1_LOG_ATTRIBUTES forwarded playback session_id, play_method,
  reason, and position_ms plus network attempt to the hosted collector.
  session_id and attempt are in the collector's FORBIDDEN_KEYS, so those
  bundles would fail the privacy_fields check; the Apple client withholds
  the same five keys. Now aligned, with a test pinning the withheld set
  in both directions. lifecycle.reason is a client-side classification
  and is deliberately still forwarded -- only playback.reason, which is
  server-authored free text, is withheld.

No production Android code emits these keys yet, so the allowlist change
is a latent-leak fix rather than a behavior change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* chore(diagnostics): update vendored registry provenance

Points SOURCE at the silo-server commit these registry additions came
from, so the vendored fixture's stated origin matches its contents.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…endering overhaul (Silo-Server#228)

* feat(tv): skip-intro countdown as shrinking-fill button

Design variant: the Skip Intro button's translucent background fill
drains left-to-right and empties as auto-skip fires. Auto-focuses so
D-pad Select skips immediately. Drops the Cancel affordance for this
variant; shared IntroAutoSkipController untouched (fill animates
between whole-second ticks).

* fix(tv): keep shrinking-fill pill small and drain continuously

Two bugs: the fill layer's fillMaxWidth/Height grabbed the screen's max
constraints, ballooning the pill across the screen (now sized to the
pill via matchParentSize); and AnimatedContent treated each per-second
CountingDown tick as a new target, recreating the subtree and restarting
the drain every second (now keyed on the state kind, with the fill
driven by one continuous Animatable over the full countdown).

* feat(tv): skip-intro fill creeps left-to-right, focus/back/pause polish

- Fill grows left-to-right (was draining right-to-left) and lands full
  exactly when the auto-skip fires; duration comes from the countdown's
  own remaining seconds at entry, not the configured total.
- Slightly larger pill (18sp, 32/18dp padding).
- Focus request now waits two frames so it doesn't fail silently; the
  button reliably has focus on popup.
- Back dismisses the banner for this intro via new controller dismiss()
  (stays hidden; no manual pill fallback).
- Moving focus off the button cancels the timer and leaves the solid
  manual pill in place; cancelCountdown no longer resurrects the banner
  after a dismiss or a completed fire.

* fix(tv): focus the manual skip pill, cancel on D-pad not focus loss

The focus-loss watcher inferred 'user moved off' from any focus change,
but focus here is transient (the player re-focuses its own overlay), so
it cancelled the countdown a beat after it appeared: the fill vanished
into the solid manual pill (reading as an instantly-full bar) and the
auto-skip never fired. Cancel now triggers on an actual D-pad direction
key press, which is what the requirement meant.

The manual pill also never took focus, so Select needed a navigate press
first. It now auto-focuses when it appears fresh, but not when it appears
because a countdown was cancelled (that would fight the user's move).

* feat(tv): intro skip-intro button with wall-clock countdown fill

Countdown variant of the skip-intro prompt: a dark pill in the lower
right whose translucent fill creeps left-to-right and lands full exactly
as the auto-skip fires.

- Fill is driven by the frame clock, not an AnimationSpec. Compose scales
  spec durations by the device's animator_duration_scale
  (MotionDurationScale); measured on a Shield at 0.5x, a 5s tween
  finished in 2508ms and the bar sat full for the last 2.5s. A countdown
  to an automatic action must report real time, so it ignores that
  setting while the decorative transitions still honor it.
- Countdown is gated on playback actually running, so the prompt and the
  timer start together instead of the prompt appearing partway into an
  already-elapsed timer.
- Select, D-pad cancel, and Back dismiss are handled in the player
  screen's root key handler: the banner is not reliably in the focus
  tree, so key modifiers on the button never fired.
- Back dismisses the prompt for the current intro via a new controller
  dismiss(); a D-pad direction stops the timer and leaves the solid
  manual pill in place.
- Buttons use a single focus target (clickable owns it); the previous
  focusable+clickable pair split focus and key handling across two nodes,
  which required two Select presses to activate.
- Prompt drops toward the corner when the transport controls fade out and
  lifts back when they return.
- Unfocused state is dimmed rather than lit.

* fix(tv): address review findings on intro skip prompt

- Back during the countdown consumes both key phases. Consuming only
  ACTION_DOWN leaked the ACTION_UP to the activity back dispatcher, where
  whichever BackHandler happened to be topmost would also fire. Matches
  the sibling BACK handling already in this key handler.
- Debounce the false edge of playbackActive: isPlaying dips on every
  ExoPlayer rebuffer, and letting that through cancelled the countdown
  and restarted it from full mid-intro. A real pause still lands after
  the grace period.
- Share one countdown constant (IntroAutoSkipController
  .DEFAULT_COUNTDOWN_SECONDS) between the controller and the banner so
  the timer and the fill cannot drift apart.
- Cover the new shared state machine with focused tests: dismiss stays
  hidden inside the same intro, cancel-after-dismiss does not resurrect
  the pill, dismiss is per-intro, the countdown is held until playback is
  active, pause stops it and resume restarts from full, and cancel
  outside an active countdown leaves state alone.
- Drop dead plumbing the root key handler made redundant: the banner's
  onCancelCountdown param and BackHandler, the overlay's
  onCancelIntroAutoSkip param, and two unused imports. Fix KDoc that
  claimed Back was handled in the banner.

* fix(tv): stop the playback-stall debounce crashing on the first false edge

settlingFalseEdges used a flow {} builder with collectLatest, which runs
each value's block in a child coroutine; emitting into the enclosing
collector from there violates the flow invariant and threw
IllegalStateException the first time isPlaying reported false, i.e. on
every playback start.

Switched to channelFlow (the mitigation the exception itself names) and
hoisted the operator to an internal top-level function so it can be
tested. Added SettlingFalseEdgesTest covering the three contracts: true
passes straight through, a sub-grace false blip never surfaces, and a
false held past the grace period does.

* feat(tv): Back stops the intro countdown instead of dismissing it

Back now behaves like a D-pad nudge: it stops the timer and leaves the
solid manual Skip Intro pill in place. The press is still consumed so it
cannot also exit playback, and because the state is no longer
CountingDown afterwards, a second Back behaves normally.

That leaves nothing calling the controller's dismiss(), so the whole
dismissed-key path goes with it: dismiss(), dismissedKeys, the Hidden
branch in handle(), the dismissed check in cancelCountdown(), the
ViewModel and overlay/banner plumbing, and the two dismiss-only tests.
The per-intro test is retargeted at cancel, which is the behavior that
now exists.

* docs(tv): trim intro skip comments to what the code is and why

* docs(player): document the intro auto-skip controller API

Adds KDoc to the state type and the controller's public surface (observe,
cancelCountdown, reset) so the shared contract is readable without
tracing the state machine. Addresses the docstring-coverage check on
PR #210.

* fix(tv): stop the intro countdown the moment playback stops

Drops the 750ms stall debounce on playbackActive. It delayed every
inactive signal, including an explicit pause, so a countdown close to
expiry could still fire after the user pressed pause. Pausing now stops
the timer immediately, and the countdown restarts from full on resume.

Removes settlingFalseEdges and its test along with it. Adds a controller
test pausing at 2.9s of a 3s countdown to pin that it stops rather than
skips.

* fix(tv): Back priority, focus theft during a scrub, and the missing rebuffer filter

Review findings against PR #210 (evulhotdog), fixed on top of that branch
merged with current main.

Back was handled only in the Activity key bridge. On API 36 Back never reaches
dispatchKeyEvent, so a countdown Back hid the controls or exited the player
instead of cancelling; on older Android the branch ran BEFORE the scrubber's
Back path, so Back during a scrub cancelled the countdown and left the scrub
running. Countdown-Back now sits in the BackHandler ladder below clean-seek and
scrub, and the legacy bridge is gated on the same conditions so the two agree.

The countdown prompt claimed focus unconditionally. The scrubber treats losing
focus as COMMIT, not cancel, so a prompt appearing mid-scrub committed a seek
the viewer never confirmed — the intro banner silently moving playback
position. It now takes focus only when no scrub or clean seek owns it; the
button still appears and is still reachable.

The PR's description says a brief rebuffer no longer resets the countdown, and
cites a SettlingFalseEdgesTest that does not exist on the branch. playbackActive
was raw `isPlaying && !isLoading`, and the controller restarts from full on any
pause, so every stutter granted a fresh countdown. Added the missing filter:
settlingFalseEdges passes true through immediately and only reports false once
it has held 1.5s. The author's deliberate "a real pause restarts it" test is
preserved — an earlier attempt to resume from remaining time failed that test,
which is what showed the filter was the intended fix rather than the controller.

NOT fixed, deliberately: after cancel or expiry the focused node is destroyed
and its replacement declines focus, so nothing owns focus. The suppression is
intentional (the viewer pressed Down to navigate away), and choosing a
successor is a design decision on the author's feature.

shared 1059, androidTvApp 993, lint clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(tv): land focus on the timeline after Skip Intro, not nowhere

Pressing Skip Intro with the controls up removed the focused prompt and  stranded focus. The skip handler now requests the scrubber, the same target D-pad Down from the prompt lands on.

* fix(tv): stop the Skip Intro countdown the moment you pause

Pausing waited out the 1.5-second "just a hiccup?" grace window, so the countdown pill kept draining under a paused picture and could even skip the intro after you'd stopped watching. A pause is a real button press, not a buffering stutter, so it now stops the countdown on the frame of the press. The grace window still does its job for actual stutters, and hitting play restarts the countdown from full.

* fix(tv): fit the phone-first sign-in on screen without scrolling

The ACCOUNT step stacked ~660dp of content into the TV's 540dp viewport
inside a verticalScroll. On entry, the focus claim on 'Sign in with a
password' scrolled the header chrome (wordmark, journey progress, eyebrow)
off the top — and since only the card's two buttons are focusable, there
was no D-pad path to ever scroll back. First-run users never saw the
onboarding progress indicator on this step.

Bring the branch inside the viewport instead of restructuring:
- outer vertical padding down to the 24dp overscan floor, and the
  header/eyebrow spacers to the compact values the password branch used
- QR card ghost buttons drop to the password card's compact 18sp spec
  (the 22sp default wrapped the long label to two lines), full-width
- card rhythm 12dp -> 8dp, redundant spacers around the divider removed,
  card 300dp -> 320dp so the compacted labels stay single-line

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* refactor(tv): one control spec across the auth flow's forms

The four onboarding forms (server setup, sign-in, sign-up, first-run
setup) each sized their own controls: text fields at 60/52/default dp,
primary actions at 58/64 dp AuroraPrimaryButtons or 32dp TvHeroActionPills,
and two competing field-label idioms (mono caption above vs Material
floating label — the latter renders oversized in the border notch at TV
type scale, which is why sign-in dropped it).

Now every field is 56dp with the mono caption idiom and every primary
action is a 60dp full-width AuroraPrimaryButton, both sized from the new
TvAuthFormDefaults; card tertiary actions share the compact 18sp ghost
spec. Sign-up and first-run setup swap their pills for the Aurora
buttons, and their shared FieldText aligns to the flow's 17sp.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(tv): land server-setup focus on the phone-pairing card

Companion pairing is the recommended path, so it gets first focus
(product call 2026-08-14, reversing the 2026-07-10 field-first default).
Auto-focusing the URL field also popped the IME over the form on
arrival, and the IME resize scrolled the header chrome off the top of
the 540dp viewport with no obvious way to bring it back.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* style(tv): match server setup to tvOS TVServerSetupView

Side-by-side against silo-apple's TVServerSetupView (1920x1080pt -> 0.5x
dp map): phone card pill moves top-leading and its copy left-aligns under
the centered beacon, adopting tvOS wording (iPhone -> phone); manual card
headline becomes 'Enter the server address', placeholder silo.example.com,
and the primary reads 'Connect to server'; a lock + 'Secure HTTPS is tried
automatically.' line fills the note slot when no error/cleartext notice
shows (truthful here — bare hosts probe https first); OR-divider hairlines
fade like tvOS; journey progress maps 430pt -> 215dp on both auth screens;
Headline drops to 20sp (36pt map + readability floor) so the longer
headline holds one line.

Kept deliberately divergent: URL shortcut chips instead of tvOS's
protocol/port disclosure, and phone-first default focus (tvOS still
defaults to the host field, which is harmless there — no auto-IME).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(tv): pin the server-setup chooser to an exact height

heightIn(min) left the cards' max height loose, so fillMaxHeight was a
no-op, the phone card collapsed to its pill, and the weight(1f) box
holding the beacon and copy measured zero — the card body never rendered.
tvOS pins the same chooser to 580pt (frame(height:)); an exact 300dp does
the equivalent here and also keeps the IME resize from squeezing it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(tv): auth fields summon the IME on select, not on focus

Focusing any auth-form field (initial claim or D-pad travel) popped the
soft keyboard over half the form. The server-address field already
suppressed that with showKeyboardOnFocus=false plus an ENTER/SELECT
key-up handler; that handler is now the shared tvShowImeOnSelect()
modifier, applied with showKeyboardOnFocus=false to every field across
sign-in, sign-up, and first-run setup. Focus can rest on a field
silently; SELECT or a tap opens the keyboard, and the IME's Next/Done
actions still advance and submit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(tv): skip auth-form focus claims for pointer users

Opening the password form (or signup/first-run setup) by click still
popped the IME: a programmatic focus claim on a text field in touch mode
shows the keyboard even with showKeyboardOnFocus=false. Pointer users
can click the field themselves, so in touch mode the claim is skipped
entirely; D-pad users keep it (focus must land somewhere) and the
select-to-summon behavior from the previous commit applies.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(tv): re-run auth focus claims when input mode flips back to keys

Skipping the claim in touch mode left a trap: after any pointer
interaction the claim was skipped (or had burned its retry budget on
buttons that refuse focus in touch mode), and when the viewer picked the
remote back up nothing re-claimed — no focus owner, dead D-pad. The
claims now key on the snapshot-backed input mode: skipped while Touch,
re-run the moment key input flips the mode. Sign-up/setup route the same
gate through rememberTvContentInitialFocus's contentKey (null in touch
mode), keeping its focus tracking attached unconditionally.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(tv): stop the select KeyUp leak; add SiloTvFocus debug tracing

Activating 'Sign in with a password' delivered the tail KeyUp of that
same press to the just-focused username field, whose show-IME-on-select
handler acted on any select KeyUp — keyboard up, D-pad captured by the
IME, and the form's buttons unreachable. The handler now requires the
KeyDown to have landed on the field too.

Debug builds also get a SiloTvFocus logcat channel (window focus
gain/loss, splash key gate, focus claims + results + input mode, IME
summon/suppress decisions) so 'keys do nothing' reports can be told
apart: window-focus theft by the launcher logs a LOST line and then
nothing, an exhausted claim logs its result, and IME capture logs the
summon that caused it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(tv): hide the IME the legacy field pops on the login form's claim

The value-based text field shows the keyboard on a programmatic focus
claim regardless of showKeyboardOnFocus (SiloTvFocus traces, 2026-08-14):
the claim reports Focused, no select KeyUp reaches the field, and the IME
still appears. Hide it two frames after the claim so the form always
arrives quiet; SELECT or a click on the field still summons it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(tv): route vertical D-pad out of single-line auth fields

The legacy text field consumes DPAD up/down for cursor moves a
single-line box cannot make, so focus could never leave a focused field
by remote — 'Sign In / Back to phone sign-in / Change server' were
unreachable below the password field. The fields' shared key modifier
now hands vertical D-pad to FocusManager.moveFocus; an open IME owns
the keys before the app sees them, so this only applies to the quiet
field state, and left/right stay with the field for in-text cursor
movement.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(tv): dismiss the stock IME when a select-to-show field is disposed

TvStockKeyboardPolicyTest pins the rule that any surface raising the
stock keyboard must also take it down on disposal — Android TV leaves it
floating over the next screen, still eating the D-pad. Moving the
show-on-select handler out of TvServerSetupScreen (which had the
disposal half) into the shared modifier dropped that half; putting
TvHideStockImeOnDispose() inside the modifier restores it for every
field that uses it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(tv): unblock the auth flow on a remote (review findings)

Six findings from the PR #228 review, all reproduced on an Android TV
emulator at 1920x1080 @ 320dpi. The three focus dead-ends were each
independently merge-blocking: on the affected screens the flow could not
be completed with a remote at all.

Focus traps. Only the first field on each screen carried
tvShowImeOnSelect(), so vertical D-pad reached the second field and
stopped: the single-line box consumes UP/DOWN for cursor moves it cannot
make. Setup's EMAIL/PASSWORD and signup's EMAIL/PASSWORD/INVITE now carry
it, so the first-run admin account can be created. TvAuthFieldEscapePolicyTest
pins the invariant per field and names the offender when it regresses.

showKeyboardOnFocus. The premise behind the whole quiet-arrival design was
unsound: foundation 1.8.0 documents the option as unsupported on the
`value: String` overload (BasicTextField.kt:639,796) that every
OutlinedTextField here uses, so D-pad focus still popped the IME. Moved
suppression into tvShowImeOnSelect(), which can tell a focus arrival from a
deliberate SELECT, and dropped the login screen's frame-counting workaround
that had been papering over one instance of it. Pointer users are exempt.

Key consumption. The stray-KeyUp branch logged "suppressed" and then
returned false, forwarding the very event it declined to act on; vertical
D-pad returned `moved`, handing a failed move back to the field that
cannot use it. Both consume now.

Create Account. `down = backToPhoneFocus` jumped over it, and the
intervening label is not focusable, so nothing caught the fall — the
button was unreachable on signup-enabled servers. The chain routes through
it in both directions when signupEnabled, and is unchanged without it.

Chooser clipping. The exact .height(300.dp) clipped the manual card:
"Connect to server" rendered as a blank pill, its label measured 6px in a
96px button, in the screen's default state. height(IntrinsicSize.Min) plus
a 300dp floor keeps fillMaxHeight resolving for the cards without capping
the taller one; the label now measures 48px.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(tv): block the IME session instead of racing it closed

Focus arriving on an auth field popped the stock keyboard for 120-270ms
before the reactive hide landed, because Material OutlinedTextField sits on
the value: String BasicTextField, which requests the IME on focus and
ignores showKeyboardOnFocus. Hiding after the fact is a race we lose.

Refuse the platform text-input session up front with
InterceptPlatformTextInput, gated by a token-held gate that SELECT opens, so
the IME is never asked for. The interceptor instance is the restart signal:
it captures the gate value so remember() yields a new object on flip, which
is what lets SELECT summon the keyboard at all.

Verified on a Shield: mInputShown stays false on focus, true after SELECT.

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

* style(tv): centre the aurora eyebrow on its own axis

The eyebrow drew one leading hairline, so the Row centred but the label did
not, leaving it visibly right of the title stacked beneath it. Mirror the
hairline. Every eyebrow in the auth flow sits in a CenterHorizontally column,
so this is fixed in the component rather than per call site.

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

* fix(tv): fit the credential form inside the 540dp viewport

The sign-in branch measured 610dp against a 960x540dp TV surface, so the
root column scrolled and took the brand mark and step chrome off the top
with no D-pad way back. The signup-disabled case only appeared to pass at
539dp because the top padding was cheating 4dp under the overscan floor.

Restore the safe area on both branches, widen the card to 520dp, drop the
subtitle, and put the three secondary actions on one row instead of stacking
them. Measured at w960dp-h540dp: 456dp, or 481dp with an error showing.

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

* feat(profiles): display uploaded profile avatars

Profile dropped avatar_url at parse time, so the presigned object-store URL
— the only fetchable form of an upload, since the client cannot sign R2
requests — never reached the UI. resolveAvatarUrl then compounded it: seeing
a slash in "upload:profile-avatars/..." it treated the ref as a path and
built "$serverUrl/upload:...", a guaranteed 404 that rendered as initials.

Carry avatar_url and avatar_source on Profile, prefer the server URL, and
return null for an upload ref with no URL rather than fabricating one.
Ref and URL travel together as ProfileAvatarRef so a screen cannot be
half-migrated into showing initials while another shows the picture.

Presigned URLs expire in 900s, so cache the bytes under the signature-free
part of the URL: keying by the full URL would miss on every re-sign and
re-download forever. An avatar that loaded once keeps rendering from cache
regardless of URL age; a fetch that does fail retires the URL and falls back
to initials rather than an empty circle.

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

* fix(player): stop a stray media key from killing the app

A transport key on a TV remote arrives as startForegroundService, because
the manifest advertises MediaSessionService.SERVICE_INTERFACE with no
MediaButtonReceiver, so Media3 mints the media-button PendingIntent with
getForegroundService(). With nothing queued the player is idle on an empty
timeline, shouldShowNotification bails before onUpdateNotification is ever
reached, startForeground is never called, and the platform watchdog kills
the process ten seconds later. Observed twice on a Shield (API 30).

Media3 already ships the escape hatch — refusing the synthetic media-button
caller in onGetSession runs its own stopSelfSafely(), the only shutdown that
is legal for a foreground-service launch. We returned the session
unconditionally, so it never ran. Overriding onUpdateNotification would not
have helped; it is not reached on this path.

Also terminate the PiP branch when its action is unusable. That path cannot
crash (getService does not arm the watchdog) but a stale intent from a dead
process left an idle service holding a live player forever.

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

* fix(tv): draw the real wordmark in the shell top bar

The bar set the literal string "SILO" in FontWeight.Black — which the
branding guide lists verbatim under Don't ("typeset 'Silo' in place of the
supplied wordmark") — while the auth screens already rendered the genuine
asset. Use R.drawable.silo_wordmark in both.

24dp tall, the largest round value that keeps branding's clear-space rule
(one bar counter, 6.32% of lockup height per side) inside the 32dp bar row.
Untinted: the white lockup carries the signal palette in its three bars and
the guide forbids recolouring the mark.

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

* feat(tv): make Diagnostics its own settings category

Diagnostics was a row buried at the bottom of the Server pane that jumped to
a top-level route rendered outside TvMainShell — no top bar, no on-screen
Back. tvOS gives it its own rail category, fourth, before Server. Match that
and render it in the detail pane like every other category.

Delete the hand-rolled focus ladder rather than repair it. Up at the top of
the consent order returned the row itself and the key was consumed, so
SEND REPORTS TO, the destination rows and Privacy Policy were on screen but
unreachable by remote — the destination feature existed and could not be
used. Both destinations now sit behind a picker row, as on tvOS, and focus
is plain Compose focus search.

Drop read-only rows from the focus graph (Status, Destination, sent
history, empty states) so D-pad only stops on controls that act, and dim
disabled labels that previously painted white regardless of enabled.

Remove the Privacy Policy row: it was the only openUri in the TV app and
AndroidUriHandler throws when nothing can handle the intent, which is normal
on a Shield with no browser. The URL survives as text in the disclosure.

Sent history sits before the manual report because bring-into-view only
scrolls to reveal a focused node, so a trailing read-only block would be
permanently unreachable at 960x540dp.

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

* fix(tv): stop the diagnostics pane stranding its read-only rows

Dropping read-only rows from the focus graph left them strandable: Compose
scrolls only far enough to reveal the focused node, so FEATURE STATE sat
above the first focus stop with nothing focusable to scroll back to, and
Status/Destination could not be recovered once off screen. The same gap
between Crash Reports and Send Diagnostics Now — privacy footer, empty
pending row and the whole sent log — made one Down press jump ~320dp.

Give boundary rows a bring-into-view rect taller than themselves, the
tvImeAwareFieldContext idiom aimed at list edges, and group sections by kind
so the controls are contiguous: state, pending, controls, then the sent log.
The Crash Reports row now pre-reveals the footer that qualifies it, which
splits the long jump into 103dp + 124dp.

The clamp is load-bearing: Compose treats a rect overhanging both container
edges as already visible and scrolls by zero, so an unclamped reveal would
silently do nothing. Sent history drops to 4 entries because 5 left exactly
zero slack in the tail budget.

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

* feat(phone): restyle settings and drop the admin surface

Three interlocking changes; the files overlap too much to split honestly.

Settings looked default because its section headers rendered inside the
cards, no row had a description, nothing was divided, and pickers were
indistinguishable from read-only rows. Rebuild it against the webapp's
mobile language: grouped surface cards, headings above them, a description
on every row, hairline dividers that skip the first, and trailing value plus
chevron so a picker reads as one. 24 descriptions come verbatim from
contracts/settings/v1, the manifest Apple already shares, so the wording
matches across clients rather than being invented here.

The phone had no spacing or dimension tokens at all, so every dp was
hardcoded at its use site; add Spacing.kt and route the tree through it.
Settings cards were also abusing primaryContainer as a surface role because
the surfaceContainer* ladder was never populated — populate it. That also
fixes four cast components that were silently getting M3's purple baseline.

Admin and session management are gone from phone, TV and shared — not
hidden, deleted. The user's call; AGENTS.md is updated to record it as a
deliberate divergence from Apple, which still surfaces the STATS dashboard,
so nobody re-adds it as a missing feature. Device pairing stays.

The profile menu turned out to be three byte-identical copies, since Home
and Libraries paint their own chrome; restyling one would have left it
unchanged on the two screens it is most often opened from. Consolidated to
one ProfileMenu behind three anchors, with a test that fails if a fourth
appears. Sign out now confirms from both entry points through one shared
dialog, and the copy states what actually happens: logout keeps downloads
and the server registry, verified against AuthRepository.

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

* feat(player): expand video past bars encoded into the picture

Scope films arrive hard-matted: the source here is a 3840x2160 coded frame
with the 2.39:1 image sitting inside ~277px of baked-in black per edge,
because Blu-ray and UHD only permit 16:9 frame sizes. FIT fits the coded
frame, correctly, so a 16:9 frame on a 19.5:9 panel pillarboxes by 280px a
side and the file's own matte adds ~184px more. Four-sided waste, no bug.

The server cannot answer this — its aspect_ratio comes from ffprobe's
display_aspect_ratio and reports 16:9 for exactly these files, with no
cropdetect anywhere. So measure the matte from sampled frames and promote
FIT to ZOOM only while the clip provably falls inside it.

Sampling uses PixelCopy off the video SurfaceView rather than a TextureView
or a GL effects chain, so tunneled decode, HDR10 and Dolby Vision
passthrough are untouched — the readable-frame alternatives break all three.

Gated on proof, because guessing here costs picture: four consecutive
frames must clear the clip by 2%, one frame that stops clearing reverts
immediately, a fade to black counts as no evidence, and two engage/revert
cycles latch it off for the item. Off by default, device-local per profile.
videoGravity keeps its meaning — fill and stretch bypass this entirely.

Blanket ZOOM was rejected: it is only lossless when the image is wider than
the display, and would cut ~128 source px per edge off a 1.85:1 film.

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

* feat(player): fill the screen by default, clear of the camera

Expanding past a file's encoded matte was shipped as an off-by-default
toggle, so the normal case was something you had to discover. Make it the
default and give the cutout its own choice, since once the image fills the
width the punch-hole lands on the picture rather than on a bar.

Fill the screen: Clear of camera (default) / Full width / Off. Defaulting on
is safe because the gating is unchanged — without proof the crop falls
inside encoded black it stays at FIT, so the failure mode is today's
behaviour, not a cropped picture.

Inset symmetrically rather than only on the camera's edge, giving up 10% of
image area. The cutout swaps sides between ROTATION_90 and ROTATION_270, so
a single-edge inset would slide the picture 139px sideways when the phone is
flipped end for end; it also reads as a rendering fault next to the
symmetric letterbox above and below. A default has to be unimpeachable.

Narrowing the box to clear the camera also shortens the image, so the
default keeps ~126px top and bottom rather than the ~68px Full width gives.
Bars on four sides still, but 23% more picture than FIT.

Content with no stored bars has nothing to eat, never reaches the engage
threshold, and stays exactly where FIT puts it — pinned by a test and
stated in the setting's copy so an unchanged TV episode does not read as a
broken feature.

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

* fix(player): fit the measured content rect, not zoom to width

Promoting FIT to ZOOM only ever asked "can I fill the width". For content
narrower than the display — a 1.90:1 series on a 2.167:1 panel — that needs
far more crop than the matte can absorb, so it declined and left the picture
short of the top and bottom edges even though it would have fit easily.
Right answer to the wrong question.

Fit the measured content rect to the box instead and let residual black fall
where the aspects genuinely differ. One rule covers both: content wider than
the box fills the width and keeps a real letterbox, content narrower fills
the height and keeps a real pillarbox, content with no matte does not move.

This is strictly safer than the threshold it replaces. With scale
s = min(Bw/Wc, Bh/Hn), the horizontal is never clipped at all, and the
vertical clip is (Hn·s − Bh)/2 + M·s — exactly the scaled matte when height
binds, strictly less when width binds. Bounded by construction rather than
by a guard, so the crop fraction leaves the decision path entirely.

The remaining margin covers measurement, not arithmetic, and is now
proportional: a flat 2% of coded height was a rounding error against a scope
film's 12.9% matte but ate two thirds of a 1.90:1 title's 3.3%, which is
what half-declined the reported case.

Hold the estimate as a running minimum. A dark scene can no longer widen the
crop, a frame with picture at the matte edge narrows it permanently, and a
monotone minimum cannot oscillate — so instant revert and latch-off both
fall out of one property instead of two thresholds.

Expansion was also visibly late. Nothing animated it; it was latency. First
samples now run at 100ms rather than 250ms, and the measured rect is cached
per coded resolution and read during composition, so a replay or a resume
starts already expanded — 300ms cold, 0ms cached. Live frames replace the
seed outright, so a stale entry self-corrects and is never written back.

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

* fix(player): anchor subtitles to the visible content frame

selectSubtitleCanvasRect preferred displayedVideoRect under ZOOM/FILL
whenever the frame's visible size differed from the view's. The intent was
right — a zoomed video covers the whole view, so captions should too — but
that rect is measured from the PlayerView's top-left while applyRect applies
it as margins inside the content frame. Right idea, wrong coordinate space.

It only bites when the frame is offset inside the view, which is exactly
what fitting the content rect produces: on a 2:1 title the view is 2814 wide
while a stale fitted frame is 2560 inset by 127, so a view-space rect applied
at frame margin zero pushed captions 127px right of centre. The same
fallback spanned the full 1583-tall frame against a 1440 view, drawing
captions 71px below the screen. Off-centre horizontally and clipped
vertically were one bug, not two.

Anchor to the content frame's visible intersection in every mode, which is
the space applyRect already speaks. displayedVideoRect survives only for
having no frame at all, so resizeMode no longer selects anything and the
parameter goes. Captions now centre on 1560 in all three fill modes at both
landscape rotations, and the canvas bottom lands on the visible edge.

The clamp's original case — 4:3 on a 16:9 TV — is better served too: it also
has an inset frame and was getting the same wrong-space treatment.

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

* fix(phone): centre the landscape player HUD on the display

The fullscreen player controls were padded by the raw safeDrawing insets,
which are lopsided in landscape (camera cutout on one edge, nothing on the
other), so the toolbar, progress bar and transport row all sat off the
device's centre line.

- Anchor the skip/play/skip cluster to the true centre of the overlay
  instead of the inset-padded column.
- Apply the larger horizontal safe-drawing inset to both sides so the
  toolbar and progress bar stay clear of the camera and symmetric.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(tv): show the skip chip on every seek and report the burst total

The ±delta chip only appeared for hidden-controls D-pad skips. The transport
buttons and the remote's skip keys took the reveal path, which showed the
overlay but no chip — so a press just made the bar twitch. Both paths set
the chip now.

The chip also reported the per-press constant, not the coalesced burst:
three fast forward presses coalesce into one +90s seek (200ms trailing-edge
QuickSkipAccumulator, matching tvOS) but read "+30s" three times, which made
the debounce look like dropped presses. The view model exposes the burst
origin so the chip shows +90s.

With the transport visible the chip drops its own track line — the live
scrubber already reports position — and sits in the gap above it. One
render site across both cases so the reveal-path skip doesn't tear down one
AnimatedVisibility and fade in another mid-transition.

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

* fix(tv): make the hidden-controls hold-seek honest about its rate

The hidden-controls scan advanced a flat 2s of content per 100ms tick at
"1×" — 20 seconds per real second — so every multiple on the chip was a
twentieth of the truth: "8×" scanned at 160×. The focused scrubber's
hold-seek had already been corrected onto TvSeekRateLadder (rate × tick
seconds per tick), which left the same gesture running 20× faster with the
chrome hidden than with it up.

Both paths now walk one ladder. Ticks cover rate × real time; the ceiling
is derived from the runtime instead of a fixed 32×, since at honest rates a
fixed ceiling cannot serve both a 22-minute episode and a three-hour film;
and stepping "slower" past the bottom stops at the base rate instead of
crossing zero and silently reversing direction (the old signed ladder ran
… -1, 1 …). Base rate is 2× — 1× scans at playback speed, so a press did not
visibly move.

tvOS carries the same 2s-per-tick arithmetic (holdSeekBaseStep); filed as
Silo-Server/silo-apple#165.

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

* feat(tv): route the settings HUD by entry point, like tvOS

D-pad Down from clean playback now opens the HUD, landing on Audio if the
title has audio tracks, else Subtitles, else Video (tvOS
preferredPlaybackHUDTab). The remote's Menu/Settings key and the transport's
Tune button — the same settings entry point — both land on Video (tvOS
applyHUDEntryPoint(.settings)). Previously Down only focused the transport
and every entry hard-reset to Info, so changing an audio or subtitle track
meant traversing two to four panes from Info every time.

Down with the transport overlay already up is unchanged: it still moves
focus into the button row under the scrubber. The old single OpenHud action
splits into OpenSettingsHud and OpenPlaybackHud, gated by a dpadDownOpensHud
flag that defaults off so the overlay's own key handler keeps its behaviour.

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

* fix(tv): dismiss the settings HUD with a single Back press

Two independent faults each cost an extra press.

openHUD() forced showControls true — invisible while the HUD is up, since
the transport overlay is gated on !hudOpen — but closeHUD() never put it
back. Closing a HUD opened from clean playback therefore revealed a
transport overlay nobody asked for, and a second Back was needed to reach
the picture. closeHUD() now restores whatever the chrome was on open.

The HUD's key handler consumed Back only on KeyUp. Compose maps an
unconsumed Back/Escape KeyDown to FocusDirection.Exit
(FocusInteropUtils.toFocusDirection) and AndroidComposeView runs a focus
search on it, which moved focus out of the HUD before the UP arrived; key
events route only to the focused subtree, so the UP never reached the
handler. The panel stayed up with no focused pill and the second press,
now unconsumed end to end, closed it via BackHandler. The handler consumes
the DOWN as well. This is the same mechanism behind the 2026-07-08 QA note
on the transport overlay ("deselected the button instead of dismissing"),
which was fixed at the key bridge without naming the cause; the same
KeyUp-only idiom remains in TvMainShell, TvLibraryBrowseControls,
TvMediaInfoDialog, TvPersonDetailScreen and TvPlayerScrubber.

Debug builds now trace Back through the bridge, the HUD handler and the
BackHandler ladder, plus HUD focus gain/loss and open/close, on the
SiloTvFocus channel — that trace is what told the two faults apart.

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

* feat(tv): redesign the player settings HUD

Same structure — pill tabs, panes, picker — proportioned and wired like
TVPlayerInfoHUD instead of a portrait slab on a landscape screen.

Composition. The panel rendered at 490dp, not the intended 680: it was
`.widthIn(max = 680).fillMaxWidth(0.72f)`, and in that order fillMaxWidth
takes its fraction of the already-capped max. That one accident was the
tab-rail clipping ("Chap…") and most of the cramping. Now fillMaxWidth then
widthIn, at 74% / 720dp. The tab rail floats over the video and the card
beneath holds only the pane (tvOS 1100×380pt on 1920×1080: wide and short),
wrapping its content between 156 and 300dp instead of a fixed 360dp — a
two-row Audio pane is a two-row card. Idle pills take the tvOS dark fill and
hairline so they survive bright frames; the card is near-opaque (0.96) for
legibility, since it is a settings surface read from the sofa.

Panes. Two columns everywhere: Stats is a 5+4 grid instead of one column
with each value 500dp from its label; Audio gains a read-only Output column
(codec, passthrough vs PCM, decoder) instead of a lone full-width column;
Video is rebalanced from 8-vs-1 to Playback | Output + Automation, so all
nine controls fit without scrolling. Info shows "H.264" / "DTS-HD" instead
of shouted mimes; Stats keeps the raw strings.

Focus. The card is a focus group with custom enter/exit: Down from a pill
lands on the pane's first focusable row (top-left), and Up from anywhere in
the pane returns to the SELECTED pill — with focus-driven selection, the
nearest pill would switch panes as a side effect of leaving (tvOS
defaultFocus(activeTab), for the same reason). Panes attach one shared
entry requester to their first enabled row; read-only panes fall back to
the default search rather than redirecting to an unattached requester,
which would cancel the move.

Toggles. The five boolean rows (HDR passthrough, Dolby Vision, Auto-skip
intro, Auto-play next, subtitle Outline) flip in place on Select with no
chevron and no picker, matching tvOS HUDToggleRow. The selected pill dims
while focus is down in the pane, so the one solid-white element on screen
is the control you are on.

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

* fix(settings): push pending writes before pulling from the server

Local writes sit in ServerSettingsFlusher's ~750ms debounce. A
refreshFromServer() inside that window read the server's OLD value for the
key and wrote it back over the change the user had just made. The TV player
refreshes at every load, so a setting toggled in the HUD and followed by an
in-place session restart reverted every time; the detail screen refreshes
on entry, so a setting changed just before opening it could revert too.

refreshFromServer() now drains the flusher first, so the pull observes the
write. Offline, both fail and the local value stands.

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

* feat(tv): apply a Dolby Vision toggle to the current session

Toggling Dolby Vision in the HUD only ever took effect at the next playback
start. Local track selection re-applied immediately, but for a single-track
DV file — the common case — the part that matters (base layer vs DV
delivery) is decided in the server's plan from the capability snapshot sent
at load. Nothing on screen said so, so the toggle read as dead.

The toggle now restarts the session in place at the current position when
the current file is Dolby Vision, reusing the version-switch path so the
old session stays mounted until the replacement is ready. onSelectFileVersion
and this share the extracted restartSessionInPlace(). A non-DV file has
nothing to re-plan and is left alone.

The row says what is happening: "Off · Applying…" from the press until the
replacement session is adopted AND playing — adoption is quick, but the
viewer's wait is the rebuffer after it — with a 20s cap so it cannot stick
if the replacement never arrives. Presses are swallowed meanwhile rather
than the row disabled: a disabled row is not focusable, and dropping focus
off the row the viewer just pressed left the next press landing on nothing.

HDR passthrough is deliberately not restarted: it feeds only local Media3
track presets (allowHdr) and is not part of the server plan, so a restart
would change nothing.

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

* feat(tv): show codecs in the Version selector like tvOS

versionShortLabel now carries resolution · video codec · DV/HDR · audio
codec, matching DetailPlaybackFormatting.versionShortLabel on tvOS, so
the detail Version pill reads "4K · HEVC · DV · TrueHD" instead of
"4K · DV". The player HUD's Version row shares the helper and follows.

versionPickerLabels drops its codec discriminator (codec is now part of
the base label) and disambiguates colliding rows by size, then container.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(tv): style the playback selector menus like the top-bar cascade

The Version / Audio / Subtitles / Edition dropdowns now draw the same
Skyline glass panel as the library and For You selectors: dim uppercase
header, bare rows that invert to the white capsule on focus (semibold
title, dimmed detail, leading check slot), hairline + hint footer. The
Material DropdownMenu remains only as the invisible anchored host.

The option list is capped at ~6 rows and scrolls inside the panel with
the header and footer pinned, so a long subtitle list no longer runs off
the bottom of the screen; the rows fade out (DstIn mask, so no colour
seam against the translucent panel) with a chevron on whichever edge
still has more rows.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(tv): choreograph the detail page's section scrolls

Focus entering Cast, Details or More Like This now anchors the section
top to a fixed viewport line (18%), the way the episodes section already
centered itself, so every Down between sections is a uniform step rather
than a gutter nudge of a different size. One 400ms fast-out/slow-in spec
drives every scroll on the page (anchors, return-to-hero, bring-into-view
fallback) instead of the 260ms/620ms ease-in-out mix.

The hero backdrop now recedes with the scroll — fades toward the page
background over the first 40% of its height and drifts at 0.4x (light
parallax) — read in the draw phase so scrolling never recomposes the hero.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(tv): pin the focused card in horizontal rails

Card carousels (TvMediaRow, Cast & Crew, episode rail) now scroll with a
shared bring-into-view spec that keeps the focused card's leading edge at
the row's start padding — the tvOS/Netflix rail model — so every Right or
Left is one uniform card-sized glide (480ms fast-out/slow-in, tuned on the
Shield) instead of a variable nudge once the card reaches the trailing
gutter. Row ends still clamp; vertical requests keep bubbling to the
enclosing column's own spec.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* perf(tv): stop per-keypress recomposition churn in rails and the hero

Rail pin: express the focused-card pin as a one-shot clamped
animateScrollBy on focus (tvRailPinOnFocus) instead of as the
BringIntoViewSpec's scroll distance. Compose re-launches the
bring-into-view scroll on every layout pass while the spec still reports
a non-zero distance, and a pinned position is unreachable when the row is
clamped at either end, so a concurrent vertical row scroll spun a new job
per frame — measured on the Shield as p90 121ms and near-frozen vertical
navigation. The rail's automatic spec is now a satisfiable minimal reveal
with the same 480ms motion.

Composition cost per focus move:
- TvMediaRow remembers each card's action bundle; the producer built four
  fresh lambdas per card per pass, so no visible card could ever skip.
- The Home feed builds the return-target section map once per rows
  snapshot rather than copying every content id on each keypress.
- CardOverlays remembers the preset style and the resolved badges per
  corner instead of recomputing them for every card composition.
- The card context menu returns before allocating its focus requester and
  popup positioner while closed.
- The root hero backdrop reads its animating tint in the draw lambda (it
  recomposed the whole backdrop + crossfade every frame of the tween) and
  caches its two mask brushes per size.
- Cast rail remembers cast.take(24); card shapes hoisted.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* build(tv): add a Baseline Profile generator for the TV app

ART will not AOT-compile debuggable builds and only compiles release
builds during idle-time dexopt, so first launches JIT the Home feed, top
bar and cascades while the user is navigating. Measured on a Shield:
47% janky / p90 121ms JIT-warm vs 26% / p90 29ms once compiled.

:baselineprofile-tv is the TV twin of :baselineprofile — a macrobenchmark
targeting :androidTvApp that records cold start plus a d-pad browse of
Home. It runs against a connected, signed-in TV (a headless managed
emulator would only ever record the login screen):

  ANDROID_SERIAL=<tv> ./gradlew :androidTvApp:generateBaselineProfile \
      -PallowDebugReleaseSigning=true

:androidTvApp applies the consumer plugin and merges the generated
profile into release APKs; profileinstaller was already linked.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* build: verification metadata for the TV nonMinifiedRelease classpath

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* docs(tv): note the API 33+ requirement for baseline profile collection

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* chore(player): opt-in subtitle placement geometry logging

Adds a SiloSubtitleGeom debug tag (enable with
`adb shell setprop log.tag.SiloSubtitleGeom DEBUG`) that prints where the
caption canvas landed in window / PlayerView / content-frame space and the
cues' own anchoring, so a misplaced caption can be attributed to the right
coordinate space from device output instead of static reading. Silent
unless enabled.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(tv): left-align the marquee logo and keep grid rows clear of the top bar

The Home marquee logo drew centred in its full-width block (Coil's default
alignment for a Fit image), floating wide logos away from the meta and
synopsis left edge; ThumbhashImage gains an alignment passthrough and the
marquee pins the logo CenterStart within its 440dp max width.

Vertical grids (Collections, audiobook groups, library browse, TvCatalogGrid)
use a bring-into-view spec whose leading gutter is at least the grid's top
content inset, so a row revealed by scrolling back up parks below the top
bar instead of at 12% of the viewport (~65dp on a 1080p canvas, under the
94dp bar), which left the first row's posters cut off.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(tv): return from a collection to the card it was opened from

The Collections/Recommended/Browse pill selection lived in a plain
remember in the shell; opening a collection is an outer route that takes
the shell out of composition, so on Back the nested nav restored the
Movies tab but the pill was gone and the tab re-landed on Recommended,
reading as "Back went Home". The selection is now saveable.

Collection opens also arm the shell's content hand-back (as item-detail
opens do), so the return resume claims content synchronously instead of
letting the default search settle on the top bar for a beat; and the
Collections grid remembers the last-focused card (saveable), scrolls it
into composition and makes it the grid's entry target, so focus lands on
the exact card that was clicked.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(tv): land content Up on the selected tab, and return to the browse card

Content->bar Up is meant to land on the selected tab, but from a control
that sits directly in the screen (the For You filter pills) Compose's 2D
search still escaped the content group — the group's exit=Cancel only
guards the level of the search that owns the focused row — and landed on
the geometrically nearest bar item, the Search icon from the left edge.
A move that leaves content is now treated like a failed move and routed
to the selected tab. Watchlist/Favorites (For You's dropdown pages, not
tab roots) map to the For You tab for that routing.

Library tabs, the Libraries screen, Watchlist/Favorites/History and
Browse now open item detail through the shell's content hand-back, and
the browse grid points its focus entry at the return-target card while
attached, so Back from a detail lands straight on the card that was
opened instead of the Sort button first.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(tv): let Up leave the calendar's first day shelf

The shelf→controls hand-off judged arrival by "the list has focus",
which is already true while a shelf card is focused, so the claim
returned focused without ever requesting the week-strip date and Up from
the first shelf was a silent no-op that re-armed 80ms later. Arrival is
now observed on the controls row itself, and that row (list item zero,
which the shelf snap scrolls out of the composed window) is scrolled
back into composition before the claim so its requester is attached.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(for-you): render For You with the Home hero + carousel rows

TV: TvRecommendationsScreen now renders TvSkylineSectionFeed (surfaceKey
"for_you") — the same focus marquee hero, ambient backdrop and poster rows
as Home — with Home's detail-return / first-row / up-fallback wiring. The
in-screen For You/Watchlist/Favorites pill band is gone; the top-menu For
You dropdown is the only switch. Watchlist/Favorites still render inline
with focus landing in the grid. Home's detail-return helper is generalised
to TvDetailReturnFocusState so both Skyline roots share it, and the
For-You-only solid top bar, top-anchor and focus-bridge code (and their
tests) are removed.

Two shell fixes surfaced on the Shield: a dropdown pick now resets the For
You detail-return token (a stale token made the feed swallow the entry
focus bump, dropping focus onto the Search icon), and the entry-request
counter is saveable so a recreated shell cannot restart it below the
screen's saved high-water mark (which silently ignored every later pick).

Phone: RecommendationsScreen shows a FeaturedCarousel built from the
server's "for-you-main" row (flagged featured in the shared conversion)
above HomeSectionRow rows, matching the Libraries Recommended shape; the
hero row stays as a row too so nothing past the carousel cap is lost.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(tv): order the For You dropdown Recommendations, Favorites, Watchlist

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(tv): stop clipping the detail page's season chips and Details focus box

Move the season picker's safe-area inset into the LazyRow contentPadding so
the first chip's focus scale isn't clipped at the row edge, and give the
Details section an inner inset so its focus highlight frames the text
instead of starting flush at its left edge.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(tv): match the Collections grid cards and group headers to Browse

Collection poster cards used the bare TV Material Card defaults, so they
read as a different card family next to Browse. They now share
TvMediaCard's focus treatment (scale, accent border, glow) and caption
metrics (11dp gap, 15.5sp start-aligned title that brightens on focus).
The "12 MOVIES" count line is dropped — it doubled the caption height —
and the monospace tracked-caps group header is replaced by the shared
TvSectionHeader used by Home/Recommended rows, nudged toward its own group.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(tv): only show the For You fallback caption after an actual fallback

"No recommendations yet — showing your saved titles." was keyed on
"saved list showing + no visible feed rows", which is also true when the
user picks Favorites/Watchlist before discover has loaded, so it flashed
on first open. Track the auto-fallback explicitly and clear it when the
user picks a list or recommendations arrive.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(tv): sort and filter for collection, Favorites, and Watchlist pages

The library collection detail page and the Favorites/Watchlist pages
(For You inline + standalone) get the Browse Sort/Filter pills and an
item count, rendered as the grid's header row so they scroll with the
content instead of clipping rows beneath them.

Sort offers the server's own order as the default ("Collection Order" /
"Recently Saved" — no sort param sent) plus Title, Date Added, Year,
Rating, Runtime; the filter panel is the Browse facet panel with the
vocabulary scoped to the collection or list via
/catalog/filters?source=…. Facet groups and match=all|any go through
the same bracket encoding as Browse (extracted into a shared helper).

Shared: CatalogResponse.effectiveSort, sort/order/facet params on the
library-collection items call and getFilters(source, collectionId), and
PersonalListQuery + applyQuery on PersonalListViewModel. Favorites and
Watchlist now fetch via /catalog?source=favorites|watchlist (identical
default order, and it reports total, which the legacy routes do not).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(tv): make the subtitle transaction adapter the only subtitle authority

TV had two independent subtitle-selection authorities. The legacy ordinal path
(onTracksChanged -> resolveAutoPreferredTextSubtitle -> a bare SharedFlow<Int>
-> backend.selectSubtitle) selected a text track straight at the player without
arming a mount owner, so onSubtitleSelectionApplied hit its silent
`pendingSubtitleMountAcknowledgement ?: return` and the transaction adapter
never learned anything. Its committed identity stayed as seeded from the plan,
which the HUD renders. On an "English - Always" profile with an embedded PGS
track the result was subtitles on screen and "Off" in the HUD. The
DefaultTrackSelector's preferred-text hint was a third, silent selector on top.

- Auto-preference, detail-page restore and Off all resolve to a typed
  SubtitleIdentity (the same tvMountedSubtitleIdentity mapping the HUD options
  use) and go through the adapter via a new selectAuto(), which commits like
  select() but does not cancel an in-flight refresh.
- An automatic pick no longer writes the durable per-item preference:
  tvSubtitlePersistenceUpdate Preserves it while the committed identity is the
  one the app chose, and any viewer pick clears the marker and persists.
- Mount requests are typed and carry their owner (TvSubtitleMountRequest), so
  an ownerless mount is unrepresentable and the silent bail-out is gone; a
  rejected mount is now logged under the TvSubtitle tag. Acknowledgements also
  release the remount latch's resolved owner, which nothing was doing.
- The TV selector preset no longer sets a preferred text language, and the
  factory does not forward one. Text enablement is left untouched so
  re-applying presets on a capability change cannot disturb a mounted subtitle.
  The phone preset is unchanged.
- Small reconciliation in onTracksChanged adopts a text track selected by
  anything outside the app (device caption settings, selector quirks) into the
  adapter and logs it loudly. Safety net, not the mechanism.
- The HUD Info tab reads the same committed identity as the Subtitles tab
  instead of asking Media3 which track is selected, so the two cannot disagree.
- Deleted the dead persisted-subtitle-fingerprint restore path, which load had
  been clearing unconditionally since the fresh-restore rework.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(tv): mount an already-mounted subtitle in place instead of replanning

Since the transaction adapter became the only subtitle authority, the launch
auto-pick of an embedded PGS track ("English - Always", direct-play MKV) tore
the stream down: subtitle_replan_mount, a new session, a media-item swap, ~6-8s
of rebuffering and a server-extracted duplicate of the same PGS track mounted
alongside the one already on screen.

Root cause is an asymmetry between the two mounted-subtitle resolvers. Protocol
v3 types EVERY non-burn-in inventory row `delivery = sidecar`, including a row
that merely describes a track muxed into a direct-play stream, so
playbackSubtitleIdentity returns ServerSidecar for it before it ever reaches its
embedded branch. tvMountedSubtitleIdentity still maps the mounted track onto
that row -- the PlayerSubtitleInfo overload of resolveMountedSubtitle matches on
typed metadata -- but the SubtitleIdentity overload matches a sidecar by its
authored `silo-subtitle:N` id alone, which a muxed track can never carry. So the
identity the app derived FROM a mounted track answered "not mounted" when asked
about that same track: isLocallyMountable said false, commitLocallyMountableSelection
declined, and applySelection fell through to the staged server replan.

The pending audio/quality guard and the publication of subtitleTracks into
uiState were both ruled out: onTracksChanged updates uiState before the auto pick
runs, and a launch-time SelectSubtitle carries no audio or quality preference.

- tvResolveMountedSubtitleTrack resolves an identity onto a mounted track
  through the inventory row it was minted from when the identity resolver alone
  cannot, so the two directions of the mapping can no longer disagree. Only an
  identity that is exactly some row's identity gets that fallback, and it must
  still find a mounted track -- catalog-only rows, sidecars the player has not
  loaded and burn-in rows still answer null and still replan.
- The ViewModel's isLocallyMountable and the remount latch both use it, so a
  selection committed locally can also resolve the ordinal it must mount. The
  latch keeps exact-id-only matching for identities carrying a real Media3 id;
  only a sidecar id, which we author rather than the stream, may fall back.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(player): make Position and Size reach PGS/DVB subtitles

Changing Subtitle Position or Size in the player HUD did nothing to an
embedded PGS track. Media3 1.10.1's SubtitlePainter reads the caption
style, the fixed text size and the bottom-padding fraction in its TEXT
branch only; setupBitmapLayout() derives the destination rect purely from
the cue's own position/line/size/bitmapHeight and anchors. Everything
SubtitleManager.applyAppearance sets is a no-op for a bitmap cue by
construction, and Silo's cue pass-through deliberately left bitmap cues
untouched.

So bake the two presets that CAN be honoured into the cue itself, in the
same forwarding pass that already neutralizes the WebVTT full-width
default:

  - remapBitmapCue re-anchors every bitmap cue's bottom edge to the
    preset's padding (shared with the text path through the new
    subtitleBottomPaddingFraction, title-safe correction included) and
    scales size/bitmapHeight about the cue's own horizontal centre,
    clamped to stay on the surface. Authored horizontal placement and
    anchors are preserved and re-expressed; a cue without a usable
    size/bitmapHeight/position is returned untouched, as are text cues.
    PgsParser and DvbParser both emit START-anchored top/left fractions
    with LINE_TYPE_FRACTION (verified against the 1.10.1 sources).
  - The size ladder is 0.85 / 1.0 / 1.15 / 1.3 / 1.5, Medium being the
    authored typesetting. It follows the shape of the television text
    ladder without its 1.8x top end: a PGS cue is fixed-resolution pixels
    and every step above 1.0 is upscaling.
  - SubtitleVideoRectSync now holds the appearance and the last cue group,
    so a Position/Size change re-forwards the caption currently on screen
    instead of waitin…
* fix(diagnostics): preserve safe hosted crash frames

Co-Authored-By: OpenAI Codex (GPT-5) <noreply@openai.com>

* fix(diagnostics): retain Java module stack frames

* fix(diagnostics): harden crash stack salvage

* fix(diagnostics): bound crash excerpts by line

---------

Co-authored-by: OpenAI Codex (GPT-5) <noreply@openai.com>
Accept the verified Gradle Plugin Portal checksum for the Kotlin Multiplatform 2.1.20 marker POM while preserving strict dependency verification.
…ver#237)

* fix(browse): stop the library sort resetting to its default

TV: backing out of item detail returned to a surviving library
ViewModel, but re-entering the screen re-issues the committed cascade
section, and onTabSelected re-applied the tab's default filter whenever
the viewer had customised it — the guard only skipped when the filter
was ALSO unchanged. Re-selecting the already-active tab is now a no-op;
only a genuine tab change applies the new tab's defaults.

Phone: the browse sort lived outside the persisted CatalogFilterState,
so "Preserve sort & filters" restored the facet chips while the sort
snapped back to Recently Added on relaunch. The sort now rides the
persisted filter state (as BrowseViewModel already does) and is derived
back out on restore. applyFilterState keeps the committed sort so the
Reset control, which changes the sort and clears facets off the same
composition frame, cannot reinstate the old one.

Verified on both emulators: TV keeps Year/Newest across Back, phone
keeps Title across a force-stop relaunch, and Reset still clears both.

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

* docs(tv): correct the section-apply comments

Both comments asserted the behaviour the guard change removes —
re-committing the same pill "re-applies the section rather than being a
silent no-op". That is now exactly what must not happen, and these are
the comments the next person reads when debugging this area.

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

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* feat: enhance Android system media controls

* fix: defer remote media service starts to foreground

* fix: keep artwork lookup off playback path
Bring prairie-android up to date with upstream silo-android since the prior
sync branch, including playback/auth/diagnostics/TV focus work and related
fixes.

Prairie adaptations:
- Remap org.siloserver.silo → org.prairieserver.prairie and Silo* → Prairie*
- Keep Live TV DI bindings and X-Prairie-* / ImageFormats headers
- Keep Kover coverage gate alongside upstream Robolectric cache + lint jobs
- Follow upstream removal of full admin management surfaces

Co-authored-by: Jonah May <JonahMMay@users.noreply.github.com>
Rename leftover skipSiloAuth / SharedPrefsSilo* / cast helpers and
user-facing Silo product strings so the tree compiles under the Prairie
namespace after taking upstream main.

Co-authored-by: Jonah May <JonahMMay@users.noreply.github.com>
@coderabbitai

coderabbitai Bot commented Aug 28, 2026

Copy link
Copy Markdown

Important

Review skipped

Too many files!

This PR contains 910 files, which is 810 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: Pro Plus

Run ID: 299357a3-325e-456e-8b1e-13134d7483cc

📥 Commits

Reviewing files that changed from the base of the PR and between d37bdcc and 1c5e82d.

⛔ Files ignored due to path filters (27)
  • Gemfile.lock is excluded by !**/*.lock
  • android-shared/src/androidMain/res/raw/startup_splash.mp4 is excluded by !**/*.mp4
  • android-shared/src/androidMain/res/raw/startup_splash_hd.mp4 is excluded by !**/*.mp4
  • androidApp/src/androidMain/res/drawable/prairie_wordmark.png is excluded by !**/*.png
  • androidApp/src/androidMain/res/mipmap-hdpi/ic_launcher.png is excluded by !**/*.png
  • androidApp/src/androidMain/res/mipmap-hdpi/ic_launcher_foreground.png is excluded by !**/*.png
  • androidApp/src/androidMain/res/mipmap-mdpi/ic_launcher.png is excluded by !**/*.png
  • androidApp/src/androidMain/res/mipmap-mdpi/ic_launcher_foreground.png is excluded by !**/*.png
  • androidApp/src/androidMain/res/mipmap-xhdpi/ic_launcher.png is excluded by !**/*.png
  • androidApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_foreground.png is excluded by !**/*.png
  • androidApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher.png is excluded by !**/*.png
  • androidApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_foreground.png is excluded by !**/*.png
  • androidApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.png is excluded by !**/*.png
  • androidApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_foreground.png is excluded by !**/*.png
  • androidTvApp/src/androidMain/res/drawable/prairie_wordmark.png is excluded by !**/*.png
  • androidTvApp/src/androidMain/res/drawable/tv_banner.png is excluded by !**/*.png
  • androidTvApp/src/androidMain/res/mipmap-hdpi/ic_launcher.png is excluded by !**/*.png
  • androidTvApp/src/androidMain/res/mipmap-hdpi/ic_launcher_foreground.png is excluded by !**/*.png
  • androidTvApp/src/androidMain/res/mipmap-mdpi/ic_launcher.png is excluded by !**/*.png
  • androidTvApp/src/androidMain/res/mipmap-mdpi/ic_launcher_foreground.png is excluded by !**/*.png
  • androidTvApp/src/androidMain/res/mipmap-xhdpi/ic_launcher.png is excluded by !**/*.png
  • androidTvApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_foreground.png is excluded by !**/*.png
  • androidTvApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher.png is excluded by !**/*.png
  • androidTvApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_foreground.png is excluded by !**/*.png
  • androidTvApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.png is excluded by !**/*.png
  • androidTvApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_foreground.png is excluded by !**/*.png
  • assets/icon.png is excluded by !**/*.png
📒 Files selected for processing (910)
  • .agents/skills/test-shield-playback/agents/openai.yaml
  • .github/workflows/android-build.yml
  • .github/workflows/release.yml
  • .superpowers/sdd/2026-07-27-pr108-slice-f-watch-together/task-1-report.md
  • .superpowers/sdd/2026-07-28-android-tv-active-header-focus-editorial-hero/task-4-report.md
  • AGENTS.md
  • FEATURES.md
  • README.md
  • android-shared/build.gradle.kts
  • android-shared/lint-baseline.xml
  • 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/di/PlayerInfraModule.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/di/PlayerModule.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/diagnostics/BreadcrumbJournal.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/diagnostics/CrashCapture.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/diagnostics/DiagnosticsArchiveEncoder.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/diagnostics/DiagnosticsBundleBuilder.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/diagnostics/DiagnosticsCoordinator.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/diagnostics/DiagnosticsFileDurability.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/diagnostics/DiagnosticsFileLogger.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/diagnostics/DiagnosticsIdentityResolver.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/diagnostics/DiagnosticsInstrumentation.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/diagnostics/DiagnosticsManualCapture.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/diagnostics/DiagnosticsModule.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/diagnostics/DiagnosticsPresentationModels.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/diagnostics/DiagnosticsPrivacyBarrier.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/diagnostics/DiagnosticsRedactor.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/diagnostics/DiagnosticsRunLedger.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/diagnostics/DiagnosticsRuntime.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/diagnostics/DiagnosticsSettingsStore.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/diagnostics/DiagnosticsUploadWorker.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/diagnostics/DiagnosticsUploader.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/diagnostics/DiagnosticsViewModel.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/diagnostics/ExitInfoCollector.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/diagnostics/HostedDiagnostics.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/diagnostics/HostedDiagnosticsDeletionWorker.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/diagnostics/PendingReportStore.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/diagnostics/PrairieLog.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/downloads/DownloadWorker.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/network/AndroidDeviceMetadataProvider.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/network/ClientBuildIdentity.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/overlays/CardOverlays.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/overlays/OverlayBadge.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/overlays/OverlayPresetStyle.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/pairing/CompanionPairingCoordinator.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/pairing/PairingAuthPort.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/pairing/PairingReceiver.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/player/AndroidSubtitleTextSizePolicy.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/player/AudioCapabilityManager.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/player/AudiobookPlayerViewModel.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/player/MediaAuthInterceptor.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/player/MediaAuthSession.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/player/Playability.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/player/PlaybackBufferPolicy.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/player/PlaybackCapabilityDetector.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/PlaybackTeardownGate.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/player/PlaybackV3Session.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/player/PrairieLoadControl.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/player/PrairieMediaSessionBitmapLoader.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/player/PrairiePlaybackService.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/player/PrairiePlayerFactory.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/player/ReplayableSubtitleDataSource.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/player/SubtitleManager.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/player/SubtitleMountResolver.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/player/TrackSelectionPresets.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/player/VideoPlayerMediaSpec.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/player/audio/DelayAudioProcessor.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/player/audio/PassthroughSuppressionRegistry.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/player/backend/Media3VideoPlaybackBackend.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/player/backend/VideoPlaybackBackend.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/player/cast/CastPlaybackPreparer.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/player/route/PlaybackRoute.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/player/seek/PlaybackTimelineSeekPolicy.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/player/subtitle/PgsSupExtractor.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/player/subtitle/SidecarSubtitleMediaSource.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/player/subtitle/StreamingWebvttExtractor.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/player/subtitle/SubripPayloadNormalizer.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/player/video/AudioReconcile.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/player/video/EpisodeSelectionHandoff.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/player/video/MountedAudioMatching.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/player/video/PlaybackContainerPolicy.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/player/video/PlaybackStartupStallDetector.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/player/video/PostResumeVideoStallDetector.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/player/video/VideoPlaybackSessionCoordinator.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/player/video/VideoPlaybackStartRequest.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/player/video/VideoPlaybackStartResult.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/player/video/VideoPlayerUiState.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/player/video/VideoTrackSelectionCoordinator.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/settings/AndroidPlayerSettingsStore.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/settings/OverlayPrefsStore.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/store/ScopedJsonFileStore.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/ui/DirectorCredit.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/ui/components/ProfileAvatarSupport.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/ui/components/StartupSplashAsset.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/ui/components/StartupSplashVideo.kt
  • android-shared/src/androidMain/kotlin/org/prairieserver/prairie/common/ui/components/ThumbhashImage.kt
  • android-shared/src/androidMain/res/raw/startup_splash_lottie.json
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/data/db/PrairieDatabaseMigrationTest.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/diagnostics/BreadcrumbJournalTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/diagnostics/CrashCaptureTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/diagnostics/DiagnosticsBundleBuilderTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/diagnostics/DiagnosticsCoordinatorTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/diagnostics/DiagnosticsFileLoggerTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/diagnostics/DiagnosticsInstrumentationTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/diagnostics/DiagnosticsPlaybackSessionTrackerTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/diagnostics/DiagnosticsPrivacyIntegrationTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/diagnostics/DiagnosticsRedactorTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/diagnostics/DiagnosticsRunLedgerTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/diagnostics/DiagnosticsSettingsStoreTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/diagnostics/DiagnosticsTestFileOperations.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/diagnostics/DiagnosticsUploaderTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/diagnostics/ExitInfoCollectorTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/diagnostics/HostedDiagnosticsTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/diagnostics/PendingReportStoreTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/diagnostics/PrairieLogTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/downloads/DownloadWorkerHttpStatusTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/downloads/DownloadWorkerProgressTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/downloads/OrphanedServerDataPurgerTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/network/WatchTogetherRealtimeWebSocketTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/pairing/PairingReceiverTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/pairing/RegistryPairingAuthPortTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/player/AndroidSubtitleTextSizePolicyTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/player/AudiobookPlayerTeardownSourceTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/player/DolbyVisionColorInfoExtractorsFactoryTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/player/PipActionCapabilityTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/player/PlaybackBufferPolicyTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/player/PlaybackCapabilityDetectorAudioSupportTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/player/PlaybackCapabilityDetectorDolbyVisionTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/player/PlaybackColorRangeFallbackTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/player/PlaybackPlanningSnapshotRegistryTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/player/PlaybackPublicationSettlementIntegrationTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/player/PlaybackSessionLifecycleLoggingTest.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/PlaybackSessionManagerTranscodeFallbackTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/player/PlaybackV3SessionTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/player/PrairieLoadControlTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/player/PrairiePlaybackServiceStartPolicyTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/player/ReplayableSubtitleDataSourceTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/player/StyledSubtitleBurnInTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/player/SubtitleBitmapCueAppearanceTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/player/SubtitleManagerAppearanceTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/player/SubtitleManagerTrackSelectionTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/player/SubtitleMountResolverTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/player/TrackReselectionGuardTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/player/TrackSelectionPresetsTextOwnershipTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/player/VideoPlayerSubtitleMountTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/player/audio/DelayAudioProcessorTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/player/backend/Media3VideoPlaybackBackendLifecycleTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/player/cast/CastPlaybackPreparerTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/player/seek/PlaybackTimelineSeekPolicyTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/player/subtitle/PgsSupExtractorTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/player/subtitle/SidecarSubtitleMediaSourceTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/player/subtitle/StreamingWebvttExtractorTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/player/subtitle/SubripPayloadNormalizerTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/player/video/AudioReconcileTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/player/video/EpisodeSelectionHandoffTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/player/video/MountedAudioMatchingTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/player/video/MountedAudioReorderTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/player/video/PlaybackStartupStallDetectorTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/player/video/PostResumeVideoStallDetectorTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/player/video/VideoTrackSelectionCoordinatorTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/settings/AndroidPlayerSettingsStoreTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/settings/OverlayPrefsStoreTest.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/settings/SubtitleSyncOverridesTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/ui/DirectorCreditTest.kt
  • android-shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/common/ui/components/ProfileAvatarSupportTest.kt
  • androidApp/build.gradle.kts
  • androidApp/gradle.lockfile
  • androidApp/lint-baseline.xml
  • androidApp/src/androidMain/AndroidManifest.xml
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/MainActivity.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/PrairieApplication.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/cast/PrairieCastArtworkResolver.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/cast/PrairieCastController.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/cast/PrairieCastMediaSessionService.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/cast/PrairieCastMediaSessionStarter.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/cast/PrairieCastSessionManager.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/cast/RemoteControlBatteryOptimization.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/di/AndroidModule.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/downloads/AppWorkerFactory.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/push/PushNotificationPresenter.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/components/HeroBackdropLayers.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/components/MainAppTopBar.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/components/MediaCard.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/components/MediaRow.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/components/PrairieConfirmDialog.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/components/PrairieKeyboardActions.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/components/PrairieMenu.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/components/PrairieTopBar.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/components/ProfileMenu.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/components/SortFilterControls.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/components/SwipeBack.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/components/TopBarActions.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/components/aurora/AuroraChrome.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/components/aurora/AuroraScreen.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/navigation/AppNavigation.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/navigation/BottomNavBar.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/navigation/ContentDeepLinkRoutes.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/navigation/DeviceLoginRouteParser.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/navigation/DeviceLoginServerMatch.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/navigation/ExternalRouteNavigation.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/navigation/InviteClaimRouteParser.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/navigation/NotificationExternalRoute.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/admin/AdminEntryViewModel.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/admin/AdminHubScreen.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/admin/AdminLogQuery.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/admin/AdminLogsScreen.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/admin/AdminScansScreen.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/admin/AdminSessionFormatters.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/admin/AdminSessionsScreen.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/admin/AdminStatsScreen.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/admin/AdminUserEditScreen.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/admin/AdminUsersScreen.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/audiobook/AudiobookPlayerScreen.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/auth/AuthComponents.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/auth/DevicePairingScreen.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/auth/DevicePairingWrongServerScreen.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/auth/InviteClaimScreen.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/auth/InviteClaimViewModel.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/browse/CatalogGrid.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/browse/FilterSheet.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/calendar/CalendarPrefsStore.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/calendar/CalendarScreen.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/cast/PrairieCastArtwork.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/cast/PrairieCastRemoteScreen.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/detail/MediaSelectors.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/detail/MovieDetailContent.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/detail/SeasonEpisodePager.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/detail/SeriesDetailContent.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/detail/SimilarRail.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/home/FeaturedCarousel.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/onboarding/OnboardingTourLocalCache.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/onboarding/OnboardingTourScreen.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/onboarding/OnboardingTourViewModel.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/pairing/CompanionPairingBottomOverlay.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/personal/FavoritesScreen.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/personal/PersonalListControls.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/personal/PersonalListControlsViewModel.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/personal/PersonalListsScreen.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/personal/PersonalMediaGridContent.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/personal/WatchlistScreen.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/ChaptersSheet.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/player/FoldablePlayerPosture.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/player/IntroAutoSkipBanner.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/player/LetterboxFillProbe.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/player/LetterboxMatte.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/player/LetterboxMatteCache.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/player/MobileFreshSubtitleRestore.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/player/MobilePlayerRouteTarget.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/player/MobileSubtitleAutoSelection.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/PlaybackRealtimeController.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/player/PlaybackStatsSheet.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/player/PlayerControls.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/PlayerNextUpScreen.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/player/PlayerOrientationPolicy.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/PlayerProgressBar.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/PlayerSettingsSheet.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/player/PlayerSheetSupport.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/QualitySelector.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/player/RoomSyncController.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/player/SleepTimerSheet.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/player/SubtitleSearchSheet.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/player/SubtitleStyleSheet.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/player/TracksSheet.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/PINEntryDialog.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/profiles/ProfileAvatar.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/recommendations/RecommendationsScreen.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/search/SearchBar.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/search/SearchResults.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/search/SearchScreen.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/search/SearchViewModel.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/settings/AccountSection.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/settings/CardOverlaySettingsScreen.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/ServerInfoSection.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/screens/settings/SubtitleSettings.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/settings/diagnostics/DiagnosticsPrompt.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/settings/diagnostics/DiagnosticsReportScreen.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/settings/diagnostics/DiagnosticsSettingsScreen.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/watchtogether/WatchTogetherEntrySheet.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/watchtogether/WatchTogetherEntryViewModel.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/watchtogether/WatchTogetherLobbyScreen.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/watchtogether/WatchTogetherLobbyViewModel.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/screens/watchtogether/WatchTogetherMenuEntrySheet.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/theme/Color.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/theme/Spacing.kt
  • androidApp/src/androidMain/kotlin/org/prairieserver/prairie/android/ui/theme/Theme.kt
  • androidApp/src/androidMain/res/values/strings.xml
  • androidApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/android/AndroidManifestPolicyTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/android/cast/PrairieCastMediaSessionStarterTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/android/pip/MobilePictureInPictureSourceTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/android/push/PushNotificationAttributionTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/android/push/PushNotificationDeliveryIntegrationTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/android/ui/components/PrairieKeyboardActionsTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/android/ui/navigation/ContentDeepLinkEncodingTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/android/ui/navigation/DeviceLoginRouteParserTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/android/ui/navigation/DeviceLoginServerMatchTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/android/ui/navigation/ExternalRouteNavigationTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/android/ui/navigation/NotificationExternalRouteTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/android/ui/performance/MobilePlayerLifecyclePerformanceSourceTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/android/ui/screens/admin/AdminEntryViewModelTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/android/ui/screens/admin/AdminLogQueryTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/android/ui/screens/admin/AdminSessionFormattersTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/android/ui/screens/browse/CatalogLetterIndexViewModelTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/android/ui/screens/detail/MobileDetailActionsSourceTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/android/ui/screens/detail/PhoneDirectorCreditSourceTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/android/ui/screens/detail/SeasonPresentationLabelTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/android/ui/screens/libraries/LibrariesViewModelTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/android/ui/screens/libraries/LibraryChromeInsetSourceTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/android/ui/screens/people/PersonDetailViewModelTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/android/ui/screens/player/FoldablePlayerPostureTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/android/ui/screens/player/LetterboxMatteTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/android/ui/screens/player/MobileAudioTrackSelectionTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/android/ui/screens/player/MobileFreshSubtitleRestoreTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/android/ui/screens/player/MobilePlayerRouteTargetTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/android/ui/screens/player/MobileSubtitleAutoSelectionTest.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/PlayerBackendLifecycleSourceTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/android/ui/screens/player/PlayerOrientationPolicyTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/android/ui/screens/player/PlayerProgressBarTest.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/RoomDeliveryKeySourceTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/android/ui/screens/player/SubtitleAspectModeWiringSourceTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/android/ui/screens/player/SubtitleAspectSyncWiringTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/android/ui/screens/player/SubtitleTrackSelectionTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/android/ui/screens/profiles/AvatarOptionsTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/android/ui/screens/profiles/ProfileSelectionGridScopeTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/android/ui/screens/reader/ReaderViewModelReaderTargetSourceTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/android/ui/screens/watchtogether/WatchTogetherEntryDestinationTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/android/ui/screens/watchtogether/WatchTogetherEntryViewModelTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/android/ui/screens/watchtogether/WatchTogetherLobbyContinuitySourceTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/android/ui/screens/watchtogether/WatchTogetherLobbyErrorTest.kt
  • androidApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/android/ui/screens/watchtogether/WatchTogetherMenuEntrySourceTest.kt
  • androidTvApp/build.gradle.kts
  • androidTvApp/gradle.lockfile
  • androidTvApp/lint-baseline.xml
  • androidTvApp/src/androidMain/AndroidManifest.xml
  • 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/cast/RemotePlaybackIdentityManager.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/data/preferences/LegacyTvPrefsMigration.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/di/AndroidTvModule.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/components/TvAlphabetRail.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/components/TvAnchoredSelectorMenu.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/components/TvAuroraChrome.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/components/TvCascadeSelector.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/components/TvCatalogGrid.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/components/TvDialogInitialFocus.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/components/TvEpisodeCard.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/components/TvFilterSheet.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/components/TvFocusMarquee.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/components/TvFocusMarqueeModel.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/components/TvHomeHeroCarousel.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/components/TvImeAwareForm.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/components/TvMediaCard.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/components/TvMediaCardActions.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/components/TvMediaRow.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/components/TvOptionDialog.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/components/TvPinEntryDialog.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/components/TvRootHeroBackdrop.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/components/TvSelectorRowVisualState.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/components/TvSkylinePrefetchPolicy.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/components/TvSkylineSectionFeed.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/components/TvSkylineUpNavigation.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/components/TvSquaredButtons.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/components/TvStockImeLifecycle.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/components/TvTextFieldDefaults.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/components/TvTextInputDialog.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/focus/TvContentInitialFocus.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/focus/TvControlEnablement.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/focus/TvFlatReturnRestoration.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/focus/TvFocusLog.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/focus/TvModalFocusOwnership.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/focus/TvObservedFocusPolicy.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/focus/TvPairDeviceFocusTarget.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/focus/TvProfileFocusTarget.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/focus/TvReturnAdapters.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/focus/TvReturnTarget.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/focus/TvSynchronousFocusClaim.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/navigation/TvAppNavigation.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/navigation/TvAudiobookRouting.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/navigation/TvEpisodeSelectionHandoffRegistry.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/navigation/TvRoute.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/navigation/TvSubtitleLaunchSelection.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/admin/TvAdminHeader.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/admin/TvAdminHubScreen.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/admin/TvAdminLogsScreen.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/admin/TvAdminLogsViewModel.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/admin/TvAdminScansScreen.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/admin/TvAdminScansViewModel.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/admin/TvAdminScreen.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/admin/TvAdminSessionsScreen.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/admin/TvAdminUserEditScreen.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/admin/TvAdminUsersScreen.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/audiobook/TvAudiobookBookmarksPanel.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/audiobook/TvAudiobookChaptersPanel.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/audiobook/TvAudiobookOverlay.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/audiobook/TvAudiobookPlayerScreen.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/audiobook/TvAudiobookSkipIntervalPanel.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/audiobook/TvAudiobookSleepPanel.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/audiobook/TvAudiobookSpeedPanel.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/audiobook/TvAudiobookTransportRow.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/auth/TvLoginScreen.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/auth/TvLoginViewModel.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/auth/TvPairDeviceScreen.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/TvSetupScreen.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/auth/TvSignupScreen.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/browse/TvBrowseScreen.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/calendar/TvCalendarScreen.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/collections/TvCollectionDetailScreen.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/collections/TvCollectionsScreen.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/collections/TvCreateCollectionDialog.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/detail/TvAudiobookDetailHero.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/detail/TvCastCrewSection.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/detail/TvDetailEpisodeRail.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/detail/TvDetailHero.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/detail/TvDetailMetadata.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/detail/TvItemDetailScreen.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/detail/TvItemDetailViewModel.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/detail/TvMediaInfoDialog.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/detail/TvPlaybackFormatting.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/detail/TvPlaybackSelectorRow.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/detail/TvSeasonPicker.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/detail/TvSimilarFocusRestoration.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/home/TvHomeScreen.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/libraries/TvLibrariesScreen.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/library/TvLibraryBrowseControls.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/library/TvLibraryCollectionDetailScreen.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/library/TvLibraryCollectionDetailViewModel.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/library/TvLibraryDetailScreen.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/library/TvLibraryDetailViewModel.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/notifications/TvInboxScreen.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/people/TvPersonDetailScreen.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/personal/TvPersonalListControlsViewModel.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/personal/TvPersonalScreens.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/TvIntroAutoSkipBanner.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/TvPlayerRemoteKeyAction.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/TvPlayerScrubber.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/player/TvPlayerSubtitlePolicy.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/player/TvPlayerTransportCluster.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/player/TvPlayerTransportVisualPolicy.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/TvQuickSubtitlePickerChromePolicy.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/player/TvRoomSyncController.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/player/TvSeekRateLadder.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/player/TvSkipSeekIndicator.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/player/TvSubtitleHudState.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/player/TvSubtitleIdentity.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/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/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/TvProfileForm.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/profiles/TvProfileFormOptions.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/recommendations/TvForYouEntryRequest.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/recommendations/TvRecommendationsScreen.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/requests/TvMyRequestsScreen.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/requests/TvRequestsScreen.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/search/TvSearchReturnProjection.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/search/TvSearchScreen.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/search/TvSearchViewModel.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/search/TvVoiceSearch.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/servers/TvServerListScreen.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/settings/TvCardOverlaySettingsScreen.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/settings/TvManageSessionsScreen.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/settings/TvManageSessionsViewModel.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/screens/settings/diagnostics/TvDiagnosticsComponents.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/settings/diagnostics/TvDiagnosticsPromptScreen.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/settings/diagnostics/TvDiagnosticsReportScreen.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/settings/diagnostics/TvDiagnosticsSettingsPane.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/settings/diagnostics/TvDiagnosticsSettingsScreen.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/settings/diagnostics/TvDiagnosticsViewModel.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/watchtogether/TvJoinCodeDialog.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/watchtogether/TvWatchTogetherDestination.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/watchtogether/TvWatchTogetherLobbyScreen.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/watchtogether/TvWatchTogetherLobbyViewModel.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/watchtogether/TvWatchTogetherMenuEntryDialog.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/screens/watchtogether/TvWatchTogetherViewModel.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/shell/TvContentUpNavigation.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/shell/TvDetailReturnFocusState.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/shell/TvMainShell.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/shell/TvShellFocusState.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/shell/TvTopMenuBar.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/shell/TvTopMenuFocusRequest.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/theme/Layout.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/ui/theme/Spacing.kt
  • androidTvApp/src/androidMain/kotlin/org/prairieserver/prairie/tv/watchnext/TvWorkerFactory.kt
  • androidTvApp/src/androidMain/res/layout/tv_player_view.xml
  • androidTvApp/src/androidMain/res/values/strings.xml
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/TvAndroidManifestPolicyTest.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/components/TvAnchoredSelectorMenuStateTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/components/TvCascadeSelectorIdentityTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/components/TvControlWiringCallSiteTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/components/TvDialogInitialFocusTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/components/TvFocusMarqueeEnrichmentTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/components/TvFocusMarqueeModelTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/components/TvImeAwareFormTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/components/TvMediaRowFocusRestoreTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/components/TvOptionDialogPositionProviderTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/components/TvSelectorRowVisualStateTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/components/TvSkylinePrefetchPolicyTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/components/TvSkylineUpNavigationTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/components/TvStockKeyboardPolicyTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/focus/TvContentInitialFocusTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/focus/TvControlEnablementTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/focus/TvControlSemanticsTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/focus/TvObservedFocusPolicyTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/focus/TvPairDeviceFocusTargetTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/focus/TvProfileFocusTargetTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/focus/TvReturnAdaptersTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/focus/TvReturnTargetTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/focus/TvSilentFocusClaimSourceTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/navigation/TvAudiobookRoutingTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/navigation/TvItemDetailNavigationTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/navigation/TvPlayerRouteTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/screens/admin/TvAdminGateTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/screens/auth/TvLoginMatchCodeLayoutTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/screens/calendar/TvCalendarFocusRoutingTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/screens/detail/TvDirectorCreditSourceTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/screens/detail/TvEpisodeFavoriteProbeTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/screens/detail/TvItemDetailSubtitlePreferenceTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/screens/detail/TvMediaInfoFormattingTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/screens/detail/TvNextUpSelectionHandoffTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/screens/detail/TvPlaybackFormattingTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/screens/detail/TvSeasonPresentationLabelTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/screens/detail/TvSimilarFocusRestorationTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/screens/detail/TvStarringOverlaySourceTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/screens/detail/TvSubtitleLaunchHandoffTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/screens/detail/TvTrackSelectionPersistenceTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/screens/library/TvLibraryFocusRestoreTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/screens/library/TvLibraryReviewWiringSourceTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/screens/library/TvLibrarySubdestinationViewModelTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/screens/people/TvPersonDetailViewModelTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/screens/player/PlayerTrackEntriesTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/screens/player/SubtitleRemountReselectionTest.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/TvAutoSubtitleFallbackTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/screens/player/TvCleanPlaybackSeekTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/screens/player/TvEpisodeHandoffPlaybackStartTest.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/TvHudPickerFocusWiringSourceTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/screens/player/TvPlayNextSelectionHandoffTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/screens/player/TvPlaybackExitSnapshotTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/screens/player/TvPlaybackFreshLoadOwnershipTest.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/TvPlaybackSourceStartTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/screens/player/TvPlayerBackendLifecycleSourceTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/screens/player/TvPlayerRemoteKeyActionTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/screens/player/TvPlayerSubtitleIntegrationPolicyTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/screens/player/TvPlayerTransportVisualPolicyTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/screens/player/TvQuickSubtitlePickerChromePolicyTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/screens/player/TvRoomDeliveryKeySourceTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/screens/player/TvScrubPreviewPolicyTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/screens/player/TvSeekRateLadderTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/screens/player/TvSubtitleAppearanceApplicabilityTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/screens/player/TvSubtitleAspectModeWiringSourceTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/screens/player/TvSubtitleAspectSyncWiringTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/screens/player/TvSubtitleFinalRollbackTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/screens/player/TvSubtitleRefreshOwnershipTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/screens/player/TvSubtitleSettlementOwnershipTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/screens/player/TvSubtitleSingleOwnerSourceTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/screens/player/TvSubtitleTransactionAdapterTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/screens/profiles/TvProfileSelectionGridScopeTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/screens/recommendations/TvForYouEntryRequestTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/screens/search/TvSearchReturnProjectionTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/screens/settings/TvSettingsCategoryTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/screens/settings/diagnostics/TvDiagnosticsStateTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/screens/watchtogether/TvWatchTogetherDestinationTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/screens/watchtogether/TvWatchTogetherLobbyContinuitySourceTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/screens/watchtogether/TvWatchTogetherLobbyErrorTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/screens/watchtogether/TvWatchTogetherMenuEntrySourceTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/screens/watchtogether/TvWatchTogetherSurfaceSourceTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/screens/watchtogether/TvWatchTogetherViewModelTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/shell/TvContentUpNavigationTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/shell/TvDetailReturnFocusStateTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/shell/TvShellFocusStateTest.kt
  • androidTvApp/src/androidUnitTest/kotlin/org/prairieserver/prairie/tv/ui/shell/TvTopMenuFocusRequestTest.kt
  • baselineprofile-tv/build.gradle.kts
  • baselineprofile-tv/gradle.lockfile
  • baselineprofile-tv/src/main/AndroidManifest.xml
  • baselineprofile-tv/src/main/kotlin/org/prairieserver/prairie/baselineprofile/tv/TvBaselineProfileGenerator.kt
  • baselineprofile/build.gradle.kts
  • docs/README.md
  • docs/notes/2026-07-27-pr108-slice-b-traceability.md
  • docs/plans/uiux-overhaul-remaining-phases.md
  • docs/playback/01-media3-only-player-architecture.md
  • docs/playback/02-migration-compatibility-validation.md
  • docs/playback/04-implementation-status-and-dv-handoff.md
  • docs/playback/README.md
  • docs/playback/intro-skip.md
  • docs/reviews/2026-07-27-android-tv-navigation-remediation-executive-summary.md
  • docs/superpowers/notes/architecture-debate.md
  • docs/superpowers/plans/2026-05-23-c-device-login.md
  • docs/superpowers/plans/2026-06-09-android-media-surfaces.md
  • docs/superpowers/plans/2026-06-09-mobile-device-login-parity.md
  • docs/superpowers/plans/2026-06-12-admin-core.md
  • docs/superpowers/plans/2026-06-12-android-client-review-fixes.md
  • docs/superpowers/plans/2026-06-12-ebook-phase1-reader-locator.md
  • docs/superpowers/plans/2026-06-12-ebook-phase2-paginated-epub.md
  • docs/superpowers/plans/2026-06-12-ebook-phase4-highlights-notes.md
  • docs/superpowers/plans/2026-06-12-ebook-phase5-search.md
  • docs/superpowers/plans/2026-06-12-notifications.md
  • docs/superpowers/plans/2026-06-12-subtitle-suite.md
  • docs/superpowers/plans/2026-06-12-watch-together.md
  • docs/superpowers/plans/2026-06-13-shared-video-player-core.md
  • docs/superpowers/plans/2026-06-17-android-tv-detail-parity.md
  • docs/superpowers/plans/2026-06-17-playback-behavior-migration.md
  • docs/superpowers/plans/2026-06-17-tv-mobile-parity.md
  • docs/superpowers/plans/2026-07-16-fire-tv-sideload-launcher-icon.md
  • docs/superpowers/plans/2026-07-27-android-media-buffer-sizing.md
  • docs/superpowers/plans/2026-07-27-android-tv-navigation-remediation.md
  • docs/superpowers/plans/2026-07-27-pr108-slice-b-auth-epub.md
  • docs/superpowers/plans/2026-07-27-pr108-slice-f-watch-together.md
  • docs/superpowers/plans/2026-07-27-watch-together-user-menu-entry.md
  • docs/superpowers/plans/2026-07-28-android-final-ledger-fixes.md
  • docs/superpowers/plans/2026-07-28-android-phone-library-chrome-inset.md
  • docs/superpowers/plans/2026-07-28-android-player-system-brightness.md
  • docs/superpowers/plans/2026-07-28-android-release-audit-fixes.md
  • docs/superpowers/plans/2026-07-28-android-subtitle-aspect-reconciliation-and-phone-sizing.md
  • docs/superpowers/plans/2026-07-28-android-tv-active-header-focus-editorial-hero.md
  • docs/superpowers/plans/2026-07-28-pr-126-coderabbit-remediation.md
  • docs/superpowers/plans/2026-07-28-subtitle-aspect-recenter.md
  • docs/superpowers/plans/2026-07-29-phone-director-credit-and-review-hardening.md
  • docs/superpowers/plans/2026-07-29-remove-tv-detail-starring-overlay.md
  • docs/superpowers/plans/2026-07-29-section-cache-profile-switch-test-race.md
  • docs/superpowers/plans/2026-07-29-specials-first-season-order.md
  • docs/superpowers/plans/2026-07-30-playback-buffer-architecture.md
  • docs/superpowers/plans/2026-08-01-fire-tv-playback-selection-ux.md
  • docs/superpowers/plans/2026-08-02-android-tv-auth-ime-relocation.md
  • docs/superpowers/plans/2026-08-02-instant-external-srt-switching-android.md
  • docs/superpowers/plans/2026-08-02-tv-mounted-srt-fast-switch.md
  • docs/superpowers/plans/2026-08-02-tv-player-transport-accessibility.md
  • docs/superpowers/plans/2026-08-02-tv-subtitle-picker-and-sizing.md
  • docs/superpowers/plans/2026-08-03-apk-only-release-publish.md
  • docs/superpowers/plans/2026-08-03-firetv-rc-review-fixes.md
  • docs/superpowers/plans/2026-08-03-for-you-late-focus-relocation.md
  • docs/superpowers/plans/2026-08-03-shield-focus-restoration.md
  • docs/superpowers/plans/2026-08-04-focus-foundations-enabled-controls.md
  • docs/superpowers/plans/2026-08-04-pr-164-review-remediation.md
  • docs/superpowers/plans/2026-08-12-android-hosted-diagnostics.md
  • docs/superpowers/specs/2026-05-23-android-tv-parity-rework-design.md
  • docs/superpowers/specs/2026-06-09-android-media-surfaces-design.md
  • docs/superpowers/specs/2026-06-09-android-requests-design.md
  • docs/superpowers/specs/2026-06-12-manga-pr138-client-audit.md
  • docs/superpowers/specs/2026-06-12-oauth-signin-mobile-design.md
  • docs/superpowers/specs/2026-06-15-best-of-source-borrow-map-design.md
  • docs/superpowers/specs/2026-06-17-android-tv-detail-parity-design.md
  • docs/superpowers/specs/2026-06-17-playback-behavior-migration-design.md
  • docs/superpowers/specs/2026-07-27-android-media-buffer-sizing-design.md
  • docs/superpowers/specs/2026-07-27-android-tv-navigation-remediation-design.md
  • docs/superpowers/specs/2026-07-27-pr108-slice-f-watch-together-design.md
  • docs/superpowers/specs/2026-07-27-watch-together-user-menu-entry-design.md
  • docs/superpowers/specs/2026-07-28-android-active-header-focus-editorial-hero-design.md
  • docs/superpowers/specs/2026-07-28-android-phone-library-chrome-inset-design.md
  • docs/superpowers/specs/2026-07-28-android-player-system-brightness-design.md
  • docs/superpowers/specs/2026-07-28-android-subtitle-aspect-reconciliation-and-phone-sizing-design.md
  • docs/superpowers/specs/2026-07-28-pr-126-coderabbit-remediation-design.md
  • docs/superpowers/specs/2026-07-28-subtitle-aspect-recenter-design.md
  • docs/superpowers/specs/2026-07-29-phone-director-credit-and-review-hardening-design.md
  • docs/superpowers/specs/2026-07-29-remove-tv-detail-starring-overlay-design.md
  • docs/superpowers/specs/2026-07-29-section-cache-profile-switch-test-race-design.md
  • docs/superpowers/specs/2026-07-29-specials-first-season-order-design.md
  • docs/superpowers/specs/2026-07-30-playback-buffer-architecture-design.md
  • docs/superpowers/specs/2026-07-31-fire-tv-playback-selection-ux-design.md
  • docs/superpowers/specs/2026-08-02-android-tv-auth-ime-relocation-design.md
  • docs/superpowers/specs/2026-08-02-tv-mounted-srt-fast-switch-design.md
  • docs/superpowers/specs/2026-08-02-tv-player-transport-accessibility-design.md
  • docs/superpowers/specs/2026-08-02-tv-subtitle-picker-and-sizing-design.md
  • docs/superpowers/specs/2026-08-03-apk-only-release-publish-design.md
  • docs/superpowers/specs/2026-08-03-for-you-late-focus-relocation-design.md
  • docs/superpowers/specs/2026-08-03-shield-focus-restoration-design.md
  • docs/superpowers/specs/2026-08-04-pr-164-review-remediation-design.md
  • docs/superpowers/specs/2026-08-04-whole-application-focus-hardening-design.md
  • docs/superpowers/specs/2026-08-12-android-hosted-diagnostics-design.md
  • fastlane/Fastfile
  • gradle/libs.versions.toml
  • gradle/verification-metadata.xml
  • libass-bridge/src/main/java/org/prairieserver/prairie/libass/LibassBridge.java
  • scripts/test-release-workflow.sh
  • settings.gradle.kts
  • shared/src/androidMain/kotlin/org/prairieserver/prairie/model/settings/LanguagePresentation.android.kt
  • shared/src/androidMain/kotlin/org/prairieserver/prairie/network/AndroidServerRegistry.kt
  • shared/src/androidMain/kotlin/org/prairieserver/prairie/network/EncryptedTokenManagerImpl.kt
  • shared/src/androidMain/kotlin/org/prairieserver/prairie/overlays/OverlayLanguageName.android.kt
  • shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/model/diagnostics/DiagnosticsAttributeRegistryParityTest.kt
  • shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/model/playback/PlaybackProtocolV3ConformanceTest.kt
  • shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/model/settings/SettingsConformanceTest.kt
  • shared/src/androidUnitTest/kotlin/org/prairieserver/prairie/network/EncryptedTokenManagerScopeGenerationTest.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/domain/ManagePlaybackUseCase.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/domain/player/IntroAutoSkipController.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/domain/player/IntroSkipMode.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/domain/player/SettlingFalseEdges.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/domain/settings/ProfileSettingsController.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/model/admin/AdminClientPolicy.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/model/admin/AdminModels.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/model/auth/AdminPermissions.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/model/auth/AuthModels.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/model/auth/InvitationModels.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/model/catalog/CatalogModels.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/model/diagnostics/DiagnosticsValidation.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/model/feature/ClientSurfacePolicy.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/model/navigation/MediaMode.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/model/onboarding/OnboardingModels.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/model/playback/AutoSubtitleResolver.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/PlaybackSubtitleChoices.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/model/playback/SubtitleTransition.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/model/profile/ProfileModels.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/model/recommendation/RecommendationModels.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/model/server/ServerEntry.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/model/settings/LanguageOptions.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/model/settings/PlaybackSettingsKeys.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/settings/SettingValueModels.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/model/settings/SubtitleAppearance.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/model/settings/SubtitleAppearanceProjection.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/network/ApiResult.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/network/AuthInterceptor.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/network/AuthInterceptorImpl.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/DiagnosticsRequestScope.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/network/IdentityTransitionBarrier.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/network/PlaybackRealtimeClient.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/network/PrairieAuthUnavailableException.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/network/ProactiveRefreshPolicy.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/network/TokenManager.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/network/TokenManagerImpl.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/network/api/AdminApi.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/network/api/AuthApi.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/network/api/BrandingApi.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/network/api/CatalogApi.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/network/api/DiagnosticsApi.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/network/api/HealthApi.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/network/api/HostedDiagnosticsApi.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/network/api/OnboardingApi.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/network/api/PlaybackApi.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/network/api/SectionApi.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/network/api/SettingsApi.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/network/api/WatchTogetherApi.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/overlays/OverlayLanguageName.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/overlays/OverlayRegistry.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/overlays/OverlaySchema.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/overlays/OverlayTypes.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/playback/PlaybackMarkersUpdate.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/playback/PlaybackSubtitleIdentity.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/playback/PlaybackSubtitleReady.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/playback/SubtitleCodecFamily.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/playback/SubtitleLanguage.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/playback/TrackSelectionFingerprint.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/repository/AdminRepository.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/OnboardingRepository.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/repository/PersonalDataRepository.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/SectionRepository.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/repository/SettingsRepository.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/AdminStatsViewModel.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/viewmodel/AdminUserEditViewModel.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/viewmodel/AdminUserForm.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/viewmodel/AdminUsersViewModel.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/viewmodel/CalendarViewModel.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/viewmodel/DevicePairingViewModel.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/viewmodel/HomeSectionHydrator.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/viewmodel/HomeViewModel.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/viewmodel/PersonalListViewModels.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/viewmodel/RecommendationsViewModel.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/viewmodel/RequestsViewModels.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/watchtogether/RoomDeliveryLatch.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/watchtogether/WatchTogetherEntryGateway.kt
  • shared/src/commonMain/kotlin/org/prairieserver/prairie/watchtogether/WatchTogetherEntryPolicy.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/domain/player/IntroAutoSkipControllerTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/domain/player/SettlingFalseEdgesTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/domain/settings/ProfileSettingsControllerTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/model/SharedModelsCoverageTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/model/admin/AdminClientPolicyTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/model/admin/AdminModelsSerializationTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/model/auth/AdminPermissionsTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/model/catalog/SeasonDisplayOrderTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/model/feature/ClientSurfacePolicyTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/model/navigation/MediaModeTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/model/onboarding/OnboardingModelsTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/model/playback/AutoSubtitleResolverTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/model/playback/PlaybackModelsV2SerializationTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/model/playback/PlaybackProtocolV3Test.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/model/playback/PlaybackSessionModelsTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/model/playback/PlaybackSubtitleChoicesTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/model/playback/SubtitleTrackMergeTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/model/profile/ProfileQualityPreferenceTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/model/settings/LanguageOptionsTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/model/settings/QualityPresetsTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/model/settings/SettingKeysContractTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/model/settings/SettingsManifest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/model/settings/SettingsResolve.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/model/settings/SubtitleAppearanceProjectionTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/network/IdentityTransitionBarrierTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/network/PrairieAuthPluginPinTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/network/PrairieAuthPluginProactiveRefreshHazardTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/network/PrairieAuthPluginProactiveRefreshTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/network/ProactiveRefreshPolicyTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/network/api/AdminApiTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/network/api/AuthApiTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/network/api/BrandingApiTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/network/api/DiagnosticsApiTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/network/api/HostedDiagnosticsApiTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/network/api/OnboardingApiTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/network/api/PlaybackApiTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/network/api/SectionApiCollectionItemsTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/network/api/SettingsApiValuesTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/overlays/OverlayRegistryTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/playback/PlaybackMarkersUpdateTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/playback/PlaybackSubtitleIdentityTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/playback/PlaybackSubtitleReadyTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/playback/TrackSelectionFingerprintTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/repository/AdminRepositoryTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/repository/AuthRepositoryAccountReplacementTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/repository/AuthRepositoryServerNameTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/repository/CatalogRepositoryDetailCacheTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/repository/PersonalDataRepositoryCacheTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/repository/ProfileIdentityCommitTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/repository/SectionRepositoryCacheTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/repository/WatchTogetherRepositoryTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/viewmodel/AdminStatsViewModelTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/viewmodel/AdminUserEditViewModelTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/viewmodel/AdminUserFormTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/viewmodel/AdminUsersViewModelTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/viewmodel/CalendarViewModelTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/viewmodel/DevicePairingDecisionOrderingTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/viewmodel/DevicePairingUiStateTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/viewmodel/HomeSectionHydratorTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/viewmodel/HomeViewModelCacheIdentityTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/viewmodel/PersonalListViewModelGenerationTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/viewmodel/RecommendationsFeaturedRowTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/viewmodel/RecommendationsSectionIdentityTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/watchtogether/RoomDeliveryLatchTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/watchtogether/RoomSessionTest.kt
  • shared/src/commonTest/kotlin/org/prairieserver/prairie/watchtogether/WatchTogetherEntryPolicyTest.kt
  • shared/src/commonTest/resources/diagnostics/v1/SOURCE
  • shared/src/commonTest/resources/diagnostics/v1/attr-registry.json
  • shared/src/commonTest/resources/playback/v3/SOURCE
  • shared/src/commonTest/resources/playback/v3/attempt_keys.json
  • shared/src/commonTest/resources/playback/v3/capability_response.json
  • shared/src/commonTest/resources/playback/v3/conformance_matrix.json
  • shared/src/commonTest/resources/playback/v3/decision_response.json
  • shared/src/commonTest/resources/playback/v3/error_response.json
  • shared/src/commonTest/resources/playback/v3/replan_request.json
  • shared/src/commonTest/resources/playback/v3/route_event.json
  • shared/src/commonTest/resources/playback/v3/start_request.json
  • shared/src/commonTest/resources/playback/v3/subtitle_inventory.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.

cursoragent and others added 4 commits August 28, 2026 00:29
Admin management code (including the STATS dashboard) was removed with the
silo-android sync; keep README/FEATURES consistent with AGENTS.md.

Co-authored-by: Jonah May <JonahMMay@users.noreply.github.com>
The silo-android merge dropped the kover alias from libs.versions.toml while
Prairie kept the :shared coverage gate in build.gradle.kts and CI.

Co-authored-by: Jonah May <JonahMMay@users.noreply.github.com>
The upstream merge left SkipSiloAuthAttributeKey references in
AuthInterceptorImpl and dropped HealthApi.checkHealth(serverUrl) that
LanDiscovery depends on.

Co-authored-by: Jonah May <JonahMMay@users.noreply.github.com>
The sync merge left adaptive launcher XML pointing at
@color/prairie_icon_background while colors.xml still defined
silo_icon_background from upstream.

Co-authored-by: Jonah May <JonahMMay@users.noreply.github.com>
@JonahMMay
JonahMMay marked this pull request as ready for review August 28, 2026 00:49
@JonahMMay
JonahMMay merged commit 9705c8a into main Aug 28, 2026
1 of 3 checks passed
@JonahMMay
JonahMMay deleted the cursor/sync-silo-android-4e8c branch August 28, 2026 00:49
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.

6 participants