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.
- checksums.yaml +4 -4
- data/.rubocop.yml +130 -10
- data/CHANGELOG.md +80 -1
- data/CLAUDE.md +168 -0
- data/CONTRIBUTING.md +640 -0
- data/README.md +165 -701
- data/RELEASE.md +41 -12
- data/Rakefile +249 -57
- data/config/README.md +1 -1
- data/config/loki-local-config.yaml +12 -0
- data/config/otel-collector-config.yaml +44 -0
- data/cucumber.yml +1 -0
- data/docker-compose.yml +18 -2
- data/docs/ADAPTERS.md +76 -0
- data/docs/ADAPTIVE_SAMPLING.md +59 -0
- data/docs/COMPARISON.md +104 -0
- data/docs/CONFIGURATION.md +52 -0
- data/docs/DISTRIBUTED_TRACING.md +44 -0
- data/docs/LIMITATIONS.md +13 -0
- data/docs/METRICS_DSL.md +84 -0
- data/docs/PERFORMANCE.md +60 -0
- data/docs/PII_FILTERING.md +40 -0
- data/docs/PRESETS.md +65 -0
- data/docs/QUICK-START.md +546 -587
- data/docs/RAILS_INTEGRATION.md +79 -0
- data/docs/SCHEMA_VALIDATION.md +63 -0
- data/docs/SLO-PROMQL-ALERTS.md +161 -0
- data/docs/TESTING.md +69 -0
- data/docs/{ADR-001-architecture.md → architecture/ADR-001-architecture.md} +36 -65
- data/docs/{ADR-002-metrics-yabeda.md → architecture/ADR-002-metrics-yabeda.md} +62 -236
- data/docs/architecture/ADR-003-slo-observability.md +1402 -0
- data/docs/{ADR-004-adapter-architecture.md → architecture/ADR-004-adapter-architecture.md} +163 -146
- data/docs/{ADR-005-tracing-context.md → architecture/ADR-005-tracing-context.md} +10 -9
- data/docs/{ADR-006-security-compliance.md → architecture/ADR-006-security-compliance.md} +184 -191
- data/docs/{ADR-007-opentelemetry-integration.md → architecture/ADR-007-opentelemetry-integration.md} +3 -21
- data/docs/{ADR-008-rails-integration.md → architecture/ADR-008-rails-integration.md} +182 -743
- data/docs/{ADR-009-cost-optimization.md → architecture/ADR-009-cost-optimization.md} +45 -54
- data/docs/architecture/ADR-010-developer-experience.md +522 -0
- data/docs/{ADR-011-testing-strategy.md → architecture/ADR-011-testing-strategy.md} +44 -86
- data/docs/{ADR-012-event-evolution.md → architecture/ADR-012-event-evolution.md} +11 -11
- data/docs/{ADR-013-reliability-error-handling.md → architecture/ADR-013-reliability-error-handling.md} +37 -12
- data/docs/{ADR-014-event-driven-slo.md → architecture/ADR-014-event-driven-slo.md} +12 -24
- data/docs/{ADR-015-middleware-order.md → architecture/ADR-015-middleware-order.md} +43 -59
- data/docs/{ADR-016-self-monitoring-slo.md → architecture/ADR-016-self-monitoring-slo.md} +58 -355
- data/docs/{ADR-017-multi-rails-compatibility.md → architecture/ADR-017-multi-rails-compatibility.md} +4 -11
- data/docs/architecture/ADR-018-memory-optimization.md +366 -0
- data/docs/{ADR-INDEX.md → architecture/ADR-INDEX.md} +11 -6
- data/docs/plans/2026-03-20-browser-overlay-svelte.md +281 -0
- data/docs/{00-ICP-AND-TIMELINE.md → prd/00-ICP-AND-TIMELINE.md} +6 -6
- data/docs/{01-SCALE-REQUIREMENTS.md → prd/01-SCALE-REQUIREMENTS.md} +6 -6
- data/docs/prd/01-overview-vision.md +19 -14
- data/docs/use_cases/README.md +22 -23
- data/docs/use_cases/UC-001-request-scoped-debug-buffering.md +50 -44
- data/docs/use_cases/UC-002-business-event-tracking.md +26 -95
- data/docs/use_cases/UC-003-event-metrics.md +66 -0
- data/docs/use_cases/UC-004-zero-config-slo-tracking.md +33 -684
- data/docs/use_cases/UC-005-sentry-integration.md +13 -15
- data/docs/use_cases/UC-006-trace-context-management.md +30 -28
- data/docs/use_cases/UC-007-pii-filtering.md +35 -87
- data/docs/use_cases/UC-008-opentelemetry-integration.md +51 -89
- data/docs/use_cases/UC-009-multi-service-tracing.md +30 -178
- data/docs/use_cases/UC-010-background-job-tracking.md +24 -91
- data/docs/use_cases/UC-011-rate-limiting.md +95 -168
- data/docs/use_cases/UC-012-audit-trail.md +21 -46
- data/docs/use_cases/UC-013-high-cardinality-protection.md +29 -167
- data/docs/use_cases/UC-014-adaptive-sampling.md +2 -2
- data/docs/use_cases/UC-015-cost-optimization.md +46 -99
- data/docs/use_cases/UC-016-rails-logger-migration.md +39 -213
- data/docs/use_cases/UC-017-local-development.md +203 -777
- data/docs/use_cases/UC-018-testing-events.md +3 -3
- data/docs/use_cases/UC-019-retention-based-routing.md +53 -106
- data/docs/use_cases/UC-020-event-versioning.md +8 -9
- data/docs/use_cases/UC-021-error-handling-retry-dlq.md +18 -22
- data/docs/use_cases/UC-022-event-registry.md +15 -21
- data/docs/use_cases/backlog.md +119 -87
- data/e11y.gemspec +2 -2
- data/gems/e11y-devtools/README.md +158 -0
- data/gems/e11y-devtools/config/routes.rb +15 -0
- data/gems/e11y-devtools/e11y-devtools.gemspec +25 -0
- data/gems/e11y-devtools/exe/e11y +34 -0
- data/gems/e11y-devtools/frontend/.gitignore +24 -0
- data/gems/e11y-devtools/frontend/README.md +51 -0
- data/gems/e11y-devtools/frontend/index.html +14 -0
- data/gems/e11y-devtools/frontend/package-lock.json +3707 -0
- data/gems/e11y-devtools/frontend/package.json +28 -0
- data/gems/e11y-devtools/frontend/public/mocks/v1/events/recent.json +4205 -0
- data/gems/e11y-devtools/frontend/public/mocks/v1/interactions.json +194 -0
- data/gems/e11y-devtools/frontend/public/mocks/v1/traces/0a2e04027cfa22d014bc22e8b27cd913/events.json +86 -0
- data/gems/e11y-devtools/frontend/public/mocks/v1/traces/0e1543af6a630fb3af6b52283154b3e0/events.json +169 -0
- data/gems/e11y-devtools/frontend/public/mocks/v1/traces/1838b691faa49564f97db8592ff3978d/events.json +78 -0
- data/gems/e11y-devtools/frontend/public/mocks/v1/traces/29f198f6588dacffb687777eb5f8f118/events.json +197 -0
- data/gems/e11y-devtools/frontend/public/mocks/v1/traces/34bc3c9c0097de28a7a6f99b90a8e7bc/events.json +194 -0
- data/gems/e11y-devtools/frontend/public/mocks/v1/traces/3ba6c20d068ab9cee00e51b180e66444/events.json +184 -0
- data/gems/e11y-devtools/frontend/public/mocks/v1/traces/435bfd8f17b9009146a79812d7c3726d/events.json +144 -0
- data/gems/e11y-devtools/frontend/public/mocks/v1/traces/4c7676e3fe668e99edb2b94d7d5678a9/events.json +222 -0
- data/gems/e11y-devtools/frontend/public/mocks/v1/traces/6daf0d47974bedfc55d5de7004a3ea9f/events.json +194 -0
- data/gems/e11y-devtools/frontend/public/mocks/v1/traces/8a81ada42834d15f287bb40010043605/events.json +194 -0
- data/gems/e11y-devtools/frontend/public/mocks/v1/traces/8c0a98900edaae105469df8daedccf02/events.json +198 -0
- data/gems/e11y-devtools/frontend/public/mocks/v1/traces/8e4f645180f8a7d1dce426b07380466b/events.json +222 -0
- data/gems/e11y-devtools/frontend/public/mocks/v1/traces/93db346fa5d44a032605a13b627f4b80/events.json +128 -0
- data/gems/e11y-devtools/frontend/public/mocks/v1/traces/98ff6146faf7bd9be8bd03a8275817ba/events.json +223 -0
- data/gems/e11y-devtools/frontend/public/mocks/v1/traces/9997ddd0247bc7e25f2ca7a5c415c93d/events.json +197 -0
- data/gems/e11y-devtools/frontend/public/mocks/v1/traces/99e35f8ef3baedd798cc4fd085980ad9/events.json +194 -0
- data/gems/e11y-devtools/frontend/public/mocks/v1/traces/b4f3095c1909924cbc98889a86c83d6d/events.json +131 -0
- data/gems/e11y-devtools/frontend/public/mocks/v1/traces/b54b7fc32b7575a7110de809d11ccda0/events.json +128 -0
- data/gems/e11y-devtools/frontend/public/mocks/v1/traces/c0b48033fa06746bcc5886745e053cff/events.json +169 -0
- data/gems/e11y-devtools/frontend/public/mocks/v1/traces/c44649ac76701b4558927cd2305ab535/events.json +169 -0
- data/gems/e11y-devtools/frontend/public/mocks/v1/traces/d601ae3320057580a39dbdac2edfdf4a/events.json +248 -0
- data/gems/e11y-devtools/frontend/public/mocks/v1/traces/e67e724bab422d2b52eeb49635e512e1/events.json +194 -0
- data/gems/e11y-devtools/frontend/public/mocks/v1/traces/e6c72765a28f158a8485b35fa63f73da/events.json +194 -0
- data/gems/e11y-devtools/frontend/public/mocks/v1/traces/f541b87405c9a54819b18ebe529f6419/events.json +194 -0
- data/gems/e11y-devtools/frontend/scripts/generate_mocks.rb +397 -0
- data/gems/e11y-devtools/frontend/src/App.svelte +827 -0
- data/gems/e11y-devtools/frontend/src/components/Fab.svelte +19 -0
- data/gems/e11y-devtools/frontend/src/components/FilterBar.svelte +38 -0
- data/gems/e11y-devtools/frontend/src/components/FullscreenPanel.svelte +82 -0
- data/gems/e11y-devtools/frontend/src/components/InteractionsTimeline.svelte +264 -0
- data/gems/e11y-devtools/frontend/src/components/RecentHistogram.svelte +354 -0
- data/gems/e11y-devtools/frontend/src/lib/api.ts +37 -0
- data/gems/e11y-devtools/frontend/src/lib/eventIdentity.ts +12 -0
- data/gems/e11y-devtools/frontend/src/lib/format.ts +37 -0
- data/gems/e11y-devtools/frontend/src/lib/listFilter.ts +43 -0
- data/gems/e11y-devtools/frontend/src/lib/recentVolume.ts +80 -0
- data/gems/e11y-devtools/frontend/src/lib/router.ts +12 -0
- data/gems/e11y-devtools/frontend/src/lib/transitions.ts +34 -0
- data/gems/e11y-devtools/frontend/src/lib/viewportOrigin.ts +25 -0
- data/gems/e11y-devtools/frontend/src/main.ts +8 -0
- data/gems/e11y-devtools/frontend/src/overlay-entry.ts +24 -0
- data/gems/e11y-devtools/frontend/src/overlay.css +1080 -0
- data/gems/e11y-devtools/frontend/svelte.config.js +2 -0
- data/gems/e11y-devtools/frontend/test_puppeteer.js +41 -0
- data/gems/e11y-devtools/frontend/test_scale.js +3 -0
- data/gems/e11y-devtools/frontend/tsconfig.app.json +21 -0
- data/gems/e11y-devtools/frontend/tsconfig.json +7 -0
- data/gems/e11y-devtools/frontend/tsconfig.node.json +26 -0
- data/gems/e11y-devtools/frontend/vite.config.ts +36 -0
- data/gems/e11y-devtools/lib/e11y/devtools/mcp/server.rb +96 -0
- data/gems/e11y-devtools/lib/e11y/devtools/mcp/tool_base.rb +25 -0
- data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/clear.rb +31 -0
- data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/errors.rb +35 -0
- data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/event_detail.rb +33 -0
- data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/events_by_trace.rb +33 -0
- data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/interactions.rb +40 -0
- data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/recent_events.rb +34 -0
- data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/search.rb +34 -0
- data/gems/e11y-devtools/lib/e11y/devtools/mcp/tools/stats.rb +30 -0
- data/gems/e11y-devtools/lib/e11y/devtools/overlay/assets/overlay.js +20 -0
- data/gems/e11y-devtools/lib/e11y/devtools/overlay/controller.rb +94 -0
- data/gems/e11y-devtools/lib/e11y/devtools/overlay/engine.rb +26 -0
- data/gems/e11y-devtools/lib/e11y/devtools/overlay/middleware.rb +80 -0
- data/gems/e11y-devtools/lib/e11y/devtools/overlay/rails_controller.rb +67 -0
- data/gems/e11y-devtools/lib/e11y/devtools/tui/app.rb +262 -0
- data/gems/e11y-devtools/lib/e11y/devtools/tui/grouping.rb +66 -0
- data/gems/e11y-devtools/lib/e11y/devtools/tui/widgets/event_detail.rb +62 -0
- data/gems/e11y-devtools/lib/e11y/devtools/tui/widgets/event_list.rb +70 -0
- data/gems/e11y-devtools/lib/e11y/devtools/tui/widgets/interaction_list.rb +47 -0
- data/gems/e11y-devtools/lib/e11y/devtools/version.rb +8 -0
- data/gems/e11y-devtools/lib/e11y/devtools.rb +13 -0
- data/gems/e11y-devtools/spec/e11y/devtools/mcp/tools_spec.rb +107 -0
- data/gems/e11y-devtools/spec/e11y/devtools/overlay/controller_spec.rb +91 -0
- data/gems/e11y-devtools/spec/e11y/devtools/overlay/middleware_spec.rb +46 -0
- data/gems/e11y-devtools/spec/e11y/devtools/tui/app_spec.rb +85 -0
- data/gems/e11y-devtools/spec/e11y/devtools/tui/grouping_spec.rb +64 -0
- data/gems/e11y-devtools/spec/spec_helper.rb +5 -0
- data/gems/e11y-devtools/spec/tui/widgets/event_list_spec.rb +44 -0
- data/gems/e11y-devtools/spec/tui/widgets/interaction_list_spec.rb +62 -0
- data/lib/e11y/adapters/audit_encrypted.rb +53 -11
- data/lib/e11y/adapters/base.rb +33 -34
- data/lib/e11y/adapters/dev_log/file_store.rb +143 -0
- data/lib/e11y/adapters/dev_log/query.rb +219 -0
- data/lib/e11y/adapters/dev_log.rb +118 -0
- data/lib/e11y/adapters/file.rb +3 -6
- data/lib/e11y/adapters/in_memory.rb +52 -5
- data/lib/e11y/adapters/in_memory_test.rb +29 -0
- data/lib/e11y/adapters/loki.rb +58 -23
- data/lib/e11y/adapters/null.rb +82 -0
- data/lib/e11y/adapters/opentelemetry_collector.rb +183 -0
- data/lib/e11y/adapters/otel_logs.rb +136 -23
- data/lib/e11y/adapters/sentry.rb +4 -7
- data/lib/e11y/adapters/stdout.rb +73 -7
- data/lib/e11y/adapters/yabeda.rb +153 -29
- data/lib/e11y/buffers/adaptive_buffer.rb +3 -17
- data/lib/e11y/buffers/{request_scoped_buffer.rb → ephemeral_buffer.rb} +72 -58
- data/lib/e11y/buffers/ring_buffer.rb +3 -16
- data/lib/e11y/configuration.rb +272 -0
- data/lib/e11y/console.rb +10 -17
- data/lib/e11y/current.rb +53 -1
- data/lib/e11y/debug/pipeline_inspector.rb +96 -0
- data/lib/e11y/documentation/generator.rb +48 -0
- data/lib/e11y/event/base.rb +176 -82
- data/lib/e11y/event/value_sampling_config.rb +1 -5
- data/lib/e11y/events/rails/database/query.rb +1 -4
- data/lib/e11y/events/rails/job/failed.rb +2 -0
- data/lib/e11y/instruments/active_job.rb +44 -12
- data/lib/e11y/instruments/rails_instrumentation.rb +49 -24
- data/lib/e11y/instruments/sidekiq.rb +135 -31
- data/lib/e11y/linters/base.rb +11 -0
- data/lib/e11y/linters/pii/pii_declaration_linter.rb +120 -0
- data/lib/e11y/linters/slo/config_consistency_linter.rb +76 -0
- data/lib/e11y/linters/slo/explicit_declaration_linter.rb +36 -0
- data/lib/e11y/linters/slo/slo_status_from_linter.rb +41 -0
- data/lib/e11y/logger/bridge.rb +26 -7
- data/lib/e11y/metrics/cardinality_protection.rb +10 -15
- data/lib/e11y/metrics/cardinality_tracker.rb +16 -6
- data/lib/e11y/metrics/registry.rb +3 -5
- data/lib/e11y/metrics/test_backend.rb +62 -0
- data/lib/e11y/metrics.rb +56 -10
- data/lib/e11y/middleware/adapter_resolver.rb +40 -0
- data/lib/e11y/middleware/audit_signing.rb +43 -6
- data/lib/e11y/middleware/baggage_protection.rb +75 -0
- data/lib/e11y/middleware/dev_log_source.rb +24 -0
- data/lib/e11y/middleware/event_slo.rb +23 -9
- data/lib/e11y/middleware/otel_span.rb +23 -0
- data/lib/e11y/middleware/pii_filter.rb +104 -75
- data/lib/e11y/middleware/rate_limiting.rb +54 -27
- data/lib/e11y/middleware/request.rb +70 -23
- data/lib/e11y/middleware/routing.rb +78 -21
- data/lib/e11y/middleware/sampling.rb +66 -17
- data/lib/e11y/middleware/self_monitoring_emit.rb +39 -0
- data/lib/e11y/middleware/trace_context.rb +45 -10
- data/lib/e11y/middleware/track_latency.rb +34 -0
- data/lib/e11y/middleware/validation.rb +7 -16
- data/lib/e11y/middleware/versioning.rb +26 -22
- data/lib/e11y/opentelemetry/semantic_conventions.rb +109 -0
- data/lib/e11y/opentelemetry/span_creator.rb +142 -0
- data/lib/e11y/pii/patterns.rb +12 -1
- data/lib/e11y/pipeline/builder.rb +4 -4
- data/lib/e11y/presets/audit_event.rb +13 -2
- data/lib/e11y/railtie.rb +52 -14
- data/lib/e11y/registry.rb +306 -0
- data/lib/e11y/reliability/circuit_breaker.rb +19 -21
- data/lib/e11y/reliability/dlq/base.rb +71 -0
- data/lib/e11y/reliability/dlq/file_adapter.rb +301 -0
- data/lib/e11y/reliability/dlq/file_storage.rb +63 -34
- data/lib/e11y/reliability/dlq/filter.rb +37 -54
- data/lib/e11y/reliability/retry_handler.rb +26 -29
- data/lib/e11y/reliability/retry_rate_limiter.rb +3 -11
- data/lib/e11y/sampling/error_spike_detector.rb +0 -2
- data/lib/e11y/sampling/load_monitor.rb +5 -9
- data/lib/e11y/sampling/stratified_tracker.rb +18 -0
- data/lib/e11y/self_monitoring/buffer_monitor.rb +2 -0
- data/lib/e11y/self_monitoring/performance_monitor.rb +19 -61
- data/lib/e11y/self_monitoring/reliability_monitor.rb +4 -74
- data/lib/e11y/slo/config_loader.rb +40 -0
- data/lib/e11y/slo/config_validator.rb +58 -0
- data/lib/e11y/slo/dashboard_generator.rb +122 -0
- data/lib/e11y/slo/event_driven.rb +8 -0
- data/lib/e11y/slo/tracker.rb +31 -4
- data/lib/e11y/testing/have_tracked_event_matcher.rb +190 -0
- data/lib/e11y/testing/rspec_matchers.rb +21 -0
- data/lib/e11y/testing/snapshot_matcher.rb +86 -0
- data/lib/e11y/trace_context/sampler.rb +35 -0
- data/lib/e11y/tracing/faraday_middleware.rb +31 -0
- data/lib/e11y/tracing/net_http_patch.rb +33 -0
- data/lib/e11y/tracing/propagator.rb +144 -0
- data/lib/e11y/tracing.rb +47 -0
- data/lib/e11y/version.rb +1 -1
- data/lib/e11y/versioning/version_extractor.rb +32 -0
- data/lib/e11y.rb +123 -266
- data/lib/generators/e11y/event/event_generator.rb +22 -0
- data/lib/generators/e11y/event/templates/event.rb.tt +16 -0
- data/lib/generators/e11y/grafana_dashboard/grafana_dashboard_generator.rb +30 -0
- data/lib/generators/e11y/grafana_dashboard/templates/e11y_dashboard.json +81 -0
- data/lib/generators/e11y/install/install_generator.rb +34 -0
- data/lib/generators/e11y/install/templates/e11y.rb +239 -0
- data/lib/generators/e11y/prometheus_alerts/prometheus_alerts_generator.rb +29 -0
- data/lib/generators/e11y/prometheus_alerts/templates/e11y_alerts.yml +28 -0
- data/lib/tasks/e11y_docs.rake +30 -0
- data/lib/tasks/e11y_events.rake +71 -0
- data/lib/tasks/e11y_lint.rake +91 -0
- data/lib/tasks/e11y_slo.rake +29 -0
- metadata +186 -39
- data/docs/ADR-003-slo-observability.md +0 -3337
- data/docs/ADR-010-developer-experience.md +0 -2166
- data/docs/API-REFERENCE-L28.md +0 -914
- data/docs/COMPREHENSIVE-CONFIGURATION.md +0 -2366
- data/docs/CONTRIBUTING.md +0 -312
- data/docs/IMPLEMENTATION_NOTES.md +0 -2804
- data/docs/IMPLEMENTATION_PLAN.md +0 -1971
- data/docs/IMPLEMENTATION_PLAN_ARCHITECTURE.md +0 -586
- data/docs/PLAN.md +0 -148
- data/docs/README.md +0 -296
- data/docs/design/00-memory-optimization.md +0 -593
- data/docs/guides/MIGRATION-L27-L28.md +0 -692
- data/docs/guides/PERFORMANCE-BENCHMARKS.md +0 -434
- data/docs/guides/README.md +0 -44
- data/docs/use_cases/UC-003-pattern-based-metrics.md +0 -1627
- 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 (
|
|
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,
|
|
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**:
|
|
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-
|
|
91
|
-
2. [ADR-
|
|
92
|
-
3. [
|
|
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?
|