Skip to content

45972 backend [ templates ] Auto-cleaning of broken links to fields in templates#201

Merged
EBirkenfeld merged 9 commits into
masterfrom
backend/template/45972__auto_cleaning_broken_links
May 5, 2026
Merged

45972 backend [ templates ] Auto-cleaning of broken links to fields in templates#201
EBirkenfeld merged 9 commits into
masterfrom
backend/template/45972__auto_cleaning_broken_links

Conversation

@EBirkenfeld
Copy link
Copy Markdown
Collaborator

@EBirkenfeld EBirkenfeld commented Apr 17, 2026

1. Description (Problem)

When editing a workflow template, after a user deletes a field from the Kick-off Form or a task's Output Fields, references to that field ({{field-xxx}}) continued to appear in:

  • Step names
  • Step descriptions
  • Workflow name template

Where it manifested: Template editing screen (/templates/edit/:id). Especially noticeable when steps were expanded — stale {{field-xxx}} references remained visible until a full page reload (F5). Collapsed steps cleaned up correctly.


2. Context

  • Ticket #45972 — auto-cleanup of invalid field references when saving templates.
  • cleanTemplateReferences was already implemented and called inside mapTemplateRequest to clean data before sending to the backend. However, the cleanup result was not synchronized back to the Redux store or Lexical editors.
  • Two independent sources of the bug:
    1. Redux storehandleChangeTemplateField stored the template without cleanup
    2. Lexical RichEditor — uses defaultValue (one-time initialization), doesn't react to prop updates

3. Solution

Two-level fix:

  1. Optimistic cleanup in Redux — call cleanTemplateReferences immediately when kickoff or tasks change in handleChangeTemplateField, before writing to the store. The user sees results instantly.

  2. Lexical editor synchronization — added replaceContent method to RichEditor that programmatically updates Lexical content. Added lastEmittedValue tracking + useEffect to InputWithVariables and TaskDescriptionEditor, so when value changes externally (from Redux cleanup), the editor updates its DOM.


4. Implementation Details

Changed Files

File What changed
utils/template.ts Added cleanTemplateReferences function to strip invalid {{field-xxx}} references from task names, descriptions, conditions, performers, due dates, wfNameTemplate
utils/__tests__/template.test.ts Tests for cleanTemplateReferences — 6 test cases
TemplateEdit.tsx Import cleanTemplateReferences, call on field === 'kickoff' || field === 'tasks'
RichEditor/types.ts Added replaceContent(markdown: string): void to IRichEditorHandle
RichEditor/RichEditor.tsx replaceContent implementation via applyMarkdownToEditor
InputWithVariables.tsx lastEmittedValue ref + useEffect for external sync + handleChange wrapper
TaskDescriptionEditor.tsx Same sync pattern: lastEmittedValue + useEffect + wrappedHandleChange

Key Decisions

  • cleanTemplateReferences is idempotent — safe to call twice (called in both UI and mapTemplateRequest)
  • lastEmittedValue ref distinguishes external prop changes from user input, preventing cursor jumps during regular editing
  • replaceContent uses applyMarkdownToEditor with 'history-merge' tag to avoid polluting the undo stack

5. What to Test

5.1 Preconditions

  • Dev environment with frontend running
  • Authenticated user with template creation permissions
  • Navigate to /templates/create or /templates/edit/:id

5.2 Positive Scenarios

Scenario 1: Delete kickoff field — collapsed steps

  1. Create a new template
  2. Add a field to the Kick-off Form (e.g., Small Text Field)
  3. In Step 1, use "Insert" to add a reference to this field in Step name
  4. Collapse the step
  5. Delete the field from Kick-off Form
  6. Expected: {{field-xxx}} reference disappears from the step name instantly, no reload needed

Scenario 2: Delete kickoff field — expanded steps

  1. Repeat steps 1-3 from Scenario 1
  2. Keep the step expanded
  3. Delete the field from Kick-off Form
  4. Expected: {{field-xxx}} reference disappears from both the Step name field (inside RichEditor) and Description

Scenario 3: Delete task output field

  1. Add an Output Field to Step 1
  2. In Step 2, insert a reference to this output in Step name and Description
  3. Delete the output field from Step 1
  4. Expected: reference is removed from Step 2

Scenario 4: System variables are preserved

  1. Insert {{workflow-starter}} in Step name or Description
  2. Delete any kickoff field
  3. Expected: {{workflow-starter}} remains intact

Scenario 5: Workflow name cleanup

  1. In Workflow name, add a reference to a kickoff field via Insert
  2. Delete that field
  3. Expected: reference removed; system variables {{date}}, {{template-name}} preserved

Scenario 6: Enable Template after cleanup

  1. Execute Scenario 2
  2. Click "Enable Template"
  3. Expected: template activates without errors, references don't reappear

