From 95c33e0b0fcbeb8d0b505cab70a753ae8bf67423 Mon Sep 17 00:00:00 2001 From: npeter83 Date: Wed, 2 Sep 2026 03:45:07 +0200 Subject: [PATCH] refactor(playlists): compact the playlist header to two rows (E9 12A) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The header portals into the shell's FIXED toolbar band, so its height is list height the user never gets back — and it was spending four stacked blocks on it: title, meta line, an action row, then a separate view-control row under the cover. Option B collapses that into two tight rows beside a smaller cover: title + rename/YouTube-link on the first, with Play-all, push/revert/delete and the unsynced badge pushed right; the meta line (count · playtime · filtered) and the view controls (group-by, reset view, save order, view status, undo/redo) sharing the second. Every control and its gating is unchanged — the view-control group still appears only for `items.length > 1 || viewActive`, so a one-item playlist shows just the meta line. Measured on a 32-item playlist: the band goes 170px -> 88px at rest (122px while a view is active, where the status sentence wraps), which is ~80px more visible list. --- frontend/src/components/Playlists.tsx | 312 +++++++++++++------------- 1 file changed, 162 insertions(+), 150 deletions(-) diff --git a/frontend/src/components/Playlists.tsx b/frontend/src/components/Playlists.tsx index 27ed8765..de3e04e6 100644 --- a/frontend/src/components/Playlists.tsx +++ b/frontend/src/components/Playlists.tsx @@ -606,173 +606,185 @@ export default function Playlists() { {/* Header (title, actions, view controls) is fixed chrome — it portals into the shell's fixed band, so only the video list scrolls. */} -
-
-
+ {/* Two tight rows beside a small cover: title + actions on the first, the meta line and the + view controls sharing the second. This band is FIXED chrome (it never scrolls), so every + row it sheds is list height won back — the old layout stacked three rows next to the + cover plus a fourth block under it, for roughly twice this height. */} +
+
+
{items[0]?.thumbnail_url && ( )}
- {renaming ? ( - setRenameValue(e.target.value)} - onBlur={saveRename} - onKeyDown={(e) => { - if (e.key === "Enter") saveRename(); - if (e.key === "Escape") setRenaming(false); - }} - aria-label={t("playlists.rename")} - className="text-lg font-semibold bg-surface border border-border rounded-md px-2 py-0.5 focus:outline-hidden focus:ring-2 focus:ring-accent/40" - /> - ) : ( -
-

{plName(detail)}

- {editable && ( - + )} + {detail.yt_playlist_id && ( + + + + )} + + )} +
+ {linked && detail.dirty && ( + - + + {t("playlists.unsynced")} + + )} + + {editable && canWrite && ( + )} - {detail.yt_playlist_id && ( - - - + + + )} + {editable && ( + )}
- )} -
- {t("playlists.itemCount", { count: items.length })} - {totalSeconds > 0 && ` · ${formatRuntime(totalSeconds)}`} - {filterActive && ` · ${t("playlists.filteredCount", { count: shown.length })}`}
-
- - {editable && canWrite && ( - )} - - )} - {linked && detail.dirty && ( - - )} - {editable && ( - - )} - {linked && detail.dirty && ( - - - {t("playlists.unsynced")} - + {canSaveOrder && ( + + )} + {/* ONE sentence that names the state. Three loose texts used to pile up here + (reset link + save hint + "reordering is off"), and none of them answered the + question a user actually has — "did I just change my playlist?". Worse, + sorting a list whose stored order ALREADY matches the sort produced a + warning, no Save button and no visible change, which reads as a malfunction. + Now the line says which of the three situations you are in; the disabled grip + still explains itself on hover/focus. */} + {viewActive && {viewStatus}} + {canEdit && ( +
+ +
+ )} + )}
- - {(items.length > 1 || viewActive) && ( -
- - {viewActive && ( - - )} - {canSaveOrder && ( - - )} - {/* ONE sentence that names the state. Three loose texts used to pile up here (reset - link + save hint + "reordering is off"), and none of them answered the question a - user actually has — "did I just change my playlist?". Worse, sorting a list whose - stored order ALREADY matches the sort produced a warning, no Save button and no - visible change, which reads as a malfunction. Now the line says which of the three - situations you are in; the disabled grip still explains itself on hover/focus. */} - {viewActive && {viewStatus}} - {canEdit && ( -
- -
- )} -
- )}