diff --git a/CHANGELOG.md b/CHANGELOG.md
index b5e4273..6f0f103 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
### Improved
+- **More reliable list rendering across the dashboard.** Pages, referrers, locations, devices, funnels, and other data lists now use stable identifiers (like page paths and referrer URLs) instead of array positions as their React keys. This prevents potential display glitches — such as stale data appearing in the wrong row — when lists are filtered, sorted, or updated in real time.
+- **Deleting a site now fully cleans up all its data.** Previously, deleting a site removed the site record but could leave behind orphaned analytics events in the database, slowly accumulating unused data. Now all events are cleaned up automatically in small batches before the site is removed, keeping your database tidy.
+
- **Modern config loading for the app.** The app's configuration file now uses proper ES module imports instead of an older CommonJS pattern. This aligns with modern JavaScript standards and enables better tooling support.
- **Complete API documentation in the backend README.** The developer documentation now lists all 80+ endpoints organized by category — ingestion, public dashboard, sites, analytics, real-time, goals, funnels, uptime, billing, notifications, admin, and audit. Previously only 12 endpoints were documented, which understated the full scope of the system.
- **Login page now shows a loading screen while redirecting.** Previously, the login page briefly showed a blank white screen before redirecting you to the sign-in page. You'll now see the Pulse logo and a "Redirecting to log in..." message, matching the signup page experience.
diff --git a/app/faq/page.tsx b/app/faq/page.tsx
index f7f574b..51393f8 100644
--- a/app/faq/page.tsx
+++ b/app/faq/page.tsx
@@ -114,7 +114,7 @@ export default function FAQPage() {
{faqs.map((faq, index) => (
-
+
))}
diff --git a/app/page.tsx b/app/page.tsx
index 8ea1c77..7ec8850 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -78,8 +78,8 @@ function ComparisonSection() {
{ feature: "GDPR Compliant", pulse: true, ga: "Complex" },
{ feature: "Script Size", pulse: "< 1 KB", ga: "45 KB+" },
{ feature: "Data Ownership", pulse: "Yours", ga: "Google's" },
- ].map((row, i) => (
-
+ ].map((row) => (
+
| {row.feature} |
{row.pulse === true ? (
@@ -303,7 +303,7 @@ export default function HomePage() {
{ icon: ZapIcon, title: "Lightweight", desc: "Our script is less than 1kb. It won't slow down your site or affect your SEO." }
].map((feature, i) => (
{stats.steps.map((step, i) => (
-
+
|
diff --git a/app/sites/[id]/funnels/new/page.tsx b/app/sites/[id]/funnels/new/page.tsx
index 74f2fa0..ecf7b1f 100644
--- a/app/sites/[id]/funnels/new/page.tsx
+++ b/app/sites/[id]/funnels/new/page.tsx
@@ -149,7 +149,7 @@ export default function CreateFunnelPage() {
{steps.map((step, index) => (
-
+
diff --git a/app/sites/[id]/funnels/page.tsx b/app/sites/[id]/funnels/page.tsx
index a7b5726..2b565c4 100644
--- a/app/sites/[id]/funnels/page.tsx
+++ b/app/sites/[id]/funnels/page.tsx
@@ -117,7 +117,7 @@ export default function FunnelsPage() {
)}
{funnel.steps.map((step, i) => (
-
+
{step.name}
diff --git a/components/dashboard/ContentStats.tsx b/components/dashboard/ContentStats.tsx
index 7f2e956..d2eeea7 100644
--- a/components/dashboard/ContentStats.tsx
+++ b/components/dashboard/ContentStats.tsx
@@ -132,8 +132,8 @@ export default function ContentStats({ topPages, entryPages, exitPages, domain,
) : hasData ? (
<>
- {displayedData.map((page, index) => (
-
+ {displayedData.map((page) => (
+
) : (
- (fullData.length > 0 ? fullData : data).map((page, index) => (
-
+ (fullData.length > 0 ? fullData : data).map((page) => (
+
{activeTab === 'countries' && {getFlagComponent(item.country ?? '')}}
{activeTab !== 'countries' && {getFlagComponent(item.country ?? '')}}
@@ -296,8 +296,8 @@ export default function Locations({ countries, cities, regions, geoDataLevel = '
) : (
- (fullData.length > 0 ? fullData : data).map((item, index) => (
-
+ (fullData.length > 0 ? fullData : data).map((item) => (
+
{getFlagComponent(item.country ?? '')}
diff --git a/components/dashboard/TechSpecs.tsx b/components/dashboard/TechSpecs.tsx
index 8f3a82f..f4bd4cd 100644
--- a/components/dashboard/TechSpecs.tsx
+++ b/components/dashboard/TechSpecs.tsx
@@ -156,8 +156,8 @@ export default function TechSpecs({ browsers, os, devices, screenResolutions, co
) : hasData ? (
<>
- {displayedData.map((item, index) => (
-
+ {displayedData.map((item) => (
+
{item.icon && {item.icon}}
{item.name}
@@ -198,8 +198,8 @@ export default function TechSpecs({ browsers, os, devices, screenResolutions, co
) : (
- (fullData.length > 0 ? fullData : data).map((item, index) => (
-
+ (fullData.length > 0 ? fullData : data).map((item) => (
+
{item.icon && {item.icon}}
{item.name === 'Unknown' ? 'Unknown' : item.name}
diff --git a/components/dashboard/TopReferrers.tsx b/components/dashboard/TopReferrers.tsx
index 9e9fb1c..05cb903 100644
--- a/components/dashboard/TopReferrers.tsx
+++ b/components/dashboard/TopReferrers.tsx
@@ -102,8 +102,8 @@ export default function TopReferrers({ referrers, collectReferrers = true, siteI
) : hasData ? (
<>
- {displayedReferrers.map((ref, index) => (
-
+ {displayedReferrers.map((ref) => (
+
{renderReferrerIcon(ref.referrer)}
{getReferrerDisplayName(ref.referrer)}
@@ -144,8 +144,8 @@ export default function TopReferrers({ referrers, collectReferrers = true, siteI
) : (
- mergeReferrersByDisplayName(fullData.length > 0 ? fullData : filteredReferrers).map((ref, index) => (
-
+ mergeReferrersByDisplayName(fullData.length > 0 ? fullData : filteredReferrers).map((ref) => (
+
{renderReferrerIcon(ref.referrer)}
{getReferrerDisplayName(ref.referrer)}
| |