kryten-webqueue 0.7.3__tar.gz → 0.7.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 (67) hide show
  1. {kryten_webqueue-0.7.3 → kryten_webqueue-0.7.4}/CHANGELOG.md +6 -0
  2. {kryten_webqueue-0.7.3 → kryten_webqueue-0.7.4}/PKG-INFO +1 -1
  3. {kryten_webqueue-0.7.3 → kryten_webqueue-0.7.4}/kryten_webqueue/catalog/db.py +2 -2
  4. {kryten_webqueue-0.7.3 → kryten_webqueue-0.7.4}/kryten_webqueue/queue/shadow.py +1 -1
  5. {kryten_webqueue-0.7.3 → kryten_webqueue-0.7.4}/kryten_webqueue/routes/pages.py +3 -0
  6. {kryten_webqueue-0.7.3 → kryten_webqueue-0.7.4}/kryten_webqueue/static/css/main.css +70 -0
  7. {kryten_webqueue-0.7.3 → kryten_webqueue-0.7.4}/kryten_webqueue/templates/catalog/item_detail.html +28 -6
  8. {kryten_webqueue-0.7.3 → kryten_webqueue-0.7.4}/pyproject.toml +1 -1
  9. {kryten_webqueue-0.7.3 → kryten_webqueue-0.7.4}/.github/workflows/python-publish.yml +0 -0
  10. {kryten_webqueue-0.7.3 → kryten_webqueue-0.7.4}/.github/workflows/release.yml +0 -0
  11. {kryten_webqueue-0.7.3 → kryten_webqueue-0.7.4}/.gitignore +0 -0
  12. {kryten_webqueue-0.7.3 → kryten_webqueue-0.7.4}/README.md +0 -0
  13. {kryten_webqueue-0.7.3 → kryten_webqueue-0.7.4}/config.example.json +0 -0
  14. {kryten_webqueue-0.7.3 → kryten_webqueue-0.7.4}/deploy/kryten-webqueue.service +0 -0
  15. {kryten_webqueue-0.7.3 → kryten_webqueue-0.7.4}/deploy/nginx-queue.conf +0 -0
  16. {kryten_webqueue-0.7.3 → kryten_webqueue-0.7.4}/docs/IMPLEMENTATION_SPEC.md +0 -0
  17. {kryten_webqueue-0.7.3 → kryten_webqueue-0.7.4}/docs/IMPL_API_GATE.md +0 -0
  18. {kryten_webqueue-0.7.3 → kryten_webqueue-0.7.4}/docs/IMPL_ECONOMY.md +0 -0
  19. {kryten_webqueue-0.7.3 → kryten_webqueue-0.7.4}/docs/IMPL_KRYTEN_PY.md +0 -0
  20. {kryten_webqueue-0.7.3 → kryten_webqueue-0.7.4}/docs/IMPL_ROBOT.md +0 -0
  21. {kryten_webqueue-0.7.3 → kryten_webqueue-0.7.4}/docs/PRE_PLAN_GAPS.md +0 -0
  22. {kryten_webqueue-0.7.3 → kryten_webqueue-0.7.4}/docs/PRODUCT_PLAN.md +0 -0
  23. {kryten_webqueue-0.7.3 → kryten_webqueue-0.7.4}/kryten_webqueue/__init__.py +0 -0
  24. {kryten_webqueue-0.7.3 → kryten_webqueue-0.7.4}/kryten_webqueue/__main__.py +0 -0
  25. {kryten_webqueue-0.7.3 → kryten_webqueue-0.7.4}/kryten_webqueue/api_gate/__init__.py +0 -0
  26. {kryten_webqueue-0.7.3 → kryten_webqueue-0.7.4}/kryten_webqueue/api_gate/client.py +0 -0
  27. {kryten_webqueue-0.7.3 → kryten_webqueue-0.7.4}/kryten_webqueue/app.py +0 -0
  28. {kryten_webqueue-0.7.3 → kryten_webqueue-0.7.4}/kryten_webqueue/auth/__init__.py +0 -0
  29. {kryten_webqueue-0.7.3 → kryten_webqueue-0.7.4}/kryten_webqueue/auth/otp.py +0 -0
  30. {kryten_webqueue-0.7.3 → kryten_webqueue-0.7.4}/kryten_webqueue/auth/rate_limit.py +0 -0
  31. {kryten_webqueue-0.7.3 → kryten_webqueue-0.7.4}/kryten_webqueue/auth/session.py +0 -0
  32. {kryten_webqueue-0.7.3 → kryten_webqueue-0.7.4}/kryten_webqueue/catalog/__init__.py +0 -0
  33. {kryten_webqueue-0.7.3 → kryten_webqueue-0.7.4}/kryten_webqueue/catalog/images.py +0 -0
  34. {kryten_webqueue-0.7.3 → kryten_webqueue-0.7.4}/kryten_webqueue/catalog/sync.py +0 -0
  35. {kryten_webqueue-0.7.3 → kryten_webqueue-0.7.4}/kryten_webqueue/config.py +0 -0
  36. {kryten_webqueue-0.7.3 → kryten_webqueue-0.7.4}/kryten_webqueue/jobs/__init__.py +0 -0
  37. {kryten_webqueue-0.7.3 → kryten_webqueue-0.7.4}/kryten_webqueue/jobs/manager.py +0 -0
  38. {kryten_webqueue-0.7.3 → kryten_webqueue-0.7.4}/kryten_webqueue/playlists/__init__.py +0 -0
  39. {kryten_webqueue-0.7.3 → kryten_webqueue-0.7.4}/kryten_webqueue/playlists/fire.py +0 -0
  40. {kryten_webqueue-0.7.3 → kryten_webqueue-0.7.4}/kryten_webqueue/playlists/importer.py +0 -0
  41. {kryten_webqueue-0.7.3 → kryten_webqueue-0.7.4}/kryten_webqueue/playlists/scheduler.py +0 -0
  42. {kryten_webqueue-0.7.3 → kryten_webqueue-0.7.4}/kryten_webqueue/queue/__init__.py +0 -0
  43. {kryten_webqueue-0.7.3 → kryten_webqueue-0.7.4}/kryten_webqueue/queue/ordering.py +0 -0
  44. {kryten_webqueue-0.7.3 → kryten_webqueue-0.7.4}/kryten_webqueue/queue/poller.py +0 -0
  45. {kryten_webqueue-0.7.3 → kryten_webqueue-0.7.4}/kryten_webqueue/routes/__init__.py +0 -0
  46. {kryten_webqueue-0.7.3 → kryten_webqueue-0.7.4}/kryten_webqueue/routes/admin_jobs.py +0 -0
  47. {kryten_webqueue-0.7.3 → kryten_webqueue-0.7.4}/kryten_webqueue/routes/admin_playlists.py +0 -0
  48. {kryten_webqueue-0.7.3 → kryten_webqueue-0.7.4}/kryten_webqueue/routes/admin_queue.py +0 -0
  49. {kryten_webqueue-0.7.3 → kryten_webqueue-0.7.4}/kryten_webqueue/routes/admin_schedules.py +0 -0
  50. {kryten_webqueue-0.7.3 → kryten_webqueue-0.7.4}/kryten_webqueue/routes/auth.py +0 -0
  51. {kryten_webqueue-0.7.3 → kryten_webqueue-0.7.4}/kryten_webqueue/routes/catalog.py +0 -0
  52. {kryten_webqueue-0.7.3 → kryten_webqueue-0.7.4}/kryten_webqueue/routes/queue.py +0 -0
  53. {kryten_webqueue-0.7.3 → kryten_webqueue-0.7.4}/kryten_webqueue/routes/user.py +0 -0
  54. {kryten_webqueue-0.7.3 → kryten_webqueue-0.7.4}/kryten_webqueue/static/js/main.js +0 -0
  55. {kryten_webqueue-0.7.3 → kryten_webqueue-0.7.4}/kryten_webqueue/templates/admin/index.html +0 -0
  56. {kryten_webqueue-0.7.3 → kryten_webqueue-0.7.4}/kryten_webqueue/templates/admin/playlists.html +0 -0
  57. {kryten_webqueue-0.7.3 → kryten_webqueue-0.7.4}/kryten_webqueue/templates/admin/queue_mgmt.html +0 -0
  58. {kryten_webqueue-0.7.3 → kryten_webqueue-0.7.4}/kryten_webqueue/templates/admin/schedules.html +0 -0
  59. {kryten_webqueue-0.7.3 → kryten_webqueue-0.7.4}/kryten_webqueue/templates/auth/login.html +0 -0
  60. {kryten_webqueue-0.7.3 → kryten_webqueue-0.7.4}/kryten_webqueue/templates/base.html +0 -0
  61. {kryten_webqueue-0.7.3 → kryten_webqueue-0.7.4}/kryten_webqueue/templates/catalog/browse.html +0 -0
  62. {kryten_webqueue-0.7.3 → kryten_webqueue-0.7.4}/kryten_webqueue/templates/catalog/item_not_found.html +0 -0
  63. {kryten_webqueue-0.7.3 → kryten_webqueue-0.7.4}/kryten_webqueue/templates/queue/index.html +0 -0
  64. {kryten_webqueue-0.7.3 → kryten_webqueue-0.7.4}/kryten_webqueue/templates/user/dashboard.html +0 -0
  65. {kryten_webqueue-0.7.3 → kryten_webqueue-0.7.4}/kryten_webqueue/ws/__init__.py +0 -0
  66. {kryten_webqueue-0.7.3 → kryten_webqueue-0.7.4}/kryten_webqueue/ws/handler.py +0 -0
  67. {kryten_webqueue-0.7.3 → kryten_webqueue-0.7.4}/kryten_webqueue/ws/manager.py +0 -0
