@tldraw/commenting 5.3.0-next.d7d8ced023d5 → 5.3.0-next.dec36c5c2930

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (239) hide show
  1. package/commenting.css +85 -87
  2. package/dist-cjs/canvas/anchor-lifecycle.js +2 -2
  3. package/dist-cjs/canvas/anchor-lifecycle.js.map +2 -2
  4. package/dist-cjs/{ui/tooltip-button.js → canvas/canvas-events.js} +16 -13
  5. package/dist-cjs/canvas/canvas-events.js.map +7 -0
  6. package/dist-cjs/canvas/cluster-badge.js +121 -0
  7. package/dist-cjs/canvas/cluster-badge.js.map +7 -0
  8. package/dist-cjs/canvas/cluster-fade.js +85 -0
  9. package/dist-cjs/canvas/cluster-fade.js.map +7 -0
  10. package/dist-cjs/canvas/cluster-input.js +51 -1
  11. package/dist-cjs/canvas/cluster-input.js.map +2 -2
  12. package/dist-cjs/canvas/cluster-model.js +252 -0
  13. package/dist-cjs/canvas/cluster-model.js.map +7 -0
  14. package/dist-cjs/canvas/comment-mutations.js +57 -28
  15. package/dist-cjs/canvas/comment-mutations.js.map +2 -2
  16. package/dist-cjs/canvas/comment-reactions.js +4 -7
  17. package/dist-cjs/canvas/comment-reactions.js.map +2 -2
  18. package/dist-cjs/canvas/comment-render.js +4 -3
  19. package/dist-cjs/canvas/comment-render.js.map +2 -2
  20. package/dist-cjs/canvas/comment-store.js.map +2 -2
  21. package/dist-cjs/canvas/comment-tool.js +12 -7
  22. package/dist-cjs/canvas/comment-tool.js.map +2 -2
  23. package/dist-cjs/canvas/comments-filter-menu.js +11 -13
  24. package/dist-cjs/canvas/comments-filter-menu.js.map +2 -2
  25. package/dist-cjs/canvas/comments-overflow-menu.js +10 -2
  26. package/dist-cjs/canvas/comments-overflow-menu.js.map +2 -2
  27. package/dist-cjs/canvas/comments-overlay.js +81 -871
  28. package/dist-cjs/canvas/comments-overlay.js.map +3 -3
  29. package/dist-cjs/canvas/comments-sidebar.js +16 -11
  30. package/dist-cjs/canvas/comments-sidebar.js.map +2 -2
  31. package/dist-cjs/canvas/comments-visibility-toggle.js +5 -25
  32. package/dist-cjs/canvas/comments-visibility-toggle.js.map +2 -2
  33. package/dist-cjs/canvas/context.js.map +1 -1
  34. package/dist-cjs/canvas/hooks.js +8 -2
  35. package/dist-cjs/canvas/hooks.js.map +2 -2
  36. package/dist-cjs/canvas/license.js.map +1 -1
  37. package/dist-cjs/canvas/mobile-placement.js +91 -0
  38. package/dist-cjs/canvas/mobile-placement.js.map +7 -0
  39. package/dist-cjs/canvas/options.js +39 -1
  40. package/dist-cjs/canvas/options.js.map +2 -2
  41. package/dist-cjs/canvas/pending-composer.js +135 -0
  42. package/dist-cjs/canvas/pending-composer.js.map +7 -0
  43. package/dist-cjs/canvas/pin-stacking.js +26 -1
  44. package/dist-cjs/canvas/pin-stacking.js.map +2 -2
  45. package/dist-cjs/canvas/region-box.js +113 -0
  46. package/dist-cjs/canvas/region-box.js.map +7 -0
  47. package/dist-cjs/canvas/state.js.map +2 -2
  48. package/dist-cjs/canvas/thread-pin.js +304 -0
  49. package/dist-cjs/canvas/thread-pin.js.map +7 -0
  50. package/dist-cjs/canvas/thread-preview.js +41 -49
  51. package/dist-cjs/canvas/thread-preview.js.map +2 -2
  52. package/dist-cjs/canvas/thread-stack.js +7 -7
  53. package/dist-cjs/canvas/thread-stack.js.map +2 -2
  54. package/dist-cjs/canvas/thread-state.js +15 -0
  55. package/dist-cjs/canvas/thread-state.js.map +2 -2
  56. package/dist-cjs/canvas/thread-view.js +120 -67
  57. package/dist-cjs/canvas/thread-view.js.map +3 -3
  58. package/dist-cjs/clustering/computeClusterTable.js +2 -2
  59. package/dist-cjs/clustering/computeClusterTable.js.map +2 -2
  60. package/dist-cjs/clustering/replay.js +89 -22
  61. package/dist-cjs/clustering/replay.js.map +3 -3
  62. package/dist-cjs/clustering/runtime.js +19 -12
  63. package/dist-cjs/clustering/runtime.js.map +2 -2
  64. package/dist-cjs/clustering/types.js.map +1 -1
  65. package/dist-cjs/index.d.ts +326 -203
  66. package/dist-cjs/index.js +7 -1
  67. package/dist-cjs/index.js.map +2 -2
  68. package/dist-cjs/ui/byline.js +5 -3
  69. package/dist-cjs/ui/byline.js.map +2 -2
  70. package/dist-cjs/ui/comment-composer.js +2 -18
  71. package/dist-cjs/ui/comment-composer.js.map +2 -2
  72. package/dist-cjs/ui/comment-pin.js +2 -15
  73. package/dist-cjs/ui/comment-pin.js.map +2 -2
  74. package/dist-cjs/ui/comments-list.js +11 -24
  75. package/dist-cjs/ui/comments-list.js.map +2 -2
  76. package/dist-cjs/ui/format-time.js +13 -1
  77. package/dist-cjs/ui/format-time.js.map +2 -2
  78. package/dist-cjs/ui/icons.js +138 -0
  79. package/dist-cjs/ui/icons.js.map +7 -0
  80. package/dist-cjs/ui/reaction-picker.js +11 -13
  81. package/dist-cjs/ui/reaction-picker.js.map +2 -2
  82. package/dist-cjs/ui/reaction.js +3 -4
  83. package/dist-cjs/ui/reaction.js.map +2 -2
  84. package/dist-cjs/ui/send-button.js +2 -9
  85. package/dist-cjs/ui/send-button.js.map +2 -2
  86. package/dist-cjs/ui/visual-viewport.js +32 -0
  87. package/dist-cjs/ui/visual-viewport.js.map +7 -0
  88. package/dist-esm/canvas/anchor-lifecycle.mjs +3 -3
  89. package/dist-esm/canvas/anchor-lifecycle.mjs.map +2 -2
  90. package/dist-esm/canvas/canvas-events.mjs +15 -0
  91. package/dist-esm/canvas/canvas-events.mjs.map +7 -0
  92. package/dist-esm/canvas/cluster-badge.mjs +106 -0
  93. package/dist-esm/canvas/cluster-badge.mjs.map +7 -0
  94. package/dist-esm/canvas/cluster-fade.mjs +65 -0
  95. package/dist-esm/canvas/cluster-fade.mjs.map +7 -0
  96. package/dist-esm/canvas/cluster-input.mjs +52 -2
  97. package/dist-esm/canvas/cluster-input.mjs.map +2 -2
  98. package/dist-esm/canvas/cluster-model.mjs +236 -0
  99. package/dist-esm/canvas/cluster-model.mjs.map +7 -0
  100. package/dist-esm/canvas/comment-mutations.mjs +57 -28
  101. package/dist-esm/canvas/comment-mutations.mjs.map +2 -2
  102. package/dist-esm/canvas/comment-reactions.mjs +5 -12
  103. package/dist-esm/canvas/comment-reactions.mjs.map +2 -2
  104. package/dist-esm/canvas/comment-render.mjs +5 -4
  105. package/dist-esm/canvas/comment-render.mjs.map +2 -2
  106. package/dist-esm/canvas/comment-store.mjs.map +2 -2
  107. package/dist-esm/canvas/comment-tool.mjs +12 -7
  108. package/dist-esm/canvas/comment-tool.mjs.map +2 -2
  109. package/dist-esm/canvas/comments-filter-menu.mjs +12 -13
  110. package/dist-esm/canvas/comments-filter-menu.mjs.map +2 -2
  111. package/dist-esm/canvas/comments-overflow-menu.mjs +11 -2
  112. package/dist-esm/canvas/comments-overflow-menu.mjs.map +2 -2
  113. package/dist-esm/canvas/comments-overlay.mjs +58 -879
  114. package/dist-esm/canvas/comments-overlay.mjs.map +3 -3
  115. package/dist-esm/canvas/comments-sidebar.mjs +18 -14
  116. package/dist-esm/canvas/comments-sidebar.mjs.map +2 -2
  117. package/dist-esm/canvas/comments-visibility-toggle.mjs +5 -25
  118. package/dist-esm/canvas/comments-visibility-toggle.mjs.map +2 -2
  119. package/dist-esm/canvas/hooks.mjs +9 -2
  120. package/dist-esm/canvas/hooks.mjs.map +2 -2
  121. package/dist-esm/canvas/license.mjs.map +1 -1
  122. package/dist-esm/canvas/mobile-placement.mjs +71 -0
  123. package/dist-esm/canvas/mobile-placement.mjs.map +7 -0
  124. package/dist-esm/canvas/options.mjs +39 -1
  125. package/dist-esm/canvas/options.mjs.map +2 -2
  126. package/dist-esm/canvas/pending-composer.mjs +127 -0
  127. package/dist-esm/canvas/pending-composer.mjs.map +7 -0
  128. package/dist-esm/canvas/pin-stacking.mjs +26 -1
  129. package/dist-esm/canvas/pin-stacking.mjs.map +2 -2
  130. package/dist-esm/canvas/region-box.mjs +93 -0
  131. package/dist-esm/canvas/region-box.mjs.map +7 -0
  132. package/dist-esm/canvas/state.mjs.map +2 -2
  133. package/dist-esm/canvas/thread-pin.mjs +310 -0
  134. package/dist-esm/canvas/thread-pin.mjs.map +7 -0
  135. package/dist-esm/canvas/thread-preview.mjs +42 -49
  136. package/dist-esm/canvas/thread-preview.mjs.map +2 -2
  137. package/dist-esm/canvas/thread-stack.mjs +8 -9
  138. package/dist-esm/canvas/thread-stack.mjs.map +2 -2
  139. package/dist-esm/canvas/thread-state.mjs +15 -0
  140. package/dist-esm/canvas/thread-state.mjs.map +2 -2
  141. package/dist-esm/canvas/thread-view.mjs +131 -72
  142. package/dist-esm/canvas/thread-view.mjs.map +3 -3
  143. package/dist-esm/clustering/computeClusterTable.mjs +2 -2
  144. package/dist-esm/clustering/computeClusterTable.mjs.map +2 -2
  145. package/dist-esm/clustering/replay.mjs +89 -22
  146. package/dist-esm/clustering/replay.mjs.map +3 -3
  147. package/dist-esm/clustering/runtime.mjs +19 -12
  148. package/dist-esm/clustering/runtime.mjs.map +2 -2
  149. package/dist-esm/index.d.mts +326 -203
  150. package/dist-esm/index.mjs +16 -3
  151. package/dist-esm/index.mjs.map +2 -2
  152. package/dist-esm/ui/byline.mjs +6 -4
  153. package/dist-esm/ui/byline.mjs.map +2 -2
  154. package/dist-esm/ui/comment-composer.mjs +2 -18
  155. package/dist-esm/ui/comment-composer.mjs.map +2 -2
  156. package/dist-esm/ui/comment-pin.mjs +2 -15
  157. package/dist-esm/ui/comment-pin.mjs.map +2 -2
  158. package/dist-esm/ui/comments-list.mjs +10 -23
  159. package/dist-esm/ui/comments-list.mjs.map +2 -2
  160. package/dist-esm/ui/format-time.mjs +13 -1
  161. package/dist-esm/ui/format-time.mjs.map +2 -2
  162. package/dist-esm/ui/icons.mjs +118 -0
  163. package/dist-esm/ui/icons.mjs.map +7 -0
  164. package/dist-esm/ui/reaction-picker.mjs +12 -13
  165. package/dist-esm/ui/reaction-picker.mjs.map +2 -2
  166. package/dist-esm/ui/reaction.mjs +3 -4
  167. package/dist-esm/ui/reaction.mjs.map +2 -2
  168. package/dist-esm/ui/send-button.mjs +2 -9
  169. package/dist-esm/ui/send-button.mjs.map +2 -2
  170. package/dist-esm/ui/visual-viewport.mjs +12 -0
  171. package/dist-esm/ui/visual-viewport.mjs.map +7 -0
  172. package/package.json +7 -6
  173. package/src/canvas/anchor-lifecycle.test.ts +2 -2
  174. package/src/canvas/anchor-lifecycle.ts +25 -39
  175. package/src/canvas/canvas-events.ts +18 -0
  176. package/src/canvas/canvas.css +5 -11
  177. package/src/canvas/cluster-badge.tsx +133 -0
  178. package/src/canvas/cluster-fade.ts +102 -0
  179. package/src/canvas/cluster-input.test.ts +148 -14
  180. package/src/canvas/cluster-input.ts +92 -5
  181. package/src/canvas/cluster-model.ts +378 -0
  182. package/src/canvas/comment-mutations.test.ts +46 -4
  183. package/src/canvas/comment-mutations.ts +115 -104
  184. package/src/canvas/comment-reactions.test.ts +22 -0
  185. package/src/canvas/comment-reactions.tsx +33 -32
  186. package/src/canvas/comment-render.ts +8 -8
  187. package/src/canvas/comment-store.ts +12 -17
  188. package/src/canvas/comment-tool.test.ts +109 -4
  189. package/src/canvas/comment-tool.tsx +31 -19
  190. package/src/canvas/comments-filter-menu.tsx +10 -17
  191. package/src/canvas/comments-overflow-menu.tsx +8 -3
  192. package/src/canvas/comments-overlay.tsx +160 -1345
  193. package/src/canvas/comments-sidebar.tsx +40 -19
  194. package/src/canvas/comments-visibility-toggle.tsx +6 -30
  195. package/src/canvas/context.ts +4 -3
  196. package/src/canvas/hooks.test.ts +94 -0
  197. package/src/canvas/hooks.ts +17 -3
  198. package/src/canvas/license.ts +1 -1
  199. package/src/canvas/mobile-placement.ts +115 -0
  200. package/src/canvas/options.test.ts +124 -2
  201. package/src/canvas/options.ts +192 -58
  202. package/src/canvas/pending-composer.tsx +161 -0
  203. package/src/canvas/pin-stacking.test.ts +111 -1
  204. package/src/canvas/pin-stacking.ts +46 -0
  205. package/src/canvas/region-box.tsx +124 -0
  206. package/src/canvas/state.ts +20 -28
  207. package/src/canvas/thread-pin.tsx +419 -0
  208. package/src/canvas/thread-preview.tsx +77 -87
  209. package/src/canvas/thread-stack.tsx +19 -29
  210. package/src/canvas/thread-state.test.ts +51 -0
  211. package/src/canvas/thread-state.ts +56 -23
  212. package/src/canvas/thread-view.test.ts +72 -0
  213. package/src/canvas/thread-view.tsx +231 -133
  214. package/src/clustering/computeClusterTable.ts +12 -3
  215. package/src/clustering/replay.test.ts +0 -7
  216. package/src/clustering/replay.ts +131 -32
  217. package/src/clustering/runtime.test.ts +50 -6
  218. package/src/clustering/runtime.ts +42 -39
  219. package/src/clustering/schedule.test.ts +0 -6
  220. package/src/clustering/screen-offsets.test.ts +171 -0
  221. package/src/clustering/types.ts +10 -0
  222. package/src/index.ts +15 -2
  223. package/src/ui/byline.tsx +16 -6
  224. package/src/ui/comment-composer.tsx +25 -69
  225. package/src/ui/comment-pin.tsx +3 -16
  226. package/src/ui/comments-list.tsx +40 -29
  227. package/src/ui/comments.css +80 -76
  228. package/src/ui/format-time.test.ts +69 -0
  229. package/src/ui/format-time.ts +22 -2
  230. package/src/ui/icons.tsx +138 -0
  231. package/src/ui/reaction-picker.tsx +9 -16
  232. package/src/ui/reaction.tsx +10 -6
  233. package/src/ui/send-button.tsx +3 -8
  234. package/src/ui/visual-viewport.test.ts +36 -0
  235. package/src/ui/visual-viewport.ts +27 -0
  236. package/dist-cjs/ui/tooltip-button.js.map +0 -7
  237. package/dist-esm/ui/tooltip-button.mjs +0 -12
  238. package/dist-esm/ui/tooltip-button.mjs.map +0 -7
  239. package/src/ui/tooltip-button.tsx +0 -20
