Skip to content

Conversation

@NathanFlurry
Copy link
Member

No description provided.

@vercel
Copy link

vercel bot commented Oct 22, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

3 Skipped Deployments
Project Deployment Preview Comments Updated (UTC)
rivet-cloud Ignored Ignored Oct 24, 2025 8:41pm
rivet-inspector Ignored Ignored Preview Oct 24, 2025 8:41pm
rivet-site Ignored Ignored Preview Oct 24, 2025 8:41pm

Copy link
Member Author

NathanFlurry commented Oct 22, 2025

Warning

This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
Learn more


How to use the Graphite Merge Queue

Add the label merge-queue to this PR to add it to the merge queue.

You must have a Graphite account in order to use the merge queue. Sign up using this link.

An organization admin has enabled the Graphite Merge Queue in this repository.

Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue.

This stack of pull requests is managed by Graphite. Learn more about stacking.

@claude
Copy link

claude bot commented Oct 22, 2025

Pull Request Review: Cursors Example

Thank you for contributing this collaborative cursors example! This is a great demonstration of real-time functionality with RivetKit. I've identified several issues that need to be addressed before merging.

Critical Issues

1. Test Implementation Doesn't Match Actor API ⚠️

The tests in tests/cursors.test.ts call methods that don't exist in the actor definition:

Problems:

  • Tests call room.getCursors(), room.getTextLabels(), room.placeText(), and room.removeCursor()
  • Actor only defines: updateCursor, updateText, removeText, and getRoomState

Examples:

  • Line 10: const initialCursors = await room.getCursors(); - method doesn't exist
  • Line 47: await room.placeText("user1", "Hello", 50, 75); - should be updateText with an id parameter
  • Line 82: await room.removeCursor("user2"); - method doesn't exist

Fix Required:
Either update the tests to match the actual API, or implement the missing methods in the actor. The tests currently won't pass.

2. Missing Newline at End of .gitignore

examples/cursors/.gitignore is missing a trailing newline (line 2). This is a common convention and some tools may complain.

3. Event Handler Registration Issue

In src/frontend/App.tsx:118-124, there's a listener for a cursorRemoved event, but the actor never broadcasts this event. The comment on line 201 mentions "Cursor is automatically removed when connection closes via connState cleanup", but there's no actual event emission.

Impact: Dead code that may confuse future maintainers.

4. Inconsistent Cursor State Management

The frontend tracks cursors by userId in the state (line 42, 92-95), but the backend stores them by connection in connState. This creates a mismatch:

  • Multiple connections from the same user would overwrite each other in the frontend
  • The getRoomState action builds cursors by userId (line 87), which could lose data if one user has multiple connections

Recommendation: Clarify whether the system should support one cursor per user or one cursor per connection.

5. Potential Race Condition in Text Updates

In handleTextChange (lines 158-169), the code updates text on every keystroke if newText.trim() is truthy. However, if a user types "abc", deletes it all, then types "def", there could be a brief moment where the text is removed from the UI but not the server state.

Code Quality Issues

6. Unused Variables in Tests

  • cursors.test.ts:97-98: Variables cursor2 and cursor3 are assigned but cursor2 is never used except in one assertion
  • Consider removing cursor2 if it's only for the timestamp comparison

7. Missing Error Handling

src/frontend/App.tsx:81-86: The getRoomState call has error handling, but it only logs to console. Consider showing an error state to the user.

8. TypeScript: Potential Undefined Reference

Line 284: const isOwnCursor = id === userId;

The variable id here is the cursor's userId from the object key, not a connection ID. This comparison seems correct but the variable naming is confusing (you're comparing cursor.userId with the local userId, not the map key).

9. Accessibility Concerns

  • The canvas has tabIndex={0} but no ARIA labels or roles
  • Screen readers won't understand what this interactive area is for
  • Consider adding role="application" and aria-label attributes

Documentation & Testing

10. README Clarity

The README mentions "persistent text labels across sessions" but there's no actual persistence - state is stored in memory only. Consider clarifying this is "session-based persistence" or removing the claim.

11. Missing Test Coverage

No tests for:

  • removeText functionality
  • Race conditions with rapid cursor updates
  • Room state synchronization when a user joins an active room
  • Behavior when text is updated/removed while typing

Performance Considerations

12. Unnecessary Re-renders

In App.tsx:99-109, the textUpdated event handler always creates a new array, even when just updating an existing label. Consider using immutability helpers or a Map for better performance.

13. No Cursor Throttling

Mouse movements trigger updateCursor on every mousemove event. For a production app, consider throttling these updates to reduce network traffic (e.g., max 60 updates/second).

Minor Issues

14. HTML File Organization

The index.html file contains 191 lines, with substantial embedded CSS. Consider extracting styles to a separate CSS file for better maintainability.

15. Console.error Formatting

Line 85: console.error('error loading room state', error); - should use structured logging format like other files (lowercase message with variables separated).

Security Considerations

Good: No obvious security issues detected. The code doesn't expose sensitive data or allow unauthorized actions.

Summary

Recommendation: Request changes before merging.

The most critical issue is that the tests don't match the actual implementation and will fail when run. The test suite needs to be completely rewritten to use the correct actor methods.

Once the tests are fixed and the API inconsistencies resolved, this will be a solid example!

Please let me know if you'd like help addressing any of these issues.


Reviewed with guidance from: /home/runner/work/rivet/rivet/CLAUDE.md

@pkg-pr-new
Copy link

pkg-pr-new bot commented Oct 22, 2025

More templates

@rivetkit/actor

pnpm add https://pkg.pr.new/rivet-dev/rivet/@rivetkit/actor@3244

@rivetkit/cloudflare-workers

pnpm add https://pkg.pr.new/rivet-dev/rivet/@rivetkit/cloudflare-workers@3244

@rivetkit/core

pnpm add https://pkg.pr.new/rivet-dev/rivet/@rivetkit/core@3244

@rivetkit/db

pnpm add https://pkg.pr.new/rivet-dev/rivet/@rivetkit/db@3244

@rivetkit/framework-base

pnpm add https://pkg.pr.new/rivet-dev/rivet/@rivetkit/framework-base@3244

@rivetkit/next-js

pnpm add https://pkg.pr.new/rivet-dev/rivet/@rivetkit/next-js@3244

@rivetkit/react

pnpm add https://pkg.pr.new/rivet-dev/rivet/@rivetkit/react@3244

rivetkit

pnpm add https://pkg.pr.new/rivet-dev/rivet/rivetkit@3244

@rivetkit/sql-loader

pnpm add https://pkg.pr.new/rivet-dev/rivet/@rivetkit/sql-loader@3244

@rivetkit/engine-runner

pnpm add https://pkg.pr.new/rivet-dev/rivet/@rivetkit/engine-runner@3244

@rivetkit/engine-runner-protocol

pnpm add https://pkg.pr.new/rivet-dev/rivet/@rivetkit/engine-runner-protocol@3244

commit: 8766cdd

@NathanFlurry NathanFlurry changed the base branch from 10-22-feat_rivetkit_support_routing_via_query_params to graphite-base/3244 October 23, 2025 07:34
@NathanFlurry NathanFlurry force-pushed the 10-22-feat_examples-cursors_cursors_example branch from 8766cdd to eb78d9e Compare October 23, 2025 07:34
@NathanFlurry NathanFlurry changed the base branch from graphite-base/3244 to 10-22-feat_core_allow_routing_traffic_to_actors_via_path October 23, 2025 07:35
@claude
Copy link

claude bot commented Oct 23, 2025

Pull Request Review: Cursors Example

Great work on this collaborative cursors example! This is a solid demonstration of RivetKit's real-time capabilities. I've reviewed the code and have the following feedback:

✅ Strengths

  1. Clean Architecture: The separation between backend actor logic and frontend UI is well-structured
  2. Good TypeScript Usage: Strong typing throughout with clear interfaces
  3. Test Coverage: Comprehensive test suite covering key scenarios
  4. User Experience: Nice touch with color-coded cursors, real-time typing indicators, and room support
  5. Documentation: Clear README with setup instructions

🐛 Issues Found

Critical: Test API Mismatch

Location: tests/cursors.test.ts

