From 4293b7683a4e225b5953b9cd37af7cb49ccd4b42 Mon Sep 17 00:00:00 2001 From: Guilhem Lemouel Date: Tue, 22 Sep 2026 18:21:51 +0200 Subject: [PATCH] feat: the runs page's own header row moves into the page header MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The title, the queue counters, the kind and status filters, the schedule and future-job switches, the search field and the timeframe picker all ride the band now, and the page starts at the chart — one row of vertical space back. The two toggle groups appear from 1500px: below that the bar cannot hold them next to the search field, and the same filtering is reachable from the search field's own kind and status chips. The search field is what gives width back, and the breadcrumb yields last, so a page's own name stays whole while the workspace and fork names truncate. The queue counters keep their icon form at every width, since the bar has room for two counters and never for the two labelled blocks. Their counts hang outside their icons, so the group carries the margins that keep a badge off its neighbour, and the suspended counter answers a hover at zero rather than sitting there as a dead icon. The band is 44px rather than 40, and a page can hand the breadcrumb a hint — the documentation tooltip that belongs with the page's name rather than with its buttons. Co-Authored-By: Claude Opus 5 (1M context) --- .../src/lib/components/NavBreadcrumb.svelte | 10 +- .../src/lib/components/PageHeaderBar.svelte | 18 +- .../lib/components/PageHeaderContent.svelte | 2 + frontend/src/lib/components/RunsPage.svelte | 439 +++++++++--------- .../toggleButton-v2/ToggleButtonMore.svelte | 9 +- .../components/pageHeaderRegistry.svelte.ts | 4 + .../src/lib/components/runs/RunsQueue.svelte | 65 ++- .../components/runs/TimeframeSelect.svelte | 19 +- .../(root)/(logged)/sessions/+page.svelte | 2 +- 9 files changed, 306 insertions(+), 262 deletions(-) diff --git a/frontend/src/lib/components/NavBreadcrumb.svelte b/frontend/src/lib/components/NavBreadcrumb.svelte index b856e1a1d6..586ebcc4a1 100644 --- a/frontend/src/lib/components/NavBreadcrumb.svelte +++ b/frontend/src/lib/components/NavBreadcrumb.svelte @@ -49,14 +49,18 @@ same weight, size and icon size, so the line reads as one control rather than fo type WorkspaceItem } from '$lib/components/workspacePicker' import type { PageHeaderItem, PageHeaderSection } from './pageHeaderRegistry.svelte' + import type { Snippet } from 'svelte' let { item, section, + hint, actingWorkspaceId }: { item?: PageHeaderItem section?: PageHeaderSection + /** A page's own mark next to its name, such as its documentation tooltip. */ + hint?: Snippet /** The workspace the page acts on, when it differs from the one the app is pointed at. */ actingWorkspaceId?: string } = $props() @@ -296,11 +300,15 @@ same weight, size and icon size, so the line reads as one control rather than fo > {#if section.content}{@render section.content()}{:else}{section.label}{/if} + {#if hint}{@render hint()}{/if} {:else if routePage} {@const RouteIcon = routePage.icon} - + + {#if RouteIcon}{/if} {routePage.label} + {#if hint}{@render hint()}{/if} {/if} diff --git a/frontend/src/lib/components/PageHeaderBar.svelte b/frontend/src/lib/components/PageHeaderBar.svelte index e30c8bdda0..cf58195194 100644 --- a/frontend/src/lib/components/PageHeaderBar.svelte +++ b/frontend/src/lib/components/PageHeaderBar.svelte @@ -32,7 +32,7 @@ The row's height matches the sidebar's own header row, so the two read as one ba
@@ -54,7 +54,17 @@ The row's height matches the sidebar's own header row, so the two read as one ba
{/if} - + +
+ +
{#if item && (item.summaryContent || item.summary)} +
{#each actions as entry, i (i)} {#key entry.contexts} diff --git a/frontend/src/lib/components/PageHeaderContent.svelte b/frontend/src/lib/components/PageHeaderContent.svelte index 1b26702cc1..12ff926a11 100644 --- a/frontend/src/lib/components/PageHeaderContent.svelte +++ b/frontend/src/lib/components/PageHeaderContent.svelte @@ -13,6 +13,7 @@ the header, so nesting is decided by the caller rather than guessed from context let { item, section, + hint, actions, contexts, barPlacement, @@ -25,6 +26,7 @@ the header, so nesting is decided by the caller rather than guessed from context const id = pageHeader.register(() => ({ item, section, + hint, actions, contexts, barPlacement, diff --git a/frontend/src/lib/components/RunsPage.svelte b/frontend/src/lib/components/RunsPage.svelte index cac8952706..66deff1d80 100644 --- a/frontend/src/lib/components/RunsPage.svelte +++ b/frontend/src/lib/components/RunsPage.svelte @@ -44,6 +44,7 @@ import ConfirmationModal from '$lib/components/common/confirmationModal/ConfirmationModal.svelte' import RunsQueue from '$lib/components/runs/RunsQueue.svelte' import OpenInSessionButton from '$lib/components/sessions/OpenInSessionButton.svelte' + import PageHeaderContent from '$lib/components/PageHeaderContent.svelte' import { pageHref, RUNS_PATH } from '$lib/components/sessions/previewPaths' import { twMerge } from 'tailwind-merge' import { computeJobKinds, useJobsLoader } from '$lib/components/runs/useJobsLoader.svelte' @@ -584,6 +585,222 @@ let resolutionNote = $state('') +{#snippet runsHint()} + + All past and schedule executions of scripts and flows, including previews. You only see your own + runs or runs of groups you belong to unless you are an admin. + +{/snippet} + +{#snippet headerActions()} + +
+ { + jobsFilter('waiting') + }} + onJobsSuspended={() => { + jobsFilter('suspended') + }} + small + /> +
+ + + + + + + + jobsLoader?.loadJobs(true)} + loading={jobsLoader?.loading} + items={runsTimeframes} + bind:value={_timeframe.val} + /> + + pageHref(RUNS_PATH) + window.location.search, + workspaceId: $workspaceStore ?? undefined + }} + btnProps={{ unifiedSize: 'sm' }} + /> +{/snippet} + {:else}
- -
-
-

