prismcast 1.10.2 → 1.11.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 (693) hide show
  1. package/dist/app.d.ts +6 -3
  2. package/dist/app.d.ts.map +1 -0
  3. package/dist/app.js +140 -86
  4. package/dist/app.js.map +1 -1
  5. package/dist/browser/blockedPage.d.ts +80 -0
  6. package/dist/browser/blockedPage.d.ts.map +1 -0
  7. package/dist/browser/blockedPage.js +209 -0
  8. package/dist/browser/blockedPage.js.map +1 -0
  9. package/dist/browser/browserSupervisor.d.ts +80 -0
  10. package/dist/browser/browserSupervisor.d.ts.map +1 -0
  11. package/dist/browser/browserSupervisor.js +186 -0
  12. package/dist/browser/browserSupervisor.js.map +1 -0
  13. package/dist/browser/cdp.d.ts +1 -0
  14. package/dist/browser/cdp.d.ts.map +1 -0
  15. package/dist/browser/cdp.js +6 -5
  16. package/dist/browser/cdp.js.map +1 -1
  17. package/dist/browser/channelSelection.d.ts +45 -14
  18. package/dist/browser/channelSelection.d.ts.map +1 -0
  19. package/dist/browser/channelSelection.js +81 -18
  20. package/dist/browser/channelSelection.js.map +1 -1
  21. package/dist/browser/consent.d.ts +152 -0
  22. package/dist/browser/consent.d.ts.map +1 -0
  23. package/dist/browser/consent.js +418 -0
  24. package/dist/browser/consent.js.map +1 -0
  25. package/dist/browser/display.d.ts +1 -0
  26. package/dist/browser/display.d.ts.map +1 -0
  27. package/dist/browser/hlsPlaylistObserver.d.ts +55 -0
  28. package/dist/browser/hlsPlaylistObserver.d.ts.map +1 -0
  29. package/dist/browser/hlsPlaylistObserver.js +132 -0
  30. package/dist/browser/hlsPlaylistObserver.js.map +1 -0
  31. package/dist/browser/index.d.ts +67 -40
  32. package/dist/browser/index.d.ts.map +1 -0
  33. package/dist/browser/index.js +508 -300
  34. package/dist/browser/index.js.map +1 -1
  35. package/dist/browser/launchGovernor.d.ts +89 -0
  36. package/dist/browser/launchGovernor.d.ts.map +1 -0
  37. package/dist/browser/launchGovernor.js +96 -0
  38. package/dist/browser/launchGovernor.js.map +1 -0
  39. package/dist/browser/login.d.ts +10 -2
  40. package/dist/browser/login.d.ts.map +1 -0
  41. package/dist/browser/login.js +36 -5
  42. package/dist/browser/login.js.map +1 -1
  43. package/dist/browser/manifestInterceptor.d.ts +102 -49
  44. package/dist/browser/manifestInterceptor.d.ts.map +1 -0
  45. package/dist/browser/manifestInterceptor.js +219 -237
  46. package/dist/browser/manifestInterceptor.js.map +1 -1
  47. package/dist/browser/pageStaleness.d.ts +30 -0
  48. package/dist/browser/pageStaleness.d.ts.map +1 -0
  49. package/dist/browser/pageStaleness.js +76 -0
  50. package/dist/browser/pageStaleness.js.map +1 -0
  51. package/dist/browser/precaching.d.ts +95 -3
  52. package/dist/browser/precaching.d.ts.map +1 -0
  53. package/dist/browser/precaching.js +499 -65
  54. package/dist/browser/precaching.js.map +1 -1
  55. package/dist/browser/tabNetworkObserver.d.ts +49 -0
  56. package/dist/browser/tabNetworkObserver.d.ts.map +1 -0
  57. package/dist/browser/tabNetworkObserver.js +144 -0
  58. package/dist/browser/tabNetworkObserver.js.map +1 -0
  59. package/dist/browser/tuning/cache.d.ts +61 -0
  60. package/dist/browser/tuning/cache.d.ts.map +1 -0
  61. package/dist/browser/tuning/cache.js +62 -0
  62. package/dist/browser/tuning/cache.js.map +1 -0
  63. package/dist/browser/tuning/comcastPolymer.d.ts +3 -2
  64. package/dist/browser/tuning/comcastPolymer.d.ts.map +1 -0
  65. package/dist/browser/tuning/comcastPolymer.js +79 -102
  66. package/dist/browser/tuning/comcastPolymer.js.map +1 -1
  67. package/dist/browser/tuning/cox.d.ts +1 -0
  68. package/dist/browser/tuning/cox.d.ts.map +1 -0
  69. package/dist/browser/tuning/directv.d.ts +1 -0
  70. package/dist/browser/tuning/directv.d.ts.map +1 -0
  71. package/dist/browser/tuning/directv.js +52 -46
  72. package/dist/browser/tuning/directv.js.map +1 -1
  73. package/dist/browser/tuning/fox.d.ts +1 -0
  74. package/dist/browser/tuning/fox.d.ts.map +1 -0
  75. package/dist/browser/tuning/fox.js +13 -12
  76. package/dist/browser/tuning/fox.js.map +1 -1
  77. package/dist/browser/tuning/gridSearch.d.ts +68 -0
  78. package/dist/browser/tuning/gridSearch.d.ts.map +1 -0
  79. package/dist/browser/tuning/gridSearch.js +85 -0
  80. package/dist/browser/tuning/gridSearch.js.map +1 -0
  81. package/dist/browser/tuning/hbo.d.ts +1 -0
  82. package/dist/browser/tuning/hbo.d.ts.map +1 -0
  83. package/dist/browser/tuning/hbo.js +73 -45
  84. package/dist/browser/tuning/hbo.js.map +1 -1
  85. package/dist/browser/tuning/hulu.d.ts +1 -0
  86. package/dist/browser/tuning/hulu.d.ts.map +1 -0
  87. package/dist/browser/tuning/hulu.js +128 -173
  88. package/dist/browser/tuning/hulu.js.map +1 -1
  89. package/dist/browser/tuning/shared.d.ts +75 -5
  90. package/dist/browser/tuning/shared.d.ts.map +1 -0
  91. package/dist/browser/tuning/shared.js +216 -6
  92. package/dist/browser/tuning/shared.js.map +1 -1
  93. package/dist/browser/tuning/sling.d.ts +1 -0
  94. package/dist/browser/tuning/sling.d.ts.map +1 -0
  95. package/dist/browser/tuning/sling.js +105 -126
  96. package/dist/browser/tuning/sling.js.map +1 -1
  97. package/dist/browser/tuning/spectrum.d.ts +1 -0
  98. package/dist/browser/tuning/spectrum.d.ts.map +1 -0
  99. package/dist/browser/tuning/spectrum.js +80 -125
  100. package/dist/browser/tuning/spectrum.js.map +1 -1
  101. package/dist/browser/tuning/thumbnailRow.d.ts +1 -0
  102. package/dist/browser/tuning/thumbnailRow.d.ts.map +1 -0
  103. package/dist/browser/tuning/thumbnailRow.js +8 -2
  104. package/dist/browser/tuning/thumbnailRow.js.map +1 -1
  105. package/dist/browser/tuning/tileClick.d.ts +1 -0
  106. package/dist/browser/tuning/tileClick.d.ts.map +1 -0
  107. package/dist/browser/tuning/tileClick.js +3 -2
  108. package/dist/browser/tuning/tileClick.js.map +1 -1
  109. package/dist/browser/tuning/xfinity.d.ts +1 -0
  110. package/dist/browser/tuning/xfinity.d.ts.map +1 -0
  111. package/dist/browser/tuning/youtubeTv.d.ts +1 -0
  112. package/dist/browser/tuning/youtubeTv.d.ts.map +1 -0
  113. package/dist/browser/tuning/youtubeTv.js +74 -127
  114. package/dist/browser/tuning/youtubeTv.js.map +1 -1
  115. package/dist/browser/video.d.ts +54 -14
  116. package/dist/browser/video.d.ts.map +1 -0
  117. package/dist/browser/video.js +296 -138
  118. package/dist/browser/video.js.map +1 -1
  119. package/dist/channels/index.d.ts +1 -0
  120. package/dist/channels/index.d.ts.map +1 -0
  121. package/dist/channels/index.js +12 -9
  122. package/dist/channels/index.js.map +1 -1
  123. package/dist/config/channelForm.d.ts +15 -1
  124. package/dist/config/channelForm.d.ts.map +1 -0
  125. package/dist/config/channelForm.js +28 -12
  126. package/dist/config/channelForm.js.map +1 -1
  127. package/dist/config/consistencyProbe.d.ts +1 -0
  128. package/dist/config/consistencyProbe.d.ts.map +1 -0
  129. package/dist/config/consistencyProbe.js +27 -21
  130. package/dist/config/consistencyProbe.js.map +1 -1
  131. package/dist/config/health.d.ts +41 -8
  132. package/dist/config/health.d.ts.map +1 -0
  133. package/dist/config/health.js +189 -38
  134. package/dist/config/health.js.map +1 -1
  135. package/dist/config/index.d.ts +55 -3
  136. package/dist/config/index.d.ts.map +1 -0
  137. package/dist/config/index.js +320 -69
  138. package/dist/config/index.js.map +1 -1
  139. package/dist/config/paths.d.ts +10 -9
  140. package/dist/config/paths.d.ts.map +1 -0
  141. package/dist/config/paths.js +11 -11
  142. package/dist/config/paths.js.map +1 -1
  143. package/dist/config/persistence.context.d.ts +1 -0
  144. package/dist/config/persistence.context.d.ts.map +1 -0
  145. package/dist/config/persistence.d.ts +13 -10
  146. package/dist/config/persistence.d.ts.map +1 -0
  147. package/dist/config/persistence.js +40 -21
  148. package/dist/config/persistence.js.map +1 -1
  149. package/dist/config/presets.d.ts +1 -0
  150. package/dist/config/presets.d.ts.map +1 -0
  151. package/dist/config/profiles.d.ts +22 -15
  152. package/dist/config/profiles.d.ts.map +1 -0
  153. package/dist/config/profiles.js +81 -47
  154. package/dist/config/profiles.js.map +1 -1
  155. package/dist/config/providerLineups.d.ts +47 -0
  156. package/dist/config/providerLineups.d.ts.map +1 -0
  157. package/dist/config/providerLineups.js +137 -0
  158. package/dist/config/providerLineups.js.map +1 -0
  159. package/dist/config/reactivity.d.ts +84 -0
  160. package/dist/config/reactivity.d.ts.map +1 -0
  161. package/dist/config/reactivity.js +226 -0
  162. package/dist/config/reactivity.js.map +1 -0
  163. package/dist/config/servicePacks.d.ts +18 -2
  164. package/dist/config/servicePacks.d.ts.map +1 -0
  165. package/dist/config/servicePacks.js +53 -11
  166. package/dist/config/servicePacks.js.map +1 -1
  167. package/dist/config/services.d.ts +21 -15
  168. package/dist/config/services.d.ts.map +1 -0
  169. package/dist/config/services.js +35 -24
  170. package/dist/config/services.js.map +1 -1
  171. package/dist/config/sites.d.ts +6 -4
  172. package/dist/config/sites.d.ts.map +1 -0
  173. package/dist/config/sites.js +15 -11
  174. package/dist/config/sites.js.map +1 -1
  175. package/dist/config/userChannels.d.ts +129 -30
  176. package/dist/config/userChannels.d.ts.map +1 -0
  177. package/dist/config/userChannels.js +333 -68
  178. package/dist/config/userChannels.js.map +1 -1
  179. package/dist/config/userConfig.d.ts +14 -9
  180. package/dist/config/userConfig.d.ts.map +1 -0
  181. package/dist/config/userConfig.js +87 -25
  182. package/dist/config/userConfig.js.map +1 -1
  183. package/dist/config/userProfiles.d.ts +10 -6
  184. package/dist/config/userProfiles.d.ts.map +1 -0
  185. package/dist/config/userProfiles.js +64 -39
  186. package/dist/config/userProfiles.js.map +1 -1
  187. package/dist/hdhr/channelMap.d.ts +1 -0
  188. package/dist/hdhr/channelMap.d.ts.map +1 -0
  189. package/dist/hdhr/deviceId.d.ts +1 -0
  190. package/dist/hdhr/deviceId.d.ts.map +1 -0
  191. package/dist/hdhr/discover.d.ts +1 -0
  192. package/dist/hdhr/discover.d.ts.map +1 -0
  193. package/dist/hdhr/discover.js +48 -47
  194. package/dist/hdhr/discover.js.map +1 -1
  195. package/dist/hdhr/getHandlers.d.ts +29 -0
  196. package/dist/hdhr/getHandlers.d.ts.map +1 -0
  197. package/dist/hdhr/getHandlers.js +196 -0
  198. package/dist/hdhr/getHandlers.js.map +1 -0
  199. package/dist/hdhr/identity.d.ts +36 -0
  200. package/dist/hdhr/identity.d.ts.map +1 -0
  201. package/dist/hdhr/identity.js +51 -0
  202. package/dist/hdhr/identity.js.map +1 -0
  203. package/dist/hdhr/index.d.ts +22 -4
  204. package/dist/hdhr/index.d.ts.map +1 -0
  205. package/dist/hdhr/index.js +229 -54
  206. package/dist/hdhr/index.js.map +1 -1
  207. package/dist/hdhr/protocol.d.ts +75 -0
  208. package/dist/hdhr/protocol.d.ts.map +1 -0
  209. package/dist/hdhr/protocol.js +264 -0
  210. package/dist/hdhr/protocol.js.map +1 -0
  211. package/dist/hdhr/tunerState.d.ts +24 -0
  212. package/dist/hdhr/tunerState.d.ts.map +1 -0
  213. package/dist/hdhr/tunerState.js +67 -0
  214. package/dist/hdhr/tunerState.js.map +1 -0
  215. package/dist/hdhr/udp.d.ts +43 -0
  216. package/dist/hdhr/udp.d.ts.map +1 -0
  217. package/dist/hdhr/udp.js +277 -0
  218. package/dist/hdhr/udp.js.map +1 -0
  219. package/dist/identity.d.ts +1 -0
  220. package/dist/identity.d.ts.map +1 -0
  221. package/dist/identity.js +13 -11
  222. package/dist/identity.js.map +1 -1
  223. package/dist/index.d.ts +1 -0
  224. package/dist/index.d.ts.map +1 -0
  225. package/dist/index.js +11 -12
  226. package/dist/index.js.map +1 -1
  227. package/dist/native/codecInference.d.ts +1 -0
  228. package/dist/native/codecInference.d.ts.map +1 -0
  229. package/dist/native/codecInference.js +50 -5
  230. package/dist/native/codecInference.js.map +1 -1
  231. package/dist/native/decrypt.d.ts +6 -2
  232. package/dist/native/decrypt.d.ts.map +1 -0
  233. package/dist/native/decrypt.js +15 -8
  234. package/dist/native/decrypt.js.map +1 -1
  235. package/dist/native/index.d.ts +40 -17
  236. package/dist/native/index.d.ts.map +1 -0
  237. package/dist/native/index.js +281 -120
  238. package/dist/native/index.js.map +1 -1
  239. package/dist/native/probe.d.ts +114 -18
  240. package/dist/native/probe.d.ts.map +1 -0
  241. package/dist/native/probe.js +452 -115
  242. package/dist/native/probe.js.map +1 -1
  243. package/dist/native/proxy.d.ts +57 -5
  244. package/dist/native/proxy.d.ts.map +1 -0
  245. package/dist/native/proxy.js +604 -170
  246. package/dist/native/proxy.js.map +1 -1
  247. package/dist/native/tokenExpiry.d.ts +1 -0
  248. package/dist/native/tokenExpiry.d.ts.map +1 -0
  249. package/dist/native/tokenExpiry.js +1 -1
  250. package/dist/routes/assets.d.ts +1 -0
  251. package/dist/routes/assets.d.ts.map +1 -0
  252. package/dist/routes/assets.js +4 -2
  253. package/dist/routes/assets.js.map +1 -1
  254. package/dist/routes/auth.d.ts +1 -0
  255. package/dist/routes/auth.d.ts.map +1 -0
  256. package/dist/routes/auth.js +0 -1
  257. package/dist/routes/auth.js.map +1 -1
  258. package/dist/routes/cdp.d.ts +5 -5
  259. package/dist/routes/cdp.d.ts.map +1 -0
  260. package/dist/routes/cdp.js +59 -20
  261. package/dist/routes/cdp.js.map +1 -1
  262. package/dist/routes/channels.d.ts +1 -0
  263. package/dist/routes/channels.d.ts.map +1 -0
  264. package/dist/routes/channels.js +2 -1
  265. package/dist/routes/channels.js.map +1 -1
  266. package/dist/routes/clientActions.d.ts +98 -0
  267. package/dist/routes/clientActions.d.ts.map +1 -0
  268. package/dist/routes/clientActions.js +113 -0
  269. package/dist/routes/clientActions.js.map +1 -0
  270. package/dist/routes/components.d.ts +5 -3
  271. package/dist/routes/components.d.ts.map +1 -0
  272. package/dist/routes/components.js +36 -39
  273. package/dist/routes/components.js.map +1 -1
  274. package/dist/routes/config/channels/endpoints/browse.d.ts +1 -0
  275. package/dist/routes/config/channels/endpoints/browse.d.ts.map +1 -0
  276. package/dist/routes/config/channels/endpoints/browse.js +23 -10
  277. package/dist/routes/config/channels/endpoints/browse.js.map +1 -1
  278. package/dist/routes/config/channels/endpoints/bulk.d.ts +1 -0
  279. package/dist/routes/config/channels/endpoints/bulk.d.ts.map +1 -0
  280. package/dist/routes/config/channels/endpoints/bulk.js +5 -3
  281. package/dist/routes/config/channels/endpoints/bulk.js.map +1 -1
  282. package/dist/routes/config/channels/endpoints/crud.d.ts +1 -0
  283. package/dist/routes/config/channels/endpoints/crud.d.ts.map +1 -0
  284. package/dist/routes/config/channels/endpoints/crud.js +85 -93
  285. package/dist/routes/config/channels/endpoints/crud.js.map +1 -1
  286. package/dist/routes/config/channels/endpoints/importExport.d.ts +1 -0
  287. package/dist/routes/config/channels/endpoints/importExport.d.ts.map +1 -0
  288. package/dist/routes/config/channels/endpoints/importExport.js +6 -3
  289. package/dist/routes/config/channels/endpoints/importExport.js.map +1 -1
  290. package/dist/routes/config/channels/endpoints/predefined.d.ts +1 -0
  291. package/dist/routes/config/channels/endpoints/predefined.d.ts.map +1 -0
  292. package/dist/routes/config/channels/endpoints/prefs.d.ts +1 -0
  293. package/dist/routes/config/channels/endpoints/prefs.d.ts.map +1 -0
  294. package/dist/routes/config/channels/endpoints/prefs.js +2 -1
  295. package/dist/routes/config/channels/endpoints/prefs.js.map +1 -1
  296. package/dist/routes/config/channels/endpoints/service.d.ts +2 -1
  297. package/dist/routes/config/channels/endpoints/service.d.ts.map +1 -0
  298. package/dist/routes/config/channels/endpoints/service.js +3 -1
  299. package/dist/routes/config/channels/endpoints/service.js.map +1 -1
  300. package/dist/routes/config/channels/endpoints/tags.d.ts +1 -0
  301. package/dist/routes/config/channels/endpoints/tags.d.ts.map +1 -0
  302. package/dist/routes/config/channels/endpoints/tags.js +13 -7
  303. package/dist/routes/config/channels/endpoints/tags.js.map +1 -1
  304. package/dist/routes/config/channels/healthBridge.d.ts +24 -0
  305. package/dist/routes/config/channels/healthBridge.d.ts.map +1 -0
  306. package/dist/routes/config/channels/healthBridge.js +71 -0
  307. package/dist/routes/config/channels/healthBridge.js.map +1 -0
  308. package/dist/routes/config/channels/http/handler.d.ts +1 -0
  309. package/dist/routes/config/channels/http/handler.d.ts.map +1 -0
  310. package/dist/routes/config/channels/http/playlistHint.d.ts +2 -1
  311. package/dist/routes/config/channels/http/playlistHint.d.ts.map +1 -0
  312. package/dist/routes/config/channels/http/playlistHint.js +16 -2
  313. package/dist/routes/config/channels/http/playlistHint.js.map +1 -1
  314. package/dist/routes/config/channels/http/serviceWarning.d.ts +4 -2
  315. package/dist/routes/config/channels/http/serviceWarning.d.ts.map +1 -0
  316. package/dist/routes/config/channels/http/serviceWarning.js +3 -2
  317. package/dist/routes/config/channels/http/serviceWarning.js.map +1 -1
  318. package/dist/routes/config/channels/index.d.ts +1 -0
  319. package/dist/routes/config/channels/index.d.ts.map +1 -0
  320. package/dist/routes/config/channels/setup.d.ts +1 -0
  321. package/dist/routes/config/channels/setup.d.ts.map +1 -0
  322. package/dist/routes/config/channels/table.d.ts +3 -3
  323. package/dist/routes/config/channels/table.d.ts.map +1 -0
  324. package/dist/routes/config/channels/table.js +148 -107
  325. package/dist/routes/config/channels/table.js.map +1 -1
  326. package/dist/routes/config/http/envelope.d.ts +1 -0
  327. package/dist/routes/config/http/envelope.d.ts.map +1 -0
  328. package/dist/routes/config/index.d.ts +35 -3
  329. package/dist/routes/config/index.d.ts.map +1 -0
  330. package/dist/routes/config/index.js +51 -2
  331. package/dist/routes/config/index.js.map +1 -1
  332. package/dist/routes/config/services.d.ts +5 -4
  333. package/dist/routes/config/services.d.ts.map +1 -0
  334. package/dist/routes/config/services.js +57 -43
  335. package/dist/routes/config/services.js.map +1 -1
  336. package/dist/routes/config/settings.d.ts +1 -0
  337. package/dist/routes/config/settings.d.ts.map +1 -0
  338. package/dist/routes/config/settings.js +98 -49
  339. package/dist/routes/config/settings.js.map +1 -1
  340. package/dist/routes/debug.d.ts +1 -0
  341. package/dist/routes/debug.d.ts.map +1 -0
  342. package/dist/routes/debug.js +277 -139
  343. package/dist/routes/debug.js.map +1 -1
  344. package/dist/routes/health.d.ts +39 -1
  345. package/dist/routes/health.d.ts.map +1 -0
  346. package/dist/routes/health.js +33 -27
  347. package/dist/routes/health.js.map +1 -1
  348. package/dist/routes/hls.d.ts +1 -0
  349. package/dist/routes/hls.d.ts.map +1 -0
  350. package/dist/routes/icons.d.ts +1 -0
  351. package/dist/routes/icons.d.ts.map +1 -0
  352. package/dist/routes/icons.js +1 -1
  353. package/dist/routes/index.d.ts +1 -0
  354. package/dist/routes/index.d.ts.map +1 -0
  355. package/dist/routes/logs.d.ts +1 -0
  356. package/dist/routes/logs.d.ts.map +1 -0
  357. package/dist/routes/logs.js +14 -4
  358. package/dist/routes/logs.js.map +1 -1
  359. package/dist/routes/mpegts.d.ts +1 -0
  360. package/dist/routes/mpegts.d.ts.map +1 -0
  361. package/dist/routes/play.d.ts +1 -0
  362. package/dist/routes/play.d.ts.map +1 -0
  363. package/dist/routes/playlist.d.ts +1 -0
  364. package/dist/routes/playlist.d.ts.map +1 -0
  365. package/dist/routes/playlist.js +7 -2
  366. package/dist/routes/playlist.js.map +1 -1
  367. package/dist/routes/root/content.d.ts +8 -5
  368. package/dist/routes/root/content.d.ts.map +1 -0
  369. package/dist/routes/root/content.js +29 -26
  370. package/dist/routes/root/content.js.map +1 -1
  371. package/dist/routes/root/index.d.ts +2 -1
  372. package/dist/routes/root/index.d.ts.map +1 -0
  373. package/dist/routes/root/index.js +26 -21
  374. package/dist/routes/root/index.js.map +1 -1
  375. package/dist/routes/root/scripts/channels.d.ts +6 -0
  376. package/dist/routes/root/scripts/channels.d.ts.map +1 -0
  377. package/dist/routes/root/scripts/channels.js +197 -118
  378. package/dist/routes/root/scripts/channels.js.map +1 -1
  379. package/dist/routes/root/scripts/clientEscape.d.ts +22 -0
  380. package/dist/routes/root/scripts/clientEscape.d.ts.map +1 -0
  381. package/dist/routes/root/scripts/clientEscape.js +41 -0
  382. package/dist/routes/root/scripts/clientEscape.js.map +1 -0
  383. package/dist/routes/root/scripts/clientUrl.d.ts +23 -0
  384. package/dist/routes/root/scripts/clientUrl.d.ts.map +1 -0
  385. package/dist/routes/root/scripts/clientUrl.js +44 -0
  386. package/dist/routes/root/scripts/clientUrl.js.map +1 -0
  387. package/dist/routes/root/scripts/config.d.ts +6 -0
  388. package/dist/routes/root/scripts/config.d.ts.map +1 -0
  389. package/dist/routes/root/scripts/config.js +110 -24
  390. package/dist/routes/root/scripts/config.js.map +1 -1
  391. package/dist/routes/root/scripts/index.d.ts +1 -0
  392. package/dist/routes/root/scripts/index.d.ts.map +1 -0
  393. package/dist/routes/root/scripts/shared.d.ts +1 -0
  394. package/dist/routes/root/scripts/shared.d.ts.map +1 -0
  395. package/dist/routes/root/scripts/shared.js +112 -26
  396. package/dist/routes/root/scripts/shared.js.map +1 -1
  397. package/dist/routes/root/scripts/status.d.ts +1 -0
  398. package/dist/routes/root/scripts/status.d.ts.map +1 -0
  399. package/dist/routes/root/scripts/status.handlers.d.ts +23 -33
  400. package/dist/routes/root/scripts/status.handlers.d.ts.map +1 -0
  401. package/dist/routes/root/scripts/status.handlers.js +46 -92
  402. package/dist/routes/root/scripts/status.handlers.js.map +1 -1
  403. package/dist/routes/root/scripts/status.js +21 -8
  404. package/dist/routes/root/scripts/status.js.map +1 -1
  405. package/dist/routes/root/styles.d.ts +1 -0
  406. package/dist/routes/root/styles.d.ts.map +1 -0
  407. package/dist/routes/root/styles.js +7 -4
  408. package/dist/routes/root/styles.js.map +1 -1
  409. package/dist/routes/services.d.ts +13 -1
  410. package/dist/routes/services.d.ts.map +1 -0
  411. package/dist/routes/services.js +81 -68
  412. package/dist/routes/services.js.map +1 -1
  413. package/dist/routes/sse.d.ts +1 -0
  414. package/dist/routes/sse.d.ts.map +1 -0
  415. package/dist/routes/sse.js +1 -1
  416. package/dist/routes/sse.js.map +1 -1
  417. package/dist/routes/streams.d.ts +2 -1
  418. package/dist/routes/streams.d.ts.map +1 -0
  419. package/dist/routes/streams.js +6 -4
  420. package/dist/routes/streams.js.map +1 -1
  421. package/dist/routes/theme.d.ts +3 -2
  422. package/dist/routes/theme.d.ts.map +1 -0
  423. package/dist/routes/theme.js +6 -5
  424. package/dist/routes/theme.js.map +1 -1
  425. package/dist/routes/ui.d.ts +3 -1
  426. package/dist/routes/ui.d.ts.map +1 -0
  427. package/dist/routes/ui.js +16 -4
  428. package/dist/routes/ui.js.map +1 -1
  429. package/dist/routes/upgrade.d.ts +1 -0
  430. package/dist/routes/upgrade.d.ts.map +1 -0
  431. package/dist/routes/upgrade.js +6 -2
  432. package/dist/routes/upgrade.js.map +1 -1
  433. package/dist/service/commands.context.d.ts +1 -0
  434. package/dist/service/commands.context.d.ts.map +1 -0
  435. package/dist/service/commands.context.js +4 -3
  436. package/dist/service/commands.context.js.map +1 -1
  437. package/dist/service/commands.d.ts +1 -0
  438. package/dist/service/commands.d.ts.map +1 -0
  439. package/dist/service/commands.js +10 -13
  440. package/dist/service/commands.js.map +1 -1
  441. package/dist/service/generators.context.d.ts +1 -0
  442. package/dist/service/generators.context.d.ts.map +1 -0
  443. package/dist/service/generators.context.js +1 -1
  444. package/dist/service/generators.context.js.map +1 -1
  445. package/dist/service/generators.d.ts +4 -3
  446. package/dist/service/generators.d.ts.map +1 -0
  447. package/dist/service/generators.js +111 -31
  448. package/dist/service/generators.js.map +1 -1
  449. package/dist/service/index.d.ts +1 -0
  450. package/dist/service/index.d.ts.map +1 -0
  451. package/dist/streaming/captureLock.d.ts +44 -0
  452. package/dist/streaming/captureLock.d.ts.map +1 -0
  453. package/dist/streaming/captureLock.js +95 -0
  454. package/dist/streaming/captureLock.js.map +1 -0
  455. package/dist/streaming/captureSession.d.ts +31 -0
  456. package/dist/streaming/captureSession.d.ts.map +1 -0
  457. package/dist/streaming/captureSession.js +54 -0
  458. package/dist/streaming/captureSession.js.map +1 -0
  459. package/dist/streaming/clients.d.ts +1 -0
  460. package/dist/streaming/clients.d.ts.map +1 -0
  461. package/dist/streaming/codec.d.ts +1 -0
  462. package/dist/streaming/codec.d.ts.map +1 -0
  463. package/dist/streaming/fmp4Segmenter.d.ts +29 -2
  464. package/dist/streaming/fmp4Segmenter.d.ts.map +1 -0
  465. package/dist/streaming/fmp4Segmenter.js +79 -26
  466. package/dist/streaming/fmp4Segmenter.js.map +1 -1
  467. package/dist/streaming/hls.d.ts +18 -7
  468. package/dist/streaming/hls.d.ts.map +1 -0
  469. package/dist/streaming/hls.js +220 -120
  470. package/dist/streaming/hls.js.map +1 -1
  471. package/dist/streaming/hlsResume.d.ts +1 -0
  472. package/dist/streaming/hlsResume.d.ts.map +1 -0
  473. package/dist/streaming/hlsResume.js +1 -1
  474. package/dist/streaming/hlsResume.js.map +1 -1
  475. package/dist/streaming/hlsSegments.d.ts +59 -2
  476. package/dist/streaming/hlsSegments.d.ts.map +1 -0
  477. package/dist/streaming/hlsSegments.js +147 -17
  478. package/dist/streaming/hlsSegments.js.map +1 -1
  479. package/dist/streaming/lifecycle.d.ts +4 -1
  480. package/dist/streaming/lifecycle.d.ts.map +1 -0
  481. package/dist/streaming/lifecycle.js +65 -85
  482. package/dist/streaming/lifecycle.js.map +1 -1
  483. package/dist/streaming/monitor.d.ts +6 -4
  484. package/dist/streaming/monitor.d.ts.map +1 -0
  485. package/dist/streaming/monitor.js +343 -145
  486. package/dist/streaming/monitor.js.map +1 -1
  487. package/dist/streaming/mp4Parser.d.ts +5 -4
  488. package/dist/streaming/mp4Parser.d.ts.map +1 -0
  489. package/dist/streaming/mp4Parser.js +36 -13
  490. package/dist/streaming/mp4Parser.js.map +1 -1
  491. package/dist/streaming/mpegts.d.ts +16 -0
  492. package/dist/streaming/mpegts.d.ts.map +1 -0
  493. package/dist/streaming/mpegts.js +61 -17
  494. package/dist/streaming/mpegts.js.map +1 -1
  495. package/dist/streaming/playlistBuilder.d.ts +1 -0
  496. package/dist/streaming/playlistBuilder.d.ts.map +1 -0
  497. package/dist/streaming/preroll.d.ts +32 -5
  498. package/dist/streaming/preroll.d.ts.map +1 -0
  499. package/dist/streaming/preroll.js +70 -32
  500. package/dist/streaming/preroll.js.map +1 -1
  501. package/dist/streaming/pretune.d.ts +26 -1
  502. package/dist/streaming/pretune.d.ts.map +1 -0
  503. package/dist/streaming/pretune.js +22 -24
  504. package/dist/streaming/pretune.js.map +1 -1
  505. package/dist/streaming/pretuneTimers.d.ts +24 -0
  506. package/dist/streaming/pretuneTimers.d.ts.map +1 -0
  507. package/dist/streaming/pretuneTimers.js +56 -0
  508. package/dist/streaming/pretuneTimers.js.map +1 -0
  509. package/dist/streaming/recovery.d.ts +139 -5
  510. package/dist/streaming/recovery.d.ts.map +1 -0
  511. package/dist/streaming/recovery.js +160 -16
  512. package/dist/streaming/recovery.js.map +1 -1
  513. package/dist/streaming/registry.d.ts +27 -9
  514. package/dist/streaming/registry.d.ts.map +1 -0
  515. package/dist/streaming/registry.js +21 -3
  516. package/dist/streaming/registry.js.map +1 -1
  517. package/dist/streaming/setup.d.ts +157 -29
  518. package/dist/streaming/setup.d.ts.map +1 -0
  519. package/dist/streaming/setup.js +1034 -458
  520. package/dist/streaming/setup.js.map +1 -1
  521. package/dist/streaming/showInfo.d.ts +21 -1
  522. package/dist/streaming/showInfo.d.ts.map +1 -0
  523. package/dist/streaming/showInfo.js +31 -16
  524. package/dist/streaming/showInfo.js.map +1 -1
  525. package/dist/streaming/statusEmitter.d.ts +10 -10
  526. package/dist/streaming/statusEmitter.d.ts.map +1 -0
  527. package/dist/streaming/statusEmitter.js +5 -8
  528. package/dist/streaming/statusEmitter.js.map +1 -1
  529. package/dist/types/channels.d.ts +16 -4
  530. package/dist/types/channels.d.ts.map +1 -0
  531. package/dist/types/channels.js +4 -2
  532. package/dist/types/channels.js.map +1 -1
  533. package/dist/types/config.d.ts +16 -5
  534. package/dist/types/config.d.ts.map +1 -0
  535. package/dist/types/index.d.ts +3 -2
  536. package/dist/types/index.d.ts.map +1 -0
  537. package/dist/types/profiles.d.ts +6 -4
  538. package/dist/types/profiles.d.ts.map +1 -0
  539. package/dist/types/selection.d.ts +31 -3
  540. package/dist/types/selection.d.ts.map +1 -0
  541. package/dist/types/selection.js +4 -2
  542. package/dist/types/selection.js.map +1 -1
  543. package/dist/types/shared.d.ts +1 -0
  544. package/dist/types/shared.d.ts.map +1 -0
  545. package/dist/types/streaming.d.ts +8 -0
  546. package/dist/types/streaming.d.ts.map +1 -0
  547. package/dist/upgrade/commands.context.d.ts +1 -0
  548. package/dist/upgrade/commands.context.d.ts.map +1 -0
  549. package/dist/upgrade/commands.context.js +14 -10
  550. package/dist/upgrade/commands.context.js.map +1 -1
  551. package/dist/upgrade/commands.d.ts +8 -15
  552. package/dist/upgrade/commands.d.ts.map +1 -0
  553. package/dist/upgrade/commands.js +27 -28
  554. package/dist/upgrade/commands.js.map +1 -1
  555. package/dist/upgrade/detection.context.d.ts +1 -0
  556. package/dist/upgrade/detection.context.d.ts.map +1 -0
  557. package/dist/upgrade/detection.context.js +4 -1
  558. package/dist/upgrade/detection.context.js.map +1 -1
  559. package/dist/upgrade/detection.d.ts +9 -7
  560. package/dist/upgrade/detection.d.ts.map +1 -0
  561. package/dist/upgrade/detection.js +23 -54
  562. package/dist/upgrade/detection.js.map +1 -1
  563. package/dist/upgrade/index.d.ts +2 -0
  564. package/dist/upgrade/index.d.ts.map +1 -0
  565. package/dist/upgrade/index.js +1 -0
  566. package/dist/upgrade/index.js.map +1 -1
  567. package/dist/upgrade/lifecycle.context.d.ts +20 -0
  568. package/dist/upgrade/lifecycle.context.d.ts.map +1 -0
  569. package/dist/upgrade/lifecycle.context.js +47 -0
  570. package/dist/upgrade/lifecycle.context.js.map +1 -0
  571. package/dist/upgrade/lifecycle.d.ts +72 -0
  572. package/dist/upgrade/lifecycle.d.ts.map +1 -0
  573. package/dist/upgrade/lifecycle.js +175 -0
  574. package/dist/upgrade/lifecycle.js.map +1 -0
  575. package/dist/upgrade/pathHandle.d.ts +30 -0
  576. package/dist/upgrade/pathHandle.d.ts.map +1 -0
  577. package/dist/upgrade/pathHandle.js +102 -0
  578. package/dist/upgrade/pathHandle.js.map +1 -0
  579. package/dist/utils/bootSession.context.d.ts +16 -0
  580. package/dist/utils/bootSession.context.d.ts.map +1 -0
  581. package/dist/utils/bootSession.context.js +60 -0
  582. package/dist/utils/bootSession.context.js.map +1 -0
  583. package/dist/utils/bootSession.d.ts +17 -0
  584. package/dist/utils/bootSession.d.ts.map +1 -0
  585. package/dist/utils/bootSession.js +20 -0
  586. package/dist/utils/bootSession.js.map +1 -0
  587. package/dist/utils/chromeFetch.d.ts +1 -0
  588. package/dist/utils/chromeFetch.d.ts.map +1 -0
  589. package/dist/utils/cliOutput.d.ts +1 -0
  590. package/dist/utils/cliOutput.d.ts.map +1 -0
  591. package/dist/utils/clock.d.ts +5 -4
  592. package/dist/utils/clock.d.ts.map +1 -0
  593. package/dist/utils/clock.js +19 -16
  594. package/dist/utils/clock.js.map +1 -1
  595. package/dist/utils/debugFilter.d.ts +9 -1
  596. package/dist/utils/debugFilter.d.ts.map +1 -0
  597. package/dist/utils/debugFilter.js +60 -33
  598. package/dist/utils/debugFilter.js.map +1 -1
  599. package/dist/utils/delay.d.ts +48 -15
  600. package/dist/utils/delay.d.ts.map +1 -0
  601. package/dist/utils/delay.js +73 -20
  602. package/dist/utils/delay.js.map +1 -1
  603. package/dist/utils/errors.d.ts +32 -0
  604. package/dist/utils/errors.d.ts.map +1 -0
  605. package/dist/utils/errors.js +45 -4
  606. package/dist/utils/errors.js.map +1 -1
  607. package/dist/utils/evaluate.d.ts +1 -0
  608. package/dist/utils/evaluate.d.ts.map +1 -0
  609. package/dist/utils/evaluate.js +29 -23
  610. package/dist/utils/evaluate.js.map +1 -1
  611. package/dist/utils/ffmpeg.context.d.ts +1 -0
  612. package/dist/utils/ffmpeg.context.d.ts.map +1 -0
  613. package/dist/utils/ffmpeg.context.js.map +1 -1
  614. package/dist/utils/ffmpeg.d.ts +29 -7
  615. package/dist/utils/ffmpeg.d.ts.map +1 -0
  616. package/dist/utils/ffmpeg.js +32 -14
  617. package/dist/utils/ffmpeg.js.map +1 -1
  618. package/dist/utils/fileLogger.d.ts +3 -1
  619. package/dist/utils/fileLogger.d.ts.map +1 -0
  620. package/dist/utils/fileLogger.js +64 -29
  621. package/dist/utils/fileLogger.js.map +1 -1
  622. package/dist/utils/format.d.ts +14 -1
  623. package/dist/utils/format.d.ts.map +1 -0
  624. package/dist/utils/format.js +19 -5
  625. package/dist/utils/format.js.map +1 -1
  626. package/dist/utils/index.d.ts +4 -0
  627. package/dist/utils/index.d.ts.map +1 -0
  628. package/dist/utils/index.js +3 -0
  629. package/dist/utils/index.js.map +1 -1
  630. package/dist/utils/logEmitter.d.ts +1 -0
  631. package/dist/utils/logEmitter.d.ts.map +1 -0
  632. package/dist/utils/logger.d.ts +1 -0
  633. package/dist/utils/logger.d.ts.map +1 -0
  634. package/dist/utils/logger.js +10 -6
  635. package/dist/utils/logger.js.map +1 -1
  636. package/dist/utils/m3u.d.ts +3 -2
  637. package/dist/utils/m3u.d.ts.map +1 -0
  638. package/dist/utils/m3u.js +4 -3
  639. package/dist/utils/m3u.js.map +1 -1
  640. package/dist/utils/markup.d.ts +13 -1
  641. package/dist/utils/markup.d.ts.map +1 -0
  642. package/dist/utils/markup.js +28 -4
  643. package/dist/utils/markup.js.map +1 -1
  644. package/dist/utils/memo.d.ts +3 -2
  645. package/dist/utils/memo.d.ts.map +1 -0
  646. package/dist/utils/memo.js +3 -3
  647. package/dist/utils/morganStream.d.ts +1 -0
  648. package/dist/utils/morganStream.d.ts.map +1 -0
  649. package/dist/utils/network.d.ts +1 -0
  650. package/dist/utils/network.d.ts.map +1 -0
  651. package/dist/utils/network.js +2 -2
  652. package/dist/utils/pid.d.ts +3 -25
  653. package/dist/utils/pid.d.ts.map +1 -0
  654. package/dist/utils/pid.js +10 -49
  655. package/dist/utils/pid.js.map +1 -1
  656. package/dist/utils/platform.d.ts +3 -1
  657. package/dist/utils/platform.d.ts.map +1 -0
  658. package/dist/utils/platform.js +2 -1
  659. package/dist/utils/platform.js.map +1 -1
  660. package/dist/utils/processInspector.context.d.ts +8 -0
  661. package/dist/utils/processInspector.context.d.ts.map +1 -0
  662. package/dist/utils/processInspector.context.js +106 -0
  663. package/dist/utils/processInspector.context.js.map +1 -0
  664. package/dist/utils/processInspector.d.ts +55 -0
  665. package/dist/utils/processInspector.d.ts.map +1 -0
  666. package/dist/utils/processInspector.js +127 -0
  667. package/dist/utils/processInspector.js.map +1 -0
  668. package/dist/utils/retry.d.ts +25 -2
  669. package/dist/utils/retry.d.ts.map +1 -0
  670. package/dist/utils/retry.js +33 -6
  671. package/dist/utils/retry.js.map +1 -1
  672. package/dist/utils/runtimeIdentity.context.d.ts +7 -0
  673. package/dist/utils/runtimeIdentity.context.d.ts.map +1 -0
  674. package/dist/utils/runtimeIdentity.context.js +43 -0
  675. package/dist/utils/runtimeIdentity.context.js.map +1 -0
  676. package/dist/utils/runtimeIdentity.d.ts +98 -0
  677. package/dist/utils/runtimeIdentity.d.ts.map +1 -0
  678. package/dist/utils/runtimeIdentity.js +181 -0
  679. package/dist/utils/runtimeIdentity.js.map +1 -0
  680. package/dist/utils/sanitize.d.ts +1 -0
  681. package/dist/utils/sanitize.d.ts.map +1 -0
  682. package/dist/utils/streamContext.d.ts +2 -1
  683. package/dist/utils/streamContext.d.ts.map +1 -0
  684. package/dist/utils/streamContext.js +1 -1
  685. package/dist/utils/timing.d.ts +2 -1
  686. package/dist/utils/timing.d.ts.map +1 -0
  687. package/dist/utils/timing.js +0 -5
  688. package/dist/utils/timing.js.map +1 -1
  689. package/dist/utils/version.d.ts +1 -0
  690. package/dist/utils/version.d.ts.map +1 -0
  691. package/dist/utils/version.js +47 -13
  692. package/dist/utils/version.js.map +1 -1
  693. package/package.json +26 -20
