feat: form flow ui editor - #883
Conversation
|
Warning Review limit reached
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 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 configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (3)
📒 Files selected for processing (3)
📝 WalkthroughSummary by CodeRabbit
WalkthroughThe 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)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
🧪 Generate unit tests (beta)
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. Comment |
|
Will create a test environment. This comment will be updated once it is available. This usually takes a few minutes.
|
There was a problem hiding this comment.
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 winAssert the
alwaysPresentdirection as well.The comment states that the test guards against drift between the handler and the registry.
containsAllonly 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 withalwaysPresent = true, this test still passes.Add an assertion that every
alwaysPresentproperty 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
⛔ Files ignored due to path filters (1)
frontend/projects/valtimo/shared/src/lib/generated/generated-backend-types.tsis excluded by!**/generated/**
📒 Files selected for processing (75)
backend/apps/dev/build.gradlebackend/form-flow/src/main/kotlin/com/ritense/formflow/AbstractFormFlowLinkTaskProvider.ktbackend/form-flow/src/main/kotlin/com/ritense/formflow/FormFlowProcessLinkActivityHandler.ktbackend/form-flow/src/main/kotlin/com/ritense/formflow/autoconfigure/FormFlowAutoConfiguration.ktbackend/form-flow/src/main/kotlin/com/ritense/formflow/security/ValtimoFormFlowHttpSecurityConfigurer.ktbackend/form-flow/src/main/kotlin/com/ritense/formflow/service/FormFlowRegistryService.ktbackend/form-flow/src/main/kotlin/com/ritense/formflow/web/rest/FormFlowRegistryResource.ktbackend/form-flow/src/main/kotlin/com/ritense/formflow/web/rest/dto/FormFlowRegistryDto.ktbackend/form-flow/src/test/kotlin/com/ritense/formflow/FormFlowProcessLinkActivityHandlerIntTest.ktbackend/form-flow/src/test/kotlin/com/ritense/formflow/service/FormFlowRegistryServiceTest.ktbackend/form-flow/src/test/kotlin/com/ritense/formflow/web/rest/FormFlowRegistryResourceIntTest.ktbackend/form-flow/src/test/kotlin/com/ritense/formflow/web/rest/FormFlowRegistrySecurityIntTest.kte2e/tests/case-details-management-form-flows/case-details-management-form-flows.spec.tse2e/tests/case-details-management-form-flows/page.tsfrontend/projects/valtimo/admin-settings/src/lib/models/accent-colors.model.tsfrontend/projects/valtimo/admin-settings/src/lib/models/feature-toggle.model.tsfrontend/projects/valtimo/building-block-management/src/lib/building-block-management-routing.tsfrontend/projects/valtimo/case-management/src/lib/case-management-routing.module.tsfrontend/projects/valtimo/case-management/src/lib/models/startable-item.model.tsfrontend/projects/valtimo/case/src/lib/models/case-inspection.models.tsfrontend/projects/valtimo/components/src/lib/models/choice-field.model.tsfrontend/projects/valtimo/document/src/lib/models/document.model.tsfrontend/projects/valtimo/form-flow-management/src/lib/components/editor/form-flow-editor.component.htmlfrontend/projects/valtimo/form-flow-management/src/lib/components/editor/form-flow-editor.component.scssfrontend/projects/valtimo/form-flow-management/src/lib/components/editor/form-flow-editor.component.tsfrontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs/form-flow-json-editor-tab/form-flow-json-editor-tab.component.htmlfrontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs/form-flow-json-editor-tab/form-flow-json-editor-tab.component.tsfrontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs/form-flow-ui-editor-tab/form-flow-context-properties/form-flow-context-properties.component.htmlfrontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs/form-flow-ui-editor-tab/form-flow-context-properties/form-flow-context-properties.component.scssfrontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs/form-flow-ui-editor-tab/form-flow-context-properties/form-flow-context-properties.component.tsfrontend/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.htmlfrontend/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.scssfrontend/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.tsfrontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs/form-flow-ui-editor-tab/form-flow-expression-list/form-flow-expression-list.component.htmlfrontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs/form-flow-ui-editor-tab/form-flow-expression-list/form-flow-expression-list.component.scssfrontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs/form-flow-ui-editor-tab/form-flow-expression-list/form-flow-expression-list.component.tsfrontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs/form-flow-ui-editor-tab/form-flow-step-detail/form-flow-step-detail.component.htmlfrontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs/form-flow-ui-editor-tab/form-flow-step-detail/form-flow-step-detail.component.scssfrontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs/form-flow-ui-editor-tab/form-flow-step-detail/form-flow-step-detail.component.tsfrontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs/form-flow-ui-editor-tab/form-flow-step-list/form-flow-step-list.component.htmlfrontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs/form-flow-ui-editor-tab/form-flow-step-list/form-flow-step-list.component.scssfrontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs/form-flow-ui-editor-tab/form-flow-step-list/form-flow-step-list.component.tsfrontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs/form-flow-ui-editor-tab/form-flow-transition-list/form-flow-transition-list.component.htmlfrontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs/form-flow-ui-editor-tab/form-flow-transition-list/form-flow-transition-list.component.scssfrontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs/form-flow-ui-editor-tab/form-flow-transition-list/form-flow-transition-list.component.tsfrontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs/form-flow-ui-editor-tab/form-flow-ui-editor-tab.component.htmlfrontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs/form-flow-ui-editor-tab/form-flow-ui-editor-tab.component.scssfrontend/projects/valtimo/form-flow-management/src/lib/components/editor/tabs/form-flow-ui-editor-tab/form-flow-ui-editor-tab.component.tsfrontend/projects/valtimo/form-flow-management/src/lib/constants/form-flow-editor.test-ids.tsfrontend/projects/valtimo/form-flow-management/src/lib/constants/index.tsfrontend/projects/valtimo/form-flow-management/src/lib/constants/injection-tokens.tsfrontend/projects/valtimo/form-flow-management/src/lib/form-flow-management.module.tsfrontend/projects/valtimo/form-flow-management/src/lib/models/form-flow-custom-component.model.tsfrontend/projects/valtimo/form-flow-management/src/lib/models/form-flow-editor.model.tsfrontend/projects/valtimo/form-flow-management/src/lib/models/form-flow.model.tsfrontend/projects/valtimo/form-flow-management/src/lib/models/index.tsfrontend/projects/valtimo/form-flow-management/src/lib/services/form-flow-component.service.tsfrontend/projects/valtimo/form-flow-management/src/lib/services/form-flow-download.service.tsfrontend/projects/valtimo/form-flow-management/src/lib/services/form-flow-editor-form.service.tsfrontend/projects/valtimo/form-flow-management/src/lib/services/form-flow.service.tsfrontend/projects/valtimo/form-flow-management/src/lib/services/index.tsfrontend/projects/valtimo/form-flow-management/src/lib/utils/index.tsfrontend/projects/valtimo/form-flow-management/src/lib/utils/translate.utils.tsfrontend/projects/valtimo/form-flow-management/src/public-api.tsfrontend/projects/valtimo/iko/src/lib/models/iko-management-list.model.tsfrontend/projects/valtimo/process-link/src/lib/components/select-form-flow/select-form-flow.component.tsfrontend/projects/valtimo/process-link/src/lib/constants/injection-tokens.tsfrontend/projects/valtimo/process-link/src/lib/models/form-flow.model.tsfrontend/projects/valtimo/process-link/src/lib/models/process-link.model.tsfrontend/projects/valtimo/process-link/src/lib/services/form-flow-component.service.tsfrontend/projects/valtimo/process-management/src/lib/models/process-management.model.tsfrontend/projects/valtimo/shared/assets/core/en.jsonfrontend/projects/valtimo/shared/assets/core/nl.jsonfrontend/projects/valtimo/task/src/lib/models/task-intermediate-save.model.tsfrontend/projects/valtimo/zgw/src/lib/case-inspection/case-inspection.models.ts
| val additionalProperties = mutableMapOf<String, Any>(PROCESS_DEFINITION_KEY to processDefinition.key) | ||
| documentId?.let { additionalProperties[DOCUMENT_ID] = it } | ||
| documentDefinitionName?.let { additionalProperties[DOCUMENT_DEFINITION_NAME] = it } |
There was a problem hiding this comment.
📐 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"
doneRepository: 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 -SRepository: 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.
| // Assert — the transition is part of the persisted definition shown in the JSON editor | ||
| await formFlowsPage.openJsonEditorTab(); | ||
| await expect(formFlowsPage.monacoEditor).toContainText('step-2'); |
There was a problem hiding this comment.
🎯 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 || trueRepository: 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 || trueRepository: 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()
PYRepository: 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.
| // 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); |
There was a problem hiding this comment.
🎯 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.tsRepository: 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"
doneRepository: 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)
PYRepository: 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.htmlRepository: 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"
fiRepository: 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
doneRepository: 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
fiRepository: 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.
| 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)); | ||
| }); | ||
| } |
There was a problem hiding this comment.
🎯 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.tsRepository: 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/editorRepository: 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:
- 1: https://angular.dev/guide/routing/customizing-route-behavior
- 2: https://github.com/angular/angular/blob/main/packages/router/src/route_reuse_strategy.ts
- 3: https://kimsereylam.com/angular/2021/05/07/component-reuse-angular.html
- 4: Route component reuse options angular/angular#20993
- 5: docs: clarify onSameUrlNavigation behavior angular/angular#42275
- 6: https://github.com/angular/angular/blob/main/packages/router/src/models.ts
- 7: https://angular.dev/api/router/OnSameUrlNavigation
- 8: Router not reusing parent component when changing only the child route angular/angular#18374
- 9: 'onSameUrlNavigation' reload feature does not reload the routed components angular/angular#22038
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.
| 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)); |
There was a problem hiding this comment.
🩺 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.
| 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)); |
| <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" | ||
| /> |
There was a problem hiding this comment.
🎯 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; fiRepository: 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"; fiRepository: 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:
- 1: https://angular.carbondesignsystem.com/documentation/components/Dropdown.html
- 2: https://github.com/carbon-design-system/carbon-components-angular/blob/master/src/dropdown/dropdown.component.ts
- 3: https://unpkg.com/carbon-components-angular@5.72.0/docs/documentation/components/Dropdown.html
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.
| private applyReadOnly(): void { | ||
| if (!this.form) return; | ||
|
|
||
| if (this.readOnly) { | ||
| this.form.disable({emitEvent: false}); | ||
| } else { | ||
| this.form.enable({emitEvent: false}); | ||
| } |
There was a problem hiding this comment.
🎯 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/libRepository: 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.tsRepository: 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:
- 1: After Reactive form is disabled it makes the form invalid. angular/angular#18678
- 2: Disabled input considered as invalid in dynamic form angular/angular#11432
- 3: Form is invalid when all fields are disabled angular/angular#39287
- 4: https://github.com/angular/angular/blob/13.1.x/packages/forms/src/model.ts
- 5: https://github.com/angular/angular/blob/main/packages/forms/src/model/abstract_model.ts
- 6: https://angular.dev/api/forms/FormGroup
- 7: https://angular.dev/api/forms/FormControl
- 8: Form with only disabled elements has "{valid: false, invalid: false}" angular/angular#39754
- 9: https://stackoverflow.com/questions/51466641/reactive-form-valid-property-return-false-when-form-is-disabled
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.
…-editor # Conflicts: # documentation/release-notes/13.x.x/13.41.0/README.md
There was a problem hiding this comment.
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.
- 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.
- 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.
- 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.
- Can we also have validation for the JSON?
- Can we validate if any steps are unreachable?
- And if there's loops that can't be broken out of?
- 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.
- As an enhancement, I think it would be nice to be able to visualize the flow.
- 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.
closes generiekzaakafhandelcomponent/gzac-issues#844