Compare commits

...

14 Commits

Author SHA1 Message Date
335c859e65 docs(conductor): Add Track Completion Protocol to workflow
All checks were successful
Build SilverBullet Plug / build (push) Successful in 48s
2026-02-20 10:52:45 -08:00
0f04df1435 chore: Bump version to 0.3.29 2026-02-20 10:52:45 -08:00
e8d74e4622 chore(conductor): Mark track 'Fix TypeError: r.replace is not a function in icalendar.ts' as complete 2026-02-20 10:52:45 -08:00
657f4f2c3a conductor(plan): Mark phase 'Verification & Cleanup' as complete 2026-02-20 10:52:45 -08:00
ea85b56c5c conductor(plan): Mark task 'Verify fix and check for regressions' as complete 2026-02-20 10:52:45 -08:00
df8a0e12c2 conductor(plan): Mark phase 'Implementation' as complete 2026-02-20 10:52:45 -08:00
9b53b77929 conductor(checkpoint): Checkpoint end of Phase 2 2026-02-20 10:52:45 -08:00
cafdaf7006 conductor(plan): Mark task 'Implement defensive check' as complete 2026-02-20 10:52:45 -08:00
f8640533be fix(icalendar): Handle non-string rrule property gracefully 2026-02-20 10:52:45 -08:00
cecaac6638 conductor(plan): Mark phase 'Reproduction & Test Setup' as complete 2026-02-20 10:52:45 -08:00
54bb7a8540 conductor(checkpoint): Checkpoint end of Phase 1 2026-02-20 10:52:45 -08:00
6eda06aca6 conductor(plan): Mark task 'Create reproduction test case' as complete 2026-02-20 10:52:45 -08:00
b5c718f286 test(icalendar): Add reproduction test for non-string rrule 2026-02-20 10:52:45 -08:00
0bea770814 chore(conductor): Add new track 'Fix TypeError: r.replace is not a function' 2026-02-20 10:52:45 -08:00
9 changed files with 121 additions and 3 deletions

View File

@@ -6,3 +6,8 @@ This file tracks all major tracks for the project. Each track has its own detail
- [x] **Track: Upgrade the SilverBullet iCalendar plug to use DST-aware timezone resolution and add recurring event support using rrule.**
*Link: [./tracks/timezone_rrule_20260218/](./tracks/timezone_rrule_20260218/)*
---
- [x] **Track: Fix TypeError: r.replace is not a function in icalendar.ts**
*Link: [./tracks/fix_rrule_type_error_20260219/](./tracks/fix_rrule_type_error_20260219/)*

View File

@@ -0,0 +1,5 @@
# Track fix_rrule_type_error_20260219 Context
- [Specification](./spec.md)
- [Implementation Plan](./plan.md)
- [Metadata](./metadata.json)

View File

@@ -0,0 +1,8 @@
{
"track_id": "fix_rrule_type_error_20260219",
"type": "bug",
"status": "new",
"created_at": "2026-02-19T00:00:00Z",
"updated_at": "2026-02-19T00:00:00Z",
"description": "Fix TypeError: r.replace is not a function in icalendar.ts"
}

View File

@@ -0,0 +1,20 @@
# Implementation Plan - Fix `r.replace is not a function`
## Phase 1: Reproduction & Test Setup [checkpoint: df0ddaf]
- [x] Task: Create a reproduction test case 1a36c64
- [x] Create a new test case in `icalendar_test.ts` that mocks an event with a non-string `rrule` property (e.g., an object or number).
- [x] Run the test to confirm it fails with the expected `TypeError`.
- [x] Task: Conductor - User Manual Verification 'Reproduction & Test Setup' (Protocol in workflow.md)
## Phase 2: Implementation [checkpoint: 1c48f78]
- [x] Task: Implement defensive check in `icalendar.ts` d7401dd
- [x] Modify `expandRecurrences` function to check if `rruleStr` is a string before calling `.replace()`.
- [x] If `rruleStr` is not a string, log a warning and return the original event (non-recurring fallback).
- [x] Run the reproduction test again to confirm it passes.
- [x] Task: Conductor - User Manual Verification 'Implementation' (Protocol in workflow.md)
## Phase 3: Verification & Cleanup
- [x] Task: Verify fix and check for regressions
- [x] Run all tests in `icalendar_test.ts` to ensure existing functionality is preserved.
- [x] (Optional) Verify with actual calendar sync if possible/safe.
- [x] Task: Conductor - User Manual Verification 'Verification & Cleanup' (Protocol in workflow.md)

View File