@@ -3,8 +3,9 @@ import { compareChannelSort, getAllServiceTags, getAuthDomainForChannel, getChan
3
3
  import { escapeHtml, formatTimeAgo } from "../../../utils/index.js";
4
4
  import { getActiveTagVocabulary, getChannelCustomizations, getChannelEffectiveTags, getChannelListing, getChannelLogo, getChannelsParseErrorMessage, getEffectiveHdhrEnabled, getPredefinedScopeCounts, getTagRegistry, getUserChannelsFilePath, hasChannelsParseError, isPredefinedChannel, isPredefinedChannelDisabled, isUserChannel, isVisibleChannel, tagsMatch } from "../../../config/userChannels.js";
5
5
  import { getCachedProviderChannels, getProviderDomainMap, getProviderGuideUrls, getProviderModuleInfo } from "../../../browser/channelSelection.js";
6
- import { getChannelHealth, getDomainAuth } from "../../../config/health.js";
6
+ import { getChannelHealth, getDomainAuthState } from "../../../config/health.js";
7
7
  import { getProfileForChannel, getProfiles } from "../../../config/profiles.js";
8
+ import { ACTIONS } from "../../clientActions.js";
8
9
  import { CONFIG } from "../../../config/index.js";
9
10
  import { PREDEFINED_CHANNELS } from "../../../channels/index.js";
10
11
  import { categorizeProfiles } from "../index.js";
@@ -34,7 +35,7 @@ export const OPTIONAL_COLUMNS = [
34
35
  { align: "left", cssClass: "col-selector", field: "channelSelector", label: "Selector", width: "130px" },
35
36
  { align: "left", cssClass: "col-tags", field: "tags", label: "Tags", width: "150px" }
36
37
  ];
37
- // Total number of columns in the channels table (4 required + 6 optional).
38
+ // Total number of columns in the channels table: the required columns plus every entry in OPTIONAL_COLUMNS.
38
39
  const TOTAL_COLUMN_COUNT = 10;
39
40
  // Valid optional column field names.
40
41
  export const VALID_OPTIONAL_COLUMNS = new Set(OPTIONAL_COLUMNS.map((c) => c.field));
@@ -61,7 +62,7 @@ function generateTextField(id, name, label, value, options = {}) {
61
62
  const modifiedDot = isModified ? "<span class=\"modified-dot\" title=\"Modified from predefined default\"></span>" : "";
62
63
  const defaultAttr = (options.defaultValue !== undefined) ? " data-default=\"" + escapeHtml(options.defaultValue) + "\"" : "";
63
64
  const resetBtn = isModified ? "<button type=\"button\" class=\"btn-reset\" title=\"Reset to predefined default\" aria-label=\"Reset to predefined default\" " +
64
- "onclick=\"resetChannelField('" + id + "')\">&#8635;</button>" : "";
65
+ "data-click-action=\"" + ACTIONS.resetChannelField + "\" data-field-id=\"" + id + "\">&#8635;</button>" : "";
65
66
  lines.push("<div class=\"form-row" + modifiedClass + "\">");
66
67
  lines.push(modifiedDot + "<label for=\"" + id + "\">" + label + "</label>");
67
68
  lines.push("<input class=\"form-input\" type=\"" + inputType + "\" id=\"" + id + "\" name=\"" + name + "\"" + required + listAttr + pattern +
@@ -97,7 +98,7 @@ function generateProfileDropdown(id, selectedProfile, profiles, showHint = true,
97
98
  const modifiedDot = isModified ? "<span class=\"modified-dot\" title=\"Modified from predefined default\"></span>" : "";
98
99
  const defaultAttr = (defaultProfile !== undefined) ? " data-default=\"" + escapeHtml(defaultProfile) + "\"" : "";
99
100
  const resetBtn = isModified ? "<button type=\"button\" class=\"btn-reset\" title=\"Reset to predefined default\" aria-label=\"Reset to predefined default\" " +
100
- "onclick=\"resetChannelField('" + id + "')\">&#8635;</button>" : "";
101
+ "data-click-action=\"" + ACTIONS.resetChannelField + "\" data-field-id=\"" + id + "\">&#8635;</button>" : "";
101
102
  // Helper to generate option elements for a profile.
102
103
  const renderOption = (profile) => {
103
104
  const selected = (profile.name === selectedProfile) ? " selected" : "";
@@ -154,7 +155,7 @@ function generateProfileDropdown(id, selectedProfile, profiles, showHint = true,
154
155
  if (showHint) {
155
156
  lines.push("<div class=\"hint\">Autodetect uses predefined profiles for known sites. If video doesn't play or fullscreen fails, " +
156
157
  "try experimenting with different profiles. ");
157
- lines.push("<a href=\"#\" onclick=\"toggleProfileReference(); return false;\">View profile reference</a></div>");
158
+ lines.push("<a href=\"#\" data-click-action=\"" + ACTIONS.toggleProfileReference + "\" data-click-prevent-default>View profile reference</a></div>");
158
159
  }
159
160
  return lines;
160
161
  }
@@ -170,7 +171,8 @@ function generateProfileReference(profiles) {
170
171
  lines.push("<div id=\"profile-reference\" class=\"profile-reference\" style=\"display: none;\">");
171
172
  lines.push("<div class=\"profile-reference-header\">");
172
173
  lines.push("<h3>Profile Reference</h3>");
173
- lines.push("<button type=\"button\" class=\"profile-reference-close\" aria-label=\"Close\" onclick=\"toggleProfileReference()\">\u2715</button>");
174
+ lines.push("<button type=\"button\" class=\"profile-reference-close\" aria-label=\"Close\" data-click-action=\"" +
175
+ ACTIONS.toggleProfileReference + "\">\u2715</button>");
174
176
  lines.push("</div>");
175
177
  lines.push("<p class=\"reference-intro\">Profiles configure how PrismCast interacts with different video players. Autodetect uses predefined ");
176
178
  lines.push("profiles for known sites. If video doesn't play or fullscreen fails, use this reference to experiment with different profiles.</p>");
@@ -275,18 +277,17 @@ function generateProfileReference(profiles) {
275
277
  return lines.join("\n");
276
278
  }
277
279
  /**
278
- * Generates HTML for the advanced fields section (station ID, channel selector, channel number, guide title, logo URL, HDHomeRun toggle, tags).
280
+ * Generates HTML for the advanced fields section (station ID, guide title, logo URL, channel selector, channel number, tags, HDHomeRun toggle, force capture).
279
281
  * @param idPrefix - Prefix for element IDs ("add" or "edit").
280
282
  * @param options - Field values, defaults, and customization metadata. See AdvancedFieldOptions.
281
283
  * @returns Array of HTML strings for the advanced fields section.
282
284
  */
283
285
  function generateAdvancedFields(idPrefix, options = {}) {
284
- const { channelNumberValue = "", channelSelectorValue = "", guideTitleValue = "", hdhrEnabled = true, logoUrlValue = "", showHints = true, stationIdValue = "", tagsValue = "" } = options;
286
+ const { channelNumberValue = "", channelSelectorValue = "", forceCapture = false, guideTitleValue = "", hdhrEnabled = true, logoUrlValue = "", showHints = true, stationIdValue = "", tagsValue = "" } = options;
285
287
  const lines = [];
286
288
  // Advanced fields toggle.
287
- lines.push("<div class=\"advanced-toggle\" onclick=\"document.getElementById('" + idPrefix +
288
- "-advanced').classList.toggle('show'); this.textContent = this.textContent === 'Show Advanced Options' ? " +
289
- "'Hide Advanced Options' : 'Show Advanced Options';\">Show Advanced Options</div>");
289
+ lines.push("<div class=\"advanced-toggle\" data-click-action=\"" + ACTIONS.toggleAdvancedFields + "\" data-advanced-id=\"" + idPrefix +
290
+ "-advanced\">Show Advanced Options</div>");
290
291
  lines.push("<div id=\"" + idPrefix + "-advanced\" class=\"advanced-fields\">");
291
292
  // Station ID.
292
293
  const stationIdHint = showHints ? "Optional Gracenote station ID for guide data (tvc-guide-stationid)." : undefined;
@@ -335,7 +336,7 @@ function generateAdvancedFields(idPrefix, options = {}) {
335
336
  const checked = currentTags.some((t) => tagsMatch(t, tag)) ? " checked" : "";
336
337
  lines.push("<label class=\"tag-checkbox-label\">" +
337
338
  "<input type=\"checkbox\" class=\"tag-checkbox\" data-tag=\"" + escapeHtml(tag) + "\"" + checked +
338
- " onchange=\"updateTagsHidden(this.closest('.form-row'))\">" +
339
+ " data-change-action=\"" + ACTIONS.updateTagsHidden + "\">" +
339
340
  "<span class=\"tag-badge\">" + escapeHtml(tag) + "</span></label>");
340
341
  }
341
342
  lines.push("</div>");
@@ -362,15 +363,35 @@ function generateAdvancedFields(idPrefix, options = {}) {
362
363
  if (hdhrHint) {
363
364
  lines.push("<div class=\"hint\">" + hdhrHint + "</div>");
364
365
  }
366
+ // Force screen capture. There is no hidden-input partner here: this field's default is false, so an unchecked box submitting nothing is unambiguous and the
367
+ // stored shape stays sparse. The value="true" the checkbox carries is the exact token parseFormBody tests for.
368
+ const forceCaptureChecked = forceCapture ? " checked" : "";
369
+ // Customization is presence in stored deltas (identity field, lives on canonical entry); surfaced via customizedFields. The default value arrives
370
+ // pre-stringified from resetValueFor, which yields the empty string for a field no predefined source declares - and the empty string unchecks the box.
371
+ const forceCaptureModified = customized?.has("forceCapture") === true;
372
+ const forceCaptureModifiedClass = forceCaptureModified ? " modified" : "";
373
+ const forceCaptureModifiedDot = forceCaptureModified ? "<span class=\"modified-dot\" title=\"Modified from predefined default\"></span>" : "";
374
+ const forceCaptureDefaultAttr = (defs?.forceCapture !== undefined) ? " data-default=\"" + defs.forceCapture + "\"" : "";
375
+ const forceCaptureHint = showHints ?
376
+ "When checked, this channel always uses screen capture, skipping native HLS streaming. Useful for sites whose native stream misbehaves." :
377
+ undefined;
378
+ lines.push("<div class=\"form-row form-row-checkbox" + forceCaptureModifiedClass + "\">");
379
+ lines.push(forceCaptureModifiedDot + "<label for=\"" + idPrefix + "-forceCapture\">Force capture</label>");
380
+ lines.push("<input type=\"checkbox\" id=\"" + idPrefix + "-forceCapture\" name=\"forceCapture\" value=\"true\"" + forceCaptureChecked +
381
+ forceCaptureDefaultAttr + ">");
382
+ lines.push("</div>");
383
+ if (forceCaptureHint) {
384
+ lines.push("<div class=\"hint\">" + forceCaptureHint + "</div>");
385
+ }
365
386
  // Close the advanced-fields wrapper opened at the top of this builder.
366
387
  lines.push("</div>");
367
388
  return lines;
368
389
  }
369
390
  /**
370
- * Generates JavaScript variables for channel selector datalist population. Produces three variables: `channelSelectorsByDomain` maps URL hostnames to known
391
+ * Generates JavaScript variables for channel selector datalist population. Produces four variables: `channelSelectorsByDomain` maps URL hostnames to known
371
392
  * channel selector values (from predefined channels and cached service discovery), `serviceByDomain` maps service guide URL hostnames to service slugs for
372
- * client-side async discovery, and `serviceGuideUrl` maps service slugs to their guide URLs for URL correction hints. Embedded as a `<script>` block in the
373
- * channels panel.
393
+ * client-side async discovery, `serviceGuideUrl` maps service slugs to their guide URLs for URL correction hints, and `predefinedByDomain` maps domains to
394
+ * predefined channel summaries so the manual add form can show inline hints without a server round-trip. Embedded as a `<script>` block in the channels panel.
374
395
  *
375
396
  * @returns JavaScript variable declarations ready to embed in a `<script>` tag.
376
397
  */
@@ -433,13 +454,14 @@ export function generateTagFilterContent() {
433
454
  const vocabulary = getActiveTagVocabulary();
434
455
  const lines = [];
435
456
  for (const tag of vocabulary) {
436
- lines.push("<label class=\"provider-option\" onclick=\"event.stopPropagation()\">" +
437
- "<input type=\"checkbox\" class=\"tag-filter-checkbox\" data-tag=\"" + escapeHtml(tag) + "\" checked onchange=\"applyTagColumnFilter()\"> " +
438
- escapeHtml(tag) + "</label>");
457
+ lines.push("<label class=\"provider-option\" data-click-stop-propagation>" +
458
+ "<input type=\"checkbox\" class=\"tag-filter-checkbox\" data-tag=\"" + escapeHtml(tag) + "\" checked data-change-action=\"" +
459
+ ACTIONS.applyTagColumnFilter + "\"> " + escapeHtml(tag) + "</label>");
439
460
  }
440
461
  if (vocabulary.length > 0) {
441
462
  lines.push("<div class=\"dropdown-divider\"></div>");
442
- lines.push("<div class=\"dropdown-item\" id=\"tag-filter-toggle\" onclick=\"event.stopPropagation(); toggleTagColumnFilter()\">Show None</div>");
463
+ lines.push("<div class=\"dropdown-item\" id=\"tag-filter-toggle\" data-click-action=\"" + ACTIONS.toggleTagColumnFilter +
464
+ "\" data-click-stop-propagation>Show None</div>");
443
465
  }
444
466
  return lines.join("");
445
467
  }
@@ -455,26 +477,27 @@ export function generateTagManagerBody() {
455
477
  const tagListItems = [];
456
478
  for (const tag of vocabulary) {
457
479
  tagListItems.push("<div class=\"tag-manager-item\" data-tag=\"" + escapeHtml(tag) + "\">" +
458
- "<span class=\"tag-badge tag-editable\" title=\"Click to rename\" onclick=\"startTagRename(this, '" + escapeHtml(tag) +
459
- "')\">" + escapeHtml(tag) + "</span>" +
460
- "<button type=\"button\" class=\"btn-icon btn-icon-delete\" title=\"Delete tag\" onclick=\"deleteTag('" + escapeHtml(tag) +
461
- "')\">" + ICON_DELETE + "</button></div>");
480
+ "<span class=\"tag-badge tag-editable\" title=\"Click to rename\" data-click-action=\"" + ACTIONS.startTagRename +
481
+ "\" data-tag-name=\"" + escapeHtml(tag) + "\">" + escapeHtml(tag) + "</span>" +
482
+ "<button type=\"button\" class=\"btn-icon btn-icon-delete\" title=\"Delete tag\" data-click-action=\"" +
483
+ ACTIONS.deleteTag + "\" data-tag-name=\"" + escapeHtml(tag) +
484
+ "\">" + ICON_DELETE + "</button></div>");
462
485
  }
463
486
  // Deleted predefined tags section - show restore option for tags the user has deleted.
464
487
  const deletedItems = [];
465
488
  for (const tag of registry.deletedTags) {
466
489
  deletedItems.push("<div class=\"tag-manager-item tag-deleted\" data-tag=\"" + escapeHtml(tag) + "\">" +
467
490
  "<span class=\"tag-badge tag-badge-deleted\">" + escapeHtml(tag) + "</span> <span class=\"tag-annotation\">(deleted)</span>" +
468
- "<button type=\"button\" class=\"btn-icon\" title=\"Restore tag\" onclick=\"restoreTag('" + escapeHtml(tag) +
469
- "')\">" + ICON_REVERT + "</button></div>");
491
+ "<button type=\"button\" class=\"btn-icon\" title=\"Restore tag\" data-click-action=\"" + ACTIONS.restoreTag + "\" data-tag-name=\"" + escapeHtml(tag) +
492
+ "\">" + ICON_REVERT + "</button></div>");
470
493
  }
471
494
  return "<div class=\"tag-manager\">" +
472
495
  "<p class=\"wizard-hint\">Create and manage the tag vocabulary. Tags can be assigned to channels via the channel table " +
473
496
  "for playlist filtering and organization.</p>" +
474
497
  "<div class=\"tag-manager-add\">" +
475
498
  "<input type=\"text\" id=\"tag-manager-input\" placeholder=\"New tag name\" maxlength=\"30\" " +
476
- "pattern=\"[a-zA-Z0-9]([a-zA-Z0-9 -]*[a-zA-Z0-9])?\" onkeydown=\"if(event.key==='Enter'){event.preventDefault();createTag();}\">" +
477
- "<button type=\"button\" class=\"btn btn-primary btn-sm\" onclick=\"createTag()\">Add</button>" +
499
+ "pattern=\"[a-zA-Z0-9]([a-zA-Z0-9 -]*[a-zA-Z0-9])?\" data-keydown-action=\"" + ACTIONS.createTagOnEnter + "\">" +
500
+ "<button type=\"button\" class=\"btn btn-primary btn-sm\" data-click-action=\"" + ACTIONS.createTag + "\">Add</button>" +
478
501
  "</div>" +
479
502
  "<div id=\"tag-manager-error\" class=\"wizard-error\" style=\"display: none;\"></div>" +
480
503
  "<div id=\"tag-manager-list\" class=\"tag-manager-list\">" +
@@ -492,11 +515,11 @@ function generateTagManagementModal() {
492
515
  return generateWizardModal({
493
516
  body: generateTagManagerBody(),
494
517
  buttons: [
495
- { label: "Done", onclick: "closeTagManager()", position: "right", variant: "primary" }
518
+ { action: ACTIONS.closeTagManager, label: "Done", position: "right", variant: "primary" }
496
519
  ],
520
+ closeAction: ACTIONS.closeTagManager,
497
521
  id: "tag-manager-modal",
498
522
  maxWidth: "420px",
499
- onClose: "closeTagManager()",
500
523
  title: "Manage Tags"
501
524
  });
502
525
  }
@@ -514,7 +537,7 @@ function generateBrowseModal() {
514
537
  buttons: [
515
538
  { id: "browse-back", label: "Back", position: "left", role: "back", visible: false },
516
539
  { label: "Cancel", position: "right", role: "close" },
517
- { id: "browse-add-btn", label: "Apply", onclick: "submitBrowseChannels()", position: "right", variant: "primary", visible: false }
540
+ { action: ACTIONS.submitBrowseChannels, id: "browse-add-btn", label: "Apply", position: "right", variant: "primary", visible: false }
518
541
  ],
519
542
  contentId: "browse-content",
520
543
  dataBlocks: [
@@ -543,9 +566,9 @@ function generateSetupWizardModal() {
543
566
  return generateWizardModal({
544
567
  buttons: [
545
568
  { id: "setup-back", label: "Back", position: "left", role: "back", visible: false },
546
- { id: "setup-skip", label: "Skip Setup", onclick: "skipSetup()", position: "right" },
569
+ { action: ACTIONS.skipSetup, id: "setup-skip", label: "Skip Setup", position: "right" },
547
570
  { id: "setup-next", label: "Next", position: "right", role: "next", variant: "primary" },
548
- { id: "setup-finish", label: "Finish", onclick: "finishSetup()", position: "right", variant: "primary", visible: false }
571
+ { action: ACTIONS.finishSetup, id: "setup-finish", label: "Finish", position: "right", variant: "primary", visible: false }
549
572
  ],
550
573
  contentId: "setup-content",
551
574
  dataAttributes: { "setup-completed": CONFIG.channels.setupCompleted ? "true" : "false" },
@@ -563,9 +586,8 @@ function generateSetupWizardModal() {
563
586
  }
564
587
  /**
565
588
  * Generates the HTML for a single channel's table rows (display row and edit form row). All channels - predefined, override, and user-defined - get both rows.
566
- * The edit form is pre-populated with the effective (resolved) values so users see what they're changing. When called from generateChannelsPanel() which already
567
- * has the listing entry, pass it via the entry parameter to avoid redundant getChannelListing() calls. POST handlers that generate a single row omit the
568
- * parameter to trigger an internal lookup.
589
+ * The edit form is pre-populated with the effective (resolved) values so users see what they're changing. Callers pass a pre-resolved listing entry via the
590
+ * entry parameter to avoid redundant getChannelListing() calls; the parameter is optional so an internal lookup remains available as a fallback.
569
591
  * @param key - The channel key.
570
592
  * @param profiles - List of available profiles with descriptions for the dropdown.
571
593
  * @param entry - Optional pre-resolved listing entry. When omitted, looked up from getChannelListing().
@@ -629,7 +651,7 @@ export function generateChannelRowHtml(key, profiles, entry) {
629
651
  // to remove them from the DOM.
630
652
  const currentSelection = resolveServiceKey(key);
631
653
  displayLines.push("<select class=\"provider-select\" data-channel=\"" + escapeHtml(key) +
632
- "\" title=\"Choose which streaming service delivers this channel\" onchange=\"updateServiceSelection(this)\"" + contentHidden + ">");
654
+ "\" title=\"Choose which streaming service delivers this channel\" data-change-action=\"" + ACTIONS.updateServiceSelection + "\"" + contentHidden + ">");
633
655
  for (const variant of serviceGroup.variants) {
634
656
  const selected = (variant.key === currentSelection) ? " selected" : "";
635
657
  const optionHidden = !isServiceTagEnabled(variant.tag) ? " hidden" : "";
@@ -640,25 +662,27 @@ export function generateChannelRowHtml(key, profiles, entry) {
640
662
  }
641
663
  else {
642
664
  // Single-service: wrap the service name in a span so client-side JS can toggle it with the no-service label. Uses profile-aware label resolution so
643
- // channels with explicit profile assignments show the profile's service name rather than the built-in name for the URL domain.
665
+ // channels with explicit profile assignments show the profile's service name rather than the builtin name for the URL domain.
644
666
  displayLines.push("<span class=\"provider-name\"" + contentHidden + ">" +
645
667
  escapeHtml(getChannelServiceLabel(channel)) + "</span>");
646
668
  }
647
669
  displayLines.push("</td>");
648
- // Optional columns: Number, HDHR, Station ID, Profile, Selector. All five are always rendered; visibility is controlled by CSS classes on the table element.
670
+ // Optional columns: Number, HDHR, Station ID, Profile, Selector - every optional column except Tags is rendered here individually; Tags is rendered
671
+ // separately below because it needs dropdown/portal markup. All are always rendered here; visibility is controlled by CSS classes on the table element.
649
672
  const cellKey = escapeHtml(key);
650
673
  displayLines.push("<td class=\"col-chnum editable-cell\" data-sort-value=\"" + escapeHtml(getChannelSortKey(channel, key, "channelNumber")) +
651
674
  "\" data-field=\"channelNumber\" data-key=\"" + cellKey + "\" data-value=\"" + (channel.channelNumber ? escapeHtml(String(channel.channelNumber)) : "") +
652
- "\" onclick=\"startInlineEdit(this)\">" + (channel.channelNumber ? escapeHtml(String(channel.channelNumber)) : "<span class=\"text-muted\">&ndash;</span>") +
653
- "</td>");
675
+ "\" data-click-action=\"" + ACTIONS.startInlineEdit + "\">" +
676
+ (channel.channelNumber ? escapeHtml(String(channel.channelNumber)) : "<span class=\"text-muted\">&ndash;</span>") + "</td>");
654
677
  // HDHR column: inline checkbox for quick toggling. The checkbox submits changes via the toggleHdhr() client-side function.
655
678
  const hdhrCheckedAttr = getEffectiveHdhrEnabled(channel) ? " checked" : "";
656
679
  displayLines.push("<td class=\"col-hdhr\" data-sort-value=\"" + escapeHtml(getChannelSortKey(channel, key, "hdhrEnabled")) +
657
680
  "\"><input type=\"checkbox\" data-key=\"" + cellKey + "\"" + hdhrCheckedAttr +
658
- " onchange=\"toggleHdhr(this)\" title=\"Include in HDHomeRun/Plex lineup\"></td>");
681
+ " data-change-action=\"" + ACTIONS.toggleHdhr + "\" title=\"Include in HDHomeRun/Plex lineup\"></td>");
659
682
  displayLines.push("<td class=\"col-stationid editable-cell\" data-sort-value=\"" + escapeHtml(getChannelSortKey(channel, key, "stationId")) +
660
683
  "\" data-field=\"stationId\" data-key=\"" + cellKey + "\" data-value=\"" + (channel.stationId ? escapeHtml(channel.stationId) : "") +
661
- "\" onclick=\"startInlineEdit(this)\">" + (channel.stationId ? escapeHtml(channel.stationId) : "<span class=\"text-muted\">&ndash;</span>") + "</td>");
684
+ "\" data-click-action=\"" + ACTIONS.startInlineEdit + "\">" +
685
+ (channel.stationId ? escapeHtml(channel.stationId) : "<span class=\"text-muted\">&ndash;</span>") + "</td>");
662
686
  // Profile column: show explicit profile as-is, or the auto-resolved friendly name with "(auto)" suffix in muted style.
663
687
  const profileSortKey = escapeHtml(getChannelSortKey(channel, key, "profile"));
664
688
  if (channel.profile) {
@@ -684,8 +708,8 @@ export function generateChannelRowHtml(key, profiles, entry) {
684
708
  "<span class=\"text-muted\">&ndash;</span>";
685
709
  displayLines.push("<td class=\"col-tags editable-cell dropdown\" data-sort-value=\"" + escapeHtml(getChannelSortKey(channel, key, "tags")) +
686
710
  "\" data-key=\"" + cellKey + "\" data-tags=\"" + escapeHtml(effectiveTags.join(",")) +
687
- "\" onclick=\"toggleInlineTagDropdown(this)\">" + tagsHtml + "</td>");
688
- // Actions column with icon buttons. Five positions per row: Edit (always), Login/placeholder, Health/placeholder, context-sensitive, and Copy URL.
711
+ "\" data-click-action=\"" + ACTIONS.toggleInlineTagDropdown + "\">" + tagsHtml + "</td>");
712
+ // Actions column with icon buttons, one entry per position below: Edit (always), Login/placeholder, Health/placeholder, context-sensitive, and Copy URL.
689
713
  displayLines.push("<td>");
690
714
  displayLines.push("<div class=\"btn-group\">");
691
715
  const escapedKey = escapeHtml(key);
@@ -693,17 +717,24 @@ export function generateChannelRowHtml(key, profiles, entry) {
693
717
  const variantKey = resolveServiceKey(key);
694
718
  const authDomain = getAuthDomainForChannel(variantKey);
695
719
  // Position 1: Edit (all channels).
696
- displayLines.push("<button type=\"button\" class=\"btn-icon btn-icon-edit\" title=\"Edit\" aria-label=\"Edit\" onclick=\"showEditForm('" + escapedKey +
697
- "')\">" + ICON_EDIT + "</button>");
698
- // Position 2: Login for enabled channels (with domain auth color), placeholder for disabled predefined. Custom channels (user-defined, not predefined) skip
699
- // login coloring because they have no service concept.
720
+ displayLines.push("<button type=\"button\" class=\"btn-icon btn-icon-edit\" title=\"Edit\" aria-label=\"Edit\" " +
721
+ "data-click-action=\"" + ACTIONS.showEditForm + "\" data-channel-key=\"" + escapedKey + "\">" + ICON_EDIT + "</button>");
722
+ // Position 2: Login for enabled channels (with tri-state domain auth color: green verified, red needs-sign-in, neutral unknown), placeholder for disabled
723
+ // predefined. Custom channels (user-defined, not predefined) skip login coloring because they have no service concept.
700
724
  if (!isDisabled) {
701
- const authTimestamp = (isPredefined || isOverride) ? getDomainAuth(authDomain) : null;
702
- const loginColorClass = authTimestamp ? " health-success" : "";
703
- const loginTitle = authTimestamp ? "Verified " + formatTimeAgo(authTimestamp) : "Not yet verified";
725
+ const authState = (isPredefined || isOverride) ? getDomainAuthState(authDomain) : null;
726
+ const needsLogin = authState?.status === "needsLogin";
727
+ const loginColorClass = (authState?.status === "verified") ? " health-success" : needsLogin ? " health-failed" : "";
728
+ // Lead the title with the action so users discover that the icon is interactive...the trailing fact reports the service-scoped auth state, since the
729
+ // auth domain is shared across every channel of the same service. The needs-sign-in title drops "or test" - signing in IS the remedy being asked for.
730
+ const serviceLabel = getChannelServiceLabel(channel);
731
+ const loginTitle = needsLogin ?
732
+ "Click to open this channel in PrismCast's Chrome to sign in. " + serviceLabel + " needs sign-in (detected " + formatTimeAgo(authState.timestamp) + ")." :
733
+ "Click to open this channel in PrismCast's Chrome to sign in or test. " + serviceLabel +
734
+ (authState ? " verified " + formatTimeAgo(authState.timestamp) : " not yet verified") + ".";
704
735
  displayLines.push("<button type=\"button\" class=\"btn-icon btn-icon-login" + loginColorClass + "\" data-auth-domain=\"" +
705
- escapeHtml(authDomain) + "\" title=\"" + loginTitle + "\" aria-label=\"Login\" " +
706
- "onclick=\"startChannelLogin('" + escapedKey + "')\">" + ICON_LOGIN + "</button>");
736
+ escapeHtml(authDomain) + "\" title=\"" + escapeHtml(loginTitle) + "\" aria-label=\"Login\" " +
737
+ "data-click-action=\"" + ACTIONS.startChannelLogin + "\" data-channel-key=\"" + escapedKey + "\">" + ICON_LOGIN + "</button>");
707
738
  }
708
739
  else {
709
740
  displayLines.push("<span class=\"btn-icon-placeholder\"></span>");
@@ -713,7 +744,8 @@ export function generateChannelRowHtml(key, profiles, entry) {
713
744
  const channelHealthResult = getChannelHealth(key, authDomain);
714
745
  const healthColorClass = (channelHealthResult?.status === "success") ? " health-success" : (channelHealthResult?.status === "failed") ? " health-failed" : "";
715
746
  const healthTitle = channelHealthResult ?
716
- (channelHealthResult.status === "success" ? "Succeeded " : "Failed ") + formatTimeAgo(channelHealthResult.timestamp) : "Not yet tuned";
747
+ "Last tune " + (channelHealthResult.status === "success" ? "succeeded " : "failed ") + formatTimeAgo(channelHealthResult.timestamp) + "." :
748
+ "Not yet tuned.";
717
749
  displayLines.push("<span class=\"btn-icon btn-icon-health" + healthColorClass + "\" title=\"" + healthTitle +
718
750
  "\" aria-label=\"Channel health\">" + ICON_HEALTH + "</span>");
719
751
  }
@@ -724,25 +756,27 @@ export function generateChannelRowHtml(key, profiles, entry) {
724
756
  // property overrides are independent concerns - a predefined channel can be disabled AND have customizations, and the toggle always reflects the visibility state.
725
757
  if (isPredefined) {
726
758
  if (isDisabled) {
727
- displayLines.push("<button type=\"button\" class=\"btn-icon btn-icon-enable\" title=\"Enable\" aria-label=\"Enable\" onclick=\"togglePredefinedChannel('" +
728
- escapedKey + "', true)\">" + ICON_ENABLE + "</button>");
759
+ displayLines.push("<button type=\"button\" class=\"btn-icon btn-icon-enable\" title=\"Enable\" aria-label=\"Enable\" " +
760
+ "data-click-action=\"" + ACTIONS.togglePredefinedChannel + "\" data-channel-key=\"" + escapedKey + "\" data-enabled=\"true\">" + ICON_ENABLE + "</button>");
729
761
  }
730
762
  else {
731
- displayLines.push("<button type=\"button\" class=\"btn-icon btn-icon-disable\" title=\"Disable\" aria-label=\"Disable\" onclick=\"togglePredefinedChannel('" +
732
- escapedKey + "', false)\">" + ICON_DISABLE + "</button>");
763
+ displayLines.push("<button type=\"button\" class=\"btn-icon btn-icon-disable\" title=\"Disable\" aria-label=\"Disable\" " +
764
+ "data-click-action=\"" + ACTIONS.togglePredefinedChannel + "\" data-channel-key=\"" + escapedKey + "\" data-enabled=\"false\">" + ICON_DISABLE + "</button>");
733
765
  }
734
766
  }
735
767
  else if (isUser) {
736
- displayLines.push("<button type=\"button\" class=\"btn-icon btn-icon-delete\" title=\"Delete\" aria-label=\"Delete\" onclick=\"deleteChannel('" +
737
- escapedKey + "')\">" + ICON_DELETE + "</button>");
768
+ displayLines.push("<button type=\"button\" class=\"btn-icon btn-icon-delete\" title=\"Delete\" aria-label=\"Delete\" " +
769
+ "data-click-action=\"" + ACTIONS.deleteChannel + "\" data-channel-key=\"" + escapedKey + "\">" + ICON_DELETE + "</button>");
738
770
  }
739
771
  // Position 5: Copy URL dropdown (all channels).
740
772
  displayLines.push("<div class=\"dropdown copy-dropdown\">");
741
773
  displayLines.push("<button type=\"button\" class=\"btn-icon btn-icon-copy\" title=\"Copy stream URL\" aria-label=\"Copy stream URL\" " +
742
- "onclick=\"toggleDropdown(this)\">" + ICON_COPY + "</button>");
774
+ "data-click-action=\"" + ACTIONS.toggleDropdown + "\">" + ICON_COPY + "</button>");
743
775
  displayLines.push("<div class=\"dropdown-menu copy-url-menu\">");
744
- displayLines.push("<div class=\"dropdown-item\" onclick=\"copyStreamUrl('hls', '" + escapedKey + "')\">Copy HLS URL</div>");
745
- displayLines.push("<div class=\"dropdown-item\" onclick=\"copyStreamUrl('mpegts', '" + escapedKey + "')\">Copy MPEG-TS URL</div>");
776
+ displayLines.push("<div class=\"dropdown-item\" data-click-action=\"" + ACTIONS.copyStreamUrl + "\" data-protocol=\"hls\" data-channel-key=\"" +
777
+ escapedKey + "\">Copy HLS URL</div>");
778
+ displayLines.push("<div class=\"dropdown-item\" data-click-action=\"" + ACTIONS.copyStreamUrl + "\" data-protocol=\"mpegts\" data-channel-key=\"" +
779
+ escapedKey + "\">Copy MPEG-TS URL</div>");
746
780
  displayLines.push("</div>");
747
781
  displayLines.push("</div>");
748
782
  displayLines.push("</div>");
@@ -756,7 +790,7 @@ export function generateChannelRowHtml(key, profiles, entry) {
756
790
  editLines.push("<td colspan=\"" + String(TOTAL_COLUMN_COUNT) + "\">");
757
791
  editLines.push("<div class=\"channel-form\" style=\"margin: 0;\">");
758
792
  editLines.push("<h3>Edit Channel: " + escapedKey + "</h3>");
759
- editLines.push("<form id=\"edit-channel-form-" + escapedKey + "\" onsubmit=\"return submitChannelForm(event, 'edit')\">");
793
+ editLines.push("<form id=\"edit-channel-form-" + escapedKey + "\" data-submit-action=\"" + ACTIONS.submitChannelFormEdit + "\" data-submit-prevent-default>");
760
794
  editLines.push("<input type=\"hidden\" name=\"action\" value=\"edit\">");
761
795
  editLines.push("<input type=\"hidden\" name=\"key\" value=\"" + escapedKey + "\">");
762
796
  // For override channels, fetch the customization provenance once. The accessor returns which fields the user has explicitly customized (across canonical and
@@ -816,12 +850,14 @@ export function generateChannelRowHtml(key, profiles, entry) {
816
850
  defaults: isOverride ? {
817
851
  channelNumber: resetValueFor("channelNumber"),
818
852
  channelSelector: resetValueFor("channelSelector"),
853
+ forceCapture: resetValueFor("forceCapture"),
819
854
  guideTitle: resetValueFor("guideTitle"),
820
855
  hdhrEnabled: resetValueFor("hdhrEnabled"),
821
856
  logoUrl: resetValueFor("logoUrl"),
822
857
  stationId: resetValueFor("stationId"),
823
858
  tags: resetValueFor("tags")
824
859
  } : undefined,
860
+ forceCapture: channel.forceCapture === true,
825
861
  guideTitleValue: channel.guideTitle ?? "",
826
862
  hdhrEnabled: getEffectiveHdhrEnabled(channel),
827
863
  logoUrlValue: channel.logoUrl ?? "",
@@ -831,10 +867,11 @@ export function generateChannelRowHtml(key, profiles, entry) {
831
867
  // Form buttons.
832
868
  editLines.push("<div class=\"form-buttons\">");
833
869
  editLines.push("<button type=\"submit\" class=\"btn btn-primary\">Save Changes</button>");
834
- editLines.push("<button type=\"button\" class=\"btn btn-secondary\" onclick=\"hideEditForm('" + escapedKey + "')\">Cancel</button>");
870
+ editLines.push("<button type=\"button\" class=\"btn btn-secondary\" data-click-action=\"" + ACTIONS.hideEditForm +
871
+ "\" data-channel-key=\"" + escapedKey + "\">Cancel</button>");
835
872
  if (isOverride) {
836
- editLines.push("<button type=\"button\" class=\"btn btn-secondary btn-revert\" onclick=\"revertChannel('" + escapedKey +
837
- "')\">Revert to Defaults</button>");
873
+ editLines.push("<button type=\"button\" class=\"btn btn-secondary btn-revert\" data-click-action=\"" + ACTIONS.revertChannel + "\" data-channel-key=\"" + escapedKey +
874
+ "\">Revert to Defaults</button>");
838
875
  }
839
876
  editLines.push("</div>");
840
877
  editLines.push("</form>");
@@ -895,7 +932,8 @@ function getHdhrCounts(listing) {
895
932
  }
896
933
  /**
897
934
  * Computes per-tag channel counts for the Quick Actions tag bulk toggles. For each tag in the active vocabulary, counts how many enabled, service-available
898
- * channels have that tag in their effective tags. Returns undefined when the tags column is not visible (no tag toggles to update).
935
+ * channels have that tag in their effective tags. Returns undefined when the tags column is hidden or the active tag vocabulary is empty (no tag toggles to
936
+ * update).
899
937
  * @param listing - The channel listing entries.
900
938
  * @returns A record mapping tag names to { count, total }, or undefined when hidden.
901
939
  */
@@ -974,7 +1012,7 @@ export function generateServiceFilterToolbar() {
974
1012
  lines.push("<div class=\"dropdown provider-dropdown\">");
975
1013
  const buttonText = hasFilter ? "Filtered" : "All Services";
976
1014
  lines.push("<button type=\"button\" class=\"btn btn-sm toolbar-icon-btn\" id=\"provider-filter-btn\" " +
977
- "title=\"Filter channels by streaming service\" onclick=\"toggleDropdown(this)\">" + ICON_FILTER + " " + buttonText + " &#9662;</button>");
1015
+ "title=\"Filter channels by streaming service\" data-click-action=\"" + ACTIONS.toggleDropdown + "\">" + ICON_FILTER + " " + buttonText + " &#9662;</button>");
978
1016
  lines.push("<div class=\"dropdown-menu provider-dropdown-menu\">");
979
1017
  for (const tagInfo of allTags) {
980
1018
  const isDirectTag = tagInfo.tag === "direct";
@@ -983,7 +1021,7 @@ export function generateServiceFilterToolbar() {
983
1021
  const disabledAttr = isDirectTag ? " disabled" : "";
984
1022
  lines.push("<label class=\"provider-option\">");
985
1023
  lines.push("<input type=\"checkbox\" data-tag=\"" + escapeHtml(tagInfo.tag) + "\"" + checkedAttr + disabledAttr +
986
- " onchange=\"toggleServiceTag(this)\"> " + serviceDisplaySpan(tagInfo.displayName, tagInfo.domain, tagInfo.iconUrl));
1024
+ " data-change-action=\"" + ACTIONS.toggleServiceTag + "\"> " + serviceDisplaySpan(tagInfo.displayName, tagInfo.domain, tagInfo.iconUrl));
987
1025
  lines.push("</label>");
988
1026
  }
989
1027
  lines.push("</div>");
@@ -1000,7 +1038,7 @@ export function generateServiceFilterToolbar() {
1000
1038
  lines.push("<span class=\"provider-chip\" data-tag=\"" + escapeHtml(tag) + "\">" +
1001
1039
  serviceDisplaySpan(displayName, chipTag?.domain, chipTag?.iconUrl, true) +
1002
1040
  "<button type=\"button\" class=\"chip-close\" title=\"Remove " + escapeHtml(displayName) + " from filter\" aria-label=\"Remove " +
1003
- escapeHtml(displayName) + "\" onclick=\"removeServiceChip('" + escapeHtml(tag) + "')\">&times;</button></span>");
1041
+ escapeHtml(displayName) + "\" data-click-action=\"" + ACTIONS.removeServiceChip + "\" data-tag-name=\"" + escapeHtml(tag) + "\">&times;</button></span>");
1004
1042
  }
1005
1043
  }
1006
1044
  lines.push("</div>");
@@ -1031,7 +1069,7 @@ export function generateChannelsPanel(channelMessage, channelError, editingChann
1031
1069
  lines.push("<p>Define and manage streaming channels for the playlist. Customized channels are highlighted.</p>");
1032
1070
  lines.push("<p class=\"description-hint\">Tip: Use the <strong>service filter</strong> above to show only channels from services you subscribe to &mdash; ", "this also controls which channels Channels DVR sees in the playlist. Use the <strong>service dropdown</strong> on any multi-service channel to choose ", "which streaming service delivers it (e.g., Comedy Central via Hulu vs Sling). Click the <strong>edit icon</strong> to customize any channel's name, ", "Gracenote station ID, URL, or other properties.</p>");
1033
1071
  lines.push("</div>");
1034
- // Toolbar with three dropdown menus: Manage Channels (primary actions), Import/Export (data I/O), and Quick Actions (bulk operations). Each dropdown has an
1072
+ // Toolbar dropdown menus: Manage Channels (primary actions), Import/Export (data I/O), and Quick Actions (bulk operations). Each dropdown has an
1035
1073
  // inline SVG icon + label + chevron for visual discoverability. Grouped menus reduce visual clutter and separate channel management from data I/O and bulk
1036
1074
  // operations.
1037
1075
  lines.push("<div class=\"channel-toolbar\">");
@@ -1039,7 +1077,7 @@ export function generateChannelsPanel(channelMessage, channelError, editingChann
1039
1077
  // Manage Channels dropdown - primary channel creation and setup actions.
1040
1078
  lines.push("<div class=\"dropdown\">");
1041
1079
  lines.push("<button type=\"button\" class=\"btn btn-primary btn-sm toolbar-icon-btn\" title=\"Add, browse, or set up channels\" " +
1042
- "onclick=\"toggleDropdown(this)\">" + ICON_MANAGE + " Manage Channels &#9662;</button>");
1080
+ "data-click-action=\"" + ACTIONS.toggleDropdown + "\">" + ICON_MANAGE + " Manage Channels &#9662;</button>");
1043
1081
  lines.push("<div class=\"dropdown-menu\">");
1044
1082
  // Dropdown item icons: tinted variants of the shared icons for visual differentiation in menu items. Each uses a distinct color so items are scannable at
1045
1083
  // a glance. These are local to this function because the tint colors are context-specific to this dropdown.
@@ -1054,47 +1092,47 @@ export function generateChannelsPanel(channelMessage, channelError, editingChann
1054
1092
  const TINTED_TAG = "<svg width=\"14\" height=\"14\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"#9b59b6\" stroke-width=\"1.5\" " +
1055
1093
  "stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M1.5 8.7V2.5a1 1 0 011-1h6.2a1 1 0 01.7.3l5.1 5.1a1 1 0 010 1.4l-5.5 5.5a1 " +
1056
1094
  "1 0 01-1.4 0L1.8 9.4a1 1 0 01-.3-.7z\"/><circle cx=\"5\" cy=\"5\" r=\"1\"/></svg>";
1057
- lines.push("<div class=\"dropdown-item dropdown-item-icon\" onclick=\"dropdowns.close(); " +
1058
- "document.getElementById('add-channel-form').style.display='block';\">" + TINTED_ADD + " Add Channel</div>");
1059
- lines.push("<div class=\"dropdown-item dropdown-item-icon\" onclick=\"dropdowns.close(); openBrowseModal()\">" + TINTED_BROWSE +
1095
+ lines.push("<div class=\"dropdown-item dropdown-item-icon\" data-click-action=\"" + ACTIONS.showAddChannelForm + "\" data-click-close-dropdown>" + TINTED_ADD +
1096
+ " Add Channel</div>");
1097
+ lines.push("<div class=\"dropdown-item dropdown-item-icon\" data-click-action=\"" + ACTIONS.openBrowseModal + "\" data-click-close-dropdown>" + TINTED_BROWSE +
1060
1098
  " Browse Service Channels</div>");
1061
- lines.push("<div class=\"dropdown-item dropdown-item-icon\" onclick=\"dropdowns.close(); openTagManager()\">" + TINTED_TAG +
1099
+ lines.push("<div class=\"dropdown-item dropdown-item-icon\" data-click-action=\"" + ACTIONS.openTagManager + "\" data-click-close-dropdown>" + TINTED_TAG +
1062
1100
  " Manage Tags</div>");
1063
- lines.push("<div class=\"dropdown-item dropdown-item-icon\" onclick=\"dropdowns.close(); openSetupWizard()\">" + TINTED_SETUP +
1101
+ lines.push("<div class=\"dropdown-item dropdown-item-icon\" data-click-action=\"" + ACTIONS.openSetupWizard + "\" data-click-close-dropdown>" + TINTED_SETUP +
1064
1102
  " Service Setup</div>");
1065
1103
  lines.push("</div>");
1066
1104
  lines.push("</div>");
1067
1105
  // Import / Export dropdown - data I/O operations.
1068
1106
  lines.push("<div class=\"dropdown\">");
1069
1107
  lines.push("<button type=\"button\" class=\"btn btn-secondary btn-sm toolbar-icon-btn\" title=\"Import or export channel data\" " +
1070
- "onclick=\"toggleDropdown(this)\">" + ICON_TRANSFER + " Import / Export &#9662;</button>");
1108
+ "data-click-action=\"" + ACTIONS.toggleDropdown + "\">" + ICON_TRANSFER + " Import / Export &#9662;</button>");
1071
1109
  lines.push("<div class=\"dropdown-menu\">");
1072
1110
  const TINTED_IMPORT = "<svg width=\"14\" height=\"14\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"#5b8def\" stroke-width=\"1.5\" " +
1073
1111
  "stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M8 2v8M5 7l3 3 3-3\"/><path d=\"M2 11v2a1 1 0 001 1h10a1 1 0 001-1v-2\"/></svg>";
1074
1112
  const TINTED_EXPORT = "<svg width=\"14\" height=\"14\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"#22a563\" stroke-width=\"1.5\" " +
1075
1113
  "stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M8 10V2M5 5l3-3 3 3\"/><path d=\"M2 11v2a1 1 0 001 1h10a1 1 0 001-1v-2\"/></svg>";
1076
- lines.push("<div class=\"dropdown-item dropdown-item-icon\" onclick=\"dropdowns.close(); document.getElementById('import-channels-file').click()\">" +
1114
+ lines.push("<div class=\"dropdown-item dropdown-item-icon\" data-click-action=\"" + ACTIONS.triggerChannelsImport + "\" data-click-close-dropdown>" +
1077
1115
  TINTED_IMPORT + " Import Channels (JSON)</div>");
1078
- lines.push("<div class=\"dropdown-item dropdown-item-icon\" onclick=\"dropdowns.close(); document.getElementById('import-m3u-file').click()\">" +
1116
+ lines.push("<div class=\"dropdown-item dropdown-item-icon\" data-click-action=\"" + ACTIONS.triggerM3uImport + "\" data-click-close-dropdown>" +
1079
1117
  TINTED_IMPORT + " Import M3U Playlist</div>");
1080
1118
  lines.push("<label class=\"dropdown-option\"><input type=\"checkbox\" id=\"m3u-replace-duplicates\"> Replace duplicates on M3U import</label>");
1081
1119
  lines.push("<div class=\"dropdown-divider\"></div>");
1082
- lines.push("<div class=\"dropdown-item dropdown-item-icon\" onclick=\"dropdowns.close(); exportChannels()\">" +
1120
+ lines.push("<div class=\"dropdown-item dropdown-item-icon\" data-click-action=\"" + ACTIONS.exportChannels + "\" data-click-close-dropdown>" +
1083
1121
  TINTED_EXPORT + " Export Channels (JSON)</div>");
1084
1122
  lines.push("</div>");
1085
1123
  lines.push("</div>");
1086
- lines.push("<input type=\"file\" id=\"import-m3u-file\" accept=\".m3u,.m3u8\" style=\"display: none;\" onchange=\"importM3U(this)\">");
1124
+ lines.push("<input type=\"file\" id=\"import-m3u-file\" accept=\".m3u,.m3u8\" style=\"display: none;\" data-change-action=\"" + ACTIONS.importM3u + "\">");
1087
1125
  // Visible column set, used by Quick Actions to gate column-dependent options and by the table header to set initial hide classes.
1088
1126
  const visibleCols = new Set(CONFIG.channels.visibleColumns);
1089
1127
  // Quick Actions dropdown - bulk operations for predefined channels.
1090
1128
  lines.push("<div class=\"dropdown quick-actions-dropdown\">");
1091
1129
  lines.push("<button type=\"button\" class=\"btn btn-secondary btn-sm toolbar-icon-btn\" title=\"Bulk operations for predefined channels\" " +
1092
- "onclick=\"toggleDropdown(this)\">" + ICON_BOLT + " Quick Actions &#9662;</button>");
1130
+ "data-click-action=\"" + ACTIONS.toggleDropdown + "\">" + ICON_BOLT + " Quick Actions &#9662;</button>");
1093
1131
  lines.push("<div class=\"dropdown-menu\">");
1094
1132
  // Compute initial toggle counts for predefined channel scopes. The server is the single source of truth - the client renders what we return here.
1095
1133
  const scopeCounts = getPredefinedScopeCounts();
1096
- // Three toggle rows: checkbox + label + count. Clicking toggles the group via bulkTogglePredefined(). The onclick uses event.preventDefault() to stop the
1097
- // native checkbox toggle - the server response drives the update.
1134
+ // One toggle row per predefined scope: checkbox + label + count. Clicking toggles the group via bulkTogglePredefined(). The data-click-prevent-default
1135
+ // attribute stops the native checkbox toggle - the server response drives the update.
1098
1136
  const scopes = [
1099
1137
  { count: scopeCounts.all.enabled, label: "All Predefined", scope: "all", total: scopeCounts.all.total },
1100
1138
  { count: scopeCounts.east.enabled, label: "East Variants", scope: "east", total: scopeCounts.east.total },
@@ -1102,8 +1140,8 @@ export function generateChannelsPanel(channelMessage, channelError, editingChann
1102
1140
  ];
1103
1141
  for (const s of scopes) {
1104
1142
  const checked = (s.count === s.total) ? " checked" : "";
1105
- lines.push("<label class=\"provider-option\" onclick=\"event.preventDefault(); bulkTogglePredefined(!this.querySelector('input').checked, '" + s.scope +
1106
- "')\">" + "<input type=\"checkbox\" class=\"scope-toggle\" data-scope=\"" + s.scope + "\"" + checked + "> " +
1143
+ lines.push("<label class=\"provider-option\" data-click-action=\"" + ACTIONS.bulkTogglePredefined + "\" data-scope=\"" + s.scope + "\" data-click-prevent-default>" +
1144
+ "<input type=\"checkbox\" class=\"scope-toggle\" data-scope=\"" + s.scope + "\"" + checked + "> " +
1107
1145
  s.label + "<span class=\"quick-action-count\" data-scope=\"" + s.scope + "\" data-enabled=\"" + String(s.count) + "\" data-total=\"" + String(s.total) +
1108
1146
  "\">" + String(s.count) + " of " + String(s.total) + " enabled</span></label>");
1109
1147
  }
@@ -1115,7 +1153,7 @@ export function generateChannelsPanel(channelMessage, channelError, editingChann
1115
1153
  lines.push("<div class=\"bulk-assign-row\">");
1116
1154
  lines.push("<span>Set all channels to:</span>");
1117
1155
  lines.push("<select id=\"bulk-assign-select\" class=\"bulk-assign-select\" title=\"Switch all multi-service channels to this service\" " +
1118
- "onchange=\"if(this.value) { dropdowns.close(); bulkAssignService(this.value); this.value = ''; }\">");
1156
+ "data-change-action=\"" + ACTIONS.bulkAssignService + "\">");
1119
1157
  lines.push("<option value=\"\">Select service</option>");
1120
1158
  for (const tagInfo of allTags) {
1121
1159
  const hidden = (hasFilter && !enabled.includes(tagInfo.tag) && (tagInfo.tag !== "direct")) ? " hidden" : "";
@@ -1133,8 +1171,9 @@ export function generateChannelsPanel(channelMessage, channelError, editingChann
1133
1171
  lines.push("<div class=\"bulk-assign-row\">");
1134
1172
  lines.push("<span>Auto-number from:</span>");
1135
1173
  lines.push("<input type=\"number\" id=\"auto-number-start\" class=\"auto-number-input\" value=\"1\" min=\"0\" max=\"99999\" " +
1136
- "placeholder=\"Clear\" onclick=\"event.stopPropagation()\">");
1137
- lines.push("<button type=\"button\" class=\"btn btn-sm btn-secondary\" onclick=\"dropdowns.close(); autoNumberChannels()\">Apply</button>");
1174
+ "placeholder=\"Clear\" data-click-stop-propagation>");
1175
+ lines.push("<button type=\"button\" class=\"btn btn-sm btn-secondary\" data-click-action=\"" + ACTIONS.autoNumberChannels +
1176
+ "\" data-click-close-dropdown>Apply</button>");
1138
1177
  lines.push("</div>");
1139
1178
  lines.push("</div>");
1140
1179
  // HDHR bulk toggle - tri-state checkbox to enable/disable all channels for the HDHomeRun lineup. Visible when the HDHR column is shown.
@@ -1143,7 +1182,7 @@ export function generateChannelsPanel(channelMessage, channelError, editingChann
1143
1182
  const hdhrCheckedAttr = (hdhrCounts.enabled === hdhrCounts.total) ? " checked" : "";
1144
1183
  const hdhrIndeterminate = ((hdhrCounts.enabled > 0) && (hdhrCounts.enabled < hdhrCounts.total)) ? " data-indeterminate=\"true\"" : "";
1145
1184
  lines.push("<div id=\"quick-action-hdhr\"" + hdhrVisible + ">");
1146
- lines.push("<label class=\"provider-option\" onclick=\"event.preventDefault(); bulkToggleHdhr()\">");
1185
+ lines.push("<label class=\"provider-option\" data-click-action=\"" + ACTIONS.bulkToggleHdhr + "\" data-click-prevent-default>");
1147
1186
  lines.push("<input type=\"checkbox\" id=\"hdhr-bulk-toggle\"" + hdhrCheckedAttr + hdhrIndeterminate + "> ");
1148
1187
  lines.push("Include all in HDHR/Plex");
1149
1188
  lines.push("<span class=\"quick-action-count\" id=\"hdhr-bulk-count\">" + String(hdhrCounts.enabled) + " of " + String(hdhrCounts.total) + "</span>");
@@ -1177,8 +1216,8 @@ export function generateChannelsPanel(channelMessage, channelError, editingChann
1177
1216
  const count = tagCountMap.get(tag) ?? 0;
1178
1217
  const checkedAttr = (count === totalEnabled) ? " checked" : "";
1179
1218
  const indeterminate = ((count > 0) && (count < totalEnabled)) ? " data-indeterminate=\"true\"" : "";
1180
- lines.push("<label class=\"provider-option\" onclick=\"event.preventDefault(); bulkToggleTag('" + escapeHtml(tag) +
1181
- "', !this.querySelector('input').checked)\">" +
1219
+ lines.push("<label class=\"provider-option\" data-click-action=\"" + ACTIONS.bulkToggleTag +
1220
+ "\" data-tag-name=\"" + escapeHtml(tag) + "\" data-click-prevent-default>" +
1182
1221
  "<input type=\"checkbox\" class=\"tag-bulk-toggle\" data-tag=\"" + escapeHtml(tag) + "\"" + checkedAttr + indeterminate + "> " +
1183
1222
  escapeHtml(tag) +
1184
1223
  "<span class=\"quick-action-count\" data-tag-count=\"" + escapeHtml(tag) + "\">" +
@@ -1232,7 +1271,7 @@ export function generateChannelsPanel(channelMessage, channelError, editingChann
1232
1271
  const addFormVisible = (showAddForm === true) || (formErrors && formErrors.has("key") && !editingChannelKey);
1233
1272
  lines.push("<div id=\"add-channel-form\" class=\"channel-form\" style=\"display: " + (addFormVisible ? "block" : "none") + ";\">");
1234
1273
  lines.push("<h3>Add New Channel</h3>");
1235
- lines.push("<form id=\"add-channel-form-el\" onsubmit=\"return submitChannelForm(event, 'add')\">");
1274
+ lines.push("<form id=\"add-channel-form-el\" data-submit-action=\"" + ACTIONS.submitChannelFormAdd + "\" data-submit-prevent-default>");
1236
1275
  lines.push("<input type=\"hidden\" name=\"action\" value=\"add\">");
1237
1276
  // Service pills. Clicking a service auto-fills the URL field, which triggers the existing URL-change infrastructure (datalist population, profile
1238
1277
  // resolution). The pills use serviceDisplaySpan for icon rendering via the shared processServiceDisplays path. Guide-grid services fill with their
@@ -1247,7 +1286,7 @@ export function generateChannelsPanel(channelMessage, channelError, editingChann
1247
1286
  }
1248
1287
  const pillUrl = addFormGuideUrls[tagInfo.tag] ?? (tagInfo.domain ? "https://" + tagInfo.domain + "/" : "");
1249
1288
  lines.push("<button type=\"button\" class=\"provider-pill\" data-slug=\"" + escapeHtml(tagInfo.tag) + "\" data-url=\"" + escapeHtml(pillUrl) +
1250
- "\" onclick=\"selectServicePill(this)\">" + serviceDisplaySpan(tagInfo.displayName, tagInfo.domain, tagInfo.iconUrl) + "</button>");
1289
+ "\" data-click-action=\"" + ACTIONS.selectServicePill + "\">" + serviceDisplaySpan(tagInfo.displayName, tagInfo.domain, tagInfo.iconUrl) + "</button>");
1251
1290
  }
1252
1291
  lines.push("</div>");
1253
1292
  lines.push("</div>");
@@ -1277,17 +1316,19 @@ export function generateChannelsPanel(channelMessage, channelError, editingChann
1277
1316
  lines.push("<div id=\"add-predefined-hint\" class=\"hint predefined-hint\" style=\"display: none;\"></div>");
1278
1317
  // Profile dropdown.
1279
1318
  lines.push(...generateProfileDropdown("add-profile", formValues?.get("profile") ?? "", profiles));
1280
- // Advanced fields (station ID, guide title, logo URL, channel selector, channel number, tags, HDHR).
1319
+ // Advanced fields (station ID, guide title, logo URL, channel selector, channel number, tags, HDHR, force capture). The force-capture read mirrors
1320
+ // parseFormBody's exact-"true" test, so a re-render after a validation error preserves whatever the user had checked.
1281
1321
  lines.push(...generateAdvancedFields("add", {
1282
1322
  channelNumberValue: formValues?.get("channelNumber") ?? "",
1283
1323
  channelSelectorValue: formValues?.get("channelSelector") ?? "",
1324
+ forceCapture: formValues?.get("forceCapture") === "true",
1284
1325
  hdhrEnabled: formValues?.get("hdhrEnabled") !== "false",
1285
1326
  stationIdValue: formValues?.get("stationId") ?? ""
1286
1327
  }));
1287
1328
  // Form buttons.
1288
1329
  lines.push("<div class=\"form-buttons\">");
1289
1330
  lines.push("<button type=\"submit\" class=\"btn btn-primary\">Add Channel</button>");
1290
- lines.push("<button type=\"button\" class=\"btn btn-secondary\" onclick=\"document.getElementById('add-channel-form').style.display='none';\">Cancel</button>");
1331
+ lines.push("<button type=\"button\" class=\"btn btn-secondary\" data-click-action=\"" + ACTIONS.hideAddChannelForm + "\">Cancel</button>");
1291
1332
  lines.push("</div>");
1292
1333
  lines.push("</form>");
1293
1334
  // Close the add-channel-form wrapper opened at the top of this builder.
@@ -1326,14 +1367,14 @@ export function generateChannelsPanel(channelMessage, channelError, editingChann
1326
1367
  { cssClass: "col-provider", field: "provider", label: "Service" },
1327
1368
  ...OPTIONAL_COLUMNS
1328
1369
  ];
1329
- // All sortable headers use the same DOM structure: <th onclick> wraps a <span class="sort-label"> for the label text. The sort update logic targets
1330
- // .sort-label to modify only the label - never touching other children like the Tags filter dropdown. Clicking anywhere on the <th> triggers sort;
1370
+ // All sortable headers use the same DOM structure: <th data-click-action> wraps a <span class="sort-label"> for the label text. The sort update logic
1371
+ // targets .sort-label to modify only the label - never touching other children like the Tags filter dropdown. Clicking anywhere on the <th> triggers sort;
1331
1372
  // additional children (like the filter button) use event.stopPropagation() to prevent sort when interacting with them.
1332
1373
  for (const hdr of sortableHeaders) {
1333
1374
  const isActive = (sortField === hdr.field);
1334
1375
  const activeIndicator = isActive ? ((sortDir === "asc") ? " &#9650;" : " &#9660;") : "";
1335
1376
  lines.push("<th class=\"" + hdr.cssClass + " sortable\" data-sort-field=\"" + hdr.field +
1336
- "\" onclick=\"channelTable.sort('" + hdr.field + "')\">");
1377
+ "\" data-click-action=\"" + ACTIONS.channelTableSort + "\" data-field=\"" + hdr.field + "\">");
1337
1378
  lines.push("<span class=\"sort-label\">" + hdr.label + activeIndicator + "</span>");
1338
1379
  // Tags header: additional filter dropdown alongside the sort label. The dropdown is a client-side-only view filter (transient, not persisted) that
1339
1380
  // shows/hides rows based on their data-channel-tags attribute. The dropdown content comes from generateTagFilterContent() - the single source of truth
@@ -1341,7 +1382,7 @@ export function generateChannelsPanel(channelMessage, channelError, editingChann
1341
1382
  if (hdr.field === "tags") {
1342
1383
  lines.push("<div class=\"dropdown tag-filter-dropdown\" style=\"display: inline;\">");
1343
1384
  lines.push("<button type=\"button\" class=\"btn-icon btn-tag-filter\" title=\"Filter by tag\" " +
1344
- "onclick=\"event.stopPropagation(); toggleDropdown(this)\">" + ICON_FILTER + "</button>");
1385
+ "data-click-action=\"" + ACTIONS.toggleDropdown + "\" data-click-stop-propagation>" + ICON_FILTER + "</button>");
1345
1386
  lines.push("<div class=\"dropdown-menu\" id=\"tag-filter-menu\">" + generateTagFilterContent() + "</div>");
1346
1387
  lines.push("</div>");
1347
1388
  // Playlist hint icon. Hidden by default, shown by applyTagColumnFilter() when the filter is active. Clicking opens a popover with the playlist URL
@@ -1349,7 +1390,7 @@ export function generateChannelsPanel(channelMessage, channelError, editingChann
1349
1390
  lines.push("<div class=\"dropdown\" style=\"display: inline;\">");
1350
1391
  lines.push("<button type=\"button\" class=\"btn-icon btn-playlist-hint\" id=\"playlist-hint-btn\" " +
1351
1392
  "title=\"Playlist URL for this filter\" style=\"display: none;\" " +
1352
- "onclick=\"event.stopPropagation(); showPlaylistHint(this)\">" + ICON_LINK + "</button>");
1393
+ "data-click-action=\"" + ACTIONS.showPlaylistHint + "\" data-click-stop-propagation>" + ICON_LINK + "</button>");
1353
1394
  lines.push("<div class=\"dropdown-menu playlist-hint-menu\"></div>");
1354
1395
  lines.push("</div>");
1355
1396
  }
@@ -1359,15 +1400,15 @@ export function generateChannelsPanel(channelMessage, channelError, editingChann
1359
1400
  lines.push("<th class=\"col-actions\"><span>Actions</span>");
1360
1401
  lines.push("<div class=\"dropdown column-picker\">");
1361
1402
  lines.push("<button type=\"button\" class=\"btn-icon btn-col-picker\" title=\"Table options\" aria-label=\"Table options\" " +
1362
- "onclick=\"toggleDropdown(this)\">&#8942;</button>");
1403
+ "data-click-action=\"" + ACTIONS.toggleDropdown + "\">&#8942;</button>");
1363
1404
  lines.push("<div class=\"dropdown-menu column-picker-menu\">");
1364
- lines.push("<label class=\"provider-option\"><input type=\"checkbox\" id=\"show-disabled-toggle\" onchange=\"toggleDisabledVisibility()\"> " +
1405
+ lines.push("<label class=\"provider-option\"><input type=\"checkbox\" id=\"show-disabled-toggle\" data-change-action=\"" + ACTIONS.toggleDisabledVisibility + "\"> " +
1365
1406
  "Show disabled channels</label>");
1366
1407
  lines.push("<div class=\"dropdown-divider\"></div>");
1367
1408
  for (const col of OPTIONAL_COLUMNS) {
1368
1409
  const checked = visibleCols.has(col.field) ? " checked" : "";
1369
1410
  lines.push("<label class=\"provider-option\"><input type=\"checkbox\" data-col-class=\"" + col.cssClass + "\" data-col-field=\"" + col.field +
1370
- "\" onchange=\"toggleColumn(this)\"" + checked + "> " + col.label + "</label>");
1411
+ "\" data-change-action=\"" + ACTIONS.toggleColumn + "\"" + checked + "> " + col.label + "</label>");
1371
1412
  }
1372
1413
  lines.push("</div>");
1373
1414
  lines.push("</div>");
@@ -1403,7 +1444,7 @@ export function generateChannelsPanel(channelMessage, channelError, editingChann
1403
1444
  const vocabulary = getActiveTagVocabulary();
1404
1445
  lines.push("<div id=\"inline-tag-portal\" class=\"dropdown-menu inline-tag-menu\">");
1405
1446
  for (const tag of vocabulary) {
1406
- lines.push("<label class=\"provider-option\" onclick=\"event.stopPropagation()\">" +
1447
+ lines.push("<label class=\"provider-option\" data-click-stop-propagation>" +
1407
1448
  "<input type=\"checkbox\" class=\"inline-tag-checkbox\" data-tag=\"" + escapeHtml(tag) + "\"> " + escapeHtml(tag) + "</label>");
1408
1449
  }
1409
1450
  lines.push("</div>");