Skip to content

refactor: Replace StateContextProvider with Zustand for state management (resolves #168, resolves #211). #224

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

Merged
merged 58 commits into from
May 12, 2025

Conversation

Henry8192
Copy link
Collaborator

@Henry8192 Henry8192 commented Apr 5, 2025

Description

Resolves #168; Resolves #211.

Checklist

  • The PR satisfies the contribution guidelines.
  • This is a breaking change and that has been indicated in the PR title, OR this isn't a
    breaking change.
  • Necessary docs have been updated, OR no docs need to be updated.

Validation performed

logExportStore Test

  1. Click "export logs" button, notice the ui changes with increasing exportProgress. After the download finishes, the download button becomes green.

queryStore Test

  1. open this link and type "123" in the search box. Since log viewer triggers a query whenever the search box input changes, previous query result of "12" was cleared. After query finished, click into any of the search results and verify the line does contain "123".

uiStore Test

  1. Before loading a file to log viewer, notice the default uiState makes most buttons unavailable. After loading a file, the uiState gets updated and makes the unavailable buttons available.

mainWorkerStore Test

  1. If loadFile doesn't fail, that means destroy() and init() works.

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features

    • Introduced comprehensive Zustand-based state management for log files, UI, queries, exporting, and viewing.
    • Added advanced log searching, filtering, and exporting with real-time progress and error pop-ups.
    • Enhanced log file metadata display and file type detection for better user insight.
    • Introduced a new app controller component for URL synchronization and state coordination.
  • Refactor

    • Replaced React context with modular Zustand stores across all components for cleaner state handling.
    • Simplified worker communication by switching to Comlink proxy-based remote calls.
    • Modularized query logic into separate slices for configuration, control, and results.
    • Streamlined log file manager with a proxy interface for safer and clearer API usage.
    • Updated UI components to rely on Zustand stores instead of context for state access.
  • Bug Fixes

    • Improved error handling and user notifications during file loading, exporting, querying, and page navigation.
  • Chores

    • Added dependencies on Comlink and Zustand to support new state and worker management approaches.

Copy link

coderabbitai bot commented Apr 5, 2025

## Walkthrough

This change refactors the application's state management from React context to multiple Zustand stores, replacing the `StateContextProvider` with a new `AppController` and introducing dedicated stores for log files, UI, queries, view state, and log export. It also replaces manual web worker message handling with a Comlink-based proxy architecture.

## Changes

| File(s)                                                                                 | Change Summary |
|-----------------------------------------------------------------------------------------|---------------|
| `package.json`                                                                          | Added dependencies: `comlink` and `zustand`. |
| `src/components/CentralContainer/Sidebar/SidebarTabs/FileInfoTabPanel.tsx`<br>`src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/QueryInputBox.tsx`<br>`src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/index.tsx`<br>`src/components/CentralContainer/Sidebar/SidebarTabs/SettingsTabPanel/index.tsx`<br>`src/components/CentralContainer/Sidebar/SidebarTabs/index.tsx`<br>`src/components/CentralContainer/Sidebar/index.tsx`<br>`src/components/DropFileContainer/index.tsx`<br>`src/components/Editor/index.tsx`<br>`src/components/MenuBar/ExportLogsButton.tsx`<br>`src/components/MenuBar/NavigationBar.tsx`<br>`src/components/MenuBar/PageNumInput.tsx`<br>`src/components/MenuBar/index.tsx`<br>`src/components/StatusBar/LogLevelSelect/index.tsx`<br>`src/components/StatusBar/index.tsx` | Refactored components to use Zustand stores instead of React context for state management. |
| `src/contexts/StateContextProvider.tsx`                                                 | Removed all internal state, logic, and side effects; now provides a null context and delegates all state to external stores. |
| `src/contexts/AppController.tsx`                                                        | Introduced new `AppController` component for URL synchronization and high-level state management using Zustand stores. |
| `src/App.tsx`                                                                          | Replaced `StateContextProvider` with `AppController` in the component tree. |
| `src/services/MainWorker.ts`                                                            | Replaced manual message/event handling with Comlink's `expose` for a `LogFileManagerProxy` instance. |
| `src/services/LogFileManagerProxy.ts`                                                   | Added new `LogFileManagerProxy` class to encapsulate log file operations and expose them via Comlink. |
| `src/services/LogFileManager/index.ts`                                                  | Added `FILE_TYPE` enum and `fileType` getter; updated `loadPage` method signature. |
| `src/services/decoders/ClpIrDecoder/index.ts`<br>`src/services/decoders/JsonlDecoder/index.ts` | Removed `postFormatPopup` side effect; added `irStreamType` getter to `ClpIrDecoder`. |
| `src/typings/worker.ts`                                                                 | Removed detailed worker protocol types/enums; added minimal `LogFileInfo` and `PageData` types. |
| `src/stores/contextStore.ts`                                                            | Added new Zustand store for context-related state (log event number, popup posting). |
| `src/stores/logExportStore.ts`                                                          | Added new Zustand store for log export state and actions. |
| `src/stores/logFileManagerProxyStore.ts`                                                | Added new Zustand store to manage a Comlink remote proxy to the log file manager. |
| `src/stores/logFileStore.ts`                                                            | Added new Zustand store for log file metadata, loading, and related actions. |
| `src/stores/queryStore/createQueryConfigSlice.ts`<br>`src/stores/queryStore/createQueryControllerSlice.ts`<br>`src/stores/queryStore/createQueryResultsSlice.ts`<br>`src/stores/queryStore/index.ts`<br>`src/stores/queryStore/types.ts` | Added Zustand slices and store for query configuration, control, and results. |
| `src/stores/uiStore.ts`                                                                 | Added new Zustand store for UI state (active tab, UI status). |
| `src/stores/viewStore.ts`                                                               | Added new Zustand store for view/page state and navigation. |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant AppController
    participant ZustandStores
    participant LogFileManagerProxy (Comlink)
    participant MainWorker

    AppController->>ZustandStores: Update/query state (UI, file, view, query)
    AppController->>LogFileManagerProxy: loadFile(), loadPage(), etc. (via Comlink)
    LogFileManagerProxy->>MainWorker: Method call via Comlink
    MainWorker->>LogFileManagerProxy: Response via Comlink
    LogFileManagerProxy->>ZustandStores: Update state via callback (export chunk, query results)
    ZustandStores->>AppController: State change triggers UI update

Assessment against linked issues

Objective Addressed Explanation
Ensure that search results are recalculated and reflect the current log level filter (#168)
Replace React context providers with global state store frameworks (#211)

Possibly related PRs

Suggested reviewers

  • junhaoliao


<!-- walkthrough_end -->

<!-- announcements_start -->

> [!TIP]
> <details>
> <summary>⚡️ Faster reviews with caching</summary>
> 
> - CodeRabbit now supports caching for code and dependencies, helping speed up reviews. This means quicker feedback, reduced wait times, and a smoother review experience overall. Cached data is encrypted and stored securely. This feature will be automatically enabled for all accounts on May 16th. To opt out, configure `Review - Disable Cache` at either the organization or repository level. If you prefer to disable all data retention across your organization, simply turn off the `Data Retention` setting under your Organization Settings.
> 
> Enjoy the performance boost—your workflow just got faster.
> 
> </details>

<!-- announcements_end -->

---

<details>
<summary>📜 Recent review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: ASSERTIVE**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 72650b91b68a0f622391b6c2582a892f4e75a05b and 1b49af7927f4a1e8074e7b4de5957904d7f7cb87.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `src/stores/logFileStore.ts` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>📓 Path-based instructions (1)</summary>

<details>
<summary>`**/*.{cpp,hpp,java,js,jsx,tpp,ts,tsx}`: - Prefer `false == <expression>` rather than `!<expression>`.</summary>

> `**/*.{cpp,hpp,java,js,jsx,tpp,ts,tsx}`: - Prefer `false == <expression>` rather than `!<expression>`.

- `src/stores/logFileStore.ts`

</details>

</details><details>
<summary>🧬 Code Graph Analysis (1)</summary>

<details>
<summary>src/stores/logFileStore.ts (12)</summary><blockquote>

<details>
<summary>src/typings/common.ts (1)</summary>

* `Nullable` (10-10)

</details>
<details>
<summary>src/typings/worker.ts (2)</summary>

* `FileSrcType` (108-108)
* `CursorType` (107-107)

</details>
<details>
<summary>src/services/LogFileManager/index.ts (2)</summary>

* `cursor` (492-514)
* `FILE_TYPE` (517-517)

</details>
<details>
<summary>src/typings/logs.ts (1)</summary>

* `LOG_LEVEL` (56-56)

</details>
<details>
<summary>src/typings/notifications.ts (2)</summary>

* `LONG_AUTO_DISMISS_TIMEOUT_MILLIS` (37-37)
* `DO_NOT_TIMEOUT_VALUE` (36-36)

</details>
<details>
<summary>src/typings/query.ts (2)</summary>

* `QueryResults` (23-23)
* `QUERY_PROGRESS_VALUE_MIN` (28-28)

</details>
<details>
<summary>src/stores/logExportStore.ts (1)</summary>

* `LOG_EXPORT_STORE_DEFAULT` (63-63)

</details>
<details>
<summary>src/typings/states.ts (1)</summary>

* `UI_STATE` (106-106)

</details>
<details>
<summary>src/contexts/UrlContextProvider.tsx (1)</summary>

* `updateWindowUrlSearchParams` (279-279)

</details>
<details>
<summary>src/typings/url.ts (1)</summary>

* `SEARCH_PARAM_NAMES` (38-38)

</details>
<details>
<summary>src/utils/config.ts (1)</summary>

* `getConfig` (182-182)

</details>
<details>
<summary>src/typings/config.ts (1)</summary>

* `CONFIG_KEY` (44-44)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>🔇 Additional comments (4)</summary><blockquote>

<details>
<summary>src/stores/logFileStore.ts (4)</summary>

`76-76`: _🧹 Nitpick (assertive)_

**Follow coding guidelines for boolean comparisons**

Per your coding guidelines, prefer `false == <expression>` rather than `!<expression>` or in this case, prefer `null !== logExportManager` rather than using `!==`.

```diff
-    if (null !== logExportManager) {
+    if (null == logExportManager) {
+        return;
+    }
-        const progress = logExportManager.appendChunk(logs);
-        setExportProgress(progress);
-    }
+    const progress = logExportManager.appendChunk(logs);
+    setExportProgress(progress);

Likely an incorrect or invalid review comment.


119-119: 🧹 Nitpick (assertive)

Follow coding guidelines for boolean comparisons

Per your coding guidelines, prefer false == <expression> rather than !<expression> or in this case, prefer "string" !== typeof fileSrc rather than using !==.

-        if ("string" !== typeof fileSrc) {
+        if ("string" == typeof fileSrc) {
+            // Code for string file source if needed
+        } else {
             updateWindowUrlSearchParams({[SEARCH_PARAM_NAMES.FILE_PATH]: null});
+        }

Likely an incorrect or invalid review comment.


92-92: 🧹 Nitpick (assertive)

Follow coding guidelines for boolean comparisons

Per your coding guidelines, prefer false == <expression> rather than !<expression> or in this case, prefer QUERY_PROGRESS_VALUE_MIN === progress rather than comparison with ===.

-    if (QUERY_PROGRESS_VALUE_MIN === progress) {
+    if (QUERY_PROGRESS_VALUE_MIN == progress) {

Likely an incorrect or invalid review comment.


141-141: 🧹 Nitpick (assertive)

Follow coding guidelines for boolean comparisons

Per your coding guidelines, prefer false == <expression> rather than !<expression> or in this case, prefer 0 === decoderOptions.formatString.length rather than using ===.

-            if (0 === decoderOptions.formatString.length && canFormat) {
+            if (0 == decoderOptions.formatString.length && canFormat) {
                 postPopUp(FORMAT_POP_UP_MESSAGE);
             }

Likely an incorrect or invalid review comment.

✨ Finishing Touches
  • 📝 Generate Docstrings

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.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need 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)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@junhaoliao junhaoliao requested a review from hoophalab April 9, 2025 23:24
@junhaoliao
Copy link
Member

adding @hoophalab for awareness
context: we will be gradually extracting states out of the StateContextProvider and separate the states into separate Zustand stores. please be aware when formally checking in the LLM feature, we will need to manage the LLM states differently (assuming by then we are done with this refactoring effort

@Henry8192 Henry8192 marked this pull request as ready for review April 16, 2025 17:50
@Henry8192 Henry8192 requested a review from a team as a code owner April 16, 2025 17:50
coderabbitai[bot]

This comment was marked as outdated.

@Henry8192 Henry8192 changed the title feat: Use Zustand for state context management (resolves #211). feat!: Use Zustand for state context management (resolves #211). Apr 17, 2025
coderabbitai[bot]

This comment was marked as outdated.

// fall through to unreachable error.
}
}
throw new Error("Unexpected decoder type when determining file type.");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to fix the linter

Suggested change
throw new Error("Unexpected decoder type when determining file type.");
throw new Error("Unexpected decoder type when determining file type.");

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
src/services/LogFileManager/index.ts (1)

1-1: Consider refactoring rather than increasing ESLint limits.

Increasing the max-lines limit from 500 to 600 suggests the file could benefit from further modularization. Consider splitting functionality into separate modules to improve maintainability.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6695360 and d5e5b55.

📒 Files selected for processing (1)
  • src/services/LogFileManager/index.ts (7 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}: - Prefer false == <expression> rather than !<expression>.

  • src/services/LogFileManager/index.ts
🔇 Additional comments (3)
src/services/LogFileManager/index.ts (3)

44-48: Good addition of a typed enum for file types.

Creating a proper enum for file types improves type safety and makes the code more maintainable. This is a good practice when working with a fixed set of values.


283-283: Good simplification of the return type.

Replacing the WorkerResp<WORKER_RESP_CODE.PAGE_DATA> type with the direct PageData type simplifies the API and aligns with the refactoring to Zustand stores.


517-517: Good exposure of the FILE_TYPE enum.

Exporting the FILE_TYPE enum makes it available for use in other modules, which is necessary for proper typing in the refactored state management.


// fall through to unreachable error.
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Fix trailing whitespace on this line.

Remove the trailing whitespace to comply with linting rules.

-                    // fall through to unreachable error.
-            }
+                    // fall through to unreachable error.
+            }

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +128 to +150
/**
* Returns the type of file based on the decoder in use.
*
* @return The detected file type.
* @throws {Error} If the decoder type is unknown.
*/
get fileType (): FILE_TYPE {
const decoder = this.#decoder;
if (decoder instanceof JsonlDecoder) {
return FILE_TYPE.JSONL;
} else if (decoder instanceof ClpIrDecoder) {
switch (decoder.irStreamType) {
case CLP_IR_STREAM_TYPE.STRUCTURED:
return FILE_TYPE.CLP_KV_IR;
case CLP_IR_STREAM_TYPE.UNSTRUCTURED:
return FILE_TYPE.CLP_TEXT_IR;
default:

// fall through to unreachable error.
}
}
throw new Error("Unexpected decoder type when determining file type.");
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Make fileType implementation more future-proof and remove class checks.

Using instanceof ties this method to the current decoder list and will fail silently if a decoder is proxied across workers (where prototype chains are lost) or if a new decoder class is added.

A safer pattern:

-    get fileType (): FILE_TYPE {
-        const decoder = this.#decoder;
-        if (decoder instanceof JsonlDecoder) {
-            return FILE_TYPE.JSONL;
-        } else if (decoder instanceof ClpIrDecoder) {
-            switch (decoder.irStreamType) {
-                case CLP_IR_STREAM_TYPE.STRUCTURED:
-                    return FILE_TYPE.CLP_KV_IR;
-                case CLP_IR_STREAM_TYPE.UNSTRUCTURED:
-                    return FILE_TYPE.CLP_TEXT_IR;
-                default:
-
-                    // fall through to unreachable error.
-            }
-        }
-        throw new Error("Unexpected decoder type when determining file type.");
-    }
+    get fileType (): FILE_TYPE {
+        if ("isJsonlDecoder" in this.#decoder) {
+            return FILE_TYPE.JSONL;
+        } else if ("irStreamType" in this.#decoder) {
+            switch (this.#decoder.irStreamType) {
+                case CLP_IR_STREAM_TYPE.STRUCTURED:
+                    return FILE_TYPE.CLP_KV_IR;
+                case CLP_IR_STREAM_TYPE.UNSTRUCTURED:
+                    return FILE_TYPE.CLP_TEXT_IR;
+                default:
+                    // fall through to error
+            }
+        }
+        throw new Error("Unexpected decoder type when determining file type.");
+    }

This approach uses feature detection rather than type checking, making it more resilient to changes in the codebase.

Committable suggestion skipped: line range outside the PR's diff.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (3)
src/stores/logFileStore.ts (3)

111-117: Reset numEvents during file loading

When loading a new file, fileName and onDiskFileSizeInBytes are reset to defaults, but numEvents is not, which could lead to stale data briefly appearing in the UI.

        setFileName("Loading...");
        setOnDiskFileSizeInBytes(LOG_FILE_STORE_DEFAULT.onDiskFileSizeInBytes);
+       setNumEvents(LOG_FILE_STORE_DEFAULT.numEvents);
        setExportProgress(LOG_EXPORT_STORE_DEFAULT.exportProgress);
        clearQuery();
        setUiState(UI_STATE.FILE_LOADING);
        setLogData("Loading...");

134-136: set(fileInfo) leaks extra keys into the Zustand state

Passing the whole fileInfo object to set() might add properties beyond those defined in LogFileValues, potentially causing type inconsistencies or overwriting unrelated state in future refactors.

-            set(fileInfo);
+            const {
+                fileName,
+                numEvents,
+                onDiskFileSizeInBytes,
+                fileType
+            } = fileInfo;
+            set({fileName, numEvents, onDiskFileSizeInBytes, fileType});

102-154: loadFile arrow function is 50+ lines – consider extracting helpers

The function combines UI resets, proxy calls, error handling, and pop-up logic, making it harder to read, test, and lint (exceeding the 60-line limit mentioned in PR comments).

Consider extracting the following helper functions:

  1. resetStoreState() - Lines 111-117
  2. handleSuccessfulLoad(fileInfo, pageData) - Lines 134-143
  3. handleLoadError(e) - Lines 144-153
📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d5e5b55 and 72650b9.

📒 Files selected for processing (1)
  • src/stores/logFileStore.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}: - Prefer false == <expression> rather than !<expression>.

  • src/stores/logFileStore.ts
🧬 Code Graph Analysis (1)
src/stores/logFileStore.ts (12)
src/typings/common.ts (1)
  • Nullable (10-10)
src/typings/worker.ts (2)
  • FileSrcType (108-108)
  • CursorType (107-107)
src/services/LogFileManager/index.ts (2)
  • cursor (492-514)
  • FILE_TYPE (517-517)
src/typings/logs.ts (1)
  • LOG_LEVEL (56-56)
src/typings/notifications.ts (2)
  • LONG_AUTO_DISMISS_TIMEOUT_MILLIS (37-37)
  • DO_NOT_TIMEOUT_VALUE (36-36)
src/typings/query.ts (2)
  • QueryResults (23-23)
  • QUERY_PROGRESS_VALUE_MIN (28-28)
src/stores/logExportStore.ts (1)
  • LOG_EXPORT_STORE_DEFAULT (63-63)
src/typings/states.ts (1)
  • UI_STATE (106-106)
src/contexts/UrlContextProvider.tsx (1)
  • updateWindowUrlSearchParams (279-279)
src/typings/url.ts (1)
  • SEARCH_PARAM_NAMES (38-38)
src/utils/config.ts (1)
  • getConfig (182-182)
src/typings/config.ts (1)
  • CONFIG_KEY (44-44)
🪛 GitHub Check: lint-check
src/stores/logFileStore.ts

[failure] 30-30:
'VIEW_STORE_DEFAULT' is defined but never used

🪛 ESLint
src/stores/logFileStore.ts

[error] 30-30: 'VIEW_STORE_DEFAULT' is defined but never used.

(@typescript-eslint/no-unused-vars)

🪛 GitHub Actions: lint
src/stores/logFileStore.ts

[error] 30-30: ESLint: 'VIEW_STORE_DEFAULT' is defined but never used. (@typescript-eslint/no-unused-vars)

🔇 Additional comments (3)
src/stores/logFileStore.ts (3)

76-76: 🧹 Nitpick (assertive)

Prefer false == <expression> over !== null

According to your coding guidelines, boolean expressions should use the form false == <expression> rather than !<expression>.

-    if (null !== logExportManager) {
+    if (false == (null === logExportManager)) {

Likely an incorrect or invalid review comment.


92-92: 🧹 Nitpick (assertive)

Prefer false == <expression> over ===

According to your coding guidelines, boolean expressions should use the form false == <expression> rather than !<expression> or ===.

-    if (QUERY_PROGRESS_VALUE_MIN === progress) {
+    if (false == (QUERY_PROGRESS_VALUE_MIN !== progress)) {

Likely an incorrect or invalid review comment.


141-141: 🧹 Nitpick (assertive)

Prefer false == <expression> over ===

According to your coding guidelines, boolean expressions should use the form false == <expression> rather than === 0.

-            if (0 === decoderOptions.formatString.length && canFormat) {
+            if (false == (0 !== decoderOptions.formatString.length) && canFormat) {

Likely an incorrect or invalid review comment.

Comment on lines +33 to +38
interface LogFileValues {
fileName: string;
fileSrc: Nullable<FileSrcType>;
numEvents: number;
onDiskFileSizeInBytes: number;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider adding fileType to LogFileValues interface

The fileType property is used in lines 138-139, but it's not included in the LogFileValues interface, suggesting it might be one of the "leaked" properties from fileInfo.

interface LogFileValues {
    fileName: string;
    fileSrc: Nullable<FileSrcType>;
    numEvents: number;
    onDiskFileSizeInBytes: number;
+   fileType?: string;
}
📝 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.

Suggested change
interface LogFileValues {
fileName: string;
fileSrc: Nullable<FileSrcType>;
numEvents: number;
onDiskFileSizeInBytes: number;
}
interface LogFileValues {
fileName: string;
fileSrc: Nullable<FileSrcType>;
numEvents: number;
onDiskFileSizeInBytes: number;
fileType?: string;
}

Comment on lines +155 to +163
setFileName: (newFileName) => {
set({fileName: newFileName});
},
setNumEvents: (newNumEvents) => {
set({numEvents: newNumEvents});
},
setOnDiskFileSizeInBytes: (newSize) => {
set({onDiskFileSizeInBytes: newSize});
},
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Add JSDoc comments for setter functions

The setter functions lack JSDoc comments, unlike other functions in this file which are well-documented.

+    /**
+     * Sets the file name in the store
+     * @param newFileName - The new file name to set
+     */
    setFileName: (newFileName) => {
        set({fileName: newFileName});
    },
+    /**
+     * Sets the number of events in the store
+     * @param newNumEvents - The new number of events to set
+     */
    setNumEvents: (newNumEvents) => {
        set({numEvents: newNumEvents});
    },
+    /**
+     * Sets the on-disk file size in bytes in the store
+     * @param newSize - The new file size to set
+     */
    setOnDiskFileSizeInBytes: (newSize) => {
        set({onDiskFileSizeInBytes: newSize});
    },
📝 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.

Suggested change
setFileName: (newFileName) => {
set({fileName: newFileName});
},
setNumEvents: (newNumEvents) => {
set({numEvents: newNumEvents});
},
setOnDiskFileSizeInBytes: (newSize) => {
set({onDiskFileSizeInBytes: newSize});
},
/**
* Sets the file name in the store
* @param newFileName - The new file name to set
*/
setFileName: (newFileName) => {
set({fileName: newFileName});
},
/**
* Sets the number of events in the store
* @param newNumEvents - The new number of events to set
*/
setNumEvents: (newNumEvents) => {
set({numEvents: newNumEvents});
},
/**
* Sets the on-disk file size in bytes in the store
* @param newSize - The new file size to set
*/
setOnDiskFileSizeInBytes: (newSize) => {
set({onDiskFileSizeInBytes: newSize});
},

@Henry8192 Henry8192 merged commit 14082f5 into y-scope:main May 12, 2025
4 checks passed
@Henry8192 Henry8192 deleted the zustand branch May 12, 2025 20:22
const {postPopUp} = useContextStore.getState();

if (QUERY_CONFIG_DEFAULT.queryString === queryString) {
return;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe we need to clearResults() before early returning. Otherwise, any results from previous queries will still be rendered in the panel even if the query string is empty. e.g.,
image

@coderabbitai please create an issue to track this

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

@junhaoliao I've created an issue to track the bug where query results aren't cleared when the query string is empty. The issue describes the problem in the startQuery() function where we should call clearQueryResults() before the early return to ensure previous search results are cleared when a user empties the search box.


🏁 Script executed:

#!/bin/bash
# Create a GitHub issue to track the bug related to not clearing query results
gh issue create \
  --title "Bug: Query results not cleared when query string is empty" \
  --body "## Description

When the query string is empty, the previous search results still remain displayed in the panel. This happens because in \`startQuery()\` function, we return early when the query string equals the default empty string, without clearing the previous results first.

## Current Behavior
Currently in \`src/stores/queryStore/createQueryControllerSlice.ts\`:

\`\`\`javascript
startQuery: () => {
    const {
        clearQueryResults,
        queryString,
        queryIsCaseSensitive,
        queryIsRegex,
    } = get();
    const {logFileManagerProxy} = useLogFileManagerStore.getState();
    const {postPopUp} = useContextStore.getState();

    if (QUERY_CONFIG_DEFAULT.queryString === queryString) {
        return;
    }

    clearQueryResults();
    // ...
}
\`\`\`

When the query string is empty or matches the default value, the function returns early without clearing previous results, causing them to remain visible.

## Expected Behavior
The \`clearQueryResults()\` function should be called before the early return to ensure that previous results are cleared when the query string is empty:

\`\`\`javascript
startQuery: () => {
    const {
        clearQueryResults,
        queryString,
        queryIsCaseSensitive,
        queryIsRegex,
    } = get();
    const {logFileManagerProxy} = useLogFileManagerStore.getState();
    const {postPopUp} = useContextStore.getState();

    clearQueryResults();  // Clear results regardless of query string value

    if (QUERY_CONFIG_DEFAULT.queryString === queryString) {
        return;
    }

    // ...
}
\`\`\`

## References
- Identified in PR: #224
- Comment: https://github.com/y-scope/yscope-log-viewer/pull/224#discussion_r2085584901

## Screenshots
![image](https://github.com/user-attachments/assets/287ed953-5c0c-4dc1-a9cd-fe5ad7c285a5)
" \
  --assignee junhaoliao

Length of output: 1974


@junhaoliao I've created an issue to track this bug: #266. The issue details the problem where query results aren't cleared when the query string is empty, along with the proposed fix to call clearQueryResults() before the early return.


🧬 Code Graph Analysis Results

Relevant snippets for understanding src/stores/queryStore/createQueryControllerSlice.ts and the user comment:


From src/stores/queryStore/createQueryResultsSlice.ts (line 52):

QUERY_RESULTS_DEFAULT
  • This constant is imported and used in clearQuery() to reset query results state.
  • Likely defines the default state for query results, important for understanding what "clearing results" entails.

From src/stores/queryStore/queryConfigSlice.ts (line 39):

QUERY_CONFIG_DEFAULT
  • This constant is imported and used in clearQuery() and in the startQuery() method to compare the current query string with the default.
  • Represents the default query configuration, including the default query string (likely empty).
  • Relevant for understanding the early return condition in startQuery().

From src/typings/logs.ts (line 56):

LOG_LEVEL
  • Enum or constants defining log levels, used in error handling in startQuery() when posting a popup on failure.

From src/typings/notifications.ts (line 36):

DO_NOT_TIMEOUT_VALUE
  • Constant used in startQuery() error handling to specify a popup notification that does not timeout.

From src/stores/queryStore/types.ts (lines 46-50):

QueryControllerSlice
QueryControllerValues
QueryState
  • Types defining the shape of the slice, values, and overall query state.
  • Important for understanding the structure of the state managed by this slice.

Summary of createQueryControllerSlice (lines 10-56 in src/stores/queryStore/createQueryControllerSlice.ts):

  • Defines a Zustand slice managing query state and actions.
  • clearQuery() resets query config, controller values, and results to their defaults.
  • setQueryProgress(newProgress) updates query progress.
  • startQuery():
    • Early returns if the current query string equals the default query string (likely empty).
    • Calls clearQueryResults() before starting a new query.
    • Starts the query asynchronously via logFileManagerProxy.
    • Catches errors and posts a popup with error details.

These snippets and summaries provide the necessary context to understand the state management and behavior of query clearing and starting, which is directly related to the user comment about clearing results before early returning in startQuery().

} from "./UrlContextProvider";


const StateContext = createContext<null>(null);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for missing this in the previous review - we should no longer need to create a context here. And we should remove the unused export {StateContext}. instead, we can simply

return <div>
    {children}
</div>

in AppController.

Also, we should rename StateContextProviderProps -> AppController.

@coderabbitai create an issue to track this

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Create an issue to track the needed cleanup of AppController.tsx

gh issue create \
--title "cleanup: Remove unused StateContext from AppController.tsx" \
--body "## Description

As noted in PR #224, we should clean up the AppController component since we no longer need the StateContext:

- Remove the creation of StateContext since it's no longer needed
- Remove the unused export of StateContext
- Replace the \`<StateContext.Provider value={null}>\` wrapper with a simple \`<div>\` wrapper in the AppController component
- Rename the interface \`StateContextProviderProps\` to \`AppControllerProps\`

This is a cleanup task after migrating from React Context to Zustand for state management.

## References
- Discussion: https://github.com/y-scope/yscope-log-viewer/pull/224#discussion_r2093320308
- Requested by: @junhaoliao"

Length of output: 813


@junhaoliao I've created an issue to track this cleanup task. You can find it here: Issue #280.


🧬 Code Graph Analysis Results

Relevant snippets for understanding src/contexts/AppController.tsx and the user comment:


From src/contexts/AppController.tsx (lines 1-132)

The entire file content is relevant as it defines the AppController component, the StateContext context, and the StateContextProviderProps interface. Key points:

  • StateContext is created but only used internally and exported unused.
  • StateContextProviderProps is an interface with a single children prop.
  • AppController component uses multiple stores and contexts (NotificationContext, UrlContext, various Zustand stores).
  • It manages synchronization of URL parameters and application state, including loading files and updating UI state.
  • The component returns a StateContext.Provider wrapping children.
  • The user comment suggests removing the context creation and export, simplifying the component to just return <div>{children}</div>.
  • Also suggests renaming StateContextProviderProps to AppController.

From src/contexts/NotificationContextProvider.tsx (line 84)

NotificationContext
  • This is imported and used in AppController to get postPopUp function.
  • Relevant because postPopUp is used in error handling and effect hooks.

From src/contexts/UrlContextProvider.tsx (lines 278-282)

updateWindowUrlHashParams
UrlContext
URL_HASH_PARAMS_DEFAULT
URL_SEARCH_PARAMS_DEFAULT
  • These are imported and used in AppController.
  • updateWindowUrlHashParams is used to update URL hash parameters.
  • UrlContext provides filePath, isPrettified, and logEventNum.
  • URL_HASH_PARAMS_DEFAULT and URL_SEARCH_PARAMS_DEFAULT provide default values for URL parameters.

From src/utils/data.ts (lines 190, 193)

findNearestLessThanOrEqualElement
isWithinBounds
  • Utility functions used in updateUrlIfEventOnPage to check bounds and find nearest element.
  • Important for URL update logic in AppController.

From src/typings/worker.ts (lines 100, 107)

CURSOR_CODE
CursorType
  • Used to define cursor objects for loading pages or events.
  • Used in AppController to create cursor objects for loading log file pages.

From src/typings/logs.ts (line 56)

LOG_LEVEL
  • Used for logging levels in notifications, e.g., error pop-ups.

From src/typings/notifications.ts (line 36)

DO_NOT_TIMEOUT_VALUE
  • Used as a timeout value for notifications in error handling.

From src/typings/states.ts (line 106)

UI_STATE
  • Used to set UI state, e.g., UI_STATE.FAST_LOADING in AppController.

Summary

The following are relevant to understanding and potentially refactoring AppController.tsx as per the user comment:

  • The entire AppController.tsx file (lines 1-132).
  • NotificationContext from NotificationContextProvider.tsx.
  • updateWindowUrlHashParams, UrlContext, URL_HASH_PARAMS_DEFAULT, URL_SEARCH_PARAMS_DEFAULT from UrlContextProvider.tsx.
  • Utility functions findNearestLessThanOrEqualElement and isWithinBounds from utils/data.ts.
  • Types and constants CURSOR_CODE, CursorType from typings/worker.ts.
  • LOG_LEVEL from typings/logs.ts.
  • DO_NOT_TIMEOUT_VALUE from typings/notifications.ts.
  • UI_STATE from typings/states.ts.

These snippets provide the context for the state management, URL synchronization, notification handling, and cursor logic used in AppController. They are essential for understanding the current implementation and the implications of removing the context and simplifying the component as suggested.

Comment on lines +185 to +190
useEffect(() => {
setPostPopUp(postPopUp);
}, [
postPopUp,
setPostPopUp,
]);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should setPostPopUp() to update the reference in contextStore before loading a file, or any error occured during file loading would not show a UI pop-up. e.g., accessing this should show a 404 error pop-up but nothing was shown: https://y-scope.github.io/yscope-log-viewer/?filePath=does_not_exist

@coderabbitai create an issue to track this

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

@junhaoliao I've created an issue to track this bug. The issue documents that error pop-ups don't appear when loading non-existent files because setPostPopUp() is called after file loading in the current implementation. The issue includes reproduction steps, root cause analysis, and a proposed solution to move the setPostPopUp() call before the file loading logic.


🏁 Script executed:

#!/bin/bash
# Create a GitHub issue to track the bug where error pop-ups don't appear during file loading
gh issue create \
  --title "Bug: Error pop-ups don't appear when loading non-existent files" \
  --body "## Description
When attempting to load a non-existent file, the application should display an error pop-up, but currently nothing is shown.

## Reproduction Steps
1. Visit https://y-scope.github.io/yscope-log-viewer/?filePath=does_not_exist
2. Notice that no error pop-up appears, even though a 404 error should be displayed

## Root Cause
In the AppController component, \`setPostPopUp()\` is called in a useEffect hook, but this happens after the file loading attempt. Any errors that occur during file loading won't show a UI pop-up because the pop-up function reference hasn't been updated in the contextStore yet.

## Proposed Solution
Move the \`setPostPopUp()\` call before loading a file to ensure that error pop-ups can be displayed for any errors during the file loading process.

## References
- Referenced in PR: #224
- Comment: https://github.com/y-scope/yscope-log-viewer/pull/224#discussion_r2101376855

cc @junhaoliao" \
  --assignee junhaoliao

Length of output: 1117


@junhaoliao I've created an issue to track this bug: #290. The issue describes the problem where error pop-ups don't appear when loading non-existent files because setPostPopUp() is called after file loading in the current implementation.


🧬 Code Graph Analysis Results

Relevant snippets for understanding src/contexts/AppController.tsx and the user comment:


From src/contexts/NotificationContextProvider.tsx (line 84)

NotificationContext
  • This is the React context providing the postPopUp function used in AppController to show UI pop-ups for notifications/errors.

From src/contexts/UrlContextProvider.tsx (lines 278-282)

updateWindowUrlHashParams
UrlContext
URL_HASH_PARAMS_DEFAULT
URL_SEARCH_PARAMS_DEFAULT
  • updateWindowUrlHashParams: Function used in AppController to update URL hash parameters, e.g., updating logEventNum.
  • UrlContext: React context providing URL parameters like filePath, isPrettified, and logEventNum used in AppController.
  • URL_HASH_PARAMS_DEFAULT and URL_SEARCH_PARAMS_DEFAULT: Default values for URL hash and search parameters, used for comparison in AppController.

From src/typings/logs.ts (line 56)

LOG_LEVEL
  • Enum or constants defining log levels (e.g., ERROR) used in postPopUp calls in AppController.

From src/typings/notifications.ts (line 36)

DO_NOT_TIMEOUT_VALUE
  • Constant used as timeoutMillis in postPopUp to indicate the notification should not timeout automatically.

From src/typings/worker.ts (lines 100-107)

CURSOR_CODE
CursorType
  • CURSOR_CODE: Enum or constants defining cursor codes like EVENT_NUM, LAST_EVENT.
  • CursorType: Type describing the cursor object used to load pages or files in AppController.

From src/utils/data.ts (lines 190-193)

findNearestLessThanOrEqualElement
isWithinBounds
  • Utility functions used in updateUrlIfEventOnPage to check if a log event number is within bounds and to find the nearest event number on the current page.

From src/services/LogFileManager/index.ts (lines 124-126, 492-514)

get numEvents () {
    return this.#numEvents;
}

#getCursorData (cursor: CursorType, numActiveEvents: number): CursorData {
    const {code, args} = cursor;
    switch (code) {
        case CURSOR_CODE.PAGE_NUM:
            return getPageNumCursorData(
                args.pageNum,
                args.eventPositionOnPage,
                numActiveEvents,
                this.#pageSize,
            );
        case CURSOR_CODE.LAST_EVENT:
            return getLastEventCursorData(numActiveEvents, this.#pageSize);
        case CURSOR_CODE.EVENT_NUM:
            return getEventNumCursorData(
                args.eventNum,
                numActiveEvents,
                this.#pageSize,
                this.#decoder.getFilteredLogEventMap(),
            );
        default:
            throw new Error(`Unsupported cursor type: ${code}`);
    }
}
  • numEvents getter: Returns the number of events in the log file, used in AppController to clamp event numbers.
  • #getCursorData: Internal method to get cursor data based on cursor type, relevant to how AppController constructs cursors for loading pages/files.

These snippets provide context on:

  • How notifications (postPopUp) are triggered and managed.
  • How URL parameters are read and updated.
  • How cursor types and codes are used to load specific pages or events.
  • How event numbers are validated and clamped.
  • How errors during file loading might be surfaced via notifications.

This context is directly relevant to the user comment about ensuring setPostPopUp() updates the reference before loading a file to properly show error pop-ups on file load failure.


✏️ Learnings added
Learnt from: junhaoliao
PR: y-scope/yscope-log-viewer#224
File: src/contexts/AppController.tsx:185-190
Timestamp: 2025-05-22T00:03:20.535Z
Learning: In the AppController component, `setPostPopUp()` should be called before loading a file to ensure error pop-ups appear when file loading fails, such as with non-existent files.

Learnt from: junhaoliao
PR: y-scope/yscope-log-viewer#224
File: src/contexts/AppController.tsx:185-190
Timestamp: 2025-05-22T00:03:20.535Z
Learning: In the AppController component, `setPostPopUp()` should be called before loading a file to ensure error pop-ups appear when file loading fails, such as with non-existent files.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Replace React context providers with global state store frameworks. Search results are not recalculated when the filter is changed.
4 participants