Skip to content

Releases: milnet01/album-builder

v0.5.2 — lyrics-pane fill + per-row pause + click-to-preview when idle

30 Apr 18:05

Choose a tag to compare

✅ v0.5.2 — UX fix-pass: lyrics-pane fill + row-button play/pause toggle (2026-04-30)

User-reported UX gaps spotted on the v0.5.1 build:

  1. Lyrics pane was small. LyricsPanel called setFixedHeight(150) and NowPlayingPane finished its QVBoxLayout with addStretch(1) after the panel — together those two pinned the lyrics block to ~150 px and let empty space accumulate underneath. On a tall window the lyrics area looked stranded in the upper third of the right pane.
  2. Per-row ▶ button was load-only. Clicking the row's preview-play button on the currently-playing row reloaded the source from scratch (set_source + play), instead of toggling pause as the transport bar's main play/pause button does. The transport's play/pause button worked correctly — only the per-row button lacked the toggle.

Spec amendments (signed off via 2-pass cold-eyes review, 7 → 0 findings):

  • Spec 06 §user-visible-behavior — per-row preview-play button is now a load-or-toggle control with explicit four-state glyph mapping (PLAYING-on-active-source → Glyphs.PAUSE; PAUSED / STOPPED / ERROR / non-active → Glyphs.PLAY). Adds an ERROR-state same-row-click bullet (re-runs set_source + play()) and an Errors-table row for "active source's file removed between load and a same-row click."
  • Spec 06 test contract — TC-06-15 amended to flag that it supersedes the v0.4.0 signal-only assertion in test_library_pane_emits_preview_play_request; new TC-06-17 (active+playing → pause without reload), TC-06-18 (active+paused → resume), TC-06-19 (per-row glyph mapping with library-pane vs album-order-pane a11y bifurcation + dataChanged-row-range observable for the perf claim).
  • Spec 07 §Lyrics panel — panel now fills the available right-pane height below now-playing metadata, with a 150 px minimum (the pre-amendment fixed value) enforced via setMinimumHeight instead of setFixedHeight. New TC-07-16 with a measurable qtbot assertion (resize(420, 800)lyrics_panel.height() >= 300).

Shipped:

  • ✅ Step 3 — failing tests for TC-06-17/18/19 + TC-07-16 (10 of 11 red on the implementation-side, 1 already passing as the existing cross-row case).
  • ✅ Step 4 — implementation (lifted setFixedHeight(150)setMinimumHeight(150), dropped competing addStretch after the lyrics panel, added LibraryPane.set_active_play_state + AlbumOrderPane.set_active_play_state, routed Player.state_changed + source-swap into both panes from MainWindow, added load-or-toggle dispatch in _on_preview_play).
  • ✅ Step 5/6 — /audit + /indie-review in parallel (1 audit + 5 review findings, all L). Round 1 folded inline: pyright None-guard on album_order_pane.py:173, spec-06 PAUSED-vs-STOPPED split (PAUSED→toggle, STOPPED→fresh-load+restart), TC-06-15 marker disambiguation on the v0.4.0 test, and 2 new TC-06-19 tests for the album-order pane (set_album re-render preservation + set_active call-count observable). Round 2 convergence confirmed clean.
  • ✅ Step 8/9 — flipped status to ✅; commit pending; push pending user OK.

Convergence trace: spec amendments 1-pass cold-eyes review (7 → 0 findings), implementation, post-implementation /audit + /indie-review (6 → 0 findings). 471 → 484 passing tests (+13: 11 new TC-06-17/18/19 + TC-07-16 contracts and 2 round-1-fold-out additions). Ruff clean. Manual smoke-launch on the Tracks/ corpus completed without error.

v0.5.2 follow-up — click-to-preview-when-idle (2026-04-30)

