fix(qt): break the activeTab binding loop, and time the ephemeral open

BINDING LOOP. The Binding's `when` read `viewLoader.item.activeTab` —
through `typeof`, which still registers a dependency — while the Binding
WRITES that same property. Every flyout navigation logged

  QML Binding: Binding loop detected for property "when"

Pre-existing, not from the two-phase commit: reproduced on the
pre-2026-08-21 binary by navigating the same way, where it logs from the
same Binding at its own line number. The guard now lives in a handler on
the Loader's `item`, which runs once per mount and registers no
dependency. Verified by log: 0 "Binding loop", and 0 QML complaints of
any kind on a full navigation pass.

EPHEMERAL OPEN — INSTRUMENTED, NOT SOLVED. Two timers split the work
either side of the parallel read phase, because the remaining ~9 s has no
confirmed owner. The owner reports fooyin opening the SAME NAS folder
near-instantly, which rules out the network as the ceiling and makes this
our code — the obvious suspect is that we read each file's tags in
`extract` and then open and read it AGAIN in `extract_artwork`, plus
decode and re-encode a thumbnail, where a grid-only player needs none of
that up front. That is a design change, not a tweak, so it is measured
rather than guessed at.

The numbers arrive on the next real open ("[ephemeral][perf] parallel tag
reads" / "serial bookkeeping"). I could not trigger one from the test
harness: the entry point is a native folder picker, and the rehydrate
path did not fire from the surface the harness can reach.

cc
This commit is contained in:
vicrodh committed 2026-08-22 11:04:23 -06:00
1 parent 2f990c0890
commit 1c1ea00f6b
2 files changed
+35 -2

No files matched your search

+12
View File
@@ -282,6 +282,7 @@ impl EphemeralLibraryState {
// `std::thread::scope`, not a new dependency: this is I/O-bound, the
// work items are independent, and borrowing `scan.audio_files` across
// the scope needs no Arc.
let t_probe = std::time::Instant::now();
let probed: Vec<(std::path::PathBuf, Result<LocalTrack, LibraryError>)> = {
let files: &[std::path::PathBuf] = &scan.audio_files;
let workers = std::thread::available_parallelism()
@@ -316,6 +317,12 @@ impl EphemeralLibraryState {
out.into_iter().flatten().collect()
};
log::info!(
"[ephemeral][perf] parallel tag reads: {:?} for {} files",
t_probe.elapsed(),
probed.len()
);
let t_rest = std::time::Instant::now();
for (audio_file, (canonical_audio, extracted)) in
scan.audio_files.iter().zip(probed.into_iter())
{
@@ -405,6 +412,11 @@ impl EphemeralLibraryState {
}
}
log::info!(
"[ephemeral][perf] serial bookkeeping (artwork + ids): {:?}",
t_rest.elapsed()
);
// Musical order (album, then disc/track/title — same as the DB-backed
// folder view): the extraction order above is readdir order, which is
// arbitrary. Ids must FOLLOW the display order because