-
Notifications
You must be signed in to change notification settings - Fork 4
fix(editor): make session transitions loss-safe #210
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -28,7 +28,8 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { host } from './host.js'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { KEYS_PATTERN, isKeysMode, updatePianoRange } from './keys.js'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { _seedExtendedStringsFromTuning } from './lanes.js'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { S } from './state.js'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { S, markSessionDirty } from './state.js'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { disposeBackendSession, stopSessionProcesses } from './session-lifecycle.js'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { _liftAllBeats, _restoreBeatLocks, _syncAppliedMessagePure } from './tempo.js'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { _editorEscHtml, _installModalKeyboard, setStatus } from './ui.js'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -88,13 +89,15 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| '🎵 Blank — start from audio', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'Audio + an empty arrangement (drum tab optional). Chart it yourself ' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| + 'in the editor. No Guitar Pro / XML needed.', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| () => { window.editorShowCreateModal(); window.editorSetCreateMode('blank'); }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Raw opener, not window.editorShowCreateModal: the transition was | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // already guarded when this picker opened; the wrapper would re-prompt. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| () => { editorShowCreateModal(); window.editorSetCreateMode('blank'); }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| )); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| inner.appendChild(mkBtn( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| '🎸 Import from Guitar Pro', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'Build a chart from a Guitar Pro file (.gp3–.gp8), saved as a native ' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| + '.feedpak.', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| () => { window.editorShowCreateModal(); window.editorSetCreateMode('gp'); }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| () => { editorShowCreateModal(); window.editorSetCreateMode('gp'); }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| )); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const cancel = document.createElement('div'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -426,7 +429,7 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Open the freshly-written sloppak via the existing load | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // path so the editor state initialises identically to a | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // normal sloppak load. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await host.loadCDLC(data.filename); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await host.loadCDLC(data.filename, { skipGuard: true }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (e) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| status.textContent = 'Failed: ' + e.message; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| status.className = 'text-xs mb-2 min-h-[1em] text-red-400'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -1572,7 +1575,7 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Left in place rather than deleted, because deleting them is a separate change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // from the bug fix that made them redundant. They arrived with the same | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // half-wired Create-New redesign (977ec65, #45). | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| function _populateCreateArrButtons() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const wrap = document.getElementById('editor-create-arr-buttons'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!wrap) return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| wrap.replaceChildren(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -1745,7 +1748,7 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| createState.lastSync = { ...createState.lastSync, ...data }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return data; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (e) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -2119,7 +2122,7 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| editorHideCreateModal(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| host.kickLibraryRescan(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await host.loadCDLC(data.filename); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await host.loadCDLC(data.filename, { skipGuard: true }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (e) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (status) status.textContent = 'Failed: ' + e.message; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (btn) btn.disabled = false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -2136,10 +2139,23 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Load into editor | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| window.editorHideCreateModal(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Same outgoing-job teardown loadCDLC performs: stop the old playback, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // the pending audio load and any drag, and drop the decoded buffer. An | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // audio-less import skips the loadAudio() branch below, so without this the | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // previous recording keeps sounding under the new chart and S.audioBuffer | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // stays stale. Dispose the old backend session too so its sandbox isn't leaked. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const oldSessionId = S.sessionId; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| stopSessionProcesses(); // also cancels the outgoing audio load | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| S.audioBuffer = null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| S.waveformPeaks = null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| S.title = data.title || ''; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| S.artist = data.artist || ''; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| S.filename = ''; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| S.sessionId = data.session_id; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (oldSessionId && oldSessionId !== data.session_id) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await disposeBackendSession(oldSessionId); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| markSessionDirty(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+2142
to
+2158
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win Blocking
🔧 Proposed fix: don't block the UI transition on backend cleanup S.sessionId = data.session_id;
if (oldSessionId && oldSessionId !== data.session_id) {
- await disposeBackendSession(oldSessionId);
+ disposeBackendSession(oldSessionId); // best-effort, fire-and-forget
}
markSessionDirty();📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| S.format = 'sloppak'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| S.arrangements = data.arrangements || []; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Create-mode import — the source builds tuning to the actual string count, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Support authoring-form packages in export.
Save Ascalls this endpoint aftersaveCDLC(), but directory-form sloppaks intentionally save in place and remain directories. Thisis_file()check therefore returns 409 for valid.feedpak/or.sloppak/sessions, so Save As fails. Zip the directory for export or materialize a packed file before exporting.🤖 Prompt for AI Agents