Some things are done as a session: a workout, a meal, a morning routine, a bath with a book. An Event is an entry that is one of those sessions, holding the entries done during it. A Workout holds a ten-minute stretch, a jog and fourteen sets; a Meal holds the foods eaten at it. Every part stays an ordinary entry of its own trackable, read and charted as it always is, so a set logged inside a Workout counts toward Daily Volume exactly like one logged on its own, and brushing your teeth needs no hygiene catalog to sit beside a shower. The Event adds only that they belong together, and when. It counts itself in its kind, how long and how often, and never its parts a second time.
An Event’s trackable is its kind: Workout, Meal, Morning routine. Its plan is a template: Workout is the kind, and Push, Pull and Legs are its templates.
An entry made of parts of its own trackable (Egg & Cheese on Diet, holding the egg and the cheddar) is not an Event: it is a thing, not a stretch of time, and it is read by its trackable’s own rules (Templates).
A trackable that logs Events
Any trackable can log Events: the trackable form’s Events setting, in its Cadence section, says so (capture.routine, read by isRoutine in packages/core/src/routines.ts), and warns how many entries would show on their own if it is turned off. An Event is an interval from Start to Stop, dated by its start, with nothing else of its own, so its kind’s own Reading is its length. The modules whose entries come in bundles make one (Modules): Strength a Workout, Food & drink a Meal, Hygiene a Morning routine. These are not asked by the Rundown, since what they hold is; none of them puts an Event inside another, so a Workout holds its sets directly.
What a kind usually holds. With Events on, the form’s Usually holds names Tags and trackables, mixed (capture.offers): a Bath names Hygiene and Read. A Tag counts with every trackable under it at any depth (offeredParts in packages/core/src/routines.ts). It orders and never restricts: what a kind usually holds is what it has held before together with what it names, offered first by Add and by Logged while it ran, and anything else can still go in. A new kind with no history starts from what it names.
The link
An entry’s part_of names the Event it is part of. It is a nullable column with no foreign key: sync drops a row whose parent is gone, and a part must outlive its Event, so the link is a plain id, like an item’s id inside a value. It syncs like any other value and never blocks a delete. The rules, held by the entry repository on every single write:
- One Event per entry. An entry is part of at most one Event, so the links form a tree. (A trackable can carry many tags; a thing done happens inside one occasion.)
- Events nest. A Bath can be part of an Evening routine, and reading can be part of the Bath.
- Refused: a link to an entry that is not there, to an entry of another trackable that does not log Events, to the entry itself, or one that would make a loop, checked by a recursive query up the chain. Bulk writes (a seed) are left to their builders, and an undo that cannot make its link puts the entry back on its own.
- Read leniently: a link to anything that holds nothing reads as in no Event (
holderOfinpackages/core/src/containers.ts). A part added on one device while another deletes its Event survives on both, in no Event. - A part keeps its own time. One that falls before its Event began or after it ended is allowed, and is an issue, Outside its event (Issues); a part at the start or at the end is inside.
- Deleting an Event asks first, counting what it holds at every level, and either leaves the parts as entries of their own or deletes them and theirs. Undo puts the Event back under its own id, with its parts in it or with the ones it took. Deleting a trackable that logs Events leaves their parts as entries of their own, and removes the template lines of other Events’ kinds that log it.
Logging into an Event
- While an Event is open, anything logged elsewhere goes into it by default. Open here is what the bar on every screen shows (
runsNow, Intervals), so an Event left running past its stale limit takes nothing unasked: the bar shows it as never ended, with End at, Issues report it first, and its span ends at its start until it is ended (Intervals, Issues). Where several are open, the latest begun is the one (a Bath inside an Evening routine); a new Event is not put inside another unasked (runningRoutine). The log panel shows the Event as a chip whose cross opts out, and the undo toast can put the entry back in. Every form that writes a new entry carries the Event in that write. - An entry’s edit form has an Event field to put it right, offering the Events running or covering its time first, then that day’s others, latest first, never the entry itself or anything inside it (
routineChoices). - Bundling afterwards: the day sheet’s “Put some into an event” ticks entries and puts them into a new Event spanning them, or into one already logged that day.
An Event’s page
An Event has its own page (/event?id=), which starting one opens, and which the Calendar’s popover, the day sheet and its own form open too. It shows when it ran, Edit (its times) and Stop, what it holds read by each part’s trackable, and what it holds as one list (SessionList; what it does to its plan and entries is session-plan.ts, written by useSessionActions, and what ends each row is rowEnd in row-state.ts):
- Planned and logged are the same rows. Lines planned from a template and entries logged are rows of one list. Ticking a planned row logs it; unticking takes the entry away and leaves the row planned with its values. An interval row (a stretch) is started and stopped in place and is then marked logged, rather than offered to start again.
- Rows group by trackable and item: consecutive rows of Bench press are one group, a heading over Set 1 and Set 2, and a circuit going round its exercises is a group each time round. A group of one is one line.
- Rows take their trackable’s shape from its definition alone: an amount’s quantity beside its unit (1 serving, with the food’s units to pick from), number fields beside their unit or name (8 reps, 75 kg), an interval with its planned length. A row shows as many fields as fit, in the order of required fields nothing can fill, then required fields a fill or the item supplies, then optional ones, and opens the whole entry’s form beneath it (
row-fields.ts). A mark on a row says a hidden required field is empty. - Values are ready before a tap: a planned row starts from its plan, or, where an earlier row of its group was planned the same and done otherwise (a set at 70 kg where 75 was planned), from what was done (
draftValues). - Add another adds a row to a group whose trackable has no amount and no length, a set; a stretch is made longer instead. Add searches for anything to log in it, what this kind of Event usually holds first, never the Event itself or one it is inside. Arrange reorders the plan by its grips and takes rows away. A template added later asks first, listing what it adds, then lights and scrolls to its rows.
- An Event inside it is a section of the same list, never a page of its own, and so is an entry standing in for a Template, an Arm Circuit in a Workout: a heading by its Template’s name over its sets. Which lines hold the lines a level deeper is one rule,
holdsBeneathinroutines.ts: an Event kind’s, or one standing for a Template (stands_for). - A Template of another trackable comes in as its own block (
templateBlock): a line standing in for it, its lines a level deeper. The first of them logged writes the Arm Circuit, naming its Template and holding no value, at the moment that row is logged, and the row goes in it. Add from a template offers this kind’s templates first, then every other trackable’s. - Logged while it ran, not in it offers what was logged during the Event and is in no Event (not another Event itself, nor a Disruption), one at a time or all: first what this kind usually holds, the rest behind “Show N more”. Nothing is ruled out.
- Stop asks first when planned rows were never logged, and drops them once stopped: its own and those of everything inside it, an Event inside that already ended included. Stopping an Event stops what is open inside it, at the same moment (
useStopInterval).
The plan of a running Event is kept with it in the routine:plan setting: which lines it was planned with, rows added, values changed, and which entry each row was logged as.
While any interval is open, an Event included, a bar on every screen says what it is, since when, how long, and how many entries an Event holds, with Stop and a way back in; it is left off an open Event’s own page.
Templates across trackables
A template of an Event’s kind is lines of other trackables’ entries (template_line.trackable_id, a plain id, so a line naming a trackable that is gone is skipped), each with its item and values, a line one deeper going into the Event line above it (depth). Starting an Event with a template logs it and plans its lines; logging one after the fact lays its lines out in order, each interval starting where the one before ended and lasting as long as the line keeps (entriesFromLines). An Event can be saved as a template (linesFromParts), what it held becoming the lines with their items, values and lengths, and a kind’s page lists its templates. Chad’s Workout has Push, Pull and Legs, and his arms days hold an Arm Circuit, a template on his Set.
Reading Events
A Reading can filter on Events: entries that were part of an Event of a given kind, or of none (Readings). Read only in the Bath, or read anywhere else. An Event’s own length is its kind’s own Reading. Every list shows an Event as one row opening to its parts, and the Calendar’s Week and Day views draw it as one block with its parts inside it; a part shown on its own says which Event it was in (Calendar Drill-down, Data Table).
Related
Readings · Intervals · Templates · Issues · Modules · Exercise & Strength · The Rundown · Entry