Skip to content
All work
Monolith app icon

MONOLITH·LOCAL-FIRST COMPETENCE TRACKER

Your work should leave a record, not a streak.

Monolith is a mobile competence tracker. Every focused session, completed task, or manual log deposits a layer into a block of stone. The grid filling up is the reward — no badges, no daily bonus, no “Good morning!” popup.

Role

Sole author

Stack

React Native · Expo SDK 57

Storage

AsyncStorage, JSON export

Status

Pre-release MVP, iOS verified

Grid — minutes today, the block of stone, 26 weeks of history.
Grid — minutes today, the block of stone, 26 weeks of history.
Focus — a count-up timer holds an unsaved draft layer.
Focus — a count-up timer holds an unsaved draft layer.
Profile — totals, level, and where the time actually went.
Profile — totals, level, and where the time actually went.
First run — choose an identity before the first session.
First run — choose an identity before the first session.
Recorded — the finished layer settles into the stack.
Recorded — the finished layer settles into the stack.
History — one day, its sessions, and a manual log.
History — one day, its sessions, and a manual log.
Identities — Code, Writing, and Movement, each with 30-day activity.
Identities — Code, Writing, and Movement, each with 30-day activity.
Dark appearance — the strata palette adapts.
Dark appearance — the strata palette adapts.

Screens

01 / 08

Architecture

How the system fits together

Focus session, task, or manual log → recorder → strata geometry → AsyncStorage activity store → grid, profile, and JSON export

The problem

Most productivity apps are engagement machines

Streaks, badges, confetti for opening the app, a dashboard that measures tapping instead of work. The metric a productivity app optimises for is retention, not competence.

Monolith optimises for something else. A focused session should leave a visible mark, and the mark should be proportional to the effort. So the app has no streak to protect and no celebration to fire — just a grid that fills, and a block of stone that grows a layer every time you do the work.

Competitive gap

What existing trackers get wrong

The three existing categories each fail in a way that is hard to patch around. Streaks punish rest. Dashboards measure the wrong thing. Cloud trackers hold your record hostage.

None of them are wrong about wanting engagement. They are wrong about what engagement is for.

ApproachWhere it fails
Streak trackersTurn a break into a loss. The punishment outlasts the motivation.
Engagement dashboardsMeasure taps, sessions, and check-ins — not what you actually built.
Cloud habit trackersYour history lives in someone else’s database until you cancel.
MonolithMeasures effort, stores it locally, and never asks for an account.

Data model

Strata: effort as geometry

The centre of the app is a block of stone. Each identity — Code, Writing, Movement — is its own block. Every recorded session or manual log deposits a layer whose thickness is derived from the effort it took: a 47-minute session is a thick band, a 3-minute log is a hairline.

Layers stack in time. Recent work stays distinct at the top; older work compresses into a base that still accounts for every minute. Tapping a slice opens the layer inspector, so the geometry is never decoration — you can always read it back.

DataStored asWhy
Sessions and logsActivity records in AsyncStorageOne source of truth for the grid, the strata, and the profile.
Layer geometryDerived from effort, never storedRecomputes when a record is edited or undone.
Totals and levelsComputed locallyNo background job, no clock to drift.
ExportA JSON file you keepYour history is a file, not a subscription.

The screens

The loop: focus, record, correct

Opening the app is not the point, so the Grid leads: minutes today against your goal, the block of stone, 26 weeks of history, identity filters, and today’s tasks. Start focus from there.

The Focus screen is a count-up timer. It keeps the screen awake, survives backgrounding, and drafts an unsaved layer into the stone as it goes, so you can see what you are about to bank. Finishing records the session exactly once; the layer then settles into the stack. History lets you inspect any day, edit a duration, change an identity, remove a record, or undo the whole thing — and every edit keeps the original task, date, and completion time.

ScreenWhat it does
GridMinutes today, the strata, 26 weeks, filters, and today’s tasks.
FocusCount-up timer with a live unsaved layer draft.
RecordedConfirms the save and settles the new layer into the stack.
HistoryAny day’s layers and sessions, with edit, remove, and undo.
IdentitiesPer-identity totals and 30-day activity.
ProfileTotals, level, best day, and where the time went.

