diff --git a/Plugin/unraid/include/ai_chat.php b/Plugin/unraid/include/ai_chat.php index 755aef9..9ee3886 100644 --- a/Plugin/unraid/include/ai_chat.php +++ b/Plugin/unraid/include/ai_chat.php @@ -1856,12 +1856,23 @@ vv_ai_profiles_script(); return b.offsetHeight + m; } + // Returns the height it just applied, in pixels, or null when that is not a number this side + // can know — a placement sized in vh resolves only against the viewport, and one that has not + // been given heights yet has nothing to report. + // + // It reports rather than letting the caller measure because .vv-ai-chat transitions its height + // (see the rule in the stylesheet). offsetHeight read straight after this returns the height + // the box is animating FROM, not the one just asked for, and a page that sizes a sibling from + // that figure sizes it for a dock that no longer exists by the time anyone looks. On the + // Scheduler that left the panel's bottom third dead: the view was cut short for a tall dock, + // the dock then shrank to its collapsed share, and nothing re-measured — so the gap survived + // until an expand/collapse fired onResize and ran the fit again against a settled box. function applyHeights() { const el = chatEl(); const base = el.dataset.h || ''; const med = el.dataset.hTall || ''; const big2 = el.dataset.hLarge || med; - if (!base || !med) return; + if (!base || !med) return null; const want = big ? (large ? big2 : med) : base; // Subtracted, not added to. The height the page asked for is the height the whole window // keeps; the list is taken out of the conversation's share of it. calc() because that height @@ -1891,6 +1902,12 @@ vv_ai_profiles_script(); : 'Expanded is medium — click for large'; sizeBtn.classList.toggle('vv-ai-grow-on', large); } + + // Only a plain pixel figure can be resolved here; anything else is left to the caller to + // measure, which is correct for the placements whose height is not a transitioning number. + const n = parseFloat(want); + if (!Number.isFinite(n) || !/^\s*[\d.]+px\s*$/.test(want)) return null; + return k ? Math.max(56, n - k) : n; } // Named functions, because the buttons are no longer the only way in — the shortcuts below // drive the same two. @@ -2071,11 +2088,13 @@ vv_ai_profiles_script(); // Heights supplied after the fact, for a placement whose room is a share of a panel rather // than a constant. Re-applies immediately at whichever of the two states is current, so a // resize while expanded stays expanded instead of snapping back. + // Hands back the pixel height it settled on, so a caller sizing a sibling from it does not + // have to measure a box that is mid-transition. See applyHeights(). setHeights(base, tallMed, tallLarge) { const el = chatEl(); el.dataset.h = base; el.dataset.hTall = tallMed; if (tallLarge) el.dataset.hLarge = tallLarge; - applyHeights(); + return applyHeights(); }, // Same contract, different subject — the Scheduler pointing the chat at another script, log diff --git a/Plugin/unraid/pages/scheduler.php b/Plugin/unraid/pages/scheduler.php index a7e8400..b1d6405 100644 --- a/Plugin/unraid/pages/scheduler.php +++ b/Plugin/unraid/pages/scheduler.php @@ -1262,6 +1262,7 @@ function vvFitRight() { // the same 1:2 relationship, or on a very short viewport the two states would clamp to the same // number and the control would appear to do nothing. const dock = document.getElementById('vv-ai-dock'); + let dockH = dock ? dock.offsetHeight : 0; if (vvSchedChat && dock) { // The share is of the transcript, but what the panel has to find room for is the whole // assistant — composer, control row, padding. Measured rather than assumed, because it is a @@ -1274,12 +1275,21 @@ function vvFitRight() { // discussed on screen too; a chat that covers it entirely is not worth the room it took. const VIEW_MIN = 90; const room = Math.max(60, availH - VIEW_MIN - chrome); - vvSchedChat.setHeights(Math.min(room, Math.max(52, Math.round(availH * 0.10))) + 'px', - Math.min(room, Math.max(100, Math.round(availH * 0.20))) + 'px', - Math.min(room, Math.max(140, Math.round(availH * 0.40))) + 'px'); + const applied = + vvSchedChat.setHeights(Math.min(room, Math.max(52, Math.round(availH * 0.10))) + 'px', + Math.min(room, Math.max(100, Math.round(availH * 0.20))) + 'px', + Math.min(room, Math.max(140, Math.round(availH * 0.40))) + 'px'); + // Taken from what setHeights() reports, not from measuring the dock again. The transcript + // animates its height, so offsetHeight here returns what it is animating away from — a figure + // that was right a frame ago and is wrong by the difference between the two states. Sizing the + // view from it cut the view short and left that difference as dead panel below the dock, which + // only an expand/collapse cleared because that is what ran the fit again once the box settled. + // + // chrome is safe to have measured against the same animating box: it is the difference between + // the dock and the transcript inside it, so the moving term cancels out of the subtraction. + if (applied != null) dockH = chrome + applied; + else dockH = dock.offsetHeight; } - // Measured after the heights are applied, so this reads the clamped height, not the natural one. - const dockH = dock ? dock.offsetHeight : 0; const contentH = Math.max(80, availH - dockH); if (pre.style.display !== 'none') { pre.style.maxHeight = 'none';