mirror of
https://github.com/logseq/logseq.git
synced 2026-05-16 17:02:34 +00:00
* fix: honor the three documented repeater cookie semantics
Logseq's documented repeater semantics (per docs.logseq.com and
`logseq/docs` `Tasks.md`) define three org-mode-style cookies for
recurring tasks:
`.+`: repeats from the last completion date
`++`: advances from scheduled, skipping in whole intervals to future
`+`: advances from scheduled by the declared interval (can stack)
The scheduler in `worker/commands.cljs` has been ignoring the cookie
entirely and applying a single, `++`-like semantic for every
recurring task. A user who wrote `.+1w` in markdown — expecting "a
week from when I actually finished" — silently got `++1w` behavior
("a week from the original scheduled date, skipped to future"),
which for a weekly task scheduled 2026-04-01 and completed on
2026-04-05 returns the next occurrence on 2026-04-08 rather than
the documented 2026-04-12.
This change:
* Adds a closed-values `:logseq.property.repeat/repeat-type` property
with values `:dotted-plus` / `:plus` / `:double-plus`. Default is
`:double-plus` so existing recurring tasks see no behavior change
on upgrade.
* Rewrites the scheduler to branch on repeat-type and implement each
semantic: `.+` anchors on now; `+` advances from original once (can
stack overdue, per org-mode); `++` iterates in whole intervals
until strictly after now. The `++` path is mathematically
equivalent to the previous scheduler, so default-path behavior is
preserved.
* Guards against frequency <= 0 — the old code would silently produce
nonsense and, under the new `++` loop, would spin forever. The
guard short-circuits to `nil`.
* Extracts `resolve-recur-frequency` and fixes the previous
`(or [A B] [C D])` pattern in `compute-reschedule-property-tx` —
any 2-vector is truthy in Clojure, so the default-value branch
was unreachable and entities without an explicit
`:recur-frequency` silently fell through to `frequency = nil`.
`if-let` makes both branches reachable so default-to-1 actually
works at migration time.
* Restores the cookie-type selector that was removed from the
date-time popover in `0a5b88467` (Nov 2020) — in-code support for
all three cookies has been present but not user-pickable for the
last ~5.5 years.
* Adds `docs/recurring-tasks.md` — a technical spec for contributors
and users that restates and augments the upstream `Tasks.md` text,
adds decision guidance, and documents the implementation surface.
* Extends the file-graph → DB-graph migration (built on top of
`44d6bd49c4` "fix: preserve repeated schedule import") to also
carry the cookie kind via a new `repeat-types` map in
`graph-parser/exporter.cljs`, so an imported `.+1w` task lands in
the DB-graph with `:repeat-type.dotted-plus` rather than picking
up the `:double-plus` default. Test updated to assert this.
* Adds deftests covering each cookie's distinctive behavior plus
boundary cases (non-positive frequency, unknown unit, frequency > 1
variants, `++` at month/year units, and both branches of
`resolve-recur-frequency`).
The preexisting `get-next-time-test` passes unchanged under the
`:double-plus` default, preserving the existing regression contract.
Tests pin `t/now` via `with-redefs` for determinism.
Refs #7731, #11260, #6715, #8531. Folds in the small remaining delta
from #12532 (now closed as superseded by `44d6bd49c4`).
* fix: harden recurring task repeat type
* fix: contain repeat type selector
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* fix: handle clamped monthly repeats
---------
Co-authored-by: Tienson Qin <tiensonqin@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
130 lines
4.9 KiB
Markdown
130 lines
4.9 KiB
Markdown
# Recurring Tasks
|
|
|
|
Logseq supports recurring tasks with three scheduling semantics, modeled
|
|
on org-mode's three repeater cookies. Picking the right one lets you
|
|
say exactly how the next occurrence should be computed when you mark
|
|
a task DONE.
|
|
|
|
This document is the reference for contributors working on the
|
|
scheduler and for users who want the full behavior specification. The
|
|
short, user-facing version lives in
|
|
[logseq/docs `Tasks.md`](https://github.com/logseq/docs/blob/master/pages/Tasks.md).
|
|
|
|
## The three semantics
|
|
|
|
### `.+` — Advance from completion
|
|
|
|
> *Repeats from the last time you marked the block done.*
|
|
|
|
The next occurrence is one interval after the moment you marked the
|
|
task DONE, regardless of what the original scheduled date was.
|
|
|
|
**Best for:** habits and cadence-driven recurrences — things where the
|
|
clock restarts the moment you finish.
|
|
|
|
**Example:** A weekly task scheduled for 2026-04-01, completed on
|
|
2026-04-05, will next appear on 2026-04-12. If you'd forgotten and
|
|
completed it on 2026-04-20, it would next appear on 2026-04-27. The
|
|
interval is anchored to your completion, not to the calendar.
|
|
|
|
### `++` — Advance from scheduled, skip to future
|
|
|
|
> *Keeps it on the same day of the week.*
|
|
|
|
The next occurrence starts at *original + one interval* and keeps
|
|
advancing in whole intervals until the result is strictly in the
|
|
future. Because the arithmetic is in UTC and advances by whole weeks
|
|
(or months, or years), weekly recurrences naturally land on the same
|
|
day-of-week as the original.
|
|
|
|
**Best for:** calendar-anchored recurrences where completion is a side
|
|
event and the anchor matters — "every Monday standup," "monthly review
|
|
on the first of the month."
|
|
|
|
**Example:** A weekly standup scheduled for Monday, 2026-04-06:
|
|
- Completed Wednesday, 2026-04-08 → next occurrence Monday, 2026-04-13.
|
|
- Completed Friday, 2026-04-17 → next occurrence Monday, 2026-04-20.
|
|
|
|
**This is the default** when a recurring task has no explicit cookie.
|
|
|
|
### `+` — Advance from scheduled, stacking
|
|
|
|
> *Repeats in X y/m/w/d/h from when you originally scheduled it.*
|
|
|
|
The next occurrence is exactly *original + one interval*. If
|
|
completion was much later than the original date, the next occurrence
|
|
may land in the past — which causes the task to appear overdue
|
|
immediately. Org-mode calls this "stacking."
|
|
|
|
**Best for:** obligations tied to an exact calendar date that should
|
|
not shift when you're late — monthly rent, annual renewals, fixed
|
|
billing cycles.
|
|
|
|
**Example:** Rent due 2026-04-01, paid 2026-04-05 → next reminder
|
|
2026-05-01. Paid 2026-04-25 → next reminder still 2026-05-01.
|
|
|
|
## Choosing the right cookie
|
|
|
|
A decision table for picking the right semantic:
|
|
|
|
| Intent | Cookie |
|
|
|---|---|
|
|
| "Every 7 days from when I last did it" | `.+` |
|
|
| "Every Monday" | `++` |
|
|
| "The 1st of every month" | `+` |
|
|
|
|
## Default and backward compatibility
|
|
|
|
Recurring tasks without an explicit `repeat-type` default to `++`.
|
|
This preserves the behavior that existed before the three cookies were
|
|
honored independently; no existing task changes on upgrade. Users who
|
|
want `.+` or `+` semantics can pick them via the repeat-setting
|
|
popover on the task.
|
|
|
|
## Implementation reference
|
|
|
|
### Scheduler
|
|
|
|
The scheduler lives in `src/main/frontend/worker/commands.cljs`. It
|
|
dispatches on `repeat-type` via `repeat-next-timestamp`:
|
|
|
|
- `advance-from-completion` implements `.+`
|
|
- `advance-from-scheduled` implements `+`
|
|
- `advance-until-future` implements `++`
|
|
|
|
The dispatch falls back to `++` for `nil` or unknown values, so the
|
|
scheduler is safe against missing or future-added cookie types.
|
|
Frequencies of zero or less short-circuit to `nil` before dispatch to
|
|
avoid infinite loops.
|
|
|
|
### Property
|
|
|
|
`:logseq.property.repeat/repeat-type` is a closed-values property
|
|
declared in `deps/db/src/logseq/db/frontend/property.cljs`. Its closed
|
|
values are:
|
|
|
|
- `:logseq.property.repeat/repeat-type.dotted-plus` — `.+`
|
|
- `:logseq.property.repeat/repeat-type.plus` — `+`
|
|
- `:logseq.property.repeat/repeat-type.double-plus` — `++` (default)
|
|
|
|
### UI
|
|
|
|
The repeat-setting popover in
|
|
`src/main/frontend/components/property/value.cljs` exposes a "Next date"
|
|
selector bound to the `repeat-type` property. It appears whenever the
|
|
task's repeat checkbox is enabled.
|
|
|
|
### Tests
|
|
|
|
Scheduler tests live in
|
|
`src/test/frontend/worker/commands_test.cljs`. Each of the three
|
|
semantics has a dedicated `deftest`; the preexisting
|
|
`get-next-time-test` exercises the default (`++`) path. Time is pinned
|
|
via `with-redefs [t/now ...]` for determinism.
|
|
|
|
## Related reading
|
|
|
|
- End-user docs: [logseq/docs `Tasks.md`](https://github.com/logseq/docs/blob/master/pages/Tasks.md)
|
|
- Org-mode spec: [Repeated tasks](https://orgmode.org/manual/Repeated-tasks.html)
|
|
- Tracking issues: [#7731](https://github.com/logseq/logseq/issues/7731), [#11260](https://github.com/logseq/logseq/issues/11260), [#6715](https://github.com/logseq/logseq/issues/6715), [#8531](https://github.com/logseq/logseq/issues/8531)
|