kryten-webqueue 0.40.2__tar.gz → 0.40.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (185) hide show
  1. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/CHANGELOG.md +24 -0
  2. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/PKG-INFO +1 -1
  3. kryten_webqueue-0.40.4/kryten_webqueue/static/js/keybindings.js +59 -0
  4. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/templates/base.html +1 -0
  5. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/templates/catalog/browse.html +25 -5
  6. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/pyproject.toml +1 -1
  7. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/.github/workflows/gitleaks.yml +0 -0
  8. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/.github/workflows/python-publish.yml +0 -0
  9. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/.github/workflows/release.yml +0 -0
  10. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/.gitignore +0 -0
  11. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/.gitleaks.toml +0 -0
  12. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/.pre-commit-config.yaml +0 -0
  13. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/AGENT-WORKFLOW-GUIDE.md +0 -0
  14. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/AGENTS.md +0 -0
  15. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/README.md +0 -0
  16. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/config.example.json +0 -0
  17. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/deploy/kryten-webqueue.service +0 -0
  18. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/deploy/nginx-queue.conf +0 -0
  19. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/docs/IMPLEMENTATION_SPEC.md +0 -0
  20. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/docs/IMPL_API_GATE.md +0 -0
  21. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/docs/IMPL_ECONOMY.md +0 -0
  22. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/docs/IMPL_KRYTEN_PY.md +0 -0
  23. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/docs/IMPL_ROBOT.md +0 -0
  24. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/docs/Kryten-Economy Service Quality Survey Report at v0.13.0.md +0 -0
  25. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/docs/Kryten-Webqueue Service Quality Survey Report at v0.27.1.md +0 -0
  26. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/docs/Log for incorrect promo insertion problem.txt +0 -0
  27. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/docs/PLAN_PRESENCE_AND_PROMOS.md +0 -0
  28. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/docs/PRD_TMDB_LOCAL_INDEX.md +0 -0
  29. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/docs/PRE_PLAN_GAPS.md +0 -0
  30. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/docs/PRODUCT_PLAN.md +0 -0
  31. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/docs/SPEC-Visual-UX-Sprint-August-2026.md +0 -0
  32. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/docs/SPEC_ADMIN_ITEM_EDITOR.md +0 -0
  33. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/docs/SPEC_CATALOG_ENRICHMENT_PIPELINE.md +0 -0
  34. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/docs/SPEC_EMOTE_REHOST.md +0 -0
  35. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/docs/SPEC_JOBS_AND_BROWSE.md +0 -0
  36. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/docs/SPEC_TMDB_LOCAL_INDEX.md +0 -0
  37. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/docs/UX_POLISH_PLAN.md +0 -0
  38. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/__init__.py +0 -0
  39. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/__main__.py +0 -0
  40. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/api_gate/__init__.py +0 -0
  41. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/api_gate/client.py +0 -0
  42. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/app.py +0 -0
  43. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/auth/__init__.py +0 -0
  44. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/auth/otp.py +0 -0
  45. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/auth/rate_limit.py +0 -0
  46. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/auth/session.py +0 -0
  47. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/catalog/__init__.py +0 -0
  48. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/catalog/db/__init__.py +0 -0
  49. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/catalog/db/_catalog.py +0 -0
  50. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/catalog/db/_connection.py +0 -0
  51. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/catalog/db/_enrichment.py +0 -0
  52. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/catalog/db/_feedback.py +0 -0
  53. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/catalog/db/_fetch_queue.py +0 -0
  54. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/catalog/db/_people.py +0 -0
  55. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/catalog/db/_playlists.py +0 -0
  56. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/catalog/db/_queue.py +0 -0
  57. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/catalog/db/_watchlist.py +0 -0
  58. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/catalog/enrichment/__init__.py +0 -0
  59. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/catalog/enrichment/classify.py +0 -0
  60. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/catalog/enrichment/normalise.py +0 -0
  61. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/catalog/enrichment/pipeline.py +0 -0
  62. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/catalog/enrichment/providers.py +0 -0
  63. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/catalog/enrichment/report.py +0 -0
  64. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/catalog/enrichment/steps/__init__.py +0 -0
  65. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/catalog/enrichment/steps/art.py +0 -0
  66. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/catalog/enrichment/steps/categories.py +0 -0
  67. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/catalog/enrichment/steps/identify.py +0 -0
  68. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/catalog/enrichment/steps/meta.py +0 -0
  69. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/catalog/enrichment/steps/sync.py +0 -0
  70. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/catalog/enrichment/steps/tags.py +0 -0
  71. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/catalog/enrichment/steps/title.py +0 -0
  72. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/catalog/images.py +0 -0
  73. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/catalog/mediacms.py +0 -0
  74. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/catalog/sync.py +0 -0
  75. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/catalog/tmdb_index/__init__.py +0 -0
  76. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/catalog/tmdb_index/_schema.py +0 -0
  77. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/catalog/tmdb_index/_textmatch.py +0 -0
  78. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/catalog/tmdb_index/_ttscrape.py +0 -0
  79. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/catalog/tmdb_index/builder.py +0 -0
  80. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/catalog/tmdb_index/coverage.py +0 -0
  81. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/catalog/tmdb_index/index.py +0 -0
  82. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/config.py +0 -0
  83. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/integrations/__init__.py +0 -0
  84. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/integrations/cmsutils/__init__.py +0 -0
  85. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/integrations/cmsutils/_common.py +0 -0
  86. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/integrations/cmsutils/enrichmeta.py +0 -0
  87. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/integrations/cmsutils/enrichtitles.py +0 -0
  88. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/integrations/cmsutils/enrichtv.py +0 -0
  89. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/integrations/cmsutils/fetchurls.py +0 -0
  90. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/integrations/cmsutils/motdposters.py +0 -0
  91. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/integrations/cmsutils/playedmovies.py +0 -0
  92. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/integrations/ytpipe/__init__.py +0 -0
  93. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/integrations/ytpipe/downloader.py +0 -0
  94. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/jobs/__init__.py +0 -0
  95. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/jobs/fetchurls_auth.py +0 -0
  96. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/jobs/job_scheduler.py +0 -0
  97. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/jobs/manager.py +0 -0
  98. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/jobs/rehost_emotes.py +0 -0
  99. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/jobs/tasks.py +0 -0
  100. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/logging_config.py +0 -0
  101. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/playlists/__init__.py +0 -0
  102. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/playlists/bulk_add.py +0 -0
  103. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/playlists/fire.py +0 -0
  104. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/playlists/importer.py +0 -0
  105. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/playlists/ordering.py +0 -0
  106. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/playlists/scheduler.py +0 -0
  107. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/promos/__init__.py +0 -0
  108. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/promos/director.py +0 -0
  109. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/queue/__init__.py +0 -0
  110. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/queue/completion.py +0 -0
  111. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/queue/ordering.py +0 -0
  112. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/queue/poller.py +0 -0
  113. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/queue/presence.py +0 -0
  114. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/queue/race_poller.py +0 -0
  115. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/queue/shadow.py +0 -0
  116. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/routes/__init__.py +0 -0
  117. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/routes/admin_catalog.py +0 -0
  118. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/routes/admin_feedback.py +0 -0
  119. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/routes/admin_job_schedules.py +0 -0
  120. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/routes/admin_jobs.py +0 -0
  121. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/routes/admin_moderation.py +0 -0
  122. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/routes/admin_playlists.py +0 -0
  123. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/routes/admin_promos.py +0 -0
  124. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/routes/admin_queue.py +0 -0
  125. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/routes/admin_schedules.py +0 -0
  126. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/routes/auth.py +0 -0
  127. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/routes/catalog.py +0 -0
  128. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/routes/feedback.py +0 -0
  129. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/routes/pages.py +0 -0
  130. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/routes/queue.py +0 -0
  131. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/routes/user.py +0 -0
  132. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/static/css/main.css +0 -0
  133. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/static/js/admin-playlists.js +0 -0
  134. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/static/js/admin-promos.js +0 -0
  135. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/static/js/admin-queue-mgmt.js +0 -0
  136. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/static/js/admin-schedules.js +0 -0
  137. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/static/js/admin.js +0 -0
  138. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/static/js/main.js +0 -0
  139. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/templates/admin/index.html +0 -0
  140. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/templates/admin/playlists.html +0 -0
  141. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/templates/admin/promos.html +0 -0
  142. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/templates/admin/queue_mgmt.html +0 -0
  143. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/templates/admin/schedules.html +0 -0
  144. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/templates/auth/login.html +0 -0
  145. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/templates/catalog/edit_item_modal.html +0 -0
  146. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/templates/catalog/item_detail.html +0 -0
  147. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/templates/catalog/item_not_found.html +0 -0
  148. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/templates/feedback/index.html +0 -0
  149. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/templates/queue/index.html +0 -0
  150. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/templates/race.html +0 -0
  151. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/templates/user/dashboard.html +0 -0
  152. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/templates/user/my_list.html +0 -0
  153. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/ws/__init__.py +0 -0
  154. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/ws/handler.py +0 -0
  155. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/kryten_webqueue/ws/manager.py +0 -0
  156. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/scripts/AGENTS.md +0 -0
  157. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/scripts/patch_mediacms_bulk_facets.py +0 -0
  158. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/scripts/patch_mediacms_tags.py +0 -0
  159. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/scripts/trigger_enrich.py +0 -0
  160. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/tests/__init__.py +0 -0
  161. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/tests/test_catalog_delete.py +0 -0
  162. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/tests/test_classify.py +0 -0
  163. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/tests/test_config_persistence.py +0 -0
  164. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/tests/test_feedback.py +0 -0
  165. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/tests/test_fetchurls_sharepoint.py +0 -0
  166. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/tests/test_identify_step.py +0 -0
  167. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/tests/test_people_facets.py +0 -0
  168. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/tests/test_phase1.py +0 -0
  169. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/tests/test_phase2_jobs.py +0 -0
  170. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/tests/test_phase3_jobs.py +0 -0
  171. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/tests/test_phase4_live_fixes.py +0 -0
  172. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/tests/test_playlist_import.py +0 -0
  173. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/tests/test_presence_refund.py +0 -0
  174. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/tests/test_promo_director.py +0 -0
  175. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/tests/test_promo_pool_exclusion.py +0 -0
  176. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/tests/test_providers.py +0 -0
  177. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/tests/test_queue_announce.py +0 -0
  178. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/tests/test_race_poller.py +0 -0
  179. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/tests/test_recently_played_hide.py +0 -0
  180. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/tests/test_save_results_to_playlist.py +0 -0
  181. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/tests/test_schedule_lock.py +0 -0
  182. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/tests/test_search_facets.py +0 -0
  183. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/tests/test_title_normalization.py +0 -0
  184. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/tests/test_tmdb_index.py +0 -0
  185. {kryten_webqueue-0.40.2 → kryten_webqueue-0.40.4}/tests/test_watchlist.py +0 -0
