<feed xmlns='http://www.w3.org/2005/Atom'>
<title>feedreader.git/app/src/main/java/org/terst/feedreader/ui/MainActivity.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-22T22:42:15+00:00</updated>
<entry>
<title>Restore the 5am morning-fetch alarm</title>
<updated>2026-08-22T22:42:15+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-08-22T22:42:15+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/feedreader.git/commit/?id=bc891a67802c837d77726f618e68c2ac84ea22d7'/>
<id>urn:sha1:bc891a67802c837d77726f618e68c2ac84ea22d7</id>
<content type='text'>
Reverts part of the previous commit: the explicit refresh button and
fetch-on-launch stay, but the user still wants the background 5am alarm so
articles are fresh before the app is opened, not just the periodic hourly
WorkManager job.
</content>
</entry>
<entry>
<title>Replace background auto-refresh with fetch-on-launch + explicit button</title>
<updated>2026-08-22T22:35:54+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-08-22T22:35:54+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/feedreader.git/commit/?id=796b6dffd920ec9228430dcca1a1f4ec855ebfc7'/>
<id>urn:sha1:796b6dffd920ec9228430dcca1a1f4ec855ebfc7</id>
<content type='text'>
Periodic hourly WorkManager fetches and the 5am exact-alarm morning fetch
both refreshed without any visible trigger, which surprised the user more
than it helped. Refreshing now only ever happens on a fresh app launch
(MainActivity.onCreate) or an explicit tap of the new refresh button on the
Unread/Older screens' top bar (wired to the fetch-status state that was
already being computed there but never rendered). Drops MorningFetchReceiver
entirely along with its exact-alarm permission and boot receiver.
</content>
</entry>
<entry>
<title>Add a 5am morning-fetch alarm so articles are fresh before you open the app</title>
<updated>2026-08-22T01:08:22+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-08-22T01:08:22+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/feedreader.git/commit/?id=7f324168464934d711953dc748fbc7c65eb88f4b'/>
<id>urn:sha1:7f324168464934d711953dc748fbc7c65eb88f4b</id>
<content type='text'>
FeedFetchWorker's periodic 60-minute WorkManager job can (and does)
get silently deferred for hours by Doze on an idle overnight device.
An exact alarm (setExactAndAllowWhileIdle) wakes the device at a fixed
5am to run a fetch, without requesting the much broader "ignore
battery optimizations" exemption -- battery optimization stays on
otherwise, just this one daily wake-up.

Android 12+ requires a one-time user grant for exact alarms (no in-app
permission dialog); MainActivity requests it via Settings if not
already granted, tracked across rotation via savedInstanceState so a
config change doesn't re-trigger the Settings screen. The alarm also
gets re-armed on BOOT_COMPLETED, since AlarmManager alarms don't
survive a device restart on their own.
</content>
</entry>
<entry>
<title>Fix reader rotation bug: replace-nav instead of a swipe-queue snapshot</title>
<updated>2026-08-22T01:02:31+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-08-22T01:02:31+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/feedreader.git/commit/?id=5c7f9d0d306b66740642cf4111f2a9ee26897ac5'/>
<id>urn:sha1:5c7f9d0d306b66740642cf4111f2a9ee26897ac5</id>
<content type='text'>
The reader used to snapshot the article-id order of its list ONCE at
entry and page a HorizontalPager across that fixed queue. Two real
bugs traced back to one root cause: the snapshot lived in plain
remember state that did NOT survive an orientation change, while the
pager's own page index did -- rotating mid-session left the index
pointing into a freshly re-fetched (and by then shorter, since paging
past an article marks it read and drops it from the Unread query)
list, landing on a different article than the one actually being
read. Back was confusing for the same reason: it always returned to
wherever the ORIGINAL initialArticleId pointed, not wherever the pager
had since scrolled to.

