Hold Conversations and Tokens to the Assistant's height

A stretched grid item is measured before it is stretched, so a long card grew the row it was
meant to fit inside and then filled it; the ceiling is taken from the Assistant instead.
This commit is contained in:
Gmer4Lfe
2026-08-09 11:09:04 -04:00
parent 7b1b017d8c
commit 4f598f7422
2 changed files with 52 additions and 1 deletions
+7 -1
View File
@@ -256,7 +256,13 @@ body.vv-fullscreen #displaybox { padding-left: 1rem !important; padding-top: .5r
there it inherited a cap from `.vv-card > div { flex:1; overflow-y:auto }`. Out here both are there it inherited a cap from `.vv-card > div { flex:1; overflow-y:auto }`. Out here both are
content-driven, so forty saved chats — or a ledger with both hosts and a footer — would make content-driven, so forty saved chats — or a ledger with both hosts and a footer — would make
the card the tallest thing in its column and set the height it is supposed to be following. the card the tallest thing in its column and set the height it is supposed to be following.
The h3 pins and the body takes what is left, the same shape the monitor grid above uses. */ The h3 pins and the body takes what is left, the same shape the monitor grid above uses.
align-self alone does not finish the job, which is why vvAiRowSync() in monitor.php sets a
max-height on both. A stretched item's own content still feeds grid track sizing — the track
is sized from content first and stretched afterwards — so a card long enough grows the row it
was meant to fit inside, and stretching to that row then changes nothing. The cap is measured
from the Assistant, which is the one card here whose height is its own business. */
#vv-monitor-ai #vv-ai-chats-card, #vv-monitor-ai #vv-ai-chats-card,
#vv-monitor-ai #vv-ai-tokens-card { #vv-monitor-ai #vv-ai-tokens-card {
align-self: stretch; align-self: stretch;
+45
View File
@@ -2087,6 +2087,36 @@ function vvRenderTokens() {
box.innerHTML = h; box.innerHTML = h;
} }
// ── AI row height ────────────────────────────────────────────────────────────
// Conversations and Tokens are capped to the Assistant and scroll inside that cap. CSS cannot
// state this on its own: align-self:stretch makes an item fill its grid track, but the track was
// sized from the item's own content first, so a card with a long list simply grows the row and
// then dutifully fills the bigger row. Measuring the Assistant breaks that circle — it is the
// only card in the row whose height is decided by nothing but itself.
//
// Applied only in the eight-column layout. Below 1025px the Assistant drops to a row of its own
// and stops being what these two sit beside, so a cap taken from it would describe nothing.
function vvAiRowSync() {
const asst = document.getElementById('vv-ai-assistant-card');
const chats = document.getElementById('vv-ai-chats-card');
const toks = document.getElementById('vv-ai-tokens-card');
const stats = document.getElementById('vv-ai-stats-card');
if (!asst || !chats) return;
if (!window.matchMedia('(min-width: 1025px)').matches) {
chats.style.maxHeight = '';
if (toks) toks.style.maxHeight = '';
return;
}
const h = asst.offsetHeight;
chats.style.maxHeight = h + 'px';
// Tokens is in the second row, under AI — its share is what the Assistant leaves once the
// first row and the 12px gap are taken. Floored so it stays a card rather than a sliver on a
// short viewport where the AI card happens to be tall.
if (toks && stats) toks.style.maxHeight = Math.max(140, h - stats.offsetHeight - 12) + 'px';
}
// Delegated on the container, which survives every render — the rows inside do not, and binding // Delegated on the container, which survives every render — the rows inside do not, and binding
// per row would rebind the whole list on each poll to no purpose. // per row would rebind the whole list on each poll to no purpose.
if (document.getElementById('vv-ai-tokens-body')) { if (document.getElementById('vv-ai-tokens-body')) {
@@ -2822,5 +2852,20 @@ if (document.getElementById('vv-mon-ai-chat')) {
}); });
vvMonChatList = VvAiChatList({ into: 'vv-mon-ai-chats', chat: vvMonChat }); vvMonChatList = VvAiChatList({ into: 'vv-mon-ai-chats', chat: vvMonChat });
vvMonChatList.reload(); vvMonChatList.reload();
// Re-measured rather than computed once. The Assistant changes height when the transcript is
// expanded, and the AI card above changes when its first real payload replaces "Loading...";
// both move the ceiling these two cards sit under. Observing the two sources covers each
// without watching the cards being adjusted, so there is no feedback loop. The resize listener
// is for crossing the 1025px breakpoint, which changes whether a cap applies at all.
vvAiRowSync();
if (window.ResizeObserver) {
const ro = new ResizeObserver(vvAiRowSync);
['vv-ai-assistant-card', 'vv-ai-stats-card'].forEach(id => {
const el = document.getElementById(id);
if (el) ro.observe(el);
});
}
window.addEventListener('resize', vvAiRowSync);
} }
</script> </script>