Skip to content

fix(input-format): field not editable race condition#5102

Merged
icecrasher321 merged 3 commits into
stagingfrom
fix/inp-format-edit
Jun 17, 2026
Merged

fix(input-format): field not editable race condition#5102
icecrasher321 merged 3 commits into
stagingfrom
fix/inp-format-edit

Conversation

@icecrasher321

Copy link
Copy Markdown
Collaborator

Summary

Race condition that prevents input format field from being edited sometimes until the "+" is clicked

Type of Change

  • Bug fix

Testing

Tested manually

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

@vercel

vercel Bot commented Jun 16, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Jun 16, 2026 10:36pm

Request Review

@cursor

cursor Bot commented Jun 16, 2026

Copy link
Copy Markdown

PR Summary

Low Risk
Targeted workflow editor bug fix and shared default factory; no auth or execution-path changes, with unit tests for the new helper.

Overview
Fixes a race where input-format rows could feel stuck until "+" was clicked: when the sub-block store was still empty, each render minted a new default field id, so keystrokes targeted ids the UI no longer showed.

The editor now keeps a ref-cached fallback row and commits updates via fieldsRef (what is rendered) instead of re-deriving defaults from the store on every change. Default rows are created through shared createDefaultInputFormatField() in input-format.ts, used when seeding defaults in defaults.ts, stores/workflows/utils.ts, and add/remove/clear paths in the UI. Tests cover the canonical default shape (no default description). Duplicate sub-block init helpers were removed from workflow/store.ts in this diff.

Reviewed by Cursor Bugbot for commit 8e92af9. Configure here.

@icecrasher321

Copy link
Copy Markdown
Collaborator Author

bugbot run

@icecrasher321

Copy link
Copy Markdown
Collaborator Author

@greptile

@greptile-apps

greptile-apps Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a race condition in the FieldFormat component where editing a newly added input-format field had no effect until clicking "+". The bug was caused by updateField re-deriving a fresh fallback field (with a brand-new generateId()) from the empty store value on every keystroke, so the rendered field's id never matched anything in the array being mapped over.

  • Stable fallback ref: fallbackFieldRef caches the placeholder Field on first render so its id is constant across all renders while the store value is still empty, preventing input remounts and ensuring updateField always maps against the same id the UI is showing.
  • fieldsRef replaces storeValueRef: updateField now reads the actual rendered fields (store value or fallback) rather than re-deriving them from the raw store value, eliminating the id mismatch entirely.
  • Factory extraction: createDefaultInputFormatField() is centralised in lib/workflows/input-format.ts, replacing three separate inline object literals (component, defaults.ts, utils.ts), and the now-dead resolveInitialSubblockValue/cloneInitialSubblockValue helpers in store.ts are removed.

Confidence Score: 5/5

Safe to merge — the changes are tightly scoped to the input-format editor component and its shared factory helper.

The fix correctly addresses the root cause: the fallback field ref ensures a stable id across renders, and switching from storeValueRef to fieldsRef ensures updateField always maps against the ids actually shown in the UI. The factory extraction is a straightforward deduplication with no observable behavioral differences. The dead-code removal in store.ts has no callers to break.

No files require special attention.

Important Files Changed

