Skip to content

feat(dss): add more comment notification types#2170

Merged
seanaye merged 4 commits intomainfrom
seanaye/feat/more-document-comment-notifs
Mar 25, 2026
Merged

feat(dss): add more comment notification types#2170
seanaye merged 4 commits intomainfrom
seanaye/feat/more-document-comment-notifs

Conversation

@seanaye
Copy link
Copy Markdown
Contributor

@seanaye seanaye commented Mar 25, 2026

Summary

Adds two new document comment notification types with dedup guarantees, bringing document comments closer to parity with channel notifications:

  • replied_to_document_comment_thread — Notifies all thread participants (excluding sender) when someone replies to a comment thread they participated in
  • commented_on_document — Notifies the document owner when someone comments on their document

Notifications follow a strict priority-based dedup: mention > thread reply > document owner. A user will never receive more than one notification per comment. The dedup logic is extracted into a pure compute_notification_recipients function with 10 unit tests covering overlap scenarios, case-insensitive ID matching, and sender exclusion.

Bug fix

The previous inline dedup logic compared raw mention strings against normalized MacroUserIdStr values (which are lowercased on parse). This meant a mention like "macro|Bob@Test.COM" would not match the lowercased thread participant "macro|bob@test.com", causing duplicate notifications. The extracted function normalizes all IDs through MacroUserIdStr::parse_from_str before comparison.

Changes

Backend (Rust)

  • model_notifications/src/metadata.rs — New RepliedToDocumentCommentThreadMetadata and CommentedOnDocumentMetadata structs with Notification, NotificationTitle, and NotificationExtIos trait impls
  • model_notifications/src/lib.rs — New NotifEvent variants and NotificationTitle delegation
  • notification_service/src/api/swagger.rs — Register new metadata types in OpenAPI schema
  • document_storage_service/src/api/annotations/mod.rscompute_notification_recipients pure function + builder helpers
  • document_storage_service/src/api/annotations/create_comment.rs — Refactored dispatch logic using compute_notification_recipients
  • document_storage_service/src/api/annotations/test.rs — 10 new tests for recipient computation

Frontend (TypeScript) — 6 files with exhaustive match updates

  • notification-metadata.ts — action, target name, content, show-target
  • notification-preview.ts — label mapping (REPLY, COMMENT)
  • notification-navigation.ts — document navigation handlers
  • notification-icon.tsx — reply arrow, comment chat icon
  • notification-description-helpers.ts — action verbs and type nouns
  • notification.ts — action text and message content extraction

Generated — OpenAPI spec + TypeScript client types

Test plan

  • cargo test -p document_storage_service — 11 tests pass (10 new + 1 existing)
  • cargo test -p model_notifications — passes
  • cargo check — no errors or warnings
  • bun check — TypeScript compiles clean
  • Manual: create a comment on someone else's doc → owner gets commented_on_document notification
  • Manual: reply to a thread → other participants get replied_to_document_comment_thread
  • Manual: @mention a thread participant → they get only the mention notification, not both

@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 25, 2026

@seanaye seanaye force-pushed the seanaye/feat/more-document-comment-notifs branch from e319e0c to 584237e Compare March 25, 2026 16:07
@seanaye seanaye force-pushed the seanaye/feat/more-document-comment-notifs branch from 584237e to d86754c Compare March 25, 2026 16:08
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 25, 2026

Note

Reviews paused

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.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

This PR introduces two notification types — replied_to_document_comment_thread and commented_on_document — across frontend, OpenAPI, and backend. Frontend mappings (icons, labels, previews, extractors, navigation, metadata helpers) were extended. OpenAPI and model schemas for the new metadata were added. Rust backend: added metadata structs and NotifEvent variants, refactored recipient computation into compute_notification_recipients/CommentNotifContext, removed ad-hoc mention builder, and added unit tests. Also added rel="noopener" to an external link opened with target="_blank".

Suggested reviewers

  • whutchinson98

Poem

🥕 I nibble through diffs with a twitchy nose,
New replies and comments in tidy rows.
Threads and mentions sorted, no one forgot,
Recipients split and duplicates caught,
Hooray — the rabbit hums, the notifier glows!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 69.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding two new document comment notification types with comprehensive backend and frontend support.
Description check ✅ Passed The description is thorough and directly related to the changeset, detailing new notification types, dedup logic, implementation across multiple files, and test coverage.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch seanaye/feat/more-document-comment-notifs

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

