{
	"issue": {
		"url": "https://api.github.com/repos/laurent22/joplin/issues/14406",
		"repository_url": "https://api.github.com/repos/laurent22/joplin",
		"labels_url": "https://api.github.com/repos/laurent22/joplin/issues/14406/labels{/name}",
		"comments_url": "https://api.github.com/repos/laurent22/joplin/issues/14406/comments",
		"events_url": "https://api.github.com/repos/laurent22/joplin/issues/14406/events",
		"html_url": "https://github.com/laurent22/joplin/pull/14406",
		"id": 3970657006,
		"node_id": "PR_kwDOBLftOs7FRi8T",
		"number": 14406,
		"title": "All: Adds #14391: Feature/support repeating todo notifications ",
		"user": {
			"login": "karan10i",
			"id": 68427055,
			"node_id": "MDQ6VXNlcjY4NDI3MDU1",
			"avatar_url": "https://avatars.githubusercontent.com/u/68427055?v=4",
			"gravatar_id": "",
			"url": "https://api.github.com/users/karan10i",
			"html_url": "https://github.com/karan10i",
			"followers_url": "https://api.github.com/users/karan10i/followers",
			"following_url": "https://api.github.com/users/karan10i/following{/other_user}",
			"gists_url": "https://api.github.com/users/karan10i/gists{/gist_id}",
			"starred_url": "https://api.github.com/users/karan10i/starred{/owner}{/repo}",
			"subscriptions_url": "https://api.github.com/users/karan10i/subscriptions",
			"organizations_url": "https://api.github.com/users/karan10i/orgs",
			"repos_url": "https://api.github.com/users/karan10i/repos",
			"events_url": "https://api.github.com/users/karan10i/events{/privacy}",
			"received_events_url": "https://api.github.com/users/karan10i/received_events",
			"type": "User",
			"user_view_type": "public",
			"site_admin": false
		},
		"labels": [
			{
				"id": 519700743,
				"node_id": "MDU6TGFiZWw1MTk3MDA3NDM=",
				"url": "https://api.github.com/repos/laurent22/joplin/labels/enhancement",
				"name": "enhancement",
				"color": "5ebeff",
				"default": true,
				"description": "Feature requests and code enhancements"
			},
			{
				"id": 1079605111,
				"node_id": "MDU6TGFiZWwxMDc5NjA1MTEx",
				"url": "https://api.github.com/repos/laurent22/joplin/labels/mobile",
				"name": "mobile",
				"color": "ffc274",
				"default": false,
				"description": "All mobile platforms"
			},
			{
				"id": 1080341216,
				"node_id": "MDU6TGFiZWwxMDgwMzQxMjE2",
				"url": "https://api.github.com/repos/laurent22/joplin/labels/desktop",
				"name": "desktop",
				"color": "ffc274",
				"default": false,
				"description": "All desktop platforms"
			},
			{
				"id": 1198535691,
				"node_id": "MDU6TGFiZWwxMTk4NTM1Njkx",
				"url": "https://api.github.com/repos/laurent22/joplin/labels/iOS",
				"name": "iOS",
				"color": "bfd4f2",
				"default": false,
				"description": ""
			},
			{
				"id": 1366672041,
				"node_id": "MDU6TGFiZWwxMzY2NjcyMDQx",
				"url": "https://api.github.com/repos/laurent22/joplin/labels/android",
				"name": "android",
				"color": "bfd4f2",
				"default": false,
				"description": ""
			},
			{
				"id": 2115946553,
				"node_id": "MDU6TGFiZWwyMTE1OTQ2NTUz",
				"url": "https://api.github.com/repos/laurent22/joplin/labels/database",
				"name": "database",
				"color": "91ca55",
				"default": false,
				"description": "Database issues"
			},
			{
				"id": 2676541710,
				"node_id": "MDU6TGFiZWwyNjc2NTQxNzEw",
				"url": "https://api.github.com/repos/laurent22/joplin/labels/server",
				"name": "server",
				"color": "ffc274",
				"default": false,
				"description": "Issues related to Joplin Server"
			}
		],
		"state": "closed",
		"locked": false,
		"assignees": [],
		"milestone": null,
		"comments": 4,
		"created_at": "2026-02-20T22:37:58Z",
		"updated_at": "2026-02-22T08:59:25Z",
		"closed_at": "2026-02-22T08:59:14Z",
		"assignee": null,
		"author_association": "NONE",
		"active_lock_reason": null,
		"draft": false,
		"pull_request": {
			"url": "https://api.github.com/repos/laurent22/joplin/pulls/14406",
			"html_url": "https://github.com/laurent22/joplin/pull/14406",
			"diff_url": "https://github.com/laurent22/joplin/pull/14406.diff",
			"patch_url": "https://github.com/laurent22/joplin/pull/14406.patch",
			"merged_at": null
		},
		"body": "## Summary\r\n\r\nFixes #14391: Feature/support repeating todo notifications\r\n\r\nCurrently, Joplin alarms are one-shot — once a todo alarm fires it is deleted and never repeats. There is no way to set a recurring reminder (e.g. daily standup, weekly review).\r\n\r\nThis PR adds support for repeating alarm notifications using the rrule standard.\r\n\r\n## Proposed Changes\r\n\r\n- **Database**: Added migration 50 to add `alarm_interval` to the notes table and `repeat_interval`/`last_trigger_time` to the alarms table (with NOT NULL defaults for all new columns).\r\n- **Alarm model (`Alarm.ts`)**: Added `deleteExpiredNonRepeatingAlarms` to only garbage-collect alarms with no repeat interval, and `rescheduleRepeatingAlarm` to advance a repeating alarm to its next occurrence after it fires.\r\n- **AlarmService (`AlarmService.ts`)**: Updated `updateNoteNotification` to detect `repeat_interval` changes and reschedule accordingly. Updated `handleNotificationTrigger` to clear the fired notification and reschedule repeating alarms instead of deleting them. Added early-due rescheduling for alarms that have already passed.\r\n- **Drivers (`AlarmServiceDriver*.ts`)**: Typed `service_` fields properly across all platform drivers (Node, Android, iOS). Wired `alarm_interval` through to the native notification schedulers. Added null guard for logger access in the Node driver.\r\n- **Desktop UI (`NotePropertiesDialog.tsx`)**: Added an \"Alarm Interval\" inline select (None / Daily / Weekly / Monthly) to the Note Properties dialog for todo notes.\r\n- **Mobile UI (SelectDateTimeDialog.tsx)**: Added repeat interval pill selector with `StyleSheet.create` for styles. Fixed `getDerivedStateFromProps` to track `prevInterval` so user pill selection is not overridden by the prop on re-render. Wrapped test renders in `TestProviderStack` (provides `SafeAreaProvider` + `FocusControl.Provider`).\r\n- **Utility (`rruleUtils.ts`)**: Added helpers `simpleIntervalToRRule`, `isRRuleString`, and `createRRule` to convert plain interval strings to RFC 5545 RRULE expressions.\r\n- **Server**: Added `alarm_interval` to serialized note template in test fixtures to match the new schema column order.\r\n- **Jest setup**: Added mock for `react-native-modal-datetime-picker` to prevent native module crashes in app-mobile tests.\r\n\r\n## Test Plan\r\n\r\n- Added 16 unit tests for `rruleUtils` covering interval conversion and RRULE parsing\r\n- Added 16 unit tests for the `Alarm` model covering repeating/non-repeating save, retrieval, and rescheduling\r\n- Added 12 integration tests for `AlarmService` covering scheduling, rescheduling, garbage collection, `repeat_interval` update detection, early-due rescheduling, and clearNotification-before-reschedule\r\n- Added 7 UI tests for `SelectDateTimeDialog` covering render, interval pills, prop initialisation, Save/Clear/Cancel callbacks\r\n- TypeScript compiles with zero errors\r\n- Migration 50 runs cleanly on an existing v49 database — all columns added without errors\r\n- Note Properties dialog opens, saves, and displays Alarm Interval correctly on desktop\r\n- All 48 lib tests and 7 mobile tests pass locally\r\n\r\n<img width=\"1264\" height=\"832\" alt=\"Screenshot 2026-02-21 at 16 15 26\" src=\"https://github.com/user-attachments/assets/32ec9f19-c887-4352-943a-551047511fbe\" />\r\n\r\n",
		"closed_by": {
			"login": "laurent22",
			"id": 1285584,
			"node_id": "MDQ6VXNlcjEyODU1ODQ=",
			"avatar_url": "https://avatars.githubusercontent.com/u/1285584?v=4",
			"gravatar_id": "",
			"url": "https://api.github.com/users/laurent22",
			"html_url": "https://github.com/laurent22",
			"followers_url": "https://api.github.com/users/laurent22/followers",
			"following_url": "https://api.github.com/users/laurent22/following{/other_user}",
			"gists_url": "https://api.github.com/users/laurent22/gists{/gist_id}",
			"starred_url": "https://api.github.com/users/laurent22/starred{/owner}{/repo}",
			"subscriptions_url": "https://api.github.com/users/laurent22/subscriptions",
			"organizations_url": "https://api.github.com/users/laurent22/orgs",
			"repos_url": "https://api.github.com/users/laurent22/repos",
			"events_url": "https://api.github.com/users/laurent22/events{/privacy}",
			"received_events_url": "https://api.github.com/users/laurent22/received_events",
			"type": "User",
			"user_view_type": "public",
			"site_admin": false
		},
		"reactions": {
			"url": "https://api.github.com/repos/laurent22/joplin/issues/14406/reactions",
			"total_count": 0,
			"+1": 0,
			"-1": 0,
			"laugh": 0,
			"hooray": 0,
			"confused": 0,
			"heart": 0,
			"rocket": 0,
			"eyes": 0
		},
		"timeline_url": "https://api.github.com/repos/laurent22/joplin/issues/14406/timeline",
		"performed_via_github_app": null,
		"state_reason": null
	},
	"comments": [
		{
			"url": "https://api.github.com/repos/laurent22/joplin/issues/comments/3937481211",
			"html_url": "https://github.com/laurent22/joplin/pull/14406#issuecomment-3937481211",
			"issue_url": "https://api.github.com/repos/laurent22/joplin/issues/14406",
			"id": 3937481211,
			"node_id": "IC_kwDOBLftOs7qsTH7",
			"user": {
				"login": "github-actions[bot]",
				"id": 41898282,
				"node_id": "MDM6Qm90NDE4OTgyODI=",
				"avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4",
				"gravatar_id": "",
				"url": "https://api.github.com/users/github-actions%5Bbot%5D",
				"html_url": "https://github.com/apps/github-actions",
				"followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers",
				"following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}",
				"gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}",
				"starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}",
				"subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions",
				"organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs",
				"repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos",
				"events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}",
				"received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events",
				"type": "Bot",
				"user_view_type": "public",
				"site_admin": false
			},
			"created_at": "2026-02-20T22:38:10Z",
			"updated_at": "2026-02-22T08:59:26Z",
			"body": "****CLA Assistant Lite bot**** All contributors have signed the CLA  ✍️ ✅",
			"author_association": "CONTRIBUTOR",
			"reactions": {
				"url": "https://api.github.com/repos/laurent22/joplin/issues/comments/3937481211/reactions",
				"total_count": 0,
				"+1": 0,
				"-1": 0,
				"laugh": 0,
				"hooray": 0,
				"confused": 0,
				"heart": 0,
				"rocket": 0,
				"eyes": 0
			},
			"performed_via_github_app": {
				"id": 15368,
				"client_id": "Iv1.05c79e9ad1f6bdfa",
				"slug": "github-actions",
				"node_id": "MDM6QXBwMTUzNjg=",
				"owner": {
					"login": "github",
					"id": 9919,
					"node_id": "MDEyOk9yZ2FuaXphdGlvbjk5MTk=",
					"avatar_url": "https://avatars.githubusercontent.com/u/9919?v=4",
					"gravatar_id": "",
					"url": "https://api.github.com/users/github",
					"html_url": "https://github.com/github",
					"followers_url": "https://api.github.com/users/github/followers",
					"following_url": "https://api.github.com/users/github/following{/other_user}",
					"gists_url": "https://api.github.com/users/github/gists{/gist_id}",
					"starred_url": "https://api.github.com/users/github/starred{/owner}{/repo}",
					"subscriptions_url": "https://api.github.com/users/github/subscriptions",
					"organizations_url": "https://api.github.com/users/github/orgs",
					"repos_url": "https://api.github.com/users/github/repos",
					"events_url": "https://api.github.com/users/github/events{/privacy}",
					"received_events_url": "https://api.github.com/users/github/received_events",
					"type": "Organization",
					"user_view_type": "public",
					"site_admin": false
				},
				"name": "GitHub Actions",
				"description": "Automate your workflow from idea to production",
				"external_url": "https://help.github.com/en/actions",
				"html_url": "https://github.com/apps/github-actions",
				"created_at": "2018-07-30T09:30:17Z",
				"updated_at": "2026-05-05T14:51:38Z",
				"permissions": {
					"actions": "write",
					"administration": "read",
					"artifact_metadata": "write",
					"attestations": "write",
					"checks": "write",
					"code_quality": "write",
					"contents": "write",
					"copilot_requests": "write",
					"deployments": "write",
					"discussions": "write",
					"issues": "write",
					"merge_queues": "write",
					"metadata": "read",
					"models": "read",
					"packages": "write",
					"pages": "write",
					"pull_requests": "write",
					"repository_hooks": "write",
					"repository_projects": "write",
					"security_events": "write",
					"statuses": "write",
					"vulnerability_alerts": "read"
				},
				"events": [
					"branch_protection_rule",
					"check_run",
					"check_suite",
					"create",
					"delete",
					"deployment",
					"deployment_status",
					"discussion",
					"discussion_comment",
					"fork",
					"gollum",
					"issues",
					"issue_comment",
					"label",
					"merge_group",
					"milestone",
					"page_build",
					"public",
					"pull_request",
					"pull_request_review",
					"pull_request_review_comment",
					"push",
					"registry_package",
					"release",
					"repository",
					"repository_dispatch",
					"status",
					"watch",
					"workflow_dispatch",
					"workflow_run"
				]
			}
		},
		{
			"url": "https://api.github.com/repos/laurent22/joplin/issues/comments/3937481870",
			"html_url": "https://github.com/laurent22/joplin/pull/14406#issuecomment-3937481870",
			"issue_url": "https://api.github.com/repos/laurent22/joplin/issues/14406",
			"id": 3937481870,
			"node_id": "IC_kwDOBLftOs7qsTSO",
			"user": {
				"login": "coderabbitai[bot]",
				"id": 136622811,
				"node_id": "BOT_kgDOCCSy2w",
				"avatar_url": "https://avatars.githubusercontent.com/in/347564?v=4",
				"gravatar_id": "",
				"url": "https://api.github.com/users/coderabbitai%5Bbot%5D",
				"html_url": "https://github.com/apps/coderabbitai",
				"followers_url": "https://api.github.com/users/coderabbitai%5Bbot%5D/followers",
				"following_url": "https://api.github.com/users/coderabbitai%5Bbot%5D/following{/other_user}",
				"gists_url": "https://api.github.com/users/coderabbitai%5Bbot%5D/gists{/gist_id}",
				"starred_url": "https://api.github.com/users/coderabbitai%5Bbot%5D/starred{/owner}{/repo}",
				"subscriptions_url": "https://api.github.com/users/coderabbitai%5Bbot%5D/subscriptions",
				"organizations_url": "https://api.github.com/users/coderabbitai%5Bbot%5D/orgs",
				"repos_url": "https://api.github.com/users/coderabbitai%5Bbot%5D/repos",
				"events_url": "https://api.github.com/users/coderabbitai%5Bbot%5D/events{/privacy}",
				"received_events_url": "https://api.github.com/users/coderabbitai%5Bbot%5D/received_events",
				"type": "Bot",
				"user_view_type": "public",
				"site_admin": false
			},
			"created_at": "2026-02-20T22:38:24Z",
			"updated_at": "2026-02-21T16:03:00Z",
			"body": "<!-- This is an auto-generated comment: summarize by coderabbit.ai -->\n<!-- This is an auto-generated comment: review paused by coderabbit.ai -->\n\n> [!NOTE]\n> ## Reviews paused\n> \n> It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the `reviews.auto_review.auto_pause_after_reviewed_commits` setting.\n> \n> Use the following commands to manage reviews:\n> - `@coderabbitai resume` to resume automatic reviews.\n> - `@coderabbitai review` to trigger a single review.\n> \n> Use the checkboxes below for quick actions:\n> - [ ] <!-- {\"checkboxId\": \"7f6cc2e2-2e4e-497a-8c31-c9e4573e93d1\"} --> ▶️ Resume reviews\n> - [ ] <!-- {\"checkboxId\": \"e9bb8d72-00e8-4f67-9cb2-caf3b22574fe\"} --> 🔍 Trigger review\n\n<!-- end of auto-generated comment: review paused by coderabbit.ai -->\n\n<!-- walkthrough_start -->\n\n<details>\n<summary>📝 Walkthrough</summary>\n\n## Walkthrough\n\nAdds RRULE-based repeating alarms: DB fields for interval/last-trigger, RRULE utilities, Alarm model methods for next-trigger/update/cleanup, AlarmService rescheduling and trigger handling, driver changes to await post-trigger handling, and mobile/desktop UI for interval selection and persistence.\n\n## Changes\n\n|Cohort / File(s)|Summary|\n|---|---|\n|**Database Schema & Migrations** <br> `packages/lib/services/database/types.ts`, `packages/lib/services/database/migrations/50.ts`, `packages/lib/services/database/migrations/index.ts`|Adds `alarm_interval` to notes and `repeat_interval` + `last_trigger_time` to alarms; adds migration 50 and registers it.|\n|**Alarm Model & Tests** <br> `packages/lib/models/Alarm.ts`, `packages/lib/models/Alarm.test.ts`|Adds RRULE-aware methods: `shouldTriggerAlarm()`, `calculateNextTriggerTime()`, `updateLastTriggered()`, `deleteExpiredNonRepeatingAlarms()` and integrates rrulUtils; tests for trigger logic and cleanup.|\n|**Alarm Service Core & Tests** <br> `packages/lib/services/AlarmService.ts`, `packages/lib/services/AlarmService.test.ts`|Persist `repeat_interval`; refine garbage collection to only remove expired non-repeating alarms; handle repeat_interval changes and rescheduling; add `handleNotificationTrigger(alarmId)` to update/delete/reschedule after notification; comprehensive tests added.|\n|**Alarm Service Drivers** <br> `packages/app-mobile/services/AlarmServiceDriver.android.ts`, `packages/app-mobile/services/AlarmServiceDriver.ios.ts`, `packages/lib/services/AlarmServiceDriverNode.ts`|Introduce `AlarmServiceInterface` injection via `setService()`; make notification callbacks async and await `service.handleNotificationTrigger()` where applicable; add Node driver `logger()` accessor.|\n|**RRule Utilities & Tests** <br> `packages/lib/utils/rruleUtils.ts`, `packages/lib/utils/rruleUtils.test.ts`|New helpers: `simpleIntervalToRRule()`, `isRRuleString()`, `createRRule()` to convert/validate/build RRULE strings; unit tests added.|\n|**Mobile UI & Tests** <br> `packages/app-mobile/components/SelectDateTimeDialog.tsx`, `packages/app-mobile/components/SelectDateTimeDialog.test.tsx`, `packages/app-mobile/components/screens/Note/Note.tsx`|Add interval selection UI (none/daily/weekly/monthly); dialog/handler signatures updated to accept (date: Date|null, interval: string); persist `alarm_interval` alongside `todo_due`; tests added.|\n|**Desktop UI** <br> `packages/app-desktop/gui/NotePropertiesDialog.tsx`|Add `todo_due` and `alarm_interval` handling in properties dialog and call AlarmService to schedule/update notifications when alarm fields change.|\n|**Note Model** <br> `packages/lib/models/Note.ts`|Clear `alarm_interval` when a todo is marked complete and dispatch alarm-field-change events when `alarm_interval` changes.|\n|**Dependencies** <br> `packages/lib/package.json`|Add runtime dependency `rrule` (v2.8.1).|\n|**Tooling & Config** <br> `packages/tools/cspell/dictionary1.txt`, `packages/tools/cspell/dictionary3.txt`, `.eslintignore`, `.gitignore`|Add RRULE-related dictionary entries; update ignore lists for new tests and migration files.|\n|**Testing / Setup** <br> `packages/app-mobile/jest.setup.js`, `packages/server/src/utils/testing/serializedItems.ts`|Add Jest mock for datetime picker; include `alarm_interval` in serialized note fixtures.|\n\n## Sequence Diagram(s)\n\n```mermaid\nsequenceDiagram\n    participant UI as Mobile/Desktop UI\n    participant AlarmService\n    participant Driver as AlarmServiceDriver\n    participant OS as OS Notification System\n    participant DB as Database\n\n    Note over UI,DB: Create or update a note with todo_due + alarm_interval\n    UI->>AlarmService: updateNoteNotification(note)\n    AlarmService->>DB: createOrUpdateAlarm(repeat_interval, trigger_time)\n    AlarmService->>Driver: scheduleNotification(alarmId, triggerTime)\n    Driver->>OS: scheduleOSAlarm(alarmId, triggerTime)\n\n    Note over OS,Driver: Notification fires\n    OS->>Driver: deliverNotification(alarmId)\n    Driver->>AlarmService: handleNotificationTrigger(alarmId)\n\n    alt repeating alarm\n        AlarmService->>DB: loadAlarm(alarmId)\n        AlarmService->>AlarmService: calculateNextTriggerTime(...)\n        AlarmService->>DB: update last_trigger_time & trigger_time\n        AlarmService->>Driver: scheduleNotification(alarmId, nextTrigger)\n        Driver->>OS: scheduleOSAlarm(alarmId, nextTrigger)\n    else non-repeating alarm\n        AlarmService->>Driver: clearNotification(alarmId)\n        AlarmService->>DB: deleteAlarm(alarmId)\n    end\n```\n\n</details>\n\n<!-- walkthrough_end -->\n\n\n<!-- pre_merge_checks_walkthrough_start -->\n\n<details>\n<summary>🚥 Pre-merge checks | ✅ 4 | ❌ 1</summary>\n\n### ❌ Failed checks (1 warning)\n\n|     Check name     | Status     | Explanation                                                                           | Resolution                                                                         |\n| :----------------: | :--------- | :------------------------------------------------------------------------------------ | :--------------------------------------------------------------------------------- |\n| Docstring Coverage | ⚠️ Warning | Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. | Write docstrings for the functions missing them to satisfy the coverage threshold. |\n\n<details>\n<summary>✅ Passed checks (4 passed)</summary>\n\n|         Check name         | Status   | Explanation                                                                                                                                                                                                                                                                                        |\n| :------------------------: | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n|         Title check        | ✅ Passed | The title clearly references the main feature being implemented: repeating todo notifications, with specific issue reference `#14391`.                                                                                                                                                               |\n|      Description check     | ✅ Passed | The description comprehensively covers the changes, objectives, testing, and proposed approach with clear sections on database, models, services, UI, and utilities.                                                                                                                               |\n|     Linked Issues check    | ✅ Passed | All major requirements from `#14391` are implemented: database fields added [50], repeat interval UI provided [NotePropertiesDialog, SelectDateTimeDialog], AlarmService updated for rescheduling [AlarmService], RRULE support added [rruleUtils], and maintenance of repeating alarms implemented. |\n| Out of Scope Changes check | ✅ Passed | Changes remain focused on repeating alarm support. Minor additions (cspell dictionary, .eslintignore, .gitignore, Jest mock, serialized test items) are all supporting artifacts directly related to the core feature implementation and testing infrastructure.                                   |\n\n</details>\n\n<sub>✏️ Tip: You can configure your own custom pre-merge checks in the settings.</sub>\n\n</details>\n\n<!-- pre_merge_checks_walkthrough_end -->\n\n<!-- finishing_touch_checkbox_start -->\n\n<details>\n<summary>✨ Finishing Touches</summary>\n\n- [ ] <!-- {\"checkboxId\": \"7962f53c-55bc-4827-bfbf-6a18da830691\"} --> 📝 Generate docstrings (stacked PR)\n- [ ] <!-- {\"checkboxId\": \"3e1879ae-f29b-4d0d-8e06-d12b7ba33d98\"} --> 📝 Generate docstrings (commit on current branch)\n<details>\n<summary>🧪 Generate unit tests (beta)</summary>\n\n- [ ] <!-- {\"checkboxId\": \"f47ac10b-58cc-4372-a567-0e02b2c3d479\", \"radioGroupId\": \"utg-output-choice-group-unknown_comment_id\"} -->   Create PR with unit tests\n- [ ] <!-- {\"checkboxId\": \"07f1e7d6-8a8e-4e23-9900-8731c2c87f58\", \"radioGroupId\": \"utg-output-choice-group-unknown_comment_id\"} -->   Post copyable unit tests in a comment\n\n</details>\n\n</details>\n\n<!-- finishing_touch_checkbox_end -->\n\n<!-- tips_start -->\n\n---\n\n\n\n<sub>Comment `@coderabbitai help` to get the list of available commands and usage tips.</sub>\n\n<!-- tips_end -->\n\n<!-- internal state start -->\n\n\n<!-- [encoded data, 23304 characters] -->\n\n<!-- internal state end -->",
			"author_association": "CONTRIBUTOR",
			"reactions": {
				"url": "https://api.github.com/repos/laurent22/joplin/issues/comments/3937481870/reactions",
				"total_count": 0,
				"+1": 0,
				"-1": 0,
				"laugh": 0,
				"hooray": 0,
				"confused": 0,
				"heart": 0,
				"rocket": 0,
				"eyes": 0
			},
			"performed_via_github_app": {
				"id": 347564,
				"client_id": "Iv1.6aaafe4fe882736b",
				"slug": "coderabbitai",
				"node_id": "A_kwHOB96YWc4ABU2s",
				"owner": {
					"login": "coderabbitai",
					"id": 132028505,
					"node_id": "O_kgDOB96YWQ",
					"avatar_url": "https://avatars.githubusercontent.com/u/132028505?v=4",
					"gravatar_id": "",
					"url": "https://api.github.com/users/coderabbitai",
					"html_url": "https://github.com/coderabbitai",
					"followers_url": "https://api.github.com/users/coderabbitai/followers",
					"following_url": "https://api.github.com/users/coderabbitai/following{/other_user}",
					"gists_url": "https://api.github.com/users/coderabbitai/gists{/gist_id}",
					"starred_url": "https://api.github.com/users/coderabbitai/starred{/owner}{/repo}",
					"subscriptions_url": "https://api.github.com/users/coderabbitai/subscriptions",
					"organizations_url": "https://api.github.com/users/coderabbitai/orgs",
					"repos_url": "https://api.github.com/users/coderabbitai/repos",
					"events_url": "https://api.github.com/users/coderabbitai/events{/privacy}",
					"received_events_url": "https://api.github.com/users/coderabbitai/received_events",
					"type": "Organization",
					"user_view_type": "public",
					"site_admin": false
				},
				"name": "coderabbitai",
				"description": "# Transforming Code Reviews with AI\r\n\r\n## Features\r\n\r\n**Automated Reviews**: Continuous reviews of the pull requests including incremental commits. \r\n\r\n**Summarization**: Generates high-level summary and a technical walkthrough of the PR changes. \r\n\r\n**Line-by-line review**: Provides line-by-line suggestions committable with one click.\r\n\r\n**Codebase verification**:  Verifies the impact on the overall codebase and identifies missing changes.\r\n\r\n**Insights into your code**:  Ask any questions on your codebase within the pull request \r\n\r\n**Chat about your code** : Chat with the bot around your code. The more you chat, the smarter it gets.\r\n\r\n**Issue Validation**:  Validates the PR against the linked issues and identifies other related issues \r\n\r\n\r\n\r\n",
				"external_url": "https://coderabbit.ai?utm_source=cr_app&utm_medium=github",
				"html_url": "https://github.com/apps/coderabbitai",
				"created_at": "2023-06-14T15:47:27Z",
				"updated_at": "2026-03-14T07:58:44Z",
				"permissions": {
					"actions": "read",
					"checks": "write",
					"contents": "write",
					"discussions": "read",
					"issues": "write",
					"members": "read",
					"metadata": "read",
					"pull_requests": "write",
					"statuses": "write"
				},
				"events": [
					"issues",
					"issue_comment",
					"label",
					"organization",
					"pull_request",
					"pull_request_review",
					"pull_request_review_comment",
					"pull_request_review_thread",
					"release",
					"repository"
				]
			}
		},
		{
			"url": "https://api.github.com/repos/laurent22/joplin/issues/comments/3938585773",
			"html_url": "https://github.com/laurent22/joplin/pull/14406#issuecomment-3938585773",
			"issue_url": "https://api.github.com/repos/laurent22/joplin/issues/14406",
			"id": 3938585773,
			"node_id": "IC_kwDOBLftOs7qwgyt",
			"user": {
				"login": "karan10i",
				"id": 68427055,
				"node_id": "MDQ6VXNlcjY4NDI3MDU1",
				"avatar_url": "https://avatars.githubusercontent.com/u/68427055?v=4",
				"gravatar_id": "",
				"url": "https://api.github.com/users/karan10i",
				"html_url": "https://github.com/karan10i",
				"followers_url": "https://api.github.com/users/karan10i/followers",
				"following_url": "https://api.github.com/users/karan10i/following{/other_user}",
				"gists_url": "https://api.github.com/users/karan10i/gists{/gist_id}",
				"starred_url": "https://api.github.com/users/karan10i/starred{/owner}{/repo}",
				"subscriptions_url": "https://api.github.com/users/karan10i/subscriptions",
				"organizations_url": "https://api.github.com/users/karan10i/orgs",
				"repos_url": "https://api.github.com/users/karan10i/repos",
				"events_url": "https://api.github.com/users/karan10i/events{/privacy}",
				"received_events_url": "https://api.github.com/users/karan10i/received_events",
				"type": "User",
				"user_view_type": "public",
				"site_admin": false
			},
			"created_at": "2026-02-21T10:39:36Z",
			"updated_at": "2026-02-21T10:39:36Z",
			"body": "I have read the CLA Document and I hereby sign the CLA\r\n",
			"author_association": "NONE",
			"reactions": {
				"url": "https://api.github.com/repos/laurent22/joplin/issues/comments/3938585773/reactions",
				"total_count": 0,
				"+1": 0,
				"-1": 0,
				"laugh": 0,
				"hooray": 0,
				"confused": 0,
				"heart": 0,
				"rocket": 0,
				"eyes": 0
			},
			"performed_via_github_app": null
		},
		{
			"url": "https://api.github.com/repos/laurent22/joplin/issues/comments/3940532679",
			"html_url": "https://github.com/laurent22/joplin/pull/14406#issuecomment-3940532679",
			"issue_url": "https://api.github.com/repos/laurent22/joplin/issues/14406",
			"id": 3940532679,
			"node_id": "IC_kwDOBLftOs7q38HH",
			"user": {
				"login": "laurent22",
				"id": 1285584,
				"node_id": "MDQ6VXNlcjEyODU1ODQ=",
				"avatar_url": "https://avatars.githubusercontent.com/u/1285584?v=4",
				"gravatar_id": "",
				"url": "https://api.github.com/users/laurent22",
				"html_url": "https://github.com/laurent22",
				"followers_url": "https://api.github.com/users/laurent22/followers",
				"following_url": "https://api.github.com/users/laurent22/following{/other_user}",
				"gists_url": "https://api.github.com/users/laurent22/gists{/gist_id}",
				"starred_url": "https://api.github.com/users/laurent22/starred{/owner}{/repo}",
				"subscriptions_url": "https://api.github.com/users/laurent22/subscriptions",
				"organizations_url": "https://api.github.com/users/laurent22/orgs",
				"repos_url": "https://api.github.com/users/laurent22/repos",
				"events_url": "https://api.github.com/users/laurent22/events{/privacy}",
				"received_events_url": "https://api.github.com/users/laurent22/received_events",
				"type": "User",
				"user_view_type": "public",
				"site_admin": false
			},
			"created_at": "2026-02-22T08:59:14Z",
			"updated_at": "2026-02-22T08:59:15Z",
			"body": "Thanks for the submission.\r\n\r\nWe’re closing this PR because it is far too large in scope for the GSoC application phase. At this stage, we expect small, focused contributions (bug fixes or limited features) so we can properly assess your skills and approach.\r\n\r\nPRs that attempt to tackle broad or complex changes are not appropriate for this phase and are unlikely to be reviewed.",
			"author_association": "OWNER",
			"reactions": {
				"url": "https://api.github.com/repos/laurent22/joplin/issues/comments/3940532679/reactions",
				"total_count": 0,
				"+1": 0,
				"-1": 0,
				"laugh": 0,
				"hooray": 0,
				"confused": 0,
				"heart": 0,
				"rocket": 0,
				"eyes": 0
			},
			"performed_via_github_app": null
		}
	]
}