Spaces:
Running
Running
Tail-follow improvements in source
Browse files- src/lib/TraceViewer.svelte +28 -10
src/lib/TraceViewer.svelte
CHANGED
|
@@ -87,6 +87,7 @@
|
|
| 87 |
async function playback() {
|
| 88 |
playing = true;
|
| 89 |
skipFlag = false;
|
|
|
|
| 90 |
for (let mi = 0; mi < messages.length; mi++) {
|
| 91 |
if (skipFlag) break;
|
| 92 |
const msg = messages[mi];
|
|
@@ -106,6 +107,7 @@
|
|
| 106 |
await typeBlock(block);
|
| 107 |
} else {
|
| 108 |
await wait(90);
|
|
|
|
| 109 |
followTail();
|
| 110 |
}
|
| 111 |
}
|
|
@@ -130,22 +132,34 @@
|
|
| 130 |
for (let c = step; c < len; c += step) {
|
| 131 |
if (skipFlag) break;
|
| 132 |
block._typedText = full.slice(0, c);
|
| 133 |
-
await
|
| 134 |
followTail();
|
|
|
|
| 135 |
}
|
| 136 |
block._typedText = full;
|
| 137 |
-
|
| 138 |
followTail();
|
|
|
|
| 139 |
}
|
| 140 |
|
| 141 |
-
// Terminal-style tail:
|
| 142 |
-
//
|
|
|
|
|
|
|
|
|
|
|
|
|
| 143 |
function followTail() {
|
| 144 |
-
if (!listEl) return;
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 149 |
}
|
| 150 |
}
|
| 151 |
|
|
@@ -303,7 +317,11 @@
|
|
| 303 |
</div>
|
| 304 |
|
| 305 |
<!-- messages / log -->
|
| 306 |
-
<div
|
|
|
|
|
|
|
|
|
|
|
|
|
| 307 |
{#if loading}
|
| 308 |
<div class="flex items-baseline gap-2">
|
| 309 |
<span class="w-[1ch] text-center text-[#5f5f5c]"
|
|
|
|
| 87 |
async function playback() {
|
| 88 |
playing = true;
|
| 89 |
skipFlag = false;
|
| 90 |
+
userScrolledUp = false;
|
| 91 |
for (let mi = 0; mi < messages.length; mi++) {
|
| 92 |
if (skipFlag) break;
|
| 93 |
const msg = messages[mi];
|
|
|
|
| 107 |
await typeBlock(block);
|
| 108 |
} else {
|
| 109 |
await wait(90);
|
| 110 |
+
await tick();
|
| 111 |
followTail();
|
| 112 |
}
|
| 113 |
}
|
|
|
|
| 132 |
for (let c = step; c < len; c += step) {
|
| 133 |
if (skipFlag) break;
|
| 134 |
block._typedText = full.slice(0, c);
|
| 135 |
+
await tick();
|
| 136 |
followTail();
|
| 137 |
+
await wait(tickMs);
|
| 138 |
}
|
| 139 |
block._typedText = full;
|
| 140 |
+
await tick();
|
| 141 |
followTail();
|
| 142 |
+
block._typing = false;
|
| 143 |
}
|
| 144 |
|
| 145 |
+
// Terminal-style tail: during playback, keep the scroll container pinned
|
| 146 |
+
// to the bottom so new content scrolls line-by-line into view. If the user
|
| 147 |
+
// actively scrolls up during playback, stop tailing until playback ends.
|
| 148 |
+
let userScrolledUp = false;
|
| 149 |
+
let lastAutoScrollTop = 0;
|
| 150 |
+
|
| 151 |
function followTail() {
|
| 152 |
+
if (!listEl || !playing || userScrolledUp) return;
|
| 153 |
+
listEl.scrollTop = listEl.scrollHeight;
|
| 154 |
+
lastAutoScrollTop = listEl.scrollTop;
|
| 155 |
+
}
|
| 156 |
+
|
| 157 |
+
function onListScroll() {
|
| 158 |
+
if (!listEl || !playing) return;
|
| 159 |
+
// If the current scroll position differs from what we last wrote by more
|
| 160 |
+
// than a small threshold, treat it as user-initiated.
|
| 161 |
+
if (Math.abs(listEl.scrollTop - lastAutoScrollTop) > 40) {
|
| 162 |
+
userScrolledUp = true;
|
| 163 |
}
|
| 164 |
}
|
| 165 |
|
|
|
|
| 317 |
</div>
|
| 318 |
|
| 319 |
<!-- messages / log -->
|
| 320 |
+
<div
|
| 321 |
+
bind:this={listEl}
|
| 322 |
+
onscroll={onListScroll}
|
| 323 |
+
class="flex-1 overflow-y-auto px-5 py-3"
|
| 324 |
+
>
|
| 325 |
{#if loading}
|
| 326 |
<div class="flex items-baseline gap-2">
|
| 327 |
<span class="w-[1ch] text-center text-[#5f5f5c]"
|