Skip to content

feat: form flow ui editor - #883

Open
mbritense wants to merge 16 commits into
next-minorfrom
story/form-flow-editor
Open

feat: form flow ui editor#883
mbritense wants to merge 16 commits into
next-minorfrom
story/form-flow-editor

Conversation

@mbritense

Copy link
Copy Markdown
Contributor

@mbritense
mbritense requested review from a team as code owners August 6, 2026 06:53
@mbritense mbritense added the test env Create a remote test environment for this PR in the Ritense product development cluster label Aug 6, 2026
@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Summary by CodeRabbit

  • New Features
    • Added a beta visual form-flow editor for steps, transitions, forms, actions, and conditions.
    • Added a JSON editor with schema validation and tab navigation.
    • Added expression suggestions, contextual property guidance, and secured form-flow metadata access.
  • Bug Fixes
    • Added unsaved-change warnings and improved invalid-definition save handling.
    • Improved validation, step cleanup, and missing start-step handling.
    • Updated process-link editing to use definition keys.
  • Documentation
    • Added English and Dutch editor translations and usage guidance.

Walkthrough

The pull request adds a cached backend form-flow registry with an administrator-only management endpoint. It adds a tabbed frontend editor with visual and JSON modes, registry-driven controls, reactive validation, expression help, custom components, routing, and pending-change handling. Frontend models are consolidated through shared-package re-exports. Process-link updates now use form-flow definition keys. Backend, integration, security, Playwright, and documentation changes cover the new behavior.

Merge Risk: 🟡 Moderate · up to dc599

The PR changes process-definition handling to use multiple resolvers, but the production configuration still provides the old single-resolver dependency, which can prevent the backend from compiling or starting. Merge should wait until that wiring is corrected; the broader resolver fanout also needs explicit owner awareness.

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch story/form-flow-editor

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🧹 Nitpick comments (1)
backend/form-flow/src/test/kotlin/com/ritense/formflow/FormFlowProcessLinkActivityHandlerIntTest.kt (1)

211-221: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert the alwaysPresent direction as well.

The comment states that the test guards against drift between the handler and the registry. containsAll only proves that every populated property is documented. It does not detect the reverse drift: if the handler stops populating a property that the registry declares with alwaysPresent = true, this test still passes.

Add an assertion that every alwaysPresent property for the context is present on the instance. The same gap applies to the start-event test at Lines 253-263.

♻️ Proposed additional assertion
-        val declaredUserTaskProperties = formFlowRegistryService.getRegistry()
-            .additionalProperties
-            .filter { it.context == "userTask" }
-            .map { it.name }
+        val declaredUserTask = formFlowRegistryService.getRegistry()
+            .additionalProperties
+            .filter { it.context == "userTask" }
+        val declaredUserTaskProperties = declaredUserTask.map { it.name }
+        val alwaysPresentProperties = declaredUserTask.filter { it.alwaysPresent }.map { it.name }
         val instanceProperties = formFlowInstanceRepository.findAll().single().getAdditionalProperties().keys
 
         assertThat(instanceProperties).isNotEmpty
         assertThat(declaredUserTaskProperties).containsAll(instanceProperties)
+        assertThat(instanceProperties).containsAll(alwaysPresentProperties)

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 31373058-2443-40d0-afbf-e28ed5b1d2e4

📥 Commits

Reviewing files that changed from the base of the PR and between 533e1f0 and 715277f.

