@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
@@ -349,7 +349,12 @@ export {
349
349
  };
350
350
  export type { NativeMarkerPlacement } from "@/lib/plan-native-anchors";
351
351
  export type { PlanOrgAccessPrompt } from "@/lib/plan-access-prompt";
352
- export { canSubmitInlineCommentDraft, mentionQueryAtCaret };
352
+ export {
353
+ AnnotationPopover,
354
+ canSubmitInlineCommentDraft,
355
+ CommentMentionEditor,
356
+ mentionQueryAtCaret,
357
+ };
353
358
  export type { CommentDraft } from "@/lib/plan-comment-editor-helpers";
354
359
 
355
360
  export function buildPlanEmailVerificationCallbackURL(
@@ -5742,6 +5747,7 @@ export function PlansPage({ localPlanSlug }: { localPlanSlug?: string } = {}) {
5742
5747
  ) : (
5743
5748
  <InlineCommentPopover
5744
5749
  key={failedCommentDraft ? "retry" : "new"}
5750
+ anchor={pendingAnnotation}
5745
5751
  position={inlineCommentPosition}
5746
5752
  initialDraft={
5747
5753
  failedCommentDraft ?? defaultInlineCommentDraft
@@ -5749,6 +5755,7 @@ export function PlansPage({ localPlanSlug }: { localPlanSlug?: string } = {}) {
5749
5755
  onCancel={closeInlineComment}
5750
5756
  onSubmit={submitInlineComment}
5751
5757
  lockToAgent={localPlanMode}
5758
+ reopened={Boolean(failedCommentDraft)}
5752
5759
  />
5753
5760
  )}
5754
5761
  </>
@@ -8930,9 +8937,26 @@ function CreatePlanDialog({
8930
8937
  );
8931
8938
  }
8932
8939
 
8940
+ /** Nothing on the page holds focus, so taking it moves nobody's caret. */
8941
+ function focusIsFree() {
8942
+ const active = document.activeElement;
8943
+ return !active || active === document.body;
8944
+ }
8945
+
8946
+ function focusCommentEditorAtEnd(root: HTMLElement) {
8947
+ root.focus();
8948
+ const selection = window.getSelection();
8949
+ const range = document.createRange();
8950
+ range.selectNodeContents(root);
8951
+ range.collapse(false);
8952
+ selection?.removeAllRanges();
8953
+ selection?.addRange(range);
8954
+ }
8955
+
8933
8956
  function CommentMentionEditor({
8934
8957
  initialMessage,
8935
8958
  resetKey,
8959
+ focusKey,
8936
8960
  placeholder,
8937
8961
  className,
8938
8962
  autoFocus,
@@ -8941,9 +8965,12 @@ function CommentMentionEditor({
8941
8965
  }: {
8942
8966
  initialMessage?: string;
8943
8967
  resetKey?: string;
8968
+ /** A new value puts the caret back in the editor and keeps its text. */
8969
+ focusKey?: unknown;
8944
8970
  placeholder: string;
8945
8971
  className?: string;
8946
- autoFocus?: boolean;
8972
+ /** "if-free" only takes focus nobody holds, for a box reopened after a failed save. */
8973
+ autoFocus?: boolean | "if-free";
8947
8974
  onChange: (draft: Pick<CommentDraft, "message" | "mentions">) => void;
8948
8975
  onSubmitShortcut?: () => void;
8949
8976
  }) {
@@ -8975,19 +9002,36 @@ function CommentMentionEditor({
8975
9002
  useEffect(() => {
8976
9003
  const root = editorRef.current;
8977
9004
  if (!root) return;
9005
+ const hadFocus = root.contains(document.activeElement);
8978
9006
  appendMessageToEditor(root, initialMessage ?? "");
8979
9007
  emitChange();
8980
- if (autoFocus) {
8981
- root.focus();
8982
- const selection = window.getSelection();
8983
- const range = document.createRange();
8984
- range.selectNodeContents(root);
8985
- range.collapse(false);
8986
- selection?.removeAllRanges();
8987
- selection?.addRange(range);
8988
- }
9008
+ // Reloading the text keeps the caret in an editor that had it.
9009
+ if (hadFocus) focusCommentEditorAtEnd(root);
9010
+ if (!autoFocus || (autoFocus === "if-free" && !focusIsFree())) return;
9011
+ // An Edit menu item mounts this editor while its menu still traps focus,
9012
+ // so keep trying for a few frames until focus lands.
9013
+ let attempts = 0;
9014
+ let frame = 0;
9015
+ const focusEditor = () => {
9016
+ focusCommentEditorAtEnd(root);
9017
+ if (document.activeElement !== root && ++attempts < 10)
9018
+ frame = requestAnimationFrame(focusEditor);
9019
+ };
9020
+ focusEditor();
9021
+ return () => cancelAnimationFrame(frame);
8989
9022
  }, [autoFocus, emitChange, initialMessage, resetKey]);
8990
9023
 
9024
+ // Only a new key moves the caret; mounting is autoFocus's job.
9025
+ const focusKeyRef = useRef(focusKey);
9026
+ useEffect(() => {
9027
+ if (focusKeyRef.current === focusKey) return;
9028
+ focusKeyRef.current = focusKey;
9029
+ const root = editorRef.current;
9030
+ if (!root || focusKey === undefined) return;
9031
+ if (root.contains(document.activeElement)) return;
9032
+ focusCommentEditorAtEnd(root);
9033
+ }, [focusKey]);
9034
+
8991
9035
  const selectMember = (member: OrgMemberSuggestion) => {
8992
9036
  const root = editorRef.current;
8993
9037
  const active = activeQueryRef.current;
@@ -9158,17 +9202,22 @@ function ResolutionTargetToggle({
9158
9202
  }
9159
9203
 
9160
9204
  function InlineCommentPopover({
9205
+ anchor,
9161
9206
  position,
9162
9207
  initialDraft,
9163
9208
  onCancel,
9164
9209
  onSubmit,
9165
9210
  lockToAgent = false,
9211
+ reopened = false,
9166
9212
  }: {
9213
+ anchor: PlanAnnotationAnchor;
9167
9214
  position: InlineCommentPosition;
9168
9215
  initialDraft: CommentDraft;
9169
9216
  onCancel: () => void;
9170
9217
  onSubmit: (draft: CommentDraft) => Promise<void>;
9171
9218
  lockToAgent?: boolean;
9219
+ /** Shown again after a failed save, when the person may have moved on. */
9220
+ reopened?: boolean;
9172
9221
  }) {
9173
9222
  const t = useT();
9174
9223
  const initialMessageRef = useRef(initialDraft.message);
@@ -9249,7 +9298,10 @@ function InlineCommentPopover({
9249
9298
  <CommentMentionEditor
9250
9299
  initialMessage={initialMessageRef.current}
9251
9300
  placeholder={t("plansPage.comments.addPlaceholder")}
9252
- autoFocus
9301
+ autoFocus={reopened ? "if-free" : true}
9302
+ // Commenting somewhere else moves this popover instead of
9303
+ // remounting it, so the draft keeps its text and gets the caret.
9304
+ focusKey={anchor}
9253
9305
  onSubmitShortcut={submit}
9254
9306
  onChange={(value) =>
9255
9307
  setDraft((current) => {
@@ -9387,42 +9439,110 @@ function CommentDeleteButton({
9387
9439
  );
9388
9440
  }
9389
9441
 
9442
+ /** What a reply box shows: the editor reloads `loadedMessage` when `version` changes. */
9443
+ function loadReplyBox({
9444
+ threadId,
9445
+ message,
9446
+ version,
9447
+ takeFocus,
9448
+ }: {
9449
+ threadId: string;
9450
+ message: string;
9451
+ version: number;
9452
+ takeFocus: boolean;
9453
+ }) {
9454
+ const draft: Pick<CommentDraft, "message" | "mentions"> = {
9455
+ message,
9456
+ mentions: extractCommentMentions(message),
9457
+ };
9458
+ return { threadId, loadedMessage: message, version, takeFocus, draft };
9459
+ }
9460
+
9390
9461
  function ReplyComposer({
9391
9462
  author,
9392
9463
  isPending,
9393
9464
  placeholder,
9465
+ autoFocus,
9466
+ threadId = "",
9394
9467
  onSubmit,
9395
9468
  }: {
9396
9469
  author: CommentAuthorPresentation;
9397
9470
  isPending: boolean;
9398
9471
  placeholder?: string;
9472
+ autoFocus?: boolean;
9473
+ /** One box can reply to several threads in turn; each keeps its own draft. */
9474
+ threadId?: string;
9399
9475
  onSubmit: (message: string) => Promise<void>;
9400
9476
  }) {
9401
9477
  const t = useT();
9402
- const [draft, setDraft] = useState<
9403
- Pick<CommentDraft, "message" | "mentions">
9404
- >({
9405
- message: "",
9406
- mentions: [],
9407
- });
9408
- const [submitError, setSubmitError] = useState(false);
9409
- const [isSubmitting, setIsSubmitting] = useState(false);
9410
- const [resetKey, setResetKey] = useState(0);
9478
+ // Unsent replies by thread, including the one on screen.
9479
+ const [drafts] = useState(() => new Map<string, string>());
9480
+ // The thread on screen and its draft change together, so Send always
9481
+ // pairs them.
9482
+ const [box, setBox] = useState(() =>
9483
+ loadReplyBox({ threadId, message: "", version: 0, takeFocus: true }),
9484
+ );
9485
+ const [sendingThreadIds, setSendingThreadIds] = useState<string[]>([]);
9486
+ const [failedThreadIds, setFailedThreadIds] = useState<string[]>([]);
9487
+
9488
+ if (box.threadId !== threadId) {
9489
+ // Opening another thread brings back that thread's own reply.
9490
+ setBox(
9491
+ loadReplyBox({
9492
+ threadId,
9493
+ message: drafts.get(threadId) ?? "",
9494
+ version: box.version + 1,
9495
+ takeFocus: true,
9496
+ }),
9497
+ );
9498
+ }
9411
9499
 
9500
+ const isSubmitting = sendingThreadIds.includes(box.threadId);
9412
9501
  const canSubmit =
9413
- commentBodyText(draft.message).length > 0 && !isSubmitting && !isPending;
9502
+ commentBodyText(box.draft.message).length > 0 &&
9503
+ !isSubmitting &&
9504
+ !isPending;
9414
9505
  const submit = async () => {
9415
9506
  if (!canSubmit) return;
9416
- setSubmitError(false);
9417
- setIsSubmitting(true);
9507
+ const sentThreadId = box.threadId;
9508
+ const message = box.draft.message;
9509
+ setFailedThreadIds((ids) => ids.filter((id) => id !== sentThreadId));
9510
+ setSendingThreadIds((ids) => [...ids, sentThreadId]);
9511
+ // Empty the box right away. The reply shows in the thread while it
9512
+ // sends, and anything typed meanwhile is a new draft.
9513
+ drafts.set(sentThreadId, "");
9514
+ setBox((current) =>
9515
+ loadReplyBox({
9516
+ threadId: current.threadId,
9517
+ message: "",
9518
+ version: current.version + 1,
9519
+ takeFocus: true,
9520
+ }),
9521
+ );
9418
9522
  try {
9419
- await onSubmit(draft.message.trim());
9420
- setDraft({ message: "", mentions: [] });
9421
- setResetKey((key) => key + 1);
9523
+ await onSubmit(message.trim());
9422
9524
  } catch {
9423
- setSubmitError(true);
9525
+ // Put the reply back in its thread's box, ahead of anything typed
9526
+ // since, wherever that box is now.
9527
+ const typedSince = drafts.get(sentThreadId)?.trim();
9528
+ const restored = typedSince ? `${message} ${typedSince}` : message;
9529
+ drafts.set(sentThreadId, restored);
9530
+ setFailedThreadIds((ids) => [
9531
+ ...ids.filter((id) => id !== sentThreadId),
9532
+ sentThreadId,
9533
+ ]);
9534
+ setBox((current) =>
9535
+ current.threadId === sentThreadId
9536
+ ? loadReplyBox({
9537
+ threadId: sentThreadId,
9538
+ message: restored,
9539
+ version: current.version + 1,
9540
+ takeFocus: false,
9541
+ })
9542
+ : current,
9543
+ );
9424
9544
  } finally {
9425
- setIsSubmitting(false);
9545
+ setSendingThreadIds((ids) => ids.filter((id) => id !== sentThreadId));
9426
9546
  }
9427
9547
  };
9428
9548
 
@@ -9435,9 +9555,16 @@ function ReplyComposer({
9435
9555
  placeholder={
9436
9556
  placeholder ?? t("plansPage.comments.replyPlaceholder")
9437
9557
  }
9438
- resetKey={`reply-${resetKey}`}
9558
+ initialMessage={box.loadedMessage}
9559
+ resetKey={`reply-${box.version}`}
9560
+ // Only opening the thread or pressing Send pulls focus here; a
9561
+ // reply put back after a failed send takes only free focus.
9562
+ autoFocus={!autoFocus ? false : box.takeFocus ? true : "if-free"}
9439
9563
  className="min-h-10 border-0 bg-transparent px-0 py-2 shadow-none focus-visible:ring-0"
9440
- onChange={setDraft}
9564
+ onChange={(next) => {
9565
+ setBox((current) => ({ ...current, draft: next }));
9566
+ drafts.set(box.threadId, next.message);
9567
+ }}
9441
9568
  onSubmitShortcut={() => void submit()}
9442
9569
  />
9443
9570
  <Button
@@ -9456,7 +9583,7 @@ function ReplyComposer({
9456
9583
  </Button>
9457
9584
  </div>
9458
9585
  </div>
9459
- {submitError && (
9586
+ {failedThreadIds.includes(box.threadId) && (
9460
9587
  <p className="ml-11 text-xs text-destructive">
9461
9588
  {t("plansPage.comments.sendFailed")}
9462
9589
  </p>
@@ -9500,6 +9627,7 @@ function AnnotationPopover({
9500
9627
  }) {
9501
9628
  const t = useT();
9502
9629
  const popoverRef = useRef<HTMLDivElement>(null);
9630
+ const editChosenFromMenuRef = useRef(false);
9503
9631
  const [editing, setEditing] = useState(false);
9504
9632
  const [messageDraft, setMessageDraft] = useState<
9505
9633
  Pick<CommentDraft, "message" | "mentions">
@@ -9612,6 +9740,12 @@ function AnnotationPopover({
9612
9740
  align="end"
9613
9741
  className="w-48 rounded-xl"
9614
9742
  data-comment-popover-portal
9743
+ onCloseAutoFocus={(event) => {
9744
+ // Edit puts focus in its editor; leave it there. Other
9745
+ // actions return to the button.
9746
+ if (editChosenFromMenuRef.current) event.preventDefault();
9747
+ editChosenFromMenuRef.current = false;
9748
+ }}
9615
9749
  >
9616
9750
  {canResolve && (
9617
9751
  <DropdownMenuItem
@@ -9629,7 +9763,10 @@ function AnnotationPopover({
9629
9763
  {canEditRootComment && (
9630
9764
  <DropdownMenuItem
9631
9765
  className="gap-2"
9632
- onClick={() => setEditing(true)}
9766
+ onClick={() => {
9767
+ editChosenFromMenuRef.current = true;
9768
+ setEditing(true);
9769
+ }}
9633
9770
  >
9634
9771
  <IconPencil className="size-4" />
9635
9772
  {t("plansPage.comments.editFirstComment")}
@@ -9759,6 +9896,9 @@ function AnnotationPopover({
9759
9896
  <ReplyComposer
9760
9897
  author={pendingAuthor}
9761
9898
  isPending={isPending}
9899
+ autoFocus
9900
+ // Opening another thread reuses this popover and its reply box.
9901
+ threadId={annotation.id}
9762
9902
  onSubmit={(reply) => onReply(annotation.id, reply)}
9763
9903
  />
9764
9904
  </div>
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-10-19
4
+ ---
5
+
6
+ Opening a comment thread puts the cursor in the reply box, and Edit puts it after the comment's text. A reply you haven't sent stays with its thread when you open another one.
@@ -95,9 +95,9 @@ MCP clients see no `outputSchema` by default. Set `mcpOutputSchema: true` (requi
95
95
 
96
96
  `authorize` decides whether the caller may run the action at all. It wraps
97
97
  `run`, so it holds at **all six dispatch sites** — agent tool, HTTP, frontend
98
- hook, MCP, A2A, CLI — unlike `needsApproval`, which is honoured only in the
99
- agent loop. Use `authorize` for "not everyone may do this"; use `needsApproval`
100
- to ask a human to bless one call a permitted caller is already allowed to make.
98
+ hook, MCP, A2A, CLI. Use `authorize` for "not everyone may do this"; use
99
+ `needsApproval` to ask a human to bless one call a permitted caller is already
100
+ allowed to make.
101
101
 
102
102
  ```ts
103
103
  import { coachAccess } from "../lib/access.js"; // defineAppRoles(...)
@@ -140,4 +140,6 @@ export default defineAction({
140
140
 
141
141
  When the gate is truthy and the call isn't yet approved, the loop emits an `approval_required` event and **stops the turn — `run()` never executes**. A predicate gates conditionally (e.g. only external recipients) and **fails closed**: a throw is treated as "approval required". It receives its own copy of the prepared snapshot of the validated arguments, defaults applied, and the approval binds to the snapshot's hash, so an approved call runs only with the arguments that were approved (a Date binds by its time). The human approves via the chat UI's Approve affordance, which re-issues the turn with the call's `approvalKey`, and only then does the action run.
142
142
 
143
+ Approval is given in the app, so HTTP never grants it. The HTTP action route and the generated Workers entry run a gated call only for the proven app UI (a frontend call from the signed-in user, same origin, holding the short-lived UI capability; `isProvenUiRequest`, the proof `uiOnly` uses) or the app's MCP directory widget. Every other HTTP caller (bearer or API key, MCP OAuth token, org service token, A2A, extension iframe, mobile app) gets `409 approval_required`, and a service identity can never approve. The shared client presents the capability with every write from a signed-in session (minted once per user, reused for nine minutes, never for reads or public pages), so a gated button works on its first request. A call without proof gets `403 ui_capability_required`; the client then re-mints and retries once, which is only the fallback for an expired or rejected capability. Every surface evaluates the gate through `preparedCallNeedsApproval`, so a throwing predicate asks there too. A caller that must run unattended needs a different action, never a bypass of the gate.
144
+
143
145
  **Keep approvals rare** — the default is off and almost every action should leave it off. The canonical example is Mail's `send-email` (`needsApproval: true`). See the `security` skill and the Human Approval doc.
@@ -30,7 +30,6 @@ import {
30
30
  IconLoader2,
31
31
  IconAdjustments,
32
32
  IconPencilPlus,
33
- IconPin,
34
33
  IconBrandGoogle,
35
34
  IconCode,
36
35
  IconCopy,
@@ -146,7 +145,6 @@ interface EditorToolbarProps {
146
145
  deckId: string;
147
146
  deckTitle: string;
148
147
  canEdit?: boolean;
149
- canComment?: boolean;
150
148
  onTitleChange: (title: string) => void;
151
149
  currentSlideIndex: number;
152
150
  sidebarOpen: boolean;
@@ -172,8 +170,6 @@ interface EditorToolbarProps {
172
170
  onToggleTweaks?: () => void;
173
171
  drawMode?: boolean;
174
172
  onToggleDrawMode?: () => void;
175
- pinMode?: boolean;
176
- onTogglePinMode?: () => void;
177
173
  textBoxMode?: boolean;
178
174
  onToggleTextBoxMode?: () => void;
179
175
  shapeType?: SlideShapeType | null;
@@ -241,8 +237,6 @@ export default function EditorToolbar({
241
237
  onToggleTweaks,
242
238
  drawMode,
243
239
  onToggleDrawMode,
244
- pinMode,
245
- onTogglePinMode,
246
240
  textBoxMode,
247
241
  onToggleTextBoxMode,
248
242
  shapeType,
@@ -269,7 +263,6 @@ export default function EditorToolbar({
269
263
  onPreviousSlide,
270
264
  onNextSlide,
271
265
  canEdit = true,
272
- canComment = canEdit,
273
266
  }: EditorToolbarProps) {
274
267
  const t = useT();
275
268
  // The top bar is the app's own in a widget. It drops only what the host owns
@@ -359,13 +352,12 @@ export default function EditorToolbar({
359
352
 
360
353
  const contextToolbarVisible = canEdit && Boolean(currentSlide);
361
354
  const showSlideTools =
362
- (canEdit &&
363
- Boolean(
364
- (currentSlide && onToggleAnimations) ||
365
- onToggleTweaks ||
366
- onToggleDrawMode,
367
- )) ||
368
- (canComment && Boolean(onTogglePinMode));
355
+ canEdit &&
356
+ Boolean(
357
+ (currentSlide && onToggleAnimations) ||
358
+ onToggleTweaks ||
359
+ onToggleDrawMode,
360
+ );
369
361
  const showCommentsItem = !widgetEmbed && Boolean(onToggleComments);
370
362
  const fileInputRef = useRef<HTMLInputElement>(null);
371
363
  const backupInputRef = useRef<HTMLInputElement>(null);
@@ -646,17 +638,6 @@ export default function EditorToolbar({
646
638
  });
647
639
  }
648
640
  }
649
- if (canComment && onTogglePinMode) {
650
- commands.push({
651
- id: "pin-comments",
652
- group: "slideTools",
653
- label: t("editorToolbar.pinComments"),
654
- keywords: ["comment", "pin"],
655
- icon: IconPin,
656
- active: pinMode,
657
- run: onTogglePinMode,
658
- });
659
- }
660
641
 
661
642
  if (canEdit && currentSlide && onChangeSlideTransition) {
662
643
  commands.push(
@@ -767,7 +748,6 @@ export default function EditorToolbar({
767
748
  addSlideGenerating,
768
749
  animationsOpen,
769
750
  layersOpen,
770
- canComment,
771
751
  canEdit,
772
752
  commentsOpen,
773
753
  currentSlide,
@@ -789,10 +769,8 @@ export default function EditorToolbar({
789
769
  onToggleLayers,
790
770
  onToggleComments,
791
771
  onToggleDrawMode,
792
- onTogglePinMode,
793
772
  onToggleTextBoxMode,
794
773
  onToggleTweaks,
795
- pinMode,
796
774
  setTheme,
797
775
  shapeType,
798
776
  t,
@@ -1057,20 +1035,6 @@ export default function EditorToolbar({
1057
1035
  {t("editorToolbar.drawOnSlide")}
1058
1036
  </DropdownMenuItem>
1059
1037
  )}
1060
- {canComment && onTogglePinMode && (
1061
- <DropdownMenuItem
1062
- onSelect={onTogglePinMode}
1063
- data-toolbar-pin-button
1064
- className={
1065
- pinMode
1066
- ? "bg-accent text-accent-foreground"
1067
- : undefined
1068
- }
1069
- >
1070
- <IconPin className="size-4" />
1071
- {t("editorToolbar.pinComments")}
1072
- </DropdownMenuItem>
1073
- )}
1074
1038
  </DropdownMenuGroup>
1075
1039
  </>
1076
1040
  )}
@@ -13,7 +13,6 @@ import {
13
13
  IconMinus,
14
14
  IconOval,
15
15
  IconPhotoVideo,
16
- IconPin,
17
16
  IconPointer,
18
17
  IconResize,
19
18
  IconRobot,
@@ -55,7 +54,7 @@ export type FloatingToolbarInsertTool =
55
54
  | "shape"
56
55
  | "media";
57
56
  export type FloatingToolbarTransition = "none" | AnimationType;
58
- type FloatingToolbarCommentAction = "comments" | "annotate" | "pin";
57
+ type FloatingToolbarCommentAction = "comments" | "annotate";
59
58
  export type FloatingToolbarAgentAction =
60
59
  | "inspiration"
61
60
  | "tighten-tone"
@@ -172,7 +171,6 @@ const COMMENT_ITEMS: MenuItem<FloatingToolbarCommentAction>[] = [
172
171
  labelKey: "floatingToolbar.annotate",
173
172
  icon: IconUserEdit,
174
173
  },
175
- { value: "pin", labelKey: "floatingToolbar.pinComment", icon: IconPin },
176
174
  ];
177
175
 
178
176
  const ALL_TRANSITIONS = TRANSITION_ITEMS.map((item) => item.value);
@@ -348,7 +346,6 @@ export interface FloatingToolbarProps {
348
346
  onOpenTransitions?: () => void;
349
347
  onSelectTransition?: (transition: FloatingToolbarTransition) => void;
350
348
  /** True while the comments panel is open. */
351
- commentsOpen?: boolean;
352
349
  /** The armed comment tool, if any. */
353
350
  commentToolValue?: FloatingToolbarCommentAction | null;
354
351
  /** Rows the viewer lacks the permission for. */
@@ -370,7 +367,6 @@ export const FloatingToolbar = memo(function FloatingToolbar({
370
367
  transitionsDisabledReason = null,
371
368
  onOpenTransitions,
372
369
  onSelectTransition,
373
- commentsOpen = false,
374
370
  commentToolValue = null,
375
371
  disabledCommentActions,
376
372
  onOpenComments,
@@ -430,8 +426,8 @@ export const FloatingToolbar = memo(function FloatingToolbar({
430
426
  tooltip={t("floatingToolbar.comment")}
431
427
  menuLabel={t("floatingToolbar.commentMenu")}
432
428
  items={COMMENT_ITEMS}
433
- active={commentsOpen || commentToolValue !== null}
434
- value={commentToolValue ?? (commentsOpen ? "comments" : null)}
429
+ active={commentToolValue !== null}
430
+ value={commentToolValue}
435
431
  disabledValues={disabledCommentActions}
436
432
  onActivate={onOpenComments}
437
433
  onSelect={onCommentAction}
@@ -1295,11 +1295,9 @@ interface SlideEditorProps {
1295
1295
  /** Close the document layers panel. */
1296
1296
  onCloseLayers?: () => void;
1297
1297
  /** Whether the comments panel is open. */
1298
- commentsOpen?: boolean;
1299
1298
  /** Toggles the comments panel. */
1300
1299
  onToggleComments?: () => void;
1301
1300
  /** Toggles the pin-comment tool. */
1302
- onTogglePinMode?: () => void;
1303
1301
  /** Toggles the draw-on-slide annotation overlay. */
1304
1302
  onToggleDrawMode?: () => void;
1305
1303
  /** Toggle the transitions panel without a target. */
@@ -1985,9 +1983,7 @@ export default function SlideEditor({
1985
1983
  onCloseLayers,
1986
1984
  onOpenAnimations,
1987
1985
  onToggleAnimations,
1988
- commentsOpen,
1989
1986
  onToggleComments,
1990
- onTogglePinMode,
1991
1987
  onToggleDrawMode,
1992
1988
  onSelectedAnimationTargetsChange,
1993
1989
  slideId,
@@ -3426,20 +3422,16 @@ export default function SlideEditor({
3426
3422
  );
3427
3423
 
3428
3424
  const disabledCommentActions = useMemo(
3429
- () => [
3430
- ...(canComment ? [] : (["pin"] as const)),
3431
- ...(readOnly ? (["annotate"] as const) : []),
3432
- ],
3433
- [canComment, readOnly],
3425
+ () => (readOnly ? (["annotate"] as const) : []),
3426
+ [readOnly],
3434
3427
  );
3435
3428
 
3436
3429
  const runCommentAction = useCallback(
3437
- (action: "comments" | "annotate" | "pin") => {
3430
+ (action: "comments" | "annotate") => {
3438
3431
  if (action === "comments") onToggleComments?.();
3439
- else if (action === "pin") onTogglePinMode?.();
3440
3432
  else onToggleDrawMode?.();
3441
3433
  },
3442
- [onToggleComments, onTogglePinMode, onToggleDrawMode],
3434
+ [onToggleComments, onToggleDrawMode],
3443
3435
  );
3444
3436
 
3445
3437
  const runAgentAction = useCallback(
@@ -11344,9 +11336,8 @@ export default function SlideEditor({
11344
11336
  transitionsDisabledReason={transitionsDisabledReason}
11345
11337
  onOpenTransitions={openTransitionsPanel}
11346
11338
  onSelectTransition={applyTransitionQuickPick}
11347
- commentsOpen={Boolean(commentsOpen)}
11348
11339
  commentToolValue={
11349
- pinMode ? "pin" : drawMode ? "annotate" : null
11340
+ pinMode ? "comments" : drawMode ? "annotate" : null
11350
11341
  }
11351
11342
  disabledCommentActions={disabledCommentActions}
11352
11343
  onOpenComments={onToggleComments}
@@ -275,7 +275,6 @@ const messages = {
275
275
  commentMenu: "Comment options",
276
276
  comments: "Comments",
277
277
  annotate: "Annotate",
278
- pinComment: "Pin Comment",
279
278
  agent: "Agent",
280
279
  agentMenu: "Agent options",
281
280
  agentUnavailable: "Couldn't reach the agent chat. Try again.",
@@ -376,7 +375,6 @@ const messages = {
376
375
  shapeLine: "Line",
377
376
  shapeStar: "Star",
378
377
  shapePolygon: "Polygon",
379
- pinComments: "Pin comments",
380
378
  undo: "Undo",
381
379
  undoWithShortcut: "Undo ({{shortcut}})",
382
380
  redo: "Redo",