One scaffold for every confirmation modal — escape, focus-trap, restored focus
Summary: One scaffold for every confirmation modal — escape, focus-trap, restored focus (v0.5.1b15).
The secantus-admin UI has nine confirmation / edit modals: drop-database, drop-collection, drop-index, drop-user, change-password, manage-roles, edit-document, delete-document, kill-cursor. They were assembled at slightly different times and drifted in five different ways — different destructive-button copy ("Drop" vs "Drop index" vs "Drop database"), different typed-confirm targets (the delete-document modal asked the user to type the collection name shared by every row, the kill-cursor modal asked for the giant int cursor id), no Escape-to-close, no focus restoration to the trigger element, no focus trap so Tab leaked back into the page behind, and aria-label="Close" only on two of nine close buttons.
v0.5.1b15 consolidates all nine on a shared scaffold: a new modal-shell.js exposes openModal(url) / closeModal() / setupModal(el) plus a global htmx hook that captures the trigger element so closeModal() can restore focus. Each modal partial has the same overlay shape — x-init="setupModal($el)", @click.self="closeModal()", @keydown.escape.window="closeModal()", role="dialog", aria-modal, aria-labelledby — and the close button always carries aria-label="Close". Tab and Shift+Tab cycle within the modal's focusable children rather than escaping into the page behind.
Three substantive fixes ride along with the scaffolding cleanup: destructive button copy now always restates action+noun (Kill cursor / Delete document / Drop index / Drop user / Drop database / Drop collection), the delete-document typed-confirm asks for the doc's _id value rather than the collection name (which was the same for every row on the page), and the kill-cursor typed-confirm asks for the collection ns rather than the unguessable cursor id. None of these change SecantusDB's wire-protocol behaviour — purely an admin-UI safety + a11y improvement.