Filename Overview
apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/starter/input-format.tsx Core bug fix: fallback field cached in a ref for stable id, and fieldsRef replaces storeValueRef in updateField so updates always map against the rendered ids.
apps/sim/lib/workflows/input-format.ts Adds InputFormatFieldState interface and createDefaultInputFormatField() factory as single source of truth; no logic changes to existing functions.
apps/sim/stores/workflows/workflow/store.ts Removes dead resolveInitialSubblockValue/cloneInitialSubblockValue helpers and their associated imports; no functional change to the store.
apps/sim/lib/workflows/defaults.ts Replaces inline default field object with createDefaultInputFormatField(); functionally identical.
apps/sim/stores/workflows/utils.ts Replaces inline default field object with createDefaultInputFormatField() in prepareBlockState; functionally identical.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant UI as FieldFormat (render)
    participant Ref as fallbackFieldRef / fieldsRef
    participant Store as SubBlock Store

    Note over UI,Store: Before fix — new block, store empty
    UI->>Ref: "fields = [createDefaultField()] → id=abc"
    UI->>Store: updateField(abc, name, foo)
    Store-->>UI: "storeValue = null/empty"
    UI->>UI: "currentFields = [createDefaultField()] → id=xyz (new!)"
    UI->>Store: "setStoreValue([{id:xyz, name:foo}])"
    Note over UI,Store: id mismatch — input never commits correctly

    Note over UI,Store: After fix — new block, store empty
    UI->>Ref: "fallbackFieldRef.current ??= createDefaultField() → id=abc (cached)"
    UI->>Ref: "fieldsRef.current = [{id:abc}]"
    UI->>Store: updateField(abc, name, foo)
    Store-->>Ref: "fieldsRef.current = [{id:abc}] (same id)"
    UI->>Store: "setStoreValue([{id:abc, name:foo}])"
    Note over UI,Store: id stable — input commits correctly
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant UI as FieldFormat (render)
    participant Ref as fallbackFieldRef / fieldsRef
    participant Store as SubBlock Store

    Note over UI,Store: Before fix — new block, store empty
    UI->>Ref: "fields = [createDefaultField()] → id=abc"
    UI->>Store: updateField(abc, name, foo)
    Store-->>UI: "storeValue = null/empty"
    UI->>UI: "currentFields = [createDefaultField()] → id=xyz (new!)"
    UI->>Store: "setStoreValue([{id:xyz, name:foo}])"
    Note over UI,Store: id mismatch — input never commits correctly

    Note over UI,Store: After fix — new block, store empty
    UI->>Ref: "fallbackFieldRef.current ??= createDefaultField() → id=abc (cached)"
    UI->>Ref: "fieldsRef.current = [{id:abc}]"
    UI->>Store: updateField(abc, name, foo)
    Store-->>Ref: "fieldsRef.current = [{id:abc}] (same id)"
    UI->>Store: "setStoreValue([{id:abc, name:foo}])"
    Note over UI,Store: id stable — input commits correctly
Loading

Reviews (1): Last reviewed commit: "simplify" | Re-trigger Greptile

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 8e92af9. Configure here.

@greptile-apps

greptile-apps Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a race condition in FieldFormat where a newly added input-format block couldn't be edited until the "+" button was clicked. The root cause was that updateField (a stable useCallback) re-created the default field with a fresh id on every invocation when the store was still empty, so the map never found a matching field and every keystroke was silently discarded.

  • Core fix: the default fallback field is now cached in fallbackFieldRef so its id stays constant across renders; updateField maps against fieldsRef.current (the rendered fields) instead of re-deriving from the raw, potentially-empty storeValue.
  • Consolidation: four identical inline field-literal objects across input-format.tsx, defaults.ts, utils.ts, and store.ts are replaced by a single createDefaultInputFormatField() factory; the duplicate resolveInitialSubblockValue / cloneInitialSubblockValue helpers in store.ts are removed entirely.
  • Tests: new unit tests for createDefaultInputFormatField cover shape, absence of description, and freshness of IDs per call.

Confidence Score: 5/5

Safe to merge — the change is tightly scoped to the input-format editor component and its seeding helpers, with no impact on execution or persistence paths.

The fix directly addresses the documented race condition: a cached ref replaces per-render ID generation so the stable updateField callback always maps against the correct field IDs. The consolidation into createDefaultInputFormatField() removes four previously divergent inline literals, and the removal of the duplicate store helpers is fully confirmed (no remaining callers). New unit tests cover the factory's shape, ID uniqueness, and the intentional omission of description.

No files require special attention.

Important Files Changed

