diff options
Diffstat (limited to 'CLAUDE.md')
| -rw-r--r-- | CLAUDE.md | 51 |
1 files changed, 43 insertions, 8 deletions
@@ -168,14 +168,17 @@ clip files on disk, and unsubscribing is the only thing that clears them. columns the on-device detector used to, which is why playback needed no changes when detection moved. No migration, no schema change, nothing for a real library to go wrong over. -Two things the deletion left standing that a future session should look at rather than assume: - -- **The per-show detection readout has nothing feeding it.** `ShowSettingsViewModel.loadDetection` - reads `ShowFingerprintDao.get` and `MidrollDao.getCandidates`, and nothing has written a - *candidate* row since detection moved: the server writes per-episode ranges and offsets, not the - confirmation state the readout describes. So "2 of 3 episodes agree" can never appear, and a - phone that never ran the old version shows "nothing found yet" for every show while the server - is finding and publishing breaks. Predates this deletion; it is the same migration's doing. +Two things the deletion left standing. The first is fixed; the second is not: + +- **The per-show detection readout had nothing feeding it — fixed 2026-11-05.** It read + `ShowFingerprintDao.get` and `MidrollDao.getCandidates`, the confirmation state the *on-device* + detector kept, and nothing had written a candidate row since detection moved. So every show + reported "Nothing found yet" for ever, including shows the server was actively publishing breaks + for — seen on Peter's phone. It is counted off the episodes now (`detectedIntroMs`, + `detectedOutroMs`, and the midroll ranges `ServerSkips` writes), worded by `SkipSummary.showState`, + and it keeps the distinction that was the point: *not checked* and *checked, nothing found* are + different answers. `show_midrolls` is consequently an entity nothing reads or writes; the table + stays because dropping it needs a migration and nothing is gained by one. - **`RetentionSweeper`'s pending-episode guard protects nothing.** It reads `getAllPendingEpisodeIds` and `getAllMidrollPendingEpisodeIds` so retention cannot reclaim a file mid-detection. Nothing sets a pending basis any more, so both are always empty — harmless, but a @@ -553,6 +556,38 @@ by default and correct; it is simply waiting for a publisher who marks breaks. ## Things a future session should know +- **Nothing the listener reads is written for a machine, and the build enforces it** + (`PlainLanguageTest`, added 2026-11-05). A red banner on Peter's phone read "Playback failed: + ERROR_CODE_IO_BAD_HTTP_STATUS"; his words were "arcane and sketchy" and both halves were fair. + The same pass found a downloader printing `${it.message}`, an OPML import printing + `permission denied`, and `FeedFetcher` putting the literal word `UnknownHostException` under a + podcast's name in the library — **and two tests that asserted the machine text came through**, + which is how it survived review. Causes go to logcat; the screen gets a sentence, from + `PlaybackFailureText`, `FeedFailureText` or `FailureText`. Accuracy is the harder half of the + rule: "it will be retried" would read better than "try the download button again" and would be + a lie, because a failed download is deliberately not retried. + +- **Every outbound request goes through one client, and the build enforces that too** + (`OutboundRequestsTest`). One `OkHttpClient`, built in `AppGraph`, carrying + `SecureSchemeInterceptor`; everything else is handed it, Coil's artwork fetches included. The + same arrangement the server has in `safefetch/audit_test.go`, for the same reason: this app + fetches URLs chosen by podcast publishers. + +- **Playback plays the app's own copy, never the publisher's** (changed 2026-11-05). + `toMediaItem` falls back to the enclosure URL when there is no local file, and that fallback was + reachable from every play: pressing play on an undownloaded episode streamed it straight from + the publisher's CDN. That is the automatic fallback the download path had removed on purpose; it + is an unasked-for request to a third party; and because nothing in playback checks + `audioFromServer`, the breaks the server measured on *its* copy were being applied to a copy with + different advertising in different places. `PlaybackAdvancer.start` now returns `NeedsFetch` for + any episode with no file, so the app fetches first. + + **One caller still reaches the fallback: Android Auto.** `onAddMediaItems` resolves a browse id + straight to a media item without going through the advancer, and a car that silently plays + nothing is worse than a car that streams. Fixing it properly means fetching inside the session + callback. `OutboundRequestsTest` pins the fallback to exactly one line so it cannot spread back. + + - **The episode screen is the player; there is no separate player screen** (merged 2026-10-19). Opening an episode puts you in its transport at the stored position, and pressing play changes nothing about the layout. Playback state still has one owner, `PlayerViewModel`; the screen |
