diff options
| author | Peter Stone <thepeterstone@gmail.com> | 2026-09-25 17:09:13 +0000 |
|---|---|---|
| committer | Peter Stone <thepeterstone@gmail.com> | 2026-09-25 17:09:13 +0000 |
| commit | 593ee7740de24aae22ddec1dc5a2e083fff06bd3 (patch) | |
| tree | a7cb89b34e2ff5f8ad32073a1078f70468daf0c0 /CLAUDE.md | |
| parent | f04a01c672c01f4913d4b9bbaf2fc4febbaec08c (diff) | |
Slow drip: work through an archive a few episodes at a time
The second of Phase 2's three tracks. A show can be set to release its back
catalogue on a cadence -- one a day, oldest first, by default -- and while drip
is on the show lists only what has been released rather than the raw feed. A 600
episode archive then reads as a show you are keeping up with instead of a wall.
A release always enqueues, and deliberately does not go through the rule engine.
Drip decides when you see an episode and the rules decide what happens to one;
running a released episode through the rules as well would let a rule silently
cancel a release, which reads as the drip being broken.
Three pieces of arithmetic worth keeping, all in DripSchedule and all tested:
The clock advances by whole intervals rather than to now. Moving it to now
discards the part-interval already elapsed, and a six-hourly worker against a
daily drip loses hours every day until "one a day" means one every day and a
bit.
A long absence is capped at ten and the excess is forgiven rather than carried.
A month away owes thirty episodes; handing those over empties the archive into
the queue, which is the thing the feature exists to avoid. Carrying the debt
instead would just pay it out as a capped burst on each of the next three runs.
Switching drip on settles what it already owes: everything played or already
queued is released up front, because hiding episodes you have heard makes a show
followed for a year look like it has three, and hiding one mid-queue strands it
where nothing can reach it. Re-enabling starts the cadence from now rather than
paying off a debt accrued while it was off.
Visibility is a query parameter, not stored state, so switching drip off
restores the whole catalogue without touching a row. Android Auto browses the
same set. Feed reconciliation deliberately still sees everything -- matching
against the visible set alone would read a held-back episode as missing and
re-insert it on every refresh.
RFC 5005 archive paging is what gives a drip anything to drip: FeedArchiveWalker
follows prev-archive pages, resolving relative hrefs against the page that
carried them, and stops on a loop, a page cap, or a fetch failure -- keeping what
it collected and reporting why, because half an archive is useful and a silent
half surfaces months later as "why did this show run out?".
Two gaps found while wiring it up, both fixed here: the subscription screen had
no snackbar at all, so a refresh started from it reported nothing and an archive
fetch would have been silent; and both toggles were bare Switches with no
accessible name, which TalkBack cannot announce and the UI checker cannot tap.
Schema v2 -> v3. The four non-null drip columns carry SQL defaults because
SQLite cannot add a NOT NULL column without one, declared on the entity too so
the migration and the exported schema describe the same table. Verified twice:
the migration test now covers 1 -> 3 straight through, and a real v2 database
seeded with a subscription, an episode at position 424242 and a rule was
upgraded in place on a device with all three surviving and drip defaulting off.
OPML moved to its own view model rather than growing ListenViewModel past its
function limit again -- it shares nothing with the library, is used by one
screen, and the settings screen now depends on it alone.
The exported-schema trap from the last commit happened again, after I had
written the warning down. Step 9 of the pre-commit hook now refuses a commit
that modifies an already-committed schema JSON, since a note was demonstrably
not enough.
No permission changes. 333 unit tests green, detekt and lint clean, coverage
63.63 -> 65.30.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B
Diffstat (limited to 'CLAUDE.md')
| -rw-r--r-- | CLAUDE.md | 34 |
1 files changed, 30 insertions, 4 deletions
@@ -94,6 +94,9 @@ claims three separate times across these repos. 6. **Schema migrations** — if `AppDatabase.kt`'s `version` changed, a migration test must be touched in the same diff. Room tracks by version; a wrong migration corrupts a real, non-reproducible library. +7. **Exported schemas are append-only** — a commit that *modifies* an existing + `app/schemas/**/*.json` is refused. Adding a new one is how a version bump is supposed to + look; changing an old one means the record of what is on the phone has been overwritten. ## State @@ -108,8 +111,10 @@ library and voice search. 207 unit tests plus instrumented tests covering the me title, duration, age and season, and actions to queue top/bottom, mark played, auto-download, or pin a speed. `RuleEngine` decides, `RuleApplier` carries it out, `RuleSummary` describes a rule in one line, and every claimed episode records which rule fired and why. -- **Track 2, slow-drip archives — not started.** Includes walking RFC 5005 `prev-archive` pages; - `ParsedFeed.previousArchiveUrl` is already parsed and tested and nothing consumes it. +- **Track 2, slow-drip archives — done.** Per-show cadence (`DripSchedule` decides what is owed, + `DripReleaser` pays it out), a released-set the show and Android Auto both list, and + `FeedArchiveWalker` following RFC 5005 `prev-archive` pages to fetch a back catalogue. A drip + release always enqueues and deliberately bypasses the rule engine. - **Track 3, simple skip — not started.** Manual per-show offsets (`skipIntroMs`/`skipOutroMs` exist on `SubscriptionEntity` and nothing reads them) and Podcasting 2.0 chapter JSON via `EpisodeEntity.chaptersUrl`, which the parser already captures. @@ -194,8 +199,12 @@ library and voice search. 207 unit tests plus instrumented tests covering the me for whatever version is declared, so editing `EpisodeEntity` while the database still says `version = 1` silently overwrites `schemas/1.json` with the new shape. That file is the only record of what is on the phone, and once it matches the new code the migration test validates - happily against a schema that never existed. It happened building the rules table; the fix was - `git checkout -- app/schemas/`. + happily against a schema that never existed. + + This happened twice in one session -- once building the rules table, once building drip -- + the second time *after* writing this warning down. Step 9 of the pre-commit hook now refuses a + commit that modifies an already-committed schema JSON, because a note was demonstrably not + enough. If it fires: bump the version first, then `git checkout -- app/schemas/`. - **The migration test needs two pieces of plumbing that fail obscurely.** `MigrationTestHelper` reads the exported schemas from the instrumented test APK's assets, so `schemas/` is wired into @@ -215,6 +224,23 @@ library and voice search. 207 unit tests plus instrumented tests covering the me episode screen is absent from the dump and plainly visible in a screenshot. Treat a missing line in a dump as inconclusive, not as absence. +- **A bare `Switch` has no name.** It announces nothing to TalkBack and `android-ui tap` cannot + find it, so both toggles carry an explicit `contentDescription` -- and the drip one says "Turn + slow drip on or off" rather than "Slow drip", because a description identical to the heading + beside it makes the tap ambiguous. + +- **`connectedDebugAndroidTest` can wipe the app's data.** A run cleared `listen.db` on the + emulator mid-session. It is the test harness's install path, not a migration: a real v2 + database seeded with a subscription, an episode at position 424242 and a rule was upgraded in + place by launching the v3 build, and all three survived with drip defaulting to off. Do not + read an empty database after an instrumented run as a migration failure -- but do check. + +- **The drip released-set is a query parameter, not stored state.** `observeVisible`/`getVisible` + take `dripEnabled` and hide unreleased episodes only when it is true, so switching drip off + restores the whole catalogue without touching a row, and switching it back on resumes where it + left off. `getForSubscription` deliberately still returns everything: feed reconciliation must + see held-back episodes or it re-inserts them on every refresh. + - **XML comments cannot contain `--`.** The house prose style uses it constantly and it makes the manifest unparseable, with a merge error that doesn't name the line. |
