@c15t/react 3.0.0-alpha.1 → 3.0.0-alpha.10

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 (406) hide show
  1. package/AGENTS.md +96 -70
  2. package/README.md +21 -17
  3. package/SKILL.md +34 -0
  4. package/dist/aggregate-components.js +1 -1
  5. package/dist/chunk-warming.js +1 -1
  6. package/dist/component-hooks/use-headless-consent-ui.js +1 -1
  7. package/dist/component-hooks/use-headless-iab-consent-ui.js +1 -1
  8. package/dist/component-hooks/use-panel-trigger.js +1 -1
  9. package/dist/components/consent-gate/atoms.js +1 -0
  10. package/dist/components/consent-gate/consent-gate.js +2 -0
  11. package/dist/components/consent-gate/frame.js +2 -0
  12. package/dist/components/consent-gate/index.js +2 -0
  13. package/dist/components/consent-preferences-link/consent-preferences-link.js +1 -1
  14. package/dist/components/iab-panel/atoms/card.js +1 -1
  15. package/dist/components/iab-panel/atoms/feature-item.js +2 -0
  16. package/dist/components/iab-panel/atoms/header.js +1 -1
  17. package/dist/components/iab-panel/atoms/purpose-item.js +1 -1
  18. package/dist/components/iab-panel/atoms/root.js +1 -1
  19. package/dist/components/iab-panel/atoms/stack-item.js +1 -1
  20. package/dist/components/iab-panel/atoms/vendor-list.js +1 -1
  21. package/dist/components/iab-panel/hooks/use-gvl-data.js +1 -1
  22. package/dist/components/iab-panel/iab-panel.js +1 -1
  23. package/dist/components/iab-panel/index.js +1 -1
  24. package/dist/components/iab-prompt/atoms/overlay.js +1 -1
  25. package/dist/components/iab-prompt/atoms/root.js +1 -1
  26. package/dist/components/panel/atoms/card.js +1 -1
  27. package/dist/components/panel/atoms/overlay.js +1 -1
  28. package/dist/components/panel/atoms/root.js +1 -1
  29. package/dist/components/panel/panel.js +1 -1
  30. package/dist/components/panel-trigger/atoms/button.js +1 -1
  31. package/dist/components/panel-trigger/atoms/icon.js +1 -1
  32. package/dist/components/panel-trigger/atoms/root.js +1 -1
  33. package/dist/components/panel-trigger/atoms/toolbar.js +1 -1
  34. package/dist/components/panel-trigger/atoms/trigger-icons.js +1 -0
  35. package/dist/components/panel-trigger/panel-trigger-toolbar.js +1 -1
  36. package/dist/components/panel-trigger/panel-trigger.js +1 -1
  37. package/dist/components/preferences/atoms/accordion.js +1 -1
  38. package/dist/components/preferences/atoms/root.js +1 -1
  39. package/dist/components/preferences/atoms/vendor-list.js +1 -0
  40. package/dist/components/preferences/index.js +1 -1
  41. package/dist/components/prompt/atoms/early-tap-script.js +2 -0
  42. package/dist/components/prompt/atoms/overlay.js +1 -1
  43. package/dist/components/prompt/atoms/root.js +1 -1
  44. package/dist/components/prompt/components.js +1 -1
  45. package/dist/components/prompt/policy-actions.js +1 -1
  46. package/dist/components/prompt/prompt.js +1 -1
  47. package/dist/components/shared/dialog-sheets.js +1 -0
  48. package/dist/components/shared/first-paint-sheets.js +1 -0
  49. package/dist/components/shared/iab-dialog-sheets.js +1 -0
  50. package/dist/components/shared/iab-first-paint-sheets.js +1 -0
  51. package/dist/components/shared/libs/compose-refs.js +1 -0
  52. package/dist/components/shared/libs/slot.js +1 -1
  53. package/dist/components/shared/primitives/button.js +1 -1
  54. package/dist/components/shared/primitives/legal-links/index.js +1 -1
  55. package/dist/components/shared/streamed-surface.js +2 -0
  56. package/dist/components/shared/surface-styles.js +2 -0
  57. package/dist/components/shared/ui/dialog/dialog.js +1 -1
  58. package/dist/components/shared/ui/icon.js +1 -1
  59. package/dist/components/shared/ui/logo.js +1 -1
  60. package/dist/components/shared/ui/preference-item/preference-item.js +1 -1
  61. package/dist/consent-gate.js +2 -0
  62. package/dist/consent-theme.js +1 -0
  63. package/dist/context/consent-tracking-context.js +1 -1
  64. package/dist/context/index.js +1 -1
  65. package/dist/context.js +1 -1
  66. package/dist/devtools-launcher.js +2 -0
  67. package/dist/devtools.js +1 -1
  68. package/dist/draft-context.js +2 -0
  69. package/dist/draft.js +1 -1
  70. package/dist/external-iab-context.js +1 -1
  71. package/dist/gpp.js +2 -0
  72. package/dist/headless.js +1 -1
  73. package/dist/hooks/use-color-scheme.js +1 -1
  74. package/dist/hooks/use-committed-ref.js +2 -0
  75. package/dist/hooks/use-focus-trap.js +1 -1
  76. package/dist/hooks/use-headless-iab-consent-ui.js +1 -1
  77. package/dist/hooks/use-iab-policy-guard.js +1 -0
  78. package/dist/hooks/use-late-entry.js +2 -0
  79. package/dist/hooks.js +1 -1
  80. package/dist/iab/styles.tw3.css +5 -45
  81. package/dist/iab-context.js +1 -1
  82. package/dist/index-parts/consent-banner.js +1 -0
  83. package/dist/index-parts/consent-dialog-link.js +1 -0
  84. package/dist/index-parts/consent-dialog-trigger.js +1 -0
  85. package/dist/index-parts/consent-dialog.js +1 -0
  86. package/dist/index-parts/consent-gate.js +1 -0
  87. package/dist/index-parts/core.js +1 -0
  88. package/dist/index-parts/draft.js +1 -0
  89. package/dist/index-parts/frame.js +1 -0
  90. package/dist/index-parts/hooks.js +1 -0
  91. package/dist/index-parts/hosted.js +1 -0
  92. package/dist/index-parts/module-hooks.js +1 -0
  93. package/dist/index-parts/offline.js +1 -0
  94. package/dist/index-parts/presets.js +1 -0
  95. package/dist/index-parts/provider.js +1 -0
  96. package/dist/index-parts/theme.js +1 -0
  97. package/dist/index-parts/translations.js +1 -0
  98. package/dist/index.js +1 -1
  99. package/dist/kernel-selector.js +1 -0
  100. package/dist/modes.js +1 -0
  101. package/dist/module-hooks/iframe-blocker.js +1 -1
  102. package/dist/module-hooks/network-blocker.js +1 -1
  103. package/dist/module-hooks/network-hold.js +2 -0
  104. package/dist/module-hooks/persistence.js +1 -1
  105. package/dist/panel-link.js +1 -1
  106. package/dist/panel-trigger.js +1 -1
  107. package/dist/panel.js +1 -1
  108. package/dist/postcss-tailwind3.js +1 -0
  109. package/dist/preferences.js +1 -2
  110. package/dist/provider.js +1 -1
  111. package/dist/server/fetch-ssr-data.js +1 -1
  112. package/dist/server/headers.js +1 -1
  113. package/dist/server/normalize-url.js +1 -1
  114. package/dist/styles.tw3.css +33 -51
  115. package/dist/transports/hosted.js +1 -0
  116. package/dist/transports/manifest.js +1 -0
  117. package/dist/transports/offline.js +1 -1
  118. package/dist/utils/deferred-module.js +2 -0
  119. package/dist/utils/merge-slot-props.js +1 -1
  120. package/dist/utils/theme-utils.js +1 -1
  121. package/dist/version.js +1 -1
  122. package/dist-types/aggregate-components.d.ts +2 -2
  123. package/dist-types/chunk-warming.d.ts +45 -8
  124. package/dist-types/component-hooks/use-headless-consent-ui.d.ts +5 -5
  125. package/dist-types/component-hooks/use-headless-iab-consent-ui.d.ts +2 -2
  126. package/dist-types/components/consent-gate/atoms.d.ts +11 -0
  127. package/dist-types/components/consent-gate/consent-gate.d.ts +2 -0
  128. package/dist-types/components/consent-gate/frame.d.ts +19 -0
  129. package/dist-types/components/consent-gate/index.d.ts +25 -0
  130. package/dist-types/components/{frame → consent-gate}/types.d.ts +7 -7
  131. package/dist-types/components/consent-preferences-link/consent-preferences-link.d.ts +1 -1
  132. package/dist-types/components/consent-preferences-link/index.d.ts +1 -1
  133. package/dist-types/components/iab-panel/atoms/feature-item.d.ts +28 -0
  134. package/dist-types/components/iab-panel/atoms/purpose-item.d.ts +5 -2
  135. package/dist-types/components/iab-panel/atoms/stack-item.d.ts +7 -2
  136. package/dist-types/components/iab-panel/atoms/vendor-list.d.ts +1 -1
  137. package/dist-types/components/iab-panel/iab-panel.d.ts +3 -3
  138. package/dist-types/components/iab-panel/index.d.ts +29 -26
  139. package/dist-types/components/iab-panel/use-iab-translations.d.ts +4 -0
  140. package/dist-types/components/iab-prompt/iab-prompt.d.ts +3 -3
  141. package/dist-types/components/iab-prompt/index.d.ts +19 -19
  142. package/dist-types/components/panel/atoms/card.d.ts +5 -4
  143. package/dist-types/components/panel/atoms/overlay.d.ts +1 -1
  144. package/dist-types/components/panel/index.d.ts +8 -8
  145. package/dist-types/components/panel/panel.d.ts +3 -3
  146. package/dist-types/components/panel-link/index.d.ts +1 -1
  147. package/dist-types/components/panel-trigger/atoms/button.d.ts +1 -1
  148. package/dist-types/components/panel-trigger/atoms/icon.d.ts +1 -1
  149. package/dist-types/components/panel-trigger/atoms/root.d.ts +2 -2
  150. package/dist-types/components/panel-trigger/atoms/toolbar.d.ts +2 -2
  151. package/dist-types/components/panel-trigger/atoms/trigger-icons.d.ts +25 -0
  152. package/dist-types/components/panel-trigger/index.d.ts +4 -4
  153. package/dist-types/components/panel-trigger/panel-trigger-toolbar.d.ts +1 -1
  154. package/dist-types/components/panel-trigger/panel-trigger.d.ts +9 -17
  155. package/dist-types/components/panel-trigger/types.d.ts +1 -1
  156. package/dist-types/components/preferences/atoms/accordion.d.ts +6 -4
  157. package/dist-types/components/preferences/atoms/button.d.ts +1 -1
  158. package/dist-types/components/preferences/atoms/footer.d.ts +1 -1
  159. package/dist-types/components/preferences/atoms/root.d.ts +1 -1
  160. package/dist-types/components/preferences/atoms/vendor-list.d.ts +23 -0
  161. package/dist-types/components/preferences/index.d.ts +14 -12
  162. package/dist-types/components/preferences/policy-actions.d.ts +2 -2
  163. package/dist-types/components/preferences/preferences.d.ts +1 -1
  164. package/dist-types/components/preferences/types.d.ts +1 -1
  165. package/dist-types/components/prompt/atoms/early-tap-script.d.ts +17 -0
  166. package/dist-types/components/prompt/atoms/root.d.ts +4 -0
  167. package/dist-types/components/prompt/components.d.ts +9 -3
  168. package/dist-types/components/prompt/index.d.ts +12 -12
  169. package/dist-types/components/prompt/policy-actions.d.ts +2 -2
  170. package/dist-types/components/prompt/prompt.d.ts +5 -1
  171. package/dist-types/components/shared/dialog-sheets.d.ts +9 -0
  172. package/dist-types/components/shared/first-paint-sheets.d.ts +8 -0
  173. package/dist-types/components/shared/iab-dialog-sheets.d.ts +3 -0
  174. package/dist-types/components/shared/iab-first-paint-sheets.d.ts +3 -0
  175. package/dist-types/components/shared/libs/compose-refs.d.ts +20 -0
  176. package/dist-types/components/shared/policy-actions.d.ts +2 -2
  177. package/dist-types/components/shared/primitives/box.d.ts +1 -1
  178. package/dist-types/components/shared/primitives/button.d.ts +2 -2
  179. package/dist-types/components/shared/primitives/button.types.d.ts +1 -1
  180. package/dist-types/components/shared/streamed-surface.d.ts +23 -0
  181. package/dist-types/components/shared/surface-styles.d.ts +36 -0
  182. package/dist-types/components/shared/ui/accordion/accordion.d.ts +2 -2
  183. package/dist-types/components/shared/ui/accordion/index.d.ts +2 -2
  184. package/dist-types/components/shared/ui/animated-collapse/index.d.ts +2 -2
  185. package/dist-types/components/shared/ui/branding.d.ts +1 -1
  186. package/dist-types/components/shared/ui/button/button.d.ts +1 -1
  187. package/dist-types/components/shared/ui/button/index.d.ts +2 -2
  188. package/dist-types/components/shared/ui/collapsible/index.d.ts +1 -1
  189. package/dist-types/components/shared/ui/dialog/index.d.ts +1 -1
  190. package/dist-types/components/shared/ui/icon.d.ts +2 -1
  191. package/dist-types/components/shared/ui/logo.d.ts +0 -10
  192. package/dist-types/components/shared/ui/preference-item/index.d.ts +1 -1
  193. package/dist-types/components/shared/ui/preference-item/preference-item.d.ts +13 -0
  194. package/dist-types/components/shared/ui/switch/index.d.ts +2 -2
  195. package/dist-types/components/shared/ui/switch/switch.d.ts +1 -1
  196. package/dist-types/components/shared/ui/tabs/index.d.ts +1 -1
  197. package/dist-types/consent-gate.d.ts +1 -0
  198. package/dist-types/consent-theme.d.ts +38 -0
  199. package/dist-types/context/consent-tracking-context.d.ts +10 -0
  200. package/dist-types/context/index.d.ts +3 -3
  201. package/dist-types/context/theme-context.d.ts +3 -3
  202. package/dist-types/context.d.ts +25 -1
  203. package/dist-types/devtools-launcher.d.ts +29 -0
  204. package/dist-types/devtools.d.ts +13 -2
  205. package/dist-types/draft-context.d.ts +31 -0
  206. package/dist-types/draft.d.ts +81 -9
  207. package/dist-types/gpp.d.ts +27 -0
  208. package/dist-types/headless.d.ts +6 -7
  209. package/dist-types/hooks/use-committed-ref.d.ts +24 -0
  210. package/dist-types/hooks/use-focus-trap.d.ts +3 -1
  211. package/dist-types/hooks/use-headless-iab-consent-ui.d.ts +1 -1
  212. package/dist-types/hooks/use-iab-manager.d.ts +5 -5
  213. package/dist-types/hooks/use-iab-policy-guard.d.ts +35 -0
  214. package/dist-types/hooks/use-late-entry.d.ts +14 -0
  215. package/dist-types/hooks/use-theme.d.ts +1 -1
  216. package/dist-types/hooks.d.ts +106 -11
  217. package/dist-types/iab.d.ts +7 -7
  218. package/dist-types/index-parts/consent-banner.d.ts +7 -0
  219. package/dist-types/index-parts/consent-dialog-link.d.ts +7 -0
  220. package/dist-types/index-parts/consent-dialog-trigger.d.ts +7 -0
  221. package/dist-types/index-parts/consent-dialog.d.ts +7 -0
  222. package/dist-types/index-parts/consent-gate.d.ts +7 -0
  223. package/dist-types/index-parts/core.d.ts +7 -0
  224. package/dist-types/index-parts/draft.d.ts +7 -0
  225. package/dist-types/index-parts/frame.d.ts +7 -0
  226. package/dist-types/index-parts/hooks.d.ts +7 -0
  227. package/dist-types/index-parts/hosted.d.ts +7 -0
  228. package/dist-types/index-parts/module-hooks.d.ts +7 -0
  229. package/dist-types/index-parts/offline.d.ts +7 -0
  230. package/dist-types/index-parts/presets.d.ts +7 -0
  231. package/dist-types/index-parts/provider.d.ts +7 -0
  232. package/dist-types/index-parts/theme.d.ts +8 -0
  233. package/dist-types/index-parts/translations.d.ts +7 -0
  234. package/dist-types/index.d.ts +32 -28
  235. package/dist-types/kernel-selector.d.ts +68 -0
  236. package/dist-types/modes.d.ts +26 -0
  237. package/dist-types/module-hooks/iframe-blocker.d.ts +33 -1
  238. package/dist-types/module-hooks/network-blocker.d.ts +23 -0
  239. package/dist-types/module-hooks/network-hold.d.ts +47 -0
  240. package/dist-types/module-hooks.d.ts +4 -4
  241. package/dist-types/panel-link.d.ts +1 -1
  242. package/dist-types/panel-trigger.d.ts +1 -1
  243. package/dist-types/panel.d.ts +13 -1
  244. package/dist-types/postcss-tailwind3.d.ts +21 -0
  245. package/dist-types/preferences.d.ts +12 -1
  246. package/dist-types/primitives/accordion.d.ts +1 -1
  247. package/dist-types/primitives/button.d.ts +1 -1
  248. package/dist-types/primitives/collapsible.d.ts +1 -1
  249. package/dist-types/primitives/dialog.d.ts +1 -1
  250. package/dist-types/primitives/preference-item.d.ts +1 -1
  251. package/dist-types/primitives/switch.d.ts +1 -1
  252. package/dist-types/primitives/tabs.d.ts +1 -1
  253. package/dist-types/primitives.d.ts +7 -7
  254. package/dist-types/prompt.d.ts +1 -1
  255. package/dist-types/provider.d.ts +169 -28
  256. package/dist-types/server/fetch-ssr-data.d.ts +1 -1
  257. package/dist-types/server/headers.d.ts +14 -4
  258. package/dist-types/server/index.d.ts +5 -5
  259. package/dist-types/server/normalize-url.d.ts +14 -7
  260. package/dist-types/server/types.d.ts +12 -1
  261. package/dist-types/theme-provider.d.ts +2 -2
  262. package/dist-types/transports/hosted.d.ts +36 -0
  263. package/dist-types/transports/manifest.d.ts +48 -0
  264. package/dist-types/transports/offline.d.ts +7 -16
  265. package/dist-types/types/index.d.ts +3 -3
  266. package/dist-types/types/manager.d.ts +1 -1
  267. package/dist-types/types/theme/index.d.ts +1 -1
  268. package/dist-types/ui-config-context.d.ts +14 -2
  269. package/dist-types/utils/deferred-module.d.ts +13 -0
  270. package/dist-types/utils/index.d.ts +4 -4
  271. package/dist-types/utils/merge-slot-props.d.ts +17 -3
  272. package/dist-types/utils/merge-styles.d.ts +1 -1
  273. package/dist-types/utils/theme-utils.d.ts +18 -3
  274. package/dist-types/version.d.ts +1 -1
  275. package/docs/README.md +96 -70
  276. package/docs/assets/v3/bottom-bar.png +0 -0
  277. package/docs/assets/v3/brand-card.png +0 -0
  278. package/docs/assets/v3/brand-preferences.png +0 -0
  279. package/docs/assets/v3/choice-wall.png +0 -0
  280. package/docs/assets/v3/headless-bar-html.png +0 -0
  281. package/docs/assets/v3/headless-bar-mobile.png +0 -0
  282. package/docs/assets/v3/headless-bar.png +0 -0
  283. package/docs/assets/v3/slim-bar.png +0 -0
  284. package/docs/concepts/choose-your-setup.md +138 -0
  285. package/docs/concepts/consent-categories.md +84 -0
  286. package/docs/concepts/consent-state.md +421 -0
  287. package/docs/concepts/data-fetching.md +270 -0
  288. package/docs/concepts/how-consent-works.md +123 -0
  289. package/docs/concepts/modes.md +278 -0
  290. package/docs/{frameworks/react/concepts/policy-presets.md → concepts/policies.md} +8 -60
  291. package/docs/customization/class-names.md +202 -0
  292. package/docs/customization/dark-mode.md +157 -0
  293. package/docs/customization/motion.md +128 -0
  294. package/docs/customization/overview.md +68 -33
  295. package/docs/customization/recipes.md +838 -47
  296. package/docs/customization/slots.md +217 -35
  297. package/docs/customization/stylesheets.md +248 -0
  298. package/docs/customization/tailwind.md +863 -0
  299. package/docs/customization/tokens.md +167 -36
  300. package/docs/customization/translations.md +87 -3
  301. package/docs/frameworks/react/callbacks.md +118 -0
  302. package/docs/frameworks/react/clear-on-revocation.md +106 -0
  303. package/docs/frameworks/react/components/consent-banner.md +48 -27
  304. package/docs/frameworks/react/components/consent-dialog-link.md +13 -11
  305. package/docs/frameworks/react/components/consent-dialog-trigger.md +86 -76
  306. package/docs/frameworks/react/components/consent-dialog.md +111 -58
  307. package/docs/frameworks/react/components/{frame.md → consent-gate.md} +70 -49
  308. package/docs/frameworks/react/components/consent-provider.md +140 -0
  309. package/docs/frameworks/react/components/consent-widget.md +69 -24
  310. package/docs/frameworks/react/components/dev-tools.md +76 -85
  311. package/docs/frameworks/react/components.md +113 -0
  312. package/docs/frameworks/react/compose.md +354 -0
  313. package/docs/frameworks/react/content-security-policy.md +143 -0
  314. package/docs/frameworks/react/customize.md +258 -0
  315. package/docs/frameworks/react/embeds.md +89 -0
  316. package/docs/frameworks/react/gpp.md +163 -0
  317. package/docs/frameworks/react/headless.md +4 -4
  318. package/docs/frameworks/react/hooks.md +70 -0
  319. package/docs/frameworks/react/iab.md +259 -0
  320. package/docs/frameworks/react/network-blocker.md +140 -0
  321. package/docs/frameworks/react/quickstart.md +220 -102
  322. package/docs/frameworks/react/rendering.md +263 -0
  323. package/docs/frameworks/react/scripts.md +114 -0
  324. package/docs/frameworks/react/translations.md +155 -0
  325. package/docs/frameworks/react/troubleshooting.md +188 -61
  326. package/docs/frameworks/react/upgrade-v3.md +651 -0
  327. package/docs/frameworks/react/vendor-consent.md +188 -0
  328. package/docs/guides/banner-experiments.md +672 -0
  329. package/docs/guides/troubleshooting.md +192 -54
  330. package/docs/guides/verify-consent.md +96 -49
  331. package/docs/integrations/adobe-analytics.md +177 -183
  332. package/docs/integrations/ahrefs-analytics.md +162 -178
  333. package/docs/integrations/amplitude.md +172 -180
  334. package/docs/integrations/building-integrations.md +137 -33
  335. package/docs/integrations/clearbit.md +164 -178
  336. package/docs/integrations/cloudflare-web-analytics.md +166 -180
  337. package/docs/integrations/cloudflare-zaraz.md +333 -0
  338. package/docs/integrations/crisp.md +174 -182
  339. package/docs/integrations/databuddy.md +167 -197
  340. package/docs/integrations/fathom-analytics.md +168 -180
  341. package/docs/integrations/front-chat.md +308 -0
  342. package/docs/integrations/google-maps.md +142 -108
  343. package/docs/integrations/google-tag-manager.md +258 -185
  344. package/docs/integrations/google-tag.md +226 -182
  345. package/docs/integrations/heap.md +173 -179
  346. package/docs/integrations/hightouch.md +171 -181
  347. package/docs/integrations/hotjar.md +169 -179
  348. package/docs/integrations/intercom.md +193 -177
  349. package/docs/integrations/klaviyo.md +473 -0
  350. package/docs/integrations/linkedin-insights.md +184 -174
  351. package/docs/integrations/logrocket.md +170 -180
  352. package/docs/integrations/matomo-analytics.md +198 -202
  353. package/docs/integrations/meta-pixel.md +198 -174
  354. package/docs/integrations/microsoft-clarity.md +173 -179
  355. package/docs/integrations/microsoft-uet.md +158 -178
  356. package/docs/integrations/mixpanel-analytics.md +165 -184
  357. package/docs/integrations/one-dollar-stats.md +292 -0
  358. package/docs/integrations/openai-pixel.md +214 -325
  359. package/docs/integrations/overview.md +152 -83
  360. package/docs/integrations/pinterest-tag.md +315 -0
  361. package/docs/integrations/pirsch.md +179 -183
  362. package/docs/integrations/plausible-analytics.md +182 -182
  363. package/docs/integrations/posthog.md +323 -264
  364. package/docs/integrations/promptwatch.md +164 -178
  365. package/docs/integrations/reddit-pixel.md +195 -181
  366. package/docs/integrations/rudderstack.md +211 -210
  367. package/docs/integrations/rybbit-analytics.md +181 -184
  368. package/docs/integrations/segment.md +192 -178
  369. package/docs/integrations/sentry.md +402 -0
  370. package/docs/integrations/snapchat-pixel.md +196 -180
  371. package/docs/integrations/tiktok-pixel.md +181 -174
  372. package/docs/integrations/umami-analytics.md +173 -182
  373. package/docs/integrations/vercel-analytics.md +176 -181
  374. package/docs/integrations/x-pixel.md +186 -174
  375. package/docs/integrations/youtube.md +150 -111
  376. package/package.json +48 -17
  377. package/dist/component-hooks/use-manager.js +0 -2
  378. package/dist/components/frame/atoms.js +0 -1
  379. package/dist/components/frame/frame.js +0 -2
  380. package/dist/components/frame/index.js +0 -2
  381. package/dist/frame.js +0 -2
  382. package/dist/ui-save.js +0 -1
  383. package/dist-types/component-hooks/use-manager.d.ts +0 -40
  384. package/dist-types/components/frame/atoms.d.ts +0 -11
  385. package/dist-types/components/frame/frame.d.ts +0 -2
  386. package/dist-types/components/frame/index.d.ts +0 -24
  387. package/dist-types/frame.d.ts +0 -1
  388. package/dist-types/ui-save.d.ts +0 -5
  389. package/docs/assets/v3/brand-bar.png +0 -0
  390. package/docs/assets/v3/mobile-card.png +0 -0
  391. package/docs/assets/v3/preferences.png +0 -0
  392. package/docs/frameworks/react/components/consent-manager-provider.md +0 -71
  393. package/docs/frameworks/react/concepts/consent-categories.md +0 -33
  394. package/docs/frameworks/react/hooks/use-consent-manager/overview.md +0 -58
  395. package/docs/frameworks/react/iab/overview.md +0 -53
  396. package/docs/frameworks/react/script-loader.md +0 -63
  397. package/docs/frameworks/react/styling/overview.md +0 -217
  398. package/docs/guides/consent-state.md +0 -60
  399. package/docs/guides/data-fetching.md +0 -163
  400. package/docs/guides/deployment-modes.md +0 -63
  401. package/docs/integrations/clear-on-revocation.md +0 -167
  402. package/docs/upgrade-v3.md +0 -334
  403. package/readme.json +0 -48
  404. package/src/iab/styles.css +0 -11
  405. package/src/styles.css +0 -10
  406. /package/dist/components/{frame → consent-gate}/types.js +0 -0
