Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
837f035
fix(table): fix table boolean, add dynamic row number col size, fix s…
waleedlatif1 May 8, 2026
a9a276c
sidebar styling
waleedlatif1 May 8, 2026
3e0ef3a
refactor(search-replace): extract duplicate WORKFLOW_SEARCH_HIGHLIGHT…
waleedlatif1 May 8, 2026
3845962
fix(tables): add missing onError toast to useAddTableColumn
waleedlatif1 May 8, 2026
8ca72d6
fix(tables): add consistent onError toast handlers across all table m…
waleedlatif1 May 8, 2026
41799e7
subblock sweep
icecrasher321 May 8, 2026
adfac54
Merge branch 'fix/tabl' of github.com:simstudioai/sim into fix/tabl
icecrasher321 May 8, 2026
c63b75f
more resources supported
icecrasher321 May 8, 2026
71e80f4
code restructuring
icecrasher321 May 8, 2026
917fe42
better organization
icecrasher321 May 8, 2026
fed57aa
canonical modes search replace counterpart
icecrasher321 May 8, 2026
b0b814e
more cases
icecrasher321 May 8, 2026
7e56bee
tool inp edge case
icecrasher321 May 8, 2026
79a1a77
more subblock cases
icecrasher321 May 8, 2026
748473c
trigger canoncial index ops consolidation
icecrasher321 May 8, 2026
427a530
minor codec fix
icecrasher321 May 8, 2026
82a5744
fix(tables): prevent double toast in workflow-sidebar and csv upload …
waleedlatif1 May 8, 2026
8f2084c
resolver fix for quoted js literals
icecrasher321 May 8, 2026
aa93229
Merge branch 'fix/tabl' of github.com:simstudioai/sim into fix/tabl
icecrasher321 May 8, 2026
e716220
code scan context
icecrasher321 May 8, 2026
72f384c
triple quotes case
icecrasher321 May 8, 2026
214a64e
escaped behaviour
icecrasher321 May 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,6 @@ function ColumnConfigBody({
toast.success(`Saved "${trimmedName}"`)
onClose()
} catch (err) {
// Server validation errors carry a Zod issue array on the body; surface
// them inline next to the offending field instead of as a raw toast.
if (isValidationError(err)) {
const nameIssue =
findValidationIssue(err, ['updates', 'name']) ??
Expand All @@ -146,14 +144,14 @@ function ColumnConfigBody({
setNameError(nameIssue.message)
return
}
toast.error(toError(err).message)
Comment thread
waleedlatif1 marked this conversation as resolved.
}
toast.error(toError(err).message)
}
Comment thread
waleedlatif1 marked this conversation as resolved.
}

return (
<div className='flex h-full flex-col'>
<div className='flex items-center justify-between border-[var(--border)] border-b px-3 py-2'>
<div className='flex items-center justify-between border-[var(--border)] border-b px-3 py-[8.5px]'>
<h2 className='font-medium text-[var(--text-primary)] text-small'>Configure column</h2>
<Button
variant='ghost'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,11 @@ export function CellRender({ kind, isEditing }: CellRenderProps): React.ReactEle
case 'boolean':
return (
<div
className={cn('flex min-h-[20px] items-center justify-center', isEditing && 'invisible')}
data-boolean-cell-toggle
className={cn(
'flex min-h-[20px] w-full items-center justify-center',
isEditing && 'invisible'
)}
>
<Checkbox size='sm' checked={kind.checked} className='pointer-events-none' />
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,25 +92,18 @@ function rowSelectionCoversAll(sel: RowSelection, rows: TableRowType[]): boolean

const COL_WIDTH_MIN = 80
const COL_WIDTH_AUTO_FIT_MAX = 1000
// Wide enough to host the row-number + per-row run button side by side.
// Single-digit row numbers (rows 1–9) and multi-digit need to render with
// the play button at the same x-position so the column doesn't reflow
// row-by-row.
//
// Bucketed by the table's plan-derived `maxRows`, not the live count: a small
// table sized for ≤9,999 always renders the narrow gutter; an enterprise
// table sized up to 9,999,999 always renders the wide one. The gutter never
// changes width as rows are added.
//
// Tables without workflow columns drop the per-row run button (~28px), so
// the gutter shrinks accordingly.
const CHECKBOX_COL_WIDTH_SMALL_WITH_RUN = 48
const CHECKBOX_COL_WIDTH_SMALL_NUMBER_ONLY = 32
const CHECKBOX_COL_WIDTH_LARGE_WITH_RUN = 68
const CHECKBOX_COL_WIDTH_LARGE_NUMBER_ONLY = 52
/** Bucket boundary: tables sized for >9,999 rows get the wide gutter. */
const LARGE_ROW_NUMBER_THRESHOLD = 10000
const ADD_COL_WIDTH = 120

/** Returns sticky row-number column dimensions sized to the digit count of `maxRows`. */
function checkboxColLayout(
maxRows: number,
hasWorkflowCols: boolean
): { colWidth: number; numDivWidth: number } {
const digits = maxRows > 0 ? Math.floor(Math.log10(maxRows)) + 1 : 1
const numDivWidth = Math.max(20, digits * 8 + 4)
const colWidth = Math.max(32, numDivWidth + 8) + (hasWorkflowCols ? 16 : 0)
return { colWidth, numDivWidth }
}
const SKELETON_COL_COUNT = 4
const SKELETON_ROW_COUNT = 10
const ROW_HEIGHT_ESTIMATE = 35
Expand All @@ -128,7 +121,7 @@ const CELL_HEADER_CHECKBOX =
const CELL_CONTENT =
'relative flex h-[22px] min-w-0 items-center overflow-clip text-ellipsis whitespace-nowrap text-small'
const SELECTION_OVERLAY =
'pointer-events-none absolute -top-px -right-px -bottom-px -left-px z-[5] border-[2px] border-[var(--selection)]'
'pointer-events-none absolute -top-px -right-px -bottom-px z-[5] border-[2px] border-[var(--selection)]'

/**
* Snapshot of grid selection state the wrapper needs to render `<TableActionBar>`.
Expand Down Expand Up @@ -452,22 +445,10 @@ export function TableGrid({
}, [columns, columnOrder, tableWorkflowGroups])

const hasWorkflowColumns = columns.some((c) => !!c.workflowGroupId)
/**
* The sticky left column hosts the row number / checkbox always, plus a
* per-row run button only when the table has workflow columns. Width is
* picked from the table's plan-derived `maxRows` so a free-tier table
* (≤9,999) gets the narrow gutter and an enterprise table (up to
* 9,999,999) gets the wide one. Bucketed, not continuous, so the gutter
* never reflows as rows are added.
*/
const isLargeRowCountTable = (tableData?.maxRows ?? 0) >= LARGE_ROW_NUMBER_THRESHOLD
const checkboxColWidth = isLargeRowCountTable
? hasWorkflowColumns
? CHECKBOX_COL_WIDTH_LARGE_WITH_RUN
: CHECKBOX_COL_WIDTH_LARGE_NUMBER_ONLY
: hasWorkflowColumns
? CHECKBOX_COL_WIDTH_SMALL_WITH_RUN
: CHECKBOX_COL_WIDTH_SMALL_NUMBER_ONLY
const { colWidth: checkboxColWidth, numDivWidth } = checkboxColLayout(
tableData?.maxRows ?? 0,
hasWorkflowColumns
)

const headerGroups = useMemo(
() => buildHeaderGroups(displayColumns, tableWorkflowGroups),
Expand Down Expand Up @@ -3053,7 +3034,7 @@ export function TableGrid({
onRowToggle={handleRowToggle}
runningCount={runningByRowId.get(row.id) ?? 0}
hasWorkflowColumns={hasWorkflowColumns}
isLargeRowCountTable={isLargeRowCountTable}
numDivWidth={numDivWidth}
onStopRow={onStopRow}
onRunRow={handleRunRow}
workflowGroups={tableWorkflowGroups}
Expand Down Expand Up @@ -3178,8 +3159,8 @@ interface DataRowProps {
runningCount: number
/** Whether the table has at least one workflow column — controls whether a run/stop icon is rendered. */
hasWorkflowColumns: boolean
/** True for tables sized for >9,999 rows; widens the row-number slot to fit 5–7 digit numbers. */
isLargeRowCountTable: boolean
/** Width of the row-number inner div in px, derived from the table's maxRows digit count. */
numDivWidth: number
onStopRow: (rowId: string) => void
onRunRow: (rowId: string) => void
/**
Expand Down Expand Up @@ -3239,7 +3220,7 @@ function dataRowPropsAreEqual(prev: DataRowProps, next: DataRowProps): boolean {
prev.onRowToggle !== next.onRowToggle ||
prev.runningCount !== next.runningCount ||
prev.hasWorkflowColumns !== next.hasWorkflowColumns ||
prev.isLargeRowCountTable !== next.isLargeRowCountTable ||
prev.numDivWidth !== next.numDivWidth ||
prev.onStopRow !== next.onStopRow ||
prev.onRunRow !== next.onRunRow ||
prev.workflowGroups !== next.workflowGroups
Expand Down Expand Up @@ -3281,7 +3262,7 @@ const DataRow = React.memo(function DataRow({
onRowToggle,
runningCount,
hasWorkflowColumns,
isLargeRowCountTable,
numDivWidth,
onStopRow,
onRunRow,
workflowGroups,
Expand Down Expand Up @@ -3313,20 +3294,23 @@ const DataRow = React.memo(function DataRow({
return (
<tr onContextMenu={(e) => onContextMenu(e, row)}>
<td className={cn(CELL_CHECKBOX, 'cursor-pointer')}>
<div className='flex items-center justify-between gap-1'>
<div
className={cn(
'flex items-center gap-1',
hasWorkflowColumns ? 'justify-between' : 'justify-center'
)}
>
<div
className={cn(
'group/checkbox flex h-[20px] shrink-0 items-center justify-end',
isLargeRowCountTable ? 'w-[40px]' : 'w-[20px]'
)}
className='group/checkbox flex h-[20px] shrink-0 items-center justify-center'
style={{ width: numDivWidth }}
onMouseDown={(e) => {
if (e.button !== 0) return
onRowToggle(rowIndex, e.shiftKey)
}}
>
<span
className={cn(
'text-right text-[var(--text-tertiary)] text-xs tabular-nums',
'text-center text-[var(--text-tertiary)] text-xs tabular-nums',
isRowSelected ? 'hidden' : 'block group-hover/checkbox:hidden'
)}
>
Expand Down Expand Up @@ -3409,7 +3393,8 @@ const DataRow = React.memo(function DataRow({
{isHighlighted && (isMultiCell || isRowChecked) && (
<div
className={cn(
'-top-px -right-px -bottom-px -left-px pointer-events-none absolute z-[4]',
'-top-px -right-px -bottom-px pointer-events-none absolute z-[4]',
colIndex === 0 ? 'left-0' : '-left-px',
SELECTION_TINT_BG,
isFirstRow && isTopEdge && 'top-0',
isTopEdge && 'border-t border-t-[var(--selection)]',
Expand All @@ -3419,7 +3404,15 @@ const DataRow = React.memo(function DataRow({
)}
/>
)}
{isAnchor && <div className={cn(SELECTION_OVERLAY, isFirstRow && 'top-0')} />}
{isAnchor && (
<div
className={cn(
SELECTION_OVERLAY,
colIndex === 0 ? 'left-0' : '-left-px',
isFirstRow && 'top-0'
)}
/>
)}
<div className={CELL_CONTENT}>
<CellContent
value={
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -659,8 +659,8 @@ function WorkflowSidebarBody({
setNameError(nameIssue.message)
return
}
toast.error(toError(err).message)
}
toast.error(toError(err).message)
}
}

Expand All @@ -678,7 +678,7 @@ function WorkflowSidebarBody({

return (
<div className='flex h-full flex-col'>
<div className='flex items-center justify-between border-[var(--border)] border-b px-3 py-2'>
<div className='flex items-center justify-between border-[var(--border)] border-b px-3 py-[8.5px]'>
<h2 className='font-medium text-[var(--text-primary)] text-small'>
{titleByMode[config.mode]}
</h2>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const WORKFLOW_SEARCH_HIGHLIGHT_CLASS =
'rounded-sm bg-orange-400 shadow-[3px_0_0_#fb923c,-3px_0_0_#fb923c]'
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ import { Button, Input, Textarea, Tooltip } from '@/components/emcn'
import { Trash } from '@/components/emcn/icons/trash'
import { Label } from '@/components/ui/label'
import { cn } from '@/lib/core/utils/cn'
import { WORKFLOW_SEARCH_HIGHLIGHT_CLASS } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/constants'
import { formatDisplayText } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/formatted-text'
import { TagDropdown } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tag-dropdown/tag-dropdown'
import { useSubBlockInput } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-input'
import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-value'
import { useAccessibleReferencePrefixes } from '@/app/workspace/[workspaceId]/w/[workflowId]/hooks/use-accessible-reference-prefixes'
import type { ActiveSearchTarget } from '@/stores/panel/editor/store'

interface EvalMetric {
id: string
Expand All @@ -27,6 +29,7 @@ interface EvalInputProps {
isPreview?: boolean
previewValue?: EvalMetric[] | null
disabled?: boolean
activeSearchTarget?: ActiveSearchTarget | null
}

// Default values
Expand All @@ -43,6 +46,7 @@ export function EvalInput({
isPreview = false,
previewValue,
disabled = false,
activeSearchTarget,
}: EvalInputProps) {
const [storeValue, setStoreValue] = useSubBlockValue<EvalMetric[]>(blockId, subBlockId)
const accessiblePrefixes = useAccessibleReferencePrefixes(blockId)
Expand All @@ -67,6 +71,17 @@ export function EvalInput({
const defaultMetric = useMemo(() => createDefaultMetric(), [])
const metrics: EvalMetric[] = value || [defaultMetric]

const isNestedSearchHighlighted = (metricIndex: number, metricPath: Array<string | number>) =>
activeSearchTarget?.subBlockId === subBlockId &&
activeSearchTarget.valuePath[0] === metricIndex &&
metricPath.every((segment, index) => activeSearchTarget.valuePath[index + 1] === segment)

const renderFieldLabel = (label: string, highlighted: boolean) => (
<Label className='text-small'>
{highlighted ? <mark className={WORKFLOW_SEARCH_HIGHLIGHT_CLASS}>{label}</mark> : label}
</Label>
)

const addMetric = () => {
if (isPreview || disabled) return

Expand Down Expand Up @@ -176,7 +191,7 @@ export function EvalInput({

<div className='flex flex-col gap-2 border-[var(--border-1)] px-2.5 pt-1.5 pb-2.5'>
<div key={`name-${metric.id}`} className='flex flex-col gap-1.5'>
<Label className='text-small'>Name</Label>
{renderFieldLabel('Name', isNestedSearchHighlighted(index, ['name']))}
<Input
name='name'
value={metric.name}
Expand All @@ -187,7 +202,7 @@ export function EvalInput({
</div>

<div key={`description-${metric.id}`} className='flex flex-col gap-1.5'>
<Label className='text-small'>Description</Label>
{renderFieldLabel('Description', isNestedSearchHighlighted(index, ['description']))}
<div className='relative'>
{(() => {
const fieldState = inputController.fieldHelpers.getFieldState(metric.id)
Expand Down Expand Up @@ -259,7 +274,7 @@ export function EvalInput({

<div key={`range-${metric.id}`} className='grid grid-cols-2 gap-2'>
<div className='flex flex-col gap-1.5'>
<Label className='text-small'>Min Value</Label>
{renderFieldLabel('Min Value', isNestedSearchHighlighted(index, ['range', 'min']))}
<Input
type='text'
value={metric.range.min}
Expand All @@ -272,7 +287,7 @@ export function EvalInput({
/>
</div>
<div className='flex flex-col gap-1.5'>
<Label className='text-small'>Max Value</Label>
{renderFieldLabel('Max Value', isNestedSearchHighlighted(index, ['range', 'max']))}
<Input
type='text'
value={metric.range.max}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ResponseFormat as SharedResponseFormat } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/starter/input-format'
import type { ActiveSearchTarget } from '@/stores/panel/editor/store'

interface ResponseFormatProps {
blockId: string
Expand All @@ -7,6 +8,7 @@ interface ResponseFormatProps {
previewValue?: any
disabled?: boolean
config?: any
activeSearchTarget?: ActiveSearchTarget | null
}

export function ResponseFormat({
Expand All @@ -16,6 +18,7 @@ export function ResponseFormat({
previewValue,
disabled = false,
config,
activeSearchTarget,
}: ResponseFormatProps) {
return (
<SharedResponseFormat
Expand All @@ -25,6 +28,7 @@ export function ResponseFormat({
previewValue={previewValue}
disabled={disabled}
config={config}
activeSearchTarget={activeSearchTarget}
/>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ interface ShortInputProps {
wandControlRef?: React.MutableRefObject<WandControlHandlers | null>
/** Whether to hide the internal wand button (controlled by parent) */
hideInternalWand?: boolean
/** Whether workflow search is actively highlighting this input */
isSearchHighlighted?: boolean
}

/**
Expand Down Expand Up @@ -74,6 +76,7 @@ export const ShortInput = memo(function ShortInput({
useWebhookUrl = false,
wandControlRef,
hideInternalWand = false,
isSearchHighlighted = false,
}: ShortInputProps) {
const [localContent, setLocalContent] = useState<string>('')
const [isFocused, setIsFocused] = useState(false)
Expand Down Expand Up @@ -332,16 +335,15 @@ export const ShortInput = memo(function ShortInput({
? webhookManagement.webhookUrl
: ctrlValue

const displayValue =
password && !isFocused ? '•'.repeat(actualValue?.length ?? 0) : actualValue
const shouldMask = password && !isFocused && !isSearchHighlighted
const displayValue = shouldMask ? '•'.repeat(actualValue?.length ?? 0) : actualValue

const formattedText =
password && !isFocused
? '•'.repeat(actualValue?.length ?? 0)
: formatDisplayText(actualValue, {
accessiblePrefixes,
highlightAll: !accessiblePrefixes,
})
const formattedText = shouldMask
? '•'.repeat(actualValue?.length ?? 0)
: formatDisplayText(actualValue, {
accessiblePrefixes,
highlightAll: !accessiblePrefixes,
})

return (
<>
Expand Down
Loading
Loading