Repository navigation
feat: add right-click context menu to the project explorer (#455) - #465
Open
musmanghani13 wants to merge 1 commit into
Open
musmanghani13 wants to merge 1 commit into
musmanghani13 wants to merge 1 commit into
Conversation
) Right-clicking a file, folder or empty space in the project explorer now opens a VS Code-style context menu. - Files: Open, Reveal in File Explorer/Finder, Cut, Copy, Paste, Copy Path, Copy Relative Path, Rename, Delete - Folders: New File, New Folder, plus the same actions - Empty space acts on the project folder: New File, New Folder, Paste, Copy Path (no Rename/Delete/Cut/Copy for the root) Backend: new /api/files/create, /rename, /delete, /copy and /move endpoints. All of them refuse paths outside the open project (including ".." and symlinks), never touch the project folder itself, and never overwrite an existing entry. Delete moves the entry to the Recycle Bin / Trash via send2trash (new dependency, added to the PyInstaller spec). Copying onto an existing name picks "a copy.md", "a copy 2.md", ... Frontend: open tabs follow renamed and moved files; deleted files close their clean tabs and detach unsaved ones so Save cannot recreate them. The menu supports arrow keys, Enter and Escape, and keeps on screen. Tests: 13 backend endpoint tests and 8 frontend path/tab tests.
petertzy
requested changes
Nov 6, 2026
petertzy
left a comment
Owner
There was a problem hiding this comment.
Thanks for implementing the explorer context menu for #455. The overall feature coverage is strong, including file/folder-specific actions, keyboard navigation, tab path updates, and tests. However, I cannot recommend merging yet.
Blocking: filesystem safety and overwrite guarantees
- In
backend/routers/files.py,create_entry,_unique_copy_target, andmove_entryrely onPath.exists()to determine whether a destination is free.exists()returns false for a dangling symlink, so these checks do not consistently recognize an occupied directory entry. Please use a lexists-style check (os.path.lexists) and add regression tests for dangling symlinks. rename_entrychecks for an existing destination and then callsPath.rename(). On POSIX, rename can replace an entry created between the check and the operation. Similarly,shutil.move()can overwrite a destination under concurrent filesystem changes. The PR promises that rename and move never overwrite existing entries; please either enforce this atomically where supported or explicitly address and test the race.- Please verify and document how symlink entries are handled:
_resolve_in_project()resolves the source path, so operations on a symlink can act on its target instead of the link itself, and links pointing outside the project are rejected. This should be intentional and covered by tests, especially for Delete, Rename, and Copy.
Validation needed before approval
- Bring the latest
maininto the PR head and resolve any merge conflicts. - Re-run backend tests and frontend tests, lint, type checking, and build against the updated branch.
- Verify desktop Reveal, Trash, and clipboard behavior on macOS/Linux when possible.
I have not independently executed the test suite or completed the upstream merge, so this review is based on source inspection rather than a verified green build. Once the blocking filesystem cases are addressed and CI passes, I would be happy to reassess.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #455
Summary
Adds a VS Code-style right-click context menu to the project explorer, so common file operations no longer require leaving the app for File Explorer / Finder.
"Reveal" only appears in the desktop app, and its label follows the platform (Finder / File Explorer / Open Containing Folder).
Why
Until now the explorer could only open files. Creating, renaming, moving or deleting anything meant switching to the system file manager and then refreshing. This brings the most-used explorer actions into the app, with behaviour modelled on VS Code.
What changed
Backend (
backend/routers/files.py)POST /api/files/create,/rename,/delete,/copy,/move...segments and symlinks) with 403. The project folder itself cannot be renamed, deleted, cut or copied. Names that are really paths (../x,a/b) or contain characters Windows forbids are rejected with 400.send2trash, so it can be restored.a copy.md,a copy 2.md, … like VS Code; folders are copied with their contents (symlinks are copied as links).send2trash>=1.8(pyproject.toml,uv.lock), added to the PyInstallerhiddenimportsinmarkdown-reader-backend.specbecause it picks its platform module at runtime.Frontend
ProjectExplorer.tsx: the context menu, a small dialog for New File / New Folder / Rename / Delete, and the cut/copy clipboard. Expanded folders are now kept in shared state so they stay open when a folder reloads, is renamed or is moved.explorer-paths.mjs(new): dependency-free path and tab helpers (relative paths, joining, remapping tabs after rename/move, closing tabs after delete), kept as.mjssonode --testcan import them liketabLifecycle.mjs.useEditor.ts:handlePathRenamed/handlePathDeleted, so open tabs follow renamed and moved files (including unsaved edits and converted documents). Deleting a file closes its tab if it has no unsaved changes; tabs with unsaved changes stay open but are detached, so Save asks for a new location instead of recreating the deleted file.api.ts:Files.create / rename / delete / copy / move.How I validated it
Automated
tests/test_files_endpoints.py(create, rename, delete, copy names, move, overwrite refusal, pasting a folder into itself, and paths outside the project). All 18 tests in the file pass;ruff checkandruff format --checkpass on the changed files.tests/explorer-paths.test.mjscovering Windows and POSIX paths.npm test(170/170),npm run lint,tsc --noEmitandnpm run buildall pass.main(I ran both and compared the lists); they are in image paths, renderer, export, knowledge and translation and are unrelated to this change. They should pass on Linux CI.Manual (Windows 11)
npm run dev): rename with an open tab, new file opens automatically, deleting a folder closes its tabs, Cut + Paste moves a file and a later Save writes to the new location, Copy + Paste createsdocs copywith its contents, and pasting a folder into itself shows an error.Not verified
navigator.clipboard, which I could not check in WebKitGTK.Screenshots
File context menu
Rename dialog
Delete dialog
Folder context menu