@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,370 @@
1
+ import ActivityKit
2
+ import Foundation
3
+ import SwiftUI
4
+ import WidgetKit
5
+
6
+ /// Renders generated Dynamic Live Activity configurations through their bundled
7
+ /// JavaScript definitions. Failures intentionally become `EmptyView`: a remote
8
+ /// ActivityKit start may already exist and a later update can recover it.
9
+ public enum VoltraDynamicLiveActivityRenderer {
10
+ public static func lockScreen<Attributes: VoltraDynamicLiveActivityDefinition>(
11
+ definitionId: String,
12
+ context: ActivityViewContext<Attributes>
13
+ ) -> some View {
14
+ if #available(iOS 18.0, *) {
15
+ return VoltraDynamicLiveActivityAdaptiveLockScreenView(definitionId: definitionId, context: context)
16
+ }
17
+ return VoltraDynamicLiveActivityLockScreenView(definitionId: definitionId, context: context)
18
+ }
19
+
20
+ public static func dynamicIsland<Attributes: VoltraDynamicLiveActivityDefinition>(
21
+ definitionId: String,
22
+ context: ActivityViewContext<Attributes>
23
+ ) -> DynamicIsland {
24
+ DynamicIsland {
25
+ DynamicIslandExpandedRegion(.leading) {
26
+ VoltraDynamicLiveActivityDynamicIslandRegionView(
27
+ definitionId: definitionId,
28
+ context: context,
29
+ region: .islandExpandedLeading
30
+ )
31
+ }
32
+ DynamicIslandExpandedRegion(.trailing) {
33
+ VoltraDynamicLiveActivityDynamicIslandRegionView(
34
+ definitionId: definitionId,
35
+ context: context,
36
+ region: .islandExpandedTrailing
37
+ )
38
+ }
39
+ DynamicIslandExpandedRegion(.center) {
40
+ VoltraDynamicLiveActivityDynamicIslandRegionView(
41
+ definitionId: definitionId,
42
+ context: context,
43
+ region: .islandExpandedCenter
44
+ )
45
+ }
46
+ DynamicIslandExpandedRegion(.bottom) {
47
+ VoltraDynamicLiveActivityDynamicIslandRegionView(
48
+ definitionId: definitionId,
49
+ context: context,
50
+ region: .islandExpandedBottom
51
+ )
52
+ }
53
+ } compactLeading: {
54
+ VoltraDynamicLiveActivityDynamicIslandRegionView(
55
+ definitionId: definitionId,
56
+ context: context,
57
+ region: .islandCompactLeading
58
+ )
59
+ } compactTrailing: {
60
+ VoltraDynamicLiveActivityDynamicIslandRegionView(
61
+ definitionId: definitionId,
62
+ context: context,
63
+ region: .islandCompactTrailing
64
+ )
65
+ } minimal: {
66
+ VoltraDynamicLiveActivityDynamicIslandRegionView(
67
+ definitionId: definitionId,
68
+ context: context,
69
+ region: .islandMinimal
70
+ )
71
+ }
72
+
73
+ // DynamicIsland itself is not a View and cannot read SwiftUI Environment.
74
+ // Do not execute a definition against fabricated values solely to obtain a
75
+ // keyline tint; its region views below read the actual environment.
76
+ }
77
+
78
+ fileprivate static func resolve<Attributes: VoltraDynamicLiveActivityDefinition>(
79
+ definitionId: String,
80
+ context: ActivityViewContext<Attributes>,
81
+ activityFamily: String?,
82
+ colorScheme: ColorScheme? = nil,
83
+ locale: Locale = .current,
84
+ widgetRenderingMode: WidgetRenderingMode = .fullColor
85
+ ) -> VoltraDynamicLiveActivityResolvedContent {
86
+ guard VoltraDynamicLiveActivityRegistry.shared.contains(definitionId) else {
87
+ logFailure(definitionId: definitionId, activityName: context.attributes.name, message: "Definition is missing from the installed catalog")
88
+ return .empty
89
+ }
90
+ guard let propsJSON = DynamicLiveActivityPropsCodec.encode(context.state.props) else {
91
+ logFailure(definitionId: definitionId, activityName: context.attributes.name, message: "Could not encode content-state props")
92
+ return .empty
93
+ }
94
+ // WidgetKit asks each Dynamic Island region (and each presentation) to render
95
+ // independently, all with identical state/environment. Cache the resolved
96
+ // variants shape so those siblings share one bundle load, one JS evaluation,
97
+ // and one failure diagnostic. `revision` changes on every hot reload, so a
98
+ // DEBUG reload busts the cache; in release it is constant.
99
+ let cacheKey = VoltraDynamicLiveActivityRenderCache.Key(
100
+ activityId: context.activityID,
101
+ definitionId: definitionId,
102
+ revision: VoltraDynamicLiveActivityBundleSource.revision(definitionId: definitionId),
103
+ props: propsJSON,
104
+ activityFamily: activityFamily,
105
+ colorScheme: String(describing: colorScheme ?? .light),
106
+ locale: locale.identifier,
107
+ widgetRenderingMode: String(describing: widgetRenderingMode),
108
+ isStale: context.isStale
109
+ )
110
+ return VoltraDynamicLiveActivityRenderCache.shared.resolve(cacheKey) {
111
+ resolveUncached(
112
+ definitionId: definitionId,
113
+ context: context,
114
+ propsJSON: propsJSON,
115
+ activityFamily: activityFamily,
116
+ colorScheme: colorScheme,
117
+ locale: locale,
118
+ widgetRenderingMode: widgetRenderingMode
119
+ )
120
+ }
121
+ }
122
+
123
+ private static func resolveUncached<Attributes: VoltraDynamicLiveActivityDefinition>(
124
+ definitionId: String,
125
+ context: ActivityViewContext<Attributes>,
126
+ propsJSON: String,
127
+ activityFamily: String?,
128
+ colorScheme: ColorScheme?,
129
+ locale: Locale,
130
+ widgetRenderingMode: WidgetRenderingMode
131
+ ) -> VoltraDynamicLiveActivityResolvedContent {
132
+ let environmentJSON = VoltraDynamicLiveActivityEnvironmentBuilder.build(
133
+ date: Date(),
134
+ colorScheme: colorScheme,
135
+ locale: locale,
136
+ widgetRenderingMode: widgetRenderingMode,
137
+ isStale: context.isStale,
138
+ activityFamily: activityFamily
139
+ )
140
+
141
+ do {
142
+ let source = try VoltraDynamicLiveActivityBundleSource.load(definitionId: definitionId)
143
+ guard VoltraJSRenderer.ensureLiveActivityEvaluated(definitionId: definitionId, source: source) else {
144
+ logFailure(definitionId: definitionId, activityName: context.attributes.name, message: "Could not evaluate definition bundle")
145
+ return .empty
146
+ }
147
+ guard let renderedJSON = VoltraJSRenderer.renderLiveActivity(
148
+ definitionId: definitionId,
149
+ propsJSON: propsJSON,
150
+ envJSON: environmentJSON
151
+ ) else {
152
+ logFailure(definitionId: definitionId, activityName: context.attributes.name, message: "Definition render failed")
153
+ return .empty
154
+ }
155
+ let payload = try VoltraLiveActivityPayload(jsonString: renderedJSON)
156
+ return VoltraDynamicLiveActivityResolvedContent(payload: payload)
157
+ } catch {
158
+ logFailure(definitionId: definitionId, activityName: context.attributes.name, message: error.localizedDescription)
159
+ return .empty
160
+ }
161
+ }
162
+
163
+ fileprivate static func logFailure(definitionId: String, activityName: String, message: String) {
164
+ VoltraDynamicLiveActivityRenderFailureReporter.record(
165
+ activityName: activityName,
166
+ definitionId: definitionId,
167
+ message: message
168
+ )
169
+ }
170
+ }
171
+
172
+ private struct VoltraDynamicLiveActivityDynamicIslandRegionView<Attributes: VoltraDynamicLiveActivityDefinition>: View {
173
+ let definitionId: String
174
+ let context: ActivityViewContext<Attributes>
175
+ let region: VoltraRegion
176
+
177
+ @Environment(\.colorScheme) private var colorScheme
178
+ @Environment(\.locale) private var locale
179
+ @Environment(\.widgetRenderingMode) private var widgetRenderingMode
180
+
181
+ var body: some View {
182
+ VoltraDynamicLiveActivityRenderer.resolve(
183
+ definitionId: definitionId,
184
+ context: context,
185
+ activityFamily: nil,
186
+ colorScheme: colorScheme,
187
+ locale: locale,
188
+ widgetRenderingMode: widgetRenderingMode
189
+ )
190
+ .view(for: region, activityId: context.activityID, deepLink: context.attributes.deepLinkUrl)
191
+ }
192
+ }
193
+
194
+ private struct VoltraDynamicLiveActivityLockScreenView<Attributes: VoltraDynamicLiveActivityDefinition>: View {
195
+ let definitionId: String
196
+ let context: ActivityViewContext<Attributes>
197
+
198
+ @Environment(\.colorScheme) private var colorScheme
199
+ @Environment(\.locale) private var locale
200
+ @Environment(\.widgetRenderingMode) private var widgetRenderingMode
201
+
202
+ var body: some View {
203
+ let content = VoltraDynamicLiveActivityRenderer.resolve(
204
+ definitionId: definitionId,
205
+ context: context,
206
+ activityFamily: nil,
207
+ colorScheme: colorScheme,
208
+ locale: locale,
209
+ widgetRenderingMode: widgetRenderingMode
210
+ )
211
+ if let tint = content.payload?.activityBackgroundTint, let color = JSColorParser.parse(tint) {
212
+ content.view(for: .lockScreen, activityId: context.activityID, deepLink: context.attributes.deepLinkUrl)
213
+ .activityBackgroundTint(color)
214
+ } else {
215
+ content.view(for: .lockScreen, activityId: context.activityID, deepLink: context.attributes.deepLinkUrl)
216
+ }
217
+ }
218
+ }
219
+
220
+ @available(iOS 18.0, *)
221
+ private struct VoltraDynamicLiveActivityAdaptiveLockScreenView<Attributes: VoltraDynamicLiveActivityDefinition>: View {
222
+ let definitionId: String
223
+ let context: ActivityViewContext<Attributes>
224
+
225
+ @Environment(\.activityFamily) private var activityFamily
226
+ @Environment(\.colorScheme) private var colorScheme
227
+ @Environment(\.locale) private var locale
228
+ @Environment(\.widgetRenderingMode) private var widgetRenderingMode
229
+
230
+ var body: some View {
231
+ let content = VoltraDynamicLiveActivityRenderer.resolve(
232
+ definitionId: definitionId,
233
+ context: context,
234
+ activityFamily: String(describing: activityFamily),
235
+ colorScheme: colorScheme,
236
+ locale: locale,
237
+ widgetRenderingMode: widgetRenderingMode
238
+ )
239
+ familyContent(content: content)
240
+ .voltraIfLet(content.payload?.activityBackgroundTint.flatMap { JSColorParser.parse($0) }) { view, color in
241
+ view.activityBackgroundTint(color)
242
+ }
243
+ }
244
+
245
+ @ViewBuilder
246
+ private func familyContent(content: VoltraDynamicLiveActivityResolvedContent) -> some View {
247
+ if activityFamily == .small {
248
+ smallFamilyContent(content: content)
249
+ } else {
250
+ content.view(for: .lockScreen, activityId: context.activityID, deepLink: context.attributes.deepLinkUrl)
251
+ }
252
+ }
253
+
254
+ @ViewBuilder
255
+ private func smallFamilyContent(content: VoltraDynamicLiveActivityResolvedContent) -> some View {
256
+ if content.hasContent(for: .supplementalActivityFamiliesSmall) {
257
+ content.view(
258
+ for: .supplementalActivityFamiliesSmall,
259
+ activityId: context.activityID,
260
+ deepLink: context.attributes.deepLinkUrl
261
+ )
262
+ } else if content.hasContent(for: .islandCompactLeading) || content.hasContent(for: .islandCompactTrailing) {
263
+ HStack(spacing: 0) {
264
+ content.view(for: .islandCompactLeading, activityId: context.activityID, deepLink: context.attributes.deepLinkUrl)
265
+ Spacer()
266
+ content.view(for: .islandCompactTrailing, activityId: context.activityID, deepLink: context.attributes.deepLinkUrl)
267
+ }
268
+ .frame(maxWidth: .infinity)
269
+ }
270
+ }
271
+ }
272
+
273
+ private struct VoltraDynamicLiveActivityResolvedContent {
274
+ static let empty = VoltraDynamicLiveActivityResolvedContent(payload: nil)
275
+
276
+ let payload: VoltraLiveActivityPayload?
277
+
278
+ func hasContent(for region: VoltraRegion) -> Bool {
279
+ !(payload?.regions[region] ?? []).isEmpty
280
+ }
281
+
282
+ @ViewBuilder
283
+ func view(for region: VoltraRegion, activityId: String, deepLink: String?) -> some View {
284
+ if let nodes = payload?.regions[region], !nodes.isEmpty {
285
+ let root: VoltraNode = nodes.count == 1 ? nodes[0] : .array(nodes)
286
+ Voltra(root: root, activityId: activityId)
287
+ .voltraIfLet(deepLink.flatMap(VoltraDeepLinkResolver.resolveUrl)) { view, url in view.widgetURL(url) }
288
+ }
289
+ }
290
+ }
291
+
292
+ /// WidgetKit may independently ask each Dynamic Island region to render. Cache
293
+ /// the complete variants shape per ActivityKit state/environment so all regions
294
+ /// share one JS evaluation and one failure diagnostic. `Key.revision` ties each
295
+ /// entry to the bundle bytes it was rendered from, so a hot reload (which
296
+ /// rewrites the source) never serves a stale render.
297
+ private final class VoltraDynamicLiveActivityRenderCache: @unchecked Sendable {
298
+ struct Key: Hashable {
299
+ let activityId: String
300
+ let definitionId: String
301
+ let revision: String
302
+ let props: String
303
+ let activityFamily: String?
304
+ let colorScheme: String
305
+ let locale: String
306
+ let widgetRenderingMode: String
307
+ let isStale: Bool
308
+ }
309
+
310
+ static let shared = VoltraDynamicLiveActivityRenderCache()
311
+ private let lock = NSLock()
312
+ private var values: [Key: VoltraDynamicLiveActivityResolvedContent] = [:]
313
+
314
+ func resolve(_ key: Key, render: () -> VoltraDynamicLiveActivityResolvedContent) -> VoltraDynamicLiveActivityResolvedContent {
315
+ lock.lock()
316
+ defer { lock.unlock() }
317
+ if let existing = values[key] {
318
+ return existing
319
+ }
320
+ let resolved = render()
321
+ if values.count >= 100 {
322
+ values.removeAll(keepingCapacity: true)
323
+ }
324
+ values[key] = resolved
325
+ return resolved
326
+ }
327
+ }
328
+
329
+ private enum VoltraDynamicLiveActivityEnvironmentBuilder {
330
+ static func build(
331
+ date: Date,
332
+ colorScheme: ColorScheme?,
333
+ locale: Locale,
334
+ widgetRenderingMode: WidgetRenderingMode,
335
+ isStale: Bool,
336
+ activityFamily: String?
337
+ ) -> String {
338
+ #if DEBUG
339
+ let isDev = true
340
+ let metroURL: String? = VoltraWidgetDefaults.devServerURL() ?? "http://localhost:8081"
341
+ #else
342
+ let isDev = false
343
+ let metroURL: String? = nil
344
+ #endif
345
+ let build: [String: Any] = [
346
+ "isDev": isDev,
347
+ "metroUrl": metroURL as Any,
348
+ "appVersion": Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "unknown",
349
+ "voltraVersion": VoltraConfig.voltraVersion(),
350
+ ]
351
+ var environment: [String: Any] = [
352
+ "date": Int(date.timeIntervalSince1970 * 1000),
353
+ "colorScheme": String(describing: colorScheme ?? .light),
354
+ "locale": locale.identifier,
355
+ "widgetRenderingMode": String(describing: widgetRenderingMode),
356
+ "build": build,
357
+ "isStale": isStale,
358
+ ]
359
+ if let activityFamily {
360
+ environment["activityFamily"] = activityFamily
361
+ }
362
+ guard
363
+ let data = try? JSONSerialization.data(withJSONObject: environment),
364
+ let json = String(data: data, encoding: .utf8)
365
+ else {
366
+ return "{}"
367
+ }
368
+ return json
369
+ }
370
+ }
@@ -0,0 +1,87 @@
1
+ import Foundation
2
+
3
+ #if os(iOS)
4
+ import ActivityKit
5
+ #endif
6
+
7
+ /// The JSON-compatible value type used by Dynamic Live Activity props.
8
+ ///
9
+ /// This deliberately models only values that can cross the ActivityKit and
10
+ /// JavaScript boundaries without a definition-specific schema.
11
+ public indirect enum VoltraDynamicLiveActivityJSONValue: Codable, Hashable {
12
+ case null
13
+ case bool(Bool)
14
+ case number(Double)
15
+ case string(String)
16
+ case array([VoltraDynamicLiveActivityJSONValue])
17
+ case object([String: VoltraDynamicLiveActivityJSONValue])
18
+
19
+ public init(from decoder: Decoder) throws {
20
+ let container = try decoder.singleValueContainer()
21
+ if container.decodeNil() {
22
+ self = .null
23
+ } else if let value = try? container.decode(Bool.self) {
24
+ self = .bool(value)
25
+ } else if let value = try? container.decode(Double.self) {
26
+ self = .number(value)
27
+ } else if let value = try? container.decode(String.self) {
28
+ self = .string(value)
29
+ } else if let value = try? container.decode([VoltraDynamicLiveActivityJSONValue].self) {
30
+ self = .array(value)
31
+ } else if let value = try? container.decode([String: VoltraDynamicLiveActivityJSONValue].self) {
32
+ self = .object(value)
33
+ } else {
34
+ throw DecodingError.dataCorruptedError(
35
+ in: container,
36
+ debugDescription: "Expected a JSON-compatible value"
37
+ )
38
+ }
39
+ }
40
+
41
+ public func encode(to encoder: Encoder) throws {
42
+ var container = encoder.singleValueContainer()
43
+ switch self {
44
+ case .null:
45
+ try container.encodeNil()
46
+ case let .bool(value):
47
+ try container.encode(value)
48
+ case let .number(value):
49
+ try container.encode(value)
50
+ case let .string(value):
51
+ try container.encode(value)
52
+ case let .array(value):
53
+ try container.encode(value)
54
+ case let .object(value):
55
+ try container.encode(value)
56
+ }
57
+ }
58
+ }
59
+
60
+ /// The generic state shared by every generated Dynamic Live Activity type.
61
+ /// Every ActivityKit update replaces the complete props record.
62
+ public struct VoltraDynamicLiveActivityContentState: Codable, Hashable {
63
+ public let props: [String: VoltraDynamicLiveActivityJSONValue]
64
+
65
+ public init(props: [String: VoltraDynamicLiveActivityJSONValue]) {
66
+ self.props = props
67
+ }
68
+ }
69
+
70
+ public enum VoltraDynamicLiveActivityError: Error {
71
+ case unknownDefinition(String)
72
+ case payloadTooLarge(size: Int)
73
+ case rendererMismatch
74
+ case resourceUnavailable(Error)
75
+ }
76
+
77
+ // ActivityKit is unavailable to the macOS SwiftPM test target. The JSON props
78
+ // contract above remains independently testable there.
79
+ #if os(iOS)
80
+ /// Metadata and static attributes supplied by each generated definition.
81
+ public protocol VoltraDynamicLiveActivityDefinition: ActivityAttributes where ContentState == VoltraDynamicLiveActivityContentState {
82
+ static var definitionId: String { get }
83
+ var name: String { get }
84
+ var deepLinkUrl: String? { get }
85
+ init(name: String, deepLinkUrl: String?)
86
+ }
87
+ #endif
@@ -0,0 +1,19 @@
1
+ import Foundation
2
+
3
+ /// Reconciles a persisted cross-engine creation order with ActivityKit's
4
+ /// currently active IDs, pruning ended activities and appending new discoveries.
5
+ public enum VoltraLiveActivityOrder {
6
+ public static func reconcile(previous: [String], active: [String]) -> [String] {
7
+ let activeIds = Set(active)
8
+ var seen = Set<String>()
9
+ var result: [String] = []
10
+
11
+ for id in previous where activeIds.contains(id) && seen.insert(id).inserted {
12
+ result.append(id)
13
+ }
14
+ for id in active where seen.insert(id).inserted {
15
+ result.append(id)
16
+ }
17
+ return result
18
+ }
19
+ }
@@ -41,6 +41,12 @@ public struct VoltraClientWidgetEntry: TimelineEntry {
41
41
  /// process, where the provider's process-static JSContext is empty. The View re-evaluates from
42
42
  /// this source so `render()` always has the widget's function available in its own process.
43
43
  public let bundleSource: String?
44
+ /// How the last server fetch went, as `env.serverUpdate`, for a widget that has a `serverUpdate`
45
+ /// in app.json. `nil` for every other Dynamic Widget, and the reason `env.serverUpdate` is
46
+ /// `undefined` there. Carried on the entry rather than read at render time because the provider
47
+ /// is the thing that knows the fetch outcome, and WidgetKit re-renders archived entries in a
48
+ /// fresh process.
49
+ public let serverUpdateJSON: String?
44
50
 
45
51
  public init(
46
52
  date: Date,
@@ -48,7 +54,8 @@ public struct VoltraClientWidgetEntry: TimelineEntry {
48
54
  bundleReady: Bool,
49
55
  errorMessage: String? = nil,
50
56
  configuration: [String: String] = [:],
51
- bundleSource: String? = nil
57
+ bundleSource: String? = nil,
58
+ serverUpdateJSON: String? = nil
52
59
  ) {
53
60
  self.date = date
54
61
  self.widgetId = widgetId
@@ -56,6 +63,20 @@ public struct VoltraClientWidgetEntry: TimelineEntry {
56
63
  self.errorMessage = errorMessage
57
64
  self.configuration = configuration
58
65
  self.bundleSource = bundleSource
66
+ self.serverUpdateJSON = serverUpdateJSON
67
+ }
68
+
69
+ /// The same entry, told how the server side is doing.
70
+ public func withServerUpdate(_ serverUpdateJSON: String?) -> VoltraClientWidgetEntry {
71
+ VoltraClientWidgetEntry(
72
+ date: date,
73
+ widgetId: widgetId,
74
+ bundleReady: bundleReady,
75
+ errorMessage: errorMessage,
76
+ configuration: configuration,
77
+ bundleSource: bundleSource,
78
+ serverUpdateJSON: serverUpdateJSON
79
+ )
59
80
  }
60
81
  }
61
82
 
@@ -234,10 +255,12 @@ public enum VoltraClientWidgetEnvBuilder {
234
255
  widgetRenderingMode: WidgetRenderingMode,
235
256
  showsWidgetContainerBackground: Bool,
236
257
  locale: Locale,
237
- configuration: [String: String]
258
+ configuration: [String: String],
259
+ serverUpdateJSON: String? = nil
238
260
  ) -> String {
239
261
  let timestampMs = Int(date.timeIntervalSince1970 * 1000)
240
262
  let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "unknown"
263
+ let voltraVersion = VoltraConfig.voltraVersion()
241
264
 
242
265
  #if DEBUG
243
266
  let isDev = true
@@ -253,7 +276,7 @@ public enum VoltraClientWidgetEnvBuilder {
253
276
  "isDev": \(isDev),
254
277
  "metroUrl": \(metroUrlLiteral),
255
278
  "appVersion": \(jsonString(appVersion)),
256
- "voltraVersion": \(jsonString("1.4.1"))
279
+ "voltraVersion": \(jsonString(voltraVersion))
257
280
  }
258
281
  """
259
282
 
@@ -267,6 +290,15 @@ public enum VoltraClientWidgetEnvBuilder {
267
290
  configurationJSON = "{ \(entries) }"
268
291
  }
269
292
 
293
+ // Only a server-driven widget gets env.serverUpdate; leaving the key out entirely is what
294
+ // makes it `undefined` for every other Dynamic Widget.
295
+ let serverUpdateEntry = serverUpdateJSON.map { ",\n \"serverUpdate\": \($0)" } ?? ""
296
+
297
+ // The instance key (ADR 0007): the hash of this placement's merged configuration, or absent
298
+ // for a widget with no configuration parameters at all — matching what the request builder
299
+ // sends and what WidgetScope.of(widgetId, configuration:) resolves to.
300
+ let instanceEntry = WidgetCanonicalConfiguration.key(configuration).map { ",\n \"instance\": \(jsonString($0))" } ?? ""
301
+
270
302
  return """
271
303
  {
272
304
  "date": \(timestampMs),
@@ -276,7 +308,7 @@ public enum VoltraClientWidgetEnvBuilder {
276
308
  "widgetRenderingMode": \(jsonString(renderingModeString(widgetRenderingMode))),
277
309
  "showsWidgetContainerBackground": \(showsWidgetContainerBackground),
278
310
  "configuration": \(configurationJSON),
279
- "build": \(buildJSON)
311
+ "build": \(buildJSON)\(serverUpdateEntry)\(instanceEntry)
280
312
  }
281
313
  """
282
314
  }
@@ -349,12 +381,28 @@ public struct VoltraClientWidgetContentView: View {
349
381
  widgetRenderingMode: widgetRenderingMode,
350
382
  showsWidgetContainerBackground: showsWidgetContainerBackground,
351
383
  locale: locale,
352
- configuration: entry.configuration
384
+ configuration: entry.configuration,
385
+ serverUpdateJSON: entry.serverUpdateJSON
353
386
  )
354
- if let resolved = VoltraJSRenderer.render(
355
- widgetId: entry.widgetId,
356
- propsJSON: "{}",
357
- envJSON: envJSON
387
+ // This placement's scope (ADR 0007): an instance of its merged configuration, or the plain
388
+ // widget scope when it has none — which is also every widget's scope before this ADR.
389
+ let scope = WidgetScope.of(entry.widgetId, configuration: entry.configuration)
390
+ let dynamicWidgetPropsStore = DynamicWidgetPropsStore()
391
+ let dynamicWidgetRenderCoordinator = DynamicWidgetRenderCoordinator(
392
+ // Reads the instance slot for `scope`'s key, falling back to the widget slot when the
393
+ // placement has not fetched yet or has no configuration.
394
+ dynamicWidgetPropsProvider: { _ in dynamicWidgetPropsStore.dynamicWidgetProps(for: scope) },
395
+ dynamicWidgetRuntimeBoundary: { dynamicWidgetID, dynamicWidgetPropsJSON, dynamicWidgetEnvironmentJSON in
396
+ VoltraJSRenderer.render(
397
+ widgetId: dynamicWidgetID,
398
+ propsJSON: dynamicWidgetPropsJSON,
399
+ envJSON: dynamicWidgetEnvironmentJSON
400
+ )
401
+ }
402
+ )
403
+ if let resolved = dynamicWidgetRenderCoordinator.renderDynamicWidget(
404
+ dynamicWidgetID: entry.widgetId,
405
+ dynamicWidgetEnvironmentJSON: envJSON
358
406
  ), let node = parseResolvedNode(jsonString: resolved) {
359
407
  return VoltraHomeWidgetEntry(date: entry.date, rootNode: node, widgetId: entry.widgetId)
360
408
  }
@@ -368,7 +416,9 @@ public struct VoltraClientWidgetContentView: View {
368
416
  private func parseResolvedNode(jsonString: String) -> VoltraNode? {
369
417
  guard let json = try? JSONValue.parse(from: jsonString) else { return nil }
370
418
  let node = VoltraNode.parse(from: json)
371
- if case .empty = node { return nil }
419
+ if case .empty = node {
420
+ return nil
421
+ }
372
422
  return node
373
423
  }
374
424