Skip to content

Logs tab, Record page rework, and a Spotify match that works - #19

Merged
revtex merged 1 commit into
mainfrom
feat/logs-tab-record-polish
Aug 14, 2026
Merged

Logs tab, Record page rework, and a Spotify match that works#19
revtex merged 1 commit into
mainfrom
feat/logs-tab-record-polish

Conversation

@revtex

Copy link
Copy Markdown
Owner

Five things from one batch.

1. Spotify metadata matched nothing SMTC reported

The attached log showed four failed attempts then "Spotify had no metadata for 'ATB - 9Pm (Till I Come)'." — for a track Spotify obviously knows.

The guard compared Spotify's bare track name against the detected title after running only Spotify's side through the window-title splitter. So it compared a parsed string against an unparsed one for every track the media session found, which is the common case since Phase 7 made SMTC primary — SMTC reports titles verbatim, where the window-title parser has already split them.

DetectedTrackMatch reduces both sides to a common form and compares them in both shapes: the title alone, and the artist joined on. A provider's bare name matches a detected Artist - Title because joining the provider's own artist onto its name produces the same thing.

Normalisation deliberately stops well short of fuzzy. The wrong answer at a track boundary is not "no metadata", it is a file tagged as a different song — so it forgives case, accents, spacing, and the punctuation each source wraps a qualifier in, and nothing else. Three guard tests pin the line: "Song" must not match "Song (Live)", "Song (Radio Edit)" must not match "Song (Extended Mix)", and "9Pm (Till I Come)" must not match "9Am (Till I Come)".

19 new tests.

2. The saved count showed zero

[ObservableProperty] on _savedCount refreshed HasSaved and not SavedCountText, which is derived from the same field. The count was correct in the debugger the whole time and stale on screen, and nothing failed. One attribute.

Worth watching for on the rest of this page — a missing [NotifyPropertyChangedFor] has no compile-time or runtime signal at all.

3. Logs is its own tab, beside Advanced

Third home in three attempts: the whole page, then a fixed-height box, then a collapsed expander. A log wants either the whole surface or none of it, and a shared page can offer neither.

LogsPage is a second view onto RecordViewModel rather than a ViewModel of its own — the lines, the filter and both buttons are already there and subscribed to the sink since startup, so a second ViewModel would mean a second subscription and two histories diverging by construction order. Auto-scroll (follow the tail only while already at the tail) moves with it.

4. Display and Currently recording

  • Backlit, not reflective. The panel was pale grey with near-black segments — a calculator face, and the palest object in a nearly black window. Inverted it goes a shade under the window with near-white segments, which is the transflective backlight a field recorder actually has, and contrast goes up. Two knock-ons that are easy to miss: the scanline brush flips black→white (a black line over a near-black ground is invisible) and the glass gradient flips too (top-dark on a dark panel reads as a shadow, not a diffuser).
  • The meter is untouched. Its own drawing, behaviour, and the whole decibel-placed spectrum are exactly as they were. Only the ground it sits on moved.
  • The track name came off the face. It was the one element that is neither a transport state nor a measurement — a proper noun of arbitrary length, ellipsised, where an instrument prints a legend. It now lives in the Currently recording card with the art and album it belongs to.
  • The counter gets the right-hand side and an ELAPSED legend. A bare 00:03:42 on a recorder could be elapsed, remaining, or a timestamp; one small word settles it once instead of every glance. Format text moves beside the transport block, which is where it belongs — "recording" and "recording as what" are one question.
  • Currently recording is art on the left, three labelled rows on the right: Artist/Song, Album/Year, Save path. Labels share a column so the values start on a common edge — which matters because two of the three rows arrive a second later, when enrichment lands, and a block that re-flows as it fills is one nobody trusts. The two late rows hide rather than blank, since an empty value beside a label reads as "there is no album" when it means "no lookup has returned yet".
  • Session totals moved onto the heading they count, right-aligned as Count: n | Time: hh:mm:ss with a rule filling the gap. They were stacked inside a card describing a single track — two timescales in one block — and the duration was prose (4m 12s) sitting inches from a fixed-width clock, so comparing them meant converting between them.

5. Removals

  • Readiness chips and ReadinessProbe, plus fourteen resource strings and the DI registration. A missing ffmpeg is already an InfoBar with a sentence in it, and a library folder is already unsaveable without one. Five green pills reporting nothing is wrong is a row that is always there and never read.
  • The FlaUI Desktop suite, deleted rather than skipped — the per-control page suites went on 2026-08-12 at your direction, and the thirteen shell/Record tests that survived were pinned to exactly the layout this PR rewrites. FlaUI.UIA3 and CI's --filter "Category!=Desktop" go with them. Automation ids stay on every control — they are a Phase 9 accessibility requirement in their own right and are what any future suite would need.

Verification