- Runs -

- - - All past and schedule executions of scripts and flows, including previews. You only see - your own runs or runs of groups you belong to unless you are an admin. - - - - { - jobsFilter('waiting') - }} - onJobsSuspended={() => { - jobsFilter('suspended') - }} - small={innerWidth < smallScreenWidth} - /> -
- - - - - - jobsLoader?.loadJobs(true)} - loading={jobsLoader?.loading} - items={runsTimeframes} - bind:value={_timeframe.val} - /> - - - pageHref(RUNS_PATH) + window.location.search, - workspaceId: $workspaceStore ?? undefined - }} - btnProps={{ unifiedSize: 'md' }} - /> -
+ + -
+
diff --git a/frontend/src/lib/components/common/toggleButton-v2/ToggleButtonMore.svelte b/frontend/src/lib/components/common/toggleButton-v2/ToggleButtonMore.svelte index 1300dc4322..86f04dfa9f 100644 --- a/frontend/src/lib/components/common/toggleButton-v2/ToggleButtonMore.svelte +++ b/frontend/src/lib/components/common/toggleButton-v2/ToggleButtonMore.svelte @@ -70,7 +70,14 @@
- + +
diff --git a/frontend/src/lib/components/pageHeaderRegistry.svelte.ts b/frontend/src/lib/components/pageHeaderRegistry.svelte.ts index 435da2757e..516e19965a 100644 --- a/frontend/src/lib/components/pageHeaderRegistry.svelte.ts +++ b/frontend/src/lib/components/pageHeaderRegistry.svelte.ts @@ -36,6 +36,9 @@ export type PageHeaderContent = { section?: PageHeaderSection /** Rendered at the right end of the bar — the page's own buttons. */ actions?: Snippet + /** Rendered right after the page's name in the breadcrumb, for what reads as part of that name + * rather than as an action — a page's documentation tooltip. */ + hint?: Snippet /** The workspace this page acts on, when it is not the one the app is pointed at — a session * running in a fork. The breadcrumb then names that workspace, and its fork part shows only * when it really is a fork. */ @@ -80,6 +83,7 @@ export const pageHeader = { const c = e.get() if (c.item !== undefined) merged.item = c.item if (c.section !== undefined) merged.section = c.section + if (c.hint !== undefined) merged.hint = c.hint if (c.contexts !== undefined) merged.contexts = c.contexts if (c.barPlacement !== undefined) merged.barPlacement = c.barPlacement if (c.actingWorkspaceId !== undefined) merged.actingWorkspaceId = c.actingWorkspaceId diff --git a/frontend/src/lib/components/runs/RunsQueue.svelte b/frontend/src/lib/components/runs/RunsQueue.svelte index 2cd2914878..4fbfe8842d 100644 --- a/frontend/src/lib/components/runs/RunsQueue.svelte +++ b/frontend/src/lib/components/runs/RunsQueue.svelte @@ -28,7 +28,9 @@ {#if small} {#snippet trigger()} -
+ +
{#if queue_count && ($queue_count ?? 0) > 0}
+ + {#snippet trigger()} -
+
- {:else} + {:else if suspended_count && ($suspended_count ?? 0) > 0} {@render suspendedContent()} {/if}
@@ -103,28 +102,28 @@ {/snippet} {#snippet suspendedContent()} - {#if suspended_count && ($suspended_count ?? 0) > 0} - - {#snippet tooltip()} - Jobs waiting for an event or approval before being resumed - {/snippet} -
0 - ? 'bg-surface-secondary-inverse text-primary-inverse rounded-full min-w-6 h-6 flex center-center' - : ''}>{suspended_count ? ($suspended_count ?? 0).toFixed(0) : '...'}
-
- -
-
- {/if} + + {#snippet tooltip()} + Jobs waiting for an event or approval before being resumed + {/snippet} +
0 + ? 'bg-surface-secondary-inverse text-primary-inverse rounded-full min-w-6 h-6 flex center-center' + : ''} + > + {suspended_count ? ($suspended_count ?? 0).toFixed(0) : '...'} +
+
+ +
+
{/snippet} diff --git a/frontend/src/lib/components/runs/TimeframeSelect.svelte b/frontend/src/lib/components/runs/TimeframeSelect.svelte index f8d2b0df71..f376b082d7 100644 --- a/frontend/src/lib/components/runs/TimeframeSelect.svelte +++ b/frontend/src/lib/components/runs/TimeframeSelect.svelte @@ -77,7 +77,7 @@