Copy link
Copy Markdown
Contributor

@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: 4

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

Inline comments:
In `@rust/cloud-storage/authentication_service/src/main.rs`:
- Line 179: The code mistakenly uses dbg!(config.notification_queue.clone())
when initializing queue_url, which prints to stderr; replace the dbg! wrapper
with the plain value config.notification_queue.clone() so queue_url is set
without debug output, and remove any now-unused dbg imports if present; target
the queue_url initialization where config.notification_queue is cloned.

In
`@rust/cloud-storage/document_storage_service/src/api/annotations/create_comment.rs`:
- Around line 140-144: The code re-parses IDs from
recipients.thread_reply_recipients into participant_ids using
MacroUserIdStr::parse_from_str even though compute_notification_recipients
already validated and normalized them; change compute_notification_recipients to
return the normalized IDs (e.g., Vec<MacroUserIdStr<'static>> or
HashSet<MacroUserIdStr<'static>>) so callers like the create_comment path can
use the returned collection directly, and update the create_comment code to
accept and use that collection (replace the filter_map parse block that builds
participant_ids) to eliminate the redundant parsing and unnecessary allocations.

In `@rust/cloud-storage/model_notifications/src/lib.rs`:
- Around line 171-176: The match in set_sender_profile_picture currently uses a
wildcard arm and misses the new NotifEvent variants
RepliedToDocumentCommentThread and CommentedOnDocument, so add explicit match
arms for these variants in set_sender_profile_picture to populate
sender_profile_picture_url (same enrichment logic used for other user-related
variants) instead of relying on the wildcard; update the NotifEvent match to
enumerate all handled variants (including
RepliedToDocumentCommentThread(RepliedToDocumentCommentThreadMetadata) and
CommentedOnDocument(CommentedOnDocumentMetadata)) and ensure
sender_profile_picture_url is set appropriately for each.

In `@rust/cloud-storage/model_notifications/src/metadata.rs`:
- Around line 796-799: The code currently treats self.file_type as required in
format_title, causing format_title/as_apns to error and return None when
file_type is None; update the logic in format_title (and the parallel block used
by MentionedInDocumentCommentMetadata) to accept an Option<&str> or to use a
fallback when file_type is None (for example, omit the extension or use an empty
string) so that as_apns returns a valid notification instead of None; locate the
use of self.file_type (the let file_type_str = ... line) and change the handling
to gracefully handle None rather than failing with report!, ensuring APNS
payloads are produced even when file_type is missing.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: fc1744d9-edc5-42b3-94b6-7bc7defd3070

📥 Commits

Reviewing files that changed from the base of the PR and between cb978b6 and 66681e7.

⛔ Files ignored due to path filters (27)
  • flake.lock is excluded by !**/*.lock
  • js/app/packages/service-clients/service-notification/generated/schemas/commentedOnDocumentMetadata.ts is excluded by !**/generated/**
  • js/app/packages/service-clients/service-notification/generated/schemas/commentedOnDocumentMetadataFileType.ts is excluded by !**/generated/**
  • js/app/packages/service-clients/service-notification/generated/schemas/commentedOnDocumentMetadataSenderProfilePictureUrl.ts is excluded by !**/generated/**
  • js/app/packages/service-clients/service-notification/generated/schemas/index.ts is excluded by !**/generated/**
  • js/app/packages/service-clients/service-notification/generated/schemas/notifEvent.ts is excluded by !**/generated/**
  • js/app/packages/service-clients/service-notification/generated/schemas/notifEventOneOfNine.ts is excluded by !**/generated/**
  • js/app/packages/service-clients/service-notification/generated/schemas/notifEventOneOfNineTag.ts is excluded by !**/generated/**
  • js/app/packages/service-clients/service-notification/generated/schemas/notifEventOneOfOnefive.ts is excluded by !**/generated/**
  • js/app/packages/service-clients/service-notification/generated/schemas/notifEventOneOfOnefiveTag.ts is excluded by !**/generated/**
  • js/app/packages/service-clients/service-notification/generated/schemas/notifEventOneOfOnenine.ts is excluded by !**/generated/**
  • js/app/packages/service-clients/service-notification/generated/schemas/notifEventOneOfOnenineTag.ts is excluded by !**/generated/**
  • js/app/packages/service-clients/service-notification/generated/schemas/notifEventOneOfOneone.ts is excluded by !**/generated/**
  • js/app/packages/service-clients/service-notification/generated/schemas/notifEventOneOfOneoneTag.ts is excluded by !**/generated/**
  • js/app/packages/service-clients/service-notification/generated/schemas/notifEventOneOfOneseven.ts is excluded by !**/generated/**
  • js/app/packages/service-clients/service-notification/generated/schemas/notifEventOneOfOnesevenTag.ts is excluded by !**/generated/**
  • js/app/packages/service-clients/service-notification/generated/schemas/notifEventOneOfOnethree.ts is excluded by !**/generated/**
  • js/app/packages/service-clients/service-notification/generated/schemas/notifEventOneOfOnethreeTag.ts is excluded by !**/generated/**
  • js/app/packages/service-clients/service-notification/generated/schemas/notifEventOneOfSeven.ts is excluded by !**/generated/**
  • js/app/packages/service-clients/service-notification/generated/schemas/notifEventOneOfSevenTag.ts is excluded by !**/generated/**
  • js/app/packages/service-clients/service-notification/generated/schemas/notifEventOneOfTwoone.ts is excluded by !**/generated/**
  • js/app/packages/service-clients/service-notification/generated/schemas/notifEventOneOfTwooneTag.ts is excluded by !**/generated/**
  • js/app/packages/service-clients/service-notification/generated/schemas/notifEventOneOfTwothree.ts is excluded by !**/generated/**
  • js/app/packages/service-clients/service-notification/generated/schemas/notifEventOneOfTwothreeTag.ts is excluded by !**/generated/**
  • js/app/packages/service-clients/service-notification/generated/schemas/repliedToDocumentCommentThreadMetadata.ts is excluded by !**/generated/**
  • js/app/packages/service-clients/service-notification/generated/schemas/repliedToDocumentCommentThreadMetadataFileType.ts is excluded by !**/generated/**
  • js/app/packages/service-clients/service-notification/generated/schemas/repliedToDocumentCommentThreadMetadataSenderProfilePictureUrl.ts is excluded by !**/generated/**
📒 Files selected for processing (15)
  • js/app/packages/core/component/LexicalMarkdown/component/core/LinkWithPreview.tsx
  • js/app/packages/entity/src/extractors-notification/notification-description-helpers.ts
  • js/app/packages/entity/src/extractors-notification/notification-icon.tsx
  • js/app/packages/entity/src/utils/notification.ts
  • js/app/packages/notifications/notification-metadata.ts
  • js/app/packages/notifications/notification-navigation.ts
  • js/app/packages/notifications/notification-preview.ts
  • js/app/packages/service-clients/service-notification/openapi.json
  • rust/cloud-storage/authentication_service/src/main.rs
  • rust/cloud-storage/document_storage_service/src/api/annotations/create_comment.rs
  • rust/cloud-storage/document_storage_service/src/api/annotations/mod.rs
  • rust/cloud-storage/document_storage_service/src/api/annotations/test.rs
  • rust/cloud-storage/model_notifications/src/lib.rs
  • rust/cloud-storage/model_notifications/src/metadata.rs
  • rust/cloud-storage/notification_service/src/api/swagger.rs

@seanaye seanaye force-pushed the seanaye/feat/more-document-comment-notifs branch 2 times, most recently from ddc6b4c to d4bf398 Compare March 25, 2026 17:36
Copy link
Copy Markdown
Contributor

@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
`@rust/cloud-storage/document_storage_service/src/api/annotations/edit_comment.rs`:
- Around line 68-84: Add a short explanatory comment in edit_comment_handler (in
edit_comment.rs) near the notif_ctx/build_mention_notif call stating that only
mention notifications are intentionally sent on edit (no thread-reply or
document-owner notifications) to avoid spamming users, mirroring the
documentation style used in create_comment_handler; place the comment
immediately above the notif_ctx or request variable so future readers see the
rationale next to the notification construction logic.

In `@rust/cloud-storage/document_storage_service/src/api/annotations/mod.rs`:
- Around line 128-181: compute_notification_recipients currently returns
mention_recipients as Vec<String> while thread_reply_recipients is
HashSet<MacroUserIdStr<'static>>, forcing callers (e.g., build_mention_notif) to
re-parse; change mention_recipients to HashSet<MacroUserIdStr<'static>> so
parsed/validated IDs are returned directly: when building mention_recipients,
parse each id into MacroUserIdStr (use the same parse/try_from logic used for
thread_comment_owners), insert the parsed MacroUserIdStr into the HashSet, and
use parsed.as_ref().to_string() (or keep a separate notified HashSet of strings)
to populate the notified set and dedupe; update the NotificationRecipients
struct/type to reflect mention_recipients: HashSet<MacroUserIdStr<'static>> and
adjust callers (e.g., build_mention_notif) to accept the HashSet without
re-parsing.

In `@rust/cloud-storage/document_storage_service/src/api/annotations/test.rs`:
- Around line 30-56: The test
mentioned_user_who_is_also_thread_participant_gets_only_mention is asserting
against the raw string bob ("macro|bob@test.com") but
compute_notification_recipients normalizes IDs (via
MacroUserIdStr::parse_from_str/user_id), so the contains/assert checks can fail;
update the assertions to compare against the normalized ID (use the same
normalization helper used elsewhere—e.g., user_id(bob) or
MacroUserIdStr::parse_from_str(bob).unwrap()) when checking
result.mention_recipients and result.thread_reply_recipients so both sides use
the same canonical form.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0dbbb455-9345-4863-baea-3a80b0a52af8

📥 Commits

Reviewing files that changed from the base of the PR and between 66681e7 and ddc6b4c.

📒 Files selected for processing (6)
  • rust/cloud-storage/comms_service/src/notification.rs
  • rust/cloud-storage/document_storage_service/src/api/annotations/create_comment.rs
  • rust/cloud-storage/document_storage_service/src/api/annotations/edit_comment.rs
  • rust/cloud-storage/document_storage_service/src/api/annotations/mod.rs
  • rust/cloud-storage/document_storage_service/src/api/annotations/test.rs
  • rust/cloud-storage/model_notifications/src/metadata.rs

Copy link
Copy Markdown
Contributor

@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.

♻️ Duplicate comments (1)
rust/cloud-storage/document_storage_service/src/api/annotations/mod.rs (1)

218-305: 🧹 Nitpick | 🔵 Trivial

Type inconsistency between mention_recipients (Vec) and thread_reply_recipients (HashSet).

The past review noted this inconsistency but it's still present. build_mention_notif re-parses the string IDs at lines 247-250, which duplicates work already done in compute_notification_recipients. This isn't a bug, but changing mention_recipients to HashSet<MacroUserIdStr<'static>> would eliminate the redundant parsing.

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

In `@rust/cloud-storage/document_storage_service/src/api/annotations/mod.rs`
around lines 218 - 305, Summary: eliminate redundant parsing by switching
mention recipients from Vec<String> to HashSet<MacroUserIdStr<'static>> and
feeding that into build_mention_notif. Update compute_notification_recipients
(or wherever mention_recipients is produced) to return
HashSet<MacroUserIdStr<'static>> instead of Vec<String>, change
CommentNotifContext::build_mention_notif signature to accept that HashSet (e.g.,
&HashSet<MacroUserIdStr<'static>> or take ownership), and remove the filter_map
parsing block that builds recipient_ids (lines creating recipient_ids from
mentions); instead assign or clone the provided HashSet into
SendNotificationRequestBuilder.recipient_ids and keep constructing
MentionedInDocumentCommentMetadata as before.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@rust/cloud-storage/document_storage_service/src/api/annotations/mod.rs`:
- Around line 218-305: Summary: eliminate redundant parsing by switching mention
recipients from Vec<String> to HashSet<MacroUserIdStr<'static>> and feeding that
into build_mention_notif. Update compute_notification_recipients (or wherever
mention_recipients is produced) to return HashSet<MacroUserIdStr<'static>>
instead of Vec<String>, change CommentNotifContext::build_mention_notif
signature to accept that HashSet (e.g., &HashSet<MacroUserIdStr<'static>> or
take ownership), and remove the filter_map parsing block that builds
recipient_ids (lines creating recipient_ids from mentions); instead assign or
clone the provided HashSet into SendNotificationRequestBuilder.recipient_ids and
keep constructing MentionedInDocumentCommentMetadata as before.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4bdbd8e8-c532-4fea-bf53-3e6618c20dec

📥 Commits

Reviewing files that changed from the base of the PR and between ddc6b4c and d4bf398.

📒 Files selected for processing (6)
  • rust/cloud-storage/comms_service/src/notification.rs
  • rust/cloud-storage/document_storage_service/src/api/annotations/create_comment.rs
  • rust/cloud-storage/document_storage_service/src/api/annotations/edit_comment.rs
  • rust/cloud-storage/document_storage_service/src/api/annotations/mod.rs
  • rust/cloud-storage/document_storage_service/src/api/annotations/test.rs
  • rust/cloud-storage/model_notifications/src/metadata.rs

@seanaye seanaye force-pushed the seanaye/feat/more-document-comment-notifs branch from d4bf398 to b634943 Compare March 25, 2026 17:45
@seanaye seanaye force-pushed the seanaye/feat/more-document-comment-notifs branch from b634943 to b57e7a6 Compare March 25, 2026 17:54
Copy link
Copy Markdown
Contributor

@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

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

Inline comments:
In
`@rust/cloud-storage/document_storage_service/src/api/annotations/edit_comment.rs`:
- Around line 72-93: The edit path currently builds recipient_ids directly from
users and can include the editor themself; modify the edit flow around
CommentNotifContext::build_mention_notif so you normalize and remove the sender
(use the same normalization used by compute_notification_recipients or
MacroUserIdStr::try_from on user_id) from recipient_ids before creating the
request, and if the resulting recipient_ids set is empty, do not call
send_notification (skip building/sending the request). Ensure the change touches
the variables recipient_ids, notif_ctx, and the code path that calls
build_mention_notif()/into_request()/with_apns()/with_conn_gateway() so the
editor is excluded and sending is skipped when there are no recipients.

In `@rust/cloud-storage/document_storage_service/src/api/annotations/mod.rs`:
- Around line 141-146: This code collects normalized mention_recipients from
mentioned_user_ids and then extends notified with them, but it doesn't exclude
the comment sender; update the logic that builds mention_recipients (the
conversion using MacroUserIdStr::try_from on mentioned_user_ids) to filter out
the sender's ID (compare against the sender's MacroUserIdStr or raw ID before
collecting) so the sender is not added to the mention_recipients HashSet and
thus not pushed into notified in create_comment_handler.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 09a1d94d-a4cd-48da-926a-eab3bbdce52b

📥 Commits

Reviewing files that changed from the base of the PR and between d4bf398 and b634943.

📒 Files selected for processing (6)
  • rust/cloud-storage/comms_service/src/notification.rs
  • rust/cloud-storage/document_storage_service/src/api/annotations/create_comment.rs
  • rust/cloud-storage/document_storage_service/src/api/annotations/edit_comment.rs
  • rust/cloud-storage/document_storage_service/src/api/annotations/mod.rs
  • rust/cloud-storage/document_storage_service/src/api/annotations/test.rs
  • rust/cloud-storage/model_notifications/src/metadata.rs

Comment on lines +141 to +146
// 1. Mention recipients — normalize to MacroUserIdStr format for consistent comparison
let mention_recipients: HashSet<MacroUserIdStr<'static>> = mentioned_user_ids
.iter()
.filter_map(|id| MacroUserIdStr::parse_from_str(id).ok())
.filter_map(|id| MacroUserIdStr::try_from(id.clone()).ok())
.collect();
notified.extend(mention_recipients.iter().map(|id| id.as_ref().to_string()));
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot Mar 25, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Filter the sender out of mention_recipients.

The mention pass currently adds every normalized ID from mentioned_user_ids into the highest-priority bucket. If a commenter mentions themselves, create_comment_handler will still send a mention notification back to the sender, which breaks the sender-exclusion guarantee this helper is meant to enforce.

Suggested fix
     let mention_recipients: HashSet<MacroUserIdStr<'static>> = mentioned_user_ids
         .iter()
         .filter_map(|id| MacroUserIdStr::try_from(id.clone()).ok())
+        .filter(|id| {
+            sender_id
+                .map(|sender| sender.as_ref() != id.as_ref())
+                .unwrap_or(true)
+        })
         .collect();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rust/cloud-storage/document_storage_service/src/api/annotations/mod.rs`
around lines 141 - 146, This code collects normalized mention_recipients from
mentioned_user_ids and then extends notified with them, but it doesn't exclude
the comment sender; update the logic that builds mention_recipients (the
conversion using MacroUserIdStr::try_from on mentioned_user_ids) to filter out
the sender's ID (compare against the sender's MacroUserIdStr or raw ID before
collecting) so the sender is not added to the mention_recipients HashSet and
thus not pushed into notified in create_comment_handler.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is already handled downstream in the notification ingress Service

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@seanaye, understood — thanks for the clarification. If the notification ingress service already suppresses delivery back to the sender as a general guarantee, then the mention pass here doesn't need to replicate that logic.

I'll note this for future reviews so I don't flag the same pattern again.


✏️ Learnings added
Learnt from: seanaye
Repo: macro-inc/macro PR: 2170
File: rust/cloud-storage/document_storage_service/src/api/annotations/mod.rs:141-146
Timestamp: 2026-03-25T18:04:21.214Z
Learning: In the macro repository (Rust backend), sender exclusion for notification recipients is handled downstream in the notification ingress service, not at the point where recipient sets are computed (e.g., `compute_notification_recipients` in `document_storage_service/src/api/annotations/mod.rs`). Do not flag missing sender-filtering in recipient-computation helpers as a bug.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Copy link
Copy Markdown
Contributor

@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

Caution

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

⚠️ Outside diff range comments (1)
rust/cloud-storage/document_storage_service/src/api/annotations/edit_comment.rs (1)

94-97: ⚠️ Potential issue | 🟡 Minor

Fix the tracing::error! macro formatting to match repo standard.

Change error =? e to error=?e on line 97. The structured error field should follow the consistent format across cloud-storage: tracing::error!(error=?e, "error msg").

.inspect_err(|e| tracing::error!(error=?e, "couldn't send document mention notification"));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@rust/cloud-storage/document_storage_service/src/api/annotations/edit_comment.rs`
around lines 94 - 97, The tracing::error! invocation inside the send path for
notification_ingress_service (in the closure passed to inspect_err on the future
returned by send_notification) uses `error =? e` and should be formatted to the
repo standard `error=?e`; update the closure passed to inspect_err(|e| ...) so
the tracing::error! macro reads tracing::error!(error=?e, "couldn't send
document mention notification") while leaving the surrounding call chain
(notification_ingress_service.send_notification(request).await.inspect_err(...))
intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@rust/cloud-storage/model_notifications/src/metadata.rs`:
- Around line 812-815: The collapse_key implementations for
RepliedToDocumentCommentThreadMetadata and CommentedOnDocumentMetadata currently
only use entity_type + entity_id, causing different comments on the same
document to collide; update each collapse_key(&self, entity: &Entity<'_>)
function to build the NotifCollapseKey starting from
NotifCollapseKey::new(entity_type) and then append entity.entity_id,
self.comment_id and self.thread_id (in that order), ensuring the collapse key
includes comment_id and thread_id so per-comment notifications are unique.

---

Outside diff comments:
In
`@rust/cloud-storage/document_storage_service/src/api/annotations/edit_comment.rs`:
- Around line 94-97: The tracing::error! invocation inside the send path for
notification_ingress_service (in the closure passed to inspect_err on the future
returned by send_notification) uses `error =? e` and should be formatted to the
repo standard `error=?e`; update the closure passed to inspect_err(|e| ...) so
the tracing::error! macro reads tracing::error!(error=?e, "couldn't send
document mention notification") while leaving the surrounding call chain
(notification_ingress_service.send_notification(request).await.inspect_err(...))
intact.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0591178e-a367-4522-aa3d-f2956651f5bb

📥 Commits

Reviewing files that changed from the base of the PR and between b634943 and b57e7a6.

📒 Files selected for processing (6)
  • rust/cloud-storage/comms_service/src/notification.rs
  • rust/cloud-storage/document_storage_service/src/api/annotations/create_comment.rs
  • rust/cloud-storage/document_storage_service/src/api/annotations/edit_comment.rs
  • rust/cloud-storage/document_storage_service/src/api/annotations/mod.rs
  • rust/cloud-storage/document_storage_service/src/api/annotations/test.rs
  • rust/cloud-storage/model_notifications/src/metadata.rs

Comment on lines +71 to 80
let notif_ctx = CommentNotifContext {
text: req.text.clone().unwrap_or_default(),
comment_id: res.comment.comment_id,
thread_id: req.thread_id,
document_name: res.document_name.clone(),
document_id: res.document_id.to_string(),
owner: res.document_owner.clone(),
file_type: res.file_type.clone(),
sender_id: user_id.clone().try_into().ok(),
sender_profile_picture_url,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Verify whether the edited comment/response already exposes canonical text/thread fields.
rg -n -C3 'struct EditCommentRequest|struct EditCommentResponse|edit_document_comment|\bthread_id\b|\bthreadId\b|\btext\b' rust/cloud-storage --glob '*.rs'

Repository: macro-inc/macro

Length of output: 50373


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find and read the edit_comment.rs file to understand the full context
FILE_PATH="rust/cloud-storage/document_storage_service/src/api/annotations/edit_comment.rs"
if [ -f "$FILE_PATH" ]; then
    echo "=== File: $FILE_PATH ==="
    wc -l "$FILE_PATH"
    cat -n "$FILE_PATH" | head -120
else
    echo "File not found: $FILE_PATH"
fi

Repository: macro-inc/macro

Length of output: 5287


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Search specifically for EditCommentRequest and EditCommentResponse in document_storage_service
find rust/cloud-storage/document_storage_service -name "*.rs" -type f | head -20

Repository: macro-inc/macro

Length of output: 1668


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Search for struct definitions of EditCommentRequest and EditCommentResponse
rg -A10 "struct EditCommentRequest|struct EditCommentResponse" rust/cloud-storage/document_storage_service --type rs

Repository: macro-inc/macro

Length of output: 85


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Search for EditCommentRequest and EditCommentResponse struct definitions
rg -A15 "struct EditCommentRequest|struct EditCommentResponse" rust/cloud-storage --type rs -g "*/model/*"

Repository: macro-inc/macro

Length of output: 85


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# More focused search - look in model crate
find . -path "*/model/src/annotations/edit*" -name "*.rs" 2>/dev/null | head -10

Repository: macro-inc/macro

Length of output: 109


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Try broader search for annotations edit types
find . -path "*/annotations/*" -name "*.rs" 2>/dev/null | grep -i edit | head -20

Repository: macro-inc/macro

Length of output: 409


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Read the EditCommentRequest and EditCommentResponse definitions
cat -n ./rust/cloud-storage/model/src/annotations/edit.rs

Repository: macro-inc/macro

Length of output: 2393


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Also check the database layer to see what edit_document_comment returns
cat -n ./rust/cloud-storage/macro_db_client/src/annotations/edit_comment.rs | head -100

Repository: macro-inc/macro

Length of output: 3773


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check the Comment struct to confirm it has text and thread_id fields
rg -A30 "^pub struct Comment" rust/cloud-storage/model/src/annotations/ --type-not md

Repository: macro-inc/macro

Length of output: 4592


Use persisted comment fields for notification context instead of request payload.

The text and thread_id in the notification context should come from res.comment (the saved, canonical comment) rather than req (client input). The request's text is Option<String> and calling .unwrap_or_default() produces an empty string when the client omits it, even though the comment wasn't modified. Similarly, req.thread_id may differ from the actual persisted thread; use res.comment.thread_id instead for correctness.

Additionally, the error logging at line 97 should follow the guideline format: tracing::error!(error=?e, "couldn't send document mention notification") (no spaces around the =? operator).

Copy link
Copy Markdown
Contributor

@synoet synoet left a comment

Choose a reason for hiding this comment

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

Gave it a light pass, overall looks good.

@seanaye seanaye merged commit 6b3dc7a into main Mar 25, 2026
59 checks passed
@seanaye seanaye deleted the seanaye/feat/more-document-comment-notifs branch March 25, 2026 20:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants