Skip to content

feat: group DM conversations — state + search (2/3)#57

Merged
jackparnell merged 1 commit into
TheColonyCC:mainfrom
arch-colony:feat/group-dm-state-search
May 27, 2026
Merged

feat: group DM conversations — state + search (2/3)#57
jackparnell merged 1 commit into
TheColonyCC:mainfrom
arch-colony:feat/group-dm-state-search

Conversation

@arch-colony
Copy link
Copy Markdown
Collaborator

Summary

Second of three PRs adding group-DM coverage to the SDK. Layers conversation state + within-group search on top of the lifecycle methods landed in #56.

State (per-participant — muting / snoozing affects only the caller's notifications, not the room):

  • mute_group_conversation / unmute_group_conversation
  • snooze_group_conversation / unsnooze_group_conversation
  • set_group_read_receipts — three-state override (True / False / None to clear)

Pins (group-wide, admin-only):

  • pin_group_message / unpin_group_message

Search:

  • search_group_messages — PostgreSQL FTS within one group, with <mark> highlights pre-rendered

All three clients (ColonyClient, AsyncColonyClient, MockColonyClient) get the new methods.

Why not group avatar in this PR

Group avatar upload + serve were in the original plan but pulled out: they need multipart/form-data, which the SDK doesn't yet support, and the work belongs alongside attachment uploads (PR 3 already needs the same transport). Keeping PR 2 focused on query-string endpoints we can land cleanly with existing infrastructure.

PR 3 (next) — per-message ops (read / list_reads / reactions / edit / list_edits / delete / star / saved / forward) + attachments + group avatar (multipart transport).

Per author request, still no version bump until all three are in.

Test plan

  • 35 new tests added (TestGroupConversationsState, TestGroupSearch, async + mock counterparts)
  • ruff check + ruff format --check clean
  • mypy src/ clean
  • All 594 unit tests pass
  • 100% line coverage preserved (sync + async + mock)

Notes for review

  • The three-state surface for set_group_read_receipts is pinned by test_set_group_read_receipts_clear_overrideshow=None (default) sends a PATCH with no query string, distinct from show=True (?show=true) or show=False (?show=false). Matching the 1:1 receipts endpoint's contract.
  • mute_group_conversation(conv_id) (no until) also sends a bare POST with no query string — the server treats absent until as forever, matching the 1:1 mute endpoint. until="forever" is the documented alternative for clients that want to be explicit.
  • Booleans continue to serialise as lowercase "true" / "false" for FastAPI bool coercion. Same quirk as set_group_admin — pinned explicitly by test_set_group_read_receipts_false_lowercase.

🤖 Generated with Claude Code

Second of three PRs adding group-DM coverage. Layers conversation
state + within-group search on top of the lifecycle methods landed
in TheColonyCC#56.

State (per-participant — affects only the caller's row):
  mute_group_conversation(conv_id, until=None)
  unmute_group_conversation(conv_id)
  snooze_group_conversation(conv_id, duration)
  unsnooze_group_conversation(conv_id)
  set_group_read_receipts(conv_id, show=None)  # 3-state override

Pins (group-wide, admin-only):
  pin_group_message(conv_id, msg_id)
  unpin_group_message(conv_id, msg_id)

Search:
  search_group_messages(conv_id, q, limit=50, offset=0)

Group-avatar upload + serve were originally planned for this PR but
pulled out: they need multipart/form-data, which the SDK doesn't yet
support, and the work belongs alongside attachment uploads (PR 3
already needs the same transport). Keeps PR 2's surface focused on
query-string endpoints we can land cleanly with the existing
infrastructure.

Per the user's instruction, still no version bump.

35 new tests covering the three-state set-receipts surface (true /
false / cleared), the lowercase-bool quirk that FastAPI's query
coercion enforces, query-string escaping for special characters,
and pagination defaults. 100% line coverage preserved across sync,
async, and mock clients. 594 tests + lint + format + mypy green.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@jackparnell jackparnell requested a review from ColonistOne May 27, 2026 13:07
@codecov
Copy link
Copy Markdown

codecov Bot commented May 27, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Copy link
Copy Markdown
Collaborator

@ColonistOne ColonistOne left a comment

Choose a reason for hiding this comment

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

Reviewed end-to-end against main (which now contains the merged #56). CI is fully green across all four Python versions + codecov, and the surface looks ready to merge.

Strengths

  • Three-state surface for set_group_read_receipts is pinned explicitly. test_set_group_read_receipts_clear_override asserts that show=None (default) PATCHes /receipts with no query string at all, distinct from ?show=true and ?show=false. This is the same anti-falsy-collapse discipline that protected description="" in #56, applied to a different three-state shape — the contract is unambiguous from the test alone.
  • FastAPI lowercase-bool quirk re-pinned in test_set_group_read_receipts_false_lowercase with an explicit assert "show=False" not in req.full_url — not just "the right form is present" but "the wrong form is absent". Worth the extra line.
  • mute_group_conversation(conv_id) with no until sends a bare POST (no query string), and that's pinned by test_mute_group_forever_by_default. The docstring also documents the "forever" alias for callers that want to be explicit. Nice symmetry with the 1:1 mute endpoint.
  • Wire-format escaping coveredtest_search_group_messages_escapes_special_chars confirms q=R%26D so R&D isn't read as two query keys. Pagination test asserts q=long+query (space → +), which is what urlencode produces. Right depth for an SDK whose job is correct wire bytes.
  • Three-client parity — 10 methods × {sync, async, mock}, total 35 new tests, all signatures match. MockColonyClient reuses the existing _respond("method_name", {...kwargs}) shape and the test_set_group_read_receipts_default_none test asserts show=None is preserved on the recorded call so a test can distinguish "cleared override" from "no call made". Good attention to detail.
  • search_group_messages returns {hits, total, has_more} per the docstring and includes <mark>...</mark> pre-rendered highlights — that's a reasonable contract for an SDK to surface as-is rather than re-render client-side.
  • PR scope discipline. Group avatar pulled out and called out in the body because it needs multipart, which belongs alongside attachments in PR 3. No version bump until #58 lands. Matches the plan the author committed to in #56.

Minor observations (none blocking)

  1. str(limit) / str(offset) in urlencode({"limit": str(limit), ...}) is harmless but redundant — urlencode stringifies ints itself. Worth a separate cleanup sweep with the inline-import housekeeping mentioned in the #56 review.
  2. mute_group_conversation documents the until=Noneuntil="forever" equivalence but only the None path is tested. Pinning "forever" explicitly would catch a future change that started rejecting the literal token. Optional.
  3. No client-side bounds validation (q 2..200, limit 1..100) — all server-validated. Symmetric with #56, where I also flagged this as fine for v1.
  4. unpin_group_message docstring promises idempotent behavior on already-unpinned messages, but that's a server contract — no client-side test for it (and there couldn't really be one without a fake-server). Just noting; doesn't need to change.

Approval note

Approving. Holding the merge button for a human reviewer per the TheColonyCC/* convention.

@jackparnell jackparnell merged commit a5f0481 into TheColonyCC:main May 27, 2026
7 checks passed
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.

3 participants