Repository: stolostron/console
Pull request #4480: ACM-20347-Difficult-to-edit-policy-using-the-yaml-editor
Author: login: jeswanke
Labels: name: lgtm
name: approved
name: dco-signoff: yes
Created: 2025-05-09T14:26:25Z | Merged: 2025-05-21T17:41:17Z

Signed-off-by: John Swanke <jswanke@redhat.com>

# 📝 Summary

**Ticket Summary (Title):**  
<!-- Use the exact title from Jira or a brief, clear summary -->
Policy YAML Editor sync is broken when quickly switching between tabs
Difficult to edit policy using the yaml editor

**Ticket Link:**  
<!-- e.g. https://issues.redhat.com/browse/ACM-12345 -->
https://issues.redhat.com/browse/ACM-20347
https://issues.redhat.com/browse/ACM-18711

**Type of Change:**  
<!-- Select one -->
- [x] 🐞 Bug Fix  
- [ ] ✨ Feature  
- [ ] 🔧 Refactor
- [ ] 💸 Tech Debt
- [ ] 🧪 Test-related  
- [ ] 📄 Docs

---

## ✅ Checklist

### General

- [x] PR title follows the convention (e.g. `ACM-12340 Fix bug with...`)
- [x] Code builds and runs locally without errors
- [x] No console logs, commented-out code, or unnecessary files
- [x] All commits are meaningful and well-labeled
- [x] All new display strings are externalized for localization (English only)
- [ ] *(Nice to have)* JSDoc comments added for new functions and interfaces

#### If Feature

- [ ] UI/UX reviewed (if applicable)
- [ ] All acceptance criteria met
- [ ] Unit test coverage added or updated
- [ ] Relevant documentation or comments included

#### If Bugfix

- [ ] Root cause and fix summary are documented in the ticket (for future reference / errata)
- [ ] Fix tested thoroughly and resolves the issue
- [ ] Test(s) added to prevent regression

---

### 🗒️ Notes for Reviewers
<!-- Optional: anything reviewers should know, special context, etc. -->


Diff:
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index a33cb098b55..d7606435f59 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -23,7 +23,7 @@
         "@octokit/types": "6.40.0",
         "@openshift-assisted/locales": "2.14.5-cim",
         "@openshift-assisted/ui-lib": "2.14.5-cim",
-        "@patternfly-labs/react-form-wizard": "^2.5.0",
+        "@patternfly-labs/react-form-wizard": "^2.6.0",
         "@patternfly/patternfly": "^5.4.2",
         "@patternfly/react-charts": "^7.4.7",
         "@patternfly/react-code-editor": "^5.4.13",
