Conversation
WalkthroughThis update refactors and streamlines the bookmark and chat features across both the extension and web app. It removes redundant state management, simplifies navigation and UI flows, introduces toast notifications, and enhances chat and bookmark visualizations with improved control and flexibility. Several utility functions and components are added, while unused models, mutations, and constants are removed. Changes
Sequence Diagram(s)Chat Session Creation and Messaging (New Flow)sequenceDiagram
participant User
participant ChatBot (UI)
participant Chat (UI)
participant API /chat/route.ts
participant Downstream /chat/stream
User->>ChatBot: Click "New Chat"
ChatBot->>Chat: Render with isNewChat=true
User->>Chat: Submit question (no sessionId)
Chat->>API /chat/route.ts: POST { userId, message }
API /chat/route.ts->>Downstream /chat/stream: POST { userId, message }
Downstream /chat/stream-->>API /chat/route.ts: Streamed response (with session_id)
API /chat/route.ts-->>Chat: Streamed response (with session_id)
Chat->>ChatBot: onSessionCreated(session_id)
ChatBot->>Chat: Set sessionId, isNewChat=false
User->>Chat: Continue chatting (sessionId present)
Bookmark Visualization: Theme Selection and DeletionsequenceDiagram
participant User
participant BookmarksPage
participant Sidebar
participant Graph/Tree/Planet
participant GraphDetail
User->>Sidebar: Select Bookmark View Theme
Sidebar->>BookmarksPage: Pass selected theme
BookmarksPage->>Graph/Tree/Planet: Render with filtered data
User->>Graph/Tree/Planet: Click node
Graph/Tree/Planet->>BookmarksPage: Open detail modal
BookmarksPage->>GraphDetail: Show details
User->>GraphDetail: Click delete
GraphDetail->>BookmarksPage: onDelete(id)
BookmarksPage->>Graph/Tree/Planet: Remove node, update view
Poem
✨ Finishing Touches
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 7
🔭 Outside diff range comments (2)
apps/www/src/components/bookmarks/graph/index.tsx (1)
90-97: Fix event listener cleanup to prevent memory leak.The removeEventListener is using a different function reference, so the original handler won't be removed.
+const handleResize = () => { + graph.width(graphRef.current?.clientWidth || 0); + graph.height(graphRef.current?.clientHeight || 0); +}; -window.addEventListener("resize", function handleResize() { - graph.width(graphRef.current?.clientWidth || 0); - graph.height(graphRef.current?.clientHeight || 0); -}); +window.addEventListener("resize", handleResize); return () => { - window.removeEventListener("resize", function handleRemoveResize() {}); + window.removeEventListener("resize", handleResize); };apps/www/src/components/bookmarks/graph/graph-detail.tsx (1)
300-312: Node sizing is fixed at a constant value and ignores computed views
Graph2D uses a custom nodeCanvasObject with a hard-coded radius (nodeSize = 5), and you’re also mapping every node’svalto 1. As a result, even if you pass a view-based value, it won’t affect node size.• File: apps/www/src/components/bookmarks/graph/graph-detail.tsx
Lines 300–312 — change<Graph2D graphData={{ nodes: graphData.nodes.map((star) => ({ - id: star.id, - name: star.name, - val: 1, + id: star.id, + name: star.name, + val: star.viewCount, // preserve the computed view-based value })), links: graphData.links.map((link) => ({ source: link.source, target: link.target, })), }} />• File: packages/ui/src/graph-2d/index.tsx — inside
nodeCanvasObject, replace the fixed size:// before const nodeSize = 5; // after const nodeSize = (node.val ?? 1) * 2; // scale factor as neededThis ensures your computed
valflows through into the canvas drawing and node sizes reflect view counts.
🧹 Nitpick comments (6)
apps/www/src/components/ui/sonner.tsx (1)
7-24: LGTM! Well-implemented theme-aware toast wrapper with minor type safety consideration.The component correctly integrates with next-themes and forwards props appropriately. The inline CSS custom properties provide good theme integration.
Consider adding validation for the theme casting to ensure type safety:
const Toaster = ({ ...props }: ToasterProps) => { const { theme = "system" } = useTheme(); + + // Validate theme value before casting + const validTheme = ["light", "dark", "system"].includes(theme) ? theme : "system"; return ( <Sonner - theme={theme as ToasterProps["theme"]} + theme={validTheme as ToasterProps["theme"]} className="toaster group"apps/www/src/components/common/nav-dropdown.tsx (1)
76-95: Consider showing bookmark themes only on bookmark pages.The "Bookmark View" dropdown appears on all pages when the nav is open. Consider showing it only when on bookmark-related routes for better UX.
)} +{pathname.includes('/bookmarks') && ( <div className="relative"> <button className="flex items-center gap-2" onClick={() => setIsBookmarkThemeOpen(!isBookmarkThemeOpen)} > <p>Bookmark View</p> <div className={cn("transition-transform", isBookmarkThemeOpen && "rotate-180")}> <Icon.arrowDown fill="#fff" size={16} /> </div> </button> {isBookmarkThemeOpen && ( <div className="absolute left-1/2 top-full mt-2 flex -translate-x-1/2 flex-col gap-2"> {BOOKMARK_THEMES.map((theme) => ( <button key={theme.label} onClick={() => onClickNav(theme.href)}> {theme.label} </button> ))} </div> )} </div> +)}apps/www/src/components/bookmarks/graph/graph-detail.tsx (3)
335-372: Good defensive programming for UI fields.The fallback values prevent undefined errors when star data fields are missing. Consider extracting common patterns for cleaner code.
You could simplify the repeated patterns by extracting a helper function:
const getFieldValue = (fieldName: keyof typeof starData.result) => { return edit.activated ? edit[fieldName] || "" : starData?.result?.[fieldName] || ""; };Then use it as:
-value={edit.activated ? edit.summaryAI : starData?.result?.summaryAI || ""} +value={getFieldValue("summaryAI")}
58-80: Consider consistent node value calculation for visual uniformity.The graph node value calculation is inconsistent:
- Related nodes use
Math.min(star.views, 10)(line 67)- Standalone current star gets hardcoded
val: 10(line 75)This could cause the current bookmark to appear larger than it should when it has fewer views.
Apply consistent value calculation:
: currentStar ? [ { id: currentStar.starId, name: currentStar.title, - val: 10, + val: Math.min(currentStar.views || 0, 10), url: currentStar.siteUrl, }, ] : [];
219-227: Optimize event listener management with proper dependencies.The useEffect hook managing mouse event listeners lacks a dependency array, causing listeners to be added/removed on every render.
Add proper dependencies to the useEffect:
useEffect(() => { + if (holdX < 0) return; + window.addEventListener("mousemove", onMouseMove); window.addEventListener("mouseup", onMouseUp); return () => { window.removeEventListener("mousemove", onMouseMove); window.removeEventListener("mouseup", onMouseUp); }; - }); + }, [holdX, saveWidth]);apps/www/src/components/bookmarks/graph/chat-bot/index.tsx (1)
103-129: Proper event listener management with cleanup.The implementation correctly manages event listeners and cleans them up. Consider splitting into separate effects for better readability.
Consider splitting into three separate
useEffecthooks for clearer separation of concerns:useEffect(() => { if (!isDragging) return; // isDragging logic }, [isDragging, onMouseMove, onMouseUp]); useEffect(() => { if (!isResizingWidth) return; // isResizingWidth logic }, [isResizingWidth, onResizeWidthMouseMove, onResizeWidthMouseUp]); useEffect(() => { if (!isResizingHeight) return; // isResizingHeight logic }, [isResizingHeight, onResizeHeightMouseMove, onResizeHeightMouseUp]);
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (3)
apps/extension/public/icon128.pngis excluded by!**/*.pngapps/extension/src/assets/menu.svgis excluded by!**/*.svgpnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (38)
apps/extension/src/components/loading/index.tsx(1 hunks)apps/extension/src/hooks/use-detect-path.ts(5 hunks)apps/extension/src/index.css(1 hunks)apps/extension/src/pages/bookmark.tsx(3 hunks)apps/extension/src/pages/create-bookmark.tsx(8 hunks)apps/extension/src/state/mutation/star.ts(3 hunks)apps/www/package.json(1 hunks)apps/www/src/app/api/chat/route.ts(1 hunks)apps/www/src/app/bookmarks/page.tsx(1 hunks)apps/www/src/app/page/bookmarks/index.tsx(1 hunks)apps/www/src/components/bookmarks/graph/chat-bot/chat.tsx(8 hunks)apps/www/src/components/bookmarks/graph/chat-bot/index.tsx(1 hunks)apps/www/src/components/bookmarks/graph/graph-detail.tsx(6 hunks)apps/www/src/components/bookmarks/graph/index.tsx(3 hunks)apps/www/src/components/bookmarks/graph/planet.tsx(4 hunks)apps/www/src/components/bookmarks/sidebar/dropdown.tsx(1 hunks)apps/www/src/components/bookmarks/sidebar/index.tsx(2 hunks)apps/www/src/components/bookmarks/tree/index.tsx(0 hunks)apps/www/src/components/common/icon.tsx(1 hunks)apps/www/src/components/common/nav-dropdown.tsx(3 hunks)apps/www/src/components/layout/root-provider.tsx(2 hunks)apps/www/src/components/ui/sonner.tsx(1 hunks)apps/www/src/constants/bookmark.ts(0 hunks)apps/www/src/lib/tanstack/mutation/chat.ts(0 hunks)apps/www/src/lib/tanstack/mutation/star.ts(2 hunks)apps/www/src/lib/tanstack/query/chat.ts(1 hunks)apps/www/src/lib/zustand/bookmark.ts(0 hunks)apps/www/src/models/chat.ts(0 hunks)apps/www/src/service/chat.ts(1 hunks)apps/www/src/utils/params.ts(1 hunks)apps/www/src/utils/toast.ts(1 hunks)packages/ui/src/graph-2d/index.tsx(2 hunks)packages/ui/src/index.ts(1 hunks)packages/ui/src/keyword/index.tsx(3 hunks)packages/ui/src/modal/index.tsx(3 hunks)packages/ui/src/spinner/index.tsx(2 hunks)packages/ui/src/textarea/index.tsx(1 hunks)packages/ui/utils/search.ts(1 hunks)
💤 Files with no reviewable changes (5)
- apps/www/src/components/bookmarks/tree/index.tsx
- apps/www/src/models/chat.ts
- apps/www/src/lib/tanstack/mutation/chat.ts
- apps/www/src/constants/bookmark.ts
- apps/www/src/lib/zustand/bookmark.ts
🧰 Additional context used
🧬 Code Graph Analysis (18)
apps/www/src/lib/tanstack/mutation/star.ts (1)
apps/www/src/utils/toast.ts (1)
successToast(3-5)
apps/www/src/lib/tanstack/query/chat.ts (1)
apps/www/src/service/chat.ts (1)
getChatMessages(7-11)
packages/ui/src/spinner/index.tsx (1)
packages/ui/utils/cn.ts (1)
cn(4-6)
packages/ui/utils/search.ts (1)
packages/ui/src/index.ts (1)
searchFilter(11-11)
apps/www/src/components/layout/root-provider.tsx (2)
apps/www/src/lib/tanstack/index.ts (1)
queryClient(3-10)apps/www/src/components/ui/sonner.tsx (1)
Toaster(26-26)
apps/extension/src/state/mutation/star.ts (3)
apps/extension/src/state/zustand/loading.ts (1)
useLoadingStore(8-11)apps/extension/src/services/star.ts (1)
completeCreateStar(17-21)apps/extension/src/components/layout/index.tsx (1)
queryClient(8-8)
apps/extension/src/hooks/use-detect-path.ts (1)
apps/extension/src/state/zustand/user.ts (1)
useUserStore(8-11)
apps/extension/src/components/loading/index.tsx (1)
apps/extension/src/state/zustand/loading.ts (1)
useLoadingStore(8-11)
packages/ui/src/textarea/index.tsx (1)
packages/ui/utils/cn.ts (1)
cn(4-6)
apps/extension/src/pages/bookmark.tsx (3)
apps/extension/src/state/zustand/tab.ts (1)
useTabStore(13-21)packages/ui/src/index.ts (1)
useOutsideClick(13-13)packages/ui/hooks/use-outside-click.ts (1)
useOutsideClick(3-26)
apps/www/src/components/bookmarks/graph/index.tsx (1)
apps/www/src/types/graph.ts (1)
NodeObject(17-31)
packages/ui/src/keyword/index.tsx (1)
packages/ui/utils/search.ts (1)
searchFilter(1-7)
apps/www/src/components/bookmarks/sidebar/index.tsx (1)
apps/www/src/utils/toast.ts (1)
successToast(3-5)
apps/www/src/app/api/chat/route.ts (1)
apps/extension/service-worker.js (1)
baseUrl(2-2)
apps/www/src/service/chat.ts (2)
apps/www/src/models/chat.ts (2)
ChatMessageDTO(10-23)ChatSessionListDTO(1-8)apps/www/src/utils/params.ts (1)
getParams(1-18)
apps/www/src/components/bookmarks/sidebar/dropdown.tsx (4)
apps/www/src/types/category.ts (1)
CategoryProps(1-5)apps/www/src/lib/tanstack/mutation/category.ts (1)
useCreateCategory(7-17)apps/www/src/utils/toast.ts (1)
infoToast(7-9)packages/ui/src/index.ts (1)
cn(10-10)
apps/www/src/components/bookmarks/graph/graph-detail.tsx (2)
packages/types/src/star.ts (1)
AllStarDTO(3-9)apps/www/src/utils/toast.ts (1)
infoToast(7-9)
apps/www/src/components/bookmarks/graph/chat-bot/chat.tsx (2)
apps/www/src/hooks/use-user-info.ts (1)
useUserInfo(6-20)apps/www/src/lib/tanstack/query/chat.ts (1)
useGetChatMessages(5-17)
🔇 Additional comments (81)
apps/extension/src/index.css (1)
56-62: LGTM! Good UX improvement for button interactions.The button cursor styles enhance user experience by providing clear visual feedback for clickable and disabled states. This follows standard UX patterns and improves accessibility.
apps/www/package.json (1)
23-23: sonner dependency is current and secure
- File: apps/www/package.json (line 23)
- Specified version
^2.0.6is the latest release (published 2 days ago)- No known security vulnerabilities reported
No changes required.
packages/ui/src/index.ts (1)
11-11: searchFilter export implementation verifiedThe
searchFilterfunction is defined and exported correctly:
- packages/ui/utils/search.ts:
export const searchFilter = (search: string, target: string) => { … }- packages/ui/src/index.ts:
export { searchFilter } from "../utils/search";- packages/ui/src/keyword/index.tsx imports and uses it without errors.
No further changes needed.
apps/www/src/lib/tanstack/mutation/star.ts (2)
4-4: LGTM! Good migration to centralized toast utilities.The import of
successToastfrom the toast utility module supports the consistent toast notification pattern across the application.
27-27: Excellent UX improvement by replacing native alert with toast.The migration from
alert()tosuccessToast()provides a much better user experience with less intrusive notifications. The Korean message is appropriate for the application's localization.packages/ui/src/graph-2d/index.tsx (2)
29-29: LGTM! Consistent height reduction for more compact UI.The default height reduction from 300 to 150 pixels creates a more compact component. Ensure this change doesn't negatively impact graph readability or existing layouts that depend on the previous height.
46-46: Consistent CSS class update to match the new height.The CSS class change from
h-[300px]toh-[9.375rem](150px) properly aligns with the updated default height prop.apps/www/src/components/common/icon.tsx (1)
132-145: LGTM! Clean addition of plus icon.The plus icon implementation follows the established pattern and is consistent with other icons in the object. The SVG path correctly renders a plus sign, and the component properly handles the standard IconProps.
apps/www/src/lib/tanstack/query/chat.ts (2)
5-11: Good refactoring to support new chat session logic.The updated signature clearly separates the sessionId from the isNewChat flag, making the hook's purpose more explicit and improving code readability.
15-15: Excellent optimization with the enabled condition.The
enabled: !!sessionId && !isNewChatcondition prevents unnecessary API calls for new chat sessions, which is both performant and logical. This aligns well with the broader chat session management refactor.packages/ui/src/modal/index.tsx (2)
1-1: Good addition of createPortal import.This import is necessary for the portal implementation and follows React best practices.
15-29: Excellent use of createPortal for modal rendering.Rendering the modal into
document.bodyvia a portal is a best practice that prevents z-index conflicts and ensures consistent overlay behavior. The implementation is clean and maintains all existing modal functionality.apps/www/src/components/layout/root-provider.tsx (2)
3-3: Good addition of Toaster import.The Toaster component import enables global toast notifications throughout the application.
12-24: Excellent integration of global toast notifications.The Toaster configuration is well-thought-out with appropriate settings:
- Top-center position for good visibility
- 3000ms duration for optimal user experience
- Close button for user control
- High z-index (99999) to ensure toast visibility above other UI elements
The integration at the root level ensures consistent toast notifications across the entire application.
apps/www/src/app/api/chat/route.ts (2)
10-11: Excellent conditional request body construction.This clean approach prevents sending undefined
session_idvalues in the JSON payload, which is more robust and follows good API practices. The conditional logic is clear and handles both new and existing chat sessions appropriately.
17-17: Good use of the conditional requestBody.Using the conditionally constructed
requestBodyensures the API receives only the necessary fields, preventing potential issues with undefined values in the JSON payload.packages/ui/src/keyword/index.tsx (3)
1-4: LGTM: Clean import organizationThe imports are well-organized with the new
searchFilterutility properly imported.
25-28: Excellent use of useMemo for performance optimizationThe filtered keyword list is properly memoized with appropriate dependencies (
keywordListandrestProps.value). This prevents unnecessary re-computations when other props change.
58-60: Consistent usage of filtered list in renderingThe UI correctly uses
filteredKeywordListfor both the visibility condition and the actual rendering, ensuring consistent behavior.packages/ui/utils/search.ts (1)
1-7: Well-implemented search utility with proper edge case handlingThe function correctly handles:
- Empty/falsy search strings (returns true)
- Case-insensitive matching
- Multi-word search with whitespace splitting
- Efficient word matching using
every()The logic is sound and performance is appropriate for typical keyword filtering use cases.
packages/ui/src/spinner/index.tsx (2)
5-8: Good API design with optional sizing propThe
smallprop is well-designed with a sensible default value (false), maintaining backward compatibility while enabling size flexibility.
22-28: Clean conditional sizing implementationThe conditional sizing logic is clear and maintainable, using the
cnutility appropriately for class name merging. The size values (28/24 for small, 56/52 for default) maintain good proportions.apps/www/src/utils/toast.ts (1)
1-13: Excellent abstraction for toast notificationsThe utility functions provide a clean, consistent API for toast notifications:
- Clear naming convention (
successToast,infoToast,errorToast)- Simple wrapper pattern enables easy future enhancements
- Centralizes toast functionality for maintainability
This abstraction will make it easy to modify toast behavior globally if needed.
apps/extension/src/state/mutation/star.ts (4)
12-12: Improved Zustand selector usageUsing the selector function
(state) => state.setIsLoadingis more efficient than destructuring, as it only re-renders when the specific property changes.
33-33: Consistent selector patternGood consistency in applying the selector pattern across all mutation hooks.
40-42: Proper cache invalidation and navigationThe implementation correctly:
- Invalidates the query cache for all stars to ensure fresh data
- Navigates with the star ID as a query parameter for proper state management
This follows good practices for cache management and URL structure.
54-54: Consistent selector pattern maintainedThe selector function usage is consistent across all mutation hooks, improving performance and maintainability.
packages/ui/src/textarea/index.tsx (3)
5-5: Good addition of optional rightElement prop.The interface extension is clean and maintains backward compatibility.
8-14: Well-implemented flexible layout for label and right element.The flex container provides clean alignment and spacing between the label and optional right element. The conditional rendering handles the optional prop correctly.
18-18: Good removal of resize-none class.Removing the
resize-noneclass allows users to resize the textarea, improving usability. This change aligns with the enhanced flexibility provided by therightElementprop.apps/extension/src/components/loading/index.tsx (2)
6-6: Good addition of optional description prop.The interface extension maintains backward compatibility while adding useful functionality.
10-18: Well-implemented description rendering with proper styling.The component correctly handles the optional description prop and provides appropriate text styling. The centered text container maintains visual consistency.
apps/extension/src/hooks/use-detect-path.ts (4)
28-28: Good integration of authentication state.The import and usage of
isLoggedInfrom the user store is appropriate for controlling navigation flow.
57-59: Proper authentication guard implementation.The early return for non-logged-in users prevents unnecessary navigation logic execution and ensures proper access control.
69-69: Correct dependency array update.Including
isLoggedInin the dependency array ensures the effect runs when authentication state changes.
41-44: Verify tab status change in use-detect-path.tsNo other occurrences of
changeInfo.statuschecks were found in the repository—this hook is the sole place where we switch from"complete"to"loading". Because"loading"fires earlier in the tab lifecycle, please confirm:
- File: apps/extension/src/hooks/use-detect-path.ts
Lines: 41–44
- That
setIsFindingExistPath(true)andupdateCurrentTabstill run with a valid URL (i.e. the path you’re detecting is available at"loading").- There are no race conditions if the final URL or other tab properties settle later.
- Consider whether you need a fallback on
"complete"to catch any edge cases where"loading"may fire without a final URL set.apps/www/src/service/chat.ts (3)
3-3: Good adoption of getParams utility.The import of the
getParamsutility promotes code reuse and consistency across the application.
13-19: Clean refactoring with improved parameter handling.The function signature change from
paramstopropsprovides better naming consistency. The use ofgetParamsutility simplifies query string construction and promotes maintainability.
2-2: Import cleanup: ChatSessionDTO safely removedNo occurrences of
ChatSessionDTOwere found in the codebase, so removing it from the import is safe.apps/extension/src/pages/bookmark.tsx (6)
1-1: LGTM: Clean import additions for new functionalityThe imports are well-organized and include the necessary dependencies for the new menu dropdown and modal functionality. The SVG imports using the
?reactsyntax are appropriate for the build system.Also applies to: 3-4, 9-9
17-20: LGTM: Appropriate state management for UI controlsThe state management is well-structured with clear naming conventions. The separation of menu and modal state allows for independent control of these UI elements.
54-63: LGTM: Proper event handling with good UX practicesThe event handlers are well-implemented with:
stopPropagation()preventing unintended event bubbling- Proper state management for UX flow (close menu when opening modal)
- Clear function naming and single responsibility
66-84: LGTM: Well-structured header with accessibility considerationsThe header implementation is clean with:
- Good semantic HTML structure
- Proper button elements for interactive controls
- Conditional rendering of the dropdown menu
- Correct use of the
useOutsideClickhook with proper ref assignment
96-98: LGTM: Simplified button logic with clear user feedbackThe button implementation properly handles the disabled state with clear messaging to users about when bookmarks cannot be added.
100-113: LGTM: Well-implemented modal with proper UX flowThe modal implementation follows good practices:
- Conditional rendering based on state
- Proper modal structure with title and subtitle
- Action buttons with clear labels and appropriate styling
- Callback handling for modal dismissal
apps/www/src/components/bookmarks/sidebar/index.tsx (3)
14-14: LGTM: Good import addition for improved user feedbackThe import of
successToastaligns with the toast notification system being implemented across the application for better user experience.
74-74: LGTM: Improved user feedback with toast notificationsReplacing alert-based notifications with toast notifications provides a better user experience with non-blocking feedback. The message is clear and appropriate for the logout action.
80-136: LGTM: Clean and well-structured sidebar implementationThe sidebar implementation is well-organized with:
- Proper state management for various dropdown states
- Good responsive design with dynamic width
- Clear semantic structure with appropriate accessibility considerations
- Proper event handling for user interactions
apps/extension/src/pages/create-bookmark.tsx (8)
7-7: LGTM: Good navigation hook adoptionThe replacement of previous navigation logic with
useReplaceNavigatehook suggests a move toward more consistent navigation patterns across the application.Also applies to: 56-56
45-45: LGTM: Proper state management for AI summary featureThe addition of
isAISummaryPendingstate prepares the component for future AI summary functionality with appropriate loading state management.
83-104: LGTM: Improved graph data computation with better fallback handlingThe updated logic properly handles cases where no related nodes exist by including the current bookmark node. This prevents empty graph scenarios and provides a better user experience.
172-174: LGTM: Simplified mutation callsThe removal of success callbacks from mutation calls suggests a move toward cleaner separation of concerns and potentially centralized state management.
178-180: LGTM: Simple and clear cancel handlerThe cancel button implementation is straightforward and provides expected navigation behavior.
194-196: LGTM: Simplified header implementationThe header is clean and focused, removing potentially unnecessary elements while maintaining clear branding.
220-230: LGTM: Well-implemented AI summary button with good UXThe right element button implementation includes:
- Proper loading state with pulse animation
- Dynamic label based on pending state
- Clean styling with conditional classes
- Appropriate event handling
250-254: LGTM: Proper button layout with cancel optionThe button section provides clear actions with appropriate styling variations (outline for cancel, solid for primary action).
apps/www/src/utils/params.ts (2)
1-18: LGTM: Well-implemented URL parameter utilityThe
getParamsfunction is well-structured with:
- Proper use of
URLSearchParamsfor URL-safe encoding- Truthy value checking to avoid empty parameters
- Clear separation between conditional and default parameters
- Appropriate type annotations
20-31: LGTM: Correct pagination logic implementationThe
calculateNextPageParamfunction properly handles:
- Edge case when
totalPagesis 0- Zero-based page indexing (comparing
page === totalPages - 1)- Returning
undefinedwhen no next page exists (standard pattern for pagination libraries)- Clear type annotations and parameter destructuring
apps/www/src/components/bookmarks/graph/planet.tsx (5)
5-5: LGTM: Simplified imports align with refactoringThe import changes reflect the removal of complex state management dependencies, focusing on the core data types needed for the component.
83-94: LGTM: Simplified and more maintainable grouping logicThe new grouping approach using
categoryNameis:
- More straightforward and easier to understand
- Provides a clear fallback ("Uncategorized") for missing categories
- Eliminates complex union-find logic that may have been over-engineered
- More predictable in behavior
97-102: LGTM: Cleaner keyword extraction with sensible limitsThe keyword extraction logic is improved with:
- Direct category-based filtering instead of complex shared logic
- Proper deduplication using
Set- Sensible limit of 3 keywords for display purposes
- Clear and readable implementation
169-171: LGTM: Focused label displayThe simplified label approach showing only the category name is cleaner and more focused, removing potential visual clutter while maintaining essential information.
179-179: LGTM: Consistent visual design with fixed colorUsing a fixed color (
#2E40A9) simplifies the component and ensures visual consistency across the application. This removes the complexity of dynamic color selection while maintaining a professional appearance.apps/www/src/app/bookmarks/page.tsx (1)
1-14: Well-structured server component implementation!The transformation from client to server component is clean and follows Next.js 13+ patterns correctly. Good use of async/await for handling searchParams.
apps/www/src/components/common/nav-dropdown.tsx (1)
25-38: Good structure for bookmark theme navigation!The BOOKMARK_THEMES constant follows the same pattern as NAVS and properly uses query parameters for theme selection.
apps/www/src/components/bookmarks/sidebar/dropdown.tsx (1)
27-44: Well-implemented form validation and mutation handling!Good validation checks for empty and duplicate category names, proper async handling, and appropriate use of the pending state to prevent multiple submissions.
apps/www/src/app/page/bookmarks/index.tsx (2)
45-54: Efficient data filtering implementation!Good use of
useMemoto optimize filtering performance and proper handling of both stars and their associated links.
64-73: Confirm Tree’s data requirements and eliminate non-null assertions.
- I verified that
TreeProps(apps/www/src/components/bookmarks/tree/index.tsx) only defines:– it doesn’t accept ainterface TreeProps { onOpen: (id: string) => void; }dataprop likeGraphandPlanet.- If
Treeshould render withfilteredDatapassed from the parent, extend its props:and update the component to useinterface TreeProps { onOpen: (id: string) => void; data: Bookmark[]; // or the appropriate type }data.- Otherwise, this is an intentional API difference and you can leave the switch case as-is.
- To remove the non-null assertions on
filteredData, add an early guard inrenderTheme, for example:This ensures you don’t need to useconst renderTheme = () => { if (!filteredData) return null; switch (theme) { case GRAPH_THEME.GRAPH: return <Graph onOpen={onOpen} data={filteredData} />; case GRAPH_THEME.TREE: return <Tree onOpen={onOpen} />; default: return <Planet onOpen={onOpen} data={filteredData} />; } };filteredData!.Let me know if
Treeshould consume the same data or if its current standalone behavior is by design.apps/www/src/components/bookmarks/graph/index.tsx (1)
57-84: Excellent defensive programming in node click handler!The validation checks ensure all required properties exist before proceeding, preventing potential runtime errors. The type assertion is safe given the thorough validation.
apps/www/src/components/bookmarks/graph/graph-detail.tsx (6)
113-113: Good UX improvement with toast notifications.Replacing native alerts with toast notifications provides a better user experience.
165-169: Proper deletion flow with parent notification.The implementation correctly deletes the bookmark first and then notifies the parent component to update its state.
58-79: Graph display fallback and error handlingThe
graphDatafallback in apps/www/src/components/bookmarks/graph/graph-detail.tsx (lines 58–79) correctly shows the current node when there are no related nodes. However, there are two edge cases to verify:
- If
stars.starListDtodoesn’t include the currentid,currentStarwill be undefined →nodesbecomes[], and the graph will render empty.- If
useGetGraphDetail(id)errors (starData.isError), the component falls through without rendering any feedback to the user.Please confirm that:
- The parent always supplies a
starsprop containing the currentid, or add a guard to handlecurrentStar === undefined.- An error state is handled (e.g. display an error message) when
starData.isErroris true.
113-113: Good use of toast notifications!Replacing native alerts with toast notifications improves user experience.
165-169: Well-implemented deletion callback pattern.The addition of the
onDeletecallback properly notifies the parent component about deletions, enabling centralized state management.
335-372: Good defensive programming with fallback values.The consistent use of fallback empty strings and arrays prevents undefined values from causing UI issues.
apps/www/src/components/bookmarks/graph/chat-bot/chat.tsx (4)
39-42: Efficient query management for chat history.The implementation correctly prevents unnecessary API calls for new chat sessions by passing the
isNewChatflag.
96-98: Proper session ID extraction from stream response.The implementation safely extracts and propagates the session ID from the streamed response.
112-163: Well-structured form submission handlers.The separation of concerns between existing and new chat sessions is properly implemented with appropriate error handling.
181-188: Modern form submission pattern with proper validation.Using
requestSubmit()ensures form validation runs before submission, and the keyboard shortcuts enhance UX.Also applies to: 267-269
apps/www/src/components/bookmarks/graph/chat-bot/index.tsx (2)
158-169: Well-implemented resize handles with proper UX.The resize handles are correctly positioned with appropriate cursor feedback and z-index layering.
174-177: Good loading state feedback for chat sessions.The loading spinner provides clear feedback while sessions are being fetched.
| "dompurify": "^3.2.6", | ||
| "marked": "^15.0.12", | ||
| "next": "15.0.3", | ||
| "next-themes": "^0.4.6", |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Verify the latest version and security status of next-themes.
Ensure that the specified version is current and secure.
🌐 Web query:
What is the latest version of next-themes npm package and are there any known security vulnerabilities?
💡 Result:
The latest version of the next-themes npm package is 0.4.6, published approximately four months ago. (npmjs.com) According to Snyk, there are no direct vulnerabilities associated with this version. (security.snyk.io)
However, it's important to note that the next-themes package has been succeeded by nextjs-themes, which offers enhanced features and improved performance. The latest version of nextjs-themes is 4.0.6, released four months ago. (npmjs.com) Snyk reports no direct vulnerabilities for this package either. (socket.dev)
Given that next-themes is no longer actively maintained, transitioning to nextjs-themes is recommended for better support and security.
Citations:
- 1: https://www.npmjs.com/package/next-themes?activeTab=versions&utm_source=openai
- 2: https://security.snyk.io/package/npm/next-themes?utm_source=openai
- 3: https://www.npmjs.com/package/nextjs-themes?utm_source=openai
- 4: https://socket.dev/npm/package/next-themes?utm_source=openai
Consider migrating from next-themes to nextjs-themes for long-term support
- apps/www/package.json currently pins
"next-themes": "^0.4.6", which is indeed the latest version and has no known vulnerabilities per Snyk. - However,
next-themesis no longer actively maintained. Its successor,nextjs-themes(latest 4.0.6), offers improved performance and features with ongoing support.
Please update your dependency accordingly:
- "next-themes": "^0.4.6",
+ "nextjs-themes": "^4.0.6",📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "next-themes": "^0.4.6", | |
| "nextjs-themes": "^4.0.6", |
🤖 Prompt for AI Agents
In apps/www/package.json at line 19, replace the dependency "next-themes":
"^0.4.6" with "nextjs-themes": "^4.0.6" to migrate to the actively maintained
successor package. Update any related import statements and usage in the
codebase to reflect the new package name and API if necessary.
| const onClickNav = (href: string) => { | ||
| router.push(href); | ||
| setIsOpen(false); | ||
| setIsNavOpen(false); | ||
| }; |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Consider closing both dropdowns on navigation.
When a user selects a bookmark theme, only the main navigation closes but the bookmark theme dropdown remains open. This could be confusing.
const onClickNav = (href: string) => {
router.push(href);
setIsNavOpen(false);
+ setIsBookmarkThemeOpen(false);
};📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const onClickNav = (href: string) => { | |
| router.push(href); | |
| setIsOpen(false); | |
| setIsNavOpen(false); | |
| }; | |
| const onClickNav = (href: string) => { | |
| router.push(href); | |
| setIsNavOpen(false); | |
| setIsBookmarkThemeOpen(false); | |
| }; |
🤖 Prompt for AI Agents
In apps/www/src/components/common/nav-dropdown.tsx around lines 53 to 56, the
onClickNav function closes only the main navigation dropdown but does not close
the bookmark theme dropdown. Update the function to also set the state
controlling the bookmark theme dropdown to false, ensuring both dropdowns close
when a navigation item is selected.
| <li key={typeof item === "string" ? item : item.id}> | ||
| <button | ||
| onClick={() => onClickItem(typeof item === "string" ? item : item.categoryId)} |
There was a problem hiding this comment.
Fix property name inconsistency.
The code uses item.categoryId but the CategoryProps interface only has an id field.
-onClick={() => onClickItem(typeof item === "string" ? item : item.categoryId)}
+onClick={() => onClickItem(typeof item === "string" ? item : item.id)}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <li key={typeof item === "string" ? item : item.id}> | |
| <button | |
| onClick={() => onClickItem(typeof item === "string" ? item : item.categoryId)} | |
| <li key={typeof item === "string" ? item : item.id}> | |
| <button | |
| onClick={() => onClickItem(typeof item === "string" ? item : item.id)} |
🤖 Prompt for AI Agents
In apps/www/src/components/bookmarks/sidebar/dropdown.tsx around lines 73 to 75,
the code incorrectly uses the property name `categoryId` on `item`, but the
CategoryProps interface defines only `id`. Replace `item.categoryId` with
`item.id` to match the interface and fix the property name inconsistency.
| if (!categoryName) { | ||
| return infoToast("카테고리 이름을 입력해주세요."); | ||
| } | ||
| if (items.some((item) => item.name === categoryName)) { | ||
| return infoToast("이미 존재하는 카테고리입니다."); | ||
| } |
There was a problem hiding this comment.
Fix type safety issue in duplicate check.
The validation incorrectly assumes all items have a name property, but items can be string[] when type is "Keyword".
-if (items.some((item) => item.name === categoryName)) {
+if (isCategory && items.some((item) => (item as CategoryProps).name === categoryName)) {
return infoToast("이미 존재하는 카테고리입니다.");
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (!categoryName) { | |
| return infoToast("카테고리 이름을 입력해주세요."); | |
| } | |
| if (items.some((item) => item.name === categoryName)) { | |
| return infoToast("이미 존재하는 카테고리입니다."); | |
| } | |
| if (!categoryName) { | |
| return infoToast("카테고리 이름을 입력해주세요."); | |
| } | |
| - if (items.some((item) => item.name === categoryName)) { | |
| + if (isCategory && items.some((item) => (item as CategoryProps).name === categoryName)) { | |
| return infoToast("이미 존재하는 카테고리입니다."); | |
| } |
🤖 Prompt for AI Agents
In apps/www/src/components/bookmarks/sidebar/dropdown.tsx around lines 32 to 37,
the duplicate category name check assumes all items have a 'name' property, but
items can be string arrays when the type is "Keyword". To fix this, add a type
check to ensure items are objects with a 'name' property before accessing it, or
adjust the logic to handle string items appropriately when type is "Keyword".
| return ( | ||
| <> | ||
| {renderTheme()} | ||
| <GraphDetail | ||
| open={detail.open} | ||
| id={detail.id} | ||
| onClose={onClose} | ||
| stars={filteredData!} | ||
| onDelete={onDelete} | ||
| /> | ||
| </> | ||
| ); |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Avoid non-null assertion in component rendering.
The non-null assertion on filteredData could cause runtime errors if data becomes undefined.
return (
<>
{renderTheme()}
- <GraphDetail
- open={detail.open}
- id={detail.id}
- onClose={onClose}
- stars={filteredData!}
- onDelete={onDelete}
- />
+ {filteredData && (
+ <GraphDetail
+ open={detail.open}
+ id={detail.id}
+ onClose={onClose}
+ stars={filteredData}
+ onDelete={onDelete}
+ />
+ )}
</>
);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| return ( | |
| <> | |
| {renderTheme()} | |
| <GraphDetail | |
| open={detail.open} | |
| id={detail.id} | |
| onClose={onClose} | |
| stars={filteredData!} | |
| onDelete={onDelete} | |
| /> | |
| </> | |
| ); | |
| return ( | |
| <> | |
| {renderTheme()} | |
| {filteredData && ( | |
| <GraphDetail | |
| open={detail.open} | |
| id={detail.id} | |
| onClose={onClose} | |
| stars={filteredData} | |
| onDelete={onDelete} | |
| /> | |
| )} | |
| </> | |
| ); |
🤖 Prompt for AI Agents
In apps/www/src/app/page/bookmarks/index.tsx between lines 75 and 86, avoid
using the non-null assertion operator on filteredData when passing it as the
stars prop to GraphDetail. Instead, add a conditional check to ensure
filteredData is defined before rendering GraphDetail or provide a safe default
value to prevent potential runtime errors if filteredData is undefined.
| if (!isNewChat) { | ||
| setMessages([]); | ||
| setStreamingContent(null); | ||
| } | ||
| }, [sessionId, isNewChat]); |
There was a problem hiding this comment.
Logic appears inverted for resetting chat state.
The condition should reset messages when starting a new chat, not when continuing an existing chat.
Apply this fix:
-if (!isNewChat) {
+if (isNewChat) {
setMessages([]);
setStreamingContent(null);
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if (!isNewChat) { | |
| setMessages([]); | |
| setStreamingContent(null); | |
| } | |
| }, [sessionId, isNewChat]); | |
| if (isNewChat) { | |
| setMessages([]); | |
| setStreamingContent(null); | |
| } | |
| }, [sessionId, isNewChat]); |
🤖 Prompt for AI Agents
In apps/www/src/components/bookmarks/graph/chat-bot/chat.tsx around lines 53 to
57, the condition to reset messages and streaming content is inverted; it
currently resets when continuing an existing chat instead of when starting a new
chat. Change the condition to check if isNewChat is true, so that
setMessages([]) and setStreamingContent(null) are called only when starting a
new chat.
| const [chatBoxWidth, setChatBoxWidth] = useState(() => Math.max(window.innerWidth * 0.5, 480)); | ||
| const [chatBoxHeight, setChatBoxHeight] = useState(384); |
There was a problem hiding this comment.
Potential SSR issue with window access during initialization.
Accessing window.innerWidth during state initialization can cause hydration mismatches in Next.js.
Use a safer initialization pattern:
-const [chatBoxWidth, setChatBoxWidth] = useState(() => Math.max(window.innerWidth * 0.5, 480));
+const [chatBoxWidth, setChatBoxWidth] = useState(480);
+
+useEffect(() => {
+ setChatBoxWidth(Math.max(window.innerWidth * 0.5, 480));
+}, []);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const [chatBoxWidth, setChatBoxWidth] = useState(() => Math.max(window.innerWidth * 0.5, 480)); | |
| const [chatBoxHeight, setChatBoxHeight] = useState(384); | |
| const [chatBoxWidth, setChatBoxWidth] = useState(480); | |
| const [chatBoxHeight, setChatBoxHeight] = useState(384); | |
| useEffect(() => { | |
| setChatBoxWidth(Math.max(window.innerWidth * 0.5, 480)); | |
| }, []); |
🤖 Prompt for AI Agents
In apps/www/src/components/bookmarks/graph/chat-bot/index.tsx around lines 20 to
21, accessing window.innerWidth directly during state initialization can cause
server-side rendering issues and hydration mismatches in Next.js. To fix this,
initialize chatBoxWidth with a safe default value that does not rely on window,
such as a fixed number, and then update it inside a useEffect hook that runs
only on the client side to set the actual width based on window.innerWidth.
🛠️ 구현한 부분
🖼️ 실행 화면(선택)
🔥 어려웠던 부분(선택)
🔍 참고(선택)
Summary by CodeRabbit
New Features
Improvements
Bug Fixes
Refactor
Chores