{"doc_urls":["index.html#jellytau-documentation","requirements.html#requirements-specification","requirements.html#1-user-requirements","requirements.html#2-software-requirements","requirements.html#21-integration-requirements","requirements.html#22-jellyfin-api-requirements","requirements.html#23-development-requirements","requirements.html#3-traceability-matrix","requirements.html#user-requirements-to-software-requirements","requirements.html#4-test-traceability","requirements.html#unit-tests-to-software-requirements","requirements.html#integration-tests","requirements.html#5-technical-debt","requirements.html#open-items-carried-over-from-the-v060-codebase-audit","requirements.html#linux-keyring-integration-workaround","requirements.html#platform-playback-backend-parity-linux-vs-android","requirements.html#frontend-playback-code-duplication","traceability.html#code-traceability-matrix","traceability.html#summary","traceability.html#requirements-by-type","traceability.html#user-requirements-ur","traceability.html#integration-requirements-ir","traceability.html#development-requirements-dr","traceability.html#jellyfin-api-requirements-ja","traceability.html#detailed-mapping","traceability.html#ir-003","traceability.html#ir-004","traceability.html#ir-006","traceability.html#ir-008","traceability.html#ir-009","traceability.html#ir-010","traceability.html#ir-011","traceability.html#ir-012","traceability.html#ir-013","traceability.html#ir-014","traceability.html#ir-015","traceability.html#ir-016","traceability.html#ir-018","traceability.html#ir-019","traceability.html#ir-020","traceability.html#ir-021","traceability.html#ir-022","traceability.html#ir-023","traceability.html#ir-024","traceability.html#ir-025","traceability.html#ir-026","traceability.html#ir-027","traceability.html#ir-028","traceability.html#ir-030","traceability.html#ir-031","traceability.html#dr-001","traceability.html#dr-002","traceability.html#dr-003","traceability.html#dr-004","traceability.html#dr-005","traceability.html#dr-006","traceability.html#dr-007","traceability.html#dr-009","traceability.html#dr-010","traceability.html#dr-011","traceability.html#dr-012","traceability.html#dr-013","traceability.html#dr-014","traceability.html#dr-015","traceability.html#dr-016","traceability.html#dr-017","traceability.html#dr-018","traceability.html#dr-020","traceability.html#dr-021","traceability.html#dr-022","traceability.html#dr-023","traceability.html#dr-024","traceability.html#dr-025","traceability.html#dr-026","traceability.html#dr-027","traceability.html#dr-028","traceability.html#dr-029","traceability.html#dr-030","traceability.html#dr-032","traceability.html#dr-033","traceability.html#dr-034","traceability.html#dr-035","traceability.html#dr-036","traceability.html#dr-037","traceability.html#dr-038","traceability.html#dr-039","traceability.html#dr-040","traceability.html#dr-041","traceability.html#dr-042","traceability.html#dr-043","traceability.html#dr-044","traceability.html#dr-045","traceability.html#dr-047","traceability.html#dr-048","traceability.html#dr-049","traceability.html#dr-050","traceability.html#dr-051","traceability.html#dr-052","traceability.html#dr-053","traceability.html#dr-054","traceability.html#dr-055","traceability.html#dr-056","traceability.html#dr-057","traceability.html#dr-058","traceability.html#dr-059","traceability.html#dr-060","traceability.html#dr-061","traceability.html#dr-062","traceability.html#dr-063","traceability.html#dr-064","traceability.html#dr-065","traceability.html#dr-066","traceability.html#dr-067","traceability.html#dr-068","traceability.html#dr-069","traceability.html#dr-070","traceability.html#dr-074","traceability.html#dr-075","traceability.html#dr-076","traceability.html#dr-077","traceability.html#dr-078","traceability.html#dr-079","traceability.html#dr-080","traceability.html#dr-081","traceability.html#dr-082","traceability.html#dr-083","traceability.html#dr-084","traceability.html#dr-085","traceability.html#dr-086","traceability.html#dr-087","traceability.html#dr-088","traceability.html#dr-089","traceability.html#dr-090","traceability.html#dr-091","traceability.html#dr-092","traceability.html#dr-093","traceability.html#dr-095","traceability.html#dr-096","traceability.html#dr-097","traceability.html#dr-098","traceability.html#dr-099","traceability.html#dr-100","traceability.html#dr-101","traceability.html#dr-102","traceability.html#dr-103","traceability.html#dr-104","traceability.html#dr-105","traceability.html#dr-106","traceability.html#dr-107","traceability.html#dr-108","traceability.html#dr-109","traceability.html#dr-110","traceability.html#dr-111","traceability.html#dr-112","traceability.html#dr-113","traceability.html#dr-114","traceability.html#dr-115","traceability.html#dr-116","traceability.html#dr-117","traceability.html#dr-118","traceability.html#dr-119","traceability.html#dr-120","traceability.html#dr-123","traceability.html#dr-126","traceability.html#dr-127","traceability.html#dr-128","traceability.html#dr-129","traceability.html#dr-130","traceability.html#dr-131","traceability.html#dr-132","traceability.html#dr-133","traceability.html#dr-134","traceability.html#dr-135","traceability.html#dr-136","traceability.html#dr-137","traceability.html#dr-140","traceability.html#dr-141","traceability.html#dr-142","traceability.html#dr-143","traceability.html#dr-147","traceability.html#dr-148","traceability.html#dr-149","traceability.html#dr-150","traceability.html#dr-151","traceability.html#dr-152","traceability.html#dr-153","traceability.html#dr-154","traceability.html#dr-155","traceability.html#dr-156","traceability.html#dr-157","traceability.html#dr-158","traceability.html#dr-159","traceability.html#dr-160","traceability.html#dr-162","traceability.html#dr-167","traceability.html#dr-168","traceability.html#dr-169","traceability.html#dr-170","traceability.html#dr-171","traceability.html#dr-173","traceability.html#dr-174","traceability.html#dr-175","traceability.html#dr-176","traceability.html#dr-177","traceability.html#dr-178","traceability.html#dr-179","traceability.html#dr-180","traceability.html#dr-181","traceability.html#dr-182","traceability.html#dr-183","traceability.html#dr-185","traceability.html#dr-186","traceability.html#dr-188","traceability.html#dr-189","traceability.html#dr-193","traceability.html#dr-195","traceability.html#dr-196","traceability.html#dr-197","traceability.html#dr-198","traceability.html#dr-201","traceability.html#dr-203","traceability.html#dr-204","traceability.html#dr-205","traceability.html#dr-206","traceability.html#dr-207","traceability.html#dr-209","traceability.html#dr-210","traceability.html#dr-211","traceability.html#dr-212","traceability.html#dr-213","traceability.html#dr-215","traceability.html#dr-216","traceability.html#dr-217","traceability.html#dr-218","traceability.html#dr-219","traceability.html#dr-220","traceability.html#dr-221","traceability.html#dr-223","traceability.html#dr-224","traceability.html#ja-001","traceability.html#ja-002","traceability.html#ja-003","traceability.html#ja-004","traceability.html#ja-005","traceability.html#ja-006","traceability.html#ja-007","traceability.html#ja-008","traceability.html#ja-009","traceability.html#ja-010","traceability.html#ja-011","traceability.html#ja-012","traceability.html#ja-013","traceability.html#ja-014","traceability.html#ja-015","traceability.html#ja-016","traceability.html#ja-017","traceability.html#ja-018","traceability.html#ja-019","traceability.html#ja-020","traceability.html#ja-021","traceability.html#ja-022","traceability.html#ja-023","traceability.html#ja-024","traceability.html#ja-025","traceability.html#ja-026","traceability.html#ja-028","traceability.html#ja-029","traceability.html#ja-030","traceability.html#ja-031","traceability.html#ja-032","traceability.html#ja-033","traceability.html#ja-034","traceability.html#ja-035","traceability.html#ja-036","traceability.html#ur-003","traceability.html#ur-004","traceability.html#ur-005","traceability.html#ur-008","traceability.html#ur-009","traceability.html#ur-018","traceability.html#ur-019","traceability.html#ur-020","traceability.html#ur-021","traceability.html#ur-023","traceability.html#ur-025","traceability.html#ur-026","traceability.html#ur-027","traceability.html#ur-034","traceability.html#ur-040","traceability.html#ur-041","traceability.html#ur-053","traceability.html#ur-055","traceability.html#ur-056","traceability.html#ur-062","traceability.html#ur-064","traceability.html#ut-010","traceability.html#ut-024","traceability.html#ut-052","traceability.html#ut-060","traceability.html#ut-061","traceability.html#ut-062","traceability.html#ut-066","traceability.html#ut-071","traceability.html#ut-085","traceability.html#ut-086","traceability.html#ut-087","traceability.html#ut-088","traceability.html#ut-089","traceability.html#ut-090","traceability.html#ut-091","traceability.html#ut-092","traceability.html#ut-093","traceability.html#ut-094","traceability.html#ut-095","traceability.html#ut-096","traceability.html#ut-097","traceability.html#ut-098","traceability.html#ut-105","traceability.html#ut-106","traceability.html#ut-117","traceability.html#ut-118","traceability.html#ut-119","traceability.html#ut-123","traceability.html#ut-124","traceability.html#ut-131","traceability.html#ut-132","traceability.html#ut-133","traceability.html#ut-134","traceability.html#ut-135","traceability.html#ut-136","traceability.html#ut-137","traceability.html#ut-138","traceability.html#ut-139","traceability.html#ut-149","traceability.html#ut-158","traceability.html#ut-159","traceability.html#ut-160","traceability.html#ut-181","traceability.html#ut-182","traceability.html#ur-067","traceability.html#it-013","traceability.html#ut-186","traceability.html#ut-192","traceability.html#ut-211","traceability.html#ur-006","traceability.html#ur-013","traceability.html#ur-015","traceability.html#ur-016","traceability.html#ur-022","traceability.html#ur-024","traceability.html#ur-043","traceability.html#ur-049","traceability.html#ur-050","traceability.html#ur-071","traceability.html#ur-073","traceability.html#ur-074","traceability.html#it-003","traceability.html#it-004","traceability.html#it-016","traceability.html#ut-003","traceability.html#ut-004","traceability.html#ut-005","traceability.html#ut-012","traceability.html#ut-026","traceability.html#ut-027","traceability.html#ut-028","traceability.html#ut-029","traceability.html#ut-030","traceability.html#ut-031","traceability.html#ut-032","traceability.html#ut-033","traceability.html#ut-051","traceability.html#ut-059","traceability.html#ut-067","traceability.html#ut-072","traceability.html#ut-073","traceability.html#ut-074","traceability.html#ut-075","traceability.html#ut-076","traceability.html#ut-077","traceability.html#ut-078","traceability.html#ut-079","traceability.html#ut-080","traceability.html#ut-081","traceability.html#ut-082","traceability.html#ut-099","traceability.html#ut-100","traceability.html#ut-101","traceability.html#ut-102","traceability.html#ut-104","traceability.html#ut-107","traceability.html#ut-108","traceability.html#ut-109","traceability.html#ut-110","traceability.html#ut-111","traceability.html#ut-112","traceability.html#ut-113","traceability.html#ut-114","traceability.html#ut-116","traceability.html#ut-120","traceability.html#ut-130","traceability.html#ut-140","traceability.html#ut-151","traceability.html#ut-170","traceability.html#ur-002","traceability.html#ur-007","traceability.html#ur-011","traceability.html#ur-029","traceability.html#ur-030","traceability.html#ur-031","traceability.html#ur-032","traceability.html#ur-035","traceability.html#ur-036","traceability.html#ur-037","traceability.html#ur-038","traceability.html#ur-042","traceability.html#ur-046","traceability.html#ur-047","traceability.html#ur-048","traceability.html#ur-051","traceability.html#ur-052","traceability.html#ur-057","traceability.html#ur-058","traceability.html#ur-063","traceability.html#ur-065","traceability.html#ur-068","traceability.html#ur-069","traceability.html#ur-075","traceability.html#ur-076","traceability.html#ur-078","traceability.html#ut-145","traceability.html#ut-146","traceability.html#ut-167","traceability.html#ut-168","traceability.html#ur-028","traceability.html#ur-054","traceability.html#ur-066","traceability.html#ut-174","traceability.html#ur-061","traceability.html#ut-143","traceability.html#ut-144","traceability.html#ut-147","traceability.html#ut-175","traceability.html#ut-184","traceability.html#ut-185","traceability.html#ut-187","traceability.html#ut-188","traceability.html#ut-189","traceability.html#ur-017","traceability.html#ur-039","traceability.html#ut-207","traceability.html#ur-010","traceability.html#ut-183","traceability.html#ur-012","traceability.html#ur-059","traceability.html#ut-208","traceability.html#ur-060","traceability.html#ur-072","traceability.html#ur-077","traceability.html#ut-201","traceability.html#ut-206","traceability.html#ut-209","traceability.html#ur-033","traceability.html#ur-045","traceability.html#it-017","traceability.html#ut-014","traceability.html#ut-015","traceability.html#ut-016","traceability.html#ut-017","traceability.html#ut-018","traceability.html#ut-019","traceability.html#ut-020","traceability.html#ut-021","traceability.html#ut-022","traceability.html#ut-023","traceability.html#ut-025","traceability.html#ut-042","traceability.html#ut-043","traceability.html#ut-044","traceability.html#ut-083","traceability.html#ut-084","traceability.html#ut-103","traceability.html#ut-115","traceability.html#ut-121","traceability.html#ut-122","traceability.html#ut-125","traceability.html#ut-126","traceability.html#ut-127","traceability.html#ut-141","traceability.html#ut-142","traceability.html#ut-148","traceability.html#ut-150","traceability.html#ut-152","traceability.html#ut-154","traceability.html#ut-155","traceability.html#ut-156","traceability.html#ut-157","traceability.html#ut-161","traceability.html#ut-162","traceability.html#ut-163","traceability.html#ut-164","traceability.html#ut-166","traceability.html#ut-171","traceability.html#ut-172","traceability.html#ut-173","traceability.html#ut-176","traceability.html#ut-177","traceability.html#ut-178","traceability.html#ut-179","traceability.html#ut-180","traceability.html#ut-190","traceability.html#ut-191","traceability.html#ut-193","traceability.html#ut-194","traceability.html#ut-195","traceability.html#ut-196","traceability.html#ut-197","traceability.html#ut-198","traceability.html#ut-200","traceability.html#ut-202","traceability.html#ut-203","traceability.html#ut-205","traceability.html#ur-014","traceability.html#ur-044","traceability.html#ut-204","traceability.html#ut-210","traceability-ci.html#requirement-traceability-cicd-pipeline","traceability-ci.html#overview","traceability-ci.html#gitea-actions-workflows","traceability-ci.html#what-gets-validated","traceability-ci.html#1-trace-extraction","traceability-ci.html#2-coverage-thresholds","traceability-ci.html#2b-dangling-requirement-ids","traceability-ci.html#3-modified-file-checking","traceability-ci.html#how-to-add-traces-to-new-code","traceability-ci.html#typescriptsvelte-example","traceability-ci.html#rust-example","traceability-ci.html#test-example","traceability-ci.html#traces-format","traceability-ci.html#workflow-behavior","traceability-ci.html#on-push-to-main-branch","traceability-ci.html#on-pull-request","traceability-ci.html#failure-scenarios","traceability-ci.html#viewing-reports","traceability-ci.html#in-gitea-actions-ui","traceability-ci.html#locally","traceability-ci.html#coverage-goals","traceability-ci.html#current-status","traceability-ci.html#targets","traceability-ci.html#improving-coverage","traceability-ci.html#for-missing-user-requirements-ur","traceability-ci.html#for-missing-integration-requirements-ir","traceability-ci.html#for-missing-development-requirements-dr","traceability-ci.html#for-jellyfin-api-requirements-ja","traceability-ci.html#example-pr-checklist","traceability-ci.html#troubleshooting","traceability-ci.html#coverage-below-minimum-threshold","traceability-ci.html#new-files-without-traces","traceability-ci.html#invalid-json-format","traceability-ci.html#integration-with-development","traceability-ci.html#before-committing","traceability-ci.html#in-your-ide","traceability-ci.html#git-hooks","traceability-ci.html#references","traceability-ci.html#support","traces-quick-ref.html#traces-quick-reference-guide","traces-quick-ref.html#what-are-traces","traces-quick-ref.html#quick-examples","traces-quick-ref.html#typescript","traces-quick-ref.html#svelte","traces-quick-ref.html#rust","traces-quick-ref.html#requirement-types","traces-quick-ref.html#where-to-find-requirements","traces-quick-ref.html#how-to-add-traces","traces-quick-ref.html#step-1-find-the-requirement","traces-quick-ref.html#step-2-add-comment","traces-quick-ref.html#step-3-run-extraction","traces-quick-ref.html#common-patterns","traces-quick-ref.html#single-requirement","traces-quick-ref.html#multiple-requirements-same-type","traces-quick-ref.html#multiple-types","traces-quick-ref.html#test-coverage","traces-quick-ref.html#modulesfiles","traces-quick-ref.html#validation","traces-quick-ref.html#check-your-changes","traces-quick-ref.html#before-committing","traces-quick-ref.html#cicd-validation","traces-quick-ref.html#tips--tricks","traces-quick-ref.html#find-related-code","traces-quick-ref.html#update-your-editor","traces-quick-ref.html#find-untraced-code","traces-quick-ref.html#faq","traces-quick-ref.html#see-also","architecture/index.html#jellytau-software-architecture","architecture/index.html#architecture-overview","architecture/index.html#architecture-principles","architecture/index.html#detailed-documentation","architecture/index.html#file-structure-summary","architecture/index.html#key-architecture-changes","architecture/01-rust-backend.html#rust-backend-architecture","architecture/01-rust-backend.html#media-session-state-machine","architecture/01-rust-backend.html#player-state-machine-low-level-playback","architecture/01-rust-backend.html#playback-mode-state-machine","architecture/01-rust-backend.html#media-item--source","architecture/01-rust-backend.html#queue-manager","architecture/01-rust-backend.html#favorites-system","architecture/01-rust-backend.html#marking","architecture/01-rust-backend.html#browsing","architecture/01-rust-backend.html#player-backend-trait","architecture/01-rust-backend.html#player-controller","architecture/01-rust-backend.html#playlist-system","architecture/01-rust-backend.html#tauri-commands-player","architecture/01-rust-backend.html#domain-vocabulary-owned-by-rust","architecture/01-rust-backend.html#search-scope-and-the-taxonomy-boundary","architecture/01-rust-backend.html#library-exclusions","architecture/01-rust-backend.html#streaming-quality-ladder","architecture/01-rust-backend.html#background-workers","architecture/01-rust-backend.html#catalog-indexer","architecture/02-svelte-frontend.html#svelte-frontend-architecture","architecture/02-svelte-frontend.html#store-structure","architecture/02-svelte-frontend.html#music-library-architecture","architecture/02-svelte-frontend.html#playback-reporting-service","architecture/02-svelte-frontend.html#repository-architecture-rust-based","architecture/02-svelte-frontend.html#playback-mode-system","architecture/02-svelte-frontend.html#database-service-abstraction","architecture/02-svelte-frontend.html#component-hierarchy","architecture/02-svelte-frontend.html#miniplayer-behavior","architecture/02-svelte-frontend.html#sleep-timer-architecture","architecture/02-svelte-frontend.html#auto-play-episode-limit","architecture/02-svelte-frontend.html#player-page-navigation-guard","architecture/02-svelte-frontend.html#playlist-management-ui","architecture/02-svelte-frontend.html#app-shell-and-chrome","architecture/02-svelte-frontend.html#visibility-rules","architecture/02-svelte-frontend.html#the-structural-fix-worth-not-undoing","architecture/02-svelte-frontend.html#accountmenu","architecture/02-svelte-frontend.html#library-mosaic","architecture/02-svelte-frontend.html#series-and-episode-navigation","architecture/02-svelte-frontend.html#downloaded-browse","architecture/02-svelte-frontend.html#safe-area-insets","architecture/02-svelte-frontend.html#native-video-store","architecture/02-svelte-frontend.html#logging","architecture/03-data-flow.html#data-flow","architecture/03-data-flow.html#repository-query-flow-cache-first","architecture/03-data-flow.html#search-flow-locally-indexed","architecture/03-data-flow.html#playback-initiation-flow","architecture/03-data-flow.html#playback-mode-transfer-flow","architecture/03-data-flow.html#queue-navigation-flow","architecture/03-data-flow.html#volume-control-flow","architecture/04-type-sync-and-threading.html#type-synchronization--thread-safety","architecture/04-type-sync-and-threading.html#playerstate-rust--typescript","architecture/04-type-sync-and-threading.html#mediaitem-serialization","architecture/04-type-sync-and-threading.html#tauri-v2-ipc-parameter-naming-convention","architecture/04-type-sync-and-threading.html#rust-backend-thread-safety","architecture/04-type-sync-and-threading.html#frontend-stores","architecture/05-platform-backends.html#platform-specific-player-backends","architecture/05-platform-backends.html#player-events-system","architecture/05-platform-backends.html#html5-video-adapter-webview-rendered-video","architecture/05-platform-backends.html#mpvbackend-linux","architecture/05-platform-backends.html#exoplayerbackend-android","architecture/05-platform-backends.html#audio-settings-on-exoplayer","architecture/05-platform-backends.html#the-equalizer-and-where-its-vocabulary-lives","architecture/05-platform-backends.html#background-audio-handoff-android","architecture/05-platform-backends.html#native-video-compositing-android","architecture/05-platform-backends.html#android-mediasession--remote-volume-control","architecture/05-platform-backends.html#android-album-art-caching","architecture/05-platform-backends.html#backend-initialization","architecture/06-downloads-and-offline.html#download-manager--offline-architecture","architecture/06-downloads-and-offline.html#overview","architecture/06-downloads-and-offline.html#download-worker","architecture/06-downloads-and-offline.html#smart-caching-engine","architecture/06-downloads-and-offline.html#one-storage-model-cache-entries-are-downloads","architecture/06-downloads-and-offline.html#offline-catalog-visibility","architecture/06-downloads-and-offline.html#download-commands","architecture/06-downloads-and-offline.html#offline-commands","architecture/06-downloads-and-offline.html#player-integration","architecture/06-downloads-and-offline.html#frontend-downloads-store","architecture/06-downloads-and-offline.html#download-ui-components","architecture/06-downloads-and-offline.html#database-schema","architecture/07-connectivity.html#connectivity--network-architecture","architecture/07-connectivity.html#http-client-with-retry-logic","architecture/07-connectivity.html#connectivity-monitor","architecture/07-connectivity.html#source-of-truth-repository-traffic","architecture/07-connectivity.html#time-window-debounce-offline--instant-recovery-online","architecture/07-connectivity.html#offline-only-recovery-probe","architecture/07-connectivity.html#network-resilience-architecture","architecture/08-database-design.html#offline-database-design","architecture/08-database-design.html#entity-relationship-diagram","architecture/08-database-design.html#table-definitions","architecture/08-database-design.html#servers","architecture/08-database-design.html#users","architecture/08-database-design.html#libraries","architecture/08-database-design.html#items","architecture/08-database-design.html#media_streams","architecture/08-database-design.html#user_data","architecture/08-database-design.html#downloads","architecture/08-database-design.html#sync_queue","architecture/08-database-design.html#thumbnails","architecture/08-database-design.html#playlists-for-localsynced-playlists","architecture/08-database-design.html#key-queries","architecture/08-database-design.html#get-items-for-offline-library-browsing","architecture/08-database-design.html#resume--continue-watching","architecture/08-database-design.html#offline-search","architecture/08-database-design.html#download-queue-management","architecture/08-database-design.html#sync-queue-processing","architecture/08-database-design.html#data-flow","architecture/08-database-design.html#online-mode","architecture/08-database-design.html#offline-mode","architecture/08-database-design.html#sync-on-reconnect","architecture/08-database-design.html#storage-estimates","architecture/08-database-design.html#rust-module-structure","architecture/09-security.html#security","architecture/09-security.html#authentication-token-storage","architecture/09-security.html#secure-storage-module","architecture/09-security.html#network-security","architecture/09-security.html#webview-content-security-policy","architecture/09-security.html#asset-protocol-scope","architecture/09-security.html#local-data-protection","architecture/09-security.html#path-confinement-and-input-binding","architecture/09-security.html#filesystem-path-confinement","architecture/09-security.html#query-and-url-construction","architecture/09-security.html#security-considerations","ux-flows.html#jellytau-ux-flows--screen-transitions","ux-flows.html#1-core-navigation-structure","ux-flows.html#11-navigation-system","ux-flows.html#12-account-menu","ux-flows.html#13-chrome-availability","ux-flows.html#14-known-deviations","ux-flows.html#2-initial-app-launch-flow","ux-flows.html#21-first-time-launch","ux-flows.html#22-subsequent-launches","ux-flows.html#3-audio-playback-flows","ux-flows.html#31-starting-audio-playback","ux-flows.html#32-miniplayer--full-player-transition","ux-flows.html#33-full-player--back-to-browsing","ux-flows.html#4-video-playback-flows","ux-flows.html#41-starting-video-playback","ux-flows.html#42-video-player-screen-ir-003-ir-004-ur-003","ux-flows.html#43-exiting-video-player","ux-flows.html#5-music-library-navigation-flows","ux-flows.html#51-music-category-landing-page","ux-flows.html#52-albums-view-flow","ux-flows.html#53-artist-navigation","ux-flows.html#5a-library-page-layouts","ux-flows.html#5a1-card-shape-follows-the-media-not-the-page","ux-flows.html#5a2-grid-vs-list","ux-flows.html#5a3-what-a-card-shows","ux-flows.html#5a4-known-deviations","ux-flows.html#5b-video-detail-page-composition","ux-flows.html#5b1-which-surface-renders","ux-flows.html#5b2-episode-focus-view--section-order","ux-flows.html#5b3-movie-detail--section-order","ux-flows.html#5b4-series-detail--section-order","ux-flows.html#5b5-home-card-interaction--tap-opens-long-press-plays","ux-flows.html#5c-favourites","ux-flows.html#5c1-the-heart-appears-wherever-an-item-does","ux-flows.html#5c2-three-ways-back-to-what-you-favourited","ux-flows.html#5c3-removing-a-favourite-removes-it-everywhere-at-once","ux-flows.html#5c4-offline","ux-flows.html#5c5-known-deviations","ux-flows.html#6-search-flow","ux-flows.html#61-scope-is-inherited-from-context","ux-flows.html#62-search-page-layout","ux-flows.html#63-result-group-order-is-user-configurable","ux-flows.html#64-known-deviations","ux-flows.html#7-download-flows","ux-flows.html#71-initiating-downloads","ux-flows.html#72-downloads--a-browsable-offline-library-not-a-flat-list","ux-flows.html#73-the-downloaded-browse-surface","ux-flows.html#74-transfers-activity-view","ux-flows.html#75-navigation--entry-points","ux-flows.html#77-known-deviations","ux-flows.html#8-settings--account-flows","ux-flows.html#81-settings-navigation","ux-flows.html#82-logout-flow","ux-flows.html#9-background--lock-screen-behavior","ux-flows.html#91-audio-playback-in-background-android","ux-flows.html#92-video-playback-in-background-android--pip--background-audio","ux-flows.html#10-error-states--edge-cases","ux-flows.html#101-network-loss-during-streaming","ux-flows.html#102-server-unreachable","ux-flows.html#103-download-failed","ux-flows.html#11-platform-specific-ux-patterns","ux-flows.html#111-android-specific","ux-flows.html#112-linux-desktop-specific","ux-flows.html#12-ux-principles-summary","ux-flows.html#121-core-principles","ux-flows.html#122-animation--transitions","ux-flows.html#123-touch-targets-mobile","ux-flows.html#13-future-ux-enhancements","ux-flows.html#131-planned-features","ux-flows.html#132-accessibility-enhancements","specs/index.html#specs-index","specs/index.html#what-lives-here","specs/index.html#partially-implemented","specs/index.html#not-started","specs/index.html#design-authority","specs/index.html#where-the-shipped-specs-went","specs/SPEC-TEMPLATE.html#spec","specs/SPEC-TEMPLATE.html#summary","specs/SPEC-TEMPLATE.html#motivation","specs/SPEC-TEMPLATE.html#layer-assignment","specs/SPEC-TEMPLATE.html#design","specs/SPEC-TEMPLATE.html#out-of-scope","specs/SPEC-TEMPLATE.html#acceptance-criteria","specs/SPEC-TEMPLATE.html#testing","specs/SPEC-TEMPLATE.html#traces","specs/SPEC-TEMPLATE.html#notes-for-the-implementer","specs/SPEC-REVIEW-CHECKLIST.html#spec-review-checklist","specs/SPEC-REVIEW-CHECKLIST.html#boundary-the-one-that-bites","specs/SPEC-REVIEW-CHECKLIST.html#ipc-contract","specs/SPEC-REVIEW-CHECKLIST.html#requirements--traceability","specs/SPEC-REVIEW-CHECKLIST.html#lifecycle","specs/SPEC-REVIEW-CHECKLIST.html#conflicts--hygiene","specs/playback-backend-unification.html#spec-playback-backend-unification--findings-and-strategy","specs/playback-backend-unification.html#summary","specs/playback-backend-unification.html#motivation","specs/playback-backend-unification.html#findings","specs/playback-backend-unification.html#1-the-current-architecture-is-not-what-the-docs-describe","specs/playback-backend-unification.html#2-native-video-cannot-be-composited-with-a-tauri-webview","specs/playback-backend-unification.html#3-mpv-would-regress-streaming-quality","specs/playback-backend-unification.html#4-crossfade-is-architecturally-blocked-on-mpv","specs/playback-backend-unification.html#5-engine-comparison-summary","specs/playback-backend-unification.html#6-two-further-options-ruled-out","specs/playback-backend-unification.html#7-if-libmpv-is-ever-revisited-on-android","specs/playback-backend-unification.html#decision","specs/playback-backend-unification.html#corrections-to-existing-docs","specs/playback-backend-unification.html#layer-assignment","specs/playback-backend-unification.html#out-of-scope","specs/playback-backend-unification.html#acceptance-criteria","specs/playback-backend-unification.html#testing","specs/playback-backend-unification.html#traces","specs/playback-backend-unification.html#notes-for-the-implementer","specs/linux-native-video-spike.html#spec-linux-native-video--bounded-compositing-spike","specs/linux-native-video-spike.html#summary","specs/linux-native-video-spike.html#motivation","specs/linux-native-video-spike.html#the-blocker-a-green-spike-does-not-clear","specs/linux-native-video-spike.html#layer-assignment","specs/linux-native-video-spike.html#design","specs/linux-native-video-spike.html#what-gets-built","specs/linux-native-video-spike.html#bindings","specs/linux-native-video-spike.html#ipc","specs/linux-native-video-spike.html#gates","specs/linux-native-video-spike.html#time-box","specs/linux-native-video-spike.html#result-2026-08-21","specs/linux-native-video-spike.html#three-traps-each-of-which-cost-a-debugging-cycle","specs/linux-native-video-spike.html#hardware-decode-through-the-render-api","specs/linux-native-video-spike.html#what-is-still-open","specs/linux-native-video-spike.html#out-of-scope","specs/linux-native-video-spike.html#why-windows-is-unaffected-and-cheaper","specs/linux-native-video-spike.html#does-this-add-a-backend","specs/linux-native-video-spike.html#acceptance-criteria","specs/linux-native-video-spike.html#testing","specs/linux-native-video-spike.html#traces","specs/linux-native-video-spike.html#notes-for-the-implementer","specs/backend-owned-stream-selection.html#spec-backend-owned-stream-selection","specs/backend-owned-stream-selection.html#summary","specs/backend-owned-stream-selection.html#motivation","specs/backend-owned-stream-selection.html#the-prior-question","specs/backend-owned-stream-selection.html#layer-assignment","specs/backend-owned-stream-selection.html#the-line","specs/backend-owned-stream-selection.html#design","specs/backend-owned-stream-selection.html#the-contract","specs/backend-owned-stream-selection.html#re-negotiation","specs/backend-owned-stream-selection.html#phases","specs/backend-owned-stream-selection.html#out-of-scope","specs/backend-owned-stream-selection.html#acceptance-criteria","specs/backend-owned-stream-selection.html#testing","specs/backend-owned-stream-selection.html#traces","specs/backend-owned-stream-selection.html#notes-for-the-implementer","specs/player-facade-enforcement.html#spec-enforce-the-unified-player-boundary","specs/player-facade-enforcement.html#summary","specs/player-facade-enforcement.html#motivation","specs/player-facade-enforcement.html#layer-assignment","specs/player-facade-enforcement.html#design","specs/player-facade-enforcement.html#1-narrow-the-principle-to-what-it-actually-means","specs/player-facade-enforcement.html#2-fill-the-facade-gaps-group-b","specs/player-facade-enforcement.html#3-migrate-group-a","specs/player-facade-enforcement.html#4-add-the-lint-rule-the-part-that-makes-it-stick","specs/player-facade-enforcement.html#out-of-scope","specs/player-facade-enforcement.html#acceptance-criteria","specs/player-facade-enforcement.html#testing","specs/player-facade-enforcement.html#traces","specs/player-facade-enforcement.html#notes-for-the-implementer","specs/windows-native-audio-backend.html#spec-windows-native-audio-backend","specs/windows-native-audio-backend.html#summary","specs/windows-native-audio-backend.html#motivation","specs/windows-native-audio-backend.html#layer-assignment","specs/windows-native-audio-backend.html#design","specs/windows-native-audio-backend.html#engine-choice","specs/windows-native-audio-backend.html#structure","specs/windows-native-audio-backend.html#build","specs/windows-native-audio-backend.html#verified-build-mechanics","specs/windows-native-audio-backend.html#size-and-shipping","specs/windows-native-audio-backend.html#out-of-scope","specs/windows-native-audio-backend.html#acceptance-criteria","specs/windows-native-audio-backend.html#testing","specs/windows-native-audio-backend.html#traces","specs/windows-native-audio-backend.html#notes-for-the-implementer","specs/libmpv2-migration.html#spec-migrate-to-libmpv2-and-declare-the-project-licence","specs/libmpv2-migration.html#summary","specs/libmpv2-migration.html#motivation","specs/libmpv2-migration.html#the-dependency-is-dead","specs/libmpv2-migration.html#the-project-has-no-licence","specs/libmpv2-migration.html#design","specs/libmpv2-migration.html#part-1--licence","specs/libmpv2-migration.html#part-2--libmpv--libmpv2","specs/libmpv2-migration.html#layer-assignment","specs/libmpv2-migration.html#out-of-scope","specs/libmpv2-migration.html#acceptance-criteria","specs/libmpv2-migration.html#testing","specs/libmpv2-migration.html#traces","specs/libmpv2-migration.html#notes-for-the-implementer","specs/read-through-media-cache.html#spec-two-path-media--selectable-playback-bitrate-independent-whole-file-download","specs/read-through-media-cache.html#summary","specs/read-through-media-cache.html#motivation","specs/read-through-media-cache.html#layer-assignment","specs/read-through-media-cache.html#design","specs/read-through-media-cache.html#dr-121--bitrate-selection-in-the-player","specs/read-through-media-cache.html#dr-122--the-playback-path-is-ephemeral","specs/read-through-media-cache.html#dr-123--the-download-path-is-independent","specs/read-through-media-cache.html#dr-124--keep-playback-bytes-only-when-they--are--the-download","specs/read-through-media-cache.html#dr-125--promotion-rendition-and-invalidation","specs/read-through-media-cache.html#gating","specs/read-through-media-cache.html#out-of-scope","specs/read-through-media-cache.html#acceptance-criteria","specs/read-through-media-cache.html#testing","specs/read-through-media-cache.html#traces","specs/read-through-media-cache.html#notes-for-the-implementer","specs/scoped-search.html#spec-context-scoped-search-with-filter-chips-and-configurable-group-order","specs/scoped-search.html#summary","specs/scoped-search.html#motivation","specs/scoped-search.html#background-what-already-exists","specs/scoped-search.html#design","specs/scoped-search.html#scope-model","specs/scoped-search.html#route--scope-resolution-dr-063","specs/scoped-search.html#scope-is-a-starting-point-not-a-lock-dr-064","specs/scoped-search.html#threading-scope-through-the-store-dr-065","specs/scoped-search.html#group-order-dr-066-dr-067","specs/scoped-search.html#out-of-scope","specs/scoped-search.html#acceptance-criteria","specs/scoped-search.html#testing","specs/scoped-search.html#notes-for-the-implementer","specs/scoped-search-boundary.html#spec-move-search-scope-taxonomy-behind-the-rust-boundary","specs/scoped-search-boundary.html#why-this-spec-exists","specs/scoped-search-boundary.html#what-is--not--a-leak-leave-it-alone","specs/scoped-search-boundary.html#the-boundary-rule-after-this-change","specs/scoped-search-boundary.html#design","specs/scoped-search-boundary.html#rust-owns-scope--item-types-query-side","specs/scoped-search-boundary.html#rust-owns-result-bucketing-result-side","specs/scoped-search-boundary.html#-the-search-event-wrinkle--both-payloads-must-change","specs/scoped-search-boundary.html#what-the-frontend-keeps-all-pure-presentation","specs/scoped-search-boundary.html#frontend-call-site-changes","specs/scoped-search-boundary.html#out-of-scope","specs/scoped-search-boundary.html#acceptance-criteria","specs/scoped-search-boundary.html#testing","specs/scoped-search-boundary.html#traces","specs/scoped-search-boundary.html#notes-for-the-implementer","specs/scoped-search-boundary-implementation.html#spec-land-the-scoped-search-boundary-fix-implementation","specs/scoped-search-boundary-implementation.html#summary","specs/scoped-search-boundary-implementation.html#motivation","specs/scoped-search-boundary-implementation.html#layer-assignment","specs/scoped-search-boundary-implementation.html#design","specs/scoped-search-boundary-implementation.html#staging-land-it-in-two-reviewable-pieces","specs/scoped-search-boundary-implementation.html#stage-1--delivered-july-2026","specs/scoped-search-boundary-implementation.html#-the-search-event-dual-payload-stage-2","specs/scoped-search-boundary-implementation.html#note-on-searchoptionsscope-and-specta","specs/scoped-search-boundary-implementation.html#out-of-scope","specs/scoped-search-boundary-implementation.html#acceptance-criteria","specs/scoped-search-boundary-implementation.html#testing","specs/scoped-search-boundary-implementation.html#traces","specs/scoped-search-boundary-implementation.html#notes-for-the-implementer","specs/frontend-domain-model.html#spec-remove-jellyfin-specific-models-from-the-frontend","specs/frontend-domain-model.html#summary","specs/frontend-domain-model.html#motivation","specs/frontend-domain-model.html#layer-assignment","specs/frontend-domain-model.html#design","specs/frontend-domain-model.html#single-canonical-model-one-location-isolated-mappings","specs/frontend-domain-model.html#the-domain-model-rust","specs/frontend-domain-model.html#-the-search-event--dual-payload-rule-applies-again","specs/frontend-domain-model.html#frontend-after","specs/frontend-domain-model.html#phased-migration","specs/frontend-domain-model.html#out-of-scope","specs/frontend-domain-model.html#acceptance-criteria","specs/frontend-domain-model.html#testing","specs/frontend-domain-model.html#traces","specs/frontend-domain-model.html#notes-for-the-implementer","specs/build-provenance.html#spec-build-provenance-git-describe--build-profile","specs/build-provenance.html#summary","specs/build-provenance.html#motivation","specs/build-provenance.html#what-this-deliberately-does--not--do","specs/build-provenance.html#layer-assignment","specs/build-provenance.html#design","specs/build-provenance.html#buildrs","specs/build-provenance.html#the-provenance-type","specs/build-provenance.html#command","specs/build-provenance.html#settings--about","specs/build-provenance.html#removing-one-version-file","specs/build-provenance.html#out-of-scope","specs/build-provenance.html#acceptance-criteria","specs/build-provenance.html#testing","specs/build-provenance.html#traces","specs/build-provenance.html#notes-for-the-implementer","build/build-release.html#build--release-workflow","build/build-release.html#overview","build/build-release.html#workflow-triggers","build/build-release.html#automatic-trigger","build/build-release.html#manual-trigger","build/build-release.html#version-tagging","build/build-release.html#format","build/build-release.html#creating-a-release","build/build-release.html#release-status","build/build-release.html#workflow-steps","build/build-release.html#1-test-phase","build/build-release.html#2-build-linux-phase","build/build-release.html#3-build-android-phase","build/build-release.html#4-create-release-phase","build/build-release.html#artifacts","build/build-release.html#linux-artifacts","build/build-release.html#android-artifacts","build/build-release.html#release-notes","build/build-release.html#build-matrix","build/build-release.html#troubleshooting","build/build-release.html#build-fails-during-test-phase","build/build-release.html#linux-build-fails","build/build-release.html#android-build-fails","build/build-release.html#release-not-created","build/build-release.html#github-release-vs-gitea","build/build-release.html#customization","build/build-release.html#change-release-notes-template","build/build-release.html#add-new-platforms","build/build-release.html#change-build-targets","build/build-release.html#monitoring","build/build-release.html#check-status","build/build-release.html#notifications","build/build-release.html#performance","build/build-release.html#build-times-approximate","build/build-release.html#caching","build/build-release.html#security","build/build-release.html#secrets","build/build-release.html#verification","build/build-release.html#best-practices","build/build-release.html#versioning","build/build-release.html#testing-before-release","build/build-release.html#documentation","build/build-release.html#example-release-workflow","build/build-release.html#references","release-checklist.html#release-checklist","release-checklist.html#pre-release-1-2-days-before","release-checklist.html#update-version-day-before","release-checklist.html#final-check-before-release","release-checklist.html#release-tag--push","release-checklist.html#during-release-while-workflow-runs","release-checklist.html#after-release-workflow-complete","release-checklist.html#rollback-if-issues-found","release-checklist.html#version-examples","release-checklist.html#major-release","release-checklist.html#minor-release","release-checklist.html#patch-release","release-checklist.html#pre-releases","release-checklist.html#file-locations","release-checklist.html#troubleshooting","release-checklist.html#tests-fail-before-release","release-checklist.html#build-fails-in-ci","release-checklist.html#release-already-exists","release-checklist.html#artifacts-missing","release-checklist.html#performance-tips","release-checklist.html#template-release-notes","release-checklist.html#quick-commands","build/build-desktop-packages.html#desktop-packaging-linux-arch-windows","build/build-desktop-packages.html#one-builder-image-shared-with-ci","build/build-desktop-packages.html#linux-deb--rpm","build/build-desktop-packages.html#arch-linux-pacman-package","build/build-desktop-packages.html#windows-nsis-installer-cross-compiled-from-linux","build/build-windows.html#windows-build","build/build-windows.html#how-playback-works-on-windows","build/build-windows.html#cross-compiling-from-linux-msvc--cargo-xwin","build/build-windows.html#caveats","build/build-windows.html#outstanding-for-a-first-class-windows-release","defect-windows.html#defect-windows--which-bugs-were-present-when","defect-windows.html#present-since-the-first-release","defect-windows.html#why-they-took-so-long-to-surface","defect-windows.html#introduced-by-a-feature-fixed-later","defect-windows.html#short-windows-one-release-or-less","defect-windows.html#fixed-twice--never-actually-broken","defect-windows.html#recurring-shapes","defect-windows.html#reproducing-this","build/docker.html#docker--cicd-setup-for-jellytau","build/docker.html#overview","build/docker.html#quick-start","build/docker.html#docker-usage","build/docker.html#prerequisites","build/docker.html#building-the-docker-image","build/docker.html#using-docker-compose","build/docker.html#extracting-build-artifacts","build/docker.html#gitea-act-cicd-pipeline","build/docker.html#workflow-triggers","build/docker.html#setting-up-the-builder-image","build/docker.html#setting-up-gitea-act","build/docker.html#customizing-the-workflow","build/docker.html#environment-setup-in-ci","build/docker.html#secret-variables","build/docker.html#troubleshooting","build/docker.html#out-of-memory-during-build","build/docker.html#android-sdk-download-timeout","build/docker.html#rust-compilation-errors","build/docker.html#cache-issues","build/docker.html#performance-tips","build/docker.html#security-considerations","build/docker.html#next-steps","build/docker.html#references","build/build-builder-image.html#building-and-pushing-the-jellytau-builder-image","build/build-builder-image.html#prerequisites","build/build-builder-image.html#building-the-builder-image","build/build-builder-image.html#step-1-build-the-image-locally","build/build-builder-image.html#step-2-tag-for-your-registry","build/build-builder-image.html#step-3-login-to-your-registry","build/build-builder-image.html#step-4-push-to-registry","build/build-builder-image.html#complete-one-liner","build/build-builder-image.html#verifying-the-build","build/build-builder-image.html#using-in-cicd","build/build-builder-image.html#updating-the-builder-image","build/build-builder-image.html#image-contents","build/build-builder-image.html#build-time","build/build-builder-image.html#storage","build/build-builder-image.html#troubleshooting","build/build-builder-image.html#image-not-found-in-ci","build/build-builder-image.html#build-fails-with-command-not-found","build/build-builder-image.html#registry-authentication-in-ci","build/build-builder-image.html#references","api-redirect.html#rust-api-reference"],"index":{"documentStore":{"docInfo":{"0":{"body":52,"breadcrumbs":3,"title":2},"1":{"body":25,"breadcrumbs":4,"title":2},"10":{"body":4303,"breadcrumbs":6,"title":4},"100":{"body":16,"breadcrumbs":4,"title":2},"1000":{"body":47,"breadcrumbs":3,"title":1},"1001":{"body":50,"breadcrumbs":4,"title":2},"1002":{"body":7,"breadcrumbs":5,"title":3},"1003":{"body":23,"breadcrumbs":3,"title":1},"1004":{"body":0,"breadcrumbs":4,"title":2},"1005":{"body":25,"breadcrumbs":4,"title":2},"1006":{"body":21,"breadcrumbs":4,"title":2},"1007":{"body":0,"breadcrumbs":4,"title":2},"1008":{"body":28,"breadcrumbs":3,"title":1},"1009":{"body":30,"breadcrumbs":4,"title":2},"101":{"body":10,"breadcrumbs":4,"title":2},"1010":{"body":27,"breadcrumbs":4,"title":2},"1011":{"body":0,"breadcrumbs":4,"title":2},"1012":{"body":18,"breadcrumbs":5,"title":3},"1013":{"body":18,"breadcrumbs":6,"title":4},"1014":{"body":27,"breadcrumbs":6,"title":4},"1015":{"body":20,"breadcrumbs":6,"title":4},"1016":{"body":0,"breadcrumbs":3,"title":1},"1017":{"body":73,"breadcrumbs":4,"title":2},"1018":{"body":44,"breadcrumbs":4,"title":2},"1019":{"body":17,"breadcrumbs":4,"title":2},"102":{"body":10,"breadcrumbs":4,"title":2},"1020":{"body":15,"breadcrumbs":4,"title":2},"1021":{"body":0,"breadcrumbs":3,"title":1},"1022":{"body":22,"breadcrumbs":7,"title":5},"1023":{"body":16,"breadcrumbs":5,"title":3},"1024":{"body":18,"breadcrumbs":5,"title":3},"1025":{"body":14,"breadcrumbs":4,"title":2},"1026":{"body":29,"breadcrumbs":6,"title":4},"1027":{"body":0,"breadcrumbs":3,"title":1},"1028":{"body":24,"breadcrumbs":6,"title":4},"1029":{"body":28,"breadcrumbs":5,"title":3},"103":{"body":10,"breadcrumbs":4,"title":2},"1030":{"body":21,"breadcrumbs":5,"title":3},"1031":{"body":0,"breadcrumbs":3,"title":1},"1032":{"body":14,"breadcrumbs":4,"title":2},"1033":{"body":9,"breadcrumbs":3,"title":1},"1034":{"body":0,"breadcrumbs":3,"title":1},"1035":{"body":19,"breadcrumbs":5,"title":3},"1036":{"body":10,"breadcrumbs":3,"title":1},"1037":{"body":0,"breadcrumbs":3,"title":1},"1038":{"body":9,"breadcrumbs":3,"title":1},"1039":{"body":14,"breadcrumbs":3,"title":1},"104":{"body":17,"breadcrumbs":4,"title":2},"1040":{"body":0,"breadcrumbs":4,"title":2},"1041":{"body":16,"breadcrumbs":3,"title":1},"1042":{"body":26,"breadcrumbs":5,"title":3},"1043":{"body":14,"breadcrumbs":3,"title":1},"1044":{"body":80,"breadcrumbs":5,"title":3},"1045":{"body":18,"breadcrumbs":3,"title":1},"1046":{"body":5,"breadcrumbs":4,"title":2},"1047":{"body":29,"breadcrumbs":8,"title":6},"1048":{"body":87,"breadcrumbs":6,"title":4},"1049":{"body":46,"breadcrumbs":6,"title":4},"105":{"body":10,"breadcrumbs":4,"title":2},"1050":{"body":64,"breadcrumbs":5,"title":3},"1051":{"body":16,"breadcrumbs":6,"title":4},"1052":{"body":144,"breadcrumbs":5,"title":3},"1053":{"body":56,"breadcrumbs":5,"title":3},"1054":{"body":0,"breadcrumbs":4,"title":2},"1055":{"body":13,"breadcrumbs":4,"title":2},"1056":{"body":9,"breadcrumbs":4,"title":2},"1057":{"body":9,"breadcrumbs":4,"title":2},"1058":{"body":16,"breadcrumbs":4,"title":2},"1059":{"body":21,"breadcrumbs":4,"title":2},"106":{"body":15,"breadcrumbs":4,"title":2},"1060":{"body":0,"breadcrumbs":3,"title":1},"1061":{"body":16,"breadcrumbs":6,"title":4},"1062":{"body":25,"breadcrumbs":5,"title":3},"1063":{"body":13,"breadcrumbs":5,"title":3},"1064":{"body":12,"breadcrumbs":4,"title":2},"1065":{"body":31,"breadcrumbs":4,"title":2},"1066":{"body":55,"breadcrumbs":5,"title":3},"1067":{"body":88,"breadcrumbs":4,"title":2},"1068":{"body":16,"breadcrumbs":7,"title":5},"1069":{"body":143,"breadcrumbs":7,"title":5},"107":{"body":39,"breadcrumbs":4,"title":2},"1070":{"body":53,"breadcrumbs":5,"title":3},"1071":{"body":72,"breadcrumbs":6,"title":4},"1072":{"body":83,"breadcrumbs":8,"title":6},"1073":{"body":30,"breadcrumbs":4,"title":2},"1074":{"body":63,"breadcrumbs":5,"title":3},"1075":{"body":149,"breadcrumbs":8,"title":6},"1076":{"body":37,"breadcrumbs":3,"title":1},"1077":{"body":25,"breadcrumbs":7,"title":5},"1078":{"body":88,"breadcrumbs":6,"title":4},"1079":{"body":205,"breadcrumbs":5,"title":3},"108":{"body":119,"breadcrumbs":4,"title":2},"1080":{"body":141,"breadcrumbs":5,"title":3},"1081":{"body":240,"breadcrumbs":6,"title":4},"1082":{"body":83,"breadcrumbs":7,"title":5},"1083":{"body":76,"breadcrumbs":7,"title":5},"1084":{"body":97,"breadcrumbs":4,"title":2},"1085":{"body":55,"breadcrumbs":3,"title":1},"1086":{"body":12,"breadcrumbs":5,"title":4},"1087":{"body":32,"breadcrumbs":2,"title":1},"1088":{"body":44,"breadcrumbs":3,"title":2},"1089":{"body":0,"breadcrumbs":3,"title":2},"109":{"body":33,"breadcrumbs":4,"title":2},"1090":{"body":17,"breadcrumbs":2,"title":1},"1091":{"body":23,"breadcrumbs":4,"title":3},"1092":{"body":78,"breadcrumbs":4,"title":3},"1093":{"body":18,"breadcrumbs":4,"title":3},"1094":{"body":46,"breadcrumbs":5,"title":4},"1095":{"body":15,"breadcrumbs":3,"title":2},"1096":{"body":37,"breadcrumbs":5,"title":4},"1097":{"body":56,"breadcrumbs":5,"title":4},"1098":{"body":73,"breadcrumbs":3,"title":2},"1099":{"body":0,"breadcrumbs":4,"title":3},"11":{"body":221,"breadcrumbs":4,"title":2},"110":{"body":15,"breadcrumbs":4,"title":2},"1100":{"body":20,"breadcrumbs":3,"title":2},"1101":{"body":0,"breadcrumbs":2,"title":1},"1102":{"body":25,"breadcrumbs":5,"title":4},"1103":{"body":13,"breadcrumbs":5,"title":4},"1104":{"body":18,"breadcrumbs":4,"title":3},"1105":{"body":21,"breadcrumbs":3,"title":2},"1106":{"body":37,"breadcrumbs":3,"title":2},"1107":{"body":33,"breadcrumbs":3,"title":2},"1108":{"body":27,"breadcrumbs":3,"title":2},"1109":{"body":13,"breadcrumbs":2,"title":1},"111":{"body":33,"breadcrumbs":4,"title":2},"1110":{"body":14,"breadcrumbs":7,"title":5},"1111":{"body":14,"breadcrumbs":3,"title":1},"1112":{"body":0,"breadcrumbs":5,"title":3},"1113":{"body":26,"breadcrumbs":7,"title":5},"1114":{"body":11,"breadcrumbs":6,"title":4},"1115":{"body":5,"breadcrumbs":6,"title":4},"1116":{"body":4,"breadcrumbs":6,"title":4},"1117":{"body":17,"breadcrumbs":5,"title":3},"1118":{"body":28,"breadcrumbs":4,"title":2},"1119":{"body":25,"breadcrumbs":4,"title":2},"112":{"body":21,"breadcrumbs":4,"title":2},"1120":{"body":43,"breadcrumbs":5,"title":3},"1121":{"body":61,"breadcrumbs":4,"title":2},"1122":{"body":17,"breadcrumbs":4,"title":2},"1123":{"body":10,"breadcrumbs":3,"title":1},"1124":{"body":0,"breadcrumbs":3,"title":1},"1125":{"body":22,"breadcrumbs":5,"title":3},"1126":{"body":15,"breadcrumbs":6,"title":4},"1127":{"body":26,"breadcrumbs":5,"title":3},"1128":{"body":8,"breadcrumbs":3,"title":1},"1129":{"body":12,"breadcrumbs":7,"title":3},"113":{"body":9,"breadcrumbs":4,"title":2},"114":{"body":9,"breadcrumbs":4,"title":2},"115":{"body":9,"breadcrumbs":4,"title":2},"116":{"body":137,"breadcrumbs":4,"title":2},"117":{"body":9,"breadcrumbs":4,"title":2},"118":{"body":15,"breadcrumbs":4,"title":2},"119":{"body":15,"breadcrumbs":4,"title":2},"12":{"body":0,"breadcrumbs":5,"title":3},"120":{"body":49,"breadcrumbs":4,"title":2},"121":{"body":15,"breadcrumbs":4,"title":2},"122":{"body":17,"breadcrumbs":4,"title":2},"123":{"body":15,"breadcrumbs":4,"title":2},"124":{"body":145,"breadcrumbs":4,"title":2},"125":{"body":131,"breadcrumbs":4,"title":2},"126":{"body":15,"breadcrumbs":4,"title":2},"127":{"body":99,"breadcrumbs":4,"title":2},"128":{"body":9,"breadcrumbs":4,"title":2},"129":{"body":21,"breadcrumbs":4,"title":2},"13":{"body":680,"breadcrumbs":9,"title":7},"130":{"body":15,"breadcrumbs":4,"title":2},"131":{"body":27,"breadcrumbs":4,"title":2},"132":{"body":10,"breadcrumbs":4,"title":2},"133":{"body":17,"breadcrumbs":4,"title":2},"134":{"body":15,"breadcrumbs":4,"title":2},"135":{"body":71,"breadcrumbs":4,"title":2},"136":{"body":9,"breadcrumbs":4,"title":2},"137":{"body":9,"breadcrumbs":4,"title":2},"138":{"body":66,"breadcrumbs":4,"title":2},"139":{"body":27,"breadcrumbs":4,"title":2},"14":{"body":191,"breadcrumbs":6,"title":4},"140":{"body":15,"breadcrumbs":4,"title":2},"141":{"body":15,"breadcrumbs":4,"title":2},"142":{"body":56,"breadcrumbs":4,"title":2},"143":{"body":47,"breadcrumbs":4,"title":2},"144":{"body":39,"breadcrumbs":4,"title":2},"145":{"body":9,"breadcrumbs":4,"title":2},"146":{"body":63,"breadcrumbs":4,"title":2},"147":{"body":49,"breadcrumbs":4,"title":2},"148":{"body":15,"breadcrumbs":4,"title":2},"149":{"body":38,"breadcrumbs":4,"title":2},"15":{"body":324,"breadcrumbs":9,"title":7},"150":{"body":45,"breadcrumbs":4,"title":2},"151":{"body":59,"breadcrumbs":4,"title":2},"152":{"body":38,"breadcrumbs":4,"title":2},"153":{"body":39,"breadcrumbs":4,"title":2},"154":{"body":31,"breadcrumbs":4,"title":2},"155":{"body":17,"breadcrumbs":4,"title":2},"156":{"body":114,"breadcrumbs":4,"title":2},"157":{"body":84,"breadcrumbs":4,"title":2},"158":{"body":51,"breadcrumbs":4,"title":2},"159":{"body":33,"breadcrumbs":4,"title":2},"16":{"body":164,"breadcrumbs":6,"title":4},"160":{"body":93,"breadcrumbs":4,"title":2},"161":{"body":140,"breadcrumbs":4,"title":2},"162":{"body":55,"breadcrumbs":4,"title":2},"163":{"body":17,"breadcrumbs":4,"title":2},"164":{"body":44,"breadcrumbs":4,"title":2},"165":{"body":24,"breadcrumbs":4,"title":2},"166":{"body":65,"breadcrumbs":4,"title":2},"167":{"body":49,"breadcrumbs":4,"title":2},"168":{"body":115,"breadcrumbs":4,"title":2},"169":{"body":86,"breadcrumbs":4,"title":2},"17":{"body":3,"breadcrumbs":5,"title":3},"170":{"body":15,"breadcrumbs":4,"title":2},"171":{"body":16,"breadcrumbs":4,"title":2},"172":{"body":38,"breadcrumbs":4,"title":2},"173":{"body":24,"breadcrumbs":4,"title":2},"174":{"body":131,"breadcrumbs":4,"title":2},"175":{"body":24,"breadcrumbs":4,"title":2},"176":{"body":21,"breadcrumbs":4,"title":2},"177":{"body":69,"breadcrumbs":4,"title":2},"178":{"body":77,"breadcrumbs":4,"title":2},"179":{"body":33,"breadcrumbs":4,"title":2},"18":{"body":27,"breadcrumbs":3,"title":1},"180":{"body":10,"breadcrumbs":4,"title":2},"181":{"body":24,"breadcrumbs":4,"title":2},"182":{"body":27,"breadcrumbs":4,"title":2},"183":{"body":9,"breadcrumbs":4,"title":2},"184":{"body":15,"breadcrumbs":4,"title":2},"185":{"body":10,"breadcrumbs":4,"title":2},"186":{"body":51,"breadcrumbs":4,"title":2},"187":{"body":38,"breadcrumbs":4,"title":2},"188":{"body":15,"breadcrumbs":4,"title":2},"189":{"body":9,"breadcrumbs":4,"title":2},"19":{"body":0,"breadcrumbs":4,"title":2},"190":{"body":42,"breadcrumbs":4,"title":2},"191":{"body":24,"breadcrumbs":4,"title":2},"192":{"body":9,"breadcrumbs":4,"title":2},"193":{"body":268,"breadcrumbs":4,"title":2},"194":{"body":17,"breadcrumbs":4,"title":2},"195":{"body":43,"breadcrumbs":4,"title":2},"196":{"body":22,"breadcrumbs":4,"title":2},"197":{"body":17,"breadcrumbs":4,"title":2},"198":{"body":66,"breadcrumbs":4,"title":2},"199":{"body":120,"breadcrumbs":4,"title":2},"2":{"body":1865,"breadcrumbs":5,"title":3},"20":{"body":152,"breadcrumbs":5,"title":3},"200":{"body":51,"breadcrumbs":4,"title":2},"201":{"body":70,"breadcrumbs":4,"title":2},"202":{"body":165,"breadcrumbs":4,"title":2},"203":{"body":63,"breadcrumbs":4,"title":2},"204":{"body":97,"breadcrumbs":4,"title":2},"205":{"body":114,"breadcrumbs":4,"title":2},"206":{"body":45,"breadcrumbs":4,"title":2},"207":{"body":74,"breadcrumbs":4,"title":2},"208":{"body":27,"breadcrumbs":4,"title":2},"209":{"body":9,"breadcrumbs":4,"title":2},"21":{"body":50,"breadcrumbs":5,"title":3},"210":{"body":9,"breadcrumbs":4,"title":2},"211":{"body":9,"breadcrumbs":4,"title":2},"212":{"body":15,"breadcrumbs":4,"title":2},"213":{"body":34,"breadcrumbs":4,"title":2},"214":{"body":17,"breadcrumbs":4,"title":2},"215":{"body":9,"breadcrumbs":4,"title":2},"216":{"body":21,"breadcrumbs":4,"title":2},"217":{"body":54,"breadcrumbs":4,"title":2},"218":{"body":24,"breadcrumbs":4,"title":2},"219":{"body":59,"breadcrumbs":4,"title":2},"22":{"body":378,"breadcrumbs":5,"title":3},"220":{"body":17,"breadcrumbs":4,"title":2},"221":{"body":31,"breadcrumbs":4,"title":2},"222":{"body":9,"breadcrumbs":4,"title":2},"223":{"body":11,"breadcrumbs":4,"title":2},"224":{"body":17,"breadcrumbs":4,"title":2},"225":{"body":313,"breadcrumbs":4,"title":2},"226":{"body":59,"breadcrumbs":4,"title":2},"227":{"body":73,"breadcrumbs":4,"title":2},"228":{"body":66,"breadcrumbs":4,"title":2},"229":{"body":10,"breadcrumbs":4,"title":2},"23":{"body":70,"breadcrumbs":6,"title":4},"230":{"body":9,"breadcrumbs":4,"title":2},"231":{"body":10,"breadcrumbs":4,"title":2},"232":{"body":34,"breadcrumbs":4,"title":2},"233":{"body":114,"breadcrumbs":4,"title":2},"234":{"body":17,"breadcrumbs":4,"title":2},"235":{"body":11,"breadcrumbs":4,"title":2},"236":{"body":19,"breadcrumbs":4,"title":2},"237":{"body":38,"breadcrumbs":4,"title":2},"238":{"body":37,"breadcrumbs":4,"title":2},"239":{"body":10,"breadcrumbs":4,"title":2},"24":{"body":0,"breadcrumbs":4,"title":2},"240":{"body":17,"breadcrumbs":4,"title":2},"241":{"body":10,"breadcrumbs":4,"title":2},"242":{"body":24,"breadcrumbs":4,"title":2},"243":{"body":17,"breadcrumbs":4,"title":2},"244":{"body":17,"breadcrumbs":4,"title":2},"245":{"body":10,"breadcrumbs":4,"title":2},"246":{"body":54,"breadcrumbs":4,"title":2},"247":{"body":16,"breadcrumbs":4,"title":2},"248":{"body":17,"breadcrumbs":4,"title":2},"249":{"body":17,"breadcrumbs":4,"title":2},"25":{"body":22,"breadcrumbs":4,"title":2},"250":{"body":17,"breadcrumbs":4,"title":2},"251":{"body":37,"breadcrumbs":4,"title":2},"252":{"body":30,"breadcrumbs":4,"title":2},"253":{"body":30,"breadcrumbs":4,"title":2},"254":{"body":17,"breadcrumbs":4,"title":2},"255":{"body":10,"breadcrumbs":4,"title":2},"256":{"body":10,"breadcrumbs":4,"title":2},"257":{"body":10,"breadcrumbs":4,"title":2},"258":{"body":10,"breadcrumbs":4,"title":2},"259":{"body":42,"breadcrumbs":4,"title":2},"26":{"body":15,"breadcrumbs":4,"title":2},"260":{"body":23,"breadcrumbs":4,"title":2},"261":{"body":23,"breadcrumbs":4,"title":2},"262":{"body":16,"breadcrumbs":4,"title":2},"263":{"body":29,"breadcrumbs":4,"title":2},"264":{"body":23,"breadcrumbs":4,"title":2},"265":{"body":10,"breadcrumbs":4,"title":2},"266":{"body":22,"breadcrumbs":4,"title":2},"267":{"body":23,"breadcrumbs":4,"title":2},"268":{"body":23,"breadcrumbs":4,"title":2},"269":{"body":72,"breadcrumbs":4,"title":2},"27":{"body":31,"breadcrumbs":4,"title":2},"270":{"body":51,"breadcrumbs":4,"title":2},"271":{"body":24,"breadcrumbs":4,"title":2},"272":{"body":17,"breadcrumbs":4,"title":2},"273":{"body":17,"breadcrumbs":4,"title":2},"274":{"body":304,"breadcrumbs":4,"title":2},"275":{"body":446,"breadcrumbs":4,"title":2},"276":{"body":695,"breadcrumbs":4,"title":2},"277":{"body":29,"breadcrumbs":4,"title":2},"278":{"body":92,"breadcrumbs":4,"title":2},"279":{"body":139,"breadcrumbs":4,"title":2},"28":{"body":10,"breadcrumbs":4,"title":2},"280":{"body":125,"breadcrumbs":4,"title":2},"281":{"body":338,"breadcrumbs":4,"title":2},"282":{"body":87,"breadcrumbs":4,"title":2},"283":{"body":214,"breadcrumbs":4,"title":2},"284":{"body":475,"breadcrumbs":4,"title":2},"285":{"body":140,"breadcrumbs":4,"title":2},"286":{"body":165,"breadcrumbs":4,"title":2},"287":{"body":122,"breadcrumbs":4,"title":2},"288":{"body":522,"breadcrumbs":4,"title":2},"289":{"body":41,"breadcrumbs":4,"title":2},"29":{"body":41,"breadcrumbs":4,"title":2},"290":{"body":137,"breadcrumbs":4,"title":2},"291":{"body":375,"breadcrumbs":4,"title":2},"292":{"body":114,"breadcrumbs":4,"title":2},"293":{"body":112,"breadcrumbs":4,"title":2},"294":{"body":49,"breadcrumbs":4,"title":2},"295":{"body":9,"breadcrumbs":4,"title":2},"296":{"body":9,"breadcrumbs":4,"title":2},"297":{"body":9,"breadcrumbs":4,"title":2},"298":{"body":27,"breadcrumbs":4,"title":2},"299":{"body":62,"breadcrumbs":4,"title":2},"3":{"body":0,"breadcrumbs":5,"title":3},"30":{"body":24,"breadcrumbs":4,"title":2},"300":{"body":9,"breadcrumbs":4,"title":2},"301":{"body":9,"breadcrumbs":4,"title":2},"302":{"body":9,"breadcrumbs":4,"title":2},"303":{"body":16,"breadcrumbs":4,"title":2},"304":{"body":16,"breadcrumbs":4,"title":2},"305":{"body":16,"breadcrumbs":4,"title":2},"306":{"body":9,"breadcrumbs":4,"title":2},"307":{"body":9,"breadcrumbs":4,"title":2},"308":{"body":9,"breadcrumbs":4,"title":2},"309":{"body":15,"breadcrumbs":4,"title":2},"31":{"body":10,"breadcrumbs":4,"title":2},"310":{"body":21,"breadcrumbs":4,"title":2},"311":{"body":15,"breadcrumbs":4,"title":2},"312":{"body":9,"breadcrumbs":4,"title":2},"313":{"body":9,"breadcrumbs":4,"title":2},"314":{"body":9,"breadcrumbs":4,"title":2},"315":{"body":9,"breadcrumbs":4,"title":2},"316":{"body":9,"breadcrumbs":4,"title":2},"317":{"body":21,"breadcrumbs":4,"title":2},"318":{"body":21,"breadcrumbs":4,"title":2},"319":{"body":58,"breadcrumbs":4,"title":2},"32":{"body":16,"breadcrumbs":4,"title":2},"320":{"body":9,"breadcrumbs":4,"title":2},"321":{"body":17,"breadcrumbs":4,"title":2},"322":{"body":15,"breadcrumbs":4,"title":2},"323":{"body":9,"breadcrumbs":4,"title":2},"324":{"body":9,"breadcrumbs":4,"title":2},"325":{"body":9,"breadcrumbs":4,"title":2},"326":{"body":9,"breadcrumbs":4,"title":2},"327":{"body":9,"breadcrumbs":4,"title":2},"328":{"body":9,"breadcrumbs":4,"title":2},"329":{"body":9,"breadcrumbs":4,"title":2},"33":{"body":24,"breadcrumbs":4,"title":2},"330":{"body":9,"breadcrumbs":4,"title":2},"331":{"body":9,"breadcrumbs":4,"title":2},"332":{"body":9,"breadcrumbs":4,"title":2},"333":{"body":15,"breadcrumbs":4,"title":2},"334":{"body":9,"breadcrumbs":4,"title":2},"335":{"body":9,"breadcrumbs":4,"title":2},"336":{"body":9,"breadcrumbs":4,"title":2},"337":{"body":45,"breadcrumbs":4,"title":2},"338":{"body":37,"breadcrumbs":4,"title":2},"339":{"body":286,"breadcrumbs":4,"title":2},"34":{"body":69,"breadcrumbs":4,"title":2},"340":{"body":29,"breadcrumbs":3,"title":1},"341":{"body":9,"breadcrumbs":4,"title":2},"342":{"body":9,"breadcrumbs":4,"title":2},"343":{"body":30,"breadcrumbs":4,"title":2},"344":{"body":59,"breadcrumbs":4,"title":2},"345":{"body":15,"breadcrumbs":4,"title":2},"346":{"body":107,"breadcrumbs":4,"title":2},"347":{"body":17,"breadcrumbs":4,"title":2},"348":{"body":10,"breadcrumbs":4,"title":2},"349":{"body":23,"breadcrumbs":4,"title":2},"35":{"body":10,"breadcrumbs":4,"title":2},"350":{"body":16,"breadcrumbs":4,"title":2},"351":{"body":149,"breadcrumbs":4,"title":2},"352":{"body":60,"breadcrumbs":4,"title":2},"353":{"body":448,"breadcrumbs":4,"title":2},"354":{"body":42,"breadcrumbs":4,"title":2},"355":{"body":328,"breadcrumbs":4,"title":2},"356":{"body":10,"breadcrumbs":3,"title":1},"357":{"body":10,"breadcrumbs":3,"title":1},"358":{"body":10,"breadcrumbs":3,"title":1},"359":{"body":12,"breadcrumbs":4,"title":2},"36":{"body":118,"breadcrumbs":4,"title":2},"360":{"body":12,"breadcrumbs":4,"title":2},"361":{"body":12,"breadcrumbs":4,"title":2},"362":{"body":13,"breadcrumbs":4,"title":2},"363":{"body":17,"breadcrumbs":4,"title":2},"364":{"body":10,"breadcrumbs":4,"title":2},"365":{"body":10,"breadcrumbs":4,"title":2},"366":{"body":10,"breadcrumbs":4,"title":2},"367":{"body":10,"breadcrumbs":4,"title":2},"368":{"body":10,"breadcrumbs":4,"title":2},"369":{"body":10,"breadcrumbs":4,"title":2},"37":{"body":23,"breadcrumbs":4,"title":2},"370":{"body":10,"breadcrumbs":4,"title":2},"371":{"body":10,"breadcrumbs":4,"title":2},"372":{"body":24,"breadcrumbs":4,"title":2},"373":{"body":10,"breadcrumbs":4,"title":2},"374":{"body":10,"breadcrumbs":4,"title":2},"375":{"body":10,"breadcrumbs":4,"title":2},"376":{"body":10,"breadcrumbs":4,"title":2},"377":{"body":10,"breadcrumbs":4,"title":2},"378":{"body":10,"breadcrumbs":4,"title":2},"379":{"body":10,"breadcrumbs":4,"title":2},"38":{"body":36,"breadcrumbs":4,"title":2},"380":{"body":10,"breadcrumbs":4,"title":2},"381":{"body":10,"breadcrumbs":4,"title":2},"382":{"body":10,"breadcrumbs":4,"title":2},"383":{"body":10,"breadcrumbs":4,"title":2},"384":{"body":10,"breadcrumbs":4,"title":2},"385":{"body":24,"breadcrumbs":4,"title":2},"386":{"body":45,"breadcrumbs":4,"title":2},"387":{"body":31,"breadcrumbs":4,"title":2},"388":{"body":17,"breadcrumbs":4,"title":2},"389":{"body":58,"breadcrumbs":4,"title":2},"39":{"body":59,"breadcrumbs":4,"title":2},"390":{"body":44,"breadcrumbs":4,"title":2},"391":{"body":17,"breadcrumbs":4,"title":2},"392":{"body":10,"breadcrumbs":4,"title":2},"393":{"body":10,"breadcrumbs":4,"title":2},"394":{"body":24,"breadcrumbs":4,"title":2},"395":{"body":17,"breadcrumbs":4,"title":2},"396":{"body":17,"breadcrumbs":4,"title":2},"397":{"body":17,"breadcrumbs":4,"title":2},"398":{"body":30,"breadcrumbs":4,"title":2},"399":{"body":17,"breadcrumbs":4,"title":2},"4":{"body":788,"breadcrumbs":5,"title":3},"40":{"body":10,"breadcrumbs":4,"title":2},"400":{"body":24,"breadcrumbs":4,"title":2},"401":{"body":9,"breadcrumbs":4,"title":2},"402":{"body":51,"breadcrumbs":4,"title":2},"403":{"body":58,"breadcrumbs":4,"title":2},"404":{"body":158,"breadcrumbs":4,"title":2},"405":{"body":125,"breadcrumbs":4,"title":2},"406":{"body":77,"breadcrumbs":4,"title":2},"407":{"body":33,"breadcrumbs":4,"title":2},"408":{"body":15,"breadcrumbs":4,"title":2},"409":{"body":17,"breadcrumbs":4,"title":2},"41":{"body":31,"breadcrumbs":4,"title":2},"410":{"body":52,"breadcrumbs":4,"title":2},"411":{"body":49,"breadcrumbs":4,"title":2},"412":{"body":37,"breadcrumbs":4,"title":2},"413":{"body":27,"breadcrumbs":4,"title":2},"414":{"body":9,"breadcrumbs":4,"title":2},"415":{"body":17,"breadcrumbs":4,"title":2},"416":{"body":10,"breadcrumbs":4,"title":2},"417":{"body":10,"breadcrumbs":4,"title":2},"418":{"body":39,"breadcrumbs":4,"title":2},"419":{"body":15,"breadcrumbs":4,"title":2},"42":{"body":10,"breadcrumbs":4,"title":2},"420":{"body":157,"breadcrumbs":4,"title":2},"421":{"body":9,"breadcrumbs":4,"title":2},"422":{"body":69,"breadcrumbs":4,"title":2},"423":{"body":63,"breadcrumbs":4,"title":2},"424":{"body":178,"breadcrumbs":4,"title":2},"425":{"body":87,"breadcrumbs":4,"title":2},"426":{"body":176,"breadcrumbs":4,"title":2},"427":{"body":85,"breadcrumbs":4,"title":2},"428":{"body":313,"breadcrumbs":4,"title":2},"429":{"body":108,"breadcrumbs":4,"title":2},"43":{"body":64,"breadcrumbs":4,"title":2},"430":{"body":37,"breadcrumbs":4,"title":2},"431":{"body":23,"breadcrumbs":4,"title":2},"432":{"body":9,"breadcrumbs":4,"title":2},"433":{"body":165,"breadcrumbs":4,"title":2},"434":{"body":15,"breadcrumbs":4,"title":2},"435":{"body":45,"breadcrumbs":4,"title":2},"436":{"body":69,"breadcrumbs":4,"title":2},"437":{"body":15,"breadcrumbs":4,"title":2},"438":{"body":27,"breadcrumbs":4,"title":2},"439":{"body":21,"breadcrumbs":4,"title":2},"44":{"body":15,"breadcrumbs":4,"title":2},"440":{"body":21,"breadcrumbs":4,"title":2},"441":{"body":51,"breadcrumbs":4,"title":2},"442":{"body":9,"breadcrumbs":4,"title":2},"443":{"body":15,"breadcrumbs":4,"title":2},"444":{"body":15,"breadcrumbs":4,"title":2},"445":{"body":9,"breadcrumbs":4,"title":2},"446":{"body":34,"breadcrumbs":4,"title":2},"447":{"body":9,"breadcrumbs":4,"title":2},"448":{"body":73,"breadcrumbs":4,"title":2},"449":{"body":9,"breadcrumbs":4,"title":2},"45":{"body":9,"breadcrumbs":4,"title":2},"450":{"body":15,"breadcrumbs":4,"title":2},"451":{"body":105,"breadcrumbs":4,"title":2},"452":{"body":15,"breadcrumbs":4,"title":2},"453":{"body":149,"breadcrumbs":4,"title":2},"454":{"body":73,"breadcrumbs":4,"title":2},"455":{"body":9,"breadcrumbs":4,"title":2},"456":{"body":52,"breadcrumbs":4,"title":2},"457":{"body":9,"breadcrumbs":4,"title":2},"458":{"body":28,"breadcrumbs":4,"title":2},"459":{"body":15,"breadcrumbs":4,"title":2},"46":{"body":10,"breadcrumbs":4,"title":2},"460":{"body":66,"breadcrumbs":4,"title":2},"461":{"body":9,"breadcrumbs":4,"title":2},"462":{"body":87,"breadcrumbs":4,"title":2},"463":{"body":10,"breadcrumbs":4,"title":2},"464":{"body":10,"breadcrumbs":3,"title":1},"465":{"body":20,"breadcrumbs":4,"title":2},"466":{"body":20,"breadcrumbs":4,"title":2},"467":{"body":20,"breadcrumbs":4,"title":2},"468":{"body":20,"breadcrumbs":4,"title":2},"469":{"body":20,"breadcrumbs":4,"title":2},"47":{"body":10,"breadcrumbs":4,"title":2},"470":{"body":20,"breadcrumbs":4,"title":2},"471":{"body":20,"breadcrumbs":4,"title":2},"472":{"body":20,"breadcrumbs":4,"title":2},"473":{"body":20,"breadcrumbs":4,"title":2},"474":{"body":20,"breadcrumbs":4,"title":2},"475":{"body":20,"breadcrumbs":4,"title":2},"476":{"body":10,"breadcrumbs":4,"title":2},"477":{"body":10,"breadcrumbs":4,"title":2},"478":{"body":10,"breadcrumbs":4,"title":2},"479":{"body":10,"breadcrumbs":4,"title":2},"48":{"body":17,"breadcrumbs":4,"title":2},"480":{"body":10,"breadcrumbs":4,"title":2},"481":{"body":45,"breadcrumbs":4,"title":2},"482":{"body":17,"breadcrumbs":4,"title":2},"483":{"body":17,"breadcrumbs":4,"title":2},"484":{"body":80,"breadcrumbs":4,"title":2},"485":{"body":24,"breadcrumbs":4,"title":2},"486":{"body":17,"breadcrumbs":4,"title":2},"487":{"body":80,"breadcrumbs":4,"title":2},"488":{"body":21,"breadcrumbs":4,"title":2},"489":{"body":10,"breadcrumbs":4,"title":2},"49":{"body":15,"breadcrumbs":4,"title":2},"490":{"body":24,"breadcrumbs":4,"title":2},"491":{"body":10,"breadcrumbs":4,"title":2},"492":{"body":31,"breadcrumbs":4,"title":2},"493":{"body":17,"breadcrumbs":4,"title":2},"494":{"body":24,"breadcrumbs":4,"title":2},"495":{"body":31,"breadcrumbs":4,"title":2},"496":{"body":24,"breadcrumbs":4,"title":2},"497":{"body":31,"breadcrumbs":4,"title":2},"498":{"body":10,"breadcrumbs":4,"title":2},"499":{"body":10,"breadcrumbs":4,"title":2},"5":{"body":378,"breadcrumbs":6,"title":4},"50":{"body":110,"breadcrumbs":4,"title":2},"500":{"body":10,"breadcrumbs":4,"title":2},"501":{"body":52,"breadcrumbs":4,"title":2},"502":{"body":17,"breadcrumbs":4,"title":2},"503":{"body":24,"breadcrumbs":4,"title":2},"504":{"body":38,"breadcrumbs":4,"title":2},"505":{"body":24,"breadcrumbs":4,"title":2},"506":{"body":48,"breadcrumbs":4,"title":2},"507":{"body":24,"breadcrumbs":4,"title":2},"508":{"body":31,"breadcrumbs":4,"title":2},"509":{"body":24,"breadcrumbs":4,"title":2},"51":{"body":20,"breadcrumbs":4,"title":2},"510":{"body":17,"breadcrumbs":4,"title":2},"511":{"body":17,"breadcrumbs":4,"title":2},"512":{"body":11,"breadcrumbs":4,"title":2},"513":{"body":10,"breadcrumbs":4,"title":2},"514":{"body":10,"breadcrumbs":4,"title":2},"515":{"body":15,"breadcrumbs":4,"title":2},"516":{"body":12,"breadcrumbs":4,"title":2},"517":{"body":10,"breadcrumbs":4,"title":2},"518":{"body":17,"breadcrumbs":4,"title":2},"519":{"body":19,"breadcrumbs":4,"title":2},"52":{"body":18,"breadcrumbs":4,"title":2},"520":{"body":73,"breadcrumbs":4,"title":2},"521":{"body":73,"breadcrumbs":4,"title":2},"522":{"body":10,"breadcrumbs":4,"title":2},"523":{"body":10,"breadcrumbs":4,"title":2},"524":{"body":59,"breadcrumbs":4,"title":2},"525":{"body":10,"breadcrumbs":4,"title":2},"526":{"body":8,"breadcrumbs":6,"title":4},"527":{"body":27,"breadcrumbs":3,"title":1},"528":{"body":65,"breadcrumbs":5,"title":3},"529":{"body":0,"breadcrumbs":4,"title":2},"53":{"body":111,"breadcrumbs":4,"title":2},"530":{"body":20,"breadcrumbs":5,"title":3},"531":{"body":185,"breadcrumbs":5,"title":3},"532":{"body":69,"breadcrumbs":6,"title":4},"533":{"body":18,"breadcrumbs":6,"title":4},"534":{"body":9,"breadcrumbs":6,"title":4},"535":{"body":11,"breadcrumbs":4,"title":2},"536":{"body":10,"breadcrumbs":4,"title":2},"537":{"body":13,"breadcrumbs":4,"title":2},"538":{"body":61,"breadcrumbs":4,"title":2},"539":{"body":0,"breadcrumbs":4,"title":2},"54":{"body":104,"breadcrumbs":4,"title":2},"540":{"body":13,"breadcrumbs":5,"title":3},"541":{"body":20,"breadcrumbs":4,"title":2},"542":{"body":34,"breadcrumbs":4,"title":2},"543":{"body":0,"breadcrumbs":4,"title":2},"544":{"body":21,"breadcrumbs":5,"title":3},"545":{"body":16,"breadcrumbs":3,"title":1},"546":{"body":0,"breadcrumbs":4,"title":2},"547":{"body":37,"breadcrumbs":4,"title":2},"548":{"body":36,"breadcrumbs":3,"title":1},"549":{"body":0,"breadcrumbs":4,"title":2},"55":{"body":15,"breadcrumbs":4,"title":2},"550":{"body":14,"breadcrumbs":6,"title":4},"551":{"body":17,"breadcrumbs":6,"title":4},"552":{"body":15,"breadcrumbs":6,"title":4},"553":{"body":14,"breadcrumbs":6,"title":4},"554":{"body":27,"breadcrumbs":5,"title":3},"555":{"body":0,"breadcrumbs":3,"title":1},"556":{"body":23,"breadcrumbs":6,"title":4},"557":{"body":26,"breadcrumbs":6,"title":4},"558":{"body":21,"breadcrumbs":5,"title":3},"559":{"body":0,"breadcrumbs":4,"title":2},"56":{"body":57,"breadcrumbs":4,"title":2},"560":{"body":19,"breadcrumbs":4,"title":2},"561":{"body":15,"breadcrumbs":3,"title":1},"562":{"body":22,"breadcrumbs":4,"title":2},"563":{"body":10,"breadcrumbs":3,"title":1},"564":{"body":23,"breadcrumbs":3,"title":1},"565":{"body":0,"breadcrumbs":7,"title":4},"566":{"body":17,"breadcrumbs":4,"title":1},"567":{"body":0,"breadcrumbs":5,"title":2},"568":{"body":24,"breadcrumbs":4,"title":1},"569":{"body":11,"breadcrumbs":4,"title":1},"57":{"body":77,"breadcrumbs":4,"title":2},"570":{"body":18,"breadcrumbs":4,"title":1},"571":{"body":49,"breadcrumbs":5,"title":2},"572":{"body":16,"breadcrumbs":5,"title":2},"573":{"body":0,"breadcrumbs":5,"title":2},"574":{"body":16,"breadcrumbs":7,"title":4},"575":{"body":14,"breadcrumbs":7,"title":4},"576":{"body":12,"breadcrumbs":7,"title":4},"577":{"body":0,"breadcrumbs":5,"title":2},"578":{"body":5,"breadcrumbs":5,"title":2},"579":{"body":9,"breadcrumbs":7,"title":4},"58":{"body":9,"breadcrumbs":4,"title":2},"580":{"body":9,"breadcrumbs":5,"title":2},"581":{"body":8,"breadcrumbs":5,"title":2},"582":{"body":14,"breadcrumbs":4,"title":1},"583":{"body":0,"breadcrumbs":4,"title":1},"584":{"body":23,"breadcrumbs":5,"title":2},"585":{"body":19,"breadcrumbs":5,"title":2},"586":{"body":26,"breadcrumbs":5,"title":2},"587":{"body":0,"breadcrumbs":5,"title":2},"588":{"body":20,"breadcrumbs":6,"title":3},"589":{"body":13,"breadcrumbs":5,"title":2},"59":{"body":60,"breadcrumbs":4,"title":2},"590":{"body":13,"breadcrumbs":6,"title":3},"591":{"body":56,"breadcrumbs":4,"title":1},"592":{"body":29,"breadcrumbs":4,"title":1},"593":{"body":18,"breadcrumbs":4,"title":3},"594":{"body":18,"breadcrumbs":3,"title":2},"595":{"body":340,"breadcrumbs":3,"title":2},"596":{"body":243,"breadcrumbs":3,"title":2},"597":{"body":362,"breadcrumbs":4,"title":3},"598":{"body":138,"breadcrumbs":4,"title":3},"599":{"body":3,"breadcrumbs":6,"title":3},"6":{"body":16599,"breadcrumbs":5,"title":3},"60":{"body":55,"breadcrumbs":4,"title":2},"600":{"body":502,"breadcrumbs":7,"title":4},"601":{"body":122,"breadcrumbs":9,"title":6},"602":{"body":224,"breadcrumbs":7,"title":4},"603":{"body":71,"breadcrumbs":6,"title":3},"604":{"body":74,"breadcrumbs":5,"title":2},"605":{"body":50,"breadcrumbs":5,"title":2},"606":{"body":80,"breadcrumbs":4,"title":1},"607":{"body":156,"breadcrumbs":4,"title":1},"608":{"body":82,"breadcrumbs":6,"title":3},"609":{"body":77,"breadcrumbs":5,"title":2},"61":{"body":44,"breadcrumbs":4,"title":2},"610":{"body":231,"breadcrumbs":5,"title":2},"611":{"body":62,"breadcrumbs":6,"title":3},"612":{"body":14,"breadcrumbs":7,"title":4},"613":{"body":119,"breadcrumbs":7,"title":4},"614":{"body":92,"breadcrumbs":5,"title":2},"615":{"body":100,"breadcrumbs":6,"title":3},"616":{"body":50,"breadcrumbs":5,"title":2},"617":{"body":74,"breadcrumbs":5,"title":2},"618":{"body":0,"breadcrumbs":6,"title":3},"619":{"body":49,"breadcrumbs":5,"title":2},"62":{"body":28,"breadcrumbs":4,"title":2},"620":{"body":222,"breadcrumbs":6,"title":3},"621":{"body":198,"breadcrumbs":6,"title":3},"622":{"body":225,"breadcrumbs":7,"title":4},"623":{"body":157,"breadcrumbs":6,"title":3},"624":{"body":175,"breadcrumbs":6,"title":3},"625":{"body":106,"breadcrumbs":5,"title":2},"626":{"body":97,"breadcrumbs":5,"title":2},"627":{"body":284,"breadcrumbs":6,"title":3},"628":{"body":214,"breadcrumbs":7,"title":4},"629":{"body":75,"breadcrumbs":7,"title":4},"63":{"body":34,"breadcrumbs":4,"title":2},"630":{"body":161,"breadcrumbs":6,"title":3},"631":{"body":48,"breadcrumbs":6,"title":3},"632":{"body":64,"breadcrumbs":5,"title":2},"633":{"body":36,"breadcrumbs":7,"title":4},"634":{"body":76,"breadcrumbs":4,"title":1},"635":{"body":97,"breadcrumbs":5,"title":2},"636":{"body":114,"breadcrumbs":6,"title":3},"637":{"body":73,"breadcrumbs":5,"title":2},"638":{"body":91,"breadcrumbs":6,"title":3},"639":{"body":89,"breadcrumbs":6,"title":3},"64":{"body":22,"breadcrumbs":4,"title":2},"640":{"body":69,"breadcrumbs":4,"title":1},"641":{"body":0,"breadcrumbs":5,"title":2},"642":{"body":170,"breadcrumbs":8,"title":5},"643":{"body":238,"breadcrumbs":7,"title":4},"644":{"body":81,"breadcrumbs":6,"title":3},"645":{"body":81,"breadcrumbs":7,"title":4},"646":{"body":28,"breadcrumbs":6,"title":3},"647":{"body":121,"breadcrumbs":6,"title":3},"648":{"body":0,"breadcrumbs":8,"title":4},"649":{"body":70,"breadcrumbs":7,"title":3},"65":{"body":22,"breadcrumbs":4,"title":2},"650":{"body":28,"breadcrumbs":6,"title":2},"651":{"body":142,"breadcrumbs":10,"title":6},"652":{"body":39,"breadcrumbs":8,"title":4},"653":{"body":29,"breadcrumbs":6,"title":2},"654":{"body":0,"breadcrumbs":7,"title":4},"655":{"body":210,"breadcrumbs":6,"title":3},"656":{"body":164,"breadcrumbs":9,"title":6},"657":{"body":140,"breadcrumbs":5,"title":2},"658":{"body":127,"breadcrumbs":5,"title":2},"659":{"body":160,"breadcrumbs":6,"title":3},"66":{"body":10,"breadcrumbs":4,"title":2},"660":{"body":98,"breadcrumbs":6,"title":3},"661":{"body":306,"breadcrumbs":7,"title":4},"662":{"body":257,"breadcrumbs":7,"title":4},"663":{"body":316,"breadcrumbs":8,"title":5},"664":{"body":255,"breadcrumbs":7,"title":4},"665":{"body":36,"breadcrumbs":5,"title":2},"666":{"body":0,"breadcrumbs":7,"title":4},"667":{"body":68,"breadcrumbs":4,"title":1},"668":{"body":145,"breadcrumbs":5,"title":2},"669":{"body":115,"breadcrumbs":6,"title":3},"67":{"body":67,"breadcrumbs":4,"title":2},"670":{"body":122,"breadcrumbs":9,"title":6},"671":{"body":84,"breadcrumbs":6,"title":3},"672":{"body":85,"breadcrumbs":5,"title":2},"673":{"body":21,"breadcrumbs":5,"title":2},"674":{"body":35,"breadcrumbs":5,"title":2},"675":{"body":78,"breadcrumbs":6,"title":3},"676":{"body":56,"breadcrumbs":6,"title":3},"677":{"body":68,"breadcrumbs":5,"title":2},"678":{"body":0,"breadcrumbs":5,"title":3},"679":{"body":100,"breadcrumbs":6,"title":4},"68":{"body":28,"breadcrumbs":4,"title":2},"680":{"body":28,"breadcrumbs":4,"title":2},"681":{"body":84,"breadcrumbs":6,"title":4},"682":{"body":55,"breadcrumbs":9,"title":7},"683":{"body":167,"breadcrumbs":5,"title":3},"684":{"body":109,"breadcrumbs":5,"title":3},"685":{"body":0,"breadcrumbs":6,"title":3},"686":{"body":256,"breadcrumbs":6,"title":3},"687":{"body":0,"breadcrumbs":5,"title":2},"688":{"body":35,"breadcrumbs":4,"title":1},"689":{"body":49,"breadcrumbs":4,"title":1},"69":{"body":10,"breadcrumbs":4,"title":2},"690":{"body":48,"breadcrumbs":4,"title":1},"691":{"body":247,"breadcrumbs":4,"title":1},"692":{"body":53,"breadcrumbs":4,"title":1},"693":{"body":99,"breadcrumbs":4,"title":1},"694":{"body":88,"breadcrumbs":4,"title":1},"695":{"body":66,"breadcrumbs":4,"title":1},"696":{"body":52,"breadcrumbs":4,"title":1},"697":{"body":76,"breadcrumbs":6,"title":3},"698":{"body":0,"breadcrumbs":5,"title":2},"699":{"body":20,"breadcrumbs":7,"title":4},"7":{"body":0,"breadcrumbs":5,"title":3},"70":{"body":98,"breadcrumbs":4,"title":2},"700":{"body":19,"breadcrumbs":6,"title":3},"701":{"body":11,"breadcrumbs":5,"title":2},"702":{"body":38,"breadcrumbs":6,"title":3},"703":{"body":27,"breadcrumbs":6,"title":3},"704":{"body":0,"breadcrumbs":5,"title":2},"705":{"body":31,"breadcrumbs":5,"title":2},"706":{"body":23,"breadcrumbs":5,"title":2},"707":{"body":23,"breadcrumbs":5,"title":2},"708":{"body":56,"breadcrumbs":5,"title":2},"709":{"body":46,"breadcrumbs":6,"title":3},"71":{"body":93,"breadcrumbs":4,"title":2},"710":{"body":0,"breadcrumbs":3,"title":1},"711":{"body":70,"breadcrumbs":5,"title":3},"712":{"body":44,"breadcrumbs":5,"title":3},"713":{"body":77,"breadcrumbs":4,"title":2},"714":{"body":404,"breadcrumbs":6,"title":4},"715":{"body":60,"breadcrumbs":5,"title":3},"716":{"body":22,"breadcrumbs":5,"title":3},"717":{"body":19,"breadcrumbs":6,"title":4},"718":{"body":87,"breadcrumbs":5,"title":3},"719":{"body":51,"breadcrumbs":5,"title":3},"72":{"body":10,"breadcrumbs":4,"title":2},"720":{"body":57,"breadcrumbs":4,"title":2},"721":{"body":11,"breadcrumbs":7,"title":5},"722":{"body":0,"breadcrumbs":6,"title":4},"723":{"body":111,"breadcrumbs":5,"title":3},"724":{"body":110,"breadcrumbs":5,"title":3},"725":{"body":56,"breadcrumbs":5,"title":3},"726":{"body":41,"breadcrumbs":5,"title":3},"727":{"body":0,"breadcrumbs":7,"title":5},"728":{"body":86,"breadcrumbs":6,"title":4},"729":{"body":61,"breadcrumbs":5,"title":3},"73":{"body":41,"breadcrumbs":4,"title":2},"730":{"body":0,"breadcrumbs":6,"title":4},"731":{"body":190,"breadcrumbs":6,"title":4},"732":{"body":97,"breadcrumbs":7,"title":5},"733":{"body":119,"breadcrumbs":7,"title":5},"734":{"body":0,"breadcrumbs":6,"title":4},"735":{"body":79,"breadcrumbs":6,"title":4},"736":{"body":138,"breadcrumbs":12,"title":10},"737":{"body":86,"breadcrumbs":6,"title":4},"738":{"body":0,"breadcrumbs":7,"title":5},"739":{"body":52,"breadcrumbs":7,"title":5},"74":{"body":16,"breadcrumbs":4,"title":2},"740":{"body":89,"breadcrumbs":6,"title":4},"741":{"body":46,"breadcrumbs":5,"title":3},"742":{"body":19,"breadcrumbs":6,"title":4},"743":{"body":87,"breadcrumbs":8,"title":6},"744":{"body":113,"breadcrumbs":6,"title":4},"745":{"body":73,"breadcrumbs":5,"title":3},"746":{"body":36,"breadcrumbs":5,"title":3},"747":{"body":26,"breadcrumbs":7,"title":5},"748":{"body":85,"breadcrumbs":5,"title":3},"749":{"body":188,"breadcrumbs":8,"title":6},"75":{"body":129,"breadcrumbs":4,"title":2},"750":{"body":23,"breadcrumbs":7,"title":5},"751":{"body":183,"breadcrumbs":7,"title":5},"752":{"body":187,"breadcrumbs":11,"title":9},"753":{"body":27,"breadcrumbs":4,"title":2},"754":{"body":133,"breadcrumbs":7,"title":5},"755":{"body":168,"breadcrumbs":7,"title":5},"756":{"body":31,"breadcrumbs":8,"title":6},"757":{"body":47,"breadcrumbs":4,"title":2},"758":{"body":70,"breadcrumbs":5,"title":3},"759":{"body":30,"breadcrumbs":5,"title":3},"76":{"body":82,"breadcrumbs":4,"title":2},"760":{"body":146,"breadcrumbs":6,"title":4},"761":{"body":70,"breadcrumbs":6,"title":4},"762":{"body":210,"breadcrumbs":8,"title":6},"763":{"body":60,"breadcrumbs":5,"title":3},"764":{"body":0,"breadcrumbs":5,"title":3},"765":{"body":88,"breadcrumbs":5,"title":3},"766":{"body":200,"breadcrumbs":9,"title":7},"767":{"body":286,"breadcrumbs":6,"title":4},"768":{"body":75,"breadcrumbs":6,"title":4},"769":{"body":39,"breadcrumbs":6,"title":4},"77":{"body":130,"breadcrumbs":4,"title":2},"770":{"body":96,"breadcrumbs":5,"title":3},"771":{"body":0,"breadcrumbs":6,"title":4},"772":{"body":144,"breadcrumbs":5,"title":3},"773":{"body":63,"breadcrumbs":5,"title":3},"774":{"body":0,"breadcrumbs":7,"title":5},"775":{"body":69,"breadcrumbs":7,"title":5},"776":{"body":237,"breadcrumbs":10,"title":8},"777":{"body":0,"breadcrumbs":7,"title":5},"778":{"body":44,"breadcrumbs":7,"title":5},"779":{"body":27,"breadcrumbs":5,"title":3},"78":{"body":9,"breadcrumbs":4,"title":2},"780":{"body":44,"breadcrumbs":5,"title":3},"781":{"body":0,"breadcrumbs":7,"title":5},"782":{"body":65,"breadcrumbs":5,"title":3},"783":{"body":48,"breadcrumbs":6,"title":4},"784":{"body":0,"breadcrumbs":6,"title":4},"785":{"body":81,"breadcrumbs":5,"title":3},"786":{"body":37,"breadcrumbs":5,"title":3},"787":{"body":35,"breadcrumbs":6,"title":4},"788":{"body":0,"breadcrumbs":6,"title":4},"789":{"body":83,"breadcrumbs":5,"title":3},"79":{"body":15,"breadcrumbs":4,"title":2},"790":{"body":25,"breadcrumbs":5,"title":3},"791":{"body":15,"breadcrumbs":4,"title":2},"792":{"body":105,"breadcrumbs":4,"title":2},"793":{"body":70,"breadcrumbs":4,"title":2},"794":{"body":96,"breadcrumbs":3,"title":1},"795":{"body":43,"breadcrumbs":4,"title":2},"796":{"body":144,"breadcrumbs":5,"title":3},"797":{"body":9,"breadcrumbs":3,"title":1},"798":{"body":0,"breadcrumbs":3,"title":1},"799":{"body":0,"breadcrumbs":3,"title":1},"8":{"body":710,"breadcrumbs":6,"title":4},"80":{"body":17,"breadcrumbs":4,"title":2},"800":{"body":6,"breadcrumbs":4,"title":2},"801":{"body":0,"breadcrumbs":3,"title":1},"802":{"body":0,"breadcrumbs":4,"title":2},"803":{"body":38,"breadcrumbs":4,"title":2},"804":{"body":0,"breadcrumbs":3,"title":1},"805":{"body":0,"breadcrumbs":3,"title":1},"806":{"body":0,"breadcrumbs":4,"title":2},"807":{"body":57,"breadcrumbs":6,"title":3},"808":{"body":123,"breadcrumbs":6,"title":3},"809":{"body":53,"breadcrumbs":5,"title":2},"81":{"body":52,"breadcrumbs":4,"title":2},"810":{"body":27,"breadcrumbs":5,"title":2},"811":{"body":57,"breadcrumbs":4,"title":1},"812":{"body":58,"breadcrumbs":5,"title":2},"813":{"body":45,"breadcrumbs":9,"title":6},"814":{"body":45,"breadcrumbs":4,"title":1},"815":{"body":45,"breadcrumbs":4,"title":1},"816":{"body":0,"breadcrumbs":4,"title":1},"817":{"body":85,"breadcrumbs":8,"title":5},"818":{"body":226,"breadcrumbs":9,"title":6},"819":{"body":113,"breadcrumbs":8,"title":5},"82":{"body":87,"breadcrumbs":4,"title":2},"820":{"body":46,"breadcrumbs":8,"title":5},"821":{"body":69,"breadcrumbs":7,"title":4},"822":{"body":63,"breadcrumbs":9,"title":6},"823":{"body":109,"breadcrumbs":7,"title":4},"824":{"body":67,"breadcrumbs":4,"title":1},"825":{"body":89,"breadcrumbs":6,"title":3},"826":{"body":33,"breadcrumbs":5,"title":2},"827":{"body":13,"breadcrumbs":5,"title":2},"828":{"body":23,"breadcrumbs":5,"title":2},"829":{"body":2,"breadcrumbs":4,"title":1},"83":{"body":77,"breadcrumbs":4,"title":2},"830":{"body":12,"breadcrumbs":4,"title":1},"831":{"body":35,"breadcrumbs":5,"title":2},"832":{"body":98,"breadcrumbs":11,"title":7},"833":{"body":56,"breadcrumbs":5,"title":1},"834":{"body":198,"breadcrumbs":5,"title":1},"835":{"body":93,"breadcrumbs":8,"title":4},"836":{"body":161,"breadcrumbs":6,"title":2},"837":{"body":7,"breadcrumbs":5,"title":1},"838":{"body":71,"breadcrumbs":6,"title":2},"839":{"body":54,"breadcrumbs":5,"title":1},"84":{"body":82,"breadcrumbs":4,"title":2},"840":{"body":19,"breadcrumbs":5,"title":1},"841":{"body":135,"breadcrumbs":5,"title":1},"842":{"body":18,"breadcrumbs":6,"title":2},"843":{"body":197,"breadcrumbs":8,"title":4},"844":{"body":127,"breadcrumbs":10,"title":6},"845":{"body":157,"breadcrumbs":9,"title":5},"846":{"body":423,"breadcrumbs":6,"title":2},"847":{"body":41,"breadcrumbs":6,"title":2},"848":{"body":142,"breadcrumbs":7,"title":3},"849":{"body":151,"breadcrumbs":6,"title":2},"85":{"body":51,"breadcrumbs":4,"title":2},"850":{"body":133,"breadcrumbs":6,"title":2},"851":{"body":61,"breadcrumbs":5,"title":1},"852":{"body":14,"breadcrumbs":5,"title":1},"853":{"body":96,"breadcrumbs":6,"title":2},"854":{"body":89,"breadcrumbs":9,"title":5},"855":{"body":49,"breadcrumbs":5,"title":1},"856":{"body":137,"breadcrumbs":5,"title":1},"857":{"body":77,"breadcrumbs":6,"title":2},"858":{"body":164,"breadcrumbs":6,"title":2},"859":{"body":69,"breadcrumbs":5,"title":1},"86":{"body":22,"breadcrumbs":4,"title":2},"860":{"body":0,"breadcrumbs":5,"title":1},"861":{"body":97,"breadcrumbs":5,"title":1},"862":{"body":61,"breadcrumbs":6,"title":2},"863":{"body":97,"breadcrumbs":5,"title":1},"864":{"body":49,"breadcrumbs":6,"title":2},"865":{"body":92,"breadcrumbs":6,"title":2},"866":{"body":60,"breadcrumbs":5,"title":1},"867":{"body":45,"breadcrumbs":5,"title":1},"868":{"body":56,"breadcrumbs":6,"title":2},"869":{"body":61,"breadcrumbs":8,"title":5},"87":{"body":16,"breadcrumbs":4,"title":2},"870":{"body":50,"breadcrumbs":4,"title":1},"871":{"body":193,"breadcrumbs":4,"title":1},"872":{"body":119,"breadcrumbs":5,"title":2},"873":{"body":0,"breadcrumbs":4,"title":1},"874":{"body":51,"breadcrumbs":8,"title":5},"875":{"body":37,"breadcrumbs":9,"title":6},"876":{"body":79,"breadcrumbs":6,"title":3},"877":{"body":75,"breadcrumbs":10,"title":7},"878":{"body":42,"breadcrumbs":5,"title":2},"879":{"body":131,"breadcrumbs":5,"title":2},"88":{"body":27,"breadcrumbs":4,"title":2},"880":{"body":97,"breadcrumbs":4,"title":1},"881":{"body":63,"breadcrumbs":4,"title":1},"882":{"body":107,"breadcrumbs":5,"title":2},"883":{"body":62,"breadcrumbs":9,"title":5},"884":{"body":51,"breadcrumbs":5,"title":1},"885":{"body":77,"breadcrumbs":5,"title":1},"886":{"body":88,"breadcrumbs":6,"title":2},"887":{"body":0,"breadcrumbs":5,"title":1},"888":{"body":97,"breadcrumbs":6,"title":2},"889":{"body":70,"breadcrumbs":5,"title":1},"89":{"body":9,"breadcrumbs":4,"title":2},"890":{"body":66,"breadcrumbs":5,"title":1},"891":{"body":147,"breadcrumbs":7,"title":3},"892":{"body":82,"breadcrumbs":6,"title":2},"893":{"body":26,"breadcrumbs":6,"title":2},"894":{"body":94,"breadcrumbs":6,"title":2},"895":{"body":70,"breadcrumbs":5,"title":1},"896":{"body":40,"breadcrumbs":5,"title":1},"897":{"body":54,"breadcrumbs":6,"title":2},"898":{"body":46,"breadcrumbs":8,"title":6},"899":{"body":40,"breadcrumbs":3,"title":1},"9":{"body":0,"breadcrumbs":5,"title":3},"90":{"body":9,"breadcrumbs":4,"title":2},"900":{"body":0,"breadcrumbs":3,"title":1},"901":{"body":65,"breadcrumbs":4,"title":2},"902":{"body":25,"breadcrumbs":4,"title":2},"903":{"body":0,"breadcrumbs":3,"title":1},"904":{"body":116,"breadcrumbs":5,"title":3},"905":{"body":130,"breadcrumbs":6,"title":4},"906":{"body":27,"breadcrumbs":4,"title":2},"907":{"body":26,"breadcrumbs":4,"title":2},"908":{"body":112,"breadcrumbs":4,"title":2},"909":{"body":77,"breadcrumbs":3,"title":1},"91":{"body":9,"breadcrumbs":4,"title":2},"910":{"body":16,"breadcrumbs":3,"title":1},"911":{"body":61,"breadcrumbs":4,"title":2},"912":{"body":125,"breadcrumbs":15,"title":11},"913":{"body":52,"breadcrumbs":5,"title":1},"914":{"body":86,"breadcrumbs":5,"title":1},"915":{"body":134,"breadcrumbs":6,"title":2},"916":{"body":0,"breadcrumbs":5,"title":1},"917":{"body":85,"breadcrumbs":9,"title":5},"918":{"body":20,"breadcrumbs":9,"title":5},"919":{"body":77,"breadcrumbs":9,"title":5},"92":{"body":101,"breadcrumbs":4,"title":2},"920":{"body":102,"breadcrumbs":10,"title":6},"921":{"body":105,"breadcrumbs":9,"title":5},"922":{"body":13,"breadcrumbs":5,"title":1},"923":{"body":65,"breadcrumbs":6,"title":2},"924":{"body":115,"breadcrumbs":6,"title":2},"925":{"body":64,"breadcrumbs":5,"title":1},"926":{"body":54,"breadcrumbs":5,"title":1},"927":{"body":54,"breadcrumbs":6,"title":2},"928":{"body":129,"breadcrumbs":11,"title":9},"929":{"body":51,"breadcrumbs":3,"title":1},"93":{"body":41,"breadcrumbs":4,"title":2},"930":{"body":32,"breadcrumbs":3,"title":1},"931":{"body":112,"breadcrumbs":5,"title":3},"932":{"body":0,"breadcrumbs":3,"title":1},"933":{"body":37,"breadcrumbs":4,"title":2},"934":{"body":34,"breadcrumbs":7,"title":5},"935":{"body":45,"breadcrumbs":8,"title":6},"936":{"body":34,"breadcrumbs":8,"title":6},"937":{"body":101,"breadcrumbs":8,"title":6},"938":{"body":26,"breadcrumbs":4,"title":2},"939":{"body":94,"breadcrumbs":4,"title":2},"94":{"body":10,"breadcrumbs":4,"title":2},"940":{"body":100,"breadcrumbs":3,"title":1},"941":{"body":34,"breadcrumbs":4,"title":2},"942":{"body":92,"breadcrumbs":11,"title":8},"943":{"body":122,"breadcrumbs":5,"title":2},"944":{"body":54,"breadcrumbs":6,"title":3},"945":{"body":23,"breadcrumbs":6,"title":3},"946":{"body":0,"breadcrumbs":4,"title":1},"947":{"body":148,"breadcrumbs":10,"title":7},"948":{"body":95,"breadcrumbs":9,"title":6},"949":{"body":53,"breadcrumbs":9,"title":6},"95":{"body":9,"breadcrumbs":4,"title":2},"950":{"body":138,"breadcrumbs":7,"title":4},"951":{"body":44,"breadcrumbs":7,"title":4},"952":{"body":28,"breadcrumbs":5,"title":2},"953":{"body":96,"breadcrumbs":5,"title":2},"954":{"body":80,"breadcrumbs":4,"title":1},"955":{"body":46,"breadcrumbs":4,"title":1},"956":{"body":90,"breadcrumbs":5,"title":2},"957":{"body":54,"breadcrumbs":11,"title":7},"958":{"body":44,"breadcrumbs":5,"title":1},"959":{"body":127,"breadcrumbs":5,"title":1},"96":{"body":21,"breadcrumbs":4,"title":2},"960":{"body":155,"breadcrumbs":6,"title":2},"961":{"body":33,"breadcrumbs":5,"title":1},"962":{"body":99,"breadcrumbs":9,"title":5},"963":{"body":96,"breadcrumbs":9,"title":5},"964":{"body":55,"breadcrumbs":10,"title":6},"965":{"body":26,"breadcrumbs":7,"title":3},"966":{"body":58,"breadcrumbs":6,"title":2},"967":{"body":132,"breadcrumbs":6,"title":2},"968":{"body":108,"breadcrumbs":5,"title":1},"969":{"body":69,"breadcrumbs":5,"title":1},"97":{"body":141,"breadcrumbs":4,"title":2},"970":{"body":75,"breadcrumbs":6,"title":2},"971":{"body":201,"breadcrumbs":9,"title":6},"972":{"body":75,"breadcrumbs":4,"title":1},"973":{"body":89,"breadcrumbs":4,"title":1},"974":{"body":179,"breadcrumbs":5,"title":2},"975":{"body":0,"breadcrumbs":4,"title":1},"976":{"body":138,"breadcrumbs":10,"title":7},"977":{"body":249,"breadcrumbs":6,"title":3},"978":{"body":32,"breadcrumbs":10,"title":7},"979":{"body":49,"breadcrumbs":4,"title":1},"98":{"body":9,"breadcrumbs":4,"title":2},"980":{"body":140,"breadcrumbs":5,"title":2},"981":{"body":35,"breadcrumbs":5,"title":2},"982":{"body":95,"breadcrumbs":5,"title":2},"983":{"body":69,"breadcrumbs":4,"title":1},"984":{"body":28,"breadcrumbs":4,"title":1},"985":{"body":81,"breadcrumbs":5,"title":2},"986":{"body":65,"breadcrumbs":9,"title":7},"987":{"body":46,"breadcrumbs":3,"title":1},"988":{"body":67,"breadcrumbs":3,"title":1},"989":{"body":58,"breadcrumbs":3,"title":1},"99":{"body":17,"breadcrumbs":4,"title":2},"990":{"body":114,"breadcrumbs":4,"title":2},"991":{"body":0,"breadcrumbs":3,"title":1},"992":{"body":91,"breadcrumbs":3,"title":1},"993":{"body":85,"breadcrumbs":4,"title":2},"994":{"body":38,"breadcrumbs":3,"title":1},"995":{"body":38,"breadcrumbs":3,"title":1},"996":{"body":43,"breadcrumbs":6,"title":4},"997":{"body":31,"breadcrumbs":4,"title":2},"998":{"body":131,"breadcrumbs":4,"title":2},"999":{"body":74,"breadcrumbs":3,"title":1}},"docs":{"0":{"body":"Cross-platform Jellyfin client — business logic in a Rust backend, SvelteKit + TypeScript frontend, talking over Tauri v2 IPC. Requirements Specification — user, integration, and development requirements. Traceability Matrix — generated map from requirements to code (regenerated on every publish). Architecture — backend, frontend, data flow, platform backends. Rust API Reference — rustdoc for the src-tauri backend. This site is published automatically from master by the publish-docs CI job.","breadcrumbs":"Introduction » JellyTau Documentation","id":"0","title":"JellyTau Documentation"},"1":{"body":"This document captures JellyTau's user requirements, software requirements, traceability matrix, test traceability, and known technical debt. For a narrative overview of the system design, see docs/architecture/ . For development workflows, see the README and scripts/README.md .","breadcrumbs":"Requirements Specification » Requirements Specification","id":"1","title":"Requirements Specification"},"10":{"body":"Test ID Test Description Traces To Status UT-001 Player state transitions DR-001 Pending UT-002 MediaItem source URL resolution DR-002, DR-003 Pending UT-003 Queue next/previous navigation DR-005 Pending UT-004 Queue shuffle order generation DR-005 Pending UT-005 Queue repeat mode behavior DR-005 Pending UT-006 Jellyfin authentication flow IR-009 Pending UT-007 Jellyfin library items parsing IR-010 Pending UT-008 Repository pattern online/offline switching DR-013 Pending UT-009 Offline mutation queue persistence DR-014 Pending UT-010 Download queue management DR-015 Done UT-011 Resume position storage and retrieval DR-022 Pending UT-012 Sleep timer countdown logic DR-029 Pending UT-013 Playback progress reporting throttling DR-028 Pending UT-014 Database open and in-memory mode IR-013, DR-012 Done UT-015 Database migrations run successfully IR-013, DR-012 Done UT-016 All database tables created IR-013, DR-012 Done UT-017 FTS5 search table created IR-013, DR-012 Done UT-018 Server CRUD operations IR-013, DR-012 Done UT-019 User CRUD operations IR-013, DR-012 Done UT-020 Cascade delete server removes users IR-013, DR-012 Done UT-021 Item insert and FTS search IR-013, DR-012 Done UT-022 User data playback position storage IR-013, DR-012, DR-022 Done UT-023 Sync queue operations IR-013, DR-014 Done UT-024 Downloads table operations IR-013, DR-015 Done UT-025 Migrations are idempotent IR-013, DR-012 Done UT-026 NullBackend volume default value DR-004 Done UT-027 NullBackend set volume DR-004 Done UT-028 NullBackend volume clamping (high/low) DR-004 Done UT-029 NullBackend volume boundary values DR-004 Done UT-030 PlayerController volume default DR-004, DR-009 Done UT-031 PlayerController set volume DR-004, DR-009 Done UT-032 PlayerController muted default DR-004, DR-009 Done UT-033 PlayerController volume delegates to backend DR-004, DR-009 Done UT-034 Download event serialization roundtrip DR-015 Done UT-035 Download event completed serialization DR-015 Done UT-036 Download event failed serialization DR-015 Done UT-037 Download worker exponential backoff DR-015 Done UT-038 Download worker error retryable check DR-015 Done UT-039 Download manager creation DR-015 Done UT-040 Download manager set max concurrent DR-015 Done UT-041 Download info serialization DR-015 Done UT-042 Download command filename sanitization DR-015, DR-018 Done UT-043 Download command filename extension preservation DR-015, DR-018 Done UT-044 Offline item serialization DR-017 Done UT-045 Smart cache default config DR-015 Done UT-046 Smart cache album affinity tracking DR-015 Done UT-047 Smart cache queue precache config DR-015 Done UT-048 Smart cache storage limit check DR-015 Done UT-049 Playlist create (offline) DR-019, JA-019 Done UT-050 Playlist delete (offline) DR-019, JA-019 Done UT-051 Playlist rename (offline) DR-019, JA-019 Done UT-052 Playlist get items (offline) DR-019, JA-019 Done UT-053 Playlist add items (offline) DR-019, JA-020 Done UT-054 Playlist remove items (offline) DR-019, JA-020 Done UT-055 Playlist reorder items (offline) DR-019, JA-020 Done UT-056 Playlist entry serialization DR-019, JA-019 Done UT-057 Playlist Tauri command param naming (camelCase) DR-019, JA-019, JA-020 Done UT-058 Playlist repository client methods DR-019, JA-019, JA-020 Done UT-059 Audio-only stream URL builder for a video item (selected audio-stream index) JA-032, DR-052 Pending UT-060 Background-audio handoff state machine (background→audio, foreground→video; no dual audio) DR-052 Pending UT-061 Background-audio Tauri command param naming (camelCase) DR-052 Pending UT-062 setBackgroundAudioEnabled reports whether the native bridge was actually reached (missing bridge, stale proxy, throwing method) so a dead bridge cannot look armed UR-040, IR-025, DR-051 Done UT-067 Offline get_items gates the synced-catalog UNION on the catalog-browse flag (downloads only when off, full catalog when on) DR-078 Done UT-068 Catalog visibility resolves to serverReachable || showServerCatalog, and is pushed to the backend on every change of either input DR-078, DR-079 Done UT-069 isConnected follows backend reachability alone: false when the server is unreachable on a live link, true for a reachable server while navigator.onLine is false DR-079 Done UT-070 Hybrid get_items returns an empty offline result as-is when the catalog-browse gate is off, without querying the server DR-080 Done UT-066 WiFi-only download gate: cellular and metered WiFi blocked, unmetered WiFi/Ethernet allowed, unknown/none fail closed, desktop default ungated; plus the frontend network reporter (transport reporting, change subscription, teardown, fail-open queries) DR-074 Done UT-071 Byte-size formatter: zero/negative/non-finite → \"0 B\"; decimal unit thresholds; 2–3 significant-figure banding; trailing-zero trimming; largest-unit cap DR-085 Done UT-072 Downloaded-only browse returns a downloaded leaf and its container, filtered to the requested album parent; a non-downloaded sibling is omitted DR-082, DR-083 Done UT-073 An empty downloaded-only browse is authoritative — no rows, no error — regardless of the catalog-browse flag DR-082 Done UT-074 Only libraries with downloaded content are listed; an empty one is omitted DR-082 Done UT-075 Disk usage reports a leaf's own size, a container's summed descendants, and reconciles the device total with the sum of leaves DR-085 Done UT-076 Downloaded library browse lists album containers, not their individual tracks; drilling into the album returns the tracks DR-082, DR-083 Done UT-077 Downloaded TV library browse lists the series, not seasons/episodes; drilling returns the season then the episode DR-082, DR-083 Done UT-078 A downloaded leaf with no cached container (e.g. a movie) still surfaces at the library level DR-082, DR-083 Done UT-079 Each EQ preset returns a 10-band gain curve within range; Flat is all zeros; Bass Boost lifts lows and leaves highs flat DR-030 Done UT-080 with_equalizer_normalised clamps out-of-range gains and forces the band vector to exactly 10 entries (pad short, truncate long) DR-030 Done UT-081 Old persisted AudioSettings JSON without EQ fields loads as disabled + flat DR-030 Done UT-082 EQ fields serialize as camelCase (equalizerEnabled/equalizerBands) and round-trip DR-030 Done UT-083 EQ filter entries are empty when disabled or when the curve is flat (clears the af filter) IR-020 Done UT-084 Enabled EQ builds one peaking equalizer per non-zero band at the right frequency and gain inside a single lavfi chain IR-020 Done UT-085 A first tap resolves to togglePlayPause immediately — no deferral and no timer DR-092, DR-098 Done UT-086 A second tap inside the window seeks (+30 s right half, −10 s left half) with the matching feedback side and re-toggles play/pause, so the two toggles cancel and the play state is unchanged by a double tap DR-092, DR-098 Done UT-087 A tap after the window, and the tap following a consumed pair, are each fresh first taps that toggle (there is no third-tap case); repeated double taps keep seeking; cancel() makes the next tap a first tap so an interpreted swipe cannot seek DR-092, DR-098 Done UT-088 resolveSeekTarget applies the delta to the reported position, clamps into [0, duration - END_SEEK_MARGIN_SECONDS], chains off an in-flight pending target so rapid skips accumulate, and ignores that target once the player reports past it DR-092, DR-095 Done UT-089 A touch drag on the video seek bar seeks to the dragged position, never toggles play/pause, and never alters brightness — the container gesture layer stays out of a control drag entirely DR-098, DR-099 Done UT-090 The seek bar commits its seek on touchend even when the engine never fires change, and commits exactly once when both signals arrive DR-099 Done UT-091 Transport intents (play/pause/toggle) reach the backend even while a video adapter is registered, and never call the adapter's own play/pause/toggle — the webview must not decide play-vs-pause from the DOM DR-097 Done UT-092 shouldReuseActivePlayback reuses backend playback for an already-loaded audio track but never for video, and never when an explicit start position or a next-episode restart was requested DR-100 Done UT-093 resolvePlayerSurface returns video only with a stream URL, pending for video whose stream URL is still missing (never audio), and audio for audio content DR-100 Done UT-094 parseNativeInsets accepts the bridge's JSON or a decoded object, and coerces missing/negative/non-finite edges to 0 rather than emitting NaNpx (which would invalidate the whole padding declaration) DR-112 Done UT-095 safeAreaCssVars/applySafeAreaInsets emit px-suffixed jt-inset custom properties for all four edges DR-112 Done UT-096 readNativeInsets returns null with no bridge and survives a stale WebView proxy (missing or throwing get) instead of throwing out of layout init IR-031, DR-112 Done UT-097 initSafeArea primes the document on start, re-applies on jellytau-insets-changed (rotation, nav-mode switch), unsubscribes on teardown, and writes nothing without a bridge so env() still wins on iOS/desktop IR-031, DR-112 Done UT-098 shellReservesBottomInset gives the bottom inset to BottomUi wherever one renders and to the app shell only on routes without one, so the gesture bar is never ignored nor double-padded DR-112 Done UT-099 A Jellyfin item payload carrying UserData.IsFavorite maps to MediaItem.user_data.is_favorite DR-113, JA-034 Done UT-100 OnlineRepository::get_favorites builds Filters=IsFavorite + Recursive=true + the scope's IncludeItemTypes, and omits the type filter entirely for SearchScope::All DR-115, JA-033 Done UT-101 OfflineRepository::get_favorites returns only is_favorite = 1 rows, honours the scope type filter, and stays downloads-only when the catalog-browse gate is off DR-115 Done UT-102 The save_to_cache favourite mirror does not overwrite a row with pending_sync = 1 DR-114 Done UT-103 The reconnect drain pushes pending favourites, clears pending_sync, and leaves failed rows pending DR-120 Done UT-104 get_items with favorites_only filters online (endpoint) and offline (SQL) DR-116 Done UT-105 favorites store precedence: override beats userData.isFavorite beats false DR-119 Done UT-106 Un-favouriting removes an item from a favourites listing view DR-117, DR-119 Done UT-107 The hybrid background refresh emits favorites-changed only for ids whose favourite state actually flipped DR-120 Done UT-109 Search covers synced-but-not-downloaded items when catalog browse is on, and stays downloads-only when off DR-108 Done UT-110 Search item-type filter is bound, not interpolated: a quote-bearing type neither errors nor widens results DR-108 Done UT-111 FTS prefix queries quote each token, so apostrophes/hyphens/slashes are data; empty or punctuation-only input returns no rows rather than erroring DR-108 Done UT-112 Repeated catalog passes leave one items_fts entry per item, not one per pass DR-110 Done UT-113 The stale-catalog sweep removes vanished synced rows, keeps downloaded ones, keeps uncrawled types, and stays scoped to one server DR-110 Done UT-114 Cached people are reachable from unscoped search and excluded from scoped search DR-111 Done UT-115 Re-index staleness policy: never-indexed and unparseable timestamps are due, fresh ones are not, future ones are not DR-109 Done UT-116 resolve_local_media_path returns a completed download's file, and None for an in-progress download, a row whose file has been deleted, or an unknown item DR-123 Done UT-118 resolveVideoSource prefers a downloaded file, never marks a local file as needing transcoding, and falls back to streaming for a blank path DR-123 Done UT-119 The audio-only handoff picks a downloaded file over the audio-only stream URL, preserving the Jellyfin id for progress sync DR-128 Done UT-120 Expiry reclaim takes only expired temporary entries: derived from completed_at+TTL, honouring an expires_at override, never a user download, and disabled by a zero TTL DR-127 Done UT-108 LRU eviction reclaims only 'auto' downloads and never a user's own, even when the user's is the oldest DR-126 Done UT-117 A background audio-only stream cut short resumes where it died instead of ending the episode; a real end still advances; the absolute position is compared against the runtime; retries at a stuck position give up. A recoverable error resumes music and video too, with growing backoff, leaving the rest of the queue intact and the seekable stream's URL untouched; local and DirectUrl sources are excluded DR-129 Done UT-124 downloadedFilePath leaves a completed download's absolute path alone (POSIX and Windows) and only roots one that is still relative DR-133 Done UT-125 A NULL media_type resolves from the item type — Movie and Episode as video, a track as audio — an uncached item still defaults to audio, and an explicit media_type overrides the item DR-135 Done UT-126 Requeueing takes only video rows downloaded under the audio default, clearing their URL, and leaves correctly-typed video rows and real audio downloads alone DR-136 Done UT-127 The media server bounds and confines every response: a range-less request yields one chunk rather than the whole file, no range exceeds the chunk cap, explicit/open-ended/suffix ranges resolve correctly, a range past the end is unsatisfiable rather than clamped, a malformed header falls back to the first chunk, path traversal and unrelated absolute paths are refused, a wrong or absent token is rejected, and content type comes from the extension then the magic bytes DR-137 Done UT-121 An EOF reads as the last observed timestamp, not zero: live readings win while the file is loaded, a not-yet-established duration is not recorded as a real zero, a seek updates the position before the next poll, and loading a new file clears the previous one's DR-130 Done UT-122 The sync-queue drain pushes queued playback reports oldest-first, defers failures for the next reconnect, abandons a row after MAX_SYNC_ATTEMPTS, ignores other users' rows, and parses both payload dialects DR-131 Done UT-123 Pending-sync rows describe themselves: every queueable operation has a label, an unknown one still renders, the item title falls back to its id, and rows list oldest-first DR-132 Done UT-130 Video and background-audio stream URLs omit AudioStreamIndex when no track was chosen, and carry the exact index when one was DR-140 Done UT-131 The Episode Focus View hero offers a download control DR-142 Done UT-132 The series name links to the series and the SxEy badge to that season's anchor DR-142 Done UT-133 Cast renders below the \"More Episodes\" strip, never above it DR-062, DR-142 Done UT-134 The episode strip is hidden when the episode has no siblings DR-142 Done UT-135 An episode with no seriesId still renders the Focus View, with title, Play and download DR-142 Done UT-136 episodeRedirectTarget sends a bare episode page into its series' Focus View, and returns null with no series DR-142 Done UT-137 Going offline with the toggle off pushes the closed gate and bumps catalogFilterVersion DR-143 Done UT-138 The version bumps only after set_show_server_catalog resolves, never before DR-143 Done UT-139 A failed visibility push is retried on the next identical transition rather than latched DR-143 Done UT-140 useOfflineFilterReload skips the value a page already loaded under and reloads on each later change DR-143 Done UT-141 The advertised channel cap: an unknown or zero reading falls back to stereo, a real route keeps its channels, an absurd driver reading is capped at 7.1, and mono is taken at its word DR-141 Done UT-148 Forcing a transcode from the client: an undecodable default track forces one, a decodable track does not, the default track decides rather than the first, the first decides when nothing is marked default, and neither an audio-less source nor an unnamed codec is second-guessed DR-149 Done UT-149 createAdapter returns the native adapter only when Rust reports native AND experimentalNativeVideo is on; the flag off forces HTML5 even when Rust says native, and the flag on never promotes a platform Rust reported as HTML5 DR-150 Done UT-150 set-version.sh stamps all four manifests without touching dependency versions, and the Android versionCode is monotonic across an upgrade sequence, clears the 1000 floor, and survives a prerelease suffix DR-153 Done UT-151 An unreportable stop lands in the queue and is pushed by the existing drain; re-queueing the same item supersedes the earlier position rather than adding a row, distinct items keep their own positions, and an abandoned row is not revived by a later report DR-154 Done UT-152 Caching a server result mirrors its watch position locally — including for an item carrying a position but no favourite flag — without inventing a row for an item the server reported no user data for, and without pulling a still-unsynced local position backwards DR-155 Done UT-162 Each downloaded library lists only its own media: the music library shows the album and neither the film nor the series, the movie library only the film, the TV library only the series DR-163 Done UT-163 partial_path appends rather than replacing the extension, so it matches what the cleanup paths delete, keeps two sources for one title apart, and still produces a sidecar for an extension-less target DR-165 Done UT-170 queue_album_tracks queues a row for every track of the album — including tracks the cache holds without an album_id and tracks it has never seen at all — links each one to its album so offline browsing can find it, returns the row ids in track order, and is idempotent: re-queuing fills the gaps without duplicating rows or resetting a completed track. cached_album_tracks (the offline fallback) finds tracks by either album link and does not sweep in another album's DR-173 Done UT-171 resolve_pending_download_urls restricted to a set of row ids resolves only those rows and leaves other pending rows untouched, and an empty id set resolves nothing rather than sweeping everything DR-173 Done UT-172 album_file_names gives every track of an album its own file: a title repeated within the album (deluxe edition, two discs) is disambiguated by track number and item id instead of the second download overwriting the first, an unambiguous title keeps its own name, and path separators in a title are sanitised so a track cannot escape the album directory DR-173 Done UT-164 resume_offset appends only when the server answered 206; a 200 after a Range request restarts the file, because that body is the whole stream DR-166 Done UT-165 A registered download starts unflagged, signal sets the flag its worker reads, signalling an unregistered id reports not-in-flight, clear forgets it, and re-registering drops a previous stop so a resumed download does not halt instantly DR-164 Done UT-166 original quality re-encodes audio the webview cannot decode (E-AC-3/AC-3/DTS/TrueHD) to AAC without capping bitrate or resolution, keeps the Static=true direct copy for audio that plays here (AAC/MP3/Opus/Vorbis/FLAC) and for an unknown codec, leaves the explicit quality presets untouched, and picks the served track by the same default-or-first rule the streaming verdict uses DR-171 Done UT-155 A seek during a background-audio handoff re-opens the stream at the requested absolute position (StartTimeTicks) and rebases the handoff to it, while a seek outside a handoff stays an ordinary seek and invents no base DR-159 Done UT-154 mark_unplayed parses to QueuedOp::MarkUnplayed and is rejected without an item id, and a queued un-mark drains to the server as clear_watch_history DR-158 Done UT-156 A capped step reaches the transcode URL as all four of its parts (total ceiling, the video/audio split summing to the cap, and a MaxHeight), the uncapped default keeps the historical 20/18 Mbps allowance and constrains no resolution, and the background-audio handoff takes the lower of the cap and its own 384 kbps DR-162 Done UT-157 The quality ladder is internally consistent — video + audio equals the cap at every step, audio never consumes the budget, only Original is uncapped — descends in bitrate, resolution and audio share together, and round-trips through the serde token it is persisted as DR-162 Done UT-158 Justified rows fill the container width exactly and never overflow it, every tile in a row shares one height, and each tile's width follows its own aspect ratio — a 16:9 tile coming out more than twice the width of a 2:3 tile at the same height DR-174 Done UT-159 The awkward cases of the packing: a short last row is left at the target height rather than stretched across the container, a last row that would overflow is brought down, an extreme ratio is clamped instead of taking a row to itself, a missing or nonsensical ratio falls back to square instead of collapsing the tile, an unmeasured container renders nothing rather than 1px tiles, and every tile is placed exactly once in order DR-174 Done UT-160 The default row height suits its container: it grows with the width, stays inside its bounds, and at phone width still fits two 16:9 tiles side by side DR-174 Done UT-161 A collection type maps to its favourites scope (movies/tvshows/music), every other kind — Live TV, channels, box sets, books, unknown — maps to none rather than to All, and a constructed library carries the scope across the wire as favoritesScope, omitted entirely when it has none DR-175 Done UT-167 The mosaic's composition: the cross-library favourites entry leads, each library is followed by its own category tile pointing at that category's tab, a category shared by two libraries still yields one tile, a library kind favourites do not carve up yields none, a scope the page offers no tab for is ignored, and every tile is uniquely keyed DR-174, DR-175 Done UT-168 Subtitles are negotiated as sidecars, never burned in: the requested SubtitleStreamIndex is the explicit \"none\" sentinel (-1) rather than omitted, every text format we can render (srt/subrip/ass/ssa/vtt) is advertised as External, and the burn-in verdict is by format — text never forces it, image formats (PGSSUB, dvdsub) always do, case-insensitively. The same sentinel rides the stream URL itself, so a stream re-opened without a fresh negotiation cannot inherit a subtitle. And the verdict reaches the picker: a subtitle stream carries supportsExternalDelivery — set only for subtitles, false for a bitmap format and for one the server left unnamed — which drops the tracks the app could never draw from the menu, the children and the native play request alike, without even fetching their URLs, while a stream carrying no verdict at all is still offered DR-176 Done UT-173 Every video stream URL carries a PlaySessionId, each open mints a fresh one, and the open reports the session it superseded so that job can be stopped DR-177 Done UT-174 A fatal HLS network error is read against the absolute position: mid-film — including after a quality switch, where the seek offset carries the whole resume position — it is retried rather than reported as the end of the stream, the last tenth of a known runtime is treated as the end, an unknown runtime retries, and retries stop once the budget is spent DR-177 Done UT-175 A stream reload that never becomes playable is reported as a failure instead of resolving as success, so the caller can revert its selection rather than leave the UI claiming a stream that is not playing DR-177 Done UT-176 A handoff's position is floored at its base: with no tick yet landed the exit position is the point the screen was locked at rather than 0, and once ticks are flowing (the base already applied natively) it is not added twice DR-178 Done UT-177 Webview-rendered media's reported position and duration are the controller's, and are dropped the moment that element stops being the player — on teardown, and when a handoff takes over DR-178 Done UT-178 A stop report at position 0 is withheld rather than sent (it would clear the resume point), while a real position is still reported from either rendering path — the element's on the webview path, the backend's on the native one DR-179 Done UT-179 An audio-only episode that ends naturally is reported stopped at its runtime, so Jellyfin marks it played; a truncated stream, which is about to be re-opened, reports nothing DR-179 Done UT-180 Position ticks report progress to the server, throttled to one report per item per window rather than one per tick DR-179 Done UT-181 The handoff plan matches its source: a downloaded file takes no base and a seek, a stream takes the base and no seek, and a handoff at 0:00 takes neither; a downloaded handoff's absolute seek stays an ordinary seek instead of a stream rebuild DR-180 Done UT-153 Scroll handling per navigation kind: a forward move always lands at the top even when the previous page was scrolled and even when the target was visited before, Back restores that route's own saved offset (and the top when it has none), offsets are kept per route rather than shared, a repeated Back still restores, and the initial load leaves the container alone DR-156 Done UT-142 The audio codecs offered for video direct play: a Dolby device's real MediaCodecList output drops ac3/eac3, AMR and raw PCM are dropped too, a fully-supported list is passed through untouched, a list with nothing decodable still claims aac, and stray spacing or casing does not decide whether the user gets sound DR-148 Done UT-143 Subtitle URLs resolve to plain strings before they reach the markup (never a Promise), unresolvable tracks are dropped, a stale selection collapses to \"Off\", and a server-default track is never auto-selected UR-020, DR-023 Done UT-144 VideoPlayer actually renders children carrying data-stream-index, with no default attribute and no async getSubtitleUrl() bound to src UR-020, DR-023 Done UT-145 The frontend's subtitle payload survives the IPC hop: a camelCase PlayItemRequest carrying subtitles deserializes, create_media_item lands them on MediaItem.subtitles in the order sent, and a request without the field still defaults to empty UR-020, IR-016 Done UT-146 The subtitle JSON serialized across the JNI boundary uses the keys JellyTauPlayer.load() reads — url, language, label and mime_type, never mimeType UR-020, IR-016, JA-008 Done UT-147 The native subtitle payload and the track-selection index come from the same resolved list: the wire shape keeps mime_type and stream order, playerPlayItem actually sends it, and the index is a position in the sent list (so a track whose URL failed to resolve cannot shift the others) rather than the menu's row number UR-020, IR-016 Done UT-182 An HLS video URL never carries StartTimeTicks — with a position supplied or not — while the master playlist, codec, media source and chosen audio track still ride on it DR-181 Done UT-183 A reloaded stream is resumed by seeking the element to the absolute position with the transcode offset cleared to zero — never by carrying the position as an offset base, which since DR-181 would display the position while playing the item from its start — and a reload to 0:00 waits for no seek DR-181 Done UT-184 The native reveal rule fires on state === \"playing\" and on a position tick carrying a position or a duration, and on nothing else — not buffering, paused, stopped, ended or error, not an empty tick, and not a negative position DR-182 Done UT-188 The control-bar auto-hide rule permits hiding only during uninterrupted playback: it declines while paused, while a seek is in flight, and while a track/subtitle/quality menu is open — asserted against the pure shouldHideControls rule rather than a clock or a DOM DR-189 Done UT-189 On the native path the player never calls player_report_state — driven through the real 10-second progress interval under fake timers, which is the call site that mattered; asserting on a freshly mounted player passes with the guard deleted and guards nothing DR-195 Done UT-187 On the native path the play overlay follows the backend: it clears when the backend resumes after a pause and is raised again when the backend pauses, and the system bars are hidden on player entry rather than only by the fullscreen button DR-186, DR-187 Done UT-186 Every attribute the native-video compositing block in app.css targets is set somewhere in the app — [data-app-shell] in particular — so a selector aimed at nothing fails the suite instead of failing silently on a device DR-185 Done UT-185 Mounted on the native path (backend reports native, opt-in flag on, no element rendered and the backend not stopped), VideoPlayer keeps the poster card up until the backend reports something, drops it on a playing state or a position tick with a duration, and keeps it up through error and stopped DR-182 Done UT-190 build_next_up_endpoint sends EnableResumable=false with the user and limit, and no SeriesId filter when none was requested DR-197, JA-036 Done UT-191 A per-series next-up query keeps SeriesId and the resumable exclusion, and defaults the limit DR-197 Done UT-192 filterInProgressNextUpItems drops an episode present in the resume list, keeps the genuinely unstarted next episode, leaves the rest of the row intact, and is a no-op when nothing is in progress DR-197 Done UT-193 The shipped Tauri security config stays restrictive: csp is set, script-src carries no 'unsafe-inline'/'unsafe-eval'/wildcard, object-src/frame-src are 'none', the directives playback needs (asset scheme, loopback, blob:, ipc:) are present, and the asset-protocol scope covers only the thumbnail cache — never the storage root that holds the database DR-198 Done UT-194 Normal audio (no background-audio handoff) keeps queue advance on both skip buttons DR-201 Done UT-195 In background-audio mode a skip scrubs +30s/-10s instead of advancing the queue — the reported defect DR-201 Done UT-196 Skipping back near the start clamps to zero rather than seeking negative DR-201 Done UT-197 Skipping forward near the end clamps to the duration rather than running past it into an EOF-driven advance DR-201 Done UT-198 An unknown duration still scrubs and still refuses to go negative DR-201 Done UT-199 The screen-wake decision: video playing holds the display, pausing releases it, audio playing never holds it, a webview element going inactive releases even without a pause report, either renderer alone is enough to hold, and teardown drops both DR-202 Done UT-201 The logging facade gates by level: a message below the active level is not emitted at all, one at or above it reaches the sink, changing the level at run time changes what passes without touching the call sites, and a scoped logger tags its output with the subsystem DR-204 Proposed UT-202 Generated traceability-matrix file links resolve from docs/: an emitted href, resolved against the directory traceability.md is written to, points at a file that exists on disk; the visible link text stays repo-root-relative; the #Lnn anchor survives; and a bare repo-root href — the regression that made every link 404 as docs/ — is rejected DR-093 Done UT-203 Library folder exclusion filters by id, not by name: an excluded folder's items are absent from a music query, an item whose title merely contains an excluded folder's name is kept, and clearing the exclusion restores the items DR-209 Proposed UT-204 Thumbnail cache writes stay inside the cache directory: a traversal-style and an absolute item_id both fail to produce a file outside it, a filename made only of already-safe characters is byte-identical to the one the previous code produced, and an odd id still round-trips through get_cached_path DR-210 Done UT-205 Queued download paths cannot escape the download root — traversal, absolute and .. forms are refused — while the four real path shapes the app builds, including the absolute one download_series produces, come back unchanged; and a completed download cannot register a file outside the root DR-211 Done UT-206 The offline item-type filter is bound rather than interpolated (a value containing a quote and OR 1=1 matches nothing instead of disabling the WHERE), build_get_items_endpoint percent-encodes its values while preserving the commas Jellyfin splits on, and volume normalisation clamps out-of-range input and maps NaN to a finite value DR-212 Done UT-200 The stream a player could only restart is refused its retry: the handoff transcode answers yes to player_retry_restarts_stream while music, video and a downloaded episode answer no, and the Kotlin decision starts permissive, flips on a non-resumable load, and is restored by the next ordinary one DR-203 Done UT-207 The hero banner's rotation timer restarts from the moment of a manual change: a swipe 5.5s into a 6s interval waits a further 6s instead of firing the leftover 500ms, repeated restarts never stack timers, and stop() ends rotation DR-038 Done UT-208 The update decision: each numeric version field is compared in order, the installed version is not offered to itself, a leading v is tolerated because that is how the tags are written, a pre-release sorts below the release of the same number so 0.9.2-rc1 is not offered to somebody on 0.9.2, a missing patch field reads as zero rather than NaN, mobile reports link-only while desktop reports install, and absent release notes normalise to null rather than undefined DR-217 Done UT-209 Redaction and forwarding. Rust: every credential shape reduces to [REDACTED] while the host, username and neighbouring parameters survive; redaction is idempotent, leaves ordinary lines alone, does not fire on the word \"token\" in prose, and does not panic on multi-byte input; a server URL keeps only scheme and host and drops an embedded user:pass@; an unparseable level falls back to info rather than failing at startup. Frontend: info and above forward while debug does not, a message the level filter suppressed is not forwarded, a throwing forwarder neither propagates nor prevents the console write, and an Error renders as name and message rather than the {} that JSON.stringify produces DR-218 Done UT-210 Cosmetic-commit detection for release notes: a chore(format), chore(deps) or style subject is skipped when deriving a range's changed files, while fix, feat, ci, docs, a bare chore: and chore(release): are kept; and the word \"format\" appearing later in a subject (\"fix(duration): format times over 24 hours\") does not make a real fix look cosmetic DR-219 Done UT-211 The background decision: a video with the toggle off pauses (the reported defect, where the media service kept playing regardless), a video with it on hands off to audio, music keeps playing whatever the toggle says because it has no picture to lose, picture-in-picture keeps playing in every combination since the window is still visible, and the answer does not vary by renderer DR-224 Done","breadcrumbs":"Requirements Specification » Unit Tests to Software Requirements","id":"10","title":"Unit Tests to Software Requirements"},"100":{"body":"Locations: 2 file(s) File: src/lib/stores/connectivity.ts Line: 10 Context: Unknown File: src-tauri/src/commands/connectivity.rs Line: 3 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-055","id":"100","title":"DR-055"},"1000":{"body":"build.rs provenance emission → // TRACES: | DR-093 BuildInfo / BuildKind / classify_build → // TRACES: | DR-093 get_build_info command → // TRACES: | DR-093 Settings About block → // TRACES: | DR-093 classify_build tests → UT-BUILD-1 Allocate DR-093 in requirements.md (\"Build provenance: git describe and build profile surfaced in-app and in logs\"). Next free DR at time of writing is DR-093.","breadcrumbs":"Build Provenance » TRACES","id":"1000","title":"TRACES"},"1001":{"body":"Do the build.rs + command + logging first; the About UI is the smaller half and the logging alone delivers most of the diagnostic value. The fetch-depth: 0 change is the easiest part to forget and the one that makes CI artifacts useless if missed — it is why that acceptance box is flagged. Weigh it per workflow: test-only jobs do not need it. Do not add a build timestamp \"while you are in there\" — see Out of scope. A parallel Claude session may be active — git diff before \"repairing\" unexpected changes.","breadcrumbs":"Build Provenance » Notes for the implementer","id":"1001","title":"Notes for the implementer"},"1002":{"body":"This document explains the automated build and release process for JellyTau.","breadcrumbs":"Build & Release » Build & Release Workflow","id":"1002","title":"Build & Release Workflow"},"1003":{"body":"The CI/CD pipeline automatically: ✅ Runs all tests (frontend + Rust) ✅ Builds Linux binaries (AppImage + DEB) ✅ Builds Android APK and AAB ✅ Creates releases with artifacts ✅ Tags releases with version numbers","breadcrumbs":"Build & Release » Overview","id":"1003","title":"Overview"},"1004":{"body":"","breadcrumbs":"Build & Release » Workflow Triggers","id":"1004","title":"Workflow Triggers"},"1005":{"body":"When you push a version tag: git tag v1.0.0\ngit push origin v1.0.0 The workflow automatically: Runs tests Builds both platforms Creates a GitHub release with artifacts Tags it as release/prerelease based on version","breadcrumbs":"Build & Release » Automatic Trigger","id":"1005","title":"Automatic Trigger"},"1006":{"body":"In Gitea Actions UI: Go to Actions tab Click Build & Release workflow Click Run workflow Optionally specify a version Workflow runs without creating a release","breadcrumbs":"Build & Release » Manual Trigger","id":"1006","title":"Manual Trigger"},"1007":{"body":"","breadcrumbs":"Build & Release » Version Tagging","id":"1007","title":"Version Tagging"},"1008":{"body":"Version tags follow semantic versioning: v{MAJOR}.{MINOR}.{PATCH} Examples: v1.0.0 - Release version v1.0.0-rc1 - Release candidate (marked as prerelease) v1.0.0-beta - Beta version (marked as prerelease) v0.1.0-alpha - Alpha version (marked as prerelease)","breadcrumbs":"Build & Release » Format","id":"1008","title":"Format"},"1009":{"body":"# Create and push a version tag\ngit tag v1.0.0 -m \"Release version 1.0.0\"\ngit push origin v1.0.0 # Or create from main branch\ngit tag -a v1.0.0 -m \"Release version 1.0.0\" main\ngit push origin v1.0.0","breadcrumbs":"Build & Release » Creating a Release","id":"1009","title":"Creating a Release"},"101":{"body":"Locations: 1 file(s) File: src-tauri/src/commands/download/pinning.rs Line: 3 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-056","id":"101","title":"DR-056"},"1010":{"body":"Versions containing rc, beta, or alpha are marked as prerelease : git tag v1.0.0-rc1 # ⚠️ Prerelease\ngit tag v1.0.0-beta # ⚠️ Prerelease\ngit tag v1.0.0-alpha # ⚠️ Prerelease\ngit tag v1.0.0 # ✅ Full release","breadcrumbs":"Build & Release » Release Status","id":"1010","title":"Release Status"},"1011":{"body":"","breadcrumbs":"Build & Release » Workflow Steps","id":"1011","title":"Workflow Steps"},"1012":{"body":"Runs on all tags and manual triggers: Frontend tests (vitest) Rust tests (cargo test) TypeScript type checking Failure: Stops workflow, no build/release","breadcrumbs":"Build & Release » 1. Test Phase","id":"1012","title":"1. Test Phase"},"1013":{"body":"Runs after tests pass: Installs system dependencies Builds with Tauri Generates: AppImage - Universal Linux binary DEB - Debian/Ubuntu package Output: artifacts/linux/","breadcrumbs":"Build & Release » 2. Build Linux Phase","id":"1013","title":"2. Build Linux Phase"},"1014":{"body":"Runs in parallel with Linux build: Installs Android SDK/NDK Configures Rust for Android targets Builds with Tauri Generates: APK - Android app package (installable) AAB - Android App Bundle (for Play Store) Output: artifacts/android/","breadcrumbs":"Build & Release » 3. Build Android Phase","id":"1014","title":"3. Build Android Phase"},"1015":{"body":"Runs after both builds succeed (only on version tags): Prepares release notes Downloads build artifacts Creates GitHub/Gitea release Uploads all artifacts Tags as prerelease if applicable","breadcrumbs":"Build & Release » 4. Create Release Phase","id":"1015","title":"4. Create Release Phase"},"1016":{"body":"","breadcrumbs":"Build & Release » Artifacts","id":"1016","title":"Artifacts"},"1017":{"body":"AppImage File: jellytau_*.AppImage Size: ~100-150 MB Use: Run directly on any Linux distro Installation: chmod +x JellyTau_*.AppImage\n./JellyTau_*.AppImage DEB Package File: JellyTau_*.deb Size: ~80-120 MB Use: Install on Debian/Ubuntu/similar Installation: sudo dpkg -i JellyTau_*.deb\njellytau Note: the Debian package is named jelly-tau (Tauri kebab-cases productName), while the command stays jellytau. The package declares Replaces/Conflicts/Provides: jellytau, so upgrading from a release built before the rename replaces it rather than installing a second copy. RPM Package File: JellyTau-*.rpm Use: Install on Fedora/openSUSE/similar Installation: sudo rpm -i JellyTau-*.rpm\njellytau","breadcrumbs":"Build & Release » Linux Artifacts","id":"1017","title":"Linux Artifacts"},"1018":{"body":"APK File: jellytau-release.apk Size: ~60-100 MB Use: Direct installation on Android devices Installation: adb install jellytau-release.apk\n# Or sideload via file manager AAB (Android App Bundle) File: jellytau-release.aab Size: ~50-90 MB Use: Upload to Google Play Console Note: Cannot be installed directly; for Play Store distribution","breadcrumbs":"Build & Release » Android Artifacts","id":"1018","title":"Android Artifacts"},"1019":{"body":"Release notes are automatically generated with: Version number Download links Installation instructions System requirements Known issues link Changelog reference","breadcrumbs":"Build & Release » Release Notes","id":"1019","title":"Release Notes"},"102":{"body":"Locations: 1 file(s) File: src-tauri/src/commands/download/smart_cache.rs Line: 3 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-057","id":"102","title":"DR-057"},"1020":{"body":"Platform OS Architecture Format Linux Any x86_64 AppImage, DEB Android 8.0+ arm64, armv7, x86_64 APK, AAB","breadcrumbs":"Build & Release » Build Matrix","id":"1020","title":"Build Matrix"},"1021":{"body":"","breadcrumbs":"Build & Release » Troubleshooting","id":"1021","title":"Troubleshooting"},"1022":{"body":"Check test output in Gitea Actions Run tests locally: bun run test and bun run test:rust Fix failing tests Create new tag with fixed code","breadcrumbs":"Build & Release » Build Fails During Test Phase","id":"1022","title":"Build Fails During Test Phase"},"1023":{"body":"Check system dependencies installed Verify Tauri configuration Check cargo dependencies Clear cache: Delete .cargo and target/ directories","breadcrumbs":"Build & Release » Linux Build Fails","id":"1023","title":"Linux Build Fails"},"1024":{"body":"Check Android SDK/NDK setup Verify Java 17 is installed Check Rust Android targets: rustup target list Clear cache and rebuild","breadcrumbs":"Build & Release » Android Build Fails","id":"1024","title":"Android Build Fails"},"1025":{"body":"Tag must start with v (e.g., v1.0.0) Tests must pass Both builds must succeed Check workflow logs for errors","breadcrumbs":"Build & Release » Release Not Created","id":"1025","title":"Release Not Created"},"1026":{"body":"The workflow uses GitHub Actions SDK but is designed for Gitea. For Gitea-native releases: Workflow creates artifacts Artifacts are available in Actions artifacts Download and manually create Gitea release, or Set up Gitea API integration to auto-publish","breadcrumbs":"Build & Release » GitHub Release vs Gitea","id":"1026","title":"GitHub Release vs Gitea"},"1027":{"body":"","breadcrumbs":"Build & Release » Customization","id":"1027","title":"Customization"},"1028":{"body":"Edit .gitea/workflows/build-release.yml, section Prepare release notes: - name: Prepare release notes id: release_notes run: | # Add your custom release notes format here echo \"Custom notes\" > release_notes.md","breadcrumbs":"Build & Release » Change Release Notes Template","id":"1028","title":"Change Release Notes Template"},"1029":{"body":"To add macOS or Windows builds: Add new build-{platform} job Set appropriate runs-on runner Add platform-specific dependencies Update artifact upload Include in needs: [build-linux, build-android, build-{platform}]","breadcrumbs":"Build & Release » Add New Platforms","id":"1029","title":"Add New Platforms"},"103":{"body":"Locations: 1 file(s) File: src-tauri/src/commands/player/remote.rs Line: 3 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-058","id":"103","title":"DR-058"},"1030":{"body":"Modify Tauri configuration or add targets: - name: Build for Linux run: | # Add target specification bun run tauri build -- --target x86_64-unknown-linux-gnu","breadcrumbs":"Build & Release » Change Build Targets","id":"1030","title":"Change Build Targets"},"1031":{"body":"","breadcrumbs":"Build & Release » Monitoring","id":"1031","title":"Monitoring"},"1032":{"body":"Go to Actions tab in Gitea View Build & Release workflow runs Click specific run to see logs","breadcrumbs":"Build & Release » Check Status","id":"1032","title":"Check Status"},"1033":{"body":"Set up notifications for: Build failures Release creation Tag pushes","breadcrumbs":"Build & Release » Notifications","id":"1033","title":"Notifications"},"1034":{"body":"","breadcrumbs":"Build & Release » Performance","id":"1034","title":"Performance"},"1035":{"body":"Test phase: 5-10 minutes Linux build: 10-15 minutes Android build: 15-20 minutes Total: 30-45 minutes","breadcrumbs":"Build & Release » Build Times (Approximate)","id":"1035","title":"Build Times (Approximate)"},"1036":{"body":"Workflow caches: Rust dependencies (cargo) Bun node_modules Android SDK components","breadcrumbs":"Build & Release » Caching","id":"1036","title":"Caching"},"1037":{"body":"","breadcrumbs":"Build & Release » Security","id":"1037","title":"Security"},"1038":{"body":"The workflow uses: GITHUB_TOKEN - Built-in, no setup needed No credentials needed for Gitea","breadcrumbs":"Build & Release » Secrets","id":"1038","title":"Secrets"},"1039":{"body":"To verify build integrity: Download artifacts Verify signatures (if implemented) Check file hashes Test on target platform","breadcrumbs":"Build & Release » Verification","id":"1039","title":"Verification"},"104":{"body":"Locations: 2 file(s) File: src-tauri/src/commands/playback_mode.rs Line: 3 Context: Unknown File: src-tauri/src/playback_mode/mod.rs Line: 165 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-059","id":"104","title":"DR-059"},"1040":{"body":"","breadcrumbs":"Build & Release » Best Practices","id":"1040","title":"Best Practices"},"1041":{"body":"Follow semantic versioning: v{MAJOR}.{MINOR}.{PATCH} Tag releases in git Update CHANGELOG.md before tagging Include release notes in tag message","breadcrumbs":"Build & Release » Versioning","id":"1041","title":"Versioning"},"1042":{"body":"# Local testing before release\nbun run test # Frontend tests\nbun run test:rust # Rust tests\nbun run check # Type checking\nbun run tauri build # Local build test","breadcrumbs":"Build & Release » Testing Before Release","id":"1042","title":"Testing Before Release"},"1043":{"body":"Update CHANGELOG.md with changes Update README.md with new features Document breaking changes Add migration guide if needed","breadcrumbs":"Build & Release » Documentation","id":"1043","title":"Documentation"},"1044":{"body":"# 1. Update version in relevant files (package.json, Cargo.toml, etc.)\nvim package.json\nvim src-tauri/tauri.conf.json # 2. Update CHANGELOG\nvim CHANGELOG.md # 3. Commit changes\ngit add .\ngit commit -m \"Bump version to v1.0.0\" # 4. Create annotated tag\ngit tag -a v1.0.0 -m \"Release version 1.0.0 Features:\n- Feature 1\n- Feature 2 Fixes:\n- Fix 1\n- Fix 2\" # 5. Push tag to trigger workflow\ngit push origin v1.0.0 # 6. Monitor workflow in Gitea Actions\n# Wait for tests → Linux build → Android build → Release # 7. Download artifacts and test\n# Visit release page and verify downloads","breadcrumbs":"Build & Release » Example Release Workflow","id":"1044","title":"Example Release Workflow"},"1045":{"body":"Tauri Documentation Semantic Versioning GitHub Release Best Practices Android App Bundle AppImage Documentation Last Updated: 2026-02-13","breadcrumbs":"Build & Release » References","id":"1045","title":"References"},"1046":{"body":"Quick reference for creating a JellyTau release.","breadcrumbs":"Release Checklist » Release Checklist","id":"1046","title":"Release Checklist"},"1047":{"body":"Code is on master/main branch All feature branches are merged and tested No failing tests locally: bun run test and bun run test:rust Requirement traceability check passes: bun run traces:json Type checking passes: bun run check","breadcrumbs":"Release Checklist » Pre-Release (1-2 days before)","id":"1047","title":"Pre-Release (1-2 days before)"},"1048":{"body":"Decide on version number (semantic versioning) Example: v1.2.0 (major.minor.patch) Example: v1.0.0-rc1 (release candidate) Example: v1.0.0-beta (beta) Update version in files: # Check these files for version numbers\ncat package.json | grep version\ncat src-tauri/tauri.conf.json | grep version\ncat src-tauri/Cargo.toml | grep version Update CHANGELOG.md: Add section for new version List all features added List all bugs fixed List breaking changes (if any) Add upgrade instructions (if needed) Format: Markdown with clear sections Update README.md: Update any version references Update feature list if applicable Update requirements if changed Commit changes: git add .\ngit commit -m \"Bump version to v1.2.0\"\ngit push origin master","breadcrumbs":"Release Checklist » Update Version (Day before)","id":"1048","title":"Update Version (Day before)"},"1049":{"body":"Run full test suite: bun run test # Frontend tests\nbun run test:rust # Rust tests\nbun run check # Type checking Build locally (optional but recommended): # Test Linux build\nbun run tauri build # Test Android build\nbun run tauri android build No uncommitted changes: git status # Should show clean working directory","breadcrumbs":"Release Checklist » Final Check Before Release","id":"1049","title":"Final Check Before Release"},"105":{"body":"Locations: 1 file(s) File: src-tauri/src/commands/storage/mod.rs Line: 3 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-060","id":"105","title":"DR-060"},"1050":{"body":"# 1. Create annotated tag with release notes\ngit tag -a v1.2.0 -m \"Release version 1.2.0 Features:\n- New feature 1\n- New feature 2 Fixes:\n- Fixed bug 1\n- Fixed bug 2 Improvements:\n- Performance improvement 1\n- UI improvement 1 Breaking Changes:\n- None (or list if applicable) Migration:\n- No action required (or include steps if applicable)\" # 2. Push tag to trigger workflow\ngit push origin v1.2.0 # 3. Monitor in Gitea Actions\n# Go to Actions tab and watch the workflow run","breadcrumbs":"Release Checklist » Release (Tag & Push)","id":"1050","title":"Release (Tag & Push)"},"1051":{"body":"Watch workflow progress in Gitea Actions Monitor for test failures Monitor for build failures Check build logs if any step fails","breadcrumbs":"Release Checklist » During Release (While Workflow Runs)","id":"1051","title":"During Release (While Workflow Runs)"},"1052":{"body":"Download artifacts from release page: JellyTau_*.AppImage (Linux) JellyTau_*.deb (Linux) JellyTau-*.rpm (Linux) jellytau-release.apk (Android) jellytau-release.aab (Android) Basic testing of artifacts: Linux AppImage runs Linux DEB installs and runs Android APK installs (via adb or sideload) Verify release page: Title is correct: \"JellyTau vX.Y.Z\" Release notes are formatted correctly All artifacts are uploaded Release type is correct (prerelease vs release) Verify integrity metadata (DR-216): SHA256SUMS is present, and sha256sum -c SHA256SUMS passes in the directory you downloaded into SBOM files are present (*.cdx.json, frontend-dependencies.txt) Verify the update path (DR-217) — this is the step that catches a broken updater before users hit it, because a bad manifest fails only on their machine: latest.json is live and names this version: curl -s https://gitea.tourolle.paris/dtourolle/jellytau/raw/branch/updater/latest.json | jq .version Both platform entries carry a non-empty signature The .AppImage.tar.gz, its .sig, and the NSIS .sig are among the release assets — the manifest points at them Install the previous release, launch it, and use Settings → Updates: it should offer this version, install it, and relaunch On Android, Settings → Updates offers the releases page rather than an install button (the updater plugin is not compiled for that target) Announce release: Post to relevant channels/communities Update website/docs Tag contributors if applicable","breadcrumbs":"Release Checklist » After Release (Workflow Complete)","id":"1052","title":"After Release (Workflow Complete)"},"1053":{"body":"If critical issues are found after release: # Option 1: Delete tag locally and remotely\ngit tag -d v1.2.0\ngit push origin :refs/tags/v1.2.0 # Option 2: Mark as prerelease in release page\n# Then plan immediate patch release (v1.2.1) # Option 3: Create hotfix branch and release v1.2.1\ngit checkout -b hotfix/v1.2.1\n# Fix issues\ngit commit -m \"Fix critical issue\"\ngit tag v1.2.1\ngit push origin hotfix/v1.2.1 v1.2.1","breadcrumbs":"Release Checklist » Rollback (If Issues Found)","id":"1053","title":"Rollback (If Issues Found)"},"1054":{"body":"","breadcrumbs":"Release Checklist » Version Examples","id":"1054","title":"Version Examples"},"1055":{"body":"v2.0.0 - Major version bump\n- Significant new features\n- Breaking API changes\n- Major UI redesign","breadcrumbs":"Release Checklist » Major Release","id":"1055","title":"Major Release"},"1056":{"body":"v1.2.0 - Feature release\n- New features\n- Backward compatible\n- Bug fixes","breadcrumbs":"Release Checklist » Minor Release","id":"1056","title":"Minor Release"},"1057":{"body":"v1.1.1 - Bug fix/patch\n- Bug fixes only\n- No new features\n- Backward compatible","breadcrumbs":"Release Checklist » Patch Release","id":"1057","title":"Patch Release"},"1058":{"body":"v1.2.0-alpha - Early development\nv1.2.0-beta - Late development, feature complete\nv1.2.0-rc1 - Release candidate, minimal fixes only","breadcrumbs":"Release Checklist » Pre-releases","id":"1058","title":"Pre-releases"},"1059":{"body":"Key files for versioning: package.json - Frontend version src-tauri/tauri.conf.json - Tauri config version src-tauri/Cargo.toml - Rust version CHANGELOG.md - Release history README.md - Project documentation","breadcrumbs":"Release Checklist » File Locations","id":"1059","title":"File Locations"},"106":{"body":"Locations: 2 file(s) File: src/lib/components/library/EpisodeFocusView.svelte Line: 7 Context: Unknown File: src/lib/components/library/EpisodeFocusView.svelte Line: 289 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-061","id":"106","title":"DR-061"},"1060":{"body":"","breadcrumbs":"Release Checklist » Troubleshooting","id":"1060","title":"Troubleshooting"},"1061":{"body":"Don't push tag yet Fix failing tests locally Push fixes to master Re-run test suite Then tag and push","breadcrumbs":"Release Checklist » Tests Fail Before Release","id":"1061","title":"Tests Fail Before Release"},"1062":{"body":"Check detailed logs in Gitea Actions Fix issue locally Delete tag: git tag -d v1.2.0 && git push origin :refs/tags/v1.2.0 Push fix to master Create new tag with fix","breadcrumbs":"Release Checklist » Build Fails in CI","id":"1062","title":"Build Fails in CI"},"1063":{"body":"If workflow runs twice, artifacts may conflict Check release page If duplicates exist, delete and re-release","breadcrumbs":"Release Checklist » Release Already Exists","id":"1063","title":"Release Already Exists"},"1064":{"body":"Check build logs for errors Verify platform-specific dependencies Delete tag and retry after fixes","breadcrumbs":"Release Checklist » Artifacts Missing","id":"1064","title":"Artifacts Missing"},"1065":{"body":"Tests: ~5-10 minutes Linux build: ~10-15 minutes Android build: ~15-20 minutes Total release time: ~30-45 minutes First build takes longer (cache warming). Subsequent releases are faster due to caching.","breadcrumbs":"Release Checklist » Performance Tips","id":"1065","title":"Performance Tips"},"1066":{"body":"## 🎉 JellyTau vX.Y.Z ### ✨ Features\n- New feature 1\n- New feature 2 ### 🐛 Bug Fixes\n- Fixed issue #123\n- Fixed issue #456 ### 🚀 Performance\n- Improvement 1\n- Improvement 2 ### 📱 Downloads\n- [Linux AppImage](#) - Run on any Linux\n- [Linux DEB](#) - Install on Ubuntu/Debian\n- [Android APK](#) - Install on Android devices\n- [Android AAB](#) - For Google Play Store ### 📋 Requirements\n**Linux:** 64-bit, GLIBC 2.29+\n**Android:** 8.0+ ### 🔗 Links\n- [Changelog](https://gitea.tourolle.paris/dtourolle/jellytau/src/branch/master/CHANGELOG.md)\n- [Issues](https://gitea.tourolle.paris/dtourolle/jellytau/issues) ---\nBuilt with Tauri, SvelteKit, and Rust 🦀","breadcrumbs":"Release Checklist » Template: Release Notes","id":"1066","title":"Template: Release Notes"},"1067":{"body":"# View existing tags\ngit tag -l # Create release locally (dry run)\ngit tag -a v1.2.0 -m \"Release v1.2.0\" --dry-run # List commits since last tag\ngit log v1.1.0..HEAD --oneline # Show tag details\ngit show v1.2.0 # Rename tag (if needed)\ngit tag v1.2.0_old v1.2.0\ngit tag -d v1.2.0\ngit push origin v1.2.0_old v1.2.0 # Delete tag locally and remotely\ngit tag -d v1.2.0\ngit push origin :refs/tags/v1.2.0 Tips: ✅ Always test locally before release ✅ Use semantic versioning consistently ✅ Document changes in CHANGELOG ✅ Wait for full workflow completion ✅ Test release artifacts before announcing Remember: A good release is a tested release! 🚀","breadcrumbs":"Release Checklist » Quick Commands","id":"1067","title":"Quick Commands"},"1068":{"body":"How to produce distributable desktop packages for JellyTau. All three flows can run in Docker so no host toolchain setup is required. Outputs land in ./dist.","breadcrumbs":"Desktop Packaging » Desktop packaging (Linux, Arch, Windows)","id":"1068","title":"Desktop packaging (Linux, Arch, Windows)"},"1069":{"body":"The deb/rpm and Windows-cross flows build on the unified registry builder ( ../Dockerfile.builder → gitea.tourolle.paris/dtourolle/jellytau-builder), the same image CI uses. It carries every packaging tool: Android SDK/NDK, rpm/file (Linux bundler), cargo-xwin + lld + llvm + nsis + the x86_64-pc-windows-msvc rust target (Windows). There is one dependency source of truth — no per-stage tool installs. The desktop stages in ../Dockerfile are thin FROM ${BUILDER_IMAGE} environments; the actual build runs at container-run time on your bind-mounted source (like the dev service), so source edits need no image rebuild. If you changed Dockerfile.builder (e.g. added a tool), rebuild and push it first, or the packaging flows use the stale registry image: scripts/build-builder-image.sh # build + push :latest to the registry\n# ...or iterate locally without pushing:\ndocker build -f Dockerfile.builder -t jellytau-builder:latest .\nBUILDER_IMAGE=jellytau-builder:latest bun run docker:build:windows Arch uses a separate archlinux image ( ../Dockerfile.arch ) because makepkg is Arch-specific — it is not part of the unified builder. Target Format Docker command Functional? Debian/Ubuntu, Fedora .deb, .rpm bun run docker:build:linux ✅ yes Arch Linux .pkg.tar.zst bun run docker:build:arch ✅ yes Windows NSIS installer + .exe bun run docker:build:windows ✅ yes (unsigned)","breadcrumbs":"Desktop Packaging » One builder image (shared with CI)","id":"1069","title":"One builder image (shared with CI)"},"107":{"body":"Locations: 6 file(s) File: src/lib/components/library/EpisodeFocusView.svelte Line: 7 Context: Unknown File: src/lib/components/library/EpisodeFocusView.svelte Line: 289 Context: Unknown File: src/lib/components/library/EpisodeFocusView.svelte Line: 394 Context: Unknown File: src/lib/components/library/episodeStrip.ts Line: 8 Context: Unknown File: src/lib/components/library/EpisodeFocusView.test.ts Line: 7 Context: Unknown File: src/routes/library/[id]/+page.svelte Line: 1 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-062","id":"107","title":"DR-062"},"1070":{"body":"Tauri's bundler produces these natively. The build runs on the existing Ubuntu builder image ( ../Dockerfile , desktop-linux-build stage): bun run docker:build:linux # deb + rpm -> ./dist\n# or, on a host with the Tauri Linux deps installed:\nBUNDLES=\"deb,rpm\" scripts/build-desktop-linux.sh Runtime dependency: the app links libmpv (audio) and WebKitGTK (webview + HTML5 transcoded video). The deb/rpm declare these. Note: appimage is also a valid Tauri target if you want a portable bundle — add it to BUNDLES.","breadcrumbs":"Desktop Packaging » Linux: deb + rpm","id":"1070","title":"Linux: deb + rpm"},"1071":{"body":"Tauri has no pacman bundle target (as of tauri-cli 2.9.x — valid targets are deb/rpm/appimage/msi/nsis/app/dmg). So we ship a hand-written PKGBUILD in ../packaging/arch/PKGBUILD and build it with makepkg on an Arch base image ( ../Dockerfile.arch ): bun run docker:build:arch # .pkg.tar.zst -> ./dist The PKGBUILD is AUR-ready: swap its source=() for a release tarball/VCS URL to publish. Runtime deps: webkit2gtk-4.1, mpv, gtk3, libayatana-appindicator. makepkg refuses to run as root, so the Docker stage builds as a non-root builder user. Because the image COPYs the source at build time, the arch-build compose service does not bind-mount the repo — rebuild the image to pick up source changes.","breadcrumbs":"Desktop Packaging » Arch Linux: pacman package","id":"1071","title":"Arch Linux: pacman package"},"1072":{"body":"Produces a working (unsigned) NSIS installer + .exe via the official Tauri cross-compile path — the x86_64-pc-windows-msvc target driven by cargo-xwin. Video plays via WebView2 and audio via the webview backend. See build-windows.md for the full explanation. bun run docker:build:windows # NSIS installer + .exe -> ./dist\nWIN_BUNDLES=none bun run docker:build:windows # exe only, skip bundling The Docker windows-cross stage is a thin layer over the builder, which carries cargo-xwin + lld + llvm + nsis + the x86_64-pc-windows-msvc target. Cross-compilation is Tauri's \"last resort\" path (less tested than building on Windows); a windows-latest CI job is the fallback if it misbehaves.","breadcrumbs":"Desktop Packaging » Windows: NSIS installer cross-compiled from Linux","id":"1072","title":"Windows: NSIS installer cross-compiled from Linux"},"1073":{"body":"JellyTau targets Linux and Android primarily, but a working Windows build — including an NSIS installer cross-compiled from Linux — is produced by the Docker tooling. It is not yet a first-class release target (no code signing / CI job / SMTC lockscreen), but it runs and plays media.","breadcrumbs":"Windows Build » Windows build","id":"1073","title":"Windows build"},"1074":{"body":"Video — renders through the webview HTML5 element (hls.js) on every platform; on Windows that is WebView2 (Chromium/Edge), which plays HLS + h264 fine. No Windows-specific code. Audio-only (music) — the native audio backends are libmpv (Linux) and ExoPlayer (Android); neither exists on Windows. Instead create_player_backend() in ../src-tauri/src/lib.rs uses WebviewAudioBackend on non-Linux/non-Android targets: it hands the stream URL to a webview element (see ../src/lib/services/webviewAudio.ts ), which reports state back through the same player_report_* round-trip the video path uses. Pure Rust + Tauri events.","breadcrumbs":"Windows Build » How playback works on Windows","id":"1074","title":"How playback works on Windows"},"1075":{"body":"We use the official Tauri cross-compile path : the MSVC target (x86_64-pc-windows-msvc) driven by cargo-xwin , which downloads the MSVC CRT / Windows SDK headers and links with lld. MSVC is the target Tauri officially supports for Windows (mingw/GNU is not), and — unlike GNU — it lets the Tauri CLI bundle the NSIS installer from a Linux host . Why not mingw/GNU? The GNU target does link a valid .exe, but the Tauri CLI gates --bundles by the host OS unless it recognizes a real Windows build. --runner cargo-xwin --target x86_64-pc-windows-msvc is what flips it into Windows mode and enables the nsis/msi bundlers on Linux. The builder image ( ../Dockerfile.builder ) bakes in the whole toolchain: the x86_64-pc-windows-msvc rust target, cargo-xwin, lld, llvm, and nsis. bun run docker:build:windows # NSIS installer + .exe -> ./dist\nWIN_BUNDLES=none bun run docker:build:windows # exe only, skip bundling Or directly on a host that has the toolchain: scripts/build-windows-cross.sh # nsis installer + exe\nWIN_BUNDLES=none scripts/build-windows-cross.sh # exe only Under the hood the build runs: tauri build --runner cargo-xwin --target x86_64-pc-windows-msvc --bundles nsis Outputs: .exe — src-tauri/target/x86_64-pc-windows-msvc/release/jellytau.exe NSIS installer — .../release/bundle/nsis/*-setup.exe (both copied to ./dist when OUTPUT_DIR is set).","breadcrumbs":"Windows Build » Cross-compiling from Linux (MSVC + cargo-xwin)","id":"1075","title":"Cross-compiling from Linux (MSVC + cargo-xwin)"},"1076":{"body":"Cross-compilation is a last resort per Tauri's own docs — it's less tested than building on Windows. If it misbehaves, a windows-latest CI job or a Windows VM building natively (tauri build --bundles nsis) is the fallback. Code signing is not wired up — the installer is unsigned, so Windows SmartScreen will warn on first run.","breadcrumbs":"Windows Build » Caveats","id":"1076","title":"Caveats"},"1077":{"body":"Gapless/crossfade + SMTC (lockscreen) — currently no-ops in the webview audio path. Downloaded (Local source) file playback needs convertFileSrc on the frontend; streaming works today. Code signing + a Windows packaging CI job.","breadcrumbs":"Windows Build » Outstanding for a first-class Windows release","id":"1077","title":"Outstanding for a first-class Windows release"},"1078":{"body":"For each fixed defect, the releases it was actually present in. Companion to CHANGELOG.md , which says what changed; this says how long each fault had been shipping before it did. \"Present since\" is the first release containing the defective code, not the first release where a user could hit it — those differ, sometimes by months, and the gap is called out where it matters. \"How dated\" records the evidence, so a row can be re-checked or disputed: Method Meaning pickaxe git log -S on the defective token — the commit that introduced the exact string, then the earliest tag containing it. Strongest evidence. feature The defect is inseparable from a feature that landed whole (bad rung in a new algorithm, missing caller in new plumbing), dated to that feature's release. absence The fix adds something that was never there. Dated to when the surrounding code was built, since there is no introducing commit to find. Weakest — treat as \"no later than\".","breadcrumbs":"Defect Windows » Defect windows — which bugs were present when","id":"1078","title":"Defect windows — which bugs were present when"},"1079":{"body":"Nine defects date to the initial proof of concept (v0.0.1, 2026-06-23) and shipped for between two weeks and seven weeks short of two months before anyone hit them. That is the dominant pattern here: not regressions, but original assumptions that went unexercised until a later feature leaned on them. Defect Present since Fixed in Shipped broken for How dated AudioStreamIndex=0 pinned the video stream as the audio track (DR-140) v0.0.1 v0.4.6 ~7 weeks pickaxe Download URL spelled videoBitrate, which Jellyfin does not bind (DR-123) v0.0.1 v0.5.1 ~7 weeks pickaxe pause_download / resume_download were no-ops (DR-168) v0.0.1 v0.5.3 ~7.5 weeks pickaxe .part sidecar named by with_extension, so no cleanup path matched it (DR-169) v0.0.1 v0.5.3 ~7.5 weeks pickaxe Range sent on every retry regardless of the response (DR-170) v0.0.1 v0.5.3 ~7.5 weeks pickaxe /Items/Latest requested with the default GroupItems=false v0.0.1 v0.5.1 ~7 weeks pickaxe SubtitleStreamIndex omitted from PlaybackInfo, letting the server burn in (DR-176) v0.0.1 v0.5.5 ~8 weeks pickaxe No PlaySessionId, and one hardcoded DeviceId, on every stream URL (DR-177) v0.0.1 v0.5.5 ~8 weeks pickaxe download_item never recorded media_type; NULL read as 'audio' (DR-135) v0.0.1 v0.4.6 ~7 weeks pickaxe download_album read its track list from the local cache (DR-173) v0.0.1 v0.5.5 ~8 weeks pickaxe Device profile carried no MaxAudioChannels (DR-141) v0.0.1 v0.4.6 ~7 weeks absence Streaming ceiling fixed at 20 Mbps with no way to lower it (UR-074) v0.0.1 v0.5.3 (as a feature) ~7.5 weeks pickaxe Hero banner auto-rotation never restarted after a manual swipe (DR-038) v0.0.1 v0.9.1 ~8.5 weeks pickaxe","breadcrumbs":"Defect Windows » Present since the first release","id":"1079","title":"Present since the first release"},"108":{"body":"Locations: 17 file(s) File: src/lib/api/bindings.ts Line: 1598 Context: Unknown File: src/lib/api/bindings.ts Line: 3110 Context: export type SearchResult = { items: MediaItem[]; totalRecordCount: num... File: src/lib/components/search/HeaderSearch.svelte Line: 12 Context: Unknown File: src/lib/utils/searchScope.ts Line: 9 Context: Unknown File: src/lib/utils/searchScope.ts Line: 36 Context: Unknown File: src/lib/utils/searchScope.ts Line: 61 Context: Unknown File: src/lib/utils/searchScope.ts Line: 91 Context: Unknown File: src/lib/utils/searchScope.ts Line: 125 Context: Unknown File: src/lib/utils/layoutShell.ts Line: 90 Context: Unknown File: src/lib/stores/library.ts Line: 239 Context: Unknown File: src/routes/library/+page.svelte Line: 26 Context: Unknown File: src/routes/search/+page.svelte Line: 21 Context: Unknown File: src/routes/search/+page.svelte Line: 34 Context: Unknown File: src-tauri/src/commands/repository.rs Line: 494 Context: pub struct SearchUpdateEvent { File: src-tauri/src/repository/types.rs Line: 355 Context: Unknown File: src-tauri/src/repository/types.rs Line: 373 Context: impl SearchScope { File: src-tauri/src/repository/types.rs Line: 439 Context: impl SearchOptions {","breadcrumbs":"Traceability Matrix » DR-063","id":"108","title":"DR-063"},"1080":{"body":"Four of these were latent until a later feature exercised them , which is why the fix lands so far from the cause: The videoBitrate casing was harmless while every download was original. It became visible only once a quality picker existed to select against — and then produced no error, just a full-size file, because Jellyfin discards an unbound query key silently. The unconditional Range header was inert for the same reason: original is the one rung served with a Content-Length and real byte-range support. It started corrupting files in v0.5.1 , the moment the casing fix made transcoded downloads actually transcode. So the code dates to v0.0.1 and the corruption to v0.5.1 — a one-release window for the visible symptom. The missing PlaySessionId only bites when a stream is re-opened for the same item. Nothing re-opened one until quality switching, transcoded seek and audio-track switching existed. The omitted SubtitleStreamIndex only bites on sources whose own default subtitle track is image-based, since that is what forces the server from sidecar to burn-in. Two were masked by soft failure : the asset protocol being disabled (DR-134) was hidden by the thumbnail cache falling back to the server copy whenever the server was reachable, and AudioStreamIndex=0 was hidden by servers that silently correct an out-of-range index — which is exactly why it was reported as \" some videos have no audio\" rather than as a bug in the client.","breadcrumbs":"Defect Windows » Why they took so long to surface","id":"1080","title":"Why they took so long to surface"},"1081":{"body":"Defect Present since Fixed in How dated Native-path resume position never applied (both layers assumed the other seeked) v0.0.9/v0.0.10 v0.5.1 feature (PlayerAdapter contract) get_downloaded_items matched \"this library exists\" rather than constraining the item to it (DR-167) v0.0.17 v0.5.3 feature (browsable downloaded library) SCOPE_ITEM_TYPES — the frontend/backend boundary leak (DR-063) v0.0.17 v0.2.1 pickaxe check:boundary anchored to the query site, blind to a named const (DR-094) v0.0.17 v0.2.1 feature (tripwire landed with the leak it missed) Coverage gate divided by hardcoded denominators, reporting 158% (DR-093) v0.0.1 v0.2.1 pickaxe Tap deferral raced the WebView's synthesized click (DR-092 → DR-098) v0.1.5 v0.2.7 feature (the deferral itself) Transport for webview media decided from el.paused in the DOM (DR-097) v0.0.9/v0.0.10 v0.2.7 feature (Html5PlayerAdapter) pick_current_episode rung 3 returned the first gap , not the furthest watched v0.3.0 v0.5.1 feature mirror_user_data mirrored is_favorite alone and returned early (DR-155) v0.4.0 v0.5.1 pickaxe Stop-report path never fed the sync queue that existed for it (DR-154) v0.4.6 v0.5.1 feature (queue + drain landed with no producer) Background-audio base applied in two display-only places (DR-159) v0.2.9 v0.5.3 pickaxe Positions reported as 0 before the first tick, and always 0 for webview media (DR-178/179/180) v0.5.3 v0.5.5 feature (DR-159's tick boundary) Length-less handoff transcode left to the player's own load-error retry, which can only restart it (DR-203) v0.0.16 v0.8.2 feature (the handoff's progressive-mp3 choice) Three of these are worth separating out, because the defect is not a mistake in the code so much as plumbing that was built and never connected : repository_get_next_up_episodes accepted a series_id from the day it was written, and no caller passed one until v0.3.0. The sync queue and its drain were built, tested and running in v0.4.6 with neither of its two would-be producers ever called. Both halves of the watched-state backend existed with no caller before v0.5.3. An automated check cannot see any of these — the code is present, tested and reachable in principle. Only tracing a requirement to a call site catches it.","breadcrumbs":"Defect Windows » Introduced by a feature, fixed later","id":"1081","title":"Introduced by a feature, fixed later"},"1082":{"body":"Defect Present since Fixed in Note experimentalNativeVideo defaulted on, shipping audio with a blank screen (DR-161 → DR-172) v0.5.3 v0.5.4 One release. The decode path was fine; the compositing step never ran. Webview-shaped audio profile insufficient — server ignores a profile's audio codec (DR-149) v0.4.7 v0.4.8 The v0.4.7 fix for DR-148 was necessary and not sufficient. Android versionCode floor went stale (minor*100 yielding less than the 5002 already in the field) v0.5.0 v0.5.3 Caught before a broken APK shipped; no released build was un-installable. Subtitle sidecar work reverted by a commit assembled from a stale tree v0.5.5 v0.5.5 Never released broken — both commits are in v0.5.5.","breadcrumbs":"Defect Windows » Short windows (one release or less)","id":"1082","title":"Short windows (one release or less)"},"1083":{"body":"Autoplay time reset (v0.0.2). Two commit objects carry this identical change: dcf08f30 (merged via Gitea PR #3, tagged v0.0.2) and fa7cb6e9 (the local original). Both have the same parent 674c8e5c and the same diff. A merge chain pulled fa7cb6e9 and its follow-up 1e599627 into master's history during v0.5.5, so git log v0.5.4..v0.5.5 lists an autoplay fix that changed no file in that release — nextEpisodeService.ts is byte-identical across the tag boundary. The fix shipped in v0.0.2 and has not regressed. This is the one case where reading the changelog off git log subjects would have produced a false entry, and it is a good argument for the project's practice of deriving release notes from TRACES rather than commit subjects.","breadcrumbs":"Defect Windows » Fixed twice / never actually broken","id":"1083","title":"Fixed twice / never actually broken"},"1084":{"body":"Four causes account for most of the table: An omitted parameter is not a neutral default. SubtitleStreamIndex, AudioStreamIndex, GroupItems and MaxAudioChannels all had a server-side default that was actively wrong, and in three of the four the server's choice was more expensive than the one intended — burn-in forcing a full re-encode being the extreme case. Silent binding failures. videoBitRate produced no error, no warning and a plausible-looking file. So did an unbound Range, and so did the coverage gate dividing by a stale denominator. Two layers each assuming the other acts. Native resume (adapter recorded the position, backend never seeked), end-of-playback dispatch (two paths, one unreachable), and the surface/attach split in v0.5.0's native video. A guard keyed on state that moves. The tap deferral keyed suppression on a timer handle the callback had already cleared; the HTML5 toggle keyed play-vs-pause on el.paused, which flips while buffering.","breadcrumbs":"Defect Windows » Recurring shapes","id":"1084","title":"Recurring shapes"},"1085":{"body":"The pickaxe rows can be re-derived directly: git log --oneline --reverse -S'' -- src-tauri/src # introducing commit\ngit tag --contains | sort -V | head -1 # first release with it Blaming the lines a fix removed (git blame at the fix's parent) is faster to run across many commits but was not used for the rows above: it reliably lands on whichever commit last touched the adjacent lines, which is usually not the commit that introduced the defect. It was used only to shortlist candidates.","breadcrumbs":"Defect Windows » Reproducing this","id":"1085","title":"Reproducing this"},"1086":{"body":"This document explains how to use the Docker configuration and Gitea Act CI/CD pipeline for building and testing JellyTau.","breadcrumbs":"Docker » Docker & CI/CD Setup for JellyTau","id":"1086","title":"Docker & CI/CD Setup for JellyTau"},"1087":{"body":"The setup includes: Dockerfile.builder : Pre-built image with all dependencies (push to your registry) Dockerfile : Multi-stage build for local testing and building docker-compose.yml : Orchestration for local development and testing .gitea/workflows/build-and-test.yml : Automated CI/CD pipeline using pre-built builder image","breadcrumbs":"Docker » Overview","id":"1087","title":"Overview"},"1088":{"body":"For CI/CD (Gitea Actions) : Build and push builder image (see build-builder-image.md ) Push to master branch - workflow runs automatically Check Actions tab for results and APK artifacts For Local Testing : docker-compose run test # Run tests\ndocker-compose run android-build # Build APK\ndocker-compose run dev # Interactive shell","breadcrumbs":"Docker » Quick Start","id":"1088","title":"Quick Start"},"1089":{"body":"","breadcrumbs":"Docker » Docker Usage","id":"1089","title":"Docker Usage"},"109":{"body":"Locations: 5 file(s) File: src/lib/components/search/HeaderSearch.svelte Line: 12 Context: Unknown File: src/lib/components/search/SearchScopeChips.svelte Line: 7 Context: Unknown File: src/lib/utils/searchScope.ts Line: 125 Context: Unknown File: src/routes/search/+page.svelte Line: 34 Context: Unknown File: src/routes/search/+page.svelte Line: 62 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-064","id":"109","title":"DR-064"},"1090":{"body":"Docker Engine 20.10+ Docker Compose 2.0+ (if using docker-compose) At least 10GB free disk space (for Android SDK and build artifacts)","breadcrumbs":"Docker » Prerequisites","id":"1090","title":"Prerequisites"},"1091":{"body":"# Build the complete image\ndocker build -t jellytau:latest . # Build specific target\ndocker build -t jellytau:test --target test .\ndocker build -t jellytau:android --target android-build .","breadcrumbs":"Docker » Building the Docker Image","id":"1091","title":"Building the Docker Image"},"1092":{"body":"Run Tests Only docker-compose run test This will: Install all dependencies Run frontend tests (Vitest) Run Rust backend tests Report results Build Android APK docker-compose run android-build This will: Run tests first (depends on test service) If tests pass, build the Android APK Output APK files to src-tauri/gen/android/app/build/outputs/apk/ Interactive Development docker-compose run dev This starts an interactive shell with all development tools available. From here you can: bun install\nbun run build\nbun test\nbun run tauri android build --apk true Run All Services in Sequence docker-compose up --abort-on-container-exit","breadcrumbs":"Docker » Using Docker Compose","id":"1092","title":"Using Docker Compose"},"1093":{"body":"After a successful build, APK files are located in: src-tauri/gen/android/app/build/outputs/apk/ Copy to your host machine: docker cp jellytau-android-build:/app/src-tauri/gen/android/app/build/outputs/apk ./apk-output","breadcrumbs":"Docker » Extracting Build Artifacts","id":"1093","title":"Extracting Build Artifacts"},"1094":{"body":"The .gitea/workflows/build-and-test.yml workflow automates: Single Job : Runs on every push to master and PRs Uses pre-built builder image (no setup time) Installs project dependencies Runs frontend tests (Vitest) Runs Rust backend tests Builds the frontend Builds the Android APK Uploads APK as artifact (30-day retention) The workflow skips markdown files to avoid unnecessary builds.","breadcrumbs":"Docker » Gitea Act CI/CD Pipeline","id":"1094","title":"Gitea Act CI/CD Pipeline"},"1095":{"body":"The workflow runs on: Push to master or main branches Pull requests to master or main branches Can be extended with: workflow_dispatch for manual triggers","breadcrumbs":"Docker » Workflow Triggers","id":"1095","title":"Workflow Triggers"},"1096":{"body":"Before using the CI/CD pipeline, you must build and push the builder image: # Build the image\ndocker build -f Dockerfile.builder -t jellytau-builder:latest . # Tag for your registry\ndocker tag jellytau-builder:latest gitea.tourolle.paris/dtourolle/jellytau-builder:latest # Push to registry\ndocker push gitea.tourolle.paris/dtourolle/jellytau-builder:latest See build-builder-image.md for detailed instructions.","breadcrumbs":"Docker » Setting Up the Builder Image","id":"1096","title":"Setting Up the Builder Image"},"1097":{"body":"Ensure builder image is pushed (see above) Push to Gitea repository : The workflow will automatically trigger on push to master or pull requests View workflow runs in Gitea UI : Navigate to your repository Go to Actions tab Click on workflow runs to see logs Test locally (optional): # Install act if needed\ncurl https://gitea.com/actions/setup-act/releases/download/v0.25.0/act-0.25.0-linux-x86_64.tar.gz | tar xz # Run locally (requires builder image to be available)\n./act push --file .gitea/workflows/build-and-test.yml","breadcrumbs":"Docker » Setting Up Gitea Act","id":"1097","title":"Setting Up Gitea Act"},"1098":{"body":"Modify Build Triggers Edit .gitea/workflows/build-and-test.yml to change when builds run: on: push: branches: - master - develop # Add more branches paths: - 'src/**' # Only run if src/ changes - 'src-tauri/**' # Only run if Rust code changes Add Notifications Add Slack, Discord, or email notifications on build completion: - name: Notify on success if: success() run: | curl -X POST https://slack-webhook-url... Customize APK Upload Modify artifact retention or add to cloud storage: - name: Upload APK to S3 uses: actions/s3-sync@v1 with: aws_access_key_id: ${{ secrets.AWS_ACCESS_KEY }} aws_secret_access_key: ${{ secrets.AWS_SECRET_KEY }} aws_bucket: my-apk-bucket source_dir: src-tauri/gen/android/app/build/outputs/apk/","breadcrumbs":"Docker » Customizing the Workflow","id":"1098","title":"Customizing the Workflow"},"1099":{"body":"","breadcrumbs":"Docker » Environment Setup in CI","id":"1099","title":"Environment Setup in CI"},"11":{"body":"Test ID Test Description Traces To Status IT-001 End-to-end authentication with Jellyfin server IR-009, UR-009 Pending IT-002 Library browsing and item loading IR-010, UR-007 Pending IT-003 Audio playback via libmpv IR-003, UR-004 Pending IT-004 Video playback via libmpv IR-003, UR-003 Pending IT-005 MPRIS lockscreen controls on Linux IR-005, UR-006 Pending IT-006 Offline mode with local database IR-013, UR-002 Pending IT-007 Media download and local playback DR-015, UR-011 Pending IT-008 Subtitle track selection on the video backends (ExoPlayer sideloaded tracks; HTML5 children) — not via libmpv, which does not implement it IR-018, UR-020 Pending IT-009 Audio track selection on the video backends (ExoPlayer track switch; HTML5 stream re-open at the chosen AudioStreamIndex) — not via libmpv, which does not implement it IR-019, UR-021 Pending IT-010 Playback progress sync to Jellyfin IR-015, UR-025 Pending IT-011 Resume playback from server position IR-015, UR-019 Pending IT-012 Equalizer bands via libmpv IR-020, UR-027 Pending IT-013 Background-audio handoff on Android: background/lock continues audio via native service and stops video decode; foreground resumes video at position IR-025, UR-040 Pending IT-016 Offline library listing end-to-end: with the server unreachable, a library page lists only downloaded media with the toggle off, and additionally reveals greyed-out cached catalog entries with the toggle on UR-052, DR-078, DR-079, DR-080 Done IT-017 A download queued from a greyed-out offline catalog entry persists and is resolved and started on reconnect UR-052, UR-011 Done","breadcrumbs":"Requirements Specification » Integration Tests","id":"11","title":"Integration Tests"},"110":{"body":"Locations: 2 file(s) File: src/lib/stores/librarySearchScope.test.ts Line: 4 Context: Unknown File: src/lib/stores/library.ts Line: 239 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-065","id":"110","title":"DR-065"},"1100":{"body":"To use secrets in the workflow, set them in Gitea: Go to Repository Settings → Secrets Add secrets like: AWS_ACCESS_KEY for S3 uploads SLACK_WEBHOOK_URL for notifications GITHUB_TOKEN for releases (pre-configured)","breadcrumbs":"Docker » Secret Variables","id":"1100","title":"Secret Variables"},"1101":{"body":"","breadcrumbs":"Docker » Troubleshooting","id":"1101","title":"Troubleshooting"},"1102":{"body":"Android builds are memory-intensive. If you get OOM errors: # Limit memory in docker-compose\nservices: android-build: deploy: resources: limits: memory: 6G Or increase Docker's memory allocation in Docker Desktop settings.","breadcrumbs":"Docker » Out of Memory During Build","id":"1102","title":"Out of Memory During Build"},"1103":{"body":"If downloads timeout, increase timeout or download manually: # In container, with longer timeout\ntimeout 600 sdkmanager --sdk_root=$ANDROID_HOME ...","breadcrumbs":"Docker » Android SDK Download Timeout","id":"1103","title":"Android SDK Download Timeout"},"1104":{"body":"Make sure Rust is updated: rustup update\nrustup target add aarch64-linux-android armv7-linux-androideabi x86_64-linux-android","breadcrumbs":"Docker » Rust Compilation Errors","id":"1104","title":"Rust Compilation Errors"},"1105":{"body":"Clear Docker cache and rebuild: docker-compose down -v # Remove volumes\ndocker system prune # Clean up dangling images\ndocker-compose up --build","breadcrumbs":"Docker » Cache Issues","id":"1105","title":"Cache Issues"},"1106":{"body":"Cache Reuse : Both Docker and Gitea Act cache dependencies across runs Parallel Steps : The workflow runs frontend and Rust tests in series; consider parallelizing for faster CI Incremental Builds : Rust and Node caches persist between runs Docker Buildkit : Enable for faster builds: DOCKER_BUILDKIT=1 docker build .","breadcrumbs":"Docker » Performance Tips","id":"1106","title":"Performance Tips"},"1107":{"body":"Dockerfile uses ubuntu:24.04 base image from official Docker Hub NDK is downloaded from official Google servers (verified via HTTPS) No credentials are stored in the Dockerfile Use Gitea Secrets for sensitive values (API keys, tokens, etc.) Lock dependency versions in Cargo.toml and package.json","breadcrumbs":"Docker » Security Considerations","id":"1107","title":"Security Considerations"},"1108":{"body":"Test locally with docker-compose up Push to your Gitea repository Monitor workflow runs in the Actions tab Configure secrets in repository settings for production builds Set up artifact retention policies (currently 30 days)","breadcrumbs":"Docker » Next Steps","id":"1108","title":"Next Steps"},"1109":{"body":"Gitea Actions Documentation Docker Multi-stage Builds Android Build Tools Tauri Android Guide","breadcrumbs":"Docker » References","id":"1109","title":"References"},"111":{"body":"Locations: 5 file(s) File: src/lib/components/settings/SearchGroupOrderList.svelte Line: 8 Context: Unknown File: src/lib/utils/searchScope.ts Line: 9 Context: Unknown File: src/lib/utils/searchScope.ts Line: 234 Context: Unknown File: src/lib/stores/searchGroupOrder.test.ts Line: 4 Context: Unknown File: src/lib/stores/searchGroupOrder.ts Line: 6 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-066","id":"111","title":"DR-066"},"1110":{"body":"This document explains how to create and push the pre-built builder Docker image to your registry for use in Gitea Act CI/CD.","breadcrumbs":"Builder Image » Building and Pushing the JellyTau Builder Image","id":"1110","title":"Building and Pushing the JellyTau Builder Image"},"1111":{"body":"Docker installed and running Access to your Docker registry (e.g., gitea.tourolle.paris) Docker registry credentials configured (docker login)","breadcrumbs":"Builder Image » Prerequisites","id":"1111","title":"Prerequisites"},"1112":{"body":"","breadcrumbs":"Builder Image » Building the Builder Image","id":"1112","title":"Building the Builder Image"},"1113":{"body":"# From the project root\ndocker build -f Dockerfile.builder -t jellytau-builder:latest . This creates a local image with: All system dependencies Rust with Android targets Android SDK and NDK Node.js and Bun All build tools pre-installed","breadcrumbs":"Builder Image » Step 1: Build the Image Locally","id":"1113","title":"Step 1: Build the Image Locally"},"1114":{"body":"Replace gitea.tourolle.paris/dtourolle with your actual registry path: docker tag jellytau-builder:latest gitea.tourolle.paris/dtourolle/jellytau-builder:latest","breadcrumbs":"Builder Image » Step 2: Tag for Your Registry","id":"1114","title":"Step 2: Tag for Your Registry"},"1115":{"body":"If not already logged in: docker login gitea.tourolle.paris","breadcrumbs":"Builder Image » Step 3: Login to Your Registry","id":"1115","title":"Step 3: Login to Your Registry"},"1116":{"body":"docker push gitea.tourolle.paris/dtourolle/jellytau-builder:latest","breadcrumbs":"Builder Image » Step 4: Push to Registry","id":"1116","title":"Step 4: Push to Registry"},"1117":{"body":"docker build -f Dockerfile.builder -t jellytau-builder:latest . && \\\ndocker tag jellytau-builder:latest gitea.tourolle.paris/dtourolle/jellytau-builder:latest && \\\ndocker push gitea.tourolle.paris/dtourolle/jellytau-builder:latest","breadcrumbs":"Builder Image » Complete One-Liner","id":"1117","title":"Complete One-Liner"},"1118":{"body":"Check that the image was pushed successfully: # List images in your registry (depends on registry API support)\ndocker search gitea.tourolle.paris/dtourolle/jellytau-builder # Or pull and test locally\ndocker pull gitea.tourolle.paris/dtourolle/jellytau-builder:latest\ndocker run -it gitea.tourolle.paris/dtourolle/jellytau-builder:latest bun --version","breadcrumbs":"Builder Image » Verifying the Build","id":"1118","title":"Verifying the Build"},"1119":{"body":"The workflow at .gitea/workflows/build-and-test.yml automatically uses: container: image: gitea.tourolle.paris/dtourolle/jellytau-builder:latest Once pushed, your CI/CD pipeline will use this pre-built image instead of installing everything during the build, saving significant time.","breadcrumbs":"Builder Image » Using in CI/CD","id":"1119","title":"Using in CI/CD"},"112":{"body":"Locations: 3 file(s) File: src/lib/components/search/SearchResults.svelte Line: 9 Context: Unknown File: src/lib/utils/searchScope.ts Line: 9 Context: Unknown File: src/lib/utils/searchScope.ts Line: 284 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-067","id":"112","title":"DR-067"},"1120":{"body":"When dependencies change (new Rust version, Android SDK update, etc.): Update Dockerfile.builder with the new configuration Rebuild and push with a new tag: docker build -f Dockerfile.builder -t jellytau-builder:v1.2.0 .\ndocker tag jellytau-builder:v1.2.0 gitea.tourolle.paris/dtourolle/jellytau-builder:v1.2.0\ndocker push gitea.tourolle.paris/dtourolle/jellytau-builder:v1.2.0 Update the workflow to use the new tag: container: image: gitea.tourolle.paris/dtourolle/jellytau-builder:v1.2.0","breadcrumbs":"Builder Image » Updating the Builder Image","id":"1120","title":"Updating the Builder Image"},"1121":{"body":"The builder image includes: Base OS : Ubuntu 24.04 Languages : Rust (stable) with targets: aarch64-linux-android, armv7-linux-androideabi, x86_64-linux-android Node.js 20.x OpenJDK 17 (for Android) Tools : Bun package manager Android SDK 34 Android NDK 27.0.11902837 Build essentials (gcc, make, etc.) Git, curl, wget libssl, libclang development libraries Pre-configured : Rust toolchain components (rustfmt, clippy) Android SDK/NDK environment variables All paths optimized for building","breadcrumbs":"Builder Image » Image Contents","id":"1121","title":"Image Contents"},"1122":{"body":"First build takes ~15-20 minutes depending on internet speed (downloads Android SDK/NDK). Subsequent builds are cached and take seconds.","breadcrumbs":"Builder Image » Build Time","id":"1122","title":"Build Time"},"1123":{"body":"The built image is approximately 4-5 GB . Ensure your registry has sufficient storage.","breadcrumbs":"Builder Image » Storage","id":"1123","title":"Storage"},"1124":{"body":"","breadcrumbs":"Builder Image » Troubleshooting","id":"1124","title":"Troubleshooting"},"1125":{"body":"Verify the image name matches exactly in the workflow Check that the image was successfully pushed: docker push output should show successful layers Ensure Gitea has access to your registry (check network/firewall)","breadcrumbs":"Builder Image » \"Image not found\" in CI","id":"1125","title":"\"Image not found\" in CI"},"1126":{"body":"The image may not have finished pushing. Wait a few moments and retry the CI job. Check that all layers were pushed successfully in the push output.","breadcrumbs":"Builder Image » Build fails with \"command not found\"","id":"1126","title":"Build fails with \"command not found\""},"1127":{"body":"If your registry requires credentials in CI: Create a deploy token in your registry Add to Gitea secrets as REGISTRY_USERNAME and REGISTRY_TOKEN Use in workflow: - name: Login to Registry run: | docker login gitea.tourolle.paris -u ${{ secrets.REGISTRY_USERNAME }} -p ${{ secrets.REGISTRY_TOKEN }}","breadcrumbs":"Builder Image » Registry authentication in CI","id":"1127","title":"Registry authentication in CI"},"1128":{"body":"Docker Build Documentation Docker Push Documentation Dockerfile Reference","breadcrumbs":"Builder Image » References","id":"1128","title":"References"},"1129":{"body":"The full backend API reference is generated by cargo doc (rustdoc). 👉 Open the Rust API Reference","breadcrumbs":"Rust API Reference (rustdoc) » Rust API Reference","id":"1129","title":"Rust API Reference"},"113":{"body":"Locations: 1 file(s) File: src/lib/components/library/MediaCard.svelte Line: 1 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-068","id":"113","title":"DR-068"},"114":{"body":"Locations: 1 file(s) File: src/lib/components/library/LibraryGrid.svelte Line: 1 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-069","id":"114","title":"DR-069"},"115":{"body":"Locations: 1 file(s) File: src/lib/components/library/LibraryGrid.svelte Line: 1 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-070","id":"115","title":"DR-070"},"116":{"body":"Locations: 17 file(s) File: src/lib/api/bindings.ts Line: 1192 Context: Unknown File: src/lib/api/bindings.ts Line: 1203 Context: Unknown File: src/lib/api/bindings.ts Line: 2489 Context: export type MergedMediaItem = { id: string; title: string; artist: str... File: src/lib/api/bindings.ts Line: 2498 Context: export type NetworkStateWrapperArg = { networkType: NetworkType; unmet... File: src/lib/services/networkType.ts Line: 10 Context: Unknown File: src/lib/services/networkType.test.ts Line: 4 Context: Unknown File: src/lib/stores/downloads.ts Line: 84 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 31 Context: pub struct DownloadManagerWrapper(pub Mutex); File: src-tauri/src/commands/download/mod.rs Line: 40 Context: pub struct NetworkStateWrapper(pub NetworkStateHandle); File: src-tauri/src/commands/download/mod.rs Line: 83 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 96 Context: pub struct NetworkStateWrapperArg { File: src-tauri/src/commands/download/mod.rs Line: 1744 Context: Unknown File: src-tauri/src/download/network.rs Line: 22 Context: Unknown File: src-tauri/src/download/network.rs Line: 42 Context: Unknown File: src-tauri/src/download/network.rs Line: 70 Context: impl NetworkState { File: src-tauri/src/download/events.rs Line: 47 Context: Unknown File: src-tauri/src/download/cache.rs Line: 86 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-074","id":"116","title":"DR-074"},"117":{"body":"Locations: 1 file(s) File: src/lib/components/account/AccountMenu.svelte Line: 7 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-075","id":"117","title":"DR-075"},"118":{"body":"Locations: 2 file(s) File: src/lib/components/AppHeader.svelte Line: 10 Context: Unknown File: src/lib/utils/layoutShell.ts Line: 75 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-076","id":"118","title":"DR-076"},"119":{"body":"Locations: 2 file(s) File: src/lib/stores/viewMode.test.ts Line: 7 Context: Unknown File: src/routes/settings/+page.svelte Line: 1 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-077","id":"119","title":"DR-077"},"12":{"body":"","breadcrumbs":"Requirements Specification » 5. Technical Debt","id":"12","title":"5. Technical Debt"},"120":{"body":"Locations: 7 file(s) File: src/lib/components/library/MediaCard.svelte Line: 1 Context: Unknown File: src/lib/services/offlineCatalog.reload.test.ts Line: 11 Context: Unknown File: src/lib/services/offlineCatalog.ts Line: 14 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 3 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 24 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 43 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 3026 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-078","id":"120","title":"DR-078"},"121":{"body":"Locations: 2 file(s) File: src/lib/stores/connectivity.ts Line: 10 Context: Unknown File: src/lib/stores/connectivity.ts Line: 250 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-079","id":"121","title":"DR-079"},"122":{"body":"Locations: 2 file(s) File: src-tauri/src/repository/hybrid.rs Line: 8 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 43 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-080","id":"122","title":"DR-080"},"123":{"body":"Locations: 2 file(s) File: src/lib/components/downloads/DownloadedBrowse.svelte Line: 10 Context: Unknown File: src/routes/downloads/+page.svelte Line: 14 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-081","id":"123","title":"DR-081"},"124":{"body":"Locations: 21 file(s) File: src/lib/api/repository-client.ts Line: 100 Context: Unknown File: src/lib/api/repository-client.ts Line: 108 Context: Unknown File: src/lib/api/bindings.ts Line: 1473 Context: Unknown File: src/lib/api/bindings.ts Line: 1481 Context: Unknown File: src/lib/components/downloads/DownloadedBrowse.svelte Line: 10 Context: Unknown File: src/lib/services/downloadedCatalog.ts Line: 13 Context: Unknown File: src/routes/downloads/+page.svelte Line: 14 Context: Unknown File: src-tauri/src/commands/repository.rs Line: 222 Context: Unknown File: src-tauri/src/commands/repository.rs Line: 237 Context: Unknown File: src-tauri/src/repository/hybrid.rs Line: 256 Context: Unknown File: src-tauri/src/repository/hybrid.rs Line: 271 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 848 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 857 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 884 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 990 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 3705 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 3727 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 3837 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 3883 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 3904 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 3923 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-082","id":"124","title":"DR-082"},"125":{"body":"Locations: 19 file(s) File: src/lib/api/repository-client.ts Line: 108 Context: Unknown File: src/lib/api/bindings.ts Line: 991 Context: Unknown File: src/lib/api/bindings.ts Line: 1481 Context: Unknown File: src/lib/components/library/MediaCard.svelte Line: 29 Context: Unknown File: src/lib/components/library/MediaCard.svelte Line: 34 Context: Unknown File: src/lib/components/downloads/DownloadedBrowse.svelte Line: 10 Context: Unknown File: src/lib/services/downloadedCatalog.ts Line: 13 Context: Unknown File: src/lib/services/downloadedCatalog.ts Line: 96 Context: function sizeOf(itemId: string): number { File: src/routes/downloads/+page.svelte Line: 14 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 2425 Context: Unknown File: src-tauri/src/commands/repository.rs Line: 237 Context: Unknown File: src-tauri/src/repository/hybrid.rs Line: 256 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 857 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 884 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 3705 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 3727 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 3837 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 3883 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 3984 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-083","id":"125","title":"DR-083"},"126":{"body":"Locations: 2 file(s) File: src/lib/stores/downloads.test.ts Line: 316 Context: Unknown File: src/routes/downloads/+page.svelte Line: 14 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-084","id":"126","title":"DR-084"},"127":{"body":"Locations: 15 file(s) File: src/lib/api/repository-client.ts Line: 116 Context: Unknown File: src/lib/api/bindings.ts Line: 1489 Context: Unknown File: src/lib/api/bindings.ts Line: 2140 Context: Unknown File: src/lib/components/library/LibraryGrid.svelte Line: 17 Context: Unknown File: src/lib/components/library/MediaCard.svelte Line: 23 Context: Unknown File: src/lib/components/downloads/DownloadedBrowse.svelte Line: 10 Context: Unknown File: src/lib/services/downloadedCatalog.ts Line: 13 Context: Unknown File: src/lib/utils/formatBytes.ts Line: 8 Context: Unknown File: src/lib/utils/formatBytes.test.ts Line: 4 Context: Unknown File: src/routes/downloads/+page.svelte Line: 14 Context: Unknown File: src-tauri/src/commands/repository.rs Line: 254 Context: Unknown File: src-tauri/src/repository/hybrid.rs Line: 278 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 1036 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 3945 Context: Unknown File: src-tauri/src/repository/types.rs Line: 303 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-085","id":"127","title":"DR-085"},"128":{"body":"Locations: 1 file(s) File: src/routes/settings/+page.svelte Line: 1 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-086","id":"128","title":"DR-086"},"129":{"body":"Locations: 3 file(s) File: src/lib/components/library/MediaCard.svelte Line: 42 Context: Unknown File: src/routes/+page.svelte Line: 74 Context: Unknown File: src/routes/+page.svelte Line: 98 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-087","id":"129","title":"DR-087"},"13":{"body":"The 2026-08-16 audit (v0.6.0, commit be907b49) was a point-in-time snapshot with no status markers, and by v0.8.2 most of it had been either fixed or overtaken. It was retired rather than left to rot into a document that half-describes the code: what survived it is the table below, which is now the record. Each row is self-contained — the audit is not needed to act on it. What was dropped as demonstrably closed, so it is not re-raised: the CSP and asset-protocol scope findings (now DR-198), cloud backup and credential restore, the WebView mixed-content override (DR-199), POST_NOTIFICATIONS and the media-session exemption (DR-200), the jvmTarget 1.8 pin (now 17), the half-declared Android TV leanback category (removed), the untraced-but-Done requirements and the contradictory UR/IR statuses (re-scoped in §2.1), the 50% traceability gate (ratcheted, and gated on a live denominator by DR-093), the flaky offlineCatalog test, the clippy warning backlog (cleared, and cargo fmt --check plus clippy now run in CI), and the \"820 production unwrap()s\" figure — a measurement error that counted test modules, corrected in the audit itself to ~19 and standing at 27 today, none of them in a command handler. The three Runtime::new().unwrap() sites that genuinely matter survive as row 5. Ordered by what would hurt most if left. Closed 2026-08-17: the R8-minified release APK was validated on device. That was the last item gating confidence in the v0.8.0 release itself; R8 stripping JNI-loaded classes has broken release builds here before, and v0.8.0 added a new Kotlin path (onFastForward/onRewind) that the unminified debug pass did not cover. # Item Why it matters Size 1 Android 16 Local Network Protections The rare platform change that could stop the app working at all: JellyTau's core function is reaching a Jellyfin server that, for most users, is on the LAN. Opt-in for testing in Android 16, enforcement signalled for a later release — so nothing is broken today and no device test will surface it. Far cheaper to handle before it is mandatory. An Android 16 device is already to hand to test the opt-in flag against M 2 The traceability matrix cannot see Kotlin scripts/extract-traces.ts walks only src, src-tauri/src and scripts, so every TRACES: comment in src-tauri/android/** is invisible — pre-existing ones included. A whole platform is unmeasured, which is plausibly why the Android IRs sat untagged for so long, and it means the 90% coverage figure is computed over a codebase that excludes the Android tree S 3 Delete the asset protocol outright It is not narrowly used, it is unused . getCachedImageUrl has no production callers (only its own test file), so convertFileSrc never executes; images arrive as base64 data: URIs from image_get_url. Confirmed on device: zero asset.localhost requests across a full browsing session. Dropping protocol-asset and the assetProtocol block retires the surface instead of shrinking it, and imageCache.ts goes with it S 4 Tighten img-src The v0.8.0 CSP grants img-src … http: https: on the premise that thumbnails are fetched direct-from-server by the webview. They are not (see #3). With no webview-side server image loads anywhere in src/, 'self' data: blob: should suffice. Needs its own device pass — a wrong img-src blanks every image, silently S 5 Three Runtime::new().unwrap() in playback-critical threads session_poller/mod.rs:102, player/mpv_backend.rs:424, player/android/mod.rs:761. A panic strands the app offline with nothing surfaced, freezes the scrubber mid-playback, or kills progress reporting across a JNI boundary. One shared helper returning Option and logging on failure retires all three. (The wider \"820 unwraps\" figure was a measurement error — the real count is 19, and none are in command handlers) S 6 Confirm the playback service rejects unknown callers JellyTauPlaybackService is exported=\"true\" with a MediaSessionService intent filter — conventional for Media3, but it means any app on the device can attempt to bind and drive playback. The session's onConnect should reject unknown packages. (Predictive back, raised alongside this, was verified working on device and needs nothing) S 7 Media3 is several minor versions behind Pinned at 1.5.0 across exoplayer/hls/session/common. Much of this app's hard-won behaviour lives in ExoPlayer edge cases — truncated progressive streams, background-audio handoff, HLS resume — so its bug-fix releases have unusually high value here. Schedule with a device pass over the playback regression list M 8 Shipped desktop bundles have no update path deb/rpm/nsis are built but tauri-plugin-updater is absent, so every desktop user upgrades by manually fetching a package — in practice a long tail of installs pinned to whatever they first downloaded. Add the updater with a signed manifest, or document the manual path so the omission is deliberate M 9 DR-042 overstates what ships It promises \"poster cards, year, and rating badges \", but MediaCard.svelte renders only productionYear; CommunityRating/OfficialRating appear solely as sort keys, never as a badge. Either build the badge or correct the requirement text — a requirement that describes unbuilt behaviour is worse than an untraced one S 10 Stray duplicate JellyTauPlayer.kt A copy exists at src-tauri/android/app/src/main/java/.../player/JellyTauPlayer.kt, outside the canonical src-tauri/android/src tree that sync-android-sources.sh reads. Two files with one name in a tree with a strict canonical-source rule is a trap for the next edit S 11 Six modules carry a disproportionate share of the complexity src-tauri/src/player/mod.rs (4,732 lines), src-tauri/src/repository/offline.rs (4,705), src-tauri/src/repository/online.rs (3,760), src-tauri/src/commands/player/mod.rs (3,327), src-tauri/src/commands/download/mod.rs (3,238) and src/lib/components/player/VideoPlayer.svelte (2,786) — all still growing. The cost is not the line count itself, it is that these are the same modules CLAUDE.md's Gotchas section keeps having to warn about : the deadlock rule about locking in event callbacks, the AutoplayDecision scrutinee, the \"no lifecycle calls after an await in onMount\" rule, the HLS master.m3u8 rule, the download concurrency cap. A file that needs a standing warning in the project's onboarding document is a file whose invariants are no longer local to it, and every such warning is a rule a newcomer has to be told rather than one the structure enforces. Recorded, not scheduled — a speculative refactor of six files this size buys nothing on its own. The trigger is the next time one of them needs substantial work: splitting it then is likely cheaper than growing it, and each rule that moves from Gotchas into a module boundary is one fewer thing to remember L","breadcrumbs":"Requirements Specification » Open items carried over from the v0.6.0 codebase audit","id":"13","title":"Open items carried over from the v0.6.0 codebase audit"},"130":{"body":"Locations: 2 file(s) File: src/lib/services/skipReporting.test.ts Line: 9 Context: Unknown File: src/lib/services/skipReporting.ts Line: 14 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-088","id":"130","title":"DR-088"},"131":{"body":"Locations: 4 file(s) File: src/lib/stores/continueWatchingFilter.test.ts Line: 12 Context: Unknown File: src/lib/stores/home.ts Line: 2 Context: Unknown File: src/lib/stores/continueWatchingFilter.ts Line: 15 Context: Unknown File: src/lib/stores/tv.ts Line: 3 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-089","id":"131","title":"DR-089"},"132":{"body":"Locations: 1 file(s) File: src-tauri/src/domain/search_rank.rs Line: 106 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-090","id":"132","title":"DR-090"},"133":{"body":"Locations: 2 file(s) File: src/lib/utils/searchScope.ts Line: 149 Context: export type SearchGroupId = File: src/lib/utils/searchScope.ts Line: 215 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-091","id":"133","title":"DR-091"},"134":{"body":"Locations: 2 file(s) File: src/lib/components/player/tapGestures.ts Line: 21 Context: Unknown File: src/lib/components/player/VideoPlayer.svelte Line: 1 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-092","id":"134","title":"DR-092"},"135":{"body":"Locations: 9 file(s) File: scripts/extract-traces.ts Line: 57 Context: Unknown File: scripts/extract-traces.ts Line: 309 Context: Unknown File: scripts/extract-traces.ts Line: 342 Context: Unknown File: scripts/extract-traces.ts Line: 385 Context: Unknown File: scripts/extract-traces.ts Line: 407 Context: Unknown File: scripts/extract-traces.ts Line: 444 Context: Unknown File: scripts/extract-traces.ts Line: 454 Context: export const MATRIX_LINK_PREFIX = \"../\"; File: scripts/extract-traces.ts Line: 537 Context: function generateJson(data: TracesData): string { File: scripts/extract-traces.ts Line: 590 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-093","id":"135","title":"DR-093"},"136":{"body":"Locations: 1 file(s) File: src/lib/components/player/tapGestures.ts Line: 21 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-095","id":"136","title":"DR-095"},"137":{"body":"Locations: 1 file(s) File: src/lib/player/adapters/html5Adapter.ts Line: 19 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-096","id":"137","title":"DR-096"},"138":{"body":"Locations: 9 file(s) File: src/lib/player/transport.authority.test.ts Line: 4 Context: Unknown File: src/lib/player/index.ts Line: 15 Context: Unknown File: src/lib/services/playerEvents.ts Line: 8 Context: Unknown File: src-tauri/src/player/mod.rs Line: 271 Context: Unknown File: src-tauri/src/player/mod.rs Line: 652 Context: Unknown File: src-tauri/src/player/mod.rs Line: 660 Context: pub fn is_html5_active(&self) -> bool { File: src-tauri/src/player/mod.rs Line: 704 Context: Unknown File: src-tauri/src/player/mod.rs Line: 1586 Context: Unknown File: src-tauri/src/player/mod.rs Line: 1602 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-097","id":"138","title":"DR-097"},"139":{"body":"Locations: 4 file(s) File: src/lib/components/player/VideoPlayer.tapSurface.test.ts Line: 5 Context: Unknown File: src/lib/components/player/VideoPlayer.touchScrub.test.ts Line: 21 Context: Unknown File: src/lib/components/player/tapGestures.ts Line: 21 Context: Unknown File: src/lib/components/player/VideoPlayer.svelte Line: 1 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-098","id":"139","title":"DR-098"},"14":{"body":"Issue : The keyring-rs crate (v3.x) has issues with retrieving credentials from the Linux Secret Service API, despite successfully saving them. Symptoms : Credentials are saved to the system keyring successfully (verified with secret-tool search) Retrieval via the keyring-rs library fails with NoEntry error Session restoration fails on app restart even though credentials exist Root Cause : The keyring-rs library's Linux backend doesn't correctly retrieve entries from the Secret Service that it previously stored. This appears to be a bug in how the library interfaces with the Secret Service D-Bus API. Current Workaround : We bypass the keyring-rs library on Linux and use direct system calls to secret-tool: Save : secret-tool store --label service username Retrieve : secret-tool lookup service username Delete : secret-tool clear service username Implementation : See src-tauri/src/credentials.rs for the Linux-specific secret-tool save/get/delete paths. Future Fix : Monitor keyring-rs for bug fixes in future versions Consider alternative secure storage libraries Test if newer versions of keyring-rs (v4.x+) resolve the issue Once fixed, remove the Linux-specific workaround and use the cross-platform keyring-rs API Impact : Low - The workaround is functionally equivalent to proper keyring integration Credentials are stored securely in the system keyring Session restoration works correctly Only affects Linux; macOS and Windows use the standard keyring-rs implementation Dependencies : Requires secret-tool to be installed on Linux systems (part of libsecret-tools package) Already available on most Linux distributions by default","breadcrumbs":"Requirements Specification » Linux Keyring Integration Workaround","id":"14","title":"Linux Keyring Integration Workaround"},"140":{"body":"Locations: 2 file(s) File: src/lib/components/player/VideoPlayer.touchScrub.test.ts Line: 21 Context: Unknown File: src/lib/components/player/VideoPlayer.svelte Line: 1 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-099","id":"140","title":"DR-099"},"141":{"body":"Locations: 2 file(s) File: src/lib/components/player/playerSurface.test.ts Line: 13 Context: Unknown File: src/lib/components/player/playerSurface.ts Line: 8 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-100","id":"141","title":"DR-100"},"142":{"body":"Locations: 8 file(s) File: src/lib/api/repository-client.ts Line: 151 Context: Unknown File: src/lib/api/repository-client.ts Line: 161 Context: Unknown File: src/lib/api/bindings.ts Line: 1536 Context: Unknown File: src/lib/api/bindings.ts Line: 1549 Context: Unknown File: src/routes/library/[id]/+page.svelte Line: 205 Context: Unknown File: src-tauri/src/commands/repository.rs Line: 356 Context: Unknown File: src-tauri/src/commands/repository.rs Line: 377 Context: Unknown File: src-tauri/src/repository/series_progress.rs Line: 12 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-101","id":"142","title":"DR-101"},"143":{"body":"Locations: 7 file(s) File: src/lib/components/library/SeasonSection.svelte Line: 1 Context: Unknown File: src/lib/components/library/EpisodeRow.svelte Line: 17 Context: Unknown File: src/lib/components/library/seriesNavigation.ts Line: 13 Context: Unknown File: src/lib/components/library/seriesNavigation.test.ts Line: 1 Context: Unknown File: src/routes/library/[id]/+page.svelte Line: 1 Context: Unknown File: src/routes/library/[id]/+page.svelte Line: 205 Context: Unknown File: src/routes/library/[id]/+page.svelte Line: 326 Context: async function handlePlayAll() {","breadcrumbs":"Traceability Matrix » DR-102","id":"143","title":"DR-102"},"144":{"body":"Locations: 6 file(s) File: src/lib/components/library/SeasonSection.svelte Line: 1 Context: Unknown File: src/lib/components/library/seriesNavigation.ts Line: 13 Context: Unknown File: src/lib/components/library/seriesNavigation.test.ts Line: 1 Context: Unknown File: src/routes/library/tv/+page.svelte Line: 8 Context: Unknown File: src/routes/library/[id]/+page.svelte Line: 1 Context: Unknown File: src/routes/library/[id]/+page.svelte Line: 124 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-103","id":"144","title":"DR-103"},"145":{"body":"Locations: 1 file(s) File: src/lib/components/library/episodeStrip.ts Line: 8 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-104","id":"145","title":"DR-104"},"146":{"body":"Locations: 10 file(s) File: src/lib/components/library/GenericMediaListPage.svelte Line: 50 Context: Unknown File: src/lib/components/library/GenericGenreBrowser.svelte Line: 45 Context: Unknown File: src/lib/components/library/LibraryViewTabs.svelte Line: 8 Context: Unknown File: src/lib/utils/libraryView.ts Line: 9 Context: Unknown File: src/routes/library/tv/+page.svelte Line: 8 Context: Unknown File: src/routes/library/tv/shows/+page.ts Line: 6 Context: Unknown File: src/routes/library/movies/all/+page.ts Line: 6 Context: Unknown File: src/routes/library/movies/genres/+page.ts Line: 6 Context: Unknown File: src/routes/library/movies/+page.svelte Line: 8 Context: Unknown File: src/routes/library/shows/genres/+page.ts Line: 6 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-105","id":"146","title":"DR-105"},"147":{"body":"Locations: 7 file(s) File: src/lib/api/repository-client.ts Line: 172 Context: Unknown File: src/lib/api/bindings.ts Line: 1563 Context: Unknown File: src/lib/components/library/ClearHistoryButton.svelte Line: 9 Context: Unknown File: src/lib/components/library/SeasonSection.svelte Line: 1 Context: Unknown File: src-tauri/src/commands/repository.rs Line: 399 Context: Unknown File: src-tauri/src/repository/mod.rs Line: 247 Context: Unknown File: src-tauri/src/repository/online.rs Line: 2321 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-106","id":"147","title":"DR-106"},"148":{"body":"Locations: 2 file(s) File: src/lib/components/library/SeasonSection.svelte Line: 1 Context: Unknown File: src/lib/components/library/SeasonSection.svelte Line: 20 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-107","id":"148","title":"DR-107"},"149":{"body":"Locations: 5 file(s) File: src-tauri/src/repository/offline.rs Line: 62 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 2548 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 2588 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 3306 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 3362 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-108","id":"149","title":"DR-108"},"15":{"body":"Issue : The Linux (MPV) and Android (ExoPlayer) playback backends have diverged in feature implementation and architecture patterns. Symptoms (as first recorded; the audio half is now closed — see Status): Audio settings (gapless playback, volume normalization, equalizer) worked on Linux but not on Android Position update frequency differs between platforms (Linux: 250ms polling, Android: on-demand callbacks) Thread safety models differ (Linux: Arc>, Android: global OnceLock statics) Root Cause : The PlayerBackend trait defines optional audio settings methods with default empty implementations. MpvBackend overrode these with MPV property commands; ExoPlayerBackend took the silent defaults, so the Settings › Audio panel rendered controls that did nothing on Android. ExoPlayerBackend now overrides them too, but the trait default is still a silent Ok(()) — a backend that omits the method still reports success rather than failing loudly. Affected Files : src-tauri/src/player/backend.rs - Trait; defaults still return Ok(()) silently src-tauri/src/player/mpv_backend.rs - Full audio settings support src-tauri/src/player/android/mod.rs - Audio settings carried to Kotlin as JSON over JNI Feature Parity Matrix : Feature Linux (MPV) Android (ExoPlayer) Status Basic playback ✅ ✅ Parity Volume control ✅ ✅ Parity Seek ✅ ✅ Parity Crossfade ❌ ❌ Not implemented (blocked on MPV) Gapless playback ✅ ⚠️ Implemented, pending on-device verification Volume normalization ✅ ⚠️ Implemented (LoudnessEnhancer — gain stage, approximate vs MPV's dynaudnorm), pending on-device verification Equalizer (10-band) ✅ ⚠️ Implemented (resampled onto device bands), pending on-device verification Position updates 250ms On-demand Inconsistent Status (see docs/architecture/05-platform-backends.md, \"Audio settings on ExoPlayer\"): ✅ set_audio_settings() implemented in ExoPlayerBackend (JSON over JNI) ✅ Gapless via ExoPlayer's pauseAtEndOfMediaItems ✅ Volume normalization via LoudnessEnhancer ✅ Equalizer via android.media.audiofx.Equalizer, canonical 10 bands resampled onto the device's band centres ⬜ Not yet verified on a physical device — the EQ/normalization effects depend on device-specific AudioEffect availability and band layouts ⬜ Flip the trait's set_audio_settings default from Ok(()) to Err(not_implemented()) so a backend that omits it fails loudly instead of silently reporting success. Deferred until (5) confirms the Android path works ⬜ Standardize position update frequency across platforms Crossfade is deliberately absent: it is unimplemented on every platform and architecturally blocked on MPV, so building it on Android alone would invert the parity gap. (The previously suggested ConcatenatingMediaSource is also deprecated in current Media3.) Impact : Medium - Android users lack audio enhancement features advertised in requirements User experience differs between platforms UR-032 (Gapless), UR-033 (Normalization) and UR-027 (Equalizer) are now implemented on Android as well as Linux, pending on-device verification UR-031 (Crossfade) works nowhere — see DR-034 Traces To : IR-004, UR-031, UR-032, UR-033, DR-034, DR-035, DR-036","breadcrumbs":"Requirements Specification » Platform Playback Backend Parity (Linux vs Android)","id":"15","title":"Platform Playback Backend Parity (Linux vs Android)"},"150":{"body":"Locations: 6 file(s) File: src-tauri/src/commands/repository.rs Line: 51 Context: Unknown File: src-tauri/src/commands/catalog.rs Line: 164 Context: Unknown File: src-tauri/src/commands/catalog.rs Line: 281 Context: Unknown File: src-tauri/src/commands/catalog.rs Line: 327 Context: Unknown File: src-tauri/src/commands/catalog.rs Line: 764 Context: Unknown File: src-tauri/src/lib.rs Line: 1564 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-109","id":"150","title":"DR-109"},"151":{"body":"Locations: 8 file(s) File: src-tauri/src/commands/storage/people.rs Line: 56 Context: Unknown File: src-tauri/src/commands/catalog.rs Line: 164 Context: Unknown File: src-tauri/src/storage/schema.rs Line: 757 Context: Unknown File: src-tauri/src/repository/hybrid.rs Line: 64 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 276 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 501 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 3144 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 3253 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-110","id":"151","title":"DR-110"},"152":{"body":"Locations: 5 file(s) File: src-tauri/src/commands/storage/people.rs Line: 56 Context: Unknown File: src-tauri/src/commands/catalog.rs Line: 96 Context: Unknown File: src-tauri/src/storage/schema.rs Line: 769 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 333 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 3087 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-111","id":"152","title":"DR-111"},"153":{"body":"Locations: 6 file(s) File: src/lib/components/BottomUi.svelte Line: 23 Context: Unknown File: src/lib/utils/layoutShell.test.ts Line: 158 Context: Unknown File: src/lib/utils/safeArea.test.ts Line: 17 Context: Unknown File: src/lib/utils/safeArea.ts Line: 4 Context: Unknown File: src/lib/utils/layoutShell.ts Line: 115 Context: Unknown File: src/routes/+layout.svelte Line: 269 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-112","id":"153","title":"DR-112"},"154":{"body":"Locations: 4 file(s) File: src-tauri/src/repository/online.rs Line: 771 Context: Unknown File: src-tauri/src/repository/online.rs Line: 1058 Context: Unknown File: src-tauri/src/repository/online.rs Line: 3728 Context: Unknown File: src-tauri/src/repository/online.rs Line: 3759 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-113","id":"154","title":"DR-113"},"155":{"body":"Locations: 2 file(s) File: src-tauri/src/repository/offline.rs Line: 688 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 4605 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-114","id":"155","title":"DR-114"},"156":{"body":"Locations: 16 file(s) File: src/lib/api/repository-client.ts Line: 356 Context: Unknown File: src/lib/api/bindings.ts Line: 1707 Context: Unknown File: src-tauri/src/commands/repository.rs Line: 898 Context: Unknown File: src-tauri/src/commands/repository.rs Line: 935 Context: Unknown File: src-tauri/src/repository/hybrid.rs Line: 196 Context: Unknown File: src-tauri/src/repository/hybrid.rs Line: 212 Context: Unknown File: src-tauri/src/repository/hybrid.rs Line: 1032 Context: Unknown File: src-tauri/src/repository/mod.rs Line: 235 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 2014 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 4370 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 4405 Context: Unknown File: src-tauri/src/repository/online.rs Line: 933 Context: Unknown File: src-tauri/src/repository/online.rs Line: 2251 Context: Unknown File: src-tauri/src/repository/online.rs Line: 3510 Context: Unknown File: src-tauri/src/repository/online.rs Line: 3532 Context: Unknown File: src-tauri/src/repository/online.rs Line: 3541 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-115","id":"156","title":"DR-115"},"157":{"body":"Locations: 11 file(s) File: src/lib/api/bindings.ts Line: 2268 Context: export type GetItemsOptions = { startIndex?: number | null; limit?: nu... File: src/lib/components/library/GenericMediaListPage.svelte Line: 1 Context: Unknown File: src/lib/components/library/GenericMediaListPage.svelte Line: 154 Context: Unknown File: src/lib/components/library/GenericMediaListPage.svelte Line: 170 Context: function handleSearch(query: string) { File: src/lib/components/library/GenericMediaListPage.svelte Line: 288 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 1269 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 4450 Context: Unknown File: src-tauri/src/repository/online.rs Line: 803 Context: Unknown File: src-tauri/src/repository/online.rs Line: 862 Context: Unknown File: src-tauri/src/repository/online.rs Line: 3563 Context: Unknown File: src-tauri/src/repository/types.rs Line: 342 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-116","id":"157","title":"DR-116"},"158":{"body":"Locations: 8 file(s) File: src/lib/utils/favoritesView.test.ts Line: 1 Context: Unknown File: src/lib/utils/favoritesView.ts Line: 8 Context: Unknown File: src/lib/stores/favorites.test.ts Line: 1 Context: Unknown File: src/lib/stores/favorites.ts Line: 79 Context: Unknown File: src/routes/library/favorites/+page.svelte Line: 9 Context: Unknown File: src/routes/library/favorites/+page.svelte Line: 44 Context: Unknown File: src/routes/library/+page.svelte Line: 225 Context: Unknown File: src/routes/library/+page.svelte Line: 288 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-117","id":"158","title":"DR-117"},"159":{"body":"Locations: 5 file(s) File: src/lib/stores/home.ts Line: 2 Context: Unknown File: src/lib/stores/home.ts Line: 18 Context: Unknown File: src/lib/stores/home.ts Line: 64 Context: Unknown File: src/routes/+page.svelte Line: 162 Context: Unknown File: src/routes/+page.svelte Line: 271 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-118","id":"159","title":"DR-118"},"16":{"body":"Issue : Playback control handlers and state derivations are duplicated between AudioPlayer.svelte and MiniPlayer.svelte. Symptoms : Identical try-catch wrapped handler functions in both components (~44 lines duplicated) Same $derived state merging logic for local/remote playback in both components Position conversion (ticks ↔ seconds) scattered across multiple files Affected Files : src/lib/components/player/AudioPlayer.svelte - Duplicate handlers src/lib/components/player/MiniPlayer.svelte - Duplicate handlers src/lib/utils/playbackUnits.ts - Position conversion (the shared helper the \"Future Fix\" below called for; playbackControl.ts, previously listed here, has since been removed) src/lib/stores/playbackMode.ts - Position conversion src/lib/services/playbackReporting.ts - Position conversion Duplicated Code : // These handlers are identical in both AudioPlayer and MiniPlayer:\nhandlePlayPause(), handleNext(), handlePrevious(),\nhandleToggleShuffle(), handleCycleRepeat(), handleVolumeChange() // These derived states use identical logic:\ndisplayMedia, displayIsPlaying, displayPosition, displayDuration Future Fix : Create src/lib/utils/playbackUnits.ts: export const TICKS_PER_SECOND = 10_000_000;\nexport const secondsToTicks = (s: number) => Math.floor(s * TICKS_PER_SECOND);\nexport const ticksToSeconds = (t: number) => t / TICKS_PER_SECOND; Create src/lib/composables/useMergedPlaybackState.svelte.ts: Export displayMedia, displayIsPlaying, displayPosition, displayDuration Single source of truth for merged local/remote state Simplify handler wrappers using a utility: export const withErrorHandler = (fn: () => Promise, context: string) => async () => { try { await fn(); } catch (e) { console.error(`${context}:`, e); } }; Impact : Low - Code works correctly but violates DRY principle Maintenance burden when logic needs to change Risk of handlers diverging over time Traces To : DR-009","breadcrumbs":"Requirements Specification » Frontend Playback Code Duplication","id":"16","title":"Frontend Playback Code Duplication"},"160":{"body":"Locations: 15 file(s) File: src/lib/components/library/PlaylistDetailView.svelte Line: 229 Context: Unknown File: src/lib/components/library/EpisodeFocusView.svelte Line: 250 Context: Unknown File: src/lib/components/library/MediaCard.svelte Line: 1 Context: Unknown File: src/lib/components/library/MediaCard.svelte Line: 48 Context: Unknown File: src/lib/components/library/MediaCard.svelte Line: 154 Context: Unknown File: src/lib/components/library/MediaCard.svelte Line: 329 Context: Unknown File: src/lib/components/library/ArtistDetailView.svelte Line: 138 Context: Unknown File: src/lib/components/FavoriteButton.svelte Line: 1 Context: Unknown File: src/lib/services/favorites.ts Line: 2 Context: Unknown File: src/lib/services/favorites.ts Line: 38 Context: Unknown File: src/lib/stores/favorites.test.ts Line: 1 Context: Unknown File: src/lib/stores/favorites.ts Line: 8 Context: Unknown File: src/lib/stores/favorites.ts Line: 55 Context: Unknown File: src/lib/stores/favorites.ts Line: 79 Context: Unknown File: src/routes/library/[id]/+page.svelte Line: 619 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-119","id":"160","title":"DR-119"},"161":{"body":"Locations: 19 file(s) File: src/lib/api/bindings.ts Line: 1707 Context: Unknown File: src/routes/library/favorites/+page.svelte Line: 76 Context: Unknown File: src/routes/+layout.svelte Line: 150 Context: Unknown File: src-tauri/src/commands/favorites.rs Line: 12 Context: Unknown File: src-tauri/src/commands/favorites.rs Line: 78 Context: Unknown File: src-tauri/src/commands/favorites.rs Line: 142 Context: Unknown File: src-tauri/src/commands/favorites.rs Line: 286 Context: Unknown File: src-tauri/src/commands/favorites.rs Line: 320 Context: Unknown File: src-tauri/src/commands/favorites.rs Line: 340 Context: Unknown File: src-tauri/src/commands/favorites.rs Line: 356 Context: Unknown File: src-tauri/src/commands/repository.rs Line: 858 Context: Unknown File: src-tauri/src/commands/repository.rs Line: 864 Context: Unknown File: src-tauri/src/commands/repository.rs Line: 879 Context: Unknown File: src-tauri/src/commands/repository.rs Line: 898 Context: Unknown File: src-tauri/src/commands/repository.rs Line: 1052 Context: Unknown File: src-tauri/src/commands/repository.rs Line: 1077 Context: Unknown File: src-tauri/src/lib.rs Line: 1571 Context: Unknown File: src-tauri/src/repository/hybrid.rs Line: 47 Context: pub fn new(online: OnlineRepository, offline: OfflineRepository) -> Se... File: src-tauri/src/repository/online.rs Line: 132 Context: impl OnlineRepository {","breadcrumbs":"Traceability Matrix » DR-120","id":"161","title":"DR-120"},"162":{"body":"Locations: 8 file(s) File: src/lib/api/bindings.ts Line: 385 Context: Unknown File: src/lib/player/localSource.ts Line: 12 Context: Unknown File: src/routes/player/[id]/+page.svelte Line: 385 Context: Unknown File: src/routes/player/[id]/+page.svelte Line: 389 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 510 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 562 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 3095 Context: Unknown File: src-tauri/src/repository/online.rs Line: 2139 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-123","id":"162","title":"DR-123"},"163":{"body":"Locations: 2 file(s) File: src-tauri/src/download/cache.rs Line: 320 Context: Unknown File: src-tauri/src/download/cache.rs Line: 550 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-126","id":"163","title":"DR-126"},"164":{"body":"Locations: 6 file(s) File: src/lib/api/bindings.ts Line: 2047 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 359 Context: Unknown File: src-tauri/src/storage/schema.rs Line: 813 Context: Unknown File: src-tauri/src/download/cache.rs Line: 33 Context: Unknown File: src-tauri/src/download/cache.rs Line: 220 Context: Unknown File: src-tauri/src/download/cache.rs Line: 428 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-127","id":"164","title":"DR-127"},"165":{"body":"Locations: 3 file(s) File: src-tauri/src/commands/player/mod.rs Line: 439 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 748 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 3011 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-128","id":"165","title":"DR-128"},"166":{"body":"Locations: 9 file(s) File: src/lib/api/bindings.ts Line: 326 Context: Unknown File: src-tauri/src/commands/player/timers.rs Line: 145 Context: Unknown File: src-tauri/src/player/mod.rs Line: 280 Context: Unknown File: src-tauri/src/player/mod.rs Line: 1690 Context: Unknown File: src-tauri/src/player/mod.rs Line: 1723 Context: Unknown File: src-tauri/src/player/mod.rs Line: 1780 Context: Unknown File: src-tauri/src/player/mod.rs Line: 1811 Context: Unknown File: src-tauri/src/player/stream_end.rs Line: 3 Context: Unknown File: src-tauri/src/player/stream_end.rs Line: 54 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-129","id":"166","title":"DR-129"},"167":{"body":"Locations: 7 file(s) File: src/lib/api/bindings.ts Line: 349 Context: Unknown File: src/lib/services/playerEvents.regression.test.ts Line: 155 Context: Unknown File: src/lib/services/playerEvents.ts Line: 292 Context: Unknown File: src-tauri/src/commands/player/timers.rs Line: 299 Context: Unknown File: src-tauri/src/player/mpv_backend.rs Line: 564 Context: Unknown File: src-tauri/src/player/mpv_backend.rs Line: 573 Context: Unknown File: src-tauri/src/player/mod.rs Line: 1780 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-130","id":"167","title":"DR-130"},"168":{"body":"Locations: 16 file(s) File: src-tauri/src/commands/sync.rs Line: 7 Context: Unknown File: src-tauri/src/commands/sync_drain.rs Line: 14 Context: Unknown File: src-tauri/src/commands/sync_drain.rs Line: 76 Context: Unknown File: src-tauri/src/commands/sync_drain.rs Line: 236 Context: Unknown File: src-tauri/src/commands/sync_drain.rs Line: 427 Context: Unknown File: src-tauri/src/commands/sync_drain.rs Line: 606 Context: Unknown File: src-tauri/src/commands/sync_drain.rs Line: 672 Context: Unknown File: src-tauri/src/commands/sync_drain.rs Line: 702 Context: Unknown File: src-tauri/src/commands/sync_drain.rs Line: 736 Context: Unknown File: src-tauri/src/commands/sync_drain.rs Line: 765 Context: Unknown File: src-tauri/src/commands/sync_drain.rs Line: 811 Context: Unknown File: src-tauri/src/commands/sync_drain.rs Line: 992 Context: Unknown File: src-tauri/src/commands/sync_drain.rs Line: 1007 Context: Unknown File: src-tauri/src/lib.rs Line: 1578 Context: Unknown File: src-tauri/src/repository/mod.rs Line: 255 Context: Unknown File: src-tauri/src/repository/online.rs Line: 2366 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-131","id":"168","title":"DR-131"},"169":{"body":"Locations: 12 file(s) File: src/lib/api/bindings.ts Line: 1338 Context: Unknown File: src/lib/api/bindings.ts Line: 3265 Context: export type SyncQueueItem = { id: number; userId: string; operation: s... File: src/lib/components/sync/PendingSyncList.svelte Line: 12 Context: Unknown File: src/lib/components/sync/PendingSyncModal.svelte Line: 5 Context: Unknown File: src/lib/services/pendingSync.logic.test.ts Line: 1 Context: Unknown File: src/lib/services/pendingSync.logic.ts Line: 9 Context: Unknown File: src/routes/settings/+page.svelte Line: 1 Context: Unknown File: src/routes/settings/+page.svelte Line: 1005 Context: Unknown File: src/routes/+layout.svelte Line: 303 Context: Unknown File: src-tauri/src/commands/sync.rs Line: 7 Context: Unknown File: src-tauri/src/commands/sync.rs Line: 33 Context: Unknown File: src-tauri/src/commands/sync_drain.rs Line: 474 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-132","id":"169","title":"DR-132"},"17":{"body":"Generated: 8/22/2026, 10:23:05 AM","breadcrumbs":"Traceability Matrix » Code Traceability Matrix","id":"17","title":"Code Traceability Matrix"},"170":{"body":"Locations: 2 file(s) File: src/lib/player/localSource.ts Line: 55 Context: Unknown File: src/routes/player/[id]/+page.svelte Line: 306 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-133","id":"170","title":"DR-133"},"171":{"body":"Locations: 2 file(s) File: src/lib/services/imageCache.ts Line: 50 Context: Unknown File: src-tauri/src/lib.rs Line: 1122 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-134","id":"171","title":"DR-134"},"172":{"body":"Locations: 5 file(s) File: src-tauri/src/commands/catalog.rs Line: 114 Context: Unknown File: src-tauri/src/commands/catalog.rs Line: 551 Context: Unknown File: src-tauri/src/commands/catalog.rs Line: 1001 Context: Unknown File: src-tauri/src/commands/catalog.rs Line: 1039 Context: Unknown File: src-tauri/src/commands/catalog.rs Line: 1062 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-135","id":"172","title":"DR-135"},"173":{"body":"Locations: 3 file(s) File: src-tauri/src/commands/catalog.rs Line: 490 Context: Unknown File: src-tauri/src/commands/catalog.rs Line: 689 Context: Unknown File: src-tauri/src/commands/catalog.rs Line: 1090 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-136","id":"173","title":"DR-136"},"174":{"body":"Locations: 18 file(s) File: src/lib/api/bindings.ts Line: 1031 Context: Unknown File: src/routes/player/[id]/+page.svelte Line: 315 Context: Unknown File: src/routes/player/[id]/+page.svelte Line: 389 Context: Unknown File: src-tauri/src/commands/storage/mod.rs Line: 93 Context: Unknown File: src-tauri/src/lib.rs Line: 1122 Context: Unknown File: src-tauri/src/lib.rs Line: 1481 Context: Unknown File: src-tauri/src/media_server.rs Line: 27 Context: Unknown File: src-tauri/src/media_server.rs Line: 70 Context: pub fn url_for(&self, path: &str) -> String { File: src-tauri/src/media_server.rs Line: 282 Context: Unknown File: src-tauri/src/media_server.rs Line: 327 Context: Unknown File: src-tauri/src/media_server.rs Line: 398 Context: Unknown File: src-tauri/src/media_server.rs Line: 449 Context: Unknown File: src-tauri/src/media_server.rs Line: 458 Context: Unknown File: src-tauri/src/media_server.rs Line: 473 Context: Unknown File: src-tauri/src/media_server.rs Line: 508 Context: Unknown File: src-tauri/src/media_server.rs Line: 515 Context: Unknown File: src-tauri/src/media_server.rs Line: 540 Context: Unknown File: src-tauri/src/media_server.rs Line: 567 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-137","id":"174","title":"DR-137"},"175":{"body":"Locations: 3 file(s) File: src-tauri/src/repository/online.rs Line: 527 Context: Unknown File: src-tauri/src/repository/online.rs Line: 620 Context: Unknown File: src-tauri/src/repository/online.rs Line: 2920 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-140","id":"175","title":"DR-140"},"176":{"body":"Locations: 2 file(s) File: src-tauri/src/repository/device_profile.rs Line: 29 Context: Unknown File: src-tauri/src/repository/device_profile.rs Line: 40 Context: pub fn clamp_max_audio_channels(reported: Option) -> u32 {","breadcrumbs":"Traceability Matrix » DR-141","id":"176","title":"DR-141"},"177":{"body":"Locations: 11 file(s) File: src/lib/components/library/EpisodeFocusView.svelte Line: 7 Context: Unknown File: src/lib/components/library/EpisodeFocusView.svelte Line: 250 Context: Unknown File: src/lib/components/library/EpisodeFocusView.svelte Line: 394 Context: Unknown File: src/lib/components/library/EpisodeFocusView.test.ts Line: 7 Context: Unknown File: src/lib/components/library/seriesNavigation.ts Line: 13 Context: Unknown File: src/lib/components/library/seriesNavigation.test.ts Line: 1 Context: Unknown File: src/routes/library/[id]/+page.svelte Line: 1 Context: Unknown File: src/routes/library/[id]/+page.svelte Line: 139 Context: Unknown File: src/routes/library/[id]/+page.svelte Line: 233 Context: Unknown File: src/routes/library/[id]/+page.svelte Line: 467 Context: Unknown File: src/routes/search/+page.svelte Line: 97 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-142","id":"177","title":"DR-142"},"178":{"body":"Locations: 11 file(s) File: src/lib/components/library/GenericMediaListPage.svelte Line: 103 Context: Unknown File: src/lib/components/library/GenericGenreBrowser.svelte Line: 67 Context: async function reloadGenreBrowse() { File: src/lib/services/offlineCatalog.reload.test.ts Line: 11 Context: Unknown File: src/lib/services/offlineCatalog.ts Line: 46 Context: Unknown File: src/lib/composables/useOfflineFilterReload.test.ts Line: 1 Context: Unknown File: src/lib/composables/useOfflineFilterReload.ts Line: 26 Context: Unknown File: src/routes/library/favorites/+page.svelte Line: 88 Context: Unknown File: src/routes/library/tv/+page.svelte Line: 74 Context: async function load() { File: src/routes/library/movies/+page.svelte Line: 73 Context: async function load() { File: src/routes/library/music/+page.svelte Line: 71 Context: async function load() { File: src/routes/library/[id]/+page.svelte Line: 100 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-143","id":"178","title":"DR-143"},"179":{"body":"Locations: 5 file(s) File: src/lib/components/search/HeaderSearch.test.ts Line: 9 Context: Unknown File: src/lib/components/search/HeaderSearch.svelte Line: 12 Context: Unknown File: src/routes/search/+page.svelte Line: 34 Context: Unknown File: src/routes/search/+page.svelte Line: 62 Context: Unknown File: src/routes/search/searchPage.test.ts Line: 14 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-147","id":"179","title":"DR-147"},"18":{"body":"Total Files Scanned: 389 Total TRACES Found: 988 Requirements Covered: User Requirements (UR): 76 Integration Requirements (IR): 25 Development Requirements (DR): 189 Jellyfin API Requirements (JA): 35","breadcrumbs":"Traceability Matrix » Summary","id":"18","title":"Summary"},"180":{"body":"Locations: 1 file(s) File: src-tauri/src/repository/device_profile.rs Line: 213 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-148","id":"180","title":"DR-148"},"181":{"body":"Locations: 3 file(s) File: src-tauri/src/repository/device_profile.rs Line: 238 Context: Unknown File: src-tauri/src/repository/device_profile.rs Line: 260 Context: Unknown File: src-tauri/src/repository/device_profile.rs Line: 277 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-149","id":"181","title":"DR-149"},"182":{"body":"Locations: 4 file(s) File: src/lib/player/adapters/adapterSelection.test.ts Line: 4 Context: Unknown File: src/lib/player/adapters/index.ts Line: 20 Context: Unknown File: src/lib/utils/videoSurface.ts Line: 4 Context: Unknown File: src/lib/stores/nativeVideo.ts Line: 3 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-150","id":"182","title":"DR-150"},"183":{"body":"Locations: 1 file(s) File: src/lib/utils/videoSurface.ts Line: 4 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-151","id":"183","title":"DR-151"},"184":{"body":"Locations: 2 file(s) File: src/lib/services/playbackCapabilities.ts Line: 4 Context: Unknown File: src/lib/stores/nativeVideo.ts Line: 3 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-152","id":"184","title":"DR-152"},"185":{"body":"Locations: 1 file(s) File: scripts/set-version.test.ts Line: 4 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-153","id":"185","title":"DR-153"},"186":{"body":"Locations: 7 file(s) File: src/lib/api/bindings.ts Line: 1675 Context: Unknown File: src-tauri/src/commands/sync_drain.rs Line: 380 Context: Unknown File: src-tauri/src/commands/sync_drain.rs Line: 856 Context: Unknown File: src-tauri/src/commands/sync_drain.rs Line: 885 Context: Unknown File: src-tauri/src/commands/sync_drain.rs Line: 915 Context: Unknown File: src-tauri/src/commands/sync_drain.rs Line: 954 Context: Unknown File: src-tauri/src/commands/repository.rs Line: 735 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-154","id":"186","title":"DR-154"},"187":{"body":"Locations: 5 file(s) File: src-tauri/src/repository/hybrid.rs Line: 408 Context: Unknown File: src-tauri/src/repository/hybrid.rs Line: 635 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 688 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 4692 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 4778 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-155","id":"187","title":"DR-155"},"188":{"body":"Locations: 2 file(s) File: src/lib/utils/scrollContainer.ts Line: 23 Context: Unknown File: src/lib/utils/scrollRestore.ts Line: 26 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-156","id":"188","title":"DR-156"},"189":{"body":"Locations: 1 file(s) File: src/lib/utils/immersive.ts Line: 4 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-157","id":"189","title":"DR-157"},"19":{"body":"","breadcrumbs":"Traceability Matrix » Requirements by Type","id":"19","title":"Requirements by Type"},"190":{"body":"Locations: 6 file(s) File: src/lib/api/bindings.ts Line: 848 Context: Unknown File: src/lib/components/library/WatchedToggleButton.svelte Line: 13 Context: Unknown File: src/lib/services/syncService.ts Line: 121 Context: Unknown File: src-tauri/src/commands/storage/mod.rs Line: 891 Context: Unknown File: src-tauri/src/commands/sync_drain.rs Line: 1047 Context: Unknown File: src-tauri/src/commands/sync_drain.rs Line: 1064 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-158","id":"190","title":"DR-158"},"191":{"body":"Locations: 3 file(s) File: src-tauri/src/player/mod.rs Line: 873 Context: Unknown File: src-tauri/src/player/mod.rs Line: 4077 Context: Unknown File: src-tauri/src/player/mod.rs Line: 4120 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-159","id":"191","title":"DR-159"},"192":{"body":"Locations: 1 file(s) File: src/lib/utils/pictureInPicture.ts Line: 111 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-160","id":"192","title":"DR-160"},"193":{"body":"Locations: 39 file(s) File: src/lib/api/bindings.ts Line: 251 Context: Unknown File: src/lib/api/bindings.ts Line: 272 Context: Unknown File: src/lib/api/bindings.ts Line: 3181 Context: Unknown File: src/lib/api/bindings.ts Line: 3207 Context: Unknown File: src/lib/api/bindings.ts Line: 3342 Context: Unknown File: src/lib/components/player/VideoPlayer.svelte Line: 276 Context: Unknown File: src/lib/components/player/VideoPlayer.svelte Line: 809 Context: Unknown File: src/lib/components/player/VideoPlayer.svelte Line: 2291 Context: Unknown File: src/lib/components/player/VideoPlayer.svelte Line: 2759 Context: Unknown File: src/lib/player/index.ts Line: 195 Context: Unknown File: src/routes/settings/+page.svelte Line: 84 Context: Unknown File: src/routes/settings/+page.svelte Line: 418 Context: function handleEpisodeLimitChange(value: number) { File: src/routes/settings/+page.svelte Line: 918 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 350 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 1613 Context: Unknown File: src-tauri/src/commands/player/settings.rs Line: 3 Context: Unknown File: src-tauri/src/commands/player/settings.rs Line: 25 Context: Unknown File: src-tauri/src/commands/player/settings.rs Line: 85 Context: Unknown File: src-tauri/src/commands/player/settings.rs Line: 107 Context: Unknown File: src-tauri/src/commands/player/settings.rs Line: 122 Context: Unknown File: src-tauri/src/commands/player/settings.rs Line: 158 Context: Unknown File: src-tauri/src/lib.rs Line: 1438 Context: Unknown File: src-tauri/src/repository/online.rs Line: 26 Context: Unknown File: src-tauri/src/repository/online.rs Line: 35 Context: Unknown File: src-tauri/src/repository/online.rs Line: 42 Context: pub fn set_streaming_quality(quality: StreamingQuality) { File: src-tauri/src/repository/online.rs Line: 59 Context: Unknown File: src-tauri/src/repository/online.rs Line: 527 Context: Unknown File: src-tauri/src/repository/online.rs Line: 657 Context: Unknown File: src-tauri/src/repository/online.rs Line: 1691 Context: Unknown File: src-tauri/src/repository/online.rs Line: 1975 Context: Unknown File: src-tauri/src/repository/online.rs Line: 2818 Context: Unknown File: src-tauri/src/repository/online.rs Line: 2842 Context: Unknown File: src-tauri/src/repository/online.rs Line: 2864 Context: Unknown File: src-tauri/src/repository/online.rs Line: 2887 Context: Unknown File: src-tauri/src/settings.rs Line: 167 Context: Unknown File: src-tauri/src/settings.rs Line: 302 Context: Unknown File: src-tauri/src/settings.rs Line: 653 Context: Unknown File: src-tauri/src/settings.rs Line: 689 Context: Unknown File: src-tauri/src/settings.rs Line: 716 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-162","id":"193","title":"DR-162"},"194":{"body":"Locations: 2 file(s) File: src-tauri/src/repository/offline.rs Line: 848 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 3767 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-167","id":"194","title":"DR-167"},"195":{"body":"Locations: 6 file(s) File: src/lib/api/bindings.ts Line: 941 Context: Unknown File: src/lib/api/bindings.ts Line: 954 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 1226 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 1260 Context: Unknown File: src-tauri/src/download/stop.rs Line: 3 Context: Unknown File: src-tauri/src/download/worker.rs Line: 42 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-168","id":"195","title":"DR-168"},"196":{"body":"Locations: 2 file(s) File: src-tauri/src/download/worker.rs Line: 247 Context: / One function so the writer and the cleaners cannot disagree again. File: src-tauri/src/download/worker.rs Line: 336 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-169","id":"196","title":"DR-169"},"197":{"body":"Locations: 2 file(s) File: src-tauri/src/download/worker.rs Line: 219 Context: Unknown File: src-tauri/src/download/worker.rs Line: 313 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-170","id":"197","title":"DR-170"},"198":{"body":"Locations: 9 file(s) File: src-tauri/src/repository/device_profile.rs Line: 277 Context: Unknown File: src-tauri/src/repository/device_profile.rs Line: 457 Context: Unknown File: src-tauri/src/repository/mod.rs Line: 212 Context: Unknown File: src-tauri/src/repository/mod.rs Line: 347 Context: Unknown File: src-tauri/src/repository/mod.rs Line: 369 Context: Unknown File: src-tauri/src/repository/online.rs Line: 2216 Context: Unknown File: src-tauri/src/repository/online.rs Line: 3402 Context: Unknown File: src-tauri/src/repository/online.rs Line: 3434 Context: Unknown File: src-tauri/src/repository/online.rs Line: 3460 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-171","id":"198","title":"DR-171"},"199":{"body":"Locations: 17 file(s) File: src/lib/api/bindings.ts Line: 899 Context: Unknown File: src/lib/stores/downloads.ts Line: 210 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 442 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 473 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 524 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 546 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 679 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 3238 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 3303 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 3334 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 3367 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 3396 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 3410 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 3437 Context: Unknown File: src-tauri/src/commands/catalog.rs Line: 916 Context: Unknown File: src-tauri/src/commands/catalog.rs Line: 957 Context: Unknown File: src-tauri/src/repository/hybrid.rs Line: 135 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-173","id":"199","title":"DR-173"},"2":{"body":"ID Requirement Priority Status UR-001 Run the app on multiple platforms (Linux, Android) High In Progress UR-002 Access media when online or offline High Done UR-003 Play videos High Done UR-004 Play audio uninterrupted High Done UR-005 Control media playback (pause, play, skip, scrub) High Done UR-006 Control media when device is on lock screen or via BLE headsets Medium Done (Android); not implemented on Linux — see IR-005 UR-007 Navigate media in library High Done UR-008 Search media across libraries High Done UR-009 Connect to Jellyfin to access media High Done UR-010 Control playback of Jellyfin remote sessions Low Done UR-011 Download media on demand Medium Done UR-012 Login info shall be stored securely and persistently High Done UR-013 View and manage downloaded media Medium Done UR-014 Make and edit playlists of music that sync back to Jellyfin Medium Done UR-015 View and manage current audio queue (add, reorder tracks) Medium Done UR-016 Change system settings while playing (brightness, volume) Low Planned UR-017 Like or unlike audio, albums, movies, etc. Medium Done UR-018 Choose to download series, albums, songs, artist discography Medium Done UR-019 Resume playback from where you left off (movies, shows, albums) High Done UR-020 Select subtitles for video content High Done UR-021 Select audio track for video content High Done UR-022 Control streaming quality and transcoding settings Medium Planned UR-023 View \"Next Up\" / Continue Watching on home screen; auto-play next episode with countdown popup and configurable episode limit Medium Done UR-024 View recently added content on server Medium Done UR-025 Sync watch history and progress back to Jellyfin High Done UR-026 Sleep timer for audio and video playback (roller UI, time/track/episode modes) Low Done UR-027 Audio equalizer for sound customization Low Done (Linux; Android pending device verification) UR-028 Navigate to artist/album by tapping names in now playing view High Done UR-029 Toggle between grid and list view in library Medium Done UR-030 Quick genre browsing and filtering Medium Done UR-031 Crossfade between audio tracks Low Not implemented (blocked — see DR-034) UR-032 Gapless playback for seamless album listening Medium Done (Linux; Android pending device verification) UR-033 Volume normalization to prevent volume jumps between tracks Low Done (Linux; Android pending device verification) UR-034 Rich home screen with hero banners, carousels, and personalized sections High Done UR-035 View cast/crew (actors, directors) on movie/show detail pages High Done UR-036 Navigate to actor/person page showing their filmography Medium Done UR-037 Visually appealing video library with poster grids and metadata High Done UR-038 Movie/show detail page with backdrop, ratings, and rich metadata High Done UR-039 Navigate between main sections via bottom navigation bar High Done UR-040 Keep a video's audio playing when the app is backgrounded or the screen is locked, stopping video decode until the app returns to the foreground (per-player toggle; Android) Medium Done (pending device verification) UR-041 Continue watching locally-playing video in a floating picture-in-picture window when leaving the app (Android) — PiP applies to video only, never to audio playback, library/menu browsing, or remote/cast sessions Medium Done UR-042 Authenticate to a server and manage the session lifecycle (connect, log in, Quick Connect, background session verification, re-authenticate, log out) High Done UR-043 Automatically detect server reachability and switch between online and offline operation without user intervention High Done UR-044 Pin downloaded media so it is protected from automatic cache eviction Low Done UR-045 Predictively pre-cache likely-next media (queue lookahead and album affinity) within a storage budget Low Done UR-046 Group multiple remote players into a synchronized playback group (LMS SyncGroups) Low Done UR-047 Manage multiple Jellyfin servers (add, list, remove) and switch the active server/account Medium Planned (backend store done; switcher UI pending) UR-048 See the next episodes of a series directly below the episode/series being viewed, above cast and similar-shows content, so continuing a show is the shortest path (see ux-flows.md §5B ) High Done UR-049 Search is scoped by where it was started — inside a library it searches that library, from Home/library-root/search-tab it searches everything — with the scope shown as filter chips under the search bar that preselect from context and can be changed without retyping (see ux-flows.md §6.1 ) High Implemented UR-050 Reorder search result groups (Songs, Albums, Artists, Movies, TV Shows) by drag and drop in settings, so the media a user cares about most appears first (see ux-flows.md §6.3 ) Medium Implemented UR-051 Browse library pages in a consistent layout where card shape signals media type (square music, poster video, thumbnail episode), ordinal content stays listed, and the grid/list preference persists across pages (see ux-flows.md §5A ) Medium Partial (implemented; toggle not reachable from settings) UR-052 While offline, library pages show only media available on the device by default, with an opt-in toggle that additionally reveals the cached server catalog as greyed-out entries which can be queued for download on the next reconnect High Done UR-053 Restrict media downloads to unmetered networks via a \"WiFi Only\" setting: when enabled, queued downloads are held while the device is on cellular or a metered connection (including metered WiFi hotspots) and resume automatically once an unmetered network is available Medium Done (pending device verification) UR-054 Reach account actions (Settings, Downloads, Display preferences, Sign out) from every authenticated screen via a single account menu anchored to the user's name, identical on desktop and mobile (see ux-flows.md §1.2 ) High Done UR-055 Browse downloaded media as an offline-scoped library — reusing the same library grids, cards, and detail pages as online browsing, showing only libraries/containers with downloaded content — with the transfer-progress list demoted to a secondary \"Transfers\" view (see ux-flows.md §7.2 ) High Done UR-056 See how much disk each downloaded item/album/series consumes, in familiar rounded units shown on the card and detail page, with a device total on the Downloaded surface and a reclaim amount stated at the point of removal (see ux-flows.md §7.3.1 ) Medium Done UR-057 Settings apply the instant a control is changed — no \"Save\" button and no save/dirty state — so leaving the page never loses a change; sliders show a live readout while dragging but persist on release (see ux-flows.md §8.1 ) Medium Done UR-058 On the home screen, a tap on a media card opens the item (movie/episode detail page, or the series Episode Focus View for episodes) rather than starting playback; a long-press starts \"play now\" after a confirm; an episode detail/focus page links back to its parent series and season (see ux-flows.md §5B.5 and §5B.1 ) Medium Done UR-059 Skipping to the next episode records the episode left behind as fully watched rather than saving a mid-episode resume point — skipping means \"done with this one\", not \"stopped here\" — and Continue Watching hides episodes the viewer has already moved past (a partial position behind that series' next-up episode), so the row only ever offers genuinely unfinished media Medium Done UR-060 Search results are ordered by how well they match: a name that starts with the query outranks one matching mid-word (typing \"parks\" finds \"Parks and Recreation\" before \"Sparks of Love\"), and at equal match quality a container outranks its contents (a series before its episodes). Results are grouped into distinct categories — TV Shows, Episodes, Movies, Songs, Albums, Artists and People — so a show never competes with its own episodes for the same slot, and searching an actor's name reaches their bio High Done UR-061 Double tapping the video skips within it — right half jumps forward 30 seconds , left half jumps back 10 seconds — with an on-screen indicator naming the amount. A double tap leaves the play state unchanged — playing jumps and keeps playing, paused jumps and stays paused — because the second tap re-toggles what the first tap toggled (see DR-098); the skip lands relative to the position the player actually reports, and repeated double taps accumulate rather than all skipping from the same spot Medium Done UR-062 Opening a TV series lands the viewer where they are in it , not at season 1: the series page scrolls the current season into view and highlights the current episode, and the hero button opens that episode (labelled Resume S2E4 / Play S1E1). \"Current\" means the episode in progress, else the server's Next Up for that series, else the first unwatched episode, else the first — resolved by the backend so it also works offline. A season is never a page of its own : every route that names a season lands on the series with that season in view, so the episodes of all seasons are always one continuous scrollable list High Done UR-063 Each video library is one page , not three. Browsing (hero, Continue Watching, Next Up, Recently Added, genre rows), the full title grid, and the genre browser are tabs of /library/tv and /library/movies rather than separate routes with inconsistent names (/library/tv/shows vs /library/movies/all, /library/shows/genres vs /library/movies/genres). The old routes redirect so existing links keep working Medium Done UR-064 Watch history can be erased , per series and per season, from the series page. Clearing marks every episode inside unwatched and clears resume positions, so the show returns to \"never watched\" and reopens on its premiere. It asks for confirmation first (it cannot be undone) and requires a connection to the server, since history cleared only locally would be undone by the next sync Medium Done UR-065 Search answers from a locally indexed copy of the library , so results appear as fast as the device can query rather than at the speed of a round trip to the server, and the same results are found with the server unreachable. A background job keeps the index current — refreshing on a schedule rather than only at app start, dropping media removed from the server, and covering everything the result groups can show (including artists and people). The server is still queried in the background so media added since the last index still turns up, merged in without reordering what is already on screen High Implemented UR-066 The app's own chrome stays clear of the device's system chrome. On Android the bottom navigation sits above the navigation/gesture bar instead of underneath it, the header clears the status bar, and full-screen video and audio playback keep their controls inside the usable screen — clear of the gesture bar and, in landscape, of the display notch. This must hold across navigation modes (gesture and 3-button) and rotation, not only on the handsets it happened to be tested on High Done UR-067 Favourited media can be found again . A Favourites page lists everything favourited across all libraries, scoped by tabs (All / Movies / Shows / Music); the home screen carries favourite rows for movies, shows and music, hidden when a category is empty; and each library page can be filtered to favourites in place. Without this the like button writes to a store nothing reads Medium Done UR-068 Anything the app shows can be favourited where it is shown — from a movie, series, episode, album, artist or playlist page, and from any card in a grid or carousel — not only from the player while the item happens to be playing Medium Done UR-069 Favourite state agrees with the server in both directions. An item favourited in another Jellyfin client shows as favourited here without being touched, and an item favourited here while the server is unreachable reaches the server once it returns — without the user going back to the screen where they marked it Medium Done UR-070 Playback quality is the viewer's choice: the player offers the bitrates the server can produce for what is playing, and changing one resumes at the same point with the same audio and subtitle tracks. Because the chosen rendition can change at any moment, nothing that streams for playback is treated as a stored copy unless it happens to be byte-identical to the real file Medium Proposed UR-071 Media the viewer is watching can be kept , by a whole-file download that runs in the background independently of playback and at its own quality, so it is unaffected by bitrate changes. Where the streamed bytes already are that file (direct play), they are kept rather than fetched twice. A completed download is then played from disk rather than streamed again Medium Proposed UR-073 Watched state is something the viewer can set , not only something playback records. Any episode, season, series or movie can be marked watched — or unwatched again — from where it is shown, without sitting through it or erasing its history wholesale. Marking a season or series covers the episodes inside it, and works with the server unreachable Medium Done UR-072 Each page opens where a page should open. Moving to a new screen starts at the top of it, and going Back returns the viewer to the place they left — their position in a long library grid or home screen, not the top of it. A page never inherits the scroll position of the page before it Medium Done UR-075 Artwork is shown at the shape it was made in. Where a screen presents a set of things side by side — the libraries on the library page and on home — they are laid out as a mosaic: rows of a common height in which each tile is as wide as its own picture, rather than a grid that crops every cover to one box. Favourites are reachable per category from that same mosaic, beside the library they belong to, not only as one undifferentiated list Medium Done UR-076 Music browsing shows only what the listener considers music. A Jellyfin server commonly keeps podcasts, audiobooks, sound effects or sample packs in their own folders inside a music library; those folders can be excluded by choice , once, and every music surface — library grids, artist and album listings, genre rows, search and the home screen — then agrees on what is in scope. The choice is by folder, not by a name the app happens to recognise, so a folder called anything at all can be excluded and an item is never dropped because its title matched a word Medium Done UR-077 The app can update itself, or tell the user how. Somebody who installed an AppImage or ran the Windows installer had no upgrade path at all: nothing in the app ever mentioned that a newer version existed, and the release notes were the only announcement. On Linux and Windows the app checks a signed manifest, offers the new version with its notes, and installs and relaunches on request — the signature check is the point, since it is what stops a substituted download from being installed by the app itself. Android cannot do this (an app may not overwrite its own APK; that is the package installer's job) and is given the honest alternative, a link to the releases page, rather than a button that would throw Medium Done UR-078 JellyTau keeps a record of what it did, and can hand it over. The app forgot everything the moment it exited: the backend logged to stdout only — which a user launching from a desktop icon never sees, and which on Android is not logcat, so the Rust half was invisible on the platform carrying the hardest bugs. A crash left nothing at all. Logs are now written to a size-capped rotating file, a panic is recorded before the process dies, the frontend's messages land in the same timeline as the backend's, and Settings exports the lot as one file to attach to a bug report. Nothing is transmitted anywhere — the user attaches it themselves, which is also what keeps this from being telemetry. Access tokens and passwords never reach the file Medium Done UR-074 Video streaming can be held to a bandwidth budget the viewer sets , rather than spent at whatever rate the server would otherwise send. A ceiling chosen once — from the source's own bitrate down to a rung that still plays on a poor connection — governs every video the app opens, live TV included, and survives a restart, so a metered connection is not quietly drained by the next thing played. A single video can be moved to a different ceiling from the player, resuming where it was, without disturbing that default Medium Done","breadcrumbs":"Requirements Specification » 1. User Requirements","id":"2","title":"1. User Requirements"},"20":{"body":"UR-002, UR-003, UR-004, UR-005, UR-006, UR-007, UR-008, UR-009, UR-010, UR-011, UR-012, UR-013, UR-014, UR-015, UR-016, UR-017, UR-018, UR-019, UR-020, UR-021, UR-022, UR-023, UR-024, UR-025, UR-026, UR-027, UR-028, UR-029, UR-030, UR-031, UR-032, UR-033, UR-034, UR-035, UR-036, UR-037, UR-038, UR-039, UR-040, UR-041, UR-042, UR-043, UR-044, UR-045, UR-046, UR-047, UR-048, UR-049, UR-050, UR-051, UR-052, UR-053, UR-054, UR-055, UR-056, UR-057, UR-058, UR-059, UR-060, UR-061, UR-062, UR-063, UR-064, UR-065, UR-066, UR-067, UR-068, UR-069, UR-071, UR-072, UR-073, UR-074, UR-075, UR-076, UR-077, UR-078","breadcrumbs":"Traceability Matrix » User Requirements (UR)","id":"20","title":"User Requirements (UR)"},"200":{"body":"Locations: 8 file(s) File: src/lib/components/library/libraryMosaic.ts Line: 12 Context: Unknown File: src/lib/components/library/MosaicGrid.svelte Line: 14 Context: Unknown File: src/lib/components/library/mosaic.ts Line: 12 Context: Unknown File: src/lib/components/library/MosaicTile.svelte Line: 10 Context: Unknown File: src/lib/components/common/CachedImage.svelte Line: 18 Context: Unknown File: src/routes/library/+page.svelte Line: 53 Context: Unknown File: src/routes/library/+page.svelte Line: 288 Context: Unknown File: src/routes/+page.svelte Line: 124 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-174","id":"200","title":"DR-174"},"201":{"body":"Locations: 9 file(s) File: src/lib/api/bindings.ts Line: 2300 Context: export type Library = { id: string; name: string; collectionType: stri... File: src/lib/components/library/libraryMosaic.ts Line: 12 Context: Unknown File: src/lib/utils/favoritesView.ts Line: 44 Context: Unknown File: src/routes/library/+page.svelte Line: 53 Context: Unknown File: src-tauri/src/repository/types.rs Line: 47 Context: Unknown File: src-tauri/src/repository/types.rs Line: 404 Context: Unknown File: src-tauri/src/repository/types.rs Line: 722 Context: Unknown File: src-tauri/src/repository/types.rs Line: 743 Context: Unknown File: src-tauri/src/repository/types.rs Line: 755 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-175","id":"201","title":"DR-175"},"202":{"body":"Locations: 24 file(s) File: src/lib/api/bindings.ts Line: 2473 Context: Unknown File: src/lib/components/player/VideoPlayer.svelte Line: 378 Context: Unknown File: src/lib/components/player/subtitleTracks.test.ts Line: 67 Context: Unknown File: src/lib/components/player/subtitleTracks.test.ts Line: 93 Context: Unknown File: src/lib/components/player/subtitleTracks.test.ts Line: 110 Context: Unknown File: src/lib/components/player/subtitleTracks.ts Line: 75 Context: Unknown File: src-tauri/src/repository/device_profile.rs Line: 84 Context: Unknown File: src-tauri/src/repository/device_profile.rs Line: 99 Context: Unknown File: src-tauri/src/repository/device_profile.rs Line: 120 Context: Unknown File: src-tauri/src/repository/device_profile.rs Line: 150 Context: Unknown File: src-tauri/src/repository/device_profile.rs Line: 184 Context: Unknown File: src-tauri/src/repository/device_profile.rs Line: 293 Context: Unknown File: src-tauri/src/repository/device_profile.rs Line: 306 Context: Unknown File: src-tauri/src/repository/device_profile.rs Line: 344 Context: Unknown File: src-tauri/src/repository/device_profile.rs Line: 362 Context: Unknown File: src-tauri/src/repository/device_profile.rs Line: 376 Context: Unknown File: src-tauri/src/repository/device_profile.rs Line: 390 Context: Unknown File: src-tauri/src/repository/online.rs Line: 1765 Context: Unknown File: src-tauri/src/repository/online.rs Line: 1835 Context: Unknown File: src-tauri/src/repository/online.rs Line: 1948 Context: Unknown File: src-tauri/src/repository/online.rs Line: 1994 Context: Unknown File: src-tauri/src/repository/online.rs Line: 3043 Context: Unknown File: src-tauri/src/repository/online.rs Line: 3066 Context: Unknown File: src-tauri/src/repository/types.rs Line: 265 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-176","id":"202","title":"DR-176"},"203":{"body":"Locations: 9 file(s) File: src/lib/components/player/hlsRecovery.ts Line: 11 Context: Unknown File: src/lib/components/player/hlsRecovery.test.ts Line: 16 Context: Unknown File: src/lib/player/adapters/html5Adapter.test.ts Line: 255 Context: Unknown File: src-tauri/src/repository/online.rs Line: 73 Context: Unknown File: src-tauri/src/repository/online.rs Line: 89 Context: Unknown File: src-tauri/src/repository/online.rs Line: 467 Context: Unknown File: src-tauri/src/repository/online.rs Line: 527 Context: Unknown File: src-tauri/src/repository/online.rs Line: 3014 Context: Unknown File: src-tauri/src/repository/online.rs Line: 3110 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-177","id":"203","title":"DR-177"},"204":{"body":"Locations: 13 file(s) File: src-tauri/src/commands/player/mod.rs Line: 2015 Context: Unknown File: src-tauri/src/player/mod.rs Line: 304 Context: Unknown File: src-tauri/src/player/mod.rs Line: 500 Context: Unknown File: src-tauri/src/player/mod.rs Line: 945 Context: Unknown File: src-tauri/src/player/mod.rs Line: 960 Context: Unknown File: src-tauri/src/player/mod.rs Line: 971 Context: pub fn observed_duration(&self) -> Option { File: src-tauri/src/player/mod.rs Line: 1009 Context: Unknown File: src-tauri/src/player/mod.rs Line: 4157 Context: Unknown File: src-tauri/src/player/mod.rs Line: 4176 Context: Unknown File: src-tauri/src/player/mod.rs Line: 4193 Context: Unknown File: src-tauri/src/player/mod.rs Line: 4208 Context: Unknown File: src-tauri/src/player/mod.rs Line: 4222 Context: Unknown File: src-tauri/src/player/mod.rs Line: 4271 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-178","id":"204","title":"DR-178"},"205":{"body":"Locations: 15 file(s) File: src/lib/services/playbackReporting.ts Line: 27 Context: Unknown File: src/lib/services/playbackReporting.ts Line: 76 Context: * This function is therefore the *local* half only, which is what its ... File: src-tauri/src/player/mod.rs Line: 68 Context: Unknown File: src-tauri/src/player/mod.rs Line: 120 Context: Unknown File: src-tauri/src/player/mod.rs Line: 764 Context: Unknown File: src-tauri/src/player/mod.rs Line: 979 Context: Unknown File: src-tauri/src/player/mod.rs Line: 988 Context: pub fn set_report_sink(&self, sink: Arc) { File: src-tauri/src/player/mod.rs Line: 1307 Context: Unknown File: src-tauri/src/player/mod.rs Line: 1551 Context: Unknown File: src-tauri/src/player/mod.rs Line: 4244 Context: Unknown File: src-tauri/src/player/mod.rs Line: 4271 Context: Unknown File: src-tauri/src/player/mod.rs Line: 4309 Context: Unknown File: src-tauri/src/player/mod.rs Line: 4337 Context: Unknown File: src-tauri/src/player/mod.rs Line: 4366 Context: Unknown File: src-tauri/src/player/mod.rs Line: 4384 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-179","id":"205","title":"DR-179"},"206":{"body":"Locations: 6 file(s) File: src-tauri/src/commands/player/mod.rs Line: 460 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 480 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 3059 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 3079 Context: Unknown File: src-tauri/src/player/mod.rs Line: 878 Context: Unknown File: src-tauri/src/player/mod.rs Line: 4014 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-180","id":"206","title":"DR-180"},"207":{"body":"Locations: 11 file(s) File: src/lib/api/repository-client.ts Line: 235 Context: Unknown File: src/lib/api/bindings.ts Line: 1616 Context: Unknown File: src/lib/components/player/VideoPlayer.svelte Line: 1899 Context: Unknown File: src/lib/player/adapters/html5Adapter.test.ts Line: 216 Context: Unknown File: src/lib/player/adapters/html5Adapter.ts Line: 172 Context: Unknown File: src/routes/player/[id]/+page.svelte Line: 416 Context: Unknown File: src/routes/player/[id]/+page.svelte Line: 640 Context: Unknown File: src-tauri/src/commands/repository.rs Line: 592 Context: Unknown File: src-tauri/src/repository/online.rs Line: 527 Context: Unknown File: src-tauri/src/repository/online.rs Line: 2920 Context: Unknown File: src-tauri/src/repository/online.rs Line: 2964 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-181","id":"207","title":"DR-181"},"208":{"body":"Locations: 4 file(s) File: src/lib/components/player/VideoPlayer.nativeReveal.test.ts Line: 16 Context: Unknown File: src/lib/components/player/VideoPlayer.svelte Line: 529 Context: Unknown File: src/lib/components/player/mediaReady.ts Line: 4 Context: Unknown File: src/lib/components/player/mediaReady.test.ts Line: 4 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-182","id":"208","title":"DR-182"},"209":{"body":"Locations: 1 file(s) File: src/lib/utils/videoSurface.ts Line: 4 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-183","id":"209","title":"DR-183"},"21":{"body":"IR-003, IR-004, IR-006, IR-008, IR-009, IR-010, IR-011, IR-012, IR-013, IR-014, IR-015, IR-016, IR-018, IR-019, IR-020, IR-021, IR-022, IR-023, IR-024, IR-025, IR-026, IR-027, IR-028, IR-030, IR-031","breadcrumbs":"Traceability Matrix » Integration Requirements (IR)","id":"21","title":"Integration Requirements (IR)"},"210":{"body":"Locations: 1 file(s) File: src/lib/utils/nativeVideoLayers.test.ts Line: 4 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-185","id":"210","title":"DR-185"},"211":{"body":"Locations: 1 file(s) File: src/lib/components/player/VideoPlayer.svelte Line: 558 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-186","id":"211","title":"DR-186"},"212":{"body":"Locations: 2 file(s) File: src/lib/stores/nativeVideo.ts Line: 73 Context: Unknown File: src/lib/stores/nativeVideo.default.test.ts Line: 15 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-188","id":"212","title":"DR-188"},"213":{"body":"Locations: 5 file(s) File: src/lib/components/player/controlsVisibility.ts Line: 4 Context: Unknown File: src/lib/components/player/controlsVisibility.test.ts Line: 4 Context: Unknown File: src/lib/components/player/VideoPlayer.svelte Line: 496 Context: Unknown File: src/lib/components/player/VideoPlayer.svelte Line: 2013 Context: Unknown File: scripts/extract-traces.test.ts Line: 61 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-189","id":"213","title":"DR-189"},"214":{"body":"Locations: 2 file(s) File: src-tauri/src/player/mod.rs Line: 537 Context: Unknown File: src-tauri/src/player/mod.rs Line: 3252 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-193","id":"214","title":"DR-193"},"215":{"body":"Locations: 1 file(s) File: src/lib/components/player/VideoPlayer.svelte Line: 1553 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-195","id":"215","title":"DR-195"},"216":{"body":"Locations: 3 file(s) File: src/lib/components/player/backgroundAudioHandoff.ts Line: 110 Context: Unknown File: src/lib/components/player/VideoPlayer.svelte Line: 1925 Context: Unknown File: src/lib/components/player/backgroundAudioHandoff.test.ts Line: 95 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-196","id":"216","title":"DR-196"},"217":{"body":"Locations: 8 file(s) File: src/lib/stores/continueWatchingFilter.test.ts Line: 12 Context: Unknown File: src/lib/stores/home.ts Line: 2 Context: Unknown File: src/lib/stores/continueWatchingFilter.ts Line: 15 Context: Unknown File: src/lib/stores/continueWatchingFilter.ts Line: 91 Context: Unknown File: src/lib/stores/tv.ts Line: 3 Context: Unknown File: src-tauri/src/repository/online.rs Line: 910 Context: Unknown File: src-tauri/src/repository/online.rs Line: 3688 Context: Unknown File: src-tauri/src/repository/online.rs Line: 3709 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-197","id":"217","title":"DR-197"},"218":{"body":"Locations: 3 file(s) File: src/lib/services/imageCache.ts Line: 50 Context: Unknown File: src-tauri/src/lib.rs Line: 1122 Context: Unknown File: scripts/tauri-security-config.test.ts Line: 11 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-198","id":"218","title":"DR-198"},"219":{"body":"Locations: 7 file(s) File: src-tauri/src/lib.rs Line: 475 Context: Unknown File: src-tauri/src/player/seek.rs Line: 103 Context: Unknown File: src-tauri/src/player/seek.rs Line: 135 Context: Unknown File: src-tauri/src/player/seek.rs Line: 152 Context: Unknown File: src-tauri/src/player/seek.rs Line: 168 Context: / which backends reject (the \"Raw(-10)\" class of error). File: src-tauri/src/player/seek.rs Line: 181 Context: / this function exists to prevent. File: src-tauri/src/player/seek.rs Line: 192 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-201","id":"219","title":"DR-201"},"22":{"body":"DR-001, DR-002, DR-003, DR-004, DR-005, DR-006, DR-007, DR-009, DR-010, DR-011, DR-012, DR-013, DR-014, DR-015, DR-016, DR-017, DR-018, DR-020, DR-021, DR-022, DR-023, DR-024, DR-025, DR-026, DR-027, DR-028, DR-029, DR-030, DR-032, DR-033, DR-034, DR-035, DR-036, DR-037, DR-038, DR-039, DR-040, DR-041, DR-042, DR-043, DR-044, DR-045, DR-047, DR-048, DR-049, DR-050, DR-051, DR-052, DR-053, DR-054, DR-055, DR-056, DR-057, DR-058, DR-059, DR-060, DR-061, DR-062, DR-063, DR-064, DR-065, DR-066, DR-067, DR-068, DR-069, DR-070, DR-074, DR-075, DR-076, DR-077, DR-078, DR-079, DR-080, DR-081, DR-082, DR-083, DR-084, DR-085, DR-086, DR-087, DR-088, DR-089, DR-090, DR-091, DR-092, DR-093, DR-095, DR-096, DR-097, DR-098, DR-099, DR-100, DR-101, DR-102, DR-103, DR-104, DR-105, DR-106, DR-107, DR-108, DR-109, DR-110, DR-111, DR-112, DR-113, DR-114, DR-115, DR-116, DR-117, DR-118, DR-119, DR-120, DR-123, DR-126, DR-127, DR-128, DR-129, DR-130, DR-131, DR-132, DR-133, DR-134, DR-135, DR-136, DR-137, DR-140, DR-141, DR-142, DR-143, DR-147, DR-148, DR-149, DR-150, DR-151, DR-152, DR-153, DR-154, DR-155, DR-156, DR-157, DR-158, DR-159, DR-160, DR-162, DR-167, DR-168, DR-169, DR-170, DR-171, DR-173, DR-174, DR-175, DR-176, DR-177, DR-178, DR-179, DR-180, DR-181, DR-182, DR-183, DR-185, DR-186, DR-188, DR-189, DR-193, DR-195, DR-196, DR-197, DR-198, DR-201, DR-203, DR-204, DR-205, DR-206, DR-207, DR-209, DR-210, DR-211, DR-212, DR-213, DR-215, DR-216, DR-217, DR-218, DR-219, DR-220, DR-221, DR-223, DR-224","breadcrumbs":"Traceability Matrix » Development Requirements (DR)","id":"22","title":"Development Requirements (DR)"},"220":{"body":"Locations: 2 file(s) File: src-tauri/src/player/stream_end.rs Line: 54 Context: Unknown File: src-tauri/src/player/stream_end.rs Line: 91 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-203","id":"220","title":"DR-203"},"221":{"body":"Locations: 4 file(s) File: src/lib/utils/logger.ts Line: 4 Context: Unknown File: src/lib/utils/logger.ts Line: 202 Context: export function isLevelEnabled(level: LogLevel): boolean { File: src/lib/utils/logger.test.ts Line: 22 Context: Unknown File: src/lib/utils/logger.test.ts Line: 333 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-204","id":"221","title":"DR-204"},"222":{"body":"Locations: 1 file(s) File: eslint.config.js Line: 3 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-205","id":"222","title":"DR-205"},"223":{"body":"Locations: 1 file(s) File: src-tauri/rust-toolchain.toml Line: 3 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-206","id":"223","title":"DR-206"},"224":{"body":"Locations: 2 file(s) File: scripts/install-hooks.sh Line: 4 Context: Unknown File: scripts/hooks/pre-commit Line: 5 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-207","id":"224","title":"DR-207"},"225":{"body":"Locations: 44 file(s) File: src/lib/api/bindings.ts Line: 1107 Context: Unknown File: src/lib/api/bindings.ts Line: 1119 Context: Unknown File: src/lib/api/bindings.ts Line: 1137 Context: Unknown File: src/lib/api/bindings.ts Line: 2226 Context: export type EqPreset = \"flat\" | \"rock\" | \"pop\" | \"jazz\" | \"classical\" ... File: src/lib/api/bindings.ts Line: 2315 Context: Unknown File: src/lib/components/library/GenericMediaListPage.svelte Line: 125 Context: Unknown File: src/lib/stores/music.ts Line: 106 Context: Unknown File: src/routes/settings/+page.svelte Line: 1 Context: Unknown File: src/routes/settings/+page.svelte Line: 105 Context: Unknown File: src/routes/settings/+page.svelte Line: 201 Context: Unknown File: src/routes/settings/+page.svelte Line: 220 Context: Unknown File: src/routes/settings/+page.svelte Line: 596 Context: Unknown File: src-tauri/src/commands/library.rs Line: 10 Context: Unknown File: src-tauri/src/commands/library.rs Line: 47 Context: Unknown File: src-tauri/src/commands/library.rs Line: 68 Context: Unknown File: src-tauri/src/commands/library.rs Line: 83 Context: Unknown File: src-tauri/src/commands/library.rs Line: 112 Context: Unknown File: src-tauri/src/commands/library.rs Line: 184 Context: Unknown File: src-tauri/src/commands/library.rs Line: 220 Context: Unknown File: src-tauri/src/commands/library.rs Line: 274 Context: Unknown File: src-tauri/src/commands/library.rs Line: 295 Context: Unknown File: src-tauri/src/commands/library.rs Line: 307 Context: Unknown File: src-tauri/src/lib.rs Line: 1450 Context: Unknown File: src-tauri/src/repository/hybrid.rs Line: 164 Context: Unknown File: src-tauri/src/repository/hybrid.rs Line: 265 Context: Unknown File: src-tauri/src/repository/hybrid.rs Line: 363 Context: Unknown File: src-tauri/src/repository/hybrid.rs Line: 408 Context: Unknown File: src-tauri/src/repository/hybrid.rs Line: 526 Context: Unknown File: src-tauri/src/repository/hybrid.rs Line: 611 Context: Unknown File: src-tauri/src/repository/exclusions.rs Line: 22 Context: Unknown File: src-tauri/src/repository/exclusions.rs Line: 34 Context: Unknown File: src-tauri/src/repository/exclusions.rs Line: 48 Context: Unknown File: src-tauri/src/repository/exclusions.rs Line: 63 Context: Unknown File: src-tauri/src/repository/exclusions.rs Line: 87 Context: / pure function and can be tested without touching process state. File: src-tauri/src/repository/exclusions.rs Line: 123 Context: Unknown File: src-tauri/src/repository/exclusions.rs Line: 191 Context: Unknown File: src-tauri/src/repository/exclusions.rs Line: 203 Context: Unknown File: src-tauri/src/repository/exclusions.rs Line: 232 Context: Unknown File: src-tauri/src/repository/exclusions.rs Line: 275 Context: Unknown File: src-tauri/src/repository/exclusions.rs Line: 293 Context: Unknown File: src-tauri/src/repository/exclusions.rs Line: 316 Context: Unknown File: src-tauri/src/repository/exclusions.rs Line: 334 Context: Unknown File: src-tauri/src/settings.rs Line: 337 Context: Unknown File: src-tauri/src/settings.rs Line: 357 Context: impl LibrarySettings {","breadcrumbs":"Traceability Matrix » DR-209","id":"225","title":"DR-209"},"226":{"body":"Locations: 8 file(s) File: src-tauri/src/thumbnail/cache.rs Line: 39 Context: Unknown File: src-tauri/src/thumbnail/cache.rs Line: 74 Context: Unknown File: src-tauri/src/thumbnail/cache.rs Line: 200 Context: Unknown File: src-tauri/src/thumbnail/cache.rs Line: 628 Context: Unknown File: src-tauri/src/thumbnail/cache.rs Line: 668 Context: Unknown File: src-tauri/src/thumbnail/cache.rs Line: 706 Context: Unknown File: src-tauri/src/thumbnail/cache.rs Line: 735 Context: Unknown File: src-tauri/src/thumbnail/cache.rs Line: 766 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-210","id":"226","title":"DR-210"},"227":{"body":"Locations: 10 file(s) File: src-tauri/src/commands/download/mod.rs Line: 138 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 156 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 186 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 329 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 1374 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 1514 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 1867 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 2560 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 2589 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 2616 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-211","id":"227","title":"DR-211"},"228":{"body":"Locations: 9 file(s) File: src-tauri/src/commands/player/mod.rs Line: 1756 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 1775 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 2844 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 1253 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 4500 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 4553 Context: Unknown File: src-tauri/src/repository/online.rs Line: 814 Context: Unknown File: src-tauri/src/repository/online.rs Line: 3602 Context: Unknown File: src-tauri/src/repository/online.rs Line: 3638 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-212","id":"228","title":"DR-212"},"229":{"body":"Locations: 1 file(s) File: scripts/restore-ownership.sh Line: 4 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-213","id":"229","title":"DR-213"},"23":{"body":"JA-001, JA-002, JA-003, JA-004, JA-005, JA-006, JA-007, JA-008, JA-009, JA-010, JA-011, JA-012, JA-013, JA-014, JA-015, JA-016, JA-017, JA-018, JA-019, JA-020, JA-021, JA-022, JA-023, JA-024, JA-025, JA-026, JA-028, JA-029, JA-030, JA-031, JA-032, JA-033, JA-034, JA-035, JA-036","breadcrumbs":"Traceability Matrix » Jellyfin API Requirements (JA)","id":"23","title":"Jellyfin API Requirements (JA)"},"230":{"body":"Locations: 1 file(s) File: vitest.config.ts Line: 3 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-215","id":"230","title":"DR-215"},"231":{"body":"Locations: 1 file(s) File: src-tauri/deny.toml Line: 3 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-216","id":"231","title":"DR-216"},"232":{"body":"Locations: 5 file(s) File: src/lib/utils/updateCheck.test.ts Line: 4 Context: Unknown File: src/lib/utils/updateCheck.ts Line: 4 Context: Unknown File: src/routes/settings/+page.svelte Line: 446 Context: Unknown File: src/routes/settings/+page.svelte Line: 1299 Context: Unknown File: src-tauri/src/lib.rs Line: 1232 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-217","id":"232","title":"DR-217"},"233":{"body":"Locations: 16 file(s) File: src/lib/api/bindings.ts Line: 1787 Context: Unknown File: src/lib/api/bindings.ts Line: 1795 Context: Unknown File: src/lib/api/bindings.ts Line: 1809 Context: Unknown File: src/lib/utils/logger.ts Line: 202 Context: export function isLevelEnabled(level: LogLevel): boolean { File: src/lib/utils/logger.test.ts Line: 352 Context: Unknown File: src/routes/settings/+page.svelte Line: 494 Context: Unknown File: src/routes/settings/+page.svelte Line: 1219 Context: Unknown File: src-tauri/src/commands/diagnostics.rs Line: 3 Context: Unknown File: src-tauri/src/commands/diagnostics.rs Line: 91 Context: Unknown File: src-tauri/src/commands/diagnostics.rs Line: 115 Context: Unknown File: src-tauri/src/commands/diagnostics.rs Line: 187 Context: Unknown File: src-tauri/src/lib.rs Line: 1013 Context: Unknown File: src-tauri/src/lib.rs Line: 1143 Context: Unknown File: src-tauri/src/lib.rs Line: 1182 Context: pub fn run() { File: src-tauri/src/lib.rs Line: 1224 Context: Unknown File: src-tauri/src/utils/diagnostics.rs Line: 3 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-218","id":"233","title":"DR-218"},"234":{"body":"Locations: 2 file(s) File: scripts/release-notes.ts Line: 74 Context: Unknown File: scripts/release-notes.test.ts Line: 4 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-219","id":"234","title":"DR-219"},"235":{"body":"Locations: 1 file(s) File: scripts/check-release-artifacts.sh Line: 4 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-220","id":"235","title":"DR-220"},"236":{"body":"Locations: 2 file(s) File: scripts/build-windows-cross.sh Line: 49 Context: Unknown File: scripts/build-desktop-linux.sh Line: 29 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-221","id":"236","title":"DR-221"},"237":{"body":"Locations: 5 file(s) File: src-tauri/src/android_context.rs Line: 3 Context: Unknown File: src-tauri/src/android_context.rs Line: 80 Context: Unknown File: src-tauri/src/android_context.rs Line: 95 Context: Unknown File: src-tauri/src/lib.rs Line: 623 Context: Unknown File: src-tauri/src/lib.rs Line: 1294 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-223","id":"237","title":"DR-223"},"238":{"body":"Locations: 5 file(s) File: src/lib/api/bindings.ts Line: 42 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 855 Context: Unknown File: src-tauri/src/player/background_policy.rs Line: 3 Context: Unknown File: src-tauri/src/player/background_policy.rs Line: 53 Context: Unknown File: src-tauri/src/player/background_policy.rs Line: 80 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-224","id":"238","title":"DR-224"},"239":{"body":"Locations: 1 file(s) File: src-tauri/src/jellyfin/client.rs Line: 1 Context: Unknown","breadcrumbs":"Traceability Matrix » JA-001","id":"239","title":"JA-001"},"24":{"body":"","breadcrumbs":"Traceability Matrix » Detailed Mapping","id":"24","title":"Detailed Mapping"},"240":{"body":"Locations: 2 file(s) File: src-tauri/src/commands/auth.rs Line: 3 Context: Unknown File: src-tauri/src/jellyfin/client.rs Line: 1 Context: Unknown","breadcrumbs":"Traceability Matrix » JA-002","id":"240","title":"JA-002"},"241":{"body":"Locations: 1 file(s) File: src-tauri/src/jellyfin/client.rs Line: 1 Context: Unknown","breadcrumbs":"Traceability Matrix » JA-003","id":"241","title":"JA-003"},"242":{"body":"Locations: 3 file(s) File: src-tauri/src/commands/repository.rs Line: 4 Context: Unknown File: src-tauri/src/commands/catalog.rs Line: 3 Context: Unknown File: src-tauri/src/jellyfin/client.rs Line: 1 Context: Unknown","breadcrumbs":"Traceability Matrix » JA-004","id":"242","title":"JA-004"},"243":{"body":"Locations: 2 file(s) File: src-tauri/src/commands/repository.rs Line: 4 Context: Unknown File: src-tauri/src/repository/online.rs Line: 1174 Context: Unknown","breadcrumbs":"Traceability Matrix » JA-005","id":"243","title":"JA-005"},"244":{"body":"Locations: 2 file(s) File: src-tauri/src/commands/repository.rs Line: 4 Context: Unknown File: src-tauri/src/repository/online.rs Line: 1493 Context: Unknown","breadcrumbs":"Traceability Matrix » JA-006","id":"244","title":"JA-006"},"245":{"body":"Locations: 1 file(s) File: src-tauri/src/jellyfin/client.rs Line: 1 Context: Unknown","breadcrumbs":"Traceability Matrix » JA-007","id":"245","title":"JA-007"},"246":{"body":"Locations: 8 file(s) File: src/lib/api/bindings.ts Line: 2613 Context: Unknown File: src/lib/api/bindings.ts Line: 3232 Context: Unknown File: src/lib/components/player/VideoPlayer.svelte Line: 866 Context: Unknown File: src/lib/components/player/subtitleTracks.test.ts Line: 233 Context: Unknown File: src/lib/components/player/subtitleTracks.ts Line: 210 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 223 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 2977 Context: Unknown File: src-tauri/src/player/media.rs Line: 42 Context: Unknown","breadcrumbs":"Traceability Matrix » JA-008","id":"246","title":"JA-008"},"247":{"body":"Locations: 2 file(s) File: src/lib/components/player/VideoPlayer.svelte Line: 304 Context: Unknown File: src-tauri/src/repository/online.rs Line: 1174 Context: Unknown","breadcrumbs":"Traceability Matrix » JA-009","id":"247","title":"JA-009"},"248":{"body":"Locations: 2 file(s) File: src-tauri/src/commands/playback_reporting.rs Line: 3 Context: Unknown File: src-tauri/src/jellyfin/client.rs Line: 1 Context: Unknown","breadcrumbs":"Traceability Matrix » JA-010","id":"248","title":"JA-010"},"249":{"body":"Locations: 2 file(s) File: src-tauri/src/commands/playback_reporting.rs Line: 3 Context: Unknown File: src-tauri/src/jellyfin/client.rs Line: 1 Context: Unknown","breadcrumbs":"Traceability Matrix » JA-011","id":"249","title":"JA-011"},"25":{"body":"Locations: 2 file(s) File: src-tauri/src/player/backend.rs Line: 39 Context: pub fn playback_failed>(message: S) -> Self { File: src-tauri/src/player/mpv_backend_test.rs Line: 8 Context: Unknown","breadcrumbs":"Traceability Matrix » IR-003","id":"25","title":"IR-003"},"250":{"body":"Locations: 2 file(s) File: src-tauri/src/commands/playback_reporting.rs Line: 3 Context: Unknown File: src-tauri/src/jellyfin/client.rs Line: 1 Context: Unknown","breadcrumbs":"Traceability Matrix » JA-012","id":"250","title":"JA-012"},"251":{"body":"Locations: 5 file(s) File: src/lib/api/bindings.ts Line: 1515 Context: Unknown File: src-tauri/src/commands/repository.rs Line: 307 Context: Unknown File: src-tauri/src/repository/online.rs Line: 771 Context: Unknown File: src-tauri/src/repository/online.rs Line: 1205 Context: Unknown File: src-tauri/src/repository/online.rs Line: 1410 Context: Unknown","breadcrumbs":"Traceability Matrix » JA-013","id":"251","title":"JA-013"},"252":{"body":"Locations: 4 file(s) File: src/lib/api/bindings.ts Line: 1523 Context: Unknown File: src-tauri/src/commands/repository.rs Line: 333 Context: Unknown File: src-tauri/src/repository/online.rs Line: 910 Context: Unknown File: src-tauri/src/repository/online.rs Line: 1232 Context: Unknown","breadcrumbs":"Traceability Matrix » JA-014","id":"252","title":"JA-014"},"253":{"body":"Locations: 4 file(s) File: src/lib/api/bindings.ts Line: 1515 Context: Unknown File: src-tauri/src/commands/repository.rs Line: 307 Context: Unknown File: src-tauri/src/repository/online.rs Line: 1205 Context: Unknown File: src-tauri/src/repository/online.rs Line: 1410 Context: Unknown","breadcrumbs":"Traceability Matrix » JA-015","id":"253","title":"JA-015"},"254":{"body":"Locations: 2 file(s) File: src-tauri/src/commands/catalog.rs Line: 3 Context: Unknown File: src-tauri/src/repository/online.rs Line: 888 Context: Unknown","breadcrumbs":"Traceability Matrix » JA-016","id":"254","title":"JA-016"},"255":{"body":"Locations: 1 file(s) File: src-tauri/src/jellyfin/client.rs Line: 1 Context: Unknown","breadcrumbs":"Traceability Matrix » JA-017","id":"255","title":"JA-017"},"256":{"body":"Locations: 1 file(s) File: src-tauri/src/repository/online.rs Line: 2275 Context: Unknown","breadcrumbs":"Traceability Matrix » JA-018","id":"256","title":"JA-018"},"257":{"body":"Locations: 1 file(s) File: src-tauri/src/commands/playlist.rs Line: 4 Context: Unknown","breadcrumbs":"Traceability Matrix » JA-019","id":"257","title":"JA-019"},"258":{"body":"Locations: 1 file(s) File: src-tauri/src/commands/playlist.rs Line: 4 Context: Unknown","breadcrumbs":"Traceability Matrix » JA-020","id":"258","title":"JA-020"},"259":{"body":"Locations: 6 file(s) File: src/lib/components/sessions/SessionPickerModal.svelte Line: 1 Context: Unknown File: src/lib/components/sessions/SessionsList.svelte Line: 1 Context: Unknown File: src/lib/stores/lmsSync.ts Line: 12 Context: Unknown File: src-tauri/src/commands/sessions.rs Line: 1 Context: Unknown File: src-tauri/src/session_poller/mod.rs Line: 6 Context: Unknown File: src-tauri/src/jellyfin/client.rs Line: 1 Context: Unknown","breadcrumbs":"Traceability Matrix » JA-021","id":"259","title":"JA-021"},"26":{"body":"Locations: 1 file(s) File: src-tauri/src/player/backend.rs Line: 39 Context: pub fn playback_failed>(message: S) -> Self {","breadcrumbs":"Traceability Matrix » IR-004","id":"26","title":"IR-004"},"260":{"body":"Locations: 3 file(s) File: src/lib/components/sessions/RemoteControls.svelte Line: 1 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 1 Context: Unknown File: src-tauri/src/commands/player/remote.rs Line: 3 Context: Unknown","breadcrumbs":"Traceability Matrix » JA-022","id":"260","title":"JA-022"},"261":{"body":"Locations: 3 file(s) File: src/lib/components/sessions/RemoteControls.svelte Line: 1 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 1 Context: Unknown File: src-tauri/src/commands/player/remote.rs Line: 3 Context: Unknown","breadcrumbs":"Traceability Matrix » JA-023","id":"261","title":"JA-023"},"262":{"body":"Locations: 2 file(s) File: src/lib/components/sessions/RemoteControls.svelte Line: 1 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 1 Context: Unknown","breadcrumbs":"Traceability Matrix » JA-024","id":"262","title":"JA-024"},"263":{"body":"Locations: 4 file(s) File: src/lib/components/sessions/SessionPickerModal.svelte Line: 1 Context: Unknown File: src/lib/stores/lmsSync.ts Line: 12 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 1 Context: Unknown File: src-tauri/src/commands/player/remote.rs Line: 3 Context: Unknown","breadcrumbs":"Traceability Matrix » JA-025","id":"263","title":"JA-025"},"264":{"body":"Locations: 3 file(s) File: src/lib/components/sessions/RemoteControls.svelte Line: 1 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 1 Context: Unknown File: src-tauri/src/commands/player/remote.rs Line: 3 Context: Unknown","breadcrumbs":"Traceability Matrix » JA-026","id":"264","title":"JA-026"},"265":{"body":"Locations: 1 file(s) File: src-tauri/src/commands/storage/thumbnails.rs Line: 3 Context: Unknown","breadcrumbs":"Traceability Matrix » JA-028","id":"265","title":"JA-028"},"266":{"body":"Locations: 3 file(s) File: src/lib/components/library/CrewLinks.svelte Line: 1 Context: Unknown File: src/lib/components/library/CastSection.svelte Line: 1 Context: Unknown File: src-tauri/src/commands/repository.rs Line: 4 Context: Unknown","breadcrumbs":"Traceability Matrix » JA-029","id":"266","title":"JA-029"},"267":{"body":"Locations: 3 file(s) File: src/lib/components/library/PersonDetailView.svelte Line: 1 Context: Unknown File: src-tauri/src/commands/repository.rs Line: 4 Context: Unknown File: src-tauri/src/repository/online.rs Line: 2415 Context: Unknown","breadcrumbs":"Traceability Matrix » JA-030","id":"267","title":"JA-030"},"268":{"body":"Locations: 3 file(s) File: src/lib/components/library/PersonDetailView.svelte Line: 1 Context: Unknown File: src-tauri/src/commands/repository.rs Line: 4 Context: Unknown File: src-tauri/src/repository/online.rs Line: 2428 Context: Unknown","breadcrumbs":"Traceability Matrix » JA-031","id":"268","title":"JA-031"},"269":{"body":"Locations: 10 file(s) File: src/lib/api/repository-client.ts Line: 253 Context: Unknown File: src/lib/api/repository-client.test.ts Line: 442 Context: Unknown File: src/lib/api/repository-client.test.ts Line: 459 Context: Unknown File: src/lib/api/bindings.ts Line: 1630 Context: Unknown File: src-tauri/src/commands/repository.rs Line: 611 Context: Unknown File: src-tauri/src/repository/hybrid.rs Line: 103 Context: Unknown File: src-tauri/src/repository/mod.rs Line: 131 Context: Unknown File: src-tauri/src/repository/online.rs Line: 620 Context: Unknown File: src-tauri/src/repository/online.rs Line: 3141 Context: Unknown File: src-tauri/src/repository/online.rs Line: 3182 Context: Unknown","breadcrumbs":"Traceability Matrix » JA-032","id":"269","title":"JA-032"},"27":{"body":"Locations: 4 file(s) File: src-tauri/src/player/mod.rs Line: 145 Context: Unknown File: src-tauri/src/player/mod.rs Line: 167 Context: Unknown File: src-tauri/src/player/android/mod.rs Line: 1123 Context: Unknown File: src-tauri/src/player/android/mod.rs Line: 1425 Context: Unknown","breadcrumbs":"Traceability Matrix » IR-006","id":"27","title":"IR-006"},"270":{"body":"Locations: 7 file(s) File: src/lib/api/bindings.ts Line: 1707 Context: Unknown File: src-tauri/src/commands/repository.rs Line: 898 Context: Unknown File: src-tauri/src/repository/mod.rs Line: 235 Context: Unknown File: src-tauri/src/repository/online.rs Line: 933 Context: Unknown File: src-tauri/src/repository/online.rs Line: 2251 Context: Unknown File: src-tauri/src/repository/online.rs Line: 2321 Context: Unknown File: src-tauri/src/repository/online.rs Line: 3510 Context: Unknown","breadcrumbs":"Traceability Matrix » JA-033","id":"270","title":"JA-033"},"271":{"body":"Locations: 3 file(s) File: src-tauri/src/repository/online.rs Line: 771 Context: Unknown File: src-tauri/src/repository/online.rs Line: 1058 Context: Unknown File: src-tauri/src/repository/online.rs Line: 3728 Context: Unknown","breadcrumbs":"Traceability Matrix » JA-034","id":"271","title":"JA-034"},"272":{"body":"Locations: 2 file(s) File: src-tauri/src/repository/mod.rs Line: 255 Context: Unknown File: src-tauri/src/repository/online.rs Line: 2366 Context: Unknown","breadcrumbs":"Traceability Matrix » JA-035","id":"272","title":"JA-035"},"273":{"body":"Locations: 2 file(s) File: src-tauri/src/repository/online.rs Line: 910 Context: Unknown File: src-tauri/src/repository/online.rs Line: 3688 Context: Unknown","breadcrumbs":"Traceability Matrix » JA-036","id":"273","title":"JA-036"},"274":{"body":"Locations: 46 file(s) File: src/lib/api/bindings.ts Line: 192 Context: Unknown File: src/lib/api/bindings.ts Line: 2663 Context: Unknown File: src/lib/api/bindings.ts Line: 2699 Context: export type PlaybackProgress = { itemId: string; File: src/lib/api/bindings.ts Line: 2803 Context: Unknown File: src/lib/components/player/controlsVisibility.ts Line: 4 Context: Unknown File: src/lib/components/player/backgroundAudioHandoff.ts Line: 110 Context: Unknown File: src/lib/components/player/controlsVisibility.test.ts Line: 4 Context: Unknown File: src/lib/components/player/VideoPlayer.nativeReveal.test.ts Line: 16 Context: Unknown File: src/lib/components/player/VideoPlayer.svelte Line: 1 Context: Unknown File: src/lib/components/player/VideoPlayer.svelte Line: 496 Context: Unknown File: src/lib/components/player/VideoPlayer.svelte Line: 529 Context: Unknown File: src/lib/components/player/VideoPlayer.svelte Line: 558 Context: Unknown File: src/lib/components/player/VideoPlayer.svelte Line: 1553 Context: Unknown File: src/lib/components/player/VideoPlayer.svelte Line: 1925 Context: Unknown File: src/lib/components/player/VideoPlayer.svelte Line: 2013 Context: Unknown File: src/lib/components/player/mediaReady.ts Line: 4 Context: Unknown File: src/lib/components/player/backgroundAudioHandoff.test.ts Line: 95 Context: Unknown File: src/lib/components/player/mediaReady.test.ts Line: 4 Context: Unknown File: src/lib/player/adapters/rustReportHost.ts Line: 13 Context: Unknown File: src/lib/player/adapters/adapterSelection.test.ts Line: 4 Context: Unknown File: src/lib/player/adapters/html5Adapter.ts Line: 19 Context: Unknown File: src/lib/player/adapters/webviewAudioAdapter.ts Line: 18 Context: Unknown File: src/lib/player/adapters/index.ts Line: 20 Context: Unknown File: src/lib/player/adapters/nativeAdapter.ts Line: 25 Context: Unknown File: src/lib/player/adapters/types.ts Line: 15 Context: Unknown File: src/lib/services/playbackCapabilities.ts Line: 4 Context: Unknown File: src/lib/services/webviewAudio.ts Line: 18 Context: Unknown File: src/lib/utils/videoSurface.ts Line: 4 Context: Unknown File: src/lib/utils/nativeVideoLayers.test.ts Line: 4 Context: Unknown File: src/lib/stores/nativeVideo.ts Line: 3 Context: Unknown File: src/lib/stores/nativeVideo.ts Line: 73 Context: Unknown File: src/lib/stores/nativeVideo.default.test.ts Line: 15 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 1 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 1970 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 1987 Context: Unknown File: src-tauri/src/commands/mod.rs Line: 2 Context: Unknown File: src-tauri/src/player/backend.rs Line: 39 Context: pub fn playback_failed>(message: S) -> Self { File: src-tauri/src/player/backend.rs Line: 251 Context: Unknown File: src-tauri/src/player/mod.rs Line: 2 Context: Unknown File: src-tauri/src/player/mod.rs Line: 537 Context: Unknown File: src-tauri/src/player/mod.rs Line: 3252 Context: Unknown File: src-tauri/src/player/media.rs Line: 60 Context: Unknown File: src-tauri/src/player/media.rs Line: 142 Context: pub enum MediaType { File: src-tauri/src/player/mpv_backend_test.rs Line: 8 Context: Unknown File: src-tauri/src/player/webview_audio_backend.rs Line: 25 Context: Unknown File: src-tauri/src/player/webview_audio_backend.rs Line: 184 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-003","id":"274","title":"UR-003"},"275":{"body":"Locations: 65 file(s) File: src/lib/api/repository-client.ts Line: 235 Context: Unknown File: src/lib/api/bindings.ts Line: 349 Context: Unknown File: src/lib/api/bindings.ts Line: 1616 Context: Unknown File: src/lib/api/bindings.ts Line: 2699 Context: export type PlaybackProgress = { itemId: string; File: src/lib/api/bindings.ts Line: 2803 Context: Unknown File: src/lib/components/player/hlsRecovery.ts Line: 11 Context: Unknown File: src/lib/components/player/AudioPlayer.svelte Line: 1 Context: Unknown File: src/lib/components/player/VideoPlayer.nativeReveal.test.ts Line: 16 Context: Unknown File: src/lib/components/player/VideoPlayer.svelte Line: 529 Context: Unknown File: src/lib/components/player/VideoPlayer.svelte Line: 1899 Context: Unknown File: src/lib/components/player/mediaReady.ts Line: 4 Context: Unknown File: src/lib/components/player/mediaReady.test.ts Line: 4 Context: Unknown File: src/lib/components/player/hlsRecovery.test.ts Line: 16 Context: Unknown File: src/lib/player/adapters/html5Adapter.test.ts Line: 216 Context: Unknown File: src/lib/player/adapters/adapterSelection.test.ts Line: 4 Context: Unknown File: src/lib/player/adapters/html5Adapter.ts Line: 172 Context: Unknown File: src/lib/player/adapters/index.ts Line: 20 Context: Unknown File: src/lib/services/playerEvents.regression.test.ts Line: 155 Context: Unknown File: src/lib/services/playerEvents.ts Line: 292 Context: Unknown File: src/lib/services/preload.test.ts Line: 4 Context: Unknown File: src/lib/services/preload.ts Line: 5 Context: Unknown File: src/lib/utils/videoSurface.ts Line: 4 Context: Unknown File: src/lib/utils/nativeVideoLayers.test.ts Line: 4 Context: Unknown File: src/lib/stores/nativeVideo.ts Line: 3 Context: Unknown File: src/lib/stores/nativeVideo.ts Line: 73 Context: Unknown File: src/lib/stores/nativeVideo.default.test.ts Line: 15 Context: Unknown File: src/routes/player/[id]/+page.svelte Line: 416 Context: Unknown File: src/routes/player/[id]/+page.svelte Line: 640 Context: Unknown File: src-tauri/src/commands/player/timers.rs Line: 299 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 1 Context: Unknown File: src-tauri/src/commands/mod.rs Line: 2 Context: Unknown File: src-tauri/src/commands/repository.rs Line: 592 Context: Unknown File: src-tauri/src/player/backend.rs Line: 39 Context: pub fn playback_failed>(message: S) -> Self { File: src-tauri/src/player/backend.rs Line: 251 Context: Unknown File: src-tauri/src/player/mod.rs Line: 2 Context: Unknown File: src-tauri/src/player/mod.rs Line: 1690 Context: Unknown File: src-tauri/src/player/mod.rs Line: 1780 Context: Unknown File: src-tauri/src/player/mod.rs Line: 1811 Context: Unknown File: src-tauri/src/player/media.rs Line: 60 Context: Unknown File: src-tauri/src/player/media.rs Line: 142 Context: pub enum MediaType { File: src-tauri/src/player/stream_end.rs Line: 91 Context: Unknown File: src-tauri/src/player/android/mod.rs Line: 438 Context: Unknown File: src-tauri/src/player/mpv_backend_test.rs Line: 8 Context: Unknown File: src-tauri/src/player/webview_audio_backend.rs Line: 25 Context: Unknown File: src-tauri/src/player/webview_audio_backend.rs Line: 184 Context: Unknown File: src-tauri/src/repository/device_profile.rs Line: 29 Context: Unknown File: src-tauri/src/repository/device_profile.rs Line: 40 Context: pub fn clamp_max_audio_channels(reported: Option) -> u32 { File: src-tauri/src/repository/device_profile.rs Line: 120 Context: Unknown File: src-tauri/src/repository/device_profile.rs Line: 150 Context: Unknown File: src-tauri/src/repository/device_profile.rs Line: 213 Context: Unknown File: src-tauri/src/repository/device_profile.rs Line: 238 Context: Unknown File: src-tauri/src/repository/device_profile.rs Line: 260 Context: Unknown File: src-tauri/src/repository/device_profile.rs Line: 277 Context: Unknown File: src-tauri/src/repository/device_profile.rs Line: 306 Context: Unknown File: src-tauri/src/repository/device_profile.rs Line: 344 Context: Unknown File: src-tauri/src/repository/device_profile.rs Line: 362 Context: Unknown File: src-tauri/src/repository/online.rs Line: 527 Context: Unknown File: src-tauri/src/repository/online.rs Line: 1765 Context: Unknown File: src-tauri/src/repository/online.rs Line: 1835 Context: Unknown File: src-tauri/src/repository/online.rs Line: 1948 Context: Unknown File: src-tauri/src/repository/online.rs Line: 2216 Context: Unknown File: src-tauri/src/repository/online.rs Line: 2920 Context: Unknown File: src-tauri/src/repository/online.rs Line: 2964 Context: Unknown File: src-tauri/src/repository/online.rs Line: 3043 Context: Unknown File: src-tauri/src/repository/online.rs Line: 3402 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-004","id":"275","title":"UR-004"},"276":{"body":"Locations: 103 file(s) File: src/lib/api/bindings.ts Line: 192 Context: Unknown File: src/lib/api/bindings.ts Line: 2663 Context: Unknown File: src/lib/api/bindings.ts Line: 2821 Context: Unknown File: src/lib/api/bindings.ts Line: 2878 Context: Unknown File: src/lib/api/bindings.ts Line: 3075 Context: export type RemoteSessionStatus = { position: number; duration: number... File: src/lib/components/BottomUi.svelte Line: 23 Context: Unknown File: src/lib/components/player/VideoPlayer.tapSurface.test.ts Line: 5 Context: Unknown File: src/lib/components/player/VideoPlayer.touchScrub.test.ts Line: 21 Context: Unknown File: src/lib/components/player/videoFit.ts Line: 7 Context: Unknown File: src/lib/components/player/backgroundAudioHandoff.ts Line: 69 Context: Unknown File: src/lib/components/player/AudioPlayer.svelte Line: 1 Context: Unknown File: src/lib/components/player/tapGestures.ts Line: 21 Context: Unknown File: src/lib/components/player/playerSurface.test.ts Line: 13 Context: Unknown File: src/lib/components/player/VideoPlayer.svelte Line: 1 Context: Unknown File: src/lib/components/player/VideoPlayer.svelte Line: 558 Context: Unknown File: src/lib/components/player/VideoPlayer.svelte Line: 970 Context: Unknown File: src/lib/components/player/VideoPlayer.svelte Line: 1553 Context: Unknown File: src/lib/components/player/Controls.svelte Line: 1 Context: Unknown File: src/lib/components/player/playerSurface.ts Line: 8 Context: Unknown File: src/lib/components/player/MiniPlayer.svelte Line: 1 Context: Unknown File: src/lib/player/adapters/html5Adapter.test.ts Line: 216 Context: Unknown File: src/lib/player/adapters/rustReportHost.ts Line: 13 Context: Unknown File: src/lib/player/adapters/html5Adapter.ts Line: 19 Context: Unknown File: src/lib/player/adapters/html5Adapter.ts Line: 172 Context: Unknown File: src/lib/player/adapters/webviewAudioAdapter.ts Line: 18 Context: Unknown File: src/lib/player/adapters/nativeAdapter.ts Line: 25 Context: Unknown File: src/lib/player/adapters/nativeAdapter.ts Line: 58 Context: Unknown File: src/lib/player/adapters/types.ts Line: 15 Context: Unknown File: src/lib/player/transport.authority.test.ts Line: 4 Context: Unknown File: src/lib/player/index.ts Line: 15 Context: Unknown File: src/lib/services/playerEvents.regression.test.ts Line: 9 Context: Unknown File: src/lib/services/playbackCapabilities.ts Line: 4 Context: Unknown File: src/lib/services/playerEvents.ts Line: 8 Context: Unknown File: src/lib/services/playerEvents.ts Line: 159 Context: Unknown File: src/lib/services/playerEvents.ts Line: 189 Context: Unknown File: src/lib/services/webviewAudio.ts Line: 18 Context: Unknown File: src/lib/services/playerEvents.test.ts Line: 4 Context: Unknown File: src/lib/services/playbackReporting.ts Line: 10 Context: Unknown File: src/lib/services/playbackReporting.ts Line: 27 Context: Unknown File: src/lib/services/playbackReporting.ts Line: 76 Context: * This function is therefore the *local* half only, which is what its ... File: src/lib/services/playbackReporting.ts Line: 107 Context: Unknown File: src/lib/services/playbackReporting.test.ts Line: 4 Context: Unknown File: src/lib/utils/layoutShell.test.ts Line: 14 Context: Unknown File: src/lib/utils/layoutShell.ts Line: 17 Context: Unknown File: src/lib/utils/duration.test.ts Line: 4 Context: Unknown File: src/lib/stores/playerVisibility.test.ts Line: 9 Context: Unknown File: src/lib/stores/appState.ts Line: 2 Context: Unknown File: src/lib/stores/player.ts Line: 8 Context: Unknown File: src/lib/stores/player.ts Line: 31 Context: Unknown File: src/lib/stores/queue.ts Line: 7 Context: Unknown File: src/lib/stores/queue.ts Line: 108 Context: Unknown File: src/lib/stores/queue.ts Line: 113 Context: async function next() { File: src/lib/stores/queue.ts Line: 118 Context: async function previous() { File: src/lib/stores/queue.ts Line: 123 Context: async function skipTo(index: number) { File: src/lib/stores/queue.ts Line: 128 Context: async function toggleShuffle() { File: src/lib/stores/queue.test.ts Line: 4 Context: Unknown File: src/routes/player/[id]/+page.svelte Line: 640 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 1 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 1970 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 1987 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 2015 Context: Unknown File: src-tauri/src/commands/player/session.rs Line: 3 Context: Unknown File: src-tauri/src/commands/mod.rs Line: 2 Context: Unknown File: src-tauri/src/commands/conversions.rs Line: 3 Context: Unknown File: src-tauri/src/player/mpv_backend.rs Line: 564 Context: Unknown File: src-tauri/src/player/mpv_backend.rs Line: 573 Context: Unknown File: src-tauri/src/player/backend.rs Line: 42 Context: Unknown File: src-tauri/src/player/backend.rs Line: 46 Context: Unknown File: src-tauri/src/player/backend.rs Line: 50 Context: Unknown File: src-tauri/src/player/backend.rs Line: 54 Context: Unknown File: src-tauri/src/player/backend.rs Line: 58 Context: Unknown File: src-tauri/src/player/mod.rs Line: 2 Context: Unknown File: src-tauri/src/player/mod.rs Line: 304 Context: Unknown File: src-tauri/src/player/mod.rs Line: 500 Context: Unknown File: src-tauri/src/player/mod.rs Line: 537 Context: Unknown File: src-tauri/src/player/mod.rs Line: 652 Context: Unknown File: src-tauri/src/player/mod.rs Line: 660 Context: pub fn is_html5_active(&self) -> bool { File: src-tauri/src/player/mod.rs Line: 704 Context: Unknown File: src-tauri/src/player/mod.rs Line: 764 Context: Unknown File: src-tauri/src/player/mod.rs Line: 873 Context: Unknown File: src-tauri/src/player/mod.rs Line: 945 Context: Unknown File: src-tauri/src/player/mod.rs Line: 960 Context: Unknown File: src-tauri/src/player/mod.rs Line: 971 Context: pub fn observed_duration(&self) -> Option { File: src-tauri/src/player/mod.rs Line: 1009 Context: Unknown File: src-tauri/src/player/mod.rs Line: 1307 Context: Unknown File: src-tauri/src/player/mod.rs Line: 1586 Context: Unknown File: src-tauri/src/player/mod.rs Line: 1602 Context: Unknown File: src-tauri/src/player/mod.rs Line: 3252 Context: Unknown File: src-tauri/src/player/mod.rs Line: 4077 Context: Unknown File: src-tauri/src/player/mod.rs Line: 4120 Context: Unknown File: src-tauri/src/player/mod.rs Line: 4193 Context: Unknown File: src-tauri/src/player/mod.rs Line: 4208 Context: Unknown File: src-tauri/src/player/mod.rs Line: 4366 Context: Unknown File: src-tauri/src/player/mod.rs Line: 4384 Context: Unknown File: src-tauri/src/player/events.rs Line: 6 Context: Unknown File: src-tauri/src/player/events.rs Line: 23 Context: Unknown File: src-tauri/src/player/queue.rs Line: 8 Context: Unknown File: src-tauri/src/player/queue.rs Line: 20 Context: pub enum RepeatMode { File: src-tauri/src/player/queue.rs Line: 535 Context: pub enum AddPosition { File: src-tauri/src/player/state.rs Line: 7 Context: Unknown File: src-tauri/src/player/state.rs Line: 25 Context: Unknown File: src-tauri/src/player/webview_audio_backend.rs Line: 25 Context: Unknown File: src-tauri/src/player/webview_audio_backend.rs Line: 184 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-005","id":"276","title":"UR-005"},"277":{"body":"Locations: 4 file(s) File: src/lib/components/library/GenericMediaListPage.searchEvent.test.ts Line: 12 Context: Unknown File: src/lib/stores/library.ts Line: 2 Context: Unknown File: src-tauri/src/commands/repository.rs Line: 4 Context: Unknown File: src-tauri/src/repository/online.rs Line: 1493 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-008","id":"277","title":"UR-008"},"278":{"body":"Locations: 14 file(s) File: src/lib/api/bindings.ts Line: 658 Context: Unknown File: src/lib/api/bindings.ts Line: 674 Context: Unknown File: src/lib/services/deviceId.test.ts Line: 7 Context: Unknown File: src/lib/services/deviceId.ts Line: 8 Context: Unknown File: src/lib/services/deviceId.ts Line: 26 Context: Unknown File: src/lib/stores/auth.ts Line: 6 Context: Unknown File: src/lib/stores/auth.ts Line: 276 Context: Unknown File: src/lib/stores/auth.ts Line: 303 Context: Unknown File: src/lib/stores/auth.test.ts Line: 4 Context: Unknown File: src-tauri/src/commands/mod.rs Line: 2 Context: Unknown File: src-tauri/src/commands/device.rs Line: 4 Context: Unknown File: src-tauri/src/commands/device.rs Line: 24 Context: Unknown File: src-tauri/src/commands/device.rs Line: 81 Context: Unknown File: src-tauri/src/jellyfin/client.rs Line: 1 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-009","id":"278","title":"UR-009"},"279":{"body":"Locations: 20 file(s) File: src/lib/api/bindings.ts Line: 899 Context: Unknown File: src/lib/stores/downloads.ts Line: 2 Context: Unknown File: src/lib/stores/downloads.ts Line: 210 Context: Unknown File: src/lib/stores/downloads.test.ts Line: 2 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 442 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 473 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 524 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 546 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 679 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 2527 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 3238 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 3303 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 3334 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 3367 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 3396 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 3410 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 3437 Context: Unknown File: src-tauri/src/commands/catalog.rs Line: 916 Context: Unknown File: src-tauri/src/commands/catalog.rs Line: 957 Context: Unknown File: src-tauri/src/repository/hybrid.rs Line: 135 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-018","id":"279","title":"UR-018"},"28":{"body":"Locations: 1 file(s) File: src-tauri/src/player/android/mod.rs Line: 438 Context: Unknown","breadcrumbs":"Traceability Matrix » IR-008","id":"28","title":"IR-008"},"280":{"body":"Locations: 18 file(s) File: src/lib/api/bindings.ts Line: 1515 Context: Unknown File: src/lib/api/bindings.ts Line: 2878 Context: Unknown File: src/lib/services/playerEvents.ts Line: 8 Context: Unknown File: src/lib/services/playerEvents.test.ts Line: 4 Context: Unknown File: src/lib/services/playbackReporting.ts Line: 10 Context: Unknown File: src/lib/services/playbackReporting.test.ts Line: 4 Context: Unknown File: src/routes/player/[id]/+page.svelte Line: 416 Context: Unknown File: src-tauri/src/commands/storage/mod.rs Line: 3 Context: Unknown File: src-tauri/src/commands/mod.rs Line: 2 Context: Unknown File: src-tauri/src/commands/playback_reporting.rs Line: 3 Context: Unknown File: src-tauri/src/commands/repository.rs Line: 307 Context: Unknown File: src-tauri/src/storage/mod.rs Line: 157 Context: pub fn file_size(&self) -> Option { File: src-tauri/src/player/mod.rs Line: 2 Context: Unknown File: src-tauri/src/player/events.rs Line: 6 Context: Unknown File: src-tauri/src/player/events.rs Line: 23 Context: Unknown File: src-tauri/src/repository/online.rs Line: 771 Context: Unknown File: src-tauri/src/repository/online.rs Line: 1205 Context: Unknown File: src-tauri/src/repository/online.rs Line: 1410 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-019","id":"280","title":"UR-019"},"281":{"body":"Locations: 51 file(s) File: src/lib/api/bindings.ts Line: 172 Context: Unknown File: src/lib/api/bindings.ts Line: 2473 Context: Unknown File: src/lib/api/bindings.ts Line: 2613 Context: Unknown File: src/lib/api/bindings.ts Line: 3232 Context: Unknown File: src/lib/components/player/VideoPlayer.svelte Line: 1 Context: Unknown File: src/lib/components/player/VideoPlayer.svelte Line: 378 Context: Unknown File: src/lib/components/player/VideoPlayer.svelte Line: 396 Context: Unknown File: src/lib/components/player/VideoPlayer.svelte Line: 405 Context: Unknown File: src/lib/components/player/VideoPlayer.svelte Line: 866 Context: Unknown File: src/lib/components/player/VideoPlayer.svelte Line: 2328 Context: function toggleSubtitleMenu() { File: src/lib/components/player/VideoPlayer.svelte Line: 2361 Context: Unknown File: src/lib/components/player/subtitleTracks.test.ts Line: 18 Context: Unknown File: src/lib/components/player/subtitleTracks.test.ts Line: 67 Context: Unknown File: src/lib/components/player/subtitleTracks.test.ts Line: 93 Context: Unknown File: src/lib/components/player/subtitleTracks.test.ts Line: 110 Context: Unknown File: src/lib/components/player/subtitleTracks.test.ts Line: 233 Context: Unknown File: src/lib/components/player/subtitleTracks.test.ts Line: 353 Context: Unknown File: src/lib/components/player/subtitleTracks.ts Line: 20 Context: Unknown File: src/lib/components/player/subtitleTracks.ts Line: 75 Context: Unknown File: src/lib/components/player/subtitleTracks.ts Line: 210 Context: Unknown File: src/lib/components/player/subtitleTracks.ts Line: 243 Context: Unknown File: src/lib/player/adapters/html5Adapter.ts Line: 19 Context: Unknown File: src/lib/player/adapters/types.ts Line: 15 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 1 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 223 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 422 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 1734 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 2888 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 2945 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 2977 Context: Unknown File: src-tauri/src/player/backend.rs Line: 119 Context: Unknown File: src-tauri/src/player/media.rs Line: 42 Context: Unknown File: src-tauri/src/repository/device_profile.rs Line: 84 Context: Unknown File: src-tauri/src/repository/device_profile.rs Line: 99 Context: Unknown File: src-tauri/src/repository/device_profile.rs Line: 120 Context: Unknown File: src-tauri/src/repository/device_profile.rs Line: 150 Context: Unknown File: src-tauri/src/repository/device_profile.rs Line: 184 Context: Unknown File: src-tauri/src/repository/device_profile.rs Line: 293 Context: Unknown File: src-tauri/src/repository/device_profile.rs Line: 306 Context: Unknown File: src-tauri/src/repository/device_profile.rs Line: 344 Context: Unknown File: src-tauri/src/repository/device_profile.rs Line: 362 Context: Unknown File: src-tauri/src/repository/device_profile.rs Line: 376 Context: Unknown File: src-tauri/src/repository/device_profile.rs Line: 390 Context: Unknown File: src-tauri/src/repository/online.rs Line: 1067 Context: Unknown File: src-tauri/src/repository/online.rs Line: 1765 Context: Unknown File: src-tauri/src/repository/online.rs Line: 1835 Context: Unknown File: src-tauri/src/repository/online.rs Line: 1948 Context: Unknown File: src-tauri/src/repository/online.rs Line: 1994 Context: Unknown File: src-tauri/src/repository/online.rs Line: 3043 Context: Unknown File: src-tauri/src/repository/online.rs Line: 3066 Context: Unknown File: src-tauri/src/repository/types.rs Line: 265 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-020","id":"281","title":"UR-020"},"282":{"body":"Locations: 13 file(s) File: src/lib/api/bindings.ts Line: 142 Context: Unknown File: src/lib/api/bindings.ts Line: 159 Context: Unknown File: src/lib/components/player/VideoPlayer.svelte Line: 1 Context: Unknown File: src/lib/components/player/VideoPlayer.svelte Line: 304 Context: Unknown File: src/lib/player/adapters/html5Adapter.ts Line: 19 Context: Unknown File: src/lib/player/adapters/types.ts Line: 15 Context: Unknown File: src/routes/player/[id]/+page.svelte Line: 640 Context: Unknown File: src-tauri/src/commands/storage/series_prefs.rs Line: 3 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 1 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 1530 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 1713 Context: Unknown File: src-tauri/src/player/backend.rs Line: 106 Context: Unknown File: src-tauri/src/repository/online.rs Line: 1174 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-021","id":"282","title":"UR-021"},"283":{"body":"Locations: 32 file(s) File: src/lib/api/bindings.ts Line: 326 Context: Unknown File: src/lib/api/bindings.ts Line: 1515 Context: Unknown File: src/lib/api/bindings.ts Line: 1523 Context: Unknown File: src/lib/api/bindings.ts Line: 2878 Context: Unknown File: src/lib/services/nextEpisodeService.ts Line: 11 Context: Unknown File: src/lib/services/playerEvents.ts Line: 8 Context: Unknown File: src/lib/services/playerEvents.ts Line: 269 Context: function handleMediaLoaded(duration: number): void { File: src/lib/services/playerEvents.ts Line: 374 Context: Unknown File: src/lib/services/playerEvents.test.ts Line: 4 Context: Unknown File: src/lib/stores/home.ts Line: 2 Context: Unknown File: src/lib/stores/movies.ts Line: 3 Context: Unknown File: src/lib/stores/tv.ts Line: 3 Context: Unknown File: src/lib/stores/nextEpisode.ts Line: 9 Context: Unknown File: src/routes/library/tv/+page.svelte Line: 8 Context: Unknown File: src/routes/library/movies/+page.svelte Line: 8 Context: Unknown File: src/routes/settings/+page.svelte Line: 1 Context: Unknown File: src-tauri/src/commands/player/timers.rs Line: 3 Context: Unknown File: src-tauri/src/commands/player/timers.rs Line: 145 Context: Unknown File: src-tauri/src/commands/repository.rs Line: 4 Context: Unknown File: src-tauri/src/commands/repository.rs Line: 307 Context: Unknown File: src-tauri/src/commands/repository.rs Line: 333 Context: Unknown File: src-tauri/src/player/mod.rs Line: 2 Context: Unknown File: src-tauri/src/player/mod.rs Line: 427 Context: Unknown File: src-tauri/src/player/mod.rs Line: 1633 Context: Unknown File: src-tauri/src/player/mod.rs Line: 1875 Context: Unknown File: src-tauri/src/player/events.rs Line: 6 Context: Unknown File: src-tauri/src/player/events.rs Line: 23 Context: Unknown File: src-tauri/src/player/autoplay.rs Line: 2 Context: Unknown File: src-tauri/src/repository/online.rs Line: 910 Context: Unknown File: src-tauri/src/repository/online.rs Line: 1205 Context: Unknown File: src-tauri/src/repository/online.rs Line: 1232 Context: Unknown File: src-tauri/src/settings.rs Line: 1 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-023","id":"283","title":"UR-023"},"284":{"body":"Locations: 68 file(s) File: src/lib/api/bindings.ts Line: 1338 Context: Unknown File: src/lib/api/bindings.ts Line: 1675 Context: Unknown File: src/lib/api/bindings.ts Line: 3265 Context: export type SyncQueueItem = { id: number; userId: string; operation: s... File: src/lib/components/sync/PendingSyncList.svelte Line: 12 Context: Unknown File: src/lib/components/sync/PendingSyncModal.svelte Line: 5 Context: Unknown File: src/lib/services/pendingSync.logic.test.ts Line: 1 Context: Unknown File: src/lib/services/playerEvents.ts Line: 159 Context: Unknown File: src/lib/services/pendingSync.logic.ts Line: 9 Context: Unknown File: src/lib/services/skipReporting.test.ts Line: 9 Context: Unknown File: src/lib/services/syncService.ts Line: 7 Context: Unknown File: src/lib/services/syncService.ts Line: 61 Context: Unknown File: src/lib/services/skipReporting.ts Line: 14 Context: Unknown File: src/lib/services/playbackReporting.ts Line: 10 Context: Unknown File: src/lib/services/playbackReporting.ts Line: 27 Context: Unknown File: src/lib/services/playbackReporting.ts Line: 107 Context: Unknown File: src/lib/services/playbackReporting.ts Line: 143 Context: Unknown File: src/lib/services/playbackReporting.test.ts Line: 4 Context: Unknown File: src/routes/settings/+page.svelte Line: 1 Context: Unknown File: src/routes/settings/+page.svelte Line: 1005 Context: Unknown File: src/routes/+layout.svelte Line: 303 Context: Unknown File: src-tauri/src/commands/sync.rs Line: 7 Context: Unknown File: src-tauri/src/commands/sync.rs Line: 33 Context: Unknown File: src-tauri/src/commands/storage/mod.rs Line: 3 Context: Unknown File: src-tauri/src/commands/mod.rs Line: 2 Context: Unknown File: src-tauri/src/commands/sync_drain.rs Line: 14 Context: Unknown File: src-tauri/src/commands/sync_drain.rs Line: 76 Context: Unknown File: src-tauri/src/commands/sync_drain.rs Line: 236 Context: Unknown File: src-tauri/src/commands/sync_drain.rs Line: 380 Context: Unknown File: src-tauri/src/commands/sync_drain.rs Line: 427 Context: Unknown File: src-tauri/src/commands/sync_drain.rs Line: 474 Context: Unknown File: src-tauri/src/commands/sync_drain.rs Line: 606 Context: Unknown File: src-tauri/src/commands/sync_drain.rs Line: 672 Context: Unknown File: src-tauri/src/commands/sync_drain.rs Line: 702 Context: Unknown File: src-tauri/src/commands/sync_drain.rs Line: 736 Context: Unknown File: src-tauri/src/commands/sync_drain.rs Line: 765 Context: Unknown File: src-tauri/src/commands/sync_drain.rs Line: 811 Context: Unknown File: src-tauri/src/commands/sync_drain.rs Line: 856 Context: Unknown File: src-tauri/src/commands/sync_drain.rs Line: 885 Context: Unknown File: src-tauri/src/commands/sync_drain.rs Line: 915 Context: Unknown File: src-tauri/src/commands/sync_drain.rs Line: 954 Context: Unknown File: src-tauri/src/commands/sync_drain.rs Line: 992 Context: Unknown File: src-tauri/src/commands/sync_drain.rs Line: 1007 Context: Unknown File: src-tauri/src/commands/playback_reporting.rs Line: 3 Context: Unknown File: src-tauri/src/commands/repository.rs Line: 735 Context: Unknown File: src-tauri/src/storage/mod.rs Line: 157 Context: pub fn file_size(&self) -> Option { File: src-tauri/src/lib.rs Line: 1578 Context: Unknown File: src-tauri/src/player/mod.rs Line: 68 Context: Unknown File: src-tauri/src/player/mod.rs Line: 120 Context: Unknown File: src-tauri/src/player/mod.rs Line: 304 Context: Unknown File: src-tauri/src/player/mod.rs Line: 764 Context: Unknown File: src-tauri/src/player/mod.rs Line: 945 Context: Unknown File: src-tauri/src/player/mod.rs Line: 979 Context: Unknown File: src-tauri/src/player/mod.rs Line: 988 Context: pub fn set_report_sink(&self, sink: Arc) { File: src-tauri/src/player/mod.rs Line: 1307 Context: Unknown File: src-tauri/src/player/mod.rs Line: 1551 Context: Unknown File: src-tauri/src/player/mod.rs Line: 4193 Context: Unknown File: src-tauri/src/player/mod.rs Line: 4244 Context: Unknown File: src-tauri/src/player/mod.rs Line: 4271 Context: Unknown File: src-tauri/src/player/mod.rs Line: 4309 Context: Unknown File: src-tauri/src/player/mod.rs Line: 4337 Context: Unknown File: src-tauri/src/player/mod.rs Line: 4366 Context: Unknown File: src-tauri/src/repository/hybrid.rs Line: 408 Context: Unknown File: src-tauri/src/repository/hybrid.rs Line: 635 Context: Unknown File: src-tauri/src/repository/mod.rs Line: 255 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 688 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 4692 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 4778 Context: Unknown File: src-tauri/src/repository/online.rs Line: 2366 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-025","id":"284","title":"UR-025"},"285":{"body":"Locations: 19 file(s) File: src/lib/api/bindings.ts Line: 326 Context: Unknown File: src/lib/api/bindings.ts Line: 2878 Context: Unknown File: src/lib/api/bindings.ts Line: 3135 Context: export type SessionInfo = { id?: string | null; userId?: string | null... File: src/lib/components/player/SleepTimerModal.svelte Line: 1 Context: Unknown File: src/lib/components/player/VideoPlayer.svelte Line: 1 Context: Unknown File: src/lib/services/playerEvents.ts Line: 8 Context: Unknown File: src/lib/services/playerEvents.ts Line: 269 Context: function handleMediaLoaded(duration: number): void { File: src/lib/services/playerEvents.ts Line: 329 Context: Unknown File: src/lib/services/playerEvents.test.ts Line: 4 Context: Unknown File: src/lib/stores/sleepTimer.ts Line: 10 Context: Unknown File: src-tauri/src/commands/player/timers.rs Line: 3 Context: Unknown File: src-tauri/src/commands/player/timers.rs Line: 145 Context: Unknown File: src-tauri/src/player/mod.rs Line: 2 Context: Unknown File: src-tauri/src/player/mod.rs Line: 427 Context: Unknown File: src-tauri/src/player/events.rs Line: 6 Context: Unknown File: src-tauri/src/player/events.rs Line: 23 Context: Unknown File: src-tauri/src/player/sleep_timer.rs Line: 4 Context: Unknown File: src-tauri/src/player/sleep_timer.rs Line: 81 Context: pub fn cancel(&mut self) { File: src-tauri/src/player/autoplay.rs Line: 2 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-026","id":"285","title":"UR-026"},"286":{"body":"Locations: 23 file(s) File: src/lib/api/bindings.ts Line: 232 Context: Unknown File: src/lib/api/bindings.ts Line: 2215 Context: Unknown File: src/routes/settings/+page.svelte Line: 1 Context: Unknown File: src-tauri/src/commands/player/settings.rs Line: 3 Context: Unknown File: src-tauri/src/commands/player/settings.rs Line: 50 Context: Unknown File: src-tauri/src/player/mpv_backend.rs Line: 658 Context: Unknown File: src-tauri/src/player/mpv_backend.rs Line: 678 Context: Unknown File: src-tauri/src/player/mpv_backend.rs Line: 747 Context: Unknown File: src-tauri/src/player/mpv_backend.rs Line: 759 Context: Unknown File: src-tauri/src/player/mpv_backend.rs Line: 833 Context: Unknown File: src-tauri/src/player/android/mod.rs Line: 579 Context: Unknown File: src-tauri/src/player/android/mod.rs Line: 617 Context: Unknown File: src-tauri/src/settings.rs Line: 1 Context: Unknown File: src-tauri/src/settings.rs Line: 35 Context: Unknown File: src-tauri/src/settings.rs Line: 48 Context: Unknown File: src-tauri/src/settings.rs Line: 142 Context: pub fn with_crossfade_clamped(mut self) -> Self { File: src-tauri/src/settings.rs Line: 383 Context: Unknown File: src-tauri/src/settings.rs Line: 400 Context: Unknown File: src-tauri/src/settings.rs Line: 424 Context: Unknown File: src-tauri/src/settings.rs Line: 460 Context: Unknown File: src-tauri/src/settings.rs Line: 489 Context: Unknown File: src-tauri/src/settings.rs Line: 516 Context: Unknown File: src-tauri/src/settings.rs Line: 527 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-027","id":"286","title":"UR-027"},"287":{"body":"Locations: 19 file(s) File: src/lib/api/bindings.ts Line: 1515 Context: Unknown File: src/lib/api/bindings.ts Line: 1523 Context: Unknown File: src/lib/components/home/heroRotation.ts Line: 8 Context: Unknown File: src/lib/components/home/HeroBanner.svelte Line: 1 Context: Unknown File: src/lib/components/home/Carousel.svelte Line: 1 Context: Unknown File: src/lib/components/home/heroRotation.test.ts Line: 1 Context: Unknown File: src/lib/utils/heroMix.ts Line: 8 Context: Unknown File: src/lib/stores/home.ts Line: 2 Context: Unknown File: src/lib/stores/music.ts Line: 3 Context: Unknown File: src/lib/stores/movies.ts Line: 3 Context: Unknown File: src/lib/stores/tv.ts Line: 3 Context: Unknown File: src/routes/library/tv/+page.svelte Line: 8 Context: Unknown File: src/routes/library/movies/+page.svelte Line: 8 Context: Unknown File: src/routes/library/music/+page.svelte Line: 1 Context: Unknown File: src-tauri/src/commands/repository.rs Line: 4 Context: Unknown File: src-tauri/src/commands/repository.rs Line: 307 Context: Unknown File: src-tauri/src/commands/repository.rs Line: 333 Context: Unknown File: src-tauri/src/repository/online.rs Line: 888 Context: Unknown File: src-tauri/src/repository/online.rs Line: 1410 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-034","id":"287","title":"UR-034"},"288":{"body":"Locations: 75 file(s) File: src/lib/api/repository-client.ts Line: 253 Context: Unknown File: src/lib/api/repository-client.test.ts Line: 442 Context: Unknown File: src/lib/api/repository-client.test.ts Line: 459 Context: Unknown File: src/lib/api/bindings.ts Line: 42 Context: Unknown File: src/lib/api/bindings.ts Line: 63 Context: Unknown File: src/lib/api/bindings.ts Line: 69 Context: Unknown File: src/lib/api/bindings.ts Line: 326 Context: Unknown File: src/lib/api/bindings.ts Line: 349 Context: Unknown File: src/lib/api/bindings.ts Line: 1630 Context: Unknown File: src/lib/components/player/backgroundAudioHandoff.ts Line: 4 Context: Unknown File: src/lib/components/player/backgroundAudioHandoff.ts Line: 69 Context: Unknown File: src/lib/components/player/backgroundAudioHandoff.ts Line: 110 Context: Unknown File: src/lib/components/player/VideoPlayer.svelte Line: 1 Context: Unknown File: src/lib/components/player/VideoPlayer.svelte Line: 1899 Context: Unknown File: src/lib/components/player/VideoPlayer.svelte Line: 1925 Context: Unknown File: src/lib/components/player/backgroundAudioHandoff.test.ts Line: 12 Context: Unknown File: src/lib/components/player/backgroundAudioHandoff.test.ts Line: 95 Context: Unknown File: src/lib/player/backgroundAudio.commands.test.ts Line: 7 Context: Unknown File: src/lib/services/playerEvents.regression.test.ts Line: 155 Context: Unknown File: src/lib/services/playerEvents.ts Line: 292 Context: Unknown File: src/lib/utils/backgroundAudio.ts Line: 4 Context: Unknown File: src/lib/utils/backgroundAudio.test.ts Line: 7 Context: Unknown File: src-tauri/src/commands/player/timers.rs Line: 145 Context: Unknown File: src-tauri/src/commands/player/timers.rs Line: 299 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 460 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 480 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 730 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 855 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 883 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 3059 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 3079 Context: Unknown File: src-tauri/src/commands/repository.rs Line: 611 Context: Unknown File: src-tauri/src/lib.rs Line: 475 Context: Unknown File: src-tauri/src/player/mod.rs Line: 120 Context: Unknown File: src-tauri/src/player/mod.rs Line: 260 Context: Unknown File: src-tauri/src/player/mod.rs Line: 271 Context: Unknown File: src-tauri/src/player/mod.rs Line: 280 Context: Unknown File: src-tauri/src/player/mod.rs Line: 873 Context: Unknown File: src-tauri/src/player/mod.rs Line: 945 Context: Unknown File: src-tauri/src/player/mod.rs Line: 1551 Context: Unknown File: src-tauri/src/player/mod.rs Line: 1572 Context: Unknown File: src-tauri/src/player/mod.rs Line: 1586 Context: Unknown File: src-tauri/src/player/mod.rs Line: 1602 Context: Unknown File: src-tauri/src/player/mod.rs Line: 1611 Context: pub fn exit_background_audio(&self) -> f64 { File: src-tauri/src/player/mod.rs Line: 1618 Context: pub fn is_background_audio_active(&self) -> bool { File: src-tauri/src/player/mod.rs Line: 1633 Context: Unknown File: src-tauri/src/player/mod.rs Line: 1690 Context: Unknown File: src-tauri/src/player/mod.rs Line: 1723 Context: Unknown File: src-tauri/src/player/mod.rs Line: 1780 Context: Unknown File: src-tauri/src/player/mod.rs Line: 1811 Context: Unknown File: src-tauri/src/player/mod.rs Line: 1875 Context: Unknown File: src-tauri/src/player/mod.rs Line: 4014 Context: Unknown File: src-tauri/src/player/mod.rs Line: 4077 Context: Unknown File: src-tauri/src/player/mod.rs Line: 4157 Context: Unknown File: src-tauri/src/player/mod.rs Line: 4176 Context: Unknown File: src-tauri/src/player/mod.rs Line: 4222 Context: Unknown File: src-tauri/src/player/mod.rs Line: 4309 Context: Unknown File: src-tauri/src/player/mod.rs Line: 4337 Context: Unknown File: src-tauri/src/player/background_policy.rs Line: 3 Context: Unknown File: src-tauri/src/player/background_policy.rs Line: 53 Context: Unknown File: src-tauri/src/player/background_policy.rs Line: 80 Context: Unknown File: src-tauri/src/player/seek.rs Line: 103 Context: Unknown File: src-tauri/src/player/seek.rs Line: 152 Context: Unknown File: src-tauri/src/player/seek.rs Line: 168 Context: / which backends reject (the \"Raw(-10)\" class of error). File: src-tauri/src/player/seek.rs Line: 181 Context: / this function exists to prevent. File: src-tauri/src/player/seek.rs Line: 192 Context: Unknown File: src-tauri/src/player/stream_end.rs Line: 3 Context: Unknown File: src-tauri/src/player/stream_end.rs Line: 54 Context: Unknown File: src-tauri/src/player/stream_end.rs Line: 91 Context: Unknown File: src-tauri/src/repository/hybrid.rs Line: 103 Context: Unknown File: src-tauri/src/repository/mod.rs Line: 131 Context: Unknown File: src-tauri/src/repository/online.rs Line: 620 Context: Unknown File: src-tauri/src/repository/online.rs Line: 2887 Context: Unknown File: src-tauri/src/repository/online.rs Line: 3141 Context: Unknown File: src-tauri/src/repository/online.rs Line: 3182 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-040","id":"288","title":"UR-040"},"289":{"body":"Locations: 6 file(s) File: src/lib/api/bindings.ts Line: 42 Context: Unknown File: src/lib/components/player/VideoPlayer.nativeReveal.test.ts Line: 16 Context: Unknown File: src/lib/utils/pictureInPicture.ts Line: 4 Context: Unknown File: src/lib/utils/pictureInPicture.ts Line: 111 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 855 Context: Unknown File: src-tauri/src/player/background_policy.rs Line: 53 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-041","id":"289","title":"UR-041"},"29":{"body":"Locations: 6 file(s) File: src/lib/stores/auth.ts Line: 6 Context: Unknown File: src/lib/stores/auth.ts Line: 276 Context: Unknown File: src/lib/stores/auth.ts Line: 303 Context: Unknown File: src/lib/stores/auth.test.ts Line: 4 Context: Unknown File: src-tauri/src/commands/auth.rs Line: 3 Context: Unknown File: src-tauri/src/jellyfin/client.rs Line: 1 Context: Unknown","breadcrumbs":"Traceability Matrix » IR-009","id":"29","title":"IR-009"},"290":{"body":"Locations: 17 file(s) File: src/lib/api/bindings.ts Line: 1192 Context: Unknown File: src/lib/api/bindings.ts Line: 1203 Context: Unknown File: src/lib/api/bindings.ts Line: 2489 Context: export type MergedMediaItem = { id: string; title: string; artist: str... File: src/lib/api/bindings.ts Line: 2498 Context: export type NetworkStateWrapperArg = { networkType: NetworkType; unmet... File: src/lib/services/networkType.ts Line: 10 Context: Unknown File: src/lib/services/networkType.test.ts Line: 4 Context: Unknown File: src/lib/stores/downloads.ts Line: 84 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 31 Context: pub struct DownloadManagerWrapper(pub Mutex); File: src-tauri/src/commands/download/mod.rs Line: 40 Context: pub struct NetworkStateWrapper(pub NetworkStateHandle); File: src-tauri/src/commands/download/mod.rs Line: 83 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 96 Context: pub struct NetworkStateWrapperArg { File: src-tauri/src/commands/download/mod.rs Line: 1744 Context: Unknown File: src-tauri/src/download/network.rs Line: 22 Context: Unknown File: src-tauri/src/download/network.rs Line: 42 Context: Unknown File: src-tauri/src/download/network.rs Line: 70 Context: impl NetworkState { File: src-tauri/src/download/events.rs Line: 47 Context: Unknown File: src-tauri/src/download/cache.rs Line: 86 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-053","id":"290","title":"UR-053"},"291":{"body":"Locations: 54 file(s) File: src/lib/api/repository-client.ts Line: 100 Context: Unknown File: src/lib/api/repository-client.ts Line: 108 Context: Unknown File: src/lib/api/bindings.ts Line: 899 Context: Unknown File: src/lib/api/bindings.ts Line: 941 Context: Unknown File: src/lib/api/bindings.ts Line: 954 Context: Unknown File: src/lib/api/bindings.ts Line: 991 Context: Unknown File: src/lib/api/bindings.ts Line: 1473 Context: Unknown File: src/lib/api/bindings.ts Line: 1481 Context: Unknown File: src/lib/components/library/MediaCard.svelte Line: 29 Context: Unknown File: src/lib/components/library/MediaCard.svelte Line: 34 Context: Unknown File: src/lib/components/downloads/DownloadedBrowse.svelte Line: 10 Context: Unknown File: src/lib/services/downloadedCatalog.ts Line: 13 Context: Unknown File: src/lib/services/downloadedCatalog.ts Line: 96 Context: function sizeOf(itemId: string): number { File: src/lib/stores/downloads.ts Line: 210 Context: Unknown File: src/lib/stores/downloads.test.ts Line: 316 Context: Unknown File: src/routes/downloads/+page.svelte Line: 14 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 442 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 473 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 524 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 546 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 679 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 1226 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 1260 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 2425 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 3238 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 3303 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 3334 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 3367 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 3396 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 3410 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 3437 Context: Unknown File: src-tauri/src/commands/repository.rs Line: 222 Context: Unknown File: src-tauri/src/commands/repository.rs Line: 237 Context: Unknown File: src-tauri/src/commands/catalog.rs Line: 916 Context: Unknown File: src-tauri/src/commands/catalog.rs Line: 957 Context: Unknown File: src-tauri/src/download/stop.rs Line: 3 Context: Unknown File: src-tauri/src/download/worker.rs Line: 42 Context: Unknown File: src-tauri/src/download/worker.rs Line: 247 Context: / One function so the writer and the cleaners cannot disagree again. File: src-tauri/src/download/worker.rs Line: 336 Context: Unknown File: src-tauri/src/repository/hybrid.rs Line: 135 Context: Unknown File: src-tauri/src/repository/hybrid.rs Line: 256 Context: Unknown File: src-tauri/src/repository/hybrid.rs Line: 271 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 848 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 857 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 884 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 990 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 3705 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 3727 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 3767 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 3837 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 3883 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 3904 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 3923 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 3984 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-055","id":"291","title":"UR-055"},"292":{"body":"Locations: 17 file(s) File: src/lib/api/repository-client.ts Line: 116 Context: Unknown File: src/lib/api/bindings.ts Line: 1489 Context: Unknown File: src/lib/api/bindings.ts Line: 2140 Context: Unknown File: src/lib/components/library/LibraryGrid.svelte Line: 17 Context: Unknown File: src/lib/components/library/MediaCard.svelte Line: 23 Context: Unknown File: src/lib/components/library/MediaCard.svelte Line: 34 Context: Unknown File: src/lib/components/downloads/DownloadedBrowse.svelte Line: 10 Context: Unknown File: src/lib/services/downloadedCatalog.ts Line: 13 Context: Unknown File: src/lib/services/downloadedCatalog.ts Line: 96 Context: function sizeOf(itemId: string): number { File: src/lib/utils/formatBytes.ts Line: 8 Context: Unknown File: src/lib/utils/formatBytes.test.ts Line: 4 Context: Unknown File: src/routes/downloads/+page.svelte Line: 14 Context: Unknown File: src-tauri/src/commands/repository.rs Line: 254 Context: Unknown File: src-tauri/src/repository/hybrid.rs Line: 278 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 1036 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 3945 Context: Unknown File: src-tauri/src/repository/types.rs Line: 303 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-056","id":"292","title":"UR-056"},"293":{"body":"Locations: 17 file(s) File: src/lib/api/repository-client.ts Line: 151 Context: Unknown File: src/lib/api/repository-client.ts Line: 161 Context: Unknown File: src/lib/api/bindings.ts Line: 1536 Context: Unknown File: src/lib/api/bindings.ts Line: 1549 Context: Unknown File: src/lib/components/library/episodeStrip.ts Line: 8 Context: Unknown File: src/lib/components/library/SeasonSection.svelte Line: 1 Context: Unknown File: src/lib/components/library/SeasonSection.svelte Line: 20 Context: Unknown File: src/lib/components/library/EpisodeRow.svelte Line: 17 Context: Unknown File: src/lib/components/library/seriesNavigation.ts Line: 13 Context: Unknown File: src/lib/components/library/seriesNavigation.test.ts Line: 1 Context: Unknown File: src/routes/library/[id]/+page.svelte Line: 1 Context: Unknown File: src/routes/library/[id]/+page.svelte Line: 124 Context: Unknown File: src/routes/library/[id]/+page.svelte Line: 205 Context: Unknown File: src/routes/library/[id]/+page.svelte Line: 326 Context: async function handlePlayAll() { File: src-tauri/src/commands/repository.rs Line: 356 Context: Unknown File: src-tauri/src/commands/repository.rs Line: 377 Context: Unknown File: src-tauri/src/repository/series_progress.rs Line: 12 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-062","id":"293","title":"UR-062"},"294":{"body":"Locations: 7 file(s) File: src/lib/api/repository-client.ts Line: 172 Context: Unknown File: src/lib/api/bindings.ts Line: 1563 Context: Unknown File: src/lib/components/library/ClearHistoryButton.svelte Line: 9 Context: Unknown File: src/lib/components/library/SeasonSection.svelte Line: 1 Context: Unknown File: src-tauri/src/commands/repository.rs Line: 399 Context: Unknown File: src-tauri/src/repository/mod.rs Line: 247 Context: Unknown File: src-tauri/src/repository/online.rs Line: 2321 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-064","id":"294","title":"UR-064"},"295":{"body":"Locations: 1 file(s) File: src/lib/stores/downloads.test.ts Line: 2 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-010","id":"295","title":"UT-010"},"296":{"body":"Locations: 1 file(s) File: src/lib/stores/downloads.test.ts Line: 2 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-024","id":"296","title":"UT-024"},"297":{"body":"Locations: 1 file(s) File: src/lib/stores/downloads.test.ts Line: 316 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-052","id":"297","title":"UT-052"},"298":{"body":"Locations: 4 file(s) File: src/lib/components/player/backgroundAudioHandoff.ts Line: 4 Context: Unknown File: src/lib/components/player/backgroundAudioHandoff.ts Line: 69 Context: Unknown File: src/lib/components/player/backgroundAudioHandoff.ts Line: 110 Context: Unknown File: src/lib/components/player/backgroundAudioHandoff.test.ts Line: 12 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-060","id":"298","title":"UT-060"},"299":{"body":"Locations: 9 file(s) File: src/lib/api/repository-client.test.ts Line: 442 Context: Unknown File: src/lib/api/repository-client.test.ts Line: 459 Context: Unknown File: src/lib/api/bindings.ts Line: 63 Context: Unknown File: src/lib/api/bindings.ts Line: 69 Context: Unknown File: src/lib/api/bindings.ts Line: 1630 Context: Unknown File: src/lib/player/backgroundAudio.commands.test.ts Line: 7 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 730 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 883 Context: Unknown File: src-tauri/src/commands/repository.rs Line: 611 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-061","id":"299","title":"UT-061"},"3":{"body":"","breadcrumbs":"Requirements Specification » 2. Software Requirements","id":"3","title":"2. Software Requirements"},"30":{"body":"Locations: 3 file(s) File: src-tauri/src/jellyfin/client.rs Line: 1 Context: Unknown File: src-tauri/src/repository/online.rs Line: 1 Context: Unknown File: src-tauri/src/repository/online.rs Line: 1493 Context: Unknown","breadcrumbs":"Traceability Matrix » IR-010","id":"30","title":"IR-010"},"300":{"body":"Locations: 1 file(s) File: src/lib/utils/backgroundAudio.test.ts Line: 7 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-062","id":"300","title":"UT-062"},"301":{"body":"Locations: 1 file(s) File: src/lib/services/networkType.test.ts Line: 4 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-066","id":"301","title":"UT-066"},"302":{"body":"Locations: 1 file(s) File: src/lib/utils/formatBytes.test.ts Line: 4 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-071","id":"302","title":"UT-071"},"303":{"body":"Locations: 2 file(s) File: src/lib/components/player/tapGestures.ts Line: 21 Context: Unknown File: src-tauri/src/player/mpv_backend.rs Line: 778 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-085","id":"303","title":"UT-085"},"304":{"body":"Locations: 2 file(s) File: src/lib/components/player/tapGestures.ts Line: 21 Context: Unknown File: src-tauri/src/player/mpv_backend.rs Line: 788 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-086","id":"304","title":"UT-086"},"305":{"body":"Locations: 2 file(s) File: src/lib/components/player/tapGestures.ts Line: 21 Context: Unknown File: src-tauri/src/player/mpv_backend.rs Line: 833 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-087","id":"305","title":"UT-087"},"306":{"body":"Locations: 1 file(s) File: src/lib/components/player/tapGestures.ts Line: 21 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-088","id":"306","title":"UT-088"},"307":{"body":"Locations: 1 file(s) File: src/lib/components/player/VideoPlayer.touchScrub.test.ts Line: 21 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-089","id":"307","title":"UT-089"},"308":{"body":"Locations: 1 file(s) File: src/lib/components/player/VideoPlayer.touchScrub.test.ts Line: 21 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-090","id":"308","title":"UT-090"},"309":{"body":"Locations: 2 file(s) File: src/lib/player/transport.authority.test.ts Line: 4 Context: Unknown File: src/lib/player/index.ts Line: 15 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-091","id":"309","title":"UT-091"},"31":{"body":"Locations: 1 file(s) File: src-tauri/src/jellyfin/client.rs Line: 1 Context: Unknown","breadcrumbs":"Traceability Matrix » IR-011","id":"31","title":"IR-011"},"310":{"body":"Locations: 3 file(s) File: src/lib/components/player/VideoPlayer.tapSurface.test.ts Line: 5 Context: Unknown File: src/lib/components/player/playerSurface.test.ts Line: 13 Context: Unknown File: src/lib/components/player/playerSurface.ts Line: 8 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-092","id":"310","title":"UT-092"},"311":{"body":"Locations: 2 file(s) File: src/lib/components/player/playerSurface.test.ts Line: 13 Context: Unknown File: src/lib/components/player/playerSurface.ts Line: 8 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-093","id":"311","title":"UT-093"},"312":{"body":"Locations: 1 file(s) File: src/lib/utils/safeArea.test.ts Line: 17 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-094","id":"312","title":"UT-094"},"313":{"body":"Locations: 1 file(s) File: src/lib/utils/safeArea.test.ts Line: 17 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-095","id":"313","title":"UT-095"},"314":{"body":"Locations: 1 file(s) File: src/lib/utils/safeArea.test.ts Line: 17 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-096","id":"314","title":"UT-096"},"315":{"body":"Locations: 1 file(s) File: src/lib/utils/safeArea.test.ts Line: 17 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-097","id":"315","title":"UT-097"},"316":{"body":"Locations: 1 file(s) File: src/lib/utils/layoutShell.test.ts Line: 158 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-098","id":"316","title":"UT-098"},"317":{"body":"Locations: 3 file(s) File: src/lib/stores/favorites.test.ts Line: 1 Context: Unknown File: src/lib/stores/favorites.ts Line: 8 Context: Unknown File: src/lib/stores/favorites.ts Line: 55 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-105","id":"317","title":"UT-105"},"318":{"body":"Locations: 3 file(s) File: src/lib/stores/favorites.test.ts Line: 1 Context: Unknown File: src/lib/stores/favorites.ts Line: 79 Context: Unknown File: src/routes/library/favorites/+page.svelte Line: 44 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-106","id":"318","title":"UT-106"},"319":{"body":"Locations: 8 file(s) File: src/lib/api/bindings.ts Line: 349 Context: Unknown File: src-tauri/src/commands/player/timers.rs Line: 299 Context: Unknown File: src-tauri/src/player/mod.rs Line: 1690 Context: Unknown File: src-tauri/src/player/mod.rs Line: 1723 Context: Unknown File: src-tauri/src/player/mod.rs Line: 1780 Context: Unknown File: src-tauri/src/player/mod.rs Line: 1811 Context: Unknown File: src-tauri/src/player/stream_end.rs Line: 3 Context: Unknown File: src-tauri/src/player/stream_end.rs Line: 54 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-117","id":"319","title":"UT-117"},"32":{"body":"Locations: 2 file(s) File: src/lib/stores/playbackMode.ts Line: 9 Context: Unknown File: src-tauri/src/commands/player/remote.rs Line: 3 Context: Unknown","breadcrumbs":"Traceability Matrix » IR-012","id":"32","title":"IR-012"},"320":{"body":"Locations: 1 file(s) File: src/lib/player/localSource.ts Line: 12 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-118","id":"320","title":"UT-118"},"321":{"body":"Locations: 2 file(s) File: src-tauri/src/commands/player/mod.rs Line: 439 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 3011 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-119","id":"321","title":"UT-119"},"322":{"body":"Locations: 2 file(s) File: src/lib/services/pendingSync.logic.test.ts Line: 1 Context: Unknown File: src/lib/services/pendingSync.logic.ts Line: 9 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-123","id":"322","title":"UT-123"},"323":{"body":"Locations: 1 file(s) File: src/lib/player/localSource.ts Line: 55 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-124","id":"323","title":"UT-124"},"324":{"body":"Locations: 1 file(s) File: src/lib/components/library/EpisodeFocusView.test.ts Line: 7 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-131","id":"324","title":"UT-131"},"325":{"body":"Locations: 1 file(s) File: src/lib/components/library/EpisodeFocusView.test.ts Line: 7 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-132","id":"325","title":"UT-132"},"326":{"body":"Locations: 1 file(s) File: src/lib/components/library/EpisodeFocusView.test.ts Line: 7 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-133","id":"326","title":"UT-133"},"327":{"body":"Locations: 1 file(s) File: src/lib/components/library/EpisodeFocusView.test.ts Line: 7 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-134","id":"327","title":"UT-134"},"328":{"body":"Locations: 1 file(s) File: src/lib/components/library/EpisodeFocusView.test.ts Line: 7 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-135","id":"328","title":"UT-135"},"329":{"body":"Locations: 1 file(s) File: src/lib/components/library/seriesNavigation.test.ts Line: 1 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-136","id":"329","title":"UT-136"},"33":{"body":"Locations: 3 file(s) File: src-tauri/src/commands/storage/mod.rs Line: 3 Context: Unknown File: src-tauri/src/storage/schema.rs Line: 3 Context: Unknown File: src-tauri/src/repository/hybrid.rs Line: 8 Context: Unknown","breadcrumbs":"Traceability Matrix » IR-013","id":"33","title":"IR-013"},"330":{"body":"Locations: 1 file(s) File: src/lib/services/offlineCatalog.reload.test.ts Line: 11 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-137","id":"330","title":"UT-137"},"331":{"body":"Locations: 1 file(s) File: src/lib/services/offlineCatalog.reload.test.ts Line: 11 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-138","id":"331","title":"UT-138"},"332":{"body":"Locations: 1 file(s) File: src/lib/services/offlineCatalog.reload.test.ts Line: 11 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-139","id":"332","title":"UT-139"},"333":{"body":"Locations: 2 file(s) File: src/lib/player/adapters/adapterSelection.test.ts Line: 4 Context: Unknown File: src/lib/player/adapters/index.ts Line: 20 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-149","id":"333","title":"UT-149"},"334":{"body":"Locations: 1 file(s) File: src/lib/components/library/mosaic.ts Line: 12 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-158","id":"334","title":"UT-158"},"335":{"body":"Locations: 1 file(s) File: src/lib/components/library/mosaic.ts Line: 12 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-159","id":"335","title":"UT-159"},"336":{"body":"Locations: 1 file(s) File: src/lib/components/library/mosaic.ts Line: 12 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-160","id":"336","title":"UT-160"},"337":{"body":"Locations: 6 file(s) File: src-tauri/src/commands/player/mod.rs Line: 460 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 480 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 3059 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 3079 Context: Unknown File: src-tauri/src/player/mod.rs Line: 878 Context: Unknown File: src-tauri/src/player/mod.rs Line: 4014 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-181","id":"337","title":"UT-181"},"338":{"body":"Locations: 5 file(s) File: src/lib/api/repository-client.ts Line: 235 Context: Unknown File: src/lib/api/bindings.ts Line: 1616 Context: Unknown File: src-tauri/src/commands/repository.rs Line: 592 Context: Unknown File: src-tauri/src/repository/online.rs Line: 527 Context: Unknown File: src-tauri/src/repository/online.rs Line: 2964 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-182","id":"338","title":"UT-182"},"339":{"body":"Locations: 42 file(s) File: src/lib/api/repository-client.ts Line: 356 Context: Unknown File: src/lib/api/bindings.ts Line: 1707 Context: Unknown File: src/lib/api/bindings.ts Line: 2268 Context: export type GetItemsOptions = { startIndex?: number | null; limit?: nu... File: src/lib/components/library/GenericMediaListPage.svelte Line: 1 Context: Unknown File: src/lib/components/library/GenericMediaListPage.svelte Line: 154 Context: Unknown File: src/lib/components/library/GenericMediaListPage.svelte Line: 170 Context: function handleSearch(query: string) { File: src/lib/components/library/GenericMediaListPage.svelte Line: 288 Context: Unknown File: src/lib/components/library/libraryMosaic.ts Line: 12 Context: Unknown File: src/lib/utils/favoritesView.test.ts Line: 1 Context: Unknown File: src/lib/utils/favoritesView.ts Line: 8 Context: Unknown File: src/lib/stores/favorites.test.ts Line: 1 Context: Unknown File: src/lib/stores/home.ts Line: 2 Context: Unknown File: src/lib/stores/home.ts Line: 18 Context: Unknown File: src/lib/stores/home.ts Line: 64 Context: Unknown File: src/lib/stores/favorites.ts Line: 79 Context: Unknown File: src/routes/library/favorites/+page.svelte Line: 9 Context: Unknown File: src/routes/library/favorites/+page.svelte Line: 44 Context: Unknown File: src/routes/library/+page.svelte Line: 225 Context: Unknown File: src/routes/library/+page.svelte Line: 288 Context: Unknown File: src/routes/+page.svelte Line: 162 Context: Unknown File: src/routes/+page.svelte Line: 271 Context: Unknown File: src-tauri/src/commands/repository.rs Line: 898 Context: Unknown File: src-tauri/src/commands/repository.rs Line: 935 Context: Unknown File: src-tauri/src/repository/hybrid.rs Line: 196 Context: Unknown File: src-tauri/src/repository/hybrid.rs Line: 212 Context: Unknown File: src-tauri/src/repository/hybrid.rs Line: 1032 Context: Unknown File: src-tauri/src/repository/mod.rs Line: 235 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 1269 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 2014 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 4370 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 4405 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 4450 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 4553 Context: Unknown File: src-tauri/src/repository/online.rs Line: 803 Context: Unknown File: src-tauri/src/repository/online.rs Line: 862 Context: Unknown File: src-tauri/src/repository/online.rs Line: 933 Context: Unknown File: src-tauri/src/repository/online.rs Line: 2251 Context: Unknown File: src-tauri/src/repository/online.rs Line: 3510 Context: Unknown File: src-tauri/src/repository/online.rs Line: 3532 Context: Unknown File: src-tauri/src/repository/online.rs Line: 3541 Context: Unknown File: src-tauri/src/repository/online.rs Line: 3563 Context: Unknown File: src-tauri/src/repository/types.rs Line: 342 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-067","id":"339","title":"UR-067"},"34":{"body":"Locations: 10 file(s) File: src/lib/stores/auth.ts Line: 6 Context: Unknown File: src/lib/stores/auth.ts Line: 303 Context: Unknown File: src/lib/stores/auth.ts Line: 465 Context: Unknown File: src/lib/stores/auth.test.ts Line: 4 Context: Unknown File: src-tauri/src/commands/auth.rs Line: 3 Context: Unknown File: src-tauri/src/credentials.rs Line: 10 Context: Unknown File: src-tauri/src/credentials.rs Line: 480 Context: Unknown File: src-tauri/src/credentials.rs Line: 905 Context: Unknown File: src-tauri/src/credentials.rs Line: 924 Context: Unknown File: src-tauri/src/credentials.rs Line: 941 Context: Unknown","breadcrumbs":"Traceability Matrix » IR-014","id":"34","title":"IR-014"},"340":{"body":"Locations: 4 file(s) File: src/lib/api/bindings.ts Line: 63 Context: Unknown File: src/lib/api/bindings.ts Line: 69 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 730 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 883 Context: Unknown","breadcrumbs":"Traceability Matrix » IT-013","id":"340","title":"IT-013"},"341":{"body":"Locations: 1 file(s) File: src/lib/utils/nativeVideoLayers.test.ts Line: 4 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-186","id":"341","title":"UT-186"},"342":{"body":"Locations: 1 file(s) File: src/lib/stores/continueWatchingFilter.test.ts Line: 12 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-192","id":"342","title":"UT-192"},"343":{"body":"Locations: 4 file(s) File: src/lib/api/bindings.ts Line: 42 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 855 Context: Unknown File: src-tauri/src/player/background_policy.rs Line: 3 Context: Unknown File: src-tauri/src/player/background_policy.rs Line: 53 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-211","id":"343","title":"UT-211"},"344":{"body":"Locations: 8 file(s) File: src-tauri/src/lib.rs Line: 475 Context: Unknown File: src-tauri/src/player/mod.rs Line: 145 Context: Unknown File: src-tauri/src/player/mod.rs Line: 167 Context: Unknown File: src-tauri/src/player/seek.rs Line: 103 Context: Unknown File: src-tauri/src/player/seek.rs Line: 135 Context: Unknown File: src-tauri/src/player/android/mod.rs Line: 438 Context: Unknown File: src-tauri/src/player/android/mod.rs Line: 1123 Context: Unknown File: src-tauri/src/player/android/mod.rs Line: 1425 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-006","id":"344","title":"UR-006"},"345":{"body":"Locations: 2 file(s) File: src/lib/stores/downloads.ts Line: 2 Context: Unknown File: src/lib/stores/downloads.test.ts Line: 2 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-013","id":"345","title":"UR-013"},"346":{"body":"Locations: 13 file(s) File: src/lib/stores/queue.ts Line: 7 Context: Unknown File: src/lib/stores/queue.ts Line: 108 Context: Unknown File: src/lib/stores/queue.ts Line: 113 Context: async function next() { File: src/lib/stores/queue.ts Line: 118 Context: async function previous() { File: src/lib/stores/queue.ts Line: 123 Context: async function skipTo(index: number) { File: src/lib/stores/queue.ts Line: 128 Context: async function toggleShuffle() { File: src/lib/stores/queue.ts Line: 133 Context: async function cycleRepeat() { File: src/lib/stores/queue.ts Line: 138 Context: async function removeFromQueue(index: number) { File: src/lib/stores/queue.ts Line: 143 Context: async function moveInQueue(fromIndex: number, toIndex: number) { File: src/lib/stores/queue.test.ts Line: 4 Context: Unknown File: src-tauri/src/commands/player/queue.rs Line: 3 Context: Unknown File: src-tauri/src/player/queue.rs Line: 20 Context: pub enum RepeatMode { File: src-tauri/src/player/queue.rs Line: 535 Context: pub enum AddPosition {","breadcrumbs":"Traceability Matrix » UR-015","id":"346","title":"UR-015"},"347":{"body":"Locations: 2 file(s) File: src-tauri/src/player/backend.rs Line: 62 Context: Unknown File: src-tauri/src/player/backend.rs Line: 257 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-016","id":"347","title":"UR-016"},"348":{"body":"Locations: 1 file(s) File: src-tauri/src/commands/player/settings.rs Line: 3 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-022","id":"348","title":"UR-022"},"349":{"body":"Locations: 3 file(s) File: src/lib/stores/home.ts Line: 2 Context: Unknown File: src-tauri/src/commands/catalog.rs Line: 3 Context: Unknown File: src-tauri/src/repository/online.rs Line: 888 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-024","id":"349","title":"UR-024"},"35":{"body":"Locations: 1 file(s) File: src-tauri/src/commands/playback_reporting.rs Line: 3 Context: Unknown","breadcrumbs":"Traceability Matrix » IR-015","id":"35","title":"IR-015"},"350":{"body":"Locations: 2 file(s) File: src/lib/stores/connectivity.ts Line: 10 Context: Unknown File: src-tauri/src/commands/connectivity.rs Line: 3 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-043","id":"350","title":"UR-043"},"351":{"body":"Locations: 22 file(s) File: src/lib/api/bindings.ts Line: 1598 Context: Unknown File: src/lib/api/bindings.ts Line: 3110 Context: export type SearchResult = { items: MediaItem[]; totalRecordCount: num... File: src/lib/components/search/HeaderSearch.test.ts Line: 9 Context: Unknown File: src/lib/components/search/HeaderSearch.svelte Line: 12 Context: Unknown File: src/lib/components/search/SearchScopeChips.svelte Line: 7 Context: Unknown File: src/lib/utils/searchScope.ts Line: 9 Context: Unknown File: src/lib/utils/searchScope.ts Line: 36 Context: Unknown File: src/lib/utils/searchScope.ts Line: 61 Context: Unknown File: src/lib/utils/searchScope.ts Line: 91 Context: Unknown File: src/lib/utils/searchScope.ts Line: 125 Context: Unknown File: src/lib/utils/layoutShell.ts Line: 90 Context: Unknown File: src/lib/stores/librarySearchScope.test.ts Line: 4 Context: Unknown File: src/lib/stores/library.ts Line: 239 Context: Unknown File: src/routes/library/+page.svelte Line: 26 Context: Unknown File: src/routes/search/+page.svelte Line: 21 Context: Unknown File: src/routes/search/+page.svelte Line: 34 Context: Unknown File: src/routes/search/+page.svelte Line: 62 Context: Unknown File: src/routes/search/searchPage.test.ts Line: 14 Context: Unknown File: src-tauri/src/commands/repository.rs Line: 494 Context: pub struct SearchUpdateEvent { File: src-tauri/src/repository/types.rs Line: 355 Context: Unknown File: src-tauri/src/repository/types.rs Line: 373 Context: impl SearchScope { File: src-tauri/src/repository/types.rs Line: 439 Context: impl SearchOptions {","breadcrumbs":"Traceability Matrix » UR-049","id":"351","title":"UR-049"},"352":{"body":"Locations: 9 file(s) File: src/lib/api/bindings.ts Line: 1598 Context: Unknown File: src/lib/components/search/SearchResults.svelte Line: 9 Context: Unknown File: src/lib/components/settings/SearchGroupOrderList.svelte Line: 8 Context: Unknown File: src/lib/utils/searchScope.ts Line: 9 Context: Unknown File: src/lib/utils/searchScope.ts Line: 234 Context: Unknown File: src/lib/utils/searchScope.ts Line: 284 Context: Unknown File: src/lib/stores/searchGroupOrder.test.ts Line: 4 Context: Unknown File: src/lib/stores/searchGroupOrder.ts Line: 6 Context: Unknown File: src-tauri/src/commands/repository.rs Line: 494 Context: pub struct SearchUpdateEvent {","breadcrumbs":"Traceability Matrix » UR-050","id":"352","title":"UR-050"},"353":{"body":"Locations: 64 file(s) File: src/lib/api/bindings.ts Line: 385 Context: Unknown File: src/lib/api/bindings.ts Line: 1031 Context: Unknown File: src/lib/api/bindings.ts Line: 2047 Context: Unknown File: src/lib/player/localSource.ts Line: 12 Context: Unknown File: src/lib/player/localSource.ts Line: 55 Context: Unknown File: src/routes/player/[id]/+page.svelte Line: 306 Context: Unknown File: src/routes/player/[id]/+page.svelte Line: 315 Context: Unknown File: src/routes/player/[id]/+page.svelte Line: 385 Context: Unknown File: src/routes/player/[id]/+page.svelte Line: 389 Context: Unknown File: src-tauri/src/commands/storage/mod.rs Line: 93 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 439 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 460 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 480 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 510 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 562 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 748 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 3011 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 3059 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 3079 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 3095 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 359 Context: Unknown File: src-tauri/src/commands/catalog.rs Line: 114 Context: Unknown File: src-tauri/src/commands/catalog.rs Line: 490 Context: Unknown File: src-tauri/src/commands/catalog.rs Line: 551 Context: Unknown File: src-tauri/src/commands/catalog.rs Line: 689 Context: Unknown File: src-tauri/src/commands/catalog.rs Line: 1001 Context: Unknown File: src-tauri/src/commands/catalog.rs Line: 1039 Context: Unknown File: src-tauri/src/commands/catalog.rs Line: 1062 Context: Unknown File: src-tauri/src/commands/catalog.rs Line: 1090 Context: Unknown File: src-tauri/src/storage/schema.rs Line: 813 Context: Unknown File: src-tauri/src/lib.rs Line: 1122 Context: Unknown File: src-tauri/src/lib.rs Line: 1481 Context: Unknown File: src-tauri/src/media_server.rs Line: 27 Context: Unknown File: src-tauri/src/media_server.rs Line: 70 Context: pub fn url_for(&self, path: &str) -> String { File: src-tauri/src/media_server.rs Line: 282 Context: Unknown File: src-tauri/src/media_server.rs Line: 327 Context: Unknown File: src-tauri/src/media_server.rs Line: 398 Context: Unknown File: src-tauri/src/media_server.rs Line: 449 Context: Unknown File: src-tauri/src/media_server.rs Line: 458 Context: Unknown File: src-tauri/src/media_server.rs Line: 473 Context: Unknown File: src-tauri/src/media_server.rs Line: 508 Context: Unknown File: src-tauri/src/media_server.rs Line: 515 Context: Unknown File: src-tauri/src/media_server.rs Line: 540 Context: Unknown File: src-tauri/src/media_server.rs Line: 567 Context: Unknown File: src-tauri/src/player/mod.rs Line: 878 Context: Unknown File: src-tauri/src/player/mod.rs Line: 4014 Context: Unknown File: src-tauri/src/download/cache.rs Line: 33 Context: Unknown File: src-tauri/src/download/cache.rs Line: 220 Context: Unknown File: src-tauri/src/download/cache.rs Line: 320 Context: Unknown File: src-tauri/src/download/cache.rs Line: 428 Context: Unknown File: src-tauri/src/download/cache.rs Line: 550 Context: Unknown File: src-tauri/src/download/worker.rs Line: 219 Context: Unknown File: src-tauri/src/download/worker.rs Line: 313 Context: Unknown File: src-tauri/src/repository/device_profile.rs Line: 277 Context: Unknown File: src-tauri/src/repository/device_profile.rs Line: 457 Context: Unknown File: src-tauri/src/repository/mod.rs Line: 212 Context: Unknown File: src-tauri/src/repository/mod.rs Line: 347 Context: Unknown File: src-tauri/src/repository/mod.rs Line: 369 Context: Unknown File: src-tauri/src/repository/online.rs Line: 2139 Context: Unknown File: src-tauri/src/repository/online.rs Line: 2216 Context: Unknown File: src-tauri/src/repository/online.rs Line: 3402 Context: Unknown File: src-tauri/src/repository/online.rs Line: 3434 Context: Unknown File: src-tauri/src/repository/online.rs Line: 3460 Context: Unknown File: scripts/tauri-security-config.test.ts Line: 11 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-071","id":"353","title":"UR-071"},"354":{"body":"Locations: 6 file(s) File: src/lib/api/bindings.ts Line: 848 Context: Unknown File: src/lib/components/library/WatchedToggleButton.svelte Line: 13 Context: Unknown File: src/lib/services/syncService.ts Line: 121 Context: Unknown File: src-tauri/src/commands/storage/mod.rs Line: 891 Context: Unknown File: src-tauri/src/commands/sync_drain.rs Line: 1047 Context: Unknown File: src-tauri/src/commands/sync_drain.rs Line: 1064 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-073","id":"354","title":"UR-073"},"355":{"body":"Locations: 48 file(s) File: src/lib/api/bindings.ts Line: 251 Context: Unknown File: src/lib/api/bindings.ts Line: 272 Context: Unknown File: src/lib/api/bindings.ts Line: 3181 Context: Unknown File: src/lib/api/bindings.ts Line: 3207 Context: Unknown File: src/lib/api/bindings.ts Line: 3342 Context: Unknown File: src/lib/components/player/hlsRecovery.ts Line: 11 Context: Unknown File: src/lib/components/player/VideoPlayer.svelte Line: 276 Context: Unknown File: src/lib/components/player/VideoPlayer.svelte Line: 809 Context: Unknown File: src/lib/components/player/VideoPlayer.svelte Line: 2291 Context: Unknown File: src/lib/components/player/VideoPlayer.svelte Line: 2759 Context: Unknown File: src/lib/components/player/hlsRecovery.test.ts Line: 16 Context: Unknown File: src/lib/player/adapters/html5Adapter.test.ts Line: 255 Context: Unknown File: src/lib/player/index.ts Line: 195 Context: Unknown File: src/routes/settings/+page.svelte Line: 84 Context: Unknown File: src/routes/settings/+page.svelte Line: 418 Context: function handleEpisodeLimitChange(value: number) { File: src/routes/settings/+page.svelte Line: 918 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 350 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 1613 Context: Unknown File: src-tauri/src/commands/player/settings.rs Line: 3 Context: Unknown File: src-tauri/src/commands/player/settings.rs Line: 25 Context: Unknown File: src-tauri/src/commands/player/settings.rs Line: 85 Context: Unknown File: src-tauri/src/commands/player/settings.rs Line: 107 Context: Unknown File: src-tauri/src/commands/player/settings.rs Line: 122 Context: Unknown File: src-tauri/src/commands/player/settings.rs Line: 158 Context: Unknown File: src-tauri/src/lib.rs Line: 1438 Context: Unknown File: src-tauri/src/repository/online.rs Line: 26 Context: Unknown File: src-tauri/src/repository/online.rs Line: 35 Context: Unknown File: src-tauri/src/repository/online.rs Line: 42 Context: pub fn set_streaming_quality(quality: StreamingQuality) { File: src-tauri/src/repository/online.rs Line: 59 Context: Unknown File: src-tauri/src/repository/online.rs Line: 73 Context: Unknown File: src-tauri/src/repository/online.rs Line: 89 Context: Unknown File: src-tauri/src/repository/online.rs Line: 467 Context: Unknown File: src-tauri/src/repository/online.rs Line: 527 Context: Unknown File: src-tauri/src/repository/online.rs Line: 657 Context: Unknown File: src-tauri/src/repository/online.rs Line: 1691 Context: Unknown File: src-tauri/src/repository/online.rs Line: 1975 Context: Unknown File: src-tauri/src/repository/online.rs Line: 2818 Context: Unknown File: src-tauri/src/repository/online.rs Line: 2842 Context: Unknown File: src-tauri/src/repository/online.rs Line: 2864 Context: Unknown File: src-tauri/src/repository/online.rs Line: 2887 Context: Unknown File: src-tauri/src/repository/online.rs Line: 2964 Context: Unknown File: src-tauri/src/repository/online.rs Line: 3014 Context: Unknown File: src-tauri/src/repository/online.rs Line: 3110 Context: Unknown File: src-tauri/src/settings.rs Line: 167 Context: Unknown File: src-tauri/src/settings.rs Line: 302 Context: Unknown File: src-tauri/src/settings.rs Line: 653 Context: Unknown File: src-tauri/src/settings.rs Line: 689 Context: Unknown File: src-tauri/src/settings.rs Line: 716 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-074","id":"355","title":"UR-074"},"356":{"body":"Locations: 1 file(s) File: src-tauri/src/player/mpv_backend_test.rs Line: 8 Context: Unknown","breadcrumbs":"Traceability Matrix » IT-003","id":"356","title":"IT-003"},"357":{"body":"Locations: 1 file(s) File: src-tauri/src/player/mpv_backend_test.rs Line: 8 Context: Unknown","breadcrumbs":"Traceability Matrix » IT-004","id":"357","title":"IT-004"},"358":{"body":"Locations: 1 file(s) File: src-tauri/src/repository/offline.rs Line: 3026 Context: Unknown","breadcrumbs":"Traceability Matrix » IT-016","id":"358","title":"IT-016"},"359":{"body":"Locations: 1 file(s) File: src-tauri/src/player/queue.rs Line: 535 Context: pub enum AddPosition {","breadcrumbs":"Traceability Matrix » UT-003","id":"359","title":"UT-003"},"36":{"body":"Locations: 18 file(s) File: src/lib/api/bindings.ts Line: 2613 Context: Unknown File: src/lib/api/bindings.ts Line: 3232 Context: Unknown File: src/lib/components/player/VideoPlayer.svelte Line: 304 Context: Unknown File: src/lib/components/player/VideoPlayer.svelte Line: 405 Context: Unknown File: src/lib/components/player/VideoPlayer.svelte Line: 866 Context: Unknown File: src/lib/components/player/VideoPlayer.svelte Line: 2361 Context: Unknown File: src/lib/components/player/subtitleTracks.test.ts Line: 233 Context: Unknown File: src/lib/components/player/subtitleTracks.test.ts Line: 353 Context: Unknown File: src/lib/components/player/subtitleTracks.ts Line: 20 Context: Unknown File: src/lib/components/player/subtitleTracks.ts Line: 210 Context: Unknown File: src/lib/components/player/subtitleTracks.ts Line: 243 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 223 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 422 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 2888 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 2945 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 2977 Context: Unknown File: src-tauri/src/player/media.rs Line: 42 Context: Unknown File: src-tauri/src/repository/online.rs Line: 1174 Context: Unknown","breadcrumbs":"Traceability Matrix » IR-016","id":"36","title":"IR-016"},"360":{"body":"Locations: 1 file(s) File: src-tauri/src/player/queue.rs Line: 535 Context: pub enum AddPosition {","breadcrumbs":"Traceability Matrix » UT-004","id":"360","title":"UT-004"},"361":{"body":"Locations: 1 file(s) File: src-tauri/src/player/queue.rs Line: 535 Context: pub enum AddPosition {","breadcrumbs":"Traceability Matrix » UT-005","id":"361","title":"UT-005"},"362":{"body":"Locations: 1 file(s) File: src-tauri/src/player/sleep_timer.rs Line: 81 Context: pub fn cancel(&mut self) {","breadcrumbs":"Traceability Matrix » UT-012","id":"362","title":"UT-012"},"363":{"body":"Locations: 2 file(s) File: src-tauri/src/player/backend.rs Line: 251 Context: Unknown File: src-tauri/src/player/backend.rs Line: 257 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-026","id":"363","title":"UT-026"},"364":{"body":"Locations: 1 file(s) File: src-tauri/src/player/backend.rs Line: 251 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-027","id":"364","title":"UT-027"},"365":{"body":"Locations: 1 file(s) File: src-tauri/src/player/backend.rs Line: 251 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-028","id":"365","title":"UT-028"},"366":{"body":"Locations: 1 file(s) File: src-tauri/src/player/backend.rs Line: 251 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-029","id":"366","title":"UT-029"},"367":{"body":"Locations: 1 file(s) File: src-tauri/src/player/backend.rs Line: 251 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-030","id":"367","title":"UT-030"},"368":{"body":"Locations: 1 file(s) File: src-tauri/src/player/backend.rs Line: 251 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-031","id":"368","title":"UT-031"},"369":{"body":"Locations: 1 file(s) File: src-tauri/src/player/backend.rs Line: 251 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-032","id":"369","title":"UT-032"},"37":{"body":"Locations: 3 file(s) File: src/lib/api/bindings.ts Line: 172 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 1734 Context: Unknown File: src-tauri/src/player/backend.rs Line: 119 Context: Unknown","breadcrumbs":"Traceability Matrix » IR-018","id":"37","title":"IR-018"},"370":{"body":"Locations: 1 file(s) File: src-tauri/src/player/backend.rs Line: 251 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-033","id":"370","title":"UT-033"},"371":{"body":"Locations: 1 file(s) File: src-tauri/src/repository/offline.rs Line: 3984 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-051","id":"371","title":"UT-051"},"372":{"body":"Locations: 3 file(s) File: src-tauri/src/repository/online.rs Line: 620 Context: Unknown File: src-tauri/src/repository/online.rs Line: 3141 Context: Unknown File: src-tauri/src/repository/online.rs Line: 3182 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-059","id":"372","title":"UT-059"},"373":{"body":"Locations: 1 file(s) File: src-tauri/src/repository/offline.rs Line: 3026 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-067","id":"373","title":"UT-067"},"374":{"body":"Locations: 1 file(s) File: src-tauri/src/repository/offline.rs Line: 3705 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-072","id":"374","title":"UT-072"},"375":{"body":"Locations: 1 file(s) File: src-tauri/src/repository/offline.rs Line: 3904 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-073","id":"375","title":"UT-073"},"376":{"body":"Locations: 1 file(s) File: src-tauri/src/repository/offline.rs Line: 3923 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-074","id":"376","title":"UT-074"},"377":{"body":"Locations: 1 file(s) File: src-tauri/src/repository/offline.rs Line: 3945 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-075","id":"377","title":"UT-075"},"378":{"body":"Locations: 1 file(s) File: src-tauri/src/repository/offline.rs Line: 3727 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-076","id":"378","title":"UT-076"},"379":{"body":"Locations: 1 file(s) File: src-tauri/src/repository/offline.rs Line: 3837 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-077","id":"379","title":"UT-077"},"38":{"body":"Locations: 5 file(s) File: src/lib/api/bindings.ts Line: 142 Context: Unknown File: src/lib/api/bindings.ts Line: 159 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 1530 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 1713 Context: Unknown File: src-tauri/src/player/backend.rs Line: 106 Context: Unknown","breadcrumbs":"Traceability Matrix » IR-019","id":"38","title":"IR-019"},"380":{"body":"Locations: 1 file(s) File: src-tauri/src/repository/offline.rs Line: 3883 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-078","id":"380","title":"UT-078"},"381":{"body":"Locations: 1 file(s) File: src-tauri/src/settings.rs Line: 460 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-079","id":"381","title":"UT-079"},"382":{"body":"Locations: 1 file(s) File: src-tauri/src/settings.rs Line: 489 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-080","id":"382","title":"UT-080"},"383":{"body":"Locations: 1 file(s) File: src-tauri/src/settings.rs Line: 516 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-081","id":"383","title":"UT-081"},"384":{"body":"Locations: 1 file(s) File: src-tauri/src/settings.rs Line: 527 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-082","id":"384","title":"UT-082"},"385":{"body":"Locations: 3 file(s) File: src-tauri/src/repository/online.rs Line: 771 Context: Unknown File: src-tauri/src/repository/online.rs Line: 3728 Context: Unknown File: src-tauri/src/repository/online.rs Line: 3759 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-099","id":"385","title":"UT-099"},"386":{"body":"Locations: 6 file(s) File: src-tauri/src/repository/mod.rs Line: 235 Context: Unknown File: src-tauri/src/repository/online.rs Line: 933 Context: Unknown File: src-tauri/src/repository/online.rs Line: 2251 Context: Unknown File: src-tauri/src/repository/online.rs Line: 3510 Context: Unknown File: src-tauri/src/repository/online.rs Line: 3532 Context: Unknown File: src-tauri/src/repository/online.rs Line: 3541 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-100","id":"386","title":"UT-100"},"387":{"body":"Locations: 4 file(s) File: src-tauri/src/repository/mod.rs Line: 235 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 2014 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 4370 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 4405 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-101","id":"387","title":"UT-101"},"388":{"body":"Locations: 2 file(s) File: src-tauri/src/repository/offline.rs Line: 688 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 4605 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-102","id":"388","title":"UT-102"},"389":{"body":"Locations: 7 file(s) File: src/lib/api/bindings.ts Line: 2268 Context: export type GetItemsOptions = { startIndex?: number | null; limit?: nu... File: src-tauri/src/repository/offline.rs Line: 1269 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 4450 Context: Unknown File: src-tauri/src/repository/online.rs Line: 803 Context: Unknown File: src-tauri/src/repository/online.rs Line: 862 Context: Unknown File: src-tauri/src/repository/online.rs Line: 3563 Context: Unknown File: src-tauri/src/repository/types.rs Line: 342 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-104","id":"389","title":"UT-104"},"39":{"body":"Locations: 8 file(s) File: src-tauri/src/commands/player/settings.rs Line: 3 Context: Unknown File: src-tauri/src/player/mpv_backend.rs Line: 658 Context: Unknown File: src-tauri/src/player/mpv_backend.rs Line: 678 Context: Unknown File: src-tauri/src/player/mpv_backend.rs Line: 747 Context: Unknown File: src-tauri/src/player/mpv_backend.rs Line: 759 Context: Unknown File: src-tauri/src/player/mpv_backend.rs Line: 833 Context: Unknown File: src-tauri/src/settings.rs Line: 1 Context: Unknown File: src-tauri/src/settings.rs Line: 35 Context: Unknown","breadcrumbs":"Traceability Matrix » IR-020","id":"39","title":"IR-020"},"390":{"body":"Locations: 6 file(s) File: src/lib/api/bindings.ts Line: 1707 Context: Unknown File: src-tauri/src/commands/repository.rs Line: 864 Context: Unknown File: src-tauri/src/commands/repository.rs Line: 879 Context: Unknown File: src-tauri/src/commands/repository.rs Line: 898 Context: Unknown File: src-tauri/src/commands/repository.rs Line: 1052 Context: Unknown File: src-tauri/src/commands/repository.rs Line: 1077 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-107","id":"390","title":"UT-107"},"391":{"body":"Locations: 2 file(s) File: src-tauri/src/download/cache.rs Line: 320 Context: Unknown File: src-tauri/src/download/cache.rs Line: 550 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-108","id":"391","title":"UT-108"},"392":{"body":"Locations: 1 file(s) File: src-tauri/src/repository/offline.rs Line: 3306 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-109","id":"392","title":"UT-109"},"393":{"body":"Locations: 1 file(s) File: src-tauri/src/repository/offline.rs Line: 3362 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-110","id":"393","title":"UT-110"},"394":{"body":"Locations: 3 file(s) File: src-tauri/src/repository/offline.rs Line: 62 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 2548 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 2588 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-111","id":"394","title":"UT-111"},"395":{"body":"Locations: 2 file(s) File: src-tauri/src/repository/offline.rs Line: 501 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 3253 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-112","id":"395","title":"UT-112"},"396":{"body":"Locations: 2 file(s) File: src-tauri/src/repository/offline.rs Line: 276 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 3144 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-113","id":"396","title":"UT-113"},"397":{"body":"Locations: 2 file(s) File: src-tauri/src/repository/offline.rs Line: 333 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 3087 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-114","id":"397","title":"UT-114"},"398":{"body":"Locations: 4 file(s) File: src/lib/api/bindings.ts Line: 385 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 510 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 562 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 3095 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-116","id":"398","title":"UT-116"},"399":{"body":"Locations: 2 file(s) File: src-tauri/src/download/cache.rs Line: 220 Context: Unknown File: src-tauri/src/download/cache.rs Line: 428 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-120","id":"399","title":"UT-120"},"4":{"body":"External system integrations and platform-specific implementations. ID Requirement Category Traces To Status IR-001 Build system supporting multiple targets (Linux, Android) Build UR-001 Done IR-002 Build scripts for Android and Linux Build UR-001 Done IR-003 Integration of libmpv for Linux playback Playback UR-003, UR-004 Done IR-004 Integration of ExoPlayer for Android playback Playback UR-003, UR-004 In Progress (basic playback works, audio settings missing) IR-005 MPRIS D-Bus integration for Linux lockscreen/media controls Platform UR-006 Planned — genuinely absent: no mpris/souvlaki/zbus/dbus code or dependency in the project (zbus appears in Cargo.lock only transitively, via tauri-plugin-opener), and no navigator.mediaSession use in the frontend. player::update_lockscreen_metadata is a no-op off Android. UR-006 is therefore Android-only IR-006 Android MediaSession integration for lockscreen controls Platform UR-006 Done IR-007 Bluetooth AVRCP integration via system media session Platform UR-006 Planned IR-008 Android audio focus handling (pause on call) Platform UR-004, UR-006 Done IR-009 Jellyfin API client for authentication API UR-009, UR-012 Done IR-010 Jellyfin API client for library browsing API UR-007, UR-008 Done IR-011 Jellyfin API client for playback streaming API UR-003, UR-004 Done IR-012 Jellyfin Sessions API for remote playback control API UR-010 Done IR-021 Android MediaRouter integration for remote volume in system panel Platform UR-010, UR-016 Planned IR-013 SQLite integration for local database Storage UR-002, UR-011 Done IR-014 Secure credential storage (keyring/keychain) Security UR-012 Done IR-015 Jellyfin API client for playback progress reporting API UR-019, UR-025 Done IR-016 Jellyfin API client for subtitle/audio track info API UR-020, UR-021 Done IR-017 Jellyfin API client for transcoding parameters API UR-022 Planned IR-018 Subtitle rendering and selection in the video playback backends: ExoPlayer sideloads each track as a MediaItem.SubtitleConfiguration and selects by text-track-group position (Android), and the WebKitGTK HTML5 element renders children carrying data-stream-index (Linux). Originally scoped to libmpv, which never implemented it : MpvBackend is the audio-only backend here and does not override PlayerBackend::set_subtitle_track, so the default not_implemented() still stands there. UR-020 is satisfied by the two paths above rather than by MPV Playback UR-020 Done IR-019 Audio track selection in the video playback backends: ExoPlayer switches track by index natively (Android), while the HTML5 path cannot switch a track in the element and instead re-opens the stream at the chosen AudioStreamIndex and resumes at the same position (Linux) — the two outcomes AudioTrackSwitchResponse distinguishes. Originally scoped to libmpv, which never implemented it : MpvBackend does not override PlayerBackend::set_audio_track, so the default not_implemented() still stands there. UR-021 is satisfied by the two paths above rather than by MPV Playback UR-021 Done IR-020 libmpv/ExoPlayer equalizer integration Playback UR-027 Done (Linux/MPV and Android/audiofx.Equalizer; Android pending device verification) IR-022 Jellyfin API client for person/cast data API UR-035, UR-036 Done IR-023 Database schema for person/cast caching Storage UR-035, UR-036 Done IR-024 Jellyfin API client for home screen data (featured, continue watching) API UR-034 Done IR-025 Android background-audio handoff: WebView → native ExoPlayer foreground service on background/lock, and back on foreground (audio continues, video decode stops) Platform UR-040 Done (pending device verification) IR-026 Android picture-in-picture: auto-enter on user-leave-hint via enterPictureInPictureMode, only while a local video surface is actively rendering (never for audio-only playback, menu/library browsing, or remote/cast sessions — enforced by the native canEnterPip guard, re-checked at leave time); aspect-ratio sizing; a play/pause RemoteAction that reflects live player play/pause state (updated whenever playback state changes, not only on button press); WebView hide/restore on mode change Platform UR-041 Done IR-027 Jellyfin /System/Info/Public reachability probe used as an offline→online recovery detector API UR-043 Done IR-028 Jellyfin/LMS SyncGroups API client (list, create, join, unsync, dissolve sync groups) API UR-046 Done IR-029 Android ConnectivityManager/NetworkCapabilities transport probe with a NetworkCallback change subscription, surfaced to the frontend via the AndroidNetworkType JS bridge and the jellytau-network-changed WebView event (requires ACCESS_NETWORK_STATE) Platform UR-053 Done (pending device verification) IR-030 Scheduled full-catalog crawl of every library (Recursive=true, paged) feeding the local index, driven by a Rust background task and the ConnectivityMonitor reconnect signal rather than by the frontend Storage UR-065 Implemented IR-031 Android WindowInsets bridge: an OnApplyWindowInsetsListener on the decor view reports `systemBars() displayCutout()in CSS pixels, pushed into the WebView asjt-insetCSS custom properties plus ajellytau-insets-changedevent, and pullable via theAndroidInsets` JS bridge Platform UR-066 IR-032 Whole-file background download of the item being played, reusing the existing resumable download worker and the Range-capable /Videos/{id}/stream.mp4 endpoint; plus per-platform read-through caching hooks (ExoPlayer CacheDataSource, mpv stream-record) for direct-play sessions only Storage UR-071 Proposed Where a UR is met by a different mechanism than its IR anticipated. Several integration requirements were written when libmpv was expected to be the single playback backend. It is not: MpvBackend is the audio-only backend, Linux plays video through a WebKitGTK HTML5 element (HLS/h264), and Android plays through ExoPlayer. So: UR-020 / UR-021 (subtitle and audio track selection) are Done, but not by MPV — MpvBackend overrides neither PlayerBackend::set_subtitle_track nor set_audio_track, leaving the trait's not_implemented() default. IR-018 and IR-019 have been re-scoped to the backends that actually deliver them (ExoPlayer sideloaded SubtitleConfigurations and native track switching; HTML5 children and stream re-open at the chosen AudioStreamIndex) and marked Done on that basis. IT-008 / IT-009 were re-worded to match. UR-006 (lockscreen / BLE headset control) is Done on Android only , via MediaSessionCompat (IR-006) and ExoPlayer/AudioManager focus (IR-008). IR-005 (MPRIS) remains Planned because it genuinely does not exist — there is no MPRIS/D-Bus code or dependency in the project, and player::update_lockscreen_metadata is a no-op off Android. UR-006's status was corrected rather than IR-005's.","breadcrumbs":"Requirements Specification » 2.1 Integration Requirements","id":"4","title":"2.1 Integration Requirements"},"40":{"body":"Locations: 1 file(s) File: src-tauri/src/playback_mode/mod.rs Line: 165 Context: Unknown","breadcrumbs":"Traceability Matrix » IR-021","id":"40","title":"IR-021"},"400":{"body":"Locations: 3 file(s) File: src-tauri/src/repository/online.rs Line: 527 Context: Unknown File: src-tauri/src/repository/online.rs Line: 620 Context: Unknown File: src-tauri/src/repository/online.rs Line: 2920 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-130","id":"400","title":"UT-130"},"401":{"body":"Locations: 1 file(s) File: src/lib/composables/useOfflineFilterReload.test.ts Line: 1 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-140","id":"401","title":"UT-140"},"402":{"body":"Locations: 7 file(s) File: src/lib/api/bindings.ts Line: 1675 Context: Unknown File: src-tauri/src/commands/sync_drain.rs Line: 380 Context: Unknown File: src-tauri/src/commands/sync_drain.rs Line: 856 Context: Unknown File: src-tauri/src/commands/sync_drain.rs Line: 885 Context: Unknown File: src-tauri/src/commands/sync_drain.rs Line: 915 Context: Unknown File: src-tauri/src/commands/sync_drain.rs Line: 954 Context: Unknown File: src-tauri/src/commands/repository.rs Line: 735 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-151","id":"402","title":"UT-151"},"403":{"body":"Locations: 8 file(s) File: src/lib/api/bindings.ts Line: 899 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 546 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 679 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 3238 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 3303 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 3334 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 3410 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 3437 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-170","id":"403","title":"UT-170"},"404":{"body":"Locations: 22 file(s) File: src/lib/api/bindings.ts Line: 2803 Context: Unknown File: src/lib/services/syncService.ts Line: 7 Context: Unknown File: src/lib/services/offlineCatalog.ts Line: 14 Context: Unknown File: src/lib/stores/connectivity.ts Line: 10 Context: Unknown File: src-tauri/src/commands/sync.rs Line: 7 Context: Unknown File: src-tauri/src/commands/storage/mod.rs Line: 3 Context: Unknown File: src-tauri/src/commands/mod.rs Line: 2 Context: Unknown File: src-tauri/src/commands/offline.rs Line: 136 Context: Unknown File: src-tauri/src/commands/sync_drain.rs Line: 14 Context: Unknown File: src-tauri/src/commands/sync_drain.rs Line: 236 Context: Unknown File: src-tauri/src/commands/catalog.rs Line: 3 Context: Unknown File: src-tauri/src/storage/schema.rs Line: 3 Context: Unknown File: src-tauri/src/storage/mod.rs Line: 157 Context: pub fn file_size(&self) -> Option { File: src-tauri/src/storage/db_service.rs Line: 311 Context: Unknown File: src-tauri/src/lib.rs Line: 1578 Context: Unknown File: src-tauri/src/player/media.rs Line: 142 Context: pub enum MediaType { File: src-tauri/src/repository/hybrid.rs Line: 8 Context: Unknown File: src-tauri/src/repository/hybrid.rs Line: 363 Context: Unknown File: src-tauri/src/repository/hybrid.rs Line: 408 Context: Unknown File: src-tauri/src/repository/hybrid.rs Line: 635 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 3 Context: Unknown File: src-tauri/src/repository/online.rs Line: 1 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-002","id":"404","title":"UR-002"},"405":{"body":"Locations: 19 file(s) File: src/lib/components/library/GenericMediaListPage.svelte Line: 1 Context: Unknown File: src/lib/components/library/AlphabetScrollBar.svelte Line: 1 Context: Unknown File: src/lib/services/imageCache.test.ts Line: 4 Context: Unknown File: src/lib/services/imageCache.ts Line: 2 Context: Unknown File: src/lib/stores/music.ts Line: 3 Context: Unknown File: src/lib/stores/movies.ts Line: 3 Context: Unknown File: src/lib/stores/library.ts Line: 2 Context: Unknown File: src/lib/stores/tv.ts Line: 3 Context: Unknown File: src/routes/library/tv/+page.svelte Line: 8 Context: Unknown File: src/routes/library/movies/+page.svelte Line: 8 Context: Unknown File: src/routes/library/music/+page.svelte Line: 1 Context: Unknown File: src-tauri/src/commands/storage/thumbnails.rs Line: 3 Context: Unknown File: src-tauri/src/commands/repository.rs Line: 4 Context: Unknown File: src-tauri/src/commands/catalog.rs Line: 3 Context: Unknown File: src-tauri/src/repository/online.rs Line: 1 Context: Unknown File: src-tauri/src/repository/online.rs Line: 803 Context: Unknown File: src-tauri/src/repository/online.rs Line: 814 Context: Unknown File: src-tauri/src/repository/online.rs Line: 3602 Context: Unknown File: src-tauri/src/repository/online.rs Line: 3638 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-007","id":"405","title":"UR-007"},"406":{"body":"Locations: 11 file(s) File: src/lib/api/bindings.ts Line: 2803 Context: Unknown File: src/lib/services/preload.test.ts Line: 4 Context: Unknown File: src/lib/services/preload.ts Line: 5 Context: Unknown File: src/lib/stores/downloads.ts Line: 2 Context: Unknown File: src/lib/stores/downloads.test.ts Line: 2 Context: Unknown File: src-tauri/src/commands/storage/mod.rs Line: 3 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 2527 Context: Unknown File: src-tauri/src/commands/mod.rs Line: 2 Context: Unknown File: src-tauri/src/commands/offline.rs Line: 136 Context: Unknown File: src-tauri/src/commands/catalog.rs Line: 878 Context: Unknown File: src-tauri/src/player/media.rs Line: 142 Context: pub enum MediaType {","breadcrumbs":"Traceability Matrix » UR-011","id":"406","title":"UR-011"},"407":{"body":"Locations: 5 file(s) File: src/lib/components/library/GenericMediaListPage.svelte Line: 1 Context: Unknown File: src/lib/components/library/LibraryGrid.svelte Line: 1 Context: Unknown File: src/lib/stores/library.ts Line: 2 Context: Unknown File: src/lib/stores/viewMode.test.ts Line: 7 Context: Unknown File: src/routes/settings/+page.svelte Line: 1 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-029","id":"407","title":"UR-029"},"408":{"body":"Locations: 2 file(s) File: src/lib/components/library/GenericMediaListPage.svelte Line: 1 Context: Unknown File: src/lib/stores/library.ts Line: 2 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-030","id":"408","title":"UR-030"},"409":{"body":"Locations: 2 file(s) File: src-tauri/src/commands/player/settings.rs Line: 3 Context: Unknown File: src-tauri/src/settings.rs Line: 1 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-031","id":"409","title":"UR-031"},"41":{"body":"Locations: 4 file(s) File: src-tauri/src/commands/repository.rs Line: 4 Context: Unknown File: src-tauri/src/repository/online.rs Line: 1174 Context: Unknown File: src-tauri/src/repository/online.rs Line: 2415 Context: Unknown File: src-tauri/src/repository/online.rs Line: 2428 Context: Unknown","breadcrumbs":"Traceability Matrix » IR-022","id":"41","title":"IR-022"},"410":{"body":"Locations: 7 file(s) File: src-tauri/src/commands/player/settings.rs Line: 3 Context: Unknown File: src-tauri/src/player/android/mod.rs Line: 579 Context: Unknown File: src-tauri/src/player/android/mod.rs Line: 617 Context: Unknown File: src-tauri/src/settings.rs Line: 1 Context: Unknown File: src-tauri/src/settings.rs Line: 383 Context: Unknown File: src-tauri/src/settings.rs Line: 400 Context: Unknown File: src-tauri/src/settings.rs Line: 424 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-032","id":"410","title":"UR-032"},"411":{"body":"Locations: 7 file(s) File: src/lib/components/library/CrewLinks.svelte Line: 1 Context: Unknown File: src/lib/components/library/CastSection.svelte Line: 1 Context: Unknown File: src/routes/library/[id]/+page.svelte Line: 1 Context: Unknown File: src-tauri/src/commands/storage/people.rs Line: 3 Context: Unknown File: src-tauri/src/commands/repository.rs Line: 4 Context: Unknown File: src-tauri/src/repository/online.rs Line: 1174 Context: Unknown File: src-tauri/src/repository/online.rs Line: 2415 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-035","id":"411","title":"UR-035"},"412":{"body":"Locations: 5 file(s) File: src/lib/components/library/PersonDetailView.svelte Line: 1 Context: Unknown File: src-tauri/src/commands/storage/people.rs Line: 3 Context: Unknown File: src-tauri/src/commands/repository.rs Line: 4 Context: Unknown File: src-tauri/src/repository/online.rs Line: 2415 Context: Unknown File: src-tauri/src/repository/online.rs Line: 2428 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-036","id":"412","title":"UR-036"},"413":{"body":"Locations: 4 file(s) File: src/lib/components/library/LibraryGrid.svelte Line: 1 Context: Unknown File: src/lib/components/library/MediaCard.svelte Line: 1 Context: Unknown File: src/routes/library/tv/+page.svelte Line: 8 Context: Unknown File: src/routes/library/movies/+page.svelte Line: 8 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-037","id":"413","title":"UR-037"},"414":{"body":"Locations: 1 file(s) File: src/routes/library/[id]/+page.svelte Line: 1 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-038","id":"414","title":"UR-038"},"415":{"body":"Locations: 2 file(s) File: src-tauri/src/commands/auth.rs Line: 3 Context: Unknown File: src-tauri/src/auth/mod.rs Line: 139 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-042","id":"415","title":"UR-042"},"416":{"body":"Locations: 1 file(s) File: src-tauri/src/commands/player/remote.rs Line: 3 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-046","id":"416","title":"UR-046"},"417":{"body":"Locations: 1 file(s) File: src-tauri/src/commands/storage/mod.rs Line: 3 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-047","id":"417","title":"UR-047"},"418":{"body":"Locations: 6 file(s) File: src/lib/components/library/EpisodeFocusView.svelte Line: 7 Context: Unknown File: src/lib/components/library/EpisodeFocusView.svelte Line: 289 Context: Unknown File: src/lib/components/library/EpisodeFocusView.svelte Line: 394 Context: Unknown File: src/lib/components/library/episodeStrip.ts Line: 8 Context: Unknown File: src/lib/components/library/EpisodeFocusView.test.ts Line: 7 Context: Unknown File: src/routes/library/[id]/+page.svelte Line: 1 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-048","id":"418","title":"UR-048"},"419":{"body":"Locations: 2 file(s) File: src/lib/components/library/LibraryGrid.svelte Line: 1 Context: Unknown File: src/lib/components/library/MediaCard.svelte Line: 1 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-051","id":"419","title":"UR-051"},"42":{"body":"Locations: 1 file(s) File: src-tauri/src/commands/storage/people.rs Line: 3 Context: Unknown","breadcrumbs":"Traceability Matrix » IR-023","id":"42","title":"IR-023"},"420":{"body":"Locations: 23 file(s) File: src/lib/components/library/GenericMediaListPage.svelte Line: 103 Context: Unknown File: src/lib/components/library/GenericGenreBrowser.svelte Line: 67 Context: async function reloadGenreBrowse() { File: src/lib/components/library/MediaCard.svelte Line: 1 Context: Unknown File: src/lib/services/offlineCatalog.reload.test.ts Line: 11 Context: Unknown File: src/lib/services/offlineCatalog.ts Line: 14 Context: Unknown File: src/lib/services/offlineCatalog.ts Line: 46 Context: Unknown File: src/lib/stores/connectivity.ts Line: 10 Context: Unknown File: src/lib/stores/connectivity.ts Line: 250 Context: Unknown File: src/lib/composables/useOfflineFilterReload.test.ts Line: 1 Context: Unknown File: src/lib/composables/useOfflineFilterReload.ts Line: 26 Context: Unknown File: src/routes/library/favorites/+page.svelte Line: 88 Context: Unknown File: src/routes/library/tv/+page.svelte Line: 74 Context: async function load() { File: src/routes/library/movies/+page.svelte Line: 73 Context: async function load() { File: src/routes/library/music/+page.svelte Line: 71 Context: async function load() { File: src/routes/library/[id]/+page.svelte Line: 100 Context: Unknown File: src-tauri/src/commands/catalog.rs Line: 551 Context: Unknown File: src-tauri/src/commands/catalog.rs Line: 878 Context: Unknown File: src-tauri/src/commands/catalog.rs Line: 1001 Context: Unknown File: src-tauri/src/repository/hybrid.rs Line: 8 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 3 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 24 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 43 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 3026 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-052","id":"420","title":"UR-052"},"421":{"body":"Locations: 1 file(s) File: src/routes/settings/+page.svelte Line: 1 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-057","id":"421","title":"UR-057"},"422":{"body":"Locations: 11 file(s) File: src/lib/components/library/EpisodeFocusView.svelte Line: 7 Context: Unknown File: src/lib/components/library/EpisodeFocusView.svelte Line: 250 Context: Unknown File: src/lib/components/library/MediaCard.svelte Line: 42 Context: Unknown File: src/lib/components/library/EpisodeFocusView.test.ts Line: 7 Context: Unknown File: src/routes/library/[id]/+page.svelte Line: 1 Context: Unknown File: src/routes/library/[id]/+page.svelte Line: 139 Context: Unknown File: src/routes/library/[id]/+page.svelte Line: 233 Context: Unknown File: src/routes/library/[id]/+page.svelte Line: 467 Context: Unknown File: src/routes/search/+page.svelte Line: 97 Context: Unknown File: src/routes/+page.svelte Line: 74 Context: Unknown File: src/routes/+page.svelte Line: 98 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-058","id":"422","title":"UR-058"},"423":{"body":"Locations: 10 file(s) File: src/lib/components/library/GenericMediaListPage.svelte Line: 50 Context: Unknown File: src/lib/components/library/GenericGenreBrowser.svelte Line: 45 Context: Unknown File: src/lib/components/library/LibraryViewTabs.svelte Line: 8 Context: Unknown File: src/lib/utils/libraryView.ts Line: 9 Context: Unknown File: src/routes/library/tv/+page.svelte Line: 8 Context: Unknown File: src/routes/library/tv/shows/+page.ts Line: 6 Context: Unknown File: src/routes/library/movies/all/+page.ts Line: 6 Context: Unknown File: src/routes/library/movies/genres/+page.ts Line: 6 Context: Unknown File: src/routes/library/movies/+page.svelte Line: 8 Context: Unknown File: src/routes/library/shows/genres/+page.ts Line: 6 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-063","id":"423","title":"UR-063"},"424":{"body":"Locations: 25 file(s) File: src-tauri/src/commands/storage/people.rs Line: 56 Context: Unknown File: src-tauri/src/commands/repository.rs Line: 51 Context: Unknown File: src-tauri/src/commands/catalog.rs Line: 96 Context: Unknown File: src-tauri/src/commands/catalog.rs Line: 164 Context: Unknown File: src-tauri/src/commands/catalog.rs Line: 281 Context: Unknown File: src-tauri/src/commands/catalog.rs Line: 327 Context: Unknown File: src-tauri/src/commands/catalog.rs Line: 764 Context: Unknown File: src-tauri/src/storage/schema.rs Line: 757 Context: Unknown File: src-tauri/src/storage/schema.rs Line: 769 Context: Unknown File: src-tauri/src/lib.rs Line: 1564 Context: Unknown File: src-tauri/src/repository/hybrid.rs Line: 64 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 62 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 276 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 333 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 501 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 1253 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 2548 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 2588 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 3087 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 3144 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 3253 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 3306 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 3362 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 4500 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 4553 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-065","id":"424","title":"UR-065"},"425":{"body":"Locations: 14 file(s) File: src/lib/components/library/PlaylistDetailView.svelte Line: 229 Context: Unknown File: src/lib/components/library/EpisodeFocusView.svelte Line: 250 Context: Unknown File: src/lib/components/library/MediaCard.svelte Line: 1 Context: Unknown File: src/lib/components/library/MediaCard.svelte Line: 48 Context: Unknown File: src/lib/components/library/MediaCard.svelte Line: 154 Context: Unknown File: src/lib/components/library/MediaCard.svelte Line: 329 Context: Unknown File: src/lib/components/library/ArtistDetailView.svelte Line: 138 Context: Unknown File: src/lib/components/FavoriteButton.svelte Line: 1 Context: Unknown File: src/lib/services/favorites.ts Line: 2 Context: Unknown File: src/lib/services/favorites.ts Line: 38 Context: Unknown File: src/lib/stores/favorites.test.ts Line: 1 Context: Unknown File: src/lib/stores/favorites.ts Line: 8 Context: Unknown File: src/lib/stores/favorites.ts Line: 55 Context: Unknown File: src/routes/library/[id]/+page.svelte Line: 619 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-068","id":"425","title":"UR-068"},"426":{"body":"Locations: 24 file(s) File: src/routes/library/favorites/+page.svelte Line: 76 Context: Unknown File: src/routes/+layout.svelte Line: 150 Context: Unknown File: src-tauri/src/commands/favorites.rs Line: 12 Context: Unknown File: src-tauri/src/commands/favorites.rs Line: 78 Context: Unknown File: src-tauri/src/commands/favorites.rs Line: 142 Context: Unknown File: src-tauri/src/commands/favorites.rs Line: 286 Context: Unknown File: src-tauri/src/commands/favorites.rs Line: 320 Context: Unknown File: src-tauri/src/commands/favorites.rs Line: 340 Context: Unknown File: src-tauri/src/commands/favorites.rs Line: 356 Context: Unknown File: src-tauri/src/commands/repository.rs Line: 858 Context: Unknown File: src-tauri/src/commands/repository.rs Line: 864 Context: Unknown File: src-tauri/src/commands/repository.rs Line: 879 Context: Unknown File: src-tauri/src/commands/repository.rs Line: 1052 Context: Unknown File: src-tauri/src/commands/repository.rs Line: 1077 Context: Unknown File: src-tauri/src/lib.rs Line: 1571 Context: Unknown File: src-tauri/src/repository/hybrid.rs Line: 47 Context: pub fn new(online: OnlineRepository, offline: OfflineRepository) -> Se... File: src-tauri/src/repository/offline.rs Line: 688 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 4605 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 4692 Context: Unknown File: src-tauri/src/repository/online.rs Line: 132 Context: impl OnlineRepository { File: src-tauri/src/repository/online.rs Line: 771 Context: Unknown File: src-tauri/src/repository/online.rs Line: 1058 Context: Unknown File: src-tauri/src/repository/online.rs Line: 3728 Context: Unknown File: src-tauri/src/repository/online.rs Line: 3759 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-069","id":"426","title":"UR-069"},"427":{"body":"Locations: 12 file(s) File: src/lib/api/bindings.ts Line: 2300 Context: export type Library = { id: string; name: string; collectionType: stri... File: src/lib/components/library/libraryMosaic.ts Line: 12 Context: Unknown File: src/lib/components/library/MosaicGrid.svelte Line: 14 Context: Unknown File: src/lib/components/library/mosaic.ts Line: 12 Context: Unknown File: src/lib/components/library/MosaicTile.svelte Line: 10 Context: Unknown File: src/lib/components/common/CachedImage.svelte Line: 18 Context: Unknown File: src/lib/utils/favoritesView.ts Line: 44 Context: Unknown File: src/routes/library/+page.svelte Line: 53 Context: Unknown File: src/routes/library/+page.svelte Line: 288 Context: Unknown File: src/routes/+page.svelte Line: 124 Context: Unknown File: src-tauri/src/repository/types.rs Line: 47 Context: Unknown File: src-tauri/src/repository/types.rs Line: 404 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-075","id":"427","title":"UR-075"},"428":{"body":"Locations: 44 file(s) File: src/lib/api/bindings.ts Line: 1107 Context: Unknown File: src/lib/api/bindings.ts Line: 1119 Context: Unknown File: src/lib/api/bindings.ts Line: 1137 Context: Unknown File: src/lib/api/bindings.ts Line: 2226 Context: export type EqPreset = \"flat\" | \"rock\" | \"pop\" | \"jazz\" | \"classical\" ... File: src/lib/api/bindings.ts Line: 2315 Context: Unknown File: src/lib/components/library/GenericMediaListPage.svelte Line: 125 Context: Unknown File: src/lib/stores/music.ts Line: 106 Context: Unknown File: src/routes/settings/+page.svelte Line: 1 Context: Unknown File: src/routes/settings/+page.svelte Line: 105 Context: Unknown File: src/routes/settings/+page.svelte Line: 201 Context: Unknown File: src/routes/settings/+page.svelte Line: 220 Context: Unknown File: src/routes/settings/+page.svelte Line: 596 Context: Unknown File: src-tauri/src/commands/library.rs Line: 10 Context: Unknown File: src-tauri/src/commands/library.rs Line: 47 Context: Unknown File: src-tauri/src/commands/library.rs Line: 68 Context: Unknown File: src-tauri/src/commands/library.rs Line: 83 Context: Unknown File: src-tauri/src/commands/library.rs Line: 112 Context: Unknown File: src-tauri/src/commands/library.rs Line: 184 Context: Unknown File: src-tauri/src/commands/library.rs Line: 220 Context: Unknown File: src-tauri/src/commands/library.rs Line: 274 Context: Unknown File: src-tauri/src/commands/library.rs Line: 295 Context: Unknown File: src-tauri/src/commands/library.rs Line: 307 Context: Unknown File: src-tauri/src/lib.rs Line: 1450 Context: Unknown File: src-tauri/src/repository/hybrid.rs Line: 164 Context: Unknown File: src-tauri/src/repository/hybrid.rs Line: 265 Context: Unknown File: src-tauri/src/repository/hybrid.rs Line: 363 Context: Unknown File: src-tauri/src/repository/hybrid.rs Line: 408 Context: Unknown File: src-tauri/src/repository/hybrid.rs Line: 526 Context: Unknown File: src-tauri/src/repository/hybrid.rs Line: 611 Context: Unknown File: src-tauri/src/repository/exclusions.rs Line: 22 Context: Unknown File: src-tauri/src/repository/exclusions.rs Line: 34 Context: Unknown File: src-tauri/src/repository/exclusions.rs Line: 48 Context: Unknown File: src-tauri/src/repository/exclusions.rs Line: 63 Context: Unknown File: src-tauri/src/repository/exclusions.rs Line: 87 Context: / pure function and can be tested without touching process state. File: src-tauri/src/repository/exclusions.rs Line: 123 Context: Unknown File: src-tauri/src/repository/exclusions.rs Line: 191 Context: Unknown File: src-tauri/src/repository/exclusions.rs Line: 203 Context: Unknown File: src-tauri/src/repository/exclusions.rs Line: 232 Context: Unknown File: src-tauri/src/repository/exclusions.rs Line: 275 Context: Unknown File: src-tauri/src/repository/exclusions.rs Line: 293 Context: Unknown File: src-tauri/src/repository/exclusions.rs Line: 316 Context: Unknown File: src-tauri/src/repository/exclusions.rs Line: 334 Context: Unknown File: src-tauri/src/settings.rs Line: 337 Context: Unknown File: src-tauri/src/settings.rs Line: 357 Context: impl LibrarySettings {","breadcrumbs":"Traceability Matrix » UR-076","id":"428","title":"UR-076"},"429":{"body":"Locations: 15 file(s) File: src/lib/api/bindings.ts Line: 1787 Context: Unknown File: src/lib/api/bindings.ts Line: 1795 Context: Unknown File: src/lib/api/bindings.ts Line: 1809 Context: Unknown File: src/lib/utils/logger.ts Line: 202 Context: export function isLevelEnabled(level: LogLevel): boolean { File: src/routes/settings/+page.svelte Line: 494 Context: Unknown File: src/routes/settings/+page.svelte Line: 1219 Context: Unknown File: src-tauri/src/commands/diagnostics.rs Line: 3 Context: Unknown File: src-tauri/src/commands/diagnostics.rs Line: 91 Context: Unknown File: src-tauri/src/commands/diagnostics.rs Line: 115 Context: Unknown File: src-tauri/src/commands/diagnostics.rs Line: 187 Context: Unknown File: src-tauri/src/lib.rs Line: 1013 Context: Unknown File: src-tauri/src/lib.rs Line: 1143 Context: Unknown File: src-tauri/src/lib.rs Line: 1182 Context: pub fn run() { File: src-tauri/src/lib.rs Line: 1224 Context: Unknown File: src-tauri/src/utils/diagnostics.rs Line: 3 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-078","id":"429","title":"UR-078"},"43":{"body":"Locations: 9 file(s) File: src/lib/api/bindings.ts Line: 1515 Context: Unknown File: src/lib/api/bindings.ts Line: 1523 Context: Unknown File: src-tauri/src/commands/repository.rs Line: 4 Context: Unknown File: src-tauri/src/commands/repository.rs Line: 307 Context: Unknown File: src-tauri/src/commands/repository.rs Line: 333 Context: Unknown File: src-tauri/src/repository/online.rs Line: 888 Context: Unknown File: src-tauri/src/repository/online.rs Line: 1205 Context: Unknown File: src-tauri/src/repository/online.rs Line: 1232 Context: Unknown File: src-tauri/src/repository/online.rs Line: 1410 Context: Unknown","breadcrumbs":"Traceability Matrix » IR-024","id":"43","title":"IR-024"},"430":{"body":"Locations: 5 file(s) File: src/lib/api/bindings.ts Line: 2613 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 223 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 422 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 2888 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 2945 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-145","id":"430","title":"UT-145"},"431":{"body":"Locations: 3 file(s) File: src/lib/api/bindings.ts Line: 3232 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 2977 Context: Unknown File: src-tauri/src/player/media.rs Line: 42 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-146","id":"431","title":"UT-146"},"432":{"body":"Locations: 1 file(s) File: src/lib/components/library/libraryMosaic.ts Line: 12 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-167","id":"432","title":"UT-167"},"433":{"body":"Locations: 24 file(s) File: src/lib/api/bindings.ts Line: 2473 Context: Unknown File: src/lib/components/player/VideoPlayer.svelte Line: 378 Context: Unknown File: src/lib/components/player/subtitleTracks.test.ts Line: 67 Context: Unknown File: src/lib/components/player/subtitleTracks.test.ts Line: 93 Context: Unknown File: src/lib/components/player/subtitleTracks.test.ts Line: 110 Context: Unknown File: src/lib/components/player/subtitleTracks.ts Line: 75 Context: Unknown File: src-tauri/src/repository/device_profile.rs Line: 84 Context: Unknown File: src-tauri/src/repository/device_profile.rs Line: 99 Context: Unknown File: src-tauri/src/repository/device_profile.rs Line: 120 Context: Unknown File: src-tauri/src/repository/device_profile.rs Line: 150 Context: Unknown File: src-tauri/src/repository/device_profile.rs Line: 184 Context: Unknown File: src-tauri/src/repository/device_profile.rs Line: 293 Context: Unknown File: src-tauri/src/repository/device_profile.rs Line: 306 Context: Unknown File: src-tauri/src/repository/device_profile.rs Line: 344 Context: Unknown File: src-tauri/src/repository/device_profile.rs Line: 362 Context: Unknown File: src-tauri/src/repository/device_profile.rs Line: 376 Context: Unknown File: src-tauri/src/repository/device_profile.rs Line: 390 Context: Unknown File: src-tauri/src/repository/online.rs Line: 1765 Context: Unknown File: src-tauri/src/repository/online.rs Line: 1835 Context: Unknown File: src-tauri/src/repository/online.rs Line: 1948 Context: Unknown File: src-tauri/src/repository/online.rs Line: 1994 Context: Unknown File: src-tauri/src/repository/online.rs Line: 3043 Context: Unknown File: src-tauri/src/repository/online.rs Line: 3066 Context: Unknown File: src-tauri/src/repository/types.rs Line: 265 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-168","id":"433","title":"UT-168"},"434":{"body":"Locations: 2 file(s) File: src/lib/components/player/AudioPlayer.svelte Line: 1 Context: Unknown File: src/lib/components/player/MiniPlayer.svelte Line: 1 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-028","id":"434","title":"UR-028"},"435":{"body":"Locations: 7 file(s) File: src/lib/components/AppHeader.svelte Line: 10 Context: Unknown File: src/lib/components/search/HeaderSearch.test.ts Line: 9 Context: Unknown File: src/lib/components/search/HeaderSearch.svelte Line: 12 Context: Unknown File: src/lib/components/account/AccountMenu.svelte Line: 7 Context: Unknown File: src/lib/utils/layoutShell.ts Line: 75 Context: Unknown File: src/lib/utils/layoutShell.ts Line: 90 Context: Unknown File: src/lib/utils/scrollRestore.ts Line: 26 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-054","id":"435","title":"UR-054"},"436":{"body":"Locations: 11 file(s) File: src/lib/components/BottomUi.svelte Line: 23 Context: Unknown File: src/lib/components/player/controlsVisibility.ts Line: 4 Context: Unknown File: src/lib/components/player/controlsVisibility.test.ts Line: 4 Context: Unknown File: src/lib/components/player/VideoPlayer.svelte Line: 496 Context: Unknown File: src/lib/components/player/VideoPlayer.svelte Line: 2013 Context: Unknown File: src/lib/utils/immersive.ts Line: 4 Context: Unknown File: src/lib/utils/layoutShell.test.ts Line: 158 Context: Unknown File: src/lib/utils/safeArea.test.ts Line: 17 Context: Unknown File: src/lib/utils/safeArea.ts Line: 4 Context: Unknown File: src/lib/utils/layoutShell.ts Line: 115 Context: Unknown File: src/routes/+layout.svelte Line: 269 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-066","id":"436","title":"UR-066"},"437":{"body":"Locations: 2 file(s) File: src/lib/components/player/hlsRecovery.ts Line: 11 Context: Unknown File: src/lib/components/player/hlsRecovery.test.ts Line: 16 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-174","id":"437","title":"UT-174"},"438":{"body":"Locations: 4 file(s) File: src/lib/components/player/VideoPlayer.tapSurface.test.ts Line: 5 Context: Unknown File: src/lib/components/player/VideoPlayer.touchScrub.test.ts Line: 21 Context: Unknown File: src/lib/components/player/tapGestures.ts Line: 21 Context: Unknown File: src/lib/components/player/VideoPlayer.svelte Line: 1 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-061","id":"438","title":"UR-061"},"439":{"body":"Locations: 3 file(s) File: src/lib/components/player/VideoPlayer.svelte Line: 396 Context: Unknown File: src/lib/components/player/subtitleTracks.test.ts Line: 18 Context: Unknown File: src/lib/components/player/subtitleTracks.ts Line: 20 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-143","id":"439","title":"UT-143"},"44":{"body":"Locations: 2 file(s) File: src/lib/utils/backgroundAudio.ts Line: 4 Context: Unknown File: src/lib/utils/backgroundAudio.test.ts Line: 7 Context: Unknown","breadcrumbs":"Traceability Matrix » IR-025","id":"44","title":"IR-025"},"440":{"body":"Locations: 3 file(s) File: src/lib/components/player/VideoPlayer.svelte Line: 396 Context: Unknown File: src/lib/components/player/subtitleTracks.test.ts Line: 18 Context: Unknown File: src/lib/components/player/subtitleTracks.ts Line: 20 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-144","id":"440","title":"UT-144"},"441":{"body":"Locations: 8 file(s) File: src/lib/components/player/VideoPlayer.svelte Line: 405 Context: Unknown File: src/lib/components/player/VideoPlayer.svelte Line: 866 Context: Unknown File: src/lib/components/player/VideoPlayer.svelte Line: 2361 Context: Unknown File: src/lib/components/player/subtitleTracks.test.ts Line: 233 Context: Unknown File: src/lib/components/player/subtitleTracks.test.ts Line: 353 Context: Unknown File: src/lib/components/player/subtitleTracks.ts Line: 20 Context: Unknown File: src/lib/components/player/subtitleTracks.ts Line: 210 Context: Unknown File: src/lib/components/player/subtitleTracks.ts Line: 243 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-147","id":"441","title":"UT-147"},"442":{"body":"Locations: 1 file(s) File: src/lib/player/adapters/html5Adapter.test.ts Line: 255 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-175","id":"442","title":"UT-175"},"443":{"body":"Locations: 2 file(s) File: src/lib/components/player/mediaReady.ts Line: 4 Context: Unknown File: src/lib/components/player/mediaReady.test.ts Line: 4 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-184","id":"443","title":"UT-184"},"444":{"body":"Locations: 2 file(s) File: src/lib/components/player/VideoPlayer.nativeReveal.test.ts Line: 16 Context: Unknown File: src/lib/components/player/VideoPlayer.svelte Line: 529 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-185","id":"444","title":"UT-185"},"445":{"body":"Locations: 1 file(s) File: src/lib/components/player/VideoPlayer.svelte Line: 558 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-187","id":"445","title":"UT-187"},"446":{"body":"Locations: 5 file(s) File: src/lib/components/player/controlsVisibility.ts Line: 4 Context: Unknown File: src/lib/components/player/controlsVisibility.test.ts Line: 4 Context: Unknown File: src/lib/components/player/VideoPlayer.svelte Line: 496 Context: Unknown File: src/lib/components/player/VideoPlayer.svelte Line: 2013 Context: Unknown File: scripts/extract-traces.test.ts Line: 61 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-188","id":"446","title":"UT-188"},"447":{"body":"Locations: 1 file(s) File: src/lib/components/player/VideoPlayer.svelte Line: 1553 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-189","id":"447","title":"UT-189"},"448":{"body":"Locations: 11 file(s) File: src/lib/components/player/MiniPlayer.svelte Line: 1 Context: Unknown File: src/lib/components/FavoriteButton.svelte Line: 1 Context: Unknown File: src/lib/services/favorites.test.ts Line: 4 Context: Unknown File: src/lib/services/favorites.ts Line: 2 Context: Unknown File: src/lib/services/syncService.ts Line: 7 Context: Unknown File: src/lib/services/syncService.ts Line: 61 Context: Unknown File: src/lib/services/syncService.ts Line: 195 Context: Unknown File: src-tauri/src/commands/sync.rs Line: 7 Context: Unknown File: src-tauri/src/commands/storage/mod.rs Line: 3 Context: Unknown File: src-tauri/src/commands/mod.rs Line: 2 Context: Unknown File: src-tauri/src/repository/online.rs Line: 2275 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-017","id":"448","title":"UR-017"},"449":{"body":"Locations: 1 file(s) File: src/lib/components/BottomNav.svelte Line: 1 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-039","id":"449","title":"UR-039"},"45":{"body":"Locations: 1 file(s) File: src/lib/utils/pictureInPicture.ts Line: 4 Context: Unknown","breadcrumbs":"Traceability Matrix » IR-026","id":"45","title":"IR-026"},"450":{"body":"Locations: 2 file(s) File: src/lib/components/home/heroRotation.ts Line: 8 Context: Unknown File: src/lib/components/home/heroRotation.test.ts Line: 1 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-207","id":"450","title":"UT-207"},"451":{"body":"Locations: 16 file(s) File: src/lib/components/sessions/SessionPickerModal.svelte Line: 1 Context: Unknown File: src/lib/components/sessions/CastButton.svelte Line: 1 Context: Unknown File: src/lib/components/sessions/RemoteControls.svelte Line: 1 Context: Unknown File: src/lib/components/sessions/SessionsList.svelte Line: 1 Context: Unknown File: src/lib/components/sessions/SessionCard.svelte Line: 1 Context: Unknown File: src/lib/stores/playbackMode.test.ts Line: 2 Context: Unknown File: src/lib/stores/lmsSync.ts Line: 12 Context: Unknown File: src/lib/stores/playbackMode.ts Line: 9 Context: Unknown File: src/lib/stores/sessions.ts Line: 2 Context: Unknown File: src/lib/stores/sessions.test.ts Line: 2 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 1 Context: Unknown File: src-tauri/src/commands/player/remote.rs Line: 3 Context: Unknown File: src-tauri/src/commands/playback_mode.rs Line: 3 Context: Unknown File: src-tauri/src/commands/sessions.rs Line: 1 Context: Unknown File: src-tauri/src/session_poller/mod.rs Line: 6 Context: Unknown File: src-tauri/src/playback_mode/mod.rs Line: 165 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-010","id":"451","title":"UR-010"},"452":{"body":"Locations: 2 file(s) File: src/lib/player/adapters/html5Adapter.test.ts Line: 216 Context: Unknown File: src/lib/player/adapters/html5Adapter.ts Line: 172 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-183","id":"452","title":"UT-183"},"453":{"body":"Locations: 21 file(s) File: src/lib/services/imageCache.ts Line: 50 Context: Unknown File: src/lib/stores/auth.ts Line: 6 Context: Unknown File: src/lib/stores/auth.ts Line: 303 Context: Unknown File: src/lib/stores/auth.ts Line: 465 Context: Unknown File: src/lib/stores/auth.test.ts Line: 4 Context: Unknown File: src-tauri/src/commands/storage/mod.rs Line: 3 Context: Unknown File: src-tauri/src/commands/mod.rs Line: 2 Context: Unknown File: src-tauri/src/credentials.rs Line: 10 Context: Unknown File: src-tauri/src/credentials.rs Line: 480 Context: Unknown File: src-tauri/src/credentials.rs Line: 905 Context: Unknown File: src-tauri/src/credentials.rs Line: 924 Context: Unknown File: src-tauri/src/credentials.rs Line: 941 Context: Unknown File: src-tauri/src/android_context.rs Line: 3 Context: Unknown File: src-tauri/src/android_context.rs Line: 80 Context: Unknown File: src-tauri/src/android_context.rs Line: 95 Context: Unknown File: src-tauri/src/storage/mod.rs Line: 157 Context: pub fn file_size(&self) -> Option { File: src-tauri/src/storage/db_service.rs Line: 311 Context: Unknown File: src-tauri/src/lib.rs Line: 623 Context: Unknown File: src-tauri/src/lib.rs Line: 1122 Context: Unknown File: src-tauri/src/lib.rs Line: 1294 Context: Unknown File: scripts/tauri-security-config.test.ts Line: 11 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-012","id":"453","title":"UR-012"},"454":{"body":"Locations: 11 file(s) File: src/lib/services/skipReporting.test.ts Line: 9 Context: Unknown File: src/lib/services/skipReporting.ts Line: 14 Context: Unknown File: src/lib/stores/continueWatchingFilter.test.ts Line: 12 Context: Unknown File: src/lib/stores/home.ts Line: 2 Context: Unknown File: src/lib/stores/continueWatchingFilter.ts Line: 15 Context: Unknown File: src/lib/stores/continueWatchingFilter.ts Line: 91 Context: Unknown File: src/lib/stores/tv.ts Line: 3 Context: Unknown File: src-tauri/src/repository/online.rs Line: 910 Context: Unknown File: src-tauri/src/repository/online.rs Line: 1232 Context: Unknown File: src-tauri/src/repository/online.rs Line: 3688 Context: Unknown File: src-tauri/src/repository/online.rs Line: 3709 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-059","id":"454","title":"UR-059"},"455":{"body":"Locations: 1 file(s) File: src/lib/utils/updateCheck.test.ts Line: 4 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-208","id":"455","title":"UT-208"},"456":{"body":"Locations: 7 file(s) File: src/lib/utils/searchScope.ts Line: 149 Context: export type SearchGroupId = File: src/lib/utils/searchScope.ts Line: 215 Context: Unknown File: src-tauri/src/commands/catalog.rs Line: 96 Context: Unknown File: src-tauri/src/domain/search_rank.rs Line: 106 Context: Unknown File: src-tauri/src/storage/schema.rs Line: 769 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 333 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 3087 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-060","id":"456","title":"UR-060"},"457":{"body":"Locations: 1 file(s) File: src/lib/utils/scrollContainer.ts Line: 23 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-072","id":"457","title":"UR-072"},"458":{"body":"Locations: 4 file(s) File: src/lib/utils/updateCheck.ts Line: 4 Context: Unknown File: src/routes/settings/+page.svelte Line: 446 Context: Unknown File: src/routes/settings/+page.svelte Line: 1299 Context: Unknown File: src-tauri/src/lib.rs Line: 1232 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-077","id":"458","title":"UR-077"},"459":{"body":"Locations: 2 file(s) File: src/lib/utils/logger.test.ts Line: 22 Context: Unknown File: src/lib/utils/logger.test.ts Line: 333 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-201","id":"459","title":"UT-201"},"46":{"body":"Locations: 1 file(s) File: src-tauri/src/commands/connectivity.rs Line: 3 Context: Unknown","breadcrumbs":"Traceability Matrix » IR-027","id":"46","title":"IR-027"},"460":{"body":"Locations: 9 file(s) File: src-tauri/src/commands/player/mod.rs Line: 1756 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 1775 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 2844 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 1253 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 4500 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 4553 Context: Unknown File: src-tauri/src/repository/online.rs Line: 814 Context: Unknown File: src-tauri/src/repository/online.rs Line: 3602 Context: Unknown File: src-tauri/src/repository/online.rs Line: 3638 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-206","id":"460","title":"UT-206"},"461":{"body":"Locations: 1 file(s) File: src/lib/utils/logger.test.ts Line: 352 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-209","id":"461","title":"UT-209"},"462":{"body":"Locations: 12 file(s) File: src-tauri/src/commands/player/settings.rs Line: 3 Context: Unknown File: src-tauri/src/player/mpv_backend.rs Line: 658 Context: Unknown File: src-tauri/src/player/mpv_backend.rs Line: 712 Context: Unknown File: src-tauri/src/player/mpv_backend.rs Line: 778 Context: Unknown File: src-tauri/src/player/mpv_backend.rs Line: 788 Context: Unknown File: src-tauri/src/player/mpv_backend.rs Line: 833 Context: Unknown File: src-tauri/src/player/android/mod.rs Line: 579 Context: Unknown File: src-tauri/src/player/android/mod.rs Line: 617 Context: Unknown File: src-tauri/src/settings.rs Line: 1 Context: Unknown File: src-tauri/src/settings.rs Line: 383 Context: Unknown File: src-tauri/src/settings.rs Line: 400 Context: Unknown File: src-tauri/src/settings.rs Line: 424 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-033","id":"462","title":"UR-033"},"463":{"body":"Locations: 1 file(s) File: src-tauri/src/commands/download/smart_cache.rs Line: 3 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-045","id":"463","title":"UR-045"},"464":{"body":"Locations: 1 file(s) File: src-tauri/src/commands/catalog.rs Line: 878 Context: Unknown","breadcrumbs":"Traceability Matrix » IT-017","id":"464","title":"IT-017"},"465":{"body":"Locations: 2 file(s) File: src-tauri/src/storage/mod.rs Line: 157 Context: pub fn file_size(&self) -> Option { File: src-tauri/src/storage/db_service.rs Line: 311 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-014","id":"465","title":"UT-014"},"466":{"body":"Locations: 2 file(s) File: src-tauri/src/storage/mod.rs Line: 157 Context: pub fn file_size(&self) -> Option { File: src-tauri/src/storage/db_service.rs Line: 311 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-015","id":"466","title":"UT-015"},"467":{"body":"Locations: 2 file(s) File: src-tauri/src/storage/mod.rs Line: 157 Context: pub fn file_size(&self) -> Option { File: src-tauri/src/storage/db_service.rs Line: 311 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-016","id":"467","title":"UT-016"},"468":{"body":"Locations: 2 file(s) File: src-tauri/src/storage/mod.rs Line: 157 Context: pub fn file_size(&self) -> Option { File: src-tauri/src/storage/db_service.rs Line: 311 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-017","id":"468","title":"UT-017"},"469":{"body":"Locations: 2 file(s) File: src-tauri/src/storage/mod.rs Line: 157 Context: pub fn file_size(&self) -> Option { File: src-tauri/src/storage/db_service.rs Line: 311 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-018","id":"469","title":"UT-018"},"47":{"body":"Locations: 1 file(s) File: src-tauri/src/commands/player/remote.rs Line: 3 Context: Unknown","breadcrumbs":"Traceability Matrix » IR-028","id":"47","title":"IR-028"},"470":{"body":"Locations: 2 file(s) File: src-tauri/src/storage/mod.rs Line: 157 Context: pub fn file_size(&self) -> Option { File: src-tauri/src/storage/db_service.rs Line: 311 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-019","id":"470","title":"UT-019"},"471":{"body":"Locations: 2 file(s) File: src-tauri/src/storage/mod.rs Line: 157 Context: pub fn file_size(&self) -> Option { File: src-tauri/src/storage/db_service.rs Line: 311 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-020","id":"471","title":"UT-020"},"472":{"body":"Locations: 2 file(s) File: src-tauri/src/storage/mod.rs Line: 157 Context: pub fn file_size(&self) -> Option { File: src-tauri/src/storage/db_service.rs Line: 311 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-021","id":"472","title":"UT-021"},"473":{"body":"Locations: 2 file(s) File: src-tauri/src/storage/mod.rs Line: 157 Context: pub fn file_size(&self) -> Option { File: src-tauri/src/storage/db_service.rs Line: 311 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-022","id":"473","title":"UT-022"},"474":{"body":"Locations: 2 file(s) File: src-tauri/src/storage/mod.rs Line: 157 Context: pub fn file_size(&self) -> Option { File: src-tauri/src/storage/db_service.rs Line: 311 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-023","id":"474","title":"UT-023"},"475":{"body":"Locations: 2 file(s) File: src-tauri/src/storage/mod.rs Line: 157 Context: pub fn file_size(&self) -> Option { File: src-tauri/src/storage/db_service.rs Line: 311 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-025","id":"475","title":"UT-025"},"476":{"body":"Locations: 1 file(s) File: src-tauri/src/commands/download/mod.rs Line: 2527 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-042","id":"476","title":"UT-042"},"477":{"body":"Locations: 1 file(s) File: src-tauri/src/commands/download/mod.rs Line: 2527 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-043","id":"477","title":"UT-043"},"478":{"body":"Locations: 1 file(s) File: src-tauri/src/commands/offline.rs Line: 136 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-044","id":"478","title":"UT-044"},"479":{"body":"Locations: 1 file(s) File: src-tauri/src/player/mpv_backend.rs Line: 747 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-083","id":"479","title":"UT-083"},"48":{"body":"Locations: 2 file(s) File: src-tauri/src/commands/catalog.rs Line: 327 Context: Unknown File: src-tauri/src/lib.rs Line: 1564 Context: Unknown","breadcrumbs":"Traceability Matrix » IR-030","id":"48","title":"IR-030"},"480":{"body":"Locations: 1 file(s) File: src-tauri/src/player/mpv_backend.rs Line: 759 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-084","id":"480","title":"UT-084"},"481":{"body":"Locations: 6 file(s) File: src-tauri/src/commands/favorites.rs Line: 12 Context: Unknown File: src-tauri/src/commands/favorites.rs Line: 78 Context: Unknown File: src-tauri/src/commands/favorites.rs Line: 286 Context: Unknown File: src-tauri/src/commands/favorites.rs Line: 320 Context: Unknown File: src-tauri/src/commands/favorites.rs Line: 340 Context: Unknown File: src-tauri/src/commands/favorites.rs Line: 356 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-103","id":"481","title":"UT-103"},"482":{"body":"Locations: 2 file(s) File: src-tauri/src/commands/catalog.rs Line: 281 Context: Unknown File: src-tauri/src/commands/catalog.rs Line: 764 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-115","id":"482","title":"UT-115"},"483":{"body":"Locations: 2 file(s) File: src-tauri/src/player/mpv_backend.rs Line: 564 Context: Unknown File: src-tauri/src/player/mpv_backend.rs Line: 573 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-121","id":"483","title":"UT-121"},"484":{"body":"Locations: 11 file(s) File: src-tauri/src/commands/sync_drain.rs Line: 14 Context: Unknown File: src-tauri/src/commands/sync_drain.rs Line: 76 Context: Unknown File: src-tauri/src/commands/sync_drain.rs Line: 236 Context: Unknown File: src-tauri/src/commands/sync_drain.rs Line: 606 Context: Unknown File: src-tauri/src/commands/sync_drain.rs Line: 672 Context: Unknown File: src-tauri/src/commands/sync_drain.rs Line: 702 Context: Unknown File: src-tauri/src/commands/sync_drain.rs Line: 736 Context: Unknown File: src-tauri/src/commands/sync_drain.rs Line: 765 Context: Unknown File: src-tauri/src/commands/sync_drain.rs Line: 811 Context: Unknown File: src-tauri/src/commands/sync_drain.rs Line: 992 Context: Unknown File: src-tauri/src/commands/sync_drain.rs Line: 1007 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-122","id":"484","title":"UT-122"},"485":{"body":"Locations: 3 file(s) File: src-tauri/src/commands/catalog.rs Line: 1001 Context: Unknown File: src-tauri/src/commands/catalog.rs Line: 1039 Context: Unknown File: src-tauri/src/commands/catalog.rs Line: 1062 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-125","id":"485","title":"UT-125"},"486":{"body":"Locations: 2 file(s) File: src-tauri/src/commands/catalog.rs Line: 490 Context: Unknown File: src-tauri/src/commands/catalog.rs Line: 1090 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-126","id":"486","title":"UT-126"},"487":{"body":"Locations: 11 file(s) File: src-tauri/src/media_server.rs Line: 27 Context: Unknown File: src-tauri/src/media_server.rs Line: 282 Context: Unknown File: src-tauri/src/media_server.rs Line: 327 Context: Unknown File: src-tauri/src/media_server.rs Line: 398 Context: Unknown File: src-tauri/src/media_server.rs Line: 449 Context: Unknown File: src-tauri/src/media_server.rs Line: 458 Context: Unknown File: src-tauri/src/media_server.rs Line: 473 Context: Unknown File: src-tauri/src/media_server.rs Line: 508 Context: Unknown File: src-tauri/src/media_server.rs Line: 515 Context: Unknown File: src-tauri/src/media_server.rs Line: 540 Context: Unknown File: src-tauri/src/media_server.rs Line: 567 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-127","id":"487","title":"UT-127"},"488":{"body":"Locations: 2 file(s) File: src-tauri/src/repository/device_profile.rs Line: 29 Context: Unknown File: src-tauri/src/repository/device_profile.rs Line: 40 Context: pub fn clamp_max_audio_channels(reported: Option) -> u32 {","breadcrumbs":"Traceability Matrix » UT-141","id":"488","title":"UT-141"},"489":{"body":"Locations: 1 file(s) File: src-tauri/src/repository/device_profile.rs Line: 213 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-142","id":"489","title":"UT-142"},"49":{"body":"Locations: 2 file(s) File: src/lib/utils/safeArea.test.ts Line: 17 Context: Unknown File: src/lib/utils/safeArea.ts Line: 4 Context: Unknown","breadcrumbs":"Traceability Matrix » IR-031","id":"49","title":"IR-031"},"490":{"body":"Locations: 3 file(s) File: src-tauri/src/repository/device_profile.rs Line: 238 Context: Unknown File: src-tauri/src/repository/device_profile.rs Line: 260 Context: Unknown File: src-tauri/src/repository/device_profile.rs Line: 277 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-148","id":"490","title":"UT-148"},"491":{"body":"Locations: 1 file(s) File: scripts/set-version.test.ts Line: 4 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-150","id":"491","title":"UT-150"},"492":{"body":"Locations: 4 file(s) File: src-tauri/src/repository/hybrid.rs Line: 635 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 688 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 4692 Context: Unknown File: src-tauri/src/repository/offline.rs Line: 4778 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-152","id":"492","title":"UT-152"},"493":{"body":"Locations: 2 file(s) File: src-tauri/src/commands/sync_drain.rs Line: 1047 Context: Unknown File: src-tauri/src/commands/sync_drain.rs Line: 1064 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-154","id":"493","title":"UT-154"},"494":{"body":"Locations: 3 file(s) File: src-tauri/src/player/mod.rs Line: 873 Context: Unknown File: src-tauri/src/player/mod.rs Line: 4077 Context: Unknown File: src-tauri/src/player/mod.rs Line: 4120 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-155","id":"494","title":"UT-155"},"495":{"body":"Locations: 4 file(s) File: src-tauri/src/repository/online.rs Line: 527 Context: Unknown File: src-tauri/src/repository/online.rs Line: 2842 Context: Unknown File: src-tauri/src/repository/online.rs Line: 2864 Context: Unknown File: src-tauri/src/repository/online.rs Line: 2887 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-156","id":"495","title":"UT-156"},"496":{"body":"Locations: 3 file(s) File: src-tauri/src/settings.rs Line: 653 Context: Unknown File: src-tauri/src/settings.rs Line: 689 Context: Unknown File: src-tauri/src/settings.rs Line: 716 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-157","id":"496","title":"UT-157"},"497":{"body":"Locations: 4 file(s) File: src-tauri/src/repository/types.rs Line: 404 Context: Unknown File: src-tauri/src/repository/types.rs Line: 722 Context: Unknown File: src-tauri/src/repository/types.rs Line: 743 Context: Unknown File: src-tauri/src/repository/types.rs Line: 755 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-161","id":"497","title":"UT-161"},"498":{"body":"Locations: 1 file(s) File: src-tauri/src/repository/offline.rs Line: 3767 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-162","id":"498","title":"UT-162"},"499":{"body":"Locations: 1 file(s) File: src-tauri/src/download/worker.rs Line: 336 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-163","id":"499","title":"UT-163"},"5":{"body":"API endpoints and data contracts required for Jellyfin integration. ID Requirement Endpoint Category Traces To Status JA-001 Server connection and discovery System UR-009 Done JA-002 User authentication (username/password) Users UR-009, UR-012 Done JA-003 Get user library views UserViews UR-007 Done JA-004 Get library items (paginated) Items UR-007 Done JA-005 Get item details and metadata Items UR-007 Done JA-006 Search across libraries Items UR-008 Done JA-007 Get playback info and stream URL MediaInfo UR-003, UR-004 Done JA-008 Get available subtitles for item MediaInfo UR-020 Done JA-009 Get available audio tracks for item MediaInfo UR-021 Done JA-010 Report playback start Sessions UR-025 Done JA-011 Report playback progress (periodic) Sessions UR-025 Done JA-012 Report playback stopped Sessions UR-025 Done JA-013 Get resume position for item UserData UR-019 Done JA-014 Get \"Next Up\" items Shows UR-023 Done JA-015 Get \"Continue Watching\" items Items UR-023 Done JA-016 Get recently added items Items UR-024 Done JA-017 Mark item as favorite UserData UR-017 Done JA-018 Remove item from favorites UserData UR-017 Done JA-019 Get/create/update playlists Playlists UR-014 Done JA-020 Add/remove items from playlist Playlists UR-014 Done JA-021 Get active sessions list Sessions UR-010 Done JA-022 Send playback commands to remote session (play/pause/stop) Sessions UR-010 Done JA-023 Send seek command to remote session Sessions UR-010 Done JA-024 Send next/previous track commands to remote session Sessions UR-010 Done JA-025 Play specific item on remote session Sessions UR-010 Done JA-026 Send volume/mute commands to remote session Sessions UR-010 Done JA-027 Get transcoding options MediaInfo UR-022 Planned JA-028 Get image/artwork URLs Images UR-007 Done JA-029 Get cast/crew for item (actors, directors) Items UR-035 Done JA-030 Get person details and filmography Persons UR-036 Done JA-031 Get items by person (actor/director filmography) Items UR-036 Done JA-032 Get audio-only stream URL for a video item (selected audio-stream index) MediaInfo UR-040 Done JA-033 Query favourite items (Filters=IsFavorite, recursive, scoped by item type) Items UR-067 Done JA-034 Read UserData (favourite, played, resume position) from item responses UserData UR-069 Done JA-035 Mark item played (POST /Users/{userId}/PlayedItems/{itemId}) UserData UR-025 Done JA-036 Query next-up episodes excluding in-progress ones (/Shows/NextUp with EnableResumable=false) Shows UR-059 Done","breadcrumbs":"Requirements Specification » 2.2 Jellyfin API Requirements","id":"5","title":"2.2 Jellyfin API Requirements"},"50":{"body":"Locations: 17 file(s) File: src/lib/api/bindings.ts Line: 2821 Context: Unknown File: src/lib/api/bindings.ts Line: 2878 Context: Unknown File: src/lib/player/adapters/rustReportHost.ts Line: 13 Context: Unknown File: src/lib/player/adapters/html5Adapter.ts Line: 19 Context: Unknown File: src/lib/player/adapters/types.ts Line: 15 Context: Unknown File: src/lib/player/index.ts Line: 15 Context: Unknown File: src/lib/services/playerEvents.regression.test.ts Line: 9 Context: Unknown File: src/lib/services/playerEvents.ts Line: 8 Context: Unknown File: src/lib/services/playerEvents.ts Line: 189 Context: Unknown File: src/lib/services/playerEvents.test.ts Line: 4 Context: Unknown File: src/lib/stores/player.ts Line: 8 Context: Unknown File: src/lib/stores/player.ts Line: 31 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 1 Context: Unknown File: src-tauri/src/player/events.rs Line: 6 Context: Unknown File: src-tauri/src/player/events.rs Line: 23 Context: Unknown File: src-tauri/src/player/state.rs Line: 7 Context: Unknown File: src-tauri/src/player/state.rs Line: 25 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-001","id":"50","title":"DR-001"},"500":{"body":"Locations: 1 file(s) File: src-tauri/src/download/worker.rs Line: 313 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-164","id":"500","title":"UT-164"},"501":{"body":"Locations: 7 file(s) File: src-tauri/src/repository/device_profile.rs Line: 277 Context: Unknown File: src-tauri/src/repository/device_profile.rs Line: 457 Context: Unknown File: src-tauri/src/repository/mod.rs Line: 347 Context: Unknown File: src-tauri/src/repository/online.rs Line: 2216 Context: Unknown File: src-tauri/src/repository/online.rs Line: 3402 Context: Unknown File: src-tauri/src/repository/online.rs Line: 3434 Context: Unknown File: src-tauri/src/repository/online.rs Line: 3460 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-166","id":"501","title":"UT-166"},"502":{"body":"Locations: 2 file(s) File: src-tauri/src/commands/catalog.rs Line: 916 Context: Unknown File: src-tauri/src/commands/catalog.rs Line: 957 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-171","id":"502","title":"UT-171"},"503":{"body":"Locations: 3 file(s) File: src-tauri/src/commands/download/mod.rs Line: 524 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 3367 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 3396 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-172","id":"503","title":"UT-172"},"504":{"body":"Locations: 5 file(s) File: src-tauri/src/repository/online.rs Line: 73 Context: Unknown File: src-tauri/src/repository/online.rs Line: 89 Context: Unknown File: src-tauri/src/repository/online.rs Line: 527 Context: Unknown File: src-tauri/src/repository/online.rs Line: 3014 Context: Unknown File: src-tauri/src/repository/online.rs Line: 3110 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-173","id":"504","title":"UT-173"},"505":{"body":"Locations: 3 file(s) File: src-tauri/src/player/mod.rs Line: 945 Context: Unknown File: src-tauri/src/player/mod.rs Line: 4157 Context: Unknown File: src-tauri/src/player/mod.rs Line: 4176 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-176","id":"505","title":"UT-176"},"506":{"body":"Locations: 6 file(s) File: src-tauri/src/player/mod.rs Line: 945 Context: Unknown File: src-tauri/src/player/mod.rs Line: 960 Context: Unknown File: src-tauri/src/player/mod.rs Line: 971 Context: pub fn observed_duration(&self) -> Option { File: src-tauri/src/player/mod.rs Line: 4193 Context: Unknown File: src-tauri/src/player/mod.rs Line: 4208 Context: Unknown File: src-tauri/src/player/mod.rs Line: 4222 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-177","id":"506","title":"UT-177"},"507":{"body":"Locations: 3 file(s) File: src-tauri/src/player/mod.rs Line: 764 Context: Unknown File: src-tauri/src/player/mod.rs Line: 4244 Context: Unknown File: src-tauri/src/player/mod.rs Line: 4271 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-178","id":"507","title":"UT-178"},"508":{"body":"Locations: 4 file(s) File: src-tauri/src/player/mod.rs Line: 120 Context: Unknown File: src-tauri/src/player/mod.rs Line: 1551 Context: Unknown File: src-tauri/src/player/mod.rs Line: 4309 Context: Unknown File: src-tauri/src/player/mod.rs Line: 4337 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-179","id":"508","title":"UT-179"},"509":{"body":"Locations: 3 file(s) File: src-tauri/src/player/mod.rs Line: 1307 Context: Unknown File: src-tauri/src/player/mod.rs Line: 4366 Context: Unknown File: src-tauri/src/player/mod.rs Line: 4384 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-180","id":"509","title":"UT-180"},"51":{"body":"Locations: 2 file(s) File: src/lib/api/bindings.ts Line: 2699 Context: export type PlaybackProgress = { itemId: string; File: src-tauri/src/player/media.rs Line: 60 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-002","id":"51","title":"DR-002"},"510":{"body":"Locations: 2 file(s) File: src-tauri/src/repository/online.rs Line: 910 Context: Unknown File: src-tauri/src/repository/online.rs Line: 3688 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-190","id":"510","title":"UT-190"},"511":{"body":"Locations: 2 file(s) File: src-tauri/src/repository/online.rs Line: 910 Context: Unknown File: src-tauri/src/repository/online.rs Line: 3709 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-191","id":"511","title":"UT-191"},"512":{"body":"Locations: 1 file(s) File: scripts/tauri-security-config.test.ts Line: 11 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-193","id":"512","title":"UT-193"},"513":{"body":"Locations: 1 file(s) File: src-tauri/src/player/seek.rs Line: 135 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-194","id":"513","title":"UT-194"},"514":{"body":"Locations: 1 file(s) File: src-tauri/src/player/seek.rs Line: 152 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-195","id":"514","title":"UT-195"},"515":{"body":"Locations: 1 file(s) File: src-tauri/src/player/seek.rs Line: 168 Context: / which backends reject (the \"Raw(-10)\" class of error).","breadcrumbs":"Traceability Matrix » UT-196","id":"515","title":"UT-196"},"516":{"body":"Locations: 1 file(s) File: src-tauri/src/player/seek.rs Line: 181 Context: / this function exists to prevent.","breadcrumbs":"Traceability Matrix » UT-197","id":"516","title":"UT-197"},"517":{"body":"Locations: 1 file(s) File: src-tauri/src/player/seek.rs Line: 192 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-198","id":"517","title":"UT-198"},"518":{"body":"Locations: 2 file(s) File: src-tauri/src/player/stream_end.rs Line: 54 Context: Unknown File: src-tauri/src/player/stream_end.rs Line: 91 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-200","id":"518","title":"UT-200"},"519":{"body":"Locations: 2 file(s) File: scripts/extract-traces.ts Line: 444 Context: Unknown File: scripts/extract-traces.ts Line: 454 Context: export const MATRIX_LINK_PREFIX = \"../\";","breadcrumbs":"Traceability Matrix » UT-202","id":"519","title":"UT-202"},"52":{"body":"Locations: 2 file(s) File: src/lib/api/bindings.ts Line: 2803 Context: Unknown File: src-tauri/src/player/media.rs Line: 142 Context: pub enum MediaType {","breadcrumbs":"Traceability Matrix » DR-003","id":"52","title":"DR-003"},"520":{"body":"Locations: 10 file(s) File: src-tauri/src/commands/library.rs Line: 274 Context: Unknown File: src-tauri/src/commands/library.rs Line: 295 Context: Unknown File: src-tauri/src/commands/library.rs Line: 307 Context: Unknown File: src-tauri/src/repository/exclusions.rs Line: 191 Context: Unknown File: src-tauri/src/repository/exclusions.rs Line: 203 Context: Unknown File: src-tauri/src/repository/exclusions.rs Line: 232 Context: Unknown File: src-tauri/src/repository/exclusions.rs Line: 275 Context: Unknown File: src-tauri/src/repository/exclusions.rs Line: 293 Context: Unknown File: src-tauri/src/repository/exclusions.rs Line: 316 Context: Unknown File: src-tauri/src/repository/exclusions.rs Line: 334 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-203","id":"520","title":"UT-203"},"521":{"body":"Locations: 10 file(s) File: src-tauri/src/commands/download/mod.rs Line: 138 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 156 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 186 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 329 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 1374 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 1514 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 1867 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 2560 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 2589 Context: Unknown File: src-tauri/src/commands/download/mod.rs Line: 2616 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-205","id":"521","title":"UT-205"},"522":{"body":"Locations: 1 file(s) File: src-tauri/src/commands/playlist.rs Line: 4 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-014","id":"522","title":"UR-014"},"523":{"body":"Locations: 1 file(s) File: src-tauri/src/commands/download/pinning.rs Line: 3 Context: Unknown","breadcrumbs":"Traceability Matrix » UR-044","id":"523","title":"UR-044"},"524":{"body":"Locations: 8 file(s) File: src-tauri/src/thumbnail/cache.rs Line: 39 Context: Unknown File: src-tauri/src/thumbnail/cache.rs Line: 74 Context: Unknown File: src-tauri/src/thumbnail/cache.rs Line: 200 Context: Unknown File: src-tauri/src/thumbnail/cache.rs Line: 628 Context: Unknown File: src-tauri/src/thumbnail/cache.rs Line: 668 Context: Unknown File: src-tauri/src/thumbnail/cache.rs Line: 706 Context: Unknown File: src-tauri/src/thumbnail/cache.rs Line: 735 Context: Unknown File: src-tauri/src/thumbnail/cache.rs Line: 766 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-204","id":"524","title":"UT-204"},"525":{"body":"Locations: 1 file(s) File: scripts/release-notes.test.ts Line: 4 Context: Unknown","breadcrumbs":"Traceability Matrix » UT-210","id":"525","title":"UT-210"},"526":{"body":"This document explains the automated requirement traceability validation system for JellyTau.","breadcrumbs":"Traceability CI » Requirement Traceability CI/CD Pipeline","id":"526","title":"Requirement Traceability CI/CD Pipeline"},"527":{"body":"The CI/CD pipeline automatically validates that code changes are properly traced to requirements. This ensures: ✅ Requirements are implemented with clear traceability ✅ No requirement coverage regressions ✅ Code changes are linked to specific requirements ✅ Quality metrics are tracked over time","breadcrumbs":"Traceability CI » Overview","id":"527","title":"Overview"},"528":{"body":"Traceability validation lives in .gitea/workflows/traceability-check.yml: ✅ Automatic trace extraction ✅ Coverage validation against minimum threshold (88%, ratcheted) ✅ Modified file checking ✅ Artifact preservation ✅ Summary reports Runs on: Every push and pull request to master/main/develop A second workflow, traceability.yml, previously duplicated this one as a \"GitHub-compatible alternative\". It was removed: CI here is Gitea Actions, and its only unique step (PR comments via actions/github-script) depended on the GitHub REST client, which Gitea does not provide. To add PR comments, post to Gitea's /api/v1/repos/{owner}/{repo}/issues/{index}/comments from traceability-check.yml rather than reviving the old file.","breadcrumbs":"Traceability CI » Gitea Actions Workflows","id":"528","title":"Gitea Actions Workflows"},"529":{"body":"","breadcrumbs":"Traceability CI » What Gets Validated","id":"529","title":"What Gets Validated"},"53":{"body":"Locations: 16 file(s) File: src/lib/api/bindings.ts Line: 192 Context: Unknown File: src/lib/api/bindings.ts Line: 2663 Context: Unknown File: src/lib/components/player/VideoPlayer.svelte Line: 970 Context: Unknown File: src/lib/player/adapters/webviewAudioAdapter.ts Line: 18 Context: Unknown File: src/lib/player/adapters/index.ts Line: 20 Context: Unknown File: src/lib/player/adapters/nativeAdapter.ts Line: 25 Context: Unknown File: src/lib/player/adapters/nativeAdapter.ts Line: 58 Context: Unknown File: src/lib/services/playbackCapabilities.ts Line: 4 Context: Unknown File: src/lib/services/webviewAudio.ts Line: 18 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 1970 Context: Unknown File: src-tauri/src/commands/player/mod.rs Line: 1987 Context: Unknown File: src-tauri/src/player/backend.rs Line: 39 Context: pub fn playback_failed>(message: S) -> Self { File: src-tauri/src/player/backend.rs Line: 251 Context: Unknown File: src-tauri/src/player/backend.rs Line: 257 Context: Unknown File: src-tauri/src/player/webview_audio_backend.rs Line: 25 Context: Unknown File: src-tauri/src/player/webview_audio_backend.rs Line: 184 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-004","id":"53","title":"DR-004"},"530":{"body":"bun run traces:json > traces-report.json Extracts all TRACES comments from: TypeScript files (src/**/*.ts) Svelte components (src/**/*.svelte) Rust code (src-tauri/src/**/*.rs) Test files","breadcrumbs":"Traceability CI » 1. Trace Extraction","id":"530","title":"1. Trace Extraction"},"531":{"body":"The workflow checks: Minimum overall coverage: 88% (MIN_THRESHOLD) Denominators are derived from docs/requirements.md at run time — they are never hardcoded here or in the workflow. Run bun run traces:coverage for the current per-type breakdown; any number written into this document is a snapshot that will drift. Why this matters. The workflow used to divide by frozen literals (UR/39, IR/24, DR/48, JA/3, total 114) while requirements.md had grown past 200. It reported 158% coverage, so the 50% threshold was unreachable and the job could not fail regardless of how far coverage dropped. See The fix derives the denominators from requirements.md at run time. Coverage is the intersection of traced and defined IDs: an ID that appears in a TRACES: comment but is not defined in requirements.md is reported as orphaned and does not count toward coverage. UT/IT test identifiers are a separate taxonomy and are excluded entirely. The workflow fails and blocks merge if coverage drops below the threshold — or if it computes above 100%, which can only mean the gate is miscounting. Ratchet policy MIN_THRESHOLD only ever goes up. It is deliberately set a few points below the coverage actually achieved (88 against a real ~90%), so a genuine regression trips it. It previously sat at 50 while true coverage was 86%: nearly half the matrix could have rotted before CI objected. It was ratcheted 50 → 82 when that was found, and 82 → 88 once coverage had held above 88% for several releases. When coverage rises durably, raise the threshold to just under the new figure. Never lower it to make a red build pass — add the missing TRACES comments instead. The same number lives in MIN_COVERAGE_PERCENT in scripts/extract-traces.ts (so bun run traces:coverage gates locally on the same bar); scripts/extract-traces.test.ts fails if the two drift apart.","breadcrumbs":"Traceability CI » 2. Coverage Thresholds","id":"531","title":"2. Coverage Thresholds"},"532":{"body":"bun run traces:validate Every ID named by a TRACES: comment must be defined as a table row in docs/requirements.md. The extractor used to accept any well-formed ID silently, so a typo or a rename that missed a call site passed unnoticed — DR-189 and UT-188 were referenced from three source files, defined nowhere, for months. This check spans all six ID types (UR/IR/DR/JA/UT/IT), unlike the coverage orphaned list above, which considers only the four requirement types so that UT/IT noise cannot bury a real typo in the ratio's reporting. The workflow step fails the build on any dangling ID and prints each offender with the files that reference it.","breadcrumbs":"Traceability CI » 2b. Dangling requirement IDs","id":"532","title":"2b. Dangling requirement IDs"},"533":{"body":"On pull requests, the workflow: Detects all changed TypeScript/Svelte/Rust files Warns if new/modified files lack TRACES comments Suggests the TRACES format for missing comments","breadcrumbs":"Traceability CI » 3. Modified File Checking","id":"533","title":"3. Modified File Checking"},"534":{"body":"When you add new code or modify existing code, include TRACES comments:","breadcrumbs":"Traceability CI » How to Add Traces to New Code","id":"534","title":"How to Add Traces to New Code"},"535":{"body":"// TRACES: UR-005, UR-026 | DR-029\nexport function handlePlayback() { // Implementation...\n}","breadcrumbs":"Traceability CI » TypeScript/Svelte Example","id":"535","title":"TypeScript/Svelte Example"},"536":{"body":"/// TRACES: UR-005 | DR-001\npub fn player_state_changed(state: PlayerState) { // Implementation...\n}","breadcrumbs":"Traceability CI » Rust Example","id":"536","title":"Rust Example"},"537":{"body":"// TRACES: UR-005 | DR-001 | UT-026, UT-027\n#[cfg(test)]\nmod tests { // Tests...\n}","breadcrumbs":"Traceability CI » Test Example","id":"537","title":"Test Example"},"538":{"body":"TRACES: [UR-###, ...] | [IR-###, ...] | [DR-###, ...] | [JA-###, ...] UR-### - User Requirements (features users see) IR-### - Integration Requirements (API/platform integration) DR-### - Development Requirements (internal architecture) JA-### - Jellyfin API Requirements (Jellyfin API usage) Examples: // TRACES: UR-005 - Single requirement // TRACES: UR-005, UR-026 - Multiple of same type // TRACES: UR-005 | DR-029 - Multiple types // TRACES: UR-005, UR-026 | DR-001, DR-029 | UT-001 - Complex","breadcrumbs":"Traceability CI » TRACES Format","id":"538","title":"TRACES Format"},"539":{"body":"","breadcrumbs":"Traceability CI » Workflow Behavior","id":"539","title":"Workflow Behavior"},"54":{"body":"Locations: 13 file(s) File: src/lib/api/bindings.ts Line: 3075 Context: export type RemoteSessionStatus = { position: number; duration: number... File: src/lib/stores/appState.ts Line: 2 Context: Unknown File: src/lib/stores/queue.ts Line: 7 Context: Unknown File: src/lib/stores/queue.ts Line: 108 Context: Unknown File: src/lib/stores/queue.ts Line: 113 Context: async function next() { File: src/lib/stores/queue.ts Line: 118 Context: async function previous() { File: src/lib/stores/queue.ts Line: 123 Context: async function skipTo(index: number) { File: src/lib/stores/queue.ts Line: 128 Context: async function toggleShuffle() { File: src/lib/stores/queue.test.ts Line: 4 Context: Unknown File: src-tauri/src/commands/player/queue.rs Line: 3 Context: Unknown File: src-tauri/src/player/queue.rs Line: 8 Context: Unknown File: src-tauri/src/player/queue.rs Line: 20 Context: pub enum RepeatMode { File: src-tauri/src/player/queue.rs Line: 535 Context: pub enum AddPosition {","breadcrumbs":"Traceability Matrix » DR-005","id":"54","title":"DR-005"},"540":{"body":"✅ Extracts all traces from code ✅ Validates coverage is >= 88% ✅ Generates full traceability report ✅ Saves report as artifact","breadcrumbs":"Traceability CI » On Push to Main Branch","id":"540","title":"On Push to Main Branch"},"541":{"body":"✅ Extracts all traces ✅ Validates coverage >= 88% ✅ Checks modified files for TRACES ✅ Warns if new code lacks TRACES ✅ Suggests proper format ✅ Generates report artifact","breadcrumbs":"Traceability CI » On Pull Request","id":"541","title":"On Pull Request"},"542":{"body":"The workflow fails (blocks merge) if: Coverage drops below 88% A TRACES: comment names an ID docs/requirements.md does not define JSON extraction fails Invalid trace format The workflow warns (but doesn't block) if: New files lack TRACES comments Coverage drops (but still above threshold)","breadcrumbs":"Traceability CI » Failure Scenarios","id":"542","title":"Failure Scenarios"},"543":{"body":"","breadcrumbs":"Traceability CI » Viewing Reports","id":"543","title":"Viewing Reports"},"544":{"body":"Go to Actions tab Click the Traceability Validation workflow run Download traceability-reports artifact View: traces-report.json - Raw trace data docs/traceability.md - Formatted report","breadcrumbs":"Traceability CI » In Gitea Actions UI","id":"544","title":"In Gitea Actions UI"},"545":{"body":"# Extract current traces\nbun run traces:json | jq '.byType' # Generate full report\nbun run traces:markdown\ncat docs/traceability.md","breadcrumbs":"Traceability CI » Locally","id":"545","title":"Locally"},"546":{"body":"","breadcrumbs":"Traceability CI » Coverage Goals","id":"546","title":"Coverage Goals"},"547":{"body":"Run bun run traces:coverage — it prints the live figure and exits non-zero below threshold. Numbers are deliberately not pinned here; the previous snapshot in this section (51%, 56/114) was stale by roughly 100 requirements and was what made the broken CI arithmetic look plausible for so long. As of August 2026 overall coverage is ~90%.","breadcrumbs":"Traceability CI » Current Status","id":"547","title":"Current Status"},"548":{"body":"Short term (Sprint): Maintain ≥88% overall (the current ratchet) Medium term (Month): Hold above 90% and ratchet the gate to match Long term (Release): Reach 95% coverage with focus on: IR requirements (API clients) JA requirements (Jellyfin API endpoints) Remaining UR/DR requirements","breadcrumbs":"Traceability CI » Targets","id":"548","title":"Targets"},"549":{"body":"","breadcrumbs":"Traceability CI » Improving Coverage","id":"549","title":"Improving Coverage"},"55":{"body":"Locations: 2 file(s) File: src/lib/services/preload.test.ts Line: 4 Context: Unknown File: src/lib/services/preload.ts Line: 5 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-006","id":"55","title":"DR-006"},"550":{"body":"Review README.md for unimplemented features Add TRACES to code that implements them Focus on high-priority features (High/Medium priority)","breadcrumbs":"Traceability CI » For Missing User Requirements (UR)","id":"550","title":"For Missing User Requirements (UR)"},"551":{"body":"Add TRACES to Jellyfin API client methods Add TRACES to platform-specific backends (Android/Linux) Link to corresponding Jellyfin API endpoints","breadcrumbs":"Traceability CI » For Missing Integration Requirements (IR)","id":"551","title":"For Missing Integration Requirements (IR)"},"552":{"body":"Add TRACES to UI components in src/lib/components/ Add TRACES to composables in src/lib/composables/ Add TRACES to player backend in src-tauri/src/player/","breadcrumbs":"Traceability CI » For Missing Development Requirements (DR)","id":"552","title":"For Missing Development Requirements (DR)"},"553":{"body":"Add TRACES to Jellyfin API wrapper methods Document which endpoints map to which requirements Link to Jellyfin API documentation","breadcrumbs":"Traceability CI » For Jellyfin API Requirements (JA)","id":"553","title":"For Jellyfin API Requirements (JA)"},"554":{"body":"When submitting a pull request: All new code has TRACES comments linking to requirements TRACES format is correct: // TRACES: UR-001 | DR-002 Workflow passes (coverage ≥ 88%) No coverage regressions Artifact traceability report was generated","breadcrumbs":"Traceability CI » Example PR Checklist","id":"554","title":"Example PR Checklist"},"555":{"body":"","breadcrumbs":"Traceability CI » Troubleshooting","id":"555","title":"Troubleshooting"},"556":{"body":"Problem: Workflow fails with coverage < 88% Solution: Run bun run traces:json locally Check which requirements are traced Add TRACES to untraced code sections Re-run extraction to verify","breadcrumbs":"Traceability CI » \"Coverage below minimum threshold\"","id":"556","title":"\"Coverage below minimum threshold\""},"557":{"body":"Problem: Workflow warns about new files lacking TRACES Solution: Add TRACES comments to all new code Format: // TRACES: UR-001 | DR-002 Map code to specific requirements from README.md Re-push","breadcrumbs":"Traceability CI » \"New files without TRACES\"","id":"557","title":"\"New files without TRACES\""},"558":{"body":"Problem: Trace extraction produces invalid JSON Solution: Check for malformed TRACES comments Run locally: bun run traces:json Look for parsing errors Fix and retry","breadcrumbs":"Traceability CI » \"Invalid JSON format\"","id":"558","title":"\"Invalid JSON format\""},"559":{"body":"","breadcrumbs":"Traceability CI » Integration with Development","id":"559","title":"Integration with Development"},"56":{"body":"Locations: 9 file(s) File: src/lib/components/library/GenericMediaListPage.svelte Line: 1 Context: Unknown File: src/lib/components/library/AlphabetScrollBar.svelte Line: 1 Context: Unknown File: src/lib/stores/music.ts Line: 3 Context: Unknown File: src/lib/stores/movies.ts Line: 3 Context: Unknown File: src/lib/stores/library.ts Line: 2 Context: Unknown File: src/lib/stores/tv.ts Line: 3 Context: Unknown File: src/routes/library/tv/+page.svelte Line: 8 Context: Unknown File: src/routes/library/movies/+page.svelte Line: 8 Context: Unknown File: src/routes/library/music/+page.svelte Line: 1 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-007","id":"56","title":"DR-007"},"560":{"body":"# Check your traces\nbun run traces:json | jq '.byType' # Regenerate report\nbun run traces:markdown # Verify traces syntax\ngrep \"TRACES:\" src/**/*.ts src/**/*.rs","breadcrumbs":"Traceability CI » Before Committing","id":"560","title":"Before Committing"},"561":{"body":"Add a file watcher to regenerate traces on save: { \"fileWatcher.watchPatterns\": [ \"src/**/*.ts\", \"src/**/*.svelte\", \"src-tauri/src/**/*.rs\" ], \"fileWatcher.command\": \"bun run traces:markdown\"\n}","breadcrumbs":"Traceability CI » In Your IDE","id":"561","title":"In Your IDE"},"562":{"body":"Add a pre-push hook to validate traces: #!/bin/bash\n# .git/hooks/pre-push\nbun run traces:json > /dev/null\nif [ $? -ne 0 ]; then echo \"❌ Invalid TRACES format\" exit 1\nfi","breadcrumbs":"Traceability CI » Git Hooks","id":"562","title":"Git Hooks"},"563":{"body":"Extract Traces Script Requirements Specification Traceability Matrix Gitea Actions Documentation","breadcrumbs":"Traceability CI » References","id":"563","title":"References"},"564":{"body":"For issues or questions: Check this document Review example traces in src/lib/stores/ Check existing TRACES comments for format Review workflow logs in Gitea Actions Last Updated: 2026-02-13","breadcrumbs":"Traceability CI » Support","id":"564","title":"Support"},"565":{"body":"","breadcrumbs":"Traces Quick Reference » TRACES Quick Reference Guide","id":"565","title":"TRACES Quick Reference Guide"},"566":{"body":"TRACES are requirement identifiers embedded in code comments to track which requirements are implemented where. Format: // TRACES: UR-001, UR-002 | DR-003","breadcrumbs":"Traces Quick Reference » What are TRACES?","id":"566","title":"What are TRACES?"},"567":{"body":"","breadcrumbs":"Traces Quick Reference » Quick Examples","id":"567","title":"Quick Examples"},"568":{"body":"// TRACES: UR-005, UR-026 | DR-029\nexport function handlePlayback() { } /** * Resume playback from saved position * TRACES: UR-019 | DR-022 */\nexport async function resumePlayback(itemId: string) { }","breadcrumbs":"Traces Quick Reference » TypeScript","id":"568","title":"TypeScript"},"569":{"body":"\n","breadcrumbs":"Traces Quick Reference » Svelte","id":"569","title":"Svelte"},"57":{"body":"Locations: 12 file(s) File: src/lib/components/BottomUi.svelte Line: 23 Context: Unknown File: src/lib/components/player/AudioPlayer.svelte Line: 1 Context: Unknown File: src/lib/components/player/Controls.svelte Line: 1 Context: Unknown File: src/lib/components/player/MiniPlayer.svelte Line: 1 Context: Unknown File: src/lib/player/index.ts Line: 15 Context: Unknown File: src/lib/utils/layoutShell.test.ts Line: 14 Context: Unknown File: src/lib/utils/layoutShell.ts Line: 17 Context: Unknown File: src/lib/stores/playerVisibility.test.ts Line: 9 Context: Unknown File: src/lib/stores/appState.ts Line: 2 Context: Unknown File: src/lib/stores/player.ts Line: 8 Context: Unknown File: src-tauri/src/commands/player/session.rs Line: 3 Context: Unknown File: src-tauri/src/commands/conversions.rs Line: 3 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-009","id":"57","title":"DR-009"},"570":{"body":"/// TRACES: UR-005 | DR-001\npub enum PlayerState { ... } #[test]\nfn test_queue_next() { // TRACES: UR-005 | DR-005 | UT-003\n}","breadcrumbs":"Traces Quick Reference » Rust","id":"570","title":"Rust"},"571":{"body":"Type Meaning Example UR User Requirement UR-005: Control media playback IR Integration Requirement IR-003: LibMPV integration DR Development Requirement DR-001: Player state machine JA Jellyfin API Requirement JA-007: Get playback info UT Unit Test UT-001: Player state transitions IT Integration Test IT-003: Audio playback via libmpv","breadcrumbs":"Traces Quick Reference » Requirement Types","id":"571","title":"Requirement Types"},"572":{"body":"User Requirements (UR): requirements.md Integration Requirements (IR): requirements.md Development Requirements (DR): requirements.md Jellyfin API (JA): requirements.md","breadcrumbs":"Traces Quick Reference » Where to Find Requirements","id":"572","title":"Where to Find Requirements"},"573":{"body":"","breadcrumbs":"Traces Quick Reference » How to Add TRACES","id":"573","title":"How to Add TRACES"},"574":{"body":"Look up the requirement in README.md or the traceability matrix. Example: UR-005: Control media playback (pause, play, skip, scrub)","breadcrumbs":"Traces Quick Reference » Step 1: Find the Requirement","id":"574","title":"Step 1: Find the Requirement"},"575":{"body":"Add TRACES comment at the top of the function/type/module: // TRACES: UR-005\nexport async function playMedia(itemId: string) { // Implementation\n}","breadcrumbs":"Traces Quick Reference » Step 2: Add Comment","id":"575","title":"Step 2: Add Comment"},"576":{"body":"Verify the trace is captured: bun run traces:json | jq '.requirements | keys | grep \"UR-005\"'","breadcrumbs":"Traces Quick Reference » Step 3: Run Extraction","id":"576","title":"Step 3: Run Extraction"},"577":{"body":"","breadcrumbs":"Traces Quick Reference » Common Patterns","id":"577","title":"Common Patterns"},"578":{"body":"// TRACES: UR-005\nfunction handlePlay() { }","breadcrumbs":"Traces Quick Reference » Single Requirement","id":"578","title":"Single Requirement"},"579":{"body":"// TRACES: UR-005, UR-026, UR-019\nfunction handlePlaybackState() { }","breadcrumbs":"Traces Quick Reference » Multiple Requirements, Same Type","id":"579","title":"Multiple Requirements, Same Type"},"58":{"body":"Locations: 1 file(s) File: src/lib/components/player/VideoPlayer.svelte Line: 1 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-010","id":"58","title":"DR-010"},"580":{"body":"// TRACES: UR-005, UR-026 | DR-029\nfunction autoplayNextEpisode() { }","breadcrumbs":"Traces Quick Reference » Multiple Types","id":"580","title":"Multiple Types"},"581":{"body":"// TRACES: UR-005 | UT-001\n#[test]\nfn test_player_state_transition() { }","breadcrumbs":"Traces Quick Reference » Test Coverage","id":"581","title":"Test Coverage"},"582":{"body":"/** * Player event handling * TRACES: UR-005, UR-019, UR-023 | DR-001, DR-028 */","breadcrumbs":"Traces Quick Reference » Modules/Files","id":"582","title":"Modules/Files"},"583":{"body":"","breadcrumbs":"Traces Quick Reference » Validation","id":"583","title":"Validation"},"584":{"body":"# View current coverage\nbun run traces:json | jq '.byType' # Generate full report\nbun run traces:markdown # Check specific requirement\nbun run traces:json | jq '.requirements.\"UR-005\"'","breadcrumbs":"Traces Quick Reference » Check Your Changes","id":"584","title":"Check Your Changes"},"585":{"body":"Ensure all new code has TRACES Format is correct: // TRACES: ... Requirements exist in docs/requirements.md — bun run traces:validate No typos in requirement IDs (same command catches them)","breadcrumbs":"Traces Quick Reference » Before Committing","id":"585","title":"Before Committing"},"586":{"body":"The workflow automatically checks: ✅ Coverage stays >= 88% (a ratchet — raise it, never lower it) ✅ Every traced ID is defined in docs/requirements.md ✅ New files have TRACES ✅ JSON format is valid ✅ Reports are generated See traceability-ci.md for details.","breadcrumbs":"Traces Quick Reference » CI/CD Validation","id":"586","title":"CI/CD Validation"},"587":{"body":"","breadcrumbs":"Traces Quick Reference » Tips & Tricks","id":"587","title":"Tips & Tricks"},"588":{"body":"# Find all code tracing to UR-005\nbun run traces:json | jq '.requirements.\"UR-005\"' # List all tests\nbun run traces:json | jq '.requirements | keys | map(select(startswith(\"UT\")))'","breadcrumbs":"Traces Quick Reference » Find Related Code","id":"588","title":"Find Related Code"},"589":{"body":"VS Code: { \"editor.wordBasedSuggestions\": false, \"editor.suggest.custom\": [ { \"name\": \"TRACES Format\", \"insertText\": \"// TRACES: $1\", \"insertTextRules\": \"InsertAsSnippet\" } ]\n}","breadcrumbs":"Traces Quick Reference » Update Your Editor","id":"589","title":"Update Your Editor"},"59":{"body":"Locations: 9 file(s) File: src/lib/api/bindings.ts Line: 658 Context: Unknown File: src/lib/api/bindings.ts Line: 674 Context: Unknown File: src/lib/services/deviceId.test.ts Line: 7 Context: Unknown File: src/lib/services/deviceId.ts Line: 8 Context: Unknown File: src/lib/services/deviceId.ts Line: 26 Context: Unknown File: src/lib/stores/library.ts Line: 2 Context: Unknown File: src-tauri/src/commands/device.rs Line: 4 Context: Unknown File: src-tauri/src/commands/device.rs Line: 24 Context: Unknown File: src-tauri/src/commands/device.rs Line: 81 Context: Unknown","breadcrumbs":"Traceability Matrix » DR-011","id":"59","title":"DR-011"},"590":{"body":"# Files modified without TRACES\ngit diff --name-only | xargs grep -L \"TRACES:\" | head -10","breadcrumbs":"Traces Quick Reference » Find Untraced Code","id":"590","title":"Find Untraced Code"},"591":{"body":"Q: Do I need TRACES on every function? A: Only for code that implements requirements. Internal helpers don't need TRACES. Q: Can I use TRACES on multiple related functions? A: Yes! Add at the file/module level or on individual functions. Q: What if code doesn't relate to any requirement? A: Leave it untraced. TRACES are for requirement-driven development. Q: How often should I regenerate reports? A: Automatically on push (CI/CD). Manually after changes: bun run traces:markdown Q: Can I trace to requirements that aren't implemented yet? A: Yes! TRACES show your implementation plan.","breadcrumbs":"Traces Quick Reference » FAQ","id":"591","title":"FAQ"},"592":{"body":"Full Traceability Matrix CI/CD Pipeline Guide Requirements Specification Extraction Script Quick Start: Add // TRACES: UR-XXX to new code Run bun run traces:markdown Check docs/traceability.md Submit PR - workflow validates automatically!","breadcrumbs":"Traces Quick Reference » See Also","id":"592","title":"See Also"},"593":{"body":"This document describes the current architecture of JellyTau, a cross-platform Jellyfin client built with Tauri, SvelteKit, and Rust. Last Updated: 2026-06-20","breadcrumbs":"Overview » JellyTau Software Architecture","id":"593","title":"JellyTau Software Architecture"},"594":{"body":"JellyTau uses a client-server architecture: business logic lives in a comprehensive Rust backend, while a UI-rich Svelte frontend handles presentation and interaction.","breadcrumbs":"Overview » Architecture Overview","id":"594","title":"Architecture Overview"},"595":{"body":"Business Logic in Rust : Core logic — playback, repository, sync, downloads, connectivity — lives in Rust for performance, reliability, and type safety. Presentation in Svelte : The frontend (~20.5k non-test lines) owns UI, layout, navigation, and interaction state and invokes Rust commands. It is intentionally UI-heavy, not a thin wrapper. Largest pieces: components + routes (~14.6k lines), stores (~3.4k), api/services/utils (~2.4k); VideoPlayer.svelte alone is ~1.6k lines. Events + Polling hybrid : Rust emits events the frontend listens to, and the UI also polls status on short intervals in a few hot spots (e.g. queue status in library/+layout.svelte, playback progress in VideoPlayer.svelte). Unified player boundary : UI components control playback only through the frontend facade src/lib/player/index.ts (playerController), never by calling commands.player* directly. Webview-rendered HTML5 video reports its state back into Rust via src/lib/player/html5Adapter.ts and the player_report_* commands, so the PlayerController stays the single source of truth in both native (MPV/ExoPlayer) and HTML5 modes (see 05-platform-backends.md ). Handle-Based Resources : UUID handles for stateful Rust objects. Cache-First : Parallel queries with intelligent fallback. Single source of truth for reachability : Server reachability is derived from the outcome of real repository traffic , not a side-channel poller. The OnlineRepository reports each server result to the ConnectivityMonitor (classified via RepoError), which applies a time-window debounce before declaring the server offline and recovers instantly on the first success. The standalone /System/Info/Public probe runs only while offline , as a recovery detector for idle sessions. Poison-tolerant locking : Shared std::sync state is accessed via the MutexSafe/RwLockSafe helpers in utils/lock.rs, which recover a poisoned lock instead of cascading a panic across the player. Graceful backend init : If a native player backend (MPV/ExoPlayer) fails to initialize, the app falls back to a no-op backend and emits a backend-init-failed event rather than crashing. flowchart TB subgraph Frontend[\"Svelte Frontend\"] subgraph Stores[\"Stores (Thin Wrappers)\"] auth[\"auth\"] player[\"player\"] queue[\"queue\"] library[\"library\"] connectivity[\"connectivity\"] playbackMode[\"playbackMode\"] end subgraph Components playerComp[\"player/\"] libraryComp[\"library/\"] Search[\"Search\"] end subgraph Routes routeLibrary[\"/library\"] routePlayer[\"/player\"] routeRoot[\"/\"] end subgraph API[\"API Layer (Thin Client)\"] RepositoryClient[\"RepositoryClient (Handle-based)\"] JellyfinClient[\"JellyfinClient (Helper)\"] end end Frontend -->|\"Tauri IPC (invoke)\"| Backend subgraph Backend[\"Rust Backend (Business Logic)\"] subgraph Commands[\"Tauri Commands (90+)\"] PlayerCmds[\"player.rs\"] RepoCmds[\"repository.rs (27)\"] PlaybackModeCmds[\"playback_mode.rs (5)\"] StorageCmds[\"storage.rs\"] ConnectivityCmds[\"connectivity.rs (7)\"] end subgraph Core[\"Core Modules\"] MediaSessionManager[\"MediaSessionManager (Audio/Movie/TvShow/Idle)\"] PlayerController[\"PlayerController + PlayerBackend + QueueManager\"] Repository[\"Repository Layer HybridRepository (cache-first) OnlineRepository (HTTP) OfflineRepository (SQLite)\"] PlaybackModeManager[\"PlaybackModeManager (Local/Remote/Idle)\"] ConnectivityMonitor[\"ConnectivityMonitor (Adaptive polling)\"] HttpClient[\"HttpClient (Exponential backoff retry)\"] end subgraph Storage[\"Storage Layer\"] DatabaseService[\"DatabaseService (Async trait)\"] SQLite[\"SQLite Database (13 tables)\"] end Commands --> Core Core --> Storage Repository --> HttpClient Repository --> DatabaseService Repository -->|\"reports server outcome (success / RepoError)\"| ConnectivityMonitor end The Repository --> ConnectivityMonitor edge is the source of truth for the offline/online banner: every server request the user actually makes updates reachability. The monitor's own polling is now an offline-only recovery probe (see 07-connectivity.md ).","breadcrumbs":"Overview » Architecture Principles","id":"595","title":"Architecture Principles"},"596":{"body":"Each major subsystem is documented in its own file in this directory: Document Contents 01 - Rust Backend Media session state machine, player state machine, playback mode, media items, queue manager, favorites (marking + browsing), player backend trait, player controller, playlist system, domain vocabulary owned by Rust (search scope, library exclusions, streaming quality ladder), background workers (catalog indexer, drains), Tauri commands 02 - Svelte Frontend Store structure, music library navigation, playback reporting, repository architecture, playback mode system, database service abstraction, component hierarchy, MiniPlayer, sleep timer, auto-play, navigation guard, playlist management UI, library mosaic, series/episode navigation, downloaded browse, safe-area insets, native-video store, logging 03 - Data Flow Repository query flow (cache-first), locally-indexed search, playback initiation, playback mode transfer, queue navigation, volume control 04 - Type Sync & Threading Rust/TypeScript type synchronization, Tauri v2 IPC parameter naming convention, thread safety patterns 05 - Platform Backends Player events system, HTML5 video adapter, MpvBackend (Linux), ExoPlayerBackend (Android) incl. audio settings parity, native video compositing , MediaSession & remote volume, album art caching, backend initialization 06 - Downloads & Offline Download manager, download worker, smart caching engine, one storage model (cache entries are downloads) , offline catalog visibility, download/offline commands, player integration, frontend store, UI components 07 - Connectivity HTTP client with retry logic, connectivity monitor, network resilience architecture 08 - Database Design Entity relationships, all table definitions (servers, users, libraries, items, user_data, downloads, media_streams, sync_queue, thumbnails, playlists), key queries, data flow diagrams, storage estimates 09 - Security Authentication token storage, secure storage module, network security, webview CSP + asset-protocol scope, path confinement and input binding , local data protection","breadcrumbs":"Overview » Detailed Documentation","id":"596","title":"Detailed Documentation"},"597":{"body":"src-tauri/src/\n├── lib.rs # Tauri app setup, state initialization\n├── commands/ # Tauri command handlers (~245 #[tauri::command] fns)\n│ ├── mod.rs # Command exports\n│ ├── player/ # Player commands: queue, remote, session, settings, timers\n│ ├── repository.rs # Repository commands (items, search, favourites, disk usage)\n│ ├── catalog.rs # Catalog sync + the background index pass\n│ ├── favorites.rs # Offline favourite drain\n│ ├── library.rs # Library listing + folder exclusions\n│ ├── playlist.rs # Playlist commands\n│ ├── playback_mode.rs # Local/remote transfer\n│ ├── playback_reporting.rs\n│ ├── connectivity.rs # Connectivity commands\n│ ├── storage/ # Storage & database commands: people, series_prefs, thumbnails\n│ ├── download/ # Download commands: mod, pinning, smart_cache\n│ ├── offline.rs # Offline commands\n│ ├── device.rs # Device id / capabilities\n│ ├── sessions.rs # Remote sessions\n│ ├── sync.rs # Sync queue commands\n│ └── sync_drain.rs # Background sync-queue drain\n├── repository/ # Repository pattern implementation\n│ ├── mod.rs # MediaRepository trait, handle management\n│ ├── types.rs # RepoError, Library, MediaItem, etc.\n│ ├── hybrid.rs # HybridRepository with cache-first racing\n│ ├── online.rs # OnlineRepository (HTTP API)\n│ └── offline.rs # OfflineRepository (SQLite queries)\n├── playback_mode/ # Playback mode manager\n│ └── mod.rs # PlaybackMode enum, transfer logic\n├── connectivity/ # Connectivity monitoring\n│ └── mod.rs # ConnectivityMonitor, adaptive polling\n├── jellyfin/ # Jellyfin API client\n│ ├── mod.rs # Module exports\n│ ├── http_client.rs # HTTP client with retry logic\n│ └── client.rs # JellyfinClient for API calls\n├── storage/ # Database layer\n│ ├── mod.rs # Database struct, migrations\n│ ├── db_service.rs # DatabaseService trait (async wrapper)\n│ ├── schema.rs # Table definitions\n│ └── queries/ # Query modules\n├── download/ # Download manager module\n│ ├── mod.rs # DownloadManager, DownloadInfo, DownloadTask\n│ ├── worker.rs # DownloadWorker, HTTP streaming, retry logic\n│ ├── events.rs # DownloadEvent enum\n│ └── cache.rs # SmartCache, CacheConfig, LRU eviction\n└── player/ # Player subsystem ├── mod.rs # PlayerController ├── session.rs # MediaSessionManager, MediaSessionType ├── state.rs # PlayerState, PlayerEvent ├── media.rs # MediaItem, MediaSource, MediaType ├── queue.rs # QueueManager, RepeatMode ├── backend.rs # PlayerBackend trait, NullBackend ├── events.rs # PlayerStatusEvent, TauriEventEmitter ├── mpv/ # Linux MPV backend │ ├── mod.rs # MpvBackend implementation │ └── event_loop.rs # Dedicated thread for MPV operations └── android/ # Android ExoPlayer backend └── mod.rs # ExoPlayerBackend + JNI bindings src/lib/\n├── api/ # Thin API layer (~200 lines total)\n│ ├── types.ts # TypeScript type definitions\n│ ├── repository-client.ts # RepositoryClient wrapper (~100 lines)\n│ ├── client.ts # JellyfinClient (helper for streaming)\n│ └── sessions.ts # SessionsApi (remote session control)\n├── player/ # Unified player boundary (frontend)\n│ ├── index.ts # playerController facade — the only write-side entry point for playback\n│ └── html5Adapter.ts # Reports webview DOM events back into Rust (player_report_*)\n├── services/\n│ ├── playerEvents.ts # Tauri event listener for player events\n│ └── playbackReporting.ts # Thin wrapper (~50 lines)\n├── stores/ # Thin reactive wrappers over Rust commands\n│ ├── index.ts # Re-exports\n│ ├── auth.ts # Auth store (calls Rust commands)\n│ ├── player.ts # Player store\n│ ├── queue.ts # Queue store\n│ ├── library.ts # Library store\n│ ├── playbackMode.ts # Playback mode store (~150 lines)\n│ ├── connectivity.ts # Connectivity store (~250 lines)\n│ └── downloads.ts # Downloads store with event listeners\n└── components/ ├── Search.svelte ├── player/ # Player UI components ├── playlist/ # Playlist modals (Create, AddTo) ├── sessions/ # Remote session control UI ├── downloads/ # Download UI components └── library/ # Library UI components + PlaylistDetailView","breadcrumbs":"Overview » File Structure Summary","id":"597","title":"File Structure Summary"},"598":{"body":"What moved to Rust (~3,500 lines of business logic): HTTP Client (338 lines) - Retry logic with exponential backoff Connectivity Monitor (301 lines) - Reachability derived from real repository traffic, time-window debounce, offline-only recovery probe, event emission Repository Pattern (1061 lines) - Cache-first hybrid with parallel racing Database Service - Async wrapper preventing UI freezing Playback Mode (303 lines) - Local/remote transfer coordination Svelte/TypeScript frontend (~20.5k non-test lines, plus ~9.6k test lines): Components + routes (~14.6k lines) — UI and presentation Stores (~3.4k lines) — reactive state that invokes Rust commands and listens for events api / services / utils (~2.4k lines) — typed clients, event listeners, conversion helpers The frontend is genuinely UI-heavy; business decisions live in Rust, but the UI owns layout, navigation, and interaction state. Total Commands: ~245 #[tauri::command] functions across 17 command modules (~58k lines of Rust, ~37k non-test lines of TypeScript/Svelte). Counts and line totals in this file are periodic snapshots, not gates — the authority is the tree. Regenerate with grep -rc '#\\[tauri::command\\]' src-tauri/src and wc -l.","breadcrumbs":"Overview » Key Architecture Changes","id":"598","title":"Key Architecture Changes"},"599":{"body":"Location : src-tauri/src/","breadcrumbs":"Overview » Rust Backend » Rust Backend Architecture","id":"599","title":"Rust Backend Architecture"},"6":{"body":"Internal architecture, components, and application logic. ID Requirement Category Traces To Status DR-001 Player state machine (idle, loading, playing, paused, seeking, error) Player UR-005 Done DR-002 MediaItem struct tracking source, location, duration, metadata Player UR-003, UR-004 Done DR-003 Source-agnostic media abstraction (Remote, Local, DirectUrl) Player UR-002, UR-011 Done DR-004 PlayerBackend trait for platform-agnostic playback Player UR-003, UR-004 Done DR-005 Queue manager with shuffle, repeat, history Player UR-005, UR-015 Done DR-006 Audio pre-caching for seamless track transitions Player UR-004 Planned DR-007 Library browsing screens (grid view, search, filters) UI UR-007, UR-008 Done DR-008 Album/Series detail view with track listing UI UR-007 Done DR-009 Audio player UI (mini player, full screen) UI UR-005 Done DR-010 Video player UI (fullscreen, controls overlay) UI UR-003, UR-005 Done DR-011 Search bar with cross-library search UI UR-008 Done DR-012 Local database for media metadata cache Storage UR-002 Done DR-013 Repository pattern for online/offline data access Storage UR-002 Done DR-014 Offline mutation queue for sync-back operations Storage UR-002, UR-014, UR-017 Done DR-015 Download manager with queue and progress tracking Storage UR-011, UR-018 Done DR-016 Thumbnail caching and sync with server Storage UR-007 Done DR-017 \"Manage Downloads\" screen for local media management UI UR-013 Done DR-018 Download buttons on library/album/player screens UI UR-011, UR-018 Done DR-019 Playlist creation and editing UI UI UR-014 Done DR-020 Queue management UI (add, remove, reorder) UI UR-015 Done DR-021 Like/favorite functionality on media items UI UR-017 Done DR-022 Resume position tracking and restoration on play Player UR-019 Done DR-023 Subtitle selection UI in video player UI UR-020 Done DR-024 Audio track selection UI in video player UI UR-021 Done DR-025 Quality/transcoding settings UI UI UR-022 Planned DR-026 \"Continue Watching\" / \"Next Up\" home section UI UR-023 Done DR-027 \"Recently Added\" home section UI UR-024 Done DR-028 Playback progress sync service (periodic reporting) Player UR-025 Done DR-029 Sleep timer with roller UI, time/track/episode modes, and auto-stop (audio + video players) Player UR-026 Done DR-049 Auto-play episode limit (configurable max episodes per session) Player UR-023 Done DR-050 Reusable scroll picker (roller) component UI UR-026 Done DR-030 Equalizer UI with presets and custom bands UI UR-027 Done DR-031 Clickable artist/album links in now playing view UI UR-028 Done DR-032 List view option for library browsing (albums, artists) UI UR-029 Done DR-033 Genre browsing screen with quick filters UI UR-030 Done DR-034 Crossfade engine with configurable duration (0-12s) Player UR-031 Not implemented (blocked on MPV: single-stream audio chain; acrossfade needs 2 inputs — see docs/specs/playback-backend-unification.md) DR-035 Gapless playback between sequential tracks Player UR-032 Done (Linux via MPV; Android via pauseAtEndOfMediaItems — pending device verification) DR-036 Volume normalization with preset levels (Loud/Normal/Quiet) Player UR-033 Done (Linux via MPV dynaudnorm; Android via LoudnessEnhancer — pending device verification) DR-037 Remote session browser and control UI UI UR-010 Done DR-038 Home screen with hero banner carousel (featured/continue watching) UI UR-034 Done DR-039 Home screen horizontal carousels (recently added, recommendations) UI UR-034, UR-024 Done DR-040 Cast/crew section on movie/show detail pages UI UR-035 Done DR-041 Person/actor detail page with filmography grid UI UR-036 Done DR-042 Video library grid with poster cards, year, and rating badges UI UR-037 Done DR-043 Movie/show detail page with backdrop hero, synopsis, and metadata UI UR-038 Done DR-044 Horizontal scrolling actor/cast row with profile images UI UR-035 Done DR-045 Bottom navigation bar with Home, Library, Search buttons UI UR-039 Done DR-046 Dedicated search page with input and results UI UR-039 Done DR-047 Next episode auto-play popup with configurable countdown and episode limit Player UR-023 Done DR-048 Video settings (auto-play toggle, countdown duration, episode limit) Settings UR-023, UR-026 Done DR-051 Background-audio toggle button in the video player controls (suppresses auto-PiP while enabled) UI UR-040 Done (pending device verification) DR-052 Background-audio handoff state machine: on background/lock tear down the WebView /HLS decode and start native audio-only playback at the current position; on foreground return position and resume ; exactly one audio source active at every transition (no dual audio) Player UR-040 Done (pending device verification) DR-053 PictureInPictureManager: canEnterPip gate (local video surface actively rendering — false for audio, browsing, and remote/cast), aspect-ratio clamp, a RemoteAction play/pause receiver whose icon reflects live player state (refreshed on every playback-state change while in PiP, not only on button press), WebView hide/restore, surface re-fit on exit; plus the AndroidPictureInPicture JS bridge and the PiP button (shown only when PiP is supported) in the video player UI UR-041 Done DR-054 Auth manager and session lifecycle: connect-to-server, login, Quick Connect verification poll (start/stop), session get/set, background session verifier, re-authenticate, logout Auth UR-042 Done DR-055 ConnectivityMonitor deriving reachability from real repository traffic, with online/offline state, mark-reachable/unreachable reporting, and a probe-based recovery poller active only while offline Connectivity UR-043 Done DR-056 Download pinning (pin/unpin/is-pinned) that excludes an item from smart-cache eviction Storage UR-044 Done DR-057 Smart cache manager: album-affinity tracking, queue-lookahead pre-cache, storage-limit enforcement, config, stats, and recommendations Storage UR-045 Done DR-058 Remote sync-group control (LMS SyncGroups): list, create, unsync a player, dissolve a group Player UR-046 Done DR-059 Playback-mode transfer state machine: get/set current mode, transferring guard, transfer-to-remote / transfer-to-local, remote session status Player UR-010 Done DR-060 Multi-server store and active-account selection: save/get/delete server, save/get user, set/get active user (per-server), active-session resolution Storage UR-047 Partial (store done; server-switcher UI pending) DR-061 Episode Focus View: episode hero followed immediately by the \"More Episodes\" strip — a forward-biased window (~3 before / ~6 after) around the current episode, spanning season boundaries in series order, with the current episode present and badged, per-card resume progress and watched state, and click-to-swap focus (no playback) UI UR-048 Done DR-062 Detail-page section ordering: continuation content precedes discovery content — Episode Focus View renders hero → episode strip → cast → similar; Series renders hero → seasons/episodes → cast → similar UI UR-048 Done DR-063 Search scope taxonomy owned by Rust: SearchScope (All / Music / Movies / TV) crosses IPC as an opaque enum and SearchScope::item_types() expands it to Jellyfin item types, resolved once in repository_search before the cache and server paths diverge so online and offline filter identically; All expands to no filter rather than the union of the other scopes (which would drop People and folders). The frontend maps the originating route to a scope (resolveSearchScope, presentation) and never names an item type for search Backend UR-049 Implemented DR-064 Scope chip row rendered under the search bar on both the search page and the in-library header search: preselected from context, horizontally scrollable, re-runs the search preserving the query on change UI UR-049 Implemented DR-065 Thread SearchOptions.includeItemTypes through library.search() so the global/header search honours scope (backend online + offline paths already support it) UI UR-049 Implemented DR-066 Persisted search result group order with a drag-and-drop settings list, keyboard-accessible reordering, a shipped default (see DR-091 for the current group set and order), and empty-group omission Settings UR-050 Implemented DR-067 SearchResults renders groups in the user-configured order rather than hardcoded markup order, without altering intra-group ranking UI UR-050 Implemented DR-068 Library card shape by media type: 1:1 square for music (circular mask for artists), 2:3 poster for movies/series/seasons, 16:9 for episodes and collection folders UI UR-051 Done DR-069 Responsive library grid (2/3/4/5/6 columns across base→xl) with two-line truncated card text and artwork-overlay progress/watched state UI UR-051 Done DR-070 Global persisted grid/list view preference honoured by browse pages, suppressed for ordinal content (album tracks, season episodes) UI UR-051, UR-029 Partial (persisted store + page-header toggle; no settings entry) DR-075 Shared AccountMenu component: identity header (user + server), Downloads / Settings / Display entries, divider, Sign out last; anchored to the username/avatar trigger and identical on desktop and mobile UI UR-054 Done DR-076 App shell exposes the header (and therefore the account menu) on every authenticated non-immersive route, including /, /search, and /downloads; only /player/* and /login remain chrome-free UI UR-054 Done DR-077 Display section in Settings binding the existing persisted grid/list viewMode store, giving the preference a discoverable home Settings UR-054, UR-029 Done DR-078 Catalog-visibility gate spanning the \"Show all server media\" toggle → set_show_server_catalog → INCLUDE_CATALOG_BROWSE → the synced-catalog UNION branch of offline get_items. Visibility resolves to serverReachable || showServerCatalog, so offline with the toggle off lists downloaded/local media only Storage UR-052, UR-002 Done DR-079 isConnected derives from backend-reported server reachability alone; navigator.onLine is advisory and may only trigger a recheck, never force or clear the offline state (a reachable LAN server while the browser reports offline, and an unreachable server on a live link, must both resolve correctly) Connectivity UR-052, UR-043 Done DR-080 With the catalog-browse gate off, an empty offline get_items result is authoritative \"no downloads here\" and must be returned as-is; the hybrid repository must not treat it as a cache miss and fall through to the server Storage UR-052, UR-013 Done DR-074 WiFi-only download gate: NetworkState/NetworkType transport model reported from the platform via set_network_state, checked in pump_download_queue before starting any pending row (cellular/metered/unknown fail closed, WiFi and Ethernet require NOT_METERED); blocked rows stay pending and re-pump on network change, with a waitingForNetwork event driving the \"Waiting for WiFi\" notice. Also wires the previously inert Smart Caching / Queue Pre-caching / WiFi Only settings toggles to CacheConfig Downloads UR-053 Done (pending device verification) DR-081 /downloads split into a default Downloaded browse view and a secondary Transfers activity view, with a view switch and a Transfers badge shown only while transfers are active UI UR-055 Done DR-082 Offline-scoped browse entry point in the repository client: browse downloaded content only (offline repository get_items/get_libraries — downloaded items plus their containers) independent of server reachability, without merging server catalog Storage UR-055 Done DR-083 Downloaded browse reuses library grids, cards, and detail pages via the offline-scoped source; omits libraries/containers with no downloaded content; badges partially- vs fully-downloaded containers; play uses the local file; remove available at item/album/season/series level UI UR-055 Done DR-084 Transfers view renders only in-flight rows (downloading/queued/paused/failed/waiting-for-WiFi) with Pause/Resume/Cancel/Retry; completed transfers leave the view and appear in Downloaded UI UR-055 Done DR-085 Per-item on-disk size: stat downloaded files, aggregate to album/season/series subtotals and a device total, format in consistent rounded human units; surface size on cards and detail pages, the device total on the Downloaded surface, and a reclaim figure in the remove confirmation Downloads UR-056 Done DR-086 Settings page persists each control on change via per-group writers (playerSetAudioSettings / playerSetVideoSettings / updateCacheConfig) rather than a batch Save action; slider controls persist on change (pointer release) not each input tick; no Save button, saving, or saveMessage state Settings UR-057 Done DR-087 MediaCard gains an onLongPress prop with pointer-based long-press detection (~500 ms hold, cancelled on >10 px move so carousel scroll is unaffected, trailing click suppressed); home carousels wire tap→detail/focus routing and long-press→confirm→player; episode taps route to /library/?episode=; the bare-episode detail page links to its parent series/season UI UR-058 Done DR-088 Skip-to-next-episode marks the outgoing episode played (markAsPlayed) instead of reporting a stop position, and arms a one-shot suppression consumed by the player's stop handler so VideoPlayer's post-navigation unmount stop report cannot overwrite the 100% progress with the partial position UI UR-059 Done DR-089 Continue Watching suppresses resume entries superseded by Next Up: an in-progress episode whose series has a next-up entry strictly later in series order (season, then episode) is dropped from the Home and TV rows; movies, series without a next-up entry, and items with unknown/mixed episode ordering are always kept UI UR-059 Done DR-090 Relevance ranking in Rust (domain/search_rank.rs): results sort by match position (prefix → word-start → mid-word substring → no name match) then by media kind (containers before their contents), stably so the backend's own relevance breaks ties. Applied in repository_search to both the instant cache result and the merged cache+server union, so the list does not reshuffle when server results land Backend UR-060 Done DR-091 Search result groups split TV into separate Shows and Episodes groups and add a People group (default order: Shows → Episodes → Movies → Songs → Albums → Artists → People); a stored tvShows order from before the split expands in place to shows+episodes so an upgrading user keeps their arrangement UI UR-060 Done DR-092 Video tap gestures resolve in tapGestures.ts (pure, unit-tested) rather than inline in VideoPlayer.svelte: registerTap classifies each tap and the component acts on it immediately — togglePlayPause for a first tap, or seek (+30 s right / −10 s left) plus a re-toggle for a second tap inside DOUBLE_TAP_WINDOW_MS (300 ms). A consumed pair resets the state, and a swipe forgets the tap. The deferral this originally used was removed in DR-098, which also covers suppressing the compatibility click the browser synthesizes after a touch tap. resolveSeekTarget converts the delta to the absolute position the facade requires, clamped per DR-095 and chained off a still-in-flight pendingSeekTarget so back-to-back skips accumulate instead of all resolving against a not-yet-updated position UI UR-061 Done DR-094 Frontend boundary tripwire (scripts/check-frontend-boundary.sh) detects Jellyfin item-type array literals anywhere in src/ rather than only inline at an includeItemTypes: query site, so a category→type mapping cannot evade the check by being assigned to a named const (the evasion that let the scoped-search leak pass CI); requires two adjacent type literals so single-type presentation and item.type === inspection stay legal, and caps the allowlist to force taxonomy into Rust instead of accumulating exceptions Tooling - Done DR-098 Video tap gestures act immediately — no deferral, no timer, and only first/second taps exist. A first tap toggles play/pause; a second tap inside DOUBLE_TAP_WINDOW_MS seeks and toggles again, so the two toggles cancel and a double tap preserves the play state (playing → jump and keep playing; paused → jump and stay paused). This replaces a design that deferred the first tap behind a 300 ms timer so a second tap could cancel it: the timer cleared its own handle before invoking the toggle, which reopened the tapTimeout !== null guard in handleVideoClick meant to suppress the compatibility click Android's WebView synthesizes after a touch — the late click then toggled a second time, producing a pause/unpause loop (long-press was unaffected, which is what identified the tap path). Click suppression no longer depends on the timer: handleVideoClick ignores detail === 0 and any click within TOUCH_CLICK_SUPPRESS_MS of a touch tap. A swipe undoes the touchstart toggle exactly once (latched on swipeGestureActive) so brightness swipes never change play state. Click suppression is shared by every click target layered over the video via isSynthesizedTouchClick, not just the : pausing renders a full-screen play-overlay button, so the synthesized click lands on that and an unguarded handler there resumed immediately — pausing appeared impossible while unpausing worked, because unpausing removes the overlay UI UR-061 Done DR-099 The video seek bar is usable by touch. Two Android-only defects made dragging or tapping it move the thumb without moving playback. (a) Gesture hijack : the container-level gesture layer skips touchstart on a control (DR-098) but kept handling touchmove, so a seek-bar drag was measured against the previous gesture's start point — a huge bogus vertical delta that read as a brightness swipe, dimmed the screen to the 0.3 floor, and fired a spurious play/pause \"correction\" mid-drag. A gesture is now latched at touchstart (playerGestureActive) and touchmove ignores anything not latched, since re-checking the move target cannot recover a start point that was never recorded. (b) Commit signal : the seek was committed only from change, which Android's WebView does not reliably fire for a touch interaction on a range input — the thumb moved to the tapped position and no seek ever ran. touchend/mouseup now commit as well; input arms a one-shot latch so whichever release signal arrives first commits and the other is a no-op. seekRelative shares the same commitSeek entry point instead of fabricating a synthetic change event UI UR-005, UR-061 Done DR-097 Transport authority (play/pause/toggle) lives in Rust for webview-rendered media, not just native. The controller tracks the state the HTML5 element reports (html5_playing, fed by report_html5_state, which now stores rather than only re-emitting); play/pause/toggle_playback consult it and drive the element by emitting a ControlCommand that playerEvents.handleControlCommand executes against the active adapter. A stopped/idle report clears it so the native backend (MPV/ExoPlayer) regains authority for music. The frontend facade no longer short-circuits transport into the adapter: adapter.toggle() previously decided play-vs-pause by reading el.paused off the DOM, a value that flips transiently while an element buffers or settles a seek — so two intents ~150 ms apart read different values, performed opposing actions, and self-sustained a play/pause loop needing no further input (observed on Android with a fully-buffered readyState=4 networkState=1 element). Same \"backend decides, adapter executes the primitive\" split as player_seek_video Player UR-005 Done DR-096 Html5PlayerAdapter.play() is resilient to stall recovery: an in-flight attempt is memoised so concurrent callers (UI plus hls.js gap-controller recovery) share one element.play() instead of stacking calls, and an AbortError (\"play() request was interrupted by a call to pause()\") is logged at debug rather than pushed to host.onError. The browser raises it whenever a pending play promise is superseded by a pause/seek/source change, which hls.js does routinely while nudging past a stall — reporting it surfaced a player error roughly once per second for the whole stall and left the UI stuck showing paused Player UR-005 Done DR-095 Seek targets clamp strictly inside the media (clampSeekTarget, END_SEEK_MARGIN_SECONDS = 6 s ≈ one HLS segment) instead of to the exact duration. Landing on the duration makes hls.js request the segment whose start time lies past the end of the media (e.g. a 6330.324 s item → segment 1055 starting at 6336.33 s), which Jellyfin never produces; the fetch times out and hls.js' gap-controller stalls at the last buffered position, presenting as \"unpausing or skipping bounces straight back to paused\". Applied on both seek paths — the relative-skip resolveSeekTarget and the seek-bar drag, whose range input max is the duration itself — and floored at 0 so media shorter than the margin still seeks to the start UI UR-061 Done DR-100 Leaving a video and re-entering it renders the video player, never the audio one. Both halves of the /player/[id] decision are pure and unit-tested in playerSurface.ts. (a) shouldReuseActivePlayback excludes video: the \"already playing, just show the UI\" shortcut (added for expanding the audio mini player) returns before a stream URL is fetched, which is fine for audio — the backend owns the stream and the route only mirrors it — but leaves with nothing to render. Closing a webview-rendered video deliberately emits no stopped state (that would break the autoplay handoff, see DR-047), so the Rust controller still reports that movie/episode as its loaded media and re-entering the same item hit the shortcut. (b) resolvePlayerSurface maps video-without-a-stream-URL to pending (spinner) instead of falling through to , so no future path can put video content in the audio surface. Video now always takes the full load path, which fetches the stream URL and applies the stored resume position UI UR-005 Done DR-101 \"Where is this viewer in this series\" is resolved in Rust , not the frontend. repository_get_series_episodes performs the season fan-out (get_items(series_id) → seasons → get_items(season_id), plus the flat-series fallback for shows whose children are episodes rather than season folders) and returns them in series order — season index ascending, episode index ascending, specials (season 0) after every numbered season. repository_get_series_current_episode layers the pure policy pick_current_episode over that list: an in-progress episode wins (earliest in series order on a tie — it is literally where playback stopped, and Next Up would skip past it), then the server's Next Up for that series, then the first unwatched episode, then the first. The third rung is the offline path, not dead code: OfflineRepository::get_next_up_episodes returns an empty vec, so without it the feature would be online-only. A failing Next Up or resume lookup degrades to empty rather than failing the call. repository_get_next_up_episodes had accepted a series_id since it was written and no caller had ever passed one Repository UR-062 Done DR-102 The series detail page anchors on that answer. It calls repositoryGetSeriesEpisodes once instead of fanning out over seasons in TypeScript (the fan-out and its flat-series fallback were domain knowledge in the presentation layer), groups the returned episodes under season headers by parentIndexNumber, and passes the resolved current episode to SeasonSection → EpisodeRow, which renders a highlight ring and scrolls itself into view. The hero button navigates to /library/?episode= — the Episode Focus View, where an explicit Play/Resume commits — per ux-flows §5B.5: Play on a container is navigation, Play on a leaf commits. It previously resolved $libraryItems[0], the first season by SortName, and navigated to /player/, which the player route bounced back to /library/ — so Play on a series played nothing and landed on the season-1 page UI UR-062 Done DR-103 A season is not a destination. /library/ redirects to /library/#season-, the anchor SeasonSection renders, so a season link scrolls the series' continuous episode list rather than opening a page. Every inbound link follows: the episode breadcrumb, handleItemClick case \"season\", the TV landing page's case \"Season\", and DownloadedBrowse. A season carrying no seriesId (deep link into a stale cache) still renders the generic view so the user is never stranded. This removes a surface that had no route of its own — it fell through the detail page's kind chain to the generic \"Contents\" poster grid, contradicting ux-flows §5A.2 (episodes must be a row list), and clicking an episode there opened a bare Episode page, which §5B.1 forbids UI UR-062 Done DR-104 The \"More Episodes\" strip spans the whole series in series order, per ux-flows §5B.2's cross-season continuity rule: at the end of a season the window runs on into the next season's first episodes instead of dead-ending. adjacentEpisodes previously filtered the pool to parentIndexNumber === current.parentIndexNumber and sorted by indexNumber alone, so the window could never leave the current season — and, when episodes of several seasons did reach it, sorting by episode number alone interleaved them. Cards crossing a season boundary are labelled SxEy rather than a bare episode number so the jump is legible UI UR-062 Done DR-105 Video library routes collapse to one per library. /library/tv and /library/movies render browse / all-titles / genres as in-page tabs driven by ?view=, omitted for the default browse (the convention searchRouteUrl already uses for the all scope); resolveLibraryView is pure and unit-tested. The four legacy routes become redirect-only +page.ts loads rather than deletions, because GenreTags links to them and users have them in history; resolveSearchScope keeps its /library/shows branch for the same reason. The \"Browse\" tile grid at the bottom of both landing pages is removed — it was a second navigation affordance to the same destinations the carousels' \"Show all\" links already reach UI UR-063 Done DR-106 Erasing watch history goes through the repository, not the local cache: clear_watch_history(item_id) maps to Jellyfin's DELETE /Users/{userId}/PlayedItems/{itemId}, which clears the played flag and zeroes the resume position, and which the server applies recursively to a folder — so one call handles a whole series or season. OfflineRepository returns RepoError::Offline rather than clearing locally, because history diverged only on the device would be silently undone by the next sync; the button disables itself while the server is unreachable. ClearHistoryButton is shared by the series hero and each SeasonSection header, confirms before acting (there is no undo), and reloads the page on success so the recomputed current episode — the premiere, for a fully cleared series — is what the viewer sees Repository UR-064 Done DR-107 Seasons on the series page are collapsible, and only the current season is expanded on load — the one holding the episode DR-101 resolved. A show with ten seasons otherwise renders every episode of every season at once, burying the one episode the viewer came for under hundreds of rows. Expansion state is per season and pure (initialExpandedSeasons in seriesNavigation.ts): the current season, or the first season when there is no current episode, so a never-watched show still opens on season 1 rather than fully collapsed. A ?episode= deep link expands that episode's season too. Toggling is local and not persisted — it is a reading position, not a preference UI UR-062 Done DR-108 The instant (cache) leg of repository_search searches the synced catalog , not just downloads. OfflineRepository::search replaces its downloaded_items CTE with the available_items CTE get_items already uses — the same downloads branches plus a synced_at IS NOT NULL branch gated on the same include_catalog_browse() flag — so search and browse cannot diverge on what is visible. Online (flag true) search reads the whole index and answers before any HTTP request completes; offline with \"Show all server media\" off (flag false) it stays downloads-only, unchanged. Requires no frontend change, since the flag is already set correctly for all three states. The include_item_types filter is switched from string interpolation to bound parameters, as SearchOptions is settable from the frontend and not only from SearchScope Backend UR-065 Implemented DR-109 Index freshness is a Rust-owned policy, not a frontend startup call. A tokio task ticks every 30 min and runs a full pass when a repository is active, the server is reachable, and last_catalog_sync (already persisted to app_settings, previously read only for a UI hint) is older than CATALOG_INDEX_TTL (6 h); the ConnectivityMonitor reconnect signal re-evaluates the same condition immediately. An AtomicBool prevents concurrent passes, replacing offlineCatalog.ts's syncInProgress — the frontend trigger is removed rather than left alongside, since two triggers with one guard each is how double-crawls happen. RepositoryManager gains an active-handle slot so the task has something to run against. Progress is emitted as the kebab-case catalog-index-event Backend UR-065 Implemented DR-110 Index hygiene. save_to_cache switches from INSERT OR REPLACE INTO items to ON CONFLICT(id) DO UPDATE: REPLACE fires no AFTER DELETE trigger unless recursive_triggers is on (it is not — only foreign_keys and journal_mode are set), so items_ad never ran, and because items.id is a TEXT PRIMARY KEY each replacement also took a fresh rowid and appended a second items_fts entry — a duplicate index per sync, invisible in results but permanently degrading MATCH. The upsert preserves the rowid items_fts keys on and fires items_au; migration 021_rebuild_items_fts clears orphans on existing installs. Separately, a post-crawl sweep deletes synced-but-not-downloaded rows a successful library crawl did not return, so media removed from the server stops being searchable; it skips items with completed downloads and skips any library whose crawl errored, because items.parent_id is ON DELETE CASCADE and a partial crawl would cascade away a whole series Storage UR-065 Implemented DR-111 The index covers what the result groups render: CATALOG_ITEM_TYPES gains MusicArtist and Playlist, and migration 022_people_fts adds a people_fts virtual table over the existing people table (which had no FTS, and is populated incidentally by item-detail fetches) with the same trigger pattern as items_fts. OfflineRepository::search UNIONs people_fts matches in as Person items when the resolved scope admits them — i.e. SearchScope::All, which expands to no filter (DR-063). Without this, the Artists and People groups UR-060 mandates can only ever be filled by the server leg Storage UR-065, UR-060 Implemented DR-112 Safe-area insets come from native , not from env() alone. env(safe-area-inset-*) is 0px without viewport-fit=cover (missing from app.html, so every safe-area rule in the app was already a no-op), and even with it Android WebView maps only the display cutout — never the status bar or navigation bar. Since enableEdgeToEdge() plus targetSdk 36 make edge-to-edge unconditional, the WebView always spans the system bars, so CSS could not learn about them by any route. WindowInsetsBridge reads the real insets and publishes jt-inset custom properties; app.css folds them with env() via max() into --safe-*, which is the only thing components may pad from. Ownership is exactly one element per edge: the app shell takes top/left/right, and BottomUi takes bottom wherever it renders (shellReservesBottomInset hands it back to the shell on routes with no bottom UI) so the padding sits inside BottomUi's surface box and the colour extends behind the gesture bar. The full-screen players inset their control layers only, leaving video and artwork edge-to-edge. The theme's fitsSystemWindows=true — which claimed the opposite and was overridden at runtime and ignored at this target SDK — is removed UI UR-066 Done DR-113 MediaItem.user_data is populated from the server instead of being hardcoded None. JellyfinItem gains a UserData field (#[serde(alias = \"UserData\")] → the existing UserData type) and to_media_item maps it, so every list and detail response carries favourite/played/resume state. UserData is named explicitly in the Fields= list rather than relying on Jellyfin's default. Without this no card or detail page can render a favourite it did not itself set, and the mini player's per-track storageGetPlaybackProgress fetch is the only way to colour one heart Repository UR-069 Done DR-114 Server favourite state is mirrored into the local user_data table by OfflineRepository::save_to_cache — the single choke point every cached server result passes through — so offline browsing and the offline Favourites page see the same favourites as the server. The upsert carries pending_sync = 0 and is guarded by WHERE user_data.pending_sync = 0, which is the conflict rule: a toggle made offline is never overwritten by a stale server value before it has been pushed Storage UR-069 Done DR-115 Cross-library favourites query: a get_favorites(scope, options) repository method plus the repository_get_favorites command. Online issues Filters=IsFavorite&Recursive=true with IncludeItemTypes expanded from SearchScope::item_types() in Rust (the frontend sends the opaque scope, never a type list — DR-063); offline reads items ⨝ user_data (is_favorite = 1) under the same include_catalog_browse() gate as browsing; hybrid races cache against server like get_items — saving server results through to the cache on a miss, so the favourites page does not re-query the server every visit and the DR-114 mirror is filled on a fresh install — and applies the DR-080 rule that an empty offline result is authoritative when the gate is off. The command falls back to this read when nothing is cached, rather than painting an empty state it will correct a round trip later. A separate method rather than get_items because favourites span libraries and get_items is ParentId-shaped Repository UR-067 Done DR-116 GetItemsOptions.favorites_only filters an existing library listing in place — online by appending Filters=IsFavorite, offline by joining user_data into the existing available_items CTE so the downloads-only gate still applies. This is what backs the per-library favourites toggle, and composes with the genre and item-type filters already there Repository UR-067 Done DR-117 The Favourites page (/library/favorites) renders favourites across libraries with All / Movies / Shows / Music scope tabs, reusing LibraryViewTabs + LibraryGrid + MediaCard so card shape still follows the media (§5A.1) and a mixed All tab reads as posters, squares and thumbnails side by side. Each tab sends a SearchScope value and nothing else. Reached from the library overview and from \"See all\" on the home rows UI UR-067 Done DR-118 Home carries favourite rows for movies, shows and music, loaded via repository_get_favorites per scope and rendered below Recently Added. A row with no items does not render at all, so a fresh install shows no empty favourite rows UI UR-067 Done DR-119 FavoriteButton is mounted wherever a whole item is shown — movie/series/episode detail heroes, album/artist/playlist headers, and as a MediaCard artwork overlay — and a favorites store holds in-session optimistic state so un-hearting on one surface updates every other without a refetch. Resolution order is store override ?? item.userData?.isFavorite ?? false. On a card the heart is its own button and stops propagation, so hearting never also opens, plays, or triggers the §5B.5 long-press; it is suppressed on server-only (greyed) cards UI UR-068 Done DR-120 Favourite toggles made while offline reach the server. A Rust drain, triggered by the ConnectivityMonitor offline→online transition, pushes every user_data row with pending_sync = 1 and clears the flag on success, leaving failures pending for the next transition. It lives in Rust rather than the frontend because a frontend drain dies with the component that started it. Both the drain and the hybrid background refresh emit the kebab-case favorites-changed event ({ itemIds }) so open views update — without it a favourite marked on another client appears only on the second visit to a page, since the cache-first read returns local rows and the server refresh is invisible to the frontend. Supersedes the unused syncService.queueFavorite, which is deleted rather than left as a second queue Backend UR-069 Done DR-121 Player quality selector: Rust reports the bitrates available for the current media source and owns the quality→transcode-parameter mapping (the one get_video_download_url already holds — playback calls into it rather than restating it, or the two tables drift). Changing quality re-negotiates the stream URL and resumes at the current position with audio/subtitle selection preserved. On Linux, video re-negotiates within HLS: returning stream.mp4 is the documented cause of transcoded playback never starting. The frontend renders the list and remembers the choice; it does not decide what the choice resolves to UI UR-070 Proposed DR-122 The playback path is ephemeral. Streamed bytes are never persisted unless DR-124 rules them keepable, and any in-flight capture is abandoned — partial file deleted, never promoted — the moment the viewer changes quality, because a capture spanning a rendition change is a splice of two encodings rather than a playable file Playback UR-070 Proposed DR-123 The download path is independent of playback: a whole-file fetch through the existing download manager at one canonical quality (default original, the direct static copy) over the Range-capable /Videos/{id}/stream.mp4, unaffected by bitrate changes and completing into an ordinary downloads row so offline browsing and refresh_queue_local_sources pick it up unchanged. Prerequisite: downloaded video is currently never played locally — repository_get_video_stream_url goes straight to the online repo and the player route calls it with no local check, so a completed video download is still streamed. Without that fix nothing in this spec is observable for video Repository UR-071 In Progress DR-124 Streamed bytes are kept only where they are the download artifact — a direct-play session. Android uses ExoPlayer SimpleCache/CacheDataSource keyed by item and media-source id so renditions cannot collide, sharing the existing smart-cache storage budget rather than opening a second one over the same disk; Linux audio uses mpv stream-record, abandoned on seek because it is documented as intended for linear streams and seeking breaks the recording. Transcoded Linux video is not captured: HLS segments are not a file, and assembling one needs ffmpeg, which is not a dependency and which CI may not install at job time — DR-123 covers that case instead Playback UR-071 Proposed DR-125 A capture is promoted to a completed downloads row only when it covers the whole resource; partials stay evictable cache. A new downloads.source_rendition column records the negotiated quality/container/codec (NULL for the existing paths, which are always original) so a captured transcode and a real download are distinguishable rows and an \"upgrade to original\" remains possible. A quality change never touches a file that already exists — not a permanent download, and not a completed temporary one, both of which stay valid copies of the rendition they hold. It invalidates only an in-flight capture or background download of cached media, which is abandoned and restarted at the newly chosen quality, because a capture spanning a rendition change is a splice of two encodings rather than a playable file Storage UR-071 Proposed DR-126 Cache eviction only reclaims the temporary tier. evict_lru_async selected every completed download ordered by completed_at ASC with no download_source filter, so hitting the 10 GB storage limit deleted the oldest download — typically a film saved deliberately for offline — to make room for a newly precached track. It now evicts only COALESCE(download_source, 'user') = 'auto' rows; COALESCE rather than a bare equality because rows predating migration 012 can be NULL and unknown provenance must be treated as the user's, never as disposable. Freeing less than requested is the correct outcome when only user downloads remain — the caller reports \"unable to free enough space\" instead of silently deleting them Storage UR-071 Done DR-127 A cache entry is a download with a shorter life: same downloads row and same file handling, distinguished by download_source = 'auto' plus an expiry, so there is one storage model rather than a cache and a download library that can disagree. Temporary rows are reclaimed on whichever comes first — the life limit elapsing, or eviction under space pressure (DR-126). Permanent ('user') rows have no expiry. A temporary row can be promoted to permanent by the user choosing to keep it, which only clears the expiry and flips the source; the bytes never move Storage UR-071 Done DR-128 Audio-only playback of downloaded media reads the local file rather than fetching an audio-only stream. No transcode is involved or wanted: the Linux backend already runs MPV with video: no, so handing it the downloaded video file decodes the audio track and ignores the video, and ExoPlayer disables its video renderer equivalently. Transcoding to a separate audio artifact would cost CPU and battery, need an encoder the project does not ship, and produce a second file to keep in step — for no gain over simply not decoding the video Playback UR-071 Done DR-129 A stream that stops delivering is recovered, not treated as terminal. Two failure shapes, because the streams differ. (a) Phantom end — the background audio-only handoff uses a progressive mp3 transcode over plain HTTP, chunked and therefore length-less, so a dropped connection reaches the player as end-of-input and ExoPlayer reports STATE_ENDED indistinguishably from the real end. The item's runtime is the only thing that can tell them apart: an end reported more than a tolerance short of it (comparing the absolute position — handoff base plus the player's relative position) is a truncation. Left unhandled, playback parked in STATE_ENDED and the next play intent from the lockscreen, notification or a Bluetooth reconnect seeks an ended player to position 0 — the user-visible \"the episode randomly restarted\". (b) Recoverable error — music (/Audio/{id}/stream?Static=true) and video (/Videos/{id}/master.m3u8) declare their length, so the player detects the truncation itself and raises an error; the frontend's handler stopped playback outright, turning a hiccup into silence. Both resume the current item in place (never via play_item, which would replace the queue with a single item and lose the album), the error path after a per-attempt backoff. Seekable streams are re-prepared at the URL they already have and seeked; the length-less transcode, which cannot be seeked, has StartTimeTicks rewritten into its existing URL so the user's audio-track selection survives and recovery needs no network round-trip. Only Remote sources qualify — a local file cannot fail from the network. A shared budget of consecutive attempts at the same position, refilled whenever playback progresses, stops an unreachable server from looping Playback UR-040, UR-004 Done DR-130 A backend's position and duration must survive the end of the file they describe. MPV exposes time-pos/duration as properties of the loaded file, so at EOF it unloads and both stop resolving — the accessors reported 0.0/unknown at exactly the moment end-of-file handling asks where playback reached, and any position-versus-runtime check would have read every natural end as a truncation. The poll thread records the last reading and the accessors fall back to it. Linux resilience is layered on the same principle that the stream, not the player, is what failed: MPV is configured with ffmpeg reconnection (stream-lavf-o, network-timeout) so ordinary blips never surface, and EndFile(ERROR) — previously a bare log, which left playback halted while the UI still showed \"playing\" — is emitted as a recoverable error. Because MpvBackend is constructed before PlayerController exists, it cannot decide in-process like the Android JNI callback: the frontend echoes the error into player_recover_stream, which keeps the decision in Rust (the same shape as PlaybackEnded → player_on_playback_ended). Android reports errors it has already declined as unrecoverable , so the echo never asks twice Playback UR-004, UR-040 Done DR-131 The offline mutation queue is drained. sync_queue had producers and no consumer: PlaybackReporter::queue_for_sync writes a row for every start/stop/mark-played that cannot reach the server, sync_mark_processing/_completed/_failed were registered commands with no callers, and no Rust task processed the table — so queued watch positions never reached Jellyfin and the offline banner's count only ever grew. A drain hangs off the same connectivity:reconnected transition as DR-120 (in Rust, because a drain started by a component dies with it) and replays rows oldest-first, so a stale start cannot move the server's resume position backwards after a later stop. update_progress replays as stopped at N rather than as progress — replaying a mid-playback report hours later would claim the item is still playing — and payloads are read in both dialects that exist in users' databases (position_ticks from Rust, camelCase positionMs from the frontend helper). A failed row stays queued for the next reconnect; after MAX_SYNC_ATTEMPTS it is abandoned and stops counting, because a row nothing can ever push is what turns the queue into a counter that only grows. An unreachable server is not counted as an attempt at all — the row goes back to pending untouched — so opening the app offline a few times cannot abandon good rows; only a server that answers and refuses spends the budget. The drain also runs once at startup, because a queue built in a previous session would otherwise sit untouched for a whole run whenever the server was reachable the entire time and no offline→online transition ever fired. Requires MediaRepository::mark_played (JA-035) — the previous stand-in reported a stop at i64::MAX Backend UR-025, UR-002 Done DR-132 The pending-sync count is answerable. The offline banner's badge read \"N pending sync(s)\" and led nowhere, so it was taken for pending transfers and looked for on the Downloads page — which lists the downloads table and structurally cannot show sync_queue rows. The badge becomes a button opening the queue it counts: each row's operation, the item's title (resolved by a LEFT JOIN items in sync_get_pending, not a per-row frontend fetch), when it was queued, and the error of anything failing, plus a \"Sync now\" that runs the DR-131 drain on demand. The same list is a Settings section, because a row that keeps failing is still queued when the server is reachable and no banner is on screen. The drain emits sync-queue-changed so the badge updates on reconnect instead of lagging by up to one 10s poll UI UR-025 Done DR-133 A downloaded file has exactly one on-disk path, and the row that names it is authoritative. downloads.file_path starts relative to the storage root, but the worker rewrites it to the absolute path it actually wrote when the transfer completes — so a completed row is already rooted. The video player's offline branch rooted it a second time, handing the asset protocol /data/user/0/app//data/user/0/app/videos/x.mp4; the webview reported MEDIA_ERR_SRC_NOT_SUPPORTED with NETWORK_NO_SOURCE, so every downloaded video failed to play while audio — which resolves the same column through Rust's resolve_local_media_path, without re-rooting — played fine. The join is absolute-aware (POSIX, Windows drive letters and UNC) so rows written before completion still resolve Playback UR-071 Done DR-134 The webview can actually fetch the local files it is handed. convertFileSrc rewrites a path to http://asset.localhost/… unconditionally, but Tauri only answers that origin when the protocol-asset cargo feature is compiled in and app.security.assetProtocol.enable is set — neither was, so every such URL reached a protocol with no handler and the webview reported NETWORK_NO_SOURCE. This silently defeated both offline video () and the cached-thumbnail path in imageCache, which fails soft to the server copy and so hid the breakage whenever the server was reachable. The scope was $APPDATA/** — the storage root under which the database, downloads/ and the thumbnail cache all live — rather than an unrestricted grant; DR-198 narrows it further to $APPDATA/thumbnails/**, since DR-137 moved downloaded media off this protocol and thumbnails are all it still serves Security UR-071 Done DR-140 An audio track is pinned only when the user picked one. Jellyfin's MediaStream.Index is global across every stream in a media source, so index 0 is the video stream on virtually all files — yet AudioStreamIndex=0 was sent as \"the first audio track\" on the HLS transcode URL, the background audio-only handoff URL, the direct-play fallback URL, and the PlaybackInfo negotiation body. A server that honours the request literally then transcodes the video stream into the audio slot and the result plays as a picture with no sound; only servers that silently correct the index hid the bug, which is why it presented as \"some videos have no audio\". The parameter is now omitted whenever no track has been chosen, so the server resolves the source's DefaultAudioStreamIndex; an explicit selection from player_switch_audio_track is still carried through unchanged. On the static=true direct-play URL it is dropped outright — the original file is served untouched, so the parameter could only mislead Playback UR-004, UR-040 Done DR-147 One search input per screen, and the URL is the search's single source of truth. The header bar rendered only under /library/** and merely navigated to /search (DR-063), so a desktop search handed the user to a screen whose input was a different element — the header box cleared itself and vanished, and the page's own box took over mid-word. That page then re-derived its input from ?q= against library.searchQuery on every store write, so the next keystroke re-ran the effect and snapped the text back to the query the header had sent (and a scope chip back to the URL's scope); entering from the bottom-nav Search tab skipped it only because the effect early-returned on an empty query. The bar now renders on /search too (showHeaderSearch) and is the sole md+ input — the page's own input is md:hidden — and on that route it republishes the query into the URL with replaceState, so a whole session of typing costs one history entry. The page consumes that URL once per distinct value (seedFromSearchUrl against a non-reactive applied marker) instead of continuously reconciling it, and the scope chips publish through the same URL so the bar and the chips cannot disagree. Landing on /search with a seeded query focuses the bar and puts the caret at the end, because the box the user was typing in belonged to the unmounted route UI UR-049, UR-054 Done DR-142 An episode has exactly one surface, and it is complete. Two divergent renderings existed: EpisodeFocusView (reached from Continue Watching, the series episode list, the TV landing page and Downloads — i.e. every real entry point) offered only Play and Favourite, while the bare /library/ page nobody routed to carried the download button, the series/season breadcrumbs and the cast section. Opening an episode the normal way therefore silently lost the ability to download it. The Focus View is now the single surface and carries the full §5B.2 composition — hero action row Play / Download / Favourite, series name and SxEy badge as links back to the series and to that season's anchor, then genres → cast → similar shows below the episode strip, never above it (DR-062). /library/ redirects into it (episodeRedirectTarget, the same rule seasons follow under DR-103), and an episode with no seriesId renders the same component series-less rather than falling back to a second, lesser page. The focused episode is fetched in full rather than reused from the season fan-out, because that is a list query and carries neither cast nor genres — the sections would have rendered empty. The strip hides itself when the episode has no siblings, a card that only shows the episode you are already on being noise UI UR-048, UR-058 Done DR-141 The device profile states how many channels the audio route can actually voice. MediaCodecList answers \"can this device decode 5.1\", which is not the question that decides whether the user hears anything — a phone decodes an AC-3 5.1 track happily and still has two channels to play it out of. With no MaxAudioChannels in the profile, Jellyfin was free to direct-play the multichannel track, and the result is device dependent: a failed AudioSink configuration (silence) or dialogue folded into surround channels that go nowhere. media3's AudioCapabilities.maxChannelCount for the current route is reported over JNI alongside the codec lists, and bounds both the direct-play profile and the transcoding profiles, so the server downmixes rather than shipping channels the sink cannot take. Codecs are never removed from the profile — a device with genuine surround output keeps direct-playing it. A missing or zero reading means \"route not yet established\", not \"no audio\", and falls back to stereo, the one capability every sink has Playback UR-004 Done DR-145 Video playback starts only once the app actually holds audio focus. Video manages focus by hand (handleAudioFocus=false, because ExoPlayer's automatic handling is reserved for the audio path), and the request's three outcomes were all treated as success: AUDIOFOCUS_REQUEST_DELAYED — which setAcceptsDelayedFocusGain(true) explicitly invites, and which means the system is withholding our audio until it calls back — and an outright REQUEST_FAILED were logged and then followed by playWhenReady = true. The picture rolled with no sound, indistinguishable to the user from a broken stream. Playback is now held when focus is not granted and started from the AUDIOFOCUS_GAIN callback; an explicit play() re-requests focus rather than resuming into a stream the system is still muting, guarded by a held-focus flag so repeated plays do not leak focus requests. A LOSS clears the pending flag, so an unrelated later GAIN cannot start playback the user never asked for Playback UR-004 Done DR-146 The no-audio-track fallback picks a track the renderer can actually play. When ExoPlayer selected no audio track, the recovery forced group 0 / track 0 unconditionally — but the most likely reason nothing was selected is that this very track cannot be decoded on this device, so the override reinstated the silence it was meant to fix. It now scans the groups for the first isTrackSupported track and overrides to that, and clears setTrackTypeDisabled(TRACK_TYPE_AUDIO) because audio may equally have been off at the type level, which an override alone does not undo. When no group holds a supported track the condition is logged as an error — the server was expected to transcode — rather than leaving a silent video with no explanation in the log Playback UR-004 Done DR-148 The video direct-play profile advertises only what the webview can decode. The audio codec list comes from MediaCodecList, which describes ExoPlayer — but video does not play through ExoPlayer on either platform: Android force-renders every video in the webview element (the interim override in VideoPlayer.svelte, because the native SurfaceView sits behind an opaque webview) and Linux always has. Chromium and WebKit decode a far narrower set than the platform does, and the gap is widest on devices whose vendor licenses Dolby: a phone shipping /vendor/etc/media_codecs_dolby_audio.xml reports ac3,eac3, so Jellyfin direct-played an E-AC-3 track with static=true and the webview built a video decoder and no audio decoder at all — full picture, no sound. The defect is triggered by capability , not the lack of it, which is why it reproduced on one Motorola while a Fairphone and an Honor tablet played the same file on the same build: a device without the Dolby decoder never claims the codec, so the server transcodes to AAC and it plays. video_audio_codecs narrows the platform list to the webview-decodable set (aac,mp3,opus,vorbis,flac) for the video direct-play profile only — the audio-only profile keeps the full list, since that playback really is the native player's and narrowing it would transcode music that plays perfectly well. A list with nothing decodable still claims aac rather than going out empty, because a profile that claims nothing invites the server to give up instead of transcoding. The video codec list is deliberately untouched: HEVC direct-plays through the webview correctly, so the constraint is specific to audio Playback UR-004 Done DR-149 The client decides whether its own renderer can decode the audio, rather than trusting the server's negotiation. Advertising a webview-shaped profile (DR-148) turned out to be necessary but not sufficient: Jellyfin 10.11.5 enforces a DirectPlayProfile's Container and VideoCodec — excluding either returns SupportsDirectPlay: false with TranscodeReasons=ContainerNotSupported / VideoCodecNotSupported — but ignores its AudioCodec , offering an E-AC-3 track for direct play against a profile listing only aac,flac,mp3,opus,vorbis. Neither a VideoAudio CodecProfile forbidding the codec nor a MaxAudioChannels: 2 against a 6-channel track changes the answer, so no profile the client can send fixes it and the picture plays silent. The negotiated source's audio is therefore checked locally against what the webview decodes, and an undecodable track forces the existing h264/aac HLS transcode URL regardless of the server saying direct play is fine — direct_play and needs_transcoding are corrected to match, so the frontend and the reporting path agree with the URL actually used. The track judged is the one the server would serve: the default, or the first when nothing is marked default, since a supported track further down the list is not the one that plays. A source with no audio streams, or a stream whose codec the server did not name, is left alone — forcing a transcode on a guess spends server CPU on files that already play Playback UR-004 Done DR-150 Android video renders on the native ExoPlayer surface behind a transparent WebView, behind the experimentalNativeVideo opt-in. Rust already reported use_html5_element: false on Android, but two frontend overrides discarded it — createAdapter() hardcoded \"html5\", and VideoPlayer.svelte forced useHtml5Element = true and stopped the native backend player_play_item had just started. The flag is a suppressor, never a promoter : off forces HTML5 even where Rust says native, so an in-progress spike cannot ship as the default, but it can never select native where Rust reported HTML5 (Linux cannot composite behind WebKitGTK, so promoting there is a black screen). Compositing requires clearing two independent opaque layers, and clearing only one leaves audio over a black picture — the WebView widget background and window drawable from Kotlin (AndroidVideoSurface.setTransparent), and the page's html/body and app-shell background from CSS (data-native-video). Transparency is declared in tauri.android.conf.json rather than the base config, because a transparent window on Linux has nothing behind it, and is toggled per playback session rather than set once, because a permanently transparent window shows the launcher through the rest of the app Playback UR-003, UR-004 Done (behind experimentalNativeVideo, default on since DR-194/DR-196) DR-151 The player's video SurfaceView actually reaches the view hierarchy. JellyTauPlayer.setActivity() had zero callers, so currentActivity was always null and autoAttachSurface() returned at \"Cannot attach surface - no Activity reference\". The surface was created and handed to ExoPlayer but never added to the content view, so native video decoded to a surface that was never on screen — independent of any webview transparency. MainActivity.onCreate now supplies the reference, which also revives PiP on the video path: canEnterPip() gates on isVideoSurfaceAttached(), which had been permanently false Playback UR-003, UR-041 Done DR-152 Platform playback facilities are reported by Rust, not sniffed from the user agent. webviewAudio.ts re-derived \"does this platform have a native audio backend\" by matching navigator.userAgent against android/linux — a second copy of the cfg! gate the backends are compiled under, free to drift from it. player_get_capabilities now returns usesWebviewAudio and supportsNativeVideo from the same cfg gates, and the frontend consumes them; the settings toggle for native video is hidden entirely where the platform cannot support it Player UR-003, UR-005 Done DR-153 The git tag is the single source of truth for a release version. The version lived in four files (package.json, tauri.conf.json, Cargo.toml, Cargo.lock) that had to be hand-edited in lockstep, and CI's release job rewrote exactly one of them — so a tagged build produced an installer named for the tag wrapped around package metadata naming the previous release, while the Linux job had no version step at all and shipped whatever was committed. scripts/set-version.sh writes all four from one argument and is the only thing that does; every release job calls it with the tag. The Android versionCode is derived in the same place as 1000 + major*10000 + minor*100 + patch, which is monotonic in semver order and clears the 1000 floor already installed in the field — a lower code than the installed one makes Android refuse the update. A prerelease suffix is stripped before that arithmetic, which would otherwise abort the script, and a non-tag ref (CI passes ${GITHUB_REF#refs/tags/} unconditionally) falls back to git describe rather than failing a branch build Build - Done DR-154 A watch position that cannot reach the server is queued, not dropped. sync_queue and its drain (DR-131) were built, tested and running, but the stop-report path never fed them: HybridRepository::report_playback_stopped is a bare pass-through to the online repository (\"Playback reporting goes directly to server\"), and on failure the error surfaced to a frontend catch whose own comment read \"Server error - could queue, but for now just log\". Both producers that would have queued it — PlaybackReporter::queue_for_sync in Rust and syncService.queuePlaybackProgress on the frontend — have no callers on the playback path, so closing a video while the server was unreachable lost the resume point outright even though user_data.pending_sync was dutifully set to 1 and nothing ever drains that flag for positions (unlike favourites, DR-120). The command layer now enqueues a report_playback_stopped row whenever the push fails, which the existing drain already knows how to parse and replay. The pending row for an item is superseded in place rather than appended to: progress is reported every 10s, so a server that stays down would otherwise add a row per tick, all of them obsoleted by the newest — the unbounded queue DR-131 exists to prevent. Only pending/failed rows are superseded, because an abandoned row has been given up on and reviving it would restore that same growing counter. Queueing is best-effort and never fails the command: the local position is already saved, so a failed queue write must not be reported as a lost position Backend UR-025, UR-002 Done DR-155 A watch position set on another device reaches this one. The resume check reads the local user_data row and nothing else, but mirror_user_data — the only path by which server UserData lands in that table — mirrored is_favorite alone, and returned early whenever that field was absent, which is exactly the shape of an ordinary watched episode. So playback_position_ticks was write-only from this device's perspective: watch 40 minutes in a browser, open JellyTau, and it resumed from whatever this device last saw or offered no resume at all — the same user-visible symptom as DR-150's Android bug, from an unrelated cause, which is why resume read as broadly flaky. The mirror now carries the position alongside the favourite flag under the same pending_sync = 0 conflict rule, so a local position still waiting to be pushed is never pulled backwards by a server that has not yet heard where we got to; COALESCE(excluded.x, user_data.x) means a field the server omitted keeps its stored value rather than being nulled, and a row with neither field is still skipped rather than fabricated as zeroes. Mirroring alone was not sufficient: get_item — the call the player route makes — returned the cached copy on a hit and never consulted the server, so for an already-cached item the mirror never ran. It now refreshes in the background on a cache hit (race_with_refresh, the reusable form of what get_items already did inline), which is why browsing a season picked up other devices' state while opening the episode directly did not. The refreshed value lands for the next read, the cache-first race still answering immediately Backend UR-025, UR-002 Done DR-156 A page no longer inherits the previous page's scroll position. The shell keeps its scrollers alive across navigation by design — the root layout, the home page and the library layout each own a flex-1 overflow-y-auto box that outlives the route inside it, which is what lets BottomUi be a flex sibling rather than a measured overlay — but the element therefore never remounts and its scrollTop survives the route change. SvelteKit's own scroll restoration could not help: it saves and restores window scroll, and in this app the window never scrolls at all, so there was no scroll handling of any kind. The symptom was that opening an item from half-way down a library grid dropped the viewer half-way down the detail page, and returning to the grid landed at the top of it — exactly backwards. ScrollMemory (pure, one instance per container, keyed on path + query so a genre-filtered grid keeps its own place) records the offset a route is left at in beforeNavigate and decides in afterNavigate: link/goto/form reset to the top, popstate restores that route's saved offset, and the initial enter is left alone. Deciding does not consume the offset, so a route returned to more than once restores each time. Applied via the scrollContainer action on all three scrollers UI UR-072 Done DR-160 Picture-in-picture works on the path that actually plays video. PiP shrinks the whole Activity , so canEnterPip demanded a native ExoPlayer SurfaceView be attached and rendering — isPlayingVideo() && getSurfaceView() != null && isVideoSurfaceAttached(). But the native path sits behind experimentalNativeVideo, which defaulted to off , so in the shipping configuration video played in the WebView's element and all three conditions were false. enterPip bailed with \"Not entering PiP: no local video playing\" every single time: the button was offered (gated only on OS capability) and could not work, however it was pressed. The manager now accepts either surface. The frontend reports the element through AndroidPictureInPicture.setHtml5VideoState(active, width, height, playing) — intrinsic size because the PiP window's aspect ratio came from the letterboxed surface's measured bounds, which do not exist here, and play state because ExoPlayer.isPlaying is false on this path and the PiP play/pause action would be frozen on \"Play\" mid-playback. Two behaviours invert when the WebView is the video: it must stay visible in PiP rather than be hidden (hideWebView is now gated on the native path — hiding it would leave an empty black window), and the play/pause RemoteAction has to reach the element, so the receiver dispatches jellytau-pip-play/jellytau-pip-pause DOM events instead of driving ExoPlayer. jellytau-pip-entered/-exited let the player strip its own chrome, since controls, title and gradients would otherwise be rendered into a window a couple of inches wide. The is deregistered on teardown so PiP is never offered over a video that has gone UI UR-041 Done (pending device verification) DR-167 Each downloaded library shows only its own media. Cached items carry no link back to their library — library_id and parent_id are NULL on every row ([[offline-libraries-never-cached]]) — so get_downloaded_items matched the library branch with EXISTS (SELECT 1 FROM libraries l WHERE l.id = ?), which asserts only that the requested library exists and never constrains the item to it. Opening any downloaded library therefore listed every downloaded top-level item on the server: films under Music, albums under TV. The sibling query that decides which libraries appear already carried the right rule — a collection_type ↔ item_type mapping — so the two disagreed about the same question. That mapping is now the named constant LIBRARY_HOLDS_ITEM, used by both, and a library of unknown collection type still keeps everything rather than being emptied by a rule that cannot classify it. The taxonomy stays in Rust, never the frontend Downloads UR-055 Done DR-168 Pause and resume actually stop and restart the bytes. pause_download wrote status = 'paused' and did nothing else, and no cancellation existed anywhere in the download stack — no token, no flag, no abort — so the streaming task ran on, kept writing, and overwrote the row with completed/failed when it finished: the row flicked to \"paused\" and undid itself. resume_download had the mirror defect, flipping the row to pending without calling pump_download_queue; the pump runs when something calls it rather than polling, so a resumed download sat untouched until an unrelated event happened to pump the queue. A per-download stop flag (download::stop) is the missing half — a module-level registry because the two sides never meet, the command holding Tauri state and the worker running detached in async_runtime::spawn. The worker reads it between chunks and on retry (so a pause is not swallowed by a 45-second backoff), flushes, and returns Stopped, which is deliberately not retryable and not recorded as a failure: the .part file is left intact because that is exactly what the resume's Range request continues from. Registering returns a fresh flag, or a resumed download would inherit the pause that stopped it and halt instantly. Cancel and clear_stale_downloads signal it too, so neither deletes a file still being written Downloads UR-055 Done DR-169 Partial files are actually reaped. The worker named its sidecar with Path::with_extension(\"part\"), which replaces the extension — movie.mp4 became movie.part — while every cleanup path deleted \"{file_path}.part\", i.e. movie.mp4.part. The two never matched, so the partial file of every cancelled or failed download stayed on disk indefinitely, invisible to the disk-usage totals because no downloads row pointed at it. partial_path appends instead, is the single definition both the writer and the cleaners use, and incidentally removes a collision the old form had, where movie.mp4 and movie.mkv mapped to one movie.part Downloads UR-055 Done DR-173 Downloading an album queues the whole album, and every track it queued is findable offline afterwards. Two independent gaps left an album with a handful of its tracks on the device while the button reported the album as downloaded. First, download_album took its track list from items WHERE album_id = ? — the local catalog cache. Jellyfin does not return AlbumId on every listing endpoint, so tracks cached by one of those endpoints sit in items with a NULL album_id and are invisible to that query; on the reporter's database three whole albums (18, 12 and 9 tracks) had it NULL on every track, so \"download album\" would have queued nothing for them, and a partially-linked album queued only the linked subset. Second, the frontend then resolved one stream URL per track from its own list and paired it with the returned row ids by position — a pairing with no basis, since the ids came back in the backend's index_number order over a different set of rows, so a row could be handed another track's URL and any track past the end of the shorter list was never started at all; on Android that loop also stopped wherever the webview was suspended. The same album_id is what OfflineRepository::get_items joins a track to its album on, so a track that did download stayed invisible under its album offline — the two halves of the same missing link. The operation now belongs to Rust end to end: HybridRepository::get_album_tracks asks the server what the album contains (cache-first get_items is right for browsing and wrong for deciding what to download) and errors offline so the caller falls back to the ungated local catalog, keeping the queue-while-offline flow; queue_album_tracks writes the album link onto every track it queues — queuing a track is the statement that it belongs to the album, rather than something to hope a listing endpoint recorded — and the stream URLs are resolved here through the existing reconnect resolver, now scoped to the rows just queued so one album cannot start every unrelated pending row. Nothing crosses the IPC boundary but the album id. Re-queuing a broken album heals it: the missing tracks are added and the tracks already on disk get their link. download_series/download_season still derive their episode lists from the cache the same way and want the same treatment Downloads UR-018, UR-055 Done DR-170 Downloads at a chosen bitrate are no longer corrupted by their own retries. Only the original preset asks for Static=true; every other rung requests a transcode , which Jellyfin serves chunked, with no Content-Length, and cannot byte-seek — so it ignores Range and answers 200 with the whole stream from the beginning rather than 206 with the requested tail. The worker sent the Range header whenever a .part existed and appended the body unconditionally, so each retry and each resume concatenated a fresh copy of the entire transcode onto the bytes already on disk: the file grew past its real size and would not play, which is why \"downloads for different bitrates\" stayed broken after the videoBitRate casing fix (DR-adc460f3) corrected the request . resume_offset makes the response decide — append only on a 206, otherwise truncate and take the stream from the top — and the total size is computed from that offset rather than from a partial length the server never agreed to Downloads UR-071 Done DR-172 Native Android video is opt-in again, because as a default it shipped as audio with no picture . DR-161 flipped experimentalNativeVideo on so picture-in-picture could shrink a real video surface; on a device that produced sound and a blank screen. The decode path was never the problem — logcat showed ExoPlayer running (Position update ticks) and feeding a live SurfaceView with an active BufferQueue. The compositing was: the SurfaceView sits behind the WebView, and the step that clears the opaque layers above it never took effect, with WebView transparent = false logged and = true never appearing. So the video rendered correctly the whole time, behind an opaque page. This is exactly the defect the flag existed to contain — VideoPlayer.scrubRegression.test.ts had recorded that \"the native SurfaceView has never been visible through the webview\" — and enabling it by default shipped a verified decode path on top of an unverified display path. Reverting costs nothing that matters: PiP does not depend on it (DR-160 drives PiP from the WebView ), and working video outranks PiP showing a native surface. The flag stays available in Settings, now described as incomplete rather than as a performance win, and the scrub-regression mocks that were made explicit under DR-161 are kept explicit so those tests state which path they guard rather than inheriting a default that has now moved twice. Fixing the compositing is the prerequisite for trying this default again UI UR-003, UR-004, UR-041 Done DR-171 A downloaded video keeps audio the device can actually decode. original quality asked for Static=true, which hands back the source file byte-for-byte — E-AC-3/AC-3/DTS/TrueHD track included — and video is rendered on both platforms by the webview element, which decodes none of them. Streaming already knew this: DR-149 judges the track the server would serve against WEBVIEW_AUDIO_CODECS and forces a transcode over Jellyfin's own direct-play offer, because 10.11.5 honours a DirectPlayProfile's container and video codec but ignores its audio codec. The download path never consulted that policy, so the same film had sound when streamed and played as picture in silence once downloaded — and offline a download is the only source a video has, so there was no working path left to fall back to. The rule is now one rule: served_audio_codec picks the track the server will serve (the default, or the first when none is marked) and both callers judge it, the streaming verdict staying a bool and the download path needing the codec itself so it can say what to re-encode. Only the audio is re-encoded — allowVideoStreamCopy=true keeps an h264 source's picture byte-for-byte and no bitrate or resolution cap is added, so original still means original quality; a source the webview could not have rendered anyway (HEVC) becomes h264 as a side effect, which is the only form of it that would have played. The decision is per item rather than blanket because the transcode costs the byte-range resumability Static=true gives the download worker (see DR-170 for what a chunked, length-less response does to a resume), so a file whose audio already plays keeps the direct copy. An unknown codec — item not fetchable, or the server named none — changes nothing: the policy only ever adds a transcode, so it cannot make a working download worse. The codec set judged against is the webview's , not the platform's, even though DR-161 made ExoPlayer the Android default: experimentalNativeVideo is a user setting, a downloaded file outlives whatever it was set to when the file arrived, and the narrow list is the only one that holds on both sides of it — at the cost of a Dolby-licensed device re-encoding a track its ExoPlayer could have played. resolve_video_download_url is the single entrance for all three resolution sites (the frontend's per-item command, the bulk series/season enqueue, and the offline-queued resume), since the pure builder cannot look a codec up and a caller that forgets to is exactly how the silent downloads shipped. Files already downloaded stay silent — the bytes on disk are the wrong bytes and only a re-download replaces them Downloads UR-071, UR-004 Done DR-162 Video streams are opened against a bandwidth ceiling the user chose , instead of a fixed allowance nobody could change. Every video URL carried MaxStreamingBitrate=20000000/VideoBitrate=18000000, PlaybackInfo negotiated at 20 Mbps, and the device profile advertised 999999999 — so on a metered or slow connection the only lever was not watching. StreamingQuality is a ladder of ceilings (Original, 20/10/8/4/2/1 Mbps, 720 kbps) in which a step is not a label but a bundle of transcode parameters: the total ceiling, the audio share of it, and the resolution that budget can carry. It lives in Rust because those numbers are Jellyfin encoding vocabulary — the frontend names a variant and reads labels back over player_get_streaming_qualities, the same arrangement as the EQ preset curves. The video bitrate is the total minus the audio share, so the two together honour the cap rather than overshooting it by the size of the audio track, and MaxHeight falls with the ladder so a small budget is not spent on pixels it cannot afford. The cap has to reach the negotiation , not only the transcode URL: max_static_bitrate in the device profile is what makes the server refuse to direct-play a source fatter than the ceiling, and without it a 30 Mbps remux is handed over untouched and every URL parameter downstream is moot — which is why it is applied at all four places that decide bandwidth (the HLS builder, PlaybackInfo, open_live_stream, and the background-audio handoff, which takes the lower of the cap and its own 384 kbps). The ceiling is process-wide rather than a field on OnlineRepository, mirroring INCLUDE_CATALOG_BROWSE: it is a preference about this device's connection , it must survive a repository rebuilt on re-login, and every builder plus the negotiation have to agree on it or the cap leaks. Settings owns the durable default and is the only writer to app_settings — persisted unlike the rest of VideoSettings, because a limit set for a metered connection that silently reverts to uncapped on the next launch spends the user's data with no changed setting to show for it — and it is restored at startup from the async runtime, defaulting to uncapped if the read fails so a database problem degrades to the old behaviour rather than to an arbitrary limit. The in-player menu is the per-video override: a cap is a property of the stream the server is producing, so it cannot be applied to one already in flight — player_set_stream_quality re-opens the stream at the new quality and resumes at the current position, reloading a native backend itself and handing HTML5 a URL for the same reloadSource primitive the audio-track switch uses, so no strategy branch lives in the UI. It deliberately does not persist. This gives UR-070 its resume-at-the-same-point mechanism; the server-offered per-item rendition list that requirement also asks for remains proposed Playback UR-074, UR-070 Done DR-174 Tiles of mixed shapes are laid out justified rather than gridded. A CSS grid gives every cell one box, so on a page holding square music covers, 16:9 library backdrops and 2:3 posters at once, everything that is not the chosen shape is cropped to it — the home shortcut strip was explicitly forcing aspect=\"video\" on music libraries for exactly this reason, which lined the row up by cutting the covers down. layoutMosaic packs tiles into rows of a shared height and gives each its own width from its own aspect ratio: it adds tiles to a row until the height needed to fill the container has fallen to the target, closes the row there (so rows land at or below the target, never above), and justifies the row to the container width by absorbing the rounding remainder into its widest tile, where a pixel is least visible. The last row is deliberately not justified — with one tile left over, filling the width would inflate it to a banner — so it sits at the target height, left-aligned. Ratios are clamped to a band, which costs a crop on genuine outliers and stops one panorama owning a row or one very tall image shrinking to a sliver. It is a pure module with no DOM: the component supplies only the two things the DOM knows — the measured container width, and the artwork's decoded aspect ratio, reported by CachedImage so the layout uses the shape an image actually has rather than the one its item type implies. Those measurements are committed in one debounced batch rather than per image, because artwork arrives over several hundred milliseconds and re-packing on each arrival would shuffle the grid under the pointer repeatedly. Labels are drawn over the bottom of each tile rather than beneath it: a caption below sits outside the computed box, and one that wraps to two lines would break the row alignment the layout exists to provide UI UR-075 Done DR-175 A library knows which favourites category it belongs to, and the frontend does not work it out. The mosaic offers a favourites tile per category beside its library, which needs a collection-type → category answer; deriving it in Svelte would have re-created the exact leak SearchScope::item_types was extracted to close (docs/specs/scoped-search-boundary.md) — one table of Jellyfin vocabulary, differing only in which vocabulary. SearchScope::for_collection_type maps movies/tvshows/music and returns None for everything else, so a Live TV or books library gets no tile at all rather than one opening an unfiltered list; All is never derived from a library, being the cross-library entry offered beside them rather than a property of one. Library::new stamps the result onto every library at construction — a constructor rather than a struct literal precisely so a derived field cannot be forgotten at one of the four sites — and it rides to the frontend as an optional favoritesScope, absent rather than null when there is none. The UI's remaining share is presentation only: what to call the tile, where to put it, and showing a category's tile once however many libraries share it, since two movie libraries have one favourites list between them UI UR-075, UR-067 Done DR-176 The server is never asked to burn a subtitle into the picture. PlaybackInfo omitted SubtitleStreamIndex, which does not mean \"none\" — the server then honours the source's default/forced flag and picks a track itself. On a source whose default subtitle is image-based (PGS/DVD/DVB) that track cannot go out as a sidecar, so the server falls back to SubtitleMethod=Encode and composites it into the video. The cost lands on the video , not the subtitle: burn-in rules out remuxing, so an HEVC stream the device could have taken untouched is re-encoded frame by frame. Observed on an HEVC + E-AC-3 + PGSSUB episode, where only the audio actually needed transcoding: the server could not sustain the re-encode in real time, the buffer never grew past a single segment, and playback stalled every few seconds — taking seeking with it, since each seek restarted the encoder and cost seconds before the first frame. The fix is to request SubtitleStreamIndex=-1 explicitly and to advertise every text format we can render (srt/subrip/ass/ssa/vtt) as External, so a subtitle can only ever arrive as a sidecar. Nothing is lost, because the app already fetches subtitle tracks itself and draws them over the video (UR-020) — the server's composited copy was always redundant. Image-based tracks are consequently not offered, which is honest rather than a regression: the renderer cannot composite a bitmap, and the previous behaviour paid for them by making the whole stream unwatchable. Both halves of that hold at the layer that can enforce them. The sentinel travels on the stream URL as well as in the negotiation, because the negotiation is not what opens most streams — a quality switch, a transcoded seek and an audio-track switch each rebuild the URL on their own, and an omitted index there lets the server pick the default track back up out of whatever session state it still holds. And \"not offered\" is enforced where the offer is made: each subtitle stream crosses the boundary carrying the backend's verdict on whether it can arrive as a sidecar, so the picker lists only tracks the app can draw instead of showing an entry that ticks and displays nothing. Only an explicit \"no\" hides a track, so a stream carrying no verdict behaves as before Playback UR-020, UR-004 Done DR-177 Each video transcode this device opens is its own server-side job, and the one it replaces is stopped. Jellyfin keys a transcode job by device and play session, and every stream URL the app built carried the same hardcoded DeviceId with no PlaySessionId at all — so the second stream for an item was indistinguishable from the first. Re-opening a stream is not rare: a mid-playback quality switch (UR-074), a transcoded seek and an audio-track switch all do it, each leaving the previous ffmpeg running. Observed on-device when switching bitrate mid-film: the server served the new playlist, then rejected the new job's segments with 400 hls1/main/0.ts while the two jobs contended for one transcode path, and playback stalled — reproducible against the server, where a second stream for a live job's item alternates between serving bytes and 400ing per attempt, which is what made it read as flaky rather than broken. begin_video_play_session mints a session id per open and reports the one it supersedes; the URL builder stops that job (DELETE /Videos/ActiveEncodings, un-retried and best-effort — a slow stop must not delay playback, and the new stream no longer collides either way) before returning. Placing it in the URL builder rather than in each caller means every re-open path is covered by construction. Two client faults made the same incident worse and are fixed with it: the fatal-HLS-error handler added the transcode seek offset to a position that already included it, so past roughly the halfway mark of a film any transient network error cleared the \"near end\" threshold and was reported as end-of-stream — turning a recoverable stall into a skip to the next item, exactly when a quality switch had just made the offset large; and the HTML5 reload primitive resolved on its own canplay timeout, so a reload the server never served reported success, leaving the picker showing a quality that was not playing and the caller with nothing to revert Playback UR-074, UR-004 Done DR-178 Every position that leaves the app is read from the controller, not from a backend that may not be playing anything. PlayerController::position() forwards to the native backend, which is authoritative for exactly one of the three ways this app renders media. On the webview path — the shipping default for video on both platforms — nothing is loaded into that backend at all: the element is the player, its ticks were re-emitted to the frontend and then dropped, and the backend answered 0 forever. During a background-audio handoff the base that converts the stream's relative timeline to the episode's is applied once at the native tick boundary (DR-159), so before ExoPlayer's first tick nothing has applied it and the reading is 0 there too. Both holes surfaced as the same user-visible bug through different doors: returning to the foreground while the audio-only transcode was still opening handed the frontend 0.0, and the video reloaded at StartTimeTicks=0 — the episode restarting from the beginning — while the Stopped report that followed wrote that zero to Jellyfin as the resume point. absolute_position() answers for all three paths: the maximum of the backend's reading, the last position webview-rendered media reported, and the handoff base. The maximum is exact rather than a heuristic, because at most one term is ever meaningful at a time and the base is a floor the stream cannot physically be behind. duration() gains the same fallback for the same reason. The element's reading is cleared wherever it stops being the player — teardown, a handoff taking over, a different item loading — so it can never be attributed to what plays next Player UR-005, UR-025, UR-040 Done (pending device verification) DR-179 Jellyfin is told what was played: progress while it plays, and a stop when it ends. A device trace of 35 minutes' playback requested /Sessions/Playing/Progress zero times and sent 14 Stopped reports, every one of them at position 0. Three faults, one subject. Progress never left the device : the frontend service writes it to the local DB by design, and nothing on the Rust side reported it for webview-rendered media — so the server learned a position only when the player was closed, and a crash or a swipe-away cost the session. It is now reported from the controller's own position ticks, through the 30s throttler it already owned and shares with the native audio path, which covers all three rendering paths in one place instead of adding a second frequent IPC caller. Zero-position stops were sent : Jellyfin stores the reported position as the resume point, so a zero does not merely fail to inform, it instructs the server to forget — and no zero was ever real, each one coming from asking a player that was not rendering the media (see DR-178). They are withheld; one landed 40s after the frontend had correctly reported 15:22 for the same episode, overwriting it. A finished episode reported nothing at all : Jellyfin decides \"watched\" from the stop report and its percentage, and in background audio-only mode nobody sends one — the webview is suspended and its element was torn down at the handoff, while the backend advances to the next episode without a word about the one that ended, so an episode listened to end-to-end on the lockscreen never counted as watched. on_playback_ended now reports it stopped at its runtime (not the last tick, which can be seconds short or, on a handoff whose ticks stopped early, nowhere near the end) before any advance, since after one the queue's current item is the next episode. Scoped to the audio-only handoff, the case the frontend provably cannot cover, so foreground playback keeps its single existing report; music ending natively remains unreported and wants its own change. The reporting seam is a PlaybackReportSink the controller sends to, which also collapses three copies of the spawn-a-task-and-hope block into one and is what let all of this be written as failing tests rather than found on a device a second time Player UR-025, UR-005, UR-040 Done (pending device verification) DR-180 A background-audio handoff of a downloaded episode starts where the video left off. The handoff prefers a local file over the audio-only stream (DR-128), but the two begin in different places and were treated alike: a stream is built with StartTimeTicks, so the server makes the handoff point that stream's zero and the base is the handoff position with no seek — while a file has no such parameter and begins at the episode's own zero, so basing it at the handoff position claimed minutes of audio that were about to play from the beginning. Backgrounding a downloaded episode therefore restarted it while the lockscreen scrubber, dutifully adding the base, showed the position it should have been at. background_audio_plan splits the two: a file gets no base and a real seek, a stream keeps the base and no seek (seeking one would skip past the content by the handoff position again). The same distinction settles an inbound seek — seek_absolute re-opens a streamed handoff at the requested position because a chunked length-less transcode cannot honour a seek, which is not true of local media, and resume_stream_at refuses a non-remote source outright, so routing a lockscreen scrub of a downloaded episode through it failed the seek rather than performing it Player UR-040, UR-071 Done (pending device verification) DR-181 A resumed transcode plays. Every video stream URL carried the resume position as StartTimeTicks, which is correct for a progressive response and fatal for an HLS one: Jellyfin builds each segment URI by echoing the master playlist's query string into it, and its segment handler opens by rejecting any request carrying StartTimeTicks > 0 (ArgumentException → 400). One position on the playlist therefore 400s every hls1/main/N.ts behind it, so hls.js exhausted its retries and gave up — presenting as an episode that will not resume while the same episode from the beginning is fine, the > 0 being exactly why the beginning survived. The parameter is also unnecessary there: a playlist spans the whole item and asking for segment N is the seek, which the server transcodes from. So it is removed from the URL builder entirely rather than conditionalised — the builder has one caller shape and no way to know whether the response will be segmented — and the position becomes what it always was for HLS, a seek issued once the player has loaded: the seek path reloads at zero and seeks the element, and the resume path lets the player seek itself. The progressive /Audio/universal builder used by the background-audio handoff is a different endpoint with no segments and keeps its StartTimeTicks, which is why an audio-only handoff resumes correctly and a video one did not Playback UR-004, UR-074 Done DR-182 Native video shows a picture. The poster/title card is an opaque bg-black overlay drawn over the whole video area while isMediaReady is false, and every signal that clears it is emitted by the HTML5 element — canplay, loadedmetadata, hls.js FRAG_BUFFERED, the playing event, and two readyState timeouts. The native path renders no such element ({#if !!useHtml5Element}), so on Android nothing could ever clear it: ExoPlayer decoded to a live SurfaceView behind a black div for the entire session. That is DR-172's \"audio with no picture\" report, and it is indistinguishable on screen from the compositing failure DR-172 attributed it to — which is why the flag was reverted rather than fixed. Both the overlay and the native branch date from the original POC commit, so the native path has never been able to reveal itself; the 2026-08-11 device verification predates neither and does not contradict this, since a spike run that never reached a steady state would not have shown it. The backend's own events are the equivalent signals and nativeSignalRevealsVideo is the rule for reading them: state === \"playing\" mirrors the element's playing event, and a position tick carrying a real position or duration mirrors the readyState backstops, covering a first state event that is dropped or arrives before the listener is attached. buffering/paused/stopped/error deliberately do not qualify — revealing on error would replace the title card with a transparent hole showing the launcher through the app. The rule is a pure module rather than a branch inside the component because the decision that was missing is exactly the part worth guarding, and the component needs a DOM and a mounted player to exercise UI UR-003, UR-004, UR-041 Done DR-183 The JavaScript bridges are installed before the page that uses them loads. WebView binds an injected object into JS at page-load time : an addJavascriptInterface call landing after the page has loaded does not appear to that page. They were installed from configureWebViewForMedia, which finds the WebView by walking the view tree 500 ms after onCreate — a race against Tauri's own page load, and one that is permanent when lost, because the identity guard added for DR-097's stale-proxy bug then declines to re-inject on every later resume pass. The whole set (AndroidVideoSurface, AndroidPictureInPicture, AndroidBackgroundAudio, AndroidNetworkType, AndroidImmersive, AndroidInsets) would simply be absent from window, and silently: every call site optional-chains the bridge, so a missing one is a no-op rather than an error. This is a candidate explanation for DR-172's other piece of evidence — WebView transparent = false logged, = true never appearing, i.e. the enable call never reaching Kotlin at all. WryActivity.setWebView() calls the onWebViewCreate hook immediately before wry issues the first loadUrl (confirmed in wry 0.55's main_pipe.rs, where the setWebView JNI call precedes load_url), so a bridge installed there is bound by the time any page runs. The hook can fire during super.onCreate(), before the rest of our own onCreate, so only work needing nothing but the WebView moves into it — insets stay in configureWebViewForMedia, which runs later and on every resume. The tree-walk path is kept as a fallback, and enableNativeVideoCompositing now logs an explicit error when the bridge is missing, so the ambiguity that left DR-172 unresolved cannot recur silently Android UR-003, UR-004, UR-040, UR-041 Done DR-184 The video SurfaceView leaves the view hierarchy when the video does. VideoOverlayManager.detachVideoSurface had no callers anywhere in the tree — the mirror of the DR-151 defect, where setActivity had none — so attachVideoSurface was one-way: JellyTauPlayer.clearVideoSurface() dropped its surfaceView reference and cleared ExoPlayer's without removing the view, leaving it parented to the content view for the life of the process, with the next native video adding another SurfaceView beneath it. The stack was invisible while the WebView was opaque, which is why it went unnoticed. Two consequences outlive the leak: isVideoSurfaceAttached() gates PictureInPictureManager.canEnterPip through isNativeVideoPath(), so it reported an attached surface forever after the first native video (saved from offering PiP over nothing only by the isPlayingVideo() check beside it), and every abandoned surface held its OnLayoutChangeListener on the content view. Detach is called from clearVideoSurface, which covers stop, the switch to audio, and the background-audio handoff, and always runs on the main thread because every caller is already inside a mainHandler.post. It removes the view from its own parent rather than looking the content view up from an Activity reference, so an Activity recreated underneath it cannot strand the view Android UR-003, UR-041 Done DR-185 The app shell stops painting over the video surface. app.css clears the page's opaque layers for native video through three selectors, and one of them — html[data-native-video=\"active\"] [data-app-shell] — was written against an attribute no component has ever set, in any commit . The shell is +layout.svelte's root div, which paints --color-background across the entire viewport; VideoPlayer is fixed inset-0 z-50 and correctly makes itself transparent on the native path, but it stacks above the shell, so the WebView still composited the shell's opaque background over the whole screen and the SurfaceView behind it could never be seen. This is the missing half of the compositing DR-172 went looking for: the spec's own layer table lists this layer as \"cleared by data-native-video → app.css\", which was written but never wired, and html/body being genuinely transparent made the CSS look correct in isolation. The failure is invisible three ways over — the CSS is valid, the selector is plausible, and a rule matching nothing looks exactly like a rule matching something already transparent — while the symptom (black screen, audio fine) is identical to a real compositing failure, which is how it survived DR-150 through DR-172. Fixed by setting the attribute the rule was written for, and guarded by asserting the relationship rather than the rule: every attribute the compositing block targets must be set somewhere in the app, so a selector aimed at nothing fails the suite instead of failing silently on a device UI UR-003, UR-004, UR-041 Done DR-186 The play overlay comes down when the backend plays. isPlaying was assigned once from the player_play_item response and thereafter only by the player://state-changed listener — a channel the backend never emits, the same dead wire that DR-182's first fix was mistakenly hung on. On the native path the flag therefore froze at whatever the initial response said: with ExoPlayer playing, the UI still believed it was paused, so the bg-black/30 play-button overlay stayed raised across the whole video area and the transport button kept showing ▶. The video was simultaneously dimmed and covered while it played, which reads as \"the overlay never goes away\" and is easily mistaken for a second compositing fault. The mirror reads the same player store playerEvents.ts feeds, which is what the architecture already says is authoritative — the player reports state, the UI consumes it — and is gated to the native path so HTML5 keeps its element-event wiring, which is authoritative there UI UR-003, UR-005 Done DR-187 The system bars go away with the player, not only with the fullscreen button. enterImmersive() had exactly one caller, toggleFullscreen(), so opening the player left the status and navigation bars painted over it until the user pressed a button most never press. On the native path this is worse than cosmetic: the SurfaceView fills the content view, so the bars sit directly on top of the video. The player is a full-screen surface by construction — fixed inset-0 z-50 over a MATCH_PARENT surface — so entry is the right moment. Called synchronously in onMount before any await, per the native-mode pitfall, and paired with the exitImmersive() already unconditional in onDestroy, so a player torn down while immersive cannot leave the rest of the app without bars UI UR-066, UR-003 Done DR-188 Native Android video is ready to be the default except for the background-audio handoff , and the flip therefore waits. The picture defects behind DR-172 are all found, fixed and device-verified — DR-185 (the app shell painted over the surface through a CSS rule targeting an attribute nothing set), DR-182 (nothing could lift the poster card on a path with no element), DR-183 (the JS bridges raced the page load, so setTransparent(true) could never arrive), DR-184 (the SurfaceView was never detached), plus DR-186 and DR-187, the two UI defects only this path could reveal. On a device logcat now carries WebView transparent = true and Marking media ready with video on screen, which is the pair DR-172 went looking for and could not find, and skip, seek and rotation were exercised by hand. Turning the default on then surfaced a different unverified sub-path: the background-audio handoff could only return through the HTML5 element, so coming back from the lockscreen left playback dead, and the flip waited for that rather than shipping a verified sub-path over an unverified one as DR-161 had. The default is now on. The two defects holding it back are fixed and device-verified — DR-196 (the handoff return restarts the renderer that is actually on screen) and DR-194 (the letterbox bars are painted rather than retaining stale framebuffer content) — with the evidence this default has been held to since DR-161: an audio handoff at 69:54 returning to video playing at 70:18, and clean bars across playback, the control bar and a rotation round-trip. An explicit stored choice still wins in both directions, so an opt-out survives the flip (the stored value is null-checked rather than compared to \"true\", which would have silently re-enabled it for everyone who turned it off) Android UR-003, UR-004 Done DR-189 The control bar comes down on a touchscreen. Its hide timer was armed from exactly one place — the player container's onmousemove — and a touchscreen never fires mousemove, so on Android the bar was never scheduled to hide and sat over the video for the whole film. It went unnoticed for as long as the native video surface was itself invisible (DR-172/DR-185): with nothing behind it to obscure, a permanent control bar reads as the UI rather than as a defect. Two changes, because there were two faults. revealControls() replaces handleMouseMove and is called on entry and on every touch interaction as well as on mouse movement, so touch arms the countdown. And the countdown became an $effect over the state rather than a one-shot timer armed by the input event: the first attempt armed a timer on entry, three seconds later playback had not started, shouldHideControls correctly declined, and nothing ever re-armed it — the timer has to follow the conditions that permit hiding, which arrive on their own schedule. The decision itself is shouldHideControls in controlsVisibility.ts, pure and separated from the clock and the DOM, because what was wrong here was the conditions and not the setTimeout: the bar stays up while paused (a user who paused by tapping the surface has no other way back), mid-seek (the position readout is the point of the bar then), and while any track/subtitle/quality menu is open (the menus are anchored to the bar, so hiding it would take the open menu with it) UI UR-003, UR-066 Done DR-191 Forcing the WebView overlay to redraw from the Activity, because with the ExoPlayer SurfaceView beneath it the overlay's ordinary damage stopped reaching the screen: the page kept mutating — the clock text every second, the control bar's opacity going to 0 — while the display held whatever frame it last presented, over video that animated perfectly. Not a state defect; the live DOM showed the slider advancing 476 → 479 across three seconds behind a screen showing neither. Only structural changes got through, which is why the play overlay always appeared to work (an {#if} block, added and removed) while the progress bar never did, and why rotation lost the transport UI. A CSS animation cannot help, since opacity animates on the compositor without repainting the layer. Superseded by DR-192 : this drove postInvalidateOnAnimation in a loop, which treats the symptom — the cause is the SurfaceView's separate layer, and removing that removes the need. Kept as the record of how the mechanism was identified Android UR-003, UR-004 Superseded by DR-192 DR-195 Play/pause works on the native path, because the frontend stops claiming a webview element is playing when there is none. html5_playing is Rust's record of \"a webview is active and in this state\", and toggle_playback, play and pause all route transport to that element whenever it is set. The player route mirrored element state into it unconditionally — from handleReportStart and, fatally, from handleReportProgress, which VideoPlayer calls on a 10-second interval — so on the native path the frontend re-declared every ten seconds that an element was playing when none existed, and every transport intent was emitted into the void. The pause button was dead from the on-screen tap, from the control bar, and from a direct player_toggle invocation, while seek and skip kept working because player_seek_video decides elsewhere; that asymmetry is the signature. It also explains the flashing, since the control bar and the JRay overlay both key off isPlaying, which was being contradicted on every interval tick. DR-193 clearing the flag at load was necessary but insufficient on its own — the interval put it straight back. The mirror now lives in mirrorElementStateToRust in VideoPlayer, gated on useHtml5Element, which is the only place that knows whether an element renders at all; the route cannot tell the two paths apart, which is precisely how it came to lie. Confirmed on device by ADB: surface tap and control bar each pause (position frozen across repeated samples, transport label flipped) and resume Playback UR-005, UR-003 Done DR-196 Returning from background audio brings the picture back on the native path, because the return now restarts the renderer that is actually on screen. The two paths resume by different means: the webview reloads off its stream URL, watched by an $effect that reinitialises HLS and lets canplay drive the seek — while ExoPlayer owns no element and nothing watches the URL on its behalf, so its playback is only ever started by an explicit player_play_item + adapter load, issued once from onMount. exitBackgroundAudioHandoff did only the URL assignment, for both paths, so on the native path it restarted nothing: player_exit_background_audio had already stopped the handoff's audio player, leaving the backend holding no item at all. The symptom is a black screen with a play overlay pinned at 0:00, a seek bar at zero, and a play button that does nothing — the process alive and the frontend still logging, since nothing crashed; the transition was simply dropped. The branch is decided by planHandoffReturn (pure, in backgroundAudioHandoff.ts), which also folds in shouldResumeOnForeground so a lockscreen pause during the handoff still wins over the snapshot taken on the way out. Subtitle configurations are reused from the ones resolved at mount, since ExoPlayer sideloads them as MediaItem.SubtitleConfigurations and cannot accept one after prepare(). Verified on device: handoff to audio at 69:54, return restored video playing at 70:18 Playback UR-040, UR-003 Done DR-197 Continue Watching and Next Up stop showing the same episode. Jellyfin's /Shows/NextUp defaults EnableResumable=true, which returns a partially-watched episode as its own series' next up — precisely the episode /Items/Resume already returns — so the Home \"Next Episode\" row and the TV landing's Next Up row duplicated Continue Watching card for card. build_next_up_endpoint sends EnableResumable=false, and because servers predating that parameter ignore it, filterInProgressNextUpItems also drops any next-up entry whose id appears in the resume list. It is the mirror of DR-089 and lives beside it: same presentation-layer de-duplication over two lists the frontend already holds, no Jellyfin taxonomy involved. The resume filter still reads its frontier from the unfiltered Next Up list, so removing in-progress entries cannot resurrect a stale resume card. The division is then exact: Continue Watching offers episodes the viewer has started and not finished, Next Up offers the episode after the ones they finished Repository UR-059 Done DR-200 The lockscreen notification is exempt from POST_NOTIFICATIONS, because of the session token , not because it belongs to a foreground service — and the difference is what the code now records. POST_NOTIFICATIONS was declared in the manifest and requested nowhere, so on Android 13+ it sat permanently denied; an audit read that as a threat to UR-006, since the media notification is what carries the lockscreen transport controls. It is not. Android's own wording is that the permission covers \"non-exempt (including Foreground Services (FGS)) notifications\", with denied users seeing FGS notices \"in the Task Manager but [not] in the notification drawer\" — so an FGS notification is explicitly not exempt — while separately \"Notifications related to media sessions are exempt from this behavior change\". The platform predicate is Notification.isMediaNotification(), which requires MediaStyle and a non-null EXTRA_MEDIA_SESSION, and it is byte-identical across API 33–36. NotificationManagerService uses it to decide whether to drop the post, and SystemUI's media carousel (MediaDataProcessor.onNotificationAdded) is gated on the same predicate — so a token-less notification is not merely absent from the shade, it never reaches the notification listener and the lockscreen/Quick-Settings controls do not exist at all. Confirmed on device (HONOR ROD2-W09, Android 16 / SDK 36): appops POST_NOTIFICATION: ignore, granted=false, and the service simultaneously isForeground=true with foregroundNoti=Notification(category=transport actions=3 vis=PUBLIC). So no runtime permission request is added — a prompt the app does not need is a prompt that can be permanently denied for nothing — and no checkSelfPermission gate is placed on startForeground, which would trade a cosmetic problem for the \"did not then call Service.startForeground()\" kill. What is added is the guard that matches the real precondition: mediaSessionCompat?.sessionToken is a null-safe call, and the exemption hangs entirely on it, so both builders now bind the token once and log an error if it is ever null while the permission is denied — converting a failure that is invisible unless the tester happened to deny the permission (most grant it reflexively) into a logcat line. The manifest declaration is kept , unrequested, and documented: media3 does not need it (media3-session declares no permissions and the MediaSessionService guide asks only for the two FOREGROUND_SERVICE ones), but the exemption covers media and self-managed-call notifications only, so a download-completion notice (UR-011) would be an ordinary notification and silently dropped — keeping the declaration is what makes adding one a one-file change Android UR-006 Done DR-201 A lockscreen skip means different things depending on what is playing, and the backend decides which. onSkipToNext/onSkipToPrevious forwarded a bare \"next\"/\"previous\" to Rust, which always advanced the queue — correct for music, wrong for a video whose audio is running through a background-audio handoff (UR-040), where the buttons should scrub. Pressing skip to re-hear a line jumped to the next episode instead. resolve_skip_action in player/seek.rs maps the command to either Advance or SeekTo, and is_background_audio_active() is the whole test: the handoff exists only for video, and an episode played through it reports MediaType::Audio, so media type cannot distinguish the case. Forward jumps 30s, back 10s — asymmetric because the back button replays dialogue just missed rather than travels — and both clamp to [0, duration], since a negative offset is rejected by backends and a seek past the end reads as EOF and would advance, the very outcome being prevented. Routed through the same spawn-then-seek_absolute path as the scrubber, because a handoff seek re-opens the stream and must not run under the blocking lock (DR-159). The Kotlin keeps sending the same opaque command; only the PlaybackStateCompat gains ACTION_FAST_FORWARD/ACTION_REWIND so the system draws seek affordances rather than skip arrows that lie about what they do Playback UR-040, UR-006 Done DR-202 Video keeps the display awake. Android counts its display timeout from the last user input , and watching something is exactly the case where there is none, so the screen dimmed and slept mid-film unless the user kept tapping it. Nothing held it: FLAG_KEEP_SCREEN_ON appeared nowhere in the app, and neither renderer supplies a hold for free — ExoPlayer's setWakeMode is a CPU/wifi wake lock that says nothing about the display, and it draws into the TextureView this app owns (DR-192) rather than media3's PlayerView, which is the widget that would otherwise set keepScreenOn itself; the WebView path is no better, because the display wake lock Chrome takes for video lives in the browser layer and not in an embedded WebView. ScreenWakeManager toggles FLAG_KEEP_SCREEN_ON on the Activity window — window-scoped, so it stops applying the moment the app is not visible and cannot outlive a crash the way an explicitly acquired PowerManager.WakeLock can, and it needs no permission (the manifest's WAKE_LOCK is the media service's). The two rendering paths are independent holders OR-ed in the pure ScreenWakeState: the native path follows onIsPlayingChanged plus surface teardown, so the hold tracks what ExoPlayer reports rather than what the UI intends, and the webview path reuses the setHtml5VideoState report the frontend already sends for PiP (DR-160) rather than adding a bridge. Audio is deliberately not a holder — playing music with the screen off is the point of that path — so the hold is gated on the media type being video, and it is dropped on pause, on stop, on surface teardown, and on a new WebView, since a page that goes away never sends its own final active = false. Also the repo's first Kotlin JVM unit tests: ScreenWakeState is framework-free so the decision is testable off-device with ./gradlew :app:testUniversalDebugUnitTest. Verified on device (FP5, native path): IS PLAYING CHANGED: true → keepScreenOn = true 17 ms later and fl=KEEP_SCREEN_ON on the window in dumpsys, a pause releasing it and the resume re-taking it. The webview path is unverified Android UR-003, UR-004 Done DR-203 The background-audio handoff stops silently rewinding to the point it started. A player retry is only a retry if it can resume where the load failed, and ExoPlayer decides that in ProgressiveMediaPeriod.configureRetry: it keeps the load position when the content length is known or the extractor produced a seek map with a duration, and otherwise assumes the source is live — the data at the URL is taken to have changed, so every sample queue is reset and the URL is re-requested from offset 0. The handoff transcode (/Audio/{id}/universal?Container=mp3&TranscodingProtocol=http, DR-129) satisfies neither condition: chunked, so no Content-Length, and a live mp3 encode carries no Xing header, so the duration is unset — on device every position tick reads / 0.0. Its URL carries StartTimeTicks = the handoff point, so \"from offset 0\" is the handoff point, and after any transient load error playback resumed there and ran on normally. Nothing was reported: a successful retry raises no error and no STATE_ENDED, so neither arm of DR-129 was ever consulted, no onPositionDiscontinuity handler existed, and the app's only trace of it was a position that went backwards — which is why it read as random, since it needs a network blip to land while a load is in flight rather than while the ~50s buffer covers it, and why it survived the two earlier fixes for the same symptom (DR-129's phantom end, DR-159's relative-timeline leak). The decision is Rust's: player_retry_restarts_stream marks a Remote audio-only video item, and loadWithMetadata carries the answer to Kotlin, where the pure StreamRetryDecision holds it for a DefaultLoadErrorHandlingPolicy subclass that returns C.TIME_UNSET — which makes onLoadError answer DONT_RETRY_FATAL before reaching configureRetry. The rewind therefore becomes a recoverable error, and recoverable_error_resume already knows what to do with one: re-open at the position playback actually reached, StartTimeTicks rewritten, with backoff and the shared attempt budget. Every other source keeps the player's retry, because a static file and an HLS playlist both declare their timeline and are resumed in place. A onPositionDiscontinuity handler is added for the log line alone, so a recurrence is visible rather than invisible — loud for DISCONTINUITY_REASON_INTERNAL, which is the rewind's own signature, and quiet for the backwards jump a resume's re-prepare legitimately makes. Reproduced and verified on device (FP5), same procedure both times: background-audio handoff, 60s to fill the buffer, a 45s radio outage, then watch. Before — the outage passed unnoticed and 3.5 minutes later, with nothing logged in between, BUFFERING → READY → position 1165.4s → 840.3s, exactly the handoff base, no error and no STATE_ENDED; the same log line reports Media ready! Duration: -9.223372036854776E15, which is C.TIME_UNSET and the precondition itself. After — Load error on a stream that cannot be resumed in place — declining the player's retry at the outage, playback continuing undisturbed off the buffer for 69s (a fatal load error is only raised when the renderer next needs data), then ERROR_CODE_IO_NETWORK_CONNECTION_FAILED → re-opening at 785.6s in 2s → READY, playing on from 785.6s with no rewind in the following 7 minutes Playback UR-040, UR-004 Done DR-199 The webview stops undoing the network security config. MainActivity.configureWebViewSettings set mixedContentMode = MIXED_CONTENT_ALWAYS_ALLOW together with allowFileAccess = true and allowContentAccess = true, which is a blanket cleartext opt-in reached by hand — exactly the thing network_security_config.xml exists to prevent and its own comment warns against (DR-138). Nothing needed any of the three. file:// is never loaded: cached thumbnails go through convertFileSrc, which on Android resolves to http://asset.localhost/… and is answered by wry's request interceptor rather than the filesystem, and downloaded media goes over the loopback HTTP server (DR-137), which exists precisely because the asset/file route cannot stream a large file. content:// is never loaded either — the manifest's FileProvider is for outbound share intents, not webview navigation. And mixed content never arises: Tauri serves the UI from http://tauri.localhost (use_https_scheme defaults false and is not set in tauri.conf.json), while both 127.0.0.1 and asset.localhost are loopback/.localhost origins that Chromium treats as potentially trustworthy, so they are not mixed content to begin with. A plain-HTTP remote Jellyfin server would be, but the network security config already rejects it before any mixed-content check runs — so ALWAYS_ALLOW bought nothing and only widened the hole. COMPATIBILITY_MODE rather than NEVER_ALLOW is a deliberate hedge and not the default — the platform default at targetSdk 21+ is NEVER_ALLOW — because none of this can be verified anywhere but a device, and compatibility mode keeps passive content (images) working if the analysis missed a path. allowFileAccess = false restores the targetSdk-30+ default; allowContentAccess = false is a genuine tightening (its default is true) and is the first thing to look at if something that used to render stops. The two files now cross-reference each other so the pair cannot drift apart again Security UR-071 Done (pending device verification) DR-194 Stale pixels in the letterbox bars — the rotation \"flash of the previous frame\", a ghost control bar stranded in the top bar, each new clock digit drawn over the last (35:42 with the 1 still showing through the 2), and menus (sleep timer, quality) leaving their imprint behind. One cause for all of it: nothing painted the bars. The window surface is opaque (the theme is not translucent), and for an opaque surface HWUI deliberately does not clear the damaged region before replaying a frame — it assumes the view hierarchy covers every pixel. That hierarchy is window background → video TextureView → transparent WebView, and fitSurfaceToScreen sizes the TextureView to the letterboxed video rect, so the bars were the window background's alone to paint. setTransparent(true) cleared that background to TRANSPARENT, leaving the bars painted by nobody and whatever was last in the framebuffer surviving in them. Fixed by keeping the window background opaque black while compositing; the WebView's own background is what lets the video through, and the TextureView is drawn on top of the window background, so an opaque one cannot hide it. Three earlier fixes aimed at the window's rotation animation and at TextureView frame-retention (two postOnAnimation hops, an onSurfaceTextureUpdated reveal, then ROTATION_ANIMATION_JUMPCUT + FLAG_FULLSCREEN) all missed, because the pixels were never the animation's; the alpha-hiding among them made it worse by blanking the one view that reliably paints its own rect. Those are removed, FLAG_FULLSCREEN included — it fought edge-to-edge insets for no gain. Verified on device: ghosting reproduced with native video on, then absent after the fix, across playback, the control bar and a rotation round-trip Android UR-003, UR-066 Done DR-193 Play/pause reaches the player that is actually rendering. toggle_playback, play and pause all route to the webview element when is_html5_active(), which is html5_playing.is_some() — a flag written only by the element's own state reports and cleared only when it reports \"stopped\"/\"idle\" (or on a background-audio handoff). An element that went away without that final report, or webview-rendered music earlier in the same process, therefore left the flag set, and on Android's native video path every transport intent was emitted as a ControlCommand at an element that no longer existed: the pause button did nothing, from the on-screen tap and from the control bar alike, while seek and skip kept working because player_seek_video decides elsewhere. Whether it happened at all depended on what had played before, which is exactly what made it read as flaky rather than broken. load_and_play — the native load path, and the one the HTML5 video path deliberately avoids via set_current_item — now clears the flag, because loading into the native backend is the statement that native renders this item. Nothing is lost on the webview path: an element re-establishes its own authority the moment it reports again, so this is the existing \"element is gone\" semantics applied where it can be known directly rather than inferred from a report that may never arrive Playback UR-005, UR-003 Done DR-192 Native video presents through a TextureView , not a SurfaceView. A SurfaceView renders on its own layer outside the app window and punches a transparent region through it; everything drawn above that hole — for us the entire Svelte UI in a transparent WebView — depends on that composition path, and Android's own graphics documentation states that \"overlays do not currently work correctly with SurfaceView or TextureView\". The consequences were four symptoms of one cause (DR-191): a frozen progress bar, controls that would not fade, rotation losing the transport UI, and overlays that lingered after the DOM removed them. A TextureView is an ordinary view whose frames are drawn as a texture in the window's normal rendering pass, so there is no second layer and no transparent region, and the WebView above composites like it would over any other view — which is why media3 offers surface_type=\"texture_view\" and why it is the standard remedy for ExoPlayer overlay problems. The trade is accepted rather than hidden: TextureView costs more power and memory than SurfaceView and adds a frame of latency, but hardware decode through MediaCodec is untouched, so the reason native video exists survives it. setVideoTextureView installs ExoPlayer's own SurfaceTextureListener, so the old SurfaceHolder.Callback wiring is deleted rather than ported — adding a listener of ours would displace it and the video would never appear. PiP needs no change, since a TextureView is a View and the aspect-ratio probe reads its measured bounds Android UR-003, UR-004, UR-041 Done DR-190 The background-audio handoff can return to the native path. Everything that restores playback on the way back is written around the WebView : applyPendingForegroundSeek returns early on !videoElement, the HLS re-init $effect returns early on !useHtml5Element, and pendingForegroundSeek/pendingForegroundPlay — which own the post-handoff position and play/pause — are consumed only by handleCanPlay and markMediaReady, an element event and a path that reaches the same guard. On the native path there is no element, so exitBackgroundAudioHandoff completes, clears handoffState, blanks and reassigns currentStreamUrl to force an effect that will not run, and nothing ever restarts ExoPlayer: the user returns from the lockscreen to a dead player. This never showed while the path was opt-in and its picture was invisible anyway. The return needs the native equivalent of the element reload — re-issue the item to the backend, seek to the position player_exit_background_audio reports, then honour wasPlaying — routed through the adapter rather than the element, so both paths restore through one contract Playback UR-040, UR-003 Superseded by DR-196 DR-161 Native video is the default, so picture-in-picture has a real surface. DR-160 makes PiP work on the HTML5 path, but that path can only ever shrink the UI into the PiP window; showing the video itself needs the SurfaceView behind the WebView, which is what experimentalNativeVideo gates. The flag now defaults to on when the user has never chosen, with an explicit stored choice still winning in both directions so anyone who turned it off keeps it off. This is a deliberate acceptance of risk: the flag existed because the native path was an unfinished spike, and VideoPlayer.scrubRegression.test.ts documents its history — a native init that flipped to HTML5 mid-lifecycle and left seeks going down one path while ExoPlayer played on another. Those tests pin the flag-off interim override (native response overridden to HTML5, backend stopped once), which the default no longer selects, so they now mock the flag off rather than inherit it: they still guard that path, but they no longer describe what ships. The native scrub/seek path is consequently not covered by the suite and needs device verification UI UR-041, UR-003 Needs device verification DR-159 The background-audio handoff stops leaking its relative timeline. The handoff plays the episode as a relative stream — the audio-only URL is built with StartTimeTicks = the position the screen was locked at, so ExoPlayer's zero is the handoff point — and background_audio_base holds the offset that turns one back into a real position. The base was a display-only correction, applied in exactly two places (the lockscreen scrubber and the internal truncation maths) while every other consumer worked in the relative timeline treating the number as absolute. Each crossing threw away exactly base seconds, which is why the jump-back distance varied with where the screen was locked and read as random. Three crossings were live: progress reporting to Jellyfin sent the relative position every 30s, so the server was told real − base — and since DR-155 now mirrors the server's position back and refreshes on a cache hit, that regressed value returned as the resume point (lock at 40 min, listen to 90, reopen at 50); lockscreen seeks went out absolute and came back relative, against a chunked length-less transcode that cannot honour a seek at all, so a clamped seek landed at stream zero; and media3's own seekToDefaultPosition/seekBack/seekForward bypassed the ForwardingPlayer wrapper entirely, reaching the real ExoPlayer — Util.handlePlayButtonAction seeking an ended player to the relative zero being the same mechanism as DR-129's truncation bug through a different door. The fix converts once, at the boundary : JellyTauPlayer's position tick adds the base (and shifts the duration with it, since the stream's own length is only what remains) before either nativeOnPositionUpdate or the lockscreen sees it, so position updates, progress reports, the frontend and the truncation check all speak the episode's timeline and none needs to know a handoff happened. The base is consequently removed from claim_stream_resume, truncated_stream_resume_position and player_exit_background_audio, where adding it now double-counts, and the lockscreen's positionOffsetMs addition goes with it (the field remains, read-only, as the tick's input). Inbound seeks go the other way: seek_absolute is the new boundary for every outside seek, re-opening the stream at the requested position via resume_stream_at when a handoff is active — which is what onSeekTo had claimed for months in a comment describing code that did not exist — and an ordinary seek otherwise. seekToDefaultPosition is swallowed rather than forwarded, since Rust already owns what \"play after the stream ended\" means and the play() that follows reaches it. Exit reads the position before clearing either base, or a tick landing in between hands back a relative one Player UR-040, UR-005, UR-025 Done (pending device verification) DR-158 A watched toggle, on the episode row, the season header, the series and movie hero, and the Episode Focus View. Both halves of the backend already existed and neither had a caller: mark_played (POST /PlayedItems) was reachable only from the sync drain replaying rows the reporter had queued, and clear_watch_history (DELETE /PlayedItems) only from the destructive \"erase this series' history\" button — so the sole way to mark something watched was to play it. Jellyfin applies both recursively over a season or series, so the container case needs no client-side fan-out online . Offline it does: storage_set_watched writes the item and its descendants (drawn from items by parent_id/album_id/season_id/series_id, so an uncached id selects nothing and the statement no-ops instead of raising a foreign-key error), because otherwise marking a season watched with no server would tick the season and leave every episode inside it unwatched. It is deliberately separate from storage_mark_played, which stays the single-item \"this finished playing\" path that increments play_count. Un-marking clears the resume position as well as the flag, matching the server. QueuedOp::MarkUnplayed gives the queue the missing direction — pushing as clear_watch_history — so the toggle works offline both ways rather than only one; without it un-marking would have been the half that needed a connection. The button is an everyday toggle, so unlike ClearHistoryButton it does not confirm, and it holds an optimistic state because the caller's watched prop only catches up after a reload (a season means a round trip, during which the button would otherwise appear to ignore the tap) UI UR-073 Done DR-157 Full-screen video on Android actually goes full screen. toggleFullscreen called document.documentElement.requestFullscreen() and nothing else, which inside an Android WebView does not touch the Activity window — it expands the element within a viewport that already spans the whole screen, because enableEdgeToEdge() is called in onCreate and SDK 36 ignores the opt-out. So the control did nothing visible while the status bar and navigation/gesture bar stayed painted over the video, and (unlike DR-112's chrome-clearance work, which is about reserving space for the bars) here the bars should not be there at all. ImmersiveModeBridge hides them via WindowInsetsControllerCompat with BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE, so an edge swipe brings them back transiently over the video instead of resizing the window mid-playback, and the system's own gestures stay reachable. Exposed as the AndroidImmersive bridge and posted to the main thread, since @JavascriptInterface methods arrive on a WebView binder thread. requestFullscreen() is kept for the platforms where it does work, but its rejection is caught rather than allowed to abort the immersive call. Restoring is wired to three paths, not one: leaving fullscreen, Escape (which previously called document.exitFullscreen() directly, bypassing the flag and the bars), and onDestroy — the bars belong to the Activity, so a player torn down while immersive would strand every screen behind it without them. The --jt-inset-* properties need no special handling: hiding the bars fires the decor view's inset listener with zeroes and WindowInsetsBridge republishes them UI UR-066 Done DR-143 Flipping the offline downloaded-only gate actually re-queries the listing. The gate (DR-078) is a process-wide flag in Rust consulted only while a query runs , but no library surface re-queried when its inputs changed: useServerReachabilityReload fires only on the offline → online transition, and GenericMediaListPage, GenericGenreBrowser and the favourites page never even called its checkServerReachability. So going offline left the full server catalog on screen under a now-closed gate, and toggling \"Show all server media\" only greyed cards — MediaCard.isServerOnly is a pure frontend derivation that updates instantly — without adding or removing a single row. The filter therefore read as \"shows everything until I filter, then greys some of it\" while the backend gate was correct and simply never exercised. catalogFilterVersion is the refetch signal: pushCatalogVisibility now awaits set_show_server_catalog and bumps the version only after the backend accepts the new flag, since a reload racing the push would re-query under the old gate and undo itself. A failed push clears lastIncludeCatalog instead of latching it, so the next identical transition is retried rather than skipped as a no-op and left permanently disagreeing with the backend. useOfflineFilterReload subscribes pages to that signal, skipping the value they already loaded under; it is wired into both generic list components and the movies/music/tv/favourites landing pages and the /library/[id] detail page UI UR-052 Done DR-135 A download's media type comes from the item, not a default. download_item — the path a media card uses to queue an item while offline — never records media_type, and the reconnect resolver read that NULL as 'audio', so a movie queued from a card had its URL resolved by get_audio_stream_url. The file that landed on disk was an audio-only transcode, which is why a \"downloaded\" film could never play offline no matter how the path or protocol was fixed. The resolver now falls back to the item's own item_type (VIDEO_ITEM_TYPES in Rust, so the frontend never learns which types are video) and only defaults to audio when the item is not cached locally. An explicit media_type on the row still wins Downloads UR-071, UR-052 Done DR-136 Rows already downloaded under the audio default are repaired, not just prevented. They are identifiable after the fact — no media_type, but a video item — so on reconnect they are reset to pending with their audio URL cleared and re-resolved by DR-135's corrected logic, overwriting the audio file in place. Without this the fix is invisible to anyone who had already queued a film: the row still reads \"downloaded\" and still fails to play. Rows carrying an explicit media_type and genuine audio downloads are left untouched Downloads UR-071 Done DR-137 Local media is served to the player over a loopback HTTP server, not the asset protocol. Tauri's asset protocol answers a request carrying no Range header by reading the whole file into memory, and only advertises Accept-Ranges: bytes from inside its range branch — so the first request never learns ranges exist and a multi-gigabyte body is attempted instead. Chromium abandoned it with PIPELINE_ERROR_READ after ~31s, which reached the user as \"downloaded video does not play offline\". Real HTTP on 127.0.0.1 is chosen over a custom URI scheme deliberately: range support becomes a property of the transport rather than depending on whether a platform's webview forwards Range to a custom scheme. No response ever exceeds a 4 MiB chunk and bodies stream from the file handle, so memory is bounded regardless of file size. Because loopback is shared between apps on Android , the server binds 127.0.0.1 only and every URL carries a random per-session token; paths are additionally confined to the app data directory, so a leaked URL cannot read outside it. This is stage 1 of making the server the single media origin — remote passthrough and download-while-watching are deliberately out of scope here Playback UR-071 Done DR-138 Loopback is exempted from Android's cleartext ban, and nothing else is. Release builds set usesCleartextTraffic=\"false\", so the webview's request to the local media server (DR-137) was rejected by network security policy before any I/O — failed in the same millisecond as loadstart, with NETWORK_NO_SOURCE and no server-side log at all, which is why it looked identical to a missing file. A network-security-config resource permits cleartext for 127.0.0.1 only and keeps base-config cleartextTrafficPermitted=\"false\", so a remote server must still be HTTPS; this is deliberately not a blanket opt-in. The manifest attribute is ignored once the config is present, so the config is the single authority. sync-android-sources.sh also had to learn to copy res/xml, which it skipped — the manifest references the resource, so a missed copy fails the resource link rather than degrading quietly Security UR-071 Done DR-093 Traceability coverage gate derives its requirement denominators from requirements.md at run time rather than hardcoded literals: countDefinedRequirements counts an ID only where it leads a markdown table row (ignoring the \"Traces To\" column and prose) and deduplicates IDs listed both in the definition tables and in the §3 traceability matrix; computeCoverage reports the intersection of traced and defined IDs so an ID traced in code but absent from requirements.md is surfaced as orphaned instead of inflating the ratio past 100%. UT/IT test identifiers are excluded as a separate taxonomy. CI and bun run traces:coverage share this computation and fail on both a sub-threshold and an impossible >100% result Tooling - Done DR-204 A leveled logging facade for the frontend, replacing raw console.* calls. One module owns the log sinks, so a level (error/warn/info/debug) decides at run time what is emitted rather than every call site deciding permanently at authoring time: a release build stays quiet, a developer chasing a playback bug turns the player's debug output on without editing and rebuilding, and nothing that reaches the console is written by a console.log nobody can find again. Scoped loggers carry the subsystem in the message, so a filtered console is usable while a player, a download worker and a store are all talking Tooling - Done DR-205 ESLint + Prettier run as a gate over the frontend, so lint and formatting are decided once by configuration rather than per reviewer. Formatting is not a matter of opinion at review time, and the classes of bug a linter sees (unused bindings, floating promises, accidental globals) should never reach a human reviewer at all. Wired as an npm script so the same command runs locally and in CI, matching how check:boundary and the traceability gate already work Tooling - Done DR-206 The Rust toolchain is pinned in-repo (rust-toolchain.toml) and the pin is what both a developer's machine and CI use. Without it, cargo fmt --check and cargo clippy are run by whatever version each host happens to have, so a formatting or lint result differs between a laptop and the builder image and CI fails on a diff that was clean locally — the failure mode is a red build nobody can reproduce. The builder image carries the pinned toolchain, so pinning is a declaration , not a CI-time install (see the no-toolchain-installs rule) Tooling - Done DR-207 A pre-commit hook runs the \"Before Committing\" gates — frontend checks and tests, cargo fmt, clippy, the boundary tripwire and the traceability checks — so the gates are enforced at the commit rather than discovered in CI. The gates already exist and are already documented; what is missing is that nothing runs them, which makes compliance a matter of memory. The hook is the mechanism that makes the documented list actually binding Tooling - Done DR-208 Documentation link integrity is checked mechanically (scripts/check-doc-links.sh): every relative markdown link in every tracked .md must resolve to a file that exists on disk. This is a real defect class, not hygiene — the generated traceability matrix shipped ~2,800 dead file links because it was written to docs/ while its hrefs were repo-root-relative, and nothing noticed for months because no check existed and nobody clicks 2,800 links. The check validates paths , deliberately not anchors or external URLs: anchor resolution needs a markdown renderer's slug rules and network checks make the gate flaky, so both are out of scope and stated as such in the script Tooling - Done DR-209 Library folders are excluded from music browsing server-side, by folder id , replacing a hardcoded frontend filter that dropped anything whose name contained \"Podcasts\". The name filter was wrong in three separate ways: it encoded a domain classification in the presentation layer, it matched on a title rather than on what an item is (so an album legitimately called \"Podcasts\" vanished while a podcast folder named anything else did not), and it applied only where someone had remembered to call it, so the same library was in scope on one screen and out of scope on the next. Excluded folder ids are stored as user configuration and applied by the repository layer to every music query — libraries, artists, albums, genres, search and the home rows — so scope is decided in one place and is the same everywhere Repository UR-076 Done DR-210 Thumbnail cache writes are confined to the cache directory. The filename was built from item_id, image_type and tag, but only tag was sanitised — and Path::join neither folds .. nor keeps the base when handed an absolute path, so a value arriving verbatim from server JSON decided where a file landed. The tag's existing rule (non-alphanumerics become _) now applies to all three parts, and the resolved path is checked with starts_with(cache_dir) at the point of use. The database keeps the raw key and the resolved path, so lookups still match and pre-existing rows still resolve. Not exploitable as shipped — server URLs must be HTTPS and Android blocks cleartext, so the id comes from a server the user chose to trust — the value is making the write path consistent with how caller-supplied paths are handled elsewhere Storage UR-012 Done DR-211 Download paths are confined to the download root. file_path and target_dir reached PathBuf::join unchecked from the frontend, and mark_download_completed persisted a caller-supplied path later passed to remove_file. A correct sanitiser already existed and download_item_and_start used it, but download_item is itself a command accepting file_path raw, so the guard was bypassable rather than absent — the fix moves it inside instead of adding a second one. Sanitising is per path component : whole-string sanitising would rewrite downloads/x.mp3 to downloads_x.mp3 and relocate every existing download. Confinement happens after the join, since a join with an absolute second half discards the root Downloads UR-011 Done DR-212 Query and URL construction bind or encode their inputs. Three sites interpolated caller-supplied values directly: the offline get_items item-type filter built IN ('a','b') by string formatting, build_get_items_endpoint wrote ParentId/IncludeItemTypes/SortBy/SortOrder into a URL unencoded, and player_set_volume accepted NaN and out-of-range floats. Each is a consistency defect rather than a novel one — the same file already did it correctly a few lines away (parameter placeholders in search, urlencoding::encode for genres, clamp in every player backend). List separators stay unencoded and encoding is per element, because Jellyfin splits these parameters on the comma Repository UR-007, UR-065 Done DR-213 Containerised builds hand their artifacts back to the host user. The compose services bind-mount the repo and run as root — their caches live at /root/.cargo and /root/.bun, so a non-root container user cannot write them — which leaves root-owned files accumulating in the developer's working tree: 11,124 of them when this was found, enough that cargo clean and scripts/clean.sh failed with EACCES and a plain cargo build died part-way, since build scripts compile for the host and land in target/debug even during a cross-build. Ownership is restored at the end of each containerised build, reading the intended owner from the checkout so no uid needs plumbing through. Running the containers as the host uid is the tidier fix and remains open; it needs the cache volumes relocated off /root first Tooling - Done DR-214 The app identifies itself correctly everywhere a user or a package manager reads its name. productName was the scaffold's lowercase jellytau, which is what the Android release build showed under its icon and what the deb/rpm/NSIS bundles carried as their display name — invisible in development because build.gradle.kts overrides the label to \"JellyTau Debug\" for the debug build type, so the install a developer looks at daily was the only correctly-cased one. mainBinaryName pins the executable filename so nothing that resolves a path by name has to change. strings.xml moves into the canonical android tree, where sync-android-sources.sh already copies res/values/*.xml, so the fix survives regenerating gen/. Bundle metadata (publisher, copyright, category, descriptions, licence) was entirely absent, which is why the packages shipped with no maintainer or description — the hand-written Arch PKGBUILD and .desktop had all of it, so only the generated packaging was wrong Packaging - Done DR-215 Frontend test coverage is a ratcheted CI gate rather than a number nobody looks at. test:coverage had been configured since the suite was created and was silently broken: @vitest/coverage-v8 resolved to 4.1.10, whose peer range pins vitest exactly, while package.json asked for >=1.0.0 <5.0.0 and got 4.0.16 — so every invocation died on a missing BaseCoverageProvider export and no coverage figure had been produced in months. Fixing the range is half the requirement; the other half is that a measured figure that gates nothing decays the same way an unrun script does. Thresholds sit a few points under the measured result (statements 54.6, branches 48.7, functions 49.6, lines 55.1 when this landed) and only ever move up, matching MIN_THRESHOLD in the traceability gate and the eslint --max-warnings ratchet. The absolute numbers are held down by .svelte components, which this project deliberately does not test directly — the pattern is to extract the logic to a plain module and test that Tooling - Done DR-216 Dependencies are gated on known vulnerabilities and on licence compatibility, and the build graph is pinned to what is actually shipped. The project had no scanning of any kind: nothing checked the ~500-crate Rust graph or the JS packages against an advisory feed, and nothing checked that everything redistributed inside an MIT-licensed bundle permits it. The first run found eight vulnerabilities and one unsoundness — bytes, four in rustls-webpki, time, two in quick-xml, rand — every one closed by a cargo update nobody had reason to run. cargo deny (src-tauri/deny.toml) now runs in CI over advisories, licences, bans and sources. Two structural fixes matter as much as the gate: the graph is scoped to the targets actually shipped, so an advisory against an Apple-only path is correctly absent rather than ignored by ID; and the one git dependency (libmpv) is pinned by revision instead of by branch, since a branch means any cargo update silently substitutes new upstream code in the one dependency that is unsigned and links a C library into the player. Licence findings are recorded rather than waved through — libmpv/libmpv-sys are LGPL-2.1, which the app satisfies by dynamic linking, and that carries obligations (keep the linkage dynamic; ship libmpv's licence text with any bundle carrying the .so) Tooling - Done DR-217 In-app update, desktop only, over a manifest we control. tauri-plugin-updater and tauri-plugin-process are compiled for everything except Android/iOS — spelled as a target-triple cfg rather than cfg(desktop), which Cargo does not evaluate in a [target.'cfg(…)'] table and which therefore drops the dependency silently, surfacing much later as \"Permission updater:default not found\". The release workflow signs updater artifacts with a minisign key held in Gitea secrets and publishes latest.json to a dedicated updater branch, read over Gitea's raw-file URL: this instance serves /releases/download// but returns 404 for /releases/latest/download/, so there is no stable latest-release URL to point at, and the docs branch is force-pushed by publish-docs.yml so it cannot host the manifest either. Bundle targets gain appimage, which the release notes had been advertising for months while tauri.conf.json never built it — the artifact step globbed for *.AppImage, found nothing, and said nothing Tooling UR-077 Done DR-218 Persistent, redacted logging and a diagnostics export. tauri-plugin-log replaces the env_logger stdout-only init, giving a rotating 5 MB file, a webview target in dev, and — the single largest gain — logcat on Android, where env_logger's stdout went nowhere. Redaction runs in the log formatter, not at export : a credential in a file on the device is already a disclosure, so stripping it on the way out would be too late; the exporter redacts a second time to cover files written by older builds. api_key/X-Emby-Token/Authorization/\"AccessToken\"/Token=\"…\" all reduce to [REDACTED] while host, item ids and filenames are deliberately kept — a bundle scrubbed of those is one nobody can debug from. The server URL is reduced to scheme and host, dropping any embedded user:pass@. The panic hook chains to the previous hook rather than replacing it, because utils/lock.rs installs a silencing hook around tests that provoke poisoned locks on purpose. The chosen level persists to disk and is re-applied at startup, since reproducing a bug usually means restarting into it. The frontend facade keeps its untouched console.* pass-through (DR-204) and additionally forwards a stringified copy at info and above, so one file holds both halves of the app in order — which is what makes a race between them legible after the fact Tooling UR-078 Done DR-219 Release notes are the reviewed CHANGELOG entry, not a generated draft. Every release from v0.0.1 to v0.9.1 published the same ~1,050 bytes of generic install instructions whose \"What's New\" section said \"See CHANGELOG.md\" — a link that does not resolve from a release page. Thirty-five releases, byte-identical, telling a reader nothing about what changed. The workflow now publishes the ## section of CHANGELOG.md and fails the release if that section is absent, since notes that say nothing are worse than a build that waits for two sentences. release:notes is printed into the job log as a drafting aid but is deliberately not published: CLAUDE.md calls its output \"a reviewed draft, not a final changelog\", and publishing it unreviewed proved why — a range containing a repo-wide formatting sweep resolved to nearly the entire requirement matrix and produced notes claiming one release had added the whole application. The script now skips cosmetic commits (chore(format), chore(deps), style) when deriving a range's files, and says how many it skipped rather than silently reporting a smaller set Tooling - Done DR-220 A release ships only its own artifacts. src-tauri/target/*/release/bundle/ is not versioned, cargo never cleans it, and the CI runner reuses the target directory — so the copy step's bundle/**/*-setup.exe glob collected every installer ever built there. Every release from v0.1.0 to v0.8.2 shipped its predecessors': sixteen Windows installers on v0.8.2, thirteen of them stale, and a download list on v0.5.0 reaching back to 0.1.0. It went unnoticed for eight months because there was nothing to notice — the upload loop reported success, the files were real, and the page looked busy rather than wrong. It stopped only when an unrelated cache change wiped the runner's target dir, leaving the defect dormant rather than fixed. Both desktop builds now clear the bundle directory first, so a stale file cannot exist to be copied — filtering the copy by version would have hidden it instead. scripts/check-release-artifacts.sh is the backstop for the next route nobody predicts: it runs before the SBOM, the checksums and the upload, and refuses to publish when any artifact's embedded version disagrees with the tag Tooling - Done DR-221 The release path is exercised before a tag exists. Nothing in build-and-test.yml runs tauri build — only a tag does — so a whole class of breakage was invisible until release day, and two instances of it were sitting on master at once. Tauri refuses to build when a plugin's Rust crate and npm package differ by minor version, which the updater and logging work had introduced (tauri-plugin-log 2.8.0 against @tauri-apps/plugin-log 2.9.0) while cargo check, clippy, the tests and svelte-check all passed; both sides are now pinned exactly rather than by caret, since a caret is what let them separate, and CI runs tauri info to compare them without building. The AppImage target had never once been built: linuxdeploy carries a strip too old to parse the .relr.dyn section modern toolchains emit, so bundling failed on every library — and Ubuntu 23.10+ links with -z pack-relative-relocs by default, so the builder image fails the same way a modern Arch host does. NO_STRIP=true is linuxdeploy's documented escape hatch; the cost is a larger, unstripped bundle. Both were found by building the target locally before tagging rather than by publishing a release that could not build Tooling - Done DR-222 Build tooling matches the package manager the project declares. scripts/build-android.sh ran npm install on its clean-build path — in a bun project, where packageManager says bun and bun.lock is the committed lockfile. npm ignores that lockfile, re-resolves the whole tree from package.json, and writes a package-lock.json that .gitignore then hides. That is not a style preference: the JS halves of the Tauri plugins are pinned exactly against Cargo.lock because the CLI refuses to build when a plugin's crate and package differ by minor version, and a silent re-resolve is precisely how they drift apart. It survived because clean builds are rare — the shape shared by nearly every defect found preparing v0.10.0, where the code running on every commit was healthy and the code running on a release, a tag or a clean build had no guard at all. scripts/check-tooling.sh fails on any npm/yarn/pnpm invocation or foreign lockfile Tooling - Done DR-223 The Android JavaVM and Application are published into ndk_context by this crate, not by a transitive dependency. Seven call sites (five in credentials.rs, two in lib.rs) read that process-global to reach JNI, and nothing here ever set it — tao did, three levels below anything this project names in Cargo.toml. tao 0.35.3 moved those pointers into a private struct and stopped publishing them, so the Tauri 2.11 upgrade made the first credential read abort the process on every launch: PANIC ... android context was not initialized. Our code had not changed; an undocumented side effect of the windowing layer had gone. The invariant is now owned here rather than assumed: JNI_OnLoad captures the JavaVM as the shared library loads, and the Application is resolved lazily via ActivityThread.currentApplication() and pinned as a global reference for the process lifetime — the Application rather than the Activity, since that is what SecureStorage.initialize() immediately reduces its argument to. Failure degrades to the encrypted-file credential path and is logged, rather than aborting. Found only by installing on a device: nothing in CI runs the app Security UR-012 Done DR-224 Backgrounding the app obeys the background-audio toggle on every renderer. The toggle (UR-040) was built for the WebView path, where losing visibility kills the decode: it chose between handing off to a native audio stream and letting playback stop. Native video then became the default renderer (DR-188), and on that path playback runs through ExoPlayer inside a MediaSessionService — a foreground media service whose purpose is to keep playing while the app is hidden. Nothing paused it and nothing in the codebase paused on background, so locking the screen kept the audio going whether or not the toggle was on: the toggle governed a handoff that no longer had a gap to bridge, and users got background playback they never asked for. The decision now lives in Rust (player/background_policy.rs) and both renderers obey it: a video with the toggle off pauses, with the toggle on hands off to audio, music is never paused by backgrounding, and picture-in-picture keeps playing because the window is still on screen (UR-041). It takes no renderer parameter on purpose — the split between the two paths is what produced the defect Player UR-040 Done DR-198 The webview runs under a real Content-Security-Policy, and the asset protocol is scoped to the one directory it still serves. csp was null, which disables CSP entirely: any script that reached the web layer — through a future {@html}, a dependency, or a devtools paste — would have inherited the whole IPC surface, and with it the user's session. script-src 'self' (Tauri injects a nonce for SvelteKit's inline bootstrap script at build time, so no 'unsafe-inline' is needed) plus object-src/frame-src 'none' and base-uri 'self' is the part that is genuinely restrictive. img-src/media-src/connect-src cannot be: the Jellyfin origin is typed in by the user at run time and is commonly plain http on a LAN, so they allow http:/https: — a wide grant for data , but one that still bars file:, filesystem: and scripting schemes, and leaves script-src untouched. style-src keeps 'unsafe-inline' because Svelte compiles style=\"…\" attributes (including app.html's display: contents wrapper) into markup; this is safe only while no