The value types say what an Entry stores and what it means. A Trackable’s fields and a catalog’s attributes (Catalog Entities) are declared with the same kinds, in packages/core/src/value-schema.ts. How a value looks, its labels, colours and order, is not here but in the Presentation Layer, so it can change without touching an entry. A change to a field’s kind can change what entries already logged mean, so the trackable form names each such change and asks before saving it (Trackable Authoring Flow); what a saved number field measures cannot change at all.
Every trackable is a list of fields
Every trackable declares fields, and every entry is an object of them (allFieldsOf). A moment, a trackable logged with no value (a pill taken, Journal), has no fields: its value schema is { kind: "none" }, and each entry is only when it happened. A tag has the same schema and takes no entries (Tags).
The field named value (VALUE_FIELD) of a plain kind (a number, a text, options, a list, a yes/no) is the trackable’s own value: its charts, zones, units, quick-add amounts and Readings read it (valueFieldOf), and it is stored in its own column (value_number or value_text) even beside other fields, which go in value_json. So adding a field to Weight changes no stored entry. A trackable whose only field is its value field, not required and with no rule for an absent value, is written as that field’s schema alone, { kind: "number", … }; schemaOfFields writes fields back in that shorthand where it can, and anything else as { kind: "record", fields }. There is no value field beside an interval, whose column holds its length. ownSchemaOf and valueSchemaOf give every reader the own value’s schema.
The trackable form has no value type, only fields: a new trackable starts with its own value, one Number field named by the trackable. A field is named by its label; its key, which entries hold its values under, is made once and never shown (newFieldKey in packages/core/src/field-kinds.ts), so a label changes freely. Entries, derived formulas, Readings and filters all refer to a field by its key, so a rename changes the label alone and nothing stored moves; every table, picker and form shows the label. The key is never made from the label: one slugged from it would drift from it after a rename and need a made-up suffix on a clash, and one that followed the label would rewrite every entry, every synced change and every reference on each rename. A stored field without a label reads as its key with a capital first letter (book reads Book), and takes that as its label the next time the trackable is saved. Fields do not nest: a field is one of the kinds below, never a record or a list of records.
The kinds of field
The fields editor (apps/renderer/src/components/fields/FieldsEditor.tsx) offers each kind with the settings FIELD_SETTINGS declares for it:
| kind | in the editor | stored as | settings |
|---|---|---|---|
number | Number | a number, in its measure’s base unit | measure, unit, whole, Limits, Step, slider |
text | Text | words | |
text with list | Tags | words, comma-separated | |
boolean | Boolean | 1 or 0 | |
coded | Options | an integer code | the options and In order, in the form’s own section |
enum | List | the word itself | the choices and Only these; offered only where a field already is one |
category | Category | an option id, or a list of ids | its values, and one or several |
reference | Reference | an item’s id | its catalog, and whether it carries an amount |
progress | Progress | { from, to } along an item | where the item ends |
interval | Interval | a start and an end | the words for its ends, Stale after, Entry date |
A trackable’s own value can be a number, a text or a list, options, or a yes/no. Two more kinds are only ever a whole value: time, a clock reading that only a derived formula produces, and none, a moment or a tag. A catalog’s attribute takes the same kinds, and a number attribute one more setting: that an item without the value has none of it (absent: "zero", a food’s caffeine), rather than an unknown amount.
Number
A number is whole (number_type: "int") or not, and either measures something or is counted in a unit of its own. One that measures declares a dimension (mass, volume, energy, duration…) and is stored in that dimension’s base unit, grams for a mass, and shown in the unit you prefer or its own; one that does not has a free-text unit (glasses, reps) or none. The registry, the base and display units, and how a unit is resolved are in Units & Dimensions. A number can carry units of its own beyond the shared ones (extra_units: a standard drink of 13.45 g of alcohol).
Its Limits (min, max) are enforced, both ends included, and kept in the unit they were typed in (limits_unit); its Step is how far − and + move it; with Limits and no measure it can be set with a slider between them, with no numbers on it (slidesOn: how stressful something was). How these are checked is in Capture Model. They are stored on the field and decide what can be logged next, not what a stored value means. Loosening them is safe. Tightening them leaves entries already logged outside them as they are, read and charted as logged, so when entries fall outside the form names the change and counts them before saving (What a change does to entries). Such an entry still saves with a new time or note: a value that is not changing, on the trackable it was logged to, is not judged again, and only a changed value is held to the Limits as they are now (updateReturning in packages/core/src/entry-repository.ts). The same holds for any other tightening of a definition since the entry was logged.
There is no separate duration kind. A number measuring a duration and an interval’s length are both stored in seconds, so a formula may add them.
Text and lists
A text is words, stored in value_text when it is the trackable’s own value. A text kept as a list (list, offered as Tags) chips every comma-separated word, a single one included, and a catalog’s Union rule folds lists of them. It is named with the glossary’s Tag on purpose: the same idea as a trackable’s tags, many labels on one thing, without their nesting. A text can offer what it has held before as you type (remember), most used first, keeping a new word for next time: a symptom, what an emotion was about.
A List (enum) is a word from its own choices, stored as the word; Only these (strict) refuses any other, and without it a word not among them is kept too. It is kept for the trackables that already are one: the form offers List only where a field already is one, since options or a Category can be renamed and a word cannot.
Options
Options (coded) pick from a set and store the option’s integer code in value_number. The code is the identity; each option’s label, colour and order are presentation, rows of trackable_option keyed by (trackable_id, code). The set carries one flag, In order (ordered):
- In order (ordinal): the code carries rank and the value can be read as a number. Energy’s Drained, Low, Fine and Charged are 1 to 4, and a Reading can take its Mean, Median, Min or Max. Steps are treated as equal.
- Not in order (nominal): the code is an identity only, never averaged; a Reading takes its Any, Count, Distinct, First or Last.
Turning In order on or off is an unsafe change: the form says so before saving, with how many entries hold an option (What a change does to entries). Taken out of order, a Reading saved by Mean, Median, Min or Max reads the trackable its own way instead of averaging codes that no longer rank (readingReduce in packages/core/src/reading.ts, through trackableDays), and the trackable’s own Reading falls back to its kind’s default in the same way.
A scale whose bottom option says there was none codes it 0, offered as Means none on a new option: a day of that option reads as none, for Any and for a streak, as a day of No does (zeroIsFalse, streakOf). A scale without one starts at 1, so a Low is still an answer. None is the code 0 rather than a flag on the option: on a scale in order the code already does arithmetic, and a mean headache of 0.4 means something only if None is 0, so a flag would say the same thing twice and could disagree; it is also the rule a yes/no’s No and a number’s 0 follow. A scale that has a none keeps it as an option rather than leaving it to a Nothing entry, since Mean leaves a day of Nothing blank and a mean headache would skip every day without one. Means none takes code 0 only while no option of the trackable holds it, retired ones included; an option already saved keeps its code, so it cannot become the none afterwards.
Codes are stable and only ever added to: a new option takes a code above the rest, and the form never changes a stored option’s code, so renaming, recolouring or reordering an option never rewrites an entry. Removing an option keeps it as a retired option (tombstoned), so the entries that hold it keep their label; those entries are stranded, and the trackable’s page offers to move them to another option or bring it back (Stranded Option Data).
Yes or no
A yes/no (boolean) stores 1 for yes and 0 for no in value_number, and nothing in value_text. Its words are presentation (true_label, false_label, Yes and No unless set, under “Words for true and false” in the form), read through one function, booleanLabel, so a calendar cell and a chart axis cannot disagree. A Reading of a day of them takes Any, Count, First or Last; over days, a rolling window’s Mean of it is a rate and its Sum a count of yes days. Whether a day with no entry means no is the trackable’s absence setting (Capture Model).
Category
A Category (a field or an attribute) picks from its own values, each with a stable id and a label, one per entry or item or several (many). A value that is merged or deleted is kept in retired with what it became, so a trackable’s entries keep the id they were logged with and read through it; a catalog’s items are rewritten instead.
Item
A Reference field picks an item of a catalog (catalog_id, or any catalog). Its attributes are not copied into the entry but read from the item as it is now. One item field at most can carry the entry’s amount (amount: true, “Logs an amount of it”): the entry then logs a quantity of the item in the item’s units, with the amount in its basis unit frozen as base (Entry), and what that amount contained is read from the item, which is what gives Diet its calories. An amount on a scale of its own (amount as a scale) is entered on a slider rather than in units: how strongly an emotion was felt. Strength’s and Read’s item fields carry no amount, so their entries only name the item. referenceSlotOf is the amount field where there is one, else the first item field.
A trackable whose stored schema is a whole reference, { kind: "reference" }, is read as the record with one required field, item, carrying the amount (asCurrentSchema), and its entries’ entity_id reads as that field. Nothing is rewritten until its definition is saved through the form.
Progress
A Progress field is where an entry started and stopped along the item another of its fields picks: a book’s pages. It names that field (along) and the item’s number attribute that says where the item ends (length, a book’s Pages), and stores { from, to }; an item without the attribute is read in percent.
Interval
An interval field is a start and an end, either of which may be missing, at most one per trackable. It dates the entry by the end it names, and its ends and length are the entry’s own start, end and duration, so every numeric path treats it as its length. A trackable whose stored schema is the interval kind itself is read as the record with one interval field, time. See Intervals.
Time
A time (kind: "time") is a clock reading, where in its day something happened, stored in value_number as seconds from that day’s midnight, negative for the evening before. It is numeric underneath, the way a yes/no is, and reads 06:42 rather than 24120. Only a derived formula produces one, by reading an instant (Time of Day); nothing logs a time directly, so the form does not offer it.
A clock time is a kind rather than a unit dimension: it has no unit to prefer or convert, and as a dimension it would turn up in the unit settings and the measure picker only to be filtered out. It sits on an interval scale, whose zero is a convention, so a zone over it reads a bound and a distance but never a percentage (scaleOf, which says the same of Celsius).
Why one set of kinds
A trackable’s entry and a catalog’s item are the same typed values, so Derived Trackables, Readings and every visualization read them the same way, and a field means the same thing wherever it is declared.
Related
Trackable · Entry · Presentation Layer · Capture Model · Stranded Option Data · Units & Dimensions · Catalog Entities · Data Model