diff --git a/frontend/src/lib/components/common/tabs/Tab.svelte b/frontend/src/lib/components/common/tabs/Tab.svelte
index a652525b45..edb1135bbc 100644
--- a/frontend/src/lib/components/common/tabs/Tab.svelte
+++ b/frontend/src/lib/components/common/tabs/Tab.svelte
@@ -99,6 +99,7 @@
}}
{disabled}
{id}
+ data-tab-selected={isSelected ? 'true' : undefined}
>
('[data-tab-selected="true"]')
+ bar = el ? { x: el.offsetLeft, w: el.offsetWidth } : { x: bar.x, w: 0 }
+ }
+
+ // Placing the bar for the first paint. Every later move comes from the observers below: a
+ // Tab marks itself selected in its own update, which has not run when an effect here does,
+ // so measuring from this side alone lands the bar on the tab that was selected before.
+ $effect(() => {
+ if (!slidingIndicator) return
+ void row
+ measureBar()
+ })
+
+ $effect(() => {
+ if (!slidingIndicator || !row) return
+ const ro = new ResizeObserver(measureBar)
+ ro.observe(row)
+ // The mark moving is the selection changing; a tab added or removed, or one that grows a
+ // count as its content arrives, changes what the bar has to sit on.
+ const mo = new MutationObserver(measureBar)
+ mo.observe(row, {
+ childList: true,
+ subtree: true,
+ attributes: true,
+ attributeFilter: ['data-tab-selected']
+ })
+ return () => {
+ ro.disconnect()
+ mo.disconnect()
+ }
+ })
+
let hashValues = $derived(values ? values.map((x) => '#' + x) : undefined)
function hashChange() {
@@ -79,8 +129,26 @@
-
+
{@render children?.({ selected })}
+ {#if slidingIndicator}
+
+ {/if}
{/if}
diff --git a/frontend/src/lib/components/copilot/chat/RunScriptCard.svelte b/frontend/src/lib/components/copilot/chat/RunScriptCard.svelte
index 4ae3cc9b3d..23e66e7d43 100644
--- a/frontend/src/lib/components/copilot/chat/RunScriptCard.svelte
+++ b/frontend/src/lib/components/copilot/chat/RunScriptCard.svelte
@@ -1,4 +1,7 @@