| Age | Commit message (Collapse) | Author |
|
Some "soft" paywalls (West Hawaii Today's included) deliver the full
article in the initial HTML response to every visitor, then hide it with
CSS (inline or via stylesheet) pending a client-side subscription check --
the content itself isn't withheld by the server, only its visibility.
Readability has a built-in heuristic that skips anything hidden this way,
treating it as boilerplate, which is exactly what was discarding real,
already-delivered article text and leaving only the visible teaser/
subscribe prompt.
Before parsing, walk the live document (getComputedStyle needs an attached
node) and force display/visibility back on for anything hidden via
display:none or visibility:hidden, then clone and hand that off to
Readability as before.
Verified live against a real West Hawaii Today article: extractedContent
went from 588 characters (the RSS teaser) to 3458 (the full body).
|
|
- ReaderScreen: the direction-ratio gesture rewrite (using a generic,
non-oriented detectDragGestures to compute a custom dx/dy ratio) broke
scrolling entirely -- reported live as "the article view doesn't scroll
at all anymore." Root cause: detectDragGestures' touch-slop detection is
isotropic (claims the first bit of movement in ANY direction before it
knows which way the gesture is going), unlike detectHorizontalDragGestures
which is orientation-aware and simply never engages for a vertical-
dominant drag. Reverted to detectHorizontalDragGestures, keeping the
distance-based (not edge-anchored) commit threshold from the previous
fix. Verified live: a long article now scrolls properly on a diagonal
swipe, and a deliberate horizontal swipe still changes pages.
- AddFeedScreen: both the OPML import and single-URL add built a bare
FeedEntity(id, title, siteUrl) and upserted it (REPLACE) even when a
feed with that id already existed -- silently wiping readerModeDisabled
(and etag/lastModified/lastError) back to defaults on every re-import or
re-add. This is exactly what made a feed's reader-mode toggle appear not
to persist. Now preserves the existing row via .copy() when one exists.
Verified live: set kottke.org's readerModeDisabled=1, re-imported the
same OPML file (107 feeds), and confirmed the flag was still 1 afterward.
|
|
Explicitly asked to revert: scrolling the article list shouldn't mark
anything read on its own -- only opening an article (or the existing
manual actions) should. Removed the LazyListState/snapshotFlow tracking
in ArticleListScreen and the now-unused ArticleDao.markReadBatch.
Verified: unread count in the DB is unchanged after scrolling through
the list (1332 before and after).
|
|
- 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 (>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.
|
|
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.
|
|
scroll, fix title entities
- 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 "“" 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 “ now renders the actual
curly quote.
All changes verified live on the emulator against the real 107-feed dataset.
|
|
reader-mode opt-out
- 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->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.
|
|
Root-caused via emulator stress test (rapid swiping through many articles):
withTimeoutOrNull's cancellation, and LaunchedEffect disposal when a page is
swiped away, both invoke invokeOnCancellation on kotlinx.coroutines.DefaultExecutor
rather than the calling coroutine's Main dispatcher. The handler called
webView.stopLoading()/destroy() directly, crashing with "A WebView method was
called on thread ..." on essentially every session once enough thin-content
articles were swiped past mid-extraction -- the reported crash loop. Now hops
back to the main looper before touching the WebView, and guards against a
double destroy() race between the JS-callback and cancellation paths.
Also fixed a real (non-crashing) leak: the visible reader WebView in
ReaderScreen was never destroy()-ed when its page left the pager, via
AndroidView's onRelease.
Reproduced pre-fix in ~80 rapid swipes on an emulator; post-fix, 320+ swipes
with real 107-feed data ran clean with stable native heap.
|
|
- 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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EZ7ikw2ukGJFTHE3bJS7zL
|
|
- 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
<img> 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->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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EZ7ikw2ukGJFTHE3bJS7zL
|
|
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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EZ7ikw2ukGJFTHE3bJS7zL
|