Skip to content
Velisa

Not yet on the App Store On the App Store

iOS · Training · Sleep · Nutrition · Recovery

Velisa refuses to guess.

Training, sleep, nutrition and recovery — built on your own Apple Health, Whoop or Amazfit data, and computed on your own iPhone. Every score names the paper it came from, and every one of them will tell you when it does not have enough of your data yet.

Not on the App Store yet — the developer is mid-enrolment in the Apple Developer Program. There is no waiting list, because there is no server to keep one on.

Every install opens fully unlocked for 30 days from first launch — no card, nothing to cancel. The price is shown in the app before you're ever asked to pay.

Velisa's Today tab on a fresh install. The card is headed Today's call, 0 of 3 inputs, and reads: Not enough to call it yet. Needs recovery markers or 28 days of training history — neither exists yet. Below it, Recovery signals reads: No markers yet. Still missing resting heart rate, overnight HRV, sleep.
0 of 3 inputs · fresh install · -startTab today
The same Today tab with history behind it. Today's call, 3 of 3 inputs, reads: Back off — cut volume about 40 percent, tagged Deload, with its reasons listed underneath: only 1 of 3 recovery markers favourable; load in range, ACWR 1.05; fitness ahead of fatigue; recovery overrides a favourable load picture.
3 of 3 inputs · with history · preview data

Real captures, iPhone 17 Pro simulator: -startTab today and -startTab today -showFixtures. The populated frame is the app's own test fixtures, not a real person's numbers.

The whole idea

What Velisa does when it does not know

“Not enough to call it yet. Needs recovery markers or 28 days of training history — neither exists yet.”

Today's call, on a fresh install · Velisa/UI/Today/TodayView.swift

That is the product, on the first screen a new install shows. Three commitments hold it up, and they are enforced by tests rather than intentions: a missing input contributes exactly zero and is shown as missing, never imputed; no screen anywhere says “no data available”, because a calibrating card has to name what it is waiting for; and where there is uncertainty, it is on the screen.

One of those tests asserts that no locked-feature copy may contain a numeral — so nobody can slip a plausible-looking preview behind a paywall. The rule applies to us too.

Sourcedocs/STATE.md — “The three commitments everything else follows from”. Engines return nil or .calibrating below their minimum window, and the metrics engine writes no row rather than a zero.

What it replaces

Three jobs that are usually three apps.

The log

4471 exercises. Bodyweight counts toward volume load, scaled per exercise — a standard push-up carries 0.64 of you2, a pull-up all of it. Twelve pull-ups at 78 kg is 936 kg of work, not zero. Deltoids split front, side and rear.

1 · 2README.md, docs/STATE.md; Velisa/Workouts/WorkoutModels.swift — the 0.64 is Ebben et al. 2011, a kinetic analysis of push-up variations. Where no published figure exists the factor stays 1.0, an explicit upper bound, rather than a guess.

The food

14,5513 foods bundled offline in 12.3 MB, including 1,014 Indian cooked dishes. A barcode scanner and a nutrition-panel reader for everything else — built that way because of what the research actually found: no Indian barcode registry publishes nutrition at all.4

3 · 4README.md food-database table (INDB, CC BY 4.0; Open Food Facts India kept in a separate file under ODbL 1.0). docs/STATE.md — “Barcode coverage”: gtin appears zero times in ONDC's entire API contract.

The layer neither has

185 explained metrics over 716 cited references. Every one states its own minimum data window and renders nothing below it. Tap any number and the arithmetic that produced it opens underneath.

5 · 618 = the ExplainedMetric cases in Velisa/Scoring/MetricExplainer.swift. 71 = distinct Citation records across Velisa/Scoring/. Both counted from source on 2026-08-01.

The actual app

Four tabs, four real captures.

The Today tab with history. Today's call, 3 of 3 inputs: Back off — cut volume about 40 percent, tagged Deload, with four reasons listed and a confidence bar reading 100 percent.
The Train tab. Training load reads 1.05 ACWR, tagged Optimal — load is in the productive range. Below: 3 sessions, 9 sets, 4,410 kg volume and 12 PRs over 7 days; then Fitness and fatigue reads Productive, fitness ahead of fatigue, fitness 12.2 against fatigue 7.3, Banister impulse-response; then weekly sets per muscle.
The Eat tab. An energy ring reads 1043 kcal left against a 2091 target, 1048 in and 506 out, with the line: shown for context only — never added to your target. Macros below read protein 78 of 196 g, carbs 98 of 163 g, fat 39 of 73 g.
The Body tab. Velisa Age leads with minus 1.4 years — 1.4 years younger over the last 6 months, compared against your own earlier reading, not other people. Today reads 28 plus or minus 28 years, tagged VO2max estimated. Trajectory sits underneath, tagged Reversing.
The Today tab with history. Today's call, 3 of 3 inputs: Back off — cut volume about 40 percent, tagged Deload, with four reasons and a confidence bar reading 100 percent.

Today

One card decides the day, and it is allowed to decline. Today's call reads three inputs — recovery markers, acute:chronic load, and fitness against fatigue — names which of them it actually had, and lists the reasons it landed where it did.

With none of the three, it makes no recommendation at all.

3 inputs · confidence printed on the card · every reason listed

The Train tab. Training load shows 1.05 ACWR tagged Optimal, with Load is in the productive range. Below: 3 sessions, 9 sets, 4,410 kg volume, 12 PRs over 7 days; Fitness and fatigue reads Productive — fitness ahead of fatigue, fitness 12.2, fatigue 7.3, Banister impulse-response; then weekly sets per muscle.

Train

A full strength and cardio logger. The acute:chronic workload ratio is computed the uncoupled way — the 7-day window is not inside the 28-day one — so it cannot manufacture its own correlation, which also means it will not agree with an app that couples.

Volume load counts bodyweight for calisthenics, scaled per exercise rather than all-or-nothing.

447 exercises · uncoupled ACWR · Banister fitness–fatigue

The Eat tab. An energy ring reads 1043 kcal left against a 2091 target, 1048 in and 506 out, with the line: Shown for context only — never added to your target. Macros below show protein 78 of 196 g, carbs 98 of 163 g, fat 39 of 73 g.

Eat

Calorie and macro targets measured from what you actually ate and what your weight actually did — not predicted from a formula, once there are two weeks of your own data to read instead. A wearable's active-energy figure is shown for context and never paid out as a calorie credit.

Unlogged days are skipped, never read as zero. One blank day treated as a fast would invent a deficit that never happened.

14,551 foods offline in 12.3 MB · barcode + nutrition-panel reader

The Body tab. Velisa Age leads with minus 1.4 years — 1.4 years younger over the last 6 months, compared against your own earlier reading, not other people. Today reads 28 plus or minus 28 years, tagged VO2max estimated, with the caveat that fitness age is a low-resolution measure. Trajectory sits underneath, tagged Reversing.

Body

Recovery Score, Velisa Age, Today's Ledger, sleep regularity, HRV-guided training, illness detection, energy availability. Each one is gated behind its own minimum data window, and each one refuses to answer when the literature does not sign the answer.

Velisa Age leads with the direction it is moving, not the level — because the level's uncertainty is wider than most of the differences people care about.

18 explained metrics · each gated on its own window

Train, Eat and Body above are captured with -showFixtures — the app's own test data, so the design is visible on a fresh install. Nothing in them is a real person's numbers.

18 metrics · 71 cited references · 0 invented weights

The mathematics

Nothing in the scoring layer is fitted, tuned, or chosen because it looked good. Every threshold is a number printed in a paper, and the paper is attached to it.

18

metrics with an explainer sheet

71

cited references in Velisa/Scoring

0

weights fitted to data

0

third-party dependencies

First, what “your normal” means

A 57 ms HRV reading is not good or bad in the abstract. It is above or below your own recent middle. Every engine builds that baseline the same way — the median, not the mean, and the median absolute deviation, not the standard deviation. One three-hour night should not be allowed to redefine what is normal for you.

z  =  0.6745 × (value − median) / MAD

