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.
Files changed (397) hide show
  1. package/README.md +275 -305
  2. package/README.zh-CN.md +290 -320
  3. package/extension/bootstrap/background.js +99 -19
  4. package/extension/bootstrap/manifest.template.json +9 -4
  5. package/extension/bootstrap/popup.html +11 -16
  6. package/extension/bootstrap/popupFallback.js +55 -0
  7. package/extension/bootstrap/update.js +25 -2
  8. package/extension/bootstrap/updateStatus.js +1 -0
  9. package/extension/runtime-manifest.json +3 -1
  10. package/extension/src/background.js +234 -56
  11. package/extension/src/backgroundBootstrapMigration.js +181 -0
  12. package/extension/src/backgroundSharedSessions.js +440 -0
  13. package/extension/src/backgroundUpdateCoordinator.js +541 -381
  14. package/extension/src/backgroundUpdateSafety.js +173 -0
  15. package/extension/src/content/activeTurnControl.js +16 -2
  16. package/extension/src/content/applyResultFormatters.js +46 -73
  17. package/extension/src/content/assetTransferBroker.js +244 -12
  18. package/extension/src/content/changeHistoryView.js +175 -0
  19. package/extension/src/content/composerAttachments.js +117 -67
  20. package/extension/src/content/composerPanel.js +41 -8
  21. package/extension/src/content/contentRuntime.js +1084 -1099
  22. package/extension/src/content/diagnosticsController.js +122 -97
  23. package/extension/src/content/diagnosticsExport.js +236 -0
  24. package/extension/src/content/diagnosticsPanel.js +78 -19
  25. package/extension/src/content/diffReviewPanel.js +8 -5
  26. package/extension/src/content/directoryCleanupRecovery.js +54 -0
  27. package/extension/src/content/generated/content.bundle.js +14680 -3606
  28. package/extension/src/content/generated/content.bundle.meta.json +30 -3
  29. package/extension/src/content/globalPreferencesController.js +1 -1
  30. package/extension/src/content/legacyGlobalRegistry.js +25 -1
  31. package/extension/src/content/localSkillsPanel.js +141 -16
  32. package/extension/src/content/markdownDomRenderer.js +9 -4
  33. package/extension/src/content/markdownText.js +5 -6
  34. package/extension/src/content/modelPicker.js +8 -5
  35. package/extension/src/content/nativeCompatibilityController.js +45 -20
  36. package/extension/src/content/otWarmMirror.js +4 -5
  37. package/extension/src/content/otWarmMirrorController.js +17 -20
  38. package/extension/src/content/pageBridgeClient.js +33 -5
  39. package/extension/src/content/panelMaintenance.js +87 -132
  40. package/extension/src/content/panelRenderer.js +10 -0
  41. package/extension/src/content/pendingInputView.js +109 -76
  42. package/extension/src/content/projectProviderSelection.js +13 -4
  43. package/extension/src/content/projectSessionCleanup.js +109 -62
  44. package/extension/src/content/projectSettingsCoordinator.js +39 -2
  45. package/extension/src/content/providerSettingsCoordinator.js +63 -14
  46. package/extension/src/content/providerSettingsDialog.js +724 -113
  47. package/extension/src/content/recentProjects.js +21 -6
  48. package/extension/src/content/runActivitySummary.js +665 -0
  49. package/extension/src/content/runCompletionMeta.js +344 -0
  50. package/extension/src/content/runController.js +40 -15
  51. package/extension/src/content/runFailureNotice.js +34 -12
  52. package/extension/src/content/runGuidanceController.js +76 -30
  53. package/extension/src/content/runGuidanceView.js +59 -10
  54. package/extension/src/content/runPresence.js +306 -0
  55. package/extension/src/content/runQueueScheduler.js +150 -43
  56. package/extension/src/content/runRecoveryActions.js +146 -0
  57. package/extension/src/content/runResultActions.js +11 -8
  58. package/extension/src/content/runReviewController.js +574 -0
  59. package/extension/src/content/runScrollLayout.js +21 -2
  60. package/extension/src/content/runTimelineView.js +315 -290
  61. package/extension/src/content/scopedPersistencePanelState.js +51 -6
  62. package/extension/src/content/selectionContextView.js +300 -0
  63. package/extension/src/content/sensitivePreflight.js +140 -0
  64. package/extension/src/content/sessionForkController.js +80 -17
  65. package/extension/src/content/sessionManager.js +1 -1
  66. package/extension/src/content/sessionPersistence.js +6 -1
  67. package/extension/src/content/settingsPanel.js +84 -55
  68. package/extension/src/content/settingsWorkbench.js +379 -0
  69. package/extension/src/content/sharedSessionViewSync.js +109 -0
  70. package/extension/src/content/subagentActivityView.js +195 -0
  71. package/extension/src/content/trackedChangeCaptureController.js +7 -3
  72. package/extension/src/content/updateIdle.js +4 -1
  73. package/extension/src/content/updateNotice.js +179 -56
  74. package/extension/src/content/updateSafetyReceiver.js +58 -0
  75. package/extension/src/content/writebackController.js +308 -2
  76. package/extension/src/content/writebackOrchestrator.js +213 -199
  77. package/extension/src/content/writebackPlan.js +231 -0
  78. package/extension/src/content/writebackRecovery.js +122 -0
  79. package/extension/src/content/writingStyleSettings.js +600 -0
  80. package/extension/src/content/writtenChangesView.js +152 -0
  81. package/extension/src/page/binaryAssetUploader.js +223 -23
  82. package/extension/src/page/fileMover.js +256 -0
  83. package/extension/src/page/overleafProjectSnapshot.js +1 -0
  84. package/extension/src/page/ownedDirectoryCleanup.js +94 -0
  85. package/extension/src/page/referenceProjects.js +149 -0
  86. package/extension/src/page/reviewContext.js +113 -0
  87. package/extension/src/page/saveState.js +20 -6
  88. package/extension/src/page/snapshotRouter.js +124 -48
  89. package/extension/src/page/textCoordinates.js +102 -0
  90. package/extension/src/page/textFileCreator.js +442 -71
  91. package/extension/src/page/trackedChangeCapture.js +53 -57
  92. package/extension/src/page/trackedChangeOwnership.js +216 -0
  93. package/extension/src/page/trackedChangeReplay.js +184 -0
  94. package/extension/src/page/trackedChangesLifecycle.js +343 -339
  95. package/extension/src/page/treeOperations.js +33 -12
  96. package/extension/src/page/updateSafetyProbe.js +41 -0
  97. package/extension/src/page/writebackReceiptJournal.js +114 -0
  98. package/extension/src/page/writebackRouter.js +242 -201
  99. package/extension/src/pageBridge.js +73 -102
  100. package/extension/src/popup.js +261 -65
  101. package/extension/src/shared/agentTranscript.js +41 -4
  102. package/extension/src/shared/auditRecords.js +73 -0
  103. package/extension/src/shared/changeHistoryModel.js +94 -0
  104. package/extension/src/shared/compatibility.js +22 -6
  105. package/extension/src/shared/compileAdapter.js +101 -37
  106. package/extension/src/shared/composerAttachmentDraft.js +151 -0
  107. package/extension/src/shared/directoryCleanupProof.js +43 -0
  108. package/extension/src/shared/failureReasons.js +329 -3
  109. package/extension/src/shared/globalPreferences.js +31 -14
  110. package/extension/src/shared/governanceRules.js +4 -37
  111. package/extension/src/shared/i18n.js +389 -25
  112. package/extension/src/shared/managedUpdateProjection.js +8 -5
  113. package/extension/src/shared/nativeRequestIdentity.js +1 -0
  114. package/extension/src/shared/pageRpcContract.js +9 -2
  115. package/extension/src/shared/projectFiles.js +21 -44
  116. package/extension/src/shared/providerProfiles.js +114 -3
  117. package/extension/src/shared/runActivityModel.js +361 -0
  118. package/extension/src/shared/runExecutionSnapshot.js +8 -2
  119. package/extension/src/shared/runExecutionSnapshotCodec.js +16 -4
  120. package/extension/src/shared/runInputQueue.js +48 -5
  121. package/extension/src/shared/scopedPersistenceBrowserAdapter.js +6 -3
  122. package/extension/src/shared/selectionContext.js +37 -0
  123. package/extension/src/shared/sessionState.js +166 -30
  124. package/extension/src/shared/settlementFacts.js +10 -2
  125. package/extension/src/shared/sharedSessionBridge.js +311 -0
  126. package/extension/src/shared/sharedSessionTransfer.js +140 -0
  127. package/extension/src/shared/staleGuard.js +118 -13
  128. package/extension/src/shared/storageDb.js +77 -78
  129. package/extension/src/shared/storageMigration.js +74 -111
  130. package/extension/src/shared/storageRunActions.js +211 -22
  131. package/extension/src/shared/storageValuePruning.js +40 -0
  132. package/extension/src/shared/subagentActivityModel.js +174 -0
  133. package/extension/src/shared/undoOperations.js +216 -6
  134. package/extension/src/shared/updateConsent.js +42 -11
  135. package/extension/src/shared/updateRuntimeIdentity.js +1 -1
  136. package/extension/src/shared/writebackIntent.js +78 -0
  137. package/extension/src/shared/writebackOperationEvidence.js +4 -15
  138. package/extension/src/shared/writebackSettlement.js +67 -48
  139. package/extension/src/shared/writingStyle.js +33 -0
  140. package/extension/styles/panel.css +1987 -385
  141. package/extension/styles/popup.css +110 -0
  142. package/native-host/src/anthropicBridgeRequest.js +6 -3
  143. package/native-host/src/anthropicBridgeResponse.js +6 -2
  144. package/native-host/src/anthropicMessagesBridge.js +24 -16
  145. package/native-host/src/bootstrapMigration.js +296 -0
  146. package/native-host/src/bootstrapMigrationContract.js +101 -0
  147. package/native-host/src/bootstrapUpdate.js +233 -0
  148. package/native-host/src/bootstrapUpdateWatchdog.js +44 -0
  149. package/native-host/src/chatBridgeResponse.js +10 -0
  150. package/native-host/src/chatCompletionsBridge.js +21 -15
  151. package/native-host/src/codexConfigPaths.js +106 -0
  152. package/native-host/src/codexHome.js +66 -73
  153. package/native-host/src/codexHomeScope.js +124 -0
  154. package/native-host/src/codexInheritedAuth.js +90 -0
  155. package/native-host/src/codexLocalSkills.js +301 -0
  156. package/native-host/src/codexModelRecovery.js +107 -0
  157. package/native-host/src/codexPromptAssembly.js +33 -12
  158. package/native-host/src/codexProviderLaunch.js +3 -3
  159. package/native-host/src/codexProviderTest.js +24 -26
  160. package/native-host/src/codexRuntimeIdentity.js +1 -0
  161. package/native-host/src/codexSessionRunner.js +211 -318
  162. package/native-host/src/codexSessionTiming.js +21 -8
  163. package/native-host/src/codexSkillIsolation.js +170 -0
  164. package/native-host/src/codexThreadFork.js +19 -3
  165. package/native-host/src/index.js +19 -7
  166. package/native-host/src/managedInstall.js +115 -39
  167. package/native-host/src/managedInstallRecovery.js +140 -0
  168. package/native-host/src/managedRuntimeEvidence.js +57 -0
  169. package/native-host/src/mirrorWorkspace.js +92 -22
  170. package/native-host/src/nativeDoctor.js +15 -0
  171. package/native-host/src/nativeEnvironment.js +70 -0
  172. package/native-host/src/nativeHelperLifecycle.js +21 -0
  173. package/native-host/src/nativeHostRegistration.js +33 -0
  174. package/native-host/src/nativeQuotas.js +15 -8
  175. package/native-host/src/nativeRunLifecycle.js +71 -0
  176. package/native-host/src/overleafWorkspaceRules.js +16 -0
  177. package/native-host/src/processStopBarrier.js +115 -0
  178. package/native-host/src/projectPathPlatform.js +30 -0
  179. package/native-host/src/providerHttp.js +80 -0
  180. package/native-host/src/providerModelCatalog.js +4 -3
  181. package/native-host/src/providerProfile.js +17 -3
  182. package/native-host/src/providerReasoning.js +10 -11
  183. package/native-host/src/providerRuntime.js +1 -0
  184. package/native-host/src/providerStore.js +76 -14
  185. package/native-host/src/releaseTransport.js +14 -5
  186. package/native-host/src/responsesPassthroughBridge.js +266 -52
  187. package/native-host/src/runtimeInstaller.js +21 -1
  188. package/native-host/src/selectionScope.js +86 -0
  189. package/native-host/src/skills/parallel-subagents/SKILL.md +31 -3
  190. package/native-host/src/skills/writing-style-builder/SKILL.md +146 -0
  191. package/native-host/src/subagentAdmission.js +26 -0
  192. package/native-host/src/subagentBroker.js +176 -159
  193. package/native-host/src/subagentTelemetry.js +325 -0
  194. package/native-host/src/subagentWorkspace.js +103 -0
  195. package/native-host/src/subagentWorkspacePath.js +34 -0
  196. package/native-host/src/taskRunnerRuntime.js +130 -57
  197. package/native-host/src/tomlStatements.js +140 -0
  198. package/native-host/src/turnAttachments.js +113 -0
  199. package/native-host/src/updateManager.js +406 -38
  200. package/native-host/src/updateNetworkControl.js +75 -0
  201. package/native-host/src/updateTrust.js +21 -2
  202. package/native-host/src/vendor/pdfjs/LICENSE +177 -0
  203. package/native-host/src/vendor/pdfjs/SOURCE.txt +3 -0
  204. package/native-host/src/vendor/pdfjs/cmaps/78-EUC-H.bcmap +0 -0
  205. package/native-host/src/vendor/pdfjs/cmaps/78-EUC-V.bcmap +0 -0
  206. package/native-host/src/vendor/pdfjs/cmaps/78-H.bcmap +0 -0
  207. package/native-host/src/vendor/pdfjs/cmaps/78-RKSJ-H.bcmap +0 -0
  208. package/native-host/src/vendor/pdfjs/cmaps/78-RKSJ-V.bcmap +0 -0
  209. package/native-host/src/vendor/pdfjs/cmaps/78-V.bcmap +0 -0
  210. package/native-host/src/vendor/pdfjs/cmaps/78ms-RKSJ-H.bcmap +0 -0
  211. package/native-host/src/vendor/pdfjs/cmaps/78ms-RKSJ-V.bcmap +0 -0
  212. package/native-host/src/vendor/pdfjs/cmaps/83pv-RKSJ-H.bcmap +0 -0
  213. package/native-host/src/vendor/pdfjs/cmaps/90ms-RKSJ-H.bcmap +0 -0
  214. package/native-host/src/vendor/pdfjs/cmaps/90ms-RKSJ-V.bcmap +0 -0
  215. package/native-host/src/vendor/pdfjs/cmaps/90msp-RKSJ-H.bcmap +0 -0
  216. package/native-host/src/vendor/pdfjs/cmaps/90msp-RKSJ-V.bcmap +0 -0
  217. package/native-host/src/vendor/pdfjs/cmaps/90pv-RKSJ-H.bcmap +0 -0
  218. package/native-host/src/vendor/pdfjs/cmaps/90pv-RKSJ-V.bcmap +0 -0
  219. package/native-host/src/vendor/pdfjs/cmaps/Add-H.bcmap +0 -0
  220. package/native-host/src/vendor/pdfjs/cmaps/Add-RKSJ-H.bcmap +0 -0
  221. package/native-host/src/vendor/pdfjs/cmaps/Add-RKSJ-V.bcmap +0 -0
  222. package/native-host/src/vendor/pdfjs/cmaps/Add-V.bcmap +0 -0
  223. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-0.bcmap +0 -0
  224. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-1.bcmap +0 -0
  225. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-2.bcmap +0 -0
  226. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-3.bcmap +0 -0
  227. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-4.bcmap +0 -0
  228. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-5.bcmap +0 -0
  229. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-6.bcmap +0 -0
  230. package/native-host/src/vendor/pdfjs/cmaps/Adobe-CNS1-UCS2.bcmap +0 -0
  231. package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-0.bcmap +0 -0
  232. package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-1.bcmap +0 -0
  233. package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-2.bcmap +0 -0
  234. package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-3.bcmap +0 -0
  235. package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-4.bcmap +0 -0
  236. package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-5.bcmap +0 -0
  237. package/native-host/src/vendor/pdfjs/cmaps/Adobe-GB1-UCS2.bcmap +0 -0
  238. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-0.bcmap +0 -0
  239. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-1.bcmap +0 -0
  240. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-2.bcmap +0 -0
  241. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-3.bcmap +0 -0
  242. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-4.bcmap +0 -0
  243. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-5.bcmap +0 -0
  244. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-6.bcmap +0 -0
  245. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Japan1-UCS2.bcmap +0 -0
  246. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Korea1-0.bcmap +0 -0
  247. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Korea1-1.bcmap +0 -0
  248. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Korea1-2.bcmap +0 -0
  249. package/native-host/src/vendor/pdfjs/cmaps/Adobe-Korea1-UCS2.bcmap +0 -0
  250. package/native-host/src/vendor/pdfjs/cmaps/B5-H.bcmap +0 -0
  251. package/native-host/src/vendor/pdfjs/cmaps/B5-V.bcmap +0 -0
  252. package/native-host/src/vendor/pdfjs/cmaps/B5pc-H.bcmap +0 -0
  253. package/native-host/src/vendor/pdfjs/cmaps/B5pc-V.bcmap +0 -0
  254. package/native-host/src/vendor/pdfjs/cmaps/CNS-EUC-H.bcmap +0 -0
  255. package/native-host/src/vendor/pdfjs/cmaps/CNS-EUC-V.bcmap +0 -0
  256. package/native-host/src/vendor/pdfjs/cmaps/CNS1-H.bcmap +0 -0
  257. package/native-host/src/vendor/pdfjs/cmaps/CNS1-V.bcmap +0 -0
  258. package/native-host/src/vendor/pdfjs/cmaps/CNS2-H.bcmap +0 -0
  259. package/native-host/src/vendor/pdfjs/cmaps/CNS2-V.bcmap +3 -0
  260. package/native-host/src/vendor/pdfjs/cmaps/ETHK-B5-H.bcmap +0 -0
  261. package/native-host/src/vendor/pdfjs/cmaps/ETHK-B5-V.bcmap +0 -0
  262. package/native-host/src/vendor/pdfjs/cmaps/ETen-B5-H.bcmap +0 -0
  263. package/native-host/src/vendor/pdfjs/cmaps/ETen-B5-V.bcmap +0 -0
  264. package/native-host/src/vendor/pdfjs/cmaps/ETenms-B5-H.bcmap +3 -0
  265. package/native-host/src/vendor/pdfjs/cmaps/ETenms-B5-V.bcmap +0 -0
  266. package/native-host/src/vendor/pdfjs/cmaps/EUC-H.bcmap +0 -0
  267. package/native-host/src/vendor/pdfjs/cmaps/EUC-V.bcmap +0 -0
  268. package/native-host/src/vendor/pdfjs/cmaps/Ext-H.bcmap +0 -0
  269. package/native-host/src/vendor/pdfjs/cmaps/Ext-RKSJ-H.bcmap +0 -0
  270. package/native-host/src/vendor/pdfjs/cmaps/Ext-RKSJ-V.bcmap +0 -0
  271. package/native-host/src/vendor/pdfjs/cmaps/Ext-V.bcmap +0 -0
  272. package/native-host/src/vendor/pdfjs/cmaps/GB-EUC-H.bcmap +0 -0
  273. package/native-host/src/vendor/pdfjs/cmaps/GB-EUC-V.bcmap +0 -0
  274. package/native-host/src/vendor/pdfjs/cmaps/GB-H.bcmap +4 -0
  275. package/native-host/src/vendor/pdfjs/cmaps/GB-V.bcmap +0 -0
  276. package/native-host/src/vendor/pdfjs/cmaps/GBK-EUC-H.bcmap +0 -0
  277. package/native-host/src/vendor/pdfjs/cmaps/GBK-EUC-V.bcmap +0 -0
  278. package/native-host/src/vendor/pdfjs/cmaps/GBK2K-H.bcmap +0 -0
  279. package/native-host/src/vendor/pdfjs/cmaps/GBK2K-V.bcmap +0 -0
  280. package/native-host/src/vendor/pdfjs/cmaps/GBKp-EUC-H.bcmap +0 -0
  281. package/native-host/src/vendor/pdfjs/cmaps/GBKp-EUC-V.bcmap +0 -0
  282. package/native-host/src/vendor/pdfjs/cmaps/GBT-EUC-H.bcmap +0 -0
  283. package/native-host/src/vendor/pdfjs/cmaps/GBT-EUC-V.bcmap +0 -0
  284. package/native-host/src/vendor/pdfjs/cmaps/GBT-H.bcmap +0 -0
  285. package/native-host/src/vendor/pdfjs/cmaps/GBT-V.bcmap +0 -0
  286. package/native-host/src/vendor/pdfjs/cmaps/GBTpc-EUC-H.bcmap +0 -0
  287. package/native-host/src/vendor/pdfjs/cmaps/GBTpc-EUC-V.bcmap +0 -0
  288. package/native-host/src/vendor/pdfjs/cmaps/GBpc-EUC-H.bcmap +0 -0
  289. package/native-host/src/vendor/pdfjs/cmaps/GBpc-EUC-V.bcmap +0 -0
  290. package/native-host/src/vendor/pdfjs/cmaps/H.bcmap +0 -0
  291. package/native-host/src/vendor/pdfjs/cmaps/HKdla-B5-H.bcmap +0 -0
  292. package/native-host/src/vendor/pdfjs/cmaps/HKdla-B5-V.bcmap +0 -0
  293. package/native-host/src/vendor/pdfjs/cmaps/HKdlb-B5-H.bcmap +0 -0
  294. package/native-host/src/vendor/pdfjs/cmaps/HKdlb-B5-V.bcmap +0 -0
  295. package/native-host/src/vendor/pdfjs/cmaps/HKgccs-B5-H.bcmap +0 -0
  296. package/native-host/src/vendor/pdfjs/cmaps/HKgccs-B5-V.bcmap +0 -0
  297. package/native-host/src/vendor/pdfjs/cmaps/HKm314-B5-H.bcmap +0 -0
  298. package/native-host/src/vendor/pdfjs/cmaps/HKm314-B5-V.bcmap +0 -0
  299. package/native-host/src/vendor/pdfjs/cmaps/HKm471-B5-H.bcmap +0 -0
  300. package/native-host/src/vendor/pdfjs/cmaps/HKm471-B5-V.bcmap +0 -0
  301. package/native-host/src/vendor/pdfjs/cmaps/HKscs-B5-H.bcmap +0 -0
  302. package/native-host/src/vendor/pdfjs/cmaps/HKscs-B5-V.bcmap +0 -0
  303. package/native-host/src/vendor/pdfjs/cmaps/Hankaku.bcmap +0 -0
  304. package/native-host/src/vendor/pdfjs/cmaps/Hiragana.bcmap +0 -0
  305. package/native-host/src/vendor/pdfjs/cmaps/KSC-EUC-H.bcmap +0 -0
  306. package/native-host/src/vendor/pdfjs/cmaps/KSC-EUC-V.bcmap +0 -0
  307. package/native-host/src/vendor/pdfjs/cmaps/KSC-H.bcmap +0 -0
  308. package/native-host/src/vendor/pdfjs/cmaps/KSC-Johab-H.bcmap +0 -0
  309. package/native-host/src/vendor/pdfjs/cmaps/KSC-Johab-V.bcmap +0 -0
  310. package/native-host/src/vendor/pdfjs/cmaps/KSC-V.bcmap +0 -0
  311. package/native-host/src/vendor/pdfjs/cmaps/KSCms-UHC-H.bcmap +0 -0
  312. package/native-host/src/vendor/pdfjs/cmaps/KSCms-UHC-HW-H.bcmap +0 -0
  313. package/native-host/src/vendor/pdfjs/cmaps/KSCms-UHC-HW-V.bcmap +0 -0
  314. package/native-host/src/vendor/pdfjs/cmaps/KSCms-UHC-V.bcmap +0 -0
  315. package/native-host/src/vendor/pdfjs/cmaps/KSCpc-EUC-H.bcmap +0 -0
  316. package/native-host/src/vendor/pdfjs/cmaps/KSCpc-EUC-V.bcmap +0 -0
  317. package/native-host/src/vendor/pdfjs/cmaps/Katakana.bcmap +0 -0
  318. package/native-host/src/vendor/pdfjs/cmaps/LICENSE +36 -0
  319. package/native-host/src/vendor/pdfjs/cmaps/NWP-H.bcmap +0 -0
  320. package/native-host/src/vendor/pdfjs/cmaps/NWP-V.bcmap +0 -0
  321. package/native-host/src/vendor/pdfjs/cmaps/RKSJ-H.bcmap +0 -0
  322. package/native-host/src/vendor/pdfjs/cmaps/RKSJ-V.bcmap +0 -0
  323. package/native-host/src/vendor/pdfjs/cmaps/Roman.bcmap +0 -0
  324. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UCS2-H.bcmap +0 -0
  325. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UCS2-V.bcmap +0 -0
  326. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF16-H.bcmap +0 -0
  327. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF16-V.bcmap +0 -0
  328. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF32-H.bcmap +0 -0
  329. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF32-V.bcmap +0 -0
  330. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF8-H.bcmap +0 -0
  331. package/native-host/src/vendor/pdfjs/cmaps/UniCNS-UTF8-V.bcmap +0 -0
  332. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UCS2-H.bcmap +0 -0
  333. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UCS2-V.bcmap +0 -0
  334. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF16-H.bcmap +0 -0
  335. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF16-V.bcmap +0 -0
  336. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF32-H.bcmap +0 -0
  337. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF32-V.bcmap +0 -0
  338. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF8-H.bcmap +0 -0
  339. package/native-host/src/vendor/pdfjs/cmaps/UniGB-UTF8-V.bcmap +0 -0
  340. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UCS2-H.bcmap +0 -0
  341. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UCS2-HW-H.bcmap +0 -0
  342. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UCS2-HW-V.bcmap +0 -0
  343. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UCS2-V.bcmap +0 -0
  344. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF16-H.bcmap +0 -0
  345. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF16-V.bcmap +0 -0
  346. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF32-H.bcmap +0 -0
  347. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF32-V.bcmap +0 -0
  348. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF8-H.bcmap +0 -0
  349. package/native-host/src/vendor/pdfjs/cmaps/UniJIS-UTF8-V.bcmap +0 -0
  350. package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF16-H.bcmap +0 -0
  351. package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF16-V.bcmap +0 -0
  352. package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF32-H.bcmap +0 -0
  353. package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF32-V.bcmap +0 -0
  354. package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF8-H.bcmap +0 -0
  355. package/native-host/src/vendor/pdfjs/cmaps/UniJIS2004-UTF8-V.bcmap +0 -0
  356. package/native-host/src/vendor/pdfjs/cmaps/UniJISPro-UCS2-HW-V.bcmap +0 -0
  357. package/native-host/src/vendor/pdfjs/cmaps/UniJISPro-UCS2-V.bcmap +0 -0
  358. package/native-host/src/vendor/pdfjs/cmaps/UniJISPro-UTF8-V.bcmap +0 -0
  359. package/native-host/src/vendor/pdfjs/cmaps/UniJISX0213-UTF32-H.bcmap +0 -0
  360. package/native-host/src/vendor/pdfjs/cmaps/UniJISX0213-UTF32-V.bcmap +0 -0
  361. package/native-host/src/vendor/pdfjs/cmaps/UniJISX02132004-UTF32-H.bcmap +0 -0
  362. package/native-host/src/vendor/pdfjs/cmaps/UniJISX02132004-UTF32-V.bcmap +0 -0
  363. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UCS2-H.bcmap +0 -0
  364. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UCS2-V.bcmap +0 -0
  365. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF16-H.bcmap +0 -0
  366. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF16-V.bcmap +0 -0
  367. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF32-H.bcmap +0 -0
  368. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF32-V.bcmap +0 -0
  369. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF8-H.bcmap +0 -0
  370. package/native-host/src/vendor/pdfjs/cmaps/UniKS-UTF8-V.bcmap +0 -0
  371. package/native-host/src/vendor/pdfjs/cmaps/V.bcmap +0 -0
  372. package/native-host/src/vendor/pdfjs/cmaps/WP-Symbol.bcmap +0 -0
  373. package/native-host/src/vendor/pdfjs/pdf.mjs +26740 -0
  374. package/native-host/src/vendor/pdfjs/pdf.worker.mjs +62201 -0
  375. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitDingbats.pfb +0 -0
  376. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitFixed.pfb +0 -0
  377. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitFixedBold.pfb +0 -0
  378. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitFixedBoldItalic.pfb +0 -0
  379. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitFixedItalic.pfb +0 -0
  380. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitSerif.pfb +0 -0
  381. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitSerifBold.pfb +0 -0
  382. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitSerifBoldItalic.pfb +0 -0
  383. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitSerifItalic.pfb +0 -0
  384. package/native-host/src/vendor/pdfjs/standard_fonts/FoxitSymbol.pfb +0 -0
  385. package/native-host/src/vendor/pdfjs/standard_fonts/LICENSE_FOXIT +27 -0
  386. package/native-host/src/vendor/pdfjs/standard_fonts/LICENSE_LIBERATION +102 -0
  387. package/native-host/src/vendor/pdfjs/standard_fonts/LiberationSans-Bold.ttf +0 -0
  388. package/native-host/src/vendor/pdfjs/standard_fonts/LiberationSans-BoldItalic.ttf +0 -0
  389. package/native-host/src/vendor/pdfjs/standard_fonts/LiberationSans-Italic.ttf +0 -0
  390. package/native-host/src/vendor/pdfjs/standard_fonts/LiberationSans-Regular.ttf +0 -0
  391. package/native-host/src/writingStyleEvidence.js +102 -0
  392. package/native-host/src/writingStylePdfWorker.js +35 -0
  393. package/native-host/src/writingStyleProfile.js +165 -0
  394. package/native-host/src/writingStyleRuntime.js +475 -0
  395. package/native-host/src/writingStyleStore.js +73 -0
  396. package/package.json +1 -1
  397. package/scripts/install-managed.mjs +1 -0