@@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+ ## [0.7.4] - 2026-06-08
8
+
9
+ ### Changed
10
+
11
+ - **Catalog item detail page redesigned** to a two-column layout: the poster and action buttons (Queue / Play Next / Queue as Admin) stack in a sticky left column, while the right column shows the title, a divider, the formatted description, and **Category** / **Tags** facet rows. Category and tag chips link back into a filtered catalog browse. Categories/tags are sourced from the catalog join tables (populated by sync since 0.7.0).
12
+
7
13
  ## [0.7.3] - 2026-06-08
8
14
 
9
15
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kryten-webqueue
3
- Version: 0.7.3
3
+ Version: 0.7.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
@@ -409,7 +409,7 @@ class Database:
409
409
  "SELECT description FROM catalog WHERE friendly_token = ?", [friendly_token]
410
410
  )
411
411
  cats = await self._fetch_all(
412
- "SELECT cat.name FROM categories cat "
412
+ "SELECT cat.name, cat.slug FROM categories cat "
413
413
  "JOIN catalog_categories cc ON cc.category_id = cat.id "
414
414
  "WHERE cc.friendly_token = ? ORDER BY cat.name",
415
415
  [friendly_token],
@@ -422,7 +422,7 @@ class Database:
422
422
  )
423
423
  return {
424
424
  "description": (row or {}).get("description"),
425
- "categories": [c["name"] for c in cats],
425
+ "categories": [{"name": c["name"], "slug": c["slug"]} for c in cats],
426
426
  "tags": [t["name"] for t in tags],
427
427
  }
