Commit graph

467 commits

Author SHA1 Message Date
npeter83
25dca48964 Merge feature/scheduler-controls: job tooltips, status legend, editable intervals 2026-06-16 15:58:30 +02:00
npeter83
2a02fb353e feat(scheduler): per-job help tooltips, status legend, inline interval edit
Each job shows a tooltip (what it does + what happens if it stops), a status
dot legend + per-dot tooltips clarify the colours, and the interval is inline-
editable (pencil -> number -> save) wired to the new PATCH endpoint. HU/EN/DE.
2026-06-16 15:58:23 +02:00
npeter83
84aebe16c7 feat(scheduler): admin-editable job intervals (DB-backed, live reschedule)
Add scheduler_settings (per-job interval override, migration 0015) and a
PATCH /api/admin/scheduler/jobs/{id} that persists the override and live-
reschedules the running APScheduler job. Intervals load from the DB (env
defaults as fallback); the snapshot reports the live trigger interval.
2026-06-16 15:58:23 +02:00
npeter83
b71d07c098 chore(dev): self-contained local dev stack (own Postgres + scheduler)
localdev no longer points at the shared database; it now runs its own
local Postgres and its own scheduler, fully decoupled. Removes the shared-DB
migration coupling (no more redeploys after migrations) and lets the
background scheduler actually run during local dev.
2026-06-16 15:21:43 +02:00
npeter83
c011972efb Merge feature/scheduler-dashboard: live admin scheduler dashboard + reusable poll hook 2026-06-16 14:38:58 +02:00
npeter83
73b06b1fb6 feat(scheduler): live admin Scheduler dashboard + reusable poll hook (HU/EN/DE)
New admin Scheduler page (left-nav entry) with a live, self-refreshing view of
job activity, queued work and quota. Polling is factored into a reusable
useLiveQuery hook (pauses when the tab is unfocused) that the notification bell
and future yt-dlp job queue will reuse instead of re-implementing.
2026-06-16 14:38:51 +02:00
npeter83
a339a73bd6 feat(scheduler): admin dashboard endpoint + per-job activity tracking
Record each scheduler job's run activity in-process (running / last result /
error / timestamps) and expose a snapshot. New admin-only GET
/api/admin/scheduler returns the per-job activity (with APScheduler next-run
times), the DB-derived work still queued (channels/videos pending, deep ETA,
live-refresh backlog) and the shared quota picture.
2026-06-16 14:38:51 +02:00
npeter83
e6c53b3f96 chore(release): v0.5.1 — graceful fallback for non-embeddable videos 2026-06-16 12:02:38 +02:00
npeter83
de4d657d42 fix(player): make the unavailable overlay fully opaque
The fallback used a translucent background and left the iframe mounted, so
YouTube's own 'Video unavailable' screen bled through and overlapped our
message. Use a solid background and hide the iframe while the error shows.
2026-06-16 11:58:57 +02:00
npeter83
715eeffff8 Merge fix/player-error-handling: graceful 'Open on YouTube' fallback for non-embeddable videos 2026-06-16 10:45:57 +02:00
npeter83
c2d9e7b5a9 fix(player): friendly fallback when a video can't be embedded
The in-app IFrame player showed YouTube's bare 'Video unavailable' screen
with no way out when a video couldn't be embedded (auto-generated Topic
art-tracks disable embedding -> error 101/150; removed/private -> 100).
Catch the IFrame onError and overlay a clear message + 'Open on YouTube'
button, with a tailored note for embedding-disabled videos.
2026-06-16 10:45:57 +02:00
npeter83
a76bbb5825 chore(release): v0.5.0 — demo account, clearer live videos 2026-06-16 10:23:29 +02:00
npeter83
e12e664579 Merge improvement/demo-banner: permanent demo banner instead of toast 2026-06-16 10:12:10 +02:00
npeter83
848fdd386f improvement(demo): permanent banner instead of a re-popping toast
The shared-account warning was a requiresInteraction toast that re-fired
on every reload. Replace it with a permanent, non-dismissible banner (like
the version banner) shown above the content while in the demo account.
2026-06-16 10:12:10 +02:00
npeter83
e81a494436 Merge fix/live-reenrich: re-enrich transient live videos until they settle 2026-06-16 10:06:58 +02:00
npeter83
b114dae39b fix(sync): re-enrich live videos so ended streams gain duration
enrich_pending only touches enriched_at IS NULL, so a video first seen
while live was stamped live + duration-null and never revisited — staying
'live' with no duration forever after the broadcast ended. Add refresh_live
(run after each enrich pass) that re-fetches anything still live/upcoming,
plus just-ended was_live videos that haven't got their duration yet, until
they settle. Cheap: videos.list is 1 unit per 50 ids.
2026-06-16 10:06:58 +02:00
npeter83
ddaa95f2a6 Merge improvement/live-duration-badge: LIVE/upcoming badge instead of blank duration 2026-06-16 09:36:51 +02:00
npeter83
d42a038a3f feat(card): show LIVE/upcoming badge where duration is absent
Live streams and upcoming/premiere videos have no fixed duration, so the
playlist row and video card left a blank where the runtime usually sits.
Show a LIVE (red) or upcoming badge instead, so it's clear the missing
time is by design, not a sync gap.
2026-06-16 09:36:51 +02:00
npeter83
15811b8b43 Merge improvement/demo-ux: graceful demo handling of unavailable YouTube actions 2026-06-16 09:27:34 +02:00
npeter83
484c6364e6 fix(demo): graceful UX when YouTube actions are unavailable
The shared demo account no longer hits YouTube affordances: the onboarding
wizard never opens (or renders) for it, the empty-feed prompt nudges into
the shared library instead of the connect wizard, and the browser-facing
/auth/upgrade redirects the demo home instead of returning a raw 403 JSON.
2026-06-16 09:27:34 +02:00
npeter83
5b6d898d31 Merge feature/demo-account: shared demo account (whitelist login, gating, admin reset) 2026-06-16 09:17:53 +02:00
npeter83
6405350104 feat(demo): login trigger, feature gating + admin UI (HU/EN/DE)
Login page quietly probes /auth/demo (debounced) as a valid email is
typed/pasted and reloads into the app on a match — no visible button.
Demo sessions default to the whole library, get a one-time shared-account
warning, never see the YouTube-connect onboarding/access UI or sync
actions, and admins get a demo whitelist + reset panel in Settings.
2026-06-16 09:17:34 +02:00
npeter83
e0c63c26d4 feat(demo): admin demo whitelist CRUD + state reset
Admin endpoints to manage the demo email whitelist (DB-backed, no env)
and a manual reset that wipes the demo account's per-user state and
re-seeds a few sample playlists from the shared catalog.
2026-06-16 09:17:27 +02:00
npeter83
5936436d26 feat(demo): hidden /auth/demo login + require_human guard
Whitelisted emails enter the shared demo user via /auth/demo (lazily
created, no OAuth token/scope), rate-limited per IP and answering
uniformly so it can't be hammered as an enumeration oracle. Add a
require_human dependency that blocks the demo account from quota-spending
sync endpoints and the OAuth upgrade flow, and surface is_demo on /api/me.
2026-06-16 09:17:21 +02:00
npeter83
9cac2cd335 feat(demo): demo-account schema + reusable rate limiter
Add the shared demo-account plumbing: users.is_demo marks the single
shared demo user, demo_whitelist holds the admin-curated emails that may
enter it without Google sign-in, and a small in-process RateLimiter
(generic groundwork) for throttling the demo-login endpoint per IP.
2026-06-16 09:17:14 +02:00
npeter83
af84cc0404 chore(release): v0.4.1 — feed toolbar + clearer counts/dates
Bump VERSION to 0.4.1 and add the 0.4.1 release-notes entry (feed view/content-type chips
above the videos, key+direction sort, exact view count + upload date on cards/player).
2026-06-16 03:18:58 +02:00
npeter83
5a2928b810 feat(player): exact upload date inline in the player meta too
Mirror the card change in PlayerModal: append the precise locale-aware date after the
relative time, for both the active video and a linked (navigated) video's stats.
2026-06-16 03:09:28 +02:00
npeter83
71d7440a8b feat(card): show the exact upload date inline after the relative time
There's room on the meta line, so append the precise date (locale-aware, e.g. '12 Jan
2017') after '9 yr ago' instead of hiding it in a tooltip.
2026-06-16 03:04:50 +02:00
npeter83
48e7f8d11e feat(card): exact view count on hover (disambiguate 9B vs 98)
The abbreviated count (e.g. '9B' for ~9 billion) can read like '98' since the B glyph
resembles an 8. Add a title tooltip with the full localized count on the views label.
2026-06-16 02:57:53 +02:00
npeter83
ab439f9717 Merge improvement/feed-toolbar-show-sort: Show chips + key/direction sort 2026-06-16 02:46:26 +02:00
npeter83
4921a95d09 feat(feed): Show chips in the toolbar + key+direction sort
1) Move the Show view filter (Unwatched/In progress/All/Watched/Hidden) up into the
   toolbar chip row as its own single-select group, divided from the content-type chips;
   removed the 'show' sidebar widget (sidebar now = Upload date / Language / Topic).
