Functional Weave
Code in TypeScript

todo.reschedule-recurrence

When a repeating todo's due date is moved by hand, move its series with it: the new date becomes the anchor.

1.0.0 · published 2026-10-03 by charlie · Anterra

Pinned by 14 tests, run in TypeScript, Python and Rust.

What it does

When someone edits a repeating todo and changes its due date, the series moves with it: the new date becomes the recurrence's anchor.

todo.item's `Recurrence` counts every occurrence from its `anchor`, the series' first date, and `validateDraft` refuses a due date before the anchor. So an edit form that simply stored the new date would either be refused (moved earlier) or keep repeating on the old rhythm (moved later). Moving the anchor is what a person means by "move this to Friday".

For example

  • rescheduleRecurrence(frequency monthly, interval 1, anchor 2026-09-29, 2026-09-29, 2026-10-05) → frequency monthly, interval 1, anchor 2026-10-05 moving a monthly todo's date moves the series to the new date
  • rescheduleRecurrence(frequency weekly, interval 1, anchor 2026-10-01, 2026-10-01, 2026-09-28) → frequency weekly, interval 1, anchor 2026-09-28 moving it earlier than the anchor, which todo.item would otherwise refuse
  • rescheduleRecurrence(frequency monthly, interval 1, anchor 2026-01-31, 2026-02-28, 2026-02-28) → frequency monthly, interval 1, anchor 2026-01-31 an unchanged date keeps a month-end anchor after the series rolled forward

The function

The same function in TypeScript, Python and Rust, pinned by the same tests. Pick your language; the choice follows you around the registry.

export function rescheduleRecurrence(recurrence: Recurrence | null, fromDue: string | null, toDue: string | null): Recurrence | null
recurrenceRecurrence?the todo's repeat as stored, or null for a one-off todo
fromDuedate?the due date it had
toDuedate?the due date the person gave it
returnsRecurrence?anchored on toDue when the date moved; otherwise unchanged

Your code names it in one line, in the file that uses it

import { rescheduleRecurrence } from "#fune/todo.reschedule-recurrence@^1";
impl/typescript.ts · 20 lines · open · raw

Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.

import { addDays } from "./dates_add_days.ts";  ← from dates.add-days ^1.0.0 · built alongside by fune
import { type Recurrence } from "./todo_item.ts";  ← from todo.item ^1.0.0 · built alongside by fune

/**
 * The repeat a todo keeps when its due date is edited. Every occurrence is
 * counted from the anchor, so moving the date moves the series: the new date
 * becomes the anchor. An unchanged date keeps the anchor, which matters once
 * a series has rolled forward (monthly from 31 January is due 28 February,
 * and re-anchoring there would lose the month end).
 */
export function rescheduleRecurrence(recurrence: Recurrence | null, fromDue: string | null, toDue: string | null): Recurrence | null {
  // addDays(x, 0) is the date check: it throws dates.add-days's message.
  if (fromDue !== null) addDays(fromDue, 0);
  if (toDue !== null) addDays(toDue, 0);
  if (recurrence === null) return null;
  addDays(recurrence.anchor, 0);
  // No date: todo.item's validateDraft says a repeating todo needs one.
  if (toDue === null || toDue === fromDue) return recurrence;
  return { ...recurrence, anchor: toDue };
}

Install

fune build

With that line in your source, in a TypeScript project (language typescript in fune.project), fune build resolves it and its 2 dependencies, pins them in fune.lock, downloads only the TypeScript package of each, and builds the code above into your project’s .fune/build, one readable file per capability with a header linking back here. Or pin a range in fune.project and build in one step:

fune add todo.reschedule-recurrence
Download for TypeScript todo.reschedule-recurrence-1.0.0-typescript.fune · 7,443 bytes sha256 f2f27c113ee706773b8e2e300011aab632698a0510e077b82d120c95b44fb2af

The manifest, vectors and README with only the TypeScript implementation. Install it without the registry with fune add ./todo.reschedule-recurrence-1.0.0-typescript.fune, or fetch it from a terminal with fune pull todo.reschedule-recurrence@1.0.0:typescript.

The whole function, every language, is one file too: todo.reschedule-recurrence-1.0.0.fune, 9,618 bytes, sha256 32bbf5607d31a2d70a36741adcf309620dd29de47922d0b5e4045c5976eb5451. It installs into a project of any language.

Customise it in your app