The tests are calling methods that don't exist in the registry:

  • room.getCursors() (lines 10, 28)
  • room.getTextLabels() (lines 43, 63, 118)
  • room.placeText() (lines 47, 60, 113-116)
  • room.removeCursor() (line 82)

Registry only defines: updateCursor, updateText, removeText, getRoomState

Impact: All tests will fail when run.

Fix Required: Either:

  1. Update tests to use getRoomState() and extract data from the result, OR
  2. Add the missing convenience methods to the registry

Bug: Event Handler Never Triggered

Location: src/frontend/App.tsx:118-124

cursorRoom.useEvent("cursorRemoved", (cursor: CursorPosition) => {
  // ...
});

The backend never emits a cursorRemoved event. Cursors are stored in connState and should be automatically cleaned up when a connection closes, but there's no explicit broadcast for this event.

Impact: Remote cursors won't disappear when users disconnect, leading to "ghost cursors" on the canvas.

Fix: Either remove this handler (if RivetKit handles cleanup automatically) or add an event emission when connections close.

⚠️ Security & Best Practices

1. Input Validation Missing

Location: src/backend/registry.ts:30-40, 43-72

No validation on:

  • Text content length (could cause DOS with extremely long strings)
  • Coordinate bounds (negative values, extremely large values)
  • User ID format/length

Recommendation: Add validation, for example:

if (text.length > 1000) throw new Error("text too long");
if (x < 0 || x > 10000 || y < 0 || y > 10000) throw new Error("invalid coordinates");

2. Unbounded Array Growth

Location: src/backend/registry.ts:21

state: {
  textLabels: [] as TextLabel[],
},

No limit on the number of text labels. A malicious user could fill the room with thousands of labels, causing memory issues and poor performance.

Recommendation: Add a maximum label count (e.g., 100-500) and implement FIFO removal or prevent new additions when limit is reached.

3. No Sanitization of Text Content

Location: src/frontend/App.tsx:247

Text from other users is rendered directly without sanitization. While React escapes by default, this should be documented.

Note: This is generally safe in React, but be cautious if text is ever rendered in a different context.

🎨 Code Quality

1. Missing Newline at EOF

Location: examples/cursors/.gitignore:2

File is missing trailing newline. This is a common git best practice.

2. Console Error Instead of Structured Logging

Location: src/frontend/App.tsx:85

console.error('error loading room state', error);

Should use consistent logging approach. Consider using a logging library or at least consistent formatting.

3. Inconsistent Comparison Operators

Location: src/frontend/App.tsx:101, 236

Mix of l => l.id and label => label.id. While both work, consistency improves readability.

4. README Package Manager Mismatch

Location: examples/cursors/README.md:20-21, 26

README uses npm install and npm run dev, but package.json uses pnpm workspace dependencies. Based on CLAUDE.md, this project uses pnpm.

Recommendation: Update README to use pnpm commands:

pnpm install
pnpm dev

🔧 Performance Considerations

1. Cursor Update Frequency

Location: src/frontend/App.tsx:138-142

Every mouse move triggers a network call. This could be optimized with throttling/debouncing.

Recommendation: Throttle updates to ~60Hz (16ms) for smoother performance:

const throttledUpdateCursor = useCallback(
  throttle((x, y) => {
    cursorRoom.connection?.updateCursor(userId, x, y);
  }, 16),
  [cursorRoom.connection, userId]
);

2. Real-time Text Updates

Location: src/frontend/App.tsx:158-169

Every keystroke broadcasts an update. This works for demonstration but could be expensive at scale.

Note: This is probably fine for an example, but document it as a simplification.

📋 Missing Test Cases

Consider adding tests for:

  1. Text label removal (removeText action)
  2. Empty text handling
  3. Duplicate text IDs
  4. Coordinate boundary values
  5. Special characters in text content
  6. Room state after connection cleanup

📝 Documentation Suggestions

  1. Add JSDoc comments to the actor actions explaining parameters
  2. Document the coordinate system (virtual 1920x1080 canvas)
  3. Add comments explaining the connState vs state distinction
  4. Document expected client behavior for cursor cleanup

Summary

This is a well-structured example that demonstrates RivetKit effectively. The main issues are:

  1. Critical: Tests don't match the implementation and will fail
  2. Important: Missing input validation and resource limits
  3. Minor: Event handler for never-emitted event
  4. Enhancement: Cursor update throttling for performance

The code is clean and readable, making it a good learning resource once the test/API mismatch is resolved.

Great work overall! 🎉

@claude
Copy link

claude bot commented Oct 23, 2025

PR Review: Cursors Example

Thanks for this contribution! This is a well-structured example demonstrating real-time collaborative cursors. I've identified several issues that need to be addressed before merging.

Critical Issues

1. Test Method Mismatch (Tests Will Fail)

Location: tests/cursors.test.ts

The tests call methods that don't exist in the registry:

  • room.getCursors() - doesn't exist
  • room.placeText() - doesn't exist (should be updateText())
  • room.getTextLabels() - doesn't exist
  • room.removeCursor() - doesn't exist

The only action defined in registry.ts is getRoomState() which returns both cursors and textLabels together.

Fix needed:

// Instead of:
const cursors = await room.getCursors();
const labels = await room.getTextLabels();

// Use:
const state = await room.getRoomState();
const cursors = state.cursors;
const labels = state.textLabels;

// Instead of:
await room.placeText("user1", "Hello", 50, 75);

// Use:
const labelId = `user1-${Date.now()}`;
await room.updateText(labelId, "user1", "Hello", 50, 75);

You'll need to rewrite all 5 test cases to use the correct API.

2. Cursor Removal Logic Missing

Location: registry.ts

The tests expect a removeCursor() action, but it doesn't exist. Additionally, there's no automatic cleanup when a connection closes. While the comment in App.tsx:201 says "Cursor is automatically removed when connection closes via connState cleanup", this isn't implemented in the registry.

Suggested fix:

  • Remove the removeCursor test since connState cleanup should handle this automatically
  • Verify that connState cleanup actually works as expected in RivetKit

3. Event Not Emitted

Location: App.tsx:118

The frontend listens for a cursorRemoved event that is never broadcast in the backend:

cursorRoom.useEvent("cursorRemoved", (cursor: CursorPosition) => {
  // This will never be called
});

Either remove this event listener or implement cursor removal broadcasts in the registry.

Bugs

4. Cursor Dictionary Key Mismatch

Location: App.tsx:284

