Time Tracker
How the Time Tracker extension is built: timers and time entries as rows of a note's timeEntries (TaskNotes' shape), totals per task, project, day and week, a timesheet view type for Bases, a report, a status item, a capture target, and the architect.time service Projects and Tasks use.
The Time Tracker (extensions/time-tracker, crate architect-time-tracker,
id time) times your work on tasks and projects and adds it up. It is
the third member of the Project Management bundle (#375), beside
Projects and Tasks.
This page walks through how it is built, in the order you would build one
like it.
1. An entry is a row of a note’s timeEntries#
Task keeps two kinds of tracked time: task::TimeEntry, an inline list in
a task note’s frontmatter (task:features/task/task-proto/src/model.rs
TimeEntry), and WorkSession, a database row with a billable flag, a
rate and an org (task:features/timer/timer-proto/src/session.rs). An
extension reaches notes only, so the Time Tracker keeps the first, in
TaskNotes’ shape, and carries what it can of the second:
---
type: task
title: Mix the album
projects: ["[[Projects/Album/project|Album]]"]
timeEntries:
- startTime: 2026-11-06T09:00:00Z
endTime: 2026-11-06T10:30:00Z
description: Rough mix
billable: true
- startTime: 2026-11-06T14:00:00Z
---
- An entry sits on the note the time was spent on: a task, a project’s
project.md, orTime/Unfiled.mdfor time on neither. - The entry with no
endTimeis the running timer. One runs at a time: starting one stops the other, as Task’sswitch_timerdoes. - Billing is SolidTime’s, read off the project note with Task’s keys:
billableDefault(a new entry on the project or its tasks is writtenbillable: true; Task snapshots it at start too),defaultRateCentsandcurrency. - Rows it cannot read, and keys it does not know, are written back as they were.
The model is extensions/time-tracker/src/model.rs: Entry
(from_value, to_value, seconds, seconds_in), appended and
stopped (the list with an entry added, or every running one ended),
Tracked (a note with time on it, project_path), parse_duration and
log_line (what a person types: 1h30m, 45m, 1:30, for:1h30),
Span (today, week, last week, month), running and report. It is
pure: the clock and the time zone are arguments.
2. It reaches the vault only through the host#
extensions/time-tracker/src/vault.rs is the only place that touches
notes, through linked::Handle::host with HostRequest::Run:
rowsfinds every note with time on it, and every project, in oneBASES_ROWSquery (tracked_base:file.hasProperty("timeEntries"),type == "project"orfile.name == "project"). Projects come along so their rates are known before their first entry.propertiesreads one note (VAULT_READ) right before it is written, so a write never works from a stale index.setwrites the wholetimeEntrieslist back (VAULT_SET_PROPERTY).
The manifest says so: [capabilities] actions = ["VAULT_", "BASES_", "EXT_VIEW_OPEN"]. The process copy (src/main.rs,
Guest::extension(&TimeTracker).serve()) works as the linked one does.
3. Its actions are its API#
Every action is a method of architect.time ([provides]): method
start is TIME_START.
| action | does |
|---|---|
TIME_START {task? | project? | note?, description?, billable?} | a timer on the note (Time/Unfiled.md when none is named); a running one stops first |
TIME_STOP | ends the running timer |
TIME_ACTIVE | the running timer, or null |
TIME_TOGGLE | its status item’s press: stop, or open the report |
TIME_LOG {…, duration? | minutes? | start+end, date?} | a finished entry by hand (Task’s log_session) |
TIME_CAPTURE {title, body, properties, …} | the time capture target’s action (below) |
TIME_TOTALS {paths?} | each note’s seconds in all, today, this week, and whether it runs |
TIME_FOR_PROJECT {project} | a project’s time, its own and its tasks’, billable seconds and the amount, the running timer, each task’s |
TIME_REPORT {range? | from+to} | totals per day, per note and per project over a range |
TIME_OPEN {view?}, TIME_TIMESHEET | open the report, or the timesheet |
4. Working with Tasks and Projects#
It [uses] architect.tasks and architect.projects, and runs alone
without them:
- Naming:
TIME_START {task: "[[Mix the album]]"}finds the task through Tasks’read, and{project: "Album"}the project through Projects’resolve(targetinextensions/time-tracker/src/lib.rs). Alone, a path or a link with a folder works as it is. - A task’s time counts toward its project (
projects_of): itsprojectslink with a folder, else the project Projects resolves a bare link to, else the project whose folder it sits in.TIME_FOR_PROJECTadds the tasks Tasks’for_projectnames. - The other way round, Projects and Tasks use
architect.time: the project page’s Time section (extensions/projects/src/lib.rsTimeSection: in all, this week, today, billable and its amount, the running timer, a Start button) and its task lines’ time and ▶ button (TaskLine); the Tasks list’s rows (extensions/tasks/src/lib.rsTaskRow) and each task’stimeinTASKS_LIST,TASKS_READandTASKS_FOR_PROJECT(refresh_time). Without the Time Tracker they show what they showed before.
5. Views and the status item#
report(ReportView): range chips (today, this week, last week, this month, all), the running timer with its clock and Stop, a “Log time” field (45m Call with the label [[Album]]), the totals and amounts, then by day, by project and by task or note, each row’s ▶ timing it.timesheet, a view type for Bases ([[base_views]] id = "timesheet",Builder::base_view("timesheet", Timesheet)): a week, a row per note with time in it and a column per day, totals on the right and at the bottom; ‹ and › page through weeks.TIME_TIMESHEETwritesTime/Timesheet.baseonce and opens it, and any base can show its notes as a timesheet.- The status item (
[[status]] id = "timer",status_text) says what is running and since when (Mix the album · since 09:00, beside itstimericon), or today’s total; a press runsTIME_TOGGLE. It shows a start time rather than a ticking clock, so it stays true between reads. - The navigator (
extensions/time-tracker/src/nav.rsTimeNav, while the report shows): Running, Today, This week and Earlier, each note in the one it was last worked on; drag one onto Running to time it.
6. A capture target: “Log time”#
[[capture_targets]] id = "time" puts “Log time” in architect’s composer
and inbox deck, with a for: token: Call with the label for:45m [[Album]] today logs 45 minutes on Album today, described as “Call with
the label” (capture). A length typed in the title (45m) works too.
Its tour ([[tours]], extensions/time-tracker/tours/time-tour.styx)
walks the report, logging time, the totals, the sidebar by project and the
timer’s status item, running only TIME_OPEN; it starts no timer.
7. Tests#
extensions/time-tracker/src/model.rsandextensions/time-tracker/src/nav.rs: entries, durations, spans and reports (across local midnight in a fixed time zone), the navigator’s groups; pure.extensions/time-tracker/tests/vault.rs: the extension in a host whose app list has the realVAULT_*,BASES_*andCAPTURE_*actions over a folder, linked in and then as its process, every entry checked in the markdown; then with Tasks and Projects, names resolved through their services and the project page’s and task list’s time read througharchitect.time.
Status and what’s next#
Done: timers and entries, totals per task, project, day and week,
billable time and amounts from the project’s rate, the report, the
timesheet view, the status item, the navigator, the “Log time” target,
the service, and its use by Projects and Tasks. Next: per-member rates
(Task’s ProjectMemberRate, OrgMemberRate) when orgs carry members,
editing and deleting an entry from the report, reporting to Finance
(#370) when it is installed, a bundle manifest when #367 lands, and the
same in Studio.
Linked from
- First-party extensionsThe extensions architect ships (#368), each a model of how one is built: a linked extension that also runs as a process, its data as vault notes, its API as a service.
- ProjectsHow the Projects extension is built: a page on architect's built-in project kind, project types through [[project_types]], goals and milestones as notes, and the architect.projects service.
- TasksHow the Tasks extension is built: tasks as vault notes in TaskNotes' shape, reached through the host service; its list, Today, My tasks and a kanban view type for Bases; the architect.tasks service.
- CalendarHow the Calendar extension is built: events as vault notes in Task's shape, repeats with exceptions, day, week, month and agenda views with drag to move and resize, tasks and tracked time on it, calendar and timeline view types for Bases, a capture target, a navigator provider, and the architect.calendar service.