Skip to content

feat(cli): react command β€” add/remove message reactions - #29

Open
sinjab wants to merge 1 commit into
mingnz:mainfrom
sinjab:feat/react-command
Open

sinjab wants to merge 1 commit into
mingnz:mainfrom
sinjab:feat/react-command

Conversation

@sinjab

@sinjab sinjab commented Oct 11, 2026

Copy link
Copy Markdown

What

Adds teams react <chat> [emoji] β€” react to a message (like default, also heart, laugh, surprised), with --msg <id> to target a specific message and --remove to take your reaction off. --headed shows the drive for debugging.

Why not the API

The chatsvc REST API rejects reaction writes outright β€” PUT .../messages/<id>/properties?name=emotions returns 400 errorCode 201 "Invalid format of emotions property" for every body shape I tried (12, including the exact doc the GET returns, keyed-dict forms, and stringified variants), and the native protocol rides a WebSocket inside a SharedWorker that a CLI cannot join. The web UI is the only scriptable path.

How it works

Reuses the persistent browser profile teams login already maintains:

  1. Launch headless Chromium with that profile, wait for the app.
  2. Open the chat natively from the chat list β€” matched by the row's accessibility name (with an optional Unread message/Chat prefix), iterating candidates (one 1:1 can render as several rows) until the target message's heading is found. Opening through the search page was avoided: the thread renders in an embedded pane that can fail with an "Oops" error.
  3. Locate the message row by its text β€” the role=heading match can resolve to a 1x1 accessibility marker, so the drive prefers the group row's bounding box and falls back to walking the DOM from the deepest visible text node.
  4. Hover the row's top edge β€” the quick bar does not reliably trigger from the row center. An offset sweep with retries handles render timing; any message-actions-* button or the picker entry counts as "bar up".
  5. Click [data-tid="message-actions-<key>"].

The expanded "More reactions" picker is deliberately not wired: its flyout can anchor to an adjacent message row and react to the wrong message (observed live during testing). Scope stays on the four hover-quick-bar reactions.

Safety

  • Server-side verification: after the click, the message is re-fetched and its emotions property is asserted (your MRI present/absent), with retries for propagation lag.
  • Re-reacting with an emoji you already set is refused instead of silently toggling it off (the UI treats a repeat click as remove). Same for --remove when you have no such reaction.
  • Adds getEmotions/hasMyReaction helpers that tolerate both the plain-array and stringified-JSON property shapes the API returns.

Testing

  • 17 new unit tests (108 total pass): reaction-name validation, selector mapping, target-message picking (newest/by id, skips event rows), snippet building (HTML strip, entity decode, truncation), emotions parsing (plain/stringified/malformed/empty-users).
  • Live-verified two full cycles (like add/remove, heart add/remove) against a real tenant with server-side verification.

Also bumps playwright to ^1.63.0 (the version the feature was developed and live-tested against).

The chatsvc REST API rejects reaction writes (the emotions property
setter returns 400 for every body shape) and the native protocol rides
a WebSocket inside a SharedWorker, so reactions are driven through the
logged-in Playwright browser profile (same one  uses):

- opens the chat natively from the chat list (accessibility-name match,
  candidate loop β€” the same 1:1 can render as several rows)
- locates the message row by snippet text (group row or DOM rect; the
  role=heading match can be a 1x1 accessibility marker)
- hovers the row's top edge (the quick bar does not trigger from the
  row center) with an offset sweep + retries
- clicks the reaction on the hover quick bar: like, heart, laugh,
  surprised. The expanded 'More reactions' picker is deliberately not
  wired: its flyout can anchor to an adjacent row and react to the
  wrong message.
- verifies server-side by re-reading the message emotions property;
  re-reacting with an emoji you already set is refused (the UI toggle
  would remove it)

Reacted 2 live cycles end-to-end (like add/remove, heart add/remove)
with server-side verification.
@sinjab

sinjab commented Oct 15, 2026

Copy link
Copy Markdown
Author

Friendly ping β€” happy to rebase this onto current main if anything has drifted. I'm also sending a few follow-up PRs (#30–#32: --json output, HTML send, file attachments) built on this line of work; no rush, just letting you know they're coming.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants