Skip to content

Conversation

@NFToby
Copy link
Contributor

@NFToby NFToby commented Jan 13, 2026

What

  • Allow pinning of chats in the chat history sidebar
  • Store pinned chats in localstorage so no backend changes needed

Why

  • Make it easier to organise and access important chat sessions

Screenshots

Screenshot 2026-01-13 at 21 27 22 Screenshot 2026-01-13 at 21 27 29 Screenshot 2026-01-13 at 21 28 51

Summary by CodeRabbit

  • New Features
    • Pin frequently accessed sessions to a dedicated "Pinned" section that stays sorted by recent activity and updates in real time.
    • Pin/unpin controls appear on hover/focus with a rotating icon; pin state persists across browser sessions.
    • Chat history continues grouping unpinned sessions by date (Today, Yesterday, This week, This month, Older) and re-renders immediately when pins change.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 13, 2026

📝 Walkthrough

Walkthrough

Adds localStorage-backed pinned sessions with an event-driven API; session items gain a pin toggle and subscribe to pinned state, and the chat history sidebar separates pinned sessions (sorted by lastActivity) from unpinned sessions (grouped by date), updating reactively on pin changes.

Changes

Cohort / File(s) Summary
Pinned sessions utility
src/containers/LlamaAI/utils/pinnedSessions.ts
New module exporting PINNED_SESSIONS_KEY, isSessionPinned(sessionId), togglePinSession(sessionId), and subscribeToPinnedSessions(callback). Manages pinned IDs in localStorage with SSR-safety, JSON error handling, and dispatches a pinnedSessionsChange event on updates.
Sidebar grouping & reactivity
src/containers/LlamaAI/components/ChatHistorySidebar.tsx
Adds useSyncExternalStore subscription to subscribeToPinnedSessions, introduces _pinnedSessions to trigger re-renders, splits sessions into pinnedSessions (sorted by lastActivity) and grouped unpinned sessions (date groups), and updates memoization deps to [sessions, _pinnedSessions]. Rendering/virtualization logic preserved.
Session item pin UI & logic
src/containers/LlamaAI/components/SessionItem.tsx
Subscribes to pinned session updates, computes isPinned(session.sessionId), adds a pin toggle button (tooltip, hover/focus visibility, rotation when pinned), handles togglePinSession on click (stopPropagation), disables control during updating/deleting/restoring, and adjusts layout/padding to accommodate the control.

Sequence Diagram

sequenceDiagram
    participant User
    participant SessionItem
    participant PinnedSvc as pinnedSessions
    participant Storage as BrowserStorage
    participant Sidebar as ChatHistorySidebar

    User->>SessionItem: Click pin icon
    SessionItem->>PinnedSvc: togglePinSession(sessionId)
    PinnedSvc->>Storage: read current pinned list
    PinnedSvc->>Storage: write updated pinned list
    PinnedSvc->>PinnedSvc: dispatch "pinnedSessionsChange"
    Sidebar->>PinnedSvc: notified via subscription
    Sidebar->>PinnedSvc: read current pinned list
    Sidebar->>Sidebar: recompute groups & ordering
    Sidebar->>User: render updated sidebar
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I nudged a pin with whiskered paw,
The list now stays where I once saw.
Boxes in localStorage, snug and small,
Events hop round to tell them all.
A happy rabbit taps — pinned for all!

