All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

Unreleased

Added

Changed

Fixed

[0.4.2] - 2026-04-18

Changed

  • Credo dependency switched from git branch back to hexmix.exs pinned credo to github: "rrrene/credo", branch: "release/1.7" as a workaround for a 1.7.x sigil crash on Elixir 1.20+. The fix has landed in credo 1.7.18 on hex, so the dep now reads {:credo, "~> 1.7", only: [:dev, :test], runtime: false}. Dev/test dependency only — no runtime or consumer impact; the shipped hex tarball is byte-identical to 0.4.1 modulo this mix.exs line and the lockfile.
  • ex_unit_json lockfile bump 0.4.2 → 0.4.3 — Picked up during mix deps.get. Dev/test dependency only; no runtime impact.

[0.4.1] - 2026-04-18

Changed

  • Handler message contract retired two unreachable shapes{:frame, term} and {:frame_error, {:decode_error, term}} are removed from @type handler_message, the default handler, USAGE_RULES.md, and the ErrorHandling example. Both shapes were documented in R047 prep but never actually delivered: {:frame, _} was the other -> catch-all of route_data_frame/2, unreachable because MessageHandler.handle_control_frame/3 consumes every non-text/non-binary decode output before it gets there; {:frame_error, {:decode_error, _}} depended on ErrorHandler.handle_error/1 returning non-:stop for {:bad_frame, _}, but check_fatal/1 classifies every bad frame (and every unclassified error) as fatal, so the recoverable branch never ran. Decision was to retire rather than expand reachability — there is no concrete user need for recoverable frame-decode behavior, and Frame.decode/1 only emits one error shape ({:error, "Unknown frame type: ..."}), which is a protocol violation with nothing to recover from. MessageHandler.handle_message/2 and decode_and_handle_control/1 simplify to always produce {:protocol_error, _} in the decode-error branch; create_handler/1 drops the {:decode_error, _} routing clause. handle_frame_error/2's tightened @spec now declares {:protocol_error, term()} as the only reachable error tag. This is a breaking change against the prepared v0.4.0 handler contract — not against the published v0.3.1 surface, since the retired tuples were added in v0.4.0 doc work (R047) but were never emitted at runtime (R048)
  • Handler message contract is now self-documenting and typedZenWebsocket.Client exposes @type handler_message/0 and @type handler/0, and the handler field in the GenServer state type uses handler() instead of (term() -> term()). USAGE_RULES.md adds a "Handler Message Reference" section with input-shape and default-handler translation tables, a custom-handler example, and a note on the :protocol_error / :frame_error payload asymmetry. The default handler now forwards :unmatched_response to the parent as :websocket_unmatched_response — previously the _other catch-all dropped it silently, making orphan JSON-RPC responses invisible to callers relying on default-handler delivery. Integration tests in client_test.exs cover the custom- and default-handler paths. The :frame and :frame_error handler shapes were discovered to be currently unreachable (control-frame consumption and fatal bad-frame classification respectively); both call sites carry TODO(Task R048) markers for the retire-or-reach decision (R047)
  • Testing policy now permits opaque Gun transport message-shape fixtures — The "NO MOCKS ALLOWED" rule in CLAUDE.md and AGENTS.md is amended with a narrow, fenced exception: test doubles may construct the four Gun transport tuples (:gun_upgrade, :gun_ws, :gun_down, :gun_error) using real pids (from self() / spawn) and real refs (from make_ref/0), because Gun's pid and stream_ref are opaque BEAM primitives with no public contract — there is no behavior for a fixture to drift against. Business-logic mocking (API response fixtures, auth-flow simulation, exchange-behavior simulation) remains explicitly prohibited; MockWebSockServer and real-API tests remain the source of truth for all Client / reconnection / subscription / exchange tests. AGENTS.md carries the "what is permitted" pointer; CLAUDE.md holds the full rationale and the "what is NOT newly allowed" list. Unblocks R045 (GunStub test helper) and R046 (MessageHandler property tests) (R044)

