Skip to content

✨(calendar) add link to a CalDAV instance to accept events directly#584

Merged
sylvinus merged 2 commits into
mainfrom
caldav
May 26, 2026
Merged

✨(calendar) add link to a CalDAV instance to accept events directly#584
sylvinus merged 2 commits into
mainfrom
caldav

Conversation

@sylvinus
Copy link
Copy Markdown
Member

@sylvinus sylvinus commented Mar 9, 2026

First step in the interop with https://github.com/suitenumerique/calendars

Summary by CodeRabbit

  • New Features

    • CalDAV-backed mailbox calendar actions: RSVP (Accept/Decline/Maybe), Add to calendar, calendar chooser, calendar listing, and conflict detection — actions enqueue background tasks and return task IDs; new API endpoints documented.
  • UX / Style

    • Redesigned calendar-invite layout: compact controls, conflict warnings, calendar select, attendee pills, and status badges.
  • Behavior

    • Unified task-status polling with clearer exhausted/error messages; mailbox write permission enforced; instance-level CalDAV defaults and throttling added.
  • Localization

    • Added English and French calendar UI/status strings.
  • Tests

    • Extensive end-to-end and unit tests covering CalDAV flows, sanitization, and conflict handling.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 9, 2026

Caution

Review failed

Failed to post review comments

📝 Walkthrough

Walkthrough

Adds a CalDAV client/service, Celery tasks, mailbox-scoped DRF endpoints (rsvp/add/conflicts/calendars), settings/openapi/schema updates, extensive backend tests, Celery task registration, and frontend calendar-invite UI/components, hooks, styles and locales to support CalDAV-backed RSVP and add-to-calendar flows.

Changes

CalDAV integration

Layer / File(s) Summary
CalDAV service and core logic
src/backend/core/services/calendar/service.py
Implements CalDAVService, CalDAVError, request/SSRF protections, home-set discovery, calendar listing, conflict checking, event sanitization/storage, RSVP handling, and Django constructors.
Celery tasks and task wiring
src/backend/core/services/calendar/tasks.py, src/backend/core/tasks.py
Adds calendar_rsvp_task and calendar_add_event_task, _get_caldav_service, and exposes tasks for autodiscovery.
DRF API, permissions, serializers, URLs, OpenAPI
src/backend/core/api/viewsets/calendar.py, src/backend/core/api/permissions.py, src/backend/core/api/serializers.py, src/backend/core/urls.py, src/backend/core/api/openapi.json
Adds CalDAVChannelMixin, mailbox-scoped CalendarRsvpView, CalendarAddEventView, CalendarConflictsView, CalendarListView, HasWriteAccessToMailbox permission, serializer reserved keys for CALDAV, URL routes, and OpenAPI schemas for the endpoints.
Instance settings and pyproject deps
src/backend/messages/settings.py, src/backend/pyproject.toml
Adds CALDAV_DEFAULT_* settings, caldav_conflicts throttle, dev cache change, and runtime dependency icalendar==7.0.3 plus dev radicale.
Backend tests
src/backend/core/tests/api/test_calendar.py, plus minor test-doc fixes
Comprehensive end-to-end/unit tests for CalDAV service, tasks, API endpoints, SSRF guards, UID sanitization, and instance vs per-channel config behaviors.

Frontend calendar UI and supporting changes

