kryten-webqueue 0.15.2__tar.gz → 0.16.0__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.
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/CHANGELOG.md +12 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/PKG-INFO +1 -1
- kryten_webqueue-0.16.0/docs/UX_POLISH_PLAN.md +246 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/static/css/main.css +56 -1
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/static/js/main.js +33 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/templates/admin/playlists.html +4 -4
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/templates/admin/promos.html +3 -3
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/templates/admin/schedules.html +1 -1
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/templates/base.html +17 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/pyproject.toml +1 -1
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/.github/workflows/python-publish.yml +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/.github/workflows/release.yml +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/.gitignore +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/README.md +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/config.example.json +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/deploy/kryten-webqueue.service +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/deploy/nginx-queue.conf +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/docs/IMPLEMENTATION_SPEC.md +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/docs/IMPL_API_GATE.md +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/docs/IMPL_ECONOMY.md +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/docs/IMPL_KRYTEN_PY.md +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/docs/IMPL_ROBOT.md +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/docs/PLAN_PRESENCE_AND_PROMOS.md +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/docs/PRE_PLAN_GAPS.md +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/docs/PRODUCT_PLAN.md +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/docs/SPEC_JOBS_AND_BROWSE.md +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/__init__.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/__main__.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/api_gate/__init__.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/api_gate/client.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/app.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/auth/__init__.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/auth/otp.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/auth/rate_limit.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/auth/session.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/catalog/__init__.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/catalog/db.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/catalog/images.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/catalog/mediacms.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/catalog/sync.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/config.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/integrations/__init__.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/integrations/cmsutils/__init__.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/integrations/cmsutils/_common.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/integrations/cmsutils/enrichmeta.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/integrations/cmsutils/enrichtitles.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/integrations/cmsutils/enrichtv.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/integrations/cmsutils/fetchurls.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/integrations/ytpipe/__init__.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/integrations/ytpipe/downloader.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/jobs/__init__.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/jobs/fetchurls_auth.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/jobs/manager.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/jobs/tasks.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/logging_config.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/playlists/__init__.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/playlists/bulk_add.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/playlists/fire.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/playlists/importer.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/playlists/ordering.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/playlists/scheduler.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/promos/__init__.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/promos/director.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/queue/__init__.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/queue/ordering.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/queue/poller.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/queue/presence.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/queue/shadow.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/routes/__init__.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/routes/admin_catalog.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/routes/admin_jobs.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/routes/admin_playlists.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/routes/admin_promos.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/routes/admin_queue.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/routes/admin_schedules.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/routes/auth.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/routes/catalog.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/routes/pages.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/routes/queue.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/routes/user.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/templates/admin/index.html +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/templates/admin/queue_mgmt.html +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/templates/auth/login.html +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/templates/catalog/browse.html +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/templates/catalog/item_detail.html +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/templates/catalog/item_not_found.html +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/templates/queue/index.html +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/templates/user/dashboard.html +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/ws/__init__.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/ws/handler.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/ws/manager.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/tests/__init__.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/tests/test_config_persistence.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/tests/test_fetchurls_sharepoint.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/tests/test_phase1.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/tests/test_phase2_jobs.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/tests/test_phase3_jobs.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/tests/test_phase4_live_fixes.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/tests/test_playlist_import.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/tests/test_presence_refund.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/tests/test_promo_director.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/tests/test_promo_pool_exclusion.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/tests/test_queue_announce.py +0 -0
- {kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/tests/test_save_results_to_playlist.py +0 -0
|
@@ -6,6 +6,18 @@ 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
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.16.0] — 2026-06-17
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **Light / dark theme toggle.** A navbar button switches between dark (default) and light themes. The choice persists in `localStorage`; first-time visitors follow their OS `prefers-color-scheme`. An inline pre-paint script in `base.html` applies the saved/preferred theme before first render to avoid a flash. Light/dark palettes are defined as `:root[data-theme="..."]` overrides of the existing CSS variables, so the whole UI re-themes without per-component changes.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- **Clearer playlist terminology (display only).** Admin playlist/schedule/promo screens now label reserved playlists as **Non-preemptable** and normal ones as **Preemptable** (previously "Immutable" / "Mutable"). This is a wording change only — the `is_immutable` data field, API payloads, and config keys are unchanged.
|
|
18
|
+
|
|
19
|
+
[0.16.0]: https://github.com/grobertson/kryten-webqueue/releases/tag/v0.16.0
|
|
20
|
+
|
|
9
21
|
## [0.15.2] — 2026-06-17
|
|
10
22
|
|
|
11
23
|
### Fixed
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# kryten-webqueue — UX Polish & Convenience Plan
|
|
2
|
+
|
|
3
|
+
Status: **proposed (awaiting approval)**. No application code changed yet.
|
|
4
|
+
|
|
5
|
+
This plan covers seven UX/convenience items. Each can ship independently; grouped
|
|
6
|
+
into phases so each phase is independently verifiable and releasable.
|
|
7
|
+
|
|
8
|
+
Scope note: display-only relabeling and front-end live-refresh are deliberately
|
|
9
|
+
kept separate from data-model/API changes so nothing downstream (api-gate,
|
|
10
|
+
economy, DB schema) needs to move in lock-step.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Item 1 — Active event shows as "active" long after it ended
|
|
15
|
+
|
|
16
|
+
**Root cause.** The `active_schedule` row (single row, `id=1`) is only removed by
|
|
17
|
+
the admin "Clear Active Schedule" button (`clear_active_schedule()` →
|
|
18
|
+
`DELETE FROM active_schedule`). The in-progress *lock* auto-lifts
|
|
19
|
+
(`disable_active_lock()` sets `lock_disabled=1` when the last scheduled item
|
|
20
|
+
begins), but the **row itself persists**, so the admin banner keeps showing the
|
|
21
|
+
event. `estimated_end_at` is computed once at fire time and never enforced.
|
|
22
|
+
|
|
23
|
+
Relevant code:
|
|
24
|
+
- `catalog/db.py` — `set_active_schedule()`, `get_active_schedule()`,
|
|
25
|
+
`clear_active_schedule()`, `disable_active_lock()`, `is_event_lock_active()`,
|
|
26
|
+
and `active_schedule` schema (`last_item_uid`, `lock_disabled`, `estimated_end_at`).
|
|
27
|
+
- `queue/shadow.py` — `_maybe_lift_event_lock()` already detects when the last
|
|
28
|
+
scheduled item is playing (event effectively over).
|
|
29
|
+
- `templates/admin/schedules.html` — `loadActive()` renders the banner once.
|
|
30
|
+
|
|
31
|
+
**Fix (event-driven primary + safety net).**
|
|
32
|
+
1. Backend: when the scheduled event is truly over, clear the active row, don't
|
|
33
|
+
just lift the lock. Extend the existing `_maybe_lift_event_lock()` logic in
|
|
34
|
+
`shadow.apply_poll_result()`: once the last scheduled item (`last_item_uid`)
|
|
35
|
+
has played out (i.e. now-playing has advanced *past* it, or the row is gone
|
|
36
|
+
from the queue), call a new `db.expire_active_schedule_if_done()` that clears
|
|
37
|
+
the row. Keeps the existing "lift lock when last item *starts*" behavior, and
|
|
38
|
+
adds "clear active when last item *ends*".
|
|
39
|
+
2. Backend safety net: in the same path, if `estimated_end_at` is more than a
|
|
40
|
+
small grace (e.g. 5 min) in the past, clear the active row. Guards against a
|
|
41
|
+
missed boundary (manual queue edits, restart during an event).
|
|
42
|
+
3. Frontend: `loadActive()` treats a past `estimated_end_at` as "ended" (hide
|
|
43
|
+
banner) even before the backend clears it, and refreshes (see Item 2).
|
|
44
|
+
|
|
45
|
+
**Verification.** Fire a short immutable schedule; confirm the banner clears
|
|
46
|
+
shortly after the last item finishes (not on reload). Unit test the new db
|
|
47
|
+
helper: row present → played past last item → row cleared. Test the
|
|
48
|
+
`estimated_end_at` grace path.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Item 2 — Admin page doesn't live-update (queue, now-playing, jobs)
|
|
53
|
+
|
|
54
|
+
**Root cause.** `templates/admin/index.html` calls `loadAdminData()` /
|
|
55
|
+
`loadJobs()` **once** on load. Unlike `templates/queue/index.html`, it never
|
|
56
|
+
opens the `/ws` WebSocket, so queue size / now-playing / job status only refresh
|
|
57
|
+
on a manual reload. The poller already broadcasts `{"type":"queue_state"}` every
|
|
58
|
+
~3s (`queue/poller.py`).
|
|
59
|
+
|
|
60
|
+
**Fix.**
|
|
61
|
+
1. Subscribe the admin page to `/ws` (reuse the queue page's connect pattern).
|
|
62
|
+
On `queue_state`, update the "Queue Status" block (items count + now-playing).
|
|
63
|
+
On `schedule_fired`, refresh the active-schedule banner + queue status.
|
|
64
|
+
2. Jobs: lightweight `setInterval` (e.g. every 5s) re-fetch of `/admin/jobs` and
|
|
65
|
+
`/admin/jobs/runs?limit=10` while the tab is visible (pause via
|
|
66
|
+
`document.visibilityState` to avoid background churn). Jobs are DB-polled, not
|
|
67
|
+
broadcast, so polling is the pragmatic choice; interval is cheap.
|
|
68
|
+
3. Active schedule banner (shared with Item 1): re-render on the same interval
|
|
69
|
+
and on `schedule_fired`.
|
|
70
|
+
|
|
71
|
+
Relevant code:
|
|
72
|
+
- `templates/admin/index.html` — `loadAdminData()`, `loadJobs()`, init at bottom.
|
|
73
|
+
- `templates/queue/index.html` — `connectWebSocket()` reference implementation.
|
|
74
|
+
- `templates/admin/schedules.html` — `loadActive()` (extract/reuse).
|
|
75
|
+
|
|
76
|
+
**Verification.** Open admin page; queue another item from CyTube → count and
|
|
77
|
+
now-playing update within a few seconds without reload. Run a job → its status
|
|
78
|
+
flips to running then completed live.
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Item 3 — Richer logging, especially fetchurls failures
|
|
83
|
+
|
|
84
|
+
**Current state.** `jobs/manager.py::_execute()` already logs unexpected failures
|
|
85
|
+
with a full traceback (`logger.exception`) and records `{type}: {msg}` to the
|
|
86
|
+
`job_runs.detail`; `JobError` logs a clean WARNING. The integration
|
|
87
|
+
(`integrations/cmsutils/fetchurls.py`) emits progress phases but uses `print()`
|
|
88
|
+
for per-URL/per-section detail, so that detail never reaches the app logger or
|
|
89
|
+
the job record.
|
|
90
|
+
|
|
91
|
+
Gaps to close:
|
|
92
|
+
- fetchurls per-section results (resolved/failed counts + the failing URLs &
|
|
93
|
+
Excel row numbers) are not summarized into the logger or `job_runs.detail`.
|
|
94
|
+
- SharePoint download failures wrap into a `RuntimeError` that loses the HTTP
|
|
95
|
+
status / response snippet.
|
|
96
|
+
- `JobContext.progress()` swallows DB errors at debug only (acceptable, but note).
|
|
97
|
+
- General: the global log format (added in `logging_config.py`) can include
|
|
98
|
+
`filename:lineno` to make every line more actionable.
|
|
99
|
+
|
|
100
|
+
**Fix.**
|
|
101
|
+
1. `jobs/tasks.py::fetchurls_job` (and `_import_section_as_playlist`): after the
|
|
102
|
+
run, log an INFO summary per section (`name: resolved X / failed Y`) and a
|
|
103
|
+
WARNING listing each failed URL with its row number; fold a compact
|
|
104
|
+
`failures` array into the returned `result` so it lands in `job_runs.detail`
|
|
105
|
+
and shows in the admin "Detail" column.
|
|
106
|
+
2. Enrich the SharePoint download error to include HTTP status + a short response
|
|
107
|
+
excerpt before raising.
|
|
108
|
+
3. `logging_config.py`: extend the default formatter to
|
|
109
|
+
`%(asctime)s %(levelname)-8s %(name)s %(filename)s:%(lineno)d: %(message)s`
|
|
110
|
+
(app loggers only; keep uvicorn/access formats lean).
|
|
111
|
+
4. Convert the most useful `print()` lines in the fetchurls integration to
|
|
112
|
+
`logger.info/warning` (guarded so standalone CLI use still prints).
|
|
113
|
+
|
|
114
|
+
**Verification.** Run fetchurls with a deliberately bad URL; confirm the admin
|
|
115
|
+
job "Detail" shows the failing URL + row, and the process log has an INFO
|
|
116
|
+
section summary + WARNING per failure with `file:line`.
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Item 4 — Search string and category/tag filters don't combine
|
|
121
|
+
|
|
122
|
+
**Root cause.** Two separate code paths. `db.browse()` ANDs category+tag via
|
|
123
|
+
subqueries; `db.search()` (FTS5 `MATCH`) accepts **no** category/tag. The
|
|
124
|
+
frontend `applyFacets()` drops the facet dropdowns entirely when a query is
|
|
125
|
+
active (`if (CURRENT_QUERY) {...} else { set category/tag }`), and
|
|
126
|
+
`/catalog/search` neither accepts facets nor returns facet lists.
|
|
127
|
+
|
|
128
|
+
**Fix (recommended: make them AND together).**
|
|
129
|
+
1. `db.search()`: add optional `category` / `tag` params and append the same
|
|
130
|
+
`AND friendly_token IN (… categories …)` / `AND … IN (… tags …)` subqueries
|
|
131
|
+
`browse()` already uses (intersection with the FTS match).
|
|
132
|
+
2. `routes/catalog.py::search`: accept `category` & `tag`, pass through, and also
|
|
133
|
+
return `categories`/`tags` facet lists (like `/browse`) plus `active_category`
|
|
134
|
+
/ `active_tag` so the template can keep selections.
|
|
135
|
+
3. `templates/catalog/browse.html::applyFacets()`: when a query is present,
|
|
136
|
+
include `category`/`tag` in the search URL instead of discarding them; keep
|
|
137
|
+
the dropdowns populated and selected on the results page.
|
|
138
|
+
|
|
139
|
+
Fallback option (if you'd rather not expand search): visually disable the facet
|
|
140
|
+
dropdowns on a search results page with a tooltip ("Clear search to filter by
|
|
141
|
+
category/tag"). Cheaper, but less capable. **Recommendation: do the real fix.**
|
|
142
|
+
|
|
143
|
+
**Verification.** Search "matrix" + pick a category → results are the
|
|
144
|
+
intersection. Remove the query → browse facets still AND as before. Add a small
|
|
145
|
+
test for `db.search(category=…, tag=…)`.
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Item 5 — Rename Mutable/Immutable → Preemptable/Non-preemptable (display only)
|
|
150
|
+
|
|
151
|
+
**Root cause.** Pure UX wording. The data field `is_immutable` (DB column, API
|
|
152
|
+
body, JS variable) must stay; only visible labels change.
|
|
153
|
+
|
|
154
|
+
**Fix (display-only).** In `templates/admin/playlists.html`:
|
|
155
|
+
- Badges: "Immutable" → "Non-preemptable"; "Mutable" → "Preemptable" (L~96).
|
|
156
|
+
- Create-modal checkbox label (L~128) and editor metadata text (L~164/170).
|
|
157
|
+
- Confirm dialog copy in `toggleImmutable()` if it references the words.
|
|
158
|
+
- Keep the button verbs ("Reserve"/"Release") as-is (decision 2 default).
|
|
159
|
+
In `templates/admin/schedules.html`: the active-banner "Immutable" badge (L~40)
|
|
160
|
+
→ "Non-preemptable".
|
|
161
|
+
|
|
162
|
+
Do **not** change: `is_immutable` column, `set_active_schedule(is_immutable=…)`,
|
|
163
|
+
API request/response keys, JS variable names, or config keys.
|
|
164
|
+
|
|
165
|
+
**Verification.** Grep templates for user-visible "mutable"/"immutable" → none
|
|
166
|
+
remain (data attributes/keys excluded). Page renders new labels; toggle still
|
|
167
|
+
posts `is_immutable`.
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## Item 6 — Zcoin dashboard: tabbed container + wider account column
|
|
172
|
+
|
|
173
|
+
**Current.** `templates/user/dashboard.html` is a 3-column grid
|
|
174
|
+
(`balance-card | history-card | transactions-card`), with vanity controls
|
|
175
|
+
crammed into the left balance card.
|
|
176
|
+
|
|
177
|
+
**Fix.** Two-region layout:
|
|
178
|
+
- **Left (widened) account card:** balance, rank/level, progress, perks. Remove
|
|
179
|
+
the vanity block from here.
|
|
180
|
+
- **Right (wide) tabbed container** with three tabs, lazy-loaded on first show:
|
|
181
|
+
1. **Queue History** (existing `loadQueue()` + pager)
|
|
182
|
+
2. **Recent Transactions** (existing `loadTransactions()` + credit/debit toggle)
|
|
183
|
+
3. **Vanity Items** (moved here: greeting + chat-color editors, with room to
|
|
184
|
+
grow to other econ-surfaced properties later)
|
|
185
|
+
|
|
186
|
+
Implementation:
|
|
187
|
+
- Restructure `dashboard.html`: account card + `.tabs` (buttons) + `.tab-panel`s.
|
|
188
|
+
- Reuse existing JS (`loadAccount`, `loadQueue`, `loadTransactions`, vanity
|
|
189
|
+
dialogs); add a tiny tab controller that lazy-loads each panel once.
|
|
190
|
+
- `static/css/main.css`: change `.dashboard-grid` to a 2-column layout
|
|
191
|
+
(e.g. `minmax(280px, 360px) 1fr`), collapse to 1 column under ~900px; add
|
|
192
|
+
`.tabs`, `.tab-btn.active`, `.tab-panel[hidden]` styles (reuse `--accent`,
|
|
193
|
+
`--border`, etc.). Mirror the existing `.tx-toggle` styling for consistency.
|
|
194
|
+
|
|
195
|
+
**Verification.** Dashboard shows account card + tabs; switching tabs loads each
|
|
196
|
+
once; vanity edit/purchase still works from its tab; responsive collapse at
|
|
197
|
+
narrow widths. Existing economy endpoints unchanged.
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## Item 7 — Light / dark mode
|
|
202
|
+
|
|
203
|
+
**Foundation.** `static/css/main.css` already drives the entire UI from CSS
|
|
204
|
+
variables on `:root` (`--bg-*`, `--text-*`, `--accent`, `--border`, …). Adding a
|
|
205
|
+
theme is mainly a palette swap + a toggle; no per-component CSS rewrite needed.
|
|
206
|
+
|
|
207
|
+
**Fix.**
|
|
208
|
+
1. Define a light palette under `:root[data-theme="light"]` (and keep the current
|
|
209
|
+
dark values as the default `:root`). Tune `--bg-*`, `--text-*`, `--border`,
|
|
210
|
+
`--shadow`; keep `--accent` family. Add an explicit
|
|
211
|
+
`:root[data-theme="dark"]` block equal to the defaults so the toggle is
|
|
212
|
+
symmetric.
|
|
213
|
+
2. Default behavior: respect `prefers-color-scheme` when the user hasn't chosen;
|
|
214
|
+
persist an explicit choice in `localStorage` (`wq_theme`).
|
|
215
|
+
3. No-FOUC: a tiny inline script in `base.html <head>` sets
|
|
216
|
+
`document.documentElement.dataset.theme` from `localStorage`/media query
|
|
217
|
+
**before** CSS paints.
|
|
218
|
+
4. Toggle control in the navbar (`base.html`), wired in `static/js/main.js`:
|
|
219
|
+
flips `data-theme`, saves to `localStorage`, updates the icon/label.
|
|
220
|
+
Default: icon-only (🌙/☀️) with `aria-label` (decision 3 default).
|
|
221
|
+
5. Audit a few hard-coded colors (e.g. badge `rgba(...)` backgrounds, toast,
|
|
222
|
+
modal overlay) for acceptable contrast in light mode; promote any offenders
|
|
223
|
+
to variables.
|
|
224
|
+
|
|
225
|
+
**Verification.** Toggle flips instantly with no flash on reload; choice
|
|
226
|
+
persists; fresh visitor matches OS preference; spot-check catalog, queue, admin,
|
|
227
|
+
dashboard (incl. new tabs) and modals/toasts in both themes for contrast.
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## Suggested phasing (each independently releasable)
|
|
232
|
+
|
|
233
|
+
- **Phase A (quick wins / low risk):** Item 5 (relabel), Item 7 (theme).
|
|
234
|
+
- **Phase B (admin live + lifecycle):** Item 1 (active-event expiry) + Item 2
|
|
235
|
+
(admin live-update) — they share the active-schedule banner refresh.
|
|
236
|
+
- **Phase C (catalog):** Item 4 (search × facets).
|
|
237
|
+
- **Phase D (dashboard):** Item 6 (tabs) — self-contained.
|
|
238
|
+
- **Phase E (observability):** Item 3 (logging) — can land anytime; complements
|
|
239
|
+
the earlier promo observability work.
|
|
240
|
+
|
|
241
|
+
## Open questions / decisions (defaults chosen)
|
|
242
|
+
1. Item 4: real combine **(recommended, default)** vs. disable-with-tooltip.
|
|
243
|
+
2. Item 5: relabel **nouns only** (default), leave the "Reserve/Release" verbs.
|
|
244
|
+
3. Item 7: navbar toggle **icon-only** (🌙/☀️) with `aria-label` (default).
|
|
245
|
+
4. Versioning: **one minor (e.g. 0.16.0) per phase** as completed (default),
|
|
246
|
+
vs. batch all into one.
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
/* kryten-webqueue — Main Stylesheet */
|
|
2
2
|
|
|
3
3
|
:root {
|
|
4
|
+
/* Default (dark) palette. An explicit [data-theme="dark"] block below mirrors
|
|
5
|
+
these so the navbar toggle is symmetric; [data-theme="light"] overrides
|
|
6
|
+
them. Theme-independent tokens (radius, fonts, nav height) live here. */
|
|
4
7
|
--bg-primary: #0f0f14;
|
|
5
8
|
--bg-secondary: #1a1a24;
|
|
6
9
|
--bg-card: #22222e;
|
|
@@ -13,13 +16,48 @@
|
|
|
13
16
|
--warning: #fdcb6e;
|
|
14
17
|
--danger: #e17055;
|
|
15
18
|
--border: #33334a;
|
|
16
|
-
--radius: 8px;
|
|
17
19
|
--shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
|
|
20
|
+
--radius: 8px;
|
|
18
21
|
--nav-height: 4rem;
|
|
19
22
|
--font-body: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
20
23
|
--font-heading: 'Sora', 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
21
24
|
}
|
|
22
25
|
|
|
26
|
+
/* Explicit dark theme (mirrors the defaults) — set by the toggle. */
|
|
27
|
+
:root[data-theme="dark"] {
|
|
28
|
+
--bg-primary: #0f0f14;
|
|
29
|
+
--bg-secondary: #1a1a24;
|
|
30
|
+
--bg-card: #22222e;
|
|
31
|
+
--bg-hover: #2a2a3a;
|
|
32
|
+
--text-primary: #e8e8f0;
|
|
33
|
+
--text-secondary: #9090a8;
|
|
34
|
+
--accent: #6c5ce7;
|
|
35
|
+
--accent-hover: #7f70f0;
|
|
36
|
+
--success: #00b894;
|
|
37
|
+
--warning: #fdcb6e;
|
|
38
|
+
--danger: #e17055;
|
|
39
|
+
--border: #33334a;
|
|
40
|
+
--shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/* Light theme. Same accent family; lighter surfaces, darker text, and slightly
|
|
44
|
+
deepened semantic colors so they keep contrast on white. */
|
|
45
|
+
:root[data-theme="light"] {
|
|
46
|
+
--bg-primary: #f3f3f8;
|
|
47
|
+
--bg-secondary: #e9e9f1;
|
|
48
|
+
--bg-card: #ffffff;
|
|
49
|
+
--bg-hover: #e4e4ee;
|
|
50
|
+
--text-primary: #1b1b26;
|
|
51
|
+
--text-secondary: #5c5c72;
|
|
52
|
+
--accent: #6c5ce7;
|
|
53
|
+
--accent-hover: #5a4bd4;
|
|
54
|
+
--success: #00997a;
|
|
55
|
+
--warning: #b9810a;
|
|
56
|
+
--danger: #c84b30;
|
|
57
|
+
--border: #d6d6e2;
|
|
58
|
+
--shadow: 0 4px 12px rgba(20, 20, 40, 0.12);
|
|
59
|
+
}
|
|
60
|
+
|
|
23
61
|
* {
|
|
24
62
|
margin: 0;
|
|
25
63
|
padding: 0;
|
|
@@ -85,6 +123,23 @@ a:hover {
|
|
|
85
123
|
color: var(--text-primary);
|
|
86
124
|
}
|
|
87
125
|
|
|
126
|
+
/* Theme toggle (navbar) */
|
|
127
|
+
.theme-toggle {
|
|
128
|
+
background: transparent;
|
|
129
|
+
border: 1px solid var(--border);
|
|
130
|
+
border-radius: var(--radius);
|
|
131
|
+
color: var(--text-secondary);
|
|
132
|
+
cursor: pointer;
|
|
133
|
+
font-size: 1rem;
|
|
134
|
+
line-height: 1;
|
|
135
|
+
padding: 0.3rem 0.5rem;
|
|
136
|
+
transition: color 0.2s, border-color 0.2s, background 0.2s;
|
|
137
|
+
}
|
|
138
|
+
.theme-toggle:hover {
|
|
139
|
+
color: var(--text-primary);
|
|
140
|
+
background: var(--bg-hover);
|
|
141
|
+
}
|
|
142
|
+
|
|
88
143
|
/* Container */
|
|
89
144
|
.container {
|
|
90
145
|
max-width: 1400px;
|
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
/* kryten-webqueue — Main JavaScript */
|
|
2
2
|
|
|
3
|
+
// --- Theme (light/dark) ---
|
|
4
|
+
// The initial theme is applied pre-paint by an inline script in base.html.
|
|
5
|
+
// Here we keep the toggle button's icon in sync and persist explicit choices.
|
|
6
|
+
function currentTheme() {
|
|
7
|
+
return document.documentElement.dataset.theme === 'light' ? 'light' : 'dark';
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function updateThemeToggle() {
|
|
11
|
+
const btn = document.getElementById('theme-toggle');
|
|
12
|
+
if (!btn) return;
|
|
13
|
+
const dark = currentTheme() === 'dark';
|
|
14
|
+
// Show the icon for the theme you'd switch TO.
|
|
15
|
+
btn.textContent = dark ? '\u2600\uFE0F' : '\uD83C\uDF19';
|
|
16
|
+
btn.setAttribute('aria-label', dark ? 'Switch to light theme' : 'Switch to dark theme');
|
|
17
|
+
btn.title = btn.getAttribute('aria-label');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function setTheme(theme) {
|
|
21
|
+
document.documentElement.dataset.theme = theme;
|
|
22
|
+
try { localStorage.setItem('wq_theme', theme); } catch (e) { /* ignore */ }
|
|
23
|
+
updateThemeToggle();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function toggleTheme() {
|
|
27
|
+
setTheme(currentTheme() === 'dark' ? 'light' : 'dark');
|
|
28
|
+
}
|
|
29
|
+
|
|
3
30
|
// Toast notification system
|
|
4
31
|
function showToast(message, type = 'success') {
|
|
5
32
|
const toast = document.createElement('div');
|
|
@@ -15,6 +42,12 @@ function showToast(message, type = 'success') {
|
|
|
15
42
|
|
|
16
43
|
// Logout handler
|
|
17
44
|
document.addEventListener('DOMContentLoaded', () => {
|
|
45
|
+
updateThemeToggle();
|
|
46
|
+
const themeBtn = document.getElementById('theme-toggle');
|
|
47
|
+
if (themeBtn) {
|
|
48
|
+
themeBtn.addEventListener('click', toggleTheme);
|
|
49
|
+
}
|
|
50
|
+
|
|
18
51
|
const logoutBtn = document.getElementById('logout-btn');
|
|
19
52
|
if (logoutBtn) {
|
|
20
53
|
logoutBtn.addEventListener('click', async (e) => {
|
{kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/templates/admin/playlists.html
RENAMED
|
@@ -93,7 +93,7 @@ async function loadPlaylists() {
|
|
|
93
93
|
${p.description ? `<div class="muted">${escapeHtml(p.description)}</div>` : ''}</td>
|
|
94
94
|
<td>${p.promo_type
|
|
95
95
|
? `<span class="badge badge-accent" title="Promo pool">Promo: ${escapeHtml(p.promo_type)}</span>`
|
|
96
|
-
: (p.is_immutable ? '<span class="badge badge-warn">
|
|
96
|
+
: (p.is_immutable ? '<span class="badge badge-warn">Non-preemptable</span>' : '<span class="muted">Preemptable</span>')}</td>
|
|
97
97
|
<td>${escapeHtml(p.created_by || '')}</td>
|
|
98
98
|
<td class="row-actions">
|
|
99
99
|
<button class="btn btn-sm" onclick="toggleImmutable(${p.id}, ${p.is_immutable ? 1 : 0}, '${escapeHtml(p.name)}')"
|
|
@@ -125,7 +125,7 @@ function showCreateModal() {
|
|
|
125
125
|
<h3>New Playlist</h3>
|
|
126
126
|
<label class="field"><span>Name</span><input type="text" id="pl-name"></label>
|
|
127
127
|
<label class="field"><span>Description</span><input type="text" id="pl-desc"></label>
|
|
128
|
-
<label class="check"><input type="checkbox" id="pl-immut">
|
|
128
|
+
<label class="check"><input type="checkbox" id="pl-immut"> Non-preemptable (reserve items — hidden from public catalog)</label>
|
|
129
129
|
<div class="modal-actions">
|
|
130
130
|
<button class="btn btn-secondary" onclick="closeModal()">Cancel</button>
|
|
131
131
|
<button class="btn btn-primary" onclick="createPlaylist()">Create</button>
|
|
@@ -167,7 +167,7 @@ async function openEditor(id) {
|
|
|
167
167
|
}));
|
|
168
168
|
document.getElementById('editor-title').textContent = pl.name;
|
|
169
169
|
document.getElementById('editor-meta').textContent =
|
|
170
|
-
`${pl.is_immutable ? '
|
|
170
|
+
`${pl.is_immutable ? 'Non-preemptable (reserved) · ' : ''}${pl.description || ''}`;
|
|
171
171
|
document.getElementById('list-view').classList.add('hidden');
|
|
172
172
|
document.getElementById('editor-view').classList.remove('hidden');
|
|
173
173
|
document.getElementById('cat-results').innerHTML = '';
|
|
@@ -307,7 +307,7 @@ function editMeta() {
|
|
|
307
307
|
showModal(`
|
|
308
308
|
<h3>Rename Playlist</h3>
|
|
309
309
|
<label class="field"><span>Name</span><input type="text" id="em-name" value="${escapeHtml(document.getElementById('editor-title').textContent)}"></label>
|
|
310
|
-
<label class="check"><input type="checkbox" id="em-immut" ${editorImmutable ? 'checked' : ''}>
|
|
310
|
+
<label class="check"><input type="checkbox" id="em-immut" ${editorImmutable ? 'checked' : ''}> Non-preemptable (reserve items)</label>
|
|
311
311
|
<div class="modal-actions">
|
|
312
312
|
<button class="btn btn-secondary" onclick="closeModal()">Cancel</button>
|
|
313
313
|
<button class="btn btn-primary" onclick="saveMeta()">Save</button>
|
{kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/templates/admin/promos.html
RENAMED
|
@@ -87,17 +87,17 @@ async function loadPools() {
|
|
|
87
87
|
${p.description ? `<div class="muted">${escapeHtml(p.description)}</div>` : ''}</td>
|
|
88
88
|
<td>${p.promo_type
|
|
89
89
|
? `<span class="badge badge-accent">${escapeHtml(typeLabel(p.promo_type))}</span>`
|
|
90
|
-
: (p.is_immutable ? '<span class="badge badge-warn">
|
|
90
|
+
: (p.is_immutable ? '<span class="badge badge-warn">Non-preemptable</span>' : '<span class="muted">Preemptable</span>')}</td>
|
|
91
91
|
<td>
|
|
92
92
|
<select onchange="setPromoType(${p.id}, this.value, '${escapeHtml(p.name)}')"
|
|
93
|
-
${p.is_immutable ? 'disabled title="
|
|
93
|
+
${p.is_immutable ? 'disabled title="Make the playlist preemptable first"' : ''}>
|
|
94
94
|
${options(p.promo_type || '')}
|
|
95
95
|
</select>
|
|
96
96
|
</td>
|
|
97
97
|
</tr>`).join('')}
|
|
98
98
|
</table>
|
|
99
99
|
<p class="muted" style="margin-top:0.5rem;font-size:0.8rem;">
|
|
100
|
-
|
|
100
|
+
Non-preemptable playlists can't be promo pools — make them preemptable on the Playlists page first.
|
|
101
101
|
Multiple playlists may share a type; their clips are unioned into that type's pool.
|
|
102
102
|
</p>`;
|
|
103
103
|
}
|
{kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/templates/admin/schedules.html
RENAMED
|
@@ -47,7 +47,7 @@ async function loadActive() {
|
|
|
47
47
|
const locked = a.is_immutable && !a.lock_disabled;
|
|
48
48
|
banner.innerHTML = `
|
|
49
49
|
<strong>Active schedule running:</strong> ${escapeHtml(name)}
|
|
50
|
-
${a.is_immutable ? '<span class="badge badge-warn">
|
|
50
|
+
${a.is_immutable ? '<span class="badge badge-warn">Non-preemptable</span>' : ''}
|
|
51
51
|
${locked ? '<span class="badge badge-warn">Pay-to-play locked</span>' : '<span class="badge">Unlocked</span>'}
|
|
52
52
|
${a.estimated_end_at ? `<div class="muted">Ends ~${formatLocalDateTime(a.estimated_end_at)}</div>` : ''}
|
|
53
53
|
<div style="margin-top:0.5rem;">
|
|
@@ -4,6 +4,22 @@
|
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title>{% block title %}Queue{% endblock %} — Channel-Z</title>
|
|
7
|
+
<script>
|
|
8
|
+
// Set the theme before first paint to avoid a flash of the wrong theme.
|
|
9
|
+
// Explicit choice in localStorage wins; otherwise follow the OS preference.
|
|
10
|
+
(function () {
|
|
11
|
+
try {
|
|
12
|
+
var saved = localStorage.getItem('wq_theme');
|
|
13
|
+
var theme = (saved === 'light' || saved === 'dark')
|
|
14
|
+
? saved
|
|
15
|
+
: (window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches
|
|
16
|
+
? 'light' : 'dark');
|
|
17
|
+
document.documentElement.dataset.theme = theme;
|
|
18
|
+
} catch (e) {
|
|
19
|
+
document.documentElement.dataset.theme = 'dark';
|
|
20
|
+
}
|
|
21
|
+
})();
|
|
22
|
+
</script>
|
|
7
23
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
8
24
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
9
25
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Sora:wght@600;700&display=swap">
|
|
@@ -27,6 +43,7 @@
|
|
|
27
43
|
{% else %}
|
|
28
44
|
<a href="/auth/login">Login</a>
|
|
29
45
|
{% endif %}
|
|
46
|
+
<button type="button" id="theme-toggle" class="theme-toggle" aria-label="Toggle light/dark theme" title="Toggle light/dark theme"></button>
|
|
30
47
|
</div>
|
|
31
48
|
</nav>
|
|
32
49
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/integrations/cmsutils/__init__.py
RENAMED
|
File without changes
|
{kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/integrations/cmsutils/_common.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/integrations/cmsutils/enrichtv.py
RENAMED
|
File without changes
|
{kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/integrations/cmsutils/fetchurls.py
RENAMED
|
File without changes
|
{kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/integrations/ytpipe/__init__.py
RENAMED
|
File without changes
|
{kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/integrations/ytpipe/downloader.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/templates/admin/index.html
RENAMED
|
File without changes
|
{kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/templates/admin/queue_mgmt.html
RENAMED
|
File without changes
|
|
File without changes
|
{kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/templates/catalog/browse.html
RENAMED
|
File without changes
|
{kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/templates/catalog/item_detail.html
RENAMED
|
File without changes
|
|
File without changes
|
{kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/templates/queue/index.html
RENAMED
|
File without changes
|
{kryten_webqueue-0.15.2 → kryten_webqueue-0.16.0}/kryten_webqueue/templates/user/dashboard.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|