An Entry is one observation of a Trackable, the unit of captured data: 78.4 kg on Tuesday morning, one tablet taken, a night’s sleep. It belongs to exactly one trackable and holds only values, never how they are shown: a label, a colour or a format is presentation, keyed by the code the entry holds, so it can change without touching an entry.

Its columns

An entry is a row of the entry table (packages/core/src/schema.ts):

  • id, a UUIDv7 made on the device that writes it, so ordering by id is ordering by creation.
  • happened_at, when it happened, as a local wall-clock time (YYYY-MM-DDTHH:mm). It defaults to the app’s moment (The Moment). For a trackable set to Whole day it is the day alone, kept so by the repository on every write. For an interval it is the end its Entry date names, or the other end while that one is missing (Intervals).
  • recorded_at, when it was written down, set by the database (to the second) unless the writer gives one.
  • its value, in three columns, value_number, value_text and value_json, below.
  • is_nothing, an entry saying there was none, below.
  • signs, how sure each of its numbers is, by path: value for its own, a field’s name for a record’s. None for a number that is Known (below).
  • note, a few words about this one entry (“dehydrated”), never its value: nothing reads it as one. It is trimmed and none when blank. A text trackable’s entries take none, since their value already is words.
  • part_of, the entry it is part of, or none: a routine’s entry it was done during (Events), or an entry of its own Trackable that stands in for it (below).
  • logged_by, what logged it for you, a source’s id such as a day’s weather or a schedule’s, or none for an entry of your own (Logged for You). Changing such an entry makes it yours and clears it.
  • template_id, the Template it was made from, or none. No foreign key, so it outlives a deleted Template, which it then names nothing of.

How a value is stored

Which column holds what follows the trackable’s fields (Value Types):

  • A trackable’s own value, the field named value of a plain kind, is stored in its column even beside other fields: a number, a yes or no and a choice in value_number, a text or a word from a list in value_text. So adding a field to Weight changes no stored entry.
  • A number is stored in its dimension’s base unit (grams for a mass, seconds for a duration), and converted for display at the edges.
  • A choice and a yes or no are stored as their integer code, a yes as 1 and a no as 0, never as their label, and value_text stays empty for both. That is what lets an option be renamed without rewriting an entry, and why a search of entries’ text never matches a boolean.
  • Every other field is stored in value_json, as an object keyed by the field’s key, which is made once and never shown.
  • An interval stores its start and end at the top of value_json, beside its other fields, and its length in seconds in value_number, empty until both ends are known, so an open interval drops out of every total (Intervals).
  • An item field that carries an amount (Diet’s Food) stores the item’s id under the field’s key, with quantity, unit and base. base is the amount in the item’s basis unit, fixed when the entry is logged, so a later change to the item’s units cannot move a past meal; quantity and unit are what was typed, kept to show it. An entry that holds its item under entity_id instead is read the same way, as the amount field’s item (currentRecord in packages/core/src/entry-object.ts).

A number may be logged with a sign: About (~), At least (≥) or At most (≤), “I ran at least 5 km”. It is picked on a mark before the number, a tap moving through them and a press and hold opening them as a menu, and starts Known on every new entry: a sign belongs to what was seen, so Prefill copies none. signs stores them as one map (signsJson in packages/core/src/value-sign.ts), and only a number the trackable has may carry one. A day read from signed entries carries their sign, combined as a total’s is (trackableDayMarks in packages/core/src/rollup.ts, and readingDays for a field), and an entry’s label shows it before its number.

What an item contains is never copied into the entry. It is worked out when read from the item as it is now, which is why correcting a food corrects every day it was eaten (Catalog Entities).

Writing

Every log adds an entry and never replaces one (Capture Model); Undo deletes the new entry, and changing an entry is editing it.

An edit leaves alone each value column it is not given; only an explicit null clears one. Giving a Nothing a value makes it an entry.

Every write of a single entry is checked at the repository against the trackable’s definition (entryProblem in packages/core/src/entry-check.ts): the kind of value and its column, a number’s limits and wholeness, a choice’s codes, an interval’s ends, an item and its amount, every field of a record. The editors check first, so this is the backstop for every other way in. A link to the entry it is part of is checked too: it must be a routine’s entry or one of the same Trackable, and not the entry itself or inside it. An entry naming a Template may hold no value of its own: it stands in for its parts (Reading, below). Bulk writes for seeding check values but leave the Nothing and routine rules to their builders.

A Nothing

A Nothing is an entry that says there was none: no food at lunch, no exercise today. It carries no value, answers its span, and every reading skips it for values; a count, a sum or a yes/no reads 0 on a day it answers (Readings). A trackable logged with no value at all, a moment such as a pill taken, has entries with no value that are not Nothing, which is why Nothing is a column and not an empty row. A Nothing is for what an empty day cannot say: that you paid attention and there was none, a fast rather than a forgotten meal, a dry day on Alcohol; Logged anything says only that you used the app that day. It is not a food with no calories, which would count as a meal, so a first meal time or a fasting window would read it as eating.

A Nothing covers a span: the day, or, where the day’s Rundown asks the trackable at several places, the place its time answers (nothingSpan in packages/core/src/check-in.ts, over the places rundownPlacesOn works out from the stored layers, never the trackable’s own defaults). A Nothing and an entry never share a span, kept by the entry repository: an entry logged or moved into a span removes the Nothing there, a second Nothing in a span returns the first, and a Nothing where an entry already stands is refused. Undo of that entry does not bring the Nothing back, which leaves the span unanswered rather than wrong.

A Nothing is logged in one place, the Rundown, and only where none is a true answer for the trackable and its value cannot say it (noneAnswer in packages/core/src/none-answer.ts): a trackable read by Count, Sum or Any, whose none is zero occurrences, and never one whose value says none itself, a yes/no’s No or a scale’s None (The Rundown). No entry form offers it.

Latest: two questions

“Latest” is two questions, answered by two orderings, and both are correct:

QuestionOrderingAsked by
Of this period’s entries, which is the value?recorded_atLast and First in a Reading (dailyValue in packages/core/src/rollup.ts, and the derived evaluator)
Across time, what is the current reading?happened_ata computed fill (latestMeasured)

The case that separates them: weigh in on Monday, record it on Tuesday, then on Tuesday afternoon fill in last week’s missing weigh-in. By recorded_at the latest is last week’s weight; by happened_at it is Monday’s. A day’s value wants the first, since a correction beats what it corrects; a pre-fill of what you weigh now wants the second. latest-entry.test.ts pins that the two disagree on that data.

Reading

A day’s value is a Reading over that day’s entries: the trackable’s own, or another wherever it is shown.

An entry holding entries of its own Trackable stands in for them, and a reading counts the parts, never the entry and its parts both (countedEntries in packages/core/src/rollup.ts, called by dailyValue, trackableDays and readingDays). A Diet entry holding an egg and cheddar adds the egg and the cheddar to the day; one holding nothing counts as itself. A routine’s entry holds other Trackables’ entries, so it is never left out of its own: it counts its span (Events). What a container holds is read by each part’s own Trackable, at any depth, and never stored on it (rollupOf in packages/core/src/containers.ts): a lunch reads Diet’s total over its foods and Water’s over its glasses. Logging is meant to take one step from anywhere (Frictionless Capture), and every capture surface stages a value before it writes: picking one writes nothing until Log.

Trackable · Value Types · Capture Model · Readings · Presentation Layer · Catalog Entities · Data Model