Rebuild the studio's fixed chrome so the mixer gets the room - #491
Merged
Conversation
added 2 commits
August 29, 2026 02:30
The panels above and below the mixer took roughly two thirds of a 1366x768 window and the mixer got what was left, which was two of six stem lanes (#480). Working through that turned up four separate problems, all in the same few files, so they land together. Analysis header, 147px to 73px (#487). Each metadata card was three stacked text lines; the qualifier (Natural Minor, Peak 3.5 dB, High) now sits on the value's own line, and the presence cards put their label and percentage side by side rather than one over the other. Type came down with it, 22px to 17px and 28px to 18px. Halving it was a question of how many lines a card is, not how big its text is. Both header rows are now grids of the same seven columns, so their dividers land on the same pixels (#488). Six presence stems fill seven columns because Vocals takes two; once the lead/backing split has run it is replaced by two cards and the row becomes a plain 7x1. A flex row and a grid round fractional track widths differently, which is why the top row is a grid now too: it was a pixel out of step at most window widths. Those two new cards had nothing to show. split_vocals never touched stem_presence, so neither half had a figure anywhere. Presence is a stem's RMS as a percentage of the loudest stem in the job and that reference is not stored, only the percentages are, so the obvious fix is decoding all eight stems again to place two of them on a scale the other six already sit on. presence_for_split recovers the reference arithmetically instead: loudest = rms[vocals] / (presence[vocals] / 100) merge_stem_peaks already measured RMS for the new stems in its existing pass and threw it away; it returns it now, and vocals rides along in the same pass. One extra file scanned. With no reference recorded it returns nothing and the cards read "--", which is honest. The footer is one line, always (#489). Its clusters sat in a wrapping flex row, so the click track either shared the line, took its own, or stayed and split its own options across two rows: the footer was never the same shape twice. Nothing wraps and nothing shrinks now, and a window too narrow to hold the row scrolls it sideways. The row is top-aligned too, so every label sits on one line no matter how tall the controls under it are; bottom-aligned, the click track threw its own label a row above the other four. That also settled the dividers. They were hidden when stranded at the end of a wrapped line, but bottom-alignment meant the check never matched and none were ever drawn. With no wrapping none can be stranded, so syncFooterDividers and the is-orphan rule are gone rather than left as logic that cannot fire. Footer height, 212px to 170px. The min-height dated from when the click cluster could wrap and a rigid height would have clipped it. Its content needs 170, so the floor only padded the footer past itself and the surplus pooled as a gap under whichever tier came last. The track identity moved to the bottom of the library sidebar (#490), pinned below the list so it stays put while the folders and Tags scroll. It was holding a 300px column of the footer to answer a different question from everything beside it. Its footer-* classes came with it as track-panel-* and track-chip-*: they no longer live in the footer, and the old names would have misdirected the next reader. The panel matches the footer's height exactly, measured live by syncTrackPanelHeight, because the footer's height is not a constant. The 300px the footer loses stays reserved, because the timeline's left edge has to land on the lane waveforms' left edge for a position to read at the same x in both. Export Mix fills that gutter rather than leaving it empty. The gutter's width is the offset, so the alignment and the button placement are one fact instead of two numbers that have to agree. The rail reads Favorites, Library, Queue, Trash. Queue was below Trash, an active state filed under an archive. Five of six lanes are visible at 1366x768 now, against two before. The mixer still scrolls, so #480 stays open.
Moving the identity block to the library sidebar freed the footer's column, but the 300px gutter beside the timeline still had to exist: the timeline's left edge lands on the lane waveforms' left edge so a position reads at the same x in both. Export Mix filled it, which left the identity in a sidebar panel whose height had to be measured and mirrored, and put the loaded track's name in the column the user reads to choose a different one. So the identity is back where it started, in the gutter, but in the tier that gutter belongs to rather than as a full-height column beside the controls. The controls keep the left edge they gained, and the block is one tier tall instead of two: cover and title on a line with the favourite, the meta line under them. 79px against the sidebar panel's 141. Export Mix moves to the right of the controls row. It acts on the track rather than on playback, so it sits opposite the clusters instead of filed among them, and its menu now opens toward the middle of the window: anchored left, a 260px menu on the footer's right edge would hang off it. That placement needed the row split. The clusters scroll sideways when the window cannot hold them, and anything after them would scroll off with them, so .footer-clusters takes the overflow and the actions sit outside it. Export Mix stays reachable at any width. The favourite rides the title line rather than going with Export Mix. It marks the track, so it belongs with what it marks. With the sidebar panel gone, syncTrackPanelHeight and --daw-footer-h go too: nothing needs to mirror the footer's height any more.
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 in the studio's fixed chrome, all found while measuring where the
mixer's vertical budget goes.
Closes #487
Closes #488
Closes #489
Closes #490
Measured, at 1366x768
The resolution #480 was reported at, library already collapsed.
#480 stays open. The mixer still scrolls at six stems, and that issue asks
for a way to reduce these panels rather than for them to be smaller once. This
is the cheapest part of its budget won back, not the answer to it.
The analysis header (#487)
Each metadata card was three stacked text lines. The qualifier now sits on the
value's own line,
E min Natural Minor, and the presence cards readVOCAL PRESENCE 66%on one line rather than stacked. Type came down with it,22 px to 17 px and 28 px to 18 px.
Halving it was a question of how many lines a card is, not how big its text is.
Three stacked lines have a floor: 10 px labels are already at the bottom of what
is readable.
The header's two rows (#488)
Seven metadata cards sat above six presence cards, each unaware of the other, so
their dividers landed at different x all the way across. Both rows are now grids
of the same seven columns. Six presence stems fill seven because Vocals takes
two; once the lead/backing split has run it is replaced by two cards and the row
becomes a plain 7x1. Verified: every bottom edge lands on a top edge, in both
states.
The top row had to become a grid too. Flex and grid round fractional track widths
differently, and as a flex row it was a pixel out of step at most widths.
Those two new cards had nothing to show.
split_vocalsnever touchedstem_presence, so after a split neither half had a figure anywhere. Presence isa stem's RMS as a percentage of the loudest stem in the job, and that reference
is not stored: only the percentages are. The obvious fix decodes all eight stems
again to place two of them on a scale the other six already sit on.
presence_for_splitrecovers the reference arithmetically instead:merge_stem_peaksalready measured RMS for the new stems in its existing passand discarded it. It returns it now, with
vocalsriding along in the same pass:one extra file scanned. With no reference recorded it returns nothing and the
cards read
--, which is honest. Three tests cover it, including the empty case.The footer's controls (#489)
The clusters sat in a wrapping flex row, so the click track either shared the
line, took its own, or stayed and split its own options across two rows. The
footer was never the same shape twice.
Nothing wraps and nothing shrinks now. Verified at 1920, 1600, 1280 and 1024:
one row, order fixed at transport, position, speed, key, click, every cluster
34 px tall. A window too narrow to hold the row scrolls it sideways, on the same
thin auto-hide scrollbar as the library list and mixer.
The row is top-aligned, so every label sits on one line no matter how tall the
controls under it are. Bottom-aligned, the click track threw its own label a row
above the other four.
That also settled the dividers. They were hidden when stranded at the end of a
wrapped line, but bottom-alignment meant the check never matched and none were
ever drawn. Nothing can be stranded now, so
syncFooterDividersand theis-orphanrule are deleted rather than left as logic that cannot fire.min-height: 200pxdated from when the click cluster could wrap. Its contentneeds 170, so the floor only padded the footer past itself and the surplus
pooled as a gap under whichever tier came last.
The track identity (#490)
It was holding a 300 px column of the footer to answer a different question from
everything beside it: the footer's left column said what is loaded, the rest said
how it plays.
It went to the library sidebar first, and came back. The gutter beside the
timeline has to exist regardless, because the timeline's left edge lands on the
lane waveforms' left edge so a position reads at the same x in both. Leaving the
identity in the sidebar meant a panel whose height had to be measured and
mirrored against the footer's, and it put the loaded track's name in the column
you read to choose a different one.
So it is back in the gutter, but in the tier that gutter belongs to rather than
as a full-height column beside the controls. The controls keep the left edge they
gained, and the block is one tier tall instead of two: cover and title on a line
with the favourite, the meta line under them. 79 px, against 141 in the sidebar.
Export Mix moved to the right of the controls row. It acts on the track rather
than on playback, so it sits opposite the clusters instead of filed among them,
and its menu opens toward the middle of the window: anchored left, a 260 px menu
on the footer's right edge would hang off it.
That placement needed the row split. The clusters scroll sideways when the window
cannot hold them, and anything after them would scroll off with them, so
.footer-clusterstakes the overflow and the actions sit outside it. Export Mixis reachable at any width.
Its
footer-*classes travelled astrack-panel-*andtrack-chip-*while itwas in the sidebar, and kept those names: they describe the block, not where it
sits. Every id is unchanged, so no JS or test needed touching beyond one selector
in
main.js.Measured: lane panel and footer timeline both at x=690; identity slot 390 to 690;
Export Mix right edge 1582, flush with the timeline's.
The rail reads Favorites, Library, Queue, Trash. Queue was below Trash, an
active state filed under an archive.
Verification
66 browser tests pass, including the whole export-menu suite, which exercises the
export button in its new home. 845 backend tests pass; the 14 failures are
pre-existing and reproduce identically on
main, nine of them because thismachine has no ffmpeg.
ruff checkandruff format --checkclean,node --checkclean on every file. i18n complete at 468 keys across all nine tables:presence.vocalGlobaladded,presence.vocalremoved.Before merging
uv.lockis untouched, so the desktop in-app updater is unaffected.0.15.2.