Skip to content

fix(web): respect granular task permissions - #1520

Merged
andrejsshell merged 4 commits into
mainfrom
fix/1505-task-update-permissions
Aug 15, 2026
Merged

fix(web): respect granular task permissions#1520
andrejsshell merged 4 commits into
mainfrom
fix/1505-task-update-permissions

Conversation

@tinsever

@tinsever tinsever commented Aug 8, 2026

Copy link
Copy Markdown
Member

Description

Respect task and label permissions individually in the task editing UI. Members with task update permission can edit task fields without requiring task delete permission.

Related Issue(s)

Fixes #1505

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Refactoring (no functional changes)
  • Performance improvement
  • Test addition or update
  • Other (please describe):

How Has This Been Tested?

  • Unit tests
  • Integration tests
  • Manual testing
  • Other (please describe):

Screenshots (if applicable)

Checklist

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • Any dependent changes have been merged and published

Additional Notes

Summary by CodeRabbit

  • New Features

    • Added granular permissions for creating, updating, deleting, assigning, and labeling tasks.
    • Bulk actions now appear individually based on available permissions.
    • Label assignment and removal are more reliable, with separate controls for creating and editing labels.
    • Task fields, subtasks, relations, and descriptions now respect update permissions.
    • Workspace label creation, editing, and deletion are independently controlled.
  • Bug Fixes

    • Unauthorized users no longer see task editing, attachment, or mutation controls.
    • Subtask creation now requires task creation permission.
    • Users can view labels without seeing unauthorized editing controls.

@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: b58f5c60-97b2-4148-94d7-07ae351950f4

📥 Commits

Reviewing files that changed from the base of the PR and between c7b5910 and 5edd50d.

📒 Files selected for processing (3)
  • apps/web/src/components/task/task-description.tsx
  • apps/web/src/components/task/task-subtasks.tsx
  • apps/web/src/hooks/use-workspace-permission.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • apps/web/src/components/task/task-subtasks.tsx
  • apps/web/src/hooks/use-workspace-permission.ts
  • apps/web/src/components/task/task-description.tsx

📝 Walkthrough

Walkthrough

The PR replaces aggregate task and label permissions with operation-specific checks. It updates task controls, bulk toolbars, workspace label settings, task descriptions, subtasks, and task-label mutations.

Changes

Granular permissions and label actions

