Skip to content

Claude/commercial mute detector c7sooi - #4

Merged
socrtwo merged 43 commits into
mainfrom
claude/commercial-mute-detector-c7sooi
Oct 19, 2026
Merged

socrtwo merged 43 commits into
mainfrom
claude/commercial-mute-detector-c7sooi

Conversation

@socrtwo

@socrtwo socrtwo commented Sep 18, 2026 •

Copy link
Copy Markdown
Owner

What changed

Why

Verification

  • make lint type test passes
  • Replay fixtures added or updated for any detector change
  • Device profiles touched are listed below with hardware actually tested

claude and others added 30 commits September 12, 2026 21:27
The first real run picked a persistent news graphic rather than the bug
and there was no way to know. Camera setup is now a screen: the live
picture with the found TV in green and the logo box in yellow, a magnified
crop of that box, a live match score (PRESENT/absent), Watch 45 s for the
automatic finder, and drag-to-draw to override it. Save & start writes the
template and starts the service; AdHush pauses while the screen is open.

Hand-held at eight feet: the screen is re-found every frame and each edge
refined at full resolution (the coarse box was only good to 8 px, most of
a small logo), capture is 1280 x 720, frames come out upright, a blur
guard makes a smeared frame inert rather than "absent", and absence must
last 2.5 s. LogoFinder exposes the screen, a stability map and a template
for any box; LogoTemplate carries the screen's edge level for the guard.
Tests cover rotation, a wandering screen, a blurred frame and a hand-drawn
box.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BcfLmuUiHYY3PbRUskf3t4
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BcfLmuUiHYY3PbRUskf3t4
Offline speech recognition on the phone (Vosk small English model,
downloaded once) turns the microphone into words with media-time stamps.
Commercials are found by repetition: RepeatLearner indexes every 4-word
key with its time; a key heard again at least two minutes later starts a
run, agreeing words extend it (single slips allowed), runs of ten or more
words become scripts, and every later airing folds into the same one. It
runs every ten minutes and on the Learn scripts now button. Teach-mode
windows are saved as scripts directly.

Live matching aligns the last four words against the scripts the way the
audio matcher aligns chroma blocks, so a misheard word breaks keys but not
the alignment; three aligned hits confirm; only aligned hits extend the
8 s grace. Legal and sales boilerplate holds the duck on its own. The
detector carries three default weights so a known script mutes alone.

Tests: an hour of synthetic transcript with two spots airing at intervals
yields exactly those two scripts with the airings folded; live matching
with 15% word errors fires within 7 s, holds through, releases within
grace; boilerplate and teach-mode windows; the store round-trip; and the
engine ducking on a script and restoring after.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BcfLmuUiHYY3PbRUskf3t4
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BcfLmuUiHYY3PbRUskf3t4
…al UI

The service installed a default uncaught-exception handler that restored
the volume and killed the process, so any error anywhere made the app
disappear with nothing recorded. AdHushApp now installs an app-wide
handler that writes the stack trace to a rolling log file, restores the
set through the service's hook, and delegates to Android's handler so the
crash is reported. AppLog collects every status line and caught error;
the Log tab shows the tail and Share error log hands the file out through
FileProvider.

Two likely causes of the reported vanish are fixed in the speech path: the
40 MB model was loaded on the main thread inside the service start, which
can stall it long enough for Android to kill the app; it now loads on the
io executor and attaches when ready. SpeechSource.close waits for the
block being processed before closing the native recogniser.

The interface is rebuilt on Material 3: a top bar carrying the live
status, four tabs (Home, TV, Senses, Log), cards, labelled text fields
with a password toggle, switches, one primary action per card. Every view
id and every function is unchanged.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BcfLmuUiHYY3PbRUskf3t4
…aptions, colour setup, help

Fixes the 0.13 log: the camera's logo detector ducked the set again seconds
after every "Not an ad" because it called the bug gone without ever having
seen it. The bug must now be sighted before its absence counts; "Not an ad"
and "Show's back" reach every detector and open a one-minute quiet period;
the logo box is slid over a search window so a hand-held frame follows the
bug; a TV that runs off the edge of the picture is "whole TV not in view"
and never a duck. A blank TV address refuses to start instead of dialling
":10002"; the speech feed no longer throws after close.

