Conversation
Acceptance Criteria QA — ✅ PASS — 4/4 code-checkable criteria confirmedValidates #4276: Manager UI - Creating New Content in a Dataset Model Requires SEO Information
Suggested Cypress coverage
|
Code Review — ✅ No blockers |
Code Review — ✅ No blockers · 🟡 2 warning(s) — see inline comments |
|
Addressed both review items:
|
Code Review — ✅ No blockers · 🟡 1 warning(s) — see inline comments |
|
Re: the negative-QA finding about manually-entered Meta Title being clobbered by a later edit to the first field — acknowledging this, leaving it out of scope for this PR. The bot's own report labels it pre-existing on the touched surface: the root cause is `Editor.js`'s unconditional `SET_ITEM_WEB metaTitle` on every edit to the first field while `isNewItem`, which predates this PR. This PR's flush-on-save does make it reproduce deterministically instead of depending on winning/losing the old debounce race, but the underlying bug — auto-population overwriting a manual edit — is a separate fix (tracking whether the user has manually touched Meta Title so auto-population stops once they have) and out of scope for a PR about the debounce race and restoring #2984's dataset-optional intent. |
Code Review — ✅ No blockers · 🟡 1 warning(s) — see inline comments |
| ContentModelField, | ||
| } from "../../../../../../shell/services/types"; | ||
| import { SchedulePublish } from "../../../../../../shell/components/SchedulePublish"; | ||
| import { refRegistry } from "../../../../../../engine/refRegistry"; |
There was a problem hiding this comment.
Does the bug reproductioned mentioned in the ticket even touch refRegistry? Why is it important to flush this? or what is being done with the registry here?
There was a problem hiding this comment.
Good question — turned out we didn't need to. Reworked the fix to not touch refRegistry at all: Field.tsx now skips debouncing entirely for the one field that drives Meta Title auto-population (a new isAutoPopulateSource prop, set by Editor.js only on that field for new items), so the value commits to the store on that field's own onChange instead of being flushed from somewhere else at Save time. ItemCreate.tsx's save() no longer imports or references refRegistry. Details in the updated PR description.
Code Review — 🔴 1 blocker(s) — see inline comments |
|
@geodem127 this has a code blocker |
Meta Title had no dataset exemption anywhere (hardcoded required in MetaTitle.tsx, unconditionally in Meta/index.tsx's REQUIRED_FIELDS, and unconditionally checked in createItem()'s hasMissingRequiredSEOFields), unlike Meta Description which was already made optional for datasets in intermittently force the URL path part to be required for datasets. Dataset items have no URL/page, so neither should ever be required. Refs #4276
The previous commit on this branch worked around #4276 by exempting dataset models from the Meta Title requirement. The actual bug is a race: Field.tsx debounces each field's onChange commit to the store by 500ms, so Editor.js's first-text-field auto-population of Meta Title (which applies to every non-block model, datasets included) doesn't reach the store until 500ms after the last keystroke. A save clicked before then validates against a value that hasn't committed yet. Revert the dataset-only exemption (content.js, Meta/index.tsx, MetaTitle.tsx are back to their pre-#4276-fix state) and fix the race instead: - useDebouncedInput now exposes flush(), wrapping lodash debounce's built-in flush. - Field.tsx adds that flush to the handle it already registers into the engine's refRegistry (the same registry the AI drawer uses), rather than introducing a new ref-forwarding path through Editor. - ItemCreate.tsx's save() flushes every registered field inside flushSync before validating. flushSync is required: flushing commits the value to the store, but React 18 batches that update, so Meta's validateMetaFields closure would otherwise stay stale until a re-render happens after this call already read it. - ItemCreate.tsx also now uses validateMetaFields()'s return value to gate the save (matching ItemEdit.js's existing pattern), instead of discarding it and checking possibly-stale SEOErrors state. - meta.spec.js's dataset test now asserts the created item's metaTitle actually matches what was auto-populated, instead of asserting Meta Title can be left blank. Refs #4276
No new e2e coverage needed for this fix; the existing meta.spec.js suite (including "Does not validate meta description for dataset items", which creates a dataset item the same way) already exercises the save path and passes with the debounce-flush fix in place. Refs #4276
…ly required fields Per #2984, Meta Title (like Meta Description already does) should be required only for single/multi-page items and optional for datasets, with the asterisk removed on the label. Restore that behavior: it was dropped when #4276 was originally "fixed" by requiring Meta Title but relying on auto-population, which took the wrong approach per that stated product intent. The debounce/flushSync fix from the previous commit is unaffected and still needed for pathPart/Meta Title auto-population on page and multi-page items. Also fixes a regression found by the PR's negative-QA review: whitespace-only text (e.g. " ") passed every required-field check here since `!value` treats a non-empty string of spaces as truthy. A dataset item's Meta Title bypassed this by not being required, but the same bug independently affects Meta Title on single/multi-page items, where it stays required. Added an isBlank() helper that trims before checking, used everywhere these fields are validated (content.js's createItem thunk, and Meta/index.tsx's live handleOnChange and validateMetaFields checks). Also fixed handleOnChange's useCallback missing REQUIRED_FIELDS and metaFields in its dependency array, which now varies by model type. Refs #4276, #2984
Addresses two review comments on 1aae4aa's flushSync block: - refRegistry is a single app-wide registry keyed by field name, not by item. "Create & Add New Related Item" (RelationalFieldBase -> CreateNewItemDialog) portals a full nested ContentEditor/ItemCreate on top of a still-mounted parent page, so saving the nested dialog was flushing every registered field app-wide -- including any debounced edit still in progress on the parent's fields, force- committing it early. - flushSync forcing a synchronous re-render of every mounted field on every Save click was flagged as a jank risk; scoping the flush to just this model's fields bounds that re-render to what Save was already about to touch, rather than the whole app. Filter to entries whose registered contentModelZUID matches this ItemCreate's own modelZUID before flushing. This resolves the reported cross-model case (the common one for relational fields); a nested dialog creating an item of the *same* model as the parent is a narrower remaining edge case that would need itemZUID-level scoping in refRegistry itself to fully close. Refs #4276
… on save Replaces the flushSync-over-refRegistry save-time flush with a targeted fix: Field.tsx now takes an isAutoPopulateSource prop, set by Editor.js only on the one field (first text/content field) that drives Meta Title auto-population on new items, and useDebouncedInput commits that field synchronously (delay=0) instead of debouncing it. The race is fixed at the point the value is typed rather than patched around at save time, so ItemCreate.tsx no longer needs refRegistry, flushSync, or a per-model filter at all — addressing review concerns about flushing unrelated in-flight fields, the refRegistry name-collision risk, and the unclear justification for touching refRegistry in the first place. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
e25c312 to
b2d7608
Compare
Localization Reviewer — ✅ No blockers🔴 BlockingNone 🟡 AdvisoryNone |
Code Review — ✅ No blockers · 🟡 1 warning(s) — see inline comments |
…taset-model-requires
Localization Reviewer — ✅ No blockers🔴 BlockingNone 🟡 AdvisoryNone |
Code Review — ✅ No blockers · 🟡 1 warning(s) — see inline comments |
…taset-model-requires
Localization Reviewer — ✅ No blockers🔴 BlockingNone 🟡 AdvisoryNone |