@@ -31,13 +31,11 @@ import { TLUiOverrides } from 'tldraw';
31
31
  import { VecLike } from 'tldraw';
32
32
 
33
33
  /**
34
- * Where a thread's pin sits on the page, for each anchor kind. Null hides the pin. For imprecise
35
- * shape anchors the pin uses the editor's {@link CommentingOptions.impreciseShapeAnchor} (a
36
- * normalized 0–1 spot, top-right by default) rather than the stored `x`/`y`.
34
+ * Where a thread's pin sits on the page, for each anchor kind. Null hides the pin. Imprecise shape
35
+ * anchors use {@link CommentingOptions.impreciseShapeAnchor} rather than the stored `x`/`y`.
37
36
  *
38
- * A shape anchor's `x`/`y` are normalized within the shape's own bounds and resolved through the
39
- * shape's page transform, so the pin rides every part of that transform — rotating the shape
40
- * carries the pin around with it instead of leaving it behind in the bounding box.
37
+ * A shape anchor's `x`/`y` are normalized within the shape's bounds and resolved through its page
38
+ * transform, so the pin rides rotation instead of being left behind in the bounding box.
41
39
  * @public
42
40
  */
43
41
  export declare function anchorPagePoint(editor: Editor, anchor: TLCommentAnchor): {
@@ -49,7 +47,11 @@ export { Avatar }
49
47
 
50
48
  export { AvatarProps }
51
49
 
52
- /** A comment's metadata line: author name, relative time, and an edited marker. @public @react */
50
+ /**
51
+ * A comment's metadata line: author name, relative time, and an edited marker. Hovering the time
52
+ * shows the full date and time in a tooltip.
53
+ * @public @react
54
+ */
53
55
  export declare function Byline({ author, date, edited }: BylineProps): JSX.Element;
54
56
 
55
57
  /** @public */
@@ -66,14 +68,11 @@ export declare interface BylineProps {
66
68
  * thread popover (with a reply composer) on click, and shows a composer where the comment tool
67
69
  * placed a new thread. Reads/writes comment records straight from `editor.store`.
68
70
  *
69
- * It's meant as the batteries-included default — every visible piece is a lever (the `CommentBody`
70
- * and `PinContent` slots on `CommentTool.configure({ components })`), and the pieces it composes
71
- * (`CommentPin`, `CommentThread`, `CommentComposer`, the hooks, the tool) are all exported, so a
72
- * consumer can rebuild this from parts instead.
73
- *
74
- * The host wiring — who the viewer is, how ids become names, read status, mentions — is the
75
- * {@link CommentingContext}, which `CanvasCommentsSidebar` takes too, so a host mounting both can
76
- * build it once and spread it into each.
71
+ * It's the batteries-included default: every visible piece is a slot on
72
+ * `CommentTool.configure({ components })`, and the pieces it composes (`CommentPin`,
73
+ * `CommentThread`, `CommentComposer`, the hooks, the tool) are all exported, so a consumer can
74
+ * rebuild it from parts instead. The host wiring is the {@link CommentingContext}, which
75
+ * `CanvasCommentsSidebar` takes too.
77
76
  *
78
77
  * @public @react
79
78
  */
@@ -200,34 +199,49 @@ export declare interface CommentingComponents {
200
199
  ThreadPreview?: ComponentType<{
201
200
  comment: TLComment;
202
201
  }>;
202
+ /**
203
+ * A whole sidebar row. Replaces the default `<CommentListItem>`, which is exported — so a row
204
+ * that only adds an unread dot or a status chip can spread these props into it. Use
205
+ * `ThreadPreview` instead when only the preview text is changing.
206
+ */
207
+ ThreadRow?: ComponentType<CommentListItemRenderProps & {
208
+ thread: TLCommentThread;
209
+ }>;
210
+ /**
211
+ * Extra controls in an open thread's header, added ahead of the built-in resolve and dismiss
212
+ * buttons rather than replacing them. "Copy link" is already built in whenever the host supplies
213
+ * `getThreadHref`.
214
+ */
215
+ ThreadActions?: ComponentType<{
216
+ comments: TLComment[];
217
+ thread: TLCommentThread;
218
+ }>;
203
219
  /**
204
220
  * A reaction's visual, given its token. The default renders the token string for the OS emoji
205
- * font to draw (so the token is the emoji glyph). Override this to render your own palette —
206
- * return an `<img>` for custom emoji, an SVG, or anything. The token is whatever your picker
207
- * emits and is what gets stored/synced; this only controls how it's drawn.
221
+ * font. Override to draw a custom palette — an `<img>`, an SVG, anything. The token is what gets
222
+ * stored and synced; this only controls how it's drawn.
208
223
  */
209
224
  ReactionContent?: ComponentType<{
210
225
  token: string;
211
226
  }>;
212
227
  /**
213
- * What the add-reaction button opens: the thing that produces a reaction token. Replaces the
214
- * default `<EmojiPicker>` grid. Pairs with `ReactionContent` (which draws whatever tokens this
215
- * emits) and with `isAllowedReaction` (which has to accept them).
228
+ * What the add-reaction button opens. Replaces the default `<EmojiPicker>` grid. Pairs with
229
+ * `ReactionContent` (which draws the tokens this emits) and `isAllowedReaction` (which must
230
+ * accept them).
216
231
  */
217
232
  ReactionPalette?: ComponentType<EmojiPickerProps>;
218
233
  /**
219
- * The hover affordance naming who reacted with an emoji. It receives the reactors and the pill
220
- * (as `children`) and returns the whole thing — so it owns the tooltip, its box, size, shape, and
221
- * position. Replaces the default (`DefaultReactionTooltip`). For a simple wording change, translate
222
- * the `comments.reacted-*` strings instead; reach for this to change the structure — a different
223
- * box, avatars, a banner anywhere on screen.
234
+ * The hover affordance naming who reacted with an emoji. Receives the reactors and the pill (as
235
+ * `children`) and owns the whole thing — box, size, shape, position. For a wording change,
236
+ * translate the `comments.reacted-*` strings instead.
224
237
  */
225
238
  ReactionTooltip?: ComponentType<ReactionTooltipProps>;
226
- /** Shown where a composer would sit when the viewer can't compose (see
227
- * {@link CommentingOptions.canComment} — a signed-out viewer, a viewer role, a host that
228
- * turns commenting off). `context` says which surface is rendering it: the bottom of an open
229
- * thread popover (`'thread'`) or the placement popover the comment tool opens (`'pending'`).
230
- * Unset, those surfaces render nothing. */
239
+ /**
240
+ * Shown where a composer would sit when the viewer can't compose (see
241
+ * {@link CommentingOptions.canComment}). `context` is the surface rendering it: an open thread
242
+ * popover (`'thread'`) or the comment tool's placement popover (`'pending'`). Unset, those
243
+ * surfaces render nothing.
244
+ */
231
245
  ComposerFallback?: ComponentType<{
232
246
  context: 'pending' | 'thread';
233
247
  }>;
@@ -261,10 +275,11 @@ export declare interface CommentingContext {
261
275
  /** Whether a comment is unread for the current user (return true for unread). */
262
276
  isCommentUnread?(commentId: TLCommentId): boolean;
263
277
  /**
264
- * Called for each unread comment shown to the user in an open thread popover, so hosts can
265
- * record a read receipt. Needs `isCommentUnread` to know what's unread.
278
+ * Called with every unread comment shown to the user in an open thread popover, batched per
279
+ * report, so hosts can record read receipts without a write per comment. Needs
280
+ * `isCommentUnread` to know what's unread.
266
281
  */
267
- onCommentRead?(commentId: TLCommentId): void;
282
+ onCommentsRead?(commentIds: TLCommentId[]): void;
268
283
  /** Resolve the members matching an `@`-query in the composers (sync or async). */
269
284
  getMentionSuggestions?(query: string): MentionMember[] | Promise<MentionMember[]>;
270
285
  /** Override a mention-picker row's content. */
@@ -294,11 +309,9 @@ export declare interface CommentingContext {
294
309
  */
295
310
  export declare interface CommentingOptions {
296
311
  /**
297
- * How comment mutations (post, reply, edit, resolve, delete) interact with the editor undo
298
- * stack. Defaults to `'ignore'` — comments are deliberately not undoable (see `TLComment`).
299
- * `'record'` is a multiplayer footgun: undoing a delete resurrects a thread a collaborator
300
- * already removed, and undoing a resolve/edit reverts their newer state. Safe only single-player
301
- * or on a non-synced local comment store.
312
+ * How comment mutations interact with the editor undo stack. Defaults to `'ignore'` — comments
313
+ * are deliberately not undoable (see `TLComment`). `'record'` is a multiplayer footgun: undoing
314
+ * a delete resurrects a thread a collaborator already removed. Safe only single-player.
302
315
  */
303
316
  readonly history: TLHistoryBatchOptions['history'];
304
317
  /**
@@ -316,56 +329,77 @@ export declare interface CommentingOptions {
316
329
  */
317
330
  readonly allowMultipleReactions: boolean;
318
331
  /**
319
- * Whether a token may be added as a reaction. Defaults to {@link isAllowedReactionEmoji} against
320
- * the built-in emoji palette, which is what keeps a scripted client from writing junk `emoji`
321
- * values the picker would never offer. Override it alongside a custom `ReactionPalette` so the
322
- * tokens that palette emits get through. Removals aren't checked — a reaction carrying an
323
- * off-palette token must still be clearable.
332
+ * Whether a token may be added as a reaction. Defaults to {@link isAllowedReactionEmoji}, which
333
+ * keeps a scripted client from writing junk values the picker would never offer. Override
334
+ * alongside a custom `ReactionPalette`. Removals aren't checked — an off-palette reaction must
335
+ * still be clearable.
324
336
  */
325
337
  isAllowedReaction(token: string): boolean;
326
338
  /**
327
339
  * Whether dragging the comment tool out creates a region anchor — a comment attached to a
328
- * rectangular area of the page, drawn as a dashed box with the thread's pin on the corner the
329
- * drag released on. Off by default: comments attach to points and shapes only, and a drag just
330
- * trails the composer. A region reveals its box while the pointer is inside it, moves by its
331
- * pin, and resizes from its corners.
340
+ * rectangular area, drawn as a dashed box with the pin on the corner the drag released on. Off
341
+ * by default, where comments attach to points and shapes only and a drag trails the composer.
332
342
  */
333
343
  readonly enableRegions: boolean;
334
344
  /**
335
- * Whether the viewer may participate in commenting: composing new threads and replies, editing
336
- * and deleting comments, resolving threads, and moving pins or regions. Composers render when
337
- * it returns true; when it returns false, the {@link CommentingComponents.ComposerFallback}
338
- * slot renders in their place (or nothing, if that slot is unset) and the action affordances
339
- * are hidden. Unset, participation is allowed exactly when `currentUserId` is set.
345
+ * Whether the viewer may participate in commenting: composing, editing, deleting, resolving, and
346
+ * moving pins. When false, {@link CommentingComponents.ComposerFallback} renders in the
347
+ * composer's place and action affordances are hidden. Unset, participation is allowed exactly
348
+ * when `currentUserId` is set.
340
349
  *
341
- * Called during render via {@link useCanComment}, so reactive reads (signals) are tracked.
342
- * The comment tool itself stays registered and selectable — hosts that want its toolbar button
343
- * to do something else (e.g. open a sign-in dialog) can override the tool item's `onSelect`.
344
- * Note posting still requires a `currentUserId` to author the records, so a callback that
345
- * returns true for a signed-out viewer yields a composer whose send button stays disabled.
350
+ * Called during render via {@link useCanComment}, so signal reads are tracked. Posting still
351
+ * needs a `currentUserId`, so returning true for a signed-out viewer yields a composer whose
352
+ * send button stays disabled. A callback that throws is logged and read as false, rather than
353
+ * taking the comments layer down with it.
346
354
  */
347
355
  readonly canComment: ((ctx: {
348
356
  currentUserId: null | string;
349
357
  editor: Editor;
350
358
  }) => boolean) | undefined;
359
+ /**
360
+ * Whether the viewer may make a particular write against a particular record: editing or
361
+ * deleting a comment, or deleting a thread. Unset, each is its record's owner's to make
362
+ * ({@link defaultCanModifyComment}) — you edit and delete your own comments, and delete threads
363
+ * you started. Override it to widen that (a workspace admin or moderator who may remove
364
+ * anyone's comment) or to narrow it (no edits after an hour). Where it returns false the
365
+ * affordance isn't rendered.
366
+ *
367
+ * Checked after {@link CommentingOptions.canComment}, which gates commenting as a whole: a
368
+ * viewer who may not participate gets no action affordances at all, whatever this returns.
369
+ *
370
+ * Called during render via {@link useCanModifyComment}, so reactive reads (signals) are tracked.
371
+ * A callback that throws is logged and read as false: an affordance is withheld rather than the
372
+ * comments layer lost, and a denial is what a server enforcing the same rule would have said.
373
+ *
374
+ * @example
375
+ * ```tsx
376
+ * CommentTool.configure({
377
+ * canModifyComment: (ctx) =>
378
+ * // Moderators may delete anything; everything else stays the owner's to do.
379
+ * (ctx.action !== 'edit-comment' && isModerator(ctx.currentUserId)) ||
380
+ * defaultCanModifyComment(ctx),
381
+ * })
382
+ * ```
383
+ */
384
+ readonly canModifyComment: ((ctx: CommentModificationContext) => boolean) | undefined;
351
385
  /** Normalized (0–1) spot within a shape where imprecise shape pins sit. Default top-right. */
352
386
  readonly impreciseShapeAnchor: {
353
387
  readonly x: number;
354
388
  readonly y: number;
355
389
  };
356
390
  /**
357
- * Whether a comment landing on a shape anchors precisely — pinned to the exact clicked spot
358
- * within the shape — or imprecisely — pinned to the shape as a whole, rendered at
359
- * `impreciseShapeAnchor`. Called wherever a shape anchor is created (placing with the comment
360
- * tool, dropping a dragged pin onto a shape). Always precise by default. Return `false` for
361
- * shape-level anchoring, or decide from the context — the Alt key's state, or the shape itself,
362
- * e.g. precise only on notes. Governs new placements only; existing anchors render as stored.
391
+ * Whether a comment landing on a shape pins to the exact clicked spot, or to the shape as a
392
+ * whole (rendered at `impreciseShapeAnchor`). Always precise by default; return `false`, or
393
+ * decide from the context. Governs new placements only — existing anchors render as stored.
363
394
  */
364
395
  shouldBePrecise(editor: Editor, context: ShapeCommentPrecisionContext): boolean;
365
396
  /** Component overrides. See {@link CommentingComponents}. */
366
397
  readonly components: CommentingComponents;
367
398
  }
368
399
 
400
+ /** One thread's row in a {@link CommentsList}. @public @react */
401
+ export declare function CommentListItem({ id, author, preview, date, resolved, page, count, selected, reactions, href, resolvedLabel, onSelect }: CommentListItemRenderProps): JSX.Element;
402
+
369
403
  /** @public */
370
404
  export declare interface CommentListItemProps {
371
405
  id: string;
@@ -381,6 +415,9 @@ export declare interface CommentListItemProps {
381
415
  count?: number;
382
416
  /** Whether this thread is the open one. */
383
417
  selected?: boolean;
418
+ /** Tallied reactions for the row (a thread's, or a single comment's when the row is one
419
+ * comment), shown as inert pills under the preview. Omit to hide. */
420
+ reactions?: ReactionSummary[];
384
421
  /**
385
422
  * Link target for the item. When set, the row renders as an anchor so browser affordances
386
423
  * (ctrl/cmd-click, middle-click) open it in a new tab; a plain click still calls `onSelect`.
@@ -388,6 +425,51 @@ export declare interface CommentListItemProps {
388
425
  href?: string;
389
426
  }
390
427
 
428
+ /**
429
+ * What a row is rendered with: the item, plus the list-level wiring it needs to be interactive.
430
+ * A custom row gets the same props the default `<CommentListItem>` does, so it can wrap the
431
+ * default rather than reimplement it.
432
+ *
433
+ * @public
434
+ */
435
+ export declare interface CommentListItemRenderProps extends CommentListItemProps {
436
+ /** Label for a resolved thread's marker on its row. */
437
+ resolvedLabel?: string;
438
+ /** Called with the thread id when the row is chosen. */
439
+ onSelect?(id: string): void;
440
+ }
441
+
442
+ /**
443
+ * A commenting write that belongs to someone in particular, and the record it targets — the
444
+ * argument to {@link CommentingOptions.canModifyComment}.
445
+ *
446
+ * Resolving, reopening, reacting, and moving a pin aren't here: none of them is anyone's in
447
+ * particular, so {@link CommentingOptions.canComment} is the only gate on them.
448
+ *
449
+ * @public
450
+ */
451
+ export declare type CommentModification = {
452
+ readonly action: 'delete-comment';
453
+ readonly comment: TLComment;
454
+ } | {
455
+ readonly action: 'delete-thread';
456
+ readonly thread: TLCommentThread;
457
+ } | {
458
+ readonly action: 'edit-comment';
459
+ readonly comment: TLComment;
460
+ };
461
+
462
+ /**
463
+ * The argument to {@link CommentingOptions.canModifyComment}: which write, against which record,
464
+ * and by whom.
465
+ *
466
+ * @public
467
+ */
468
+ export declare type CommentModificationContext = {
469
+ readonly currentUserId: null | string;
470
+ readonly editor: Editor;
471
+ } & CommentModification;
472
+
391
473
  /** A canvas comment marker: shows its `children` (or a check when resolved). Purely
392
474
  * presentational — it reflects open/resolved state via CSS; wrap it to make it clickable.
393
475
  * @public @react */
@@ -404,9 +486,8 @@ export declare interface CommentPinProps {
404
486
  }
405
487
 
406
488
  /**
407
- * The add-reaction button for one comment. Belongs with the comment card's hover actions (under
408
- * the ⋯ button) rather than in the reaction row, so opening it doesn't chase the row as reactions
409
- * are added.
489
+ * The add-reaction button for one comment. Belongs with the comment card's hover actions rather
490
+ * than in the reaction row, so opening it doesn't chase the row as reactions are added.
410
491
  * @public @react
411
492
  */
412
493
  export declare function CommentReactionPicker({ comment, currentUserId, emoji }: CommentReactionPickerProps): JSX.Element | null;
@@ -433,7 +514,7 @@ export declare interface CommentReactionsProps {
433
514
  /** The reacting user. Null/omitted gives a read-only row (signed out): counts show, but the
434
515
  * pills don't toggle. */
435
516
  currentUserId?: null | string;
436
- /** Names a reactor id for the hover list. Ids it can't name fall back to the id. */
517
+ /** Names a reactor id for the hover list. Ids it can't name fall back to a generic "Someone". */
437
518
  resolveName?(userId: string): string | undefined;
438
519
  }
439
520
 
@@ -477,8 +558,12 @@ export declare interface CommentsListProps {
477
558
  empty?: ReactNode;
478
559
  /** Label for a resolved thread's marker on its row. Defaults to "Resolved". */
479
560
  resolvedLabel?: string;
480
- /** Override how each item renders. Defaults to `<CommentListItem>`. */
481
- renderItem?(item: CommentListItemProps): ReactNode;
561
+ /**
562
+ * Override how each item renders. Defaults to `<CommentListItem>`, which is exported — so a
563
+ * row that only adds something can spread these props into it rather than start over. The list
564
+ * supplies the key, so a custom row doesn't need one.
565
+ */
566
+ renderItem?(props: CommentListItemRenderProps): ReactNode;
482
567
  }
483
568
 
484
569
  /**
@@ -496,10 +581,8 @@ export declare function CommentsMenuItem(): JSX.Element;
496
581
  export declare function CommentsOverflowMenu(): JSX.Element;
497
582
 
498
583
  /**
499
- * Whether the comments sidebar (the thread list) is open. Driven by an explicit control — a button
500
- * next to Share on dotcom — rather than by which tool is active, so browsing threads is separate
501
- * from placing them. The comment tool additionally closes it on enter, keeping placement
502
- * canvas-focused.
584
+ * Whether the comments sidebar (the thread list) is open. Driven by an explicit control rather than
585
+ * by which tool is active, so browsing threads is separate from placing them.
503
586
  * @public
504
587
  */
505
588
  export declare const commentsSidebarOpen: EditorAtom<boolean>;
@@ -537,11 +620,12 @@ export declare interface CommentThreadProps {
537
620
  }
538
621
 
539
622
  /**
540
- * The comment tool. Pressing down opens the comment composer immediately at the pointer, and it
541
- * follows the pointer until release — like placing a sticky note — settling on a point, or on a
542
- * shape when released over one. (With region comments enabled, dragging past the threshold instead
543
- * draws a region rectangle.) Placement only opens a composer (via `pendingComment`); the records
544
- * are created when the comment is posted.
623
+ * The comment tool. Pressing down opens the comment composer at the pointer and it follows until
624
+ * release — like placing a sticky note — settling on a point, or on a shape when released over one.
625
+ * With region comments enabled, dragging past the threshold draws a region rectangle instead.
626
+ * Placement only opens a composer; the records are created when the comment is posted. The tool
627
+ * stays active while the composer is open — posting returns to select, and clicking elsewhere
628
+ * re-places the composer.
545
629
  * @public
546
630
  */
547
631
  export declare class CommentTool extends StateNode {
@@ -572,7 +656,7 @@ export declare class CommentTool extends StateNode {
572
656
  }
573
657
 
574
658
  /** Registers the comment tool in the UI (icon, label, shortcut). Compose into your overrides.
575
- * Once registered, tldraw's `DefaultToolbarContent` shows the comment button next to the eraser.
659
+ * Once registered, tldraw's `DefaultQuickActionsContent` shows the comment button.
576
660
  * @public */
577
661
  export declare const commentToolOverrides: TLUiOverrides;
578
662
 
@@ -607,6 +691,18 @@ export declare const DEFAULT_REACTION_EMOJI: string[];
607
691
  * @public */
608
692
  export declare const DEFAULT_SIDEBAR_FILTERS: SidebarFilters;
609
693
 
694
+ /**
695
+ * The default {@link CommentingOptions.canModifyComment}: a write is its record's owner's to make —
696
+ * a comment's author edits and deletes it, a thread's creator deletes the thread — and a viewer
697
+ * with no identity may make none of them.
698
+ *
699
+ * Exported so a callback can widen the default rather than restate it:
700
+ * `(ctx) => isModerator(ctx.currentUserId) || defaultCanModifyComment(ctx)`.
701
+ *
702
+ * @public
703
+ */
704
+ export declare function defaultCanModifyComment(ctx: CommentModificationContext): boolean;
705
+
610
706
  /**
611
707
  * The default {@link CommentingOptions}. Override via `CommentTool.configure({ ... })`.
612
708
  *
@@ -615,6 +711,7 @@ export declare const DEFAULT_SIDEBAR_FILTERS: SidebarFilters;
615
711
  export declare const defaultCommentingOptions: {
616
712
  readonly allowMultipleReactions: true;
617
713
  readonly canComment: undefined;
714
+ readonly canModifyComment: undefined;
618
715
  readonly components: {};
619
716
  readonly dragHistory: undefined;
620
717
  readonly enableClustering: true;
@@ -654,21 +751,18 @@ export declare function defaultRenderReaction(token: string): ReactNode;
654
751
  /**
655
752
  * Delete a comment.
656
753
  *
657
- * This is a soft delete: it sets the record's `isDeleted` flag rather than removing it, and the
658
- * server prunes the comment and its reactions once the flag is persisted. That way no client ever
659
- * removes records it doesn't own — a reaction belongs to whoever left it — and a server enforcing
660
- * per-record permissions has a write it can check rather than a deletion it can only refuse.
661
- * Deleting is the author's to do; the built-in UI only offers it on your own comments.
754
+ * This is a soft delete: it sets `isDeleted` rather than removing the record, and the server prunes
755
+ * the comment and its reactions once the flag is persisted — so no client removes records it
756
+ * doesn't own, and a server enforcing per-record permissions has a write it can check.
662
757
  *
663
- * The write is never undoable, whatever {@link CommentingOptions.history} says: the flag is
664
- * write-once server-side, so an undo clearing it would be vetoed and rebased rather than bring the
665
- * comment back.
758
+ * Deleting is the author's to do by default; {@link CommentingOptions.canModifyComment} widens
759
+ * that, as does its counterpart on the server.
666
760
  *
667
- * Deleting a thread's last comment leaves the thread with no surface — an empty thread renders
668
- * nothing — and closes it if it's open. The thread record is left for the server to prune, since
669
- * the deleter may not be its creator.
761
+ * Never undoable, whatever {@link CommentingOptions.history} says: the flag is write-once
762
+ * server-side, so an undo clearing it would be vetoed rather than bring the comment back.
670
763
  *
671
- * A comment that's already deleted, or already pruned, is a no-op.
764
+ * Deleting a thread's last comment closes it and leaves the thread record for the server to prune,
765
+ * since the deleter may not be its creator. An already-deleted comment is a no-op.
672
766
  *
673
767
  * @public
674
768
  */
@@ -677,27 +771,23 @@ export declare function deleteComment(editor: Editor, comment: TLComment): void;
677
771
  /**
678
772
  * Delete a thread and, with it, the whole conversation.
679
773
  *
680
- * A soft delete on the same model as {@link deleteComment}: the flag goes on the thread record and
681
- * the server prunes the thread, its comments, and their reactions once it's persisted. Deleting a
682
- * thread is its creator's to do — a server enforcing per-record permissions vetoes anyone else —
683
- * and the write is never undoable.
684
- *
685
- * Closes the thread if it's the open one. A thread that's already pruned is a no-op.
774
+ * A soft delete on the same model as {@link deleteComment}: the server prunes the thread, its
775
+ * comments, and their reactions once the flag is persisted. Deleting a thread is its creator's to
776
+ * do by default ({@link CommentingOptions.canModifyComment}), and the write is never undoable.
777
+ * Closes the thread if it's the open one; a pruned thread is a no-op.
686
778
  *
687
779
  * @public
688
780
  */
689
781
  export declare function deleteThread(editor: Editor, thread: TLCommentThread): void;
690
782
 
691
783
  /**
692
- * Replace a comment's body and stamp it as edited, which is what renders the "(edited)" marker on
693
- * its byline.
784
+ * Replace a comment's body and stamp it as edited, which renders the "(edited)" marker on its
785
+ * byline. Editing is the author's to do by default ({@link CommentingOptions.canModifyComment}),
786
+ * and a server enforcing per-record permissions rejects anyone else's. Widening one end without the
787
+ * other leaves an edit that's offered and then rejected, so widen both.
694
788
  *
695
- * Editing is the author's to do. The built-in UI only offers it on your own comments, and a server
696
- * that enforces per-record permissions rejects anyone else's edit.
697
- *
698
- * The `comment` you pass says which comment to edit; the body lands on the version the store
699
- * currently holds, so a copy you've held on to can't revert a change made since it, and can't
700
- * re-create a comment that's already been removed — editing one of those does nothing.
789
+ * The body lands on the version the store currently holds, so a stale copy can't revert a later
790
+ * change or re-create a removed comment — editing one of those does nothing.
701
791
  *
702
792
  * @example
703
793
  * ```ts
@@ -741,7 +831,14 @@ export { filterMentionMembers }
741
831
  export declare function focusThread(editor: Editor, thread: TLCommentThread): void;
742
832
 
743
833
  /**
744
- * Format an ISO datetime as short relative time ("2 hr. ago", "yesterday", "last wk.").
834
+ * Format an ISO datetime as a full date and time ("Tuesday, July 22, 2025 at 4:44 PM").
835
+ * Locale-aware via Intl.DateTimeFormat.
836
+ * @public
837
+ */
838
+ export declare function formatFullDateTime(iso: string, locale?: string): string;
839
+
840
+ /**
841
+ * Format an ISO datetime as compact relative time ("2h ago", "yesterday", "last wk.").
745
842
  * Locale-aware via Intl.RelativeTimeFormat.
746
843
  * @public
747
844
  */
@@ -749,16 +846,30 @@ export declare function formatRelativeTime(iso: string, locale?: string): string
749
846
 
750
847
  /**
751
848
  * Whether the viewer may participate in commenting, per {@link CommentingOptions.canComment}
752
- * (defaulting to `currentUserId != null` when unset). Where this is false, composers give way to
753
- * the {@link CommentingComponents.ComposerFallback} slot and action affordances are hidden.
849
+ * (defaulting to `currentUserId != null` when unset). A callback that throws denies.
754
850
  *
755
- * This is a plain, untracked read — a `canComment` callback that reads signals is not observed.
756
- * In React, use {@link useCanComment} instead.
851
+ * This is a plain, untracked read — in React, use {@link useCanComment} instead.
757
852
  *
758
853
  * @public
759
854
  */
760
855
  export declare function getCanComment(editor: Editor, currentUserId: null | string | undefined): boolean;
761
856
 
857
+ /**
858
+ * Whether the viewer may make a given write against a given record, per
859
+ * {@link CommentingOptions.canModifyComment} (defaulting to {@link defaultCanModifyComment} when
860
+ * unset). Where this is false the affordance isn't rendered.
861
+ *
862
+ * This is the per-record rule alone: the built-in UI additionally requires
863
+ * {@link CommentingOptions.canComment}, since a viewer who may not participate gets no action
864
+ * affordances at all.
865
+ *
866
+ * A plain, untracked read — a `canModifyComment` callback that reads signals is not observed. In
867
+ * React, use {@link useCanModifyComment} instead.
868
+ *
869
+ * @public
870
+ */
871
+ export declare function getCanModifyComment(editor: Editor, currentUserId: null | string | undefined, modification: CommentModification): boolean;
872
+
762
873
  /**
763
874
  * The merged {@link CommentingOptions} for an editor, read off the registered comment tool (which
764
875
  * carries them via `CommentTool.configure`). Falls back to {@link defaultCommentingOptions} when
@@ -785,9 +896,8 @@ export declare function getCommentRecord(editor: Editor, id: string): TLCommentR
785
896
  export declare function getComments(editor: Editor): TLComment[];
786
897
 
787
898
  /**
788
- * Every comment thread in the store, **including soft-deleted ones** and ones left empty by their
789
- * last comment's delete — records that are still present but awaiting the server's prune, and that
790
- * nothing renders. For the set the UI shows, use {@link getLiveCommentThreads}.
899
+ * Every comment thread in the store, **including soft-deleted and emptied ones** awaiting the
900
+ * server's prune, which nothing renders. For the set the UI shows, use {@link getLiveCommentThreads}.
791
901
  *
792
902
  * Non-reactive; wrap in `useValue`, or use `useCommentThreads`, to react.
793
903
  * @public
@@ -796,8 +906,7 @@ export declare function getCommentThreads(editor: Editor): TLCommentThread[];
796
906
 
797
907
  /**
798
908
  * The comments that should render: not soft-deleted. A deleted record lingers in the store until
799
- * the server prunes it, so this — not {@link getComments} — is what a count or a list of your own
800
- * should be built from.
909
+ * the server prunes it, so build counts and lists from this rather than {@link getComments}.
801
910
  *
802
911
  * Non-reactive; the reactive equivalent is `useComments` (which also sorts oldest first).
803
912
  * @public
@@ -805,10 +914,9 @@ export declare function getCommentThreads(editor: Editor): TLCommentThread[];
805
914
  export declare function getLiveComments(editor: Editor): TLComment[];
806
915
 
807
916
  /**
808
- * The comment threads that should render (pins, sidebar): live — not soft-deleted — and still
809
- * holding at least one live comment. A soft-deleted thread or comment is awaiting the server's
810
- * prune, as is a thread emptied by its last comment's delete; until the prune lands, the emptied
811
- * thread record lingers with no surface.
917
+ * The comment threads that should render (pins, sidebar): not soft-deleted, and still holding at
918
+ * least one live comment. A thread emptied by its last comment's delete lingers with no surface
919
+ * until the server's prune lands.
812
920
  *
813
921
  * Non-reactive; the reactive equivalent is `useCommentThreads`.
814
922
  * @public
@@ -817,12 +925,10 @@ export declare function getLiveCommentThreads(editor: Editor): TLCommentThread[]
817
925
 
818
926
  /**
819
927
  * The id passed to the most recent {@link revealThread} call that `CanvasComments` hasn't served
820
- * yet, or null when there's nothing outstanding. A request also clears when `CanvasComments`
821
- * unmounts, since nothing is left to serve it.
928
+ * yet, or null. A request also clears when `CanvasComments` unmounts.
822
929
  *
823
- * This is a plain, untracked read. In React, use {@link useRevealThreadPending} — but reach for
824
- * this one inside a timer or callback that needs the value as of *now* rather than as of the
825
- * render it closed over.
930
+ * This is a plain, untracked read — in React, use {@link useRevealThreadPending}, unless you need
931
+ * the value as of *now* rather than as of the render you closed over.
826
932
  *
827
933
  * @public
828
934
  */
@@ -861,13 +967,11 @@ export { MentionSuggestionOptions }
861
967
  export declare const openThreadId: EditorAtom<null | string>;
862
968
 
863
969
  /**
864
- * Write comment records to the store, under the configured
865
- * {@link CommentingOptions.history} behavior — so a record you write lands on the undo stack (or
866
- * doesn't) exactly like one the built-in UI writes. Defaults to `'ignore'`.
970
+ * Write comment records to the store, under the configured {@link CommentingOptions.history}
971
+ * behavior. Defaults to `'ignore'`.
867
972
  *
868
- * Use it to seed or import threads, and to save an edit. To delete, prefer
869
- * {@link deleteComment} and {@link deleteThread} over {@link removeCommentRecords}: comments are
870
- * soft-deleted, and a synced server rejects the hard delete.
973
+ * Use it to seed or import threads, and to save an edit. To delete, prefer {@link deleteComment}
974
+ * and {@link deleteThread}: comments are soft-deleted, and a synced server rejects the hard delete.
871
975
  *
872
976
  * @public
873
977
  */
@@ -990,6 +1094,19 @@ export declare interface ReactionSummary {
990
1094
  reactors: ReactionReactor[];
991
1095
  }
992
1096
 
1097
+ /**
1098
+ * The reaction fields {@link summarizeReactions} needs — a structural subset of
1099
+ * {@link tldraw#TLCommentReaction}, so tallies can also be built from rows synced outside the
1100
+ * editor store.
1101
+ *
1102
+ * @public
1103
+ */
1104
+ export declare interface ReactionSummaryInput {
1105
+ userId: string;
1106
+ emoji: string;
1107
+ createdAt: number;
1108
+ }
1109
+
993
1110
  /** @public */
994
1111
  export declare interface ReactionTooltipProps {
995
1112
  /** Who reacted with this emoji, in reaction order. */
@@ -1001,35 +1118,26 @@ export declare interface ReactionTooltipProps {
1001
1118
  /**
1002
1119
  * Keep shape-anchored threads alive across their shape's lifecycle:
1003
1120
  *
1004
- * - When the shape is deleted, the thread converts to a `point` anchor at the spot its pin last
1005
- * occupied, so the conversation outlives the shape instead of becoming invisible (a missing
1006
- * shape has no bounds, which hides the pin).
1121
+ * - When the shape is deleted, the thread converts to a `point` anchor where its pin last sat, so
1122
+ * the conversation outlives the shape instead of becoming invisible.
1007
1123
  * - When the shape moves to another page, the thread follows: its `pageId` and each comment's
1008
- * denormalized `pageId` update to the shape's new page, and the anchor keeps riding the shape.
1009
- * - When a deleted shape comes back (a page move re-creates it with its id preserved; undoing a
1010
- * delete restores it), the thread re-attaches — unless its pin was manually moved in the
1011
- * meantime, in which case the manual placement wins.
1124
+ * denormalized `pageId` update, and the anchor keeps riding the shape.
1125
+ * - When a deleted shape comes back, the thread re-attaches — unless its pin was manually moved in
1126
+ * the meantime, in which case the manual placement wins.
1012
1127
  *
1013
1128
  * A page move is `deleteShapes` + re-create with preserved ids inside one `editor.run`, but each
1014
- * store write is its own operation — so at no single moment does "this shape is being moved"
1015
- * exist as an observable event. The handlers therefore cooperate across operations:
1016
- * `beforeDelete` snapshots where each affected pin sits (every record is still in the store at
1017
- * that point, so page bounds resolve even for children of a deleted frame); the
1018
- * operation-complete pass converts threads whose shape is really gone, remembering the original
1019
- * anchor; `afterCreate` notes a reappeared shape id, and the operation-complete pass restores
1020
- * the anchor once the store has settled (creation order within an operation is arbitrary, so a
1021
- * fresh shape may not resolve an ancestor page mid-operation). Undo/redo of a move
1022
- * replays as a `parentId` update (remove + add of one id squash to an update in the history
1023
- * diff), so an `afterChange` handler re-homes threads on cross-page reparents — including
1024
- * threads anchored to descendants, which move with their parent without change events of their
1025
- * own.
1026
- *
1027
- * Remote changes are ignored: the client that performed the operation runs this same
1028
- * maintenance and syncs the resulting thread updates. Writes honour the
1029
- * {@link CommentingOptions.history} option, so the consumer decides whether they're undoable.
1030
- *
1031
- * Registered by `CanvasComments` on mount; parts-built consumers can call this directly.
1032
- * Returns a cleanup function that unregisters all handlers.
1129
+ * store write is its own operation, so "this shape is being moved" is never observable as a single
1130
+ * event. The handlers therefore cooperate across operations: `beforeDelete` snapshots where each
1131
+ * affected pin sits, the operation-complete pass converts threads whose shape is really gone, and
1132
+ * `afterCreate` plus that same pass restore the anchor once the store has settled. Undo/redo of a
1133
+ * move replays as a `parentId` update, so `afterChange` re-homes threads on cross-page reparents —
1134
+ * including threads anchored to descendants, which move without change events of their own.
1135
+ *
1136
+ * Remote changes are ignored: the client that performed the operation runs this same maintenance
1137
+ * and syncs the result. Writes honour the {@link CommentingOptions.history} option.
1138
+ *
1139
+ * Registered by `CanvasComments` on mount; parts-built consumers can call this directly. Returns a
1140
+ * cleanup function that unregisters all handlers.
1033
1141
  *
1034
1142
  * @public
1035
1143
  */
@@ -1039,12 +1147,10 @@ export declare function registerCommentAnchorLifecycle(editor: Editor): () => vo
1039
1147
  * Remove comment records from the store by id, under the configured
1040
1148
  * {@link CommentingOptions.history} behavior.
1041
1149
  *
1042
- * This is a hard delete, which is rarely what you want for a comment or a thread: the built-in UI
1043
- * soft-deletes them ({@link deleteComment}, {@link deleteThread}) so the server can prune the
1044
- * records — including reactions, which belong to whoever left them rather than to the deleter. A
1045
- * server that enforces per-record permissions will veto a hard delete outright. Reach for this on
1046
- * a local, unsynced comment store, or to drop a reaction (which is a hard delete — see
1047
- * {@link toggleCommentReaction}).
1150
+ * This is a hard delete, which is rarely what you want: the built-in UI soft-deletes
1151
+ * ({@link deleteComment}, {@link deleteThread}) so the server can prune the records, and a server
1152
+ * enforcing per-record permissions vetoes a hard delete outright. Reach for this on a local,
1153
+ * unsynced store, or to drop a reaction (see {@link toggleCommentReaction}).
1048
1154
  *
1049
1155
  * @public
1050
1156
  */
@@ -1065,21 +1171,20 @@ export declare function reopenThread(editor: Editor, thread: TLCommentThread): v
1065
1171
  * Mark a thread resolved, stamping who resolved it and when. Resolved threads keep their pin (a
1066
1172
  * checked one) and are hidden from the sidebar until its "show resolved" filter is on.
1067
1173
  *
1068
- * Only the resolution is written: the rest of the thread is read fresh, so resolving with a stale
1069
- * copy in hand won't drag a pin back to where it used to be. A no-op on a thread that's gone.
1174
+ * Only the resolution is written — the rest of the thread is read fresh, so a stale copy can't drag
1175
+ * a pin back. A no-op on a thread that's gone.
1070
1176
  *
1071
1177
  * @public
1072
1178
  */
1073
1179
  export declare function resolveThread(editor: Editor, thread: TLCommentThread, userId: string): void;
1074
1180
 
1075
1181
  /**
1076
- * Open a thread and bring it into view, given a thread id or the id of any comment in it. Use it
1077
- * to jump to a thread from outside the canvas — a notification, a deep link, your own list.
1182
+ * Open a thread and bring it into view, given a thread id or the id of any comment in it. Use it to
1183
+ * jump to a thread from outside the canvas — a notification, a deep link, your own list.
1078
1184
  *
1079
- * The request is served by `CanvasComments`, so it works before the records have arrived: the
1080
- * layer waits for them to sync in, switches pages if it needs to, unhides pins, zooms in far
1081
- * enough to split the thread out of any cluster it's folded into, and then opens it. That also
1082
- * means nothing happens if `CanvasComments` isn't mounted.
1185
+ * The request is served by `CanvasComments`, so it works before the records have arrived: the layer
1186
+ * waits for them, switches pages, unhides pins, and zooms far enough to split the thread out of any
1187
+ * cluster. That also means nothing happens if `CanvasComments` isn't mounted.
1083
1188
  *
1084
1189
  * To open a thread you already hold and skip the wait, see {@link focusThread}.
1085
1190
  *
@@ -1113,11 +1218,9 @@ export declare interface SendButtonProps {
1113
1218
 
1114
1219
  /**
1115
1220
  * A shape anchor for a page point. `x`/`y` are the point's normalized (0–1) offset within the
1116
- * shape's own bounds — taken in the shape's own space, so a pin placed on a rotated shape records
1117
- * the spot it was dropped on rather than a spot in the bounding box. Remembered either way: when
1118
- * `precise` the pin sits at exactly `x`/`y`; otherwise it sits at the consumer's imprecise default
1119
- * (top-right out of the box). Placement gestures get `precise` from the `shouldBePrecise`
1120
- * commenting option (always precise, by default).
1221
+ * shape's own bounds, taken in the shape's own space, so a pin on a rotated shape records the spot
1222
+ * it was dropped on. Remembered either way: when `precise` the pin sits at exactly `x`/`y`,
1223
+ * otherwise at the consumer's imprecise default.
1121
1224
  * @public
1122
1225
  */
1123
1226
  export declare function shapeAnchorAt(editor: Editor, shapeId: TLShapeId, page: {
@@ -1160,29 +1263,45 @@ export declare interface SidebarFilters {
1160
1263
  * @public */
1161
1264
  export declare const sidebarFilters: EditorAtom<SidebarFilters>;
1162
1265
 
1266
+ /** A list row paired with the sort key that isn't part of what the row displays. @public */
1267
+ export declare interface SidebarRow {
1268
+ item: CommentListItemProps;
1269
+ /** When the thread's most recent comment was posted — what the list orders by. */
1270
+ lastActivity: number;
1271
+ }
1272
+
1163
1273
  /**
1164
- * Tally a comment's reactions into an entry per emoji, ordered by when that emoji was first used
1165
- * so the row stays stable as later reactions arrive. `active` marks the emoji the current user
1166
- * reacted with (the pills render those highlighted), and `reactors` lists who reacted with it, in
1167
- * reaction order, for the hover list. `resolveName` names each reactor; an id it can't name falls
1168
- * back to the id itself.
1274
+ * Order the list: unresolved threads first, then by most recent activity, id as a stable tiebreak.
1275
+ * Recency is the thread's *latest* comment, not its first, so a thread someone just replied to rises
1276
+ * to the top instead of staying wherever it was started. (The row still shows the thread's opening
1277
+ * comment and its date — that's what identifies the thread; only the ordering follows the replies.)
1278
+ *
1279
+ * Exported so a hand-built list can match the sidebar's ordering instead of re-deriving it.
1169
1280
  *
1170
1281
  * @public
1171
1282
  */
1172
- export declare function summarizeReactions(reactions: TLCommentReaction[], currentUserId?: null | string, resolveName?: (userId: string) => string | undefined): ReactionSummary[];
1283
+ export declare function sortSidebarRows(rows: readonly SidebarRow[]): readonly SidebarRow[];
1284
+
1285
+ /**
1286
+ * Tally a comment's reactions into an entry per emoji, ordered by when that emoji was first used so
1287
+ * the row stays stable as later reactions arrive. `active` marks the current user's emoji and
1288
+ * `reactors` lists who reacted, in reaction order. `resolveName` names each reactor; an id it can't
1289
+ * name falls back to a generic "Someone", never the raw user id.
1290
+ *
1291
+ * @public
1292
+ */
1293
+ export declare function summarizeReactions(reactions: readonly ReactionSummaryInput[], currentUserId?: null | string, resolveName?: (userId: string) => string | undefined): ReactionSummary[];
1173
1294
 
1174
1295
  /**
1175
1296
  * Typed reads of comment records on the editor store.
1176
1297
  *
1177
- * Comment threads and comments live on the editor's local store so the canvas can render them
1178
- * reactively, but they are opt-in records that aren't part of the `TLRecord` union (they ride the
1179
- * sync server's object-store lane on the wire — see `TLCommentThread`). `editor.store` is therefore
1180
- * statically typed `Store<TLRecord>` and doesn't know about them, so every access has to reinterpret
1181
- * the type. These helpers own that reinterpretation — an `unknown` hop to exactly the type the store
1182
- * expects, so the rest of each call stays checked — behind one boundary, and keep call sites typed.
1298
+ * Comment records live on the editor's local store so the canvas can render them reactively, but
1299
+ * they're opt-in and aren't part of the `TLRecord` union — so `editor.store` is statically typed
1300
+ * `Store<TLRecord>` and every access has to reinterpret the type. These helpers own that
1301
+ * reinterpretation behind one boundary and keep call sites typed.
1183
1302
  *
1184
- * Writes do the same reinterpretation, but they also answer to the undo/redo policy, so they live
1185
- * with the rest of the write path in `comment-mutations.ts`.
1303
+ * Writes do the same, but also answer to the undo/redo policy, so they live in
1304
+ * `comment-mutations.ts`.
1186
1305
  */
1187
1306
  /**
1188
1307
  * A record that lives in a comment thread: the thread itself, one of its messages, or a reaction
@@ -1194,14 +1313,12 @@ export declare type TLCommentRecord = TLComment | TLCommentReaction | TLCommentT
1194
1313
  /**
1195
1314
  * Toggle one user's reaction with a given emoji on a comment.
1196
1315
  *
1197
- * Each reaction is its own record keyed by (comment, user, emoji), so this only ever touches that
1198
- * one user's own records — two people reacting at once never conflict. Behaviour depends on the
1199
- * `allowMultipleReactions` option:
1316
+ * Each reaction is its own record keyed by (comment, user, emoji), so this only touches that user's
1317
+ * own records and two people reacting at once never conflict. Behaviour depends on
1318
+ * `allowMultipleReactions`:
1200
1319
  *
1201
- * - **multiple** (default): the emoji toggles independently — add it if absent, remove it if
1202
- * present, leaving the user's other reactions alone.
1203
- * - **single**: picking a new emoji first removes the user's existing reaction(s) on the comment,
1204
- * then adds this one (a replace); picking the one they already have removes it.
1320
+ * - **multiple** (default): the emoji toggles independently, leaving other reactions alone.
1321
+ * - **single**: a new emoji replaces the user's existing reaction; the same one removes it.
1205
1322
  *
1206
1323
  * @public
1207
1324
  */
@@ -1223,6 +1340,14 @@ export declare function toggleCommentsSidebar(editor: Editor): void;
1223
1340
  */
1224
1341
  export declare function useCanComment(currentUserId: null | string | undefined): boolean;
1225
1342
 
1343
+ /**
1344
+ * Reactive React hook for {@link getCanModifyComment}: a `canModifyComment` callback that reads
1345
+ * signals re-evaluates when they change.
1346
+ *
1347
+ * @public
1348
+ */
1349
+ export declare function useCanModifyComment(currentUserId: null | string | undefined, modification: CommentModification): boolean;
1350
+
1226
1351
  /**
1227
1352
  * Whether commenting is licensed for this editor. Enabled in development; in production it requires
1228
1353
  * a tldraw license that includes the commenting feature (or the collaboration umbrella that grants
@@ -1230,7 +1355,7 @@ export declare function useCanComment(currentUserId: null | string | undefined):
1230
1355
  * pending, so gated UI stays hidden until the license is confirmed.
1231
1356
  *
1232
1357
  * The built-in commenting components (`CanvasComments`, `CanvasCommentsSidebar`, and the comment
1233
- * tool's toolbar button) gate on this. Use it to gate any custom commenting UI the same way.
1358
+ * tool's Quick Action) gate on this. Use it to gate any custom commenting UI the same way.
1234
1359
  * @public
1235
1360
  */
1236
1361
  export declare function useCommentingEnabled(): boolean;
@@ -1278,11 +1403,9 @@ export declare function useOpenThreadId(): null | string;
1278
1403
  /**
1279
1404
  * Reactive React hook for {@link getRevealThreadPending}.
1280
1405
  *
1281
- * Use it to notice a reveal that never lands — most often a deep link to a comment that has since
1282
- * been deleted. Give it a grace period before you act: a request also sits here while its records
1283
- * are still syncing in, which is the normal case on a cold load. Re-check with
1284
- * {@link getRevealThreadPending} when the grace period elapses, since the request can clear inside
1285
- * it without this hook's value having caught up yet.
1406
+ * Use it to notice a reveal that never lands — usually a deep link to a deleted comment. Give it a
1407
+ * grace period first, since a request also sits here while its records sync in, and re-check with
1408
+ * {@link getRevealThreadPending} when it elapses.
1286
1409
  *
1287
1410
  * @public
1288
1411
  */