5.3 Negative Scenarios and Edge Cases

  1. Delete all fields: remove all kickoff fields while all steps reference them → all references should be cleaned, steps should not break
  2. Empty step name after cleanup: if Step name consists only of {{field-xxx}}, after cleanup the name should become Step N (fallback)
  3. Rapid deletion of multiple fields: delete 2-3 fields quickly in succession → all references cleaned correctly, UI doesn't freeze
  4. Conditions: if a deleted field is used in a "Check If" condition rule → the rule should be removed from conditions

5.4 Verification Points

  • UI: visually confirm that {{field-xxx}} does not appear after field deletion
  • DevTools → Network: on auto-save, verify that PATCH /templates/:id request body has no invalid {{field-xxx}} in tasks[].name and tasks[].description
  • After F5: confirm that data matches what was shown before reload (no UI ↔ server discrepancy)

5.5 API Checks

  • PATCH/PUT /api/templates/:idtasks[].name, tasks[].description, wf_name_template must not contain references to deleted fields
  • GET /api/templates/:id — after saving, response should contain cleaned data

5.6 What Was NOT Tested

  • Locales were not tested (out of scope)
  • Not tested on mobile devices
  • Not tested in production (dev only)
  • Not tested under load (concurrent users)

6. Affected Areas (Dependencies)

Component Where used What to verify
RichEditor (replaceContent added) Task descriptions, workflow comments, kick-off description, public form Ensure replaceContent is not called unnecessarily; regular text editing works as before
InputWithVariables (sync added) Step name, Workflow name Regular text input and variable insertion work without glitches, cursor doesn't jump
TaskDescriptionEditor (sync added) Step description Text input, variable insertion, checklists — no regressions
cleanTemplateReferences mapTemplateRequest (save flow), handleChangeTemplateField Function is idempotent — double call doesn't change the result

7. Refactoring

Minimal refactoring within the task:

  • In handleChangeTemplateField, variable newWorkflow split into updatedWorkflow + newWorkflow (cleanup result) for clarity
  • handleChange in InputWithVariables renamed from direct onChange passthrough to wrapped handleChange/wrappedHandleChange

Additional checks:

  • Regular template editing (without deleting fields) works as before — auto-save, Enable Template, Disable Template

8. Commits

  • 756ed51545972 feat(template): auto-cleanup broken field references on save
  • 2ae1cb0845972 fix(template): sync editor UI after field reference cleanup

9. Release Notes

Template Editor: Fixed stale field references ({{field-xxx}}) remaining visible in step names and descriptions after deleting kickoff or output fields. References are now cleaned up instantly in the UI without requiring a page reload.


Note

Medium Risk
Updates template persistence and editor syncing logic; incorrect cleanup or editor replacement behavior could unintentionally remove user-entered {{...}} tokens or affect undo/history in the Lexical editor.

Overview
Fixes stale {{field}} references in the template editor by auto-removing references to deleted kick-off/task fields from workflow name templates, step names/descriptions, conditions, performers, and field-based due dates.

cleanTemplateReferences is now applied optimistically when kickoff/tasks are changed (both in TemplateEdit and the patchTemplate saga), and mapTemplateRequest also cleans before sending to the API; new unit tests cover the cleanup behavior.

Adds an imperative replaceContent(markdown) API to RichEditor and wires InputWithVariables and TaskDescriptionEditor to call it when their value prop changes, preventing Lexical editors from getting stuck with old content after Redux-driven cleanup.

Reviewed by Cursor Bugbot for commit abbb4d7. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

1. Description (Problem)

When editing a workflow template, after a user deletes a field from the Kick-off Form or a task's Output Fields, references to that field ({{field-xxx}}) continued to appear in:

  • Step names
  • Step descriptions
  • Workflow name template

Where it manifested: Template editing screen (/templates/edit/:id). Especially noticeable when steps were expanded — stale {{field-xxx}} references remained visible until a full page reload (F5). Collapsed steps cleaned up correctly.


2. Context

  • Ticket #45972 — auto-cleanup of invalid field references when saving templates.
  • cleanTemplateReferences was already implemented and called inside mapTemplateRequest to clean data before sending to the backend. However, the cleanup result was not synchronized back to the Redux store or Lexical editors.
  • Two independent sources of the bug:
    1. Redux storehandleChangeTemplateField stored the template without cleanup
    2. Lexical RichEditor — uses defaultValue (one-time initialization), doesn't react to prop updates

3. Solution

Two-level fix:

  1. Optimistic cleanup in Redux — call cleanTemplateReferences immediately when kickoff or tasks change in handleChangeTemplateField, before writing to the store. The user sees results instantly.

  2. Lexical editor synchronization — added replaceContent method to RichEditor that programmatically updates Lexical content. Added lastEmittedValue tracking + useEffect to InputWithVariables and TaskDescriptionEditor, so when value changes externally (from Redux cleanup), the editor updates its DOM.


4. Implementation Details

Changed Files

File What changed
utils/template.ts Added cleanTemplateReferences function to strip invalid {{field-xxx}} references from task names, descriptions, conditions, performers, due dates, wfNameTemplate
utils/__tests__/template.test.ts Tests for cleanTemplateReferences — 6 test cases
TemplateEdit.tsx Import cleanTemplateReferences, call on field === 'kickoff' || field === 'tasks'
RichEditor/types.ts Added replaceContent(markdown: string): void to IRichEditorHandle
RichEditor/RichEditor.tsx replaceContent implementation via applyMarkdownToEditor
InputWithVariables.tsx lastEmittedValue ref + useEffect for external sync + handleChange wrapper
TaskDescriptionEditor.tsx Same sync pattern: lastEmittedValue + useEffect + wrappedHandleChange

Key Decisions

  • cleanTemplateReferences is idempotent — safe to call twice (called in both UI and mapTemplateRequest)
  • lastEmittedValue ref distinguishes external prop changes from user input, preventing cursor jumps during regular editing
  • replaceContent uses applyMarkdownToEditor with 'history-merge' tag to avoid polluting the undo stack

5. What to Test

5.1 Preconditions

  • Dev environment with frontend running
  • Authenticated user with template creation permissions
  • Navigate to /templates/create or /templates/edit/:id

5.2 Positive Scenarios

Scenario 1: Delete kickoff field — collapsed steps

  1. Create a new template
  2. Add a field to the Kick-off Form (e.g., Small Text Field)
  3. In Step 1, use "Insert" to add a reference to this field in Step name
  4. Collapse the step
  5. Delete the field from Kick-off Form
  6. Expected: {{field-xxx}} reference disappears from the step name instantly, no reload needed

Scenario 2: Delete kickoff field — expanded steps

  1. Repeat steps 1-3 from Scenario 1
  2. Keep the step expanded
  3. Delete the field from Kick-off Form
  4. Expected: {{field-xxx}} reference disappears from both the Step name field (inside RichEditor) and Description

Scenario 3: Delete task output field

  1. Add an Output Field to Step 1
  2. In Step 2, insert a reference to this output in Step name and Description
  3. Delete the output field from Step 1
  4. Expected: reference is removed from Step 2

Scenario 4: System variables are preserved

  1. Insert {{workflow-starter}} in Step name or Description
  2. Delete any kickoff field
  3. Expected: {{workflow-starter}} remains intact

Scenario 5: Workflow name cleanup

  1. In Workflow name, add a reference to a kickoff field via Insert
  2. Delete that field
  3. Expected: reference removed; system variables {{date}}, {{template-name}} preserved

Scenario 6: Enable Template after cleanup

  1. Execute Scenario 2
  2. Click "Enable Template"
  3. Expected: template activates without errors, references don't reappear

5.3 Negative Scenarios and Edge Cases

  1. Delete all fields: remove all kickoff fields while all steps reference them → all references should be cleaned, steps should not break
  2. Empty step name after cleanup: if Step name consists only of {{field-xxx}}, after cleanup the name should become Step N (fallback)
  3. Rapid deletion of multiple fields: delete 2-3 fields quickly in succession → all references cleaned correctly, UI doesn't freeze
  4. Conditions: if a deleted field is used in a "Check If" condition rule → the rule should be removed from conditions

5.4 Verification Points

  • UI: visually confirm that {{field-xxx}} does not appear after field deletion
  • DevTools → Network: on auto-save, verify that PATCH /templates/:id request body has no invalid {{field-xxx}} in tasks[].name and tasks[].description
  • After F5: confirm that data matches what was shown before reload (no UI ↔ server discrepancy)

5.5 API Checks

  • PATCH/PUT /api/templates/:idtasks[].name, tasks[].description, wf_name_template must not contain references to deleted fields
  • GET /api/templates/:id — after saving, response should contain cleaned data

5.6 What Was NOT Tested

  • Locales were not tested (out of scope)
  • Not tested on mobile devices
  • Not tested in production (dev only)
  • Not tested under load (concurrent users)

6. Affected Areas (Dependencies)

Component Where used What to verify
RichEditor (replaceContent added) Task descriptions, workflow comments, kick-off description, public form Ensure replaceContent is not called unnecessarily; regular text editing works as before
InputWithVariables (sync added) Step name, Workflow name Regular text input and variable insertion work without glitches, cursor doesn't jump
TaskDescriptionEditor (sync added) Step description Text input, variable insertion, checklists — no regressions
cleanTemplateReferences mapTemplateRequest (save flow), handleChangeTemplateField Function is idempotent — double call doesn't change the result

