adp-web-components 0.4.0 → 0.4.2

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 (476) hide show
  1. package/dist/cjs/{add-icon-BklgM02f.js → add-icon-r-uugVfd.js} +1 -1
  2. package/dist/cjs/{app-globals-XGpRBTr6.js → app-globals-BoQSQFz7.js} +1 -1
  3. package/dist/cjs/{arrow-up-icon-BLo1aQC2.js → arrow-up-icon-BfnEuyBs.js} +1 -1
  4. package/dist/cjs/branch-date-dropdown_17.cjs.entry.js +51 -45
  5. package/dist/cjs/{calendar-days-icon-Cf14ROXV.js → calendar-days-icon-CDsv0_-r.js} +1 -1
  6. package/dist/cjs/{check-icon-DaQxP2EY.js → check-icon-BNxUv6rL.js} +1 -1
  7. package/dist/cjs/clock-DsYk5zt3.js +30 -0
  8. package/dist/cjs/dead-stock-lookup_3.cjs.entry.js +10 -9
  9. package/dist/cjs/{decode-time-offset-DOUImlif.js → decode-time-offset-CcpzHLhD.js} +10 -2
  10. package/dist/cjs/flexible-container.cjs.entry.js +2 -2
  11. package/dist/cjs/form-checkbox.cjs.entry.js +3 -3
  12. package/dist/cjs/form-date-picker.cjs.entry.js +25 -19
  13. package/dist/cjs/{form-error-message-BtLdHgxO.js → form-error-message-DdEeLvoD.js} +1 -1
  14. package/dist/cjs/{form-input-label-B4lkF89K.js → form-input-label-U5kCHkJl.js} +1 -1
  15. package/dist/cjs/form-input_7.cjs.entry.js +13 -13
  16. package/dist/cjs/form-switch.cjs.entry.js +3 -3
  17. package/dist/cjs/form-time-picker.cjs.entry.js +4 -4
  18. package/dist/cjs/{functions-gb1i8Hkl.js → functions-Bz2-CHK9.js} +5 -4
  19. package/dist/cjs/general-form.cjs.entry.js +11 -10
  20. package/dist/cjs/general-inquiry-form.cjs.entry.js +11 -10
  21. package/dist/cjs/{get-language-from-url-DwOt9DVH.js → get-language-from-url-CivsCH6R.js} +1 -1
  22. package/dist/cjs/{get-local-language-CZeC0BN9.js → get-local-language-BBdU8ppU.js} +2 -2
  23. package/dist/cjs/get-mock-files-VecUdVE7.js +61 -0
  24. package/dist/cjs/{index-BdHAYIvO.js → index-BXyIPFx-.js} +3 -0
  25. package/dist/cjs/information-table.cjs.entry.js +3 -3
  26. package/dist/cjs/{loader-icon-aY56OHXW.js → loader-icon-BkPuyYFm.js} +1 -1
  27. package/dist/cjs/loader.cjs.js +3 -3
  28. package/dist/cjs/loading-spinner.cjs.entry.js +1 -1
  29. package/dist/cjs/manufacturer-part-lookup.cjs.entry.js +4 -4
  30. package/dist/cjs/{mappers-DnWgECCv.js → mappers-D-v6mh0y.js} +10 -8
  31. package/dist/cjs/{material-card-D1UQcy6U.js → material-card-mXTu16Ev.js} +1 -1
  32. package/dist/cjs/mock-assets-Dio-Q8Yd.js +13 -0
  33. package/dist/cjs/part-lookup.cjs.entry.js +4 -4
  34. package/dist/cjs/{qr-code-scan-CtKK6wLL.js → qr-code-scan-BH1paDvW.js} +1 -1
  35. package/dist/cjs/{reveal-below-CyYolTYX.js → reveal-below-7_fSN_sU.js} +1 -1
  36. package/dist/cjs/service-booking-form.cjs.entry.js +11 -10
  37. package/dist/cjs/shift-accordion.cjs.entry.js +1 -1
  38. package/dist/cjs/shift-checkbox.cjs.entry.js +3 -3
  39. package/dist/cjs/shift-components.cjs.js +3 -3
  40. package/dist/cjs/shift-slider.cjs.entry.js +1 -1
  41. package/dist/cjs/shift-switch.cjs.entry.js +2 -2
  42. package/dist/cjs/shift-tab-content.cjs.entry.js +2 -2
  43. package/dist/cjs/shift-tabs_2.cjs.entry.js +7 -7
  44. package/dist/cjs/ssc-lookup-form.cjs.entry.js +11 -10
  45. package/dist/cjs/test-drive-form.cjs.entry.js +11 -10
  46. package/dist/cjs/{triangle-alert-CTvU1eGY.js → triangle-alert-C_xSEmRy.js} +1 -1
  47. package/dist/cjs/vehicle-accessories_8.cjs.entry.js +72 -44
  48. package/dist/cjs/{vehicle-info-layout-CRpL8UMY.js → vehicle-info-layout-CjYyiBEq.js} +1 -1
  49. package/dist/cjs/vehicle-lookup.cjs.entry.js +21 -17
  50. package/dist/cjs/vehicle-quotation-form.cjs.entry.js +8 -7
  51. package/dist/cjs/{white-loader-CveXuRsH.js → white-loader-CH2BrmT6.js} +1 -1
  52. package/dist/collection/components/components/empty.css +1 -1
  53. package/dist/collection/components/components/flexible-container.css +1 -1
  54. package/dist/collection/components/components/information-table.css +1 -1
  55. package/dist/collection/components/components/shift-portal.css +1 -1
  56. package/dist/collection/components/components/shift-select-dropdown.css +1 -1
  57. package/dist/collection/components/components/shift-select.css +1 -1
  58. package/dist/collection/components/components/shift-tab-content.css +1 -1
  59. package/dist/collection/components/components/shift-tabs.css +1 -1
  60. package/dist/collection/components/form-elements/branch-date-picker.js +30 -8
  61. package/dist/collection/components/form-elements/branch-slot-dropdown.css +1 -1
  62. package/dist/collection/components/form-elements/branch-slot-picker.js +29 -6
  63. package/dist/collection/components/form-elements/form-date-picker-legacy.js +40 -15
  64. package/dist/collection/components/form-elements/form-inputs.css +1 -1
  65. package/dist/collection/components/form-elements/form-picker-input.js +27 -7
  66. package/dist/collection/components/form-elements/form-structure.js +20 -1
  67. package/dist/collection/components/forms/defaults/mappers.js +6 -4
  68. package/dist/collection/components/forms/general-form.js +21 -2
  69. package/dist/collection/components/forms/general-inquiry.js +21 -2
  70. package/dist/collection/components/forms/service-booking.js +21 -2
  71. package/dist/collection/components/forms/ssc-lookup.js +21 -2
  72. package/dist/collection/components/forms/test-drive.js +21 -2
  73. package/dist/collection/components/forms/vehicle-quotation.js +21 -2
  74. package/dist/collection/components/part-lookup/dead-stock-lookup.css +1 -1
  75. package/dist/collection/components/part-lookup/distributor-lookup.css +1 -1
  76. package/dist/collection/components/part-lookup/manufacturer-lookup.css +1 -1
  77. package/dist/collection/components/part-lookup/manufacturer-part-lookup.css +1 -1
  78. package/dist/collection/components/part-lookup/part-lookup.css +1 -1
  79. package/dist/collection/components/vehicle-lookup/components/CoverageTimeline.js +2 -2
  80. package/dist/collection/components/vehicle-lookup/vehicle-accessories.css +1 -1
  81. package/dist/collection/components/vehicle-lookup/vehicle-accessories.js +21 -2
  82. package/dist/collection/components/vehicle-lookup/vehicle-claimable-items.css +1 -1
  83. package/dist/collection/components/vehicle-lookup/vehicle-claimable-items.js +24 -5
  84. package/dist/collection/components/vehicle-lookup/vehicle-item-claim-form.css +1 -1
  85. package/dist/collection/components/vehicle-lookup/vehicle-lookup.css +1 -1
  86. package/dist/collection/components/vehicle-lookup/vehicle-lookup.js +61 -12
  87. package/dist/collection/components/vehicle-lookup/vehicle-paint-thickness.css +1 -1
  88. package/dist/collection/components/vehicle-lookup/vehicle-paint-thickness.js +19 -0
  89. package/dist/collection/components/vehicle-lookup/vehicle-sale-information.css +1 -1
  90. package/dist/collection/components/vehicle-lookup/vehicle-sale-information.js +21 -2
  91. package/dist/collection/components/vehicle-lookup/vehicle-service-history.css +1 -1
  92. package/dist/collection/components/vehicle-lookup/vehicle-service-history.js +20 -1
  93. package/dist/collection/components/vehicle-lookup/vehicle-specification.css +1 -1
  94. package/dist/collection/components/vehicle-lookup/vehicle-specification.js +20 -1
  95. package/dist/collection/components/vehicle-lookup/vehicle-ssc.css +1 -1
  96. package/dist/collection/components/vehicle-lookup/vehicle-ssc.js +57 -12
  97. package/dist/collection/components/vehicle-lookup/vehicle-warranty-timeline.css +1 -1
  98. package/dist/collection/components/vehicle-lookup/vehicle-warranty-timeline.js +20 -1
  99. package/dist/collection/features/form-hook/functions.js +2 -1
  100. package/dist/collection/features/form-hook/render-structure.js +1 -1
  101. package/dist/collection/features/mocks/data/assets/accessories/accessory.svg +10 -0
  102. package/dist/collection/features/mocks/data/assets/accessories/floor-mats.svg +10 -0
  103. package/dist/collection/features/mocks/data/assets/accessories/mud-guards.svg +11 -0
  104. package/dist/collection/features/mocks/data/assets/accessories/roof-rack.svg +11 -0
  105. package/dist/collection/features/mocks/data/assets/accessories/roof-rails.svg +11 -0
  106. package/dist/collection/features/mocks/data/assets/accessories/side-steps.svg +11 -0
  107. package/dist/collection/features/mocks/data/assets/accessories/tow-bar.svg +12 -0
  108. package/dist/collection/features/mocks/data/assets/badges/badge-1.svg +5 -0
  109. package/dist/collection/features/mocks/data/assets/badges/badge-2.svg +5 -0
  110. package/dist/collection/features/mocks/data/assets/badges/badge-3.svg +5 -0
  111. package/dist/collection/features/mocks/data/assets/badges/badge-4.svg +5 -0
  112. package/dist/collection/features/mocks/data/assets/badges/badge-5.svg +5 -0
  113. package/dist/collection/features/mocks/data/assets/badges/badge-6.svg +5 -0
  114. package/dist/collection/features/mocks/data/assets/documents/service-invoice-qr.svg +18 -0
  115. package/dist/collection/features/mocks/data/assets/documents/signed-claim-document.svg +21 -0
  116. package/dist/collection/features/mocks/data/assets/paint-panels/door-front-left-detail.svg +7 -0
  117. package/dist/collection/features/mocks/data/assets/paint-panels/door-front-left.svg +6 -0
  118. package/dist/collection/features/mocks/data/assets/paint-panels/door-front-right-detail.svg +7 -0
  119. package/dist/collection/features/mocks/data/assets/paint-panels/door-front-right.svg +6 -0
  120. package/dist/collection/features/mocks/data/assets/paint-panels/door-rear-left-detail.svg +7 -0
  121. package/dist/collection/features/mocks/data/assets/paint-panels/door-rear-left.svg +6 -0
  122. package/dist/collection/features/mocks/data/assets/paint-panels/door-rear-right-detail.svg +7 -0
  123. package/dist/collection/features/mocks/data/assets/paint-panels/door-rear-right.svg +6 -0
  124. package/dist/collection/features/mocks/data/assets/paint-panels/fender-front-left-detail.svg +7 -0
  125. package/dist/collection/features/mocks/data/assets/paint-panels/fender-front-left.svg +6 -0
  126. package/dist/collection/features/mocks/data/assets/paint-panels/fender-front-right-detail.svg +7 -0
  127. package/dist/collection/features/mocks/data/assets/paint-panels/fender-front-right.svg +6 -0
  128. package/dist/collection/features/mocks/data/assets/paint-panels/fender-rear-left-detail.svg +7 -0
  129. package/dist/collection/features/mocks/data/assets/paint-panels/fender-rear-left.svg +6 -0
  130. package/dist/collection/features/mocks/data/assets/paint-panels/fender-rear-right-detail.svg +7 -0
  131. package/dist/collection/features/mocks/data/assets/paint-panels/fender-rear-right.svg +6 -0
  132. package/dist/collection/features/mocks/data/assets/paint-panels/hood-detail.svg +7 -0
  133. package/dist/collection/features/mocks/data/assets/paint-panels/hood.svg +6 -0
  134. package/dist/collection/features/mocks/data/assets/paint-panels/panel.svg +6 -0
  135. package/dist/collection/features/mocks/data/assets/paint-panels/roof-detail.svg +7 -0
  136. package/dist/collection/features/mocks/data/assets/paint-panels/roof.svg +6 -0
  137. package/dist/collection/features/mocks/data/assets/paint-panels/tail-gate-detail.svg +7 -0
  138. package/dist/collection/features/mocks/data/assets/paint-panels/tail-gate.svg +6 -0
  139. package/dist/collection/features/mocks/get-mock-files.js +19 -14
  140. package/dist/collection/features/mocks/index.js +1 -0
  141. package/dist/collection/features/mocks/mock-assets.js +33 -0
  142. package/dist/collection/features/vehicle-lookup-component/vehicle-lookup-api-integration.js +3 -1
  143. package/dist/collection/global/lib/clock.js +25 -0
  144. package/dist/collection/global/lib/decode-time-offset.js +9 -1
  145. package/dist/collection/templates/forms/docs-content/structure.js +1 -1
  146. package/dist/collection/templates/harness-connection.js +282 -0
  147. package/dist/collection/templates/harness-environment.js +31 -0
  148. package/dist/collection/templates/harness.js +610 -83
  149. package/dist/collection/templates/site-header.js +2 -2
  150. package/dist/components/branch-date-picker.js +1 -1
  151. package/dist/components/branch-slot-dropdown.js +1 -1
  152. package/dist/components/branch-slot-picker.js +1 -1
  153. package/dist/components/dead-stock-lookup.js +1 -1
  154. package/dist/components/distributor-lookup.js +1 -1
  155. package/dist/components/flexible-container.js +1 -1
  156. package/dist/components/form-checkbox.js +1 -1
  157. package/dist/components/form-date-picker.js +1 -1
  158. package/dist/components/form-file.js +1 -1
  159. package/dist/components/form-input-preview.js +1 -1
  160. package/dist/components/form-input.js +1 -1
  161. package/dist/components/form-phone-number.js +1 -1
  162. package/dist/components/form-picker-input.js +1 -1
  163. package/dist/components/form-select.js +1 -1
  164. package/dist/components/form-shadow-input.js +1 -1
  165. package/dist/components/form-stepper-control.js +1 -1
  166. package/dist/components/form-stepper-submit.js +1 -1
  167. package/dist/components/form-stepper.js +1 -1
  168. package/dist/components/form-structure-error.js +1 -1
  169. package/dist/components/form-structure.js +1 -1
  170. package/dist/components/form-submit.js +1 -1
  171. package/dist/components/form-switch.js +1 -1
  172. package/dist/components/form-text-area.js +1 -1
  173. package/dist/components/form-time-picker.js +1 -1
  174. package/dist/components/form-vin-input.js +1 -1
  175. package/dist/components/general-form.js +1 -1
  176. package/dist/components/general-inquiry-form.js +1 -1
  177. package/dist/components/index.js +1 -1
  178. package/dist/components/information-table.js +1 -1
  179. package/dist/components/manufacturer-lookup.js +1 -1
  180. package/dist/components/manufacturer-part-lookup.js +1 -1
  181. package/dist/components/{p-Ype8FNjy.js → p-3e83-if9.js} +1 -1
  182. package/dist/components/p-B3ztFt72.js +5 -0
  183. package/dist/components/p-B4PUnAtc.js +5 -0
  184. package/dist/components/{p-bVbnoz7C.js → p-B7OLLrcA.js} +1 -1
  185. package/dist/components/{p-d1WgLP9O.js → p-BEHSLtOA.js} +1 -1
  186. package/dist/components/{p-UFwS4oV2.js → p-B_EZX2Nj.js} +1 -1
  187. package/dist/components/{p-BAkLdLKZ.js → p-BcgJGb3U.js} +1 -1
  188. package/dist/components/p-BdxXAS3w.js +5 -0
  189. package/dist/components/{p-CebvOd8T.js → p-Bf7SK7BI.js} +1 -1
  190. package/dist/components/{p-BDOUh9fr.js → p-Bhy0nttE.js} +1 -1
  191. package/dist/components/p-BjDxP9Ar.js +5 -0
  192. package/dist/components/{p-DxRQkoRz.js → p-C-R7Zf1I.js} +1 -1
  193. package/dist/components/{p-CT-MbmyW.js → p-C1-ek7if.js} +1 -1
  194. package/dist/components/p-C3gFyogx.js +5 -0
  195. package/dist/components/p-CI0_r45y.js +5 -0
  196. package/dist/components/p-CJkthPWR.js +5 -0
  197. package/dist/components/{p-BxGye2qd.js → p-COKUmuuf.js} +1 -1
  198. package/dist/components/{p-D4aXUm2Y.js → p-CQ0f2MVb.js} +1 -1
  199. package/dist/components/{p-ByrX91PC.js → p-CYj0zRtR.js} +1 -1
  200. package/dist/components/{p-DOIpj5PH.js → p-C_91yYxZ.js} +1 -1
  201. package/dist/components/p-CbIjjt3n.js +5 -0
  202. package/dist/components/{p-BhekdMkM.js → p-CpX72zLP.js} +1 -1
  203. package/dist/components/{p-DkCqJ072.js → p-Cuy8EM9Y.js} +1 -1
  204. package/dist/components/p-CwYr8ZOV.js +5 -0
  205. package/dist/components/{p-BySGDlQi.js → p-Cy-0pkLa.js} +1 -1
  206. package/dist/components/p-DDWKS6lH.js +5 -0
  207. package/dist/components/p-DJr19tRD.js +5 -0
  208. package/dist/components/p-DNpppK6M.js +5 -0
  209. package/dist/components/{p-D_uU2Pi-.js → p-DRtn1_4P.js} +1 -1
  210. package/dist/components/{p-BwoBk7eI.js → p-D_2r73sx.js} +1 -1
  211. package/dist/components/p-De3WmHLm.js +5 -0
  212. package/dist/components/{p-BvM3siIc.js → p-DokN3NzH.js} +1 -1
  213. package/dist/components/{p-D4VK-Fv7.js → p-DqCIuURq.js} +1 -1
  214. package/dist/components/{p-DConDqrK.js → p-DuaTomIT.js} +1 -1
  215. package/dist/components/{p-DVkAlTjC.js → p-DzPqV0I6.js} +1 -1
  216. package/dist/components/p-GgvQ0B7I.js +5 -0
  217. package/dist/components/p-HVSqY6-d.js +5 -0
  218. package/dist/components/{p-DCO6piZ9.js → p-RKSY-Miv.js} +1 -1
  219. package/dist/components/p-Ty1rh9Up.js +5 -0
  220. package/dist/components/{p-AkWKeTFc.js → p-VTefgaxv.js} +1 -1
  221. package/dist/components/{p-DDTSLfBG.js → p-_8V0swcd.js} +1 -1
  222. package/dist/components/{p-B2Rwfoy0.js → p-ge2RA7lk.js} +1 -1
  223. package/dist/components/p-mZnLUhNr.js +5 -0
  224. package/dist/components/{p-CbRoCi89.js → p-mzWr8UUl.js} +1 -1
  225. package/dist/components/{p-D8w2fZLP.js → p-wX0fQQYY.js} +1 -1
  226. package/dist/components/{p-CETvww16.js → p-x1Tlf19a.js} +1 -1
  227. package/dist/components/p-z7jdJ402.js +5 -0
  228. package/dist/components/part-lookup.js +1 -1
  229. package/dist/components/service-booking-form.js +1 -1
  230. package/dist/components/shift-accordion.js +1 -1
  231. package/dist/components/shift-checkbox.js +1 -1
  232. package/dist/components/shift-portal.js +1 -1
  233. package/dist/components/shift-select-dropdown.js +1 -1
  234. package/dist/components/shift-select.js +1 -1
  235. package/dist/components/shift-slider.js +1 -1
  236. package/dist/components/shift-switch.js +1 -1
  237. package/dist/components/shift-tab-content.js +1 -1
  238. package/dist/components/shift-tabs.js +1 -1
  239. package/dist/components/ssc-lookup-form.js +1 -1
  240. package/dist/components/test-drive-form.js +1 -1
  241. package/dist/components/vehicle-accessories.js +1 -1
  242. package/dist/components/vehicle-claimable-items.js +1 -1
  243. package/dist/components/vehicle-item-claim-form.js +1 -1
  244. package/dist/components/vehicle-lookup.js +1 -1
  245. package/dist/components/vehicle-paint-thickness.js +1 -1
  246. package/dist/components/vehicle-quotation-form.js +1 -1
  247. package/dist/components/vehicle-sale-information.js +1 -1
  248. package/dist/components/vehicle-service-history.js +1 -1
  249. package/dist/components/vehicle-specification.js +1 -1
  250. package/dist/components/vehicle-ssc.js +1 -1
  251. package/dist/components/vehicle-warranty-timeline.js +1 -1
  252. package/dist/esm/{add-icon-DHh6DJXR.js → add-icon-D9Jel7-B.js} +1 -1
  253. package/dist/esm/{app-globals-CusTGK9D.js → app-globals-inc67m-q.js} +1 -1
  254. package/dist/esm/{arrow-up-icon-Dw_d-rG9.js → arrow-up-icon-Dfl2olS1.js} +1 -1
  255. package/dist/esm/branch-date-dropdown_17.entry.js +51 -45
  256. package/dist/esm/{calendar-days-icon-CnASOv7M.js → calendar-days-icon-zUqcb-fP.js} +1 -1
  257. package/dist/esm/{check-icon-BZQF4tzH.js → check-icon-CcYgNcDZ.js} +1 -1
  258. package/dist/esm/clock-DDWKS6lH.js +27 -0
  259. package/dist/esm/dead-stock-lookup_3.entry.js +10 -9
  260. package/dist/esm/{decode-time-offset-Cib1aJfV.js → decode-time-offset-CDd68_1V.js} +10 -2
  261. package/dist/esm/flexible-container.entry.js +2 -2
  262. package/dist/esm/form-checkbox.entry.js +3 -3
  263. package/dist/esm/form-date-picker.entry.js +25 -19
  264. package/dist/esm/{form-error-message-DWl6p6Dz.js → form-error-message-DxLEOczK.js} +1 -1
  265. package/dist/esm/{form-input-label-6blOw1BN.js → form-input-label-DEqq8CcC.js} +1 -1
  266. package/dist/esm/form-input_7.entry.js +13 -13
  267. package/dist/esm/form-switch.entry.js +3 -3
  268. package/dist/esm/form-time-picker.entry.js +4 -4
  269. package/dist/esm/{functions-SHZO3nZB.js → functions-M6Gsc8Mz.js} +5 -4
  270. package/dist/esm/general-form.entry.js +11 -10
  271. package/dist/esm/general-inquiry-form.entry.js +11 -10
  272. package/dist/esm/{get-language-from-url-fV-K15uy.js → get-language-from-url-Bq07dpsD.js} +1 -1
  273. package/dist/esm/{get-local-language-BLpgwBTx.js → get-local-language-D9DW53vG.js} +2 -2
  274. package/dist/esm/get-mock-files-DQV5ioOw.js +59 -0
  275. package/dist/esm/{index-CteZTPhq.js → index-CsEiWLCt.js} +3 -1
  276. package/dist/esm/information-table.entry.js +3 -3
  277. package/dist/esm/{loader-icon-BvAmA41s.js → loader-icon-CbaFiiB_.js} +1 -1
  278. package/dist/esm/loader.js +4 -4
  279. package/dist/esm/loading-spinner.entry.js +1 -1
  280. package/dist/esm/manufacturer-part-lookup.entry.js +4 -4
  281. package/dist/esm/{mappers-CZs2R7k6.js → mappers-DOWx0ySR.js} +10 -8
  282. package/dist/esm/{material-card-jbQd-0as.js → material-card-CB2xHWMN.js} +1 -1
  283. package/dist/esm/mock-assets-Bixlrdd4.js +11 -0
  284. package/dist/esm/part-lookup.entry.js +4 -4
  285. package/dist/esm/{qr-code-scan-B1nViqZl.js → qr-code-scan-FY8RR9ow.js} +1 -1
  286. package/dist/esm/{reveal-below-DWXRi5w3.js → reveal-below-8wAOxdZk.js} +1 -1
  287. package/dist/esm/service-booking-form.entry.js +11 -10
  288. package/dist/esm/shift-accordion.entry.js +1 -1
  289. package/dist/esm/shift-checkbox.entry.js +3 -3
  290. package/dist/esm/shift-components.js +4 -4
  291. package/dist/esm/shift-slider.entry.js +1 -1
  292. package/dist/esm/shift-switch.entry.js +2 -2
  293. package/dist/esm/shift-tab-content.entry.js +2 -2
  294. package/dist/esm/shift-tabs_2.entry.js +7 -7
  295. package/dist/esm/ssc-lookup-form.entry.js +11 -10
  296. package/dist/esm/test-drive-form.entry.js +11 -10
  297. package/dist/esm/{triangle-alert-BlnfPzWh.js → triangle-alert-DNqiuE_3.js} +1 -1
  298. package/dist/esm/vehicle-accessories_8.entry.js +73 -45
  299. package/dist/esm/{vehicle-info-layout-Dmx94GLp.js → vehicle-info-layout-I6iG9Fcg.js} +1 -1
  300. package/dist/esm/vehicle-lookup.entry.js +21 -17
  301. package/dist/esm/vehicle-quotation-form.entry.js +8 -7
  302. package/dist/esm/{white-loader-B3tJFuNO.js → white-loader-DusGamWD.js} +1 -1
  303. package/dist/integration-manifest.json +19 -9
  304. package/dist/mocks/assets/README.md +54 -0
  305. package/dist/mocks/assets/accessories/accessory.svg +10 -0
  306. package/dist/mocks/assets/accessories/floor-mats.svg +10 -0
  307. package/dist/mocks/assets/accessories/mud-guards.svg +11 -0
  308. package/dist/mocks/assets/accessories/roof-rack.svg +11 -0
  309. package/dist/mocks/assets/accessories/roof-rails.svg +11 -0
  310. package/dist/mocks/assets/accessories/side-steps.svg +11 -0
  311. package/dist/mocks/assets/accessories/tow-bar.svg +12 -0
  312. package/dist/mocks/assets/badges/badge-1.svg +5 -0
  313. package/dist/mocks/assets/badges/badge-2.svg +5 -0
  314. package/dist/mocks/assets/badges/badge-3.svg +5 -0
  315. package/dist/mocks/assets/badges/badge-4.svg +5 -0
  316. package/dist/mocks/assets/badges/badge-5.svg +5 -0
  317. package/dist/mocks/assets/badges/badge-6.svg +5 -0
  318. package/dist/mocks/assets/documents/service-invoice-qr.svg +18 -0
  319. package/dist/mocks/assets/documents/signed-claim-document.svg +21 -0
  320. package/dist/mocks/assets/paint-panels/door-front-left-detail.svg +7 -0
  321. package/dist/mocks/assets/paint-panels/door-front-left.svg +6 -0
  322. package/dist/mocks/assets/paint-panels/door-front-right-detail.svg +7 -0
  323. package/dist/mocks/assets/paint-panels/door-front-right.svg +6 -0
  324. package/dist/mocks/assets/paint-panels/door-rear-left-detail.svg +7 -0
  325. package/dist/mocks/assets/paint-panels/door-rear-left.svg +6 -0
  326. package/dist/mocks/assets/paint-panels/door-rear-right-detail.svg +7 -0
  327. package/dist/mocks/assets/paint-panels/door-rear-right.svg +6 -0
  328. package/dist/mocks/assets/paint-panels/fender-front-left-detail.svg +7 -0
  329. package/dist/mocks/assets/paint-panels/fender-front-left.svg +6 -0
  330. package/dist/mocks/assets/paint-panels/fender-front-right-detail.svg +7 -0
  331. package/dist/mocks/assets/paint-panels/fender-front-right.svg +6 -0
  332. package/dist/mocks/assets/paint-panels/fender-rear-left-detail.svg +7 -0
  333. package/dist/mocks/assets/paint-panels/fender-rear-left.svg +6 -0
  334. package/dist/mocks/assets/paint-panels/fender-rear-right-detail.svg +7 -0
  335. package/dist/mocks/assets/paint-panels/fender-rear-right.svg +6 -0
  336. package/dist/mocks/assets/paint-panels/hood-detail.svg +7 -0
  337. package/dist/mocks/assets/paint-panels/hood.svg +6 -0
  338. package/dist/mocks/assets/paint-panels/panel.svg +6 -0
  339. package/dist/mocks/assets/paint-panels/roof-detail.svg +7 -0
  340. package/dist/mocks/assets/paint-panels/roof.svg +6 -0
  341. package/dist/mocks/assets/paint-panels/tail-gate-detail.svg +7 -0
  342. package/dist/mocks/assets/paint-panels/tail-gate.svg +6 -0
  343. package/dist/shift-components/{p-c40bda31.entry.js → p-0395b900.entry.js} +1 -1
  344. package/dist/shift-components/{p-be19d7ff.entry.js → p-041ff6e5.entry.js} +1 -1
  345. package/dist/shift-components/p-131c79c9.entry.js +5 -0
  346. package/dist/shift-components/{p-3895d8e5.entry.js → p-287c5e75.entry.js} +1 -1
  347. package/dist/shift-components/{p-164eeced.entry.js → p-30be4db4.entry.js} +1 -1
  348. package/dist/shift-components/p-3c735b0a.entry.js +5 -0
  349. package/dist/shift-components/{p-90034cd8.entry.js → p-4c826390.entry.js} +1 -1
  350. package/dist/shift-components/p-6833930c.entry.js +5 -0
  351. package/dist/shift-components/{p-391c55af.entry.js → p-7760cb43.entry.js} +1 -1
  352. package/dist/shift-components/p-81a7b7dd.entry.js +5 -0
  353. package/dist/shift-components/p-82adf890.entry.js +5 -0
  354. package/dist/shift-components/{p-3790e469.entry.js → p-91d15921.entry.js} +1 -1
  355. package/dist/shift-components/{p-005d0a3a.entry.js → p-9f017e84.entry.js} +1 -1
  356. package/dist/shift-components/p-B2ivJ0St.js +5 -0
  357. package/dist/shift-components/{p-Tjvj6pk4.js → p-BHLT4-AT.js} +1 -1
  358. package/dist/shift-components/p-BKobZCvH.js +5 -0
  359. package/dist/shift-components/{p-MzYTUgsB.js → p-BTJYrubY.js} +1 -1
  360. package/dist/shift-components/p-BTfrQeMt.js +5 -0
  361. package/dist/shift-components/{p-BkVfhjgg.js → p-Bxxrkd94.js} +1 -1
  362. package/dist/shift-components/{p-C8PvXL-3.js → p-C7KIr58m.js} +1 -1
  363. package/dist/shift-components/{p-Ccwtsrgg.js → p-CHQ9vjK9.js} +1 -1
  364. package/dist/shift-components/{p-DzV_ls5o.js → p-CTvdWiVG.js} +1 -1
  365. package/dist/shift-components/p-CTy3HfBE.js +5 -0
  366. package/dist/shift-components/{p-oQAUbfw7.js → p-Cet0ur1z.js} +1 -1
  367. package/dist/shift-components/{p-OlAbjfKx.js → p-Cn5_F5mW.js} +1 -1
  368. package/dist/shift-components/p-CsEiWLCt.js +6 -0
  369. package/dist/shift-components/{p-oFzLYYzz.js → p-D5QsRU5_.js} +1 -1
  370. package/dist/shift-components/p-DBog8aSI.js +5 -0
  371. package/dist/shift-components/p-DDWKS6lH.js +5 -0
  372. package/dist/shift-components/{p-DtD7FZCG.js → p-DZGcyLWV.js} +1 -1
  373. package/dist/shift-components/p-Dh2tKZeJ.js +5 -0
  374. package/dist/shift-components/{p-BTNA9XsD.js → p-DxRW_Tfa.js} +1 -1
  375. package/dist/shift-components/{p-D7cqTTll.js → p-M1S8gM54.js} +1 -1
  376. package/dist/shift-components/p-a863f984.entry.js +5 -0
  377. package/dist/shift-components/p-a8c58abe.entry.js +5 -0
  378. package/dist/shift-components/p-b6dfb75b.entry.js +5 -0
  379. package/dist/shift-components/{p-d47428d1.entry.js → p-bcfc5126.entry.js} +1 -1
  380. package/dist/shift-components/{p-879faeec.entry.js → p-c20b97c6.entry.js} +1 -1
  381. package/dist/shift-components/p-c7e694c0.entry.js +5 -0
  382. package/dist/shift-components/p-ceeebc95.entry.js +5 -0
  383. package/dist/shift-components/p-d118078b.entry.js +5 -0
  384. package/dist/shift-components/{p-af71f000.entry.js → p-d89b19fa.entry.js} +1 -1
  385. package/dist/shift-components/p-dc408027.entry.js +5 -0
  386. package/dist/shift-components/p-e3c2fb00.entry.js +5 -0
  387. package/dist/shift-components/{p-f638970e.entry.js → p-e3f9e20f.entry.js} +1 -1
  388. package/dist/shift-components/p-f6ca88a5.entry.js +5 -0
  389. package/dist/shift-components/{p-bfwabdVa.js → p-h1L6xTqG.js} +1 -1
  390. package/dist/shift-components/p-iSEtUbmS.js +5 -0
  391. package/dist/shift-components/p-xtI4pqVk.js +5 -0
  392. package/dist/shift-components/shift-components.esm.js +1 -1
  393. package/dist/stencil-docs.json +501 -1
  394. package/dist/types/components/form-elements/branch-date-picker.d.ts +2 -0
  395. package/dist/types/components/form-elements/branch-slot-picker.d.ts +2 -0
  396. package/dist/types/components/form-elements/form-date-picker-legacy.d.ts +4 -0
  397. package/dist/types/components/form-elements/form-picker-input.d.ts +2 -0
  398. package/dist/types/components/form-elements/form-structure.d.ts +1 -0
  399. package/dist/types/components/forms/general-form.d.ts +2 -0
  400. package/dist/types/components/forms/general-inquiry.d.ts +2 -0
  401. package/dist/types/components/forms/service-booking.d.ts +2 -0
  402. package/dist/types/components/forms/ssc-lookup.d.ts +2 -0
  403. package/dist/types/components/forms/test-drive.d.ts +2 -0
  404. package/dist/types/components/forms/vehicle-quotation.d.ts +2 -0
  405. package/dist/types/components/vehicle-lookup/vehicle-accessories.d.ts +1 -0
  406. package/dist/types/components/vehicle-lookup/vehicle-claimable-items.d.ts +1 -0
  407. package/dist/types/components/vehicle-lookup/vehicle-lookup.d.ts +5 -1
  408. package/dist/types/components/vehicle-lookup/vehicle-paint-thickness.d.ts +1 -0
  409. package/dist/types/components/vehicle-lookup/vehicle-sale-information.d.ts +1 -0
  410. package/dist/types/components/vehicle-lookup/vehicle-service-history.d.ts +1 -0
  411. package/dist/types/components/vehicle-lookup/vehicle-specification.d.ts +1 -0
  412. package/dist/types/components/vehicle-lookup/vehicle-ssc.d.ts +14 -1
  413. package/dist/types/components/vehicle-lookup/vehicle-warranty-timeline.d.ts +2 -0
  414. package/dist/types/components.d.ts +136 -0
  415. package/dist/types/features/form-hook/interface.d.ts +1 -0
  416. package/dist/types/features/mocks/index.d.ts +1 -0
  417. package/dist/types/features/mocks/mock-assets.d.ts +1 -0
  418. package/dist/types/features/vehicle-lookup-component/interface.d.ts +1 -0
  419. package/dist/types/global/lib/clock.d.ts +11 -0
  420. package/dist/types/global/lib/decode-time-offset.d.ts +2 -1
  421. package/package.json +2 -1
  422. package/dist/cjs/get-mock-files-NMHm1Vgn.js +0 -53
  423. package/dist/collection/global/api/urls.js +0 -6
  424. package/dist/collection/templates/vehicle-lookup/mock-data.js +0 -4922
  425. package/dist/collection/templates/vehicle-lookup/mock-loader.js +0 -26
  426. package/dist/collection/templates/vehicle-lookup/warranty-mock-data.js +0 -3211
  427. package/dist/components/p-B4nj03om.js +0 -5
  428. package/dist/components/p-BDCBfHZQ.js +0 -5
  429. package/dist/components/p-BGf2deKN.js +0 -5
  430. package/dist/components/p-BNmsN10T.js +0 -5
  431. package/dist/components/p-BOaqSzZZ.js +0 -5
  432. package/dist/components/p-BpZlI-gl.js +0 -5
  433. package/dist/components/p-C9uBduBY.js +0 -5
  434. package/dist/components/p-CUO3vXQr.js +0 -5
  435. package/dist/components/p-DJDhhX5A.js +0 -5
  436. package/dist/components/p-DKWqzBVM.js +0 -5
  437. package/dist/components/p-DfVskL_i.js +0 -5
  438. package/dist/components/p-DpuHkgw0.js +0 -5
  439. package/dist/components/p-Imck426q.js +0 -5
  440. package/dist/components/p-XFwQkeZH.js +0 -5
  441. package/dist/components/p-a4ooD0o_.js +0 -5
  442. package/dist/components/p-d7wdNFwt.js +0 -5
  443. package/dist/components/p-vlA3PJQv.js +0 -5
  444. package/dist/esm/get-mock-files-Dpb0udoz.js +0 -51
  445. package/dist/mocks/generated/broker-dealer/paint-thickness-certificate.json +0 -1
  446. package/dist/mocks/generated/broker-dealer/part-lookup.json +0 -155
  447. package/dist/mocks/generated/broker-dealer/vehicle-lookup.json +0 -794
  448. package/dist/mocks/generated/edge-cases/paint-thickness-certificate.json +0 -1
  449. package/dist/mocks/generated/edge-cases/part-lookup.json +0 -1
  450. package/dist/mocks/generated/edge-cases/vehicle-lookup.json +0 -124
  451. package/dist/mocks/generated/standard-dealer/paint-thickness-certificate.json +0 -146
  452. package/dist/mocks/generated/standard-dealer/part-lookup.json +0 -157
  453. package/dist/mocks/generated/standard-dealer/vehicle-lookup.json +0 -809
  454. package/dist/shift-components/p-0965eb41.entry.js +0 -5
  455. package/dist/shift-components/p-219e589c.entry.js +0 -5
  456. package/dist/shift-components/p-3324bdf6.entry.js +0 -5
  457. package/dist/shift-components/p-3ba3f543.entry.js +0 -5
  458. package/dist/shift-components/p-48Fp2I0C.js +0 -5
  459. package/dist/shift-components/p-5c28fb6d.entry.js +0 -5
  460. package/dist/shift-components/p-60232b66.entry.js +0 -5
  461. package/dist/shift-components/p-82bb8960.entry.js +0 -5
  462. package/dist/shift-components/p-93ddc847.entry.js +0 -5
  463. package/dist/shift-components/p-B9INqdFT.js +0 -5
  464. package/dist/shift-components/p-C4NpWwNE.js +0 -5
  465. package/dist/shift-components/p-C_QDvFTV.js +0 -5
  466. package/dist/shift-components/p-CteZTPhq.js +0 -6
  467. package/dist/shift-components/p-DIe_G0iC.js +0 -5
  468. package/dist/shift-components/p-DWoutohJ.js +0 -5
  469. package/dist/shift-components/p-a4f1502c.entry.js +0 -5
  470. package/dist/shift-components/p-bd8dbd1d.entry.js +0 -5
  471. package/dist/shift-components/p-d8844424.entry.js +0 -5
  472. package/dist/shift-components/p-e7986265.entry.js +0 -5
  473. package/dist/shift-components/p-f93d79bc.entry.js +0 -5
  474. package/dist/shift-components/p-fa296247.entry.js +0 -5
  475. package/dist/shift-components/p-pkYWYULB.js +0 -5
  476. package/dist/types/global/api/urls.d.ts +0 -2
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Provides the `harness()` Alpine data factory and stamps the shared chrome into
5
5
  * the placeholder elements below, so a page describes what it is testing instead