@@ -2,6 +2,30 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.40.4] - 2026-08-21
6
+
7
+ ### Added
8
+
9
+ - **Keyboard pagination on browse/search pages.** The Left and Right arrow keys
10
+ now navigate to the previous/next page of results. Keys are ignored while
11
+ typing in a field or when a modal is open, and fall through to the browser's
12
+ default behaviour on the first/last page. Backed by a new global
13
+ `keybindings.js` registry (`Keybindings.register`/`unregister`) so future
14
+ shortcuts can be added without re-implementing the "don't steal keys while
15
+ typing" guard.
16
+
17
+ ## [0.40.3] - 2026-08-21
18
+
19
+ ### Fixed
20
+
21
+ - **Browse/search pagination no longer bounces back to page 1 when a non-default
22
+ duration or sort is active.** Pagination links omitted the `duration` filter
23
+ (and carried a stale `hide_short=0` param), so paging landed on a URL without
24
+ `duration`; the client-side preference-restore then re-applied the saved filter
25
+ via `applyFacets()`, which rebuilds the URL without `page` — sending the user
26
+ to page 1. Links now carry `duration`, and `restorePreferences()` skips any URL
27
+ that already has a `page` param so explicit pagination is never hijacked.
28
+
5
29
  ## [0.40.2] - 2026-08-21