Fixed

  • Duplicate live request IDs no longer silently overwrite the first callerRequestCorrelator.track/4 used Map.put/3 on state.pending_requests, so tracking a second request whose ID matched an already-pending entry silently replaced the first caller's from and timer. The first caller then blocked on GenServer.call until its per-call request_timeout fired, and its timeout timer became orphaned against an ID now owned by the second caller. track/4 now returns {:ok, new_state} on success and {:error, :duplicate_id, state} on collision, leaving the first caller's pending entry (and its timer) intact. At the Client.handle_call({:send_message, _}, …) call site, the second caller receives {:error, :duplicate_request_id} immediately and no WebSocket frame is sent. No :track telemetry is emitted on the duplicate path, keeping event counts honest. Covered by unit tests in request_correlator_test.exs (describe "track/4 duplicate ID") and an integration test in client_test.exs (describe "duplicate request ID (R043)") that fires two send_message calls with the same JSON-RPC id back-to-back and asserts the second returns the error while the first still resolves when the server replies (R043)
  • Blocked callers no longer hang or inherit stale timeouts on automatic disconnect — On the automatic Gun disconnect/reconnect path, state.pending_requests was never drained, so callers blocked on GenServer.call for a correlated response waited until their per-call request_timeout fired even though the socket was gone. handle_connection_error/2 now calls RequestCorrelator.fail_all/2, which replies {:error, :disconnected} to every pending caller and emits [:zen_websocket, :request_correlator, :fail_all] telemetry. Correlation timers now use unique timer refs in their mailbox messages, so a stale timeout from a disconnected or already-resolved request cannot incorrectly time out a new request that reuses the same ID after reconnect (R042)
  • retry_count not reset after successful reconnect — After a disconnect-reconnect cycle, retry_count accumulated instead of resetting to 0. This silently degraded reconnection capability over the lifetime of a long-running process — each successive disconnect cycle had fewer retry attempts available. Now reset to 0 on successful WebSocket upgrade (R030)
  • Explicit reconnect now preserves the original connection contractClient.reconnect/1 no longer falls back to URL-only reconnects. Client structs returned by connect/2 or ClientSupervisor.start_client/2 now retain their validated config plus runtime callbacks such as handler, heartbeat_config, on_connect, and on_disconnect, so explicit reconnects keep the same headers, timeouts, retry settings, callback behavior, and supervision mode (R030)
  • Config inspection now redacts header valuesinspect(config) (and inspect(client) output containing client.config) redacts header values via a custom Inspect impl, preventing bearer tokens or API keys from leaking through struct inspection. Debug-mode log line that directly logged config.headers during WebSocket upgrade has been removed (R030)

Added

  • GunStub test helper codifies the R044 transport-shape fence — New ZenWebsocket.Test.Support.GunStub in test/support/ exposes four constructors (gun_upgrade/3, gun_ws/3, gun_down/4, gun_error/3) that build the raw Gun transport tuples consumed by MessageHandler.handle_message/2. Every constructor defaults to real pids (self/0) and real refs (make_ref/0) — no fake opaque values, as required by R044. The @moduledoc states the fence verbatim and enumerates what is explicitly not allowed (API fixtures, auth flows, exchange semantics), pointing to MockWebSockServer as the source of truth for behavioral tests. One existing test in message_handler_test.exs ("handles gun_upgrade message for websocket") adopts the helper to prove it is viable; further adoption is incremental (R045)
  • MessageHandler property-test coverage — New message_handler_property_test.exs uses GunStub + stream_data to assert handle_message/2 routing totality and shape determinism: arbitrary non-Gun tuples always route to {:ok, {:unknown_message, _}} without raising; gun_down returns {:connection_down, pid, reason} for any reason term; gun_error returns {:connection_error, pid, ref, reason} for any reason term; text and binary data frames reach the handler callback across StreamData.string(:utf8) and StreamData.binary() respectively. Runs async: true and is fully pure (no network, no MockWebSockServer), so it stays in the default unit-test band (R046)
  • Property-based test coverage — Added property tests using stream_data for three pure, deterministic modules (R010):
    • Frame: Gun-format and direct-format decode round-trips, constructor round-trips, close-frame normalization (integer code discarded), totality on arbitrary unknown shapes
    • Config: valid-input totality across all positive-int fields, URL scheme/host validation, per-field non-positive rejection, max_backoff < retry_delay ordering constraint, new!/2 consistency with new/2
    • JsonRpc: build_request/2 shape invariants, unique ID generation across N calls, match_response/1 coverage for result, error, and notification cases
    • Deferred: MessageHandler property tests require Gun transport shape fixtures, which are blocked by the current "NO MOCKS" policy — tracked as R044 (policy amendment), R045 (GunStub helper), R046 (MessageHandler properties)
  • Error scenario test coverage — Added explicit coverage for previously-untested error paths (R011):
    • Gun error variants in error_handler_test.exs: {:gun_error, ..., :closed}, {:gun_error, ..., :timeout}, {:gun_down, ..., :tls_error, []}, {:gun_down, ..., :protocol_error, [refs]}, and explain/1 unwrapping for both shapes
    • Frame corruption in frame_test.exs: unknown atom frame types, non-tuple input, empty tuple, arity-mismatched :ws frames, unknown inner :ws type, empty and 1MB payloads, deeply nested inner type, map input — all return {:error, _} rather than crashing
    • Concurrent correlation timeout cleanup in request_correlator_test.exs: N concurrent tracks timing out clear pending_requests and emit N telemetry events; timeout on an already-resolved request is a no-op
    • Rate limit recovery in rate_limiter_test.exs: queue drain via refill after :queue_full, mixed-cost bucket-capacity cap behavior, refill token cap verification, and concurrent consume calls not losing tokens
    • Follow-up: The gap R011 surfaced (pending requests not drained on Gun disconnect) is addressed by R042 above.
  • Deployment considerations guide — New docs/guides/deployment_considerations.md covering latency sensitivity by strategy type, geographic proximity to exchange matching engines, connection architecture trade-offs (single vs pool vs per-account), production monitoring signals, and cancel-on-disconnect interactions. Framed as "questions to ask yourself" rather than prescriptive rules — the right answer depends on the operator's strategy and constraints. Cross-linked from README guide table and registered in mix.exs ExDoc Guides group (R025)
  • Reconnection behavior documentation — USAGE_RULES.md now distinguishes automatic reconnect from explicit Client.reconnect/1, documenting what is preserved, reset, or carried across each path (R030)
  • Config preservation regression tests — Mock-server regression tests now run in the default test suite and verify: retry_count resets after successful automatic reconnect, handler callbacks survive reconnect, Config structs remain identical across reconnects, supervised reconnect reruns lifecycle callbacks under ClientSupervisor, and explicit reconnect preserves the stored connection contract even after the original client is closed (R030)

