Commit Graph
7 Commits
Author SHA1 Message Date
Vincent Rozenberg 41d638b232 feat: read the subscription list off YouTube, and an app-shaped menu bar panel
Takeout is a snapshot you have to go and fetch. This reads the live list
from youtube.com/feed/channels in a browser already signed in.

It runs JavaScript in the page rather than fetching it here, because
the session cannot be borrowed: Chromium encrypts its cookie store with
a per-app Keychain key, and Arc's cookies read with Chrome's key came
back undecryptable — 1346 of 2196, the session cookies among them. The
list is also lazily loaded, so it has to be scrolled to the end, which
only the page can do. Measured against a hand-scrolled count: 208 both
ways.

The browser is brought to the front first. A background tab has its DOM
discarded, so it reports its address while having nothing on it, which
reads exactly like an empty subscription list.

The page gives handles, not channel ids. Most belong to channels already
known here and are matched by name, costing nothing; only the rest are
looked up, four at a time. That mattered more than it sounds: with the
name selector wrong every name came back empty, nothing matched, all 208
were looked up blind, and the result claimed 58 channels should be
deleted. With names read correctly it is 4 lookups and 0 deletions.
Names arriving doubled — "3D OCD 3D OCD", which reads fine and matches
nothing — are collapsed.

osascript prints a string result in source form, quoted with the
newlines escaped, so a list of rows arrived as one line that looked
like no rows at all. It is unquoted before parsing.

The menu bar item is now a window, not an NSMenu: the app's own type,
spacing and colours, hanging from the icon and closing on blur. Adding a
channel is gone from it — the sidebar already does that.

Replacing the subscription list is confirmed in the main window, never
in a panel that closes when you look away, and the confirmation counts
what would go before anything happens.

Verified end to end: 208 read, 4 looked up, 0 removed, 48 downloads
untouched.
2026-09-04 01:34:29 +02:00
vincent 832a8f0f26 fix: the menu bar could never read a browser's address
Tested end to end from the menu bar: a video playing in Arc was saved,
its channel subscribed to, and the subscription removed again.

Three things were wrong.

One script named all seven browsers. AppleScript resolves an
application's terminology when it compiles, so naming a browser that is
not installed is a compile error — which no `try` can catch, and which
kills the whole script before a line of it runs. On a Mac without Brave
the lookup failed outright and Arc, first in the list and working, was
never asked. Each browser now gets its own script.

The tray icon was dropped as soon as it was built. TrayIcon is
reference-counted and "the icon is removed when the last instance is
dropped", so it was created and destroyed in the same breath. It is
held for the life of the app.

Subscribing to a channel already present as a bare row — the parent of
a video saved from the menu bar — was refused as a duplicate, and the
upsert would not have promoted it anyway. Saving a video from a channel
therefore made it impossible to subscribe to. The check asks whether it
is a subscription, not whether the row exists, and subscribing promotes.

Feedback no longer depends on one channel that can fail silently: a
notification from the app, the AppleScript one behind it, and the
menu's own first line, which reports the last result and cannot be
suppressed. A log beside the database records each step, which is how
all three of these were found rather than guessed at.
2026-08-29 20:27:52 +02:00
vincent 9bb7b71225 feat: uniform control height, chrome auto-hide, streaming quality
Every control in the app now shares one height (CONTROL_H), with width
still growing to fit its label. The player transport is larger, Back is
an icon, and the footer buttons match the prev/next size.

Player chrome — transport and edge arrows — fades after 2.6s of
inactivity and never hides while paused.

The title-bar strip collapses in macOS window fullscreen, where the
traffic lights are gone and it was just a blank white bar.

Streaming quality is now selectable alongside download quality. A cap is
applied by rewriting YouTube's HLS master playlist down to the best
variant at or below the chosen height, keeping its audio group. That
playlist is served from a small loopback HTTP server: Safari's native
HLS is backed by AVFoundation, which cannot read blob: or custom-scheme
URLs, so a Blob URL silently fails to play.

Settings closes with an icon button and its selects match the shared
control height.
2026-08-29 11:28:05 +02:00
vincent 61db10b2c8 fix: native video fullscreen, window dragging, quality picker, spinner
Enable WKWebView's elementFullscreenEnabled at startup. It is off by
default in a Tauri window, which is why the native player had no
full-screen button and why right-click 'Enter Full Screen' did nothing.
With it on, WebKit's own control appears on the video and gives the real
system fullscreen player, so the app-level workaround is gone.

Restore window dragging: data-tauri-drag-region needs
core:window:allow-start-dragging, which was missing, leaving the
title-bar strip inert.

Download quality is now a resolution picker (best/2160/1440/1080/720/
480) instead of a two-way toggle; every selector still pins AAC audio
because Opus in MP4 is silent in WebKit.

Tile titles truncate to a single line so rows stay uniform, and the
player shows a spinner while the video buffers, not just while the
stream URL resolves.
2026-08-29 10:38:35 +02:00
vincent 359873cff1 fix: real video fullscreen, and edge navigation arrows
The full-screen button did nothing: WKWebView inside a Tauri window has
element fullscreen disabled outright, so both requestFullscreen() and
WebKit's webkitEnterFullscreen() are inert, and Tauri exposes no switch
for it. Fullscreening the window alone is not the same thing either —
the player's header and footer stay on screen around the picture. The
button now fullscreens the window and hides every piece of chrome, so
the video alone fills the display, with Esc and a hover control to exit.

Prev/next also get large arrows on the left and right edges of the
picture, fading in on hover, alongside the header buttons.
2026-08-29 10:19:09 +02:00
vincent a30423e4b5 feat: Tauri commands, concurrent refresh, download manager
Refresh fetches 8 channel feeds concurrently and mirrors thumbnails to
disk so the offline feed still renders. Downloads are capped at 2 and
shell out to yt-dlp with an absolute binary path, since GUI apps do not
inherit a login shell PATH.
2026-08-29 02:28:26 +02:00
vincent b724e9d56f feat: Takeout CSV, Atom feed, yt-dlp progress, and SQLite layers
37 unit tests covering the three parsers and the database, including
that a feed refresh preserves download state and that a truncated
feed response fails rather than storing partial results.
2026-08-29 02:26:17 +02:00