diff --git a/content/docs/dashboard/paywalls.mdx b/content/docs/dashboard/paywalls.mdx index 0ca21500..3c5f15b8 100644 --- a/content/docs/dashboard/paywalls.mdx +++ b/content/docs/dashboard/paywalls.mdx @@ -22,10 +22,10 @@ You can toggle which paywalls are showing by using the **date toggle**, located Choose **Custom** to select any arbitrary date range to filter by. - +{/* The date toggle works when you are viewing your paywalls as a - [**list**](#viewing-paywall-metrics). - + **list**. + */} ### Viewing paywalls by status @@ -48,15 +48,15 @@ Here's what each status means: | Archived | Paywalls that have been archived. These can be restored if needed. | | Search | Perform a search across all of the app's paywalls, regardless of its status. | -### Viewing paywalls as a table or list +{/* ### Viewing paywalls as a table or list To toggle between viewing your paywalls by either a table or list, click the toggle buttons at the top: ![](/images/paywalls-overview-toggle-by-metric.png) -When viewing them as a **list**, Superwall also displays additional metrics. +When viewing them as a **list**, Superwall also displays additional metrics. */} -### Viewing paywall metrics +{/* ### Viewing paywall metrics Choose the **list** view to see high-level metrics about each paywall: @@ -81,7 +81,7 @@ Each metric displays the data in the time frame that's selected from the date to Click on any of these values at the top of the list to order the data by that metric, either ascending or descending. - + */} ### Creating a new paywall diff --git a/content/docs/integrations/firebase.mdx b/content/docs/integrations/firebase.mdx index 08a133c8..f1559347 100644 --- a/content/docs/integrations/firebase.mdx +++ b/content/docs/integrations/firebase.mdx @@ -5,6 +5,10 @@ description: "The Firebase integration automatically sends Superwall subscriptio The Firebase integration automatically sends Superwall subscription and payment events to Firebase Analytics (Google Analytics 4) using the Measurement Protocol. Track subscription lifecycle events, analyze revenue metrics, and leverage Firebase's powerful analytics capabilities with automatic event mapping and ecommerce tracking. + +This integration requires you to set the Firebase App Instance ID using `setIntegrationAttributes` in your client app. Without it, events will be silently skipped and won't appear in Firebase Analytics. See [App Instance ID Requirement](#app-instance-id-requirement) for setup instructions. + + ## Features - **Standard Ecommerce Events**: Uses Firebase's standard `purchase` and `refund` events for revenue tracking @@ -76,11 +80,11 @@ Firebase requires separate credentials for iOS and Android apps since each platf ## App Instance ID Requirement -**Critical**: The Firebase integration requires `firebaseAppInstanceId` to be set in the user's `userAttributes` from your client app. This is the unique installation identifier from the Firebase Analytics SDK. +**Critical**: The Firebase integration requires the Firebase App Instance ID to be set from your client app using `setIntegrationAttributes`. This is the unique installation identifier from the Firebase Analytics SDK, and without it events will be silently skipped. ### How to Set It Up -1. In your app, retrieve the Firebase App Instance ID: +Retrieve the Firebase App Instance ID and pass it to Superwall using `setIntegrationAttributes`: **iOS (Swift):** ```swift @@ -88,8 +92,8 @@ import FirebaseAnalytics Analytics.appInstanceID { appInstanceId, error in if let appInstanceId = appInstanceId { - Superwall.shared.setUserAttributes([ - "firebaseAppInstanceId": appInstanceId + Superwall.shared.setIntegrationAttributes([ + .firebaseInstallationId: appInstanceId ]) } } @@ -100,15 +104,27 @@ Analytics.appInstanceID { appInstanceId, error in import com.google.firebase.analytics.FirebaseAnalytics FirebaseAnalytics.getInstance(context).appInstanceId.addOnSuccessListener { appInstanceId -> - Superwall.instance.setUserAttributes(mapOf( - "firebaseAppInstanceId" to appInstanceId + Superwall.instance.setIntegrationAttributes(mapOf( + IntegrationAttribute.FIREBASE_APP_INSTANCE_ID to appInstanceId )) } ``` +**Flutter (Dart):** +```dart +import 'package:firebase_analytics/firebase_analytics.dart'; + +final appInstanceId = await FirebaseAnalytics.instance.appInstanceId; +if (appInstanceId != null) { + await Superwall.shared.setIntegrationAttributes({ + IntegrationAttribute.firebaseAppInstanceId: appInstanceId, + }); +} +``` + ### What Happens Without It -If `firebaseAppInstanceId` is not found in `userAttributes`: +If the Firebase App Instance ID is not set via `setIntegrationAttributes`: - The event is **skipped** (not sent to Firebase) ### Revenue Events (Standard Ecommerce) @@ -320,7 +336,7 @@ After sending events, verify in Firebase: ## Best Practices -1. **Set App Instance ID Early**: Call `setUserAttributes` with `firebaseAppInstanceId` as soon as the app launches to ensure all subscription events are tracked. +1. **Set App Instance ID Early**: Call `setIntegrationAttributes` with the Firebase App Instance ID as soon as the app launches to ensure all subscription events are tracked. 2. **Separate Environments**: Use separate Firebase projects (or at minimum, separate measurement streams) for sandbox and production to keep analytics clean. @@ -363,7 +379,7 @@ Calculate: Sum of value per user ### Events Not Appearing in Firebase -1. **Check App Instance ID**: Ensure `firebaseAppInstanceId` is set in `userAttributes` +1. **Check App Instance ID**: Ensure the Firebase App Instance ID is set via `setIntegrationAttributes` 2. **Verify Platform Credentials**: Confirm the correct platform credentials are configured: - iOS events (App Store) require `ios_firebase_app_id` + `ios_api_secret` - Android events (Play Store) require `android_firebase_app_id` + `android_api_secret` @@ -381,14 +397,14 @@ Calculate: Sum of value per user **Single-Platform Apps**: If your app is iOS-only or Android-only, you only need to configure credentials for that platform. Events from unconfigured platforms will be skipped (this is expected behavior). -### Missing firebaseAppInstanceId +### Missing Firebase App Instance ID -**Problem**: Events are being skipped with warning about missing `firebaseAppInstanceId` +**Problem**: Events are being skipped with warning about missing Firebase App Instance ID **Solutions**: -1. Ensure your app calls `FirebaseAnalytics.getAppInstanceId()` and passes it to Superwall -2. Verify `setUserAttributes` is called before any purchases occur -3. Check that the attribute key is exactly `firebaseAppInstanceId` (case-sensitive) +1. Ensure your app calls `FirebaseAnalytics.getAppInstanceId()` (or platform equivalent) and passes it to Superwall via `setIntegrationAttributes` +2. Verify `setIntegrationAttributes` is called before any purchases occur +3. Use the correct integration attribute key for your platform (e.g., `.firebaseInstallationId` on iOS, `IntegrationAttribute.firebaseAppInstanceId` on Flutter) ### Revenue Not Tracking diff --git a/src/components/ChangelogTimeline.tsx b/src/components/ChangelogTimeline.tsx index 3d3a6558..62704629 100644 --- a/src/components/ChangelogTimeline.tsx +++ b/src/components/ChangelogTimeline.tsx @@ -49,6 +49,59 @@ function filterToRecentMonths( }); } +/** + * Deduplicate entries by path when they fall within a short time window of each + * other (e.g. same deploy / commit batch). Genuine updates days or weeks apart + * are preserved as separate entries. + */ +const DEDUP_WINDOW_MS = 24 * 60 * 60 * 1000; // 24 hours + +function deduplicateByPath( + entries: ChangelogDataType['entries'] +): ChangelogDataType['entries'] { + const grouped = new Map(); + + for (const entry of entries) { + if (!grouped.has(entry.path)) { + grouped.set(entry.path, []); + } + grouped.get(entry.path)!.push(entry); + } + + const result: (typeof entries)[number][] = []; + + for (const group of grouped.values()) { + group.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()); + + let kept = group[0]; + result.push(kept); + + for (let i = 1; i < group.length; i++) { + const gap = new Date(kept.date).getTime() - new Date(group[i].date).getTime(); + if (gap > DEDUP_WINDOW_MS) { + kept = group[i]; + result.push(kept); + } + } + } + + result.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()); + + // Only the oldest entry per path can be "added" (New); later ones become "modified" (Update) + const firstSeen = new Map(); + for (let i = result.length - 1; i >= 0; i--) { + if (!firstSeen.has(result[i].path)) { + firstSeen.set(result[i].path, i); + } + } + return result.map((entry, i) => { + if (entry.changeType === 'added' && firstSeen.get(entry.path) !== i) { + return { ...entry, changeType: 'modified' as const }; + } + return entry; + }); +} + /** * Get the month key for a date (e.g., "2026-01"). */ @@ -153,8 +206,10 @@ export function ChangelogTimeline() { return ; } - // Only show entries from the last 3 months - const recentEntries = filterToRecentMonths(data.entries, MONTHS_TO_SHOW); + // Only show entries from the last 3 months, deduplicated per page + const recentEntries = deduplicateByPath( + filterToRecentMonths(data.entries, MONTHS_TO_SHOW) + ); if (recentEntries.length === 0) { return ; diff --git a/src/lib/changelog-entries.json b/src/lib/changelog-entries.json index a15b60e9..9f619737 100644 --- a/src/lib/changelog-entries.json +++ b/src/lib/changelog-entries.json @@ -1,6 +1,195 @@ { - "lastUpdated": "2026-02-19T17:46:40.862Z", + "lastUpdated": "2026-02-25T19:22:26.331Z", "entries": [ + { + "key": "content/docs/ios/guides/test-mode.mdx:2824ed71fb4bae46a9c080b9bf663e78bd4adc76", + "path": "ios/guides/test-mode.mdx", + "title": "Test Mode", + "description": "New guides for iOS SDK", + "category": "iOS SDK", + "subcategory": "Guides", + "url": "/docs/ios/guides/test-mode", + "date": "2026-02-24T15:55:53.000Z", + "changeType": "added" + }, + { + "key": "content/docs/dashboard/dashboard-creating-paywalls/paywall-editor-styling-elements.mdx:2824ed71fb4bae46a9c080b9bf663e78bd4adc76", + "path": "dashboard/dashboard-creating-paywalls/paywall-editor-styling-elements.mdx", + "title": "Styling Elements", + "description": "Updated creating paywalls for Dashboard", + "category": "Dashboard", + "subcategory": "Creating Paywalls", + "url": "/docs/dashboard/dashboard-creating-paywalls/paywall-editor-styling-elements", + "date": "2026-02-24T15:55:53.000Z", + "changeType": "modified" + }, + { + "key": "content/docs/flutter/changelog.mdx:8bb42154b95e4a3c3a91c023416e8149263c208a", + "path": "flutter/changelog.mdx", + "title": "Changelog", + "description": "Updated Flutter SDK documentation", + "category": "Flutter SDK", + "url": "/docs/flutter/changelog", + "date": "2026-02-23T17:39:35.000Z", + "changeType": "modified" + }, + { + "key": "content/docs/flutter/index.mdx:8bb42154b95e4a3c3a91c023416e8149263c208a", + "path": "flutter/index.mdx", + "title": "Welcome", + "description": "Updated Flutter SDK documentation", + "category": "Flutter SDK", + "url": "/docs/flutter", + "date": "2026-02-23T17:39:35.000Z", + "changeType": "modified" + }, + { + "key": "content/docs/flutter/sdk-reference/index.mdx:8bb42154b95e4a3c3a91c023416e8149263c208a", + "path": "flutter/sdk-reference/index.mdx", + "title": "Overview", + "description": "Updated sdk reference for Flutter SDK", + "category": "Flutter SDK", + "subcategory": "SDK Reference", + "url": "/docs/flutter/sdk-reference", + "date": "2026-02-23T17:39:35.000Z", + "changeType": "modified" + }, + { + "key": "content/docs/android/changelog.mdx:90c26e89d5b4eea20fc2483b063fac3359d3a666", + "path": "android/changelog.mdx", + "title": "Changelog", + "description": "Updated Android SDK documentation", + "category": "Android SDK", + "url": "/docs/android/changelog", + "date": "2026-02-21T00:00:02.000Z", + "changeType": "modified" + }, + { + "key": "content/docs/android/index.mdx:90c26e89d5b4eea20fc2483b063fac3359d3a666", + "path": "android/index.mdx", + "title": "Welcome", + "description": "Updated Android SDK documentation", + "category": "Android SDK", + "url": "/docs/android", + "date": "2026-02-21T00:00:02.000Z", + "changeType": "modified" + }, + { + "key": "content/docs/android/quickstart/install.mdx:90c26e89d5b4eea20fc2483b063fac3359d3a666", + "path": "android/quickstart/install.mdx", + "title": "Install the SDK", + "description": "Updated quickstart for Android SDK", + "category": "Android SDK", + "subcategory": "Quickstart", + "url": "/docs/android/quickstart/install", + "date": "2026-02-21T00:00:02.000Z", + "changeType": "modified" + }, + { + "key": "content/docs/android/sdk-reference/index.mdx:90c26e89d5b4eea20fc2483b063fac3359d3a666", + "path": "android/sdk-reference/index.mdx", + "title": "Overview", + "description": "Updated sdk reference for Android SDK", + "category": "Android SDK", + "subcategory": "SDK Reference", + "url": "/docs/android/sdk-reference", + "date": "2026-02-21T00:00:02.000Z", + "changeType": "modified" + }, + { + "key": "content/docs/dashboard/dashboard-settings/overview-settings.mdx:f67a22d4c7806bcff37fc771c678e444e6102c56", + "path": "dashboard/dashboard-settings/overview-settings.mdx", + "title": "General", + "description": "Updated settings for Dashboard", + "category": "Dashboard", + "subcategory": "Settings", + "url": "/docs/dashboard/dashboard-settings/overview-settings", + "date": "2026-02-19T23:32:36.000Z", + "changeType": "modified" + }, + { + "key": "content/docs/web-checkout/web-checkout-sdk-setup.mdx:2488c662531b501f7b4f160e969038e51aa2b0ba", + "path": "web-checkout/web-checkout-sdk-setup.mdx", + "title": "SDK Setup", + "description": "New Web Checkout documentation", + "category": "Web Checkout", + "url": "/docs/web-checkout/web-checkout-sdk-setup", + "date": "2026-02-19T19:09:31.000Z", + "changeType": "added" + }, + { + "key": "content/docs/ios/changelog.mdx:ef47cc98d95c72f4b99ccd71144eb35c5c25d12f", + "path": "ios/changelog.mdx", + "title": "Changelog", + "description": "Updated iOS SDK documentation", + "category": "iOS SDK", + "url": "/docs/ios/changelog", + "date": "2026-02-19T01:27:18.000Z", + "changeType": "modified" + }, + { + "key": "content/docs/ios/index.mdx:ef47cc98d95c72f4b99ccd71144eb35c5c25d12f", + "path": "ios/index.mdx", + "title": "Welcome", + "description": "Updated iOS SDK documentation", + "category": "iOS SDK", + "url": "/docs/ios", + "date": "2026-02-19T01:27:18.000Z", + "changeType": "modified" + }, + { + "key": "content/docs/ios/sdk-reference/index.mdx:ef47cc98d95c72f4b99ccd71144eb35c5c25d12f", + "path": "ios/sdk-reference/index.mdx", + "title": "Overview", + "description": "Updated sdk reference for iOS SDK", + "category": "iOS SDK", + "subcategory": "SDK Reference", + "url": "/docs/ios/sdk-reference", + "date": "2026-02-19T01:27:18.000Z", + "changeType": "modified" + }, + { + "key": "content/docs/expo/changelog.mdx:69b5351a6bb335ee8f92f9f12cfc17b02db9dc92", + "path": "expo/changelog.mdx", + "title": "Changelog", + "description": "Updated Expo SDK documentation", + "category": "Expo SDK", + "url": "/docs/expo/changelog", + "date": "2026-02-19T01:26:44.000Z", + "changeType": "modified" + }, + { + "key": "content/docs/expo/index.mdx:69b5351a6bb335ee8f92f9f12cfc17b02db9dc92", + "path": "expo/index.mdx", + "title": "Welcome", + "description": "Updated Expo SDK documentation", + "category": "Expo SDK", + "url": "/docs/expo", + "date": "2026-02-19T01:26:44.000Z", + "changeType": "modified" + }, + { + "key": "content/docs/expo/sdk-reference/index.mdx:69b5351a6bb335ee8f92f9f12cfc17b02db9dc92", + "path": "expo/sdk-reference/index.mdx", + "title": "Overview", + "description": "Updated sdk reference for Expo SDK", + "category": "Expo SDK", + "subcategory": "SDK Reference", + "url": "/docs/expo/sdk-reference", + "date": "2026-02-19T01:26:44.000Z", + "changeType": "modified" + }, + { + "key": "content/docs/flutter/sdk-reference/PaywallPresentationHandler.mdx:1cdac17265b4e8d6390a554fb62020c77c7989dd", + "path": "flutter/sdk-reference/PaywallPresentationHandler.mdx", + "title": "PaywallPresentationHandler", + "description": "Updated sdk reference for Flutter SDK", + "category": "Flutter SDK", + "subcategory": "SDK Reference", + "url": "/docs/flutter/sdk-reference/PaywallPresentationHandler", + "date": "2026-02-19T01:24:55.000Z", + "changeType": "modified" + }, { "key": "content/docs/ios/guides/test-mode.mdx:ab65878499ce2ae1e2bd0afc2be7b48c3b358e46", "path": "ios/guides/test-mode.mdx",