Skip to content

perf: reduce cache work and keep catalogue artwork loading - #34

Merged
blurbery merged 2 commits into
mainfrom
perf/cache-and-artwork
Sep 26, 2026
Merged

blurbery merged 2 commits into
mainfrom
perf/cache-and-artwork

Conversation

@blurbery

@blurbery blurbery commented Sep 26, 2026 •

Copy link
Copy Markdown
Owner

Problem and reproduction

Browsing repeatedly recalculated identical artwork cache keys, queued intermediate Home snapshots and could download the same artwork separately for different consumers. Home also read and decoded its saved snapshot synchronously during startup.

On Apple TV, scrolling farther down Movies or Series could leave blank posters after the focus highlight disappeared. The artwork window followed the remembered focus position after it had left the viewport, and cards replaced their image subtree when leaving that window.

Implementation and impact

  • Reuse the last server/account/profile cache key while preserving its exact encoding, including Unicode boundaries.
  • Combine queued Home saves per scope. Running writes finish, and reads/deletions preserve ordering.
  • Prepare saved Home metadata on the serial I/O queue before normal startup and profile/login navigation. Cached first paint remains available without waiting for fresh server content; direct callers retain the synchronous fallback.
  • Share simultaneous artwork bytes across sizes for Silo, Emby and Jellyfin on iOS/iPadOS and tvOS. Full URLs and account/profile scopes remain separate. Cancelling one consumer preserves the others; the final cancellation stops the transfer.
  • Follow visible catalogue rows, prioritise visible/upcoming posters and retain a stable image view. Seven-column grids request the next page four rows from the end instead of two.

General Settings metadata save/clear controls, cache formats, image dimensions, retry limits and playback configuration are preserved. Account changes, clears and deletions invalidate stale preparation and save callbacks. Dependencies and release settings are unchanged.

Validation

  • Review follow-up (03d7387): All six artwork tests passed in the focused macOS harness after replacing timing assumptions with held responses and observed waiter counts. They also passed with consumers deliberately delayed by 250 ms. Source comparison confirmed that application code outside the two read-only Debug observers is unchanged; a standalone optimised pipeline object excluded both observer symbols. Both the iOS test suite and tvOS simulator build passed in Player regression for this exact follow-up commit.
  • Passed: 26 focused XCTest cases in a temporary macOS SwiftPM harness using the production cache, metadata and image-pipeline logic. UIKit wrapping, app dependencies and diagnostics used stand-ins. Synthetic URLProtocol responses exercised Foundation transport and ImageIO decoding. Coverage includes identity compatibility, ordered/coalesced persistence, stale preparation, clear/delete behaviour, cross-account isolation, cancellation and recovery.
  • Passed: python3 scripts/tests/startup-recovery-tests.py --platform ios --compile-only and the same command with --platform tvos --compile-only. These compile the extracted startup flows; no simulator UI was launched.
  • Passed: focused catalogue checks for stale/missing focus, reverse scrolling, page boundaries and bounded traversal through 100,000 synthetic metadata items, plus artwork-window checks with cache/transport doubles. This was not a device test with a 100,000-item library.
  • Passed: signed VividTV Release app and Top Shelf build with Xcode 27.0, signing/shared-login identity checks, and verification that the existing native audio fix remained linked. The app was installed in place and launched successfully.
  • Passed: git diff --check. The iOS suite and tvOS simulator build also passed in the remote Player regression workflow for 03d7387; those results are separate from the local checks above.

Performance

Area Previous work Current behaviour and evidence
Artwork cache key JSON encode and SHA-256 on every lookup Reuses the last unchanged identity. Compatibility and concurrency tests pass; CPU time was not benchmarked.
Home snapshot saves Each update queued a save A blocked-queue test submits 100 updates for each of two scopes, then observes only two final save closures. This measures scheduled work, not device disk writes or elapsed time.
Home startup Synchronous snapshot read/decode on normal startup paths Normal preparation awaits background I/O. Tests confirm the main actor remains available and cached metadata is ready before hydration. Startup duration was not measured.
Artwork downloads Byte sharing was limited to the Emby URL path on tvOS Two decode sizes plus one raw-byte consumer use one transfer for each synthetic Silo/Emby/Jellyfin URL shape. Distinct image sizes remain correct. The old implementation was not benchmarked with this workload.
Catalogue warming Remembered focus could hold the window behind the viewport; pagination began two rows from the end Visible rows drive the same ten-row window, with two concurrent warmers and four-row pagination lead. The reported focus/poster issue was confirmed fixed on Apple TV.

