Skip to main content
Recurring tasks allow you to define tasks that repeat on a schedule. When you complete a recurring task, Philo automatically recreates it on the next due date.

Recurrence Tags

Add a hashtag to any task to make it recurring:
- [ ] Morning meditation #daily
- [ ] Review weekly goals #weekly  
- [ ] Pay rent #monthly
- [ ] Water plants #3days
- [ ] Team sync #2weeks

Supported Patterns

Philo recognizes these recurrence patterns:

Keywords

#daily, #weekly, #monthly

Custom Intervals

#2days, #3weeks, #2months, etc.

Pattern Matching

The recurrence parser uses this regex:
const RECURRENCE_TAG = /#(daily|weekly|monthly|(\d+)(days?|weeks?|months?))\b/i;

Examples

PatternMatches
#dailyEvery 1 day
#weeklyEvery 7 days
#monthlyEvery 30 days
#2days or #2dayEvery 2 days
#3weeks or #3weekEvery 21 days
#2months or #2monthEvery 60 days
Both singular and plural forms are accepted (e.g., #2day and #2days).

Parsing Logic

interface Recurrence {
  intervalDays: number;
  tag: string;
}

export function parseRecurrence(text: string): Recurrence | null {
  const match = text.match(RECURRENCE_TAG);
  if (!match) return null;

  const tag = match[0];
  const keyword = match[1].toLowerCase();

  // Handle keyword patterns
  if (keyword === "daily") return { intervalDays: 1, tag };
  if (keyword === "weekly") return { intervalDays: 7, tag };
  if (keyword === "monthly") return { intervalDays: 30, tag };

  // Handle custom intervals
  const n = parseInt(match[2], 10);
  const unit = match[3].toLowerCase();

  if (unit.startsWith("day")) return { intervalDays: n, tag };
  if (unit.startsWith("week")) return { intervalDays: n * 7, tag };
  if (unit.startsWith("month")) return { intervalDays: n * 30, tag };

  return null;
}

How Recurrence Works

1

Complete the task

Check off a task with a recurrence tag (e.g., - [x] Exercise #daily)
2

Task rollover scans

When rolloverTasks() runs, it scans past notes for completed recurring tasks
3

Calculate next due date

Adds the interval to the completion date
4

Check if due

If next due date ≤ today, recreates the task as unchecked
5

Add to today

Prepends the recreated task to today’s note

Implementation

Recurring tasks are handled during the rollover process:
// Extract checked tasks with recurrence tags
function extractCheckedRecurringTasks(content: string): string[] {
  const lines = content.split("\n");
  const tasks: string[] = [];

  for (const line of lines) {
    const match = line.match(CHECKED_TASK);
    if (match && parseRecurrence(match[2])) {
      tasks.push(match[2]);
    }
  }

  return tasks;
}

// During rollover
const checkedRecurring = extractCheckedRecurringTasks(markdown);
for (const taskText of checkedRecurring) {
  const recurrence = parseRecurrence(taskText)!;
  const nextDue = addDaysToDate(date, recurrence.intervalDays);
  if (nextDue <= today && !taskMap.has(taskText)) {
    taskMap.set(taskText, { indent: "", text: taskText });
  }
}

Date Calculation

function dateToString(d: Date): string {
  return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
}

function addDaysToDate(dateStr: string, days: number): string {
  const d = new Date(dateStr + "T00:00:00");
  d.setDate(d.getDate() + days);
  return dateToString(d);
}
Months are approximated as 30 days. For precise monthly recurrence, consider using a calendar-based system.

Behavior Examples

Daily Task

- [x] Morning meditation #daily

Weekly Task

- [x] Review weekly goals #weekly

Custom Interval

- [x] Deep work session #3days

Deduplication

Recurring tasks are deduplicated like regular tasks:
if (nextDue <= today && !taskMap.has(taskText)) {
  taskMap.set(taskText, { indent: "", text: taskText });
}
This prevents the same recurring task from appearing multiple times if rollover runs multiple times.

Best Practices

Be specific

Use descriptive task text: Morning workout #daily not just Workout #daily

Choose appropriate intervals

Match recurrence to actual needs—don’t make everything daily

Review periodically

Check recurring tasks monthly to remove outdated ones

Use consistent tags

Stick with the same recurrence tag to track task history

Advanced Patterns

Multiple Tags

You can combine recurrence with other tags:
- [ ] Gym workout #daily #health #morning
- [ ] Team standup #weekly #work #meeting
Only the recurrence tag affects rollover behavior.

Skipping Recurrence

To stop a recurring task from repeating:
  1. Complete it as usual: - [x] Old habit #daily
  2. Remove the recurrence tag: - [x] Old habit
  3. The task won’t be recreated
Use recurrence tags for habits and routines you want to maintain consistently. For one-time tasks, use regular task lists.

Limitations

  • Monthly recurrence is approximated as 30 days
  • Tasks don’t account for weekends or holidays
  • No support for complex patterns like “every Monday” or “first of the month”
  • Recurrence is based on completion date, not original due date

Future Enhancements

Potential improvements to the recurrence system:
  • Calendar-aware monthly recurrence
  • Weekday-specific patterns (e.g., #weekdays, #mondays)
  • Skip weekends option
  • Recurrence based on original schedule vs. completion date
  • Custom recurrence rules with more complex logic

Build docs developers (and LLMs) love