Same release wave; user feedback after the v0.5.2 commit: the running ~/.local-installed app was still v0.4.1 (the install.sh deploy hadn't been re-run after the v0.5.2 source-tree commit), so the lyrics-pane fill + per-row pause weren't observed. Triaged that as a deployment gap (re-run install.sh). Same round, the user requested a new behaviour: clicking a track row (anywhere outside the play / toggle column) should populate the now-playing pane with that track's metadata + lyrics, but only when nothing is playing.

Spec amendments (signed off via 1-pass cold-eyes review, 10 → 0 findings):

  • Spec 06 §user-visible-behavior — new top bullet "Row body click previews-without-playing when idle" with sub-bullets for: hit-zones (library = non-_play / non-_toggle columns; album-order = label area only, drag-handle and play button excluded), keyboard-navigation parity (preview is mouse-click only — Enter routes through a separate _on_table_activated slot that handles only _play/_toggle), app-start state (STOPPED on launch, preview enabled from first click; restored last_played_track_path is not mutated by preview), Player.source() decoupling during preview, hover affordance (PointingHand cursor when STOPPED, Arrow otherwise — applies to both panes' viewports).
  • Spec 06 Errors table — new row for "late state_changed(STOPPED) arrives after preview populated the now-playing pane" (preview metadata wins; state_changed only repaints row glyphs, not the now-playing block).
  • Spec 06 test contract — TC-06-20 (STOPPED → preview), TC-06-21 (PLAYING/PAUSED/ERROR → no-op), TC-06-22 (album-order pane parity), TC-06-23 (last_played_track_path not mutated), TC-06-24 (late STOPPED clobber), TC-06-25 (keyboard nav inert), TC-06-26 (cursor mapping for all four states).

Code changes:

  • LibraryPane — new row_body_clicked signal; _on_table_clicked emits it for non-_play/_toggle columns; new _on_table_activated slot handles keyboard activation (no row-body branch); new set_row_body_cursor_for_state(stopped=) method.
  • _OrderRowWidget — new body_clicked signal; overridden mousePressEvent captures press position; overridden mouseReleaseEvent emits body_clicked only when the press → release delta is within QApplication.startDragDistance() (so genuine drags don't fire previews); label has WA_TransparentForMouseEvents so clicks fall through to the row widget.
  • AlbumOrderPane — new row_body_clicked signal; set_album connects each row's body_clicked to re-emit the row's path; new set_row_body_cursor_for_state(stopped=) mirroring the library pane.
  • MainWindow — new _on_row_body_clicked(path) handler gates on Player.state() == STOPPED, then set_track + _sync_lyrics_for_track; missing-track shows toast (mirroring _on_preview_play); _on_player_state_changed_for_rows flips both panes' cursors and is now also called once at end of __init__ so the construction-time cursor is correct without waiting for a state-change emission.

Audit/review trace: post-implementation /audit + /indie-review (12 → 0 findings, all H+M folded inline). 484 → 500 passing tests (+16: 9 initial TC-06-20..26 contracts + 7 round-1-fold-out additions covering ERROR-state no-op, PAUSED+ERROR cursor, real-mousePress plumbing, play-button-negative case, fresh-LRC lyrics population, Enter-doesn't-preview, construction-time cursor). Ruff clean. Manual smoke pending on ~/.local install (gated on the running v0.4.1 instance being closed first).


v0.5.1 — outstanding-roadmap sweep

30 Apr 16:25

Choose a tag to compare

Same-day follow-up to v0.5.0 (Phase 4: Export & Approval) closing every actionable 📋 item still surviving in ROADMAP.md.

This is also the first true tagged release since v0.4.2 — v0.5.0 (Phase 4 ship) shipped as a feat: commit without a release tag, so v0.5.1 bundles Phase 4 + the sweep.

Phase 4 (v0.5.0) recap

M3U + symlink folder per album, hard-lock approval state, PDF + HTML report generation via WeasyPrint. Specs: 02 / 08 / 09 / 10 / 11. Convergence trace: 4-round pre-implementation spec sweep (39 → 17 → 3 → 0 actionable findings) + 3-round post-implementation /audit + /indie-review (40 → 3 → 0 findings) + full-codebase audit (ruff / bandit / semgrep / gitleaks all clean).

v0.5.1 sweep — 8 items

Audit tooling

  • pyrightconfig.json at repo root pointing pyright at .venv (recovers the 4 mutagen unresolved-import diagnostics on every /audit run).
  • .gitleaks.toml with extends.useDefault = true + path-regexp allowlist for .venv/, __pycache__/, gitignored data dirs (Tracks/, Albums/, .album-builder/).

Code

  • ACCENT_ROLE = Qt.ItemDataRole.UserRole + 2 extracted as module-level constant in library_pane.py — closes the v0.2.1 Tier 2 L6-M2 deferral; mirrors MISSING_ROLE / TITLE_ROLE in album_order_pane.py.
  • _write_settings(data) helper in persistence/settings.py stamps SETTINGS_SCHEMA_VERSION = 1 at every write site so a hand-rolled settings.json lacking schema_version self-heals on next save (closes the final piece of Theme B recurrence; +4 regression tests).

Docs

  • README §Status promoted from v0.2.0 to v0.5.0 prose.
  • README §System dependencies adds WeasyPrint runtime-library install commands (Pango / Cairo / GDK-PixBuf) for openSUSE + Debian/Ubuntu.
  • ROADMAP cross-cutting summary 📋 → ✅ flips for Themes A-H (2026-04-28 indie-review) and Themes B/F/I/J/K/L recurrence (2026-04-30 indie-review), each citing the per-item Tier fix that actually closed it.
  • Phase 4 prep round-1/2/3 section headers flipped 📝 → ✅ to match round-4's already-✅ marker.

Verification

  • 467 → 471 passing tests (+4 schema_version regression tests in tests/persistence/test_settings.py).
  • Ruff clean.
  • 11 skipped (audio integration gates).

Outstanding

Two genuine 📋 deferrals remain in the roadmap, both intentional:

  • DebouncedWriter._timers unbounded growth — bounded by album count today; revisit when high-cardinality keys land.
  • Stale-segment recovery TOCTOU — microsecond race window during owner shutdown; accepted as v1 design.

The §Future / deferred section lists 7 explicit non-v1 features (group-by-artist tabs, tap-along LRC editor, multi-project, album cover compositing, bulk pre-alignment scheduler, light-theme support, recursive subfolder scanning).

Phases 1–4 are feature-complete and hardened.