generated from nhcarrigan/template
feat: productivity suite — task loop, workflow, theming, docs & more #197
@@ -1,6 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { claudeStore, type TerminalLine } from "$lib/stores/claude";
|
import { claudeStore, type TerminalLine } from "$lib/stores/claude";
|
||||||
import { afterUpdate, tick, onMount, onDestroy } from "svelte";
|
import { afterUpdate, tick, onMount, onDestroy } from "svelte";
|
||||||
|
import { get } from "svelte/store";
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
|
|
||||||
import ConversationTabs from "./ConversationTabs.svelte";
|
import ConversationTabs from "./ConversationTabs.svelte";
|
||||||
@@ -25,6 +26,7 @@
|
|||||||
let isRestoringScroll = false;
|
let isRestoringScroll = false;
|
||||||
let windowStart = 0;
|
let windowStart = 0;
|
||||||
let isLoadingMore = false;
|
let isLoadingMore = false;
|
||||||
|
let isSwitchingConversation = false;
|
||||||
|
|
||||||
let searchDebounceTimer: ReturnType<typeof setTimeout> | null = null;
|
let searchDebounceTimer: ReturnType<typeof setTimeout> | null = null;
|
||||||
searchQuery.subscribe((value) => {
|
searchQuery.subscribe((value) => {
|
||||||
@@ -59,24 +61,39 @@
|
|||||||
|
|
||||||
currentConversationId = newId;
|
currentConversationId = newId;
|
||||||
|
|
||||||
// Peek at the saved position to set windowStart before the first tick,
|
// Guard the $: reactive auto-scroll block from firing with stale `lines`
|
||||||
// preventing a stale windowStart from a previous conversation leaving
|
// (the old conversation's data) during the switch. Without this, Svelte's
|
||||||
// visibleLines empty (windowStart >= lines.length).
|
// reactive system can re-run the window-advance block before `terminalLines`
|
||||||
|
// has recomputed for the new conversation, overriding our correct windowStart.
|
||||||
|
isSwitchingConversation = true;
|
||||||
|
|
||||||
|
// Read the new conversation's lines directly from the store — the derived
|
||||||
|
// `terminalLines` store (and thus `lines`) may not have recomputed yet when
|
||||||
|
// this subscriber fires, so using `lines` here would give stale data.
|
||||||
|
const newConvLines = get(claudeStore.conversations).get(newId)?.terminalLines ?? [];
|
||||||
const savedPosition = claudeStore.getScrollPosition(newId);
|
const savedPosition = claudeStore.getScrollPosition(newId);
|
||||||
if (savedPosition === -1) {
|
if (savedPosition === -1) {
|
||||||
// Will auto-scroll: pin the window to the tail of the new conversation
|
// Will auto-scroll: pin the window to the tail of the new conversation
|
||||||
shouldAutoScroll = true;
|
shouldAutoScroll = true;
|
||||||
windowStart = Math.max(0, lines.length - WINDOW_SIZE);
|
windowStart = Math.max(0, newConvLines.length - WINDOW_SIZE);
|
||||||
} else {
|
} else {
|
||||||
// Will restore a specific position: always start from the top of history
|
// Will restore a specific position: always start from the top of history
|
||||||
shouldAutoScroll = false;
|
shouldAutoScroll = false;
|
||||||
windowStart = 0;
|
windowStart = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore scroll position for the new conversation after DOM updates
|
// Block the scroll handler during the entire DOM transition — scroll events
|
||||||
await tick();
|
// can fire mid-tick when the content changes, and handleScroll would see
|
||||||
if (terminalElement) {
|
// scrollTop not at the bottom yet and set shouldAutoScroll = false, breaking
|
||||||
|
// autoscroll for the new conversation permanently.
|
||||||
isRestoringScroll = true;
|
isRestoringScroll = true;
|
||||||
|
|
||||||
|
// Restore scroll position for the new conversation after DOM updates.
|
||||||
|
// Clear the switch guard first so the $: block can react to new lines
|
||||||
|
// arriving after the switch settles.
|
||||||
|
await tick();
|
||||||
|
isSwitchingConversation = false;
|
||||||
|
if (terminalElement) {
|
||||||
if (savedPosition === -1) {
|
if (savedPosition === -1) {
|
||||||
terminalElement.scrollTop = terminalElement.scrollHeight;
|
terminalElement.scrollTop = terminalElement.scrollHeight;
|
||||||
} else {
|
} else {
|
||||||
@@ -94,8 +111,10 @@
|
|||||||
const { scrollTop, scrollHeight, clientHeight } = terminalElement;
|
const { scrollTop, scrollHeight, clientHeight } = terminalElement;
|
||||||
shouldAutoScroll = scrollHeight - scrollTop - clientHeight < 100;
|
shouldAutoScroll = scrollHeight - scrollTop - clientHeight < 100;
|
||||||
|
|
||||||
// Load older lines when the user scrolls near the top
|
// Load older lines when the user scrolls near the top of the visible window.
|
||||||
if (scrollTop < 300 && windowStart > 0 && !isLoadingMore) {
|
// Use windowStart * AVG_LINE_HEIGHT (the spacer height) as the baseline so
|
||||||
|
// we trigger at the top of the rendered content, not the absolute container top.
|
||||||
|
if (scrollTop < windowStart * AVG_LINE_HEIGHT + 300 && windowStart > 0 && !isLoadingMore) {
|
||||||
isLoadingMore = true;
|
isLoadingMore = true;
|
||||||
const prevScrollHeight = terminalElement.scrollHeight;
|
const prevScrollHeight = terminalElement.scrollHeight;
|
||||||
const prevScrollTop = terminalElement.scrollTop;
|
const prevScrollTop = terminalElement.scrollTop;
|
||||||
@@ -182,8 +201,10 @@
|
|||||||
// Height of the invisible spacer above the visible window
|
// Height of the invisible spacer above the visible window
|
||||||
$: topSpacerHeight = windowStart * AVG_LINE_HEIGHT;
|
$: topSpacerHeight = windowStart * AVG_LINE_HEIGHT;
|
||||||
|
|
||||||
// Advance the window forward when auto-scrolling and new lines overflow it
|
// Advance the window forward when auto-scrolling and new lines overflow it.
|
||||||
$: if (shouldAutoScroll && lines.length > windowStart + WINDOW_SIZE) {
|
// Skip during conversation switches — `lines` may still hold the previous
|
||||||
|
// conversation's data, which would push windowStart past the new conv's end.
|
||||||
|
$: if (shouldAutoScroll && !isSwitchingConversation && lines.length > windowStart + WINDOW_SIZE) {
|
||||||
windowStart = Math.max(0, lines.length - WINDOW_SIZE);
|
windowStart = Math.max(0, lines.length - WINDOW_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user