v1.9.3
A small follow-up release: two crash fixes caught by manual smoke after v1.9.2, two CLI/utility fixes, plus three community PRs.
New features
- Configurable default theme — set
themeunder[ui]inconfig.tomlto control which Textual theme loads on startup (defaultytm-dark). Changing theme viaCtrl+P→Themeupdates the current session only; the startup default is no longer overwritten by session state. To persist the active theme as the new default, useCtrl+P→Theme: Set Current as Default. Thanks
@Villoh (#85). Theme: Set Current as Defaultcommand palette action — saves the currently active theme toconfig.tomlso it becomes the startup default. Includes rollback on save failure (e.g. read-only filesystem) with an error toast.- Discovery round-robin —
Dnow cycles deterministically through Charts → Trending → For You → Liked → Artist → Recently Played (was random source selection). Charts sub-rotates through its shelves between presses, and the Discovery label shows the active source (e.g.Discovery (US Daily Top 100)). Mood source dropped (the Moods & Genres tab was removed in v1.9.1 due to upstream crashes). Thanks
@wgordon17 (#75). - Radio queues prepend their seed tracks — radio playback now starts with the seed before suggestions, matching YouTube Music's native behaviour. Append-mode background refill is unchanged. Thanks

@wgordon17 (#75). - Persistent queue source header — Queue page shows
Generated from: …beneath Now Playing for radio and discovery queues, with up to three seed titles inline and a tooltip for the full list. Toggle via[ui] show_queue_source(default on). Thanks
@wgordon17 (#75).
Fixes
- Crash on Go to Artist / Album / Playlist —
TrackTableand_ArtistAlbumListset up their columns inon_mount, butcontext._build_artist's nested-mount chain callsload_tracks/load_albumssynchronously beforeon_mountfires, soadd_rowran with 0 columns and raisedValueError: More values provided than there are columns. Both widgets now eager-init columns in__init__, eliminating the mount-order race. - Update check version comparison —
_is_newernow usespackaging.version.Versionfor proper PEP 440 comparison. The hand-rolled tuple parser dropped non-numeric chunks and got post-releases (e.g.1.6.0.post1) wrong. ytm configwith multi-arg$EDITOR—EDITOR="code -w"and similar now work; previously the whole string was passed as a single argv entry, so subprocess looked for an executable literally namedcode -wand failed. Malformed quoting (e.g. unbalanced quotes) now exits cleanly via the existing error path instead of crashing.- Search race conditions — fixed four interacting bugs that caused searches to require a second Enter, the suggestion overlay to re-appear on top of incoming results, and selecting a suggestion to cancel the in-flight search worker. Thanks

@wgordon17 (#84).
Changed
- Theme persistence model —
config.tomlis now the authoritative source for the startup theme;session.jsonstores runtime state only and no longer restores the theme on launch. This separates user-authored configuration from app-managed session state. - Command palette provider architecture — app-specific commands moved from
get_system_commands()to a dedicatedYTMCommandProviderregistered inApp.COMMANDS. Isolates command definitions insrc/ytm_player/app/_commands.pyand keeps_app.pyfocused on app logic.
Infrastructure
- Pre-commit hooks + pyright in CI +
dbus-fastmigration — new.pre-commit-config.yamlruns ruff-format / ruff / pyright on commit and pytest on push (pre-commit installto activate). New CI job runs pyright at standard strictness. The Linux MPRIS service migrates from the unmaintaineddbus-nextto the maintaineddbus-fastfork (identical API). Thanks
@wgordon17 (#83). - Settings save Windows fallback —
Settings.save()now falls back to a directpath.write_text()whenos.replace()raisesPermissionError(e.g. whenconfig.tomlis held open by an external editor on Windows).