Skip to content

🐛(global) allow thread viewers to post internal comments#632

Merged
jbpenrath merged 1 commit into
mainfrom
fix/few-issues
Apr 15, 2026
Merged

🐛(global) allow thread viewers to post internal comments#632
jbpenrath merged 1 commit into
mainfrom
fix/few-issues

Conversation

@jbpenrath
Copy link
Copy Markdown
Contributor

@jbpenrath jbpenrath commented Apr 15, 2026

Purpose

Writing an internal comment is a personal authoring act that should not require thread edit rights: support teammates invited as thread viewers must still be able to comment and mention colleagues, as long as they have edit rights on the mailbox. ThreadEvent IM writes and ThreadUser listing are relaxed accordingly, while every other thread mutation keeps the full edit-rights check.

The message composer is now gated by the thread edit ability so that
read-only users cannot bypass the check through reply or forward, and
the thread-panel selection separator is hidden when no bulk action is
available. A few unrelated UI polish fixes (disabled link button
style, combobox placeholder visibility) ship alongside.

Summary by CodeRabbit

  • New Features

    • Enhanced permission controls for thread comments and event editing based on user mailbox and thread access roles.
  • Bug Fixes

    • Fixed attachment uploader to properly respect disabled state.
    • Fixed combobox input visibility in text measurement elements.
    • Added styling for disabled primary buttons.
  • Style

    • Added border-radius design tokens for form inputs, selects, textareas, and datepickers.
    • Updated global font weight token.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 15, 2026

Warning

Rate limit exceeded

@jbpenrath has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 22 minutes and 5 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 22 minutes and 5 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f82092bd-017a-406a-ae76-b1725ae981fa

📥 Commits

Reviewing files that changed from the base of the PR and between 7906e62 and 400a9b6.

📒 Files selected for processing (15)
  • src/backend/core/api/permissions.py
  • src/backend/core/api/viewsets/thread_event.py
  • src/backend/core/api/viewsets/thread_user.py
  • src/backend/core/tests/api/test_thread_event_permissions.py
  • src/backend/core/tests/api/test_thread_user.py
  • src/frontend/src/features/forms/components/combobox/_index.scss
  • src/frontend/src/features/forms/components/message-form/attachment-uploader.tsx
  • src/frontend/src/features/forms/components/message-form/index.tsx
  • src/frontend/src/features/layouts/components/mailbox-panel/components/mailbox-actions/index.tsx
  • src/frontend/src/features/layouts/components/thread-panel/components/thread-panel-header.tsx
  • src/frontend/src/features/layouts/components/thread-view/index.tsx
  • src/frontend/src/styles/cunningham-tokens.css
  • src/frontend/src/styles/cunningham-tokens.scss
  • src/frontend/src/styles/cunningham-tokens.ts
  • src/frontend/src/styles/globals.scss
📝 Walkthrough

Walkthrough

This PR introduces type-aware permissions for thread event creation and comment access, adds comment-specific authorization checks to multiple viewsets, updates frontend permission gating and component behavior based on thread editability, and includes styling refinements for form controls and typography.

Changes

Cohort / File(s) Summary
Backend Thread Event Permissions
src/backend/core/api/permissions.py
Added _user_can_comment_on_thread() helper, HasThreadCommentAccess permission class, and HasThreadEventWriteAccess class with type-aware logic: IM events allow comment access via mailbox editor role, while other event types require stricter thread editability.
Backend Viewset Updates
src/backend/core/api/viewsets/thread_event.py, src/backend/core/api/viewsets/thread_user.py
Updated ThreadEventViewSet to use new type-aware HasThreadEventWriteAccess permission; replaced ThreadUserViewSet permission from IsAllowedToManageThreadAccess to HasThreadCommentAccess.
Backend Permission Tests
src/backend/core/tests/api/test_thread_event_permissions.py, src/backend/core/tests/api/test_thread_user.py
Replaced generic event creation test suite with IM-specific tests allowing VIEWER users with mailbox editor role to create comments; added author-only edit/delete checks; updated thread-user list permission tests to reflect broadened authorization for editor-level mailbox roles.
Frontend Styling
src/frontend/src/styles/cunningham-tokens.css, src/frontend/src/styles/cunningham-tokens.scss, src/frontend/src/styles/globals.scss, src/frontend/src/features/forms/components/combobox/_index.scss
Updated global font-weight token from 900 to 800; added border-radius tokens for form controls (forms-input, forms-select, forms-textarea, forms-datepicker); added disabled state styling for primary buttons; hid combobox input pseudo-element text measurement.
Frontend Component Logic
src/frontend/src/features/forms/components/message-form/attachment-uploader.tsx, src/frontend/src/features/forms/components/message-form/index.tsx, src/frontend/src/features/layouts/components/thread-view/index.tsx
Propagated disabled prop to DriveAttachmentPicker; added thread-edit permission gating with canEditCurrentThread ability check affecting message composition; updated showIMInput logic to use mailbox-level role checks instead of per-thread access.
Frontend UI Refactoring
src/frontend/src/features/layouts/components/thread-panel/components/thread-panel-header.tsx, src/frontend/src/features/layouts/components/mailbox-panel/components/mailbox-actions/index.tsx
Consolidated selection-action permission booleans in thread-panel header; removed trailing blank line from mailbox-actions component.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • sylvinus

