Repository navigation
feat(analytics-events): share the analytics event contract - #1538
Merged
Merged
Conversation
Adds the `flipcash.analytics.catalogue` convention plugin. It reads a module's `events.toml` and generates the value enums, `PeopleCounter`, and one builder object per domain into `commonMain`, in the shapes `:libs:analytics-events` wrote by hand. The parser rejects unknown keys, types and groups, parameters nothing sends, templates naming a missing or optional parameter, and duplicate builders, cases and property keys. Each message names the entry at fault.
Applies the catalogue plugin, with an events.toml that declares only the package. Domains move into it in the following commits.
The amount helper writes Mint and Exchange Rate only when the amount has them, so a builder may send its own Mint first, as SwapEvents does. Only Fiat, Currency, USDC and Quarks now count as duplicates.
Add the generator's sources to the SharedCore tests paths filter, and run build-logic's tests from flipcashTestDebug so the Flipcash tests lane covers the generator. Document in events.toml how to add an event, and point the Swift facade's comment at the catalogue.
This was referenced Sep 24, 2026
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.
Both apps spell every analytics event name and property key by hand, in parallel, and the inventory found them drifting in both names and values. This adds the shared half of the fix: a KMP module whose events are defined once, in
libs/analytics-events/events.toml, and exported to iOS through:kmp:shared-corewith a Swift facade. Nothing calls it yet.Design:
docs/superpowers/specs/2026-09-23-shared-analytics-events-design.mdin the orchestrator repo, with the per-event table in2026-09-23-analytics-inventory.mdbeside it.The module
:libs:analytics-eventsis shaped like:libs:reporting.AnalyticsEvent(name, properties)carries a sealedPropertyValue(Text,Number,Flag), because Mixpanel treats"12"and12as different values and the type is part of the contract. The value enums and 13 builder objects, one per domain with 45 builders in all, are generated from the catalogue (see below). A null argument omits its key rather than sending an empty value. The module knows nothing about Mixpanel,Fiat,MintorThrowable, so callers convert their own types.It covers every event Android sends today, not only the ones the two apps already agree on. Where iOS sends something different, the builder's KDoc has a
DRIFT:line saying what iOS sends instead. Android-only events sayAndroid only; part 2 decides whether iOS sends it.Those notes are the worklist for settling the drift, so the two markers are kept distinct.Each domain has a commonTest suite that writes every expected name, key and value as a string literal and never references the module's enums. A renamed key or a mistyped value then fails the build on its own, rather than changing both sides of the assertion at once. 38 tests, run on the Android host,
macosArm64andiosSimulatorArm64.The catalogue
events.tomlis the source of truth. A build-logic task,generateAnalyticsEvents, writes the builders, value enums andPeopleCounterintobuild/generated/analytics/commonMainat build time, so no generated code is checked in.AnalyticsEvent.ktandAmount.ktstay hand-written. An entry looks like this:The format also covers fixed-format values (
format = "Failed to parse deeplink => {url}"), two builders sending one event name (Deeplink: Parse), and theamountandoutcomeproperty groups. The header ofevents.tomllists the steps for adding an event, including the literal test and the iOS release.The generator rejects a catalogue it would otherwise have to guess at: unknown keys or types, a parameter nothing sends, a template naming a missing parameter, and a duplicate builder or property key.
MintandExchange Ratemay sit beside{ group = "amount" }, becauseamount(...)writes them only when the amount has them. Its 26 tests are inbuild-logic/convention/src/test.The builders were hand-written first and moved into the catalogue one domain per commit. The 38 commonTest suites and the 6 Swift tests are unchanged since before the move, so they check that the generated builders send exactly what the hand-written ones did.
The facade renames four types
AnalyticsEvents.swiftre-exports the Kotlin types as typealiases, asReportReason.swiftdoes, and addsscalarProperties, which flattensPropertyValueinto a SwiftAnalyticsScalarenum for the app to map ontoMixpanelType.Four names change on the way through:
AnalyticsEventTrackedEventprotocol AnalyticsEvent, andFlipcashCorere-exportsSharedCoreKitButtonAnalyticsButtonButtonStateEventState@StateAmountAnalyticsAmountAgainst
code-ios-appmain, the only other exported names that match a Swift declaration are theAnalytics.AddMoneySourceandAnalytics.DisplayNameSourcenested enums, which don't collide with a top-level name and go away as iOS migrates.The builders are Kotlin objects, so Swift calls them through
.shared, e.g.ChatEvents.shared.sentMessage(chatType: .group, error: nil). Kotlin/Native keeps the hump on these enum cases:TokenInfoSource.chatGate,AnalyticsButton.shareTokenInfo.PropertyValuebridges as a protocol with no exhaustiveness, soscalarPropertieshits apreconditionFailureon an unmapped case. A fourth value type then failsAnalyticsEventsTestsinstead of silently dropping properties in production.Registrations
includeandkmpUnitTestModulesinsettings.gradle.kts,apiandexportin:kmp:shared-core, and inshared-core-tests.ymlthelibs/analytics-events/**and generator source path filters plus:libs:analytics-events:iosSimulatorArm64Test. The workflow entries are the ones that fail silently if missing.flipcashTestDebugnow also runs:build-logic:convention:test, which is how the generator's tests reach the Flipcash tests lane; build-logic's tests didn't run in CI before.What's next
The Android refactor that moves every call site onto these builders follows on a branch stacked on this one. iOS is publish-gated: it pins
.upToNextMinor(from: "0.7.0"), and the newestflipcash-shared-core-spmrelease is0.7.1, so the iOS work waits on the next minor after this merges. If reporting's release hasn't gone out by then, the two ship together.