Skip to content

Conversation

@splindsay-92
Copy link
Contributor

@splindsay-92 splindsay-92 commented Aug 12, 2025

Context

Pubsub will be moving to Pv4 soon, where appropriate, we should modify the chat message structure to match.
This will be a breaking change and require new version REST endpoints.
CHA-1101

Description

  • Version field is now an object, it contains the version string, timestamp of this version, and the clientId, description, and metadata fields previously held in the Operation field.
  • CreatedAt has been updated to timestamp.
  • Operation has been removed, its contents are now part of the version object.
  • For the operation endpoints, redundant fields have been removed, so it only returns the message.
  • For the send endpoint, createdAt is renamed to timestamp, and both the serial and timestamp filed are now embedded within a message field to mirror the delete/update endpoints.

Checklist

  • QA'd by the author.
  • Unit tests created (if applicable).
  • Integration tests created (if applicable).
  • Follow coding style guidelines found here.
  • TypeDoc updated (if applicable).
  • Updated the website documentation (if applicable).
  • Browser tests created (if applicable).
  • In repo demo app updated (if applicable).

Testing Instructions (Optional)

  • Explain how to test the changes in this PR.
  • Provide specific steps or commands to execute.

Summary by CodeRabbit

  • New Features

    • Message timestamps now use a unified timestamp field; message versions expose richer details (serial, timestamp, client, description, metadata). Annotations (e.g., summary) are supported and used for reactions/summary displays.
  • Refactor

    • Public message shape migrated from operation/createdAt to a structured version object plus a top-level timestamp; version-based comparison and history behavior updated.
  • Chores

    • Chat API moved to v4 endpoints and Ably peer dependency updated.
  • Tests

    • Test suite and demo fixtures updated to the new timestamp/version/annotations model.

@coderabbitai
Copy link

coderabbitai bot commented Aug 12, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Migrates chat model and REST API to Protocol V4: replaces message.createdAt with message.timestamp, replaces primitive/Operation-based versioning with a structured Version object (serial, timestamp, clientId, description, metadata), updates REST types/parsing/mapping and chat API endpoints to v4, adds annotations support, and updates demo, tests, and test helpers accordingly.

Changes

