Skip to content

feat: add real-time latency indicator and websocket heartbeat#84

Open
Nakshatra480 wants to merge 4 commits intoAOSSIE-Org:mainfrom
Nakshatra480:feature/latency-indicator
Open

feat: add real-time latency indicator and websocket heartbeat#84
Nakshatra480 wants to merge 4 commits intoAOSSIE-Org:mainfrom
Nakshatra480:feature/latency-indicator

Conversation

@Nakshatra480
Copy link

@Nakshatra480 Nakshatra480 commented Feb 13, 2026

Description

This PR adds a real-time connection quality indicator to the trackpad interface. It implements a lightweight heartbeat mechanism to measure the Round-Trip Time (RTT) between the client and the server, helping users diagnose lag or connection issues.

Changes

  • Backend: Added a pong response handler in websocket.ts to return the client's timestamp.
  • Hook Logic: Updated useRemoteConnection to initiate a heartbeat every 3 seconds and calculate latency.
  • UI Enhancement: Added a color-coded Ping display in the ControlBar:
    • Green: < 50ms (Stable)
    • Yellow: 50ms - 150ms (Noticeable lag)
    • Red: > 150ms (High latency)
  • Stability: Ensured all timers are properly cleared on disconnect/unmount to prevent memory leaks.

Verification

  • Verified that the heartbeat stops when the connection is closed.
  • Confirmed the UI updates correctly and resets to --- when disconnected.
  • Checked that no irrelevant code or "Screen Wake Lock" logic was included in the final diff.

Closes #83

Summary by CodeRabbit

  • New Features
    • Added a live ping latency display to the trackpad control panel ("Ping: {ms}" or "Ping: ---" when unavailable) to monitor connection quality.
  • Improvements
    • Connection now measures and updates latency periodically for real-time feedback; UI layout adjusted to place latency above controls, with Copy/Paste shown disabled and button accessibility improved.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 13, 2026

📝 Walkthrough

Walkthrough

Adds an application-level ping/pong heartbeat: the frontend hook sends periodic pings and measures RTT, the server replies with pong, and the Trackpad UI displays latency (ms) with color-coded indicator in the ControlBar.

Changes

