Skip to content

Sync with upstream silo-apple main (2026-09-28) - #31

Merged
JonahMMay merged 395 commits into
mainfrom
sync/upstream-2026-09-28
Sep 29, 2026
Merged

JonahMMay merged 395 commits into
mainfrom
sync/upstream-2026-09-28

Conversation

@JonahMMay

Copy link
Copy Markdown

Summary

Sync with upstream Silo-Server/silo-apple main (work in progress; body will be updated once CI is green).

Merge with 'Create a merge commit' — do not squash.

AI disclosure

  • Tool: Claude Code
  • Model: claude-opus-5-5
  • Involvement: AI-generated

🤖 Generated with Claude Code

Quick104 and others added 30 commits September 3, 2026 22:29
…connect task

- Only a connection whose hello was sent may trigger a reconnect, so a
  timed-out connect reports its error instead of racing into silent
  reconnecting
- Clear reconnectTask when the loop finishes so a later background drop
  is not mistaken for an in-flight attempt

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…vos-backdrop

perf(tvos): swap the hero backdrop as soon as the row scroll has visibly settled
…iscovery

Review follow-ups: a long-resident app never renewed a display token
that had expired, and a server that started returning one after an
upgrade was never asked again. Keep the token's expiry in the App Group
defaults, re-register a week before it lapses, and retry discovery on
an older server every six hours instead of once per process.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ote-disconnects

fix(ios): handle remote disconnect timeouts
Review follow-ups: the display-token slot survived a server or profile
switch until the next registration succeeded, so the extension could
pair a new context with the previous profile's credential, and a
registration response landing after sign-out could write the old token
back. TokenStore now clears the slot on profile activation, deactivation,
and server retargeting, and the coordinator discards a response whose
account or profile identity changed while it was in flight.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Review follow-up: the actor starts with an empty active server on every
process, so background launches that retargeted to the same persisted
server were clearing a valid display token. Record the issuing server
with the token and clear it only when the active server actually
differs.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ain item

Review follow-up: a failed Keychain write or delete left the previous
token in place while its expiry and issuing server were removed, so the
coordinator treated a stale credential as current and stopped renewing
it. Metadata now changes only after the Keychain mutation succeeds, and
a token with no recorded expiry is treated as needing renewal.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Review follow-up: an app backgrounded past the display token's expiry
could still have a fresh access token from background work, but the
extension never tried it. On a 401 or 403 with the display token, the
client now retries once with the mirrored access token.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…cation-metadata-delivery

fix(notifications): use a long-lived display token for push enrichment
- Route panel-less menu items to the page's first card
- Prioritize the first content item while focus leaves the top menu
- Apply the content focus claim after the menu has fully resigned
- Prevent focus repair from stranding focus in the top menu
…ost-down-focus

fix(tvos): route top-menu Down to left-most content
The previous fix reasserted focus from onMoveCommand, but the focus engine
resolves a Left/Right move to any focusable beyond the rail before that
handler fires, so focus still escaped. Represent the hard edges in the
focus graph instead: a hidden one-point focusable guide hugs each end of
the card strip, and the rail bounces focus from a guide back to the first
or last episode card.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…-episode-carousel-focus

fix(tvos): retain episode rail focus at horizontal edges
- Remove tvOS Menu transition delays from detail actions
- Preserve focused selection for version, audio, subtitle, and More actions
… on screen

The action popover was one focusable composite whose VoiceOver element
only spoke the static title, so a VoiceOver user could not hear or
activate the row that Select would commit. The composite is now an
adjustable button: its value is the highlighted row, swipe up/down moves
the highlight, activate commits, and escape closes. Rows are hidden from
the accessibility tree so the cursor never lands on a passive label.

The host also always placed the panel below the trigger. On the series
hero a seven-row list ran past the bottom of the screen. The host now
measures the panel and opens it above the trigger when it will not fit
below, or pins it to the bottom inset when neither side has room.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…-subtitles-open-pill

fix(tvos): replace detail menus with instant popovers
The Home feed rewrite in Silo-Server#106 set resume stills to 184pt to match the
poster rows' density, which made Continue Watching cards read as
thumbnails and the progress rail hard to see.

Raise the base still width to 240pt. The Poster Size setting already
scales this value, so Compact and Large follow along.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…atching-card-size-9b1202

