clickclack/docs/features/replies.md
2026-05-08 13:23:35 +01:00

2.8 KiB

read_when
changing message create flow or quoted_message_id semantics
touching the `quoted_*` columns on `messages`
changing scope rules for inline replies

Inline replies (quotes)

ClickClack has two reply patterns:

  1. Threads — a flat reply list anchored to a root message; see threads.md.
  2. Inline quote-replies — Discord/Telegram-style. The reply lives in the same stream (channel timeline, DM, or thread pane) and renders a clickable quote of the message it answers.

This page covers (2).

Wire format

POST /api/channels/{channel_id}/messages, POST /api/dms/{conversation_id}/messages, and POST /api/messages/{message_id}/thread/replies all accept an optional quoted_message_id:

{ "body": "responding", "quoted_message_id": "msg_..." }

Server-side, three columns are stored on the new row:

column nullable notes
quoted_message_id yes FK to messages.id, ON DELETE SET NULL
quoted_body_snapshot no ('') trimmed, capped at 280 runes
quoted_author_id yes FK to users.id, ON DELETE SET NULL

When the new message is read back, an additional quoted_author object is hydrated alongside author.

Scope rules

The quoted message must live in the same context as the new message:

  • Channel timeline: same channel_id, and the quoted message must be a top-level message (parent_message_id IS NULL).
  • DM: same direct_conversation_id, top-level only.
  • Thread: same thread_root_id (the root or any reply in the same thread).

Cross-context quoting and quoting a soft-deleted message both fail with HTTP 400 and ErrQuotedMessageOutOfScope. An empty quoted_message_id string is treated as absent.

Soft-snapshot semantics

quoted_body_snapshot is captured at send time and never updated. If the quoted message is later edited, the snapshot stays as-is — the UI shows the historical preview but, on click, scrolls to the live (edited) message.

If the quoted source is hard-deleted, the FK becomes NULL while the snapshot survives. The UI renders the snapshot prefixed with [original deleted] and disables the click target.

Soft-delete (today's DELETE /api/messages/...) sets deleted_at but keeps the row, so existing quote refs continue to resolve. Hard delete is expected to be rare (admin/export purge paths) and is the only path that trips ON DELETE SET NULL.

CLI

clickclack send and clickclack threads reply both accept --reply-to msg_..., which is forwarded as quoted_message_id.

Realtime

No new event types. The existing message.created and thread.reply_created events carry the new fields in the same payload they already used.