Work / Apps & Platforms
Avalon
A health-first nutrition tracker: micronutrient-deep tracking, ingredient facts read through a lens the user chooses, and logging by voice, barcode or saved meal. Built as a mobile-first app, to ship native.
- Status
- In development · not released
- Stack
- TypeScript · React · Supabase · Capacitor (planned) · voice + barcode logging
- Started
- August 2026
- Source
- Private repo
What it is
Avalon is a nutrition tracker for people who care what is in their food as well as how much of it they ate. It tracks macros and micronutrients against targets set from the user's own profile, and it reads ingredient lists for facts: whether a product contains seed oils, high-fructose corn syrup, added sugar or artificial sweeteners.
Its first rule is facts, never verdicts. A flag states what is in a product. Interpretation only appears through a lens the user opts into, with cited education and a not-medical-advice line on every surface that could read as advice.
The food data is rented and the curation is owned: USDA FoodData Central is the spine, Open Food Facts products live in a separate, attributed lane, and nothing enters search without passing a curation gate, whether it came from an import or a member's submission. Every AI call runs server-side and is metered per user.
It is built to ship as a native iOS and Android app through a Capacitor wrap. The browser build is a development harness, not a product: nothing is deployed, there is no public URL, and there are no users. The native build is the next milestone and is not yet scheduled.
What it does
- Micronutrient dashboardMacro rings and vitamin and mineral bars against targets banded to the user's age and profile, with the foods that contributed most to each.
- Ingredient facts and lensesFlags parsed from ingredient text, rendered as neutral facts; an opt-in lens adds a cited perspective without turning a fact into a judgement.
- Voice, barcode and mealsSay a meal and confirm editable rows, scan a barcode, or log a saved meal in one tap with portions you can adjust.
- A curated food databasePublic-domain USDA data plus a segregated Open Food Facts lane, with submissions that are screened, rate-limited and visibly unverified until reviewed.
- Offline loggingEntries made without a connection queue on the device and sync when it returns, scoped to the account that made them.
- Privacy by constructionRow-level security on every user table, analytics only after consent, and account deletion proved by test to leave nothing behind.
Build log
From spec to almanac
The record of how Avalon was built, from the first commit on 7 August 2026 to today: the stages, the two audits, the corrections and the calls, dated and oldest first.
- 185commits
- 58days from first commit, with work on 17 of them
- 31database migrations
- 102test files
- 3server functions
As of 4 Oct 2026, counted from the repository.
- Era I · 7 – 9 AugSpec to code-complete
- Era II · 9 – 13 AugThe first audit
- Era III · 18 – 20 AugWhat green was not proving
- Era IV · 5 – 7 SepThe pre-release audit
- Era V · 2 – 4 OctThe orchard almanac
Era I · 7 – 9 Aug 2026
Spec to code-complete
Three days from an approved design spec to every stage of the first version written: the founding decisions, a staged blueprint, the food database, the core loop and the features that set Avalon apart.
-
Avalon begins
Avalon was the first venture to come out of the Venture pipeline (then named Ares), arriving with an approved design spec and a launch kit that included pre-committed kill criteria. A project memory, a decisions log and a design file were written the same day, so every working session could start from the record rather than from chat.
Brian's call The intake scorecard landed just under its strict go line. Brian confirmed go under the scorecard's own portfolio calibration, with dated kill gates to keep the bet honest.
-
A web stack wrapped native, against the default
Every serious competitor the research found ships Flutter or native code. Avalon was built as a React and TypeScript app to be wrapped with Capacitor anyway, for a browser-driven development loop, store-free iteration and a native path that is packaging rather than a rewrite. The accepted cost was no step data and weaker notifications until the native phase, with a standing rule that no browser-only API is used without a platform abstraction.
Brian's call Locked at the spec review, along with putting barcode and voice logging in the first version over a leaner default.
-
A curation layer, not a database
Commercial nutrition APIs were rejected because their terms prohibit building a database. Instead, USDA FoodData Central (public domain) became the spine, Open Food Facts went into a segregated, attributed lane because of its share-alike licence, and a curation gate was set at ingestion. The health lens was fixed as facts-not-verdicts from the start: contested claims only render through a lens the user chooses.
-
A blueprint, then the first stages
A blueprint laid out 22 stages from foundation to a final audit, and implementation waited for its approval. A research stage then checked its constants against live data, correcting nutrient mappings and inverting the barcode strategy so a cross-platform decoder became the default. A runnable shell, design tokens and sign-in with profiles followed the same day.
Method Every session ended by writing a handoff into the roadmap, so the build carried on across sessions without relying on memory.
-
The food database, capped to fit
The FoodData Central import and a development seed landed, then the Open Food Facts lane. Both imports were capped to fit the hosted database's size limit rather than pulling everything. A failed import had been leaving its connection pool open, so every importer was changed to close it on failure.
-
Ingredient facts, parsed and backfilled
A flag parser began reading ingredient text for seed oils, high-fructose corn syrup, added sugar, artificial sweeteners and sugar alcohols, and was run back across the catalogue. One function served the app, the server and the import pipeline, so a fact could not be parsed two ways.
-
The core loop closes
Search and food pages, a diary with an offline write queue, and a targets engine shipped in a day, with energy targets computed from the profile and micronutrient targets from published reference intakes. The phase closed on an end-to-end test that searched, logged and read the result back from the dashboard. The same day, the end-to-end tests turned out to be outside every type check and strict mode never to have been switched on; both were fixed.
-
Barcode and voice
Barcode scanning shipped, then voice logging: a server function turns a spoken meal into editable rows the user confirms before anything is logged. Every AI call was metered per user from the first one, so premium limits could be added later without rework, and no model key ever reaches the device.
-
The health lens, enforced by test
Two lenses shipped, with education cards for every flag and all 14 citation links checked live. A test bans verdict words from every fact and requires a real source on every rendered perspective. Flag chips were made neutral, never danger red, because colour can pass a judgement the text does not.
-
Submissions through a gate
Members can submit a food, and each submission passes validation, an energy sanity check against its own macros and a daily quota before a model tidies its formatting. Published submissions stay marked unverified until a curator reviews them.
-
The rest of the first version
Onboarding, settings, water and weight, offline hardening, an accessibility and performance gate, legal pages and account deletion all landed the same day, with consent-gated analytics after them. Deletion was proved by a test that seeds a user across every table, deletes them through the interface and checks that no row remains.
Brian's call Light testing during the build, with logic tests and one smoke test per phase, and a full audit once everything was written.
Era II · 9 – 13 Aug 2026
The first audit
A full audit of the finished first version, then five days of fixing it, re-verifying the fixes and finding out how many of them were not done.
-
The audit, finished by a second model
An overnight audit across 28 dimensions raised 101 findings. The usage limit killed its verification layer part-way, so Codex finished the decisive verdicts the next day, refuting by default. It confirmed 65: three critical, eight high, 44 medium and ten low. No fixes were made in the audit stage itself.
Method The finder tier overstated severity by about a level: critical findings survived verification 3 times in 7, medium ones 44 in 50.
-
The criticals closed first
The most serious findings were fixed the same day. AI spend is now reserved in the database before any paid call is made, behind a project-wide ceiling as well as a per-user one; curator rights moved out of a row members could edit into a table no client can reach; and the offline queue was scoped to the account that wrote it, so one person's queued entries could never replay under another's session.
-
Facts have to be right
The flag parser learned negation ("no sugar"), terms at the end of a sentence and Open Food Facts' allergen markup, each fix proved against a real product rather than invented input. A false flag is a false factual claim about a named company's product, the most serious mistake this app can make. Micronutrient targets also became age-banded, where before every account had been shown the same adult table.
-
The fixer is never the verifier
Re-verification kept finding fixes that were not finished: three findings shipped half-closed, two more failed a second check a session later, and one fix introduced a new high-severity regression. Each was caught only because someone other than its author re-ran the finding's own reproduction.
Method This became a standing rule: a fix is closed by someone other than its author, and a test counts as evidence only after it has been seen to fail.
-
The end-to-end suite had never really run
Every seeded end-to-end test had been skipping itself quietly because the test harness lacked the access it needed to seed data. Once it ran, it found that every new user who finished onboarding was sent back to step one with an empty form. Their data had saved; a stale cached profile told the app otherwise.
-
An apple for a mark
Avalon is the isle of apples, so the mark became one solid apple in the app's green, checked at 16 to 48 pixels before it was accepted. It replaced two unrelated placeholders, a leftover template favicon and a generated letter, and both icons now come from one shape definition. Empty screens that said nothing were rewritten in the same pass.
Brian's call He picked the direction for the mark and the empty states.
-
Consent that holds
Withdrawing analytics consent and then signing out had quietly turned collection back on, because the reset cleared the opt-out; consent is now re-applied after every reset. The analytics library was also injecting scripts past the strict content policy, and its loader was switched off rather than the policy widened, in an app that holds health data.
-
"All closed" was not the whole story
All 65 confirmed findings were closed, but under that headline sat unjudged leads, coverage gaps and one real liability: the parser fixes only corrected future parses. The leftovers moved to the roadmap before the findings file was deleted. The flag refresh was made transactional so an interruption could no longer delete facts without replacing them, then re-run across 85,981 foods, adding 303 facts.
-
A Retry that never retried, and a milestone tag
The scanner's Retry button had done nothing since it was built: the tests only checked that it was enabled. Retry now resets the state that hid the camera. With the fix passes done, the code was tagged v0.1.0-beta as an internal milestone. Nothing was released.
Era III · 18 – 20 Aug 2026
What green was not proving
A sweep for things that were green, documented or audited while being untrue, a correction to what the product is, and a refinement pass over every screen in the running app.
-
Continuous integration, at last
There had been no CI, so the gate was whatever someone remembered to run. A workflow now runs lint, type checks, unit tests, the build, server function tests and the docs link check on every push. Its first run caught that the server function tests could not run on a clean checkout and had only passed on the development machine.
-
Security tests that can fail
Nothing had ever made a request as one user against another user's rows, because the suite seeded everything with a role that bypasses access rules. A two-user test now makes 41 checks across ten tables, each with a positive control. It was then broken on purpose, and dropped from 41 passing to 1, to show it can fail. A mobile Safari project was added too, since the app had only been tested in desktop Chrome.
-
A cache that held nothing, and data that had no backup
The offline food cache matched the wrong requests and had never stored a single search. Open Food Facts attribution became a database relation rather than a naming convention. The hosted plan has no automated backups, so a backup script for user-owned data was written and its restore rehearsed inside a rolled-back transaction.
-
Avalon is not a web app
Several documents had come to describe a public web beta as the next milestone, and most of a session went into choosing a web host before the premise was checked. Avalon ships native; the browser build is a development harness. The hosting config was deleted, and the dated traction gates, which assumed a public web launch, were suspended rather than missed.
Brian's call The correction was his, mid-session, and it was written into the always-loaded project memory so no later session could miss it.
-
A persuasive diagnosis, measured wrong
A search rewrite cut the database work behind a test query by 82% and was kept, but the slow first search it was meant to fix still happened. Timing the same query cold and warm showed a 40-fold difference with no extra reads, so the cause was the idle hosting instance, not the query. Every earlier measurement had been taken warm.
Brian's call Stay on the current hosting plan and revisit at the end of development, since the only person paying the slow first search is him.
-
Refinement on the running app
Every screen was driven at phone width in both themes. The macro rings could not show that a target had been passed, the serving picker said "46 g · 46 g" on more than 11,000 foods, the review queue outlined its work items in danger red, and "Submit food" sat beside Scan and Voice as if it were a way to log. Each was fixed against the running app.
-
What an error or a wait owes the reader
A failed load took four requests and 7.4 seconds to admit it had failed, so retries dropped to one. Error screens that said "reload" were rewritten, because a native app has no reload button. The loading screen now names what it is waiting for and, after four seconds, explains why.
Brian's call Name the wait from the first frame, and explain it once it has run long enough to look stuck.
-
Three calls credited before they were made
A session that ran unattended let three design questions resolve to their recommended defaults, and the record then credited them to Brian. They were put to him two sessions later and he ratified all three unchanged, but the record had been false either way. It was corrected in the decisions log rather than by rewriting history.
Method A question that resolves itself is recorded as the session's decision, unratified, until a person says otherwise.
Era IV · 5 – 7 Sep 2026
The pre-release audit
A second full audit before any release, three days of fixes re-verified by a second party, and two account-level decisions put to Brian with a recommendation.
-
A second audit, verified by hand
The full audit covered 28 dimensions over 238 files. The usage window killed most of its agents, including every verifier, so it returned 127 findings with no verdicts, and verification was carried by Codex plus live checks against the running app. No critical finding survived; five high ones did, including silent loss of unsynced offline entries on sign-out. Access rules held on every user table, and deleting an account deleted it.
-
The fixes, and the shapes they left
A client write path that nothing in the app used was removed instead of narrowed. Growth limits moved into the database and count by time rather than by a date the client supplies. Sign-out now asks before discarding unsynced entries, saved meals log one item at a time and remember what landed, and analytics page addresses are stripped of anything a member typed or scanned.
Method Every fix was re-verified by someone other than its author: a Codex pass over the whole diff and a Claude investigator re-running each finding's reproduction against the live project.
-
Branded foods get real names
77% of branded foods shared a name and brand with another row, because the importer took the source description as it was. The pipeline now keeps one row per brand and barcode, repairs generic names from the source's own identity fields, and retires displaced rows without breaking any diary that references them. Name collisions went from 35,444 to none in 50,000.
Brian's call The full fix, rather than a partial one.
-
Barcodes a scanner can reach
783 of about 90,000 stored barcodes were shapes no scanner produces, most of them UPC codes with the leading zero dropped, so those products could never be found by scanning. Barcodes are now normalised at import, and the database refuses anything that is not shaped like a real barcode.
-
Two calls on data
After the re-imports the database sat close to its plan's size limit. Separately, a food taken down after a member logged it had been showing in their own diary as "Unknown food" next to real numbers. The access rule was relaxed so a member can read the name of a food they logged, and search kept its own filter so a hidden food stays out of discovery.
Brian's call Freeze imports and stay on the current plan until the native wrap is scheduled, then upgrade before anyone is invited. And fix the diary names through the access rule, with no app change.
-
The residue, closed by measurement
An oversized upload was meant to be refused before it was read, but a live probe showed the refusal never arrived, so the server now drains the body before answering. The blocked-word filter was checked against 40,000 real product labels: all 54 hits were false positives, such as rapeseed oil, and two words were removed. A final verification pass found four more gaps, which were fixed and checked again.
Brian's call Voice logging prepares each confirmed food's nutrition while the device is online, rather than widening the search.
Era V · 2 – 4 Oct 2026
The orchard almanac
An art direction chosen from four mocked options, and the work of making a dark default hold on every screen.
-
The orchard almanac, dark by default
Avalon takes its name from Insula Avallonis, the isle of apples, and the new direction leans on it: forest-black grounds with a warm bone ink, a serif voice for headings, and its own rosehip ink for the lens layer. Dark became the default theme and light an option. The serif is bundled with the app rather than loaded from a CDN, because the native build has to work offline.
Brian's call He chose the orchard almanac from four mocked directions, with dark as the default, and ruled that ingredient flags stay neutral bone: red on a fact reads as a verdict.
-
Overlays that dim in both themes
With dark as the default, every sheet's overlay was built from the ink colour, which is now bone, so sheets fogged the screen and the barcode viewfinder turned pale. A fixed overlay colour that stays dark in both themes replaced them. Accessibility scans pass on every signed-in screen in both themes; a by-eye pass of those screens after the change is still to come.