v0.7.0
This release shipped without written notes.
Changelog
Every release, newest first — 4 in total.
This release shipped without written notes.
v0.6.1 - first downloadable AppImage
First downloadable build. Album Builder now ships as a single-file Linux AppImage - download AlbumBuilder-0.6.1-x86_64.AppImage below, chmod +x, and run it. No Python, no pip, no install script; runs on most Linux distributions from ~2022 onward (glibc 2.35+).
Built inside a pinned Ubuntu 22.04 container and verified to launch + render on a clean system (Phase Dist-2 / Spec 23). Lyric auto-alignment (WhisperX/torch) stays an optional source-install extra, kept out of the bundle to hold the download to a few hundred MB.
The .AppImage asset attaches automatically once the release build finishes (a few minutes).
v0.5.2 — lyrics-pane fill + per-row pause + click-to-preview when idle
User-reported UX gaps spotted on the v0.5.1 build:
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.Spec amendments (signed off via 2-pass cold-eyes review, 7 → 0 findings):
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."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).setMinimumHeight instead of setFixedHeight. New TC-07-16 with a measurable qtbot assertion (resize(420, 800) → lyrics_panel.height() >= 300).Shipped:
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)./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.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.
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):
_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).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).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
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.
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).
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/).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).schema_version regression tests in tests/persistence/test_settings.py).Two genuine 📋 deferrals remain in the roadmap, both intentional:
DebouncedWriter._timers unbounded growth — bounded by album count today; revisit when high-cardinality keys land.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.