Datler only becomes useful if a lot gets logged, so logging has to be part of whatever you are doing rather than a chore set apart from it. Capture is built around that: a trackable is one search away from anywhere, an entry is one action once it is found, and the same panel logs it on every surface, so there is one way to learn and one place to fix.

Principles

  • One action to an entry. Find the trackable, the value editor opens ready, Log. The entry is logged at the app’s moment, Now unless it has been set (The Moment).
  • Keyboard first. The command palette logs without leaving the keyboard, and Enter logs.
  • Staged, then logged. Picking a value only stages it; nothing is written until Log is pressed, so a stray tap on a chip never logs something you did not mean.
  • Fast for everything, and always a way to be correct. A food, a set or a record logs in place like a number does, and when the item is not in the catalog it can be added from the log without leaving it.
  • One moment for backfill. Filling in a missed morning is one setting of the moment in the top bar, not a time per card.
  • Responsive. Every surface works at phone width with touch-sized targets (Entry Panel covers how it holds still when the keyboard opens).

Surfaces

Every place an entry is logged shows the same entry panel (Entry Panel lists them). The command palette is the primary one.

The command palette

The palette (apps/renderer/src/components/capture/QuickAddPalette.tsx) opens on Ctrl+K, or ⌘K on a Mac, from anywhere in the app, an open dialog included, since logging something else mid-edit is what a palette is for (App Shell lists the app’s chords). It has two modes on one bar, toggled with Tab:

  • Log searches the trackables you log and, once something is typed, the Templates by name, each naming its trackable. Choosing a row opens the entry panel inline under it, a template’s started with it (Entry Panel says how it sits there, and how the palette holds still on a phone). Text that matches no trackable is offered as Add ”…” to the Inbox (Inbox).
  • Go to searches trackables and pages, and, once something is typed, the appearance settings (“Mode: Light”, “Accent: Teal”) from the Theming System’s axes.

Ranking

A row’s place is decided by three things in turn:

  1. How well the name matches: a prefix beats a substring, which beats a subsequence (score). Typing stays predictable: usage never lifts a worse match over a better one.
  2. How much it is logged, usageRank in packages/core/src/usage.ts. Each entry adds 0.5 ** (daysAgo / 14), so today counts 1, a fortnight ago a half and a month ago a quarter. One decayed number, rather than a recent list and a frequent list, because the two disagree (a thing logged once yesterday is recent and not frequent) and a decay lets a daily habit beat both. It reads happened_at, not recorded_at: the palette ranks what you are tracking, so a month of weigh-ins backfilled in one evening does not pin Weight to the top for weeks. A future happened_at counts as today.
  3. The name, so the order is total.

With nothing typed every trackable matches equally, so usage is the whole order. Ranking applies to Log only; Go to navigates, and how often something is logged says nothing about where you want to go.

Enter logs

Focus stays in the search field while a row is open, so a key handler inside the panel would never see Enter. The panel hands its Log to the palette instead (commitRef): ⏎ presses it, and ⌘⏎ (Ctrl+⏎) logs and keeps the palette open to log another. Enter is bound for a trackable with a single action to bind: a value, words, a catalog item or a moment (entersLog). An interval needs a choice of its ends and a record a filled form, so neither binds it. The footer’s legend is derived from what is bound for the selected row, so it never advertises a key that does nothing, and it is shown only where the app shows key hints.

One editor per value type

ValueEditor (apps/renderer/src/components/capture/ValueEditor.tsx) is the one place a scalar value (a number, a choice from options, a list word, a yes/no, text) becomes an input: a number stepper or slider, coded chips or an ordinal scale, list chips, a yes/no, a text box. It is rendered in two places: the entry panel, which every logging surface hosts, and the entries table’s inline cell. It is input-only: it owns the draft, the encoding for each kind and whether the draft can be logged, and reports { draft, canLog } up; each surface keeps its own buttons and layout and hands the draft to logItem, which converts it from the unit on screen to the stored one. That is why a coded value cannot be stored as text, and why a yes/no is written as the number 1 or 0 only (booleanValue in core is the one resolver).

A number’s quick-add chips set the draft to their amount (1, 2, 8), on the panel and in the table cell alike. A list that is not strict offers a free-text “other” beside its choices on every surface, so a new word can be logged anywhere.

An interval, an amount of a catalog item and a record with no value field are not scalars, and the entry panel enters them with their own fields (Entry Panel). isScalarKind decides it from the kind of the value field (ownSchemaOf), so a record whose value field is a number or a choice is entered here like a plain one.

Where a new value starts

A new number starts from its trackable’s constant default when it has one, else its latest value when it asks for Prefill, else its first quick-add amount, else its step (numberSeed); an edit starts from the entry’s own value. A new record with Prefill starts at the item still underway, the most recent one not Finished and not Dropped where a Progress runs along it, else the latest entry’s (prefillItem), and its plain fields start from that item’s latest entry, its amount with it (prefillValues in packages/core/src/prefill.ts). Picking another item starts the plain fields from that item’s own latest entry, filling only what is empty or what Prefill put there, so a Template’s planned values and anything typed stay. A Progress continues where it stopped (nextFrom), and a live session starts now. An interval logged afterwards, where the moment fills its end, starts the last one’s length before it, ending now (clockBefore); its “last” chip still sets that length by hand. A choice, a scale, a yes/no and words start at the last value (lastOwnValue), so Log logs it again in one tap. Prefill is offered for any trackable with a value to start, everything but a moment and a clock time (takesPrefill in packages/core/src/value-schema.ts, the one copy). Limits are checked in the unit on screen before Log is offered, with the reason under the editor. Log is disabled until a loggable value is staged; a number draft of 0 cannot be logged, and the button then reads Logged.

Foods, sets and records log in place

A catalog item (a food, an exercise) and a record log in the same panel as a number: the panel hosts the item picker with its amount and unit, or the record’s fields, so no surface sends you to another page to log them. The picker is the catalog’s own table, with the catalog page’s filter, sort, group and view, in a popover on a pointer and a sheet on a touch screen (Catalog Entities describes it).

When the item is not there, Not in the catalog opens the catalog’s own item form inside the log, with the name already typed; saving it adds the item and picks it with the amount a person would log (Diet and Nutrition). A missing option teaches you to go to the catalog; a dead one teaches you the app is broken, so the way to add what is missing is always live.

A picker inside a log dialog portals its list into the dialog, since a dialog’s scroll lock and focus handling reach only its own subtree (Jeliroco Components has the rule). So the dialog’s own content never scrolls, or it would clip the list and drag the card with it: the log dialog scrolls an inner box, and the list paints over it (apps/renderer/src/components/home/LogDialog.tsx). The general shape: a portal escapes the DOM tree but not the behaviours attached to it, and the symptom is always a thing that is there and does nothing.

Outside the app

Capture happens inside the app. There is no operating-system shortcut, tray entry or widget that logs without opening it.

Entry Panel · The Moment · The Rundown · Inbox · Entry · Trackable · Capture Model · Diet and Nutrition · Page Specs