The video above covers what Veta is. This is what it doesn't have room for: the rules underneath, and the things that went wrong on the way there.
The design problem
Streak counters are a threat that eventually gets carried out. They work by holding something over you, and the day you slip they take it — which means the mechanic that motivated you for two months is the same one that makes you delete the app. The punishment is load-bearing.
I wanted to know whether a habit tracker could work with the threat removed entirely: nothing to lose, nothing reset, no penalty of any kind. Kintsugi gave me the shape of the answer, but the interesting part was never the metaphor. It was whether an app can still feel like it matters when it has no leverage over you.
The rules that make it work
Three, and they're the whole product:
A crack is a consequence, not a decoration. It isn't a "you failed" badge
drawn on top of the piece. Fracture.generate takes the vessel's form, a seed,
and a ceiling — how tall the piece was on the day it broke. A crack
physically cannot wander into ceramic that didn't exist yet, it dies against the
foot instead of bouncing off it, and its width is the number of days you were
gone. Two people who miss the same week get different vessels, forever, and
neither of them can be redrawn.
Repair is free, permanently. Charging to recover from a bad week would monetise the exact moment the product exists to protect. Everything sold is expansion — more concurrent pieces, the premium glazes, the museum — and never restoration.
A window can only fail once it has closed. Not everything is daily; you can ask a piece for three days a week. While that window is still open, nothing can happen to the vessel no matter how many days are blank. The person who runs on Saturdays must not find their piece broken on a Wednesday.
How it's built
Flutter, and not one bitmap of ceramic anywhere in the app. Every vessel is
a surface of revolution drawn at runtime with a CustomPainter: banded shading,
elliptical rim and base arcs, per-clay gloss and speckle across nine glazes.
The gold palette was sampled from photographs of real kintsugi rather than
picked from a colour wheel — its brightest tone is cream, never white, which is
why it reads as metal instead of a glowing UI accent.
Because the geometry is generated from a seed, a piece is reproducible from a few integers: no images are stored, and the same scar redraws identically on any device, forever.
RevenueCat drives a single plus entitlement from a default offering, and the
paywall is assembled entirely from what that offering returns — prices, plans
and trial state are never hard-coded, so packaging changes from the dashboard
without shipping a build.
Challenges I ran into
The hard ones were all in the boring layer, and all of them could have silently robbed a user.
Daylight saving. The day index came from
DateTime(y,m,d).difference(epoch).inDays, which truncates the 23- and 25-hour
days a DST shift produces. Twice a year that would have deleted a day of
progress. It now uses civil-date arithmetic.
A save that could destroy the collection. load() cleared what was in
memory and then persisted it, so any read failure became permanent loss. Writing
is now a permission that has to be earned: nothing is saved until the app
knows what was on disk, and anything unreadable is quarantined verbatim —
"this build can't parse it" and "it's worthless" are different claims.
A two-phase write that rescued nothing. Saving writes a copy, promotes it, then deletes the copy, so a kill mid-write leaves a whole version behind. Except recovery preferred the main record — which always decodes in exactly that scenario, because it holds the older, complete version. The newer copy was found, ignored, and then overwritten by the very save that was meant to protect it. Only a test that seeds the disk in that precise state exposes it.
The same lesson, three times. Ugly cracks were never a seed problem. A fracture is generated against the height the piece had when it broke, so a break on day 12 of 60 lives inside a disc of clay and comes out as a stub against the foot. No width and no random number rescues that. What actually needed choosing was when the piece broke.
What I learned
Measurements filter; they don't decide. I built instrument after instrument to judge visual and audio work, and they misled me in the same direction every single time. A gold-detection mask counted warm terracotta as gold. An "attack time" metric measured the waveform's frequency instead of its envelope, reporting 0.1 ms for everything. A one-second RMS window declared four sound effects inaudible while they were playing perfectly — a 4 ms transient averaged across 1000 ms moves the number by a tenth of a decibel. Every one of those detected whether something changed, never whether the change was perceptible.
So the rule became: measure only to discard, and only where the descriptor is a physical claim. A crack is a fast attack — if it takes time to peak it's a thud, and that's not an opinion. A kiln is low-frequency. But whether gold looks beautiful is not something a number can answer, and there the instrument has to stay quiet.
Everything else went to blind comparison: candidates rendered, anonymised, and judged with no context about the outcome I was hoping for. When I told the judge what to look for it voted 6–0 in that direction — and 6–0 the opposite way when asked neutrally. That is the most useful thing I learned building this, and it had nothing to do with pottery.
What's next
Accounts and cloud storage. A collection currently lives entirely on the device that made it, which means replacing a phone costs sixty days of work. The next milestone is an account layer with server-side persistence, so that a piece outlives the hardware that grew it and the same museum can be opened from any device. It closes the last reachable state in which the app could still take progress away unfairly — the one thing it promises never to do.
iOS. Veta is written in Flutter and its rendering engine is entirely platform-independent, so reaching iPhone is a matter of adding a build target rather than rewriting the app. The constraint is not engineering: shipping to the App Store requires a Mac and an Apple Developer subscription, and the release will follow as soon as both are within reach.


Log in or sign up for Devpost to join the conversation.