- Settings > Manage users: add an account (password, or none for proxy
sign-in), toggle admin, remove. Backed by GET/POST /api/users and
PATCH/DELETE /api/users/{id}, 403 for non-admins. The only admin
cannot be demoted or removed.
- GET /api/logs is admin-only and the Log button is hidden for others;
the log names every account, feed and failed sign-in.
- Feeds inside an OPML list those with unread items first, in the
sidebar folder and on the subscription's page.
- Deploying is now buildx --push to 192.168.1.130:5000 and recreating
the ipodderx service of the Arcane project content; CLAUDE.md and the
README's Docker section say so.
- Tests: Playwright for user admin, the last-admin guard, 403s for a
non-admin and the unread ordering (new Aardvark Radio fixture); a unit
test for last_admin; the smoke test drives usersModal.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0173mGu6rK18Ne7UGTwAaVJV
The rss crate keeps at most one enclosure per item and, when a feed ships
several, silently keeps the last -- so a two-file item lost its first
file. enclosures_by_item reads them from the XML in document order,
unescaping attributes so a URL's & survives. The row summarises the
one you would act on and counts the rest; the pane below lists them all.
Non-media enclosures gain a View link opening in a new tab: the
publisher's URL, or the local copy once downloaded. A direct link, not a
proxy, so the daemon does not become a fetch-anything relay.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AdXho5tTkjFLeUXKbEjKBh
The media-type filter stopped new image enclosures being fetched, but ones
already on disk still got a play button and an <audio> element, because
the UI tested for a path rather than for a playable type. Four places did
this, including play() itself, which picked the first downloaded enclosure
whatever it was.
isPlayable() checks audio/* or video/*, falling back to the extension when
a feed declares no type. A downloaded non-media file now shows as its kind
with Save and Delete, so it stays available without posing as an episode.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AdXho5tTkjFLeUXKbEjKBh
The log view splits into All / Daemon I/O / Scans / HTTP. Daemon I/O is
the control protocol itself, logged where every command funnels through
so it covers socket clients, the CLI and the web UI alike. stderr and the
in-app buffer now have separate filters, so the UI can keep debug detail
the terminal should not carry.
Playwright drives a real browser against a daemon on fixture feeds. Eight
tests, each mapping to a bug that reached a user -- the Rust tests and the
stub-DOM smoke test cannot see a wrong selector or a dead handler.
It immediately found one: OPML folders rendered expanded by default,
because the code stored closed groups, so any folder never toggled counted
as open.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AdXho5tTkjFLeUXKbEjKBh