Adding Gamification to a Personal Dashboard, Because Data Alone Doesn't Change Behavior

Adding Gamification to a Personal Dashboard, Because Data Alone Doesn't Change Behavior

Most personal dashboards are passive. You look at charts, nod thoughtfully, and close the tab. After 19 phases of building Life Hub, I had comprehensive tracking for mood, sleep, habits, goals, journal entries, and even an AI council of advisors analyzing my patterns. But something was off.

Bramwell Finch, the devil’s advocate advisor I built specifically to challenge comfortable narratives, surfaced it bluntly:

“Your Learn Kubernetes goal is parked at 50% with zero journal entries in 7 days. Are we tracking goals or collecting them?”

He was right. Three structural problems had been hiding in plain sight:

  1. Goals were invisible daily. The daily digest showed mood, sleep, and habits, but goals weren’t mentioned. Out of sight, out of mind.
  2. No connection between habits and goals. Daily habits existed in isolation. There was no way to see that “study 30 minutes” was connected to “Pass CISSP”.
  3. No reward loop. Nothing acknowledged consistency. Log a 30-day meditation streak and the system treats it exactly like day one.

So I shipped four phases in a single session: foundation fixes, goal-habit linking, milestones, and a full gamification layer.

Phase 1: Foundation Fixes

Before building anything new, I had to fix what was broken.

GoalProgressLog, the table that tracks goal progress changes over time and feeds the forecasting engine, was only being written from one of three interfaces. The REST API logged progress correctly, but the HTMX UI and MCP tools both silently skipped it. Every goal update from the dashboard or through Claude was invisible to the forecasting system.

I also added goals to the daily digest (the MCP tool that gives Claude my day-at-a-glance), made goals fully editable from the HTMX UI (name, target date, notes, status), and ensured all three interfaces behaved identically. Boring work, but the kind that compounds.

Phase 2: Goal-Habit Linking

The idea is simple: connect daily habits to long-term goals so you can see the relationship.

goal_habit_links
├── goal_id  → FK to goals
├── habit_name → string (not FK — habits are inferred from log entries)
└── UNIQUE(goal_id, habit_name)

A deliberate design choice: these are informational links only. Completing a linked habit does not auto-increment goal progress. I considered it, but the math gets weird fast: does one meditation session equal 1% of “Build Mindfulness Practice”? The link exists to surface context, not to pretend a complex goal can be reduced to a habit counter.

In the UI, linked habits appear as chips inside each goal card. In the MCP layer, Claude can see which habits feed which goals when generating daily briefings. Small touch, surprisingly useful.

Phase 3: Milestones

Goals like “Pass CISSP” or “Migrate homelab to Kubernetes” aren’t single actions. They need sub-steps.

goal_milestones
├── id, goal_id, title, sort_order
├── completed (bool), completed_at
└── renders as a checklist inside the goal card

Each goal now has a progress_mode: either manual (you set the percentage yourself) or milestones (progress auto-calculates as completed/total). When you check off a milestone in milestones mode, the system recalculates progress and writes a GoalProgressLog entry, which means the forecasting engine from Phase 18 immediately gets fresh data to project completion dates.

This was the feature I didn’t know I needed. Manually estimating “I’m 60% done with CISSP prep” was always a guess. Breaking it into 8 domain milestones and checking them off as I complete practice exams? That’s measurable.

Phase 4: Gamification

This is the speculative bet. Everything above is structural improvement that pays off regardless. Gamification is the experiment: does adding XP, levels, and achievements actually change behavior?

XP System

Every meaningful action earns experience points:

XP_VALUES = {
    "habit_completed": 10,
    "all_habits_completed": 25,   # daily bonus
    "mood_logged": 5,
    "sleep_logged": 5,
    "journal_entry": 20,
    "milestone_completed": 30,
    "goal_completed": 200,
}

XP accumulates into seven levels:

LevelNameXP Required
1Newcomer0
2Apprentice100
3Practitioner500
4Disciplined1,500
5Architect3,000
6Master Architect5,000
7Life Engineer10,000

The grant_xp() function handles deduplication: it checks for an existing XPEvent with the same action, source ID, and date before inserting. This prevents double-awarding if a handler fires twice, which matters more than you’d think in an async web app with three interfaces.

Achievements

Fifteen-plus achievements across five categories, from “First Summit” (complete your first goal) to “XP Legend” (reach 10,000 XP). The interesting ones are the dynamic per-habit streak achievements: they’re generated programmatically for every habit you track:

  • Week Warrior: 7-day streak
  • Fortnight Force: 14-day streak
  • Monthly Master: 30-day streak
  • Two-Month Titan: 60-day streak
  • Quarter Champion: 90-day streak

So if you track meditation and exercise, you get separate achievement tracks for each. The slug format is streak-7-meditation, streak-30-exercise, etc.

When you earn an achievement, a gold-styled toast notification slides in with the achievement name and XP bonus. Multiple achievements stack with staggered timing so they don’t overlap.

The Architecture

Four new tables, one new column, two new files:

  • app/gamification.py: XP ledger operations, level computation, award_xp() (session-aware) and grant_xp() (standalone, opens its own session)
  • app/achievement_definitions.py: Achievement catalog as dataclasses, check_achievements() scanner that evaluates all definitions against current state

The pattern that emerged: every HTMX route handler that creates a meaningful event calls grant_xp() at the end, which returns any newly earned achievements. Those get injected into the HX-Trigger response header as an achievementUnlocked event. Client-side Alpine.js picks it up and renders the toasts.

# In htmx_routes.py — after any XP-worthy action
achievements = await grant_xp("habit_completed", habit_name, session)
if achievements:
    triggers["achievementUnlocked"] = [
        {"name": a.name, "xp": a.xp_value} for a in achievements
    ]

The MCP layer got two new tools: get_xp_summary (current XP, level, progress to next) and get_achievements (earned and available). Claude can now reference your level and recent achievements in daily briefings.

By the Numbers

MetricCount
New tables4
Total ORM models20
MCP tools44
UI pages15
Tests414 across 36 files
Data collectors9

Honest Assessment

It’s too early to say whether gamification changes behavior. The XP numbers feel good today (that first achievement toast triggered a genuine dopamine hit), but novelty effects are real and well-documented. Ask me in three months whether I’m still motivated by leveling up or whether I stopped noticing.

What I can say: the structural improvements are already paying off regardless. Seeing goals in my daily digest means I actually think about them. Milestones turned vague aspirations into concrete checklists. Goal-habit links make the connection between daily actions and long-term outcomes visible. Even if I ripped out the entire XP system tomorrow, those three changes would justify the work.

The gamification layer is a bet that acknowledgment matters: that the difference between a 30-day streak and day one shouldn’t be invisible. If it works, it’ll be because it made consistency legible, not because internet points are inherently motivating. We’ll see.


Life Hub is open source at github.com/stevennaviaux/life-hub. Previous posts: initial build, MCP data warehouse, council of advisors, v2.0 intelligence platform.