+ {#if sessionActivity.loading || !sessionActivity.loaded || !sessionActivity.isForSession(sessionId)}
+
Loading activity...
+ {:else if sessionActivity.error}
+
+ {sessionActivity.error}
+
+
+ {:else if sessionActivity.buckets.length === 0}
+
+ No timestamp data available
+
+ {:else if chart}
+
+
+
+
+ {#if tooltip}
+
+ {tooltip.text}
+
+ {/if}
+
+
+ {startTime}
+ {endTime}
+
+ {/if}
+
+
+
diff --git a/frontend/src/lib/components/content/MessageList.svelte b/frontend/src/lib/components/content/MessageList.svelte
index ce8f820f..dc0c16c7 100644
--- a/frontend/src/lib/components/content/MessageList.svelte
+++ b/frontend/src/lib/components/content/MessageList.svelte
@@ -18,6 +18,7 @@
} from "../../utils/content-parser.js";
import { isSystemMessage } from "../../utils/messages.js";
import { inSessionSearch } from "../../stores/inSessionSearch.svelte.js";
+ import { sessionActivity } from "../../stores/sessionActivity.svelte.js";
import SessionFindBar from "./SessionFindBar.svelte";
let containerRef: HTMLDivElement | undefined = $state(undefined);
@@ -126,25 +127,71 @@
};
}
+ function publishVisibleTimestamp() {
+ const v = virtualizer.instance;
+ if (!v) return;
+ const items = v.getVirtualItems();
+ // Skip overscanned items above the viewport.
+ const scrollTop = v.scrollOffset ?? 0;
+ for (const vi of items) {
+ if (vi.end <= scrollTop) continue;
+ const item =
+ displayItemsAsc[
+ ui.sortNewestFirst
+ ? displayItemsAsc.length - 1 - vi.index
+ : vi.index
+ ];
+ if (!item) continue;
+ const ts =
+ item.kind === "message"
+ ? item.message.timestamp
+ : item.timestamp;
+ if (ts) {
+ sessionActivity.firstVisibleTimestamp = ts;
+ return;
+ }
+ }
+ sessionActivity.firstVisibleTimestamp = null;
+ }
+
+ // Recompute visible timestamp when minimap opens or
+ // message content changes (e.g. SSE reload).
+ $effect(() => {
+ if (ui.activityMinimapOpen) {
+ // Track message array so the effect re-runs after
+ // content changes while the minimap is open.
+ void messages.messages.length;
+ publishVisibleTimestamp();
+ }
+ });
+
function handleScroll() {
if (!containerRef) return;
if (scrollRaf !== null) return;
scrollRaf = requestAnimationFrame(() => {
scrollRaf = null;
if (!containerRef) return;
- const items = virtualizer.instance?.getVirtualItems() ?? [];
+ const items =
+ virtualizer.instance?.getVirtualItems() ?? [];
if (items.length > 0 && messages.hasOlder) {
const firstVisible = items[0]!.index;
- const lastVisible = items[items.length - 1]!.index;
+ const lastVisible =
+ items[items.length - 1]!.index;
const threshold = 30;
if (
(ui.sortNewestFirst &&
- lastVisible >= displayItemsAsc.length - threshold) ||
- (!ui.sortNewestFirst && firstVisible <= threshold)
+ lastVisible >=
+ displayItemsAsc.length - threshold) ||
+ (!ui.sortNewestFirst &&
+ firstVisible <= threshold)
) {
messages.loadOlder();
}
}
+
+ if (ui.activityMinimapOpen) {
+ publishVisibleTimestamp();
+ }
});
}
diff --git a/frontend/src/lib/components/layout/SessionBreadcrumb.svelte b/frontend/src/lib/components/layout/SessionBreadcrumb.svelte
index c58a280b..456c76dd 100644
--- a/frontend/src/lib/components/layout/SessionBreadcrumb.svelte
+++ b/frontend/src/lib/components/layout/SessionBreadcrumb.svelte
@@ -20,6 +20,7 @@
import { inSessionSearch } from "../../stores/inSessionSearch.svelte.js";
import { messages as messagesStore } from "../../stores/messages.svelte.js";
+ import { ui } from "../../stores/ui.svelte.js";
interface Props {
session: Session | undefined;
@@ -46,15 +47,27 @@
.catch(() => {});
});
+ let resolvedSessionDirId: string | null = null;
$effect(() => {
- sessionDir = null;
- if (!session) return;
+ if (!session) {
+ sessionDir = null;
+ resolvedSessionDirId = null;
+ return;
+ }
const id = session.id;
+ if (id === resolvedSessionDirId) return;
+ sessionDir = null;
getSessionDirectory(id)
.then(({ path }) => {
- if (session?.id === id) sessionDir = path || null;
+ if (session?.id === id) {
+ sessionDir = path || null;
+ resolvedSessionDirId = id;
+ }
})
- .catch(() => {});
+ .catch(() => {
+ // Don't cache the ID on failure so the next
+ // session refresh retries the lookup.
+ });
});
let sessionContextTokens = $derived(session?.peak_context_tokens ?? 0);
@@ -494,6 +507,17 @@
{/if}
+