Summary
Fixes #4276 ("Creating New Content in a Dataset Model Requires SEO Information") by fixing the actual race condition, and restores the product direction stated in #2984 (Meta Title optional for datasets, like Meta Description already is), rather than making Meta Title required-but-auto-populated everywhere.
Root cause of #4276:
Field.tsxdebounces every field'sonChangecommit to the store by 500ms (useDebouncedInput).Editor.js's first-text-field auto-population of Meta Title/Meta Link Text/path part runs inside that same debounced commit. If Save is clicked within 500ms of the last keystroke, the auto-populated value hasn't reached the Redux store yet, so client-side validation sees it as missing and blocks the save — even though the field visibly shows a value on screen. (The existingmeta.spec.jstest "Does not validate meta description for dataset items" already worked around this exact race with a hardcodedcy.wait(500)before Save.)A separate, compounding bug:
ItemCreate.tsx'ssave()calledmetaRef.current.validateMetaFields()but discarded its return value, instead gating the save on possibly-staleSEOErrorsstate. This let some invalid saves reach thecreateItemthunk, which returned{ err: "VALIDATION_ERROR" }with none of the fieldsItemCreate.tsx's error handling recognizes — so the failure was swallowed silently, leaving the user stuck on/newwith no visible error.Fix for the race, revised: an earlier version of this PR fixed the race by flushing every registered field (via
refRegistry+flushSync) at save time. Review feedback flagged real problems with that: it could force-commit an unrelated parent page's in-progress edits when a nested "Create & Add New Related Item" dialog saves,flushSyncforced a synchronous full re-render on every Save click, andrefRegistry's name-only keying meant two same-named fields mounted at once could silently drop the flush. Rather than patch around those, the fix now removes the flush entirely and addresses the race at its source:Field.tsxtakes a newisAutoPopulateSourceprop;Editor.jssets it only on the one field (first text/content field) that actually drives Meta Title/Meta Link Text/pathPart auto-population, and only for new items on non-block models.useDebouncedInputcommits that field's value synchronously (delay <= 0) instead of debouncing it, so its ownonChange— not Save — is what lands the value in the Redux store. By the time Save is clicked (a separate, later event), the store is already current.ItemCreate.tsxno longer importsrefRegistryor usesflushSyncat all;save()just readsvalidateMetaFields()'s return value directly, matching the pattern already used inItemEdit.js.RefHandle'sflushmethod anduseDebouncedInput'sflushexport are removed as dead code now that nothing calls them.Restoring #2984's intent: an earlier commit on this branch fixed #4276 by making Meta Title required for all types and relying on the auto-population fix above. That contradicts explicit, still-relevant product direction from #2984: Meta Title (like Meta Description already does) should be required only for single/multi-page items, optional for datasets, with the asterisk removed. Restored that —
content.js,Meta/index.tsx'sREQUIRED_FIELDS, andMetaTitle.tsx'srequiredprop once again treat dataset models as SEO-exempt. The debounce fix is unaffected and still needed for page/multi-page item auto-population.Whitespace-only regression (found by this PR's own negative-QA review): every required-field check here used
!value, which treats a string of spaces as truthy. This let a whitespace-only Meta Title through validation on models where it's still required (single/multi-page items), leaving items with no visible title anywhere. Added anisBlank()helper that trims before checking presence, applied everywhere Meta Title/parentZUID/pathPart/dynamic OG-TC fields are validated (content.js'screateItemthunk,Meta/index.tsx's livehandleOnChangeandvalidateMetaFields). Also fixedhandleOnChange'suseCallbackmissingREQUIRED_FIELDS/metaFieldsfrom its dependency array now thatREQUIRED_FIELDSvaries by model type.No new e2e coverage added — the existing
meta.spec.jssuite already exercises dataset item creation and covers this path.Test plan
cypress/e2e/content/meta.spec.js— all 4 existing tests pass, including "Does not validate meta description for dataset items" which creates a dataset item via the same save flow (its hardcodedcy.wait(500)workaround is removed since the race is now fixed at the source).cypress/e2e/content/content.spec.js— full regression pass against theField.tsxchange (46/46 passing, 5 pending as expected).pageset,templateset, anddatasetmodel types: (1) typing into the auto-population field then clicking Save immediately, (2) typing then clearing it then clicking Save immediately, and (3) clicking Save as fast as possible with no input at all.pageset/templatesetcorrectly block a blank/whitespace title with a visible "Required Field" error and never leak one through;datasetsaves cleanly in all cases since Meta Title isn't required there.block-type models use a separate "Create Variant" UI (BlockItem.tsx) and don't exercise this code path at all.npx tsc --noEmit— clean.🤖 Generated with Claude Code