diff --git a/web/src/lib/components/repo/pipelines/WorkflowLogs.stories.svelte b/web/src/lib/components/repo/pipelines/WorkflowLogs.stories.svelte index 176c187b2..e2ebd527c 100644 --- a/web/src/lib/components/repo/pipelines/WorkflowLogs.stories.svelte +++ b/web/src/lib/components/repo/pipelines/WorkflowLogs.stories.svelte @@ -25,14 +25,16 @@ // the tail stories need a log that actually grows, because the whole point is // what the viewport does between two frames. one feed each, or whichever story // you open second starts on an exhausted tail - const feed = () => { + const feed = (every = 250, frames = 1) => { const tail = createLogTail(); let steps = $state(tail.advance()); $effect(() => { const timer = setInterval(() => { - steps = tail.advance(); + let next = steps; + for (let i = 0; i < frames; i++) next = tail.advance(); + steps = next; if (tail.exhausted()) clearInterval(timer); - }, 250); + }, every); return () => clearInterval(timer); }); return { @@ -44,6 +46,8 @@ const live = feed(); const replayed = feed(); + // the app flushes once a frame, so a busy step lands several lines a tick + const burst = feed(60, 2); @@ -59,7 +63,24 @@ - + you scroll or select, then hands the viewport over and offers the way back. + the steps go through the template, because args are a snapshot: storybook + reads them once per story and never sees the feed move --> + + {#snippet template(args)} + + {/snippet} + - + + {#snippet template(args)} + + {/snippet} + + + + {#snippet template(args)} + + {/snippet} + diff --git a/web/src/lib/components/repo/pipelines/WorkflowLogs.svelte b/web/src/lib/components/repo/pipelines/WorkflowLogs.svelte index 47becc804..81a6601b8 100644 --- a/web/src/lib/components/repo/pipelines/WorkflowLogs.svelte +++ b/web/src/lib/components/repo/pipelines/WorkflowLogs.svelte @@ -39,9 +39,14 @@ // reader took over const END_SLACK = 8; - // a scroll event lands a frame or two after the virtualizer sets scrollTop, so - // anything inside this window is ours and never counts as the reader scrolling - const COMMAND_WINDOW = 120; + // a row measuring taller than its estimate parks the viewport short of the end, + // so anything tighter than this strands the follow there + const NEAR_END = 70; + + const distanceFromEnd = (scroller: HTMLElement): number => + scroller.scrollHeight - scroller.scrollTop - scroller.clientHeight; + + const OFFSET_SLACK = 1; - + (pressing = false)} + onpointercancel={() => (pressing = false)} + onpointermove={(event) => { + // a release outside the window never reaches us + if (event.buttons === 0) pressing = false; + }} +/> {#if steps.length === 0}
@@ -249,6 +327,7 @@
@@ -335,7 +414,7 @@ {/each}
- {#if live && detached} + {#if behindTail}