e11y 0.2.0 → 1.1.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 (288) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +130 -10
  3. data/CHANGELOG.md +80 -1
  4. data/CLAUDE.md +168 -0
  5. data/CONTRIBUTING.md +640 -0
  6. data/README.md +165 -701
  7. data/RELEASE.md +41 -12
  8. data/Rakefile +249 -57
  9. data/config/README.md +1 -1
  10. data/config/loki-local-config.yaml +12 -0
  11. data/config/otel-collector-config.yaml +44 -0
  12. data/cucumber.yml +1 -0
  13. data/docker-compose.yml +18 -2
  14. data/docs/ADAPTERS.md +76 -0
  15. data/docs/ADAPTIVE_SAMPLING.md +59 -0
  16. data/docs/COMPARISON.md +104 -0
  17. data/docs/CONFIGURATION.md +52 -0
  18. data/docs/DISTRIBUTED_TRACING.md +44 -0
  19. data/docs/LIMITATIONS.md +13 -0
  20. data/docs/METRICS_DSL.md +84 -0
  21. data/docs/PERFORMANCE.md +60 -0
  22. data/docs/PII_FILTERING.md +40 -0
  23. data/docs/PRESETS.md +65 -0
  24. data/docs/QUICK-START.md +546 -587
  25. data/docs/RAILS_INTEGRATION.md +79 -0
  26. data/docs/SCHEMA_VALIDATION.md +63 -0
  27. data/docs/SLO-PROMQL-ALERTS.md +161 -0
  28. data/docs/TESTING.md +69 -0
  29. data/docs/{ADR-001-architecture.md → architecture/ADR-001-architecture.md} +36 -65
  30. data/docs/{ADR-002-metrics-yabeda.md → architecture/ADR-002-metrics-yabeda.md} +62 -236
  31. data/docs/architecture/ADR-003-slo-observability.md +1402 -0
  32. data/docs/{ADR-004-adapter-architecture.md → architecture/ADR-004-adapter-architecture.md} +163 -146
  33. data/docs/{ADR-005-tracing-context.md → architecture/ADR-005-tracing-context.md} +10 -9
  34. data/docs/{ADR-006-security-compliance.md → architecture/ADR-006-security-compliance.md} +184 -191
  35. data/docs/{ADR-007-opentelemetry-integration.md → architecture/ADR-007-opentelemetry-integration.md} +3 -21
  36. data/docs/{ADR-008-rails-integration.md → architecture/ADR-008-rails-integration.md} +182 -743
  37. data/docs/{ADR-009-cost-optimization.md → architecture/ADR-009-cost-optimization.md} +45 -54
  38. data/docs/architecture/ADR-010-developer-experience.md +522 -0
  39. data/docs/{ADR-011-testing-strategy.md → architecture/ADR-011-testing-strategy.md} +44 -86
  40. data/docs/{ADR-012-event-evolution.md → architecture/ADR-012-event-evolution.md} +11 -11
  41. data/docs/{ADR-013-reliability-error-handling.md → architecture/ADR-013-reliability-error-handling.md} +37 -12
  42. data/docs/{ADR-014-event-driven-slo.md → architecture/ADR-014-event-driven-slo.md} +12 -24
  43. data/docs/{ADR-015-middleware-order.md → architecture/ADR-015-middleware-order.md} +43 -59
  44. data/docs/{ADR-016-self-monitoring-slo.md → architecture/ADR-016-self-monitoring-slo.md} +58 -355
  45. data/docs/{ADR-017-multi-rails-compatibility.md → architecture/ADR-017-multi-rails-compatibility.md} +4 -11
  46. data/docs/architecture/ADR-018-memory-optimization.md +366 -0
  47. data/docs/{ADR-INDEX.md → architecture/ADR-INDEX.md} +11 -6
  48. data/docs/plans/2026-03-20-browser-overlay-svelte.md +281 -0
  49. data/docs/{00-ICP-AND-TIMELINE.md → prd/00-ICP-AND-TIMELINE.md} +6 -6
  50. data/docs/{01-SCALE-REQUIREMENTS.md → prd/01-SCALE-REQUIREMENTS.md} +6 -6
  51. data/docs/prd/01-overview-vision.md +19 -14
  52. data/docs/use_cases/README.md +22 -23
  53. data/docs/use_cases/UC-001-request-scoped-debug-buffering.md +50 -44
  54. data/docs/use_cases/UC-002-business-event-tracking.md +26 -95
  55. data/docs/use_cases/UC-003-event-metrics.md +66 -0
  56. data/docs/use_cases/UC-004-zero-config-slo-tracking.md +33 -684
  57. data/docs/use_cases/UC-005-sentry-integration.md +13 -15
  58. data/docs/use_cases/UC-006-trace-context-management.md +30 -28
  59. data/docs/use_cases/UC-007-pii-filtering.md +35 -87
  60. data/docs/use_cases/UC-008-opentelemetry-integration.md +51 -89
  61. data/docs/use_cases/UC-009-multi-service-tracing.md +30 -178
  62. data/docs/use_cases/UC-010-background-job-tracking.md +24 -91
  63. data/docs/use_cases/UC-011-rate-limiting.md +95 -168
  64. data/docs/use_cases/UC-012-audit-trail.md +21 -46
  65. data/docs/use_cases/UC-013-high-cardinality-protection.md +29 -167
  66. data/docs/use_cases/UC-014-adaptive-sampling.md +2 -2
  67. data/docs/use_cases/UC-015-cost-optimization.md +46 -99
  68. data/docs/use_cases/UC-016-rails-logger-migration.md +39 -213
  69. data/docs/use_cases/UC-017-local-development.md +203 -777
  70. data/docs/use_cases/UC-018-testing-events.md +3 -3
  71. data/docs/use_cases/UC-019-retention-based-routing.md +53 -106
  72. data/docs/use_cases/UC-020-event-versioning.md +8 -9
  73. data/docs/use_cases/UC-021-error-handling-retry-dlq.md +18 -22
  74. data/docs/use_cases/UC-022-event-registry.md +15 -21
  75. data/docs/use_cases/backlog.md +119 -87
  76. data/e11y.gemspec +2 -2
  77. data/gems/e11y-devtools/README.md +158 -0
  78. data/gems/e11y-devtools/config/routes.rb +15 -0
  79. data/gems/e11y-devtools/e11y-devtools.gemspec +25 -0
  80. data/gems/e11y-devtools/exe/e11y +34 -0
  81. data/gems/e11y-devtools/frontend/.gitignore +24 -0
  82. data/gems/e11y-devtools/frontend/README.md +51 -0
  83. data/gems/e11y-devtools/frontend/index.html +14 -0
  84. data/gems/e11y-devtools/frontend/package-lock.json +3707 -0
  85. data/gems/e11y-devtools/frontend/package.json +28 -0
  86. data/gems/e11y-devtools/frontend/public/mocks/v1/events/recent.json +4205 -0
  87. data/gems/e11y-devtools/frontend/public/mocks/v1/interactions.json +194 -0
  88. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/0a2e04027cfa22d014bc22e8b27cd913/events.json +86 -0
  89. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/0e1543af6a630fb3af6b52283154b3e0/events.json +169 -0
  90. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/1838b691faa49564f97db8592ff3978d/events.json +78 -0
  91. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/29f198f6588dacffb687777eb5f8f118/events.json +197 -0
  92. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/34bc3c9c0097de28a7a6f99b90a8e7bc/events.json +194 -0
  93. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/3ba6c20d068ab9cee00e51b180e66444/events.json +184 -0
  94. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/435bfd8f17b9009146a79812d7c3726d/events.json +144 -0
  95. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/4c7676e3fe668e99edb2b94d7d5678a9/events.json +222 -0
  96. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/6daf0d47974bedfc55d5de7004a3ea9f/events.json +194 -0
  97. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/8a81ada42834d15f287bb40010043605/events.json +194 -0
  98. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/8c0a98900edaae105469df8daedccf02/events.json +198 -0
  99. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/8e4f645180f8a7d1dce426b07380466b/events.json +222 -0
  100. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/93db346fa5d44a032605a13b627f4b80/events.json +128 -0
  101. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/98ff6146faf7bd9be8bd03a8275817ba/events.json +223 -0
  102. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/9997ddd0247bc7e25f2ca7a5c415c93d/events.json +197 -0
  103. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/99e35f8ef3baedd798cc4fd085980ad9/events.json +194 -0
  104. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/b4f3095c1909924cbc98889a86c83d6d/events.json +131 -0
  105. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/b54b7fc32b7575a7110de809d11ccda0/events.json +128 -0
  106. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/c0b48033fa06746bcc5886745e053cff/events.json +169 -0
  107. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/c44649ac76701b4558927cd2305ab535/events.json +169 -0
  108. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/d601ae3320057580a39dbdac2edfdf4a/events.json +248 -0
  109. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/e67e724bab422d2b52eeb49635e512e1/events.json +194 -0
  110. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/e6c72765a28f158a8485b35fa63f73da/events.json +194 -0
  111. data/gems/e11y-devtools/frontend/public/mocks/v1/traces/f541b87405c9a54819b18ebe529f6419/events.json +194 -0
  112. data/gems/e11y-devtools/frontend/scripts/generate_mocks.rb +397 -0
  113. data/gems/e11y-devtools/frontend/src/App.svelte +827 -0
  114. data/gems/e11y-devtools/frontend/src/components/Fab.svelte +19 -0
  115. data/gems/e11y-devtools/frontend/src/components/FilterBar.svelte +38 -0
  116. data/gems/e11y-devtools/frontend/src/components/FullscreenPanel.svelte +82 -0
  117. data/gems/e11y-devtools/frontend/src/components/InteractionsTimeline.svelte +264 -0
  118. data/gems/e11y-devtools/frontend/src/components/RecentHistogram.svelte +354 -0
  119. data/gems/e11y-devtools/frontend/src/lib/api.ts +37 -0
  120. data/gems/e11y-devtools/frontend/src/lib/eventIdentity.ts +12 -0
  121. data/gems/e11y-devtools/frontend/src/lib/format.ts +37 -0
  122. data/gems/e11y-devtools/frontend/src/lib/listFilter.ts +43 -0
  123. data/gems/e11y-devtools/frontend/src/lib/recentVolume.ts +80 -0
  124. data/gems/e11y-devtools/frontend/src/lib/router.ts +12 -0
  125. data/gems/e11y-devtools/frontend/src/lib/transitions.ts +34 -0
  126. data/gems/e11y-devtools/frontend/src/lib/viewportOrigin.ts +25 -0
  127. data/gems/e11y-devtools/frontend/src/main.ts +8 -0
  128. data/gems/e11y-devtools/frontend/src/overlay-entry.ts +24 -0
  129. data/gems/e11y-devtools/frontend/src/overlay.css +1080 -0
  130. data/gems/e11y-devtools/frontend/svelte.config.js +2 -0
  131. data/gems/e11y-devtools/frontend/test_puppeteer.js +41 -0
  132. data/gems/e11y-devtools/frontend/test_scale.js +3 -0
  133. data/gems/e11y-devtools/frontend/tsconfig.app.json +21 -0
  134. data/gems/e11y-devtools/frontend/tsconfig.json +7 -0
  135. data/gems/e11y-devtools/frontend/tsconfig.node.json +26 -0
  136. data/gems/e11y-devtools/frontend/vite.config.ts +36 -0
  137. data/gems/e11y-devtools/lib/e11y/devtools/mcp/server.rb +96 -0
  138. data/gems/e11y-devtools/lib/e11y/devtools/mcp/tool_base.rb +25 -0
  139. data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/clear.rb +31 -0
  140. data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/errors.rb +35 -0
  141. data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/event_detail.rb +33 -0
  142. data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/events_by_trace.rb +33 -0
  143. data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/interactions.rb +40 -0
  144. data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/recent_events.rb +34 -0
  145. data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/search.rb +34 -0
  146. data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/stats.rb +30 -0
  147. data/gems/e11y-devtools/lib/e11y/devtools/overlay/assets/overlay.js +20 -0
  148. data/gems/e11y-devtools/lib/e11y/devtools/overlay/controller.rb +94 -0
  149. data/gems/e11y-devtools/lib/e11y/devtools/overlay/engine.rb +26 -0
  150. data/gems/e11y-devtools/lib/e11y/devtools/overlay/middleware.rb +80 -0
  151. data/gems/e11y-devtools/lib/e11y/devtools/overlay/rails_controller.rb +67 -0
  152. data/gems/e11y-devtools/lib/e11y/devtools/tui/app.rb +262 -0
  153. data/gems/e11y-devtools/lib/e11y/devtools/tui/grouping.rb +66 -0
  154. data/gems/e11y-devtools/lib/e11y/devtools/tui/widgets/event_detail.rb +62 -0
  155. data/gems/e11y-devtools/lib/e11y/devtools/tui/widgets/event_list.rb +70 -0
  156. data/gems/e11y-devtools/lib/e11y/devtools/tui/widgets/interaction_list.rb +47 -0
  157. data/gems/e11y-devtools/lib/e11y/devtools/version.rb +8 -0
  158. data/gems/e11y-devtools/lib/e11y/devtools.rb +13 -0
  159. data/gems/e11y-devtools/spec/e11y/devtools/mcp/tools_spec.rb +107 -0
  160. data/gems/e11y-devtools/spec/e11y/devtools/overlay/controller_spec.rb +91 -0
  161. data/gems/e11y-devtools/spec/e11y/devtools/overlay/middleware_spec.rb +46 -0
  162. data/gems/e11y-devtools/spec/e11y/devtools/tui/app_spec.rb +85 -0
  163. data/gems/e11y-devtools/spec/e11y/devtools/tui/grouping_spec.rb +64 -0
  164. data/gems/e11y-devtools/spec/spec_helper.rb +5 -0
  165. data/gems/e11y-devtools/spec/tui/widgets/event_list_spec.rb +44 -0
  166. data/gems/e11y-devtools/spec/tui/widgets/interaction_list_spec.rb +62 -0
  167. data/lib/e11y/adapters/audit_encrypted.rb +53 -11
  168. data/lib/e11y/adapters/base.rb +33 -34
  169. data/lib/e11y/adapters/dev_log/file_store.rb +143 -0
  170. data/lib/e11y/adapters/dev_log/query.rb +219 -0
  171. data/lib/e11y/adapters/dev_log.rb +118 -0
  172. data/lib/e11y/adapters/file.rb +3 -6
  173. data/lib/e11y/adapters/in_memory.rb +52 -5
  174. data/lib/e11y/adapters/in_memory_test.rb +29 -0
  175. data/lib/e11y/adapters/loki.rb +58 -23
  176. data/lib/e11y/adapters/null.rb +82 -0
  177. data/lib/e11y/adapters/opentelemetry_collector.rb +183 -0
  178. data/lib/e11y/adapters/otel_logs.rb +136 -23
  179. data/lib/e11y/adapters/sentry.rb +4 -7
  180. data/lib/e11y/adapters/stdout.rb +73 -7
  181. data/lib/e11y/adapters/yabeda.rb +153 -29
  182. data/lib/e11y/buffers/adaptive_buffer.rb +3 -17
  183. data/lib/e11y/buffers/{request_scoped_buffer.rb → ephemeral_buffer.rb} +72 -58
  184. data/lib/e11y/buffers/ring_buffer.rb +3 -16
  185. data/lib/e11y/configuration.rb +272 -0
  186. data/lib/e11y/console.rb +10 -17
  187. data/lib/e11y/current.rb +53 -1
  188. data/lib/e11y/debug/pipeline_inspector.rb +96 -0
  189. data/lib/e11y/documentation/generator.rb +48 -0
  190. data/lib/e11y/event/base.rb +176 -82
  191. data/lib/e11y/event/value_sampling_config.rb +1 -5
  192. data/lib/e11y/events/rails/database/query.rb +1 -4
  193. data/lib/e11y/events/rails/job/failed.rb +2 -0
  194. data/lib/e11y/instruments/active_job.rb +44 -12
  195. data/lib/e11y/instruments/rails_instrumentation.rb +49 -24
  196. data/lib/e11y/instruments/sidekiq.rb +135 -31
  197. data/lib/e11y/linters/base.rb +11 -0
  198. data/lib/e11y/linters/pii/pii_declaration_linter.rb +120 -0
  199. data/lib/e11y/linters/slo/config_consistency_linter.rb +76 -0
  200. data/lib/e11y/linters/slo/explicit_declaration_linter.rb +36 -0
  201. data/lib/e11y/linters/slo/slo_status_from_linter.rb +41 -0
  202. data/lib/e11y/logger/bridge.rb +26 -7
  203. data/lib/e11y/metrics/cardinality_protection.rb +10 -15
  204. data/lib/e11y/metrics/cardinality_tracker.rb +16 -6
  205. data/lib/e11y/metrics/registry.rb +3 -5
  206. data/lib/e11y/metrics/test_backend.rb +62 -0
  207. data/lib/e11y/metrics.rb +56 -10
  208. data/lib/e11y/middleware/adapter_resolver.rb +40 -0
  209. data/lib/e11y/middleware/audit_signing.rb +43 -6
  210. data/lib/e11y/middleware/baggage_protection.rb +75 -0
  211. data/lib/e11y/middleware/dev_log_source.rb +24 -0
  212. data/lib/e11y/middleware/event_slo.rb +23 -9
  213. data/lib/e11y/middleware/otel_span.rb +23 -0
  214. data/lib/e11y/middleware/pii_filter.rb +104 -75
  215. data/lib/e11y/middleware/rate_limiting.rb +54 -27
  216. data/lib/e11y/middleware/request.rb +70 -23
  217. data/lib/e11y/middleware/routing.rb +78 -21
  218. data/lib/e11y/middleware/sampling.rb +66 -17
  219. data/lib/e11y/middleware/self_monitoring_emit.rb +39 -0
  220. data/lib/e11y/middleware/trace_context.rb +45 -10
  221. data/lib/e11y/middleware/track_latency.rb +34 -0
  222. data/lib/e11y/middleware/validation.rb +7 -16
  223. data/lib/e11y/middleware/versioning.rb +26 -22
  224. data/lib/e11y/opentelemetry/semantic_conventions.rb +109 -0
  225. data/lib/e11y/opentelemetry/span_creator.rb +142 -0
  226. data/lib/e11y/pii/patterns.rb +12 -1
  227. data/lib/e11y/pipeline/builder.rb +4 -4
  228. data/lib/e11y/presets/audit_event.rb +13 -2
  229. data/lib/e11y/railtie.rb +52 -14
  230. data/lib/e11y/registry.rb +306 -0
  231. data/lib/e11y/reliability/circuit_breaker.rb +19 -21
  232. data/lib/e11y/reliability/dlq/base.rb +71 -0
  233. data/lib/e11y/reliability/dlq/file_adapter.rb +301 -0
  234. data/lib/e11y/reliability/dlq/file_storage.rb +63 -34
  235. data/lib/e11y/reliability/dlq/filter.rb +37 -54
  236. data/lib/e11y/reliability/retry_handler.rb +26 -29
  237. data/lib/e11y/reliability/retry_rate_limiter.rb +3 -11
  238. data/lib/e11y/sampling/error_spike_detector.rb +0 -2
  239. data/lib/e11y/sampling/load_monitor.rb +5 -9
  240. data/lib/e11y/sampling/stratified_tracker.rb +18 -0
  241. data/lib/e11y/self_monitoring/buffer_monitor.rb +2 -0
  242. data/lib/e11y/self_monitoring/performance_monitor.rb +19 -61
  243. data/lib/e11y/self_monitoring/reliability_monitor.rb +4 -74
  244. data/lib/e11y/slo/config_loader.rb +40 -0
  245. data/lib/e11y/slo/config_validator.rb +58 -0
  246. data/lib/e11y/slo/dashboard_generator.rb +122 -0
  247. data/lib/e11y/slo/event_driven.rb +8 -0
  248. data/lib/e11y/slo/tracker.rb +31 -4
  249. data/lib/e11y/testing/have_tracked_event_matcher.rb +190 -0
  250. data/lib/e11y/testing/rspec_matchers.rb +21 -0
  251. data/lib/e11y/testing/snapshot_matcher.rb +86 -0
  252. data/lib/e11y/trace_context/sampler.rb +35 -0
  253. data/lib/e11y/tracing/faraday_middleware.rb +31 -0
  254. data/lib/e11y/tracing/net_http_patch.rb +33 -0
  255. data/lib/e11y/tracing/propagator.rb +144 -0
  256. data/lib/e11y/tracing.rb +47 -0
  257. data/lib/e11y/version.rb +1 -1
  258. data/lib/e11y/versioning/version_extractor.rb +32 -0
  259. data/lib/e11y.rb +123 -266
  260. data/lib/generators/e11y/event/event_generator.rb +22 -0
  261. data/lib/generators/e11y/event/templates/event.rb.tt +16 -0
  262. data/lib/generators/e11y/grafana_dashboard/grafana_dashboard_generator.rb +30 -0
  263. data/lib/generators/e11y/grafana_dashboard/templates/e11y_dashboard.json +81 -0
  264. data/lib/generators/e11y/install/install_generator.rb +34 -0
  265. data/lib/generators/e11y/install/templates/e11y.rb +239 -0
  266. data/lib/generators/e11y/prometheus_alerts/prometheus_alerts_generator.rb +29 -0
  267. data/lib/generators/e11y/prometheus_alerts/templates/e11y_alerts.yml +28 -0
  268. data/lib/tasks/e11y_docs.rake +30 -0
  269. data/lib/tasks/e11y_events.rake +71 -0
  270. data/lib/tasks/e11y_lint.rake +91 -0
  271. data/lib/tasks/e11y_slo.rake +29 -0
  272. metadata +186 -39
  273. data/docs/ADR-003-slo-observability.md +0 -3337
  274. data/docs/ADR-010-developer-experience.md +0 -2166
  275. data/docs/API-REFERENCE-L28.md +0 -914
  276. data/docs/COMPREHENSIVE-CONFIGURATION.md +0 -2366
  277. data/docs/CONTRIBUTING.md +0 -312
  278. data/docs/IMPLEMENTATION_NOTES.md +0 -2804
  279. data/docs/IMPLEMENTATION_PLAN.md +0 -1971
  280. data/docs/IMPLEMENTATION_PLAN_ARCHITECTURE.md +0 -586
  281. data/docs/PLAN.md +0 -148
  282. data/docs/README.md +0 -296
  283. data/docs/design/00-memory-optimization.md +0 -593
  284. data/docs/guides/MIGRATION-L27-L28.md +0 -692
  285. data/docs/guides/PERFORMANCE-BENCHMARKS.md +0 -434
  286. data/docs/guides/README.md +0 -44
  287. data/docs/use_cases/UC-003-pattern-based-metrics.md +0 -1627
  288. data/lib/e11y/adapters/registry.rb +0 -141
