Comments
import { Aside } from ‘@astrojs/starlight/components’;
Threaded, real-time comments attached to an artifact or any context within it. Access
via sdk.comments.
Methods
Section titled “Methods”// CRUDadd(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-timesubscribe(handler: (event: CommentEvent) => void): () => voidsubscribeToContext(contextId: string, handler: (event: CommentEvent) => void): () => voiddisconnect(): void
// State bridge (pinned comments)onCaptureState(fn: () => unknown | Promise<unknown>): voidonRestoreState(fn: (state: unknown) => void | Promise<void>): voidQuery builder
Section titled “Query builder”find(filter?: { contextId?: string; parentId?: string | null }): CommentsQuery
query.context(contextId: string): CommentsQueryquery.topLevel(): CommentsQueryquery.sort(field: 'createdAt' | 'updatedAt', order: 'asc' | 'desc'): CommentsQueryquery.limit(n: number): CommentsQueryquery.skip(n: number): CommentsQueryquery.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;}Examples
Section titled “Examples”// Configure (owner only)await sdk.comments.setConfig({ enabled: true, identityMode: 'named', allowReplies: true, maxDepth: 3,});
// Add a comment pinned to a slideconst comment = await sdk.comments.add({ content: 'Great chart!', contextId: 'slide-5', authorName: 'Alice',});
// Replyawait sdk.comments.reply(comment.id, 'Thanks!', 'Bob');
// Query top-level comments for a contextconst comments = await sdk.comments .find({ contextId: 'slide-5' }) .topLevel() .sort('createdAt', 'desc') .limit(50) .exec();
// Fetch a full nested threadconst thread = await sdk.comments.getThread(comment.id);
// Real-time updatesconst unsub = sdk.comments.subscribeToContext('slide-5', (event) => { if (event.type === 'comment:added') renderComment(event.comment);});// Later:unsub();State bridge
Section titled “State bridge”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.
Action items
Section titled “Action items”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 creatingPOST /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 commentPATCH /v1/data/{artifactId}/comments/{id}/assign{ "assignee": "alice@example.com", "dueAt": "2025-08-01T00:00:00Z" }
# UnassignPATCH /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=meGET /v1/data/{artifactId}/comments?assignee=alice@example.comAI crew tools: action_item_create, action_item_list.
Viewer toolbar (built-in UI)
Section titled “Viewer toolbar (built-in UI)”When comments are enabled on a page, signed-in viewers get a floating toolbar:
| Feature | Notes |
|---|---|
| Unread badge | Counts update over WebSocket even before the panel opens |
| Open / Resolved filters | Switch thread views |
| Reactions | 👍 ❤️ ✅ 🎉 👀 on any comment |
| Typing indicators | See when someone is composing |
| Presence | ”N others here” in the comment header (comments channel, not page-wide analytics) |
| Resolve / Reopen | Mark threads done without deleting |
Owners and collaborators can also manage threads from the Comments tab in the workspace Inspector.