Layer / File(s) Summary
Calendar-invite widget and wiring
src/frontend/src/features/layouts/components/thread-view/components/calendar-invite/*
CalendarInvite accepts optional mailboxId, loads calendars/conflicts, posts RSVP/add requests, seeds RSVP state, polls task status, shows toasts, and refactors event card/participants/footer.
Event display sanitization & tests
src/frontend/src/features/layouts/components/thread-view/components/calendar-invite/event-display.ts, event-display.test.ts
Adds cleanEventForDisplay and extractUrl with tests for trimming, prefix stripping, conference block extraction with allowlist, and deduplication.
Calendar select component
src/frontend/src/features/layouts/components/thread-view/components/calendar-invite/calendar-select.tsx
Adds controlled CalendarSelect with swatch rendering and memoized options.
Thread message/attachments integration
src/frontend/src/features/layouts/components/thread-view/components/thread-message/*, thread-message-footer.tsx, types.ts
Detects calendar MIME types, deduplicates calendar attachments, renders CalendarInvite widgets, and passes regularAttachments into footer.
Task status hook and consumers
src/frontend/src/hooks/use-task-status.ts, src/frontend/src/features/controlled-modals/message-importer/*, authenticated.tsx
Renames useImportTaskStatususeTaskStatus, adds exhaustedError option, local TaskMetadata type, and updates consumers to use the new hook; StepLoader handles falsy status with spinner.
Styles and locales
src/frontend/src/features/layouts/components/thread-view/components/calendar-invite/_index.scss, src/frontend/public/locales/common/*
Updates calendar-invite styles and adds/relocates many calendar-related i18n keys (en-US, fr-FR).
Misc frontend tweaks
src/frontend/src/features/ui/components/contact-chip/index.tsx, package.json
Adjusts ContactChip identity rendering and reorders dependencies entry ordering in package.json.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant API
  participant Celery
  participant CalDAV
  Client->>API: POST /mailboxes/{mailbox_id}/calendar/rsvp or /add or /conflicts
  API->>Celery: enqueue calendar task (rsvp/add) with mailbox/channel ids
  Celery->>CalDAV: CalDAVService.add_event/respond_to_event/check_conflicts
  CalDAV-->>Celery: result or CalDAVError
  Celery-->>API: task registered (task_id) or FAILURE
  API-->>Client: 200 (task_id) / 502 / 503 / 404 as mapped
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Suggested reviewers

  • jbpenrath
  • sdemagny
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (3)
src/frontend/src/features/layouts/components/thread-view/components/calendar-invite/_index.scss (1)

267-273: Consider layout for three-item conflict rows.

The conflict item uses justify-content: space-between with three children (conflict-summary, conflict-calendar, conflict-time). This distributes items with the summary on the left, calendar in the middle, and time on the right. On narrow viewports, this may cause text truncation or awkward spacing.

Consider using flex-wrap: wrap or adjusting the layout so content can flow more gracefully when space is constrained.

💡 Optional: Allow wrapping for conflict items
 .calendar-invite__conflict-item {
     display: flex;
     justify-content: space-between;
     align-items: center;
     gap: var(--c--globals--spacings--sm);
     padding: var(--c--globals--spacings--3xs) 0;
+    flex-wrap: wrap;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/frontend/src/features/layouts/components/thread-view/components/calendar-invite/_index.scss`
around lines 267 - 273, The .calendar-invite__conflict-item currently uses
justify-content: space-between which spreads .conflict-summary,
.conflict-calendar, and .conflict-time awkwardly on narrow viewports; update
.calendar-invite__conflict-item to allow wrapping (e.g., add flex-wrap: wrap)
and adjust child flex rules so .conflict-summary can shrink/grow (e.g., give it
flex: 1 1 auto and min-width: 0) while keeping .conflict-calendar and
.conflict-time fixed or non-growing (e.g., flex: 0 0 auto), and tweak gaps or
padding as needed so the three-item row flows gracefully on small screens.
src/backend/core/services/calendar/service.py (2)

133-133: Move import re to module level.

The re module is imported inside methods at lines 133 and 157. Moving it to the top of the file alongside other imports improves readability and follows Python conventions.

Proposed fix at module level
 """CalDAV calendar service for RSVP and event management."""
 
 import logging
+import re
 
 import caldav
 from caldav.elements import dav

Then remove the local imports at lines 133 and 157.

Also applies to: 157-157

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/backend/core/services/calendar/service.py` at line 133, Move the "import
re" statement to the top of the module alongside the other imports (add a single
module-level "import re") and remove the local "import re" statements that
currently appear inside methods in this file; ensure those methods (which use
regex functionality) continue to reference the module-level "re" symbol rather
than importing it locally.

20-28: Add a timeout to DAVClient to prevent indefinite request blocking.

The caldav.DAVClient is created without a timeout parameter. If the CalDAV server is slow or unresponsive, this could cause request threads (in CalendarConflictsView and CalendarListView) or Celery workers to block indefinitely.

Proposed fix
     `@property`
     def client(self):
         if self._client is None:
             self._client = caldav.DAVClient(
                 url=self.url,
                 username=self.username,
                 password=self.password,
+                timeout=30,  # seconds
             )
         return self._client
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/backend/core/services/calendar/service.py` around lines 20 - 28, The
DAVClient instantiation in the client property lacks a timeout, risking
indefinite blocking; update the client property to pass a sensible timeout value
(e.g., timeout=10 or use a configured constant) into caldav.DAVClient when
creating self._client so requests from CalendarConflictsView/CalendarListView
and Celery workers don't hang indefinitely; ensure the timeout value is
configurable via settings or an environment variable and referenced where
caldav.DAVClient is constructed in the client property.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/backend/core/api/viewsets/calendar.py`:
- Around line 259-267: CalendarListView.get is making blocking I/O via
CalDAVService.from_channel and service.list_calendars (synchronous calls) which
can block the event loop; refactor this handler to offload the blocking calls to
an async-friendly pattern—either convert CalDAVService methods to async or run
the synchronous calls in a thread/process executor (e.g., using
asyncio.to_thread or a background task) before returning the Response;
specifically wrap CalDAVService.from_channel(...) and service.list_calendars()
so they execute off the main thread and handle/propagate exceptions the same way
as existing logger.exception and Response logic.
- Around line 219-229: CalendarConflictsView is performing a blocking call to
the external CalDAV server via CalDAVService.from_channel(...) and
service.check_conflicts(...); change this to avoid blocking the request thread
by either converting the view to an async Django view and awaiting an async
CalDAVService.check_conflicts (ensure CalDAVService implements an async API and
enforces a network timeout), or move the conflict check into a background Celery
task (enqueue a task from CalendarConflictsView that calls
CalDAVService.check_conflicts and return a 202 with a task id), and in either
case ensure CalDAVService has a strict configurable timeout for network calls to
prevent long waits.
- Around line 25-46: The mailbox lookup in CalDAVChannelMixin is missing an
authorization check allowing any authenticated user to access arbitrary
mailboxes; update the cached_property mailbox to resolve the Mailbox through the
same authorized/queryset pattern used in blob.py and contacts.py (i.e., restrict
the queryset to mailboxes the current request.user may access or call the shared
helper that returns an authorized mailbox) so that get_object_or_404 only
returns a mailbox the requester is permitted to use; leave get_caldav_channel
and require_caldav_channel unchanged except that they should rely on the
now-authorized mailbox property.

In
`@src/frontend/src/features/layouts/components/thread-view/components/calendar-invite/index.tsx`:
- Around line 358-361: The CalDAV query errors are being swallowed and replaced
with empty arrays, making transient failures indistinguishable from genuine "no
data" and hiding UI like the ConflictWarning; update the query hooks used around
the referenced areas (the calendar fetch and event fetch calls used to compute
`conflicts`, `calendars`, and related data) so they propagate an error state
instead of returning `[]` on failure, and adjust the rendering in this component
to check for that error state (e.g., `calendarsError`/`eventsError` or similar)
and render a local "Calendar unavailable — retry" UI with a retry action; ensure
`ConflictWarning` is still shown when `conflicts.length > 0` but that when the
queries report an error you show the unavailable/retry UI instead of collapsing
to an empty state.
- Around line 469-470: The RSVP/Add flow is committing rsvpResponse and relying
on isPending only after the POST returns a task_id, allowing multiple clicks and
lost/incorrect state; add a local "pendingAction" boolean (or enum) state (e.g.,
isSubmitting or pendingRsvp) that is set immediately in the RSVP/Add click
handlers (before the POST) to disable controls, and preserve the intended
response plus the returned task_id in a small pending map (taskId -> intended
response). Keep selectedCalendarId handling but do not call setRsvpResponse
until the background poll for that task_id reaches SUCCESS; on FAILURE/timeout
clear the pendingAction and do not commit rsvpResponse (and re-enable controls).
Update the RSVP/Add handler functions and the polling completion logic to look
up the pending map by task_id and commit or clear state accordingly so clicks
cannot enqueue multiple jobs or prematurely highlight a choice.

---

Nitpick comments:
In `@src/backend/core/services/calendar/service.py`:
- Line 133: Move the "import re" statement to the top of the module alongside
the other imports (add a single module-level "import re") and remove the local
"import re" statements that currently appear inside methods in this file; ensure
those methods (which use regex functionality) continue to reference the
module-level "re" symbol rather than importing it locally.
- Around line 20-28: The DAVClient instantiation in the client property lacks a
timeout, risking indefinite blocking; update the client property to pass a
sensible timeout value (e.g., timeout=10 or use a configured constant) into
caldav.DAVClient when creating self._client so requests from
CalendarConflictsView/CalendarListView and Celery workers don't hang
indefinitely; ensure the timeout value is configurable via settings or an
environment variable and referenced where caldav.DAVClient is constructed in the
client property.

In
`@src/frontend/src/features/layouts/components/thread-view/components/calendar-invite/_index.scss`:
- Around line 267-273: The .calendar-invite__conflict-item currently uses
justify-content: space-between which spreads .conflict-summary,
.conflict-calendar, and .conflict-time awkwardly on narrow viewports; update
.calendar-invite__conflict-item to allow wrapping (e.g., add flex-wrap: wrap)
and adjust child flex rules so .conflict-summary can shrink/grow (e.g., give it
flex: 1 1 auto and min-width: 0) while keeping .conflict-calendar and
.conflict-time fixed or non-growing (e.g., flex: 0 0 auto), and tweak gaps or
padding as needed so the three-item row flows gracefully on small screens.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a7bfb200-20b4-433f-ad17-21d8b2b39b4e

📥 Commits

Reviewing files that changed from the base of the PR and between dc52f1f and 924321e.

⛔ Files ignored due to path filters (1)
  • src/backend/uv.lock is excluded by !**/*.lock
