Files
windmill/frontend/src/lib/components/flows/map/FlowModuleSchemaItem.svelte
T
centdix 8e6b519a0d feat(aichat): simplify flow mode edits (#6981)
* draft

* Phase 1: Remove deprecated granular flow AI tools

Simplify AI chat flow mode to use only YAML-based editing:
- Remove all commented-out granular tools (add_step, remove_step, set_code, etc.)
- Clean up FlowAIChatHelpers interface to only essential methods
- Update system prompts to focus on YAML-only workflow
- Remove unused imports and type definitions

This is part of a larger refactoring to simplify the flow editing
experience to a single YAML editing tool with automatic diff visualization.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* use minified json

* use openflow in system prompt

* handle inputs

* cleaning

* cleaning

* diffmode in flowgraph

* remove acceptrejectmodule

* use new diff mode

* cleaning

* better props

* better logic

* cleaning

* accept reject logic

* use get set

* draft manager

* use diff manager

* draft

* Refactor flowDiffManager to be instance-based with auto-computation

- Remove singleton export, making it instantiable per FlowGraphV2
- Add afterFlow state tracking for auto-diff computation
- Add beforeInputSchema/afterInputSchema for schema change tracking
- Add $effect for reactive auto-computation when beforeFlow/afterFlow changes
- Add setAfterFlow() and setInputSchemas() methods
- Simplify accept/reject methods to just mark pending=false
- Add validation to throw error when accepting/rejecting without beforeFlow
- Update setSnapshot to accept undefined for clearing

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Refactor FlowGraphV2 to own diffManager instance

- Import and create diffManager instance per FlowGraphV2
- Remove onAcceptModule and onRejectModule props
- Add validation $effect to error if both diffBeforeFlow and moduleActions provided
- Add $effect to sync props (diffBeforeFlow or moduleActions) to diffManager
- Add $effect to watch current flow changes and update afterFlow
- Replace computedDiff with diffManager.moduleActions
- Use raw modules instead of merged flow (diffManager handles merging)
- Expose getDiffManager() and setBeforeFlow() methods
- Pass diffManager to graph context instead of callbacks
- Remove $inspect for removed props

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Update FlowModuleSchemaMap to use FlowGraphV2's diffManager

- Remove import of flowDiffManager singleton
- Update setBeforeFlow to call graph.setBeforeFlow()
- Update setModuleActions and getModuleActions to use graph.getDiffManager()
- Add getDiffManager() proxy method
- Simplify handleAcceptModule and handleRejectModule to use new API
- Handle editor state separately from diff operations
- Remove diffBeforeFlow, moduleActions, onAcceptModule, onRejectModule props passed to FlowGraphV2
- Remove onAcceptModule and onRejectModule from Props interface and destructured props

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Update FlowAIChat to use flowModuleSchemaMap's diffManager

- Remove import of flowDiffManager singleton
- Update revertToSnapshot to use flowModuleSchemaMap.getDiffManager()
- Add null check for diffManager before using

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Verify FlowGraphDiffViewer compatibility with refactored architecture

FlowGraphDiffViewer already uses the correct prop patterns:
- Before graph: moduleActions prop (display-only mode)
- After graph: diffBeforeFlow prop (full diff mode with auto-computation)

Each FlowGraphV2 instance creates its own diffManager, making the side-by-side
view work correctly with independent diff state per graph.

No code changes required.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Update graph components to use diffManager instead of callbacks

- Update graphBuilder.svelte.ts to pass diffManager instead of onAcceptModule/onRejectModule
- Update InputNode and ModuleN type definitions with diffManager
- Update ModuleNode.svelte to pass diffManager to MapItem
- Update MapItem.svelte to pass diffManager to FlowModuleSchemaItem
- Update FlowModuleSchemaItem.svelte to use diffManager directly for accept/reject
- Replace callback-based accept/reject with direct diffManager calls
- Only show accept/reject buttons when beforeFlow exists and action is pending

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Fix removed modules not showing in diff viewer

Problem: After refactoring, removed modules were no longer appearing in the
diff viewer because we changed effectiveModules from using the merged flow
(which includes removed modules) to using raw modules.

Solution:
- Add mergedFlow state to flowDiffManager to store timeline's merged flow
- Add markRemovedAsShadowed parameter support for side-by-side view
- Store timeline.mergedFlow in auto-computation $effect
- Add getter for mergedFlow and setMarkRemovedAsShadowed method
- Clear mergedFlow in clearSnapshot()
- Update FlowGraphV2 to set markRemovedAsShadowed in diffManager
- Update effectiveModules/FailureModule/PreprocessorModule to use mergedFlow

The merged flow contains all modules including removed ones, enabling:
- Unified view: Removed modules appear in red with "removed" badge
- Side-by-side view: Removed modules show as shadowed in After graph

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Simplify accept/reject logic by removing actions instead of toggling pending state

Previously, accepting or rejecting a module action would set pending to false but keep the action in the moduleActions map. This caused a bug where the $effect would overwrite moduleActions with fresh actions having pending: true, making accept/reject buttons reappear on previously handled modules.

Now, when a user accepts or rejects a module action, we remove it entirely from the moduleActions map. This is simpler and fixes the button reappearing issue.

Changes:
- acceptModule: Remove action from moduleActions instead of setting pending: false
- rejectModule: Remove action from moduleActions instead of setting pending: false
- checkAndClearSnapshot: Check if moduleActions is empty instead of checking pending states
- Fix typo: getModuleFromFrom → getModuleFromFlow

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* cleaning

* fix logic

* make diff drawer part of manager

* accept submodules

* fixes

* Phase 4: Add checkAndApplyChanges() helper to flowDiffManager

- Added new checkAndApplyChanges() function to apply mergedFlow to flowStore when all changes are decided
- This replaces the old checkAndClearSnapshot() behavior and ensures flowStore is updated atomically
- Handles both flow structure and input schema updates

* Phase 2: Simplify acceptModule() - only modify mergedFlow

- Remove flowStore mutations from acceptModule()
- For removed modules: just delete the shadowed (__prefix) version from mergedFlow
- For added/modified: no action needed (already correct in mergedFlow)
- Call checkAndApplyChanges() to apply changes when all decided

* Phase 3: Simplify rejectModule() - only modify mergedFlow

- Remove all flowStore mutations from rejectModule()
- For added modules: delete from mergedFlow
- For removed modules: replace shadowed (__) module with original from beforeFlow
- For modified modules: restore old version in mergedFlow
- For Input schema: revert afterInputSchema
- Call checkAndApplyChanges() to apply changes when all decided

* Phase 5: Verify acceptAll/rejectAll work with new architecture

- acceptAll() and rejectAll() already pass options correctly to acceptModule/rejectModule
- They will automatically benefit from checkAndApplyChanges()
- No changes needed for this phase

* Phase 6: Remove FlowGraphV2 reactive effect that updates afterFlow

- Removed the  (lines 252-266) that continuously updated afterFlow
- This effect created reactive loops when flowStore changed
- afterFlow should only be set once when AI generates changes via setFlowYaml()
- The initial sync effect (lines 226-250) is kept for prop-driven diff mode

* Phase 7: Update FlowAIChat setFlowYaml to use diffManager

- Changed setFlowYaml() to use diffManager.setAfterFlow() instead of modifying flowStore
- flowStore remains unchanged during AI review phase
- Changes are staged in mergedFlow for user review
- Only applied to flowStore when all changes are accepted/rejected
- Added error handling for missing diffManager

* Fix linter warnings

- Remove unused FlowTimeline type import
- Fix ChangeTracker initialization with proper type parameter
- Keep deleteModuleFromFlow and checkAndClearSnapshot for potential future use

* Update plan document with implementation status

- Mark all phases as complete
- Add commit references
- Update file checklist
- Add implementation summary at top of document

* Add comprehensive implementation summary document

- Detailed overview of architecture changes
- Before/after comparisons for each file
- Complete testing scenarios checklist
- Troubleshooting guide
- Migration notes and backwards compatibility info

* Show pending modules in editor panel

- Pass diffManager from FlowModuleSchemaMap to FlowEditorPanel
- Add effectiveModules derived value that uses mergedFlow when in diff mode
- Update module iteration to use effectiveModules instead of flowStore
- Allows users to view added/modified modules during AI review
- Fixes issue where clicking on pending modules showed nothing

* Add implementation summary for show pending modules feature

* fix

* shorter system prompt

* Fix Input schema diff mode issues

- Add Accept/Reject buttons to Input node (previously only showed Diff button)
- Pass diffManager to FlowInput component
- Add effectiveSchema derived value that uses afterInputSchema when in diff mode
- Add effectiveDisabled to prevent editing Input when reviewing AI changes
- Update FlowInputViewer to show pending schema changes
- Fixes issue where Input schema changes couldn't be accepted/rejected
- Fixes issue where pending Input schema wasn't visible in the panel

* Disable delete and move buttons when in pending mode

- Add effectiveDeletable derived value that checks diffManager.hasPendingChanges
- Replace all instances of deletable with effectiveDeletable in template
- Prevents delete/move operations when AI changes are being reviewed
- Delete and move buttons are hidden when there are pending changes
- Buttons reappear once all changes are accepted or rejected
- Prevents conflicting operations during review phase

* no move or delte when reviewing

* use context

* inline script reduction

* use json

* rollback to direct modif

* fix merge

* cleaning

* fix reject removed

* add set step code tool

* better prompt

* add back relevant tools

* add back accept reject

* use edit mode for pending

* fix input

* remove unneeded effect

* cleaner + bug fix

* fix failure and preprocessor

* fix show diff for failure module

* fix accept reject on failre module

* no auto add module to context

* cleaning

* add back effect

* cleaning

* fix multiple setflowjson

* track effectivemoduleactions for graph rendering

* nit prompt

* styling

* rm md files

* rm flake copy

* cleaning

* fix z index

* fix revert

* only change before after

* use add remove modify tools

* input + failure + preproc tools

* parsing issues

* nit

* use raw schema for tools

* resolve ref for gemini

* fix schema

* show test on graph

* much cleaner logic

* ignore empty assets

* Remove debug console.log statements from production code

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Remove debug $inspect calls from FlowGraphV2

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Add error logging to setFlowJson before re-throwing

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Standardize null/undefined handling to prefer null

- Use .nullable().optional() instead of .nullish() in Zod schemas
- Simplify addModuleToFlow signature to use string | null
- Coerce undefined to null when extracting parsed args
- Simplify null checks to only check !== null

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Remove debug console.log from AI tool functions

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Extract special module IDs to constants

Add SPECIAL_MODULE_IDS constant with INPUT, PREPROCESSOR, and FAILURE
to avoid magic strings throughout the flow AI chat code.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Add cleanup for diffDrawer reference on unmount

Prevents potential memory leaks by clearing the diffDrawer reference
when the FlowGraphV2 component is destroyed.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Use structuredClone instead of JSON.parse(JSON.stringify())

structuredClone is more efficient and type-safe for deep cloning objects.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Cache module lookups in reconstructMergedFlow

Move getAllModulesMap and getAllModuleIds calls outside the loop to avoid
redundant recomputation. Track merged IDs incrementally as modules are added.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Revert "Use structuredClone instead of JSON.parse(JSON.stringify())"

This reverts commit a62ba5b980.

* cleaning

* allow delete

* better openflow for ai agents + truncate system prompt

* handle ai agent tools

* fix set code for tool

* fix wrong cancel request called

* mark tool calls as canceled

* get lang instructions

* use streamiing args

* give db url to claude

* fix revert

* save and clear when leaving editor

* keep whitespace in user message

* uniformize colors

* fix diff button

* remove db from backend claude

* remove move module tool

* no failure and preprocessor

* fix error given to llm

* fix z index

* fix ts errors

* cleaning

* fix add module logic

* fix(copilot): add 'tools' to branchPath description for aiagent containers

The branchPath parameter description was missing 'tools' option for aiagent
containers and didn't mention branchall support.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(copilot): correct AI agent tool IDs and summaries documentation

Tool summaries CAN contain spaces (they're human-readable descriptions).
Only tool IDs must avoid spaces.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(copilot): remove reference to non-existent set_flow_json tool

The set_module_code tool description referenced set_flow_json which
doesn't exist as an exposed tool (it's an internal helper).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(copilot): clarify inspect_inline_script is read-only

The tool description incorrectly suggested it could modify code.
This tool only inspects - use set_module_code to modify.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(copilot): clarify afterId behavior for AI agent tools

Updated wording to clarify that afterId can be used but is optional
for AI agent tools since tool order doesn't affect execution.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor(copilot): remove unused id param from get_instructions_for_code_generation

The id parameter was only used to check for preprocessor, which is no
longer needed. Simplified the tool to only require the language param.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* docs(copilot): add result format to search_scripts tool description

Helps AI understand what data format to expect from the tool.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* docs(copilot): add result format to resource_type tool description

Helps AI understand what data format to expect from the tool and
provides example resource type names.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* nit

* Add support for adding branches to branchall/branchone via add_module

Previously, add_module could only add modules inside existing branches.
Now, using insideId with branchPath=null will add a NEW branch to a
branchall or branchone container.

API:
- add_module({ insideId: "my_branchall", branchPath: null, value: { summary: "New Branch", skip_failure: false, modules: [] } })
- add_module({ insideId: "my_branchone", branchPath: null, value: { summary: "Condition", expr: "...", modules: [] } })

Changes:
- Extended addModuleToFlow to handle branchPath=null case
- Updated validation to allow branchPath=null when adding branches
- Updated tool descriptions and system prompt documentation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* nit

* add remove branch tool

* check all ids for duplicates

* no dup

* nit

* cleaning

* fix dup ids

* split core.ts

* only mount diff drawer if useful

* remove wrong logic

* update exprs

* fix

* chore(flow): Add unit tests to flow diff manager (#7291)

* setup

* add basic tests for flowdiff

* add complex tests

* fix branch issue

* more complex tests

* add flow diff manager tests

* add utils

* better handling of moved case

* more tests for move case

* add buggy test case

* rm

* rework config

* cleaning

* fix config

* rm

* fix for reverting type change module

* all good

* rm

* add missing testmode

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-12-08 19:08:14 +01:00

646 lines
18 KiB
Svelte

<script lang="ts">
import { preventDefault, stopPropagation } from 'svelte/legacy'
import Popover from '$lib/components/Popover.svelte'
import { classNames, type StateStore } from '$lib/utils'
import {
Bed,
Database,
Gauge,
Move,
PhoneIncoming,
Repeat,
Square,
SkipForward,
Pin,
X,
Play,
Loader2,
TriangleAlert,
Timer,
Maximize2
} from 'lucide-svelte'
import { createEventDispatcher, getContext } from 'svelte'
import { fade } from 'svelte/transition'
import type { FlowEditorContext } from '../types'
import { twMerge } from 'tailwind-merge'
import IdEditorInput from '$lib/components/IdEditorInput.svelte'
import { dfs } from '../dfs'
import { dfs as dfsPreviousResults } from '../previousResults'
import { Drawer } from '$lib/components/common'
import DrawerContent from '$lib/components/common/drawer/DrawerContent.svelte'
import { getDependeeAndDependentComponents } from '../flowExplorer'
import { replaceId } from '../flowStore.svelte'
import FlowModuleSchemaItemViewer from './FlowModuleSchemaItemViewer.svelte'
import type { PropPickerContext } from '$lib/components/prop_picker'
import OutputPicker from '$lib/components/flows/propPicker/OutputPicker.svelte'
import OutputPickerInner from '$lib/components/flows/propPicker/OutputPickerInner.svelte'
import type { FlowState } from '$lib/components/flows/flowState'
import { Button } from '$lib/components/common'
import ModuleTest from '$lib/components/ModuleTest.svelte'
import { getStepHistoryLoaderContext } from '$lib/components/stepHistoryLoader.svelte'
import type { Job } from '$lib/gen'
import {
getNodeColorClasses,
aiActionToNodeState,
type FlowNodeState
} from '$lib/components/graph'
import type { ModuleActionInfo } from '$lib/components/flows/flowDiff'
import DiffActionBar from './DiffActionBar.svelte'
import { getGraphContext } from '$lib/components/graph/graphContext'
interface Props {
selected?: boolean
deletable?: boolean
moduleAction: ModuleActionInfo | undefined
retry?: boolean
cache?: boolean
earlyStop?: boolean
skip?: boolean
suspend?: boolean
sleep?: boolean
mock?:
| {
enabled?: boolean
return_value?: unknown
}
| undefined
bold?: boolean
id?: string | undefined
label: string
path?: string
modType?: string | undefined
nodeState?: FlowNodeState
concurrency?: boolean
// TODO: Implement for this one. See how concurrency is implemented.
debouncing?: boolean
retries?: number | undefined
warningMessage?: string | undefined
isTrigger?: boolean
editMode?: boolean
alwaysShowOutputPicker?: boolean
loopStatus?: { type: 'inside' | 'self'; flow: 'forloopflow' | 'whileloopflow' } | undefined
icon?: import('svelte').Snippet
onTestUpTo?: ((id: string) => void) | undefined
inputTransform?: Record<string, any> | undefined
onUpdateMock?: (mock: { enabled: boolean; return_value?: unknown }) => void
onEditInput?: (moduleId: string, key: string) => void
flowJob?: Job | undefined
isOwner?: boolean
enableTestRun?: boolean
maximizeSubflow?: () => void
}
let {
selected = false,
deletable = false,
moduleAction = undefined,
retry = false,
cache = false,
earlyStop = false,
skip = false,
suspend = false,
sleep = false,
mock = { enabled: false },
bold = false,
id = undefined,
label,
path = '',
modType = undefined,
nodeState,
concurrency = false,
debouncing = false,
retries = undefined,
warningMessage = undefined,
isTrigger = false,
editMode = false,
alwaysShowOutputPicker = false,
loopStatus = undefined,
icon,
onTestUpTo,
inputTransform,
onUpdateMock,
onEditInput,
flowJob,
enableTestRun = false,
maximizeSubflow = undefined
}: Props = $props()
// Execution state takes priority over AI action colors
let effectiveState = $derived(nodeState ?? aiActionToNodeState(moduleAction?.action))
let colorClasses = $derived(getNodeColorClasses(effectiveState, selected))
const flowEditorContext = getContext<FlowEditorContext | undefined>('FlowEditorContext')
const flowInputsStore = flowEditorContext?.flowInputsStore
const flowStore = flowEditorContext?.flowStore
const flowGraphContext = getGraphContext()
const diffManager = flowGraphContext?.diffManager
let pickableIds: Record<string, any> | undefined = $state(undefined)
const dispatch = createEventDispatcher()
const propPickerContext = getContext<PropPickerContext>('PropPickerContext')
const flowPropPickerConfig = propPickerContext?.flowPropPickerConfig
const pickablePropertiesFiltered = propPickerContext?.pickablePropertiesFiltered
$effect(() => {
pickableIds = $pickablePropertiesFiltered?.priorIds
})
let editId = $state(false)
let newId: string = $state(id ?? '')
let moduleTest: ModuleTest | undefined = $state(undefined)
let testIsLoading = $state(false)
let hover = $state(false)
let connectingData: any | undefined = $state(undefined)
let outputPicker: OutputPicker | undefined = $state(undefined)
let testJob: any | undefined = $state(undefined)
let outputPickerBarOpen = $state(false)
let flowStateStore = $derived(flowEditorContext?.flowStateStore)
let stepHistoryLoader = getStepHistoryLoaderContext()
function updateConnectingData(
id: string | undefined,
pickableIds: Record<string, any> | undefined,
flowPropPickerConfig: any | undefined,
flowStateStore: StateStore<FlowState> | undefined
) {
if (!id) return
connectingData =
flowPropPickerConfig && pickableIds && Object.keys(pickableIds).includes(id)
? pickableIds[id]
: (flowStateStore?.val?.[id]?.previewResult ?? {})
}
$effect(() => {
updateConnectingData(id, pickableIds, $flowPropPickerConfig, flowStateStore)
})
let isConnectingCandidate = $derived(
!!id && !!$flowPropPickerConfig && !!pickableIds && Object.keys(pickableIds).includes(id)
)
const outputPickerVisible = $derived(
editMode && (isConnectingCandidate || alwaysShowOutputPicker) && !!id
)
const icon_render = $derived(icon)
let testRunDropdownOpen = $state(false)
let outputPickerInner: OutputPickerInner | undefined = $state(undefined)
let historyOpen = $derived.by(() => outputPickerInner?.getHistoryOpen?.() ?? false)
</script>
{#if deletable && id && editId}
{@const flowStore = flowEditorContext?.flowStore ?? undefined}
{@const getDeps = getDependeeAndDependentComponents(
id,
flowStore?.val?.value.modules ?? [],
flowStore?.val?.value.failure_module
)}
<Drawer bind:open={editId}>
<DrawerContent title="Edit Step Id {id}" on:close={() => (editId = false)}>
<div>
<IdEditorInput
buttonText="Edit Id "
btnClasses="!ml-1"
label=""
initialId={id}
acceptUnderScores
reservedIds={dfs(flowStore?.val?.value.modules ?? [], (x) => x.id)}
bind:value={newId}
onSave={({ oldId, newId }) => {
dispatch('changeId', { id: oldId, newId, deps: getDeps?.dependents ?? {} })
editId = false
}}
onClose={() => {
editId = false
}}
/>
<div class="mt-8">
<h3>Step Inputs Replacements</h3>
<div class="text-2xs text-primary pt-0.5">
Replace all occurrences of `results.<span class="font-bold">{id}</span>` with{' '}
results.<span class="font-bold">{newId}</span> in the step inputs of all steps that depend
on it.
</div>
<div class="pt-8 flex flex-col gap-y-4">
{#if Object.keys(getDeps?.dependents ?? {})?.length > 0}
{#each Object.entries(getDeps?.dependents ?? {}) as dependents}
<div>
<h4>{dependents[0]}</h4>
<div>
{#each dependents?.[1] as d}
<div>
<span class="font-mono text-sm">{d}</span> &rightarrow;
<span class="font-mono text-sm">{replaceId(d, id, newId)}</span>
</div>
{/each}
</div>
</div>
{/each}
{:else}
<div class="text-2xs text-primary"> No dependents </div>
{/if}
</div>
</div>
</div>
</DrawerContent>
</Drawer>
{/if}
{#if deletable && id && flowStore && outputPickerVisible}
{@const flowStoreVal = flowStore.val}
{@const mod = flowStoreVal?.value ? dfsPreviousResults(id, flowStoreVal, false)[0] : undefined}
{#if mod && flowStateStore?.val?.[id]}
<ModuleTest
bind:this={moduleTest}
{mod}
bind:testIsLoading
bind:testJob
onJobDone={() => {
outputPickerInner?.setJobPreview?.()
}}
/>
{/if}
{/if}
<div class="relative">
<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div
class={classNames(
'w-full module flex rounded-md cursor-pointer max-w-full drop-shadow-base',
colorClasses.bg
)}
style="width: 275px; height: 34px;"
onmouseenter={() => (hover = true)}
onmouseleave={() => (hover = false)}
onpointerdown={stopPropagation(preventDefault((e) => dispatch('pointerdown', e)))}
>
{#if id}
<DiffActionBar moduleId={id} {moduleAction} {diffManager} {flowStore} />
{/if}
<div
class={classNames('absolute z-0 rounded-md outline-offset-0', colorClasses.outline)}
style={`width: 275px; height: 34px;`}
></div>
<div
class="absolute text-sm right-2 flex flex-row gap-1 z-10 transition-all duration-100"
style={`bottom: ${outputPickerBarOpen ? '-38px' : '-12px'}`}
>
{#if retry}
<Popover notClickable>
<div
transition:fade|local={{ duration: 200 }}
class="center-center rounded border bg-surface border-gray-400 text-secondary px-1 py-0.5"
>
{#if retries}<span class="text-red-400 mr-2">{retries}</span>{/if}
<Repeat size={12} />
</div>
{#snippet text()}
Retries
{/snippet}
</Popover>
{/if}
{#if concurrency}
<Popover notClickable>
<div
transition:fade|local={{ duration: 200 }}
class="center-center rounded border bg-surface border-gray-400 text-secondary px-1 py-0.5"
>
<Gauge size={12} />
</div>
{#snippet text()}
Concurrency Limits
{/snippet}
</Popover>
{/if}
{#if debouncing}
<Popover notClickable>
<div
transition:fade|local={{ duration: 200 }}
class="center-center rounded border bg-surface border-gray-400 text-secondary px-1 py-0.5"
>
<Timer size={12} />
</div>
{#snippet text()}
Debouncing
{/snippet}
</Popover>
{/if}
{#if cache}
<Popover notClickable>
<div
transition:fade|local={{ duration: 200 }}
class="center-center rounded border bg-surface border-gray-400 text-secondary px-1 py-0.5"
>
<Database size={12} />
</div>
{#snippet text()}
Cached
{/snippet}
</Popover>
{/if}
{#if earlyStop}
<Popover notClickable>
<div
transition:fade|local={{ duration: 200 }}
class="center-center bg-surface rounded border border-gray-400 text-secondary px-1 py-0.5"
>
<Square size={12} />
</div>
{#snippet text()}
{isTrigger ? 'Stop early if there are no new events' : 'Early stop/break'}
{/snippet}
</Popover>
{/if}
{#if skip}
<Popover notClickable>
<div
transition:fade|local={{ duration: 200 }}
class="center-center bg-surface rounded border border-gray-400 text-secondary px-1 py-0.5"
>
<SkipForward size={12} />
</div>
{#snippet text()}
Skip
{/snippet}
</Popover>
{/if}
{#if suspend}
<Popover notClickable>
<div
transition:fade|local={{ duration: 200 }}
class="center-center bg-surface rounded border border-gray-400 text-secondary px-1 py-0.5"
>
<PhoneIncoming size={12} />
</div>
{#snippet text()}
Suspend
{/snippet}
</Popover>
{/if}
{#if sleep}
<Popover notClickable>
<div
transition:fade|local={{ duration: 200 }}
class="center-center bg-surface rounded border border-gray-400 text-secondary px-1 py-0.5"
>
<Bed size={12} />
</div>
{#snippet text()}
Sleep
{/snippet}
</Popover>
{/if}
{#if mock?.enabled}
<Popover notClickable>
<button
transition:fade|local={{ duration: 200 }}
class="center-center bg-surface rounded border border-gray-400 text-secondary px-1 py-0.5"
onclick={() => {
outputPicker?.toggleOpen()
}}
data-popover
>
<Pin size={12} />
</button>
{#snippet text()}
Pinned
{/snippet}
</Popover>
{/if}
</div>
<div class="flex flex-col w-full">
<FlowModuleSchemaItemViewer
{label}
{path}
{id}
{deletable}
{bold}
bind:editId
{hover}
{colorClasses}
>
{#snippet icon()}
{@render icon_render?.()}
{/snippet}
</FlowModuleSchemaItemViewer>
{#if outputPickerVisible}
<OutputPicker
bind:this={outputPicker}
{selected}
{hover}
{isConnectingCandidate}
{historyOpen}
{inputTransform}
id={id ?? ''}
bind:bottomBarOpen={outputPickerBarOpen}
{loopStatus}
{onEditInput}
>
{#snippet children({ allowCopy, isConnecting, selectConnection })}
<OutputPickerInner
{allowCopy}
prefix={'results'}
connectingData={isConnecting ? connectingData : undefined}
{mock}
{testJob}
moduleId={id}
onSelect={selectConnection}
{onUpdateMock}
{path}
{loopStatus}
rightMargin
historyOffset={{ mainAxis: 12, crossAxis: -9 }}
clazz="p-1"
isLoading={testIsLoading ||
(id ? stepHistoryLoader?.stepStates[id]?.loadingJobs : false)}
initial={id ? stepHistoryLoader?.stepStates[id]?.initial : undefined}
bind:this={outputPickerInner}
/>
{/snippet}
</OutputPicker>
{/if}
</div>
{#if deletable}
{#if maximizeSubflow !== undefined}
{@render buttonMaximizeSubflow?.()}
{/if}
{#if id !== 'preprocessor'}
<!-- The `style="will-change: transform;"` fixes a bug in Safari where the close and move
and delete buttons would get clipped (unless an animation is running) -->
<div
class={twMerge('absolute -translate-y-[100%] top-2 right-4 h-7 p-1 min-w-7')}
style="will-change: transform;"
>
<button
class={twMerge(
'trash center-center p-1 text-secondary shadow-sm bg-surface duration-0 hover:bg-surface-tertiary',
hover || selected ? 'block' : '!hidden',
'shadow-md rounded-md',
'group-hover:block'
)}
onclick={stopPropagation(preventDefault((event) => dispatch('move')))}
title="Move"
>
<Move size={12} />
</button>
</div>
{/if}
<div
class="absolute -translate-y-[100%] top-2 -right-2 h-7 p-1 min-w-7"
style="will-change: transform;"
>
<button
class={twMerge(
'trash center-center text-secondary shadow-sm bg-surface duration-0 hover:bg-red-400 hover:text-white p-1',
selected || hover ? 'block' : '!hidden',
'group-hover:block',
'shadow-md rounded-md'
)}
title="Delete"
onclick={stopPropagation(
preventDefault((event) => dispatch('delete', { id, type: modType }))
)}
onpointerdown={stopPropagation(preventDefault(() => {}))}
>
<X size={12} />
</button>
</div>
{#if (id && Object.values($flowInputsStore?.[id]?.flowStepWarnings || {}).length > 0) || Boolean(warningMessage)}
<Popover
style="will-change: transform;"
class={twMerge(
'absolute -translate-y-[100%] top-1 -left-1',
'flex items-center justify-center rounded-b-none rounded-md p-1 shadow-md duration-0 ',
id &&
Object.values($flowInputsStore?.[id]?.flowStepWarnings || {})?.some(
(x) => x.type === 'error'
)
? 'border-red-600 text-red-600 bg-red-100 hover:bg-red-300'
: ' text-yellow-600 bg-yellow-100 hover:bg-yellow-300'
)}
>
{#snippet text()}
<ul class="list-disc px-2">
{#if id}
{#each Object.values($flowInputsStore?.[id]?.flowStepWarnings || {}) as m}
<li>
{m.message}
</li>
{/each}
{/if}
</ul>
{/snippet}
<TriangleAlert size={12} strokeWidth={2} />
</Popover>
{/if}
{:else if maximizeSubflow !== undefined}
{@render buttonMaximizeSubflow?.()}
{/if}
</div>
{#if editMode && enableTestRun && flowJob?.type !== 'QueuedJob'}
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div
class="absolute top-1/2 -translate-y-1/2 -translate-x-[100%] -left-[0] flex items-center w-fit px-1 h-9 min-w-9"
onmouseenter={() => (hover = true)}
onmouseleave={() => (hover = false)}
>
{#if (hover || selected || testRunDropdownOpen) && outputPickerVisible}
<div transition:fade={{ duration: 100 }}>
{#if !testIsLoading}
<Button
size="xs"
title="Run"
variant="default"
btnClasses="px-1 py-1.5 bg-surface"
on:click={() => {
outputPicker?.toggleOpen(true)
moduleTest?.loadArgsAndRunTest()
}}
dropdownItems={[
{
label: 'Test up to here',
onClick: () => {
if (id) {
onTestUpTo?.(id)
}
}
}
]}
dropdownBtnClasses="!w-3 px-0.5"
bind:dropdownOpen={testRunDropdownOpen}
>
{#if testIsLoading}
<Loader2 size={12} class="animate-spin" />
{:else}
<Play size={12} />
{/if}
</Button>
{:else}
<Button
size="xs"
color="red"
variant="contained"
btnClasses="!h-[25.5px] !w-[36px] !p-1.5 gap-0.5"
on:click={async () => {
moduleTest?.cancelJob()
}}
>
<Loader2 size={10} class="animate-spin mr-0.5" />
<X size={14} />
</Button>
{/if}
</div>
{/if}
</div>
{/if}
</div>
{#snippet buttonMaximizeSubflow()}
<div class="absolute -translate-y-[100%] top-2 right-10 h-7 p-1">
<button
title="Expand subflow"
class={twMerge(
'center-center text-secondary shadow-sm bg-surface duration-0 hover:bg-surface-tertiary p-1',
'shadow-md rounded-md',
hover || selected ? 'opacity-100' : 'opacity-50'
)}
onclick={(e) => {
e.stopPropagation()
e.preventDefault()
maximizeSubflow?.()
}}
onpointerdown={(e) => {
e.stopPropagation()
e.preventDefault()
}}
>
<Maximize2 size={12} />
</button>
</div>
{/snippet}
<style>
.module:hover .trash {
display: flex !important;
}
</style>