Dismissing the required-comment modal no longer looks like a close (#20)
Lint / JS (eslint) (push) Successful in 15s
Lint / PHP requirements (version + extensions) (push) Successful in 41s
Security / PHP Security (semgrep) (push) Successful in 1m8s
Lint / PHP (phpcs PSR-12) (push) Successful in 18s
Lint / Notify on failure (push) Skipped
Lint / Deploy (push) Successful in 2s

A modal can be dismissed four ways: the ✕ button, Cancel, a backdrop click, or
Escape. base.js handles the last two globally (a document click handler and
registerKey('escape', closeAllModals)), so the status-change modal — which wired
only the two buttons — never learned it had been dismissed. The status dropdown
kept displaying the new status even though update_ticket.php was never called,
so the ticket looked closed with no comment until a reload showed it still open.

The same gap left every dynamically-inserted modal in the DOM when dismissed
that way, so the next open inserted a duplicate id that shadowed the live one.

- base.js closeModal now dispatches a bubbling lt:modalclose event (synced to
  web_template as bbec859), and _statusCommentModal treats it as "no comment".
- ticket.js reverts the dropdown on any dismissal, guarded against the re-entry
  its own lt.modal.close() would otherwise cause.
- dashboard.js gains openModalWithDismiss() so all seven dynamic modals plus the
  generic prompt modal tear down however they are dismissed.

Verified in headless chromium against all four dismissal routes plus a
confirm-with-comment control: 22/22. Against the pre-fix files the same test
fails 6 assertions — backdrop and Escape leave the dropdown on "Closed *" with
an orphaned overlay — so it reproduces the reported behaviour exactly.
This commit is contained in:
2026-08-07 23:07:06 -04:00
parent 1de04d4908
commit 2ff7345a73
3 changed files with 44 additions and 8 deletions
+8
View File
@@ -241,6 +241,11 @@
trigger.focus();
}
}
// Announce the close so whoever opened the modal can undo optimistic UI or
// clean up a dynamically-inserted overlay. A modal can be dismissed four
// ways — the ✕ button, a Cancel button, a backdrop click, and Escape — and
// the last two are handled globally here, so button-only listeners miss them.
el.dispatchEvent(new CustomEvent('lt:modalclose', { bubbles: true }));
}
function closeAllModals() {
@@ -2774,6 +2779,9 @@
setTimeout(() => { if (modalEl && modalEl.parentNode) modalEl.remove(); }, 300);
resolve(value);
};
// Any dismissal counts as "no comment given", including a backdrop click or
// Escape, which close the overlay through the global handlers above.
modalEl.addEventListener('lt:modalclose', () => finish(null));
modalEl.querySelector('[data-modal-close]').addEventListener('click', () => finish(null));
document.getElementById(modalId + '_cancel').addEventListener('click', () => finish(null));
document.getElementById(modalId + '_confirm').addEventListener('click', () => {