Adds the Methods page with all six methods as switches (at least one must
be on), on-screen captions as the sixth method (on-device text recognition
of the caption band, fed to the transcript matcher), running indicators
(chips, dots, tinted buttons, green Running / red Stop), a Test mode card
on Home, a Help page and info buttons explaining the bug, scripts, ducking
and each method, a splash screen, and camera setup in colour with pinch
zoom and a whole-TV warning.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BcfLmuUiHYY3PbRUskf3t4
… package

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BcfLmuUiHYY3PbRUskf3t4
android:padding overrides android:paddingBottom on the same view, so the
room left under every page was ignored and the tab bar covered the last
content. Pad each side explicitly and let the scroll views scroll past
the padding.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BcfLmuUiHYY3PbRUskf3t4
…wrong password; no FGS crash before the mic is allowed

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BcfLmuUiHYY3PbRUskf3t4
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BcfLmuUiHYY3PbRUskf3t4
Inert votes and a present-mass fusion normaliser; the user's word reaches
the detectors; the logo detector requires a sighting, slides a search
window and goes stale without frames; the camera drops partial screens;
teach mode with "Show's back" and a quiet period after "Not an ad";
network_ip keeps one connection, names a busy set, and ducks the volume
through the profile's VOLM commands with crash recovery. Listener example
config, ADR 0014, and a beginner's guide for a Pi on a shelf.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BcfLmuUiHYY3PbRUskf3t4
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BcfLmuUiHYY3PbRUskf3t4
The Sharp RS-232C controller gains duck_level / normal_volume /
duck_state_file with crash recovery and restore-on-close. The listener
config defaults to the cable, with Wi-Fi as the documented alternative.
The beginner's guide and its NESPi 4 PDF are rewritten for the cable
(parts, wiring diagram, dialout group, port discovery, error messages).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BcfLmuUiHYY3PbRUskf3t4
…er as a camera target, a better speech model

JudgeDetector (core) collects words from speech and captions and asks a
TranscriptJudge on a cadence — tie-breaker by default — holding a
COMMERCIAL answer as a logo-weight vote and saving confident answers as
scripts. ClaudeJudge uses the Anthropic Java SDK (Haiku 4.5 default,
cached instruction, text only); LocalJudge runs Qwen2.5-0.5B through
MediaPipe LLM Inference from a resumable one-time download.
LogoFinder.bandResult finds a news channel's lower-third band and the
logo detector watches it under the name ticker_absence. Methods page
gains cards 7 and 8, the camera target and speech model choices, help
topics and warnings. ADR 0015.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BcfLmuUiHYY3PbRUskf3t4
…lus guide

