Feature spec
IntermediateCategory
personalization-ux
Build with AI
4–6 hours with Lovable or FlutterFlow
Custom build
1–2 weeks custom dev
Running cost
$0/mo up to ~1K users · $25–50/mo at 10K users
Works on
Everything it takes to ship Personalized Daily Routines — parts, prompts, and real costs.
A personalized daily routines feature needs three things: a drag-and-drop routine builder backed by a Supabase table, a streak engine that counts consecutive days in the user's local timezone, and push notification reminders at a scheduled time. With Lovable or FlutterFlow you can ship the core in 4–6 hours for $0/month on the free tier. Costs appear only at 10K+ users when Supabase Pro ($25/mo) is needed.
What a Personalized Daily Routines Feature Actually Is
A daily routines feature lets users build their own sequence of habits — morning workouts, reading sessions, medication reminders, journaling — and tracks whether they complete each one every day. The key product decisions are how streaks work (UTC vs local timezone is a known pain point), how completion is logged (once per day per routine or multiple times), and what happens when a routine is missed. The drag-and-drop builder, streak counter, and weekly heatmap are the three UI components users compare between apps. The reminder scheduling is what makes the feature sticky: a push notification at the right time pulls users back in. AI tools handle the CRUD and visualization reliably; the streak timezone logic and server-side notifications are where builds break.
What users consider table stakes in 2026
- Drag-and-drop routine ordering that persists immediately — no Save button required
- Streak counter showing the exact number of consecutive completed days, reset at midnight in the user's local timezone
- Satisfying completion micro-animation on the checkbox (scale + color transition, at minimum)
- Push notification reminder at the user-set time, delivered even when the app is closed
- Weekly overview heatmap or bar chart showing completion rate per routine
- Per-routine time allocation so users can plan blocks in their schedule
Anatomy of the Feature
Seven components. The CRUD and chart layers are what AI tools generate first; the streak engine and notification scheduler are where the complexity lives and where builds most commonly need a second pass.
Routine Builder UI
UIDrag-and-drop list built with @dnd-kit/sortable on web or ReorderableListView in Flutter. Each item has a name field, duration picker (minutes), an icon picker from a predefined set, and a reminder time selector. The sort_order column in the database persists the user's chosen sequence.
Note: After drag-end, batch-upsert all affected rows in one Supabase call to avoid race conditions if the user reorders quickly.
Streak Engine
BackendA Supabase Edge Function or Server Action that computes the streak by querying routine_logs for consecutive calendar days in the user's local timezone. The result is cached in the routines.current_streak and routines.longest_streak columns so the UI reads a single integer rather than re-aggregating logs on every load.
Note: Do not calculate streaks in UTC. A user who completes their routine at 11pm local time may be in the next UTC day, which breaks naive date comparisons. Use AT TIME ZONE in your PostgreSQL query or Luxon in the Edge Function.
Daily Completion Grid
UIA calendar heatmap showing completion intensity per day. On web: react-calendar-heatmap library using data aggregated by date from routine_logs. On Flutter: table_calendar with a custom cell builder. Aggregation groups logs by the user's local date, not UTC.
Note: react-calendar-heatmap expects date strings in YYYY-MM-DD format. Normalize completed_at to the user's timezone before grouping — mismatched timezones produce empty cells on days that actually had completions.
Reminder Scheduler
ServiceOn web: a pg_cron job triggers a Supabase Edge Function at the correct UTC time per user; the Edge Function sends a Web Push notification via the web-push npm package. On Flutter: flutter_local_notifications schedules an on-device alarm tied to the user's local time, removing the server dependency entirely. The scheduled_time is stored in the routines table as a time column; timezone is pulled from the user's profile.
Note: Do not use JavaScript setTimeout for reminders — it is destroyed when the browser tab closes. A Supabase Edge Function cron job is the correct web approach.
Routines Table
DataThe primary Supabase table holding each user's routines: name, icon, sort_order, duration_minutes, reminder_time (time type), is_active flag, and cached streak values. RLS restricts all operations to the row owner via auth.uid() = user_id.
Note: Caching current_streak in the table trades some accuracy for query speed. Recompute nightly via Edge Function cron rather than on every page load.
Routine Log Table
DataRecords every completion event: routine_id, user_id, completed_at timestamptz, duration_actual in minutes, and optional notes. A composite index on (user_id, routine_id, completed_at) keeps history queries fast even when users accumulate thousands of entries.
Progress Summary Widget
UIA weekly/monthly completion percentage displayed as a bar chart (Recharts on web, fl_chart on Flutter). Shows per-routine completion rates so users can identify which habits they struggle with. Clicking a routine in the chart drills down to its individual log history.
Note: Keep the chart aggregation server-side via a Supabase RPC function rather than pulling all logs to the client and aggregating in JavaScript.
The data model
Two tables with RLS locked to the authenticated user. Run this in the Supabase SQL editor — it creates the schema, enables RLS, and adds the performance indexes in one pass.
1create table public.routines (2 id uuid primary key default gen_random_uuid(),3 user_id uuid references auth.users(id) on delete cascade not null,4 name text not null,5 icon text,6 sort_order int not null default 0,7 duration_minutes int not null default 5,8 reminder_time time,9 is_active boolean not null default true,10 current_streak int not null default 0,11 longest_streak int not null default 0,12 created_at timestamptz not null default now()13);1415alter table public.routines enable row level security;1617create policy "Users can manage own routines"18 on public.routines19 using (auth.uid() = user_id)20 with check (auth.uid() = user_id);2122create table public.routine_logs (23 id uuid primary key default gen_random_uuid(),24 user_id uuid references auth.users(id) on delete cascade not null,25 routine_id uuid references public.routines(id) on delete cascade not null,26 completed_at timestamptz not null default now(),27 duration_actual int,28 notes text29);3031alter table public.routine_logs enable row level security;3233create policy "Users can manage own routine logs"34 on public.routine_logs35 using (auth.uid() = user_id)36 with check (auth.uid() = user_id);3738create index routine_logs_user_routine_date_idx39 on public.routine_logs (user_id, routine_id, completed_at desc);4041create index routine_logs_user_date_idx42 on public.routine_logs (user_id, completed_at desc);Heads up: The second index on (user_id, completed_at) powers the weekly heatmap query that groups completions by date across all routines. The first index powers per-routine streak calculation.
Build it — pick your path
Each tool fits this feature differently. Switch tabs to compare fit, time, the exact steps, and a copy-paste prompt.
The strongest path for a mobile-first routines app: FlutterFlow's Supabase queries handle CRUD natively, flutter_local_notifications gives reliable on-device alarms, and table_calendar renders the completion heatmap without custom code.
Step by step
- 1Add the routines and routine_logs tables in Supabase and paste the SQL schema from this page into the Supabase SQL editor
- 2In FlutterFlow, connect Supabase under Settings → Supabase and import both tables; FlutterFlow auto-generates the query types
- 3Build the RoutineList page: add a ListView backed by a Supabase query on routines filtered to the logged-in user; wrap each row in ReorderableListView and add an on-reorder action that updates sort_order via a Supabase batch update custom action
- 4For the completion checkbox: add an Action to the CheckBox widget that queries routine_logs for today's date in the user's timezone, inserts a new log row if none exists, then triggers a Rebuild of the streak display widget
- 5Set up flutter_local_notifications as a custom action: schedule a notification at reminder_time for each active routine; call this custom action on app launch and whenever a routine's reminder_time changes; add the iOS permission request in Settings → Permissions with a usage message explaining why reminders are needed
Where this path bites
- Timezone-correct streak calculation (grouping logs by local date, not UTC) requires a Supabase RPC function or custom Dart action — FlutterFlow's visual query builder compares timestamps directly in UTC
- The completion heatmap via table_calendar with a custom cell builder requires writing custom Dart code — it is not available as a built-in FlutterFlow widget
- Drag-and-drop reorder with persistent sort_order requires a custom Dart action for the batch Supabase upsert; the visual Action editor does not support looping over a list of updated rows
Third-party services you'll need
This feature is free at small scale. The only paid dependency is Supabase Pro when your routine_logs table outgrows the free tier:
| Service | What it does | Free tier | Paid from |
|---|---|---|---|
| Supabase | Tables, RLS, Auth, and Edge Functions for streak recalculation and push scheduling | Free tier: 2 projects, 500MB DB, 50K MAU — covers most early-stage apps | $25/mo (Pro) — 8GB DB, no MAU cap for most plans |
| web-push (npm) | Web Push API notifications from the Supabase Edge Function; sends reminders to browsers that have granted notification permission | Open-source, zero cost | Free |
| Firebase Cloud Messaging | Server-side push notifications for Flutter (Android and iOS); alternative to flutter_local_notifications when notifications must fire from a server event | Generous free tier; covers the vast majority of apps | $0 for most use cases (Spark plan) |
| flutter_local_notifications | On-device alarm scheduling for Flutter apps; no server required — notifications fire from the device itself at the scheduled time | Open-source, zero cost | Free |
Swipe the table sideways to see pricing.
What it costs to run
Drag through the tiers to see how your monthly bill scales with users — no surprises later.
Estimated monthly running cost
Supabase free tier covers storage and auth at this scale. flutter_local_notifications and web-push are free. FCM has no cost at low volume. Streak Edge Function compute is negligible.
Estimates use verified 2026 pricing and assume typical usage per user. Your real bill depends on activity, storage, and third-party plans.
What breaks when AI tools build this
The failures people actually hit on their first build — the symptom, why it happens, and the exact fix.
Streak shows 0 even though the user completed the routine yesterday
Symptom: The streak calculation compares completed_at dates in UTC. A user who finishes their routine at 11pm in New York (UTC-5) generates a log timestamped 4am the next UTC day. The consecutive-day check sees a gap where there is none, and resets the streak to 0. This is the most common streak bug — and the one users are most likely to complain about.
Fix: Convert completed_at to the user's local timezone before grouping by date. In PostgreSQL: SELECT completed_at AT TIME ZONE 'America/New_York' AS local_date. Store the user's IANA timezone string (from Intl.DateTimeFormat().resolvedOptions().timeZone on the client) in the profiles table. In the Edge Function use Luxon or date-fns-tz to perform all date math in the user's timezone.
Drag-and-drop reorder works visually but reverts on page refresh
Symptom: AI tools frequently generate the @dnd-kit optimistic UI update (which instantly shows the new order on screen) but omit the Supabase batch UPDATE that persists sort_order. The in-memory state looks correct but the database still has the old order, so a refresh reverts the list.
Fix: After drag-end, call Supabase with an array of { id, sort_order } pairs using upsert: await supabase.from('routines').upsert(reorderedItems.map((r, i) => ({ id: r.id, sort_order: i }))); Verify persistence by reordering, refreshing, and confirming the new order holds.
Push notification reminder fires at 9am UTC, not 9am local time
Symptom: pg_cron schedules in UTC. If the Edge Function queries routines where reminder_time = current_time without timezone conversion, it fires for users whose UTC wall clock matches, regardless of local time. A user in Tokyo who sets a 7am reminder gets it at 7am UTC (4pm Tokyo), hours late.
Fix: Store the user's timezone in the profiles table. In the pg_cron trigger, group users by their UTC offset and schedule separate Edge Function calls per timezone group — or have a single Edge Function that runs every minute, filters routines where the local time matches reminder_time for each user's timezone, and sends only the matching notifications.
Lovable generates setTimeout for reminders instead of a cron job
Symptom: Without explicit instruction, Lovable often implements notification reminders using a client-side setTimeout. This appears to work in the preview but stops firing the moment the browser tab is closed, backgrounded, or the device sleeps — which defeats the entire purpose of a routine reminder.
Fix: In your Lovable prompt, explicitly specify: 'Do not use setTimeout for notification reminders. Use a Supabase Edge Function triggered by pg_cron to send Web Push notifications server-side.' The Edge Function approach works regardless of whether the user's browser is open.
Completion heatmap shows empty cells on days that had completions
Symptom: react-calendar-heatmap expects values in the format { date: 'YYYY-MM-DD', count: N }. If completed_at timestamps are grouped in UTC before converting to local dates, a completion at 11pm local time on Monday appears as a Tuesday UTC date, creating an empty cell for Monday and an inflated count for Tuesday.
Fix: Normalize all completed_at values to the user's local timezone before grouping by date: use date-fns-tz format(toZonedTime(completed_at, userTimezone), 'yyyy-MM-dd') for each log entry before aggregating. Do this server-side in the Supabase RPC function or Edge Function, not in the React component.
Best practices
Store the user's IANA timezone string in the profiles table at signup so all streak and heatmap calculations run in the correct timezone from day one
Batch-upsert sort_order after drag-and-drop in a single Supabase call — individual UPDATE calls per row create N round trips and visible lag on slow connections
Cache current_streak in the routines table and recompute it nightly via Edge Function cron, not on every page load — streak recalculation over thousands of log entries is expensive
Log both completed_at (UTC) and duration_actual (minutes) in routine_logs — duration data enables 'time spent per habit' insights that significantly increase long-term engagement
Use flutter_local_notifications for on-device alarms in Flutter apps rather than server-push for reminders — on-device alarms fire reliably even without an internet connection and require no server infrastructure
Prevent double-logging by checking whether a routine_log already exists for today's local date before inserting; return a clear 'already logged today' state rather than silently discarding the duplicate
Show the longest_streak alongside current_streak on each routine card — users are more motivated by their personal best than today's count alone
Test the streak logic specifically at midnight boundary conditions by temporarily setting a test user's timezone to one that is 12+ hours ahead of UTC and verifying logs on day boundaries are attributed correctly
When You Need Custom Development
AI tools handle solo habit tracking with Supabase reliably. These patterns push past what a prompted build can sustain:
- AI habit coach: the app should analyze past completion rates and generate personalized routine suggestions using an LLM, with feedback loops that refine recommendations over time
- Wearable device sync: routines like 'Morning walk' should auto-complete when Apple Health or Google Fit confirms the activity — this requires native HealthKit/Fit SDK access not available in web apps
- Team or family routines: multiple users share a routine and see each other's streaks and completion status, requiring a routines_members join table, per-member RLS policies, and real-time updates
- Enterprise wellness platform: admin-defined routine libraries, compliance dashboards showing completion rates across an organization, and integration with HRIS systems for employee wellness reporting
RapidDev builds these features for production
Real auth, database, and payments — fixed price, delivered in weeks. You own 100% of the code.
Frequently asked questions
Can routines repeat on specific days of the week rather than daily?
Yes — add a repeat_days integer array column to the routines table (where 0 = Sunday, 1 = Monday, etc.). Filter which routines show on a given day by checking if today's day number is in the array. The streak logic needs to account for scheduled days only, so a Monday-only routine that was completed last Monday has a streak of 1 regardless of what happened Tuesday through Sunday.
How do I prevent the streak counter from breaking due to timezone differences?
Store the user's IANA timezone string (from Intl.DateTimeFormat().resolvedOptions().timeZone in the browser) in your profiles table at signup. Always convert completed_at from UTC to the user's local timezone before grouping by date. In PostgreSQL: SELECT (completed_at AT TIME ZONE user_timezone)::date as local_date. Running streak comparisons in UTC is the single most common cause of streak resets that shouldn't happen.
What happens if I miss a day — can I mark a backdated completion?
You can allow it by accepting a completed_at parameter in the log insert instead of defaulting to now(). Show a date picker in the completion flow with a note like 'Logging a past completion.' Decide upfront whether backdated completions count for streak restoration — many habit apps deliberately block backdating to maintain streak integrity, while others allow it with a visual indicator marking the entry as manually logged.
Can routines be grouped into different areas like morning, evening, or fitness?
Add a category text column to the routines table and a category filter in the UI. Display routines in collapsible sections by category on the main screen. Categories also enable category-level completion stats (how often does the user finish all morning routines?) which are a useful insight to surface in the weekly summary.
How do I export routine data?
The simplest path: a Server Action that queries routine_logs for a date range, formats it as CSV with columns (routine_name, completed_at, duration_actual, notes), and returns it as a file download. For Supabase apps, you can also expose a read-only API route that returns the data as JSON for users who want to import it into spreadsheets or other tools.
Can the app integrate with Apple Health or Google Fit?
Only in native mobile apps — HealthKit requires an iOS native build (React Native, Swift, or Flutter with the health package), and Google Fit requires Android-native access. Web apps including PWAs cannot read step count, sleep data, or workout data from these platforms. If wearable sync is core to your feature, plan for a Flutter or React Native build from the start.
How do I add an AI coach that suggests routines?
On the back end, collect the user's stated goals and their past completion rates per routine. Pass this context to an LLM API call (Claude or GPT-4o) with a prompt that asks for 3–5 SMART routine suggestions with realistic durations. Return the suggestions as a pre-filled 'Add Routine' flow the user can accept, edit, or dismiss. Cache the suggestions so you aren't calling the LLM API on every visit.
Can users share their routine with another user or make it public?
Add an is_public boolean column to routines. Update the Supabase RLS SELECT policy to allow reads when is_public = true, in addition to the existing owner-only access. Add a shareable link like /routines/{routine_id} that fetches the public routine data without requiring login. Other users can then import the routine to their own account by inserting a copy into their own routines table.
Need this feature production-ready?
RapidDev builds personalized daily routines into real apps — auth, database, payments — at $13K–$25K.
Book a free consultation30-min call. No commitment.