Skip to content

Comments

import { Aside } from ‘@astrojs/starlight/components’;

Threaded, real-time comments attached to an artifact or any context within it. Access via sdk.comments.

// CRUD
add(options: {
content: string;
contextId?: string;
parentId?: string;
authorName?: string;
position?: CommentPosition;
state?: unknown;
mentions?: string[];
}): Promise<Comment>
reply(parentId: string, content: string, authorName?: string): Promise<Comment>
edit(id: string, content: string): Promise<Comment>
delete(id: string): Promise<boolean>
findById(id: string): Promise<Comment | null>
getReplies(parentId: string): Promise<Comment[]>
getThread(rootId: string): Promise<CommentThread>
// Configuration (owner only)
getConfig(): Promise<CommentsConfig>
setConfig(config: Partial<CommentsConfig>): Promise<CommentsConfig>
// Real-time
subscribe(handler: (event: CommentEvent) => void): () => void
subscribeToContext(contextId: string, handler: (event: CommentEvent) => void): () => void
disconnect(): void
// State bridge (pinned comments)
onCaptureState(fn: () => unknown | Promise<unknown>): void
onRestoreState(fn: (state: unknown) => void | Promise<void>): void
find(filter?: { contextId?: string; parentId?: string | null }): CommentsQuery
query.context(contextId: string): CommentsQuery
query.topLevel(): CommentsQuery
query.sort(field: 'createdAt' | 'updatedAt', order: 'asc' | 'desc'): CommentsQuery
query.limit(n: number): CommentsQuery
query.skip(n: number): CommentsQuery
query.exec(): Promise<Comment[]>
interface Comment {
id: string;
contextId: string | null;
parentId: string | null;
authorId: string | null;
authorName: string;
content: string;
createdAt: string;
updatedAt: string;
resolved?: boolean;
resolvedBy?: string | null;
resolvedAt?: string | null;
position?: CommentPosition | null;
state?: unknown | null;
mentions?: string[];
// Action item fields (present when assigned)
assigneeUserId?: string | null;
assigneeEmail?: string | null;
dueAt?: string | null;
}
interface CommentsConfig {
enabled: boolean;
identityMode: 'anonymous' | 'named' | 'authenticated';
allowReplies: boolean;
maxDepth: number;
overlayEnabled?: boolean;
}
interface CommentEvent {
type: 'comment:added' | 'comment:updated' | 'comment:deleted' | 'comment:resolved';
comment: Comment;
}
interface CommentThread {
comment: Comment;
replies: CommentThread[];
}
interface CommentPosition {
selector?: string;
relX?: number;
relY?: number;
pctX?: number;
pctY?: number;
scrollY?: number;
}
// Configure (owner only)
await sdk.comments.setConfig({
enabled: true,
identityMode: 'named',
allowReplies: true,
maxDepth: 3,
});
// Add a comment pinned to a slide
const comment = await sdk.comments.add({
content: 'Great chart!',
contextId: 'slide-5',
authorName: 'Alice',
});
// Reply
await sdk.comments.reply(comment.id, 'Thanks!', 'Bob');
// Query top-level comments for a context
const comments = await sdk.comments
.find({ contextId: 'slide-5' })
.topLevel()
.sort('createdAt', 'desc')
.limit(50)
.exec();
// Fetch a full nested thread
const thread = await sdk.comments.getThread(comment.id);
// Real-time updates
const unsub = sdk.comments.subscribeToContext('slide-5', (event) => {
if (event.type === 'comment:added') renderComment(event.comment);
});
// Later:
unsub();

Pinned comments can capture and restore app state (e.g. active filters on a dashboard). Register callbacks before adding comments:

sdk.comments.onCaptureState(() => ({
filters: currentFilters,
activeTab,
}));
sdk.comments.onRestoreState((state) => {
currentFilters = state.filters;
activeTab = state.activeTab;
});

When a comment is added, the current state is stored with it. Opening a pinned comment replays the state via onRestoreState.

Any comment can be turned into an action item by assigning it to a workspace member or collaborator. The assignee gets an email (and a Telegram message if linked). When they resolve the comment, the requester is notified and can reopen with one click.

In the artifact viewer: open the comment panel → Assign button → pick a person and an optional due date.

Via API:

# Assign when creating
POST /v1/data/{artifactId}/comments
{ "content": "Fix the Y-axis label", "assignee": "alice@example.com", "dueAt": "2025-08-01T00:00:00Z" }
# Assign or unassign an existing comment
PATCH /v1/data/{artifactId}/comments/{id}/assign
{ "assignee": "alice@example.com", "dueAt": "2025-08-01T00:00:00Z" }
# Unassign
PATCH /v1/data/{artifactId}/comments/{id}/assign
{ "assignee": null }

The assignee must be in the artifact’s people set (workspace members + collaborators); unknown addresses return 400 ASSIGNEE_NOT_FOUND.

Tracking: everyone sees their open action items across all artifacts in the Home notifications bell — sorted by due date, overdue items highlighted, with Done and Reopen buttons inline.

Filter by assignee:

GET /v1/data/{artifactId}/comments?assignee=me
GET /v1/data/{artifactId}/comments?assignee=alice@example.com

AI crew tools: action_item_create, action_item_list.

When comments are enabled on a page, signed-in viewers get a floating toolbar:

FeatureNotes
Unread badgeCounts update over WebSocket even before the panel opens
Open / Resolved filtersSwitch thread views
Reactions👍 ❤️ ✅ 🎉 👀 on any comment
Typing indicatorsSee when someone is composing
Presence”N others here” in the comment header (comments channel, not page-wide analytics)
Resolve / ReopenMark threads done without deleting

Owners and collaborators can also manage threads from the Comments tab in the workspace Inspector.