Discussion is copied UI that opens a block's comments and authored changes in one anchored popover. The suggestion package supplies reusable change state and activation. Comments remains optional and owns its thread records.
| Owner | Responsibility |
|---|---|
platejs/suggestion and platejs/suggestion/react | Supply suggestion modes, semantic change attributes, exact-view activation, and stable change hooks |
platejs/authored | Store proposed edits, identities, dependencies, decisions, and retained content in the document |
platejs/comments and platejs/comments/react | Own comment records, actions, mapped ranges, decoration, and activation |
Copied suggestion item | Configure the package plugin's content attributes and compose SuggestionKit |
Copied discussion item | Join authored changes to optional comment threads, then render block triggers, cards, and Floating Discussion |
The application pairs CommentsJSON with a document revision, provides users, and authorizes durable writes through initialState.mutate. Authored metadata stays in the document envelope. Comment threads contain semantic targets; current placement comes from attachment(id) and may refer to a stable authored change ID.
Install the combined view and the comment toolbar action. The registry command copies its Comment and Suggestion UI dependencies.
DiscussionKit installs Comments and the complete review surface. Add SuggestionKit to editors that review authored changes.
Pass the verified revision's CommentsJSON to CommentsPlugin. EditorKit includes the copied SuggestionKit; a smaller kit can install it explicitly.
'use client';
import type { EditorValueInput } from 'platejs';
import type { CommentsJSON, CommentUser } from 'platejs/comments';
import { CommentsPlugin } from 'platejs/comments/react';
import { EditorRoot, useCreateEditor } from 'platejs/react';
import { AllCommentsButton } from '@/components/editor/comment-toolbar-button';
import { DiscussionKit } from '@/components/editor/discussion';
import { Editor, EditorContainer } from '@/components/editor/editor';
import { EditorKit } from '@/components/editor/plugins';
import { Toolbar } from '@/components/editor/toolbar';
export function DiscussionEditor({
initialValue,
initialComments,
users,
currentUserId,
}: {
initialValue: EditorValueInput;
initialComments: CommentsJSON;
users: Record<string, CommentUser>;
currentUserId: string;
}) {
const editor = useCreateEditor({
plugins: [
...EditorKit,
...DiscussionKit,
CommentsPlugin.configure({
initialState: { initialComments, users, currentUserId },
}),
],
initialValue,
userId: currentUserId,
});
return (
<EditorRoot editor={editor}>
<EditorContainer>
<Toolbar>
<AllCommentsButton />
</Toolbar>
<Editor />
</EditorContainer>
</EditorRoot>
);
}'use client';
import type { EditorValueInput } from 'platejs';
import type { CommentsJSON, CommentUser } from 'platejs/comments';
import { CommentsPlugin } from 'platejs/comments/react';
import { EditorRoot, useCreateEditor } from 'platejs/react';
import { AllCommentsButton } from '@/components/editor/comment-toolbar-button';
import { DiscussionKit } from '@/components/editor/discussion';
import { Editor, EditorContainer } from '@/components/editor/editor';
import { EditorKit } from '@/components/editor/plugins';
import { Toolbar } from '@/components/editor/toolbar';
export
For a smaller editor composition, configure SuggestionPlugin from platejs/suggestion/react, or use SuggestionKit with its default styles. DiscussionSlots reads suggestion state from the package hooks; it does not require a registry-local semantic plugin.
Discussion groups unresolved records by block and sorts each block chronologically:
useSuggestionChanges(path) supplies native AuthoredChange records for each block. Discussion derives dates, labels, ranges, and card text in copied registry code, then joins change-targeted threads from Comments. Editing a comment body does not rebuild suggestion decorations or wake unrelated blocks.
Review cards call authored directly:
import { DefaultAuthoredPlugin } from 'platejs/authored';
const authored = editor.plugin(DefaultAuthoredPlugin);
const selection = authored.read.select({ ids: [changeId] });
const result = authored.update.decide({
action: 'accept',
selection,
});import { DefaultAuthoredPlugin } from 'platejs/authored';
const authored = editor.plugin(DefaultAuthoredPlugin);
const selection = authored.read.select({ ids: [changeId] });
const result = authored.update.decide({
action: 'accept',
selection,
});Use action: 'reject' to reject the change. Keep a card open for stale or blocked results so the reviewer can refresh the record or resolve its dependencies explicitly.
A block trigger opens every unresolved comment and pending suggestion currently attached to that block. Clicking inline comment text opens the records at that point. Clicking an authored-change marker activates that change in the mounted suggestion view. These live entry points open the same copied popover and cards.
When comment ranges overlap, clicking the shared text opens every thread at that point in source order. The selected group survives edits and source reordering until another activation or thread removal changes it. Clicking unmarked editor content clears the active review.
When attachment(id) reports unavailable, Discussion removes its inline decoration and block count. It does not remember the previous block or move the thread to nearby text. All comments keeps the conversation and original excerpt reachable independently of block placement. Live-session undo can restore exact mapped coverage without rewinding the conversation.
Resolved conversations also leave Floating Discussion and remain available through the Resolved filter in All comments. The dialog belongs to comment-toolbar-button; it works with Comments alone and locates authored-change targets only when Authored is installed in that editor view.
Target an authored change ID to attach a conversation:
import { CommentsPlugin } from 'platejs/comments/react';
const comments = editor.plugin(CommentsPlugin).api;
const result = await comments.createThread({
body,
excerpt: 'insert suggestion',
target: { id: changeId, type: 'change' },
});
if (result.status === 'applied') {
comments.setActive([result.value]);
}import { CommentsPlugin } from 'platejs/comments/react';
const comments = editor.plugin(CommentsPlugin).api;
const result = await comments.createThread({
body,
excerpt: 'insert suggestion',
target: { id: changeId, type: 'change' },
});
if (result.status === 'applied') {
comments.setActive([result.value]);
}Preserve input for non-applied results and thrown errors. Replies use the change's location without allocating a comment anchor. Comment records retain their bodies and resolution when the change leaves the active projection. If undo or a document update restores that change ID, its unresolved replies appear again.
SuggestionPlugin does not depend on Comments. An editor can use suggestion modes, semantic decorations, active-change state, and useSuggestionChanges(path) without installing CommentsPlugin or DiscussionKit.
Add Discussion only when the product needs cards, replies, authors, dates, or popovers. Those presentation and thread-joining choices remain in copied registry code.
Pass initialComments to each fresh editor with its matching document value. Use CommentsPlugin for a read-only React editor and BaseCommentsPlugin with copied static comment styling for EditorStatic. Each editor binds its own attachments and suggestion view state; local undo starts empty.
Save editor.plugin(CommentsPlugin).api.toJSON() alongside editor.read.value() under the same revision. Observe conversation data through subscribeThreads and placement through subscribeAttachments. See Comments for loading, saving, historical target joins, and awaited actions, and Suggestions for modes and authored decisions.
Install the copied kit in the editor plugin list. It includes CommentKit and the complete DiscussionSlots composition. Editors that review authored changes also install SuggestionKit or a configured SuggestionPlugin.
For custom composition, pass the complete value to CommentsPlugin.configure({ slots: DiscussionSlots }). It owns the shared discussion index, block triggers, review cards, and Floating Discussion together.