6
- * of re-implementing navigation and the same six controls:
6
+ * of re-implementing navigation and the shared controls:
7
7
  *
8
8
  * <div data-harness-nav x-cloak></div>
9
9
  *
@@ -21,11 +21,11 @@
21
21
  *
22
22
  * …and the controls, split by how often you touch them:
23
23
  *
24
- * [data-harness-bar] Mode and Fixtures, inline above the component. Reached
25
- * in one click, because that is every interaction.
26
- * [data-harness-rail] Language, theme, platform width and the event log, in a
27
- * drawer behind a tab on the right edge (a button on a
28
- * phone). Set once, then forgotten.
24
+ * [data-harness-bar] Fixtures, inline above the component. Reached in one
25
+ * click, because that is every interaction.
26
+ * [data-harness-rail] Connection, Environment, Today, language, theme,
27
+ * platform width and the event log, in a drawer behind a
28
+ * tab on the right edge (a button on a phone).
29
29
  *
30
30
  * The rail used to be a 300px aside, which cost the component under test a third
31
31
  * of the page on every demo. Its stamped markup is all `position: fixed`, so the
@@ -46,6 +46,23 @@
46
46
  * .shift/repos/adp/web-components/templates-design-language.md.
47
47
  */
48
48
 
49
+ import { chooseEnvironment, environmentFileUrl, environmentLabel, environmentsFrom } from './harness-environment.js';
50
+ import {
51
+ captureConnectionState,
52
+ clearConnection,
53
+ connectProfile,
54
+ connectionFields,
55
+ connectionLog,
56
+ connectionOptions,
57
+ connectionStorageKey,
58
+ disconnectProfile,
59
+ emptyConnectionDraft,
60
+ loadConnection,
61
+ saveConnection,
62
+ todayChoiceAvailable,
63
+ validateConnection,
64
+ } from './harness-connection.js';
65
+
49
66
  /*
50
67
  * Every path this file emits or compares is resolved against the directory this
51
68
  * module was loaded from, never against the server root. Root-absolute paths
@@ -58,6 +75,41 @@
58
75
  * tags; the fix has to be here, where `import.meta.url` knows the real depth.
59
76
  */