7. Refactoring

Minimal refactoring within the task:

  • In handleChangeTemplateField, variable newWorkflow split into updatedWorkflow + newWorkflow (cleanup result) for clarity
  • handleChange in InputWithVariables renamed from direct onChange passthrough to wrapped handleChange/wrappedHandleChange

Additional checks:

  • Regular template editing (without deleting fields) works as before — auto-save, Enable Template, Disable Template

8. Commits

  • 756ed51545972 feat(template): auto-cleanup broken field references on save
  • 2ae1cb0845972 fix(template): sync editor UI after field reference cleanup

9. Release Notes

Template Editor: Fixed stale field references ({{field-xxx}}) remaining visible in step names and descriptions after deleting kickoff or output fields. References are now cleaned up instantly in the UI without requiring a page reload.


[!NOTE]
Medium Risk
Updates template persistence and editor syncing logic; incorrect cleanup or editor replacement behavior could unintentionally remove user-entered {{...}} tokens or affect undo/history in the Lexical editor.

Overview
Fixes stale {{field}} references in the template editor by auto-removing references to deleted kick-off/task fields from workflow name templates, step names/descriptions, conditions, performers, and field-based due dates.

cleanTemplateReferences is now applied optimistically when kickoff/tasks are changed (both in TemplateEdit and the patchTemplate saga), and mapTemplateRequest also cleans before sending to the API; new unit tests cover the cleanup behavior.

Adds an imperative replaceContent(markdown) API to RichEditor and wires InputWithVariables and TaskDescriptionEditor to call it when their value prop changes, preventing Lexical editors from getting stuck with old content after Redux-driven cleanup.

Reviewed by Cursor Bugbot for commit abbb4d7. Bugbot is set up for automated code reviews on this repo. Configure here.

Changes since #201 opened

  • Modified cleanTemplateReferences utility function to set sourceId to null and change ruleTarget to 'task started' for tasks with invalid field-based due date references [abbb4d7]
  • Integrated automatic template reference cleaning into patchTemplateSaga saga for task and kickoff field changes [abbb4d7]
  • Modified cleanTemplateReferences function to preserve condition rules with falsy rule.field values (including empty strings), and rules where rule.fieldType is 'task' or 'kickoff', in addition to rules with valid field references [85f7aa2]
  • Modified cleanTemplateReferences utility to preserve leading and trailing whitespace when removing invalid template variable references and to return empty strings for task names instead of defaulting to 'Step N' format [ba29330]
  • Updated unit tests in template.test.ts to validate that cleanTemplateReferences preserves trailing spaces and allows empty task names [ba29330]
  • Refactored InputWithVariables component to track and compare raw prop values instead of escaped values for change detection and editor content updates [42f421d]

@EBirkenfeld EBirkenfeld self-assigned this Apr 17, 2026
@EBirkenfeld EBirkenfeld added Backend API changes request Frontend Web client changes request labels Apr 17, 2026
Comment thread backend/src/processes/serializers/templates/task.py Outdated
Comment thread backend/src/processes/serializers/templates/task.py
Comment thread backend/src/processes/serializers/templates/template.py Outdated
Comment thread frontend/src/public/utils/template.ts Outdated
Comment thread frontend/src/public/utils/__tests__/template.test.ts
Comment thread frontend/src/public/components/TemplateEdit/TemplateEdit.tsx
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit ba29330. Configure here.

...template,
wfNameTemplate: removeInvalidReferences(template.wfNameTemplate, validKickoffApiNames, WF_NAME_SYSTEM_VARS),
tasks: cleanedTasks,
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cleanup reorders tasks by number

Medium Severity

cleanTemplateReferences sorts tasks by number and returns them in that order. Because cleanup is now invoked on every tasks/kickoff change in handleChangeTemplateField and in the saga's merge path, any operation that updates tasks while the array is not already strictly sorted by number (e.g., during reorder/move flows before numbers settle) will silently reorder the array stored in Redux, potentially fighting with in-progress reorder logic.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit ba29330. Configure here.

ruleTarget: 'task started',
};
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

DueDate fallback may produce invalid preposition combination

Low Severity

When a task's rawDueDate references an invalid field, the cleanup switches ruleTarget to 'task started' but preserves rulePreposition. If the original preposition was 'before' (only valid with ruleTarget: 'field'), the result is the invalid combination 'before task started', which has no corresponding API rule mapping.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit ba29330. Configure here.

@EBirkenfeld EBirkenfeld merged commit 62bd0d3 into master May 5, 2026
1 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Backend API changes request Frontend Web client changes request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants