1. Kebab Cut/Copy silently did nothing, and sometimes cut the wrong rows.
`EllipsisMenu` stops click propagation so opening it does not toggle
selection — but Cut, Copy, Download, Delete and Compress all act on the
SELECTION, which meant the kebab operated on whatever was selected elsewhere,
or returned early with no toast at all. Right-click never had this because it
selects first. Both now call one `ensureSelected`, on open.
2. Every menu vanished during a search. That branch had no ContextMenu and its
rows are not FileItems, so right-click fell through to the browser. The folder
menu is now declared once and mounted in both branches — it acts on
`currentPath`, which a search does not change, so it stays meaningful.
Search ROWS get their own three-item menu rather than the row menu: a hit
carries an absolute path from anywhere in the tree, while every row-menu
handler builds its path from the folder being browsed. Reusing it would have
acted on a different file with the same name. `DirEntry.path` is optional —
only search results carry one — so it is guarded, not asserted.
3. Download and Copy path now fan out over a multi-selection, the rule Delete and
Compress already used. The menu downloaded one of six selected files while the
toolbar zipped all six.
Chat is deliberately NOT included: `chatContext` is a single path that the
provider string-splits into a working directory, so a list would need Chat's
contract changed. Left alone rather than half-done.
4. Read Aloud stopped offering TTS on binaries — same guard the editor got, since
`text` is `getFileType`'s fallback and caught .exe and .sqlite.
5. `POST /file-browser/download` is filed as a read. It zips a selection and
writes nothing; it is a POST only because a list of paths does not fit a query
string. A read-level member could download a whole folder but not two files.
Proved by hand that `readOnlyWrites: ['/download']` permits GET and POST
/download, and still denies /download-video, /rm and /write — `isPrefixOf` is
segment-aware, so the sibling route does not leak.
6. `disabled` had no use anywhere in the feature — the shared item type did not
even expose it. It does now, and the first real case: `/file-browser/read`
refuses over 5 MB, so Open in editor on a 40 MB log opened an editor that then
failed. Hiding it would have suggested the file is not text; disabled says the
true thing.
7. The grid kebab could fade out while its own dropdown was open — list view had
`data-[state=open]:opacity-100` and grid did not.
8. Dead exports `getMatchingTasks` / `getMatchingAgents` removed. Both were the
ungrouped input to the grouped form the menu actually renders; exporting them
invited a second code path that never arrived.
9 is untouched on purpose — a plugin cannot register a menu item, and the music
plugin is where that should be designed.
tsgo clean, frontend builds, 808 pass / 7 fail unchanged.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>