<feed xmlns='http://www.w3.org/2005/Atom'>
<title>feedreader.git/app/src/main/java/org/terst/feedreader/data/Entities.kt, branch master</title>
<subtitle>FeeDreader — personal Android RSS reader
</subtitle>
<id>https://git.terst.org/feedreader.git/atom?h=master</id>
<link rel='self' href='https://git.terst.org/feedreader.git/atom?h=master'/>
<link rel='alternate' type='text/html' href='https://git.terst.org/feedreader.git/'/>
<updated>2026-08-22T01:13:30+00:00</updated>
<entry>
<title>Keep a just-read article visible (and Previous-reachable) until the next fetch</title>
<updated>2026-08-22T01:13:30+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-08-22T01:13:30+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/feedreader.git/commit/?id=55cdfc8fab6ddd1052593f8fd53dbb5bf730cc63'/>
<id>urn:sha1:55cdfc8fab6ddd1052593f8fd53dbb5bf730cc63</id>
<content type='text'>
The Unread/Older views are live Room Flow queries filtered on
isRead = 0, so marking an article read (which only happens on
Next/Previous, not on open) makes it vanish from the list the instant
Room re-emits -- mid-scroll, or from underneath the reader's own
Previous button, which re-queries this same flow to find its
neighbors. Concretely: Next from C to D marks C read and prunes it
from the live list; Previous from D then can't find C anymore, and
jumps past it to whatever was unread before C. A misclick or the
in-flight prune could cost you the article you meant to keep reading.

Adds ArticleEntity.readAt (set whenever isRead flips true, cleared on
explicit "mark unread"); observeUnread/observeOlderUnread now match
"isRead = 0 OR readAt &gt;= sinceFetch", where sinceFetch is
Settings.lastFetchAt -- the same durable signal the new-articles
snackbar already uses. A read article now lingers (shown dimmed via
the existing showReadState styling) until the next real fetch
actually completes, not the next DB write, and Previous/Next read from
the same still-inclusive query so stepping back to what you just read
finds it right where it was.
</content>
</entry>
<entry>
<title>Rebrand to FeeDreader; overflow-menu cleanup; deferred read-marking; folder-organized feed list</title>
<updated>2026-07-23T07:38:28+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-07-23T07:38:28+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/feedreader.git/commit/?id=ecdb45778e274126737aa87e27ed1b1c00cda01c'/>
<id>urn:sha1:ecdb45778e274126737aa87e27ed1b1c00cda01c</id>
<content type='text'>
- Rename app to FeeDreader with an owl launcher icon and a paywall-avoidance
  note in Settings
- Move Open original/Save to Stash into the reader's overflow menu; add
  Mark unread there too
- Only mark an article read when navigating away via Previous/Next, not on
  open or on back
- Saved list no longer crosses out read items; its count includes them
- Back from Unread (the root screen) now opens the feed list instead of
  exiting the app
- Feed list: clearer separator above the per-feed list, folder grouping
  (from OPML categories) with case-insensitive sort, and a virtual "Last
  fetch failed" section at the top
