@@ -137,30 +137,64 @@ private func selectContentForFamily(_ data: Data, family: WidgetFamily) -> Data
137137
138138 // Try to get content for the specific family
139139 if let familyContent = root [ familyKey] {
140- if JSONSerialization . isValidJSONObject ( familyContent) ,
141- let familyData = try ? JSONSerialization . data ( withJSONObject: familyContent)
142- {
143- return familyData
144- }
140+ return reconstructWithSharedData ( content: familyContent, root: root)
145141 }
146142
147143 // Fallback: try families in order of preference
148144 let fallbackOrder = [ " systemMedium " , " systemSmall " , " systemLarge " , " systemExtraLarge " ,
149145 " accessoryRectangular " , " accessoryCircular " , " accessoryInline " ]
150146 for fallbackKey in fallbackOrder {
151147 if let fallbackContent = root [ fallbackKey] {
152- if JSONSerialization . isValidJSONObject ( fallbackContent) ,
153- let fallbackData = try ? JSONSerialization . data ( withJSONObject: fallbackContent)
154- {
155- return fallbackData
156- }
148+ return reconstructWithSharedData ( content: fallbackContent, root: root)
157149 }
158150 }
159151
160152 // No content found, return empty
161153 return Data ( " [] " . utf8)
162154}
163155
156+ /// Reconstruct JSON with family-specific content plus shared stylesheet and elements.
157+ /// This ensures VoltraNode.parse can resolve style references and element deduplication.
158+ private func reconstructWithSharedData( content: Any , root: [ String : Any ] ) -> Data {
159+ var result : [ String : Any ] = [ : ]
160+
161+ // If content is a dictionary (single component), wrap it in the result
162+ // If content is an array or other type, it will be returned as-is below
163+ if let contentDict = content as? [ String : Any ] {
164+ // Copy all keys from the component
165+ result = contentDict
166+ }
167+
168+ // Add shared stylesheet if present (key "s")
169+ if let stylesheet = root [ " s " ] {
170+ result [ " s " ] = stylesheet
171+ }
172+
173+ // Add shared elements if present (key "e")
174+ if let sharedElements = root [ " e " ] {
175+ result [ " e " ] = sharedElements
176+ }
177+
178+ // If we built a result dict with shared data, serialize it
179+ if !result. isEmpty {
180+ if JSONSerialization . isValidJSONObject ( result) ,
181+ let data = try ? JSONSerialization . data ( withJSONObject: result)
182+ {
183+ return data
184+ }
185+ }
186+
187+ // Fallback: return content as-is if it's serializable
188+ if JSONSerialization . isValidJSONObject ( content) ,
189+ let data = try ? JSONSerialization . data ( withJSONObject: content)
190+ {
191+ return data
192+ }
193+
194+ // Final fallback: empty array
195+ return Data ( " [] " . utf8)
196+ }
197+
164198// MARK: - Deep link + rendering helpers
165199
166200private func buildStaticContentView( data: Data , source _: String ) -> AnyView {
@@ -233,14 +267,11 @@ private func normalizeJsonData(_ data: Data) -> Data? {
233267 return data
234268 }
235269
236- // If it's a single component (dictionary), wrap it in an array
237- if let dict = obj as? [ String : Any ] {
238- guard JSONSerialization . isValidJSONObject ( [ dict] ) ,
239- let wrapped = try ? JSONSerialization . data ( withJSONObject: [ dict] )
240- else {
241- return nil
242- }
243- return wrapped
270+ // If it's a single component (dictionary), return as-is
271+ // Don't wrap in array - VoltraNode.parse handles single objects and needs
272+ // to find "s" (stylesheet) and "e" (elements) at the root level
273+ if obj is [ String : Any ] {
274+ return data
244275 }
245276
246277 // Invalid input (string, number, boolean, null) - return nil to indicate error
0 commit comments