Velisa/Scoring/Baseline.swift:24 — 0.6745 rescales MAD so it is comparable to a standard deviation. isReliable is count >= 14: below fourteen days of history, z() returns nothing at all and every engine that depends on it declines to render a verdict.

Two rules govern the published tables underneath that. Thresholds are health-optimised, not population averages — anchoring to a population mean is exactly why other effective-age calculators tell the average adult they are biologically younger than they are, because the mean is dragged down by the unwell. And a missing interval is never invented: where a paper publishes only a point estimate, that contributor is recorded as carrying no disclosed error, and the output states how many contributors were in that position — so “no uncertainty shown” is never quietly confused with “no uncertainty exists”.

SourceVelisa/Scoring/References.swift, file header — “Nothing in this file is fitted, tuned or guessed.”

Velisa Age

two layers, kept separate on purpose

“The age of a person whose all-cause mortality risk matches yours, given your fitness and a short list of measured behaviours.” The spine is your fitness age: a published VO₂max reference table is inverted, so instead of asking what you should hit, it asks at what age the reference value equals what you actually have. That is a purely descriptive statement and needs no assumption about how fast anything declines.

The ledger is then added on top. For each behaviour with a published hazard ratio — sleep regularity, sleep duration, steps, moderate and vigorous activity, strength training, resting heart rate, body fat — the ratio converts to years through Spiegelhalter's effective-age transform. All-cause mortality rises about 10% per year of age after 25, so:

Δ years  =  ln(HR) ÷ ln(1.10)   ≈   10 × ln(HR)

    HR 1.22  →  +2.0 years
    HR 0.85  →  −1.6 years

Each Δ is shrunk by how well Velisa actually measured that exposure, capped at ±5 years per contributor and ±15 in total. A contributor you have no data for contributes exactly zero years and widens the ± — it is never filled in with a population average.

Shows nothing until

60 days of wear, 45 nights of sleep, and at least 4 ledger contributors present.

Spiegelhalter 2016 · WHOOP Healthspan 2025 · Kaminsky 2015 (FRIEND) · Nes 2011 (HUNT)

Recovery Score

a rendering of the markers, never a replacement for them

Four overnight markers — HRV (log-transformed, because raw HRV is right-skewed), resting heart rate, respiratory rate and sleep adequacy — each turned into a personal robust z-score against a 28-night baseline, then combined with equal weights.

composite Z  =  mean(z₁, z₂, z₃, z₄)

unit-weighted — no component is favoured

“The weights are equal, and that is a choice with a paper behind it rather than a shrug: unit-weighted models match or beat optimally-weighted ones when the criterion is noisy and the true weights are unknown (Dawes 1979). Polar and WHOOP publish contradictory orderings and no numbers.”

Shown inside the app, in the Recovery Score explainer · Velisa/Scoring/RecoveryScore.swift:838

The ± around it is the wider of two things: how far the number would move under Polar's stated ordering or WHOOP's stated ordering instead of unit weights, or the device's own published overnight measurement error. Whichever is larger is named as the reason for the band's width — a false sentence about why a number is uncertain is still a false sentence.

And four conditions under which it refuses to answer at all. Each one is a case where the arithmetic would still run and the answer would still be wrong, which is exactly when a health app is most dangerous.

  • Two readings of the same night. HRV below baseline and resting heart rate also below baseline. Both the fatigue reading and the parasympathetic-saturation reading are live, and the literature does not sign that quadrant.
  • Illness signals own today. A recovery score is answering the wrong question, and “you're at 22%” reads as “train less” when the correct statement is “three overnight signals moved together”.
  • Your HRV baseline restarted. The source or convention changed inside the comparison window — Apple stores SDNN, Whoop reports RMSSD. The step is a device swap, not you.
  • The weighting decides this number. If the sensitivity band spans 20 percentile points or more, the four components are shown instead of a single figure.

Shows nothing until

14 nights of baseline per signal (28 preferred) · 3 valid nights a week · 3 of the 4 signals present · and 30 days of composite history before any 0–100 percentile is shown at all.

Below that last one the app says so, in these words: “A percentile computed from fewer is a number pretending to be one, so this shows the band and the four signals instead.”

