Skip to content

refactor: modernize wave typing hook with shared websocket#1515

Merged
simo6529 merged 4 commits intomainfrom
071025-3
Oct 7, 2025
Merged

refactor: modernize wave typing hook with shared websocket#1515
simo6529 merged 4 commits intomainfrom
071025-3

Conversation

@simo6529
Copy link
Copy Markdown
Collaborator

@simo6529 simo6529 commented Oct 7, 2025

Summary by CodeRabbit

  • Bug Fixes
    • Typing indicator updates more reliably and excludes your own typing.
    • Status clears correctly after inactivity and when related content is removed.
    • Improved handling during connectivity changes to prevent stale indicators.
  • Refactor
    • Migrated to a standardized real-time messaging layer for greater stability and consistency.
  • Tests
    • Added message-driven tests for typing behavior and timeouts; removed obsolete tests tied to the prior real-time implementation.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Oct 7, 2025

Warning

Rate limit exceeded

@simo6529 has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 3 minutes and 29 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 76eeca5 and 8d01d84.

📒 Files selected for processing (1)
  • hooks/useWaveIsTyping.ts (2 hunks)

Walkthrough

Refactors useWaveIsTyping to use a WebSocket abstraction (useWebSocket/useWebSocketMessage), adds subscription lifecycle, message handlers for typing and drop updates, and periodic cleanup. Removes the legacy useWaveWebSocket hook and its tests; updates tests to a message-driven mock harness for the new hooks.

Changes

Cohort / File(s) Summary
Hook refactor to WebSocket abstraction
hooks/useWaveIsTyping.ts
Replaces direct WebSocket wiring with useWebSocket/useWebSocketMessage; subscribes/unsubscribes to wave; handles USER_IS_TYPING and DROP_UPDATE messages with wave/user filtering; centralizes typing-string derivation and adds periodic stale-typer cleanup.
Legacy hook removal
hooks/useWaveWebSocket.ts
Removes useWaveWebSocket hook, its reconnection lifecycle, exported result/interface, and related constants.
Tests updated for new message-driven flow
__tests__/hooks/useWaveIsTyping.test.tsx
Reworks test setup to mock useWebSocket/useWebSocketMessage, introduces in-test messageHandlers and emitMessage helper, validates subscribe/unsubscribe, typing events, drop pruning, timeout behavior, and cleanup.
Deprecated tests removed
__tests__/hooks/useWaveWebSocket.test.ts
Deletes the prior test suite that implemented MockWebSocket, global WebSocket replacement, and reconnect/disconnect tests.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Component as Component (uses useWaveIsTyping)
  participant WS as useWebSocket
  participant MSG as useWebSocketMessage
  participant State as Typers Map / Typing String

  Component->>WS: connect(waveId)
  WS-->>Component: onConnect
  Component->>WS: send SUBSCRIBE_TO_WAVE(waveId, subscribe=true)

  Note over MSG,State: Incoming messages (filtered by waveId)
  MSG-->>Component: USER_IS_TYPING{waveId, userId, timestamp}
  Component->>State: upsert typer (ignore current user)
  Component->>State: updateTypingString()

  MSG-->>Component: DROP_UPDATE{waveId, drop:{authorId}}
  Component->>State: remove typers by authorId
  Component->>State: updateTypingString()

  Note over Component,State: Periodic cleanup when not CONNECTED
  Component->>State: clear stale typers
  Component->>State: updateTypingString()

  Component-->>WS: onUnmount -> send SUBSCRIBE_TO_WAVE(waveId, subscribe=false)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • ragnep

Poem

I twitched my whiskers at the stream,
Old sockets stretched and hopped from dream.
New hooks catch whispers, typing light,
Drop prunes the chorus out of sight.
I nibble carrots, tests now bright. 🥕🐇

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title “refactor: modernize wave typing hook with shared websocket” succinctly captures the primary change of replacing the legacy WebSocket hook with a shared useWebSocket abstraction in the useWaveIsTyping hook.

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 81662c0 and f659611.

📒 Files selected for processing (4)
  • __tests__/hooks/useWaveIsTyping.test.tsx (1 hunks)
  • __tests__/hooks/useWaveWebSocket.test.ts (0 hunks)
  • hooks/useWaveIsTyping.ts (3 hunks)
  • hooks/useWaveWebSocket.ts (0 hunks)
