fix(ui): make the app fit the window it enforces a minimum for

The track list shared out its whole clientWidth across the resizable
columns while every row spends 24px on the favourite column and 2x8px
on its own padding before the first one starts, so the grid was always
exactly 40px wider than the box holding it and the last column was
clipped at every size (scrollWidth 1280 vs clientWidth 1240, measured).
Both numbers now live in one place and are read by the two call sites
that had written them out separately, which is how they came to
disagree.

The enforced minimum was 512x384, which the layout had never
supported: at 700x480 the eleven sidebar items needed 406px of a 352px
pane, overflow:hidden cut the last two off with nothing to scroll, and
Settings and Jobs could not be reached at all. The pane scrolls now,
the sidebar collapses to icons below 900px (its .collapsed mode existed
and only a manual drag ever reached it), the subtitle hides at the same
breakpoint so the title stops wrapping out of the 4em bar, and the
minimum is 800x600 - measured as where the shell still works rather
than picked as a round number.
This commit is contained in:
2026-08-12 01:44:56 -04:00
parent 9e92721bb7
commit e9ca16362f
6 changed files with 354 additions and 10 deletions
@@ -70,6 +70,18 @@ const SORT_DIR_KEY = 'track-list-sort-direction';
const MIN_COLUMN_WIDTH = 50;
const DEFAULT_FIXED_WIDTH = 80;
/**
* Chrome the resizable columns cannot use: the fixed favourite column
* and the row's own horizontal padding. Both numbers also appear in
* the CSS below — the `24px` first track of `--grid-cols` and
* `.track-row`/`.header-row`'s `padding: 8px` — so they live here and
* are read from both places rather than written out four times.
*/
const FAV_COL_WIDTH = 24;
const ROW_PADDING_X = 8;
const ROW_CHROME_WIDTH =
FAV_COL_WIDTH + ROW_PADDING_X * 2;
// Inline SVG paths for favorite icons — eliminates wa-icon shadow DOM
// overhead (30-50 shadow roots during scroll). Font Awesome 6 paths.
const FAV_ICONS = {
@@ -448,7 +460,7 @@ export class TrackList
private get gridTemplateColumns(): string {
const cols = this.activeColumns;
const favCol = '24px';
const favCol = `${FAV_COL_WIDTH}px`;
if (this.columnWidths.length === 0 || this.columnWidths.length !== cols.length) {
return (
@@ -472,10 +484,9 @@ export class TrackList
private get colBoundaryPositions(): number[] {
if (this.columnWidths.length === 0) return [];
const padding = 8;
const favColWidth = 24;
const positions: number[] = [];
let cumulative = padding + favColWidth;
let cumulative =
ROW_PADDING_X + FAV_COL_WIDTH;
for (
let i = 0;
@@ -502,8 +513,18 @@ export class TrackList
this.computeDefaultWidths();
}
/**
* Width the resizable columns may share. H-7: this was the raw
* `clientWidth`, which the columns then summed to exactly — so
* every row was `ROW_CHROME_WIDTH` (40 px) wider than the box it
* had to fit in and the last column was always clipped.
*/
private get availableColumnWidth(): number {
return this.clientWidth - ROW_CHROME_WIDTH;
}
private computeDefaultWidths() {
const totalWidth = this.clientWidth;
const totalWidth = this.availableColumnWidth;
if (totalWidth <= 0) return;
@@ -631,13 +652,14 @@ export class TrackList
}
/**
* Scale widths so they sum to exactly the container width.
* Scale widths so they sum to exactly the width available to the
* columns (the host minus the row chrome).
* Every column is guaranteed at least MIN_COLUMN_WIDTH.
*/
private normalizeWidths(
widths: number[],
): number[] {
const container = this.clientWidth;
const container = this.availableColumnWidth;
if (container <= 0 || widths.length === 0) {
return widths;
@@ -731,7 +753,7 @@ export class TrackList
private onColResizeMove = (e: MouseEvent) => {
if (this.resizingColumn === null) return;
const container = this.clientWidth;
const container = this.availableColumnWidth;
if (container <= 0) return;