Skip to content

[DX-789] Add annotations support#167

Merged
umair-ably merged 4 commits intomainfrom
DX-789-annotations
Mar 13, 2026
Merged

[DX-789] Add annotations support#167
umair-ably merged 4 commits intomainfrom
DX-789-annotations

Conversation

@umair-ably
Copy link
Contributor

@umair-ably umair-ably commented Mar 12, 2026

Summary

  • Add channels annotations publish — publish annotations (reactions, flags) on channel messages
  • Add channels annotations subscribe — real-time subscription to annotation events with ANNOTATION_SUBSCRIBE channel mode
  • Add channels annotations get — retrieve annotations for a specific message
  • Add channels annotations delete — remove annotations from a message
  • Add channels annotations topic index command
  • Add annotation mock support in mock-ably-realtime.ts and mock-ably-rest.ts test helpers

Review Strategy

Order Area Key Files What to Look For
1 Topic index src/commands/channels/annotations/index.ts Standard BaseTopicCommand pattern
2 Publish src/commands/channels/annotations/publish.ts REST client, channel.annotations.publish(), JSON envelope, --name/--count/--data/--encoding flags
3 Delete src/commands/channels/annotations/delete.ts Uses Realtime (REST annotations.delete not exposed in SDK types), manual client.close() in finally
4 Get src/commands/channels/annotations/get.ts REST client, paginated results with --limit, formatCountLabel/formatLimitWarning output
5 Subscribe src/commands/channels/annotations/subscribe.ts Realtime with ANNOTATION_SUBSCRIBE mode, --type filter, --rewind/--duration flags, waitAndTrackCleanup lifecycle
6 Mock helpers test/helpers/mock-ably-{realtime,rest}.ts MockRealtimeAnnotations/MockRestAnnotations interfaces, event emitter wiring for subscribe
7 Tests test/unit/commands/channels/annotations/*.test.ts All 4 commands tested with standard 5-block structure (help, argument validation, functionality, flags, error handling)

Test Plan

  • pnpm prepare succeeds
  • pnpm exec eslint . — 0 errors
  • pnpm test:unit — all pass
  • Verify ably channels annotations publish my-channel "serial" "reactions:flag.v1" --name thumbsup works
  • Verify ably channels annotations subscribe my-channel streams events with --json and human output
  • Verify ably channels annotations get my-channel "serial" retrieves annotations
  • Verify ably channels annotations delete my-channel "serial" "reactions:flag.v1" removes annotations

@vercel
Copy link

vercel bot commented Mar 12, 2026

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

Project Deployment Actions Updated (UTC)
cli-web-cli Ready Ready Preview, Comment Mar 13, 2026 10:08am

Request Review

@umair-ably umair-ably requested a review from Copilot March 12, 2026 16:20
@coderabbitai
Copy link

coderabbitai bot commented Mar 12, 2026

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 2a2a0a28-e48b-4da8-8209-dcdce11f3b23

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

This pull request introduces a new "channels annotations" feature for the Ably CLI with four subcommands: delete, get, publish, and subscribe. It includes complete command implementations, test helpers for mocking annotation functionality, comprehensive unit tests, and documentation updates.

Changes

Cohort / File(s) Summary
Documentation
README.md
Added extensive documentation for the new channels annotations feature, including usage examples, arguments, flags, and descriptions for all four subcommands (delete, get, publish, subscribe).
Command implementations
src/commands/channels/annotations/index.ts, src/commands/channels/annotations/delete.ts, src/commands/channels/annotations/get.ts, src/commands/channels/annotations/publish.ts, src/commands/channels/annotations/subscribe.ts
Implemented five new command classes: top-level ChannelsAnnotations topic command and four subcommands for managing channel message annotations. Each command handles argument parsing, Ably client initialization, annotation operations, and JSON/human-readable output formatting.
Test infrastructure
test/helpers/mock-ably-realtime.ts, test/helpers/mock-ably-rest.ts
Extended mock utilities to support annotation operations. Added MockRealtimeAnnotations and MockRestAnnotations interfaces with publish, delete, subscribe, and get methods for simulating Ably annotation functionality in tests.
Command unit tests
test/unit/commands/channels/annotations/delete.test.ts, test/unit/commands/channels/annotations/get.test.ts, test/unit/commands/channels/annotations/publish.test.ts, test/unit/commands/channels/annotations/subscribe.test.ts
Comprehensive test suites for all four annotation subcommands covering help/argument validation, functional behavior, flag handling, JSON output formatting, and error scenarios.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • sacOO7

Poem

🐰 Annotations hop along the channel stream,
Four new commands make the CLI gleam,
Delete, get, publish, subscribe with care,
Messages adorned with data to share!
Tests and mocks ensure all works divine,
A fuzzy feature, perfectly aligned! ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Title check ✅ Passed The title 'DX-789 Add annotations support' clearly and directly describes the main change: introducing annotations support to the Ably CLI with new commands for channels.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch DX-789-annotations
📝 Coding Plan
  • Generate coding plan for human review comments

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.

@umair-ably umair-ably review requested due to automatic review settings March 12, 2026 16:20
@umair-ably umair-ably requested a review from Copilot March 12, 2026 16:22
@umair-ably
Copy link
Contributor Author

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Mar 12, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds support for Ably message annotations and richer message metadata (serial/version/annotations) across channel-related CLI commands, including new channels:annotations:* commands, and updates output formatting/tests accordingly.

Changes:

  • Introduces channels:annotations topic commands (subscribe/publish/get/delete) with unit tests and README docs.
  • Updates channel subscribe/history and presence subscribe outputs to use structured formatting helpers, including version + annotations summary display.
  • Extends test mocks (REST + Realtime) to support channel.annotations.* APIs and adjusts JSON log envelope expectations in tests.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
test/unit/commands/channels/subscribe.test.ts Updates message fixtures/assertions for serial/version/annotations and new JSON envelope shape.
test/unit/commands/channels/presence/subscribe.test.ts Updates presence output assertions and adds a flags describe for --client-id.
test/unit/commands/channels/history.test.ts Adds fixtures/assertions for serial/version/annotations; restructures describe blocks and updates “no messages” text.
test/unit/commands/channels/annotations/subscribe.test.ts New tests for annotations subscribe (human + JSON + --type).
test/unit/commands/channels/annotations/publish.test.ts New tests for publishing annotations and JSON result output.
test/unit/commands/channels/annotations/get.test.ts New tests for fetching annotations (limit/empty/JSON/human output).
test/unit/commands/channels/annotations/delete.test.ts New tests for deleting annotations and JSON result output.
test/helpers/mock-ably-rest.ts Adds annotations mocks to REST channel mock.
test/helpers/mock-ably-realtime.ts Adds annotations mocks to Realtime channel mock.
test/e2e/channels/channels-e2e.test.ts Removes a brittle history output assertion.
src/utils/output.ts Adds formatMessagesOutput / formatPresenceOutput and related field interfaces; adds annotations/version formatting.
src/commands/channels/subscribe.ts Switches to formatMessagesOutput; includes serial/version/annotations; changes JSON event envelope shape.
src/commands/channels/presence/subscribe.ts Switches to formatPresenceOutput; changes JSON event envelope shape.
src/commands/channels/history.ts Switches to formatMessagesOutput; updates empty-history message.
src/commands/channels/annotations/index.ts Adds channels:annotations topic command.
src/commands/channels/annotations/subscribe.ts New annotations subscribe implementation (rewind/type filter, human + JSON output).
src/commands/channels/annotations/publish.ts New annotations publish implementation (count/name/data/encoding flags, human + JSON output).
src/commands/channels/annotations/get.ts New annotations get implementation (limit flag, human + JSON output).
src/commands/channels/annotations/delete.ts New annotations delete implementation (realtime-based delete, human + JSON output).
README.md Adds generated CLI docs for the new channels:annotations commands.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

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: 2

🧹 Nitpick comments (4)
test/unit/commands/channels/annotations/get.test.ts (1)

101-113: Use captureJsonLogs() here too.

Directly parsing stdout bypasses the NDJSON helper the unit-test suite uses for JSON assertions, so this gets fragile once the command emits more than one JSON line. Based on learnings, "For JSON output assertions, use the captureJsonLogs() helper from test/helpers/ndjson.ts."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/unit/commands/channels/annotations/get.test.ts` around lines 101 - 113,
The test is directly parsing stdout which bypasses the NDJSON helper; replace
the JSON.parse(stdout) usage with the test/helpers/ndjson.ts helper
captureJsonLogs() when invoking the command (the same way other tests do) so you
capture NDJSON lines robustly; call runCommand with captureJsonLogs() to
retrieve parsed JSON entries for the "channels:annotations:get" invocation, then
perform the same assertions against the first parsed object (e.g., result =
parsed[0]) instead of parsing stdout.
test/unit/commands/channels/annotations/delete.test.ts (1)

106-124: Use captureJsonLogs() for the JSON assertion.

Parsing raw stdout here is brittle if the command later emits multiple NDJSON lines or verbose JSON events. The unit-test pattern in this repo is to assert JSON output through the helper instead of JSON.parse(stdout). Based on learnings, "For JSON output assertions, use the captureJsonLogs() helper from test/helpers/ndjson.ts."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/unit/commands/channels/annotations/delete.test.ts` around lines 106 -
124, Replace the brittle JSON.parse(stdout) approach in the "should output JSON
when --json flag is used" test with the shared helper captureJsonLogs() from
test/helpers/ndjson.ts: call captureJsonLogs on the output of runCommand
(instead of JSON.parse(stdout)), retrieve the captured JSON event (e.g., first
object) and run the same assertions on its properties (type, command, success,
channel, serial); update the test around the runCommand/capture point
accordingly so it uses captureJsonLogs to parse NDJSON-style output reliably.
src/commands/channels/annotations/index.ts (1)

4-5: Pluralize commandGroup for the generated help heading.

BaseTopicCommand renders this as Ably ${commandGroup} commands:. With the singular value here, the heading reads awkwardly as Ably Pub/Sub channel annotation commands:.

✏️ Suggested fix
-  protected commandGroup = "Pub/Sub channel annotation";
+  protected commandGroup = "Pub/Sub channel annotations";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/commands/channels/annotations/index.ts` around lines 4 - 5, The
commandGroup string is singular and leads to an awkward help heading; update the
protected property commandGroup in this class (where topicName =
"channels:annotations") to a plural form such as "Pub/Sub channel annotations"
so BaseTopicCommand will render the heading correctly; locate and change the
protected commandGroup = "Pub/Sub channel annotation" to the pluralized text in
the same file.
src/commands/channels/annotations/delete.ts (1)

79-91: Include count in logged and returned data for consistency.

The count flag is used when building the annotation (line 75) but is omitted from both logCliEvent (line 84) and logJsonResult (line 89). For debugging and auditability, include count alongside name:

Proposed fix
       this.logCliEvent(
         flags,
         "annotationDelete",
         "annotationDeleted",
         `Deleted annotation on message ${serial} in channel ${channelName}`,
-        { channel: channelName, serial, type, name: flags.name },
+        { channel: channelName, serial, type, name: flags.name, count: flags.count },
       );

       if (this.shouldOutputJson(flags)) {
         this.logJsonResult(
-          { channel: channelName, serial, type, name: flags.name },
+          { channel: channelName, serial, type, name: flags.name, count: flags.count },
           flags,
         );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/commands/channels/annotations/delete.ts` around lines 79 - 91, The logged
and returned payloads omit the count used when building the annotation; update
the calls to include flags.count so both audit logs and JSON output contain
count. Specifically, modify the logCliEvent invocation (the call to
this.logCliEvent) to add count: flags.count in the metadata object, and modify
the this.logJsonResult call to include count: flags.count in the returned object
alongside channel, serial, type, and name; keep using the existing flags
variable and existing function signatures (logCliEvent, logJsonResult).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/commands/channels/annotations/subscribe.ts`:
- Around line 112-125: The subscribe callback currently calls
formatMessageTimestamp(annotation.timestamp) unconditionally in function
callback which can produce "Invalid Date" or throw when annotation.timestamp is
missing; update callback to guard the field: compute timestamp only if
annotation.timestamp is present (e.g., const timestamp = annotation.timestamp ?
formatMessageTimestamp(annotation.timestamp) : undefined) and set
annotationEvent.timestamp accordingly (or omit it), so the handler mirrors the
optional behavior used in get.ts and avoids emitting invalid timestamps or
throwing in the stream callback.

In `@test/helpers/mock-ably-realtime.ts`:
- Around line 183-187: The annotations mock's subscribe function doesn't
transition its parent channel to attached like MockRealtimeChannel.subscribe
does; update the factory that creates the annotations object to accept the
parent channel's attach() method and modify annotations.subscribe (the vi.fn at
subscribe) to call that attach() when a subscription is made (before registering
the emitter callback), so channel.annotations.subscribe() will attach the
enclosing channel the same way MockRealtimeChannel.subscribe() does; ensure you
update any initialization sites to pass the parentChannel.attach reference into
the annotations mock.

---

Nitpick comments:
In `@src/commands/channels/annotations/delete.ts`:
- Around line 79-91: The logged and returned payloads omit the count used when
building the annotation; update the calls to include flags.count so both audit
logs and JSON output contain count. Specifically, modify the logCliEvent
invocation (the call to this.logCliEvent) to add count: flags.count in the
metadata object, and modify the this.logJsonResult call to include count:
flags.count in the returned object alongside channel, serial, type, and name;
keep using the existing flags variable and existing function signatures
(logCliEvent, logJsonResult).

In `@src/commands/channels/annotations/index.ts`:
- Around line 4-5: The commandGroup string is singular and leads to an awkward
help heading; update the protected property commandGroup in this class (where
topicName = "channels:annotations") to a plural form such as "Pub/Sub channel
annotations" so BaseTopicCommand will render the heading correctly; locate and
change the protected commandGroup = "Pub/Sub channel annotation" to the
pluralized text in the same file.

In `@test/unit/commands/channels/annotations/delete.test.ts`:
- Around line 106-124: Replace the brittle JSON.parse(stdout) approach in the
"should output JSON when --json flag is used" test with the shared helper
captureJsonLogs() from test/helpers/ndjson.ts: call captureJsonLogs on the
output of runCommand (instead of JSON.parse(stdout)), retrieve the captured JSON
event (e.g., first object) and run the same assertions on its properties (type,
command, success, channel, serial); update the test around the
runCommand/capture point accordingly so it uses captureJsonLogs to parse
NDJSON-style output reliably.

In `@test/unit/commands/channels/annotations/get.test.ts`:
- Around line 101-113: The test is directly parsing stdout which bypasses the
NDJSON helper; replace the JSON.parse(stdout) usage with the
test/helpers/ndjson.ts helper captureJsonLogs() when invoking the command (the
same way other tests do) so you capture NDJSON lines robustly; call runCommand
with captureJsonLogs() to retrieve parsed JSON entries for the
"channels:annotations:get" invocation, then perform the same assertions against
the first parsed object (e.g., result = parsed[0]) instead of parsing stdout.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: a1d34e53-6679-42c0-96e1-3dad7ad379fc

📥 Commits

Reviewing files that changed from the base of the PR and between fe576ee and 97c0915.

📒 Files selected for processing (12)
  • README.md
  • src/commands/channels/annotations/delete.ts
  • src/commands/channels/annotations/get.ts
  • src/commands/channels/annotations/index.ts
  • src/commands/channels/annotations/publish.ts
  • src/commands/channels/annotations/subscribe.ts
  • test/helpers/mock-ably-realtime.ts
  • test/helpers/mock-ably-rest.ts
  • test/unit/commands/channels/annotations/delete.test.ts
  • test/unit/commands/channels/annotations/get.test.ts
  • test/unit/commands/channels/annotations/publish.test.ts
  • test/unit/commands/channels/annotations/subscribe.test.ts

@umair-ably umair-ably changed the base branch from fix/channel-messages-formatting-with-missing-fields to main March 12, 2026 16:52
@umair-ably umair-ably changed the title Dx 789 annotations [DX-789] Add annotations support Mar 12, 2026
@umair-ably umair-ably requested a review from sacOO7 March 12, 2026 17:14
@umair-ably umair-ably marked this pull request as ready for review March 12, 2026 17:14
@umair-ably
Copy link
Contributor Author

LGTM - repo rules prevent me from also approving this @sacOO7

Copy link
Contributor

@sacOO7 sacOO7 left a comment

Choose a reason for hiding this comment

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

LGTM

@umair-ably umair-ably merged commit 639e243 into main Mar 13, 2026
10 checks passed
@umair-ably umair-ably deleted the DX-789-annotations branch March 13, 2026 12:53
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.

3 participants