💤 Files with no reviewable changes (2)
  • tests/hooks/useWaveWebSocket.test.ts
  • hooks/useWaveWebSocket.ts
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx}: Do not include any comments in the code
Use react-query for data fetching
Always add readonly before props

Use TypeScript for implementation code

Files:

  • __tests__/hooks/useWaveIsTyping.test.tsx
  • hooks/useWaveIsTyping.ts
**/*.tsx

📄 CodeRabbit inference engine (.cursorrules)

**/*.tsx: Use FontAwesome for icons
Use TailwindCSS for styling

Use React functional components with hooks

Files:

  • __tests__/hooks/useWaveIsTyping.test.tsx
**/{__tests__/**/*.{ts,tsx},*.test.tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/{__tests__/**/*.{ts,tsx},*.test.tsx}: Place tests in tests directories or alongside components as ComponentName.test.tsx
Mock external dependencies and APIs in tests

Files:

  • __tests__/hooks/useWaveIsTyping.test.tsx
__tests__/**

📄 CodeRabbit inference engine (tests/AGENTS.md)

Place Jest test suites under the __tests__ directory mirroring source folders (e.g., components, contexts, hooks, utils)

Files:

  • __tests__/hooks/useWaveIsTyping.test.tsx
🧬 Code graph analysis (2)
__tests__/hooks/useWaveIsTyping.test.tsx (2)
services/websocket/useWebSocketMessage.ts (1)
  • useWebSocketMessage (17-47)
hooks/useWaveIsTyping.ts (1)
  • useWaveIsTyping (64-173)
hooks/useWaveIsTyping.ts (2)
services/websocket/useWebSocketMessage.ts (1)
  • useWebSocketMessage (17-47)
helpers/Types.tsx (2)
  • WsTypingMessage (331-338)
  • WsDropUpdateMessage (340-343)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (13)
__tests__/hooks/useWaveIsTyping.test.tsx (5)

1-6: LGTM!

The imports are correctly updated to use the new WebSocket abstractions (useWebSocket and useWebSocketMessage), replacing the legacy useWaveWebSocket hook.


8-14: LGTM!

The mock setup correctly replaces the new WebSocket hooks with Jest mocks, enabling controlled testing of message-driven behavior.


16-27: LGTM!

The message-driven test harness (messageHandlers, emitMessage) effectively simulates WebSocket message routing, enabling comprehensive testing of the hook's behavior without requiring a real WebSocket connection.


29-55: LGTM!

The test setup and teardown are well-structured:

  • Fake timers enable testing of time-based typing timeout behavior
  • Mock implementations correctly simulate the WebSocket hooks' behavior
  • Proper cleanup prevents test interference

57-109: LGTM! Comprehensive test coverage.

The test thoroughly validates the hook's lifecycle and message-driven behavior:

  1. Subscription on mount
  2. Typing indicator updates from USER_IS_TYPING messages
  3. Typer removal on DROP_UPDATE messages
  4. Timeout-based clearing of stale typers (5-second window)
  5. Unsubscription on unmount

The use of act, waitFor, and fake timers ensures reliable testing of asynchronous and time-dependent behavior.

hooks/useWaveIsTyping.ts (8)

33-52: LGTM!

The buildTypingString function correctly handles all cases (0, 1, 2, or 3+ typers) and sorts by level to prioritize higher-level users in the display string.


64-82: LGTM! Efficient state management.

The hook correctly uses:

  • useState for the typing message (triggers re-renders when the display string changes)
  • useRef for the typers map (mutable state that doesn't trigger re-renders)
  • useCallback with stable dependencies for updateTypingString

The optimization in line 81 prevents unnecessary re-renders by only updating state when the message actually changes.


84-88: LGTM!

The reset effect correctly clears the typers map and updates the display string when the wave changes, ensuring clean state when switching between waves.


90-107: LGTM! Subscription lifecycle is correct.

The subscription effect properly:

  • Only subscribes when the WebSocket is connected
  • Sends a subscription message on mount/reconnect
  • Sends an unsubscription message on unmount or wave change

Note: The cleanup function (lines 101-106) will attempt to send an unsubscribe message even if the connection has dropped by the time cleanup runs. This is typically handled gracefully by WebSocket implementations as a no-op, but verify that the send function handles disconnected state appropriately.


109-128: LGTM!

The USER_IS_TYPING handler correctly:

  • Filters messages by wave ID
  • Excludes the current user's typing events (prevents "you are typing" display)
  • Updates the typers map with timestamp for timeout tracking
  • Triggers display string update

The callback dependencies are appropriate and stable.


130-145: LGTM!

The DROP_UPDATE handler correctly removes typers when they post a drop to the current wave, with a good optimization to only trigger a display update if the author was actually present in the typers map.


147-153: LGTM!

The disconnect cleanup effect ensures the typing indicators are cleared when the WebSocket connection drops, preventing stale typing indicators from persisting during connection issues.


156-170: LGTM!

The periodic cleanup effect correctly:

  • Runs every 1 second to prune stale typers (older than 5 seconds)
  • Updates the display string after pruning
  • Cleans up the interval on unmount

This ensures typing indicators automatically disappear after the configured timeout period, even without explicit DROP_UPDATE messages.

Comment thread hooks/useWaveIsTyping.ts Outdated
Signed-off-by: Simo <simo@6529.io>
Signed-off-by: Simo <simo@6529.io>
Copy link
Copy Markdown

@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: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
hooks/useWaveIsTyping.ts (1)

22-38: Fix grammatical error for singular case.

Line 37 produces grammatically incorrect text when exactly 3 people are typing ("and 1 more people are typing" should be "and 1 more person are typing").

Apply this diff:

-  return `${names[0]}, ${names[1]} and ${names.length - 2} more people are typing`;
+  const remaining = names.length - 2;
+  const peopleWord = remaining === 1 ? "person" : "people";
+  return `${names[0]}, ${names[1]} and ${remaining} more ${peopleWord} are typing`;
🧹 Nitpick comments (1)
hooks/useWaveIsTyping.ts (1)

14-17: Consider adding readonly modifiers for immutability.

While the coding guideline about readonly specifically targets props interfaces, making TypingEntry properties readonly would better reflect their immutable nature and prevent accidental mutations.

Apply this diff:

 interface TypingEntry {
-  profile: ApiProfileMin;
-  lastTypingAt: number;
+  readonly profile: ApiProfileMin;
+  readonly lastTypingAt: number;
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f659611 and 76eeca5.

📒 Files selected for processing (1)
  • hooks/useWaveIsTyping.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx}: Do not include any comments in the code
Use react-query for data fetching
Always add readonly before props

Use TypeScript for implementation code

Files:

  • hooks/useWaveIsTyping.ts
🧬 Code graph analysis (1)
hooks/useWaveIsTyping.ts (3)
generated/models/ApiProfileMin.ts (1)
  • ApiProfileMin (16-133)
services/websocket/useWebSocketMessage.ts (1)
  • useWebSocketMessage (17-47)
helpers/Types.tsx (2)
  • WsTypingMessage (331-338)
  • WsDropUpdateMessage (340-343)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (3)
hooks/useWaveIsTyping.ts (3)

50-55: LGTM! Good optimization pattern.

The centralized updateTypingString callback with the identity check (prev === newMessage ? prev : newMessage) prevents unnecessary re-renders when the typing string hasn't changed. The empty dependency array is correct since it only references stable values (the ref and setState function).


62-78: LGTM! Proper subscription lifecycle.

The effect correctly subscribes to wave updates only when connected and properly unsubscribes during cleanup. This aligns with the broader refactor to use the shared WebSocket abstraction.


80-136: LGTM! Message handlers and cleanup logic are well-structured.

The implementation correctly:

  • Filters typing events by wave and excludes the current user
  • Removes typers from the list when they post (DROP_UPDATE handler)
  • Clears stale typers when disconnected and via periodic cleanup
  • Uses proper type guards for nullable fields (lines 84-88, 104)
  • Manages dependencies correctly across all effects and callbacks

The overall refactor successfully migrates to the new WebSocket abstraction while maintaining correct typing indicator behavior.

@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented Oct 7, 2025

@simo6529 simo6529 merged commit 1bee858 into main Oct 7, 2025
9 checks passed
@simo6529 simo6529 deleted the 071025-3 branch October 7, 2025 12:45
This was referenced Oct 8, 2025
@coderabbitai coderabbitai Bot mentioned this pull request Feb 20, 2026
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.

2 participants