From 69ab350919cd8cb4a250949898cd94fcfbf70d8d Mon Sep 17 00:00:00 2001 From: red person Date: Tue, 2 Jun 2026 17:31:09 +0300 Subject: [PATCH] fix(ui): keep minimized windows above composer (#1197) --- static/js/init.js | 33 +++++++++++++++++++++ static/style.css | 4 +-- tests/test_modal_dock_composer_clearance.py | 18 +++++++++++ 3 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 tests/test_modal_dock_composer_clearance.py diff --git a/static/js/init.js b/static/js/init.js index 4749f4f..a15365c 100644 --- a/static/js/init.js +++ b/static/js/init.js @@ -165,6 +165,39 @@ window.addEventListener('pageshow', clearFreshComposerRestore); window.addEventListener('resize', _sync); } +/* Keep minimized tool chips above the composer. Both the current modalManager + dock and the legacy fallback dock consume this root-level clearance. */ +{ + const root = document.documentElement; + const chatBar = document.querySelector('.chat-input-bar'); + const attachStrip = document.getElementById('attach-strip'); + const chatContainer = document.getElementById('chat-container'); + const _syncComposerClearance = () => { + let top = window.innerHeight; + for (const el of [attachStrip, chatBar]) { + if (!el) continue; + const rect = el.getBoundingClientRect(); + if (rect.height > 0) top = Math.min(top, rect.top); + } + const clearance = Math.max(12, Math.ceil(window.innerHeight - top + 8)); + root.style.setProperty('--composer-clearance', clearance + 'px'); + }; + requestAnimationFrame(_syncComposerClearance); + if (typeof ResizeObserver !== 'undefined') { + const ro = new ResizeObserver(_syncComposerClearance); + if (chatBar) ro.observe(chatBar); + if (attachStrip) ro.observe(attachStrip); + } + if (chatContainer && typeof MutationObserver !== 'undefined') { + new MutationObserver(_syncComposerClearance).observe(chatContainer, { + attributes: true, + attributeFilter: ['class'], + }); + } + if (chatBar) chatBar.addEventListener('transitionend', _syncComposerClearance); + window.addEventListener('resize', _syncComposerClearance); +} + /* ---- Resizable sidebar — drag edge to resize, collapse if small, drag rail edge to expand ---- */ { const sidebar = document.getElementById('sidebar'); diff --git a/static/style.css b/static/style.css index 4680ad7..b45424d 100644 --- a/static/style.css +++ b/static/style.css @@ -838,7 +838,7 @@ body.bg-pattern-sparkles { #tile-ghost.visible { opacity: 1; transform: scale(1); } /* Bottom dock — chip per minimized modal */ #minimized-dock { - position: fixed; bottom: 12px; left: 50%; transform: translateX(-50%); + position: fixed; bottom: var(--composer-clearance, 12px); left: 50%; transform: translateX(-50%); display: flex; gap: 6px; flex-wrap: wrap; max-width: calc(100vw - 24px); padding: 4px; @@ -4901,7 +4901,7 @@ body.bg-pattern-sparkles { /* Bottom dock for minimized modals */ #modal-dock { position:fixed; - bottom:0; + bottom:var(--composer-clearance, 0px); left:0; right:0; display:flex; diff --git a/tests/test_modal_dock_composer_clearance.py b/tests/test_modal_dock_composer_clearance.py new file mode 100644 index 0000000..5dfcfe2 --- /dev/null +++ b/tests/test_modal_dock_composer_clearance.py @@ -0,0 +1,18 @@ +from pathlib import Path + + +CSS = Path("static/style.css").read_text(encoding="utf-8") +INIT_JS = Path("static/js/init.js").read_text(encoding="utf-8") + + +def test_both_minimized_window_docks_clear_the_composer(): + assert "#minimized-dock {" in CSS + assert "bottom: var(--composer-clearance, 12px);" in CSS + assert "#modal-dock {" in CSS + assert "bottom:var(--composer-clearance, 0px);" in CSS + + +def test_composer_clearance_tracks_input_and_attachment_height(): + assert "const chatBar = document.querySelector('.chat-input-bar');" in INIT_JS + assert "const attachStrip = document.getElementById('attach-strip');" in INIT_JS + assert "root.style.setProperty('--composer-clearance', clearance + 'px');" in INIT_JS