codex-overleaf-link 2.4.1 → 2.5.1
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/README.md +275 -305
- package/README.zh-CN.md +290 -320
- package/extension/bootstrap/background.js +99 -19
- package/extension/bootstrap/manifest.template.json +9 -4
- package/extension/bootstrap/popup.html +11 -16
- package/extension/bootstrap/popupFallback.js +55 -0
- package/extension/bootstrap/update.js +25 -2
- package/extension/bootstrap/updateStatus.js +1 -0
- package/extension/runtime-manifest.json +3 -1
- package/extension/src/background.js +234 -56
- package/extension/src/backgroundBootstrapMigration.js +181 -0
- package/extension/src/backgroundSharedSessions.js +440 -0
- package/extension/src/backgroundUpdateCoordinator.js +541 -381
- package/extension/src/backgroundUpdateSafety.js +173 -0
- package/extension/src/content/activeTurnControl.js +16 -2
- package/extension/src/content/applyResultFormatters.js +46 -73
- package/extension/src/content/assetTransferBroker.js +244 -12
- package/extension/src/content/changeHistoryView.js +175 -0
- package/extension/src/content/composerAttachments.js +117 -67
- package/extension/src/content/composerPanel.js +41 -8
- package/extension/src/content/contentRuntime.js +1084 -1099
- package/extension/src/content/diagnosticsController.js +122 -97
- package/extension/src/content/diagnosticsExport.js +236 -0
- package/extension/src/content/diagnosticsPanel.js +78 -19
- package/extension/src/content/diffReviewPanel.js +8 -5
- package/extension/src/content/directoryCleanupRecovery.js +54 -0
- package/extension/src/content/generated/content.bundle.js +14680 -3606
- package/extension/src/content/generated/content.bundle.meta.json +30 -3
- package/extension/src/content/globalPreferencesController.js +1 -1
- package/extension/src/content/legacyGlobalRegistry.js +25 -1
- package/extension/src/content/localSkillsPanel.js +141 -16
- package/extension/src/content/markdownDomRenderer.js +9 -4
- package/extension/src/content/markdownText.js +5 -6
- package/extension/src/content/modelPicker.js +8 -5
- package/extension/src/content/nativeCompatibilityController.js +45 -20
- package/extension/src/content/otWarmMirror.js +4 -5
- package/extension/src/content/otWarmMirrorController.js +17 -20
- package/extension/src/content/pageBridgeClient.js +33 -5
- package/extension/src/content/panelMaintenance.js +87 -132
- package/extension/src/content/panelRenderer.js +10 -0
- package/extension/src/content/pendingInputView.js +109 -76
- package/extension/src/content/projectProviderSelection.js +13 -4
- package/extension/src/content/projectSessionCleanup.js +109 -62
- package/extension/src/content/projectSettingsCoordinator.js +39 -2
- package/extension/src/content/providerSettingsCoordinator.js +63 -14
- package/extension/src/content/providerSettingsDialog.js +724 -113
- package/extension/src/content/recentProjects.js +21 -6
- package/extension/src/content/runActivitySummary.js +665 -0
- package/extension/src/content/runCompletionMeta.js +344 -0
- package/extension/src/content/runController.js +40 -15
- package/extension/src/content/runFailureNotice.js +34 -12
- package/extension/src/content/runGuidanceController.js +76 -30
- package/extension/src/content/runGuidanceView.js +59 -10
- package/extension/src/content/runPresence.js +306 -0
- package/extension/src/content/runQueueScheduler.js +150 -43
- package/extension/src/content/runRecoveryActions.js +146 -0
- package/extension/src/content/runResultActions.js +11 -8
- package/extension/src/content/runReviewController.js +574 -0
- package/extension/src/content/runScrollLayout.js +21 -2
- package/extension/src/content/runTimelineView.js +315 -290
- package/extension/src/content/scopedPersistencePanelState.js +51 -6
- package/extension/src/content/selectionContextView.js +300 -0
- package/extension/src/content/sensitivePreflight.js +140 -0
- package/extension/src/content/sessionForkController.js +80 -17
- package/extension/src/content/sessionManager.js +1 -1
- package/extension/src/content/sessionPersistence.js +6 -1
- package/extension/src/content/settingsPanel.js +84 -55
- package/extension/src/content/settingsWorkbench.js +379 -0
- package/extension/src/content/sharedSessionViewSync.js +109 -0
- package/extension/src/content/subagentActivityView.js +195 -0
- package/extension/src/content/trackedChangeCaptureController.js +7 -3
- package/extension/src/content/updateIdle.js +4 -1
- package/extension/src/content/updateNotice.js +179 -56
- package/extension/src/content/updateSafetyReceiver.js +58 -0
- package/extension/src/content/writebackController.js +308 -2
- package/extension/src/content/writebackOrchestrator.js +213 -199
- package/extension/src/content/writebackPlan.js +231 -0
- package/extension/src/content/writebackRecovery.js +122 -0
- package/extension/src/content/writingStyleSettings.js +600 -0
- package/extension/src/content/writtenChangesView.js +152 -0
- package/extension/src/page/binaryAssetUploader.js +223 -23
- package/extension/src/page/fileMover.js +256 -0
- package/extension/src/page/overleafProjectSnapshot.js +1 -0
- package/extension/src/page/ownedDirectoryCleanup.js +94 -0
- package/extension/src/page/referenceProjects.js +149 -0
- package/extension/src/page/reviewContext.js +113 -0
- package/extension/src/page/saveState.js +20 -6
- package/extension/src/page/snapshotRouter.js +124 -48
- package/extension/src/page/textCoordinates.js +102 -0
- package/extension/src/page/textFileCreator.js +442 -71
- package/extension/src/page/trackedChangeCapture.js +53 -57
- package/extension/src/page/trackedChangeOwnership.js +216 -0
- package/extension/src/page/trackedChangeReplay.js +184 -0
- package/extension/src/page/trackedChangesLifecycle.js +343 -339
- package/extension/src/page/treeOperations.js +33 -12
- package/extension/src/page/updateSafetyProbe.js +41 -0
- package/extension/src/page/writebackReceiptJournal.js +114 -0
- package/extension/src/page/writebackRouter.js +242 -201
- package/extension/src/pageBridge.js +73 -102
- package/extension/src/popup.js +261 -65
- package/extension/src/shared/agentTranscript.js +41 -4
- package/extension/src/shared/auditRecords.js +73 -0
- package/extension/src/shared/changeHistoryModel.js +94 -0
- package/extension/src/shared/compatibility.js +22 -6
- package/extension/src/shared/compileAdapter.js +101 -37
- package/extension/src/shared/composerAttachmentDraft.js +151 -0
- package/extension/src/shared/directoryCleanupProof.js +43 -0
- package/extension/src/shared/failureReasons.js +329 -3
- package/extension/src/shared/globalPreferences.js +31 -14
- package/extension/src/shared/governanceRules.js +4 -37
- package/extension/src/shared/i18n.js +389 -25
- package/extension/src/shared/managedUpdateProjection.js +8 -5
- package/extension/src/shared/nativeRequestIdentity.js +1 -0
- package/extension/src/shared/pageRpcContract.js +9 -2
- package/extension/src/shared/projectFiles.js +21 -44
- package/extension/src/shared/providerProfiles.js +114 -3
- package/extension/src/shared/runActivityModel.js +361 -0
- package/extension/src/shared/runExecutionSnapshot.js +8 -2
- package/extension/src/shared/runExecutionSnapshotCodec.js +16 -4
- package/extension/src/shared/runInputQueue.js +48 -5
- package/extension/src/shared/scopedPersistenceBrowserAdapter.js +6 -3
- package/extension/src/shared/selectionContext.js +37 -0
- package/extension/src/shared/sessionState.js +166 -30
- package/extension/src/shared/settlementFacts.js +10 -2
- package/extension/src/shared/sharedSessionBridge.js +311 -0
- package/extension/src/shared/sharedSessionTransfer.js +140 -0
- package/extension/src/shared/staleGuard.js +118 -13
- package/extension/src/shared/storageDb.js +77 -78
- package/extension/src/shared/storageMigration.js +74 -111
- package/extension/src/shared/storageRunActions.js +211 -22
- package/extension/src/shared/storageValuePruning.js +40 -0
- package/extension/src/shared/subagentActivityModel.js +174 -0
- package/extension/src/shared/undoOperations.js +216 -6
- package/extension/src/shared/updateConsent.js +42 -11
- package/extension/src/shared/updateRuntimeIdentity.js +1 -1
- package/extension/src/shared/writebackIntent.js +78 -0
- package/extension/src/shared/writebackOperationEvidence.js +4 -15
- package/extension/src/shared/writebackSettlement.js +67 -48
- package/extension/src/shared/writingStyle.js +33 -0
- package/extension/styles/panel.css +1987 -385
- package/extension/styles/popup.css +110 -0
- package/native-host/src/anthropicBridgeRequest.js +6 -3
- package/native-host/src/anthropicBridgeResponse.js +6 -2
- package/native-host/src/anthropicMessagesBridge.js +24 -16
- package/native-host/src/bootstrapMigration.js +296 -0
- package/native-host/src/bootstrapMigrationContract.js +101 -0
- package/native-host/src/bootstrapUpdate.js +233 -0
- package/native-host/src/bootstrapUpdateWatchdog.js +44 -0
- package/native-host/src/chatBridgeResponse.js +10 -0
- package/native-host/src/chatCompletionsBridge.js +21 -15
- package/native-host/src/codexConfigPaths.js +106 -0
- package/native-host/src/codexHome.js +66 -73
- package/native-host/src/codexHomeScope.js +124 -0
- package/native-host/src/codexInheritedAuth.js +90 -0
- package/native-host/src/codexLocalSkills.js +301 -0
- package/native-host/src/codexModelRecovery.js +107 -0
- package/native-host/src/codexPromptAssembly.js +33 -12
- package/native-host/src/codexProviderLaunch.js +3 -3
- package/native-host/src/codexProviderTest.js +24 -26
- package/native-host/src/codexRuntimeIdentity.js +1 -0
- package/native-host/src/codexSessionRunner.js +211 -318
- package/native-host/src/codexSessionTiming.js +21 -8
- package/native-host/src/codexSkillIsolation.js +170 -0
- package/native-host/src/codexThreadFork.js +19 -3
- package/native-host/src/index.js +19 -7
- package/native-host/src/managedInstall.js +115 -39
- package/native-host/src/managedInstallRecovery.js +140 -0
- package/native-host/src/managedRuntimeEvidence.js +57 -0
- package/native-host/src/mirrorWorkspace.js +92 -22
- package/native-host/src/nativeDoctor.js +15 -0
- package/native-host/src/nativeEnvironment.js +70 -0
- package/native-host/src/nativeHelperLifecycle.js +21 -0
- package/native-host/src/nativeHostRegistration.js +33 -0
- package/native-host/src/nativeQuotas.js +15 -8
- package/native-host/src/nativeRunLifecycle.js +71 -0
- package/native-host/src/overleafWorkspaceRules.js +16 -0
- package/native-host/src/processStopBarrier.js +115 -0
- package/native-host/src/projectPathPlatform.js +30 -0
- package/native-host/src/providerHttp.js +80 -0
- package/native-host/src/providerModelCatalog.js +4 -3
- package/native-host/src/providerProfile.js +17 -3
- package/native-host/src/providerReasoning.js +10 -11
- package/native-host/src/providerRuntime.js +1 -0
- package/native-host/src/providerStore.js +76 -14
- package/native-host/src/releaseTransport.js +14 -5
- package/native-host/src/responsesPassthroughBridge.js +266 -52
- package/native-host/src/runtimeInstaller.js +21 -1
- package/native-host/src/selectionScope.js +86 -0
- package/native-host/src/skills/parallel-subagents/SKILL.md +31 -3
- package/native-host/src/skills/writing-style-builder/SKILL.md +146 -0
- package/native-host/src/subagentAdmission.js +26 -0
- package/native-host/src/subagentBroker.js +176 -159
- package/native-host/src/subagentTelemetry.js +325 -0
- package/native-host/src/subagentWorkspace.js +103 -0
- package/native-host/src/subagentWorkspacePath.js +34 -0
- package/native-host/src/taskRunnerRuntime.js +130 -57
- package/native-host/src/tomlStatements.js +140 -0
- package/native-host/src/turnAttachments.js +113 -0
- package/native-host/src/updateManager.js +406 -38
- package/native-host/src/updateNetworkControl.js +75 -0
- package/native-host/src/updateTrust.js +21 -2
- package/native-host/src/vendor/pdfjs/LICENSE +177 -0
- package/native-host/src/vendor/pdfjs/SOURCE.txt +3 -0
- package/native-host/src/vendor/pdfjs/cmaps/78-EUC-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/78-EUC-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/78-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/78-RKSJ-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/78-RKSJ-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/78-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/78ms-RKSJ-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/78ms-RKSJ-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/83pv-RKSJ-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/90ms-RKSJ-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/90ms-RKSJ-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/90msp-RKSJ-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/90msp-RKSJ-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/90pv-RKSJ-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/90pv-RKSJ-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Add-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Add-RKSJ-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Add-RKSJ-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Add-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-0.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-1.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-2.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-3.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-4.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-5.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-6.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-UCS2.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-0.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-1.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-2.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-3.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-4.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-5.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-UCS2.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-0.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-1.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-2.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-3.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-4.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-5.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-6.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-UCS2.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-Korea1-0.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-Korea1-1.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-Korea1-2.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Adobe-Korea1-UCS2.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/B5-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/B5-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/B5pc-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/B5pc-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/CNS-EUC-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/CNS-EUC-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/CNS1-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/CNS1-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/CNS2-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/CNS2-V.bcmap +3 -0
- package/native-host/src/vendor/pdfjs/cmaps/ETHK-B5-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/ETHK-B5-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/ETen-B5-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/ETen-B5-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/ETenms-B5-H.bcmap +3 -0
- package/native-host/src/vendor/pdfjs/cmaps/ETenms-B5-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/EUC-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/EUC-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Ext-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Ext-RKSJ-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Ext-RKSJ-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Ext-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/GB-EUC-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/GB-EUC-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/GB-H.bcmap +4 -0
- package/native-host/src/vendor/pdfjs/cmaps/GB-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/GBK-EUC-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/GBK-EUC-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/GBK2K-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/GBK2K-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/GBKp-EUC-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/GBKp-EUC-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/GBT-EUC-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/GBT-EUC-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/GBT-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/GBT-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/GBTpc-EUC-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/GBTpc-EUC-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/GBpc-EUC-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/GBpc-EUC-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/HKdla-B5-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/HKdla-B5-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/HKdlb-B5-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/HKdlb-B5-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/HKgccs-B5-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/HKgccs-B5-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/HKm314-B5-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/HKm314-B5-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/HKm471-B5-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/HKm471-B5-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/HKscs-B5-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/HKscs-B5-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Hankaku.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Hiragana.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/KSC-EUC-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/KSC-EUC-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/KSC-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/KSC-Johab-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/KSC-Johab-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/KSC-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/KSCms-UHC-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/KSCms-UHC-HW-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/KSCms-UHC-HW-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/KSCms-UHC-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/KSCpc-EUC-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/KSCpc-EUC-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Katakana.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/LICENSE +36 -0
- package/native-host/src/vendor/pdfjs/cmaps/NWP-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/NWP-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/RKSJ-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/RKSJ-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/Roman.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UCS2-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UCS2-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF16-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF16-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF32-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF32-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF8-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF8-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniGB-UCS2-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniGB-UCS2-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF16-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF16-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF32-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF32-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF8-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF8-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UCS2-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UCS2-HW-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UCS2-HW-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UCS2-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF16-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF16-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF32-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF32-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF8-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF8-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF16-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF16-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF32-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF32-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF8-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF8-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJISPro-UCS2-HW-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJISPro-UCS2-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJISPro-UTF8-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJISX0213-UTF32-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJISX0213-UTF32-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJISX02132004-UTF32-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniJISX02132004-UTF32-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniKS-UCS2-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniKS-UCS2-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF16-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF16-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF32-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF32-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF8-H.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF8-V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/V.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/cmaps/WP-Symbol.bcmap +0 -0
- package/native-host/src/vendor/pdfjs/pdf.mjs +26740 -0
- package/native-host/src/vendor/pdfjs/pdf.worker.mjs +62201 -0
- package/native-host/src/vendor/pdfjs/standard_fonts/FoxitDingbats.pfb +0 -0
- package/native-host/src/vendor/pdfjs/standard_fonts/FoxitFixed.pfb +0 -0
- package/native-host/src/vendor/pdfjs/standard_fonts/FoxitFixedBold.pfb +0 -0
- package/native-host/src/vendor/pdfjs/standard_fonts/FoxitFixedBoldItalic.pfb +0 -0
- package/native-host/src/vendor/pdfjs/standard_fonts/FoxitFixedItalic.pfb +0 -0
- package/native-host/src/vendor/pdfjs/standard_fonts/FoxitSerif.pfb +0 -0
- package/native-host/src/vendor/pdfjs/standard_fonts/FoxitSerifBold.pfb +0 -0
- package/native-host/src/vendor/pdfjs/standard_fonts/FoxitSerifBoldItalic.pfb +0 -0
- package/native-host/src/vendor/pdfjs/standard_fonts/FoxitSerifItalic.pfb +0 -0
- package/native-host/src/vendor/pdfjs/standard_fonts/FoxitSymbol.pfb +0 -0
- package/native-host/src/vendor/pdfjs/standard_fonts/LICENSE_FOXIT +27 -0
- package/native-host/src/vendor/pdfjs/standard_fonts/LICENSE_LIBERATION +102 -0
- package/native-host/src/vendor/pdfjs/standard_fonts/LiberationSans-Bold.ttf +0 -0
- package/native-host/src/vendor/pdfjs/standard_fonts/LiberationSans-BoldItalic.ttf +0 -0
- package/native-host/src/vendor/pdfjs/standard_fonts/LiberationSans-Italic.ttf +0 -0
- package/native-host/src/vendor/pdfjs/standard_fonts/LiberationSans-Regular.ttf +0 -0
- package/native-host/src/writingStyleEvidence.js +102 -0
- package/native-host/src/writingStylePdfWorker.js +35 -0
- package/native-host/src/writingStyleProfile.js +165 -0
- package/native-host/src/writingStyleRuntime.js +475 -0
- package/native-host/src/writingStyleStore.js +73 -0
- package/package.json +1 -1
- package/scripts/install-managed.mjs +1 -0
|
@@ -1,20 +1,18 @@
|
|
|
1
1
|
(function initCodexOverleafTrackedChangesLifecycle(root, factory) {
|
|
2
2
|
if (typeof module === 'object' && module.exports) {
|
|
3
|
-
module.exports = factory();
|
|
3
|
+
module.exports = factory(require('./trackedChangeReplay'), require('./reviewContext'));
|
|
4
4
|
} else {
|
|
5
|
-
root.CodexOverleafTrackedChangesLifecycle = factory();
|
|
5
|
+
root.CodexOverleafTrackedChangesLifecycle = factory(root.CodexOverleafTrackedChangeReplay, root.CodexOverleafReviewContext);
|
|
6
6
|
}
|
|
7
|
-
})(typeof window !== 'undefined' ? window : globalThis, function trackedChangesLifecycleFactory() {
|
|
7
|
+
})(typeof window !== 'undefined' ? window : globalThis, function trackedChangesLifecycleFactory(Replay, ReviewContext) {
|
|
8
8
|
'use strict';
|
|
9
|
-
|
|
10
|
-
// Tracked-changes lifecycle — carved out of writebackRouter.js in v1.8.0
|
|
11
|
-
// (structural-debt phase 7, #117): the accept/reject flows, the
|
|
12
|
-
// accept-replay + editor-undo recovery machinery, the reviewing/editing
|
|
13
|
-
// blocked-result builders, and the DOM collectors for Overleaf's
|
|
14
|
-
// tracked-change widgets. Code moved verbatim (original indentation kept);
|
|
15
|
-
// page-side collaborators are factory-injected by writebackRouter, which
|
|
16
|
-
// remains the only consumer and the owner of applyOperations itself.
|
|
17
9
|
function create(deps = {}) {
|
|
10
|
+
const base = createCore(deps);
|
|
11
|
+
const run = (method, params) => ReviewContext.create(deps, params).run((scoped, input) => createCore(scoped)[method](input));
|
|
12
|
+
return { ...base, rejectTrackedChanges: params => run('rejectTrackedChanges', params),
|
|
13
|
+
acceptTrackedChanges: params => run('acceptTrackedChanges', params) };
|
|
14
|
+
}
|
|
15
|
+
function createCore(deps = {}) {
|
|
18
16
|
const {
|
|
19
17
|
window,
|
|
20
18
|
checkWritebackRunProjectId,
|
|
@@ -41,7 +39,7 @@
|
|
|
41
39
|
normalizeOperationPaths,
|
|
42
40
|
normalizeReviewingSignalText,
|
|
43
41
|
normalizeSafeProjectPath,
|
|
44
|
-
openFileByPath,
|
|
42
|
+
openFileByPath: openReviewFileByPath,
|
|
45
43
|
readActiveEditorText,
|
|
46
44
|
readNodeSignalText,
|
|
47
45
|
setReviewingEnabled,
|
|
@@ -49,10 +47,19 @@
|
|
|
49
47
|
verifyActiveEditorText,
|
|
50
48
|
collectElements,
|
|
51
49
|
} = deps;
|
|
50
|
+
const { buildAcceptReplayOperations, rebaseCheckpointPair, prepareSnapshotUndoCheckpoints, buildSnapshotUndoResult } = Replay.create({
|
|
51
|
+
normalizeSafeProjectPath, applyTextPatches
|
|
52
|
+
});
|
|
53
|
+
function openFileByPath(path, ...options) {
|
|
54
|
+
if (path !== getActiveFilePath()) rememberTrackedChangeNodeContext(getActiveFilePath());
|
|
55
|
+
return openReviewFileByPath(path, ...options);
|
|
56
|
+
}
|
|
52
57
|
|
|
53
58
|
function nativeReviewBlocked(path, code, applied = [], requestStarted = false) {
|
|
54
59
|
const reason = requestStarted
|
|
55
60
|
? 'The native review request could not be fully verified; some target changes may already be accepted. No text replay was attempted. Check Overleaf Review before retrying.'
|
|
61
|
+
: code === 'native_untracked_undo_pending_changes'
|
|
62
|
+
? 'This file has unresolved tracked changes. Undo without track changes was stopped to preserve them; no document changes were made.'
|
|
56
63
|
: 'The native tracked-change scope could not be safely isolated. No review mutation was attempted. Use Overleaf Review to handle the changes individually.';
|
|
57
64
|
return { ok: false, applied, skipped: [{ trackedChange: { path }, result: {
|
|
58
65
|
ok: false, code, reason, failure: {
|
|
@@ -65,14 +72,15 @@
|
|
|
65
72
|
} }] };
|
|
66
73
|
}
|
|
67
74
|
|
|
68
|
-
async function waitForNativeReview(path, refs, untracked = false) {
|
|
75
|
+
async function waitForNativeReview(path, refs, untracked = false, checkpointReady = () => true) {
|
|
69
76
|
const prepare = () => untracked ? prepareUntrackedUndo(path) : prepareTrackedChangeReview(path, refs);
|
|
70
77
|
for (let attempt = 0; attempt < 16; attempt++) {
|
|
71
78
|
const scope = prepare();
|
|
72
|
-
if (scope.ok) return scope;
|
|
79
|
+
if (scope.ok && checkpointReady(scope)) return scope;
|
|
73
80
|
await delay(150);
|
|
74
81
|
}
|
|
75
|
-
|
|
82
|
+
const scope = prepare();
|
|
83
|
+
return scope.ok && !checkpointReady(scope) ? { ...scope, ok: false } : scope;
|
|
76
84
|
}
|
|
77
85
|
|
|
78
86
|
async function prepareNativeReview(params, allowUntrackedUndo = false) {
|
|
@@ -96,7 +104,7 @@
|
|
|
96
104
|
...checkpoints.map(file => normalizeSafeProjectPath(file?.path))])];
|
|
97
105
|
if ((!refs.length && !untracked) || refs.some(ref => ref.invalidProjectPath || !ref.path) || paths.some(path => !path))
|
|
98
106
|
return nativeReviewBlocked('', 'native_review_identity_unavailable');
|
|
99
|
-
const files = [], deadline = Date.now() + 90000;
|
|
107
|
+
const files = [], deadline = Math.min(Date.now() + 90000, deps.reviewDeadlineAt ?? Infinity);
|
|
100
108
|
for (const path of paths) {
|
|
101
109
|
if (Date.now() >= deadline || checkWritebackRunProjectId(params))
|
|
102
110
|
return nativeReviewBlocked(path, 'native_review_context_changed');
|
|
@@ -104,13 +112,121 @@
|
|
|
104
112
|
return nativeReviewBlocked(path, 'native_review_file_unavailable');
|
|
105
113
|
const scope = await waitForNativeReview(path, refs, untracked);
|
|
106
114
|
if (!scope.ok) return nativeReviewBlocked(path, scope.reason || 'native_review_unavailable');
|
|
115
|
+
if (scope.merged) {
|
|
116
|
+
const before = (params.expectedFiles || []).filter(file => file.path === path);
|
|
117
|
+
const post = (params.postFiles || []).filter(file => file.path === path);
|
|
118
|
+
if (scope.merged.runProjectId !== params.runProjectId || before.length !== 1 || post.length !== 1
|
|
119
|
+
|| before[0].content !== scope.merged.preContent || post[0].content !== scope.merged.postContent)
|
|
120
|
+
return nativeReviewBlocked(path, 'native_merged_review_checkpoint_mismatch');
|
|
121
|
+
}
|
|
107
122
|
files.push({ path, nativeDocId: scope.nativeDocId, scope });
|
|
108
123
|
}
|
|
109
124
|
return { ok: true, files, refs, deadline };
|
|
110
125
|
}
|
|
111
126
|
|
|
127
|
+
function mergedReviewContextMatches(params, file, content) {
|
|
128
|
+
const projectId = typeof deps.getProjectId === 'function' ? deps.getProjectId()
|
|
129
|
+
: /\/project\/([^/?#]+)/.exec(window?.location?.pathname || '')?.[1];
|
|
130
|
+
return Boolean(params.runProjectId && projectId === params.runProjectId
|
|
131
|
+
&& !checkWritebackRunProjectId(params) && getActiveFilePath() === file.path
|
|
132
|
+
&& readActiveEditorText() === content);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async function reviewMergedInsertion(params, file, action) {
|
|
136
|
+
const path = file.path, accepting = action === 'accept';
|
|
137
|
+
let scope = prepareTrackedChangeReview(path, file.scope.targets);
|
|
138
|
+
if (!scope.ok || !scope.merged || scope.nativeDocId !== file.nativeDocId
|
|
139
|
+
|| !mergedReviewContextMatches(params, file, scope.merged.postContent))
|
|
140
|
+
return nativeReviewBlocked(path, 'native_merged_review_scope_changed');
|
|
141
|
+
const before = (params.expectedFiles || []).filter(item => item.path === path);
|
|
142
|
+
const post = (params.postFiles || []).filter(item => item.path === path);
|
|
143
|
+
if (before.length !== 1 || post.length !== 1
|
|
144
|
+
|| before[0].content !== scope.merged.preContent || post[0].content !== scope.merged.postContent)
|
|
145
|
+
return nativeReviewBlocked(path, 'native_merged_review_checkpoint_mismatch');
|
|
146
|
+
const view = deps.getCodeMirrorEditorView?.();
|
|
147
|
+
if (typeof view?.dispatch !== 'function' || view.state?.doc?.toString() !== post[0].content)
|
|
148
|
+
return nativeReviewBlocked(path, 'native_merged_review_editor_unavailable');
|
|
149
|
+
|
|
150
|
+
const editing = await forceEditingForAcceptReplay();
|
|
151
|
+
if (!editing.ok) return nativeReviewBlocked(path, 'native_merged_review_editing_unavailable');
|
|
152
|
+
let attempted = false;
|
|
153
|
+
try {
|
|
154
|
+
const stable = await waitForStableEditingForAcceptReplay({ waitMs: 1400, intervalMs: 160 });
|
|
155
|
+
scope = prepareTrackedChangeReview(path, file.scope.targets);
|
|
156
|
+
if (!stable.ok || !scope.ok || !scope.merged || scope.nativeDocId !== file.nativeDocId
|
|
157
|
+
|| !mergedReviewContextMatches(params, file, post[0].content)
|
|
158
|
+
|| deps.getCodeMirrorEditorView?.() !== view || view.state.doc.toString() !== post[0].content)
|
|
159
|
+
return nativeReviewBlocked(path, 'native_merged_review_scope_changed');
|
|
160
|
+
|
|
161
|
+
// A same-text replacement is deliberate: plain Editing removes tracking
|
|
162
|
+
// only from these exact inserted slices, without a transient text rollback.
|
|
163
|
+
// Never send a merged parent's ID to the whole-change Accept endpoint.
|
|
164
|
+
const changes = scope.merged.ranges.map(range => ({
|
|
165
|
+
from: range.start, to: range.end,
|
|
166
|
+
insert: accepting ? post[0].content.slice(range.start, range.end) : ''
|
|
167
|
+
}));
|
|
168
|
+
const expectedText = accepting ? post[0].content : before[0].content;
|
|
169
|
+
const expectedKeys = accepting ? scope.merged.preservedKeys : scope.merged.beforeKeys;
|
|
170
|
+
if (deps.reviewGuard?.()) return nativeReviewBlocked(path, 'native_review_context_changed');
|
|
171
|
+
attempted = true;
|
|
172
|
+
view.dispatch({ changes });
|
|
173
|
+
compileBridge.markSourceEdited();
|
|
174
|
+
|
|
175
|
+
const until = Math.min(file.deadline || Date.now() + 8000, Date.now() + 8000);
|
|
176
|
+
let matchingSince = null;
|
|
177
|
+
while (Date.now() <= until) {
|
|
178
|
+
if (!mergedReviewContextMatches(params, file, expectedText))
|
|
179
|
+
return nativeReviewBlocked(path, 'native_merged_review_content_or_context_changed', [], true);
|
|
180
|
+
const after = prepareTrackedChangeCapture(path);
|
|
181
|
+
const matches = after.ready && after.source === 'native' && after.nativeDocId === file.nativeDocId
|
|
182
|
+
&& JSON.stringify(after.refs.map(ref => ref.key).sort()) === JSON.stringify(expectedKeys);
|
|
183
|
+
if (matches) {
|
|
184
|
+
if (matchingSince === null) matchingSince = Date.now();
|
|
185
|
+
if (Date.now() - matchingSince >= 500) {
|
|
186
|
+
return { ok: true, verified: true, applied: scope.targets.map(trackedChange => ({
|
|
187
|
+
trackedChange, result: { ok: true, verified: true, verifiedContent: expectedText,
|
|
188
|
+
method: accepting ? 'overleaf-accept-untracked-replay' : 'overleaf-merged-insertion-undo',
|
|
189
|
+
preservedTrackedChanges: true }
|
|
190
|
+
})), skipped: [] };
|
|
191
|
+
}
|
|
192
|
+
} else matchingSince = null;
|
|
193
|
+
await delay(150);
|
|
194
|
+
}
|
|
195
|
+
return nativeReviewBlocked(path, 'native_merged_review_not_verified', [], true);
|
|
196
|
+
} catch (_error) {
|
|
197
|
+
return nativeReviewBlocked(path, 'native_merged_review_interrupted', [], attempted);
|
|
198
|
+
} finally {
|
|
199
|
+
// Match the existing no-trace Accept policy: do not re-enable tracking
|
|
200
|
+
// while an untracked operation can still be awaiting its server flush.
|
|
201
|
+
if (!attempted && editing.activated) await setReviewingEnabled(true, { waitMs: 1800 });
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async function rejectPlanWithMergedInsertions(params, plan) {
|
|
206
|
+
const mixed = plan.files.find(file => !file.scope.merged && file.scope.unrelated.length);
|
|
207
|
+
if (mixed) return nativeReviewBlocked(mixed.path, 'native_reject_mixed_changes');
|
|
208
|
+
const applied = deps.createReviewApplied?.() || [];
|
|
209
|
+
for (const file of plan.files) {
|
|
210
|
+
if (Date.now() >= plan.deadline || checkWritebackRunProjectId(params))
|
|
211
|
+
return nativeReviewBlocked(file.path, 'native_review_context_changed', applied, applied.length > 0);
|
|
212
|
+
if (getActiveFilePath() !== file.path && !(await openFileByPath(file.path)).ok)
|
|
213
|
+
return nativeReviewBlocked(file.path, 'native_review_file_unavailable', applied, applied.length > 0);
|
|
214
|
+
const scoped = { ...params,
|
|
215
|
+
trackedChanges: plan.refs.filter(ref => ref.path === file.path),
|
|
216
|
+
expectedFiles: (params.expectedFiles || []).filter(item => item.path === file.path),
|
|
217
|
+
postFiles: (params.postFiles || []).filter(item => item.path === file.path)
|
|
218
|
+
};
|
|
219
|
+
const result = file.scope.merged
|
|
220
|
+
? await reviewMergedInsertion(scoped, { ...file, deadline: plan.deadline }, 'reject')
|
|
221
|
+
: await rejectTrackedChanges(scoped);
|
|
222
|
+
applied.push(...(result.applied || []));
|
|
223
|
+
if (!result.ok) return { ...result, applied };
|
|
224
|
+
}
|
|
225
|
+
return { ok: true, applied, skipped: [] };
|
|
226
|
+
}
|
|
227
|
+
|
|
112
228
|
async function acceptNativeReview(params, plan) {
|
|
113
|
-
const applied = [];
|
|
229
|
+
const applied = deps.createReviewApplied?.() || [];
|
|
114
230
|
let token = window?.document?.querySelector?.('meta[name="ol-csrfToken"]')?.getAttribute('content');
|
|
115
231
|
try { token = JSON.parse(token); } catch (_error) { /* Older pages expose plain text. */ }
|
|
116
232
|
if (typeof token !== 'string' || !token || token.length > 4096
|
|
@@ -126,6 +242,12 @@
|
|
|
126
242
|
return nativeReviewBlocked(path, 'native_review_context_changed', applied, applied.length > 0);
|
|
127
243
|
if (getActiveFilePath() !== path && !(await openFileByPath(path)).ok)
|
|
128
244
|
return nativeReviewBlocked(path, 'native_review_file_unavailable', applied, applied.length > 0);
|
|
245
|
+
if (file.scope.merged) {
|
|
246
|
+
const result = await reviewMergedInsertion(params, { ...file, deadline: plan.deadline }, 'accept');
|
|
247
|
+
applied.push(...(result.applied || []));
|
|
248
|
+
if (!result.ok) return { ...result, applied };
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
129
251
|
await waitForNativeReview(path, plan.refs);
|
|
130
252
|
const scope = prepareTrackedChangeReview(path, plan.refs), text = readActiveEditorText();
|
|
131
253
|
if (!scope.ok || !scope.acceptByIdSupported || scope.nativeDocId !== file.nativeDocId
|
|
@@ -135,6 +257,7 @@
|
|
|
135
257
|
const controller = new window.AbortController();
|
|
136
258
|
const timer = window.setTimeout(() => controller.abort(), Math.min(12000, Math.max(1, plan.deadline - Date.now())));
|
|
137
259
|
try {
|
|
260
|
+
if (deps.reviewGuard?.()) return nativeReviewBlocked(path, 'native_review_context_changed', applied, applied.length > 0);
|
|
138
261
|
// Same ID-scoped endpoint and CSRF header used by Overleaf's RangesProvider.
|
|
139
262
|
const response = await window.fetch('/project/' + encodeURIComponent(params.runProjectId)
|
|
140
263
|
+ '/doc/' + encodeURIComponent(scope.nativeDocId) + '/changes/accept', {
|
|
@@ -142,6 +265,7 @@
|
|
|
142
265
|
headers: { 'Content-Type': 'application/json', Accept: 'application/json', 'X-Csrf-Token': token },
|
|
143
266
|
body: JSON.stringify({ change_ids: scope.ids })
|
|
144
267
|
});
|
|
268
|
+
if (deps.reviewGuard?.()) return nativeReviewBlocked(path, 'native_review_context_changed', applied, true);
|
|
145
269
|
if (!response.ok) return nativeReviewBlocked(path, 'native_accept_http_' + response.status, applied, true);
|
|
146
270
|
} catch (_error) {
|
|
147
271
|
return nativeReviewBlocked(path, 'native_accept_request_unconfirmed', applied, true);
|
|
@@ -166,19 +290,15 @@
|
|
|
166
290
|
return { ok: true, verified: true, applied, skipped: [] };
|
|
167
291
|
}
|
|
168
292
|
|
|
293
|
+
let nativeRejectPlan = null;
|
|
169
294
|
async function rejectTrackedChanges(params = {}) {
|
|
170
|
-
//
|
|
171
|
-
// The pageBridge wrapper already runs the runProjectId guard, but a
|
|
172
|
-
// future caller could reach the router directly. A missing or empty
|
|
173
|
-
// `runProjectId` blocks the reject with the same shape the page-side
|
|
174
|
-
// guard uses. Existing tests that target individual reject behaviors set
|
|
175
|
-
// `runProjectId` on params; tests that intentionally omit it land here.
|
|
295
|
+
// Direct router callers also require the original run project identity.
|
|
176
296
|
const writeGuardBlock = checkWritebackRunProjectId(params);
|
|
177
297
|
if (writeGuardBlock) return writeGuardBlock;
|
|
178
298
|
const trackedChanges = normalizeTrackedChangeRefs(params.trackedChanges || []);
|
|
179
299
|
const expectedFiles = Array.isArray(params.expectedFiles) ? params.expectedFiles : [];
|
|
180
300
|
const postFiles = Array.isArray(params.postFiles) ? params.postFiles : [];
|
|
181
|
-
const applied = [];
|
|
301
|
+
const applied = deps.createReviewApplied?.() || [];
|
|
182
302
|
const skipped = [];
|
|
183
303
|
const appliedPaths = new Set();
|
|
184
304
|
const invalidTrackedChange = trackedChanges.find(trackedChange => trackedChange.invalidProjectPath);
|
|
@@ -194,9 +314,26 @@
|
|
|
194
314
|
}
|
|
195
315
|
|
|
196
316
|
const nativeReview = await prepareNativeReview(params, true);
|
|
317
|
+
nativeRejectPlan = nativeReview;
|
|
197
318
|
if (nativeReview && !nativeReview.ok) return nativeReview;
|
|
319
|
+
if (nativeReview?.files.some(file => file.scope.merged))
|
|
320
|
+
return rejectPlanWithMergedInsertions(params, nativeReview);
|
|
198
321
|
const mixedFile = nativeReview?.files.find(file => file.scope.unrelated.length);
|
|
199
322
|
if (mixedFile) return nativeReviewBlocked(mixedFile.path, 'native_reject_mixed_changes');
|
|
323
|
+
if (params.untrackedUndo === true && !trackedChanges.length) {
|
|
324
|
+
if (!nativeReview) return nativeReviewBlocked('', 'native_review_unavailable');
|
|
325
|
+
// An ordinary write has no owned editor-history entry. Restore its
|
|
326
|
+
// checkpoint directly; global Undo may belong to an earlier user edit.
|
|
327
|
+
const restored = await restoreExpectedFilesWithNoTraceUndo(expectedFiles, postFiles, {
|
|
328
|
+
runProjectId: params.runProjectId, untrackedPlan: nativeReview
|
|
329
|
+
});
|
|
330
|
+
if (restored.applied.some(entry => entry.result?.changedDocument !== false)) compileBridge.markSourceEdited();
|
|
331
|
+
return { ...restored, ok: restored.attempted && restored.ok,
|
|
332
|
+
skipped: restored.attempted ? restored.skipped : [{ trackedChange: { path: restored.file || '' }, result: {
|
|
333
|
+
ok: false, file: restored.file || '', code: restored.code || 'snapshot_undo_unavailable',
|
|
334
|
+
reason: restored.reason || 'The saved checkpoint is unavailable for this undo.'
|
|
335
|
+
} }] };
|
|
336
|
+
}
|
|
200
337
|
const editorUndo = await rejectTrackedChangesViaEditorUndo(expectedFiles, postFiles, applied);
|
|
201
338
|
if (editorUndo.ok) {
|
|
202
339
|
if (applied.length > 0) {
|
|
@@ -295,7 +432,7 @@
|
|
|
295
432
|
continue;
|
|
296
433
|
}
|
|
297
434
|
|
|
298
|
-
const rejectControl = findRejectControlForTrackedChangeNode(node);
|
|
435
|
+
const rejectControl = isTrackedChangeNodeInNativeScope(node, trackedChange.path || getActiveFilePath()) ? findRejectControlForTrackedChangeNode(node) : null;
|
|
299
436
|
if (!rejectControl) {
|
|
300
437
|
if (expectedFiles.length || trackedChange.path) {
|
|
301
438
|
continue;
|
|
@@ -345,7 +482,7 @@
|
|
|
345
482
|
const completion = await rejectRemainingTrackedChangesForExpectedFiles(expectedFiles, applied);
|
|
346
483
|
if (!completion.ok) {
|
|
347
484
|
skipped.push({
|
|
348
|
-
trackedChange: null,
|
|
485
|
+
trackedChange: completion.file ? { path: completion.file } : null,
|
|
349
486
|
result: completion
|
|
350
487
|
});
|
|
351
488
|
}
|
|
@@ -353,7 +490,7 @@
|
|
|
353
490
|
const completion = await rejectRemainingTrackedChangesForTrackedPaths(trackedChanges, applied);
|
|
354
491
|
if (!completion.ok) {
|
|
355
492
|
skipped.push({
|
|
356
|
-
trackedChange: null,
|
|
493
|
+
trackedChange: completion.file ? { path: completion.file } : null,
|
|
357
494
|
result: completion
|
|
358
495
|
});
|
|
359
496
|
}
|
|
@@ -373,18 +510,15 @@
|
|
|
373
510
|
// Native-ledger browsers accept exact IDs without changing document text.
|
|
374
511
|
// The original undo/replay flow remains only for legacy adapter callers.
|
|
375
512
|
async function acceptTrackedChanges(params = {}) {
|
|
376
|
-
//
|
|
377
|
-
// The pageBridge wrapper already runs the runProjectId guard, but a
|
|
378
|
-
// future caller could reach the router directly. A missing or empty
|
|
379
|
-
// `runProjectId` blocks the accept with the same shape the page-side
|
|
380
|
-
// guard uses.
|
|
513
|
+
// Direct router callers also require the original run project identity.
|
|
381
514
|
const writeGuardBlock = checkWritebackRunProjectId(params);
|
|
382
515
|
if (writeGuardBlock) return writeGuardBlock;
|
|
516
|
+
if (params.verifyManualReview === true) return nativeReviewBlocked('', 'native_accept_protocol_unsupported');
|
|
383
517
|
const nativeReview = await prepareNativeReview(params);
|
|
384
518
|
if (nativeReview) return nativeReview.ok ? acceptNativeReview(params, nativeReview) : nativeReview;
|
|
385
519
|
const expectedFiles = Array.isArray(params.expectedFiles) ? params.expectedFiles : [];
|
|
386
520
|
const postFiles = Array.isArray(params.postFiles) ? params.postFiles : [];
|
|
387
|
-
const applied = [];
|
|
521
|
+
const applied = deps.createReviewApplied?.() || [];
|
|
388
522
|
const skipped = [];
|
|
389
523
|
// diagnostics: a per-step trace returned to the caller so the run card can
|
|
390
524
|
// surface exactly what happened on each Accept All step. Each entry is
|
|
@@ -427,12 +561,8 @@
|
|
|
427
561
|
const editorUndo = await rejectTrackedChangesViaEditorUndo(expectedFiles, postFiles, applied);
|
|
428
562
|
let undoReady = editorUndo.ok === true;
|
|
429
563
|
let snapshotUndo = null;
|
|
430
|
-
//
|
|
431
|
-
//
|
|
432
|
-
// reuse Undo's guarded snapshot path: it first verifies that every file is
|
|
433
|
-
// still exactly at this run's post-write content, then restores the
|
|
434
|
-
// persisted pre-write content with Track Changes disabled. The normal
|
|
435
|
-
// Accept replay below can then apply the post-write patch untracked.
|
|
564
|
+
// After a refresh clears native history, guarded snapshot rollback can
|
|
565
|
+
// restore exact checkpoints in Editing before the legacy Accept replay.
|
|
436
566
|
if (!undoReady && applied.length === appliedBeforeUndo) {
|
|
437
567
|
snapshotUndo = await restoreExpectedFilesWithNoTraceUndo(expectedFiles, postFiles, {
|
|
438
568
|
runProjectId: params.runProjectId
|
|
@@ -498,10 +628,8 @@
|
|
|
498
628
|
reason: editingSwitch.reason || ''
|
|
499
629
|
});
|
|
500
630
|
if (!editingSwitch.ok) {
|
|
501
|
-
//
|
|
502
|
-
//
|
|
503
|
-
// tracked changes. The structured failure from forceEditingForAcceptReplay
|
|
504
|
-
// (editing_not_confirmed, changedDocument:true) is preserved verbatim.
|
|
631
|
+
// Undo already changed the document. Preserve that evidence and stop
|
|
632
|
+
// when Editing cannot be confirmed for an untracked replay.
|
|
505
633
|
return {
|
|
506
634
|
ok: false,
|
|
507
635
|
applied: [],
|
|
@@ -544,12 +672,8 @@
|
|
|
544
672
|
};
|
|
545
673
|
}
|
|
546
674
|
|
|
547
|
-
//
|
|
548
|
-
//
|
|
549
|
-
// write only the minimal changed fragments via the `patches` path of
|
|
550
|
-
// applyOperationsCore — never a whole-file replaceAll, which would clobber
|
|
551
|
-
// any unrelated content and produce one giant tracked change if anything
|
|
552
|
-
// about the mode switch were imperfect.
|
|
675
|
+
// Replay only the changed fragments in Editing; whole-file replacement
|
|
676
|
+
// would overwrite unrelated text or create one large tracked change.
|
|
553
677
|
const operationsResult = buildAcceptReplayOperations(
|
|
554
678
|
paths,
|
|
555
679
|
expectedByPath,
|
|
@@ -571,18 +695,9 @@
|
|
|
571
695
|
};
|
|
572
696
|
}
|
|
573
697
|
const operations = operationsResult.operations;
|
|
574
|
-
//
|
|
575
|
-
//
|
|
576
|
-
//
|
|
577
|
-
// Bug C: even after forceEditingForAcceptReplay confirmed Editing once,
|
|
578
|
-
// Overleaf has been observed flipping back to Reviewing between the
|
|
579
|
-
// confirm and the next CodeMirror write (Overleaf-side override, per-user
|
|
580
|
-
// "Track Changes for me" setting, or a positive-confirm false-positive
|
|
581
|
-
// window). The fix: re-verify Editing immediately BEFORE every single
|
|
582
|
-
// operation, and if Reviewing has slipped back on, force the toggle off
|
|
583
|
-
// again and positively re-confirm before writing. If Editing still cannot
|
|
584
|
-
// be positively confirmed after the re-toggle, bail the rest of the loop
|
|
585
|
-
// so we never land a tracked write.
|
|
698
|
+
// Overleaf may return to Reviewing between confirmation and a write.
|
|
699
|
+
// Reconfirm stable Editing before every operation, force it off again
|
|
700
|
+
// if needed, and stop the tail whenever confirmation fails.
|
|
586
701
|
let bailedReason = null;
|
|
587
702
|
for (const operation of operations) {
|
|
588
703
|
const opPath = operation?.path || '';
|
|
@@ -736,12 +851,8 @@
|
|
|
736
851
|
}
|
|
737
852
|
}
|
|
738
853
|
|
|
739
|
-
//
|
|
740
|
-
//
|
|
741
|
-
// CodeMirror dispatch can race with Overleaf's collaboration/reviewing
|
|
742
|
-
// settlement and cause the replay itself to land as fresh tracked changes.
|
|
743
|
-
// Leave the editor in Editing after a successful replay; the user can turn
|
|
744
|
-
// Reviewing back on manually after Overleaf has saved the accepted text.
|
|
854
|
+
// Leave Editing active: restoring Reviewing before collaboration settles
|
|
855
|
+
// can turn the untracked Accept replay into fresh tracked changes.
|
|
745
856
|
if (weToggledOff) {
|
|
746
857
|
pushDiagnostic('restoreReviewing', {
|
|
747
858
|
ok: true,
|
|
@@ -763,12 +874,8 @@
|
|
|
763
874
|
};
|
|
764
875
|
}
|
|
765
876
|
|
|
766
|
-
//
|
|
767
|
-
//
|
|
768
|
-
// reviewing detector's verdict (ok/status/source), a compact controls
|
|
769
|
-
// summary, and the two strict gates (isReviewingPositivelyOn /
|
|
770
|
-
// isEditingPositivelyConfirmed). Self-contained — the run card can render
|
|
771
|
-
// this verbatim without re-reading any page state.
|
|
877
|
+
// Capture the detector signals and strict gates for self-contained
|
|
878
|
+
// diagnostics; rendering must not reread mutable page state.
|
|
772
879
|
function summarizeReviewingStateForDiagnostics(state = {}) {
|
|
773
880
|
const reviewing = state.reviewing || {};
|
|
774
881
|
const controls = (state.signals?.controls || []).slice(0, 6).map(control => {
|
|
@@ -794,10 +901,8 @@
|
|
|
794
901
|
};
|
|
795
902
|
}
|
|
796
903
|
|
|
797
|
-
//
|
|
798
|
-
//
|
|
799
|
-
// gate the Accept replay needs: it must never replay while tracked, so
|
|
800
|
-
// anything short of a positive "Editing on / Reviewing off" is rejected.
|
|
904
|
+
// Accept replay requires positively detected Editing and no positively
|
|
905
|
+
// confirmed Reviewing; every weaker observation must stop the replay.
|
|
801
906
|
function isEditingPositivelyConfirmed(state = {}) {
|
|
802
907
|
return isEditingConfirmedForNoTraceUndo(state) && !isReviewingConfirmedForWrite(state);
|
|
803
908
|
}
|
|
@@ -856,42 +961,20 @@
|
|
|
856
961
|
};
|
|
857
962
|
}
|
|
858
963
|
|
|
859
|
-
//
|
|
860
|
-
//
|
|
861
|
-
//
|
|
862
|
-
// Bug B root cause: the accept flow used ensureEditing, which short-circuits
|
|
863
|
-
// on isEditingConfirmedForNoTraceUndo. That detector is negation-based — it
|
|
864
|
-
// returns true whenever Reviewing is not *positively* confirmed-for-write
|
|
865
|
-
// (which requires an active aria attribute the real Overleaf reviewing
|
|
866
|
-
// control does not always carry) AND a control loosely matches an Editing
|
|
867
|
-
// pattern. So while Track Changes was actually ON, it false-positived
|
|
868
|
-
// "already Editing", ensureEditing returned activated:false without toggling,
|
|
869
|
-
// and the replay landed as tracked changes.
|
|
870
|
-
//
|
|
871
|
-
// The fix here does NOT trust that lenient short-circuit: it reads the
|
|
872
|
-
// reviewing state explicitly and treats Editing as confirmed only when it is
|
|
873
|
-
// *positively* confirmed (Editing detected AND Reviewing not confirmed on).
|
|
874
|
-
// Whenever that strict gate is not met it forces setReviewingEnabled(false),
|
|
875
|
-
// then positively re-confirms Editing before returning ok; if it still cannot
|
|
876
|
-
// confirm Editing it returns not-ok so the caller bails instead of replaying
|
|
877
|
-
// while tracked.
|
|
964
|
+
// Accept replay requires positively confirmed Editing. The legacy detector
|
|
965
|
+
// can misclassify Reviewing controls without aria state; force the toggle
|
|
966
|
+
// and recheck before replaying untracked text.
|
|
878
967
|
async function forceEditingForAcceptReplay() {
|
|
879
968
|
const initial = getReviewingState({});
|
|
880
969
|
if (isEditingPositivelyConfirmed(initial)) {
|
|
881
|
-
// Positively confirmed already in Editing — no toggle needed.
|
|
882
970
|
return { ok: true, activated: false };
|
|
883
971
|
}
|
|
884
972
|
|
|
885
|
-
//
|
|
886
|
-
// toggle rather than trusting ensureEditing's lenient detection.
|
|
973
|
+
// Force the toggle whenever Editing is not positively confirmed.
|
|
887
974
|
const switched = await setReviewingEnabled(false, { waitMs: 1800 });
|
|
888
975
|
if (!switched.ok) {
|
|
889
|
-
//
|
|
890
|
-
//
|
|
891
|
-
// was rolled back so the user-visible doc state has moved from the
|
|
892
|
-
// post-write tracked state back to pre-write — set changedDocument:true
|
|
893
|
-
// per the design spec's "is there text the user might want to inspect"
|
|
894
|
-
// contract for accept-side failures after a rollback.
|
|
976
|
+
// Native Undo already changed the document; a failed mode switch must
|
|
977
|
+
// preserve changedDocument evidence even though replay never started.
|
|
895
978
|
return {
|
|
896
979
|
ok: false,
|
|
897
980
|
code: switched.code || 'accept_editing_not_confirmed',
|
|
@@ -933,126 +1016,6 @@
|
|
|
933
1016
|
return { ok: true, activated: true };
|
|
934
1017
|
}
|
|
935
1018
|
|
|
936
|
-
// Builds the minimal replay operations for Accept All. The replay must write
|
|
937
|
-
// only the changed fragments, never a whole-file replaceAll.
|
|
938
|
-
//
|
|
939
|
-
// Preferred source: the run's own original forward writeback operations
|
|
940
|
-
// (carrying their `patches`). After the editor-undo the document is back at
|
|
941
|
-
// the pre-write content, so those patches' `expected` slices match and
|
|
942
|
-
// re-apply cleanly.
|
|
943
|
-
//
|
|
944
|
-
// Fallback: when a path has no usable original patch operation, compute a
|
|
945
|
-
// minimal pre->post diff (trim the common prefix/suffix to a single targeted
|
|
946
|
-
// {from,to,insert} patch).
|
|
947
|
-
function buildAcceptReplayOperations(paths, expectedByPath, postByPath, appliedOperations) {
|
|
948
|
-
const patchesByPath = collectAppliedEditPatchesByPath(appliedOperations);
|
|
949
|
-
const operations = [];
|
|
950
|
-
for (const path of paths) {
|
|
951
|
-
const preContent = expectedByPath.get(path);
|
|
952
|
-
const postContent = postByPath.get(path);
|
|
953
|
-
if (typeof preContent !== 'string' || typeof postContent !== 'string') {
|
|
954
|
-
return {
|
|
955
|
-
ok: false,
|
|
956
|
-
code: 'accept_missing_run_content',
|
|
957
|
-
reason: `${path} 缺少写入前/写入后内容;Codex 没有重放本轮改动。`
|
|
958
|
-
};
|
|
959
|
-
}
|
|
960
|
-
|
|
961
|
-
// Preferred: re-apply the run's original forward patches verbatim.
|
|
962
|
-
const originalPatches = patchesByPath.get(path);
|
|
963
|
-
if (originalPatches && originalPatches.length && patchesApplyCleanly(preContent, originalPatches)) {
|
|
964
|
-
operations.push({
|
|
965
|
-
type: 'edit',
|
|
966
|
-
path,
|
|
967
|
-
patches: originalPatches,
|
|
968
|
-
reason: 'Accept tracked edit (replay untracked)'
|
|
969
|
-
});
|
|
970
|
-
continue;
|
|
971
|
-
}
|
|
972
|
-
|
|
973
|
-
// Fallback: minimal pre->post diff trimmed to a single targeted patch.
|
|
974
|
-
const diffPatch = buildMinimalDiffPatch(preContent, postContent);
|
|
975
|
-
if (!diffPatch) {
|
|
976
|
-
// pre === post: nothing to replay for this file.
|
|
977
|
-
continue;
|
|
978
|
-
}
|
|
979
|
-
operations.push({
|
|
980
|
-
type: 'edit',
|
|
981
|
-
path,
|
|
982
|
-
patches: [diffPatch],
|
|
983
|
-
reason: 'Accept tracked edit (replay untracked)'
|
|
984
|
-
});
|
|
985
|
-
}
|
|
986
|
-
return { ok: true, operations };
|
|
987
|
-
}
|
|
988
|
-
|
|
989
|
-
// Collects the per-path `patches` arrays from the run's applied edit
|
|
990
|
-
// operations. Only edit operations that actually carried `patches` are
|
|
991
|
-
// usable; whole-file replaceAll / verifiedContent operations are skipped so
|
|
992
|
-
// the replay never falls back to a whole-file write.
|
|
993
|
-
function collectAppliedEditPatchesByPath(appliedOperations) {
|
|
994
|
-
const patchesByPath = new Map();
|
|
995
|
-
for (const rawOperation of Array.isArray(appliedOperations) ? appliedOperations : []) {
|
|
996
|
-
if (!rawOperation || rawOperation.type !== 'edit') {
|
|
997
|
-
continue;
|
|
998
|
-
}
|
|
999
|
-
const path = typeof rawOperation.path === 'string'
|
|
1000
|
-
? normalizeSafeProjectPath(rawOperation.path)
|
|
1001
|
-
: '';
|
|
1002
|
-
if (!path || !Array.isArray(rawOperation.patches) || !rawOperation.patches.length) {
|
|
1003
|
-
continue;
|
|
1004
|
-
}
|
|
1005
|
-
const normalized = rawOperation.patches.map(patch => ({
|
|
1006
|
-
from: Number(patch?.from),
|
|
1007
|
-
to: Number(patch?.to),
|
|
1008
|
-
expected: String(patch?.expected ?? ''),
|
|
1009
|
-
insert: String(patch?.insert ?? '')
|
|
1010
|
-
}));
|
|
1011
|
-
const existing = patchesByPath.get(path) || [];
|
|
1012
|
-
patchesByPath.set(path, existing.concat(normalized));
|
|
1013
|
-
}
|
|
1014
|
-
return patchesByPath;
|
|
1015
|
-
}
|
|
1016
|
-
|
|
1017
|
-
// Confirms a patch set re-applies cleanly against the given (pre-write) text:
|
|
1018
|
-
// every patch range is valid and its `expected` slice matches.
|
|
1019
|
-
function patchesApplyCleanly(text, patches) {
|
|
1020
|
-
const applied = applyTextPatches(text, patches);
|
|
1021
|
-
return applied.ok === true;
|
|
1022
|
-
}
|
|
1023
|
-
|
|
1024
|
-
// Computes a single targeted {from,to,insert} patch describing the pre->post
|
|
1025
|
-
// change by trimming the common prefix and suffix. Returns null when the two
|
|
1026
|
-
// strings are identical.
|
|
1027
|
-
function buildMinimalDiffPatch(preContent, postContent) {
|
|
1028
|
-
if (preContent === postContent) {
|
|
1029
|
-
return null;
|
|
1030
|
-
}
|
|
1031
|
-
const preLen = preContent.length;
|
|
1032
|
-
const postLen = postContent.length;
|
|
1033
|
-
let prefix = 0;
|
|
1034
|
-
const maxPrefix = Math.min(preLen, postLen);
|
|
1035
|
-
while (prefix < maxPrefix && preContent[prefix] === postContent[prefix]) {
|
|
1036
|
-
prefix += 1;
|
|
1037
|
-
}
|
|
1038
|
-
let suffix = 0;
|
|
1039
|
-
const maxSuffix = Math.min(preLen, postLen) - prefix;
|
|
1040
|
-
while (
|
|
1041
|
-
suffix < maxSuffix
|
|
1042
|
-
&& preContent[preLen - 1 - suffix] === postContent[postLen - 1 - suffix]
|
|
1043
|
-
) {
|
|
1044
|
-
suffix += 1;
|
|
1045
|
-
}
|
|
1046
|
-
const from = prefix;
|
|
1047
|
-
const to = preLen - suffix;
|
|
1048
|
-
return {
|
|
1049
|
-
from,
|
|
1050
|
-
to,
|
|
1051
|
-
expected: preContent.slice(from, to),
|
|
1052
|
-
insert: postContent.slice(from, postLen - suffix)
|
|
1053
|
-
};
|
|
1054
|
-
}
|
|
1055
|
-
|
|
1056
1019
|
async function rejectTrackedChangesViaEditorUndo(expectedFiles, postFiles, applied) {
|
|
1057
1020
|
const expectedByPath = new Map((expectedFiles || [])
|
|
1058
1021
|
.filter(file => file?.path && typeof file.content === 'string')
|
|
@@ -1071,10 +1034,8 @@
|
|
|
1071
1034
|
const postContent = postByPath.get(path);
|
|
1072
1035
|
const activePathMatches = getActiveFilePath() === path;
|
|
1073
1036
|
const activeContentMatches = readActiveEditorText() === postContent;
|
|
1074
|
-
//
|
|
1075
|
-
//
|
|
1076
|
-
// readActiveEditorText() still belongs to the previously viewed file.
|
|
1077
|
-
// Force-reopen the target whenever either identity signal disagrees.
|
|
1037
|
+
// Tree selection can precede the editor swap. Reopen when either
|
|
1038
|
+
// the target path or its exact postimage is not yet active.
|
|
1078
1039
|
if (path && (!activePathMatches || !activeContentMatches)) {
|
|
1079
1040
|
const opened = await openFileByPath(path, { force: true });
|
|
1080
1041
|
if (!opened.ok) {
|
|
@@ -1125,18 +1086,23 @@
|
|
|
1125
1086
|
}
|
|
1126
1087
|
|
|
1127
1088
|
async function restoreExpectedFilesWithNoTraceUndo(expectedFiles, postFiles, options = {}) {
|
|
1128
|
-
const expectedByPath =
|
|
1129
|
-
.filter(file => file?.path && typeof file.content === 'string')
|
|
1130
|
-
.map(file => [normalizeSafeProjectPath(file.path), file.content])
|
|
1131
|
-
.filter(([path]) => path));
|
|
1132
|
-
const postByPath = new Map((postFiles || [])
|
|
1133
|
-
.filter(file => file?.path && typeof file.content === 'string')
|
|
1134
|
-
.map(file => [normalizeSafeProjectPath(file.path), file.content])
|
|
1135
|
-
.filter(([path]) => path));
|
|
1136
|
-
const paths = Array.from(expectedByPath.keys()).filter(path => postByPath.has(path));
|
|
1089
|
+
const { expectedByPath, postByPath, paths } = prepareSnapshotUndoCheckpoints(expectedFiles, postFiles);
|
|
1137
1090
|
const rebasedExpectedByPath = new Map();
|
|
1138
1091
|
const rebasedPostByPath = new Map();
|
|
1139
1092
|
const undoRebaseProofs = new Map();
|
|
1093
|
+
// Only the ordinary, native-ledger-checked Undo route may recognize a
|
|
1094
|
+
// lost receipt's exact pre-image. This consumes an existing checkpoint;
|
|
1095
|
+
// it grants no native review, file creation or move ownership.
|
|
1096
|
+
const untrackedByPath = new Map((options.untrackedPlan?.files || []).map(file => [file.path, file]));
|
|
1097
|
+
const alreadyRestored = new Set();
|
|
1098
|
+
const mismatch = path => ({ ok: false, file: path, changedDocument: false, code: 'snapshot_undo_current_mismatch',
|
|
1099
|
+
reason: `${path} 当前内容或文档身份与本轮撤销检查点不一致;Codex 保留恢复材料并停止撤销。` });
|
|
1100
|
+
async function verifyAlreadyRestored(path) {
|
|
1101
|
+
if (getActiveFilePath() !== path && !(await openFileByPath(path)).ok) return false;
|
|
1102
|
+
return (await waitForNativeReview(path, [], true, scope => Boolean(scope.nativeDocId)
|
|
1103
|
+
&& scope.nativeDocId === untrackedByPath.get(path)?.nativeDocId
|
|
1104
|
+
&& getActiveFilePath() === path && readActiveEditorText() === expectedByPath.get(path))).ok === true;
|
|
1105
|
+
}
|
|
1140
1106
|
if (!paths.length) {
|
|
1141
1107
|
return {
|
|
1142
1108
|
ok: false,
|
|
@@ -1157,7 +1123,7 @@
|
|
|
1157
1123
|
attempted: false,
|
|
1158
1124
|
applied: [],
|
|
1159
1125
|
skipped: [],
|
|
1160
|
-
code: 'snapshot_undo_open_failed',
|
|
1126
|
+
code: 'snapshot_undo_open_failed', file: path,
|
|
1161
1127
|
reason: `无法打开 ${path} 来执行快照撤销。`
|
|
1162
1128
|
};
|
|
1163
1129
|
}
|
|
@@ -1166,7 +1132,8 @@
|
|
|
1166
1132
|
path,
|
|
1167
1133
|
expectedByPath.get(path),
|
|
1168
1134
|
postByPath.get(path),
|
|
1169
|
-
1500
|
|
1135
|
+
1500,
|
|
1136
|
+
untrackedByPath.has(path)
|
|
1170
1137
|
);
|
|
1171
1138
|
if (!ready.ok) {
|
|
1172
1139
|
return {
|
|
@@ -1176,10 +1143,14 @@
|
|
|
1176
1143
|
skipped: [],
|
|
1177
1144
|
rebasedExpectedFiles: [],
|
|
1178
1145
|
rebasedPostFiles: [],
|
|
1179
|
-
code: 'snapshot_undo_current_mismatch',
|
|
1146
|
+
code: 'snapshot_undo_current_mismatch', file: path,
|
|
1180
1147
|
reason: `${path} 当前内容已经不是本轮写入后的内容;为避免覆盖你的后续修改,Codex 不执行快照撤销。`
|
|
1181
1148
|
};
|
|
1182
1149
|
}
|
|
1150
|
+
if (ready.alreadyRestored) {
|
|
1151
|
+
if (!(await verifyAlreadyRestored(path))) return { ...mismatch(path), attempted: false, applied: [], skipped: [] };
|
|
1152
|
+
alreadyRestored.add(path);
|
|
1153
|
+
}
|
|
1183
1154
|
rebasedExpectedByPath.set(path, ready.expectedContent);
|
|
1184
1155
|
rebasedPostByPath.set(path, ready.postContent);
|
|
1185
1156
|
if (ready.prefixLength || ready.suffixLength) {
|
|
@@ -1187,55 +1158,49 @@
|
|
|
1187
1158
|
}
|
|
1188
1159
|
}
|
|
1189
1160
|
|
|
1190
|
-
|
|
1161
|
+
// Re-check observations after other preflight awaits before starting the
|
|
1162
|
+
// unfinished tail. They are checked again after writes before settlement.
|
|
1163
|
+
for (const path of alreadyRestored) {
|
|
1164
|
+
if (!(await verifyAlreadyRestored(path))) return { ...mismatch(path), attempted: false, applied: [], skipped: [] };
|
|
1165
|
+
}
|
|
1166
|
+
const operations = paths.filter(path => !alreadyRestored.has(path)).map(path => ({
|
|
1191
1167
|
type: 'edit',
|
|
1192
1168
|
path,
|
|
1193
1169
|
replaceAll: rebasedExpectedByPath.get(path),
|
|
1194
1170
|
reason: 'Undo tracked edit'
|
|
1195
1171
|
}));
|
|
1196
|
-
const result = await applyOperationsWithNoTraceUndo(operations, {
|
|
1172
|
+
const result = operations.length ? await applyOperationsWithNoTraceUndo(operations, {
|
|
1197
1173
|
baseFiles: paths.map(path => ({
|
|
1198
1174
|
path,
|
|
1199
1175
|
content: rebasedPostByPath.get(path)
|
|
1200
1176
|
})),
|
|
1201
1177
|
runProjectId: typeof options.runProjectId === 'string' ? options.runProjectId : ''
|
|
1202
|
-
});
|
|
1203
|
-
const
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
},
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
}
|
|
1216
|
-
return {
|
|
1217
|
-
ok: !(result.skipped || []).length,
|
|
1218
|
-
attempted: true,
|
|
1219
|
-
applied: (result.applied || []).map(toTrackedResult),
|
|
1220
|
-
skipped: (result.skipped || []).map(toTrackedResult),
|
|
1221
|
-
rebasedExpectedFiles: paths.map(path => ({
|
|
1222
|
-
path,
|
|
1223
|
-
content: rebasedExpectedByPath.get(path)
|
|
1224
|
-
})),
|
|
1225
|
-
rebasedPostFiles: paths.map(path => ({
|
|
1226
|
-
path,
|
|
1227
|
-
content: rebasedPostByPath.get(path)
|
|
1228
|
-
}))
|
|
1229
|
-
};
|
|
1178
|
+
}) : { applied: [], skipped: [] };
|
|
1179
|
+
const converted = buildSnapshotUndoResult(result, rebasedExpectedByPath, rebasedPostByPath, undoRebaseProofs);
|
|
1180
|
+
const applied = deps.createReviewApplied?.() || [];
|
|
1181
|
+
applied.push(...converted.applied);
|
|
1182
|
+
const skipped = converted.skipped;
|
|
1183
|
+
for (const path of alreadyRestored) {
|
|
1184
|
+
const verified = await verifyAlreadyRestored(path);
|
|
1185
|
+
const entry = { trackedChange: { key: `snapshot-undo:${path}`, id: '', path,
|
|
1186
|
+
label: 'Already restored checkpoint' }, result: verified
|
|
1187
|
+
? { ok: true, verified: true, verifiedContent: expectedByPath.get(path), changedDocument: false, idempotent: true }
|
|
1188
|
+
: mismatch(path) };
|
|
1189
|
+
(verified ? applied : skipped).push(entry);
|
|
1190
|
+
}
|
|
1191
|
+
return { ...converted, ok: !skipped.length, applied, skipped };
|
|
1230
1192
|
}
|
|
1231
1193
|
|
|
1232
|
-
async function waitForActiveEditorCheckpoint(filePath, expectedContent, postContent, timeoutMs) {
|
|
1194
|
+
async function waitForActiveEditorCheckpoint(filePath, expectedContent, postContent, timeoutMs, allowAlreadyRestored = false) {
|
|
1233
1195
|
const deadline = Date.now() + Math.max(0, Number(timeoutMs) || 0);
|
|
1234
1196
|
let actual = readActiveEditorText();
|
|
1235
1197
|
while (Date.now() < deadline) {
|
|
1236
1198
|
const activeFileMatches = !filePath || getActiveFilePath() === filePath;
|
|
1237
1199
|
actual = readActiveEditorText();
|
|
1238
1200
|
if (activeFileMatches) {
|
|
1201
|
+
if (allowAlreadyRestored && actual === expectedContent) {
|
|
1202
|
+
return { ok: true, alreadyRestored: true, expectedContent, postContent, prefixLength: 0, suffixLength: 0 };
|
|
1203
|
+
}
|
|
1239
1204
|
const rebased = rebaseCheckpointPair(actual, expectedContent, postContent);
|
|
1240
1205
|
if (rebased.ok) {
|
|
1241
1206
|
return rebased;
|
|
@@ -1249,37 +1214,6 @@
|
|
|
1249
1214
|
};
|
|
1250
1215
|
}
|
|
1251
1216
|
|
|
1252
|
-
function rebaseCheckpointPair(actualContent, expectedContent, postContent) {
|
|
1253
|
-
const actual = String(actualContent ?? '');
|
|
1254
|
-
const expected = String(expectedContent ?? '');
|
|
1255
|
-
const post = String(postContent ?? '');
|
|
1256
|
-
if (actual === post) {
|
|
1257
|
-
return {
|
|
1258
|
-
ok: true,
|
|
1259
|
-
expectedContent: expected,
|
|
1260
|
-
postContent: post,
|
|
1261
|
-
prefixLength: 0,
|
|
1262
|
-
suffixLength: 0
|
|
1263
|
-
};
|
|
1264
|
-
}
|
|
1265
|
-
if (!post) {
|
|
1266
|
-
return { ok: false };
|
|
1267
|
-
}
|
|
1268
|
-
const matchIndex = actual.indexOf(post);
|
|
1269
|
-
if (matchIndex < 0 || actual.indexOf(post, matchIndex + 1) >= 0) {
|
|
1270
|
-
return { ok: false };
|
|
1271
|
-
}
|
|
1272
|
-
const prefix = actual.slice(0, matchIndex);
|
|
1273
|
-
const suffix = actual.slice(matchIndex + post.length);
|
|
1274
|
-
return {
|
|
1275
|
-
ok: true,
|
|
1276
|
-
expectedContent: prefix + expected + suffix,
|
|
1277
|
-
postContent: actual,
|
|
1278
|
-
prefixLength: prefix.length,
|
|
1279
|
-
suffixLength: suffix.length
|
|
1280
|
-
};
|
|
1281
|
-
}
|
|
1282
|
-
|
|
1283
1217
|
async function waitForActiveEditorExpectedText(filePath, expectedContent, timeoutMs) {
|
|
1284
1218
|
const deadline = Date.now() + Math.max(0, Number(timeoutMs) || 0);
|
|
1285
1219
|
let actual = readActiveEditorText();
|
|
@@ -1306,34 +1240,36 @@
|
|
|
1306
1240
|
const maxClicks = 200;
|
|
1307
1241
|
for (let clicks = 0; clicks <= maxClicks; clicks += 1) {
|
|
1308
1242
|
if (readActiveEditorText() === expectedContent) {
|
|
1309
|
-
return {
|
|
1310
|
-
ok: true,
|
|
1311
|
-
attempted: clicks > 0,
|
|
1312
|
-
clicks
|
|
1313
|
-
};
|
|
1243
|
+
return { ok: true, attempted: clicks > 0, clicks };
|
|
1314
1244
|
}
|
|
1315
1245
|
if (clicks === maxClicks) {
|
|
1316
1246
|
break;
|
|
1317
1247
|
}
|
|
1318
1248
|
|
|
1319
|
-
const
|
|
1249
|
+
const beforeText = readActiveEditorText();
|
|
1250
|
+
let undoControl = findEditorUndoControl();
|
|
1320
1251
|
if (!undoControl) {
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1252
|
+
// Text and the native ledger can be ready before the toolbar updates.
|
|
1253
|
+
// Wait only before dispatch, keeping this click's scope fixed.
|
|
1254
|
+
const assertStable = await createReviewScopeGuard(path, beforeText), until = Date.now() + 1500;
|
|
1255
|
+
while (!undoControl && Date.now() < until) {
|
|
1256
|
+
assertStable();
|
|
1257
|
+
await delay(Math.min(60, until - Date.now()));
|
|
1258
|
+
assertStable();
|
|
1259
|
+
undoControl = findEditorUndoControl();
|
|
1260
|
+
}
|
|
1261
|
+
assertStable();
|
|
1262
|
+
}
|
|
1263
|
+
if (!undoControl) {
|
|
1264
|
+
return { ok: false, attempted: clicks > 0, code: 'editor_undo_control_not_found',
|
|
1325
1265
|
reason: `没有找到 Overleaf 编辑器自己的 Undo/撤销按钮,无法一次性撤销 ${path} 的本轮留痕。`
|
|
1326
1266
|
};
|
|
1327
1267
|
}
|
|
1328
1268
|
|
|
1329
|
-
const beforeText = readActiveEditorText();
|
|
1330
1269
|
clickNode(undoControl);
|
|
1331
1270
|
await waitForEditorTextProgress(beforeText, expectedContent, 1000);
|
|
1332
1271
|
if (readActiveEditorText() === beforeText) {
|
|
1333
|
-
return {
|
|
1334
|
-
ok: false,
|
|
1335
|
-
attempted: clicks > 0,
|
|
1336
|
-
code: 'editor_undo_no_progress',
|
|
1272
|
+
return { ok: false, attempted: clicks > 0, code: 'editor_undo_no_progress',
|
|
1337
1273
|
reason: `Codex 点击了 Overleaf Undo/撤销,但 ${path} 内容没有变化。`
|
|
1338
1274
|
};
|
|
1339
1275
|
}
|
|
@@ -1366,7 +1302,9 @@
|
|
|
1366
1302
|
}
|
|
1367
1303
|
const result = await rejectRemainingTrackedChangesForExpectedFile(file, applied);
|
|
1368
1304
|
if (!result.ok) {
|
|
1369
|
-
|
|
1305
|
+
// The sweep knows its target even when no DOM review node remains.
|
|
1306
|
+
// Keep that scope so independently verified files can leave recovery.
|
|
1307
|
+
return { ...result, file: file.path };
|
|
1370
1308
|
}
|
|
1371
1309
|
}
|
|
1372
1310
|
return { ok: true };
|
|
@@ -1387,7 +1325,7 @@
|
|
|
1387
1325
|
for (const path of paths) {
|
|
1388
1326
|
const result = await rejectRemainingTrackedChangesForPath(path, applied);
|
|
1389
1327
|
if (!result.ok) {
|
|
1390
|
-
return result;
|
|
1328
|
+
return { ...result, file: path };
|
|
1391
1329
|
}
|
|
1392
1330
|
}
|
|
1393
1331
|
return { ok: true };
|
|
@@ -1408,7 +1346,9 @@
|
|
|
1408
1346
|
const appliedCountBefore = applied.length;
|
|
1409
1347
|
const maxAttempts = 200;
|
|
1410
1348
|
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
|
|
1411
|
-
const
|
|
1349
|
+
const selection = await waitForRejectableTrackedChange(path);
|
|
1350
|
+
selection.assertStable();
|
|
1351
|
+
const node = selection.node;
|
|
1412
1352
|
if (!node) {
|
|
1413
1353
|
if (applied.length > appliedCountBefore) {
|
|
1414
1354
|
return { ok: true };
|
|
@@ -1420,7 +1360,7 @@
|
|
|
1420
1360
|
};
|
|
1421
1361
|
}
|
|
1422
1362
|
|
|
1423
|
-
const rejectControl = findRejectControlForTrackedChangeNode(node);
|
|
1363
|
+
const rejectControl = isTrackedChangeNodeInNativeScope(node, path) ? findRejectControlForTrackedChangeNode(node) : null;
|
|
1424
1364
|
if (!rejectControl) {
|
|
1425
1365
|
return {
|
|
1426
1366
|
ok: false,
|
|
@@ -1431,6 +1371,7 @@
|
|
|
1431
1371
|
|
|
1432
1372
|
const trackedChange = trackedChangeRefFromNode(node, path);
|
|
1433
1373
|
const beforeText = readActiveEditorText();
|
|
1374
|
+
selection.assertStable();
|
|
1434
1375
|
clickNode(rejectControl);
|
|
1435
1376
|
await waitForTrackedChangeRejectProgress(trackedChange, path, beforeText, 1200);
|
|
1436
1377
|
applied.push({
|
|
@@ -1461,6 +1402,17 @@
|
|
|
1461
1402
|
};
|
|
1462
1403
|
}
|
|
1463
1404
|
|
|
1405
|
+
async function verifyReviewFileContent(file, applied) {
|
|
1406
|
+
const verified = await verifyActiveEditorText(file.content, file.path, 800);
|
|
1407
|
+
if (getActiveFilePath() !== file.path) verified.ok = false;
|
|
1408
|
+
if (verified.ok) for (const entry of applied) {
|
|
1409
|
+
if (entry.trackedChange?.path === file.path && entry.result?.ok === true
|
|
1410
|
+
&& ['overleaf-review-reject', 'overleaf-review-reject-sweep'].includes(entry.result.method))
|
|
1411
|
+
Object.assign(entry.result, { verified: true, verifiedContent: file.content });
|
|
1412
|
+
}
|
|
1413
|
+
return verified;
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1464
1416
|
async function rejectRemainingTrackedChangesForExpectedFile(file, applied) {
|
|
1465
1417
|
const opened = await openFileByPath(file.path);
|
|
1466
1418
|
if (!opened.ok) {
|
|
@@ -1474,12 +1426,16 @@
|
|
|
1474
1426
|
const appliedCountBefore = applied.length;
|
|
1475
1427
|
const maxAttempts = 200;
|
|
1476
1428
|
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
|
|
1477
|
-
|
|
1429
|
+
if (applied.some(entry => entry.trackedChange?.path === file.path
|
|
1430
|
+
&& ['overleaf-review-reject', 'overleaf-review-reject-sweep'].includes(entry.result?.method))
|
|
1431
|
+
&& getActiveFilePath() === file.path && readActiveEditorText() === file.content
|
|
1432
|
+
&& (await verifyReviewFileContent(file, applied)).ok) return { ok: true };
|
|
1433
|
+
const selection = await waitForRejectableTrackedChange(file.path);
|
|
1434
|
+
selection.assertStable();
|
|
1435
|
+
const node = selection.node;
|
|
1478
1436
|
if (!node) {
|
|
1479
|
-
const verified = await
|
|
1480
|
-
if (verified.ok) {
|
|
1481
|
-
return { ok: true };
|
|
1482
|
-
}
|
|
1437
|
+
const verified = await verifyReviewFileContent(file, applied);
|
|
1438
|
+
if (verified.ok) return { ok: true };
|
|
1483
1439
|
const rejectedAnyForFile = applied.length > appliedCountBefore;
|
|
1484
1440
|
return {
|
|
1485
1441
|
...verified,
|
|
@@ -1490,7 +1446,7 @@
|
|
|
1490
1446
|
};
|
|
1491
1447
|
}
|
|
1492
1448
|
|
|
1493
|
-
const rejectControl = findRejectControlForTrackedChangeNode(node);
|
|
1449
|
+
const rejectControl = isTrackedChangeNodeInNativeScope(node, file.path) ? findRejectControlForTrackedChangeNode(node) : null;
|
|
1494
1450
|
if (!rejectControl) {
|
|
1495
1451
|
return {
|
|
1496
1452
|
ok: false,
|
|
@@ -1501,6 +1457,7 @@
|
|
|
1501
1457
|
|
|
1502
1458
|
const trackedChange = trackedChangeRefFromNode(node, file.path);
|
|
1503
1459
|
const beforeText = readActiveEditorText();
|
|
1460
|
+
selection.assertStable();
|
|
1504
1461
|
clickNode(rejectControl);
|
|
1505
1462
|
await waitForTrackedChangeRejectProgress(trackedChange, file.path, beforeText, 1200);
|
|
1506
1463
|
applied.push({
|
|
@@ -1546,13 +1503,10 @@
|
|
|
1546
1503
|
return false;
|
|
1547
1504
|
}
|
|
1548
1505
|
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
1506
|
const captureModule = window?.CodexOverleafTrackedChangeCapture
|
|
1553
1507
|
|| (typeof module === 'object' && module.exports ? require('./trackedChangeCapture.js') : null);
|
|
1554
1508
|
const { collectTrackedChangeNodes, trackedChangeRefFromNode, collectTrackedChangeRefsForPaths,
|
|
1555
|
-
waitForTrackedChangeDiff, prepareTrackedChangeCapture, prepareTrackedChangeReview, prepareUntrackedUndo, getTrackedChangeCaptureStatus,
|
|
1509
|
+
waitForTrackedChangeDiff, prepareTrackedChangeCapture, prepareTrackedChangeReview, prepareUntrackedUndo, getTrackedChangeCaptureStatus, isTrackedChangeNodeInNativeScope, rememberTrackedChangeNodeContext,
|
|
1556
1510
|
captureTrackedWrite, reconcileTrackedChangeCapture } = captureModule.create(deps);
|
|
1557
1511
|
|
|
1558
1512
|
function orderTrackedChangesForReviewAction(refs = []) {
|
|
@@ -1575,9 +1529,56 @@
|
|
|
1575
1529
|
return nodes[0] || null;
|
|
1576
1530
|
}
|
|
1577
1531
|
|
|
1578
|
-
function
|
|
1579
|
-
const
|
|
1580
|
-
|
|
1532
|
+
async function createReviewScopeGuard(path, expectedText) {
|
|
1533
|
+
const owner = nativeRejectPlan?.files?.find(file => file.path === path);
|
|
1534
|
+
const changed = () => {
|
|
1535
|
+
const failure = nativeReviewBlocked(path, 'native_review_scope_changed').skipped[0].result;
|
|
1536
|
+
throw Object.assign(new Error(failure.reason), { reviewFailure: failure });
|
|
1537
|
+
};
|
|
1538
|
+
let snapshot = prepareTrackedChangeCapture(path);
|
|
1539
|
+
const until = Date.now() + 1500;
|
|
1540
|
+
while ((owner || snapshot.source === 'native') && (snapshot.source !== 'native' || !snapshot.ready) && Date.now() < until) {
|
|
1541
|
+
if (getActiveFilePath() !== path || (expectedText !== undefined && readActiveEditorText() !== expectedText)) changed();
|
|
1542
|
+
await delay(Math.min(60, until - Date.now()));
|
|
1543
|
+
snapshot = prepareTrackedChangeCapture(path);
|
|
1544
|
+
}
|
|
1545
|
+
if (owner || snapshot.source === 'native') {
|
|
1546
|
+
const owned = (owner?.scope.refs || []).map(ref => JSON.stringify([ref.id, ref.kind, ref.textHash]));
|
|
1547
|
+
if (!snapshot.ready || !snapshot.refs.length || !owner || snapshot.nativeDocId !== owner.nativeDocId || snapshot.refs.some(ref => {
|
|
1548
|
+
const index = owned.indexOf(JSON.stringify([ref.id, ref.kind, ref.textHash]));
|
|
1549
|
+
if (index < 0) return true;
|
|
1550
|
+
owned.splice(index, 1); return false;
|
|
1551
|
+
})) changed();
|
|
1552
|
+
}
|
|
1553
|
+
const beforeText = expectedText ?? readActiveEditorText();
|
|
1554
|
+
const key = value => value.source !== 'native' ? '' : !value.ready ? null
|
|
1555
|
+
: JSON.stringify([value.nativeDocId, value.refs.map(ref => ref.key).sort()]);
|
|
1556
|
+
const beforeScope = key(snapshot);
|
|
1557
|
+
return () => {
|
|
1558
|
+
if (getActiveFilePath() !== path || readActiveEditorText() !== beforeText
|
|
1559
|
+
|| key(prepareTrackedChangeCapture(path)) !== beforeScope) changed();
|
|
1560
|
+
};
|
|
1561
|
+
}
|
|
1562
|
+
async function waitForRejectableTrackedChange(path) {
|
|
1563
|
+
const until = Date.now() + 1500;
|
|
1564
|
+
const select = () => {
|
|
1565
|
+
const nodes = findTrackedChangeNodesForPath(path).reverse();
|
|
1566
|
+
for (const node of nodes) {
|
|
1567
|
+
if (!isTrackedChangeNodeInNativeScope(node, path)) continue;
|
|
1568
|
+
const control = findRejectControlForTrackedChangeNode(node);
|
|
1569
|
+
if (control) return { node, control };
|
|
1570
|
+
}
|
|
1571
|
+
return { node: nodes[0] || null, control: null };
|
|
1572
|
+
};
|
|
1573
|
+
let selection = select();
|
|
1574
|
+
if (!selection.node) return { ...selection, assertStable() {} };
|
|
1575
|
+
const assertStable = await createReviewScopeGuard(path);
|
|
1576
|
+
selection = select();
|
|
1577
|
+
while (!selection.control && Date.now() < until) {
|
|
1578
|
+
assertStable(); await delay(Math.min(60, until - Date.now())); assertStable();
|
|
1579
|
+
selection = select();
|
|
1580
|
+
}
|
|
1581
|
+
return { ...selection, assertStable };
|
|
1581
1582
|
}
|
|
1582
1583
|
|
|
1583
1584
|
function findTrackedChangeNodesForPath(path) {
|
|
@@ -1590,6 +1591,7 @@
|
|
|
1590
1591
|
}
|
|
1591
1592
|
|
|
1592
1593
|
function findRejectControlForTrackedChangeNode(node) {
|
|
1594
|
+
if (!node || node.isConnected === false) return null;
|
|
1593
1595
|
const scopes = [];
|
|
1594
1596
|
let current = node;
|
|
1595
1597
|
for (let index = 0; current && index < 6; index += 1) {
|
|
@@ -1616,17 +1618,19 @@
|
|
|
1616
1618
|
}
|
|
1617
1619
|
|
|
1618
1620
|
function isRejectTrackedChangeControl(node) {
|
|
1619
|
-
if (!node || node.disabled) {
|
|
1621
|
+
if (!node || node.disabled || node.isConnected === false) {
|
|
1620
1622
|
return false;
|
|
1621
1623
|
}
|
|
1622
1624
|
if (/^(true|disabled)$/i.test(node.getAttribute?.('aria-disabled') || '')) {
|
|
1623
1625
|
return false;
|
|
1624
1626
|
}
|
|
1625
|
-
const signal = normalizeReviewingSignalText(readNodeSignalText(node)
|
|
1626
|
-
|
|
1627
|
+
const signal = normalizeReviewingSignalText([readNodeSignalText(node),
|
|
1628
|
+
...Array.from(node.querySelectorAll?.('.visually-hidden') || [])
|
|
1629
|
+
.map(label => label.textContent || '')].join(' '));
|
|
1630
|
+
if (/\b(?:accept|approve|apply|resolve)\b|接受|批准|应用/i.test(signal)) {
|
|
1627
1631
|
return false;
|
|
1628
1632
|
}
|
|
1629
|
-
return /\b(?:reject|decline|discard|revert
|
|
1633
|
+
return /\b(?:reject|decline|discard|revert)\b|拒绝|丢弃|还原/i.test(signal);
|
|
1630
1634
|
}
|
|
1631
1635
|
return {
|
|
1632
1636
|
acceptTrackedChanges,
|