diff --git a/static/app.js b/static/app.js index ef45697..674d3b4 100644 --- a/static/app.js +++ b/static/app.js @@ -8100,6 +8100,26 @@ function _resolveEditRegion() { return { a: Math.max(0, t - 4), b: t + 4 }; } +/* @pure:editor-pending-view:start */ +function _buildEditorPendingViewPure(filename, arrangement, region, opts) { + const options = opts || {}; + const view = { + filename, + arrangement: Number.isFinite(arrangement) && arrangement >= 0 ? arrangement : 0, + barSel: region ? { startTime: region.a, endTime: region.b } : null, + }; + if (options.returnToHighway) view.returnToHighway = true; + if (typeof options.cursorTime === 'number') { + view.cursorTime = options.cursorTime; + } else if (region && typeof region.a === 'number') { + view.cursorTime = region.a; + } + if (typeof options.scrollX === 'number') view.scrollX = Math.max(0, options.scrollX); + if (typeof options.zoom === 'number' && options.zoom > 0) view.zoom = options.zoom; + return view; +} +/* @pure:editor-pending-view:end */ + // Enable "Edit region" whenever the editor plugin is present and a song is // loaded; show "↩ Editor" only while a return context is pending. function _updateEditRegionBtn() { @@ -8126,12 +8146,9 @@ function editRegionInEditor() { arrangement = si.arrangement_index; } } catch (_) { /* default to 0 */ } - window._editorPendingView = { - filename: currentFilename, - arrangement, - barSel: { startTime: region.a, endTime: region.b }, + window._editorPendingView = _buildEditorPendingViewPure(currentFilename, arrangement, region, { returnToHighway: true, - }; + }); window.editSong(currentFilename); } window.editRegionInEditor = editRegionInEditor; @@ -8143,14 +8160,14 @@ function returnToEditorFromHighway() { const ctx = window._highwayReturnCtx; if (!ctx || typeof window.editSong !== 'function') return; window._highwayReturnCtx = null; - window._editorPendingView = { - filename: ctx.filename, - arrangement: ctx.arrangement, + const region = ctx.barSel + ? { a: ctx.barSel.startTime, b: ctx.barSel.endTime } + : null; + window._editorPendingView = _buildEditorPendingViewPure(ctx.filename, ctx.arrangement, region, { scrollX: ctx.scrollX, zoom: ctx.zoom, cursorTime: ctx.cursorTime, - barSel: ctx.barSel, - }; + }); window.editSong(ctx.filename); } window.returnToEditorFromHighway = returnToEditorFromHighway; diff --git a/tests/js/editor_pending_view.test.js b/tests/js/editor_pending_view.test.js new file mode 100644 index 0000000..868d80b --- /dev/null +++ b/tests/js/editor_pending_view.test.js @@ -0,0 +1,47 @@ +'use strict'; +const test = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); + +const src = fs.readFileSync(path.join(__dirname, '..', '..', 'static', 'app.js'), 'utf8'); +const m = src.match(/\/\* @pure:editor-pending-view:start \*\/[\s\S]*?\/\* @pure:editor-pending-view:end \*\//); +if (!m) throw new Error('pending-view helper block not found'); + +const api = new Function('"use strict";' + m[0] + '\nreturn { _buildEditorPendingViewPure };')(); + +test('edit-region handoff defaults cursor to region start and marks return path', () => { + const out = api._buildEditorPendingViewPure('song.sloppak', 2, { a: 12.5, b: 20 }, { returnToHighway: true }); + assert.deepStrictEqual(out, { + filename: 'song.sloppak', + arrangement: 2, + barSel: { startTime: 12.5, endTime: 20 }, + returnToHighway: true, + cursorTime: 12.5, + }); +}); + +test('return-trip handoff preserves explicit viewport state', () => { + const out = api._buildEditorPendingViewPure('song.sloppak', 1, { a: 8, b: 14 }, { + scrollX: -4, + zoom: 160, + cursorTime: 9.25, + }); + assert.deepStrictEqual(out, { + filename: 'song.sloppak', + arrangement: 1, + barSel: { startTime: 8, endTime: 14 }, + cursorTime: 9.25, + scrollX: 0, + zoom: 160, + }); +}); + +test('missing region still produces a stable pending view shell', () => { + const out = api._buildEditorPendingViewPure('song.sloppak', -1, null, {}); + assert.deepStrictEqual(out, { + filename: 'song.sloppak', + arrangement: 0, + barSel: null, + }); +});