-
Notifications
You must be signed in to change notification settings - Fork 18
refactor: Move logEventNum update handling from AppController to viewStore; Split viewStore into slices. #316
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
Conversation
# Conflicts: # src/components/AppController.tsx
… now be reflected in the editor
## Walkthrough
This change refactors view state management by introducing a modular Zustand store architecture with distinct slices for events, pagination, filtering, and formatting. It removes URL and page synchronization logic from `AppController`, delegating log event navigation and related side effects to the new store. Associated components are updated to use the new store API.
## Changes
| File(s) | Change Summary |
|------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| src/components/AppController.tsx | Removed all logic syncing `logEventNum` state with URL and page loading; simplified URL param update functions; removed `logEventNum` state subscription; now only initializes state from URL/hash on load and hash changes. |
| src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/Result.tsx<br>src/components/Editor/index.tsx | Updated to use `updateLogEventNum` (from Zustand store) instead of `setLogEventNum` for log event navigation. |
| src/stores/viewStore/index.ts | Introduced and exported a new Zustand store `useViewStore`, combining four view state slices: event, page, filter, and formatting. |
| src/stores/viewStore/types.ts | Added TypeScript interfaces/types for view state slices: pagination, event, formatting, filtering, and the combined `ViewState`. |
| src/stores/viewStore/createViewEventSlice.ts | New Zustand slice: manages `logEventNum` state, updates URL, loads new pages if needed, and handles errors. Exports `createViewEventSlice` and default state. |
| src/stores/viewStore/createViewPageSlice.ts | New Zustand slice: manages pagination state, provides actions for page navigation/loading, updates URL and global state, and handles errors. |
| src/stores/viewStore/createViewFilterSlice.ts | New Zustand slice: provides `filterLogs` method for log level filtering, updates UI state, fetches filtered data, and handles errors. |
| src/stores/viewStore/createViewFormattingSlice.ts | New Zustand slice: manages prettification state, updates UI and page data on change, and handles errors. |
| src/stores/logFileStore.ts | Removed explicit setting of log data to `"Loading..."` during file load; replaced with resetting structured page data. No other logic changed. |
## Sequence Diagram(s)
```mermaid
sequenceDiagram
participant UI_Component as UI Component (e.g., Editor, Search Result)
participant ViewStore as useViewStore (Zustand)
participant LogFileManager as logFileManagerProxy
participant URL as URL Hash
UI_Component->>ViewStore: updateLogEventNum(newLogEventNum)
alt event is on current page
ViewStore->>URL: Update hash param with new logEventNum
else event not on current page
ViewStore->>UI_Component: Set UI to FAST_LOADING
ViewStore->>LogFileManager: loadPage({eventNum: newLogEventNum})
LogFileManager-->>ViewStore: Return page data
ViewStore->>ViewStore: updatePageData(pageData)
ViewStore->>URL: Update hash param with new logEventNum
end Possibly related PRs
Suggested reviewers
|
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 the PR title, how about:
refactor: Move logEventNum update handling from AppController to viewStore.
updateLogEventNum
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: 9
🔭 Outside diff range comments (1)
src/stores/viewStore/createViewUtilitySlice.ts (1)
113-129
: Avoid hard-crashing on “reload” pre-conditionsThrowing an Error here will bubble up past the store call-site and blank the React tree. Prefer returning early with a warning, or surface the issue via the notification store for a controlled UX.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (7)
src/components/AppController.tsx
(2 hunks)src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/Result.tsx
(1 hunks)src/components/Editor/index.tsx
(1 hunks)src/stores/viewStore/createViewUpdateSlice.ts
(1 hunks)src/stores/viewStore/createViewUtilitySlice.ts
(4 hunks)src/stores/viewStore/index.ts
(1 hunks)src/stores/viewStore/types.ts
(1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
`**/*.{cpp,hpp,java,js,jsx,tpp,ts,tsx}`: - Prefer `false == ` rather than `!`.
**/*.{cpp,hpp,java,js,jsx,tpp,ts,tsx}
: - Preferfalse == <expression>
rather than!<expression>
.
src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/Result.tsx
src/stores/viewStore/index.ts
src/components/Editor/index.tsx
src/stores/viewStore/types.ts
src/components/AppController.tsx
src/stores/viewStore/createViewUtilitySlice.ts
src/stores/viewStore/createViewUpdateSlice.ts
🧠 Learnings (1)
📓 Common learnings
Learnt from: zzxthehappiest
PR: y-scope/yscope-log-viewer#286
File: src/components/Editor/index.tsx:138-139
Timestamp: 2025-06-01T13:40:12.222Z
Learning: In the yscope-log-viewer codebase, when using Zustand stores in React components, the preferred pattern is to use `getState()` for static setters that never change (like `setLogEventNum`) to avoid unnecessary subscriptions, while using hooks for actions that do more than just setting values. All store state variables should be declared at the beginning of the component for consistency and clear dependency overview.
🔇 Additional comments (2)
src/components/AppController.tsx (2)
26-33
: Potential hash-change feedback loop
updateLogEventNum
triggers its ownupdateWindowUrlHashParams
. When invoked from ahashchange
listener this could rewrite the hash again, causing an extra (though usually identical)hashchange
event on some browsers. Consider short-circuiting inupdateLogEventNum
when the incoming value already matches the hash.
70-73
: Double processing of view parameters
handleHashChange
callsupdateViewHashParams()
which in turn callsupdateLogEventNum
, leading again to the feedback loop risk noted above. Confirm no infinite recursion occurs when the store writes back an unchanged hash.
src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/Result.tsx
Show resolved
Hide resolved
updateWindowUrlHashParams({logEventNum: newLogEventNum}); | ||
const {setLogEventNum} = useViewStore.getState(); | ||
setLogEventNum(newLogEventNum); | ||
const {updateLogEventNum} = useViewStore.getState(); | ||
updateLogEventNum(newLogEventNum); | ||
}, []); |
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.
🛠️ Refactor suggestion
Same redundancy as in Result.tsx
handleCursorExplicitPosChange
updates the hash, then hands the same value to updateLogEventNum
, which updates it again. This can bloat the browser history and cause an extra render.
Apply the same one-liner fix as suggested for Result.tsx
.
🤖 Prompt for AI Agents
In src/components/Editor/index.tsx around lines 264 to 267, the function updates
the URL hash and then redundantly updates the same value in the state, causing
extra renders and bloating browser history. Refactor the code to update the
state first and then update the URL hash in a single step or combine the updates
into one call to avoid duplication and unnecessary renders, following the
one-liner fix applied in Result.tsx.
const newLogEventNum = pageData.logEventNum; | ||
updateWindowUrlHashParams({logEventNum: newLogEventNum}); | ||
const {updateLogEventNum} = get(); | ||
updateLogEventNum(newLogEventNum); |
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.
🧹 Nitpick (assertive)
Drop the second updateLogEventNum
call
updatePageData
already updates the URL hash; calling updateLogEventNum
immediately afterwards sets the same state again and triggers another hash update, generating an extra render and history entry.
🤖 Prompt for AI Agents
In src/stores/viewStore/createViewUpdateSlice.ts around lines 163 to 166, remove
the second call to updateLogEventNum(newLogEventNum) because updatePageData
already updates the URL hash and state. This redundant call causes an extra
render and history entry. Keep only the updateWindowUrlHashParams call and the
initial assignment to newLogEventNum.
const clampedLogEventNum = clamp(newLogEventNum, 1, numEvents); | ||
const {beginLineNumToLogEventNum} = get(); | ||
const logEventNumsOnPage: number [] = Array.from(beginLineNumToLogEventNum.values()); | ||
set({logEventNum: clampedLogEventNum}); |
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.
🛠️ Refactor suggestion
Ensure logEventNumsOnPage
is ordered before bounds checks
Array.from(beginLineNumToLogEventNum.values())
relies on insertion order. If the map is ever populated out of order, isWithinBounds
and nearest-element lookup will misbehave.
-const logEventNumsOnPage: number [] = Array.from(beginLineNumToLogEventNum.values());
+const logEventNumsOnPage = [...beginLineNumToLogEventNum.values()].sort((a, b) => a - b);
📝 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 clampedLogEventNum = clamp(newLogEventNum, 1, numEvents); | |
const {beginLineNumToLogEventNum} = get(); | |
const logEventNumsOnPage: number [] = Array.from(beginLineNumToLogEventNum.values()); | |
set({logEventNum: clampedLogEventNum}); | |
const clampedLogEventNum = clamp(newLogEventNum, 1, numEvents); | |
const {beginLineNumToLogEventNum} = get(); | |
- const logEventNumsOnPage: number [] = Array.from(beginLineNumToLogEventNum.values()); | |
+ const logEventNumsOnPage = [...beginLineNumToLogEventNum.values()].sort((a, b) => a - b); | |
set({logEventNum: clampedLogEventNum}); |
🤖 Prompt for AI Agents
In src/stores/viewStore/createViewUpdateSlice.ts around lines 131 to 134, the
array logEventNumsOnPage is created from a Map's values without sorting, which
can cause incorrect behavior if the Map is populated out of order. To fix this,
sort the logEventNumsOnPage array numerically after creating it from the Map
values to ensure it is always ordered before any bounds checks or
nearest-element lookups.
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 the PR title, how about:
refactor: Move logEventNum update handling from AppController to viewStore; Split viewStore into slices.
-
setNumPages
/setPageNum
/setBeginLineNumToLogEventNum
are never used, right? Can we remove them? -
Following (2.), I believe
setLogData
should be removed as well, sincelogData
can be set byupdatePageData
. We do usesetLogData
inloadFile
to initializelogData
, but I think it's better we also initialize other states, so it happens that we can just useupdatePageData
. -
I think we discussed that we want to create slices based on features in the very initial Zustand refactoring PR. can we slice the store this way instead:
createViewPageSliceState:
- pageNum
- numPages
- logData
- beginLineNumToLogEventNum
Actions:
- loadPageByAction
- updatePageData
createViewEventSlice
State:
- logEventNum
Actions:
- updateLogEventNum
createViewFormattingSlice
State:
- isPrettified
Actions:
- updateIsPrettified
createViewFilterSlice
State:
- (none)
Actions:
- filterLogs
Co-authored-by: Junhao Liao <junhao@junhao.ca>
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
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (7)
src/stores/logFileStore.ts
(0 hunks)src/stores/viewStore/createViewEventSlice.ts
(1 hunks)src/stores/viewStore/createViewFilterSlice.ts
(1 hunks)src/stores/viewStore/createViewFormattingSlice.ts
(1 hunks)src/stores/viewStore/createViewPageSlice.ts
(1 hunks)src/stores/viewStore/index.ts
(1 hunks)src/stores/viewStore/types.ts
(1 hunks)
💤 Files with no reviewable changes (1)
- src/stores/logFileStore.ts
🧰 Additional context used
📓 Path-based instructions (1)
`**/*.{cpp,hpp,java,js,jsx,tpp,ts,tsx}`: - Prefer `false == ` rather than `!`.
**/*.{cpp,hpp,java,js,jsx,tpp,ts,tsx}
: - Preferfalse == <expression>
rather than!<expression>
.
src/stores/viewStore/index.ts
src/stores/viewStore/createViewFormattingSlice.ts
src/stores/viewStore/types.ts
src/stores/viewStore/createViewPageSlice.ts
src/stores/viewStore/createViewFilterSlice.ts
src/stores/viewStore/createViewEventSlice.ts
🔇 Additional comments (11)
src/stores/viewStore/index.ts (1)
10-15
: LGTM: Clean store composition patternThe store composition using Zustand slices follows a clear and maintainable pattern. The slice combination is straightforward and the type system ensures compile-time safety.
src/stores/viewStore/createViewFormattingSlice.ts (1)
34-38
: LGTM: Efficient early return optimizationGood optimization to avoid unnecessary work when the prettification state hasn't actually changed.
src/stores/viewStore/createViewFilterSlice.ts (1)
26-49
: LGTM: Well-structured async filtering operationThe filtering logic properly coordinates UI state, async operations, and error handling. The sequence of setting loading state, filtering logs, updating page data, and restarting queries is correct.
src/stores/viewStore/types.ts (2)
1-6
: LGTM: Clean import structure without file extensionsThe imports correctly omit TypeScript file extensions, maintaining tool-agnostic compatibility. The past review concern about .ts extensions appears to have been addressed.
27-44
: Method naming is now more consistentThe action methods now consistently use descriptive prefixes that better reflect their behaviour:
updateLogEventNum
,updateIsPrettified
,updatePageData
for state changes with side effects, andloadPageByAction
,filterLogs
for specific operations. This addresses the past review concern about naming consistency.src/stores/viewStore/createViewPageSlice.ts (3)
37-75
: LGTM: Robust navigation cursor logicThe
getPageNumCursor
function properly handles all navigation actions with appropriate page clamping and event positioning. The switch statement covers all cases and returns null for unknown actions, providing good error handling.
110-126
: LGTM: Comprehensive RELOAD validation logicThe RELOAD action handling includes proper validation of file source and log event number states, with descriptive error messages for debugging.
128-134
: Good UI state validation prevents concurrent operationsProper check for UI_STATE.READY prevents navigation during ongoing operations, with helpful console warning for debugging.
src/stores/viewStore/createViewEventSlice.ts (3)
37-37
: LGTM! Coding guideline compliance.Good adherence to the coding guideline by using
false === isWithinBounds(...)
instead of!isWithinBounds(...)
.
80-80
: LGTM! Coding guideline compliance.Good adherence to the coding guideline by using
0 === numEvents
instead of!numEvents
.
52-54
: Excellent type safety documentation.The detailed comments explaining why the type casting is safe are exemplary. This makes the code much more maintainable and demonstrates thorough understanding of the logic constraints.
(async () => { | ||
const {logFileManagerProxy} = useLogFileManagerStore.getState(); | ||
const pageData = await logFileManagerProxy.loadPage(cursor, newIsPrettified); | ||
|
||
const {updatePageData} = get(); | ||
updatePageData(pageData); | ||
})().catch(handleErrorWithNotification); | ||
}, |
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.
🧹 Nitpick (assertive)
Consider adding race condition protection
The async operation could potentially race with other state updates or be called multiple times rapidly. Consider adding a loading flag or debouncing mechanism to prevent concurrent executions.
updateIsPrettified: (newIsPrettified: boolean) => {
const {isPrettified} = get();
if (newIsPrettified === isPrettified) {
return;
}
+
+ // Prevent concurrent updates
+ const currentState = get();
+ if (currentState.isUpdatingPrettification) {
+ return;
+ }
+ set({isUpdatingPrettification: true});
const {setUiState} = useUiStore.getState();
setUiState(UI_STATE.FAST_LOADING);
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/stores/viewStore/createViewFormattingSlice.ts around lines 54 to 61, the
async function that loads page data can be triggered multiple times rapidly,
causing race conditions. To fix this, add a loading flag in the state to track
if the operation is in progress and prevent re-entry until it completes, or
implement a debounce mechanism to limit how often the function can be called.
Update the state accordingly before and after the async call to ensure only one
execution runs at a time.
(async () => { | ||
const {logFileManagerProxy} = useLogFileManagerStore.getState(); | ||
const {isPrettified, logEventNum} = get(); | ||
const pageData = await logFileManagerProxy.setFilter( | ||
{ | ||
code: CURSOR_CODE.EVENT_NUM, | ||
args: { | ||
eventNum: logEventNum, | ||
}, | ||
}, | ||
isPrettified, | ||
filter | ||
); | ||
|
||
const {updatePageData} = get(); | ||
updatePageData(pageData); | ||
|
||
const {startQuery} = useQueryStore.getState(); | ||
startQuery(); | ||
})().catch(handleErrorWithNotification); |
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.
🧹 Nitpick (assertive)
Consider adding concurrent operation protection
Similar to the formatting slice, this async operation could benefit from protection against concurrent executions to prevent race conditions.
🤖 Prompt for AI Agents
In src/stores/viewStore/createViewFilterSlice.ts around lines 29 to 48, the
async IIFE performing filter setting and page data updating lacks protection
against concurrent executions, risking race conditions. Introduce a concurrency
control mechanism such as a flag or a mutex to ensure only one instance of this
async operation runs at a time, preventing overlapping executions and
inconsistent state updates.
Do you think |
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.
all good except some nits
Co-authored-by: Junhao Liao <junhao@junhao.ca>
ah, |
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 (3)
src/stores/viewStore/index.ts (1)
10-15
: Guard against key collisions when merging slicesSpreading multiple slice objects means duplicate keys are silently overwritten. Consider asserting uniqueness during dev builds (e.g., compare
Object.keys
) or giving each slice its own namespace.src/stores/viewStore/createViewEventSlice.ts (2)
84-91
: Consider performance optimization for frequent calls.Creating a new array from
beginLineNumToLogEventNum.values()
on every call could be inefficient ifupdateLogEventNum
is called frequently during user interactions. Consider caching or memoizing the array creation, or modifyupdateUrlIfEventOnPage
to accept the Map directly.
94-107
: Ensure UI state is reset on page loading failure.The async page loading logic is well-structured, but if page loading fails, the UI state might remain in
FAST_LOADING
state sincehandleErrorWithNotification
only handles error notification. Consider wrapping the async logic in try-catch-finally to properly reset the UI state in all cases.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (2)
src/stores/viewStore/createViewEventSlice.ts
(1 hunks)src/stores/viewStore/index.ts
(1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
`**/*.{cpp,hpp,java,js,jsx,tpp,ts,tsx}`: - Prefer `false == ` rather than `!`.
**/*.{cpp,hpp,java,js,jsx,tpp,ts,tsx}
: - Preferfalse == <expression>
rather than!<expression>
.
src/stores/viewStore/index.ts
src/stores/viewStore/createViewEventSlice.ts
🔇 Additional comments (1)
src/stores/viewStore/createViewEventSlice.ts (1)
33-61
: LGTM! Proper use of boolean comparison and comprehensive bounds checking.The helper function correctly uses
false === isWithinBounds()
following the coding guidelines and handles edge cases well with detailed comments explaining the safety of type casting.
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.
sorry for missing a few things in the last review
Description
Instead of subscribing
logEventNum
changes in AppControlleruseEffect
,setLogEventNum
is changed toupdateLogEventNum
, which handleslogEventNum
change and triggers a page load if needed.viewStore
is also refactored using zustand slices.Checklist
breaking change.
Validation performed
Summary by CodeRabbit
New Features
Improvements
Refactor