Repository navigation
Description
Renaming an open file in the playground leaves its tab and selection on the old name, and every later edit to it is thrown away. renameFile only changes name on the entry in files. openFilenames and selectedFilename keep the old name, so the tab still shows it and Monaco keeps editing the old path. Each keystroke then reaches setSelectedFileContent, which calls updateFile(selectedFilename, { content }) with the old name. updateFile finds no file by that name, logs Cannot update file '<old>': file does not exist to the console and returns. Monaco keeps showing the typed text, but nothing reaches the store, so the preview stops updating and Save, Download and Share all use the content from before the rename. Renaming the index file also leaves indexFilename stale.
Where
apps/playground/src/store/slices/editor.slice.ts:56-58 and apps/playground/src/store/slices/editor.slice.ts:81-90:
renameFile: (currentName, updatedName) => {
get().updateFile(currentName, { name: updatedName });
},
// ...
updateFile: (name, update) => {
set((state) => {
const index = state.files.findIndex((file) => file.name === name);
if (index === -1) {
console.error(`Cannot update file '${name}': file does not exist`);
return;
}Reproduce
- In the playground, click Add File, name it
helpers.tsand press Enter. It opens in a tab and is selected. - Right-click
helpers.tsin the file sidebar, choose Rename, change it toutils.tsand press Enter. - Type
export const answer = 42;in the editor. - Click Download Archive and open the zip.
Actual: the tab still reads helpers.ts, the console logs Cannot update file 'helpers.ts': file does not exist once per keystroke, and utils.ts in the zip is empty.
Expected: the tab and selection follow the rename to utils.ts, and the zip's utils.ts contains the typed line.
Tests
apps/playground/src/store/slices/__tests__/editor.slice.test.ts: it('should move an open file's tab and selection to its new name, so edits after a rename reach the renamed file').
E2E in testing/src/specs/playground.spec.ts: add a file, rename it, type into it, and assert that the tab shows the new name and that the share link's decoded files contain the typed content.
Suggested fix
Make renameFile one set that renames the file, replaces the old name in openFilenames, moves selectedFilename when it was the old name, and recomputes indexFilename. Refuse a name that already exists with feedback the user can see, rather than only console.error.
Activity
joshunrau commentedon Oct 26, 2026
Still reproduces on
main(d86740e). #1825 reworkscloseFileanddeleteFileineditor.slice.tsbut leavesrenameFileas it is.Gaps in the suggested fix
indexFilenamethrows for a non-index name.resolveIndexFilenamereturnsnullonly for an empty list (apps/playground/src/utils/file.ts:8-10,:42-44). OtherwiseresolveIndexInputthrows (packages/instrument-bundler/src/resolve.ts:16-18). So with the proposed recompute, renamingindex.tstomain.tsthrows inside the immer producer and the rename is dropped. The same throw already breaksaddFileandaddFiles(editor.slice.ts:16,:31) once the index file is deleted, whichdeleteFileallows. After that, adding or uploading files fails unless one of them is an index file. Make the lookup returnnullwhen no file is an index candidate. Put it inresolve.ts, withresolveIndexInputthrowing on thenull, so the extension priority stays in one place. That package is published and coverage-gated, so the new export needs its own tests there. Use the same lookup indeleteFile, which leavesindexFilenamestale today. This can be its own commit, landed first.renameFile(name, name)(EditorFileButton.tsx:68-72). That hits the "already exists" branch (editor.slice.ts:87-89), so with visible feedback it would warn on a no-op. Treat it as a no-op.displayFilenameresets only when thefilenameprop changes (EditorFileButton.tsx:46-48). The click-outside handler (:33-38) retries the rename on every click while the two differ, so a visible error would fire on every click. ResetdisplayFilenameon refusal. HaverenameFilereturn the reason, so the button can show a translated notification (useNotificationsStore, asDeleteInstrumentDialog.tsxdoes) without repeating the check.renameFile, as Add File does in the UI (Editor.tsx:109-115). Today theupdate.name &&guard (editor.slice.ts:87) skips the duplicate check for'', so a file can be renamed to an empty name.Monaco half
The store fix alone fixes the reported case. Moving
selectedFilenamechanges the editor's key (EditorPane.tsx:61,:138), and the model at the new path is created from store content. The old model is never disposed, though, andgetOrCreateModelreturns an existing model as-is, both in@monaco-editor/react4.7.0 and in #1660'sMonacoEditor.tsx. So renamehelpers.tstoutils.ts, type, and rename it back: the editor reopens the stalehelpers.tsmodel, and the next keystroke replaces the store's content with that stale text plus the keystroke. This is #1741's cause. #1741's fix covers it only if it disposes the model of every name that leavesfiles, not only ondeleteFile.Order and tests
editor.slice.test.tswith theidb-keyvaland@/instrumentsmocks. Of build: upgrade dependencies held back by breaking changes #1660's files, it touches onlytesting/src/specs/playground.spec.ts. The Monaco half lands with Re-creating a deleted playground file with the same name shows the deleted content, and the first keystroke overwrites the new file #1741, after build: upgrade dependencies held back by breaking changes #1660.index.tstoindex.tsxkeeps the file as the index;index.tstomain.tssetsindexFilenametonull; a duplicate, unchanged or extensionless name changes nothing.playground editor tabsblock, hoisting itsfileButtonandtabhelpers out of the existing test. The rename-back case belongs in Re-creating a deleted playground file with the same name shows the deleted content, and the first keystroke overwrites the new file #1741's e2e, because it fails until the old model is disposed.Triage note written with Claude Code. Code references point at
main@d86740e.