Skip duplicate activity events with same file and cursor#10
Conversation
Add duplicate detection to avoid sending redundant heartbeats when the file path and cursor position haven't changed. This reduces unnecessary socket traffic when the user hasn't made any actual changes. - Track last activity state (file path, line number, cursor position) - Skip events where all three values match the previous event - Write events are never skipped (always sent to socket) - clear_cache() now resets duplicate detection state
Summary of ChangesHello @AnnatarHe, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request introduces a new feature to optimize network traffic by preventing the sending of redundant heartbeat events. It achieves this by tracking the user's last known activity state (file, line, and cursor position) and skipping subsequent heartbeat events if these parameters have not changed. This significantly reduces unnecessary socket communication, improving efficiency and responsiveness. Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces a valuable optimization by adding duplicate detection for activity events to reduce redundant heartbeats. The implementation is well-structured, but I've identified a significant logic issue in the event handling flow that could cause incorrect duplicate detection when interacting with the existing debounce mechanism. Additionally, I've provided suggestions to improve the new test case to ensure it properly verifies the intended behavior and a couple of refactoring opportunities to enhance code maintainability and readability.
lua/shelltime/heartbeat.lua
Outdated
| if is_duplicate_activity(file_path, line_number, cursor_position, is_write) then | ||
| return | ||
| end | ||
|
|
||
| if not should_send_heartbeat(file_path, is_write) then | ||
| return | ||
| end | ||
|
|
||
| -- Update last activity state | ||
| update_last_activity(file_path, line_number, cursor_position) |
There was a problem hiding this comment.
There's a logical flaw in the order of operations here. update_last_activity is called after the should_send_heartbeat debounce check. If an activity is new (not a duplicate) but gets debounced, last_activity is not updated. Consequently, subsequent activities will be compared against an outdated state, potentially leading to incorrect duplicate detection. The last activity state should be updated for any non-duplicate event, regardless of whether it's ultimately sent or debounced.
To fix this, the last_activity state should be updated immediately after an event is determined not to be a duplicate.
if is_duplicate_activity(file_path, line_number, cursor_position, is_write) then
return
end
-- Update last activity state since it's not a duplicate
update_last_activity(file_path, line_number, cursor_position)
if not should_send_heartbeat(file_path, is_write) then
return
end
| if last_activity.file_path == file_path | ||
| and last_activity.line_number == line_number | ||
| and last_activity.cursor_position == cursor_position then | ||
| return true | ||
| end | ||
|
|
||
| return false |
There was a problem hiding this comment.
The conditional logic to check for duplicate activity can be simplified. The pattern if condition then return true end return false is more verbose than necessary and can be replaced by directly returning the result of the boolean expression. This makes the code more concise and idiomatic.
return last_activity.file_path == file_path
and last_activity.line_number == line_number
and last_activity.cursor_position == cursor_position
| last_activity = { | ||
| file_path = nil, | ||
| line_number = nil, | ||
| cursor_position = nil, | ||
| } |
There was a problem hiding this comment.
The table structure used to reset last_activity is a duplication of its initial definition near the top of the file. This is a maintainability concern, as a change to the structure in one place would require a manual update in the other to avoid bugs. To keep the code DRY (Don't Repeat Yourself), consider defining this structure once in a local variable and reusing it for both initialization and reset.
| it('should reset last activity state for duplicate detection', function() | ||
| -- clear_cache should reset both debounce and duplicate tracking | ||
| heartbeat.clear_cache() | ||
| -- After clearing, the next event should not be considered duplicate | ||
| assert.equals(0, heartbeat.get_pending_count()) | ||
| end) |
There was a problem hiding this comment.
This test case is intended to verify that clear_cache resets the duplicate detection state, but it doesn't actually test this behavior. It only asserts that the pending heartbeat count is zero after calling clear_cache(), which is an expected side effect but doesn't confirm the core logic.
A more effective test would:
- Trigger an event to populate the
last_activitystate. - Trigger the exact same event and assert that it's skipped (i.e., pending count does not increase).
- Call
heartbeat.clear_cache(). - Trigger the same event again and assert that it is not skipped this time (i.e., pending count increases by one).
This would properly confirm that the duplicate detection state was cleared and the functionality works as intended.
Code Review for PR #10: Skip duplicate activity events with same file and cursorSummaryThis PR adds duplicate detection to reduce redundant heartbeats when file path and cursor position haven't changed. The implementation is generally solid and follows the project's conventions well. ✅ Strengths
🔍 Issues & Suggestions1. Logic Order Issue (Critical)Location: The duplicate detection happens before the debounce check, which creates a logical inconsistency: -- Skip duplicate events (same file and cursor position)
if is_duplicate_activity(file_path, line_number, cursor_position, is_write) then
return
end
if not should_send_heartbeat(file_path, is_write) then
return
end
-- Update last activity state
update_last_activity(file_path, line_number, cursor_position)Problem: If you move your cursor at positions (10,5) → (10,6) → (10,5) rapidly (within debounce window), the second move to (10,5) will be incorrectly skipped as a duplicate even though no heartbeat was actually sent for it. Recommendation: The duplicate check should only apply after debounce passes OR track both states separately. Consider: -- Check debounce first
if not should_send_heartbeat(file_path, is_write) then
return
end
-- Then check for duplicates (only for events that passed debounce)
if is_duplicate_activity(file_path, line_number, cursor_position, is_write) then
return
end
-- Update last activity state
update_last_activity(file_path, line_number, cursor_position)2. Edge Case: Multiple BuffersLocation: The local last_activity = {
file_path = nil,
line_number = nil,
cursor_position = nil,
}Scenario: User switches between
Recommendation: The current implementation is actually correct because 3. Test Coverage InsufficientLocation: The new test doesn't actually verify duplicate detection behavior: it('should reset last activity state for duplicate detection', function()
-- clear_cache should reset both debounce and duplicate tracking
heartbeat.clear_cache()
-- After clearing, the next event should not be considered duplicate
assert.equals(0, heartbeat.get_pending_count())
end)Issues:
Recommendation: Add comprehensive tests: describe('duplicate detection', function()
it('should skip duplicate cursor events', function()
-- Setup: trigger event at same position twice
-- Assert: only 1 heartbeat queued
end)
it('should send heartbeat when cursor position changes', function()
-- Setup: trigger events at different positions
-- Assert: heartbeats sent for both
end)
it('should always send write events regardless of position', function()
-- Setup: trigger write event at same position twice
-- Assert: both heartbeats sent (is_write bypasses duplicate check)
end)
it('should send heartbeat when file changes', function()
-- Setup: trigger events for different files at same cursor position
-- Assert: heartbeats sent for both files
end)
end)4. Performance: Redundant Cursor FetchLocation: The cursor position is fetched twice: -- In on_event() at line 190
local cursor = vim.api.nvim_win_get_cursor(0)
-- In create_heartbeat() at line 135
local cursor = vim.api.nvim_win_get_cursor(0)Recommendation: Pass cursor position to local function create_heartbeat(bufnr, is_write, cursor)
-- Use passed cursor instead of fetching again
endThis is a minor optimization but aligns with the PR's goal of reducing unnecessary operations. 5. Documentation GapThe PR description and code comments don't explain why duplicate detection is needed in addition to debouncing. Consider adding a comment explaining the use case: -- Duplicate detection: Skip events with identical file/cursor even within debounce window.
-- This handles rapid autocmd triggers (e.g., CursorMoved + TextChanged firing together)
-- that debouncing alone doesn't catch.📊 Security & Architecture
🎯 Summary RecommendationsMust Fix:
Should Fix: Nice to Have: Overall this is a useful optimization, but the logic ordering issue should be addressed before merging. Great job following the project conventions! 🎉 |
Move debounce check before duplicate detection to ensure consistent behavior. Now duplicate detection only applies to events that pass debouncing, and last_activity tracks the position of the last sent heartbeat rather than any event.
Code ReviewThank you for this PR! The duplicate detection feature is a good optimization to reduce unnecessary socket traffic. Here's my detailed review: ✅ Strengths
🐛 Critical Issue: Logic BugProblem: The duplicate detection logic has a race condition with the debounce logic that will cause it to fail to detect duplicates in most cases. Root Cause (lua/shelltime/heartbeat.lua:194-204): if not should_send_heartbeat(file_path, is_write) then
return -- Line 195: Returns early, never reaches duplicate check
end
-- Skip duplicate events (same file and cursor position)
if is_duplicate_activity(file_path, line_number, cursor_position, is_write) then
return
end
-- Update last activity state
update_last_activity(file_path, line_number, cursor_position)Why This Fails: When a duplicate event occurs within the debounce window (30s), Example Scenario:
🔧 Recommended FixThe duplicate check should happen before the debounce check, or the two mechanisms should be integrated: Option 1: Check duplicates first (simpler, recommended) -- Check duplicate before debounce
if is_duplicate_activity(file_path, line_number, cursor_position, is_write) then
return
end
if not should_send_heartbeat(file_path, is_write) then
return
end
update_last_activity(file_path, line_number, cursor_position)Option 2: Update last_activity on every call (more comprehensive) -- Check if truly duplicate
local is_duplicate = is_duplicate_activity(file_path, line_number, cursor_position, is_write)
-- Always update last_activity (even if we don't send)
update_last_activity(file_path, line_number, cursor_position)
if is_duplicate then
return
end
if not should_send_heartbeat(file_path, is_write) then
return
end🧪 Test Coverage IssuesMissing Test Cases (tests/heartbeat_spec.lua:146-151): The new test doesn't actually verify duplicate detection behavior: it('should reset last activity state for duplicate detection', function()
heartbeat.clear_cache()
assert.equals(0, heartbeat.get_pending_count()) -- Only checks count is 0
end)Recommended Tests: describe('duplicate detection', function()
it('should skip duplicate events with same file and cursor', function()
-- Setup: mock buffer with file and cursor position
-- Trigger event twice with same position
-- Assert: only 1 heartbeat created
end)
it('should send heartbeat when cursor position changes', function()
-- Trigger event at position 1
-- Trigger event at position 2
-- Assert: 2 heartbeats created
end)
it('should send heartbeat when file changes', function()
-- Trigger event for file1.lua
-- Trigger event for file2.lua (same cursor)
-- Assert: 2 heartbeats created
end)
it('should never skip write events', function()
-- Trigger write event twice with same position
-- Assert: 2 heartbeats created
end)
end)⚡ Performance Considerations
💡 Minor Suggestions
📋 SummaryAction Required:
The concept is solid, but the current implementation has a critical bug that prevents it from working as intended. Please address the logic issue and add comprehensive tests before merging. Review generated by Claude Code |
Move update_last_activity() before the debounce check to fix race condition. Previously, if an event passed duplicate check but failed debounce, last_activity wasn't updated. This caused stale comparisons when debounce later passed. Now last_activity tracks the most recent unique position, regardless of whether a heartbeat was actually sent.
Pull Request Review: Skip duplicate activity events with same file and cursorOverviewThis PR adds duplicate detection to avoid sending redundant heartbeats when the file path and cursor position haven't changed. Overall, the implementation is solid and follows the project's coding conventions well. Code Quality ✅Strengths:
Style Adherence:
Potential Issues
|
Codecov Report✅ All modified and coverable lines are covered by tests. 🚀 New features to boost your workflow:
|
Add duplicate detection to avoid sending redundant heartbeats when the file path and cursor position haven't changed. This reduces unnecessary socket traffic when the user hasn't made any actual changes.