@@ -1,9 +1,11 @@
1
1
  (function (root, factory) {
2
- if (typeof module === 'object' && module.exports) module.exports = factory();
3
- else root.CodexOverleafTextFileCreator = factory();
4
- })(typeof window !== 'undefined' ? window : globalThis, function () {
2
+ if (typeof module === 'object' && module.exports) module.exports = factory(require('./ownedDirectoryCleanup'));
3
+ else root.CodexOverleafTextFileCreator = factory(root.CodexOverleafOwnedDirectoryCleanup);
4
+ })(typeof window !== 'undefined' ? window : globalThis, function (OwnedDirectoryCleanup) {
5
5
  'use strict';
6
6
 
7
+ const MAX_CONFIRMATION_OBSERVATIONS = 8;
8
+
7
9
  function create(deps = {}) {
8
10
  const { window, document, treeOperations, uploadHelpers, snapshotRouter } = deps;
9
11
  const { getProjectId, findFileTreeNode, projectPathExists, collectProjectTextPaths,
@@ -14,6 +16,162 @@
14
16
  };
15
17
  const normalizeDomText = value => String(value || '').replace(/\s+/g, ' ').trim();
16
18
  const delay = ms => new Promise(resolve => window.setTimeout(resolve, ms));
19
+ const now = typeof deps.now === 'function' ? deps.now : Date.now;
20
+ const verificationReads = new Set();
21
+ const uploadParentProofs = new WeakMap();
22
+ const verificationBudget = () => ({ remainingMs: 90000 });
23
+ async function pauseVerification(budget, durationMs) {
24
+ const started = now();
25
+ await delay(Math.max(0, Math.min(durationMs, budget.remainingMs)));
26
+ budget.remainingMs = Math.max(0, budget.remainingMs - Math.max(0, now() - started));
27
+ }
28
+ function assertVerificationOwner(projectId, isCurrent) {
29
+ if (!projectId || getProjectId() !== projectId) {
30
+ throw Object.assign(new Error('The project changed during writeback.'), { code: 'aborted_project_changed' });
31
+ }
32
+ if (isCurrent?.() === false) {
33
+ throw Object.assign(new Error('Writeback was cancelled.'), { code: 'codex_cancelled' });
34
+ }
35
+ }
36
+ async function boundedSnapshotRead(params, controller, timeoutMs) {
37
+ const deadline = now() + timeoutMs;
38
+ const expired = diagnostics => Object.assign(new Error('The server snapshot request timed out.'), {
39
+ code: 'source_zip_unavailable', diagnostics,
40
+ technicalMessage: 'The server snapshot request reached its deletion verification deadline.'
41
+ });
42
+ let timer, onAbort;
43
+ try {
44
+ const interruption = new Promise((_, reject) => {
45
+ onAbort = () => reject(Object.assign(new Error('Writeback was cancelled.'), { code: 'codex_cancelled' }));
46
+ controller?.signal.addEventListener('abort', onAbort, { once: true });
47
+ timer = window.setTimeout(() => { reject(expired(null)); controller?.abort(); }, timeoutMs);
48
+ });
49
+ const result = await Promise.race([snapshotRouter.fetchProjectZipSnapshot(params), interruption]);
50
+ if (now() >= deadline) throw expired(result?.diagnostics);
51
+ return result;
52
+ } finally {
53
+ (window.clearTimeout || globalThis.clearTimeout)(timer);
54
+ controller?.signal.removeEventListener('abort', onAbort);
55
+ }
56
+ }
57
+ async function readServerSnapshot({ projectId, isCurrent, budget = verificationBudget(), includeBinaryFiles = false,
58
+ singleAttempt = false, deadlineAt = Infinity }) {
59
+ const started = now(), deadline = Math.min(started + Math.max(0, budget.remainingMs), deadlineAt);
60
+ const attemptLimit = singleAttempt ? 1 : 3;
61
+ let last;
62
+ try {
63
+ for (let attempt = 0; attempt < attemptLimit && now() < deadline; attempt++) {
64
+ assertVerificationOwner(projectId, isCurrent);
65
+ const Controller = window.AbortController || globalThis.AbortController;
66
+ const controller = typeof Controller === 'function' ? new Controller() : null;
67
+ if (controller) verificationReads.add(controller);
68
+ let result;
69
+ try {
70
+ const params = { force: true, maxAgeMs: 0,
71
+ serverOnly: true, writebackVerification: true, includeBinaryFiles, includeContent: true,
72
+ signal: controller?.signal, saveCheckId: String(now()) + '-' + attempt,
73
+ zipTimeoutMs: Math.min(30000, Math.max(1, deadline - now())) };
74
+ result = singleAttempt ? await boundedSnapshotRead(params, controller, params.zipTimeoutMs)
75
+ : await snapshotRouter.fetchProjectZipSnapshot(params);
76
+ } catch (error) {
77
+ if (singleAttempt) assertVerificationOwner(projectId, isCurrent);
78
+ if (['codex_cancelled', 'aborted_project_changed'].includes(error?.code)) throw error;
79
+ result = { ok: false, reason: error?.message || String(error), ...(singleAttempt ? {
80
+ reason: error?.technicalMessage || error?.message || String(error), diagnostics: error?.diagnostics,
81
+ status: error?.status || error?.statusCode || error?.response?.status, code: error?.code, retryable: error?.retryable
82
+ } : {}) };
83
+ } finally { if (controller) verificationReads.delete(controller); }
84
+ assertVerificationOwner(projectId, isCurrent);
85
+ if (result?.ok === true && Array.isArray(result.files)) {
86
+ const skipped = [...(Array.isArray(result.skipped) ? result.skipped : []),
87
+ ...(Array.isArray(result.capabilities?.skipped) ? result.capabilities.skipped : [])];
88
+ if (result.pathIdentityComplete === false || result.capabilities?.pathIdentityComplete === false
89
+ || skipped.some(file => file?.unknownPath || ['zip_path_invalid', 'zip_path_duplicate'].includes(file?.reason))) {
90
+ throw Object.assign(new Error('The server snapshot contains unresolved file identities; absence cannot be confirmed.'), {
91
+ code: 'source_zip_unavailable', originalCode: 'zip_path_identity_incomplete', retryable: false,
92
+ diagnostics: { ...(result.diagnostics || {}), pathIdentityComplete: false }
93
+ });
94
+ }
95
+ return result;
96
+ }
97
+ last = Object.assign(new Error('The server project snapshot could not be read.'), {
98
+ code: 'source_zip_unavailable', diagnostics: result?.diagnostics || null,
99
+ technicalMessage: result?.reason || 'No complete server snapshot was returned.',
100
+ ...(singleAttempt ? { status: result?.status || result?.statusCode, originalCode: result?.code, retryable: result?.retryable } : {})
101
+ });
102
+ const attempts = result?.diagnostics?.attempts || [];
103
+ if (attempts.length && attempts.every(item => [401, 403].includes(item.status))) break;
104
+ if (attempt < attemptLimit - 1 && now() < deadline) await delay(Math.min(1000 * (2 ** attempt), deadline - now()));
105
+ }
106
+ throw last || Object.assign(new Error('The project verification budget was exhausted.'), { code: 'source_zip_unavailable' });
107
+ } finally { budget.remainingMs = Math.max(0, budget.remainingMs - Math.max(0, now() - started)); }
108
+ }
109
+ function verificationFailure(error, changedDocument, phase) {
110
+ const interrupted = ['codex_cancelled', 'aborted_project_changed'].includes(error?.code);
111
+ const code = interrupted ? error.code : changedDocument ? 'file_upload_unconfirmed' : 'source_zip_unavailable';
112
+ const reason = interrupted ? error.message : changedDocument
113
+ ? 'The file was submitted, but its saved content could not be confirmed.'
114
+ : 'The project could not be checked before uploading this file.';
115
+ return { ok: false, code, reason, changedDocument, diagnostics: {
116
+ verificationPhase: phase, ...(error?.diagnostics || {}), reason: error?.technicalMessage || error?.message || ''
117
+ }, failure: { code, stage: changedDocument ? 'verify' : 'preflight', severity: changedDocument ? 'warning' : 'blocked',
118
+ retryable: !interrupted, terminalState: interrupted ? 'cancelled' : changedDocument ? 'needs_review' : 'blocked',
119
+ changedDocument, userMessage: reason, nextAction: interrupted ? '' : 'Retry sync to check the file and upload only if it is missing.',
120
+ technicalMessage: error?.technicalMessage || error?.message || '' } };
121
+ }
122
+ async function makeSaveReceipt(projectId, path, content) {
123
+ const cryptoApi = window.crypto || globalThis.crypto;
124
+ if (!cryptoApi?.subtle || typeof TextEncoder !== 'function') return undefined;
125
+ const digest = await cryptoApi.subtle.digest('SHA-256', new TextEncoder().encode(content));
126
+ return { v: 1, source: 'overleaf-zip', projectId, path,
127
+ sha256: Array.from(new Uint8Array(digest), byte => byte.toString(16).padStart(2, '0')).join(''),
128
+ verifiedAt: new Date().toISOString() };
129
+ }
130
+ async function beginCreateBatch(operations, options = {}) {
131
+ const projectId = getProjectId(), budget = verificationBudget();
132
+ const batch = { projectId, budget, targets: new Set(operations.map(op => op.path)),
133
+ before: new Map(), pending: new Map(), blocked: new Set(), isCurrent: options.isCurrent, error: null };
134
+ try {
135
+ const snapshot = await readServerSnapshot({ projectId, budget, isCurrent: options.isCurrent });
136
+ batch.before = new Map(snapshot.files.map(file => [file.path, file]));
137
+ } catch (error) { batch.error = error; }
138
+ return batch;
139
+ }
140
+ async function finishCreateBatch(batch, entries, appliedEntries = []) {
141
+ let observed = new Map(), lastError = batch.error;
142
+ try {
143
+ for (let attempt = 0; entries.length && attempt < 3 && batch.budget.remainingMs > 0; attempt++) {
144
+ const snapshot = await readServerSnapshot(batch);
145
+ observed = new Map(snapshot.files.map(file => [file.path, file]));
146
+ if (entries.every(entry => observed.get(entry.operation.path)?.content === entry.operation.content)) break;
147
+ if (attempt < 2) await pauseVerification(batch.budget, 500 * (2 ** attempt));
148
+ }
149
+ assertVerificationOwner(batch.projectId, batch.isCurrent);
150
+ } catch (error) { lastError = error; }
151
+ const results = [];
152
+ for (const entry of entries) {
153
+ const operation = entry.operation;
154
+ if (!lastError && observed.get(operation.path)?.content === operation.content) {
155
+ results.push({ operation, result: { ok: true, verified: true, changedDocument: true,
156
+ method: 'overleaf.native-text-upload', verification: 'overleaf-zip',
157
+ createdDirectories: entry.result?.createdDirectories || [], fileUploadAttempted: entry.result?.fileUploadAttempted === true,
158
+ saveReceipt: await makeSaveReceipt(batch.projectId, operation.path, operation.content) } });
159
+ } else {
160
+ results.push({ operation, result: { ...verificationFailure(lastError || new Error(
161
+ observed.has(operation.path) ? 'Server content differs from the submitted file.' : 'The file is not yet present in the server snapshot.'
162
+ ), true, 'post-upload'), createdDirectories: entry.result?.createdDirectories || [],
163
+ fileUploadAttempted: entry.result?.fileUploadAttempted === true } });
164
+ }
165
+ }
166
+ // The final fresh snapshot can also prove unrelated edits in this batch.
167
+ if (!lastError) for (const entry of appliedEntries) {
168
+ const content = entry.result?.verifiedContent ?? (entry.operation?.type === 'create' ? entry.operation.content : undefined);
169
+ if (typeof content === 'string' && observed.get(entry.operation.path)?.content === content) {
170
+ entry.result.saveReceipt = await makeSaveReceipt(batch.projectId, entry.operation.path, content);
171
+ }
172
+ }
173
+ return results;
174
+ }
17
175
  const controlName = node => {
18
176
  const labelledBy = (node.getAttribute('aria-labelledby') || '').split(/\s+/).filter(Boolean)
19
177
  .map(id => document.getElementById?.(id)?.textContent || '').join(' ');
@@ -47,13 +205,16 @@
47
205
  return folder;
48
206
  };
49
207
 
208
+ const directoryCleanup = OwnedDirectoryCleanup.create({ document, treeOperations, delay, now,
209
+ readServerSnapshot, assertVerificationOwner, normalizeDomText, normalizeSafeProjectPath });
210
+ const { folderIdentity, cleanupCreatedDirectories } = directoryCleanup;
211
+
50
212
  async function createFolderWithDom(entryPath, options = {}) {
51
213
  const target = normalizeSafeProjectPath(entryPath);
52
214
  const projectId = getProjectId();
53
215
  const assertCurrent = () => {
54
- if (!target || !projectId || getProjectId() !== projectId || options.isCurrent?.() === false) {
55
- throw new Error('Project changed or folder creation was cancelled.');
56
- }
216
+ if (!target) throw Object.assign(new Error('A valid folder path is required.'), { code: 'invalid_project_path' });
217
+ assertVerificationOwner(projectId, options.isCurrent);
57
218
  };
58
219
  const visible = node => Boolean(node && !node.disabled && node.getClientRects?.().length);
59
220
  const dialogs = () => Array.from(document.querySelectorAll('[role="dialog"]')).filter(visible);
@@ -87,7 +248,15 @@
87
248
  toggle.click();
88
249
  await waitFor(() => findFolder(parentPath)?.getAttribute('aria-expanded') === 'true');
89
250
  };
90
- let ownedDialog = null;
251
+ let ownedDialog = null, createdProof = null;
252
+ const publishProof = () => {
253
+ if (!createdProof) return;
254
+ if (getProjectId() === projectId) {
255
+ const row = findFolder(target);
256
+ if (row) { createdProof.nativeFolderId = folderIdentity(row); createdProof.observedAfterCreate = true; }
257
+ }
258
+ options.onCreatedDirectory?.({ ...createdProof });
259
+ };
91
260
  try {
92
261
  assertCurrent();
93
262
  if (findFolder(target)) return;
@@ -116,12 +285,18 @@
116
285
  await delay(0);
117
286
  assertCurrent();
118
287
  if (findFolder(target) || projectPathExists(target)) throw new Error('The target path appeared during creation; no replacement was attempted.');
288
+ const confirm = choose(ownedDialog, /^(?:Create|创建)$/i);
289
+ createdProof = { v: 1, projectId, path: target, nativeFolderId: '', ownershipConfirmed: false, observedAfterCreate: false };
290
+ options.onCreatedDirectory?.({ ...createdProof });
119
291
  options.onMutation?.();
120
- choose(ownedDialog, /^(?:Create|创建)$/i).click();
292
+ confirm.click();
293
+ publishProof();
121
294
  await waitFor(() => !dialogs().length);
122
295
  await expandParent();
123
296
  await waitFor(() => findFolder(target));
297
+ publishProof();
124
298
  } finally {
299
+ publishProof();
125
300
  if (ownedDialog && getProjectId() === projectId && dialogs().includes(ownedDialog)) {
126
301
  const cancel = Array.from(ownedDialog.querySelectorAll('button')).filter(node => visible(node) && /^(?:Cancel|取消)$/i.test(controlName(node)));
127
302
  if (cancel.length === 1) cancel[0].click();
@@ -138,16 +313,20 @@
138
313
  if (!target || !projectId || typeof operation.content !== 'string' || !window.CodexOverleafProjectFiles?.isTextProjectPath(target)) {
139
314
  return { ok: false, code: 'invalid_project_path', reason: 'A valid text-file path and project are required.' };
140
315
  }
141
- let mutationAttempted = false;
142
- const assertCurrent = () => {
143
- if (getProjectId() !== projectId || options.isCurrent?.() === false) {
144
- throw new Error('Project changed or the write was cancelled.');
145
- }
316
+ let mutationAttempted = false, fileUploadAttempted = false;
317
+ const createdDirectories = [];
318
+ const recordDirectory = proof => {
319
+ const index = createdDirectories.findIndex(item => item.path === proof.path);
320
+ if (index < 0) createdDirectories.push(proof); else createdDirectories[index] = proof;
146
321
  };
322
+ let verificationPhase = 'preflight', zipReadCount = 0, lastZipFailure = null;
323
+ const readBudget = options.batch?.budget || verificationBudget();
324
+ let uploadDialog = null;
325
+ const assertCurrent = () => assertVerificationOwner(projectId, options.isCurrent);
147
326
  const visible = node => Boolean(node && !node.disabled && node.getClientRects?.().length);
148
327
  const dialogs = () => Array.from(document.querySelectorAll('[role="dialog"]')).filter(visible);
149
- const waitFor = async read => {
150
- const until = Date.now() + 5000;
328
+ const waitFor = async (read, timeoutMs = 5000) => {
329
+ const until = Date.now() + timeoutMs;
151
330
  do {
152
331
  assertCurrent();
153
332
  invalidateDomProjectPathCache();
@@ -174,13 +353,12 @@
174
353
  await waitFor(() => findFolder(folderPath)?.getAttribute('aria-expanded') === 'true');
175
354
  };
176
355
  const readServerFiles = async () => {
177
- assertCurrent();
178
- snapshotRouter.invalidateCache?.();
179
- const result = await snapshotRouter.fetchProjectZipSnapshot({ force: true, maxAgeMs: 0,
180
- includeBinaryFiles: false, includeContent: true, zipTimeoutMs: 15000 });
181
- assertCurrent();
182
- if (result?.ok !== true || !Array.isArray(result.files)) throw new Error('Server source ZIP is unavailable; text creation cannot be verified safely.');
183
- return result.files;
356
+ zipReadCount += 1;
357
+ try {
358
+ const result = await readServerSnapshot({ projectId, budget: readBudget,
359
+ isCurrent: () => { assertCurrent(); return true; } });
360
+ return result.files;
361
+ } catch (error) { lastZipFailure = { reason: error.message, diagnostics: error.diagnostics }; throw error; }
184
362
  };
185
363
  const uploadText = async () => {
186
364
  const parentPath = target.split('/').slice(0, -1).join('/');
@@ -197,6 +375,7 @@
197
375
  const tree = document.querySelector('#ide-rail-tabs-tabpane-file-tree, #ide-redesign-file-tree, .ide-redesign-file-tree, .file-tree');
198
376
  chooseButton(tree, /^(?:Upload|上传)$/i).click();
199
377
  const dialog = await waitFor(() => dialogs().length === 1 && dialogs()[0]);
378
+ uploadDialog = dialog;
200
379
  const input = await waitFor(() => uploadHelpers.chooseOverleafFileInput(dialog.querySelectorAll('input[type="file"]')));
201
380
  if (projectPathExists(target)) throw new Error('The target appeared before upload; no replacement was attempted.');
202
381
  const file = new window.File([operation.content], target.split('/').pop(), { type: 'text/plain' });
@@ -204,10 +383,23 @@
204
383
  data.items.add(file);
205
384
  assertCurrent();
206
385
  uploadHelpers.assignFilesToInput(input, data.files, window);
207
- mutationAttempted = true;
386
+ mutationAttempted = true; fileUploadAttempted = true;
387
+ options.batch?.pending.set(target, operation);
208
388
  input.dispatchEvent(new window.Event('change', { bubbles: true }));
209
389
  // Never click Replace/Overwrite. A concurrent name conflict must stop.
210
- const deadline = Date.now() + 15000;
390
+ verificationPhase = 'post-upload';
391
+ const deadline = now() + readBudget.remainingMs;
392
+ let observations = 0;
393
+ if (options.batch) {
394
+ await waitFor(() => {
395
+ if (dialogs().some(d => /replace|overwrite|替换|覆盖/i.test(d.innerText || ''))) {
396
+ options.batch.blocked.add(target);
397
+ throw new Error('Overleaf reported an upload conflict; replacement was not authorized.');
398
+ }
399
+ return projectPathExists(target);
400
+ }, 30000);
401
+ return;
402
+ }
211
403
  do {
212
404
  assertCurrent();
213
405
  const conflict = dialogs().some(d => /replace|overwrite|替换|覆盖/i.test(d.innerText || ''));
@@ -222,15 +414,24 @@
222
414
  await waitFor(() => !dialogs().length);
223
415
  return;
224
416
  }
225
- await delay(250);
226
- } while (Date.now() < deadline);
417
+ const remaining = deadline - now();
418
+ if (remaining <= 0) break;
419
+ await pauseVerification(readBudget, Math.min(250 * (2 ** Math.min(observations++, 3)), remaining));
420
+ } while (now() < deadline && observations < MAX_CONFIRMATION_OBSERVATIONS);
227
421
  throw new Error('The new file content was not confirmed in the server source ZIP. No editor content was modified.');
228
422
  };
229
423
  try {
230
424
  assertCurrent();
425
+ if (options.batch?.error) throw options.batch.error;
426
+ const existing = options.batch?.before.get(target);
427
+ if (existing && options.allowExistingMatches === true && existing.content === operation.content) {
428
+ return { ok: true, verified: true, changedDocument: false, idempotent: true,
429
+ method: 'overleaf.existing-text-match', verification: 'overleaf-zip',
430
+ saveReceipt: await makeSaveReceipt(projectId, target, operation.content) };
431
+ }
231
432
  if (projectPathExists(target)) return { ok: false, code: 'target_file_already_exists', reason: 'The target file already exists.' };
232
433
  if (!uploadHelpers?.assignFilesToInput || !snapshotRouter?.fetchProjectZipSnapshot) throw new Error('Verified text upload support is unavailable.');
233
- if ((await readServerFiles()).some(file => file.path === target)) {
434
+ if (options.batch ? options.batch.before.has(target) : (await readServerFiles()).some(file => file.path === target)) {
234
435
  return { ok: false, code: 'target_file_already_exists', reason: 'The server already contains the target file.' };
235
436
  }
236
437
  const parts = target.split('/');
@@ -238,33 +439,93 @@
238
439
  const folderPath = parts.slice(0, depth).join('/');
239
440
  if (!findFolder(folderPath)) await createFolderWithDom(folderPath, {
240
441
  isCurrent: () => { assertCurrent(); return true; },
241
- onMutation: () => { mutationAttempted = true; }
442
+ onMutation: () => { mutationAttempted = true; }, onCreatedDirectory: recordDirectory
242
443
  });
243
444
  await ensureFolderExpanded(folderPath);
244
445
  }
245
446
  await uploadText();
246
- return { ok: true, method: 'overleaf.native-text-upload', changedDocument: true,
247
- verified: true, verification: 'overleaf-zip' };
447
+ if (options.batch) return { ok: false, pendingVerification: true, changedDocument: true, createdDirectories, fileUploadAttempted };
448
+ return { ok: true, method: 'overleaf.native-text-upload', changedDocument: true, createdDirectories, fileUploadAttempted,
449
+ verified: true, verification: 'overleaf-zip',
450
+ saveReceipt: await makeSaveReceipt(projectId, target, operation.content) };
248
451
  } catch (error) {
249
- return {
250
- ok: false,
251
- code: mutationAttempted ? 'file_tree_operation_unverified' : 'file_tree_controls_unavailable',
252
- reason: error.message,
253
- changedDocument: mutationAttempted
254
- };
452
+ const directoryCleanup = createdDirectories.length
453
+ ? await cleanupCreatedDirectories(createdDirectories, { projectId, isCurrent: options.isCurrent }) : undefined;
454
+ const result = verificationFailure(error, mutationAttempted, verificationPhase);
455
+ if (!fileUploadAttempted && mutationAttempted) {
456
+ const reason = directoryCleanup?.ok ? 'File upload did not start. The new empty folders were removed.'
457
+ : 'File upload did not start. Some new folders remain and need review.';
458
+ result.reason = reason; result.code = 'file_tree_operation_unverified';
459
+ result.failure = { ...result.failure, code: result.code, stage: 'directory-preparation', userMessage: reason,
460
+ nextAction: directoryCleanup?.ok ? 'Retry sync to create the file.' : 'Review the remaining folders before retrying.' };
461
+ }
462
+ return { ...result, createdDirectories: directoryCleanup?.remaining || createdDirectories,
463
+ fileUploadAttempted, directoryCleanup, diagnostics: { verificationPhase, zipReadCount, lastZipFailure } };
464
+ } finally {
465
+ if (uploadDialog && getProjectId() === projectId && dialogs().includes(uploadDialog)) {
466
+ const close = Array.from(uploadDialog.querySelectorAll('button')).find(node => visible(node)
467
+ && /^(?:Close dialog|Close|Done|Cancel|关闭|完成|取消)$/i.test(controlName(node)));
468
+ close?.click();
469
+ }
255
470
  }
256
471
  }
472
+ const rootUploadIdentity = row => row?.querySelector?.('[data-file-type="doc"][data-file-id], [data-file-type="file"][data-file-id]')
473
+ ?.getAttribute?.('data-file-id') || '';
474
+ function rememberUploadParent(path, node, projectId) {
475
+ if (node && typeof node === 'object') uploadParentProofs.set(node, Object.freeze({
476
+ path, projectId, nativeId: path ? folderIdentity(node) : rootUploadIdentity(node)
477
+ }));
478
+ return node;
479
+ }
480
+ // Synchronous last-moment guard after any uploader await. Snapshot the
481
+ // identity at preparation, since React can reuse a DOM row for a new ID.
482
+ function isUploadParentCurrent(folderPath, parentNode) {
483
+ const target = normalizeSafeProjectPath(folderPath);
484
+ const proof = parentNode && uploadParentProofs.get(parentNode);
485
+ if ((!target && folderPath !== '') || !proof?.projectId || !proof.nativeId
486
+ || proof.path !== target || getProjectId() !== proof.projectId) return false;
487
+ const root = document.querySelector('[data-testid="file-tree-list-root"], .file-tree-list[role="tree"]');
488
+ if (!root?.children) return false;
489
+ const selected = Array.from(root.querySelectorAll?.('[role="treeitem"]') || [])
490
+ .filter(row => row.getAttribute('aria-selected') === 'true');
491
+ if (selected.length !== 1 || selected[0] !== parentNode) return false;
492
+ if (!target) return parentNode.closest?.('[role="tree"]') === root
493
+ && !parentNode.querySelector('[data-file-type="folder"]') && rootUploadIdentity(parentNode) === proof.nativeId;
494
+ return findFolder(target) === parentNode && folderIdentity(parentNode) === proof.nativeId;
495
+ }
496
+
257
497
  // Binary uploads use the same folder hierarchy as verified text uploads.
258
498
  // A folder is not a text document and must never use basename fallback.
259
499
  async function prepareUploadParent(folderPath, options = {}) {
260
500
  const target = normalizeSafeProjectPath(folderPath);
261
501
  const projectId = getProjectId();
262
- if (!target || !projectId) throw new Error('A valid upload parent and project are required.');
263
- const assertCurrent = () => {
264
- if (getProjectId() !== projectId || options.isCurrent?.() === false) {
265
- throw new Error('Project changed or the upload was cancelled.');
502
+ if (!projectId || (!target && folderPath !== '')) throw new Error('A valid upload parent and project are required.');
503
+ const assertCurrent = () => assertVerificationOwner(projectId, options.isCurrent);
504
+ if (!target) {
505
+ // Uppy inherits the selected entity's parent. A previous nested upload
506
+ // must not redirect a root asset into that folder. Use a direct root
507
+ // file row, never the tree adapter's basename fallback.
508
+ assertCurrent();
509
+ const readRoot = () => document.querySelector('[data-testid="file-tree-list-root"], .file-tree-list[role="tree"]');
510
+ const root = readRoot();
511
+ const anchor = Array.from(root?.querySelectorAll?.('[role="treeitem"]') || []).find(row =>
512
+ row.closest?.('[role="tree"]') === root
513
+ && !row.querySelector('[data-file-type="folder"]')
514
+ && row.querySelector('[data-file-type="doc"][data-file-id], [data-file-type="file"][data-file-id]'));
515
+ if (!anchor) throw new Error('A root-level file could not be selected safely for asset upload.');
516
+ if (anchor.getAttribute('aria-selected') !== 'true') {
517
+ (anchor.querySelector('.file-tree-entity-button, .file-tree-entity-details') || anchor).click();
518
+ await delay(100);
266
519
  }
267
- };
520
+ assertCurrent();
521
+ const currentRoot = readRoot();
522
+ const selected = Array.from(currentRoot?.querySelectorAll?.('[role="treeitem"]') || [])
523
+ .filter(row => row.getAttribute('aria-selected') === 'true');
524
+ if (selected.length !== 1 || selected[0] !== anchor || anchor.closest('[role="tree"]') !== currentRoot) {
525
+ throw new Error('Overleaf did not confirm the selected root upload parent.');
526
+ }
527
+ return rememberUploadParent('', anchor, projectId);
528
+ }
268
529
  const expand = async path => {
269
530
  assertCurrent();
270
531
  const node = findFolder(path);
@@ -288,7 +549,8 @@
288
549
  const path = parts.slice(0, depth).join('/');
289
550
  assertCurrent();
290
551
  if (!findFolder(path) && options.createMissing === true) await createFolderWithDom(path, {
291
- isCurrent: () => { assertCurrent(); return true; }, onMutation: options.onMutation
552
+ isCurrent: () => { assertCurrent(); return true; }, onMutation: options.onMutation,
553
+ onCreatedDirectory: options.onCreatedDirectory
292
554
  });
293
555
  await expand(path);
294
556
  }
@@ -302,7 +564,7 @@
302
564
  if (selected.getAttribute('aria-selected') !== 'true') {
303
565
  throw new Error('Overleaf did not confirm the selected upload parent.');
304
566
  }
305
- return selected;
567
+ return rememberUploadParent(target, selected, projectId);
306
568
  }
307
569
 
308
570
  async function prepareEditor(params = {}) {
@@ -358,40 +620,97 @@
358
620
  async function deleteFile(operation, options = {}) {
359
621
  const target = normalizeSafeProjectPath(operation.path);
360
622
  const projectId = getProjectId();
623
+ const deadlineAt = Math.min(now() + 90000, Number.isFinite(options.deadlineAt) ? options.deadlineAt : Infinity);
624
+ const preflightDeadlineAt = deadlineAt - 15000;
361
625
  const expected = options.expectedContent;
626
+ const finishCreatedDeletion = result => {
627
+ const proofs = operation.undoCreatedFile?.createdDirectories;
628
+ if (options.undoCreatedFile !== true || !Array.isArray(proofs) || !proofs.length) return result;
629
+ // The ZIP-confirmed file deletion completes its Undo checkpoint.
630
+ // Directory observations are retained separately and grant no deletion authority.
631
+ return { ...result, fileDeleted: true, directoryCleanup: {
632
+ ok: false, removed: [], remaining: proofs, changedDocument: false
633
+ } };
634
+ };
635
+ const binary = operation.undoCreatedFile?.v === 1 && operation.undoCreatedFile.kind === 'binary'
636
+ && options.undoCreatedFile === true && /^[a-f0-9]{64}$/i.test(options.expectedSha256 || '');
637
+ const matchesExpected = async file => {
638
+ if (!binary) return file && file.content === expected;
639
+ if (typeof file?.contentBase64 !== 'string') return false;
640
+ const cryptoApi = window.crypto || globalThis.crypto;
641
+ if (!cryptoApi?.subtle) return false;
642
+ const decode = window.atob || globalThis.atob;
643
+ if (typeof decode !== 'function') return false;
644
+ const bytes = Uint8Array.from(decode(file.contentBase64.replace(/\s/g, '')), value => value.charCodeAt(0));
645
+ const hash = await cryptoApi.subtle.digest('SHA-256', bytes);
646
+ return Array.from(new Uint8Array(hash), byte => byte.toString(16).padStart(2, '0')).join('')
647
+ === options.expectedSha256.toLowerCase();
648
+ };
362
649
  const canDelete = options.canDelete || (() => typeof deps.readActiveEditorText === 'function'
363
650
  && treeOperations.getActiveFilePath?.() === target && deps.readActiveEditorText() === expected);
364
651
  let mutationAttempted = false;
365
652
  let stage = 'preflight';
366
653
  let ownedDialog = null;
654
+ let zipReadCount = 0;
655
+ const zipFailures = [];
656
+ const remainingMs = () => Math.max(0, deadlineAt - now());
657
+ const budgetExpired = () => Object.assign(new Error('The deletion verification budget was exhausted.'), {
658
+ code: 'delete_confirmation_budget_exhausted',
659
+ technicalMessage: mutationAttempted ? 'No time remains to confirm the deletion in the server snapshot.'
660
+ : 'Deletion was not submitted because at least 15000ms must remain for server confirmation.'
661
+ });
367
662
  const visible = node => Boolean(node && !node.disabled && node.getClientRects?.().length);
368
663
  const dialogs = () => Array.from(document.querySelectorAll('[role="dialog"]')).filter(visible);
369
664
  const assertCurrent = () => {
370
- if (!projectId || getProjectId() !== projectId || options.isCurrent?.() === false) {
371
- throw new Error('Project changed or the deletion was cancelled.');
372
- }
665
+ assertVerificationOwner(projectId, options.isCurrent);
666
+ if (now() >= (mutationAttempted ? deadlineAt : preflightDeadlineAt)) throw budgetExpired();
373
667
  };
374
668
  const waitFor = async (read, timeoutMs = 5000) => {
375
- const deadline = Date.now() + timeoutMs;
376
- do {
669
+ const deadline = Math.min(now() + timeoutMs, preflightDeadlineAt);
670
+ while (now() < deadline) {
377
671
  assertCurrent();
378
672
  invalidateDomProjectPathCache();
379
673
  const value = await read();
674
+ assertCurrent();
380
675
  if (value) return value;
381
- await delay(100);
382
- } while (Date.now() < deadline);
676
+ await delay(Math.max(0, Math.min(100, deadline - now())));
677
+ }
678
+ assertCurrent();
383
679
  throw new Error(`Overleaf did not confirm ${stage} before timeout.`);
384
680
  };
681
+ const canRetryRead = error => {
682
+ if (['codex_cancelled', 'aborted_project_changed'].includes(error?.code) || error?.retryable === false) return false;
683
+ const diagnostics = error?.diagnostics || {};
684
+ return ![error?.status, diagnostics.status, diagnostics.statusCode,
685
+ ...(Array.isArray(diagnostics.attempts) ? diagnostics.attempts.map(item => item?.status) : [])]
686
+ .some(status => [401, 403].includes(Number(status)));
687
+ };
385
688
  const readServerFiles = async () => {
386
- assertCurrent();
387
- snapshotRouter.invalidateCache?.();
388
- const result = await snapshotRouter.fetchProjectZipSnapshot({ force: true, maxAgeMs: 0,
389
- includeBinaryFiles: false, includeContent: true, zipTimeoutMs: 15000 });
390
- assertCurrent();
391
- if (result?.ok !== true || !Array.isArray(result.files)) {
392
- throw new Error('Server source ZIP is unavailable; deletion cannot be verified safely.');
689
+ const deadline = mutationAttempted ? deadlineAt : preflightDeadlineAt;
690
+ for (let attempt = 0; ; attempt++) {
691
+ assertCurrent();
692
+ snapshotRouter.invalidateCache?.();
693
+ zipReadCount++;
694
+ try {
695
+ const result = await readServerSnapshot({ projectId, includeBinaryFiles: binary, singleAttempt: true,
696
+ deadlineAt: deadline, budget: { remainingMs: Math.max(0, deadline - now()) }, isCurrent: options.isCurrent });
697
+ assertCurrent();
698
+ if ((result.skipped || []).some(file => file?.path === target)) {
699
+ throw Object.assign(new Error('The target was skipped in the server snapshot.'), {
700
+ code: 'source_zip_unavailable', retryable: false, diagnostics: result.diagnostics || null,
701
+ technicalMessage: 'The target was skipped in the server snapshot; absence cannot confirm deletion.'
702
+ });
703
+ }
704
+ return result.files;
705
+ } catch (error) {
706
+ zipFailures.push({ code: error?.originalCode || error?.code, status: error?.status,
707
+ technicalMessage: error?.technicalMessage || error?.message, diagnostics: error?.diagnostics || null,
708
+ remainingMs: remainingMs() });
709
+ if (zipFailures.length > MAX_CONFIRMATION_OBSERVATIONS) zipFailures.shift();
710
+ if (!canRetryRead(error) || now() >= deadline) throw error;
711
+ await delay(Math.min(1000 * (2 ** Math.min(attempt, 2)), deadline - now()));
712
+ }
393
713
  }
394
- return result.files;
395
714
  };
396
715
  const choose = (scope, selector, pattern) => {
397
716
  const matches = Array.from(scope.querySelectorAll(selector)).filter(node => visible(node)
@@ -406,26 +725,55 @@
406
725
  // Current Overleaf keeps the stable document ID on the draggable
407
726
  // entity inside the row. The menu only mounts on hover/selection.
408
727
  const rowIdentity = row => row?.querySelector('[data-file-type="doc"][data-file-id]')?.getAttribute('data-file-id')
728
+ || row?.querySelector('[data-file-type="file"][data-file-id]')?.getAttribute('data-file-id')
409
729
  || row?.getAttribute('data-entity-id') || row?.getAttribute('data-doc-id')
410
730
  || row?.getAttribute('data-file-id') || row?.querySelector('[id^="menu-button-"]')?.id || '';
411
731
  try {
412
- if (!target || !projectId || typeof expected !== 'string' || typeof canDelete !== 'function'
413
- || !window.CodexOverleafProjectFiles?.isTextProjectPath(target)) {
414
- throw new Error('A checked text-file pre-image is required for deletion.');
732
+ if (!target || !projectId || typeof canDelete !== 'function'
733
+ || (!binary && (typeof expected !== 'string' || !window.CodexOverleafProjectFiles?.isTextProjectPath(target)))) {
734
+ throw new Error('A checked file pre-image is required for deletion.');
415
735
  }
416
736
  assertCurrent();
417
737
  if (dialogs().length) throw new Error('An existing Overleaf dialog must be closed before deleting files.');
418
738
  if (!snapshotRouter?.fetchProjectZipSnapshot) throw new Error('Verified deletion support is unavailable.');
419
739
  const original = (await readServerFiles()).find(file => file.path === target);
420
- if (!original || original.content !== expected || canDelete() !== true) {
740
+ if (!original && options.undoCreatedFile === true && !resolveRow()) {
741
+ return finishCreatedDeletion({ ok: true, method: 'overleaf.native-created-file-delete', changedDocument: false,
742
+ idempotent: true, verified: true, verification: 'overleaf-zip' });
743
+ }
744
+ const originalMatches = await matchesExpected(original);
745
+ assertCurrent();
746
+ if (!originalMatches) {
421
747
  throw new Error('The file no longer matches the checked pre-image; deletion was stopped.');
422
748
  }
423
749
  const parentPath = target.split('/').slice(0, -1).join('/');
424
- if (parentPath) await prepareUploadParent(parentPath, options);
750
+ // Folder expansion must share the preflight deadline too. Uploads keep their existing timing.
751
+ const parents = parentPath ? parentPath.split('/') : [];
752
+ for (let depth = 1; depth <= parents.length; depth++) {
753
+ assertCurrent();
754
+ const path = parents.slice(0, depth).join('/'), parent = findFolder(path);
755
+ if (!parent) throw new Error('The target parent folder is unavailable.');
756
+ if (parent.getAttribute('aria-expanded') !== 'false') continue;
757
+ stage = 'parent folder expansion';
758
+ const toggle = parent.querySelector('.folder-expand-collapse-button');
759
+ if (!toggle) throw new Error('The target parent folder cannot be expanded safely.');
760
+ toggle.click();
761
+ await waitFor(() => findFolder(path)?.getAttribute('aria-expanded') === 'true');
762
+ }
763
+ // Collapsed folders can hide an existing file from the tree adapter.
764
+ // Server presence proves existence; prepare its editor only after expansion.
765
+ if (!binary && canDelete() !== true && typeof options.prepareTextPreimage === 'function') {
766
+ const current = await options.prepareTextPreimage();
767
+ assertCurrent();
768
+ if (!current?.ok || current.text !== expected) {
769
+ throw new Error('The file no longer matches the checked pre-image; deletion was stopped.');
770
+ }
771
+ }
772
+ if (canDelete() !== true) throw new Error('The file no longer matches the checked pre-image; deletion was stopped.');
425
773
  const row = resolveRow();
426
774
  const identity = rowIdentity(row);
427
775
  if (!row || !identity || row.querySelector('[data-file-type="folder"]')) {
428
- throw new Error('An exact Overleaf text-file row could not be identified.');
776
+ throw new Error('An exact Overleaf file row could not be identified.');
429
777
  }
430
778
  stage = 'file selection';
431
779
  (row.querySelector('.file-tree-entity-details, .file-tree-entity-button') || row).click();
@@ -459,21 +807,42 @@
459
807
  };
460
808
  if (!verifyDialog()) throw new Error('The deletion dialog does not name exactly the authorized file.');
461
809
  const latest = (await readServerFiles()).find(file => file.path === target);
810
+ const latestMatches = await matchesExpected(latest);
462
811
  assertCurrent();
463
- if (!latest || latest.content !== expected || canDelete() !== true
812
+ if (!latestMatches || canDelete() !== true
464
813
  || rowIdentity(resolveRow()) !== identity || !verifyDialog()) {
465
814
  throw new Error('The target changed before confirmation; deletion was stopped.');
466
815
  }
467
816
  const confirm = choose(ownedDialog, 'button', /^(?:Delete|删除)$/i);
817
+ assertCurrent();
468
818
  stage = 'server deletion receipt';
469
819
  mutationAttempted = true;
470
820
  confirm.click();
471
- await waitFor(async () => !(await readServerFiles()).some(file => file.path === target), 15000);
472
- return { ok: true, method: 'overleaf.native-text-delete', changedDocument: true,
473
- verified: true, verification: 'overleaf-zip' };
821
+ for (let observation = 0; ; observation++) {
822
+ const files = await readServerFiles();
823
+ if (!files.some(file => file.path === target)) {
824
+ return finishCreatedDeletion({ ok: true, method: binary ? 'overleaf.native-created-file-delete' : 'overleaf.native-text-delete', changedDocument: true,
825
+ verified: true, verification: 'overleaf-zip' });
826
+ }
827
+ await delay(Math.min(250 * (2 ** Math.min(observation, 3)), remainingMs()));
828
+ }
474
829
  } catch (error) {
475
- return { ok: false, code: mutationAttempted ? 'file_tree_operation_unverified' : 'file_tree_controls_unavailable',
476
- reason: error.message, stage: stage.replace(/ /g, '_'), changedDocument: mutationAttempted };
830
+ const interrupted = ['codex_cancelled', 'aborted_project_changed'].includes(error?.code);
831
+ const code = interrupted ? error.code : mutationAttempted ? 'file_tree_operation_unverified'
832
+ : error?.code || 'file_tree_controls_unavailable';
833
+ const retryable = canRetryRead(error), technicalMessage = error?.technicalMessage || error?.message || '';
834
+ const reason = interrupted ? error.message : mutationAttempted ? 'Deletion could not be confirmed.'
835
+ : code === 'source_zip_unavailable' ? 'The server copy could not be checked. No file was deleted.'
836
+ : code === 'delete_confirmation_budget_exhausted' ? 'Not enough time remains to verify deletion. No file was deleted.' : error.message;
837
+ return { ok: false, code, reason, stage: stage.replace(/ /g, '_'), changedDocument: mutationAttempted,
838
+ technicalMessage, deadlineAt, remainingMs: remainingMs(), diagnostics: {
839
+ ...(error?.diagnostics || {}), verificationPhase: 'delete-' + stage.replace(/ /g, '-'), zipReadCount, zipFailures,
840
+ reason: technicalMessage, originalCode: error?.originalCode || error?.code || '', status: error?.status,
841
+ deadlineAt, remainingMs: remainingMs(), mutationAttempted
842
+ }, failure: { code, stage: mutationAttempted ? 'verify' : 'preflight', severity: mutationAttempted ? 'warning' : 'blocked',
843
+ retryable, terminalState: interrupted ? 'cancelled' : mutationAttempted ? 'needs_review' : 'blocked',
844
+ changedDocument: mutationAttempted, file: target, operationType: 'delete', userMessage: reason,
845
+ nextAction: retryable ? 'Retry Undo to check the file.' : '', technicalMessage } };
477
846
  } finally {
478
847
  // Close only the dialog opened by this operation; never another modal.
479
848
  if (ownedDialog && dialogs().includes(ownedDialog) && getProjectId() === projectId) {
@@ -487,7 +856,9 @@
487
856
  async function createFile(operation, options = {}) {
488
857
  return createTextFileWithDom(operation, options);
489
858
  }
490
- return { createFile, deleteFile, prepareEditor, ensureWriteMode, prepareUploadParent, findFolderNode: findFolder };
859
+ return { createFile, deleteFile, prepareEditor, ensureWriteMode, prepareUploadParent, isUploadParentCurrent, cleanupCreatedDirectories, findFolderNode: findFolder,
860
+ beginCreateBatch, finishCreateBatch,
861
+ cancelVerifications() { for (const controller of verificationReads) controller.abort(); } };
491
862
  }
492
863
  return { create };
493
864
  });