feat: display session ID and custom title in chat UI#129
feat: display session ID and custom title in chat UI#129dbasclpy wants to merge 2 commits intomatt1398:mainfrom
Conversation
Add SessionInfoBar component between SearchBar and ChatHistory showing the session UUID (truncated, full on hover), copy-to-clipboard, and a Resume button that copies `claude --resume <id>`. Parse custom-title JSONL entries written by /rename and surface them in the sidebar, tab label, and session detail. Both light and deep metadata paths extract the title, with the light path using a fast string-match scan to avoid full JSON parsing. Closes matt1398#115
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request enhances the user experience by providing better session management and identification. It introduces the ability to view and copy session IDs directly within the chat UI and allows users to assign custom titles to their chat sessions. These custom titles are then reflected in various parts of the UI, improving navigation and organization of conversations. Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request effectively adds the ability to display session IDs and custom titles in the UI. The changes are well-structured, spanning across the backend for data extraction and the frontend for display. The inclusion of both deep and light metadata extraction paths for custom titles is a thoughtful performance consideration. The new SessionInfoBar component is a nice addition for user convenience. I've added a couple of suggestions to improve maintainability and type safety. Overall, this is a solid feature implementation with good test coverage.
| const rawEntry = entry as unknown as Record<string, unknown>; | ||
| if (rawEntry.type === 'custom-title' && typeof rawEntry.customTitle === 'string') { | ||
| customTitle = rawEntry.customTitle; | ||
| } |
There was a problem hiding this comment.
This type assertion to Record<string, unknown> is necessary because the entry variable was optimistically cast to ChatHistoryEntry when parsed from JSON. However, a custom-title entry is not a valid ChatHistoryEntry, leading to this type-unsound workaround.
A more robust and type-safe approach would be to parse the JSON into a generic type (like unknown or Record<string, unknown>) and then use type guards to safely narrow it down to either a custom-title entry or a ChatHistoryEntry.
While a full fix would involve changing code outside of this diff, I recommend refactoring this in the future to improve type safety and avoid these kinds of workarounds.
There was a problem hiding this comment.
Good catch. Added mtime+size cache matching the existing sessionPreviewCache pattern. Fixed in 481c382.
|
Thanks for the review. Type cast in jsonl.ts: Agreed the double-cast isn't ideal, but Inline styles vs Tailwind arbitrary values: The codebase consistently uses |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughAdds extraction and caching of per-session custom titles from JSONL session files, surfaces customTitle in Session objects and UI, introduces a SessionInfoBar component showing/copying session ID and resume command, and updates UI label logic and tests to prefer/display custom titles. Changes
🚥 Pre-merge checks | ✅ 2✅ Passed checks (2 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: 2
🧹 Nitpick comments (4)
src/renderer/components/sidebar/SessionItem.tsx (1)
181-194: Unify and normalize session label derivation in one place.
handleClickandsessionLabelcurrently duplicate logic. Consider deriving once (withtrim()/fallback handling) and reusing it to avoid drift and blank-label edge cases.♻️ Suggested cleanup
+ const normalizedCustomTitle = session.customTitle?.trim(); + const sessionLabel = + (normalizedCustomTitle && normalizedCustomTitle.length > 0 + ? normalizedCustomTitle + : session.firstMessage?.slice(0, 50)) ?? 'Session'; const handleClick = (event: React.MouseEvent): void => { @@ - label: session.customTitle ?? session.firstMessage?.slice(0, 50) ?? 'Session', + label: sessionLabel, @@ - const sessionLabel = session.customTitle ?? session.firstMessage?.slice(0, 50) ?? 'Session';🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/renderer/components/sidebar/SessionItem.tsx` around lines 181 - 194, The session label logic is duplicated between handleClick and the sessionLabel constant and can produce blank labels; create a single normalized label (e.g., derive in a helper or at top of SessionItem using session.customTitle ?? session.firstMessage, apply trim(), and fallback to 'Session') and replace uses in handleClick and sessionLabel with that single value so both navigation (handleClick) and rendering reuse the same trimmed/fallback label.test/renderer/components/SessionInfoBar.test.ts (2)
1-5: Consider renaming test file to reflect actual test scope.The file tests store state and selector logic rather than the
SessionInfoBarcomponent itself. The nameSessionInfoBar.test.tssuggests component tests, but these are store integration tests that simulate the selector logic manually.This is acceptable for validating the data flow, but consider either:
- Renaming to
sessionInfoBarStore.test.tsfor clarity, or- Adding actual component render tests using a testing library to verify the UI renders correctly with the store data.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/renderer/components/SessionInfoBar.test.ts` around lines 1 - 5, The test file currently named SessionInfoBar.test.ts covers store/selector behavior (store state, selector logic that determines visibility and session data) rather than rendering the SessionInfoBar component; either rename the file to something like sessionInfoBarStore.test.ts to reflect it tests selectors/store logic (update any test imports) or add actual component render tests that mount the SessionInfoBar component and assert the UI shows the session ID and resume command using your testing library (refer to the SessionInfoBar component and the selector(s) used in these tests to wire the store mock/state).
69-76: Test verifies string concatenation rather than component behavior.This test constructs the resume command inline using template literals and asserts it matches itself. The actual
resumeCommandconstruction logic lives inSessionInfoBar.tsx, not in the store.Consider either removing this test or converting it to a component render test that verifies the resume command is correctly passed to the button's
titleattribute or clipboard action.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/renderer/components/SessionInfoBar.test.ts` around lines 69 - 76, The test currently validates a template literal instead of component behavior; update the test in SessionInfoBar.test.ts to render the SessionInfoBar component (with store.setState({ sessionDetail: makeSessionDetail(sessionId) })) and assert the resume command produced by the component is used in the UI (e.g., check the resume button's title/aria-label or the clipboard action) rather than building the string inline; locate references to makeSessionDetail, store.getState().sessionDetail, and the resume button in SessionInfoBar.tsx to implement the render+assert flow, or remove the redundant test if you prefer.src/renderer/components/chat/SessionInfoBar.tsx (1)
1-6: Import order doesn't follow coding guidelines.Per coding guidelines, imports should be organized: external packages first, then path aliases (
@renderer), then relative imports.Suggested import reorder
import React from 'react'; +import { Hash, Terminal } from 'lucide-react'; +import { useShallow } from 'zustand/react/shallow'; import { CopyButton } from '@renderer/components/common/CopyButton'; import { useStore } from '@renderer/store'; -import { Hash, Terminal } from 'lucide-react'; -import { useShallow } from 'zustand/react/shallow';🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/renderer/components/chat/SessionInfoBar.tsx` around lines 1 - 6, The import statements in SessionInfoBar.tsx are out of order; reorder them to follow project guidelines: place external packages first (e.g., 'react', 'lucide-react', 'zustand/react/shallow'), then path-alias imports (e.g., '@renderer/components/common/CopyButton', '@renderer/store' which provide CopyButton and useStore), and finally any relative imports if present; ensure imports for React, Hash, Terminal, and useShallow come before the `@renderer` imports to satisfy the project import ordering rule.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/main/services/discovery/ProjectScanner.ts`:
- Around line 824-825: The light metadata path in ProjectScanner repeatedly
calls extractCustomTitle(filePath, this.fsProvider) causing repeated streaming
of large JSONL; add an LRU cache keyed by filePath plus its mtime and size (use
stats from this.fsProvider.stat or equivalent) and consult it before calling
extractCustomTitle, updating the cache entry when mtime/size change; follow the
same caching pattern used for the preview cache (reuse its key format and
eviction policy) and store the resolved customTitle so subsequent light-listing
builds skip re-parsing unchanged files.
In `@src/renderer/components/chat/SessionInfoBar.tsx`:
- Around line 60-68: The Resume button in SessionInfoBar.tsx uses
navigator.clipboard.writeText(resumeCommand) directly in the onClick handler
which can cause unhandled promise rejections when the Clipboard API is
unavailable; update the onClick to call an async handler (or a promise .catch)
that first checks for navigator.clipboard, then awaits writeText inside a
try/catch and surface errors (e.g., log or show a user notification) so failures
are handled gracefully for the resumeCommand copy action.
---
Nitpick comments:
In `@src/renderer/components/chat/SessionInfoBar.tsx`:
- Around line 1-6: The import statements in SessionInfoBar.tsx are out of order;
reorder them to follow project guidelines: place external packages first (e.g.,
'react', 'lucide-react', 'zustand/react/shallow'), then path-alias imports
(e.g., '@renderer/components/common/CopyButton', '@renderer/store' which provide
CopyButton and useStore), and finally any relative imports if present; ensure
imports for React, Hash, Terminal, and useShallow come before the `@renderer`
imports to satisfy the project import ordering rule.
In `@src/renderer/components/sidebar/SessionItem.tsx`:
- Around line 181-194: The session label logic is duplicated between handleClick
and the sessionLabel constant and can produce blank labels; create a single
normalized label (e.g., derive in a helper or at top of SessionItem using
session.customTitle ?? session.firstMessage, apply trim(), and fallback to
'Session') and replace uses in handleClick and sessionLabel with that single
value so both navigation (handleClick) and rendering reuse the same
trimmed/fallback label.
In `@test/renderer/components/SessionInfoBar.test.ts`:
- Around line 1-5: The test file currently named SessionInfoBar.test.ts covers
store/selector behavior (store state, selector logic that determines visibility
and session data) rather than rendering the SessionInfoBar component; either
rename the file to something like sessionInfoBarStore.test.ts to reflect it
tests selectors/store logic (update any test imports) or add actual component
render tests that mount the SessionInfoBar component and assert the UI shows the
session ID and resume command using your testing library (refer to the
SessionInfoBar component and the selector(s) used in these tests to wire the
store mock/state).
- Around line 69-76: The test currently validates a template literal instead of
component behavior; update the test in SessionInfoBar.test.ts to render the
SessionInfoBar component (with store.setState({ sessionDetail:
makeSessionDetail(sessionId) })) and assert the resume command produced by the
component is used in the UI (e.g., check the resume button's title/aria-label or
the clipboard action) rather than building the string inline; locate references
to makeSessionDetail, store.getState().sessionDetail, and the resume button in
SessionInfoBar.tsx to implement the render+assert flow, or remove the redundant
test if you prefer.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: e693f8b0-e963-41d6-a088-18429f65565e
📒 Files selected for processing (10)
src/main/services/discovery/ProjectScanner.tssrc/main/types/domain.tssrc/main/utils/jsonl.tssrc/main/utils/metadataExtraction.tssrc/renderer/components/chat/SessionInfoBar.tsxsrc/renderer/components/layout/MiddlePanel.tsxsrc/renderer/components/sidebar/SessionItem.tsxsrc/renderer/store/slices/sessionDetailSlice.tstest/main/utils/jsonl.test.tstest/renderer/components/SessionInfoBar.test.ts
Cache extractCustomTitle results by mtime+size in the light metadata path, matching the existing sessionPreviewCache pattern. Avoids re-streaming JSONL files on every sidebar refresh when nothing changed. Add .catch() to the Resume button clipboard call to prevent unhandled promise rejections when the Clipboard API is unavailable.

Summary
Adds session ID visibility and /rename custom title support. Closes #115.
claude --resume <id>buttonChanges
SessionInfoBar.tsx(new): compact info strip with session ID and resume commandmetadataExtraction.ts:extractCustomTitle()fast scanner for light metadata pathjsonl.ts: custom-title detection in deep metadata pathdomain.ts:customTitlefield on Session interfaceProjectScanner.ts: wire custom title through both metadata pathsSessionItem.tsx: prefer custom title over first message in sidebarsessionDetailSlice.ts: prefer custom title in tab label updatesMiddlePanel.tsx: mount SessionInfoBarScreenshot
Validation
pnpm typecheckpassespnpm lintpassespnpm testpasses (663/663, 10 new)pnpm buildpassesSummary by CodeRabbit
New Features
Performance
Tests