feat(m5a): channel manager, tabbed settings panel, per-user sync status
Backend: /api/channels (list + PATCH priority/hidden + attach/detach user tags),
user-tag CRUD on /api/tags, /api/sync/my-status (per-user channel sync counts).
Frontend: feed|channels page nav (URL-synced) from the account menu; a slide-in
tabbed Settings panel (Appearance, Notifications=6b sound+duration, Sync status +
actions + admin pause/resume, Account); a channel manager with priority, hide,
per-channel user tags, sync badges and 'view in feed'. Notifications now honor the
configurable sound + auto-dismiss settings.
2026-06-11 20:45:48 +02:00
import { useState } from "react" ;
import { useMutation , useQuery , useQueryClient } from "@tanstack/react-query" ;
import {
ArrowDown ,
ArrowUp ,
Eye ,
EyeOff ,
2026-06-11 23:07:09 +02:00
History ,
feat(m5a): channel manager, tabbed settings panel, per-user sync status
Backend: /api/channels (list + PATCH priority/hidden + attach/detach user tags),
user-tag CRUD on /api/tags, /api/sync/my-status (per-user channel sync counts).
Frontend: feed|channels page nav (URL-synced) from the account menu; a slide-in
tabbed Settings panel (Appearance, Notifications=6b sound+duration, Sync status +
actions + admin pause/resume, Account); a channel manager with priority, hide,
per-channel user tags, sync badges and 'view in feed'. Notifications now honor the
configurable sound + auto-dismiss settings.
2026-06-11 20:45:48 +02:00
Plus ,
RefreshCw ,
Search ,
2026-06-11 23:27:11 +02:00
UserMinus ,
feat(m5a): channel manager, tabbed settings panel, per-user sync status
Backend: /api/channels (list + PATCH priority/hidden + attach/detach user tags),
user-tag CRUD on /api/tags, /api/sync/my-status (per-user channel sync counts).
Frontend: feed|channels page nav (URL-synced) from the account menu; a slide-in
tabbed Settings panel (Appearance, Notifications=6b sound+duration, Sync status +
actions + admin pause/resume, Account); a channel manager with priority, hide,
per-channel user tags, sync badges and 'view in feed'. Notifications now honor the
configurable sound + auto-dismiss settings.
2026-06-11 20:45:48 +02:00
X ,
} from "lucide-react" ;
import { api , type ManagedChannel , type Tag } from "../lib/api" ;
2026-06-11 23:07:09 +02:00
import { formatEta } from "../lib/format" ;
feat(m5a): channel manager, tabbed settings panel, per-user sync status
Backend: /api/channels (list + PATCH priority/hidden + attach/detach user tags),
user-tag CRUD on /api/tags, /api/sync/my-status (per-user channel sync counts).
Frontend: feed|channels page nav (URL-synced) from the account menu; a slide-in
tabbed Settings panel (Appearance, Notifications=6b sound+duration, Sync status +
actions + admin pause/resume, Account); a channel manager with priority, hide,
per-channel user tags, sync badges and 'view in feed'. Notifications now honor the
configurable sound + auto-dismiss settings.
2026-06-11 20:45:48 +02:00
import { notify } from "../lib/notifications" ;
feat(ui): liquid-glass design system, settings polish, hints, notif fixes
- Add a theme-aware glass surface system (.glass/.glass-card + ambient backdrop,
performance-mode opt-out) and apply it across panels, popovers, toasts, cards,
sidebar widgets, channel rows, video cards and login.
- SettingsPanel: slide in/out animation, glass styling, wrapping pill tabs (no
horizontal scrollbar) with a prominent active state.
- Notifications: auto-dismiss can be switched off (stays until closed); the test
notification now also triggers the alert sound; resume a suspended AudioContext.
- Add an app-wide, toggleable hint/tooltip system (lib/hints + Tooltip) and wire
hints across the settings and channel-manager surfaces; persisted per account.
2026-06-11 21:08:35 +02:00
import Tooltip from "./Tooltip" ;
feat(m5a): channel manager, tabbed settings panel, per-user sync status
Backend: /api/channels (list + PATCH priority/hidden + attach/detach user tags),
user-tag CRUD on /api/tags, /api/sync/my-status (per-user channel sync counts).
Frontend: feed|channels page nav (URL-synced) from the account menu; a slide-in
tabbed Settings panel (Appearance, Notifications=6b sound+duration, Sync status +
actions + admin pause/resume, Account); a channel manager with priority, hide,
per-channel user tags, sync badges and 'view in feed'. Notifications now honor the
configurable sound + auto-dismiss settings.
2026-06-11 20:45:48 +02:00
export default function Channels ( {
2026-06-11 23:27:11 +02:00
canWrite ,
feat(m5a): channel manager, tabbed settings panel, per-user sync status
Backend: /api/channels (list + PATCH priority/hidden + attach/detach user tags),
user-tag CRUD on /api/tags, /api/sync/my-status (per-user channel sync counts).
Frontend: feed|channels page nav (URL-synced) from the account menu; a slide-in
tabbed Settings panel (Appearance, Notifications=6b sound+duration, Sync status +
actions + admin pause/resume, Account); a channel manager with priority, hide,
per-channel user tags, sync badges and 'view in feed'. Notifications now honor the
configurable sound + auto-dismiss settings.
2026-06-11 20:45:48 +02:00
onViewChannel ,
} : {
2026-06-11 23:27:11 +02:00
canWrite : boolean ;
feat(m5a): channel manager, tabbed settings panel, per-user sync status
Backend: /api/channels (list + PATCH priority/hidden + attach/detach user tags),
user-tag CRUD on /api/tags, /api/sync/my-status (per-user channel sync counts).
Frontend: feed|channels page nav (URL-synced) from the account menu; a slide-in
tabbed Settings panel (Appearance, Notifications=6b sound+duration, Sync status +
actions + admin pause/resume, Account); a channel manager with priority, hide,
per-channel user tags, sync badges and 'view in feed'. Notifications now honor the
configurable sound + auto-dismiss settings.
2026-06-11 20:45:48 +02:00
onViewChannel : ( id : string , name : string ) = > void ;
} ) {
const qc = useQueryClient ( ) ;
const channelsQuery = useQuery ( { queryKey : [ "channels" ] , queryFn : api.channels } ) ;
const tagsQuery = useQuery ( { queryKey : [ "tags" ] , queryFn : api.tags } ) ;
const statusQuery = useQuery ( { queryKey : [ "my-status" ] , queryFn : api.myStatus } ) ;
const [ q , setQ ] = useState ( "" ) ;
const [ newTag , setNewTag ] = useState ( "" ) ;
const invalidate = ( ) = > {
qc . invalidateQueries ( { queryKey : [ "channels" ] } ) ;
qc . invalidateQueries ( { queryKey : [ "tags" ] } ) ;
qc . invalidateQueries ( { queryKey : [ "my-status" ] } ) ;
} ;
const userTags = ( tagsQuery . data ? ? [ ] ) . filter ( ( t ) = > ! t . system ) ;
const patch = useMutation ( {
2026-06-11 23:07:09 +02:00
mutationFn : ( v : {
id : string ;
body : { priority? : number ; hidden? : boolean ; deep_requested? : boolean } ;
} ) = > api . updateChannel ( v . id , v . body ) ,
2026-06-12 00:00:50 +02:00
// Requesting full history may have just pulled in a channel's recent uploads, so
// refresh the per-user status and feed too — not only the channel list.
onSuccess : ( ) = > {
qc . invalidateQueries ( { queryKey : [ "channels" ] } ) ;
qc . invalidateQueries ( { queryKey : [ "my-status" ] } ) ;
qc . invalidateQueries ( { queryKey : [ "feed" ] } ) ;
qc . invalidateQueries ( { queryKey : [ "feed-count" ] } ) ;
} ,
feat(m5a): channel manager, tabbed settings panel, per-user sync status
Backend: /api/channels (list + PATCH priority/hidden + attach/detach user tags),
user-tag CRUD on /api/tags, /api/sync/my-status (per-user channel sync counts).
Frontend: feed|channels page nav (URL-synced) from the account menu; a slide-in
tabbed Settings panel (Appearance, Notifications=6b sound+duration, Sync status +
actions + admin pause/resume, Account); a channel manager with priority, hide,
per-channel user tags, sync badges and 'view in feed'. Notifications now honor the
configurable sound + auto-dismiss settings.
2026-06-11 20:45:48 +02:00
} ) ;
const attach = useMutation ( {
mutationFn : ( v : { id : string ; tagId : number } ) = > api . attachChannelTag ( v . id , v . tagId ) ,
onSuccess : ( ) = > qc . invalidateQueries ( { queryKey : [ "channels" ] } ) ,
} ) ;
const detach = useMutation ( {
mutationFn : ( v : { id : string ; tagId : number } ) = > api . detachChannelTag ( v . id , v . tagId ) ,
onSuccess : ( ) = > qc . invalidateQueries ( { queryKey : [ "channels" ] } ) ,
} ) ;
const syncSubs = useMutation ( {
mutationFn : ( ) = > api . syncSubscriptions ( ) ,
onSuccess : ( r : { subscriptions? : number } ) = > {
invalidate ( ) ;
notify ( { level : "success" , message : ` Synced ${ r . subscriptions ? ? 0 } subscriptions ` } ) ;
} ,
onError : ( ) = > notify ( { level : "error" , message : "Subscription sync failed" } ) ,
} ) ;
const createTag = useMutation ( {
mutationFn : ( name : string ) = > api . createTag ( { name , category : "other" } ) ,
onSuccess : ( ) = > {
setNewTag ( "" ) ;
qc . invalidateQueries ( { queryKey : [ "tags" ] } ) ;
} ,
} ) ;
const deleteTag = useMutation ( {
mutationFn : ( id : number ) = > api . deleteTag ( id ) ,
onSuccess : ( ) = > invalidate ( ) ,
} ) ;
2026-06-11 23:27:11 +02:00
const unsubscribe = useMutation ( {
mutationFn : ( id : string ) = > api . unsubscribeChannel ( id ) ,
onSuccess : ( ) = > {
qc . invalidateQueries ( { queryKey : [ "channels" ] } ) ;
qc . invalidateQueries ( { queryKey : [ "my-status" ] } ) ;
notify ( { level : "success" , message : "Unsubscribed on YouTube" } ) ;
} ,
onError : ( ) = > notify ( { level : "error" , message : "Unsubscribe failed" } ) ,
} ) ;
2026-06-11 23:07:09 +02:00
const deepAll = useMutation ( {
mutationFn : ( ) = > api . deepAll ( true ) ,
onSuccess : ( r : { updated? : number } ) = > {
qc . invalidateQueries ( { queryKey : [ "channels" ] } ) ;
qc . invalidateQueries ( { queryKey : [ "my-status" ] } ) ;
notify ( {
level : "success" ,
message : ` Full history requested for ${ r . updated ? ? 0 } channels ` ,
} ) ;
} ,
onError : ( ) = > notify ( { level : "error" , message : "Couldn't request full history" } ) ,
} ) ;
feat(m5a): channel manager, tabbed settings panel, per-user sync status
Backend: /api/channels (list + PATCH priority/hidden + attach/detach user tags),
user-tag CRUD on /api/tags, /api/sync/my-status (per-user channel sync counts).
Frontend: feed|channels page nav (URL-synced) from the account menu; a slide-in
tabbed Settings panel (Appearance, Notifications=6b sound+duration, Sync status +
actions + admin pause/resume, Account); a channel manager with priority, hide,
per-channel user tags, sync badges and 'view in feed'. Notifications now honor the
configurable sound + auto-dismiss settings.
2026-06-11 20:45:48 +02:00
const channels = ( channelsQuery . data ? ? [ ] ) . filter (
( c ) = > ! q || ( c . title ? ? "" ) . toLowerCase ( ) . includes ( q . toLowerCase ( ) )
) ;
const s = statusQuery . data ;
return (
< div className = "p-4 max-w-4xl mx-auto" >
{ /* Per-user sync status */ }
{ s && (
< div className = "flex flex-wrap items-center gap-x-5 gap-y-1 text-sm text-muted mb-4" >
feat(ui): liquid-glass design system, settings polish, hints, notif fixes
- Add a theme-aware glass surface system (.glass/.glass-card + ambient backdrop,
performance-mode opt-out) and apply it across panels, popovers, toasts, cards,
sidebar widgets, channel rows, video cards and login.
- SettingsPanel: slide in/out animation, glass styling, wrapping pill tabs (no
horizontal scrollbar) with a prominent active state.
- Notifications: auto-dismiss can be switched off (stays until closed); the test
notification now also triggers the alert sound; resume a suspended AudioContext.
- Add an app-wide, toggleable hint/tooltip system (lib/hints + Tooltip) and wire
hints across the settings and channel-manager surfaces; persisted per account.
2026-06-11 21:08:35 +02:00
< Stat label = "Channels" value = { s . channels_total } hint = "Channels you're subscribed to." / >
< Stat
label = "Recent synced"
value = { ` ${ s . channels_recent_synced } / ${ s . channels_total } ` }
hint = "Channels whose recent uploads are in the local DB and show in your feed."
/ >
< Stat
label = "Full history"
2026-06-11 23:07:09 +02:00
value = { ` ${ s . channels_deep_done } / ${ s . channels_deep_requested } ` }
hint = "Channels whose entire back-catalog is fetched, out of the ones you've requested full history for."
feat(ui): liquid-glass design system, settings polish, hints, notif fixes
- Add a theme-aware glass surface system (.glass/.glass-card + ambient backdrop,
performance-mode opt-out) and apply it across panels, popovers, toasts, cards,
sidebar widgets, channel rows, video cards and login.
- SettingsPanel: slide in/out animation, glass styling, wrapping pill tabs (no
horizontal scrollbar) with a prominent active state.
- Notifications: auto-dismiss can be switched off (stays until closed); the test
notification now also triggers the alert sound; resume a suspended AudioContext.
- Add an app-wide, toggleable hint/tooltip system (lib/hints + Tooltip) and wire
hints across the settings and channel-manager surfaces; persisted per account.
2026-06-11 21:08:35 +02:00
/ >
2026-06-11 23:07:09 +02:00
{ s . deep_pending_count > 0 && (
< Stat
label = "left"
value = { formatEta ( s . deep_eta_seconds ) }
hint = { ` Rough estimate to finish full-history backfill of ${ s . deep_pending_count } pending channel(s), based on the shared daily quota. ` }
/ >
) }
feat(ui): liquid-glass design system, settings polish, hints, notif fixes
- Add a theme-aware glass surface system (.glass/.glass-card + ambient backdrop,
performance-mode opt-out) and apply it across panels, popovers, toasts, cards,
sidebar widgets, channel rows, video cards and login.
- SettingsPanel: slide in/out animation, glass styling, wrapping pill tabs (no
horizontal scrollbar) with a prominent active state.
- Notifications: auto-dismiss can be switched off (stays until closed); the test
notification now also triggers the alert sound; resume a suspended AudioContext.
- Add an app-wide, toggleable hint/tooltip system (lib/hints + Tooltip) and wire
hints across the settings and channel-manager surfaces; persisted per account.
2026-06-11 21:08:35 +02:00
< Stat label = "My videos" value = { s . my_videos . toLocaleString ( ) } hint = "Total videos available across your channels." / >
< Stat
label = "Quota left"
value = { s . quota_remaining_today . toLocaleString ( ) }
hint = "Shared YouTube API budget left today (resets midnight US Pacific)."
/ >
feat(m5a): channel manager, tabbed settings panel, per-user sync status
Backend: /api/channels (list + PATCH priority/hidden + attach/detach user tags),
user-tag CRUD on /api/tags, /api/sync/my-status (per-user channel sync counts).
Frontend: feed|channels page nav (URL-synced) from the account menu; a slide-in
tabbed Settings panel (Appearance, Notifications=6b sound+duration, Sync status +
actions + admin pause/resume, Account); a channel manager with priority, hide,
per-channel user tags, sync badges and 'view in feed'. Notifications now honor the
configurable sound + auto-dismiss settings.
2026-06-11 20:45:48 +02:00
< / div >
) }
{ /* Toolbar */ }
< div className = "flex items-center gap-2 mb-4" >
< div className = "relative flex-1" >
< Search className = "w-4 h-4 absolute left-3 top-1/2 -translate-y-1/2 text-muted" / >
< input
value = { q }
onChange = { ( e ) = > setQ ( e . target . value ) }
placeholder = "Filter channels…"
className = "w-full bg-card border border-border rounded-full pl-9 pr-4 py-2 text-sm outline-none focus:border-accent"
/ >
< / div >
2026-06-11 21:30:25 +02:00
< Tooltip
side = "bottom"
hint = "Re-import your subscription list from YouTube — adds channels you've newly followed and drops ones you've unfollowed. The videos themselves keep syncing automatically in the background; this does not re-fetch them."
feat(m5a): channel manager, tabbed settings panel, per-user sync status
Backend: /api/channels (list + PATCH priority/hidden + attach/detach user tags),
user-tag CRUD on /api/tags, /api/sync/my-status (per-user channel sync counts).
Frontend: feed|channels page nav (URL-synced) from the account menu; a slide-in
tabbed Settings panel (Appearance, Notifications=6b sound+duration, Sync status +
actions + admin pause/resume, Account); a channel manager with priority, hide,
per-channel user tags, sync badges and 'view in feed'. Notifications now honor the
configurable sound + auto-dismiss settings.
2026-06-11 20:45:48 +02:00
>
2026-06-11 21:30:25 +02:00
< button
onClick = { ( ) = > syncSubs . mutate ( ) }
disabled = { syncSubs . isPending }
className = "glass-card glass-hover flex items-center gap-2 px-3 py-2 rounded-xl text-sm disabled:opacity-50 transition"
>
< RefreshCw className = { ` w-4 h-4 ${ syncSubs . isPending ? "animate-spin" : "" } ` } / >
Sync subscriptions
< / button >
< / Tooltip >
2026-06-11 23:07:09 +02:00
< Tooltip
side = "bottom"
hint = "Request full back-catalog backfill for every channel you're subscribed to. Older videos and search become complete as the shared daily quota allows — this can take a while."
>
< button
onClick = { ( ) = > deepAll . mutate ( ) }
disabled = { deepAll . isPending }
className = "glass-card glass-hover flex items-center gap-2 px-3 py-2 rounded-xl text-sm disabled:opacity-50 transition"
>
< History className = { ` w-4 h-4 ${ deepAll . isPending ? "animate-pulse" : "" } ` } / >
Backfill everything
< / button >
< / Tooltip >
feat(m5a): channel manager, tabbed settings panel, per-user sync status
Backend: /api/channels (list + PATCH priority/hidden + attach/detach user tags),
user-tag CRUD on /api/tags, /api/sync/my-status (per-user channel sync counts).
Frontend: feed|channels page nav (URL-synced) from the account menu; a slide-in
tabbed Settings panel (Appearance, Notifications=6b sound+duration, Sync status +
actions + admin pause/resume, Account); a channel manager with priority, hide,
per-channel user tags, sync badges and 'view in feed'. Notifications now honor the
configurable sound + auto-dismiss settings.
2026-06-11 20:45:48 +02:00
< / div >
2026-06-11 21:30:25 +02:00
< p className = "text-xs text-muted mb-4 leading-relaxed" >
Set a channel ' s < b className = "text-fg/80" > priority < / b > to push its videos up when you sort
by “ Channel priority ” , attach your own < b className = "text-fg/80" > tags < / b > to filter the feed ,
or < b className = "text-fg/80" > hide < / b > a channel to drop it from the feed without unsubscribing .
< / p >
feat(m5a): channel manager, tabbed settings panel, per-user sync status
Backend: /api/channels (list + PATCH priority/hidden + attach/detach user tags),
user-tag CRUD on /api/tags, /api/sync/my-status (per-user channel sync counts).
Frontend: feed|channels page nav (URL-synced) from the account menu; a slide-in
tabbed Settings panel (Appearance, Notifications=6b sound+duration, Sync status +
actions + admin pause/resume, Account); a channel manager with priority, hide,
per-channel user tags, sync badges and 'view in feed'. Notifications now honor the
configurable sound + auto-dismiss settings.
2026-06-11 20:45:48 +02:00
{ /* Your tags */ }
< div className = "flex flex-wrap items-center gap-1.5 mb-4" >
2026-06-11 21:30:25 +02:00
< Tooltip hint = "Your personal labels. Attach them to channels below, then filter the feed by tag from the sidebar. (Separate from the automatic language/topic tags.)" >
< span className = "text-xs uppercase tracking-wide text-muted mr-1 cursor-help underline decoration-dotted decoration-muted/40 underline-offset-4" >
Your tags
< / span >
< / Tooltip >
feat(m5a): channel manager, tabbed settings panel, per-user sync status
Backend: /api/channels (list + PATCH priority/hidden + attach/detach user tags),
user-tag CRUD on /api/tags, /api/sync/my-status (per-user channel sync counts).
Frontend: feed|channels page nav (URL-synced) from the account menu; a slide-in
tabbed Settings panel (Appearance, Notifications=6b sound+duration, Sync status +
actions + admin pause/resume, Account); a channel manager with priority, hide,
per-channel user tags, sync badges and 'view in feed'. Notifications now honor the
configurable sound + auto-dismiss settings.
2026-06-11 20:45:48 +02:00
{ userTags . map ( ( t ) = > (
< span
key = { t . id }
className = "inline-flex items-center gap-1 text-xs px-2 py-1 rounded-full bg-card border border-border"
>
{ t . name }
< button onClick = { ( ) = > deleteTag . mutate ( t . id ) } className = "text-muted hover:text-red-400" >
< X className = "w-3 h-3" / >
< / button >
< / span >
) ) }
< form
onSubmit = { ( e ) = > {
e . preventDefault ( ) ;
if ( newTag . trim ( ) ) createTag . mutate ( newTag . trim ( ) ) ;
} }
className = "inline-flex items-center gap-1"
>
< input
value = { newTag }
onChange = { ( e ) = > setNewTag ( e . target . value ) }
placeholder = "new tag"
className = "w-24 bg-card border border-border rounded-full px-2.5 py-1 text-xs outline-none focus:border-accent"
/ >
< button type = "submit" className = "text-muted hover:text-accent" title = "Create tag" >
< Plus className = "w-4 h-4" / >
< / button >
< / form >
< / div >
{ /* Channel list */ }
{ channelsQuery . isLoading ? (
< div className = "text-muted py-8" > Loading channels … < / div >
) : channels . length === 0 ? (
< div className = "text-muted py-8" > No channels . < / div >
) : (
< div className = "flex flex-col gap-1.5" >
{ channels . map ( ( c ) = > (
< ChannelRow
key = { c . id }
c = { c }
userTags = { userTags }
2026-06-11 23:27:11 +02:00
canWrite = { canWrite }
onUnsubscribe = { ( ) = > {
if (
window . confirm (
` Unsubscribe from " ${ c . title ? ? c . id } " on YouTube? This changes your real YouTube account. To just remove it from your feed, hide it instead. `
)
)
unsubscribe . mutate ( c . id ) ;
} }
feat(m5a): channel manager, tabbed settings panel, per-user sync status
Backend: /api/channels (list + PATCH priority/hidden + attach/detach user tags),
user-tag CRUD on /api/tags, /api/sync/my-status (per-user channel sync counts).
Frontend: feed|channels page nav (URL-synced) from the account menu; a slide-in
tabbed Settings panel (Appearance, Notifications=6b sound+duration, Sync status +
actions + admin pause/resume, Account); a channel manager with priority, hide,
per-channel user tags, sync badges and 'view in feed'. Notifications now honor the
configurable sound + auto-dismiss settings.
2026-06-11 20:45:48 +02:00
onView = { ( ) = > onViewChannel ( c . id , c . title ? ? "This channel" ) }
onPriority = { ( d ) = > patch . mutate ( { id : c.id , body : { priority : c.priority + d } } ) }
onHide = { ( ) = > patch . mutate ( { id : c.id , body : { hidden : ! c . hidden } } ) }
2026-06-11 23:07:09 +02:00
onDeep = { ( ) = >
patch . mutate ( { id : c.id , body : { deep_requested : ! c . deep_requested } } )
}
feat(m5a): channel manager, tabbed settings panel, per-user sync status
Backend: /api/channels (list + PATCH priority/hidden + attach/detach user tags),
user-tag CRUD on /api/tags, /api/sync/my-status (per-user channel sync counts).
Frontend: feed|channels page nav (URL-synced) from the account menu; a slide-in
tabbed Settings panel (Appearance, Notifications=6b sound+duration, Sync status +
actions + admin pause/resume, Account); a channel manager with priority, hide,
per-channel user tags, sync badges and 'view in feed'. Notifications now honor the
configurable sound + auto-dismiss settings.
2026-06-11 20:45:48 +02:00
onToggleTag = { ( tagId ) = >
c . tag_ids . includes ( tagId )
? detach . mutate ( { id : c.id , tagId } )
: attach . mutate ( { id : c.id , tagId } )
}
/ >
) ) }
< / div >
) }
< / div >
) ;
}
feat(ui): liquid-glass design system, settings polish, hints, notif fixes
- Add a theme-aware glass surface system (.glass/.glass-card + ambient backdrop,
performance-mode opt-out) and apply it across panels, popovers, toasts, cards,
sidebar widgets, channel rows, video cards and login.
- SettingsPanel: slide in/out animation, glass styling, wrapping pill tabs (no
horizontal scrollbar) with a prominent active state.
- Notifications: auto-dismiss can be switched off (stays until closed); the test
notification now also triggers the alert sound; resume a suspended AudioContext.
- Add an app-wide, toggleable hint/tooltip system (lib/hints + Tooltip) and wire
hints across the settings and channel-manager surfaces; persisted per account.
2026-06-11 21:08:35 +02:00
function Stat ( {
label ,
value ,
hint ,
} : {
label : string ;
value : string | number ;
hint? : string ;
} ) {
feat(m5a): channel manager, tabbed settings panel, per-user sync status
Backend: /api/channels (list + PATCH priority/hidden + attach/detach user tags),
user-tag CRUD on /api/tags, /api/sync/my-status (per-user channel sync counts).
Frontend: feed|channels page nav (URL-synced) from the account menu; a slide-in
tabbed Settings panel (Appearance, Notifications=6b sound+duration, Sync status +
actions + admin pause/resume, Account); a channel manager with priority, hide,
per-channel user tags, sync badges and 'view in feed'. Notifications now honor the
configurable sound + auto-dismiss settings.
2026-06-11 20:45:48 +02:00
return (
feat(ui): liquid-glass design system, settings polish, hints, notif fixes
- Add a theme-aware glass surface system (.glass/.glass-card + ambient backdrop,
performance-mode opt-out) and apply it across panels, popovers, toasts, cards,
sidebar widgets, channel rows, video cards and login.
- SettingsPanel: slide in/out animation, glass styling, wrapping pill tabs (no
horizontal scrollbar) with a prominent active state.
- Notifications: auto-dismiss can be switched off (stays until closed); the test
notification now also triggers the alert sound; resume a suspended AudioContext.
- Add an app-wide, toggleable hint/tooltip system (lib/hints + Tooltip) and wire
hints across the settings and channel-manager surfaces; persisted per account.
2026-06-11 21:08:35 +02:00
< Tooltip hint = { hint ? ? "" } >
< span className = { hint ? "cursor-help" : "" } >
< span className = "text-fg font-semibold" > { value } < / span > { label }
< / span >
< / Tooltip >
feat(m5a): channel manager, tabbed settings panel, per-user sync status
Backend: /api/channels (list + PATCH priority/hidden + attach/detach user tags),
user-tag CRUD on /api/tags, /api/sync/my-status (per-user channel sync counts).
Frontend: feed|channels page nav (URL-synced) from the account menu; a slide-in
tabbed Settings panel (Appearance, Notifications=6b sound+duration, Sync status +
actions + admin pause/resume, Account); a channel manager with priority, hide,
per-channel user tags, sync badges and 'view in feed'. Notifications now honor the
configurable sound + auto-dismiss settings.
2026-06-11 20:45:48 +02:00
) ;
}
feat(ui): liquid-glass design system, settings polish, hints, notif fixes
- Add a theme-aware glass surface system (.glass/.glass-card + ambient backdrop,
performance-mode opt-out) and apply it across panels, popovers, toasts, cards,
sidebar widgets, channel rows, video cards and login.
- SettingsPanel: slide in/out animation, glass styling, wrapping pill tabs (no
horizontal scrollbar) with a prominent active state.
- Notifications: auto-dismiss can be switched off (stays until closed); the test
notification now also triggers the alert sound; resume a suspended AudioContext.
- Add an app-wide, toggleable hint/tooltip system (lib/hints + Tooltip) and wire
hints across the settings and channel-manager surfaces; persisted per account.
2026-06-11 21:08:35 +02:00
function SyncBadge ( { ok , label , hint } : { ok : boolean ; label : string ; hint? : string } ) {
feat(m5a): channel manager, tabbed settings panel, per-user sync status
Backend: /api/channels (list + PATCH priority/hidden + attach/detach user tags),
user-tag CRUD on /api/tags, /api/sync/my-status (per-user channel sync counts).
Frontend: feed|channels page nav (URL-synced) from the account menu; a slide-in
tabbed Settings panel (Appearance, Notifications=6b sound+duration, Sync status +
actions + admin pause/resume, Account); a channel manager with priority, hide,
per-channel user tags, sync badges and 'view in feed'. Notifications now honor the
configurable sound + auto-dismiss settings.
2026-06-11 20:45:48 +02:00
return (
feat(ui): liquid-glass design system, settings polish, hints, notif fixes
- Add a theme-aware glass surface system (.glass/.glass-card + ambient backdrop,
performance-mode opt-out) and apply it across panels, popovers, toasts, cards,
sidebar widgets, channel rows, video cards and login.
- SettingsPanel: slide in/out animation, glass styling, wrapping pill tabs (no
horizontal scrollbar) with a prominent active state.
- Notifications: auto-dismiss can be switched off (stays until closed); the test
notification now also triggers the alert sound; resume a suspended AudioContext.
- Add an app-wide, toggleable hint/tooltip system (lib/hints + Tooltip) and wire
hints across the settings and channel-manager surfaces; persisted per account.
2026-06-11 21:08:35 +02:00
< Tooltip hint = { hint ? ? "" } >
< span
className = { ` text-[10px] px-1.5 py-0.5 rounded-full border ${
ok ? "border-accent/40 text-accent" : "border-border text-muted"
} ` }
>
{ label }
< / span >
< / Tooltip >
feat(m5a): channel manager, tabbed settings panel, per-user sync status
Backend: /api/channels (list + PATCH priority/hidden + attach/detach user tags),
user-tag CRUD on /api/tags, /api/sync/my-status (per-user channel sync counts).
Frontend: feed|channels page nav (URL-synced) from the account menu; a slide-in
tabbed Settings panel (Appearance, Notifications=6b sound+duration, Sync status +
actions + admin pause/resume, Account); a channel manager with priority, hide,
per-channel user tags, sync badges and 'view in feed'. Notifications now honor the
configurable sound + auto-dismiss settings.
2026-06-11 20:45:48 +02:00
) ;
}
function ChannelRow ( {
c ,
userTags ,
2026-06-11 23:27:11 +02:00
canWrite ,
onUnsubscribe ,
feat(m5a): channel manager, tabbed settings panel, per-user sync status
Backend: /api/channels (list + PATCH priority/hidden + attach/detach user tags),
user-tag CRUD on /api/tags, /api/sync/my-status (per-user channel sync counts).
Frontend: feed|channels page nav (URL-synced) from the account menu; a slide-in
tabbed Settings panel (Appearance, Notifications=6b sound+duration, Sync status +
actions + admin pause/resume, Account); a channel manager with priority, hide,
per-channel user tags, sync badges and 'view in feed'. Notifications now honor the
configurable sound + auto-dismiss settings.
2026-06-11 20:45:48 +02:00
onView ,
onPriority ,
onHide ,
2026-06-11 23:07:09 +02:00
onDeep ,
feat(m5a): channel manager, tabbed settings panel, per-user sync status
Backend: /api/channels (list + PATCH priority/hidden + attach/detach user tags),
user-tag CRUD on /api/tags, /api/sync/my-status (per-user channel sync counts).
Frontend: feed|channels page nav (URL-synced) from the account menu; a slide-in
tabbed Settings panel (Appearance, Notifications=6b sound+duration, Sync status +
actions + admin pause/resume, Account); a channel manager with priority, hide,
per-channel user tags, sync badges and 'view in feed'. Notifications now honor the
configurable sound + auto-dismiss settings.
2026-06-11 20:45:48 +02:00
onToggleTag ,
} : {
c : ManagedChannel ;
userTags : Tag [ ] ;
2026-06-11 23:27:11 +02:00
canWrite : boolean ;
onUnsubscribe : ( ) = > void ;
feat(m5a): channel manager, tabbed settings panel, per-user sync status
Backend: /api/channels (list + PATCH priority/hidden + attach/detach user tags),
user-tag CRUD on /api/tags, /api/sync/my-status (per-user channel sync counts).
Frontend: feed|channels page nav (URL-synced) from the account menu; a slide-in
tabbed Settings panel (Appearance, Notifications=6b sound+duration, Sync status +
actions + admin pause/resume, Account); a channel manager with priority, hide,
per-channel user tags, sync badges and 'view in feed'. Notifications now honor the
configurable sound + auto-dismiss settings.
2026-06-11 20:45:48 +02:00
onView : ( ) = > void ;
onPriority : ( delta : number ) = > void ;
onHide : ( ) = > void ;
2026-06-11 23:07:09 +02:00
onDeep : ( ) = > void ;
feat(m5a): channel manager, tabbed settings panel, per-user sync status
Backend: /api/channels (list + PATCH priority/hidden + attach/detach user tags),
user-tag CRUD on /api/tags, /api/sync/my-status (per-user channel sync counts).
Frontend: feed|channels page nav (URL-synced) from the account menu; a slide-in
tabbed Settings panel (Appearance, Notifications=6b sound+duration, Sync status +
actions + admin pause/resume, Account); a channel manager with priority, hide,
per-channel user tags, sync badges and 'view in feed'. Notifications now honor the
configurable sound + auto-dismiss settings.
2026-06-11 20:45:48 +02:00
onToggleTag : ( tagId : number ) = > void ;
} ) {
return (
< div
feat(ui): liquid-glass design system, settings polish, hints, notif fixes
- Add a theme-aware glass surface system (.glass/.glass-card + ambient backdrop,
performance-mode opt-out) and apply it across panels, popovers, toasts, cards,
sidebar widgets, channel rows, video cards and login.
- SettingsPanel: slide in/out animation, glass styling, wrapping pill tabs (no
horizontal scrollbar) with a prominent active state.
- Notifications: auto-dismiss can be switched off (stays until closed); the test
notification now also triggers the alert sound; resume a suspended AudioContext.
- Add an app-wide, toggleable hint/tooltip system (lib/hints + Tooltip) and wire
hints across the settings and channel-manager surfaces; persisted per account.
2026-06-11 21:08:35 +02:00
className = { ` glass-card glass-hover flex items-center gap-3 p-2.5 rounded-xl transition ${
feat(m5a): channel manager, tabbed settings panel, per-user sync status
Backend: /api/channels (list + PATCH priority/hidden + attach/detach user tags),
user-tag CRUD on /api/tags, /api/sync/my-status (per-user channel sync counts).
Frontend: feed|channels page nav (URL-synced) from the account menu; a slide-in
tabbed Settings panel (Appearance, Notifications=6b sound+duration, Sync status +
actions + admin pause/resume, Account); a channel manager with priority, hide,
per-channel user tags, sync badges and 'view in feed'. Notifications now honor the
configurable sound + auto-dismiss settings.
2026-06-11 20:45:48 +02:00
c . hidden ? "opacity-60" : ""
} ` }
>
2026-06-11 21:30:25 +02:00
< Tooltip hint = "Your ranking for this channel. Sort the feed by “Channel priority” to bring higher-priority channels to the top." >
< div className = "flex flex-col items-center cursor-help" >
< button onClick = { ( ) = > onPriority ( 1 ) } className = "text-muted hover:text-accent" aria-label = "Raise priority" >
< ArrowUp className = "w-3.5 h-3.5" / >
< / button >
< span className = "text-xs text-muted tabular-nums" > { c . priority } < / span >
< button onClick = { ( ) = > onPriority ( - 1 ) } className = "text-muted hover:text-accent" aria-label = "Lower priority" >
< ArrowDown className = "w-3.5 h-3.5" / >
< / button >
< / div >
< / Tooltip >
feat(m5a): channel manager, tabbed settings panel, per-user sync status
Backend: /api/channels (list + PATCH priority/hidden + attach/detach user tags),
user-tag CRUD on /api/tags, /api/sync/my-status (per-user channel sync counts).
Frontend: feed|channels page nav (URL-synced) from the account menu; a slide-in
tabbed Settings panel (Appearance, Notifications=6b sound+duration, Sync status +
actions + admin pause/resume, Account); a channel manager with priority, hide,
per-channel user tags, sync badges and 'view in feed'. Notifications now honor the
configurable sound + auto-dismiss settings.
2026-06-11 20:45:48 +02:00
{ c . thumbnail_url ? (
< img src = { c . thumbnail_url } alt = "" className = "w-10 h-10 rounded-full shrink-0" / >
) : (
< div className = "w-10 h-10 rounded-full bg-card shrink-0" / >
) }
< div className = "min-w-0 flex-1" >
< button onClick = { onView } className = "text-sm font-semibold truncate hover:text-accent block max-w-full text-left" >
{ c . title ? ? c . id }
< / button >
< div className = "flex items-center gap-2 text-[11px] text-muted" >
< span > { c . stored_videos . toLocaleString ( ) } stored < / span >
{ c . subscriber_count != null && < span > · { c . subscriber_count . toLocaleString ( ) } subs < / span > }
< / div >
< div className = "flex flex-wrap items-center gap-1 mt-1" >
feat(ui): liquid-glass design system, settings polish, hints, notif fixes
- Add a theme-aware glass surface system (.glass/.glass-card + ambient backdrop,
performance-mode opt-out) and apply it across panels, popovers, toasts, cards,
sidebar widgets, channel rows, video cards and login.
- SettingsPanel: slide in/out animation, glass styling, wrapping pill tabs (no
horizontal scrollbar) with a prominent active state.
- Notifications: auto-dismiss can be switched off (stays until closed); the test
notification now also triggers the alert sound; resume a suspended AudioContext.
- Add an app-wide, toggleable hint/tooltip system (lib/hints + Tooltip) and wire
hints across the settings and channel-manager surfaces; persisted per account.
2026-06-11 21:08:35 +02:00
< SyncBadge
ok = { c . recent_synced }
label = "recent"
hint = { c . recent_synced ? "Recent uploads synced." : "Recent uploads not fetched yet." }
/ >
2026-06-11 23:07:09 +02:00
{ c . backfill_done ? (
< SyncBadge ok label = "full" hint = "Full history fetched." / >
2026-06-12 02:29:10 +02:00
) : c . deep_requested ? (
< Tooltip hint = "Full history requested — this channel's whole back-catalog will backfill as the shared quota allows. Click to cancel your request." >
< button
onClick = { onDeep }
className = "inline-flex items-center gap-1 text-[10px] px-1.5 py-0.5 rounded-full border border-accent bg-accent text-accent-fg transition"
>
< History className = "w-3 h-3" / >
full history queued
< / button >
< / Tooltip >
) : c . deep_in_queue ? (
< Tooltip hint = "Another subscriber already requested this channel's full history, so its whole back-catalog is on its way to everyone — nothing to do here." >
< span className = "inline-flex items-center gap-1 text-[10px] px-1.5 py-0.5 rounded-full border border-accent/40 text-accent" >
< History className = "w-3 h-3" / >
full history queued
< / span >
< / Tooltip >
2026-06-11 23:07:09 +02:00
) : (
2026-06-12 02:29:10 +02:00
< Tooltip hint = "Only recent uploads so far. Click to request this channel's full back-catalog (older videos + complete search)." >
2026-06-11 23:07:09 +02:00
< button
onClick = { onDeep }
2026-06-12 02:29:10 +02:00
className = "inline-flex items-center gap-1 text-[10px] px-1.5 py-0.5 rounded-full border bg-card border-border text-muted hover:border-accent transition"
2026-06-11 23:07:09 +02:00
>
< History className = "w-3 h-3" / >
2026-06-12 02:29:10 +02:00
get full history
2026-06-11 23:07:09 +02:00
< / button >
< / Tooltip >
) }
feat(m5a): channel manager, tabbed settings panel, per-user sync status
Backend: /api/channels (list + PATCH priority/hidden + attach/detach user tags),
user-tag CRUD on /api/tags, /api/sync/my-status (per-user channel sync counts).
Frontend: feed|channels page nav (URL-synced) from the account menu; a slide-in
tabbed Settings panel (Appearance, Notifications=6b sound+duration, Sync status +
actions + admin pause/resume, Account); a channel manager with priority, hide,
per-channel user tags, sync badges and 'view in feed'. Notifications now honor the
configurable sound + auto-dismiss settings.
2026-06-11 20:45:48 +02:00
{ userTags . map ( ( t ) = > {
const on = c . tag_ids . includes ( t . id ) ;
return (
< button
key = { t . id }
onClick = { ( ) = > onToggleTag ( t . id ) }
className = { ` text-[10px] px-1.5 py-0.5 rounded-full border transition ${
on
? "bg-accent text-accent-fg border-accent"
: "bg-card border-border text-muted hover:border-accent"
} ` }
>
{ t . name }
< / button >
) ;
} ) }
< / div >
< / div >
2026-06-11 21:30:25 +02:00
< Tooltip
hint = {
c . hidden
? "Hidden — this channel's videos are kept out of your feed. Click to show them again."
: "Hide this channel's videos from your feed. It stays subscribed; this doesn't unsubscribe on YouTube."
}
>
< button onClick = { onHide } className = "text-muted hover:text-fg shrink-0" aria-label = { c . hidden ? "Unhide" : "Hide from feed" } >
{ c . hidden ? < EyeOff className = "w-4 h-4" / > : < Eye className = "w-4 h-4" / > }
< / button >
< / Tooltip >
2026-06-11 23:27:11 +02:00
{ canWrite && (
< Tooltip hint = "Unsubscribe from this channel on YouTube (changes your real account). Read-only mode hides this — use Hide instead." >
< button
onClick = { onUnsubscribe }
className = "text-muted hover:text-red-400 shrink-0"
aria - label = "Unsubscribe on YouTube"
>
< UserMinus className = "w-4 h-4" / >
< / button >
< / Tooltip >
) }
feat(m5a): channel manager, tabbed settings panel, per-user sync status
Backend: /api/channels (list + PATCH priority/hidden + attach/detach user tags),
user-tag CRUD on /api/tags, /api/sync/my-status (per-user channel sync counts).
Frontend: feed|channels page nav (URL-synced) from the account menu; a slide-in
tabbed Settings panel (Appearance, Notifications=6b sound+duration, Sync status +
actions + admin pause/resume, Account); a channel manager with priority, hide,
per-channel user tags, sync badges and 'view in feed'. Notifications now honor the
configurable sound + auto-dismiss settings.
2026-06-11 20:45:48 +02:00
< / div >
) ;
}