Cohort / File(s) Summary
Heartbeat & Latency Hook
src/hooks/useRemoteConnection.ts
Adds `latency: number
UI Latency Display
src/components/Trackpad/ControlBar.tsx
Adds latency prop to ControlBarProps, implements getLatencyColor(ms) mapping to CSS classes, reworks layout to render a latency row above controls and applies color to "Ping: Xms" display.
Routing & Propagation
src/routes/trackpad.tsx
Destructures latency from useRemoteConnection() and passes latency={latency} into ControlBar.
Server Ping Handler
src/server/websocket.ts
Adds handling for incoming {type: 'ping'} messages and responds immediately with {type: 'pong', timestamp} echoing the original timestamp before other message processing.

Sequence Diagram(s)

sequenceDiagram
    participant Browser as Client Browser
    participant Hook as useRemoteConnection Hook
    participant WS as WebSocket Server
    participant UI as ControlBar

    Browser->>Hook: Render TrackpadPage
    Hook->>WS: Open WebSocket
    Note over Hook: Start ping timer (every 3s)

    loop Heartbeat interval
        Hook->>WS: {"type":"ping","timestamp":T0}
        WS->>Hook: {"type":"pong","timestamp":T0}
        Hook->>Hook: RTT = now() - T0
        Hook->>UI: update latency prop
        UI->>UI: render "Ping: Xms" with color
    end

    Browser->>Hook: Unmount / Disconnect
    Hook->>Hook: clear timer, close ws, reset latency
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐇 I pinged the world and waited near,

A pong hopped back — so crisp and clear,
Green for spry, red for slow, I sing,
A heartbeat tells how swift we spring,
I twitch my nose and show the ping!

🚥 Pre-merge checks | ✅ 5 | ❌ 1
❌ 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%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and accurately summarizes the main changes: adding a real-time latency indicator and websocket heartbeat mechanism that are the core focus of the entire changeset.
Linked Issues check ✅ Passed The PR fully implements all requirements from issue #83: heartbeat mechanism (ping/pong every 3 seconds) [#83], RTT calculation in useRemoteConnection [#83], pong response in websocket.ts [#83], and color-coded latency display in ControlBar [#83].
Out of Scope Changes check ✅ Passed All changes are directly related to the latency indicator and heartbeat feature scope; no unrelated modifications such as Screen Wake Lock logic or other extraneous changes are present.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

No actionable comments were generated in the recent review. 🎉

🧹 Recent nitpick comments
src/components/Trackpad/ControlBar.tsx (1)

10-10: onLeftClick is a required prop but never used — the L-Click button is commented out.

The onLeftClick callback is still declared as required in ControlBarProps and destructured, but the only consumer (L-Click button) is commented out. This forces callers to pass a handler that is silently discarded.

Consider making it optional while the button is disabled, or removing it from the interface until re-enabled.

Suggested change
 interface ControlBarProps {
 	scrollMode: boolean;
 	modifier: ModifierState;
 	buffer: string;
 	latency: number | null;
 	onToggleScroll: () => void;
-	onLeftClick: () => void;
+	onLeftClick?: () => void;
 	onRightClick: () => void;
 	onKeyboardToggle: () => void;
 	onModifierToggle: () => void;
 }

Also applies to: 22-22, 95-103


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

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

Copy link
Contributor

@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

🤖 Fix all issues with AI agents
In `@src/components/Trackpad/ControlBar.tsx`:
- Around line 73-116: The grid is defined as "grid-cols-5" but there are six
active buttons in ControlBar (the Scroll/Cursor button using onToggleScroll,
Copy, Paste, R-Click using onRightClick, Modifier using
getModifierButtonClass/getModifierLabel and onModifierToggle, and Keyboard using
onKeyboardToggle), causing wrapping; update the grid to "grid-cols-6" (or reduce
buttons to five) so all buttons fit on one row—adjust the className on the
container div accordingly.
- Around line 73-116: Every <button> in ControlBar.tsx lacks an explicit type
and therefore defaults to type="submit"; update each button element (the
Scroll/Cursor toggle that calls handleInteraction with onToggleScroll, the Copy
and Paste buttons, the R-Click button that calls handleInteraction with
onRightClick, the modifier button that uses
getModifierButtonClass/getModifierLabel and onModifierToggle, and the Keyboard
button that calls onKeyboardToggle) to include type="button" to prevent
accidental form submission.

In `@src/hooks/useRemoteConnection.ts`:
- Around line 55-59: The cleanup closure currently captures the stale ws state
(initially null) so ws?.close() is never called; change the WebSocket storage to
a ref (e.g., wsRef via useRef<WebSocket | null>) and in connect() assign
wsRef.current = socket (and update any other ws usages to read wsRef.current),
then in the cleanup return call wsRef.current?.close() alongside clearing
reconnectTimer and heartbeatTimer (and null out wsRef.current on close) so the
real socket is reliably closed on unmount.
🧹 Nitpick comments (2)
src/hooks/useRemoteConnection.ts (1)

21-28: Heartbeat fires immediately after reconnect — consider a brief delay.

When the WebSocket reconnects (after the 3s reconnect timeout), the first heartbeat ping fires immediately in onopen, then every 3 seconds via setInterval. This is fine functionally, but if you want the first latency reading sooner after connect, you could send an initial ping immediately in onopen before starting the interval.

This is a minor UX observation — the first latency display won't appear until up to 3 seconds after connection.

Optional: send initial ping immediately
 socket.onopen = () => {
     setStatus('connected');
+    // Send first ping immediately for quick latency reading
+    socket.send(JSON.stringify({ type: 'ping', timestamp: Date.now() }));
     heartbeatTimer = setInterval(() => {
         if (socket.readyState === WebSocket.OPEN) {
             socket.send(JSON.stringify({ type: 'ping', timestamp: Date.now() }));
         }
     }, 3000);
 };
src/components/Trackpad/ControlBar.tsx (1)

80-97: Non-functional Copy/Paste buttons and commented-out L-Click code.

The Copy and Paste buttons (lines 80–89) have no event handlers — they render but do nothing on interaction. The L-Click button (lines 90–97) is fully commented out. If these are WIP placeholders, consider removing them from this PR to keep the diff focused on the latency feature, or add TODO comments indicating they're planned.

Copy link
Contributor

@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

🤖 Fix all issues with AI agents
In `@src/components/Trackpad/ControlBar.tsx`:
- Around line 81-92: The Copy and Paste buttons in the ControlBar component are
rendered without handlers so they are non-functional; either wire them up or
mark them disabled for now. Locate the ControlBar component (buttons with text
"Copy" and "Paste") and either add onClick handlers that call the appropriate
functions (e.g., copySelection, pasteClipboard or existing methods in the same
component) or set the buttons to non-interactive by adding the disabled
attribute and a CSS class for reduced opacity/aria-disabled for accessibility.
Ensure any new handlers use existing clipboard utilities or component methods
and keep ARIA attributes consistent.

In `@src/hooks/useRemoteConnection.ts`:
- Around line 4-5: The ws state (useState WebSocket | null) is dead — remove the
unused state and its updater to avoid needless re-renders: delete the const [ws,
setWs] = useState<WebSocket | null>(null); declaration in the
useRemoteConnection hook and remove the setWs(socket) call where the WebSocket
is assigned (leaving wsRef.current = socket and all send/sendCombo/cleanup logic
using wsRef intact).
🧹 Nitpick comments (1)
src/hooks/useRemoteConnection.ts (1)

31-41: RTT calculation assumes msg.timestamp is echoed back as a number.

If the server-side handler ever coerces the timestamp to a string (e.g., via JSON key handling), Date.now() - msg.timestamp would still work due to JS implicit coercion, but a negative or nonsensical RTT could surface if the field is missing or malformed. Consider a guard:

 if (msg.type === 'pong') {
-    const rtt = Date.now() - msg.timestamp;
-    setLatency(rtt);
+    const rtt = Date.now() - msg.timestamp;
+    if (Number.isFinite(rtt) && rtt >= 0) {
+        setLatency(rtt);
+    }
 }

@Nakshatra480 Nakshatra480 marked this pull request as draft February 13, 2026 20:07
@Nakshatra480 Nakshatra480 marked this pull request as ready for review February 14, 2026 21:00
@Nakshatra480
Copy link
Author

Hi @imxade i have added a latency indicator and websocket heartbeat feature, can you review this PR.
Thanks

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.

[Feature]: Add Connection Latency (Ping) Indicator and Heartbeat Mechanism

1 participant