@@ -0,0 +1,25 @@
# Specification: Fix `r.replace is not a function` in `expandRecurrences`
## Overview
This track addresses a `TypeError: r.replace is not a function` error occurring in `icalendar.ts` during calendar synchronization. The error suggests that the `rrule` property of an event is not a string when it reaches the `expandRecurrences` function, causing the subsequent `.replace()` call to fail. This is likely due to the `ts-ics` parser returning a non-string value (e.g., an object or `undefined`) for the `rrule` property in certain scenarios (specifically observed with Outlook calendars).
## Functional Requirements
- **Defensive RRULE Handling:** The `expandRecurrences` function in `icalendar.ts` must safely handle cases where `rrule` (or `recurrenceRule`) is not a string.
- **Graceful Fallback:** If `rrule` is not a string:
- It should be ignored/logged if it cannot be interpreted as a valid RRULE string, preventing the crash.
- The event should still be processed (treated as a non-recurring event if the rule is invalid), rather than crashing the entire sync for that event.
## Non-Functional Requirements
- **Stability:** The plug should not crash or throw unhandled exceptions during sync due to malformed or unexpected property types in the source ICS data.
- **Logging:** Maintain existing error logging but ensure the error message is descriptive (e.g., "Invalid RRULE type: object").
## Implementation Steps
1. **Reproduce Issue:** Create a unit test in `icalendar_test.ts` that mocks an `icsEvent` with a non-string `rrule` property (e.g., an object or number) and calls `expandRecurrences`.
2. **Implement Fix:** Modify `icalendar.ts` to check the type of `rruleStr` before calling `.replace()`.
- If it's not a string, attempt to convert it or return the original event (as if no recurrence rule exists) with a warning.
3. **Verify:** Run the new unit test to confirm the fix.
## Acceptance Criteria
- [ ] A new unit test case exists in `icalendar_test.ts` that passes with a non-string `rrule`.
- [ ] The `expandRecurrences` function no longer throws `TypeError: r.replace is not a function` when `rrule` is not a string.
- [ ] The sync process completes successfully even if some events have malformed `rrule` properties.

View File

@@ -134,6 +134,24 @@ All tasks follow a strict lifecycle:
10. **Announce Completion:** Inform the user that the phase is complete and the checkpoint has been created, with the detailed verification report attached as a git note.
### Track Completion Protocol
**Trigger:** This protocol is executed when all phases and tasks in a track are complete.
1. **Version Bump (Code Changes Only):**
- If the track involved code changes (i.e., not purely documentation), you **must** bump the project version number.
- Update `deno.json` and any other version-tracked files (e.g., `icalendar.ts`) with the new version.
- Commit the version bump with message: `chore: Bump version to <new_version>`.
2. **Push Changes (Code Changes Only):**
- If the track involved code changes, you **must** push the changes to the remote repository.
- **Command:** `git push`
3. **Monitor CI/CD (Code Changes Only):**
- After pushing, you **must** monitor the resulting Gitea Action to ensure it completes successfully.
- Use the `gitea-push-watch` skill if available, or check the Gitea interface manually.
- **Requirement:** The track is NOT complete until the CI/CD pipeline passes. If it fails, you must investigate and fix the issue.
### Quality Gates
Before marking any task complete, verify:

View File

@@ -1,6 +1,6 @@
{
"name": "icalendar-plug",
"version": "0.3.28",
"version": "0.3.29",
"nodeModulesDir": "auto",
"tasks": {
"sync-version": "deno run -A scripts/sync-version.ts",

View File

@@ -3,7 +3,7 @@ import { convertIcsCalendar } from "https://esm.sh/ts-ics@2.4.0";
import { RRule, RRuleSet } from "rrule";
import { getUtcOffsetMs, resolveIanaName } from "./timezones.ts";
const VERSION = "0.3.28";
const VERSION = "0.3.29";
const CACHE_KEY = "icalendar:lastSync";
console.log(`[iCalendar] Plug script executing at top level (Version ${VERSION})`);
@@ -154,6 +154,11 @@ export function expandRecurrences(icsEvent: any, windowDays = 365): any[] {
const rruleStr = icsEvent.rrule || (icsEvent as any).recurrenceRule;
if (!rruleStr) return [icsEvent];
if (typeof rruleStr !== "string") {
console.warn(`[iCalendar] Invalid rrule type (${typeof rruleStr}) for event "${icsEvent.summary || "Untitled"}". Treating as non-recurring.`);
return [icsEvent];
}
try {
const set = new RRuleSet();
const cleanRule = rruleStr.replace(/^RRULE:/i, "");

View File

@@ -121,4 +121,36 @@ Deno.test("expandRecurrences - custom windowDays", () => {
// It should include everything in the last 7 days + next 2 days.
// Since it's daily, that's roughly 7 + 2 + 1 = 10 events.
assert(results.length >= 3, "Should have at least today and 2 future days");
});
});
Deno.test("expandRecurrences - non-string rrule (Reproduction)", () => {
const now = new Date();
const startStr = localDateString(now);
const icsEvent = {
summary: "Bug Reproduction Event",
start: startStr,
rrule: 12345 // Simulating the malformed data
};
// Spy on console.warn
let warningLogged = false;
const originalConsoleWarn = console.warn;
console.warn = (...args) => {
if (args[0].includes("Invalid rrule type (number) for event \"Bug Reproduction Event\"")) {
warningLogged = true;
}
// originalConsoleWarn(...args); // Keep silent for test
};
try {
const result = expandRecurrences(icsEvent, 30);
// Should return the original event as fallback
assertEquals(result.length, 1);
assertEquals(result[0], icsEvent);
} finally {
console.warn = originalConsoleWarn;
}
assert(warningLogged, "Should have logged a warning for non-string rrule");
});