Skip to content

Add v2 mark workspace as unread#3773

Merged
saddlepaddle merged 2 commits intomainfrom
unread-workspaces-notific
Apr 26, 2026
Merged

Add v2 mark workspace as unread#3773
saddlepaddle merged 2 commits intomainfrom
unread-workspaces-notific

Conversation

@saddlepaddle
Copy link
Copy Markdown
Collaborator

@saddlepaddle saddlepaddle commented Apr 26, 2026

Summary

  • Adds a Mark as Unread / Mark as Read toggle to the v2 sidebar workspace right-click menu (icons match v1: LuEye / LuEyeOff).
  • Reuses the existing notification system instead of a parallel unread store: a new { type: "manual", id: workspaceId } source with status "review" shows the same green dot as a real ready-for-review notification.
  • Clicking the workspace already calls clearWorkspaceAttention(workspaceId), so manual unread auto-clears on visit — no extra wiring needed.

Test plan

  • Right-click a v2 workspace → menu shows Mark as Unread
  • Selecting it adds a green dot on the workspace icon (collapsed and expanded sidebar)
  • Right-click the same workspace → menu now shows Mark as Read; selecting clears the dot
  • Manually mark unread, then click the workspace → dot clears (auto-marked as read on visit)
  • Real ready-for-review notification still works and is unaffected

Summary by cubic

Add a Mark as Unread/Read toggle to the v2 sidebar workspace context menu to quickly flag workspaces with the same green dot. “Mark as Unread” creates a { type: "manual", id: workspaceId } review entry; “Mark as Read” now clears any review state (manual or real) via clearWorkspaceAttention, and dots auto‑clear on open.

Written for commit 281acab. Summary will update on new commits.

Summary by CodeRabbit

  • New Features
    • Context menus on workspace sidebar items now include "Mark as Read" / "Mark as Unread" actions with matching icons for clearer state control.
    • Users can manually toggle a workspace's unread status, allowing persistent manual unread/clear behavior to better manage workspace notifications and attention.

Adds a "Mark as Unread"/"Mark as Read" toggle to the v2 sidebar
workspace context menu. Marking unread inserts a manual notification
source ({type:"manual",id:workspaceId}) with status "review", so the
workspace shows the same green dot as a real ready-for-review
notification and is auto-cleared by the existing
clearWorkspaceAttention call when the workspace is clicked.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 26, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5fe647eb-24f2-414c-bf25-c073a68d7425

📥 Commits

Reviewing files that changed from the base of the PR and between 6e45617 and 281acab.

📒 Files selected for processing (5)
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/DashboardSidebarWorkspaceItem.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceContextMenu/DashboardSidebarWorkspaceContextMenu.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/hooks/useDashboardSidebarWorkspaceItemActions/useDashboardSidebarWorkspaceItemActions.ts
  • apps/desktop/src/renderer/stores/v2-notifications/index.ts
  • apps/desktop/src/renderer/stores/v2-notifications/store.ts
✅ Files skipped from review due to trivial changes (1)
  • apps/desktop/src/renderer/stores/v2-notifications/index.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/desktop/src/renderer/stores/v2-notifications/store.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/DashboardSidebarWorkspaceItem.tsx

📝 Walkthrough

Walkthrough

Adds a manual "unread" notification source to the v2 notification store and wires a toggle action through workspace sidebar hook and context menu, allowing users to mark workspace items as read/unread via the sidebar context menu.

Changes