📒 Files selected for processing (14)
  • src/backend/core/api/viewsets/calendar.py
  • src/backend/core/services/calendar/__init__.py
  • src/backend/core/services/calendar/service.py
  • src/backend/core/services/calendar/tasks.py
  • src/backend/core/tasks.py
  • src/backend/core/urls.py
  • src/backend/pyproject.toml
  • src/frontend/public/locales/common/en-US.json
  • src/frontend/src/features/controlled-modals/message-importer/step-loader.tsx
  • src/frontend/src/features/layouts/components/main/header/authenticated.tsx
  • src/frontend/src/features/layouts/components/thread-view/components/calendar-invite/_index.scss
  • src/frontend/src/features/layouts/components/thread-view/components/calendar-invite/index.tsx
  • src/frontend/src/features/layouts/components/thread-view/components/thread-message/thread-message-footer.tsx
  • src/frontend/src/hooks/use-task-status.ts

Comment thread src/backend/core/api/viewsets/calendar.py Outdated
Comment thread src/backend/core/api/viewsets/calendar.py Outdated
Comment thread src/backend/core/api/viewsets/calendar.py
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/backend/core/api/viewsets/calendar.py`:
- Around line 97-129: The view currently forwards calendar_id from post into
calendar_rsvp_task/calendar_add_event_task without verifying it belongs to the
mailbox; instead of adding validation here, implement strict ownership
validation inside the calendar service by updating _pick_calendar_url in
src/backend/core/services/calendar/service.py to resolve/verify that a provided
calendar_id (or URL) is actually part of the requester’s calendar-home-set
(raise an error or return None on mismatch), and ensure calendar_rsvp_task and
calendar_add_event_task call that service helper (or surface the validation
error) so the view (post) can safely forward calendar_id knowing the service
enforces ownership.

In `@src/backend/core/services/calendar/service.py`:
- Around line 218-268: The _pick_calendar_url implementation currently returns
caller-supplied calendar_id verbatim, allowing authenticated PUTs to arbitrary
URLs; change _pick_calendar_url to validate any provided calendar_id against the
set returned by list_calendars() (or the parsed IDs from each calendar dict) and
only use it if it exactly equals one of those known calendar URLs (and
optionally ensure its origin matches self.home_set/origin); if it does not
match, fall back to selecting the first calendar from list_calendars() or raise
CalDAVError. Update callers (respond_to_event / add_event flow that call
_pick_calendar_url before _put_event) to rely on this validated URL so
_put_event never issues authenticated requests to attacker-provided hosts.
- Around line 246-260: The matching logic in _update_partstat incorrectly uses
substring containment (email_lower in str(att).lower()) which can match sibling
addresses; change it to parse and compare the attendee's canonical email address
for exact equality (e.g., extract the address from the vCalAddress/ATTENDEE
value by stripping any "mailto:"/scheme and lowercasing, or use the library's
property that returns the email) and only update when equal, keeping the rest of
the loop (comp walk, attendees normalization, setting att.params["PARTSTAT"],
and popping RSVP) the same; add a regression test TestUpdatePartstat in
test_calendar.py that creates two attendees where one address is a substring of
the other and asserts only the exact match is updated.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3b03b3fe-934a-4af2-a929-82681362e162

📥 Commits

Reviewing files that changed from the base of the PR and between 924321e and f349129.

⛔ Files ignored due to path filters (1)
  • src/backend/uv.lock is excluded by !**/*.lock
📒 Files selected for processing (15)
  • src/backend/core/api/viewsets/calendar.py
  • src/backend/core/services/calendar/__init__.py
  • src/backend/core/services/calendar/service.py
  • src/backend/core/services/calendar/tasks.py
  • src/backend/core/tasks.py
  • src/backend/core/tests/api/test_calendar.py
  • src/backend/core/urls.py
  • src/backend/messages/settings.py
  • src/backend/pyproject.toml
  • src/frontend/public/locales/common/en-US.json
  • src/frontend/src/features/controlled-modals/message-importer/step-loader.tsx
  • src/frontend/src/features/layouts/components/main/header/authenticated.tsx
  • src/frontend/src/features/layouts/components/thread-view/components/calendar-invite/_index.scss
  • src/frontend/src/features/layouts/components/thread-view/components/calendar-invite/index.tsx
  • src/frontend/src/features/layouts/components/thread-view/components/thread-message/thread-message-footer.tsx
✅ Files skipped from review due to trivial changes (3)
  • src/frontend/public/locales/common/en-US.json
  • src/backend/messages/settings.py
  • src/frontend/src/features/layouts/components/thread-view/components/calendar-invite/_index.scss
🚧 Files skipped from review as they are similar to previous changes (6)
  • src/frontend/src/features/layouts/components/thread-view/components/thread-message/thread-message-footer.tsx
  • src/backend/pyproject.toml
  • src/backend/core/tasks.py
  • src/frontend/src/features/controlled-modals/message-importer/step-loader.tsx
  • src/backend/core/urls.py
  • src/backend/core/services/calendar/tasks.py

Comment thread src/backend/core/api/viewsets/calendar.py
Comment thread src/backend/core/services/calendar/service.py
Comment thread src/backend/core/services/calendar/service.py
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
src/backend/core/services/calendar/service.py (2)

193-198: Minor: findtext called twice per response.

The list comprehension evaluates findtext(...) twice for every matching response (once in the filter, once in the expression). For large calendar result sets this doubles XML tree traversal. A simple loop (or walrus) keeps it single-pass:

Proposed fix
-        root = ET.fromstring(resp.text)
-        return [
-            r.findtext(f".//{_q(CALDAV_NS, 'calendar-data')}") or ""
-            for r in root.findall(_q(DAV_NS, "response"))
-            if r.findtext(f".//{_q(CALDAV_NS, 'calendar-data')}")
-        ]
+        root = ET.fromstring(resp.text)
+        results = []
+        tag = f".//{_q(CALDAV_NS, 'calendar-data')}"
+        for r in root.findall(_q(DAV_NS, "response")):
+            data = r.findtext(tag)
+            if data:
+                results.append(data)
+        return results
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/backend/core/services/calendar/service.py` around lines 193 - 198, The
list comprehension calls r.findtext(...) twice per response; change it to a
single-pass by iterating over root.findall(_q(DAV_NS, "response")) and storing
the calendar-data result in a local variable (or use the walrus operator) once
per response (use the same _q(CALDAV_NS, 'calendar-data') key), then append only
non-empty values to the result list and return that list from the function where
ET.fromstring(resp.text) and root.findall are used.