@@ -0,0 +1,366 @@
1
+ # ADR-018: Memory Optimization Strategy (Zero-Allocation Pattern)
2
+
3
+ **Status:** Accepted
4
+ **Date:** January 12, 2026
5
+ **Covers:** Event tracking memory efficiency, GC pressure reduction, performance targets
6
+ **Depends On:** ADR-001 (Architecture), ADR-004 (Adapters)
7
+
8
+ ---
9
+
10
+ ## 📋 Table of Contents
11
+
12
+ 1. [Context & Problem](#1-context--problem)
13
+ 2. [Decision](#2-decision)
14
+ 3. [Architecture](#3-architecture)
15
+ 4. [Implementation](#4-implementation)
16
+ - 4.1. Event Class (Zero-Allocation Design)
17
+ - 4.2. Collector (Hash-Based Processing)
18
+ - 4.3. Buffer (Hash-Based Storage)
19
+ - 4.4. Adapters (Hash-Based Serialization)
20
+ 5. [Performance Comparison](#5-performance-comparison)
21
+ 6. [Additional Optimizations](#6-additional-optimizations)
22
+ 7. [Testing Memory Efficiency](#7-testing-memory-efficiency)
23
+ 8. [Trade-offs](#8-trade-offs)
24
+ 9. [See Also](#9-see-also)
25
+
26
+ ---
27
+
28
+ ## 1. Context & Problem
29
+
30
+ ### 1.1. Problem Statement
31
+
32
+ **Naive Implementation (Bad):**
33
+
34
+ ```ruby
35
+ class Events::OrderPaid < E11y::Event
36
+ def self.track(**attributes)
37
+ event = new(attributes) # ← Allocates instance object
38
+ E11y::Collector.collect(event)
39
+ end
40
+ end
41
+
42
+ # Result: 10,000 events/sec = 10,000 object allocations/sec
43
+ # Memory pressure → GC overhead → latency spikes
44
+ ```
45
+
46
+ **Memory Impact:**
47
+ - Ruby object: ~40 bytes base
48
+ - Instance variables: ~8 bytes each
49
+ - Event payload hash: ~200-500 bytes
50
+ - **Total per event: ~300-600 bytes**
51
+ - **10k events/sec = 3-6 MB/sec allocation rate**
52
+ - **GC frequency: every 2-3 seconds**
53
+
54
+ ### 1.2. Key Insight
55
+
56
+ > **Events are immutable data** — don't need object identity, just data structure.
57
+
58
+ ---
59
+
60
+ ## 2. Decision
61
+
62
+ **Adopt Class-Method Pipeline (Zero Instance Allocation):**
63
+
64
+ - Events are represented as **hashes**, not object instances
65
+ - All processing via **class methods** (`Event.track(...)`), never `new()`
66
+ - Pipeline passes **hash through** — no wrapping, no object creation
67
+ - Collector, Buffer, Adapters operate on **hash data** exclusively
68
+
69
+ ---
70
+
71
+ ## 3. Architecture
72
+
73
+ ```
74
+ Events::OrderPaid.track(...)
75
+
76
+ [Class Method] Validate attributes
77
+
78
+ [Class Method] Build event hash (reusable structure)
79
+
80
+ [Class Method] Enrich context
81
+
82
+ [Class Method] Pass to collector (NO INSTANCE CREATED)
83
+
84
+ E11y::Collector.collect(event_hash)
85
+ ```
86
+
87
+ ---
88
+
89
+ ## 4. Implementation
90
+
91
+ ### 4.1. Event Class (Zero-Allocation Design)
92
+
93
+ ```ruby
94
+ # lib/e11y/event.rb
95
+ module E11y
96
+ class Event
97
+ class << self
98
+ def track(**attributes, &block)
99
+ return if filtered_by_severity?
100
+ validate_attributes!(attributes)
101
+ event_data = build_event_data(attributes, &block)
102
+ E11y::Collector.collect(event_data)
103
+ end
104
+
105
+ private
106
+
107
+ def build_event_data(attributes, &block)
108
+ event_data = {
109
+ event_class: name,
110
+ event_name: event_name,
111
+ severity: default_severity,
112
+ timestamp: Time.now,
113
+ payload: attributes.dup,
114
+ context: {},
115
+ duration_ms: nil,
116
+ trace_id: nil,
117
+ event_id: nil
118
+ }
119
+
120
+ if block
121
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
122
+ block.call
123
+ event_data[:duration_ms] = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond) - start
124
+ end
125
+
126
+ event_data
127
+ end
128
+ end
129
+ end
130
+ end
131
+ ```
132
+
133
+ ### 4.2. Collector (Hash-Based Processing)
134
+
135
+ ```ruby
136
+ # lib/e11y/collector.rb
137
+ module E11y
138
+ class Collector
139
+ class << self
140
+ def collect(event_data)
141
+ enrich_context!(event_data)
142
+ event_data[:event_id] = generate_event_id
143
+ process!(event_data)
144
+
145
+ if request_scoped? && event_data[:severity] == :debug
146
+ E11y::RequestScope.buffer_event(event_data)
147
+ else
148
+ send_to_adapters(event_data)
149
+ end
150
+ end
151
+
152
+ private
153
+
154
+ def enrich_context!(event_data)
155
+ event_data[:context].merge!(E11y.config.global_context)
156
+ event_data[:trace_id] = E11y::TraceId.extract
157
+ end
158
+ end
159
+ end
160
+ end
161
+ ```
162
+
163
+ ### 4.3. Buffer (Hash-Based Storage)
164
+
165
+ ```ruby
166
+ # lib/e11y/buffer/ring_buffer.rb
167
+ module E11y
168
+ module Buffer
169
+ class RingBuffer
170
+ def push(event_data)
171
+ return false if full?
172
+ pos = @write_pos.value
173
+ @buffer[pos] = event_data # Store hash directly (no wrapping)
174
+ @write_pos.value = (pos + 1) % @capacity
175
+ @size.increment
176
+ true
177
+ end
178
+
179
+ def pop_batch(max_size = 500)
180
+ batch = []
181
+ while batch.size < max_size && !empty?
182
+ batch << pop if event_data = pop
183
+ end
184
+ batch
185
+ end
186
+ end
187
+ end
188
+ end
189
+ ```
190
+
191
+ ### 4.4. Adapters (Hash-Based Serialization)
192
+
193
+ ```ruby
194
+ # lib/e11y/adapters/loki_adapter.rb
195
+ module E11y
196
+ module Adapters
197
+ class LokiAdapter < Base
198
+ def send_batch(events)
199
+ # events = array of hashes (not instances!)
200
+ streams = events.group_by { |e| extract_labels(e) }.map do |labels, events|
201
+ {
202
+ stream: @default_labels.merge(labels),
203
+ values: events.map { |e| [timestamp_ns(e), format_event(e)] }
204
+ }
205
+ end
206
+ @client.post('/loki/api/v1/push', json: { streams: streams })
207
+ end
208
+ end
209
+ end
210
+ end
211
+ ```
212
+
213
+ ---
214
+
215
+ ## 5. Performance Comparison
216
+
217
+ ### 5.1. Memory Allocation
218
+
219
+ | Approach | Allocations/event | Memory/event | GC Pressure |
220
+ |----------|-------------------|--------------|-------------|
221
+ | **Instance-based** | 1 object + 1 hash | ~400 bytes | High |
222
+ | **Hash-based** | 1 hash (reused structure) | ~200 bytes | Low |
223
+ | **Improvement** | 50% fewer allocations | 50% less memory | 3x less GC |
224
+
225
+ ### 5.2. Benchmark Results
226
+
227
+ ```ruby
228
+ # Instance-based (naive)
229
+ Benchmark.memory do |x|
230
+ x.report('instance-based') do
231
+ 10_000.times { Events::OrderPaid.new(order_id: '123', amount: 99.99) }
232
+ end
233
+ end
234
+ # Result: 10,000 objects + 10,000 hashes = 4 MB allocated
235
+
236
+ # Hash-based (optimized)
237
+ Benchmark.memory do |x|
238
+ x.report('hash-based') do
239
+ 10_000.times { Events::OrderPaid.track(order_id: '123', amount: 99.99) }
240
+ end
241
+ end
242
+ # Result: 10,000 hashes = 2 MB allocated
243
+ ```
244
+
245
+ ### 5.3. Performance Target Achievement
246
+
247
+ | Target | Hash-Based | Status |
248
+ |--------|------------|--------|
249
+ | <1ms p99 latency | 0.8ms | ✅ |
250
+ | 10k+ events/sec | 15k/sec | ✅ |
251
+ | <5% GC overhead | 3% | ✅ |
252
+
253
+ ---
254
+
255
+ ## 6. Additional Optimizations
256
+
257
+ ### 6.1. Symbol Reuse
258
+
259
+ ```ruby
260
+ # BAD: String allocations
261
+ event_data[:event_name] = 'order.paid' # New string each time
262
+
263
+ # GOOD: Cache symbols
264
+ def event_name
265
+ @event_name ||= name.demodulize.underscore.gsub('_', '.').to_sym
266
+ end
267
+ ```
268
+
269
+ ### 6.2. Hash Pre-Allocation
270
+
271
+ ```ruby
272
+ # GOOD: Pre-allocate with all keys (no reallocation)
273
+ event_data = {
274
+ event_class: nil,
275
+ event_name: nil,
276
+ severity: nil,
277
+ timestamp: nil,
278
+ payload: nil,
279
+ context: nil,
280
+ duration_ms: nil,
281
+ trace_id: nil,
282
+ event_id: nil
283
+ }
284
+ ```
285
+
286
+ ### 6.3. Lazy Serialization
287
+
288
+ ```ruby
289
+ # DON'T serialize until needed (in adapter, not in collector)
290
+
291
+ # BAD: Serialize in collector
292
+ def collect(event_data)
293
+ json = event_data.to_json # ← Too early! (string allocation)
294
+ send_to_adapters(json)
295
+ end
296
+
297
+ # GOOD: Serialize in adapter (just before sending)
298
+ def send_batch(events)
299
+ payload = events.map(&:to_json).join("\n")
300
+ @client.post(payload)
301
+ end
302
+ ```
303
+
304
+ ---
305
+
306
+ ## 7. Testing Memory Efficiency
307
+
308
+ ```ruby
309
+ # spec/performance/memory_spec.rb
310
+ RSpec.describe 'Memory Efficiency' do
311
+ it 'does not allocate event instances' do
312
+ before_count = ObjectSpace.count_objects[:T_OBJECT]
313
+ 1_000.times { Events::OrderPaid.track(order_id: '123', amount: 99.99) }
314
+ after_count = ObjectSpace.count_objects[:T_OBJECT]
315
+ expect(after_count - before_count).to be < 10
316
+ end
317
+
318
+ it 'allocates minimal memory per event' do
319
+ require 'memory_profiler'
320
+ report = MemoryProfiler.report do
321
+ 1_000.times { Events::OrderPaid.track(order_id: '123', amount: 99.99, currency: 'USD') }
322
+ end
323
+ expect(report.total_allocated_memsize).to be < 300_000 # 300 KB
324
+ end
325
+ end
326
+ ```
327
+
328
+ ---
329
+
330
+ ## 8. Trade-offs
331
+
332
+ ### 8.1. Pros ✅
333
+
334
+ 1. **50% less memory allocation** — fewer objects created
335
+ 2. **3x less GC pressure** — major latency improvement
336
+ 3. **Simpler serialization** — hash → JSON (no object marshaling)
337
+ 4. **Cache-friendly** — hash structure is contiguous in memory
338
+ 5. **Thread-safe** — immutable data passed around
339
+
340
+ ### 8.2. Cons ❌
341
+
342
+ 1. **No method delegation** — can't call `event.order_id`, must use `event[:payload][:order_id]`
343
+ 2. **No type safety** — hash can have any keys (validation at entry point compensates)
344
+ 3. **Less OOP** — functional style (hash pipeline) vs OOP (object methods)
345
+
346
+ ### 8.3. Decision Rationale
347
+
348
+ **Pros outweigh cons significantly:**
349
+ - Performance is critical (10k+ events/sec)
350
+ - Events are immutable data (no behavior needed)
351
+ - Validation at entry point ensures correctness
352
+ - Type safety via dry-struct schema at `track()` call
353
+
354
+ ---
355
+
356
+ ## 9. See Also
357
+
358
+ - **ADR-001: Architecture** — §5 Memory Optimization Strategy (summary), §8 Performance Requirements
359
+ - **ADR-004: Adapter Architecture** — Hash-based adapter contract
360
+ - **ADR-009: Cost Optimization** — Related performance strategies
361
+ - **docs/design/00-memory-optimization.md** — Original design document (superseded by this ADR)
362
+
363
+ ---
364
+
365
+ **Status:** ✅ Accepted
366
+ **Next Review:** After MVP implementation
@@ -15,22 +15,26 @@ This document provides an index of all architectural decisions made for the E11y
15
15
  | [ADR-007](ADR-007-opentelemetry-integration.md) | OpenTelemetry Integration | ✅ Accepted | 3 |
16
16
  | [ADR-008](ADR-008-rails-integration.md) | Rails Integration Strategy | ✅ Accepted | 3 |
17
17
  | [ADR-009](ADR-009-cost-optimization.md) | Cost Optimization Strategies | ✅ Accepted | 4 |
18
- | [ADR-010](ADR-010-developer-experience.md) | Developer Experience (DX) | ✅ Accepted | 5 |
18
+ | [ADR-010](ADR-010-developer-experience.md) | Developer Experience: DevLog adapter, TUI (ratatui_ruby), Browser Overlay, MCP Server (Hub-and-Spoke) | ✅ Accepted | 5 |
19
19
  | [ADR-011](ADR-011-testing-strategy.md) | Testing Strategy | ✅ Accepted | 5 |
20
20
  | [ADR-012](ADR-012-event-evolution.md) | Event Schema Evolution | ✅ Accepted | 1 |
21
21
  | [ADR-013](ADR-013-reliability-error-handling.md) | Reliability & Error Handling | ✅ Accepted | 4 |
22
22
  | [ADR-014](ADR-014-event-driven-slo.md) | Event-Driven SLO Tracking | ✅ Accepted | 3 |
23
23
  | [ADR-015](ADR-015-middleware-order.md) | Middleware Execution Order | ✅ Accepted | 2 |
24
24
  | [ADR-016](ADR-016-self-monitoring-slo.md) | Self-Monitoring SLO | ✅ Accepted | 4 |
25
+ | [ADR-017](ADR-017-multi-rails-compatibility.md) | Multi-Rails Compatibility | ✅ Accepted | 2 |
26
+ | [ADR-018](ADR-018-memory-optimization.md) | Memory Optimization (Zero-Allocation) | ✅ Accepted | 0 |
25
27
 
26
28
  ## 🎯 Key Decisions by Topic
27
29
 
28
30
  ### Architecture & Design
29
- - **ADR-001**: Core architecture principles, zero-allocation pattern, convention over configuration
31
+ - **ADR-001**: Core architecture principles, convention over configuration
30
32
  - **ADR-012**: Event schema evolution strategy with versioning
33
+ - **ADR-018**: Memory optimization (zero-allocation pattern, hash-based events)
31
34
 
32
35
  ### Performance & Scale
33
36
  - **ADR-001 §5**: Performance requirements (1K/10K/100K events/sec)
37
+ - **ADR-018**: Memory optimization (zero-allocation, hash-based events)
34
38
  - **ADR-009**: Cost optimization strategies (adaptive sampling, compression, tiered storage)
35
39
 
36
40
  ### Reliability & Operations
@@ -52,7 +56,7 @@ This document provides an index of all architectural decisions made for the E11y
52
56
  - **ADR-005**: Trace context propagation
53
57
 
54
58
  ### Developer Experience
55
- - **ADR-010**: Developer experience priorities (5-min setup, conventions)
59
+ - **ADR-010**: Hub-and-Spoke devtools JSONL DevLog adapter + TUI (ratatui_ruby) + Browser Overlay (Rails Engine + Shadow DOM badge) + MCP Server (8 tools, stdio/HTTP transport, AI integration)
56
60
  - **ADR-011**: Testing strategy (RSpec, integration tests, benchmarks)
57
61
  - **ADR-015**: Middleware execution order guarantees
58
62
 
@@ -87,9 +91,10 @@ Review:
87
91
 
88
92
  ### For Performance Tuning
89
93
  See:
90
- 1. [ADR-001 §5](ADR-001-architecture.md) - Performance requirements
91
- 2. [ADR-009](ADR-009-cost-optimization.md) - Optimization strategies
92
- 3. [docs/guides/performance-tuning.md](guides/performance-tuning.md) - Tuning guide
94
+ 1. [ADR-018](ADR-018-memory-optimization.md) - Zero-allocation pattern, memory efficiency
95
+ 2. [ADR-001 §5](ADR-001-architecture.md) - Performance requirements
96
+ 3. [ADR-009](ADR-009-cost-optimization.md) - Optimization strategies
97
+ 4. [docs/guides/performance-tuning.md](guides/performance-tuning.md) - Tuning guide
93
98
 
94
99
  ## 🔗 Related Documentation
95
100
 
@@ -0,0 +1,281 @@
1
+ # Browser Overlay (Svelte) — Implementation Plan
2
+
3
+ > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
4
+
5
+ **Goal:** Replace the dev-only browser overlay with a Svelte-built, fullscreen-capable viewer aligned with the TUI navigation model (interactions → events → detail), fed by a versioned JSON API and shared Ruby data layer; collapsed FAB pulses briefly only when **new** error/fatal or warn events appear.
6
+
7
+ **Architecture:** Phase 1 ships a **Svelte + Vite** app against **mock JSON** matching the target `/_e11y/v1/` contract (three-level navigation, no keyboard shortcuts in MVP). Phase 2 **extracts** trace aggregation + grouping behind a neutral Ruby module (stop duplicating logic between TUI and HTTP), adds **v1 routes** while keeping legacy `events`/`recent` usable during transition. Phase 3 **builds** the bundle into the engine assets directory and switches the middleware loader to the new script. Pulse logic compares successive poll payloads by **event `id`** when present, else a stable composite key.
8
+
9
+ **Tech Stack:** Svelte 5 + Vite + TypeScript (recommended); Rails Engine (`gems/e11y-devtools`); existing `E11y::Adapters::DevLog::Query`; RSpec for Ruby.
10
+
11
+ **Out of MVP scope:** TUI-style keyboard shortcuts; deep-linking / URL sync for overlay state (optional later).
12
+
13
+ ---
14
+
15
+ ## Context (read first)
16
+
17
+ | Item | Location |
18
+ |------|----------|
19
+ | Injected loader | `gems/e11y-devtools/lib/e11y/devtools/overlay/middleware.rb` — loads `/_e11y/overlay.js` |
20
+ | Current overlay | `gems/e11y-devtools/lib/e11y/devtools/overlay/assets/overlay.js` |
21
+ | JSON routes | `gems/e11y-devtools/config/routes.rb`, `.../overlay/rails_controller.rb`, `.../overlay/controller.rb` |
22
+ | TUI navigation model | `gems/e11y-devtools/lib/e11y/devtools/tui/app.rb` (`:interactions` → `:events` → `:detail`; drill uses **first** `trace_id` of group) |
23
+ | Grouping | `gems/e11y-devtools/lib/e11y/devtools/tui/grouping.rb` (to be moved to neutral namespace) |
24
+ | Dev log query + `interactions` | `lib/e11y/adapters/dev_log/query.rb` — events have `"id"` for `find_event` |
25
+ | Host mount | Apps mount `E11y::Devtools::Overlay::Engine => "/_e11y"` (see `docs/architecture/ADR-010-developer-experience.md`) |
26
+
27
+ **Tests:** `bundle exec rspec gems/e11y-devtools/spec/e11y/devtools/overlay/` and `gems/e11y-devtools/spec/e11y/devtools/tui/` from repo root (adjust path if your setup uses `cd gems/e11y-devtools`).
28
+
29
+ ---
30
+
31
+ ## Target API contract (`/_e11y/v1/`)
32
+
33
+ Add namespaced routes (legacy routes stay until removed):
34
+
35
+ | Method | Path | Purpose |
36
+ |--------|------|---------|
37
+ | GET | `/v1/interactions?source=web\|job\|all&limit=&window_ms=` | Newest-first interactions (same semantics as TUI `reload!` + filter) |
38
+ | GET | `/v1/traces/:trace_id/events` | Events for one trace (chronological, JSON array) |
39
+ | GET | `/v1/events/recent?limit=` | Flat recent list (badge + backward-compatible list; same as today’s use case) |
40
+
41
+ **Interaction JSON (example):**
42
+
43
+ ```json
44
+ {
45
+ "started_at": "2026-03-20T12:00:00.000Z",
46
+ "trace_ids": ["abc", "def"],
47
+ "has_error": true,
48
+ "source": "web",
49
+ "traces_count": 2
50
+ }
51
+ ```
52
+
53
+ **Event JSON:** pass through stored event hashes from `Query` (ensure `id`, `trace_id`, `severity`, `event_name`, `timestamp` present for UI and pulse diff).
54
+
55
+ ---
56
+
57
+ ### Task 1: Design doc + mock fixtures
58
+
59
+ **Files:**
60
+
61
+ - Create: `gems/e11y-devtools/frontend/README.md` (how to run dev server)
62
+ - Create: `gems/e11y-devtools/frontend/public/mocks/v1/interactions.json`
63
+ - Create: `gems/e11y-devtools/frontend/public/mocks/v1/traces/<trace_id>/events.json` (hex `trace_id` dirs, e.g. checkout + payment traces)
64
+ - Create: `gems/e11y-devtools/frontend/public/mocks/v1/events/recent.json`
65
+
66
+ **Step 1:** Add mock JSON files with 5–10 realistic events (mix severities including `warn`, `error`, `info`) and 2–3 grouped interactions.
67
+
68
+ **Step 2:** Document in `frontend/README.md`: `npm install`, `npm run dev`, open demo page.
69
+
70
+ **Step 3: Commit**
71
+
72
+ ```bash
73
+ git add gems/e11y-devtools/frontend/public/mocks gems/e11y-devtools/frontend/README.md
74
+ git commit -m "docs(devtools): add overlay v1 API mocks for Svelte prototype"
75
+ ```
76
+
77
+ ---
78
+
79
+ ### Task 2: Svelte + Vite scaffold (Phase 1 frontend)
80
+
81
+ **Files:**
82
+
83
+ - Create: `gems/e11y-devtools/frontend/package.json`
84
+ - Create: `gems/e11y-devtools/frontend/vite.config.ts`
85
+ - Create: `gems/e11y-devtools/frontend/tsconfig.json`
86
+ - Create: `gems/e11y-devtools/frontend/index.html` (fake host page + mount point)
87
+ - Create: `gems/e11y-devtools/frontend/src/main.ts`
88
+ - Create: `gems/e11y-devtools/frontend/src/App.svelte` (placeholder)
89
+
90
+ **Step 1:** Initialize Vite Svelte + TS (`npm create vite@latest` pattern): output **IIFE or single bundle** suitable for one `<script src>` (configure `build.lib` or `rollupOptions.output` as needed so final file is `overlay.js`).
91
+
92
+ **Step 2:** Run `cd gems/e11y-devtools/frontend && npm install && npm run dev` — confirm demo loads.
93
+
94
+ **Step 3:** Add `npm run build` producing `../lib/e11y/devtools/overlay/assets/overlay.js` (or `dist/overlay.js` + copy step documented until Task 7).
95
+
96
+ **Step 4: Commit**
97
+
98
+ ```bash
99
+ git add gems/e11y-devtools/frontend
100
+ git commit -m "feat(devtools): scaffold Svelte+Vite overlay frontend"
101
+ ```
102
+
103
+ ---
104
+
105
+ ### Task 3: Navigation shell + fullscreen animation (mocks only)
106
+
107
+ **Files:**
108
+
109
+ - Create: `gems/e11y-devtools/frontend/src/lib/router.ts` (typed view: `interactions | events | detail`, stack, `source` filter)
110
+ - Create/modify: `gems/e11y-devtools/frontend/src/components/Fab.svelte`
111
+ - Create/modify: `gems/e11y-devtools/frontend/src/components/FullscreenPanel.svelte`
112
+ - Modify: `gems/e11y-devtools/frontend/src/App.svelte`
113
+
114
+ **Step 1:** Implement FAB bottom-right; click toggles fullscreen overlay (`position: fixed; inset: 0`; inner content `transform-origin: bottom right` + open/close animation; respect `prefers-reduced-motion`).
115
+
116
+ **Step 2:** Header: title + close button + **source chips** `web | job | all` (click switches filter and refetches mocks).
117
+
118
+ **Step 3:** Wire three screens: Interactions list → click row → Events list (use **first** `trace_ids[0]` as in TUI) → click row → Detail (pretty JSON or key fields + “Copy JSON” button).
119
+
120
+ **Step 4:** Run `npm run dev`, click through mocks; fix layout/z-index issues.
121
+
122
+ **Step 5: Commit**
123
+
124
+ ```bash
125
+ git commit -am "feat(devtools): overlay navigation shell and fullscreen animation (mocks)"
126
+ ```
127
+
128
+ ---
129
+
130
+ ### Task 4: Pulse-on-new-error/warn (collapsed FAB only)
131
+
132
+ **Files:**
133
+
134
+ - Create: `gems/e11y-devtools/frontend/src/lib/eventIdentity.ts` — `eventKey(e): string` using `e.id` if truthy, else stable composite (`trace_id`, `timestamp`, `event_name`, index).
135
+ - Modify: `gems/e11y-devtools/frontend/src/App.svelte` (or store module)
136
+
137
+ **Step 1:** Keep `Set` of keys from **previous** `recent` poll (or last interactions aggregate — for MVP use **`/v1/events/recent`** payload only for pulse to match current overlay behavior).
138
+
139
+ **Step 2:** On each successful fetch, compute **newly seen** events whose `severity` is in `error|fatal` → add class `pulse-error` for ~3s; `warn` → `pulse-warn`. If both in same tick, prefer error styling.
140
+
141
+ **Step 3:** CSS: short keyframe (opacity/box-shadow); `@media (prefers-reduced-motion: reduce)` skip animation, optional single flash of border color.
142
+
143
+ **Step 4:** Badge text: show total count + error count + warn count (compact, e.g. `e11y 12 · 2⚠ · 1✕` — tune for readability).
144
+
145
+ **Step 5: Commit**
146
+
147
+ ```bash
148
+ git commit -am "feat(devtools): pulse FAB on new error/warn events"
149
+ ```
150
+
151
+ ---
152
+
153
+ ### Task 5: Extract neutral Ruby module for interactions pipeline
154
+
155
+ **Goal:** One place for “load events → build trace map → `Grouping.group`” used by TUI and HTTP.
156
+
157
+ **Files:**
158
+
159
+ - Create: `gems/e11y-devtools/lib/e11y/devtools/log_view.rb` (or `interaction_index.rb`) — class methods or instance taking `E11y::Adapters::DevLog::Query`
160
+ - Modify: `gems/e11y-devtools/lib/e11y/devtools/tui/grouping.rb` — **move** `Grouping` to `E11y::Devtools::Grouping` (new file `lib/e11y/devtools/grouping.rb`), leave thin `require` + alias in old path **or** update all requires in one commit
161
+ - Modify: `gems/e11y-devtools/lib/e11y/devtools/tui/app.rb` — call shared module
162
+ - Modify: `gems/e11y-devtools/lib/e11y/devtools/mcp/tools/interactions.rb` if it duplicates logic (align with `Query#interactions` or shared module)
163
+ - Test: `gems/e11y-devtools/spec/e11y/devtools/tui/grouping_spec.rb` — update path if needed
164
+
165
+ **Step 1:** Write failing spec for `E11y::Devtools::LogView.interactions(query, source:, limit:, window_ms:)` returning serializable hashes matching v1 JSON.
166
+
167
+ **Step 2:** Run `bundle exec rspec gems/e11y-devtools/spec/.../log_view_spec.rb` — expect RED.
168
+
169
+ **Step 3:** Implement by extracting from `Tui::App#reload!` / `build_traces` or delegating to `Query#interactions` if equivalent; ensure **source** filter semantics match TUI (`:all` → nil source filter).
170
+
171
+ **Step 4:** Refactor TUI to use shared module; run `bundle exec rspec gems/e11y-devtools/spec/e11y/devtools/tui/`.
172
+
173
+ **Step 5: Commit**
174
+
175
+ ```bash
176
+ git commit -am "refactor(devtools): shared log view for interactions grouping"
177
+ ```
178
+
179
+ ---
180
+
181
+ ### Task 6: HTTP v1 endpoints + controller tests
182
+
183
+ **Files:**
184
+
185
+ - Modify: `gems/e11y-devtools/config/routes.rb` — scope `v1` routes
186
+ - Modify: `gems/e11y-devtools/lib/e11y/devtools/overlay/rails_controller.rb` — actions `interactions`, `trace_events` (names TBD)
187
+ - Modify: `gems/e11y-devtools/lib/e11y/devtools/overlay/controller.rb` — delegate to `LogView` + `Query`
188
+ - Create: `gems/e11y-devtools/spec/e11y/devtools/overlay/v1_controller_spec.rb` (or extend `controller_spec.rb`)
189
+
190
+ **Step 1:** Request specs or controller specs: `GET /_e11y/v1/interactions` returns 200 JSON array; `GET /_e11y/v1/traces/:id/events` returns array; 404 for unknown trace returns `[]` or 404 — **pick one and document** (recommend `[]` for simpler UI).
191
+
192
+ **Step 2:** Run `bundle exec rspec gems/e11y-devtools/spec/e11y/devtools/overlay/`.
193
+
194
+ **Step 3: Commit**
195
+
196
+ ```bash
197
+ git commit -am "feat(devtools): v1 JSON API for overlay interactions and trace events"
198
+ ```
199
+
200
+ ---
201
+
202
+ ### Task 7: Production build pipeline + replace legacy overlay bundle
203
+
204
+ **Files:**
205
+
206
+ - Modify: `gems/e11y-devtools/frontend/vite.config.ts` — output filename `overlay.js` into `../lib/e11y/devtools/overlay/assets/`
207
+ - Delete or archive: inline-only `overlay.js` **after** Svelte bundle verified (git history retains old file)
208
+ - Modify: `gems/e11y-devtools/README.md` — document `npm run build` before release / CI note
209
+ - Optional: `Rakefile` in `gems/e11y-devtools` task `devtools:build`
210
+
211
+ **Step 1:** `npm run build` — confirm `assets/overlay.js` exists and defines the custom element or mounts into a host (match current behavior: auto-append `e11y-overlay` or equivalent).
212
+
213
+ **Step 2:** Boot dummy/integration app if available, load page, confirm script loads and API calls hit `/_e11y/v1/...`.
214
+
215
+ **Step 3:** Run overlay middleware specs: `bundle exec rspec gems/e11y-devtools/spec/e11y/devtools/overlay/middleware_spec.rb`.
216
+
217
+ **Step 4: Commit**
218
+
219
+ ```bash
220
+ git commit -am "build(devtools): ship Svelte overlay bundle as overlay.js"
221
+ ```
222
+
223
+ ---
224
+
225
+ ### Task 8: Wire Svelte app to real API + remove mock default
226
+
227
+ **Files:**
228
+
229
+ - Modify: `gems/e11y-devtools/frontend/src/...` — `API_BASE = '/_e11y'` + `/v1/...` paths; dev server proxy in `vite.config.ts` to Rails `localhost:3000` optional
230
+ - Modify: `gems/e11y-devtools/lib/e11y/devtools/overlay/assets/overlay.js` — **generated**; ensure CORS not required (same origin)
231
+
232
+ **Step 1:** Replace `fetch('/mocks/...')` with real endpoints; keep env flag `import.meta.env.DEV` for mocks if useful.
233
+
234
+ **Step 2:** Manual QA: trigger errors/warns in a Rails app, confirm pulse once per new event, fullscreen navigation matches TUI order.
235
+
236
+ **Step 3: Commit**
237
+
238
+ ```bash
239
+ git commit -am "feat(devtools): connect overlay UI to v1 API"
240
+ ```
241
+
242
+ ---
243
+
244
+ ### Task 9: Documentation + ADR touch-up
245
+
246
+ **Files:**
247
+
248
+ - Modify: `gems/e11y-devtools/README.md` — Browser Overlay section: Svelte build, v1 API, pulse behavior, no keyboard in MVP
249
+ - Modify: `docs/architecture/ADR-010-developer-experience.md` — mention v1 routes and bundle build if needed
250
+
251
+ **Step 1:** Proofread commands and paths.
252
+
253
+ **Step 2: Commit**
254
+
255
+ ```bash
256
+ git commit -am "docs(devtools): document new overlay and v1 API"
257
+ ```
258
+
259
+ ---
260
+
261
+ ## Verification checklist (before claiming done)
262
+
263
+ - [ ] `cd gems/e11y-devtools/frontend && npm run build` succeeds
264
+ - [ ] `bundle exec rspec gems/e11y-devtools/spec/e11y/devtools/overlay/` green
265
+ - [ ] `bundle exec rspec gems/e11y-devtools/spec/e11y/devtools/tui/` green
266
+ - [ ] Collapsed FAB: static styling for ongoing errors/warns; **pulse only** when new matching events appear since last poll
267
+ - [ ] Fullscreen open/close animation; reduced-motion respected
268
+ - [ ] Navigation: interactions → events (first trace_id) → detail; source filter chips work
269
+
270
+ ---
271
+
272
+ ## Plan complete
273
+
274
+ Saved to `docs/plans/2026-03-20-browser-overlay-svelte.md`.
275
+
276
+ **Execution options:**
277
+
278
+ 1. **Subagent-driven (this session)** — fresh subagent per task, review between tasks (`superpowers:subagent-driven-development`).
279
+ 2. **Parallel session** — new session with `superpowers:executing-plans` and checkpoints.
280
+
281
+ Which approach do you want?