fix(blocks): give the detail headers the same tile as everything else (#6672)

The tile consolidation left the large detail headers on their own treatment,
so a block wore one colour on the canvas and another in the header naming it:
the preview panel behind the deploy modal and the logs trace detail both
painted straight from the catalog `bgColor`, showing Start's catalog blue over
a neutral card, and hardcoded `#2FB3FF`/`#FEE12B` for the two subflows.

Adds the 18px header slot to `BlockTile` and points all three headers at it —
preview panel, trace detail, and the editor header, which had been carrying its
own inline copy of the accent rule. `WorkflowTypeIcon` takes an icon class so
the accent chip can draw the larger glyph the header uses.
This commit is contained in:
Waleed
2026-08-13 12:34:19 -07:00
committed by GitHub
parent 1a62c44913
commit 85c8451a86
5 changed files with 53 additions and 84 deletions
@@ -47,7 +47,6 @@ import {
} from '@/app/workspace/[workspaceId]/logs/components/log-details/utils'
import { BlockTile } from '@/blocks/block-tile'
import { isCustomBlockType } from '@/blocks/custom/build-config'
import { getTileIconColorClass } from '@/blocks/icon-color'
import { useCodeViewerFeatures } from '@/hooks/use-code-viewer'
const DEFAULT_TREE_PANE_WIDTH = 240
@@ -708,14 +707,13 @@ const TraceDetailPane = memo(function TraceDetailPane({ span }: { span: TraceSpa
<div className='flex min-h-0 flex-1 flex-col gap-3 overflow-y-auto px-3.5 pt-3 pb-4'>
<div className='flex items-start gap-2'>
{!isIterationType(span.type) && (
<div
className='mt-[2px] flex size-[18px] flex-shrink-0 items-center justify-center overflow-hidden rounded-sm [&_img]:size-full'
style={{ background: bgColor }}
>
{BlockIcon && (
<BlockIcon className={cn('size-[12px]', getTileIconColorClass(bgColor))} />
)}
</div>
<BlockTile
blockType={span.type?.toLowerCase() ?? ''}
icon={BlockIcon ?? undefined}
bgColor={bgColor}
size='lg'
className='mt-[2px]'
/>
)}
<div className='flex min-w-0 flex-1 flex-col gap-0.5'>
<h3
@@ -1,7 +1,7 @@
'use client'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { Button, ChipTag, DashedDividerLine, FieldDivider, Loader, Tooltip } from '@sim/emcn'
import { Button, DashedDividerLine, FieldDivider, Loader, Tooltip } from '@sim/emcn'
import {
BookOpen,
Check,
@@ -12,7 +12,6 @@ import {
SquareArrowUpRight,
Unlock,
} from '@sim/emcn/icons'
import { getWorkflowTypeAccent } from '@sim/workflow-renderer'
import type { BlockRetryConfig } from '@sim/workflow-types/workflow'
import { isEqual } from 'es-toolkit'
import { useParams } from 'next/navigation'
@@ -54,8 +53,7 @@ import {
isBlockProtected,
} from '@/app/workspace/[workspaceId]/w/[workflowId]/utils/block-protection-utils'
import { PreviewWorkflow } from '@/app/workspace/[workspaceId]/w/components/preview'
import { hasBlockAccent } from '@/blocks/accent'
import { isLightTileColor } from '@/blocks/icon-color'
import { BlockTile } from '@/blocks/block-tile'
import { getBlock } from '@/blocks/registry'
import { useFolderMap } from '@/hooks/queries/folders'
import { isWorkflowEffectivelyLocked } from '@/hooks/queries/utils/folder-tree'
@@ -69,18 +67,6 @@ import { useWorkflowStore } from '@/stores/workflows/workflow/store'
/** Stable empty object to avoid creating new references */
const EMPTY_SUBBLOCK_VALUES = {} as Record<string, any>
/**
* Icon component for rendering block icons.
*
* @param icon - The icon component to render
* @param className - Optional CSS classes
* @returns Rendered icon or null if no icon provided
*/
const IconComponent = ({ icon: Icon, className }: { icon: any; className?: string }) => {
if (!Icon) return null
return <Icon className={className} />
}
/**
* Editor panel component.
* Provides editor configuration and customization options for the workflow.
@@ -107,8 +93,6 @@ export function Editor() {
const currentWorkflow = useCurrentWorkflow()
const currentBlock = currentBlockId ? currentWorkflow.getBlockById(currentBlockId) : null
const blockConfig = currentBlock ? getBlock(currentBlock.type) : null
const typeAccent = getWorkflowTypeAccent(currentBlock?.type ?? '')
const isIntegration = blockConfig != null && !hasBlockAccent(blockConfig.type)
const title = currentBlock?.name || 'Editor'
const isBlockNameSearchHighlighted =
activeSearchTarget?.targetKind === 'block-name' && activeSearchTarget.blockId === currentBlockId
@@ -440,21 +424,8 @@ export function Editor() {
{/* Header */}
<div className='mx-[-1px] flex flex-shrink-0 items-center justify-between rounded-none border border-[var(--border)] bg-[var(--surface-4)] px-3 py-1.5'>
<div className='flex min-w-0 flex-1 items-center gap-2'>
{(blockConfig || isSubflow) && (
<ChipTag
variant={isIntegration ? 'brand' : typeAccent.variant}
tone={isIntegration ? undefined : typeAccent.tone}
brandColor={isIntegration ? blockConfig.bgColor : undefined}
brandForeground={
isIntegration && isLightTileColor(blockConfig.bgColor) ? 'dark' : 'light'
}
className='size-[18px] justify-center px-0'
>
<IconComponent
icon={isSubflow ? subflowConfig?.icon : blockConfig?.icon}
className='size-[12px]'
/>
</ChipTag>
{currentBlock && (blockConfig || isSubflow) && (
<BlockTile blockType={currentBlock.type} size='lg' />
)}
{isRenaming ? (
<input
@@ -21,9 +21,7 @@ import {
ChevronUp,
Clipboard,
Expand,
Repeat,
Search,
Split,
SquareArrowUpRight,
X,
} from '@sim/emcn/icons'
@@ -46,8 +44,7 @@ import { PreviewWorkflow } from '@/app/workspace/[workspaceId]/w/components/prev
import { useContextMenu } from '@/app/workspace/[workspaceId]/w/components/sidebar/hooks'
import { getBlock } from '@/blocks'
import { BlockTile } from '@/blocks/block-tile'
import { getTileIconColorClass } from '@/blocks/icon-color'
import type { BlockConfig, BlockIcon, SubBlockConfig, SubBlockType } from '@/blocks/types'
import type { BlockConfig, SubBlockConfig, SubBlockType } from '@/blocks/types'
import { normalizeName } from '@/executor/constants'
import { navigatePath } from '@/executor/variables/resolvers/reference'
import { useWorkflowState } from '@/hooks/queries/workflows'
@@ -533,20 +530,6 @@ function ConnectionsSection({
)
}
/**
* Icon component for rendering block icons
*/
function IconComponent({
icon: Icon,
className,
}: {
icon: BlockIcon | undefined
className?: string
}) {
if (!Icon) return null
return <Icon className={className} />
}
/**
* Configuration for subflow types (loop and parallel) - matches use-subflow-editor.ts
*/
@@ -1083,20 +1066,13 @@ function PreviewEditorContent({
if (isSubflow) {
const isLoop = block.type === 'loop'
const SubflowIcon = isLoop ? Repeat : Split
const subflowBgColor = isLoop ? '#2FB3FF' : '#FEE12B'
const subflowName = block.name || (isLoop ? 'Loop' : 'Parallel')
return (
<div className='relative flex h-full w-full flex-col overflow-hidden border-[var(--border)] border-l bg-[var(--surface-1)]'>
{/* Header - styled like subflow header */}
<div className='mx-[-1px] flex flex-shrink-0 items-center gap-2 rounded-b-[4px] border-[var(--border)] border-x border-b bg-[var(--surface-4)] px-3 py-1.5'>
<div
className='flex size-[18px] flex-shrink-0 items-center justify-center rounded-sm'
style={{ backgroundColor: subflowBgColor }}
>
<SubflowIcon className={cn('size-[12px]', getTileIconColorClass(subflowBgColor))} />
</div>
<BlockTile blockType={block.type} size='lg' />
<span className='min-w-0 flex-1 truncate text-[var(--text-primary)] text-sm'>
{subflowName}
</span>
@@ -1183,17 +1159,7 @@ function PreviewEditorContent({
<div className='relative flex h-full w-full flex-col overflow-hidden border-[var(--border)] border-l bg-[var(--surface-1)]'>
{/* Header - styled like editor */}
<div className='mx-[-1px] flex flex-shrink-0 items-center gap-2 rounded-b-[4px] border-[var(--border)] border-x border-b bg-[var(--surface-4)] px-3 py-1.5'>
{block.type !== 'note' && (
<div
className='flex size-[18px] flex-shrink-0 items-center justify-center overflow-hidden rounded-sm [&_img]:size-full'
style={{ backgroundColor: blockConfig.bgColor }}
>
<IconComponent
icon={blockConfig.icon}
className={cn('size-[12px]', getTileIconColorClass(blockConfig.bgColor))}
/>
</div>
)}
{block.type !== 'note' && <BlockTile blockType={block.type} size='lg' />}
<span className='min-w-0 flex-1 truncate text-[var(--text-primary)] text-sm'>
{block.name || blockConfig.name}
</span>
+24 -3
View File
@@ -6,12 +6,23 @@ import { WorkflowTypeIcon } from '@sim/workflow-renderer'
import { getBlockTileColor, getBlockTileIcon, hasBlockAccent } from '@/blocks/accent'
import { getTileIconColorClass } from '@/blocks/icon-color'
/** Slot sizes the tile ships in: the canvas 16px chip, or 14px for dense rows. */
/**
* Slot sizes the tile ships in: the 18px detail header, the canvas 16px chip,
* or 14px for dense rows.
*/
const TILE_SIZE_CLASS = {
lg: 'size-[18px]',
md: 'size-[16px]',
sm: 'size-[14px]',
} as const
/** Icon drawn inside each slot. Only the header tile takes the larger glyph. */
const TILE_ICON_SIZE_CLASS = {
lg: 'size-[12px]',
md: 'size-[10px]',
sm: 'size-[10px]',
} as const
export interface BlockTileProps extends Omit<HTMLAttributes<HTMLElement>, 'children' | 'style'> {
/**
* Block the tile represents; decides whether it takes the canvas role accent.
@@ -54,9 +65,18 @@ export function BlockTile({
}: BlockTileProps) {
const Icon = icon ?? (blockType ? getBlockTileIcon(blockType) : undefined)
const sizeClass = cn(TILE_SIZE_CLASS[size], className)
const iconSizeClass = TILE_ICON_SIZE_CLASS[size]
if (blockType && Icon && hasBlockAccent(blockType)) {
return <WorkflowTypeIcon type={blockType} Icon={Icon} className={sizeClass} {...props} />
return (
<WorkflowTypeIcon
type={blockType}
Icon={Icon}
className={sizeClass}
iconClassName={iconSizeClass}
{...props}
/>
)
}
const fill = bgColor ?? (blockType ? getBlockTileColor(blockType) : undefined)
@@ -70,7 +90,8 @@ export function BlockTile({
{Icon ? (
<Icon
className={cn(
'size-[10px] transition-transform duration-100 group-hover:scale-110',
iconSizeClass,
'transition-transform duration-100 group-hover:scale-110',
getTileIconColorClass(fill, true)
)}
/>
@@ -183,10 +183,18 @@ export const getWorkflowTypeAccent = (type: string) =>
export interface WorkflowTypeIconProps extends Omit<HTMLAttributes<HTMLSpanElement>, 'children'> {
type: string
Icon: ComponentType<{ className?: string }>
/** Overrides the glyph size when the chip is rendered at a non-default slot. */
iconClassName?: string
}
/** Shared compact core-block icon used by workflow discovery surfaces. */
export function WorkflowTypeIcon({ type, Icon, className, ...props }: WorkflowTypeIconProps) {
export function WorkflowTypeIcon({
type,
Icon,
className,
iconClassName,
...props
}: WorkflowTypeIconProps) {
const typeAccent = getWorkflowTypeAccent(type)
return (
@@ -197,7 +205,12 @@ export function WorkflowTypeIcon({ type, Icon, className, ...props }: WorkflowTy
data-workflow-type-icon={type}
{...props}
>
<Icon className='size-[10px] transition-transform duration-100 group-hover:scale-110' />
<Icon
className={cn(
'size-[10px] transition-transform duration-100 group-hover:scale-110',
iconClassName
)}
/>
</ChipTag>
)
}