@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,12 +1,111 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
ContentDatabaseFilter,
|
|
3
3
|
ContentDatabaseFilterMode,
|
|
4
|
+
ContentDatabaseFilterOperator,
|
|
4
5
|
ContentDatabaseItem,
|
|
5
6
|
ContentDatabaseSort,
|
|
6
7
|
ContentDatabaseTableQuery,
|
|
7
8
|
DocumentProperty,
|
|
8
9
|
} from "./api.js";
|
|
9
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
documentPropertyDatePart,
|
|
12
|
+
formulaValueText,
|
|
13
|
+
type DocumentPropertyType,
|
|
14
|
+
} from "./properties.js";
|
|
15
|
+
|
|
16
|
+
const OPTION_FILTER_OPERATORS = [
|
|
17
|
+
"contains",
|
|
18
|
+
"does_not_equal",
|
|
19
|
+
"is_empty",
|
|
20
|
+
"is_not_empty",
|
|
21
|
+
] as const satisfies readonly ContentDatabaseFilterOperator[];
|
|
22
|
+
|
|
23
|
+
const DATE_FILTER_OPERATORS = [
|
|
24
|
+
"equals",
|
|
25
|
+
"does_not_equal",
|
|
26
|
+
"before",
|
|
27
|
+
"after",
|
|
28
|
+
"between",
|
|
29
|
+
"is_empty",
|
|
30
|
+
"is_not_empty",
|
|
31
|
+
] as const satisfies readonly ContentDatabaseFilterOperator[];
|
|
32
|
+
|
|
33
|
+
const FILTER_OPERATORS_BY_TYPE: Partial<
|
|
34
|
+
Record<DocumentPropertyType, readonly ContentDatabaseFilterOperator[]>
|
|
35
|
+
> = {
|
|
36
|
+
checkbox: ["is_checked", "is_unchecked"],
|
|
37
|
+
select: OPTION_FILTER_OPERATORS,
|
|
38
|
+
status: OPTION_FILTER_OPERATORS,
|
|
39
|
+
multi_select: OPTION_FILTER_OPERATORS,
|
|
40
|
+
person: OPTION_FILTER_OPERATORS,
|
|
41
|
+
number: [
|
|
42
|
+
"equals",
|
|
43
|
+
"does_not_equal",
|
|
44
|
+
"greater_than",
|
|
45
|
+
"less_than",
|
|
46
|
+
"greater_than_or_equal",
|
|
47
|
+
"less_than_or_equal",
|
|
48
|
+
"is_empty",
|
|
49
|
+
"is_not_empty",
|
|
50
|
+
],
|
|
51
|
+
date: DATE_FILTER_OPERATORS,
|
|
52
|
+
created_time: DATE_FILTER_OPERATORS,
|
|
53
|
+
last_edited_time: DATE_FILTER_OPERATORS,
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const DEFAULT_FILTER_OPERATORS = [
|
|
57
|
+
"contains",
|
|
58
|
+
"equals",
|
|
59
|
+
"does_not_equal",
|
|
60
|
+
"is_empty",
|
|
61
|
+
"is_not_empty",
|
|
62
|
+
] as const satisfies readonly ContentDatabaseFilterOperator[];
|
|
63
|
+
|
|
64
|
+
const VALUELESS_FILTER_OPERATORS: ReadonlySet<ContentDatabaseFilterOperator> =
|
|
65
|
+
new Set(["is_empty", "is_not_empty", "is_checked", "is_unchecked"]);
|
|
66
|
+
|
|
67
|
+
const NUMBER_FILTER_COMPARISONS: Partial<
|
|
68
|
+
Record<
|
|
69
|
+
ContentDatabaseFilterOperator,
|
|
70
|
+
(current: number, target: number) => boolean
|
|
71
|
+
>
|
|
72
|
+
> = {
|
|
73
|
+
greater_than: (current, target) => current > target,
|
|
74
|
+
less_than: (current, target) => current < target,
|
|
75
|
+
greater_than_or_equal: (current, target) => current >= target,
|
|
76
|
+
less_than_or_equal: (current, target) => current <= target,
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// Listed in menu order; the first entry is the operator a new filter starts with.
|
|
80
|
+
export function contentDatabaseFilterOperatorsForType(
|
|
81
|
+
type: DocumentPropertyType,
|
|
82
|
+
): readonly ContentDatabaseFilterOperator[] {
|
|
83
|
+
return FILTER_OPERATORS_BY_TYPE[type] ?? DEFAULT_FILTER_OPERATORS;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function contentDatabaseFilterOperatorNeedsValue(
|
|
87
|
+
operator: ContentDatabaseFilterOperator,
|
|
88
|
+
) {
|
|
89
|
+
return !VALUELESS_FILTER_OPERATORS.has(operator);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// A "between" value is a JSON [start, end] pair. Read it by position: a
|
|
93
|
+
// one-day range repeats its date, so deduplicating would lose the end.
|
|
94
|
+
export function contentDatabaseFilterDateRangeValues(
|
|
95
|
+
value: string,
|
|
96
|
+
): [string, string] {
|
|
97
|
+
const trimmed = value.trim();
|
|
98
|
+
if (!trimmed) return ["", ""];
|
|
99
|
+
try {
|
|
100
|
+
const parsed = JSON.parse(trimmed) as unknown;
|
|
101
|
+
if (!Array.isArray(parsed)) return [trimmed, ""];
|
|
102
|
+
const start = typeof parsed[0] === "string" ? parsed[0].trim() : "";
|
|
103
|
+
const end = typeof parsed[1] === "string" ? parsed[1].trim() : "";
|
|
104
|
+
return [start, end];
|
|
105
|
+
} catch {
|
|
106
|
+
return [trimmed, ""];
|
|
107
|
+
}
|
|
108
|
+
}
|
|
10
109
|
|
|
11
110
|
export function applyContentDatabaseTableQuery(
|
|
12
111
|
items: ContentDatabaseItem[],
|
|
@@ -94,11 +193,7 @@ function combineMatches(
|
|
|
94
193
|
}
|
|
95
194
|
|
|
96
195
|
function isActiveFilter(filter: ContentDatabaseFilter) {
|
|
97
|
-
if (
|
|
98
|
-
!["is_empty", "is_not_empty", "is_checked", "is_unchecked"].includes(
|
|
99
|
-
filter.operator,
|
|
100
|
-
)
|
|
101
|
-
) {
|
|
196
|
+
if (contentDatabaseFilterOperatorNeedsValue(filter.operator)) {
|
|
102
197
|
return selectedFilterValues(filter.value).length > 0;
|
|
103
198
|
}
|
|
104
199
|
return true;
|
|
@@ -116,13 +211,12 @@ function itemMatchesFilter(
|
|
|
116
211
|
if (filter.operator === "is_checked") return property?.value === true;
|
|
117
212
|
if (filter.operator === "is_unchecked") return property?.value !== true;
|
|
118
213
|
|
|
119
|
-
|
|
214
|
+
const compareNumbers = NUMBER_FILTER_COMPARISONS[filter.operator];
|
|
215
|
+
if (compareNumbers) {
|
|
120
216
|
const current = propertyNumberValue(property);
|
|
121
217
|
const target = Number(filter.value.trim());
|
|
122
218
|
if (!Number.isFinite(current) || !Number.isFinite(target)) return false;
|
|
123
|
-
return
|
|
124
|
-
? current > target
|
|
125
|
-
: current < target;
|
|
219
|
+
return compareNumbers(current, target);
|
|
126
220
|
}
|
|
127
221
|
|
|
128
222
|
if (
|
|
@@ -198,9 +292,9 @@ function selectedFilterValues(value: string) {
|
|
|
198
292
|
}
|
|
199
293
|
|
|
200
294
|
function filterDateRange(value: string): [number, number] | null {
|
|
201
|
-
const
|
|
202
|
-
const start = new Date(
|
|
203
|
-
const end = new Date(
|
|
295
|
+
const [startValue, endValue] = contentDatabaseFilterDateRangeValues(value);
|
|
296
|
+
const start = new Date(startValue).getTime();
|
|
297
|
+
const end = new Date(endValue).getTime();
|
|
204
298
|
if (!Number.isFinite(start) || !Number.isFinite(end)) return null;
|
|
205
299
|
return start <= end ? [start, end] : [end, start];
|
|
206
300
|
}
|
|
@@ -951,6 +951,41 @@ export function markdownSuggestionOperationsForFindReplace(input: {
|
|
|
951
951
|
return operations;
|
|
952
952
|
}
|
|
953
953
|
|
|
954
|
+
/**
|
|
955
|
+
* Splits several non-overlapping find/replace edits against one source the
|
|
956
|
+
* way one edit is split. Each operation's anchor context stops at its
|
|
957
|
+
* neighbors, so accepting one never rewrites the text another is found by.
|
|
958
|
+
*/
|
|
959
|
+
export function markdownSuggestionOperationsForFindReplaceEdits(input: {
|
|
960
|
+
before: string;
|
|
961
|
+
edits: ReadonlyArray<{ find: string; replace: string; start: number }>;
|
|
962
|
+
}): MarkdownSuggestionOperation[] {
|
|
963
|
+
return isolateSiblingAnchorContexts(
|
|
964
|
+
input.edits
|
|
965
|
+
.flatMap((edit) =>
|
|
966
|
+
markdownSuggestionOperationsForFindReplace({
|
|
967
|
+
before: input.before,
|
|
968
|
+
...edit,
|
|
969
|
+
}),
|
|
970
|
+
)
|
|
971
|
+
.sort((left, right) => left.anchor.from - right.anchor.from)
|
|
972
|
+
.map((operation, ordinal) => ({ ...operation, ordinal })),
|
|
973
|
+
);
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
/** A one-line review label for a single operation's change. */
|
|
977
|
+
export function markdownSuggestionOperationSummary(
|
|
978
|
+
operation: MarkdownSuggestionOperation,
|
|
979
|
+
): string {
|
|
980
|
+
const removed = operation.before.changedText.slice(0, 80);
|
|
981
|
+
const inserted = operation.after.changedText.slice(0, 80);
|
|
982
|
+
return removed && inserted
|
|
983
|
+
? `Replace "${removed}" with "${inserted}"`
|
|
984
|
+
: removed
|
|
985
|
+
? `Delete "${removed}"`
|
|
986
|
+
: `Insert "${inserted}"`;
|
|
987
|
+
}
|
|
988
|
+
|
|
954
989
|
export function markdownSuggestionOperationsForReplacements(input: {
|
|
955
990
|
before: string;
|
|
956
991
|
after: string;
|
|
@@ -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
|
|
99
|
-
|
|
100
|
-
|
|
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.
|
|
@@ -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
|
|
99
|
-
|
|
100
|
-
|
|
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.
|
|
@@ -11,6 +11,7 @@ from the command menu (Cmd+K → "What's new") or from Settings.
|
|
|
11
11
|
|
|
12
12
|
### Fixed
|
|
13
13
|
|
|
14
|
+
- Comments return to the prior Interact screen and device, stay open when that return is blocked, and fall back to Design if that screen was removed. Layers search toggles closed.
|
|
14
15
|
- Public Design viewers no longer see repeated PNG export errors
|
|
15
16
|
- Design resizes more high-resolution reference images before adding them to a prompt.
|
|
16
17
|
- Review menus no longer trigger canvas gestures, and rewrite previews and proposal controls follow the uniquely resolved target.
|
|
@@ -29,6 +30,7 @@ from the command menu (Cmd+K → "What's new") or from Settings.
|
|
|
29
30
|
|
|
30
31
|
### Fixed
|
|
31
32
|
|
|
33
|
+
- Reply and Edit on a comment, in the sidebar or on a canvas pin, put the cursor in the box, after the text when editing.
|
|
32
34
|
- Comment mode no longer selects or hovers the design underneath, and pan and zoom work while commenting.
|
|
33
35
|
- Text color adapts when layers move between screens with different backgrounds
|
|
34
36
|
- Text keeps readable contrast when moved between screens with different backgrounds.
|
|
@@ -432,10 +432,7 @@ export default defineAction({
|
|
|
432
432
|
"Legacy frontend checkpoint to validate before a delete; the live pre-delete version is always captured in the delete transaction.",
|
|
433
433
|
),
|
|
434
434
|
}),
|
|
435
|
-
|
|
436
|
-
// normalizes boolean strings, so use the same truthiness boundary here.
|
|
437
|
-
needsApproval: (args) =>
|
|
438
|
-
Boolean((args as unknown as Record<string, unknown>).allowLockedLayers),
|
|
435
|
+
needsApproval: ({ allowLockedLayers }) => allowLockedLayers,
|
|
439
436
|
allowPersistentApproval: false,
|
|
440
437
|
run: async (
|
|
441
438
|
{ id, fileIds, allowLockedLayers, historyCheckpointId },
|
|
@@ -1188,6 +1188,15 @@ function LayersPanelImpl(
|
|
|
1188
1188
|
window.requestAnimationFrame(() => searchInputRef.current?.focus());
|
|
1189
1189
|
}, []);
|
|
1190
1190
|
|
|
1191
|
+
const toggleSearch = useCallback(() => {
|
|
1192
|
+
if (searchOpen || searchQuery.trim()) {
|
|
1193
|
+
setSearchOpen(false);
|
|
1194
|
+
if (searchQuery) onSearchQueryChange("");
|
|
1195
|
+
return;
|
|
1196
|
+
}
|
|
1197
|
+
focusSearch();
|
|
1198
|
+
}, [focusSearch, onSearchQueryChange, searchOpen, searchQuery]);
|
|
1199
|
+
|
|
1191
1200
|
const beginRename = useCallback(
|
|
1192
1201
|
(layerId: string): boolean => {
|
|
1193
1202
|
if (!onRename) return false;
|
|
@@ -1570,7 +1579,7 @@ function LayersPanelImpl(
|
|
|
1570
1579
|
<IconTooltipButton
|
|
1571
1580
|
label={labels.searchPlaceholder}
|
|
1572
1581
|
dataAction="search"
|
|
1573
|
-
onClick={
|
|
1582
|
+
onClick={toggleSearch}
|
|
1574
1583
|
>
|
|
1575
1584
|
<IconSearch
|
|
1576
1585
|
className="!size-[var(--design-icon-size)]"
|
|
@@ -1108,6 +1108,8 @@ export function ReviewCanvasPins({
|
|
|
1108
1108
|
const setUnread = useSetReviewThreadUnread();
|
|
1109
1109
|
const updateComment = useUpdateReviewComment();
|
|
1110
1110
|
const [canvas, setCanvas] = useState<HTMLElement | null>(null);
|
|
1111
|
+
const onCloseRef = useRef(onClose);
|
|
1112
|
+
onCloseRef.current = onClose;
|
|
1111
1113
|
const [layoutTick, setLayoutTick] = useState(0);
|
|
1112
1114
|
const [draftPin, setDraftPin] = useState<ReviewDraftPin | null>(null);
|
|
1113
1115
|
const [draftAttachments, setDraftAttachments] = useState<
|
|
@@ -1964,8 +1966,8 @@ export function ReviewCanvasPins({
|
|
|
1964
1966
|
setReplyAttachments([]);
|
|
1965
1967
|
setReplyMentions([]);
|
|
1966
1968
|
setExpandedClusterKey(null);
|
|
1967
|
-
if (active)
|
|
1968
|
-
}, [active, cancelDraft, hidden
|
|
1969
|
+
if (active) onCloseRef.current();
|
|
1970
|
+
}, [active, cancelDraft, hidden]);
|
|
1969
1971
|
|
|
1970
1972
|
const dropCanvasPin = useCallback(
|
|
1971
1973
|
(canvasPoint: ReviewCanvasPoint) => {
|
|
@@ -3977,6 +3979,7 @@ function ReviewThreadPopover({
|
|
|
3977
3979
|
onSendToAgent?: () => void;
|
|
3978
3980
|
}) {
|
|
3979
3981
|
const t = useT();
|
|
3982
|
+
const editChosenFromMenuRef = useRef(false);
|
|
3980
3983
|
const rootAuthor = reviewAuthorLabel(thread.root, t("review.reviewer"));
|
|
3981
3984
|
const avatarUrl = useAvatarUrl(thread.root.authorEmail);
|
|
3982
3985
|
const [replyUploading, setReplyUploading] = useState(false);
|
|
@@ -4015,6 +4018,7 @@ function ReviewThreadPopover({
|
|
|
4015
4018
|
mentionLabel={t("review.mention")}
|
|
4016
4019
|
noMentionsLabel={t("review.noMentions")}
|
|
4017
4020
|
textareaProps={{ "aria-label": t("review.editComment") }}
|
|
4021
|
+
autoFocus
|
|
4018
4022
|
disabled={updating}
|
|
4019
4023
|
onChange={onEditDraftChange}
|
|
4020
4024
|
onSubmit={() => onEditSubmit()}
|
|
@@ -4058,9 +4062,24 @@ function ReviewThreadPopover({
|
|
|
4058
4062
|
<IconDots className="size-3.5" />
|
|
4059
4063
|
</Button>
|
|
4060
4064
|
</DropdownMenuTrigger>
|
|
4061
|
-
<DropdownMenuContent
|
|
4065
|
+
<DropdownMenuContent
|
|
4066
|
+
data-review-popover
|
|
4067
|
+
align="end"
|
|
4068
|
+
className="w-44"
|
|
4069
|
+
onCloseAutoFocus={(event) => {
|
|
4070
|
+
// Edit puts focus in its composer; leave it there. Other
|
|
4071
|
+
// actions return to the button.
|
|
4072
|
+
if (editChosenFromMenuRef.current) event.preventDefault();
|
|
4073
|
+
editChosenFromMenuRef.current = false;
|
|
4074
|
+
}}
|
|
4075
|
+
>
|
|
4062
4076
|
{canEdit ? (
|
|
4063
|
-
<DropdownMenuItem
|
|
4077
|
+
<DropdownMenuItem
|
|
4078
|
+
onSelect={() => {
|
|
4079
|
+
editChosenFromMenuRef.current = true;
|
|
4080
|
+
onEdit();
|
|
4081
|
+
}}
|
|
4082
|
+
>
|
|
4064
4083
|
{t("review.editComment")}
|
|
4065
4084
|
</DropdownMenuItem>
|
|
4066
4085
|
) : null}
|
|
@@ -4144,6 +4163,7 @@ function ReviewThreadPopover({
|
|
|
4144
4163
|
mentionLabel={t("review.mention")}
|
|
4145
4164
|
noMentionsLabel={t("review.noMentions")}
|
|
4146
4165
|
textareaProps={{ "data-review-reply-input": true }}
|
|
4166
|
+
autoFocus
|
|
4147
4167
|
disabled={replying || resolving || replyUploading}
|
|
4148
4168
|
onChange={onReplyDraftChange}
|
|
4149
4169
|
onSubmit={() => onReply()}
|
|
@@ -97,7 +97,7 @@ export function runModeChange(
|
|
|
97
97
|
/** A screen change made from inside Interact keeps the chosen device. */
|
|
98
98
|
keepInteractDevice?: boolean;
|
|
99
99
|
},
|
|
100
|
-
) {
|
|
100
|
+
): boolean {
|
|
101
101
|
const next = resolveModeForAnnotateLab(requested, annotateLab);
|
|
102
102
|
const interactEntry =
|
|
103
103
|
next === "interact"
|
|
@@ -120,7 +120,7 @@ export function runModeChange(
|
|
|
120
120
|
: options?.targetFileId
|
|
121
121
|
? files.find((file) => file.id === options.targetFileId)
|
|
122
122
|
: activeFile;
|
|
123
|
-
if (!canEditDesign && next === "annotate") return;
|
|
123
|
+
if (!canEditDesign && next === "annotate") return false;
|
|
124
124
|
if (interactEntry && !nextActiveFile) {
|
|
125
125
|
toast.error(
|
|
126
126
|
t(
|
|
@@ -129,9 +129,9 @@ export function runModeChange(
|
|
|
129
129
|
: "designEditor.responsiveInteract.unknownScreen",
|
|
130
130
|
),
|
|
131
131
|
);
|
|
132
|
-
return;
|
|
132
|
+
return false;
|
|
133
133
|
}
|
|
134
|
-
if (next === "annotate" && !nextActiveFile) return;
|
|
134
|
+
if (next === "annotate" && !nextActiveFile) return false;
|
|
135
135
|
if (
|
|
136
136
|
next === "interact" &&
|
|
137
137
|
hasPendingVisualEdits &&
|
|
@@ -140,7 +140,7 @@ export function runModeChange(
|
|
|
140
140
|
) {
|
|
141
141
|
onPendingVisualEditsBlocked();
|
|
142
142
|
toast.error(t("designEditor.pendingVisualStyles.interactBlocked"));
|
|
143
|
-
return;
|
|
143
|
+
return false;
|
|
144
144
|
}
|
|
145
145
|
if (options?.discardPendingLiveEdits) {
|
|
146
146
|
requestPendingVisualStyleRevert(pendingVisualStyleEdits);
|
|
@@ -155,13 +155,17 @@ export function runModeChange(
|
|
|
155
155
|
if (routing === "enter-single-interact") {
|
|
156
156
|
rememberOverviewScreenSelection(nextActiveFile!.id);
|
|
157
157
|
setOverviewInteractScreenId(nextActiveFile!.id);
|
|
158
|
-
|
|
159
|
-
|
|
158
|
+
if (options?.keepInteractDevice) {
|
|
159
|
+
enterSingleScreen(nextActiveFile?.id, { keepInteractDevice: true });
|
|
160
|
+
} else {
|
|
161
|
+
enterSingleScreen(nextActiveFile?.id);
|
|
162
|
+
}
|
|
163
|
+
return true;
|
|
160
164
|
}
|
|
161
165
|
if (routing === "enter-overview") {
|
|
162
166
|
if (options?.targetFileId) setActiveFileId(options.targetFileId);
|
|
163
167
|
enterOverviewFromZoom(next);
|
|
164
|
-
return;
|
|
168
|
+
return true;
|
|
165
169
|
}
|
|
166
170
|
if (next === "interact" && overviewInteractScreenId) {
|
|
167
171
|
setOverviewInteractScreenId(nextActiveFile!.id);
|
|
@@ -175,7 +179,7 @@ export function runModeChange(
|
|
|
175
179
|
} else {
|
|
176
180
|
enterSingleScreen(nextActiveFile!.id);
|
|
177
181
|
}
|
|
178
|
-
return;
|
|
182
|
+
return true;
|
|
179
183
|
}
|
|
180
184
|
}
|
|
181
185
|
if (options?.targetFileId) setActiveFileId(options.targetFileId);
|
|
@@ -202,4 +206,5 @@ export function runModeChange(
|
|
|
202
206
|
setDrawMode(false);
|
|
203
207
|
setPinMode(false);
|
|
204
208
|
}
|
|
209
|
+
return true;
|
|
205
210
|
}
|
|
@@ -48,6 +48,8 @@ import {
|
|
|
48
48
|
import { computeInteractZoomToFit } from "../responsive-interact";
|
|
49
49
|
import { hasSelectableCodeLayerParent } from "../selection-state";
|
|
50
50
|
import {
|
|
51
|
+
commentModeReturnOptions,
|
|
52
|
+
resolveCommentModeExit,
|
|
51
53
|
resolveDrawStateWithoutLab,
|
|
52
54
|
resolveModeForAnnotateLab,
|
|
53
55
|
shouldAutoEnableDrawOverlay,
|
|
@@ -466,6 +468,16 @@ export function useEditorModes({
|
|
|
466
468
|
|
|
467
469
|
const lastSettledSingleZoomRef = useRef<number | null>(null);
|
|
468
470
|
const suppressOverviewPopForExplicitZoomRef = useRef(false);
|
|
471
|
+
const commentReturnOptionsRef =
|
|
472
|
+
useRef<ReturnType<typeof commentModeReturnOptions>>(null);
|
|
473
|
+
useEffect(() => {
|
|
474
|
+
if (mode === "annotate") return;
|
|
475
|
+
commentReturnOptionsRef.current = commentModeReturnOptions(
|
|
476
|
+
mode,
|
|
477
|
+
activeFile?.id,
|
|
478
|
+
);
|
|
479
|
+
}, [activeFile?.id, mode]);
|
|
480
|
+
|
|
469
481
|
useEffect(() => {
|
|
470
482
|
if (!activeFile || viewMode !== "single" || mode !== "edit") {
|
|
471
483
|
lastSettledSingleZoomRef.current = null;
|
|
@@ -781,12 +793,21 @@ export function useEditorModes({
|
|
|
781
793
|
]);
|
|
782
794
|
|
|
783
795
|
const handleExitReviewCommentMode = useCallback(() => {
|
|
796
|
+
const exit = resolveCommentModeExit(
|
|
797
|
+
commentReturnOptionsRef.current,
|
|
798
|
+
new Set(overviewScreens.map((screen) => screen.id)),
|
|
799
|
+
new Set(files.map((file) => file.id)),
|
|
800
|
+
);
|
|
801
|
+
if (exit.mode === "interact" && !handleModeChange("interact", exit.options))
|
|
802
|
+
return;
|
|
803
|
+
|
|
804
|
+
commentReturnOptionsRef.current = null;
|
|
784
805
|
setPinMode(false);
|
|
785
806
|
setOverviewCommentPinRequest(null);
|
|
786
807
|
setDrawMode(false);
|
|
787
808
|
setActiveTool("move");
|
|
788
|
-
setMode("edit");
|
|
789
|
-
}, []);
|
|
809
|
+
if (exit.mode === "edit") setMode("edit");
|
|
810
|
+
}, [files, handleModeChange, overviewScreens]);
|
|
790
811
|
|
|
791
812
|
const handleOverviewCommentPin = useCallback(
|
|
792
813
|
(canvasPoint: { x: number; y: number }) => {
|
|
@@ -82,6 +82,35 @@ export function normalizeDesignMode(value: unknown): EditorMode | null {
|
|
|
82
82
|
: null;
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
export function commentModeReturnOptions(
|
|
86
|
+
mode: EditorMode,
|
|
87
|
+
activeFileId: string | null | undefined,
|
|
88
|
+
): CommentModeReturnOptions | null {
|
|
89
|
+
return mode === "interact" && activeFileId
|
|
90
|
+
? { targetFileId: activeFileId, keepInteractDevice: true }
|
|
91
|
+
: null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface CommentModeReturnOptions {
|
|
95
|
+
targetFileId: string;
|
|
96
|
+
keepInteractDevice: true;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function resolveCommentModeExit(
|
|
100
|
+
returnOptions: CommentModeReturnOptions | null,
|
|
101
|
+
screenIds: ReadonlySet<string>,
|
|
102
|
+
fileIds: ReadonlySet<string>,
|
|
103
|
+
): { mode: "interact"; options: CommentModeReturnOptions } | { mode: "edit" } {
|
|
104
|
+
if (
|
|
105
|
+
returnOptions &&
|
|
106
|
+
screenIds.has(returnOptions.targetFileId) &&
|
|
107
|
+
fileIds.has(returnOptions.targetFileId)
|
|
108
|
+
) {
|
|
109
|
+
return { mode: "interact", options: returnOptions };
|
|
110
|
+
}
|
|
111
|
+
return { mode: "edit" };
|
|
112
|
+
}
|
|
113
|
+
|
|
85
114
|
/**
|
|
86
115
|
* `loading` is the stretch before the Labs answer arrives. It reads as off, but
|
|
87
116
|
* a request that needs the lab waits for the answer instead of being refused.
|