@tldraw/commenting 0.0.0-bootstrap → 5.3.0-canary.31036673d3f8

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 (279) hide show
  1. package/README.md +0 -2
  2. package/commenting.css +1139 -329
  3. package/dist-cjs/canvas/anchor-lifecycle.js +148 -0
  4. package/dist-cjs/canvas/anchor-lifecycle.js.map +7 -0
  5. package/dist-cjs/canvas/cluster-input.js +2 -2
  6. package/dist-cjs/canvas/cluster-input.js.map +2 -2
  7. package/dist-cjs/canvas/comment-body.js +1 -1
  8. package/dist-cjs/canvas/comment-body.js.map +2 -2
  9. package/dist-cjs/canvas/comment-drafts.js +54 -0
  10. package/dist-cjs/canvas/comment-drafts.js.map +7 -0
  11. package/dist-cjs/canvas/comment-mutations.js +123 -0
  12. package/dist-cjs/canvas/comment-mutations.js.map +7 -0
  13. package/dist-cjs/canvas/comment-reactions.js +163 -0
  14. package/dist-cjs/canvas/comment-reactions.js.map +7 -0
  15. package/dist-cjs/canvas/comment-render.js +5 -3
  16. package/dist-cjs/canvas/comment-render.js.map +2 -2
  17. package/dist-cjs/canvas/comment-store.js +19 -9
  18. package/dist-cjs/canvas/comment-store.js.map +2 -2
  19. package/dist-cjs/canvas/comment-tool.js +64 -14
  20. package/dist-cjs/canvas/comment-tool.js.map +2 -2
  21. package/dist-cjs/canvas/comments-filter-menu.js +20 -38
  22. package/dist-cjs/canvas/comments-filter-menu.js.map +2 -2
  23. package/dist-cjs/canvas/comments-overflow-menu.js +22 -27
  24. package/dist-cjs/canvas/comments-overflow-menu.js.map +2 -2
  25. package/dist-cjs/canvas/comments-overlay.js +429 -474
  26. package/dist-cjs/canvas/comments-overlay.js.map +2 -2
  27. package/dist-cjs/canvas/comments-sidebar.js +35 -32
  28. package/dist-cjs/canvas/comments-sidebar.js.map +2 -2
  29. package/dist-cjs/canvas/comments-visibility-toggle.js +65 -0
  30. package/dist-cjs/canvas/comments-visibility-toggle.js.map +7 -0
  31. package/dist-cjs/canvas/context.js +17 -0
  32. package/dist-cjs/canvas/context.js.map +7 -0
  33. package/dist-cjs/canvas/hooks.js +22 -3
  34. package/dist-cjs/canvas/hooks.js.map +2 -2
  35. package/dist-cjs/canvas/options.js +19 -2
  36. package/dist-cjs/canvas/options.js.map +2 -2
  37. package/dist-cjs/canvas/pin-stacking.js +56 -0
  38. package/dist-cjs/canvas/pin-stacking.js.map +7 -0
  39. package/dist-cjs/canvas/sidebar-filters.js +2 -2
  40. package/dist-cjs/canvas/sidebar-filters.js.map +2 -2
  41. package/dist-cjs/canvas/state.js +28 -14
  42. package/dist-cjs/canvas/state.js.map +2 -2
  43. package/dist-cjs/canvas/thread-preview.js +185 -0
  44. package/dist-cjs/canvas/thread-preview.js.map +7 -0
  45. package/dist-cjs/canvas/thread-stack.js +199 -0
  46. package/dist-cjs/canvas/thread-stack.js.map +7 -0
  47. package/dist-cjs/canvas/thread-state.js +67 -21
  48. package/dist-cjs/canvas/thread-state.js.map +2 -2
  49. package/dist-cjs/canvas/thread-view.js +397 -0
  50. package/dist-cjs/canvas/thread-view.js.map +7 -0
  51. package/dist-cjs/clustering/computeClusterTable.js +4 -4
  52. package/dist-cjs/clustering/computeClusterTable.js.map +2 -2
  53. package/dist-cjs/clustering/runtime.js +119 -7
  54. package/dist-cjs/clustering/runtime.js.map +2 -2
  55. package/dist-cjs/clustering/types.js.map +1 -1
  56. package/dist-cjs/index.d.ts +763 -333
  57. package/dist-cjs/index.js +46 -28
  58. package/dist-cjs/index.js.map +2 -2
  59. package/dist-cjs/ui/byline.js +6 -4
  60. package/dist-cjs/ui/byline.js.map +2 -2
  61. package/dist-cjs/ui/comment-card.js +15 -8
  62. package/dist-cjs/ui/comment-card.js.map +2 -2
  63. package/dist-cjs/ui/comment-composer.js +137 -36
  64. package/dist-cjs/ui/comment-composer.js.map +2 -2
  65. package/dist-cjs/ui/comment-pin.js +24 -2
  66. package/dist-cjs/ui/comment-pin.js.map +2 -2
  67. package/dist-cjs/ui/comment-thread.js +9 -7
  68. package/dist-cjs/ui/comment-thread.js.map +2 -2
  69. package/dist-cjs/ui/comments-list.js +30 -18
  70. package/dist-cjs/ui/comments-list.js.map +2 -2
  71. package/dist-cjs/ui/count-badge.js +3 -2
  72. package/dist-cjs/ui/count-badge.js.map +2 -2
  73. package/dist-cjs/ui/emoji-picker.js +54 -0
  74. package/dist-cjs/ui/emoji-picker.js.map +7 -0
  75. package/dist-cjs/ui/empty-state.js +3 -3
  76. package/dist-cjs/ui/empty-state.js.map +2 -2
  77. package/dist-cjs/ui/format-time.js +4 -1
  78. package/dist-cjs/ui/format-time.js.map +2 -2
  79. package/dist-cjs/ui/reaction-picker.js +73 -0
  80. package/dist-cjs/ui/reaction-picker.js.map +7 -0
  81. package/dist-cjs/ui/reaction.js +59 -6
  82. package/dist-cjs/ui/reaction.js.map +2 -2
  83. package/dist-cjs/ui/reactions.js +20 -7
  84. package/dist-cjs/ui/reactions.js.map +2 -2
  85. package/dist-cjs/ui/{mention.js → reply-count.js} +9 -11
  86. package/dist-cjs/ui/reply-count.js.map +7 -0
  87. package/dist-cjs/ui/send-button.js +20 -1
  88. package/dist-cjs/ui/send-button.js.map +2 -2
  89. package/dist-cjs/ui/{comment-text.js → tooltip-button.js} +12 -9
  90. package/dist-cjs/ui/tooltip-button.js.map +7 -0
  91. package/dist-esm/canvas/anchor-lifecycle.mjs +128 -0
  92. package/dist-esm/canvas/anchor-lifecycle.mjs.map +7 -0
  93. package/dist-esm/canvas/cluster-input.mjs +2 -2
  94. package/dist-esm/canvas/cluster-input.mjs.map +2 -2
  95. package/dist-esm/canvas/comment-body.mjs +1 -1
  96. package/dist-esm/canvas/comment-body.mjs.map +2 -2
  97. package/dist-esm/canvas/comment-drafts.mjs +34 -0
  98. package/dist-esm/canvas/comment-drafts.mjs.map +7 -0
  99. package/dist-esm/canvas/comment-mutations.mjs +103 -0
  100. package/dist-esm/canvas/comment-mutations.mjs.map +7 -0
  101. package/dist-esm/canvas/comment-reactions.mjs +152 -0
  102. package/dist-esm/canvas/comment-reactions.mjs.map +7 -0
  103. package/dist-esm/canvas/comment-render.mjs +5 -3
  104. package/dist-esm/canvas/comment-render.mjs.map +2 -2
  105. package/dist-esm/canvas/comment-store.mjs +19 -9
  106. package/dist-esm/canvas/comment-store.mjs.map +2 -2
  107. package/dist-esm/canvas/comment-tool.mjs +67 -17
  108. package/dist-esm/canvas/comment-tool.mjs.map +2 -2
  109. package/dist-esm/canvas/comments-filter-menu.mjs +20 -38
  110. package/dist-esm/canvas/comments-filter-menu.mjs.map +2 -2
  111. package/dist-esm/canvas/comments-overflow-menu.mjs +23 -27
  112. package/dist-esm/canvas/comments-overflow-menu.mjs.map +2 -2
  113. package/dist-esm/canvas/comments-overlay.mjs +450 -479
  114. package/dist-esm/canvas/comments-overlay.mjs.map +3 -3
  115. package/dist-esm/canvas/comments-sidebar.mjs +38 -35
  116. package/dist-esm/canvas/comments-sidebar.mjs.map +2 -2
  117. package/dist-esm/canvas/comments-visibility-toggle.mjs +45 -0
  118. package/dist-esm/canvas/comments-visibility-toggle.mjs.map +7 -0
  119. package/dist-esm/canvas/context.mjs +1 -0
  120. package/dist-esm/canvas/context.mjs.map +7 -0
  121. package/dist-esm/canvas/hooks.mjs +28 -5
  122. package/dist-esm/canvas/hooks.mjs.map +2 -2
  123. package/dist-esm/canvas/options.mjs +21 -3
  124. package/dist-esm/canvas/options.mjs.map +2 -2
  125. package/dist-esm/canvas/pin-stacking.mjs +36 -0
  126. package/dist-esm/canvas/pin-stacking.mjs.map +7 -0
  127. package/dist-esm/canvas/sidebar-filters.mjs +2 -2
  128. package/dist-esm/canvas/sidebar-filters.mjs.map +2 -2
  129. package/dist-esm/canvas/state.mjs +29 -19
  130. package/dist-esm/canvas/state.mjs.map +2 -2
  131. package/dist-esm/canvas/thread-preview.mjs +170 -0
  132. package/dist-esm/canvas/thread-preview.mjs.map +7 -0
  133. package/dist-esm/canvas/thread-stack.mjs +190 -0
  134. package/dist-esm/canvas/thread-stack.mjs.map +7 -0
  135. package/dist-esm/canvas/thread-state.mjs +71 -22
  136. package/dist-esm/canvas/thread-state.mjs.map +2 -2
  137. package/dist-esm/canvas/thread-view.mjs +402 -0
  138. package/dist-esm/canvas/thread-view.mjs.map +7 -0
  139. package/dist-esm/clustering/computeClusterTable.mjs +4 -4
  140. package/dist-esm/clustering/computeClusterTable.mjs.map +2 -2
  141. package/dist-esm/clustering/runtime.mjs +119 -7
  142. package/dist-esm/clustering/runtime.mjs.map +2 -2
  143. package/dist-esm/index.d.mts +763 -333
  144. package/dist-esm/index.mjs +87 -41
  145. package/dist-esm/index.mjs.map +2 -2
  146. package/dist-esm/ui/byline.mjs +6 -4
  147. package/dist-esm/ui/byline.mjs.map +2 -2
  148. package/dist-esm/ui/comment-card.mjs +15 -8
  149. package/dist-esm/ui/comment-card.mjs.map +2 -2
  150. package/dist-esm/ui/comment-composer.mjs +148 -36
  151. package/dist-esm/ui/comment-composer.mjs.map +2 -2
  152. package/dist-esm/ui/comment-pin.mjs +24 -2
  153. package/dist-esm/ui/comment-pin.mjs.map +2 -2
  154. package/dist-esm/ui/comment-thread.mjs +9 -7
  155. package/dist-esm/ui/comment-thread.mjs.map +2 -2
  156. package/dist-esm/ui/comments-list.mjs +43 -29
  157. package/dist-esm/ui/comments-list.mjs.map +2 -2
  158. package/dist-esm/ui/count-badge.mjs +3 -2
  159. package/dist-esm/ui/count-badge.mjs.map +2 -2
  160. package/dist-esm/ui/emoji-picker.mjs +34 -0
  161. package/dist-esm/ui/emoji-picker.mjs.map +7 -0
  162. package/dist-esm/ui/empty-state.mjs +3 -3
  163. package/dist-esm/ui/empty-state.mjs.map +2 -2
  164. package/dist-esm/ui/format-time.mjs +4 -1
  165. package/dist-esm/ui/format-time.mjs.map +2 -2
  166. package/dist-esm/ui/reaction-picker.mjs +60 -0
  167. package/dist-esm/ui/reaction-picker.mjs.map +7 -0
  168. package/dist-esm/ui/reaction.mjs +60 -7
  169. package/dist-esm/ui/reaction.mjs.map +2 -2
  170. package/dist-esm/ui/reactions.mjs +21 -8
  171. package/dist-esm/ui/reactions.mjs.map +2 -2
  172. package/dist-esm/ui/reply-count.mjs +9 -0
  173. package/dist-esm/ui/reply-count.mjs.map +7 -0
  174. package/dist-esm/ui/send-button.mjs +20 -1
  175. package/dist-esm/ui/send-button.mjs.map +2 -2
  176. package/dist-esm/ui/tooltip-button.mjs +12 -0
  177. package/dist-esm/ui/tooltip-button.mjs.map +7 -0
  178. package/package.json +6 -6
  179. package/src/canvas/anchor-lifecycle.test.ts +290 -0
  180. package/src/canvas/anchor-lifecycle.ts +205 -0
  181. package/src/canvas/canvas.css +461 -46
  182. package/src/canvas/cluster-input.test.ts +31 -23
  183. package/src/canvas/cluster-input.ts +3 -4
  184. package/src/canvas/comment-body.tsx +1 -1
  185. package/src/canvas/comment-drafts.test.ts +55 -0
  186. package/src/canvas/comment-drafts.ts +46 -0
  187. package/src/canvas/comment-mutations.test.ts +410 -0
  188. package/src/canvas/comment-mutations.ts +288 -0
  189. package/src/canvas/comment-reactions.test.ts +132 -0
  190. package/src/canvas/comment-reactions.tsx +245 -0
  191. package/src/canvas/comment-render.test.ts +1 -1
  192. package/src/canvas/comment-render.ts +9 -5
  193. package/src/canvas/comment-store.ts +67 -27
  194. package/src/canvas/comment-tool.test.ts +162 -0
  195. package/src/canvas/comment-tool.tsx +105 -23
  196. package/src/canvas/comments-filter-menu.tsx +23 -32
  197. package/src/canvas/comments-overflow-menu.tsx +14 -21
  198. package/src/canvas/comments-overlay.tsx +695 -622
  199. package/src/canvas/comments-sidebar.test.ts +45 -0
  200. package/src/canvas/comments-sidebar.tsx +76 -58
  201. package/src/canvas/comments-visibility-toggle.tsx +49 -0
  202. package/src/canvas/context.ts +47 -0
  203. package/src/canvas/hooks.ts +52 -11
  204. package/src/canvas/options.test.ts +52 -2
  205. package/src/canvas/options.ts +133 -9
  206. package/src/canvas/pin-stacking.test.ts +114 -0
  207. package/src/canvas/pin-stacking.ts +57 -0
  208. package/src/canvas/sidebar-filters.ts +4 -3
  209. package/src/canvas/state.ts +92 -35
  210. package/src/canvas/thread-preview.test.ts +91 -0
  211. package/src/canvas/thread-preview.tsx +304 -0
  212. package/src/canvas/thread-stack.tsx +252 -0
  213. package/src/canvas/thread-state.test.ts +209 -0
  214. package/src/canvas/thread-state.ts +137 -36
  215. package/src/canvas/thread-view.tsx +516 -0
  216. package/src/clustering/computeClusterTable.test.ts +24 -23
  217. package/src/clustering/computeClusterTable.ts +5 -5
  218. package/src/clustering/runtime.test.ts +217 -7
  219. package/src/clustering/runtime.ts +171 -17
  220. package/src/clustering/types.ts +9 -9
  221. package/src/index.ts +76 -40
  222. package/src/ui/byline.tsx +7 -5
  223. package/src/ui/comment-card.tsx +20 -10
  224. package/src/ui/comment-composer.tsx +206 -40
  225. package/src/ui/comment-pin.tsx +26 -2
  226. package/src/ui/comment-thread.tsx +13 -6
  227. package/src/ui/comments-list.test.ts +31 -0
  228. package/src/ui/comments-list.tsx +45 -21
  229. package/src/ui/comments.css +566 -281
  230. package/src/ui/count-badge.tsx +9 -2
  231. package/src/ui/emoji-picker.test.ts +25 -0
  232. package/src/ui/emoji-picker.tsx +76 -0
  233. package/src/ui/empty-state.tsx +3 -3
  234. package/src/ui/format-time.ts +7 -2
  235. package/src/ui/reaction-picker.tsx +109 -0
  236. package/src/ui/reaction.tsx +121 -6
  237. package/src/ui/reactions.tsx +69 -10
  238. package/src/ui/reply-count.ts +16 -0
  239. package/src/ui/send-button.tsx +18 -3
  240. package/src/ui/tooltip-button.tsx +20 -0
  241. package/dist-cjs/canvas/region-options.js +0 -49
  242. package/dist-cjs/canvas/region-options.js.map +0 -7
  243. package/dist-cjs/ui/avatar.js +0 -43
  244. package/dist-cjs/ui/avatar.js.map +0 -7
  245. package/dist-cjs/ui/comment-mention.js +0 -42
  246. package/dist-cjs/ui/comment-mention.js.map +0 -7
  247. package/dist-cjs/ui/comment-text.js.map +0 -7
  248. package/dist-cjs/ui/mention-list.js +0 -70
  249. package/dist-cjs/ui/mention-list.js.map +0 -7
  250. package/dist-cjs/ui/mention-suggestion.js +0 -206
  251. package/dist-cjs/ui/mention-suggestion.js.map +0 -7
  252. package/dist-cjs/ui/mention.js.map +0 -7
  253. package/dist-cjs/ui/render-markdown.js +0 -63
  254. package/dist-cjs/ui/render-markdown.js.map +0 -7
  255. package/dist-esm/canvas/region-options.mjs +0 -29
  256. package/dist-esm/canvas/region-options.mjs.map +0 -7
  257. package/dist-esm/ui/avatar.mjs +0 -23
  258. package/dist-esm/ui/avatar.mjs.map +0 -7
  259. package/dist-esm/ui/comment-mention.mjs +0 -22
  260. package/dist-esm/ui/comment-mention.mjs.map +0 -7
  261. package/dist-esm/ui/comment-text.mjs +0 -9
  262. package/dist-esm/ui/comment-text.mjs.map +0 -7
  263. package/dist-esm/ui/mention-list.mjs +0 -50
  264. package/dist-esm/ui/mention-list.mjs.map +0 -7
  265. package/dist-esm/ui/mention-suggestion.mjs +0 -186
  266. package/dist-esm/ui/mention-suggestion.mjs.map +0 -7
  267. package/dist-esm/ui/mention.mjs +0 -11
  268. package/dist-esm/ui/mention.mjs.map +0 -7
  269. package/dist-esm/ui/render-markdown.mjs +0 -45
  270. package/dist-esm/ui/render-markdown.mjs.map +0 -7
  271. package/src/canvas/region-options.ts +0 -57
  272. package/src/ui/avatar.tsx +0 -31
  273. package/src/ui/comment-mention.ts +0 -47
  274. package/src/ui/comment-text.tsx +0 -12
  275. package/src/ui/mention-list.tsx +0 -106
  276. package/src/ui/mention-suggestion.test.ts +0 -18
  277. package/src/ui/mention-suggestion.tsx +0 -285
  278. package/src/ui/mention.tsx +0 -9
  279. package/src/ui/render-markdown.tsx +0 -72
@@ -1,84 +1,114 @@
1
1
  /* Real comment UI components. Co-located so styles travel to any view (studio,
2
2
  canvas), and themed via tldraw's --tl-color tokens so they follow light/dark. */
3
- .cmt-card {
3
+ .tlui-cmt-card {
4
4
  position: relative;
5
5
  display: flex;
6
- gap: 10px;
7
- padding: 12px;
6
+ flex-direction: column;
7
+ gap: 8px;
8
+ padding: 8px;
8
9
  width: 260px;
9
10
  background: var(--tl-color-panel);
10
11
  border: 1px solid var(--tl-color-divider);
11
- border-radius: 12px;
12
+ border-radius: var(--tl-radius-3);
13
+ }
14
+
15
+ /* The avatar lives in the byline row (see Byline), so the body text below indents to line up with
16
+ the name — the avatar's width plus the head's gap. */
17
+ .tlui-cmt-body {
18
+ padding-left: calc(28px + 6px);
12
19
  }
13
20
 
14
- /* hover-revealed controls (edit…) at the card's top-right */
15
- .cmt-card__actions {
21
+ /* hover-revealed controls (edit, react…) stacked at the card's top-right */
22
+ .tlui-cmt-card__actions {
16
23
  position: absolute;
17
24
  top: 6px;
18
25
  right: 6px;
19
26
  display: flex;
20
- gap: 2px;
27
+ gap: 0;
21
28
  opacity: 0;
22
29
  }
23
- .cmt-card:hover .cmt-card__actions,
24
- .cmt-card:focus-within .cmt-card__actions {
30
+ .tlui-cmt-card:hover .tlui-cmt-card__actions,
31
+ .tlui-cmt-card:focus-within .tlui-cmt-card__actions,
32
+ /* an open menu is portaled out of the card, so neither :hover nor :focus-within survives moving
33
+ the pointer into it — keep the controls up while their menu is open */
34
+ .tlui-cmt-card__actions:has([data-state='open']) {
25
35
  opacity: 1;
26
36
  }
27
37
 
28
- .cmt-body {
38
+ .tlui-cmt-body {
29
39
  flex: 1;
30
40
  min-width: 0;
31
41
  }
32
42
 
33
- .cmt-head {
43
+ .tlui-cmt-head {
34
44
  display: flex;
35
- align-items: baseline;
45
+ align-items: center;
36
46
  gap: 6px;
37
47
  }
38
48
 
39
- .cmt-author {
40
- font-weight: 600;
41
- font-size: 13px;
49
+ .tlui-cmt-author {
50
+ font-weight: 500;
51
+ font-size: 12px;
42
52
  color: var(--tl-color-text-1);
53
+ /* a long name ellipsizes rather than pushing the time out or running under the actions */
54
+ min-width: 0;
55
+ overflow: hidden;
56
+ text-overflow: ellipsis;
57
+ white-space: nowrap;
43
58
  }
44
59
 
45
- .cmt-time {
46
- font-size: 11px;
60
+ .tlui-cmt-time {
61
+ font-size: 12px;
47
62
  color: var(--tl-color-text-3);
63
+ flex-shrink: 0;
64
+ white-space: nowrap;
65
+ }
66
+
67
+ /* In the hover preview a long comment truncates with an ellipsis rather than growing the card —
68
+ ~7 lines (≈120px at this line height). Preview only. */
69
+ .tlui-cmt-canvas-preview--thread .tlui-cmt-text {
70
+ display: -webkit-box;
71
+ -webkit-box-orient: vertical;
72
+ -webkit-line-clamp: 7;
73
+ line-clamp: 7;
74
+ overflow: hidden;
48
75
  }
49
76
 
50
- .cmt-text {
51
- margin: 3px 0 0;
52
- font-size: 13px;
77
+ .tlui-cmt-text {
78
+ margin: 0 0 6px;
79
+ font-size: 12px;
53
80
  line-height: 1.4;
54
81
  color: var(--tl-color-text-1);
82
+ /* Break long unbroken strings (URLs, keyboard-mashing) so they wrap inside the card instead of
83
+ overflowing it. `anywhere` also lets the flex body shrink to the card width. */
84
+ overflow-wrap: anywhere;
55
85
  }
56
86
 
57
- .cmt-edited {
87
+ .tlui-cmt-edited {
58
88
  font-style: italic;
59
89
  }
60
90
 
61
91
  /* rendered rich text inside a comment body — bare tags emitted by the comment renderer
62
92
  (paragraphs, lists, code, highlight, links), matching the limited comment extension set. */
63
- .cmt-text p {
93
+ .tlui-cmt-text p {
64
94
  margin: 0;
65
95
  }
66
96
 
67
- .cmt-text p + p,
68
- .cmt-text p + ul,
69
- .cmt-text p + ol,
70
- .cmt-text ul + p,
71
- .cmt-text ol + p {
97
+ .tlui-cmt-text p + p,
98
+ .tlui-cmt-text p + ul,
99
+ .tlui-cmt-text p + ol,
100
+ .tlui-cmt-text ul + p,
101
+ .tlui-cmt-text ol + p {
72
102
  margin-top: 6px;
73
103
  }
74
104
 
75
- .cmt-text ul,
76
- .cmt-text ol {
105
+ .tlui-cmt-text ul,
106
+ .tlui-cmt-text ol {
77
107
  margin: 0;
78
108
  padding-left: 18px;
79
109
  }
80
110
 
81
- .cmt-text code {
111
+ .tlui-cmt-text code {
82
112
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
83
113
  font-size: 0.92em;
84
114
  background: var(--tl-color-muted-2);
@@ -86,212 +116,263 @@
86
116
  padding: 0 3px;
87
117
  }
88
118
 
89
- .cmt-text mark {
119
+ .tlui-cmt-text mark {
90
120
  background: color-mix(in srgb, #ffeb3b 60%, transparent);
91
121
  color: inherit;
92
122
  border-radius: 2px;
93
123
  }
94
124
 
95
- .cmt-text a {
125
+ .tlui-cmt-text a {
96
126
  color: var(--tl-color-selected);
97
127
  text-decoration: underline;
98
128
  }
99
129
 
100
- .cmt-avatar {
101
- width: 28px;
102
- height: 28px;
103
- border-radius: 50%;
104
- background: var(--tl-color-selected);
105
- color: #fff;
106
- display: flex;
107
- align-items: center;
108
- justify-content: center;
109
- font-size: 11px;
110
- font-weight: 600;
111
- flex-shrink: 0;
112
- }
113
-
114
- .cmt-avatar--image {
115
- object-fit: cover;
116
- background: none;
117
- }
130
+ /* Avatar and mention styles live in @tldraw/mentions (src/mention.css) and are bundled into
131
+ commenting.css by copy-css-files.mjs. */
118
132
 
119
133
  /* composer — an avatar beside a rounded-rect field holding the rich-text input and the send button.
120
134
  Formatting is applied via markdown and keyboard shortcuts (no toolbar). The avatar aligns to the
121
135
  top so it stays put as the field grows to multiple lines. */
122
- .cmt-composer {
136
+ .tlui-cmt-composer {
123
137
  display: flex;
124
138
  align-items: flex-start;
125
139
  gap: 10px;
126
140
  padding: 4px 0 8px 4px;
127
141
  }
128
142
 
129
- .cmt-composer__field {
143
+ .tlui-cmt-composer__field {
130
144
  flex: 1;
131
145
  min-width: 0;
132
146
  display: flex;
133
147
  align-items: center;
134
148
  gap: 6px;
135
- padding: 3px 3px 3px 12px;
136
- background: var(--tl-color-muted-2);
149
+ padding: 3px 3px 3px 8px;
150
+ background: var(--tl-color-muted-0);
137
151
  border: 1px solid var(--tl-color-divider);
138
- border-radius: var(--tl-radius-3);
152
+ border-radius: var(--tl-radius-2);
153
+ /* The whole field reads as the text input — clicking its empty area focuses the editor
154
+ (see CommentComposer). The send button restores its own pointer cursor. */
155
+ cursor: text;
139
156
  }
140
157
 
141
- .cmt-composer__field:focus-within {
158
+ .tlui-cmt-composer__field:focus-within {
142
159
  border-color: var(--tl-color-selected);
160
+ background: var(--tl-color-panel);
161
+ }
162
+
163
+ /* Expanded: the content no longer fits on one line beside the send button, so the input takes
164
+ the full width and the send button drops to its own row, right-aligned. */
165
+ .tlui-cmt-composer__field--expanded {
166
+ flex-wrap: wrap;
167
+ /* No vertical gap between the input and the send-button row it wraps onto. */
168
+ row-gap: 0;
169
+ }
170
+ .tlui-cmt-composer__field--expanded .tlui-cmt-composer__input-wrap {
171
+ flex-basis: 100%;
172
+ /* The field pads 8px left but only 3px right (the send button's inset). With the input
173
+ spanning the full width, give the text the same 8px on the right. */
174
+ padding-right: 5px;
175
+ }
176
+ .tlui-cmt-composer__field--expanded .tlui-cmt-send {
177
+ margin-left: auto;
143
178
  }
144
179
 
145
180
  /* the editable area, with the placeholder overlaid while it's empty */
146
- .cmt-composer__input-wrap {
181
+ .tlui-cmt-composer__input-wrap {
147
182
  position: relative;
148
183
  flex: 1;
149
184
  min-width: 0;
150
- padding: 3px 0;
185
+ /* Frames the input's line height symmetrically so the text sits centred in the field. Paired
186
+ with the input's min-height, which equals its line height (no gap for the text to top-align
187
+ into). Keeps the field's overall height. */
188
+ padding: 4.5px 0;
189
+ }
190
+
191
+ /* The hidden single-line measuring clone of the editor's content (see CommentComposer). */
192
+ .tlui-cmt-composer__mirror {
193
+ position: absolute;
194
+ top: 0;
195
+ left: 0;
196
+ visibility: hidden;
197
+ pointer-events: none;
198
+ white-space: nowrap;
199
+ width: max-content;
200
+ max-height: none;
201
+ overflow: visible;
151
202
  }
152
203
 
153
- .cmt-input {
204
+ .tlui-cmt-input {
154
205
  font: inherit;
155
- font-size: 13px;
206
+ font-size: 12px;
156
207
  line-height: 1.4;
157
208
  color: var(--tl-color-text-1);
158
209
  outline: none;
159
- min-height: 20px;
160
- max-height: 40vh;
161
- overflow-y: auto;
210
+ min-height: 17px;
162
211
  white-space: pre-wrap;
163
212
  word-break: break-word;
164
213
  }
165
214
 
166
- .cmt-input p {
215
+ .tlui-cmt-input p {
167
216
  margin: 0;
168
217
  }
169
218
 
170
- .cmt-input p + p,
171
- .cmt-input p + ul,
172
- .cmt-input p + ol {
219
+ .tlui-cmt-input p + p,
220
+ .tlui-cmt-input p + ul,
221
+ .tlui-cmt-input p + ol {
173
222
  margin-top: 4px;
174
223
  }
175
224
 
176
- .cmt-input ul,
177
- .cmt-input ol {
225
+ .tlui-cmt-input ul,
226
+ .tlui-cmt-input ol {
178
227
  margin: 0;
179
228
  padding-left: 18px;
180
229
  }
181
230
 
182
- .cmt-input__placeholder {
231
+ .tlui-cmt-input__placeholder {
183
232
  position: absolute;
184
- top: 3px;
233
+ top: 4.5px;
185
234
  left: 0;
186
- font-size: 13px;
235
+ font-size: 12px;
187
236
  line-height: 1.4;
188
237
  color: var(--tl-color-text-3);
189
238
  pointer-events: none;
190
239
  }
191
240
 
192
- .cmt-send {
241
+ /* A bare up-arrow icon button (matching the placement composer): no fill by default, a grey square
242
+ on hover, grey while there's nothing to send and dark once there is. */
243
+ .tlui-cmt-send {
244
+ flex-shrink: 0;
245
+ display: inline-flex;
246
+ align-items: center;
247
+ justify-content: center;
248
+ width: 28px;
249
+ height: 28px;
193
250
  border: 0;
194
251
  border-radius: var(--tl-radius-2);
195
- background: var(--tl-color-selected);
196
- color: #fff;
197
- font: inherit;
198
- font-size: 12px;
199
- font-weight: 600;
200
- padding: 6px 12px;
252
+ background: transparent;
253
+ color: var(--tl-color-text-3);
201
254
  cursor: pointer;
202
- align-self: flex-end;
255
+ align-self: center;
203
256
  }
204
257
 
205
- .cmt-send:hover,
206
- .pseudo-hover .cmt-send {
207
- background: color-mix(in srgb, var(--tl-color-selected) 88%, black);
258
+ .tlui-cmt-send:not(:disabled) {
259
+ color: var(--tl-color-text-1);
260
+ }
261
+
262
+ .tlui-cmt-send:hover:not(:disabled) {
263
+ background: var(--tl-color-muted-2);
208
264
  }
209
265
 
210
- .cmt-send:active,
211
- .pseudo-active .cmt-send {
212
- background: color-mix(in srgb, var(--tl-color-selected) 78%, black);
266
+ .tlui-cmt-send:active:not(:disabled),
267
+ .pseudo-active .tlui-cmt-send:not(:disabled) {
268
+ background: var(--tl-color-muted-1);
213
269
  }
214
270
 
215
- .cmt-send:focus-visible,
216
- .pseudo-focus-visible .cmt-send {
271
+ .tlui-cmt-send:focus-visible,
272
+ .pseudo-focus-visible .tlui-cmt-send {
217
273
  outline: 2px solid var(--tl-color-selected);
218
274
  outline-offset: 2px;
219
275
  }
220
276
 
221
- .cmt-send:disabled {
222
- opacity: 0.45;
277
+ .tlui-cmt-send:disabled {
278
+ background: transparent;
279
+ color: var(--tl-color-text-3);
223
280
  cursor: not-allowed;
224
281
  }
225
282
 
226
- /* pin */
227
- .cmt-pin {
228
- width: 34px;
229
- height: 34px;
230
- border-radius: 50% 50% 50% 3px;
231
- background: var(--tl-color-selected);
283
+ /* The shared marker treatment. To the pointer a comment pin and a count badge are the same
284
+ object — a token sitting on the canvas — so they rest and lift identically; everything shared
285
+ lives here so the two can't drift apart. */
286
+ .tlui-cmt-marker {
287
+ box-shadow: var(--tlui-cmt-marker-shadow);
288
+ cursor: pointer;
289
+ transition:
290
+ transform var(--tlui-cmt-marker-transition),
291
+ box-shadow var(--tlui-cmt-marker-transition);
292
+ }
293
+
294
+ .tlui-cmt-marker:hover {
295
+ transform: scale(var(--tlui-cmt-marker-hover-scale));
296
+ }
297
+
298
+ /* The hover shadow is for closed markers only — an open one keeps its selection ring, which this
299
+ box-shadow would otherwise override (the hover selector outranks the --open rule). */
300
+ .tlui-cmt-marker:not(.tlui-cmt-marker--open):hover {
301
+ box-shadow: var(--tlui-cmt-marker-shadow-hover);
302
+ }
303
+
304
+ /* Count badges don't react to hover — no scale, no shadow lift. Only pins do. */
305
+ .tlui-cmt-count-badge:hover {
306
+ transform: none;
307
+ box-shadow: var(--tlui-cmt-marker-shadow);
308
+ }
309
+
310
+ /* Pins don't scale on hover — only the shadow lifts. */
311
+ .tlui-cmt-pin:hover {
312
+ transform: none;
313
+ }
314
+
315
+ /* open: the thread (or a stack's list) is showing — the active marker just keeps its lift shadow,
316
+ no selection ring. */
317
+ .tlui-cmt-marker--open {
318
+ box-shadow: var(--tlui-cmt-marker-shadow);
319
+ }
320
+
321
+ /* pin — a canvas marker: a white teardrop holding the author's avatar circle, so a thread is
322
+ identifiable at a glance. */
323
+ .tlui-cmt-pin {
324
+ --tlui-cmt-marker-size: var(--tlui-cmt-pin-size);
325
+ width: var(--tlui-cmt-marker-size);
326
+ height: var(--tlui-cmt-marker-size);
327
+ border-radius: 50% 50% 50% 0;
328
+ background: var(--tl-color-panel);
232
329
  color: #fff;
233
330
  display: flex;
234
331
  align-items: center;
235
332
  justify-content: center;
236
333
  font-size: 14px;
237
334
  font-weight: 600;
238
- box-shadow: 0 2px 6px rgba(0, 0, 0, 0.25);
239
- cursor: pointer;
240
- transition:
241
- transform 0.08s ease,
242
- box-shadow 0.08s ease;
243
- }
244
-
245
- .cmt-pin:hover,
246
- .pseudo-hover .cmt-pin {
247
- transform: scale(1.08);
248
- box-shadow: 0 3px 10px rgba(0, 0, 0, 0.32);
249
335
  }
250
-
251
- /* open: the thread is showing — a selection ring marks the active pin */
252
- .cmt-pin--open {
253
- box-shadow:
254
- 0 0 0 3px var(--tl-color-panel),
255
- 0 0 0 5px var(--tl-color-selected),
256
- 0 2px 6px rgba(0, 0, 0, 0.25);
336
+ /* The pin holds the author's 22px avatar circle, centred in its 28px face. */
337
+ .tlui-cmt-pin .tlui-cmt-avatar {
338
+ width: 22px;
339
+ height: 22px;
257
340
  }
258
341
 
259
342
  /* resolved: a solid, muted grey (the default --tl-color-muted-1 is 10% black, so it goes
260
343
  translucent over the canvas). Dark mode gets its own grey — the token's dark value is near-black. */
261
- .cmt-pin--resolved {
344
+ .tlui-cmt-pin--resolved {
262
345
  background: #dfe0e1;
263
346
  color: hsl(214, 8%, 42%);
264
347
  }
265
- .tl-theme__dark .cmt-pin--resolved {
348
+ .tl-theme__dark .tlui-cmt-pin--resolved {
266
349
  background: hsl(240, 5%, 30%);
267
350
  color: hsl(0, 0%, 72%);
268
351
  }
269
352
 
270
- /* count badge */
271
- .cmt-count-badge {
272
- width: 34px;
273
- height: 34px;
353
+ /* count badge — the same marker as a pin. */
354
+ .tlui-cmt-count-badge {
355
+ width: var(--tlui-cmt-marker-size);
356
+ height: var(--tlui-cmt-marker-size);
274
357
  border-radius: 50%;
275
358
  display: flex;
276
359
  align-items: center;
277
360
  justify-content: center;
278
361
  background: var(--tl-color-text-1);
279
362
  color: var(--tl-color-panel);
280
- font-size: 13px;
363
+ font-size: 12px;
281
364
  font-weight: 700;
282
- box-shadow: 0 2px 6px rgba(0, 0, 0, 0.25);
283
365
  user-select: none;
284
366
  }
285
367
 
286
368
  /* thread */
287
- .cmt-thread {
369
+ .tlui-cmt-thread {
288
370
  display: flex;
289
371
  flex-direction: column;
290
- gap: 8px;
291
372
  width: 300px;
292
373
  }
293
374
 
294
- .cmt-thread__head {
375
+ .tlui-cmt-thread__head {
295
376
  display: flex;
296
377
  align-items: center;
297
378
  gap: 8px;
@@ -302,7 +383,7 @@
302
383
  letter-spacing: 0.04em;
303
384
  }
304
385
 
305
- .cmt-badge {
386
+ .tlui-cmt-badge {
306
387
  font-size: 10px;
307
388
  font-weight: 600;
308
389
  text-transform: none;
@@ -314,134 +395,105 @@
314
395
  }
315
396
 
316
397
  /* reactions */
317
- .cmt-reactions {
398
+ .tlui-cmt-reactions {
318
399
  display: flex;
319
- gap: 6px;
400
+ flex-wrap: wrap;
401
+ gap: 4px;
402
+ margin-top: 6px;
320
403
  }
321
404
 
322
- .cmt-reaction {
405
+ .tlui-cmt-reaction {
323
406
  display: inline-flex;
324
407
  align-items: center;
325
- gap: 5px;
326
- padding: 3px 9px;
327
- border-radius: 999px;
328
- border: 1px solid var(--tl-color-divider);
329
- background: var(--tl-color-panel);
408
+ gap: 4px;
409
+ padding: 2px 6px;
410
+ border: none;
411
+ border-radius: var(--tl-radius-1);
412
+ background: var(--tl-color-muted-2);
330
413
  font: inherit;
331
- font-size: 12px;
414
+ font-size: 10px;
415
+ font-weight: 400;
332
416
  color: var(--tl-color-text-1);
333
417
  cursor: pointer;
334
418
  }
335
419
 
336
- .cmt-reaction:hover,
337
- .pseudo-hover .cmt-reaction {
338
- border-color: var(--tl-color-selected);
420
+ /* Hover deepens the grey — no border to tint now. Mixing toward the text colour is theme-aware:
421
+ it darkens the fill in light mode and lightens it in dark, tracking the pill's own contrast. */
422
+ .tlui-cmt-reaction:hover {
423
+ background: var(--tl-color-muted-1);
339
424
  }
340
425
 
341
- .cmt-reaction--active {
342
- border-color: var(--tl-color-selected);
343
- background: color-mix(in srgb, var(--tl-color-selected) 12%, var(--tl-color-panel));
426
+ /* a reaction you're part of fills with the selection colour, so "I reacted with this" stands out
427
+ from the grey pills of reactions you haven't joined. */
428
+ .tlui-cmt-reaction--active {
429
+ background: color-mix(in srgb, var(--tl-color-selected) 28%, var(--tl-color-panel));
344
430
  }
345
431
 
346
- .cmt-reaction__count {
347
- color: var(--tl-color-text-3);
432
+ .tlui-cmt-reaction--active:hover {
433
+ background: color-mix(in srgb, var(--tl-color-selected) 38%, var(--tl-color-panel));
348
434
  }
349
435
 
350
- .cmt-reaction--add {
436
+ /* the emoji sits at the same 10px as the count beside it */
437
+ .tlui-cmt-reaction__emoji {
438
+ font-size: 10px;
439
+ line-height: 1;
440
+ }
441
+
442
+ .tlui-cmt-reaction__count {
351
443
  color: var(--tl-color-text-3);
352
- font-weight: 600;
444
+ font-variant-numeric: tabular-nums;
353
445
  }
354
446
 
355
- /* mention */
356
- .cmt-mention {
357
- color: var(--tl-color-selected);
358
- font-weight: 600;
359
- background: color-mix(in srgb, var(--tl-color-selected) 12%, transparent);
360
- padding: 0 4px;
361
- border-radius: 4px;
447
+ .tlui-cmt-reaction--active .tlui-cmt-reaction__count {
448
+ color: var(--tl-color-text-1);
362
449
  }
363
450
 
364
- /* mention picker */
365
- .cmt-mention-list {
366
- display: flex;
367
- flex-direction: column;
368
- gap: 1px;
451
+ /* emoji picker */
452
+ .tlui-cmt-emoji-picker {
453
+ display: grid;
454
+ grid-template-columns: repeat(5, 40px);
455
+ gap: 0;
369
456
  padding: 4px;
370
- width: 100%;
371
- box-sizing: border-box;
372
- max-height: 260px;
373
- overflow-y: auto;
374
- color: var(--tl-color-text-1, #1d1d1d);
375
- background: var(--tl-color-panel, #fff);
376
- border: 1px solid var(--tl-color-divider, rgba(0, 0, 0, 0.08));
377
- border-radius: 10px;
378
- box-shadow: var(--tl-shadow-2, 0 2px 12px rgba(0, 0, 0, 0.14));
379
457
  }
380
458
 
381
- .cmt-mention-list__item {
459
+ .tlui-cmt-emoji-picker__item {
460
+ position: relative;
382
461
  display: flex;
383
462
  align-items: center;
384
- gap: 8px;
385
- padding: 6px 8px;
463
+ justify-content: center;
464
+ width: 40px;
465
+ height: 40px;
466
+ padding: 0;
386
467
  border: none;
387
- background: transparent;
388
- border-radius: 6px;
389
- cursor: pointer;
390
- text-align: left;
391
- color: var(--tl-color-text-1);
468
+ background: none;
392
469
  font: inherit;
470
+ font-size: 21px;
471
+ line-height: 1;
472
+ cursor: pointer;
393
473
  }
394
474
 
395
- .cmt-mention-list__item--active,
396
- .cmt-mention-list__item:hover {
397
- background: var(--tl-color-muted-2, rgba(0, 0, 0, 0.06));
398
- }
399
-
400
- .cmt-mention-list__text {
401
- display: flex;
402
- flex-direction: column;
403
- min-width: 0;
404
- line-height: 1.3;
405
- }
406
-
407
- .cmt-mention-list__name {
408
- font-size: 13px;
409
- font-weight: 500;
410
- white-space: nowrap;
411
- overflow: hidden;
412
- text-overflow: ellipsis;
413
- }
414
-
415
- .cmt-mention-list__you {
416
- margin-left: 4px;
417
- font-weight: 400;
418
- color: var(--tl-color-text-3);
475
+ /* Inset wash behind the glyph — the 40px hit cells tile edge-to-edge, the squares sit 4px apart,
476
+ like the thread action buttons. Sits under the emoji so an opaque selected fill can't hide it. */
477
+ .tlui-cmt-emoji-picker__item::after {
478
+ content: '';
479
+ position: absolute;
480
+ inset: 2px;
481
+ z-index: -1;
482
+ border-radius: var(--tl-radius-2);
419
483
  }
420
-
421
- .cmt-mention-list__secondary {
422
- font-size: 12px;
423
- color: var(--tl-color-text-3);
424
- white-space: nowrap;
425
- overflow: hidden;
426
- text-overflow: ellipsis;
484
+ .tlui-cmt-emoji-picker__item:hover {
485
+ z-index: 1;
427
486
  }
428
-
429
- .cmt-mention-list--empty {
430
- padding: 8px 10px;
431
- width: 100%;
432
- box-sizing: border-box;
433
- color: var(--tl-color-text-3);
434
- font-size: 13px;
487
+ .tlui-cmt-emoji-picker__item:hover::after {
488
+ background: var(--tl-color-muted-2);
435
489
  }
436
490
 
437
- /* the @-picker popup — caret-anchored above the page, positioned by the suggestion plugin */
438
- .cmt-mention-popup {
439
- position: fixed;
440
- z-index: var(--tl-layer-menus);
491
+ .tlui-cmt-emoji-picker__item--active::after {
492
+ background: color-mix(in srgb, var(--tl-color-selected) 28%, var(--tl-color-panel));
441
493
  }
442
494
 
443
495
  /* empty state */
444
- .cmt-empty {
496
+ .tlui-cmt-empty {
445
497
  display: flex;
446
498
  flex-direction: column;
447
499
  align-items: center;
@@ -451,50 +503,116 @@
451
503
  width: 240px;
452
504
  }
453
505
 
454
- .cmt-empty__icon {
506
+ .tlui-cmt-empty__icon {
455
507
  font-size: 28px;
456
508
  }
457
509
 
458
- .cmt-empty__message {
510
+ .tlui-cmt-empty__message {
459
511
  margin: 0;
460
512
  color: var(--tl-color-text-3);
461
- font-size: 13px;
513
+ font-size: 12px;
462
514
  line-height: 1.4;
463
515
  }
464
516
 
465
- /* a comment thread: an elevated panel the same surface treatment as the toolbar — holding a
466
- header, an optional resolved banner, the comments, and a reply composer */
467
- .cmt-thread {
517
+ /* a comment thread: an elevated panel holding a header, an optional resolved banner, the
518
+ comments, and a reply composer */
519
+ /* The thread panel's layout metrics live on the container because two separate portals need them:
520
+ the panel itself, and the canvas hover preview, which has to land its first comment on exactly
521
+ the pixel the panel would. Deriving the preview's offset from these means the two can't drift —
522
+ retune the padding or the header here and the preview follows. */
523
+ .tl-container {
524
+ /* Everything that has to line up against a pin — its preview's bridge, the draft composer's
525
+ offsets, `PIN_SIZE` in thread-view.tsx — derives from this, so the pin's size stays one
526
+ number. */
527
+ --tlui-cmt-pin-size: 28px;
528
+ /* A cluster badge and a stack badge are this square — the same size as a pin, so the marker
529
+ kinds read as one family. The hover preview measures its bridge against it, so the bridge
530
+ stops at the marker's edge instead of covering it. Mirrored by `MARKER_SIZE` in
531
+ thread-view.tsx. */
532
+ --tlui-cmt-marker-size: var(--tlui-cmt-pin-size);
533
+ /* How a marker sits, lifts, and rings. Shared so a pin and a badge respond identically.
534
+ shadow-3's depth without its `inset 1px panel-contrast` edge — that inset reads as a white
535
+ border on a coloured pin (it's invisible on the panel-white menus shadow-3 is meant for). */
536
+ --tlui-cmt-marker-shadow: 0px 1px 2px hsl(0, 0%, 0%, 28%), 0px 2px 6px hsl(0, 0%, 0%, 14%);
537
+ --tlui-cmt-marker-shadow-hover: var(--tlui-cmt-marker-shadow);
538
+ --tlui-cmt-marker-hover-scale: 1.08;
539
+ --tlui-cmt-marker-transition: 0.08s ease;
540
+ --tlui-cmt-thread-padding: 12px;
541
+ --tlui-cmt-thread-gap: 0px;
542
+ --tlui-cmt-thread-action-size: 26px;
543
+ /* A floating pill's edge — its hairline ring is the only thing separating it from the panel it
544
+ overlaps, so the ring is a theme token rather than a literal black: a near-black hairline
545
+ vanishes on a dark panel, where the divider token lightens instead. The drop below it stays
546
+ literal; a soft dark blur reads on both themes. */
547
+ --tlui-cmt-pill-shadow: 0 0 0 1px var(--tl-color-divider), 0 1px 3px hsl(0, 0%, 0%, 0.05);
548
+ }
549
+
550
+ .tlui-cmt-thread {
551
+ position: relative;
468
552
  display: flex;
469
553
  flex-direction: column;
470
- gap: 6px;
471
554
  width: 300px;
472
- padding: 8px;
473
555
  background: var(--tl-color-panel);
474
556
  border-radius: var(--tl-radius-4);
475
- box-shadow: var(--tl-shadow-2);
557
+ box-shadow: var(--tl-shadow-3);
476
558
  }
477
- .cmt-thread__header {
559
+ /* A pill of its own, floating above the panel's top-right corner and overlapping its top edge. */
560
+ .tlui-cmt-thread__header {
561
+ position: absolute;
562
+ right: 10px;
563
+ bottom: calc(100% - 15px);
478
564
  display: flex;
565
+ gap: 2px;
566
+ padding: 2px;
567
+ background: var(--tl-color-panel);
568
+ border-radius: var(--tl-radius-3);
569
+ box-shadow: var(--tlui-cmt-pill-shadow);
570
+ transition: opacity 0.1s ease;
571
+ }
572
+ /* The first comment's hover actions surface right where the header pill sits, so hide the pill
573
+ while that comment is hovered — one pill in the corner, never two stacked. */
574
+ .tlui-cmt-thread:has(.tlui-cmt-thread__list > :first-child:hover)
575
+ .tlui-cmt-thread__header:not(:has(.tlui-cmt-thread__title)) {
576
+ opacity: 0;
577
+ pointer-events: none;
578
+ }
579
+ /* A title puts the header back in the panel, in flow. The pill above is tuned for the actions-only
580
+ case tldraw itself uses: right-anchored and only as wide as its buttons, so a title inside it
581
+ would grow leftwards off the panel's edge and float over whatever sits above. `header` is public
582
+ API (see `CommentThreadProps`), so it has to keep laying out sensibly for anyone passing one. */
583
+ .tlui-cmt-thread__header:has(.tlui-cmt-thread__title) {
584
+ position: static;
479
585
  align-items: center;
480
- justify-content: space-between;
481
- padding: 2px 2px 2px 4px;
586
+ padding: 0;
587
+ background: none;
588
+ border-radius: 0;
589
+ box-shadow: none;
482
590
  }
483
- .cmt-thread__title {
591
+ .tlui-cmt-thread__title {
592
+ padding: 10px 12px;
484
593
  font-size: 12px;
485
- font-weight: 600;
594
+ line-height: 20px;
595
+ font-weight: 500;
486
596
  color: var(--tl-color-text-1);
487
597
  }
488
- .cmt-thread__actions {
598
+ .tlui-cmt-thread__actions {
489
599
  display: flex;
490
- gap: 2px;
491
- }
492
- .cmt-thread__action {
600
+ gap: 0;
601
+ /* Right-align the actions whether or not the header has a title (it currently has none, so
602
+ space-between alone would leave them at the left). */
603
+ margin-left: auto;
604
+ }
605
+ /* Follows tldraw's toolbar buttons (.tlui-button), used by both the header and card actions: the
606
+ button is the 40px hit target that tiles with its neighbours; its 32px ::after is the visual
607
+ square carrying the hover wash, so the hit area (not the ::after) is what's clickable and it
608
+ never reaches past its row. */
609
+ .tlui-cmt-thread__action {
610
+ position: relative;
493
611
  display: flex;
494
612
  align-items: center;
495
613
  justify-content: center;
496
- width: 22px;
497
- height: 22px;
614
+ width: 40px;
615
+ height: 40px;
498
616
  padding: 0;
499
617
  border: none;
500
618
  border-radius: 6px;
@@ -504,39 +622,156 @@
504
622
  line-height: 1;
505
623
  cursor: pointer;
506
624
  }
507
- .cmt-thread__action:hover {
508
- background: var(--tl-color-muted-2);
625
+ /* Overlap each button 4px into the previous so the 32px visual squares sit 4px apart, not 8px.
626
+ Left-only, so the last button's hit area still stops at its row's right edge. */
627
+ .tlui-cmt-thread__action + .tlui-cmt-thread__action {
628
+ margin-left: -4px;
629
+ }
630
+ .tlui-cmt-thread__action::after {
631
+ content: '';
632
+ position: absolute;
633
+ inset: 4px;
634
+ border-radius: var(--tl-radius-2);
635
+ }
636
+ .tlui-cmt-thread__action:hover {
509
637
  color: var(--tl-color-text-1);
638
+ /* Raise the hovered button above its overlapping neighbours so its 40px hit area wins the
639
+ overlap zone — the same z-index flip tldraw's own toolbar buttons use. */
640
+ z-index: 1;
641
+ }
642
+ .tlui-cmt-thread__action:hover::after {
643
+ background: var(--tl-color-muted-2);
644
+ }
645
+ .tlui-cmt-thread__action--danger:hover {
646
+ color: var(--tl-color-danger);
510
647
  }
511
- .cmt-thread__resolved {
648
+ .tlui-cmt-thread__resolved {
649
+ margin: 4px 12px;
512
650
  padding: 6px 10px;
513
651
  border-radius: 8px;
514
652
  background: var(--tl-color-muted-2);
515
653
  font-size: 12px;
516
654
  color: var(--tl-color-text-3);
517
655
  }
518
- .cmt-thread__list {
656
+ .tlui-cmt-thread__list {
519
657
  display: flex;
520
658
  flex-direction: column;
521
- gap: 6px;
659
+ gap: var(--tlui-cmt-thread-gap);
660
+ }
661
+ /* When nothing follows the list (a resolved thread has no composer or footer) the last comment
662
+ would sit against the panel's rounded bottom — restore the trailing inset the composer supplies. */
663
+ .tlui-cmt-thread__list:last-child {
664
+ padding-bottom: 12px;
522
665
  }
666
+ /* The footer slot sits where the composer would — its content (e.g. a sign-in prompt) sits at
667
+ the thread's trailing edge, like the composer's send button. */
668
+ .tlui-cmt-thread__footer {
669
+ display: flex;
670
+ justify-content: flex-end;
671
+ padding: 4px 2px 2px;
672
+ }
673
+ /* Inside a thread, author identity is a small colour dot, no initial — the letter doesn't fit
674
+ at this size. */
675
+ .tlui-cmt-thread .tlui-cmt-avatar,
676
+ .tlui-cmt-canvas-preview--thread .tlui-cmt-avatar,
677
+ .tlui-cmt-canvas-preview--list .tlui-cmt-avatar,
678
+ .tlui-cmt-stack-list .tlui-cmt-avatar {
679
+ width: 12px;
680
+ height: 12px;
681
+ font-size: 0;
682
+ }
683
+
684
+ .tlui-cmt-thread .tlui-cmt-composer {
685
+ padding: 12px;
686
+ }
687
+ .tlui-cmt-thread .tlui-cmt-composer .tlui-cmt-avatar {
688
+ display: none;
689
+ }
690
+
691
+ /* The sidebar's rows use the same small colour dot as the thread view. */
692
+ .tlui-cmt-list .tlui-cmt-avatar {
693
+ width: 14px;
694
+ height: 14px;
695
+ font-size: 0;
696
+ margin-top: 2px;
697
+ }
698
+ /* The sidebar row already leads with its own avatar (the flex row's first column); the byline's
699
+ avatar — added for the canvas thread views — would double it, so hide the byline's inside a row. */
700
+ .tlui-cmt-list .tlui-cmt-head .tlui-cmt-avatar {
701
+ display: none;
702
+ }
703
+
704
+ /* The dot treatment is for colour-only identity — an image avatar stays full size. */
705
+ .tlui-cmt-thread .tlui-cmt-avatar--image,
706
+ .tlui-cmt-canvas-preview--thread .tlui-cmt-avatar--image,
707
+ .tlui-cmt-canvas-preview--list .tlui-cmt-avatar--image,
708
+ .tlui-cmt-stack-list .tlui-cmt-avatar--image {
709
+ /* Match the 12px colour dot's width so the body's calc(12px + 6px) indent still lines up under
710
+ the byline name when the author has an image rather than a dot. */
711
+ width: 12px;
712
+ height: 12px;
713
+ margin-top: 0;
714
+ }
715
+ .tlui-cmt-list .tlui-cmt-avatar--image {
716
+ width: 24px;
717
+ height: 24px;
718
+ margin-top: 0;
719
+ }
720
+
523
721
  /* inside the panel, comments are plain content — no surrounding box; the composer's field carries
524
- its own surface. */
525
- .cmt-thread .cmt-card {
722
+ its own surface. The hover preview is the same panel without a header, so its comment is laid
723
+ out by the same rule rather than a copy of it. */
724
+ .tlui-cmt-thread .tlui-cmt-card,
725
+ .tlui-cmt-canvas-preview--thread .tlui-cmt-card {
526
726
  width: 100%;
527
- padding: 6px 0 6px 4px;
727
+ gap: 0;
728
+ padding: 0 12px;
528
729
  background: transparent;
529
730
  border: none;
530
731
  }
732
+ /* The avatar is a 12px dot, so the body lines up 12px + the head's 6px gap in from the row. */
733
+ .tlui-cmt-thread .tlui-cmt-body,
734
+ .tlui-cmt-canvas-preview--thread .tlui-cmt-body,
735
+ .tlui-cmt-canvas-preview--list .tlui-cmt-body,
736
+ .tlui-cmt-stack-list .tlui-cmt-body {
737
+ padding-left: calc(12px + 6px);
738
+ }
739
+ /* Inside a panel the actions float as their own pill — the same pill the thread header is, in the
740
+ same corner (which is why the header hides while the first comment is hovered). It sits just
741
+ above the card's top edge rather than on the byline. */
742
+ /* Line the card actions up with the header actions: flush to the full-width card's right edge (the
743
+ panel edge, matching the header ✕) and flush on the 40px byline row, so the 40px buttons fill it. */
744
+ .tlui-cmt-thread .tlui-cmt-card .tlui-cmt-card__actions {
745
+ top: 0;
746
+ right: 0;
747
+ }
748
+ /* The byline row is 40px: on the opened thread it fits the action pills (react/edit/delete) that
749
+ float in its corner without the text running underneath; previews and collapsed cards carry the
750
+ same height so every comment row reads identically, solo or in a stack. */
751
+ .tlui-cmt-thread .tlui-cmt-card .tlui-cmt-head,
752
+ .tlui-cmt-canvas-preview--thread .tlui-cmt-card .tlui-cmt-head,
753
+ .tlui-cmt-canvas-preview--list .tlui-cmt-card .tlui-cmt-head,
754
+ .tlui-cmt-stack-list .tlui-cmt-card .tlui-cmt-head {
755
+ min-height: 40px;
756
+ }
757
+ /* Only the opened thread's comments actually carry the pills, so only they reserve room for them.
758
+ Derived from the action size so it can't drift: n buttons + (n−1) 2px gaps + the pill's 2px
759
+ padding either side + its 2px right inset. One button (react) on others' comments. */
760
+ .tlui-cmt-thread .tlui-cmt-card .tlui-cmt-head {
761
+ padding-right: calc(var(--tlui-cmt-thread-action-size) + 6px);
762
+ }
763
+ .tlui-cmt-thread .tlui-cmt-card--you .tlui-cmt-head {
764
+ padding-right: calc(3 * var(--tlui-cmt-thread-action-size) + 10px);
765
+ }
531
766
 
532
767
  /* comments list — thread summaries, one per row (used by the canvas sidebar) */
533
- .cmt-list {
768
+ .tlui-cmt-list {
534
769
  display: flex;
535
770
  flex-direction: column;
536
771
  width: 100%;
537
772
  min-height: 0;
538
773
  }
539
- .cmt-list__header {
774
+ .tlui-cmt-list__header {
540
775
  flex-shrink: 0;
541
776
  display: flex;
542
777
  align-items: center;
@@ -545,16 +780,15 @@
545
780
  padding: 6px 6px 6px 14px;
546
781
  border-bottom: 1px solid var(--tl-color-divider);
547
782
  }
548
- .cmt-list__header-title {
549
- font-size: 11px;
550
- font-weight: 600;
551
- letter-spacing: 0.06em;
552
- text-transform: uppercase;
553
- color: var(--tl-color-text-3);
783
+ .tlui-cmt-list__header-title {
784
+ font-size: 12px;
785
+ font-weight: 500;
786
+ color: var(--tl-color-text-1);
554
787
  }
555
788
 
556
789
  /* a small square icon button in the sidebar header (filter funnel, overflow …) */
557
- .cmt-header-btn {
790
+ .tlui-cmt-header-btn {
791
+ position: relative;
558
792
  display: flex;
559
793
  align-items: center;
560
794
  justify-content: center;
@@ -567,51 +801,83 @@
567
801
  color: var(--tl-color-text-2);
568
802
  cursor: pointer;
569
803
  }
570
- .cmt-header-btn:hover,
571
- .cmt-header-btn[data-state='open'] {
804
+ /* Same hit-target expansion as the thread actions, tiled the same way — this row's gap is 2px too
805
+ (see `.tlui-cmt-list__header-actions`). */
806
+ .tlui-cmt-header-btn::after {
807
+ content: '';
808
+ position: absolute;
809
+ inset: -6px -1px;
810
+ }
811
+ .tlui-cmt-header-btn:first-child::after {
812
+ left: -6px;
813
+ }
814
+ .tlui-cmt-header-btn:last-child::after {
815
+ right: -6px;
816
+ }
817
+ .tlui-cmt-header-btn:hover,
818
+ .tlui-cmt-header-btn[data-state='open'] {
572
819
  background: var(--tl-color-muted-2);
573
820
  color: var(--tl-color-text-1);
574
821
  }
575
- .cmt-list__header-actions {
822
+ .tlui-cmt-list__header-actions {
576
823
  display: flex;
577
824
  align-items: center;
578
825
  gap: 2px;
579
826
  }
580
827
 
828
+ /* The commenting dropdowns' surface — a little air between the items and the menu edge. */
829
+ .tlui-cmt-menu {
830
+ min-width: 100px;
831
+ padding: 4px;
832
+ }
833
+
581
834
  /* a menu row with a label and a right-aligned shortcut hint (overflow menu) */
582
- .cmt-menu-item {
835
+ .tlui-cmt-menu-item {
836
+ position: relative;
583
837
  display: flex;
584
838
  align-items: center;
585
839
  justify-content: space-between;
586
840
  gap: 24px;
587
841
  width: 100%;
588
- padding: 6px 8px;
842
+ min-height: 40px;
843
+ padding: 0 10px;
589
844
  border: none;
590
- border-radius: 6px;
591
845
  background: transparent;
592
846
  font: inherit;
593
- font-size: 13px;
847
+ font-size: 12px;
594
848
  color: var(--tl-color-text-1);
595
849
  cursor: pointer;
596
850
  }
597
- .cmt-menu-item:hover {
851
+ /* 36px visual inset within the 40px row — the hover wash the action and emoji buttons use. */
852
+ .tlui-cmt-menu-item::after {
853
+ content: '';
854
+ position: absolute;
855
+ inset: 2px;
856
+ border-radius: var(--tl-radius-2);
857
+ }
858
+ .tlui-cmt-menu-item:hover::after {
598
859
  background: var(--tl-color-muted-2);
599
860
  }
600
- .cmt-menu-item__shortcut {
861
+ /* Destructive menu entries (delete) — the same danger colour as tldraw's danger buttons. */
862
+ .tlui-cmt-menu-item--danger {
863
+ color: var(--tl-color-danger);
864
+ }
865
+
866
+ .tlui-cmt-menu-item__shortcut {
601
867
  color: var(--tl-color-text-3);
602
868
  font-size: 12px;
603
869
  }
604
- .cmt-list__items {
870
+ .tlui-cmt-list__items {
605
871
  flex: 1;
606
872
  min-height: 0;
607
873
  overflow-y: auto;
608
874
  }
609
- .cmt-list__empty {
875
+ .tlui-cmt-list__empty {
610
876
  padding: 20px 14px;
611
877
  color: var(--tl-color-text-3);
612
- font-size: 13px;
878
+ font-size: 12px;
613
879
  }
614
- .cmt-list__item {
880
+ .tlui-cmt-list__item {
615
881
  display: flex;
616
882
  gap: 10px;
617
883
  width: 100%;
@@ -622,34 +888,48 @@
622
888
  font: inherit;
623
889
  text-align: left;
624
890
  cursor: pointer;
891
+ /* rows can render as anchors (for open-in-new-tab); keep them looking like list rows */
892
+ color: inherit;
893
+ text-decoration: none;
625
894
  }
626
- .cmt-list__item:hover {
895
+ .tlui-cmt-list__item:hover {
627
896
  background: var(--tl-color-muted-2);
628
897
  }
629
- .cmt-list__item--selected {
630
- background: var(--tl-color-muted-1);
898
+ /* Selected sits a step above hover so the open thread's row stays distinguishable while
899
+ pointing at other rows — but only a step: the muted-1 token (10%) reads too heavy here.
900
+ These rows sit on the opaque sidebar panel, so a translucent tint composes safely. */
901
+ .tlui-cmt-list__item--selected,
902
+ .tlui-cmt-list__item--selected:hover {
903
+ background: hsl(0, 0%, 0%, 5%);
904
+ }
905
+ .tl-theme__dark .tlui-cmt-list__item--selected,
906
+ .tl-theme__dark .tlui-cmt-list__item--selected:hover {
907
+ background: hsl(0, 0%, 100%, 5%);
631
908
  }
632
909
  /* resolved rows mute their content but keep the "Resolved" marker legible */
633
- .cmt-list__item[data-resolved] .cmt-head,
634
- .cmt-list__item[data-resolved] .cmt-list__item-preview {
910
+ .tlui-cmt-list__item[data-resolved] .tlui-cmt-head,
911
+ .tlui-cmt-list__item[data-resolved] .tlui-cmt-list__item-preview {
635
912
  opacity: 0.55;
636
913
  }
637
- .cmt-list__item-body {
914
+ .tlui-cmt-list__item-body {
638
915
  flex: 1;
639
916
  min-width: 0;
640
917
  }
641
- .cmt-list__item-preview {
918
+ .tlui-cmt-list__item-preview {
642
919
  margin-top: 2px;
643
- font-size: 13px;
920
+ font-size: 12px;
644
921
  color: var(--tl-color-text-1);
645
922
  overflow: hidden;
646
923
  text-overflow: ellipsis;
647
924
  display: -webkit-box;
648
925
  -webkit-line-clamp: 2;
649
926
  -webkit-box-orient: vertical;
927
+ /* Long unbroken strings (URLs, mashed keys) wrap onto the clamped lines instead of clipping
928
+ mid-string — same treatment as the card body. */
929
+ overflow-wrap: anywhere;
650
930
  }
651
931
  /* meta line under the preview: page label and/or a resolved marker */
652
- .cmt-list__item-meta {
932
+ .tlui-cmt-list__item-meta {
653
933
  display: flex;
654
934
  align-items: center;
655
935
  gap: 8px;
@@ -657,22 +937,27 @@
657
937
  font-size: 11px;
658
938
  color: var(--tl-color-text-3);
659
939
  }
660
- .cmt-list__item-resolved {
940
+ .tlui-cmt-list__item-resolved {
661
941
  display: inline-flex;
662
942
  align-items: center;
663
943
  gap: 3px;
664
944
  font-weight: 600;
665
945
  }
666
- .cmt-list__item-page {
946
+ .tlui-cmt-list__item-page {
667
947
  display: inline-flex;
668
948
  align-items: center;
669
949
  padding: 1px 6px;
670
950
  border-radius: 4px;
671
951
  background: var(--tl-color-muted-2);
672
952
  }
673
- .cmt-list__item-count {
674
- flex-shrink: 0;
675
- align-self: center;
953
+ .tlui-cmt-list__item-replies {
954
+ display: inline-flex;
955
+ align-items: center;
956
+ }
957
+ /* reply count under a preview card's opening comment — the thread continues past what's shown */
958
+ .tlui-cmt-card__replies {
959
+ display: block;
960
+ margin-top: 6px;
676
961
  font-size: 12px;
677
962
  color: var(--tl-color-text-3);
678
963
  }