2) Rework ordering like the Playlists page: a sort-key dropdown (Date, Popular, Duration,
   Name, Channel subscribers, Channel priority, Surprise me) + a single asc/desc arrow
   toggle, instead of separate directional entries. 'Most viewed' is now 'Popular'.
   Backend gains the missing directions (views_asc, title_desc, subscribers_asc,
   priority_asc); the frontend maps (key, dir) -> the backend sort string, so FeedFilters
   is unchanged. Trilingual (feed.sortKey.*, feed.dirAsc/dirDesc). Feed.tsx normalized to LF.
2026-06-16 02:46:26 +02:00
npeter83
a44bdf7fdc Merge feature/1d-feed-toolbar: content-type chips + sort above the feed 2026-06-16 02:34:13 +02:00
npeter83
d96b47ae5d feat(feed): 1D — content-type chips + sort in a toolbar above the cards
Move content type and ordering out of the filter sidebar into a feed toolbar (approved
proposal 2): a prominent content-type chip row (Normal/Shorts/Live toggle chips) with the
sort control + count + reshuffle in a quieter row beneath it. The toolbar renders even on
'no matches' so content-type can be turned back on after it filtered everything out. The
sidebar keeps Show / Upload date / Language / Topic; 'sort' and 'content' widgets removed
from the layout (normalizeLayout drops them from any saved layout). New feed.sortLabel
string (HU/EN/DE).
2026-06-16 02:34:13 +02:00
npeter83
f6efe44e37 chore(release): v0.4.0 — navigation overhaul, glass, account switch
Bump VERSION to 0.4.0 and add the 0.4.0 release-notes entry (Epic N: left nav sidebar,
multi-account switch, Settings page, in-app back, frosted-glass refresh).
2026-06-16 02:16:21 +02:00
npeter83
15716dd578 fix(auth): always seed the active account into the switch list
Sessions created before multi-session existed had no account_ids, so adding a second
account left the first one (e.g. the long-lived session) absent from the switcher.
current_user now ensures the active user_id is always present in account_ids, so the
switch list is never missing the account you're using.
2026-06-16 02:12:59 +02:00
npeter83
2d8c47b04e Merge feature/n3-multi-session: account switch without re-auth (Epic N, N3) 2026-06-16 02:05:38 +02:00
npeter83
583e003c23 feat(auth): N3 — server-side multi-session account switch
Track every account that completes OAuth in a browser session (session 'account_ids',
~14-day cookie), so the user can switch between them without another Google round-trip.
New GET /api/me/accounts (switchable accounts, active flagged) and POST /api/me/switch
(guarded: target must be in the session list — proof it signed in here — and re-checked
against is_allowed in case the invite was revoked). Logout now signs out the active
account and falls back to the most recent remaining one, else clears the session. UI: the
account popover lists the other signed-in accounts (click to switch, reloads) plus an
'Add another account' action (-> /auth/login, which uses prompt=select_account).
Trilingual. Third step of Epic N.
2026-06-16 02:05:38 +02:00
npeter83
8c076f7a75 feat(ui): brighten dark-mode glass so the frost is visible over dark UI
Dark glass looked nearly flat because the blurred backdrop is itself dark. Add a
brightness lift to backdrop-filter in dark mode for .glass/.glass-menu so the frosted
sheen reads even over dark surfaces (over colourful content it just looks richer).
Perf-mode still disables the blur (later rule wins).
2026-06-16 01:57:41 +02:00
npeter83
ed2d76f92d fix(nav): remove backdrop div so the account popover frosts
The full-screen transparent dismiss backdrop sat directly behind the popover and acted as
a compositing layer, so the popover's backdrop-filter sampled it (empty) instead of the
page — making the glass look fully solid. Drop the backdrop and dismiss via document
mousedown/Escape listeners (like the other menus). Now backdrop-filter samples real
content and the frost shows.
2026-06-16 01:54:01 +02:00
npeter83
a18924bf97 fix(nav): restore frosted glass on the portaled account popover
Now that the popover is portaled to body its backdrop-filter actually samples the content
behind it, so the near-opaque glass-menu (needed only as a workaround while blur was
trapped) made it look solid. Switch it back to the frosty .glass (78%); the working blur
softens the background so text stays readable without the bleed-through. Trapped header
menus (language/notifications) keep glass-menu.
2026-06-16 01:50:01 +02:00
npeter83
5bb85b039a fix(nav): portal account popover to body (stacking + click-through)
The nav's .glass backdrop-filter makes it a containing block for fixed descendants and a
stacking context, so the account popover's fixed inset-0 backdrop only covered the nav (not
the viewport) and the popover sat below the main content — controls behind it stayed
clickable. Portal the popover + its dismiss backdrop to <body> with fixed coords computed
from the trigger rect, so it's truly top-most (z-50) and blocks clicks underneath.
2026-06-16 01:46:32 +02:00
npeter83
74491991d7 fix(ui): opaque .glass-menu for floating popovers (readability)
Floating menus hover over undimmed content (no backdrop scrim like dialogs), so the
frosted .glass (78%) let the content bleed through and hurt readability. Add a near-opaque
.glass-menu (surface 92%, keeps blur) and use it for the account, language, notification
and add-to-playlist popovers. Dialogs/chrome keep the frostier .glass (they sit over a dark
scrim / the ambient bg). perf-mode disables its blur too.
2026-06-16 01:39:56 +02:00
npeter83
0adc1104a3 fix(ui): restore glass opacity in dark mode (account popover readability)
The dark-mode experiment made .glass globally more translucent (surface 66%), which
also hit content-overlapping surfaces like the account popover — text became hard to
read over the feed behind it. Drop that override so .glass is back to the readable 78%
everywhere; keep the richer dark ambient (harmless to readability). Docked chrome loses
the marginal extra frost (which was barely noticeable anyway).
2026-06-16 01:34:25 +02:00
npeter83
7b2d4dfb78 Merge feature/n2-back-history: in-app Back/Forward navigation (Epic N, N2) 2026-06-16 01:27:49 +02:00
npeter83
250c1da214 feat(nav): N2 — browser Back navigates in-app, not to OAuth
Push an in-app history entry on each page switch (page rides in history.state, URL stays
clean — filters still never go in the URL) and sync the page from history.state on
popstate. The initial entry is stamped after the strip-params effect (which nulls
history.state). Now the mouse/browser Back button steps through visited pages instead of
jumping straight to the Google consent/redirect. Second step of Epic N.
2026-06-16 01:27:49 +02:00
npeter83
36c7a649ab feat(ui): stronger ambient + more translucent glass in dark mode
In dark mode the faint accent pools left the docked frosted chrome (nav/header) looking
flat — no content behind it to refract, unlike dialogs over the feed. Boost the dark-mode
ambient pools and make .glass a bit more translucent there so the chrome reads as glass
near the accent pools. Light mode unchanged. The full app-wide dark effect still needs
Phase 2 (ambient content/mosaic), deferred.
2026-06-16 01:20:40 +02:00
npeter83
67e518f7ef Merge feature/glassmorphism-phase1: frosted glass on nav, header, dialogs 2026-06-16 01:11:50 +02:00
npeter83
d46be00d47 feat(ui): glassmorphism phase 1 — frost the app chrome
Apply the real frosted .glass surface (blur) to the few large chrome surfaces — the nav
sidebar, the header and all Modal-based dialogs (was glass-card / plain surfaces) — and
enrich the ambient backdrop a touch (three soft accent pools) so the glass has more to
refract app-wide. Popovers (notifications, language, account, add-to-playlist) were
already glass. Bulk feed cards stay glass-card (no per-card blur) for performance; the
existing perf-mode still disables blur. Phase 2 (ambient thumbnail mosaic / bg image +
toggle) deferred to end-of-project polish.
2026-06-16 01:11:50 +02:00
npeter83
2d54167afd Merge feature/n1b-settings-page: Settings as a left-nav page (Design B) 2026-06-16 01:05:05 +02:00
npeter83
d30dc7f760 feat(nav): Settings as a page module (Design B)
Move Settings out of the right-side overlay into a left-nav page (page='settings'),
so it opens where you're already looking — no cross-screen mouse travel. The Settings
rail item now sets the page (with active highlight) instead of opening a dialog; the
panel is refactored to an in-flow .glass card (keeps the frosted look over the ambient
backdrop), with the page title shown in the header. Removed the overlay + Esc/backdrop
close path.
2026-06-16 01:05:05 +02:00