diff --git a/static/js/ui.js b/static/js/ui.js index aa82cc6..90cab07 100644 --- a/static/js/ui.js +++ b/static/js/ui.js @@ -519,7 +519,20 @@ export function getAutoScroll() { export function autoResize(textarea) { const lineHeight = parseInt(getComputedStyle(textarea).lineHeight); const isMobile = window.innerWidth <= 768; - const maxHeight = isMobile ? 150 : lineHeight * 8; + const autoMaxHeight = isMobile ? 150 : lineHeight * 8; + + // Keep a height chosen with the native desktop resize handle. Automatic + // changes are recorded before the observer runs, so only a real drag + // updates the manual floor. + if (!textarea._manualResizeObserver && typeof ResizeObserver !== 'undefined') { + textarea._manualResizeObserver = new ResizeObserver(() => { + const height = textarea.offsetHeight; + if (Math.abs(height - (textarea._autoResizeHeight || height)) > 1) { + textarea._manualResizeHeight = height; + } + }); + textarea._manualResizeObserver.observe(textarea); + } // Use a hidden clone to measure without disrupting the real textarea let clone = textarea._resizeClone; @@ -539,9 +552,12 @@ export function autoResize(textarea) { clone.style.width = textarea.offsetWidth + 'px'; clone.value = textarea.value; clone.style.height = '0'; - const newHeight = Math.min(Math.max(clone.scrollHeight, lineHeight), maxHeight); + const manualHeight = textarea._manualResizeHeight || 0; + const maxHeight = Math.max(autoMaxHeight, manualHeight); + const newHeight = Math.min(Math.max(clone.scrollHeight, lineHeight, manualHeight), maxHeight); + textarea._autoResizeHeight = newHeight; textarea.style.height = newHeight + 'px'; - textarea.style.overflow = newHeight >= maxHeight ? 'auto' : 'hidden'; + textarea.style.overflow = newHeight >= autoMaxHeight ? 'auto' : 'hidden'; } /** diff --git a/static/style.css b/static/style.css index b45424d..b3d9407 100644 --- a/static/style.css +++ b/static/style.css @@ -2192,12 +2192,12 @@ body.bg-pattern-sparkles { background: transparent; border: none; outline: none; - resize: none; + resize: vertical; font-size: 14px; line-height: 1.5; color: var(--fg); min-height: 24px; - max-height: 200px; + max-height: min(60vh, 600px); padding: 0; font-family: inherit; transition: height 0.12s ease-out; diff --git a/tests/test_prompt_bar_manual_resize.py b/tests/test_prompt_bar_manual_resize.py new file mode 100644 index 0000000..5792c13 --- /dev/null +++ b/tests/test_prompt_bar_manual_resize.py @@ -0,0 +1,16 @@ +from pathlib import Path + + +CSS = Path("static/style.css").read_text(encoding="utf-8") +UI_JS = Path("static/js/ui.js").read_text(encoding="utf-8") + + +def test_prompt_bar_exposes_desktop_resize_handle(): + assert "resize: vertical;" in CSS + assert "max-height: min(60vh, 600px);" in CSS + + +def test_auto_resize_preserves_a_manually_chosen_height(): + assert "textarea._manualResizeHeight = height;" in UI_JS + assert "const manualHeight = textarea._manualResizeHeight || 0;" in UI_JS + assert "const maxHeight = Math.max(autoMaxHeight, manualHeight);" in UI_JS