0.4.0 - 2026-04-12

Added

  • Self-describing API via Descripex — All 17 library modules annotated with use Descripex and api() macro declarations. Root ZenWebsocket module uses Descripex.Discoverable for three-level progressive disclosure: describe/0 (library overview), describe/1 (module functions), describe/2 (full function detail). Existing @doc strings preserved — api() writes machine-readable hints (BEAM slot 5) while @doc retains human prose (slot 4). Enables MCP tool discovery and JSON Schema generation. Tests cover all three describe levels and module registration completeness (R040)
  • Custom client discovery hookssend_balanced/2 accepts optional :client_discovery function for plugging in custom registries (pg, Horde, :global) instead of local-only discovery. start_client/2 accepts :on_connect and :on_disconnect lifecycle callbacks for external registry integration. Default behavior (local discovery via list_clients/0) unchanged. Documentation with pg and Horde examples in USAGE_RULES.md (R024)

Fixed

  • Stale client PIDs no longer crash callerssend_message/2, get_state/1, get_heartbeat_health/1, get_state_metrics/1, and get_latency_stats/1 now check Process.alive? before GenServer.call. Dead PIDs return appropriate fallbacks (error tuple, :disconnected, or nil) instead of raising :exit. send_balanced/2 benefits automatically via existing failover logic. Best-effort guard — callers needing race-proof delivery should use send_balanced/2 with :client_discovery (R024) (R029)
  • Subscription messages not reaching user handlerroute_data_frame/2 sent "method" => "subscription" messages only to SubscriptionManager, never forwarding to the user handler callback. Now updates tracker state and forwards to handler (R038)
  • Protocol errors not reaching user handlerhandle_frame_error/2 stopped the GenServer on protocol errors without notifying the user handler first. Now calls handler.({:protocol_error, reason}) before stopping, matching the create_handler/1 contract (R039)
  • Double callback delivery bugMessageHandler.handle_message/2 called user handler, then route_data_frame called it again for every data frame. Added decode_and_handle_control/1 to MessageHandler for decode + control frame handling without handler invocation; Client GenServer uses this instead. Malformed frames are still classified as fatal protocol errors via ErrorHandler (R035)
  • Skipped reconnection TODO replaced with real integration test — verifies Client GenServer survives MockWebSockServer disconnect and enters reconnection mode (R033)
  • WebSocket upgrade now preserves query parameters from the connection URL — previously wss://host/path?token=abc would upgrade as just /path, dropping the query string (R031)
  • DeribitAdapter.subscribe/2, unsubscribe/2, authenticate/1, and send_request/3 now return {:error, :not_connected} when client is nil instead of raising FunctionClauseError (R027)
  • BatchSubscriptionManager now handles subscribe failures: marks request as failed with error reason and stops processing instead of silently ignoring the return value (R028)
  • DeribitGenServerAdapter @doc corrected from "handler module" to "handler function" (R028)
  • ErrorHandling example missing handle_info clause{:websocket_error, reason} messages caused FunctionClauseError in examples/docs/error_handling.ex. Added catch-all error handler clause (R041)
  • subscribe/2 return type documented incorrectly — USAGE_RULES.md showed {:ok, subscription_id} but actual spec returns :ok | {:error, term()} (R041)

  • send_message/2 examples passed maps instead of binaries — README.md and USAGE_RULES.md examples used %{action: "ping"} but spec requires binary(). Fixed to use Jason.encode!/1 (R041)
  • Non-existent telemetry event documented[:zen_websocket, :client, :message_received] in USAGE_RULES.md replaced with accurate event list (R041)
  • Stale telemetry events in performance_tuning.md — Events table listed [:zen_websocket, :request, :start/complete/timeout] and [:zen_websocket, :subscription, :add/remove] which use wrong namespaces. Replaced with actual 16 events from codebase across 6 namespaces (R041)
  • get_state/1 misused in performance_tuning.md — Examples showed Client.get_state/1 returning full state map. Fixed to use get_latency_stats/1, get_heartbeat_health/1, get_state_metrics/1 (R041)
  • Monitoring return shapes wrong in docsget_heartbeat_health documented as %{failures: ..., last_at: ...} but returns %{failure_count: ..., last_heartbeat_at: ...}. get_state_metrics documented as %{pending_requests: ..., subscriptions: ..., memory_bytes: ...} but returns %{pending_requests_size: ..., subscriptions_size: ..., state_memory: ...}. Latency stats documented as floats but returns integers. Fixed in USAGE_RULES.md and performance_tuning.md (R041)
  • reconnect/1 missing limitation note — Documented without noting it drops custom opts (headers, timeouts, etc.) on reconnect. Added note referencing R030 (R041)
  • ErrorHandling example send_message @doc claimed JSON encoding@doc said "will be JSON encoded" but Client.send_message/2 requires binary. Fixed doc and spec (R041)
  • Architecture.md claimed "Gun HTTP/2" — Library actually forces HTTP/1.1 ALPN for WSS upgrades. Also claimed "5 functions per module" without qualifying existing modules. Fixed both (R041)
  • last_heartbeat_at documented as DateTime — Docs showed ~U[...] but actual value is System.monotonic_time(:millisecond) (monotonic integer). Fixed in USAGE_RULES.md and performance_tuning.md (R041)
  • ErrorHandling example understated error surfacesend_message/1 doc/spec claimed only :ok | {:error, :not_connected} but delegates to Client.send_message/2 which returns :ok | {:ok, map()} | {:error, term()} including {:error, {:not_connected, reason}} variants. Fixed spec and doc (R041)

