Skip to content

[DX-154] Fix ably spaces command group#160

Merged
sacOO7 merged 10 commits intomainfrom
fix/ably-spaces-command-group-formatting
Mar 19, 2026
Merged

[DX-154] Fix ably spaces command group#160
sacOO7 merged 10 commits intomainfrom
fix/ably-spaces-command-group-formatting

Conversation

@sacOO7
Copy link
Contributor

@sacOO7 sacOO7 commented Mar 10, 2026


CLI Issues & Improvements

1. General / Output Formatting

JSON Structure Issues

  • JSON output is incorrectly structured.

  • Top-level fields should only include:

    • type
    • command
    • success
  • Currently:

    • Additional fields are mixed into the root.
    • Nested contextual/domain fields are missing.

2. Spaces Commands

pnpm cli spaces list

  • Returns occupancy data, which appears incorrect.
  • Requires verification and fixing.

3. Members Commands

cli spaces members subscribe

  • Automatically enters the current member into the channel.

  • Expected behavior:

    • Should support passive (“dumb”) subscription.
    • Must not implicitly enter the member.

cli spaces members enter

  • Issues:

    • Dashboard flow:

      • Enters the member and subscribes to other members (unexpected behavior).
    • Response data:

      • Missing member-specific details.
  • Expected improvements:

    • Include:

      • clientId
      • connectionId

4. Locations Commands

cli spaces locations set dashboard

--location='{"slide": 1}' --client-id=...
  • Issue:

    • Waits for other location updates before exiting.
  • Expected behavior:

    • Should set location and exit immediately (as per documentation).

cli spaces locations subscribe dashboard

  • Automatically enters the current client into presence.

  • Expected:

    • Support passive subscription without entering.

cli spaces locations get-all

  • Issue:

    • Requires entering presence before execution.
  • Expected:

    • Should work as a simple “get-all” command.
    • Must not require presence.

5. Cursors Commands

cli spaces cursors set dashboard

  • Issue:

    • Waits before exiting.
  • Expected:

    • Should terminate immediately after setting cursor.

cli spaces cursors get-all dashboard

Side Effects

  • Automatically enters the member.

  • Expected:

    • Should only fetch cursor data (no side effects).

Output Issues

  • Includes unnecessary messages:

    • Example: “Collecting cursor positions for 5 seconds…”
  • Output format is:

    • Inconsistent with other commands
    • Unclear and not standardized

Expected Output Improvements

  • Follow --pretty-json structure

  • Include:

    {
      "clientId": "...",
      "connectionId": "...",
      "position": { "x": 0, "y": 0 },
      "data": {}
    }
  • Formatting rules:

    • clientId should appear on a new line

    • If time-based or paginated:

      • Include timestamps at the top
      • Include an index

cli spaces cursors subscribe

Behavior Issue

  • Automatically enters the member.

  • Expected:

    • Should allow passive subscription (no implicit enter).

Output Issues

  • JSON output:

    • Not properly indented
  • Non-JSON output:

    • Missing explicit:

      • x
      • y fields in position display

Summary of Key Problems

  • ❌ Implicit presence entry in multiple commands
  • Inconsistent and unclear output formats
  • ❌ Commands blocking/waiting unnecessarily
  • ❌ Missing standardized JSON structure
  • ❌ Unexpected side effects in read operations

Summary by CodeRabbit

  • New Features

    • Introduced multi-line labeled blocks format for human-readable output with improved clarity and consistency.
  • Bug Fixes

    • Standardized JSON output structure across Spaces commands with domain-key nesting convention.
    • Simplified command flows to reduce unnecessary initial data fetching and live-update subscriptions.
  • Documentation

    • Expanded guidance on output formatting conventions and data structure requirements for Spaces commands.
    • Updated skill documentation with new JSON envelope semantics and non-JSON formatting standards.
  • Refactor

    • Streamlined Spaces command implementations (cursors, locations, locks, members) with cleaner output paths.
    • Migrated from custom type interfaces to direct Ably SDK types for improved maintainability.

@vercel
Copy link

vercel bot commented Mar 10, 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 19, 2026 11:30am

Request Review

@coderabbitai
Copy link

coderabbitai bot commented Mar 10, 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: 6ce8086a-4286-48e8-9204-1cd2033493bb

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 refactors Ably Spaces commands to use domain-keyed envelope nesting for JSON and structured multi-line formatting for human-readable output. It introduces a new output utilities module, simplifies multiple command flows by removing complex subscription/monitoring logic, and updates documentation and test expectations to align with these new data-structure conventions.

Changes