32-36: Naive datetime silently treated as UTC.

If a caller passes a tz-naive datetime, the if dt.tzinfo is not None branch is skipped and strftime("…Z") stamps it as UTC regardless of what timezone it actually represented. With Django's USE_TZ=True this is usually fine, but a bug upstream (e.g. a datetime.now() slipping in from a view/serializer) will silently produce wrong CalDAV time-range queries rather than fail loudly.

Consider either asserting aware input or assuming a documented timezone explicitly:

Proposed fix
 def _format_utc(dt):
-    if dt.tzinfo is not None:
-        dt = dt.astimezone(timezone.utc)
-    return dt.strftime("%Y%m%dT%H%M%SZ")
+    if dt.tzinfo is None:
+        raise ValueError("_format_utc requires a timezone-aware datetime")
+    return dt.astimezone(timezone.utc).strftime("%Y%m%dT%H%M%SZ")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/backend/core/services/calendar/service.py` around lines 32 - 36, The
function _format_utc currently treats tz-naive datetimes as UTC; change it to
require timezone-aware input by checking with django.utils.timezone.is_naive (or
dt.tzinfo is None) and raise a clear exception (ValueError) if the datetime is
naive so callers must pass an aware datetime; retain the existing behavior of
converting an aware datetime via dt.astimezone(timezone.utc) and then formatting
with "%Y%m%dT%H%M%SZ" to preserve output.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/backend/core/api/openapi.json`:
- Around line 2563-2715: Update the OpenAPI responses by adding the missing 400
and 502 response schemas to the `@extend_schema`(responses=...) annotations in
src/backend/core/api/viewsets/calendar.py for the affected view methods (the
RSVP handler, add event handler, conflicts handler, and list-calendars handler
referenced around lines 65-292); specifically include 400 validation responses
for RSVP, add event, and conflicts, and include 502 for conflicts and list
calendars, then re-run the OpenAPI regeneration (drf-spectacular / your
project’s generate-openapi step) so
/api/v1.0/mailboxes/{mailbox_id}/calendar/... endpoints
(mailboxes_calendar_rsvp_create, mailboxes_calendar_add_create,
mailboxes_calendar_conflicts_create, mailboxes_calendar_calendars_retrieve)
reflect those responses.

In `@src/backend/core/services/calendar/service.py`:
- Around line 291-310: The _put_event function builds event_url by concatenating
an attacker-controlled UID into the path; fix by sanitizing or encoding the UID
before use: after extracting uid in _put_event validate it against a strict
allowlist (e.g. only alphanumerics, dot, hyphen, underscore and a reasonable max
length) and reject or replace any UID with path delimiters, CR/LF, whitespace,
percent sequences, or ".."; alternatively percent-encode the UID as a single
path segment (use urllib.parse.quote with no "/" in the safe set) and then build
event_url using calendar_url.rstrip("/") + "/" + quoted_uid + ".ics"; ensure the
code raises a clear error for rejected UIDs and does not allow raw uid to flow
into event_url (refer to variables uid, calendar_url, event_url and function
_put_event).

