Add entity pinning feature with sidebar widget#3858
Conversation
Adds a "Pinned" section to the app sidebar below the Unread channels section, and a right-click "Pin"/"Unpin" action in the soup entity context menu for all access-backed entity types (documents/files, emails, channels, chats, projects, calls). The macro backend already persists pins via the /pins endpoints, but its hydrating GET /pins query only returns documents, chats and projects. To support pinning any entity type for display, a small client-side store keeps a persisted snapshot of pinned entities and writes the pin set through to the backend. https://claude.ai/code/session_01SgxdRrh8YABj1ANhdDNJUe
📝 WalkthroughSummary by CodeRabbit
WalkthroughThis PR introduces an entity pinning feature across three surfaces. The core ( 🚥 Pre-merge checks | ✅ 3 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. 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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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 `@js/app/packages/app/signal/pins.ts`:
- Around line 31-49: Replace the native switch in toPinType with ts-pattern's
match: import { match } from 'ts-pattern' and use
match(entity.type).with('document', () => 'document').with('chat', () =>
'chat').with('project', () => 'project').with('email', () =>
'email_thread').with('call', () => 'call').with('channel', () =>
'channel').otherwise(() => null) so the mapping stays identical but becomes
exhaustive and type-safe; update the toPinType function to use this match
expression and remove the switch.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 3ff3b174-a5e0-4c14-98b5-ff0f5e0fbf3a
📒 Files selected for processing (6)
js/app/packages/app/component/app-sidebar/pinned-widget.tsxjs/app/packages/app/component/app-sidebar/sidebar.tsxjs/app/packages/app/component/next-soup/actions/index.tsjs/app/packages/app/component/next-soup/actions/make-pin-action.tsjs/app/packages/app/component/next-soup/soup-view/create-soup-entity-actions.tsjs/app/packages/app/signal/pins.ts
| function toPinType(entity: EntityData): string | null { | ||
| switch (entity.type) { | ||
| case 'document': | ||
| return 'document'; | ||
| case 'chat': | ||
| return 'chat'; | ||
| case 'project': | ||
| return 'project'; | ||
| case 'email': | ||
| return 'email_thread'; | ||
| case 'call': | ||
| return 'call'; | ||
| case 'channel': | ||
| return 'channel'; | ||
| // channel_message, automation and foreign entities are not pinnable. | ||
| default: | ||
| return null; | ||
| } | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win
Use match from ts-pattern for exhaustive entity type switching.
Per coding guidelines, exhaustive switch statements should use match from ts-pattern instead of native switch for better type safety and explicit case handling.
♻️ Refactor to use ts-pattern
+import { match } from 'ts-pattern';
+
/**
* Maps a frontend entity to the backend pin type (`EntityType`, snake_case).
* Returns `null` for entities that cannot be pinned (no entity_access backing).
*/
function toPinType(entity: EntityData): string | null {
- switch (entity.type) {
- case 'document':
- return 'document';
- case 'chat':
- return 'chat';
- case 'project':
- return 'project';
- case 'email':
- return 'email_thread';
- case 'call':
- return 'call';
- case 'channel':
- return 'channel';
- // channel_message, automation and foreign entities are not pinnable.
- default:
- return null;
- }
+ return match(entity.type)
+ .with('document', () => 'document' as const)
+ .with('chat', () => 'chat' as const)
+ .with('project', () => 'project' as const)
+ .with('email', () => 'email_thread' as const)
+ .with('call', () => 'call' as const)
+ .with('channel', () => 'channel' as const)
+ .otherwise(() => null);
}As per coding guidelines: "For exhaustive switch statements in TypeScript, use match from ts-pattern instead of native switch" and "Use match from ts-pattern for exhaustive switch/case logic."
🤖 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 `@js/app/packages/app/signal/pins.ts` around lines 31 - 49, Replace the native
switch in toPinType with ts-pattern's match: import { match } from 'ts-pattern'
and use match(entity.type).with('document', () => 'document').with('chat', () =>
'chat').with('project', () => 'project').with('email', () =>
'email_thread').with('call', () => 'call').with('channel', () =>
'channel').otherwise(() => null) so the mapping stays identical but becomes
exhaustive and type-safe; update the toPinType function to use this match
expression and remove the switch.
Source: Coding guidelines
Summary
Implements a client-side pinning system for entities with persistent storage and UI integration. Users can now pin documents, chats, projects, emails, calls, and channels for quick access from the sidebar.
Key Changes
pins.ts): Manages pinned entities with local persistence via localStorage while syncing with the backend. Supports pinning any entity type that has backend pin support.pinned-widget.tsx): Displays pinned entities in the sidebar with two layouts:make-pin-action.ts): Implements toggle pin/unpin functionality for entity selections with toast notificationsImplementation Details
email→email_thread)https://claude.ai/code/session_01SgxdRrh8YABj1ANhdDNJUe