fix(ios): enlarge Continue Watching cards
…order (Silo-Server#247)

* fix(player): resolve the initial subtitle intent in combined ordinal order

Every Protocol V3 episode start loaded the engine twice. The pre-start
subtitle intent resolved over the watch detail, which lists embedded tracks
before externals, while the post-load caption policy resolved over the plan
inventory, which lists externals first. The resolver is first-match within a
track class, so a file with two same-language tracks produced a different
pick on each side. The client then issued a subtitle_track_changed replan
and a full engine reload on top of a picture that was already playing.

SubtitleTrackCandidates now orders catalog tracks external-first to match the
combined ordinal space, so both passes land on the same track. The auto
policy logs when it still disagrees with the plan. Verified on the tvOS
simulator: one engine session per episode, no replan.

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

* fix(player): number subtitle candidates by combined position

indexedPlayerTracks reordered tracks external-first but kept each track's
catalog offset as its ordinal, so an embedded-first catalog returned ordinals
like [2, 3, 0, 1]. Enumerate after partitioning so ordinals are sequential
in combined order; the regression test now asserts them.

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

* fix(detail): preview the Auto subtitle in playback's combined order

The detail screen's "Auto:" label ran its own resolver pass over the
catalog in embedded-first order, while playback now resolves external-first.
With an embedded and an external track of the same language the label named
one track and playback started the other. The preview now searches the same
combined order and maps the pick back to its catalog offset for labels.

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

* fix(player): compare the auto subtitle pick against the plan's resolved index

A plan can name its selected subtitle by stable identity without a numeric
index. Comparing the auto pick against selectedTracks.subtitle?.index then
read as a mismatch and replanned for the track already playing. Resolve
through selectedSubtitleCombinedIndex, which falls back to the inventory
entry the identity names.

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

* chore: restore the main Package.resolved on the subtitle branch

The lockfile from the AetherEngine bump branch was swept into da68f6e by
mistake. This PR touches no package declaration, so the lockfile must match
project.yml on main.

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

* fix(player): keep the Auto subtitle preview and replan guard on selectable tracks

The detail preview could name an embedded track with no stream index, which
SubtitleTrackCandidates already drops because the plan cannot select it. It
now filters those out before resolving.

The auto-policy replan guard treated an off plan's stale subtitle identity
as a selection, so a .disable pick read as a mismatch and replanned. Off
means nothing is selected, as subtitlePickerTracks already assumes.

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

* chore: restore the main Package.resolved on the subtitle branch again

The shared worktree's lockfile follows whichever branch was last built, and
1183fa7 swept it in. This PR declares no package change.

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

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
…x its attribution (Silo-Server#246)

* docs: correct AetherEngine attribution and credit the engine in README

THIRD_PARTY_NOTICES.md recorded the upstream 6.34.0 commit while the app
links 653be639, a published fork revision that adds Silo's handover patches.
The notices and in-app acknowledgements now name the fork branch as the
modified source, the upstream tag as its base, and state the LGPL
publication obligation for those patches. README.md now credits
AetherEngine and its author next to the other third-party notices.

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

* chore(deps): move AetherEngine to the Silo-Server fork at 6.67.2 plus the handover patch

The app was pinned to a personal fork of AetherEngine frozen at 6.34.0 with
five local commits. It now pins Silo-Server/AetherEngine at dfe11a08, which
is upstream 6.67.2 plus one re-implemented commit that lets a host request
the in-place native item handover on an episode change. That keeps the tvOS
autoplay fix from Silo-Server#221 while picking up 52 upstream releases, including the
6.50.0 loopback-origin exposure fix and the libzvbi GHSA-86rm-g7qf-j2fh
update via FFmpegBuild 3.0.0.

FFmpegBuild 3.0.0 renames the nine embedded frameworks with an Aether
prefix and moves to dav1d 1.5.4, zimg 3.0.6, and libzvbi 0.2.45; LibDovi
moves to 2.1.0 (dolby_vision 3.4.0). THIRD_PARTY_NOTICES.md, the in-app
acknowledgements, and quietvoid's libdovi notice are updated to the
resolved revisions.

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

* docs: record the FFmpegBuild 3.0.0 artifact inspection in the notices

The inventory claim described a 2.4.3 debug build. A tvOS Simulator debug
build against 3.0.0 was inspected: nine Aether-prefixed frameworks embedded,
--enable-shared present, no GPL, version3, or nonfree flags, and no FFmpeg
symbols exported by the app binary.

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

* chore(deps): bump the AetherEngine fork so the remote-HLS bypass honours the handover

CI's iOS suite failed PlayerSurfaceLayoutTests: the synthetic next-episode
handover on the nativeRemoteHLS route showed 2 item changes and 1 nil item.
The fork's bypass callsite never read the handover flag. Fork 745de1cc
consumes it the same way the loopback callsite does; the test now reports
1 item swap and 0 nil items.

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

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
…erver#249)

* fix(ios): restore quick-play on Continue Watching still cards

The play badge on Continue Watching and Next Up stills was decorative
after the Home feed rewrite. Tapping it now presents the player with
the item's resume position; the rest of the card still opens detail.
The badge grows from 28pt to 36pt so it is a reliable touch target.

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

* fix(player): carry last-used version through macOS and expose quick-play to VoiceOver

The macOS player route dropped prefersLastUsedVersion, so a
Continue Watching quick-play could resume a different file than the
one last watched. Route, ContentView, and macOS PlayerView now
forward the flag. Audio and subtitle memory already ride on the
server's effective_* fields and need no extra plumbing.

HomeCardTap collapses its children into one accessibility element,
which hid the nested play button from VoiceOver. The card now exposes
Play/Resume as a custom accessibility action.

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

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
…lo-Server#250)

Replace the home, movie detail, and Movies library shots with new demo-server
captures, add series detail and TV Shows library shots, and rebuild the iOS
contact sheet from all 13 images.

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
* Gate debug launch paths and name URL schemes

* Preserve deep links across cold launches

* Fix play deep-link presentation

* Harden deep link intent handling

* fix(deeplink): drain queued Downloads links right after capability refresh

A cold-launch continuum://downloads link waited for the entire
DownloadManager.onAppActive() pass (reconcile, subscription sync,
progress sync, retention) before it could be routed. On a slow or
unreachable server that left a notification tap sitting on the initial
screen until several requests timed out.

onAppActive() now reports when the capability refresh completes so
ContentView can mark Downloads hydrated and drain the link immediately,
while the remaining sync work continues independently.

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

* refactor: rename legacy Continuum identifiers to Silo

Rename the remaining Continuum-era type names, files, view modifiers,
color and typography tokens, notification names, Info.plist keys, os_log
subsystems, gesture recognizer names, and realtime client names to Silo.

Persisted contracts are intentionally unchanged so existing installs
keep their state: the continuum:// URL scheme (Top Shelf, push payloads,
Android parity), com.continuum.* keychain accounts, the
continuumServerRegistry.* UserDefaults keys, the BGTaskScheduler and
background URLSession identifiers, and the legacy migration source keys.

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

---------

Co-authored-by: Quick104 <31828688+Quick104@users.noreply.github.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
…e preview (Silo-Server#248)

* fix(player): keep embedded stream 0 selectable and align the signature preview

Two gaps from Silo-Server#247, raised in review after merge:

The server emits index,omitempty on an Int, so an embedded subtitle at
FFmpeg stream 0 arrives with no index. SubtitleTrackCandidates dropped it,
the detail preview filtered it out, and the plan adapter could not map it
to a combined ordinal. All three now read a nil embedded index as stream 0.

The detail preview's signature pass skipped tracks without an index, which
excludes every external sidecar, while playback's resolver scores them. It
now considers every row, so a signature tie resolves to the same track.

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

* docs: describe both AetherEngine fork patches in the notices

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

* fix(detail): let the selector pick embedded subtitle stream 0

The detail selector still read the raw nil index for an embedded track at
FFmpeg stream 0, so subtitleOptions marked it unselectable, the iOS and
tvOS selectors disabled the row, and sanitization and persistence rejected
index 0 because they only inspected non-nil catalog indices.

Add SubtitleTrack.selectionIndex, which reads a missing embedded index as
stream 0 and stays nil for external sidecars, and use it in subtitleOptions,
the value label, the server-preferred resolver, both sanitizers, subtitle
pref persistence, and the earlier ad-hoc `?? 0` sites.

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

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
…o-Server#251)

* refactor: remove obsolete Apple client code and test scaffolding

* refactor: finish overlay cleanup from ponytail review
…o-Server#252)

* fix(player): select native subtitles and preserve sidecar timing

* fix(player): preserve local subtitle selections during renewal

* fix(subtitles): use external track state for Apple cue timing

* fix(player): dispose rejected native subtitle candidates

* fix(player): renew with the current downloaded subtitle

* fix(player): preserve subtitle intent through recovery and teardown

* fix(player): retain native subtitle identity for secondary selection
* feat(player): render styled ASS subtitles locally

* fix(player): complete ASS subtitle routing and release sources
JonahMMay and others added 26 commits September 28, 2026 21:42
…stub

PrairieAPI now joins the image-size probe with refresh(retryFailed: false).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Upstream's account-session rework removed ServerRegistry.signOut,
discardLegacyKeychainAccountsIfUnmigrated and
TokenStore.clearTokensAfterRejectedRefresh. Drop the registry tests that
only exercised the removed methods, and check the persistent rejected-
refresh clear through invalidateRejectedRefresh instead (first call
clears, a replay of the same capture is refused).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Add upstream's API v2 client, model, decoding and projection suites
(catalog, collections, library browse, downloads, auth/device, probe and
contract state, metadata AI, onboarding, requests, track preferences,
watch state) to PrairieNetworkingTests, together with the shared
URLProtocol stub, fixture loader and the vendored Tests/Fixtures/APIv2
bodies. They run through the real HTTPClient and APIv2Client, so the
new Networking/APIv2 surface is covered by real tests rather than
excluded from the 95% gate.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
AuthDeviceV2Tests (QRLoginViewModel + real AuthService),
APIv2ContractStateTests and APIv2ProbeTests (real ConnectionMonitor
verdict state) and DiagnosticsCapabilitiesV2Tests (DiagnosticsCoordinator
helpers) need app types the FFmpeg-free host only stubs. They keep
running in PrairieTests/PrairieTVTests.

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

Upstream's move to /api/v2 deleted the v1 wire models (Profile,
ProfilesResponse, LoginResponse, Signup/SetupRequest, device-login poll,
approve and capability bodies, SubtitlePref/AudioPref, library playback
prefs, AdminStats, CatalogFilters, CollectionItemsResponse, discover and
library-collection wire shapes, SettingsContractCapabilities) and made
UpdateProfileBody, MetadataAIStatus and DeviceLookupResponse non-Codable.
Remove the Prairie gate-fill assertions that only exercised those, and
update the rest to the new shapes: FileVersion(editionRaw:), string
Person ids, failable SyncProgressItem, ServerEntry.legacyProfileId.
RequestsV2Tests stays in PrairieTests only (it drives
RequestDetailViewModel).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- ServerRegistryMigrationTests reads ServerEntry.legacyProfileId (the
  public profileId accessor and ServerRegistry.setProfileId are gone).
- SettingValueModelsGateFillTests drops the removed
  contractIsAheadOfServer check.
- SettingsConformanceTests (now asserts app-side SeekIntervalContract,
  ProfileSettingKeys and player settings batches) and WatchStateV2Tests
  (drives DownloadManager) stay in PrairieTests/PrairieTVTests only.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Add the Foundation-only StreamRequest, catalog filter/facet/sort and
audiobook playback-context types to PrairieNetworkingHost so
AIModelDecodingTests, CatalogActionsAPITests and CatalogV2Tests compile
there. CatalogPagingAPITests (StartupContentPrefetcher, query builder)
and the two download-registry suites (DownloadManager/DownloadStore)
stay in PrairieTests/PrairieTVTests only.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…stry rework

After the sync, persistent sessions go through AccountSessionPersistence
(installing one needs the server origin), the keychain moved from the
com.continuum service to org.prairieserver.prairie with the old names
kept for migration, cache keys carry a v2 segment, Specials sort first,
editions decode from edition_raw, and ServerRegistry no longer merges or
mirrors the legacy per-entry profile.

- TokenStorePersistenceTests sets the server URL before saving tokens
  and asserts the new keychain names plus the legacy ones.
- ResponseCache, season sort, edition, requests status and settings
  error mapping assertions follow the new behaviour.
- Drop the ServerRegistryMigrationTests cases and gate-fill assertions
  that only described the removed legacy-profile and pre-canonical
  token migration.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Add CredentialIdentity, MediaRequestAuthorization, MetadataSingleFlight,
PersonalListsAPI, ServerIdentityMatching, SettingValueWrites,
SharedDefaults, SharedKeychainCheckedRead, HTTPClient diagnostics
classification and WatchPartyAPI tests, plus the Foundation-only watch
party invitation/socket and URL scheme types they use.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
APIv2CatalogReadProjectionTests decodes a fully populated catalog item
(versions with every track kind, chapters and markers, playback
variants, audiobook extension, videos, extras, overlay, user data and
state), a watch detail, seasons, episodes, a person and a user library,
and checks each field lands on the presentation model. It also pins the
legacy-ID boundary (opaque, zero, padded and negative file IDs are
rejected) and the partial-page guard on finite catalog collections.

HTTPClientDiagnosticsClassificationTests stays out of the host: the
types it asserts are compiled for iOS/tvOS only.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…en test

APIv2PlaybackModelsTests covers the capability gate (every unavailable
state, missing protocol version or feature, missing installation id),
the playback-control WebSocket handshake and its input validation,
control capabilities, sequenced progress samples and the progress/stop
bodies that wrap them, mutation receipts, and the source projection
onto the protocol v3 descriptor including the legacy-ID guard.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- APIv2WireModelsTests: string-enum forward compatibility, account,
  system and problem documents, profile projection and PATCH encoding
  (set/clear/unchanged), device-login poll validation, capability,
  start and lookup presentation, subtitle AI status/quota/job projection
  and create-body validation, download registry and series-monitor
  models, requests errors.
- APIv2CatalogQueryAndSyncTests: catalog query parameters, validation
  and GET/POST sizing, query factories and body, catalog restart
  classification, page/filter/collection-tab decoding, progress sync
  items, batches and outcome summaries, mutation delivery, and the
  update-requirement classification.
- APIv2PlaybackModelsTests reads sequenced samples with a non-converting
  decoder: the sample spells its snake_case keys out.

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

The sync replaced PrairieAPI's /api/v1 calls with upstream's APIv2Client.
Its files are the per-operation request/response plumbing over
HTTPClient, the same category as the PrairieAPI*.swift files the gate
already excludes:

- APIv2Client.swift: request building, auth capture, response gating.
- APIv2Client+{Collections,Diagnostics,Downloads,DownloadSubscriptions,
  MetadataAI,PersonalLists,Playback,Progress,Requests,Settings,
  SubtitleAI,Subtitles,WatchParty}.swift: one extension per endpoint
  group, each a thin call into APIv2Client.
- APIv2Probe.swift: the live unauthenticated /api/v2/system/info probe.

The wire models, catalog projection, mutation-outcome and dispatch
classification under Networking/APIv2 stay in scope, now with tests, and
the 95% threshold is unchanged.

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

- MetadataRequestPoolTests drives the pool over PrairieAPI/APIv2Client
  on the shared stub: concurrent reads of one item share a request, a
  finished flight or another key dispatches again, season/episode/watch
  reads resolve, and a cancelled sole waiter drops its flight.
- ServerIdentityProbeTests covers the identity probe's answers (id,
  blank id, legacy plain 404, other 404, 5xx, offline), the branding
  name, the bearer connections read and its availability gate, and
  ServerEndpoint coding and help text.
