feat: the runs page's own header row moves into the page header

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) <noreply@anthropic.com>
This commit is contained in:
Guilhem Lemouel
2026-09-22 18:21:51 +02:00
co-authored by Claude Opus 5
parent 559ffb7285
commit 4293b7683a
9 changed files with 306 additions and 262 deletions
@@ -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}
</span>
{#if hint}{@render hint()}{/if}
{:else if routePage}
{@const RouteIcon = routePage.icon}
<span class="{SEGMENT} hover:bg-transparent hover:text-primary" aria-current="page">
<!-- shrink-0: a page's own name is short and is the one part of the trail worth keeping
whole, so the workspace and fork names give way first when the bar is full. -->
<span class="{SEGMENT} shrink-0 hover:bg-transparent hover:text-primary" aria-current="page">
{#if RouteIcon}<RouteIcon size={ICON} class="flex-shrink-0 text-tertiary" />{/if}
<span class="truncate">{routePage.label}</span>
</span>
{#if hint}{@render hint()}{/if}
{/if}
</nav>
@@ -32,7 +32,7 @@ The row's height matches the sidebar's own header row, so the two read as one ba
<div
data-page-header
class="flex items-center gap-1 h-10 px-2 shrink-0 min-w-0 bg-surface border-b {showBorder
class="flex items-center gap-1 h-11 px-2 shrink-0 min-w-0 bg-surface border-b {showBorder
? ''
: 'border-transparent'}"
>
@@ -54,7 +54,17 @@ The row's height matches the sidebar's own header row, so the two read as one ba
</div>
{/if}
<NavBreadcrumb {item} {section} actingWorkspaceId={content?.actingWorkspaceId} />
<!-- The breadcrumb yields width grudgingly (shrink-[0.1]): when a page fills the bar with
controls, they are what should narrow, not the name of where the user is. It still gives
way rather than pushing them off the bar once there is nothing left to take. -->
<div class="flex min-w-0 shrink-[0.1]">
<NavBreadcrumb
{item}
{section}
hint={content?.hint}
actingWorkspaceId={content?.actingWorkspaceId}
/>
</div>
{#if item && (item.summaryContent || item.summary)}
<!-- A dot rather than a slash: the summary names the same item the path just located, it is
@@ -72,7 +82,9 @@ The row's height matches the sidebar's own header row, so the two read as one ba
{/if}
{#if actions.length > 0}
<div class="ml-auto flex items-center gap-2 shrink-0">
<!-- min-w-0, not shrink-0: a page whose actions are a filter row (Runs) puts a control in
here that can give width back, and it can only do that if this box may shrink. -->
<div class="ml-auto flex items-center gap-2 min-w-0 pl-4">
{#each actions as entry, i (i)}
{#key entry.contexts}
<ContextBridge contexts={entry.contexts}>
@@ -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,
+221 -218
View File
@@ -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('')
</script>
{#snippet runsHint()}
<Tooltip
documentationLink="https://www.windmill.dev/docs/core_concepts/monitor_past_and_future_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.
</Tooltip>
{/snippet}
{#snippet headerActions()}
<!-- Always the icon form: the bar has room for two counters, never for the two labelled
blocks. pr-2 on top of the row's own gap because the counts hang outside their icons,
so without it the badge butts against the first filter. -->
<div class="flex items-center pr-2">
<RunsQueue
success={filters.val.status ?? null}
{queue_count}
{suspended_count}
onJobsWaiting={() => {
jobsFilter('waiting')
}}
onJobsSuspended={() => {
jobsFilter('suspended')
}}
small
/>
</div>
<!-- The two groups are the widest thing in the bar: they appear only once the bar is wide
enough to hold them next to the search field, below which the same filtering is reachable
from the search field's own `kind` and `status` chips. -->
<div class="hidden min-[1500px]:flex gap-2">
<ToggleButtonGroup
tabListClass="hidden min-[1500px]:flex"
bind:selected={
() => filters.val.job_kinds ?? 'runs',
(v) => (v === 'runs' ? delete filters.val.job_kinds : (filters.val.job_kinds = v))
}
>
{#snippet children({ item })}
<ToggleButton size="sm" value="all" label="All" {item} />
<ToggleButton
size="sm"
value="runs"
label="Runs"
tooltip="Runs are jobs that have no parent jobs (flows are jobs that are parent of the jobs they start), they have been triggered through the UI, a schedule or webhook"
{item}
/>
<ToggleButton
size="sm"
value="dependencies"
label="Deps"
tooltip="Deploying a script, flow or an app launch a dependency job that create and then attach the lockfile to the deployed item. This mechanism ensure that logic is always executed with the exact same direct and indirect dependencies."
{item}
/>
<ToggleButtonMore
small
hideSelectedOption={innerWidth < smallScreenWidth}
togglableItems={[
{
label: 'Previews',
value: 'previews',
tooltip: "Previews are jobs that have been started in the editor as 'Tests'"
},
{
label: 'Sync',
value: 'deploymentcallbacks',
tooltip:
'Sync jobs that are triggered on every script deployment to sync the workspace with the Git repository configured in the the workspace settings'
}
]}
{item}
bind:selected={filters.val.job_kinds}
/>
{/snippet}
</ToggleButtonGroup>
<ToggleButtonGroup
bind:selected={
() => filters.val.status ?? 'all',
(v) => (v === 'all' ? delete filters.val.status : (filters.val.status = v))
}
id="status"
>
{#snippet children({ item })}
<ToggleButton size="sm" value={'all'} label="All" {item} />
<ToggleButton
size="sm"
value={'running'}
tooltip="Running"
class="whitespace-nowrap"
icon={CirclePlay}
iconProps={{
class:
'group-data-[state=on]:text-yellow-600 dark:group-data-[state=on]:text-yellow-400'
}}
{item}
/>
<ToggleButton
size="sm"
value={'success'}
tooltip="Success"
class="whitespace-nowrap"
icon={CircleCheck}
iconProps={{
class: 'group-data-[state=on]:text-green-500 dark:group-data-[state=on]:text-green-300'
}}
{item}
/>
<ToggleButton
size="sm"
value={'failure'}
tooltip="Failure"
class="whitespace-nowrap"
icon={CircleAlert}
iconProps={{
class: 'group-data-[state=on]:text-red-500 dark:group-data-[state=on]:text-red-300'
}}
{item}
/>
<ToggleButton
size="sm"
value={'canceled'}
tooltip="Canceled"
class="whitespace-nowrap"
icon={Hourglass}
selectedColor="gray"
{item}
/>
{#if filters.val.status == 'waiting'}
<ToggleButton
size="sm"
value={'waiting'}
tooltip="Waiting"
class="whitespace-nowrap"
icon={Hourglass}
selectedColor="blue"
{item}
/>
{:else if filters.val.status == 'suspended'}
<ToggleButton
size="sm"
value={'suspended'}
tooltip="Suspended"
class="whitespace-nowrap"
icon={Hourglass}
selectedColor="purple"
{item}
/>
{/if}
{/snippet}
</ToggleButtonGroup>
</div>
<div class="hidden xl:flex gap-2 items-center">
{#if !filters.val.job_trigger_kind || filters.val.job_trigger_kind === '!schedule'}
<div class="flex items-center gap-1" title="Show schedules">
<Toggle
size="xs"
id="show-schedules"
bind:checked={
() => filters.val.job_trigger_kind !== '!schedule',
(v) =>
v ? delete filters.val.job_trigger_kind : (filters.val.job_trigger_kind = '!schedule')
}
/>
<Calendar size={14} />
</div>
{/if}
<div class="flex items-center gap-1" title="Show future jobs">
<Toggle
size="xs"
id="show-future-jobs"
bind:checked={
() => filters.val.show_future_jobs !== false,
(v) => (v ? delete filters.val.show_future_jobs : (filters.val.show_future_jobs = false))
}
/>
<Clock size={14} />
</div>
</div>
<!-- The one control in the band that gives way: everything else keeps its width and the
search field takes what is left, down to a width that still shows a filter chip. -->
<FilterSearchbar
class={twMerge(
'relative flex-1 min-w-[8rem]',
Object.keys(filters.val).length <= 3 ? 'max-w-[20rem]' : 'max-w-[26rem]',
ButtonType.UnifiedMinHeightClasses.sm
)}
schema={runsFilterSearchbarSchema}
presets={buildRunsFilterPresets({
isSuperAdminOrDevops: !!$superadmin || !!$devopsRole,
isAdminsWorkspace: $workspaceStore === 'admins'
})}
bind:value={filters.val}
placeholder="Filter runs..."
autofocus
/>
<TimeframeSelect
unifiedSize="sm"
onClick={() => jobsLoader?.loadJobs(true)}
loading={jobsLoader?.loading}
items={runsTimeframes}
bind:value={_timeframe.val}
/>
<!-- The filters are shallow-routed, so the search has to come off `window.location` at click
time — `page.url` never sees them. Always the canonical `/runs`: only that is a recognized
preview page, and the `/runs/<path>` route mirrors its path into `?path=` anyway. -->
<OpenInSessionButton
source={{
page: () => pageHref(RUNS_PATH) + window.location.search,
workspaceId: $workspaceStore ?? undefined
}}
btnProps={{ unifiedSize: 'sm' }}
/>
{/snippet}
<ConfirmationModal
title={askingForConfirmation?.title ?? ''}
confirmationText={askingForConfirmation?.confirmBtnText ?? ''}
@@ -650,226 +867,12 @@
</div>
{:else}
<div class="w-full h-screen flex flex-col" bind:clientWidth={innerWidth}>
<!-- Header and filters -->
<div id="runs-filters-bar" class="flex flex-row items-start w-full px-4 gap-3 py-4 flex-wrap">
<div class="flex flex-row items-center gap-6">
<h1
class={twMerge(
'!text-2xl font-semibold leading-6 tracking-tight',
$userStore?.operator ? 'pl-10' : ''
)}
>
Runs
</h1>
<Tooltip
documentationLink="https://www.windmill.dev/docs/core_concepts/monitor_past_and_future_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.
</Tooltip>
<!-- Queue -->
<RunsQueue
success={filters.val.status ?? null}
{queue_count}
{suspended_count}
onJobsWaiting={() => {
jobsFilter('waiting')
}}
onJobsSuspended={() => {
jobsFilter('suspended')
}}
small={innerWidth < smallScreenWidth}
/>
</div>
<div class="hidden xl:flex gap-2 ml-6">
<ToggleButtonGroup
tabListClass="hidden xl:flex"
bind:selected={
() => filters.val.job_kinds ?? 'runs',
(v) => (v === 'runs' ? delete filters.val.job_kinds : (filters.val.job_kinds = v))
}
>
{#snippet children({ item })}
<ToggleButton value="all" label="All" {item} />
<ToggleButton
value="runs"
label="Runs"
tooltip="Runs are jobs that have no parent jobs (flows are jobs that are parent of the jobs they start), they have been triggered through the UI, a schedule or webhook"
{item}
/>
<ToggleButton
value="dependencies"
label="Deps"
tooltip="Deploying a script, flow or an app launch a dependency job that create and then attach the lockfile to the deployed item. This mechanism ensure that logic is always executed with the exact same direct and indirect dependencies."
{item}
/>
<ToggleButtonMore
hideSelectedOption={innerWidth < smallScreenWidth}
togglableItems={[
{
label: 'Previews',
value: 'previews',
tooltip: "Previews are jobs that have been started in the editor as 'Tests'"
},
{
label: 'Sync',
value: 'deploymentcallbacks',
tooltip:
'Sync jobs that are triggered on every script deployment to sync the workspace with the Git repository configured in the the workspace settings'
}
]}
{item}
bind:selected={filters.val.job_kinds}
/>
{/snippet}
</ToggleButtonGroup>
<ToggleButtonGroup
bind:selected={
() => filters.val.status ?? 'all',
(v) => (v === 'all' ? delete filters.val.status : (filters.val.status = v))
}
id="status"
>
{#snippet children({ item })}
<ToggleButton value={'all'} label="All" {item} />
<ToggleButton
value={'running'}
tooltip="Running"
class="whitespace-nowrap"
icon={CirclePlay}
iconProps={{
class:
'group-data-[state=on]:text-yellow-600 dark:group-data-[state=on]:text-yellow-400'
}}
{item}
/>
<ToggleButton
value={'success'}
tooltip="Success"
class="whitespace-nowrap"
icon={CircleCheck}
iconProps={{
class:
'group-data-[state=on]:text-green-500 dark:group-data-[state=on]:text-green-300'
}}
{item}
/>
<ToggleButton
value={'failure'}
tooltip="Failure"
class="whitespace-nowrap"
icon={CircleAlert}
iconProps={{
class: 'group-data-[state=on]:text-red-500 dark:group-data-[state=on]:text-red-300'
}}
{item}
/>
<ToggleButton
value={'canceled'}
tooltip="Canceled"
class="whitespace-nowrap"
icon={Hourglass}
selectedColor="gray"
{item}
/>
{#if filters.val.status == 'waiting'}
<ToggleButton
value={'waiting'}
tooltip="Waiting"
class="whitespace-nowrap"
icon={Hourglass}
selectedColor="blue"
{item}
/>
{:else if filters.val.status == 'suspended'}
<ToggleButton
value={'suspended'}
tooltip="Suspended"
class="whitespace-nowrap"
icon={Hourglass}
selectedColor="purple"
{item}
/>
{/if}
{/snippet}
</ToggleButtonGroup>
</div>
<div class="hidden xl:flex gap-2 items-center min-h-8 ml-2">
{#if !filters.val.job_trigger_kind || filters.val.job_trigger_kind === '!schedule'}
<div class="flex items-center gap-1" title="Show schedules">
<Toggle
size="xs"
color="nord"
id="show-schedules"
bind:checked={
() => filters.val.job_trigger_kind !== '!schedule',
(v) =>
v
? delete filters.val.job_trigger_kind
: (filters.val.job_trigger_kind = '!schedule')
}
/>
<Calendar size={14} />
</div>
{/if}
<div class="flex items-center gap-1" title="Show future jobs">
<Toggle
size="xs"
color="nord"
id="show-future-jobs"
bind:checked={
() => filters.val.show_future_jobs !== false,
(v) =>
v ? delete filters.val.show_future_jobs : (filters.val.show_future_jobs = false)
}
/>
<Clock size={14} />
</div>
</div>
<TimeframeSelect
wrapperClasses="ml-auto"
onClick={() => jobsLoader?.loadJobs(true)}
loading={jobsLoader?.loading}
items={runsTimeframes}
bind:value={_timeframe.val}
/>
<FilterSearchbar
class={twMerge(
'flex-1 relative min-w-[18rem]',
Object.keys(filters.val).length <= 3
? 'max-w-[20rem] 2xl:max-w-[28rem]'
: 'max-w-[34rem]',
ButtonType.UnifiedMinHeightClasses.md
)}
schema={runsFilterSearchbarSchema}
presets={buildRunsFilterPresets({
isSuperAdminOrDevops: !!$superadmin || !!$devopsRole,
isAdminsWorkspace: $workspaceStore === 'admins'
})}
bind:value={filters.val}
placeholder="Filter runs..."
autofocus
/>
<!-- The filters are shallow-routed, so the search has to come off
`window.location` at click time — `page.url` never sees them. Always the
canonical `/runs`: only that is a recognized preview page, and the
`/runs/<path>` route mirrors its path into `?path=` anyway. -->
<OpenInSessionButton
source={{
page: () => pageHref(RUNS_PATH) + window.location.search,
workspaceId: $workspaceStore ?? undefined
}}
btnProps={{ unifiedSize: 'md' }}
/>
</div>
<!-- Everything that used to sit in a row above the graph — the page's name, its queue, the
filters and the timeframe — is in the page header. -->
<PageHeaderContent hint={runsHint} actions={headerActions} barBorder={false} />
<!-- Graph -->
<div id="runs-chart" class="p-2 px-4 bg-surface-tertiary mx-4 border rounded-md">
<div id="runs-chart" class="p-2 px-4 bg-surface-tertiary mx-4 mt-4 border rounded-md">
<div class="relative z-10 mb-2 flex gap-2">
<Tabs bind:selected={graph}>
<Tab value="RunChart" label="Duration" id="runs-chart-duration-tab" />
@@ -70,7 +70,14 @@
<div class="flex items-center">
<!-- The tooltip fixes a bug where the other tooltips won't disappear -->
<Tooltip disablePopup>
<DropdownV2 {btnText} enableFlyTransition {items} size={small ? 'sm' : 'md'} />
<!-- fixedHeight pins the wrapper at h-8, which overhangs a small group's 28px row. -->
<DropdownV2
{btnText}
enableFlyTransition
{items}
size={small ? 'sm' : 'md'}
fixedHeight={!small}
/>
</Tooltip>
</div>
</div>
@@ -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
@@ -28,7 +28,9 @@
{#if small}
<Popover contentClasses="p-4" openOnHover debounceDelay={100}>
{#snippet trigger()}
<div class="relative">
<!-- mr-2: the count badge hangs half outside this box, so the margin — not the row's
gap — is what keeps it off whatever sits to the right. -->
<div class="relative mr-2">
<ServerCog size={16} />
{#if queue_count && ($queue_count ?? 0) > 0}
<div
@@ -49,14 +51,11 @@
{/if}
{#if small}
<Popover
contentClasses="p-4"
openOnHover
debounceDelay={100}
disablePopup={!suspended_count || ($suspended_count ?? 0) <= 0}
>
<!-- The icon is on the bar whatever the count, so it answers a hover whatever the count —
a disabled trigger takes no pointer events and reads as a dead icon. -->
<Popover contentClasses="p-4" openOnHover debounceDelay={100}>
{#snippet trigger()}
<div class="relative">
<div class="relative mr-2">
<Hourglass size={16} />
<div
class="absolute top-0 right-0 translate-x-1/2 -translate-y-1/2 bg-surface-secondary-inverse rounded-full text-primary-inverse text-2xs h-4 min-w-4 px-1"
@@ -69,7 +68,7 @@
{@render suspendedContent()}
{/snippet}
</Popover>
{:else}
{:else if suspended_count && ($suspended_count ?? 0) > 0}
{@render suspendedContent()}
{/if}
</div>
@@ -103,28 +102,28 @@
{/snippet}
{#snippet suspendedContent()}
{#if suspended_count && ($suspended_count ?? 0) > 0}
<RunOption label="Suspended">
{#snippet tooltip()}
Jobs waiting for an event or approval before being resumed
{/snippet}
<div
class={suspended_count && ($suspended_count ?? 0) > 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) : '...'}</div
>
<div class="truncate text-2xs !text-secondary">
<Button unifiedSize="md" variant="subtle" on:click={() => onJobsSuspended?.()}>
{#if success == 'suspended'}
<div class="flex flex-row items-center gap-1">
Reset filter
<X size={12} />
</div>
{:else}
<ListFilterPlus size={14} />
{/if}
</Button>
</div>
</RunOption>
{/if}
<RunOption label="Suspended">
{#snippet tooltip()}
Jobs waiting for an event or approval before being resumed
{/snippet}
<div
class={suspended_count && ($suspended_count ?? 0) > 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) : '...'}
</div>
<div class="truncate text-2xs !text-secondary">
<Button unifiedSize="md" variant="subtle" on:click={() => onJobsSuspended?.()}>
{#if success == 'suspended'}
<div class="flex flex-row items-center gap-1">
Reset filter
<X size={12} />
</div>
{:else}
<ListFilterPlus size={14} />
{/if}
</Button>
</div>
</RunOption>
{/snippet}
@@ -77,7 +77,7 @@
<script lang="ts">
import { CalendarIcon, RefreshCw } from 'lucide-svelte'
import { Button } from '../common'
import { Button, type ButtonType } from '../common'
import ToggleButtonGroup from '../common/toggleButton-v2/ToggleButtonGroup.svelte'
import ToggleButton from '../common/toggleButton-v2/ToggleButton.svelte'
import Popover from '../meltComponents/Popover.svelte'
@@ -94,9 +94,18 @@
value: Timeframe
wrapperClasses?: string
onClick?: () => void
/** Sizes the three buttons of the control itself; the presets inside the popover keep md. */
unifiedSize?: ButtonType.UnifiedSize
}
let { loading = false, onClick, items, value = $bindable(), wrapperClasses }: Props = $props()
let {
loading = false,
onClick,
items,
value = $bindable(),
wrapperClasses,
unifiedSize = 'md'
}: Props = $props()
let isOpen = $state(false)
@@ -141,7 +150,7 @@
<div class="relative flex {wrapperClasses}">
<Button
unifiedSize="md"
{unifiedSize}
wrapperClasses="flex-1"
btnClasses="!rounded-r-none whitespace-nowrap"
onClick={() => onClick?.()}
@@ -154,7 +163,7 @@
{#if value.type === 'manual'}
<Button
btnClasses="!rounded-none border-l-0"
unifiedSize="md"
{unifiedSize}
onClick={() => (value = { ...items[0] })}
>
Reset
@@ -168,7 +177,7 @@
>
{#snippet trigger()}
<Button
unifiedSize="md"
{unifiedSize}
iconOnly
btnClasses="!rounded-l-none border-l-0"
endIcon={{ icon: CalendarIcon }}
@@ -932,7 +932,7 @@
<Pane
bind:size={chatPaneSize}
minSize={25}
class="flex flex-col min-h-0 {previewCollapsed || fullscreen ? '' : 'pt-10'}"
class="flex flex-col min-h-0 {previewCollapsed || fullscreen ? '' : 'pt-11'}"
>
<div class="relative flex-1 min-h-0">
{#each warmSessions as s (s.id)}