# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
## [0.6.0] - 2026-06-20
### Added
- **`EventMetadata::instant_utc()`** — returns the true UTC instant for events
carrying an embedded epoch-millisecond `"timestamp"` (currently
match-lifecycle events from `matchGameRoomStateChangedEvent`). Unlike the
log-header timestamp, this is timezone-independent and correct regardless of
the host's local zone (including the `wasm`/headless build). Returns `None`
for events without an embedded epoch-ms field ([#251], [#252]).
- **`EventMetadata::local_timestamp()`** — returns the log-header timestamp as a
zone-less `chrono::NaiveDateTime`, the honest representation of MTGA's local
wall-clock header value ([#251], [#252]).
- **`EventMetadata::with_instant()`** — constructor that accepts both the header
timestamp and an `instant_utc` value ([#251], [#252]).
### Changed
- **`EventMetadata::timestamp()` is now `#[deprecated]`.** MTGA log-header
timestamps are local wall-clock time mislabeled as UTC (the inner
`NaiveDateTime` is promoted with `.and_utc()`). Use `instant_utc()` for the
absolute UTC instant, or `local_timestamp()` for the honest zone-less local
value. The accessor still works unchanged; this release only adds the
deprecation warning ([#251], [#252]).
- Corrected the `src/log/timestamp.rs` documentation, which previously claimed
log-header timestamps were UTC ([#251], [#252]).
### Notes
- This release is **additive and backward-compatible**. Serialized payloads
round-trip with and without the new `instant_utc` field (`#[serde(default)]`).
Wiring `instant_utc` for `.NET-ticks` events (e.g. `client_actions`) is
deferred to a future release.
[#251]: https://github.com/manasight/manasight-parser/issues/251
[#252]: https://github.com/manasight/manasight-parser/pull/252
## [0.5.3] - 2026-06-19
### Fixed
- **`parse_whole_log_js` (WASM) now serializes event payloads as plain JS
objects instead of `Map` instances.** The wasm-bindgen export used the default
`serde_wasm_bindgen` serializer, which emits dynamic `serde_json::Value`
objects — every `payload` and nested dynamic object — as JS `Map`s, so JS/TS
consumers reading fields by property access (`payload.match_id`) got
`undefined` and had to fall back to `.get(...)`. This diverged from the native
`serde_json` output. The serializer is now configured with
`serialize_maps_as_objects(true)`, so WASM output matches the native
plain-object shape and the documented externally-tagged event contract. Only
WASM consumers were affected; native Rust output is unchanged (#243, #244).
## [0.5.2] - 2026-06-19
### Fixed
- **`parse_whole_log` now parses logs from the newer MTGA Mac client** that
prefixes every line with a Unity frame counter
(`[<frame>] [UnityCrossThreadLogger]…`, the uploaded `UTC_Log` archive
variant). Header and metadata detection is anchored at byte 0, so these
otherwise-valid logs produced **0 events**. `LineBuffer::push_line` now strips
an optional leading `[<digits>] ` prefix before detection — a single chokepoint
covering every byte-0-anchored detector. Output is byte-identical for
unprefixed input across the full corpus, so the live `Player.log` path is
unaffected (#240, #241).
## [0.5.1] - 2026-06-17
### Added
- **`GameEvent::DeckSubmission(DeckSubmissionEvent)`** surfacing the submitted
deck's registered `Format` and `DeckId` from `EventSetDeckV2` / `EventSetDeckV3`
request lines. A new family-matched parser claims the bare `==> EventSetDeck`
prefix — covering V2, V3, and any future `Vn` without panicking on an
unrecognized version — decodes the double-encoded `request`, and emits a
`{ metadata, payload }` event whose payload carries
`{ deck_id, deck_format, event_name, is_singleton }`. This gives the format
model a precise fallback for generic / bot-match queues whose event name is not
format-bearing. `GameEvent` is `#[non_exhaustive]`, so the new variant is
additive (#232, #237).
## [0.5.0] - 2026-06-16
> Released directly from `0.3.0`. Versions `0.4.0` and `0.5.0` were bumped in
> `Cargo.toml` during feature work but never published; `0.4.0` is intentionally
> skipped on crates.io and its changes are folded into this entry.
### Added
- **WebAssembly build target and synchronous whole-log parser.** New
`parse_whole_log(input: &str) -> Vec<GameEvent>` — a pure, synchronous, I/O-free
entry point that reuses the same `LineBuffer` + `Router` core as the async
desktop path. New `wasm` cargo feature exports `parseWholeLog` via `wasm-bindgen`
(`src/wasm.rs`) for a browser/Node parse worker; build with
`wasm-pack build --target web --no-default-features --features wasm`. The crate
now builds as both `cdylib` and `rlib` (#224, #230).
- **`tailer` cargo feature (default-on)** gating the async stack — `tokio`,
`known-folders`, the file tailer, log discovery, `event_bus`, and `stream`.
Building with `--no-default-features` yields a pure-sync, WASM-compatible subset
that still exposes `parse_whole_log` without pulling in `tokio` or filesystem
access. The default desktop build (`brace_depth_flush` + `tailer`) is unchanged
(#224).
- **`ScrubOptions` and `scrub_raw_log_with`.** New
`ScrubOptions { keep_player_names: bool }` (defaults to `false`) and
`scrub_raw_log_with(input, &ScrubOptions)`; `scrub_raw_log` keeps its existing
signature and behavior, now delegating with default options. Setting
`keep_player_names = true` retains in-game screen/player names (needed for seat
attribution) while still redacting everything else. Email, IPv4, and IPv6
addresses are now scrubbed as well (#225).
- **`GameEvent::Truncation(TruncationEvent)`** surfacing Arena's
`[Message summarized … exceeded the 50 GameObject or 50 Annotation limit.]`
marker that replaces an oversized GSM body. The payload carries
`{ object_count, annotation_count }`; a new `EntryHeader::TruncationMarker`
(classified `MultiLine`) accumulates the marker and its follow-on count lines.
`GameEvent` is `#[non_exhaustive]`, so the new variant is additive (#200, #201).
- **`prev_game_state_id` on game-state-message payloads**, read from inside the
`gameStateMessage` sub-object. Always present — serializes to `null` on Full
GSMs that omit it — giving consumers a contiguous Diff-sequence signal (#201).
- **ChoiceResult and LinkInfo annotation details.** `AnnotationType_ChoiceResult`
and `AnnotationType_LinkInfo` previously fell through the catch-all arm and
dropped all detail. The parser now emits `choice_value` (plus optional
`choice_domain` / `choice_sentiment`) and `choose_link_type` /
`source_ability_grp_id` / `link_type`, restoring the chosen card name for
"name a card" effects (e.g. Pithing Needle, Meddling Mage) that previously
reached consumers with base fields only (#221).
- **Linux (Steam/Proton + Lutris) `Player.log` auto-discovery** in
`discover_log_file()`. Parses
`~/.local/share/Steam/steamapps/libraryfolders.vdf` to find the Steam library
containing MTGA (app id 2141910) across any configured disk, with a Lutris prefix
fallback. On Linux, `UnsupportedPlatform` is no longer returned; instead a
`LogFileMissing` error (matching the Windows/macOS absent-file contract) is
returned when no candidate location contains a `Player.log` (#212, #213).
### Changed
- **`SelectN` client-action payload now reads `selectNResp.ids`** and emits a
single `selected_ids: Vec<i64>` field. The previous `selected_option_ids` /
`selected_object_ids` fields are **removed** — neither name appears in real MTGA
logs (zero corpus hits) and no downstream consumer read them. The optional
`useArbitrary` ordering tag remains reachable through `raw_client_action`
(#206, #208).
### Fixed
- GameOver `GameStateMessage`s carrying annotations (e.g. the lethal combat damage that ends the match) no longer drop them silently. When `gameInfo.stage == GameStage_GameOver` and the GSM has a non-empty `annotations` or `persistentAnnotations` array, the parser now emits a `GameEvent::GameState` carrying both arrays immediately before the existing `GameEvent::GameResult`. Per-producer ordering on the broadcast channel guarantees annotation-walker consumers see the killing-blow data before the result payload. GameOver GSMs with empty annotation arrays continue to emit only the `GameResult` event; the `MatchState_MatchComplete` suppression branch is unchanged (#196, #197).
### Removed
- **Dead `EntryHeader::ClientGre` variant.** `[Client GRE]` never appears in real
MTGA logs (zero occurrences across 44 sessions / 606k lines); the variant was
speculative scaffolding. Removed from the `EntryHeader` enum and all five match
arms (#207, #209).
- **Three zero-corpus-hit parser markers and their dead code paths**:
`Updated account. DisplayName:` account-update parsing (`authenticateResponse`
is the real identity source), `LogBusinessEvents`, and `PickGrpId`. All had zero
occurrences across the corpus (#205).
## [0.3.0] - 2026-05-13
### Changed
- **`LineBuffer` now flushes multi-line entries on JSON brace-balance** instead of waiting for the next header (#193, #194). Entries containing a `{` are emitted the moment their JSON body's depth returns to 0, dropping draft-event and other interactive-flow latency from seconds-to-minutes to one polling cycle (~50 ms). Public method signatures are unchanged — but downstream consumers that previously batched entries between headers will now receive them sooner.
- Non-JSON multi-line entries (`[Message summarized…]` GRE markers, `true`-bodied REST responses) keep the original "flush on next header" behavior as a deterministic fallback.
### Added
- New `brace_depth_flush` cargo feature, **default-on**, gating the new flush trigger. Disabling the feature reverts to the original next-header flush behavior — kept as a one-flip rollback in case a live-Arena edge case surfaces.
- New `BraceState` internal struct with a string-literal + escape-aware state machine; handles nested-JSON-in-string values, escaped quotes, escaped backslashes, and brace noise inside string literals.
- New `test-no-default-features` CI job (`.github/workflows/ci.yml`) so the rollback path cannot bit-rot.
- `proptest = "1"` added to `[dev-dependencies]` for state-machine property tests (3 generators + 6 corpus-derived regression cases).
### Fixed
- Smoke-test CI step now uses `set -o pipefail` so failures inside the piped `cargo test … | tee` no longer silently pass (#191, #192).
## [0.2.2] - 2026-05-07
### Added
- New `game_state_type` field on `game_state_message` and `queued_game_state_message` payloads, sourced from the inner `gameStateMessage.type` (`GameStateType_Full` / `GameStateType_Diff`). Field is always present — `None` serializes to JSON `null` — so the schema contract is unambiguous (#182, #183).
### Changed
- `Makefile` added with `precommit`, `precommit-trivial`, `coverage`, and `fmt` targets (#186)
- CI `check` job updated to call `make precommit` instead of inlining gate steps
- CLAUDE.md pre-commit checklist updated to reference `make precommit`
## [0.2.1] - 2026-04-30
### Added
- `DeckCollection` event variant for `StartHook` deck snapshots, correlating `DeckSummaries` metadata with `Decks` payloads by `DeckId`
- Ordered emit for `ConnectResp` GRE messages
- `SubmitDeckResp` Bo3 round-trip support
- Typed `Designation` annotation extractor
- Typed `Shuffle` annotation extractor
### Fixed
- Restored `BotDraft` parsing for modern API log signatures
## [0.2.0] - 2026-04-25
### Changed
- **Breaking:** `LineBuffer::push_line` now returns `Vec<LogEntry>` instead of `Option<LogEntry>`, so single-line entries can flush in the same call that produced them when a prior multi-line entry also needs to flush
### Removed
- **Breaking:** Dead `StartHook` `PlayerCards` collection-event path; inventory is the only supported `StartHook` snapshot
### Fixed
- Silenced routine post-flush "headerless line" warnings; the warning now fires only for true file-start / post-rotation anomalies
## [0.1.2] - 2026-04-15
### Added
- Installation instructions in README for crates.io consumers
## [0.1.1] - 2026-04-15
### Added
- **Parsers** for all Arena log event categories: session, match state, GRE (connect, game state messages, annotations), client actions, game results, bot draft, human draft, rank, inventory, collection, event lifecycle
- **Async event bus** with broadcast fan-out for real-time event delivery
- **File tailer** with polling-based log following
- **Multi-platform log discovery** for Windows and macOS
- **Timestamp parser** with Arena-specific format handling
- **Line buffer** with log entry header detection and reassembly
- **Performance-class router** for fast dispatch to the correct parser