Functional Weave
Code in Rust

Suites · todo

Todo and task lists

A to-do list's logic as pure functions: the item type, a list reducer, quick-add parsing, due dates, repeats, tags, filters, sorting and CSV import/export.

41 capabilities (16 core, 25 optional) and 8 gaps, in build order. Put each line in the file that calls it, then fune build.

1. Items and the list

The todo type and every change to a list as a pure reducer.

todo.item core
The Todo, TodoDraft and Recurrence types, with validators giving a message per field.
The to-do item type (Todo, TodoDraft, Recurrence) and its validators, with a message per field.
fune!(todo.item@^1);  // then call validate_draft, validate_todo
todo.list core
Adds, edits, completes, removes, clears and reorders todos, each returning a new list.
The to-do list reducer: add, edit, complete, remove, clear and reorder todos, each returning a new list.
fune!(todo.list@^1);  // then call add_todo, update_todo, toggle_todo, remove_todo, clear_completed, toggle_all, move_todo, toggle_all_mode

2. Quick entry

Turn what people type into todos.

todo.parse-quick-add core
Turns "Pay rent tomorrow !high
Turn one typed line like "Pay rent tomorrow !high #home every month" into a todo draft.
fune!(todo.parse-quick-add@^1);  // then call parse_quick_add(…)
todo.parse-date-phrase optional
Reads "tomorrow", "next friday", "in 2 weeks" as a date relative to today.
Read a typed due date like "tomorrow", "next friday", "in 2 weeks" or "2026-10-01" as a date, relative to today.
fune!(todo.parse-date-phrase@^1);  // then call parse_date_phrase(…)
todo.parse-tags optional
Reads a tags box ("#Home, work") into normalised tags.
Read a tags box as typed, "#Home, work errands", into normalised tags: split on spaces and commas.
fune!(todo.parse-tags@^1);  // then call parse_tags(…)
todo.normalise-tags optional
Cleans tags: trims, drops
Clean up tags as typed: trim, drop the #, lower-case slug form, drop empties and duplicates, keep the order.
fune!(todo.normalise-tags@^1);  // then call normalise_tags(…)
todo.parse-repeat optional
Reads a Repeat box such as "every 2 weeks" as a recurrence.
Read a Repeat box, "every 2 weeks" or "every monday on 2026-10-05", as a todo's recurrence, or null.
fune!(todo.parse-repeat@^1);  // then call parse_repeat(…)

3. Due dates and repeats

Say where each todo stands against its date, and roll repeating ones forward.

todo.due-status core
Done, overdue, today, tomorrow, this week, later or no date, for a todo today.
Where a todo stands against its due date today: done, overdue, today, tomorrow, this week, later or no date.
fune!(todo.due-status@^1);  // then call due_status(…)
todo.due-label core
The short label beside a todo: "2 days overdue", "Due tomorrow".
The short English label shown next to a todo: "2 days overdue", "Due today", "Due tomorrow", "Due Fri 2 Oct".
fune!(todo.due-label@^1);  // then call due_label(…)
todo.next-occurrence optional
The next due date of a repeating todo once done, month-end safe.
The next due date of a repeating todo once it is done: daily, weekdays, weekly, monthly or yearly, month-end safe.
fune!(todo.next-occurrence@^1);  // then call next_occurrence(…)
todo.reschedule-recurrence optional
Moves a repeating todo's series when its due date is changed by hand.
When a repeating todo's due date is moved by hand, move its series with it: the new date becomes the anchor.
fune!(todo.reschedule-recurrence@^1);  // then call reschedule_recurrence(…)
todo.recurrence-label optional
Text for a repeat chip: "Every weekday", "Every month on the 31st".
English text for a repeating todo's chip: "Every weekday", "Every 2 weeks on Monday", "Every month on the 31st".
fune!(todo.recurrence-label@^1);  // then call recurrence_label(…)

4. Views

The list as people see it: filtered, sorted, grouped and counted.