Improved

  • Reconnection test now restarts mock server and verifies post-reconnect frame delivery — previously only proved GenServer survived disconnect (R036)

  • Subscribe test now captures server-received frame and validates JSON-RPC payload structure (method, channels) — previously only checked :ok return (R037)

  • Example files updated for handler contract changeErrorHandling example now handles {:websocket_protocol_error, ...} and {:websocket_frame_error, ...} instead of non-existent {:websocket_error, ...}; JsonRpcClient.handle_message/1 now accepts pre-decoded maps instead of assuming raw JSON strings (codex review)

  • AGENTS.md module overview corrected — Fixed stale function names across 9 modules (Frame, ErrorHandler, JsonRpc, Reconnection, MessageHandler, HeartbeatManager, SubscriptionManager, RequestCorrelator, RateLimiter). Added missing LatencyStats entry. Fixed test code example using non-existent Frame.encode/1 (codex review)

Changed

  • Quality workflow updated — Removed mix lint, mix typecheck, mix coverage, mix check, mix rebuild aliases from mix.exs. Use mix test.json, mix dialyzer.json --quiet, mix credo --strict --format json directly for AI-friendly structured output. mix security remains as the Sobelow alias and now includes --skip so .sobelow-skips is honored for the known low-confidence Recorder findings.
  • CLAUDE.md imports updated — Added cli-aliases.md and agent-economy.md includes; reordered to match Elixir Library template
  • Roadmap reformatted — Migrated to [D:X/B:Y/U:Z → Eff:W] priority format; archived completed task details to CHANGELOG; added doc-update requirement to all pending tasks
  • Added descripex ~> 0.6 dependency for self-describing APIs
  • All docs updated — README, AGENTS.md, CONTRIBUTING.md, USAGE_RULES.md updated to reference JSON output commands instead of removed aliases
  • Handler callback contract — valid JSON text frames are now delivered as decoded maps (%{"key" => "value"}) instead of raw binary strings. Non-JSON text frames remain as binary. If your handler pattern-matches on {:websocket_message, msg} when is_binary(msg) and calls Jason.decode/1, update to match on {:websocket_message, %{} = msg} for JSON and {:websocket_message, msg} when is_binary(msg) for non-JSON text (R035)
  • Root ZenWebsocket moduledoc rewritten to document current API — replaces legacy references to Connection, Platform, Behaviors, and Defaults with actual Client, ClientSupervisor, and module index (R034)
  • AGENTS.md module overview and file tree updated — Corrected function names/arities for all modules, added missing ConnectionRegistry/Debug entries, updated file organization tree to all 19 modules, removed stale "separate mix project" guidance for examples (R041)
  • docs/Architecture.md updated — Added 9 missing modules (LatencyStats, RecorderServer, Testing, ClientSupervisor, PoolRouter, Debug, HeartbeatManager, SubscriptionManager, RequestCorrelator), updated data flow diagram (R041)
  • "5 functions" framing updated — USAGE_RULES.md, README.md, and AGENTS.md now describe "5 essential/core functions" with note about additional monitoring and management functions (R041)
  • Version bump to 0.4.0

Reverted

  • R026 (Deribit example as separate mix project) — ergonomic cost outweighed benefit: broken Tidewave access, broken .iex.exs, 13+ stale doc references. Examples stay in lib/zen_websocket/examples/

0.3.1 - 2026-01-21

Changed

  • Update ex_doc to ~> 0.40 for llms.txt support (AI-friendly documentation)

0.3.0 - 2026-01-21

Added

  • ZenWebsocket.PoolRouter module for health-based connection routing (R022)
    • select_connection/1 - select healthiest connection from pool
    • calculate_health/1 - score (0-100) based on pending requests, latency, errors, pressure
    • record_error/1 / clear_errors/1 - error tracking with 60s decay
    • pool_health/1 - get health snapshot for all connections
    • Round-robin fallback when connections have equal health
  • ClientSupervisor.send_balanced/2 for load-balanced message routing (R022)
    • Routes to healthiest connection using PoolRouter scoring
    • Automatic failover on send failure (configurable max_attempts)
    • Records errors and emits telemetry on failover
  • Telemetry events for pool routing (R022)
    • [:zen_websocket, :pool, :route] - connection selected with health score
    • [:zen_websocket, :pool, :health] - pool health snapshot
    • [:zen_websocket, :pool, :failover] - failover attempt with reason
  • AGENTS.md guide for AI coding agents contributing to the project (R023)
    • Module overview with key functions
    • Project constraints (5 functions, 15 lines, real API testing)
    • Testing strategy and common patterns
    • Debugging guide (recording, state inspection)
  • ZenWebsocket.Testing module with consumer-facing test utilities (R020)
    • start_mock_server/1 - simplified mock server startup with URL generation
    • stop_server/1 - cleanup server and resources
    • simulate_disconnect/2 - trigger disconnect scenarios (:normal, :going_away, {:code, n})
    • inject_message/2 - send message from server to connected clients
    • assert_message_sent/3 - verify client sent expected message (string, regex, map, or function matcher)
    • Helpers integrate with ExUnit setup/on_exit patterns
  • ZenWebsocket.Recorder module for session recording (R019)
    • format_entry/3 - format frames as JSONL entries
    • parse_entry/1 - parse JSONL entries back to structs
    • replay/3 - stream recorded sessions to handler function
    • metadata/1 - get session statistics (count, duration, timestamps)
  • ZenWebsocket.RecorderServer async GenServer for file I/O (R019)
    • Buffered writes with periodic flush (1s interval or 100 entries)
    • Non-blocking record/3 via send (not call)
    • stats/1 returns entries count and bytes written
  • Config option record_to: path enables session recording (R019)
    • Records inbound and outbound frames with microsecond timestamps
    • JSONL format (one JSON object per line) for streaming writes
    • Binary frames encoded as base64
    • Close frames include code and reason