Doherty 2025 · Bellenger 2016 · Lundstrom 2024 · Dawes 1979 · Dial 2025 · Polar Nightly Recharge 2019 · Plews & Buchheit 2013

Today's Ledger

why there is no “energy” number

“Energy” is not a measured quantity. It is four different real things wearing one word — metabolic energy, sleep pressure, autonomic arousal, and perceived fatigability — and only the first is genuinely conserved. There is no physiological reservoir that drains from 100 to 0 over a day.

So a published alertness curve carries the spine, and three ledgers sit beside it. They are never summed, with the curve or with each other:

lane 1   minutes above your resting range
lane 2   training load added today
lane 3   kilocalories in versus out
──────────────────────────────────────────
total    —  different quantities, different
            units, and no published factor
            converts between them

The heart-rate lane is labelled “time above your resting range”, not “stress”, on purpose: the classifier it would be imitating reads beat-to-beat HRV, and Velisa has heart-rate bins. Calling it the same thing would be the false-precision this metric exists to avoid.

The spine is the Three-Process Model, and checking it found an error. Velisa's own research memo prints the two homeostatic equations with their asymptotes swapped. As written, the wake process rises and the sleep process diverges. The memo's own definition of the recovery rate settles which way round they go, because that expression is the algebraic solution of one equation and not of the other:

g  =  ln((ha − 14.0) / (ha − 7.96)) / 8
   =  ln(0.3 / 6.34) / 8
   =  −0.38136

used:  S (t_aw)  =  la + (s_w − la)·e^(d·t_aw)
                   → decays toward la = 2.4

       S′(t_as)  =  ha − (ha − s_s)·e^(g·t_as)
                   → recovers toward ha = 14.3

Every constant is printed in Ingre et al. 2014 — 136 aircrew, 964 sleep logs. None is fitted here. The correction is not merely written down: the test suite re-derives the published 7.96 → 14.0-in-eight-hours anchor from the implementation, so it is asserted rather than asserted-about. Where a form is not published — Ingre's two brake constants are, the algebraic form that consumes them is not — that gap ships as a visible disclosure on the lane itself.

Shows nothing until

48 hours of sleep and wake history for the alertness curve. Each other lane gates independently on its own data.

Åkerstedt & Folkard 1997 · Ingre 2014 · Enoka & Duchateau 2016 · Tanaka 2001 · Fuller 2020

What each one waits for

verbatim from MetricExplainer.swift

Every metric carries its own minimumWindow string, and the type will not compile a metric without one. These are those strings, unedited.

Ten of Velisa's eighteen explained metrics, the minimum amount of your own data each needs before it will show a number, and the references behind it.
MetricShows nothing untilSources
Today's call At least one of: recovery markers with a 14-day baseline behind them, or 28 days of training history. With none of the three inputs no recommendation is made at all. Doherty 2025 · Saw 2016 · Impellizzeri 2020 · Banister 1975
Acute:chronic workload ratio 28 days of training history — 7 acute plus 21 chronic. Nothing is shown before that. Gabbett 2016 · Impellizzeri 2020
Fitness and fatigue 28 days of training history before a state is named. Banister 1975 · Clarke & Skiba 2013
Velisa Age 60 days of wear, 45 nights of sleep, and at least 4 ledger contributors present. Spiegelhalter 2016 · WHOOP Healthspan 2025 · Kaminsky 2015 (FRIEND) · Nes 2011 (HUNT)
Sleep Regularity Index 7 consecutive recorded nights, and 28 prior values before any personal comparison. Phillips 2017 · Windred 2024 · UK Biobank SRI 2023
Nocturnal temperature deviation 21 nights before any number is shown; a 28-night rolling baseline behind it. TemPredict 2022 · Maijala 2019 · Kräuchi 2000
HRV-guided training 14 days for a first normal band (28 preferred), and 7 valid readings for the swing. Plews & Buchheit 2013 · Plews 2012 · Kiviniemi 2007
Overnight load signals 28 nights of personal baseline. This is non-negotiable — the baseline is the entire method. Alavi 2021 · Miller 2020 · TemPredict 2022
Energy availability 7 consecutive days with at least 6 complete food logs, plus a measured fat-free mass. IOC RED-S 2018 · Cunningham 1991
Adaptive expenditure 14 days of history with at least 8 logged days. Before that a formula carries it, and says so. Hall & Chow 2011 · Sanghvi 2015 · Mifflin-St Jeor 1990

