-
Notifications
You must be signed in to change notification settings - Fork 121
feat: Implemented log search functionality with highlighted results #647
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
WalkthroughThe changes introduce a comprehensive search and navigation feature to the system logs interface. A new Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant LogSearch
participant SystemLogs
participant LogEntry (PlainLog/FormattedLog)
User->>LogSearch: Types search query / uses navigation controls
LogSearch->>SystemLogs: onSearch(query) / onNavigate(direction)
SystemLogs->>SystemLogs: Update search state, find matches, update highlights
SystemLogs->>LogEntry: Pass searchQuery and isCurrentMatch props
LogEntry->>LogEntry: Highlight matching text
SystemLogs->>SystemLogs: Scroll to highlighted match (if navigating)
Possibly related PRs
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
♻️ Duplicate comments (1)
web-server/src/components/Service/SystemLog/FormattedLog.tsx (1)
13-30
: 🛠️ Refactor suggestionFactor‑out
HighlightedText
to avoid code duplicationSee earlier comment in
PlainLog.tsx
.
🧹 Nitpick comments (4)
web-server/src/components/Service/SystemLog/PlainLog.tsx (1)
3-7
: Unused prop:index
index
is accepted inPlainLogProps
but never used in the component. Either remove it from the prop list or use it (e.g. askey
when mapping). Keeping unused props increases cognitive load.web-server/src/components/Service/SystemLog/LogSearch.tsx (2)
7-16
:gap
has no effect withoutdisplay:flex
(or grid)
SearchContainer
setsgap: 5
but the default display isblock
, so the property is ignored. Adddisplay: flex
(or grid) to get the intended spacing:-const SearchContainer = styled('div')(() => ({ +const SearchContainer = styled('div')(() => ({ position: 'sticky', top: 0, zIndex: 1, gap: 5, + display: 'flex', paddingBottom: 8, alignItems: 'center', backdropFilter: 'blur(10px)', borderRadius: 5, }));
64-71
: Prevent the default “Enter” behaviour in the text fieldPressing in a single‑line
TextField
can submit the surrounding form (if any) or trigger an unintended click on the first<button>
in the DOM. Addevent.preventDefault()
after handling navigation.- if (event.key === 'Enter' && event.shiftKey) { + if (event.key === 'Enter' && event.shiftKey) { + event.preventDefault(); onNavigate('prev'); } else if (event.key === 'Enter') { + event.preventDefault(); onNavigate('next'); }web-server/src/content/Service/SystemLogs.tsx (1)
75-78
: Use a data‑attribute instead of inline style to mark matchesSelecting by
span[style*="background-color: yellow"]
can yield false positives and is brittle if the style changes. When rendering highlights, adddata-log-match
and query via[data-log-match]
to decouple logic from styling.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
web-server/src/components/Service/SystemLog/FormattedLog.tsx
(2 hunks)web-server/src/components/Service/SystemLog/LogSearch.tsx
(1 hunks)web-server/src/components/Service/SystemLog/PlainLog.tsx
(1 hunks)web-server/src/content/Service/SystemLogs.tsx
(4 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
web-server/src/components/Service/SystemLog/FormattedLog.tsx (1)
web-server/src/types/resources.ts (1)
ParsedLog
(1056-1062)
There was a problem hiding this 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
♻️ Duplicate comments (1)
web-server/src/content/Service/SystemLogs.tsx (1)
75-87
:⚠️ Potential issueCritical issue: useEffect inside a callback function
React hooks can only be called at the top level of your component or other hooks, not inside regular JavaScript functions, conditions, or loops. The
useEffect
hook inside yourhandleSearch
callback will cause runtime errors.Move this effect to the component's top level:
- const handleSearch = useCallback((query: string) => { - setSearchQuery(query); - setCurrentMatch(0); - setTotalMatches(0); - setHighlightedElements([]); - updateHighlight(null); - - if (!query) return; - - useEffect(() => { - const elements = Array.from( - containerRef.current?.querySelectorAll('span[style*="background-color: yellow"]') ?? [] - ) as HTMLElement[]; - setHighlightedElements(elements); - setTotalMatches(elements.length); - setCurrentMatch(elements.length ? 1 : 0); - - if (elements.length) { - updateHighlight(elements[0]); - elements[0].scrollIntoView({ behavior: 'smooth', block: 'center' }); - } - }, [searchQuery, logs, updateHighlight]); - - }, []); + const handleSearch = useCallback((query: string) => { + setSearchQuery(query); + setCurrentMatch(0); + setTotalMatches(0); + setHighlightedElements([]); + updateHighlight(null); + }, [updateHighlight]); + + useEffect(() => { + if (!searchQuery) return; + + const elements = Array.from( + containerRef.current?.querySelectorAll('span[style*="background-color: yellow"]') ?? [] + ) as HTMLElement[]; + setHighlightedElements(elements); + setTotalMatches(elements.length); + setCurrentMatch(elements.length ? 1 : 0); + + if (elements.length) { + updateHighlight(elements[0]); + elements[0].scrollIntoView({ behavior: 'smooth', block: 'center' }); + } + }, [searchQuery, logs, updateHighlight]);
🧹 Nitpick comments (1)
web-server/src/content/Service/SystemLogs.tsx (1)
51-64
: Consider using CSS classes for highlights instead of inline stylesUsing inline styles for highlights works, but it can be harder to maintain. Consider using CSS classes with defined styles in your stylesheet.
- if (currentHighlightRef.current) { - currentHighlightRef.current.style.backgroundColor = 'yellow'; - currentHighlightRef.current.style.color = 'black'; - } - - if (element) { - element.style.backgroundColor = 'rgb(255, 161, 10)'; - element.style.color = 'white'; - currentHighlightRef.current = element; - } else { - currentHighlightRef.current = null; - } + if (currentHighlightRef.current) { + currentHighlightRef.current.classList.remove('active-highlight'); + currentHighlightRef.current.classList.add('search-highlight'); + } + + if (element) { + element.classList.remove('search-highlight'); + element.classList.add('active-highlight'); + currentHighlightRef.current = element; + } else { + currentHighlightRef.current = null; + }And add these CSS classes to your stylesheet:
.search-highlight { background-color: yellow; color: black; } .active-highlight { background-color: rgb(255, 161, 10); color: white; }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
web-server/src/content/Service/SystemLogs.tsx
(4 hunks)
🔇 Additional comments (7)
web-server/src/content/Service/SystemLogs.tsx (7)
128-137
: Properly implemented container ref on scrollable elementGreat job fixing the container ref issue from the previous review! The
containerRef
is now correctly attached to the scrollable FlexBox, which will allow the scroll logic to work properly.
91-107
: The navigation logic works wellThe navigation function properly handles cycling through matches in both directions. It updates the current match index, scrolls to the selected element, and updates the highlight styling.
33-36
: Simplified scroll detection logicGood simplification of the scroll detection logic! Using a direct scroll event listener is more straightforward than an IntersectionObserver for this use case.
133-134
: Properly integrated search query with log componentsThe search query is correctly passed down to both
PlainLog
andFormattedLog
components, which will enable highlighting of matched text in the logs.Also applies to: 135-136
116-121
: Well-integrated search UI componentThe LogSearch component is properly integrated with all the necessary props for search functionality and navigation.
42-49
: Smooth scrolling implementationThe scrollToBottom function properly implements smooth scrolling behavior to enhance the user experience.
77-77
: 🛠️ Refactor suggestionPotential null reference issue
The optional chaining on
containerRef.current?.querySelectorAll
protects against null, but if the component hasn't rendered yet, there won't be any elements to query.Add a check to ensure the search is only performed when the container is available:
- const elements = Array.from( - containerRef.current?.querySelectorAll('span[style*="background-color: yellow"]') ?? [] - ) as HTMLElement[]; + const elements = containerRef.current + ? Array.from(containerRef.current.querySelectorAll('span[style*="background-color: yellow"]')) as HTMLElement[] + : [];Likely an incorrect or invalid review comment.
from the looks of it is just highlighting the logs, not actually searching through them or filtering the search results. Seems satisfactory from the issue. But can we make this more usable ? |
…t field and improve INP
There was a problem hiding this 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
🔭 Outside diff range comments (1)
web-server/src/content/Service/SystemLogs.tsx (1)
154-167
: 🛠️ Refactor suggestionRemove direct reference to DOM elements in JSX calculation.
Accessing DOM properties directly in render calculations can cause issues during SSR and is generally not a React best practice. Move this calculation to a useEffect or memoized value.
+ const [buttonMarginLeft, setButtonMarginLeft] = useState(0); + + useEffect(() => { + if (containerRef.current) { + setButtonMarginLeft(containerRef.current.clientWidth / 2 - 67); + } + }, []); // In the JSX <Button onClick={scrollToBottom} component={MotionBox} initial={{ opacity: 0, scale: 0.7 }} animate={{ opacity: 1, scale: 1 }} transition={{ ease: 'easeOut' }} bottom={20} sx={{ position: 'fixed', - marginLeft: `calc(${containerRef.current - ? containerRef.current.clientWidth / 2 - 67 - : 0 - }px)` + marginLeft: `${buttonMarginLeft}px` }} >
♻️ Duplicate comments (1)
web-server/src/content/Service/SystemLogs.tsx (1)
69-84
: DOM query for highlighted elements is brittle and should be improved.The current approach of querying elements by their inline style attribute is fragile. React might reformat or reorder style properties, causing the selector to fail. This was noted in a previous review comment as well.
As suggested in the previous review, use a dedicated class or data attribute for highlighted elements and update your query selector:
- const elements = Array.from( - containerRef.current?.querySelectorAll('span[style*="background-color: yellow"]') ?? [] - ) as HTMLElement[]; + const elements = Array.from( + containerRef.current?.querySelectorAll('span.log-highlight') ?? [] + ) as HTMLElement[];Make sure to add the corresponding class to the highlighted spans in both PlainLog and FormattedLog components.
🧹 Nitpick comments (2)
web-server/src/content/Service/SystemLogs.tsx (2)
54-67
: Consider debouncing the search input to improve performance.For an improved user experience, consider adding debounce to prevent excessive processing during rapid typing. This is especially important since you're performing DOM operations in response to search input.
+import { debounce } from 'lodash'; + const SystemLogs = memo(({ serviceName }: { serviceName?: ServiceNames }) => { // ... existing code - const handleSearch = useCallback((query: string) => { + const debouncedSearch = useCallback( + debounce((query: string) => { + // Reset states only when query is empty or below a threshold + if (!query || query.length < 3) { + setCurrentMatch(0); + setTotalMatches(0); + setHighlightedElements([]); + updateHighlight(null); + return; + } + // Let the effect handle DOM updates + }, 300), + [updateHighlight] + ); + + const handleSearch = useCallback((query: string) => { setSearchQuery(query); - - // Reset states only when query is empty or below a threshold - if (!query || query.length < 3) { - setCurrentMatch(0); - setTotalMatches(0); - setHighlightedElements([]); - updateHighlight(null); - return; - } - - // Let the effect handle DOM updates + debouncedSearch(query); }, [updateHighlight]);
87-103
: Add error handling for navigation edge cases.The navigation function works well, but lacks error handling for edge cases. Consider adding checks to ensure the element still exists in the DOM before attempting to scroll to it.
const handleNavigate = useCallback((direction: 'prev' | 'next') => { if (highlightedElements.length === 0) return; let newIndex = currentMatch; if (direction === 'next') { newIndex = currentMatch < totalMatches ? currentMatch + 1 : 1; } else { newIndex = currentMatch > 1 ? currentMatch - 1 : totalMatches; } setCurrentMatch(newIndex); const element = highlightedElements[newIndex - 1]; - if (element) { + if (element && document.body.contains(element)) { element.scrollIntoView({ behavior: 'smooth', block: 'center' }); updateHighlight(element); + } else { + // Element no longer in DOM, re-query highlighted elements + const freshElements = Array.from( + containerRef.current?.querySelectorAll('span[style*="background-color: yellow"]') ?? [] + ) as HTMLElement[]; + setHighlightedElements(freshElements); + if (freshElements.length) { + setTotalMatches(freshElements.length); + setCurrentMatch(1); + updateHighlight(freshElements[0]); + freshElements[0].scrollIntoView({ behavior: 'smooth', block: 'center' }); + } } }, [currentMatch, totalMatches, highlightedElements, updateHighlight]);
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
web-server/src/components/Service/SystemLog/LogSearch.tsx
(1 hunks)web-server/src/content/Service/SystemLogs.tsx
(5 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- web-server/src/components/Service/SystemLog/LogSearch.tsx
🔇 Additional comments (8)
web-server/src/content/Service/SystemLogs.tsx (8)
3-3
: Component optimization with React.memo and new imports look good.The component is properly wrapped with
memo
for performance optimization, and all necessary hooks and components are imported. This will prevent unnecessary re-renders when theserviceName
prop doesn't change.Also applies to: 8-8, 19-19, 175-175
22-28
: State management for search functionality is well-structured.The state variables are appropriately separated for different aspects of the search functionality. Good use of the ref for tracking the currently highlighted element across renders.
30-37
: Clean implementation of scrollToBottom function.The scroll functionality is simplified with direct scrollTo calls, which is more straightforward than the previous approach.
39-52
: The highlight management callback looks good.The updateHighlight callback effectively toggles between the default yellow background and the distinct orange highlight for the active match.
105-115
: Initial scroll implementation looks good.The initial scroll implementation now correctly uses a ref to track the initial load and requestAnimationFrame to ensure the DOM is ready before scrolling.
117-125
: Memoized renderLogs function is a good optimization.Extracting the logs rendering logic into a memoized callback is a good optimization that prevents unnecessary re-renders.
134-139
: Good integration of the LogSearch component.The LogSearch component is properly integrated with all the necessary props. This cleanly separates the search UI from the main component.
146-148
: Container ref placement is now correct.The containerRef is now properly attached to the scrollable container, fixing the previous issue where it was attached to an empty element.
There was a problem hiding this 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
♻️ Duplicate comments (2)
web-server/src/content/Service/SystemLogs.tsx (2)
88-90
: Replace fragile style selector with semantic class / data-attributeThe DOM lookup relies on matching the literal string
background-color: yellow
, which is brittle (style order, minification, different color formats, etc.).
A robust approach is to mark the highlighted<span>
with a class (log-highlight
) ordata-
attribute and query that instead.This point was raised in a previous review but is still unresolved.
22-23
:showScrollDownButton
is never toggled – add scroll listener
showScrollDownButton
is initialised but never updated when the user scrolls away from the bottom, so the floating button will never appear.A simple scroll handler on the
containerRef
will fix this:+ /* show / hide “scroll-to-bottom” button */ + useEffect(() => { + const el = containerRef.current; + if (!el) return; + + const handleScroll = () => { + const { scrollTop, scrollHeight, clientHeight } = el; + const atBottom = Math.abs(scrollHeight - scrollTop - clientHeight) < 10; + showScrollDownButton.set(!atBottom); + }; + + el.addEventListener('scroll', handleScroll); + handleScroll(); // initialise + return () => el.removeEventListener('scroll', handleScroll); + }, [showScrollDownButton]);Also applies to: 179-185
🧹 Nitpick comments (1)
web-server/src/content/Service/SystemLogs.tsx (1)
191-195
: Hard-codedmarginLeft
breaks on resize – use CSS centeringThe floating button’s horizontal position is calculated once during render and won’t adjust if the log pane resizes.
Usingleft: 50%
withtransform: translateX(-50%)
is simpler and fully responsive:- bottom={20} - sx={{ - position: 'fixed', - marginLeft: `calc(${containerRef.current - ? containerRef.current.clientWidth / 2 - 67 - : 0 - }px)` - }} + sx={{ + position: 'fixed', + bottom: 20, + left: '50%', + transform: 'translateX(-50%)' + }}
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
web-server/src/content/Service/SystemLogs.tsx
(4 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
web-server/src/content/Service/SystemLogs.tsx (5)
web-server/src/hooks/useSystemLogs.tsx (1)
useSystemLogs
(5-22)web-server/src/utils/logFormatter.ts (1)
parseLogLine
(12-95)web-server/src/components/Service/SystemLog/PlainLog.tsx (1)
PlainLog
(34-40)web-server/src/components/Service/SystemLog/FormattedLog.tsx (1)
FormattedLog
(37-82)web-server/src/components/Service/SystemLog/SystemLogsErrorFllback.tsx (1)
SystemLogsErrorFallback
(12-60)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (3)
web-server/src/content/Service/SystemLogs.tsx (3)
87-89
: Use a stable marker for highlighted spans instead of relying on inline styleThe selector
'span[style*="background-color: yellow"]'
is fragile as React may normalize styles or reorder properties.- const elements = Array.from( - containerRef.current?.querySelectorAll('span[style*="background-color: yellow"]') ?? [] - ) as HTMLElement[]; + const elements = Array.from( + containerRef.current?.querySelectorAll('.log-highlight') ?? [] + ) as HTMLElement[];This means you'll need to add the
className="log-highlight"
to the highlighted spans in PlainLog and FormattedLog components.
84-102
: Re-run highlight scan when new logs arriveThe useLayoutEffect only depends on debouncedSearchQuery. If new logs load while a search query is active, matches in those logs won't be highlighted or included in navigation.
- }, [debouncedSearchQuery]); + }, [debouncedSearchQuery, logs]);
22-22
: Missing implementation for showScrollDownButton toggleYou've removed the IntersectionObserver but there's no mechanism to detect when the user scrolls up to show the scroll-down button.
const SystemLogs = memo(({ serviceName }: { serviceName?: ServiceNames }) => { // ... existing code + useEffect(() => { + const handleScroll = () => { + if (!containerRef.current) return; + + const { scrollTop, scrollHeight, clientHeight } = containerRef.current; + const isScrolledToBottom = Math.abs(scrollHeight - scrollTop - clientHeight) < 10; + showScrollDownButton.set(!isScrolledToBottom); + }; + + const scrollContainer = containerRef.current; + if (scrollContainer) { + scrollContainer.addEventListener('scroll', handleScroll); + } + + return () => { + if (scrollContainer) { + scrollContainer.removeEventListener('scroll', handleScroll); + } + }; + }, [showScrollDownButton]);
🧹 Nitpick comments (2)
web-server/src/content/Service/SystemLogs.tsx (2)
53-66
: Direct DOM manipulation may conflict with React's renderingYou're directly manipulating DOM element styles instead of using React's state-based rendering. This can lead to issues if React re-renders the elements.
Consider modifying the
HighlightedText
component to accept anisActive
prop, so React can handle the highlighting:// In PlainLog and FormattedLog components: <HighlightedText text={text} searchQuery={searchQuery} + isActiveMatch={matchIndex === currentActiveMatchIndex} /> // In HighlightedText component: const getStyle = (isMatch, isActive) => { if (isMatch) { return isActive ? { backgroundColor: 'rgb(255, 161, 10)', color: 'white' } : { backgroundColor: 'yellow', color: 'black' }; } return {}; };
This would eliminate the need for direct DOM manipulation.
72-81
: Consider extracting magic numbers as constantsThe minimum search query length (3 characters) is hardcoded in multiple places.
+const MIN_SEARCH_LENGTH = 3; const handleSearch = useCallback((query: string) => { setSearchQuery(query); isSearchingRef.current = !!query; - if (!query || query.length < 3) { + if (!query || query.length < MIN_SEARCH_LENGTH) { // ...Also update line 85 to use the constant.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
web-server/src/content/Service/SystemLogs.tsx
(4 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
web-server/src/content/Service/SystemLogs.tsx (6)
web-server/src/hooks/useSystemLogs.tsx (1)
useSystemLogs
(5-22)web-server/src/utils/logFormatter.ts (1)
parseLogLine
(12-95)web-server/src/components/Service/SystemLog/PlainLog.tsx (1)
PlainLog
(34-40)web-server/src/components/Service/SystemLog/FormattedLog.tsx (1)
FormattedLog
(37-82)web-server/src/components/Service/SystemLog/SystemLogsErrorFllback.tsx (1)
SystemLogsErrorFallback
(12-60)web-server/src/components/Service/SystemLog/LogSearch.tsx (1)
LogSearch
(178-178)
🔇 Additional comments (5)
web-server/src/content/Service/SystemLogs.tsx (5)
176-178
: Proper implementation of container with refThis is a good improvement that addresses a previous critical issue - you've correctly attached the
containerRef
to the scrollable container.
164-169
: Good integration of the search componentThe LogSearch component is well-integrated with proper props being passed for search functionality and navigation.
142-155
: Good extraction of renderLogs as a memoized callbackThe extraction of the log rendering logic into a memoized callback with the correct dependencies is a good performance optimization.
27-34
: 🛠️ Refactor suggestionRedundant state and ref duplications
You have duplicate state and refs for highlightedElements, currentMatch, and totalMatches. This creates potential synchronization issues.
Remove the redundant state variables and use either the state or the refs consistently:
- const [highlightedElements, setHighlightedElements] = useState<HTMLElement[]>([]); - const [currentMatch, setCurrentMatch] = useState(0); - const [totalMatches, setTotalMatches] = useState(0); const currentHighlightRef = useRef<HTMLElement | null>(null); const isInitialLoad = useRef(true); const isSearchingRef = useRef(false); const currentMatchRef = useRef(0); const totalMatchesRef = useRef(0); const highlightedElementsRef = useRef<HTMLElement[]>([]); const isNavigatingRef = useRef(false);Then update your component to only use the refs for DOM manipulation and state for rendering:
// When updating for UI rendering - setHighlightedElements(elements); highlightedElementsRef.current = elements; - setTotalMatches(newTotalMatches); totalMatchesRef.current = newTotalMatches; - setCurrentMatch(newTotalMatches ? 1 : 0); currentMatchRef.current = newTotalMatches ? 1 : 0; // In LogSearch component props <LogSearch onSearch={handleSearch} onNavigate={handleNavigate} - currentMatch={currentMatch} - totalMatches={totalMatches} + currentMatch={currentMatchRef.current} + totalMatches={totalMatchesRef.current} />Likely an incorrect or invalid review comment.
68-82
:✅ Verification successful
Consider enhancing search with filtering capability
According to the PR comments, the current implementation only highlights matches but doesn't filter the logs to show only matching entries.
Consider enhancing the feature by adding an option to filter logs to show only entries containing the search query:
+ const [shouldFilterLogs, setShouldFilterLogs] = useState(false); // In handleSearch: const handleSearch = useCallback((query: string) => { // ...existing code... }, [updateHighlight]); + const filteredLogs = useMemo(() => { + if (!debouncedSearchQuery || !shouldFilterLogs) return logs; + return logs.filter(log => log.toLowerCase().includes(debouncedSearchQuery.toLowerCase())); + }, [logs, debouncedSearchQuery, shouldFilterLogs]); // In renderLogs: - return logs.map((log, index) => { + return (shouldFilterLogs ? filteredLogs : logs).map((log, index) => { // Update LogSearch component: <LogSearch onSearch={handleSearch} onNavigate={handleNavigate} currentMatch={currentMatch} totalMatches={totalMatches} + onToggleFilter={setShouldFilterLogs} + isFilterEnabled={shouldFilterLogs} />Then update your LogSearch component to include a filter toggle button.
🏁 Script executed:
#!/bin/bash # Check if this enhancement aligns with the PR requirements echo "PR objectives mention:" cat << EOF The pull request titled "feat: Implemented log search functionality with highlighted results" aims to enhance the log viewing experience by adding search capabilities within logs. The comment on the PR by user samad-yar-khan notes that the current implementation appears to highlight the logs but does not actually perform searching through or filtering of the search results. The commenter suggests considering enhancements to make the feature more usable. EOF echo -e "\nChecking the current LogSearch component implementation:" fd -t f LogSearch.tsx | xargs catLength of output: 5083
🏁 Script executed:
#!/bin/bash # Locate SystemLogs.tsx and show relevant sections around log rendering file=$(fd -t f SystemLogs.tsx) echo "----- File: $file -----" sed -n '1,200p' "$file" echo "----- Rendering logs in SystemLogs.tsx -----" grep -R "renderLogs" -n "$file" grep -R "logs" -n "$file" | sed -n '1,50p'Length of output: 1131
🏁 Script executed:
#!/bin/bash set -e # Find SystemLogs.tsx files files=$(fd -t f SystemLogs.tsx) for f in $files; do echo "===== File: $f =====" # Show first 200 lines for context sed -n '1,200p' "$f" echo -e "\n--- Occurrences of 'logs', 'render', and 'renderLogs' in $f ---" grep -E -n "(logs|renderLogs|render)" "$f" || true echo -e "\n\n" doneLength of output: 9077
No changes needed: filtering logs is a future enhancement
The current PR implements in-line match highlighting per its scope. Introducing entry‐level filtering would be a valuable follow-up but isn’t required to fulfill the “highlight search results” objective here.• The existing
handleSearch
+renderLogs
flow correctly highlights matches without altering the log list.
• If you’d like to expose a “show only matching entries” toggle, consider capturing it in a separate issue or PR to avoid expanding the current feature scope.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, first things first, my review might be overwhelming.
Sorry about that, should have done this in parts perhaps.
But given that I rarely get time to review PRs in our OSS repo, I decided doing it all in one go might save you more time.
That being said, great first attempt, because it functions alright. :)
However, the code quality leaves a lot to be desired.
Take this as a challenge, I'm hoping you'll come away having learnt a lot of serious stuff from this.
Some learning material I've linked in one of the comments:
https://kentcdodds.com/blog/make-impossible-states-impossible
https://www.youtube.com/watch?v=IcgmSRJHu_8 (link is about Elm lang, but focus on the concept - it's beautifully presented)
} | ||
|
||
const HighlightedText = ({ text, searchQuery }: { text: string; searchQuery?: string }) => { | ||
if (!searchQuery) return <>{text}</>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A component can directly return text. Text nodes are valid react nodes.
searchQuery?: string; | ||
} | ||
|
||
const HighlightedText = ({ text, searchQuery }: { text: string; searchQuery?: string }) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Calling the component HighlightedText is misleading because this is used to render logs regardless of whether it's highlighted or not.
FormattedLog was perhaps a better name.
requestAnimationFrame(() => { | ||
let newIndex = currentMatchRef.current; | ||
const total = totalMatchesRef.current; | ||
|
||
if (direction === 'next') { | ||
newIndex = newIndex < total ? newIndex + 1 : 1; | ||
} else { | ||
newIndex = newIndex > 1 ? newIndex - 1 : total; | ||
} | ||
}; | ||
}, [showScrollDownButton]); | ||
|
||
setCurrentMatch(newIndex); | ||
currentMatchRef.current = newIndex; | ||
const element = highlightedElementsRef.current[newIndex - 1]; | ||
if (element) { | ||
element.scrollIntoView({ behavior: 'smooth', block: 'center' }); | ||
updateHighlight(element); | ||
} | ||
isNavigatingRef.current = false; | ||
}); | ||
}, [updateHighlight]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is an example of adding hacky code to make existing hacky code work.
Using a proper state machine architecture will make this SO MUCH EASIER to handle.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (1)
web-server/src/content/Service/SystemLogs.tsx (1)
76-96
: 🛠️ Refactor suggestionSearch effect needs to watch for new logs
The search effect only depends on
searchQuery
, but it should also re-run when new logs are loaded. If new logs arrive while a search is active, the new content won't be searched or highlighted.- }, [searchState.query]); + }, [searchState.query, logs]);
🧹 Nitpick comments (3)
web-server/src/content/Service/SystemLogs.tsx (3)
166-177
: Good implementation of scroll detectionThe scroll event listener correctly implements the scroll detection for showing/hiding the scroll-down button, fixing the previously identified issue. Consider debouncing the scroll handler for better performance, especially with large log volumes.
+ import { debounce } from '@mui/material/utils'; useEffect(() => { const container = containerRef.current; if (!container) return; - const handleScroll = () => { + const handleScroll = debounce(() => { const isScrolledUp = container.scrollTop < container.scrollHeight - container.clientHeight - 100; showScrollDownButton.set(isScrolledUp); - }; + }, 100); container.addEventListener('scroll', handleScroll); - return () => container.removeEventListener('scroll', handleScroll); + return () => { + container.removeEventListener('scroll', handleScroll); + handleScroll.clear(); + }; }, [showScrollDownButton]);
98-105
: Consider optimizing the scrollToBottom functionThe scrollToBottom function works correctly, but could be optimized to avoid unnecessary scrolling when already at the bottom.
const scrollToBottom = useCallback(() => { if (containerRef.current) { + const { scrollTop, scrollHeight, clientHeight } = containerRef.current; + const isAtBottom = Math.abs(scrollHeight - scrollTop - clientHeight) < 10; + + if (isAtBottom) return; + containerRef.current.scrollTo({ top: containerRef.current.scrollHeight, behavior: 'smooth' }); } }, []);
246-249
: Consider refactoring button position calculationThe inline calculation for the button position is complex and could be extracted for better readability. Also, there's potential for unnecessary re-renders since the calculation references the DOM directly in the JSX.
+ const getButtonPosition = useCallback(() => { + if (!containerRef.current) return 0; + return containerRef.current.clientWidth / 2 - 67; + }, []); + + useEffect(() => { + // Force re-calculation when container size changes + const resizeObserver = new ResizeObserver(() => { + // Trigger re-render if button is visible + if (showScrollDownButton.value) { + showScrollDownButton.set(true); + } + }); + + if (containerRef.current) { + resizeObserver.observe(containerRef.current); + } + + return () => { + if (containerRef.current) { + resizeObserver.unobserve(containerRef.current); + } + resizeObserver.disconnect(); + }; + }, [showScrollDownButton]); // In the JSX: sx={{ position: 'fixed', - marginLeft: `calc(${containerRef.current - ? containerRef.current.clientWidth / 2 - 67 - : 0 - }px)` + marginLeft: `${getButtonPosition()}px` }}
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
web-server/src/components/Service/SystemLog/FormattedLog.tsx
(2 hunks)web-server/src/components/Service/SystemLog/PlainLog.tsx
(1 hunks)web-server/src/content/Service/SystemLogs.tsx
(4 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- web-server/src/components/Service/SystemLog/PlainLog.tsx
- web-server/src/components/Service/SystemLog/FormattedLog.tsx
🔇 Additional comments (8)
web-server/src/content/Service/SystemLogs.tsx (8)
19-66
: Excellent refactoring to a reducer pattern! 👍The implementation of a unified search state with a reducer pattern is a significant improvement over managing multiple separate state variables. This addresses the previous feedback about making "impossible states impossible" and reduces the potential for bugs from inconsistent state.
The state structure clearly represents all possible search states, and the reducer actions are well-defined with appropriate state transitions.
87-89
: Great improvement using data attributes instead of style selectorsUsing
data-highlighted="true"
instead of the previous style-based selector (span[style*="background-color: yellow"]
) is a significant improvement. This addressing previous feedback and provides a more reliable way to identify highlighted elements.
111-122
: Solid navigation logic with proper boundary handlingThe navigation implementation correctly handles edge cases:
- Skips navigation when there are no matches or search is in progress
- Properly cycles through matches (wrapping from last to first and vice versa)
- Updates the current index in the state
179-204
: Well-structured log rendering with memoizationThe memoized rendering logic is well-implemented and correctly passes the search query and match status to child components. The use of
data-log-index
attribute is a good practice for DOM traversal.
225-231
: Properly addressed previous container ref issueThe container ref is now correctly attached to the scrollable FlexBox, resolving the previous issue where the ref was attached to an empty element. This ensures the scroll functionality works as expected.
68-68
: Good use of memoization and clean exportWrapping the component with
memo
is good for performance optimization, and the explicit export is cleaner than using default export.Also applies to: 258-260
152-164
: Smooth initial scroll implementationThe implementation for initial scrolling to the bottom is well done, with proper checks for loading state and log availability. Using
behavior: 'auto'
ensures the initial scroll is immediate rather than animated.
124-150
:✅ Verification successful
Verify highlighted element selection works in all browsers
The DOM traversal logic to find the parent log element is well-implemented, but there's a potential edge case: if the DOM structure changes, the
data-log-index
attribute might not be found.
🏁 Script executed:
#!/bin/bash # Verify that PlainLog and FormattedLog components properly set data-highlighted attribute echo "Checking PlainLog component implementation:" rg -A 10 -B 5 "data-highlighted" --glob "*.tsx" src/components/Service/SystemLog/PlainLog.tsx echo -e "\nChecking FormattedLog component implementation:" rg -A 10 -B 5 "data-highlighted" --glob "*.tsx" src/components/Service/SystemLog/FormattedLog.tsxLength of output: 575
I want to locate the actual component files and see how they annotate each log entry. Let’s search for PlainLog, FormattedLog, and any use of
data-log-index
ordata-highlighted
in the repo:This will tell us the correct paths and whether those components set the necessary attributes.
🏁 Script executed:
#!/bin/bash # Find where PlainLog and FormattedLog are defined echo "=== PlainLog definitions ===" rg -n "PlainLog" --glob "*.tsx" echo -e "\n=== FormattedLog definitions ===" rg -n "FormattedLog" --glob "*.tsx" # Find uses of data-log-index echo -e "\n=== data-log-index usage ===" rg -n "data-log-index" --glob "*.tsx" # Find uses of data-highlighted (highlight attr) echo -e "\n=== data-highlighted usage ===" rg -n "data-highlighted" --glob "*.tsx"Length of output: 2304
Let’s confirm how PlainLog leverages HighlightedText (from FormattedLog) to set the
data-highlighted
attribute:
🏁 Script executed:
#!/bin/bash # Check where PlainLog wraps matches in HighlightedText echo "=== HighlightedText import & usage in PlainLog.tsx ===" rg -n "HighlightedText" --glob "web-server/src/components/Service/SystemLog/PlainLog.tsx" echo -e "\n=== Context around HighlightedText in PlainLog.tsx ===" rg -A 5 -B 5 "HighlightedText" --glob "web-server/src/components/Service/SystemLog/PlainLog.tsx"Length of output: 2083
We should inspect the
HighlightedText
implementation inFormattedLog.tsx
to confirm it appliesdata-highlighted
correctly. Let’s pull that definition:
🏁 Script executed:
#!/bin/bash # Show HighlightedText implementation including data-highlighted spans rg -A5 -B20 "const HighlightedText" --glob "web-server/src/components/Service/SystemLog/FormattedLog.tsx"Length of output: 2413
Confirmed
data-highlighted
on all highlighted spans
We’ve verified that:
- The
HighlightedText
component in
web-server/src/components/Service/SystemLog/FormattedLog.tsx
renders each match wrapped in
<span data-highlighted="true">…</span>
.PlainLog
imports and usesHighlightedText
directly.- SystemLogs.tsx’s lookup
containerRef.current?.querySelectorAll('span[data-highlighted="true"]')
(line 88) will therefore find every highlighted element.No changes are required—your DOM traversal and attribute usage will work consistently across browsers.
@harshit078 one of the linter checks is failing. |
const handleSearchChange = useCallback( | ||
(event: React.ChangeEvent<HTMLInputElement>) => { | ||
const query = event.target.value; | ||
setSearchQuery(query); | ||
debouncedSearch(query); | ||
}, | ||
[debouncedSearch] | ||
); | ||
|
||
const handleClear = useCallback(() => { | ||
setSearchQuery(''); | ||
onSearch(''); | ||
}, [onSearch]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Think about it, when resetting, you're doing both - setting the search query to something, and also triggering search function - separately - with that query.
Could there be a better way to do this?
); | ||
|
||
const showSearchControls = useMemo( | ||
() => searchQuery && totalMatches > 0, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if search query is just a bunch of spaces?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, this really doesn't need to be memoized.
const handleNavigate = useCallback( | ||
(direction: 'prev' | 'next') => { | ||
onNavigate(direction); | ||
}, | ||
[onNavigate] | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If handleNavigate just calls onNavigate
and nothing else, and it re-triggers when onNavigate
changes, why even wrap it in a useCallback
?
<SearchIcon color="action" /> | ||
</InputAdornment> | ||
), | ||
endAdornment: searchQuery && ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps using Boolean(searchQuery.length)
(or !!searchQuery.length
) will be a more obvious way of doing this.
dispatch({ type: 'SET_SEARCHING', payload: true }); | ||
|
||
const timer = setTimeout(() => { | ||
requestAnimationFrame(() => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why requestAnimationFrame
here?
if (total === 0 || isSearching) return; | ||
|
||
const newIndex = direction === 'next' | ||
? (currentIndex < total ? currentIndex + 1 : 1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This indexing starts at 1?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For next
, you can just use modulus.
curr + 1 % total
or something.
prev
is fine.
while (parentElement && !parentElement.hasAttribute('data-log-index')) { | ||
parentElement = parentElement.parentElement; | ||
} | ||
|
||
if (parentElement) { | ||
const lineIndex = parseInt(parentElement.getAttribute('data-log-index') || '-1', 10); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a better way than reading to/writing from HTML attributes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can use React's createRef and then alter our approach using react ref system. Shall we implement this way, What do you think ?
if (containerRef.current && !showScrollDownButton.value) { | ||
scrollDown(); | ||
const { currentIndex, elements } = searchState; | ||
if (currentIndex === 0 || !elements.length) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think index being 0 as a "null" state will confused folks.
In almost all languages indices start at 0.
If you MUST start from 1, maybe call it rank, or order, etc. But I really think with very little refactoring you can make it start from 0.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, I made the refactoring 0 based indexing
Much better. But still has lint failures, and some feedback remaining. |
There was a problem hiding this 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
♻️ Duplicate comments (1)
web-server/src/content/Service/SystemLogs.tsx (1)
97-99
: 🛠️ Refactor suggestionUse a more specific selector for highlighted elements
Using
span[data-highlighted="true"]
as a selector could potentially conflict with third-party extensions or other components. Consider using a more specific and namespaced attribute or class name.- containerRef.current?.querySelectorAll( - 'span[data-highlighted="true"]' - ) ?? [] + containerRef.current?.querySelectorAll( + 'span[data-mhq-log-highlighted="true"]' + ) ?? []Also make sure to update the corresponding attributes in your PlainLog and FormattedLog components.
🧹 Nitpick comments (4)
web-server/src/content/Service/SystemLogs.tsx (4)
122-136
: Simplify navigation logic with modulo operatorThe navigation logic can be simplified using the modulo operator for the "next" case, making it more concise and easier to understand.
- const newIndex = direction === 'next' - ? (currentIndex < total ? currentIndex + 1 : 1) - : (currentIndex > 1 ? currentIndex - 1 : total); + const newIndex = direction === 'next' + ? ((currentIndex % total) + 1) + : (currentIndex > 1 ? currentIndex - 1 : total);
130-130
: Consider zero-based indexing for consistencyStarting the indexing at 1 instead of 0 is inconsistent with JavaScript conventions and could lead to confusion. Consider refactoring to use zero-based indexing throughout the codebase.
If changing to zero-based indexing isn't possible at this stage, consider renaming variables to make it clear that they're one-based (e.g.,
matchPosition
instead ofcurrentIndex
).
140-166
: Extract element navigation logic to a separate functionThe element navigation logic is complex and could benefit from being extracted into a separate named function for better readability and maintainability.
Consider extracting this logic to a function like
scrollToMatchedElement(element: HTMLElement)
that handles finding the parent with data attributes and scrolling to the element.
232-236
: Type assertion may not be necessaryThe type assertion
serviceName as ServiceNames
may be unnecessary if the component typing is correctly set up. Consider reviewing if this can be handled through proper typing of the component props.- serviceName={serviceName as ServiceNames} + serviceName={serviceName}If type assertion is truly needed, add a comment explaining why.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
web-server/src/components/Service/SystemLog/FormattedLog.tsx
(2 hunks)web-server/src/components/Service/SystemLog/LogSearch.tsx
(1 hunks)web-server/src/components/Service/SystemLog/PlainLog.tsx
(1 hunks)web-server/src/content/Service/SystemLogs.tsx
(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
- web-server/src/components/Service/SystemLog/PlainLog.tsx
- web-server/src/components/Service/SystemLog/LogSearch.tsx
- web-server/src/components/Service/SystemLog/FormattedLog.tsx
🔇 Additional comments (9)
web-server/src/content/Service/SystemLogs.tsx (9)
26-73
: Well-implemented reducer pattern for search state management! 👍Great job implementing a reducer pattern for the search state. This is a significant improvement that addresses previous feedback about having too many separate states. The SearchState type and reducer implementation are clean and well-organized, making the component's state logic more maintainable.
75-84
: Good component organization with memo optimizationUsing memo to wrap the component and properly organizing the state declarations shows thoughtful implementation. The initial state setup is clear and the reducer integration is well done.
95-95
: Consider if requestAnimationFrame is necessary hereThe use of
requestAnimationFrame
may be unnecessary in this callback that's already wrapped in a setTimeout. Both functions defer execution, and the double deferral could potentially cause a slight delay in updating the UI.Is there a specific reason for using both setTimeout and requestAnimationFrame? In most cases, just one of these timing mechanisms would be sufficient.
160-165
: Ensure smooth scrolling works consistentlyUsing
scrollIntoView
withbehavior: 'smooth'
can be inconsistent across browsers. Consider adding a fallback or polyfill for browsers that don't support smooth scrolling.Test this functionality in different browsers to ensure consistent behavior.
169-186
: Good attention to initial load UXThe effect to scroll to the bottom on initial load is a nice touch for user experience. Using 'auto' for the behavior ensures it happens immediately without animation, which is appropriate for initial load.
188-201
: Well-implemented scroll listener for button visibilityThis is a good implementation of the scroll listener to toggle the scroll button visibility. The cleanup in the effect's return function properly removes the event listener when the component unmounts.
203-228
: Well-structured log rendering with memoizationThe memoized renderLogs function is a good performance optimization. It correctly passes the search query and current match status to the log components.
240-245
: Great integration of search UI componentThe LogSearch component integration looks clean and well-implemented, with all necessary props passed correctly.
252-258
: Properly structured container with refThe container structure with ref attachment is correctly implemented. This addresses previous issues where the ref was attached to an empty element.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (1)
web-server/src/content/Service/SystemLogs.tsx (1)
85-107
: Add logs dependency to search effectThe search effect only depends on
searchState.query
, but if new logs arrive while the same search query is active, those new logs won't be highlighted until the query changes.- }, [searchState.query]); + }, [searchState.query, logs]);
🧹 Nitpick comments (2)
web-server/src/content/Service/SystemLogs.tsx (2)
96-100
: Consider using a more specific class name for highlighted logsThe
.mhq--highlighted-log
class is better than querying by style attributes, but consider using an even more specific namespace like.mhq-systemlogs-highlight
to avoid potential conflicts with third-party extensions or other components.const elements = Array.from( containerRef.current?.querySelectorAll( - '.mhq--highlighted-log' + '.mhq-systemlogs-highlight' ) ?? [] ) as HTMLElement[];
140-169
: Consider using a ref-based approach for highlight scrollingWhile the DOM traversal approach works, consider using React refs for managing highlighted elements instead of direct DOM manipulation. This would better align with React's declarative paradigm.
You could maintain a map of refs for highlighted elements and use those for scrolling instead of querying the DOM directly.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
web-server/src/components/Service/SystemLog/FormattedLog.tsx
(2 hunks)web-server/src/components/Service/SystemLog/LogSearch.tsx
(1 hunks)web-server/src/components/Service/SystemLog/PlainLog.tsx
(1 hunks)web-server/src/content/Service/SystemLogs.tsx
(3 hunks)web-server/src/hooks/useSystemLogs.tsx
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
- web-server/src/components/Service/SystemLog/PlainLog.tsx
- web-server/src/components/Service/SystemLog/FormattedLog.tsx
- web-server/src/components/Service/SystemLog/LogSearch.tsx
🔇 Additional comments (9)
web-server/src/hooks/useSystemLogs.tsx (1)
9-9
: LGTM: Type signature update correctly makes serviceName requiredMaking
serviceName
a required parameter is a good improvement that aligns with the search functionality implementation, ensuring logs are always associated with a specific service.web-server/src/content/Service/SystemLogs.tsx (8)
26-73
: Nice implementation of reducer pattern for search stateThe use of a reducer pattern with well-defined types for managing complex search state is a good design choice. It makes state transitions explicit and easier to reason about, which will help maintain the code as this feature evolves.
95-95
: Consider if requestAnimationFrame is necessary hereThe
requestAnimationFrame
call wraps a DOM query operation, which is already synchronous. While this won't cause issues, it may not be necessary in this specific case and adds complexity.Do you have specific performance reasons for using
requestAnimationFrame
here instead of directly querying the DOM?
122-138
: Good implementation of navigation with modulus for wrappingThe navigation logic is well-implemented, especially the use of modulus for wrapping around to the first match when navigating past the last one. The code also correctly handles edge cases (no matches, search in progress).
171-188
: Good implementation of initial scroll behaviorThe initial scroll behavior is well-implemented, using a ref to track the initial load state and only scrolling on the first load. Using
requestAnimationFrame
here is appropriate to ensure scrolling happens after rendering.
190-203
: Good addition of scroll event listenerThe scroll event listener correctly replaces the previous IntersectionObserver approach and properly cleans up the listener in the effect cleanup function to prevent memory leaks.
205-230
: Well-structured memoized log rendering functionThe memoized rendering function is well-structured and correctly passes the search query and current match status to the log components. Using
useCallback
here is good for performance.
242-247
: Good integration of LogSearch componentThe LogSearch component is well integrated with the necessary props for search and navigation functionality.
254-260
: Good implementation of logs container with proper refThe logs container is correctly implemented with a ref and uses the memoized rendering function. The styling for overflow and max height is appropriate for a scrollable container.
Linked Issue(s)
#556
Acceptance Criteria fulfillment
Proposed changes
Screen.Recording.2025-04-21.at.4.27.38.PM.mov
Summary by CodeRabbit
Summary by CodeRabbit
New Features
Improvements