Changed

  • PoolRouter.calculate_health/1 uses div/2 for cleaner integer arithmetic
  • ClientSupervisor restart policy documented in @moduledoc (moved from comment)
  • Private functions consistently use @doc false with explanatory comments
  • USAGE_RULES.md expanded with v0.2.0+ features (R023)
    • Testing module documentation (replaced MockWebSockServer references)
    • Session recording section with Recorder API
    • Expanded monitoring/observability section (latency, heartbeat, metrics)
    • ErrorHandler.explain/1 usage example
    • New config options (record_to, latency_buffer_size)
    • Telemetry events reference table
    • Updated test count (93 → 395)

Removed

  • Stale documentation files superseded by ROADMAP.md and AGENTS.md
    • docs/TaskList.md, docs/deferred_tasks.md
    • docs/JsonRPCElixir_MigrationTasks.md, docs/json_rpc_elixir.md
    • docs/WNX0019_learnings.md, docs/test_roadmap.md

0.2.0 - 2026-01-20

Added

  • Building Adapters Guide enhanced at docs/guides/building_adapters.md (R012)
    • Adapter decision tree (plain client vs struct vs GenServer)
    • Heartbeat interface documentation (:deribit, :ping_pong, :binance, custom)
    • Authentication patterns (API key+secret, HMAC signature, OAuth token flow)
    • Binance Spot adapter example (non-JSON-RPC pattern)
  • Performance Tuning Guide at docs/guides/performance_tuning.md (R013)
    • Configuration parameter tuning (timeouts, reconnection, latency buffer)
    • Rate limiter tuning with exchange-specific cost functions
    • Telemetry events reference table
    • Memory characteristics documentation
    • Common tuning scenarios (HFT, market data, resource-constrained)
  • JsonRpc edge case tests for nil/empty params, empty methods, malformed responses (R016)
  • ErrorHandler.explain/1 returns human-readable error messages with fix suggestions (R018)
  • ZenWebsocket.LatencyStats module for bounded circular buffer latency statistics (R017)
  • Client.get_latency_stats/1 returns p50/p99/last/count latency metrics (R017)
  • Telemetry event [:zen_websocket, :connection, :upgrade] with connect_time_ms measurement (R017)
  • Telemetry event [:zen_websocket, :heartbeat, :pong] with rtt_ms measurement (R017)
  • Telemetry event [:zen_websocket, :rate_limiter, :pressure] for backpressure signaling (R021)
  • Config option latency_buffer_size (default: 100) for latency stats circular buffer (R017)
  • RateLimiter.status/1 now returns pressure_level and suggested_delay_ms fields (R021)
  • ZenWebsocket.HeartbeatManager module for heartbeat lifecycle management (R001)
  • ZenWebsocket.SubscriptionManager module for subscription tracking and restoration (R002)
  • ZenWebsocket.RequestCorrelator module for JSON-RPC request/response correlation (R003)
  • Telemetry events for SubscriptionManager: :add, :remove, :restore (R002)
  • Telemetry events for RequestCorrelator: :track, :resolve, :timeout (R003)
  • RateLimiter.shutdown/1 for proper ETS table cleanup (R005)
  • Configurable max_queue_size option for RateLimiter (default: 100) (R005)
  • Telemetry events for rate limiter: :consume, :queue, :queue_full, :refill (R005)
  • Memory characteristics documentation in RateLimiter @moduledoc (R005)
  • Test tagging conventions documentation in test_helper.exs (R015)
  • Test coverage exclusion config for non-production modules (T001)
  • Test coverage roadmap documentation at docs/test_roadmap.md
  • Frame edge case tests for direct frame format decoding (T002)
  • Config boundary value tests for new!/2 and request_timeout validation (T003)
  • Reconnection extreme value tests for nil max_backoff and zero retries (T004)