Filename Overview
apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/starter/input-format.tsx Core fix: stable fallback via fallbackFieldRef and fieldsRef replaces per-call createDefaultField() in updateField; correctly eliminates the ID mismatch race condition.
apps/sim/lib/workflows/input-format.ts New createDefaultInputFormatField() export provides a single source of truth for the default field shape; intentionally omits description (was '' in the component's local helper) — tested explicitly.
apps/sim/stores/workflows/workflow/store.ts Removes duplicate resolveInitialSubblockValue / cloneInitialSubblockValue helpers and the inline input-format seeding object; no remaining callers confirmed.
apps/sim/stores/workflows/utils.ts Replaces inline default-field literal in prepareBlockState with createDefaultInputFormatField(); straightforward deduplication.
apps/sim/lib/workflows/defaults.ts Replaces inline default-field literal in resolveInitialValue with createDefaultInputFormatField(); straightforward deduplication.
apps/sim/lib/workflows/input-format.test.ts Adds three concurrent unit tests for createDefaultInputFormatField covering shape correctness, absence of description, and ID uniqueness per call.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant User
    participant FieldFormat
    participant fieldsRef
    participant SubBlockStore

    Note over FieldFormat: Component mounts, storeValue = null

    rect rgb(255, 220, 220)
        Note over FieldFormat: BEFORE FIX (bug)
        FieldFormat->>FieldFormat: "render → createDefaultField() → id='X'"
        User->>FieldFormat: type 'a' in name input
        FieldFormat->>fieldsRef: updateField('X', 'name', 'a')
        Note over fieldsRef: storeValueRef.current = null → createDefaultField() → id='Y' (new!)
        fieldsRef->>SubBlockStore: map finds no 'X' in ['Y'] → no update
        SubBlockStore-->>FieldFormat: storeValue still null → keystroke lost
    end

    rect rgb(200, 255, 200)
        Note over FieldFormat: AFTER FIX
        FieldFormat->>FieldFormat: "render → fallbackFieldRef.current ??= createDefaultInputFormatField() → id='X' (cached)"
        User->>FieldFormat: type 'a' in name input
        FieldFormat->>fieldsRef: updateField('X', 'name', 'a')
        Note over fieldsRef: fieldsRef.current = [fallbackField id='X']
        fieldsRef->>SubBlockStore: "map finds 'X' → setStoreValue([{id:'X', name:'a', ...}])"
        SubBlockStore-->>FieldFormat: "storeValue = [{id:'X', name:'a'}] → re-render OK"
    end
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant User
    participant FieldFormat
    participant fieldsRef
    participant SubBlockStore

    Note over FieldFormat: Component mounts, storeValue = null

    rect rgb(255, 220, 220)
        Note over FieldFormat: BEFORE FIX (bug)
        FieldFormat->>FieldFormat: "render → createDefaultField() → id='X'"
        User->>FieldFormat: type 'a' in name input
        FieldFormat->>fieldsRef: updateField('X', 'name', 'a')
        Note over fieldsRef: storeValueRef.current = null → createDefaultField() → id='Y' (new!)
        fieldsRef->>SubBlockStore: map finds no 'X' in ['Y'] → no update
        SubBlockStore-->>FieldFormat: storeValue still null → keystroke lost
    end

    rect rgb(200, 255, 200)
        Note over FieldFormat: AFTER FIX
        FieldFormat->>FieldFormat: "render → fallbackFieldRef.current ??= createDefaultInputFormatField() → id='X' (cached)"
        User->>FieldFormat: type 'a' in name input
        FieldFormat->>fieldsRef: updateField('X', 'name', 'a')
        Note over fieldsRef: fieldsRef.current = [fallbackField id='X']
        fieldsRef->>SubBlockStore: "map finds 'X' → setStoreValue([{id:'X', name:'a', ...}])"
        SubBlockStore-->>FieldFormat: "storeValue = [{id:'X', name:'a'}] → re-render OK"
    end
Loading

Reviews (2): Last reviewed commit: "simplify" | Re-trigger Greptile

@icecrasher321 icecrasher321 merged commit 8fe090a into staging Jun 17, 2026
16 checks passed
@waleedlatif1 waleedlatif1 deleted the fix/inp-format-edit branch June 17, 2026 02:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant