Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion changelog.d/Fixed/fix-recurring-tracked-time.md
Original file line number Diff line number Diff line change
@@ -1 +1 @@
- **A repeating task's tracked time starts again with each occurrence.** Completing a repeating task, or skipping a habit, carried its tracked time over to the next date, so the next occurrence started with the old total against its estimate. Rows, the Time tab, the `tracked` filter terms, automation conditions and the assistant's `tracked_minutes` now count only the current occurrence's entries; the Time tab adds **All occurrences** with every entry's sum, and statistics and the CSV export are unchanged. Nothing is rewritten: past entries keep their place.
- **A repeating task's tracked time starts again with each occurrence.** Completing a repeating task, or skipping a habit, carried its tracked time over to the next date, so the next occurrence started with the old total against its estimate. Rows and the Time tab now count only the current occurrence's entries, and the Time tab adds **All occurrences** with every entry's sum. What scripts, assistants, routines and saved views read keeps its 1.0 meaning, every entry of the task: the assistant's `tracked_minutes`, the routine condition on tracked time and the `tracked` filter terms. The current occurrence is added alongside, as `tracked_minutes_occurrence` on repeating tasks and as the filter term `tracked occurrence` (in the builder, **All occurrences** turned off). Statistics and the CSV export are unchanged. Nothing is rewritten: past entries keep their place.
4 changes: 2 additions & 2 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1673,7 +1673,7 @@ Kinds: `created`, `completed`, `reopened`, `deleted`, `restored`, `rescheduled`,

### Time entries

A time entry (OpenSpec `time-tracking`, see [the guide](time-tracking.md)) is one stretch of work on a task, stored in the operation log in the task's sharing scope like a comment. Every route needs authentication (`401 unauthorized`). Reads are open to every token scope; the writes refuse `read` tokens with `403 insufficient_scope` before anything is written. An `agent` token starts, stops, logs and edits through the [agent tool registry](#agent-tokens) under the assistant switches and limits: its start is refused with `409 timer_running` while another of your entries runs unless the body carries `"stopRunning": true`, its PATCH reaches only entries the same token created (`403 not_permitted`), and its `DELETE` is `403 insufficient_scope`, so an assistant never removes tracked time. A task or entry you cannot read answers `404 not_found`, exactly as an unknown id. Viewers of a shared project cannot track; only the author changes an entry (`403 forbidden`), and the author, project admins and the owner may delete it. Writes wake your devices like every REST write.
A time entry (OpenSpec `time-tracking`, see [the guide](time-tracking.md)) is one stretch of work on a task, stored in the operation log in the task's sharing scope like a comment. Every route needs authentication (`401 unauthorized`). Reads are open to every token scope; the writes refuse `read` tokens with `403 insufficient_scope` before anything is written. An `agent` token starts, stops, logs and edits through the [agent tool registry](#agent-tokens) under the assistant switches and limits: its start is refused with `409 timer_running` while another of your entries runs unless the body carries `"stopRunning": true`, its PATCH reaches only entries the same token created (`403 not_permitted`), and its `DELETE` is `403 insufficient_scope`, so an assistant never removes tracked time. A task or entry you cannot read answers `404 not_found`, exactly as an unknown id. Viewers of a shared project cannot track; only the author changes an entry (`403 forbidden`), and the author, project admins and the owner may delete it. Writes wake your devices like every REST write. A task row carries no tracked total: sum its entries, or read `tracked_minutes` (every entry, as in 1.0) and, for a repeating task, `tracked_minutes_occurrence` (its current occurrence, since it was last completed or skipped) from the [MCP task tools](mcp.md); the filter terms `tracked` and `tracked occurrence` make the same distinction in saved views.

An entry is returned as `{id, taskId, userId, startedAt, endedAt, minutes, note, deviceId, taskTitle, projectId, projectName, focus}`: Unix milliseconds, `endedAt` `null` while it runs, `minutes` whole minutes up to the server time for a running one. `focus` is `{mode, session, plannedEndAt}` for an entry a [Focus](focus.md) work session wrote and `null` for any other (the Start button, the API, CalDAV, an import, entries from before 0.20): `mode` is `pomodoro`, `flowtime` or `countdown` (a newer app may write another value; show it as a Focus session), `session` the work session's number in the Pomodoro cycle (`0` for the other modes) and `plannedEndAt` when the stretch ends if nothing changes (Unix milliseconds, `null` for Flowtime). A user has one running entry: a start closes the running one at the new start in the same batch. `endedAt` before `startedAt` (on create or by a PATCH) is `400 invalid_input` naming both fields.