Poem

🐰 Permission threads now split in two,
IM comments for the willing crew,
While edits demand the stricter right,
Mailbox roles align the sight,
Forms now radius with grace! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 72.22% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: enabling thread viewers with mailbox editor access to post internal comments, which is the central permission-relaxation objective of this PR.

✏️ 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 fix/few-issues

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

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.

🧹 Nitpick comments (4)
src/backend/core/tests/api/test_thread_event_permissions.py (1)

390-411: Minor: Docstring describes a scenario not demonstrated by the test setup.

The docstring states "Mailbox role dropped to VIEWER after the event was created," but the test creates the event via factory while the user already has VIEWER role from the start. The test correctly simulates the end state (author with VIEWER mailbox role cannot edit), but doesn't demonstrate the downgrade process itself.

Consider clarifying:

     def test_mailbox_viewer_author_cannot_update_own_im(self, api_client):
-        """Mailbox role dropped to VIEWER after the event was created:
-        the author has lost comment rights and can no longer edit.
+        """Even if the author owns the IM event, they cannot edit it
+        without sufficient mailbox privileges (simulates role downgrade).
         """
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/backend/core/tests/api/test_thread_event_permissions.py` around lines 390
- 411, Update the docstring of test_mailbox_viewer_author_cannot_update_own_im
to reflect the actual setup: the author already has a VIEWER mailbox role when
the event is created (not that the role was "dropped" after creation). Locate
the test in which factories.MailboxAccessFactory assigns
enums.MailboxRoleChoices.VIEWER before factories.ThreadEventFactory creates the
event and replace the misleading sentence with one that states the author has
VIEWER role at creation time and therefore cannot edit their "im" event.
src/backend/core/api/permissions.py (1)

608-618: Consider defensive handling for malformed request.data.

Line 609 assumes request.data is dict-like. If a client sends malformed JSON or an unexpected content type, request.data.get("type") could raise an AttributeError. DRF typically handles this, but if you want extra safety:

event_type = request.data.get("type") if isinstance(request.data, dict) else None

That said, DRF's content negotiation generally ensures request.data is a dict for JSON payloads, so this may be acceptable as-is.

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

In `@src/backend/core/api/permissions.py` around lines 608 - 618, In the
create-action branch where event_type is read (the if view.action == "create"
block), guard against malformed request.data by checking its type before calling
.get; replace the current event_type = request.data.get("type") if
hasattr(request, "data") else None with a safe check like using
isinstance(request.data, dict) (so event_type defaults to None for non-dict
request.data) and keep the subsequent logic that calls
_user_can_comment_on_thread(request.user, thread_id) for IM and the
models.ThreadAccess.objects.editable_by(...).filter(thread_id=thread_id).exists()
path for other types.
src/frontend/src/styles/cunningham-tokens.css (1)

363-364: Font weight --black now equals --extrabold — verify intentional.

Both --c--globals--font--weights--black (line 364) and --c--globals--font--weights--extrabold (line 363) are now set to 800. Traditionally, "black" is 900 and "extrabold" is 800. If the design intent is to collapse these two weights, the tokens are now functionally identical; otherwise, --black may need to remain 900 to preserve typographic hierarchy.

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

In `@src/frontend/src/styles/cunningham-tokens.css` around lines 363 - 364, The
two CSS tokens --c--globals--font--weights--extrabold and
--c--globals--font--weights--black are both set to 800 which collapses their
semantic difference; decide whether --c--globals--font--weights--black should be
the traditional 900 and update its value accordingly (change
--c--globals--font--weights--black: 900;) or, if intentional, add a
comment/docstring near the tokens stating that black and extrabold are unified
at 800 to preserve intended hierarchy.
src/frontend/src/features/layouts/components/thread-panel/components/thread-panel-header.tsx (1)

203-268: LGTM! Excellent refactoring of permission checks.

Replacing inline permission checks with the derived canArchive, canReportSpam, canTrash, and canAssignLabel booleans is a solid improvement:

  • Eliminates code duplication (DRY principle)
  • Improves readability with semantic naming
  • Creates a single source of truth for permission logic
  • Reduces cognitive load in the JSX rendering logic

The implementation is consistent across all four action controls.

🤖 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-panel/components/thread-panel-header.tsx`
around lines 203 - 268, The permission-refactor is good as-is; no code changes
required—keep the derived booleans canArchive, canReportSpam, canTrash, and
canAssignLabel and their usage in the Tooltip/Button blocks and
LabelsWidget(threadIds={Array.from(selectedThreadIds)}), retaining the existing
onClick mutation calls (archiveMutation, spamMutation, trashMutation) with their
onSuccess handlers that call unselectThread() and onClearSelection().
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/backend/core/api/permissions.py`:
- Around line 608-618: In the create-action branch where event_type is read (the
if view.action == "create" block), guard against malformed request.data by
checking its type before calling .get; replace the current event_type =
request.data.get("type") if hasattr(request, "data") else None with a safe check
like using isinstance(request.data, dict) (so event_type defaults to None for
non-dict request.data) and keep the subsequent logic that calls
_user_can_comment_on_thread(request.user, thread_id) for IM and the
models.ThreadAccess.objects.editable_by(...).filter(thread_id=thread_id).exists()
path for other types.

In `@src/backend/core/tests/api/test_thread_event_permissions.py`:
- Around line 390-411: Update the docstring of
test_mailbox_viewer_author_cannot_update_own_im to reflect the actual setup: the
author already has a VIEWER mailbox role when the event is created (not that the
role was "dropped" after creation). Locate the test in which
factories.MailboxAccessFactory assigns enums.MailboxRoleChoices.VIEWER before
factories.ThreadEventFactory creates the event and replace the misleading
sentence with one that states the author has VIEWER role at creation time and
therefore cannot edit their "im" event.

In
`@src/frontend/src/features/layouts/components/thread-panel/components/thread-panel-header.tsx`:
- Around line 203-268: The permission-refactor is good as-is; no code changes
required—keep the derived booleans canArchive, canReportSpam, canTrash, and
canAssignLabel and their usage in the Tooltip/Button blocks and
LabelsWidget(threadIds={Array.from(selectedThreadIds)}), retaining the existing
onClick mutation calls (archiveMutation, spamMutation, trashMutation) with their
onSuccess handlers that call unselectThread() and onClearSelection().

In `@src/frontend/src/styles/cunningham-tokens.css`:
- Around line 363-364: The two CSS tokens --c--globals--font--weights--extrabold
and --c--globals--font--weights--black are both set to 800 which collapses their
semantic difference; decide whether --c--globals--font--weights--black should be
the traditional 900 and update its value accordingly (change
--c--globals--font--weights--black: 900;) or, if intentional, add a
comment/docstring near the tokens stating that black and extrabold are unified
at 800 to preserve intended hierarchy.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 44e598eb-518b-4e5c-a588-78804959f0a6

📥 Commits

Reviewing files that changed from the base of the PR and between efbae5f and 7906e62.

📒 Files selected for processing (15)
  • src/backend/core/api/permissions.py
  • src/backend/core/api/viewsets/thread_event.py
  • src/backend/core/api/viewsets/thread_user.py
  • src/backend/core/tests/api/test_thread_event_permissions.py
  • src/backend/core/tests/api/test_thread_user.py
  • src/frontend/src/features/forms/components/combobox/_index.scss
  • src/frontend/src/features/forms/components/message-form/attachment-uploader.tsx
  • src/frontend/src/features/forms/components/message-form/index.tsx
  • src/frontend/src/features/layouts/components/mailbox-panel/components/mailbox-actions/index.tsx
  • src/frontend/src/features/layouts/components/thread-panel/components/thread-panel-header.tsx
  • src/frontend/src/features/layouts/components/thread-view/index.tsx
  • src/frontend/src/styles/cunningham-tokens.css
  • src/frontend/src/styles/cunningham-tokens.scss
  • src/frontend/src/styles/cunningham-tokens.ts
  • src/frontend/src/styles/globals.scss
💤 Files with no reviewable changes (1)
  • src/frontend/src/features/layouts/components/mailbox-panel/components/mailbox-actions/index.tsx

Writing an internal comment is a personal authoring act that should
not require thread edit rights: support teammates invited as thread
viewers must still be able to comment and mention colleagues, as long
as they have edit rights on the mailbox. ThreadEvent IM writes and
ThreadUser listing are relaxed accordingly, while every other thread
mutation keeps the full edit-rights check.

  The message composer is now gated by the thread edit ability so that
  read-only users cannot bypass the check through reply or forward, and
  the thread-panel selection separator is hidden when no bulk action is
  available. A few unrelated UI polish fixes (disabled link button
  style, combobox placeholder visibility) ship alongside.
@jbpenrath jbpenrath merged commit 8f8798c into main Apr 15, 2026
19 of 20 checks passed
@jbpenrath jbpenrath deleted the fix/few-issues branch April 15, 2026 15:07
@coderabbitai coderabbitai Bot mentioned this pull request Apr 22, 2026
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