Skip to content

Commit 49593b3

Browse files
authored
refactor: replace hand-rolled utilities and dead code with the shared forms (#7021)
* refactor: replace hand-rolled utilities and dead code with the shared forms Each of these has a mandated helper or an established accessor in the repo that the site predates or missed. All are behavior-preserving: - `omit()` for the three `Object.fromEntries(Object.entries(x).filter(...))` block-input filters, which also recovers the `Omit<T, K>` typing that `Object.fromEntries` erases to an index signature. - `getErrorMessage()` for the inline `instanceof Error` message ternary. - `getBlock()` for two `getAllBlocks().find((b) => b.type === x)` scans, one of them inside a loop over selected tools. The same file already resolves the same values through `getBlock`. - A memoised `Map` for three `.find()`-by-id scans over the workspace skill list, one of them inside a render `.map()`. - `SELECTOR_SEARCH_STALE` for three copy-pasted `15 * 1000` literals. They are deliberately shorter than `SELECTOR_STALE`, so this is a new named constant rather than a fold into the existing one. - Tailwind classes for the static half of two duplicated anchor styles, keeping only the genuinely dynamic `left`/`top` inline. - Dropped the unused `catch` bindings on three intentional JSON-parse swallows. `panel.tsx`'s run-button gate loses a `TODO`-stubbed `hasValidationErrors = false` and the `isWorkflowBlocked` term built on it. That term was dead twice over: it reduced to `isExecuting`, and the enclosing expression is already guarded by `!isExecuting`. * fix: guard the registry lookups, and scope the search-stale doc to its callers `getBlock` normalizes its argument with `type.replace(...)`, so it throws on `undefined` where the `getAllBlocks().find(...)` it replaced returned `undefined` harmlessly. Both call sites can be reached without a type: `tool-input` reads `state.blocks[blockId]?.type`, which is undefined once the block is deleted while the panel is mounted — and `Record` indexing hides that from the compiler, so it would have thrown during render. `agent-handler`'s `tool.type` is optional and the compiler did catch it. Also index the skill lookup in `resolveSkillsLabel`, which runs a `.find()` inside a `.map()` for every block on the canvas — the case the memoised map in `skill-input` addressed for one component while leaving the hot path. `providers/utils.ts` keeps its `getAllBlocks().find(...)`: it takes the registry as an injected dependency precisely so a client-reachable module never imports it, and reaching for `getBlock` there would cross that boundary. The new constant's doc claimed search-backed selectors take a shorter window. Several still sit on `SELECTOR_STALE`, so it now describes the value its three callers share rather than asserting a rule the tree does not follow. * fix: guard the second registry lookup in tool-input `selectedTools` validates only `value[0]?.type` and then casts the whole array, so a persisted workflow whose later rows lost their `type` yields `undefined` here — the cast is what makes the compiler believe otherwise. `getBlock` normalizes with `type.replace`, so that throws during render.
1 parent fc7aa66 commit 49593b3

17 files changed

Lines changed: 63 additions & 49 deletions

File tree

apps/sim/app/workspace/[workspaceId]/home/components/user-input/components/plus-menu-dropdown/plus-menu-dropdown.tsx

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -289,14 +289,8 @@ export const PlusMenuDropdown = React.memo(
289289
<DropdownMenu open={open} onOpenChange={handleOpenChange}>
290290
<DropdownMenuTrigger asChild>
291291
<div
292-
style={{
293-
position: 'fixed',
294-
left: anchorPos?.left ?? 0,
295-
top: anchorPos?.top ?? 0,
296-
width: 0,
297-
height: 0,
298-
pointerEvents: 'none',
299-
}}
292+
className='pointer-events-none fixed size-0'
293+
style={{ left: anchorPos?.left ?? 0, top: anchorPos?.top ?? 0 }}
300294
/>
301295
</DropdownMenuTrigger>
302296
<DropdownMenuContent

apps/sim/app/workspace/[workspaceId]/home/components/user-input/components/skills-menu-dropdown/skills-menu-dropdown.tsx

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -182,14 +182,8 @@ export const SkillsMenuDropdown = React.memo(
182182
<DropdownMenu open={open} onOpenChange={handleOpenChange}>
183183
<DropdownMenuTrigger asChild>
184184
<div
185-
style={{
186-
position: 'fixed',
187-
left: anchorPos?.left ?? 0,
188-
top: anchorPos?.top ?? 0,
189-
width: 0,
190-
height: 0,
191-
pointerEvents: 'none',
192-
}}
185+
className='pointer-events-none fixed size-0'
186+
style={{ left: anchorPos?.left ?? 0, top: anchorPos?.top ?? 0 }}
193187
/>
194188
</DropdownMenuTrigger>
195189
<DropdownMenuContent

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/skill-input/skill-input.tsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,17 @@ export function SkillInput({
4444
const [editingSkillId, setEditingSkillId] = useState<string | null>(null)
4545
const [editingSkillSnapshot, setEditingSkillSnapshot] = useState<SkillDefinition | null>(null)
4646

47+
const skillsById = useMemo(
48+
() => new Map(workspaceSkills.map((skill) => [skill.id, skill])),
49+
[workspaceSkills]
50+
)
51+
4752
// Prefer the live query cache so the modal reflects concurrent edits, but
4853
// fall back to the click-time snapshot when a background refetch drops the
4954
// skill — otherwise the modal would close mid-edit and silently discard the
5055
// draft; saving surfaces the real server error instead.
5156
const editingSkill = editingSkillId
52-
? (workspaceSkills.find((s) => s.id === editingSkillId) ?? editingSkillSnapshot)
57+
? (skillsById.get(editingSkillId) ?? editingSkillSnapshot)
5358
: null
5459

5560
const selectedSkills: StoredSkill[] = useMemo(() => {
@@ -119,10 +124,10 @@ export function SkillInput({
119124

120125
const resolveSkillName = useCallback(
121126
(stored: StoredSkill): string => {
122-
const found = workspaceSkills.find((s) => s.id === stored.skillId)
127+
const found = skillsById.get(stored.skillId)
123128
return found?.name ?? stored.name ?? stored.skillId
124129
},
125-
[workspaceSkills]
130+
[skillsById]
126131
)
127132

128133
return (
@@ -141,7 +146,7 @@ export function SkillInput({
141146

142147
{selectedSkills.length > 0 &&
143148
selectedSkills.map((stored, index) => {
144-
const fullSkill = workspaceSkills.find((s) => s.id === stored.skillId)
149+
const fullSkill = skillsById.get(stored.skillId)
145150
const skillName = resolveSkillName(stored)
146151
const workflowSearchHighlight = getWorkflowSearchLabelHighlight({
147152
activeSearchTarget,

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/tool-input.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -520,7 +520,7 @@ export const ToolInput = memo(function ToolInput({
520520
// subBlock): shown in the picker but greyed out with a tooltip instead of added.
521521
const blockType = useWorkflowStore(useCallback((state) => state.blocks[blockId]?.type, [blockId]))
522522
const unsupportedToolTypes = useMemo<readonly ('mcp' | 'custom-tool')[]>(() => {
523-
const block = getAllBlocks().find((b) => b.type === blockType)
523+
const block = blockType ? getBlock(blockType) : undefined
524524
return block?.subBlocks.find((sb) => sb.id === subBlockId)?.unsupportedToolTypes ?? []
525525
}, [blockType, subBlockId])
526526
const mcpUnsupported = unsupportedToolTypes.includes('mcp')
@@ -529,9 +529,8 @@ export const ToolInput = memo(function ToolInput({
529529
// Look up credential type for reactive condition filtering (e.g. service account detection).
530530
// Uses canonical resolution so the active field (basic vs advanced) is respected.
531531
const toolCredentialId = useMemo(() => {
532-
const allBlocks = getAllBlocks()
533532
for (const [toolIndex, tool] of selectedTools.entries()) {
534-
const blockConfig = allBlocks.find((b: { type: string }) => b.type === tool.type)
533+
const blockConfig = tool.type ? getBlock(tool.type) : undefined
535534
if (!blockConfig?.subBlocks) continue
536535
// canonical-index-unscoped: a nested tool resolves against `tool.params`, which only ever
537536
// holds action-surface values — a tool is never invoked in trigger mode.

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/panel.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -640,13 +640,10 @@ export const Panel = memo(function Panel() {
640640
setIsMenuOpen(false)
641641
}, [collaborativeBatchToggleLocked])
642642

643-
// Compute run button state
644-
const canRun = userPermissions.canRead // Running only requires read permissions
643+
const canRun = userPermissions.canRead
645644
const isLoadingPermissions = userPermissions.isLoading
646-
const hasValidationErrors = false // TODO: Add validation logic if needed
647-
const isWorkflowBlocked = isExecuting || hasValidationErrors
648645
const isButtonDisabled =
649-
!isExecuting && (isUsageGateLoading || isWorkflowBlocked || (!canRun && !isLoadingPermissions))
646+
!isExecuting && (isUsageGateLoading || (!canRun && !isLoadingPermissions))
650647

651648
/**
652649
* Register global keyboard shortcuts using the central commands registry.

apps/sim/blocks/blocks/fireflies.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { omit } from '@sim/utils/object'
12
import { FirefliesIcon } from '@/components/icons'
23
import { resolveHttpsUrlFromFileInput } from '@/lib/uploads/utils/file-utils'
34
import type { BlockConfig, BlockMeta } from '@/blocks/types'
@@ -698,9 +699,7 @@ Return ONLY the summary text - no quotes, no labels.`,
698699
const firefliesV2SubBlocks = (FirefliesBlock.subBlocks || []).filter(
699700
(subBlock) => subBlock.id !== 'audioUrl'
700701
)
701-
const firefliesV2Inputs = FirefliesBlock.inputs
702-
? Object.fromEntries(Object.entries(FirefliesBlock.inputs).filter(([key]) => key !== 'audioUrl'))
703-
: {}
702+
const firefliesV2Inputs = FirefliesBlock.inputs ? omit(FirefliesBlock.inputs, ['audioUrl']) : {}
704703

705704
export const FirefliesV2Block: BlockConfig<FirefliesResponse> = {
706705
...FirefliesBlock,

apps/sim/blocks/blocks/grain.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { omit } from '@sim/utils/object'
12
import { GrainIcon } from '@/components/icons'
23
import type { BlockConfig, BlockMeta } from '@/blocks/types'
34
import { AuthMode, IntegrationType } from '@/blocks/types'
@@ -758,7 +759,7 @@ export const GrainV2Block: BlockConfig = {
758759
},
759760
},
760761
inputs: {
761-
...Object.fromEntries(Object.entries(GrainBlock.inputs).filter(([key]) => key !== 'viewId')),
762+
...omit(GrainBlock.inputs, ['viewId']),
762763
apiKey: { type: 'string', description: 'Grain API key (Personal or Workspace Access Token)' },
763764
hookType: { type: 'string', description: 'Grain event type for the webhook' },
764765
hookInclude: {

apps/sim/blocks/blocks/stt.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { omit } from '@sim/utils/object'
12
import { STTIcon } from '@/components/icons'
23
import { AuthMode, type BlockConfig, IntegrationType } from '@/blocks/types'
34
import { createVersionedToolSelector, normalizeFileInput } from '@/blocks/utils'
@@ -368,9 +369,7 @@ export const SttBlock: BlockConfig<SttBlockResponse> = {
368369
},
369370
}
370371

371-
const sttV2Inputs = SttBlock.inputs
372-
? Object.fromEntries(Object.entries(SttBlock.inputs).filter(([key]) => key !== 'audioUrl'))
373-
: {}
372+
const sttV2Inputs = SttBlock.inputs ? omit(SttBlock.inputs, ['audioUrl']) : {}
374373
const sttV2SubBlocks = (SttBlock.subBlocks || []).filter((subBlock) => subBlock.id !== 'audioUrl')
375374

376375
export const SttV2Block: BlockConfig<SttBlockResponse> = {

apps/sim/executor/handlers/agent/agent-handler.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import { selectModelBoundFileInputPaths } from '@/lib/uploads/utils/model-input'
3232
import { hydrateUserFilesWithBase64 } from '@/lib/uploads/utils/user-file-base64.server'
3333
import { resolveCustomBlockToolBinding } from '@/lib/workflows/custom-blocks/operations'
3434
import { getCustomToolById } from '@/lib/workflows/custom-tools/operations'
35-
import { getAllBlocks } from '@/blocks'
35+
import { getAllBlocks, getBlock } from '@/blocks'
3636
import { assembleCustomBlockInputMapping, isCustomBlockType } from '@/blocks/custom/build-config'
3737
import type { BlockOutput } from '@/blocks/types'
3838
import { normalizeFileInput } from '@/blocks/utils'
@@ -857,7 +857,7 @@ export class AgentBlockHandler implements BlockHandler {
857857
)
858858
if (tool.type === 'mcp' || tool.type === 'custom-tool') return alignedParams
859859

860-
const blockInputs = getAllBlocks().find((block) => block.type === tool.type)?.inputs
860+
const blockInputs = tool.type ? getBlock(tool.type)?.inputs : undefined
861861
return prepareResolvedSecretProjectedInputs(alignedParams, blockInputs, formattedParams)
862862
}
863863

apps/sim/executor/handlers/api/api-handler.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ export class ApiBlockHandler implements BlockHandler {
5858
if (trimmedBody.startsWith('{') || trimmedBody.startsWith('[')) {
5959
processedInputs.body = JSON.parse(trimmedBody)
6060
}
61-
} catch (e) {}
61+
} catch {}
6262
} else if (processedInputs.body === null) {
6363
processedInputs.body = undefined
6464
}

0 commit comments

Comments
 (0)