summaryrefslogtreecommitdiff
path: root/docs/superpowers
AgeCommit message (Collapse)Author
2026-10-27Plan: the labels are wired end to end, and when they are not appliedPeter Stone
Verified on the live server: a mark vouching for a real detected break reaches the signature that break produced and sets confirmed on it. The probe and the flag it set were removed afterwards; that corpus holds the listener's statements, not test data. Also records the property that cost two runs to notice -- a label is only actionable while its episode is still mirrored. They outlive the audio deliberately, so the server holds plenty naming episodes long rotated out, and those are simply not applied. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B
2026-10-27Use the labels: seed signatures from them, and take them backPeter Stone
The labels have been arriving and sitting there. Now the shadow run acts on all three kinds. A report is advertising pointed at where detection found nothing, so it becomes a signature in its own right -- that being the whole gap the library exists to close, since the scan accounts for about 38% of what the feeds imply is there. A mark saying a break is right adds no audio, because the break is harvested anyway. What it adds is that a person vouched for it. A mark saying a break is wrong does two things. It stops that audio being harvested, and it takes back any signature already held that matches it. That second half is the one that matters: a signature built from programme would cut the programme in every episode carrying it, until somebody noticed. So his "no" removes it outright rather than lowering a score. Being pointed at outranks partners and occasions both. Those are ways of inferring what a stretch of audio is; he was there. A confirmed signature is trusted at once and stays confirmed -- he does not un-hear it next week. A report whose end was never corrected is trimmed to its first fifteen seconds. An uncorrected end is wherever the 25-second jump landed rather than where the advertising stopped, so its tail may be programme -- and one ad unit is already far more audio than a landmark fingerprint needs to be distinctive. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B
2026-10-27Start testing the Compose screensPeter Stone
The dependencies for this were already in the build -- ui-test-junit4 on androidTest, ui-test-manifest on debug -- and had never been used. So the spike was one test, and it passed first run. Eighteen now. What they cover is the data-driven composables, chosen because a regression in them looks completely normal: a readout that quietly stops printing one of its three numbers, a control that stops reporting whether it will play or pause, a bar that stops naming what is playing while a screen shows something else. The episode transport: the ad button is offered only while something is playing, because with nothing playing there is no advertising to be in the middle of and an enabled button would record a claim about a position nobody is at. The primary control says what pressing it will do. The skip buttons move by the amount they advertise. The skip readout: all three numbers on one line, the status when there is nothing to cut -- "nothing found" and "not checked yet" being different answers is the mistake the old readout made -- and a legend that names only what is actually on the track. The now-playing bar: names the episode and the show, opens what is playing when tapped, and reports play against pause. Screens that need view models are still verified by eye. This is the half that did not need them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B
2026-10-27Write down a refused token instead of only remembering itPeter Stone
401 and 403 are permanent by decision -- a refused token cannot fix itself -- and the notice saying so was held in memory. So it vanished with the process and only came back once the next refresh had gone and been refused all over again, which for 61 feeds is hours of the app quietly downloading from publishers with nothing on screen to say why. It now lives in the preferences file and is there at launch. Saving different credentials clears it at once rather than waiting for a request to prove it, because that is the one act that answers the complaint. Saving the same ones again proves nothing and leaves the notice standing. Behind a small interface so SkipServer does not have to know whether it is writing to a preferences file or to nothing: every test wants it in memory, without a Context to hang a DataStore off. The stored one takes the reading and the writing as two narrow seams rather than a Preferences. Passing Preferences dragged a Context in and made it the one piece of the server client that could only be exercised on a device -- the coverage gate caught that, which is what it is for. What it actually decides, when *not* to write, is now plain Kotlin with five tests on it: clearing runs after every successful reply, so two hundred successes in a sweep must write nothing. Verified on a device: a refusal written by one Preferences instance is read back by another, and four cases around saving credentials behave as described. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B
2026-10-25Plan: step 4 of the UI redesign is donePeter Stone
Records what moved and why, including the two controls that left Settings -- speed because the episode screen already had it, the sleep timer because it is an action rather than a setting. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B
2026-10-24Plan: mark the library built and the shadow run gatheringPeter Stone
Four and five are done and six is deliberately not. Everything six needs exists; what is missing is weeks of real batches, and building it now would throw away the only reason the shadow run exists. Records what to watch for in the log, and the one decision the tests forced: a copy that arrives with partners is trusted at once, because holding it back would mean only ever proposing advertising detection had already found twice. A cue gets no shortcut, which is the independence intro and outro are waiting for. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B
2026-10-24Send the listener's labels to the server and keep themPeter Stone
The phone has had two kinds of label and nowhere to put them: a mark, which is a judgement on a break detection found, and a report, which is the notification button pointing at advertising it missed. They catch opposite errors, and the signature library needs both. POST /api/labels now takes them and state.json keeps them. Episodes are named by feed and guid rather than by either side's own id -- the phone never sees the server's, and the server's belongs to a mirror that forgets an episode after eight. The labels sit beside the shows rather than on them for the same reason: the corpus has to outlive the audio it was given on. A report's end is sent as guessed or measured. It is only measured once the listener came back and trimmed it; otherwise it is wherever the jump landed, and a signature cut to that would carry programme. The phone re-sends everything each sweep and keeps no record of what arrived. The server replaces rather than accumulating, so a re-send is free, there is no sync state to get wrong, and a label lost to a crash comes back on the next refresh. Gated on audioFromServer, like every other server re-query: a label names a position in a particular file, and dynamic ad insertion means the publisher's copy is a different one. Accepting a label writes the state file immediately. Everything else is written at the end of a poll cycle, which for 61 feeds is hours, and a tap made once should not wait that long to become durable. Deployed and checked against the live server: a label is accepted, a re-send leaves one, an unauthenticated POST gets 401, and the label is on disk within seconds. The probe label used for that check was removed afterwards. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B
2026-10-24Spike: landmark hashing passes the gate for the signature libraryPeter Stone
The plan made this the gate: a linear scan of N stored signatures against each episode does not survive, so the library is only possible if an index can shortlist candidates cheaply. It can. Built as a throwaway harness against the live corpus. The episodes the plan named had rotated out of the server's 8-episode window, so the test set was rebuilt from current audio and came out stronger than the one it replaces -- 6 true repeats and 16 non-repeats, against 2 and 3. Six repeats scored 813-1564 votes; sixteen non-repeats scored 27-94. The narrowest separation is 8.6x, where the correlator it replaces manages 4.9x on the same audio. Nothing sits in the gap. Offsets land within 1.3s and every proposal is cheap to confirm by correlating against a short window at the claimed position, so the shape is: index proposes, correlator disposes. That is not a formality. In one episode the advertisement occurs twice and the index picked the weaker instance, which only a confirming pass would catch. Measured: 9.8ms to hash a signature when it is stored, 196ms to look one up against 23 episodes, ~8s per episode to index, 353MB for 23. Five hundred signatures cost 98 seconds where linear correlation costs about 63 minutes. The harness is not committed -- it read absolute server paths and had no tests, and its output was an answer rather than code to keep. The parameters it found are written into the plan so it can be rebuilt test-first, along with the one weakness worth knowing: the hashes collide heavily, and that density is what makes a lookup cost milliseconds rather than microseconds. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B
2026-10-20Jump 25 seconds, the forward button's amount, not 45Peter Stone
The "this is an ad" press jumped 45 seconds, a number argued from ad-unit arithmetic rather than from the controls that already exist. Peter: "the jump should stay 25 seconds. Don't go inventing new timing." Skipping forward is already what you do to an advertisement, so the button goes the same distance and additionally says why. One number to know, and a second press goes the same distance again. The amount had three definitions already -- the player's increments, the episode screen's transport buttons, and the notification caption's hardcoded "Forward 25 seconds" -- and this nearly became a fourth with a different value. They now all read one, and the captions are derived from it so a caption cannot name a number its button does not do. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B
2026-10-20Add a "this is an ad" button to the notificationPeter Stone
The notification carried four buttons doing two jobs: Media3's default previous/next (which PodcastPlayer redefines as seek) plus our own back-15/forward-25 pair. Media button preferences with explicit slots let the custom pair claim the two seek slots, so the duplicates go and the freed space carries "this is an ad". No player command is removed to achieve it, which is the route that once silently killed the headphone skip button. One press means four things, told apart by where the playhead lands rather than by guessing: press claims advertising from here and jumps past it, pressing again extends the same claim, coming back inside the jump moves its end to where you landed, and coming back past the press withdraws it. Outside a 20-second window a seek is ordinary navigation and says nothing. The window is stored with the label so changing it later cannot reinterpret old data. Every backwards seek feeds the gesture, whatever caused it -- notification, headphone remote, in-app bar, dragged slider -- because you correct with whatever is to hand. This is the opposite evidence to the tap-a-break popup. That one can only correct breaks detection already found, and detection accounts for about 38% of the advertising the feeds imply. Verified on the emulator: the notification now shows exactly back-15, pause, forward-25 and the new button; the lock screen shows it too and pressing it there records a claim without unlocking; all four gesture arms behave as specified; and the media transport keys still seek by 25s and 15s. Android Auto still needs the car. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B
2026-10-20Let the listener validate or invalidate a detected break from a popupPeter Stone
Keeps partners on episode_midrolls (schema 15), stores verdicts in their own break_verdicts table so a server refresh cannot wipe them, and drops invalidated breaks from skipping on the next tick. Verdicts are local only. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B
2026-10-12Plan the break-kinds slice and the furniture toggleClaude
Eight tasks covering the spec decisions end to end: the server decides a kind for every break it publishes, the phone stores it, advertising is always skipped and a show own intro, cue and credits follow one preference, and the readout names which is which. The cue prior and the furniture library are deliberately not planned here. Each is independently shippable, and the cue signature thresholds should be chosen against kinds visible in real data rather than guessed at before any exist. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B
2026-10-08Record the approach-2 decision and the furniture toggleClaude
Approach 2 chosen, built in the phase order already proposed -- it is a superset of approach 1, so the ad-detection work still lands first and the library last. Advertising is always skipped; the show own intro, cue and credits obey a single preference defaulting to on, because that is what happens today. The toggle settles a bootstrapping question the design had left open. A furniture clip cannot be promoted until a second fetch occasion has passed, so until then there is nothing to label with. The zero-gap classification fills that gap: served length equal to declared means nothing was inserted, so every break is furniture and position decides which kind; otherwise a break is advertising unless a promoted clip says otherwise. Day one is correct on the 138 zero-gap episodes and identical to current behaviour everywhere else. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B
2026-10-08Design: separate a show's own audio from the inserted advertisingClaude
Three defects turn out to be one missing distinction. Ad detection finds a median 67% of the advertising the length gap implies, because it can only see what two simultaneously-mirrored episodes share. It reports a show's own intro, break cue and credits as advertisements on 111 of the 138 episodes that had nothing inserted into them. And intro/outro offsets stay withheld for want of independent evidence. Peter's observation about theme music around ad breaks is the clue, and probing it showed both what it gives and what it does not: the cue is real, already detected, and marks a break's entry but not its extent -- scanning to r=0.6 finds one isolated hit per slot in ad-laden and ad-free copies alike, so there is no closing cue to bracket against. Writing this up changed the recommendation. The furniture library is only needed for the intro/outro problem; the mislabelling falls out exactly and for free from served length versus declared, and cues are identifiable from their signature within a single batch. So the proposal is now the cheap approach first, with the library kept for later and only if the withheld offsets are wanted. Nothing here is built. Review document, not an implementation. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B