From 0d44432fbaeadefb645090fdecef852bea1ea2b6 Mon Sep 17 00:00:00 2001
From: Thales <>
Date: Sun, 20 Sep 2026 07:58:32 +0100
Subject: [PATCH 1/2] Three UI cleanups from the library and the top bar
From #636, the three items I agreed to in that thread. The hamburger and the
note thumbnails are deliberately unchanged, for the reasons given there.
Drop the bullet from each Extract chip. It was a dot in the button's own text
colour, so it said nothing the button was not already saying: a chip changes
border, background and text colour when it is selected. Six of them in a row
read as a bulleted list rather than a row of controls.
Show the library status dot only when it has something to say. A finished
import is the resting state of every row in a settled library, so a green dot
on all of them marked nothing out. Importing, waiting in the queue and
unavailable keep it, and are easier to pick out now that they are the only rows
wearing one. The element stays in the DOM because updateTrackStatus flips it
live without a re-render.
Move the notification bell into the library rail. The top bar is the import and
split controls, so a bell parked at the end of it read as part of that job.
Everything else in the rail is app-wide rather than track-wide, which is what a
notification is.
The panel it opens is fixed rather than absolute, and placed from JS. It cannot
hang off its own wrapper: .sidebar is overflow:hidden and narrows to the rail's
66px when the library is collapsed, which clips an absolutely positioned panel
to nothing in that state. placeNotifPanel puts it level with the bell and clear
of the rail, the same way the search dropdown and the metronome popover are
already placed, and clamps it on a window too short to hang it off the bell at
all.
Horizontal placement measures the rail, not the button. The button is 40px
centred in a 66px column, so measuring its own right edge tucks the panel back
under the rail by the difference.
Both new tests are negative-controlled: restoring either old behaviour fails
them.
---
static/css/daw.css | 32 +++++----
static/index.html | 99 ++++++++++++++-------------
static/js/ui-chrome.js | 34 +++++++++
tests/e2e/library-status-dot.spec.mjs | 50 ++++++++++++++
tests/e2e/report-failure.spec.mjs | 48 +++++++++++++
5 files changed, 202 insertions(+), 61 deletions(-)
create mode 100644 tests/e2e/library-status-dot.spec.mjs
diff --git a/static/css/daw.css b/static/css/daw.css
index 938979bb..efb50bd5 100644
--- a/static/css/daw.css
+++ b/static/css/daw.css
@@ -255,13 +255,6 @@ input, textarea { font-family: inherit; }
background: color-mix(in srgb, var(--color) 14%, transparent);
color: var(--color);
}
-.stem-dot {
- width: 5px; height: 5px;
- border-radius: 50%;
- background: currentColor;
- opacity: 0.85;
-}
-.stem-choice[aria-pressed="false"] .stem-dot { opacity: 0.4; }
/* Automatic song-structure detection. It sits at the end of the composer next
to Split stems and matches its footprint, so the pair reads as two segments
of one bar rather than a chip loose beside a button. Colour still separates
@@ -392,12 +385,17 @@ input, textarea { font-family: inherit; }
.daw-iconbtn:active { transform: scale(0.96); }
/* Notification panel */
+/* The wrapper is still the badge's anchor; the panel is not anchored to it.
+ .sidebar is overflow:hidden and narrows to the rail's 66px when the library
+ is collapsed, so an absolutely positioned panel would be clipped to nothing
+ in that state. Fixed escapes the clip, and placeNotifPanel in ui-chrome.js
+ sets left/top from the bell's rect, the same way the search dropdown and the
+ metronome popover are placed. No coordinates here on purpose: the only code
+ that shows this panel is the code that positions it. */
.daw-notif-wrap { position: relative; }
.daw-notif-panel {
display: none;
- position: absolute;
- top: calc(100% + 8px);
- right: 0;
+ position: fixed;
width: 280px;
background: var(--panel-2);
border: 1px solid var(--border-strong);
@@ -772,9 +770,14 @@ input, textarea { font-family: inherit; }
.cat-meta { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
.cat-title { color: var(--fg); font-size: 12px; font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cat-sub { color: var(--muted); font-size: 10.5px; display: flex; gap: 5px; white-space: nowrap; overflow: hidden; }
-.cat-status { width: 6px; height: 6px; border-radius: 50%; background: #5fbc56; flex-shrink: 0; }
-.cat-status.processing { background: var(--accent); animation: cat-pulse 1.4s infinite; }
-.cat-status.unavailable { background: #666; }
+/* Shown only when it has something to say (#636). A finished import is the
+ resting state of every row in the library, so a green dot on all of them
+ marked nothing out: it was one more thing to read past on the way to the
+ title. The states that still carry information keep the dot, which is also
+ what makes them visible now that they are the only rows wearing one. */
+.cat-status { display: none; width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0; }
+.cat-status.processing { display: block; background: var(--accent); animation: cat-pulse 1.4s infinite; }
+.cat-status.unavailable { display: block; background: #666; }
.cat-item.unavailable { cursor: pointer; }
.cat-item.unavailable .cat-title { opacity: 0.55; }
.cat-unavailable-warning { color: var(--accent); font-weight: 600; opacity: 1; }
@@ -791,6 +794,9 @@ input, textarea { font-family: inherit; }
.cat-item.queue-waiting .cat-thumb { opacity: 0.55; }
.cat-item.queue-waiting .cat-title { color: var(--muted); }
.cat-item.queue-waiting .cat-status {
+ /* Explicit, not inherited from .processing: a waiting row is only styled
+ through its parent, so it must turn the dot on for itself. */
+ display: block;
background: transparent;
box-shadow: inset 0 0 0 1.5px var(--muted);
animation: none;
diff --git a/static/index.html b/static/index.html
index f432726d..2ea9c7a5 100644
--- a/static/index.html
+++ b/static/index.html
@@ -81,7 +81,7 @@