---

Nitpick comments:
In `@src/backend/core/services/calendar/service.py`:
- Around line 193-198: The list comprehension calls r.findtext(...) twice per
response; change it to a single-pass by iterating over root.findall(_q(DAV_NS,
"response")) and storing the calendar-data result in a local variable (or use
the walrus operator) once per response (use the same _q(CALDAV_NS,
'calendar-data') key), then append only non-empty values to the result list and
return that list from the function where ET.fromstring(resp.text) and
root.findall are used.
- Around line 32-36: The function _format_utc currently treats tz-naive
datetimes as UTC; change it to require timezone-aware input by checking with
django.utils.timezone.is_naive (or dt.tzinfo is None) and raise a clear
exception (ValueError) if the datetime is naive so callers must pass an aware
datetime; retain the existing behavior of converting an aware datetime via
dt.astimezone(timezone.utc) and then formatting with "%Y%m%dT%H%M%SZ" to
preserve output.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a9df9385-b1a7-427e-9d83-ed8bb879d1db

📥 Commits

Reviewing files that changed from the base of the PR and between f349129 and 4231bee.

⛔ Files ignored due to path filters (13)
  • src/frontend/src/features/api/gen/calendar/calendar.ts is excluded by !**/gen/**
  • src/frontend/src/features/api/gen/index.ts is excluded by !**/gen/**
  • src/frontend/src/features/api/gen/models/calendar_add_event_request_request.ts is excluded by !**/gen/**
  • src/frontend/src/features/api/gen/models/calendar_add_event_response.ts is excluded by !**/gen/**
  • src/frontend/src/features/api/gen/models/calendar_conflicts_request_request.ts is excluded by !**/gen/**
  • src/frontend/src/features/api/gen/models/calendar_conflicts_response.ts is excluded by !**/gen/**
  • src/frontend/src/features/api/gen/models/calendar_conflicts_response_conflicts_item.ts is excluded by !**/gen/**
  • src/frontend/src/features/api/gen/models/calendar_list_response.ts is excluded by !**/gen/**
  • src/frontend/src/features/api/gen/models/calendar_list_response_calendars_item.ts is excluded by !**/gen/**
  • src/frontend/src/features/api/gen/models/calendar_rsvp_request_request.ts is excluded by !**/gen/**
  • src/frontend/src/features/api/gen/models/calendar_rsvp_response.ts is excluded by !**/gen/**
  • src/frontend/src/features/api/gen/models/index.ts is excluded by !**/gen/**
  • src/frontend/src/features/api/gen/models/response_enum.ts is excluded by !**/gen/**
📒 Files selected for processing (5)
  • src/backend/core/api/openapi.json
  • src/backend/core/services/calendar/service.py
  • src/backend/core/tests/api/test_calendar.py
  • src/backend/core/tests/api/test_messages_import.py
  • src/backend/core/tests/importer/conftest.py

Comment thread src/backend/core/api/openapi.json
Comment thread src/backend/core/services/calendar/service.py
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/frontend/src/hooks/use-task-status.ts (1)

16-35: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reset retry/poll state when taskId changes.

The hook can carry exhausted state across different tasks in the same component instance. A new taskId may immediately appear failed or never poll.

🔧 Suggested fix
 export function useTaskStatus(
   taskId: string | null,
@@
 ) {
   const { t } = useTranslation();
   const [queryEnabled, setQueryEnabled] = useState(enabled);
   const [hasExhaustedRetries, setHasExhaustedRetries] = useState(false);
   const errorCountRef = useRef(0);
+
+  useEffect(() => {
+    errorCountRef.current = 0;
+    setHasExhaustedRetries(false);
+    setQueryEnabled(enabled);
+  }, [taskId, enabled]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/frontend/src/hooks/use-task-status.ts` around lines 16 - 35, The hook
useTaskStatus is not resetting its retry/poll state when taskId changes, causing
hasExhaustedRetries and errorCountRef to leak between tasks; add a useEffect
that runs when taskId changes to reset errorCountRef.current = 0, call
setHasExhaustedRetries(false) and restore setQueryEnabled(enabled) (and any
other per-task state) so each new taskId starts with a clean polling/retry
state; reference useTaskStatus, taskId, errorCountRef, setHasExhaustedRetries
and setQueryEnabled when making the change.
♻️ Duplicate comments (1)
src/backend/core/services/calendar/service.py (1)

360-380: ⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy lift

UID must be validated or percent-encoded before URL construction.

The UID is extracted from caller-supplied ICS (line 365) and concatenated directly into event_url (line 372) without validation or encoding. A malicious UID containing path delimiters (/, ..), query/fragment separators (?, #), percent-encoded sequences (%2e%2e), or CRLF (\r\n) can escape the validated calendar_url, redirect to unintended endpoints, or inject headers into the HTTP request.

🔒 Recommended hardening
 def _put_event(self, calendar_url, ics_data):
     uid = ""
     try:
         cal = ICalendar.from_ical(ics_data)
         for comp in cal.walk("VEVENT"):
             uid = str(comp.get("UID") or "")
             break
     except Exception:  # pylint: disable=broad-exception-caught
         logger.debug("Could not extract UID from ICS, using random", exc_info=True)
     if not uid:
         uid = str(uuid.uuid4())
+    
+    # Reject or sanitize UIDs with path delimiters, CRLF, or other unsafe chars.
+    # CalDAV UIDs are typically stable identifiers (e.g., UUID@domain); anything
+    # unusual should be replaced to prevent path traversal or header injection.
+    import re
+    if not re.fullmatch(r"[A-Za-z0-9._@+\-]{1,255}", uid):
+        logger.warning("UID contains unsafe characters, replacing: %s", uid)
+        uid = str(uuid.uuid4())

-    event_url = calendar_url.rstrip("/") + f"/{uid}.ics"
+    from urllib.parse import quote
+    # Percent-encode the UID as a single path segment (no "/" in safe set).
+    event_url = calendar_url.rstrip("/") + "/" + quote(uid, safe="") + ".ics"

Based on learnings, logger.exception() will report to Sentry automatically via DjangoIntegration's default LoggingIntegration at event_level=logging.ERROR. The added logger.warning() will not trigger Sentry events unless log level is ERROR or above, so explicit capture_exception() is not needed here.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/backend/core/services/calendar/service.py` around lines 360 - 380, The
UID extracted in _put_event is used directly in event_url and must be
validated/encoded to prevent path traversal, query/fragment injection, CRLF
injection, or malicious percent-encodings; update _put_event to sanitize uid
after extraction (e.g., reject or normalize any control characters or sequences
like CR/LF, remove or fail on path separators and "..", and ensure it contains
only an allowed charset) and then percent-encode the sanitized uid (use
urllib.parse.quote or equivalent) before concatenating with calendar_url to form
event_url, or else raise/return an error when uid is invalid so you never build
a URL from an unsafe UID.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/backend/core/api/viewsets/calendar.py`:
- Line 298: The expression assigns web_url using getattr(settings,
"CALDAV_WEB_URL", None) or None which is redundant; remove the trailing "or
None" and simply set web_url = getattr(settings, "CALDAV_WEB_URL", None) (refer
to the web_url assignment and getattr(settings, "CALDAV_WEB_URL", None) in
calendar.py) to simplify the code.

In `@src/frontend/public/locales/common/en-US.json`:
- Line 637: The singular pluralization variant key "{{count}} messages were
imported before the error._one" has incorrect grammar ("message were"); update
its value to use correct singular verb tense, e.g. change the string to
"{{count}} message was imported before the error." so the _one variant reads
properly.

In
`@src/frontend/src/features/controlled-modals/message-importer/step-loader.tsx`:
- Around line 29-31: The code forces a non-null assertion on useTaskStatus which
can return null; update the importStatus handling in step-loader.tsx (the
useTaskStatus call and any subsequent reads of importStatus) to guard against a
null return instead of using the `!` operator: call useTaskStatus(taskId, ...)
without `!`, check if importStatus is null and handle it (e.g., render a
loading/placeholder or an error UI) before accessing properties like
importStatus.status or importStatus.error, ensuring all uses of importStatus are
null-safe.

In
`@src/frontend/src/features/layouts/components/thread-view/components/calendar-invite/_index.scss`:
- Line 89: Replace the deprecated CSS declaration "word-break: break-word" in
_index.scss with the modern equivalent by removing that property and adding
"overflow-wrap: break-word" (optionally keep "word-wrap: break-word" for legacy
support) within the same selector where "word-break: break-word" appears to
ensure proper wrapping and cross-browser compatibility.

In
`@src/frontend/src/features/layouts/components/thread-view/components/calendar-invite/calendar-select.tsx`:
- Around line 44-59: The label is currently cast to any which weakens type
safety; update the options creation in the useMemo (the calendars -> optionJsx
block) to give label a proper React type instead of any—e.g., type it as
React.ReactNode or JSX.Element (or define the options array generically to match
Cunningham's Select label type) and remove the eslint-disable; keep render
returning the JSX element (CalendarOptionItem) and ensure the options variable
signature reflects the chosen label type so TypeScript enforces correct usage.

---

Outside diff comments:
In `@src/frontend/src/hooks/use-task-status.ts`:
- Around line 16-35: The hook useTaskStatus is not resetting its retry/poll
state when taskId changes, causing hasExhaustedRetries and errorCountRef to leak
between tasks; add a useEffect that runs when taskId changes to reset
errorCountRef.current = 0, call setHasExhaustedRetries(false) and restore
setQueryEnabled(enabled) (and any other per-task state) so each new taskId
starts with a clean polling/retry state; reference useTaskStatus, taskId,
errorCountRef, setHasExhaustedRetries and setQueryEnabled when making the
change.

---

Duplicate comments:
In `@src/backend/core/services/calendar/service.py`:
- Around line 360-380: The UID extracted in _put_event is used directly in
event_url and must be validated/encoded to prevent path traversal,
query/fragment injection, CRLF injection, or malicious percent-encodings; update
_put_event to sanitize uid after extraction (e.g., reject or normalize any
control characters or sequences like CR/LF, remove or fail on path separators
and "..", and ensure it contains only an allowed charset) and then
percent-encode the sanitized uid (use urllib.parse.quote or equivalent) before
concatenating with calendar_url to form event_url, or else raise/return an error
when uid is invalid so you never build a URL from an unsafe UID.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 85d8a5d1-d6bf-4457-9d5e-67f2c8f6f09c

📥 Commits

Reviewing files that changed from the base of the PR and between 4231bee and ae60e36.

⛔ Files ignored due to path filters (1)
  • src/frontend/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (19)
  • src/backend/core/api/viewsets/calendar.py
  • src/backend/core/services/calendar/service.py
  • src/backend/core/tests/api/test_calendar.py
  • src/backend/messages/settings.py
  • src/frontend/package.json
  • src/frontend/public/locales/common/en-US.json
  • src/frontend/public/locales/common/fr-FR.json
  • src/frontend/src/features/controlled-modals/message-importer/step-loader.tsx
  • src/frontend/src/features/layouts/components/thread-view/components/calendar-invite/_index.scss
  • src/frontend/src/features/layouts/components/thread-view/components/calendar-invite/calendar-helper.tsx
  • src/frontend/src/features/layouts/components/thread-view/components/calendar-invite/calendar-select.tsx
  • src/frontend/src/features/layouts/components/thread-view/components/calendar-invite/event-display.test.ts
  • src/frontend/src/features/layouts/components/thread-view/components/calendar-invite/event-display.ts
  • src/frontend/src/features/layouts/components/thread-view/components/calendar-invite/index.tsx
  • src/frontend/src/features/layouts/components/thread-view/components/thread-message/index.tsx
  • src/frontend/src/features/layouts/components/thread-view/components/thread-message/thread-message-footer.tsx
  • src/frontend/src/features/layouts/components/thread-view/components/thread-message/types.ts
  • src/frontend/src/features/ui/components/contact-chip/index.tsx
  • src/frontend/src/hooks/use-task-status.ts

Comment thread src/backend/core/api/viewsets/calendar.py Outdated
Comment thread src/frontend/public/locales/common/en-US.json Outdated
Comment thread src/frontend/src/features/controlled-modals/message-importer/step-loader.tsx Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/backend/core/tests/api/test_calendar.py (1)

836-1001: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Add a regression test for _put_event() path escaping.

This helper coverage stops at calendar selection. Please add one test that feeds a UID containing path separators / traversal characters and asserts the final PUT target is percent-encoded (or rejected). That will lock the upload-path fix in place.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/backend/core/tests/api/test_calendar.py` around lines 836 - 1001, Add a
regression test that exercises CalDAVService._put_event() with a UID containing
path separators/traversal characters (e.g. "/" or "..") and assert that the
final HTTP PUT target used by _put_event is percent-encoded (or that the call is
rejected with CalDAVError); specifically, create a test that constructs a
CalDAVService (or uses the existing test helpers), stub/spy the service._request
or service._put method to capture the request URL when calling
service._put_event(uid=bad_uid, ...) and then assert the captured URL does not
contain raw separators (it contains percent-encoding) or that _put_event raises
for malformed UIDs, ensuring the upload-path escaping fix is covered.
♻️ Duplicate comments (2)
src/frontend/src/features/controlled-modals/message-importer/step-loader.tsx (1)

29-31: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Remove the forced non-null assertion on task status.

useTaskStatus is treated as nullable elsewhere (importStatus?.state), but ! makes render-time reads unsafe (renderProgressText, ProgressBar, importStatus.state) if it returns null.

Suggested fix
-    const importStatus = useTaskStatus(taskId, {
+    const importStatus = useTaskStatus(taskId, {
         exhaustedError: t('An error occurred while importing messages.'),
-    })!;
+    });
+
+    if (!importStatus) return null;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/frontend/src/features/controlled-modals/message-importer/step-loader.tsx`
around lines 29 - 31, Remove the forced non-null assertion on the result of
useTaskStatus (the importStatus variable) and make all accesses to its
properties safe: delete the trailing `!` on the call to useTaskStatus and update
any direct reads like `importStatus.state` to use optional chaining or guard
early (e.g., return a loading/null state if importStatus is null) so usages in
renderProgressText, ProgressBar, and anywhere else referencing importStatus are
null-safe; ensure taskId is still passed into useTaskStatus and that components
receive importStatus?.state or are not rendered until importStatus is non-null.
src/backend/core/services/calendar/service.py (1)

360-379: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Encode or reject the extracted UID before building the PUT URL.

This is still concatenating attacker-controlled ICS content into the request path verbatim. A UID containing /, .., ?, #, or control characters can escape the selected calendar collection or target a different resource.

🔒 Proposed hardening
+from urllib.parse import quote
@@
-        event_url = calendar_url.rstrip("/") + f"/{uid}.ics"
+        event_url = calendar_url.rstrip("/") + "/" + quote(uid, safe="") + ".ics"
         data = ics_data.encode("utf-8") if isinstance(ics_data, str) else ics_data

You may also want to reject obviously dangerous UIDs up front and fall back to a generated UUID.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/backend/core/services/calendar/service.py` around lines 360 - 379, In
_put_event, the extracted UID is used directly to build event_url which allows
path-traversal or URI injection; before concatenating, validate and sanitize uid
by either rejecting UIDs containing unsafe characters (e.g. '/', '\\', '..',
control chars, '?', '#') and falling back to a generated uuid, or
percent-encoding the UID using a safe encoder (e.g. urllib.parse.quote) so
reserved/path characters are escaped; ensure the sanitized/encoded uid is used
when creating event_url and keep the fallback behavior to uuid.uuid4() for any
rejected or empty UIDs.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/backend/core/api/viewsets/calendar.py`:
- Around line 241-250: After parsing start and end in the calendar view (the
block that converts ISO strings to datetimes), add a validation that rejects
inverted ranges by checking if end <= start and returning a HTTP 400 Response;
update the same view/function handling conflicts (the variables start and end in
this try/except block inside the calendar viewset) to return Response({"detail":
"end must be after start."}, status=status.HTTP_400_BAD_REQUEST) when the range
is invalid so the request fails fast before any conflict querying.
- Around line 120-129: The Celery task enqueue calls (calendar_rsvp_task.delay)
are unguarded and may raise uncaught exceptions; wrap each .delay(...) call
(both the one before register_task_owner and the other occurrence at lines
176–181) in a try-except that catches Celery/broker/serialization errors, log
the exception, and return or raise a controlled API error response instead of
letting a 500 propagate; only call register_task_owner(task.id, request.user.id)
after the task is successfully created inside the try block so you don't
reference task when enqueue fails.

In `@src/backend/core/services/calendar/service.py`:
- Around line 65-70: The _request method currently calls self.session.request
directly so RequestException (e.g. timeouts, connection errors) can escape and
not be wrapped as CalDAVError; update _request to catch
requests.exceptions.RequestException around self.session.request and re-raise a
CalDAVError with context (include method, url and original exception) so callers
like check_conflicts() receive a CalDAVError; reference the _request method,
CalDAVError class, and session.request/RequestException when making the change.

In
`@src/frontend/src/features/layouts/components/thread-view/components/calendar-invite/index.tsx`:
- Around line 590-595: The RSVP/Add controls are interactive even when the raw
ICS query (useQuery returning icsContent) is loading or errored; gate their
enabled/visible state on the query status by checking icsContent (and query
error/loading) before rendering or enabling the buttons. Update the useQuery
call usage around downloadUrl/mailboxId and in renderFooter() treat isIcsError
or missing icsContent as a local-unavailable state so the RSVP/Add controls are
disabled/hidden (same fix for the other render blocks at the other ranges
mentioned) to prevent no-op clicks when the payload is absent.
- Around line 661-665: The logic currently treats isConflictsError as making the
entire CalDAV service unavailable via showUnavailable; change showUnavailable to
only consider isCalendarsError and isCalDAVEmpty (remove isConflictsError from
that expression) so calendar-loaded flows still render footer actions
(RSVP/Add), and handle conflictsResponse errors separately by surfacing a
non-blocking warning near the existing warning area (use the
conflicts/conflictsResponse and isConflictsError flags to render that warning).
Also update the same pattern around the other block referenced (the code at the
845-853 area) to ensure conflict-query failures do not hide the footer controls.

In
`@src/frontend/src/features/layouts/components/thread-view/components/thread-message/index.tsx`:
- Around line 116-127: The calendar detection and deduplication is brittle:
CALENDAR_MIME_TYPES exact matching misses params like "text/calendar;
method=REQUEST" and deduping only by att.sha256 can incorrectly drop items when
sha256 is empty; update the logic that builds calendar/regular from
message.attachments to treat a MIME as calendar when its type
startsWith('text/calendar') or when parsing the media type ignores parameters,
and change the dedupe for uniqueCalendar to use a composite key (e.g.,
att.sha256 if present else a fallback combining att.name and att.size or
att.filename) so missing/empty sha256 doesn't collapse distinct files; adjust
references to CALENDAR_MIME_TYPES, message.attachments, and uniqueCalendar
accordingly.

---

Outside diff comments:
In `@src/backend/core/tests/api/test_calendar.py`:
- Around line 836-1001: Add a regression test that exercises
CalDAVService._put_event() with a UID containing path separators/traversal
characters (e.g. "/" or "..") and assert that the final HTTP PUT target used by
_put_event is percent-encoded (or that the call is rejected with CalDAVError);
specifically, create a test that constructs a CalDAVService (or uses the
existing test helpers), stub/spy the service._request or service._put method to
capture the request URL when calling service._put_event(uid=bad_uid, ...) and
then assert the captured URL does not contain raw separators (it contains
percent-encoding) or that _put_event raises for malformed UIDs, ensuring the
upload-path escaping fix is covered.

---

Duplicate comments:
In `@src/backend/core/services/calendar/service.py`:
- Around line 360-379: In _put_event, the extracted UID is used directly to
build event_url which allows path-traversal or URI injection; before
concatenating, validate and sanitize uid by either rejecting UIDs containing
unsafe characters (e.g. '/', '\\', '..', control chars, '?', '#') and falling
back to a generated uuid, or percent-encoding the UID using a safe encoder (e.g.
urllib.parse.quote) so reserved/path characters are escaped; ensure the
sanitized/encoded uid is used when creating event_url and keep the fallback
behavior to uuid.uuid4() for any rejected or empty UIDs.

In
`@src/frontend/src/features/controlled-modals/message-importer/step-loader.tsx`:
- Around line 29-31: Remove the forced non-null assertion on the result of
useTaskStatus (the importStatus variable) and make all accesses to its
properties safe: delete the trailing `!` on the call to useTaskStatus and update
any direct reads like `importStatus.state` to use optional chaining or guard
early (e.g., return a loading/null state if importStatus is null) so usages in
renderProgressText, ProgressBar, and anywhere else referencing importStatus are
null-safe; ensure taskId is still passed into useTaskStatus and that components
receive importStatus?.state or are not rendered until importStatus is non-null.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: d8d4514a-4b11-4d48-a702-6c74cfff378d

📥 Commits

Reviewing files that changed from the base of the PR and between ae60e36 and a3d59b4.

⛔ Files ignored due to path filters (1)
  • src/frontend/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (19)
  • src/backend/core/api/viewsets/calendar.py
  • src/backend/core/services/calendar/service.py
  • src/backend/core/tests/api/test_calendar.py
  • src/backend/messages/settings.py
  • src/frontend/package.json
  • src/frontend/public/locales/common/en-US.json
  • src/frontend/public/locales/common/fr-FR.json
  • src/frontend/src/features/controlled-modals/message-importer/step-loader.tsx
  • src/frontend/src/features/layouts/components/thread-view/components/calendar-invite/_index.scss
  • src/frontend/src/features/layouts/components/thread-view/components/calendar-invite/calendar-helper.tsx
  • src/frontend/src/features/layouts/components/thread-view/components/calendar-invite/calendar-select.tsx
  • src/frontend/src/features/layouts/components/thread-view/components/calendar-invite/event-display.test.ts
  • src/frontend/src/features/layouts/components/thread-view/components/calendar-invite/event-display.ts
  • src/frontend/src/features/layouts/components/thread-view/components/calendar-invite/index.tsx
  • src/frontend/src/features/layouts/components/thread-view/components/thread-message/index.tsx
  • src/frontend/src/features/layouts/components/thread-view/components/thread-message/thread-message-footer.tsx
  • src/frontend/src/features/layouts/components/thread-view/components/thread-message/types.ts
  • src/frontend/src/features/ui/components/contact-chip/index.tsx
  • src/frontend/src/hooks/use-task-status.ts

Comment thread src/backend/core/api/viewsets/calendar.py Outdated
Comment thread src/backend/core/api/viewsets/calendar.py
Comment thread src/backend/core/services/calendar/service.py
@sylvinus sylvinus force-pushed the caldav branch 2 times, most recently from 74b64b2 to a0984ff Compare May 21, 2026 21:20
@sylvinus
Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 21, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@sylvinus sylvinus merged commit bd779a1 into main May 26, 2026
14 checks passed
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.

1 participant