There is no controlled before/after measurement of app startup time, FPS, memory, CPU or live-server traffic, so this PR makes no percentage speed-up claim.

Testing status

  • Tested? Yes, focused automated checks, a full tvOS Release build and owner device testing.
  • Environment: Apple TV 4K (3rd generation), tvOS 27.0; local build checks used Xcode 27.0.
  • Revision: 0e7f653393294f53860ce61e6f8ce27abcaebe54. The device build used the same application sources before committing, version 0.14.3 (40). Review follow-up 03d7387363df75e57519d3692d51afb500245564 changes tests and adds Debug-only observers; it has no Release behaviour change.
  • Checks and results: I confirmed that the scrolling fix resolved the reported issue, then confirmed that the final build works well. Automated results are listed above.
  • Limitations: No full local iOS app build or physical iPhone/iPad verification. Device feedback does not establish every provider, playback format, subtitle or audio-route combination. No controlled app benchmark was run.

Design impact and approval

  • Design impact: No design changes. Existing layout, card controls, focus styling and navigation are preserved.
  • Approval from blurbery: Not applicable, no design changes.

Documentation impact

Updated the existing Apple TV browsing and focus guides for cache preparation, shared artwork transfers, cancellation and catalogue warming.

AI disclosure

Implemented these changes with Codex as a tool.

@coderabbitai

coderabbitai Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

📝 Walkthrough

Walkthrough

The update adds scoped sharing for remote artwork transfers, asynchronous Home snapshot preparation and ordered persistence, and startup checks that await prefetch results. It also changes catalogue artwork retention and paging to follow visible rows, and adds tests for these behaviours.

Changes

Home Cache and Startup