Cohort / File(s) Summary
Notification Store Implementation
apps/desktop/src/renderer/stores/v2-notifications/store.ts
Adds manual notification source variant, setManualUnread(workspaceId) store method, getV2ManualNotificationSource(workspaceId) helper, and selector/hook pair selectV2WorkspaceIsUnread / useV2WorkspaceIsUnread to report workspace unread state.
Notification Store Exports
apps/desktop/src/renderer/stores/v2-notifications/index.ts
Re-exports getV2ManualNotificationSource, selectV2WorkspaceIsUnread, and useV2WorkspaceIsUnread.
Workspace Item Hook
apps/desktop/src/renderer/.../useDashboardSidebarWorkspaceItemActions/useDashboardSidebarWorkspaceItemActions.ts
Imports and uses useV2WorkspaceIsUnread(workspaceId) and setManualUnread, exposes isUnread and handleToggleUnread that clear attention or set manual unread depending on current state.
Context Menu & Item Component
apps/desktop/src/renderer/.../DashboardSidebarWorkspaceContextMenu/DashboardSidebarWorkspaceContextMenu.tsx, apps/desktop/src/renderer/.../DashboardSidebarWorkspaceItem/DashboardSidebarWorkspaceItem.tsx
Context menu props extended with isUnread and onToggleUnread; menu renders "Mark as Read"/"Mark as Unread" with eye/eye-off icons and calls handler. DashboardSidebarWorkspaceItem forwards isUnread and handleToggleUnread in both collapsed and expanded branches.

Sequence Diagram(s)

sequenceDiagram
  participant User as User
  participant UI as Sidebar ContextMenu
  participant Hook as Workspace Item Hook
  participant Store as V2 Notification Store
  User->>UI: Open context menu & select toggle
  UI->>Hook: call onToggleUnread()
  Hook->>Store: setManualUnread(workspaceId) OR clear attention
  Store->>Store: update sources (manual type, status "review")
  Store-->>Hook: updated isUnread selector
  Hook-->>UI: updated isUnread prop
  UI-->>User: update menu/indicator state
  rect rgba(200,230,255,0.5)
    note right of Hook: Hook coordinates UI <> Store
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I nibble keys and flip the light,

A workspace marked unread tonight.
I toggle eyes with a gentle hop,
The store remembers—never stops.
Hooray for tiny rabbit ops! 🥕✨

🚥 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
Title check ✅ Passed The title accurately summarizes the main change: adding v2 mark workspace as unread functionality. It is concise, specific, and directly related to the changeset.
Description check ✅ Passed The description covers the key aspects well: feature summary, implementation approach (reusing notification system), test plan, and additional context. While the provided template requires specific sections (Description, Related Issues, Type of Change, Testing, Screenshots, Additional Notes), the PR description adequately addresses the essential information.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch unread-workspaces-notific

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.

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 26, 2026

Greptile Summary

