order_sql takes pinned_first, which puts coalesce(s.flagged, 0) DESC ahead of
the chosen sort, so pins lead every list in whatever order is asked for and on
every page of it. Not when sorting by the pin column itself, where the direction
is the point, and not for Currently Listening. Pinning now asks for the list again
so the row moves at once, instead of redrawing it where it stood.
Closes#35.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
/admin, with Server, Accounts and Log sections chosen by the URL's hash. The
server sends the page and /admin.js to admins only (anyone else asking for the
page goes back to the app, and the script is 403), and removes the header's link
to it from everyone else's page rather than hiding it. The API keeps refusing
all of it to non-admins as before.
Settings becomes personal: theme, OPML import and export, and the schedule and
download folder to read. The server fields, the Users dialog and the Log dialog
move out of dialogs.ts into admin.ts.
The CSS moves out of index.html into web/app.css, which both pages load as
/app.css?v=<hash>, served immutable like the scripts. The smoke test checks both
pages.
Closes#19.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
subscriptions.pinned, per person, set by PATCH /api/feeds/{id} {pinned} and
returned as FeedRow.pinned. Kept out of Sub, which the scanner merges into its
policy; set_subscription names its columns, so saving a feed's settings leaves
the pin alone (tested).
Pinned feeds come first in the list, a pin before the name and a rule under the
block: a pinned folder with its feeds under it, a feed from inside one lifted out
of it. The pin button is on both the feed and the folder page.
Closes#33.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Only the player bar's button changed; the files pane's, the row's and the
toolbar's kept showing play while it played. play() now pauses when asked to play
what is already playing, which makes each of them a toggle, and syncPlayButtons()
repaints them on play, pause and ended and whenever the list or reader is drawn.
Closes#34.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The files sit over the text on a phone, so an item without any showed a box
saying so before its text. Nothing is shown now; the desktop files pane already
hid itself when empty.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- users.theme and users.theme_mode, added by migrate(); GET /api/me returns them
and PATCH /api/me saves them, refusing anything but a plain name and
light/dark/auto, since index() writes them into the page's <html> tag.
- The page arrives with data-theme and data-choice already on <html> (and
data-mode unless Auto), so it is drawn in the account's theme from the start.
- A theme a browser kept in localStorage goes up to the account once, the first
time an account with none loads the page.
- Saves go one at a time, each with the choice as it stands: sent all at once, a
quick run through the list could land out of order and keep a theme passed on
the way. The browser test caught it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Pull the item list down from its top: checks the feed (or every feed, on All
Subscriptions) for new items, which arrive as they do from the scan button.
overscroll-behavior keeps the browser's own pull-to-reload out of it.
- Swipe the item you are reading left for the next, right for the one before,
or back to the list from the first. A vertical move is a scroll; something
that scrolls sideways, or takes typing, keeps its own swipe.
Touch events only, so a mouse never sets them off.
Closes#22.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- The logo as favicon, squared up (it is 128x121), at /favicon.png and at
/favicon.ico outside the auth layer, where a browser asking on its own got a
401; an apple-touch-icon on white (#32).
- An item not yet downloaded had its download bar on a line of its own under the
file icon, lifting the icon above its row's; the bar now sits under it without
taking space (#31).
- A feed error is Font Awesome's exclamation, hung in the margin where a folder's
triangle is, in the same column; a folder holding a failing feed has its
triangle turn red.
Closes#31, #32.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The theme picker lists Modern (the old Dark and Light), Classic and six new
palettes, from Dracula's spec (with Alucard), Material 3's baseline scheme,
libadwaita's CSS variables, Flat Remix's _colors.scss, Paper and Nord. A second
setting picks Light, Dark or Auto where a theme has both; Classic and Paper do
not, so it is hidden for them.
The page gets data-mode, light or dark, and Auto is worked out in theme.ts from
the system, so each palette is written once instead of again under a media
query. Every new palette clears WCAG AA for text on its backgrounds. An old
ipx.theme of dark, light or auto carries over as Modern.
Closes#27.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- web/src/*.ts: the script that was inline in index.html and login.html, split along its
existing sections. Still one scope, concatenated in order, not modules.
- web/build.mjs strips the types, puts the script in the page and minifies it with swc;
build.rs runs it into OUT_DIR and web.rs include_str!s the result. 137 KB -> 106 KB.
- npx tsc -p . type-checks web/src, loosely; the handful of annotations it needed
change no behaviour.
- The Docker build installs node and swc (npm ci --omit=dev).
- Two list requests racing no longer let the older one win, and switching tabs clears
the selection it closes, which made a browser test flaky.
Closes#23, #24.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>