todo.filter core
All, active or completed, one tag and a search box.
The todos a list view shows: all, active or completed, optionally one tag, and a search box, in input order.
fune!(todo.filter@^1);  // then call filter_todos(…)
todo.sort core
Sorts by manual order, due date, priority, creation time or title, stably.
Sort todos by manual order, due date, priority, creation time or title, stably, with sensible tie-breakers.
fune!(todo.sort@^1);  // then call sort_todos(…)
todo.group-by-due optional
Overdue, Today, Tomorrow, This week, Later, No date and Done sections.
Split todos into Overdue, Today, Tomorrow, This week, Later, No date and Done sections, in that order.
fune!(todo.group-by-due@^1);  // then call group_by_due, group_by_due_with_order
todo.summary optional
Counts for the header or footer: "3 items left", percent done, overdue.
Counts for a todo list's header or footer: total, active, done, overdue, due today, percent done and "3 items left".
fune!(todo.summary@^1);  // then call summarise_todos(…)
todo.tag-counts optional
How many todos carry each tag, for a tag sidebar.
How many todos carry each tag, for a tag sidebar: most used first, open todos only unless asked.
fune!(todo.tag-counts@^1);  // then call tag_counts(…)

5. Import and export

Move todos in and out as CSV.

todo.export-csv optional
Writes todos as an RFC 4180 CSV file for backups and spreadsheets.
Write todos as a CSV file (RFC 4180) with one column per field, for backups, spreadsheets and other apps.
fune!(todo.export-csv@^1);  // then call export_csv(…)
todo.import-csv optional
Reads that CSV back, checking every row with row-numbered errors.
Read todos from a CSV file (RFC 4180) as todo.export-csv writes it, checking every row, with row-numbered errors.
fune!(todo.import-csv@^1);  // then call import_csv, validate_todo_csv
todo.merge optional
Merges imported todos into a list by id.
Merge imported todos into a list by id: an imported todo replaces the one with its id, new ids are appended.
fune!(todo.merge@^1);  // then call merge_todos(…)

6. Calendar arithmetic from dates-basics

Move and measure ISO dates exactly, with no date library and no clock reads.

dates.add-days core
Shifts a date by whole days, for due dates and windows.
Shift an ISO date by a whole number of days, forwards or backwards, with exact calendar arithmetic.
fune!(dates.add-days@^1);  // then call add_days(…)
dates.add-months core
Adds months clamping to month end, so 31 January plus a month is the end of February.
Add calendar months to an ISO date, clamping to the month end: 31 Jan + 1 month is 28 or 29 Feb.
fune!(dates.add-months@^1);  // then call add_months(…)
dates.days-between core
Signed calendar days between two dates, for ages of invoices and stays.
Calendar days from one ISO date to another, signed: negative when the second date is earlier.
fune!(dates.days-between@^1);  // then call days_between(…)
dates.month-boundaries core
First and last day of a date's month, for monthly periods and statements.
The first day, last day and length of the calendar month containing a date.
fune!(dates.month-boundaries@^1);  // then call month_boundaries(…)
dates.day-of-week core
ISO weekday of a date, for weekend rules and opening hours.
The ISO 8601 day of the week for a date: 1 is Monday through 7 is Sunday.
fune!(dates.day-of-week@^1);  // then call day_of_week(…)
dates.is-leap-year optional
Whether a year is a leap year, for 365/366-day calculations.
Whether a year is a Gregorian leap year: every 4th, except centuries, except every 400th.
fune!(dates.is-leap-year@^1);  // then call is_leap_year(…)
dates.overlap optional
Days two date ranges share, for prorating and clash checks.
Days two date ranges have in common, with the end of each range stated as inclusive or exclusive.
fune!(dates.overlap@^1);  // then call overlap_days(…)
dates.age optional
Age in whole years on a date, handling 29 February birthdays.
Age in whole years on a given date, with 29 February birthdays ageing on 1 March in other years.
fune!(dates.age@^1);  // then call age(…)

7. Periods and reporting dates from dates-basics

Put dates into the periods reports are grouped by.

