@danieljvdm/dev-kit 1.0.2 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (329) hide show
  1. package/README.md +6 -6
  2. package/package.json +1 -1
  3. package/skill-sources.jsonc +2 -33
  4. package/skill-sources.lock.json +15 -117
  5. package/skills/animate/LICENSE +21 -0
  6. package/skills/animate/NOTICE +10 -0
  7. package/skills/animate/SKILL.md +34 -0
  8. package/skills/animate/references/gestures.md +27 -0
  9. package/skills/animate/references/glossary.md +136 -0
  10. package/skills/animate/references/implementation.md +22 -0
  11. package/skills/animate/references/opportunities.md +18 -0
  12. package/skills/animate/references/principles.md +48 -0
  13. package/skills/animate/references/review.md +19 -0
  14. package/skills/cloudflare-workers/LICENSE +202 -0
  15. package/skills/cloudflare-workers/NOTICE +9 -0
  16. package/skills/cloudflare-workers/SKILL.md +26 -0
  17. package/skills/cloudflare-workers/references/durable-objects.md +68 -0
  18. package/skills/cloudflare-workers/references/sandbox.md +48 -0
  19. package/skills/cloudflare-workers/references/workers.md +63 -0
  20. package/skills/cloudflare-workers/references/wrangler.md +56 -0
  21. package/skills/design-ui/LICENSE +21 -0
  22. package/skills/design-ui/NOTICE +8 -0
  23. package/skills/design-ui/SKILL.md +29 -0
  24. package/skills/design-ui/references/appearance.md +22 -0
  25. package/skills/design-ui/references/components.md +33 -0
  26. package/skills/design-ui/references/exploration.md +50 -0
  27. package/skills/design-ui/references/libraries.md +39 -0
  28. package/skills/design-ui/references/themes.md +23 -0
  29. package/skills/dev-kit/SKILL.md +19 -38
  30. package/skills/dev-kit/references/cloudflare-worker-api.md +1 -1
  31. package/skills/dev-kit/references/skills.md +2 -2
  32. package/skills/effect-development/SKILL.md +27 -0
  33. package/skills/effect-development/agents/openai.yaml +4 -0
  34. package/skills/effect-development/references/api/index.md +32 -0
  35. package/skills/effect-development/references/api/schema-codecs.md +15 -0
  36. package/skills/{build-effect-apis/references → effect-development/references/api}/verification.md +10 -5
  37. package/skills/{effect-architecture-audit/SKILL.md → effect-development/references/architecture/index.md} +1 -6
  38. package/skills/{effect-atom-state/references → effect-development/references/atom}/effect-atom-client.md +8 -6
  39. package/skills/{effect-atom-state/references → effect-development/references/atom}/effect-atom-testing.md +7 -2
  40. package/skills/effect-development/references/atom/index.md +40 -0
  41. package/skills/{build-effect-clis/references → effect-development/references/cli}/entrypoints-and-testing.md +6 -2
  42. package/skills/effect-development/references/cli/index.md +39 -0
  43. package/skills/effect-development/references/setup.md +15 -0
  44. package/skills/open-pull-request/SKILL.md +10 -70
  45. package/skills/open-pull-request/references/evidence.md +22 -0
  46. package/skills/open-pull-request/references/explanation.md +21 -0
  47. package/skills/open-pull-request/references/publication.md +21 -0
  48. package/skills/sentry/LICENSE +202 -0
  49. package/skills/sentry/LICENSE-UPSTREAM +21 -0
  50. package/skills/sentry/NOTICE +14 -0
  51. package/skills/sentry/SKILL.md +33 -0
  52. package/skills/sentry/references/alert-payloads.md +105 -0
  53. package/skills/sentry/references/alerts.md +17 -0
  54. package/skills/sentry/references/artifacts.md +25 -0
  55. package/skills/sentry/references/auth-token.md +65 -0
  56. package/skills/sentry/references/concepts/ai-monitoring.md +131 -0
  57. package/skills/sentry/references/concepts/choosing-a-signal.md +78 -0
  58. package/skills/sentry/references/concepts/crons.md +50 -0
  59. package/skills/sentry/references/concepts/data-scrubbing.md +43 -0
  60. package/skills/sentry/references/concepts/errors.md +40 -0
  61. package/skills/sentry/references/concepts/logging.md +85 -0
  62. package/skills/sentry/references/concepts/metrics.md +49 -0
  63. package/skills/sentry/references/concepts/monitors.md +78 -0
  64. package/skills/sentry/references/concepts/profiling.md +38 -0
  65. package/skills/sentry/references/concepts/reduce-volume.md +49 -0
  66. package/skills/sentry/references/concepts/releases.md +43 -0
  67. package/skills/sentry/references/concepts/session-replay.md +35 -0
  68. package/skills/sentry/references/concepts/tracing.md +54 -0
  69. package/skills/sentry/references/concepts/user-feedback.md +36 -0
  70. package/skills/sentry/references/debug-artifacts/android.md +68 -0
  71. package/skills/sentry/references/debug-artifacts/apple.md +96 -0
  72. package/skills/sentry/references/debug-artifacts/dotnet.md +70 -0
  73. package/skills/sentry/references/debug-artifacts/flutter.md +65 -0
  74. package/skills/sentry/references/debug-artifacts/index.md +75 -0
  75. package/skills/sentry/references/debug-artifacts/javascript.md +94 -0
  76. package/skills/sentry/references/debug-artifacts/matching.md +73 -0
  77. package/skills/sentry/references/debug-artifacts/react-native.md +75 -0
  78. package/skills/sentry/references/first-error-setup.md +25 -0
  79. package/skills/sentry/references/instrumentation.md +30 -0
  80. package/skills/sentry/references/issues.md +32 -0
  81. package/skills/sentry/references/new-project.md +17 -0
  82. package/skills/sentry/references/otel-exporter.md +30 -0
  83. package/skills/sentry/references/release-workflow.md +31 -0
  84. package/skills/sentry/references/releases/ci-pipeline.md +214 -0
  85. package/skills/sentry/references/releases/index.md +93 -0
  86. package/skills/sentry/references/releases/suspect-commits.md +155 -0
  87. package/skills/sentry/references/releases/tagging.md +131 -0
  88. package/skills/sentry/references/releases/troubleshooting.md +67 -0
  89. package/skills/sentry/references/sdks/android/crons.md +305 -0
  90. package/skills/sentry/references/sdks/android/error-monitoring.md +800 -0
  91. package/skills/sentry/references/sdks/android/index.md +748 -0
  92. package/skills/sentry/references/sdks/android/integrations.md +48 -0
  93. package/skills/sentry/references/sdks/android/logging.md +374 -0
  94. package/skills/sentry/references/sdks/android/metrics.md +288 -0
  95. package/skills/sentry/references/sdks/android/profiling.md +397 -0
  96. package/skills/sentry/references/sdks/android/session-replay.md +406 -0
  97. package/skills/sentry/references/sdks/android/tracing.md +861 -0
  98. package/skills/sentry/references/sdks/browser/error-monitoring.md +861 -0
  99. package/skills/sentry/references/sdks/browser/index.md +714 -0
  100. package/skills/sentry/references/sdks/browser/logging.md +315 -0
  101. package/skills/sentry/references/sdks/browser/profiling.md +236 -0
  102. package/skills/sentry/references/sdks/browser/session-replay.md +586 -0
  103. package/skills/sentry/references/sdks/browser/tracing.md +561 -0
  104. package/skills/sentry/references/sdks/browser/user-feedback.md +354 -0
  105. package/skills/sentry/references/sdks/cloudflare/ai-monitoring.md +661 -0
  106. package/skills/sentry/references/sdks/cloudflare/crons.md +231 -0
  107. package/skills/sentry/references/sdks/cloudflare/durable-objects.md +381 -0
  108. package/skills/sentry/references/sdks/cloudflare/error-monitoring.md +329 -0
  109. package/skills/sentry/references/sdks/cloudflare/index.md +698 -0
  110. package/skills/sentry/references/sdks/cloudflare/logging.md +169 -0
  111. package/skills/sentry/references/sdks/cloudflare/nodejs-compat.md +134 -0
  112. package/skills/sentry/references/sdks/cloudflare/tracing.md +475 -0
  113. package/skills/sentry/references/sdks/cocoa/error-monitoring.md +394 -0
  114. package/skills/sentry/references/sdks/cocoa/index.md +497 -0
  115. package/skills/sentry/references/sdks/cocoa/logging.md +209 -0
  116. package/skills/sentry/references/sdks/cocoa/metrics.md +136 -0
  117. package/skills/sentry/references/sdks/cocoa/profiling.md +223 -0
  118. package/skills/sentry/references/sdks/cocoa/session-replay.md +257 -0
  119. package/skills/sentry/references/sdks/cocoa/tracing.md +524 -0
  120. package/skills/sentry/references/sdks/cocoa/user-feedback.md +310 -0
  121. package/skills/sentry/references/sdks/dotnet/crons.md +465 -0
  122. package/skills/sentry/references/sdks/dotnet/error-monitoring.md +1100 -0
  123. package/skills/sentry/references/sdks/dotnet/index.md +614 -0
  124. package/skills/sentry/references/sdks/dotnet/logging.md +556 -0
  125. package/skills/sentry/references/sdks/dotnet/metrics.md +229 -0
  126. package/skills/sentry/references/sdks/dotnet/profiling.md +296 -0
  127. package/skills/sentry/references/sdks/dotnet/tracing.md +897 -0
  128. package/skills/sentry/references/sdks/elixir/crons.md +371 -0
  129. package/skills/sentry/references/sdks/elixir/error-monitoring.md +335 -0
  130. package/skills/sentry/references/sdks/elixir/index.md +383 -0
  131. package/skills/sentry/references/sdks/elixir/logging.md +239 -0
  132. package/skills/sentry/references/sdks/elixir/tracing.md +233 -0
  133. package/skills/sentry/references/sdks/flutter/ecosystem-integrations.md +70 -0
  134. package/skills/sentry/references/sdks/flutter/error-monitoring.md +312 -0
  135. package/skills/sentry/references/sdks/flutter/index.md +728 -0
  136. package/skills/sentry/references/sdks/flutter/logging.md +387 -0
  137. package/skills/sentry/references/sdks/flutter/metrics.md +310 -0
  138. package/skills/sentry/references/sdks/flutter/profiling.md +214 -0
  139. package/skills/sentry/references/sdks/flutter/session-replay.md +308 -0
  140. package/skills/sentry/references/sdks/flutter/tracing.md +502 -0
  141. package/skills/sentry/references/sdks/go/crons.md +270 -0
  142. package/skills/sentry/references/sdks/go/error-monitoring.md +361 -0
  143. package/skills/sentry/references/sdks/go/index.md +324 -0
  144. package/skills/sentry/references/sdks/go/logging.md +354 -0
  145. package/skills/sentry/references/sdks/go/metrics.md +273 -0
  146. package/skills/sentry/references/sdks/go/profiling.md +46 -0
  147. package/skills/sentry/references/sdks/go/tracing.md +366 -0
  148. package/skills/sentry/references/sdks/index.md +41 -0
  149. package/skills/sentry/references/sdks/nestjs/ai-monitoring.md +410 -0
  150. package/skills/sentry/references/sdks/nestjs/crons.md +268 -0
  151. package/skills/sentry/references/sdks/nestjs/error-monitoring.md +729 -0
  152. package/skills/sentry/references/sdks/nestjs/index.md +699 -0
  153. package/skills/sentry/references/sdks/nestjs/logging.md +174 -0
  154. package/skills/sentry/references/sdks/nestjs/metrics.md +243 -0
  155. package/skills/sentry/references/sdks/nestjs/profiling.md +127 -0
  156. package/skills/sentry/references/sdks/nestjs/tracing.md +762 -0
  157. package/skills/sentry/references/sdks/nextjs/ai-monitoring.md +494 -0
  158. package/skills/sentry/references/sdks/nextjs/crons.md +383 -0
  159. package/skills/sentry/references/sdks/nextjs/error-monitoring.md +974 -0
  160. package/skills/sentry/references/sdks/nextjs/index.md +491 -0
  161. package/skills/sentry/references/sdks/nextjs/logging.md +381 -0
  162. package/skills/sentry/references/sdks/nextjs/metrics.md +411 -0
  163. package/skills/sentry/references/sdks/nextjs/profiling.md +403 -0
  164. package/skills/sentry/references/sdks/nextjs/session-replay.md +462 -0
  165. package/skills/sentry/references/sdks/nextjs/tracing.md +628 -0
  166. package/skills/sentry/references/sdks/node/ai-monitoring.md +303 -0
  167. package/skills/sentry/references/sdks/node/crons.md +432 -0
  168. package/skills/sentry/references/sdks/node/error-monitoring.md +1125 -0
  169. package/skills/sentry/references/sdks/node/index.md +942 -0
  170. package/skills/sentry/references/sdks/node/logging.md +380 -0
  171. package/skills/sentry/references/sdks/node/metrics.md +278 -0
  172. package/skills/sentry/references/sdks/node/profiling.md +373 -0
  173. package/skills/sentry/references/sdks/node/tracing.md +878 -0
  174. package/skills/sentry/references/sdks/php/ai-monitoring.md +310 -0
  175. package/skills/sentry/references/sdks/php/crons.md +265 -0
  176. package/skills/sentry/references/sdks/php/error-monitoring.md +626 -0
  177. package/skills/sentry/references/sdks/php/index.md +362 -0
  178. package/skills/sentry/references/sdks/php/laravel.md +542 -0
  179. package/skills/sentry/references/sdks/php/logging.md +242 -0
  180. package/skills/sentry/references/sdks/php/metrics.md +193 -0
  181. package/skills/sentry/references/sdks/php/profiling.md +115 -0
  182. package/skills/sentry/references/sdks/php/symfony.md +552 -0
  183. package/skills/sentry/references/sdks/php/tracing.md +516 -0
  184. package/skills/sentry/references/sdks/python/ai-monitoring.md +351 -0
  185. package/skills/sentry/references/sdks/python/crons.md +218 -0
  186. package/skills/sentry/references/sdks/python/error-monitoring.md +292 -0
  187. package/skills/sentry/references/sdks/python/index.md +361 -0
  188. package/skills/sentry/references/sdks/python/logging.md +234 -0
  189. package/skills/sentry/references/sdks/python/metrics.md +154 -0
  190. package/skills/sentry/references/sdks/python/profiling.md +120 -0
  191. package/skills/sentry/references/sdks/python/tracing.md +364 -0
  192. package/skills/sentry/references/sdks/react/error-monitoring.md +1607 -0
  193. package/skills/sentry/references/sdks/react/index.md +492 -0
  194. package/skills/sentry/references/sdks/react/logging.md +364 -0
  195. package/skills/sentry/references/sdks/react/profiling.md +322 -0
  196. package/skills/sentry/references/sdks/react/react-features.md +1272 -0
  197. package/skills/sentry/references/sdks/react/session-replay.md +1193 -0
  198. package/skills/sentry/references/sdks/react/tracing.md +1415 -0
  199. package/skills/sentry/references/sdks/react-native/error-monitoring.md +1481 -0
  200. package/skills/sentry/references/sdks/react-native/expo-config-plugin.md +45 -0
  201. package/skills/sentry/references/sdks/react-native/index.md +1030 -0
  202. package/skills/sentry/references/sdks/react-native/logging.md +444 -0
  203. package/skills/sentry/references/sdks/react-native/profiling.md +386 -0
  204. package/skills/sentry/references/sdks/react-native/session-replay.md +704 -0
  205. package/skills/sentry/references/sdks/react-native/tracing.md +1086 -0
  206. package/skills/sentry/references/sdks/react-native/user-feedback.md +815 -0
  207. package/skills/sentry/references/sdks/react-router-framework/error-monitoring.md +127 -0
  208. package/skills/sentry/references/sdks/react-router-framework/index.md +411 -0
  209. package/skills/sentry/references/sdks/react-router-framework/logging.md +66 -0
  210. package/skills/sentry/references/sdks/react-router-framework/profiling.md +57 -0
  211. package/skills/sentry/references/sdks/react-router-framework/react-router-framework-features.md +110 -0
  212. package/skills/sentry/references/sdks/react-router-framework/session-replay.md +67 -0
  213. package/skills/sentry/references/sdks/react-router-framework/tracing.md +96 -0
  214. package/skills/sentry/references/sdks/react-router-framework/user-feedback.md +71 -0
  215. package/skills/sentry/references/sdks/ruby/crons.md +184 -0
  216. package/skills/sentry/references/sdks/ruby/error-monitoring.md +248 -0
  217. package/skills/sentry/references/sdks/ruby/index.md +297 -0
  218. package/skills/sentry/references/sdks/ruby/logging.md +168 -0
  219. package/skills/sentry/references/sdks/ruby/metrics.md +196 -0
  220. package/skills/sentry/references/sdks/ruby/migration.md +321 -0
  221. package/skills/sentry/references/sdks/ruby/profiling.md +107 -0
  222. package/skills/sentry/references/sdks/ruby/tracing.md +388 -0
  223. package/skills/sentry/references/sdks/svelte/error-monitoring.md +456 -0
  224. package/skills/sentry/references/sdks/svelte/index.md +467 -0
  225. package/skills/sentry/references/sdks/svelte/logging.md +344 -0
  226. package/skills/sentry/references/sdks/svelte/session-replay.md +369 -0
  227. package/skills/sentry/references/sdks/svelte/tracing.md +406 -0
  228. package/skills/sentry/references/sdks/tanstack-start/error-monitoring.md +149 -0
  229. package/skills/sentry/references/sdks/tanstack-start/index.md +381 -0
  230. package/skills/sentry/references/sdks/tanstack-start/logging.md +74 -0
  231. package/skills/sentry/references/sdks/tanstack-start/session-replay.md +76 -0
  232. package/skills/sentry/references/sdks/tanstack-start/tanstackstart-features.md +128 -0
  233. package/skills/sentry/references/sdks/tanstack-start/tracing.md +123 -0
  234. package/skills/sentry/references/sdks/tanstack-start/user-feedback.md +81 -0
  235. package/skills/sentry/references/search-query-language.md +226 -0
  236. package/skills/sentry/references/semantics/angular.md +7 -0
  237. package/skills/sentry/references/semantics/app.md +28 -0
  238. package/skills/sentry/references/semantics/art.md +17 -0
  239. package/skills/sentry/references/semantics/aws.md +43 -0
  240. package/skills/sentry/references/semantics/browser.md +34 -0
  241. package/skills/sentry/references/semantics/cache.md +12 -0
  242. package/skills/sentry/references/semantics/client.md +8 -0
  243. package/skills/sentry/references/semantics/cloud.md +12 -0
  244. package/skills/sentry/references/semantics/cloudflare.md +23 -0
  245. package/skills/sentry/references/semantics/code.md +11 -0
  246. package/skills/sentry/references/semantics/culture.md +11 -0
  247. package/skills/sentry/references/semantics/db.md +21 -0
  248. package/skills/sentry/references/semantics/device.md +43 -0
  249. package/skills/sentry/references/semantics/error.md +7 -0
  250. package/skills/sentry/references/semantics/event.md +8 -0
  251. package/skills/sentry/references/semantics/exception.md +10 -0
  252. package/skills/sentry/references/semantics/faas.md +19 -0
  253. package/skills/sentry/references/semantics/file.md +8 -0
  254. package/skills/sentry/references/semantics/flag.md +7 -0
  255. package/skills/sentry/references/semantics/gcp.md +16 -0
  256. package/skills/sentry/references/semantics/gen_ai.md +53 -0
  257. package/skills/sentry/references/semantics/general.md +11 -0
  258. package/skills/sentry/references/semantics/graphql.md +9 -0
  259. package/skills/sentry/references/semantics/grpc.md +19 -0
  260. package/skills/sentry/references/semantics/http.md +34 -0
  261. package/skills/sentry/references/semantics/index.md +63 -0
  262. package/skills/sentry/references/semantics/jsonrpc.md +8 -0
  263. package/skills/sentry/references/semantics/jvm.md +12 -0
  264. package/skills/sentry/references/semantics/koa.md +7 -0
  265. package/skills/sentry/references/semantics/logger.md +7 -0
  266. package/skills/sentry/references/semantics/mcp.md +37 -0
  267. package/skills/sentry/references/semantics/mdc.md +7 -0
  268. package/skills/sentry/references/semantics/messaging.md +23 -0
  269. package/skills/sentry/references/semantics/middleware.md +7 -0
  270. package/skills/sentry/references/semantics/navigation.md +9 -0
  271. package/skills/sentry/references/semantics/nel.md +11 -0
  272. package/skills/sentry/references/semantics/network.md +17 -0
  273. package/skills/sentry/references/semantics/os.md +15 -0
  274. package/skills/sentry/references/semantics/otel.md +10 -0
  275. package/skills/sentry/references/semantics/params.md +7 -0
  276. package/skills/sentry/references/semantics/process.md +14 -0
  277. package/skills/sentry/references/semantics/react.md +7 -0
  278. package/skills/sentry/references/semantics/remix.md +7 -0
  279. package/skills/sentry/references/semantics/resource.md +7 -0
  280. package/skills/sentry/references/semantics/rpc.md +10 -0
  281. package/skills/sentry/references/semantics/score.md +10 -0
  282. package/skills/sentry/references/semantics/sentry.md +69 -0
  283. package/skills/sentry/references/semantics/server.md +8 -0
  284. package/skills/sentry/references/semantics/service.md +8 -0
  285. package/skills/sentry/references/semantics/session.md +7 -0
  286. package/skills/sentry/references/semantics/state.md +7 -0
  287. package/skills/sentry/references/semantics/thread.md +8 -0
  288. package/skills/sentry/references/semantics/timber.md +7 -0
  289. package/skills/sentry/references/semantics/trpc.md +8 -0
  290. package/skills/sentry/references/semantics/ui.md +18 -0
  291. package/skills/sentry/references/semantics/url.md +15 -0
  292. package/skills/sentry/references/semantics/user.md +17 -0
  293. package/skills/sentry/references/semantics/user_agent.md +7 -0
  294. package/skills/sentry/references/semantics/vercel.md +42 -0
  295. package/skills/sentry/references/setup-verification.md +39 -0
  296. package/skills/sentry/references/snapshots/github-actions-fanout.md +206 -0
  297. package/skills/sentry/references/snapshots/github-actions-simple.md +102 -0
  298. package/skills/sentry/references/snapshots/github-actions-swift-snapshot-testing.md +118 -0
  299. package/skills/sentry/references/snapshots/index.md +19 -0
  300. package/skills/sentry/references/snapshots/snapshot-previews.md +100 -0
  301. package/skills/sentry/references/snapshots/snapshots.md +123 -0
  302. package/skills/sentry/references/snapshots/wizard-setup.md +134 -0
  303. package/skills/testing/LICENSE +21 -0
  304. package/skills/testing/NOTICE +7 -0
  305. package/skills/testing/SKILL.md +13 -19
  306. package/skills/testing/agents/openai.yaml +1 -2
  307. package/skills/testing/references/selection.md +23 -0
  308. package/skills/testing/references/tdd.md +19 -0
  309. package/skills/testing/references/test-design.md +17 -0
  310. package/src/catalog.ts +1 -7
  311. package/skills/build-effect-apis/SKILL.md +0 -81
  312. package/skills/build-effect-apis/agents/openai.yaml +0 -4
  313. package/skills/build-effect-clis/SKILL.md +0 -92
  314. package/skills/build-effect-clis/agents/openai.yaml +0 -4
  315. package/skills/effect-architecture-audit/agents/openai.yaml +0 -4
  316. package/skills/effect-atom-state/SKILL.md +0 -97
  317. package/skills/effect-atom-state/agents/openai.yaml +0 -4
  318. package/skills/effect-ts/SKILL.md +0 -35
  319. package/skills/effect-ts/agents/openai.yaml +0 -4
  320. /package/skills/{build-effect-apis/references → effect-development/references/api}/cloudflare-workers.md +0 -0
  321. /package/skills/{build-effect-apis/references → effect-development/references/api}/runtime-assembly.md +0 -0
  322. /package/skills/{build-effect-apis/references → effect-development/references/api}/server-and-middleware.md +0 -0
  323. /package/skills/{build-effect-apis/references → effect-development/references/api}/shared-contracts.md +0 -0
  324. /package/skills/{effect-architecture-audit/references → effect-development/references/architecture}/service-and-boundary-audit.md +0 -0
  325. /package/skills/{effect-atom-state/references → effect-development/references/atom}/effect-atom-lifecycle.md +0 -0
  326. /package/skills/{effect-atom-state/references → effect-development/references/atom}/effect-atom-workflows.md +0 -0
  327. /package/skills/{effect-atom-state/references → effect-development/references/atom}/tanstack-start.md +0 -0
  328. /package/skills/{build-effect-clis/references → effect-development/references/cli}/command-design.md +0 -0
  329. /package/skills/{build-effect-clis/references → effect-development/references/cli}/processes-and-platform.md +0 -0
