<feed xmlns='http://www.w3.org/2005/Atom'>
<title>listen.git/gradle.properties, branch main</title>
<subtitle>Listen — personal Android podcast app
</subtitle>
<id>https://git.terst.org/listen.git/atom?h=main</id>
<link rel='self' href='https://git.terst.org/listen.git/atom?h=main'/>
<link rel='alternate' type='text/html' href='https://git.terst.org/listen.git/'/>
<updated>2026-10-31T09:16:45+00:00</updated>
<entry>
<title>Deep clean, part one: get the pipeline out of the command</title>
<updated>2026-10-31T09:16:45+00:00</updated>
<author>
<name>Peter Stone</name>
<email>thepeterstone@gmail.com</email>
</author>
<published>2026-10-31T09:16:45+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/listen.git/commit/?id=ff40abd84236288cc0a82226460f8815ad31b76d'/>
<id>urn:sha1:ff40abd84236288cc0a82226460f8815ad31b76d</id>
<content type='text'>
cmd/listend/main.go had reached 1,249 lines carrying nine passes, and cmd has no
test file -- so every judgement in the transcript pipeline lived where nothing
could reach it. That is not hypothetical: a refetch master blocked the recurring
detector for a fortnight because the condition was wrong in a place no test
could see.

internal/words now holds the five passes with judgement in them -- Harvest,
Recognise, Rederive, Recurring, SecondCopy -- behind a Pipeline that names its
dependencies. main.go is 631 lines and does what a command should: read flags,
wire things together, serve.

**Moving it found a latent bug.** Pruning an episode's audio cleared its
transcript fields but not its master ones, so a re-mirrored episode would claim
a master whose file had been deleted -- a second description it could not
produce, and subtraction finding nothing for it for ever. Exactly the shape of
the bug that had just cost a fortnight. Pipeline.Forget now clears all of it,
including the attempt timestamps, so a re-mirrored episode is done again rather
than skipped for ever. Pinned by a test that asserts it needs recognising
afterwards.

Also in this part: Gradle's file-system watching is off. Chaquopy extracts a
3,200-file Python environment into app/build mid-build and the watcher tripped
over re-registering it, printing "Caught exception: Already watching path" in
front of every build error this project produced all week. It costs nothing to
turn off -- measured twice each on a no-op compileDebugKotlin, 1.50s and 1.59s
with, 1.46s and 1.46s without -- because that extraction invalidates the VFS
anyway. A watcher holding stale state about a tree another tool rewrites causes
the "clean and try again" class of problem rather than curing it.

Co-Authored-By: Claude Opus 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B
</content>
</entry>
<entry>
<title>Scaffold Listen, with a podcast-native feed parser</title>
<updated>2026-09-21T08:13:49+00:00</updated>
<author>
<name>Claude</name>
<email>noreply@anthropic.com</email>
</author>
<published>2026-09-21T08:13:49+00:00</published>
<link rel='alternate' type='text/html' href='https://git.terst.org/listen.git/commit/?id=5876025559ba0ad287d3a7a4d9fa0246509e668e'/>
<id>urn:sha1:5876025559ba0ad287d3a7a4d9fa0246509e668e</id>
<content type='text'>
Sibling to FeeDreader, not a shared module. That was a considered call: its
FeedParser only inspects &lt;enclosure&gt; while hunting for a thumbnail, and its
FilterRuleEngine is 46 lines of title substring matching that doesn't
generalise to queue rules, so only Opml.kt would genuinely have transferred.

The parser is the piece everything else sits on, so it goes first, test-first:
18 tests covering audio/video enclosure selection, iTunes durations in both
second and clock form, season/episode numbers, podcast:chapters for the skip
feature, RFC 5005 archive paging for drip subscriptions, and episode identity
falling back to the enclosure URL when a guid is missing.

Two things worth recording:

- Built on XmlPullParserFactory rather than android.util.Xml. The latter
  returns null off-device, which is why FeeDreader's 297-line parser has no
  tests at all. Both resolve to the same KXmlParser on Android.
- SimpleDateFormat with isLenient=false rejects "Tue, 02 Sep 2026 06:00:00
  GMT" -- an entirely ordinary RFC 822 date. Left strict, every pubDate in
  the app would have been null and episode ordering would have been silently
  wrong everywhere.

minSdk is 29, deliberately not FeeDreader's 36, which would restrict the app
to Android 16 and newer.

Permissions: INTERNET and ACCESS_NETWORK_STATE for feeds; FOREGROUND_SERVICE,
FOREGROUND_SERVICE_MEDIA_PLAYBACK, POST_NOTIFICATIONS and WAKE_LOCK because
Android requires a foreground service with a notification to play audio in the
background. That notification is the transport control, not an alert -- Listen
posts no new-episode notifications by design.

Co-Authored-By: Claude Opus 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_018AwnALZL6bD4VBtATDVv3B
</content>
</entry>
</feed>
