summaryrefslogtreecommitdiff
path: root/CLAUDE.md
diff options
context:
space:
mode:
Diffstat (limited to 'CLAUDE.md')
-rw-r--r--CLAUDE.md68
1 files changed, 59 insertions, 9 deletions
diff --git a/CLAUDE.md b/CLAUDE.md
index a1a57d5..4e266ac 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -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.