Case study · 01

Nirvix

Build your character: an RPG-style life app where every finished quest levels up one of six character stats. Local-first, on Linux and Android.

Role
Solo: product, design, build
Stack
Flutter, Dart, SQLite (drift), Riverpod
Timeline
Since October 2026, ongoing
Code
Private repo, demo on request
Nirvix on desktop: character sheet with a radar chart of six stats, current level and the Nix mascot
Nirvix on a phone: today's quests with a running focus timer and next step

01 — Problem

Lists don't make me move

To-do apps never stuck for me, but game quests always did. Nirvix turns the day into quests: finishing one earns XP for a stat (sport, cooking, intelligence, social, active life, entertainment), and the character levels up through seven ranks.

The bar for "done" was set before any code: it has to be useful every day, work offline, and never punish. No red, no lost XP, no streak reset to zero; there is always a way out: later, split it, or let it go.

02 — Architecture

Rules in pure Dart

app/

Flutter UI

Rendering only: screens, themes, notifications.

packages/core

Rules

Pure Dart, no Flutter import: XP, ranks, quests, nutrition. Written test-first.

packages/storage

SQLite

drift with full-text search, an append-only operation log, daily backups, JSON and CSV export.

  • Level and XP are derived from an append-only event log, never stored twice.
  • Domain lists (sport, reading, watchlist, cooking…) are declared as data: one generic screen renders them all.
  • Sync (Syncthing) and AI suggestions are optional add-ons behind interfaces; the app is complete offline.

03 — Who did what

AI drafts, I decide

Claude Code

  • Wrote most of the Dart code from my specs, test-first on the rules package.
  • Generated the widget tests and the screenshot goldens I review after every visual change.
  • Drafted the docs: architecture, game design and the iteration log.

Me

  • Set the rules that never bend: local-first, business logic only in core, no guilt mechanics.
  • Cut Rust out of version 1 to keep one language and one toolchain.
  • Ran the user-test iterations and chose what stayed: daily priorities, steps, focus timer, quick add.

04 — Results

What came out

336
automated tests (rules, storage, widgets)
10
domain pages from one generic screen
2
platforms from one code base: Linux and Android

Keeping every rule in a package with no UI import keeps the tests fast and leaves the screens free to change. Next: the domain pages move from Dart declarations to JSON specs, so new pages can be added without new code.