Skip to content

feat: Adds VTODO support#124

Merged
panteLx merged 3 commits intomainfrom
feat/vtodo-support
Jan 16, 2026
Merged

feat: Adds VTODO support#124
panteLx merged 3 commits intomainfrom
feat/vtodo-support

Conversation

@panteLx
Copy link
Owner

@panteLx panteLx commented Jan 16, 2026

Adds support for importing VTODO (tasks/todos) from external iCal feeds as shifts. Updates ICS validation to accept VTODOs, introduces a VTODO processing routine to convert task properties into shift data, and integrates tasks into the existing sync flow with sync-window filtering, fingerprint-based deduplication, and conditional update/insert logic. Also includes task counts in sync metrics to surface imported todo activity.

Summary by CodeRabbit

  • New Features

    • External calendar sync now imports task/to-do items in addition to events.
  • Improvements

    • Better detection of task items in imported calendars and improved deduplication to avoid duplicate entries.
    • Tasks are converted into shifts with proper date/time handling and integrated into the existing sync window and update logic.

✏️ Tip: You can customize this high-level summary in your review settings.

Adds support for importing VTODO (tasks/todos) from external iCal feeds as shifts. Updates ICS validation to accept VTODOs, introduces a VTODO processing routine to convert task properties into shift data, and integrates tasks into the existing sync flow with sync-window filtering, fingerprint-based deduplication, and conditional update/insert logic. Also includes task counts in sync metrics to surface imported todo activity.
Copilot AI review requested due to automatic review settings January 16, 2026 19:15
@panteLx panteLx linked an issue Jan 16, 2026 that may be closed by this pull request
3 tasks
@coderabbitai
Copy link

coderabbitai bot commented Jan 16, 2026

Caution

Review failed

The pull request is closed.

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

📝 Walkthrough

Walkthrough

Adds VTODO (task) support to external calendar sync: parses VTODOs, normalizes them via processTodoToShift, deduplicates using fingerprints, updates/inserts shifts within the sync window, and extends ICS validity to accept calendars with VTODOs.

Changes

Cohort / File(s) Summary
External Sync Route Handler
app/api/external-syncs/[id]/sync/route.ts
Adds VTODO extraction and processing loop: calls processTodoToShift, filters by sync window, derives eventId/fingerprint, checks existing shifts, updates or batch-inserts new todo-shifts, and counts totalTodos. Integrates todo fingerprints into obsolete-shift deletion logic.
Calendar Utilities
lib/external-calendar-utils.ts
Extends isValidICSContent to accept VTODOs and adds exported processTodoToShift(vtodo), which derives date/time, detects all-day todos, extracts title/notes/uid, returns normalized shift data or null on invalid input.

Sequence Diagram

sequenceDiagram
    participant API as Sync API Route
    participant ICS as ICS Parser
    participant Utils as processTodoToShift
    participant DB as Database

    API->>ICS: Parse ICS -> extract VTODOs
    ICS-->>API: List of VTODO components

    loop For each VTODO
        API->>Utils: processTodoToShift(vtodo)
        Utils-->>API: shiftData or null

        alt shiftData returned
            API->>DB: Query shift by fingerprint
            DB-->>API: existingShift or none

            alt existing & needsUpdate
                API->>DB: Update shift
            else not existing
                API->>DB: Insert new shift (batch)
            end
        else invalid/null
            API-->>API: Skip VTODO
        end
    end

    API->>DB: Delete obsolete shifts (exclude processed fingerprints)
    DB-->>API: Commit/ack
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

Possibly related issues

Possibly related PRs

  • feat: Adds VTODO support #124: Modifies the same files to add processTodoToShift and VTODO handling in the external sync route — likely a duplicate or closely related implementation.

Poem

🐇 I nibble at calendars through the night,
VTODOs hop in, tidy and bright.
Fingerprints mark each little task,
I sync them up — no need to ask.
Hooray for tidy schedules and light delight!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: Adds VTODO support' clearly and concisely summarizes the main change—adding support for VTODO components in the external calendar sync flow.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings


📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 21ab4ae and 3576b80.

📒 Files selected for processing (1)
  • app/api/external-syncs/[id]/sync/route.ts

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds support for importing VTODO (tasks/todos) from external iCal feeds, converting them to shifts alongside existing VEVENT support. VTODOs are processed with due/start date handling, integrated into the sync flow with fingerprint-based deduplication, and included in sync metrics.

