Functional Weave
Code in TypeScript

todo.summary

Counts for a todo list's header or footer: total, active, done, overdue, due today, percent done and "3 items left".

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

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

What it does

The numbers a todo list shows about itself, in one call: how many there are, how many are open and done, how many are overdue or due today, the percentage done and the "N items left" text.

- `overdue` and `dueToday` come from todo.due-status, so they count exactly the todos in the Overdue and Today sections of todo.group-by-due. Done todos are never counted as overdue. - **`percentDone` rounds down** (math.round-div, mode `down`): 199 of 200 done is 99, not 100, so the bar only reads 100% when everything is done, and only reads 0% when nothing is. An empty list is 0. - `itemsLeft` counts active todos, with English plurals: `"0 items left"`, `"1 item left"`, `"2 items left"`. A localised app builds its own string from `active`. - `today` is checked even for an empty list, and a malformed or impossible date anywhere is an error (dates.add-days's messages).

For example

  • summariseTodos(, 2026-09-28) → total 0, active 0, done 0, overdue 0, due today 0, percent done 0, items left 0 items left an empty list is all zeros
  • summariseTodos(todos ×3, 2026-09-28) → total 3, active 0, done 3, overdue 0, due today 0, percent done 100, items left 0 items left everything done is 100%, 0 items left, and a done late todo is not overdue
  • summariseTodos(todos ×1, 2026-09-28) → total 1, active 1, done 0, overdue 0, due today 1, percent done 0, items left 1 item left one open todo due today is 1 item left

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 summariseTodos(todos: readonly Todo[], today: string): TodoSummary
todosTodo[]
todaydatethe user's local date, from the app; never the clock
returnsTodoSummary

The type it declares, generated into your project

/** The numbers a todo list shows about itself. */
export interface TodoSummary {
  readonly total: number;
  /** not done */
  readonly active: number;
  readonly done: number;
  /** open and past due */
  readonly overdue: number;
  /** open and due today */
  readonly dueToday: number;
  /** done * 100 / total rounded down; 0 for an empty list */
  readonly percentDone: number;
  /** "0 items left", "1 item left", "3 items left" */
  readonly itemsLeft: string;
}

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

import { summariseTodos } from "#fune/todo.summary@^1";
impl/typescript.ts · 33 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 { dayOfWeek } from "./dates_day_of_week.ts";  ← from dates.day-of-week ^1.0.0 · built alongside by fune
import { roundDiv } from "./math_round_div.ts";  ← from math.round-div ^1.0.0 · built alongside by fune
import { dueStatus } from "./todo_due_status.ts";  ← from todo.due-status ^1.0.0 · built alongside by fune
import { type Todo } from "./todo_item.ts";  ← from todo.item ^1.0.0 · built alongside by fune
import { type TodoSummary } from "./todo_summary_types.ts";

/**
 * Counts for a todo list. percentDone rounds down so 100 means everything is
 * done; overdue and dueToday agree with dueStatus and todo.group-by-due.
 */
export function summariseTodos(todos: readonly Todo[], today: string): TodoSummary {
  dayOfWeek(today); // a bad today is an error even for an empty list
  let done = 0;
  let overdue = 0;
  let dueToday = 0;
  for (const todo of todos) {
    const status = dueStatus(todo, today);
    if (todo.done) done += 1;
    if (status === "overdue") overdue += 1;
    if (status === "today") dueToday += 1;
  }
  const total = todos.length;
  const active = total - done;
  return {
    total,
    active,
    done,
    overdue,
    dueToday,
    percentDone: total === 0 ? 0 : roundDiv(done * 100, total, "down"),
    itemsLeft: active === 1 ? "1 item left" : `${active} items left`,
  };
}

Install

fune build

With that line in your source, in a TypeScript project (language typescript in fune.project), fune build resolves it and its 4 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.summary
Download for TypeScript todo.summary-1.0.0-typescript.fune · 62,951 bytes sha256 3bc0be92d6b69cce7e354077a9cd14f5b5a0fa43c302267564d5dd01cc6053e4

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

The whole function, every language, is one file too: todo.summary-1.0.0.fune, 66,009 bytes, sha256 c18e7aa1361361be82208402f37176789c3697b153d357e69072e6d82a83db45. 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.summary

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

// fune: after todo.summary

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.day-of-week in todo.summary
// fune: replace math.round-div in todo.summary
// fune: replace todo.due-status in todo.summary
// fune: replace todo.item in todo.summary

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.summary --steps.

// fune: step todo.summary 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
an empty list is all zeros , 2026-09-28 → total 0, active 0, done 0, overdue 0, due today 0, percent done 0, items left 0 items left
everything done is 100%, 0 items left, and a done late todo is not overdue todos ×3, 2026-09-28 → total 3, active 0, done 3, overdue 0, due today 0, percent done 100, items left 0 items left
one open todo due today is 1 item left todos ×1, 2026-09-28 → total 1, active 1, done 0, overdue 0, due today 1, percent done 0, items left 1 item left
a mixed list todos ×4, 2026-09-28 → total 4, active 3, done 1, overdue 1, due today 1, percent done 25, items left 3 items left
two of three done rounds down to 66 todos ×3, 2026-09-28 → total 3, active 1, done 2, overdue 0, due today 0, percent done 66, items left 1 item left
one of three done rounds down to 33 todos ×3, 2026-09-28 → total 3, active 2, done 1, overdue 0, due today 0, percent done 33, items left 2 items left
199 of 200 done is 99, not 100 todos ×200, 2026-09-28 → total 200, active 1, done 199, overdue 0, due today 0, percent done 99, items left 1 item left
a done todo due today does not count as due today todos ×2, 2026-09-28 → total 2, active 1, done 1, overdue 0, due today 1, percent done 50, items left 1 item left
two overdue, from last year and yesterday todos ×3, 2026-09-28 → total 3, active 3, done 0, overdue 2, due today 0, percent done 0, items left 3 items left
on Sunday, yesterday's todo is overdue todos ×2, 2026-10-04 → total 2, active 2, done 0, overdue 1, due today 1, percent done 0, items left 2 items left
Show the other 3 tests
CaseArgumentsExpected
a malformed today is an error even for an empty list , 20260928 → error: is not an ISO date
a today that never existed todos ×1, 2026-02-29 → error: is not a real calendar date
a due date that never existed todos ×1, 2026-09-28 → error: is not a real calendar date

Files

PathBytes
README.md898
impl/python.py1,074
impl/rust.rs1,831
impl/typescript.ts1,100
vectors.json50,954