@use-voltra/ios-client 2.1.1 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (365) hide show
  1. package/README.md +61 -8
  2. package/Voltra.podspec +1 -0
  3. package/build/commonjs/dynamic-widget/api.js +15 -0
  4. package/build/commonjs/dynamic-widget/api.js.map +1 -0
  5. package/build/commonjs/events.js +23 -0
  6. package/build/commonjs/events.js.map +1 -1
  7. package/build/commonjs/index.js +74 -7
  8. package/build/commonjs/index.js.map +1 -1
  9. package/build/commonjs/live-activity/api.js +3 -1
  10. package/build/commonjs/live-activity/api.js.map +1 -1
  11. package/build/commonjs/live-activity/dynamic-api.js +173 -0
  12. package/build/commonjs/live-activity/dynamic-api.js.map +1 -0
  13. package/build/commonjs/native/NativeVoltra.js.map +1 -1
  14. package/build/commonjs/utils/enableDynamicLiveActivityHotReload.js +47 -0
  15. package/build/commonjs/utils/enableDynamicLiveActivityHotReload.js.map +1 -0
  16. package/build/commonjs/utils/enableWidgetHotReload.js +8 -11
  17. package/build/commonjs/utils/enableWidgetHotReload.js.map +1 -1
  18. package/build/commonjs/utils/useUpdateOnHMR.js +3 -7
  19. package/build/commonjs/utils/useUpdateOnHMR.js.map +1 -1
  20. package/build/commonjs/widgets/server-credentials.js +9 -0
  21. package/build/commonjs/widgets/server-credentials.js.map +1 -1
  22. package/build/commonjs/widgets/server-update.js +78 -0
  23. package/build/commonjs/widgets/server-update.js.map +1 -0
  24. package/build/module/dynamic-widget/api.js +10 -0
  25. package/build/module/dynamic-widget/api.js.map +1 -0
  26. package/build/module/events.js +23 -0
  27. package/build/module/events.js.map +1 -1
  28. package/build/module/index.js +4 -0
  29. package/build/module/index.js.map +1 -1
  30. package/build/module/live-activity/api.js +2 -2
  31. package/build/module/live-activity/api.js.map +1 -1
  32. package/build/module/live-activity/dynamic-api.js +167 -0
  33. package/build/module/live-activity/dynamic-api.js.map +1 -0
  34. package/build/module/native/NativeVoltra.js.map +1 -1
  35. package/build/module/utils/enableDynamicLiveActivityHotReload.js +43 -0
  36. package/build/module/utils/enableDynamicLiveActivityHotReload.js.map +1 -0
  37. package/build/module/utils/enableWidgetHotReload.js +9 -11
  38. package/build/module/utils/enableWidgetHotReload.js.map +1 -1
  39. package/build/module/utils/useUpdateOnHMR.js +3 -7
  40. package/build/module/utils/useUpdateOnHMR.js.map +1 -1
  41. package/build/module/widgets/server-credentials.js +9 -0
  42. package/build/module/widgets/server-credentials.js.map +1 -1
  43. package/build/module/widgets/server-update.js +72 -0
  44. package/build/module/widgets/server-update.js.map +1 -0
  45. package/build/typescript/commonjs/dynamic-widget/api.d.ts +8 -0
  46. package/build/typescript/commonjs/dynamic-widget/api.d.ts.map +1 -0
  47. package/build/typescript/commonjs/events.d.ts +7 -0
  48. package/build/typescript/commonjs/events.d.ts.map +1 -1
  49. package/build/typescript/commonjs/index.d.ts +4 -0
  50. package/build/typescript/commonjs/index.d.ts.map +1 -1
  51. package/build/typescript/commonjs/live-activity/api.d.ts +7 -0
  52. package/build/typescript/commonjs/live-activity/api.d.ts.map +1 -1
  53. package/build/typescript/commonjs/live-activity/dynamic-api.d.ts +54 -0
  54. package/build/typescript/commonjs/live-activity/dynamic-api.d.ts.map +1 -0
  55. package/build/typescript/commonjs/native/NativeVoltra.d.ts +21 -0
  56. package/build/typescript/commonjs/native/NativeVoltra.d.ts.map +1 -1
  57. package/build/typescript/commonjs/types.d.ts +1 -1
  58. package/build/typescript/commonjs/types.d.ts.map +1 -1
  59. package/build/typescript/commonjs/utils/enableDynamicLiveActivityHotReload.d.ts +15 -0
  60. package/build/typescript/commonjs/utils/enableDynamicLiveActivityHotReload.d.ts.map +1 -0
  61. package/build/typescript/commonjs/utils/enableWidgetHotReload.d.ts +5 -7
  62. package/build/typescript/commonjs/utils/enableWidgetHotReload.d.ts.map +1 -1
  63. package/build/typescript/commonjs/utils/useUpdateOnHMR.d.ts +0 -3
  64. package/build/typescript/commonjs/utils/useUpdateOnHMR.d.ts.map +1 -1
  65. package/build/typescript/commonjs/widgets/server-credentials.d.ts +8 -0
  66. package/build/typescript/commonjs/widgets/server-credentials.d.ts.map +1 -1
  67. package/build/typescript/commonjs/widgets/server-update.d.ts +64 -0
  68. package/build/typescript/commonjs/widgets/server-update.d.ts.map +1 -0
  69. package/build/typescript/module/dynamic-widget/api.d.ts +8 -0
  70. package/build/typescript/module/dynamic-widget/api.d.ts.map +1 -0
  71. package/build/typescript/module/events.d.ts +7 -0
  72. package/build/typescript/module/events.d.ts.map +1 -1
  73. package/build/typescript/module/index.d.ts +4 -0
  74. package/build/typescript/module/index.d.ts.map +1 -1
  75. package/build/typescript/module/live-activity/api.d.ts +7 -0
  76. package/build/typescript/module/live-activity/api.d.ts.map +1 -1
  77. package/build/typescript/module/live-activity/dynamic-api.d.ts +54 -0
  78. package/build/typescript/module/live-activity/dynamic-api.d.ts.map +1 -0
  79. package/build/typescript/module/native/NativeVoltra.d.ts +21 -0
  80. package/build/typescript/module/native/NativeVoltra.d.ts.map +1 -1
  81. package/build/typescript/module/types.d.ts +1 -1
  82. package/build/typescript/module/types.d.ts.map +1 -1
  83. package/build/typescript/module/utils/enableDynamicLiveActivityHotReload.d.ts +15 -0
  84. package/build/typescript/module/utils/enableDynamicLiveActivityHotReload.d.ts.map +1 -0
  85. package/build/typescript/module/utils/enableWidgetHotReload.d.ts +5 -7
  86. package/build/typescript/module/utils/enableWidgetHotReload.d.ts.map +1 -1
  87. package/build/typescript/module/utils/useUpdateOnHMR.d.ts +0 -3
  88. package/build/typescript/module/utils/useUpdateOnHMR.d.ts.map +1 -1
  89. package/build/typescript/module/widgets/server-credentials.d.ts +8 -0
  90. package/build/typescript/module/widgets/server-credentials.d.ts.map +1 -1
  91. package/build/typescript/module/widgets/server-update.d.ts +64 -0
  92. package/build/typescript/module/widgets/server-update.d.ts.map +1 -0
  93. package/expo-plugin/build/cjs/constants.js +20 -1
  94. package/expo-plugin/build/cjs/constants.js.map +1 -1
  95. package/expo-plugin/build/cjs/index.js +9 -1
  96. package/expo-plugin/build/cjs/index.js.map +1 -1
  97. package/expo-plugin/build/cjs/ios/index.js +1 -0
  98. package/expo-plugin/build/cjs/ios/index.js.map +1 -1
  99. package/expo-plugin/build/cjs/ios/infoPlist.js +28 -5
  100. package/expo-plugin/build/cjs/ios/infoPlist.js.map +1 -1
  101. package/expo-plugin/build/cjs/ios/serverUpdate.js +25 -0
  102. package/expo-plugin/build/cjs/ios/serverUpdate.js.map +1 -0
  103. package/expo-plugin/build/cjs/ios-widget/clientRendered.js +4 -4
  104. package/expo-plugin/build/cjs/ios-widget/clientRendered.js.map +1 -1
  105. package/expo-plugin/build/cjs/ios-widget/clientRenderedPrerender.js +5 -5
  106. package/expo-plugin/build/cjs/ios-widget/clientRenderedPrerender.js.map +1 -1
  107. package/expo-plugin/build/cjs/ios-widget/dynamic-live-activity/swift.js +130 -0
  108. package/expo-plugin/build/cjs/ios-widget/dynamic-live-activity/swift.js.map +1 -0
  109. package/expo-plugin/build/cjs/ios-widget/files/index.js +9 -2
  110. package/expo-plugin/build/cjs/ios-widget/files/index.js.map +1 -1
  111. package/expo-plugin/build/cjs/ios-widget/files/infoPlist.js +6 -3
  112. package/expo-plugin/build/cjs/ios-widget/files/infoPlist.js.map +1 -1
  113. package/expo-plugin/build/cjs/ios-widget/files/manifest.js +24 -0
  114. package/expo-plugin/build/cjs/ios-widget/files/manifest.js.map +1 -1
  115. package/expo-plugin/build/cjs/ios-widget/files/swift-utils.js +7 -0
  116. package/expo-plugin/build/cjs/ios-widget/files/swift-utils.js.map +1 -0
  117. package/expo-plugin/build/cjs/ios-widget/files/swift.js +56 -26
  118. package/expo-plugin/build/cjs/ios-widget/files/swift.js.map +1 -1
  119. package/expo-plugin/build/cjs/ios-widget/index.js +9 -3
  120. package/expo-plugin/build/cjs/ios-widget/index.js.map +1 -1
  121. package/expo-plugin/build/cjs/ios-widget/podfile.js +38 -12
  122. package/expo-plugin/build/cjs/ios-widget/podfile.js.map +1 -1
  123. package/expo-plugin/build/cjs/ios-widget/widgetPlist.js +34 -22
  124. package/expo-plugin/build/cjs/ios-widget/widgetPlist.js.map +1 -1
  125. package/expo-plugin/build/cjs/ios-widget/xcode/buildPhases.js +194 -118
  126. package/expo-plugin/build/cjs/ios-widget/xcode/buildPhases.js.map +1 -1
  127. package/expo-plugin/build/cjs/ios-widget/xcode/configurationList.js +30 -22
  128. package/expo-plugin/build/cjs/ios-widget/xcode/configurationList.js.map +1 -1
  129. package/expo-plugin/build/cjs/ios-widget/xcode/fileReferences.js +50 -0
  130. package/expo-plugin/build/cjs/ios-widget/xcode/fileReferences.js.map +1 -0
  131. package/expo-plugin/build/cjs/ios-widget/xcode/groups.js +45 -43
  132. package/expo-plugin/build/cjs/ios-widget/xcode/groups.js.map +1 -1
  133. package/expo-plugin/build/cjs/ios-widget/xcode/index.js +93 -91
  134. package/expo-plugin/build/cjs/ios-widget/xcode/index.js.map +1 -1
  135. package/expo-plugin/build/cjs/ios-widget/xcode/mainAppSettings.js +41 -39
  136. package/expo-plugin/build/cjs/ios-widget/xcode/mainAppSettings.js.map +1 -1
  137. package/expo-plugin/build/cjs/ios-widget/xcode/productFile.js +2 -2
  138. package/expo-plugin/build/cjs/ios-widget/xcode/productFile.js.map +1 -1
  139. package/expo-plugin/build/cjs/ios-widget/xcode/target.js +29 -63
  140. package/expo-plugin/build/cjs/ios-widget/xcode/target.js.map +1 -1
  141. package/expo-plugin/build/cjs/utils/urlScheme.js +36 -14
  142. package/expo-plugin/build/cjs/utils/urlScheme.js.map +1 -1
  143. package/expo-plugin/build/cjs/validation.js +54 -0
  144. package/expo-plugin/build/cjs/validation.js.map +1 -1
  145. package/expo-plugin/build/esm/constants.js +17 -0
  146. package/expo-plugin/build/esm/constants.js.map +1 -1
  147. package/expo-plugin/build/esm/index.js +9 -1
  148. package/expo-plugin/build/esm/index.js.map +1 -1
  149. package/expo-plugin/build/esm/ios/index.js +1 -0
  150. package/expo-plugin/build/esm/ios/index.js.map +1 -1
  151. package/expo-plugin/build/esm/ios/infoPlist.js +28 -5
  152. package/expo-plugin/build/esm/ios/infoPlist.js.map +1 -1
  153. package/expo-plugin/build/esm/ios/serverUpdate.js +21 -0
  154. package/expo-plugin/build/esm/ios/serverUpdate.js.map +1 -0
  155. package/expo-plugin/build/esm/ios-widget/clientRendered.js +5 -5
  156. package/expo-plugin/build/esm/ios-widget/clientRendered.js.map +1 -1
  157. package/expo-plugin/build/esm/ios-widget/clientRenderedPrerender.js +6 -6
  158. package/expo-plugin/build/esm/ios-widget/clientRenderedPrerender.js.map +1 -1
  159. package/expo-plugin/build/esm/ios-widget/dynamic-live-activity/swift.js +122 -0
  160. package/expo-plugin/build/esm/ios-widget/dynamic-live-activity/swift.js.map +1 -0
  161. package/expo-plugin/build/esm/ios-widget/files/index.js +10 -3
  162. package/expo-plugin/build/esm/ios-widget/files/index.js.map +1 -1
  163. package/expo-plugin/build/esm/ios-widget/files/infoPlist.js +6 -3
  164. package/expo-plugin/build/esm/ios-widget/files/infoPlist.js.map +1 -1
  165. package/expo-plugin/build/esm/ios-widget/files/manifest.js +23 -1
  166. package/expo-plugin/build/esm/ios-widget/files/manifest.js.map +1 -1
  167. package/expo-plugin/build/esm/ios-widget/files/swift-utils.js +4 -0
  168. package/expo-plugin/build/esm/ios-widget/files/swift-utils.js.map +1 -0
  169. package/expo-plugin/build/esm/ios-widget/files/swift.js +50 -20
  170. package/expo-plugin/build/esm/ios-widget/files/swift.js.map +1 -1
  171. package/expo-plugin/build/esm/ios-widget/index.js +9 -3
  172. package/expo-plugin/build/esm/ios-widget/index.js.map +1 -1
  173. package/expo-plugin/build/esm/ios-widget/podfile.js +36 -11
  174. package/expo-plugin/build/esm/ios-widget/podfile.js.map +1 -1
  175. package/expo-plugin/build/esm/ios-widget/widgetPlist.js +34 -22
  176. package/expo-plugin/build/esm/ios-widget/widgetPlist.js.map +1 -1
  177. package/expo-plugin/build/esm/ios-widget/xcode/buildPhases.js +194 -117
  178. package/expo-plugin/build/esm/ios-widget/xcode/buildPhases.js.map +1 -1
  179. package/expo-plugin/build/esm/ios-widget/xcode/configurationList.js +31 -22
  180. package/expo-plugin/build/esm/ios-widget/xcode/configurationList.js.map +1 -1
  181. package/expo-plugin/build/esm/ios-widget/xcode/fileReferences.js +45 -0
  182. package/expo-plugin/build/esm/ios-widget/xcode/fileReferences.js.map +1 -0
  183. package/expo-plugin/build/esm/ios-widget/xcode/groups.js +45 -42
  184. package/expo-plugin/build/esm/ios-widget/xcode/groups.js.map +1 -1
  185. package/expo-plugin/build/esm/ios-widget/xcode/index.js +97 -96
  186. package/expo-plugin/build/esm/ios-widget/xcode/index.js.map +1 -1
  187. package/expo-plugin/build/esm/ios-widget/xcode/mainAppSettings.js +41 -39
  188. package/expo-plugin/build/esm/ios-widget/xcode/mainAppSettings.js.map +1 -1
  189. package/expo-plugin/build/esm/ios-widget/xcode/productFile.js +3 -2
  190. package/expo-plugin/build/esm/ios-widget/xcode/productFile.js.map +1 -1
  191. package/expo-plugin/build/esm/ios-widget/xcode/target.js +28 -62
  192. package/expo-plugin/build/esm/ios-widget/xcode/target.js.map +1 -1
  193. package/expo-plugin/build/esm/utils/urlScheme.js +34 -14
  194. package/expo-plugin/build/esm/utils/urlScheme.js.map +1 -1
  195. package/expo-plugin/build/esm/validation.js +54 -1
  196. package/expo-plugin/build/esm/validation.js.map +1 -1
  197. package/expo-plugin/build/types/constants.d.ts +10 -1
  198. package/expo-plugin/build/types/constants.d.ts.map +1 -1
  199. package/expo-plugin/build/types/index.d.ts +1 -1
  200. package/expo-plugin/build/types/index.d.ts.map +1 -1
  201. package/expo-plugin/build/types/ios/index.d.ts +2 -0
  202. package/expo-plugin/build/types/ios/index.d.ts.map +1 -1
  203. package/expo-plugin/build/types/ios/infoPlist.d.ts +2 -0
  204. package/expo-plugin/build/types/ios/infoPlist.d.ts.map +1 -1
  205. package/expo-plugin/build/types/ios/serverUpdate.d.ts +11 -0
  206. package/expo-plugin/build/types/ios/serverUpdate.d.ts.map +1 -0
  207. package/expo-plugin/build/types/ios-widget/clientRendered.d.ts.map +1 -1
  208. package/expo-plugin/build/types/ios-widget/clientRenderedPrerender.d.ts.map +1 -1
  209. package/expo-plugin/build/types/ios-widget/dynamic-live-activity/swift.d.ts +7 -0
  210. package/expo-plugin/build/types/ios-widget/dynamic-live-activity/swift.d.ts.map +1 -0
  211. package/expo-plugin/build/types/ios-widget/files/index.d.ts +3 -1
  212. package/expo-plugin/build/types/ios-widget/files/index.d.ts.map +1 -1
  213. package/expo-plugin/build/types/ios-widget/files/infoPlist.d.ts +2 -1
  214. package/expo-plugin/build/types/ios-widget/files/infoPlist.d.ts.map +1 -1
  215. package/expo-plugin/build/types/ios-widget/files/manifest.d.ts +9 -2
  216. package/expo-plugin/build/types/ios-widget/files/manifest.d.ts.map +1 -1
  217. package/expo-plugin/build/types/ios-widget/files/swift-utils.d.ts +2 -0
  218. package/expo-plugin/build/types/ios-widget/files/swift-utils.d.ts.map +1 -0
  219. package/expo-plugin/build/types/ios-widget/files/swift.d.ts +6 -2
  220. package/expo-plugin/build/types/ios-widget/files/swift.d.ts.map +1 -1
  221. package/expo-plugin/build/types/ios-widget/index.d.ts +3 -1
  222. package/expo-plugin/build/types/ios-widget/index.d.ts.map +1 -1
  223. package/expo-plugin/build/types/ios-widget/podfile.d.ts +19 -1
  224. package/expo-plugin/build/types/ios-widget/podfile.d.ts.map +1 -1
  225. package/expo-plugin/build/types/ios-widget/widgetPlist.d.ts.map +1 -1
  226. package/expo-plugin/build/types/ios-widget/xcode/buildPhases.d.ts +6 -8
  227. package/expo-plugin/build/types/ios-widget/xcode/buildPhases.d.ts.map +1 -1
  228. package/expo-plugin/build/types/ios-widget/xcode/configurationList.d.ts +15 -10
  229. package/expo-plugin/build/types/ios-widget/xcode/configurationList.d.ts.map +1 -1
  230. package/expo-plugin/build/types/ios-widget/xcode/fileReferences.d.ts +16 -0
  231. package/expo-plugin/build/types/ios-widget/xcode/fileReferences.d.ts.map +1 -0
  232. package/expo-plugin/build/types/ios-widget/xcode/groups.d.ts +0 -4
  233. package/expo-plugin/build/types/ios-widget/xcode/groups.d.ts.map +1 -1
  234. package/expo-plugin/build/types/ios-widget/xcode/index.d.ts +23 -3
  235. package/expo-plugin/build/types/ios-widget/xcode/index.d.ts.map +1 -1
  236. package/expo-plugin/build/types/ios-widget/xcode/mainAppSettings.d.ts +16 -4
  237. package/expo-plugin/build/types/ios-widget/xcode/mainAppSettings.d.ts.map +1 -1
  238. package/expo-plugin/build/types/ios-widget/xcode/productFile.d.ts +0 -4
  239. package/expo-plugin/build/types/ios-widget/xcode/productFile.d.ts.map +1 -1
  240. package/expo-plugin/build/types/ios-widget/xcode/target.d.ts +11 -26
  241. package/expo-plugin/build/types/ios-widget/xcode/target.d.ts.map +1 -1
  242. package/expo-plugin/build/types/types.d.ts +23 -4
  243. package/expo-plugin/build/types/types.d.ts.map +1 -1
  244. package/expo-plugin/build/types/utils/urlScheme.d.ts +16 -0
  245. package/expo-plugin/build/types/utils/urlScheme.d.ts.map +1 -1
  246. package/expo-plugin/build/types/validation.d.ts +2 -1
  247. package/expo-plugin/build/types/validation.d.ts.map +1 -1
  248. package/expo-plugin/src/constants.ts +24 -1
  249. package/expo-plugin/src/index.ts +14 -1
  250. package/expo-plugin/src/ios/index.ts +3 -0
  251. package/expo-plugin/src/ios/infoPlist.ts +33 -5
  252. package/expo-plugin/src/ios/serverUpdate.ts +29 -0
  253. package/expo-plugin/src/ios-widget/clientRendered.ts +9 -5
  254. package/expo-plugin/src/ios-widget/clientRenderedPrerender.ts +11 -6
  255. package/expo-plugin/src/ios-widget/dynamic-live-activity/swift.ts +138 -0
  256. package/expo-plugin/src/ios-widget/files/index.ts +15 -4
  257. package/expo-plugin/src/ios-widget/files/infoPlist.ts +17 -3
  258. package/expo-plugin/src/ios-widget/files/manifest.ts +42 -2
  259. package/expo-plugin/src/ios-widget/files/swift-utils.ts +3 -0
  260. package/expo-plugin/src/ios-widget/files/swift.ts +69 -22
  261. package/expo-plugin/src/ios-widget/index.ts +13 -3
  262. package/expo-plugin/src/ios-widget/podfile.ts +46 -13
  263. package/expo-plugin/src/ios-widget/widgetPlist.ts +37 -23
  264. package/expo-plugin/src/ios-widget/xcode/__fixtures__/fresh.pbxproj +197 -0
  265. package/expo-plugin/src/ios-widget/xcode/__fixtures__/with-existing-extension.pbxproj +321 -0
  266. package/expo-plugin/src/ios-widget/xcode/__fixtures__/with-per-config-signing.pbxproj +203 -0
  267. package/expo-plugin/src/ios-widget/xcode/__snapshots__/applyXcodeChanges.node.test.ts.snap +363 -0
  268. package/expo-plugin/src/ios-widget/xcode/__tests__/pbxConsistency.ts +130 -0
  269. package/expo-plugin/src/ios-widget/xcode/buildPhases.ts +210 -164
  270. package/expo-plugin/src/ios-widget/xcode/configurationList.ts +52 -35
  271. package/expo-plugin/src/ios-widget/xcode/fileReferences.ts +51 -0
  272. package/expo-plugin/src/ios-widget/xcode/groups.ts +52 -54
  273. package/expo-plugin/src/ios-widget/xcode/index.ts +124 -119
  274. package/expo-plugin/src/ios-widget/xcode/mainAppSettings.ts +59 -54
  275. package/expo-plugin/src/ios-widget/xcode/productFile.ts +3 -2
  276. package/expo-plugin/src/ios-widget/xcode/target.ts +31 -83
  277. package/expo-plugin/src/types.ts +28 -4
  278. package/expo-plugin/src/utils/urlScheme.ts +47 -17
  279. package/expo-plugin/src/validation.ts +76 -1
  280. package/ios/Package.swift +39 -4
  281. package/ios/VoltraWidget.podspec +1 -0
  282. package/ios/app/NativeVoltra.mm +126 -7
  283. package/ios/app/VoltraLiveActivityManager.swift +30 -0
  284. package/ios/app/VoltraLiveActivityService.swift +77 -5
  285. package/ios/app/VoltraModule.swift +117 -1
  286. package/ios/app/VoltraModuleImpl.swift +116 -2
  287. package/ios/app/VoltraViewComponentView.mm +3 -3
  288. package/ios/app/VoltraWidgetService.swift +147 -12
  289. package/ios/app/dynamic-live-activity/VoltraDynamicLiveActivityService.swift +97 -0
  290. package/ios/app/dynamic-live-activity/VoltraLiveActivityChronology.swift +68 -0
  291. package/ios/shared/DynamicWidgetPropsCodec.swift +51 -0
  292. package/ios/shared/DynamicWidgetPropsStorage.swift +53 -0
  293. package/ios/shared/DynamicWidgetPropsStore.swift +117 -0
  294. package/ios/shared/DynamicWidgetRenderCoordinator.swift +31 -0
  295. package/ios/shared/DynamicWidgetServerUpdate/DynamicWidgetServerFetchResolver.swift +34 -0
  296. package/ios/shared/DynamicWidgetServerUpdate/DynamicWidgetServerProps.swift +55 -0
  297. package/ios/shared/DynamicWidgetServerUpdate/DynamicWidgetServerPropsStore.swift +146 -0
  298. package/ios/shared/DynamicWidgetServerUpdate/DynamicWidgetServerUpdateRunner.swift +193 -0
  299. package/ios/shared/DynamicWidgetUpdater.swift +30 -0
  300. package/ios/shared/ServerWidgetContentResolver.swift +60 -0
  301. package/ios/shared/ServerWidgetResponseStore.swift +25 -0
  302. package/ios/shared/ShortNames.swift +6 -0
  303. package/ios/shared/VoltraConfig.swift +41 -0
  304. package/ios/shared/VoltraConstants.swift +19 -0
  305. package/ios/shared/VoltraElement.swift +127 -43
  306. package/ios/shared/VoltraEventBus.swift +52 -7
  307. package/ios/shared/VoltraJSRenderer.swift +93 -23
  308. package/ios/shared/VoltraNode.swift +7 -111
  309. package/ios/shared/VoltraRefreshIntent.swift +1 -1
  310. package/ios/shared/VoltraWidgetDefaults.swift +9 -1
  311. package/ios/shared/VoltraWidgetKind.swift +41 -0
  312. package/ios/shared/VoltraWidgetServerFetcher.swift +52 -107
  313. package/ios/shared/WidgetServer/VoltraWidgetAppearance.swift +17 -0
  314. package/ios/shared/WidgetServer/VoltraWidgetServer.swift +138 -0
  315. package/ios/shared/WidgetServer/WidgetCanonicalConfiguration.swift +77 -0
  316. package/ios/shared/WidgetServer/WidgetScope.swift +55 -0
  317. package/ios/shared/WidgetServer/WidgetServerEtagStore.swift +55 -0
  318. package/ios/shared/WidgetServer/WidgetServerFetcher.swift +179 -0
  319. package/ios/shared/WidgetServer/WidgetServerRequestBuilder.swift +114 -0
  320. package/ios/shared/WidgetServer/WidgetServerSettingsCodec.swift +47 -0
  321. package/ios/shared/WidgetServer/WidgetServerSettingsResolver.swift +129 -0
  322. package/ios/shared/WidgetServer/WidgetServerSettingsStore.swift +170 -0
  323. package/ios/shared/WidgetServer/WidgetServerSettingsValidator.swift +73 -0
  324. package/ios/shared/WidgetServer/WidgetServerUpdateSettings.swift +123 -0
  325. package/ios/shared/WidgetServer/WidgetServerUpdateSettingsJson.swift +115 -0
  326. package/ios/shared/dynamic-live-activity/DynamicLiveActivityPropsCodec.swift +13 -0
  327. package/ios/shared/dynamic-live-activity/VoltraDynamicLiveActivityBundleSource.swift +156 -0
  328. package/ios/shared/dynamic-live-activity/VoltraDynamicLiveActivityObserver.swift +97 -0
  329. package/ios/shared/dynamic-live-activity/VoltraDynamicLiveActivityOperations.swift +105 -0
  330. package/ios/shared/dynamic-live-activity/VoltraDynamicLiveActivityPayloadValidator.swift +55 -0
  331. package/ios/shared/dynamic-live-activity/VoltraDynamicLiveActivityRegistry.swift +143 -0
  332. package/ios/shared/dynamic-live-activity/VoltraDynamicLiveActivityRenderFailureQueue.swift +158 -0
  333. package/ios/shared/dynamic-live-activity/VoltraDynamicLiveActivityRenderFailureReporter.swift +120 -0
  334. package/ios/shared/dynamic-live-activity/VoltraDynamicLiveActivityRenderer.swift +370 -0
  335. package/ios/shared/dynamic-live-activity/VoltraDynamicLiveActivityTypes.swift +87 -0
  336. package/ios/shared/dynamic-live-activity/VoltraLiveActivityOrder.swift +19 -0
  337. package/ios/target/VoltraClientWidgetRuntime.swift +60 -10
  338. package/ios/target/VoltraDynamicWidgetServerUpdateProvider.swift +204 -0
  339. package/ios/target/VoltraHomeWidget.swift +91 -92
  340. package/ios/target/VoltraWidget.swift +3 -1
  341. package/ios/ui/Extensions/VoltraNode+View.swift +113 -0
  342. package/ios/ui/Generated/Parameters/ChartParameters.swift +3 -0
  343. package/ios/ui/Helpers/VoltraDeepLinkResolver.swift +5 -3
  344. package/ios/ui/Helpers/VoltraProgressDriver.swift +6 -2
  345. package/ios/ui/Layout/VoltraFlexStackLayout.swift +9 -3
  346. package/ios/ui/Style/JSColorParser.swift +24 -8
  347. package/ios/ui/Style/JSGradientParser.swift +36 -12
  348. package/ios/ui/Style/JSStyleParser.swift +36 -12
  349. package/ios/ui/Style/LayoutStyle.swift +12 -4
  350. package/ios/ui/Views/VoltraChart.swift +160 -16
  351. package/ios/ui/Views/VoltraLinearGradient.swift +6 -2
  352. package/package.json +5 -5
  353. package/src/dynamic-widget/api.ts +25 -0
  354. package/src/events.ts +34 -0
  355. package/src/index.ts +23 -0
  356. package/src/live-activity/api.ts +2 -2
  357. package/src/live-activity/dynamic-api.ts +240 -0
  358. package/src/native/NativeVoltra.ts +22 -0
  359. package/src/types.ts +4 -0
  360. package/src/utils/enableDynamicLiveActivityHotReload.ts +50 -0
  361. package/src/utils/enableWidgetHotReload.ts +9 -16
  362. package/src/utils/useUpdateOnHMR.ts +3 -12
  363. package/src/widgets/server-credentials.ts +8 -0
  364. package/src/widgets/server-update.ts +84 -0
  365. /package/ios/shared/{VoltraKeychainHelper.swift → WidgetServer/VoltraKeychainHelper.swift} +0 -0