@@ -6076,10 +6076,9 @@
       }
     },
     "node_modules/@patternfly-labs/react-form-wizard": {
-      "version": "2.5.0",
-      "resolved": "https://registry.npmjs.org/@patternfly-labs/react-form-wizard/-/react-form-wizard-2.5.0.tgz",
-      "integrity": "sha512-WzmMhbcbru7Kyc/ebqUGjpI5Dn/RNUoAz8AJ3rjMmRBv91jZI1SYqssSVwX++QMb5GvYMuwijpw+rRnmDxsxBw==",
-      "license": "Apache-2.0",
+      "version": "2.6.0",
+      "resolved": "https://registry.npmjs.org/@patternfly-labs/react-form-wizard/-/react-form-wizard-2.6.0.tgz",
+      "integrity": "sha512-zQ7NprSh6sbDy1EGEnyywc6tO5OASG8GfGzDT/Lld6I01pKSvThyuW9bXJru5RDelaFTen65NcCfX4fzhFLkxA==",
       "dependencies": {
         "get-value": "3.0.1",
         "klona": "2.0.6",
@@ -38225,9 +38224,9 @@
       }
     },
     "@patternfly-labs/react-form-wizard": {
-      "version": "2.5.0",
-      "resolved": "https://registry.npmjs.org/@patternfly-labs/react-form-wizard/-/react-form-wizard-2.5.0.tgz",
-      "integrity": "sha512-WzmMhbcbru7Kyc/ebqUGjpI5Dn/RNUoAz8AJ3rjMmRBv91jZI1SYqssSVwX++QMb5GvYMuwijpw+rRnmDxsxBw==",
+      "version": "2.6.0",
+      "resolved": "https://registry.npmjs.org/@patternfly-labs/react-form-wizard/-/react-form-wizard-2.6.0.tgz",
+      "integrity": "sha512-zQ7NprSh6sbDy1EGEnyywc6tO5OASG8GfGzDT/Lld6I01pKSvThyuW9bXJru5RDelaFTen65NcCfX4fzhFLkxA==",
       "requires": {
         "get-value": "3.0.1",
         "klona": "2.0.6",
diff --git a/frontend/package.json b/frontend/package.json
index a7887c0e39d..4dee78aa1b1 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -48,7 +48,7 @@
     "@octokit/types": "6.40.0",
     "@openshift-assisted/locales": "2.14.5-cim",
     "@openshift-assisted/ui-lib": "2.14.5-cim",
-    "@patternfly-labs/react-form-wizard": "^2.5.0",
+    "@patternfly-labs/react-form-wizard": "^2.6.0",
     "@patternfly/patternfly": "^5.4.2",
     "@patternfly/react-charts": "^7.4.7",
     "@patternfly/react-code-editor": "^5.4.13",
diff --git a/frontend/public/locales/en/translation.json b/frontend/public/locales/en/translation.json
index 9d16b2e6593..d2f640ce830 100644
--- a/frontend/public/locales/en/translation.json
+++ b/frontend/public/locales/en/translation.json
@@ -1448,7 +1448,9 @@
   "Find the Amazon Web Services (AWS) STS credential and role ARN that you used to create your hosted cluster. The STS credential by default expires in 12 hours so a new one may be needed.": "Find the Amazon Web Services (AWS) STS credential and role ARN that you used to create your hosted cluster. The STS credential by default expires in 12 hours so a new one may be needed.",
   "find.label": "Find",
   "Fit to Screen": "Fit to Screen",
+  "Fix editor syntax errors": "Fix editor syntax errors",
   "Fix exceptions": "Fix exceptions",
+  "Fix validation errors": "Fix validation errors",
   "Flavor": "Flavor",
   "Flux": "Flux",
   "Follow documentation for more information.": "Follow documentation for more information.",
@@ -1456,7 +1458,6 @@
   "For Subscription applications, displays the number of remote and local clusters where resources for the application are deployed. For Argo applications, this is the name of the destination cluster. For OpenShift applications, this is the cluster where the application is deployed.": "For Subscription applications, displays the number of remote and local clusters where resources for the application are deployed. For Argo applications, this is the name of the destination cluster. For OpenShift applications, this is the cluster where the application is deployed.",
   "Forbidden": "Forbidden",
   "foreground": "foreground",
-  "Form edits will be ignored until YAML syntax errors are fixed.": "Form edits will be ignored until YAML syntax errors are fixed.",
   "formerror.valid.baseDNSPeriod": "The base DNS domain must not begin with a period.",
   "formerror.valid.name": "Must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character",
   "Fuzzy search": "Fuzzy search",
@@ -2152,7 +2153,6 @@
   "Placements": "Placements",
   "Placements define the target clusters that must subscribe to a ClusterSet where subscriptions and application sets are delivered. This is done by cluster name, cluster resource annotation(s), or cluster resource label(s).": "Placements define the target clusters that must subscribe to a ClusterSet where subscriptions and application sets are delivered. This is done by cluster name, cluster resource annotation(s), or cluster resource label(s).",
   "Platform specific data for the add node pool form will no longer automatically populate after the last node pool is removed.": "Platform specific data for the add node pool form will no longer automatically populate after the last node pool is removed.",
-  "Please fix validation errors": "Please fix validation errors",
   "Plus": "Plus",
   "Pod": "Pod",
   "Pod details for {{0}}": "Pod details for {{0}}",
@@ -3327,6 +3327,7 @@
   "vSphere disk type": "vSphere disk type",
   "vSphere folder": "vSphere folder",
   "vSphere resource pool": "vSphere resource pool",
+  "Wait for editor syntax check": "Wait for editor syntax check",
   "Want more information? Check out the developer community.": "Want more information? Check out the developer community.",
   "Want to learn more?": "Want to learn more?",
   "warning": "warning",
@@ -3385,6 +3386,7 @@
   "You don't have any policy sets": "You don't have any policy sets",
   "You don't have any subscriptions": "You don't have any subscriptions",
   "You must fill out all required fields before you can proceed.": "You must fill out all required fields before you can proceed.",
+  "You must fix editor syntax errors.": "You must fix editor syntax errors.",
   "You must fix the issues with fields before you can proceed.": "You must fix the issues with fields before you can proceed.",
   "You must select an existing placement or placement rule.": "You must select an existing placement or placement rule.",
   "You selected {{count}} cluster that cannot be destroyed": "You selected {{count}} cluster that cannot be destroyed",
diff --git a/frontend/src/components/AcmDataForm.tsx b/frontend/src/components/AcmDataForm.tsx
index 8d04bdfd36b..3bb59cb85bd 100644
--- a/frontend/src/components/AcmDataForm.tsx
+++ b/frontend/src/components/AcmDataForm.tsx
@@ -89,7 +89,7 @@ import {
   SectionGroup,
   SelectOptionInput,
 } from './AcmFormData'
-import { SyncEditor } from './SyncEditor/SyncEditor'
+import { SyncEditor, ValidationStatus } from './SyncEditor/SyncEditor'
 import { LostChangesContext, LostChangesPrompt } from './LostChanges'
 import { AcmHelperText } from '../ui-components/AcmHelperText/AcmHelperText'
 
@@ -134,6 +134,20 @@ export function AcmDataFormPage(props: AcmDataFormProps): JSX.Element {
   const [copyHint, setCopyHint] = useState<ReactNode>(
     <span style={{ wordBreak: 'keep-all' }}>{t('Copy to clipboard')}</span>
   )
+  const [editorValidationStatus, setEditorValidationStatus] = useState<ValidationStatus>(ValidationStatus.success)
+  const renderErrors = (showErrors: boolean, hasRequiredErrors: boolean) => {
+    if (showErrors || editorValidationStatus === ValidationStatus.failure) {
+      return (
+        <AlertGroup>
+          {showErrors && hasRequiredErrors && <Alert isInline variant="danger" title={requiredValidationMessage(t)} />}
+          {showErrors && !hasRequiredErrors && <Alert isInline variant="danger" title={generalValidationMessage(t)} />}
+          {editorValidationStatus === ValidationStatus.failure && (
+            <Alert isInline variant="danger" title={t('You must fix editor syntax errors.')} />
+          )}
+        </AlertGroup>
+      )
+    }
+  }
 
   const resources = formData.stateToData()
 
@@ -174,6 +188,9 @@ export function AcmDataFormPage(props: AcmDataFormProps): JSX.Element {
                     onEditorChange={(changes: { resources: any[] }): void => {
                       formData.customData = changes?.resources
                     }}
+                    onStatusChange={(editorStatus: ValidationStatus): void => {
+                      setEditorValidationStatus(editorStatus)
+                    }}
                   />
                 ) : (
                   <CodeBlock
@@ -217,6 +234,7 @@ export function AcmDataFormPage(props: AcmDataFormProps): JSX.Element {
                   mode={mode}
                   showFormErrors={showFormErrors}
                   setShowFormErrors={setShowFormErrors}
+                  renderErrors={renderErrors}
                   isHorizontal={isHorizontal}
                   globalWizardAlert={globalWizardAlert}
                   isModalWizard={isModalWizard}
@@ -227,6 +245,7 @@ export function AcmDataFormPage(props: AcmDataFormProps): JSX.Element {
                 <AcmDataForm
                   {...props}
                   mode={mode}
+                  renderErrors={renderErrors}
                   showFormErrors={showFormErrors}
                   setShowFormErrors={setShowFormErrors}
                   isHorizontal={isHorizontal}
@@ -278,17 +297,13 @@ export function AcmDataFormPage(props: AcmDataFormProps): JSX.Element {
                   )
                 }
               />
-              {showFormErrors && mode === 'form' && formHasErrors(t, formData) && (
-                <PageSection variant="light" style={{ paddingTop: 0 }}>
-                  <AlertGroup>
-                    {formHasRequiredErrors(formData) ? (
-                      <Alert isInline variant="danger" title={requiredValidationMessage(t)} />
-                    ) : (
-                      <Alert isInline variant="danger" title={generalValidationMessage(t)} />
-                    )}
-                  </AlertGroup>
-                </PageSection>
-              )}
+              {showFormErrors &&
+                mode === 'form' &&
+                (editorValidationStatus === ValidationStatus.failure || formHasErrors(t, formData)) && (
+                  <PageSection variant="light" style={{ paddingTop: 0 }}>
+                    {renderErrors(true, formHasRequiredErrors(formData))}
+                  </PageSection>
+                )}
             </Fragment>
           }
           groupProps={{ stickyOnBreakpoint: { default: 'top' } }}
@@ -303,12 +318,22 @@ export function AcmDataFormPage(props: AcmDataFormProps): JSX.Element {
 export function AcmDataForm(
   props: AcmDataFormProps & {
     showFormErrors: boolean
+    renderErrors: (showErrors: boolean, hasRequiredErrors: boolean) => ReactNode
     setShowFormErrors: (showFormErrors: boolean) => void
     globalWizardAlert?: ReactNode
     isModalWizard?: boolean
   }
 ): JSX.Element {
-  const { formData, isHorizontal, globalWizardAlert, showFormErrors, setShowFormErrors, isModalWizard, mode } = props
+  const {
+    formData,
+    isHorizontal,
+    globalWizardAlert,
+    showFormErrors,
+    setShowFormErrors,
+    renderErrors,
+    isModalWizard,
+    mode,
+  } = props
   if (mode === 'details') {
     return (
       <Form>
@@ -323,6 +348,7 @@ export function AcmDataForm(
           <AcmDataFormWizard
             formData={formData}
             isHorizontal={isHorizontal ?? false}
+            renderErrors={renderErrors}
             showFormErrors={showFormErrors}
             setShowFormErrors={setShowFormErrors}
             globalWizardAlert={globalWizardAlert}
@@ -456,10 +482,12 @@ export function AcmDataFormWizard(props: {
   globalWizardAlert?: ReactNode
   showFormErrors: boolean
   isModalWizard?: boolean
+  renderErrors: (showErrors: boolean, hasRequiredErrors: boolean) => ReactNode
   setShowFormErrors: (showFormErrors: boolean) => void
 }): JSX.Element {
   const { t } = useTranslation()
-  const { formData, isHorizontal, globalWizardAlert, showFormErrors, setShowFormErrors, isModalWizard } = props
+  const { formData, isHorizontal, globalWizardAlert, showFormErrors, setShowFormErrors, renderErrors, isModalWizard } =
+    props
   const [showSectionErrors, setShowSectionErrors] = useState<Record<string, boolean>>({})
   const [submitText, setSubmitText] = useState(formData.submitText)
   const [submitError, setSubmitError] = useState('')
@@ -495,14 +523,9 @@ export function AcmDataFormWizard(props: {
       component: section.type === 'Section' && (
         <Form isHorizontal={isHorizontal}>
           {globalWizardAlert && <AlertGroup>{globalWizardAlert}</AlertGroup>}
-          {(showFormErrors || showSectionErrors[section.title]) && hasError && (
-            <AlertGroup>
-              {sectionHasRequiredErrors(section) ? (
-                <Alert isInline variant="danger" title={requiredValidationMessage(t)} />
-              ) : (
-                <Alert isInline variant="danger" title={generalValidationMessage(t)} />
-              )}
-            </AlertGroup>
+          {renderErrors(
+            (showFormErrors || showSectionErrors[section.title]) && hasError,
+            sectionHasRequiredErrors(section)
           )}
           {section.alerts && <AlertGroup>{section.alerts}</AlertGroup>}
           <Title headingLevel="h2">{section.wizardTitle ?? section.title}</Title>
@@ -533,15 +556,7 @@ export function AcmDataFormWizard(props: {
     name: t('Review'),
     component: (
       <Form>
-        {showFormErrors && formHasErrors(t, formData) && (
-          <AlertGroup>
-            {formHasRequiredErrors(formData) ? (
-              <Alert isInline variant="danger" title={requiredValidationMessage(t)} />
-            ) : (
-              <Alert isInline variant="danger" title={generalValidationMessage(t)} />
-            )}
-          </AlertGroup>
-        )}
+        {renderErrors(showFormErrors && formHasErrors(t, formData), formHasRequiredErrors(formData))}
         <AcmDataFormDetails formData={formData} wizardSummary={true} />
       </Form>
     ),
diff --git a/frontend/src/components/SyncEditor/SyncEditor.tsx b/frontend/src/components/SyncEditor/SyncEditor.tsx
index db6e3a49ee1..a95871a4e84 100644
--- a/frontend/src/components/SyncEditor/SyncEditor.tsx
+++ b/frontend/src/components/SyncEditor/SyncEditor.tsx
@@ -3,11 +3,11 @@ import { HTMLProps, ReactNode, useRef, useEffect, useState, useCallback, useMemo
 import useResizeObserver from '@react-hook/resize-observer'
 import { CodeEditor, CodeEditorControl, Language } from '@patternfly/react-code-editor'
 import { RedoIcon, UndoIcon, SearchIcon, EyeIcon, EyeSlashIcon, CloseIcon } from '@patternfly/react-icons'
-import { Alert, ClipboardCopyButton } from '@patternfly/react-core'
+import { ClipboardCopyButton } from '@patternfly/react-core'
 import { debounce, noop, isEqual, cloneDeep } from 'lodash'
 import { processForm, processUser, ProcessedType } from './process'
-import { compileAjvSchemas, ErrorType, formatErrors } from './validation'
-import { getFormChanges, getUserChanges, formatChanges } from './changes'
+import { compileAjvSchemas } from './validation'
+import { getFormChanges, getUserChanges } from './changes'
 import { decorate, getResourceEditorDecorations } from './decorate'
 import { setFormValues, updateReferences } from './synchronize'
 import { global_BackgroundColor_200 as globalBackground200 } from '@patternfly/react-tokens/dist/js/global_BackgroundColor_200'
@@ -23,6 +23,12 @@ import { loader, Monaco } from '@monaco-editor/react'
 // loader can be null in tests
 loader?.config({ monaco })
 
+export enum ValidationStatus {
+  success = 'success',
+  pending = 'pending',
+  failure = 'failure',
+}
+
 export interface SyncEditorProps extends HTMLProps<HTMLPreElement> {
   variant?: string
   editorTitle?: string
@@ -38,7 +44,7 @@ export interface SyncEditorProps extends HTMLProps<HTMLPreElement> {
   mock?: boolean
   autoCreateNs?: boolean
   onClose?: () => void
-  onStatusChange?: (editorState: any) => void
+  onStatusChange?: (status: ValidationStatus) => void
   onEditorChange?: (editorResources: any) => void
 }
 
@@ -97,15 +103,6 @@ export function SyncEditor(props: SyncEditorProps): JSX.Element {
   const [filteredRows, setFilteredRows] = useState<number[]>([])
   const [userEdits, setUserEdits] = useState<any>([])
   const [customValidationErrors, setCustomValidationErrors] = useState<any>([])
-  const [statusChanges, setStatusChanges] = useState<{
-    changes: any[]
-    errors: any[]
-    redactedChange: {
-      mappings: { [name: string]: any[] }
-      parsed: { [name: string]: any[] }
-      resources: any[]
-    }
-  }>()
   const [lastUserEdits, setLastUserEdits] = useState<any>([])
   const [squigglyTooltips, setSquigglyTooltips] = useState<any>([])
   const [lastChange, setLastChange] = useState<ProcessedType>()
@@ -434,8 +431,8 @@ export function SyncEditor(props: SyncEditorProps): JSX.Element {
           setHasRedo(false)
           setHasUndo(false)
 
-          // if there were remaining edits, report to form
-          setStatusChanges(cloneDeep({ changes: remainingEdits, redactedChange: change, errors: allErrors }))
+          // report to form
+          onStatusChange?.(allErrors.length === 0 ? ValidationStatus.success : ValidationStatus.failure)
 
           // user edits that haven't been incorporated into form
           setLastUserEdits(remainingEdits)
@@ -554,7 +551,7 @@ export function SyncEditor(props: SyncEditorProps): JSX.Element {
             setCustomValidationErrors(customErrors)
           }
           setEditorHasErrors(editorHasErrors)
-          setStatusChanges(cloneDeep({ changes, redactedChange: change, errors: allErrors }))
+          onStatusChange?.(allErrors.length === 0 ? ValidationStatus.success : ValidationStatus.failure)
 
           // decorate errors, changes
           const squigglyTooltips = decorate(
@@ -610,38 +607,18 @@ export function SyncEditor(props: SyncEditorProps): JSX.Element {
       showSecrets,
       syncs,
       xreferences,
+      onStatusChange,
     ]
   )
 
-  const debouncedEditorChange = useMemo(() => debounce(editorChanged, 300), [editorChanged])
-
-  useEffect(() => {
-    return () => {
-      debouncedEditorChange.cancel()
-    }
-  }, [debouncedEditorChange])
-
   const editorChange = useCallback<ChangeHandler>(
     (value, e) => {
-      debouncedEditorChange(value, e)
+      onStatusChange?.(ValidationStatus.pending)
+      editorChanged(value, e)
     },
-    [debouncedEditorChange]
+    [editorChanged, onStatusChange]
   )
 
-  // report errors/user edits to form
-  useEffect(() => {
-    if (statusChanges && onStatusChange && editor && monaco) {
-      const { changes, errors, redactedChange } = statusChanges
-
-      // report just errors and user changes
-      onStatusChange({
-        warnings: formatErrors(errors, ErrorType.warning),
-        errors: formatErrors(errors, ErrorType.error),
-        changes: formatChanges(editor, monaco, changes, redactedChange, syncs),
-      })
-    }
-  }, [editor, monaco, onStatusChange, statusChanges, syncs])
-
   /* eslint-enable react-hooks/exhaustive-deps */
   const toolbarControls = useMemo(
     () => {
@@ -782,9 +759,6 @@ export function SyncEditor(props: SyncEditorProps): JSX.Element {
 
   return (
     <div ref={pageRef} className="sync-editor__container">
-      {editorHasErrors && (
-        <Alert variant="danger" title={t('Form edits will be ignored until YAML syntax errors are fixed.')} />
-      )}
       <CodeEditor
         isLineNumbersVisible={true}
         isReadOnly={readonly}
diff --git a/frontend/src/lib/wizardStrings.ts b/frontend/src/lib/wizardStrings.ts
index 872ec73642c..a3433352f4c 100644
--- a/frontend/src/lib/wizardStrings.ts
+++ b/frontend/src/lib/wizardStrings.ts
@@ -26,7 +26,9 @@ export const useWizardStrings = (
       backButtonText: t('Back'),
       cancelButtonText: t('Cancel'),
       nextButtonText: t('Next'),
-      fixValidationErrorsMsg: t('Please fix validation errors'),
+      fixValidationErrorsMsg: t('Fix validation errors'),
+      fixEditorValidationErrorsMsg: t('Fix editor syntax errors'),
+      waitforEditorValidationErrorsMsg: t('Wait for editor syntax check'),
       submitText: t('Submit'),
       submittingText: t('Submitting'),
       moreInfo: t('More info'),
diff --git a/frontend/src/routes/Applications/CreateApplication/CreateApplicationArgo.tsx b/frontend/src/routes/Applications/CreateApplication/CreateApplicationArgo.tsx
index 11a31731923..e05d5cee4de 100644
--- a/frontend/src/routes/Applications/CreateApplication/CreateApplicationArgo.tsx
+++ b/frontend/src/routes/Applications/CreateApplication/CreateApplicationArgo.tsx
@@ -1,12 +1,12 @@
 /* Copyright Contributors to the Open Cluster Management project */
 
-import { useData, useItem } from '@patternfly-labs/react-form-wizard'
+import { EditorValidationStatus, useData, useEditorValidationStatus, useItem } from '@patternfly-labs/react-form-wizard'
 import { ArgoWizard } from '../../../wizards/Argo/ArgoWizard'
 import { AcmToastContext } from '../../../ui-components'
 import { useContext, useEffect, useState } from 'react'
 import { generatePath, useNavigate } from 'react-router-dom-v5-compat'
 import { useRecoilValue, useSharedAtoms, useSharedSelectors } from '../../../shared-recoil'
-import { SyncEditor } from '../../../components/SyncEditor/SyncEditor'
+import { SyncEditor, ValidationStatus } from '../../../components/SyncEditor/SyncEditor'
 import { useTranslation } from '../../../lib/acm-i18next'
 import { isType } from '../../../lib/is-type'
 import { NavigationPath } from '../../../NavigationPath'
@@ -43,6 +43,7 @@ export function GetGitOpsClusters(gitOpsClusters: GitOpsCluster[]) {
 export function WizardSyncEditor() {
   const resources = useItem() // Wizard framework sets this context
   const { update } = useData() // Wizard framework sets this context
+  const { setEditorValidationStatus } = useEditorValidationStatus()
   const { t } = useTranslation()
   return (
     <SyncEditor
@@ -53,6 +54,9 @@ export function WizardSyncEditor() {
       onEditorChange={(changes: { resources: any[] }): void => {
         update(changes?.resources)
       }}
+      onStatusChange={(editorStatus: ValidationStatus): void => {
+        setEditorValidationStatus(editorStatus as unknown as EditorValidationStatus)
+      }}
     />
   )
 }
diff --git a/frontend/src/routes/Applications/CreateApplication/CreateApplicationArgoPullModel.tsx b/frontend/src/routes/Applications/CreateApplication/CreateApplicationArgoPullModel.tsx
index 28f3005e1ef..3d2ce54b0c9 100644
--- a/frontend/src/routes/Applications/CreateApplication/CreateApplicationArgoPullModel.tsx
+++ b/frontend/src/routes/Applications/CreateApplication/CreateApplicationArgoPullModel.tsx
@@ -1,12 +1,12 @@
 /* Copyright Contributors to the Open Cluster Management project */
 
-import { useData, useItem } from '@patternfly-labs/react-form-wizard'
+import { EditorValidationStatus, useData, useEditorValidationStatus, useItem } from '@patternfly-labs/react-form-wizard'
 import { ArgoWizard } from '../../../wizards/Argo/ArgoWizard'
 import { AcmToastContext } from '../../../ui-components'
 import { useContext, useEffect, useState } from 'react'
 import { generatePath, useNavigate } from 'react-router-dom-v5-compat'
 import { useRecoilValue, useSharedAtoms, useSharedSelectors } from '../../../shared-recoil'
-import { SyncEditor } from '../../../components/SyncEditor/SyncEditor'
+import { SyncEditor, ValidationStatus } from '../../../components/SyncEditor/SyncEditor'
 import { useTranslation } from '../../../lib/acm-i18next'
 import { isType } from '../../../lib/is-type'
 import { NavigationPath } from '../../../NavigationPath'
@@ -43,6 +43,7 @@ export function GetGitOpsClusters(gitOpsClusters: GitOpsCluster[]) {
 export function WizardSyncEditor() {
   const resources = useItem() // Wizard framework sets this context
   const { update } = useData() // Wizard framework sets this context
+  const { setEditorValidationStatus } = useEditorValidationStatus()
   const { t } = useTranslation()
   return (
     <SyncEditor
@@ -53,6 +54,9 @@ export function WizardSyncEditor() {
       onEditorChange={(changes: { resources: any[] }): void => {
         update(changes?.resources)
       }}
+      onStatusChange={(editorStatus: ValidationStatus): void => {
+        setEditorValidationStatus(editorStatus as unknown as EditorValidationStatus)
+      }}
     />
   )
 }
diff --git a/frontend/src/routes/Applications/CreateApplication/EditArgoApplicationSet.tsx b/frontend/src/routes/Applications/CreateApplication/EditArgoApplicationSet.tsx
index 32b669f4cb6..b709951458e 100644
--- a/frontend/src/routes/Applications/CreateApplication/EditArgoApplicationSet.tsx
+++ b/frontend/src/routes/Applications/CreateApplication/EditArgoApplicationSet.tsx
@@ -1,12 +1,12 @@
 /* Copyright Contributors to the Open Cluster Management project */
 
-import { useData, useItem } from '@patternfly-labs/react-form-wizard'
+import { EditorValidationStatus, useData, useEditorValidationStatus, useItem } from '@patternfly-labs/react-form-wizard'
 import { ArgoWizard } from '../../../wizards/Argo/ArgoWizard'
 import { useContext, useEffect, useState } from 'react'
 import { useParams, useNavigate, PathParam, generatePath } from 'react-router-dom-v5-compat'
 import { useRecoilValue, useSharedAtoms, useSharedSelectors } from '../../../shared-recoil'
 import { LoadingPage } from '../../../components/LoadingPage'
-import { SyncEditor } from '../../../components/SyncEditor/SyncEditor'
+import { SyncEditor, ValidationStatus } from '../../../components/SyncEditor/SyncEditor'
 import { useTranslation } from '../../../lib/acm-i18next'
 import { isType } from '../../../lib/is-type'
 import { useSearchParams } from '../../../lib/search'
@@ -33,6 +33,7 @@ import { useTimezones } from '../../../hooks/useTimezone'
 export function WizardSyncEditor() {
   const resources = useItem() // Wizard framework sets this context
   const { update } = useData() // Wizard framework sets this context
+  const { setEditorValidationStatus } = useEditorValidationStatus()
   const { t } = useTranslation()
   return (
     <SyncEditor
@@ -43,6 +44,9 @@ export function WizardSyncEditor() {
       onEditorChange={(changes: { resources: any[] }): void => {
         update(changes?.resources)
       }}
+      onStatusChange={(editorStatus: ValidationStatus): void => {
+        setEditorValidationStatus(editorStatus as unknown as EditorValidationStatus)
+      }}
     />
   )
 }
diff --git a/frontend/src/routes/Governance/policies/CreatePolicy.tsx b/frontend/src/routes/Governance/policies/CreatePolicy.tsx
index 7fce81f8b69..05dc67355ac 100644
--- a/frontend/src/routes/Governance/policies/CreatePolicy.tsx
+++ b/frontend/src/routes/Governance/policies/CreatePolicy.tsx
@@ -1,11 +1,11 @@
 /* Copyright Contributors to the Open Cluster Management project */
-import { useData, useItem } from '@patternfly-labs/react-form-wizard'
+import { useData, useItem, useEditorValidationStatus, EditorValidationStatus } from '@patternfly-labs/react-form-wizard'
 import { PolicyWizard } from '../../../wizards/Governance/Policy/PolicyWizard'
 import { AcmToastContext } from '../../../ui-components'
 import { useContext, useEffect, useMemo, useState } from 'react'
 import { generatePath, useNavigate } from 'react-router-dom-v5-compat'
 import { useRecoilValue, useSharedAtoms } from '../../../shared-recoil'
-import { SyncEditor } from '../../../components/SyncEditor/SyncEditor'
+import { SyncEditor, ValidationStatus } from '../../../components/SyncEditor/SyncEditor'
 import { useTranslation } from '../../../lib/acm-i18next'
 import { NavigationPath } from '../../../NavigationPath'
 import { IResource, Policy, PolicyKind } from '../../../resources'
@@ -16,6 +16,8 @@ import { LostChangesContext } from '../../../components/LostChanges'
 export function WizardSyncEditor() {
   const resources = useItem() // Wizard framework sets this context
   const { update } = useData() // Wizard framework sets this context
+  const { setEditorValidationStatus } = useEditorValidationStatus()
+
   const { t } = useTranslation()
   return (
     <SyncEditor
@@ -26,6 +28,9 @@ export function WizardSyncEditor() {
       onEditorChange={(changes: { resources: any[] }): void => {
         update(changes?.resources)
       }}
+      onStatusChange={(editorStatus: ValidationStatus): void => {
+        setEditorValidationStatus(editorStatus as unknown as EditorValidationStatus)
+      }}
       editableUidSiblings={true}
       autoCreateNs
     />
diff --git a/frontend/src/routes/Governance/policies/CreatePolicyAutomation.tsx b/frontend/src/routes/Governance/policies/CreatePolicyAutomation.tsx
index 166c58c5536..5eff41bc189 100644
--- a/frontend/src/routes/Governance/policies/CreatePolicyAutomation.tsx
+++ b/frontend/src/routes/Governance/policies/CreatePolicyAutomation.tsx
@@ -1,11 +1,11 @@
 /* Copyright Contributors to the Open Cluster Management project */
-import { useData, useItem } from '@patternfly-labs/react-form-wizard'
+import { EditorValidationStatus, useData, useEditorValidationStatus, useItem } from '@patternfly-labs/react-form-wizard'
 import { PolicyAutomationWizard } from '../../../wizards/Governance/PolicyAutomation/PolicyAutomationWizard'
 import { AcmToastContext } from '../../../ui-components'
 import { useContext, useMemo } from 'react'
 import { useParams, useNavigate, PathParam, useLocation, generatePath } from 'react-router-dom-v5-compat'
 import { useRecoilValue, useSharedAtoms } from '../../../shared-recoil'
-import { SyncEditor } from '../../../components/SyncEditor/SyncEditor'
+import { SyncEditor, ValidationStatus } from '../../../components/SyncEditor/SyncEditor'
 import { useTranslation } from '../../../lib/acm-i18next'
 import { NavigationPath } from '../../../NavigationPath'
 import { PolicyAutomationApiVersion, PolicyAutomationKind, Secret } from '../../../resources'
@@ -17,6 +17,7 @@ import { LostChangesContext } from '../../../components/LostChanges'
 export function WizardSyncEditor() {
   const resources = useItem() // Wizard framework sets this context
   const { update } = useData() // Wizard framework sets this context
+  const { setEditorValidationStatus } = useEditorValidationStatus()
   const { t } = useTranslation()
   return (
     <SyncEditor
@@ -27,6 +28,9 @@ export function WizardSyncEditor() {
       onEditorChange={(changes: { resources: any[] }): void => {
         update(changes?.resources)
       }}
+      onStatusChange={(editorStatus: ValidationStatus): void => {
+        setEditorValidationStatus(editorStatus as unknown as EditorValidationStatus)
+      }}
       autoCreateNs
     />
   )
diff --git a/frontend/src/routes/Governance/policies/EditPolicy.tsx b/frontend/src/routes/Governance/policies/EditPolicy.tsx
index 06bb8640e07..917f81ea3c7 100644
--- a/frontend/src/routes/Governance/policies/EditPolicy.tsx
+++ b/frontend/src/routes/Governance/policies/EditPolicy.tsx
@@ -1,11 +1,17 @@
 /* Copyright Contributors to the Open Cluster Management project */
-import { EditMode, useData, useItem } from '@patternfly-labs/react-form-wizard'
+import {
+  EditMode,
+  EditorValidationStatus,
+  useData,
+  useEditorValidationStatus,
+  useItem,
+} from '@patternfly-labs/react-form-wizard'
 import { PolicyWizard } from '../../../wizards/Governance/Policy/PolicyWizard'
 import { useContext, useEffect, useMemo, useState } from 'react'
 import { useParams, useNavigate, generatePath } from 'react-router-dom-v5-compat'
 import { useRecoilValue, useSharedAtoms } from '../../../shared-recoil'
 import { LoadingPage } from '../../../components/LoadingPage'
-import { SyncEditor } from '../../../components/SyncEditor/SyncEditor'
+import { SyncEditor, ValidationStatus } from '../../../components/SyncEditor/SyncEditor'
 import { useTranslation } from '../../../lib/acm-i18next'
 import { useSearchParams } from '../../../lib/search'
 import { NavigationPath } from '../../../NavigationPath'
@@ -24,6 +30,7 @@ import { LostChangesContext } from '../../../components/LostChanges'
 export function WizardSyncEditor() {
   const resources = useItem() // Wizard framework sets this context
   const { update } = useData() // Wizard framework sets this context
+  const { setEditorValidationStatus } = useEditorValidationStatus()
   const { t } = useTranslation()
   return (
     <SyncEditor
@@ -36,6 +43,9 @@ export function WizardSyncEditor() {
       onEditorChange={(changes: { resources: any[] }): void => {
         update(changes?.resources)
       }}
+      onStatusChange={(editorStatus: ValidationStatus): void => {
+        setEditorValidationStatus(editorStatus as unknown as EditorValidationStatus)
+      }}
     />
   )
 }
diff --git a/frontend/src/routes/Governance/policies/EditPolicyAutomation.tsx b/frontend/src/routes/Governance/policies/EditPolicyAutomation.tsx
index 5426c45bc32..afb857921bb 100644
--- a/frontend/src/routes/Governance/policies/EditPolicyAutomation.tsx
+++ b/frontend/src/routes/Governance/policies/EditPolicyAutomation.tsx
@@ -1,12 +1,18 @@
 /* Copyright Contributors to the Open Cluster Management project */
-import { EditMode, useData, useItem } from '@patternfly-labs/react-form-wizard'
+import {
+  EditMode,
+  EditorValidationStatus,
+  useData,
+  useEditorValidationStatus,
+  useItem,
+} from '@patternfly-labs/react-form-wizard'
 import { PolicyAutomationWizard } from '../../../wizards/Governance/PolicyAutomation/PolicyAutomationWizard'
 import { AcmToastContext } from '../../../ui-components'
 import { useContext, useMemo } from 'react'
 import { useParams, useNavigate, generatePath, PathParam, useLocation } from 'react-router-dom-v5-compat'
 import { useRecoilValue, useSharedAtoms } from '../../../shared-recoil'
 import { LoadingPage } from '../../../components/LoadingPage'
-import { SyncEditor } from '../../../components/SyncEditor/SyncEditor'
+import { SyncEditor, ValidationStatus } from '../../../components/SyncEditor/SyncEditor'
 import { useTranslation } from '../../../lib/acm-i18next'
 import { NavigationPath } from '../../../NavigationPath'
 import { PolicyAutomation, Secret } from '../../../resources'
@@ -18,6 +24,7 @@ import { LostChangesContext } from '../../../components/LostChanges'
 export function WizardSyncEditor() {
   const resources = useItem() // Wizard framework sets this context
   const { update } = useData() // Wizard framework sets this context
+  const { setEditorValidationStatus } = useEditorValidationStatus()
   const { t } = useTranslation()
   return (
     <SyncEditor
@@ -29,6 +36,9 @@ export function WizardSyncEditor() {
       onEditorChange={(changes: { resources: any[] }): void => {
         update(changes?.resources)
       }}
+      onStatusChange={(editorStatus: ValidationStatus): void => {
+        setEditorValidationStatus(editorStatus as unknown as EditorValidationStatus)
+      }}
     />
   )
 }
diff --git a/frontend/src/routes/Governance/policy-sets/CreatePolicySet.tsx b/frontend/src/routes/Governance/policy-sets/CreatePolicySet.tsx
index 3e4e2e9c611..f35b60eb502 100644
--- a/frontend/src/routes/Governance/policy-sets/CreatePolicySet.tsx
+++ b/frontend/src/routes/Governance/policy-sets/CreatePolicySet.tsx
@@ -1,11 +1,11 @@
 /* Copyright Contributors to the Open Cluster Management project */
-import { useData, useItem } from '@patternfly-labs/react-form-wizard'
+import { EditorValidationStatus, useData, useEditorValidationStatus, useItem } from '@patternfly-labs/react-form-wizard'
 import { PolicySetWizard } from '../../../wizards/Governance/PolicySet/PolicySetWizard'
 import { AcmToastContext } from '../../../ui-components'
 import { useContext, useMemo } from 'react'
 import { useNavigate } from 'react-router-dom-v5-compat'
 import { useRecoilValue, useSharedAtoms } from '../../../shared-recoil'
-import { SyncEditor } from '../../../components/SyncEditor/SyncEditor'
+import { SyncEditor, ValidationStatus } from '../../../components/SyncEditor/SyncEditor'
 import { useTranslation } from '../../../lib/acm-i18next'
 import { NavigationPath } from '../../../NavigationPath'
 import { IResource, PolicySetKind } from '../../../resources'
@@ -16,6 +16,7 @@ import { LostChangesContext } from '../../../components/LostChanges'
 export function WizardSyncEditor() {
   const resources = useItem() // Wizard framework sets this context
   const { update } = useData() // Wizard framework sets this context
+  const { setEditorValidationStatus } = useEditorValidationStatus()
   const { t } = useTranslation()
   return (
     <SyncEditor
@@ -26,6 +27,9 @@ export function WizardSyncEditor() {
       onEditorChange={(changes: { resources: any[] }): void => {
         update(changes?.resources)
       }}
+      onStatusChange={(editorStatus: ValidationStatus): void => {
+        setEditorValidationStatus(editorStatus as unknown as EditorValidationStatus)
+      }}
       autoCreateNs
     />
   )
diff --git a/frontend/src/routes/Governance/policy-sets/EditPolicySet.tsx b/frontend/src/routes/Governance/policy-sets/EditPolicySet.tsx
index 6c69f762606..690f273e216 100644
--- a/frontend/src/routes/Governance/policy-sets/EditPolicySet.tsx
+++ b/frontend/src/routes/Governance/policy-sets/EditPolicySet.tsx
@@ -1,5 +1,11 @@
 /* Copyright Contributors to the Open Cluster Management project */
-import { EditMode, useData, useItem } from '@patternfly-labs/react-form-wizard'
+import {
+  EditMode,
+  EditorValidationStatus,
+  useData,
+  useEditorValidationStatus,
+  useItem,
+} from '@patternfly-labs/react-form-wizard'
 import { IResource, PolicySetKind } from '../../../resources'
 import { PathParam, useNavigate, useParams } from 'react-router-dom-v5-compat'
 import { getPlacementBindingsForResource, getPlacementsForResource } from '../common/util'
@@ -11,7 +17,7 @@ import { LoadingPage } from '../../../components/LoadingPage'
 import { LostChangesContext } from '../../../components/LostChanges'
 import { NavigationPath } from '../../../NavigationPath'
 import { PolicySetWizard } from '../../../wizards/Governance/PolicySet/PolicySetWizard'
-import { SyncEditor } from '../../../components/SyncEditor/SyncEditor'
+import { SyncEditor, ValidationStatus } from '../../../components/SyncEditor/SyncEditor'
 import { reconcileResources } from '../../../resources/utils'
 import schema from './schema.json'
 import { useTranslation } from '../../../lib/acm-i18next'
@@ -19,6 +25,7 @@ import { useTranslation } from '../../../lib/acm-i18next'
 export function WizardSyncEditor() {
   const resources = useItem() // Wizard framework sets this context
   const { update } = useData() // Wizard framework sets this context
+  const { setEditorValidationStatus } = useEditorValidationStatus()
   const { t } = useTranslation()
   return (
     <SyncEditor
@@ -31,6 +38,9 @@ export function WizardSyncEditor() {
       onEditorChange={(changes: { resources: any[] }): void => {
         update(changes?.resources)
       }}
+      onStatusChange={(editorStatus: ValidationStatus): void => {
+        setEditorValidationStatus(editorStatus as unknown as EditorValidationStatus)
+      }}
     />
   )
 }
diff --git a/frontend/src/routes/Infrastructure/Clusters/ManagedClusters/ImportCluster/ImportCluster.tsx b/frontend/src/routes/Infrastructure/Clusters/ManagedClusters/ImportCluster/ImportCluster.tsx
index 131986512ff..c7faad8078d 100644
--- a/frontend/src/routes/Infrastructure/Clusters/ManagedClusters/ImportCluster/ImportCluster.tsx
+++ b/frontend/src/routes/Infrastructure/Clusters/ManagedClusters/ImportCluster/ImportCluster.tsx
@@ -21,7 +21,7 @@ import {
 import { cloneDeep, get, groupBy, isEqual, pick } from 'lodash'
 import { Dispatch, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useReducer, useState } from 'react'
 import { Link, generatePath, useNavigate } from 'react-router-dom-v5-compat'
-import { SyncEditor } from '../../../../../components/SyncEditor/SyncEditor'
+import { SyncEditor, ValidationStatus } from '../../../../../components/SyncEditor/SyncEditor'
 import { useTranslation } from '../../../../../lib/acm-i18next'
 import { DOC_LINKS } from '../../../../../lib/doc-util'
 import { PluginContext } from '../../../../../lib/PluginContext'
@@ -64,6 +64,8 @@ import {
   WizTextArea,
   WizTextInput,
   useSetHasValue,
+  useEditorValidationStatus,
+  EditorValidationStatus,
 } from '@patternfly-labs/react-form-wizard'
 import { TemplateLinkOut, TemplateSummaryExpandable } from '../../../../../components/TemplateSummaryModal'
 import { ExternalLinkAltIcon } from '@patternfly/react-icons'
@@ -511,6 +513,7 @@ export default function ImportClusterPage() {
 
   function WizardSyncEditor() {
     const resources = useItem() // Wizard framework sets this context
+    const { setEditorValidationStatus } = useEditorValidationStatus()
     const { update } = useData() // Wizard framework sets this context
 
     return (
@@ -530,6 +533,9 @@ export default function ImportClusterPage() {
         onEditorChange={(changes: { resources: any[] }): void => {
           update(changes?.resources)
         }}
+        onStatusChange={(editorStatus: ValidationStatus): void => {
+          setEditorValidationStatus(editorStatus as unknown as EditorValidationStatus)
+        }}
       />
     )
   }
diff --git a/frontend/src/wizards/Argo/CreateArgoResources.tsx b/frontend/src/wizards/Argo/CreateArgoResources.tsx
index 0591c2dec06..7025dc55a9b 100644
--- a/frontend/src/wizards/Argo/CreateArgoResources.tsx
+++ b/frontend/src/wizards/Argo/CreateArgoResources.tsx
@@ -1,10 +1,10 @@
 /* Copyright Contributors to the Open Cluster Management project */
 
-import { useData, useItem } from '@patternfly-labs/react-form-wizard'
+import { EditorValidationStatus, useData, useEditorValidationStatus, useItem } from '@patternfly-labs/react-form-wizard'
 import { useContext, useState } from 'react'
 import { AcmDataFormPage } from '../../components/AcmDataForm'
 import { FormData } from '../../components/AcmFormData'
-import { SyncEditor } from '../../components/SyncEditor/SyncEditor'
+import { SyncEditor, ValidationStatus } from '../../components/SyncEditor/SyncEditor'
 import { useTranslation } from '../../lib/acm-i18next'
 import {
   GitOpsCluster,
@@ -32,6 +32,7 @@ export interface ICreateArgoResourcesModalProps {
 export function WizardSyncEditor() {
   const resources = useItem() // Wizard framework sets this context
   const { update } = useData() // Wizard framework sets this context
+  const { setEditorValidationStatus } = useEditorValidationStatus()
   const { t } = useTranslation()
   return (
     <SyncEditor
@@ -42,6 +43,9 @@ export function WizardSyncEditor() {
       onEditorChange={(changes: { resources: any[] }): void => {
         update(changes?.resources)
       }}
+      onStatusChange={(editorStatus: ValidationStatus): void => {
+        setEditorValidationStatus(editorStatus as unknown as EditorValidationStatus)
+      }}
     />
   )
 }


Reviews:
user: login: KevinFCormier
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T19:09:22Z
user: login: oafischer
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T08:58:56Z
user: login: jeswanke
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T15:00:47Z
user: login: oafischer
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-15T15:39:03Z

Review comments:
diff_hunk: @@ -3385,6 +3386,7 @@
   "You don't have any policy sets": "You don't have any policy sets",
   "You don't have any subscriptions": "You don't have any subscriptions",
   "You must fill out all required fields before you can proceed.": "You must fill out all required fields before you can proceed.",
+  "You must fix editor syntax errors.": "You must fix editor syntax errors.",
path: frontend/public/locales/en/translation.json
user: login: KevinFCormier
body: @jeswanke Why do we have two versions of this string. This one, and the polite one?

@oafischer Can you weigh in on whether we should use "please" in these cases?

@jeswanke Some screenshots would be helpful to show Oliver (and any PR reviewer) where these strings appear. Thanks.
created_at: 2025-05-14T19:09:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -3385,6 +3386,7 @@
   "You don't have any policy sets": "You don't have any policy sets",
   "You don't have any subscriptions": "You don't have any subscriptions",
   "You must fill out all required fields before you can proceed.": "You must fill out all required fields before you can proceed.",
+  "You must fix editor syntax errors.": "You must fix editor syntax errors.",
path: frontend/public/locales/en/translation.json
user: login: oafischer
body: We should generally avoid using "please". I'll take a look at the screenshots as well once they are available.
created_at: 2025-05-15T08:58:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -3385,6 +3386,7 @@
   "You don't have any policy sets": "You don't have any policy sets",
   "You don't have any subscriptions": "You don't have any subscriptions",
   "You must fill out all required fields before you can proceed.": "You must fill out all required fields before you can proceed.",
+  "You must fix editor syntax errors.": "You must fix editor syntax errors.",
path: frontend/public/locales/en/translation.json
user: login: jeswanke
body: @oafischer here's the two spots-- so one wizard is polite and the other isn't:
![CleanShot 2025-05-15 at 10 58 44@2x](https://github.com/user-attachments/assets/fa2e7cb9-1d34-43cb-a855-e3f907b83260)
![CleanShot 2025-05-15 at 11 00 30@2x](https://github.com/user-attachments/assets/ba4de5dc-02aa-4be1-8fda-6a6babb6203d)

created_at: 2025-05-15T15:00:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -3385,6 +3386,7 @@
   "You don't have any policy sets": "You don't have any policy sets",
   "You don't have any subscriptions": "You don't have any subscriptions",
   "You must fill out all required fields before you can proceed.": "You must fill out all required fields before you can proceed.",
+  "You must fix editor syntax errors.": "You must fix editor syntax errors.",
path: frontend/public/locales/en/translation.json
user: login: oafischer
body: We can always refer back to [Patternfly](https://www.patternfly.org/ux-writing/error-messages). We should remove the please and maybe we can write something like "Before you continue, fix editor syntax errors." 
created_at: 2025-05-15T15:39:03Z
author_association: CONTRIBUTOR

PR comments:
user: login: jeswanke
created_at: 2025-05-09T17:55:08Z
body: /test unit-tests-sonarcloud
author_association: CONTRIBUTOR
user: login: sonarqubecloud[bot]
created_at: 2025-05-20T16:10:23Z
body: ## [![Quality Gate Failed](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/QualityGateBadge/qg-failed-20px.png 'Quality Gate Failed')](https://sonarcloud.io/dashboard?id=open-cluster-management_console&pullRequest=4480) **Quality Gate failed**  
Failed conditions  
![](https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/failed-16px.png '') [64.2% Coverage on New Code](https://sonarcloud.io/component_measures?id=open-cluster-management_console&pullRequest=4480&metric=new_coverage&view=list) (required ≥ 70%)  
  
[See analysis details on SonarQube Cloud](https://sonarcloud.io/dashboard?id=open-cluster-management_console&pullRequest=4480)


author_association: NONE
user: login: KevinFCormier
created_at: 2025-05-21T15:24:58Z
body: /override unit-tests-sonarcloud
author_association: CONTRIBUTOR
user: login: openshift-ci[bot]
created_at: 2025-05-21T15:25:02Z
body: @KevinFCormier: /override requires failed status contexts, check run or a prowjob name to operate on.
The following unknown contexts/checkruns were given:
 - `unit-tests-sonarcloud`

Only the following failed contexts/checkruns were expected:
 - `Red Hat Konflux / acm-214-enterprise-contract-registry-standard / console-acm-214`
 - `Red Hat Konflux / mce-29-enterprise-contract-registry-standard / console-mce-mce-29`
 - `ci/prow/check`
 - `ci/prow/images`
 - `ci/prow/pr-image-mirror`
 - `ci/prow/pr-image-mirror-mce`
 - `ci/prow/unit-tests-sonarcloud`
 - `pull-ci-stolostron-console-backplane-2.0-images`
 - `pull-ci-stolostron-console-backplane-2.0-pr-image-mirror-mce`
 - `pull-ci-stolostron-console-backplane-2.3-check`
 - `pull-ci-stolostron-console-backplane-2.3-unit-tests-sonarcloud`
 - `pull-ci-stolostron-console-main-pr-image-mirror`
 - `tide`

If you are trying to override a checkrun that has a space in it, you must put a double quote on the context.


<details>

In response to [this](https://github.com/stolostron/console/pull/4480#issuecomment-2898368785):

>/override unit-tests-sonarcloud


Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes-sigs/prow](https://github.com/kubernetes-sigs/prow/issues/new?title=Prow%20issue:) repository.
</details>
author_association: NONE
user: login: KevinFCormier
created_at: 2025-05-21T15:25:17Z
body: /override "ci/prow/unit-tests-sonarcloud"
author_association: CONTRIBUTOR
user: login: openshift-ci[bot]
created_at: 2025-05-21T15:25:21Z
body: @KevinFCormier: Overrode contexts on behalf of KevinFCormier: ci/prow/unit-tests-sonarcloud

<details>

In response to [this](https://github.com/stolostron/console/pull/4480#issuecomment-2898369971):

>/override "ci/prow/unit-tests-sonarcloud"


Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes-sigs/prow](https://github.com/kubernetes-sigs/prow/issues/new?title=Prow%20issue:) repository.
</details>
author_association: NONE
user: login: KevinFCormier
created_at: 2025-05-21T15:26:54Z
body: /retest
author_association: CONTRIBUTOR
user: login: KevinFCormier
created_at: 2025-05-21T16:59:38Z
body: /lgtm
author_association: CONTRIBUTOR
user: login: openshift-ci[bot]
created_at: 2025-05-21T16:59:48Z
body: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/stolostron/console/pull/4480#" title="Author self-approved">jeswanke</a>*, *<a href="https://github.com/stolostron/console/pull/4480#issuecomment-2898637089" title="LGTM">KevinFCormier</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=stolostron%2Fconsole).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/stolostron/console/blob/main/OWNERS)~~ [KevinFCormier,jeswanke]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
author_association: NONE