Repository navigation
NewAmp 2.2.0
A correctness release. Nothing here is a new feature; it is the app doing what
it already claimed to do, with a regression test behind each fix.
Your data
- A corrupted library database stopped NewAmp from starting at all. Opening the
database only checked that the file loaded and its schema applied, and
page-level corruption passes both of those — the header and schema stay
intact, and the damage only shows up on the first real read. That read
happened during startup, before the window was shown, so the app exited with
no window and no error. Reinstalling didn't help, because the database lives
in your user data folder and an uninstall leaves it there. NewAmp now runs an
integrity check when it opens the database, sets a corrupt one aside with a
.corrupt-suffix instead of failing, and rebuilds the library from a rescan. - The library and settings files are written through a temp file, synced to
disk, and renamed into place. A crash or power cut in the middle of a save can
no longer leave you with a truncatedlibrary.dbor asettings.jsonthat
has forgotten your music folders and Last.fm login. The quit-time save and
the background save also use different temp files now; before, quitting during
a background save could let one writer's bytes land inside the other's file. - A library file that is merely locked (antivirus, a sync client, a network
share) is retried instead of being mistaken for a corrupt one and quarantined.
If it stays locked through every retry, a packaged build used to exit with
nothing on screen; it now shows a dialog saying why and records the failure
in the diagnostics log. - When the rename that swaps a fresh file into place kept failing under a
Windows lock, the fallback was a plain in-place write with no sync to disk,
the exact hazard the atomic path exists to remove. It now retries for about
a second, tries once more through a fresh temp file, and only then writes in
place, synced. - Support backups copied
library.dbandsettings.jsonoff disk, but the
library batches play counts for up to thirty seconds and the resume position
is debounced, so a backup taken right after listening could be missing the
last half minute. Backups now pause the watcher and scanner and snapshot both
stores from memory. Restore stops those first, takes its safety copy from the
live stores, waits for any write still in flight, and only then replaces the
files. - Auto-watch no longer deletes a track, with its ratings and play history, the
moment a file disappears. Editors that save by renaming and sync clients make
files vanish for a fraction of a second; NewAmp now re-checks after 1.5 seconds
and only prunes what is still gone. - Eviland's per-track visual memory could lose a love, a skip, or a newly
learned section if it arrived while a save was in flight. Saves now record
what they covered and run again for anything that landed mid-write. - Scrobbles made while your Last.fm session key is dead were being attempted,
refused, and dropped with no sign of it. They are kept in the outbox now, so
they survive and the app can tell you a reconnect is needed. - Album art you applied by hand survives a rescan of a file that has no embedded
art of its own.
Playback
- Shuffle and repeat are two separate toggles again. They were stored as one
value, so turning one on turned the other off, in the transport bar and in
three deck skins. A shuffle-plus-repeat combination also survives a restart now. - Shuffle gets the same crossfade and gapless handoff every other mode has. It
was excluded because the next track is random; the pick is now made once,
ahead of time, and the handoff plays exactly the track it prepared. - Removing the track that is playing while paused, or removing the last track in
the queue, left the audio engine holding it. Pressing Play resumed the removed
track under the new track's title. - Restoring your queue after a restart follows the track you were on by its id.
If tracks before it had been deleted since, the old code landed you a few
tracks off. A queue that was loaded without pressing Play also came back with
its first track selected; it stays idle now, and the saved position is only
applied when the track it was saved for is the one selected. - Click one track and then another before the first has started, and both were
counted as played and both were sent to Last.fm as now playing. The engine
now tells the store when a request was overtaken, and play counts, Last.fm,
podcast progress and Auto DJ only act on the request that actually started. - Auto DJ no longer undoes a Clear Queue that happens while it is looking for
candidates. - Space no longer both starts a highlighted track and toggles playback.
- The volume you start the app at goes through the same loudness taper as every
later change, so the first track is not louder than the rest.
Library, search, and tags
- Searching for a literal
%or_works. - Camelot keys were scored as if they were on a different wheel, so a library
tagged8Bmixed badly with one taggedC major. The 24 Camelot codes now map
to their real keys. - Opening an album from Now Playing or a track link snapped back to the album
grid about a fifth of a second later, because the navigation also changed the
grid's search filter and the resulting reload cleared the selection. - Tag rules that use
matchesnow run on a matcher whose time is proportional
to the text it is matching, whatever the pattern looks like. Before, patterns
went to the regular expression engine behind a guard that rejected the shapes
known to be catastrophic;.*.*=was not one of them, and it froze the whole
app when the rules ran. The cost is a smaller grammar: backreferences,
lookahead, lookbehind and lazy quantifiers are refused when a rule is saved,
with the reason shown in the Tags view. Named groups, classes, alternation,
anchors, word boundaries and repeats up to 64 all still work, and matching is
still case-insensitive. - A pair of tag rules that reference each other no longer wipes the whole tag
table when rules are recomputed; the cyclic pair is reported and skipped.
tag()references are matched case-insensitively. - The Weird Shelf on the Discover page counted genres once per track. It now
builds the shelf in 96 ms on a 12,000-track library; smart shuffle picks the
next track in 13 ms on a 40,000-track queue. Both measured on the release
machine withnpm run test:discover-weirdness-perfand
npm run test:smart-shuffle-perf. - Picking a playlist cover and then taking more than ten minutes to finish the
playlist failed the save. Approvals no longer expire on a clock. - A Wikipedia outage was being cached as "this artist has no page" for a day.
Only a real miss is cached now, and the lyrics and facts caches prune old
entries instead of failing when browser storage fills up.
Radio Brain
- Enabling, disabling, or changing the port of the LAN station server could
leave two servers running, a listener stranded on the old port, or a shutdown
that never finished because it waited for phone clients that hold their
connection open on purpose. One reconciler owns the server now; stop ends the
live connections itself and completes in well under a second.
Security and privacy
- Podcast fetches classified IPv6 addresses by their text, so an IPv4-mapped
address like::ffff:169.254.169.254slipped past the guard that keeps feeds
from reaching your local network, and a plain hostname that resolved to a
private address was never checked at all. All podcast traffic now goes
through one HTTP path that validates the resolved addresses and connects only
to those, re-checks every redirect hop (at most five), and gives up on a
server that accepts a connection and never answers. - Feed and episode bodies were read whole and only then compared against their
size caps, so a server that omitted its Content-Length could push hundreds of
megabytes into memory first. Bodies are cut off the moment they pass the cap;
downloads stream to a temporary file and are renamed into place only when
complete, and a failed download leaves nothing behind. - A 169-byte Winamp skin file could freeze the app: its bitmap declared
100000 by 100000 pixels and the colour sampler looped over the declared size
before reading a byte. Bitmap dimensions are now checked against a ceiling
and against the bytes actually present, every archive header field is checked
to lie inside the file, and encrypted or otherwise unsupported archives are
refused with a plain message. Archives are also decompressed under per-entry
and total size caps, and a skin file over 20 MB is refused before it is read. - Custom skin colour and radius values are checked against a grammar for what
each slot can hold — hex,rgb(),hsl(), a named colour, or a bounded
length. A skin file could previously carryurl(https://…)in a colour slot
and make an outbound request when applied. The check runs on import, when a
skin is saved to settings, in the Skin Workshop's live preview, and again at
the moment a value reaches the page. - Electron moves from 42.0.1 to 42.11.1, which fixes a session-isolation bug in
custom protocol handlers that NewAmp's own schemes were exposed to. No
dependency changed its declared range, and the audit is clean. - Saving a playlist cover only accepts a path that the cover picker itself just
returned, so the renderer can no longer ask the main process to read an
arbitrary file.
Other
- The Windows installer is 115 MB, down from 136 MB for 2.1.0, and the unpacked
app is 376 MB, down from 488 MB. The difference is duplicate library copies
that were already bundled, 54 unused Chromium locale files, and a DirectX
shader compiler that only WebGPU needs. Measured on the release machine
against the published 2.1.0 installer. - On macOS, closing the last window keeps NewAmp running, as Mac apps do,
instead of tearing the library down. - The release workflow now runs the typecheck and the headless smokes before it
packages a tagged build. Previously a tag packaged, signed and published
without running a single test. Several smokes that launched the app, printed a
result and always exited 0 now assert what they print, and every unit test in
the repository runs from one entry point in CI and in the release gate, so a
test can no longer be added without anything running it. - The standalone
@eviland/corepackage had stopped building under its own
stricter compiler settings and nothing noticed. It builds again, its public
index matches what the engine exports, and CI checks both.