You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Playground something duplicates index.html when saving an instrument with index.html and reopening #932
Likely root cause: Save merges the editor's files into the saved instrument with lodash merge. merge combines arrays by index and never shortens the target array. For example, delete a.ts from a saved instrument whose files are [index.ts, a.ts, b.ts], then save. The editor's files [index.ts, b.ts] are merged onto the saved array, giving [index.ts, b.ts, b.ts], and that duplicated list is persisted. The same thing happens with index.html.
Still reproduces on main (d86740e). updateSelectedInstrument has not changed since 2b61382 (2024-05-02). I ran the slice's merge (lodash-es 4.18.1) in a zustand immer store (immer 11.1.18), and the cause above holds. It does more than duplicate files:
Deleting the last entry of files (the most recently added file) duplicates nothing. The deletion is not saved, and the file is back the next time the instrument is opened. Deleting k files keeps the old last k entries, so a deleted file can come back next to a duplicate: removing a.ts and b.ts from [index.ts, a.ts, b.ts, c.ts] saves [index.ts, c.ts, b.ts, c.ts].
After such a save, Save stays enabled until the instrument is opened again. apps/playground/src/components/Header/SaveButton/SaveButton.tsx:18-22 compares with isEqual, and the lengths differ.
Once duplicates are saved, the editor cannot remove them. deleteFile filters by name (apps/playground/src/store/slices/editor.slice.ts:45), so deleting one copy deletes both, and the next save still cannot shorten the array.
{ ...state.selectedInstrument, ...update } works under immer. selectedInstrument and the instruments entry become the same frozen object, holding exactly the editor's files. Drop the merge import from instrument.slice.ts. The other two playground merge calls (editor.slice.ts:92, settings.slice.ts:20) merge flat objects with no arrays and can stay. Base the change on #1825, which edits this file and adds the store test harness in instrument.slice.test.ts.
Land #1737 first or in the same PR. Today a Save after a reload overwrites index 0 of the saved files. With a full replace, the same click replaces all of them with the template's one file. Land this before #1767: its hasUnsavedChanges compares the same two arrays as Save, so it would report unsaved changes right after a save that deleted a file.
The fix does not repair instruments already saved with duplicates. If #1756's migrate is used to clean them up, do not just keep the first copy of each name. After a delete and save, the later copies are the stale tail, but after #1737 the first index.ts is the template and the second is the user's code. A deleted file that came back cannot be detected at all.
Tests
apps/playground/src/store/slices/__tests__/instrument.slice.test.ts: save [index.ts, b.ts] over [index.ts, a.ts, b.ts], and [index.ts] over [index.ts, a.ts]. Assert that selectedInstrument.files and the instruments entry both equal exactly the saved list.
Activity
joshunrau commentedon Oct 7, 2026
Likely root cause: Save merges the editor's files into the saved instrument with lodash
merge.mergecombines arrays by index and never shortens the target array. For example, deletea.tsfrom a saved instrument whose files are[index.ts, a.ts, b.ts], then save. The editor's files[index.ts, b.ts]are merged onto the saved array, giving[index.ts, b.ts, b.ts], and that duplicated list is persisted. The same thing happens withindex.html.apps/playground/src/store/slices/instrument.slice.ts:52-58:Suggested fix: replace
filesoutright rather than deep-merging it, e.g.{ ...state.selectedInstrument, ...update }.joshunrau commentedon Oct 26, 2026
Still reproduces on
main(d86740e).updateSelectedInstrumenthas not changed since 2b61382 (2024-05-02). I ran the slice'smerge(lodash-es 4.18.1) in a zustand immer store (immer 11.1.18), and the cause above holds. It does more than duplicate files:files(the most recently added file) duplicates nothing. The deletion is not saved, and the file is back the next time the instrument is opened. Deleting k files keeps the old last k entries, so a deleted file can come back next to a duplicate: removinga.tsandb.tsfrom[index.ts, a.ts, b.ts, c.ts]saves[index.ts, c.ts, b.ts, c.ts].apps/playground/src/components/Header/SaveButton/SaveButton.tsx:18-22compares withisEqual, and the lengths differ.deleteFilefilters by name (apps/playground/src/store/slices/editor.slice.ts:45), so deleting one copy deletes both, and the next save still cannot shorten the array.index.ts, and Save writes it over index 0 only.[a.ts, index.ts]saves as[index.ts (template), index.ts (yours)].Fix
{ ...state.selectedInstrument, ...update }works under immer.selectedInstrumentand theinstrumentsentry become the same frozen object, holding exactly the editor's files. Drop themergeimport frominstrument.slice.ts. The other two playgroundmergecalls (editor.slice.ts:92,settings.slice.ts:20) merge flat objects with no arrays and can stay. Base the change on #1825, which edits this file and adds the store test harness ininstrument.slice.test.ts.Land #1737 first or in the same PR. Today a Save after a reload overwrites index 0 of the saved files. With a full replace, the same click replaces all of them with the template's one file. Land this before #1767: its
hasUnsavedChangescompares the same two arrays as Save, so it would report unsaved changes right after a save that deleted a file.The fix does not repair instruments already saved with duplicates. If #1756's
migrateis used to clean them up, do not just keep the first copy of each name. After a delete and save, the later copies are the stale tail, but after #1737 the firstindex.tsis the template and the second is the user's code. A deleted file that came back cannot be detected at all.Tests
apps/playground/src/store/slices/__tests__/instrument.slice.test.ts: save[index.ts, b.ts]over[index.ts, a.ts, b.ts], and[index.ts]over[index.ts, a.ts]. Assert thatselectedInstrument.filesand theinstrumentsentry both equal exactly the saved list.testing/src/specs/playground.spec.ts: open a share link withindex.ts,a.tsandb.ts, which imports as a Saved instrument (apps/playground/src/pages/IndexPage.tsx:65-72). Deletea.ts, click Save, and assert that Save becomes disabled. Switch to Unilingual Form and back, and assert thatb.tsis listed once. Do not reload: After a reload, the playground shows the default template's files under the selected saved instrument, and Save overwrites it #1737 shows the template after a reload, and Reloading a playground share link after editing its instrument adds a new copy on every load and switches away from the edits #1745 imports the link again. Save and View Files have no accessible names (Playground icon-only buttons have no accessible names, and editor tabs cannot be closed from the keyboard #1763). Land this after Playground icon-only buttons have no accessible names, and editor tabs cannot be closed from the keyboard #1763, or pass the already translatedtooltip/tipasaria-labelinActionButtonandEditorButton, which is the first part of Playground icon-only buttons have no accessible names, and editor tabs cannot be closed from the keyboard #1763's fix.Triage note written with Claude Code. Code references point at
main@d86740e.