From cd0ed0a43411668075e0e4e1fcf2e7eebc006e21 Mon Sep 17 00:00:00 2001 From: jvega190 Date: Mon, 17 Aug 2026 15:26:29 -0600 Subject: [PATCH 1/4] [8869] Create content via Form should provide a default comment --- .../src/components/FormsEngine/FormsEngine.tsx | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/studio-ui/ui/app/src/components/FormsEngine/FormsEngine.tsx b/studio-ui/ui/app/src/components/FormsEngine/FormsEngine.tsx index cd52de559..5d1063df6 100644 --- a/studio-ui/ui/app/src/components/FormsEngine/FormsEngine.tsx +++ b/studio-ui/ui/app/src/components/FormsEngine/FormsEngine.tsx @@ -286,6 +286,7 @@ function FormBootstrap(props: FormsEngineProps) { const triggerReload = useCallback(() => setReloadNonce((nonce) => nonce + 1), []); const effectiveUpdatePath = renamedPath ?? update?.path; const customControls = useSelection((state) => state.uiConfig.controls); + const { formatMessage } = useIntl(); const contextApi = useMemo(() => { const getInitialValues = () => stableFormContextRef.current.originalValues; @@ -491,7 +492,9 @@ function FormBootstrap(props: FormsEngineProps) { lockResult: lockResultAtom, readonly: atom(false), expandedStateBySectionId: buildSectionExpandedStateAtoms(contentType.sections), - fileName: atom('') + fileName: atom(''), + // Default version comment for new content. + versionComment: atom(formatMessage({ defaultMessage: 'Created content' })) }); const contentObject = createObjectWithSystemProps(contentType); const values = createParsedValuesObject( @@ -718,6 +721,7 @@ function FormOrchestrator(props: FormsEngineProps) { const effectRefs = useUpdateRefs({ fieldsToRender, versionCommentAtom: stableFormContext.atoms.versionComment, + fileNameAtom: stableFormContext.atoms.fileName, lockStatus }); const [collapseHeader, setCollapseHeader] = useState(false); @@ -752,9 +756,16 @@ function FormOrchestrator(props: FormsEngineProps) { // String-type fields have auto-rollback detection; the fieldUpdates$ will emit anyway. Checking if the fieldId // emitted is in changedFieldIds should tell if the field was rolled back. setHasPendingChanges(changedFieldIds.size > 0); - // No comment generation for content creation. - if (isCreateMode) return; const versionCommentAtom = effectRefs.current.versionCommentAtom; + // Create mode uses a fixed default comment; only update when the page URL (file-name) changes. + if (isCreateMode) { + if (changedFieldIds.has(XmlKeys.fileName)) { + const pageUrl = store.get(effectRefs.current.fileNameAtom); + if (pageUrl) + store.set(versionCommentAtom, formatMessage({ defaultMessage: 'Created {pageUrl}' }, { pageUrl })); + } + return; + } const newMessage = generateDefaultChangesComment( contentType.fields, effectRefs.current.fieldsToRender, From 4ab75fdefba6bad28d52abc1c98da19ddb5574bf Mon Sep 17 00:00:00 2001 From: jvega190 Date: Mon, 17 Aug 2026 15:34:58 -0600 Subject: [PATCH 2/4] Reset the version comment when pageUrl is empty. --- .../src/components/FormsEngine/FormsEngine.tsx | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/studio-ui/ui/app/src/components/FormsEngine/FormsEngine.tsx b/studio-ui/ui/app/src/components/FormsEngine/FormsEngine.tsx index 5d1063df6..12b626526 100644 --- a/studio-ui/ui/app/src/components/FormsEngine/FormsEngine.tsx +++ b/studio-ui/ui/app/src/components/FormsEngine/FormsEngine.tsx @@ -762,7 +762,12 @@ function FormOrchestrator(props: FormsEngineProps) { if (changedFieldIds.has(XmlKeys.fileName)) { const pageUrl = store.get(effectRefs.current.fileNameAtom); if (pageUrl) - store.set(versionCommentAtom, formatMessage({ defaultMessage: 'Created {pageUrl}' }, { pageUrl })); + store.set( + versionCommentAtom, + pageUrl + ? formatMessage({ defaultMessage: 'Created {pageUrl}' }, { pageUrl }) + : formatMessage({ defaultMessage: 'Created content' }) + ); } return; } @@ -777,7 +782,16 @@ function FormOrchestrator(props: FormsEngineProps) { return () => { sub.unsubscribe(); }; - }, [changedFieldIds, contentType.fields, effectRefs, setHasPendingChanges, fieldUpdates$, store, isCreateMode]); + }, [ + changedFieldIds, + contentType.fields, + effectRefs, + setHasPendingChanges, + fieldUpdates$, + store, + isCreateMode, + formatMessage + ]); const sourceMapPaths = useMemo(() => Object.values(sourceMap ?? []).sort(), [sourceMap]); useFetchContentItems(sourceMapPaths); From 6dd95a315694dcbd48328c6ed8bea7a4ab0d2687 Mon Sep 17 00:00:00 2001 From: jvega190 Date: Mon, 17 Aug 2026 15:45:24 -0600 Subject: [PATCH 3/4] Remove pageUrl validation --- .../app/src/components/FormsEngine/FormsEngine.tsx | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/studio-ui/ui/app/src/components/FormsEngine/FormsEngine.tsx b/studio-ui/ui/app/src/components/FormsEngine/FormsEngine.tsx index 12b626526..5c54e95c6 100644 --- a/studio-ui/ui/app/src/components/FormsEngine/FormsEngine.tsx +++ b/studio-ui/ui/app/src/components/FormsEngine/FormsEngine.tsx @@ -761,13 +761,12 @@ function FormOrchestrator(props: FormsEngineProps) { if (isCreateMode) { if (changedFieldIds.has(XmlKeys.fileName)) { const pageUrl = store.get(effectRefs.current.fileNameAtom); - if (pageUrl) - store.set( - versionCommentAtom, - pageUrl - ? formatMessage({ defaultMessage: 'Created {pageUrl}' }, { pageUrl }) - : formatMessage({ defaultMessage: 'Created content' }) - ); + store.set( + versionCommentAtom, + pageUrl + ? formatMessage({ defaultMessage: 'Created {pageUrl}' }, { pageUrl }) + : formatMessage({ defaultMessage: 'Created content' }) + ); } return; } From 7cdec37f15eea6f6c5603d0d45c6942c825b213e Mon Sep 17 00:00:00 2001 From: jvega190 Date: Mon, 17 Aug 2026 16:44:15 -0600 Subject: [PATCH 4/4] Add generateDefaultCreationComment --- .../components/FormsEngine/FormsEngine.tsx | 27 +++++++----- .../components/FormsEngine/lib/formUtils.tsx | 42 +++++++++++++++++++ 2 files changed, 59 insertions(+), 10 deletions(-) diff --git a/studio-ui/ui/app/src/components/FormsEngine/FormsEngine.tsx b/studio-ui/ui/app/src/components/FormsEngine/FormsEngine.tsx index 5c54e95c6..ebe8f2c48 100644 --- a/studio-ui/ui/app/src/components/FormsEngine/FormsEngine.tsx +++ b/studio-ui/ui/app/src/components/FormsEngine/FormsEngine.tsx @@ -96,6 +96,7 @@ import { displayFormBeingSavedSnack, fetchUpdateRequirements, generateDefaultChangesComment, + generateDefaultCreationComment, getAdditionalFieldsIdsFromDescriptor, resolveControlDescriptors, getCurrentChildFormStateSummary, @@ -104,6 +105,7 @@ import { internalLockContentService, internalUnlockContentService, prepareEmbeddedItemForm, + produceCreationMessage, setFieldAtoms, useUnlockOnClose, useValidateFormProps @@ -494,7 +496,7 @@ function FormBootstrap(props: FormsEngineProps) { expandedStateBySectionId: buildSectionExpandedStateAtoms(contentType.sections), fileName: atom(''), // Default version comment for new content. - versionComment: atom(formatMessage({ defaultMessage: 'Created content' })) + versionComment: atom(produceCreationMessage('', formatMessage)) }); const contentObject = createObjectWithSystemProps(contentType); const values = createParsedValuesObject( @@ -724,6 +726,9 @@ function FormOrchestrator(props: FormsEngineProps) { fileNameAtom: stableFormContext.atoms.fileName, lockStatus }); + // Holds the create mode comment generated last, so that a comment written by the user isn't overwritten. Starts off + // with the default comment the version comment atom was created with. + const lastCreationCommentRef = useRef(produceCreationMessage('', formatMessage)); const [collapseHeader, setCollapseHeader] = useState(false); const [saveAsDraftAction, setSaveAsDraftAction] = useState(false); const [invalidForm, setInvalidForm] = useState(false); @@ -757,16 +762,18 @@ function FormOrchestrator(props: FormsEngineProps) { // emitted is in changedFieldIds should tell if the field was rolled back. setHasPendingChanges(changedFieldIds.size > 0); const versionCommentAtom = effectRefs.current.versionCommentAtom; - // Create mode uses a fixed default comment; only update when the page URL (file-name) changes. + // Create mode bases the comment off of the page URL (file-name) instead of the fields changed. Note that any + // field update re-runs this since every field's validation depends on the file name. if (isCreateMode) { - if (changedFieldIds.has(XmlKeys.fileName)) { - const pageUrl = store.get(effectRefs.current.fileNameAtom); - store.set( - versionCommentAtom, - pageUrl - ? formatMessage({ defaultMessage: 'Created {pageUrl}' }, { pageUrl }) - : formatMessage({ defaultMessage: 'Created content' }) - ); + const newMessage = generateDefaultCreationComment( + store.get(effectRefs.current.fileNameAtom), + store.get(versionCommentAtom).trim(), + lastCreationCommentRef.current, + formatMessage + ); + if (newMessage) { + lastCreationCommentRef.current = newMessage; + store.set(versionCommentAtom, newMessage); } return; } diff --git a/studio-ui/ui/app/src/components/FormsEngine/lib/formUtils.tsx b/studio-ui/ui/app/src/components/FormsEngine/lib/formUtils.tsx index 82f3dec7e..265e91498 100644 --- a/studio-ui/ui/app/src/components/FormsEngine/lib/formUtils.tsx +++ b/studio-ui/ui/app/src/components/FormsEngine/lib/formUtils.tsx @@ -796,6 +796,48 @@ export function generateDefaultChangesComment( return newMessage; } +/** + * Produces the "save comment" for content being created at the supplied page URL (file name). An empty page URL + * produces the generic comment new content forms start off with. + **/ +export function produceCreationMessage(pageUrl: string, formatMessage: IntlShape['formatMessage']): string { + return pageUrl + ? formatMessage({ defaultMessage: 'Created {pageUrl}' }, { pageUrl }) + : formatMessage({ defaultMessage: 'Created content' }); +} + +/** + * Generates the default "save comment" for content being created, based on the page URL (file name) it will be + * created at. + * @param pageUrl The current value of the file name field. + * @param currentMessage The version comment currently held by the form. + * @param lastGeneratedMessage The comment this function generated last, used to detect user input on the comment. + * @returns The new comment, or undefined when the comment should be left untouched. + **/ +export function generateDefaultCreationComment( + pageUrl: string, + currentMessage: string, + lastGeneratedMessage: string, + formatMessage: IntlShape['formatMessage'] +): string | undefined { + const newMessage = pageUrl + ? formatMessage({ defaultMessage: 'Created {pageUrl}' }, { pageUrl }) + : formatMessage({ defaultMessage: 'Created content' }); + if ( + // Nothing to change + currentMessage === newMessage || + // If message is blank, no point in checking if the user has altered the message. + (currentMessage !== '' && + // The version comment has been manually altered by the user (i.e. if the current message isn't the last + // message generated here, we can assume the message has been altered by user input) + currentMessage !== lastGeneratedMessage) + ) { + // Do not set a new message + return; + } + return newMessage; +} + /** * Creates a summary of the state the current stacked form being rendered (last one on the stack) **/