Layer / File(s) Summary
Cache identity and ordered writes
iosApp/iosApp/Shared/VividCacheScope.swift, iosApp/iosApp/tvOS/Caching/HomeMetadataWriter.swift, iosApp/Tests/CachePerformanceTests.swift
Cache keys reuse a matching identity while retaining the existing format. Home metadata writes are ordered, and queued writes are coalesced by scope. Tests cover key compatibility, ordering, deletion, and write recovery.
Snapshot preparation and persistence
iosApp/iosApp/tvOS/Caching/TVHomeMetadataCache.swift, iosApp/Tests/HomeMetadataPreparationTests.swift, docs/apple-tv-browsing.md
The cache prepares snapshots asynchronously and activates them only when scope and generation checks pass. Persistence updates storage-error state only for the matching scope, generation, and revision. Tests cover preparation races and invalid snapshots.
Startup and profile prefetch gating
iosApp/iosApp/Startup/StartupContentPrefetcher.swift, iosApp/iosApp/ContentView.swift, iosApp/iosApp/Screens/Profiles/ProfileSelectionViewModel.swift, iosApp/iosApp/tvOS/Profiles/*, scripts/tests/startup-recovery-tests.py
Startup, profile selection, and TV login flows await prefetch results before continuing. The startup recovery harness now supports the asynchronous prefetch stub.

Shared Artwork Transfers

Layer / File(s) Summary
Scoped byte flights and cancellation
iosApp/iosApp/Shared/VividImagePipeline.swift, iosApp/Tests/ImageDataCoalescingTests.swift, iosApp/Tests/VividImageRetryTests.swift, docs/apple-tv-focus.md
Remote image and data requests share transfers by URL and cache scope. Waiter cancellation leaves transfers active for other consumers and cancels the transfer when the final cancelled waiter leaves. Tests cover sharing, isolation, cancellation, and retries.

Catalogue Artwork Window

Layer / File(s) Summary
Visible artwork window and paging
iosApp/iosApp/tvOS/Screens/Components/TVCatalogGrid.swift, iosApp/iosApp/tvOS/Screens/Components/CachedAsyncImage.swift, iosApp/iosApp/tvOS/Screens/Components/TVMediaCard.swift, docs/apple-tv-browsing.md
Artwork loading follows visible rows and focus, with a ten-row retention window and a four-row paging threshold for fixed-column grids. Non-resident cards use the ordinary placeholder without requesting artwork.

Priority: ➖ Normal

Estimated code review effort: 4 (Complex) | ~55 minutes

Change: Bug fix

Sequence Diagram(s)

sequenceDiagram
  participant StartupCaller
  participant StartupContentPrefetcher
  participant TVHomeMetadataCache
  StartupCaller->>StartupContentPrefetcher: await authenticated content prefetch
  StartupContentPrefetcher->>TVHomeMetadataCache: prepare snapshot
  TVHomeMetadataCache-->>StartupContentPrefetcher: preparation result
  StartupContentPrefetcher-->>StartupCaller: return whether startup can continue
Loading
sequenceDiagram
  participant ImageRequest
  participant VividImageDataFlights
  participant URLSession
  ImageRequest->>VividImageDataFlights: request bytes for URL and cache scope
  VividImageDataFlights->>URLSession: start or join shared transfer
  URLSession-->>VividImageDataFlights: return response data
  VividImageDataFlights-->>ImageRequest: return data or cancellation
Loading

Merge Risk: 🔵 Low · up to 0e7f6

The artwork cancellation test can fail intermittently on a slow runner. Make its synchronization deterministic; the remaining risk is limited to test reliability.

Security Architecture Review

Security architecture risk: 🔵 Low · up to 0e7f6

The changes affect account-scoped cached content and startup behavior, so they warrant review. The examined paths retain separate account and profile scopes and guard against stale work during account changes; no introduced security issue was established. Some recovery and security coverage remains incomplete.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The new sharing can affect simultaneous artwork consumers on both platforms, but its examined key and transport caches partition work by account/profile scope; no new cross-account byte-sharing path was established.

Trust Boundaries and Controls

  • observed — Per-scope artwork sessions disable shared cookie and credential storage. Remote responses require a successful HTTP status and are limited to 32 MiB before decoding; the examined path does not establish how every provider constructs or authorizes its artwork URL.

Resilience and Maintainability Implications

  • observed — Individual artwork consumers have distinct waiter identities: cancelling one releases its waiter, and the shared transfer is cancelled only when the final cancelled waiter leaves. Explicit scoped deletion and account switching can cancel all matching work.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.63% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 86 functions across 17 files. (2 skipped:… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarises the main performance and catalogue artwork changes. It is concise and specific.
Description check ✅ Passed The description is directly related to the changeset. It explains cache reuse, snapshot persistence, shared artwork downloads, catalogue warming, validation, limitations, and documentation updates.
Full details: Docstring Coverage

Explanation

Docstring coverage is 11.63% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 86 functions across 17 files. (2 skipped: 2 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

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

❤️ Share

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

@blurbery

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Sep 26, 2026 •

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: blurbery/vivid/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: d43aa176-8a47-45f3-bc99-dede268d955e

📥 Commits

Reviewing files that changed from the base of the PR and between 1617a37 and 0e7f653.

📒 Files selected for processing (19)
  • docs/apple-tv-browsing.md
  • docs/apple-tv-focus.md
  • iosApp/Tests/CachePerformanceTests.swift
  • iosApp/Tests/HomeMetadataPreparationTests.swift
  • iosApp/Tests/ImageDataCoalescingTests.swift
  • iosApp/Tests/VividImageRetryTests.swift
  • iosApp/iosApp/ContentView.swift
  • iosApp/iosApp/Screens/Profiles/ProfileSelectionViewModel.swift
  • iosApp/iosApp/Shared/VividCacheScope.swift
  • iosApp/iosApp/Shared/VividImagePipeline.swift
  • iosApp/iosApp/Startup/StartupContentPrefetcher.swift
  • iosApp/iosApp/tvOS/Caching/CachedAsyncImage.swift
  • iosApp/iosApp/tvOS/Caching/HomeMetadataWriter.swift
  • iosApp/iosApp/tvOS/Caching/TVHomeMetadataCache.swift
  • iosApp/iosApp/tvOS/Profiles/TVLoginPreparation.swift
  • iosApp/iosApp/tvOS/Profiles/TVSavedAccountStore.swift
  • iosApp/iosApp/tvOS/Screens/Components/TVCatalogGrid.swift
  • iosApp/iosApp/tvOS/Screens/Components/TVMediaCard.swift
  • scripts/tests/startup-recovery-tests.py

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

Comment thread iosApp/Tests/ImageDataCoalescingTests.swift
@blurbery

Copy link
Copy Markdown
Owner Author

I also checked the docstring coverage warning. I am leaving the blanket 80% target suggestion unchanged: the ordering, cancellation and cache compatibility rules already have targeted comments and updated guides. Adding docstrings to every internal helper and test would not improve the behaviour or clarify a missing contract identified by this review. No review settings were changed.

@blurbery

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Sep 26, 2026 •

Copy link
Copy Markdown
⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@blurbery
blurbery merged commit e5f1c5d into main Sep 26, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant