A Category is a field whose value is picked from a list it keeps: Food type with Fruit, Vegetables, Meat & poultry, Sweets and the rest; Origin with Homecooked, Restaurant and Takeout, which the demo profile Chad adds to his Food catalog; Body area for exercises. A catalog declares as many Categories as it wants, each an independent axis, and every catalog can, so nothing about them is about food. A trackable’s own record fields can be Categories too.

This is faceted classification: several named axes, each with its own values, rather than one tree of folders. It is what tools that group arbitrary items offer (Notion’s selects, Drupal’s vocabularies, Lightroom’s keyword axes), and what food trackers with a fixed set of tags do not. Tags group trackables; Categories describe catalog items.

Shape

  • A list of values, each { id, label, color? }, and many: whether an item takes one value or several. Food type takes one; a Category used as tags takes several.
  • An item stores ids: one id, or a list when it takes several. Renaming a value changes the list alone. An id is a slug of the value’s first label, suffixed on a clash (homecooked, then homecooked-2), which keeps stored data readable and lets a migration compute it.
  • A value the list does not have is refused on every save of an item and of its catalog.
  • A Category belongs to its item. An item based on another borrows none of its Categories; based on lends numbers only.

Editing the list

Values are added, renamed, recoloured, merged into another and deleted, and a Category can switch between taking one value and several (CategoryEdit in packages/core/src/category.ts). What a merge, a delete or a switch does to what holds the value depends on where it lives:

  • In a catalog, the items are rewritten: a merged value becomes the one it was merged into, a deleted one is taken off. Items are written before the list, so a failure in between never leaves an item holding an id the list no longer has (catalogRepo.editCategory).
  • On a trackable’s field, entries are never rewritten. A merged or deleted id stays in the list as a retired id and is read through on the way out: a retired id reads as the value it was merged into, a deleted one as nothing, and a stored value as one or several as the Category takes them now (categoryNow).

The catalog form offers Category as a field type with its values editor beneath it, and an existing text or tag field can be turned into a Category: its distinct values become the list and the items are rewritten to hold their ids (convertToCategory). A row in the form changes only what it edits, so a field keeps the properties the form does not show, such as a density or the names a label prints.

Picking values

The item form shows each Category as a group of toggle chips in the values’ colours, several pressable when it takes several, with a New chip that adds a value on the spot and selects it; the chips grow to touch size on a phone.

Browsing by Category

  • On a catalog’s page each Category is a column. It is a facet by its values’ labels in the list’s order, with “No value”. One that takes one value sorts and groups in the list’s order; one that takes several does not sort, and groups an item under each of its values. The list groups under labelled headings by a Category, then by a second field within each group. Values show as coloured chips in the table and the cards (The catalog page and picking an item).
  • In the item picker, which is the catalog’s own table under the same toolbar, a Category filters, sorts and groups as it does on the page. An option shows the catalog’s main Category by default (mainCategory): the one its bundled dataset’s grouping fills (Food type, Body area), else the first Category that takes one value.
  • On a Catalog Scatter a Category colours the points.

A derived trackable can read only some items

An entry selection can carry a Category filter on the item the entry names: a field, some of its values, and optionally not. An entry counts when its item holds one of the values, or, with not, when its item holds a value and none of these; an item with no value is unknown and left out either way. The days the trackable answers on stay those of the unfiltered reading. So Chad’s Calories from sweets is Diet’s calories where Food type is Sweets, and his Ate out is any Diet entry whose Origin is not Homecooked. The trackable form offers the filter beside the time window, for the Any and Aggregate formulas, over a trackable whose item field carries the amount (Diet, Emotions). Stress is the same filter written by its module: Emotions whose Family is Fear or Anger (Emotions). See Derived Trackables.

Bundled data keeps its groups

A bundled dataset’s grouping fills a named Category (STARTER_GROUP_FIELD). The Food catalog’s Food type holds the USDA bundle’s groups plus Meals and Sweets, which the typical foods use. The Exercise catalog’s Body area holds its bundle’s groups, and its Equipment, Type, Force, Mechanic and Level are Categories over the values the bundle uses. starterAttributes is the one path a bundled row takes into a catalog, at install and at Import: a Category written as text is read by its label, and nothing the catalog does not declare is kept, so a profile that made its own Food type keeps it.

Code

  • packages/core/src/category.ts: the list’s edits, categoryProblem, categoryNow, categoryFromText, mainCategory.
  • packages/core/src/catalog-repository.ts: editCategory, convertToCategory; packages/core/src/starter-data.ts: starterAttributes; packages/core/src/catalog-datasets.ts: STARTER_GROUP_FIELD.
  • packages/core/src/derived-spec.ts: an entry selection’s categories; packages/core/src/list-steps.ts: how the filter holds.
  • apps/renderer/src/components/catalog/CategoryPicker.tsx, CategoryValuesEditor.tsx, CatalogForm.tsx, useCatalogTable.tsx; apps/renderer/src/components/fields/FieldsEditor.tsx for a trackable’s fields.

Catalog Entities · Catalog Scatter · Derived Trackables · Diet and Nutrition · Exercise & Strength · Emotions · Glossary