@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.
- package/corpus/README.md +1 -1
- package/corpus/templates/analytics/.agents/skills/actions/references/action-fields.md +5 -3
- package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +41 -2
- package/corpus/templates/analytics/.agents/skills/data-querying/SKILL.md +4 -2
- package/corpus/templates/analytics/CHANGELOG.md +18 -0
- package/corpus/templates/analytics/actions/build-data-index.ts +11 -2
- package/corpus/templates/analytics/actions/configure-content-collection.ts +29 -4
- package/corpus/templates/analytics/actions/configure-first-party-events-model.ts +9 -7
- package/corpus/templates/analytics/actions/create-analytics-public-key.ts +4 -0
- package/corpus/templates/analytics/actions/estimate-content-signups-cost.ts +72 -0
- package/corpus/templates/analytics/actions/estimate-query-cost.ts +16 -134
- package/corpus/templates/analytics/actions/list-sigma-workbooks.ts +123 -42
- package/corpus/templates/analytics/actions/migrate-sigma-workbook.ts +20 -17
- package/corpus/templates/analytics/actions/revoke-analytics-public-key.ts +4 -0
- package/corpus/templates/analytics/app/components/dashboard/SigmaMigrationDialog.tsx +246 -680
- package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +1 -1
- package/corpus/templates/analytics/app/i18n/en-US.ts +54 -55
- package/corpus/templates/analytics/app/pages/DashboardOverview.tsx +0 -1
- package/corpus/templates/analytics/app/pages/DataSources.tsx +56 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/filter-vars.ts +4 -3
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +26 -8
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/interpolate.ts +3 -1
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/panel-cost-notice.ts +172 -0
- package/corpus/templates/analytics/app/pages/data-sources/SearchConsoleDetail.tsx +441 -0
- package/corpus/templates/analytics/changelog/2026-10-15-a-content-dashboard-lists-every-blog-article-with-its-author.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-15-data-sources-has-a-google-search-console-card-that-shows-the.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-16-the-content-dashboard-can-show-signups-per-blog-article.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-19-creating-or-revoking-a-public-analytics-key-now-asks-you-to-.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-19-search-sigma-workbooks-by-name-and-open-ones-a-teammate-alre.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-20-blog-signup-attribution-excludes-signups-before-the-first-vi.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-20-collapsed-analytics-navigation-starts-at-the-top.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-20-content-keeps-successful-coverage-dates-and-missing-day-gaps.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-20-private-github-repositories-can-be-indexed-with-your-connect.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-20-search-console-preserves-backfill-and-paused-collection.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-20-search-console-refreshes-historical-coverage-when-properties.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-20-search-console-setup-keeps-saved-properties-readable-and-sup.md +6 -0
- package/corpus/templates/analytics/seeds/dashboards/content-view.json +182 -0
- package/corpus/templates/analytics/server/db/schema.ts +1 -0
- package/corpus/templates/analytics/server/lib/bi-migration/external-dashboards-store.ts +56 -18
- package/corpus/templates/analytics/server/lib/bi-migration/unfinished-import.ts +64 -0
- package/corpus/templates/analytics/server/lib/builtin-dashboard-pass.ts +17 -2
- package/corpus/templates/analytics/server/lib/content-metrics/collect.ts +2 -0
- package/corpus/templates/analytics/server/lib/content-metrics/config.ts +92 -3
- package/corpus/templates/analytics/server/lib/content-metrics/legacy-content-view.json +172 -0
- package/corpus/templates/analytics/server/lib/content-metrics/migration.ts +2 -0
- package/corpus/templates/analytics/server/lib/content-metrics/steps/daily-metrics.ts +8 -3
- package/corpus/templates/analytics/server/lib/content-metrics/steps/search-console.ts +16 -3
- package/corpus/templates/analytics/server/lib/content-metrics/steps/signups.ts +240 -0
- package/corpus/templates/analytics/server/lib/content-metrics/store.ts +5 -0
- package/corpus/templates/analytics/server/lib/dashboard-catalog.ts +21 -0
- package/corpus/templates/analytics/server/lib/dashboard-panel-verification.ts +300 -5
- package/corpus/templates/analytics/server/lib/dashboard-seeds.ts +2 -0
- package/corpus/templates/analytics/server/lib/dashboards-store.ts +28 -0
- package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +128 -24
- package/corpus/templates/analytics/server/lib/google-analytics.ts +56 -16
- package/corpus/templates/analytics/server/lib/index-build-github.ts +5 -0
- package/corpus/templates/analytics/server/lib/provider-credentials.ts +47 -2
- package/corpus/templates/analytics/server/lib/query-cost-estimate.ts +184 -5
- package/corpus/templates/analytics/server/plugins/db.ts +9 -1
- package/corpus/templates/analytics/shared/builtin-dashboard-ids.ts +2 -0
- package/corpus/templates/analytics/shared/search-console-property.ts +78 -0
- package/corpus/templates/assets/.agents/skills/actions/references/action-fields.md +5 -3
- package/corpus/templates/brain/.agents/skills/actions/references/action-fields.md +5 -3
- package/corpus/templates/calendar/.agents/skills/actions/references/action-fields.md +5 -3
- package/corpus/templates/calendar/actions/delete-event.ts +1 -3
- package/corpus/templates/calendar/actions/delete-events.ts +1 -2
- package/corpus/templates/calendar/actions/event-action-helpers.ts +0 -4
- package/corpus/templates/calendar/actions/update-event.ts +2 -6
- package/corpus/templates/chat/.agents/skills/actions/references/action-fields.md +5 -3
- package/corpus/templates/clips/.agents/skills/actions/references/action-fields.md +5 -3
- package/corpus/templates/clips/CHANGELOG.md +11 -24
- package/corpus/templates/clips/actions/create-recording-upload.ts +5 -1
- package/corpus/templates/clips/app/components/player/chapter-list.tsx +7 -6
- package/corpus/templates/clips/app/components/player/comment-composer.tsx +5 -1
- package/corpus/templates/clips/app/components/player/player-controls.tsx +1 -0
- package/corpus/templates/clips/app/components/player/scrubber.tsx +4 -2
- package/corpus/templates/clips/app/i18n/en-US.ts +3 -3
- package/corpus/templates/clips/app/lib/extension-auth.ts +70 -0
- package/corpus/templates/clips/app/root.tsx +22 -10
- package/corpus/templates/clips/app/routes/_app.notifications.tsx +3 -0
- package/corpus/templates/clips/app/routes/import.tsx +115 -110
- package/corpus/templates/clips/changelog/2026-10-16-agent-uploads-can-use-free-clips-storage.md +6 -0
- package/corpus/templates/clips/changelog/2026-10-19-agents-see-a-clip-s-frames-as-soon-as-it-finishes-processing.md +6 -0
- package/corpus/templates/clips/changelog/2026-10-19-chapter-list-times-now-match-the-player-s-clock-and-each-cha.md +6 -0
- package/corpus/templates/clips/changelog/2026-10-19-editing-a-comment-puts-the-cursor-after-its-text-and-reply-o.md +6 -0
- package/corpus/templates/clips/changelog/2026-10-19-youtube-import-followups.md +6 -0
- package/corpus/templates/clips/changelog/2026-10-20-locally-saved-desktop-recordings-now-appear-in-the-recovery-.md +6 -0
- package/corpus/templates/clips/desktop/src/app.tsx +29 -186
- package/corpus/templates/clips/desktop/src/components/RecordingRecovery.tsx +171 -16
- package/corpus/templates/clips/desktop/src/lib/record-first.ts +9 -32
- package/corpus/templates/clips/desktop/src/lib/recording-recovery.ts +30 -1
- package/corpus/templates/clips/server/lib/agent-frame-request.ts +5 -2
- package/corpus/templates/clips/server/lib/recording-upload-ticket.ts +8 -0
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +2 -0
- package/corpus/templates/content/.agents/skills/actions/references/action-fields.md +5 -3
- package/corpus/templates/content/CHANGELOG.md +12 -0
- package/corpus/templates/content/actions/_content-database-personal-view.ts +5 -15
- package/corpus/templates/content/actions/_database-navigation.ts +3 -3
- package/corpus/templates/content/actions/_database-utils.ts +10 -22
- package/corpus/templates/content/actions/_database-view-setup.ts +16 -76
- package/corpus/templates/content/actions/create-comment-ai-suggestion.ts +93 -44
- package/corpus/templates/content/actions/export-document.ts +2 -14
- package/corpus/templates/content/actions/suggest-document-edit.ts +5 -7
- package/corpus/templates/content/actions/update-content-database-view.ts +2 -14
- package/corpus/templates/content/app/components/editor/CommentsSidebar.tsx +86 -30
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +64 -28
- package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +6 -46
- package/corpus/templates/content/app/components/editor/PageDraftRecovery.tsx +72 -7
- package/corpus/templates/content/app/components/editor/TableHoverControls.tsx +292 -103
- package/corpus/templates/content/app/components/editor/ThreadSuggestion.tsx +147 -40
- package/corpus/templates/content/app/components/editor/VisualEditor.tsx +108 -11
- package/corpus/templates/content/app/components/editor/database/ContentTable.tsx +2 -1
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +150 -166
- package/corpus/templates/content/app/components/editor/database/database-cell-comments.tsx +515 -0
- package/corpus/templates/content/app/components/editor/database/database-item-comments.tsx +5 -43
- package/corpus/templates/content/app/components/editor/database/filter-sort.ts +8 -783
- package/corpus/templates/content/app/components/editor/live-editor-session.ts +5 -1
- package/corpus/templates/content/app/components/editor/page-draft-journal.ts +61 -1
- package/corpus/templates/content/app/components/editor/property-comments.tsx +35 -4
- package/corpus/templates/content/app/components/editor/property-date-format.ts +45 -0
- package/corpus/templates/content/app/components/editor/thread-suggestions.ts +30 -0
- package/corpus/templates/content/app/components/editor/useDocumentReconcileRecovery.ts +23 -4
- package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +36 -10
- package/corpus/templates/content/app/global.css +23 -0
- package/corpus/templates/content/app/i18n-data.ts +1 -0
- package/corpus/templates/content/changelog/2026-10-16-opening-a-page-in-a-new-tab-while-another-tab-is-still-savin.md +6 -0
- package/corpus/templates/content/changelog/2026-10-16-typing-steadily-in-one-tab-no-longer-makes-another-open-tab-.md +6 -0
- package/corpus/templates/content/changelog/2026-10-19-database-views-can-filter-the-same-property-more-than-once-n.md +6 -0
- 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
- package/corpus/templates/content/changelog/2026-10-19-starting-a-comment-puts-the-cursor-in-the-comment-box-ready-.md +6 -0
- package/corpus/templates/content/changelog/2026-10-19-table-row-and-column-options-now-appear-for-the-row-or-colum.md +6 -0
- package/corpus/templates/content/docs/product/capabilities/content.comment.page-owned.md +5 -1
- package/corpus/templates/content/docs/product/capabilities/content.revision.suggestions.md +3 -1
- package/corpus/templates/content/e2e/helpers.ts +10 -0
- package/corpus/templates/content/shared/api.ts +18 -12
- package/corpus/templates/content/shared/database-query.ts +107 -13
- package/corpus/templates/content/shared/suggestion-diff.ts +35 -0
- package/corpus/templates/crm/.agents/skills/actions/references/action-fields.md +5 -3
- package/corpus/templates/design/.agents/skills/actions/references/action-fields.md +5 -3
- package/corpus/templates/design/CHANGELOG.md +2 -0
- package/corpus/templates/design/actions/delete-file.ts +1 -4
- package/corpus/templates/design/app/components/design/LayersPanel.tsx +10 -1
- package/corpus/templates/design/app/components/visual-editor/ReviewCanvasPins.tsx +24 -4
- package/corpus/templates/design/app/pages/design-editor/commands/mode-change.ts +14 -9
- package/corpus/templates/design/app/pages/design-editor/domains/use-editor-modes.ts +23 -2
- package/corpus/templates/design/app/pages/design-editor/tool-state.ts +29 -0
- 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
- package/corpus/templates/design/changelog/2026-10-20-comments-return-to-the-mode-they-came-from-and-layers-search.md +6 -0
- package/corpus/templates/design/e2e/global-setup.ts +185 -85
- package/corpus/templates/dispatch/.agents/skills/actions/references/action-fields.md +5 -3
- package/corpus/templates/dispatch/CHANGELOG.md +4 -0
- package/corpus/templates/dispatch/actions/apply-workspace-connection-setup.ts +4 -0
- package/corpus/templates/dispatch/actions/delete-workspace-connection.ts +4 -0
- package/corpus/templates/dispatch/actions/set-workspace-connection-grant.ts +4 -0
- package/corpus/templates/dispatch/actions/upsert-workspace-connection.ts +4 -0
- package/corpus/templates/dispatch/changelog/2026-10-19-vault-secrets-grants-link-tokens-app-access-and-workspace-co.md +6 -0
- package/corpus/templates/factory/.agents/skills/actions/references/action-fields.md +5 -3
- package/corpus/templates/factory/.agents/skills/review-latest-feedback/SKILL.md +105 -33
- package/corpus/templates/factory/.agents/skills/review-latest-feedback/references/channel-inventory.md +128 -74
- package/corpus/templates/factory/.agents/skills/review-latest-feedback/references/slack-reaction-replies.md +39 -11
- package/corpus/templates/forms/.agents/skills/actions/references/action-fields.md +5 -3
- package/corpus/templates/mail/.agents/skills/actions/references/action-fields.md +5 -3
- package/corpus/templates/mail/.agents/skills/contacts-and-crm/SKILL.md +22 -17
- package/corpus/templates/mail/AGENTS.md +1 -1
- package/corpus/templates/mail/CHANGELOG.md +14 -0
- package/corpus/templates/mail/actions/provider-api-request.ts +3 -3
- package/corpus/templates/mail/app/components/email/IntegrationsSidebar.tsx +211 -54
- package/corpus/templates/mail/app/hooks/use-inbox-threads.ts +26 -2
- package/corpus/templates/mail/app/i18n/en-US.ts +5 -1
- package/corpus/templates/mail/app/lib/gong-connection.ts +27 -0
- package/corpus/templates/mail/app/lib/thread-cache.ts +110 -9
- package/corpus/templates/mail/app/root.tsx +10 -2
- package/corpus/templates/mail/changelog/2026-10-20-connected-gong-settings-now-open-the-shared-integration-mana.md +6 -0
- package/corpus/templates/mail/changelog/2026-10-20-gong-connection-details-now-match-the-call-data-available-in.md +6 -0
- package/corpus/templates/mail/changelog/2026-10-20-improve-gong-setup-with-a-click-to-connect-flow-and-permissi.md +6 -0
- package/corpus/templates/mail/changelog/2026-10-20-mail-lets-you-retry-when-it-cannot-check-your-gong-access.md +6 -0
- package/corpus/templates/mail/changelog/2026-10-20-mail-thread-cache-stays-isolated-when-mailbox-scope-changes.md +6 -0
- package/corpus/templates/mail/changelog/2026-10-20-the-inbox-and-recently-opened-threads-now-open-instantly-fro.md +6 -0
- package/corpus/templates/mail/server/handlers/emails.ts +9 -0
- package/corpus/templates/plan/.agents/skills/actions/references/action-fields.md +5 -3
- package/corpus/templates/plan/CHANGELOG.md +4 -0
- package/corpus/templates/plan/app/pages/PlansPage.tsx +173 -33
- package/corpus/templates/plan/changelog/2026-10-19-opening-a-comment-thread-puts-the-cursor-in-the-reply-box-an.md +6 -0
- package/corpus/templates/slides/.agents/skills/actions/references/action-fields.md +5 -3
- package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +6 -42
- package/corpus/templates/slides/app/components/editor/FloatingToolbar.tsx +3 -7
- package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +5 -14
- package/corpus/templates/slides/app/i18n/en-US.ts +0 -2
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +27 -33
- package/corpus/templates/slides/scripts/edit-fidelity/run.ts +1 -3
- package/corpus/templates/tasks/.agents/skills/actions/references/action-fields.md +5 -3
- package/package.json +1 -1
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { defineAction } from "@agent-native/core/action";
|
|
2
2
|
import createSuggestion from "@agent-native/core/review/suggestions/actions/create-resource-suggestion";
|
|
3
|
+
import createSuggestionProposal from "@agent-native/core/review/suggestions/actions/create-resource-suggestion-proposal";
|
|
3
4
|
import listSuggestions from "@agent-native/core/review/suggestions/actions/list-resource-suggestions";
|
|
4
5
|
import { and, eq, lt } from "drizzle-orm";
|
|
5
6
|
import { z } from "zod";
|
|
6
7
|
|
|
7
|
-
import { markdownSuggestionOperation } from "../app/components/editor/suggestions/markdown-operation.js";
|
|
8
8
|
import { getDb, schema } from "../server/db/index.js";
|
|
9
9
|
import {
|
|
10
10
|
CommentAiOperationError,
|
|
@@ -20,6 +20,10 @@ import {
|
|
|
20
20
|
import { CONTENT_DOCUMENT_SUGGESTION_ADAPTER } from "../server/lib/suggested-edits.js";
|
|
21
21
|
import type { CommentAiRequest } from "../shared/comment-ai.js";
|
|
22
22
|
import { resolveDocumentTextEdits } from "../shared/document-text-edits.js";
|
|
23
|
+
import {
|
|
24
|
+
markdownSuggestionOperationsForFindReplaceEdits,
|
|
25
|
+
markdownSuggestionOperationSummary,
|
|
26
|
+
} from "../shared/suggestion-diff.js";
|
|
23
27
|
import { documentRevisionToken } from "./_document-edit-mutation.js";
|
|
24
28
|
import { addCommentWithGuard, commentIdForIdempotency } from "./add-comment.js";
|
|
25
29
|
|
|
@@ -33,25 +37,38 @@ const payloadSchema = z.object({
|
|
|
33
37
|
.describe(
|
|
34
38
|
"One or two sentences to the commenter about this change, including anything they should check; shown on the suggestion in their comment thread",
|
|
35
39
|
),
|
|
36
|
-
|
|
37
|
-
.
|
|
40
|
+
edits: z
|
|
41
|
+
.array(
|
|
42
|
+
z.object({
|
|
43
|
+
find: z
|
|
44
|
+
.string()
|
|
45
|
+
.min(1)
|
|
46
|
+
.max(24000)
|
|
47
|
+
.describe("Exact unique text to propose replacing"),
|
|
48
|
+
replace: z
|
|
49
|
+
.string()
|
|
50
|
+
.max(24000)
|
|
51
|
+
.describe("Proposed replacement; canonical text remains unchanged"),
|
|
52
|
+
}),
|
|
53
|
+
)
|
|
38
54
|
.min(1)
|
|
39
|
-
.max(
|
|
40
|
-
.describe(
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
.max(24000)
|
|
44
|
-
.describe("Proposed replacement; canonical text remains unchanged"),
|
|
55
|
+
.max(20)
|
|
56
|
+
.describe(
|
|
57
|
+
"One entry per separate change. Each find holds the words that change plus only enough surrounding text to occur once, so changes in different places become separate edits instead of one span of unchanged text between them.",
|
|
58
|
+
),
|
|
45
59
|
});
|
|
46
60
|
|
|
47
61
|
/**
|
|
48
62
|
* Earlier pending proposals the requester got in this thread are replaced by
|
|
49
|
-
* a revision instead of stacking up
|
|
50
|
-
*
|
|
63
|
+
* a revision instead of stacking up: those from requests made before this one,
|
|
64
|
+
* and those an earlier attempt of this request created. Edits from this
|
|
65
|
+
* attempt never count, so replaying it builds the same list and its creation
|
|
66
|
+
* receipt still matches.
|
|
51
67
|
*/
|
|
52
68
|
async function supersededSuggestionIds(
|
|
53
69
|
request: Awaited<ReturnType<typeof requireCommentAiRequest>>,
|
|
54
70
|
ctx: Parameters<typeof listSuggestions.run>[1],
|
|
71
|
+
attemptId: string,
|
|
55
72
|
) {
|
|
56
73
|
const earlier = await getDb()
|
|
57
74
|
.select({ id: schema.commentAiRequests.id })
|
|
@@ -64,8 +81,11 @@ async function supersededSuggestionIds(
|
|
|
64
81
|
lt(schema.commentAiRequests.createdAt, request.createdAt),
|
|
65
82
|
),
|
|
66
83
|
);
|
|
67
|
-
if (!earlier.length) return [];
|
|
68
84
|
const earlierIds = new Set(earlier.map((row) => row.id));
|
|
85
|
+
// Only an earlier request, or an earlier attempt of this one, can have left
|
|
86
|
+
// edits to replace. Attempts are counted rather than results, because an
|
|
87
|
+
// attempt can create edits and then fail before recording them.
|
|
88
|
+
if (!earlierIds.size && request.attemptCount <= 1) return [];
|
|
69
89
|
const { suggestions } = await listSuggestions.run(
|
|
70
90
|
{ resourceType: "document", resourceId: request.documentId },
|
|
71
91
|
ctx,
|
|
@@ -76,10 +96,12 @@ async function supersededSuggestionIds(
|
|
|
76
96
|
suggestion.adapterKind === CONTENT_DOCUMENT_SUGGESTION_ADAPTER &&
|
|
77
97
|
suggestion.authorEmail === request.requesterEmail &&
|
|
78
98
|
suggestion.metadata?.sourceThreadId === request.threadId &&
|
|
79
|
-
earlierIds.has(String(suggestion.metadata?.commentAiRequestId))
|
|
99
|
+
(earlierIds.has(String(suggestion.metadata?.commentAiRequestId)) ||
|
|
100
|
+
(suggestion.metadata?.commentAiRequestId === request.id &&
|
|
101
|
+
suggestion.metadata?.commentAiAttemptId !== attemptId)),
|
|
80
102
|
)
|
|
81
103
|
.sort((left, right) => left.createdAt.localeCompare(right.createdAt))
|
|
82
|
-
.slice(-
|
|
104
|
+
.slice(-100)
|
|
83
105
|
.map((suggestion) => suggestion.id);
|
|
84
106
|
}
|
|
85
107
|
|
|
@@ -112,7 +134,7 @@ function targetError(kind: "missing" | "ambiguous" | "overlapping") {
|
|
|
112
134
|
|
|
113
135
|
export default defineAction({
|
|
114
136
|
description:
|
|
115
|
-
"
|
|
137
|
+
"Propose anchored edits against the latest Page text, reviewed in the comment thread that asked for them. Pass the latest attemptId from get-comment-ai-context. Unrelated edits are tolerated when each exact target remains unique; missing, repeated, or overlapping targets become typed review conflicts. New edits on the same comment replace the requester's earlier pending ones, so never ask them to reject those.",
|
|
116
138
|
schema: payloadSchema,
|
|
117
139
|
run: async (args, ctx) => {
|
|
118
140
|
const request = await requireCommentAiRequest("suggest");
|
|
@@ -126,45 +148,72 @@ export default defineAction({
|
|
|
126
148
|
);
|
|
127
149
|
const proposed = resolveDocumentTextEdits(
|
|
128
150
|
verification.source.document.content,
|
|
129
|
-
|
|
151
|
+
payload.edits,
|
|
130
152
|
);
|
|
131
153
|
if (!proposed.ok) throw targetError(proposed.error.kind);
|
|
132
|
-
const
|
|
133
|
-
verification.source.document.content,
|
|
134
|
-
proposed.
|
|
135
|
-
);
|
|
136
|
-
if (!
|
|
154
|
+
const operations = markdownSuggestionOperationsForFindReplaceEdits({
|
|
155
|
+
before: verification.source.document.content,
|
|
156
|
+
edits: proposed.ranges,
|
|
157
|
+
});
|
|
158
|
+
if (!operations.length) {
|
|
137
159
|
throw new CommentAiOperationError(
|
|
138
160
|
"target_deleted",
|
|
139
161
|
"The proposal does not change the latest Page text",
|
|
140
162
|
false,
|
|
141
163
|
);
|
|
142
164
|
}
|
|
143
|
-
const supersedes = await supersededSuggestionIds(
|
|
144
|
-
|
|
145
|
-
{
|
|
146
|
-
...(supersedes.length ? { supersedes } : {}),
|
|
147
|
-
resourceType: "document",
|
|
148
|
-
resourceId: request.documentId,
|
|
149
|
-
adapterKind: CONTENT_DOCUMENT_SUGGESTION_ADAPTER,
|
|
150
|
-
baseRevision: verification.source.document.updatedAt,
|
|
151
|
-
summary: payload.summary,
|
|
152
|
-
idempotencyKey: `comment-ai:${request.id}:suggestion`,
|
|
153
|
-
operations: [operation],
|
|
154
|
-
metadata: {
|
|
155
|
-
sourceCommentId: request.rootCommentId,
|
|
156
|
-
sourceThreadId: request.threadId,
|
|
157
|
-
sourceUrl: `/page/${encodeURIComponent(request.documentId)}?comment=${encodeURIComponent(request.threadId)}`,
|
|
158
|
-
commentAiRequestId: request.id,
|
|
159
|
-
commentAiOperationId: request.id,
|
|
160
|
-
commentAiAttemptId: args.attemptId,
|
|
161
|
-
agentThreadId: request.agentThreadId,
|
|
162
|
-
runId: request.runId,
|
|
163
|
-
model: request.model,
|
|
164
|
-
},
|
|
165
|
-
},
|
|
165
|
+
const supersedes = await supersededSuggestionIds(
|
|
166
|
+
request,
|
|
166
167
|
ctx,
|
|
168
|
+
args.attemptId,
|
|
167
169
|
);
|
|
170
|
+
const proposal = {
|
|
171
|
+
...(supersedes.length ? { supersedes } : {}),
|
|
172
|
+
resourceType: "document",
|
|
173
|
+
resourceId: request.documentId,
|
|
174
|
+
adapterKind: CONTENT_DOCUMENT_SUGGESTION_ADAPTER,
|
|
175
|
+
baseRevision: verification.source.document.updatedAt,
|
|
176
|
+
summary: payload.summary,
|
|
177
|
+
};
|
|
178
|
+
const metadata = {
|
|
179
|
+
sourceCommentId: request.rootCommentId,
|
|
180
|
+
sourceThreadId: request.threadId,
|
|
181
|
+
sourceUrl: `/page/${encodeURIComponent(request.documentId)}?comment=${encodeURIComponent(request.threadId)}`,
|
|
182
|
+
commentAiRequestId: request.id,
|
|
183
|
+
commentAiOperationId: request.id,
|
|
184
|
+
commentAiAttemptId: args.attemptId,
|
|
185
|
+
agentThreadId: request.agentThreadId,
|
|
186
|
+
runId: request.runId,
|
|
187
|
+
model: request.model,
|
|
188
|
+
};
|
|
189
|
+
// A Content suggestion holds exactly one edit, so separate changes become
|
|
190
|
+
// one proposal. The receipt links its first edit. Each attempt creates
|
|
191
|
+
// under its own key, because a retry may propose different edits.
|
|
192
|
+
const suggestion =
|
|
193
|
+
operations.length === 1
|
|
194
|
+
? await createSuggestion.run(
|
|
195
|
+
{
|
|
196
|
+
...proposal,
|
|
197
|
+
idempotencyKey: `comment-ai:${args.attemptId}:suggestion`,
|
|
198
|
+
operations,
|
|
199
|
+
metadata,
|
|
200
|
+
},
|
|
201
|
+
ctx,
|
|
202
|
+
)
|
|
203
|
+
: (
|
|
204
|
+
await createSuggestionProposal.run(
|
|
205
|
+
{
|
|
206
|
+
...proposal,
|
|
207
|
+
idempotencyKey: `comment-ai:${args.attemptId}:proposal`,
|
|
208
|
+
suggestions: operations.map((operation) => ({
|
|
209
|
+
summary: markdownSuggestionOperationSummary(operation),
|
|
210
|
+
operations: [{ ...operation, ordinal: 0 }],
|
|
211
|
+
metadata,
|
|
212
|
+
})),
|
|
213
|
+
},
|
|
214
|
+
ctx,
|
|
215
|
+
)
|
|
216
|
+
).suggestions[0]!;
|
|
168
217
|
await updateCommentAiRequest(request, {
|
|
169
218
|
status: "running",
|
|
170
219
|
result: { suggestionId: suggestion.id },
|
|
@@ -3,6 +3,7 @@ import { buildDeepLink } from "@agent-native/core/server";
|
|
|
3
3
|
import { resolveAccess } from "@agent-native/core/sharing";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
|
|
6
|
+
import { CONTENT_DATABASE_FILTER_OPERATORS } from "../shared/api.js";
|
|
6
7
|
import { blocksContentHash } from "../shared/blocks-field-identity.js";
|
|
7
8
|
import {
|
|
8
9
|
renderCollectionCsv,
|
|
@@ -31,20 +32,7 @@ const collectionSchema = z.object({
|
|
|
31
32
|
z.object({
|
|
32
33
|
key: z.string(),
|
|
33
34
|
label: z.string(),
|
|
34
|
-
operator: z.enum(
|
|
35
|
-
"contains",
|
|
36
|
-
"equals",
|
|
37
|
-
"does_not_equal",
|
|
38
|
-
"greater_than",
|
|
39
|
-
"less_than",
|
|
40
|
-
"before",
|
|
41
|
-
"after",
|
|
42
|
-
"between",
|
|
43
|
-
"is_checked",
|
|
44
|
-
"is_unchecked",
|
|
45
|
-
"is_empty",
|
|
46
|
-
"is_not_empty",
|
|
47
|
-
]),
|
|
35
|
+
operator: z.enum(CONTENT_DATABASE_FILTER_OPERATORS),
|
|
48
36
|
value: z.string(),
|
|
49
37
|
filterGroupId: z.string().optional(),
|
|
50
38
|
parentFilterGroupId: z.string().optional(),
|
|
@@ -15,7 +15,10 @@ import { track } from "@agent-native/core/tracking";
|
|
|
15
15
|
import { z } from "zod";
|
|
16
16
|
|
|
17
17
|
import { resolveDocumentTextEdits } from "../shared/document-text-edits.js";
|
|
18
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
markdownSuggestionOperationsForFindReplace,
|
|
20
|
+
markdownSuggestionOperationSummary,
|
|
21
|
+
} from "../shared/suggestion-diff.js";
|
|
19
22
|
import { contentSuggestionPath } from "../shared/suggestion-link.js";
|
|
20
23
|
import { resolveDocumentAccess } from "./_document-access.js";
|
|
21
24
|
import { documentRevisionToken } from "./_document-edit-mutation.js";
|
|
@@ -366,12 +369,7 @@ export default defineAction({
|
|
|
366
369
|
proposalId: args.proposalId,
|
|
367
370
|
idempotencyKey: args.idempotencyKey ?? crypto.randomUUID(),
|
|
368
371
|
suggestions: operations.map((operation, index) => ({
|
|
369
|
-
summary:
|
|
370
|
-
operation.before.changedText && operation.after.changedText
|
|
371
|
-
? `Replace "${operation.before.changedText.slice(0, 80)}" with "${operation.after.changedText.slice(0, 80)}"`
|
|
372
|
-
: operation.before.changedText
|
|
373
|
-
? `Delete "${operation.before.changedText.slice(0, 80)}"`
|
|
374
|
-
: `Insert "${operation.after.changedText.slice(0, 80)}"`,
|
|
372
|
+
summary: markdownSuggestionOperationSummary(operation),
|
|
375
373
|
operations: [{ ...operation, ordinal: 0 }],
|
|
376
374
|
metadata:
|
|
377
375
|
index === 0
|
|
@@ -9,6 +9,7 @@ import { z } from "zod";
|
|
|
9
9
|
|
|
10
10
|
import { getDb, schema } from "../server/db/index.js";
|
|
11
11
|
import { syncPrivateViewIconReferences } from "../server/lib/private-icon-references.js";
|
|
12
|
+
import { CONTENT_DATABASE_FILTER_OPERATORS } from "../shared/api.js";
|
|
12
13
|
import { withSavedTableColumnPresentation } from "../shared/database-table-columns.js";
|
|
13
14
|
import { lockContentDatabaseMutation } from "./_content-database-mutation-lock.js";
|
|
14
15
|
import {
|
|
@@ -39,20 +40,7 @@ const filterSchema = z
|
|
|
39
40
|
.object({
|
|
40
41
|
key: z.string(),
|
|
41
42
|
label: z.string(),
|
|
42
|
-
operator: z.enum(
|
|
43
|
-
"contains",
|
|
44
|
-
"equals",
|
|
45
|
-
"does_not_equal",
|
|
46
|
-
"greater_than",
|
|
47
|
-
"less_than",
|
|
48
|
-
"before",
|
|
49
|
-
"after",
|
|
50
|
-
"between",
|
|
51
|
-
"is_checked",
|
|
52
|
-
"is_unchecked",
|
|
53
|
-
"is_empty",
|
|
54
|
-
"is_not_empty",
|
|
55
|
-
]),
|
|
43
|
+
operator: z.enum(CONTENT_DATABASE_FILTER_OPERATORS),
|
|
56
44
|
value: z.string(),
|
|
57
45
|
filterGroupId: z.string().optional(),
|
|
58
46
|
parentFilterGroupId: z.string().optional(),
|
|
@@ -116,6 +116,7 @@ import {
|
|
|
116
116
|
import type { DraftSuggestion } from "./suggestions/draft-session";
|
|
117
117
|
import { SuggestionText } from "./SuggestionText";
|
|
118
118
|
import {
|
|
119
|
+
proposalGroups,
|
|
119
120
|
receiptSuggestion,
|
|
120
121
|
suggestionSourceThreadId,
|
|
121
122
|
suggestionsByThread,
|
|
@@ -741,6 +742,8 @@ interface CommentsSidebarOptions {
|
|
|
741
742
|
surface?: CommentSurface;
|
|
742
743
|
/** Closes the anchored popover; shown as its header close button. */
|
|
743
744
|
onClose?: () => void;
|
|
745
|
+
/** Extra header actions for each comment thread's card. */
|
|
746
|
+
renderThreadActions?: (thread: CommentThread) => ReactNode;
|
|
744
747
|
}
|
|
745
748
|
|
|
746
749
|
type CommentsSidebarProps = CommentsSidebarOptions &
|
|
@@ -812,6 +815,7 @@ export function CommentsSidebar({
|
|
|
812
815
|
presentation = "inline",
|
|
813
816
|
surface: surfaceProp,
|
|
814
817
|
onClose,
|
|
818
|
+
renderThreadActions,
|
|
815
819
|
}: CommentsSidebarProps) {
|
|
816
820
|
const surface: CommentSurface =
|
|
817
821
|
surfaceProp ?? (presentation === "history" ? "panel" : "rail");
|
|
@@ -1148,23 +1152,52 @@ export function CommentsSidebar({
|
|
|
1148
1152
|
const pendingFocus = displayedPendingComment?.focus;
|
|
1149
1153
|
useEffect(() => {
|
|
1150
1154
|
if (!pendingFocus || presentation !== "inline") return;
|
|
1151
|
-
|
|
1155
|
+
// A new comment has no saved caret yet ({}); a draft that lost focus has
|
|
1156
|
+
// none at all (null) and stays put. A new comment takes focus from the
|
|
1157
|
+
// page, which keeps it through the toolbar click or shortcut that opened
|
|
1158
|
+
// the comment, so typing goes into the comment. A restored draft only
|
|
1159
|
+
// takes focus nobody else holds.
|
|
1160
|
+
const isNew =
|
|
1161
|
+
pendingFocus.current !== null && pendingFocus.current.start === undefined;
|
|
1162
|
+
let movedAway = false;
|
|
1163
|
+
const relinquish = (event: FocusEvent) => {
|
|
1164
|
+
if (!pendingCardRef.current?.contains(event.target as Node))
|
|
1165
|
+
movedAway = true;
|
|
1166
|
+
};
|
|
1167
|
+
document.addEventListener("focusin", relinquish);
|
|
1168
|
+
let attempts = 0;
|
|
1169
|
+
let frame = 0;
|
|
1170
|
+
const focusComposer = () => {
|
|
1152
1171
|
const input = pendingInputRef.current;
|
|
1172
|
+
const card = pendingCardRef.current;
|
|
1153
1173
|
const saved = pendingFocus.current;
|
|
1154
|
-
if (!
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
active
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1174
|
+
if (!saved || movedAway) return;
|
|
1175
|
+
if (input && card) {
|
|
1176
|
+
if (card.contains(document.activeElement)) return;
|
|
1177
|
+
const active = document.activeElement;
|
|
1178
|
+
if (
|
|
1179
|
+
!isNew &&
|
|
1180
|
+
active &&
|
|
1181
|
+
active !== document.body &&
|
|
1182
|
+
active !== document.documentElement &&
|
|
1183
|
+
active.isConnected
|
|
1184
|
+
)
|
|
1185
|
+
return;
|
|
1186
|
+
input.focus();
|
|
1187
|
+
if (saved.start !== undefined && saved.end !== undefined)
|
|
1188
|
+
input.setSelection(saved.start, saved.end, saved.direction);
|
|
1189
|
+
if (card.contains(document.activeElement)) return;
|
|
1190
|
+
}
|
|
1191
|
+
// The composer may still be mounting, or hidden while its popover is
|
|
1192
|
+
// positioned; focus can't land until it's on screen.
|
|
1193
|
+
if (++attempts < 30) frame = requestAnimationFrame(focusComposer);
|
|
1194
|
+
};
|
|
1195
|
+
const timer = setTimeout(focusComposer, 50);
|
|
1196
|
+
return () => {
|
|
1197
|
+
clearTimeout(timer);
|
|
1198
|
+
cancelAnimationFrame(frame);
|
|
1199
|
+
document.removeEventListener("focusin", relinquish);
|
|
1200
|
+
};
|
|
1168
1201
|
}, [pendingFocus, presentation]);
|
|
1169
1202
|
|
|
1170
1203
|
const handlePendingSubmit = async (
|
|
@@ -1622,25 +1655,47 @@ export function CommentsSidebar({
|
|
|
1622
1655
|
const freshResolution =
|
|
1623
1656
|
thread.resolved && commentAi?.freshResolutions.has(thread.threadId);
|
|
1624
1657
|
const embedded = threadSuggestions.get(thread.threadId) ?? [];
|
|
1625
|
-
|
|
1658
|
+
// A receipt links the first edit of its proposal and stands for them all.
|
|
1659
|
+
const groups = proposalGroups(embedded);
|
|
1660
|
+
const receipts = new Map<string, ResourceSuggestion[]>();
|
|
1626
1661
|
for (const comment of thread.comments) {
|
|
1627
1662
|
const suggestion = receiptSuggestion(comment, embedded);
|
|
1628
|
-
|
|
1663
|
+
const group =
|
|
1664
|
+
suggestion && groups.find((members) => members.includes(suggestion));
|
|
1665
|
+
if (group) receipts.set(comment.id, group);
|
|
1629
1666
|
}
|
|
1630
|
-
const receiptless =
|
|
1631
|
-
(
|
|
1667
|
+
const receiptless = groups.filter(
|
|
1668
|
+
(group) => ![...receipts.values()].includes(group),
|
|
1632
1669
|
);
|
|
1633
1670
|
const aiWorking = Boolean(
|
|
1634
1671
|
commentAi && isCommentAiWorkingOn(commentAi, thread.threadId),
|
|
1635
1672
|
);
|
|
1636
|
-
const suggestionProps = (
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1673
|
+
const suggestionProps = (members: ResourceSuggestion[]) => {
|
|
1674
|
+
const proposalId = members.length > 1 ? members[0]?.proposalId : null;
|
|
1675
|
+
return {
|
|
1676
|
+
suggestions: members,
|
|
1677
|
+
canDecide: canDecideSuggestions,
|
|
1678
|
+
busy: suggestionBusy,
|
|
1679
|
+
conflict: isUnplaceable,
|
|
1680
|
+
onDecide: (
|
|
1681
|
+
suggestion: ResourceSuggestion,
|
|
1682
|
+
decision: SuggestionDecision,
|
|
1683
|
+
) => onDecideSuggestion?.(suggestion, decision),
|
|
1684
|
+
onDecideRemaining:
|
|
1685
|
+
proposalId && onDecideSuggestionProposal
|
|
1686
|
+
? (decision: SuggestionDecision) =>
|
|
1687
|
+
onDecideSuggestionProposal(
|
|
1688
|
+
proposalId,
|
|
1689
|
+
decision,
|
|
1690
|
+
suggestions.filter(
|
|
1691
|
+
(suggestion) =>
|
|
1692
|
+
suggestion.proposalId === proposalId &&
|
|
1693
|
+
suggestion.status === "pending",
|
|
1694
|
+
),
|
|
1695
|
+
)
|
|
1696
|
+
: undefined,
|
|
1697
|
+
};
|
|
1698
|
+
};
|
|
1644
1699
|
const showsRequestSuggestion =
|
|
1645
1700
|
aiRequest?.status === "suggested" &&
|
|
1646
1701
|
embedded.some(
|
|
@@ -1794,6 +1849,7 @@ export function CommentsSidebar({
|
|
|
1794
1849
|
resolved={Boolean(thread.resolved)}
|
|
1795
1850
|
surface={surface}
|
|
1796
1851
|
onClose={onClose}
|
|
1852
|
+
threadActions={renderThreadActions?.(thread)}
|
|
1797
1853
|
currentUserEmail={currentUserEmail}
|
|
1798
1854
|
quote={thread.quotedText}
|
|
1799
1855
|
propertyTarget={
|
|
@@ -1843,10 +1899,10 @@ export function CommentsSidebar({
|
|
|
1843
1899
|
feedback={
|
|
1844
1900
|
(aiRequest && commentAi) || receiptless.length ? (
|
|
1845
1901
|
<>
|
|
1846
|
-
{receiptless.map((
|
|
1902
|
+
{receiptless.map((members) => (
|
|
1847
1903
|
<ThreadSuggestionRow
|
|
1848
|
-
key={
|
|
1849
|
-
{...suggestionProps(
|
|
1904
|
+
key={members[0]!.id}
|
|
1905
|
+
{...suggestionProps(members)}
|
|
1850
1906
|
/>
|
|
1851
1907
|
))}
|
|
1852
1908
|
{!aiRequest || !commentAi ? null : continuation ||
|
|
@@ -264,7 +264,6 @@ import {
|
|
|
264
264
|
} from "./local-source-write-state";
|
|
265
265
|
import { NotionConflictBanner } from "./NotionConflictBanner";
|
|
266
266
|
import {
|
|
267
|
-
clearPageDraftJournal,
|
|
268
267
|
clearPageDraftJournalGeneration,
|
|
269
268
|
listPageDraftJournal,
|
|
270
269
|
syncPageDraftJournalBeforePersistingRecoveryDraft,
|
|
@@ -449,12 +448,14 @@ export interface PageEditorSession {
|
|
|
449
448
|
|
|
450
449
|
/**
|
|
451
450
|
* Opens the Page with its Comments showing: one thread when `threadId` is
|
|
452
|
-
* set,
|
|
451
|
+
* set, a new comment on the Property `propertyId` names, otherwise the list.
|
|
452
|
+
* A new `key` asks again.
|
|
453
453
|
*/
|
|
454
454
|
export interface PageCommentIntent {
|
|
455
455
|
key: number;
|
|
456
456
|
documentId: string;
|
|
457
457
|
threadId?: string | null;
|
|
458
|
+
propertyId?: string;
|
|
458
459
|
}
|
|
459
460
|
|
|
460
461
|
export interface PageEditorSurfaceProps extends DocumentEditorProps {
|
|
@@ -3346,24 +3347,11 @@ function PageEditorSessionBody({
|
|
|
3346
3347
|
[isLinkedLocalSourceDocument, isLocalFileDocument, journalScope, t],
|
|
3347
3348
|
);
|
|
3348
3349
|
const clearConfirmedDraftJournal = useCallback(
|
|
3349
|
-
(
|
|
3350
|
-
saved: Document & {
|
|
3351
|
-
bodyIntentOutcome?: { status: "applied" | "displaced-preserved" };
|
|
3352
|
-
},
|
|
3353
|
-
editGeneration: number,
|
|
3354
|
-
) => {
|
|
3350
|
+
(editGeneration: number) => {
|
|
3355
3351
|
const scope = journalScope();
|
|
3356
3352
|
if (!scope) return;
|
|
3357
3353
|
try {
|
|
3358
|
-
|
|
3359
|
-
clearPageDraftJournalGeneration(scope, editGeneration);
|
|
3360
|
-
} else {
|
|
3361
|
-
clearPageDraftJournal(scope, {
|
|
3362
|
-
editGeneration,
|
|
3363
|
-
title: saved.title,
|
|
3364
|
-
content: saved.content,
|
|
3365
|
-
});
|
|
3366
|
-
}
|
|
3354
|
+
clearPageDraftJournalGeneration(scope, editGeneration);
|
|
3367
3355
|
} catch {
|
|
3368
3356
|
if (!recoveryWriteErrorShownRef.current) {
|
|
3369
3357
|
toast.error(t("editor.pageSaveBeforeNavigationFailed"));
|
|
@@ -4354,17 +4342,13 @@ function PageEditorSessionBody({
|
|
|
4354
4342
|
: null,
|
|
4355
4343
|
});
|
|
4356
4344
|
}
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
4345
|
+
// Text another writer added while this save was in flight is theirs to
|
|
4346
|
+
// save, so it holds back neither the confirmation nor the journal clear.
|
|
4347
|
+
// An entry kept for it outlives this tab's edits, and the next tab to
|
|
4348
|
+
// open the page replays it as a save of its own.
|
|
4349
|
+
if (contentEditVersionRef.current === contentEditVersion) {
|
|
4361
4350
|
confirmedContentEditVersionRef.current = contentEditVersion;
|
|
4362
|
-
|
|
4363
|
-
saved.title === localTitleRef.current &&
|
|
4364
|
-
saved.content === localContentRef.current
|
|
4365
|
-
) {
|
|
4366
|
-
clearConfirmedDraftJournal(saved, editorEditGeneration);
|
|
4367
|
-
}
|
|
4351
|
+
clearConfirmedDraftJournal(editorEditGeneration);
|
|
4368
4352
|
}
|
|
4369
4353
|
|
|
4370
4354
|
if (autoSync) {
|
|
@@ -7408,6 +7392,7 @@ function PageEditorSessionBody({
|
|
|
7408
7392
|
)
|
|
7409
7393
|
? pagePropertiesQuery.data.properties
|
|
7410
7394
|
: undefined;
|
|
7395
|
+
const pagePropertiesFailed = !pageProperties && pagePropertiesQuery.isError;
|
|
7411
7396
|
const documentLayoutRef = useRef<HTMLDivElement>(null);
|
|
7412
7397
|
const commentLaneRef = useRef<HTMLElement>(null);
|
|
7413
7398
|
const anchoredCommentRef = useRef<HTMLElement>(null);
|
|
@@ -7792,6 +7777,7 @@ function PageEditorSessionBody({
|
|
|
7792
7777
|
],
|
|
7793
7778
|
);
|
|
7794
7779
|
const handledCommentDeepLinkRef = useRef<string | null>(null);
|
|
7780
|
+
const handledPropertyCommentLinkRef = useRef<string | null>(null);
|
|
7795
7781
|
|
|
7796
7782
|
const startPropertyComment = useCallback(
|
|
7797
7783
|
(property: DocumentProperty) => {
|
|
@@ -7928,6 +7914,40 @@ function PageEditorSessionBody({
|
|
|
7928
7914
|
activateCommentThread(threadId, true);
|
|
7929
7915
|
}, [activateCommentThread, documentId, location.search, threads]);
|
|
7930
7916
|
|
|
7917
|
+
// A Collection that opens rows as full pages links here to start a comment
|
|
7918
|
+
// on one Property, as the row's peek does on a touch screen.
|
|
7919
|
+
useEffect(() => {
|
|
7920
|
+
const propertyId = new URLSearchParams(location.search).get(
|
|
7921
|
+
"commentProperty",
|
|
7922
|
+
);
|
|
7923
|
+
if (!propertyId) {
|
|
7924
|
+
handledPropertyCommentLinkRef.current = null;
|
|
7925
|
+
return;
|
|
7926
|
+
}
|
|
7927
|
+
const linkKey = `${documentId}:${propertyId}`;
|
|
7928
|
+
// Wait for the Properties; a failed read opens the list instead.
|
|
7929
|
+
if (
|
|
7930
|
+
handledPropertyCommentLinkRef.current === linkKey ||
|
|
7931
|
+
(!pageProperties && !pagePropertiesFailed)
|
|
7932
|
+
) {
|
|
7933
|
+
return;
|
|
7934
|
+
}
|
|
7935
|
+
handledPropertyCommentLinkRef.current = linkKey;
|
|
7936
|
+
const property = pageProperties?.find(
|
|
7937
|
+
(candidate) => candidate.definition.id === propertyId,
|
|
7938
|
+
);
|
|
7939
|
+
if (property && canComment) startPropertyComment(property);
|
|
7940
|
+
else handleUtilityPanelChange("comments");
|
|
7941
|
+
}, [
|
|
7942
|
+
canComment,
|
|
7943
|
+
documentId,
|
|
7944
|
+
handleUtilityPanelChange,
|
|
7945
|
+
location.search,
|
|
7946
|
+
pageProperties,
|
|
7947
|
+
pagePropertiesFailed,
|
|
7948
|
+
startPropertyComment,
|
|
7949
|
+
]);
|
|
7950
|
+
|
|
7931
7951
|
const handledCommentIntentRef = useRef<number | null>(null);
|
|
7932
7952
|
useEffect(() => {
|
|
7933
7953
|
if (
|
|
@@ -7935,7 +7955,7 @@ function PageEditorSessionBody({
|
|
|
7935
7955
|
handledCommentIntentRef.current === commentIntent.key
|
|
7936
7956
|
)
|
|
7937
7957
|
return;
|
|
7938
|
-
const threadId = commentIntent
|
|
7958
|
+
const { threadId, propertyId } = commentIntent;
|
|
7939
7959
|
if (threadId) {
|
|
7940
7960
|
// Wait for the thread; a thread that is gone opens the list instead.
|
|
7941
7961
|
if (!threads) return;
|
|
@@ -7944,14 +7964,30 @@ function PageEditorSessionBody({
|
|
|
7944
7964
|
activateCommentThread(threadId);
|
|
7945
7965
|
return;
|
|
7946
7966
|
}
|
|
7967
|
+
} else if (propertyId) {
|
|
7968
|
+
// Wait for the Properties; one that is gone, or a failed read, opens
|
|
7969
|
+
// the list instead.
|
|
7970
|
+
if (!pageProperties && !pagePropertiesFailed) return;
|
|
7971
|
+
handledCommentIntentRef.current = commentIntent.key;
|
|
7972
|
+
const property = pageProperties?.find(
|
|
7973
|
+
(candidate) => candidate.definition.id === propertyId,
|
|
7974
|
+
);
|
|
7975
|
+
if (property && canComment) {
|
|
7976
|
+
startPropertyComment(property);
|
|
7977
|
+
return;
|
|
7978
|
+
}
|
|
7947
7979
|
}
|
|
7948
7980
|
handledCommentIntentRef.current = commentIntent.key;
|
|
7949
7981
|
handleUtilityPanelChange("comments");
|
|
7950
7982
|
}, [
|
|
7951
7983
|
activateCommentThread,
|
|
7984
|
+
canComment,
|
|
7952
7985
|
commentIntent,
|
|
7953
7986
|
documentId,
|
|
7954
7987
|
handleUtilityPanelChange,
|
|
7988
|
+
pageProperties,
|
|
7989
|
+
pagePropertiesFailed,
|
|
7990
|
+
startPropertyComment,
|
|
7955
7991
|
threads,
|
|
7956
7992
|
]);
|
|
7957
7993
|
|