diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/editor.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/editor.tsx
index ac65eab57fa..2c531a80a83 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/editor.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/editor.tsx
@@ -32,6 +32,7 @@ import {
SubBlock,
SubflowEditor,
} from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components'
+import { WORKFLOW_SEARCH_HIGHLIGHT_CLASS } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/constants'
import {
useBlockConnections,
useConnectionsResize,
@@ -104,6 +105,8 @@ export function Editor() {
const currentBlock = currentBlockId ? currentWorkflow.getBlockById(currentBlockId) : null
const blockConfig = currentBlock ? getBlock(currentBlock.type) : null
const title = currentBlock?.name || 'Editor'
+ const isBlockNameSearchHighlighted =
+ activeSearchTarget?.targetKind === 'block-name' && activeSearchTarget.blockId === currentBlockId
const isSubflow =
currentBlock && (currentBlock.type === 'loop' || currentBlock.type === 'parallel')
@@ -253,6 +256,7 @@ export function Editor() {
useEffect(() => {
if (!activeSearchTarget || activeSearchTarget.blockId !== currentBlockId) return
+ if (activeSearchTarget.targetKind === 'block-name') return
const container = subBlocksRef.current
if (!container) return
@@ -402,7 +406,11 @@ export function Editor() {
}
}}
>
- {title}
+ {isBlockNameSearchHighlighted ? (
+ {title}
+ ) : (
+ title
+ )}
)}
diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/search-replace/workflow-search-replace.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/search-replace/workflow-search-replace.tsx
index aad657f5643..d67fcf896ea 100644
--- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/search-replace/workflow-search-replace.tsx
+++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/search-replace/workflow-search-replace.tsx
@@ -265,6 +265,7 @@ export function WorkflowSearchReplace() {
canonicalSubBlockId: match.canonicalSubBlockId,
valuePath: match.valuePath,
kind: match.kind,
+ targetKind: match.target.kind,
resourceGroupKey: match.resource?.resourceGroupKey,
})
},
diff --git a/apps/sim/lib/workflows/search-replace/indexer.test.ts b/apps/sim/lib/workflows/search-replace/indexer.test.ts
index 53975b65ad5..31e45f3715e 100644
--- a/apps/sim/lib/workflows/search-replace/indexer.test.ts
+++ b/apps/sim/lib/workflows/search-replace/indexer.test.ts
@@ -31,6 +31,39 @@ describe('indexWorkflowSearchMatches', () => {
expect(matches.at(-1)?.reason).toBe('Block is locked')
})
+ it('finds matches in block names', () => {
+ const workflow = createSearchReplaceWorkflowFixture()
+
+ const matches = indexWorkflowSearchMatches({
+ workflow,
+ query: 'agent',
+ mode: 'text',
+ blockConfigs: SEARCH_REPLACE_BLOCK_CONFIGS,
+ })
+
+ const blockNameMatches = matches.filter((match) => match.target.kind === 'block-name')
+ expect(blockNameMatches.map((match) => [match.blockId, match.rawValue])).toEqual([
+ ['agent-1', 'Agent'],
+ ['locked-1', 'Agent'],
+ ])
+ expect(blockNameMatches.every((match) => match.editable === false)).toBe(true)
+ expect(blockNameMatches.every((match) => match.navigable === true)).toBe(true)
+ expect(blockNameMatches[0]?.fieldTitle).toBe('Block name')
+ })
+
+ it('does not include block-name matches in resource-only mode', () => {
+ const workflow = createSearchReplaceWorkflowFixture()
+
+ const matches = indexWorkflowSearchMatches({
+ workflow,
+ query: 'agent',
+ mode: 'resource',
+ blockConfigs: SEARCH_REPLACE_BLOCK_CONFIGS,
+ })
+
+ expect(matches.some((match) => match.target.kind === 'block-name')).toBe(false)
+ })
+
it('does not index internal row metadata in structured subblock values', () => {
const workflow = createSearchReplaceWorkflowFixture()
diff --git a/apps/sim/lib/workflows/search-replace/indexer.ts b/apps/sim/lib/workflows/search-replace/indexer.ts
index 198b5c6567f..a839bd39008 100644
--- a/apps/sim/lib/workflows/search-replace/indexer.ts
+++ b/apps/sim/lib/workflows/search-replace/indexer.ts
@@ -982,6 +982,32 @@ export function indexWorkflowSearchMatches(
const protectedByLock = isWorkflowBlockProtected(block.id, workflow.blocks)
const editable = !protectedByLock && !isReadOnly
+ if (mode !== 'resource' && query && typeof block.name === 'string' && block.name.length > 0) {
+ const blockNameRanges = findTextRanges(block.name, query, caseSensitive)
+ blockNameRanges.forEach((range, occurrenceIndex) => {
+ matches.push({
+ id: createMatchId(['block-name', block.id, range.start, occurrenceIndex]),
+ blockId: block.id,
+ blockName: block.name,
+ blockType: block.type,
+ subBlockId: '',
+ canonicalSubBlockId: '',
+ subBlockType: 'short-input',
+ fieldTitle: 'Block name',
+ valuePath: [],
+ target: { kind: 'block-name' },
+ kind: 'text',
+ rawValue: block.name.slice(range.start, range.end),
+ searchText: block.name,
+ range,
+ editable: false,
+ navigable: true,
+ protected: protectedByLock,
+ reason: 'Block names cannot be edited via replace',
+ })
+ })
+ }
+
if (mode !== 'resource') {
for (const field of getWorkflowSearchSubflowFields(block)) {
const fieldEditable = editable && field.editable
diff --git a/apps/sim/lib/workflows/search-replace/types.ts b/apps/sim/lib/workflows/search-replace/types.ts
index 835cf43d71d..9298430318d 100644
--- a/apps/sim/lib/workflows/search-replace/types.ts
+++ b/apps/sim/lib/workflows/search-replace/types.ts
@@ -45,6 +45,7 @@ export interface WorkflowSearchResourceMeta {
export type WorkflowSearchTarget =
| { kind: 'subblock' }
| { kind: 'subflow'; fieldId: WorkflowSearchSubflowFieldId }
+ | { kind: 'block-name' }
export interface WorkflowSearchMatch {
id: string
diff --git a/apps/sim/stores/panel/editor/store.ts b/apps/sim/stores/panel/editor/store.ts
index d76019befea..9a0f6d1e8fd 100644
--- a/apps/sim/stores/panel/editor/store.ts
+++ b/apps/sim/stores/panel/editor/store.ts
@@ -2,11 +2,14 @@
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
+import type { WorkflowSearchTarget } from '@/lib/workflows/search-replace/types'
import { EDITOR_CONNECTIONS_HEIGHT } from '@/stores/constants'
import { usePanelStore } from '../store'
let renameCallback: (() => void) | null = null
+export type ActiveSearchTargetKind = WorkflowSearchTarget['kind']
+
export interface ActiveSearchTarget {
matchId: string
blockId: string
@@ -14,6 +17,7 @@ export interface ActiveSearchTarget {
canonicalSubBlockId: string
valuePath: Array
kind: string
+ targetKind: ActiveSearchTargetKind
resourceGroupKey?: string
}