feat: compact inline title badges on home rows

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Diego Imbert
2026-10-07 15:18:04 +02:00
co-authored by Claude Opus 5.5
parent acd13b0f9c
commit 1ab2c6d681
9 changed files with 73 additions and 50 deletions
@@ -237,9 +237,6 @@
</span>
{:else}
<Badge small color="indigo" class="gap-0.5">
{#if orderedUsers.length > 0}
{@render circleStack()}
{/if}
{draft_only ? 'Draft only' : 'Draft'}
</Badge>
{/if}
@@ -3,9 +3,9 @@
import { Badge } from './common'
</script>
<Popover notClickable>
<Popover notClickable class="inline-flex items-center">
{#snippet text()}
Library script (no exported main function)
{/snippet}
<Badge small color="yellow" baseClass="border">Library</Badge>
<Badge small color="yellow">Library</Badge>
</Popover>
@@ -63,7 +63,7 @@
</script>
{#if kind === 'read' || kind === 'write'}
<Popover notClickable>
<Popover notClickable class="inline-flex items-center">
<Badge capitalize color="blue" icon={{ icon: Users, position: 'right' }}></Badge>
{#snippet text()}
<span>{kind == 'read' ? 'Read & Run only' : 'Read & Write'} {reason}</span>
@@ -2,6 +2,7 @@
import { twMerge } from 'tailwind-merge'
import { type BadgeColor, type BadgeIconProps, ColorModifier } from './model'
import { X } from 'lucide-svelte'
import { isCompactBadges } from './density'
interface Props {
color?: BadgeColor
@@ -48,6 +49,7 @@
}: Props = $props()
let hidden = $state(false)
const compact = isCompactBadges()
const colors: Record<BadgeColor, string> = {
gray: 'bg-surface-sunken text-primary',
blue: 'bg-blue-50 text-blue-800 dark:text-blue-100 dark:bg-blue-700/40',
@@ -126,7 +128,8 @@
: 'rounded-md px-2 py-0.5',
verySmall ? 'px-0.5 py-0.5' : '',
'flex flex-row gap-1 items-center justify-center',
classNames
classNames,
compact ? 'text-3xs px-1 py-0 gap-0.5' : ''
)
)
const handleHide = () => (hidden = !hidden)
@@ -148,11 +151,19 @@
role={clickable ? 'button' : undefined}
>
{#if icon?.icon && icon.position === 'left'}
<icon.icon size={12} />
<icon.icon size={compact ? 10 : 12} />
{/if}
{#if compact}
<!-- `contents` keeps the children in the badge's flex layout; only the
text colour is softened, not the background. -->
<span class="contents [color:color-mix(in_srgb,currentColor_85%,transparent)]">
{@render children?.()}
</span>
{:else}
{@render children?.()}
{/if}
{@render children?.()}
{#if icon?.icon && icon.position === 'right'}
<icon.icon size={12} />
<icon.icon size={compact ? 10 : 12} />
{/if}
{#if dismissable}
<button onclick={handleHide}>
@@ -0,0 +1,12 @@
import { getContext, setContext } from 'svelte'
const CONTEXT_KEY = 'compactBadges'
/** Render every `Badge` below the calling component in its compact density. */
export function setCompactBadges() {
setContext(CONTEXT_KEY, true)
}
export function isCompactBadges(): boolean {
return getContext<boolean | undefined>(CONTEXT_KEY) ?? false
}
@@ -157,7 +157,7 @@
<InheritedLabels labels={app.inherited_labels} />
{/snippet}
{#snippet actions()}
<span class="hidden md:inline-flex gap-x-1">
<span class="hidden md:inline-flex gap-x-2">
{#if !$userStore?.operator}
{#if showEditButton && app.canWrite}
<div>
@@ -191,7 +191,7 @@
<InheritedLabels labels={flow.inherited_labels} />
{/snippet}
{#snippet actions()}
<span class="hidden md:inline-flex gap-x-1">
<span class="hidden md:inline-flex gap-x-2">
{#if !hideForOperator && showEditButton && flow.canWrite && !flow.archived}
<div>
<Button
@@ -10,6 +10,7 @@
import Checkbox from '../checkbox/Checkbox.svelte'
import type { RowSelection } from './rowSelection'
import RunnableActivityCells from '$lib/components/home/RunnableActivityCells.svelte'
import { setCompactBadges } from '../badge/density'
interface Props {
marked: string | undefined
@@ -118,6 +119,8 @@
?.split('/')
?.slice(-1)?.[0]) ?? ''
setCompactBadges()
let rowEl: HTMLDivElement | undefined = $state()
$effect(() => {
if (keyboardSelected) {
@@ -181,7 +184,7 @@
data-row-selection-key={rowSelection?.key}
data-row-keyboard-selected={keyboardSelected ? 'true' : undefined}
class={twMerge(
'group/row relative w-full inline-flex items-center gap-4 first-of-type:!border-t-0 first-of-type:rounded-t-md last-of-type:rounded-b-md [*:not(:last-child)]:border-b px-4 py-3 border-b last:border-b-0',
'group/row relative w-full inline-flex items-center gap-4 first-of-type:!border-t-0 first-of-type:rounded-t-md last-of-type:rounded-b-md [*:not(:last-child)]:border-b pl-4 pr-1 py-3 border-b last:border-b-0',
depth > 0 ? '!rounded-none' : '',
disabled ? 'opacity-25' : 'hover:bg-surface-hover',
clickToSelect || inSelectionMode ? 'cursor-pointer select-none' : '',
@@ -246,30 +249,22 @@
</div>
{/if}
<!-- The link covers the whole row rather than wrapping the title, so the
badges on the title line can hold their own popovers and buttons. -->
{#if href && !inSelectionMode}
<a
{href}
class="min-w-0 hover:underline decoration-gray-400 inline-flex items-center gap-4 after:absolute after:inset-0 after:content-['']"
>
{@render rowContent(!rowSelection)}
</a>
{:else}
{@render rowContent(!rowSelection)}
aria-label={!summary || summary.length == 0 ? displayPath : summary}
class="absolute inset-0"
></a>
{/if}
{#if badges}
<div class="relative z-[1] hidden lg:flex flex-row gap-1.5 items-center">
{@render badges?.()}
</div>
{/if}
<div class="grow"></div>
{@render rowContent(!rowSelection, !!href && !inSelectionMode)}
{#if errorHandlerMuted}
<BellOff class="w-8 opacity-60" size={12} fill="currentcolor" />
{/if}
<div class="relative z-[1] flex items-center gap-1.5 shrink-0">
<div class="relative z-[1] flex items-center gap-3 shrink-0">
{#if kind == 'script' || kind == 'flow' || kind == 'app' || kind == 'raw_app' || kind == 'agent'}
<RunnableActivityCells
kind={kind == 'script' || kind == 'flow' ? kind : undefined}
@@ -291,33 +286,41 @@
<div class="w-7"></div>
{/if}
<div data-row-actions class="flex gap-1 items-center justify-end pr-2">
<div data-row-actions class="flex gap-2 items-center justify-end">
{@render actions?.()}
</div>
</div>
</div>
{#snippet rowContent(withIcon: boolean)}
{#snippet rowContent(withIcon: boolean, linked: boolean)}
{#if withIcon}
<div class="shrink">
<RowIcon {kind} {triggerKind} />
</div>
{/if}
<div class="min-w-0">
<div
class={twMerge(
'text-emphasis flex-wrap text-left text-xs font-semibold',
titleBadge ? 'inline-flex items-center gap-2' : ''
)}
>
{#if customSummary}
{@render customSummary?.()}
{:else if marked}
{@html marked}
{:else}
{!summary || summary.length == 0 ? displayPath : summary}
<div class="min-w-0 grow">
<div class="flex items-center gap-1.5 min-w-0">
<div
class={twMerge(
'text-emphasis flex-wrap text-left text-xs font-semibold min-w-0',
titleBadge ? 'inline-flex items-center gap-2' : '',
linked ? 'group-hover/row:underline decoration-gray-400' : ''
)}
>
{#if customSummary}
{@render customSummary?.()}
{:else if marked}
{@html marked}
{:else}
{!summary || summary.length == 0 ? displayPath : summary}
{/if}
{@render titleBadge?.()}
</div>
{#if badges}
<div class="relative z-[1] hidden lg:flex flex-row gap-1 items-center shrink-0">
{@render badges()}
</div>
{/if}
{@render titleBadge?.()}
</div>
<div class="text-hint text-3xs truncate text-left font-normal" title={path}>
{#if pathDisplay}
@@ -173,6 +173,9 @@
{rowSelection}
>
{#snippet badges()}
{#if script.language}
<LanguageIcon lang={script.language} width={14} height={14} />
{/if}
{#if script.lock_error_logs}
<Badge color="red" baseClass="border border-red-200">Deployment failed</Badge>
{/if}
@@ -185,7 +188,7 @@
<NoMainFuncBadge />
{/if}
{#if script.auto_kind === 'wac'}
<Popover notClickable>
<Popover notClickable class="inline-flex items-center">
{#snippet text()}
Workflow-as-Code
{/snippet}
@@ -193,7 +196,7 @@
</Popover>
{/if}
{#if script.auto_kind === 'test'}
<Popover notClickable>
<Popover notClickable class="inline-flex items-center">
{#snippet text()}
CI test script
{/snippet}
@@ -237,13 +240,10 @@
</div>
{/if}
<InheritedLabels labels={script.inherited_labels} />
<div class="w-5 center-center">
<LanguageIcon lang={script.language} width={16} height={16} />
</div>
{/snippet}
{#snippet actions()}
<span class="hidden md:inline-flex gap-x-1">
<span class="hidden md:inline-flex gap-x-2">
{#if !$userStore?.operator}
{#if showEditButton}
{#if script.use_codebase}