Privacy and storage

Local-first, with the cloud as an option

Everything is on the device. Activity records live in AsyncStorage, state is persisted with Zustand, and secrets — if any — go to SecureStore. The app is fully functional offline; there is no request to make before the first session.

Supabase is there but off by default. The repo carries a schema, row-level security policies, and an auth-profile trigger migration, and the email sign-in UI is built. What is not built is the sync itself: no mutation queue, no conflict resolution, no realtime. Turning it on is a decision, not a default.

Settings can export every record as JSON and delete all local data behind a confirmation. Both exist because local-first software has to be honest about who owns the data.

ConcernWhere it livesWhy
Activity recordsAsyncStorage on deviceThe app works with the network off.
Auth (optional)SecureStore + SupabaseOnly used if you switch sync on.
History exportA JSON file on diskYour record survives the app.
Cloud syncNot built yetThe schema is ready; the queue is not.

Interaction

Calm motion, and how to turn it off

The app animates like stone settling, not like a slot machine. Presses spring. The tab indicator slides. A new layer drops into the block and finds its place. Nothing loops, nothing pulses for attention, and there is no sound.

Every animation respects the system’s Reduce Motion setting: the geometry swaps for fades rather than moving, because a layer that slides into place is the whole point of the model — and a reduced-motion build that still slides is worse than one that does not move at all. Haptics fire on the same layer drop, so the stone lands with weight.

If the reward is the grid filling up, the animation has to get out of the way.

Results

What is verified, and what is not

The local-first loop is implemented end to end and verified on the iOS Simulator through both Expo Go 57 and a Debug development build. Android is verified through a production export, and the web build is used for browser QA. TypeScript and ESLint are clean, and 32 Jest tests across 4 suites cover the date, grid, and XP utilities.

The honest part: this is a pre-release MVP. Nothing is on the App Store, and verification so far covers simulators and exports rather than physical devices.

32

Jest tests

ACROSS 4 SUITES

0

Accounts required

WORKS OFFLINE

3

Exports passing

IOS · ANDROID · WEB

Source · iOS Simulator (Expo Go 57 + Debug build), Android production export, browser QA

Lessons

What building it taught me

Designing a reward you cannot lose took longer than designing a streak. The first attempts kept wanting a celebration, and every celebration turned into a nag. The grid only works because it is quiet.

The second lesson was making effort legible. A layer whose thickness is derived from real minutes is a far better signal than a checkbox, but it means the geometry has to be data-driven rather than decorative — recomputed from the record, not drawn by hand. That decision is what makes edit and undo cheap: change the record and the stone changes shape.

The third was scope. It is much easier to ship a local-first MVP that admits it does not sync than a cloud product that pretends otherwise.

A reward you cannot lose is harder to design than a streak you can.

Still open

What is not built yet

No cloud sync

The Supabase schema, RLS policies, and auth trigger are in the repo, and the sign-in UI is built. The mutation queue, conflict resolution, and realtime layer are not. The app is local-only today.

No widgets, no notifications

Home-screen and lock-screen widgets and local reminders are on the list and not started. The app only speaks to you when you open it.

Physical devices are unverified

Verification covers the iOS Simulator, Android production exports, and browser QA. Haptics on real hardware, Android at runtime, VoiceOver, and enlarged Dynamic Type are all still unverified.

Desktop app is a plan

The repo is a Turborepo monorepo with a shared package for date, grid, and XP utilities, and a Tauri desktop app is planned on that seam. It does not exist yet.

In short

What Monolith came down to

01

Measure effort, not taps

The unit is a focused minute that turned into a layer, not a screen you opened.

02

No account, no hostage

The app works offline and exports to a file you own. Cloud is a switch you flip, never a toll you pay.

03

Quiet is a feature

The reward is the grid filling up. Everything else — motion, sound, badges — stays out of the way.

More work