Address review feedback and UX issues on the file browser.
Editor
- Replace the TipTap editor with Monaco, reusing the self-hosted assets. The
editor is kept mounted (wire:ignore) and preloaded on page init, and receives
content + language via the load-file-editor event, applying the language with
setModelLanguage so highlighting is correct without recreating the instance.
- Keep the Monaco instance on the DOM node, not in Alpine reactive state, so its
object graph is never proxied (that hung and crashed the tab).
- Drop @tiptap/*, lowlight and highlight.js; remove the TipTap CSS and JS.
- Broaden guessLanguage to Monaco ids and common extension-less/dotfiles.
Listing / read (perf)
- List a directory in one stat call instead of a per-entry loop (embedding a
real tab, since stat -c does not expand \t), and surface owner/group columns.
- Fold the read size-cap, binary probe and base64 into one SSH round trip.
Actions
- Collapse the per-row Edit/Download/Rename/Delete buttons into a 3-dots menu.
- Fix actions silently failing on names with apostrophes: the entry name now
lives in the row's Alpine scope instead of passing @js() through a component
attribute (which double-encodes the quotes).
Hardening
- Lock client-controllable state (container/type/resource) with #[Locked].
- Re-check guard() in open/goTo/refresh; sanitize upload filenames; clean up the
download temp file and report errors; route >96KiB writes through docker cp.
Tests updated and extended (32 passing). Frontend rebuild (npm run build) is
still needed only to drop TipTap from the bundle; the editor works without it.
- Escape dynamic file/path names with @js() in Livewire click handlers
instead of interpolating raw strings, and lock resource/container
properties to prevent client-side property tampering
- Route writes over ~96 KiB through docker cp instead of an inline
base64 shell argument, which was capped by MAX_ARG_STRLEN
- Treat empty files as editable text instead of misclassifying them
as binary