Skip to content

feat: form flow ui editor - #883

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

feat: form flow ui editor#883
mbritense wants to merge 6 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

Warning

Review limit reached

@mbritense, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 13 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 80c9a581-818a-470a-a1ff-2d864dc43789

📥 Commits

Reviewing files that changed from the base of the PR and between 715277f and 7e48502.

⛔ Files ignored due to path filters (3)
  • documentation/features/case/images/form-flow-editor-actions.png is excluded by !**/*.png
  • documentation/features/case/images/form-flow-editor-help.png is excluded by !**/*.png
  • documentation/features/case/images/form-flow-editor.png is excluded by !**/*.png
📒 Files selected for processing (3)
  • documentation/features/building-blocks/form-flows.md
  • documentation/features/case/form-flow.md
  • documentation/release-notes/13.x.x/13.41.0/README.md
📝 Walkthrough

Summary by CodeRabbit

  • New Features
    • Added a visual form-flow editor for managing steps, transitions, forms, actions, and conditions.
    • Added a JSON editor tab with schema validation and tab-specific navigation.
    • Added expression suggestions, contextual property guidance, and help content.
    • Added a secured registry endpoint exposing available form-flow metadata.
  • Bug Fixes
    • Added unsaved-change protection when leaving the editor.
    • Improved validation for duplicate steps, invalid transitions, and missing start steps.
    • Updated process-link editing to use form-flow definition keys.
  • Documentation
    • Added English and Dutch translations for the enhanced editor experience.

Walkthrough

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

🚥 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 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 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.

@valtimo-platform

valtimo-platform Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Will create a test environment. This comment will be updated once it is available. This usually takes a few minutes.
Closing or merging this PR will automatically delete the test environment. Pushing commits to this PR will update the test environment.
Progress:

  • Created test environment
  • Waiting for frontend and backend images with tag "testEnv.pr883_commit.<commit sha>_contents.<contents sha>_build.<build number>". Check the GitHub Actions logs for progress on the build / image tagging.
  • Starting test environment
  • Waiting for environment to run

@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.

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

Labels

feedback test env Create a remote test environment for this PR in the Ritense product development cluster

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants