xeve started as a weekend project to track my app usage. Eighteen months later, it is a full personal analytics platform with a native macOS app, iOS companion app, web dashboard, VS Code extension, and Claude Code integration. Here is how the architecture evolved and what I learned.
Why Supabase Over Firebase
The backend decision came down to two options: Firebase and Supabase. I chose Supabase for three reasons:
- PostgreSQL: Real SQL with real queries. Personal analytics requires complex aggregations — hourly activity breakdowns, category groupings, time-series correlations. PostgreSQL handles this natively. Firestore would have required denormalization gymnastics.
- Row-Level Security: Every table has RLS policies that enforce data isolation. Users can only read their own data, period. This is baked into the database layer, not the application layer.
- Edge Functions: Deno-based serverless functions for integrations. The Spotify polling function, GitHub sync, daily rollup, and AI insights all run as edge functions. No separate backend to deploy.
RPC Functions for Aggregations
One architectural pattern that worked well: using PostgreSQL RPC functions for all dashboard queries. Instead of fetching raw rows and aggregating in JavaScript, the database does the heavy lifting. Functions like get_app_usage_summary, get_hourly_activity, and compute_correlation return pre-aggregated data. This keeps the web app fast and the data transfer minimal.
Why Native Swift Over Electron
The macOS tracker had to be a native app. There was no other option. Here is why:
- System APIs: App tracking uses
NSWorkspacenotifications and accessibility APIs. Electron cannot access these without native modules, which defeats the purpose. - Resource usage: A menu bar app that runs 24/7 must use minimal CPU and memory. Electron apps start at 100MB+ RAM. The Swift app uses under 20MB.
- BLE heart rate: CoreBluetooth integration for Whoop and Polar heart rate monitors. This requires native Bluetooth access.
- Sparkle updates: The app uses Sparkle for auto-updates with EdDSA signing. This is a mature, native-only solution.
Next.js App Router for the Dashboard
The web dashboard uses Next.js 15 with the App Router. Server components by default, client components only for charts (Recharts) and animations (Framer Motion). This keeps the initial page load fast and reduces client-side JavaScript.
The design system follows an industrial minimalist aesthetic: monospace typography, dark backgrounds, orange accents, sharp corners. Every component uses the same design tokens defined in CSS custom properties.
Date Handling
One surprising challenge was timezone-safe date handling. Users in different timezones need their "today" to align with their local day, not UTC. The solution is a getDateRange utility that adds a 12-hour buffer to date boundaries, ensuring queries capture the full local day regardless of timezone offset.
iOS Companion App
The iOS app adds HealthKit data (steps, energy, sleep, heart rate), location tracking (home/work detection), and widgets. Key learnings:
- HealthKit queries are async and slow. Batch queries for multiple data types and cache aggressively with SwiftData.
- Nested ObservableObjects do not propagate changes. You need Combine's
.objectWillChange.sinkin the parent to forward child updates. - App Groups are required for sharing data between the main app and widget extensions. SharedData uses a JSON file in the shared container.
- Background location updates require careful power management. The app uses significant location changes, not continuous GPS.
The Correlation Engine
The correlation engine computes Pearson correlation coefficients across 19 metric pairs automatically. It runs as part of the daily rollup edge function, pulling from daily summaries. Each correlation gets a plain-English interpretation and a statistical significance flag.
The hardest part was choosing which metric pairs to correlate. Too many pairs and you get noise. Too few and you miss insights. The current 19 pairs cover the most actionable relationships: sleep vs. productivity, exercise vs. focus, music vs. coding, and more.
Lessons Learned
- Start with the tracker, not the dashboard. Without data, the dashboard is empty and useless. Ship the tracker first, collect data, then build visualizations around what you actually have.
- RLS is worth the upfront cost. Debugging RLS policies is painful, but the security guarantee is invaluable. You never have to worry about data leaks.
- Edge functions have cold starts. The first request after idle takes 1-2 seconds. For background jobs like daily rollup, this is fine. For user-facing API calls, it is noticeable.
- Ship weekly. As a solo developer, momentum matters more than perfection. Every week should have a visible improvement.
What is Next
The roadmap includes Whoop OAuth integration for recovery and strain data, Stripe billing for a Pro tier, and interactive iOS widgets. The architecture is solid enough to support these additions without major refactoring — which is the whole point of getting the foundation right.
If you want to try xeve, it is free during early access. Sign up at xeve.io/signup.