- Shipped
- September 2, 2026 at 1:31 AM UTC
- Author
- Kamo
- Commit
- 1b4e247
Two defects behind one report: the PDF editor would not open, and delete was offered to a member who may not delete. **The editor.** DocManager posts to /api/docs/form-fillable/{id} and that Next route was never created. There is no catch-all under /api/docs — every other area proxies through app/api/<area>/[[...path]] but this one routes file by file — so Next answered with its own 404 HTML and the call failed for EVERY member, including one holding EDIT_DOCUMENTS whose request the backend would have granted. It surfaced as the generic "Failed to open document" banner, which reads exactly like a permission problem and is not one. The upstream endpoint has been live all along: **************** answers 401 on the deployed docsservice and through the APIService relay, where an invented path answers 404. The new route mirrors its /api/docs/open sibling and passes the body (imgId, originalImgId, classicEditable) through untouched. **The delete button.** Every row rendered delete unconditionally, so the only thing that refused was the server — as an alert reading "DELETE_DOCUMENTS required" on a button that should not have been there. The reporter's own diagnosis was right. Gated now, along with the toolbar's bulk delete, which calls the same endpoint and would have produced the identical refusal the moment a row was selected. Modify is gated on EDIT_DOCUMENTS for the same reason: both modes behind it refuse without that right, so a view-only member could only ever reach an error through it. Download, binder and share stay ungated -- they follow from VIEW_DOCUMENTS, already settled by the grid having loaded at all. This is presentation, not enforcement. ImagingController and DocumentController check these rights on every call and remain the authority; hiding a button removes a dead end, it does not protect anything. The gating rule lives in app/lib/ beside modifyOptions.ts and for the same reason: vitest runs in the node environment, so a rule inside a component cannot be covered at all. The route guard is scoped to /api/docs because that is the area without a catch-all, and it fails on exactly this bug when the route is removed.