Cohort / File(s) Summary
Core message model & types
src/core/message.ts, src/core/rest-types.ts, src/core/index.ts
Introduces Version type; Message.version changed from stringVersion; removes operation and createdAt in favor of version and top-level timestamp; adds RestVersion and updates RestMessage; freeze/clone version handling updated.
Message parsing & mapping
src/core/message-parser.ts, src/core/messages.ts
Parser accepts numeric timestamps and annotations, builds Version objects (converts timestamps → Date), emits DefaultMessage with version: Version and timestamp; REST response mapping now consumes full response object with version/timestamp.
Chat API endpoints & types
src/core/chat-api.ts
Migrates REST endpoints v3 → v4 (history, getMessage, deleteMessage, sendMessage, updateMessage, reactions, getOccupancy); CreateMessageResponse.createdAttimestamp; removes MessageOperationResponse and re-aliases some response types to RestMessage.
Reactions & annotations
src/core/messages-reactions.ts, test/core/messages-reactions.test.ts
Reaction summary moved to event.annotations.summary; dropped implicit summary init; reads summary from annotations and adjusts emitted summary fields; tests updated accordingly.
Demo & runtime options
demo/src/components/MessageComponent/MessageComponent.tsx, demo/src/main.tsx, demo/package.json
Demo UI now shows message.timestamp (not createdAt); Ably client opts use endpoint-based configuration; demo ably dependency bumped to ^2.13.0.
Tests updated to Version + timestamp
test/core/*, test/react/hooks/*, test/core/message-parser.test.ts, test/core/message.test.ts, test/core/messages*.test.ts, test/core/rest-types.test.ts, test/core/serial.test.ts
Extensive fixtures and assertions updated: version is an object (serial, timestamp, clientId, description, metadata); top-level timestamp replaces createdAt; removed operation usage; many tests assert on version.* and v4 endpoints.
Test helpers / env
test/helper/environment.ts, test/helper/realtime-client.ts, .env.test.example
Replaced testEnvironmenttestEndpoint; test helpers use endpoint defaults (local-rest.ably.io, nonprod:sandbox, production undefined); example env adjusted.
Misc config / lints
package.json, cspell.json
Bumped peerDependencies.ably (root) and demo ably; added "nonprod" to cspell word list.
Small API/test tweaks
demo/src/components/MessageComponent/MessageComponent.tsx, test/core/chat-api.test.ts
Minor UI display change and updated single test expectation for occupancy REST path to v4.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant UI as UI / Demo
  participant Hooks as Hooks / Messages module
  participant API as Chat REST (v4)
  participant Mapper as messageFromRest
  participant Model as DefaultMessage

  rect rgba(230,245,255,0.9)
  note over UI,API: Send / Update / Delete (Protocol V4)
  UI->>Hooks: send/update/delete(payload)
  Hooks->>API: REST v4 request (uses endpoint)
  API-->>Hooks: RestMessage { version:{serial,timestamp,clientId,...}, timestamp, ... }
  Hooks->>Mapper: messageFromRest(RestMessage)
  Mapper->>Model: new DefaultMessage({ version: Version, timestamp: Date, ... })
  Model-->>Hooks: Message (version: Version, timestamp: Date)
  Hooks-->>UI: Message
  end

  note right of Model: version.serial used for ordering\nannotations read from message.annotations
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title concisely and accurately reflects the PR's primary purpose: refactoring the message API to align with Pubsub Protocol V4. It is specific, short, and matches the documented objectives and changes (version object, timestamp rename, removal of Operation, and REST v4 endpoints).
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch chat/move-to-protocol-v4

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.

@github-actions
Copy link

github-actions bot commented Aug 12, 2025

Coverage Report

Status Category Percentage Covered / Total
🟢 Lines 96.36% (🎯 96%) 3867 / 4013
🟢 Statements 96.36% (🎯 96%) 3867 / 4013
🟢 Functions 98.35% (🎯 98%) 299 / 304
🟢 Branches 97.34% (🎯 96%) 991 / 1018
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
src/core/chat-api.ts 94.77% 100% 88.88% 94.77% 176-183
src/core/message-parser.ts 100% 100% 100% 100%
src/core/message.ts 93.46% 98.11% 100% 93.46% 440-451
src/core/messages-reactions.ts 98.72% 96.22% 100% 98.72% 284-285
src/core/messages.ts 92.88% 91.11% 95.23% 92.88% 443-445, 484-489, 505-509, 528-530, 659
src/core/rest-types.ts 100% 100% 100% 100%
Generated in workflow #2790 for commit a9bfaf3 by the Vitest Coverage Report Action

@splindsay-92 splindsay-92 force-pushed the chat/move-to-protocol-v4 branch from ff5799a to a45f435 Compare August 14, 2025 17:00
@vladvelici
Copy link
Contributor

vladvelici commented Sep 3, 2025

Something to consider. I know PR is still a a draft but I'll forget.

The isUpdated and isDeleted getters should also work if the action is for summary updates. The rule is if the action is a summary:

  • message is not deleted if action is summary because we don't send summary updates for deleted messages. [give or take, since if the summaries are rolled up and message gets deleted, a summary might get broadcast anyway?]
  • message is updated if serial != version.serial
  • message is created otherwise

(Also: can be deferred to later, since we still have the message.with() stuff, which if used, avoids the problem altogether by not treating summary updates as messages)

@splindsay-92 splindsay-92 force-pushed the chat/move-to-protocol-v4 branch 2 times, most recently from a074ea2 to b65c4c9 Compare September 3, 2025 19:10
@splindsay-92 splindsay-92 marked this pull request as ready for review September 3, 2025 19:11
@splindsay-92 splindsay-92 force-pushed the chat/move-to-protocol-v4 branch from b65c4c9 to 7c18632 Compare September 3, 2025 19:15
@github-actions github-actions bot temporarily deployed to staging/pull/620/typedoc September 3, 2025 19:16 Inactive
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (6)
src/core/chat-api.ts (1)

119-119: Fix protocol/version mismatch (tests 404).

Endpoints use /chat/v4 but _apiProtocolVersion is 3. Set to 4 to align and fix 404s.

Apply:

-  private readonly _apiProtocolVersion: number = 3;
+  private readonly _apiProtocolVersion: number = 4;
test/core/message-parser.test.ts (3)

287-305: Align “created” fixture to PV4 shape; remove createdAt, use version object.

Created events should not include createdAt; use timestamp for creation and set version to the same time.

Apply:

   extras: {
     headers: { headerKey: 'headerValue' },
   },
-  createdAt: 1728402074206,
-  timestamp: 1728402074207,
-  version: '01728402074207-000@cbfkKvEYgBhDaZ38195418:0',
+  timestamp: 1728402074206,
+  version: {
+    serial: '01728402074207-000@cbfkKvEYgBhDaZ38195418:0',
+    timestamp: 1728402074206,
+  },
   action: ChatMessageAction.MessageCreate,
   serial: '01728402074206-000@cbfkKvEYgBhDaZ38195418:0',

And expectations:

- expect(result.timestamp).toEqual(new Date(1728402074206));
+ expect(result.timestamp).toEqual(new Date(1728402074206));
...
- expect(result.version.serial).toBe('01728402074207-000@cbfkKvEYgBhDaZ38195418:0');
- expect(result.version.timestamp).toEqual(new Date(1728402074207));
+ expect(result.version.serial).toBe('01728402074207-000@cbfkKvEYgBhDaZ38195418:0');
+ expect(result.version.timestamp).toEqual(new Date(1728402074206));

327-350: Align “updated” fixture: createdAt removed; top-level timestamp = creation; version carries update details.

Update to v4 by moving operation into version and fixing timestamps.

Apply:

   action: ChatMessageAction.MessageUpdate,
   serial: '01728402074206-000@cbfkKvEYgBhDaZ38195418:0',
-  createdAt: 1728402074206,
-  timestamp: 1728402074207,
-  version: '01728402074207-000@cbfkKvEYgBhDaZ38195418:0',
-  operation: { clientId: 'client2', description: 'update message', metadata: { 'custom-update': 'some flag' } },
+  timestamp: 1728402074206,
+  version: {
+    serial: '01728402074207-000@cbfkKvEYgBhDaZ38195418:0',
+    timestamp: 1728402074207,
+    clientId: 'client2',
+    description: 'update message',
+    metadata: { 'custom-update': 'some flag' },
+  },

And assertions:

- expect(result.timestamp).toEqual(new Date(1728402074206));
+ expect(result.timestamp).toEqual(new Date(1728402074206));
  expect(result.updatedAt).toEqual(new Date(1728402074207));

366-392: Align “deleted” fixture to v4: remove createdAt; move operation into version.

Top-level timestamp = creation; version holds delete timestamp and actor/details.

Apply:

- timestamp: 1728402074207,
- createdAt: 1728402074206,
- version: '01728402074207-000@cbfkKvEYgBhDaZ38195418:0',
- operation: {
-   clientId: 'client2',
-   description: 'delete message',
-   metadata: { 'custom-warning': 'this is a warning' },
- },
+ timestamp: 1728402074206,
+ version: {
+   serial: '01728402074207-000@cbfkKvEYgBhDaZ38195418:0',
+   timestamp: 1728402074207,
+   clientId: 'client2',
+   description: 'delete message',
+   metadata: { 'custom-warning': 'this is a warning' },
+ },

And assertions remain consistent (timestamp = creation; deletedAt = version.timestamp).

test/core/message.test.ts (1)

579-581: Wrong event type for DELETE scenario

Applying a DELETE should emit ChatMessageEventType.Deleted, not Updated. This makes the test assert the wrong contract.

Apply this diff:

-      const event: ChatMessageEvent = {
-        type: ChatMessageEventType.Updated,
-        message: eventMessage,
-      };
+      const event: ChatMessageEvent = {
+        type: ChatMessageEventType.Deleted,
+        message: eventMessage,
+      };
test/core/messages.test.ts (1)

1109-1112: Remove legacy fields (createdAt, operation) — PV4 no longer sends them

Protocol V4 removed createdAt and operation. Keeping them in inbound fixtures risks masking parser issues and diverges from the new contract.

Apply this diff in both places:

-        timestamp: Date.now(),
-        createdAt: Date.now(),
-        operation: { clientId: 'yoda' },
+        timestamp: Date.now(),

Also applies to: 1143-1145

♻️ Duplicate comments (2)
test/core/message-parser.test.ts (2)

405-432: Do the same v4 adjustments for this delete test.

Apply the same changes as above: remove createdAt, set top-level timestamp to creation, and move operation → version.


446-473: Do the same v4 adjustments for this delete-with-empty-data test.

Apply the same changes as above: remove createdAt, set top-level timestamp to creation, and move operation → version.

🧹 Nitpick comments (14)
src/core/message.ts (4)

33-58: Widen Version docs; annotate spec.

Version also represents the initial creation state, not only updates/deletes. Add a brief note and annotate the spec point.

Apply:

-export interface Version {
-  /**
-   * A unique identifier for the latest version of this message.
-   */
+// @CHA-1101
+export interface Version {
+  /**
+   * A unique identifier for the latest version of this message (including creation).
+   */

450-464: Consider addressing TODO: newer summaries vs newer versions.

if (this.version.serial >= message.version.serial) return this; ignores the case where reactions summary is newer than the current instance. Optional: compare a summary watermark if available to avoid dropping newer summaries.


420-454: Use ErrorCodes enum instead of magic numbers.

Replace 40000 with ErrorCodes.* per project guidelines and document why this code applies.

Apply:

+import { ErrorCodes } from './errors.js';
...
-throw new Ably.ErrorInfo('cannot apply a created event to a message', 40000, 400);
+throw new Ably.ErrorInfo('cannot apply a created event to a message', ErrorCodes.InvalidArgument, 400);
...
-throw new Ably.ErrorInfo('cannot apply event for a different message', 40000, 400);
+throw new Ably.ErrorInfo('cannot apply event for a different message', ErrorCodes.InvalidArgument, 400);

If ErrorCodes names differ, I can adjust once you confirm the enum entries.


300-303: Annotate spec points in changed fields.

Add @CHA-1101 near version/timestamp usage and cloning to make the PV4 rationale discoverable.

Apply:

 export interface DefaultMessageParams {
   serial: string;
   clientId: string;
   text: string;
   metadata: MessageMetadata;
   headers: MessageHeaders;
   action: ChatMessageAction;
+  // @CHA-1101
   version: Version;
-  timestamp: Date;
+  // @CHA-1101
+  timestamp: Date;
   reactions: MessageReactions;
 }
...
-    this.version = version;
+    // @CHA-1101
+    this.version = version;
...
-      version: replace?.version ?? cloneDeep(source.version),
+      // @CHA-1101
+      version: replace?.version ?? cloneDeep(source.version),

Also applies to: 317-319, 475-478

src/core/chat-api.ts (1)

116-125: Add JSDoc for public API methods.

Per guidelines, document params/returns/throws on public methods (history/getMessage/send/update/delete/react). Also add trace-level logs with context at method entry.

Example:

 export class ChatApi {
@@
-  async history(roomName: string, params: HistoryQueryParams): Promise<PaginatedResult<Message>> {
+  /**
+   * Fetch room message history.
+   * @param roomName Room name (will be URL-encoded).
+   * @param params History query filters.
+   * @returns PaginatedResult of mapped {@link Message}.
+   * @throws {@link Ably.ErrorInfo} on transport or server error.
+   */
+  async history(roomName: string, params: HistoryQueryParams): Promise<PaginatedResult<Message>> {
+    this._logger.trace('ChatApi.history()', { roomName, hasFromSerial: Boolean(params.fromSerial) });
test/core/message-parser.test.ts (2)

1-7: Mock ably in tests per guidelines.

Add a module mock to avoid accidental runtime coupling.

Apply:

 import * as Ably from 'ably';
 import { describe, expect, it } from 'vitest';
+vi.mock('ably');

8-14: Annotate tests with spec point.

Add // @CHA-1101 on the describe for PV4 message shape.

Apply:

-describe('parseMessage', () => {
+describe('parseMessage', () => {
+  // @CHA-1101
test/core/message.test.ts (3)

689-696: Test name doesn’t match assertions

The description says “return original message if newer message is older”, but assertions expect replacement with the newer message. Rename for clarity.

Apply this diff:

-    it('should return original message if newer message is older', () => {
+    it('should replace original when incoming message has a newer version', () => {

18-24: De-duplicate version object construction

You repeat the same version literal (with description/metadata undefined) many times. Use a small helper to reduce noise and keep shapes consistent.

Example:

+// helper to keep Version construction consistent across tests
+const makeVersion = (serial: string, tsMs: number, clientId?: string) => ({
+  serial,
+  timestamp: new Date(tsMs),
+  clientId,
+  description: undefined,
+  metadata: undefined,
+});

And then:

-      version: {
-        serial: firstSerial,
-        timestamp: new Date(1672531200000),
-        clientId: 'clientId',
-        description: undefined,
-        metadata: undefined,
-      },
+      version: makeVersion(firstSerial, 1672531200000, 'clientId'),

Repeat similarly for other occurrences in this file.

Also applies to: 36-42, 61-67, 79-85


265-287: Add summary-action coverage per DR comment

Per PR discussion, summary updates shouldn’t mark a message deleted and should mark updated when serial != version.serial. Add tests for ChatMessageAction.MessageSummary to lock this behavior.

I can add two tests: one asserting isDeleted === false for summary actions; another asserting isUpdated based on serial/version.serial mismatch. Want me to push a patch?

Also applies to: 289-310, 312-358, 360-445

test/core/messages.test.ts (4)

388-404: Incoming version should follow PV4 object shape (not string)

Subscription fixtures still use version as a string. If PV4 emits an object { serial, timestamp, clientId, ... }, update the fixtures to reflect that; otherwise you’re testing stale behavior.

I can update all emits to:

-  version: '01672531200000-123@abcdefghij',
+  version: { serial: '01672531200000-123@abcdefghij', timestamp: publishTimestamp, clientId: 'yoda' },

Confirm the PV4 inbound schema and I’ll push a scoped patch.

Also applies to: 416-423, 461-466, 481-497, 520-526, 540-556


369-378: Avoid flakiness: replace real timers with fake timers/waiters

Using a 300ms real timeout can be flaky on CI. Prefer vi.useFakeTimers + vi.runAllTimers or vi.waitFor.

Sketch:

vi.useFakeTimers();
const unsub = context.room.messages.subscribe(() => { /* ... */ });
/* emit 3 events */
vi.runAllTimers();
unsub();

1-2: Align Ably imports with project guideline

Guideline: “When importing the package ably, use import * as Ably from 'ably'.” Consider importing RealtimeChannel as a type to avoid dual runtime imports.

Apply:

-import * as Ably from 'ably';
-import { RealtimeChannel } from 'ably';
+import * as Ably from 'ably';
+import type { RealtimeChannel } from 'ably';

124-151: Add spec annotations for PV4 changes

Per coding guidelines, annotate tests that validate PV4 payload changes.

Example:

-    it<TestContext>('should be able to delete a message and get it back from response', async (context) => {
+    it<TestContext>('should be able to delete a message and get it back from response', async (context) => {
+      // @CHA-1101: PV4 delete returns message with Version object and top-level timestamp

Also applies to: 261-306

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between b65c4c9 and 7c18632.

📒 Files selected for processing (16)
  • demo/src/components/MessageComponent/MessageComponent.tsx (1 hunks)
  • src/core/chat-api.ts (8 hunks)
  • src/core/index.ts (1 hunks)
  • src/core/message-parser.ts (2 hunks)
  • src/core/message.ts (9 hunks)
  • src/core/messages.ts (3 hunks)
  • src/core/rest-types.ts (3 hunks)
  • test/core/chat-api.test.ts (1 hunks)
  • test/core/message-parser.test.ts (11 hunks)
  • test/core/message.test.ts (37 hunks)
  • test/core/messages.integration.test.ts (12 hunks)
  • test/core/messages.test.ts (15 hunks)
  • test/core/rest-types.test.ts (11 hunks)
  • test/core/serial.test.ts (1 hunks)
  • test/react/hooks/use-messages.integration.test.tsx (1 hunks)
  • test/react/hooks/use-messages.test.tsx (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (11)
  • src/core/index.ts
  • demo/src/components/MessageComponent/MessageComponent.tsx
  • test/core/chat-api.test.ts
  • test/core/serial.test.ts
  • test/core/messages.integration.test.ts
  • src/core/messages.ts
  • src/core/rest-types.ts
  • test/core/rest-types.test.ts
  • test/react/hooks/use-messages.integration.test.tsx
  • test/react/hooks/use-messages.test.tsx
  • src/core/message-parser.ts
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: When importing in this project, use relative imports.
When importing the package ably, do import * as Ably from 'ably'.
Use named exports over default exports.
Group related exports together.
Use PascalCase for interface names.
Use descriptive names that reflect the purpose for interfaces.
Prefix interfaces with 'I' is not required.
Use JSDoc style comments for interfaces.
Include a clear description of the interface's purpose in JSDoc.
Document all properties inline, alongside the property itself in interfaces.
Document all errors using @throws in JSDoc.
Document the return type using @return in JSDoc.
Link using {@link} to types mentioned in JSDoc.
Use PascalCase for enum names.
Use PascalCase for enum members.
Include JSDoc documentation for both enum and members.
Use PascalCase for class names.
Implement interfaces explicitly in classes.
Avoid extension wherever possible in classes.
Document class purpose and implementation details.
Use underscore prefix for private members in classes.
Group class members by visibility (private, protected, public).
Document all public methods and properties in classes.
Use JSDoc for all public methods.
Make use of @param, @returns and @throws in method documentation.
Use descriptive parameter names in methods.
Group related parameters into parameter objects in methods.
Use optional parameters with default values when appropriate in methods.
The error type used is ErrorInfo from the package 'ably'.
Use consistent logging levels (trace, debug, error).
Include relevant context in log messages, the second argument is a free-formed object for any relevant context.
Never log instances of Ably channels.
Structure log messages for easy parsing.
Minimize use of type assertions.
Use type guards when possible.
Document when and why type assertions are necessary.
Never use any. If we really can't avoid it, use unknown, but strong typing always preferred.
Use async/await over raw promises.
Handle promise re...

Files:

  • test/core/message.test.ts
  • test/core/messages.test.ts
  • src/core/chat-api.ts
  • test/core/message-parser.test.ts
  • src/core/message.ts
**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.test.{ts,tsx}: Test files should end with .test.ts.
Name test files to reflect the module being tested.
Use clear, descriptive names that explain the test's purpose in test descriptions.
Group related tests using describe blocks.
Use nested describes for sub-features in tests.
Arrange-Act-Assert structure for test cases.
Test both positive and negative cases, including edge cases and boundary conditions.
Use clear, specific assertions in tests.
Create meaningful test data that clearly demonstrates the test case.
Use random room IDs to avoid conflicts in tests.
Use .each to write table/data driven tests.
Use custom matchers like toBeErrorInfo, toThrowErrorInfo, toBeErrorInfoWithCode, toThrowErrorInfoWithCode, toBeErrorInfoWithCauseCode for error testing.
Include an annotation of the specification point in the relevant test methods, e.g // CHA-M10a.

**/*.test.{ts,tsx}: Use the custom matcher toBeErrorInfo to validate that an error matches expected ErrorInfo properties (code, statusCode, message, cause) when testing error scenarios.
Use the custom matcher toThrowErrorInfo to check if a function throws an error matching expected ErrorInfo properties in tests.
Use the custom matcher toBeErrorInfoWithCode to validate that an error has a specific error code in tests.
Use the custom matcher toThrowErrorInfoWithCode to check if a function throws an error with a specific code in tests.
Use the custom matcher toBeErrorInfoWithCauseCode to validate that an error has a cause with a specific error code in tests.
Use waitForEventualHookValueToBeDefined utility to wait for a React hook value to become defined in asynchronous hook tests.
Use waitForEventualHookValue utility to wait for a React hook to return an expected value in asynchronous hook tests.
Use vi.waitFor to wait for asynchronous events to happen in tests.

**/*.test.{ts,tsx}: Mock the ably library as a module using vi.mock('ably') in test files that use Ably.
Each test file should ...

Files:

  • test/core/message.test.ts
  • test/core/messages.test.ts
  • test/core/message-parser.test.ts
test/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/file-conventions.mdc)

Create all Typescript and TSX files using kebab-case.

Files:

  • test/core/message.test.ts
  • test/core/messages.test.ts
  • test/core/message-parser.test.ts
**/*.test.ts

📄 CodeRabbit inference engine (.cursor/rules/unit-testing.mdc)

Test files should end with .test.ts and the name should reflect the module being tested (e.g., message.test.ts for message.ts).

Files:

  • test/core/message.test.ts
  • test/core/messages.test.ts
  • test/core/message-parser.test.ts
src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/file-conventions.mdc)

Create all Typescript and TSX files using kebab-case.

Files:

  • src/core/chat-api.ts
  • src/core/message.ts

⚙️ CodeRabbit configuration file

src/**/*.{ts,tsx}: If there are any changes to the public-facing API, add a review comment: "Public API changes detected. Please update the documentation and the Ably CLI."

Files:

  • src/core/chat-api.ts
  • src/core/message.ts
🧠 Learnings (4)
📚 Learning: 2024-12-04T14:08:24.299Z
Learnt from: vladvelici
PR: ably/ably-chat-js#427
File: src/core/message.ts:251-275
Timestamp: 2024-12-04T14:08:24.299Z
Learning: In `src/core/message.ts` (TypeScript), the `version` property of the `Message` class can be compared directly using `<`, `>`, and `===` operators, as the `version` strings are safely comparable in this context.

Applied to files:

  • test/core/message.test.ts
  • test/core/messages.test.ts
  • test/core/message-parser.test.ts
  • src/core/message.ts
📚 Learning: 2024-11-07T21:42:51.681Z
Learnt from: vladvelici
PR: ably/ably-chat-js#378
File: test/core/messages.test.ts:0-0
Timestamp: 2024-11-07T21:42:51.681Z
Learning: In `test/core/messages.test.ts`, the mocked response for `chatApi.deleteMessage` in the test case `'should be able to delete a message and get it back from response'` should only include `serial` and `deletedAt`, as the real `deleteMessage` API response does not provide additional properties like `text`, `clientId`, `createdAt`, or `roomId`.

Applied to files:

  • test/core/message.test.ts
  • test/core/messages.test.ts
  • src/core/chat-api.ts
  • test/core/message-parser.test.ts
📚 Learning: 2024-12-04T14:12:11.101Z
Learnt from: vladvelici
PR: ably/ably-chat-js#427
File: src/core/messages.ts:501-502
Timestamp: 2024-12-04T14:12:11.101Z
Learning: In `src/core/messages.ts`, when constructing a `DefaultMessage` in the `send` method, passing the same `createdAt` date twice is intentional. One date is for `createdAt`, and the other is for `timestamp`. They should point to the same value, represented by `createdAt` after a message is posted.

Applied to files:

  • test/core/message.test.ts
  • test/core/messages.test.ts
  • test/core/message-parser.test.ts
  • src/core/message.ts
📚 Learning: 2024-10-22T15:54:58.070Z
Learnt from: splindsay-92
PR: ably/ably-chat-js#365
File: test/core/messages.integration.test.ts:95-107
Timestamp: 2024-10-22T15:54:58.070Z
Learning: In tests handling `messageEvent.type`, we should throw an error for unexpected message event types to ensure changes are caught.

Applied to files:

  • test/core/message.test.ts
🧬 Code graph analysis (4)
test/core/message.test.ts (1)
src/core/message.ts (1)
  • emptyMessageReactions (490-494)
test/core/messages.test.ts (2)
src/core/message.ts (1)
  • emptyMessageReactions (490-494)
src/core/chat-api.ts (2)
  • deleteMessage (185-198)
  • updateMessage (216-224)
src/core/chat-api.ts (2)
src/core/rest-types.ts (1)
  • RestMessage (34-44)
src/core/messages.ts (1)
  • UpdateMessageParams (113-128)
src/core/message.ts (1)
src/core/operation-metadata.ts (1)
  • OperationMetadata (10-10)
🪛 GitHub Actions: Build and test
src/core/chat-api.ts

[error] 262-262: ChatApi._makeAuthorizedRequest() failed with 404 Not Found (not found) during tests. REST endpoint '/chat/v4/rooms/{room}/messages' not found.

🔇 Additional comments (16)
src/core/message.ts (5)

82-82: Public API change: createdAt → timestamp. Update docs and CLI.

This is a public API rename. Please update TypeDoc, website docs, and Ably CLI.


118-122: Public API change: version now an object. Update docs and CLI.

Export shape changed from string to structured Version. Ensure TypeDoc and CLI are updated.


342-342: Freezing version is good.

Runtime immutability for nested version is a solid guard.


358-372: Confirm summary-action semantics for isUpdated/isDeleted.

Current getters rely solely on action. Per DR note, summary updates should not flip deleted state and should not be treated as full updates. Add tests to verify these.

Would you like me to add focused tests in test/core/message.test.ts for summary events?


379-396: Version ordering via serial string comparisons is OK.

Using <, >, === on version.serial matches prior guidance; keep as is.

src/core/chat-api.ts (4)

185-198: v4 endpoints OK; error handling OK.

Paths and error propagation look correct pending the version fix.

Also applies to: 216-224, 232-241


1-286: Public API changes detected. Update docs and Ably CLI.

Creating/updating/deleting message response types and endpoints changed. Please update TypeDoc, website docs, and CLI.


149-153: Resolved: no stale v3 paths
Grep shows no /chat/v3/ endpoints in code or tests (only in UPGRADING.md examples), so mocks/backends have been updated.


32-37: Confirm CreateMessageResponse contract for v4: the backend should return an envelope ({ message: { serial, timestamp } }) rather than top-level serial/timestamp. After verifying the actual API response, update CreateMessageResponse in src/core/chat-api.ts (lines 32–37 and 200–214) to either reuse RestMessage (export type CreateMessageResponse = { message: RestMessage }) or introduce a dedicated RestCreateMessageResponse and map it via messageFromRest.

test/core/message-parser.test.ts (1)

124-132: Defaults for missing version look good.

Using top-level timestamp for default Version.timestamp is consistent.

test/core/messages.test.ts (6)

251-257: Good negative-path coverage for missing serial

These assertions use toBeErrorInfo correctly and read well.

Also applies to: 355-361


732-769: History attachment/serial resolution tests look solid

Nice coverage for attach vs channel serial selection, resume behavior, and state-change updates. No changes requested.

Also applies to: 771-864, 950-1063


617-635: Default-value handling test is aligned with Version object

Asserting version.serial === '' ensures parser constructs a Version even when missing. LGTM.


205-206: Assert Version clientId on mutation responses

Good to assert version.clientId presence; ensures provenance is wired through.

Also applies to: 248-249, 305-306, 352-353


149-151: Mocking reactions with emptyMessageReactions is consistent with public shape

This keeps type shape realistic. LGTM.

Also applies to: 191-193, 234-235, 287-288, 334-335


69-71: No changes needed: sendMessage returns top-level serial/timestamp
Confirmed CreateMessageResponse is defined as:

export interface CreateMessageResponse {
  serial: string;
  timestamp: string;
}

Mocks of { serial, timestamp } at the top level are correct—no update required.

Comment on lines +278 to +286
action: ChatMessageAction.MessageDelete,
version: {
serial: '01672531200001-123@abcdefghij:0',
timestamp: deleteTimestamp,
reactions: emptyMessageReactions(),
operation: {
clientId: 'clientId',
},
clientId: 'clientId',
description: undefined,
metadata: undefined,
},
timestamp: sendTimestamp,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Update mocks: action should be MessageUpdate, not MessageDelete

Both update tests set action to MessageDelete in the mocked response. That contradicts the scenario and weakens the contract being tested.

Apply this diff in both update tests:

-        action: ChatMessageAction.MessageDelete,
+        action: ChatMessageAction.MessageUpdate,

Also applies to: 325-335

🤖 Prompt for AI Agents
In test/core/messages.test.ts around lines 278-286 (and also update the similar
block at lines 325-335), the mocked message action is incorrectly set to
ChatMessageAction.MessageDelete for the update tests; change the action field to
ChatMessageAction.MessageUpdate in both mocked responses so the mocks match the
tested update scenario, and update any related variable names or expectations if
necessary to reflect the update action.

@splindsay-92 splindsay-92 requested review from a team, AndyTWF, owenpearson and vladvelici and removed request for a team and owenpearson September 4, 2025 11:30
* Represents the detail of a message deletion or update.
*/
export interface Operation {
export interface Version {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We've named it in MessageVersion in the spec, so should probably do the same here

/**
* A unique identifier for the latest version of this message.
*/
serial: string;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional in the spec

/**
* The timestamp at which this version was updated, deleted, or created.
*/
timestamp: Date;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional in the spec

@github-actions github-actions bot temporarily deployed to staging/pull/620/typedoc September 10, 2025 21:21 Inactive
Copy link

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

Caution

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

⚠️ Outside diff range comments (1)
test/core/message-parser.test.ts (1)

1-6: Mock Ably and fix the import extension to TypeScript.

Tests that import ably should mock it per guidelines; also use .ts for local TS imports for consistency.

-import * as Ably from 'ably';
-import { describe, expect, it } from 'vitest';
+import * as Ably from 'ably';
+import { describe, expect, it, vi } from 'vitest';

 import { ChatMessageAction } from '../../src/core/events.ts';
 import { DefaultMessage } from '../../src/core/message.ts';
-import { parseMessage } from '../../src/core/message-parser.js';
+import { parseMessage } from '../../src/core/message-parser.ts';
+
+vi.mock('ably');
🧹 Nitpick comments (11)
src/core/messages-reactions.ts (1)

161-169: Use allowed log levels (trace, debug, error); avoid info.
Project guidelines don’t list “info”. Use “debug” here.

-      this._logger.info('MessagesReactions._processAnnotationEvent(); ignoring unknown reaction type', { event });
+      this._logger.debug('MessagesReactions._processAnnotationEvent(); ignoring unknown reaction type', { event });
@@
-      this._logger.info('MessagesReactions._processAnnotationEvent(); ignoring unknown reaction event type', { event });
+      this._logger.debug('MessagesReactions._processAnnotationEvent(); ignoring unknown reaction event type', { event });
src/core/message.ts (2)

33-58: Add spec annotation for PV4 Version object introduction.
Documenting linkage helps future readers.

-export interface Version {
+// @CHA-1101: Protocol V4 – Operation removed; structured Version introduced (serial/timestamp/clientId/description/metadata)
+export interface Version {

457-474: Tidy comparison helper: fix comment typo and use consistent checks.
No behavior change, improves clarity.

-    // If our serials aren't the same, not same message and cannot compare1
+    // If our serials aren't the same, not same message and cannot compare
     if (!first.equal(second)) {
       return false;
     }
 
-    // If one our version serials isn't set, cannot compare
-    if (first.version.serial == undefined || second.version.serial === undefined) {
+    // If one of the version serials isn't set, cannot compare
+    if (first.version.serial === undefined || second.version.serial === undefined) {
       return false;
     }
 
     // Use the comparator to determine the comparison
     return comparator(first.version as VersionWithSerial, second.version as VersionWithSerial);
test/helper/environment.ts (1)

3-15: Defaulting to undefined endpoint changes previous sandbox default.
If CI/dev rely on sandbox by default, consider keeping sandbox as fallback or document the change.

Option A — keep sandbox fallback:

 export const testEndpoint = () => {
   switch (process.env.VITE_ABLY_ENV) {
     case 'local': {
       return 'local-rest.ably.io';
     }
     case 'sandbox': {
       return 'nonprod:sandbox';
     }
-    default: {
-      return;
-    }
+    default: {
+      return 'nonprod:sandbox';
+    }
   }
 };

If you keep undefined, ensure CI sets VITE_ABLY_ENV explicitly.

src/core/message-parser.ts (1)

36-46: Avoid broad cast; build a typed Version and annotate spec.
Removes the need for as Version and clarifies timestamp handling.

-  // Create the version - only converting the timestamp if it's actually defined.
-  const versionTimestamp = message.version.timestamp === undefined ? undefined : new Date(message.version.timestamp);
-  const version = (
-    versionTimestamp
-      ? {
-          ...message.version,
-          timestamp: versionTimestamp,
-        }
-      : message.version
-  ) as Version;
+  // @CHA-1101: PV4 version object; convert numeric timestamp to Date only when present
+  const version: Version = {
+    serial: message.version?.serial,
+    timestamp:
+      message.version?.timestamp === undefined ? undefined : new Date(message.version.timestamp),
+    clientId: message.version?.clientId,
+    description: message.version?.description,
+    metadata: message.version?.metadata,
+  };
test/core/message.test.ts (4)

611-611: Use idiomatic identity assertions.

Prefer Jest/Vitest identity matchers for clarity.

Apply:

- expect(newMessage !== message).toBe(true);
+ expect(newMessage).not.toBe(message);

- expect(newMessage === message).toBe(true);
+ expect(newMessage).toBe(message);

Also applies to: 640-640, 669-669, 691-691, 732-732, 774-774, 816-816


736-736: Rename misleading test title.

The test replaces the original when the incoming message is newer; the title says the opposite.

-it('should return original message if newer message is older', () => {
+it('should return updated message when incoming message is newer', () => {

265-287: Add coverage for summary-action semantics on isUpdated/isDeleted.

Per PR discussion, summary updates shouldn’t mark messages as deleted and may affect isUpdated. Consider adding tests for summary annotations.

Example to add:

describe('summary actions', () => {
  it('does not mark as deleted for summary', () => {
    const msg = new DefaultMessage({
      serial: 's',
      clientId: 'c',
      text: 't',
      metadata: {},
      headers: {},
      action: ChatMessageAction.MessageUpdate, // summary implied by annotations
      version: { serial: 'v:1', timestamp: new Date(1), clientId: 'm' },
      timestamp: new Date(0),
      reactions: emptyMessageReactions(),
    });
    // Simulate a summary-only update event if your model exposes it
    expect(msg.isDeleted).toBe(false);
    expect(msg.isUpdated).toBe(true);
  });
});

Also applies to: 289-310


11-27: Minor: extract shared fixtures to constants/helpers.

Dates and version fragments repeat heavily; factoring them reduces noise and maintenance.

Example:

const T0 = new Date(1672531200000);
const mkVersion = (serial: string, ts = T0, clientId?: string) =>
  ({ serial, timestamp: ts, clientId, description: undefined, metadata: undefined } as const);

Also applies to: 29-45, 54-71, 72-89, 97-114, 115-132, 140-157, 158-175, 183-200, 201-218, 226-243, 244-261

test/core/message-parser.test.ts (2)

312-328: Freeze time to assert the default timestamp deterministically.

Makes the “timestamp undefined” test robust and precise.

 it('should use current time as default when timestamp is undefined', () => {
-  const message = {
+  vi.useFakeTimers();
+  vi.setSystemTime(new Date(1234567890000));
+  const message = {
     data: { text: 'hello' },
     clientId: 'client1',
     extras: {},
     serial: '01728402074206-000@cbfkKvEYgBhDaZ38195418:0',
     version: {
       serial: '01728402074207-000@cbfkKvEYgBhDaZ38195418:0',
       timestamp: 1728402074207,
     },
     action: ChatMessageAction.MessageCreate,
   };
 
   const result = parseMessage(message as Ably.InboundMessage);
   expect(result).toBeInstanceOf(DefaultMessage);
-  expect(result.timestamp).toBeInstanceOf(Date);
+  expect(result.timestamp).toEqual(new Date(1234567890000));
+  vi.useRealTimers();
 });

387-390: Clarify intentional omission of annotations in tests
The parseMessage function and DefaultMessage class don’t surface annotations (including annotations.summary), so the incoming annotations are deliberately ignored. In the test around lines 387–390, add a brief comment stating that annotations.summary is intentionally dropped (or assert that the resulting Message has no annotations property) to document this behavior.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 7c18632 and c7e3371.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (11)
  • cspell.json (1 hunks)
  • package.json (1 hunks)
  • src/core/message-parser.ts (3 hunks)
  • src/core/message.ts (10 hunks)
  • src/core/messages-reactions.ts (1 hunks)
  • test/core/message-parser.test.ts (27 hunks)
  • test/core/message.test.ts (38 hunks)
  • test/core/messages-reactions.test.ts (7 hunks)
  • test/core/messages.test.ts (27 hunks)
  • test/helper/environment.ts (1 hunks)
  • test/helper/realtime-client.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/core/messages.test.ts
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: When importing in this project, use relative imports.
When importing the package ably, do import * as Ably from 'ably'.
Use named exports over default exports.
Group related exports together.
Use PascalCase for interface names.
Use descriptive names that reflect the purpose for interfaces.
Prefix interfaces with 'I' is not required.
Use JSDoc style comments for interfaces.
Include a clear description of the interface's purpose in JSDoc.
Document all properties inline, alongside the property itself in interfaces.
Document all errors using @throws in JSDoc.
Document the return type using @return in JSDoc.
Link using {@link} to types mentioned in JSDoc.
Use PascalCase for enum names.
Use PascalCase for enum members.
Include JSDoc documentation for both enum and members.
Use PascalCase for class names.
Implement interfaces explicitly in classes.
Avoid extension wherever possible in classes.
Document class purpose and implementation details.
Use underscore prefix for private members in classes.
Group class members by visibility (private, protected, public).
Document all public methods and properties in classes.
Use JSDoc for all public methods.
Make use of @param, @returns and @throws in method documentation.
Use descriptive parameter names in methods.
Group related parameters into parameter objects in methods.
Use optional parameters with default values when appropriate in methods.
The error type used is ErrorInfo from the package 'ably'.
Use consistent logging levels (trace, debug, error).
Include relevant context in log messages, the second argument is a free-formed object for any relevant context.
Never log instances of Ably channels.
Structure log messages for easy parsing.
Minimize use of type assertions.
Use type guards when possible.
Document when and why type assertions are necessary.
Never use any. If we really can't avoid it, use unknown, but strong typing always preferred.
Use async/await over raw promises.
Handle promise re...

Files:

  • test/helper/environment.ts
  • src/core/messages-reactions.ts
  • test/helper/realtime-client.ts
  • test/core/messages-reactions.test.ts
  • test/core/message.test.ts
  • test/core/message-parser.test.ts
  • src/core/message-parser.ts
  • src/core/message.ts
test/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/file-conventions.mdc)

Create all Typescript and TSX files using kebab-case.

Files:

  • test/helper/environment.ts
  • test/helper/realtime-client.ts
  • test/core/messages-reactions.test.ts
  • test/core/message.test.ts
  • test/core/message-parser.test.ts
src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/file-conventions.mdc)

Create all Typescript and TSX files using kebab-case.

Files:

  • src/core/messages-reactions.ts
  • src/core/message-parser.ts
  • src/core/message.ts

⚙️ CodeRabbit configuration file

src/**/*.{ts,tsx}: If there are any changes to the public-facing API, add a review comment: "Public API changes detected. Please update the documentation and the Ably CLI."

Files:

  • src/core/messages-reactions.ts
  • src/core/message-parser.ts
  • src/core/message.ts
**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.test.{ts,tsx}: Test files should end with .test.ts.
Name test files to reflect the module being tested.
Use clear, descriptive names that explain the test's purpose in test descriptions.
Group related tests using describe blocks.
Use nested describes for sub-features in tests.
Arrange-Act-Assert structure for test cases.
Test both positive and negative cases, including edge cases and boundary conditions.
Use clear, specific assertions in tests.
Create meaningful test data that clearly demonstrates the test case.
Use random room IDs to avoid conflicts in tests.
Use .each to write table/data driven tests.
Use custom matchers like toBeErrorInfo, toThrowErrorInfo, toBeErrorInfoWithCode, toThrowErrorInfoWithCode, toBeErrorInfoWithCauseCode for error testing.
Include an annotation of the specification point in the relevant test methods, e.g // CHA-M10a.

**/*.test.{ts,tsx}: Use the custom matcher toBeErrorInfo to validate that an error matches expected ErrorInfo properties (code, statusCode, message, cause) when testing error scenarios.
Use the custom matcher toThrowErrorInfo to check if a function throws an error matching expected ErrorInfo properties in tests.
Use the custom matcher toBeErrorInfoWithCode to validate that an error has a specific error code in tests.
Use the custom matcher toThrowErrorInfoWithCode to check if a function throws an error with a specific code in tests.
Use the custom matcher toBeErrorInfoWithCauseCode to validate that an error has a cause with a specific error code in tests.
Use waitForEventualHookValueToBeDefined utility to wait for a React hook value to become defined in asynchronous hook tests.
Use waitForEventualHookValue utility to wait for a React hook to return an expected value in asynchronous hook tests.
Use vi.waitFor to wait for asynchronous events to happen in tests.

**/*.test.{ts,tsx}: Mock the ably library as a module using vi.mock('ably') in test files that use Ably.
Each test file should ...

Files:

  • test/core/messages-reactions.test.ts
  • test/core/message.test.ts
  • test/core/message-parser.test.ts
**/*.test.ts

📄 CodeRabbit inference engine (.cursor/rules/unit-testing.mdc)

Test files should end with .test.ts and the name should reflect the module being tested (e.g., message.test.ts for message.ts).

Files:

  • test/core/messages-reactions.test.ts
  • test/core/message.test.ts
  • test/core/message-parser.test.ts
🧠 Learnings (12)
📚 Learning: 2025-07-23T10:06:04.393Z
Learnt from: CR
PR: ably/ably-chat-js#0
File: .cursor/rules/typescript-conventions.mdc:0-0
Timestamp: 2025-07-23T10:06:04.393Z
Learning: Applies to **/*.{ts,tsx} : When importing the package ably, use `import * as Ably from 'ably'`.

Applied to files:

  • package.json
  • src/core/message-parser.ts
📚 Learning: 2025-07-22T15:04:27.777Z
Learnt from: CR
PR: ably/ably-chat-js#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-07-22T15:04:27.777Z
Learning: Applies to **/*.{ts,tsx} : When importing the package ably, do `import * as Ably from 'ably'`.

Applied to files:

  • package.json
  • src/core/message-parser.ts
📚 Learning: 2025-08-08T16:24:19.988Z
Learnt from: splindsay-92
PR: ably/ably-chat-js#601
File: test/core/realtime-subscriptions.test.ts:3-4
Timestamp: 2025-08-08T16:24:19.988Z
Learning: Applies to test/**/*.{ts,tsx} in ably/ably-chat-js: Keep explicit .ts/.tsx extensions in import paths within test files; do not switch to .js extensions. This aligns with the project’s Vitest + TS setup and existing test code conventions.

Applied to files:

  • test/helper/environment.ts
  • test/helper/realtime-client.ts
📚 Learning: 2025-06-30T12:57:08.726Z
Learnt from: CR
PR: ably/ably-chat-js#0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-06-30T12:57:08.726Z
Learning: Applies to test-setup.ts : Automatically create a new app in the sandbox environment before tests run, handle API key setup, and set `process.env.sandboxApiKey` for test authentication in test setup.

Applied to files:

  • test/helper/environment.ts
📚 Learning: 2025-06-30T12:57:08.726Z
Learnt from: CR
PR: ably/ably-chat-js#0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-06-30T12:57:08.726Z
Learning: Applies to test-setup.ts : Skip test environment setup when using the local realtime cluster.

Applied to files:

  • test/helper/environment.ts
  • test/helper/realtime-client.ts
📚 Learning: 2025-07-22T15:05:08.887Z
Learnt from: CR
PR: ably/ably-chat-js#0
File: .cursor/rules/integration-testing.mdc:0-0
Timestamp: 2025-07-22T15:05:08.887Z
Learning: Applies to *.{integration.test.ts,integration.test.tsx} : Do not mock modules in integration tests; use full Ably.Realtime clients to talk to a real Ably service.

Applied to files:

  • test/helper/realtime-client.ts
📚 Learning: 2025-07-22T15:04:27.777Z
Learnt from: CR
PR: ably/ably-chat-js#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-07-22T15:04:27.777Z
Learning: Applies to **/*.integration.test.{ts,tsx} : Do not mock modules in integration tests; use real Ably.Realtime clients.

Applied to files:

  • test/helper/realtime-client.ts
📚 Learning: 2024-10-30T19:57:12.859Z
Learnt from: AndyTWF
PR: ably/ably-chat-js#382
File: test/core/chat.integration.test.ts:92-98
Timestamp: 2024-10-30T19:57:12.859Z
Learning: In `test/core/chat.integration.test.ts`, the `ablyRealtimeClient` function returns an instance of `Ably.Realtime`, so the TypeScript compiler recognizes methods like `close()` without needing type assertions.

Applied to files:

  • test/helper/realtime-client.ts
📚 Learning: 2024-11-07T21:42:51.681Z
Learnt from: vladvelici
PR: ably/ably-chat-js#378
File: test/core/messages.test.ts:0-0
Timestamp: 2024-11-07T21:42:51.681Z
Learning: In `test/core/messages.test.ts`, the mocked response for `chatApi.deleteMessage` in the test case `'should be able to delete a message and get it back from response'` should only include `serial` and `deletedAt`, as the real `deleteMessage` API response does not provide additional properties like `text`, `clientId`, `createdAt`, or `roomId`.

Applied to files:

  • test/core/messages-reactions.test.ts
  • test/core/message.test.ts
  • test/core/message-parser.test.ts
📚 Learning: 2024-12-04T14:08:24.299Z
Learnt from: vladvelici
PR: ably/ably-chat-js#427
File: src/core/message.ts:251-275
Timestamp: 2024-12-04T14:08:24.299Z
Learning: In `src/core/message.ts` (TypeScript), the `version` property of the `Message` class can be compared directly using `<`, `>`, and `===` operators, as the `version` strings are safely comparable in this context.

Applied to files:

  • test/core/message.test.ts
  • test/core/message-parser.test.ts
  • src/core/message-parser.ts
  • src/core/message.ts
📚 Learning: 2024-12-04T14:12:11.101Z
Learnt from: vladvelici
PR: ably/ably-chat-js#427
File: src/core/messages.ts:501-502
Timestamp: 2024-12-04T14:12:11.101Z
Learning: In `src/core/messages.ts`, when constructing a `DefaultMessage` in the `send` method, passing the same `createdAt` date twice is intentional. One date is for `createdAt`, and the other is for `timestamp`. They should point to the same value, represented by `createdAt` after a message is posted.

Applied to files:

  • test/core/message.test.ts
  • test/core/message-parser.test.ts
  • src/core/message.ts
📚 Learning: 2024-10-22T15:54:58.070Z
Learnt from: splindsay-92
PR: ably/ably-chat-js#365
File: test/core/messages.integration.test.ts:95-107
Timestamp: 2024-10-22T15:54:58.070Z
Learning: In tests handling `messageEvent.type`, we should throw an error for unexpected message event types to ensure changes are caught.

Applied to files:

  • test/core/message.test.ts
🧬 Code graph analysis (6)
test/helper/realtime-client.ts (1)
test/helper/environment.ts (1)
  • testEndpoint (3-15)
test/core/messages-reactions.test.ts (1)
src/core/index.ts (2)
  • ChatMessageAction (22-22)
  • MessageReactionType (25-25)
test/core/message.test.ts (2)
src/core/message.ts (2)
  • emptyMessageReactions (522-526)
  • Message (68-253)
src/core/index.ts (1)
  • Message (36-36)
test/core/message-parser.test.ts (1)
src/core/index.ts (1)
  • ChatMessageAction (22-22)
src/core/message-parser.ts (2)
src/core/message.ts (1)
  • Version (33-58)
src/core/index.ts (1)
  • Version (42-42)
src/core/message.ts (2)
src/core/index.ts (4)
  • MessageOperationMetadata (40-40)
  • OperationMetadata (65-65)
  • Version (42-42)
  • Message (36-36)
src/core/operation-metadata.ts (1)
  • OperationMetadata (10-10)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: test node 18
  • GitHub Check: test node 24
  • GitHub Check: test node 20
  • GitHub Check: test node 22
🔇 Additional comments (9)
cspell.json (1)

21-23: LGTM: added dictionary terms align with new endpoint naming.
No issues; “nonprod” supports testEndpoint('nonprod:sandbox').

src/core/message.ts (1)

124-127: Public API changes detected. Please update the documentation and the Ably CLI.
Version object replaces Operation; createdAt → timestamp.

test/helper/realtime-client.ts (1)

10-10: Supported ClientOptions.endpoint
Ably JS PV4 deprecates environment in favor of endpoint, which accepts only a single routing-policy name (e.g. "main" or "example-eu"). It does not support compound values like "nonprod:sandbox". To target a non-production environment via DNS, use the SDK’s nonprod flag or configure your nonprod routing-policy; to point at an arbitrary host (e.g. local-rest.ably.io), set restHost/realtimeHost instead.

src/core/message-parser.ts (1)

19-21: Confirm version is always present on inbound PV4 messages.
Parser assumes message.version exists; if not guaranteed, add a guard or default.

test/core/message.test.ts (4)

18-24: PV4 version object usage is consistent and correct.

All constructors now pass a structured version { serial, timestamp, clientId?, description?, metadata? }. Looks good.

Also applies to: 36-42, 61-67, 79-85, 104-110, 122-128, 147-153, 165-171, 190-196, 208-214, 233-239, 251-257, 275-281, 298-304, 326-332, 344-350, 472-477, 490-495, 512-518, 533-538, 564-569, 595-600, 624-629, 653-658, 703-708, 721-726, 745-750, 763-768, 787-792, 805-810


25-25: Top-level timestamp migration is correct.

Using message.timestamp (Date) aligns with PV4 and tests assert it properly.

Also applies to: 43-43, 68-68, 86-86, 111-111, 129-129, 154-154, 172-172, 197-197, 215-215, 240-240, 258-258, 282-282, 305-305, 333-333, 351-351, 478-478, 496-496, 519-519, 539-539, 570-570, 601-601, 630-630, 659-659, 681-681, 709-709, 727-727, 751-751, 769-769, 793-793, 811-811


512-518: with() event application scenarios look sound.

Update, delete, equal-version, and older-version behaviors are exercised well.

Also applies to: 533-538, 564-569, 595-600, 624-629, 653-658, 703-708, 721-726, 745-750, 763-768, 787-792, 805-810


267-269: Serial is opaque; version suffix is part of valid format. Tests using :0 suffix are correct—no change needed.

Likely an incorrect or invalid review comment.

test/core/message-parser.test.ts (1)

19-22: PV4 message shape assertions are accurate.

Inbound → DefaultMessage mapping (timestamp to Date, nested version.*, action normalization) is validated correctly across cases.

Also applies to: 36-39, 55-57, 73-75, 84-84, 95-98, 114-116, 130-132, 137-156, 179-182, 196-198, 212-214, 228-230, 244-246, 260-262, 276-278, 292-294, 318-321, 339-341, 381-386, 387-390, 426-432, 433-436, 469-474, 475-478, 513-518, 519-522, 352-357, 447-454, 491-497, 535-541

Comment on lines +201 to 239
// As Chat uses mutable messages, we know that `serial` will be defined, so this cast is ok
const serial = event.serial as unknown as string;

// Set the reaction types from the summary
const summary = event.annotations.summary;

const unique = (event.summary[ReactionAnnotationType.Unique] ?? {}) as unknown as Ably.SummaryUniqueValues;
const distinct = (event.summary[ReactionAnnotationType.Distinct] ?? {}) as unknown as Ably.SummaryDistinctValues;
const multiple = (event.summary[ReactionAnnotationType.Multiple] ?? {}) as Ably.SummaryMultipleValues;
const unique = (summary[ReactionAnnotationType.Unique] ?? {}) as unknown as Ably.SummaryUniqueValues;
const distinct = (summary[ReactionAnnotationType.Distinct] ?? {}) as unknown as Ably.SummaryDistinctValues;
const multiple = (summary[ReactionAnnotationType.Multiple] ?? {}) as Ably.SummaryMultipleValues;

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Guard against missing serial/annotations and drop unsafe cast.
Avoid double assertion; add runtime checks for event.serial and event.annotations?.summary to prevent undefined access.

Apply:

-    // As Chat uses mutable messages, we know that `serial` will be defined, so this cast is ok
-    const serial = event.serial as unknown as string;
-
-    // Set the reaction types from the summary
-    const summary = event.annotations.summary;
+    // @CHA-1101: summary events carry message serial and annotations.summary in PV4
+    if (typeof event.serial !== 'string' || !event.annotations?.summary) {
+      this._logger.debug('MessagesReactions._processMessageEvent(); ignoring summary with missing serial/summary', { event });
+      return;
+    }
+    const serial = event.serial;
+
+    // Set the reaction types from the summary
+    const summary = event.annotations.summary;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// As Chat uses mutable messages, we know that `serial` will be defined, so this cast is ok
const serial = event.serial as unknown as string;
// Set the reaction types from the summary
const summary = event.annotations.summary;
const unique = (event.summary[ReactionAnnotationType.Unique] ?? {}) as unknown as Ably.SummaryUniqueValues;
const distinct = (event.summary[ReactionAnnotationType.Distinct] ?? {}) as unknown as Ably.SummaryDistinctValues;
const multiple = (event.summary[ReactionAnnotationType.Multiple] ?? {}) as Ably.SummaryMultipleValues;
const unique = (summary[ReactionAnnotationType.Unique] ?? {}) as unknown as Ably.SummaryUniqueValues;
const distinct = (summary[ReactionAnnotationType.Distinct] ?? {}) as unknown as Ably.SummaryDistinctValues;
const multiple = (summary[ReactionAnnotationType.Multiple] ?? {}) as Ably.SummaryMultipleValues;
// @CHA-1101: summary events carry message serial and annotations.summary in PV4
if (typeof event.serial !== 'string' || !event.annotations?.summary) {
this._logger.debug('MessagesReactions._processMessageEvent(); ignoring summary with missing serial/summary', { event });
return;
}
const serial = event.serial;
// Set the reaction types from the summary
const summary = event.annotations.summary;
const unique = (summary[ReactionAnnotationType.Unique] ?? {}) as unknown as Ably.SummaryUniqueValues;
const distinct = (summary[ReactionAnnotationType.Distinct] ?? {}) as unknown as Ably.SummaryDistinctValues;
const multiple = (summary[ReactionAnnotationType.Multiple] ?? {}) as unknown as Ably.SummaryMultipleValues;
🤖 Prompt for AI Agents
In src/core/messages-reactions.ts around lines 201 to 210, remove the unsafe
double-cast and add runtime guards: check that event.serial is present and is a
string (or convert/return early) and that event.annotations?.summary exists
before accessing it; then narrow types using proper TypeScript checks (e.g.,
typeof/optional chaining) and assign unique/distinct/multiple from the guarded
summary with correct typing rather than using "as unknown as". If any required
value is missing, handle it gracefully (early return or log and skip) so no
undefined access occurs.

Comment on lines +315 to +338
summary: { [MessageReactionType.Unique]: { '🥦': { total: 1, clientIds: ['user1'] } } },
},
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Wrong enum used for summary keys; should be ReactionAnnotationType.
Using MessageReactionType here produces mismatched keys and fragile tests.

-          summary: { [MessageReactionType.Unique]: { '🥦': { total: 1, clientIds: ['user1'] } } },
+          summary: { [ReactionAnnotationType.Unique]: { '🥦': { total: 1, clientIds: ['user1'] } } },
@@
-          summary: { [MessageReactionType.Unique]: { '🥦': { total: 1, clientIds: ['user1'] } } },
+          summary: { [ReactionAnnotationType.Unique]: { '🥦': { total: 1, clientIds: ['user1'] } } },

Also applies to: 333-334

🤖 Prompt for AI Agents
In test/core/messages-reactions.test.ts around lines 315-316 (and also lines
333-334), the test is using MessageReactionType as keys in the summary object
but the code expects ReactionAnnotationType; update the summary entries to use
ReactionAnnotationType instead of MessageReactionType, adjust any test imports
to include ReactionAnnotationType if not already imported, and ensure the
summary shapes/types still match the expected structure after replacing the
enum.

@AndyTWF AndyTWF force-pushed the chat/move-to-protocol-v4 branch from c7e3371 to 69a5362 Compare September 10, 2025 21:38
@github-actions github-actions bot temporarily deployed to staging/pull/620/typedoc September 10, 2025 21:39 Inactive
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (4)
src/core/message.ts (1)

198-216: @throws docs don’t match implementation for before()/after().

JSDoc claims these throw ErrorInfo for invalid serials, but code never throws. Either implement validation or remove @throws.

Minimal fix (docs-only):

-   * @throws {@link ErrorInfo} if serials of either message is invalid.

(Apply to both before() and after() blocks.)

src/core/messages.ts (2)

562-570: Public API changes detected. Please update the documentation and the Ably CLI.

The delete method now returns messages with the Protocol V4 structure, which is a breaking change in the public API.


575-591: Public API changes detected. Please update the documentation and the Ably CLI.

The update method now returns messages with the Protocol V4 structure, which is a breaking change in the public API.

test/core/message-parser.test.ts (1)

1-6: Mock Ably, use type-only import, and switch parser import to .ts

File: test/core/message-parser.test.ts — top of file.

  • Add vi.mock('ably') and import vi from vitest to match other tests.
  • Make the Ably import type-only to avoid a runtime load.
  • Use the .ts extension for the parser import to follow test import conventions.
-import * as Ably from 'ably';
-import { describe, expect, it } from 'vitest';
+import type * as Ably from 'ably';
+import { describe, expect, it, vi } from 'vitest';
+
+vi.mock('ably');

 import { ChatMessageAction } from '../../src/core/events.ts';
 import { DefaultMessage } from '../../src/core/message.ts';
-import { parseMessage } from '../../src/core/message-parser.js';
+import { parseMessage } from '../../src/core/message-parser.ts';
♻️ Duplicate comments (3)
test/core/messages-reactions.test.ts (2)

315-316: Wrong enum used for summary keys; should be ReactionAnnotationType.

Using MessageReactionType here produces mismatched keys and fragile tests.

-          summary: { [MessageReactionType.Unique]: { '🥦': { total: 1, clientIds: ['user1'] } } },
+          summary: { [ReactionAnnotationType.Unique]: { '🥦': { total: 1, clientIds: ['user1'] } } },

333-334: Wrong enum used for summary keys; should be ReactionAnnotationType.

Using MessageReactionType here produces mismatched keys and fragile tests.

-          summary: { [MessageReactionType.Unique]: { '🥦': { total: 1, clientIds: ['user1'] } } },
+          summary: { [ReactionAnnotationType.Unique]: { '🥦': { total: 1, clientIds: ['user1'] } } },
test/core/messages.test.ts (1)

278-279: Fix incorrect action in update message mocks.

The mocked response for update operations incorrectly uses ChatMessageAction.MessageDelete instead of ChatMessageAction.MessageUpdate. This contradicts the update scenario being tested.

Apply this diff to fix the action in both update tests:

-        action: ChatMessageAction.MessageDelete,
+        action: ChatMessageAction.MessageUpdate,

Also applies to: 325-326

Also applies to: 325-326

🧹 Nitpick comments (12)
test/core/rest-types.test.ts (2)

54-57: Avoid realistic auth header values in tests.

Use a neutral placeholder to avoid normalizing sensitive-looking tokens in fixtures.

Apply:

-        authorization: 'bearer-token',
+        authorization: 'redacted',

373-397: Rename test to reflect current mapping (no spread).

The title mentions “spread” but implementation maps fields explicitly. Rename for clarity.

-it('should preserve all other RestMessage fields in the spread', () => {
+it('should map all expected fields from RestMessage to Message', () => {
src/core/message.ts (3)

457-474: Tidy comparator: strict checks and typo fix; document assertion need.

  • Use strict undefined checks.
  • Fix “cannot compare1” typo.
  • Add a short note justifying the type assertion as per guidelines.
   private _compareVersions(
     first: Message,
     second: Message,
     comparator: (first: VersionWithSerial, second: VersionWithSerial) => boolean,
   ): boolean {
-    // If our serials aren't the same, not same message and cannot compare1
+    // If serials differ, they're different messages; cannot compare versions
     if (!first.equal(second)) {
       return false;
     }

-    // If one our version serials isn't set, cannot compare
-    if (first.version.serial == undefined || second.version.serial === undefined) {
+    // If either version serial is missing, cannot compare
+    if (first.version.serial === undefined || second.version.serial === undefined) {
       return false;
     }
 
-    // Use the comparator to determine the comparison
-    return comparator(first.version as VersionWithSerial, second.version as VersionWithSerial);
+    // Type assertion rationale: guarded above, so serial is defined on both versions. // @CHA-1101
+    return comparator(first.version as VersionWithSerial, second.version as VersionWithSerial);
   }

347-353: Optional: freeze metadata and headers for full immutability.

You freeze version and reactions; consider also freezing metadata and headers to prevent accidental mutation of nested structures. This would require minor test updates.


435-436: Replace magic 40000 error codes with ErrorCode.BadRequest

src/core/errors.ts exports enum ErrorCode (BadRequest = 40000) — replace literal 40000 usages with ErrorCode.BadRequest. Example locations: src/core/message.ts (around lines 434–436, 439–442, 483–487). Run a repo-wide search for 40000 (src/** and test/**) and update occurrences accordingly.

test/core/message.test.ts (3)

1-5: Mock ably to isolate tests.

Since the SUT constructs Ably.ErrorInfo, mock the module in this file.

 import { describe, expect, it } from 'vitest';
 
 import { ChatMessageAction, ChatMessageEvent, ChatMessageEventType } from '../../src/core/events.ts';
 import { DefaultMessage, emptyMessageReactions, Message } from '../../src/core/message.ts';
 
+vi.mock('ably');

6-6: Annotate spec reference.

Tie this suite to CHA-1101 for future readers.

-describe('ChatMessage', () => {
+// @CHA-1101: PV4 Version object + timestamp semantics
+describe('ChatMessage', () => {

736-736: Rename misleading test title.

The test asserts that a newer message replaces the original, but the name says “newer message is older”.

-it('should return original message if newer message is older', () => {
+it('should replace with the incoming message when it is a newer version', () => {
test/core/message-parser.test.ts (4)

4-4: Prefer importing enums from the public surface to reduce test coupling

If ChatMessageAction is re-exported from src/core/index.ts, importing from there keeps tests decoupled from internal file paths. If not re-exported, ignore.

-import { ChatMessageAction } from '../../src/core/events.ts';
+import { ChatMessageAction } from '../../src/core/index.ts';

170-310: Solid non-object coercion coverage

Nice table-driven tests covering data and extras with invalid primitive types. Consider adding one more case: extras is an object but extras.headers is an invalid type (e.g., string), to verify deep validation.

Happy to add that targeted case if you want it in this PR.


312-328: Strengthen the “default to current time” assertion

As written, the test passes even if parseMessage uses version.timestamp as a fallback, because it only checks for a Date instance. Freeze time and assert exact equality to the mocked “now”; also avoid providing version.timestamp to remove ambiguity.

-  it('should use current time as default when timestamp is undefined', () => {
-    const message = {
-      data: { text: 'hello' },
-      clientId: 'client1',
-      extras: {},
-      serial: '01728402074206-000@cbfkKvEYgBhDaZ38195418:0',
-      version: {
-        serial: '01728402074207-000@cbfkKvEYgBhDaZ38195418:0',
-        timestamp: 1728402074207,
-      },
-      action: ChatMessageAction.MessageCreate,
-    };
-
-    const result = parseMessage(message as Ably.InboundMessage);
-    expect(result).toBeInstanceOf(DefaultMessage);
-    expect(result.timestamp).toBeInstanceOf(Date);
-  });
+  it('should use current time as default when timestamp is undefined', () => {
+    vi.useFakeTimers();
+    const NOW = new Date('2025-01-01T00:00:00.000Z');
+    vi.setSystemTime(NOW);
+
+    const message = {
+      data: { text: 'hello' },
+      clientId: 'client1',
+      extras: {},
+      serial: '01728402074206-000@cbfkKvEYgBhDaZ38195418:0',
+      action: ChatMessageAction.MessageCreate,
+    };
+
+    const result = parseMessage(message as Ably.InboundMessage);
+    expect(result).toBeInstanceOf(DefaultMessage);
+    expect(result.timestamp).toEqual(NOW);
+
+    vi.useRealTimers();
+  });

369-413: Updated-message case looks correct; consider exercising summary semantics via getters

Great check for updatedAt/updatedBy deriving from version. If DefaultMessage exposes isUpdated/isDeleted getters, add expectations (especially with annotations.summary) to lock in the intended behavior from the PR comments.

I can add a small follow-up test asserting result.isUpdated === true and result.isDeleted === false for summary updates—let me know.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between c7e3371 and 69a5362.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (23)
  • .env.test.example (1 hunks)
  • cspell.json (1 hunks)
  • demo/src/components/MessageComponent/MessageComponent.tsx (1 hunks)
  • package.json (1 hunks)
  • src/core/chat-api.ts (8 hunks)
  • src/core/index.ts (1 hunks)
  • src/core/message-parser.ts (3 hunks)
  • src/core/message.ts (10 hunks)
  • src/core/messages-reactions.ts (1 hunks)
  • src/core/messages.ts (3 hunks)
  • src/core/rest-types.ts (3 hunks)
  • test/core/chat-api.test.ts (1 hunks)
  • test/core/message-parser.test.ts (27 hunks)
  • test/core/message.test.ts (38 hunks)
  • test/core/messages-reactions.test.ts (7 hunks)
  • test/core/messages.integration.test.ts (12 hunks)
  • test/core/messages.test.ts (27 hunks)
  • test/core/rest-types.test.ts (11 hunks)
  • test/core/serial.test.ts (1 hunks)
  • test/helper/environment.ts (1 hunks)
  • test/helper/realtime-client.ts (1 hunks)
  • test/react/hooks/use-messages.integration.test.tsx (1 hunks)
  • test/react/hooks/use-messages.test.tsx (2 hunks)
✅ Files skipped from review due to trivial changes (1)
  • src/core/rest-types.ts
🚧 Files skipped from review as they are similar to previous changes (11)
  • test/helper/realtime-client.ts
  • test/react/hooks/use-messages.integration.test.tsx
  • src/core/index.ts
  • cspell.json
  • package.json
  • demo/src/components/MessageComponent/MessageComponent.tsx
  • test/helper/environment.ts
  • test/react/hooks/use-messages.test.tsx
  • src/core/messages-reactions.ts
  • test/core/chat-api.test.ts
  • src/core/message-parser.ts
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: When importing in this project, use relative imports.
When importing the package ably, do import * as Ably from 'ably'.
Use named exports over default exports.
Group related exports together.
Use PascalCase for interface names.
Use descriptive names that reflect the purpose for interfaces.
Prefix interfaces with 'I' is not required.
Use JSDoc style comments for interfaces.
Include a clear description of the interface's purpose in JSDoc.
Document all properties inline, alongside the property itself in interfaces.
Document all errors using @throws in JSDoc.
Document the return type using @return in JSDoc.
Link using {@link} to types mentioned in JSDoc.
Use PascalCase for enum names.
Use PascalCase for enum members.
Include JSDoc documentation for both enum and members.
Use PascalCase for class names.
Implement interfaces explicitly in classes.
Avoid extension wherever possible in classes.
Document class purpose and implementation details.
Use underscore prefix for private members in classes.
Group class members by visibility (private, protected, public).
Document all public methods and properties in classes.
Use JSDoc for all public methods.
Make use of @param, @returns and @throws in method documentation.
Use descriptive parameter names in methods.
Group related parameters into parameter objects in methods.
Use optional parameters with default values when appropriate in methods.
The error type used is ErrorInfo from the package 'ably'.
Use consistent logging levels (trace, debug, error).
Include relevant context in log messages, the second argument is a free-formed object for any relevant context.
Never log instances of Ably channels.
Structure log messages for easy parsing.
Minimize use of type assertions.
Use type guards when possible.
Document when and why type assertions are necessary.
Never use any. If we really can't avoid it, use unknown, but strong typing always preferred.
Use async/await over raw promises.
Handle promise re...

Files:

  • src/core/messages.ts
  • test/core/serial.test.ts
  • test/core/rest-types.test.ts
  • test/core/message-parser.test.ts
  • test/core/message.test.ts
  • test/core/messages-reactions.test.ts
  • src/core/chat-api.ts
  • src/core/message.ts
  • test/core/messages.test.ts
  • test/core/messages.integration.test.ts
src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/file-conventions.mdc)

Create all Typescript and TSX files using kebab-case.

Files:

  • src/core/messages.ts
  • src/core/chat-api.ts
  • src/core/message.ts

⚙️ CodeRabbit configuration file

src/**/*.{ts,tsx}: If there are any changes to the public-facing API, add a review comment: "Public API changes detected. Please update the documentation and the Ably CLI."

Files:

  • src/core/messages.ts
  • src/core/chat-api.ts
  • src/core/message.ts
**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.test.{ts,tsx}: Test files should end with .test.ts.
Name test files to reflect the module being tested.
Use clear, descriptive names that explain the test's purpose in test descriptions.
Group related tests using describe blocks.
Use nested describes for sub-features in tests.
Arrange-Act-Assert structure for test cases.
Test both positive and negative cases, including edge cases and boundary conditions.
Use clear, specific assertions in tests.
Create meaningful test data that clearly demonstrates the test case.
Use random room IDs to avoid conflicts in tests.
Use .each to write table/data driven tests.
Use custom matchers like toBeErrorInfo, toThrowErrorInfo, toBeErrorInfoWithCode, toThrowErrorInfoWithCode, toBeErrorInfoWithCauseCode for error testing.
Include an annotation of the specification point in the relevant test methods, e.g // CHA-M10a.

**/*.test.{ts,tsx}: Use the custom matcher toBeErrorInfo to validate that an error matches expected ErrorInfo properties (code, statusCode, message, cause) when testing error scenarios.
Use the custom matcher toThrowErrorInfo to check if a function throws an error matching expected ErrorInfo properties in tests.
Use the custom matcher toBeErrorInfoWithCode to validate that an error has a specific error code in tests.
Use the custom matcher toThrowErrorInfoWithCode to check if a function throws an error with a specific code in tests.
Use the custom matcher toBeErrorInfoWithCauseCode to validate that an error has a cause with a specific error code in tests.
Use waitForEventualHookValueToBeDefined utility to wait for a React hook value to become defined in asynchronous hook tests.
Use waitForEventualHookValue utility to wait for a React hook to return an expected value in asynchronous hook tests.
Use vi.waitFor to wait for asynchronous events to happen in tests.

**/*.test.{ts,tsx}: Mock the ably library as a module using vi.mock('ably') in test files that use Ably.
Each test file should ...

Files:

  • test/core/serial.test.ts
  • test/core/rest-types.test.ts
  • test/core/message-parser.test.ts
  • test/core/message.test.ts
  • test/core/messages-reactions.test.ts
  • test/core/messages.test.ts
  • test/core/messages.integration.test.ts
test/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/file-conventions.mdc)

Create all Typescript and TSX files using kebab-case.

Files:

  • test/core/serial.test.ts
  • test/core/rest-types.test.ts
  • test/core/message-parser.test.ts
  • test/core/message.test.ts
  • test/core/messages-reactions.test.ts
  • test/core/messages.test.ts
  • test/core/messages.integration.test.ts
**/*.test.ts

📄 CodeRabbit inference engine (.cursor/rules/unit-testing.mdc)

Test files should end with .test.ts and the name should reflect the module being tested (e.g., message.test.ts for message.ts).

Files:

  • test/core/serial.test.ts
  • test/core/rest-types.test.ts
  • test/core/message-parser.test.ts
  • test/core/message.test.ts
  • test/core/messages-reactions.test.ts
  • test/core/messages.test.ts
  • test/core/messages.integration.test.ts
**/*.integration.test.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.integration.test.{ts,tsx}: Integration tests should use the vitest framework and follow the described structure.
Do not mock modules in integration tests; use real Ably.Realtime clients.
For async operations in integration tests, always use async/await and proper timeout handling.
Use vi.waitFor to wait for async events in integration tests.

Files:

  • test/core/messages.integration.test.ts
🧠 Learnings (5)
📚 Learning: 2024-11-07T21:42:51.681Z
Learnt from: vladvelici
PR: ably/ably-chat-js#378
File: test/core/messages.test.ts:0-0
Timestamp: 2024-11-07T21:42:51.681Z
Learning: In `test/core/messages.test.ts`, the mocked response for `chatApi.deleteMessage` in the test case `'should be able to delete a message and get it back from response'` should only include `serial` and `deletedAt`, as the real `deleteMessage` API response does not provide additional properties like `text`, `clientId`, `createdAt`, or `roomId`.

Applied to files:

  • src/core/messages.ts
  • test/core/serial.test.ts
  • test/core/rest-types.test.ts
  • test/core/message-parser.test.ts
  • test/core/message.test.ts
  • test/core/messages-reactions.test.ts
  • src/core/chat-api.ts
  • test/core/messages.test.ts
  • test/core/messages.integration.test.ts
📚 Learning: 2024-12-04T14:12:11.101Z
Learnt from: vladvelici
PR: ably/ably-chat-js#427
File: src/core/messages.ts:501-502
Timestamp: 2024-12-04T14:12:11.101Z
Learning: In `src/core/messages.ts`, when constructing a `DefaultMessage` in the `send` method, passing the same `createdAt` date twice is intentional. One date is for `createdAt`, and the other is for `timestamp`. They should point to the same value, represented by `createdAt` after a message is posted.

Applied to files:

  • src/core/messages.ts
  • test/core/serial.test.ts
  • test/core/rest-types.test.ts
  • test/core/message-parser.test.ts
  • test/core/message.test.ts
  • src/core/message.ts
  • test/core/messages.test.ts
📚 Learning: 2024-12-04T14:08:24.299Z
Learnt from: vladvelici
PR: ably/ably-chat-js#427
File: src/core/message.ts:251-275
Timestamp: 2024-12-04T14:08:24.299Z
Learning: In `src/core/message.ts` (TypeScript), the `version` property of the `Message` class can be compared directly using `<`, `>`, and `===` operators, as the `version` strings are safely comparable in this context.

Applied to files:

  • src/core/messages.ts
  • test/core/serial.test.ts
  • test/core/rest-types.test.ts
  • test/core/message-parser.test.ts
  • test/core/message.test.ts
  • src/core/message.ts
  • test/core/messages.integration.test.ts
📚 Learning: 2024-10-22T15:54:58.070Z
Learnt from: splindsay-92
PR: ably/ably-chat-js#365
File: test/core/messages.integration.test.ts:95-107
Timestamp: 2024-10-22T15:54:58.070Z
Learning: In tests handling `messageEvent.type`, we should throw an error for unexpected message event types to ensure changes are caught.

Applied to files:

  • test/core/rest-types.test.ts
  • test/core/message.test.ts
📚 Learning: 2025-08-08T16:24:19.988Z
Learnt from: splindsay-92
PR: ably/ably-chat-js#601
File: test/core/realtime-subscriptions.test.ts:3-4
Timestamp: 2025-08-08T16:24:19.988Z
Learning: Applies to test/**/*.{ts,tsx} in ably/ably-chat-js: Keep explicit .ts/.tsx extensions in import paths within test files; do not switch to .js extensions. This aligns with the project’s Vitest + TS setup and existing test code conventions.

Applied to files:

  • test/core/messages.test.ts
🧬 Code graph analysis (9)
src/core/messages.ts (2)
src/core/message.ts (1)
  • DefaultMessage (315-516)
src/core/rest-types.ts (1)
  • messageFromRest (51-82)
test/core/rest-types.test.ts (2)
src/core/rest-types.ts (2)
  • messageFromRest (51-82)
  • RestMessage (34-44)
src/core/message.ts (2)
  • DefaultMessage (315-516)
  • emptyMessageReactions (522-526)
test/core/message-parser.test.ts (1)
src/core/index.ts (1)
  • ChatMessageAction (22-22)
test/core/message.test.ts (1)
src/core/message.ts (2)
  • emptyMessageReactions (522-526)
  • Message (68-253)
test/core/messages-reactions.test.ts (1)
src/core/index.ts (2)
  • ChatMessageAction (22-22)
  • MessageReactionType (25-25)
src/core/chat-api.ts (4)
src/core/rest-types.ts (1)
  • RestMessage (34-44)
src/core/messages.ts (1)
  • UpdateMessageParams (113-128)
src/core/messages-reactions.ts (1)
  • DeleteMessageReactionParams (64-76)
src/core/occupancy-parser.ts (1)
  • OccupancyData (6-16)
src/core/message.ts (1)
src/core/operation-metadata.ts (1)
  • OperationMetadata (10-10)
test/core/messages.test.ts (2)
src/core/message.ts (1)
  • emptyMessageReactions (522-526)
src/core/chat-api.ts (2)
  • deleteMessage (185-198)
  • updateMessage (216-224)
test/core/messages.integration.test.ts (2)
src/core/chat-api.ts (1)
  • history (126-154)
src/core/messages.ts (1)
  • history (519-522)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: test node 18
  • GitHub Check: test node 24
  • GitHub Check: test node 20
  • GitHub Check: test node 22
  • GitHub Check: demo-app
  • GitHub Check: build
🔇 Additional comments (25)
.env.test.example (1)

4-4: Approve — sandbox mapping & v4 routes verified

environment.ts maps 'sandbox' → 'nonprod:sandbox'; realtime-client.ts defaults endpoint via testEndpoint(); tests reference /chat/v4/ (see environment.ts, realtime-client.ts, test/core/chat-api.test.ts). Update/verify CI and developer docs (and CI .env) so missing .env.test cannot cause tests to target production.

test/core/rest-types.test.ts (1)

11-14: Good migration to Version object and Date conversions.

The tests correctly assert the new RestVersion shape and the Date coercions for both top-level timestamp and version.timestamp. Also validates action fallback to MessageCreate. Looks solid.

Also applies to: 27-27, 33-35

src/core/message.ts (2)

33-58: Public API changes detected. Please update the documentation and the Ably CLI.

Version introduced (object), Message.timestamp semantics, and Message.version type change are public. Ensure TypeDoc, website docs, and CLI are updated per PV4 (CHA-1101).

Also applies to: 84-88, 123-127


355-361: Clarify semantics for summary actions in getters.

Confirm that summary events cannot set action to MessageUpdate/Delete; otherwise isUpdated/isDeleted would misclassify rolled-up summaries. Consider a note in JSDoc or a guard if needed.

Also applies to: 371-377

test/core/message.test.ts (1)

615-639: Confirm event type used for deletes.

This test applies a delete using ChatMessageEventType.Updated. Validate against the spec whether a dedicated Deleted event type exists or if Updated is correct for both update and delete.

test/core/serial.test.ts (1)

30-30: LGTM! Version object structure correctly updated for Protocol V4.

The test now properly uses the nested version object with serial and timestamp fields, aligning with the Protocol V4 message structure changes.

test/core/messages-reactions.test.ts (2)

194-201: LGTM! Annotations structure properly migrated to Protocol V4 format.

The test correctly moves the summary data to the nested annotations.summary structure, which aligns with Protocol V4's approach to message annotations.


781-788: LGTM! Reaction events correctly use Protocol V4 version and annotations structure.

The emulated backend events properly include the version object and nested annotations structure, ensuring tests accurately validate the Protocol V4 message format.

Also applies to: 818-823

src/core/messages.ts (3)

541-556: LGTM! Message creation correctly implements Protocol V4 version structure.

The implementation properly constructs the version object with both serial and timestamp, and correctly uses the timestamp for both the version and top-level timestamp fields, as expected for newly created messages.


590-590: Resolved — updateMessage returns a RestMessage; current change is correct.

Unit tests assert updateMessage returns the message object (serial, text, clientId, timestamp), so passing response to messageFromRest(response) is appropriate.


569-569: Resolved — chatApi.deleteMessage returns a RestMessage; no change needed.
ChatApi.deleteMessage is typed as DeleteMessageResponse = RestMessage (src/core/chat-api.ts), RestMessage is defined in src/core/rest-types.ts, and tests mock deleteMessage with a RestMessage-shaped object (test/core/messages.test.ts). Using messageFromRest(response) is correct.

src/core/chat-api.ts (4)

32-37: LGTM! Documentation improvements enhance clarity.

The addition of JSDoc comments for serial and timestamp fields in CreateMessageResponse improves API documentation and makes the interface contract clearer.


45-47: LGTM! Type aliases correctly reflect the new v4 API structure.

The changes to UpdateMessageResponse and DeleteMessageResponse to alias RestMessage align with the Protocol V4 message structure where these responses now return the full message data.


150-151: LGTM! All endpoints correctly migrated to v4.

All Chat API endpoints have been successfully migrated from /chat/v3/ to /chat/v4/:

  • history
  • getMessage
  • deleteMessage
  • sendMessage
  • updateMessage
  • sendMessageReaction
  • deleteMessageReaction
  • getOccupancy

This migration aligns with the Protocol V4 changes for chat message payloads.

Also applies to: 179-180, 193-194, 213-213, 220-221, 229-229, 236-237, 245-245


119-119: Verify/update _apiProtocolVersion to v4

_apiProtocolVersion is still 3 (src/core/chat-api.ts:119) and is passed to this._realtime.request (lines ~254 and ~273); a search for "chat/v3" returned no matches — confirm whether the HTTP endpoints were migrated to v4 and, if so, change the constant to 4; if this value intentionally represents the Ably realtime protocol version, leave it as-is.

test/core/messages.integration.test.ts (4)

172-177: LGTM! Version object assertions correctly validate the new structure.

The test correctly validates that:

  • deletedAt equals version.timestamp
  • deletedBy equals version.clientId
  • Version metadata fields (description, metadata) are properly preserved

This aligns with the new Protocol V4 structure where version information is now an object containing metadata previously held in the Operation field.


391-396: LGTM! Update message assertions properly validate version metadata.

The test correctly validates the version object fields for updated messages, ensuring the description and metadata from the update operation are preserved in the version object.


646-653: LGTM! History tests correctly validate the version object structure.

The test properly validates the version object in history results, ensuring all fields (serial, timestamp, clientId, description, metadata) are correctly populated from the deleted message's version data.


694-699: LGTM! Update history test correctly validates version fields.

The test properly validates that updated messages in history contain the correct version object with all expected fields populated.

test/core/messages.test.ts (3)

134-150: LGTM! Delete message mocks correctly implement v4 structure.

The mocked responses for delete operations correctly implement the new Protocol V4 structure with:

  • Top-level message fields (serial, clientId, text, metadata, headers, action, timestamp)
  • Nested version object containing versioning metadata
  • Proper use of emptyMessageReactions() helper

Also applies to: 176-192, 219-235


205-206: LGTM! Version assertions correctly validate clientId field.

The tests properly validate that the version object contains the expected clientId, ensuring the version metadata is correctly populated.

Also applies to: 248-249, 305-306, 352-353


624-643: LGTM! Default values test correctly handles empty version object.

The test properly validates that messages with missing fields use appropriate defaults, including an empty version object and properly structured reactions.

test/core/message-parser.test.ts (3)

137-156: Good coverage for missing version fields

Validates that version subfields default to undefined when absent without leaking top-level values into version. This guards against accidental fallback coupling.


330-367: LGTM: new-message shape and expectations match PV4

Asserts top-level timestamp mapping and version object presence without conflating their semantics. Good negative checks for update/delete fields.


415-545: Deletion scenarios well covered

Verifies empty text/metadata/headers and derives deletedAt/deletedBy from version. Good separation from update fields.

@AndyTWF AndyTWF force-pushed the chat/move-to-protocol-v4 branch from 69a5362 to 65957d3 Compare September 10, 2025 21:51
@github-actions github-actions bot temporarily deployed to staging/pull/620/typedoc September 10, 2025 21:52 Inactive
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
demo/src/main.tsx (1)

1-12: Replace deprecated Ably host options (restHost/realtimeHost → endpoint).

  • demo/api/ably-token-request/index.ts:26–27 — restHost and realtimeHost are still set to process.env.VITE_ABLY_HOST; update to use the Ably SDK's current 'endpoint' option (or remove these fields).
  • demo/src/main.tsx:30,54 and vitest.config.ts:28,38 — the identifier "environment" appears (app/test config); confirm no Ably ClientOptions.environment remains.
🧹 Nitpick comments (5)
demo/package.json (1)

17-17: Pin Ably dependency to a commit SHA for reproducible builds.

Branch heads can move and break the demo unexpectedly. Pin to a specific commit (or Git tag) on protocol-v4.

-    "ably": "github:ably/ably-js#protocol-v4",
+    "ably": "github:ably/ably-js#<commit-sha>",
demo/src/main.tsx (2)

48-51: Avoid setting endpoint: undefined in production.

Only set endpoint when a host is provided; otherwise omit the property.

-      realtimeOptions.endpoint = import.meta.env.VITE_ABLY_HOST ?? undefined;
+      const host = import.meta.env.VITE_ABLY_HOST;
+      if (host) {
+        realtimeOptions.endpoint = host;
+      }

36-56: Minor: structure logs with context.

Optional: include structured context to aid log parsing.

-      console.log('Using local Ably environment');
+      console.log('Using local Ably environment', { env: environment });
-      console.log('Using sandbox Ably environment');
+      console.log('Using sandbox Ably environment', { env: environment });
-      console.log('Using production Ably environment');
+      console.log('Using production Ably environment', { env: environment, host: import.meta.env.VITE_ABLY_HOST ?? null });
test/helper/environment.ts (2)

3-15: Type the return and be explicit about undefined; add a brief spec note.

Improves readability and avoids implicit undefined; ties to CHA-1101.

-export const testEndpoint = () => {
-  switch (process.env.VITE_ABLY_ENV) {
-    case 'local': {
-      return 'local-rest.ably.io';
-    }
-    case 'production': {
-      return;
-    }
-    default: {
-      return 'nonprod:sandbox';
-    }
-  }
-};
+/**
+ * Returns the Ably endpoint for tests based on VITE_ABLY_ENV.
+ * - 'local'       -> 'local-rest.ably.io'
+ * - 'production'  -> undefined (use Ably production defaults)
+ * - otherwise     -> 'nonprod:sandbox'
+ * @see CHA-1101
+ */
+export const testEndpoint = (): string | undefined => {
+  switch (process.env.VITE_ABLY_ENV) {
+    case 'local':
+      return 'local-rest.ably.io';
+    case 'production':
+      return undefined;
+    default:
+      return 'nonprod:sandbox';
+  }
+};

1-1: Ensure boolean return from isNonSandboxEnvironment.

Current expression can return undefined when the env var is unset.

-export const isNonSandboxEnvironment = () => process.env.VITE_ABLY_ENV && process.env.VITE_ABLY_ENV !== 'sandbox';
+export const isNonSandboxEnvironment = () =>
+  Boolean(process.env.VITE_ABLY_ENV) && process.env.VITE_ABLY_ENV !== 'sandbox';
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 69a5362 and 65957d3.

⛔ Files ignored due to path filters (1)
  • demo/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (6)
  • .env.test.example (1 hunks)
  • cspell.json (1 hunks)
  • demo/package.json (1 hunks)
  • demo/src/main.tsx (1 hunks)
  • test/helper/environment.ts (1 hunks)
  • test/helper/realtime-client.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • cspell.json
  • test/helper/realtime-client.ts
  • .env.test.example
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: When importing in this project, use relative imports.
When importing the package ably, do import * as Ably from 'ably'.
Use named exports over default exports.
Group related exports together.
Use PascalCase for interface names.
Use descriptive names that reflect the purpose for interfaces.
Prefix interfaces with 'I' is not required.
Use JSDoc style comments for interfaces.
Include a clear description of the interface's purpose in JSDoc.
Document all properties inline, alongside the property itself in interfaces.
Document all errors using @throws in JSDoc.
Document the return type using @return in JSDoc.
Link using {@link} to types mentioned in JSDoc.
Use PascalCase for enum names.
Use PascalCase for enum members.
Include JSDoc documentation for both enum and members.
Use PascalCase for class names.
Implement interfaces explicitly in classes.
Avoid extension wherever possible in classes.
Document class purpose and implementation details.
Use underscore prefix for private members in classes.
Group class members by visibility (private, protected, public).
Document all public methods and properties in classes.
Use JSDoc for all public methods.
Make use of @param, @returns and @throws in method documentation.
Use descriptive parameter names in methods.
Group related parameters into parameter objects in methods.
Use optional parameters with default values when appropriate in methods.
The error type used is ErrorInfo from the package 'ably'.
Use consistent logging levels (trace, debug, error).
Include relevant context in log messages, the second argument is a free-formed object for any relevant context.
Never log instances of Ably channels.
Structure log messages for easy parsing.
Minimize use of type assertions.
Use type guards when possible.
Document when and why type assertions are necessary.
Never use any. If we really can't avoid it, use unknown, but strong typing always preferred.
Use async/await over raw promises.
Handle promise re...

Files:

  • demo/src/main.tsx
  • test/helper/environment.ts
test/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/file-conventions.mdc)

Create all Typescript and TSX files using kebab-case.

Files:

  • test/helper/environment.ts
🧠 Learnings (5)
📚 Learning: 2025-07-23T10:06:04.393Z
Learnt from: CR
PR: ably/ably-chat-js#0
File: .cursor/rules/typescript-conventions.mdc:0-0
Timestamp: 2025-07-23T10:06:04.393Z
Learning: Applies to **/*.{ts,tsx} : When importing the package ably, use `import * as Ably from 'ably'`.

Applied to files:

  • demo/package.json
📚 Learning: 2025-07-22T15:04:27.777Z
Learnt from: CR
PR: ably/ably-chat-js#0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-07-22T15:04:27.777Z
Learning: Applies to **/*.{ts,tsx} : When importing the package ably, do `import * as Ably from 'ably'`.

Applied to files:

  • demo/package.json
📚 Learning: 2025-06-30T12:57:08.726Z
Learnt from: CR
PR: ably/ably-chat-js#0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-06-30T12:57:08.726Z
Learning: Applies to test-setup.ts : Skip test environment setup when using the local realtime cluster.

Applied to files:

  • demo/src/main.tsx
📚 Learning: 2025-08-08T16:24:19.988Z
Learnt from: splindsay-92
PR: ably/ably-chat-js#601
File: test/core/realtime-subscriptions.test.ts:3-4
Timestamp: 2025-08-08T16:24:19.988Z
Learning: Applies to test/**/*.{ts,tsx} in ably/ably-chat-js: Keep explicit .ts/.tsx extensions in import paths within test files; do not switch to .js extensions. This aligns with the project’s Vitest + TS setup and existing test code conventions.

Applied to files:

  • test/helper/environment.ts
📚 Learning: 2025-06-30T12:57:08.726Z
Learnt from: CR
PR: ably/ably-chat-js#0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-06-30T12:57:08.726Z
Learning: Applies to test-setup.ts : Automatically create a new app in the sandbox environment before tests run, handle API key setup, and set `process.env.sandboxApiKey` for test authentication in test setup.

Applied to files:

  • test/helper/environment.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: check-all-package-lock-updated
  • GitHub Check: test node 18
  • GitHub Check: test node 22
  • GitHub Check: demo-app
  • GitHub Check: test node 20
  • GitHub Check: test node 24
🔇 Additional comments (1)
demo/src/main.tsx (1)

36-41: Local endpoint mapping looks good.

Setting endpoint + port 8081 + tls: false matches the local REST setup.

Copy link
Contributor Author

@splindsay-92 splindsay-92 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me, but I can't approve this..

- Version field is now an object, it contains the version string, timestamp of this version, and the clientId, description, and metadata fields previously held in the Operation field.

- CreatedAt has been updated to timestamp.

- Operation has been removed, its contents are now part of the version object.

- For the operation endpoints, redundant fields have been removed, so it only returns the message.

- For the `send` endpoint, createdAt is renamed to timestamp, and both the serial and timestamp filed are now embedded within a message field to mirror the delete/update endpoints.
@AndyTWF AndyTWF force-pushed the chat/move-to-protocol-v4 branch from 65957d3 to 8e04a86 Compare September 19, 2025 09:03
@github-actions github-actions bot temporarily deployed to staging/pull/620/typedoc September 19, 2025 09:04 Inactive
cursor[bot]

This comment was marked as outdated.

@github-actions github-actions bot temporarily deployed to staging/pull/620/typedoc September 19, 2025 09:09 Inactive
This change updates ably-js to a protocol-v4 capable version and fixes
various tests. We will need to pin it to a released version upon
release.
environment is deprecated so generating warnings.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (8)
src/core/message.ts (2)

33-58: Public API changes detected. Please update the documentation and the Ably CLI.

The Version interface represents a significant API change where version information is now structured as an object instead of a primitive string. This version-centric approach aligns with Protocol V4 requirements.


87-87: Public API changes detected. Please update the documentation and the Ably CLI.

The Message interface now uses timestamp instead of createdAt and version is now a Version object instead of a string. These changes align with Protocol V4 specifications.

Also applies to: 124-126

test/core/messages-reactions.test.ts (1)

337-337: Inconsistent enum usage in test data.

Line 337 uses MessageReactionType.Unique while the rest of the file consistently uses ReactionAnnotationType.Unique. This should be corrected for consistency.

Apply this fix for consistency:

-          summary: { [MessageReactionType.Unique]: { '🥦': { total: 1, clientIds: ['user1'] } } },
+          summary: { [ReactionAnnotationType.Unique]: { '🥦': { total: 1, clientIds: ['user1'] } } },

Also on line 355:

-          summary: { [MessageReactionType.Unique]: { '🥦': { total: 1, clientIds: ['user1'] } } },
+          summary: { [ReactionAnnotationType.Unique]: { '🥦': { total: 1, clientIds: ['user1'] } } },

Also applies to: 355-355

src/core/rest-types.ts (2)

17-24: Public API changes detected. Please update the documentation and the Ably CLI.

The new RestVersion interface replaces the removed RestOperation interface, consolidating version information with operation metadata. This is a significant API change that requires documentation updates.


33-43: Public API changes detected. Please update the documentation and the Ably CLI.

The RestMessage interface has been updated to:

  • Use RestVersion for the version field (instead of string)
  • Remove createdAt and operation fields
  • Keep timestamp at the top level

This aligns with Protocol V4 requirements.

src/core/chat-api.ts (3)

32-37: Public API changes detected. Please update the documentation and the Ably CLI.

The CreateMessageResponse interface has been updated:

  • Removed createdAt: number
  • Added timestamp: number
  • Added documentation comments for both fields

This change aligns with Protocol V4.


45-47: Public API changes detected. Please update the documentation and the Ably CLI.

The response types have been simplified:

  • MessageOperationResponse interface removed
  • UpdateMessageResponse and DeleteMessageResponse now alias RestMessage

This consolidation simplifies the API surface and aligns with Protocol V4.


150-150: LGTM! All API endpoints correctly migrated to v4.

All Chat API endpoints have been consistently updated from /chat/v3/ to /chat/v4/, including:

  • history, getMessage, deleteMessage, sendMessage, updateMessage
  • sendMessageReaction, deleteMessageReaction, getClientReactions, getOccupancy

This ensures consistent use of Protocol V4 across all operations.

Also applies to: 179-179, 193-193, 213-213, 220-220, 229-229, 236-236, 248-248, 257-257

🧹 Nitpick comments (1)
test/core/message-parser.test.ts (1)

84-84: Consider adding more comprehensive test for empty version object

While testing with an empty version object is valuable, this test case could be more comprehensive by including other required fields to better validate the default handling behavior.

     description: 'message.data.metadata is not an object',
     message: {
       data: { text: 'hello', metadata: 'not an object' },
+      clientId: 'client1',
+      timestamp: 1728402074206,
+      serial: '01728402074206-000@cbfkKvEYgBhDaZ38195418:0',
+      action: ChatMessageAction.MessageCreate,
       version: {},
     },
     expectedDefaults: { metadata: {} },
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 65957d3 and a9bfaf3.

⛔ Files ignored due to path filters (2)
  • demo/package-lock.json is excluded by !**/package-lock.json
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (25)
  • .env.test.example (1 hunks)
  • cspell.json (1 hunks)
  • demo/package.json (1 hunks)
  • demo/src/components/MessageComponent/MessageComponent.tsx (1 hunks)
  • demo/src/main.tsx (1 hunks)
  • package.json (1 hunks)
  • src/core/chat-api.ts (9 hunks)
  • src/core/index.ts (1 hunks)
  • src/core/message-parser.ts (3 hunks)
  • src/core/message.ts (10 hunks)
  • src/core/messages-reactions.ts (1 hunks)
  • src/core/messages.ts (3 hunks)
  • src/core/rest-types.ts (3 hunks)
  • test/core/chat-api.test.ts (3 hunks)
  • test/core/message-parser.test.ts (27 hunks)
  • test/core/message.test.ts (38 hunks)
  • test/core/messages-reactions.test.ts (7 hunks)
  • test/core/messages.integration.test.ts (12 hunks)
  • test/core/messages.test.ts (27 hunks)
  • test/core/rest-types.test.ts (11 hunks)
  • test/core/serial.test.ts (1 hunks)
  • test/helper/environment.ts (1 hunks)
  • test/helper/realtime-client.ts (1 hunks)
  • test/react/hooks/use-messages.integration.test.tsx (1 hunks)
  • test/react/hooks/use-messages.test.tsx (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (16)
  • test/core/chat-api.test.ts
  • cspell.json
  • src/core/index.ts
  • test/react/hooks/use-messages.integration.test.tsx
  • src/core/messages-reactions.ts
  • demo/src/main.tsx
  • demo/package.json
  • test/core/serial.test.ts
  • .env.test.example
  • package.json
  • test/core/message.test.ts
  • test/helper/environment.ts
  • src/core/messages.ts
  • test/core/messages.test.ts
  • test/core/messages.integration.test.ts
  • demo/src/components/MessageComponent/MessageComponent.tsx
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.{ts,tsx}: When importing in this project, use relative imports.
When importing the package ably, do import * as Ably from 'ably'.
Use named exports over default exports.
Group related exports together.
Use PascalCase for interface names.
Use descriptive names that reflect the purpose for interfaces.
Prefix interfaces with 'I' is not required.
Use JSDoc style comments for interfaces.
Include a clear description of the interface's purpose in JSDoc.
Document all properties inline, alongside the property itself in interfaces.
Document all errors using @throws in JSDoc.
Document the return type using @return in JSDoc.
Link using {@link} to types mentioned in JSDoc.
Use PascalCase for enum names.
Use PascalCase for enum members.
Include JSDoc documentation for both enum and members.
Use PascalCase for class names.
Implement interfaces explicitly in classes.
Avoid extension wherever possible in classes.
Document class purpose and implementation details.
Use underscore prefix for private members in classes.
Group class members by visibility (private, protected, public).
Document all public methods and properties in classes.
Use JSDoc for all public methods.
Make use of @param, @returns and @throws in method documentation.
Use descriptive parameter names in methods.
Group related parameters into parameter objects in methods.
Use optional parameters with default values when appropriate in methods.
The error type used is ErrorInfo from the package 'ably'.
Use consistent logging levels (trace, debug, error).
Include relevant context in log messages, the second argument is a free-formed object for any relevant context.
Never log instances of Ably channels.
Structure log messages for easy parsing.
Minimize use of type assertions.
Use type guards when possible.
Document when and why type assertions are necessary.
Never use any. If we really can't avoid it, use unknown, but strong typing always preferred.
Use async/await over raw promises.
Handle promise re...

Files:

  • test/helper/realtime-client.ts
  • test/react/hooks/use-messages.test.tsx
  • test/core/messages-reactions.test.ts
  • src/core/chat-api.ts
  • src/core/message-parser.ts
  • test/core/message-parser.test.ts
  • test/core/rest-types.test.ts
  • src/core/message.ts
  • src/core/rest-types.ts
test/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/file-conventions.mdc)

Create all Typescript and TSX files using kebab-case.

Files:

  • test/helper/realtime-client.ts
  • test/react/hooks/use-messages.test.tsx
  • test/core/messages-reactions.test.ts
  • test/core/message-parser.test.ts
  • test/core/rest-types.test.ts
**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.test.{ts,tsx}: Test files should end with .test.ts.
Name test files to reflect the module being tested.
Use clear, descriptive names that explain the test's purpose in test descriptions.
Group related tests using describe blocks.
Use nested describes for sub-features in tests.
Arrange-Act-Assert structure for test cases.
Test both positive and negative cases, including edge cases and boundary conditions.
Use clear, specific assertions in tests.
Create meaningful test data that clearly demonstrates the test case.
Use random room IDs to avoid conflicts in tests.
Use .each to write table/data driven tests.
Use custom matchers like toBeErrorInfo, toThrowErrorInfo, toBeErrorInfoWithCode, toThrowErrorInfoWithCode, toBeErrorInfoWithCauseCode for error testing.
Include an annotation of the specification point in the relevant test methods, e.g // CHA-M10a.

**/*.test.{ts,tsx}: Use the custom matcher toBeErrorInfo to validate that an error matches expected ErrorInfo properties (code, statusCode, message, cause) when testing error scenarios.
Use the custom matcher toThrowErrorInfo to check if a function throws an error matching expected ErrorInfo properties in tests.
Use the custom matcher toBeErrorInfoWithCode to validate that an error has a specific error code in tests.
Use the custom matcher toThrowErrorInfoWithCode to check if a function throws an error with a specific code in tests.
Use the custom matcher toBeErrorInfoWithCauseCode to validate that an error has a cause with a specific error code in tests.
Use waitForEventualHookValueToBeDefined utility to wait for a React hook value to become defined in asynchronous hook tests.
Use waitForEventualHookValue utility to wait for a React hook to return an expected value in asynchronous hook tests.
Use vi.waitFor to wait for asynchronous events to happen in tests.

**/*.test.{ts,tsx}: Mock the ably library as a module using vi.mock('ably') in test files that use Ably.
Each test file should ...

Files:

  • test/react/hooks/use-messages.test.tsx
  • test/core/messages-reactions.test.ts
  • test/core/message-parser.test.ts
  • test/core/rest-types.test.ts
**/*.test.tsx

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use waitForEventualHookValueToBeDefined and waitForEventualHookValue for testing async React hooks.

Files:

  • test/react/hooks/use-messages.test.tsx
**/*.test.ts

📄 CodeRabbit inference engine (.cursor/rules/unit-testing.mdc)

Test files should end with .test.ts and the name should reflect the module being tested (e.g., message.test.ts for message.ts).

Files:

  • test/core/messages-reactions.test.ts
  • test/core/message-parser.test.ts
  • test/core/rest-types.test.ts
src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/file-conventions.mdc)

Create all Typescript and TSX files using kebab-case.

Files:

  • src/core/chat-api.ts
  • src/core/message-parser.ts
  • src/core/message.ts
  • src/core/rest-types.ts

⚙️ CodeRabbit configuration file

src/**/*.{ts,tsx}: If there are any changes to the public-facing API, add a review comment: "Public API changes detected. Please update the documentation and the Ably CLI."

Files:

  • src/core/chat-api.ts
  • src/core/message-parser.ts
  • src/core/message.ts
  • src/core/rest-types.ts
🧬 Code graph analysis (7)
test/helper/realtime-client.ts (1)
test/helper/environment.ts (1)
  • testEndpoint (3-15)
test/core/messages-reactions.test.ts (1)
src/core/index.ts (2)
  • ChatMessageAction (22-22)
  • MessageReactionType (25-25)
src/core/chat-api.ts (3)
src/core/rest-types.ts (1)
  • RestMessage (34-44)
src/core/messages-reactions.ts (1)
  • DeleteMessageReactionParams (65-77)
src/core/occupancy-parser.ts (1)
  • OccupancyData (6-16)
src/core/message-parser.ts (2)
src/core/message.ts (1)
  • Version (33-58)
src/core/index.ts (1)
  • Version (42-42)
test/core/rest-types.test.ts (2)
src/core/rest-types.ts (2)
  • messageFromRest (51-82)
  • RestMessage (34-44)
src/core/message.ts (2)
  • DefaultMessage (315-516)
  • emptyMessageReactions (522-526)
src/core/message.ts (1)
src/core/operation-metadata.ts (1)
  • OperationMetadata (10-10)
src/core/rest-types.ts (1)
src/core/message.ts (1)
  • DefaultMessage (315-516)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Cursor Bugbot
🔇 Additional comments (27)
src/core/message.ts (7)

60-64: LGTM! Well-structured helper type for version comparisons.

The VersionWithSerial helper type effectively enforces that version comparisons can only occur when both messages have defined version serials, preventing runtime errors in comparison operations.


305-305: LGTM! Parameter and property types correctly updated.

The DefaultMessageParams and DefaultMessage class have been properly updated to use the new Version type instead of strings, maintaining type safety throughout the codebase.

Also applies to: 322-322


347-347: Good practice: Freezing version object for immutability.

Adding Object.freeze(this.version) ensures the version object remains immutable after construction, which is consistent with the readonly contract of the Message interface.


364-377: LGTM! Getters correctly updated to use version properties.

The getter methods now properly extract clientId and timestamp from the version object instead of legacy operation properties, maintaining backward compatibility for consumers.


379-409: Well-implemented version comparison logic with proper safety checks.

The version comparison methods now use the new _compareVersions helper which ensures both messages are the same message and have defined version serials before comparison. This prevents runtime errors and provides more reliable version ordering.


457-474: Robust version comparison helper with appropriate safety measures.

The _compareVersions method correctly validates that messages are the same (via equal()) and have defined version serials before applying the comparator function. This prevents invalid comparisons between different messages or messages without version information.


507-507: LGTM! Deep cloning ensures version object isolation.

Using cloneDeep(source.version) prevents unintended mutations between message copies, which is important since version objects may contain nested metadata.

test/helper/realtime-client.ts (1)

4-4: LGTM! Configuration updated for new environment setup.

The changes correctly update the import to use testEndpoint instead of testEnvironment and set options.endpoint instead of options.environment. This aligns with the broader endpoint-based configuration approach.

Also applies to: 10-10

test/core/messages-reactions.test.ts (1)

216-224: Test data correctly updated for version-centric structure.

The test payloads have been properly updated to use:

  • version as an object with { serial, timestamp } structure
  • annotations.summary for reaction summary data
  • Consistent use of ReactionAnnotationType enums

This correctly reflects the Protocol V4 message structure.

Also applies to: 229-237, 242-252, 257-275

test/react/hooks/use-messages.test.tsx (1)

124-128: Test expectations correctly updated for version object structure.

The test data has been properly updated to use the new version object structure with { serial, timestamp } instead of a simple string, and the DefaultMessage construction correctly reflects the new API without createdAt.

Also applies to: 204-204

test/core/rest-types.test.ts (2)

11-14: Test cases correctly updated for RestVersion structure.

All test cases have been properly updated to use the new RestVersion object structure:

  • version field now contains { serial, timestamp } object
  • Operation-related data (clientId, description, metadata) now properly nested within version
  • Tests correctly verify the new structure including result.version.serial and version timestamp conversion

These changes accurately reflect the Protocol V4 REST API changes.

Also applies to: 27-27, 40-43, 86-89, 106-114, 125-131, 136-139, 178-182, 214-217, 257-260, 320-323, 356-359, 376-379, 402-405, 423-426, 477-480, 498-501


392-392: Verify version timestamp handling consistency.

The tests correctly access result.version.serial and check result.version.timestamp conversion to Date objects. This properly validates the RestVersion structure and timestamp handling.

Also applies to: 492-492

src/core/rest-types.ts (2)

62-69: LGTM! Version construction properly handles optional fields.

The version object construction correctly maps all RestVersion fields including optional clientId, description, and metadata, with proper timestamp conversion from number to Date.


71-81: LGTM! DefaultMessage construction updated for new API.

The DefaultMessage constructor call has been properly updated to use the new version object structure and maintains all required fields for the Protocol V4 message format.

src/core/message-parser.ts (4)

4-4: Import correctly updated for new Version type.

The import statement has been properly updated to include Version from the message module and removed the unused Operation import, reflecting the API changes.


19-21: Payload structure updated for Protocol V4.

The MessagePayload interface has been correctly updated to:

  • Use Ably.MessageVersion for the version field
  • Add required annotations: Ably.MessageAnnotations

This aligns with Protocol V4 message structure requirements.


36-45: Robust version object construction with optional timestamp handling.

The version construction logic properly handles cases where message.version.timestamp may be undefined, only converting to Date when the timestamp is actually present. The conditional spread operator ensures the version object maintains the correct structure.


56-66: DefaultMessage construction correctly updated for new structure.

The DefaultMessage constructor call has been properly updated to use the constructed version object instead of a string, and all other fields are correctly mapped including the preserved timestamp field.

test/core/message-parser.test.ts (9)

19-22: LGTM - Version structure correctly updated to Protocol V4

The test fixture correctly uses the new nested version object structure with serial and timestamp fields, aligning with Protocol V4 changes.


137-156: Verify the version defaults test implementation

The test description says "message.version.timestamp is undefined" but the version object is completely empty. The test correctly validates that all version subfields default to undefined when the version object lacks those properties.


352-352: LGTM - Timestamp field correctly updated

The assertion correctly validates the new timestamp field structure, replacing the old createdAt field as per Protocol V4 changes.


355-356: LGTM - Version object structure correctly validated

The assertions properly validate the nested version object structure with serial and timestamp fields, aligning with Protocol V4 requirements.


398-398: LGTM - Consistent timestamp usage across test cases

All test assertions correctly use the timestamp field and convert version timestamps to Date objects, maintaining consistency with the Protocol V4 changes throughout all message types (create, update, delete).

Also applies to: 401-401, 444-444, 447-447, 487-487, 490-490, 531-531, 534-534


404-408: LGTM - Comprehensive version object validation

The test properly validates all version object fields including serial, timestamp, clientId, description, and metadata, ensuring complete Protocol V4 compliance.


433-435: Verify annotations.summary requirement for deleted messages

Deleted-message tests include annotations.summary (test/core/message-parser.test.ts: 433–435, 475–477, 519–521). src/core/messages-reactions.ts reads event.annotations.summary — confirm whether annotations.summary must always be present for deleted messages; if required, enforce/validate it, otherwise make tests and runtime tolerant of its absence.


387-389: Verify annotations field usage in message parsing

Confirm parseMessage validates and preserves annotations.summary as used in test/core/message-parser.test.ts (lines 387–389):

      annotations: {
        summary: {},
      },

Repo search with the supplied script returned no matches for parseMessage — run a targeted search for parseMessage or provide the message-parser implementation so this can be verified.


312-328: Adjust test to omit version.timestamp
The test description targets the “timestamp undefined” branch, but the message object still includes version.timestamp. Remove or explicitly set version.timestamp to undefined so that parseMessage falls back to the current‐time default.

@splindsay-92
Copy link
Contributor Author

I've reviewed the last few commits and they look good, lets merge 👍

Copy link
Collaborator

@AndyTWF AndyTWF left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@splindsay-92 has approved the last few commits, so we'll merge this

@AndyTWF AndyTWF merged commit 8e2277c into main Sep 19, 2025
14 checks passed
@AndyTWF AndyTWF deleted the chat/move-to-protocol-v4 branch September 19, 2025 21:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

4 participants