This PR adds a Mark as Unread / Mark as Read toggle to the v2 sidebar workspace right-click context menu. It introduces a new { type: \"manual\" } notification source that integrates cleanly into the existing V2NotificationState store, reusing clearWorkspaceAttention to auto-clear on workspace visit.

Confidence Score: 5/5

Safe to merge — the change is well-scoped, reuses existing store infrastructure correctly, and has no logic bugs.

All findings are P2 or lower. The manual unread state is in-memory only (ephemeral across sessions), which appears intentional. The selectV2WorkspaceIsManuallyUnread selector contains a slightly redundant workspaceId guard but is functionally correct. No correctness, data-integrity, or security concerns.

No files require special attention.

Important Files Changed

Filename Overview
apps/desktop/src/renderer/stores/v2-notifications/store.ts Adds manual source type, setManualUnread/clearManualUnread actions, and selectV2WorkspaceIsManuallyUnread selector; logic is correct and consistent with existing patterns
apps/desktop/src/renderer/stores/v2-notifications/index.ts Re-exports new public symbols (getV2ManualNotificationSource, selectV2WorkspaceIsManuallyUnread, useV2WorkspaceIsManuallyUnread); no issues
apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/hooks/useDashboardSidebarWorkspaceItemActions/useDashboardSidebarWorkspaceItemActions.ts Wires isManuallyUnread, setManualUnread, clearManualUnread from the store into handleToggleUnread; straightforward and correct
apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceContextMenu/DashboardSidebarWorkspaceContextMenu.tsx Adds isManuallyUnread prop and toggleable menu item with LuEye/LuEyeOff icons; UI logic is correct
apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/DashboardSidebarWorkspaceItem.tsx Propagates isManuallyUnread and onToggleUnread to both collapsed and expanded DashboardSidebarWorkspaceContextMenu instances; no issues

Sequence Diagram

sequenceDiagram
    participant User
    participant ContextMenu as DashboardSidebarWorkspaceContextMenu
    participant Hook as useDashboardSidebarWorkspaceItemActions
    participant Store as V2NotificationStore

    User->>ContextMenu: Right-click workspace
    ContextMenu-->>User: Show "Mark as Unread" (LuEyeOff) or "Mark as Read" (LuEye)

    User->>ContextMenu: Click "Mark as Unread"
    ContextMenu->>Hook: onToggleUnread()
    Hook->>Store: setManualUnread(workspaceId)
    Store->>Store: setSourceStatus({type:"manual", id:workspaceId}, workspaceId, "review")
    Store-->>ContextMenu: isManuallyUnread = true → green dot shown

    User->>ContextMenu: Right-click → Click "Mark as Read"
    ContextMenu->>Hook: onToggleUnread()
    Hook->>Store: clearManualUnread(workspaceId)
    Store->>Store: clearSourceStatus({type:"manual", id:workspaceId})
    Store-->>ContextMenu: isManuallyUnread = false → dot removed

    User->>Hook: Click workspace (navigate)
    Hook->>Store: clearWorkspaceAttention(workspaceId)
    Store->>Store: Remove all "review" sources for workspaceId (incl. manual)
    Store-->>ContextMenu: isManuallyUnread = false → dot removed
Loading
Prompt To Fix All With AI
This is a comment left during a code review.
Path: apps/desktop/src/renderer/stores/v2-notifications/store.ts
Line: 318-319

Comment:
**Redundant `workspaceId` guard in selector**

The check `?.workspaceId === workspaceId` is always true when the entry exists, because the source key is `manual:${workspaceId}` and `setManualUnread` always writes the same `workspaceId` into the entry. A simpler expression would be `!!state.sources[sourceKey]`, which makes the intent (entry presence) clearer.

```suggestion
state.sources[sourceKey] != null;
```

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "add v2 mark workspace as unread" | Re-trigger Greptile

Comment on lines +318 to +319
return (state: V2NotificationState) =>
state.sources[sourceKey]?.workspaceId === 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.

P2 Redundant workspaceId guard in selector

The check ?.workspaceId === workspaceId is always true when the entry exists, because the source key is manual:${workspaceId} and setManualUnread always writes the same workspaceId into the entry. A simpler expression would be !!state.sources[sourceKey], which makes the intent (entry presence) clearer.

Suggested change
return (state: V2NotificationState) =>
state.sources[sourceKey]?.workspaceId === workspaceId;
state.sources[sourceKey] != null;
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/renderer/stores/v2-notifications/store.ts
Line: 318-319

Comment:
**Redundant `workspaceId` guard in selector**

The check `?.workspaceId === workspaceId` is always true when the entry exists, because the source key is `manual:${workspaceId}` and `setManualUnread` always writes the same `workspaceId` into the entry. A simpler expression would be `!!state.sources[sourceKey]`, which makes the intent (entry presence) clearer.

```suggestion
state.sources[sourceKey] != null;
```

How can I resolve this? If you propose a fix, please make it concise.

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: 1

🧹 Nitpick comments (1)
apps/desktop/src/renderer/stores/v2-notifications/store.ts (1)

314-324: Optional: redundant workspaceId equality check.

Since sourceKey is derived from workspaceId (manual:${workspaceId}), any entry stored under that key is guaranteed to have a matching workspaceId. The ?.workspaceId === workspaceId check is equivalent to a presence check.

♻️ Optional simplification
 export function selectV2WorkspaceIsManuallyUnread(workspaceId: string) {
 	const sourceKey = getV2NotificationSourceKey(
 		getV2ManualNotificationSource(workspaceId),
 	);
-	return (state: V2NotificationState) =>
-		state.sources[sourceKey]?.workspaceId === workspaceId;
+	return (state: V2NotificationState) => sourceKey in state.sources;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/renderer/stores/v2-notifications/store.ts` around lines 314
- 324, The selectV2WorkspaceIsManuallyUnread function does an unnecessary
equality check because sourceKey is derived from workspaceId; replace the
current condition state.sources[sourceKey]?.workspaceId === workspaceId with a
simple presence check like Boolean(state.sources[sourceKey]) (or
!!state.sources[sourceKey]) to return whether the manual source exists; update
selectV2WorkspaceIsManuallyUnread (and confirm useV2WorkspaceIsManuallyUnread
remains unchanged) and ensure references to getV2NotificationSourceKey and
getV2ManualNotificationSource are used to compute sourceKey as before.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/hooks/useDashboardSidebarWorkspaceItemActions/useDashboardSidebarWorkspaceItemActions.ts`:
- Around line 137-143: The current handleToggleUnread in
useDashboardSidebarWorkspaceItemActions toggles manual unread even when the
workspace isActive, causing a green dot to appear on the active workspace;
change handleToggleUnread to no-op when isActive is true (i.e., if (isActive)
return) so marking unread does nothing for the currently-viewed workspace,
otherwise keep the existing logic calling clearManualUnread(workspaceId) or
setManualUnread(workspaceId); update any related tests or UX notes to reflect
this behavior and ensure references: handleToggleUnread, isActive,
isManuallyUnread, clearManualUnread, setManualUnread, and
clearWorkspaceAttention are considered.

---

Nitpick comments:
In `@apps/desktop/src/renderer/stores/v2-notifications/store.ts`:
- Around line 314-324: The selectV2WorkspaceIsManuallyUnread function does an
unnecessary equality check because sourceKey is derived from workspaceId;
replace the current condition state.sources[sourceKey]?.workspaceId ===
workspaceId with a simple presence check like Boolean(state.sources[sourceKey])
(or !!state.sources[sourceKey]) to return whether the manual source exists;
update selectV2WorkspaceIsManuallyUnread (and confirm
useV2WorkspaceIsManuallyUnread remains unchanged) and ensure references to
getV2NotificationSourceKey and getV2ManualNotificationSource are used to compute
sourceKey as before.
🪄 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: 2432262a-dfdc-4b85-b92a-1b51832d0712

📥 Commits

Reviewing files that changed from the base of the PR and between 97c48d7 and 6e45617.

📒 Files selected for processing (5)
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/DashboardSidebarWorkspaceItem.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceContextMenu/DashboardSidebarWorkspaceContextMenu.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/hooks/useDashboardSidebarWorkspaceItemActions/useDashboardSidebarWorkspaceItemActions.ts
  • apps/desktop/src/renderer/stores/v2-notifications/index.ts
  • apps/desktop/src/renderer/stores/v2-notifications/store.ts

Comment on lines +137 to +143
const handleToggleUnread = () => {
if (isManuallyUnread) {
clearManualUnread(workspaceId);
} else {
setManualUnread(workspaceId);
}
};
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

Minor UX note: marking unread while the workspace is active.

If the user invokes "Mark as Unread" while currently viewing the workspace, the green dot will appear on the active workspace and will only auto-clear once the user navigates away and then re-enters via a sidebar click (the only path that calls clearWorkspaceAttention). Switching tabs/panes within the active workspace won't clear it. Probably fine — and matches the PR description's auto-clear-on-visit model — but worth confirming this is the intended behavior, or consider a no-op when isActive is true.

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

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/hooks/useDashboardSidebarWorkspaceItemActions/useDashboardSidebarWorkspaceItemActions.ts`
around lines 137 - 143, The current handleToggleUnread in
useDashboardSidebarWorkspaceItemActions toggles manual unread even when the
workspace isActive, causing a green dot to appear on the active workspace;
change handleToggleUnread to no-op when isActive is true (i.e., if (isActive)
return) so marking unread does nothing for the currently-viewed workspace,
otherwise keep the existing logic calling clearManualUnread(workspaceId) or
setManualUnread(workspaceId); update any related tests or UX notes to reflect
this behavior and ensure references: handleToggleUnread, isActive,
isManuallyUnread, clearManualUnread, setManualUnread, and
clearWorkspaceAttention are considered.

Toggle now reflects any review-status entry (manual or real
ready-for-review notification) and clears them all via
clearWorkspaceAttention. Drops the now-unused clearManualUnread
action.
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

No issues found across 5 files

@saddlepaddle saddlepaddle merged commit 6ada9bf into main Apr 26, 2026
7 checks passed
@github-actions
Copy link
Copy Markdown
Contributor

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ⚠️ Neon database branch

Thank you for your contribution! 🎉

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