60
77
  const SITE_ROOT = new URL('../', import.meta.url);
78
+ const ENVIRONMENT_STORAGE_KEY = 'adp-harness-environment';
79
+ const ENVIRONMENT_SETTLE_MS = 320;
80
+ const TODAY_STORAGE_KEY = 'adp-harness-today';
81
+ const TODAY_MODE_STORAGE_KEY = 'adp-harness-today-mode';
82
+ const ISO_CALENDAR_DATE = /^\d{4}-\d{2}-\d{2}$/;
83
+
84
+ const wallToday = () => new Date().toISOString().slice(0, 10);
85
+
86
+ const isCalendarDate = value => {
87
+ if (!ISO_CALENDAR_DATE.test(value || '')) return false;
88
+
89
+ const resolved = new Date(`${value}T00:00:00.000Z`);
90
+ return !Number.isNaN(resolved.getTime()) && resolved.toISOString().slice(0, 10) === value;
91
+ };
92
+
93
+ const readStoredToday = () => {
94
+ try {
95
+ return {
96
+ mode: localStorage.getItem(TODAY_MODE_STORAGE_KEY),
97
+ value: localStorage.getItem(TODAY_STORAGE_KEY),
98
+ };
99
+ } catch {
100
+ return { mode: null, value: null };
101
+ }
102
+ };
103
+
104
+ const readStoredEnvironment = () => {
105
+ try {
106
+ return sessionStorage.getItem(ENVIRONMENT_STORAGE_KEY);
107
+ } catch {
108
+ return null;
109
+ }
110
+ };
111
+
112
+ const afterEnvironmentSettles = () => new Promise(resolve => setTimeout(resolve, ENVIRONMENT_SETTLE_MS));
61
113
 
