From 732989a452bd655dfb3b69260207dec30c79b709 Mon Sep 17 00:00:00 2001 From: MelekTaus Date: Sun, 18 Oct 2026 12:48:15 +0200 Subject: [PATCH] Keep Fit inside the song tab's header again (#429) Suite 719 for 0dd671e was red in e2e 65. The Arrange button that came with b9d1602 sits in every graph tab's header, the song's too. There it pushed Bake and Fit past the right edge of the tab. The Fit click hit nothing, the last track stayed below the edge, and its head menu with "Add track below" never opened. - The song tab shows Arrange with its routing only. On the timeline it was greyed out and of no use anyway. - SnapToggle.setArrangeShown hides the button and lays its header out again, so the timeline's header is as wide as before #429. - The magnet stays where it was, greyed out on the timeline, as e2e 65 expects. The other red check in suite 719, e2e 34 "the pause holds the stream", is the wobble left open in 5b9d084 (task 712). Its message now shows the stream behind before the pause already (skipped 12950), so the pause did not lose its place. On melektaus-atx it passed. Tests: - snap-toggle.test.ts 6/6, the new test is red without the relayout - e2e 65 on melektaus-atx: red before (same error as in CI), now ALL PASSED, 59 checks - Gate on melektaus-atx: CHECK EXIT 0, 318 files, 4931 tests Co-Authored-By: Claude Opus 5.5 --- src/editor/graph/SnapToggle.ts | 13 ++++++++++++- src/editor/song/SongEditor.ts | 3 ++- tests/editor/snap-toggle.test.ts | 17 ++++++++++++++++- 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/editor/graph/SnapToggle.ts b/src/editor/graph/SnapToggle.ts index 96511c08..4ad4a481 100644 --- a/src/editor/graph/SnapToggle.ts +++ b/src/editor/graph/SnapToggle.ts @@ -1,4 +1,4 @@ -import { Button, Theme } from '../../ui'; +import { Button, Stack, Theme } from '../../ui'; import { HEADER_ICON_SVG } from '../panels/headerIcons'; /** Whatever a tab snaps: the graph canvas, the vector art canvas. A graph arranges its nodes too. */ @@ -58,4 +58,15 @@ export class SnapToggle { this.button.setEnabled(on); this.arrangeButton?.setEnabled(on); } + + /** + * Shows or hides *Arrange*, for a tab whose graph is one of its views: the song's timeline has + * no use for it, and a header that wide pushed *Fit* past the edge of the tab. + */ + setArrangeShown(on: boolean): void { + const button = this.arrangeButton; + if (!button || button.visible === on) return; + button.visible = on; + if (button.parent instanceof Stack) button.parent.relayout(); + } } diff --git a/src/editor/song/SongEditor.ts b/src/editor/song/SongEditor.ts index cecef9a2..c6020871 100644 --- a/src/editor/song/SongEditor.ts +++ b/src/editor/song/SongEditor.ts @@ -217,10 +217,11 @@ export class SongEditor extends AssetTabEditor { this.syncPlayButton(); } - /** The grid is the timeline's, the snap the routing's. */ + /** The grid is the timeline's, the snap the routing's, and *Arrange* only shows with the routing. */ private syncViewButtons(asset: SongAsset | null): void { this.gridButton.setEnabled(asset !== null && this.shown === 'timeline'); this.snap.setEnabled(asset !== null && this.shown === 'routing'); + this.snap.setArrangeShown(this.shown === 'routing'); } protected override body(canvas: SongTimeline): Widget { diff --git a/tests/editor/snap-toggle.test.ts b/tests/editor/snap-toggle.test.ts index 68492e8c..3fc78bd0 100644 --- a/tests/editor/snap-toggle.test.ts +++ b/tests/editor/snap-toggle.test.ts @@ -5,7 +5,7 @@ import { beforeAll, describe, expect, it, vi } from 'vitest'; import { CanvasTextMetrics } from 'pixi.js'; import { GRAPH_SNAPS_AT_FIRST, SnapToggle } from '../../src/editor/graph/SnapToggle'; -import { Theme } from '../../src/ui'; +import { Button, HStack, Theme } from '../../src/ui'; // Node has no DOMParser to read the file with; what the magnet looks like is the icon tests' job. vi.mock('../../src/ui/svg', () => ({ drawSvg: () => undefined, tintSvg: (svg: string) => svg })); @@ -67,6 +67,21 @@ describe('SnapToggle', () => { expect(new SnapToggle(() => canvas).arrangeButton).toBeNull(); }); + it('hides Arrange from a header it shares, which then lays the rest out without it', () => { + const snap = new SnapToggle(() => target(), GRAPH_SNAPS_AT_FIRST, true); + const header = new HStack({ gap: 4 }); + const fit = new Button('Fit'); + for (const widget of [...snap.widgets, fit]) header.add(widget); + header.resize(400, Theme.controlHeight); + const shownAt = fit.x; + snap.setArrangeShown(false); + expect(snap.arrangeButton!.visible).toBe(false); + expect(fit.x).toBeLessThan(shownAt); + snap.setArrangeShown(true); + expect(fit.x).toBe(shownAt); + new SnapToggle(() => target()).setArrangeShown(false); + }); + it('asks for the target at the click, so a canvas mounted later is the one switched', () => { let canvas = target(); const snap = new SnapToggle(() => canvas);