Let the user put a panel away, and make the lanes actually fit - #500
Merged
Conversation
added 5 commits
August 30, 2026 17:05
The lanes were sized once, when a track loaded, and never again. Everything the
panel gained after that became empty space under them rather than taller lanes.
Resizing the window was enough to show it. On a 1366x768 window the panel is
370px and each lane 72px; drag the window to 1200px tall and the panel becomes
802px while the lanes stay at 72, so 432px of it goes unused. The bigger the
display, the more of it the waveforms refused to occupy.
Three things had to change for the lanes to actually follow.
_applyLaneHeight runs again on a resize, from an observer on the wave panel. The
panel is flex: 1 inside a fixed-height column, so its height comes from its
parent and never from the lanes: writing lane heights from that callback cannot
feed it its own output.
.waves-column takes a floor from the stack height that function already
computes. Its natural height is the multitrack's, whose lane height is fixed
when the tracks are created, so without a floor the column stayed 432px however
much room it was given and the lanes distributed across the old size.
And the height maths allows for the separators. It divided the whole panel by
the lane count, but the stack is lanes plus the 2px between them, so it
overshot by exactly that. Invisible for as long as the lanes sat at their 70px
floor and overflowed anyway; it surfaces as a 7px scrollbar the moment they fit.
The streaming path keeps its load-time height deliberately. Its lanes are
WaveSurfer canvases, and setOptions({height}) only re-renders the ones that have
audio, so a lane for a stem the user did not extract keeps the old size: 93, 93,
93, 70, 70, 93 on a six-lane job with two empty, against mixer rows all at 95. A
mixer column out of step with its waveforms is worse than unused space, so the
refit is limited to the path where the SVG overlay is what you see.
Closes #497
Six stems separated and two visible: on a 1366x768 laptop the panels around the mixer took roughly two thirds of the window and the mixer got what was left (#480). Making them smaller only moved the number, which is why that issue was left open after the last round trimmed 116px out of them. The reporter had already said what the actual problem was: every one of those panels shows something useful [...] The issue is that they are all mandatory at all times. I do not need the presence percentages while I am setting fader levels, and I do not need the waveform while I am reading the analysis. Right now the app has no way to say that. So this is a way to say it. Three toggles collapse the analysis header, the sections bar and the footer timeline, each handing its height straight to the mixer: default mixer 370px 5 of 6 lanes scrolls analysis off mixer 443px 6 of 6 lanes fits all three off mixer 573px 6 of 6 lanes fits One click on Analysis is enough. Together they are worth 202px. They collapse to nothing rather than to a stub, which is only possible because the controls are somewhere else: a stub tall enough to hold its own control costs most of what collapsing the smaller panels returns. So all three live in one row under the composer, sharing the right edge and the exact width of the two controls above them, from the same --composer-action-w those are sized from. A longer translation widens all three together instead of leaving the row ragged. The topbar does not grow: it already carried 27px of slack around a 50px composer, and the row fits inside it. Legible means the panel is there, struck through means it is put away. The other way round -- highlighting the hidden ones -- inverts what a pressed toggle looks like everywhere else and makes the default state the loud one. A class on .app and a flag in localStorage each, the same shape as the sidebar collapse, with no state anywhere else. This does not decide what the studio shows on first open. Every panel is still there by default, which is a separate question from whether the user can put one away. Closes #480
The mixer strip and the waveform lanes are two columns of the same rows, and nothing made them agree. Resize the window and they walked apart, worst at the bottom: at 1600x768 after a resize from 900, mixer rows sat 72px apart, waveform rows 83px apart, and the last pair was 55px out of line. By the last stem the label was most of a row away from the waveform it names. They were built from different numbers. The mixer stack is count * --lane-h, recomputed from the panel height. The waveform column took its height from the multitrack, whose lane height is fixed when its tracks are created, so after a resize the column was still the size it was at load and its rows distributed across that. The computed stack said 432px; the column was 498. The column is now that same count * --lane-h. The two agree by construction rather than by coincidence, and the multitrack is no longer the source of truth for a height it cannot change after the fact. Two smaller errors in the same place, both of which only ever pushed the same way. Every mixer row draws a 2px bottom border while the stack counted count - 1 separators, so the row is now the shared unit and both columns get the identical total. And the stem name sat 7px above the row's centre line, because the name and its meter are stacked and centred as a pair while the waveform beside them is centred on the line itself; the name centres now and the meter hangs below it. Zero row drift across twelve window heights from 720 to 1300, including the range where the lanes overflow rather than fit, which is where the 55px came from. Name drift is 1px of rounding. All three new tests were checked against the unfixed build, which fails them. Closes #498
…rence Switched on once, it stayed on: for that import, for every import after it, and for every session until someone remembered to switch it off. It is stored server-side, so it survived a restart and applied to anyone else pointed at the same StemDeck. The pass it enables costs minutes of CPU per import, and a setting that quietly keeps spending that long after the song it was turned on for is a cost nobody chose. It is off at startup now, and off again as soon as the studio loads a song. The button and the setting move together, because the setting is what the runner reads: clearing only the button would leave the next import still paying for a pass nobody asked for. That alone would have introduced a worse bug than it fixed. The runner read the setting when it reached the sections stage, which is the last thing the pipeline does, and there was a comment explaining that this was deliberate: it let a change apply to the next job without a restart. Correct for a preference, wrong for a per-import choice. With the toggle clearing itself on the next song, a user who switched it on, started an import and browsed to another track while waiting would have lost the pass they asked and waited for, silently: job done, no sections, no explanation. So the decision moves to the moment the user makes it. Job carries the flag, captured at creation from the setting, at all three places a job is created -- URL submit, file upload, and playlist import, where one read covers the batch so every job in it agrees. The stage reads the job. The runner tests set the flag on the job rather than patching a setting the runner no longer reads. Two new ones cover the thing that is easy to regress: the API captures the setting at submit, and the stage honours the job's flag even when the live setting says otherwise. Closes #499
The backend half of that change is tested: the flag is captured at submit, and the stage honours the job's flag over the live setting. The browser half was not, and it is the half that regresses quietly -- a button that looks off while the setting behind it is still on would leave the next import paying for a pass nobody asked for. So both assertions check both. Checked against a build with the reset removed, which fails them.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Four problems around the mixer, found by pulling on one thread: #480 asked for a
way to reduce the panels around it, and building that exposed three defects the
panels had been hiding.
Addresses #480, which stays open: this is a way to reclaim the space, not a
decision about what the studio shows on first open, and collapsing is all or
nothing per panel where the report asked for one row of it.
Closes #497
Closes #498
Closes #499
Put a panel away when you are not using it (#480, partial)
#480 was not asking for smaller panels. Its reporter said so directly:
So this is a way to say it. Three toggles collapse the analysis header, the
sections bar and the footer timeline, each handing its height to the mixer:
One click on
Analysisis enough. Together they are worth 202 px.They collapse to nothing rather than to a stub, which is only possible because
the controls live somewhere else: a stub tall enough to hold its own control
costs most of what collapsing the smaller panels returns. So all three sit in one
row under the composer, sharing the right edge and the exact width of the two
controls above them, from the same
--composer-action-wthose are sized from.The topbar does not grow to hold it: it already carried 27 px of slack around a
50 px composer.
Legible means the panel is there, struck through means it is put away. The other
way round inverts what a pressed toggle looks like everywhere else, and makes the
default state the loud one.
This does not decide what the studio shows on first open. Every panel is
still there by default. Whether that default is right is a separate question.
Lanes never fitted the panel (#497)
Found by collapsing a panel and watching the space go nowhere. The lanes were
sized once, at track load, and never again.
Window resizing already showed it, with no toggles involved: on a 1366x768
window the panel is 370 px and each lane 72 px; drag the window to 1200 px tall
and the panel becomes 802 px while the lanes stay at 72. 432 px unused. The
bigger the display, the more of it the waveforms refused to occupy.
Three things had to change for the lanes to follow: the fit runs again from an
observer on the wave panel,
.waves-columntakes a floor from the stack heightrather than the multitrack's frozen lane height, and the maths allows for the
2 px separators it had been ignoring.
A name and its waveform drifted apart (#498)
The mixer strip and the waveform lanes are two columns of the same rows, and
nothing made them agree. At 1600x768 after a resize from 900: mixer rows 72 px
apart, waveform rows 83 px apart, the last pair 55 px out of line. By the
last stem the label was most of a row from the waveform it names.
They were built from different numbers. The mixer stack is
count * --lane-h,recomputed from the panel; the waveform column took its height from the
multitrack, whose lane height is fixed when its tracks are created. The computed
stack said 432 px and the column was still 498.
Two smaller errors in the same place, both pushing the same way: every mixer row
draws a 2 px bottom border while the stack counted
count - 1separators, andthe stem name sat 7 px above the row's centre because it and its meter are
centred as a pair while the waveform is centred on the line itself.
Zero row drift now across twelve window heights from 720 to 1300, including the
range where lanes overflow rather than fit, which is where the 55 px came from.
Song structure was a preference (#499)
Switched on once it stayed on: for that import, for every import after it, and
for every session until someone remembered. Stored server-side, so it survived a
restart and applied to anyone else pointed at the same StemDeck. The pass costs
minutes of CPU per import.
It is off at startup now, and off again as soon as the studio loads a song. The
button and the setting move together, because the setting is what the runner
reads.
That alone would have introduced a worse bug than it fixed. The runner read
the setting when it reached the sections stage, the last thing the pipeline does,
and a comment said that was deliberate. Correct for a preference, wrong for a
per-import choice: a user who switched it on, started an import and browsed to
another track while waiting would have lost the pass they asked and waited for.
Silently, with no sections and no explanation.
So the decision moved to the moment the user makes it. The job carries the flag,
captured at creation, at all three places a job is created -- URL submit, file
upload, and playlist import, where one read covers the batch.
Verification
83 browser tests pass, five of them new. Backend 845 pass; the 14 failures are
pre-existing and reproduce on
main, nine of them because this machine has noffmpeg.
Every new guarantee was checked against a build with the fix removed: the three
alignment tests, and both toggle tests.
i18n complete at 474 keys across all nine tables.
ruff check,ruff format --checkandnode --checkclean.Before merging
uv.lockuntouched, so the desktop in-app updater is unaffected.0.15.2: it moves after a release ispublished with all its assets, not at merge time.