Changes:

  • Updates ICS validation to accept calendars containing only VTODOs
  • Adds processTodoToShift() function to convert VTODO components to shift data
  • Integrates VTODO processing into the sync route with window filtering and deduplication

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
lib/external-calendar-utils.ts Adds VTODO validation in isValidICSContent() and new processTodoToShift() function to convert tasks to shifts
app/api/external-syncs/[id]/sync/route.ts Retrieves VTODOs from calendar, processes them through sync window filtering and fingerprint deduplication, adds totalTodos to metrics

Comment on lines +452 to +462
if (!isAllDay && dueDate) {
// If there's a specific due time, show it as ending at that time
const hours = jsDate.getHours().toString().padStart(2, "0");
const minutes = jsDate.getMinutes().toString().padStart(2, "0");
endTime = `${hours}:${minutes}`;
} else if (!isAllDay && startDate) {
// If there's a start time but no due time, show it starting at that time
const hours = jsDate.getHours().toString().padStart(2, "0");
const minutes = jsDate.getMinutes().toString().padStart(2, "0");
startTime = `${hours}:${minutes}`;
endTime = "23:59";
Copy link

Copilot AI Jan 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic doesn't handle VTODOs that have both DTSTART and DUE dates. When both are present, taskDate is set to dueDate (line 437), so jsDate contains the due date. In the condition at line 452, if a due time exists and it's not all-day, the code extracts hours/minutes from jsDate (the due date) for the endTime, but startTime remains '00:00'. However, if the VTODO has both start and due dates, you should use the start date for startTime and the due date for endTime, not default startTime to '00:00'. This creates shifts that always start at midnight even when a specific start time is provided.

Suggested change
if (!isAllDay && dueDate) {
// If there's a specific due time, show it as ending at that time
const hours = jsDate.getHours().toString().padStart(2, "0");
const minutes = jsDate.getMinutes().toString().padStart(2, "0");
endTime = `${hours}:${minutes}`;
} else if (!isAllDay && startDate) {
// If there's a start time but no due time, show it starting at that time
const hours = jsDate.getHours().toString().padStart(2, "0");
const minutes = jsDate.getMinutes().toString().padStart(2, "0");
startTime = `${hours}:${minutes}`;
endTime = "23:59";
if (!isAllDay) {
if (startDate && dueDate) {
// When both start and due are present, use start for startTime and due for endTime
const startJsDate = startDate.toJSDate();
const startHours = startJsDate.getHours().toString().padStart(2, "0");
const startMinutes = startJsDate.getMinutes().toString().padStart(2, "0");
startTime = `${startHours}:${startMinutes}`;
const dueJsDate = dueDate.toJSDate();
const endHours = dueJsDate.getHours().toString().padStart(2, "0");
const endMinutes = dueJsDate.getMinutes().toString().padStart(2, "0");
endTime = `${endHours}:${endMinutes}`;
} else if (dueDate) {
// If there's a specific due time but no start time, show it as ending at that time
const dueJsDate = dueDate.toJSDate();
const hours = dueJsDate.getHours().toString().padStart(2, "0");
const minutes = dueJsDate.getMinutes().toString().padStart(2, "0");
endTime = `${hours}:${minutes}`;
} else if (startDate) {
// If there's a start time but no due time, show it starting at that time
const startJsDate = startDate.toJSDate();
const hours = startJsDate.getHours().toString().padStart(2, "0");
const minutes = startJsDate.getMinutes().toString().padStart(2, "0");
startTime = `${hours}:${minutes}`;
endTime = "23:59";
}

Copilot uses AI. Check for mistakes.
* @param vtodo - The VTODO component from iCal.js
* @returns Shift data object or null if invalid
*/
export function processTodoToShift(vtodo: ICAL.Component): {
Copy link

Copilot AI Jan 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

VTODOs can have recurrence rules (RRULE) just like VEVENTs, but processTodoToShift() doesn't handle recurring tasks. The existing expandRecurringEvents() function works with VEVENT components specifically. Consider whether recurring VTODOs should be supported, and if so, either extend expandRecurringEvents() to work with VTODOs or create similar expansion logic for tasks. Without this, recurring tasks will only appear once instead of generating multiple shift occurrences.

Copilot uses AI. Check for mistakes.
@panteLx panteLx merged commit c4a527c into main Jan 16, 2026
2 of 3 checks passed
@panteLx panteLx deleted the feat/vtodo-support branch January 16, 2026 19:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feature request: Add VTODO Support

1 participant