6
30
 
7
31
  ### Changed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: kryten-webqueue
3
- Version: 0.40.2
3
+ Version: 0.40.4
4
4
  Summary: Netflix/Tubi-style catalog browser and pay-to-play queue management for CyTube
5
5
  Author: grobertson
6
6
  License-Expression: MIT
@@ -0,0 +1,59 @@
1
+ /* kryten-webqueue — Keyboard shortcuts
2
+ *
3
+ * A tiny global registry so pages can bind keys without each re-implementing
4
+ * "ignore keystrokes while the user is typing / a modal is open" logic.
5
+ *
6
+ * Usage:
7
+ * Keybindings.register('ArrowRight', () => { ... }); // return false to fall through
8
+ * Keybindings.unregister('ArrowRight');
9
+ *
10
+ * Key strings are modifier-prefixed and use the raw KeyboardEvent.key value,
11
+ * e.g. 'ArrowLeft', 'ctrl+k', 'shift+/'.
12
+ */
13
+ (function () {
14
+ const bindings = new Map();
15
+
16
+ function isEditableTarget(el) {
17
+ if (!el) return false;
18
+ const tag = el.tagName;
19
+ if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT') return true;
20
+ return !!el.isContentEditable;
21
+ }
22
+
23
+ function keyFromEvent(e) {
24
+ const parts = [];
25
+ if (e.ctrlKey) parts.push('ctrl');
26
+ if (e.altKey) parts.push('alt');
27
+ if (e.shiftKey) parts.push('shift');
28
+ if (e.metaKey) parts.push('meta');
29
+ parts.push(e.key);
30
+ return parts.join('+');
31
+ }
32
+
33
+ function onKeyDown(e) {
34
+ // Never steal keys while typing in a field.
35
+ if (isEditableTarget(document.activeElement)) return;
36
+ // Leave an open modal to its own keyboard handling.
37
+ if (document.querySelector('.modal-overlay')) return;
38
+
39
+ const handler = bindings.get(keyFromEvent(e));
40
+ if (!handler) return;
41
+
42
+ // Handlers return false to decline (e.g. no target to act on), in which
43
+ // case we leave the browser's default behaviour intact.
44
+ if (handler(e) !== false) {
45
+ e.preventDefault();
46
+ }
47
+ }
48
+
49
+ document.addEventListener('keydown', onKeyDown);
50
+
51
+ window.Keybindings = {
52
+ register(key, handler) {
53
+ bindings.set(key, handler);
54
+ },
55
+ unregister(key) {
56
+ bindings.delete(key);
57
+ },
58
+ };
59
+ })();
@@ -61,6 +61,7 @@
61
61
  </footer>
