Fit a song both ways and zoom its time in further (#362)

Fit in the Song tab only set the time, from the very left edge, and
kept the height the tracks had. The time also stopped zooming at the
bound of the Pattern tab, too soon to lay a take down closely.

- fitSong in songView.ts frames the song with 9 % of the lanes free on
  every side. The tracks share out the height in between, held to
  28 to 320 px. A song opened without a kept view fits the same way.
- The time zooms in to 8 px a tick, eight times the bound of the
  Pattern tab. zoomTime takes the bound as a parameter, so the Pattern
  tab keeps its own.

Tests:
- Unit song-edit: 15 green (1 new), the counter-check without the
  margin is red.
- e2e 65: ALL PASSED, 56 checks (1 new). The new check presses Fit
  after the tracks are added and finds room around bar and tracks.
  Adding a track presses Fit first now: a new song opens with its one
  track 320 px tall, so the track added below it lay off the edge.
- Gate: 272 files, 4213 tests green.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 5.5 committed 2026-10-09 07:33:47 +02:00
1 parent 5cb7ef7bed
commit d0b76ec355
5 files changed
+78 -15

No files matched your search

+24 -3
View File
@@ -133,8 +133,13 @@ export async function song(t) {
);
const id = created.songs[0]?.id;
const model = () => json(`__gm.song.model(${JSON.stringify(id)})`);
/** Adds a track under the last one through its head's menu: there is no other way to. */
/**
* Adds a track under the last one through its head's menu: there is no other way to. Fit first:
* the tracks a fit shares the height out to leave the one added last below the edge.
*/
const addTrackBelowLast = async () => {
const fit = await json('__gm.song.fitAt()');
await click(fit.x, fit.y);
const count = (await model()).tracks.length;
const head = await json(`__gm.song.headAt(${count - 1}, 'name')`);
await click(head.x, head.y, 'right');
@@ -145,10 +150,26 @@ export async function song(t) {
for (let i = 0; i < 3; i++) await addTrackBelowLast();
const tracks = (await model()).tracks.map((track) => track.id);
// 65b. An empty song is a bar long and fits it across; the wheel takes the view out to eight bars.
// A pattern dragged onto the second track lands on the bar before the pointer, a bar long.
// 65a-ii. Fit takes the song across and its four tracks down, with some room on every side: the
// bar starts right of the heads and ends left of the edge, the last track ends above it.
const fitButton = await json('__gm.song.fitAt()');
await click(fitButton.x, fitButton.y);
const fitted = await json('__gm.song.view()');
const area = await json('__gm.song.rect()');
const fitEnds = await json(`[__gm.song.laneAt(0, 0), __gm.song.laneAt(${BAR}, 3)]`);
const bottom = fitEnds[1].y + fitted.trackHeight / 2;
check(
'Fit leaves room around the song: across the bar and down all four tracks',
fitted.panX > 0 &&
fitted.panY > 0 &&
fitEnds[1].x < area.x + area.w - 20 &&
bottom < area.y + area.h - 20 &&
bottom > area.y + area.h * 0.8,
JSON.stringify({ fitted, area, fitEnds }),
);
// 65b. An empty song is a bar long and fits it across; the wheel takes the view out to eight bars.
// A pattern dragged onto the second track lands on the bar before the pointer, a bar long.
for (let i = 0; i < 30; i++) {
const far = await json(`__gm.song.laneAt(${8 * BAR}, 0)`);
if (far.x < area.x + area.w) break;