⛔ Files ignored due to path filters (1)
  • frontend/projects/valtimo/shared/src/lib/generated/generated-backend-types.ts is excluded by !**/generated/**
📒 Files selected for processing (75)
  • backend/apps/dev/build.gradle
  • backend/form-flow/src/main/kotlin/com/ritense/formflow/AbstractFormFlowLinkTaskProvider.kt
  • backend/form-flow/src/main/kotlin/com/ritense/formflow/FormFlowProcessLinkActivityHandler.kt
  • backend/form-flow/src/main/kotlin/com/ritense/formflow/autoconfigure/FormFlowAutoConfiguration.kt
  • backend/form-flow/src/main/kotlin/com/ritense/formflow/security/ValtimoFormFlowHttpSecurityConfigurer.kt
  • backend/form-flow/src/main/kotlin/com/ritense/formflow/service/FormFlowRegistryService.kt
  • backend/form-flow/src/main/kotlin/com/ritense/formflow/web/rest/FormFlowRegistryResource.kt
  • backend/form-flow/src/main/kotlin/com/ritense/formflow/web/rest/dto/FormFlowRegistryDto.kt
  • backend/form-flow/src/test/kotlin/com/ritense/formflow/FormFlowProcessLinkActivityHandlerIntTest.kt
  • backend/form-flow/src/test/kotlin/com/ritense/formflow/service/FormFlowRegistryServiceTest.kt
  • backend/form-flow/src/test/kotlin/com/ritense/formflow/web/rest/FormFlowRegistryResourceIntTest.kt
  • backend/form-flow/src/test/kotlin/com/ritense/formflow/web/rest/FormFlowRegistrySecurityIntTest.kt
  • e2e/tests/case-details-management-form-flows/case-details-management-form-flows.spec.ts
  • e2e/tests/case-details-management-form-flows/page.ts
  • frontend/projects/valtimo/admin-settings/src/lib/models/accent-colors.model.ts
  • frontend/projects/valtimo/admin-settings/src/lib/models/feature-toggle.model.ts
  • frontend/projects/valtimo/building-block-management/src/lib/building-block-management-routing.ts
  • frontend/projects/valtimo/case-management/src/lib/case-management-routing.module.ts
  • frontend/projects/valtimo/case-management/src/lib/models/startable-item.model.ts
  • frontend/projects/valtimo/case/src/lib/models/case-inspection.models.ts
  • frontend/projects/valtimo/components/src/lib/models/choice-field.model.ts
  • frontend/projects/valtimo/document/src/lib/models/document.model.ts
  • frontend/projects/valtimo/form-flow-management/src/lib/components/editor/form-flow-editor.component.html
  • frontend/projects/valtimo/form-flow-management/src/lib/components/editor/form-flow-editor.component.scss
  • frontend/projects/valtimo/form-flow-management/src/lib/components/editor/form-flow-editor.component.ts
  • frontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs/form-flow-json-editor-tab/form-flow-json-editor-tab.component.html
  • frontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs/form-flow-json-editor-tab/form-flow-json-editor-tab.component.ts
  • frontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs/form-flow-ui-editor-tab/form-flow-context-properties/form-flow-context-properties.component.html
  • frontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs/form-flow-ui-editor-tab/form-flow-context-properties/form-flow-context-properties.component.scss
  • frontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs/form-flow-ui-editor-tab/form-flow-context-properties/form-flow-context-properties.component.ts
  • frontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs/form-flow-ui-editor-tab/form-flow-expression-help-modal/form-flow-expression-help-modal.component.html
  • frontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs/form-flow-ui-editor-tab/form-flow-expression-help-modal/form-flow-expression-help-modal.component.scss
  • frontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs/form-flow-ui-editor-tab/form-flow-expression-help-modal/form-flow-expression-help-modal.component.ts
  • frontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs/form-flow-ui-editor-tab/form-flow-expression-list/form-flow-expression-list.component.html
  • frontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs/form-flow-ui-editor-tab/form-flow-expression-list/form-flow-expression-list.component.scss
  • frontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs/form-flow-ui-editor-tab/form-flow-expression-list/form-flow-expression-list.component.ts
  • frontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs/form-flow-ui-editor-tab/form-flow-step-detail/form-flow-step-detail.component.html
  • frontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs/form-flow-ui-editor-tab/form-flow-step-detail/form-flow-step-detail.component.scss
  • frontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs/form-flow-ui-editor-tab/form-flow-step-detail/form-flow-step-detail.component.ts
  • frontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs/form-flow-ui-editor-tab/form-flow-step-list/form-flow-step-list.component.html
  • frontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs/form-flow-ui-editor-tab/form-flow-step-list/form-flow-step-list.component.scss
  • frontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs/form-flow-ui-editor-tab/form-flow-step-list/form-flow-step-list.component.ts
  • frontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs/form-flow-ui-editor-tab/form-flow-transition-list/form-flow-transition-list.component.html
  • frontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs/form-flow-ui-editor-tab/form-flow-transition-list/form-flow-transition-list.component.scss
  • frontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs/form-flow-ui-editor-tab/form-flow-transition-list/form-flow-transition-list.component.ts
  • frontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs/form-flow-ui-editor-tab/form-flow-ui-editor-tab.component.html
  • frontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs/form-flow-ui-editor-tab/form-flow-ui-editor-tab.component.scss
  • frontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs/form-flow-ui-editor-tab/form-flow-ui-editor-tab.component.ts
  • frontend/projects/valtimo/form-flow-management/src/lib/constants/form-flow-editor.test-ids.ts
  • frontend/projects/valtimo/form-flow-management/src/lib/constants/index.ts
  • frontend/projects/valtimo/form-flow-management/src/lib/constants/injection-tokens.ts
  • frontend/projects/valtimo/form-flow-management/src/lib/form-flow-management.module.ts
  • frontend/projects/valtimo/form-flow-management/src/lib/models/form-flow-custom-component.model.ts
  • frontend/projects/valtimo/form-flow-management/src/lib/models/form-flow-editor.model.ts
  • frontend/projects/valtimo/form-flow-management/src/lib/models/form-flow.model.ts
  • frontend/projects/valtimo/form-flow-management/src/lib/models/index.ts
  • frontend/projects/valtimo/form-flow-management/src/lib/services/form-flow-component.service.ts
  • frontend/projects/valtimo/form-flow-management/src/lib/services/form-flow-download.service.ts
  • frontend/projects/valtimo/form-flow-management/src/lib/services/form-flow-editor-form.service.ts
  • frontend/projects/valtimo/form-flow-management/src/lib/services/form-flow.service.ts
  • frontend/projects/valtimo/form-flow-management/src/lib/services/index.ts
  • frontend/projects/valtimo/form-flow-management/src/lib/utils/index.ts
  • frontend/projects/valtimo/form-flow-management/src/lib/utils/translate.utils.ts
  • frontend/projects/valtimo/form-flow-management/src/public-api.ts
  • frontend/projects/valtimo/iko/src/lib/models/iko-management-list.model.ts
  • frontend/projects/valtimo/process-link/src/lib/components/select-form-flow/select-form-flow.component.ts
  • frontend/projects/valtimo/process-link/src/lib/constants/injection-tokens.ts
  • frontend/projects/valtimo/process-link/src/lib/models/form-flow.model.ts
  • frontend/projects/valtimo/process-link/src/lib/models/process-link.model.ts
  • frontend/projects/valtimo/process-link/src/lib/services/form-flow-component.service.ts
  • frontend/projects/valtimo/process-management/src/lib/models/process-management.model.ts
  • frontend/projects/valtimo/shared/assets/core/en.json
  • frontend/projects/valtimo/shared/assets/core/nl.json
  • frontend/projects/valtimo/task/src/lib/models/task-intermediate-save.model.ts
  • frontend/projects/valtimo/zgw/src/lib/case-inspection/case-inspection.models.ts

Comment on lines +106 to +108
val additionalProperties = mutableMapOf<String, Any>(PROCESS_DEFINITION_KEY to processDefinition.key)
documentId?.let { additionalProperties[DOCUMENT_ID] = it }
documentDefinitionName?.let { additionalProperties[DOCUMENT_DEFINITION_NAME] = it }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check the imports of the constants used at lines 106-108.
fd -t f 'FormFlowProcessLinkActivityHandler.kt' backend | while IFS= read -r file; do
  echo "== $file"
  sed -n '1,45p' "$file"
done

Repository: valtimo-platform/valtimo

Length of output: 2288


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="backend/form-flow/src/main/kotlin/com/ritense/formflow/FormFlowProcessLinkActivityHandler.kt"

echo "== file outline =="
ast-grep outline "$file" --match FormFlowProcessLinkActivityHandler --view expanded || true

echo "== relevant usages and class signature =="
sed -n '1,150p' "$file"

echo "== definitions/usages of FORM_FLOW_TASK_TYPE_KEY, PROCESS_DEFINITION_KEY, DOCUMENT_ID, DOCUMENT_DEFINITION_NAME =="
rg -n "FORM_FLOW_TASK_TYPE_KEY|PROCESS_DEFINITION_KEY|DOCUMENT_ID|DOCUMENT_DEFINITION_NAME" backend/form-flow -S

Repository: valtimo-platform/valtimo

Length of output: 9804


Use the qualified constants from the superclass.

AbstractFormFlowLinkTaskProvider defines FORM_FLOW_TASK_TYPE_KEY, PROCESS_DEFINITION_KEY, DOCUMENT_ID, and DOCUMENT_DEFINITION_NAME, but Kotlin does not import superclass companion members; call them through AbstractFormFlowLinkTaskProvider.* or add local imports/aliases.

Comment on lines +198 to +200
// Assert — the transition is part of the persisted definition shown in the JSON editor
await formFlowsPage.openJsonEditorTab();
await expect(formFlowsPage.monacoEditor).toContainText('step-2');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate and inspect the referenced test file and nearby context.
if [ -f "e2e/tests/case-details-management-form-flows/case-details-management-form-flows.spec.ts" ]; then
  echo "== file exists =="
  wc -l "e2e/tests/case-details-management-form-flows/case-details-management-form-flows.spec.ts"
  echo
  echo "== header =="
  sed -n '1,20p' "e2e/tests/case-details-management-form-flows/case-details-management-form-flows.spec.ts"
  echo
  echo "== lines 140-230 =="
  sed -n '140,230p' "e2e/tests/case-details-management-form-flows/case-details-management-form-flows.spec.ts"
else
  echo "Referenced file not found"
  echo "Candidate files:"
  fd -i 'case-details-management-form-flows\.spec\.ts' .
fi

# Search for the page helpers and editor assertions to understand whether containing 'step-2' is equivalent to validating the transition config.
echo
echo "== formFlowsPage / monacoEditor occurrences =="
rg -n "formFlowsPage|monacoEditor|openJsonEditorTab|nextSteps|toContainText|step-2|step2|step-1|step1" e2e/tests/case-details-management-form-flows/case-details-management-form-flows.spec.ts e2e/lib/** 2>/dev/null || true

Repository: valtimo-platform/valtimo

Length of output: 12810


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find and inspect page helpers that provide the JSON editor assertions and helpers.
echo "== page.ts outline =="
ast-grep outline e2e/tests/case-details-management-form-flows/page.ts --view expanded 2>/dev/null || true
echo
echo "== page.ts relevant occurrences =="
rg -n "monacoEditor|openJsonEditorTab|toContainText|get.*monaco|steps|nextSteps|editFormFlowJson|pasteRawTextInEditor" e2e/tests/case-details-management-form-flows/page.ts e2e/lib e2e/tests/case-details-management-form-flows 2>/dev/null || true

# Search for the test data model to understand the persisted JSON shape around nextSteps.
echo
echo "== test data model occurrences =="
rg -n "createFormFlowTestData|validFormFlowJson|nextSteps|formFlow" e2e/tests/case-details-management-form-flows/*.ts 2>/dev/null || true

Repository: valtimo-platform/valtimo

Length of output: 15810


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== spec helper model =="
sed -n '1,80p' e2e/tests/case-details-management-form-flows/case-details-management-form-flows.ts

echo
echo "== page helpers =="
sed -n '128,148p' e2e/tests/case-details-management-form-flows/page.ts
sed -n '220,240p' e2e/tests/case-details-management-form-flows/page.ts

echo
echo "== read-only semantic model: JSON shapes that satisfy current assertion =="
python3 - <<'PY'
import json
cases = [
    {
        "name": "transition present",
        "json": {
            "steps": [{"key": "step1", "name": "step1", "nextSteps": ["step-2"]}, {"key": "step-2"}],
        },
    },
    {
        "name": "no transition; only new step",
        "json": {
            "steps": [{"key": "step1", "name": "step1", "nextSteps": []}, {"key": "step-2"}],
        },
    },
    {
        "name": "transition present but wrong source step",
        "json": {
            "steps": [{"key": "step1", "name": "step1", "nextSteps": []}, {"key": "step-2", "nextSteps": ["step1"]}],
        },
    },
]
for case_ in cases:
    text = json.dumps(case_["json"])
    print(case_["name"])
    print("  contains 'step-2':", "step-2" in text)
    print("  contains 'step1.nextSteps' text:", "step1.nextSteps" in text)
    print("  nextSteps list contains step-2:", any("step-2" in s.get("nextSteps", []) for s in case_["json"].get("steps", [])))
    print()
PY

Repository: valtimo-platform/valtimo

Length of output: 2799


Assert the persisted transition.

toContainText('step-2') can pass when the new step is present without being linked from step1. Assert that step1.nextSteps contains step-2.

Comment on lines +143 to +148
// A freshly loaded definition is the clean baseline: the next editor emission recaptures it,
// saving without edits is possible from either tab, and the unsaved-changes flag is cleared.
this._updatedModelValue$.next(this.serializeDefinition(formFlowDefinition));
this._pendingBaseline = null;
this.pendingChanges = false;
this.valid$.next(true);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

fd -a 'form-flow-*-editor-tab.component.ts' frontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs \
  -x rg -n -C 4 'onValueChange|valueChange|modelChange|EventEmitter|emit\(' {}

rg -n -C 6 'onValueChange|_pendingBaseline|serializeDefinition' \
  frontend/projects/valtimo/form-flow-management/src/lib/components/editor/form-flow-editor.component.ts

Repository: valtimo-platform/valtimo

Length of output: 3560


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== candidate tabs files =="
fd -a 'form-flow-*-editor-tab.component.ts' frontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs || true

echo "== editor component outline =="
ast-grep outline frontend/projects/valtimo/form-flow-management/src/lib/components/editor/form-flow-editor.component.ts || true

echo "== relevant editor component =="
sed -n '120,215p' frontend/projects/valtimo/form-flow-management/src/lib/components/editor/form-flow-editor.component.ts
sed -n '280,420p' frontend/projects/valtimo/form-flow-management/src/lib/components/editor/form-flow-editor.component.ts

echo "== tab files contents =="
for f in $(fd 'form-flow-.*editor-tab.component.ts' frontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs); do
  echo "--- $f ---"
  sed -n '1,220p' "$f"
done

Repository: valtimo-platform/valtimo

Length of output: 19124


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== form-flow-ui-editor-tab remainder =="
sed -n '150,360p' frontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs/form-flow-ui-editor-tab.component.ts

echo "== all valueChangeEvent usages =="
rg -n -C 5 'valueChangeEvent|onValueChange\(value' frontend/projects/valtimo/form-flow-management/src

echo "== template bindings for editors =="
fd -a 'form-flow.*editor.*component.html|.*form-flow.*json.*tab.*html|.*form-flow.*ui.*tab.*html' frontend/projects/valtimo/form-flow-management/src \
  -x sh -c 'echo "--- $1 ---"; sed -n "1,220p" "$1"' sh {}

Repository: valtimo-platform/valtimo

Length of output: 360


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== files in tabs =="
fd . frontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs -t f

echo "== form-flow-ui-editor-tab component =="
if [ -f frontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs/form-flow-ui-editor-tab.component.ts ]; then
  sed -n '150,420p' frontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs/form-flow-ui-editor-tab.component.ts
fi

echo "== all valueChangeEvent / onValueChange usages =="
rg -n -C 5 'valueChangeEvent|onValueChange\(value|FormFlowUiEditorTabComponent|FormFlowJsonEditorTabComponent|EditorModel' frontend/projects/valtimo/form-flow-management/src

echo "== editor templates =="
fd -a '-i' 'form-flow.*editor.*component\.html|.*json.*tab.*component\.html|.*ui.*tab.*component\.html|.*editor.*html' frontend/projects/valtimo/form-flow-management/src -t f -x sh -c 'echo "--- $1 ---"; sed -n "1,240p" "$1"' sh {}

Repository: valtimo-platform/valtimo

Length of output: 40921


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== ui editor parse/build and emitState area =="
sed -n '160,230p' frontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs/form-flow-ui-editor-tab.component.ts
sed -n '320,420p' frontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs/form-flow-ui-editor-tab.component.ts

echo "== locate validator/component packages =="
rg -n -C 4 'valueChangeEvent|onValueChange' frontend -g '*.ts' -g '*.html' | head -200

echo "== locate validation/monitor usage =="
rg -n -C 4 'validEvent|pendingChanges|PendingChangesComponent|valueChangeEvent' frontend/projects/valtimo/form-flow-management/src -g '*.ts' -g '*.html' | sed -n '1,240p'

Repository: valtimo-platform/valtimo

Length of output: 367


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== resolve actual ui editor path and emitState area =="
actual_path="$(realpath frontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs/form-flow-ui-editor-tab.component.ts 2>/dev/null || true)"
echo "actual_path=$actual_path"
if [ -n "${actual_path}" ]; then
  sed -n '160,230p' "$actual_path"
  sed -n '320,420p' "$actual_path"
fi

echo "== all validEvent subscriptions and pendingChanges in editor tree =="
rg -n -C 4 'validEvent|pendingChanges|PendingChangesComponent|onValueChange|valueChangeEvent' frontend/projects/valtimo/form-flow-management/src/lib/components/editor -g '*.ts' -g '*.html'

echo "== deterministic behavior for new baseline when valueChanges only emit after edit =="
python3 - <<'PY'
baseline = None
loading_model = 'loading'
definition_json = '{"steps": []}'
for value in ["loading initial from UI", definition_json]:
    normalized = ''.join(value.split())
    if baseline is None:
        baseline = normalized
        changed = False
    else:
        changed = normalized != baseline
    print({"value": repr(value[:30]), "normalized_first_30": repr(normalized[:30]), "baseline_was_null": baseline == normalized, "changed": changed})
print("final_baseline_matches_definition=", ''.join(definition_json.split()) == baseline)
PY

Repository: valtimo-platform/valtimo

Length of output: 21617


🏁 Script executed:

#!/bin/bash
set -euo pipefail

target="$(cd frontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs/form-flow-ui-editor-tab && pwd)/component.ts"
echo "== ui editor component lines 76-180 =="
sed -n '76,180p' "$target"

echo "== ui editor emitState callers and valueChangeEvent wiring =="
rg -n -C 6 'emitState|valueChangeEvent|validEvent|buildForm|parseModel|OnChanges' frontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs/form-flow-ui-editor-tab/component.ts

echo "== ui editor template event output binding =="
sed -n '1,80p' frontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs/form-flow-ui-editor-tab/component.html

Repository: valtimo-platform/valtimo

Length of output: 378


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== UI editor actual paths =="
find frontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs/form-flow-ui-editor-tab -maxdepth 2 -type f -print | sort

echo "== UI editor component lines 76-180 (using find name pattern) =="
find frontend/projects/valtimo/form-flow-management/src/lib/component s/editor/tabs/form-flow-ui-ed iter -name 'component.ts' -type f | while read -r f; do
  echo "--- $f ---"
  sed -n '76,180p' "$f"
  sed -n '320,420p' "$f"
done

echo "== emitState and valueChangeEvent references in UI editor component =="
ui="$(find frontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs/form-flow-ui-editor-tab -name 'component.ts' -type f | head -n 1)"
if [ -n "${ui}" ]; then
  rg -n -C 6 'emitState|valueChangeEvent|validEvent|buildForm|parseModel|constructor|ngOnInit|ngAfterViewInit|OnChanges|ngOnChanges' "$ui"
fi

Repository: valtimo-platform/valtimo

Length of output: 3906


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== UI editor component 1-170 =="
sed -n '1,170p' frontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs/form-flow-ui-editor-tab.component.ts

echo "== UI editor component 400-420 and template event binding =="
sed -n '400,420p' frontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs/form-flow-ui-editor-tab.component.ts
sed -n '1,80p' frontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs/form-flow-ui-editor-tab.component.html

echo "== package declarations around valtimo-editor/monaco dependency =="
fd -a 'package.json' frontend | while read -r f; do
  if rg -q '"monaco-editor"|`@valtimo/components`|EditorModule|valtimo-editor' "$f"; then
    echo "--- $f ---"
    rg -n -C 4 '"monaco-editor"|`@valtimo/components`|EditorModule|valtimo-editor' "$f"
  fi
done

Repository: valtimo-platform/valtimo

Length of output: 352


🏁 Script executed:

#!/bin/bash
set -u

echo "== pwd and ls candidate =="
pwd
ls -la frontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs/form-flow-ui-editor-tab 2>/dev/null || true
echo
echo "== top-level form flow management files =="
find frontend/projects/valtimo/form-flow-management/src/lib -name 'form-flow-ui-editor-tab.component.ts' -o -name 'form-flow-json-editor-tab.component.ts' -o -name 'form-flow-editor.component.ts' -o -name 'form-flow-editor.component.html' -print 2>/dev/null | sort

candidate="$(find frontend/projects/valtimo/form-flow-management/src/lib -name 'form-flow-ui-editor-tab.component.ts' -print 2>/dev/null | head -n 1)"
if [ -n "${candidate}" ]; then
  echo "== UI editor component first 170 lines =="
  sed -n '1,170p' "$candidate"
  echo "== UI emitter lines =="
  sed -n '400,420p' "$candidate"
  echo "== UI template event bindings =="
  template="$(dirname "$candidate")/form-flow-ui-editor-tab.component.html"
  if [ -f "$template" ]; then sed -n '1,80p' "$template"; fi
fi

Repository: valtimo-platform/valtimo

Length of output: 10887


Set _pendingBaseline when loading a definition.

onValueChange(...) uses _pendingBaseline === null as the initial baseline, so when the editor emits a different value first, that value becomes the baseline. Set _pendingBaseline = this.normalizeJson(this.serializeDefinition(formFlowDefinition)) in the load tap when pendingChanges is cleared.

Comment on lines +206 to +214
public setActiveTab(tab: FormFlowEditorTab): void {
if (this.$activeTab() === tab) return;

combineLatest([this._params$, this._context$])
.pipe(take(1))
.subscribe(([params, context]) => {
this.router.navigate(this.editorRouteSegments(params, context, tab));
});
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 8 'FormFlowEditorComponent|json-editor|form-flows' \
  frontend/projects/valtimo/building-block-management/src/lib/building-block-management-routing.ts \
  frontend/projects/valtimo/case-management/src/lib/case-management-routing.module.ts

rg -n -C 6 '\$activeTab|setActiveTab|restoreActiveTabFromUrl' \
  frontend/projects/valtimo/form-flow-management/src/lib/components/editor/form-flow-editor.component.ts

Repository: valtimo-platform/valtimo

Length of output: 14494


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the surrounding signal/template routing code and any router/event subscription that may update $activeTab.
ast-grep outline frontend/projects/valtimo/form-flow-management/src/lib/components/editor/form-flow-editor.component.ts --view expanded
sed -n '73,240p' frontend/projects/valtimo/form-flow-management/src/lib/components/editor/form-flow-editor.component.ts
sed -n '240,380p' frontend/projects/valtimo/form-flow-management/src/lib/components/editor/form-flow-editor.component.ts

printf '\n--- search route-related updates ---\n'
rg -n -C 4 'route\.events|NavigationEnd|navigationEnd|routerEvent|activeTab|JSON_EDITOR|EDITOR' \
  frontend/projects/valtimo/form-flow-management/src/lib/components/editor/form-flow-editor.component.ts \
  frontend/projects/valtimo/form-flow-management/src

printf '\n--- FormFlowEditorTab definition ---\n'
rg -n -C 5 'enum FormFlowEditorTab|FormFlowEditorTab' frontend/projects/valtimo/form-flow-management/src/lib/components/editor

Repository: valtimo-platform/valtimo

Length of output: 50380


🌐 Web query:

Angular Router reusable routes reuse component when params config option false

💡 Result:

In Angular, the router manages component instances based on the RouteReuseStrategy [1]. By default, the router reuses the existing component instance if the route configuration (routeConfig) remains the same [2][3]. There is no native configuration option (like a boolean flag) on the route itself to disable this reuse [4]. To force a component to be destroyed and recreated (or "reload") instead of reused, you must implement a custom RouteReuseStrategy [1][5]. Specifically, if you want to force a reload when navigating to the same URL: 1. You must set the router's onSameUrlNavigation option to 'reload' [6][7]. 2. You must provide a custom RouteReuseStrategy where the shouldReuseRoute method returns false when the component should be reloaded [6][7]. The shouldReuseRoute method is defined as [2][3]: typescript shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean { return future.routeConfig === curr.routeConfig; } To prevent reuse, your custom strategy should override this method to return false under your specific conditions [6][8]. Note that onSameUrlNavigation: 'reload' alone does not recreate the component; it only forces the router to re-run the navigation pipeline (guards, resolvers) for the current URL [7][5]. The RouteReuseStrategy is the definitive mechanism that controls whether the component instance is preserved or destroyed [1][5]. It is generally recommended by the Angular team to design components to be reactive to parameter changes using ActivatedRoute observables rather than forcing component destruction, as this is more performant and aligns with Angular's intended architecture [9].

Citations:


Keep $activeTab in sync with the current route.

setActiveTab navigates but does not update $activeTab. If Angular reuses FormFlowEditorComponent when switching between the editor and json-editor, the visible tab stays on the old value because restoreActiveTabFromUrl runs only during construction. Restore the tab from the URL snapshot in ngOnInit or subscribe to router.events/route.url and set $activeTab on successful navigation.

Comment on lines 258 to +286
public onDelete(): void {
this.loading$.next(true);
this.pendingChanges = false;

combineLatest([this._params$, this._context$])
.pipe(
take(1),
switchMap(([params, context]) => {
if (context === 'buildingBlock') {
return this.formFlowService.deleteBuildingBlockFormFlowDefinition(
return this.formFlowService
.deleteBuildingBlockFormFlowDefinition(
params.caseDefinitionKey,
params.caseDefinitionVersionTag,
params.formFlowDefinitionKey
)
.pipe(map(() => ({params, context})));
}

return this.formFlowService
.deleteFormFlowDefinition(
params.caseDefinitionKey,
params.caseDefinitionVersionTag,
params.formFlowDefinitionKey
);
}

return this.formFlowService.deleteFormFlowDefinition(
params.caseDefinitionKey,
params.caseDefinitionVersionTag,
params.formFlowDefinitionKey
);
)
.pipe(map(() => ({params, context})));
})
)
.subscribe(() => {
this.router.navigate(['../'], {relativeTo: this.route});
.subscribe(({params, context}) => {
this.router.navigate(this.overviewRouteSegments(params, context));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Keep dirty-state protection when deletion fails.

Line 260 clears pendingChanges before the delete request succeeds. The observable has no error handling or finalize block. If deletion fails, loading remains enabled and navigation no longer warns about unsaved edits.

Clear pendingChanges only in the success handler. Reset loading$ on both success and error.

Proposed fix
   public onDelete(): void {
     this.loading$.next(true);
-    this.pendingChanges = false;

     combineLatest([this._params$, this._context$])
       .pipe(
         take(1),
         switchMap(([params, context]) => {
           // ...
-        })
+        }),
+        finalize(() => this.loading$.next(false))
       )
       .subscribe(({params, context}) => {
+        this.pendingChanges = false;
         this.router.navigate(this.overviewRouteSegments(params, context));
       });
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public onDelete(): void {
this.loading$.next(true);
this.pendingChanges = false;
combineLatest([this._params$, this._context$])
.pipe(
take(1),
switchMap(([params, context]) => {
if (context === 'buildingBlock') {
return this.formFlowService.deleteBuildingBlockFormFlowDefinition(
return this.formFlowService
.deleteBuildingBlockFormFlowDefinition(
params.caseDefinitionKey,
params.caseDefinitionVersionTag,
params.formFlowDefinitionKey
)
.pipe(map(() => ({params, context})));
}
return this.formFlowService
.deleteFormFlowDefinition(
params.caseDefinitionKey,
params.caseDefinitionVersionTag,
params.formFlowDefinitionKey
);
}
return this.formFlowService.deleteFormFlowDefinition(
params.caseDefinitionKey,
params.caseDefinitionVersionTag,
params.formFlowDefinitionKey
);
)
.pipe(map(() => ({params, context})));
})
)
.subscribe(() => {
this.router.navigate(['../'], {relativeTo: this.route});
.subscribe(({params, context}) => {
this.router.navigate(this.overviewRouteSegments(params, context));
public onDelete(): void {
this.loading$.next(true);
combineLatest([this._params$, this._context$])
.pipe(
take(1),
switchMap(([params, context]) => {
if (context === 'buildingBlock') {
return this.formFlowService
.deleteBuildingBlockFormFlowDefinition(
params.caseDefinitionKey,
params.caseDefinitionVersionTag,
params.formFlowDefinitionKey
)
.pipe(map(() => ({params, context})));
}
return this.formFlowService
.deleteFormFlowDefinition(
params.caseDefinitionKey,
params.caseDefinitionVersionTag,
params.formFlowDefinitionKey
)
.pipe(map(() => ({params, context})));
}),
finalize(() => this.loading$.next(false))
)
.subscribe(({params, context}) => {
this.pendingChanges = false;
this.router.navigate(this.overviewRouteSegments(params, context));

Comment on lines +73 to +183
<input
cdsText
formControlName="key"
[invalid]="isControlInvalid('key') || duplicateKey"
[attr.data-test-id]="testIds.stepKeyInput"
placeholder="{{ 'formFlow.uiEditor.keyPlaceholder' | translate }}"
/>
</cds-label>

<cds-label>
<span class="step-detail__label">
{{ 'formFlow.uiEditor.title' | translate }}

<v-tooltip-icon
[tooltip]="'formFlow.uiEditor.fieldTooltips.title' | translate"
></v-tooltip-icon>
</span>

<input
cdsText
formControlName="title"
[attr.data-test-id]="testIds.stepTitleInput"
placeholder="{{ 'formFlow.uiEditor.titlePlaceholder' | translate }}"
/>
</cds-label>

<div [attr.data-test-id]="testIds.stepTypeDropdown">
<cds-dropdown formControlName="typeName" itemValueKey="id" [label]="stepTypeLabelTemplate">
<cds-dropdown-list [items]="stepTypeItems"></cds-dropdown-list>
</cds-dropdown>

<ng-template #stepTypeLabelTemplate>
<span class="step-detail__label">
{{ 'formFlow.uiEditor.stepType' | translate }}

<v-tooltip-icon [tooltip]="getTypeTooltip()"></v-tooltip-icon>
</span>
</ng-template>
</div>

<ng-container formGroupName="properties">
@for (propertyName of propertyNames; track propertyName) {
@if (isFormDefinitionProperty(propertyName)) {
<div [attr.data-test-id]="testIds.stepPropertyDropdown">
<cds-dropdown
formControlName="definition"
itemValueKey="id"
[invalid]="
propertiesGroup.get('definition')?.invalid &&
propertiesGroup.get('definition')?.touched
"
[label]="formDefinitionLabelTemplate"
[placeholder]="'formFlow.uiEditor.selectForm' | translate"
>
<cds-dropdown-list [items]="getFormDefinitionItems()"></cds-dropdown-list>
</cds-dropdown>

<ng-template #formDefinitionLabelTemplate>
<span class="step-detail__label">
{{ getPropertyLabel(propertyName) }}

<v-tooltip-icon
[tooltip]="'formFlow.uiEditor.fieldTooltips.definition' | translate"
></v-tooltip-icon>
</span>
</ng-template>
</div>
} @else if (isCustomComponentProperty(propertyName)) {
<div [attr.data-test-id]="testIds.stepPropertyDropdown">
<cds-dropdown
formControlName="componentId"
itemValueKey="id"
[invalid]="
propertiesGroup.get('componentId')?.invalid &&
propertiesGroup.get('componentId')?.touched
"
[label]="componentIdLabelTemplate"
[placeholder]="'formFlow.uiEditor.selectComponent' | translate"
>
<cds-dropdown-list [items]="getCustomComponentItems()"></cds-dropdown-list>
</cds-dropdown>

<ng-template #componentIdLabelTemplate>
<span class="step-detail__label">
{{ getPropertyLabel(propertyName) }}

<v-tooltip-icon
[tooltip]="'formFlow.uiEditor.fieldTooltips.componentId' | translate"
></v-tooltip-icon>
</span>
</ng-template>
</div>
} @else {
<cds-label
[invalid]="
propertiesGroup.get(propertyName)?.invalid &&
propertiesGroup.get(propertyName)?.touched
"
[invalidText]="'formFlow.uiEditor.required' | translate"
>
{{ getPropertyLabel(propertyName) }}

<input
cdsText
[formControlName]="propertyName"
[invalid]="
propertiesGroup.get(propertyName)?.invalid &&
propertiesGroup.get(propertyName)?.touched
"
[attr.data-test-id]="testIds.stepPropertyInput"
/>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

target='frontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs/form-flow-ui-editor-tab/form-flow-step-detail/form-flow-step-detail.component.html'
ts_target='frontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs/form-flow-ui-editor-tab/form-flow-step-detail/form-flow-step-detail.component.ts'

echo "== files =="
git ls-files | grep -F "$target" || true
git ls-files | grep -F "$ts_target" || true

echo "== headers =="
for f in "$target" "$ts_target"; do
  echo "--- $f"
  if [ -f "$f" ]; then sed -n '1,18p' "$f"; fi
done

echo "== outline ts =="
if [ -f "$ts_target" ]; then ast-grep outline "$ts_target" --view compact || true; fi

echo "== relevant html read-only/readable tokens =="
if [ -f "$target" ]; then rg -n "readOnly|readonly|disabled|formControlName|cds-dropdown|propertiesGroup|testIds" "$target" || true; fi

echo "== relevant ts readonly/read-only/readable/readOnly =="
if [ -f "$ts_target" ]; then rg -n "readonly|readOnly|editable|formBuilder|propertiesGroup|testIds|stepTypeItems|getFormDefinitionItems|getCustomComponentItems|getPropertyLabel|isControlInvalid" "$ts_target" || true; fi

echo "== package carbon deps =="
if [ -f frontend/package.json ]; then jq -r '.dependencies, .devDependencies | with_entries(select(.key|test("`@carbon/`|cds-'))) frontend/package.json' || true; fi

Repository: valtimo-platform/valtimo

Length of output: 5492


🏁 Script executed:

#!/bin/bash
set -euo pipefail

target='frontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs/form-flow-ui-editor-tab/form-flow-step-detail/form-flow-step-detail.component.html'
ts_target='frontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs/form-flow-ui-editor-tab/form-flow-step-detail/form-flow-step-detail.component.ts'

echo "== html section =="
if [ -f "$target" ]; then sed -n '1,240p' "$target"; fi

echo "== ts section =="
if [ -f "$ts_target" ]; then sed -n '1,320p' "$ts_target"; fi

Repository: valtimo-platform/valtimo

Length of output: 17348


🌐 Web query:

carbon-components-angular dropdown disabled binding documentation CDSDropdown disabled

💡 Result:

In carbon-components-angular, the Dropdown component provides a disabled property to control its state [1][2]. Binding and Configuration: You can manage the disabled state using the @Input disabled property [1][2]. 1. Template Binding: You can bind a boolean value directly to the disabled input in your component template: <cds-dropdown [disabled]="isDisabled">... 2. Reactive Forms Integration: The Dropdown component implements the ControlValueAccessor interface, which includes the setDisabledState method [1][2]. This allows you to programmatically manage the disabled state through an Angular FormControl or FormGroup [1][2]. Example using Reactive Forms: this.myDropdownControl.disable; // Disables the dropdown this.myDropdownControl.enable; // Enables the dropdown Behind the scenes, the component's setDisabledState(isDisabled: boolean) method updates the internal disabled property when the form control state changes [1][2]. Technical Details: - Property Name: disabled - Type: boolean - Default Value: false [1][3] - The property is defined as @Input disabled = false; within the Dropdown component [1][2].

Citations:


Disable the read-only step controls.

When readOnly is true, the header hides destructive actions, but the key, title, type, and property controls remain editable. Bind native inputs to readonly and disable the Carbon dropdowns. Prefer disabling the reactive controls if child components must also reject programmatic edits.

Comment on lines +271 to +278
private applyReadOnly(): void {
if (!this.form) return;

if (this.readOnly) {
this.form.disable({emitEvent: false});
} else {
this.form.enable({emitEvent: false});
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Trace the validity event through the editor shell and identify read-only handling.
rg -n -C 5 '\(validEvent\)|validEvent|readOnly' \
  frontend/projects/valtimo/form-flow-management/src/lib/components/editor \
  frontend/projects/valtimo/form-flow-management/src/lib

Repository: valtimo-platform/valtimo

Length of output: 50381


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the UI editor tab component before inspecting validity/read-only behavior.
ast-grep outline frontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs/form-flow-ui-editor-tab/form-flow-ui-editor-tab.component.ts || true

# Show relevant implementation around buildForm, applyReadOnly, validEvent, and readOnly lifecycle.
sed -n '1,70p' frontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs/form-flow-ui-editor-tab/form-flow-ui-editor-tab.component.ts
printf '\n--- lines 180-230 ---\n'
sed -n '180,230p' frontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs/form-flow-ui-editor-tab/form-flow-ui-editor-tab.component.ts
printf '\n--- lines 240-430 ---\n'
sed -n '240,430p' frontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs/form-flow-ui-editor-tab/form-flow-ui-editor-tab.component.ts

# Search validEvent consumers and readonly-only editor exports for broader usage.
rg -n -C 6 'validEvent|ReadOnly|readOnly|disabled=' \
  frontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs/form-flow-ui-editor-tab \
  frontend/projects/valtimo/form-flow-management/src/lib/components/editor/form-flow-editor.component.ts

Repository: valtimo-platform/valtimo

Length of output: 50380


🌐 Web query:

Angular FormGroup disable status DISABLED valid property invalid disabled enabled documentation

💡 Result:

In Angular, the FormGroup (and FormControl) validation status system uses four mutually-exclusive states: VALID, INVALID, PENDING, and DISABLED [1][2]. Because these statuses are mutually exclusive, a FormGroup or FormControl cannot be both DISABLED and VALID simultaneously [1][2]. When a FormGroup is DISABLED, its valid property will always return false [1][2][3]. Key properties and behaviors include: - valid: Returns true only if the control's status is VALID. If the control is DISABLED, this property is false [4][5]. - invalid: Returns true only if the control's status is INVALID. If the control is DISABLED, this property is false [4][5]. - disabled: Returns true if the status is DISABLED [4][5]. - enabled: Returns true as long as the status is not DISABLED [4][5]. When a control is disabled, it is exempt from all validation checks [6][7]. This design is intentional; disabled controls are excluded from aggregate value calculations and validation logic to maintain a consistent model where the parent status is a reduction of its children's statuses [6][7][2]. Developers often encounter confusion because !valid is not equivalent to invalid when a form is disabled [3]. When a control is disabled, both valid and invalid properties may return false [3][8]. If your application logic requires treating a disabled form as valid (e.g., for enabling a submit button), you should implement a custom check that explicitly accounts for the disabled state, such as form.disabled || form.valid [9].

Citations:


Do not emit the disabled form status as definition validity.

When readOnly is true, applyReadOnly() disables the root FormGroup, which makes form.valid === false. emitState() then emits that disabled status through validEvent, so a valid read-only definition can disable export/save UI on lines 74 and 99. Preserve validation independently from the read-only UI state, or derive validEvent from the validation state before disabling the form.

@sofiaIvarsRitense sofiaIvarsRitense self-assigned this Aug 6, 2026

@marijnritense marijnritense Aug 6, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Just adding it as a comment here so you can respond. I don't think these are all things that need to happen, they're just things I'm noticing. I'll test more tomorrow.

  1. When creating the form flow, I get an error the start step does not yet exist. Since every form flow needs one, it makes sense to create the form flow with the first step.
  2. When the start step has been defined, I do not see an error (apart from a small ! at step-1, which also seems a bit blurry). Since I cannot click save and do not see any error, I don't know what is wrong.
Image
  1. When switching to the JSON editor, I would expect the step I just added (but couldn't save) to just show up correctly there, but with some fields not yet filled in.
  2. Can we also have validation for the JSON?
  3. Can we validate if any steps are unreachable?
  4. And if there's loops that can't be broken out of?
  5. I already mentioned this somewhere else as well, but it would be nice if we can split the expressions (for actions) into 1) selecting the bean, and then 2) selecting the method, and 3) filling in the arguments.
  6. As an enhancement, I think it would be nice to be able to visualize the flow.
  7. Expressions in conditions would also benefit from additional help. Knowing what "basic" fields are present, maybe based on the forms added we can show a full list of fields available for each step even. This obviously has moments where it can break, so it's important to then also add feedback about that.

@mbritense

Copy link
Copy Markdown
Contributor Author

@marijnritense please re-review, I only picked up your points 1 and 2, which were bugs

@mbritense

Copy link
Copy Markdown
Contributor Author
  1. Is not possible, only configurations able to be saved in BE shows up in JSON editor, if no changes saved yet, the user is warned (the state of each tab is independent)

…-editor

# Conflicts:
#	backend/form-flow/src/main/kotlin/com/ritense/formflow/FormFlowProcessLinkActivityHandler.kt
#	documentation/release-notes/13.x.x/13.41.0/README.md
#	frontend/package-lock.json
- Kept the shared generated ProcessDefinitionValidationError (regenerated
  backend types so it includes the new validation fields from next-minor)
- Removed the old-structure form flow documentation and 13.42.0 release
  notes; they will be redone against the new documentation standard and
  the 13.44.0 release notes
@coderabbitai

coderabbitai Bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

| Area | Fix |
|------|-----|
| Cases | The process selector on the Progress tab shows long process names in full instead of cutting them off |
| Process links | Changing the form flow definition on an existing form flow process link is now saved (previously the change was silently ignored). |

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
| Process links | Changing the form flow definition on an existing form flow process link is now saved (previously the change was silently ignored). |
| Process links | Changing the form flow definition on an existing form flow process link is now saved. |

@marijnritense

Copy link
Copy Markdown
Collaborator

Found a bug with testing. When saving a form also adds a new "start step" step.
image

It's in the database too so I'm not sure if this is a problem with not deleting the initial start-step, or if it actually gets added every time.

…-editor

# Conflicts:
#	documentation/release-notes/13.x.x/13.44.0/README.md
@marijnritense marijnritense added tested and removed test env Create a remote test environment for this PR in the Ritense product development cluster labels Aug 27, 2026
@github-actions github-actions Bot deleted a comment from valtimo-platform Bot Aug 27, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
backend/plugin-valtimo/src/test/kotlin/com/ritense/valtimo/processlink/listener/ProcessDefinitionChangedEventListenerTest.kt (1)

48-48: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a two-resolver test.

The current setup provides one mock. It does not verify that all resolvers run or that a failure in one resolver does not stop the next resolver. Add two mocks, make the first throw, and verify that the second receives caseDefinitionId.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: dd820261-5ff1-470d-8c2f-b3b0d6fa04e6

📥 Commits

Reviewing files that changed from the base of the PR and between d1faadc and dc599ce.

📒 Files selected for processing (4)
  • backend/plugin-valtimo/src/main/kotlin/com/ritense/valtimo/processlink/listener/ProcessDefinitionChangedEventListener.kt
  • backend/plugin-valtimo/src/test/kotlin/com/ritense/valtimo/processlink/listener/ProcessDefinitionChangedEventListenerTest.kt
  • documentation/release-notes/13.x.x/13.44.0/README.md
  • frontend/package.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • documentation/release-notes/13.x.x/13.44.0/README.md

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.

*/
class ProcessDefinitionChangedEventListener(
private val pluginConfigurationMappingResolver: PluginConfigurationMappingResolver
private val pluginConfigurationMappingResolvers: List<PluginConfigurationMappingResolver>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

rg -n -C 2 'ProcessDefinitionChangedEventListener\s*\(' --glob '*.kt' .

Repository: valtimo-platform/valtimo

Length of output: 2577


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf '%s\n' '--- ProcessDefinitionChangedEventListener ---'
sed -n '1,90p' backend/plugin-valtimo/src/main/kotlin/com/ritense/valtimo/processlink/listener/ProcessDefinitionChangedEventListener.kt

printf '%s\n' '--- ProcessLinkAutoConfiguration bean ---'
sed -n '210,245p' backend/plugin-valtimo/src/main/kotlin/com/ritense/valtimo/processlink/ProcessLinkAutoConfiguration.kt

printf '%s\n' '--- Repository conventions and learnings ---'
find /tmp/coderabbit-repo-knowledge/valtimo-platform-valtimo-233df332 \
  -type f -name '*.md' -print
for file in /tmp/coderabbit-repo-knowledge/valtimo-platform-valtimo-233df332/*/*.md; do
  [ -f "$file" ] || continue
  printf '\n--- %s ---\n' "$file"
  head -80 "$file"
done

Repository: valtimo-platform/valtimo

Length of output: 9720


Pass a resolver list to ProcessDefinitionChangedEventListener

ProcessLinkAutoConfiguration passes a PluginConfigurationMappingResolver, but the constructor requires List<PluginConfigurationMappingResolver>. This causes compilation to fail. Inject and pass the resolver list.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants