feat(qt): collapsible rows in the My QBZ detail, and ephemeral leaves no trace

**The accordion the owner asked for.** Each expandable row gets a chevron;
clicking it opens THAT row's tracks inline and nothing else. Verified by driving
the real app: in list mode every row is collapsed with the chevron pointing
right, clicking row 3 — a PLEX row, so the hybrid path — flips its chevron,
renders its three tracks with durations and quality badges, and leaves rows 1,
2, 4 and 5 untouched.

This is a DELIBERATE divergence from both references, decided by the owner with
the facts in hand: neither Slint nor Tauri has a per-row accordion. Both render
inline tracks only as a view MODE, for every row at once, and both say "no
chevron" in their own comments (MixtapeDetailView.slint:1139,
MixtapeCollectionDetailView.svelte:124-127). The owner first believed Tauri had
it, asked for the code to be checked rather than the comment, and on the
evidence chose the accordion anyway. Do not "restore parity" by removing it.

Built additively on machinery that already existed: the document already carried
`canExpand` / `inlineTracks` / `expandLoading` / `tracksLoaded`. What was added
is per-row OPEN state in Rust, a toggle invokable, and lazy per-row fetching —
opening a row fetches only its tracks, once. The "expanded" view mode survives
and now means "open all", sharing ONE notion of open rather than a second
parallel state.

Also here:
- `body`'s `anchors.bottomMargin: 100` shrank the list VIEWPORT; in the .slint
  that 100px is padding INSIDE the Flickable, i.e. scroll runway. Measured on
  screen before the fix: the last row clipped mid-height with ~85px of dead
  band under it.
- `apply_resolved` cloned and serialised the WHOLE document per resolved item
  (34 serialize→parse→patch cycles for one collection, 200 for a big one, on the
  GUI thread), and `patch_expanded` did the same while the document carried
  every loaded item's inline tracks — O(n²).
- The DEAD affordances the audit filed against the modals and the grids.

**Ephemeral honours its name** (owner rule): an ephemeral folder plays and
queues, and writes nothing durable. Recently Played and Most Played are now
guarded — an ephemeral album played through would previously have outranked real
albums in Most Played, one row per track start. The Slint reference does NOT
have this exemption; that is a gap in both frontends, not a Qt regression, and
this does not restore parity by removing it.

Two stores were deliberately NOT changed and need the owner's word: the
scrobblers and Discord presence. Neither is "las bibliotecas" — nothing is
stored on our side — but both publish the track to a third party, so if "ni now
playing ni nada" is meant literally they belong in the same decision. The
one-line fix location is in the lane report.

cc
This commit is contained in:
vicrodh committed 2026-07-30 18:41:06 -06:00
1 parent b6cf06ff2a
commit 83c1aec4f5
10 files changed
+912 -101

No files matched your search

+8 -1
View File
@@ -315,7 +315,14 @@ Item {
QbzTextArea {
visible: editPanel.isDescription
width: parent.width
height: visible ? 96 : 0
// A FLAT 96, never `visible ? 96 : 0`: a Column does not
// position invisible children, so the hidden arm already
// costs no layout height, while a 0-height text area
// hands its inner Flickable height = 0 - 8 - 8 = -16 —
// the silent negative-size trap QbzLineEdit.qml:166-167
// documents. The sibling QbzLineEdit is single-line and
// lands at a clean 0, so it keeps its ternary.
height: 96
wrapMode: TextEdit.WordWrap
text: editPanel.draftDescription
onEdited: function (v) { editPanel.draftDescription = v }