Fixed by not having a separate queue/index at all: each Next/Previous
now replaces this screen's own back-stack entry with a fresh
navigation to the neighboring article's id (MainActivity wires
onNavigateToArticle with the same popUpTo-replace pattern
LinkReaderScreen already uses for its own link chain). "Which article
is showing" is now the nav route argument itself -- Android's own
Saved State mechanism, not custom state that can drift out of sync
with it -- and the back stack is always exactly [list, one reader
entry] no matter how many articles were paged through.
</content>
</entry>
<entry>
<title>Collapsible folders, long-press link menu, YouTube thumbnails, strip more Ars sequel chrome</title>
<updated>2026-07-24T03:28:31+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-07-24T03:28:31+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/feedreader.git/commit/?id=72f76cc41ae406e00a4dd9d448e2b552f9a6baaa'/>
<id>urn:sha1:72f76cc41ae406e00a4dd9d448e2b552f9a6baaa</id>
<content type='text'>
- Feed list: folders now collapse (start collapsed, with a per-folder unread
  count so collapsing doesn't hide "is there anything new")
- Long-pressing a link inside an article shows an action menu (open in
  browser, share, save to Stash, save for later) instead of WebView's
  default text-selection popup -- confirmed live end to end, including the
  Stash/save-for-later paths creating a real standalone article
- New shared ensureStandaloneArticle() helper (data/ArticleSaving.kt),
  factored out of ShareReceiverActivity, now also used by the long-press menu
- YouTube iframe embeds become tap-to-open thumbnails (JS is off in the
  reader WebView, so the real player never worked anyway); tapping one goes
  straight to the YouTube app/browser instead of our own article-extraction
  pipeline
- Fixed back navigation through a chain of links-within-links: each hop was
  stacking a new backstack entry, so back stepped through the chain one link
  at a time instead of returning straight to the source article. Confirmed
  live: A -&gt; link -&gt; B -&gt; link -&gt; C, one back-press now goes straight to A
- Extended the &lt;header&gt; chrome-stripping from last round: also strip
  &lt;footer&gt;, an author bio card, and a "Most Read" trending-stories widget --
  confirmed live one of the latter showed up as a giant unrelated photo plus
  an empty numbered list (its lazy-loaded items never populate in this
  hidden, off-screen extraction WebView)

Co-Authored-By: Claude Sonnet 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01EZ7ikw2ukGJFTHE3bJS7zL
</content>
</entry>
<entry>
<title>Fix extraction regression on kottke; add Older view, mark-read-from-here</title>
<updated>2026-07-22T20:37:41+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-07-22T20:37:41+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/feedreader.git/commit/?id=34b663c6e75a2c360de154418dafb8f616b546a1'/>
<id>urn:sha1:34b663c6e75a2c360de154418dafb8f616b546a1</id>
<content type='text'>
- ReaderExtractor: the un-hide-before-extracting fix (for soft paywalls
  like West Hawaii Today) was unconditional -- it also un-hid normal,
  legitimately-collapsed chrome (kottke's footer/membership notice), which
  Readability then folded into the result alongside the real article.
  Reported live with a screenshot showing the extracted content wrapped in
  site footer boilerplate. Now tries the normal (hidden-respecting) pass
  first and only falls back to the un-hide pass if that came back thin
  (&lt;500 chars), keeping whichever pass actually found more text. Verified
  live: the same kottke article now stays clean and stable (no delayed
  switch to the footer version).

- New Unread/Older split: the default Unread view is now clamped to the
  last 72 hours (RECENT_WINDOW_MS); the rest of the unread backlog moved
  to a new "Older" view (new FeedListScreen row, ArticleDao.observeUnread
  now takes a cutoff, observeOlderUnread/observeUnreadCount/
  observeOlderUnreadCount added). Undated articles are kept in the main
  Unread view rather than silently dropped from both. Verified live: 150
  recent / 1172 older, summing to the prior combined total.

- "Mark read from here": long-press an article row to mark it and
  everything older in that same filtered list as read in one batch
  (re-added ArticleDao.markReadBatch, this time as an explicit,
  confirmed action -- not the implicit on-scroll version that was
  reverted earlier). Verified live: confirmed a 147-article batch: the
  tapped article and older were marked read, newer ones above it were not.

- BACKLOG.md: added a YouTube-embeds note (JS is off in the reader
  WebView, so embeds currently show broken -- rough shape is a tap-to-play
  thumbnail rather than turning JS on for third-party content).
</content>
</entry>
<entry>
<title>Fix accidental page-swipe while scrolling; bring linked-article reader to parity</title>
<updated>2026-07-22T18:56:47+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-07-22T18:56:47+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/feedreader.git/commit/?id=bedf8a47a8b0601f4e3c253e4d2a7325c6ccbfc6'/>
<id>urn:sha1:bedf8a47a8b0601f4e3c253e4d2a7325c6ccbfc6</id>
<content type='text'>
- ReaderScreen: HorizontalPager's full-surface drag detection was treating
  ordinary vertical scrolling inside the embedded WebView (a native View,
  which doesn't arbitrate with Compose's gesture system the way a
  pure-Compose scrollable child would) as an attempted horizontal page-turn
  on anything less than a perfectly vertical swipe -- i.e. most real thumb
  scrolling. Tried gating it to edge-zone-only drags first, but that
  collides with Android's own edge-swipe-back gesture on gesture-nav
  devices (reproduced live: an edge swipe triggered system back, not the
  pager). Landed on disabling the Pager's built-in gesture and driving it
  from a custom detector that works anywhere on screen but only commits to
  a page turn once a drag is clearly horizontal-dominant (&gt;2x the vertical
  movement) -- ordinary scrolling, even sloppy/diagonal, is left completely
  unconsumed for the WebView. Verified live: a deliberately diagonal
  mid-screen scroll now stays on the same article; a decisive horizontal
  swipe still changes pages.

- LinkReaderScreen: was missing the bookmark/Stash/reader-toggle/menu
  actions entirely -- reported after tapping a link left "all my icons...
  gone." Brought to parity with the main article reader: save locally and
  save to Stash now lazily create a standalone ArticleEntity (feedId=null,
  id = hash of the URL, same scheme ShareReceiverActivity uses) on first
  use; reader-mode toggle falls back to the live page (JS on) instead of
  always forcing extraction; same login/find-another-source menu. Verified
  live: all 5 icons present once extraction finishes, bookmarking a linked
  article correctly creates a standalone DB row.
</content>
</entry>
<entry>
<title>Add site login and alternate-source search for paywalled articles</title>
<updated>2026-07-22T18:35:06+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-07-22T18:35:06+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/feedreader.git/commit/?id=e804e7a77a6cac048d5f6edeac0a339ca89e4d40'/>
<id>urn:sha1:e804e7a77a6cac048d5f6edeac0a339ca89e4d40</id>
<content type='text'>
Deliberately not a paywall-bypass mechanism -- that space (archive.today-style
proxies, "Bypass Paywalls" extensions) is legally grey, adversarial toward
publishers, and constantly breaking. Instead:

- BrowserScreen: a real interactive mini-browser (JS + DOM storage on,
  ordinary navigation, no Readability interception) for the two cases where
  the user needs to actually USE a page rather than read it. Reached from
  a new overflow menu on the article reader:
  - "Log in to this site" opens the article's own URL there so the user can
    sign into a site they already subscribe to. Needs no extra plumbing:
    Android's CookieManager is one process-wide store shared by every WebView
    in the app, so a session cookie set here is picked up automatically by
    the reader's (JS-off) extraction WebView on the next visit to that
    domain -- same as a normal browser remembering you're signed in.
  - "Find another source" runs a web search on the article's title in the
    same browser, so the user can pick a different, non-paywalled outlet
    covering the same story themselves -- plain search/discovery, not
    circumvention.

Verified live: search opened a real, interactive Google results page with
working title-bar sync; back navigation (via BackHandler, goBack() when the
WebView has history, else pop the screen) returned cleanly to the article.
</content>
</entry>
<entry>
<title>Split save/Stash actions, follow links in-app via reader pipeline, tune scroll, fix title entities</title>
<updated>2026-07-22T18:26:14+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-07-22T18:26:14+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/feedreader.git/commit/?id=91edd1f5f29a3336fa9e3bd1762c70ea74e3e48d'/>
<id>urn:sha1:91edd1f5f29a3336fa9e3bd1762c70ea74e3e48d</id>
<content type='text'>
- ReaderScreen: bookmark icon is now a purely local save (just flips isSaved,
  no network) -- Stash archiving is a separate explicit action (new cloud
  icon), since it's a heavier networked action the user should trigger on
  purpose rather than have bundled into "save." Verified via direct DB
  queries and (once I stopped misreading small screenshots) the icon
  updates immediately in-session for both actions.
- New ReaderWebView.kt: shared WebView factory for the dark reader palette
  and link handling, used by both ReaderScreen and the new LinkReaderScreen.
  Links tapped inside an article's WebView now open in a lightweight
  in-app reader (extracted via the same Readability pipeline, no DB
  persistence) instead of falling through to the system browser --
  recursive, so a chain of links stays in-app. Verified live: tapping a
  link mid-article navigated to a real extracted page with a working back
  stack.
- WebView scroll tuning: overscroll glow and scrollbars off, on both the
  article reader and the new link reader -- aimed at the reported "still a
  little choppy" scrolling on top of the previous reload-on-recompose fix.
- FeedParser: some feeds (kottke.org among them) double-encode titles --
  the XML parser resolves outer XML escaping, but leftover HTML entities
  like "&amp;#8220;" were showing up literally in the title text. Titles are
  now run through a second HTML-entity decode pass. Verified: a kottke
  title that previously showed a literal &amp;#8220; now renders the actual
  curly quote.

All changes 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>
</feed>
