Add multi-user chat sharing foundation#18
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
✅ Files skipped from review due to trivial changes (1)
📝 WalkthroughWalkthroughThis PR adds a multi-user chat MVP: a docs spec, a relay-side sharing module with invite token creation/acceptance, share state persistence and normalization, guest request gating and display helpers, Vitest tests covering major flows, and test discovery configuration updates. ChangesMulti-User Chat Sharing
Sequence Diagram(s)sequenceDiagram
participant OwnerCLI
participant RelayDO
participant EmailService
participant GuestBrowser
participant Daemon
OwnerCLI->>RelayDO: createShareInvite(owner, sessionId, inviteEmail)
RelayDO->>EmailService: sendInviteLink(hashed token metadata)
GuestBrowser->>RelayDO: acceptInvite(link -> Clerk auth, token)
RelayDO->>RelayDO: validate token hash, activate guest participant
RelayDO->>Daemon: proxy guest request with injected trusted actor metadata
🎯 4 (Complex) | ⏱️ ~45 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (2)
relay/src/sharing.test.ts (2)
145-148: 💤 Low valueConsider adding edge cases for display prefix sanitization.
The current test covers basic whitespace normalization and empty string handling. Consider adding test cases for:
- Whitespace-only strings (e.g.,
" ")- Names with special characters or Unicode
- Very long display names
However, if the current coverage aligns with your "focused tests" goal, this is acceptable as-is.
🧪 Optional additional test cases
it("sanitizes actor display prefixes", () => { expect(actorDisplayPrefix({ userId: "u", displayName: " Sam Teady ", role: "owner" })).toBe("[Sam Teady]:"); expect(actorDisplayPrefix({ userId: "u", displayName: "", role: "guest" })).toBe("[User]:"); // Additional edge cases: expect(actorDisplayPrefix({ userId: "u", displayName: " ", role: "guest" })).toBe("[User]:"); expect(actorDisplayPrefix({ userId: "u", displayName: "A".repeat(100), role: "owner" })).toBe(`[${"A".repeat(100)}]:`); });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@relay/src/sharing.test.ts` around lines 145 - 148, Add tests to cover edge cases for actorDisplayPrefix: include a whitespace-only displayName (e.g., " ") to assert it falls back to "[User]:", a very long displayName (e.g., "A".repeat(100)) to confirm it preserves long names, and optionally a name with special/Unicode characters to ensure sanitization doesn't strip them. Update the existing test block calling actorDisplayPrefix to add these extra expect assertions so whitespace-only, Unicode/special characters, and long-name behaviors are explicitly validated.
96-118: 💤 Low valueConsider extracting the userId to a variable for consistency.
The hardcoded string
"user_guest"on line 113 could be extracted to a constant (e.g., from theactorobject defined earlier). This would make the test more maintainable and reduce the risk of typos.♻️ Optional refactor
const accepted = await acceptShareInvite(created.state, { token: created.token.token, actor: { userId: "user_guest", displayName: "Alex", email: "alex@example.com", role: "guest", }, }); - const removed = removeShareParticipant(accepted.state, accepted.share.id, "user_guest"); + const guestUserId = "user_guest"; + const accepted = await acceptShareInvite(created.state, { + token: created.token.token, + actor: { + userId: guestUserId, + displayName: "Alex", + email: "alex@example.com", + role: "guest", + }, + }); + + const removed = removeShareParticipant(accepted.state, accepted.share.id, guestUserId);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@relay/src/sharing.test.ts` around lines 96 - 118, Extract the repeated "user_guest" literal into a local variable and use it throughout the test to avoid duplication and typos: define const guestId = "user_guest" (or reuse actor.userId if you prefer) near where the actor is declared, then replace occurrences in acceptShareInvite actor.userId and the removeShareParticipant call and the final assertions; this affects the actor object used with acceptShareInvite, the removeShareParticipant(accepted.state, accepted.share.id, ...), and the participant access removed.share!.participants[guestId].status, while leaving createShareInvite, acceptShareInvite, removeShareParticipant, and getShareChatMode unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/multi-user-chat.md`:
- Around line 41-43: The docs state invites can be "pending, accepted, revoked,
or expired" but the persisted model only stores statuses as
`pending|accepted|revoked` and prunes expired pending invites during
normalization; update the documentation (in the multi-user-chat description of
`invites`) to match the actual model by removing "expired" from the persisted
statuses and adding a sentence that expired pending invites are pruned during
normalization (or alternatively implement explicit `expired` persistence in the
invite normalization logic if you prefer to keep the doc wording); reference the
`invites` field and the normalization logic that enforces
`pending|accepted|revoked` so the contract stays consistent.
In `@relay/src/sharing.ts`:
- Around line 211-214: The route check permits paths like "/attachments/../..."
allowing traversal; update the check in the handler that uses method and path
(and the helper normalizePath) to operate on a canonicalized,
dot-segment-resolved path before matching: first resolve/canonicalize the
incoming path (e.g., using a POSIX path normalization or URL pathname resolution
used consistently in normalizePath), reject any path that still contains ".." or
whose resolved pathname does not start with the exact "/attachments" segment
boundary (accept only "/attachments" or "/attachments/" or "/attachments/<rest>"
where "<rest>" cannot climb up), and then apply the GET/POST + prefix check
using the resolvedPath; also fix normalizePath to properly remove dot segments
for relative inputs so both the approval branch and other uses rely on the same
canonicalized path.
- Around line 164-175: The current assignment
share.participants[participant.userId] can overwrite an owner record with role
"guest"; change it to first check const existing =
share.participants[participant.userId] and if existing?.role === "owner" then
merge updates instead of replacing (preserve existing.role and joinedAt, update
email/status/lastSeenAt as appropriate and keep owner-specific fields),
otherwise assign the new participant object; keep the invite mutations
(invite.status, invite.acceptedAt, invite.acceptedByUserId) as-is and use
actor.userId to locate the participant.
---
Nitpick comments:
In `@relay/src/sharing.test.ts`:
- Around line 145-148: Add tests to cover edge cases for actorDisplayPrefix:
include a whitespace-only displayName (e.g., " ") to assert it falls back to
"[User]:", a very long displayName (e.g., "A".repeat(100)) to confirm it
preserves long names, and optionally a name with special/Unicode characters to
ensure sanitization doesn't strip them. Update the existing test block calling
actorDisplayPrefix to add these extra expect assertions so whitespace-only,
Unicode/special characters, and long-name behaviors are explicitly validated.
- Around line 96-118: Extract the repeated "user_guest" literal into a local
variable and use it throughout the test to avoid duplication and typos: define
const guestId = "user_guest" (or reuse actor.userId if you prefer) near where
the actor is declared, then replace occurrences in acceptShareInvite
actor.userId and the removeShareParticipant call and the final assertions; this
affects the actor object used with acceptShareInvite, the
removeShareParticipant(accepted.state, accepted.share.id, ...), and the
participant access removed.share!.participants[guestId].status, while leaving
createShareInvite, acceptShareInvite, removeShareParticipant, and
getShareChatMode unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 6e02d177-6bf9-4326-a60f-a9e77cf51a1c
📒 Files selected for processing (4)
docs/multi-user-chat.mdrelay/src/sharing.test.tsrelay/src/sharing.tsvitest.config.ts
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
Summary
Verification
Summary by CodeRabbit
New Features
Documentation
Tests
Chores