diff options
Diffstat (limited to 'CLAUDE.md')
| -rw-r--r-- | CLAUDE.md | 68 |
1 files changed, 59 insertions, 9 deletions
@@ -147,7 +147,25 @@ prototyped against real episodes before any Android code, per the design plan's that, and unlike a download it never touches the network. No per-show toggle, same as intro/outro -- the confirmation threshold is the safety mechanism. -Neither has been verified on a device yet -- see the known-gaps list below. +**Both halves have now run on a device**, and doing so found a blocking defect the unit tests +could never have caught. `show_fingerprints.pendingClipPcm` held a whole decoded head/tail +window -- 240 seconds at 8kHz is 3.84MB as 16-bit PCM -- and Android reads every Room row +through a CursorWindow, which refuses a row over 2MB *on the way out*. The insert succeeded; +the next read threw `SQLiteBlobTooBigException`. Intro/outro detection therefore crashed the +app the moment a show's second episode was fingerprinted, and could never have worked as +shipped. Fixed in migration 9 -> 10 by storing only `pendingEpisodeId` and re-decoding on +demand -- the pattern `midrollPendingEpisodeId` already used for the same reason. + +`StoredClip` now owns that limit, `MidrollUpdater` caps a candidate segment at 60 seconds +before persisting it (a long sponsor block would otherwise hit the same wall), and +`StoredClipSizeTest` pins both against the limit so the class of bug cannot come back quietly. + +What is proven on hardware: the MediaCodec decode loop runs, `FingerprintWorker` completes +SUCCESS per download, and the re-decode path is exercised. What is **not** yet proven is that +detection actually confirms anything -- three downloaded Up First episodes left both ends still +"Nothing found yet". That may be correct (the confirmation threshold is deliberately strict) or +it may mean the 8-second reference window is too wide for a ~6-second sting; the desktop +prototype is the thing to compare against, and nobody has. **ID3 `CHAP` frames are dropped, not deferred** — decided 2026-09-26 by measuring Peter's actual 61 subscriptions rather than guessing. Every feed's newest episode was fetched far enough to read @@ -176,14 +194,19 @@ by default and correct; it is simply waiting for a publisher who marks breaks. - **Auto-downloads are not retried.** A rule's download that fails is left FAILED rather than requeued: retrying forever against a 404 would burn the battery, and the failed row is visible in the episode list. Retry is the manual Download button. -- **Android Auto is unverified in a real car.** The session protocol is proven by - `MediaBrowseTest`; whether Auto's own compatibility checks pass on a head unit is untested, - and a sideloaded media app may need Auto's developer mode enabled. -- **Neither half of Tier 3 fingerprinting has run on a device.** The decode/decimate/correlate - pipeline is proven against synthetic fixtures and, separately, against real downloaded episodes - in a throwaway desktop prototype -- never against `MediaCodecClipSource`'s actual - MediaExtractor/MediaCodec decode loop on real hardware, and the `MigrationTest` 7→8 case has - only run on the JVM-backed `MigrationTestHelper`, not an emulator or device. +- **Tier 3 has never confirmed a real intro or outro.** The pipeline runs on hardware and the + worker succeeds, but three consecutive Up First downloads produced no candidate at either + end. Untested hypothesis: `FingerprintUpdater.DEFAULT_REFERENCE_SECONDS` is 8 and the + prototype measured Up First's sting at about 6, so every 8-second window compared also + contains two seconds of different speech. Do not change that constant without re-running the + desktop prototype -- it is tuned, and the confirmation threshold is what keeps a rotating ad + from being learned as an intro. + +- **Android Auto is verified in a real car**, as of 2026-09-28. It appears, and connects. The + notification Peter saw on first connect was Android Auto's own new-media-app announcement, + not Listen: the only notification this app can raise is the Media3 transport control, which + a foreground media service is required to post and which is on a low-importance channel. + What is still untested in a car is browsing and voice search. ## Domain notes worth keeping @@ -272,6 +295,33 @@ by default and correct; it is simply waiting for a publisher who marks breaks. come from. Only then: a title that was shared is the one that was meant, and the probe costs a network round trip. +- **Per-show controls live behind the show's overflow menu**, in a bottom sheet, not stacked + above the episode list where they used to be -- reaching the episodes meant scrolling past + drip cadence and two paragraphs of explanation. The explanation went too, at Peter's request: + this is a single-user app and he wrote the requirements, so labels are one line and nothing + is taught. Unsubscribe moved into the same menu, off the top bar where it sat one tap from + Refresh. + +- **Bedtime is a flag on a show, not a category system.** A bedtime show is absent from the + ordinary library entirely and listed only in bedtime mode, which the moon icon toggles. That + mode is deliberately *not* persisted -- waking to an app still in bedtime mode, library + apparently empty, is the failure being avoided; a restart is morning. Entering it also arms + the sleep timer, and `BedtimePolicy` owns the two cases that make that safe: it will not + overwrite a timer set by hand, and leaving only cancels a timer bedtime itself armed. + +- **A show with nothing waiting is dimmed, not hidden.** Same condition the library sort + already used -- nothing unplayed *and* unqueued. `hasWaiting` comes from a COUNT, not from + the sort key: an episode with no publish date ranks at 0, which is right for ordering and + would wrongly grey out a show that has one sitting unheard. The `lastError` line keeps its + error colour through the dimming, because a feed failing to refresh looks exactly like a feed + with nothing new. + +- **What the skip detection has worked out is visible**, per show in the settings sheet and per + episode on its detail screen. `SkipSummary` owns the wording and is tested, because the + distinction that matters -- confirmed-and-acting versus still-gathering -- is the whole point, + and "not checked yet" is a third state again (midroll scanning runs nightly, so a fresh + download legitimately has nothing to say). + - **Played episodes are hidden by default**, globally, via a DataStore preference the show screen toggles. Android Auto always hides them regardless: a car list is scrolled blind at a traffic light, and something already heard is pure noise there. |
