]> git.djapps.eu Git - pkg/ggml/sources/llama.cpp/commitdiff
ui: fix context gauge card regressions and land at the conversation end (#26099)
authorPascal <redacted>
Sun, 26 Jul 2026 04:51:10 +0000 (06:51 +0200)
committerGitHub <redacted>
Sun, 26 Jul 2026 04:51:10 +0000 (06:51 +0200)
The context gauge card starts monitoring like the dial does, because
its own processing state instance only follows the live stream while
its monitoring flag is set. It also gets back the text-sm and ring
classes the removed hover card wrapper used to inject, which restores
its layout. Routing to a conversation now lands at the bottom
instantly and keeps the pin one frame at a time until the page height
settles, since content-visibility size realizations and syntax
highlight passes grow the page without DOM mutations.

tools/ui/src/lib/components/app/chat/ChatForm/ChatFormContextGauge/ContextGaugePopup.svelte
tools/ui/src/lib/components/app/chat/ChatScreen/ChatScreen.svelte
tools/ui/src/lib/constants/auto-scroll.ts

index 81acdbaf72967fc3008ce4c98f9a6ec9c0095928..af9ad010e3a3c6db60f969450240cc66ca144cb5 100644 (file)
 
        const gauge = useContextGauge();
 
+       // The gauge hook wraps a processing state instance that only follows the
+       // live stream while its own monitoring flag is set, so the card instance
+       // starts monitoring like the dial does.
+       $effect(() => {
+               gauge.startMonitoring();
+       });
+
        let cardEl = $state<HTMLElement | null>(null);
 
        // Any press outside the card and outside the dial closes the card.
@@ -44,7 +51,7 @@
        <div
                role="status"
                bind:this={cardEl}
-               class="absolute z-50 w-64 -translate-x-1/2 rounded-lg border border-border/50 bg-popover p-3 text-popover-foreground shadow-lg"
+               class="absolute z-50 w-64 -translate-x-1/2 rounded-lg border border-border/50 bg-popover p-3 text-sm text-popover-foreground shadow-lg ring-1 ring-foreground/10"
                style="left: {gaugePopup.centerX}px; bottom: {gaugePopup.bottom}px"
                onpointerenter={gaugeCardEnter}
                onpointerleave={gaugeCardLeave}
index a0d66c6421780ecb8d1d0dd083cb81240216ead9..6e32fc7aa362639d0aa5cb77d517f97e6320ce06 100644 (file)
        import { config } from '$lib/stores/settings.svelte';
        import { serverLoading, serverError } from '$lib/stores/server.svelte';
        import { parseFilesToMessageExtras } from '$lib/utils/browser-only';
-       import { onDestroy, onMount } from 'svelte';
+       import { onDestroy, onMount, tick } from 'svelte';
        import ChatScreenGreeting from './ChatScreenGreeting.svelte';
        import ChatScreenActionScrollDown from './ChatScreenActionScrollDown.svelte';
        import ChatScreenDialogsAndAlerts from './ChatScreenDialogsAndAlerts.svelte';
-       import { ROUTES } from '$lib/constants';
+       import { LANDING_SETTLE_MAX_MS, LANDING_STABLE_FRAMES, ROUTES } from '$lib/constants';
 
        let { showCenteredEmpty = false } = $props();
 
                return true;
        }
 
+       let lastScrolledConversationId: string | null = null;
+
+       // Lands at the bottom of a conversation the first time its messages
+       // render, whether the route comes from another conversation or from a
+       // non-conversation route. The page keeps growing after the first pin
+       // without DOM mutations (content-visibility size realizations, syntax
+       // highlight passes), so the instant pin repeats every frame until the
+       // height settles, bailing out on user scroll or conversation change.
+       async function handleMessagesReady(messageCount: number) {
+               if (messageCount === 0) return;
+               const id = activeConversation()?.id ?? null;
+               if (!id || id === lastScrolledConversationId) return;
+               lastScrolledConversationId = id;
+               await tick();
+               autoScroll.scrollToBottom();
+
+               const container = scroll.chatScrollContainer;
+               if (!container) return;
+               const started = performance.now();
+               let stableFrames = 0;
+               let lastHeight = container.scrollHeight;
+               const settle = () => {
+                       if (autoScroll.userScrolledUp) return;
+                       if (activeConversation()?.id !== id) return;
+                       autoScroll.scrollToBottom();
+                       const height = container.scrollHeight;
+                       stableFrames = height === lastHeight ? stableFrames + 1 : 0;
+                       lastHeight = height;
+                       if (stableFrames >= LANDING_STABLE_FRAMES) return;
+                       if (performance.now() - started > LANDING_SETTLE_MAX_MS) return;
+                       requestAnimationFrame(settle);
+               };
+               requestAnimationFrame(settle);
+       }
+
        function handleSendLikeScroll() {
                if (!isMobile.current) {
                        autoScroll.enable();
                {#if !isEmpty}
                        <ChatMessages
                                messages={activeMessages()}
+                               onMessagesReady={handleMessagesReady}
                                onUserAction={() => {
                                        handleSendLikeScroll();
                                }}
index c629228eb485c4540c478156ae8f4d9cda46f3fe..67c5f930108d5630f590a06e9c06059dd29d411b 100644 (file)
@@ -1,4 +1,10 @@
 export const AUTO_SCROLL_INTERVAL = 100;
+// Conversation landing: the page keeps growing after the first bottom pin
+// without DOM mutations (content-visibility size realizations, syntax
+// highlight passes), so the pin repeats every frame until the height holds
+// for this many consecutive frames, bounded by the time cap below.
+export const LANDING_STABLE_FRAMES = 10;
+export const LANDING_SETTLE_MAX_MS = 1000;
 // Chat main view: tight threshold because scroll-here events come from
 // discrete assistant-message appends.
 export const AUTO_SCROLL_AT_BOTTOM_THRESHOLD = 10;