@@ -989,28 +989,26 @@ export function updateSelectedUcComponentState(state, data) {
989989/**
990990 * Apply a Studio WebSocket UC component update.
991991 *
992- * Studio's "updateComponents" notification carries both a full `tree` snapshot
993- * and a `delta` of what just changed. After a successful install, Studio has
994- * been observed to emit a follow-up snapshot that re-marks the just-installed
995- * leaf with isSelected:false, which previously caused the missing-component
996- * warning to flicker back.
992+ * Studio's "updateComponents" notification carries a full tree snapshot. After
993+ * a successful install, Studio has been observed to emit a follow-up snapshot
994+ * that re-marks the just-installed leaf with isSelected:false, which used to
995+ * make the missing-component warning flicker back.
997996 *
998- * ZAP is therefore the authoritative source for its own selection set:
999- * - tree/delta leaves with isSelected:true -> upserted (Studio can ADD)
1000- * - tree/delta leaves with isSelected:false -> ignored (Studio cannot remove)
1001- * - leaves not mentioned at all -> left alone (no wipe)
997+ * The fix is to treat Studio's WS updates as ADD-ONLY for the local selection
998+ * set:
999+ * - leaves with isSelected:true -> upserted (Studio can ADD)
1000+ * - leaves with isSelected:false -> ignored (Studio cannot remove)
1001+ * - leaves not mentioned at all -> left alone (no wipe)
10021002 *
1003- * Removals happen only via applyLocalUcComponentChange when ZAP itself issues
1004- * a remove POST and Studio responds 2xx. ucComponents (the catalog used for
1003+ * Removals happen only via locallyMarkUcComponents when ZAP itself issues a
1004+ * remove POST and Studio responds 2xx. ucComponents (the catalog used for
10051005 * labels / metadata) is always merged so we keep accumulating entries.
10061006 *
10071007 * @param {* } state
1008- * @param {{ treeLeaves: any[], delta: any } } payload
1008+ * @param {any[] } treeLeaves Flattened list of leaf nodes from Studio's tree.
10091009 */
1010- export function applyUcComponentUpdate ( state , payload ) {
1011- if ( ! payload ) return
1012- const treeLeaves = Array . isArray ( payload . treeLeaves ) ? payload . treeLeaves : [ ]
1013- const delta = payload . delta
1010+ export function applyUcComponentUpdate ( state , treeLeaves ) {
1011+ const leaves = Array . isArray ( treeLeaves ) ? treeLeaves : [ ]
10141012
10151013 const prevSelected = Array . isArray ( state . studio . selectedUcComponents )
10161014 ? state . studio . selectedUcComponents
@@ -1026,69 +1024,7 @@ export function applyUcComponentUpdate(state, payload) {
10261024 prevAll . filter ( ( x ) => x && x . id != null ) . map ( ( x ) => [ String ( x . id ) , x ] )
10271025 )
10281026
1029- /**
1030- * Upsert a component into the selected and catalog maps for this mutation.
1031- * @param {* } id Component id to add.
1032- * @param {* } node Optional node payload to store; falls back to a stub.
1033- */
1034- function applyAdded ( id , node ) {
1035- if ( id == null ) return
1036- const key = String ( id )
1037- selectedById . set ( key , node || { id : key , isSelected : true } )
1038- if ( node ) allById . set ( key , node )
1039- }
1040-
1041- // Studio's WebSocket updates are ADD-ONLY for selectedUcComponents.
1042- //
1043- // Background: after we POST a component install, Studio sometimes emits a
1044- // follow-up "updateComponents" tree where the freshly-installed leaf has
1045- // isSelected:false. We don't know whether that's a bona-fide Studio bug or
1046- // a settle artifact (a recomputed snapshot from a stale internal source);
1047- // either way the result is the missing-component warning flickering back.
1048- //
1049- // To make ZAP authoritative for its own POST results we accept Studio's
1050- // tree/delta as evidence that a component IS selected, but we never let it
1051- // unselect a component. Removals happen only via locallyMarkUcComponents
1052- // when ZAP itself POSTs a remove and gets a 2xx back.
1053- if ( delta ) {
1054- if ( Array . isArray ( delta ) ) {
1055- for ( const entry of delta ) {
1056- if ( ! entry ) continue
1057- if ( typeof entry === 'string' ) {
1058- applyAdded ( entry , null )
1059- } else if ( typeof entry === 'object' ) {
1060- const id = entry . id != null ? entry . id : entry . componentId
1061- const sel =
1062- entry . isSelected != null
1063- ? entry . isSelected
1064- : entry . selected != null
1065- ? entry . selected
1066- : entry . installed != null
1067- ? entry . installed
1068- : null
1069- if ( sel === true ) applyAdded ( id , entry )
1070- else if ( entry . action === 'add' || entry . action === 'added' )
1071- applyAdded ( id , entry )
1072- }
1073- }
1074- } else if ( typeof delta === 'object' ) {
1075- const added = [ ] . concat (
1076- delta . added || [ ] ,
1077- delta . installed || [ ] ,
1078- delta . selected || [ ]
1079- )
1080- for ( const item of added ) {
1081- if ( item == null ) continue
1082- if ( typeof item === 'string' ) applyAdded ( item , null )
1083- else if ( typeof item === 'object' )
1084- applyAdded ( item . id != null ? item . id : item . componentId , item )
1085- }
1086- }
1087- }
1088-
1089- // Tree merge: add isSelected:true leaves, ignore isSelected:false. Always
1090- // merge into the ucComponents catalog so labels/metadata stay fresh.
1091- for ( const node of treeLeaves ) {
1027+ for ( const node of leaves ) {
10921028 if ( ! node || node . id == null ) continue
10931029 const key = String ( node . id )
10941030 allById . set ( key , node )
0 commit comments