Borrowed from admuffs (ADR 0016): the engine tells room-microphone
detectors about every duck. The loudness detector freezes its vote for one
window, measures how far the room dropped and judges the ducked ad on the
original scale, so a ducked commercial no longer reads as "programme
resumed" and the set is not ducked again and again. When the ducked set is
buried under the room (near the silence gate, or flat fan noise by the
silence detector's own spectral-flatness test) loudness and silence go
inert and the other methods carry the unmute. Python listener and Kotlin
core alike, with replay tests for both cases.

Android: method 8 offers Qwen 2.5 0.5B, Qwen 2.5 1.5B and Qwen 3 4B, each
in its own file, chosen under Methods. docs/print gains the Local AI guide
for the OnePlus Ace 6 Ultra (HTML source and PDF).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BcfLmuUiHYY3PbRUskf3t4
… on it

ADR 0017. Method 9, the break clock, learns which minutes of the hour the
breaks land on from every confirmed break and votes once a minute has been
watched in three hours; a default-weight vote that tips the balance and
never ducks alone. Kotlin and Python, with a shared TSV format.

Timed manual ducks (30/60/90/120 s) turn the set down from any state,
including the recovery pause, and back up on their own; nothing is
learned. Android Home and remote, the web page, and a duck_for IPC command.

The remote: every Sharp handset key through the control port (RCKY), on an
Android screen that also carries Start, Stop, Is an ad, Show's back, Not an
ad and the timed ducks, and on the web page; send_key on controllers, a
remote IPC command, and a commands.remote_key template in the Sharp profile.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BcfLmuUiHYY3PbRUskf3t4
…een phones

ADR 0018. On Android 10+ the phone can hear its own playback through
Android's playback capture: play a channel's live stream (MS NOW, Xfinity
Stream, any web player) and the engine runs without a TV, fingerprints on,
speech and the AI judges as configured, learning every break it ends into
the same ads.tsv and scripts.tsv the TV mode reads. The notification counts
the breaks; a player that blocks capture is reported as silence.

Share this phone's memory / Import memory from a file zip and merge the
breaks, scripts and break clock between phones without doubling what is
known. The stream-learning guide (HTML + PDF) is in docs/print.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BcfLmuUiHYY3PbRUskf3t4
ADR 0019. One-file PyInstaller binaries of the core for Windows x64
(with a windowless adhushw.exe for the logon task), macOS arm64, Linux x64
and Linux arm64, each smoke-tested on its own runner (--version, init,
doctor, an offline replay) before publishing; a Raspberry Pi bundle with
the arm64 binary, the listener config and the PDF guides; the APK, the web
app (the iOS install, as a PWA) and the wheel as before.

The web front end, the device profiles and the example configs travel
inside the binary (util/resources.bundled); `adhush init` writes a starter
config and the profile library next to it. Release notes carry a
per-platform download table; the README and docs/release.md match.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BcfLmuUiHYY3PbRUskf3t4
…ing directory

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BcfLmuUiHYY3PbRUskf3t4
…able countdown (ADR 0020)

Method 10, break jingles: the channel's sting into and out of every break
is learned from the breaks the other methods end (three breaks opened by
the same three seconds promote it) and then ducks alone the moment it is
heard; a closer heard while ducked is the programme coming back. Kotlin
core and Python (`jingle` detector, on in the listener example); the
detector is inert until it has a promoted opener so a learner never
dilutes the fusion normaliser; `jingles.tsv` travels with the shared
memory.

The ad badge during Android stream learning: ML Kit reads the player's
corners once a second; "Ad", "Ad 1 of 3", "AD 0:15" or "Your video will
resume" votes like the channel bug, and its absence while scanning is
programme evidence.

Learned break lengths: the break clock keeps a 30-s histogram of how long
this channel's breaks run (clock.tsv v2); a mute's ceiling becomes
p90 + 30 s once five breaks are known, and the status shows "about m:ss
left" during an automatic duck.

Manual ducks now run 30 s to 300 s in 30-s steps everywhere (phone Home,
the remote, the web page, IPC duck_for), with a +30 s button and
notification action that extends a running duck (or holds an automatic
one as a timed duck); the status line counts down. A remote icon sits in
the toolbar of every phone screen.

Replay and CLI flow tests point the jingle and clock stores at temp
files; data/clock.tsv and data/jingles.tsv are gitignored.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BcfLmuUiHYY3PbRUskf3t4
…s (ADR 0021)

aspect_change (Python, HDMI and screen capture): dark, flat bars at the
frame's edges give the active picture's aspect ratio; the programme's
shape is a rolling mode that freezes while the picture is changed, and a
spot that arrives pillarboxed or letterboxed votes 1.0 for as long as it
holds the other shape. Inert unless changed, so it never dilutes the
detectors that do see something; default weight, never alone; a change
longer than any break restarts the baseline. Synthetic fixtures gain an
ad_43 kind for its ground truth. On by default and in the HDMI example
configs.

Crest factor inside loudness (Python and Kotlin): each block's
peak-to-RMS ratio rides in the short-term window against a baseline taken
and updated exactly when the loudness baseline is; a drop of crest_drop_db
(4 dB) x 1.4 adds up to half a vote, so a spot compressed flat shows even
when it is no louder, and a music bed cannot mute by itself. The
conformance fixture is regenerated (unchanged: steady tones have a
constant crest).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BcfLmuUiHYY3PbRUskf3t4
… backlog (ADR 0022)

SetupWizardActivity: seven steps, offered once on first run and always on
the Home page. It asks how the phone reaches the TV (and asks a networked
Sharp its volume), where the phone sits, the channel, ticker and captions;
listens to fifteen seconds of the show and ten of the room with the set
muted (dBFS, flatness, crest per block; numbers only); looks through the
camera for eight seconds for a whole, lit screen and how wide it is; then
suggests every method, the duck level (ADR 0016's posture as numbers), the
camera target and zoom, and the local AI size from the phone's memory,
each with its reason. Apply and review writes Settings and opens the
Methods page (MainActivity.EXTRA_PAGE; a running instance reloads in
onNewIntent).

Wording audit: every count of the phone's methods says ten (Help
"Choosing methods" said eight and left the clock and the jingle out of its
weak/strong lists; the Help header said six; the Android README's screens
section said six; the local-AI print guide said eight, PDF regenerated).

docs/ideas-backlog.md ranks fourteen ideas from comskip, MythTV, the
ATSC A/335 and DVB-TA watermark specs, patents and peer projects.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BcfLmuUiHYY3PbRUskf3t4
…ut, and three feeds (ADR 0023, 0024)

ADR 0023, all inert until they have evidence and all in the default set:
rating_bug (the parental-rating box after a break is positive programme
evidence; Python HDMI path and the phone camera via the logo detector's
screen box); ad_units (separators on the 15-second ad-unit grid hold a
mute through a pod; both cores, on the phone riding on Quiet gaps);
uniform separators in black_frame (a white flash or colour card counts
like black, spread measured on strided raw pixels); cutscene (captured
intro/outro frames, `adhush cutscene add`, phash + thumbnail
correlation; "in" ducks alone, "out" is the show back); stereo_width
(the mono/stereo switch, measured by hdmi_uvc, line_in and ts_stream
before their downmix, AudioEvent.width); watermark (ATSC A/335 / DVB-TA
presence lost when a local spot replaces the marked picture;
experimental, written from the spec's shape).

ADR 0024: schedule (XMLTV via util/xmltv; a grace window after a start
dilutes mutes, ad_free channels never mute); ts_stream capture (HTTP or
UDP transport stream fanned out to two ffmpegs and util/mpegts, which
follows PAT -> PMT to the SCTE-35 PID and parses splice_insert and
segmentation descriptors) with scte35 votes from the out cue to the in
cue; crowd (an opt-in shared feed with k-anonymous channel hashes,
`adhush crowd serve`, polled off the decision thread, this device's own
breaks reported).

Programme evidence now comes from any detector with program_present
(base property, Python and Kotlin); wall time ticks every detector;
cues are a third event type pumped by run_live. Detectors that identify
a break outright (jingle, cutscene, scte35, crowd) carry three default
weights unless a profile says otherwise.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BcfLmuUiHYY3PbRUskf3t4
…s on every branch

The v0.24.0 release built all four binaries and then refused to publish:
`mypy src` under strict mode cannot find Pillow, which `adhush cutscene
add --image` imports optionally inside a try/except and which CI does not
install. This machine has Pillow with its type information, so the local
run passed and the drift only appeared at the tag.

A mypy override ignores the missing import; verified by hiding Pillow from
site-packages and re-running, which is exactly CI's situation.

The reason it surfaced so late: ci.yml ran only on main and pull requests,
and all this work lives on a long-lived branch with no pull request, so the
Python checks had never run in a clean environment until the release job
ran them. It now runs on every branch push.

No detector or behaviour changes; 0.24.0's version strings stand.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BcfLmuUiHYY3PbRUskf3t4
socrtwo and others added 13 commits September 18, 2026 16:46
…ed, each tried and reported

The TV step no longer trusts a typed address alone. Find my TV tries all
three paths: the Wi-Fi (the typed address, else a scan of the phone's own
/24 on the Sharp control port with a 300 ms connect on 32 threads and a
VOLM? to every listener until one answers as a Sharp; the found address is
saved), the serial cable (adapter present, USB permission asked for through
the same broadcast the Home page uses and the cable retried when granted,
then VOLM? over the cable), and the infrared blaster (present, MUTE fired
twice two seconds apart, then a Yes/No from the owner since infrared is
one-way). The first that worked is chosen, network before cable before
blaster, and the radio group still lets the owner override. The plan
names how the TV was reached. ACCESS_NETWORK_STATE for the phone's own
Wi-Fi address. ADR 0022 amended.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BcfLmuUiHYY3PbRUskf3t4
… proves each way in (ADR 0025)

Core: VolumeDevice and LevelController (the Sharp controller made general;
SharpIpClient implements the interface, SharpController is the level
controller with the Sharp client underneath). Drivers, pure JVM: Sony
Bravia JSON-RPC (pre-shared key), LG webOS SSAP over a WebSocket (allow
once, client key kept), UPnP RenderingControl for any DLNA set, Samsung's
remote channel over a WebSocket (allow once, token kept), Roku ECP, Vizio
SmartCast (PIN once, token kept). MiniHttp and a hundred-line
MiniWebSocket, tested against a loopback server. Infrared encoders for
NEC, Samsung, Sony SIRC-12, RC-5 and Panasonic with a code table for
seven brands. TvFinder: one SSDP search, then each brand's key-free
question on every address, exact-volume paths ordered first.

App: the wizard's Find my TV lists every set found and proves the paths
in order — exact ones by reading the volume, key-only ones by firing MUTE
twice and asking — then the serial cable, then infrared brand by brand.
The service builds a level or step controller per path; the remote works
over Roku, Samsung and Vizio; the TV page's fourth radio carries what the
wizard found; tokens live in the encrypted settings; cleartext HTTP is
allowed for the sets on the LAN.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BcfLmuUiHYY3PbRUskf3t4
…, Philips JointSpace (ADR 0026)

Core, pure JVM and unit-tested: MiniProto (a fifty-line protocol-buffers
codec), ClientIdentity (a self-signed client certificate via BouncyCastle,
kept as a PKCS#12 blob), AndroidTvRemote (pairing on 6467 with the
six-character code hashed with both RSA keys; the session on 6466 answers
configure and pings and reads the set's volume reports, so the level is
known and set by stepping), MiniMqtt (MQTT 3.1.1 in a hundred lines,
tested against a loopback broker), HisenseVidaa (service credentials,
gettvstate, the four-digit code, getvolume/changevolume/sendkey), Digest
(RFC 2617, checked against its example) and PhilipsJointSpace (version 1
plain, version 6 with digest auth and the signed PIN grant). TvFinder
probes the three ports.

App: three more level controllers, remote keys over all three, settings
for the identity, client id and paired credentials, and the wizard's
Find my TV shows one code row when a set asks for a code and waits for
it on the path's own thread.

A Kotlin trap fixed and noted: inside Socket().apply { } the name `port`
is the socket's own; the MQTT client connected to port 0 until the
loopback test caught it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BcfLmuUiHYY3PbRUskf3t4
The three bcpkix/bcutil/bcprov jars each ship META-INF/versions/9/OSGI-INF/MANIFEST.MF and the debug APK's resource merge refuses the duplicate; the core tests were green.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BcfLmuUiHYY3PbRUskf3t4
The whole picture in one page: both implementations, the release table,
the quick start with every CLI command, how fusion decides, all seventeen
Python detectors and the ten phone methods, every control path on both
sides with the wizard's brand table, the capture paths, five design goals,
and a map of the documentation.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BcfLmuUiHYY3PbRUskf3t4
Jingle detector (both cores): a learned sting is also recognised
transposed by up to two semitones (the chroma bits rotate) and stretched
by up to ten per cent in tempo, docked a small penalty, so a channel's
per-show re-cuts count for the sting they resemble; the vote reason
names the variant. Every jingle remembers the local hours it opened
breaks in; at one of those hours it is trusted a break early and matched
a little more loosely. jingles.tsv becomes v2 with an hours column; v1
files load unchanged, and the Android memory merge carries the hours.

New default-weight detector `stinger` (detect/stinger.py, Stinger.kt):
a short noisy burst over the two-second bed, then the level moves. Inert
until it fires, decays over 2.5 s, ignores our own duck. On by default
in the Python core; on the phone it rides on the Loudness method.

Engine passes the break's wall-clock start to the jingle detector and
ticks it with the wall clock. Tests for families, tempo, hours, the TSV
round-trip and the stinger in both cores; replay pins and the Android
method-toggle test updated. Docs: ADR 0027, changelog, release notes,
roadmap phase 19, READMEs, Help topic, example configs; version 0.28.0.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BcfLmuUiHYY3PbRUskf3t4
…zard button, a real remote

Both cores (ADR 0027, amended): a break shorter than 20 s teaches the
jingle learner nothing; two hearings of a sting within half an hour
count once (each jingle keeps the wall time of its last counted hit,
TSV column 9); a taught break under 30 s is a slip of the finger and one
over six minutes is cut there; a remembered break whose duck ends within
ten seconds on programme evidence is a false match, two of which forget
the record. Tests for each rule in both cores.

Android: one model download at a time, and a finished model is never
deleted (a double press deleted it). Forget what it learned… (Methods
page and the ⋮ menu) with a tick per kind of memory. Save to a file on
the Log page through the system picker. The set-up wizard is a Home-page
button beside Start, Stop, Test TV and Remote, labelled on its screen,
and never opens by itself. Home page: side-by-side teach buttons, timed
ducks as chips, test output only when there is some. The remote laid
out like one: rockers either side of a D-pad, a number pad, transport
and picture keys, 56 dp keys. Taller, rounder buttons and cards; dark
mode colours. Version 0.28.1 (versionCode 29); changelog, ADR, release
notes, READMEs, Help.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BcfLmuUiHYY3PbRUskf3t4
…ht seconds

The badge reader notices a picture that is black all over (a protected
window mirrors as nothing); the service reports, on the Home card and the
notification, digital silence from the first seconds (the player opted
out of playback capture) and a blacked-out picture (Android's "content
hidden from screenshare" flag), each with what to do instead: the
channel's website in Chrome, or the microphone with the normal Start.
A black picture with audio still heard is reported as well, since the
AD badge cannot be read from it. Help and the README explain the
message. Version 0.28.2 (versionCode 30).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BcfLmuUiHYY3PbRUskf3t4
Feedback.kt: a button dips when pressed, turns green with a tick for a
second and a half when what it asked for went through, red with a cross
when it did not, then comes back as it was. The service broadcasts each
control action's outcome (Is an ad, Show's back, Not an ad, the timed
ducks, +30 s, every remote key) and the Home page and the remote flash
the button that asked; local actions (Save, Test TV, downloads, share,
import, forget, save the log) report their own. The teach buttons and
the ducks are bright only when they would act right now. A Help topic
explains the colours. Version 0.28.3 (versionCode 31).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BcfLmuUiHYY3PbRUskf3t4
… resumable

Feedback: the press animation is gone. A button that started a job (a
model download, Test TV, an AI test, an import, saving the log) stays
blue and disabled with the job's progress as its label until the job
ends, so it cannot be pressed twice; then green with a tick or red with
a cross and the label the outcome calls for.

Downloads.kt: the bytes go to a .part file with the expected size kept
beside it; a download that stops short throws with how far it got and
the next press resumes; only a part of exactly the expected size is
moved into place. LocalJudge keeps the installed size beside the model
and counts it installed only when every byte is there; SpeechSource
unpacks into a folder of its own and moves it into place whole. The
buttons read Resume with the percentage; Delete partial model downloads
in the ⋮ menu. Version 0.28.4 (versionCode 32).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BcfLmuUiHYY3PbRUskf3t4
…mentions restored

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BcfLmuUiHYY3PbRUskf3t4
…s text

The Remote tab (RemotePanel, replacing RemoteActivity): AdHush's own
controls on top, then the TV's remote drawn as one — a dark rounded
body, power, input and mute, a number pad, the volume and channel
rockers either side of a round D-pad with OK in the middle, menu,
transport, the colour keys and the rest, every key a pill or circle
with one line of white text. Tabs: Home, Remote, TV, Methods, Log; Help
is the ? in the toolbar. Every button app-wide is one line tall with
text that shrinks to fit (maxLines 1, autosize in the styles); shorter
labels on the Log and memory buttons. Version 0.28.5 (versionCode 33).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BcfLmuUiHYY3PbRUskf3t4
@socrtwo
socrtwo merged commit 4b57c8f into main Oct 19, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants