@agent-native/core-corpus 0.210.2 → 0.211.0

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 (192) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/.agents/skills/actions/references/action-fields.md +5 -3
  3. package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +41 -2
  4. package/corpus/templates/analytics/.agents/skills/data-querying/SKILL.md +4 -2
  5. package/corpus/templates/analytics/CHANGELOG.md +18 -0
  6. package/corpus/templates/analytics/actions/build-data-index.ts +11 -2
  7. package/corpus/templates/analytics/actions/configure-content-collection.ts +29 -4
  8. package/corpus/templates/analytics/actions/configure-first-party-events-model.ts +9 -7
  9. package/corpus/templates/analytics/actions/create-analytics-public-key.ts +4 -0
  10. package/corpus/templates/analytics/actions/estimate-content-signups-cost.ts +72 -0
  11. package/corpus/templates/analytics/actions/estimate-query-cost.ts +16 -134
  12. package/corpus/templates/analytics/actions/list-sigma-workbooks.ts +123 -42
  13. package/corpus/templates/analytics/actions/migrate-sigma-workbook.ts +20 -17
  14. package/corpus/templates/analytics/actions/revoke-analytics-public-key.ts +4 -0
  15. package/corpus/templates/analytics/app/components/dashboard/SigmaMigrationDialog.tsx +246 -680
  16. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +1 -1
  17. package/corpus/templates/analytics/app/i18n/en-US.ts +54 -55
  18. package/corpus/templates/analytics/app/pages/DashboardOverview.tsx +0 -1
  19. package/corpus/templates/analytics/app/pages/DataSources.tsx +56 -0
  20. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/filter-vars.ts +4 -3
  21. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +26 -8
  22. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/interpolate.ts +3 -1
  23. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/panel-cost-notice.ts +172 -0
  24. package/corpus/templates/analytics/app/pages/data-sources/SearchConsoleDetail.tsx +441 -0
  25. package/corpus/templates/analytics/changelog/2026-10-15-a-content-dashboard-lists-every-blog-article-with-its-author.md +6 -0
  26. package/corpus/templates/analytics/changelog/2026-10-15-data-sources-has-a-google-search-console-card-that-shows-the.md +6 -0
  27. package/corpus/templates/analytics/changelog/2026-10-16-the-content-dashboard-can-show-signups-per-blog-article.md +6 -0
  28. package/corpus/templates/analytics/changelog/2026-10-19-creating-or-revoking-a-public-analytics-key-now-asks-you-to-.md +6 -0
  29. package/corpus/templates/analytics/changelog/2026-10-19-search-sigma-workbooks-by-name-and-open-ones-a-teammate-alre.md +6 -0
  30. package/corpus/templates/analytics/changelog/2026-10-20-blog-signup-attribution-excludes-signups-before-the-first-vi.md +6 -0
  31. package/corpus/templates/analytics/changelog/2026-10-20-collapsed-analytics-navigation-starts-at-the-top.md +6 -0
  32. package/corpus/templates/analytics/changelog/2026-10-20-content-keeps-successful-coverage-dates-and-missing-day-gaps.md +6 -0
  33. package/corpus/templates/analytics/changelog/2026-10-20-private-github-repositories-can-be-indexed-with-your-connect.md +6 -0
  34. package/corpus/templates/analytics/changelog/2026-10-20-search-console-preserves-backfill-and-paused-collection.md +6 -0
  35. package/corpus/templates/analytics/changelog/2026-10-20-search-console-refreshes-historical-coverage-when-properties.md +6 -0
  36. package/corpus/templates/analytics/changelog/2026-10-20-search-console-setup-keeps-saved-properties-readable-and-sup.md +6 -0
  37. package/corpus/templates/analytics/seeds/dashboards/content-view.json +182 -0
  38. package/corpus/templates/analytics/server/db/schema.ts +1 -0
  39. package/corpus/templates/analytics/server/lib/bi-migration/external-dashboards-store.ts +56 -18
  40. package/corpus/templates/analytics/server/lib/bi-migration/unfinished-import.ts +64 -0
  41. package/corpus/templates/analytics/server/lib/builtin-dashboard-pass.ts +17 -2
  42. package/corpus/templates/analytics/server/lib/content-metrics/collect.ts +2 -0
  43. package/corpus/templates/analytics/server/lib/content-metrics/config.ts +92 -3
  44. package/corpus/templates/analytics/server/lib/content-metrics/legacy-content-view.json +172 -0
  45. package/corpus/templates/analytics/server/lib/content-metrics/migration.ts +2 -0
  46. package/corpus/templates/analytics/server/lib/content-metrics/steps/daily-metrics.ts +8 -3
  47. package/corpus/templates/analytics/server/lib/content-metrics/steps/search-console.ts +16 -3
  48. package/corpus/templates/analytics/server/lib/content-metrics/steps/signups.ts +240 -0
  49. package/corpus/templates/analytics/server/lib/content-metrics/store.ts +5 -0
  50. package/corpus/templates/analytics/server/lib/dashboard-catalog.ts +21 -0
  51. package/corpus/templates/analytics/server/lib/dashboard-panel-verification.ts +300 -5
  52. package/corpus/templates/analytics/server/lib/dashboard-seeds.ts +2 -0
  53. package/corpus/templates/analytics/server/lib/dashboards-store.ts +28 -0
  54. package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +128 -24
  55. package/corpus/templates/analytics/server/lib/google-analytics.ts +56 -16
  56. package/corpus/templates/analytics/server/lib/index-build-github.ts +5 -0
  57. package/corpus/templates/analytics/server/lib/provider-credentials.ts +47 -2
  58. package/corpus/templates/analytics/server/lib/query-cost-estimate.ts +184 -5
  59. package/corpus/templates/analytics/server/plugins/db.ts +9 -1
  60. package/corpus/templates/analytics/shared/builtin-dashboard-ids.ts +2 -0
  61. package/corpus/templates/analytics/shared/search-console-property.ts +78 -0
  62. package/corpus/templates/assets/.agents/skills/actions/references/action-fields.md +5 -3
  63. package/corpus/templates/brain/.agents/skills/actions/references/action-fields.md +5 -3
  64. package/corpus/templates/calendar/.agents/skills/actions/references/action-fields.md +5 -3
  65. package/corpus/templates/calendar/actions/delete-event.ts +1 -3
  66. package/corpus/templates/calendar/actions/delete-events.ts +1 -2
  67. package/corpus/templates/calendar/actions/event-action-helpers.ts +0 -4
  68. package/corpus/templates/calendar/actions/update-event.ts +2 -6
  69. package/corpus/templates/chat/.agents/skills/actions/references/action-fields.md +5 -3
  70. package/corpus/templates/clips/.agents/skills/actions/references/action-fields.md +5 -3
  71. package/corpus/templates/clips/CHANGELOG.md +11 -24
  72. package/corpus/templates/clips/actions/create-recording-upload.ts +5 -1
  73. package/corpus/templates/clips/app/components/player/chapter-list.tsx +7 -6
  74. package/corpus/templates/clips/app/components/player/comment-composer.tsx +5 -1
  75. package/corpus/templates/clips/app/components/player/player-controls.tsx +1 -0
  76. package/corpus/templates/clips/app/components/player/scrubber.tsx +4 -2
  77. package/corpus/templates/clips/app/i18n/en-US.ts +3 -3
  78. package/corpus/templates/clips/app/lib/extension-auth.ts +70 -0
  79. package/corpus/templates/clips/app/root.tsx +22 -10
  80. package/corpus/templates/clips/app/routes/_app.notifications.tsx +3 -0
  81. package/corpus/templates/clips/app/routes/import.tsx +115 -110
  82. package/corpus/templates/clips/changelog/2026-10-16-agent-uploads-can-use-free-clips-storage.md +6 -0
  83. package/corpus/templates/clips/changelog/2026-10-19-agents-see-a-clip-s-frames-as-soon-as-it-finishes-processing.md +6 -0
  84. package/corpus/templates/clips/changelog/2026-10-19-chapter-list-times-now-match-the-player-s-clock-and-each-cha.md +6 -0
  85. package/corpus/templates/clips/changelog/2026-10-19-editing-a-comment-puts-the-cursor-after-its-text-and-reply-o.md +6 -0
  86. package/corpus/templates/clips/changelog/2026-10-19-youtube-import-followups.md +6 -0
  87. package/corpus/templates/clips/changelog/2026-10-20-locally-saved-desktop-recordings-now-appear-in-the-recovery-.md +6 -0
  88. package/corpus/templates/clips/desktop/src/app.tsx +29 -186
  89. package/corpus/templates/clips/desktop/src/components/RecordingRecovery.tsx +171 -16
  90. package/corpus/templates/clips/desktop/src/lib/record-first.ts +9 -32
  91. package/corpus/templates/clips/desktop/src/lib/recording-recovery.ts +30 -1
  92. package/corpus/templates/clips/server/lib/agent-frame-request.ts +5 -2
  93. package/corpus/templates/clips/server/lib/recording-upload-ticket.ts +8 -0
  94. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +2 -0
  95. package/corpus/templates/content/.agents/skills/actions/references/action-fields.md +5 -3
  96. package/corpus/templates/content/CHANGELOG.md +12 -0
  97. package/corpus/templates/content/actions/_content-database-personal-view.ts +5 -15
  98. package/corpus/templates/content/actions/_database-navigation.ts +3 -3
  99. package/corpus/templates/content/actions/_database-utils.ts +10 -22
  100. package/corpus/templates/content/actions/_database-view-setup.ts +16 -76
  101. package/corpus/templates/content/actions/create-comment-ai-suggestion.ts +93 -44
  102. package/corpus/templates/content/actions/export-document.ts +2 -14
  103. package/corpus/templates/content/actions/suggest-document-edit.ts +5 -7
  104. package/corpus/templates/content/actions/update-content-database-view.ts +2 -14
  105. package/corpus/templates/content/app/components/editor/CommentsSidebar.tsx +86 -30
  106. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +64 -28
  107. package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +6 -46
  108. package/corpus/templates/content/app/components/editor/PageDraftRecovery.tsx +72 -7
  109. package/corpus/templates/content/app/components/editor/TableHoverControls.tsx +292 -103
  110. package/corpus/templates/content/app/components/editor/ThreadSuggestion.tsx +147 -40
  111. package/corpus/templates/content/app/components/editor/VisualEditor.tsx +108 -11
  112. package/corpus/templates/content/app/components/editor/database/ContentTable.tsx +2 -1
  113. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +150 -166
  114. package/corpus/templates/content/app/components/editor/database/database-cell-comments.tsx +515 -0
  115. package/corpus/templates/content/app/components/editor/database/database-item-comments.tsx +5 -43
  116. package/corpus/templates/content/app/components/editor/database/filter-sort.ts +8 -783
  117. package/corpus/templates/content/app/components/editor/live-editor-session.ts +5 -1
  118. package/corpus/templates/content/app/components/editor/page-draft-journal.ts +61 -1
  119. package/corpus/templates/content/app/components/editor/property-comments.tsx +35 -4
  120. package/corpus/templates/content/app/components/editor/property-date-format.ts +45 -0
  121. package/corpus/templates/content/app/components/editor/thread-suggestions.ts +30 -0
  122. package/corpus/templates/content/app/components/editor/useDocumentReconcileRecovery.ts +23 -4
  123. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +36 -10
  124. package/corpus/templates/content/app/global.css +23 -0
  125. package/corpus/templates/content/app/i18n-data.ts +1 -0
  126. package/corpus/templates/content/changelog/2026-10-16-opening-a-page-in-a-new-tab-while-another-tab-is-still-savin.md +6 -0
  127. package/corpus/templates/content/changelog/2026-10-16-typing-steadily-in-one-tab-no-longer-makes-another-open-tab-.md +6 -0
  128. package/corpus/templates/content/changelog/2026-10-19-database-views-can-filter-the-same-property-more-than-once-n.md +6 -0
  129. package/corpus/templates/content/changelog/2026-10-19-edits-made-from-a-block-s-menu-or-a-table-s-row-and-column-m.md +6 -0
  130. package/corpus/templates/content/changelog/2026-10-19-starting-a-comment-puts-the-cursor-in-the-comment-box-ready-.md +6 -0
  131. package/corpus/templates/content/changelog/2026-10-19-table-row-and-column-options-now-appear-for-the-row-or-colum.md +6 -0
  132. package/corpus/templates/content/docs/product/capabilities/content.comment.page-owned.md +5 -1
  133. package/corpus/templates/content/docs/product/capabilities/content.revision.suggestions.md +3 -1
  134. package/corpus/templates/content/e2e/helpers.ts +10 -0
  135. package/corpus/templates/content/shared/api.ts +18 -12
  136. package/corpus/templates/content/shared/database-query.ts +107 -13
  137. package/corpus/templates/content/shared/suggestion-diff.ts +35 -0
  138. package/corpus/templates/crm/.agents/skills/actions/references/action-fields.md +5 -3
  139. package/corpus/templates/design/.agents/skills/actions/references/action-fields.md +5 -3
  140. package/corpus/templates/design/CHANGELOG.md +2 -0
  141. package/corpus/templates/design/actions/delete-file.ts +1 -4
  142. package/corpus/templates/design/app/components/design/LayersPanel.tsx +10 -1
  143. package/corpus/templates/design/app/components/visual-editor/ReviewCanvasPins.tsx +24 -4
  144. package/corpus/templates/design/app/pages/design-editor/commands/mode-change.ts +14 -9
  145. package/corpus/templates/design/app/pages/design-editor/domains/use-editor-modes.ts +23 -2
  146. package/corpus/templates/design/app/pages/design-editor/tool-state.ts +29 -0
  147. package/corpus/templates/design/changelog/2026-10-19-reply-and-edit-on-a-comment-in-the-sidebar-or-on-a-canvas-pi.md +6 -0
  148. package/corpus/templates/design/changelog/2026-10-20-comments-return-to-the-mode-they-came-from-and-layers-search.md +6 -0
  149. package/corpus/templates/design/e2e/global-setup.ts +185 -85
  150. package/corpus/templates/dispatch/.agents/skills/actions/references/action-fields.md +5 -3
  151. package/corpus/templates/dispatch/CHANGELOG.md +4 -0
  152. package/corpus/templates/dispatch/actions/apply-workspace-connection-setup.ts +4 -0
  153. package/corpus/templates/dispatch/actions/delete-workspace-connection.ts +4 -0
  154. package/corpus/templates/dispatch/actions/set-workspace-connection-grant.ts +4 -0
  155. package/corpus/templates/dispatch/actions/upsert-workspace-connection.ts +4 -0
  156. package/corpus/templates/dispatch/changelog/2026-10-19-vault-secrets-grants-link-tokens-app-access-and-workspace-co.md +6 -0
  157. package/corpus/templates/factory/.agents/skills/actions/references/action-fields.md +5 -3
  158. package/corpus/templates/factory/.agents/skills/review-latest-feedback/SKILL.md +105 -33
  159. package/corpus/templates/factory/.agents/skills/review-latest-feedback/references/channel-inventory.md +128 -74
  160. package/corpus/templates/factory/.agents/skills/review-latest-feedback/references/slack-reaction-replies.md +39 -11
  161. package/corpus/templates/forms/.agents/skills/actions/references/action-fields.md +5 -3
  162. package/corpus/templates/mail/.agents/skills/actions/references/action-fields.md +5 -3
  163. package/corpus/templates/mail/.agents/skills/contacts-and-crm/SKILL.md +22 -17
  164. package/corpus/templates/mail/AGENTS.md +1 -1
  165. package/corpus/templates/mail/CHANGELOG.md +14 -0
  166. package/corpus/templates/mail/actions/provider-api-request.ts +3 -3
  167. package/corpus/templates/mail/app/components/email/IntegrationsSidebar.tsx +211 -54
  168. package/corpus/templates/mail/app/hooks/use-inbox-threads.ts +26 -2
  169. package/corpus/templates/mail/app/i18n/en-US.ts +5 -1
  170. package/corpus/templates/mail/app/lib/gong-connection.ts +27 -0
  171. package/corpus/templates/mail/app/lib/thread-cache.ts +110 -9
  172. package/corpus/templates/mail/app/root.tsx +10 -2
  173. package/corpus/templates/mail/changelog/2026-10-20-connected-gong-settings-now-open-the-shared-integration-mana.md +6 -0
  174. package/corpus/templates/mail/changelog/2026-10-20-gong-connection-details-now-match-the-call-data-available-in.md +6 -0
  175. package/corpus/templates/mail/changelog/2026-10-20-improve-gong-setup-with-a-click-to-connect-flow-and-permissi.md +6 -0
  176. package/corpus/templates/mail/changelog/2026-10-20-mail-lets-you-retry-when-it-cannot-check-your-gong-access.md +6 -0
  177. package/corpus/templates/mail/changelog/2026-10-20-mail-thread-cache-stays-isolated-when-mailbox-scope-changes.md +6 -0
  178. package/corpus/templates/mail/changelog/2026-10-20-the-inbox-and-recently-opened-threads-now-open-instantly-fro.md +6 -0
  179. package/corpus/templates/mail/server/handlers/emails.ts +9 -0
  180. package/corpus/templates/plan/.agents/skills/actions/references/action-fields.md +5 -3
  181. package/corpus/templates/plan/CHANGELOG.md +4 -0
  182. package/corpus/templates/plan/app/pages/PlansPage.tsx +173 -33
  183. package/corpus/templates/plan/changelog/2026-10-19-opening-a-comment-thread-puts-the-cursor-in-the-reply-box-an.md +6 -0
  184. package/corpus/templates/slides/.agents/skills/actions/references/action-fields.md +5 -3
  185. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +6 -42
  186. package/corpus/templates/slides/app/components/editor/FloatingToolbar.tsx +3 -7
  187. package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +5 -14
  188. package/corpus/templates/slides/app/i18n/en-US.ts +0 -2
  189. package/corpus/templates/slides/app/pages/DeckEditor.tsx +27 -33
  190. package/corpus/templates/slides/scripts/edit-fidelity/run.ts +1 -3
  191. package/corpus/templates/tasks/.agents/skills/actions/references/action-fields.md +5 -3
  192. package/package.json +1 -1
@@ -20,26 +20,73 @@ import { trimDiffContext, wordDiff } from "./comment-ai-diff";
20
20
  import { CommentRow } from "./CommentRow";
21
21
  import { suggestionLineExcerpt } from "./thread-suggestions";
22
22
 
23
- /**
24
- * An AI suggestion shown in place of its receipt reply inside the comment
25
- * thread that asked for it. Deciding it changes only the suggestion; the
26
- * thread stays open until someone resolves it.
27
- */
28
- export function ThreadSuggestionBody({
23
+ type ThreadSuggestionProps = {
24
+ /** One suggestion, or every edit of the proposal it belongs to, in Page order. */
25
+ suggestions: ResourceSuggestion[];
26
+ canDecide: boolean;
27
+ /** A decision, or an AI revision in this thread, is still in flight. */
28
+ busy: (suggestion: ResourceSuggestion) => boolean;
29
+ conflict: (suggestion: ResourceSuggestion) => boolean;
30
+ onDecide: (
31
+ suggestion: ResourceSuggestion,
32
+ decision: SuggestionDecision,
33
+ ) => void;
34
+ /** Decides every edit of the proposal still pending. */
35
+ onDecideRemaining?: (decision: SuggestionDecision) => void;
36
+ };
37
+
38
+ function useStatusLabel() {
39
+ const t = useT();
40
+ return (status: ResourceSuggestion["status"]) =>
41
+ status === "accepted"
42
+ ? { icon: IconCircleCheck, label: t("comments.accepted") }
43
+ : status === "rejected"
44
+ ? { icon: IconCircleX, label: t("comments.rejected") }
45
+ : status === "superseded"
46
+ ? { icon: IconArrowBackUp, label: t("comments.suggestionReplaced") }
47
+ : status === "withdrawn"
48
+ ? {
49
+ icon: IconArrowBackUp,
50
+ label: t("comments.suggestionWithdrawn"),
51
+ }
52
+ : null;
53
+ }
54
+
55
+ function SuggestionStatus({
56
+ status,
57
+ }: {
58
+ status: ReturnType<ReturnType<typeof useStatusLabel>>;
59
+ }) {
60
+ if (!status) return null;
61
+ return (
62
+ <span
63
+ role="status"
64
+ className="inline-flex items-center gap-1.5 text-xs font-medium text-muted-foreground"
65
+ >
66
+ <status.icon size={14} aria-hidden />
67
+ {status.label}
68
+ </span>
69
+ );
70
+ }
71
+
72
+ /** One edit's word diff, with its own Accept and Reject or outcome. */
73
+ function ThreadSuggestionEdit({
29
74
  suggestion,
30
75
  canDecide,
31
76
  busy,
32
77
  conflict,
33
78
  onDecide,
79
+ showStatus,
34
80
  }: {
35
81
  suggestion: ResourceSuggestion;
36
82
  canDecide: boolean;
37
- /** A decision, or an AI revision in this thread, is still in flight. */
38
83
  busy: boolean;
39
84
  conflict: boolean;
40
85
  onDecide: (decision: SuggestionDecision) => void;
86
+ showStatus: boolean;
41
87
  }) {
42
88
  const t = useT();
89
+ const statusLabel = useStatusLabel();
43
90
  const excerpt = useMemo(
44
91
  () => suggestionLineExcerpt(suggestion.operations),
45
92
  [suggestion.operations],
@@ -50,28 +97,12 @@ export function ThreadSuggestionBody({
50
97
  [excerpt],
51
98
  );
52
99
  const pending = suggestion.status === "pending";
53
- const status =
54
- suggestion.status === "accepted"
55
- ? { icon: IconCircleCheck, label: t("comments.accepted") }
56
- : suggestion.status === "rejected"
57
- ? { icon: IconCircleX, label: t("comments.rejected") }
58
- : suggestion.status === "superseded"
59
- ? { icon: IconArrowBackUp, label: t("comments.suggestionReplaced") }
60
- : suggestion.status === "withdrawn"
61
- ? {
62
- icon: IconArrowBackUp,
63
- label: t("comments.suggestionWithdrawn"),
64
- }
65
- : null;
66
100
  return (
67
101
  <div
68
- className="mt-0.5 grid gap-2"
102
+ className="grid grid-cols-1 gap-2"
69
103
  data-thread-suggestion={suggestion.id}
70
104
  data-thread-suggestion-status={suggestion.status}
71
105
  >
72
- <p className={cn(!pending && "text-muted-foreground")}>
73
- {suggestion.summary}
74
- </p>
75
106
  {segments.length ? (
76
107
  <p
77
108
  aria-label={t("comments.suggestedChange")}
@@ -108,7 +139,7 @@ export function ThreadSuggestionBody({
108
139
  </p>
109
140
  ) : null}
110
141
  {pending && canDecide ? (
111
- <div className="flex items-center gap-1.5">
142
+ <div className="flex flex-wrap items-center gap-1.5">
112
143
  <Button
113
144
  type="button"
114
145
  size="sm"
@@ -137,27 +168,103 @@ export function ThreadSuggestionBody({
137
168
  {t("editor.rejectSuggestion")}
138
169
  </Button>
139
170
  </div>
140
- ) : status ? (
141
- <span
142
- role="status"
143
- className="inline-flex items-center gap-1.5 text-xs font-medium text-muted-foreground"
144
- >
145
- <status.icon size={14} aria-hidden />
146
- {status.label}
147
- </span>
171
+ ) : showStatus ? (
172
+ <SuggestionStatus status={statusLabel(suggestion.status)} />
173
+ ) : null}
174
+ </div>
175
+ );
176
+ }
177
+
178
+ /**
179
+ * An AI suggestion shown in place of its receipt reply inside the comment
180
+ * thread that asked for it. Separate changes arrive as one proposal, so the
181
+ * summary is said once above every edit. Deciding changes only the
182
+ * suggestions; the thread stays open until someone resolves it.
183
+ */
184
+ export function ThreadSuggestionBody({
185
+ suggestions,
186
+ canDecide,
187
+ busy,
188
+ conflict,
189
+ onDecide,
190
+ onDecideRemaining,
191
+ }: ThreadSuggestionProps) {
192
+ const t = useT();
193
+ const statusLabel = useStatusLabel();
194
+ const [first] = suggestions;
195
+ if (!first) return null;
196
+ const pending = suggestions.filter(
197
+ (suggestion) => suggestion.status === "pending",
198
+ );
199
+ // Edits that all ended the same way say so once, under the last of them.
200
+ const sharedStatus =
201
+ suggestions.length > 1 &&
202
+ suggestions.every((suggestion) => suggestion.status === first.status)
203
+ ? first.status
204
+ : null;
205
+ return (
206
+ <div
207
+ className="mt-0.5 grid grid-cols-1 gap-2"
208
+ data-thread-suggestion-group={first.proposalId ?? first.id}
209
+ >
210
+ <p className={cn(!pending.length && "text-muted-foreground")}>
211
+ {first.proposalSummary || first.summary}
212
+ </p>
213
+ {suggestions.map((suggestion) => (
214
+ <ThreadSuggestionEdit
215
+ key={suggestion.id}
216
+ suggestion={suggestion}
217
+ canDecide={canDecide}
218
+ busy={busy(suggestion)}
219
+ conflict={conflict(suggestion)}
220
+ onDecide={(decision) => onDecide(suggestion, decision)}
221
+ showStatus={!sharedStatus}
222
+ />
223
+ ))}
224
+ {sharedStatus ? (
225
+ <SuggestionStatus status={statusLabel(sharedStatus)} />
226
+ ) : null}
227
+ {canDecide && onDecideRemaining && pending.length > 1 ? (
228
+ <div className="flex flex-wrap items-center gap-1.5">
229
+ <Button
230
+ type="button"
231
+ size="sm"
232
+ variant="ghost"
233
+ disabled={pending.some(busy) || pending.some(conflict)}
234
+ onClick={(event) => {
235
+ event.stopPropagation();
236
+ onDecideRemaining("accepted");
237
+ }}
238
+ data-suggestion-proposal-decision="accepted"
239
+ >
240
+ {t("comments.acceptRemaining")}
241
+ </Button>
242
+ <Button
243
+ type="button"
244
+ size="sm"
245
+ variant="ghost"
246
+ className="text-muted-foreground"
247
+ disabled={pending.some(busy)}
248
+ onClick={(event) => {
249
+ event.stopPropagation();
250
+ onDecideRemaining("rejected");
251
+ }}
252
+ data-suggestion-proposal-decision="rejected"
253
+ >
254
+ {t("comments.rejectRemaining")}
255
+ </Button>
256
+ </div>
148
257
  ) : null}
149
258
  </div>
150
259
  );
151
260
  }
152
261
 
153
262
  /** A thread suggestion whose receipt reply is not in the thread. */
154
- export function ThreadSuggestionRow(
155
- props: Parameters<typeof ThreadSuggestionBody>[0],
156
- ) {
263
+ export function ThreadSuggestionRow(props: ThreadSuggestionProps) {
264
+ const [first] = props.suggestions;
265
+ if (!first) return null;
157
266
  const model =
158
- typeof props.suggestion.metadata?.model === "string"
159
- ? props.suggestion.metadata.model
160
- : null;
267
+ typeof first.metadata?.model === "string" ? first.metadata.model : null;
161
268
  return (
162
269
  <CommentRow
163
270
  avatar={<AgentAvatar model={model} />}
@@ -167,7 +274,7 @@ export function ThreadSuggestionRow(
167
274
  model,
168
275
  run: {
169
276
  effort: null,
170
- ranAt: props.suggestion.createdAt,
277
+ ranAt: first.createdAt,
171
278
  threadId: null,
172
279
  canOpen: false,
173
280
  },
@@ -67,6 +67,7 @@ import {
67
67
  TextSelection,
68
68
  type Transaction,
69
69
  } from "@tiptap/pm/state";
70
+ import { CellSelection, cellAround } from "@tiptap/pm/tables";
70
71
  import { Decoration, DecorationSet, type EditorView } from "@tiptap/pm/view";
71
72
  import {
72
73
  useEditor,
@@ -738,6 +739,25 @@ const NotionMarkdownShortcuts = Extension.create({
738
739
  });
739
740
 
740
741
  const CustomTable = BaseTable.extend({
742
+ addKeyboardShortcuts() {
743
+ return {
744
+ ...this.parent?.(),
745
+ // Escape leaves the editor (`contentEditorEscape`), and Tab can't leave a
746
+ // table, so from a cell's text the first Escape only selects the cell.
747
+ Escape: ({ editor }) => {
748
+ const { selection } = editor.state;
749
+ if (selection instanceof CellSelection) return false;
750
+ const $cell = cellAround(selection.$from);
751
+ if (!$cell) return false;
752
+ editor.view.dispatch(
753
+ editor.state.tr.setSelection(
754
+ CellSelection.create(editor.state.doc, $cell.pos),
755
+ ),
756
+ );
757
+ return true;
758
+ },
759
+ };
760
+ },
741
761
  addAttributes() {
742
762
  return {
743
763
  ...this.parent?.(),
@@ -835,6 +855,14 @@ const NotionTableHeader = TableHeader.extend({
835
855
  addAttributes() {
836
856
  return { ...this.parent?.(), ...tableAlignmentAttribute };
837
857
  },
858
+ // Header cells render as <td>, so copying or pasting a table reads them back
859
+ // from their class. The higher priority puts this rule before tableCell's.
860
+ parseHTML() {
861
+ return [
862
+ { tag: "td.content-table-header-cell", priority: 60 },
863
+ ...(this.parent?.() ?? []),
864
+ ];
865
+ },
838
866
  renderHTML({ HTMLAttributes }) {
839
867
  return [
840
868
  "td",
@@ -907,7 +935,39 @@ function normalizeTableHeaderCells(
907
935
  return rowChanged ? row.copy(Fragment.fromArray(normalizedCells)) : row;
908
936
  });
909
937
 
910
- return changed ? table.copy(Fragment.fromArray(normalizedRows)) : table;
938
+ // Saved tables keep their headers only in these attributes, so update them
939
+ // whenever they no longer produce the header cells, e.g. after a header
940
+ // toggle or after deleting the header row.
941
+ const isHeaderAt = (
942
+ headerRow: boolean,
943
+ headerColumn: boolean,
944
+ rowIndex: number,
945
+ columnIndex: number,
946
+ ) => (headerRow && rowIndex === 0) || (headerColumn && columnIndex === 0);
947
+ const savedHeaderRow = table.attrs.headerRow === true;
948
+ const savedHeaderColumn = table.attrs.headerColumn === true;
949
+ const attrsMatchCells = rows.every((row, rowIndex) =>
950
+ getNodeChildren(row).every(
951
+ (_cell, columnIndex) =>
952
+ isHeaderAt(savedHeaderRow, savedHeaderColumn, rowIndex, columnIndex) ===
953
+ isHeaderAt(hasHeaderRow, hasHeaderColumn, rowIndex, columnIndex),
954
+ ),
955
+ );
956
+
957
+ if (!changed && attrsMatchCells) return table;
958
+
959
+ const content = changed ? Fragment.fromArray(normalizedRows) : table.content;
960
+ return attrsMatchCells
961
+ ? table.copy(content)
962
+ : table.type.create(
963
+ {
964
+ ...table.attrs,
965
+ headerRow: hasHeaderRow,
966
+ headerColumn: hasHeaderColumn,
967
+ },
968
+ content,
969
+ table.marks,
970
+ );
911
971
  }
912
972
 
913
973
  const normalizeTableHeadersPluginKey = new PluginKey("normalizeTableHeaders");
@@ -929,11 +989,20 @@ function buildNormalizeTableHeadersTransaction(state: CoreEditor["state"]) {
929
989
  tableHeaderType,
930
990
  );
931
991
  if (normalizedTable !== node) {
932
- transaction = transaction.replaceWith(
933
- pos,
934
- pos + node.nodeSize,
935
- normalizedTable,
936
- );
992
+ // When only the header attributes change, keep the cells, and the
993
+ // selection inside them, in place.
994
+ transaction =
995
+ normalizedTable.content === node.content
996
+ ? transaction.setNodeMarkup(
997
+ transaction.mapping.map(pos),
998
+ undefined,
999
+ normalizedTable.attrs,
1000
+ )
1001
+ : transaction.replaceWith(
1002
+ transaction.mapping.map(pos),
1003
+ transaction.mapping.map(pos + node.nodeSize),
1004
+ normalizedTable,
1005
+ );
937
1006
  changed = true;
938
1007
  }
939
1008
 
@@ -3351,7 +3420,9 @@ export function VisualEditor({
3351
3420
  }),
3352
3421
  Extension.create({
3353
3422
  name: "contentEditorEscape",
3354
- priority: 0,
3423
+ // Last, so editor Escape handlers such as the table's run first. Not 0:
3424
+ // TipTap reads a priority of 0 as unset, which puts this first instead.
3425
+ priority: 1,
3355
3426
  addProseMirrorPlugins() {
3356
3427
  return [
3357
3428
  new Plugin({
@@ -3397,6 +3468,12 @@ export function VisualEditor({
3397
3468
 
3398
3469
  const guardsRef = useRef<UseCollabReconcileResult | null>(null);
3399
3470
  const draftEmissionGenerationRef = useRef(0);
3471
+ const pendingDraftEmissionsRef = useRef<Array<() => void>>([]);
3472
+ const drainPendingDraftEmissions = useCallback(() => {
3473
+ const pending = pendingDraftEmissionsRef.current;
3474
+ pendingDraftEmissionsRef.current = [];
3475
+ for (const emit of pending) emit();
3476
+ }, []);
3400
3477
  const lastUserEditIntentAtRef = useRef(0);
3401
3478
  const hasUserEditIntentRef = useRef(false);
3402
3479
  const markUserEditIntent = useCallback(() => {
@@ -3439,10 +3516,11 @@ export function VisualEditor({
3439
3516
  if (options?.immediate) return "failed" as const;
3440
3517
  if (!guards.registerEmitted(normalized)) return "unchanged" as const;
3441
3518
  const generation = draftEmissionGenerationRef.current;
3442
- setTimeout(() => {
3519
+ pendingDraftEmissionsRef.current.push(() => {
3443
3520
  if (generation === draftEmissionGenerationRef.current)
3444
3521
  onChangeRef.current(normalized);
3445
- }, 0);
3522
+ });
3523
+ setTimeout(drainPendingDraftEmissions, 0);
3446
3524
  return "scheduled" as const;
3447
3525
  } catch (err: any) {
3448
3526
  toast.error(
@@ -3452,7 +3530,24 @@ export function VisualEditor({
3452
3530
  return "failed" as const;
3453
3531
  }
3454
3532
  },
3455
- [content, localFileMode, scheduleLiveBodyParity, t],
3533
+ [
3534
+ content,
3535
+ drainPendingDraftEmissions,
3536
+ localFileMode,
3537
+ scheduleLiveBodyParity,
3538
+ t,
3539
+ ],
3540
+ );
3541
+ // A remote snapshot is the whole live document, so it already holds local
3542
+ // typing whose deferred report has not run. Reporting that typing first keeps
3543
+ // it a local edit; otherwise the snapshot absorbs it and an older save's
3544
+ // acknowledgment can clear the only journaled copy.
3545
+ const reportRemoteSnapshot = useCallback(
3546
+ (markdown: string) => {
3547
+ drainPendingDraftEmissions();
3548
+ onRemoteSnapshotChange?.(markdown);
3549
+ },
3550
+ [drainPendingDraftEmissions, onRemoteSnapshotChange],
3456
3551
  );
3457
3552
  onMediaSourceCommittedRef.current = async (editorToPersist, transaction) => {
3458
3553
  if (suggestingRef.current) return;
@@ -4189,7 +4284,9 @@ export function VisualEditor({
4189
4284
  : collabContentRevision,
4190
4285
  requestCollabSync,
4191
4286
  onBaseAwareReconcile,
4192
- onRemoteSnapshotChange,
4287
+ onRemoteSnapshotChange: onRemoteSnapshotChange
4288
+ ? reportRemoteSnapshot
4289
+ : undefined,
4193
4290
  isEditorClean,
4194
4291
  requestInitialSeed:
4195
4292
  ydoc && editable && documentId ? requestInitialSeed : undefined,
@@ -42,7 +42,8 @@ function subscribeToNoHover(onChange: () => void) {
42
42
  return () => media.removeEventListener("change", onChange);
43
43
  }
44
44
 
45
- function deviceCannotHover() {
45
+ /** Whether no pointer on this device can hover, as on a phone. */
46
+ export function deviceCannotHover() {
46
47
  return (
47
48
  typeof window !== "undefined" &&
48
49
  typeof window.matchMedia === "function" &&