Layer / File(s) Summary
Permission contract and label settings
apps/web/src/hooks/use-workspace-permission.ts, apps/web/src/routes/.../workspace/labels.tsx, apps/web/src/components/shared/modals/create-task-modal.*
The permission hook exposes separate task and label capabilities. Label actions render independently for create, update, and delete permissions.
Task edit permission wiring
apps/web/src/components/task/*, apps/web/src/components/kanban-board/..., apps/web/src/components/shared/modals/create-task-modal.tsx
Task editors, descriptions, menus, fields, and subtasks use update, delete, and create permissions.
Bulk action permissions
apps/web/src/components/bulk-selection/*toolbar.tsx
Bulk actions are included independently for task update, delete, assignment, and label-update permissions.
Task-label association flow
apps/web/src/components/task/task-labels-popover.tsx, apps/web/src/fetchers/label/*, apps/web/src/hooks/mutations/label/*, apps/web/src/lib/get-task-label-options.*
Task labels use dedicated attach and detach requests. Mutation hooks update task and workspace label caches. Label options exclude unrelated task-scoped labels.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant TaskLabelsPopover
  participant LabelMutation
  participant LabelAPI
  TaskLabelsPopover->>LabelMutation: attach or detach task label
  LabelMutation->>LabelAPI: send label-task request
  LabelAPI-->>LabelMutation: return response
  LabelMutation-->>TaskLabelsPopover: update cached labels
Loading

Possibly related PRs

Suggested reviewers: andrejsshell

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: enforcing granular task permissions in the web UI.
Linked Issues check ✅ Passed The changes satisfy issue [#1505] by applying separate task and label permissions to state, dates, labels, priorities, and related actions.
Out of Scope Changes check ✅ Passed The permission checks, label mutations, UI updates, and tests are directly related to issue [#1505].
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/1505-task-update-permissions

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.

@tinsever
tinsever marked this pull request as ready for review August 8, 2026 11:10
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Fix web UI to honor granular task/label permissions

🐞 Bug fix 🧪 Tests 🕐 40+ Minutes

Grey Divider

AI Description

• Split task and label permissions into create/update/delete checks across the UI.
• Show/hide bulk actions and context menu items based on update vs delete capabilities.
• Fix task label assignment to attach/detach labels instead of creating/deleting labels.
Diagram

graph TD
  UI["Task & bulk UI"] --> PERM["useWorkspacePermission"] --> AUTH["authClient.hasPermission"]
  UI --> LH["Label attach/detach hooks"] --> LF["Label task fetchers"] --> API[("Label↔Task API")]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Keep legacy canManageTasks/canManageLabels as derived aliases
  • ➕ Reduces churn across the UI by preserving existing call sites
  • ➕ Allows incremental migration to granular permissions
  • ➖ Easy to keep using coarse checks accidentally, reintroducing the original bug
  • ➖ Maintains ambiguous semantics (manage implies multiple actions)
2. Centralize UI gating into a single 'capability map' helper
  • ➕ Prevents repetitive permission wiring in each component
  • ➕ Makes it harder to accidentally tie unrelated actions together
  • ➖ Adds abstraction that may hide important per-action differences
  • ➖ Still requires broad touch points for initial adoption

Recommendation: The PR’s approach (explicit canCreate/canUpdate/canDelete per resource, with UI gating per action) is the right fix because it prevents accidental coupling of update and delete permissions (root cause of #1505). Consider optionally adding derived legacy aliases (e.g., canManageTasks = create+update+delete) only if you need a smoother migration path elsewhere, but keep new call sites on granular checks.

Files changed (25) +422 / -155

Enhancement (4) +143 / -0
attach-label-to-task.tsAdd fetcher for attaching an existing label to a task +24/-0

Add fetcher for attaching an existing label to a task

• Introduces a typed fetcher that calls PUT label/:id/task with the taskId payload and throws on non-OK responses.

apps/web/src/fetchers/label/attach-label-to-task.ts

detach-label-from-task.tsAdd fetcher for detaching a label from a task +19/-0

Add fetcher for detaching a label from a task

• Adds a fetcher that calls DELETE label/:id/task and returns the server response JSON for cache updates.

apps/web/src/fetchers/label/detach-label-from-task.ts

use-attach-label-to-task.tsAdd attach-label mutation with cache synchronization +54/-0

Add attach-label mutation with cache synchronization

• Adds a React Query mutation wrapper for attaching labels, updating both workspace- and task-label query caches and syncing task list caches via shared helpers.

apps/web/src/hooks/mutations/label/use-attach-label-to-task.ts

use-detach-label-from-task.tsAdd detach-label mutation with cache synchronization +46/-0

Add detach-label mutation with cache synchronization

• Adds a React Query mutation wrapper for detaching labels, updating caches and syncing task list caches to remove the label association.

apps/web/src/hooks/mutations/label/use-detach-label-from-task.ts

Bug fix (16) +193 / -148
backlog-bulk-toolbar.tsxGate backlog bulk actions by update/delete/label-update permissions +35/-20

Gate backlog bulk actions by update/delete/label-update permissions

• Replaces coarse task permission checks with canUpdateTasks/canDeleteTasks/canUpdateLabels. Bulk delete and archive entries are conditionally included based on the specific capability, and the toolbar now renders only when at least one bulk operation is permitted.

apps/web/src/components/bulk-selection/backlog-bulk-toolbar.tsx

bulk-toolbar.tsxSplit bulk toolbar groups by granular task and label permissions +37/-20

Split bulk toolbar groups by granular task and label permissions

• Uses canUpdateTasks/canDeleteTasks/canUpdateLabels to build grouped bulk menu items. Ensures delete is available without requiring update, and label actions are available only with label-update permission.

apps/web/src/components/bulk-selection/bulk-toolbar.tsx

column-header.tsxUse canUpdateTasks for kanban column task operations +2/-2

Use canUpdateTasks for kanban column task operations

• Swaps canManageTasks for canUpdateTasks to align UI gating with update permission instead of an implied manage bundle.

apps/web/src/components/kanban-board/column/column-header.tsx

task-card-context-menu-content.tsxSeparate task archive vs delete in context menu permissions +36/-22

Separate task archive vs delete in context menu permissions

• Introduces canDeleteTasks alongside canUpdateTasks so archive/mark-planned stays available with update permission, while delete remains restricted. Context menu separators/items are now conditionally rendered to avoid showing unavailable actions.

apps/web/src/components/kanban-board/task-card-context-menu/task-card-context-menu-content.tsx

create-task-modal.tsxGate label creation in task modal with canCreateLabels +2/-2

Gate label creation in task modal with canCreateLabels

• Replaces canManageLabels with canCreateLabels to ensure only label creation is required for creating workspace labels during task creation.

apps/web/src/components/shared/modals/create-task-modal.tsx

subtask-status-popover.tsxUse canUpdateTasks for subtask status changes +2/-2

Use canUpdateTasks for subtask status changes

• Replaces canManageTasks with canUpdateTasks so status editing requires only update permission.

apps/web/src/components/task/subtask-status-popover.tsx

task-description.tsxGate task description editing on update permission +2/-2

Gate task description editing on update permission

• Switches from canManageTasks to canUpdateTasks for enabling/disabling description edits.

apps/web/src/components/task/task-description.tsx

task-due-date-popover.tsxGate due date edits on canUpdateTasks +2/-2

Gate due date edits on canUpdateTasks

• Updates permission check to canUpdateTasks so users with update capability can change due dates without delete permission.

apps/web/src/components/task/task-due-date-popover.tsx

task-labels-popover.tsxFix label assignment to attach/detach and gate by label capabilities +17/-25

Fix label assignment to attach/detach and gate by label capabilities

• Replaces label deletion/creation misuse with attach/detach label-to-task mutations and uses canCreateLabels/canUpdateLabels for UI gating. Also fixes the 'create label then assign to task' flow by creating once, then attaching the created label to the task.

apps/web/src/components/task/task-labels-popover.tsx

task-priority-popover.tsxUse canUpdateTasks for priority edits +2/-2

Use canUpdateTasks for priority edits

• Moves from canManageTasks to canUpdateTasks to correctly require only update capability for priority changes.

apps/web/src/components/task/task-priority-popover.tsx

task-relations.tsxGate relation add/remove on task update permission +2/-2

Gate relation add/remove on task update permission

• Uses canUpdateTasks instead of canManageTasks to allow relation editing for members with update capability.

apps/web/src/components/task/task-relations.tsx

task-start-date-popover.tsxUse canUpdateTasks for start date edits +2/-2

Use canUpdateTasks for start date edits

• Updates edit gating from canManageTasks to canUpdateTasks for task start date changes.

apps/web/src/components/task/task-start-date-popover.tsx

task-status-popover.tsxGate status edits on update permission +2/-2

Gate status edits on update permission

• Replaces canManageTasks with canUpdateTasks to ensure status changes only require update capability.

apps/web/src/components/task/task-status-popover.tsx

task-subtasks.tsxUse canUpdateTasks for subtask editing controls +2/-2

Use canUpdateTasks for subtask editing controls

• Swaps canManageTasks for canUpdateTasks so subtask status and related edits are correctly gated by update permission.

apps/web/src/components/task/task-subtasks.tsx

task-title.tsxGate title editing on canUpdateTasks +2/-2

Gate title editing on canUpdateTasks

• Replaces canManageTasks with canUpdateTasks so title edits no longer require delete permission.

apps/web/src/components/task/task-title.tsx

labels.tsxHonor create/update/delete label permissions in label settings UI +46/-39

Honor create/update/delete label permissions in label settings UI

• Updates the labels settings screen to show the create button only with canCreateLabels, and show edit/delete controls only when the respective update/delete capability is granted.

apps/web/src/routes/_layout/_authenticated/dashboard/settings/workspace/labels.tsx

Refactor (1) +10 / -4
use-workspace-permission.tsSplit manageTasks/manageLabels into create/update/delete capabilities +10/-4

Split manageTasks/manageLabels into create/update/delete capabilities

• Replaces bundled manageTasks/manageLabels capabilities with explicit create/update/delete entries for tasks and labels, and exposes corresponding helper methods from the hook.

apps/web/src/hooks/use-workspace-permission.ts

Tests (4) +76 / -3
create-task-modal.test.tsxUpdate create-task modal permission mock for label creation +1/-1

Update create-task modal permission mock for label creation

• Adjusts the mocked permission API to use canCreateLabels instead of canManageLabels, matching the new capability split.

apps/web/src/components/shared/modals/create-task-modal.test.tsx

task-status-popover.test.tsxUpdate status popover test to mock canUpdateTasks +1/-1

Update status popover test to mock canUpdateTasks

• Adjusts permission hook mocks in tests to reflect the new canUpdateTasks API.

apps/web/src/components/task/task-status-popover.test.tsx

task-subtasks.test.tsxUpdate subtasks tests to mock canUpdateTasks +1/-1

Update subtasks tests to mock canUpdateTasks

• Updates permission mocking to align with the new granular task update capability.

apps/web/src/components/task/task-subtasks.test.tsx

use-workspace-permission.test.tsxAdd unit test ensuring update and delete capabilities are independent +73/-0

Add unit test ensuring update and delete capabilities are independent

• Adds a hook test that mocks permission checks and verifies canUpdate* does not imply canDelete* for tasks and labels (regression coverage for #1505).

apps/web/src/hooks/use-workspace-permission.test.tsx

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 2

🤖 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 `@apps/web/src/components/task/task-description.tsx`:
- Around line 267-268: Use the canEdit value from useWorkspacePermission to gate
all task-description editor mutations: guard language-selector and BubbleMenu
editor.chain actions, and prevent onUpdate from publishing changes when canEdit
is false. Keep editor.setEditable(canEdit) and preserve read-only actions such
as code copying.

In `@apps/web/src/components/task/task-subtasks.tsx`:
- Around line 71-72: In the task-subtask component, read canCreateTasks()
separately from canUpdateTasks() and use the resulting canCreate permission for
the add-subtask controls and handleAddSubtask gating. Keep canEdit backed by
canUpdateTasks() for status and other edit controls, and ensure creation is
allowed only when task-create permission is present.
🪄 Autofix

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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: eddac81c-58e5-4dc1-a30c-e347e2a3c16d

📥 Commits

Reviewing files that changed from the base of the PR and between df60800 and 64f9f56.

📒 Files selected for processing (25)
  • apps/web/src/components/bulk-selection/backlog-bulk-toolbar.tsx
  • apps/web/src/components/bulk-selection/bulk-toolbar.tsx
  • apps/web/src/components/kanban-board/column/column-header.tsx
  • apps/web/src/components/kanban-board/task-card-context-menu/task-card-context-menu-content.tsx
  • apps/web/src/components/shared/modals/create-task-modal.test.tsx
  • apps/web/src/components/shared/modals/create-task-modal.tsx
  • apps/web/src/components/task/subtask-status-popover.tsx
  • apps/web/src/components/task/task-description.tsx
  • apps/web/src/components/task/task-due-date-popover.tsx
  • apps/web/src/components/task/task-labels-popover.tsx
  • apps/web/src/components/task/task-priority-popover.tsx
  • apps/web/src/components/task/task-relations.tsx
  • apps/web/src/components/task/task-start-date-popover.tsx
  • apps/web/src/components/task/task-status-popover.test.tsx
  • apps/web/src/components/task/task-status-popover.tsx
  • apps/web/src/components/task/task-subtasks.test.tsx
  • apps/web/src/components/task/task-subtasks.tsx
  • apps/web/src/components/task/task-title.tsx
  • apps/web/src/fetchers/label/attach-label-to-task.ts
  • apps/web/src/fetchers/label/detach-label-from-task.ts
  • apps/web/src/hooks/mutations/label/use-attach-label-to-task.ts
  • apps/web/src/hooks/mutations/label/use-detach-label-from-task.ts
  • apps/web/src/hooks/use-workspace-permission.test.tsx
  • apps/web/src/hooks/use-workspace-permission.ts
  • apps/web/src/routes/_layout/_authenticated/dashboard/settings/workspace/labels.tsx

Comment thread apps/web/src/components/task/task-description.tsx
Comment thread apps/web/src/components/task/task-subtasks.tsx Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 64f9f560b2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +144 to 147
await attachLabel({
labelId: workspaceLabel.id,
taskId: task.id,
workspaceId,
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve assignments when attaching task-scoped labels

When the label list contains only a task-scoped copy—such as a GitHub/Gitea-imported label without a workspace-level row—workspaceLabel.id identifies the copy already assigned to another task. assignLabelToTask deletes that row from its previous task before inserting it for the target, so selecting the label here silently moves it instead of copying it as the previous implementation did. Only pass workspace-level labels to this endpoint, or retain copy semantics for task-scoped rows.

Useful? React with 👍 / 👎.

Comment on lines +71 to +72
const { canUpdateTasks } = useWorkspacePermission();
const canEdit = canUpdateTasks();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Require task-create permission for subtask creation

For a role with task:update but not task:create, this makes canEdit true and renders the Add Subtask control, but handleAddSubtask calls the task-creation endpoint, which requires task:create, so every attempt ends in an authorization error. Subtask creation should be gated by both the update permission needed to create the relation and the create permission needed to create the task.

Useful? React with 👍 / 👎.

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Aug 8, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Label attach moves labels ✓ Resolved 🐞 Bug ≡ Correctness
Description
TaskLabelsPopover calls attachLabelToTask using the selected label row’s id from the workspace label
list; if that row is a task-level label (taskId != null), the API deletes it from its previous task
and recreates it for the current task, effectively moving the label. Because the workspace-label
query includes task-level labels and the popover can surface a task-level label when no
workspace-level label exists for a name, “adding” a label can unintentionally remove it from another
task.
Code

apps/web/src/components/task/task-labels-popover.tsx[R144-147]

+        await attachLabel({
+          labelId: workspaceLabel.id,
      taskId: task.id,
-          workspaceId,
    });
Evidence
The popover derives choices from workspaceLabels and may keep a label with taskId != null when
no workspace-level label exists for a name, then passes that label’s id into attachLabelToTask.
The backend attach controller deletes the existing label row when it already has a taskId before
inserting a new row for the new task, which is a move behavior. The workspace label query returns
all label rows for the workspace, including task-level rows, making this scenario reachable.

apps/web/src/components/task/task-labels-popover.tsx[84-149]
apps/api/src/label/controllers/get-labels-by-workspace-id.ts[5-10]
apps/api/src/label/controllers/assign-label-to-task.ts[96-113]
apps/api/src/label/controllers/create-label.ts[16-46]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`TaskLabelsPopover.handleToggleLabel` uses the selected label’s `id` (from `workspaceLabels`) when calling `attachLabelToTask`. If the selected label is a task-level label (`taskId != null`), the API treats this as a move (delete the existing row, insert a new row for the new task), which can remove the label from a different task.
### Issue Context
- The workspace labels endpoint returns all labels with the workspaceId (including task-level label rows).
- The popover’s `filteredLabels` can return a task-level label when there is no workspace-level (`taskId == null`) label for that name.
### Fix Focus Areas
- apps/web/src/components/task/task-labels-popover.tsx[84-107]
- apps/web/src/components/task/task-labels-popover.tsx[127-149]
### Suggested fix
- Ensure the selectable list only contains **workspace-level** labels (`taskId === null`) for attach operations.
- Update `isCreatingNewLabel` to check existence only among workspace-level labels (so users can create a workspace-level label even if task-level copies exist).
- Defensive guard in `handleToggleLabel`: if `workspaceLabel.taskId !== null`, do not call `attachLabelToTask` with that id; instead, resolve/create a workspace-level label for that name (or block with a clear error) before attaching.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can turn on the rule miner and Qodo learns your standards from review history

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread apps/web/src/components/task/task-labels-popover.tsx

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 2

🤖 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 `@apps/web/src/components/task/task-description.tsx`:
- Line 757: Update the debouncedUpdate callback in the task description editor
to recheck canEditRef.current immediately before calling updateTaskDescription,
while preserving the existing scheduling guard and syncing-content behavior.

In `@apps/web/src/components/task/task-subtasks.tsx`:
- Line 262: Use the task-create permission alone for subtask creation: in
apps/web/src/components/task/task-subtasks.tsx lines 262-262, remove canEdit
from the creation guard; at lines 341-341 and 402-402, conditionally render the
add button and creation form when canCreate is true. In
apps/web/src/components/task/task-subtasks.test.tsx lines 162-179, add coverage
for task-create allowed with task-update denied and verify the add action
remains available.
🪄 Autofix

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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 3d505b75-16c0-49d4-9a65-f1b0ea306b90

📥 Commits

Reviewing files that changed from the base of the PR and between 64f9f56 and 642fe11.

📒 Files selected for processing (6)
  • apps/web/src/components/task/task-description.tsx
  • apps/web/src/components/task/task-labels-popover.tsx
  • apps/web/src/components/task/task-subtasks.test.tsx
  • apps/web/src/components/task/task-subtasks.tsx
  • apps/web/src/lib/get-task-label-options.test.ts
  • apps/web/src/lib/get-task-label-options.ts

Comment thread apps/web/src/components/task/task-description.tsx

const handleAddSubtask = async () => {
if (!newTitle.trim()) return;
if (!canCreate || !canEdit || !newTitle.trim()) return;

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Use task-create permission alone for subtask creation.

The add-subtask flow now requires canUpdateTasks() as well as canCreateTasks(). This denies task-create-only members, even though handleAddSubtask creates a task.

  • apps/web/src/components/task/task-subtasks.tsx#L262-L262: Remove canEdit from the creation guard.
  • apps/web/src/components/task/task-subtasks.tsx#L341-L341: Render the add button when canCreate is true.
  • apps/web/src/components/task/task-subtasks.tsx#L402-L402: Render the creation form when canCreate is true.
  • apps/web/src/components/task/task-subtasks.test.tsx#L162-L179: Add a case with task-create allowed and task-update denied. Verify that the add action remains available.
Proposed source fix
-    if (!canCreate || !canEdit || !newTitle.trim()) return;
+    if (!canCreate || !newTitle.trim()) return;

-          {canEdit && canCreate && (
+          {canCreate && (

-          {isAdding && canEdit && canCreate && (
+          {isAdding && canCreate && (
📍 Affects 2 files
  • apps/web/src/components/task/task-subtasks.tsx#L262-L262 (this comment)
  • apps/web/src/components/task/task-subtasks.tsx#L341-L341
  • apps/web/src/components/task/task-subtasks.tsx#L402-L402
  • apps/web/src/components/task/task-subtasks.test.tsx#L162-L179
🤖 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 `@apps/web/src/components/task/task-subtasks.tsx` at line 262, Use the
task-create permission alone for subtask creation: in
apps/web/src/components/task/task-subtasks.tsx lines 262-262, remove canEdit
from the creation guard; at lines 341-341 and 402-402, conditionally render the
add button and creation form when canCreate is true. In
apps/web/src/components/task/task-subtasks.test.tsx lines 162-179, add coverage
for task-create allowed with task-update denied and verify the add action
remains available.

@luantaraschi luantaraschi left a comment

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.

Checked this out at 5edd50dd, alongside #1559, which lands on the same issue. Since two PRs are open against #1505 I measured how they relate rather than reviewing this one in isolation.

The core of the fix is byte-identical in both PRs. Nine of the seventeen files #1559 touches are the same content at both heads, and each is a two-line change in #1559:

column-header.tsx, subtask-status-popover.tsx, task-due-date-popover.tsx,
task-priority-popover.tsx, task-relations.tsx, task-start-date-popover.tsx,
task-status-popover.tsx, task-status-popover.test.tsx, task-title.tsx

Both split manageTasks: { task: ["create","update","delete"] } into updateTasks and deleteTasks, with the same capability names and the same helper names. So on the task half of #1505 there is no disagreement between the two, and #1559's seventeen files are a strict subset of this PR's twenty-seven.

The difference is the label half, and #1505 asks for it explicitly. The report lists four broken things: state, dates, labels, priorities. #1559 stops at manageLabels, which still requires label: ["create","update","delete"] together:

// #1559, task-labels-popover.tsx
const { canUpdateTasks, canManageLabels } = useWorkspacePermission();
const canCreateLabels = canManageLabels();

The reporter's role grants labels create, read and update, with no delete, which is exactly the combination that fails that check. So #1559 fixes three of the four symptoms for that role and leaves the label one where it was. This PR splits the label capability into create, update and delete, which is what closes the fourth.

The label gating here matches the server, and the comment it removes did not. I went looking for a problem in gating attach and detach on canUpdateLabels, because the comment on main says attaching a label to a task is a task mutation. The API disagrees with that comment. Both routes in apps/api/src/label/index.ts guard on the label capability:

.put("/:id/task", ..., requireWorkspacePermission({ label: ["update"] }), ...)
.delete("/:id/task", ..., requireWorkspacePermission({ label: ["update"] }), ...)

So this PR's canUpdateLabels() mirrors what the server will actually enforce, and gating on task update would have shown controls that then fail with a 403. The new fetchers also target routes that already exist on main, so nothing here waits on an API change.

What I ran, on Windows with Node 22.20.0 and pnpm 10.34.1:

tree test files tests
main 3fc77f00 27 passed 82 passed
#1520 5edd50dd 26 passed 75 passed
#1559 a588f776 24 passed 71 passed

Each tree also has the same three failures, and all three are mine rather than anyone's code: two files import @kaneo/permissions, which my install did not build, and src/env.test.ts shells out to sed, which Windows does not have. The counts differ because the branches sit on different bases, not because tests were removed.

For whoever decides: this PR is the superset and the one that closes #1505 as filed. #1559 is a clean, smaller change that agrees with it line for line on the task half, so if you would rather land the small one first, the two do not conflict in intent, only in size. What I would avoid is merging #1559 and treating #1505 as done, since the label case that the reporter described would still be broken.

Thanks to both of you for working the same bug carefully.

@andrejsshell
andrejsshell merged commit 5472e6a into main Aug 15, 2026
17 checks passed
@andrejsshell
andrejsshell deleted the fix/1505-task-update-permissions branch August 15, 2026 14:25
andrejsshell added a commit that referenced this pull request Aug 15, 2026
#1581 added this test while the editor still read canManageTasks, and
#1520 renamed that capability to canUpdateTasks. Each PR was green on its
own branch; merged together the mock returns an object without the
function the component destructures, so the render throws.

Nothing to fix in either change, only the mock they were both written
against.
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.

[Bug]: member with 'task-update' permission cannot modify tasks

3 participants