dates.quarter core
Calendar and fiscal quarter of a date for any year-start month.
Calendar quarter and fiscal quarter of a date, with the fiscal year and quarter date ranges, for any start month.
fune!(dates.quarter@^1);  // then call quarter(…)
dates.week-number optional
ISO week number and week-year, for weekly reports.
ISO 8601 week number and week-numbering year of a date: weeks start Monday, week 1 holds the first Thursday.
fune!(dates.week-number@^1);  // then call week_number(…)
dates.day-count-fraction optional
Year fraction by ACT/365F, ACT/360, 30/360 or ACT/ACT, for interest periods.
Year fraction between two dates by ACT/365F, ACT/360, 30/360, 30E/360 or ACT/ACT ISDA, as an exact fraction.
fune!(dates.day-count-fraction@^1);  // then call day_count_fraction(…)

8. Working days and holidays from dates-basics

Skip weekends and public holidays when counting or adding days.

dates.bank-holidays core
UK bank holidays for a region and year, as gov.uk publishes them.
UK bank and public holidays for a region and year, as published by gov.uk, including one-off holidays.
fune!(dates.bank-holidays@^1);  // then call bank_holidays(…)
dates.add-business-days core
Moves a date by working days, skipping weekends and a holiday list.
Move a date forward or back by working days, skipping weekends and a caller-supplied holiday list.
fune!(dates.add-business-days@^1);  // then call add_business_days(…)
dates.business-days-between core
Working days between two dates, for SLAs and notice periods.
Working days between two dates, excluding weekends and a caller-supplied holiday list.
fune!(dates.business-days-between@^1);  // then call business_days_between(…)

9. Repeating schedules from dates-basics

Generate the dates of something that repeats.

dates.recurrence optional
The next n dates of a rule: monthly on the 31st, last Friday, every two weeks.
The next n dates of a repeating rule: monthly on a day, last weekday of the month, every k days or weeks, yearly.
fune!(dates.recurrence@^1);  // then call recurrence(…)

10. Times and durations from dates-basics

Work with times of day, durations and timestamps.

time.duration optional
Parses and adds durations like 1h30m in whole minutes, and formats the total.
Parse durations written as 1h30m, 90m or 01:30, add them up in whole minutes, and format the total.
fune!(time.duration@^1);  // then call duration(…)
time.minutes-between optional
Minutes between two local date-times, across midnight.
Minutes from one local date and time to another, correct across midnight; wall-clock time, no time zones.
fune!(time.minutes-between@^1);  // then call minutes_between(…)
time.round-to-increment optional
Rounds minutes to a billing increment such as 6 or 15.
Round a duration in minutes to a billing increment such as 6 or 15 minutes, up, down or to nearest.
fune!(time.round-to-increment@^1);  // then call round_to_increment(…)
time.iso-to-unix optional
RFC 3339 timestamps to Unix seconds.
An RFC 3339 timestamp with a Z or offset, such as 2026-09-26T13:00:00+01:00, to Unix time in whole seconds.
fune!(time.iso-to-unix@^1);  // then call iso_to_unix(…)
time.unix-to-iso optional
Unix seconds to an ISO 8601 UTC timestamp.
Unix time in seconds to an ISO 8601 UTC timestamp such as 2026-09-26T12:00:00Z, for years 0001 to 9999.
fune!(time.unix-to-iso@^1);  // then call unix_to_iso(…)
time.countdown optional
Seconds left until a moment and a 4:05 style timer text.
Seconds left until a Unix time, whether it has passed, and a timer text like 4:05, for tokens or retry waits.
fune!(time.countdown@^1);  // then call countdown(…)

Gaps

What this kind of app usually needs that Functional Weave does not have yet: write these yourself, or use a service.

  • persistence Storing todos (localStorage, a database) and loading them: caps works on lists in memory.
  • sync Syncing between devices and resolving edits made offline on two of them.
  • notifications Reminders that fire at a time: push, email or OS notifications and their scheduler.
  • sharing Shared lists, assignees and permissions between users.
  • ui The list UI itself: Functional Weave has no todo components, only the logic and React form fields.
  • time-zones Time zones and daylight saving: caps works in ISO dates, UTC and wall-clock times; converting with the IANA tz rules is yours.
  • locale-formatting Showing dates in a user's language and format ("3 octobre 2026"): caps returns ISO dates, not localised text.
  • non-uk-holidays Public holidays outside the UK: dates.bank-holidays is UK only; pass another country's list to the working-day functions yourself.