From b0f35eabc9b94618f34252323c1bd1030a56ea0a Mon Sep 17 00:00:00 2001
From: Frank Stellmacher <171614930+Psychotoxical@users.noreply.github.com>
Date: Thu, 4 Jun 2026 11:16:48 +0200
Subject: [PATCH] fix: usable layout when the window is small (#981)
* fix(layout): collapse browse toolbar to icons on compact width
On a narrow window the labelled filter buttons wrapped into several rows
and, being a fixed-height sticky header, starved the album grid below them
down to a clipped strip. Wrap each toolbar label in a hideable span and drop
to icon-only in compact (mobile) mode; icons keep their tooltip and
aria-label so the action stays discoverable.
* fix(window): raise minimum window size to 520x640
The old 360x480 floor let the window shrink far past the point the layout
holds together. Floor it at a laptop-friendly size where the compact layout
(icon toolbar + scrollable lists) still works.
* fix(player): scale mobile cover to fit short windows
The cover width was viewport-derived with no height bound, so on a short
window the square grew past its slot and overlapped the title. Bind it to
the shrinkable wrap via max-height and keep it square with auto sizing.
* docs: changelog for small-window layout fixes (#981)
---
CHANGELOG.md | 8 ++++++++
src-tauri/tauri.conf.json | 4 ++--
src/components/GenreFilterBar.tsx | 2 +-
src/components/LosslessFilterButton.tsx | 2 +-
src/components/SortDropdown.tsx | 2 +-
src/components/StarFilterButton.tsx | 2 +-
src/components/YearFilterButton.tsx | 2 +-
src/pages/Albums.tsx | 22 ++++++++++++----------
src/styles/layout/cover-art.css | 11 +++++++++--
src/styles/layout/main-content.css | 9 +++++++++
10 files changed, 45 insertions(+), 19 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0cca34cb..4db1811f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -885,6 +885,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* Suggested Songs now show the favorite heart and star rating like the playlist above, so the Favorite/Rating columns no longer leave an empty gap.
* Tracks with several artists split into individually clickable names, matching the rest of the app — and reading the same before and after you add the track.
+### Small windows — usable layout when scaled down
+
+**By [@Psychotoxical](https://github.com/Psychotoxical), reported by zunoz on Discord, PR [#981](https://github.com/Psychotoxical/psysonic/pull/981)**
+
+* Browse toolbars (Albums, Artists, …) collapse their filter buttons to icons on a narrow window instead of wrapping into rows that pushed the list off-screen; hover or focus still shows each button's label.
+* The minimum window size is a touch larger so the layout can no longer be shrunk past the point where it breaks.
+* On a short window the Now Playing cover scales down to fit instead of overlapping the track title.
+
## [1.46.0] - 2026-05-18
> **🙏 Special thanks to [@zz5zz](https://github.com/zz5zz)** for his tireless quirk-spotting and bug reports on the [Psysonic Discord](https://discord.gg/AMnDRErm4u) — several of the polish fixes in this release landed directly off the back of his messages.
diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json
index b36e9b5c..37a1f0da 100644
--- a/src-tauri/tauri.conf.json
+++ b/src-tauri/tauri.conf.json
@@ -16,8 +16,8 @@
"title": "Psysonic",
"width": 1280,
"height": 800,
- "minWidth": 360,
- "minHeight": 480,
+ "minWidth": 520,
+ "minHeight": 640,
"resizable": true,
"fullscreen": false,
"decorations": true,
diff --git a/src/components/GenreFilterBar.tsx b/src/components/GenreFilterBar.tsx
index 309e3eb6..85bb94be 100644
--- a/src/components/GenreFilterBar.tsx
+++ b/src/components/GenreFilterBar.tsx
@@ -158,7 +158,7 @@ export default function GenreFilterBar({
style={{ display: 'flex', alignItems: 'center', gap: '0.4rem' }}
>
- {t('common.filterGenre')}
+ {t('common.filterGenre')}
{count > 0 && {count}}
{count > 0 && }
diff --git a/src/components/LosslessFilterButton.tsx b/src/components/LosslessFilterButton.tsx
index e38d4f53..3d87e87b 100644
--- a/src/components/LosslessFilterButton.tsx
+++ b/src/components/LosslessFilterButton.tsx
@@ -25,7 +25,7 @@ export default function LosslessFilterButton({ active, onChange }: Props) {
}}
>
- {t('albums.losslessLabel')}
+ {t('albums.losslessLabel')}
{active && onChange(false)} />}
);
diff --git a/src/components/SortDropdown.tsx b/src/components/SortDropdown.tsx
index 0a0790eb..d4a118c8 100644
--- a/src/components/SortDropdown.tsx
+++ b/src/components/SortDropdown.tsx
@@ -98,7 +98,7 @@ export default function SortDropdown({ value, options, onChang
style={{ display: 'flex', alignItems: 'center', gap: '0.4rem' }}
>
- {current?.label ?? value}
+ {current?.label ?? value}
{open && createPortal(
diff --git a/src/components/StarFilterButton.tsx b/src/components/StarFilterButton.tsx
index f4c2e3e6..21b75434 100644
--- a/src/components/StarFilterButton.tsx
+++ b/src/components/StarFilterButton.tsx
@@ -62,7 +62,7 @@ export default function StarFilterButton({ active, onChange, size = 'default' }:
}}
>
- {t('common.favorites')}
+ {t('common.favorites')}
{active && onChange(false)} />}
);
diff --git a/src/components/YearFilterButton.tsx b/src/components/YearFilterButton.tsx
index 3209d719..40c63598 100644
--- a/src/components/YearFilterButton.tsx
+++ b/src/components/YearFilterButton.tsx
@@ -144,7 +144,7 @@ export default function YearFilterButton({
}}
>
- {active && activeLabel ? activeLabel : t('albums.yearFilterLabel')}
+ {active && activeLabel ? activeLabel : t('albums.yearFilterLabel')}
{active && }
diff --git a/src/pages/Albums.tsx b/src/pages/Albums.tsx
index b8b163cf..f6b689af 100644
--- a/src/pages/Albums.tsx
+++ b/src/pages/Albums.tsx
@@ -365,17 +365,17 @@ export default function Albums() {
{selectionMode && selectedIds.size > 0 ? (
<>
-
diff --git a/src/styles/layout/cover-art.css b/src/styles/layout/cover-art.css
index 315c30db..0f9564b4 100644
--- a/src/styles/layout/cover-art.css
+++ b/src/styles/layout/cover-art.css
@@ -8,8 +8,15 @@
}
.mp-cover {
- width: min(calc(100vw - 28px), 55vh);
- max-width: 440px;
+ /* Largest square that fits BOTH the viewport width and the height the flex
+ column leaves for the cover. `max-height: 100%` ties the image to its
+ (shrinkable) wrap, so on a short window it scales down instead of growing
+ past its slot and overlapping the title (zunoz, RC2 small-window report).
+ `width/height: auto` + `aspect-ratio` keep it square while it shrinks. */
+ max-width: min(calc(100vw - 28px), 440px);
+ max-height: 100%;
+ width: auto;
+ height: auto;
aspect-ratio: 1;
object-fit: cover;
border-radius: 14px;
diff --git a/src/styles/layout/main-content.css b/src/styles/layout/main-content.css
index 09c9079b..c0399614 100644
--- a/src/styles/layout/main-content.css
+++ b/src/styles/layout/main-content.css
@@ -180,6 +180,15 @@
min-height: 0;
}
+/* Compact mode: the sticky toolbar is `flex-shrink: 0`, so labelled filter
+ buttons wrap into several rows on a narrow window and starve the `flex: 1`
+ list below them, clipping the album grid (zunoz, RC2). Collapse the toolbar
+ buttons to icon-only (label hidden, icon + tooltip + aria-label kept) so the
+ row stays short and the list keeps its height. */
+.app-shell[data-mobile] .mainstage-inpage-toolbar .toolbar-btn-label {
+ display: none;
+}
+
.mainstage-inpage-scroll__viewport {
padding-bottom: var(--space-6);
padding-right: var(--space-6);