dotnet build clean, dotnet format --verify-no-changes clean, 995 tests green (824 Core + 171 UI). No UI testing run, per your standing instruction — worth a look at the Record and Logs tabs, and at whether the inverted display reads right on your monitor.

🤖 Generated with Claude Code

Five things the user asked for, in one pass.
**Spotify metadata matched nothing the media session reported.** The guard
compared Spotify's bare track name against the detected title after running
only Spotify's side through the window-title splitter — so it compared a
parsed string against an unparsed one for every track SMTC found, which has
been the common case since Phase 7 made SMTC the primary source. "9Pm (Till I
Come)" never matched "ATB - 9Pm (Till I Come)": four attempts, several
seconds, then an untagged recording. `DetectedTrackMatch` reduces both sides
to a common form and compares them in both shapes, title alone and artist
joined on. Normalisation stops well short of fuzzy — the wrong answer here is
not "no metadata" but a file tagged as a different song, so it forgives case,
spacing and the punctuation each source wraps a qualifier in, and nothing else.
**The session total read "0 saved" forever.** `_savedCount` refreshed
`HasSaved` and not `SavedCountText`, which is derived from the same field. The
count was right the whole time; only the string was stale, and nothing failed.
**The activity log is a tab.** Third home in three attempts, because a log
wants either the whole surface or none of it and a shared page offers neither.
`LogsPage` is a second view onto `RecordViewModel` — a ViewModel of its own
would mean a second sink subscription and two histories.
**The display is backlit rather than reflective**, and no longer carries the
track name. Pale grey with near-black segments was a calculator face and the
palest object in a nearly black window; inverted, contrast goes up and it
reads as the one lit thing on the page. The scanlines and the glass gradient
flip with it — a black line over a near-black ground is invisible, and a
top-dark gradient reads as a shadow. The meter's own drawing and spectrum are
untouched. The track name moves to the Currently recording card, where it sits
with the art and album it belongs to; the face keeps transport, format and the
counter, which now has the right-hand side and a printed ELAPSED legend.
Session totals move onto the heading they count, as `Count: n | Time:
hh:mm:ss` — they had been stacked in a card describing one track, two
timescales in one block, with the duration spelled as prose inches from a
fixed-width clock.
**The readiness chips and `ReadinessProbe` are gone**, with their fourteen
resource strings. A missing ffmpeg is already an InfoBar with a sentence in
it; five green pills saying nothing is wrong is a row always present and never
read. **The FlaUI Desktop suite is deleted** rather than skipped — it was
pinned to exactly the layout this rewrites — along with CI's Category filter
and the FlaUI.UIA3 reference. Automation ids stay on every control; they are a
Phase 9 accessibility requirement in their own right.
995 tests green, 19 of them new over the matcher.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@revtex
revtex merged commit 36492ef into mainAug 14, 2026
1 check passed
@revtex
revtex deleted the feat/logs-tab-record-polish branch August 14, 2026 05:07
revtex added a commit that referenced this pull request Aug 14, 2026
* Bring the changelog up to date with PRs 12-23
It was last touched in #11 and ten PRs have merged since, so the file
described an app several phases behind the one in the tree - which for
a Keep a Changelog file is worse than an empty one, because it reads as
current.
Entries for: the refresh token lost an hour into every session, SMTC as
the primary track source and the TFM raise that allows it, endpoint
hot-plug, extended-length paths, VB-CABLE detection, the existing-file
policy checked before it could know the destination, the Logs tab and
the Record page rework, the Spotify match guard, genre from Spotify's
artists with Last.fm behind it, the media-session floor and its two
mappers, Last.fm's missing album guard, the shutdown that left a ghost
process, and the provider summaries on Settings.
Readiness is deliberately absent: it was added in #18 and removed in
#19, so no release ever carried it and an entry for each would be two
lines describing nothing.
Test count updated to 1051 (877 Core, 174 UI).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Require a changelog entry per PR, and drop two stray images
The changelog going ten PRs stale was not an oversight anyone would
catch by trying harder, so it is a rule with a check behind it now
rather than a good intention. CLAUDE.md states it; a CI job fails a
pull request whose diff does not touch CHANGELOG.md, with a
`no-changelog` label as the escape hatch so a test-only fix opts out
by saying so instead of by staying quiet.
The job runs on ubuntu with no `needs`, so it answers in seconds
alongside the Windows build rather than behind it - which is why it is
a job of its own, despite the note on `build` arguing against exactly
that for publish-check. That case was gated on `needs: build` and paid
for a second VM to run strictly afterwards; this one does not.
Also removes 06aa2f03-...jpg and b514cfc3-...jpg from the repository
root. Both were untracked, and both were mine to be suspicious of and
wrong about: I guessed earlier they were leaked cover-art downloads.
They are not. CoverArtFetcher writes to GetTempPath() under the name
"<random>.offstream-cover.jpg" and never to the working directory, and
these were a matched pair of 1024x572 images, which is not the shape
album art comes in.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Sign up for freeto 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.

1 participant

@revtex