Skip to content

refactor: Extract upper-bound binary search from findNearestLogEventByTimestamp into a function (resolves #343).#347

Merged
Henry8192 merged 15 commits intoy-scope:mainfrom
Henry8192:extract-binary-search
Aug 21, 2025
Merged

refactor: Extract upper-bound binary search from findNearestLogEventByTimestamp into a function (resolves #343).#347
Henry8192 merged 15 commits intoy-scope:mainfrom
Henry8192:extract-binary-search

Conversation

@Henry8192
Copy link
Contributor

@Henry8192 Henry8192 commented Jul 18, 2025

Description

This pull request refactors the findNearestLogEventByTimestamp method in the JsonlDecoder class by introducing a reusable utility function, upperBoundBinarySearch, to simplify and optimize the binary search logic. Additionally, it includes unit tests for the new utility function. Below is a summary of the most important changes:

Refactoring and Optimization:

  • The findNearestLogEventByTimestamp core binary search logic in JsonlDecoder was refactored to use the newly introduced upperBoundBinarySearch utility function, replacing the inline binary search implementation. This improves code readability and reusability.

Testing:

  • Added comprehensive unit tests for upperBoundBinarySearch in test/utils/math.test.ts, covering edge cases such as empty arrays, single-element arrays, and arrays with duplicate values.

  • 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

Summary by CodeRabbit

  • Refactor

    • Nearest-log-event lookup now uses a shared upper-bound binary-search utility for clearer, more maintainable behaviour.
  • New Features

    • Added a reusable upper-bound binary-search utility for efficient lookups on sorted collections.
  • Tests

    • Added comprehensive tests for the new utility covering empty inputs, edge cases, boundaries and duplicates.
  • Documentation

    • Clarified that the log-event lookup may return null when no events exist.

@coderabbitai
Copy link

coderabbitai bot commented Jul 18, 2025

Warning

Rate limit exceeded

@Henry8192 has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 5 minutes and 0 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 3296637 and bc9742f.

📒 Files selected for processing (3)
  • src/services/decoders/JsonlDecoder/index.ts (2 hunks)
  • src/utils/math.ts (2 hunks)
  • test/utils/math.test.ts (2 hunks)

Walkthrough

Replaced JsonlDecoder's manual binary search with a new generic utility upperBoundBinarySearch in src/utils/math.ts; added unit tests for the utility and clarified the findNearestLogEventByTimestamp doc comment to explicitly allow null.

Changes

Cohort / File(s) Change Summary
Math utility
src/utils/math.ts
Added upperBoundBinarySearch<T, U>(array: U[], upperBoundValue: T, get: (arrayElement: U) => T) => Nullable<number> and exported it. Performs an upper-bound binary search returning the last index whose projected value ≤ target, or null for empty arrays.
Decoder refactor
src/services/decoders/JsonlDecoder/index.ts
Replaced manual loop and boundary checks in findNearestLogEventByTimestamp with a call to upperBoundBinarySearch, using logEvent => logEvent.timestamp.valueOf() as the extractor.
Tests
test/utils/math.test.ts
Added tests for upperBoundBinarySearch covering empty arrays, single-element arrays, multi-element arrays, duplicates, and boundary conditions.
Typings / docs
src/typings/decoders.ts
Updated the findNearestLogEventByTimestamp documentation comment to state it returns the index or null when there are no log events.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant JsonlDecoder
    participant MathUtils

    Client->>JsonlDecoder: findNearestLogEventByTimestamp(timestamp)
    JsonlDecoder->>MathUtils: upperBoundBinarySearch(this.#logEvents, timestamp, get: e => e.timestamp.valueOf())
    MathUtils-->>JsonlDecoder: index or null
    JsonlDecoder-->>Client: index or null
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

Suggested reviewers

  • kirkrodrigues
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.
    • 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.
  • 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 the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

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

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • 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.

@Henry8192 Henry8192 marked this pull request as ready for review July 20, 2025 20:30
@Henry8192 Henry8192 requested a review from a team as a code owner July 20, 2025 20:30
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

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between f91e8c9 and 0618c19.

📒 Files selected for processing (3)
  • src/services/decoders/JsonlDecoder/index.ts (2 hunks)
  • src/utils/math.ts (2 hunks)
  • test/utils/math.test.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{cpp,hpp,java,js,jsx,tpp,ts,tsx}

Instructions used from:

Sources:
⚙️ CodeRabbit Configuration File

🧠 Learnings (4)
📓 Common learnings
Learnt from: Henry8192
PR: y-scope/yscope-log-viewer#80
File: new-log-viewer/src/services/LogFileManager.ts:297-313
Timestamp: 2024-10-09T02:08:56.053Z
Learning: In the `#searchChunk` method of `LogFileManager.ts`, checking `typeof match.index === "number"` correctly handles matches at index 0, ensuring matches at the beginning of the string are not skipped.
test/utils/math.test.ts (3)
Learnt from: junhaoliao
PR: y-scope/yscope-log-viewer#115
File: test/utils/math.test.ts:0-0
Timestamp: 2024-11-10T21:25:24.592Z
Learning: The `clamp` function in `src/utils/math.ts` correctly handles cases where `max` is less than `min` by returning the upper boundary.
Learnt from: Henry8192
PR: y-scope/yscope-log-viewer#80
File: new-log-viewer/src/services/LogFileManager.ts:297-313
Timestamp: 2024-10-09T02:08:56.053Z
Learning: In the `#searchChunk` method of `LogFileManager.ts`, checking `typeof match.index === "number"` correctly handles matches at index 0, ensuring matches at the beginning of the string are not skipped.
Learnt from: Henry8192
PR: y-scope/yscope-log-viewer#80
File: new-log-viewer/src/services/LogFileManager/index.ts:305-321
Timestamp: 2024-10-10T20:26:31.573Z
Learning: When a function like `#searchChunkAndScheduleNext` requires significant improvements, it's preferred to rewrite the entire function rather than making incremental changes.
src/services/decoders/JsonlDecoder/index.ts (8)
Learnt from: Henry8192
PR: y-scope/yscope-log-viewer#80
File: new-log-viewer/src/services/LogFileManager.ts:297-313
Timestamp: 2024-10-09T02:08:56.053Z
Learning: In the `#searchChunk` method of `LogFileManager.ts`, checking `typeof match.index === "number"` correctly handles matches at index 0, ensuring matches at the beginning of the string are not skipped.
Learnt from: Henry8192
PR: y-scope/yscope-log-viewer#80
File: new-log-viewer/src/services/LogFileManager/index.ts:305-321
Timestamp: 2024-10-10T20:26:31.573Z
Learning: When a function like `#searchChunkAndScheduleNext` requires significant improvements, it's preferred to rewrite the entire function rather than making incremental changes.
Learnt from: junhaoliao
PR: y-scope/yscope-log-viewer#77
File: new-log-viewer/src/components/StatusBar/LogLevelSelect/index.tsx:99-105
Timestamp: 2024-10-08T15:52:50.753Z
Learning: `LOG_LEVEL` is a numerical enum with values starting from 0, so using `index` in `LOG_LEVEL_NAMES.map((logLevelName, index) => ...)` is safe.
Learnt from: junhaoliao
PR: y-scope/yscope-log-viewer#77
File: new-log-viewer/src/components/StatusBar/LogLevelSelect/index.tsx:99-105
Timestamp: 2024-09-26T08:12:44.589Z
Learning: `LOG_LEVEL` is a numerical enum with values starting from 0, so using `index` in `LOG_LEVEL_NAMES.map((logLevelName, index) => ...)` is safe.
Learnt from: junhaoliao
PR: y-scope/yscope-log-viewer#77
File: new-log-viewer/src/components/StatusBar/LogLevelSelect/index.tsx:99-105
Timestamp: 2024-10-08T15:52:50.753Z
Learning: Using `Object.values(LOG_LEVEL)` returns both the enum's numeric values and key names, so extra filtering is needed to obtain only the numeric values.
Learnt from: junhaoliao
PR: y-scope/yscope-log-viewer#77
File: new-log-viewer/src/components/StatusBar/LogLevelSelect/index.tsx:99-105
Timestamp: 2024-09-26T08:12:44.589Z
Learning: Using `Object.values(LOG_LEVEL)` returns both the enum's numeric values and key names, so extra filtering is needed to obtain only the numeric values.
Learnt from: davemarco
PR: y-scope/yscope-log-viewer#123
File: src/services/decoders/ClpIrDecoder.ts:42-42
Timestamp: 2024-11-18T01:35:35.812Z
Learning: The interface `YscopeFormatterOptionsType` is defined in `src/typings/formatters.ts` and is used to type the options passed to `YscopeFormatter`, ensuring type safety in constructor calls like `new YscopeFormatter({ formatString: decoderOptions.formatString })`.
Learnt from: Henry8192
PR: y-scope/yscope-log-viewer#107
File: src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/Result.tsx:1-7
Timestamp: 2024-10-28T18:40:29.816Z
Learning: In the y-scope/yscope-log-viewer project, importing components via destructuring from '@mui/joy' is acceptable, as the package size impact has been analyzed and found acceptable.
src/utils/math.ts (2)
Learnt from: junhaoliao
PR: y-scope/yscope-log-viewer#115
File: test/utils/math.test.ts:0-0
Timestamp: 2024-11-10T21:25:24.592Z
Learning: The `clamp` function in `src/utils/math.ts` correctly handles cases where `max` is less than `min` by returning the upper boundary.
Learnt from: Henry8192
PR: y-scope/yscope-log-viewer#80
File: new-log-viewer/src/services/LogFileManager.ts:297-313
Timestamp: 2024-10-09T02:08:56.053Z
Learning: In the `#searchChunk` method of `LogFileManager.ts`, checking `typeof match.index === "number"` correctly handles matches at index 0, ensuring matches at the beginning of the string are not skipped.
🧬 Code Graph Analysis (3)
test/utils/math.test.ts (1)
src/utils/math.ts (1)
  • upperBoundBinarySearch (69-69)
src/services/decoders/JsonlDecoder/index.ts (1)
src/utils/math.ts (1)
  • upperBoundBinarySearch (69-69)
src/utils/math.ts (1)
src/typings/common.ts (1)
  • Nullable (6-6)
🪛 Biome (1.9.4)
src/utils/math.ts

[error] 52-52: Reassigning a function parameter is confusing.

The parameter is declared here:

Use a local variable instead.

(lint/style/noParameterAssign)


[error] 54-54: Reassigning a function parameter is confusing.

The parameter is declared here:

Use a local variable instead.

(lint/style/noParameterAssign)

🔇 Additional comments (7)
src/utils/math.ts (3)

1-1: LGTM: Import statement correctly adds Nullable type.

The import statement properly brings in the Nullable type from the common typings module, which is used in the function return type.


69-69: LGTM: Export statement correctly includes the new function.

The export statement properly adds the new upperBoundBinarySearch function to the module exports.


43-45: Consider using explicit boolean comparison per coding guidelines.

The coding guidelines prefer false == <expression> rather than !<expression>.

Apply this diff to align with coding guidelines:

-    if (highIdx < lowIdx || "undefined" === typeof (get(highIdx))) {
+    if (highIdx < lowIdx || false === (typeof get(highIdx) !== "undefined")) {
         return null;
     }

However, the current implementation is more readable. Consider whether strict adherence to this guideline improves code clarity in this case.

⛔ Skipped due to learnings
Learnt from: junhaoliao
PR: y-scope/yscope-log-viewer#84
File: new-log-viewer/src/contexts/NotificationContextProvider/index.tsx:0-0
Timestamp: 2024-10-11T19:37:56.597Z
Learning: In our TypeScript codebase with strong typing, variables are strictly typed and cannot be `undefined` where `null` is expected, so checking for `undefined` in null checks is unnecessary.
test/utils/math.test.ts (2)

5-5: LGTM: Import statement correctly adds the new function.

The import statement properly includes the upperBoundBinarySearch function from the math utils module.


55-103: Excellent comprehensive test coverage for the binary search function.

The test suite thoroughly covers all important scenarios:

Edge cases handled:

  • Empty collections with invalid ranges
  • Single-element arrays
  • Values outside the array bounds

Boundary conditions tested:

  • First and last element matches
  • Values between elements
  • Duplicate element handling

Algorithm correctness verified:

  • All test expectations align with the expected binary search upper bound behavior
  • Test data includes negative numbers, duplicates, and various array sizes

The test cases correctly validate that the function returns:

  • null for invalid ranges
  • 0 when all elements are greater than the search value
  • The correct index for the last element ≤ the search value
src/services/decoders/JsonlDecoder/index.ts (2)

21-21: LGTM: Import statement correctly adds the utility function.

The import statement properly brings in the upperBoundBinarySearch function from the math utils module.


142-149: Excellent refactoring that simplifies the timestamp search logic.

The refactoring successfully replaces the manual binary search implementation with the new utility function. The implementation is much cleaner and more maintainable:

Correct parameter mapping:

  • get: (idx) => this.#logEvents[idx]?.timestamp.valueOf() - Proper accessor function with safe navigation
  • lowIdx: 0 - Correct starting index
  • highIdx: this.#logEvents.length - 1 - Correct ending index
  • upperboundValue: timestamp - Correct search target

Behavior preservation:

  • Returns the same type (Nullable<number>)
  • Maintains the same upper bound binary search semantics
  • Handles edge cases (empty arrays, out-of-bounds values) correctly

The use of optional chaining (?.) in the getter function is particularly good as it handles potential undefined log events gracefully.

@Henry8192 Henry8192 changed the title refactor: add upperboundBinarySearch and refactor timestamp log event search (resolves #343). refactor: Add upperboundBinarySearch and refactor timestamp log event search (resolves #343). Jul 22, 2025
Copy link
Contributor

@quinntaylormitchell quinntaylormitchell left a comment

Choose a reason for hiding this comment

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

PR is looking good Henry; a few high-level comments for you as the code itself looks pretty good:

  1. I would flesh out your “Description” section. I would A) communicate the purpose for the PR, B) say whether or not you changed any logic (or if it’s just a pure refactor), and C) provide a link to issue #343.
  2. In the “Validation performed” section, the link you provided does go directly to a log result, but I’m not sure how that link by itself provides validation for the PR. On the page I see when I follow the link, I don’t see the “query” you’re referring to. I ran the query 2015-03-24T01:09: myself and it successfully returned all 37 of the logs on page 38 that occurred during 1:09am, so maybe you can put that in the validation section (with the link) if you’d like to flesh it out.
  3. I agree with the rabbit re. improving the detail in the docstring for the function, but parameters like lowIdx and highIdx are self-explanatory and don't need explanation.
  4. I kind of agree with the rabbit re. avoiding parameter reassignment, but I also think it’s good that you stuck to the conventions about naming 0-based index variables with the Idx suffix. @kirkrodrigues or @junhaoliao what do you think?

@Henry8192
Copy link
Contributor Author

2. In the “Validation performed” section, the link you provided does go directly to a log result, but I’m not sure how that link by itself provides validation for the PR. On the page I see when I follow the link, I don’t see the “query” you’re referring to. I ran the query 2015-03-24T01:09: myself and it successfully returned all 37 of the logs on page 38 that occurred during 1:09am, so maybe you can put that in the validation section (with the link) if you’d like to flesh it out.

Just to clarify, that link is to query timestamp, which is not related to query strings (that's searching for keywords). By adding #timestamp= in the hash parameters, we are starting a "query" that searches log events by that unix timestamp. If you convert this unix timestamp (in milliseconds) back to normal, you will find out that it matches the exact timestamp on that line.

This link is just a check to prove this existing feature is working. Hope this would clarify your questions.

@Henry8192
Copy link
Contributor Author

  1. C) provide a link to issue Refactor: Extract binary search logic to generic upper_bound_binary_search utility #343.

discussed with @kirkrodrigues and he says it's fine if we mention this in the pr title.

@Henry8192
Copy link
Contributor Author

@quinntaylormitchell thanks for the review! I've reworded the descriptions in math.test.ts according to previous description style, and let me know if they are still confusing.

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: 1

♻️ Duplicate comments (1)
src/utils/math.ts (1)

26-37: Documentation needs completion and clarification.

The JSDoc comment has several issues that were previously identified but haven't been addressed:

  1. The @return section is incomplete - it doesn't describe the actual return value
  2. Parameter descriptions are missing for most parameters
  3. The null return case needs clearer documentation per previous feedback
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 0618c19 and 776298b.

📒 Files selected for processing (3)
  • src/typings/decoders.ts (1 hunks)
  • src/utils/math.ts (2 hunks)
  • test/utils/math.test.ts (3 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{cpp,hpp,java,js,jsx,tpp,ts,tsx}

⚙️ CodeRabbit Configuration File

  • Prefer false == <expression> rather than !<expression>.

Files:

  • src/typings/decoders.ts
  • test/utils/math.test.ts
  • src/utils/math.ts
🧠 Learnings (4)
📓 Common learnings
Learnt from: Henry8192
PR: y-scope/yscope-log-viewer#347
File: src/utils/math.ts:47-56
Timestamp: 2025-07-24T21:28:56.802Z
Learning: In the y-scope/yscope-log-viewer codebase, Henry8192 prefers not to introduce local variables to avoid parameter reassignment in binary search implementations, considering the additional constants unnecessary.
Learnt from: Henry8192
PR: y-scope/yscope-log-viewer#80
File: new-log-viewer/src/services/LogFileManager.ts:297-313
Timestamp: 2024-10-09T02:08:56.053Z
Learning: In the `#searchChunk` method of `LogFileManager.ts`, checking `typeof match.index === "number"` correctly handles matches at index 0, ensuring matches at the beginning of the string are not skipped.
Learnt from: Henry8192
PR: y-scope/yscope-log-viewer#80
File: new-log-viewer/src/services/LogFileManager/index.ts:305-321
Timestamp: 2024-10-10T20:26:31.573Z
Learning: When a function like `#searchChunkAndScheduleNext` requires significant improvements, it's preferred to rewrite the entire function rather than making incremental changes.
src/typings/decoders.ts (1)

Learnt from: Henry8192
PR: #80
File: new-log-viewer/src/services/LogFileManager.ts:297-313
Timestamp: 2024-10-09T02:08:56.053Z
Learning: In the #searchChunk method of LogFileManager.ts, checking typeof match.index === "number" correctly handles matches at index 0, ensuring matches at the beginning of the string are not skipped.

test/utils/math.test.ts (4)

Learnt from: junhaoliao
PR: #115
File: test/utils/math.test.ts:0-0
Timestamp: 2024-11-10T21:25:24.592Z
Learning: The clamp function in src/utils/math.ts correctly handles cases where max is less than min by returning the upper boundary.

Learnt from: Henry8192
PR: #80
File: new-log-viewer/src/services/LogFileManager.ts:297-313
Timestamp: 2024-10-09T02:08:56.053Z
Learning: In the #searchChunk method of LogFileManager.ts, checking typeof match.index === "number" correctly handles matches at index 0, ensuring matches at the beginning of the string are not skipped.

Learnt from: Henry8192
PR: #107
File: src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/Result.tsx:9-12
Timestamp: 2024-10-28T18:39:28.680Z
Learning: In the Result component (Result.tsx), if end < start, the for loop won't run and it will return nothing, so additional validation for matchRange indices is not necessary.

Learnt from: Henry8192
PR: #80
File: new-log-viewer/src/services/LogFileManager/index.ts:305-321
Timestamp: 2024-10-10T20:26:31.573Z
Learning: When a function like #searchChunkAndScheduleNext requires significant improvements, it's preferred to rewrite the entire function rather than making incremental changes.

src/utils/math.ts (10)

Learnt from: junhaoliao
PR: #115
File: test/utils/math.test.ts:0-0
Timestamp: 2024-11-10T21:25:24.592Z
Learning: The clamp function in src/utils/math.ts correctly handles cases where max is less than min by returning the upper boundary.

Learnt from: Henry8192
PR: #347
File: src/utils/math.ts:47-56
Timestamp: 2025-07-24T21:28:56.802Z
Learning: In the y-scope/yscope-log-viewer codebase, Henry8192 prefers not to introduce local variables to avoid parameter reassignment in binary search implementations, considering the additional constants unnecessary.

Learnt from: Henry8192
PR: #80
File: new-log-viewer/src/services/LogFileManager.ts:297-313
Timestamp: 2024-10-09T02:08:56.053Z
Learning: In the #searchChunk method of LogFileManager.ts, checking typeof match.index === "number" correctly handles matches at index 0, ensuring matches at the beginning of the string are not skipped.

Learnt from: Henry8192
PR: #196
File: src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/QueryInputBox.tsx:55-58
Timestamp: 2025-03-03T18:10:02.451Z
Learning: In the y-scope/yscope-log-viewer repository, the team prefers using the ! operator to flip boolean values (e.g., !isRegex) rather than the pattern false == <expression> that's mentioned in the coding guidelines.

Learnt from: junhaoliao
PR: #224
File: src/contexts/states/viewStore.ts:108-109
Timestamp: 2025-05-09T01:07:32.803Z
Learning: For the viewStore in the yscope-log-viewer project, it's more appropriate to suppress the max-lines-per-function ESLint rule than to split the store into slices due to the tight coupling and cohesive nature of its functions.

Learnt from: Henry8192
PR: #152
File: src/components/AppController.tsx:42-42
Timestamp: 2025-07-15T16:29:02.609Z
Learning: In the y-scope/yscope-log-viewer repository, the team never uses == for comparisons, preferring strict equality (===) instead. This overrides the coding guidelines that suggest using false == <expression> rather than !<expression>.

Learnt from: Henry8192
PR: #107
File: src/components/CentralContainer/Sidebar/SidebarTabs/SearchTabPanel/Result.tsx:1-7
Timestamp: 2024-10-28T18:40:29.816Z
Learning: In the y-scope/yscope-log-viewer project, importing components via destructuring from '@mui/joy' is acceptable, as the package size impact has been analyzed and found acceptable.

Learnt from: junhaoliao
PR: #285
File: package-lock.json:33-33
Timestamp: 2025-05-23T15:19:35.710Z
Learning: For the y-scope/yscope-log-viewer and y-scope/clp repositories, the team has decided to lock the "eslint-config-yscope" dependency at a specific version (not "latest" or "*"). When a new version is released, the version string will be updated explicitly in each referencing repository.

Learnt from: junhaoliao
PR: #94
File: new-log-viewer/src/components/CentralContainer/Sidebar/index.tsx:99-118
Timestamp: 2024-10-19T03:33:29.578Z
Learning: In new-log-viewer/src/components/CentralContainer/Sidebar/index.tsx, when using useEffect to register window resize event handlers, it's acceptable to have an empty dependency array because the variables and functions used within the effect (getPanelWidth, PANEL_CLIP_THRESHOLD_IN_PIXELS, and tabListRef) are constants or refs declared outside the functional component and do not change.

Learnt from: Henry8192
PR: #80
File: new-log-viewer/src/services/LogFileManager/index.ts:305-321
Timestamp: 2024-10-10T20:26:31.573Z
Learning: When a function like #searchChunkAndScheduleNext requires significant improvements, it's preferred to rewrite the entire function rather than making incremental changes.

🧬 Code Graph Analysis (2)
test/utils/math.test.ts (1)
src/utils/math.ts (1)
  • upperBoundBinarySearch (70-70)
src/utils/math.ts (1)
src/typings/common.ts (1)
  • Nullable (6-6)
🔇 Additional comments (7)
src/utils/math.ts (4)

1-2: LGTM: Clean import addition.

The import of Nullable type is appropriate for the function's return type annotation.


48-57: Parameter reassignment acknowledged per team preference.

The static analysis correctly identifies parameter reassignment, but based on previous discussion, the team prefers this implementation style for binary search algorithms to avoid unnecessary local variables.


59-65: Logic correctly handles edge case.

The corner case handling for when all values are greater than or equal to upperboundValue is implemented correctly. When highIdx becomes negative after the loop, returning 0 represents the first index as documented.


70-70: Export addition is appropriate.

The function is correctly added to the exports list for external usage.

src/typings/decoders.ts (1)

131-131: Documentation update correctly reflects the interface contract.

The clarification that the method returns "null if there are no log events" properly aligns with the refactored implementation that uses upperBoundBinarySearch, which can return null for invalid ranges or empty collections.

test/utils/math.test.ts (2)

5-5: Import addition is correct.

The import of upperBoundBinarySearch is properly added to the existing import statement.


55-103: Comprehensive test coverage with good edge case handling.

The test suite covers all critical scenarios:

  • Empty collections (both valid and invalid ranges)
  • Single-element arrays
  • Multiple elements with duplicates
  • Boundary conditions (first/last elements)
  • Values that don't exist in the array
  • Upper bound matching unique and duplicate values

The test data [-5, -3, 0, 1, 1, 8, 8, 8, 100] provides good coverage with negative numbers, zero, duplicates, and a large positive value. The assertions correctly verify the expected behaviour of finding the last index where element <= upperboundValue.

Copy link
Contributor

@quinntaylormitchell quinntaylormitchell left a comment

Choose a reason for hiding this comment

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

Looks good Henry - left a comment on a change that I think is outside the scope of the PR, but other than that I think you're good to go.

Copy link
Contributor

@quinntaylormitchell quinntaylormitchell left a comment

Choose a reason for hiding this comment

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

Looks good to me!

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 (3)
src/utils/math.ts (3)

48-56: Fix variable names in the comment; keep parameter reassignment as per project preference.

The inline comment references low/high, but the code uses lowIdx/highIdx.

Apply this diff:

-        // `mid` is guaranteed to be within bounds since `low <= high`.
+        // `mid` is guaranteed to be within bounds since `lowIdx <= highIdx`.

Note: Maintaining parameter reassignment is acceptable here given the stated team preference to avoid introducing local variables for binary search bounds.


44-46: Guard negative lowIdx to align with the documented “invalid indices → null” behaviour.

Currently, lowIdx < 0 proceeds into the loop and may produce a non-null result. Either document minimum preconditions or treat negative lowIdx as invalid.

Apply this diff:

-    if (highIdx < lowIdx || "undefined" === typeof (get(highIdx))) {
+    if (highIdx < lowIdx || 0 > lowIdx || "undefined" === typeof get(highIdx)) {
         return null;
     }

26-37: Fix typos and fully specify the contract (ascending sort, inclusive indices, null semantics).

Clarify the doc to avoid ambiguity and fix upperboundValue casing.

Apply this diff:

 /**
- * Finds the last element in a sorted collection that is less than or equal to `upperboundValue`. If
- * all elements in the collection are greater than `upperboundValue`, returns the first index of the
- * collection (i.e., `0`).
+ * Finds the index of the last element in a sorted (ascending) collection that is less than or equal
+ * to `upperBoundValue`. If all elements in the collection are greater than `upperBoundValue`,
+ * returns the first index of the collection (i.e., `0`).
  *
- * @param get Function to access elements in the collection by index.
- * @param lowIdx
- * @param highIdx
- * @param upperBoundValue
- * @return The index of the last element less than or equal to `upperboundValue`, `0` if all
- * elements are greater, or `null` if the collection is empty or the indices are invalid.
+ * @param get Function to access elements in the collection by index. Must return `undefined` for
+ *            out-of-range indices.
+ * @param lowIdx The starting index (inclusive) of the search range.
+ * @param highIdx The ending index (inclusive) of the search range.
+ * @param upperBoundValue The value to search for.
+ * @return The index of the last element <= `upperBoundValue`, `0` if all elements are greater,
+ *         or `null` if the collection is empty or the indices are invalid.
  */
📜 Review details

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

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these settings in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between a9b520c and cf142d7.

📒 Files selected for processing (1)
  • src/utils/math.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{cpp,hpp,java,js,jsx,tpp,ts,tsx}

⚙️ CodeRabbit Configuration File

  • Prefer false == <expression> rather than !<expression>.

Files:

  • src/utils/math.ts
🧠 Learnings (3)
📓 Common learnings
Learnt from: Henry8192
PR: y-scope/yscope-log-viewer#347
File: src/utils/math.ts:47-56
Timestamp: 2025-07-24T21:28:56.838Z
Learning: In the y-scope/yscope-log-viewer codebase, Henry8192 prefers not to introduce local variables to avoid parameter reassignment in binary search implementations, considering the additional constants unnecessary.
📚 Learning: 2024-11-10T21:25:24.592Z
Learnt from: junhaoliao
PR: y-scope/yscope-log-viewer#115
File: test/utils/math.test.ts:0-0
Timestamp: 2024-11-10T21:25:24.592Z
Learning: The `clamp` function in `src/utils/math.ts` correctly handles cases where `max` is less than `min` by returning the upper boundary.

Applied to files:

  • src/utils/math.ts
📚 Learning: 2025-07-24T21:28:56.838Z
Learnt from: Henry8192
PR: y-scope/yscope-log-viewer#347
File: src/utils/math.ts:47-56
Timestamp: 2025-07-24T21:28:56.838Z
Learning: In the y-scope/yscope-log-viewer codebase, Henry8192 prefers not to introduce local variables to avoid parameter reassignment in binary search implementations, considering the additional constants unnecessary.

Applied to files:

  • src/utils/math.ts
🧬 Code Graph Analysis (1)
src/utils/math.ts (1)
src/typings/common.ts (1)
  • Nullable (6-6)
🔇 Additional comments (2)
src/utils/math.ts (2)

1-1: LGTM: correct Nullable import location.

Import path and type usage look right for this codebase.


70-70: LGTM: export added in the public utils surface.

Good to see this utility reusable elsewhere.

Comment on lines +38 to +43
const upperBoundBinarySearch = <T>(
get: (index: number) => T,
lowIdx: number,
highIdx: number,
upperBoundValue: T,
): Nullable<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

Type-safety: constrain T or accept a comparator to ensure valid <= comparisons.

get(mid) <= upperBoundValue assumes T is comparable with <=. To make this safe and self-documenting, either constrain T to a comparable type (e.g., number) or accept a comparator.

Minimal change (matches current usage with timestamps as numbers):

-const upperBoundBinarySearch = <T>(
+const upperBoundBinarySearch = <T extends number>(
     get: (index: number) => T,
     lowIdx: number,
     highIdx: number,
     upperBoundValue: T,
 ): Nullable<number> => {

Alternatively (if you want generic support), introduce a comparator:

const upperBoundBinarySearchBy = <T>(
  get: (index: number) => T,
  lowIdx: number,
  highIdx: number,
  upperBoundValue: T,
  cmp: (a: T, b: T) => number,
): Nullable<number> => {
  if (highIdx < lowIdx || "undefined" === typeof get(highIdx)) return null;
  while (lowIdx <= highIdx) {
    const mid = Math.floor((lowIdx + highIdx) / 2);
    if (cmp(get(mid), upperBoundValue) <= 0) lowIdx = mid + 1;
    else highIdx = mid - 1;
  }
  return 0 > highIdx ? 0 : highIdx;
};
🤖 Prompt for AI Agents
In src/utils/math.ts around lines 38 to 43, the binary search uses get(mid) <=
upperBoundValue on a generic T which is unsafe; either constrain T to a
comparable type (e.g., change signature to <T extends number>) so numeric
comparisons are type-safe, or add a comparator argument (e.g., cmp: (a: T, b: T)
=> number), replace the <= check with cmp(get(mid), upperBoundValue) <= 0, and
update the return logic and all callers accordingly (or expose a new
upperBoundBinarySearchBy name if you want to keep the old API).

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: 1

♻️ Duplicate comments (3)
src/utils/math.ts (3)

60-63: Typo: “upperboundValue” → “upperBoundValue”.

Keeps terminology consistent with the parameter name and docstring.

Apply this diff:

-    // corner case: all values >= upperboundValue
+    // Corner case: all values >= upperBoundValue

26-36: Docstring: correct inaccuracies and clarify contract.

The function no longer accepts index bounds, so “indices are invalid” is misleading. Also clarify sort order, the return semantics, and parameter naming to match the implementation.

Apply this diff:

 /**
- * Finds the last element in a sorted collection that is less than or equal to `upperBoundValue`. If
- * all elements in the collection are greater than `upperBoundValue`, returns the first index of the
- * collection (i.e., `0`).
+ * Finds the index of the last element in a sorted collection whose mapped value is less than
+ * or equal to `upperBoundValue`. If all elements are greater than `upperBoundValue`, returns
+ * the first index of the collection (i.e., `0`). Returns `null` if the collection is empty.
+ *
+ * Assumes the array is sorted in ascending order by the value returned from `valueFn`.
  *
- * @param get Function to access an element (or its property) in the collection.
- * @param array
- * @param upperBoundValue
- * @return The index of the last element less than or equal to `upperBoundValue`, `0` if all
- * elements are greater, or `null` if the collection is empty or the indices are invalid.
+ * @param valueFn Function that maps an element to a comparable number.
+ * @param array The sorted collection to search.
+ * @param upperBoundValue The numeric target value for the upper-bound search.
+ * @return The index of the last element <= `upperBoundValue`, `0` if all elements are greater,
+ * or `null` if the collection is empty.
  */

37-41: Type-safety: constrain the comparable type to number (or accept a comparator).

<= on a generic T is not type-safe and will fail type-checking unless T is constrained. Given current usage (timestamps), constraining T to number is simplest and self-documenting.

Apply this diff:

-const upperBoundBinarySearch = <T, U>(
-    get: (logEvent: U) => T,
+const upperBoundBinarySearch = <T extends number, U>(
+    valueFn: (item: U) => T,
     array: U[],
     upperBoundValue: T,
 ): Nullable<number> => {

And update the usage inside the function:

-        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-        if (get(array[mid]!) <= upperBoundValue) {
+        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+        if (valueFn(array[mid]!) <= upperBoundValue) {
📜 Review details

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

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these settings in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between cf142d7 and ae6ae6f.

📒 Files selected for processing (2)
  • src/services/decoders/JsonlDecoder/index.ts (2 hunks)
  • src/utils/math.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{cpp,hpp,java,js,jsx,tpp,ts,tsx}

⚙️ CodeRabbit Configuration File

  • Prefer false == <expression> rather than !<expression>.

Files:

  • src/services/decoders/JsonlDecoder/index.ts
  • src/utils/math.ts
🧠 Learnings (2)
📚 Learning: 2024-11-10T21:25:24.592Z
Learnt from: junhaoliao
PR: y-scope/yscope-log-viewer#115
File: test/utils/math.test.ts:0-0
Timestamp: 2024-11-10T21:25:24.592Z
Learning: The `clamp` function in `src/utils/math.ts` correctly handles cases where `max` is less than `min` by returning the upper boundary.

Applied to files:

  • src/utils/math.ts
📚 Learning: 2025-07-24T21:28:56.838Z
Learnt from: Henry8192
PR: y-scope/yscope-log-viewer#347
File: src/utils/math.ts:47-56
Timestamp: 2025-07-24T21:28:56.838Z
Learning: In the y-scope/yscope-log-viewer codebase, Henry8192 prefers not to introduce local variables to avoid parameter reassignment in binary search implementations, considering the additional constants unnecessary.

Applied to files:

  • src/utils/math.ts
🧬 Code Graph Analysis (2)
src/services/decoders/JsonlDecoder/index.ts (1)
src/utils/math.ts (1)
  • upperBoundBinarySearch (71-71)
src/utils/math.ts (1)
src/typings/common.ts (1)
  • Nullable (6-6)
🔇 Additional comments (4)
src/services/decoders/JsonlDecoder/index.ts (2)

21-21: Import looks good; reuse over reimplementation.

Pulling in the shared utility reduces duplication and centralizes the search logic.


142-148: Refactor to shared binary search is correct and maintains semantics.

Passing a numeric timestamp extractor and delegating to the utility matches the intended “last ≤ target, else 0; null if empty” behaviour.

src/utils/math.ts (2)

1-1: Import of Nullable is correct.

Matches usage and keeps types explicit.


68-72: Explicitly exporting the new utility is good.

Keeps the module’s public surface clear and consistent.

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
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: 1

♻️ Duplicate comments (2)
src/utils/math.ts (2)

60-63: Fix typo in comment: “upperboundValue” → “upperBoundValue”.

Keeps naming consistent with the parameter.

-    // corner case: all values >= upperboundValue
+    // corner case: all values >= upperBoundValue

37-41: Constrain generic T to number (or add a comparator) — <= on unconstrained T is unsafe

Using <= on an unconstrained generic T will fail under strict TS (TS2365). I verified the call sites — they all use numeric values — so the minimal, safe fix is to constrain T to number.

Files to update / check:

  • src/utils/math.ts — function signature (around lines 37–41)
  • src/services/decoders/JsonlDecoder/index.ts — call uses timestamp.valueOf() (lines ~143–146)
  • test/utils/math.test.ts — tests use numeric arrays (lines ~60–102)

Suggested minimal change:

-const upperBoundBinarySearch = <T, U>(
+const upperBoundBinarySearch = <T extends number, U>(
     get: (logEvent: U) => T,
     array: U[],
     upperBoundValue: T,
 ): Nullable<number> => {

If you need generic support for non-numeric types, add a comparator-based variant (e.g. upperBoundBinarySearchBy) instead of using the <= operator directly.

📜 Review details

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

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these settings in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between ae6ae6f and 7b42016.

📒 Files selected for processing (1)
  • src/utils/math.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{cpp,hpp,java,js,jsx,tpp,ts,tsx}

⚙️ CodeRabbit Configuration File

  • Prefer false == <expression> rather than !<expression>.

Files:

  • src/utils/math.ts
🧠 Learnings (2)
📚 Learning: 2025-07-24T21:28:56.838Z
Learnt from: Henry8192
PR: y-scope/yscope-log-viewer#347
File: src/utils/math.ts:47-56
Timestamp: 2025-07-24T21:28:56.838Z
Learning: In the y-scope/yscope-log-viewer codebase, Henry8192 prefers not to introduce local variables to avoid parameter reassignment in binary search implementations, considering the additional constants unnecessary.

Applied to files:

  • src/utils/math.ts
📚 Learning: 2024-11-10T21:25:24.592Z
Learnt from: junhaoliao
PR: y-scope/yscope-log-viewer#115
File: test/utils/math.test.ts:0-0
Timestamp: 2024-11-10T21:25:24.592Z
Learning: The `clamp` function in `src/utils/math.ts` correctly handles cases where `max` is less than `min` by returning the upper boundary.

Applied to files:

  • src/utils/math.ts
🧬 Code Graph Analysis (1)
src/utils/math.ts (1)
src/typings/common.ts (1)
  • Nullable (6-6)
🔇 Additional comments (4)
src/utils/math.ts (4)

48-58: Binary-search core looks correct and handles duplicates (upper bound) properly.

The loop invariant and updates correctly implement the upper bound (last index <= target). Good use of mid calculation and boundary updates.


1-1: Import is correct and consistent with local Nullable usage.

No issues found.


71-71: Export looks good.

Utility is properly exposed alongside existing exports.


41-46: upperBoundBinarySearch — docstring already documents the whole-collection return behaviour

The function's docstring already states that it returns 0 when all elements are greater than upperBoundValue and the implementation has no sub-range parameters, so no change is strictly required. If you'd like an explicit sentence saying "this searches the entire collection (no sub-range parameters) and returning 0 is by design", I can add it.

  • File: src/utils/math.ts — upperBoundBinarySearch docstring (above the function) — currently documents the "returns 0" behaviour.

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 (3)
src/utils/math.ts (3)

37-41: Type-safety: constrain to numbers or accept a comparator

Using <= on generic T is not type-safe. Since current usage maps to numbers (timestamps), either constrain to numbers or introduce a comparator.

Option A (minimal, matches current usage):

-const upperBoundBinarySearch = <T, U>(
-    get: (arrayElement: U) => T,
+const upperBoundBinarySearch = <U>(
+    get: (arrayElement: U) => number,
     array: U[],
-    upperBoundValue: T,
+    upperBoundValue: number,
 ): Nullable<number> => {

Option B (generic comparator-based variant):

const upperBoundBinarySearchBy = <T, U>(
  get: (arrayElement: U) => T,
  array: U[],
  upperBoundValue: T,
  cmp: (a: T, b: T) => number,
): Nullable<number> => {
  if (0 === array.length) return null;
  let lowIdx = 0;
  let highIdx = array.length - 1;
  while (lowIdx <= highIdx) {
    const mid = Math.floor((lowIdx + highIdx) / 2);
    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
    if (cmp(get(array[mid]!), upperBoundValue) <= 0) lowIdx = mid + 1;
    else highIdx = mid - 1;
  }
  return 0 > highIdx ? 0 : highIdx;
};

60-63: Fix comment casing: “upperBoundValue”

Minor nit for consistency.

-    // corner case: all values >= upperboundValue
+    // Corner case: all values >= upperBoundValue

26-36: Update JSDoc: remove obsolete “indices” wording; clarify contract and precondition

The docstring predates the simplified API (no low/high indices) and still references “indices are invalid”. Also, parameter descriptions are sparse and the return description can be clearer.

Apply this diff to tighten the documentation:

 /**
- * Finds the last element in a sorted collection that is less than or equal to `upperBoundValue`. If
- * all elements in the collection are greater than `upperBoundValue`, returns the first index of the
- * collection (i.e., `0`).
+ * Finds the index of the last element in a sorted collection whose mapped value is less than or
+ * equal to `upperBoundValue`. If all elements in the collection are greater than `upperBoundValue`,
+ * returns the first index of the collection (i.e., `0`).
+ *
+ * Precondition: `array` is sorted in non-decreasing order by the value returned from `get`.
  *
- * @param get Function to access an element (or its property) in the collection.
- * @param array
- * @param upperBoundValue
- * @return The index of the last element less than or equal to `upperBoundValue`, `0` if all
- * elements are greater, or `null` if the collection is empty or the indices are invalid.
+ * @param get Function that maps an element to a comparable value used for ordering.
+ * @param array The sorted collection to search.
+ * @param upperBoundValue The target value to upper-bound against.
+ * @return The index of the last element <= `upperBoundValue`, `0` if all elements are greater,
+ * or `null` if the collection is empty.
  */
📜 Review details

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

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these settings in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 7b42016 and 625ac0e.

📒 Files selected for processing (2)
  • src/utils/math.ts (2 hunks)
  • test/utils/math.test.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{cpp,hpp,java,js,jsx,tpp,ts,tsx}

⚙️ CodeRabbit Configuration File

  • Prefer false == <expression> rather than !<expression>.

Files:

  • test/utils/math.test.ts
  • src/utils/math.ts
🧠 Learnings (2)
📚 Learning: 2024-11-10T21:25:24.592Z
Learnt from: junhaoliao
PR: y-scope/yscope-log-viewer#115
File: test/utils/math.test.ts:0-0
Timestamp: 2024-11-10T21:25:24.592Z
Learning: The `clamp` function in `src/utils/math.ts` correctly handles cases where `max` is less than `min` by returning the upper boundary.

Applied to files:

  • test/utils/math.test.ts
  • src/utils/math.ts
📚 Learning: 2025-07-24T21:28:56.838Z
Learnt from: Henry8192
PR: y-scope/yscope-log-viewer#347
File: src/utils/math.ts:47-56
Timestamp: 2025-07-24T21:28:56.838Z
Learning: In the y-scope/yscope-log-viewer codebase, Henry8192 prefers not to introduce local variables to avoid parameter reassignment in binary search implementations, considering the additional constants unnecessary.

Applied to files:

  • src/utils/math.ts
🧬 Code Graph Analysis (1)
src/utils/math.ts (1)
src/typings/common.ts (1)
  • Nullable (6-6)
🔇 Additional comments (2)
test/utils/math.test.ts (1)

55-113: Solid coverage and edge-case handling for upperBoundBinarySearch

Good breadth of cases: empty arrays, single-element arrays, boundaries, non-matches, and duplicate-elements returning the last match. Assertions align with the intended “upper bound” semantics.

src/utils/math.ts (1)

45-66: Binary search implementation looks correct and efficient

Clear loop invariants, correct mid calculation, and correct return semantics for “upper bound” (last index <= target; 0 when all are greater). The non-null assertion is justified by the bounds check.

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: 1

♻️ Duplicate comments (5)
src/utils/math.ts (3)

37-41: Constrain the comparable type (or accept a comparator) to make <= type-safe

Using <= on a generic T relies on inference and can accept unintended types. Two options:

  • Minimal: constrain T to number (current usage with timestamps).
  • Generic-friendly: add a comparator.

Minimal change:

-const upperBoundBinarySearch = <T, U>(
+const upperBoundBinarySearch = <T extends number, U>(
     array: U[],
     upperBoundValue: T,
     get: (arrayElement: U) => T,
 ): Nullable<number> => {

If you want broader support, I can provide a upperBoundBinarySearchBy variant with a comparator without breaking this API.


60-63: Fix casing in the inline comment: upperBoundValue

Typo in the comment.

-    // corner case: all values >= upperboundValue
+    // corner case: all values >= upperBoundValue

26-36: Doc precision: say “index of the last element” and call out non-decreasing order

Small clarity tweaks:

  • The function returns an index, so the lead sentence should say “Finds the index of the last element…”.
  • Since duplicates are supported, “non-decreasing order” is clearer than “ascending”.

Apply this diff to the JSDoc:

- * Finds the last element in a sorted collection that is less than or equal to `upperBoundValue`. If
- * all elements in the collection are greater than `upperBoundValue`, returns the first index of the
- * collection (i.e., `0`).
+ * Finds the index of the last element in a sorted collection that is less than or equal to
+ * `upperBoundValue`. If all elements in the collection are greater than `upperBoundValue`,
+ * returns the first index of the collection (i.e., `0`).
@@
- * @param array The entire collection in ascending order.
+ * @param array The entire collection in non-decreasing (ascending with duplicates) order.
test/utils/math.test.ts (2)

59-66: Remove duplicate empty-array test and reword name (indices no longer apply)

Both tests assert the same thing and reference low/high indices that the function no longer accepts. Keep a single, accurate test name.

-    test("returns null if collection is empty with lowIdx < highIdx", () => {
+    test("returns null for empty array", () => {
         const result = upperBoundBinarySearch(emptyArray, 5, (arrayElement) => arrayElement);
         expect(result).toBeNull();
     });
-    test("returns null if collection is empty with highIdx < lowIdx", () => {
-        const result = upperBoundBinarySearch(emptyArray, 5, (arrayElement) => arrayElement);
-        expect(result).toBeNull();
-    });

67-111: Unify casing in test descriptions: “upperBoundValue”

Align test descriptions with implementation/JSDoc.

-    test("returns the first index if upperboundValue < all elements in array", () => {
+    test("returns the first index if upperBoundValue < all elements in array", () => {
@@
-    test("returns the first index if upperboundValue < element in singleElementArray", () => {
+    test("returns the first index if upperBoundValue < element in singleElementArray", () => {
@@
-    test("returns the last index if upperboundValue > all elements in array", () => {
+    test("returns the last index if upperBoundValue > all elements in array", () => {
@@
-    test("returns the last index if upperboundValue > element in singleElementArray", () => {
+    test("returns the last index if upperBoundValue > element in singleElementArray", () => {
@@
-    test("returns the correct index if upperboundValue doesn't match any elements", () => {
+    test("returns the correct index if upperBoundValue doesn't match any elements", () => {
@@
-    test("returns the correct index if upperboundValue matches a unique element", () => {
+    test("returns the correct index if upperBoundValue matches a unique element", () => {
@@
-    test("returns the last matched index if upperboundValue matches a duplicated element", () => {
+    test("returns the last matched index if upperBoundValue matches a duplicated element", () => {
@@
-    test("returns index 0 if upperboundValue matches the first element", () => {
+    test("returns index 0 if upperBoundValue matches the first element", () => {
@@
-    test("returns the last index if upperboundValue matches the last element", () => {
+    test("returns the last index if upperBoundValue matches the last element", () => {
📜 Review details

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

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these settings in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 625ac0e and 5c5d23b.

📒 Files selected for processing (3)
  • src/services/decoders/JsonlDecoder/index.ts (2 hunks)
  • src/utils/math.ts (2 hunks)
  • test/utils/math.test.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{cpp,hpp,java,js,jsx,tpp,ts,tsx}

⚙️ CodeRabbit Configuration File

  • Prefer false == <expression> rather than !<expression>.

Files:

  • src/utils/math.ts
  • src/services/decoders/JsonlDecoder/index.ts
  • test/utils/math.test.ts
🧠 Learnings (2)
📚 Learning: 2024-11-10T21:25:24.592Z
Learnt from: junhaoliao
PR: y-scope/yscope-log-viewer#115
File: test/utils/math.test.ts:0-0
Timestamp: 2024-11-10T21:25:24.592Z
Learning: The `clamp` function in `src/utils/math.ts` correctly handles cases where `max` is less than `min` by returning the upper boundary.

Applied to files:

  • src/utils/math.ts
  • test/utils/math.test.ts
📚 Learning: 2025-07-24T21:28:56.838Z
Learnt from: Henry8192
PR: y-scope/yscope-log-viewer#347
File: src/utils/math.ts:47-56
Timestamp: 2025-07-24T21:28:56.838Z
Learning: In the y-scope/yscope-log-viewer codebase, Henry8192 prefers not to introduce local variables to avoid parameter reassignment in binary search implementations, considering the additional constants unnecessary.

Applied to files:

  • src/utils/math.ts
🧬 Code Graph Analysis (3)
src/utils/math.ts (1)
src/typings/common.ts (1)
  • Nullable (6-6)
src/services/decoders/JsonlDecoder/index.ts (1)
src/utils/math.ts (1)
  • upperBoundBinarySearch (71-71)
test/utils/math.test.ts (1)
src/utils/math.ts (1)
  • upperBoundBinarySearch (71-71)
🔇 Additional comments (1)
src/utils/math.ts (1)

42-46: LGTM: explicit empty-collection handling

Returning null for an empty array aligns with the JSDoc and makes the contract unambiguous.

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)
test/utils/math.test.ts (1)

63-63: Fix casing in test descriptions: use “upperBoundValue”

Keep test names consistent with the API and JSDoc. This was previously suggested and appears still pending here.

-    test("returns the first index if upperboundValue < all elements in array", () => {
+    test("returns the first index if upperBoundValue < all elements in array", () => {
-    test("returns the first index if upperboundValue < element in singleElementArray", () => {
+    test("returns the first index if upperBoundValue < element in singleElementArray", () => {
-    test("returns the last index if upperboundValue > all elements in array", () => {
+    test("returns the last index if upperBoundValue > all elements in array", () => {
-    test("returns the last index if upperboundValue > element in singleElementArray", () => {
+    test("returns the last index if upperBoundValue > element in singleElementArray", () => {
-    test("returns the correct index if upperboundValue doesn't match any elements", () => {
+    test("returns the correct index if upperBoundValue doesn't match any elements", () => {
-    test("returns the correct index if upperboundValue matches a unique element", () => {
+    test("returns the correct index if upperBoundValue matches a unique element", () => {
-    test("returns the last matched index if upperboundValue matches a duplicated element", () => {
+    test("returns the last matched index if upperBoundValue matches a duplicated element", () => {
-    test("returns index 0 if upperboundValue matches the first element", () => {
+    test("returns index 0 if upperBoundValue matches the first element", () => {
-    test("returns the last index if upperboundValue matches the last element", () => {
+    test("returns the last index if upperBoundValue matches the last element", () => {

Also applies to: 67-67, 76-76, 80-80, 89-89, 93-93, 97-97, 101-101, 105-105

📜 Review details

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

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 5c5d23b and 682d6b2.

📒 Files selected for processing (1)
  • test/utils/math.test.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{cpp,hpp,java,js,jsx,tpp,ts,tsx}

⚙️ CodeRabbit Configuration File

  • Prefer false == <expression> rather than !<expression>.

Files:

  • test/utils/math.test.ts
🧠 Learnings (1)
📚 Learning: 2024-11-10T21:25:24.592Z
Learnt from: junhaoliao
PR: y-scope/yscope-log-viewer#115
File: test/utils/math.test.ts:0-0
Timestamp: 2024-11-10T21:25:24.592Z
Learning: The `clamp` function in `src/utils/math.ts` correctly handles cases where `max` is less than `min` by returning the upper boundary.

Applied to files:

  • test/utils/math.test.ts
🧬 Code Graph Analysis (1)
test/utils/math.test.ts (1)
src/utils/math.ts (1)
  • upperBoundBinarySearch (71-71)
🔇 Additional comments (1)
test/utils/math.test.ts (1)

5-5: LGTM: Correctly importing upperBoundBinarySearch

Import path and named export usage look correct.

Copy link
Member

@kirkrodrigues kirkrodrigues left a 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: Extract upper-bound binary search from `findNearestLogEventByTimestamp` into a function (resolves #343).

@Henry8192 Henry8192 changed the title refactor: Add upperboundBinarySearch and refactor timestamp log event search (resolves #343). refactor: Extract upper-bound binary search from findNearestLogEventByTimestamp into a function (resolves #343). Aug 21, 2025
@Henry8192 Henry8192 merged commit 61bd41f into y-scope:main Aug 21, 2025
4 checks passed
@Henry8192 Henry8192 deleted the extract-binary-search branch August 21, 2025 23:31
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.

3 participants