🚥 Pre-merge checks | ✅ 2 | ❌ 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 (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat: pin llamaAI chats' accurately and concisely summarizes the main change—adding the ability to pin chats in the LlamaAI chat history sidebar.

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

✨ Finishing touches
  • 📝 Generate docstrings

🧹 Recent nitpick comments
src/containers/LlamaAI/components/SessionItem.tsx (1)

32-44: Consider using the snapshot value directly instead of re-computing.

The current implementation discards the snapshot from useSyncExternalStore and then re-computes the pinned state via isSessionPinned(). While functional, this results in parsing the localStorage JSON twice per render.

You could derive isPinned directly from the snapshot:

♻️ Suggested optimization
-	// Subscribe to pinned sessions changes to trigger re-renders
-	useSyncExternalStore(
+	const pinnedSessionsSnapshot = useSyncExternalStore(
 		subscribeToPinnedSessions,
 		() => localStorage.getItem(PINNED_SESSIONS_KEY) ?? '[]',
 		() => '[]'
 	)

-	const isPinned = isSessionPinned(session.sessionId)
+	const isPinned = JSON.parse(pinnedSessionsSnapshot).includes(session.sessionId)

Alternatively, keep the current approach if you prefer the separation of concerns with isSessionPinned().

📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 717bc57 and 7e1eaf8.

📒 Files selected for processing (2)
  • src/containers/LlamaAI/components/ChatHistorySidebar.tsx
  • src/containers/LlamaAI/components/SessionItem.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/containers/LlamaAI/components/ChatHistorySidebar.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
src/containers/LlamaAI/components/SessionItem.tsx (3)
src/containers/LlamaAI/utils/pinnedSessions.ts (4)
  • subscribeToPinnedSessions (34-43)
  • PINNED_SESSIONS_KEY (1-1)
  • isSessionPinned (15-18)
  • togglePinSession (20-32)
src/components/Tooltip.tsx (1)
  • Tooltip (30-64)
src/components/Icon.tsx (1)
  • Icon (108-114)
🔇 Additional comments (4)
src/containers/LlamaAI/components/SessionItem.tsx (4)

1-17: LGTM!

The imports are well-organized. Using useSyncExternalStore from React 19 is the correct approach for subscribing to external stores like localStorage.


41-44: LGTM!

The handler correctly stops propagation to prevent the parent click handler from firing and delegates to the utility function.


173-173: LGTM!

The dynamic padding (pr-0 default, pr-20 on hover) properly accommodates the action buttons that appear on hover, including the new pin button.


177-192: Verify disabled state consistency.

The pin button's disabled conditions don't include isTogglingVisibility, unlike the other menu items (line 261). Since pinning is localStorage-only and doesn't interact with the backend, this might be intentional. If so, the current implementation is fine.

Otherwise, for UI consistency, consider:

-						<button onClick={handleTogglePin} disabled={isUpdatingTitle || isDeletingSession || isRestoringSession} />
+						<button onClick={handleTogglePin} disabled={isUpdatingTitle || isDeletingSession || isRestoringSession || isTogglingVisibility} />

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

Copy link

@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

🤖 Fix all issues with AI agents
In @src/containers/LlamaAI/utils/pinnedSessions.ts:
- Around line 34-38: subscribeToPinnedSessions currently accesses window
directly which breaks during SSR; guard the call by checking typeof window !==
'undefined' (or globalThis) and if absent return a no-op subscribe/unsubscribe
pair. Specifically, in subscribeToPinnedSessions add an early branch that
returns a function that does nothing when window is undefined so
useSyncExternalStore can safely call it during server rendering, otherwise
attach window.addEventListener('pinnedSessionsChange', callback) and return the
real remover window.removeEventListener('pinnedSessionsChange', callback).
- Around line 20-32: togglePinSession directly uses window.localStorage and
window.dispatchEvent which will crash in SSR; wrap the side-effect portion in a
guard like typeof window !== 'undefined' (or return early when window is
undefined) so that reading via getStoredPinnedSessions still works server-side
but storage/dispatch only run in the browser; update togglePinSession to compute
nextPinned as before but only call
window.localStorage.setItem(PINNED_SESSIONS_KEY, ...) and
window.dispatchEvent(...) inside that guard and keep the same boolean return
behavior.
🧹 Nitpick comments (4)
src/containers/LlamaAI/components/ChatHistorySidebar.tsx (2)

1-2: Consolidate React imports.

useSyncExternalStore can be imported alongside the other React hooks on line 1 instead of a separate import.

Proposed fix
-import { useEffect, useMemo, useRef } from 'react'
-import { useSyncExternalStore } from 'react'
+import { useEffect, useMemo, useRef, useSyncExternalStore } from 'react'

48-53: Use the exported constant instead of hardcoded string.

The localStorage key 'llamaai-pinned-sessions' is hardcoded here, but PINNED_SESSIONS_KEY is already exported from the utils module. Using the constant ensures consistency if the key ever changes.

Proposed fix
 import { isSessionPinned, subscribeToPinnedSessions } from '../utils/pinnedSessions'
+import { PINNED_SESSIONS_KEY } from '../utils/pinnedSessions'

Then update the getSnapshot:

 	const _pinnedSessions = useSyncExternalStore(
 		subscribeToPinnedSessions,
-		() => localStorage.getItem('llamaai-pinned-sessions') ?? '[]',
+		() => localStorage.getItem(PINNED_SESSIONS_KEY) ?? '[]',
 		() => '[]'
 	)
src/containers/LlamaAI/components/SessionItem.tsx (2)

1-5: Consolidate React imports.

Same as in ChatHistorySidebar - useSyncExternalStore can be combined with the other React imports on line 1.

Proposed fix
-import React, { useEffect, useRef, useState } from 'react'
+import React, { useEffect, useRef, useState, useSyncExternalStore } from 'react'
 import { useRouter } from 'next/router'
 import * as Ariakit from '@ariakit/react'
 import { useMutation, useQueryClient } from '@tanstack/react-query'
-import { useSyncExternalStore } from 'react'

28-33: Use the exported constant for the localStorage key.

The hardcoded key 'llamaai-pinned-sessions' should be replaced with PINNED_SESSIONS_KEY for consistency with the utils module.

Proposed fix
-import { isSessionPinned, togglePinSession, subscribeToPinnedSessions } from '../utils/pinnedSessions'
+import { isSessionPinned, togglePinSession, subscribeToPinnedSessions, PINNED_SESSIONS_KEY } from '../utils/pinnedSessions'

Then update the getSnapshot:

 	useSyncExternalStore(
 		subscribeToPinnedSessions,
-		() => localStorage.getItem('llamaai-pinned-sessions') ?? '[]',
+		() => localStorage.getItem(PINNED_SESSIONS_KEY) ?? '[]',
 		() => '[]'
 	)
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1652c5c and 88a9758.

📒 Files selected for processing (3)
  • src/containers/LlamaAI/components/ChatHistorySidebar.tsx
  • src/containers/LlamaAI/components/SessionItem.tsx
  • src/containers/LlamaAI/utils/pinnedSessions.ts
🧰 Additional context used
🧬 Code graph analysis (2)
src/containers/LlamaAI/components/SessionItem.tsx (3)
src/containers/LlamaAI/utils/pinnedSessions.ts (3)
  • subscribeToPinnedSessions (34-39)
  • isSessionPinned (15-18)
  • togglePinSession (20-32)
src/components/Tooltip.tsx (1)
  • Tooltip (30-64)
src/components/Icon.tsx (1)
  • Icon (108-114)
src/containers/LlamaAI/components/ChatHistorySidebar.tsx (1)
src/containers/LlamaAI/utils/pinnedSessions.ts (2)
  • subscribeToPinnedSessions (34-39)
  • isSessionPinned (15-18)
🔇 Additional comments (5)
src/containers/LlamaAI/utils/pinnedSessions.ts (1)

3-13: Well-designed localStorage accessor with proper error handling.

Good defensive coding with SSR check, try/catch for JSON parse errors, and array validation.

src/containers/LlamaAI/components/ChatHistorySidebar.tsx (1)

55-92: Grouping logic is well-structured.

The separation of pinned/unpinned sessions with proper sorting and the reactive dependency on _pinnedSessions is correctly implemented.

src/containers/LlamaAI/components/SessionItem.tsx (3)

37-40: Pin toggle handler is correctly implemented.

Properly stops event propagation to prevent triggering the parent session click, and delegates to the utility function.


173-191: Pin button UI implementation looks good.

The tooltip appropriately changes based on pin state, disabled states cover all pending operations, and the visual feedback (rotation + fill) provides clear indication of the pinned state.


184-190: The --icon-fill CSS variable is properly supported and working correctly.

The pin icon in the SVG sprite at public/icons/v34.svg explicitly references this variable with fill="var(--icon-fill)". The Icon component passes all style props directly to the SVG element, which cascades through the <use> element to the symbol. The code in SessionItem.tsx will work as expected.

Copy link

@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

🤖 Fix all issues with AI agents
In @src/containers/LlamaAI/components/SessionItem.tsx:
- Around line 27-32: Replace the hardcoded localStorage key in the
useSyncExternalStore selector with the exported constant PINNED_SESSIONS_KEY
from pinnedSessions.ts: import PINNED_SESSIONS_KEY at the top of
SessionItem.tsx, then change the selector function passed to
useSyncExternalStore (the one calling
localStorage.getItem('llamaai-pinned-sessions') ?? '[]') to use
localStorage.getItem(PINNED_SESSIONS_KEY) ?? '[]' so the component reads the key
from the single source of truth.
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 88a9758 and 717bc57.

📒 Files selected for processing (3)
  • src/containers/LlamaAI/components/ChatHistorySidebar.tsx
  • src/containers/LlamaAI/components/SessionItem.tsx
  • src/containers/LlamaAI/utils/pinnedSessions.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/containers/LlamaAI/components/ChatHistorySidebar.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
src/containers/LlamaAI/components/SessionItem.tsx (3)
src/containers/LlamaAI/utils/pinnedSessions.ts (3)
  • subscribeToPinnedSessions (34-43)
  • isSessionPinned (15-18)
  • togglePinSession (20-32)
src/components/Tooltip.tsx (1)
  • Tooltip (30-64)
src/components/Icon.tsx (1)
  • Icon (108-114)
🔇 Additional comments (10)
src/containers/LlamaAI/utils/pinnedSessions.ts (5)

1-1: LGTM!

Clear and descriptive constant name for the localStorage key.


3-13: LGTM!

The getStoredPinnedSessions helper is well-implemented with proper SSR safety (typeof window === 'undefined' check), defensive JSON parsing wrapped in try-catch, and array type validation to handle corrupted localStorage data.


15-18: LGTM!

Simple and correct implementation. The includes() check is appropriate for a reasonably-sized list of pinned sessions.


20-32: LGTM!

The toggle logic correctly handles add/remove, persists to localStorage, dispatches a custom event for subscribers, and returns the new pinned state. SSR safety is properly handled.


34-43: LGTM!

Clean subscription pattern that returns an unsubscribe function. The SSR safety check correctly returns a no-op cleanup function when window is undefined.

src/containers/LlamaAI/components/SessionItem.tsx (5)

1-1: LGTM!

Appropriate import of useSyncExternalStore for subscribing to external state.


12-12: LGTM!

Correct imports from the new pinned sessions utility module.


34-39: LGTM!

The isPinned derivation and handleTogglePin handler are correctly implemented. Using e.stopPropagation() prevents the click from bubbling to the parent session click handler.


168-168: LGTM!

The pr-20 padding on hover accommodates the new pin button alongside existing action buttons.


172-187: LGTM!

Well-implemented pin button UI:

  • Consistent styling with existing action buttons (edit, menu)
  • Proper disabled states during other operations
  • Good accessibility with tooltip indicating action
  • Visual feedback via rotation and fill for pinned state

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