Expand Down Expand Up @@ -2174,7 +2174,7 @@ Rules (OpenSpec `automation-rules`; guide: [automations.md](automations.md)) are
| `PUT` | `/api/v1/me/rules/paused` | signed in, write | `{paused}` → `{enabled, paused}`: pause or resume all your rules |
| `GET` / `PUT` | `/api/v1/settings/automations` | owner | `{enabled}` → `{enabled, locked, locked_by}`; `409 set_by_environment` while `OPENTODO_AUTOMATIONS` is set; a change is recorded as `settings.changed` (`automations.enabled`) |

`trigger` is `{kind}` with `kind` one of `task.created`, `task.completed`, `task.reopened`, `label.added` and `label.removed` (with `label_id`), `assigned.to_me`, `task.moved` (`project_id`, optional `section_id`), `due.reached`, `overdue`, `schedule` (`at` `HH:MM`, `weekdays` 1 Monday to 7 Sunday), `manual` (only when run by hand). `condition` is `null` or an object of predicates: `project` (`{in, descendants?}` or `"none"`), `section`, `labels` (`{any?, all?, none?}`), `priority` (`{in}`), `due` and `deadline` (`"none"`, `"set"`, `"today"`, `"overdue"`, `{within_days}`), `start`, `evening`, `blocked`, `assignee` (`"me"`, `"none"`, `"anyone"`), `origin`, `habit`, `estimate`, `tracked`. `actions` holds the on-drop keys of a board column plus `due: {in_days}`, `deadline` (`{in_days}` or `"clear"`), `assignee` (`"me"`, `"clear"`), `comment: {text}`, `notify: true`, `webhook: {webhook_id}`. Lists hold at most 32 ids; days are 0 to 365.
`trigger` is `{kind}` with `kind` one of `task.created`, `task.completed`, `task.reopened`, `label.added` and `label.removed` (with `label_id`), `assigned.to_me`, `task.moved` (`project_id`, optional `section_id`), `due.reached`, `overdue`, `schedule` (`at` `HH:MM`, `weekdays` 1 Monday to 7 Sunday), `manual` (only when run by hand). `condition` is `null` or an object of predicates: `project` (`{in, descendants?}` or `"none"`), `section`, `labels` (`{any?, all?, none?}`), `priority` (`{in}`), `due` and `deadline` (`"none"`, `"set"`, `"today"`, `"overdue"`, `{within_days}`), `start`, `evening`, `blocked`, `assignee` (`"me"`, `"none"`, `"anyone"`), `origin`, `habit`, `estimate`, `tracked` (every entry of the task, a repeating task's earlier occurrences included, as in 1.0). `actions` holds the on-drop keys of a board column plus `due: {in_days}`, `deadline` (`{in_days}` or `"clear"`), `assignee` (`"me"`, `"clear"`), `comment: {text}`, `notify: true`, `webhook: {webhook_id}`. Lists hold at most 32 ids; days are 0 to 365.

Every rule representation carries `condition`, `actions` and `steps` (`null` when unset), `paused_reason` (`null` or `runaway`) and, for a routine holding steps that are not available now, `warnings` (`[{step, kind, reason, setting, message}]`); these annotations are dropped from write bodies. A rule's batch is written with device `rule:<rule-id>` (reserved: `/api/v1/sync` refuses it from devices) and the author as actor; the history labels it `Routine (<name>)` for its author, and `Routine (<name>) undone` for the batch of an undo. Limits: 60 runs a minute and 2,000 a day per account, 500 tasks per run; a rule refused three times in an hour by the per-minute limit is paused (`paused_reason: "runaway"`) until it is turned on again.

Expand Down
15 changes: 11 additions & 4 deletions docs/filters.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ term := "@" name label
| "assigned:" name the assignee is you (`me`) or that member
| "sector:" (sectorword | period) the task's time sector
| "workspace:" name the task's workspace
| "tracked" ("<" | "<=" | "=" | ">=" | ">") duration tracked time
| "tracked" ["occurrence"] ("<" | "<=" | "=" | ">=" | ">") duration tracked time
| field ("<" | "<=" | "=" | ">=" | ">") value custom field
| "no" field custom field without a value
field := a word that is not a reserved word | '"' chars '"'
Expand All @@ -50,7 +50,7 @@ weekday := "monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturd
keyword := "overdue" | "today" | "tomorrow" | "no date" | "no project"
| "no labels" | "no time" | "no deadline" | "no start" | "completed" | "open" | "subtask"
| "someday" | "deferred" | "evening" | "morning" | "afternoon" | "blocked" | "unblocked" | "available" | "habit"
| "origin" | "no origin" | "tracked" | "energy" | "no energy" | "notes" | "no notes"
| "origin" | "no origin" | "tracked" | "tracked occurrence" | "energy" | "no energy" | "notes" | "no notes"
| "sector" | "no sector" | "carried" | "assigned" | "unassigned"
sectorword := ("this" | "next") ("week" | "month" | "quarter"), with a space or a hyphen
period := YYYY "-W" WW | YYYY "-" MM | YYYY "-Q" N a week, a month or a quarter
Expand Down Expand Up @@ -124,8 +124,9 @@ text := word | '"' chars '"'
| `carried` | tasks whose sector's period has ended before your current week, month or quarter (carried over) |
| `sector: value` | tasks whose sector is `this week`, `next week`, `this month`, `next month`, `this quarter` or `next quarter` (named on your date and first day of the week, so `sector: this week` follows you into the next week without editing the view), or exactly a period: `sector: 2026-W44`, `sector: 2026-11`, `sector: 2026-Q4`. Any other value is an error that names it. The terms work whether or not **Time sectors** is on |
| `workspace: name` | tasks of the [workspace](workspaces.md) of that name, ignoring case (`workspace: Home`, `workspace: "Side projects"`); see [Workspaces](#workspaces) |
| `tracked` | tasks with at least one minute of [tracked time](time-tracking.md), every member's entries counted and a running entry up to now; a repeating task counts its [current occurrence](time-tracking.md#tracked-against-estimated) only |
| `tracked` | tasks with at least one minute of [tracked time](time-tracking.md), every member's entries counted and a running entry up to now; a repeating task counts every occurrence's entries |
| `tracked <cmp> duration` | tasks whose tracked time compares as asked: `tracked > 1h`, `tracked <= 30m`, `tracked >= 1h30`, `tracked < 1.5h`, `tracked = 0`; a bare number is minutes |
| `tracked occurrence`, `tracked occurrence <cmp> duration` | the same, with a repeating task's [current occurrence](time-tracking.md#tracked-against-estimated) only: the entries since it was last completed or skipped, the time its row shows against the estimate. `tracked occurrence > 1h` on a task that does not repeat is `tracked > 1h` |
| `name <cmp> value` | tasks whose [custom field](custom-fields.md) of that name compares as asked (see [Custom fields](#custom-fields)) |
| `no name` | tasks of a project that defines the field, with no value for it |

Expand Down Expand Up @@ -222,7 +223,7 @@ You can also type a query straight into the [search box](search.md) (<kbd>Ctrl</

In the filter panel, above the query input, the **builder** shows the same query as conditions you can read: *Label is errand*, *Due on or before tomorrow*, *Priority is P1*, *No project*.

- **Conditions.** **+ Condition** opens a small dialog: choose what to filter on (label, project, priority, one of the five dates, a state such as *Due earlier* or *Waiting on another task*, text in the title or notes, origin, workspace (with more than one), tracked time, energy, time sector or a custom field), then its value. Labels and projects are picked from a searchable list. Click a condition to change it.
- **Conditions.** **+ Condition** opens a small dialog: choose what to filter on (label, project, priority, one of the five dates, a state such as *Due earlier* or *Waiting on another task*, text in the title or notes, origin, workspace (with more than one), tracked time (turn off **All occurrences** for a repeating task's current occurrence), energy, time sector or a custom field), then its value. Labels and projects are picked from a searchable list. Click a condition to change it.
- **Groups.** A group is a block headed **All of these** (every condition must match, `&`) or **Any of these** (one is enough, `|`). **+ Group** adds one inside, up to three levels deep. Change the heading to switch between the two.
- **Not.** The **Not** switch in a condition's dialog, or the **Not** button on a group, matches the tasks it would otherwise leave out (`!`).
- **Keyboard.** Tab reaches every control. Inside a group, the arrow keys move between conditions, and Delete or Backspace removes the focused one.
Expand Down Expand Up @@ -432,3 +433,9 @@ no notes & p1
```filter
assigned: me | unassigned
```

### 32. Repeating chores that ran over this time

```filter
tracked occurrence > 30m & no date
```
Loading
Loading