-
-
Notifications
You must be signed in to change notification settings - Fork 292
feat(repeat): migrate from legacy repeat fields to RFC 5545 RRULE #2032
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
feat(repeat): migrate from legacy repeat fields to RFC 5545 RRULE #2032
Conversation
- Add FORK.md documenting fork purpose, features, and installation - Add docs/fork/ directory for demo videos and screenshots - Add fork banner to README.md pointing to FORK.md - Document open PRs: go-vikunja#2032 (RRULE), go-vikunja#2031 (filter favorite) - Include Docker image info: ghcr.io/iamsamuelrodda/vikunja:unstable-fork 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Adds support for months and years in the repeat interval selector and implements fixed day-of-month repeating for monthly tasks. Changes: - Add months and years options to the repeat interval dropdown - Add calendar-aware yearly repeat mode (REPEAT_MODE_YEAR = 3) - Add quick buttons: quarterly, semi-annual, monthly, yearly - Add "On day" dropdown for monthly repeats (1-31 or same as due date) - Add repeat_day column to tasks table (migration 20251228214425) - Add repeat configuration tooltip on repeat icon in task lists - Fix repeat icon visibility for calendar-aware modes The fixed day feature allows users to set a task to repeat on a specific day each month (e.g., always on the 15th). If the selected day doesn't exist in a month (e.g., 31st in February), it uses the last day. Closes go-vikunja#1369
BREAKING CHANGE: Replaces repeatAfter/repeatMode/repeatDay with RRULE string Backend: - Add migration to convert legacy fields to RRULE format - Update Task struct to use `repeats` (RRULE string) and `repeatsFromCurrentDate` - Update CalDAV to parse/generate RRULE directly - Update Typesense indexing for new field - Update Microsoft Todo migration Frontend: - Add RRULE helper library (src/helpers/rrule.ts) - Update ITask interface and TaskModel - Rewrite RepeatAfter.vue component for RRULE - Update task display components to use isRepeating() - Update parseTaskText to return RRULE strings - Remove legacy type files (IRepeatAfter, IRepeatMode) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add translations for everyHour, everyMonthOnDay, and everyN keys used by describeRRule() function for repeat interval tooltips. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
When a repeating task had no due date, marking it as done would reset the done status but leave the due date empty. This fix ensures the next occurrence is always set as the due date for repeating tasks. Also: - Fix legacy repeat_after field in test fixture - Remove obsolete CalDAV repeat test for deleted function 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
CalDAV export already included RRULE in the output, but import was missing RRULE parsing. Now recurring tasks synced from calendar apps will have their recurrence pattern preserved. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The repeat_after column was removed in favor of the new RRULE-based repeats field. Sorting by repeat_after no longer works. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Update test assertions to use the new repeats/repeats_from_current_date fields instead of the deprecated repeat_after/repeat_mode fields. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Convert Todoist's natural language recurrence patterns to RFC 5545 RRULE format during migration. Supports common patterns: - Basic frequencies (daily, weekly, monthly, yearly) - Interval variations (every 2 days, every 3 weeks, etc.) - Weekday patterns (every monday, every friday) - Special patterns (weekdays, weekends) - "every other" variations Unknown or complex patterns are gracefully skipped with debug logging. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
TickTick exports the Repeat field directly in RRULE format, so we can use it as-is. Added normalizeTickTickRepeat() to handle edge cases: - Remove RRULE: prefix if present - Handle multiline repeat rules (take first one) - Trim whitespace 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Convert if-else chain to switch statement in tasks.go (gocritic) - Fix gofmt formatting in typesense.go - Update test expectation to match fixture title (task go-vikunja#28 "repeats") 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Search from now (not baseDate) when due date is in the past to avoid returning past dates - Search from due date when it's in the future to get proper interval - Calculate timeDiff from baseDate when no due date exists - Check RepeatsFromCurrentDate before timeDiff to ensure it takes priority - Use nextOccurrence instead of now for RepeatsFromCurrentDate dates 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
00a87dc to
08cc19a
Compare
kolaente
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey thanks for the PR!
I've only done a light review now, will take another look at this after the 1.0 release (early February). This is quite a large feature, I'd like to get it right and right now we're pretty close to the 1.0 release.
| * Parses an RRULE string into a structured object. | ||
| * Example: "FREQ=DAILY;INTERVAL=2" -> { freq: 'DAILY', interval: 2 } | ||
| */ | ||
| export function parseRRule(rrule: string): ParsedRRule | null { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should try to not parse as much in the frontend about this, because it essentially doubles the logic to what we have in the API. But I'm unsure here how much is actually feasible and what a good data model would look like to pass this across.
| } | ||
| type tasks20251228214425 struct { | ||
| // The day of month (1-31) to repeat on for monthly repeats. 0 means use the due date's day. | ||
| RepeatDay int8 `xorm:"tinyint null default 0"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we really need this as a new field? Isn't this part of the RRULE?
|
|
||
| func setTaskDatesDefault(oldTask, newTask *Task) { | ||
| if oldTask.RepeatAfter == 0 { | ||
| // Parse the RRULE string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should validate the rule when saving as well (does this already happen?)
| timeDiff = nextOccurrence.Sub(baseDate) | ||
| } | ||
| // Always set the due date for repeating tasks - if there was no due date, | ||
| // the next occurrence becomes the new due date |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a breaking change, right now the repeating interval does nothing when no dates are specified. Please keep it that way (we should add better validation and error handling around this, but that's another topic)
|
|
||
| // todoistDueStringToRRule converts Todoist's natural language due string to an RRULE. | ||
| // Supports common patterns like "every day", "every week", "every monday", "every 2 weeks", etc. | ||
| func todoistDueStringToRRule(dueString string, isRecurring bool) string { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Their docs seem to indicate that it is possible to do this in a lot of languages, not sure if we'll be able to handle all of this (and maintain it). Maybe there's a library to do it?
Closes #1369
This PR migrates Vikunja's recurrence system from the legacy
repeat_after/repeat_mode/repeat_dayfields to RFC 5545 compliant RRULE strings, as suggested by @kolaente.Changes
Backend
teambition/rrule-golibrary for RRULE parsing20251229100000) converts legacy repeat data to RRULE format and drops old columnsFrontend
rrule.tshelper for parsing/formatting RRULE stringsRepeatAfter.vuecomponent with new UIRRULE Format
Recurrence is now stored as RFC 5545 RRULE strings:
FREQ=DAILY;INTERVAL=1- Every dayFREQ=WEEKLY;INTERVAL=1- Every weekFREQ=MONTHLY;INTERVAL=1;BYMONTHDAY=15- Monthly on the 15thFREQ=YEARLY;INTERVAL=1- Yearly (calendar-aware)This format supports flexible patterns like BYDAY for weekly recurrence and BYMONTHDAY for monthly, providing the foundation for more complex patterns (like "first Tuesday of every other month") in future iterations.
Reopened from #2029 to use a dedicated branch and avoid polluting the PR with unrelated fork commits.