The seams this capability offers. Put a marker directly above a function of your own and fune build wires it into the built code; the package on the registry is not changed, the built file’s header lists it under CUSTOMISED, and fune hooks lists every hook in the project. How hooks work.

before — your function gets the arguments and returns them, changed or not, or throws to refuse the call.

// fune: before todo.reschedule-recurrence

after — your function gets the result and the arguments, and returns the final result.

// fune: after todo.reschedule-recurrence

replace — inside this capability’s code only, calls to a dependency go to your function, with the same signature. Other capabilities that use it are unaffected; write in * to replace it everywhere.

// fune: replace dates.add-days in todo.reschedule-recurrence
// fune: replace todo.item in todo.reschedule-recurrence

step — your function runs at a numbered point inside the function’s body, receives the in-scope values it names as parameters, and may return replacements. List the points with fune show todo.reschedule-recurrence --steps.

// fune: step todo.reschedule-recurrence after <n|label>

Tests

A version published now needs at least 8 tests for every function, and one that expects the error for each function that throws; the registry refuses it otherwise. fune verify --all runs each case in TypeScript, Python and Rust, and a project runs them again with fune verify. This page lists the cases; it does not run them. The exact JSON is vectors.json.

CaseArgumentsExpected
moving a monthly todo's date moves the series to the new date frequency monthly, interval 1, anchor 2026-09-29, 2026-09-29, 2026-10-05 → frequency monthly, interval 1, anchor 2026-10-05
moving it earlier than the anchor, which todo.item would otherwise refuse frequency weekly, interval 1, anchor 2026-10-01, 2026-10-01, 2026-09-28 → frequency weekly, interval 1, anchor 2026-09-28
an unchanged date keeps a month-end anchor after the series rolled forward frequency monthly, interval 1, anchor 2026-01-31, 2026-02-28, 2026-02-28 → frequency monthly, interval 1, anchor 2026-01-31
a rolled-forward series moved by two days is anchored on the new date, not the old anchor frequency monthly, interval 1, anchor 2026-01-31, 2026-02-28, 2026-03-02 → frequency monthly, interval 1, anchor 2026-03-02
frequency and interval are kept frequency weekly, interval 2, anchor 2026-09-28, 2026-09-28, 2026-10-02 → frequency weekly, interval 2, anchor 2026-10-02
a one-off todo stays one-off —, 2026-09-28, 2026-10-01 → —
clearing the date leaves the repeat for todo.item to report frequency daily, interval 1, anchor 2026-09-28, 2026-09-28, — → frequency daily, interval 1, anchor 2026-09-28
a repeat that had no date gets one: the new date is the anchor frequency daily, interval 1, anchor 2026-09-28, —, 2026-10-01 → frequency daily, interval 1, anchor 2026-10-01
a weekday series moved to a Saturday is anchored there as given frequency weekdays, interval 1, anchor 2026-09-28, 2026-09-30, 2026-10-03 → frequency weekdays, interval 1, anchor 2026-10-03
no dates at all and no repeat —, —, — → —
Show the other 4 tests
CaseArgumentsExpected
an impossible new date is an error frequency daily, interval 1, anchor 2026-09-28, 2026-09-28, 2026-02-30 → error: is not a real calendar date
a malformed old date is an error frequency daily, interval 1, anchor 2026-09-28, 28/09/2026, 2026-10-01 → error: is not an ISO date
a malformed anchor is an error frequency monthly, interval 1, anchor 2026-13-01, 2026-09-28, 2026-10-01 → error: is not a real calendar date
a bad date is an error even for a one-off todo —, 2026-09-28, 2026-9-30 → error: is not an ISO date

More from the author

| recurrence | fromDue → toDue | result | | --- | --- | --- | | null | anything | null | | any | toDue null | unchanged (validateDraft then says a repeating todo needs a due date) | | any | toDue = fromDue | unchanged | | any | toDue ≠ fromDue | the same frequency and interval, anchored on toDue |

- **An unchanged date keeps the anchor.** A monthly series from 31 January is due 28 February after one roll forward; saving an edit to its title must not re-anchor it on the 28th, or it would lose the month end. - A new date is taken as given, even a Saturday for a weekday series: todo.next-occurrence decides the following dates from there. - Every date given (`fromDue`, `toDue`, the anchor) must be a real ISO date, with or without a recurrence; the messages are dates.add-days's (`"2026-02-30" is not a real calendar date`).

Files

PathBytes
README.md1,367
impl/python.py833
impl/rust.rs1,257
impl/typescript.ts1,015
vectors.json2,954