From a871bca8f710494e4f191a384292c12201c8148f Mon Sep 17 00:00:00 2001 From: OrcaWin Date: Wed, 28 Oct 2026 17:26:36 -0700 Subject: [PATCH] Enforce Monaco paste cap before Monaco's capture handler (#35057) Monaco's CopyPasteController claims paste on the editor container in the capture phase before Orca's listener, so Ctrl/Cmd+V bypassed the 16 MiB cap. Listen on the editor shell instead, scoped to this editor's subtree and text focus, and only reject over-cap pastes; smaller pastes keep Monaco's atomic native paste. Remove the DOM chunked-paste plumbing (save deferral, isApplyingLargePasteRef) that could never run. Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> --- .../editor/editor-large-paste-in-flight.ts | 23 --- .../editor/editor-save-large-paste.test.ts | 156 ------------------ .../components/editor/editor-save-queue.ts | 6 +- ...ditor-input-bindings.paste-capture.test.ts | 92 +++++++++++ .../editor/monaco-editor-input-bindings.ts | 30 +--- .../editor/monaco-editor-mount-params.ts | 1 - .../editor/monaco-large-text-paste.ts | 3 +- .../editor/use-monaco-content-sync-bridge.ts | 8 - ...-monaco-editor-mount.cursor-owner.test.tsx | 3 +- ...e-monaco-editor-mount.stale-cache.test.tsx | 3 +- .../editor/use-monaco-editor-mount.ts | 6 +- 11 files changed, 107 insertions(+), 224 deletions(-) delete mode 100644 src/renderer/src/components/editor/editor-large-paste-in-flight.ts delete mode 100644 src/renderer/src/components/editor/editor-save-large-paste.test.ts create mode 100644 src/renderer/src/components/editor/monaco-editor-input-bindings.paste-capture.test.ts diff --git a/src/renderer/src/components/editor/editor-large-paste-in-flight.ts b/src/renderer/src/components/editor/editor-large-paste-in-flight.ts deleted file mode 100644 index f50b5afac13..00000000000 --- a/src/renderer/src/components/editor/editor-large-paste-in-flight.ts +++ /dev/null @@ -1,23 +0,0 @@ -// Chunked pastes publish drafts only at completion; hold saves across every tab sharing the model URI to avoid writing outdated text. -const pastesInFlight = new Map() - -export function beginEditorLargePaste(modelUri: string): () => void { - pastesInFlight.set(modelUri, (pastesInFlight.get(modelUri) ?? 0) + 1) - let ended = false - return () => { - if (ended) { - return - } - ended = true - const remaining = (pastesInFlight.get(modelUri) ?? 1) - 1 - if (remaining > 0) { - pastesInFlight.set(modelUri, remaining) - } else { - pastesInFlight.delete(modelUri) - } - } -} - -export function isEditorLargePasteInFlight(modelUri: string): boolean { - return pastesInFlight.has(modelUri) -} diff --git a/src/renderer/src/components/editor/editor-save-large-paste.test.ts b/src/renderer/src/components/editor/editor-save-large-paste.test.ts deleted file mode 100644 index d107db0a1df..00000000000 --- a/src/renderer/src/components/editor/editor-save-large-paste.test.ts +++ /dev/null @@ -1,156 +0,0 @@ -// @vitest-environment happy-dom -import { afterEach, expect, it, vi } from 'vitest' -import type { editor } from 'monaco-editor' -import { getDiskBaselineSignature } from '../../../../shared/disk-baseline-signature' -import { createEditorStore } from './editor-autosave-controller-test-fixture' -import { createEditorSaveQueue } from './editor-save-queue' -import { toEditorModelUri } from './editor-model-uri' -import { installMonacoEditorInputBindings } from './monaco-editor-input-bindings' -import { MONACO_PASTE_DIRECT_MAX_BYTES } from './monaco-large-text-paste' -import { compareDiskWritePrecondition } from '../../../../shared/file-write-disk-precondition' -import type { GuardedWriteArgs } from './editor-save-guarded-disk-fixture' - -vi.mock('@/store', () => ({ useAppStore: { getState: () => ({}) } })) -vi.mock('@/i18n/i18n', () => ({ translate: (_key: string, fallback: string) => fallback })) -vi.mock('@/lib/connection-context', () => ({ getConnectionIdForFile: () => undefined })) -vi.mock('./editor-shortcuts', () => ({ - installEditorAddReviewNoteShortcut: () => () => undefined, - installEditorSaveShortcut: () => () => undefined, - installMonacoEditorFindShortcut: () => () => undefined -})) - -const PATH = '/repo/file.ts' - -afterEach(() => { - vi.unstubAllGlobals() -}) - -function createSingleLineEditor(initial: string) { - let value = initial - let selection = { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 } - const container = document.createElement('div') - document.body.appendChild(container) - const model = { uri: { toString: () => toEditorModelUri(PATH) }, getValue: () => value } - const editorStub = { - getModel: () => model, - getContainerDomNode: () => container, - hasTextFocus: () => true, - getValue: () => value, - getSelection: () => selection, - setSelection: (next: typeof selection) => { - selection = next - }, - executeEdits: (_source: string, edits: { text: string }[]) => { - const at = selection.startColumn - 1 - value = value.slice(0, at) + edits[0].text + value.slice(at) - return true - }, - pushUndoStop: () => true, - addAction: () => ({ dispose: () => undefined }) - } - // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the bindings and chunked paste call only the methods stubbed above. - return { container, editorInstance: editorStub as unknown as editor.IStandaloneCodeEditor } -} - -function pasteIntoGuardedTab(pasted: string) { - const files = new Map([[PATH, 'base']]) - const writeFile = vi.fn(async (args: GuardedWriteArgs) => { - const conflict = args.diskPrecondition - ? compareDiskWritePrecondition( - args.diskPrecondition, - files.get(args.filePath) ?? null, - args.content - ) - : null - if (conflict === 'identical') { - return { ok: true, unchanged: true } - } - if (conflict) { - return { ok: false, diskConflict: conflict } - } - files.set(args.filePath, args.content) - return { ok: true } - }) - vi.stubGlobal('api', { fs: { writeFile } }) - const store = createEditorStore() - store.getState().openFile({ - filePath: PATH, - relativePath: 'file.ts', - worktreeId: 'wt-1', - language: 'typescript', - mode: 'edit' - }) - store.getState().setLastKnownDiskSignature(PATH, getDiskBaselineSignature('base')) - store.getState().setEditorDraft(PATH, 'base') - store.getState().markFileDirty(PATH, true) - const queue = createEditorSaveQueue(store) - - const { container, editorInstance } = createSingleLineEditor('base') - const onContentChange = vi.fn((value: string) => store.getState().setEditorDraft(PATH, value)) - installMonacoEditorInputBindings({ - editorInstance, - worktreeId: undefined, - editorContainerRef: { current: container }, - propsRef: { - current: { relativePath: 'file.ts', language: 'typescript', onSave: vi.fn(), onContentChange } - }, - readOnlyRef: { current: false }, - lastSyncedContentRef: { current: 'base' }, - isApplyingLargePasteRef: { current: false }, - commentPopoverRef: { current: null }, - shouldShowMarkdownAnnotationsRef: { current: false }, - setCommentPopover: vi.fn(), - setSelectionAnnotationTarget: vi.fn() - }) - - const paste = new Event('paste', { cancelable: true }) - Object.defineProperty(paste, 'clipboardData', { value: { getData: () => pasted } }) - container.dispatchEvent(paste) - - return { files, writeFile, store, queue, onContentChange } -} - -it('holds autosave while a chunked paste is in flight, then saves the pasted text guarded', async () => { - const pasted = 'x'.repeat(MONACO_PASTE_DIRECT_MAX_BYTES * 2) - const { files, writeFile, store, queue, onContentChange } = pasteIntoGuardedTab(pasted) - - // Mid-paste the draft still holds the pre-paste text; autosave must not write it. - expect(await queue.queueSave(store.getState().openFiles[0], 'base', 'autosave')).toBe('failed') - expect(writeFile).not.toHaveBeenCalled() - - await vi.waitFor(() => expect(onContentChange).toHaveBeenCalled()) - expect(await queue.queueSave(store.getState().openFiles[0], 'base', 'autosave')).toBe('written') - expect(files.get(PATH)).toBe(`${pasted}base`) - expect(writeFile).toHaveBeenCalledWith( - expect.objectContaining({ - diskPrecondition: { - expectedSignature: getDiskBaselineSignature('base'), - allowMissing: false, - requireLosslessUtf8: true - } - }) - ) - queue.dispose() -}) - -it('refuses a save after a 70 KiB paste when an agent wrote the file meanwhile', async () => { - const pasted = 'y'.repeat(70 * 1024) - const { files, writeFile, store, queue, onContentChange } = pasteIntoGuardedTab(pasted) - await vi.waitFor(() => expect(onContentChange).toHaveBeenCalled()) - files.set(PATH, 'agent rewrite') - - expect(await queue.queueSave(store.getState().openFiles[0], 'base', 'user')).toBe('blocked-stale') - expect(writeFile).toHaveBeenCalledWith( - expect.objectContaining({ - content: `${pasted}base`, - diskPrecondition: { - expectedSignature: getDiskBaselineSignature('base'), - allowMissing: true, - requireLosslessUtf8: true - } - }) - ) - expect(files.get(PATH)).toBe('agent rewrite') - expect(store.getState().openFiles[0]?.externalMutation).toBe('changed') - queue.dispose() -}) diff --git a/src/renderer/src/components/editor/editor-save-queue.ts b/src/renderer/src/components/editor/editor-save-queue.ts index 0dbd5d713e2..70966d0c5bf 100644 --- a/src/renderer/src/components/editor/editor-save-queue.ts +++ b/src/renderer/src/components/editor/editor-save-queue.ts @@ -24,7 +24,6 @@ import { } from './editor-self-write-registry' import { getDiskBaselineSignature } from '../../../../shared/disk-baseline-signature' import { editorTabFileAccess } from '@/lib/local-file-access' -import { isEditorLargePasteInFlight } from './editor-large-paste-in-flight' import { markEditorSaveRefusal, settleEditorRecordAfterWrite, @@ -35,7 +34,6 @@ import { import type { FileWriteResult } from '../../../../shared/file-write-disk-precondition' import { editorSavePrecondition, - editorTabModelUri, editorSaveGrant, writeEditorSave, type EditorSaveResult, @@ -122,9 +120,7 @@ export function createEditorSaveQueue(store: AppStoreApi): EditorSaveQueue { // Explicit saves bypass autosave suspension but remain guarded; only Overwrite grants conflict consent. if ( trigger === 'autosave' && - (isAutosaveSuspendedForFile(liveFile) || - isEditorLargePasteInFlight(editorTabModelUri(state, liveFile)) || - holds.isHeld(state, liveFile)) + (isAutosaveSuspendedForFile(liveFile) || holds.isHeld(state, liveFile)) ) { return 'failed' } diff --git a/src/renderer/src/components/editor/monaco-editor-input-bindings.paste-capture.test.ts b/src/renderer/src/components/editor/monaco-editor-input-bindings.paste-capture.test.ts new file mode 100644 index 00000000000..6cd013d4182 --- /dev/null +++ b/src/renderer/src/components/editor/monaco-editor-input-bindings.paste-capture.test.ts @@ -0,0 +1,92 @@ +// @vitest-environment happy-dom +import type { editor } from 'monaco-editor' +import { afterEach, expect, it, vi } from 'vitest' +import { toast } from 'sonner' +import { installMonacoEditorInputBindings } from './monaco-editor-input-bindings' +import { MONACO_PASTE_MAX_BYTES } from './monaco-large-text-paste' + +vi.mock('sonner', () => ({ toast: { error: vi.fn() } })) +vi.mock('@/store', () => ({ useAppStore: { getState: vi.fn() } })) +vi.mock('@/i18n/i18n', () => ({ translate: (_key: string, fallback: string) => fallback })) +vi.mock('./editor-shortcuts', () => ({ + installEditorSaveShortcut: () => () => undefined, + installMonacoEditorFindShortcut: () => () => undefined, + installEditorAddReviewNoteShortcut: () => () => undefined +})) + +afterEach(() => { + document.body.replaceChildren() + vi.clearAllMocks() +}) + +function mountEditor() { + const shell = document.createElement('div') + const container = document.createElement('div') + const editorInput = document.createElement('textarea') + const commentInput = document.createElement('textarea') + container.append(editorInput) + shell.append(container, commentInput) + document.body.append(shell) + let value = 'base' + // Mirrors Monaco's CopyPasteController, which claims paste on the container during construction. + container.addEventListener( + 'paste', + (event) => { + event.preventDefault() + event.stopImmediatePropagation() + value += event.clipboardData?.getData('text/plain') ?? '' + }, + { capture: true } + ) + const editorStub = { + getModel: () => ({ uri: { toString: () => 'file:///paste.txt' } }), + getValue: () => value, + getContainerDomNode: () => container, + hasTextFocus: () => true, + addAction: () => ({ dispose: () => undefined }) + } + const onContentChange = vi.fn() + const binding = installMonacoEditorInputBindings({ + // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: an over-limit paste is rejected after only the methods stubbed above. + editorInstance: editorStub as unknown as editor.IStandaloneCodeEditor, + worktreeId: undefined, + editorContainerRef: { current: shell }, + propsRef: { + current: { + relativePath: 'paste.txt', + language: 'plaintext', + onSave: vi.fn(), + onContentChange + } + }, + readOnlyRef: { current: false }, + commentPopoverRef: { current: null }, + shouldShowMarkdownAnnotationsRef: { current: false }, + setCommentPopover: vi.fn(), + setSelectionAnnotationTarget: vi.fn() + }) + const paste = (target: HTMLElement, text: string): ClipboardEvent => { + const clipboardData = new DataTransfer() + clipboardData.setData('text/plain', text) + const event = new ClipboardEvent('paste', { bubbles: true, cancelable: true, clipboardData }) + target.dispatchEvent(event) + return event + } + return { editorInput, commentInput, binding, paste, getValue: () => value, onContentChange } +} + +it('rejects over-limit paste before Monaco container capture handler can claim it', async () => { + const editorMount = mountEditor() + editorMount.paste(editorMount.editorInput, 'x'.repeat(MONACO_PASTE_MAX_BYTES + 1)) + await vi.waitFor(() => expect(toast.error).toHaveBeenCalledWith('Paste is too large.')) + expect(editorMount.getValue()).toBe('base') + expect(editorMount.onContentChange).not.toHaveBeenCalled() + editorMount.binding.disposeInputBindings() +}) + +it('leaves comment-overlay paste outside the editor route', () => { + const editorMount = mountEditor() + const event = editorMount.paste(editorMount.commentInput, 'x'.repeat(MONACO_PASTE_MAX_BYTES + 1)) + expect(event.defaultPrevented).toBe(false) + editorMount.binding.disposeInputBindings() +}) diff --git a/src/renderer/src/components/editor/monaco-editor-input-bindings.ts b/src/renderer/src/components/editor/monaco-editor-input-bindings.ts index 2519fe7eb29..05373268485 100644 --- a/src/renderer/src/components/editor/monaco-editor-input-bindings.ts +++ b/src/renderer/src/components/editor/monaco-editor-input-bindings.ts @@ -14,8 +14,7 @@ import { getMonacoMarkdownSelectionAnnotationTarget, type MonacoMarkdownSelectionAnnotationTarget } from './monaco-markdown-selection-annotation' -import { handleMonacoLargeTextPaste } from './monaco-large-text-paste' -import { beginEditorLargePaste } from './editor-large-paste-in-flight' +import { handleMonacoLargeTextPaste, MONACO_PASTE_MAX_BYTES } from './monaco-large-text-paste' import type { MarkdownCommentPopoverState } from './use-monaco-markdown-annotations' import type { MonacoEditorPropsRef } from './monaco-editor-mount-params' @@ -25,8 +24,6 @@ type MonacoEditorInputBindingsParams = { editorContainerRef: MutableRefObject propsRef: MonacoEditorPropsRef readOnlyRef: MutableRefObject - lastSyncedContentRef: MutableRefObject - isApplyingLargePasteRef: MutableRefObject commentPopoverRef: MutableRefObject shouldShowMarkdownAnnotationsRef: MutableRefObject setCommentPopover: Dispatch> @@ -45,8 +42,6 @@ export function installMonacoEditorInputBindings(params: MonacoEditorInputBindin editorContainerRef, propsRef, readOnlyRef, - lastSyncedContentRef, - isApplyingLargePasteRef, commentPopoverRef, shouldShowMarkdownAnnotationsRef, setCommentPopover, @@ -54,6 +49,7 @@ export function installMonacoEditorInputBindings(params: MonacoEditorInputBindin } = params const editorDomNode = editorInstance.getContainerDomNode() + const pasteCaptureNode = editorContainerRef.current ?? editorDomNode const cleanupSaveShortcut = installEditorSaveShortcut(editorDomNode, () => { const value = editorInstance.getValue() propsRef.current.onSave(value) @@ -103,22 +99,14 @@ export function installMonacoEditorInputBindings(params: MonacoEditorInputBindin } }) const onLargeTextPaste = (event: ClipboardEvent): void => { - let endSaveDeferral: (() => void) | null = null + if (!event.composedPath().includes(editorDomNode)) { + return + } + // Why: chunking re-serializes the document per chunk via @monaco-editor/react's onChange, so only enforce the cap. handleMonacoLargeTextPaste(editorInstance, event, { readOnly: readOnlyRef.current, - onPasteStart: () => { - isApplyingLargePasteRef.current = true - const modelUri = editorInstance.getModel()?.uri.toString() - endSaveDeferral = modelUri ? beginEditorLargePaste(modelUri) : null - }, + directMaxBytes: MONACO_PASTE_MAX_BYTES, onPasteResult: (result) => { - isApplyingLargePasteRef.current = false - endSaveDeferral?.() - if (result.status === 'pasted' || result.status === 'cancelled') { - const value = editorInstance.getValue() - lastSyncedContentRef.current = value - propsRef.current.onContentChange(value) - } if (result.status === 'rejected' && result.reason === 'too-large') { toast.error( translate( @@ -130,14 +118,14 @@ export function installMonacoEditorInputBindings(params: MonacoEditorInputBindin } }) } - editorDomNode.addEventListener('paste', onLargeTextPaste, { capture: true }) + pasteCaptureNode.addEventListener('paste', onLargeTextPaste, { capture: true }) return { disposeInputBindings: () => { cleanupSaveShortcut() cleanupFindShortcut() cleanupAddReviewNoteShortcut() - editorDomNode.removeEventListener('paste', onLargeTextPaste, { capture: true }) + pasteCaptureNode.removeEventListener('paste', onLargeTextPaste, { capture: true }) searchInFilesAction.dispose() } } diff --git a/src/renderer/src/components/editor/monaco-editor-mount-params.ts b/src/renderer/src/components/editor/monaco-editor-mount-params.ts index bb231e914af..756c4de4316 100644 --- a/src/renderer/src/components/editor/monaco-editor-mount-params.ts +++ b/src/renderer/src/components/editor/monaco-editor-mount-params.ts @@ -48,7 +48,6 @@ export type MonacoEditorMountParams = { lastSyncedContentRef: MutableRefObject contentSyncModeRef: MutableRefObject isApplyingProgrammaticContentRef: MutableRefObject - isApplyingLargePasteRef: MutableRefObject } decorations: { markdownDocLinkDecorationsRef: MutableRefObject diff --git a/src/renderer/src/components/editor/monaco-large-text-paste.ts b/src/renderer/src/components/editor/monaco-large-text-paste.ts index 2018b612681..624272133aa 100644 --- a/src/renderer/src/components/editor/monaco-large-text-paste.ts +++ b/src/renderer/src/components/editor/monaco-large-text-paste.ts @@ -212,7 +212,8 @@ export function handleMonacoLargeTextPaste( if (options.readOnly) { return { status: 'ignored', reason: 'read-only' } } - if (!monacoEditor?.getModel()) { + // Why: find/replace/rename overlay inputs sit inside the editor container. + if (!monacoEditor?.getModel() || !monacoEditor.hasTextFocus()) { return { status: 'ignored', reason: 'no-editor' } } diff --git a/src/renderer/src/components/editor/use-monaco-content-sync-bridge.ts b/src/renderer/src/components/editor/use-monaco-content-sync-bridge.ts index ca519d150d3..6a20cd74ae1 100644 --- a/src/renderer/src/components/editor/use-monaco-content-sync-bridge.ts +++ b/src/renderer/src/components/editor/use-monaco-content-sync-bridge.ts @@ -13,7 +13,6 @@ export type MonacoContentSyncBridge = { lastSyncedContentRef: MutableRefObject contentSyncModeRef: MutableRefObject isApplyingProgrammaticContentRef: MutableRefObject - isApplyingLargePasteRef: MutableRefObject handleChange: (value: string | undefined) => void } @@ -35,7 +34,6 @@ export function useMonacoContentSyncBridge(params: { // Suppress reconciliation’s real edit events so programmatic updates do not mark the file dirty. const isApplyingProgrammaticContentRef = useRef(false) - const isApplyingLargePasteRef = useRef(false) const handleChange = useCallback( (value: string | undefined) => { @@ -43,11 +41,6 @@ export function useMonacoContentSyncBridge(params: { if (editorRef.current?.getModel()?.uri.toString() !== modelKey) { return } - // Publish chunked pastes only on completion instead of scheduling saves for intermediate content. - if (isApplyingLargePasteRef.current) { - lastSyncedContentRef.current = value - return - } if ( shouldIgnoreMonacoContentChange({ modelKey, @@ -90,7 +83,6 @@ export function useMonacoContentSyncBridge(params: { lastSyncedContentRef, contentSyncModeRef, isApplyingProgrammaticContentRef, - isApplyingLargePasteRef, handleChange } } diff --git a/src/renderer/src/components/editor/use-monaco-editor-mount.cursor-owner.test.tsx b/src/renderer/src/components/editor/use-monaco-editor-mount.cursor-owner.test.tsx index 622752fda1d..7b5f38015bd 100644 --- a/src/renderer/src/components/editor/use-monaco-editor-mount.cursor-owner.test.tsx +++ b/src/renderer/src/components/editor/use-monaco-editor-mount.cursor-owner.test.tsx @@ -70,8 +70,7 @@ function mountParams( contentRef: { current: '' }, lastSyncedContentRef: { current: '' }, contentSyncModeRef: { current: 'undoable' }, - isApplyingProgrammaticContentRef: { current: false }, - isApplyingLargePasteRef: { current: false } + isApplyingProgrammaticContentRef: { current: false } }, decorations: { markdownDocLinkDecorationsRef: { current: null }, diff --git a/src/renderer/src/components/editor/use-monaco-editor-mount.stale-cache.test.tsx b/src/renderer/src/components/editor/use-monaco-editor-mount.stale-cache.test.tsx index 99228515f2e..a8b83c03ebd 100644 --- a/src/renderer/src/components/editor/use-monaco-editor-mount.stale-cache.test.tsx +++ b/src/renderer/src/components/editor/use-monaco-editor-mount.stale-cache.test.tsx @@ -77,8 +77,7 @@ function setupMount() { contentRef: { current: 'A (stale cache)' }, lastSyncedContentRef: { current: 'A (stale cache)' }, contentSyncModeRef: { current: 'undoable' }, - isApplyingProgrammaticContentRef: { current: false }, - isApplyingLargePasteRef: { current: false } + isApplyingProgrammaticContentRef: { current: false } }, decorations: { markdownDocLinkDecorationsRef: { current: null }, diff --git a/src/renderer/src/components/editor/use-monaco-editor-mount.ts b/src/renderer/src/components/editor/use-monaco-editor-mount.ts index 7ebbff138fa..4bbec181823 100644 --- a/src/renderer/src/components/editor/use-monaco-editor-mount.ts +++ b/src/renderer/src/components/editor/use-monaco-editor-mount.ts @@ -56,8 +56,7 @@ export function useMonacoEditorMount(params: MonacoEditorMountParams): OnMount { contentRef, lastSyncedContentRef, contentSyncModeRef, - isApplyingProgrammaticContentRef, - isApplyingLargePasteRef + isApplyingProgrammaticContentRef }, decorations: { markdownDocLinkDecorationsRef, @@ -163,8 +162,6 @@ export function useMonacoEditorMount(params: MonacoEditorMountParams): OnMount { editorContainerRef, propsRef, readOnlyRef, - lastSyncedContentRef, - isApplyingLargePasteRef, commentPopoverRef, shouldShowMarkdownAnnotationsRef, setCommentPopover, @@ -288,7 +285,6 @@ export function useMonacoEditorMount(params: MonacoEditorMountParams): OnMount { lastSyncedContentRef, contentSyncModeRef, isApplyingProgrammaticContentRef, - isApplyingLargePasteRef, markdownDocLinkDecorationsRef, conflictDecorationsRef, commentPopoverRef,