2.1.0 — fix two silent event-loss bugs, add observability, docs and repo health - #9
Merged
Merged
Conversation
Additive throughout — 2.0.0 code keeps compiling. The two fixes are behaviour changes, and both replace silent data loss with delivery. Fixed (each confirmed by probe before fixing, each with a regression test): * Events tracked before any tracker was registered were silently lost. That is exactly the app-launch case: anything reported before `register` returned went nowhere, with no diagnostic. * Events could reach a provider before its SDK was initialised. `track` before `start()` called `record()` on a tracker that had never been started — Firebase logging before `FirebaseApp.configure()`, Mixpanel discarding the event internally. Both are governed by AnalyticsStartupBuffer, which holds up to 100 events by default and replays them in order once `start()` completes. `.disabled` restores the previous behaviour. Added: * Global properties, merged into every record; event attributes win on conflict. * AnalyticsDiagnostic and a handler on Configuration, reporting every event the system buffers, drops, rejects or rewrites. All of these were previously invisible — an event that never sent looked identical to one never tracked. * AnalyticsRecordValidator per registration, with `.firebase` encoding Firebase's real limits. Firebase discards violations server-side and reports nothing, so these were undebuggable. * flushPendingEvents() on AnalyticsTracker (default no-op), surfaced as flushProviders(), wired to Mixpanel and Facebook, for backgrounding. * DocC catalog with two articles, published to GitHub Pages; .spi.yml; CONTRIBUTING, SECURITY, issue/PR templates, CODEOWNERS, Dependabot; code coverage and a DocC job in CI. Coverage rose from 71.9% to 87.8% of lines; 96 tests in 18 suites, up from 57 in 11. Note: DocC is built with `xcodebuild docbuild` rather than swift-docc-plugin, because the plugin appears in a default `swift package resolve` and would break the zero-dependency guarantee. CI asserts that guarantee on every run. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
I probed 2.0.0 for weaknesses before proposing anything, and found two real bugs — both of which lose data silently. Everything else here follows from the same theme: analytics failures are invisible by default, so the library should say when it discards something.
Additive throughout; 2.0.0 code keeps compiling.
The two bugs
Confirmed by probe before fixing, each now with a named regression test:
track()before any tracker is registeredstart()track()beforestart()record()delivered to a provider whose SDK was never initialised — Firebase logging beforeFirebaseApp.configure(), Mixpanel discarding internallystart()Governed by
AnalyticsStartupBuffer— 100 events by default, replayed in order, bounded with oldest-dropped-first..disabledrestores the old behaviour.Note this is a behaviour change in a minor version. I think it's justified: in both cases the previous behaviour was losing data, and the new behaviour delivers it. Tests that assert on delivery now need
start(), which is what an app does anyway.Making failure visible
AnalyticsDiagnosticplus a handler onConfigurationreports every event the system buffers, drops, rejects or rewrites. Previously all of it was invisible — an event that never sent looked identical to one never tracked.AnalyticsRecordValidatoris the same idea for vendor limits. Firebase silently discards events with names over 40 characters, more than 25 parameters, or reservedfirebase_/google_/ga_prefixes — nothing is reported, the data just never appears..firebaseruns those rules locally, sanitizing what it safely can and rejecting the rest with a reason.Also
setGlobalProperties(_:)merged into every record, event attributes winning on conflict.flushProviders()— asks each vendor SDK to send what it batched, for backgrounding. Wired to Mixpanel and Facebook;flushPendingEvents()defaults to a no-op so no provider is forced to stub it..spi.yml, CONTRIBUTING, SECURITY, issue/PR templates, CODEOWNERS, Dependabot.One deliberate non-choice
I added
swift-docc-plugin, then removed it: it shows up in a defaultswift package resolve, which would break the zero-dependency guarantee that is this package's headline feature. Docs are built withxcodebuild docbuildinstead, which costs nothing. CI asserts the guarantee on every run.Verification
AppEvents.shared.flush()and.firebaseverified on the iOS integration build, since Facebook's code is stubbed out on macOS.-strict-concurrency=completeclean,swiftlint --strictclean, podspec lints at 2.1.0, DocC builds.swift package resolvestill pulls zero third-party packages.🤖 Generated with Claude Code