- APIv2PlaybackModelsTests projects the vendored playable decision onto
  legacy file ids and rejects an opaque effective id.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
TokenStoreSessionTests exercises the post-sync session store against
in-memory keychains: install preconditions and expected-account checks,
installing for another server, verified-account binding and durable
authority, the ownership fence on both sides of an await, request
capture against each identity field, the mismatch-reason classifier,
temporary scope begin/replace/end, session snapshots restored across
session, signed-out and legacy states, token deletion, profile
activation and deactivation, and expiry-event routing for persistent
and temporary credentials.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- CatalogShapeModelsTests: catalog pages as card grids, server-relative
  artwork resolved against the decoding server (and protocol-relative
  URLs left alone), section/browse row conversion both ways, season
  download names, subtitle selection indexes, and the collection and
  user bodies.
- WatchPartyModelsTests: forward-compatible enums, capability defaults
  and socket gating, strict room validation with numeric-or-string ids,
  suggestions, transport commands, tickets, member state and the picker.

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

- APIv2SubtitleSearchModelsTests: provider status, stored-subtitle
  projection and its file-id guard, search/download bodies, and every
  step of SubtitleDownloadOutcome.resolve (download failure, relist
  failure, owner change, missing listing entry, failed registration,
  success) plus its error classification.
- APIv2PlaybackModelsTests wraps the vendored protocol v3 start, replan
  and route-event requests in their v2 bodies (installation id, event
  id, opaque file id). The PlaybackV3 fixtures are now bundled for the
  networking host too.