Ten of eighteen. The remaining eight — volume load, estimated one-rep max, weekly sets per muscle, Velisa Trajectory, sleep debt, chronotype, trend weight and macro targets — carry the same field and the same rule.

And what it refuses to compute

the restraint is the feature

Things the literature or the data genuinely does not support, listed on purpose rather than quietly shipped anyway.

  • Your circadian phase, as a clock time. Published wearable phase estimators carry roughly ±2 hours of error and depend on light exposure, which no connected device measures. “Your melatonin onset is 21:47” is exactly the kind of claim that turns an honest layer into a wrapper with extra confidence. Velisa shows your sleep-timing midpoint instead, disclosed as not the same thing.
  • A simulated Process S. The physiology is real, but its standard time constants are calibrated against EEG slow-wave activity no wearable provides, and the commonly quoted values could not be traced to a primary source. A transparent decaying sleep-debt heuristic ships instead, labelled a heuristic everywhere it appears.
  • The coupled acute:chronic workload ratio most training apps ship, where the last 7 days sit inside the 28-day average. Mathematical coupling manufactures an apparent injury association even in randomly generated data, because the numerator is part of the denominator (Impellizzeri 2020). Velisa computes the uncoupled version — which is why its number will not match an app that couples.
  • A body-battery figure. Beat-to-beat data across a whole day is not available from any source Velisa reads, and the minutes-to-points mapping is unpublished everywhere. It would be a reimplementation of an unpublished algorithm on worse data than the original.
  • A precise South Asian body-fat threshold. The literature states the direction the correction should move and publishes no magnitude. Waist cut-offs are published and are implemented — 90 cm male, 80 cm female, against 102 and 88 for the global set — but the body-fat offset returns nil in code, and the gap is disclosed in the output rather than filled with an invented coefficient.

SourceVelisa/Scoring/References.swift, ReferencePopulation.unverifiedBodyFatOffsetPoints — a documented nil with a comment saying what would have to be published for it to become a number.

Six rules, on all eighteen

Every explainer answers the same six things, in the order a curious person wants them: what it is, one plain sentence · how it is worked out · what your number is, with the arithmetic spelled out · what counts as good, with your position marked on it · what moves it · and when it is wrong.

That last one is not a convention. In the type that carries an explainer, five of the six fields have defaults and limitations does not — the comment beside it reads “Required. No default, so it cannot be forgotten.” A metric with an empty limitations list fails isWellFormed, and the test suite holds all eighteen against it.

“Until then Velisa leaves this blank rather than showing a number it cannot stand behind. Everything below still applies — it is how the figure will be worked out once the data is there.”

The explainer sheet, calibrating · Velisa/Design/ExplainerSheet.swift

A card from the explainer sheet headed Your number, tagged calibrating. It reads: Not enough data yet. What it needs: at least one of recovery markers with a 14-day baseline behind them, or 28 days of training history. With none of the three inputs no recommendation is made at all. Until then Velisa leaves this blank rather than showing a number it cannot stand behind.
The explainer sheet behind every metric in the app, shown here still calibrating.

The ± is the honest part

28, plus or minus 28.

This is the app's own headline number from the capture beside it, drawn to scale. The point estimate is 28. The band around it runs from 0 to 56 — wider than the claim, and the app says so on the card rather than in a footnote.

A scale from 0 to 56 years. The point estimate sits at 28, and the uncertainty band spans the entire scale, from 0 to 56 — a width of plus or minus 28 years. Drawn beneath it at the same scale, a second bar three years wide runs from 28 to 31, the calendar age printed on the same card.

The short white bar underneath is drawn at the same scale. It is three years — the distance from 28 to the calendar age of 31 printed on the same card. That is the part a person reacts to. The amber band is the part that is honest, and it is nearly nineteen times wider.

A competitor would ship “Fitness Age: 28” and stop. The reason the band is that wide on this reading is printed on the card too: VO₂max was estimated rather than measured, and the app tags it VO₂MAX ESTIMATED so you know which half of the uncertainty is yours to fix.

NoteVelisa Age's ± is measurement error — how precisely your own VO₂max is known — not the spread between people of your age, which would have produced a useless ±33-year band on every reading. Those are two different questions, and the second is stated separately, in the caveat quoted here.

The Velisa Age card. It leads with minus 1.4 years, then: 1.4 years younger over the last 6 months, compared against your own earlier reading, not other people. Today reads 28, plus or minus 28 years. A pill reads VO2max estimated, and beneath it: VO2max was estimated, not measured — that's most of your plus-or-minus. The footer reads chronological 31, fitness age 33, 7 of 8 contributors.
Body · Velisa Age · preview data. The caveat on the card, verbatim: “Fitness age is a low-resolution measure — the spread between people of the same age is over a decade. Track the direction it moves, not the number.”

Appearance

Three palettes, all dark.

The Today tab in the Ember palette — amber accents on a warm near-black, with the trial strip reading 5 days left of everything. The same screen in the Graphite palette — the amber replaced by a near-white accent on a cool near-black. The same screen in the Indigo palette — a periwinkle accent on a deep blue-black.
Settings → Appearance. Three palettes, all dark, all shipped — the app has no light theme to match.

Verifiable, not asserted

What Velisa does not collect.

Every line below is quoted from the privacy policy already published at velisa.app/privacy — written before this page existed, and required by App Store Connect for a HealthKit app.

No account
Velisa never asks for or stores an email address, a name, or a password of its own.
No server
There is nothing to sell from. Your data lives in a database on your phone, and on-device StoreKit verification means even the subscription needs no backend.
No analytics SDK
No crash reporter and no third-party tracking library of any kind. This is a fact about the app's code — there is nothing to point to — not a policy about how one is configured.
No ad identifier
And no advertising anywhere in the app.
No location, microphone or contacts
None of the three is requested, at any point, for any feature.
Nothing sold, ever
There is no server to sell from and no buyer this app is built to serve besides you.

Zero third-party dependencies in the app itself, and zero external requests on this page — enforced in the browser by a content-security policy, not just intended.

Thirty days, no card

You see your own numbers before anyone asks you for anything.

“Velisa opens with 30 days of everything, no card needed. Then one subscription keeps it.” Not an App Store introductory offer — that kind requires a card, a password and a “you will be charged on…” before you have seen a single one of your own numbers, which is the opposite of an app whose whole argument is that the synthesis only earns its keep once there is real history behind it.

If you decide not to subscribe: “Nothing you logged is deleted, and exporting it stays open either way.” Data export is the one permanent member of the always-available set — it does not lock, even when the rest of the app does.

Apple is the merchant of record. Velisa never sees a card number, a billing address, or any payment detail at all.

The price is shown in the app before you are ever asked to pay. It is not on this page, because it is not final — and publishing a placeholder would be inventing a number, which is the one thing this app is built not to do.

SourceBoth sentences are the app's own paywall copy. Trial length is read from LocalTrial.days everywhere it appears, and a test pins every sentence about it to that constant — so no string can drift out of sync with the code.

AlsoEntitlement.alwaysAvailable contains exactly one member: .dataExport.

Not on the App Store yet

Available on the App Store

Velisa isn't shipping yet. Thirty days of everything. No card.

The developer is mid-enrolment in the Apple Developer Program. There is no waiting list, because there is no server to keep one on — so this is a plain email to a real inbox.

Every install opens fully unlocked for 30 days from first launch — no card, nothing to cancel. The price is shown in the app before you're ever asked to pay.

Opens your own mail app. Nothing is stored or tracked here — same as Velisa itself. support@velisa.app · replies come from one person.

“Nothing you logged is deleted, and exporting it stays open either way.” support@velisa.app · replies come from one person.