From 569bf733e974ad56b33bf9fa4b7e5db2252dbd26 Mon Sep 17 00:00:00 2001 From: Thomas Faour Date: Fri, 24 Jul 2026 14:48:50 -0400 Subject: [PATCH] Show save/error status inside the open widget dialog, not behind it showStatus() always wrote to the page-level #result div, which sits behind a 's backdrop -- a save inside a widget's gear-icon dialog produced a message the user couldn't see without closing the dialog first. It now prefers a .dialog-result element inside whichever is currently open, falling back to #result everywhere else. The dialog is a flex column with its body scrolling independently so .dialog-result stays pinned as a visible footer regardless of scroll position -- otherwise a save message on a long form (e.g. the calendar dialog) could land off-screen below the fold with no visible feedback at all. --- server/app/static/common.js | 8 +++++++- server/app/static/frame_layout.js | 1 + server/app/static/theme.css | 10 ++++++++++ server/app/templates/frame_layout.html | 1 + 4 files changed, 19 insertions(+), 1 deletion(-) diff --git a/server/app/static/common.js b/server/app/static/common.js index 27ec4da..fc6bbe3 100644 --- a/server/app/static/common.js +++ b/server/app/static/common.js @@ -59,7 +59,13 @@ })(); function showStatus(ok, message) { - var el = document.getElementById('result'); + // While a is open, its own .dialog-result container gets the + // message instead of the page-level #result -- otherwise it lands + // behind the dialog's backdrop, invisible until the dialog closes + // (e.g. the widget config dialogs on the Layout tab, see + // frame_layout.js). Falls back to #result for everything else. + var openDialog = document.querySelector('dialog[open]'); + var el = (openDialog && openDialog.querySelector('.dialog-result')) || document.getElementById('result'); if (!el) return; el.innerHTML = '
'; el.firstChild.textContent = message; diff --git a/server/app/static/frame_layout.js b/server/app/static/frame_layout.js index a6078a8..bc33c8a 100644 --- a/server/app/static/frame_layout.js +++ b/server/app/static/frame_layout.js @@ -274,6 +274,7 @@ async function openWidgetDialog(widget) { const dialogEl = document.getElementById('widget-dialog'); const bodyEl = document.getElementById('widget-dialog-body'); bodyEl.innerHTML = '

Loading...

'; + dialogEl.querySelector('.dialog-result').innerHTML = ''; // clear any message left over from a previous dialog openDialogWidgetType = widget.widget_type; dialogEl.showModal(); try { diff --git a/server/app/static/theme.css b/server/app/static/theme.css index a3ce890..641b4dc 100644 --- a/server/app/static/theme.css +++ b/server/app/static/theme.css @@ -402,6 +402,13 @@ button.secondary:hover { background: var(--surface-alt); } background: var(--surface); color: var(--text); box-shadow: var(--shadow-hover); + /* #widget-dialog-body scrolls on its own (min-height: 0 is what lets a + flex child actually shrink/scroll instead of forcing the dialog + past max-height) so .dialog-result stays pinned as a visible footer + regardless of scroll position -- otherwise a save message can land + off-screen below a long form with no visible feedback at all. */ + display: flex; + flex-direction: column; } #widget-dialog::backdrop { background: var(--overlay); } #widget-dialog-close { @@ -414,8 +421,11 @@ button.secondary:hover { background: var(--surface-alt); } z-index: 1; } .dialog-title { margin: 0 40px 16px 0; font-size: 18px; } +#widget-dialog-body { overflow-y: auto; min-height: 0; } #widget-dialog-body .card { box-shadow: none; } #widget-dialog-body .card:first-child { margin-top: 0; } +.dialog-result { flex: none; } +.dialog-result:not(:empty) { margin-top: 16px; padding-top: 16px; border-top: 1px solid var(--border); } code { background: var(--surface-alt); diff --git a/server/app/templates/frame_layout.html b/server/app/templates/frame_layout.html index 6998fd0..132a410 100644 --- a/server/app/templates/frame_layout.html +++ b/server/app/templates/frame_layout.html @@ -38,6 +38,7 @@

Loading...

+