Skip to content

Tell real conversations from system-only threads (0.3.3) - #7

Open
rameerez wants to merge 1 commit into
mainfrom
feat/conversational-scopes
Open

rameerez wants to merge 1 commit into
mainfrom
feat/conversational-scopes

Conversation

@rameerez

@rameerez rameerez commented Oct 3, 2026

Copy link
Copy Markdown
Owner

A system message is a message: post_system_message! bumps messages_count and moves last_message like a human one. The inbox relies on that, so "Your ride was cancelled" still surfaces a thread nobody typed in. Ops walls, moderation lists and analytics ask the opposite question: did a person actually say anything here? CarHey's madmin chats wall answered it with its own EXISTS subquery and a PostgreSQL-only DISTINCT ON "last human message" query. The primitive belongs in the gem.

Everything is additive. The inbox, unread counts, messages_count and last_message behave exactly as before.

  • Chats::Message.conversational: kind text and not deleted. A captionless photo counts, a tombstone does not.
  • Chats::Conversation.with_conversation / .without_conversation: one correlated EXISTS / NOT EXISTS, built through Arel from Message.conversational so the two definitions cannot drift. They compose with inbox_for, about and unread_by and never multiply rows.
  • Conversation#last_conversational_message: the newest of those, or nil. It is a query, not the denormalized pointer.
  • Chats::Message.last_conversational_in(conversations): the one-query batch twin for lists, ranked with ROW_NUMBER like Chats::Inbox's stacks. It returns a relation, so a host can narrow the columns before index_by(&:conversation_id). The ranked subquery is built unscoped so a chained .select never leaks into it.

README gains "Real conversations vs system-only threads" under the data model, plus the API reference lines and the Upgrading note. CHANGELOG has 0.3.3 - unreleased, and Chats::VERSION is 0.3.3. No gem was built or published.

Validation, run locally on Ruby 3.4.7:

Leg Result
SQLite, Rails 8.1 340 runs, 1,400 assertions, 0 failures
SQLite, Rails 7.1 340 runs, 1,400 assertions, 0 failures
PostgreSQL 18 340 runs, 1,400 assertions, 0 failures
MySQL 8.4 340 runs, 1,400 assertions, 0 failures
RuboCop 82 files, no offenses

The new test/models/chats/conversational_scopes_test.rb has 15 tests. They cover text vs system vs tombstone vs empty threads in both directions, the exact-complement property, composition with inbox_for and unread_by, a single-query assertion for the scopes and the batch helper, and a created_at tie broken the way recent_first breaks it.

Host follow-up after the bump: CarHey's Madmin::ChatsDashboard swaps HUMAN_MESSAGE_SQL / real_conversations for with_conversation, and its human-message count and last-message queries for Message.conversational and last_conversational_in.

馃 Generated with Claude Code

https://claude.ai/code/session_01T5EyMJjNWMPADZwjXBdiiZ

A system message is a message: post_system_message! bumps messages_count
and moves last_message like a human one, and the inbox relies on that, so
"Your ride was cancelled" still surfaces a thread nobody typed in. Ops
walls, moderation lists and analytics ask the opposite question: did a
person actually say anything here? Hosts were answering it with their own
EXISTS subquery and their own "last human message" query (CarHey's madmin
chats wall, with a PostgreSQL-only DISTINCT ON).

Additive, nothing changes by default:

- Chats::Message.conversational: kind "text" and not deleted (a
  captionless photo counts, a tombstone does not)
- Chats::Conversation.with_conversation / .without_conversation: one
  correlated EXISTS / NOT EXISTS built from Message.conversational through
  Arel, so they compose with inbox_for, about, unread_by and never
  multiply rows
- Conversation#last_conversational_message, and its one-query batch twin
  Chats::Message.last_conversational_in(conversations), ranked with
  ROW_NUMBER like Chats::Inbox's stacks (PostgreSQL, MySQL 8, SQLite);
  a relation, so a host can narrow the columns before index_by

README gains "Real conversations vs system-only threads" under the data
model plus the API reference lines; CHANGELOG 0.3.3 (unreleased).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T5EyMJjNWMPADZwjXBdiiZ
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.

1 participant