- TokenStoreSessionTests: the two account-identity mismatches share one
  reason, so the classifier names seven distinct causes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The vendored replan and route-event fixtures are the server's own
recovered shapes and omit fields the client model requires
(local_mutations, applied_quirk_ids). Build both requests from the
vendored start request and plan instead, and check the v2 body adds the
installation and event ids around them.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The coverage gate reports one scoped percentage. When it drops, print
each partly covered Networking file's unexecuted line ranges from the
result bundle, so the gap can be found from the CI log alone.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- TokenStoreFailureTests injects canonical session storage that can
  fail reads, writes or removes, and checks that rotations and
  invalidations for a foreign or stale owner are discarded, a temporary
  rejection is reported once, unreadable or unwritable storage blocks
  the runtime session instead of trusting stale credentials, and a
  server switch drops a push display token issued for another server.
- ServerRegistryEdgeTests covers verified-identity updates and their
  rollback, cancelled switches and removals, a removal that rolls back
  when sign-out cannot persist, seeding the shared suite from the
  standard fallback, and moving legacy per-entry profiles into the
  launch store.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
TemporaryAuthScope.profileToken is not optional.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The step's final while-loop test returned 1 for a fully covered last
file, which failed the job under the runner's errexit+pipefail shell and
skipped the coverage gate.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The sync's Apple regression workflow compiles the whole Tests tree into
PrairieTests (iOS) and PrairieTVTests (tvOS).

- NetworkingPrairieGateFillTests set AuthService.shared.isLoggedIn,
  which only the networking host's stand-in allows. Those lines now sit
  behind PRAIRIE_NETWORKING_HOST, a condition set on the host and its
  test bundle.
- LiveTVChannelListViewInspectorTests imports ViewInspector, which only
  the iOS test bundle links, so PrairieTVTests excludes it like its
  other platform-bound suites.

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

Upstream's HTTPClient asserts in DEBUG that every request is an /api/v2
route or the health probe. Prairie's Live TV deliberately stays on its
Prairie-only /api/v1/livetv routes, so every debug build (and every test
that switches servers, which refreshes LiveTVFeatureStore) crashed on
the assertion. Accept the /api/v1/livetv/ prefix alongside v2 and pin it
in HTTPClientRequestPathTests; regenerate the v1 allowlist for the new
deliberate matches.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…a relative URL

ArtworkVariantURL.candidates rewrote .webp to .png/.avif only for
absolute URLs. A server-relative signed path such as
/artwork/.../w300.rev.webp?expires=..&sig=.. has a path extension of
"webp?expires=..", so the sibling was dropped and only the WebP
candidate was offered. Rewrite the path part and keep the query and
fragment, as ArtworkVariantURLTests expects.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- ProfileAndQualitySettingsTests: the merge rebranded "X-Silo-Original",
  but it is the settings contract's private-use language tag (the code
  and the vendored manifest use x-silo-original), not branding.
- LiveTVChannelListViewInspectorTests: ViewInspector inspects a fresh
  copy of the view, so tapping a tab never reached the next inspection.
  Check the tab bar and the Guide tab the list opens on instead.
- ServerRegistryMigrationTests' relaunch test and ServerRegistryEdgeTests'
  suite-seeding test describe the defaults-backed registry; tvOS keeps
  its registry in the shared Keychain, so they are skipped there.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@JonahMMay
JonahMMay marked this pull request as ready for review September 29, 2026 00:13
@JonahMMay
JonahMMay merged commit 6dae823 into main Sep 29, 2026
6 checks passed
@JonahMMay
JonahMMay deleted the sync/upstream-2026-09-28 branch September 29, 2026 00:15
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