Changed

  • RequestCorrelator now stores timestamps and emits round_trip_ms in resolve telemetry (R017)
  • RateLimiter tracks pressure level and emits threshold-crossing events at 25%/50%/75% queue fill (R021)
  • HeartbeatManager emits RTT telemetry on heartbeat responses (R017)
  • Client tracks connection timing from connect start to WebSocket upgrade (R017)
  • Test coverage metrics now exclude non-production modules (Examples, Test.Support, Mix.Tasks) - reported coverage ~38% → ~70% (T001)
  • Frame module now at 100% test coverage (T002)
  • Config module now at 100% test coverage (T003)
  • Extracted heartbeat logic from Client.ex to HeartbeatManager (789 lines from 870) (R001)
  • Extracted subscription tracking from Client.ex to SubscriptionManager (R002)
  • Extracted request/response correlation from Client.ex to RequestCorrelator (R003)
  • Client.ex now fully delegates to extracted modules; removed dead restore_subscriptions/4 from Reconnection (R004)
  • Automatic subscription restoration on reconnect via maybe_restore_subscriptions/1 (R002)
  • Replaced magic numbers with named module attributes in Client, ClientSupervisor, and Reconnection modules (R008)
  • Standardized Debug.log/2 to accept only Config.t() struct, removed polymorphic state map interface (R009)
  • BasicUsage example now uses Deribit testnet instead of echo.websocket.org
  • MockWebSockServer handler registration improved in websocket_init/1
  • Downgraded Elixir from 1.20.0-rc.1 to 1.19.5 (fixes Credo false positives)
  • RateLimiter handle_rate_limit/5 now accepts config parameter to avoid double ETS lookup (R005)

Fixed

  • Dialyzer warning on Client.maybe_restore_subscriptions/1 - expanded Client.state() type to include all fields (R002)
  • ConnectionRegistry monitor leaks: cleanup_dead/1 and shutdown/0 now properly demonitor before deletion (R006)
  • Flaky tests: migrated from unreliable echo.websocket.org to local MockWebSockServer
  • Race conditions in ErrorHandlingTest with proper wait_for_connection/1 polling
  • MockWebSockServer now raises clear error when TLS certificates unavailable
  • Test tagging: 7 integration test files now properly tagged with @moduletag :integration (R015)
    • mix test now runs 141 unit tests (~5 seconds vs ~93 seconds for full suite)
    • Removed incorrect @moduletag :integration from platform_adapter_template_test.exs (pure unit tests)
    • Removed module-level tag from rate_limiting_test.exs (uses @describetag for integration blocks only)

0.1.4 - 2025-11-05

Changed

  • Breaking: DeribitRpc functions now return {:ok, map()} tuples instead of bare maps for consistency with library conventions
  • Updated Erlang from 27.3.4 to 28.1.1
  • Updated Elixir from 1.18.4 to 1.19.1-otp-28

Improved

  • DeribitAdapter and DeribitGenServerAdapter updated to use with statements for better error handling
  • Correlation test improved with MockWebSockServer usage
  • Test helper configuration cleaned up for better readability

0.1.3 - 2025-08-11

Fixed

  • Compilation error in mix zen_websocket.validate_usage task (regex in module attributes)

0.1.2 - 2025-08-11

Added

  • USAGE_RULES.md for AI agents and developer guidance
  • Mix task zen_websocket.usage to export usage rules
  • Mix task zen_websocket.validate_usage to validate code patterns
  • Integration with usage_rules library ecosystem
  • JSON export format for usage rules
  • Automated code validation for common anti-patterns

Improved

  • Documentation with clear usage patterns and examples
  • Package metadata for Hex.pm publishing

0.1.1 - 2025-05-24

Added

  • Initial release of ZenWebsocket
  • Core WebSocket client implementation with Gun transport
  • Automatic reconnection with exponential backoff
  • Comprehensive error handling and categorization
  • JSON-RPC 2.0 protocol support
  • Request/response correlation manager
  • Configurable token bucket rate limiter
  • Integrated heartbeat/keepalive functionality
  • Fault-tolerant adapter architecture
  • Production-ready Deribit exchange integration
  • Connection registry for multi-connection management
  • Message handler with routing capabilities
  • WebSocket frame encoding/decoding
  • Telemetry events for monitoring
  • Comprehensive test suite using real APIs (no mocks)
  • Full documentation with examples

Features

  • Simple 5-function public API
  • Financial-grade reliability for trading systems
  • Platform-agnostic design with adapter pattern
  • Real-world tested against live WebSocket endpoints
  • Strict code quality standards (max 5 functions per module, 15 lines per function)