PROD-2341: sync single-item linked-content selections correctly - #199
Merged
Conversation
A linked-content dropdown that selects ONE item (e.g. Game Banner's "Draw Game")
arrived empty on the target after a sync, and the component could not be edited.
Three coordinated defects caused it:
1. Ordering — the content classifier only treated whole-list references
(fulllist:true) as push-order dependencies. A single-item selection
({ contentid: N, fulllist: false }) was invisible, so the referenced item
could be pushed after the item referencing it, leaving the reference
unresolvable. Add collect-content-id-references and mark single-item
contentid/sortids targets into the push-first "linked" bucket.
2. Remap never happened — content-item mapping records expose targetContentID,
but the field mapper read `.contentID` (always undefined), so content
references were never remapped and shipped with the SOURCE id (a dangling
reference the target stores as empty). Fix via resolveTargetContentID()
(targetContentID ?? contentID) on both the contentid and sortids branches.
3. Serialization — the server batch engine reads a linked-content field with
`string value = row[col] as string` (Agility.Shared BatchProcessing_
InsertContentItem.cs); the CLI sent the { contentid, fulllist } object, whose
string cast is null, so the selection stored empty (rendered as the
SharedContent list name). Emit the SCALAR remapped contentID string for
single-item selections (isSingleItemContentSelection), leaving whole-list
links and bare nested { contentid } references unchanged.
Verified live (63b1dc5d-us2 -> 0c45c4b0-us2): 28/28 Game Banner items with a
selection now populate the correct target id; 0 empty.
Tests: field-mapper (scalar output, targetContentID resolution, sortids,
empty/whole-list/bare-reference untouched), classifier (single-item + sortids
promotion, empty/unmapped no-op), and collect-content-id-references.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5PK
approved these changes
Jul 29, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A linked-content dropdown that selects a single item — e.g. Game Banner's "Draw Game" field — arrived empty on the target after a sync, and the component could not be edited. Other components referencing the same list synced fine because they use whole-list links.
Investigated on a live sync
63b1dc5d-us2(pegasus-qa clone) →0c45c4b0-us2(blank). Root cause was three coordinated defects in the content-push path:1. Push ordering ignored single-item references
The content classifier (
getContentItemTypes) only treated whole-list references (fulllist:true) as push-order dependencies. A single-item selection{ contentid: N, fulllist: false }was invisible, so the referenced item could be pushed after the item referencing it → reference unresolvable.Fix: new
collectContentIDReferences+ classifier promotes single-itemcontentid/sortidstargets into the push-first "linked" bucket.2. Content references were never remapped
Content-item mapping records expose
targetContentID, but the field mapper read.contentID(alwaysundefined) — socontentid/sortidsremapping silently no-op'd and the payload shipped with the source id (a dangling reference the target stores as empty).Fix:
resolveTargetContentID()=targetContentID ?? contentID, applied to both thecontentidandsortidsbranches.3. Object form isn't accepted by the batch engine
The server batch engine reads a linked-content field with
string value = row[col] as string(Agility.Shared/Engines/BatchProcessing/BatchProcessing_InsertContentItem.cs). The CLI sent the{ contentid, fulllist }object, whose string cast isnull→ the selection stored empty (rendered as the SharedContent list name,"drawgamesassets").Fix: emit the scalar remapped contentID string for single-item selections (
isSingleItemContentSelection), leaving whole-list links (fulllist:true) and bare nested{ contentid }references unchanged.Verification
Live re-sync
63b1dc5d-us2 → 0c45c4b0-us2(Game Banner + deps): 28/28 items with a selection populate the correct target id; 0 empty; 2 genuinely had no source selection. Confirmed by reading the field values back from the target via the management API.Tests
content-field-mapper.test.ts— scalar output for single-item selection,targetContentIDresolution,sortidsremap, and empty / whole-list / bare-reference cases left unchanged.get-content-item-types.test.ts— single-item +sortidstargets promoted to linked; empty / unmapped / unknown-id are no-ops; existing whole-list behavior preserved.collect-content-id-references.test.ts.🤖 Generated with Claude Code