Cohort / File(s) Summary
Documentation & Guidance
.claude/skills/ably-codebase-review/SKILL.md, .claude/skills/ably-new-command/SKILL.md, .claude/skills/ably-review/SKILL.md, .claude/skills/ably-new-command/references/patterns.md, AGENTS.md
Updated guidance documents to enforce domain-key nesting for JSON outputs, standardized envelope structure (type, command, success), multi-line labeled-block formatting for non-JSON, and clarified metadata handling and SDK type imports across multiple agent skill files.
Output Infrastructure
src/utils/spaces-output.ts, src/spaces-base-command.ts
Introduced new formatting utility module with JSON and human-readable block formatters for space objects (members, cursors, locks, locations); added state tracking (hasEnteredSpace) to base command to conditionally perform cleanup only when space was entered.
Cursor Commands
src/commands/spaces/cursors/get-all.ts, src/commands/spaces/cursors/set.ts, src/commands/spaces/cursors/subscribe.ts
Simplified cursor-fetching logic by removing live-update subscriptions and space-entry waits; replaced with direct getAll calls; updated output to use new formatCursorBlock/formatCursorOutput formatters; adjusted JSON structure to nest cursors under domain key.
Location Commands
src/commands/spaces/locations/get-all.ts, src/commands/spaces/locations/set.ts, src/commands/spaces/locations/subscribe.ts
Removed complex location retrieval and formatting logic; simplified initialization to use enterSpace: false; replaced detailed metrics output with compact LocationEntry structures; removed duration-based lifecycle and E2E-specific optimization paths; updated to use new location formatters.
Lock Commands
src/commands/spaces/locks/acquire.ts, src/commands/spaces/locks/get-all.ts, src/commands/spaces/locks/get.ts, src/commands/spaces/locks/subscribe.ts
Replaced custom lock detail structures with standard Lock type; removed complex space-connection sequences; simplified output to use formatLockBlock/formatLockOutput; adjusted JSON to nest locks under domain key; removed legacy per-lock detail rendering.
Member Commands
src/commands/spaces/members/enter.ts, src/commands/spaces/members/subscribe.ts
Removed live presence subscriptions and per-member event handling; simplified enter command to direct member entry and output via formatMemberOutput; added duplicate-event suppression and simplified event logging in subscribe; removed extensive per-member update tracking.
Space List Command
src/commands/spaces/list.ts
Removed SpaceMetrics/SpaceStatus interfaces and occupancy metric tracking; simplified JSON output to include only spaceName and channelId; removed detailed formatting helpers.
Test Updates - Unit
test/unit/commands/spaces/cursors/*.test.ts, test/unit/commands/spaces/locations/*.test.ts, test/unit/commands/spaces/locks/*.test.ts, test/unit/commands/spaces/members/*.test.ts, test/unit/commands/spaces/list.test.ts
Updated mock return values from arrays to objects/maps; changed test assertions to reflect new domain-keyed JSON structures, removed expectations for space.enter calls where initialization changed; updated stdout checks to validate new formatter outputs.
Test Updates - E2E & Mocks
test/e2e/spaces/spaces-e2e.test.ts, test/helpers/mock-ably-spaces.ts
Updated E2E test signal checks to match new subscription messages; expanded mock data structures with richer member objects including lastEvent and location fields; changed mock return types from arrays to objects for consistency.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Poem

🐰 The spaces now nest so neatly in their domains,
With envelopes sealed and formatters refined—
No more flat sprawl, just structured refrains,
Each cursor and lock and member aligned! 🎯
The rabbit rejoices at simpler command flows,
And tests that dance with the new patterns that grow. ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'DX-154 Fix ably spaces command group' clearly identifies the issue being addressed and summarizes the main fix related to the spaces command group formatting and behavior.
Docstring Coverage ✅ Passed Docstring coverage is 83.33% which is sufficient. The required threshold is 80.00%.
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
  • Commit unit tests in branch fix/ably-spaces-command-group-formatting
📝 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.

@sacOO7 sacOO7 changed the title Fix ably spaces command group [DX-154] Fix ably spaces command group Mar 10, 2026
@sacOO7 sacOO7 force-pushed the fix/ably-spaces-command-group-formatting branch from 95a65b9 to cd25291 Compare March 13, 2026 09:33
@sacOO7 sacOO7 force-pushed the fix/ably-spaces-command-group-formatting branch from cd25291 to 6a66293 Compare March 16, 2026 09:53
@sacOO7 sacOO7 force-pushed the fix/ably-spaces-command-group-formatting branch from 2551916 to 362e694 Compare March 16, 2026 14:13
@sacOO7 sacOO7 force-pushed the fix/ably-spaces-command-group-formatting branch from 362e694 to e56ad94 Compare March 16, 2026 15:10
@sacOO7 sacOO7 force-pushed the fix/ably-spaces-command-group-formatting branch from e56ad94 to ae2214f Compare March 16, 2026 15:20
@sacOO7 sacOO7 force-pushed the fix/ably-spaces-command-group-formatting branch from ae2214f to 03f1ab8 Compare March 16, 2026 15:36
@sacOO7 sacOO7 marked this pull request as ready for review March 17, 2026 08:42
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

🧹 Nitpick comments (8)
.claude/skills/ably-new-command/references/patterns.md (1)

403-412: Consider adding language identifiers to example output blocks.

The static analysis tool flags several fenced code blocks without language specifiers (lines 403, 434, 470, 500). While these are example outputs rather than code, adding text or plaintext as the language identifier would satisfy markdownlint and maintain consistency.

✏️ Suggested fix
-```
+```text
 [2024-01-15T10:30:00.000Z]
 ID: msg-123

Apply similar changes to the other flagged blocks at lines 434, 470, and 500.

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

In @.claude/skills/ably-new-command/references/patterns.md around lines 403 -
412, Add a language identifier (e.g., "text" or "plaintext") to the fenced
example output blocks so markdownlint stops flagging them; specifically update
the triple-backtick fences around the sample output that begins with
"[2024-01-15T10:30:00.000Z] ID: msg-123 ..." and apply the same change to the
other similar examples in the file (the blocks starting with the outputs flagged
by the reviewer). Ensure you only modify the opening fence to ```text (or
```plaintext) and leave the block contents unchanged.
src/commands/spaces/cursors/set.ts (1)

171-188: Consider using singular cursor key for single-item result.

Per the JSON Data Nesting Convention in patterns.md (lines 623-630), single-item results from create/get/set operations should use a singular domain key (e.g., { cursor: {...} }), while collections should use plural keys (e.g., { cursors: [...] }).

The current implementation returns { cursors: [{ ... }] } which is technically a collection with one item, but semantically this is setting a single cursor.

♻️ Suggested change for consistency
       if (this.shouldOutputJson(flags)) {
         this.logJsonResult(
           {
-            cursors: [
-              {
-                clientId: this.realtimeClient!.auth.clientId,
-                connectionId: this.realtimeClient!.connection.id,
-                position: (
-                  cursorForOutput as { position: { x: number; y: number } }
-                ).position,
-                data:
-                  (cursorForOutput as { data?: Record<string, unknown> })
-                    .data ?? null,
-              },
-            ],
+            cursor: {
+              clientId: this.realtimeClient!.auth.clientId,
+              connectionId: this.realtimeClient!.connection.id,
+              position: (
+                cursorForOutput as { position: { x: number; y: number } }
+              ).position,
+              data:
+                (cursorForOutput as { data?: Record<string, unknown> })
+                  .data ?? null,
+            },
           },
           flags,
         );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/commands/spaces/cursors/set.ts` around lines 171 - 188, The JSON output
currently wraps the single set result in a plural array under cursors; change it
to emit a singular cursor object when only one item is returned: inside the
branch that calls this.logJsonResult (the block using
this.shouldOutputJson(flags)), construct and pass { cursor: { clientId:
this.realtimeClient!.auth.clientId, connectionId:
this.realtimeClient!.connection.id, position: (cursorForOutput as { position: {
x: number; y: number } }).position, data: (cursorForOutput as { data?:
Record<string, unknown> }).data ?? null } } instead of { cursors: [ ... ] };
keep the rest of the call (flags) unchanged and only adjust the top-level
key/name used for single-item responses.
src/commands/spaces/locations/get-all.ts (1)

79-82: Use this.logToStderr() for warning output.

Similar to the cursors/get-all command, warning messages should be written to stderr.

Suggested fix
        } else if (entries.length === 0) {
-          this.log(
-            formatWarning("No locations are currently set in this space."),
-          );
+          this.logToStderr(
+            formatWarning("No locations are currently set in this space."),
+          );
         } else {

Based on learnings: "Always use this.logToStderr(formatWarning('...')) for warning messages."

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

In `@src/commands/spaces/locations/get-all.ts` around lines 79 - 82, Replace the
warning that currently uses this.log(formatWarning(...)) with
this.logToStderr(formatWarning(...)) in the branch that handles entries.length
=== 0; locate the else-if that checks entries.length === 0 (where
formatWarning("No locations are currently set in this space.") is used) and call
this.logToStderr with the same formatWarning output instead of this.log.
src/commands/spaces/cursors/get-all.ts (1)

71-72: Use this.logToStderr() for warning output.

Per project conventions, warning messages should be written to stderr rather than stdout to avoid polluting normal output.

Suggested fix
       } else if (cursors.length === 0) {
-        this.log(formatWarning("No active cursors found in space."));
+        this.logToStderr(formatWarning("No active cursors found in space."));
       } else {

Based on learnings: "In the ably/ably-cli TypeScript (oclif) codebase, do not use this.warn() directly in command implementations. Always use this.logToStderr(formatWarning('...')) for warning messages."

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

In `@src/commands/spaces/cursors/get-all.ts` around lines 71 - 72, The warning is
currently being logged to stdout using this.log(formatWarning(...)) in the
get-all command; change it to write to stderr by replacing that call with
this.logToStderr(formatWarning("No active cursors found in space.")) so it
follows the project's convention for warnings in the get-all command
implementation (file: src/commands/spaces/cursors/get-all.ts, locate the branch
where cursors.length === 0 and update the this.log call).
test/unit/commands/spaces/locations/get-all.test.ts (1)

64-69: Assert the serialized coordinates here, not just the key.

toHaveProperty("location") still passes for null or the wrong payload, so this won’t catch regressions in the new map-to-array formatter.

🔎 Tighten the assertion
       expect(resultRecord!.locations[0]).toHaveProperty(
         "connectionId",
         "conn-1",
       );
-      expect(resultRecord!.locations[0]).toHaveProperty("location");
+      expect(resultRecord!.locations[0]).toHaveProperty("location", {
+        x: 100,
+        y: 200,
+      });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/unit/commands/spaces/locations/get-all.test.ts` around lines 64 - 69,
The test currently only asserts that resultRecord.locations[0] has a "location"
property which allows null/wrong values to slip through; update the assertion to
validate the serialized coordinates payload itself (for example assert
resultRecord!.locations[0].location equals the expected coordinates object/array
produced by the map-to-array formatter). Locate the assertion in the
get-all.test.ts where resultRecord!.locations[0] is checked and replace the
toHaveProperty("location") check with a strict equality/assertion against the
expected serialized coordinates (matching the output shape produced by the
map-to-array formatter).
test/unit/commands/spaces/locations/subscribe.test.ts (1)

59-73: Avoid wall-clock sleeps in these unit tests.

These 50ms waits make the assertions timing-dependent. On a busy runner the handler can still be unset, and on a fast exit the update can be emitted after runCommand() has already finished. It’s more deterministic to fire the update from the subscribe mock itself or coordinate with fake timers/a controllable promise.

Also applies to: 100-112

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

In `@test/unit/commands/spaces/locations/subscribe.test.ts` around lines 59 - 73,
The test uses a 50ms setTimeout to wait for the subscribe handler to be
registered (locationHandler) which makes the test flaky; change the
space.locations.subscribe mockImplementation to synchronously emit the mock
update (i.e., call handler(mockUpdate) inside the mock) or return a controllable
promise that resolves when the handler is set and awaited by the test so
runCommand([... "spaces:locations:subscribe" ...]) sees the update
deterministically; update the logic around the locationHandler variable and the
subscribe mock so the test does not rely on wall-clock sleeps.
test/unit/commands/spaces/locks/subscribe.test.ts (2)

124-126: Use captureJsonLogs() instead of parseNdjsonLines() in this unit test

For mocked unit tests, prefer the repo-standard JSON capture helper to keep assertions consistent with other unit suites and reduce parser-coupling drift.

Suggested update
-import { parseNdjsonLines } from "../../../../helpers/ndjson.js";
+import { captureJsonLogs } from "../../../../helpers/ndjson.js";
...
-      const records = parseNdjsonLines(stdout);
+      const records = captureJsonLogs(stdout);

Based on learnings: In unit tests for this repo, JSON output assertions should use captureJsonLogs() from test/helpers/ndjson.ts rather than manual/parsing-oriented approaches.

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

In `@test/unit/commands/spaces/locks/subscribe.test.ts` around lines 124 - 126,
Replace the manual NDJSON parsing with the repo-standard JSON capture helper:
remove the use of parseNdjsonLines(stdout) in this test and instead call
captureJsonLogs() (imported from test/helpers/ndjson.ts) to obtain the logged
records (await if it returns a promise), then locate the event record the same
way (const eventRecord = records.find((r) => r.type === "event" && r.lock));
also update imports to remove parseNdjsonLines and add captureJsonLogs so
assertions remain consistent with other unit tests.

158-165: Tighten the rejection-path assertion

Consider asserting the error message (or command component) in addition to error being defined; this avoids passing on unrelated failures.

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

In `@test/unit/commands/spaces/locks/subscribe.test.ts` around lines 158 - 165,
The test currently only checks that an error was defined after calling
runCommand for the "spaces:locks:subscribe" command, which can mask unrelated
failures; update the assertion to inspect the error message or command-specific
component returned by runCommand (e.g., check error.message or error.output) to
ensure it contains a distinguishing token such as "spaces:locks:subscribe" or
the space name "test-space". Locate the failing test using runCommand in this
file and replace or add the loose expect(error).toBeDefined() with a tighter
check like expect(error.message).toContain('spaces:locks:subscribe') or
expect(error.message).toContain('test-space') so the test only passes for the
intended rejection path.
🤖 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/spaces/locks/acquire.ts`:
- Around line 124-130: The JSON output uses an array under "locks" for a
single-item result; change the payload in the acquire command (the block using
this.shouldOutputJson, this.logJsonResult, and formatLockOutput) to return a
singular domain key "lock" with the single formatted lock (e.g., { lock:
formatLockOutput(lock) }) instead of { locks: [...] }; also scan and align the
other single-item commands mentioned (members/enter, cursors/set, locks/get) to
the same singular-key convention to stay consistent with AGENTS.md.

---

Nitpick comments:
In @.claude/skills/ably-new-command/references/patterns.md:
- Around line 403-412: Add a language identifier (e.g., "text" or "plaintext")
to the fenced example output blocks so markdownlint stops flagging them;
specifically update the triple-backtick fences around the sample output that
begins with "[2024-01-15T10:30:00.000Z] ID: msg-123 ..." and apply the same
change to the other similar examples in the file (the blocks starting with the
outputs flagged by the reviewer). Ensure you only modify the opening fence to
```text (or ```plaintext) and leave the block contents unchanged.

In `@src/commands/spaces/cursors/get-all.ts`:
- Around line 71-72: The warning is currently being logged to stdout using
this.log(formatWarning(...)) in the get-all command; change it to write to
stderr by replacing that call with this.logToStderr(formatWarning("No active
cursors found in space.")) so it follows the project's convention for warnings
in the get-all command implementation (file:
src/commands/spaces/cursors/get-all.ts, locate the branch where cursors.length
=== 0 and update the this.log call).

In `@src/commands/spaces/cursors/set.ts`:
- Around line 171-188: The JSON output currently wraps the single set result in
a plural array under cursors; change it to emit a singular cursor object when
only one item is returned: inside the branch that calls this.logJsonResult (the
block using this.shouldOutputJson(flags)), construct and pass { cursor: {
clientId: this.realtimeClient!.auth.clientId, connectionId:
this.realtimeClient!.connection.id, position: (cursorForOutput as { position: {
x: number; y: number } }).position, data: (cursorForOutput as { data?:
Record<string, unknown> }).data ?? null } } instead of { cursors: [ ... ] };
keep the rest of the call (flags) unchanged and only adjust the top-level
key/name used for single-item responses.

In `@src/commands/spaces/locations/get-all.ts`:
- Around line 79-82: Replace the warning that currently uses
this.log(formatWarning(...)) with this.logToStderr(formatWarning(...)) in the
branch that handles entries.length === 0; locate the else-if that checks
entries.length === 0 (where formatWarning("No locations are currently set in
this space.") is used) and call this.logToStderr with the same formatWarning
output instead of this.log.

In `@test/unit/commands/spaces/locations/get-all.test.ts`:
- Around line 64-69: The test currently only asserts that
resultRecord.locations[0] has a "location" property which allows null/wrong
values to slip through; update the assertion to validate the serialized
coordinates payload itself (for example assert
resultRecord!.locations[0].location equals the expected coordinates object/array
produced by the map-to-array formatter). Locate the assertion in the
get-all.test.ts where resultRecord!.locations[0] is checked and replace the
toHaveProperty("location") check with a strict equality/assertion against the
expected serialized coordinates (matching the output shape produced by the
map-to-array formatter).

In `@test/unit/commands/spaces/locations/subscribe.test.ts`:
- Around line 59-73: The test uses a 50ms setTimeout to wait for the subscribe
handler to be registered (locationHandler) which makes the test flaky; change
the space.locations.subscribe mockImplementation to synchronously emit the mock
update (i.e., call handler(mockUpdate) inside the mock) or return a controllable
promise that resolves when the handler is set and awaited by the test so
runCommand([... "spaces:locations:subscribe" ...]) sees the update
deterministically; update the logic around the locationHandler variable and the
subscribe mock so the test does not rely on wall-clock sleeps.

In `@test/unit/commands/spaces/locks/subscribe.test.ts`:
- Around line 124-126: Replace the manual NDJSON parsing with the repo-standard
JSON capture helper: remove the use of parseNdjsonLines(stdout) in this test and
instead call captureJsonLogs() (imported from test/helpers/ndjson.ts) to obtain
the logged records (await if it returns a promise), then locate the event record
the same way (const eventRecord = records.find((r) => r.type === "event" &&
r.lock)); also update imports to remove parseNdjsonLines and add captureJsonLogs
so assertions remain consistent with other unit tests.
- Around line 158-165: The test currently only checks that an error was defined
after calling runCommand for the "spaces:locks:subscribe" command, which can
mask unrelated failures; update the assertion to inspect the error message or
command-specific component returned by runCommand (e.g., check error.message or
error.output) to ensure it contains a distinguishing token such as
"spaces:locks:subscribe" or the space name "test-space". Locate the failing test
using runCommand in this file and replace or add the loose
expect(error).toBeDefined() with a tighter check like
expect(error.message).toContain('spaces:locks:subscribe') or
expect(error.message).toContain('test-space') so the test only passes for the
intended rejection path.
🪄 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: 262c13ff-538d-44de-91a0-c868d4aa7448

📥 Commits

Reviewing files that changed from the base of the PR and between ad9f0a5 and 563d187.

📒 Files selected for processing (35)
  • .claude/skills/ably-codebase-review/SKILL.md
  • .claude/skills/ably-new-command/SKILL.md
  • .claude/skills/ably-new-command/references/patterns.md
  • .claude/skills/ably-review/SKILL.md
  • AGENTS.md
  • src/commands/spaces/cursors/get-all.ts
  • src/commands/spaces/cursors/set.ts
  • src/commands/spaces/cursors/subscribe.ts
  • src/commands/spaces/list.ts
  • src/commands/spaces/locations/get-all.ts
  • src/commands/spaces/locations/set.ts
  • src/commands/spaces/locations/subscribe.ts
  • src/commands/spaces/locks/acquire.ts
  • src/commands/spaces/locks/get-all.ts
  • src/commands/spaces/locks/get.ts
  • src/commands/spaces/locks/subscribe.ts
  • src/commands/spaces/members/enter.ts
  • src/commands/spaces/members/subscribe.ts
  • src/spaces-base-command.ts
  • src/utils/spaces-output.ts
  • test/e2e/spaces/spaces-e2e.test.ts
  • test/helpers/mock-ably-spaces.ts
  • test/unit/commands/spaces/cursors/get-all.test.ts
  • test/unit/commands/spaces/cursors/set.test.ts
  • test/unit/commands/spaces/cursors/subscribe.test.ts
  • test/unit/commands/spaces/list.test.ts
  • test/unit/commands/spaces/locations/get-all.test.ts
  • test/unit/commands/spaces/locations/set.test.ts
  • test/unit/commands/spaces/locations/subscribe.test.ts
  • test/unit/commands/spaces/locks/acquire.test.ts
  • test/unit/commands/spaces/locks/get-all.test.ts
  • test/unit/commands/spaces/locks/get.test.ts
  • test/unit/commands/spaces/locks/subscribe.test.ts
  • test/unit/commands/spaces/members/enter.test.ts
  • test/unit/commands/spaces/members/subscribe.test.ts
💤 Files with no reviewable changes (1)
  • test/unit/commands/spaces/list.test.ts

@sacOO7
Copy link
Contributor Author

sacOO7 commented Mar 18, 2026

Will fix the conflict asap.

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

This PR fixes and standardizes the Ably Spaces command group behavior and output, aligning commands with the documented “passive subscribe / one-shot get / one-shot set / hold enter-acquire” semantics and introducing shared structured output helpers.

Changes:

  • Introduces src/utils/spaces-output.ts for consistent JSON shaping and multi-line labeled block output across Spaces commands.
  • Refactors multiple Spaces commands to avoid unintended side effects (e.g., subscribe/get commands no longer space.enter() or fetch initial snapshots) and updates cleanup tracking via markAsEntered().
  • Updates unit/e2e tests and documentation to reflect new argument naming (SPACE_NAME) and output envelopes.

Reviewed changes

Copilot reviewed 37 out of 37 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
test/unit/commands/spaces/spaces.test.ts Updates help/usage assertions for SPACE_NAME.
test/unit/commands/spaces/members/subscribe.test.ts Reworks expectations for event-only subscribe output (block + JSON event envelope).
test/unit/commands/spaces/members/enter.test.ts Updates JSON expectations to return a member object.
test/unit/commands/spaces/locks/subscribe.test.ts Aligns tests with passive subscribe behavior and event envelopes.
test/unit/commands/spaces/locks/get.test.ts Aligns output shape to { lock: ... } and removes enter side effects.
test/unit/commands/spaces/locks/get-all.test.ts Updates lock fixtures/expectations and removes enter side effects.
test/unit/commands/spaces/locks/acquire.test.ts Updates lock shape and JSON expectations to { lock: { id, ... } }.
test/unit/commands/spaces/locations/subscribe.test.ts Updates subscribe tests to event-only output (no initial getAll snapshot).
test/unit/commands/spaces/locations/set.test.ts Removes --duration 0 path expectations and updates JSON structure.
test/unit/commands/spaces/locations/get-all.test.ts Updates to locations map return type and revised JSON envelope.
test/unit/commands/spaces/list.test.ts Refactors tests into standard suites + reorganizes functionality vs flag tests.
test/unit/commands/spaces/cursors/subscribe.test.ts Updates to passive subscribe semantics and event envelope shape.
test/unit/commands/spaces/cursors/set.test.ts Updates human-readable output expectations (Position X/Y) and JSON cursor envelope.
test/unit/commands/spaces/cursors/get-all.test.ts Updates to map-based getAll return, JSON envelope, and cleanup expectations.
test/helpers/mock-ably-spaces.ts Updates mocks to reflect new SDK-shaped returns (e.g., cursors/locations maps, member fields).
test/e2e/spaces/spaces-e2e.test.ts Updates readiness signals/output checks; removes --duration 0 usage for locations set.
src/utils/spaces-output.ts New shared formatters for Spaces JSON output + multi-line labeled blocks.
src/spaces-base-command.ts Adds hasEnteredSpace tracking + gates leave()/member-cleanup waiting behind actual entry.
src/commands/spaces/members/subscribe.ts Makes subscribe passive + switches to block output and domain-key JSON events.
src/commands/spaces/members/enter.ts Refactors enter output + JSON returns member object; removes extra subscriptions.
src/commands/spaces/locks/subscribe.ts Makes subscribe passive + switches to block output and domain-key JSON events.
src/commands/spaces/locks/get.ts Makes get one-shot without entering; outputs lock domain-key JSON + block output.
src/commands/spaces/locks/get-all.ts Makes get-all one-shot; outputs list + block formatting.
src/commands/spaces/locks/acquire.ts Uses shared lock formatting/output and marks entry for cleanup.
src/commands/spaces/locations/subscribe.ts Makes subscribe passive + emits block + JSON event envelope.
src/commands/spaces/locations/set.ts Makes set one-shot (no subscribe/watch); clears location in finally before base cleanup.
src/commands/spaces/locations/get-all.ts Simplifies get-all to map→entries conversion + block output.
src/commands/spaces/list.ts Simplifies list output/JSON payload (removes occupancy metrics + extra metadata).
src/commands/spaces/cursors/subscribe.ts Makes subscribe passive + switches to block output and domain-key JSON events.
src/commands/spaces/cursors/set.ts Removes custom cursor interfaces; improves output and keeps simulate mode behavior.
src/commands/spaces/cursors/get-all.ts Simplifies to map-based getAll + consistent block + JSON output.
README.md Updates generated CLI docs (but currently contains formatting + placeholder drift issues).
AGENTS.md Documents new structured output + JSON nesting and Spaces command semantics.
.claude/skills/ably-review/SKILL.md Extends review checklist for structured output + JSON nesting + side-effect rules.
.claude/skills/ably-new-command/references/patterns.md Updates guidance/examples for block output + JSON domain-key nesting.
.claude/skills/ably-new-command/SKILL.md Updates skill checklist for block output + JSON nesting.
.claude/skills/ably-codebase-review/SKILL.md Adds repo-wide review steps for block formatting + JSON nesting compliance.

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

@umair-ably
Copy link
Contributor

Addressing all CodeRabbit nitpick and actionable comments from the review:

Actionable (1):

  • src/commands/spaces/locks/acquire.ts singular lock: key — Already addressed in a prior commit. Code already uses { lock: formatLockOutput(lock) }.

Nitpicks (8):

  1. patterns.md code block lang identifiers — Skipped. Cosmetic only, these are skill reference files not rendered docs.
  2. src/commands/spaces/cursors/set.ts singular cursor: key — Already addressed in a prior commit. Code already uses { cursor: {...} }.
  3. src/commands/spaces/locations/get-all.ts warning to stderr — Fixed. Changed this.log(formatWarning(...))this.logToStderr(formatWarning(...)).
  4. src/commands/spaces/cursors/get-all.ts warning to stderr — Fixed. Same change.
  5. test/unit/commands/spaces/locations/get-all.test.ts tighten assertion — Fixed. Now asserts toHaveProperty("location", { x: 100, y: 200 }).
  6. test/unit/commands/spaces/locations/subscribe.test.ts avoid wall-clock sleeps — Skipped. The 50ms setTimeout pattern is used consistently across all subscribe tests. Refactoring it is risky and the tests pass reliably.
  7. test/unit/commands/spaces/locks/subscribe.test.ts use captureJsonLogs() — Skipped. captureJsonLogs() wraps a function with a console.log spy — it's not interchangeable with parseNdjsonLines() which parses stdout from runCommand(). The current usage is correct.
  8. test/unit/commands/spaces/locks/subscribe.test.ts tighten error assertion — Fixed. Added expect(error?.message).toContain("Failed to subscribe").

Additionally fixed (not flagged by CodeRabbit but flagged by Copilot):

  • src/commands/spaces/locks/get-all.ts warning to stderr — Fixed.
  • src/commands/spaces/members/subscribe.tsFixed. Using member.lastEvent.timestamp instead of wall-clock.
  • src/commands/spaces/locks/subscribe.tsFixed. Using lock.timestamp instead of wall-clock.
  • src/utils/spaces-output.ts formatMemberEventBlockFixed. Added "Last Event" line for field parity with JSON output.

@umair-ably
Copy link
Contributor

Addressing all Copilot review comments:

All 6 inline comments have been replied to and resolved:

  1. members/subscribe.ts:129 timestamp — Fixed. Using member.lastEvent.timestamp instead of wall-clock.
  2. members/subscribe.ts:124 JSON missing action — No change needed. formatMemberOutput already includes lastEvent: { name, timestamp } where name is the action.
  3. locks/subscribe.ts:70 timestamp — Fixed. Using lock.timestamp instead of wall-clock.
  4. spaces-output.ts:145 missing lastEvent — Fixed. Added "Last Event" line to formatMemberEventBlock.
  5. README.md:1417 USAGE line split — Auto-generated by oclif readme, not manually controlled.
  6. README.md:4638 SPACE vs SPACE_NAME — Auto-generated, already regenerated with correct SPACE_NAME.

} from "../../utils/pagination.js";
import { SpacesBaseCommand } from "../../spaces-base-command.js";

interface SpaceMetrics {
Copy link
Contributor

Choose a reason for hiding this comment

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

i partially understand removing this from the list command but it doesn't seem like we provide a way for a user to observe the metrics on a spaces channel now? I had a skim through your follow up PR and couldn't see anything here either.

A user can of course use ably channels occupancy get "my-space::$space" but:

  1. This isn't in the spaces command group
  2. A user would need to know/remember the $space namespace

WDYT about adding a ably spaces occupancy get "my-space"? (and ably spaces occupancy subscribe "my-space"). Basically mirrors ably channels occupancy but tailored for spaces.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Okay, maybe would make sense to add it 👍

Copy link
Contributor Author

@sacOO7 sacOO7 Mar 19, 2026

Choose a reason for hiding this comment

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

Added spaces occupancy get and spaces occupancy subscribe -> 39199d4

@sacOO7 sacOO7 force-pushed the fix/ably-spaces-command-group-formatting branch from 58782e9 to 0dc59cc Compare March 19, 2026 08:48
@sacOO7 sacOO7 force-pushed the fix/ably-spaces-command-group-formatting branch from 5512389 to 326550a Compare March 19, 2026 09:12
@sacOO7 sacOO7 requested a review from umair-ably March 19, 2026 09:16
@umair-ably
Copy link
Contributor

Feedback shared via slack:

  1. Occupancy commands don't nest JSON data under a domain key like other commands do
  2. Members subscribe lost the ability to fetch initial state. This will be addressed in a follow up PR
  3. Cleanup code accesses Spaces SDK internals via unsafe cast to get the channel property
  4. Forgetting markAsEntered() silently breaks cleanup and is easy to miss in future commands
  5. Locks acquire manually enters the space instead of using initializeSpace({ enterSpace: true })
  6. JSON status type keeps growing as a string union and should probably be an enum

@sacOO7 sacOO7 merged commit 5692f63 into main Mar 19, 2026
10 checks passed
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