v0.17.1: a library to break

Round 47 was verified by loading trav's real 21,531-track library. Nothing was
written and nothing was at risk, but it's the wrong habit and he said so. The
alternative is better for development anyway: reproducible, in-repo, and full
of the awkward cases on purpose rather than by luck.

scripts/make_dev_library.py builds a complete synthetic library — data dir and
music tree — from ffmpeg sine waves in six containers, with art on some albums
and not others, every playlist type (regular, folder, live/non-live/
unsupported/limited+nested smart, system, empty, duplicates), a play journal
and a tombstone. ~4 MB, gitignored; the generator is the artifact worth
keeping, not the sine waves.

The edge cases are the point. A collision pair identical in artist/album/title/
number, slashes and colons in every name, a track with no artist, a multi-disc
release, a compilation whose album_artist differs, a dangling location, a
208-character title, and non-ASCII plus an emoji all the way out to the m3u
filename. CLAUDE.md now carries the rule: never develop against the real
library, and when a feature needs a shape the fixture lacks, add it here.

Using it found two bugs in it — a relative --out made every location resolve
against the wrong root, and four-minute uncompressed clips made it 62 MB.

andTunes is paused on a weak connection (the Android SDK is a ~1-1.5 GB
one-time download; after that --offline builds need nothing). andtunes/
README.md now carries everything needed to resume cold: measured device facts,
the dp trap, exact toolchain commands, locked design decisions, and the
library.json contract. JDK 21 is installed and ticked off.

Also answers the scroll wheel question: it reads as volume because the ROM's
key layout maps the wheel's KEY_UP/KEY_DOWN to KEYCODE_VOLUME_UP/DOWN, but a
focused activity sees key events first — so andTunes can claim it by consuming
both those and DPAD_UP/DOWN, in onKeyDown and onKeyUp. No system file touched,
no other app affected.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015wrTys5U1fLb4pBD2LKWzV
This commit is contained in:
2026-09-10 00:49:26 -04:00
co-authored by Claude Opus 5
parent cc1ca5ef78
commit bcba621f3f
8 changed files with 696 additions and 7 deletions
+58
View File
@@ -1,5 +1,63 @@
## Done
### Round 48 (2026-09-10) — A library to break (v0.17.1)
Round 47 was verified by loading trav's real 21,531-track library. Nothing was
written to it and nothing was at risk, but that is the wrong habit and he said
so: "it's too precious… mistakes happen and it makes me nervous." He's right —
the safety of a given run isn't the point when the alternative is a fixture
that's better for development anyway: reproducible, in-repo, and full of the
awkward cases on purpose rather than by luck.
- [x] **`scripts/make_dev_library.py`.** Builds a complete synthetic library —
data dir *and* music tree — from ffmpeg sine waves in six containers
(mp3, m4a, flac, wav, aiff, ogg), with embedded art on some albums and
none on others, every playlist type (regular, folder + children,
live/non-live/unsupported/limited+nested smart, system, empty,
duplicates), a per-machine play journal and a tombstone. ~4 MB, a few
seconds to build, `--force` to rebuild, gitignored — the generator is
the artifact worth keeping, not the sine waves.
- [x] **The edge cases are the point.** Each one has bitten something: a
collision pair identical in artist/album/title/number (the `[track_id]`
suffix), slashes and colons in every name (`sanitize_name`), a track
with no artist and none with an album (Unknown Artist/Album), a
multi-disc release (the `2-04` prefix), a compilation whose
`album_artist` differs from every track artist (album grouping and the
art key), a dangling `location` (the "skipped, no local file" counters),
a 208-character title (the 150-char truncation — and the manifest still
carries it in full, which is what the app displays), and non-ASCII plus
an emoji all the way out to the device's m3u filename.
- [x] **Two bugs in the generator, found by using it.** Passing a relative
`--out` made every `Track.location` resolve against the wrong root on
load, because locations are absolute in memory and stored relative to
the data dir; and four-minute uncompressed clips made the fixture 62 MB.
Durations only need to *differ* (sorting, smart-playlist limits,
`#EXTINF`), so they're now 2–20 s and the whole thing is 4.4 MB.
- [x] **Round 47 re-verified against the fixture**, not the real library:
first sync, an idempotent re-sync (0 copied, 6 kept), unticking, and the
awkward playlist end-to-end — `Song [19].mp3` / `Song [20].mp3` for the
collision pair, `mix summer 2003 🎧.m3u`, the dangling track counted as
skipped rather than fatal.
- [x] **A standing rule in CLAUDE.md**: never develop or verify against the
real library; when a feature needs a shape the fixture lacks, add it to
the generator.
- [x] **andTunes documented for a cold resume.** `andtunes/README.md` now
carries the measured device facts, the dp trap, the exact toolchain
commands, the locked design decisions and the `library.json` contract;
`andtunes/TASKS.md` renumbered to andTunes' own phases. Work is paused
on a weak connection — the Android SDK is a ~1–1.5 GB one-time download,
after which `./gradlew --offline` needs no connection at all. JDK 21 is
already installed and ticked off.
- [x] **The scroll wheel question answered.** It reads as volume in every app
because the ROM's key layout maps the wheel's KEY_UP/KEY_DOWN to
`KEYCODE_VOLUME_UP/DOWN` — but a focused activity sees key events first,
so andTunes can claim it by consuming both those *and* `DPAD_UP/DOWN`
(the mapping is unconfirmed) in `onKeyDown` **and** `onKeyUp`. No system
file touched, no other app affected.
Not done, deliberately: no iTunes XML in the fixture (the importer tests still
use the gitignored real export), and no synthetic Syncthing conflict pair.
### Round 47 (2026-09-06) — LinTunes writes the device's library (v0.17.0)
The first half of andTunes, a music player for the Rabbit R1 that never scans