@@ -0,0 +1,138 @@
1
+ ---
2
+ title: Choose your setup
3
+ description: Pick the c15t setup for your framework, rendering mode and hosting,
4
+ and decide who runs the consent backend.
5
+ group: concepts
6
+ ---
7
+
8
+ ## Find your app in this table
9
+
10
+ Most apps need one row. Follow its guide from start to finish; each guide links
11
+ to the alternatives it rules out.
12
+
13
+ | Your app | Setup | Guide |
14
+ | -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
15
+ | Next.js App Router with a Next.js server | Server resolves consent from a build-time manifest and streams it to the page | [App Router](https://v3.c15t.com/docs/frameworks/next/quickstart) |
16
+ | Next.js App Router, page must arrive with its banner | Await consent inside `Suspense` | [Stream or await](https://v3.c15t.com/docs/frameworks/next/rendering#stream-or-await-consent-in-the-app-router) |
17
+ | Next.js with static, ISR, `'use cache'` or `ensureStatic = 'navigation'` pages | Browser resolves consent on those pages | [Static, ISR and cached pages](https://v3.c15t.com/docs/frameworks/next/rendering#render-static-isr-and-cached-pages) |
18
+ | Next.js Pages Router | Resolve consent in `getServerSideProps` | [Pages Router](https://v3.c15t.com/docs/frameworks/next/rendering#pick-your-rendering-path) |
19
+ | Next.js with `output: 'export'` | Browser resolves consent | [Static export](https://v3.c15t.com/docs/frameworks/next/static-export) |
20
+ | TanStack Start with a server | Root loader resolves consent on the server | [TanStack Start](https://v3.c15t.com/docs/frameworks/tanstack-start/quickstart) |
21
+ | TanStack Start on a serverless host, banner may follow the page | Root loader streams consent | [TanStack Start rendering](https://v3.c15t.com/docs/frameworks/tanstack-start/rendering#stream-the-page-while-consent-resolves) |
22
+ | TanStack Start in SPA mode or prerendered | Browser resolves consent | [TanStack Start rendering](https://v3.c15t.com/docs/frameworks/tanstack-start/rendering#spa-mode-prerendered-pages-and-static-hosts) |
23
+ | React with Vite, or another single-page app | Browser resolves consent | [React](../frameworks/react/quickstart.md) |
24
+ | React Router framework mode, Remix, or another server-rendered React app | Browser resolves consent after hydration | [React rendering](../frameworks/react/rendering.md#react-router-remix-and-other-server-rendered-apps) |
25
+ | Nuxt with a Nuxt server | Nuxt module resolves consent on the server from a build-time manifest | [Nuxt](https://v3.c15t.com/docs/frameworks/nuxt/quickstart) |
26
+ | Nuxt with prerendered or cached routes | Nuxt server for most pages, the browser on prerendered and cached routes | [Nuxt prerendered and cached routes](https://v3.c15t.com/docs/frameworks/nuxt/rendering#prerendered-and-cached-routes) |
27
+ | Nuxt with `nuxt generate` on static hosting | Nuxt module, browser resolves consent from the manifest or `/init` | [Nuxt static hosting](https://v3.c15t.com/docs/frameworks/nuxt/rendering#deploy-to-static-hosting) |
28
+ | Nuxt with `ssr: false` | Nuxt module, browser resolves consent from the manifest | [Nuxt single-page app](https://v3.c15t.com/docs/frameworks/nuxt/rendering#build-a-single-page-app-with-ssr-false) |
29
+ | Vue with Vite | Vue plugin, browser resolves consent | [Vue](https://v3.c15t.com/docs/frameworks/vue/quickstart) |
30
+ | Vue rendered on the server without Nuxt | Vue plugin; the banner mounts after hydration. Use Nuxt for the banner in server HTML. | [Vue rendering](https://v3.c15t.com/docs/frameworks/vue/rendering#server-rendering-without-nuxt) |
31
+ | Astro with static output | Browser resolves consent through the hosted backend | [Astro static output](https://v3.c15t.com/docs/frameworks/astro/rendering#static-output-with-hosted) |
32
+ | Astro with server output and an adapter | Middleware resolves consent from a build-time manifest | [Astro server output](https://v3.c15t.com/docs/frameworks/astro/rendering#server-output-with-manifest) |
33
+ | Svelte with Vite | Svelte provider, browser resolves consent | [Svelte](https://v3.c15t.com/docs/frameworks/svelte/quickstart) |
34
+ | SvelteKit with a server | Root layout load resolves consent on the server | [SvelteKit](https://v3.c15t.com/docs/frameworks/sveltekit/quickstart) |
35
+ | SvelteKit with `adapter-static`, prerendered pages or SPA mode | Browser resolves consent | [SvelteKit rendering](https://v3.c15t.com/docs/frameworks/sveltekit/rendering#build-a-static-site) |
36
+ | Plain HTML, a CMS, a page builder, or a static site generator such as Hugo or Eleventy | One script tag, stock banner included | [HTML](https://v3.c15t.com/docs/frameworks/html/quickstart) |
37
+ | JavaScript with a bundler and no UI framework | `@c15t/browser` module, stock banner included | [JavaScript](https://v3.c15t.com/docs/frameworks/javascript/quickstart) |
38
+ | Your own UI, or a framework without an adapter such as Solid | Headless runtime | [JavaScript headless](https://v3.c15t.com/docs/frameworks/javascript/headless) |
39
+
40
+ Solid has no c15t adapter. Use the [headless runtime](https://v3.c15t.com/docs/frameworks/javascript/headless#use-c15t-with-solid)
41
+ and subscribe to it from your components.
42
+
43
+ ## Decide who runs the consent backend
44
+
45
+ The backend supplies policies and stores consent records. Every setup above
46
+ works with each option; only the URL changes.
47
+
48
+ | Option | Use it when |
49
+ | ----------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
50
+ | [Inth](https://inth.com) | You want hosted policies and consent records. The guides use this. |
51
+ | [Self-hosted c15t backend](https://v3.c15t.com/docs/self-host/overview) | You need to run the service and its database yourself. |
52
+ | Offline mode | Local development and tests. Policies live in your code and choices stay in the browser, with no consent records. Not recommended for production environments. |
53
+
54
+ To use Inth, create a project, set its policy rules, add your site's origin to
55
+ its trusted origins, and copy the backend URL. The guides use the placeholder
56
+ `https://your-project.inth.app`; replace it with your project's backend URL,
57
+ including any path prefix.
58
+
59
+ ## Server rendering or browser rendering
60
+
61
+ Where consent resolves decides what the visitor's first HTML contains.
62
+
63
+ | Rendering | What the visitor gets |
64
+ | -------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
65
+ | The server resolves consent for each request | The server reads the visitor's cookie and location headers, so the browser starts with the answer. Gated scripts can run right after hydration, and the banner can be part of the HTML. |
66
+ | The browser resolves consent | The HTML is the same for everyone. After the page loads, the browser requests the policy, then shows the banner or runs gated scripts. |
67
+
68
+ A static or prerendered page cannot contain one visitor's consent, because the
69
+ same HTML goes to everyone. Static sites still use Inth; the browser talks to it
70
+ directly. A same-origin `/api/c15t` route only works where a server runs it.
71
+
72
+ Pages that are cached and shared between visitors, such as Next.js ISR or
73
+ `'use cache'` routes, need browser resolution for the same reason. Cache the
74
+ public policy manifest, never a response that contains a visitor's consent.
75
+
76
+ ## How the server gets the policy
77
+
78
+ Use a build-time manifest as the default for supported production builds.
79
+ `manifest()` is the default mode in Next.js, TanStack Start, Nuxt, Astro and
80
+ SvelteKit, so their servers resolve requests from the deployment's snapshot.
81
+ Vite apps can bundle it for browser resolution too; location-based policies
82
+ may still need a location request. The framework quickstarts show the build
83
+ integration. [Consent modes](./modes.md) lists every mode, its
84
+ options and the backend URL variable each framework reads.
85
+
86
+ The build fetches your public policy once and bundles it, so the server never
87
+ fetches it at runtime.
88
+
89
+ The snapshot is fixed at build time:
90
+
91
+ * Rebuild after changing policies, translations or vendors. If your CI caches
92
+ build output, force a fresh build.
93
+ * Consent choices still go to the backend.
94
+
95
+ The build reads the backend URL from your public backend URL variable when
96
+ the config doesn't pass one: `NEXT_PUBLIC_C15T_BACKEND_URL` in Next.js,
97
+ `NUXT_PUBLIC_C15T_BACKEND_URL` in Nuxt, `PUBLIC_C15T_BACKEND_URL` in Astro,
98
+ Svelte and SvelteKit, and `VITE_C15T_BACKEND_URL` in TanStack Start and other
99
+ Vite apps. Each also reads the matching Inth variable, such as
100
+ `NEXT_PUBLIC_INTH_PROJECT_URL`, when the c15t one is unset. See
101
+ [set the backend URL](./modes.md#set-the-backend-url).
102
+
103
+ The fetch waits at most 10 seconds. When it fails, or no backend URL is set,
104
+ every framework does the same thing:
105
+
106
+ | Command | Default when the fetch fails |
107
+ | ------------------------------------------------------------------------- | -------------------------------------------------------- |
108
+ | Production build: `next build`, `vite build`, `nuxt build`, `astro build` | The build stops with an error. |
109
+ | Dev: `next dev`, `vite dev`, `nuxt dev`, `astro dev` | A warning, and the server fetches the policy at runtime. |
110
+
111
+ Set `onBuildError` to use one behaviour for both. `'fail'` stops dev too.
112
+ `'runtime'` lets a production build finish, and the server fetches the policy
113
+ at runtime. The `C15T_ON_BUILD_ERROR` environment variable overrides the
114
+ option, so you can deploy during a backend outage without a code change:
115
+
116
+ ```sh
117
+ C15T_ON_BUILD_ERROR=runtime npm run build
118
+ ```
119
+
120
+ Turborepo's strict environment mode hides undeclared variables from tasks, so
121
+ list `C15T_ON_BUILD_ERROR` in the build task's `passThroughEnv` there.
122
+
123
+ The build skips the fetch, without an error, when it can't use a snapshot,
124
+ for example when the backend URL is relative. With `onBuildError: 'fail'`, a
125
+ relative URL stops the build. [Consent modes](./modes.md#what-happens-when-the-download-fails)
126
+ lists every case.
127
+
128
+ For deployments that need policy updates without a rebuild, choose a runtime
129
+ path:
130
+
131
+ * **Cached manifest**, `manifest({ source: 'runtime' })`.
132
+ Your server downloads the project's public policy file, caches it, and
133
+ resolves each visitor locally. Consent saves still go to the backend.
134
+ * **`/init`**, `hosted()`. Each visit asks the backend to resolve the visitor.
135
+ Simpler, but every page load makes a request to the backend.
136
+
137
+ [Data fetching](./data-fetching.md) compares them in detail,
138
+ including proxies and custom transports.
@@ -0,0 +1,84 @@
1
+ ---
2
+ title: Consent categories
3
+ description: Assign scripts, embeds and features to c15t consent categories, and
4
+ understand which categories the preference dialog shows.
5
+ group: concepts
6
+ ---
7
+
8
+ ## Choose categories by purpose
9
+
10
+ | Category | Purpose |
11
+ | --------------- | ---------------------------------------------- |
12
+ | `necessary` | Functionality required for the site to operate |
13
+ | `functionality` | Optional features such as support widgets |
14
+ | `measurement` | Analytics and usage measurement |
15
+ | `experience` | Optional personalization |
16
+ | `marketing` | Advertising and marketing |
17
+
18
+ `necessary` is always permitted. Assign categories based on what an integration
19
+ does in your application; renaming analytics to necessary does not change its
20
+ purpose.
21
+
22
+ c15t discovers categories from registered scripts, network rules, React `ConsentGate`
23
+ components, and iframes with `data-category` handled by the iframe blocker.
24
+ The browser client also discovers inert scripts tagged with `data-c15t-category`.
25
+ Compound script and network conditions contribute every category they reference.
26
+
27
+ Discovered categories are added to `consentCategories`, when supplied. The dialog
28
+ always includes Necessary and offers the optional categories in that combined
29
+ set that are also in the resolved policy scope. Choice completion uses the same
30
+ set.
31
+
32
+ If neither configuration nor integrations supply categories, the result depends
33
+ on the policy's `scopeMode`:
34
+
35
+ * Under a permissive policy, the dialog lists only Necessary. The banner still
36
+ appears when the policy prompts for a choice. Accept All, Reject All and Save
37
+ each record that the visitor saw it, send a consent receipt for Necessary
38
+ alone in hosted and manifest modes, and keep the banner dismissed after
39
+ reload. The acknowledgement expires with the policy's choice validity and
40
+ after a policy change, like a choice would. A stored choice that is still
41
+ valid under the current policy also counts as an acknowledgement.
42
+ * Under a strict policy, or an IAB TCF policy, the dialog uses the full policy
43
+ scope. TCF consent is given per purpose and recorded in the TC string.
44
+
45
+ A category declared after that acknowledgement, such as a newly registered
46
+ script or a discovered iframe, needs a choice, so the banner asks again.
47
+
48
+ Categories discovered later are added immediately and retained until the provider
49
+ or runtime is recreated. Adding a category can require a new choice; removing a
50
+ script or unmounting a frame does not remove its category or erase consent.
51
+ Discovery does not grant consent or change backend permission restrictions.
52
+
53
+ ## Respect policy scope
54
+
55
+ A strict scope denies categories outside the rule. A permissive scope can allow
56
+ out-of-scope categories unless another restriction applies. When a rule selects
57
+ only some optional categories, set `scopeMode` explicitly. An omitted scope,
58
+ `['*']`, or a list containing only `necessary` expands to the default optional
59
+ categories; a necessary-only list is not a shortcut for disabling all tracking.
60
+
61
+ For example, a backend rule with `categories: ['necessary']` and scripts assigned
62
+ to `marketing` and `measurement` displays Necessary, Marketing, and Analytics,
63
+ even without `consentCategories`. Accept All records both optional choices and
64
+ keeps the banner dismissed after reload. An explicit list of
65
+ `['necessary', 'measurement']` with no other integrations displays Necessary and
66
+ Analytics.
67
+
68
+ Hidden optional categories do not need a choice to dismiss the banner. Their
69
+ permissions still follow the backend policy, so hidden opt-in categories remain
70
+ denied without a valid grant. React providers enable DOM iframe blocking and
71
+ discovery by default. The blocker loads when the first iframe with
72
+ `data-category` or `data-vendor` is on the page, so pages without one never
73
+ download it; until it runs, such an iframe that arrives with a `src` consent
74
+ does not allow is paused. Write gated iframes with `data-src`, not `src`. The
75
+ browser requests a `src` as soon as the iframe is in the document, before the
76
+ blocker can pause it, whether the iframe comes from the server HTML or a
77
+ client render. Set
78
+ `iframeBlocker: false` to disable it, or
79
+ `iframeBlocker: { disableAutomaticBlocking: true }` to scan manually with
80
+ `useIframeBlocker({ disableAutomaticBlocking: true }).processAllIframes()`.
81
+
82
+ Use effective permissions to gate work and explicit choices to inspect what the
83
+ visitor confirmed. Read [how consent works](./how-consent-works.md#a-permission-is-not-a-recorded-choice) for that
84
+ distinction and [policies](./policies.md) for v3 policy configuration.
@@ -0,0 +1,421 @@
1
+ ---
2
+ title: Consent state reference
3
+ description: How c15t saves choices, gates IAB vendors, hydrates server records
4
+ and keeps browser tabs and storage in step.
5
+ group: concepts
6
+ ---
7
+
8
+ This reference covers the edge cases behind the model in
9
+ [how consent works](./how-consent-works.md). You need it when you
10
+ build custom consent UI, run c15t across subdomains, or debug a choice that
11
+ changes between tabs.
12
+
13
+ ## Gate IAB vendors on the TC string
14
+
15
+ Under an IAB policy, a script, network rule or iframe that names a `vendorId`
16
+ or IAB purposes is an IAB target. It runs only while a confirmed TC string
17
+ grants what it declares: purpose and vendor consent for `iabPurposes`, purpose
18
+ and vendor legitimate interest for `iabLegIntPurposes`, and opt-ins for
19
+ `iabSpecialFeatures`. Every category it names must also be free of
20
+ restrictions, so GPC or strict scope blocks it.
21
+
22
+ A refused category is the one exception. It does not block an IAB target that
23
+ processes only on legitimate interest, after publisher restrictions, because
24
+ the TCF lets that processing run without consent. The visitor's control for it
25
+ is the objection, which the legitimate interest signals record. The category
26
+ itself stays refused: its `effectivePermissions` entry is `false`, and targets
27
+ that name only the category, or that also declare a consent purpose or special
28
+ feature, stay blocked.
29
+
30
+ ## Categories under an IAB policy
31
+
32
+ Under an IAB policy the visitor chooses TCF purposes, and c15t derives each
33
+ category from them when the choice is saved. A category is granted when the
34
+ visitor consented to every purpose in it that a listed vendor processes on
35
+ consent:
36
+
37
+ | Category | TCF purposes |
38
+ | --------------- | ------------ |
39
+ | `marketing` | 2, 3, 4 |
40
+ | `measurement` | 7, 8, 9 |
41
+ | `experience` | 5, 6 |
42
+ | `functionality` | 10, 11 |
43
+
44
+ `necessary` is always granted. Purpose 1, device storage, decides no category.
45
+
46
+ Accept All consents only to the purposes and special features that the vendors
47
+ in your list declare, because the TCF Policies allow no consent signal for a
48
+ purpose the visitor was not shown. A category whose purposes no listed vendor
49
+ processes on consent therefore stays denied after Accept All, and so does every
50
+ script, iframe or network rule gated on it. With a vendor list whose vendors
51
+ declare only purposes 1 to 4 and 7, for example, `experience` and
52
+ `functionality` stay denied.
53
+
54
+ If your own scripts depend on such a category, do one of these:
55
+
56
+ * Declare the processing. Add a custom vendor for it, with the purposes it
57
+ uses, through the `customVendors` option of your IAB setup. The preference
58
+ centre then lists those purposes, and Accept All and the purpose switches
59
+ can grant the category.
60
+ * Gate the script on a category that your listed vendors' purposes cover, or on
61
+ `necessary` if it needs no consent.
62
+
63
+ Check `effectivePermissions` after Accept All on a fresh visit to see which
64
+ categories your vendor list leaves denied.
65
+
66
+ ## When a choice is saved
67
+
68
+ A save records the choice in the browser first and sends it to the backend
69
+ afterwards. The stock banner, preference dialog and IAB surfaces in every
70
+ framework adapter, and the `acceptAll()`, `rejectAll()` and `save()` methods
71
+ of the browser and Astro clients, close without waiting for the backend. In
72
+ order:
73
+
74
+ 1. In the click task, the explicit choice and effective permissions change,
75
+ `onChoiceRecorded` and `onPermissionsChanged` run, gated scripts, iframes
76
+ and network rules follow the new permissions, and the surface leaves the
77
+ active state. Its exit animation still plays.
78
+ 2. In the next task, the choice is written to the cookie and localStorage.
79
+ 3. After that write is queued, the request to the backend starts.
80
+ 4. When the request settles, the kernel emits `command:save:completed`. The
81
+ promise returned by `kernel.commands.save()`, React's `performAction()` and
82
+ Svelte's `saveConsents()` resolves or rejects only then.
83
+ 5. If the save turned off a category or vendor that was granted, the page
84
+ reloads in the next task, after `onBeforeConsentRevocationReload` runs.
85
+ Removing a script cannot stop code that already ran, so the reload starts a
86
+ page with only permitted code. With several saves in flight, it waits for
87
+ the last one. Set `reloadOnConsentRevoked: false` to handle revocation
88
+ yourself.
89
+
90
+ A failed request does not reopen the surface or roll the choice back. The
91
+ kernel emits `command:error`, which reaches the `onError` callback in adapters
92
+ that accept one, and queues the payload in localStorage. Where localStorage is
93
+ unavailable, the queue lives in memory until the page unloads. The queue is
94
+ replayed after the next successful initialization and when the browser comes
95
+ back online, up to 10 attempts over 7 days. A replay carries the original action
96
+ time and policy snapshot token, so the backend records when the visitor
97
+ decided, and a duplicate submission resolves to the same consent record. A
98
+ backend that signs policy snapshot tokens rejects a replay made after the
99
+ token expires, which is 30 minutes by default for the self-hosted backend. A
100
+ save still queued by then is recorded only in the browser.
101
+
102
+ A replay that fails and stays queued emits `save:replayed` with `ok: false` but
103
+ not `command:error`, since the save already reported its first failure. A save
104
+ that leaves the queue without the backend recording it, because it ran out of
105
+ attempts, waited more than 7 days or was refused, emits `command:error` once.
106
+ Development builds also log a `[c15t]` console warning for each of these.
107
+
108
+ IAB surfaces close in the click task too, but an IAB choice is recorded only
109
+ after its TC string is encoded, which can wait for the TCF library to load.
110
+ If that local step records nothing, for example because the vendor list
111
+ failed to load, the surface comes back so the visitor can try again.
112
+
113
+ Every adapter leaves the same surface after a save: the banner while the
114
+ policy still owes a choice or a notice, and nothing once no prompt is owed,
115
+ while the policy is still loading, or after it failed to resolve. A banner
116
+ the visitor reopened closes too. A save that records nothing new, such as an
117
+ unchanged selection, closes the surface when it resolves successfully. Opening
118
+ or closing a surface, or starting another save, before then leaves the surface
119
+ as the visitor set it.
120
+
121
+ Closing the dialog without saving, with Escape or the adapter's close call,
122
+ leaves the same surface a save would. A visitor who opens preferences from the
123
+ banner and backs out still owes a choice, so the banner comes back. Once a
124
+ choice is recorded, closing the dialog leaves nothing open.
125
+
126
+ ## Preserve records during hydration
127
+
128
+ Server helpers return records with policy information and evaluation time.
129
+ Forward that configuration intact. Copying an allowed category into a receipt
130
+ would invent a grant and lose its original confirmation time.
131
+
132
+ Valid v2 records can be read without a startup rewrite. The next explicit action
133
+ writes the v3 format. The upgrade guides for
134
+ [Next.js](https://v3.c15t.com/docs/frameworks/next/upgrade-v3#keep-visitors-existing-choices),
135
+ [React](../frameworks/react/upgrade-v3.md#keep-visitors-existing-choices) and
136
+ [JavaScript](https://v3.c15t.com/docs/frameworks/javascript/upgrade-v3#keep-visitors-existing-choices)
137
+ cover how v3 treats v2 records.
138
+
139
+ ## Keep open tabs in step
140
+
141
+ Tabs and windows on the same origin (scheme, host and port) share the consent
142
+ cookie and localStorage. When a visitor rejects in one tab, every other open tab
143
+ on that origin applies the rejection without a reload. Scripts and features
144
+ gated on `effectivePermissions` lose permission, subscribers are notified once
145
+ and `onPermissionsChanged` fires. Clearing records in one tab returns the others
146
+ to the active policy's defaults: an opt-in policy denies optional categories and
147
+ shows the prompt again.
148
+
149
+ Tabs on another subdomain that shares the consent cookie do not share
150
+ localStorage, so the browser sends them no `storage` event. They pick up the
151
+ change on their next focus or visibility change, or when you reconcile
152
+ yourself as described in
153
+ [Reconcile yourself or turn it off](#reconcile-yourself-or-turn-it-off).
154
+
155
+ The `storage` event only exists for localStorage. When localStorage is
156
+ unavailable (blocked by the browser, a sandboxed frame or a privacy mode) and
157
+ c15t stores in the cookie alone, another tab's change arrives only on the next
158
+ focus or visibility change, or when you call `runtime.reconcileStorage()` or
159
+ `persistence.reconcile()`. c15t does not poll the cookie or use a
160
+ `BroadcastChannel` for this.
161
+
162
+ Browser persistence reads stored records again at these moments:
163
+
164
+ | Moment | What happens |
165
+ | ------------------------------------------------------------------------ | --------------------------------- |
166
+ | Another tab or window on the same origin changes a c15t localStorage key | Reconciles on the `storage` event |
167
+ | The page becomes visible again | Reconciles on `visibilitychange` |
168
+ | The window regains focus | Reconciles on `focus` |
169
+ | The network reconnects | No storage read |
170
+
171
+ Several triggers in quick succession run one reconciliation in a later task.
172
+ Reconnecting does not read storage, because going online changes nothing in
173
+ browser storage. On reconnect the kernel retries saves the backend did not
174
+ accept and a failed initialization. Save retries do not write the cookie or
175
+ localStorage. A write that follows initialization obeys the rules in
176
+ [Ordering with pending writes](#ordering-with-pending-writes).
177
+
178
+ Every adapter that mounts browser persistence does this: the React, Next.js and
179
+ TanStack Start providers, Vue, Svelte, Astro, the script tag and
180
+ `createConsentRuntime`. With `persistence: false` nothing is stored or read.
181
+
182
+ ### What a reconciliation applies
183
+
184
+ Stored records are merged into the ones in memory:
185
+
186
+ * Category decisions merge per category. Each category keeps the decision with
187
+ the newer confirmation time, so a tab that only changed marketing never
188
+ reverts another tab's newer measurement decision.
189
+ * The notice dismissal and the vendor record are single decisions. A stored one
190
+ at least as new as the one in memory replaces it; an older one is ignored.
191
+ * When two tabs record decisions in the same millisecond, the one stored first
192
+ wins in both tabs.
193
+ * Tabs keep one subject. A stored subject is considered only when the record
194
+ that carries it changed, never on focus alone. It replaces a subject the tab
195
+ generated on its first save or copied from storage, so a tab that opened
196
+ before another tab stored a subject joins it. A subject id the server
197
+ resolved (at init, from a prefetch or in a save response) is replaced only by
198
+ a strictly newer stored choice, and an identity set with `identify()` is
199
+ never replaced.
200
+ * Under an IAB policy, the TC string follows the reconciled choice. See
201
+ [How the TC string is reconciled](#how-the-tc-string-is-reconciled).
202
+ * A record removed from readable storage since this tab last saw it present is
203
+ cleared, and the active policy decides again. A record this tab never saw in
204
+ storage, such as a receipt merged from the server after `identify()` or a
205
+ choice seeded while storage was blocked, stays.
206
+ * Blocked storage, or bytes that do not decode, change nothing. A failed read
207
+ never grants a category.
208
+
209
+ Expiry is not decided here. The applied records are evaluated at the time of the
210
+ read, the same way as at startup.
211
+
212
+ ### How the TC string is reconciled
213
+
214
+ Under an IAB policy, a tab adopts the TC string another tab stored, unless that
215
+ TC string conflicts with the reconciled choice. A conflicting TC string is
216
+ withdrawn, and `__tcfapi` reports no consent until the next save. Vendors never
217
+ receive a stale TC string: c15t withdraws it, or holds it back, before
218
+ `__tcfapi` publishes.
219
+
220
+ 1. The `@c15t/iab` module loads the TC string the other tab stored, with its
221
+ purpose, vendor and special-feature selections, so `__tcfapi` and the
222
+ preference controls show the choice now in force. Selections the visitor
223
+ changed in this tab without saving are kept.
224
+ 2. A missing or older stored TC string leaves the current one in place, unless
225
+ the current one conflicts with the reconciled choice. Then it is withdrawn.
226
+ 3. A category denied after the TC string was saved conflicts if the TC string
227
+ grants any of its purposes. A partial selection saved through IAB, which
228
+ records its category as denied because not every purpose is granted, keeps
229
+ its TC string.
230
+ 4. A TC string confirmed before the choice's newest decision no longer
231
+ describes it and is withdrawn, unless a newer receipt replaces it. That
232
+ receipt is adopted even when its TC string is identical, because TC strings
233
+ round their time to the day and custom-vendor selections live only in the
234
+ receipt. The receipt's expiry then applies.
235
+ 5. The TC string and its receipt (`euconsent-v2`, `c15t-iab-authority-v1`)
236
+ belong to one origin. After a save on a sibling subdomain that shares the
237
+ consent cookie, this subdomain reports no consent through `__tcfapi` until
238
+ it saves again.
239
+ 6. A tab reloads the TC string when another tab on the same origin stores a
240
+ new one. This covers a save in the same millisecond, or one that changed
241
+ only vendors.
242
+ 7. When two tabs save in the same millisecond with different selections, the
243
+ more restrictive TC string wins in both, so a revoked vendor is never
244
+ advertised again. If each grants something the other denies, neither is
245
+ published until the next save, and the stored receipt is removed so a page
246
+ opened later does not restore it.
247
+ 8. When another tab removes the receipt or clears localStorage, this tab
248
+ withdraws its TC string too, because a page opened now would find none. A
249
+ receipt this tab was still decoding is not installed.
250
+ 9. The removal after a tie checks that the stored receipt is the one it read.
251
+ localStorage has no conditional removal, so a receipt another tab stored a
252
+ moment earlier can still be removed. Every tab then withdraws its TC string
253
+ until the next save, so the race only ever withholds consent.
254
+
255
+ ### Clearing records across tabs
256
+
257
+ `clearRecords()` also drops every save queued for replay, with or without
258
+ browser persistence, so nothing the visitor decided before the clear reaches
259
+ the backend afterwards.
260
+
261
+ `clearRecords()` removes every record and then stores the time of the clear,
262
+ the clear epoch, under its own key: `c15t-epoch` in localStorage and a cookie
263
+ of the same name (`<storageKey>-epoch` with a custom `storageKey`). Clearing
264
+ never removes it. Every consent record written afterwards also records the
265
+ epoch it was written under.
266
+
267
+ A decision confirmed before the epoch was made before the clear, so it is void
268
+ wherever it turns up. A decision stamped in the very millisecond of the clear
269
+ counts only if it comes from a tab that had already seen that clear, so a tab's
270
+ own choice right after its own clear stands while another tab's decision in the
271
+ same millisecond does not. A stored record left with no decisions after the
272
+ clear is void too, subject included.
273
+
274
+ * A tab that reconciles only after another tab cleared and saved again drops
275
+ its pre-clear decisions instead of merging them back. Its subject from before
276
+ the clear is dropped too.
277
+ * A tab that missed the clear writes only decisions it made after it. Its queued
278
+ write of an earlier decision is discarded, so it cannot bring back a cleared
279
+ record.
280
+ * Browser hydration and server reads (`readStoredRecordsFromCookieHeader`, used
281
+ by the Next.js, TanStack Start, Nuxt, SvelteKit and Astro helpers) apply the
282
+ same rule, so a server render agrees with the browser.
283
+
284
+ Records from before any clear, including v2 and legacy records, read as epoch 0
285
+ and are unaffected. A corrupt epoch, or one that cannot be read, also reads as
286
+ 0: it voids nothing, so a failed read never grants a category. A consent record
287
+ whose own epoch field is corrupt is kept, and only its epoch is ignored.
288
+
289
+ Each clear moves the epoch forward, even when the device clock went back, so a
290
+ later clear never lets earlier decisions back in. Two clears in the same
291
+ millisecond therefore leave the epoch a millisecond ahead, and a decision saved
292
+ in that millisecond is void. An epoch up to one hour ahead
293
+ of the clock is kept, as it is when the clock was set back after a clear. Until
294
+ the clock catches up, decisions saved in that window are void too. An epoch more
295
+ than an hour ahead is treated as corrupt and reads as 0, so a clear never writes
296
+ one: after the clock went back more than an hour, the new epoch is capped at an
297
+ hour ahead of the clock.
298
+
299
+ That cap is a known limit. A cleared record carries times from before the clock
300
+ went back, and a runtime that missed the clear can write those times back. The
301
+ capped epoch is lower than them, so once the clock has recovered, such a
302
+ decision counts again. Leaving the epoch uncapped does not help: every tab
303
+ whose clock is still behind reads it as corrupt, which voids nothing. Times
304
+ alone cannot order a clear against decisions stamped by a clock that went back
305
+ more than an hour.
306
+
307
+ This changes the stored format. After a clear, the consent cookie carries
308
+ `&e=<time>` (16 bytes) and the localStorage record an `epoch` field (22 bytes),
309
+ and the epoch cookie itself holds a 13-digit time. Visitors who never cleared
310
+ their records store exactly what they did before. An older c15t build rejects
311
+ both the cookie and the localStorage record once they carry the epoch, so a page
312
+ still running one treats the visitor as undecided. Under an opt-out policy that
313
+ page grants optional categories by default until a new choice is saved, and the
314
+ configured prompt may appear again. Deploy the new build to every page of the
315
+ site before visitors can clear their records.
316
+
317
+ ### When the cookie and localStorage disagree
318
+
319
+ When both copies hold a decision for a category from the same millisecond and
320
+ the two conflict, the denial wins.
321
+
322
+ The subject and IAB metadata come from the cookie. A server response, such as
323
+ server-side consent restoration, and a sibling subdomain sharing the cookie
324
+ with `crossSubdomain` can both rewrite it without touching this origin's
325
+ localStorage, so the local copy can be the older one. The local copy's subject
326
+ is used only when this browser's last consent write reached localStorage but
327
+ not the cookie, for example because the cookie grew past the size limit, and
328
+ the cookie has not changed since. Such a write stores the cookie as it stood
329
+ under `<storageKey>-cookie-miss` in localStorage; the next write that reaches
330
+ the cookie, or a clear, removes it. When localStorage rejects a write that the
331
+ cookie takes, for example because storage is full, the older localStorage copy
332
+ is removed.
333
+
334
+ When a server render seeded the page from the consent cookie
335
+ (`skipHydration`), that seed stays authoritative. A denial that reached only
336
+ localStorage is still applied on top of it when the page mounts, since it can
337
+ only restrict, if it is newer than the seeded decision or from the same
338
+ millisecond as a seeded grant. A stored grant is not.
339
+
340
+ The notice dismissal and vendor denials are stored twice as well. A vendor
341
+ list in localStorage at least as new as the cookie's adds its denials but never
342
+ lifts one the cookie holds; a copy confirmed before the last clear is ignored, so its
343
+ denials never come back. The newer notice dismissal applies; it still only hides a
344
+ notice with the fingerprint it names. The consent record follows the rules
345
+ below.
346
+
347
+ The consent record is stored twice: as a cookie, which a server render reads,
348
+ and in localStorage. The cookie is authoritative. A well-formed cookie wins
349
+ even when it has expired, so a local copy can never bring back a grant the
350
+ cookie no longer carries.
351
+
352
+ A browser can still drop a cookie write, for example when the record grows past
353
+ the cookie size limit, while localStorage takes it. A denial in the local copy
354
+ that is newer than the cookie's decision for that category is therefore applied
355
+ on top of the cookie. A newer local grant is not, so a dropped cookie write only
356
+ ever leaves the visitor with less permission. A server render sees only the
357
+ cookie; after a dropped write that carried a denial, the browser is the stricter
358
+ of the two.
359
+
360
+ When the two copies were written under different clear epochs, each loses its
361
+ decisions from before the later epoch first, and the same rule applies to what
362
+ remains. A later epoch in the local copy never lets its grant replace a cookie
363
+ denial. The subject and IAB metadata come from the copy written under the later
364
+ epoch; a record written before the clear in force keeps its later decisions but
365
+ no subject.
366
+
367
+ A server render cannot know about a clear that never reached a cookie. If the
368
+ page could write localStorage but its cookie writes failed during
369
+ `clearRecords()` (cookies blocked for the page, or a cookie setter that
370
+ throws), the removal of the consent cookie failed too, and so did the epoch
371
+ cookie. The browser then applies the clear from localStorage, while a server
372
+ render still reads the old consent cookie until the next successful cookie
373
+ write. The same applies to a consent cookie set with a different `domain` than
374
+ the current `storageConfig` uses, which the clear cannot remove.
375
+
376
+ ### Ordering with pending writes
377
+
378
+ A tab writes its own choices in a later task, not during the click. Before it
379
+ reads storage, it lands its own queued writes, so a reconciliation never undoes
380
+ the visitor's latest action in that tab. A queued write follows the same rules
381
+ as a read: it stores the per-category merge of its choice and the stored one,
382
+ and never replaces a newer notice or vendor record. It keeps the stored subject
383
+ unless this tab identified a different user.
384
+ When a slow save response returns a server subject id, the tab adds it only to
385
+ the record it wrote; it does not recreate records another tab cleared or
386
+ overwrite another tab's newer choice.
387
+
388
+ Two tabs that write at the same moment can both read storage before either
389
+ writes, and the later write can then drop the other tab's category decision.
390
+ The tab whose decision was dropped still holds it, and on its next
391
+ reconciliation it writes it back, merged with what storage holds. It does so
392
+ only for decisions it actually stored itself, never for one a clear voided or
393
+ another tab replaced with a newer one.
394
+
395
+ If another tab's change reaches this tab while one of its saves is pending, the
396
+ save depends on how far it got. A save not yet sent is dropped. A request
397
+ already sent still reaches the backend, which may record it; this tab ignores
398
+ the response, so it queues no retry and applies no subject id from it. A save
399
+ already queued for retry keeps its original decision time.
400
+
401
+ ### Reconcile yourself or turn it off
402
+
403
+ Browsers send no event for a change made in the same document, or for a cookie
404
+ rewritten without a localStorage change, such as a `Set-Cookie` response header
405
+ or another subdomain sharing the cookie. The next focus or visibility change
406
+ picks it up. To apply it at once, call the method yourself:
407
+
408
+ ```ts
409
+ // Runtime owners: returns true when any record changed.
410
+ runtime.reconcileStorage();
411
+
412
+ // Kernel owners with createPersistence from c15t/modules/persistence:
413
+ persistence.reconcile();
414
+ ```
415
+
416
+ To keep storage but stop automatic reconciliation, pass `sync: false` in the
417
+ persistence options, for example `createConsentRuntime({ persistence: { sync:
418
+ false } })` or `createPersistence({ kernel, sync: false })`. The manual methods
419
+ still work.
420
+
421
+ `dispose()` removes the listeners and cancels a scheduled reconciliation.