@@ -0,0 +1,1193 @@
1
+ # Session Replay — Sentry React SDK
2
+
3
+ > Minimum SDK: `@sentry/react` ≥7.27.0+\
4
+ > `replayCanvasIntegration()`: requires `@sentry/react` ≥7.98.0+
5
+
6
+ > ⚠️ **Browser-only feature.** Never add `replayIntegration()` to SSR entry points,
7
+ > Node.js server code, or web workers.
8
+ > Only add it where browser APIs are available.
9
+
10
+ ---
11
+
12
+ ## Setup
13
+
14
+ Session Replay is bundled in `@sentry/react` — no separate package needed.
15
+
16
+ ```typescript
17
+ // src/instrument.ts
18
+ import * as Sentry from "@sentry/react";
19
+
20
+ Sentry.init({
21
+ dsn: import.meta.env.VITE_SENTRY_DSN,
22
+
23
+ // Sample rates live at init level, NOT inside replayIntegration()
24
+ replaysSessionSampleRate: 0.1, // record 10% of all sessions from start
25
+ replaysOnErrorSampleRate: 1.0, // record 100% of sessions that hit an error
26
+
27
+ integrations: [
28
+ Sentry.replayIntegration({
29
+ maskAllText: true, // default: true
30
+ blockAllMedia: true, // default: true
31
+ }),
32
+ ],
33
+ });
34
+ ```
35
+
36
+ ### When NOT to Add Replay
37
+
38
+ | Context | Reason |
39
+ | -------------------------------------------------------- | ---------------------------------------- |
40
+ | Node.js / Express / Next.js server | No DOM — DOM recording APIs don’t exist |
41
+ | Web Workers / Service Workers | No DOM access |
42
+ | Next.js `_document.tsx` or `instrumentation.ts` (server) | Server-side code — use client-only init |
43
+ | Electron main process | Only the renderer process is appropriate |
44
+ | Browser extensions | Not a supported use case |
45
+ | CI/build pipelines | No user sessions to record |
46
+
47
+ For **Next.js App Router**, add replay only in `instrumentation-client.ts` or
48
+ `sentry.client.config.ts` — never in server instrumentation.
49
+
50
+ ---
51
+
52
+ ## Sample Rates
53
+
54
+ `replaysSessionSampleRate` and `replaysOnErrorSampleRate` are set on `Sentry.init()`,
55
+ not on the integration itself.
56
+
57
+ | Option | Type | Default | Behavior |
58
+ | -------------------------- | -------------- | ------- | ----------------------------------------------------------------------------------------------------- |
59
+ | `replaysSessionSampleRate` | `number` (0–1) | `0` | Fraction of all sessions recorded continuously from start |
60
+ | `replaysOnErrorSampleRate` | `number` (0–1) | `0` | Fraction of sessions captured when an error occurs — flushes ~60s of buffer, then continues recording |
61
+
62
+ ### How Session and Error Sampling Interact
63
+
64
+ ```
65
+ Session starts
66
+
67
+
68
+ replaysSessionSampleRate > 0?
69
+
70
+ ├─ YES → Roll dice against replaysSessionSampleRate
71
+ │ ├─ WIN → Record continuously (session mode) — streamed in real-time chunks
72
+ │ └─ LOSE → Buffer last 60s in memory
73
+
74
+ └─ NO → Always buffer last 60s in memory
75
+
76
+
77
+ Error occurs?
78
+
79
+ ├─ YES → Roll dice against replaysOnErrorSampleRate
80
+ │ ├─ WIN → Upload 60s buffer + continue recording
81
+ │ └─ LOSE → Discard buffer, nothing sent
82
+
83
+ └─ NO → Buffer discarded at session end
84
+ ```
85
+
86
+ ### Recommended Strategies
87
+
88
+ | Strategy | `replaysSessionSampleRate` | `replaysOnErrorSampleRate` | Use when |
89
+ | ---------------- | -------------------------- | -------------------------- | --------------------------------------- |
90
+ | **Errors-only** | `0` | `1.0` | Privacy-first; capture only on problems |
91
+ | **Balanced** | `0.1` | `1.0` | Most production apps |
92
+ | **Full** | `1.0` | `1.0` | Development or low-traffic apps |
93
+ | **High-traffic** | `0.01` | `1.0` | 100k+ sessions/day |
94
+
95
+ **Errors-only** (`replaysSessionSampleRate: 0`, `replaysOnErrorSampleRate: 1.0`) is the
96
+ most common production choice: near-zero overhead during normal operation, full context
97
+ when something breaks.
98
+
99
+ ```typescript
100
+ // Errors-only setup:
101
+ Sentry.init({
102
+ dsn: "...",
103
+ replaysSessionSampleRate: 0,
104
+ replaysOnErrorSampleRate: 1.0,
105
+ integrations: [Sentry.replayIntegration()],
106
+ });
107
+ ```
108
+
109
+ ---
110
+
111
+ ## `replayIntegration()` — All Constructor Options
112
+
113
+ ```typescript
114
+ Sentry.replayIntegration({
115
+ // ── SESSION MANAGEMENT ─────────────────────────────────────────
116
+ stickySession: true, // persist session across page refreshes
117
+ minReplayDuration: 5000, // min ms before sending (max: 15000)
118
+ maxReplayDuration: 3600000, // max replay length (1 hour hard cap)
119
+
120
+ // ── PRIVACY / MASKING ──────────────────────────────────────────
121
+ maskAllText: true, // replace all text with ***
122
+ maskAllInputs: true, // replace all input values with ***
123
+ blockAllMedia: true, // replace media elements with placeholder
124
+ mask: [".sentry-mask", "[data-sentry-mask]"],
125
+ unmask: [],
126
+ block: [".sentry-block", "[data-sentry-block]"],
127
+ unblock: [],
128
+ ignore: [".sentry-ignore", "[data-sentry-ignore]"],
129
+ maskFn: (text) => "*".repeat(text.length),
130
+
131
+ // ── NETWORK CAPTURE ────────────────────────────────────────────
132
+ networkDetailAllowUrls: [], // (string|RegExp)[] — enable body/header capture
133
+ networkDetailDenyUrls: [], // (string|RegExp)[] — override allowlist
134
+ networkCaptureBodies: true, // capture req/res bodies for allowed URLs
135
+ networkRequestHeaders: [], // extra request headers to capture
136
+ networkResponseHeaders: [], // extra response headers to capture
137
+
138
+ // ── PERFORMANCE / DOM PROTECTION ──────────────────────────────
139
+ mutationLimit: 10000, // stop recording after N mutations
140
+ mutationBreadcrumbLimit: 750, // emit warning breadcrumb after N mutations
141
+ slowClickIgnoreSelectors: [], // suppress dead/rage click detection here
142
+
143
+ // ── ADVANCED ───────────────────────────────────────────────────
144
+ beforeAddRecordingEvent: (event) => event, // filter/modify events
145
+ beforeErrorSampling: (event) => true, // control which errors trigger replay
146
+ useCompression: true, // compress data in Web Worker
147
+ workerUrl: undefined, // self-host worker for strict CSP
148
+ });
149
+ ```
150
+
151
+ ### Session Management Options
152
+
153
+ | Option | Type | Default | Notes |
154
+ | ------------------- | ----------- | --------- | ------------------------------------------------------------------------------------------------ |
155
+ | `stickySession` | `boolean` | `true` | Uses `sessionStorage` to survive page refreshes within the same tab. Tab close ends the session. |
156
+ | `minReplayDuration` | `number` ms | `5000` | Discard replays shorter than this. Max allowed: 15000ms. Prevents “bounce” uploads. |
157
+ | `maxReplayDuration` | `number` ms | `3600000` | After this, session ends and a new one begins (re-sampled). Server enforces 1-hour ceiling. |
158
+
159
+ ---
160
+
161
+ ## Privacy & Masking
162
+
163
+ This is the most critical part of session replay.
164
+ Misconfiguration can expose PII.
165
+
166
+ ### Default Behavior (Out of the Box)
167
+
168
+ With zero configuration, Replay ships in **maximum privacy mode**:
169
+
170
+ | Data type | Default behavior |
171
+ | ----------------------------------------------------------------------------------- | ------------------------------------------------------------ |
172
+ | All text content | ✅ Masked — replaced with `*` characters (length-preserving) |
173
+ | All `<input>` values | ✅ Masked |
174
+ | `<img>`, `<video>`, `<audio>`, `<svg>`, `<picture>`, `<embed>`, `<map>`, `<object>` | ✅ Blocked — replaced with same-size placeholder rectangle |
175
+ | Network request/response bodies | ❌ Not captured — opt-in required |
176
+ | Network headers beyond Content-Type/Length/Accept | ❌ Not captured — opt-in required |
177
+
178
+ ### The Three Privacy Primitives
179
+
180
+ | Primitive | Effect | Visual result in replay |
181
+ | ---------- | ----------------------------------------------------------------- | ----------------------------------------------- |
182
+ | **Mask** | Replaces text content character-by-character with `*` | Element shape preserved; text unreadable |
183
+ | **Block** | Replaces entire element with opaque placeholder box | Solid grey/black rectangle same size as element |
184
+ | **Ignore** | Suppresses interaction events (clicks, keystrokes) on the element | Element visible; no input captured |
185
+
186
+ **Mask vs Block distinction:** Masked elements show their layout (you see the form
187
+ structure, field labels, button positions) but no readable text.
188
+ Blocked elements are completely opaque — you can’t see anything inside.
189
+ Use mask for forms where structure matters; use block for images, payment widgets, or
190
+ entire sensitive sections.
191
+
192
+ ### `maskAllText` and `maskAllInputs`
193
+
194
+ ```typescript
195
+ // Defaults (most secure):
196
+ Sentry.replayIntegration({
197
+ maskAllText: true, // every text node → ***
198
+ maskAllInputs: true, // every <input> value → ***
199
+ });
200
+
201
+ // Unmask everything (e.g., non-sensitive marketing site):
202
+ Sentry.replayIntegration({
203
+ maskAllText: false,
204
+ maskAllInputs: false,
205
+ blockAllMedia: false,
206
+ });
207
+ ```
208
+
209
+ ### `mask` and `unmask` — Selector-Based Text Masking
210
+
211
+ `mask`: Additional CSS selectors whose text is masked **in addition to** `maskAllText`.
212
+
213
+ `unmask`: CSS selectors **exempt** from masking, even when `maskAllText: true`.
214
+
215
+ ```typescript
216
+ Sentry.replayIntegration({
217
+ maskAllText: true,
218
+ // Reveal specific safe elements:
219
+ unmask: [".app-title", "nav a", ".breadcrumb", "[data-public]"],
220
+ // Explicitly mask additional elements even with maskAllText: false:
221
+ mask: [".user-email", "#account-number", ".billing-address"],
222
+ });
223
+ ```
224
+
225
+ > **v7 → v8 migration:** In v7, `unmask` defaulted to
226
+ > `['.sentry-unmask', '[data-sentry-unmask]']`. In v8, the default is `[]`. To restore
227
+ > v7 behavior:
228
+ >
229
+ > ```typescript
230
+ > Sentry.replayIntegration({
231
+ > unmask: [".sentry-unmask", "[data-sentry-unmask]"],
232
+ > });
233
+ > ```
234
+
235
+ ### `block` and `unblock` — Selector-Based Element Blocking
236
+
237
+ `block`: Additional CSS selectors to replace with a placeholder, on top of
238
+ `blockAllMedia`.
239
+
240
+ `unblock`: CSS selectors **exempt** from blocking, even when `blockAllMedia: true`.
241
+
242
+ ```typescript
243
+ Sentry.replayIntegration({
244
+ blockAllMedia: true,
245
+ // Show specific images even though blockAllMedia is on:
246
+ unblock: [".product-thumbnail", "img.company-logo", ".hero-banner"],
247
+ // Block additional sensitive elements:
248
+ block: ["#payment-iframe", ".ssn-display", ".confidential-report"],
249
+ });
250
+ ```
251
+
252
+ > **v7 → v8 migration:** Same as unmask — in v8, `unblock` defaults to `[]`. To restore:
253
+ >
254
+ > ```typescript
255
+ > Sentry.replayIntegration({
256
+ > unblock: [".sentry-unblock", "[data-sentry-unblock]"],
257
+ > });
258
+ > ```
259
+
260
+ ### `ignore` — Input Event Suppression
261
+
262
+ Suppresses keypress and input value events on specific fields.
263
+ The element remains visible in the replay; the SDK just doesn’t record what the user
264
+ types.
265
+
266
+ ```typescript
267
+ Sentry.replayIntegration({
268
+ ignore: ["#otp-code", ".credit-card-number", "#ssn-input"],
269
+ });
270
+ ```
271
+
272
+ ### HTML Attribute API — Inline Privacy Control in JSX
273
+
274
+ Apply privacy controls directly in your React components without touching SDK config:
275
+
276
+ | Attribute | Class equivalent | Effect |
277
+ | --------------------- | ---------------- | ---------------------------------------------- |
278
+ | `data-sentry-mask` | `sentry-mask` | Masks this element’s text content |
279
+ | `data-sentry-unmask` | `sentry-unmask` | Unmasks this element (overrides `maskAllText`) |
280
+ | `data-sentry-block` | `sentry-block` | Replaces entire element with placeholder |
281
+ | `data-sentry-unblock` | `sentry-unblock` | Shows this element (overrides `blockAllMedia`) |
282
+ | `data-sentry-ignore` | `sentry-ignore` | Suppresses input events |
283
+
284
+ ```tsx
285
+ // Mask a specific field even if maskAllText is false:
286
+ <p data-sentry-mask>{user.fullName}</p>
287
+
288
+ // Safe to display even with maskAllText: true:
289
+ <h1 data-sentry-unmask>Welcome to Our App</h1>
290
+
291
+ // Block entire subtree — shows as grey box in replay:
292
+ <div data-sentry-block>
293
+ <CreditCardForm />
294
+ </div>
295
+
296
+ // Show this image even with blockAllMedia: true:
297
+ <img data-sentry-unblock src="/company-logo.png" alt="Logo" />
298
+
299
+ // Record the field but not what the user types:
300
+ <input data-sentry-ignore type="password" placeholder="Password" />
301
+ ```
302
+
303
+ > **SDK v8 note:** For the `data-sentry-*` attributes to be recognized automatically,
304
+ > they are built into the SDK’s default selector lists.
305
+ > The class-based equivalents (`.sentry-mask`, etc.)
306
+ > require explicit listing in `mask`/`unmask`/`block`/`unblock` options in v8.
307
+
308
+ ### `maskFn` — Custom Text Replacement Function
309
+
310
+ Controls how masked text is transformed.
311
+ Receives the original string, returns the replacement:
312
+
313
+ ```typescript
314
+ Sentry.replayIntegration({
315
+ maskFn: (text) => {
316
+ // Default behavior — asterisks preserving length:
317
+ return "*".repeat(text.length);
318
+
319
+ // Fixed placeholder:
320
+ return "[REDACTED]";
321
+
322
+ // Preserve structure, mask content:
323
+ return text[0] + "*".repeat(Math.max(0, text.length - 1));
324
+
325
+ // Mask only digits (preserve non-sensitive structure):
326
+ return text.replace(/\d/g, "*");
327
+ },
328
+ });
329
+ ```
330
+
331
+ ### Three Privacy Configuration Modes
332
+
333
+ **Mode 1 — Maximum Privacy (default):** Mask everything, reveal nothing
334
+
335
+ ```typescript
336
+ Sentry.replayIntegration({
337
+ maskAllText: true,
338
+ maskAllInputs: true,
339
+ blockAllMedia: true,
340
+ });
341
+ ```
342
+
343
+ **Mode 2 — Selective Masking (balanced):** Show most UI, hide sensitive fields
344
+
345
+ ```typescript
346
+ Sentry.replayIntegration({
347
+ maskAllText: false,
348
+ blockAllMedia: false,
349
+ maskAllInputs: true, // still hide input values
350
+ mask: ["#ssn", "#dob", ".pii"], // mask these explicitly
351
+ block: ["#payment-form", ".private-avatar"],
352
+ });
353
+ ```
354
+
355
+ **Mode 3 — Whitelist (mask-all, unmask-approved):** Start fully masked, allow-list safe
356
+ UI
357
+
358
+ ```typescript
359
+ Sentry.replayIntegration({
360
+ maskAllText: true,
361
+ blockAllMedia: true,
362
+ unmask: ["nav", "header", ".app-chrome", ".breadcrumb", "[data-public]"],
363
+ unblock: ["img.product-image", ".marketing-hero", ".public-icon"],
364
+ });
365
+ ```
366
+
367
+ ### How React Component Trees Interact with Masking
368
+
369
+ Masking operates on the **rendered DOM**, not React component hierarchy.
370
+ Key behaviors:
371
+
372
+ - Masking **cascades** through the DOM tree — a `data-sentry-mask` on a parent masks all
373
+ child text nodes
374
+ - Blocking a parent **hides everything inside** — the entire subtree is replaced with a
375
+ placeholder box
376
+ - `unmask`/`unblock` on a child **overrides** the parent’s mask/block for that specific
377
+ subtree
378
+ - React’s virtual DOM is invisible to Replay — it observes via `MutationObserver` on the
379
+ real DOM
380
+ - Conditionally rendered elements (e.g., toggled modals) are captured when they appear
381
+ in the DOM
382
+
383
+ ```tsx
384
+ // Masking cascades to all children:
385
+ <div data-sentry-mask>
386
+ <UserProfile /> {/* All text inside UserProfile is masked */}
387
+ <ContactInfo /> {/* This too */}
388
+ </div>
389
+
390
+ // Child can opt out of parent masking:
391
+ <div data-sentry-mask>
392
+ <AccountNumber /> {/* masked */}
393
+ <span data-sentry-unmask>Account Type</span> {/* visible */}
394
+ </div>
395
+
396
+ // Block a payment widget; unblock the logo inside:
397
+ <div data-sentry-block>
398
+ <PaymentProcessor />
399
+ <img data-sentry-unblock src="/payment-logo.svg" /> {/* shows in replay */}
400
+ </div>
401
+ ```
402
+
403
+ ### `beforeAddRecordingEvent` — Custom Event Scrubbing
404
+
405
+ Available since v7.53.0. Intercepts every recording event before it is buffered.
406
+ Return `null` to drop, return the event (mutated or not) to keep:
407
+
408
+ ```typescript
409
+ Sentry.replayIntegration({
410
+ beforeAddRecordingEvent: (event) => {
411
+ // Drop noisy console breadcrumbs
412
+ if (event.data.tag === "breadcrumb" && event.data.payload?.category === "console") {
413
+ return null;
414
+ }
415
+
416
+ // Only capture network events for 4xx/5xx responses
417
+ if (
418
+ event.data.tag === "performanceSpan" &&
419
+ (event.data.payload.op === "resource.fetch" || event.data.payload.op === "resource.xhr")
420
+ ) {
421
+ const status = event.data.payload.data?.statusCode;
422
+ if (status && status < 400) return null;
423
+ }
424
+
425
+ return event;
426
+ },
427
+ });
428
+ ```
429
+
430
+ ### Scrubbing URLs via `addEventProcessor`
431
+
432
+ To redact sensitive data from URLs in the replay event metadata (not the recording
433
+ stream):
434
+
435
+ ```typescript
436
+ Sentry.addEventProcessor((event) => {
437
+ if (event.type !== "replay_event") return event;
438
+
439
+ const scrub = (url: string) => url.replace(/\/users\/[a-z0-9-]+\//gi, "/users/[id]/");
440
+
441
+ event.urls = event.urls?.map(scrub);
442
+ return event;
443
+ });
444
+ ```
445
+
446
+ ### `srcdoc` Iframe Warning
447
+
448
+ Elements using the `srcdoc` attribute bypass masking logic.
449
+ Always block them explicitly:
450
+
451
+ ```typescript
452
+ Sentry.replayIntegration({
453
+ block: ["iframe[srcdoc]"],
454
+ });
455
+ ```
456
+
457
+ ---
458
+
459
+ ## Network Request Recording
460
+
461
+ ### What Is Captured by Default
462
+
463
+ For every `fetch` and `XHR` request, without any configuration:
464
+
465
+ | Field | Captured? |
466
+ | ----------------------------- | --------- |
467
+ | URL | ✅ Yes |
468
+ | HTTP method | ✅ Yes |
469
+ | HTTP status code | ✅ Yes |
470
+ | Request body size (bytes) | ✅ Yes |
471
+ | Response body size (bytes) | ✅ Yes |
472
+ | Request/response body content | ❌ No |
473
+ | Custom headers | ❌ No |
474
+
475
+ ### `networkDetailAllowUrls` — Enable Body and Header Capture
476
+
477
+ Body and header capture is **opt-in** and only activates for URLs that match this list:
478
+
479
+ ```typescript
480
+ Sentry.replayIntegration({
481
+ networkDetailAllowUrls: [
482
+ window.location.origin, // same-origin (recommended minimum)
483
+ "https://api.myapp.com", // specific domain
484
+ /^https:\/\/api\.myapp\.com\//, // regex pattern
485
+ "/api/", // substring match
486
+ "graphql", // matches any URL containing "graphql"
487
+ ],
488
+ });
489
+ ```
490
+
491
+ > **Security:** Only allowlist your own APIs.
492
+ > Never include third-party payment processors, auth providers, or analytics endpoints.
493
+
494
+ ### `networkDetailDenyUrls` — Exclude Sensitive Endpoints
495
+
496
+ Takes precedence over `networkDetailAllowUrls`. Matching URLs never have bodies/headers
497
+ captured:
498
+
499
+ ```typescript
500
+ Sentry.replayIntegration({
501
+ networkDetailAllowUrls: [window.location.origin],
502
+ networkDetailDenyUrls: [
503
+ "/api/auth",
504
+ "/api/login",
505
+ "/api/payment",
506
+ "/api/users/me",
507
+ /\/sensitive\//,
508
+ /\/admin\//,
509
+ ],
510
+ });
511
+ ```
512
+
513
+ ### `networkCaptureBodies` (boolean, default: `true`)
514
+
515
+ Controls whether request/response bodies are captured for allowlisted URLs.
516
+ Set `false` to capture only headers and metadata without body content:
517
+
518
+ ```typescript
519
+ Sentry.replayIntegration({
520
+ networkDetailAllowUrls: ["https://api.myapp.com"],
521
+ networkCaptureBodies: false, // headers yes, bodies no
522
+ });
523
+ ```
524
+
525
+ **Body format support:**
526
+
527
+ | Format | Captured |
528
+ | ----------------------------------- | -------- |
529
+ | `application/json` | ✅ Yes |
530
+ | XML | ✅ Yes |
531
+ | `text/plain` | ✅ Yes |
532
+ | `multipart/form-data` (text fields) | ✅ Yes |
533
+ | Binary / byte arrays | ❌ No |
534
+ | File uploads | ❌ No |
535
+ | Blobs / media streams | ❌ No |
536
+
537
+ Bodies are truncated at **150,000 characters** (150 KB). This limit is not configurable.
538
+
539
+ ### `networkRequestHeaders` and `networkResponseHeaders`
540
+
541
+ By default, only `Content-Type`, `Content-Length`, and `Accept` are captured.
542
+ Add more:
543
+
544
+ ```typescript
545
+ Sentry.replayIntegration({
546
+ networkDetailAllowUrls: [window.location.origin],
547
+ networkRequestHeaders: [
548
+ "Cache-Control",
549
+ "X-Request-ID",
550
+ "X-Correlation-ID",
551
+ // ⚠️ Avoid: 'Authorization', 'Cookie' — contain credentials
552
+ ],
553
+ networkResponseHeaders: [
554
+ "Referrer-Policy",
555
+ "X-Request-ID",
556
+ "X-Response-Time",
557
+ "CF-Ray",
558
+ "X-Cache",
559
+ ],
560
+ });
561
+ ```
562
+
563
+ > ⚠️ Never capture `Authorization`, `Cookie`, or `Set-Cookie` headers in production.
564
+ > They contain bearer tokens and session credentials.
565
+
566
+ ### Capturing GraphQL Operation Names
567
+
568
+ GraphQL requests all go to the same endpoint.
569
+ To distinguish operations, capture the request body and filter by operation name in
570
+ `beforeAddRecordingEvent`:
571
+
572
+ ```typescript
573
+ Sentry.replayIntegration({
574
+ networkDetailAllowUrls: ["https://api.myapp.com/graphql"],
575
+ networkCaptureBodies: true,
576
+ beforeAddRecordingEvent: (event) => {
577
+ // Optionally: drop mutations from replay to reduce noise
578
+ if (event.data.tag === "performanceSpan" && event.data.payload.op === "resource.fetch") {
579
+ const body = event.data.payload.data?.request?.body;
580
+ if (body?.operationName === "InternalAdminMutation") return null;
581
+ }
582
+ return event;
583
+ },
584
+ });
585
+ ```
586
+
587
+ ### Apollo Client Body Capture Issue
588
+
589
+ Apollo uses an `AbortController` to cancel in-flight queries on component unmount.
590
+ This abort fires before Replay can read the response body, resulting in empty response
591
+ bodies.
592
+
593
+ **Workaround:** Supply a custom signal that doesn’t abort the Replay read:
594
+
595
+ ```typescript
596
+ import { HttpLink } from "@apollo/client";
597
+
598
+ const httpLink = new HttpLink({
599
+ uri: "/graphql",
600
+ // Don't pass an AbortController signal here, or use your own
601
+ // that only cancels at route-change boundaries rather than component unmount
602
+ });
603
+ ```
604
+
605
+ ---
606
+
607
+ ## Canvas Recording
608
+
609
+ ### Setup — `replayCanvasIntegration()`
610
+
611
+ Canvas elements are **not** recorded by default.
612
+ Add a second integration:
613
+
614
+ ```typescript
615
+ import * as Sentry from "@sentry/react";
616
+
617
+ Sentry.init({
618
+ dsn: "...",
619
+ replaysSessionSampleRate: 0.1,
620
+ replaysOnErrorSampleRate: 1.0,
621
+ integrations: [
622
+ Sentry.replayIntegration(),
623
+ Sentry.replayCanvasIntegration(), // requires @sentry/react ≥7.98.0
624
+ ],
625
+ });
626
+ ```
627
+
628
+ > ⚠️ **No PII scrubbing in canvas recordings.** If your canvas renders sensitive data
629
+ > (user documents, medical images, profile photos), either avoid canvas recording or
630
+ > sanitize the canvas content before each snapshot.
631
+
632
+ ### Standard 2D Canvas
633
+
634
+ For standard 2D canvas, the default configuration records automatically — no extra code
635
+ needed. The integration periodically snapshots the canvas content.
636
+
637
+ ### 3D / WebGL Canvas — Manual Snapshotting
638
+
639
+ WebGL requires `enableManualSnapshot: true` because:
640
+
641
+ 1. The integration must enable `preserveDrawingBuffer` on the WebGL context to read
642
+ pixel data
643
+ 2. `preserveDrawingBuffer` prevents the GPU from discarding draw buffers after
644
+ compositing — this can degrade performance on complex scenes
645
+ 3. Manual mode lets you control exactly when snapshots are taken
646
+
647
+ ```typescript
648
+ Sentry.init({
649
+ integrations: [
650
+ Sentry.replayIntegration(),
651
+ Sentry.replayCanvasIntegration({ enableManualSnapshot: true }),
652
+ ],
653
+ });
654
+
655
+ // React ref + render loop:
656
+ import { useRef, useEffect } from 'react';
657
+
658
+ function WebGLScene() {
659
+ const canvasRef = useRef<HTMLCanvasElement>(null);
660
+
661
+ useEffect(() => {
662
+ const canvas = canvasRef.current!;
663
+ const gl = canvas.getContext('webgl');
664
+
665
+ function render() {
666
+ // ... your WebGL rendering ...
667
+
668
+ // After rendering, snapshot for Replay:
669
+ const canvasIntegration = Sentry.getClient()?.getIntegrationByName('ReplayCanvas');
670
+ canvasIntegration?.snapshot(canvas);
671
+
672
+ requestAnimationFrame(render);
673
+ }
674
+
675
+ requestAnimationFrame(render);
676
+ }, []);
677
+
678
+ return <canvas ref={canvasRef} id="scene" />;
679
+ }
680
+ ```
681
+
682
+ ### WebGPU Canvas
683
+
684
+ WebGPU requires an additional flag since it doesn’t use `requestAnimationFrame` in the
685
+ same way:
686
+
687
+ ```typescript
688
+ Sentry.replayCanvasIntegration({ enableManualSnapshot: true });
689
+
690
+ // In your render loop:
691
+ const canvasIntegration = Sentry.getClient()?.getIntegrationByName("ReplayCanvas");
692
+ canvasIntegration?.snapshot(canvasRef, { skipRequestAnimationFrame: true });
693
+ ```
694
+
695
+ ### Cross-Origin Canvas Content
696
+
697
+ If your canvas draws images/videos from a different origin, the browser throws a
698
+ `SecurityError` when Replay calls `canvas.toDataURL()` (tainted canvas).
699
+
700
+ **Fix:** Add `crossorigin="anonymous"` to media elements and configure CORS on your CDN:
701
+
702
+ ```tsx
703
+ <img crossOrigin="anonymous" src="https://cdn.example.com/texture.png" />
704
+ <video crossOrigin="anonymous" src="https://cdn.example.com/clip.mp4" />
705
+ ```
706
+
707
+ Your CDN must respond with `Access-Control-Allow-Origin: *` (or your specific origin).
708
+
709
+ ---
710
+
711
+ ## Lazy Loading Replay
712
+
713
+ Replay adds ~50 KB (gzipped) to your initial bundle.
714
+ If initial page load performance is critical, defer loading until after the first
715
+ render:
716
+
717
+ ```typescript
718
+ // src/instrument.ts — initialize without replay
719
+ Sentry.init({
720
+ dsn: import.meta.env.VITE_SENTRY_DSN,
721
+ integrations: [], // no replay at startup
722
+ });
723
+
724
+ // Load replay after initial render (e.g., in a useEffect or after login):
725
+ async function enableReplay() {
726
+ const replayIntegration = await Sentry.lazyLoadIntegration("replayIntegration");
727
+ Sentry.addIntegration(
728
+ replayIntegration({
729
+ maskAllText: true,
730
+ blockAllMedia: true,
731
+ }),
732
+ );
733
+ }
734
+ ```
735
+
736
+ **Or with dynamic import:**
737
+
738
+ ```typescript
739
+ async function enableReplay() {
740
+ const { replayIntegration } = await import("@sentry/react");
741
+ Sentry.addIntegration(replayIntegration({ maskAllText: true }));
742
+ }
743
+ ```
744
+
745
+ **Trade-off:** Any errors or sessions that occur before the integration loads are not
746
+ captured. Prefer lazy loading when:
747
+
748
+ - The app has a significant unauthenticated surface (home page, landing page)
749
+ - You only want replay for authenticated users (load after login)
750
+ - Initial page load performance is actively measured
751
+
752
+ ---
753
+
754
+ ## Advanced Configuration
755
+
756
+ ### `beforeErrorSampling` — Control Which Errors Trigger Replay
757
+
758
+ Only relevant in buffer mode (`replaysOnErrorSampleRate > 0`). Called before the dice
759
+ roll for error-based sampling.
760
+ Return `false` to prevent the error from triggering a replay capture:
761
+
762
+ ```typescript
763
+ Sentry.replayIntegration({
764
+ beforeErrorSampling: (event) => {
765
+ // Don't capture replay for known benign errors
766
+ if (event.exception?.values?.[0]?.value?.includes("ResizeObserver loop")) {
767
+ return false;
768
+ }
769
+ // Skip console-captured errors (from captureConsoleIntegration)
770
+ if (event.logger === "console") {
771
+ return false;
772
+ }
773
+ // Skip test environments
774
+ if (event.tags?.environment === "test") {
775
+ return false;
776
+ }
777
+ // Skip low-severity user-land errors
778
+ if (event.level === "warning") {
779
+ return false;
780
+ }
781
+ return true;
782
+ },
783
+ });
784
+ ```
785
+
786
+ > **`captureConsoleIntegration` warning:** If you use this integration, every
787
+ > `console.error()` call captures a Sentry event, which can trigger replay sampling for
788
+ > every console error.
789
+ > Use `beforeErrorSampling` to filter these out.
790
+
791
+ ### `mutationBreadcrumbLimit` and `mutationLimit`
792
+
793
+ Protect against pages that cause excessive DOM mutations (real-time dashboards, animated
794
+ charts, chat feeds):
795
+
796
+ | Option | Default | Behavior |
797
+ | ------------------------- | ------- | -------------------------------------------------------------- |
798
+ | `mutationBreadcrumbLimit` | `750` | Adds a warning breadcrumb to the replay timeline when exceeded |
799
+ | `mutationLimit` | `10000` | Stops recording entirely to protect page performance |
800
+
801
+ ```typescript
802
+ Sentry.replayIntegration({
803
+ mutationBreadcrumbLimit: 500, // warn earlier for noisy pages
804
+ mutationLimit: 5000, // stop recording earlier
805
+ });
806
+ ```
807
+
808
+ **When you hit `mutationLimit`, fix the root cause:**
809
+
810
+ - Virtualize long lists with `react-virtual`, `react-window`, or TanStack Virtual
811
+ - Paginate large data tables instead of rendering all rows
812
+ - Use CSS animations instead of JS-driven DOM mutations
813
+ - Batch React state updates (`unstable_batchedUpdates` or React 18 automatic batching)
814
+
815
+ ### `slowClickIgnoreSelectors`
816
+
817
+ Sentry automatically detects **dead clicks** (click with no DOM response) and **rage
818
+ clicks** (3+ clicks within 7 seconds).
819
+ Suppress this detection for elements that intentionally don’t mutate the DOM:
820
+
821
+ ```typescript
822
+ Sentry.replayIntegration({
823
+ slowClickIgnoreSelectors: [
824
+ "a[download]", // download links
825
+ ".copy-to-clipboard", // clipboard buttons
826
+ '[aria-label*="print" i]',
827
+ ".pdf-export-btn",
828
+ "#video-player", // video controls
829
+ ".toast-close", // dismissal buttons that animate out
830
+ ],
831
+ });
832
+ ```
833
+
834
+ ### `workerUrl` — Self-Host the Compression Worker
835
+
836
+ By default, Sentry creates the compression worker via a `blob:` URL. If your CSP blocks
837
+ `blob:`, self-host the worker:
838
+
839
+ 1. Copy the worker file from `node_modules/@sentry/replay/build/npm/esm/worker/` after
840
+ install
841
+ 2. Serve it from your own origin (e.g., `/assets/sentry-replay-worker.min.js`)
842
+ 3. Configure the path:
843
+
844
+ ```typescript
845
+ Sentry.replayIntegration({
846
+ workerUrl: "/assets/sentry-replay-worker.min.js",
847
+ });
848
+ ```
849
+
850
+ **With Sentry Vite Plugin (v2.10.0+):**
851
+
852
+ ```typescript
853
+ // vite.config.ts
854
+ sentryVitePlugin({
855
+ bundleSizeOptimizations: {
856
+ excludeReplayWorker: true, // extract worker as separate asset
857
+ },
858
+ });
859
+ ```
860
+
861
+ > The worker is forward/backward compatible within the same major SDK version.
862
+ > Update it when upgrading major versions.
863
+
864
+ ### Manual Session Control
865
+
866
+ When both sample rates are `0`, control recording programmatically:
867
+
868
+ ```typescript
869
+ Sentry.init({
870
+ dsn: "...",
871
+ replaysSessionSampleRate: 0,
872
+ replaysOnErrorSampleRate: 0,
873
+ integrations: [Sentry.replayIntegration()],
874
+ });
875
+
876
+ const replay = Sentry.getReplay();
877
+
878
+ // Start continuous recording:
879
+ replay.start();
880
+
881
+ // Start in buffer mode (hold in memory, send on flush):
882
+ replay.startBuffering();
883
+
884
+ // Stop recording and flush all pending data:
885
+ await replay.stop();
886
+
887
+ // Upload buffered data without stopping:
888
+ await replay.flush();
889
+
890
+ // Get current replay ID (for linking to support tickets, etc.):
891
+ const replayId = replay.getReplayId();
892
+ const replayUrl = `https://YOUR-ORG.sentry.io/replays/${replayId}/`;
893
+ ```
894
+
895
+ **React Hook pattern for support widget integration:**
896
+
897
+ ```tsx
898
+ import { useEffect } from "react";
899
+ import * as Sentry from "@sentry/react";
900
+
901
+ function SupportButton() {
902
+ const handleOpen = async () => {
903
+ const replay = Sentry.getReplay();
904
+ await replay.flush();
905
+ const replayId = replay.getReplayId();
906
+
907
+ openSupportWidget({
908
+ metadata: {
909
+ sentryReplayUrl: replayId ? `https://myorg.sentry.io/replays/${replayId}/` : undefined,
910
+ },
911
+ });
912
+ };
913
+
914
+ return <button onClick={handleOpen}>Contact Support</button>;
915
+ }
916
+ ```
917
+
918
+ ### Deferred Initialization (Feature Flags / A/B Testing)
919
+
920
+ If your sampling rates come from an external feature flag service:
921
+
922
+ ```typescript
923
+ // Start Sentry without replay
924
+ Sentry.init({
925
+ dsn: "...",
926
+ integrations: [],
927
+ });
928
+
929
+ // After feature flags resolve:
930
+ async function initReplayFromFlags() {
931
+ const flags = await featureFlagService.getFlags();
932
+
933
+ const client = Sentry.getClient();
934
+ if (!client) return;
935
+
936
+ const options = client.getOptions();
937
+ options.replaysSessionSampleRate = flags.replaySessionRate;
938
+ options.replaysOnErrorSampleRate = flags.replayErrorRate;
939
+
940
+ const replay = Sentry.replayIntegration({ maskAllText: true });
941
+ client.addIntegration(replay);
942
+ }
943
+ ```
944
+
945
+ ### Route-Based Recording (Selective Pages)
946
+
947
+ ```typescript
948
+ Sentry.init({
949
+ dsn: "...",
950
+ replaysSessionSampleRate: 0,
951
+ replaysOnErrorSampleRate: 0,
952
+ integrations: [Sentry.replayIntegration()],
953
+ });
954
+
955
+ // React Router example: only record checkout flow
956
+ import { useLocation } from "react-router-dom";
957
+ import { useEffect } from "react";
958
+
959
+ function ReplayController() {
960
+ const location = useLocation();
961
+
962
+ useEffect(() => {
963
+ const replay = Sentry.getReplay();
964
+ const isCheckout = location.pathname.startsWith("/checkout");
965
+
966
+ if (isCheckout && !replay.getReplayId()) {
967
+ replay.start();
968
+ } else if (!isCheckout && replay.getReplayId()) {
969
+ replay.stop();
970
+ }
971
+ }, [location.pathname]);
972
+
973
+ return null;
974
+ }
975
+ ```
976
+
977
+ ---
978
+
979
+ ## Understanding Sessions
980
+
981
+ ### What Is a Session vs a Segment
982
+
983
+ - **Replay Session:** The full recording from start to end — one replay visible in the
984
+ Sentry UI. Has a unique `replayId`.
985
+ - **Segment:** A chunk of recording data transmitted to the server.
986
+ Large recordings are split into segments sent in sequence and reassembled server-side.
987
+
988
+ One session = multiple segments streamed over time.
989
+
990
+ ### Session Lifecycle
991
+
992
+ | Event | Session behavior |
993
+ | ------------------------------------------ | ---------------------------------------------------- |
994
+ | SDK initializes | Sampling evaluated; session or buffer mode begins |
995
+ | User is inactive 15+ minutes | Session ends; new session starts on next interaction |
996
+ | Total duration exceeds `maxReplayDuration` | Session ends; new session starts (re-sampled) |
997
+ | User closes the tab | Session ends; `sessionStorage` is cleared |
998
+ | `replay.stop()` called | Session ends; pending data flushed |
999
+
1000
+ An “interaction” that resets the idle timer = mouse click OR browser navigation event.
1001
+
1002
+ ### Session Mode vs Buffer Mode
1003
+
1004
+ | Aspect | Session Mode | Buffer Mode |
1005
+ | ---------------------- | ----------------------------------------------------- | ----------------------------------- |
1006
+ | Activated when | `replaysSessionSampleRate > 0` AND session is sampled | Only `replaysOnErrorSampleRate > 0` |
1007
+ | Data transmission | Continuous real-time chunks to Sentry | Held in memory; sent only on error |
1008
+ | Memory usage | Low (continuously streamed out) | ~2–5 MB in RAM (last ~60 seconds) |
1009
+ | What appears in Sentry | Full session from start | 60s before error + rest of session |
1010
+ | Idle timeout | 15 minutes | 15 minutes |
1011
+ | Max duration | `maxReplayDuration` | `maxReplayDuration` |
1012
+
1013
+ ### How Buffer Mode Flushes on Error
1014
+
1015
+ 1. Error occurs in the browser
1016
+ 2. Sentry SDK captures the error event
1017
+ 3. `beforeErrorSampling(event)` is called — `return false` to abort
1018
+ 4. Dice roll against `replaysOnErrorSampleRate` — fail → buffer discarded
1019
+ 5. Pass → buffered ~60 seconds of DOM events sent to Sentry
1020
+ 6. Recording continues in session mode for the remainder of the session
1021
+ 7. Error event is linked to the replay via `replayId` tag
1022
+
1023
+ ---
1024
+
1025
+ ## Performance Considerations
1026
+
1027
+ ### Bundle Size
1028
+
1029
+ | Component | Size (gzipped) |
1030
+ | --------------------------- | --------------------------- |
1031
+ | `replayIntegration()` | ~50 KB |
1032
+ | `replayCanvasIntegration()` | Additional (small) |
1033
+ | Compression Web Worker | Extracted as separate chunk |
1034
+
1035
+ **Reduction strategies:**
1036
+
1037
+ - Use `lazyLoadIntegration('replayIntegration')` to keep it out of the critical bundle
1038
+ - Set `excludeReplayWorker: true` in the Sentry bundler plugin
1039
+
1040
+ ### Runtime Overhead
1041
+
1042
+ | Operation | Performance impact |
1043
+ | ----------------------------------------------------- | --------------------------------------- |
1044
+ | DOM observation (MutationObserver) | Low — reads, doesn’t write |
1045
+ | Compression | Off main thread via Web Worker |
1046
+ | Network interception (fetch/XHR) | Low — thin wrappers |
1047
+ | Canvas recording (2D) | Moderate — pixel buffer reads |
1048
+ | Canvas recording (WebGL with `preserveDrawingBuffer`) | High — prevents GPU buffer discard |
1049
+ | High DOM mutation rate | High — can trigger `mutationLimit` stop |
1050
+
1051
+ **Errors-only mode has near-zero overhead** when no error occurs — the in-memory buffer
1052
+ (~2–5 MB) is the only cost.
1053
+
1054
+ ---
1055
+
1056
+ ## CSP Requirements
1057
+
1058
+ Without correct CSP headers, Replay **fails silently** — no error in the console, no
1059
+ replay recorded.
1060
+
1061
+ ### Required Directives
1062
+
1063
+ ```
1064
+ worker-src 'self' blob:;
1065
+ child-src 'self' blob:; ← required for Safari ≤15.4
1066
+ ```
1067
+
1068
+ **As an HTTP response header:**
1069
+
1070
+ ```http
1071
+ Content-Security-Policy: default-src 'self'; worker-src 'self' blob:; child-src 'self' blob:;
1072
+ ```
1073
+
1074
+ **As a meta tag:**
1075
+
1076
+ ```html
1077
+ <meta
1078
+ http-equiv="Content-Security-Policy"
1079
+ content="default-src 'self'; worker-src 'self' blob:; child-src 'self' blob:;"
1080
+ />
1081
+ ```
1082
+
1083
+ **When using `workerUrl` (self-hosted worker):**
1084
+
1085
+ ```
1086
+ worker-src 'self'; ← blob: not required
1087
+ child-src 'self';
1088
+ ```
1089
+
1090
+ **If using a `tunnel` to relay events through your own server:**
1091
+
1092
+ ```
1093
+ connect-src 'self' https://o<ORG_ID>.ingest.sentry.io;
1094
+ ```
1095
+
1096
+ Or with `tunnel: "/sentry-tunnel"`:
1097
+
1098
+ ```
1099
+ connect-src 'self'; ← all requests go to your own origin
1100
+ ```
1101
+
1102
+ ---
1103
+
1104
+ ## Troubleshooting
1105
+
1106
+ | Issue | Cause | Solution |
1107
+ | ------------------------------------ | ------------------------------------------- | ----------------------------------------------------------------------------- |
1108
+ | No replays appearing at all | CSP blocks Web Worker blob URL | Add `worker-src 'self' blob:` and `child-src 'self' blob:` to CSP |
1109
+ | No replays appearing | `replayIntegration()` not in init | Confirm it’s in the `integrations` array in `Sentry.init()` |
1110
+ | No replays appearing | Both sample rates are `0` | Set `replaysSessionSampleRate: 0.1` or `replaysOnErrorSampleRate: 1.0` |
1111
+ | No replays appearing | SDK version too old | Upgrade to `@sentry/react` ≥7.27.0 |
1112
+ | No replays appearing | Running in SSR/Node context | Ensure `replayIntegration()` is only in browser-side init code |
1113
+ | No replays appearing | `minReplayDuration` too high | Lower it or set to `0` for debugging |
1114
+ | All text shows as `***` | `maskAllText: true` (default) | Expected. Use `unmask` or `data-sentry-unmask` to reveal safe content |
1115
+ | Replay cuts off early | `mutationLimit` exceeded | Increase limit or virtualize your lists; check for DOM thrash |
1116
+ | Replay shows broken layout | External CSS/fonts blocked by CORS | Add `sentry.io` to `Access-Control-Allow-Origin` on your CDN |
1117
+ | Images missing in replay | External images blocked by CORS | Add `sentry.io` to CDN CORS policy for image assets |
1118
+ | Network bodies always empty | URL not in `networkDetailAllowUrls` | Add your API domain to the allowlist |
1119
+ | Network bodies empty (Apollo) | AbortController cancels before Replay reads | Don’t abort on component unmount; use route-level cancellation |
1120
+ | Network body truncated | > 150,000 characters | Expected behavior — limit is not configurable |
1121
+ | Canvas not recording | Not added by default | Add `replayCanvasIntegration()` to `integrations` array |
1122
+ | Canvas SecurityError | Cross-origin media taints canvas | Add `crossOrigin="anonymous"` to `<img>`/`<video>` and enable CORS on CDN |
1123
+ | WebGL canvas blank in replay | `preserveDrawingBuffer` not enabled | Use `enableManualSnapshot: true` on `replayCanvasIntegration()` |
1124
+ | Rage/dead clicks on download buttons | SDK detects non-DOM-mutating clicks | Add selector to `slowClickIgnoreSelectors` |
1125
+ | Replays triggered by `console.error` | `captureConsoleIntegration` sends events | Use `beforeErrorSampling` to return `false` when `event.logger === 'console'` |
1126
+ | Worker CSP error in console | CSP missing `blob:` in `worker-src` | Add `worker-src 'self' blob:` or use `workerUrl` with self-hosted worker |
1127
+ | High replay volume / storage costs | `replaysSessionSampleRate` too high | Lower it; keep `replaysOnErrorSampleRate: 1.0` for error coverage |
1128
+ | Replay blocked by ad-blockers | Direct requests to Sentry ingest | Set `tunnel: "/sentry-tunnel"` and implement a server-side relay |
1129
+
1130
+ ---
1131
+
1132
+ ## Complete Configuration Reference
1133
+
1134
+ ```typescript
1135
+ import * as Sentry from "@sentry/react";
1136
+
1137
+ Sentry.init({
1138
+ dsn: import.meta.env.VITE_SENTRY_DSN,
1139
+
1140
+ // ── SAMPLING (init-level, not in replayIntegration) ────────────
1141
+ replaysSessionSampleRate: 0.1, // 10% full session recording
1142
+ replaysOnErrorSampleRate: 1.0, // 100% error-triggered recording
1143
+
1144
+ integrations: [
1145
+ Sentry.replayIntegration({
1146
+ // ── SESSION ──────────────────────────────────────────────
1147
+ stickySession: true, // survive page refreshes
1148
+ minReplayDuration: 5000, // discard replays < 5s (max: 15000)
1149
+ maxReplayDuration: 3600000, // cap at 1 hour (server limit)
1150
+
1151
+ // ── PRIVACY ──────────────────────────────────────────────
1152
+ maskAllText: true, // mask all text (default: true)
1153
+ maskAllInputs: true, // mask all inputs (default: true)
1154
+ blockAllMedia: true, // block all media (default: true)
1155
+ mask: [".pii", "[data-sensitive]"],
1156
+ unmask: [".app-chrome", "nav", ".public-label"],
1157
+ block: ["#payment-form", ".ssn-widget"],
1158
+ unblock: [".company-logo", ".product-thumbnail"],
1159
+ ignore: ["#otp-field"],
1160
+ maskFn: (text) => "*".repeat(text.length),
1161
+
1162
+ // ── NETWORK ──────────────────────────────────────────────
1163
+ networkDetailAllowUrls: [window.location.origin, "https://api.myapp.com"],
1164
+ networkDetailDenyUrls: ["/api/auth", "/api/payment", /\/admin\//],
1165
+ networkCaptureBodies: true,
1166
+ networkRequestHeaders: ["X-Request-ID", "Cache-Control"],
1167
+ networkResponseHeaders: ["X-Request-ID", "X-Response-Time"],
1168
+
1169
+ // ── DOM PROTECTION ────────────────────────────────────────
1170
+ mutationLimit: 10000,
1171
+ mutationBreadcrumbLimit: 750,
1172
+ slowClickIgnoreSelectors: ["a[download]", ".copy-btn", ".print-btn"],
1173
+
1174
+ // ── ADVANCED ─────────────────────────────────────────────
1175
+ workerUrl: "/assets/sentry-replay-worker.min.js", // if strict CSP
1176
+ beforeAddRecordingEvent: (event) => {
1177
+ if (event.data.tag === "breadcrumb" && event.data.payload?.category === "console")
1178
+ return null;
1179
+ return event;
1180
+ },
1181
+ beforeErrorSampling: (event) => {
1182
+ if (event.logger === "console") return false;
1183
+ return true;
1184
+ },
1185
+ }),
1186
+
1187
+ // Canvas recording (optional):
1188
+ Sentry.replayCanvasIntegration({
1189
+ enableManualSnapshot: false, // set true for WebGL/WebGPU
1190
+ }),
1191
+ ],
1192
+ });
1193
+ ```