Luma
Gamified productivity with an AI assistant that acts for you.
- Next.js 16
- React 19
- Tailwind 4
- TanStack Query
- Fastify 5
- Drizzle
- PostgreSQL
- Better Auth
- AI SDK
- Google Calendar API
Overview
Tasks, calendar, goals and a focus timer in one place, with XP, streaks and achievements to turn organization into a habit. The assistant doesn't just answer: it creates tasks, logs progress and plans your day.
Context
Productivity apps are easy to install and easy to abandon. Luma bets on two things: making consistency visible, and removing friction by letting an assistant do the busywork.
I built it as a real product, not a demo: landing page with SEO, onboarding, transactional email, an admin area, and a monorepo that runs locally without any external keys.
What I built
- An AI assistant with 25 tools that change real data: create and reschedule tasks, log goal progress, plan the day. Replies stream over SSE, with up to 5 tool steps per turn.
- A gamification engine driven by data: points, levels, streaks, achievements, an in-app currency with a ledger, a shop, avatars and weekly leaderboards.
- Per-user timezones: hourly jobs close streaks and leaderboards and send notifications at each user's local midnight, daylight saving included.
- Google Calendar sync over OAuth, with tasks dragged straight onto the calendar.
- A modular Fastify API: 17 modules with the same shape (routes, controller, repository, schema), Zod validation, rate limiting and a single error type.
Key decisions
01A separate API
I split the API from the Next.js front end so a future native app can consume the same API, and because a dedicated server is more robust for scheduled jobs and business rules.
02Game rules in the database
XP values, triggers and rewards live in the database so the admin is in control: anything can be changed straight from the admin panel, whenever needed, without a deploy.
03Keeping AI cost bounded
A sliding window of the last 20 messages, the user's daily context cached for 5 minutes, small models, token usage logged per call, and a rate limit on the chat route.
04One hourly job, converted per user
Instead of one scheduled job per timezone, a single hourly job checks which users just crossed midnight in their own timezone.
05Cutting scope on purpose
I built subscriptions, trials and credits, then removed them to focus on the core loop, and merged the app and the API into one public monorepo.
What was hard
Time. A streak means "you did something today", and today depends on where the user is. Getting streaks, leaderboards and reminders right across timezones and daylight saving changes took more care than any UI in the app.
Letting a model change user data. Every tool is a small, typed function with its own validation, so the assistant can only do what the app already allows, and each call is logged with its cost.