Features
Post Comments & Threads

Post Comments & Threads

Comments let reviewers give feedback in context — attached to the exact sentence or paragraph they're talking about — instead of as a disconnected review note. Select text in the canvas editor, drop a comment on it, and everyone else looking at the post sees the highlight and the thread appear in real time.

This page covers the user-facing comment thread feature. For the wire-level contract (request/response shapes, status codes), see API Reference → Comments. For the authoritative spec, see specs/features/comments.md and specs/api/comments.md in the repo.

What you can do

  • Comment on a text selection. Highlight any text in the post canvas and click the comment bubble that appears — the highlighted span turns into a persistent highlight tied to your comment.
  • Comment on the post generally. Comments created through the API without a text selection show up as thread-level comments with no highlight.
  • Reply. Anyone in the workspace can reply to any comment thread — not just the original commenter and the post author.
  • Resolve / reopen. Click the checkmark to mark a thread resolved; click it again to reopen it. Resolving does not delete anything — it just moves the thread out of the default "open" view.
  • Delete. The comment's author, or a workspace/platform admin or owner, can delete a comment thread (and every reply on it) or an individual reply.
  • See it live. Every add/resolve/delete is pushed to every other open editor session on the same post over the existing collaboration WebSocket — no refresh needed.

Where it lives

The comment panel is a collapsible sidebar in the post canvas editor (PostEditorClientCommentPanel), toggled from the editor toolbar. It shows:

  • An open comment count badge.
  • A "Show resolved" / "Hide resolved" toggle — resolved threads are hidden by default.
  • One card per thread: author, timestamp, the anchored text excerpt (if any), the comment body, any replies indented underneath, and a reply input.
  • Resolve and delete controls per thread; delete also appears per reply.

Clicking a thread in the panel scrolls the editor to and re-selects the anchored text, so you can see exactly what the comment refers to.

How anchoring works

 select text  ──▶  "Comment" button appears above the selection


 click "Comment" ──▶ editor highlights the selection immediately
      │               (client-generated id, before the API call resolves)

 type + submit ──▶ POST /api/posts/{id}/comments
                    { workspace_id, body, mark_id, text_excerpt }

      ├─ success ──▶ highlight stays, thread appears in the panel

      └─ failure ──▶ highlight is removed, error is shown

The highlight is a TipTap mark (data-comment-id="…" span) that survives further edits to the surrounding text — as long as the marked characters themselves aren't deleted, the highlight (and therefore the link back to the comment) stays put. If the marked text is deleted, the comment record still exists (visible in the panel and via the API) but has nothing left to highlight in the document.

⚠️

mark_id is a client-generated identifier, not validated by the backend against the document's actual content. It's purely a join key between the comment record and the editor's highlight span — deleting the highlighted text does not delete or orphan-check the comment.

Who can do what

ActionWho
View commentsAny workspace member
Add a commentAny workspace member
ReplyAny workspace member (not restricted to the thread's participants)
Resolve / reopenAny workspace member (not restricted to the comment's author)
Delete a comment or replyThe comment/reply's author, or a workspace admin/owner, or a platform admin/owner

This is intentionally permissive for triage actions (reply, resolve) and restrictive only for destructive ones (delete) — see API Reference → Comments → Authorization for the exact rule.

Notifications

WhenWho gets notifiedVia
A comment is added to your postThe post's authorSlack DM: "*{commenter}* commented on *{post title}*: _{excerpt}_" with a link to the post
Someone replies to your commentThe comment's original author (unless they're replying to themselves)Slack DM: "*{replier}* replied to your comment on *{post title}*: _{reply text}_"

Notifications are best-effort — if Slack lookup or delivery fails, the comment/reply action still succeeds; only the notification is skipped.

Edge cases worth knowing

  • Resolving is a toggle, not two separate actions. The same button (and the same API route) both resolves and unresolves a thread depending on its current state.
  • Deleting a comment deletes its replies too. There's no way to delete just the parent comment and keep the replies as an orphaned thread.
  • Replies aren't editable. Once posted, a reply's text can't be changed — only deleted.
  • No content validation on mark_id/text_excerpt. A comment created directly via the API (e.g. through automation) with a mark_id that doesn't correspond to anything in the document will simply show up with no highlight to jump to.