@agent-native/core-corpus 0.216.0 → 0.218.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/session-replay/SKILL.md +61 -1
- package/corpus/templates/analytics/AGENTS.md +1 -1
- package/corpus/templates/analytics/CHANGELOG.md +3 -0
- package/corpus/templates/analytics/actions/get-session-replay-summary.ts +9 -65
- package/corpus/templates/analytics/actions/list-event-catalog.ts +1 -1
- package/corpus/templates/analytics/actions/list-session-outputs.ts +40 -0
- package/corpus/templates/analytics/actions/list-session-recordings.ts +17 -4
- package/corpus/templates/analytics/actions/view-screen.ts +29 -3
- package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +38 -167
- package/corpus/templates/analytics/app/i18n/en-US.ts +27 -0
- package/corpus/templates/analytics/app/pages/sessions/EventCatalogPage.tsx +18 -1
- package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +3 -1
- package/corpus/templates/analytics/app/pages/sessions/SessionOutputs.tsx +197 -0
- package/corpus/templates/analytics/app/pages/sessions/SessionsTriagePage.tsx +114 -4
- package/corpus/templates/analytics/changelog/2026-10-15-sessions-can-find-people-who-made-a-slides-deck-but-didn-t-p.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-16-owned-and-shared-chats-appear-in-separate-history-sections.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-16-the-activation-funnel-counts-visitors-who-decline-analytics-.md +6 -0
- package/corpus/templates/analytics/server/db/schema.ts +67 -0
- package/corpus/templates/analytics/server/lib/canonical-first-party-dashboard-repair.ts +9 -0
- package/corpus/templates/analytics/server/lib/first-party-analytics.ts +28 -22
- package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +36 -1
- package/corpus/templates/analytics/server/lib/output-outcomes.ts +712 -0
- package/corpus/templates/analytics/server/lib/replay-origins.ts +33 -0
- package/corpus/templates/analytics/server/lib/session-event-index.ts +20 -0
- package/corpus/templates/analytics/server/lib/session-performance.ts +6 -40
- package/corpus/templates/analytics/server/lib/session-recording-associations.ts +30 -1
- package/corpus/templates/analytics/server/lib/session-replay-triage.ts +99 -0
- package/corpus/templates/analytics/server/lib/session-replay.ts +45 -31
- package/corpus/templates/analytics/server/lib/sessions-triage-lab.ts +8 -2
- package/corpus/templates/analytics/server/plugins/db.ts +46 -0
- package/corpus/templates/analytics/shared/output-use.ts +205 -0
- package/corpus/templates/analytics/shared/session-events.ts +6 -0
- package/corpus/templates/assets/CHANGELOG.md +6 -0
- package/corpus/templates/assets/app/components/layout/Sidebar.tsx +51 -117
- package/corpus/templates/assets/changelog/2026-10-16-owned-and-shared-chats-appear-in-separate-history-sections.md +6 -0
- package/corpus/templates/brain/app/components/layout/Sidebar.tsx +34 -101
- package/corpus/templates/brain/app/routes/sources.tsx +1 -1
- package/corpus/templates/brain/vitest.config.ts +7 -0
- package/corpus/templates/calendar/CHANGELOG.md +12 -0
- package/corpus/templates/calendar/actions/create-event.ts +2 -4
- package/corpus/templates/calendar/actions/update-event.ts +35 -6
- package/corpus/templates/calendar/app/pages/BookingLinksPage.tsx +1 -1
- package/corpus/templates/calendar/changelog/2026-10-16-booking-links-show-localized-visibility-badges.md +6 -0
- package/corpus/templates/calendar/changelog/2026-10-21-solo-events-avoid-notification-approval.md +6 -0
- package/corpus/templates/chat/app/components/layout/Sidebar.tsx +62 -101
- package/corpus/templates/clips/CHANGELOG.md +7 -20
- package/corpus/templates/clips/app/components/meetings/share-meeting-dialog.tsx +20 -77
- package/corpus/templates/clips/app/components/player/share-dialog.tsx +63 -90
- package/corpus/templates/clips/app/components/sharing/share-test-support.tsx +180 -0
- package/corpus/templates/clips/app/components/sharing/share-ui.tsx +5 -584
- package/corpus/templates/clips/changelog/2026-10-16-share-dialogs-make-it-easier-to-see-who-has-access-and-manag.md +6 -0
- package/corpus/templates/clips/changelog/2026-10-21-share-panels-describe-general-access-in-a-sentence.md +6 -0
- package/corpus/templates/community/qr/package.json +1 -1
- package/corpus/templates/content/.agents/skills/document-editing/references/databases.md +6 -3
- package/corpus/templates/content/CHANGELOG.md +10 -0
- package/corpus/templates/content/actions/_browser-document-save-attempt.ts +23 -1
- package/corpus/templates/content/actions/_builder-cms-blob-custody.ts +467 -0
- package/corpus/templates/content/actions/_builder-cms-execution-lease.ts +17 -0
- package/corpus/templates/content/actions/_builder-cms-intent-lookup.ts +5 -0
- package/corpus/templates/content/actions/_builder-cms-read-client.ts +881 -21
- package/corpus/templates/content/actions/_builder-cms-write-adapter.ts +28 -2
- package/corpus/templates/content/actions/_builder-cms-write-client.ts +109 -11
- package/corpus/templates/content/actions/_builder-docs-client.ts +1 -0
- package/corpus/templates/content/actions/_builder-private-blob.test-fixture.ts +90 -0
- package/corpus/templates/content/actions/_builder-source-execution-guard.ts +48 -0
- package/corpus/templates/content/actions/_content-save-outcomes.ts +5 -0
- package/corpus/templates/content/actions/_database-source-utils.ts +290 -8
- package/corpus/templates/content/actions/_database-utils.ts +93 -7
- package/corpus/templates/content/actions/_database-view-setup.ts +21 -2
- package/corpus/templates/content/actions/_delete-content-space.ts +8 -1
- package/corpus/templates/content/actions/_github-source-freshness.ts +30 -4
- package/corpus/templates/content/actions/add-content-database-source-field-property.ts +23 -6
- package/corpus/templates/content/actions/cancel-prepared-builder-source-update.ts +110 -26
- package/corpus/templates/content/actions/change-content-database-source-role.ts +51 -7
- package/corpus/templates/content/actions/delete-document.ts +41 -3
- package/corpus/templates/content/actions/disconnect-content-database-source.ts +68 -2
- package/corpus/templates/content/actions/disconnect-local-folder-source.ts +33 -2
- package/corpus/templates/content/actions/execute-builder-source-execution.ts +900 -140
- package/corpus/templates/content/actions/get-document.ts +2 -0
- package/corpus/templates/content/actions/permanently-delete-document.ts +6 -0
- package/corpus/templates/content/actions/prepare-builder-source-execution.ts +155 -13
- package/corpus/templates/content/actions/prepare-builder-source-review.ts +299 -73
- package/corpus/templates/content/actions/set-live-body-switch.ts +45 -0
- package/corpus/templates/content/actions/update-document.ts +139 -20
- package/corpus/templates/content/actions/validate-builder-source-execution.ts +135 -26
- package/corpus/templates/content/app/components/editor/CommentsSidebar.tsx +119 -30
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +56 -13
- package/corpus/templates/content/app/components/editor/SuggestionText.tsx +18 -3
- package/corpus/templates/content/app/components/editor/VisualEditor.tsx +89 -22
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +23 -0
- package/corpus/templates/content/app/components/editor/database/GitHubSourceStatusLine.tsx +27 -2
- package/corpus/templates/content/app/components/editor/database/ListItemLayout.tsx +32 -8
- package/corpus/templates/content/app/components/editor/database/ListItemRow.tsx +27 -2
- package/corpus/templates/content/app/components/editor/database-sources/BuilderSourceReviewDialog.tsx +1 -0
- package/corpus/templates/content/app/components/editor/extensions/SuggestionHighlight.ts +383 -34
- package/corpus/templates/content/app/components/editor/suggestions/draft-session.ts +31 -0
- package/corpus/templates/content/app/components/editor/suggestions/presentation-rebase.ts +82 -4
- package/corpus/templates/content/app/global.css +31 -0
- package/corpus/templates/content/app/hooks/use-content-database.ts +40 -2
- package/corpus/templates/content/app/hooks/use-documents.ts +14 -0
- package/corpus/templates/content/app/i18n-data.ts +1 -0
- package/corpus/templates/content/changelog/2026-10-16-accepting-a-suggestion-on-a-page-that-changed-since-it-was-s.md +6 -0
- package/corpus/templates/content/changelog/2026-10-17-builder-content-avoids-retrying-a-write-when-builder-accepte.md +6 -0
- package/corpus/templates/content/changelog/2026-10-18-link-a-new-collection-to-a-github-folder-from-its-sources.md +1 -1
- package/corpus/templates/content/changelog/2026-10-20-builder-content-sync-keeps-read-and-write-status-accurate-wh.md +6 -0
- package/corpus/templates/content/changelog/2026-10-20-suggested-edits-that-add-remove-split-or-merge-paragraphs-li.md +6 -0
- package/corpus/templates/content/changelog/2026-10-21-list-rows-can-show-each-pages-first-lines.md +6 -0
- package/corpus/templates/content/changelog/2026-10-22-a-collection-linked-to-a-big-github-folder-fills-faster-and-p.md +6 -0
- package/corpus/templates/content/docs/product/capabilities/content.source.adapters.md +29 -0
- package/corpus/templates/content/parity/matrix.md +42 -42
- package/corpus/templates/content/parity/matrix.ts +3 -1
- package/corpus/templates/content/server/db/schema.ts +20 -0
- package/corpus/templates/content/server/lib/content-trash-purge.ts +15 -1
- package/corpus/templates/content/server/lib/live-body-writer.ts +138 -26
- package/corpus/templates/content/server/live-copy/binding.ts +31 -0
- package/corpus/templates/content/server/live-copy/bounded-task.d.ts +6 -0
- package/corpus/templates/content/server/live-copy/exec-db.ts +1 -0
- package/corpus/templates/content/server/live-copy/holds.ts +59 -0
- package/corpus/templates/content/server/live-copy/persist.ts +656 -0
- package/corpus/templates/content/server/live-copy/switch.ts +136 -0
- package/corpus/templates/content/server/live-copy/task-names.ts +4 -0
- package/corpus/templates/content/server/live-copy/tasks.ts +183 -0
- package/corpus/templates/content/server/live-copy/write-cause.ts +85 -0
- package/corpus/templates/content/server/live-copy/write.ts +85 -0
- package/corpus/templates/content/server/plugins/builder-private-payload-cleanup.ts +22 -0
- package/corpus/templates/content/server/plugins/collab.ts +3 -0
- package/corpus/templates/content/server/plugins/db.ts +28 -0
- package/corpus/templates/content/shared/api.ts +30 -1
- package/corpus/templates/content/shared/database-item-layout.ts +54 -22
- package/corpus/templates/content/shared/document-intent-merge.ts +16 -9
- package/corpus/templates/content/shared/og-description.ts +258 -36
- package/corpus/templates/content/shared/suggestion-diff.ts +23 -6
- package/corpus/templates/content/shared/suggestion-formatting.ts +44 -14
- package/corpus/templates/content/shared/suggestion-text.ts +20 -5
- package/corpus/templates/design/.agents/skills/design-templates/SKILL.md +5 -1
- package/corpus/templates/design/.generated/bridge/shader-runtime.generated.ts +25 -4
- package/corpus/templates/design/CHANGELOG.md +5 -0
- package/corpus/templates/design/actions/get-getting-started.ts +53 -0
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +10 -0
- package/corpus/templates/design/app/components/design/bridge/native-shader-runtime.bridge.ts +132 -79
- package/corpus/templates/design/app/components/design/bridge/native-source-provider.ts +268 -130
- package/corpus/templates/design/app/components/design/bridge/native-worker-scene-host.ts +122 -42
- package/corpus/templates/design/app/components/design/bridge/shader-runtime.bridge.ts +41 -4
- package/corpus/templates/design/app/components/design/design-canvas/text-content-change-result.ts +46 -12
- package/corpus/templates/design/app/components/home/DesignHomeLibrary.tsx +15 -8
- package/corpus/templates/design/app/components/layout/GettingStarted.tsx +50 -0
- package/corpus/templates/design/app/components/layout/Sidebar.tsx +3 -0
- package/corpus/templates/design/app/components/templates/DesignTemplateGrid.tsx +163 -0
- package/corpus/templates/design/app/components/templates/DesignTemplateLibrary.tsx +65 -78
- package/corpus/templates/design/app/components/templates/TemplatePreview.tsx +6 -2
- package/corpus/templates/design/app/global.css +6 -0
- package/corpus/templates/design/app/i18n/en-US.ts +17 -0
- package/corpus/templates/design/app/lib/canvas-load-outcome.ts +147 -0
- package/corpus/templates/design/app/lib/canvas-outcome-tracking.ts +531 -0
- package/corpus/templates/design/app/lib/canvas-style-kind.ts +50 -0
- package/corpus/templates/design/app/pages/DesignSystems.tsx +1 -1
- package/corpus/templates/design/app/pages/Index.tsx +28 -67
- package/corpus/templates/design/app/pages/design-editor/commands/commit-visual-styles.ts +86 -29
- package/corpus/templates/design/app/pages/design-editor/commands/save-file-content.ts +38 -3
- package/corpus/templates/design/app/pages/design-editor/domains/use-editor-core.ts +9 -0
- package/corpus/templates/design/app/pages/design-editor/domains/use-editor-files-and-saving.ts +2 -0
- package/corpus/templates/design/changelog/2026-10-16-design-pages-use-localized-visibility-badges-and-a-shared-ow.md +6 -0
- package/corpus/templates/design/changelog/2026-10-21-a-getting-started-checklist-at-the-bottom-of-the-sidebar-tra.md +6 -0
- package/corpus/templates/design/changelog/2026-10-21-templates-now-open-a-read-only-preview-from-the-card-and-the.md +6 -0
- package/corpus/templates/design/changelog/2026-10-22-collapsed-shader-layers-no-longer-interrupt-other-shaders-on.md +6 -0
- package/corpus/templates/design/changelog/2026-10-22-shader-fills-keep-rendering-when-other-screen-content-cannot.md +6 -0
- package/corpus/templates/design/scripts/design-export-validation/export-baseline.json +24 -0
- package/corpus/templates/design/server/lib/getting-started.ts +267 -0
- package/corpus/templates/design/server/plugins/getting-started.ts +5 -0
- package/corpus/templates/design/shared/design-template-presets/analytics-dashboard.ts +127 -0
- package/corpus/templates/design/shared/design-template-presets/design-studio.ts +98 -0
- package/corpus/templates/design/shared/design-template-presets/messaging-app.ts +101 -0
- package/corpus/templates/design/shared/design-template-presets/project-tasks.ts +120 -0
- package/corpus/templates/design/shared/design-template-presets.ts +8 -0
- package/corpus/templates/dispatch/CHANGELOG.md +4 -0
- package/corpus/templates/dispatch/app/i18n-data.ts +75 -0
- package/corpus/templates/dispatch/changelog/2026-10-21-owned-and-shared-chats-appear-in-separate-history-sections.md +6 -0
- package/corpus/templates/factory/app/components/layout/Sidebar.tsx +34 -100
- package/corpus/templates/forms/app/pages/FormsListPage.tsx +1 -1
- package/corpus/templates/forms/server/plugins/agent-chat.ts +1 -1
- package/corpus/templates/plan/CHANGELOG.md +6 -0
- package/corpus/templates/plan/app/components/layout/Sidebar.tsx +34 -109
- package/corpus/templates/plan/changelog/2026-10-16-owned-and-shared-chats-appear-in-separate-history-sections.md +6 -0
- package/corpus/templates/slides/CHANGELOG.md +7 -0
- package/corpus/templates/slides/actions/delete-deck.ts +7 -0
- package/corpus/templates/slides/app/components/deck/DeckCard.tsx +1 -1
- package/corpus/templates/slides/app/components/design-system/DesignSystemCard.tsx +1 -1
- package/corpus/templates/slides/app/pages/Index.tsx +7 -2
- package/corpus/templates/slides/changelog/2026-10-16-deck-lists-use-the-shared-ownership-filter-and-visibility-ba.md +6 -0
- package/corpus/templates/slides/changelog/2026-10-21-connected-deck-deletion-requires-approval.md +6 -0
- package/corpus/templates/slides/scripts/edit-fidelity/client-action.ts +61 -0
- package/corpus/templates/slides/scripts/edit-fidelity/run.ts +10 -0
- package/package.json +1 -1
- package/corpus/templates/chat/app/lib/sidebar-thread-state.ts +0 -29
- package/corpus/templates/slides/app/components/deck/DeckFilterMenu.tsx +0 -83
|
@@ -45,7 +45,11 @@ import {
|
|
|
45
45
|
findReplaceSuggestionSummary,
|
|
46
46
|
suggestionDiffParts,
|
|
47
47
|
} from "@shared/suggestion-diff";
|
|
48
|
-
import
|
|
48
|
+
import {
|
|
49
|
+
suggestionAnchorText,
|
|
50
|
+
suggestionTextForDisplay,
|
|
51
|
+
type SuggestionPresentationContext,
|
|
52
|
+
} from "@shared/suggestion-text";
|
|
49
53
|
|
|
50
54
|
import {
|
|
51
55
|
DropdownMenu,
|
|
@@ -197,6 +201,20 @@ function isAmbiguousCommentCreateError(error: unknown) {
|
|
|
197
201
|
);
|
|
198
202
|
}
|
|
199
203
|
|
|
204
|
+
/** The session's name for the signed-in person's own entries, if it has one. */
|
|
205
|
+
function ownAuthorName(
|
|
206
|
+
authorEmail: string | null | undefined,
|
|
207
|
+
currentUserEmail?: string,
|
|
208
|
+
currentUserName?: string,
|
|
209
|
+
) {
|
|
210
|
+
const name = currentUserName?.trim();
|
|
211
|
+
return name &&
|
|
212
|
+
authorEmail &&
|
|
213
|
+
authorEmail.trim().toLowerCase() === currentUserEmail?.trim().toLowerCase()
|
|
214
|
+
? name
|
|
215
|
+
: null;
|
|
216
|
+
}
|
|
217
|
+
|
|
200
218
|
function cssEscape(value: string) {
|
|
201
219
|
return globalThis.CSS?.escape
|
|
202
220
|
? globalThis.CSS.escape(value)
|
|
@@ -757,6 +775,8 @@ interface CommentsSidebarOptions {
|
|
|
757
775
|
onSelectedThreadChange?: (id: string | null) => void;
|
|
758
776
|
onHoveredThreadChange?: (id: string | null) => void;
|
|
759
777
|
currentUserEmail?: string;
|
|
778
|
+
/** The signed-in person's display name, for their own suggestions. */
|
|
779
|
+
currentUserName?: string;
|
|
760
780
|
currentUserOrgId?: string;
|
|
761
781
|
canComment?: boolean;
|
|
762
782
|
canResolve?: boolean;
|
|
@@ -841,6 +861,7 @@ export function CommentsSidebar({
|
|
|
841
861
|
onSelectedThreadChange,
|
|
842
862
|
onHoveredThreadChange,
|
|
843
863
|
currentUserEmail,
|
|
864
|
+
currentUserName,
|
|
844
865
|
currentUserOrgId,
|
|
845
866
|
canComment = true,
|
|
846
867
|
canResolve = false,
|
|
@@ -1114,34 +1135,32 @@ export function CommentsSidebar({
|
|
|
1114
1135
|
}, [canComment, presentation, selectedThreadId, selectedThreadIsOpen]);
|
|
1115
1136
|
const historyAuthors = useMemo(() => {
|
|
1116
1137
|
const authors = new Map<string, string>();
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
}
|
|
1125
|
-
for (const suggestion of
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
suggestion.authorEmail,
|
|
1129
|
-
suggestion.authorEmail.split("@")[0],
|
|
1130
|
-
);
|
|
1131
|
-
}
|
|
1132
|
-
}
|
|
1138
|
+
// A name from any record outranks the email's local part from another.
|
|
1139
|
+
const addAuthor = (email: string | null, name?: string | null) => {
|
|
1140
|
+
if (!email) return;
|
|
1141
|
+
const known =
|
|
1142
|
+
name?.trim() || ownAuthorName(email, currentUserEmail, currentUserName);
|
|
1143
|
+
if (known) authors.set(email, known);
|
|
1144
|
+
else if (!authors.has(email)) authors.set(email, email.split("@")[0]);
|
|
1145
|
+
};
|
|
1146
|
+
for (const suggestion of suggestions) addAuthor(suggestion.authorEmail);
|
|
1147
|
+
for (const suggestion of draftSuggestions)
|
|
1148
|
+
addAuthor(suggestion.authorEmail);
|
|
1133
1149
|
for (const thread of threads) {
|
|
1134
1150
|
for (const comment of thread.comments) {
|
|
1135
|
-
|
|
1136
|
-
comment.author_email,
|
|
1137
|
-
comment.author_name ?? comment.author_email.split("@")[0],
|
|
1138
|
-
);
|
|
1151
|
+
addAuthor(comment.author_email, comment.author_name);
|
|
1139
1152
|
}
|
|
1140
1153
|
}
|
|
1141
1154
|
return [...authors.entries()].sort((left, right) =>
|
|
1142
1155
|
left[1].localeCompare(right[1]),
|
|
1143
1156
|
);
|
|
1144
|
-
}, [
|
|
1157
|
+
}, [
|
|
1158
|
+
currentUserEmail,
|
|
1159
|
+
currentUserName,
|
|
1160
|
+
draftSuggestions,
|
|
1161
|
+
suggestions,
|
|
1162
|
+
threads,
|
|
1163
|
+
]);
|
|
1145
1164
|
// Suggestions edit the page body, so they sit in its tab.
|
|
1146
1165
|
const historyBodyTabShown =
|
|
1147
1166
|
activeHistoryTab === null || activeHistoryTab === commentTabKey(null);
|
|
@@ -1465,6 +1484,22 @@ export function CommentsSidebar({
|
|
|
1465
1484
|
null,
|
|
1466
1485
|
);
|
|
1467
1486
|
const pendingCardRef = useRef<HTMLDivElement>(null);
|
|
1487
|
+
// An inline group remounts under its next card once its leading edit is
|
|
1488
|
+
// decided, so whether it is open has to outlive the group.
|
|
1489
|
+
const [expandedProposalIds, setExpandedProposalIds] = useState<
|
|
1490
|
+
ReadonlySet<string>
|
|
1491
|
+
>(() => new Set());
|
|
1492
|
+
const setProposalExpanded = useCallback(
|
|
1493
|
+
(proposalId: string, expanded: boolean) =>
|
|
1494
|
+
setExpandedProposalIds((current) => {
|
|
1495
|
+
if (current.has(proposalId) === expanded) return current;
|
|
1496
|
+
const next = new Set(current);
|
|
1497
|
+
if (expanded) next.add(proposalId);
|
|
1498
|
+
else next.delete(proposalId);
|
|
1499
|
+
return next;
|
|
1500
|
+
}),
|
|
1501
|
+
[],
|
|
1502
|
+
);
|
|
1468
1503
|
const inlineProposalLeaders = useMemo(() => {
|
|
1469
1504
|
const leaders = new Map<string, string>();
|
|
1470
1505
|
for (const proposalId of inlineProposalMembers.keys()) {
|
|
@@ -2097,6 +2132,7 @@ export function CommentsSidebar({
|
|
|
2097
2132
|
surface={surface}
|
|
2098
2133
|
onClose={onClose}
|
|
2099
2134
|
currentUserEmail={currentUserEmail}
|
|
2135
|
+
currentUserName={currentUserName}
|
|
2100
2136
|
compact={compact}
|
|
2101
2137
|
replyDrafts={replyDrafts}
|
|
2102
2138
|
key={suggestion.id}
|
|
@@ -2144,6 +2180,8 @@ export function CommentsSidebar({
|
|
|
2144
2180
|
(member) =>
|
|
2145
2181
|
member.id === activeSuggestionId || member.id === focusSuggestionId,
|
|
2146
2182
|
)}
|
|
2183
|
+
expanded={expandedProposalIds.has(proposalId)}
|
|
2184
|
+
onExpandedChange={(expanded) => setProposalExpanded(proposalId, expanded)}
|
|
2147
2185
|
deciding={members.some(suggestionBusy)}
|
|
2148
2186
|
canDecide={canDecideSuggestions && !!onDecideSuggestionProposal}
|
|
2149
2187
|
onDecide={(decision) =>
|
|
@@ -2176,6 +2214,7 @@ export function CommentsSidebar({
|
|
|
2176
2214
|
surface={surface}
|
|
2177
2215
|
onClose={onClose}
|
|
2178
2216
|
currentUserEmail={currentUserEmail}
|
|
2217
|
+
currentUserName={currentUserName}
|
|
2179
2218
|
key={suggestion.id}
|
|
2180
2219
|
marginTop={marginTop}
|
|
2181
2220
|
onHeightChange={handleThreadCardHeightChange}
|
|
@@ -2667,6 +2706,8 @@ function ProposalGroup({
|
|
|
2667
2706
|
totalCount,
|
|
2668
2707
|
members,
|
|
2669
2708
|
active,
|
|
2709
|
+
expanded,
|
|
2710
|
+
onExpandedChange,
|
|
2670
2711
|
deciding,
|
|
2671
2712
|
canDecide,
|
|
2672
2713
|
onDecide,
|
|
@@ -2679,6 +2720,8 @@ function ProposalGroup({
|
|
|
2679
2720
|
totalCount: number;
|
|
2680
2721
|
members: ResourceSuggestion[];
|
|
2681
2722
|
active: boolean;
|
|
2723
|
+
expanded: boolean;
|
|
2724
|
+
onExpandedChange: (expanded: boolean) => void;
|
|
2682
2725
|
deciding: boolean;
|
|
2683
2726
|
canDecide: boolean;
|
|
2684
2727
|
onDecide: (decision: SuggestionDecision) => void;
|
|
@@ -2686,11 +2729,10 @@ function ProposalGroup({
|
|
|
2686
2729
|
children: ReactNode;
|
|
2687
2730
|
t: ReturnType<typeof useT>;
|
|
2688
2731
|
}) {
|
|
2689
|
-
const [expanded, setExpanded] = useState(active);
|
|
2690
2732
|
const groupRef = useRef<HTMLDivElement>(null);
|
|
2691
2733
|
const detailsId = useId();
|
|
2692
|
-
|
|
2693
|
-
if (active)
|
|
2734
|
+
useLayoutEffect(() => {
|
|
2735
|
+
if (active) onExpandedChange(true);
|
|
2694
2736
|
}, [active]);
|
|
2695
2737
|
useLayoutEffect(() => {
|
|
2696
2738
|
const element = groupRef.current;
|
|
@@ -2714,7 +2756,7 @@ function ProposalGroup({
|
|
|
2714
2756
|
type="button"
|
|
2715
2757
|
aria-expanded={expanded}
|
|
2716
2758
|
aria-controls={detailsId}
|
|
2717
|
-
onClick={() =>
|
|
2759
|
+
onClick={() => onExpandedChange(!expanded)}
|
|
2718
2760
|
className="flex w-full min-w-0 items-start gap-2 px-3 py-2 text-start text-xs hover:bg-accent/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring"
|
|
2719
2761
|
>
|
|
2720
2762
|
<IconChevronDown
|
|
@@ -2733,7 +2775,9 @@ function ProposalGroup({
|
|
|
2733
2775
|
{summary}
|
|
2734
2776
|
</span>
|
|
2735
2777
|
<span className="shrink-0 text-muted-foreground">
|
|
2736
|
-
{t("comments.proposalEditCount", {
|
|
2778
|
+
{t("comments.proposalEditCount", {
|
|
2779
|
+
count: pending.length || totalCount,
|
|
2780
|
+
})}
|
|
2737
2781
|
</span>
|
|
2738
2782
|
</button>
|
|
2739
2783
|
{expanded ? (
|
|
@@ -2796,6 +2840,19 @@ export function agentSuggestionReason(
|
|
|
2796
2840
|
return reason;
|
|
2797
2841
|
}
|
|
2798
2842
|
|
|
2843
|
+
/**
|
|
2844
|
+
* A change that adds or removes a line break, or swaps one run of whitespace
|
|
2845
|
+
* for another, doesn't say which lines it joins or splits, and a break alone
|
|
2846
|
+
* reads as a lone "↵", so what it replaces is shown too.
|
|
2847
|
+
*/
|
|
2848
|
+
function changesLayout(removed: string, inserted: string) {
|
|
2849
|
+
const before = suggestionAnchorText(removed);
|
|
2850
|
+
const after = suggestionAnchorText(inserted);
|
|
2851
|
+
return (
|
|
2852
|
+
/\n/.test(before + after) || (/^\s+$/.test(before) && /^\s+$/.test(after))
|
|
2853
|
+
);
|
|
2854
|
+
}
|
|
2855
|
+
|
|
2799
2856
|
function SuggestionOperationSummary({
|
|
2800
2857
|
operations,
|
|
2801
2858
|
expanded,
|
|
@@ -2851,6 +2908,18 @@ function SuggestionOperationSummary({
|
|
|
2851
2908
|
: null;
|
|
2852
2909
|
const hasSharedContext = diff?.some((part) => part.type === "equal");
|
|
2853
2910
|
const richText = /[<>*_`\[\]]/.test(previousText + nextText);
|
|
2911
|
+
const changed = (type: "delete" | "insert", whole: string) =>
|
|
2912
|
+
diff && hasSharedContext
|
|
2913
|
+
? diff
|
|
2914
|
+
.flatMap((part) => (part.type === type ? [part.text] : []))
|
|
2915
|
+
.join("")
|
|
2916
|
+
: whole;
|
|
2917
|
+
const showReplaced =
|
|
2918
|
+
expanded ||
|
|
2919
|
+
changesLayout(
|
|
2920
|
+
changed("delete", previousText),
|
|
2921
|
+
changed("insert", nextText),
|
|
2922
|
+
);
|
|
2854
2923
|
const renderDiff = (side: "before" | "after") => {
|
|
2855
2924
|
let offset = 0;
|
|
2856
2925
|
const presentation =
|
|
@@ -2879,7 +2948,9 @@ function SuggestionOperationSummary({
|
|
|
2879
2948
|
to: from + part.text.length,
|
|
2880
2949
|
},
|
|
2881
2950
|
)
|
|
2882
|
-
: part.text
|
|
2951
|
+
: part.type !== "equal" && /^\s+$/.test(part.text)
|
|
2952
|
+
? suggestionTextForDisplay(part.text)
|
|
2953
|
+
: part.text}
|
|
2883
2954
|
</span>
|
|
2884
2955
|
);
|
|
2885
2956
|
});
|
|
@@ -2897,7 +2968,7 @@ function SuggestionOperationSummary({
|
|
|
2897
2968
|
: renderSuggestionText(nextText, nextPresentation)}
|
|
2898
2969
|
{"”"}
|
|
2899
2970
|
</div>
|
|
2900
|
-
{
|
|
2971
|
+
{showReplaced ? (
|
|
2901
2972
|
<div className="text-muted-foreground">
|
|
2902
2973
|
{t("comments.suggestionReplace")}: {"“"}
|
|
2903
2974
|
{diff && hasSharedContext
|
|
@@ -2947,11 +3018,13 @@ function DraftSuggestionThreadView({
|
|
|
2947
3018
|
surface = "rail",
|
|
2948
3019
|
onClose,
|
|
2949
3020
|
currentUserEmail,
|
|
3021
|
+
currentUserName,
|
|
2950
3022
|
t,
|
|
2951
3023
|
}: {
|
|
2952
3024
|
surface?: CommentSurface;
|
|
2953
3025
|
onClose?: () => void;
|
|
2954
3026
|
currentUserEmail?: string;
|
|
3027
|
+
currentUserName?: string;
|
|
2955
3028
|
marginTop?: number;
|
|
2956
3029
|
onHeightChange: (threadId: string, height: number) => void;
|
|
2957
3030
|
suggestion: DraftSuggestion;
|
|
@@ -2984,7 +3057,11 @@ function DraftSuggestionThreadView({
|
|
|
2984
3057
|
{
|
|
2985
3058
|
id: suggestion.id,
|
|
2986
3059
|
author_email: suggestion.authorEmail ?? "",
|
|
2987
|
-
author_name:
|
|
3060
|
+
author_name: ownAuthorName(
|
|
3061
|
+
suggestion.authorEmail,
|
|
3062
|
+
currentUserEmail,
|
|
3063
|
+
currentUserName,
|
|
3064
|
+
),
|
|
2988
3065
|
created_at: suggestion.createdAt,
|
|
2989
3066
|
content: "",
|
|
2990
3067
|
mentions: [],
|
|
@@ -3102,11 +3179,13 @@ function SuggestionThreadView({
|
|
|
3102
3179
|
surface = "rail",
|
|
3103
3180
|
onClose,
|
|
3104
3181
|
currentUserEmail,
|
|
3182
|
+
currentUserName,
|
|
3105
3183
|
t,
|
|
3106
3184
|
}: {
|
|
3107
3185
|
surface?: CommentSurface;
|
|
3108
3186
|
onClose?: () => void;
|
|
3109
3187
|
currentUserEmail?: string;
|
|
3188
|
+
currentUserName?: string;
|
|
3110
3189
|
compact: boolean;
|
|
3111
3190
|
replyDrafts: ReturnType<typeof useCommentReplyDrafts>;
|
|
3112
3191
|
marginTop?: number;
|
|
@@ -3182,6 +3261,11 @@ function SuggestionThreadView({
|
|
|
3182
3261
|
author_email: suggestion.authorEmail ?? "",
|
|
3183
3262
|
author_name:
|
|
3184
3263
|
root?.authorName ??
|
|
3264
|
+
ownAuthorName(
|
|
3265
|
+
suggestion.authorEmail,
|
|
3266
|
+
currentUserEmail,
|
|
3267
|
+
currentUserName,
|
|
3268
|
+
) ??
|
|
3185
3269
|
(suggestion.authorEmail ? null : suggestion.actorKind),
|
|
3186
3270
|
created_at: suggestion.createdAt,
|
|
3187
3271
|
content: "",
|
|
@@ -3192,6 +3276,11 @@ function SuggestionThreadView({
|
|
|
3192
3276
|
author_email: comment.authorEmail ?? "",
|
|
3193
3277
|
author_name:
|
|
3194
3278
|
comment.authorName ??
|
|
3279
|
+
ownAuthorName(
|
|
3280
|
+
comment.authorEmail,
|
|
3281
|
+
currentUserEmail,
|
|
3282
|
+
currentUserName,
|
|
3283
|
+
) ??
|
|
3195
3284
|
(comment.authorEmail ? null : comment.createdBy),
|
|
3196
3285
|
created_at: comment.createdAt,
|
|
3197
3286
|
content: comment.body,
|
|
@@ -327,6 +327,8 @@ import {
|
|
|
327
327
|
planSuggestionDraftPersistence,
|
|
328
328
|
saveUnlessSuggestionChanged,
|
|
329
329
|
suggestionAmendmentIdempotencyKey,
|
|
330
|
+
suggestionProposalCreationIdempotencyKey,
|
|
331
|
+
forgetSuggestionProposalCreationIdempotencyKey,
|
|
330
332
|
type DraftSuggestion,
|
|
331
333
|
type SuggestionDraftSession,
|
|
332
334
|
type SuggestionPersistenceEntry,
|
|
@@ -336,11 +338,13 @@ import {
|
|
|
336
338
|
import { suggestedEditorIsolation } from "./suggestions/editor-isolation";
|
|
337
339
|
import { useRememberedEditorMode } from "./suggestions/editor-mode-memory";
|
|
338
340
|
import {
|
|
341
|
+
createDraftSuggestionPresentationTransition,
|
|
339
342
|
createObservedSuggestionPresentationTransition,
|
|
340
343
|
hydrateSuggestionPresentationTransitions,
|
|
341
344
|
preciseSuggestionPresentationOperations,
|
|
342
345
|
retainCommittedSuggestionPresentationTransitions,
|
|
343
346
|
resolveSuggestionPresentationRange,
|
|
347
|
+
resolveSuggestionPresentationRangeInDraft,
|
|
344
348
|
suggestionPresentationTransitionKey,
|
|
345
349
|
type SuggestionPresentationTransition,
|
|
346
350
|
type SuggestionPresentationTransitions,
|
|
@@ -635,6 +639,7 @@ export function suggestionPresentation(
|
|
|
635
639
|
currentMarkdown: string,
|
|
636
640
|
transition?: SuggestionPresentationTransition,
|
|
637
641
|
observedTransition?: SuggestionPresentationTransition,
|
|
642
|
+
draftTransition?: SuggestionPresentationTransition,
|
|
638
643
|
): VisualEditorSuggestion | null {
|
|
639
644
|
if (suggestion.status !== "pending") return null;
|
|
640
645
|
const operation = suggestion.operations[0];
|
|
@@ -671,12 +676,22 @@ export function suggestionPresentation(
|
|
|
671
676
|
) {
|
|
672
677
|
return null;
|
|
673
678
|
}
|
|
674
|
-
const
|
|
679
|
+
const canonicalRange = resolveSuggestionPresentationRange(
|
|
675
680
|
currentMarkdown,
|
|
676
681
|
operation,
|
|
677
682
|
transition,
|
|
678
683
|
observedTransition,
|
|
679
684
|
);
|
|
685
|
+
const range =
|
|
686
|
+
canonicalRange ??
|
|
687
|
+
(draftTransition?.after === currentMarkdown
|
|
688
|
+
? resolveSuggestionPresentationRangeInDraft(
|
|
689
|
+
draftTransition,
|
|
690
|
+
operation,
|
|
691
|
+
transition,
|
|
692
|
+
observedTransition,
|
|
693
|
+
)
|
|
694
|
+
: null);
|
|
680
695
|
if (!range) return null;
|
|
681
696
|
const editorMarkdown = canonicalizeNfm(currentMarkdown);
|
|
682
697
|
const currentText = currentMarkdown.slice(range.from, range.to);
|
|
@@ -694,7 +709,9 @@ export function suggestionPresentation(
|
|
|
694
709
|
return {
|
|
695
710
|
id: suggestion.id,
|
|
696
711
|
kind: operation.kind as VisualEditorSuggestion["kind"],
|
|
697
|
-
|
|
712
|
+
// The editor re-resolves a canonical operation on its own text, where only
|
|
713
|
+
// the draft carries this one, so the anchor places it instead.
|
|
714
|
+
canonicalOperation: canonicalRange ? operation : undefined,
|
|
698
715
|
canonicalTransition: transition,
|
|
699
716
|
observedTransition,
|
|
700
717
|
beforeText: before.changedText,
|
|
@@ -726,6 +743,7 @@ export function suggestionPresentations(
|
|
|
726
743
|
currentMarkdown: string,
|
|
727
744
|
transition?: SuggestionPresentationTransition,
|
|
728
745
|
observedTransition?: SuggestionPresentationTransition,
|
|
746
|
+
draftTransition?: SuggestionPresentationTransition,
|
|
729
747
|
): VisualEditorSuggestion[] {
|
|
730
748
|
// Decided edits have no presentation; skip their full-Page diff.
|
|
731
749
|
if (suggestion.status !== "pending") return [];
|
|
@@ -734,6 +752,7 @@ export function suggestionPresentations(
|
|
|
734
752
|
currentMarkdown,
|
|
735
753
|
transition,
|
|
736
754
|
observedTransition,
|
|
755
|
+
draftTransition,
|
|
737
756
|
);
|
|
738
757
|
if (suggestion.operations.length !== 1) return original ? [original] : [];
|
|
739
758
|
const saved = suggestion.operations[0]!;
|
|
@@ -746,6 +765,7 @@ export function suggestionPresentations(
|
|
|
746
765
|
currentMarkdown,
|
|
747
766
|
transition,
|
|
748
767
|
observedTransition,
|
|
768
|
+
draftTransition,
|
|
749
769
|
),
|
|
750
770
|
);
|
|
751
771
|
return precise.every((presentation) => presentation !== null)
|
|
@@ -764,6 +784,13 @@ export function documentEditorSuggestionPresentations(args: {
|
|
|
764
784
|
transitions: SuggestionPresentationTransitions;
|
|
765
785
|
observedTransition?: SuggestionPresentationTransition;
|
|
766
786
|
}): VisualEditorSuggestion[] {
|
|
787
|
+
const draftTransition =
|
|
788
|
+
args.savedSuggestions.length > 0
|
|
789
|
+
? (createDraftSuggestionPresentationTransition(
|
|
790
|
+
args.drafts,
|
|
791
|
+
args.currentMarkdown,
|
|
792
|
+
) ?? undefined)
|
|
793
|
+
: undefined;
|
|
767
794
|
const savedPresentations: VisualEditorSuggestion[] = [];
|
|
768
795
|
for (const suggestion of args.savedSuggestions) {
|
|
769
796
|
if (suggestion.id === args.editingSuggestionId) continue;
|
|
@@ -773,6 +800,7 @@ export function documentEditorSuggestionPresentations(args: {
|
|
|
773
800
|
args.currentMarkdown,
|
|
774
801
|
args.transitions.get(suggestionPresentationTransitionKey(suggestion)),
|
|
775
802
|
args.observedTransition,
|
|
803
|
+
draftTransition,
|
|
776
804
|
),
|
|
777
805
|
);
|
|
778
806
|
}
|
|
@@ -5906,6 +5934,10 @@ function PageEditorSessionBody({
|
|
|
5906
5934
|
unresolved.request.idempotencyKey,
|
|
5907
5935
|
unresolved.baseId,
|
|
5908
5936
|
);
|
|
5937
|
+
forgetSuggestionProposalCreationIdempotencyKey(
|
|
5938
|
+
suggestionProposalCreationKeysRef.current,
|
|
5939
|
+
unresolved.request,
|
|
5940
|
+
);
|
|
5909
5941
|
unresolvedProposalCreationRef.current = null;
|
|
5910
5942
|
}
|
|
5911
5943
|
const operations = suggestionDraftOperations(base, draft);
|
|
@@ -6086,29 +6118,28 @@ function PageEditorSessionBody({
|
|
|
6086
6118
|
if (create.length > 0) {
|
|
6087
6119
|
const pending = create.map(({ operation }) => operation);
|
|
6088
6120
|
const pendingKeys = create.map(({ key }) => key);
|
|
6089
|
-
const requestKey = JSON.stringify([base.id, pendingKeys]);
|
|
6090
|
-
const idempotencyKey =
|
|
6091
|
-
suggestionProposalCreationKeysRef.current.get(requestKey) ??
|
|
6092
|
-
globalThis.crypto.randomUUID();
|
|
6093
|
-
suggestionProposalCreationKeysRef.current.set(
|
|
6094
|
-
requestKey,
|
|
6095
|
-
idempotencyKey,
|
|
6096
|
-
);
|
|
6097
6121
|
const existingProposal = suggestionProposalsRef.current.get(base.id);
|
|
6098
6122
|
const proposalSummary =
|
|
6099
6123
|
existingProposal?.summary ?? t("editor.toolbar.suggestEdits");
|
|
6100
|
-
const
|
|
6124
|
+
const fields = {
|
|
6101
6125
|
resourceType: "document",
|
|
6102
6126
|
resourceId: documentId,
|
|
6103
6127
|
adapterKind: "content.document-markdown",
|
|
6104
6128
|
baseRevision: base.baseRevision,
|
|
6105
6129
|
summary: proposalSummary,
|
|
6106
6130
|
proposalId: existingProposal?.id,
|
|
6107
|
-
idempotencyKey,
|
|
6108
6131
|
suggestions: pending.map((operation) => ({
|
|
6109
6132
|
summary: proposalSummary,
|
|
6110
6133
|
operations: [operation],
|
|
6111
6134
|
})),
|
|
6135
|
+
};
|
|
6136
|
+
const idempotencyKey = suggestionProposalCreationIdempotencyKey(
|
|
6137
|
+
suggestionProposalCreationKeysRef.current,
|
|
6138
|
+
fields,
|
|
6139
|
+
);
|
|
6140
|
+
const request = {
|
|
6141
|
+
...fields,
|
|
6142
|
+
idempotencyKey,
|
|
6112
6143
|
} satisfies Parameters<
|
|
6113
6144
|
typeof createSuggestionProposal.mutateAsync
|
|
6114
6145
|
>[0];
|
|
@@ -6120,6 +6151,10 @@ function PageEditorSessionBody({
|
|
|
6120
6151
|
};
|
|
6121
6152
|
const created = await createSuggestionProposal.mutateAsync(request);
|
|
6122
6153
|
recordCreated(created, pendingKeys, pending, idempotencyKey, base.id);
|
|
6154
|
+
forgetSuggestionProposalCreationIdempotencyKey(
|
|
6155
|
+
suggestionProposalCreationKeysRef.current,
|
|
6156
|
+
request,
|
|
6157
|
+
);
|
|
6123
6158
|
unresolvedProposalCreationRef.current = null;
|
|
6124
6159
|
pendingKeys.forEach((operationKey, index) => {
|
|
6125
6160
|
persisted.set(operationKey, created.suggestions[index]!);
|
|
@@ -6135,7 +6170,14 @@ function PageEditorSessionBody({
|
|
|
6135
6170
|
status < 500 &&
|
|
6136
6171
|
status !== 408 &&
|
|
6137
6172
|
status !== 429;
|
|
6138
|
-
|
|
6173
|
+
const refused = clientError && unresolvedProposalCreationRef.current;
|
|
6174
|
+
if (refused) {
|
|
6175
|
+
forgetSuggestionProposalCreationIdempotencyKey(
|
|
6176
|
+
suggestionProposalCreationKeysRef.current,
|
|
6177
|
+
refused.request,
|
|
6178
|
+
);
|
|
6179
|
+
unresolvedProposalCreationRef.current = null;
|
|
6180
|
+
}
|
|
6139
6181
|
adoptConfirmedSuggestions();
|
|
6140
6182
|
if (error instanceof SuggestionFormattingMappingError) {
|
|
6141
6183
|
// The draft keeps its persistent unsupported-formatting alert.
|
|
@@ -8651,6 +8693,7 @@ function PageEditorSessionBody({
|
|
|
8651
8693
|
onSelectedThreadChange={setSelectedThreadId}
|
|
8652
8694
|
onHoveredThreadChange={setHoveredThreadId}
|
|
8653
8695
|
currentUserEmail={session?.email}
|
|
8696
|
+
currentUserName={session?.name}
|
|
8654
8697
|
currentUserOrgId={session?.orgId}
|
|
8655
8698
|
canComment={canComment}
|
|
8656
8699
|
canResolve={canEdit}
|
|
@@ -9,8 +9,9 @@ import { Fragment, type ReactNode } from "react";
|
|
|
9
9
|
|
|
10
10
|
function renderNode(node: SuggestionPresentationNode, key: string): ReactNode {
|
|
11
11
|
if (node.type === "text") return <Fragment key={key}>{node.value}</Fragment>;
|
|
12
|
-
if (node.type === "indent")
|
|
12
|
+
if (node.type === "indent" || node.type === "block")
|
|
13
13
|
return <Fragment key={key}>{node.value}</Fragment>;
|
|
14
|
+
if (node.type === "lineBreak") return <br key={key} />;
|
|
14
15
|
const children = node.children.map((child, index) =>
|
|
15
16
|
renderNode(child, `${key}-${index}`),
|
|
16
17
|
);
|
|
@@ -42,16 +43,30 @@ export function SuggestionText({
|
|
|
42
43
|
context?: SuggestionPresentationContext;
|
|
43
44
|
}) {
|
|
44
45
|
const t = useT();
|
|
45
|
-
const
|
|
46
|
+
const presented = context
|
|
46
47
|
? suggestionTextPresentationForSource(content, context)
|
|
47
48
|
: suggestionTextPresentation(content);
|
|
48
|
-
if (!
|
|
49
|
+
if (!presented) {
|
|
49
50
|
return (
|
|
50
51
|
<span data-suggestion-text-unavailable="true">
|
|
51
52
|
{t("editor.sourceComponent.previewUnavailable")}
|
|
52
53
|
</span>
|
|
53
54
|
);
|
|
54
55
|
}
|
|
56
|
+
// A new paragraph's own line break would open or close the card with an
|
|
57
|
+
// empty line, so a break at either edge shows as its marker instead; the
|
|
58
|
+
// breaks between lines stay.
|
|
59
|
+
let start = 0;
|
|
60
|
+
let end = presented.length;
|
|
61
|
+
while (start < end && presented[start]!.type === "lineBreak") start += 1;
|
|
62
|
+
while (end > start && presented[end - 1]!.type === "lineBreak") end -= 1;
|
|
63
|
+
const marker = (count: number): SuggestionPresentationNode[] =>
|
|
64
|
+
count ? [{ type: "text", value: "↵".repeat(count) }] : [];
|
|
65
|
+
const nodes = [
|
|
66
|
+
...marker(start),
|
|
67
|
+
...presented.slice(start, end),
|
|
68
|
+
...marker(presented.length - end),
|
|
69
|
+
];
|
|
55
70
|
return (
|
|
56
71
|
<span className="whitespace-pre-wrap break-words">
|
|
57
72
|
{nodes.map((node, index) => renderNode(node, String(index)))}
|