@@ -0,0 +1,25 @@
1
+ import Foundation
2
+
3
+ /// In-memory record of the last successful server response for each server-driven widget.
4
+ ///
5
+ /// Lives for the lifetime of the widget extension process. It is the source of truth for
6
+ /// "the last content the server actually sent", independent of whether an App Group is
7
+ /// configured for on-disk persistence.
8
+ actor ServerWidgetResponseStore {
9
+ struct Response: Equatable {
10
+ let data: Data
11
+ let fetchedAt: Date
12
+ }
13
+
14
+ private var responses: [String: Response] = [:]
15
+
16
+ init() {}
17
+
18
+ func response(for widgetId: String) -> Response? {
19
+ responses[widgetId]
20
+ }
21
+
22
+ func store(_ data: Data, for widgetId: String, fetchedAt: Date) {
23
+ responses[widgetId] = Response(data: data, fetchedAt: fetchedAt)
24
+ }
25
+ }
@@ -77,6 +77,7 @@ public enum ShortNames {
77
77
  "g": "gap",
78
78
  "gs": "gaugeStyle",
79
79
  "ge": "glassEffect",
80
+ "gc": "gradientColors",
80
81
  "h": "height",
81
82
  "halig": "horizontalAlignment",
82
83
  "hp": "horizontalPadding",
@@ -92,6 +93,7 @@ public enum ShortNames {
92
93
  "l": "left",
93
94
  "lgv": "legendVisibility",
94
95
  "ls": "letterSpacing",
96
+ "lc": "lineCap",
95
97
  "lh": "lineHeight",
96
98
  "ll": "lineLimit",
97
99
  "lsp": "lineSpacing",
@@ -149,12 +151,15 @@ public enum ShortNames {
149
151
  "smc": "smallCaps",
150
152
  "src": "source",
151
153
  "sp": "spacing",
154
+ "sa": "startAngle",
152
155
  "start": "startAtMs",
153
156
  "sic": "startIcon",
154
157
  "stp": "startPoint",
155
158
  "sts": "stops",
156
159
  "st": "strikethrough",
160
+ "sw": "strokeWidth",
157
161
  "s": "style",
162
+ "swa": "sweepAngle",
158
163
  "si": "systemImage",
159
164
  "txt": "text",
160
165
  "ta": "textAlign",
@@ -184,6 +189,7 @@ public enum ShortNames {
184
189
  "xav": "xAxisVisibility",
185
190
  "ygs": "yAxisGridStyle",
186
191
  "yav": "yAxisVisibility",
192
+ "ysc": "yScale",
187
193
  "zi": "zIndex",
188
194
  ]
189
195
 
@@ -15,4 +15,45 @@ public enum VoltraConfig {
15
15
  public static func keychainGroup() -> String? {
16
16
  Bundle.main.object(forInfoDictionaryKey: VoltraStorageKeys.keychainGroup) as? String
17
17
  }
18
+
19
+ /// Reported when `Voltra_Version` is missing or unusable, mirroring the `appVersion` fallback.
20
+ public static let unknownVoltraVersion = "unknown"
21
+
22
+ /// Get the installed Voltra client package version from Info.plist
23
+ /// Written at prebuild (`expo prebuild`) or apply (`voltra apply`) time from the installed
24
+ /// `@use-voltra/{ios,android}-client` manifest, and surfaced to widgets as `env.build.voltraVersion`.
25
+ public static func voltraVersion(bundle: Bundle = .main) -> String {
26
+ normalizeVoltraVersion(bundle.object(forInfoDictionaryKey: VoltraStorageKeys.voltraVersion))
27
+ }
28
+
29
+ /// Value handling for `voltraVersion(bundle:)`, split out so every malformed shape the plist can
30
+ /// hold is covered without building a bundle per case.
31
+ static func normalizeVoltraVersion(_ rawValue: Any?) -> String {
32
+ guard let version = rawValue as? String, !version.isEmpty else {
33
+ return unknownVoltraVersion
34
+ }
35
+
36
+ return version
37
+ }
38
+
39
+ /// Get the `{ widgetId: kind }` map of widgets that pin a custom WidgetKit kind from Info.plist
40
+ /// Written at prebuild (`expo prebuild`) or apply (`voltra apply`) time from the `kind` option.
41
+ /// Absent when every widget keeps the default `Voltra_Widget_<id>` kind.
42
+ public static func widgetKinds(bundle: Bundle = .main) -> [String: String] {
43
+ normalizeWidgetKinds(bundle.object(forInfoDictionaryKey: VoltraStorageKeys.widgetKinds))
44
+ }
45
+
46
+ /// Value handling for `widgetKinds(bundle:)`, split out so every malformed shape the plist can
47
+ /// hold is covered without building a bundle per case. Entries that are not non-empty strings
48
+ /// are dropped, leaving those widgets on the default kind.
49
+ static func normalizeWidgetKinds(_ rawValue: Any?) -> [String: String] {
50
+ guard let rawMap = rawValue as? [String: Any] else {
51
+ return [:]
52
+ }
53
+
54
+ return rawMap.compactMapValues { value in
55
+ guard let kind = value as? String, !kind.isEmpty else { return nil }
56
+ return kind
57
+ }
58
+ }
18
59
  }
@@ -28,6 +28,22 @@ public enum VoltraStorageKeys {
28
28
  "Voltra_Widget_ServerUrl_\(widgetId)"
29
29
  }
30
30
 
31
+ public static func dynamicWidgetPropsV1(_ dynamicWidgetId: String) -> String {
32
+ "Voltra_DynamicWidget_Props_v1_\(dynamicWidgetId)"
33
+ }
34
+
35
+ /// One placement's own props slot (ADR 0007), keyed by `WidgetScope.Instance.storageKey`
36
+ /// (`<widgetId>#<key>`).
37
+ public static func dynamicWidgetInstancePropsV1(_ storageKey: String) -> String {
38
+ "Voltra_DynamicWidget_Props_v1_\(storageKey)"
39
+ }
40
+
41
+ /// The set of instance keys `dynamicWidgetId` has ever committed props into, so a widget-level
42
+ /// clear (or logout) can drop every instance slot without enumerating placements.
43
+ public static func dynamicWidgetPropsInstanceIndexV1(_ dynamicWidgetId: String) -> String {
44
+ "Voltra_DynamicWidget_Props_Instances_v1_\(dynamicWidgetId)"
45
+ }
46
+
31
47
  // MARK: - Prefixes / kind identifiers
32
48
 
33
49
  public static let widgetKindPrefix = "Voltra_Widget_"
@@ -39,6 +55,8 @@ public enum VoltraStorageKeys {
39
55
  // MARK: - Info.plist keys
40
56
 
41
57
  public static let widgetIds = "Voltra_WidgetIds"
58
+ /// `{ widgetId: kind }` for widgets that pin a custom WidgetKit kind (plugin `kind` option)
59
+ public static let widgetKinds = "Voltra_WidgetKinds"
42
60
  public static let enablePushNotifications = "Voltra_EnablePushNotifications"
43
61
  public static let widgetServerUrls = "Voltra_WidgetServerUrls"
44
62
  public static let widgetServerIntervals = "Voltra_WidgetServerIntervals"
@@ -46,4 +64,5 @@ public enum VoltraStorageKeys {
46
64
  public static let appGroupIdentifier = "Voltra_AppGroupIdentifier"
47
65
  public static let legacyAppGroupIdentifier = "AppGroupIdentifier"
48
66
  public static let keychainGroup = "Voltra_KeychainGroup"
67
+ public static let voltraVersion = "Voltra_Version"
49
68
  }
@@ -11,17 +11,20 @@ public struct VoltraElement: Hashable {
11
11
  /// Child nodes or text content
12
12
  public let children: VoltraNode?
13
13
 
14
- /// Raw properties (stored as JSONValue for type safety)
14
+ /// Raw properties (stored as JSONValue for type safety).
15
+ ///
16
+ /// Stylesheet indices and shared element references are substituted for the values they point
17
+ /// at while parsing, so this holds no handle into a per-payload lookup table.
15
18
  private let _props: [String: JSONValue]?
16
19
 
17
- /// Optional stylesheet for resolving style references
18
- public let stylesheet: [[String: JSONValue]]?
19
-
20
- /// Optional shared elements for resolving element references
21
- public let sharedElements: [JSONValue]?
20
+ /// Style dictionary with expanded keys, resolved once while parsing.
21
+ private let _style: [String: JSONValue]?
22
22
 
23
23
  // MARK: - Hashable
24
24
 
25
+ /// `_props` carries fully resolved values, so it alone distinguishes elements that render
26
+ /// differently. Identity deliberately omits `_style`, which is derived from `_props` and would
27
+ /// only restate it.
25
28
  public func hash(into hasher: inout Hasher) {
26
29
  hasher.combine(type)
27
30
  hasher.combine(id)
@@ -52,39 +55,15 @@ public struct VoltraElement: Hashable {
52
55
 
53
56
  /// Style dictionary with expanded keys
54
57
  public var style: [String: JSONValue]? {
55
- guard let styleValue = props?["style"] else {
56
- return nil
57
- }
58
-
59
- let styleDict: [String: JSONValue]
60
-
61
- // Handle stylesheet reference (integer index)
62
- if let index = styleValue.intValue,
63
- let stylesheet = stylesheet,
64
- index >= 0, index < stylesheet.count
65
- {
66
- styleDict = stylesheet[index]
67
- }
68
- // Handle inline style (object)
69
- else if let objectValue = styleValue.objectValue {
70
- styleDict = objectValue
71
- } else {
72
- return nil
73
- }
74
-
75
- var expanded: [String: JSONValue] = [:]
76
- for (key, value) in styleDict {
77
- // Use unified ShortNames mapping for style properties
78
- let expandedKey = ShortNames.expand(key)
79
- expanded[expandedKey] = value
80
- }
81
-
82
- return expanded
58
+ _style
83
59
  }
84
60
 
85
61
  // MARK: - Initialization
86
62
 
87
63
  /// Initialize from JSONValue (no serialization roundtrip)
64
+ ///
65
+ /// The stylesheet and shared elements are consumed here rather than retained: every reference
66
+ /// into them is replaced by the value it names, leaving a self-contained element.
88
67
  /// - Parameters:
89
68
  /// - json: The JSON value to parse
90
69
  /// - stylesheet: Optional shared stylesheet for style deduplication
@@ -112,25 +91,28 @@ public struct VoltraElement: Hashable {
112
91
  children = nil
113
92
  }
114
93
 
115
- // Extract props
94
+ // Extract props, substituting the values behind every stylesheet index and shared element
95
+ // reference so the element stops depending on lookup tables it does not carry.
96
+ let resolvedProps: [String: JSONValue]?
116
97
  if let propsValue = dict["p"], case let .object(propsDict) = propsValue {
117
- _props = propsDict
98
+ resolvedProps = Self.resolvingReferences(
99
+ inProps: propsDict,
100
+ stylesheet: stylesheet,
101
+ sharedElements: sharedElements
102
+ )
118
103
  } else {
119
- _props = nil
104
+ resolvedProps = nil
120
105
  }
121
106
 
122
- // Store stylesheet reference
123
- self.stylesheet = stylesheet
124
-
125
- // Store shared elements reference
126
- self.sharedElements = sharedElements
107
+ _props = resolvedProps
108
+ _style = Self.expandedStyle(from: resolvedProps)
127
109
  }
128
110
 
129
111
  /// Get component prop by name - handles both single component and array
130
112
  public func componentProp(_ propName: String) -> VoltraNode {
131
113
  guard let propValue = props?[propName] else { return .empty }
132
114
 
133
- return VoltraNode(from: propValue, stylesheet: stylesheet, sharedElements: sharedElements)
115
+ return VoltraNode(from: propValue)
134
116
  }
135
117
 
136
118
  /// Decode parameters from props
@@ -150,4 +132,106 @@ public struct VoltraElement: Hashable {
150
132
  return try! JSONDecoder().decode(T.self, from: "{}".data(using: .utf8)!)
151
133
  }
152
134
  }
135
+
136
+ // MARK: - Reference Resolution
137
+
138
+ /// Substitutes the stylesheet entry behind a style index, and the shared element behind a
139
+ /// `$r` reference, throughout a props dictionary.
140
+ ///
141
+ /// Element-valued props carry serialized nodes of their own, and those nodes reference the
142
+ /// same payload-level tables. Leaving them unresolved would make two elements with equal
143
+ /// props compare equal while rendering differently.
144
+ /// The renderer registers shared elements only after rendering their contents, so generated
145
+ /// references point to completed entries and cannot form cycles.
146
+ private static func resolvingReferences(
147
+ inProps props: [String: JSONValue],
148
+ stylesheet: [[String: JSONValue]]?,
149
+ sharedElements: [JSONValue]?
150
+ ) -> [String: JSONValue] {
151
+ guard stylesheet != nil || sharedElements != nil else {
152
+ return props
153
+ }
154
+
155
+ var resolved = props
156
+ for (key, value) in props {
157
+ if ShortNames.expand(key) == "style" {
158
+ if let index = value.intValue,
159
+ let stylesheet = stylesheet,
160
+ index >= 0, index < stylesheet.count
161
+ {
162
+ resolved[key] = .object(stylesheet[index])
163
+ }
164
+ } else {
165
+ resolved[key] = resolvingReferences(
166
+ in: value,
167
+ stylesheet: stylesheet,
168
+ sharedElements: sharedElements
169
+ )
170
+ }
171
+ }
172
+ return resolved
173
+ }
174
+
175
+ /// Resolves references inside a value that may hold serialized nodes.
176
+ private static func resolvingReferences(
177
+ in value: JSONValue,
178
+ stylesheet: [[String: JSONValue]]?,
179
+ sharedElements: [JSONValue]?
180
+ ) -> JSONValue {
181
+ switch value {
182
+ case let .array(items):
183
+ return .array(items.map {
184
+ resolvingReferences(in: $0, stylesheet: stylesheet, sharedElements: sharedElements)
185
+ })
186
+
187
+ case let .object(dict):
188
+ // Element reference ($r key) - substitute the shared element it names
189
+ if let refIndex = dict["$r"]?.intValue,
190
+ let sharedElements = sharedElements,
191
+ refIndex >= 0, refIndex < sharedElements.count
192
+ {
193
+ return resolvingReferences(
194
+ in: sharedElements[refIndex],
195
+ stylesheet: stylesheet,
196
+ sharedElements: sharedElements
197
+ )
198
+ }
199
+
200
+ var resolved = dict
201
+ if let propsValue = dict["p"], case let .object(propsDict) = propsValue {
202
+ resolved["p"] = .object(resolvingReferences(
203
+ inProps: propsDict,
204
+ stylesheet: stylesheet,
205
+ sharedElements: sharedElements
206
+ ))
207
+ }
208
+ if let childrenValue = dict["c"] {
209
+ resolved["c"] = resolvingReferences(
210
+ in: childrenValue,
211
+ stylesheet: stylesheet,
212
+ sharedElements: sharedElements
213
+ )
214
+ }
215
+ return .object(resolved)
216
+
217
+ default:
218
+ return value
219
+ }
220
+ }
221
+
222
+ /// Expands the short keys of an already resolved style prop into full property names.
223
+ private static func expandedStyle(from props: [String: JSONValue]?) -> [String: JSONValue]? {
224
+ guard let styleValue = props?["s"] ?? props?["style"],
225
+ let styleDict = styleValue.objectValue
226
+ else {
227
+ return nil
228
+ }
229
+
230
+ var expanded: [String: JSONValue] = [:]
231
+ for (key, value) in styleDict {
232
+ // Use unified ShortNames mapping for style properties
233
+ expanded[ShortNames.expand(key)] = value
234
+ }
235
+ return expanded
236
+ }
153
237
  }
@@ -8,6 +8,9 @@ public class VoltraEventBus {
8
8
  public static let shared = VoltraEventBus()
9
9
 
10
10
  private var observer: NSObjectProtocol?
11
+ private var renderFailureObserver: UUID?
12
+ private var handler: ((String, [String: Any]) -> Void)?
13
+ private var renderFailureListenerCount = 0
11
14
  private let lock = NSLock()
12
15
 
13
16
  private init() {}
@@ -39,20 +42,18 @@ public class VoltraEventBus {
39
42
  /// - Parameter handler: A closure that receives the event name and event data dictionary
40
43
  public func subscribe(handler: @escaping (String, [String: Any]) -> Void) {
41
44
  lock.lock()
42
- defer { lock.unlock() }
43
-
44
45
  if let observer {
45
46
  NotificationCenter.default.removeObserver(observer)
46
47
  self.observer = nil
47
48
  }
49
+ if let renderFailureObserver {
50
+ VoltraDynamicLiveActivityRenderFailureReporter.removeChangeObserver(renderFailureObserver)
51
+ self.renderFailureObserver = nil
52
+ }
53
+ self.handler = handler
48
54
 
49
55
  // 1. Replay persisted events from UserDefaults (interactions from widget)
50
56
  let persistedEvents = VoltraPersistentEventQueue.popAll()
51
- for event in persistedEvents {
52
- handler(event.name, event.data)
53
- }
54
- VoltraLogger.event.info("Replayed \(persistedEvents.count) persisted events")
55
-
56
57
  // 2. Listen for all events via NotificationCenter (hot delivery)
57
58
  observer = NotificationCenter.default.addObserver(
58
59
  forName: .voltraEvent,
@@ -66,6 +67,44 @@ public class VoltraEventBus {
66
67
  }
67
68
  handler(eventName, userInfo)
68
69
  }
70
+ renderFailureObserver = VoltraDynamicLiveActivityRenderFailureReporter.observeChanges { [weak self] in
71
+ self?.drainDynamicLiveActivityRenderFailures()
72
+ }
73
+ lock.unlock()
74
+
75
+ for event in persistedEvents {
76
+ handler(event.name, event.data)
77
+ }
78
+ VoltraLogger.event.info("Replayed \(persistedEvents.count) persisted events")
79
+ }
80
+
81
+ /// JS owns the dedicated emitter subscription count. Persisted failures must
82
+ /// remain queued whenever there is no relevant JavaScript listener.
83
+ public func setDynamicLiveActivityRenderFailureListenerActive(_ active: Bool) {
84
+ lock.lock()
85
+ renderFailureListenerCount = active ? 1 : 0
86
+ lock.unlock()
87
+ if active {
88
+ drainDynamicLiveActivityRenderFailures()
89
+ }
90
+ }
91
+
92
+ /// Flush only the dedicated Dynamic Live Activity diagnostic queue. This
93
+ /// cannot read, displace, or clear persistent interaction events.
94
+ public func drainDynamicLiveActivityRenderFailures() {
95
+ lock.lock()
96
+ let handler = handler
97
+ let isReady = renderFailureListenerCount > 0
98
+ lock.unlock()
99
+ guard isReady, let handler else { return }
100
+
101
+ let failures = VoltraDynamicLiveActivityRenderFailureReporter.drain()
102
+ for failure in failures {
103
+ handler(failure.type, failure.dictionary)
104
+ }
105
+ if !failures.isEmpty {
106
+ VoltraLogger.event.info("Replayed \(failures.count) Dynamic Live Activity render failures")
107
+ }
69
108
  }
70
109
 
71
110
  /// Unsubscribe from Voltra events
@@ -77,6 +116,12 @@ public class VoltraEventBus {
77
116
  NotificationCenter.default.removeObserver(observer)
78
117
  self.observer = nil
79
118
  }
119
+ if let renderFailureObserver {
120
+ VoltraDynamicLiveActivityRenderFailureReporter.removeChangeObserver(renderFailureObserver)
121
+ self.renderFailureObserver = nil
122
+ }
123
+ handler = nil
124
+ renderFailureListenerCount = 0
80
125
  }
81
126
 
82
127
  deinit {
@@ -15,6 +15,7 @@ import JavaScriptCore
15
15
  /// indefinitely.
16
16
  public enum VoltraJSRenderer {
17
17
  private static var _context: JSContext?
18
+ private static var liveActivitySources: [String: String] = [:]
18
19
  private static let lock = NSLock()
19
20
  private static let TAG = "VoltraJSRenderer"
20
21
 
@@ -30,6 +31,28 @@ public enum VoltraJSRenderer {
30
31
  /// Idempotent: re-evaluating the same widget overwrites the captured exports — used
31
32
  /// by dev-mode hot-reload (always-refetch policy).
32
33
  public static func evaluateBundle(source: String, widgetId: String) -> Bool {
34
+ evaluateBundle(source: source, id: widgetId, registryName: "__voltraWidgets", kind: "widget")
35
+ }
36
+
37
+ /// Evaluate a Dynamic Live Activity bundle in the shared JSContext. Its exports
38
+ /// are captured separately from Dynamic Widgets so both collections may use the
39
+ /// same definition ID without overwriting one another.
40
+ public static func evaluateLiveActivityBundle(source: String, definitionId: String) -> Bool {
41
+ let evaluated = evaluateBundle(
42
+ source: source,
43
+ id: definitionId,
44
+ registryName: "__voltraDynamicLiveActivities",
45
+ kind: "live activity"
46
+ )
47
+ if evaluated {
48
+ lock.lock()
49
+ liveActivitySources[definitionId] = source
50
+ lock.unlock()
51
+ }
52
+ return evaluated
53
+ }
54
+
55
+ private static func evaluateBundle(source: String, id: String, registryName: String, kind: String) -> Bool {
33
56
  lock.lock()
34
57
  defer { lock.unlock() }
35
58
 
@@ -38,7 +61,7 @@ public enum VoltraJSRenderer {
38
61
  return false
39
62
  }
40
63
 
41
- let escapedId = jsStringLiteral(widgetId)
64
+ let escapedId = jsStringLiteral(id)
42
65
  // Metro emits the bundle's entry invocation as `__r(<entryModuleId>);` near the
43
66
  // end of the file (before the sourcemap/sourceURL comments). The entry id is NOT
44
67
  // always 0 — when Metro serves multiple widget bundles from the same process, it
@@ -50,34 +73,34 @@ public enum VoltraJSRenderer {
50
73
  let wrapped = """
51
74
  \(source)
52
75
  ;(function () {
53
- if (!globalThis.__voltraWidgets) { globalThis.__voltraWidgets = {}; }
54
- globalThis.__voltraWidgets[\(escapedId)] = __r(\(entryModuleId));
76
+ if (!globalThis.\(registryName)) { globalThis.\(registryName) = {}; }
77
+ globalThis.\(registryName)[\(escapedId)] = __r(\(entryModuleId));
55
78
  })();
56
79
  """
57
80
 
58
81
  ctx.exception = nil
59
82
  ctx.evaluateScript(wrapped)
60
83
  if let message = exceptionMessage(ctx) {
61
- VoltraLogger.widget.error("[\(TAG)] Bundle eval failed for widgetId=\(widgetId): \(message)")
84
+ VoltraLogger.widget.error("[\(TAG)] Bundle eval failed for \(kind) id=\(id): \(message)")
62
85
  return false
63
86
  }
64
87
 
65
88
  // Verify the bootstrap captured the exports correctly
66
89
  guard
67
- let registry = ctx.objectForKeyedSubscript("__voltraWidgets"),
90
+ let registry = ctx.objectForKeyedSubscript(registryName),
68
91
  !registry.isUndefined,
69
- let widget = registry.objectForKeyedSubscript(widgetId),
70
- !widget.isUndefined,
71
- let renderFn = widget.objectForKeyedSubscript("render"),
92
+ let entry = registry.objectForKeyedSubscript(id),
93
+ !entry.isUndefined,
94
+ let renderFn = entry.objectForKeyedSubscript("render"),
72
95
  renderFn.isObject,
73
96
  renderFn.objectForKeyedSubscript("call") != nil
74
97
  else {
75
- VoltraLogger.widget.error("[\(TAG)] Bundle evaluated but did not expose render() for widgetId=\(widgetId)")
98
+ VoltraLogger.widget.error("[\(TAG)] Bundle evaluated but did not expose render() for \(kind) id=\(id)")
76
99
  return false
77
100
  }
78
101
  _ = renderFn
79
102
 
80
- VoltraLogger.widget.info("[\(TAG)] Bundle evaluated for widgetId=\(widgetId) (\(source.count) chars)")
103
+ VoltraLogger.widget.info("[\(TAG)] Bundle evaluated for \(kind) id=\(id) (\(source.count) chars)")
81
104
  return true
82
105
  }
83
106
 
@@ -90,11 +113,31 @@ public enum VoltraJSRenderer {
90
113
  /// evaluation never ran. The View calls this before `render()` so rendering never depends on
91
114
  /// which process evaluated the bundle.
92
115
  public static func ensureEvaluated(widgetId: String, source: String) -> Bool {
116
+ ensureEvaluated(source: source, id: widgetId, registryName: "__voltraWidgets", kind: "widget")
117
+ }
118
+
119
+ public static func ensureLiveActivityEvaluated(definitionId: String, source: String) -> Bool {
120
+ lock.lock()
121
+ let currentSource = liveActivitySources[definitionId]
122
+ let hasRender =
123
+ _context?
124
+ .objectForKeyedSubscript("__voltraDynamicLiveActivities")?
125
+ .objectForKeyedSubscript(definitionId)?
126
+ .objectForKeyedSubscript("render")?
127
+ .isObject ?? false
128
+ lock.unlock()
129
+ if hasRender, currentSource == source {
130
+ return true
131
+ }
132
+ return evaluateLiveActivityBundle(source: source, definitionId: definitionId)
133
+ }
134
+
135
+ private static func ensureEvaluated(source: String, id: String, registryName: String, kind: String) -> Bool {
93
136
  lock.lock()
94
137
  let alreadyEvaluated =
95
138
  _context?
96
- .objectForKeyedSubscript("__voltraWidgets")?
97
- .objectForKeyedSubscript(widgetId)?
139
+ .objectForKeyedSubscript(registryName)?
140
+ .objectForKeyedSubscript(id)?
98
141
  .objectForKeyedSubscript("render")?
99
142
  .isObject ?? false
100
143
  lock.unlock()
@@ -102,7 +145,7 @@ public enum VoltraJSRenderer {
102
145
  if alreadyEvaluated {
103
146
  return true
104
147
  }
105
- return evaluateBundle(source: source, widgetId: widgetId)
148
+ return evaluateBundle(source: source, id: id, registryName: registryName, kind: kind)
106
149
  }
107
150
 
108
151
  /// Invoke the previously-evaluated widget's `render(propsJSON, envJSON)` function and
@@ -114,39 +157,64 @@ public enum VoltraJSRenderer {
114
157
  widgetId: String,
115
158
  propsJSON: String,
116
159
  envJSON: String
160
+ ) -> String? {
161
+ render(id: widgetId, propsJSON: propsJSON, envJSON: envJSON, registryName: "__voltraWidgets", kind: "widget")
162
+ }
163
+
164
+ /// Render a Dynamic Live Activity from its separately registered definition.
165
+ public static func renderLiveActivity(
166
+ definitionId: String,
167
+ propsJSON: String,
168
+ envJSON: String
169
+ ) -> String? {
170
+ render(
171
+ id: definitionId,
172
+ propsJSON: propsJSON,
173
+ envJSON: envJSON,
174
+ registryName: "__voltraDynamicLiveActivities",
175
+ kind: "live activity"
176
+ )
177
+ }
178
+
179
+ private static func render(
180
+ id: String,
181
+ propsJSON: String,
182
+ envJSON: String,
183
+ registryName: String,
184
+ kind: String
117
185
  ) -> String? {
118
186
  lock.lock()
119
187
  defer { lock.unlock() }
120
188
 
121
189
  guard let ctx = _context else {
122
- VoltraLogger.widget.error("[\(TAG)] render(\(widgetId)): no JSContext (call evaluateBundle first)")
190
+ VoltraLogger.widget.error("[\(TAG)] render \(kind) id=\(id): no JSContext (call evaluateBundle first)")
123
191
  return nil
124
192
  }
125
193
 
126
194
  guard
127
- let registry = ctx.objectForKeyedSubscript("__voltraWidgets"),
195
+ let registry = ctx.objectForKeyedSubscript(registryName),
128
196
  !registry.isUndefined,
129
- let widget = registry.objectForKeyedSubscript(widgetId),
130
- !widget.isUndefined,
131
- let renderFn = widget.objectForKeyedSubscript("render"),
197
+ let entry = registry.objectForKeyedSubscript(id),
198
+ !entry.isUndefined,
199
+ let renderFn = entry.objectForKeyedSubscript("render"),
132
200
  renderFn.isObject
133
201
  else {
134
- VoltraLogger.widget.error("[\(TAG)] render(\(widgetId)): no captured render() — bundle not evaluated?")
202
+ VoltraLogger.widget.error("[\(TAG)] render \(kind) id=\(id): no captured render() — bundle not evaluated?")
135
203
  return nil
136
204
  }
137
205
 
138
206
  ctx.exception = nil
139
207
  guard let result = renderFn.call(withArguments: [propsJSON, envJSON]) else {
140
- VoltraLogger.widget.error("[\(TAG)] render(\(widgetId)): call returned nil")
208
+ VoltraLogger.widget.error("[\(TAG)] render \(kind) id=\(id): call returned nil")
141
209
  return nil
142
210
  }
143
211
  if let message = exceptionMessage(ctx) {
144
- VoltraLogger.widget.error("[\(TAG)] render(\(widgetId)) threw: \(message)")
212
+ VoltraLogger.widget.error("[\(TAG)] render \(kind) id=\(id) threw: \(message)")
145
213
  return nil
146
214
  }
147
215
 
148
216
  guard result.isString else {
149
- VoltraLogger.widget.error("[\(TAG)] render(\(widgetId)) did not return a string")
217
+ VoltraLogger.widget.error("[\(TAG)] render \(kind) id=\(id) did not return a string")
150
218
  return nil
151
219
  }
152
220
  return result.toString()
@@ -155,7 +223,9 @@ public enum VoltraJSRenderer {
155
223
  // MARK: - Lifecycle
156
224
 
157
225
  private static func context() -> JSContext? {
158
- if let existing = _context { return existing }
226
+ if let existing = _context {
227
+ return existing
228
+ }
159
229
  guard let ctx = JSContext() else {
160
230
  VoltraLogger.widget.error("[\(TAG)] Failed to create JSContext")
161
231
  return nil