@agent-native/core 0.187.0-nightly-20260924183037 → 0.187.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/CHANGELOG.md +11 -0
- package/corpus/templates/analytics/app/i18n/en-US.ts +1 -0
- package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +101 -11
- package/corpus/templates/analytics/changelog/2026-09-24-session-list-hides-0m-recordings-by-default.md +6 -0
- package/corpus/templates/assets/CHANGELOG.md +6 -0
- package/corpus/templates/calendar/CHANGELOG.md +6 -0
- package/corpus/templates/clips/CHANGELOG.md +10 -0
- package/corpus/templates/content/CHANGELOG.md +22 -0
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +559 -227
- package/corpus/templates/content/app/components/editor/VisualEditor.tsx +25 -1
- package/corpus/templates/design/CHANGELOG.md +36 -0
- package/corpus/templates/dispatch/CHANGELOG.md +7 -0
- package/corpus/templates/mail/CHANGELOG.md +17 -0
- package/corpus/templates/slides/CHANGELOG.md +7 -0
- package/dist/observability/routes.d.ts +8 -8
- package/dist/org/handlers.d.ts +1 -1
- package/dist/progress/routes.d.ts +1 -1
- package/package.json +4 -4
|
@@ -24,7 +24,10 @@ import {
|
|
|
24
24
|
useResourceSuggestions,
|
|
25
25
|
useUpdateResourceSuggestion,
|
|
26
26
|
} from "@agent-native/core/client/review";
|
|
27
|
-
import type {
|
|
27
|
+
import type {
|
|
28
|
+
ResourceSuggestion,
|
|
29
|
+
SuggestionDecision,
|
|
30
|
+
} from "@agent-native/core/review";
|
|
28
31
|
import { normalizeDocumentTitle } from "@agent-native/core/shared";
|
|
29
32
|
import type { Document, DocumentSyncStatus } from "@shared/api";
|
|
30
33
|
import { canonicalizeNfm } from "@shared/nfm";
|
|
@@ -325,6 +328,56 @@ export function suggestionAmendmentTargetIsResolved(
|
|
|
325
328
|
return !!suggestion && suggestion.status !== "pending";
|
|
326
329
|
}
|
|
327
330
|
|
|
331
|
+
export function suggestionDecisionPreviewContent(
|
|
332
|
+
suggestion: Pick<ResourceSuggestion, "operations">,
|
|
333
|
+
decision: SuggestionDecision,
|
|
334
|
+
canonicalContent: string,
|
|
335
|
+
optimistic = true,
|
|
336
|
+
) {
|
|
337
|
+
if (!optimistic || decision === "rejected") return canonicalContent;
|
|
338
|
+
const after = suggestion.operations[0]?.after as {
|
|
339
|
+
markdown?: unknown;
|
|
340
|
+
} | null;
|
|
341
|
+
return typeof after?.markdown === "string"
|
|
342
|
+
? after.markdown
|
|
343
|
+
: canonicalContent;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
export function materializedSuggestionForDraft(
|
|
347
|
+
persisted: ReadonlyMap<string, ResourceSuggestion>,
|
|
348
|
+
draft: Pick<DraftSuggestion, "operations">,
|
|
349
|
+
) {
|
|
350
|
+
const exact = persisted.get(suggestionOperationKey(draft.operations[0]!));
|
|
351
|
+
if (exact) return exact;
|
|
352
|
+
return persisted.size === 1
|
|
353
|
+
? (persisted.values().next().value ?? null)
|
|
354
|
+
: null;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
export function sameSuggestionAnchorIds(
|
|
358
|
+
current: string[] | null,
|
|
359
|
+
next: string[],
|
|
360
|
+
) {
|
|
361
|
+
return (
|
|
362
|
+
current !== null &&
|
|
363
|
+
current.length === next.length &&
|
|
364
|
+
current.every((id) => next.includes(id))
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
export function documentEditorReservesInlineReviewSpace(args: {
|
|
369
|
+
showInlineComments: boolean;
|
|
370
|
+
preserveInlineReviewSpace: boolean;
|
|
371
|
+
hasInlineCommentSpace: boolean;
|
|
372
|
+
isDatabasePage: boolean;
|
|
373
|
+
}) {
|
|
374
|
+
return (
|
|
375
|
+
!args.isDatabasePage &&
|
|
376
|
+
(args.showInlineComments ||
|
|
377
|
+
(args.preserveInlineReviewSpace && args.hasInlineCommentSpace))
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
|
|
328
381
|
export function suggestionPresentation(
|
|
329
382
|
suggestion: Pick<ResourceSuggestion, "id" | "status" | "operations">,
|
|
330
383
|
currentMarkdown: string,
|
|
@@ -1667,6 +1720,18 @@ function PageEditorSessionBody({
|
|
|
1667
1720
|
{ enabled: !isLocalFileDocument },
|
|
1668
1721
|
);
|
|
1669
1722
|
const [isSuggesting, setIsSuggesting] = useState(false);
|
|
1723
|
+
const [isStartingSuggestion, setIsStartingSuggestion] = useState(false);
|
|
1724
|
+
const [pendingSuggestionDecision, setPendingSuggestionDecision] = useState<{
|
|
1725
|
+
suggestion: ResourceSuggestion;
|
|
1726
|
+
decision: SuggestionDecision;
|
|
1727
|
+
continueSuggesting: boolean;
|
|
1728
|
+
optimistic: boolean;
|
|
1729
|
+
} | null>(null);
|
|
1730
|
+
const [decisionRefreshFailed, setDecisionRefreshFailed] = useState(false);
|
|
1731
|
+
const decisionRefreshInFlightRef = useRef(false);
|
|
1732
|
+
const suggestionDecisionInFlightRef = useRef(false);
|
|
1733
|
+
const [preserveInlineReviewSpace, setPreserveInlineReviewSpace] =
|
|
1734
|
+
useState(false);
|
|
1670
1735
|
// Registry blocks do not yet produce typed suggestion operations. Keep their
|
|
1671
1736
|
// canonical child actions read-only while the surrounding body is a draft.
|
|
1672
1737
|
const blockRenderContext = useMemo(
|
|
@@ -1678,16 +1743,21 @@ function PageEditorSessionBody({
|
|
|
1678
1743
|
[documentId, canEdit, isSuggesting],
|
|
1679
1744
|
);
|
|
1680
1745
|
const [isSubmittingSuggestions, setIsSubmittingSuggestions] = useState(false);
|
|
1681
|
-
const [decidingSuggestionIds, setDecidingSuggestionIds] = useState<
|
|
1682
|
-
ReadonlySet<string>
|
|
1683
|
-
>(() => new Set());
|
|
1684
|
-
const decidingSuggestionIdsRef = useRef(new Set<string>());
|
|
1685
1746
|
const [suggestionAmendmentConflict, setSuggestionAmendmentConflict] =
|
|
1686
1747
|
useState(false);
|
|
1687
1748
|
const [suggestionDraft, setSuggestionDraft] = useState(document.content);
|
|
1688
1749
|
const [anchoredSuggestionIds, setAnchoredSuggestionIds] = useState<
|
|
1689
1750
|
string[] | null
|
|
1690
1751
|
>(null);
|
|
1752
|
+
const handleSuggestionAnchorsChange = useCallback(
|
|
1753
|
+
(next: string[]) => {
|
|
1754
|
+
if (isSuggesting) return;
|
|
1755
|
+
setAnchoredSuggestionIds((current) =>
|
|
1756
|
+
sameSuggestionAnchorIds(current, next) ? current : next,
|
|
1757
|
+
);
|
|
1758
|
+
},
|
|
1759
|
+
[isSuggesting],
|
|
1760
|
+
);
|
|
1691
1761
|
const [selectedSuggestionId, setSelectedSuggestionId] = useState<
|
|
1692
1762
|
string | null
|
|
1693
1763
|
>(null);
|
|
@@ -1834,15 +1904,21 @@ function PageEditorSessionBody({
|
|
|
1834
1904
|
useRef<VisualEditorPersistenceController | null>(null);
|
|
1835
1905
|
const [editorHistoryState, setEditorHistoryState] =
|
|
1836
1906
|
useState<VisualEditorHistoryState>({ canUndo: false, canRedo: false });
|
|
1907
|
+
const editorHistoryStateRef = useRef(editorHistoryState);
|
|
1837
1908
|
const handleHistoryStateChange = useCallback(
|
|
1838
1909
|
(next: VisualEditorHistoryState) => {
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
)
|
|
1910
|
+
const current = editorHistoryStateRef.current;
|
|
1911
|
+
if (
|
|
1912
|
+
current.canUndo === next.canUndo &&
|
|
1913
|
+
current.canRedo === next.canRedo
|
|
1914
|
+
) {
|
|
1915
|
+
return;
|
|
1916
|
+
}
|
|
1917
|
+
editorHistoryStateRef.current = next;
|
|
1918
|
+
if (isSuggesting) return;
|
|
1919
|
+
setEditorHistoryState(next);
|
|
1844
1920
|
},
|
|
1845
|
-
[],
|
|
1921
|
+
[isSuggesting],
|
|
1846
1922
|
);
|
|
1847
1923
|
const handleHistoryControllerChange = useCallback(
|
|
1848
1924
|
(controller: VisualEditorHistoryController | null) => {
|
|
@@ -3676,6 +3752,28 @@ function PageEditorSessionBody({
|
|
|
3676
3752
|
suggestionsQuery.data?.suggestions ?? [],
|
|
3677
3753
|
);
|
|
3678
3754
|
}, [locallyCreatedSuggestions, suggestionsQuery.data?.suggestions]);
|
|
3755
|
+
const presentedSuggestions = useMemo(() => {
|
|
3756
|
+
if (!pendingSuggestionDecision?.continueSuggesting) return savedSuggestions;
|
|
3757
|
+
return savedSuggestions.map((suggestion) =>
|
|
3758
|
+
suggestion.id === pendingSuggestionDecision.suggestion.id
|
|
3759
|
+
? {
|
|
3760
|
+
...suggestion,
|
|
3761
|
+
status: pendingSuggestionDecision.optimistic
|
|
3762
|
+
? pendingSuggestionDecision.decision
|
|
3763
|
+
: pendingSuggestionDecision.suggestion.status,
|
|
3764
|
+
}
|
|
3765
|
+
: suggestion,
|
|
3766
|
+
);
|
|
3767
|
+
}, [pendingSuggestionDecision, savedSuggestions]);
|
|
3768
|
+
const pendingSuggestionDecisionContent =
|
|
3769
|
+
pendingSuggestionDecision?.continueSuggesting
|
|
3770
|
+
? suggestionDecisionPreviewContent(
|
|
3771
|
+
pendingSuggestionDecision.suggestion,
|
|
3772
|
+
pendingSuggestionDecision.decision,
|
|
3773
|
+
document.content,
|
|
3774
|
+
pendingSuggestionDecision.optimistic,
|
|
3775
|
+
)
|
|
3776
|
+
: null;
|
|
3679
3777
|
const amendmentTargetIsResolved = suggestionAmendmentTargetIsResolved(
|
|
3680
3778
|
editingSuggestionId,
|
|
3681
3779
|
savedSuggestions,
|
|
@@ -3714,154 +3812,166 @@ function PageEditorSessionBody({
|
|
|
3714
3812
|
setSuggestionPersistenceRevision((revision) => revision + 1);
|
|
3715
3813
|
}, []);
|
|
3716
3814
|
|
|
3717
|
-
const flushSuggestionDraft = useCallback(
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
if (base.existingSuggestion) {
|
|
3740
|
-
toast.error(t("editor.suggestionAmendmentEmpty"));
|
|
3815
|
+
const flushSuggestionDraft = useCallback(
|
|
3816
|
+
async ({ keepMode = false }: { keepMode?: boolean } = {}) => {
|
|
3817
|
+
if (!isSuggesting) return null;
|
|
3818
|
+
if (isSubmittingSuggestions) return null;
|
|
3819
|
+
const base = suggestionBaseRef.current;
|
|
3820
|
+
if (!base) return null;
|
|
3821
|
+
if (base.existingSuggestion && suggestionDraft === base.initialContent) {
|
|
3822
|
+
if (!keepMode) {
|
|
3823
|
+
setIsSuggesting(false);
|
|
3824
|
+
suggestionBaseRef.current = null;
|
|
3825
|
+
setEditingSuggestionId(null);
|
|
3826
|
+
setSuggestionInitialSelection(null);
|
|
3827
|
+
setSuggestionAmendmentConflict(false);
|
|
3828
|
+
suggestionAmendmentKeysRef.current.clear();
|
|
3829
|
+
}
|
|
3830
|
+
return new Map<string, ResourceSuggestion>();
|
|
3831
|
+
}
|
|
3832
|
+
if (
|
|
3833
|
+
base.existingSuggestion &&
|
|
3834
|
+
(suggestionAmendmentConflict || amendmentTargetIsResolved)
|
|
3835
|
+
) {
|
|
3836
|
+
setSuggestionAmendmentConflict(true);
|
|
3741
3837
|
return null;
|
|
3742
3838
|
}
|
|
3743
|
-
|
|
3744
|
-
suggestionBaseRef.current = null;
|
|
3745
|
-
setEditingSuggestionId(null);
|
|
3746
|
-
setSuggestionInitialSelection(null);
|
|
3747
|
-
setSuggestionAmendmentConflict(false);
|
|
3748
|
-
createdSuggestionOperationsRef.current.clear();
|
|
3749
|
-
return new Map<string, ResourceSuggestion>();
|
|
3750
|
-
}
|
|
3751
|
-
setIsSubmittingSuggestions(true);
|
|
3752
|
-
try {
|
|
3753
|
-
const operations = suggestionDraftOperations(base, suggestionDraft);
|
|
3754
|
-
if (operations.length === 0) {
|
|
3839
|
+
if (suggestionDraft === base.baseContent) {
|
|
3755
3840
|
if (base.existingSuggestion) {
|
|
3756
3841
|
toast.error(t("editor.suggestionAmendmentEmpty"));
|
|
3757
3842
|
return null;
|
|
3758
3843
|
}
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3844
|
+
if (!keepMode) {
|
|
3845
|
+
setIsSuggesting(false);
|
|
3846
|
+
suggestionBaseRef.current = null;
|
|
3847
|
+
setEditingSuggestionId(null);
|
|
3848
|
+
setSuggestionInitialSelection(null);
|
|
3849
|
+
setSuggestionAmendmentConflict(false);
|
|
3850
|
+
createdSuggestionOperationsRef.current.clear();
|
|
3851
|
+
}
|
|
3764
3852
|
return new Map<string, ResourceSuggestion>();
|
|
3765
3853
|
}
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
const
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3854
|
+
setIsSubmittingSuggestions(true);
|
|
3855
|
+
try {
|
|
3856
|
+
const operations = suggestionDraftOperations(base, suggestionDraft);
|
|
3857
|
+
if (operations.length === 0) {
|
|
3858
|
+
if (base.existingSuggestion) {
|
|
3859
|
+
toast.error(t("editor.suggestionAmendmentEmpty"));
|
|
3860
|
+
return null;
|
|
3861
|
+
}
|
|
3862
|
+
if (!keepMode) {
|
|
3863
|
+
setIsSuggesting(false);
|
|
3864
|
+
suggestionBaseRef.current = null;
|
|
3865
|
+
setEditingSuggestionId(null);
|
|
3866
|
+
setSuggestionInitialSelection(null);
|
|
3867
|
+
setSuggestionAmendmentConflict(false);
|
|
3868
|
+
}
|
|
3869
|
+
return new Map<string, ResourceSuggestion>();
|
|
3870
|
+
}
|
|
3871
|
+
let persisted: Map<string, ResourceSuggestion>;
|
|
3872
|
+
if (base.existingSuggestion) {
|
|
3873
|
+
const operationKey = JSON.stringify(operations);
|
|
3874
|
+
const idempotencyKey =
|
|
3875
|
+
suggestionAmendmentKeysRef.current.get(operationKey) ??
|
|
3876
|
+
globalThis.crypto.randomUUID();
|
|
3877
|
+
suggestionAmendmentKeysRef.current.set(operationKey, idempotencyKey);
|
|
3878
|
+
const amended = await updateSuggestion.mutateAsync({
|
|
3879
|
+
id: base.existingSuggestion.id,
|
|
3880
|
+
observedRevision: base.existingSuggestion.revision,
|
|
3881
|
+
idempotencyKey,
|
|
3882
|
+
operations,
|
|
3883
|
+
summary: t("editor.toolbar.suggestEdits"),
|
|
3884
|
+
});
|
|
3885
|
+
setLocallyCreatedSuggestions((current) => {
|
|
3886
|
+
const byId = new Map(
|
|
3887
|
+
current.map((suggestion) => [suggestion.id, suggestion]),
|
|
3888
|
+
);
|
|
3889
|
+
byId.set(amended.id, amended);
|
|
3890
|
+
return [...byId.values()];
|
|
3891
|
+
});
|
|
3892
|
+
setSuggestionPersistenceRevision((revision) => revision + 1);
|
|
3893
|
+
persisted = new Map([
|
|
3894
|
+
[suggestionOperationKey(operations[0]!), amended],
|
|
3895
|
+
]);
|
|
3896
|
+
} else {
|
|
3897
|
+
persisted = await persistSuggestionDraftOperations(
|
|
3898
|
+
operations,
|
|
3899
|
+
createdSuggestionOperationsRef.current,
|
|
3900
|
+
(operation, idempotencyKey) =>
|
|
3901
|
+
createSuggestion.mutateAsync({
|
|
3902
|
+
resourceType: "document",
|
|
3903
|
+
resourceId: documentId,
|
|
3904
|
+
adapterKind: "content.document-markdown",
|
|
3905
|
+
baseRevision: base.baseRevision,
|
|
3906
|
+
summary: t("editor.toolbar.suggestEdits"),
|
|
3907
|
+
idempotencyKey,
|
|
3908
|
+
operations: [operation],
|
|
3909
|
+
}),
|
|
3783
3910
|
);
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
resourceType: "document",
|
|
3798
|
-
resourceId: documentId,
|
|
3799
|
-
adapterKind: "content.document-markdown",
|
|
3800
|
-
baseRevision: base.baseRevision,
|
|
3801
|
-
summary: t("editor.toolbar.suggestEdits"),
|
|
3802
|
-
idempotencyKey,
|
|
3803
|
-
operations: [operation],
|
|
3804
|
-
}),
|
|
3805
|
-
);
|
|
3911
|
+
adoptConfirmedSuggestions();
|
|
3912
|
+
}
|
|
3913
|
+
if (!keepMode) {
|
|
3914
|
+
setIsSuggesting(false);
|
|
3915
|
+
suggestionBaseRef.current = null;
|
|
3916
|
+
setEditingSuggestionId(null);
|
|
3917
|
+
setSuggestionInitialSelection(null);
|
|
3918
|
+
setSuggestionAmendmentConflict(false);
|
|
3919
|
+
createdSuggestionOperationsRef.current.clear();
|
|
3920
|
+
suggestionAmendmentKeysRef.current.clear();
|
|
3921
|
+
}
|
|
3922
|
+
return persisted;
|
|
3923
|
+
} catch (error) {
|
|
3806
3924
|
adoptConfirmedSuggestions();
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3925
|
+
if (error instanceof SuggestionFormattingMappingError) {
|
|
3926
|
+
toast.error(t("editor.suggestionFormattingUnsupported"));
|
|
3927
|
+
return null;
|
|
3928
|
+
}
|
|
3929
|
+
if (base.existingSuggestion && isSuggestionConflictActionError(error)) {
|
|
3930
|
+
setSuggestionAmendmentConflict(true);
|
|
3931
|
+
void suggestionsQuery.refetch();
|
|
3932
|
+
void queryClient.invalidateQueries(documentQueryFilter(documentId));
|
|
3933
|
+
return null;
|
|
3934
|
+
}
|
|
3935
|
+
toast.error(
|
|
3936
|
+
t(
|
|
3937
|
+
base.existingSuggestion
|
|
3938
|
+
? "editor.suggestionAmendmentFailed"
|
|
3939
|
+
: "editor.suggestionCreateFailed",
|
|
3940
|
+
),
|
|
3941
|
+
{
|
|
3942
|
+
description: actionErrorMessage(error) ?? t("empty.genericError"),
|
|
3943
|
+
},
|
|
3944
|
+
);
|
|
3826
3945
|
return null;
|
|
3946
|
+
} finally {
|
|
3947
|
+
setIsSubmittingSuggestions(false);
|
|
3827
3948
|
}
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
updateSuggestion,
|
|
3845
|
-
adoptConfirmedSuggestions,
|
|
3846
|
-
documentId,
|
|
3847
|
-
isSuggesting,
|
|
3848
|
-
isSubmittingSuggestions,
|
|
3849
|
-
amendmentTargetIsResolved,
|
|
3850
|
-
queryClient,
|
|
3851
|
-
suggestionAmendmentConflict,
|
|
3852
|
-
suggestionDraft,
|
|
3853
|
-
suggestionsQuery,
|
|
3854
|
-
t,
|
|
3855
|
-
]);
|
|
3949
|
+
},
|
|
3950
|
+
[
|
|
3951
|
+
createSuggestion,
|
|
3952
|
+
updateSuggestion,
|
|
3953
|
+
adoptConfirmedSuggestions,
|
|
3954
|
+
documentId,
|
|
3955
|
+
isSuggesting,
|
|
3956
|
+
isSubmittingSuggestions,
|
|
3957
|
+
amendmentTargetIsResolved,
|
|
3958
|
+
queryClient,
|
|
3959
|
+
suggestionAmendmentConflict,
|
|
3960
|
+
suggestionDraft,
|
|
3961
|
+
suggestionsQuery,
|
|
3962
|
+
t,
|
|
3963
|
+
],
|
|
3964
|
+
);
|
|
3856
3965
|
|
|
3857
3966
|
const startSuggestionDraft = useCallback(
|
|
3858
3967
|
(
|
|
3968
|
+
nextDocument: Document,
|
|
3859
3969
|
suggestion?: ResourceSuggestion,
|
|
3860
3970
|
initialSelection?: VisualEditorSelectionSnapshot | null,
|
|
3861
3971
|
) => {
|
|
3862
3972
|
if (!canSuggest || isSuggesting) return false;
|
|
3863
3973
|
try {
|
|
3864
|
-
suggestionMarkedSourceRanges(
|
|
3974
|
+
suggestionMarkedSourceRanges(nextDocument.content);
|
|
3865
3975
|
} catch (error) {
|
|
3866
3976
|
if (!(error instanceof SuggestionFormattingMappingError)) throw error;
|
|
3867
3977
|
toast.error(t("editor.suggestionFormattingBaselineUnsupported"));
|
|
@@ -3871,9 +3981,9 @@ function PageEditorSessionBody({
|
|
|
3871
3981
|
? editableSuggestionDraft({
|
|
3872
3982
|
suggestion,
|
|
3873
3983
|
currentUserEmail: session?.email,
|
|
3874
|
-
canonicalContent:
|
|
3984
|
+
canonicalContent: nextDocument.content,
|
|
3875
3985
|
canonicalRevision: canonicalSuggestionRevision(
|
|
3876
|
-
|
|
3986
|
+
nextDocument,
|
|
3877
3987
|
suggestion,
|
|
3878
3988
|
),
|
|
3879
3989
|
})
|
|
@@ -3886,11 +3996,11 @@ function PageEditorSessionBody({
|
|
|
3886
3996
|
existing?.session ??
|
|
3887
3997
|
createSuggestionDraftSession({
|
|
3888
3998
|
id: globalThis.crypto.randomUUID(),
|
|
3889
|
-
baseContent:
|
|
3890
|
-
baseRevision: canonicalSuggestionRevision(
|
|
3999
|
+
baseContent: nextDocument.content,
|
|
4000
|
+
baseRevision: canonicalSuggestionRevision(nextDocument),
|
|
3891
4001
|
startedAt: new Date().toISOString(),
|
|
3892
4002
|
});
|
|
3893
|
-
setSuggestionDraft(existing?.content ??
|
|
4003
|
+
setSuggestionDraft(existing?.content ?? nextDocument.content);
|
|
3894
4004
|
setSuggestionInitialSelection(
|
|
3895
4005
|
existing?.caret ?? initialSelection ?? null,
|
|
3896
4006
|
);
|
|
@@ -3899,13 +4009,127 @@ function PageEditorSessionBody({
|
|
|
3899
4009
|
setIsSuggesting(true);
|
|
3900
4010
|
return true;
|
|
3901
4011
|
},
|
|
4012
|
+
[canSuggest, isSuggesting, session?.email, t],
|
|
4013
|
+
);
|
|
4014
|
+
|
|
4015
|
+
const prepareSuggestionDraftDocument = useCallback(async () => {
|
|
4016
|
+
const content = localContentRef.current;
|
|
4017
|
+
const title = localTitleRef.current;
|
|
4018
|
+
const pending = pendingDocumentSaveRef.current;
|
|
4019
|
+
if (
|
|
4020
|
+
!pending &&
|
|
4021
|
+
content === document.content &&
|
|
4022
|
+
title === document.title &&
|
|
4023
|
+
title === lastSavedTitleRef.current.title
|
|
4024
|
+
)
|
|
4025
|
+
return document;
|
|
4026
|
+
if (pending) {
|
|
4027
|
+
clearTimeout(pending.timeout);
|
|
4028
|
+
pendingDocumentSaveRef.current = null;
|
|
4029
|
+
saveTimeoutRef.current = null;
|
|
4030
|
+
}
|
|
4031
|
+
try {
|
|
4032
|
+
const saved = await queueDocumentSave(title, content, {
|
|
4033
|
+
historySessionId: pending?.historySessionId,
|
|
4034
|
+
contentEditVersion: contentEditVersionRef.current,
|
|
4035
|
+
});
|
|
4036
|
+
if (
|
|
4037
|
+
!saved.contentPersisted ||
|
|
4038
|
+
localContentRef.current !== content ||
|
|
4039
|
+
localTitleRef.current !== title ||
|
|
4040
|
+
lastSavedTitleRef.current.title !== title
|
|
4041
|
+
) {
|
|
4042
|
+
toast.error(t("editor.suggestionCreateFailed"));
|
|
4043
|
+
return null;
|
|
4044
|
+
}
|
|
4045
|
+
const refreshedDocument = await callAction(
|
|
4046
|
+
"get-document",
|
|
4047
|
+
{
|
|
4048
|
+
id: documentId,
|
|
4049
|
+
...(databaseId ? { databaseId } : {}),
|
|
4050
|
+
...(databaseDocumentId ? { databaseDocumentId } : {}),
|
|
4051
|
+
},
|
|
4052
|
+
{ method: "GET" },
|
|
4053
|
+
);
|
|
4054
|
+
if (
|
|
4055
|
+
refreshedDocument.content !== content ||
|
|
4056
|
+
refreshedDocument.title !== title
|
|
4057
|
+
) {
|
|
4058
|
+
toast.error(t("editor.toolbar.conflict"));
|
|
4059
|
+
return null;
|
|
4060
|
+
}
|
|
4061
|
+
patchDocumentCaches(queryClient, documentId, refreshedDocument);
|
|
4062
|
+
return refreshedDocument;
|
|
4063
|
+
} catch (error) {
|
|
4064
|
+
toast.error(t("editor.suggestionCreateFailed"), {
|
|
4065
|
+
description:
|
|
4066
|
+
error instanceof Error ? error.message : t("empty.genericError"),
|
|
4067
|
+
});
|
|
4068
|
+
return null;
|
|
4069
|
+
}
|
|
4070
|
+
}, [
|
|
4071
|
+
databaseDocumentId,
|
|
4072
|
+
databaseId,
|
|
4073
|
+
document,
|
|
4074
|
+
documentId,
|
|
4075
|
+
queryClient,
|
|
4076
|
+
queueDocumentSave,
|
|
4077
|
+
t,
|
|
4078
|
+
]);
|
|
4079
|
+
|
|
4080
|
+
const continueSuggestionModeFrom = useCallback((nextDocument: Document) => {
|
|
4081
|
+
createdSuggestionOperationsRef.current.clear();
|
|
4082
|
+
suggestionAmendmentKeysRef.current.clear();
|
|
4083
|
+
suggestionBaseRef.current = createSuggestionDraftSession({
|
|
4084
|
+
id: globalThis.crypto.randomUUID(),
|
|
4085
|
+
baseContent: nextDocument.content,
|
|
4086
|
+
baseRevision: canonicalSuggestionRevision(nextDocument),
|
|
4087
|
+
startedAt: new Date().toISOString(),
|
|
4088
|
+
});
|
|
4089
|
+
setSuggestionDraft(nextDocument.content);
|
|
4090
|
+
setEditingSuggestionId(null);
|
|
4091
|
+
setSuggestionInitialSelection(null);
|
|
4092
|
+
setSuggestionAmendmentConflict(false);
|
|
4093
|
+
setIsSuggesting(true);
|
|
4094
|
+
setSuggestionPersistenceRevision((revision) => revision + 1);
|
|
4095
|
+
}, []);
|
|
4096
|
+
|
|
4097
|
+
const refreshSuggestionDecisionDocument = useCallback(
|
|
4098
|
+
async (continueSuggesting: boolean) => {
|
|
4099
|
+
if (decisionRefreshInFlightRef.current) return;
|
|
4100
|
+
decisionRefreshInFlightRef.current = true;
|
|
4101
|
+
setDecisionRefreshFailed(false);
|
|
4102
|
+
try {
|
|
4103
|
+
const refreshedDocument = await callAction(
|
|
4104
|
+
"get-document",
|
|
4105
|
+
{
|
|
4106
|
+
id: documentId,
|
|
4107
|
+
...(databaseId ? { databaseId } : {}),
|
|
4108
|
+
...(databaseDocumentId ? { databaseDocumentId } : {}),
|
|
4109
|
+
},
|
|
4110
|
+
{ method: "GET" },
|
|
4111
|
+
);
|
|
4112
|
+
patchDocumentCaches(queryClient, documentId, refreshedDocument);
|
|
4113
|
+
if (continueSuggesting) continueSuggestionModeFrom(refreshedDocument);
|
|
4114
|
+
setPendingSuggestionDecision(null);
|
|
4115
|
+
suggestionDecisionInFlightRef.current = false;
|
|
4116
|
+
} catch (error) {
|
|
4117
|
+
setDecisionRefreshFailed(true);
|
|
4118
|
+
void queryClient.invalidateQueries(documentQueryFilter(documentId));
|
|
4119
|
+
toast.error(t("empty.genericError"), {
|
|
4120
|
+
description:
|
|
4121
|
+
error instanceof Error ? error.message : t("empty.genericError"),
|
|
4122
|
+
});
|
|
4123
|
+
} finally {
|
|
4124
|
+
decisionRefreshInFlightRef.current = false;
|
|
4125
|
+
}
|
|
4126
|
+
},
|
|
3902
4127
|
[
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
session?.email,
|
|
4128
|
+
continueSuggestionModeFrom,
|
|
4129
|
+
databaseDocumentId,
|
|
4130
|
+
databaseId,
|
|
4131
|
+
documentId,
|
|
4132
|
+
queryClient,
|
|
3909
4133
|
t,
|
|
3910
4134
|
],
|
|
3911
4135
|
);
|
|
@@ -3913,28 +4137,40 @@ function PageEditorSessionBody({
|
|
|
3913
4137
|
const handleSuggestionModeChange = useCallback(
|
|
3914
4138
|
async (next: boolean) => {
|
|
3915
4139
|
if (next) {
|
|
4140
|
+
if (isStartingSuggestion) return;
|
|
3916
4141
|
const initialSelection = pageActionsSelectionRef.current;
|
|
3917
4142
|
pageActionsSelectionRef.current = null;
|
|
3918
|
-
|
|
3919
|
-
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
const started = startSuggestionDraft(undefined, initialSelection);
|
|
3923
|
-
pageActionsSelectionRef.current = null;
|
|
3924
|
-
if (!started && initialSelection) {
|
|
4143
|
+
setIsStartingSuggestion(true);
|
|
4144
|
+
try {
|
|
4145
|
+
const readyDocument = await prepareSuggestionDraftDocument();
|
|
4146
|
+
if (!readyDocument) {
|
|
3925
4147
|
restoreCapturedEditorSelection(
|
|
3926
4148
|
editorSelectionControllerRef.current,
|
|
3927
4149
|
initialSelection,
|
|
3928
4150
|
);
|
|
4151
|
+
return;
|
|
3929
4152
|
}
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
4153
|
+
if (
|
|
4154
|
+
!shouldResumeSelectedSuggestionFromPageActions(initialSelection)
|
|
4155
|
+
) {
|
|
4156
|
+
if (
|
|
4157
|
+
!startSuggestionDraft(readyDocument, undefined, initialSelection)
|
|
4158
|
+
) {
|
|
4159
|
+
restoreCapturedEditorSelection(
|
|
4160
|
+
editorSelectionControllerRef.current,
|
|
4161
|
+
initialSelection,
|
|
4162
|
+
);
|
|
4163
|
+
}
|
|
4164
|
+
return;
|
|
4165
|
+
}
|
|
4166
|
+
const selected = savedSuggestions.find(
|
|
4167
|
+
(suggestion) => suggestion.id === selectedSuggestionId,
|
|
4168
|
+
);
|
|
4169
|
+
if (!selected || !startSuggestionDraft(readyDocument, selected)) {
|
|
4170
|
+
startSuggestionDraft(readyDocument);
|
|
4171
|
+
}
|
|
4172
|
+
} finally {
|
|
4173
|
+
setIsStartingSuggestion(false);
|
|
3938
4174
|
}
|
|
3939
4175
|
return;
|
|
3940
4176
|
}
|
|
@@ -3942,6 +4178,8 @@ function PageEditorSessionBody({
|
|
|
3942
4178
|
},
|
|
3943
4179
|
[
|
|
3944
4180
|
flushSuggestionDraft,
|
|
4181
|
+
isStartingSuggestion,
|
|
4182
|
+
prepareSuggestionDraftDocument,
|
|
3945
4183
|
savedSuggestions,
|
|
3946
4184
|
selectedSuggestionId,
|
|
3947
4185
|
startSuggestionDraft,
|
|
@@ -4015,8 +4253,15 @@ function PageEditorSessionBody({
|
|
|
4015
4253
|
setEditingSuggestionId(null);
|
|
4016
4254
|
setSuggestionInitialSelection(null);
|
|
4017
4255
|
setSuggestionAmendmentConflict(false);
|
|
4256
|
+
setPendingSuggestionDecision(null);
|
|
4257
|
+
setDecisionRefreshFailed(false);
|
|
4258
|
+
setPreserveInlineReviewSpace(false);
|
|
4018
4259
|
}, [documentId]);
|
|
4019
4260
|
|
|
4261
|
+
useEffect(() => {
|
|
4262
|
+
if (!isSuggesting) setPreserveInlineReviewSpace(false);
|
|
4263
|
+
}, [isSuggesting]);
|
|
4264
|
+
|
|
4020
4265
|
const discardConflictedSuggestionDraft = useCallback(() => {
|
|
4021
4266
|
setIsSuggesting(false);
|
|
4022
4267
|
setSuggestionDraft(document.content);
|
|
@@ -4056,9 +4301,11 @@ function PageEditorSessionBody({
|
|
|
4056
4301
|
}, [sessionDraftSuggestions, suggestionPersistenceRevision]);
|
|
4057
4302
|
|
|
4058
4303
|
const visualSuggestions = useMemo<VisualEditorSuggestion[]>(() => {
|
|
4059
|
-
const currentMarkdown =
|
|
4304
|
+
const currentMarkdown =
|
|
4305
|
+
pendingSuggestionDecisionContent ??
|
|
4306
|
+
(isSuggesting ? suggestionDraft : document.content);
|
|
4060
4307
|
const byId = new Map<string, VisualEditorSuggestion>();
|
|
4061
|
-
for (const suggestion of
|
|
4308
|
+
for (const suggestion of presentedSuggestions) {
|
|
4062
4309
|
if (suggestion.id === editingSuggestionId) continue;
|
|
4063
4310
|
const presentation = suggestionPresentation(suggestion, currentMarkdown);
|
|
4064
4311
|
if (presentation) byId.set(presentation.id, presentation);
|
|
@@ -4067,6 +4314,7 @@ function PageEditorSessionBody({
|
|
|
4067
4314
|
sessionDraftSuggestions,
|
|
4068
4315
|
createdSuggestionOperationsRef.current,
|
|
4069
4316
|
)) {
|
|
4317
|
+
if (suggestion.id === pendingSuggestionDecision?.suggestion.id) continue;
|
|
4070
4318
|
const operation = suggestion.operations[0]!;
|
|
4071
4319
|
const before = operation.before as { changedText: string };
|
|
4072
4320
|
const after = operation.after as {
|
|
@@ -4100,21 +4348,23 @@ function PageEditorSessionBody({
|
|
|
4100
4348
|
document.content,
|
|
4101
4349
|
editingSuggestionId,
|
|
4102
4350
|
isSuggesting,
|
|
4103
|
-
|
|
4351
|
+
pendingSuggestionDecision,
|
|
4352
|
+
pendingSuggestionDecisionContent,
|
|
4353
|
+
presentedSuggestions,
|
|
4104
4354
|
sessionDraftSuggestions,
|
|
4105
4355
|
suggestionPersistenceRevision,
|
|
4106
4356
|
suggestionDraft,
|
|
4107
4357
|
]);
|
|
4108
4358
|
const sidebarSuggestions = useMemo(() => {
|
|
4109
4359
|
if (!editingSuggestionId || sessionDraftSuggestions.length !== 1) {
|
|
4110
|
-
return
|
|
4360
|
+
return presentedSuggestions;
|
|
4111
4361
|
}
|
|
4112
|
-
return
|
|
4362
|
+
return presentedSuggestions.map((suggestion) =>
|
|
4113
4363
|
suggestion.id === editingSuggestionId
|
|
4114
4364
|
? { ...suggestion, operations: sessionDraftSuggestions[0]!.operations }
|
|
4115
4365
|
: suggestion,
|
|
4116
4366
|
);
|
|
4117
|
-
}, [editingSuggestionId,
|
|
4367
|
+
}, [editingSuggestionId, presentedSuggestions, sessionDraftSuggestions]);
|
|
4118
4368
|
|
|
4119
4369
|
useEffect(() => {
|
|
4120
4370
|
void setClientAppState(
|
|
@@ -4123,7 +4373,7 @@ function PageEditorSessionBody({
|
|
|
4123
4373
|
documentId,
|
|
4124
4374
|
suggesting: isSuggesting,
|
|
4125
4375
|
draftChanged: isSuggesting && suggestionDraft !== document.content,
|
|
4126
|
-
pendingCount:
|
|
4376
|
+
pendingCount: presentedSuggestions.filter(
|
|
4127
4377
|
(suggestion) => suggestion.status === "pending",
|
|
4128
4378
|
).length,
|
|
4129
4379
|
},
|
|
@@ -4136,7 +4386,7 @@ function PageEditorSessionBody({
|
|
|
4136
4386
|
documentId,
|
|
4137
4387
|
isSuggesting,
|
|
4138
4388
|
suggestionDraft,
|
|
4139
|
-
|
|
4389
|
+
presentedSuggestions,
|
|
4140
4390
|
]);
|
|
4141
4391
|
|
|
4142
4392
|
const handleContentSaveNow = useCallback(
|
|
@@ -4478,8 +4728,9 @@ function PageEditorSessionBody({
|
|
|
4478
4728
|
const hasOpenCommentThreads =
|
|
4479
4729
|
threads?.some((thread) => !thread.resolved) ?? false;
|
|
4480
4730
|
const hasOpenSuggestions =
|
|
4481
|
-
|
|
4482
|
-
|
|
4731
|
+
presentedSuggestions.some(
|
|
4732
|
+
(suggestion) => suggestion.status === "pending",
|
|
4733
|
+
) || draftSuggestions.length > 0;
|
|
4483
4734
|
const hasSelectedCommentThread =
|
|
4484
4735
|
!!selectedThreadId &&
|
|
4485
4736
|
(threads?.some((thread) => thread.threadId === selectedThreadId) ?? false);
|
|
@@ -4492,6 +4743,12 @@ function PageEditorSessionBody({
|
|
|
4492
4743
|
hasSelectedCommentThread,
|
|
4493
4744
|
hasPendingComment: !!pendingComment,
|
|
4494
4745
|
});
|
|
4746
|
+
const reserveInlineReviewSpace = documentEditorReservesInlineReviewSpace({
|
|
4747
|
+
showInlineComments,
|
|
4748
|
+
preserveInlineReviewSpace,
|
|
4749
|
+
hasInlineCommentSpace,
|
|
4750
|
+
isDatabasePage: Boolean(document.database),
|
|
4751
|
+
});
|
|
4495
4752
|
const showDesktopInfoPanel = utilityPanel === "info" && hasUtilityRailSpace;
|
|
4496
4753
|
const showDesktopRightRail = showInlineComments || showDesktopInfoPanel;
|
|
4497
4754
|
const showAnchoredCommentPopover =
|
|
@@ -4607,7 +4864,7 @@ function PageEditorSessionBody({
|
|
|
4607
4864
|
}, []);
|
|
4608
4865
|
|
|
4609
4866
|
const activateInlineSuggestion = useCallback(
|
|
4610
|
-
(suggestionId: string) => {
|
|
4867
|
+
async (suggestionId: string) => {
|
|
4611
4868
|
if (
|
|
4612
4869
|
isSuggesting &&
|
|
4613
4870
|
suggestionBaseRef.current?.existingSuggestion?.id === suggestionId
|
|
@@ -4617,17 +4874,36 @@ function PageEditorSessionBody({
|
|
|
4617
4874
|
const suggestion = savedSuggestions.find(
|
|
4618
4875
|
(candidate) => candidate.id === suggestionId,
|
|
4619
4876
|
);
|
|
4620
|
-
if (suggestion &&
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
|
|
4626
|
-
|
|
4877
|
+
if (suggestion && !isStartingSuggestion) {
|
|
4878
|
+
setIsStartingSuggestion(true);
|
|
4879
|
+
try {
|
|
4880
|
+
const readyDocument = await prepareSuggestionDraftDocument();
|
|
4881
|
+
if (
|
|
4882
|
+
readyDocument &&
|
|
4883
|
+
startSuggestionDraft(readyDocument, suggestion)
|
|
4884
|
+
) {
|
|
4885
|
+
setPendingComment(null);
|
|
4886
|
+
setHoveredThreadId(null);
|
|
4887
|
+
setSelectedThreadId(null);
|
|
4888
|
+
setSelectedSuggestionId(null);
|
|
4889
|
+
setHoveredSuggestionId(null);
|
|
4890
|
+
return;
|
|
4891
|
+
}
|
|
4892
|
+
if (!readyDocument) return;
|
|
4893
|
+
} finally {
|
|
4894
|
+
setIsStartingSuggestion(false);
|
|
4895
|
+
}
|
|
4627
4896
|
}
|
|
4628
4897
|
activateSuggestion(suggestionId);
|
|
4629
4898
|
},
|
|
4630
|
-
[
|
|
4899
|
+
[
|
|
4900
|
+
activateSuggestion,
|
|
4901
|
+
isStartingSuggestion,
|
|
4902
|
+
isSuggesting,
|
|
4903
|
+
prepareSuggestionDraftDocument,
|
|
4904
|
+
savedSuggestions,
|
|
4905
|
+
startSuggestionDraft,
|
|
4906
|
+
],
|
|
4631
4907
|
);
|
|
4632
4908
|
const handledCommentDeepLinkRef = useRef<string | null>(null);
|
|
4633
4909
|
|
|
@@ -5108,62 +5384,87 @@ function PageEditorSessionBody({
|
|
|
5108
5384
|
suggestions={sidebarSuggestions}
|
|
5109
5385
|
draftSuggestions={draftSuggestions}
|
|
5110
5386
|
onMaterializeDraft={async (draft) => {
|
|
5111
|
-
const persisted = await flushSuggestionDraft();
|
|
5387
|
+
const persisted = await flushSuggestionDraft({ keepMode: true });
|
|
5112
5388
|
if (!persisted) return null;
|
|
5113
|
-
const suggestion = persisted
|
|
5114
|
-
suggestionOperationKey(draft.operations[0]!),
|
|
5115
|
-
);
|
|
5389
|
+
const suggestion = materializedSuggestionForDraft(persisted, draft);
|
|
5116
5390
|
if (suggestion) setSelectedSuggestionId(suggestion.id);
|
|
5117
5391
|
return suggestion ?? null;
|
|
5118
5392
|
}}
|
|
5119
5393
|
canDecideSuggestions={canEdit}
|
|
5120
|
-
decidingSuggestion={(
|
|
5121
|
-
|
|
5394
|
+
decidingSuggestion={() =>
|
|
5395
|
+
decideSuggestion.isPending ||
|
|
5396
|
+
isSubmittingSuggestions ||
|
|
5397
|
+
!!pendingSuggestionDecision
|
|
5122
5398
|
}
|
|
5123
5399
|
onDecideSuggestion={async (suggestion, decision) => {
|
|
5124
|
-
if (
|
|
5400
|
+
if (
|
|
5401
|
+
suggestionDecisionInFlightRef.current ||
|
|
5402
|
+
pendingSuggestionDecision ||
|
|
5403
|
+
decideSuggestion.isPending ||
|
|
5404
|
+
isSubmittingSuggestions
|
|
5405
|
+
)
|
|
5406
|
+
return;
|
|
5407
|
+
suggestionDecisionInFlightRef.current = true;
|
|
5408
|
+
const continueSuggesting = isSuggesting;
|
|
5125
5409
|
let observedSuggestion = suggestion;
|
|
5126
|
-
if (
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
5410
|
+
if (continueSuggesting) {
|
|
5411
|
+
const persisted = await flushSuggestionDraft({ keepMode: true });
|
|
5412
|
+
if (!persisted) {
|
|
5413
|
+
suggestionDecisionInFlightRef.current = false;
|
|
5414
|
+
return;
|
|
5415
|
+
}
|
|
5416
|
+
if (suggestion.id === editingSuggestionId) {
|
|
5417
|
+
observedSuggestion = [...persisted.values()][0] ?? suggestion;
|
|
5418
|
+
}
|
|
5131
5419
|
}
|
|
5132
|
-
if (
|
|
5133
|
-
|
|
5134
|
-
|
|
5420
|
+
if (showInlineComments) setPreserveInlineReviewSpace(true);
|
|
5421
|
+
setPendingSuggestionDecision({
|
|
5422
|
+
suggestion: observedSuggestion,
|
|
5423
|
+
decision,
|
|
5424
|
+
continueSuggesting,
|
|
5425
|
+
optimistic: true,
|
|
5426
|
+
});
|
|
5427
|
+
setDecisionRefreshFailed(false);
|
|
5428
|
+
let result: Awaited<ReturnType<typeof decideSuggestion.mutateAsync>>;
|
|
5135
5429
|
try {
|
|
5136
|
-
|
|
5430
|
+
result = await decideSuggestion.mutateAsync({
|
|
5137
5431
|
id: observedSuggestion.id,
|
|
5138
5432
|
decision,
|
|
5139
5433
|
idempotencyKey: globalThis.crypto.randomUUID(),
|
|
5140
5434
|
observedBase: observedSuggestion.baseRevision,
|
|
5141
5435
|
observedRevision: observedSuggestion.revision,
|
|
5142
5436
|
});
|
|
5143
|
-
if (
|
|
5144
|
-
result.suggestion.id === editingSuggestionId &&
|
|
5145
|
-
result.suggestion.status !== "pending"
|
|
5146
|
-
) {
|
|
5147
|
-
setIsSuggesting(false);
|
|
5148
|
-
suggestionBaseRef.current = null;
|
|
5149
|
-
setEditingSuggestionId(null);
|
|
5150
|
-
setSuggestionInitialSelection(null);
|
|
5151
|
-
suggestionAmendmentKeysRef.current.clear();
|
|
5152
|
-
}
|
|
5153
|
-
if (result.suggestion.status === "stale") {
|
|
5154
|
-
toast.error(t("editor.toolbar.conflict"));
|
|
5155
|
-
setSelectedSuggestionId(result.suggestion.id);
|
|
5156
|
-
setUtilityPanel("comments");
|
|
5157
|
-
setCommentsBrowseOpen(true);
|
|
5158
|
-
}
|
|
5159
5437
|
} catch (error) {
|
|
5438
|
+
setPendingSuggestionDecision(null);
|
|
5439
|
+
setDecisionRefreshFailed(false);
|
|
5440
|
+
suggestionDecisionInFlightRef.current = false;
|
|
5160
5441
|
toast.error(t("empty.genericError"), {
|
|
5161
5442
|
description:
|
|
5162
5443
|
error instanceof Error ? error.message : t("empty.genericError"),
|
|
5163
5444
|
});
|
|
5164
|
-
|
|
5165
|
-
|
|
5166
|
-
|
|
5445
|
+
return;
|
|
5446
|
+
}
|
|
5447
|
+
if (result.suggestion.status !== decision) {
|
|
5448
|
+
setPendingSuggestionDecision((current) =>
|
|
5449
|
+
current?.suggestion.id === observedSuggestion.id
|
|
5450
|
+
? { ...current, suggestion: result.suggestion, optimistic: false }
|
|
5451
|
+
: current,
|
|
5452
|
+
);
|
|
5453
|
+
}
|
|
5454
|
+
setLocallyCreatedSuggestions((current) => {
|
|
5455
|
+
const byId = new Map(
|
|
5456
|
+
current.map((candidate) => [candidate.id, candidate]),
|
|
5457
|
+
);
|
|
5458
|
+
byId.set(result.suggestion.id, result.suggestion);
|
|
5459
|
+
return [...byId.values()];
|
|
5460
|
+
});
|
|
5461
|
+
void suggestionsQuery.refetch();
|
|
5462
|
+
await refreshSuggestionDecisionDocument(continueSuggesting);
|
|
5463
|
+
if (result.suggestion.status === "stale") {
|
|
5464
|
+
toast.error(t("editor.toolbar.conflict"));
|
|
5465
|
+
setSelectedSuggestionId(result.suggestion.id);
|
|
5466
|
+
setUtilityPanel("comments");
|
|
5467
|
+
setCommentsBrowseOpen(true);
|
|
5167
5468
|
}
|
|
5168
5469
|
}}
|
|
5169
5470
|
canSuggest={canSuggest}
|
|
@@ -5397,8 +5698,16 @@ function PageEditorSessionBody({
|
|
|
5397
5698
|
onOpenBreadcrumbItem={
|
|
5398
5699
|
host === "page" ? handleOpenToolbarBreadcrumb : undefined
|
|
5399
5700
|
}
|
|
5400
|
-
canUndo={
|
|
5401
|
-
|
|
5701
|
+
canUndo={
|
|
5702
|
+
isSuggesting
|
|
5703
|
+
? editorHistoryStateRef.current.canUndo
|
|
5704
|
+
: editorHistoryState.canUndo
|
|
5705
|
+
}
|
|
5706
|
+
canRedo={
|
|
5707
|
+
isSuggesting
|
|
5708
|
+
? editorHistoryStateRef.current.canRedo
|
|
5709
|
+
: editorHistoryState.canRedo
|
|
5710
|
+
}
|
|
5402
5711
|
onUndo={() => editorHistoryControllerRef.current?.undo()}
|
|
5403
5712
|
onRedo={() => editorHistoryControllerRef.current?.redo()}
|
|
5404
5713
|
canSuggest={canSuggest}
|
|
@@ -5552,7 +5861,7 @@ function PageEditorSessionBody({
|
|
|
5552
5861
|
className={cn(
|
|
5553
5862
|
"min-w-0",
|
|
5554
5863
|
showDesktopInfoPanel ? "flex-1" : "w-full",
|
|
5555
|
-
|
|
5864
|
+
reserveInlineReviewSpace && "pr-80",
|
|
5556
5865
|
)}
|
|
5557
5866
|
>
|
|
5558
5867
|
<div
|
|
@@ -5770,6 +6079,19 @@ function PageEditorSessionBody({
|
|
|
5770
6079
|
/>
|
|
5771
6080
|
</div>
|
|
5772
6081
|
) : null}
|
|
6082
|
+
{decisionRefreshFailed &&
|
|
6083
|
+
pendingSuggestionDecision ? (
|
|
6084
|
+
<div role="alert">
|
|
6085
|
+
<QueryErrorState
|
|
6086
|
+
compact
|
|
6087
|
+
onRetry={() =>
|
|
6088
|
+
void refreshSuggestionDecisionDocument(
|
|
6089
|
+
pendingSuggestionDecision.continueSuggesting,
|
|
6090
|
+
)
|
|
6091
|
+
}
|
|
6092
|
+
/>
|
|
6093
|
+
</div>
|
|
6094
|
+
) : null}
|
|
5773
6095
|
{suggestionDraftPreview.status ===
|
|
5774
6096
|
"unsupported-formatting" ? (
|
|
5775
6097
|
<div
|
|
@@ -5781,6 +6103,11 @@ function PageEditorSessionBody({
|
|
|
5781
6103
|
) : null}
|
|
5782
6104
|
<VisualEditor
|
|
5783
6105
|
onEscape={handleEditorEscape}
|
|
6106
|
+
contentResetKey={
|
|
6107
|
+
pendingSuggestionDecision
|
|
6108
|
+
? `${pendingSuggestionDecision.suggestion.id}:${pendingSuggestionDecision.decision}:${pendingSuggestionDecision.optimistic ? "optimistic" : "canonical"}`
|
|
6109
|
+
: null
|
|
6110
|
+
}
|
|
5784
6111
|
key={`${visualEditorInstanceKey({
|
|
5785
6112
|
documentId,
|
|
5786
6113
|
documentUpdatedAt: document.updatedAt,
|
|
@@ -5795,9 +6122,10 @@ function PageEditorSessionBody({
|
|
|
5795
6122
|
content={
|
|
5796
6123
|
isLocalFileDocument
|
|
5797
6124
|
? localContent
|
|
5798
|
-
:
|
|
5799
|
-
|
|
5800
|
-
|
|
6125
|
+
: (pendingSuggestionDecisionContent ??
|
|
6126
|
+
(isSuggesting
|
|
6127
|
+
? suggestionDraft
|
|
6128
|
+
: document.content))
|
|
5801
6129
|
}
|
|
5802
6130
|
contentUpdatedAt={
|
|
5803
6131
|
isLocalFileDocument
|
|
@@ -5841,7 +6169,9 @@ function PageEditorSessionBody({
|
|
|
5841
6169
|
user={currentUser}
|
|
5842
6170
|
editable={
|
|
5843
6171
|
suggestionEditorIsolation.editable &&
|
|
5844
|
-
!
|
|
6172
|
+
!isStartingSuggestion &&
|
|
6173
|
+
!isSubmittingSuggestions &&
|
|
6174
|
+
!pendingSuggestionDecision
|
|
5845
6175
|
}
|
|
5846
6176
|
suggesting={isSuggesting}
|
|
5847
6177
|
localFileMode={isLocalFileDocument}
|
|
@@ -5872,7 +6202,9 @@ function PageEditorSessionBody({
|
|
|
5872
6202
|
: undefined
|
|
5873
6203
|
}
|
|
5874
6204
|
initialSelection={suggestionInitialSelection}
|
|
5875
|
-
onSuggestionAnchorsChange={
|
|
6205
|
+
onSuggestionAnchorsChange={
|
|
6206
|
+
handleSuggestionAnchorsChange
|
|
6207
|
+
}
|
|
5876
6208
|
showCommentIndicators={showCommentIndicators}
|
|
5877
6209
|
onJoinTitle={joinFirstBodyBlockToTitle}
|
|
5878
6210
|
notionPageLinks={notionPageLinks}
|