428
428
 
@@ -222,7 +222,7 @@ class QueueShadow:
222
222
  try:
223
223
  facets = await db.get_item_facets(np["friendly_token"])
224
224
  np.setdefault("description", facets.get("description"))
225
- np["categories"] = facets.get("categories") or []
225
+ np["categories"] = [c["name"] for c in (facets.get("categories") or [])]
226
226
  np["tags"] = facets.get("tags") or []
227
227
  except Exception:
228
228
  logger.debug("Failed to enrich now-playing facets", exc_info=True)
@@ -109,9 +109,12 @@ async def catalog_item_page(request: Request, friendly_token: str):
109
109
  return templates.TemplateResponse(
110
110
  request, "catalog/item_not_found.html", {"user": user}, status_code=404
111
111
  )
112
+ facets = await db.get_item_facets(friendly_token)
112
113
  return templates.TemplateResponse(request, "catalog/item_detail.html", {
113
114
  "user": user,
114
115
  "item": item,
116
+ "categories": facets.get("categories") or [],
117
+ "tags": facets.get("tags") or [],
115
118
  })
116
119
 
117
120
 
@@ -507,12 +507,82 @@ a:hover {
507
507
  white-space: pre-line;
508
508
  overflow-wrap: anywhere;
509
509
  }
510
+ /* Item detail: poster + buttons on the left, title/description/facets on right. */
511
+ .item-detail-layout {
512
+ display: grid;
513
+ grid-template-columns: 300px minmax(0, 1fr);
514
+ gap: 2rem;
515
+ align-items: start;
516
+ }
517
+ @media (max-width: 700px) {
518
+ .item-detail-layout {
519
+ grid-template-columns: 1fr;
520
+ }
521
+ }
522
+ .item-detail-poster {
523
+ display: flex;
524
+ flex-direction: column;
525
+ gap: 1rem;
526
+ position: sticky;
527
+ top: calc(var(--nav-height) + 1rem);
528
+ }
529
+ .item-detail-poster img,
530
+ .item-detail-poster .card-poster-placeholder {
531
+ width: 100%;
532
+ aspect-ratio: 2 / 3;
533
+ object-fit: cover;
534
+ border-radius: var(--radius);
535
+ display: block;
536
+ }
537
+ .item-detail-actions {
538
+ display: flex;
539
+ flex-direction: column;
540
+ gap: 0.5rem;
541
+ }
542
+ .item-detail-actions .btn {
543
+ width: 100%;
544
+ }
545
+ .item-detail-info {
546
+ min-width: 0;
547
+ }
548
+ .item-detail-title {
549
+ margin: 0;
550
+ line-height: 1.2;
551
+ overflow-wrap: anywhere;
552
+ }
553
+ .item-detail-meta {
554
+ margin-top: 0.35rem;
555
+ font-size: 0.85rem;
556
+ color: var(--text-secondary);
557
+ }
510
558
  /* MediaCMS descriptions are newline-delimited plain text; preserve the line
511
559
  breaks (Synopsis / Tagline / Cast & Crew sections) instead of collapsing them. */
