diff --git a/frontend/src/lib/components/PageHeaderBar.svelte b/frontend/src/lib/components/PageHeaderBar.svelte
index 56377d6382..af6dfba47a 100644
--- a/frontend/src/lib/components/PageHeaderBar.svelte
+++ b/frontend/src/lib/components/PageHeaderBar.svelte
@@ -152,173 +152,199 @@ The row's height matches the sidebar's own header row, so the two read as one ba
data-page-header
bind:clientWidth={() => pageHeader.barWidth, (w) => pageHeader.setBarWidth(w)}
class={twMerge(
- 'flex items-center gap-1 shrink-0 min-w-0 bg-surface transition-shadow duration-150',
- // One row at every width. A wrapping row never squeezes anything — flex moves an item to the
- // next line instead of taking it below its content width — so wrapping here would hand the
- // trail a line of its own and leave it whole, which is the opposite of what a narrow bar
- // wants: the trail truncating from its start.
- phone ? `${BAR_HEIGHT} pl-1 pr-2` : `${BAR_HEIGHT} pl-2 pr-4`,
- // The filling surface is what meets the right edge, so the bar keeps no gutter of its own
- // there.
- content?.actionsFill && 'pr-0',
- // The small left padding is there to sit a button against: the sidebar handle, or the
- // workspace disc, both of which carry their own visual margin. Embedded there is neither,
- // and the page's name would start as bare text 8px from the edge.
- navHidden && 'pl-4',
+ 'flex flex-col shrink-0 min-w-0 bg-surface transition-shadow duration-150',
showEdge &&
'shadow-[inset_0_-1px_0_0_rgb(var(--color-border-light))] dark:shadow-[inset_0_-1px_0_0_#374151] [html.github-dark_&]:shadow-[inset_0_-1px_0_0_rgb(var(--color-border-light))]'
)}
>
- {#if (navDetached.val || panelled) && !navHidden}
-
-
-
{/if}
diff --git a/frontend/src/lib/components/PageHeaderContent.svelte b/frontend/src/lib/components/PageHeaderContent.svelte
index 3d95e74796..76dd0834b0 100644
--- a/frontend/src/lib/components/PageHeaderContent.svelte
+++ b/frontend/src/lib/components/PageHeaderContent.svelte
@@ -19,6 +19,8 @@ the header, so nesting is decided by the caller rather than guessed from context
actionsFlexible,
actionsFill,
actionFolds,
+ belowBar,
+ belowBarFold,
separator,
contexts,
actingWorkspaceId,
@@ -35,6 +37,8 @@ the header, so nesting is decided by the caller rather than guessed from context
actionsFlexible,
actionsFill,
actionFolds,
+ belowBar,
+ belowBarFold,
separator,
contexts,
actingWorkspaceId,
diff --git a/frontend/src/lib/components/RunsPage.svelte b/frontend/src/lib/components/RunsPage.svelte
index 7a130869b5..73f715d321 100644
--- a/frontend/src/lib/components/RunsPage.svelte
+++ b/frontend/src/lib/components/RunsPage.svelte
@@ -37,9 +37,8 @@
import RunsQueue from '$lib/components/runs/RunsQueue.svelte'
import OpenInSessionButton from '$lib/components/sessions/OpenInSessionButton.svelte'
import PageHeaderContent from '$lib/components/PageHeaderContent.svelte'
- import { pageHeader, PHONE_BAR } from '$lib/components/pageHeaderRegistry.svelte'
+ import { pageHeader } from '$lib/components/pageHeaderRegistry.svelte'
import { RUNS_PATH } from '$lib/components/sessions/previewPaths'
- import { twMerge } from 'tailwind-merge'
import { computeJobKinds, useJobsLoader } from '$lib/components/runs/useJobsLoader.svelte'
import ConcurrentJobsChart from '$lib/components/ConcurrentJobsChart.svelte'
import BatchLoadProgress from '$lib/components/BatchLoadProgress.svelte'
@@ -603,7 +602,11 @@
openedRunId = undefined
}
let resolutionNote = $state('')
- const phone = $derived(pageHeader.barWidth > 0 && pageHeader.barWidth < PHONE_BAR)
+ // The bar folds this row (`actionFolds` below) once its breadcrumb has given all it can: first
+ // the counters, which are a glance and repeat the chart below; then the session button; then
+ // the filter field moves to a row of its own rather than shrinking past usable.
+ const fold = $derived(pageHeader.actionFold)
+ const filterBelow = $derived(fold >= 3)
{#snippet runsHint()}
@@ -615,18 +618,34 @@
{/snippet}
+{#snippet runsFilter(cls: string)}
+
+{/snippet}
+
+{#snippet filterRow()}
+ {@render runsFilter('relative w-full min-w-0')}
+{/snippet}
+
{#snippet headerActions()}
-
+ two or three buttons, so they take a wider gap than the band's own. Every control keeps its
+ width: the bar measures the row and folds it rather than squeezing it. -->
+
-
+ so without it the badge butts against the first filter. -->
+