62
62
 
63
63
  <script src="/static/js/main.js"></script>
64
+ <script src="/static/js/keybindings.js"></script>
64
65
  {% block scripts %}{% endblock %}
65
66
  </body>
66
67
  </html>
@@ -125,7 +125,7 @@
125
125
  </div>
126
126
 
127
127
  {% set sort_q = ('&sort=' ~ sort) if (sort and sort != 'default') else '' %}
128
- {% set hide_short_q = '' if hide_short else '&hide_short=0' %}
128
+ {% set duration_q = ('&duration=' ~ duration) if (duration and duration != '10+') else '' %}
129
129
  {% set person_q = ('&person=' ~ active_person | urlencode) if active_person else '' %}
130
130
  {% set studio_q = ('&studio=' ~ active_studio | urlencode) if active_studio else '' %}
131
131
  {% if not items %}
@@ -168,7 +168,7 @@
168
168
 
169
169
  <div class="pagination">
170
170
  {% if page > 1 %}
171
- <a href="?page={{ page - 1 }}{% if query %}&q={{ query }}{% endif %}{% if active_category %}&category={{ active_category }}{% endif %}{% if active_tag %}&tag={{ active_tag | urlencode }}{% endif %}{{ sort_q }}{{ hide_short_q }}{{ person_q }}{{ studio_q }}" class="btn btn-sm">&larr; Prev</a>
171
+ <a id="pagination-prev" href="?page={{ page - 1 }}{% if query %}&q={{ query }}{% endif %}{% if active_category %}&category={{ active_category }}{% endif %}{% if active_tag %}&tag={{ active_tag | urlencode }}{% endif %}{{ sort_q }}{{ duration_q }}{{ person_q }}{{ studio_q }}" class="btn btn-sm">&larr; Prev</a>
172
172
  {% endif %}
