Commit Graph
100 Commits
Author SHA1 Message Date
mathias 43269bf1d7 Merge pull request 'Replace the feed select with a multi-feed filter modal' (#4) from feature/feed-filter-modal into master
Reviewed-on: #4
2026-09-27 11:31:27 +02:00
mathias 5ed780baa3 Replace the feed select with a multi-feed filter modal
The header's feed <select> is now a funnel button that opens a modal with
an "All feeds" checkbox and one checkbox per feed with unread items. The
selection is either "All feeds" or at least one feed, is applied once when
the modal closes, and is persisted in localStorage.

- feedFilter holds string[] | null; setFeedFilter is a no-op when the
  selection is unchanged (order-insensitive), sparing callers the
  scroll/mark-read side effects.
- Selected feeds stay listed at 0 unread so they can be unchecked; titles
  whose feed no longer exists are pruned after each fetch.
- The persisted filter is cleared on logout and on 401 (handler extracted
  to httpAuthInterceptor.js so it is unit-testable).
- The modal locks body scroll while open so scrolling behind it can't
  mark articles read.
2026-09-27 11:28:48 +02:00
mathias 8f7ddb77c4 Stop reserving layout space for the idle sync spinner
visibility:hidden still occupied the header's flex row, squeezing the
title on narrow screens and clipping the unread counter. Render the
spinner with v-if instead so it takes no space when not syncing.
2026-09-25 13:55:23 +02:00
mathias e51bce4244 added pwa ap manifest 2026-09-23 17:57:44 +02:00
mathias f31fccdd16 fix break header 2026-09-14 19:59:22 +02:00
mathias a7cfd267c6 Strip Al Jazeera's Recommended Stories section from readable articles
Al Jazeera's article pages embed a <section class="more-on"> block (a
'Recommended Stories' heading plus a list of unrelated article teasers)
that was getting pulled into the parsed content by Readability. Remove
it from the fetched DOM before Readability runs, same as the existing
social-bar/abo/teaser stripping in getReadable().
2026-09-14 18:40:34 +02:00
mathias c584ac4f60 Merge pull request 'Show a sync-in-progress spinner in the header next to the feed filter' (#3) from feature/sync-spinner into master
Reviewed-on: #3
2026-09-14 18:08:44 +02:00
mathias 9c042411fb Show a sync-in-progress spinner in the header next to the feed filter
activeSyncCount/isSyncing() in useFeeds.js already refcounted both boot
reload (fetchData) and manual sync() but wasn't reactive. Make it a ref
+ computed and expose it so AppNav can render a small spinner next to
the feed filter whenever a sync is in flight.

The spinner sits outside the filter's own v-if so it also shows during
a manual sync from pages other than /feeds, and its box is always
reserved (visibility toggle, not v-if) so the one-time --app-nav-height
measurement stays correct.
2026-09-14 18:05:24 +02:00
mathias 4d9e899519 Disable mark-as-read while a sync or reload is in progress
useFeeds.js had no concept of a sync/reload being in flight, so two
passive mark-as-read paths could race a wholesale fetchData()/sync()
list replacement:

- handleIntersection() (list-view scroll marking)
- markCurrentArticleRead() (article-view display/paging marking)

Both are now gated by a refcount (activeSyncCount/beginSync/endSync/
isSyncing), mirroring the existing pendingReadCounts pattern for the
same class of overlapping-async problem. A refcount rather than a
boolean because sync() nests fetchData() inside it, and fetchData()
can also run independently while a sync() elsewhere is still in
flight.

Reads suppressed during a sync/reload aren't dropped: their ids are
queued and flushed (marked read, and swept out of the list) once
every in-flight fetchData()/sync() call has finished. The explicit
"Mark all read" action is left ungated, since it's a deliberate,
already-confirmed action rather than a passive side effect.

87/87 frontend tests passing.
2026-09-13 12:11:14 +02:00
mathias c290513ed1 Fix read-article handling across feed filter switches in article view
Article view marks the displayed article read in place (feed.read =
true) without removing it, so currentIndex stays valid while paging.
leaveArticleView() already dropped those afterward, but switching the
feed filter while still in article view never did — so an
already-read article could resurface (re-selecting its feed
re-projected it from allItems as if still unread). Extracted the drop
into a shared dropReadArticles() and call it from setFeedFilter() too.

Also, switching the filter while in article view immediately displays
the new feed's article in full, which is a genuine view — same as
paging or toggling into article view — but nothing marked it read, so
a single-article feed could never be marked read that way at all. This
was deliberately excluded (see commit 169c294) after a generic watch
proved too broad; setFeedFilter() now marks it explicitly, scoped to
article view only, so list view's scroll-driven read-marking is
unaffected.
2026-09-12 19:42:22 +02:00
mathias 41a17eba6d Move Add Feed button from hamburger menu to admin area
The Add RSS modal is now opened from a button directly above the feed
list in AdminFeeds.vue instead of the hamburger menu, so feed creation
lives alongside the rest of feed management. AppNav.vue no longer
renders it.

AddUrl.vue emits an `added` event on a successful save; AdminFeeds.vue
listens for it and reloads its feed list, so a newly added feed shows
up immediately without leaving the admin page.

Also fixes a bug this move surfaced: the modal's "Close" button had no
`type`, so inside the form it defaulted to type="submit" — clicking
Close on a filled-in form silently created the feed.
2026-09-12 19:42:08 +02:00
mathiasandClaude Sonnet 5 169c294e62 Mark article read on display in article view, including on boot
markCurrentArticleRead() already marked the displayed article read
immediately at each deliberate navigation point (entering article view,
paging next/prev), but reloading the page while viewMode was already
'article' (persisted in localStorage) booted straight into article view
without marking anything read — the article on screen stayed unread
until the user paged forward/back at least once.

Close that gap with a single explicit call in RssFeeds.vue's onMounted:
once the initial fetch resolves, if we're already in article view, mark
the now-displayed article read the same way toggleViewMode()/
nextArticle()/prevArticle() do.

(A first attempt replaced the explicit call sites with a generic watch
on the displayed article's id, but that also fired on unrelated changes
to it — e.g. switching the feed filter while in article view resets
currentIndex and reprojects feeds, so the watch would silently mark an
article the user never viewed as read. Reverted to the explicit,
call-site-scoped approach and added only the one boot-time call needed.)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K2sYVA7rh7N5RYmcCd2KDV
2026-09-12 15:29:32 +02:00
mathias e2df60d112 Merge pull request 'Fix unread counter stuck after reading all articles' (#2) from hotfix/unread-counter-stuck into master
Reviewed-on: #2
2026-09-11 17:22:28 +02:00
mathiasandClaude Sonnet 5 5f816bb0f5 Fix unread counter stuck after reading all articles
markRead() fired its PUT fire-and-forget with no retry and silently
swallowed failures. If one PUT among a burst (paging through article
view fires one per page turn) failed or lost a race with the app's own
background sync()->fetchData() refetch, that article's read-mark was
permanently lost: the header's unreadCount (derived live from
allItems) would show it again mid-session, and a reload reproduced it
identically since the DB genuinely still had it unread.

- markRead() now retries transient failures (no response / 5xx / 429)
  with backoff and a request timeout, skips retrying non-retryable
  4xx, and surfaces a message on final failure instead of only
  console.log.
- A pendingReadCounts refcount (keyed by article id) tracks in-flight/
  retrying marks; fetchData() excludes those ids - snapshotting before
  its GET is dispatched, not just once the response lands - so a
  concurrent refetch can't resurrect an article whose read-confirmation
  is still in flight or unsettled at request time.
- markAllRead() now reports a partial-failure summary instead of
  unconditionally claiming full success when some marks fail.

Extends vue/src/composables/__tests__/useFeeds.spec.js with coverage
for retry/backoff, non-retryable errors, the dispatch-before-response
race, overlapping concurrent marks for the same id, and markAllRead's
partial-failure message.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CVw1XNQjodp4B7qiZqjtDE
2026-09-11 17:21:16 +02:00
mathiasandClaude Sonnet 5 67b970df9b Merge branch 'fix/concurrent-sync-duplicate-items'
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012CHcxDSPbHhe7sLJQBRVC9
2026-09-10 16:11:48 +02:00
mathiasandClaude Sonnet 5 6c8b4e9e32 Fix duplicate feed items from concurrent syncs
A sync button press racing a page-reload sync could run two sync
requests concurrently. create_feed_item used a check-then-insert
pattern (query for an existing item, then insert if none found), so
both requests could pass the check before either inserted, creating
duplicate items.

- Add a UNIQUE (feed_id, url) constraint on feed_item, keyed on the
  article's link rather than its title since that's RSS's stable
  article identity (a feed editing a headline after publishing, same
  link, would otherwise dupe under title-based matching).
- The migration first collapses any duplicates already created by the
  race, propagating read=true onto the surviving row when any
  duplicate in its group was already read, so the cleanup can't make
  an already-read article look unread.
- create_feed_item now does a single atomic
  INSERT ... ON CONFLICT (feed_id, url) DO NOTHING instead of
  select-then-insert, closing the race entirely.
- Add a test that races two threads on separate connections inserting
  the same item and asserts only one row survives.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012CHcxDSPbHhe7sLJQBRVC9
2026-09-10 16:04:34 +02:00
mathias e2fb32f898 fix cargo audit reports and upgraded cargo dependencies 2026-08-29 14:28:59 +02:00
mathias a2c947f870 Change top bar to only appear whene scrolling up 2026-08-28 07:42:59 +02:00
mathias abb6002078 remove counter of selected feed 2026-08-28 07:37:42 +02:00
mathias f447a9a163 remove slide effect 2026-08-27 21:33:58 +02:00
mathias a26f31a150 bugfix: extend swipe listener to swipe on empty space 2026-08-27 14:25:38 +02:00
mathias c911258578 Revert "Merge branch 'swipe-drag-follow'"
This reverts commit d52fa90b6b, reversing
changes made to 77438d0532.
2026-08-26 22:07:58 +02:00
mathiasandClaude Sonnet 5 d52fa90b6b Merge branch 'swipe-drag-follow'
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-26 22:04:01 +02:00
mathiasandClaude Sonnet 5 bfb926cec6 Replace threshold-based swipe with live drag-follow in article view
The article now visually tracks the finger in real time while swiping
(via a rAF-throttled touchmove handler), instead of only reacting
after the fact on touchend. On release it completes the page-turn if
dragged past ~35% of the article's width, or on a fast-enough flick
even short of that distance; otherwise it springs back to center.
Dragging past the first/last article is damped rather than free, for
tactile feedback that there's nothing further that way. A vertical
drag is detected within the first ~10px of movement and left alone so
it can't fight normal page scrolling.

Also fixes a real bug found while testing the flick path: Date.now()
has ~1ms resolution, so a genuinely fast flick often measures as
elapsed === 0. The velocity calculation was treating that as "zero
velocity" (dividing 0 elapsed into "no flick") instead of "as fast as
it gets" — now a zero-elapsed gesture with real movement counts as a
flick outright.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-26 22:03:41 +02:00
mathiasandClaude Sonnet 5 77438d0532 Merge branch 'fix-android-swipe-gesture'
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-26 21:28:18 +02:00
mathiasandClaude Sonnet 5 49ceec5125 Fix article-view swipe being swallowed by Chrome-on-Android back gesture
On real Android touchscreens, a horizontal drag on content with nothing
left to scroll horizontally gets claimed by Chrome's native
swipe-to-go-back/forward gesture before our own touchend listener
(article-view swipe navigation) ever sees it — silently, with no
touchend firing at all. Synthetic touch testing (mouse-drag emulation
in devtools) doesn't go through that gesture recognizer, so it worked
there while doing nothing on an actual phone.

Fix: overscroll-behavior-x: contain on body stops Chrome from claiming
the gesture for its own navigation, letting our touchend fire normally.

Also drops the overflow-x: hidden added to .article-single for the
slide transition — body already has it (for the same full-bleed-image
reason), so it was redundant.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-26 21:27:23 +02:00
mathiasandClaude Sonnet 5 15ac8c287b Merge branch 'article-view-swipe-nav'
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-26 21:16:26 +02:00
mathiasandClaude Sonnet 5 13d9c2c908 Add swipe navigation and slide transition to article view
Swiping left/right in the single-article view now pages to the
next/previous article, same as the up/down nav buttons. Touch handlers
live on .article-single: a swipe needs >=50px horizontal travel and
<=75px vertical travel to count (so scrolling/diagonal drags don't
trigger it), and multi-touch or swipes starting inside a horizontally
scrollable <pre> block are ignored so code blocks keep their own
scroll behavior.

Also adds a direction-aware slide/fade transition (via Vue's
<Transition>, keyed on currentIndex) when paging between articles,
triggered by both the swipe gesture and the nav buttons.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-26 21:14:52 +02:00
mathias 01f8a30505 autorestrat images 2026-08-03 14:58:45 +02:00
mathias a898e7ac33 counter on feed select 2026-07-13 17:46:49 +02:00
mathias 671ed86b48 added test 2026-07-13 09:47:10 +02:00
mathias af5e2b8137 upgrade packages 2026-07-12 17:27:16 +02:00
mathias 2b1d1c57ff keep state of aritcle/list view 2026-07-12 17:22:30 +02:00
mathias 31991ea1f8 Feed filter implementation 2026-07-12 12:28:32 +02:00
mathias 0e3142bac9 remove deprecations, remove header when scrolling 2026-07-04 17:06:45 +02:00
mathias a73a1b57de padding for article view 2026-07-03 19:38:53 +02:00
mathias 3642635b20 remove variable header size 2026-07-03 11:10:14 +02:00
mathias b3cf5e4787 variable header size 2026-07-02 22:24:01 +02:00
mathias fe0adcf68e added shadow to header 2026-07-02 21:51:58 +02:00
mathias 3f9de099aa new textadjustment options 2026-06-23 18:03:11 +02:00
mathias b9c0951f2b Filter comercials in readable 2026-06-22 20:30:00 +02:00
mathias 7a24980101 Fix article get read when navigation to admin area. 2026-06-19 16:41:16 +02:00
mathias dfc2e29e36 Change version number 2026-06-19 13:44:05 +02:00
mathias a90d10368e New feauter, change font family and font size 2026-06-19 13:42:57 +02:00
mathias 5417176dd4 add some filters for unwanted content 2026-06-16 19:08:31 +02:00
mathias 4d3f5d3285 fix autoscroll 2026-06-16 16:26:33 +02:00
mathias 967803c326 bugfixes 2026-06-16 12:34:08 +02:00
mathias e9c865a254 Improved sticky header 2026-06-16 12:04:42 +02:00
mathias 570db2d948 keep original article link in readable 2026-06-15 19:58:13 +02:00
mathias a37d845875 Fix stuttering list view 2026-06-15 19:53:18 +02:00
mathias 8e57e2f02a Added sync on reload 2026-06-14 17:13:41 +02:00
mathias 3671b90b81 Fix fontend tests, move next button in list view 2026-06-14 17:04:37 +02:00
mathias a399ede401 Fonts, Docker fixes 2026-06-14 09:03:06 +02:00
mathias 82ec6ea902 fix article read after switching to article view 2026-06-13 11:59:33 +02:00
mathias fbf3597984 improve desktop readable for article view 2026-06-13 11:48:45 +02:00
mathias e9580037ef increase token lifetime to one month 2026-06-12 19:34:08 +02:00
mathias b457b8abaa Improve security 2026-06-12 19:22:07 +02:00
mathias 0820ce6ef7 fix some frontend issues 2026-06-12 10:57:48 +02:00
mathias ed1241490d fix scroll to top in article view 2026-06-12 09:19:15 +02:00
mathias 177d975b4d frontend improvement 2026-06-10 19:22:24 +02:00
mathias 52ea84747a added anyhow, improve hamburger menu, improve dw articles 2026-06-10 18:51:55 +02:00
mathias 0420cf0dd5 fix sync missing articles 2026-06-10 06:08:17 +02:00
mathias 972e967432 fix sync missing articles 2026-06-10 05:35:46 +02:00
mathias 400648c3d1 added admin area to delete feeds 2026-06-09 21:55:07 +02:00
mathias 6ae6490dec Merge pull request 'added admin area to delete feeds' (#1) from feature/admin-area into master
Reviewed-on: #1
2026-06-09 21:48:28 +02:00
mathias f3af42c3c5 added admin area to delete feeds 2026-06-09 21:45:13 +02:00
mathias 8b56174856 fix scroll to mark read bug 2026-06-09 21:30:26 +02:00
mathias 039e0b448c all caught up icon, counter dynamic 2026-06-09 20:20:17 +02:00
mathias b851e0257c fix sync issue, frontend improvement 2026-06-09 19:50:47 +02:00
mathias d826a8f3dc cleanup when syncing 2026-06-08 17:31:10 +02:00
mathias 83fdb464af fix card layout 2026-06-08 16:59:30 +02:00
mathias 0ee9e22109 added docker update to readme 2026-06-08 14:31:06 +02:00
mathias a98a8ba9e6 card view, minor css bugfixes 2026-06-08 14:22:04 +02:00
mathias 3c42ebb972 mark all as read 2026-06-08 07:06:38 +02:00
mathias b4fc86302f hamburger menu, article view 2026-06-08 06:39:47 +02:00
mathias 39f08c7218 added favicon, improve layout 2026-06-07 20:18:31 +02:00
mathias b865fe982e upgraded packages 2026-06-07 17:58:03 +02:00
mathias 6eeb3384fe fixed rootless docker setup 2026-06-07 17:31:46 +02:00
mathias 675bf15828 fixed rootless docker setup 2026-06-07 17:12:17 +02:00
mathias c5e5e463f9 fixed rootless docker setup 2026-06-07 17:07:26 +02:00
mathias 997fc2989a added rootless docker setup 2026-06-07 16:57:12 +02:00
mathias 09eae69041 upgrade postgres 2026-06-07 16:50:14 +02:00
mathias e9d7f63ff3 remove .env 2026-06-07 16:29:52 +02:00
mathias 841e8419b0 updated rust version, minor fixes 2026-06-07 16:26:42 +02:00
mathias b4874ad318 claude rework 2026-06-07 15:43:43 +02:00
mathias a2e2ff141e added modal.css 2024-01-06 11:46:49 +01:00
mathias e33bb76f50 centralize modal css 2023-11-26 12:27:51 +01:00
mathias 20d76c2a56 database error message, date sorting 2023-11-19 14:03:03 +01:00
mathias 91eeb29b06 added dates, clippy fixes, sync date improvement 2023-11-14 19:09:21 +01:00
mathias ea754b6f3e Improve parsing 2023-11-12 11:05:40 +01:00
mathias d920f5b9b9 Add new feed url 2023-11-10 17:17:29 +01:00
mathias f9f274f6e2 modal, add feed [wip] 2023-11-05 13:26:02 +01:00
mathias 0fae2e407c minor navigation changes 2023-11-02 17:26:42 +01:00
mathias 579fa1f7ca backend, mark item as read 2023-10-30 19:52:51 +01:00
mathias abf9a1b818 mark read backend 2023-10-29 17:06:19 +01:00
mathias 1789458830 observer frontend 2023-10-29 12:52:40 +01:00
mathias b0280b0a41 observer for article divs 2023-10-28 22:43:29 +02:00
mathias e4a65416c7 added timestamp for feed items 2023-10-24 19:19:47 +02:00
mathias ee80cbd53b Added readable mode for article content 2023-10-15 17:44:05 +02:00
mathias 3d77c6f30f Change get articles to read from database instead of dummy data. 2023-10-14 18:32:49 +02:00