- Reader extraction: cap unsized decorative SVG icons (fixes Ars
  Technica's giant section-badge icon), force native loading="lazy"
  images to load eagerly in the hidden extraction WebView, and drop a
  lightbox's duplicate caption only when a real figcaption sibling exists
- Backlog notes on swipe-to-next research, extraction test coverage, and
  an async-rendered-gallery race confirmed live on Ars Technica

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01EZ7ikw2ukGJFTHE3bJS7zL
</content>
</entry>
<entry>
<title>Remove reader-mode heuristics entirely; explicit per-feed opt-in only</title>
<updated>2026-07-22T21:07:05+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-07-22T21:07:05+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/feedreader.git/commit/?id=d7c05609692153670dc136082da4e0566d7838a6'/>
<id>urn:sha1:d7c05609692153670dc136082da4e0566d7838a6</id>
<content type='text'>
Reader mode was still switching kottke articles even after the v3
opt-out and the "un-hide only if thin" fallback -- both were still
heuristic (content-length-based), and kottke's posts are routinely short
by design, not paywalled, so the heuristics kept firing on ordinary
content. Removed the auto-extract-on-thin-content logic entirely: reader
mode now NEVER turns on by itself, under any condition.

- FeedEntity.readerModeDisabled -&gt; readerModeEnabled (opt-in, default off
  for every feed -- schema v4, full table rebuild since Room's migration
  validation requires an exact column match and a bare ADD COLUMN left
  the old column orphaned; reproduced live as a crash, "Migration didn't
  properly handle: feeds").
- The reader's toggle icon now sets reader mode for the article's WHOLE
  FEED, persists it immediately, and confirms via snackbar ("Reader mode
  enabled/disabled for X") -- both in the reader screen and the Feeds
  list's own toggle. This is the only way reader mode ever turns on.
- ReaderExtractor: replaced the "un-hide everything if thin" paywall
  fallback (which is what pulled kottke's site footer into the result --
  confirmed live, twice) with one that only un-hides elements whose
  class/id actually names them as paywall/premium/subscriber content
  (West Hawaii Today's hidden div is literally class="paywall
  paywall_&lt;id&gt;"). This turned out not to be the kottke bug's actual cause
  either -- see below -- but it's a real, independent correctness
  improvement over the previous blanket version, so it stays.
- Also added: noscript-wrapped lazy-load image fallback (a common
  pattern pairs a placeholder &lt;img&gt; with a real one inside &lt;noscript&gt;,
  which our JS-enabled extraction WebView never parses into DOM per the
  HTML spec) and lazy data-src/srcset resolution generally.

Root cause of the kottke footer bug, found by fetching and inspecting
the actual page structure rather than guessing again: it's not a
CSS-hidden-element issue at all. kottke's real post content lives in a
plain, undecorated &lt;div class="post"&gt; that's a sibling (not ancestor) of
an ads container and a "Socials &amp; More" footer widget, all under one
shared &lt;div class="grid-container"&gt;. Because kottke's posts are so
short, Readability's generic paragraph-density scoring doesn't find
enough signal in .post alone and climbs to a broader container that
happens to include the neighboring footer. No un-hide logic is involved
at any point -- this reproduces identically on the plain, unmodified
Readability pass. This is a genuine limit of heuristic content
extraction on very short-form posts, not something to keep patching with
more heuristics. Reset kottke's setting back to off (its correct
default) after testing.
</content>
</entry>
<entry>
<title>Smooth scrolling, dark reader, mark-read-on-scroll, feed names, per-feed reader-mode opt-out</title>
<updated>2026-07-22T17:56:50+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-07-22T17:56:50+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/feedreader.git/commit/?id=9da3fbcdef71e90d52045531c7c18faff02f2ff6'/>
<id>urn:sha1:9da3fbcdef71e90d52045531c7c18faff02f2ff6</id>
<content type='text'>
- ReaderScreen: AndroidView's update() was reloading the WebView's HTML on
  every recomposition (e.g. isSaving flipping mid-read), resetting scroll
  position -- this is what made scrolling feel unsmooth. Now only reloads
  when the content string actually changes.
- ReaderScreen: reader content now uses a hardcoded dark palette instead of
  browser-default white, matching the app's always-dark theme.
- ArticleListScreen: mark-as-read-on-scroll -- once an article has scrolled
  fully above the viewport it's marked read, the standard feed-reader
  behavior, via a new batched ArticleDao.markReadBatch(). Verified against
  the on-device DB (scrolled-past articles flipped to isRead=1, the
  still-visible one stayed unread).
- ArticleListItem/queries: LEFT JOIN feeds for a feedTitle column, shown per
  row in the Unread/Saved aggregated views and as a reader subtitle; the
  per-feed article list's top bar now shows the real feed title instead of
  a generic "Articles".
- FeedEntity.readerModeDisabled (schema v3, migration 2-&gt;3): per-feed
  opt-out of the Readability fallback/toggle, toggled from a new icon in
  FeedListScreen. Added for kottke.org specifically -- confirmed its thin
  linkblog posts were making Readability extract the page footer instead
  of the actual post; disabling reader mode for that feed now shows its
  own (short but correct) content instead.

All verified live on the emulator against the real 107-feed dataset.
</content>
</entry>
<entry>
<title>Fix Unread/startup crash; add reader-mode toggle and swipe navigation</title>
<updated>2026-07-22T16:54:03+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-07-22T16:54:03+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/feedreader.git/commit/?id=39cca1cb3650edb066f57d0891b1873b767b1c43'/>
<id>urn:sha1:39cca1cb3650edb066f57d0891b1873b767b1c43</id>
<content type='text'>
- Root cause of the reported crashes: observeUnread/observeByFeed/
  observeSaved used SELECT *, pulling feedContentHtml/extractedContent
  (potentially large per-article blobs) just to render a list row.
  With 107 feeds worth of real articles this blows past Android's
  CursorWindow/memory limits, scaling with item count -- and since
  Unread is now the landing screen, it crashed at startup too. Added
  an ArticleListItem projection excluding the heavy content columns;
  list queries now only select what a row actually renders.
- FilterRuleEngine now takes a small Titled interface (title/feedId)
  instead of the full entity, so it works with both ArticleEntity and
  the new lighter projection.
- Reader-mode toggle: a button in the reader to manually switch
  between feed-provided content and Readability-extracted full text,
  matching Inoreader's full-text-view toggle (previously only an
  automatic fallback when feed content was thin).
- Swipe between articles: ReaderScreen now wraps a HorizontalPager over
  a snapshot of the current list's article-id order, so swiping moves
  to the next/previous article. Snapshotted rather than live, since
  marking an article read would otherwise drop it from the live Unread
  query mid-swipe and shift every later page underneath the user.

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01EZ7ikw2ukGJFTHE3bJS7zL
</content>
</entry>
<entry>
<title>First round of testing feedback: images, unread landing, dark, delete</title>
<updated>2026-07-22T16:26:45+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-07-22T16:26:45+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/feedreader.git/commit/?id=52d7877fccd60a1dae33d877ba6aa765dc098a52'/>
<id>urn:sha1:52d7877fccd60a1dae33d877ba6aa765dc098a52</id>
<content type='text'>
- Fix OPML import status message reporting the raw pre-dedup count
  (144) instead of the actual unique feed count after Room's upsert
  collapses duplicates (108) -- cosmetic bug, the DB was already
  correct.
- Article thumbnails: extract media:thumbnail/enclosure or the first
  &lt;img&gt; in feed content, show via Coil in the article list.
- App now lands on Unread instead of the feed list; a top-bar icon
  gets from there to the feed list (forward nav, not back-stack pop,
  since Unread is now the root).
- Dark color scheme by default.
- Delete-feed action (with confirmation) on each feed row -- was
  entirely missing. Deletes its non-saved articles too, so they don't
  linger as orphaned rows in the cross-feed Unread view; saved articles
  are kept regardless.
- Room migration 1-&gt;2 for the new imageUrl column (real migration, not
  destructive fallback, since there's now real on-device subscription
  data from testing).

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01EZ7ikw2ukGJFTHE3bJS7zL
</content>
</entry>
<entry>
<title>Initial commit: Feeds, an Android RSS reader</title>
<updated>2026-07-22T08:08:58+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-07-22T08:08:58+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/feedreader.git/commit/?id=d8ce4b3c0f383e844b125d2d3abd618715bb3c89'/>
<id>urn:sha1:d8ce4b3c0f383e844b125d2d3abd618715bb3c89</id>
<content type='text'>
Feed subscriptions (RSS 2.0 + Atom, hand-rolled parser), OPML
import/export, WorkManager periodic fetch with conditional GET,
Room-backed local storage (feeds/articles/filter rules), keyword
mute/highlight filtering.

Reader mode: on-device extraction via Mozilla's Readability.js
(vendored from github.com/mozilla/readability, pinned to release
0.6.0) run in a hidden WebView, for feed articles whose provided
content is too thin to be the real article body.

Save: explicit action (from a feed article, or the Android share-sheet
target for any URL) archives to Stash via POST /artifacts?sync=true,
while a local on-device copy (Readability-extracted) is always kept so
saved articles read offline regardless of Stash's reachability.

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01EZ7ikw2ukGJFTHE3bJS7zL
</content>
</entry>
</feed>