62
114
  /** A catalog path (`/templates/…`) as a URL path valid from wherever this page sits. */
63
115
  const site = value => new URL(String(value).replace(/^\//, ''), SITE_ROOT).pathname;
@@ -81,7 +133,7 @@ function fail(message) {
81
133
  }
82
134
 
83
135
  /**
84
- * The four component shapes these demos drive. A profile is only a bundle of
136
+ * The five component shapes these demos drive. A profile is only a bundle of
85
137
  * defaults — every field it sets can still be overridden per page.
86
138
  *
87
139
  * `events` maps a component callback property to a log line. Handlers run with
@@ -90,10 +142,10 @@ function fail(message) {
90
142
  */
91
143
  const PROFILES = {
92
144
  // vehicle-lookup family: a VIN goes in, one response comes back.
93
- lookup: {
145
+ 'lookup': {
94
146
  apply: (subject, data) => subject.setMockData(data),
95
147
  select: (subject, key) => subject.fetchVin(key),
96
- endpoint: subject => subject.baseUrl,
148
+ clear: subject => (subject.clearData ? subject.clearData() : subject.fetchVin({ vin: '' })),
97
149
  events: {
98
150
  loadingStateChange: track,
99
151
  errorCallback: fail,
@@ -101,12 +153,17 @@ const PROFILES = {
101
153
  },
102
154
  },
103
155
 
104
- // part-lookup family: a part number goes in, and the component loads its own
105
- // mock file when `isDev` flips rather than being handed one.
106
- part: {
107
- apply: null,
108
- select: (subject, key) => subject.fetchData(key),
109
- endpoint: subject => subject.endpoint?.url,
156
+ // part-lookup family: the harness owns the generated file, while the single
157
+ // panels still receive it through their public mock API. The wrapper has no
158
+ // setMockData method, so a selected DTO is distributed to all three tabs.
159
+ 'part': {
160
+ apply: (subject, data) => subject.setMockData?.(data),
161
+ select: (subject, key, harness) => {
162
+ const mock = !harness.connected && harness.data?.[key];
163
+
164
+ return mock && subject.localName === 'part-lookup' ? subject.handleLoadData(mock, null) : subject.fetchData(key);
165
+ },
166
+ clear: subject => (subject.localName === 'part-lookup' ? subject.handleLoadData({ partNumber: '' }, null) : subject.fetchData('')),
110
167
  events: {
111
168
  loadingStateChange: track,
112
169
  errorCallback: fail,
@@ -123,10 +180,9 @@ const PROFILES = {
123
180
 
124
181
  // forms: no fixture list — the structure JSON is the input, and what matters
125
182
  // is the submit lifecycle.
126
- form: {
183
+ 'form': {
127
184
  apply: null,
128
185
  select: null,
129
- endpoint: subject => subject.structureUrl || subject.getAttribute('structure-url'),
130
186
  events: {
131
187
  formReadyCallback() {
132
188
  this.ready = true;
@@ -149,19 +205,19 @@ const PROFILES = {
149
205
 
150
206
  // vehicle-lookup: the shell that holds the eight single-vehicle components and
151
207
  // shows one at a time. It reports its own state rather than the children's.
152
- composite: {
153
- apply: null,
154
- endpoint: subject => subject.baseUrl,
208
+ 'composite': {
209
+ apply: (subject, data) => subject.setMockData(data),
210
+ clear: subject => subject.handleLoadData({ vin: '' }, null),
155
211
  // In development, push the fixture into every tab at once instead of only the
156
212
  // active one, which is what `fetchVin` does — otherwise switching tabs shows
157
213
  // whatever the previous search left behind. Empty and Error have no fixture,
158
214
  // so they go the normal route and let each child render its own state.
159
215
  select: (subject, key, harness) => {
160
- const mock = harness.mode === 'dev' && harness.data?.[key];
216
+ const mock = !harness.connected && harness.data?.[key];
161
217
 
162
218
  // A page that sets ssc-query-string is showing the SSC tab's own lookup and what the other
163
219
  // tabs' searches leave it with, so its searches go through fetchVin the way a host's do.
164
- return mock && !subject.sscQueryString ? subject.handleLoadData(mock, null) : subject.fetchVin(key, { headers: 'headers-value' });
220
+ return mock && !subject.sscQueryString ? subject.handleLoadData(mock, null) : subject.fetchVin(key);
165
221
  },
166
222
  events: {
167
223
  loadingStateChanged: track,
@@ -169,6 +225,13 @@ const PROFILES = {
169
225
  dynamicClaimActivate: information => ['claim activate', information?.vin ?? '(no vin)'],
170
226
  },
171
227
  },
228
+
229
+ // Gallery page: one Connection value is applied to every OCR-enabled case.
230
+ 'vin-extractor': {
231
+ apply: null,
232
+ select: null,
233
+ events: {},
234
+ },
172
235
  };
173
236
 
174
237
  const DEFAULTS = {
@@ -176,9 +239,6 @@ const DEFAULTS = {
176
239
  profile: 'lookup',
177
240
  mocks: null,
178
241
  labels: {},
179
- // Fixture keys to offer in production mode, where there is no mock file to
180
- // generate them from. Development mode always uses the generated list (R7).
181
- samples: [],
182
242
  // Fixture to run as soon as the subject is ready, so a page can deep-link a
183
243
  // scenario (`?vin=…`) instead of asking whoever opened the link to click.
184
244
  start: null,
@@ -196,8 +256,10 @@ const DEFAULTS = {
196
256
  { label: 'Full', value: '100%' },
197
257
  ],
198
258
  language: true,
259
+ environment: true,
260
+ today: true,
199
261
  theme: true,
200
- mode: true,
262
+ connection: {},
201
263
  platform: true,
202
264
  log: true,
203
265
  fixtures: null,
@@ -208,7 +270,7 @@ const DEFAULTS = {
208
270
  // `undefined` means "take the profile's"; `null` means "this page has none".
209
271
  apply: undefined,
210
272
  select: undefined,
211
- endpoint: undefined,
273
+ clear: undefined,
212
274
  };
213
275
 
214
276
  window.harness = function harness(options = {}) {
@@ -217,7 +279,11 @@ window.harness = function harness(options = {}) {
217
279
 
218
280
  const apply = config.apply === undefined ? profile.apply : config.apply;
219
281
  const select = config.select === undefined ? profile.select : config.select;
220
- const endpoint = config.endpoint === undefined ? profile.endpoint : config.endpoint;
282
+ const clear = config.clear === undefined ? profile.clear : config.clear;
283
+ const profileName = PROFILES[config.profile] ? config.profile : 'lookup';
284
+ const configuredConnection = connectionOptions(profileName, config.connection);
285
+ const fields = connectionFields(profileName, configuredConnection);
286
+ const storageKey = connectionStorageKey(profileName, window.location.pathname);
221
287
  // `false` wires nothing: a page demonstrating the Blazor bridge needs those
222
288
  // same callback props left as the attribute strings the component dispatches
223
289
  // through the .NET ref, and it feeds the log with `harness:log` instead.
@@ -231,22 +297,38 @@ window.harness = function harness(options = {}) {
231
297
  platforms: config.platforms,
232
298
  show: {
233
299
  language: config.language,
300
+ environment: config.environment && Boolean(config.mocks),
301
+ today: config.today && config.subject !== false,
234
302
  theme: config.theme,
235
- mode: config.mode,
303
+ connection: config.connection !== false && config.subject !== false,
236
304
  platform: config.platform,
237
305
  log: config.log,
238
- fixtures: (config.fixtures ?? Boolean(config.mocks || config.samples.length)) && Boolean(select),
306
+ fixtures: (config.fixtures ?? Boolean(config.mocks)) && Boolean(select),
239
307
  // The bar only earns its space when it has something in it.
240
308
  get bar() {
241
- return this.mode || this.fixtures;
309
+ return this.fixtures;
242
310
  },
243
311
  },
244
312
 
245
313
  language: config.languages[0],
314
+ environments: [],
315
+ environment: '',
316
+ environmentChanging: false,
317
+ environmentError: '',
318
+ environmentRequest: 0,
319
+ today: '',
320
+ todayMode: 'live',
321
+ todayAnchor: '',
322
+ customToday: wallToday(),
323
+ generatedTodayChoice: null,
246
324
  theme: window.harnessTheme?.current() ?? 'system',
247
- // Development is the default: it is the only mode that works without a local
248
- // API running, which is how these pages are opened most of the time.
249
- mode: 'dev',
325
+ connected: false,
326
+ connectionBusy: false,
327
+ connectionError: '',
328
+ connectionErrors: {},
329
+ connectionFields: fields,
330
+ connectionDraft: emptyConnectionDraft(profileName, configuredConnection),
331
+ connectionBaseline: null,
250
332
  // The rail is a drawer, not a column: it would otherwise take a third of the
251
333
  // width away from the thing under test on every page.
252
334
  open: false,
@@ -261,6 +343,7 @@ window.harness = function harness(options = {}) {
261
343
  // How many fixtures `has` filtered out. Shown, never silently dropped.
262
344
  hidden: 0,
263
345
  fixture: null,
346
+ generatedFixture: null,
264
347
  // Forms flip this from formReadyCallback; an external submit button waits on it.
265
348
  ready: false,
266
349
  lines: [],
@@ -269,17 +352,39 @@ window.harness = function harness(options = {}) {
269
352
  return config.rtl.includes(this.language) ? 'rtl' : 'ltr';
270
353
  },
271
354
 
355
+ get todayLabel() {
356
+ return this.todayMode === 'live' ? wallToday() : this.today;
357
+ },
358
+
359
+ get environmentLabel() {
360
+ return environmentLabel(this.environment);
361
+ },
362
+
363
+ get connectionLabel() {
364
+ return this.connected ? 'Live' : 'Generated';
365
+ },
366
+
367
+ get liveTodayExplanation() {
368
+ return 'Live API responses use the server’s current time. Disconnect to use the generated Anchor or Custom choices.';
369
+ },
370
+
272
371
  /** Empty and Error are always offered; the rest is generated, never listed (R7). */
273
372
  get fixtures() {
274
- const keys = this.mode === 'dev' ? this.keys : config.samples;
275
-
276
- return [{ value: '', label: 'Empty' }, { value: 'error', label: 'Error' }, ...keys.map(key => ({ value: key, label: key, note: config.labels[key] }))];
373
+ return [{ value: '', label: 'Empty' }, { value: 'error', label: 'Error' }, ...this.keys.map(key => ({ value: key, label: key, note: config.labels[key] }))];
277
374
  },
278
375
 
279
376
  async init() {
280
377
  // Any script on the page can write to the log without an Alpine scope.
281
378
  window.addEventListener('harness:log', event => this.write(event.detail?.label, event.detail?.detail));
282
379
 
380
+ // `source` was the temporary bridge to generated fixtures. The picker is
381
+ // now the only source selector, so retire the parameter wherever it appears.
382
+ const initialUrl = new URL(window.location.href);
383
+ if (initialUrl.searchParams.has('source')) {
384
+ initialUrl.searchParams.delete('source');
385
+ window.history.replaceState({}, '', initialUrl);
386
+ }
387
+
283
388
  // A gallery page drives several elements itself and has no single subject.
284
389
  if (config.subject === false) return;
285
390
 
@@ -294,15 +399,22 @@ window.harness = function harness(options = {}) {
294
399
 
295
400
  await config.setup?.(this.subject, this);
296
401
 
402
+ if (typeof configuredConnection.targets === 'string') configuredConnection.targets = [...document.querySelectorAll(configuredConnection.targets)];
403
+
404
+ this.connectionBaseline = captureConnectionState(profileName, this.subject, configuredConnection);
405
+
297
406
  if (config.language) this.setLanguage(this.subject.language || this.language);
407
+ if (config.mocks) await this.initEnvironment();
408
+ if (config.today) await this.initToday();
298
409
 
299
410
  // Before isDev, not after: flipping it makes some components load their own
300
411
  // mock file and call straight back, so the listeners have to be in place.
301
412
  this.observe();
302
- this.subject.isDev = true;
413
+ if (profileName !== 'vin-extractor') this.subject.isDev = true;
303
414
 
304
- if (config.mocks) await this.loadFixtures();
305
- if (config.start) this.run(config.start);
415
+ await this.restoreConnection();
416
+
417
+ if (!this.connected && config.start && this.keys.includes(config.start)) this.run(config.start);
306
418
  },
307
419
 
308
420
  /* ---------- controls ---------- */
@@ -324,6 +436,321 @@ window.harness = function harness(options = {}) {
324
436
  this.write('language', `${language} · ${this.dir}`);
325
437
  },
326
438
 
439
+ async initEnvironment() {
440
+ try {
441
+ const response = await fetch(new URL('mocks/generated/index.json', SITE_ROOT));
442
+ if (!response.ok) throw new Error(`${response.status}`);
443
+
444
+ this.environments = environmentsFrom(await response.json());
445
+ if (!this.environments.length) throw new Error('the generated index contains no environments');
446
+
447
+ const url = new URL(window.location.href);
448
+ const selected = chooseEnvironment(this.environments, url.searchParams.get('env'), readStoredEnvironment());
449
+
450
+ await this.loadEnvironment(selected.name, true);
451
+ } catch (error) {
452
+ this.environmentError = `Failed to load generated environments — ${error.message}`;
453
+ this.write('environment', this.environmentError);
454
+ }
455
+ },
456
+
457
+ async setEnvironment(name) {
458
+ if (this.connected || !name || name === this.environment || this.environmentChanging) return;
459
+
460
+ await this.loadEnvironment(name, false);
461
+ },
462
+
463
+ async loadEnvironment(name, initial) {
464
+ const selected = this.environments.find(environment => environment.name === name);
465
+ if (!selected) return;
466
+
467
+ const request = ++this.environmentRequest;
468
+ const mockUrl = environmentFileUrl(SITE_ROOT, selected.name, config.mocks);
469
+
470
+ this.environment = selected.name;
471
+ this.environmentError = '';
472
+ this.fixture = '';
473
+ this.data = null;
474
+ this.keys = [];
475
+ this.hidden = 0;
476
+ this.todayAnchor = isCalendarDate(selected.anchor) ? selected.anchor : '';
477
+ this.subject.mockUrl = mockUrl.href;
478
+
479
+ const url = new URL(window.location.href);
480
+ url.searchParams.delete('source');
481
+ url.searchParams.set('env', selected.name);
482
+ if (!initial) {
483
+ url.searchParams.delete('vin');
484
+ url.searchParams.delete('claim');
485
+ }
486
+ window.history.replaceState({}, '', url);
487
+
488
+ try {
489
+ sessionStorage.setItem(ENVIRONMENT_STORAGE_KEY, selected.name);
490
+ } catch {
491
+ // Storage can be unavailable in privacy modes; the URL remains authoritative.
492
+ }
493
+
494
+ // Anchor follows the environment; Live and Custom deliberately do not.
495
+ if (!initial && this.todayMode === 'anchor') this.setToday('anchor');
496
+
497
+ if (!initial) {
498
+ this.environmentChanging = true;
499
+ this.setEnvironmentTransition(true);
500
+ }
501
+
502
+ try {
503
+ if (!initial) {
504
+ await afterEnvironmentSettles();
505
+ await clear?.(this.subject, this);
506
+ }
507
+
508
+ const response = await fetch(mockUrl);
509
+ if (!response.ok) throw new Error(`${response.status}`);
510
+
511
+ const data = (await response.json()) ?? {};
512
+ if (request !== this.environmentRequest) return;
513
+
514
+ this.data = data;
515
+ await apply?.(this.subject, data);
516
+ this.setFixtures(Object.keys(data), data);
517
+ this.write('environment', `${selected.name} · ${this.keys.length} shown`);
518
+ } catch (error) {
519
+ if (request !== this.environmentRequest) return;
520
+
521
+ this.data = {};
522
+ this.setFixtures([], {});
523
+ this.environmentError = `Failed to load ${selected.name} — ${error.message}`;
524
+ this.write('environment', this.environmentError);
525
+ } finally {
526
+ if (!initial && request === this.environmentRequest) {
527
+ this.environmentChanging = false;
528
+ this.setEnvironmentTransition(false);
529
+ }
530
+ }
531
+ },
532
+
533
+ setEnvironmentTransition(changing) {
534
+ this.subject
535
+ .closest('section')
536
+ ?.querySelectorAll('.frame')
537
+ .forEach(frame => frame.setAttribute('data-environment-changing', String(changing)));
538
+ },
539
+
540
+ async initToday() {
541
+ const url = new URL(window.location.href);
542
+
543
+ const requested = url.searchParams.get('today');
544
+
545
+ if (isCalendarDate(requested)) {
546
+ this.customToday = requested;
547
+ this.setToday(requested === this.todayAnchor ? 'anchor' : 'custom', requested);
548
+ return;
549
+ }
550
+
551
+ const stored = readStoredToday();
552
+
553
+ if (stored.mode === 'live') {
554
+ this.setToday('live');
555
+ return;
556
+ }
557
+
558
+ if (stored.mode === 'custom' && isCalendarDate(stored.value)) {
559
+ this.customToday = stored.value;
560
+ this.setToday('custom', stored.value);
561
+ return;
562
+ }
563
+
564
+ if (this.todayAnchor) {
565
+ this.setToday('anchor');
566
+ return;
567
+ }
568
+
569
+ this.setToday('live', this.customToday, false);
570
+ },
571
+
572
+ setToday(mode, customDate = this.customToday, persist = true) {
573
+ if (this.connected && mode !== 'live') return;
574
+ if (mode === 'anchor' && !this.todayAnchor) return;
575
+ if (mode === 'custom' && !isCalendarDate(customDate)) return;
576
+
577
+ const value = mode === 'anchor' ? this.todayAnchor : mode === 'custom' ? customDate : '';
578
+
579
+ this.todayMode = mode;
580
+ this.today = value;
581
+ if (mode === 'custom') this.customToday = customDate;
582
+ this.subject.today = value;
583
+
584
+ const url = new URL(window.location.href);
585
+ if (value) url.searchParams.set('today', value);
586
+ else url.searchParams.delete('today');
587
+ window.history.replaceState({}, '', url);
588
+
589
+ if (persist) {
590
+ try {
591
+ localStorage.setItem(TODAY_MODE_STORAGE_KEY, mode);
592
+ if (value) localStorage.setItem(TODAY_STORAGE_KEY, value);
593
+ else localStorage.removeItem(TODAY_STORAGE_KEY);
594
+ } catch {
595
+ // Storage can be unavailable in privacy modes; the prop and URL still work.
596
+ }
597
+ }
598
+
599
+ this.write('today', mode === 'live' ? `${wallToday()} · live` : `${value} · ${mode}`);
600
+ },
601
+
602
+ canUseToday(mode) {
603
+ return todayChoiceAvailable(mode, this.connected, Boolean(this.todayAnchor));
604
+ },
605
+
606
+ clearConnectionError(name) {
607
+ if (!this.connectionErrors[name] && !this.connectionError) return;
608
+
609
+ this.connectionErrors = { ...this.connectionErrors, [name]: '' };
610
+ this.connectionError = '';
611
+ },
612
+
613
+ async restoreConnection() {
614
+ let settings;
615
+
616
+ try {
617
+ settings = loadConnection(sessionStorage, storageKey, profileName, configuredConnection);
618
+ } catch {
619
+ settings = null;
620
+ }
621
+
622
+ if (!settings) return;
623
+
624
+ this.generatedTodayChoice = { mode: this.todayMode, customDate: this.customToday };
625
+ this.generatedFixture = this.fixture;
626
+ this.connectionBusy = true;
627
+
628
+ try {
629
+ if (profileName === 'form') await this.replaceFormSubject(settings.structureUrl, false);
630
+
631
+ connectProfile(profileName, this.subject, settings, configuredConnection);
632
+ this.connected = true;
633
+ this.fixture = null;
634
+ if (config.today) this.setToday('live', this.customToday, false);
635
+ this.write('connection', connectionLog('connect', profileName));
636
+ } catch {
637
+ try {
638
+ clearConnection(sessionStorage, storageKey);
639
+ } catch {
640
+ // A blocked session store also means there is nothing useful to clear.
641
+ }
642
+ this.connectionError = 'The saved connection could not be applied. Generated data remains active.';
643
+ this.write('connection', 'saved live settings rejected · generated data kept');
644
+ } finally {
645
+ this.connectionBusy = false;
646
+ }
647
+ },
648
+
649
+ async connect() {
650
+ if (this.connectionBusy || this.connected) return;
651
+
652
+ const validated = validateConnection(profileName, this.connectionDraft, configuredConnection);
653
+ this.connectionErrors = validated.errors;
654
+
655
+ if (!validated.ok) {
656
+ this.connectionError = 'Fix the highlighted connection fields before connecting.';
657
+ this.write('connection', 'validation failed · no values logged');
658
+ return;
659
+ }
660
+
661
+ this.connectionError = '';
662
+ this.connectionBusy = true;
663
+ this.generatedTodayChoice = { mode: this.todayMode, customDate: this.customToday };
664
+ this.generatedFixture = this.fixture;
665
+
666
+ try {
667
+ if (profileName === 'form') await this.replaceFormSubject(validated.settings.structureUrl, false);
668
+ else if (profileName !== 'vin-extractor') await clear?.(this.subject, this);
669
+
670
+ connectProfile(profileName, this.subject, validated.settings, configuredConnection);
671
+
672
+ try {
673
+ saveConnection(sessionStorage, storageKey, validated.settings);
674
+ } catch {
675
+ // The connection still works for this page view when storage is blocked.
676
+ }
677
+
678
+ this.connected = true;
679
+ this.fixture = null;
680
+ if (config.today) this.setToday('live', this.customToday, false);
681
+ this.write('connection', connectionLog('connect', profileName));
682
+ } catch {
683
+ disconnectProfile(profileName, this.subject, this.connectionBaseline, configuredConnection);
684
+ this.connectionError = 'The connection could not be applied. Generated data remains active.';
685
+ this.write('connection', 'connect failed · no values logged');
686
+ } finally {
687
+ this.connectionBusy = false;
688
+ }
689
+ },
690
+
691
+ async disconnect() {
692
+ if (this.connectionBusy || !this.connected) return;
693
+
694
+ this.connectionBusy = true;
695
+
696
+ try {
697
+ try {
698
+ clearConnection(sessionStorage, storageKey);
699
+ } catch {
700
+ // The in-memory state is still cleared below.
701
+ }
702
+
703
+ if (profileName === 'form') await this.replaceFormSubject(this.connectionBaseline.structureUrl, true);
704
+
705
+ disconnectProfile(profileName, this.subject, this.connectionBaseline, configuredConnection);
706
+ this.connected = false;
707
+ this.connectionDraft = emptyConnectionDraft(profileName, configuredConnection);
708
+ this.connectionErrors = {};
709
+ this.connectionError = '';
710
+
711
+ if (config.mocks) {
712
+ await apply?.(this.subject, this.data ?? {});
713
+
714
+ const previous = this.generatedFixture;
715
+ this.fixture = null;
716
+ if (previous === '' || previous === 'error' || this.keys.includes(previous)) await this.run(previous);
717
+ else await clear?.(this.subject, this);
718
+ }
719
+
720
+ if (config.today) {
721
+ const choice = this.generatedTodayChoice;
722
+ const mode = choice?.mode === 'custom' || choice?.mode === 'live' || choice?.mode === 'anchor' ? choice.mode : this.todayAnchor ? 'anchor' : 'live';
723
+ this.customToday = choice?.customDate || this.customToday;
724
+ this.setToday(mode, this.customToday, false);
725
+ }
726
+
727
+ this.write('connection', connectionLog('disconnect', profileName));
728
+ } finally {
729
+ this.connectionBusy = false;
730
+ }
731
+ },
732
+
733
+ async replaceFormSubject(structureUrl, isDev) {
734
+ const current = this.subject;
735
+ const replacement = current.cloneNode(false);
736
+
737
+ replacement.removeAttribute('structure-url');
738
+ if (structureUrl) replacement.setAttribute('structure-url', structureUrl);
739
+ replacement.structureUrl = structureUrl;
740
+ replacement.isDev = isDev;
741
+ if (config.language) replacement.language = this.language;
742
+ if (config.today) replacement.today = this.today;
743
+ if (this.connectionBaseline?.fields) replacement.fields = this.connectionBaseline.fields;
744
+
745
+ current.replaceWith(replacement);
746
+ this.subject = replacement;
747
+
748
+ await customElements.whenDefined(replacement.localName);
749
+ await replacement.componentOnReady?.();
750
+ await config.setup?.(replacement, this);
751
+ this.observe();
752
+ },
753
+
327
754
  setOpen(open) {
328
755
  this.open = open;
329
756
 
@@ -347,13 +774,6 @@ window.harness = function harness(options = {}) {
347
774
  this.write('theme', theme);
348
775
  },
349
776
 
350
- setMode(mode) {
351
- this.mode = mode;
352
- this.subject.isDev = mode === 'dev';
353
-
354
- this.write('mode', mode === 'dev' ? 'development · mock data' : `production · ${endpoint?.(this.subject) || 'no endpoint set'}`);
355
- },
356
-
357
777
  setWidth(width) {
358
778
  this.width = width;
359
779
 
@@ -366,23 +786,6 @@ window.harness = function harness(options = {}) {
366
786
 
367
787
  /* ---------- fixtures ---------- */
368
788
 
369
- async loadFixtures() {
370
- let data = {};
371
-
372
- try {
373
- data = (await window.loadMockData(config.mocks)) ?? {};
374
- this.data = data;
375
-
376
- await apply?.(this.subject, data);
377
- } catch (error) {
378
- this.write('fixtures', `failed to load — ${error.message}`);
379
- }
380
-
381
- // Generated from the mock keys, never hand-listed: a hardcoded list drifts
382
- // the moment someone adds a scenario (R7). Labels only annotate.
383
- this.setFixtures(Object.keys(data), data);
384
- },
385
-
386
789
  /** Public, so a component that pushes its own mock set can feed the rail. */
387
790
  setFixtures(keys, data) {
388
791
  if (data) this.data = data;
@@ -397,10 +800,12 @@ window.harness = function harness(options = {}) {
397
800
  },
398
801
 
399
802
  run(key) {
803
+ if (this.environmentChanging) return;
804
+
400
805
  this.fixture = key;
401
806
  this.write('fetch', key === '' ? '(empty)' : key);
402
807
 
403
- select?.(this.subject, key, this);
808
+ return key === '' ? clear?.(this.subject, this) : select?.(this.subject, key, this);
404
809
  },
405
810
 
406
811
  reload() {
@@ -587,41 +992,34 @@ const NAV = /* html */ `
587
992
  `;
588
993
 
589
994
  /*
590
- * Mode and Fixtures sit ABOVE the component, not in the drawer. They are the two
591
- * controls that get touched on every single interaction, and making someone open
592
- * a panel to pick a VIN was the worst thing about the first pass. Everything
593
- * you set once and forget — language, theme, width, the log — stays in the drawer.
995
+ * Fixtures sit ABOVE the component because they are touched on every interaction.
996
+ * Connection and the other set-once controls stay in the drawer.
594
997
  */
595
998
  const BAR = /* html */ `
596
999
  <template x-if="show.bar">
597
1000
  <div class="card border-base-300 bg-base-100 flex flex-col gap-2 border border-dashed p-3">
598
1001
  <div class="flex flex-wrap items-start gap-x-5 gap-y-3">
599
- <template x-if="show.mode">
600
- <div class="flex shrink-0 items-center gap-2">
601
- <span class="eyebrow text-neutral">Mode</span>
602
- <div class="flex gap-1.5" role="group" aria-label="Mode">
603
- <template x-for="item in [{ value: 'dev', label: 'Development' }, { value: 'prod', label: 'Production' }]" :key="item.value">
604
- <button type="button" class="btn btn-xs" :class="mode === item.value ? 'btn-primary' : 'btn-outline'"
605
- :aria-pressed="mode === item.value" @click="setMode(item.value)" x-text="item.label"></button>
606
- </template>
607
- </div>
608
- </div>
609
- </template>
610
-
611
1002
  <template x-if="show.fixtures">
612
1003
  <div class="flex min-w-0 flex-1 flex-wrap items-center gap-2">
613
1004
  <span class="eyebrow text-neutral">Fixtures</span>
614
- <button type="button" class="btn btn-xs btn-outline" @click="reload()">Reload</button>
1005
+ <button type="button" class="btn btn-xs btn-outline" :disabled="connected || environmentChanging" @click="reload()">Reload</button>
615
1006
 
616
1007
  <div class="flex flex-wrap gap-1.5" role="group" aria-label="Fixtures">
617
1008
  <template x-for="item in fixtures" :key="item.value">
618
- <button type="button" class="btn btn-xs font-mono font-normal" :class="fixture === item.value ? 'btn-primary' : 'btn-outline'"
1009
+ <button type="button" class="btn btn-xs font-mono font-normal" :class="fixture === item.value ? 'btn-primary' : 'btn-outline'" :disabled="connected || environmentChanging"
619
1010
  :aria-pressed="fixture === item.value" @click="run(item.value)" :title="item.note || item.label">
620
1011
  <span x-text="item.label"></span>
621
1012
  <span class="font-sans opacity-70" x-show="item.note" x-text="item.note"></span>
622
1013
  </button>
623
1014
  </template>
624
1015
  </div>
1016
+
1017
+ <div
1018
+ class="grid overflow-hidden transition-[grid-template-rows,opacity] duration-[320ms]"
1019
+ :class="connected ? 'grid-rows-[1fr] opacity-100' : 'grid-rows-[0fr] opacity-0'"
1020
+ >
1021
+ <p class="text-base-content/60 min-h-0 text-xs">Generated fixtures are available again after Disconnect.</p>
1022
+ </div>
625
1023
  </div>
626
1024
  </template>
627
1025
  </div>
@@ -714,6 +1112,70 @@ const RAIL = /* html */ `
714
1112
  </div>
715
1113
 
716
1114
  <div class="border-base-300 flex flex-col gap-4 border-t pt-4">
1115
+ <template x-if="show.connection">
1116
+ <div class="flex flex-col gap-2">
1117
+ <span class="eyebrow text-neutral" x-text="'Connection · ' + connectionLabel"></span>
1118
+
1119
+ <div
1120
+ class="grid overflow-hidden transition-[grid-template-rows,opacity] duration-[320ms]"
1121
+ :class="connected ? 'grid-rows-[0fr] opacity-0' : 'grid-rows-[1fr] opacity-100'"
1122
+ :aria-hidden="connected ? 'true' : 'false'"
1123
+ :inert="connected"
1124
+ >
1125
+ <div class="min-h-0">
1126
+ <div class="flex flex-col gap-2">
1127
+ <template x-for="field in connectionFields" :key="field.name">
1128
+ <label class="flex flex-col gap-1">
1129
+ <span class="text-base-content/70 text-xs font-medium" x-text="field.label"></span>
1130
+ <input
1131
+ class="input input-sm input-bordered w-full font-mono text-xs"
1132
+ :type="field.inputType"
1133
+ :name="'connection-' + field.name"
1134
+ :placeholder="field.placeholder"
1135
+ :autocomplete="field.secret ? 'new-password' : 'off'"
1136
+ spellcheck="false"
1137
+ x-model="connectionDraft[field.name]"
1138
+ :disabled="connected || connectionBusy"
1139
+ @input="clearConnectionError(field.name)"
1140
+ />
1141
+ <div
1142
+ class="grid overflow-hidden transition-[grid-template-rows,opacity] duration-[320ms]"
1143
+ :class="connectionErrors[field.name] ? 'grid-rows-[1fr] opacity-100' : 'grid-rows-[0fr] opacity-0'"
1144
+ >
1145
+ <p class="text-error min-h-0 text-xs" x-text="connectionErrors[field.name]"></p>
1146
+ </div>
1147
+ </label>
1148
+ </template>
1149
+
1150
+ <div
1151
+ class="grid overflow-hidden transition-[grid-template-rows,opacity] duration-[320ms]"
1152
+ :class="connectionError ? 'grid-rows-[1fr] opacity-100' : 'grid-rows-[0fr] opacity-0'"
1153
+ >
1154
+ <p class="text-error min-h-0 text-xs" x-text="connectionError"></p>
1155
+ </div>
1156
+
1157
+ <p class="text-base-content/60 text-xs">Kept only in this tab’s session storage. Nothing is added to the page URL or event log.</p>
1158
+ <button type="button" class="btn btn-sm btn-primary" :disabled="connectionBusy" @click="connect()">Connect to live API</button>
1159
+ </div>
1160
+ </div>
1161
+ </div>
1162
+
1163
+ <div
1164
+ class="grid overflow-hidden transition-[grid-template-rows,opacity] duration-[320ms]"
1165
+ :class="connected ? 'grid-rows-[1fr] opacity-100' : 'grid-rows-[0fr] opacity-0'"
1166
+ :aria-hidden="connected ? 'false' : 'true'"
1167
+ :inert="!connected"
1168
+ >
1169
+ <div class="min-h-0">
1170
+ <div class="flex flex-col gap-2">
1171
+ <p class="text-base-content/70 text-xs">Live settings are active. Their values stay masked and are never logged.</p>
1172
+ <button type="button" class="btn btn-sm btn-outline" :disabled="connectionBusy" @click="disconnect()">Disconnect</button>
1173
+ </div>
1174
+ </div>
1175
+ </div>
1176
+ </div>
1177
+ </template>
1178
+
717
1179
  <template x-if="show.language">
718
1180
  <div class="flex flex-col gap-1.5">
719
1181
  <span class="eyebrow text-neutral" x-text="'Language · ' + dir.toUpperCase()"></span>
@@ -726,6 +1188,71 @@ const RAIL = /* html */ `
726
1188
  </div>
727
1189
  </template>
728
1190
 
1191
+ <template x-if="show.environment">
1192
+ <div class="flex flex-col gap-1.5">
1193
+ <span class="eyebrow text-neutral" x-text="'Environment · ' + (environmentLabel || 'Unavailable')"></span>
1194
+ <label>
1195
+ <span class="sr-only">Environment</span>
1196
+ <select
1197
+ class="select select-sm select-bordered w-full font-mono"
1198
+ aria-label="Environment"
1199
+ :value="environment"
1200
+ :disabled="connected || environmentChanging || !environments.length"
1201
+ :title="connected ? 'Disconnect to switch generated environments.' : ''"
1202
+ @change="setEnvironment($event.target.value)"
1203
+ >
1204
+ <template x-for="item in environments" :key="item.name">
1205
+ <option :value="item.name" x-text="item.label"></option>
1206
+ </template>
1207
+ </select>
1208
+ </label>
1209
+ <p class="text-base-content/60 text-xs" x-show="environmentChanging">Loading generated fixtures…</p>
1210
+ <div
1211
+ class="grid overflow-hidden transition-[grid-template-rows,opacity] duration-[320ms]"
1212
+ :class="connected ? 'grid-rows-[1fr] opacity-100' : 'grid-rows-[0fr] opacity-0'"
1213
+ >
1214
+ <p class="text-base-content/60 min-h-0 text-xs">Disconnect to switch generated environments.</p>
1215
+ </div>
1216
+ <p class="text-error text-xs" x-show="environmentError" x-text="environmentError"></p>
1217
+ </div>
1218
+ </template>
1219
+
1220
+ <template x-if="show.today">
1221
+ <div class="flex flex-col gap-1.5">
1222
+ <span class="eyebrow text-neutral" x-text="'Today · ' + todayLabel"></span>
1223
+ <div class="flex flex-wrap gap-1.5" role="group" aria-label="Today">
1224
+ <button type="button" class="btn btn-sm" :class="todayMode === 'anchor' ? 'btn-primary' : 'btn-outline'"
1225
+ :aria-pressed="todayMode === 'anchor'" :disabled="!canUseToday('anchor')" :title="connected ? liveTodayExplanation : (todayAnchor ? 'Fixture anchor · ' + todayAnchor : 'No generated environment selected')"
1226
+ @click="setToday('anchor')">Anchor</button>
1227
+ <button type="button" class="btn btn-sm" :class="todayMode === 'live' ? 'btn-primary' : 'btn-outline'"
1228
+ :aria-pressed="todayMode === 'live'" @click="setToday('live')">Live</button>
1229
+ <button type="button" class="btn btn-sm" :class="todayMode === 'custom' ? 'btn-primary' : 'btn-outline'"
1230
+ :aria-pressed="todayMode === 'custom'" :disabled="!canUseToday('custom')" :title="connected ? liveTodayExplanation : ''" @click="setToday('custom')">Custom</button>
1231
+ </div>
1232
+
1233
+ <div
1234
+ class="grid overflow-hidden transition-[grid-template-rows,opacity] duration-200"
1235
+ :class="connected ? 'grid-rows-[1fr] opacity-100' : 'grid-rows-[0fr] opacity-0'"
1236
+ >
1237
+ <p class="text-base-content/60 min-h-0 text-xs" x-text="liveTodayExplanation"></p>
1238
+ </div>
1239
+
1240
+ <div
1241
+ class="grid overflow-hidden transition-[grid-template-rows,opacity] duration-200"
1242
+ :class="todayMode === 'custom' && !connected ? 'grid-rows-[1fr] opacity-100' : 'grid-rows-[0fr] opacity-0'"
1243
+ :aria-hidden="todayMode === 'custom' && !connected ? 'false' : 'true'"
1244
+ >
1245
+ <div class="min-h-0">
1246
+ <label class="mt-1 flex flex-col gap-1">
1247
+ <span class="sr-only">Custom today</span>
1248
+ <input class="input input-sm input-bordered w-full" type="date" x-model="customToday" :disabled="connected || todayMode !== 'custom'" @change="setToday('custom', customToday)" />
1249
+ </label>
1250
+ <p class="text-base-content/60 mt-1 text-xs" x-text="todayAnchor ? 'Display only — statuses were computed at ' + todayAnchor : 'Display only — fixture statuses are not recomputed'"></p>
1251
+ </div>
1252
+ </div>
1253
+ </div>
1254
+ </template>
1255
+
729
1256
  <template x-if="show.theme">
730
1257
  <div class="flex flex-col gap-1.5">
731
1258
  <span class="eyebrow text-neutral">Theme</span>