173
173
 
174
174
  {% set start_page = [1, page - 2] | max %}
@@ -177,14 +177,14 @@
177
177
  {% if p == page %}
178
178
  <span class="page-num page-current">{{ p }}</span>
179
179
  {% else %}
180
- <a href="?page={{ p }}{% if query %}&q={{ query }}{% endif %}{% if active_category %}&category={{ active_category }}{% endif %}{% if active_tag %}&tag={{ active_tag | urlencode }}{% endif %}{{ sort_q }}{{ hide_short_q }}{{ person_q }}{{ studio_q }}" class="btn btn-sm btn-page">{{ p }}</a>
180
+ <a href="?page={{ p }}{% if query %}&q={{ query }}{% endif %}{% if active_category %}&category={{ active_category }}{% endif %}{% if active_tag %}&tag={{ active_tag | urlencode }}{% endif %}{{ sort_q }}{{ duration_q }}{{ person_q }}{{ studio_q }}" class="btn btn-sm btn-page">{{ p }}</a>
181
181
  {% endif %}
182
182
  {% endfor %}
183
183
 
184
184
  <span class="page-info">of {{ total_pages }}</span>
185
185
 
186
186
  {% if page < total_pages %}
187
- <a href="?page={{ page + 1 }}{% if query %}&q={{ query }}{% endif %}{% if active_category %}&category={{ active_category }}{% endif %}{% if active_tag %}&tag={{ active_tag | urlencode }}{% endif %}{{ sort_q }}{{ hide_short_q }}{{ person_q }}{{ studio_q }}" class="btn btn-sm">Next &rarr;</a>
187
+ <a id="pagination-next" href="?page={{ page + 1 }}{% if query %}&q={{ query }}{% endif %}{% if active_category %}&category={{ active_category }}{% endif %}{% if active_tag %}&tag={{ active_tag | urlencode }}{% endif %}{{ sort_q }}{{ duration_q }}{{ person_q }}{{ studio_q }}" class="btn btn-sm">Next &rarr;</a>
188
188
  {% endif %}
189
189
  </div>
190
190
  {% endblock %}
@@ -229,7 +229,11 @@ function applyFacets() {
229
229
  const sortSel = document.getElementById('sort-select');
230
230
  const durationSel = document.getElementById('duration-select');
231
231
  const url = new URL(window.location.href);
232
-
232
+
233
+ // Never hijack an explicit paginated navigation: applyFacets() drops the
234
+ // page, so restoring a saved pref here would bounce the user back to page 1.
235
+ if (url.searchParams.has('page')) return;
236
+
233
237
  // Restore sort preference
234
238
  if (sortSel && !url.searchParams.has('sort')) {
235
239
  let saved = null;
@@ -471,6 +475,22 @@ async function clearPlayed(token) {
471
475
  }
472
476
  // queueItem / playNext / queueAsAdmin / showToast / escapeHtml are provided by /static/js/main.js
473
477
  initWatchlistButtons();
478
+
479
+ // Left/Right arrows page through browse/search results. Handlers decline
480
+ // (return false) when there's no prev/next link so the browser keeps its
481
+ // default arrow behaviour on the first/last page.
482
+ if (window.Keybindings) {
483
+ Keybindings.register('ArrowLeft', () => {
484
+ const prev = document.getElementById('pagination-prev');
485
+ if (!prev) return false;
486
+ window.location.href = prev.href;
487
+ });
488
+ Keybindings.register('ArrowRight', () => {
489
+ const next = document.getElementById('pagination-next');
490
+ if (!next) return false;
491
+ window.location.href = next.href;
492
+ });
493
+ }
474
494
  </script>
475
495
 
476
496
  {% include "catalog/edit_item_modal.html" %}
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "kryten-webqueue"
3
- version = "0.40.2"
3
+ version = "0.40.4"
4
4
  description = "Netflix/Tubi-style catalog browser and pay-to-play queue management for CyTube"
5
5
  readme = "README.md"
6
6
  license = "MIT"