512
560
  .item-detail-description {
513
561
  white-space: pre-line;
514
562
  overflow-wrap: anywhere;
515
563
  line-height: 1.6;
564
+ margin-top: 0.75rem;
565
+ padding-top: 0.75rem;
566
+ border-top: 1px solid var(--border);
567
+ }
568
+ .item-detail-facet {
569
+ margin-top: 1rem;
570
+ }
571
+ .item-detail-facet-label {
572
+ display: block;
573
+ font-size: 0.75rem;
574
+ text-transform: uppercase;
575
+ letter-spacing: 0.05em;
576
+ color: var(--text-secondary);
577
+ margin-bottom: 0.4rem;
578
+ }
579
+ .item-detail-chips {
580
+ display: flex;
581
+ flex-wrap: wrap;
582
+ gap: 0.4rem;
583
+ }
584
+ a.np-chip {
585
+ text-decoration: none;
516
586
  }
517
587
  .np-chips {
518
588
  display: flex;
@@ -19,6 +19,14 @@
19
19
  {% else %}
20
20
  <div class="card-poster-placeholder"><span>{{ item.title[:1] }}</span></div>
21
21
  {% endif %}
22
+
23
+ <div class="card-actions item-detail-actions">
24
+ <button class="btn btn-queue" onclick="queueItem('{{ item.friendly_token }}')">Queue</button>
25
+ <button class="btn btn-playnext" onclick="playNext('{{ item.friendly_token }}')">Play Next</button>
26
+ {% if user.rank >= 3 %}
27
+ <button class="btn btn-admin" onclick="queueAsAdmin('{{ item.friendly_token }}')">Queue as Admin</button>
28
+ {% endif %}
29
+ </div>
22
30
  </div>
23
31
 
24
32
  <div class="item-detail-info">
@@ -35,13 +43,27 @@
35
43
  <p class="item-detail-description empty-state">No description available.</p>
36
44
  {% endif %}
37
45
 
38
- <div class="card-actions item-detail-actions">
39
- <button class="btn btn-queue" onclick="queueItem('{{ item.friendly_token }}')">Queue</button>
40
- <button class="btn btn-playnext" onclick="playNext('{{ item.friendly_token }}')">Play Next</button>
41
- {% if user.rank >= 3 %}
42
- <button class="btn btn-admin" onclick="queueAsAdmin('{{ item.friendly_token }}')">Queue as Admin</button>
43
- {% endif %}
46
+ {% if categories %}
47
+ <div class="item-detail-facet">
48
+ <span class="item-detail-facet-label">Category</span>
49
+ <div class="item-detail-chips">
50
+ {% for cat in categories %}
51
+ <a class="np-chip np-chip-cat" href="/catalog/browse?category={{ cat.slug }}">{{ cat.name }}</a>
52
+ {% endfor %}
53
+ </div>
44
54
  </div>
55
+ {% endif %}
56
+
57
+ {% if tags %}
58
+ <div class="item-detail-facet">
59
+ <span class="item-detail-facet-label">Tags</span>
60
+ <div class="item-detail-chips">
61
+ {% for tag in tags %}
62
+ <a class="np-chip np-chip-tag" href="/catalog/browse?tag={{ tag | urlencode }}">{{ tag }}</a>
63
+ {% endfor %}
64
+ </div>
65
+ </div>
66
+ {% endif %}
45
67
  </div>
46
68
  </div>
47
69
  </div>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "kryten-webqueue"
3
- version = "0.7.3"
3
+ version = "0.7.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"