Object.entries(cursors).map(([id, cursor]) => {
  const isOwnCursor = id === userId; // BUG: comparing wrong values

The id here is cursor.userId from the cursors dictionary, not the connection ID. The comparison should be:

const isOwnCursor = cursor.userId === userId;

5. Missing .gitignore Entry

Location: .gitignore

File is missing newline at end of file (line 2). While minor, this should be fixed for consistency.

Code Quality Suggestions

6. Error Handling

Location: App.tsx:84-86

Good that you added error handling, but consider showing an error message to the user instead of just console logging:

.catch((error) => {
  console.error('error loading room state', error);
  // Consider: setError('Failed to load room state')
});

7. Cursor Position Updates Performance

Location: registry.ts:30-39

Consider throttling cursor updates to reduce broadcast frequency. Sending every mousemove event could be expensive with many users. A 16ms throttle (60fps) would be reasonable:

// In frontend, throttle the updateCursor calls
const throttledUpdateCursor = useThrottle(
  (x: number, y: number) => cursorRoom.connection.updateCursor(userId, x, y),
  16
);

8. Text Label ID Generation

Location: App.tsx:150

The text ID generation ${userId}-${Date.now()} could theoretically collide if a user clicks twice in the same millisecond. Consider using a counter or UUID:

const newTextId = `${userId}-${Date.now()}-${Math.random()}`;

9. Input Blur Behavior

Location: App.tsx:336-342

When the hidden input loses focus, text is cancelled. This could be frustrating UX - consider keeping the input focused or completing the text on blur instead of canceling it.

Documentation

10. Missing Information

  • README.md doesn't mention the default port (6420) for the backend
  • No mention of how to run tests (npm test)
  • Consider adding a troubleshooting section for common issues (CORS, port conflicts)

Security

✅ No security concerns identified. The code:

  • Properly handles user input
  • Uses local state only
  • CORS is appropriately configured for development
  • No authentication needed for this example

Test Coverage

Need to add tests for:

  • Updating existing text labels (edit functionality)
  • Empty text handling
  • Concurrent updates from multiple users
  • Connection cleanup

Summary

Required Changes:

  1. ✅ Fix all test method calls to match the actual registry API
  2. ✅ Fix cursor dictionary key comparison bug in App.tsx
  3. ✅ Remove unused cursorRemoved event listener or implement the functionality
  4. ⚠️ Add newline to .gitignore

Recommended Changes:

  • Add throttling to cursor position updates
  • Improve text ID generation to prevent collisions
  • Enhance error handling with user feedback
  • Expand README documentation

Once the critical issues are fixed, this will be a great example of RivetKit's real-time collaboration capabilities!

@NathanFlurry NathanFlurry force-pushed the 10-22-feat_core_allow_routing_traffic_to_actors_via_path branch from ed34c5a to 89114d1 Compare October 23, 2025 08:22
@NathanFlurry NathanFlurry force-pushed the 10-22-feat_examples-cursors_cursors_example branch from eb78d9e to 23bc2c4 Compare October 23, 2025 08:22
@claude
Copy link

claude bot commented Oct 23, 2025

PR Review: Cursors Example

This is a great example demonstrating real-time collaborative cursors with RivetKit! The code is generally well-structured and demonstrates the framework's capabilities effectively. Here are my findings:

✅ Strengths

  1. Clean Architecture: Nice separation between backend (actor logic) and frontend (React components)
  2. Good UX: Responsive scaling, multi-room support, and visual feedback for typing states
  3. TypeScript Throughout: Strong typing with well-defined interfaces
  4. Comprehensive Tests: Good test coverage of core functionality

🐛 Issues Found

Critical: Test/Implementation Mismatch

The tests reference methods that don't exist in the actor implementation:

In tests/cursors.test.ts:

  • Line 10: room.getCursors() - does not exist
  • Line 43: room.getTextLabels() - does not exist
  • Line 47: room.placeText() - does not exist
  • Line 82: room.removeCursor() - does not exist

Actual actor methods in registry.ts:

  • updateCursor() (line 30)
  • updateText() (line 43)
  • removeText() (line 75)
  • getRoomState() (line 83) - returns { cursors, textLabels }

The tests will fail as written. You need to either:

  1. Update tests to use the actual API, OR
  2. Add the missing convenience methods to the actor

Frontend Issues

Line 118-124 in App.tsx: Event listener for cursorRemoved is registered, but this event is never broadcast from the backend. The comment on line 201 mentions "Cursor is automatically removed when connection closes via connState cleanup", but the frontend listener will never fire.

Recommendation: Either remove the unused event listener or implement cursor removal broadcasting in the backend.

📋 Code Quality

Backend (registry.ts)

Good:

  • Clean, focused actor implementation
  • Good use of connState for per-connection cursor tracking
  • Proper broadcasting of events

Suggestions:

  • Consider adding input validation for coordinates (negative values, out-of-bounds)
  • Text labels grow unbounded - consider adding a limit or cleanup strategy
  • No sanitization of text input - could allow arbitrarily long strings

Frontend (App.tsx)

Good:

  • Responsive canvas scaling (lines 58-76)
  • Proper coordinate transformation (lines 127-135)
  • Good keyboard handling with Enter/Escape

Suggestions:

  • Line 284: isOwnCursor is computed but not used - consider using it to style own cursor differently
  • Performance: handleMouseMove (line 138) fires on every mouse movement. Consider throttling for production use
  • Line 101: Arrow function comparison in findIndex - performance concern in hot path
  • Line 85: Error logged to console but not shown to user - consider user-facing error handling
  • Security: User-provided text rendered directly without sanitization (line 247) - should escape HTML

🔒 Security Considerations

  1. Input Validation: No length limits on text input - could cause DoS
  2. XSS Risk: Text content rendered without escaping (though React provides some protection)
  3. Room IDs: No validation - users could create rooms with malicious names
  4. Rate Limiting: No throttling on cursor updates - could spam the server

🎯 Performance

  1. Cursor Updates: Unthrottled mouse events could generate excessive network traffic
  2. Re-renders: State updates on every cursor movement of any user could cause performance issues with many users
  3. Text Label Filtering: Line 235-236 filters on every render - consider memoization

📝 Documentation

Good:

  • Excellent README with clear setup instructions
  • Inline comments explaining key functionality
  • Feature list helps understand capabilities

Suggestions:

  • Add JSDoc comments for exported interfaces
  • Document the coordinate system and scaling approach
  • Explain the text ID generation strategy

🧪 Test Coverage

Missing test scenarios:

  • Concurrent cursor updates from multiple users
  • Text update/overwrite behavior
  • Boundary conditions (negative coordinates, very large values)
  • Connection cleanup and cursor removal
  • Empty/whitespace-only text handling

📦 Configuration

Minor Issues:

  • .gitignore missing trailing newline (line 2)
  • package.json lists @types/prompts but prompts isn't used in the code

✏️ Style Notes

All code follows TypeScript and React conventions well. No style issues identified.

🎬 Recommendations

Must Fix Before Merge:

  1. ✅ Fix test suite to match actual actor API
  2. ✅ Remove unused cursorRemoved event listener or implement the feature

Should Consider:

  1. Add input validation and rate limiting
  2. Throttle cursor movement updates
  3. Add text length limits
  4. Document the missing test helper methods or update tests

Nice to Have:

  1. Use the computed isOwnCursor variable or remove it
  2. Add error boundaries for better error handling
  3. Memoize expensive computations
  4. Add integration tests with actual WebSocket connections

📊 Overall Assessment

This is a solid example that demonstrates RivetKit's capabilities well. The main blocker is the test/implementation mismatch that will cause CI failures. Once that's fixed, this will be a great addition to the examples directory!

Recommendation: Request changes to fix the test suite.

@claude
Copy link

claude bot commented Oct 23, 2025

PR Review: Cursors Example

Thank you for this well-structured example! This is a great demonstration of RivetKit's capabilities for real-time collaborative features. I've reviewed the code and have several findings to share:

Critical Issues

1. Test-Implementation Mismatch

Location: tests/cursors.test.ts

The tests reference methods that don't exist in the actor implementation:

  • Line 10, 28: room.getCursors() - not defined in registry
  • Line 43: room.getTextLabels() - not defined in registry
  • Line 47, 60, 113-116: room.placeText() - should be room.updateText()
  • Line 82: room.removeCursor() - not defined in registry

Impact: Tests will fail when run.

Recommendation: Either:

  1. Add the missing methods to the actor (getCursors, getTextLabels, removeCursor), OR
  2. Update tests to use the existing getRoomState() method and updateText() instead of placeText()

2. Missing Newline in .gitignore

Location: examples/cursors/.gitignore:2

The file is missing a trailing newline, which is a common practice and may cause linting warnings.

Moderate Issues

3. Cursor Position Keying Issue

Location: src/frontend/App.tsx:282

The cursors are keyed by connection ID (the map key), but compared with userId:

Object.entries(cursors).map(([id, cursor]) => {
    const isOwnCursor = id === userId; // This comparison won't work

The key is from cursors dictionary where keys are cursor.userId (line 94), so this should work, but the variable naming is confusing. Consider renaming id to cursorUserId for clarity.

4. Event Listener Not Emitted

Location: src/frontend/App.tsx:118

The code listens for a "cursorRemoved" event, but the backend never emits this event. Cursors are only removed when connections close via connState cleanup. This listener is dead code.

Recommendation: Remove the unused event listener or document that cursor cleanup happens automatically via connection state.

5. Potential Race Condition

Location: src/frontend/App.tsx:336-342

The onBlur handler could race with the Enter key handler:

onBlur={() => {
    if (!textInput.trim() && cursorRoom.connection && currentTextId) {
        cursorRoom.connection.removeText(currentTextId);
        setCurrentTextId(null);
    }
    setIsTyping(false);
}}

If a user presses Enter and then the input loses focus, both handlers might try to finalize the text. Consider adding a debounce or checking isTyping state.

Minor Issues & Suggestions

6. Missing Input Validation

Location: src/backend/registry.ts:30, 43

The actions don't validate input:

  • Coordinates could be negative or NaN
  • Text could be empty strings or contain special characters
  • User IDs aren't validated

Recommendation: Add basic validation, especially for coordinates to ensure they're valid numbers.

7. Performance: Unnecessary Re-renders

Location: src/frontend/App.tsx:91-124

Each event listener causes a state update and re-render. For high-frequency cursor updates, consider:

  • Using useCallback for event handlers
  • Batching cursor updates with requestAnimationFrame
  • Memoizing the cursor rendering

8. Missing Error Handling

Location: src/frontend/App.tsx:141, 161, 176, 185, 193

Multiple cursorRoom.connection calls have no error handling. If the connection drops mid-action, errors will be uncaught.

Recommendation: Add try-catch blocks or .catch() handlers for network operations.

9. TypeScript Strictness

Location: tsconfig.json:36

While "strict": true is enabled (good!), consider adding:

"noUncheckedIndexedAccess": true,
"noImplicitOverride": true

10. Memory Leak: Event Listeners

Location: src/frontend/App.tsx:91-124

The useEvent hooks don't appear to have cleanup. Verify that the RivetKit library handles unsubscription when the component unmounts or when cursorRoom.connection changes.

11. Accessibility Issues

Location: src/frontend/App.tsx:221-233

The canvas has tabIndex={0} but:

  • No role attribute
  • No aria-label
  • Keyboard navigation beyond typing isn't implemented
  • No way to interact with existing text labels via keyboard

Recommendation: Add ARIA attributes and consider keyboard navigation for accessibility.

Positive Highlights

Clean Architecture: Nice separation between backend (registry) and frontend
Type Safety: Good use of TypeScript interfaces for data structures
User Experience: The color hashing function is clever and provides consistent colors
State Management: Using connState for per-connection cursor tracking is elegant
Documentation: README is clear and helpful
Responsive Design: Scale calculation maintains aspect ratio nicely

Security Assessment

✅ No major security concerns for an example project
⚠️ In production, consider:

  • Rate limiting for cursor updates
  • Text length limits (currently unbounded)
  • Input sanitization for XSS prevention
  • Room access control (currently anyone can join any room)

Test Coverage Assessment

The tests cover core functionality well:

  • ✅ Cursor updates
  • ✅ Text placement
  • ✅ Multi-user scenarios
  • ✅ State overwriting

Missing coverage:

  • ❌ Edge cases (negative coordinates, empty strings)
  • ❌ Concurrent operations
  • ❌ Connection cleanup
  • ❌ Text update vs. create logic

Recommendations Summary

Before Merge:

  1. Fix test-implementation mismatch (Critical [SVC-2555] Set up issue templates #1)
  2. Add newline to .gitignore

After Merge (Future Improvements):
3. Add input validation
4. Improve error handling
5. Consider performance optimizations for high-frequency updates
6. Enhance accessibility

Overall, this is a solid example that demonstrates RivetKit's capabilities well! The critical issue is the test mismatch - everything else is relatively minor for an example project. Great work! 🎉

@NathanFlurry NathanFlurry force-pushed the 10-22-feat_core_allow_routing_traffic_to_actors_via_path branch from 89114d1 to e5f9730 Compare October 23, 2025 08:37
@NathanFlurry NathanFlurry force-pushed the 10-22-feat_examples-cursors_cursors_example branch from 23bc2c4 to db3c3ad Compare October 23, 2025 08:37
@claude
Copy link

claude bot commented Oct 23, 2025

PR Review: Cursors Example

Overview

This PR adds a real-time collaborative cursors example demonstrating RivetKit's capabilities. The implementation is well-structured and provides a good showcase of the framework's features.

✅ Strengths

  1. Clear Architecture: Clean separation between backend (actor registry) and frontend (React UI)
  2. Good Type Safety: Proper TypeScript interfaces for CursorPosition and TextLabel
  3. Comprehensive Tests: Test coverage includes cursor updates, text placement, removal, and multi-user scenarios
  4. User Experience:
    • Responsive canvas with proper scaling
    • Color-coded cursors per user
    • Real-time updates via broadcast events
    • Visual feedback for typing state

🐛 Issues Found

Critical

  1. Test Implementation Mismatch (tests/cursors.test.ts)

    • Tests call methods that don't exist in the registry:
      • getCursors() - not defined as an action
      • getTextLabels() - not defined as an action
      • placeText() - registry has updateText() instead
      • removeCursor() - not defined as an action

    Fix: Update tests to match actual registry actions or add missing actions:

    // Either update getRoomState to return cursors separately:
    getCursors: (c) => {
        const cursors: Record<string, CursorPosition> = {};
        for (const conn of c.conns.values()) {
            if (conn.state.cursor) {
                cursors[conn.state.cursor.userId] = conn.state.cursor;
            }
        }
        return cursors;
    },
    getTextLabels: (c) => c.state.textLabels,
  2. Missing File Extension (.gitignore:2)

    • File ends without newline - should have a trailing newline for POSIX compliance

High Priority

  1. Unused Event Handler (App.tsx:117)

    • cursorRemoved event is listened for but never emitted by the backend
    • Connection-based cursor removal happens automatically via connState, but there's no broadcast

    Fix: Add cursor removal broadcast in registry or remove the unused event handler

  2. Error Handling (App.tsx:85)

    • Empty catch block only logs to console - should show user-facing error state

    Suggestion:

    const [loadError, setLoadError] = useState<string | null>(null);
    // ... in useEffect:
    .catch((error) => {
        console.error('error loading room state', error);
        setLoadError('Failed to load room state');
    });
  3. Race Condition Potential (App.tsx:159)

    • updateText called on every keystroke, could cause race conditions with rapid typing
    • Consider debouncing updates or optimistic local updates

Medium Priority

  1. Inconsistent Text Update Logic (registry.ts:30-71)

    • updateText uses an id parameter passed from client, but client generates IDs
    • This could lead to ID collisions if multiple clients use the same ID generation strategy

    Suggestion: Generate IDs server-side or use a more robust UUID library

  2. Memory Leak Risk (App.tsx:92)

    • Cursors accumulate in state and are never cleaned up when users disconnect
    • While connState.cursor is cleaned up, the frontend state persists

    Fix: Implement proper cleanup or add cursorRemoved broadcast

  3. Logging Convention (App.tsx:85)

    • Log message should be lowercase per CLAUDE.md: 'error loading room state' is correct format ✓

🎯 Code Quality

Good Practices Observed

  • ✅ Structured logging convention followed
  • ✅ TypeScript types properly defined
  • ✅ React hooks used correctly
  • ✅ Workspace dependencies used appropriately
  • ✅ Naming conventions followed (e.g., created_at pattern for timestamps)

Suggestions

  1. Magic Numbers (App.tsx:36-37)

    const CANVAS_WIDTH = 1920;
    const CANVAS_HEIGHT = 1080;

    Consider making these configurable or documented as "virtual canvas size"

    • Actually, there is a comment on line 35! ✓
  2. State Management (App.tsx:42-49)

    • Multiple useState calls could be consolidated into useReducer for cleaner state transitions
    • Current approach is acceptable but becomes harder to manage as complexity grows
  3. Canvas Coordinate Conversion (App.tsx:126-134)

    • screenToCanvas function is well-implemented
    • Consider memoizing with useCallback since it's used in mouse move handler

🔒 Security

  • ✅ No obvious security concerns
  • ✅ CORS properly configured for development
  • ✅ No sensitive data exposure
  • ⚠️ User IDs are client-generated - fine for example but would need server validation in production

⚡ Performance

  1. Mouse Move Performance (App.tsx:137-141)

    • Mouse move handler triggers WebSocket messages on every pixel movement
    • Consider throttling updates (e.g., max 60fps / ~16ms intervals)

    Suggestion:

    const throttledUpdate = useCallback(
        throttle((x: number, y: number) => {
            cursorRoom.connection?.updateCursor(userId, x, y);
        }, 16), // ~60fps
        [cursorRoom.connection, userId]
    );
  2. Re-render Optimization (App.tsx:230)

    • Cursor components re-render on every cursor update
    • Consider memoizing cursor components with React.memo

📚 Documentation

  • ✅ Good README with clear getting started instructions
  • ✅ Features clearly documented
  • ✅ Code comments where helpful
  • ⚠️ Missing JSDoc comments for actor actions - would help API documentation

🧪 Test Coverage

Current Coverage: Good basic coverage

Missing Test Cases:

  • Connection/disconnection handling
  • Error scenarios (network failures, invalid data)
  • Concurrent cursor updates
  • Text label edge cases (empty text, very long text)
  • Canvas boundary conditions
  • Scale calculation edge cases

📝 Recommendations

Must Fix Before Merge

  1. ✅ Fix test implementation to match registry actions
  2. ✅ Add newline to end of .gitignore

Should Fix Before Merge

  1. Remove unused cursorRemoved event listener or implement proper cursor cleanup
  2. Add user-facing error handling for room state loading
  3. Consider debouncing/throttling text updates on keystroke

Nice to Have

  1. Add throttling to mouse move updates
  2. Implement proper cursor cleanup on disconnect
  3. Add more comprehensive test coverage
  4. Consider memoizing expensive render operations

🎉 Conclusion

This is a solid example implementation that effectively demonstrates RivetKit's real-time capabilities. The code is generally well-structured and follows the project's conventions. The main issues are test mismatches and some missing edge case handling, which should be addressed before merging.

Recommendation: ✅ Approve with requested changes

Great work on this example! 🚀

@claude
Copy link

claude bot commented Oct 23, 2025

PR Review: Cursors Example

Summary

This PR adds a real-time collaborative cursors example demonstrating RivetKit's actor capabilities. The implementation includes cursor tracking, text placement, and multi-room support with a React frontend.

Code Quality & Best Practices

Strengths ✅

  • Clean architecture: Well-separated backend (actor registry) and frontend (React UI)
  • Type safety: Comprehensive TypeScript types for CursorPosition and TextLabel
  • Testing: Good test coverage with 5 test cases covering core functionality
  • User experience: Smooth cursor tracking with visual feedback and color-coded users

Issues & Recommendations

1. State Management Bug in registry.ts ⚠️

Location: src/backend/registry.ts:30-38

The updateCursor action stores cursor position in connState, but getCursors iterates over all connections to build a cursors object. This could lead to stale cursor data not being cleaned up when users disconnect.

Recommendation:

  • Add a cleanup action or lifecycle hook to remove cursor from shared state on disconnect
  • Consider storing cursors in shared state instead of connState with explicit removal logic
// Suggested approach - add to actions:
onDisconnect: (c) => {
    if (c.conn.state.cursor) {
        c.broadcast("cursorRemoved", c.conn.state.cursor);
    }
}

2. Missing Error Handling in App.tsx ⚠️

Location: src/frontend/App.tsx:80-87

The getRoomState() call uses .catch() but only logs to console. Users won't see feedback if loading fails.

Recommendation: Add user-facing error state:

const [loadError, setLoadError] = useState<string | null>(null);

// In useEffect:
.catch((error) => {
    console.error('error loading room state', error);
    setLoadError('Failed to load room state');
});

3. Incorrect Test Implementation 🐛

Location: tests/cursors.test.ts

Multiple issues with the test file:

  • Line 20: Calls room.getCursors() but the action is getRoomState() which returns { cursors, textLabels }
  • Line 43: Calls room.getTextLabels() but this doesn't exist
  • Line 52: Calls room.placeText() but the action is updateText() and requires an id parameter
  • Line 71: Calls room.removeCursor() but there's no such action in the registry

These tests will fail when run.

Recommendation: Rewrite tests to match actual API:

test("Cursor room can handle cursor updates", async (ctx) => {
    const { client } = await setupTest(ctx, registry);
    const room = client.cursorRoom.getOrCreate(["test-room"]);
    
    const state = await room.getRoomState();
    expect(state.cursors).toEqual({});
    
    await room.updateCursor("user1", 100, 200);
    const updatedState = await room.getRoomState();
    expect(updatedState.cursors.user1).toBeDefined();
});

4. Event Handler Not Defined 🐛

Location: src/frontend/App.tsx:116-122

The frontend listens for cursorRemoved event, but the backend registry doesn't emit this event anywhere. This listener is dead code.

Recommendation: Either:

  • Remove the unused event listener
  • Implement cursor cleanup in the backend to emit this event

5. Missing Newline at EOF ⚠️

Location: .gitignore:2, index.html:191

Files should end with a newline character (POSIX standard).

6. Inconsistent Naming Convention 📝

Location: src/backend/registry.ts:51

The action is named updateText but it's used for both creating and updating. Consider renaming to upsertText or splitting into createText and updateText for clarity.

7. Magic Number 📝

Location: src/frontend/App.tsx:23-30

The CURSOR_COLORS array is hard-coded. Consider moving to a constants file or making it configurable.

Performance Considerations

Potential Issues

  1. Broadcast on Every Cursor Move (Line 38 in registry.ts)

    • Every mouse movement triggers a broadcast to all connected clients
    • Recommendation: Implement throttling/debouncing on the client side before calling updateCursor
  2. No Cleanup for Old Text Labels

    • Text labels accumulate indefinitely in state
    • Recommendation: Add a mechanism to archive or remove old labels, or set a maximum limit
  3. Re-renders on Cursor Updates

    • Every cursor movement causes React re-renders
    • Recommendation: Consider using React.memo for cursor components or virtualization for many cursors

Security Concerns

Issues Found

  1. No Input Validation ⚠️

    • No validation on userId, text, x, y parameters in actions
    • Malicious clients could send invalid coordinates or oversized text
    • Recommendation: Add validation:
    updateText: (c, id: string, userId: string, text: string, x: number, y: number) => {
        // Validate input
        if (text.length > 500) throw new Error("Text too long");
        if (x < 0 || x > CANVAS_WIDTH || y < 0 || y > CANVAS_HEIGHT) {
            throw new Error("Invalid coordinates");
        }
        // ... rest of logic
    }
  2. CORS Configuration ⚠️

    • src/backend/server.ts:4-5 hardcodes localhost origin
    • Fine for development, but should be configurable via environment variables for production
  3. No Rate Limiting

    • A malicious client could spam cursor updates or text placements
    • Recommendation: Consider adding rate limiting at the actor or server level

Test Coverage

Strengths ✅

  • Tests cover core functionality (cursor updates, text placement, removal)
  • Tests verify data structures and state management

Issues ⚠️

Recommendations

  1. Fix all test implementations to match actual API
  2. Add tests for:
    • Concurrent cursor updates from multiple users
    • Text label updates (not just creation)
    • Empty/whitespace-only text handling
    • Connection/disconnection scenarios

Additional Notes

Documentation

  • README.md is well-written and clear ✅
  • Missing inline code documentation/comments for complex logic
  • Consider adding JSDoc comments for public actions

Code Style

  • Code generally follows TypeScript best practices ✅
  • Consistent formatting throughout ✅
  • Consider running a linter to catch unused imports/variables

Verdict

⚠️ NEEDS WORK - While the concept and architecture are solid, there are several bugs that prevent this example from working correctly:

  1. Test file has incorrect API calls that will fail
  2. Missing event emission for cursor removal
  3. No error handling for critical operations
  4. Security and validation concerns

Required Changes

  1. Fix all test implementations
  2. Add error handling in frontend
  3. Either implement cursorRemoved event or remove the listener
  4. Add input validation

Recommended Changes

  1. Implement cursor throttling for performance
  2. Add rate limiting
  3. Move CORS origin to environment variable
  4. Add JSDoc documentation
  5. Consider state cleanup mechanisms

Overall: This is a great example showcasing RivetKit's capabilities, but it needs bug fixes before merge. The architecture is sound and the UX is polished. Once the tests are fixed and error handling is added, this will be an excellent learning resource. 👍

@NathanFlurry NathanFlurry force-pushed the 10-22-feat_examples-cursors_cursors_example branch from db3c3ad to 120bdcb Compare October 24, 2025 06:32
@NathanFlurry NathanFlurry force-pushed the 10-22-feat_core_allow_routing_traffic_to_actors_via_path branch from e5f9730 to a5f5632 Compare October 24, 2025 06:32
@claude
Copy link

claude bot commented Oct 24, 2025

Pull Request Review: Cursors Example

Summary

This PR adds a real-time collaborative cursors example for RivetKit. Overall, this is a high-quality example that demonstrates RivetKit's capabilities well. The code is clean, well-structured, and includes comprehensive tests. However, I've identified several issues that should be addressed.


🔴 Critical Issues

1. Test-Backend API Mismatch

Location: tests/cursors.test.ts

The tests reference methods that don't exist in the backend registry:

  • Line 10, 28, 78: room.getCursors() - doesn't exist
  • Line 43: room.getTextLabels() - doesn't exist
  • Line 47, 60, 113-116: room.placeText() - doesn't exist
  • Line 82: room.removeCursor() - doesn't exist

The backend only exposes:

  • updateCursor(userId, x, y)
  • updateText(id, userId, text, x, y)
  • removeText(id)
  • getRoomState()

Impact: All tests will fail when run.

Fix Required: Either:

  1. Update tests to use the actual API (updateText instead of placeText, getRoomState instead of separate getters), OR
  2. Add the missing methods to the backend registry

🟡 Medium Priority Issues

2. Missing Input Validation

Location: src/backend/registry.ts:30-72

The updateCursor and updateText actions don't validate inputs:

  • No bounds checking for x and y coordinates
  • No validation for text length (could cause DoS with extremely long strings)
  • No sanitization of userId or id parameters

Recommendation: Add validation:

updateText: (c, id: string, userId: string, text: string, x: number, y: number) => {
    // Validate text length
    if (text.length > 1000) {
        throw new Error('text too long');
    }
    
    // Validate coordinates
    if (!Number.isFinite(x) || !Number.isFinite(y)) {
        throw new Error('invalid coordinates');
    }
    
    // ... rest of implementation
}

3. Memory Leak Potential

Location: src/backend/registry.ts:21

The textLabels array grows unbounded. In a long-running room, this could consume excessive memory.

Recommendation: Add a limit or cleanup strategy:

  • Maximum number of text labels per room
  • Timestamp-based cleanup of old labels
  • Document the expected behavior

4. Race Condition in Frontend

Location: src/frontend/App.tsx:336-342

The onBlur handler can race with the Enter key handler, potentially removing text that should be finalized:

onBlur={() => {
    if (!textInput.trim() && cursorRoom.connection && currentTextId) {
        cursorRoom.connection.removeText(currentTextId);
        setCurrentTextId(null);
    }
    setIsTyping(false);
}}

Recommendation: Add debouncing or check isTyping state before removing.

5. Hardcoded Backend URL

Location: src/frontend/App.tsx:9

const { useActor } = createRivetKit<typeof registry>("http://localhost:6420");

Hardcoding localhost:6420 prevents production deployment.

Recommendation: Use environment variables:

const BACKEND_URL = import.meta.env.VITE_BACKEND_URL || "http://localhost:6420";
const { useActor } = createRivetKit<typeof registry>(BACKEND_URL);

🟢 Minor Issues / Suggestions

6. Missing Newline at EOF

Location: examples/cursors/.gitignore:2

The .gitignore file is missing a trailing newline (standard practice).

7. Unused Event Listener

Location: src/frontend/App.tsx:118-124

The cursorRemoved event is listened to but never emitted by the backend. Either:

  • Remove the listener, OR
  • Document that cursor cleanup is handled by connState cleanup

8. Console.error Instead of Proper Error Handling

Location: src/frontend/App.tsx:85

.catch((error) => {
    console.error('error loading room state', error);
});

Recommendation: Show user-facing error message or retry logic.

9. TypeScript Inconsistency

Location: src/frontend/App.tsx:101, 113

Using prev.findIndex(l => l.id === label.id) - the arrow function parameter l could be more descriptive (label is already in scope).

Suggestion: Use (existingLabel) => for clarity.

10. Performance: O(n) Text Label Updates

Location: src/frontend/App.tsx:100-109

Every text update triggers a findIndex scan. For rooms with many labels, consider using a Map for O(1) lookups.


✅ Positive Aspects

  1. Excellent code organization - clear separation of backend/frontend
  2. Good UX design - typing hints, color-coded users, responsive canvas
  3. Comprehensive test coverage (once API mismatch is fixed)
  4. Clean React patterns - proper use of hooks and refs
  5. Well-documented - good comments and README
  6. Proper TypeScript usage - strong typing throughout
  7. Good example structure - easy to understand and extend

🔒 Security Notes

No major security concerns for an example project, but for production use:

  • Add rate limiting on cursor updates (high-frequency events)
  • Sanitize text content before rendering (XSS prevention)
  • Add room access controls if needed
  • Consider userId authentication/validation

📋 Recommendations

Before Merging:

  1. Fix test API mismatch (critical)
  2. ✅ Add input validation to backend actions
  3. ✅ Fix hardcoded backend URL
  4. ✅ Add trailing newline to .gitignore

Future Enhancements:

  • Add text label limit or cleanup
  • Improve error handling in frontend
  • Add cursor throttling for performance
  • Consider using Map for text label storage

Conclusion

This is a well-crafted example that will be valuable for the RivetKit community. The main blocker is the test/backend API mismatch which will cause test failures. Once the critical issues are addressed, this will be ready to merge.

Great work overall! 🎉

@claude
Copy link

claude bot commented Oct 24, 2025

PR Review: Cursors Example for RivetKit

This is a well-crafted example demonstrating real-time collaborative cursor tracking. The code is generally clean and functional, but there are several issues that need addressing.


Critical Issues

1. Test-Implementation Mismatch ⚠️

Location: tests/cursors.test.ts

The tests call methods that don't exist in the backend implementation:

  • Tests call room.getCursors(), room.getTextLabels(), room.placeText(), room.removeCursor()
  • Backend only implements: getRoomState(), updateText(), removeText(), updateCursor()

Impact: All tests will fail when run.

Fix needed: Either:

  1. Update tests to use the correct API (getRoomState() instead of getCursors()/getTextLabels())
  2. Add the missing methods to the backend registry, OR
  3. Create helper methods/getters to match the test API

Bugs

2. Missing Event Broadcast 🐛

Location: src/backend/registry.ts:75-79

removeText: (c, id: string) => {
    c.state.textLabels = c.state.textLabels.filter(
        (label) => label.id !== id,
    );
    c.broadcast("textRemoved", id);
},

The removeText action doesn't return anything, but the frontend expects a response. This could cause issues if the frontend code expects a return value.

3. Cursor Event Handler for Non-existent Event 🐛

Location: src/frontend/App.tsx:118-124

// Listen for cursor removal (when connection closes)
cursorRoom.useEvent("cursorRemoved", (cursor: CursorPosition) => {
    setCursors((prev) => {
        const newCursors = { ...prev };
        delete newCursors[cursor.userId];
        return newCursors;
    });
});

The backend never broadcasts a "cursorRemoved" event. Cursors are stored in connState, so they're automatically cleaned up when a connection closes, but the frontend never receives notification of this.

Impact: When users disconnect, their cursors remain visible to other users.

Fix: Either remove this event handler or implement cursor removal broadcasting in the backend when connections close.


Security & Input Validation

4. Missing Input Validation 🔒

Location: src/backend/registry.ts

No validation on user inputs:

  • userId is not validated (could be extremely long, contain malicious content)
  • text has no length limits (could cause memory issues)
  • x, y coordinates are not validated (could be negative, NaN, Infinity)
  • id format is not validated

Recommendation: Add validation:

updateText: (c, id: string, userId: string, text: string, x: number, y: number) => {
    // Validate inputs
    if (!id || id.length > 100) throw new Error("Invalid id");
    if (!userId || userId.length > 100) throw new Error("Invalid userId");
    if (!text || text.length > 1000) throw new Error("Text too long");
    if (!Number.isFinite(x) || !Number.isFinite(y)) throw new Error("Invalid coordinates");
    // ... rest of implementation
}

5. No Rate Limiting 🔒

Location: src/backend/registry.ts:30-40

The updateCursor action has no throttling or rate limiting. A malicious user could spam cursor updates and overwhelm the server/other clients.

Recommendation: Implement rate limiting or throttling on cursor updates.


Code Quality Issues

6. Inconsistent Error Handling

Location: src/frontend/App.tsx:84-86

.catch((error) => {
    console.error('error loading room state', error);
});

Per CLAUDE.md guidelines, log messages should be lowercase. Also, using console.error instead of proper error handling could hide issues in production.

Suggestion: Use structured logging if available, or at minimum handle the error more gracefully (show user feedback).

7. Missing TypeScript Return Types

Location: src/backend/registry.ts

Actions don't have explicit return type annotations. While TypeScript can infer them, explicit types improve code clarity and catch errors.

Recommendation:

updateCursor: (c, userId: string, x: number, y: number): CursorPosition => {
    // ...
}

8. Potential Memory Leak ⚠️

Location: src/backend/registry.ts:21

state: {
    textLabels: [] as TextLabel[],
},

Text labels grow unbounded. In a long-running room, this array could grow indefinitely.

Recommendation: Implement:

  • Maximum label count (remove oldest when limit reached)
  • TTL for text labels
  • Manual cleanup API

9. Performance: Inefficient Text Updates

Location: src/backend/registry.ts:61-68

const existingIndex = c.state.textLabels.findIndex(
    (label) => label.id === id,
);

Using findIndex is O(n). For better performance with many labels, consider using a Map instead of an array.


Minor Issues

10. Hardcoded URL

Location: src/frontend/App.tsx:9

const { useActor } = createRivetKit<typeof registry>("http://localhost:6420");

Should use environment variables for configuration.

11. Magic Number

Location: src/frontend/App.tsx:150

const newTextId = `${userId}-${Date.now()}`;

ID generation using timestamp could theoretically collide if the same user clicks multiple times in the same millisecond. Consider using crypto.randomUUID() or adding a counter.

12. Missing Accessibility

Location: src/frontend/App.tsx

The canvas interface lacks proper ARIA labels and keyboard navigation support beyond typing. Users relying on screen readers won't get cursor position information.


Positive Aspects ✅

  1. Clean Architecture: Clear separation between backend/frontend, good use of TypeScript
  2. Good UX: The typing interaction with Enter/Escape is intuitive
  3. Responsive Design: Canvas scaling logic is well-implemented
  4. Code Clarity: Functions are well-named and commented
  5. Color Hashing: Deterministic color assignment is clever and works well

Test Coverage Gaps

Missing test scenarios:

  • Concurrent text updates to the same ID
  • Invalid input handling (negative coordinates, empty strings, etc.)
  • Maximum text label limits
  • Room state synchronization with multiple connections
  • Event broadcasting verification
  • Connection cleanup behavior

Recommendations

High Priority

  1. ✅ Fix test-implementation mismatch - tests won't run
  2. ✅ Add input validation to prevent abuse
  3. ✅ Fix cursor removal event or remove the handler
  4. ✅ Implement bounds on text label storage

Medium Priority

  1. Add rate limiting on cursor updates
  2. Use crypto.randomUUID() for text IDs
  3. Add environment configuration
  4. Add proper error handling in frontend

Low Priority

  1. Add explicit TypeScript return types
  2. Improve accessibility
  3. Add more comprehensive tests

Overall Assessment

This is a solid example that demonstrates RivetKit's capabilities well. The core functionality is sound, but the critical test failures and missing input validation need to be addressed before merging. The cursor removal event discrepancy should also be resolved.

Recommendation: Request changes for the test failures and input validation. The other issues can be addressed as follow-up improvements.

@NathanFlurry NathanFlurry force-pushed the 10-22-feat_examples-cursors_cursors_example branch from 120bdcb to b2d5bfe Compare October 24, 2025 20:40
@NathanFlurry NathanFlurry force-pushed the 10-22-feat_core_allow_routing_traffic_to_actors_via_path branch from a5f5632 to 82faa7e Compare October 24, 2025 20:40
@claude
Copy link

claude bot commented Oct 24, 2025

Pull Request Review: Cursors Example

Summary

This PR adds a real-time collaborative cursors example demonstrating RivetKit's actor system capabilities. Overall, the implementation is well-structured and demonstrates good practices. However, there are several issues that need to be addressed.

Critical Issues

1. Backend API Mismatch with Tests ⚠️

The test file references methods that don't exist in the actual actor implementation:

Tests reference (cursors.test.ts):

  • room.getCursors() - doesn't exist
  • room.getTextLabels() - doesn't exist
  • room.placeText() - doesn't exist
  • room.removeCursor() - doesn't exist

Actual implementation (registry.ts):

  • updateCursor()
  • updateText()
  • removeText()
  • getRoomState() - returns both cursors and textLabels

Impact: All tests will fail when run.

Fix: Either update the tests to match the actual API, or update the backend to match the test expectations. I recommend updating the tests since the actual implementation appears more complete.

2. Missing .gitignore Newline 🔧

File: examples/cursors/.gitignore

The file is missing a trailing newline, which violates standard Unix text file conventions.

Fix: Add a newline at the end of the file.

3. Connection State Cleanup Issue 🐛

File: src/frontend/App.tsx:196

There's a comment about cursor removal on connection close:

// Cursor is automatically removed when connection closes via connState cleanup

However, there's no cursorRemoved event being broadcast from the backend when a connection closes. The frontend listens for this event (line 117), but it will never fire.

Fix: Either:

  1. Remove the event listener and comment if relying on automatic state cleanup, OR
  2. Add a connection close handler in the backend that broadcasts the cursorRemoved event

Code Quality Issues

4. Inconsistent Error Handling ⚠️

File: src/frontend/App.tsx:84-86

cursorRoom.connection.getRoomState().then((state) => {
    setCursors(state.cursors);
    setTextLabels(state.textLabels);
}).catch((error) => {
    console.error('error loading room state', error);
});

Issues:

  • Using console.error instead of proper structured logging
  • Error is logged but not displayed to the user
  • No recovery mechanism if initial state load fails

Recommendation: Add user-facing error state and display, similar to the loading overlay pattern already used.

5. Missing Input Validation 🔒

File: src/backend/registry.ts

The updateCursor and updateText actions don't validate inputs:

  • No bounds checking on x/y coordinates
  • No length limits on text content
  • No validation on userId format

Security Impact: Could allow malicious clients to:

  • Send extremely large coordinate values
  • Send very long text strings (potential DoS)
  • Send malformed user IDs

Recommendation: Add validation:

updateCursor: (c, userId: string, x: number, y: number) => {
    // Validate coordinates are within reasonable bounds
    if (!isFinite(x) || !isFinite(y) || x < 0 || y < 0 || x > 10000 || y > 10000) {
        throw new Error('Invalid coordinates');
    }
    if (!userId || userId.length > 100) {
        throw new Error('Invalid userId');
    }
    // ... rest of implementation
}

6. Type Safety Issue 🔧

File: src/frontend/App.tsx:100

setTextLabels((prev) => {
    const existingIndex = prev.findIndex(l => l.id === label.id);
    // ...
});

The arrow function parameters don't have explicit types. While TypeScript can infer them, explicit typing improves code clarity.

7. Missing HTML File Extension 📁

File: examples/cursors/src/frontend/index.html:191

The file is missing a newline at the end, similar to the .gitignore issue.

Performance Considerations

8. Cursor Movement Throttling

File: src/frontend/App.tsx:138-141

const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
    if (cursorRoom.connection && canvasRef.current) {
        const { x, y } = screenToCanvas(e.clientX, e.clientY);
        cursorRoom.connection.updateCursor(userId, x, y);
    }
};

Issue: Sends cursor updates on every mouse move event without throttling. This could generate hundreds of network requests per second.

Impact:

  • High network bandwidth usage
  • Potential server overload with many users
  • Poor performance on slower connections

Recommendation: Implement throttling/debouncing:

const handleMouseMove = useCallback(
    throttle((e: React.MouseEvent<HTMLDivElement>) => {
        if (cursorRoom.connection && canvasRef.current) {
            const { x, y } = screenToCanvas(e.clientX, e.clientY);
            cursorRoom.connection.updateCursor(userId, x, y);
        }
    }, 50), // Update at most every 50ms (20 updates/sec)
    [cursorRoom.connection]
);

9. Inefficient Text Label Updates

File: src/frontend/App.tsx:156-168

Every character typed triggers a network request to update the text label. For a user typing "Hello World", that's 11 separate network requests.

Recommendation: Implement local echo with debounced server sync:

  • Update local state immediately for responsive UI
  • Debounce server updates to every 500ms or on Enter key

Documentation Issues

10. README Inconsistency 📝

File: examples/cursors/README.md:19-20

git clone https://github.com/rivet-dev/rivetkit
cd rivetkit/examples/cursors

The repository URL is rivet-dev/rivet (as seen in this PR), not rivet-dev/rivetkit.

Fix: Update to:

git clone https://github.com/rivet-dev/rivet
cd rivet/examples/cursors

11. Missing Development Instructions 📝

The README doesn't mention:

  • How to run tests (npm run test)
  • Backend/frontend are started together with npm run dev
  • What ports are used (backend likely 6420, frontend 5173)

Test Coverage Assessment

12. Incomplete Test Coverage ⚠️

File: tests/cursors.test.ts

Missing test scenarios:

  • Concurrent cursor updates from multiple users
  • Edge cases (negative coordinates, very large values, special characters in text)
  • Text update/removal operations (tests only check placeText which doesn't exist)
  • Connection state lifecycle
  • Event broadcasting verification
  • Room state after text removal

What's tested well:

  • Basic cursor placement (if API was correct)
  • Multi-user scenarios (if API was correct)
  • Timestamp ordering

Positive Aspects ✅

  1. Good Architecture: Clean separation of backend/frontend, proper use of actor pattern
  2. Type Safety: Strong TypeScript usage throughout
  3. User Experience: Nice features like color-coded cursors, real-time updates, multiple room support
  4. Code Organization: Well-structured files and logical component breakdown
  5. Styling: Professional UI with good dark theme implementation
  6. Documentation: README provides clear getting started instructions (aside from minor issues)

Recommendations Summary

Must Fix Before Merge:

  1. ❗ Fix test API mismatch - tests will fail as written
  2. ❗ Add input validation to prevent potential DoS/malformed data
  3. ❗ Add throttling to cursor movement events

Should Fix:
4. Add newlines to end of .gitignore and index.html
5. Fix README repository URL
6. Improve error handling with user-facing messages
7. Clarify cursor removal behavior on disconnect

Nice to Have:
8. Debounce text input updates
9. Add more comprehensive tests
10. Add explicit types to arrow functions
11. Expand documentation with test/development instructions

Overall Assessment

This is a solid example project that demonstrates RivetKit's capabilities well. The code is clean and well-organized. However, the test suite needs significant fixes to match the actual implementation, and production-readiness requires input validation and performance optimizations.

Recommendation: Request changes - address critical issues (tests, validation, throttling) before merging.

@claude
Copy link

claude bot commented Oct 24, 2025

PR Review: Cursors Example

Great work on creating this collaborative cursors example! This is a well-structured demonstration of RivetKit's real-time capabilities. Here's my detailed review:

✅ Strengths

  1. Clean Architecture: Nice separation of backend (actor registry) and frontend (React app)
  2. Good Documentation: README is clear and helpful with setup instructions
  3. Test Coverage: Comprehensive test suite covering the main functionality
  4. Type Safety: Excellent use of TypeScript throughout
  5. User Experience: The visual design and interaction patterns are intuitive

🐛 Critical Issues

Test API Mismatch

The tests reference methods that don't exist in the registry:

// tests/cursors.test.ts:10, 28, 43, 47, 60, 82
await room.getCursors()         // ❌ Not defined
await room.getTextLabels()      // ❌ Not defined  
await room.placeText(...)       // ❌ Not defined
await room.removeCursor(...)    // ❌ Not defined

The registry only defines:

  • updateCursor
  • updateText
  • removeText
  • getRoomState

Fix needed: Either:

  1. Update tests to use getRoomState() and extract cursors/labels from it
  2. Add the missing helper methods to the registry

⚠️ Issues to Address

1. Missing Error Handling in Frontend

// App.tsx:141
cursorRoom.connection.updateCursor(userId, x, y);

No try-catch blocks around actor calls. If the connection drops or action fails, errors will be unhandled.

Recommendation: Add error handling:

try {
  cursorRoom.connection.updateCursor(userId, x, y);
} catch (error) {
  console.error('failed to update cursor', error);
}

2. Input Sanitization Missing

The updateText action accepts arbitrary text without validation:

// registry.ts:43-50
updateText: (c, id: string, userId: string, text: string, x: number, y: number)

Security concerns:

  • No length limits (could cause memory issues with very long strings)
  • No content filtering (could be used for inappropriate content)

Recommendation: Add validation:

const MAX_TEXT_LENGTH = 1000;
const MAX_LABELS_PER_ROOM = 10000;

if (text.length > MAX_TEXT_LENGTH) {
  throw new Error(\`text exceeds maximum length of \${MAX_TEXT_LENGTH}\`);
}
if (c.state.textLabels.length >= MAX_LABELS_PER_ROOM) {
  throw new Error('room has reached maximum number of text labels');
}

3. Performance: Unbounded State Growth

// registry.ts:67
c.state.textLabels.push(textLabel);

Text labels accumulate indefinitely. In a long-running room, this could consume significant memory.

Recommendation: Add cleanup mechanism:

  • Maximum label count
  • Time-based expiration
  • Manual cleanup action

4. Race Condition in Text Input

// App.tsx:336-342
onBlur={() => {
  if (!textInput.trim() && cursorRoom.connection && currentTextId) {
    cursorRoom.connection.removeText(currentTextId);
    setCurrentTextId(null);
  }
  setIsTyping(false);
}}

If the user clicks away while typing, the blur handler might race with the Enter key handler.

Recommendation: Use a ref to track whether text was committed.

5. Coordinate Validation Missing

No bounds checking on x/y coordinates. Malicious clients could send invalid coordinates.

Recommendation:

if (x < 0 || x > CANVAS_WIDTH || y < 0 || y > CANVAS_HEIGHT) {
  throw new Error('coordinates out of bounds');
}

📊 Performance Considerations

  1. Cursor Update Frequency: Mouse move events fire very frequently. Consider throttling:
const throttledUpdateCursor = useThrottle((x, y) => {
  cursorRoom.connection.updateCursor(userId, x, y);
}, 50); // Update max once per 50ms
  1. Broadcast on Every Keystroke: handleTextChange calls updateText on every character typed (line 161). This could generate many broadcasts for minimal changes.

Recommendation: Debounce text updates while typing, only broadcast when user pauses.

  1. Re-renders: Object.entries(cursors).map() on line 282 creates new entries array on every render.

Recommendation: Use useMemo to memoize the cursor entries.


🔒 Security Concerns

  1. No Authentication: userId is client-generated (line 13). Users can impersonate others by setting the same userId.

Recommendation: Add server-side user identity validation.

  1. XSS Risk: Text content is rendered directly into DOM. While React escapes by default, be cautious with any future changes.

  2. CORS Configuration: Hardcoded to localhost:5173 (server.ts:5). Fine for example, but document that production apps need proper CORS setup.


📝 Code Quality & Style

Minor Issues:

  1. Inconsistent Comments: Some comments use //, which is fine, but be consistent with capitalization (lowercase per CLAUDE.md logging convention).

  2. Magic Numbers:

    • Line 13: .substring(2, 9) - unclear why 9
    • Line 150: userId-${Date.now()} - might collide if user clicks rapidly
  3. Dead Code: Line 201 has a comment with no associated code.

  4. .gitignore: Missing newline at end of file (line 2).

  5. Event Listener: cursorRemoved event is registered (line 118) but never emitted by the backend.


✨ Suggestions for Enhancement

  1. Cursor Cleanup: Add TTL for inactive cursors (e.g., remove after 30s of inactivity)

  2. Text Editing: Allow users to edit/delete their own text labels

  3. Undo/Redo: Track text label history

  4. Room Listing: Show active rooms and user counts

  5. Cursor Smoothing: Interpolate cursor positions for smoother animations

  6. Mobile Support: Add touch event handlers


🧪 Testing

Good coverage of:

  • ✅ Basic cursor updates
  • ✅ Text placement
  • ✅ Multi-user scenarios

Missing tests for:

  • ❌ Edge cases (empty strings, very long text, special characters)
  • ❌ Concurrent updates to the same text label
  • ❌ Room state synchronization after connection issues
  • ❌ Performance under high message frequency

📋 Summary

This is a solid example that demonstrates RivetKit well! The main blockers are:

  1. Critical: Fix test API mismatch - tests will fail as written
  2. Important: Add input validation and bounds checking
  3. Recommended: Add error handling in frontend
  4. Nice-to-have: Performance optimizations (throttling, debouncing)

The code is clean and well-structured. After addressing the test issues and adding validation, this will be an excellent example for users to learn from.

Overall: Approve with requested changes 👍


Review generated by Claude Code

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.

1 participant