The Frankenstein Problem: When Your Side Project Grows Teeth

How a personal life dashboard grew from a mood tracker to a 55-page monster with 35 sidebar items, 240 MCP tools, and three different frontend patterns, and why that is both the problem and the proof it works.

The Frankenstein Problem: When Your Side Project Grows Teeth

I built Life Hub to track my mood. That was it: a single FastAPI endpoint that accepted a 1-10 score and a note. One model, one page, one purpose.

Fourteen months later, I have 55 pages, 51 database models, 240 MCP tools, 19 data collectors, a webhook system that fires events to an AI agent team running on Kubernetes, and a sidebar with 35 items organized into four groups that I have to scroll to see the bottom of.

I built a junk drawer, and it took Claude Code auditing every page with Playwright for me to finally admit it.

How We Got Here

The trajectory is predictable in hindsight. You build mood tracking, then you want to correlate it with sleep, so you add a sleep logger. Sleep quality depends on habits, so you add habit tracking. Habits need goals, so you build a goal system with milestones, progress bars, and dependency graphs. Goals should tie back to life pillars, so you invent an 8-pillar scoring engine with tier-weighted averaging, sigmoid normalization, and cross-pillar correlation boosts.

At no point did I sit down and design a product. I solved problems. Every page was the right answer to a specific question asked at a specific time. The journal page was built during a week I wanted to track my writing. The finance page appeared when I connected Actual Budget. The Liam page materialized the week my son was born and I needed to track feeds, diapers, and sleep in 3-hour increments while running on no sleep myself.

Each page is individually defensible. Collectively, they are chaos.

The Symptoms

35 Sidebar Items

The navigation has four groups: Track (15 items), Analyze (9 items), World (1 item), and System (10 items). When you open Life Hub on a phone, you cannot see the bottom of the sidebar. Conflicts gets its own nav section. It has exactly one page in it. Nobody would design this.

Three Frontend Patterns

Life Wheel uses Alpine.js with x-data components that fetch JSON from the API, render charts via Chart.js, and manage all state client-side. The Journal page uses pure HTMX: server-rendered partials swapped into the DOM. The Council page uses raw fetch() calls in vanilla JS. Some pages mix all three.

This means you cannot share component patterns across pages. A card on Life Wheel is an Alpine x-for template. A card on the Today page is a Jinja2 template partial. A card on Journal is an HTMX fragment returned from a POST endpoint. Same visual element, three completely different implementation strategies.

Wildly Uneven Depth

The Life Wheel page has sparklines, radar charts, baseline bands comparing your scores to your 30-day personal average, weather context on the Environment pillar, interest-based activity suggestions, linked goals with recurring progress indicators, target score sliders, and LLM-generated narrative explanations.

The Character pillar that Life Wheel scores has a single sub-score: Goal Velocity, sitting at 0.9 out of 10. The pillar is essentially a stub.

The Fitness page was a hardcoded workout program with static JSON data until this week, when it got HTMX quick-log forms, real completion tracking, and a workout streak counter. Its neighbor, the Health page, has biohacker metrics, Apple Health integration, and CDC vaccine schedules for my son. These pages live next to each other in the sidebar and feel like they were built by different teams in different decades.

What Actually Works

The data layer is excellent. 51 ORM models with clean relationships, upsert patterns, and consistent validation via VALID_* frozensets. The pillar scoring engine is genuinely sophisticated: geometric averaging for Health metrics, sigmoid normalization for diminishing returns, consistency modifiers, cross-pillar correlation boosts, and burnout risk detection from multi-pillar decline signals.

The MCP server exposes 240 tools over Streamable HTTP. My AI agents (an entire team of them running on Kubernetes via the OpenClaw platform) use these tools to read my mood, check my sleep, review my goals, log habits, and generate morning briefings. The data architecture supports use cases I had not imagined when I built it.

The webhook system fires events on trigger conditions (burnout risk change, streak at risk, pillar alert) to OpenClaw Mission Control, which classifies them by severity and routes them to Henry, my AI chief of staff, who decides what to tell me about via Telegram. This pipeline works exactly as designed.

The 30-day rolling baselines are useful. I recently added arrows next to sub-scores on the Life Wheel showing whether each metric is above, below, or within your personal normal range. The weather context on the Environment card shows current conditions and outdoor suitability. These are features that make data personally meaningful rather than generically informative.

The Honest Assessment

Life Hub has the data infrastructure of a serious product and the UI of a prototype graveyard. The backend is over-engineered in the best way: it can answer questions I have not thought to ask yet. The frontend is under-designed in the worst way: it answers every question simultaneously and expects you to find the right page.

The mistake was never stopping to ask: what should you see when you open this app? Instead, I kept asking: what else can I track?

The fix is not more features. It is fewer pages, a consistent design language, and opinionated navigation that tells you where to go instead of showing you everything at once.

That is what comes next.

This is part 1 of a 4-part series on evolving Life Hub from a collection of features into a cohesive product. Next up: the data plane / control plane architecture that actually worked.