codex-overleaf-link 2.3.6 → 2.4.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 (52) hide show
  1. package/README.md +62 -34
  2. package/README.zh-CN.md +546 -0
  3. package/extension/bootstrap/manifest.template.json +1 -1
  4. package/extension/src/background.js +3 -0
  5. package/extension/src/content/activeTurnControl.js +1 -0
  6. package/extension/src/content/assetTransferBroker.js +2 -0
  7. package/extension/src/content/contentRuntime.js +122 -118
  8. package/extension/src/content/contextTray.js +4 -0
  9. package/extension/src/content/diagnosticsController.js +4 -0
  10. package/extension/src/content/diagnosticsPanel.js +128 -4
  11. package/extension/src/content/generated/content.bundle.js +1233 -550
  12. package/extension/src/content/generated/content.bundle.meta.json +4 -3
  13. package/extension/src/content/legacyGlobalRegistry.js +1 -0
  14. package/extension/src/content/modelPicker.js +21 -9
  15. package/extension/src/content/modelPickerSupport.js +38 -1
  16. package/extension/src/content/nativeCompatibilityController.js +4 -1
  17. package/extension/src/content/otWarmMirror.js +34 -30
  18. package/extension/src/content/otWarmMirrorController.js +20 -0
  19. package/extension/src/content/pageBridgeClient.js +2 -0
  20. package/extension/src/content/panelMaintenance.js +8 -4
  21. package/extension/src/content/recentProjects.js +354 -355
  22. package/extension/src/content/runResultActions.js +16 -1
  23. package/extension/src/content/runTimelineView.js +9 -11
  24. package/extension/src/content/scopedPersistenceCoordinator.js +2 -1
  25. package/extension/src/content/scopedPersistencePanelState.js +45 -2
  26. package/extension/src/content/sessionForkController.js +20 -11
  27. package/extension/src/content/sessionPersistence.js +4 -1
  28. package/extension/src/content/settingsPanel.js +16 -2
  29. package/extension/src/content/trackedChangeCaptureController.js +153 -0
  30. package/extension/src/content/writebackOrchestrator.js +31 -0
  31. package/extension/src/page/binaryAssetUploader.js +101 -29
  32. package/extension/src/page/overleafRealtimeObserver.js +4 -1
  33. package/extension/src/page/saveState.js +44 -1
  34. package/extension/src/page/textFileCreator.js +493 -0
  35. package/extension/src/page/trackedChangeCapture.js +342 -0
  36. package/extension/src/page/trackedChangesLifecycle.js +149 -150
  37. package/extension/src/page/treeOperations.js +40 -29
  38. package/extension/src/page/writebackRouter.js +95 -49
  39. package/extension/src/pageBridge.js +65 -25
  40. package/extension/src/shared/compatibility.js +1 -1
  41. package/extension/src/shared/i18n.js +94 -40
  42. package/extension/src/shared/pageRpcContract.js +2 -1
  43. package/extension/src/shared/sessionState.js +6 -2
  44. package/extension/src/shared/storageDb.js +11 -12
  45. package/extension/src/shared/storageRunActions.js +73 -1
  46. package/extension/src/shared/writebackSettlement.js +19 -5
  47. package/extension/styles/panel.css +254 -405
  48. package/native-host/src/codexSessionRunner.js +41 -38
  49. package/native-host/src/codexSessionTiming.js +68 -0
  50. package/native-host/src/readProgressGuard.js +84 -17
  51. package/native-host/src/taskRunnerRuntime.js +25 -0
  52. package/package.json +5 -1
@@ -1,3 +1,4 @@
1
+ if (typeof document !== 'undefined' && document.documentElement) document.documentElement.setAttribute('data-codex-overleaf-source-digest', "b19a018ab6eb9c7bdca8cfad8ff6efcc4a30e80f81c3c0a2e89a7f78b6361d97");
1
2
  (() => {
2
3
  // extension/entries/content-entry.mjs
3
4
  (function runClassicContentModule(module, exports, require2) {
@@ -4582,14 +4583,39 @@ ${block}`;
4582
4583
  failureReason_editor_project_id_unavailable_next: "Refresh the Overleaf tab and retry; if it persists, reload the extension.",
4583
4584
  // Welcome-panel + write-guard: Recent-projects
4584
4585
  // variant copy. Spec §5.3–§5.5, §5.8, §5.10 are the source of truth.
4585
- recentProjects_welcome: "Codex Overleaf Link",
4586
- recentProjects_welcome_subtitle: "Open a project from the Overleaf list on the left to start.",
4587
- recentProjects_empty: "You haven't run any Codex sessions yet. Open a project from the Overleaf list on the left to start.",
4588
- recentProjects_sessions_empty: "No sessions in this project yet",
4589
- recentProjects_sessions_toggle: "Show / manage this project's sessions",
4590
- recentProjects_sessions_action: "View runs",
4591
- recentProjects_sessions_action_open: "Hide runs",
4592
- recentProjects_clearProject: "Delete all sessions for this project",
4586
+ recentProjects_welcome: "Continue working",
4587
+ recentProjects_focusEyebrow: "Last conversation",
4588
+ recentProjects_historyHeading: "Other recent conversations",
4589
+ recentProjects_historyHint: "Select to continue",
4590
+ recentProjects_projectConversations: "View this project's conversations",
4591
+ recentProjects_allProjects: "All projects",
4592
+ recentProjects_showAllConversations: "View all {count} conversations",
4593
+ recentProjects_unstarted: "Not started \xB7 {count}",
4594
+ recentProjects_unstartedHint: "Empty conversations are kept here, without deleting their records.",
4595
+ recentProjects_noStarted: "No conversation has been started yet. Empty conversations are available below.",
4596
+ recentProjects_savedDraft: "Saved draft, ready to continue.",
4597
+ recentProjects_attention: "Needs attention",
4598
+ recentProjects_unavailableRecords: "Records with unavailable project links",
4599
+ recentProjects_loadingHistory: "Loading conversations...",
4600
+ recentProjects_loadFailed: "Conversation history could not be read. Please retry.",
4601
+ recentProjects_continueHint: "Open this conversation without automatically starting a task.",
4602
+ recentProjects_more: "More options",
4603
+ recentProjects_continue: "Continue",
4604
+ recentProjects_openProject: "Open project",
4605
+ recentProjects_loadingConversation: "Loading conversation...",
4606
+ recentProjects_noConversation: "No saved conversation",
4607
+ recentProjects_noRuns: "No runs yet",
4608
+ recentProjects_previewUnavailable: "Conversation unavailable",
4609
+ recentProjects_previewUnavailableHint: "Saved conversation could not be loaded.",
4610
+ recentProjects_conversationGone: "This conversation is no longer available. Choose another conversation from the refreshed list.",
4611
+ recentProjects_continueFailed: "Could not open the conversation. Please try again.",
4612
+ recentProjects_welcome_subtitle: "Conversations saved in this browser",
4613
+ recentProjects_empty: "No conversations saved in this browser yet. Open an Overleaf project and start a Codex conversation.",
4614
+ recentProjects_sessions_empty: "No saved conversations in this project",
4615
+ recentProjects_sessions_toggle: "Show or manage this project's conversations",
4616
+ recentProjects_sessions_action: "All conversations",
4617
+ recentProjects_sessions_action_open: "Hide conversations",
4618
+ recentProjects_clearProject: "Delete this project's conversations",
4593
4619
  recentProjects_clearProject_title: "Delete all Codex sessions for {project}?",
4594
4620
  recentProjects_clearProject_message: "This permanently deletes all {count} plugin sessions, run history, and matching plugin-local Codex thread history for {project}. It will not modify Overleaf files or global Codex history.",
4595
4621
  recentProjects_clearProject_confirm: "Delete all",
@@ -4605,15 +4631,17 @@ ${block}`;
4605
4631
  recentProjects_degraded: "Sign in to Overleaf to see your recent Codex projects.",
4606
4632
  recentProjects_row_projectLinkUnavailable: "Project link unavailable.",
4607
4633
  recentProjects_settings_entry: "Settings & Diagnostics",
4608
- recentProjects_badge_pending: "pending",
4609
- recentProjects_badge_accepted: "accepted",
4610
- recentProjects_badge_rejected: "rejected",
4611
- recentProjects_badge_needs_review: "pending",
4612
- recentProjects_badge_running: "running",
4613
- recentProjects_badge_completed: "completed",
4614
- recentProjects_badge_failed: "failed",
4615
- recentProjects_badge_interrupted: "interrupted",
4616
- recentProjects_badge_stale: "stale",
4634
+ recentProjects_settingsTitle: "General Settings",
4635
+ recentProjects_settingsSubtitle: "Manage Codex preferences and local history across all projects.",
4636
+ recentProjects_badge_pending: "Last run pending",
4637
+ recentProjects_badge_accepted: "Last changes accepted",
4638
+ recentProjects_badge_rejected: "Last changes undone",
4639
+ recentProjects_badge_needs_review: "Last run needs review",
4640
+ recentProjects_badge_running: "Last recorded as running",
4641
+ recentProjects_badge_completed: "Last run completed",
4642
+ recentProjects_badge_failed: "Last run failed",
4643
+ recentProjects_badge_interrupted: "Last run may have been interrupted",
4644
+ recentProjects_badge_stale: "Last run status unconfirmed",
4617
4645
  recentProjects_unnamed: "Project \xB7 {prefix}",
4618
4646
  recentProjects_zombieRunningNote: "Marked as running, but it may be a leftover from an interrupted run.",
4619
4647
  recentProjects_zombieDeleteConfirm: "This session is marked as running. If no other tab is actually running it, you can delete it anyway.",
@@ -4621,9 +4649,9 @@ ${block}`;
4621
4649
  recentProjects_degraded_hint: "Could not identify the Overleaf account on this page, so the project list is hidden to avoid mixing accounts. This usually resolves on a reload.",
4622
4650
  recentProjects_sessionsCount: "{count} session(s)",
4623
4651
  recentProjects_welcome_stats: "{projects} projects \xB7 {sessions} Codex sessions",
4624
- recentProjects_badge_background_completed: "completed in background",
4625
- recentProjects_badge_needs_review_after_navigation: "pending",
4626
- recentProjects_badge_abandoned_after_navigation: "abandoned"
4652
+ recentProjects_badge_background_completed: "Last run completed in background",
4653
+ recentProjects_badge_needs_review_after_navigation: "Last run needs review",
4654
+ recentProjects_badge_abandoned_after_navigation: "Last run stopped after navigation"
4627
4655
  },
4628
4656
  zh: {
4629
4657
  resizePanel: "\u62D6\u52A8\u8C03\u6574 Codex \u9762\u677F\u5BBD\u5EA6\uFF0C\u53CC\u51FB\u6062\u590D\u9ED8\u8BA4\u5BBD\u5EA6",
@@ -5121,14 +5149,39 @@ ${block}`;
5121
5149
  failureReason_editor_project_id_unavailable_next: "\u8BF7\u5237\u65B0 Overleaf \u9875\u9762\u540E\u91CD\u8BD5\uFF1B\u5982\u679C\u4ECD\u7136\u5931\u8D25\uFF0C\u8BF7\u91CD\u65B0\u52A0\u8F7D\u6269\u5C55\u3002",
5122
5150
  // Welcome-panel + write-guard: Recent-projects
5123
5151
  // variant copy. 见 spec §5.3–§5.5、§5.8、§5.10。
5124
- recentProjects_welcome: "Codex Overleaf Link",
5125
- recentProjects_welcome_subtitle: "\u4ECE\u5DE6\u4FA7 Overleaf \u9879\u76EE\u5217\u8868\u4E2D\u6253\u5F00\u4EFB\u610F\u9879\u76EE\u5373\u53EF\u5F00\u59CB\u3002",
5126
- recentProjects_empty: "\u4F60\u8FD8\u6CA1\u6709\u8FD0\u884C\u8FC7 Codex \u4F1A\u8BDD\u3002\u4ECE\u5DE6\u4FA7 Overleaf \u9879\u76EE\u5217\u8868\u4E2D\u6253\u5F00\u4EFB\u610F\u9879\u76EE\u5373\u53EF\u5F00\u59CB\u3002",
5127
- recentProjects_sessions_empty: "\u8BE5\u9879\u76EE\u8FD8\u6CA1\u6709\u4F1A\u8BDD",
5128
- recentProjects_sessions_toggle: "\u67E5\u770B/\u7BA1\u7406\u8BE5\u9879\u76EE\u7684\u4F1A\u8BDD",
5129
- recentProjects_sessions_action: "\u67E5\u770B\u4F1A\u8BDD",
5130
- recentProjects_sessions_action_open: "\u6536\u8D77\u4F1A\u8BDD",
5131
- recentProjects_clearProject: "\u5220\u9664\u8BE5\u9879\u76EE\u7684\u5168\u90E8\u4F1A\u8BDD",
5152
+ recentProjects_welcome: "\u7EE7\u7EED\u5DE5\u4F5C",
5153
+ recentProjects_focusEyebrow: "\u4E0A\u6B21\u5BF9\u8BDD",
5154
+ recentProjects_historyHeading: "\u5176\u4ED6\u6700\u8FD1\u5BF9\u8BDD",
5155
+ recentProjects_historyHint: "\u70B9\u51FB\u6761\u76EE\u7EE7\u7EED",
5156
+ recentProjects_projectConversations: "\u67E5\u770B\u8BE5\u9879\u76EE\u7684\u5168\u90E8\u5BF9\u8BDD",
5157
+ recentProjects_allProjects: "\u6240\u6709\u9879\u76EE",
5158
+ recentProjects_showAllConversations: "\u67E5\u770B\u5168\u90E8 {count} \u6BB5\u5BF9\u8BDD",
5159
+ recentProjects_unstarted: "\u672A\u5F00\u59CB \xB7 {count}",
5160
+ recentProjects_unstartedHint: "\u7A7A\u767D\u5BF9\u8BDD\u6536\u7EB3\u5728\u8FD9\u91CC\uFF0C\u8BB0\u5F55\u4E0D\u4F1A\u88AB\u5220\u9664\u3002",
5161
+ recentProjects_noStarted: "\u5C1A\u65E0\u5DF2\u5F00\u59CB\u7684\u5BF9\u8BDD\uFF0C\u7A7A\u767D\u5BF9\u8BDD\u53EF\u5728\u4E0B\u65B9\u5C55\u5F00\u3002",
5162
+ recentProjects_savedDraft: "\u8349\u7A3F\u5DF2\u4FDD\u5B58\uFF0C\u53EF\u7EE7\u7EED\u8865\u5145\u3002",
5163
+ recentProjects_attention: "\u9700\u8981\u67E5\u770B",
5164
+ recentProjects_unavailableRecords: "\u9879\u76EE\u94FE\u63A5\u4E0D\u53EF\u7528\u7684\u8BB0\u5F55",
5165
+ recentProjects_loadingHistory: "\u6B63\u5728\u52A0\u8F7D\u5BF9\u8BDD\u2026",
5166
+ recentProjects_loadFailed: "\u6682\u65F6\u65E0\u6CD5\u8BFB\u53D6\u5BF9\u8BDD\u5386\u53F2\uFF0C\u8BF7\u91CD\u8BD5\u3002",
5167
+ recentProjects_continueHint: "\u4EC5\u6253\u5F00\u8FD9\u6BB5\u5BF9\u8BDD\uFF0C\u4E0D\u4F1A\u81EA\u52A8\u542F\u52A8\u4EFB\u52A1\u3002",
5168
+ recentProjects_more: "\u66F4\u591A\u9009\u9879",
5169
+ recentProjects_continue: "\u7EE7\u7EED",
5170
+ recentProjects_openProject: "\u6253\u5F00\u9879\u76EE",
5171
+ recentProjects_loadingConversation: "\u6B63\u5728\u52A0\u8F7D\u5BF9\u8BDD\u2026",
5172
+ recentProjects_noConversation: "\u6682\u65E0\u4FDD\u5B58\u7684\u5BF9\u8BDD",
5173
+ recentProjects_noRuns: "\u5C1A\u672A\u8FD0\u884C\u4EFB\u52A1",
5174
+ recentProjects_previewUnavailable: "\u6682\u65F6\u65E0\u6CD5\u8BFB\u53D6\u5BF9\u8BDD",
5175
+ recentProjects_previewUnavailableHint: "\u5DF2\u4FDD\u5B58\u7684\u5BF9\u8BDD\u6682\u65F6\u65E0\u6CD5\u8BFB\u53D6\u3002",
5176
+ recentProjects_conversationGone: "\u8BE5\u5BF9\u8BDD\u5DF2\u4E0D\u53EF\u7528\uFF0C\u8BF7\u4ECE\u66F4\u65B0\u540E\u7684\u5217\u8868\u4E2D\u9009\u62E9\u5176\u4ED6\u5BF9\u8BDD\u3002",
5177
+ recentProjects_continueFailed: "\u6682\u65F6\u65E0\u6CD5\u6253\u5F00\u5BF9\u8BDD\uFF0C\u8BF7\u91CD\u8BD5\u3002",
5178
+ recentProjects_welcome_subtitle: "\u5F53\u524D\u6D4F\u89C8\u5668\u4FDD\u5B58\u7684\u5BF9\u8BDD",
5179
+ recentProjects_empty: "\u5F53\u524D\u6D4F\u89C8\u5668\u8FD8\u6CA1\u6709\u4FDD\u5B58\u7684\u5BF9\u8BDD\u3002\u6253\u5F00\u4E00\u4E2A Overleaf \u9879\u76EE\uFF0C\u4E0E Codex \u5F00\u59CB\u5BF9\u8BDD\u5373\u53EF\u3002",
5180
+ recentProjects_sessions_empty: "\u8BE5\u9879\u76EE\u8FD8\u6CA1\u6709\u4FDD\u5B58\u7684\u5BF9\u8BDD",
5181
+ recentProjects_sessions_toggle: "\u67E5\u770B\u6216\u7BA1\u7406\u8BE5\u9879\u76EE\u7684\u5BF9\u8BDD",
5182
+ recentProjects_sessions_action: "\u5168\u90E8\u5BF9\u8BDD",
5183
+ recentProjects_sessions_action_open: "\u6536\u8D77\u5BF9\u8BDD",
5184
+ recentProjects_clearProject: "\u5220\u9664\u8BE5\u9879\u76EE\u7684\u5168\u90E8\u5BF9\u8BDD",
5132
5185
  recentProjects_clearProject_title: "\u5220\u9664 {project} \u7684\u5168\u90E8 Codex \u4F1A\u8BDD\uFF1F",
5133
5186
  recentProjects_clearProject_message: "\u8FD9\u4F1A\u6C38\u4E45\u5220\u9664 {project} \u7684\u5168\u90E8 {count} \u4E2A\u63D2\u4EF6\u4F1A\u8BDD\u3001\u8FD0\u884C\u5386\u53F2\u53CA\u5BF9\u5E94\u7684\u63D2\u4EF6\u672C\u5730 Codex \u7EBF\u7A0B\u5386\u53F2\u3002\u4E0D\u4F1A\u4FEE\u6539 Overleaf \u6587\u4EF6\u6216\u5168\u5C40 Codex \u5386\u53F2\u3002",
5134
5187
  recentProjects_clearProject_confirm: "\u5168\u90E8\u5220\u9664",
@@ -5144,15 +5197,17 @@ ${block}`;
5144
5197
  recentProjects_degraded: "\u8BF7\u767B\u5F55 Overleaf \u540E\u67E5\u770B\u4F60\u6700\u8FD1\u7684 Codex \u9879\u76EE\u3002",
5145
5198
  recentProjects_row_projectLinkUnavailable: "\u9879\u76EE\u94FE\u63A5\u4E0D\u53EF\u7528\u3002",
5146
5199
  recentProjects_settings_entry: "\u8BBE\u7F6E\u4E0E\u8BCA\u65AD",
5147
- recentProjects_badge_pending: "\u5F85\u5904\u7406",
5148
- recentProjects_badge_accepted: "\u5DF2\u63A5\u53D7",
5149
- recentProjects_badge_rejected: "\u5DF2\u64A4\u56DE",
5150
- recentProjects_badge_needs_review: "\u5F85\u5904\u7406",
5151
- recentProjects_badge_running: "\u8FD0\u884C\u4E2D",
5152
- recentProjects_badge_completed: "\u5DF2\u5B8C\u6210",
5153
- recentProjects_badge_failed: "\u5931\u8D25",
5154
- recentProjects_badge_interrupted: "\u5DF2\u4E2D\u65AD",
5155
- recentProjects_badge_stale: "\u4E45\u672A\u5904\u7406",
5200
+ recentProjects_settingsTitle: "\u901A\u7528\u8BBE\u7F6E",
5201
+ recentProjects_settingsSubtitle: "\u7BA1\u7406\u9002\u7528\u4E8E\u6240\u6709\u9879\u76EE\u7684 Codex \u504F\u597D\u8BBE\u7F6E\u4E0E\u672C\u5730\u5386\u53F2\u8BB0\u5F55\u3002",
5202
+ recentProjects_badge_pending: "\u4E0A\u6B21\u8FD0\u884C\u5F85\u5904\u7406",
5203
+ recentProjects_badge_accepted: "\u4E0A\u6B21\u4FEE\u6539\u5DF2\u63A5\u53D7",
5204
+ recentProjects_badge_rejected: "\u4E0A\u6B21\u4FEE\u6539\u5DF2\u64A4\u56DE",
5205
+ recentProjects_badge_needs_review: "\u4E0A\u6B21\u8FD0\u884C\u9700\u8981\u68C0\u67E5",
5206
+ recentProjects_badge_running: "\u4E0A\u6B21\u8BB0\u5F55\u4E3A\u8FD0\u884C\u4E2D",
5207
+ recentProjects_badge_completed: "\u4E0A\u6B21\u8FD0\u884C\u5DF2\u5B8C\u6210",
5208
+ recentProjects_badge_failed: "\u4E0A\u6B21\u8FD0\u884C\u5931\u8D25",
5209
+ recentProjects_badge_interrupted: "\u4E0A\u6B21\u8FD0\u884C\u53EF\u80FD\u5DF2\u4E2D\u65AD",
5210
+ recentProjects_badge_stale: "\u4E0A\u6B21\u8FD0\u884C\u72B6\u6001\u672A\u786E\u8BA4",
5156
5211
  recentProjects_unnamed: "\u9879\u76EE \xB7 {prefix}",
5157
5212
  recentProjects_zombieRunningNote: "\u6807\u8BB0\u4E3A\u8FD0\u884C\u4E2D\uFF0C\u4F46\u53EF\u80FD\u662F\u88AB\u4E2D\u65AD\u540E\u7684\u6B8B\u7559\u3002",
5158
5213
  recentProjects_zombieDeleteConfirm: "\u8BE5\u4F1A\u8BDD\u6807\u8BB0\u4E3A\u8FD0\u884C\u4E2D\u3002\u5982\u679C\u786E\u8BA4\u6CA1\u6709\u5176\u4ED6\u6807\u7B7E\u9875\u5728\u8FD0\u884C\u5B83\uFF0C\u53EF\u4EE5\u76F4\u63A5\u5220\u9664\u3002",
@@ -5160,9 +5215,9 @@ ${block}`;
5160
5215
  recentProjects_degraded_hint: "\u65E0\u6CD5\u8BC6\u522B\u672C\u9875\u7684 Overleaf \u8D26\u53F7\uFF0C\u4E3A\u907F\u514D\u8D26\u53F7\u95F4\u4E32\u6570\u636E\u5DF2\u9690\u85CF\u9879\u76EE\u5217\u8868\u3002\u901A\u5E38\u5237\u65B0\u5373\u53EF\u6062\u590D\u3002",
5161
5216
  recentProjects_sessionsCount: "{count} \u4E2A\u4F1A\u8BDD",
5162
5217
  recentProjects_welcome_stats: "{projects} \u4E2A\u9879\u76EE \xB7 {sessions} \u4E2A Codex \u4F1A\u8BDD",
5163
- recentProjects_badge_background_completed: "\u540E\u53F0\u5DF2\u5B8C\u6210",
5164
- recentProjects_badge_needs_review_after_navigation: "\u5F85\u5904\u7406",
5165
- recentProjects_badge_abandoned_after_navigation: "\u5DF2\u4E2D\u6B62"
5218
+ recentProjects_badge_background_completed: "\u4E0A\u6B21\u8FD0\u884C\u5DF2\u5728\u540E\u53F0\u5B8C\u6210",
5219
+ recentProjects_badge_needs_review_after_navigation: "\u4E0A\u6B21\u8FD0\u884C\u9700\u8981\u68C0\u67E5",
5220
+ recentProjects_badge_abandoned_after_navigation: "\u4E0A\u6B21\u8FD0\u884C\u5DF2\u5728\u79BB\u5F00\u9875\u9762\u540E\u505C\u6B62"
5166
5221
  }
5167
5222
  };
5168
5223
  function normalizeLocale(value) {
@@ -7886,7 +7941,7 @@ ${block}`;
7886
7941
  if (!result || typeof result !== "object" || result.ok === false) return result;
7887
7942
  if (Array.isArray(result.skipped) && result.skipped.length > 0) return result;
7888
7943
  const expectedFiles = Array.isArray(run?.undoExpectedFiles) ? run.undoExpectedFiles : [];
7889
- if (!expectedFiles.length || isUndoVerifiedContentMatching(run, result)) return result;
7944
+ if (!expectedFiles.length || isUndoVerifiedContentMatching(run, result, options2)) return result;
7890
7945
  const path = expectedFiles.find((entry) => typeof entry?.path === "string")?.path || "";
7891
7946
  appendSyntheticFailure(result, options2.buildFailure?.("undo_not_verified", { path, type: "undo" }, {
7892
7947
  changedDocument: true,
@@ -7895,12 +7950,13 @@ ${block}`;
7895
7950
  }), path, "undo_not_verified", "undo");
7896
7951
  return result;
7897
7952
  }
7898
- function isUndoVerifiedContentMatching(run, result) {
7953
+ function isUndoVerifiedContentMatching(run, result, options2 = {}) {
7899
7954
  const expectedByPath = new Map((run?.undoExpectedFiles || []).filter((file) => typeof file?.path === "string" && typeof file?.content === "string").map((file) => [normalizeSettlementPath(file.path), file.content]).filter(([path]) => path));
7900
7955
  if (!expectedByPath.size) return true;
7956
+ const postByPath = new Map((Array.isArray(options2?.postFiles) ? options2.postFiles : []).filter((file) => typeof file?.path === "string" && typeof file?.content === "string").map((file) => [normalizeSettlementPath(file.path), file.content]));
7901
7957
  const applied = Array.isArray(result?.applied) ? result.applied : [];
7902
7958
  return Array.from(expectedByPath).every(
7903
- ([path, expected]) => applied.some((entry) => isVerifiedUndoEntryForPath(entry, path, expected))
7959
+ ([path, expected]) => applied.some((entry) => isVerifiedUndoEntryForPath(entry, path, expected, postByPath.get(path)))
7904
7960
  );
7905
7961
  }
7906
7962
  function isUndoResultEffectivelyApplied(run, result, undoOperations = []) {
@@ -7914,7 +7970,7 @@ ${block}`;
7914
7970
  (item) => isVerifiedUndoEntryForPath(item, path, expectedByPath.get(path))
7915
7971
  ));
7916
7972
  }
7917
- function isVerifiedUndoEntryForPath(entry, path, expectedContent) {
7973
+ function isVerifiedUndoEntryForPath(entry, path, expectedContent, postContent) {
7918
7974
  const inner = entry?.result || {};
7919
7975
  if (inner.ok === false) return false;
7920
7976
  const normalizedPath = normalizeSettlementPath(path);
@@ -7929,7 +7985,12 @@ ${block}`;
7929
7985
  return inner.method === "overleaf-editor-undo" && inner.verified === true && inner.verifiedContent === expectedContent;
7930
7986
  }
7931
7987
  if (key === `snapshot-undo:${normalizedPath}`) {
7932
- return inner.verifiedContent === expectedContent;
7988
+ if (inner.verifiedContent === expectedContent) return true;
7989
+ const proof = inner.undoRebaseProof, before = proof?.beforeUndoContent;
7990
+ if (inner.verified !== true || proof?.version !== 1 || normalizeSettlementPath(proof.path) !== normalizedPath || typeof before !== "string" || typeof postContent !== "string" || !postContent || typeof inner.verifiedContent !== "string") return false;
7991
+ const start = before.indexOf(postContent);
7992
+ if (start < 0 || before.indexOf(postContent, start + 1) >= 0) return false;
7993
+ return inner.verifiedContent === before.slice(0, start) + expectedContent + before.slice(start + postContent.length);
7933
7994
  }
7934
7995
  return false;
7935
7996
  }
@@ -8775,6 +8836,7 @@ ${block}`;
8775
8836
  undoExpectedFiles: normalizeRunFiles(run.undoExpectedFiles),
8776
8837
  undoStatus: sanitizeAssistantVisibleText(run.undoStatus),
8777
8838
  nativeRequestId: sanitizeAssistantVisibleText(run.nativeRequestId),
8839
+ codexThreadId: sanitizeAssistantVisibleText(run.codexThreadId),
8778
8840
  codexTurnId: sanitizeAssistantVisibleText(run.codexTurnId),
8779
8841
  forkSnapshot: run.forkSnapshot === true,
8780
8842
  forkSourceRunId: sanitizeAssistantVisibleText(run.forkSourceRunId),
@@ -8792,6 +8854,9 @@ ${block}`;
8792
8854
  if (run.changedDocument === true) {
8793
8855
  normalized.changedDocument = true;
8794
8856
  }
8857
+ if (Array.isArray(run.trackedChangeCaptures) && run.trackedChangeCaptures.length && !run.forkSnapshot) {
8858
+ normalized.trackedChangeCaptures = sanitizeAssistantVisibleValue(run.trackedChangeCaptures);
8859
+ }
8795
8860
  applyTrackedChangeStatus(normalized, run.trackedChangeStatus);
8796
8861
  return normalized;
8797
8862
  }
@@ -9226,6 +9291,7 @@ ${block}`;
9226
9291
  undoExpectedFiles: undoPayload.undoExpectedFiles,
9227
9292
  undoStatus: summarizeTextForStorage(run.undoStatus, "undo status"),
9228
9293
  nativeRequestId: normalizeTextField(run.nativeRequestId, 160),
9294
+ codexThreadId: normalizeTextField(run.codexThreadId, 160),
9229
9295
  codexTurnId: normalizeTextField(run.codexTurnId, 160),
9230
9296
  forkSnapshot: run.forkSnapshot === true,
9231
9297
  forkSourceRunId: normalizeTextField(run.forkSourceRunId, 160),
@@ -9558,6 +9624,7 @@ ${block}`;
9558
9624
  setActiveSession,
9559
9625
  updateActiveSession,
9560
9626
  normalizeRuns,
9627
+ normalizeProjectReferenceFiles,
9561
9628
  prepareStateForStorage,
9562
9629
  estimateJsonBytes,
9563
9630
  computeSafeTaskSummary
@@ -9798,7 +9865,13 @@ ${block}`;
9798
9865
  } catch (_error) {
9799
9866
  return empty;
9800
9867
  }
9801
- return getUtf8ByteLength(serialized) <= MAX_PERSISTED_ACTION_BYTES_PER_RUN ? payload : empty;
9868
+ if (getUtf8ByteLength(serialized) > MAX_PERSISTED_ACTION_BYTES_PER_RUN) return empty;
9869
+ const captures = cloneSerializableArray(run.trackedChangeCaptures);
9870
+ if (captures.length) {
9871
+ const extended = { ...payload, trackedChangeCaptures: captures };
9872
+ if (getUtf8ByteLength(JSON.stringify(extended)) <= MAX_PERSISTED_ACTION_BYTES_PER_RUN) return extended;
9873
+ }
9874
+ return payload;
9802
9875
  }
9803
9876
  function emptyActionPayload() {
9804
9877
  return {
@@ -9826,6 +9899,37 @@ ${block}`;
9826
9899
  }
9827
9900
  return value.length * 2;
9828
9901
  }
9902
+ function compactRunExecutionSnapshot(run, normalizeString) {
9903
+ var snapshot = run && run.executionSnapshot;
9904
+ if (!snapshot || typeof snapshot !== "object" || Array.isArray(snapshot)) return {};
9905
+ var normalize = typeof normalizeString === "function" ? normalizeString : String;
9906
+ var compact = {};
9907
+ var fields = [
9908
+ "schemaVersion",
9909
+ "mode",
9910
+ "providerId",
9911
+ "providerRevision",
9912
+ "model",
9913
+ "reasoningEffort",
9914
+ "speedTier",
9915
+ "autoRecompile",
9916
+ "requireReviewing",
9917
+ "capturedAt",
9918
+ "source"
9919
+ ];
9920
+ for (var key of fields) {
9921
+ if (!Object.prototype.hasOwnProperty.call(snapshot, key)) continue;
9922
+ var value = snapshot[key];
9923
+ if (typeof value === "string") compact[key] = normalize(value);
9924
+ else if (value === null || typeof value === "number" || typeof value === "boolean") compact[key] = value;
9925
+ }
9926
+ if (Array.isArray(snapshot.focusFiles)) {
9927
+ compact.focusFiles = snapshot.focusFiles.slice(0, 100).filter(function(value2) {
9928
+ return typeof value2 === "string";
9929
+ }).map(normalize);
9930
+ }
9931
+ return { executionSnapshot: compact };
9932
+ }
9829
9933
  function compactProviderSnapshot(value) {
9830
9934
  return {
9831
9935
  providerId: typeof value.providerId === "string" && value.providerId ? value.providerId : "builtin",
@@ -9854,6 +9958,56 @@ ${block}`;
9854
9958
  }
9855
9959
  return out;
9856
9960
  }
9961
+ function mergeSessionReviewState(incoming, existing, reviewRunIds) {
9962
+ if (!existing || incoming.id !== existing.id || incoming.projectId !== existing.projectId || !incoming.accountScopeId || incoming.accountScopeId !== existing.accountScopeId) return incoming;
9963
+ var terminal = (run) => ["accepted", "rejected"].includes(run?.trackedChangeStatus);
9964
+ var incomingRuns = new Map((incoming.runs || []).map((run) => [run.id, run]));
9965
+ var existingRuns = new Map((existing.runs || []).map((run) => [run.id, run]));
9966
+ var requestedIds = new Set(reviewRunIds || []);
9967
+ var explicitReview = Array.from(requestedIds).some((id) => ["accepted", "rejected", "needs_review"].includes(incomingRuns.get(id)?.trackedChangeStatus));
9968
+ var newer = (Date.parse(incoming.updatedAt) || 0) >= (Date.parse(existing.updatedAt) || 0);
9969
+ if (!newer && !explicitReview) return incoming;
9970
+ var base = newer ? incoming : existing;
9971
+ var fields = [
9972
+ "trackedChangeStatus",
9973
+ "undoStatus",
9974
+ "undoTrackedChanges",
9975
+ "undoExpectedFiles",
9976
+ "undoOperations",
9977
+ "undoBaseFiles",
9978
+ "appliedOperations",
9979
+ "trackedChangeCaptures",
9980
+ "settlement",
9981
+ "settlementFacts"
9982
+ ];
9983
+ var changed = false;
9984
+ var runs = (base.runs || []).map(function(run) {
9985
+ var previous = existingRuns.get(run.id), candidate = incomingRuns.get(run.id);
9986
+ var source = terminal(previous) ? previous : explicitReview && requestedIds.has(run.id) ? candidate : null;
9987
+ if (!source || source === run) return run;
9988
+ if (source.runProjectId && source.runProjectId !== incoming.projectId) return run;
9989
+ var merged2 = { ...run };
9990
+ for (var field of fields) {
9991
+ if (Object.prototype.hasOwnProperty.call(source, field)) {
9992
+ merged2[field] = JSON.parse(JSON.stringify(source[field]));
9993
+ } else {
9994
+ delete merged2[field];
9995
+ }
9996
+ }
9997
+ changed = true;
9998
+ return merged2;
9999
+ });
10000
+ if (!changed) return base;
10001
+ var merged = { ...base, runs };
10002
+ if (explicitReview && !newer) {
10003
+ merged.updatedAt = new Date(Math.max(
10004
+ Date.now(),
10005
+ Date.parse(existing.updatedAt) || 0,
10006
+ Date.parse(incoming.updatedAt) || 0
10007
+ ) + 1).toISOString();
10008
+ }
10009
+ return merged;
10010
+ }
9857
10011
  function hashString(value) {
9858
10012
  var hash = 2166136261;
9859
10013
  var text = String(value || "");
@@ -9865,9 +10019,11 @@ ${block}`;
9865
10019
  }
9866
10020
  return {
9867
10021
  compactStructuredEventValue,
10022
+ mergeSessionReviewState,
9868
10023
  compactRunsForStorage,
9869
10024
  compactRunActionPayload,
9870
10025
  compactProviderSnapshot,
10026
+ compactRunExecutionSnapshot,
9871
10027
  hashString
9872
10028
  };
9873
10029
  });
@@ -9945,19 +10101,21 @@ ${block}`;
9945
10101
  require2("./storageRunActions"),
9946
10102
  require2("./runInputQueue"),
9947
10103
  require2("./settlementFacts"),
9948
- require2("./storageSessionClaims")
10104
+ require2("./storageSessionClaims"),
10105
+ require2("./sessionState")
9949
10106
  );
9950
10107
  } else {
9951
10108
  root.CodexOverleafModuleRegistry.define("StorageDb", [
9952
10109
  "StorageRunActions",
9953
10110
  "RunInputQueue",
9954
10111
  "SettlementFacts",
9955
- "StorageSessionClaims"
10112
+ "StorageSessionClaims",
10113
+ "SessionState"
9956
10114
  ], factory);
9957
10115
  }
9958
- })(typeof globalThis !== "undefined" ? globalThis : window, function storageDbFactory(StorageRunActions, RunInputQueue, SettlementFacts, StorageSessionClaims) {
10116
+ })(typeof globalThis !== "undefined" ? globalThis : window, function storageDbFactory(StorageRunActions, RunInputQueue, SettlementFacts, StorageSessionClaims, SessionState) {
9959
10117
  "use strict";
9960
- if (!StorageRunActions?.compactRunsForStorage || !StorageRunActions?.compactRunActionPayload || !StorageRunActions?.compactProviderSnapshot || !StorageRunActions?.compactStructuredEventValue || !StorageRunActions?.hashString || !RunInputQueue?.compactForStorage || !SettlementFacts?.compactSettlementFacts || !StorageSessionClaims?.createSessionClaimer) {
10118
+ if (!StorageRunActions?.compactRunsForStorage || !StorageRunActions?.compactRunActionPayload || !StorageRunActions?.compactProviderSnapshot || !StorageRunActions?.compactRunExecutionSnapshot || !StorageRunActions?.compactStructuredEventValue || !StorageRunActions?.hashString || !RunInputQueue?.compactForStorage || !SettlementFacts?.compactSettlementFacts || !StorageSessionClaims?.createSessionClaimer) {
9961
10119
  throw new Error("Codex Overleaf storage run-action helpers are unavailable.");
9962
10120
  }
9963
10121
  var TARGET_SCHEMA_VERSION = 3;
@@ -10225,6 +10383,7 @@ ${block}`;
10225
10383
  codexThreadId: typeof input.codexThreadId === "string" ? input.codexThreadId : "",
10226
10384
  status: typeof input.status === "string" && input.status ? input.status : "active",
10227
10385
  focusFiles: normalizePathList(input.focusFiles),
10386
+ projectReferenceFiles: SessionState.normalizeProjectReferenceFiles(input.projectReferenceFiles),
10228
10387
  pendingInputs: RunInputQueue.compactForStorage(input.pendingInputs, {
10229
10388
  normalizeField: normalizeTextField,
10230
10389
  normalizeDisplay: normalizeDisplayTextForStorage,
@@ -10264,8 +10423,8 @@ ${block}`;
10264
10423
  }
10265
10424
  }
10266
10425
  function deriveSafeTaskSummaryFromInput(input) {
10267
- var SessionState = loadSessionState();
10268
- if (!SessionState || !(SessionState.computeSafeTaskSummary instanceof Function)) {
10426
+ var SessionState2 = loadSessionState();
10427
+ if (!SessionState2 || !(SessionState2.computeSafeTaskSummary instanceof Function)) {
10269
10428
  return "";
10270
10429
  }
10271
10430
  var task = typeof input.task === "string" && input.task ? input.task : "";
@@ -10275,7 +10434,7 @@ ${block}`;
10275
10434
  task = firstRun.task;
10276
10435
  }
10277
10436
  }
10278
- return SessionState.computeSafeTaskSummary(task);
10437
+ return SessionState2.computeSafeTaskSummary(task);
10279
10438
  }
10280
10439
  var _sessionStateCache = null;
10281
10440
  function loadSessionState() {
@@ -10560,12 +10719,7 @@ ${block}`;
10560
10719
  });
10561
10720
  }
10562
10721
  function compactRunsForStorage(runs, options2) {
10563
- return StorageRunActions.compactRunsForStorage(
10564
- runs,
10565
- options2,
10566
- SESSION_STORAGE_LIMITS.maxRunsPerSession,
10567
- compactRunForStorage
10568
- );
10722
+ return StorageRunActions.compactRunsForStorage(runs, options2, SESSION_STORAGE_LIMITS.maxRunsPerSession, compactRunForStorage);
10569
10723
  }
10570
10724
  function compactRunForStorage(run, keepActionPayload) {
10571
10725
  var actionPayload = StorageRunActions.compactRunActionPayload(run, keepActionPayload);
@@ -10574,6 +10728,9 @@ ${block}`;
10574
10728
  task: normalizeDisplayTextForStorage(run.task || "untitled task", SESSION_STORAGE_LIMITS.taskChars),
10575
10729
  mode: typeof run.mode === "string" ? redactSecretLikeText(run.mode) : "",
10576
10730
  ...StorageRunActions.compactProviderSnapshot(run),
10731
+ ...StorageRunActions.compactRunExecutionSnapshot(run, function(text) {
10732
+ return normalizeTextField(text, 4096);
10733
+ }),
10577
10734
  model: normalizeTextField(run.model, 80),
10578
10735
  reasoningEffort: typeof run.reasoningEffort === "string" ? redactSecretLikeText(run.reasoningEffort) : "",
10579
10736
  speedTier: typeof run.speedTier === "string" ? redactSecretLikeText(run.speedTier) : "",
@@ -10589,8 +10746,10 @@ ${block}`;
10589
10746
  undoBaseFiles: actionPayload.undoBaseFiles,
10590
10747
  undoTrackedChanges: actionPayload.undoTrackedChanges,
10591
10748
  undoExpectedFiles: actionPayload.undoExpectedFiles,
10749
+ ...actionPayload.trackedChangeCaptures ? { trackedChangeCaptures: actionPayload.trackedChangeCaptures } : {},
10592
10750
  undoStatus: normalizeDisplayTextForStorage(run.undoStatus, SESSION_STORAGE_LIMITS.statusTextChars),
10593
10751
  nativeRequestId: normalizeTextField(run.nativeRequestId, 160),
10752
+ codexThreadId: normalizeTextField(run.codexThreadId, 160),
10594
10753
  codexTurnId: normalizeTextField(run.codexTurnId, 160),
10595
10754
  nativeEventSeq: Number.isFinite(Number(run.nativeEventSeq)) ? Number(run.nativeEventSeq) : 0,
10596
10755
  queueItemId: normalizeTextField(run.queueItemId, 160)
@@ -11149,6 +11308,7 @@ ${block}`;
11149
11308
  clearStore,
11150
11309
  clearAllStores,
11151
11310
  buildSessionRecord,
11311
+ mergeSessionReviewState: StorageRunActions.mergeSessionReviewState,
11152
11312
  buildTurnRecord,
11153
11313
  buildEventRecord,
11154
11314
  buildArtifactRecord,
@@ -11775,7 +11935,7 @@ ${block}`;
11775
11935
  const MIN_NATIVE_VERSION = "1.0.0";
11776
11936
  const MIN_COMPATIBLE_NATIVE_VERSION = "1.0.0";
11777
11937
  const MIN_COMPATIBLE_EXTENSION_VERSION = "1.0.0";
11778
- const BUILD_TARGET_VERSION = "2.3.6";
11938
+ const BUILD_TARGET_VERSION = "2.4.1";
11779
11939
  const DEFAULT_CHROME_EXTENSION_ID = "illdpneeeopfffmiepaejglgmhpmdhdc";
11780
11940
  const REQUIRED_CAPABILITIES = Object.freeze([
11781
11941
  "bridgePing",
@@ -12078,7 +12238,7 @@ ${block}`;
12078
12238
  if (root) root.CodexOverleafPageRpcContract = api;
12079
12239
  })(typeof window !== "undefined" ? window : typeof globalThis !== "undefined" ? globalThis : this, function() {
12080
12240
  "use strict";
12081
- var REVISION = "2026-08-04-page-rpc-contract-v15";
12241
+ var REVISION = "2026-09-14-native-text-upload-v28";
12082
12242
  var METHODS = freezeCatalog({
12083
12243
  initializeCapability: entry("bridge.initialize", "control", "none", "default", false, "none"),
12084
12244
  probe: entry("bridge.probe", "read", "none", "default", false, "safe"),
@@ -12095,6 +12255,7 @@ ${block}`;
12095
12255
  binaryUploadCommit: entry("asset.write", "document", "required", "writeback", true, "no_retry"),
12096
12256
  binaryUploadAbort: entry("asset.stage", "cache", "none", "default", false, "idempotent"),
12097
12257
  jumpToPosition: entry("editor.navigate", "navigation", "required", "default", false, "idempotent"),
12258
+ reconcileTrackedChangeCapture: entry("tracked_changes.read", "read", "required", "default", false, "safe"),
12098
12259
  rejectTrackedChanges: entry("tracked_changes.reject", "document", "required", "lifecycle", true, "no_retry"),
12099
12260
  acceptTrackedChanges: entry("tracked_changes.accept", "document", "required", "lifecycle", true, "no_retry"),
12100
12261
  triggerCompile: entry("compile.trigger", "page_state", "required", "compile", false, "no_retry"),
@@ -13463,9 +13624,11 @@ ${block}`;
13463
13624
  ["src/page/overleafProjectSnapshot.js", "codex-overleaf-project-snapshot-script"],
13464
13625
  ["src/page/pageBridgeCapability.js", "codex-overleaf-page-bridge-capability-script"],
13465
13626
  ["src/page/treeOperations.js", "codex-overleaf-tree-operations-script", true],
13627
+ ["src/page/textFileCreator.js", "codex-overleaf-text-file-creator-script", true],
13466
13628
  ["src/page/binaryAssetUploader.js", "codex-overleaf-binary-asset-uploader-script", true],
13467
13629
  ["src/page/snapshotRouter.js", "codex-overleaf-snapshot-router-script"],
13468
13630
  ["src/page/writeGuard.js", "codex-overleaf-write-guard-script", true],
13631
+ ["src/page/trackedChangeCapture.js", "codex-overleaf-tracked-change-capture-script", true],
13469
13632
  ["src/page/trackedChangesLifecycle.js", "codex-overleaf-tracked-changes-lifecycle-script", true],
13470
13633
  ["src/page/writebackRouter.js", "codex-overleaf-writeback-router-script", true],
13471
13634
  ["src/pageBridge.js", "codex-overleaf-page-bridge-script", true]
@@ -13665,7 +13828,9 @@ ${block}`;
13665
13828
  async function dispatch(payload, channelMethod) {
13666
13829
  const compatibilityGate = await ensureForMethod(payload?.method);
13667
13830
  if (!compatibilityGate.ok) return compatibilityGate.response;
13668
- if (gatedMethods.has(payload?.method)) options2.throwIfCancellationRequested?.();
13831
+ if (gatedMethods.has(payload?.method) && payload.method !== "mirror.invalidate") {
13832
+ options2.throwIfCancellationRequested?.();
13833
+ }
13669
13834
  return nativeChannel[channelMethod](
13670
13835
  attachEvidence(payload, compatibilityGate.compatibility)
13671
13836
  );
@@ -14318,8 +14483,10 @@ ${block}`;
14318
14483
  merged.applied.push(...Array.isArray(result?.applied) ? result.applied : []);
14319
14484
  merged.skipped.push(...Array.isArray(result?.skipped) ? result.skipped : []);
14320
14485
  merged.trackedChanges.push(...Array.isArray(result?.trackedChanges) ? result.trackedChanges : []);
14486
+ if (result?.trackedChangeCaptures?.length) (merged.trackedChangeCaptures || (merged.trackedChangeCaptures = [])).push(...result.trackedChangeCaptures);
14321
14487
  if (result?.ok !== true) merged.ok = false;
14322
14488
  }
14489
+ if (merged.trackedChangeCaptures?.some((capture) => capture.state !== "observed")) merged.trackedChanges = [];
14323
14490
  if (merged.skipped.length) merged.ok = false;
14324
14491
  return merged;
14325
14492
  }
@@ -15315,6 +15482,8 @@ ${block}`;
15315
15482
  saveState = null,
15316
15483
  saveStateSoon = () => {
15317
15484
  },
15485
+ onProjectFilesReady = () => {
15486
+ },
15318
15487
  updateActiveSession = defaultUpdateActiveSession,
15319
15488
  callPageBridge = () => Promise.resolve({ ok: false }),
15320
15489
  getCurrentProjectId = () => "",
@@ -15478,6 +15647,7 @@ ${block}`;
15478
15647
  renderContextFiles(contextProject);
15479
15648
  if (isExactContextFileListProject(contextProject)) {
15480
15649
  setContextSyncState("exact-ready");
15650
+ onProjectFilesReady(contextProject);
15481
15651
  } else if (contextSyncState !== "partial") {
15482
15652
  setContextSyncState("exact-loading");
15483
15653
  void enhanceContextFilesFromExactSnapshot({ loadId: contextLoadId });
@@ -15499,6 +15669,7 @@ ${block}`;
15499
15669
  contextProject = project;
15500
15670
  setContextSyncState(isExactContextFileListProject(project) ? "exact-ready" : "exact-loading");
15501
15671
  renderContextFiles(project);
15672
+ if (isExactContextFileListProject(project)) onProjectFilesReady(project);
15502
15673
  if (!isExactContextFileListProject(project)) {
15503
15674
  void enhanceContextFilesFromExactSnapshot({
15504
15675
  force: Boolean(options2.force),
@@ -15556,6 +15727,7 @@ ${block}`;
15556
15727
  contextProject = project;
15557
15728
  setContextSyncState("exact-ready");
15558
15729
  renderContextFiles(project);
15730
+ onProjectFilesReady(project);
15559
15731
  } catch (_error) {
15560
15732
  if (loadId === contextLoadId) {
15561
15733
  setContextSyncState("partial");
@@ -16756,6 +16928,20 @@ ${block}`;
16756
16928
  })) return { ok: false, reason: "ot_event_expired" };
16757
16929
  return { ok: true };
16758
16930
  }
16931
+ function readOtBridgeStatus(response) {
16932
+ if (!response || response.ok === false) return "unavailable";
16933
+ return response.state || response.status || response.result?.state || response.result?.status || "unavailable";
16934
+ }
16935
+ function readOtBridgeEvents(response) {
16936
+ if (Array.isArray(response)) return response;
16937
+ if (Array.isArray(response?.events)) return response.events;
16938
+ if (Array.isArray(response?.result?.events)) return response.result.events;
16939
+ return [];
16940
+ }
16941
+ function readOtBridgeErrorCode(response, fallback) {
16942
+ const value = response?.lastErrorCode || response?.reason || response?.error?.code || response?.error || fallback;
16943
+ return typeof value === "string" ? value : fallback;
16944
+ }
16759
16945
  function normalizeFailureCode(value, fallback = "ot_error") {
16760
16946
  const code = typeof value === "string" ? value : value?.code;
16761
16947
  if (/^[a-z][a-z0-9_]{0,79}$/.test(code || "")) return code;
@@ -16833,6 +17019,9 @@ ${block}`;
16833
17019
  takePatchBatch,
16834
17020
  validatePatchReceipt,
16835
17021
  normalizeFailureCode,
17022
+ readOtBridgeStatus,
17023
+ readOtBridgeEvents,
17024
+ readOtBridgeErrorCode,
16836
17025
  getFailureMessageKey,
16837
17026
  buildPatchFilesRequest,
16838
17027
  canUseOtWarmStart,
@@ -17976,8 +18165,8 @@ ${block}`;
17976
18165
  <div class="codex-custom-instructions-head">
17977
18166
  <button type="button" data-settings-back title="Back" aria-label="Back">\u2039</button>
17978
18167
  <div>
17979
- <div class="codex-custom-instructions-title" data-i18n="projectSettingsTitle">Project Settings</div>
17980
- <div class="codex-custom-instructions-subtitle" data-i18n="projectSettingsSubtitle">Customize how Codex behaves in this and all projects.</div>
18168
+ <div class="codex-custom-instructions-title" data-settings-title data-i18n="projectSettingsTitle">Project Settings</div>
18169
+ <div class="codex-custom-instructions-subtitle" data-settings-subtitle data-i18n="projectSettingsSubtitle">Customize how Codex behaves in this and all projects.</div>
17981
18170
  </div>
17982
18171
  </div>
17983
18172
  <div class="codex-project-settings-status" data-project-settings-status></div>
@@ -18235,6 +18424,7 @@ ${block}`;
18235
18424
  if (!root) {
18236
18425
  return;
18237
18426
  }
18427
+ refreshSettingsScope(instance, root);
18238
18428
  const button = getButton(instance);
18239
18429
  if (button) {
18240
18430
  button.dataset.active = "true";
@@ -18242,6 +18432,17 @@ ${block}`;
18242
18432
  }
18243
18433
  root.querySelector("[data-custom-instructions-input]")?.focus?.();
18244
18434
  }
18435
+ function refreshSettingsScope(instance, root) {
18436
+ const accountScope = instance.container?.closest?.("[data-settings-scope]")?.dataset?.settingsScope === "account";
18437
+ const prefix = accountScope ? "recentProjects_settings" : "projectSettings";
18438
+ for (const [selector, suffix] of [["[data-settings-title]", "Title"], ["[data-settings-subtitle]", "Subtitle"]]) {
18439
+ const label = root.querySelector(selector);
18440
+ if (!label) continue;
18441
+ const key = prefix + suffix;
18442
+ label.setAttribute("data-i18n", key);
18443
+ label.textContent = t(instance, key);
18444
+ }
18445
+ }
18245
18446
  function hide(target) {
18246
18447
  const instance = target?._instance || target;
18247
18448
  const root = getRoot(instance);
@@ -18269,6 +18470,7 @@ ${block}`;
18269
18470
  if (!root) {
18270
18471
  return;
18271
18472
  }
18473
+ refreshSettingsScope(instance, root);
18272
18474
  if (Object.prototype.hasOwnProperty.call(state, "customInstructions")) {
18273
18475
  const input = root.querySelector("[data-custom-instructions-input]");
18274
18476
  if (input) {
@@ -20132,7 +20334,12 @@ ${block}`;
20132
20334
  await Promise.all(deletedExisting.map((record) => StorageDb.deleteRecord("sessions", record.id)));
20133
20335
  }
20134
20336
  const existingById = new Map(existingSessions.filter((record) => !deleted.has(record.id)).map((record) => [record.id, record]));
20135
- const writable = sessionRecords.filter((record) => {
20337
+ const mergedRecords = sessionRecords.map((record) => StorageDb.mergeSessionReviewState?.(
20338
+ record,
20339
+ existingById.get(record.id),
20340
+ options2.reviewRunIds
20341
+ ) || record);
20342
+ const writable = mergedRecords.filter((record) => {
20136
20343
  const existing = existingById.get(record.id);
20137
20344
  if (!existing) return true;
20138
20345
  return (Date.parse(record.updatedAt || "") || 0) >= (Date.parse(existing.updatedAt || "") || 0);
@@ -20188,6 +20395,41 @@ ${block}`;
20188
20395
  }
20189
20396
  return JSON.parse(JSON.stringify(value));
20190
20397
  }
20398
+ async function applyReviewTransition({ getState, runId, transition, persist }) {
20399
+ const targets = () => Array.from(/* @__PURE__ */ new Set([
20400
+ ...getState().runs || [],
20401
+ ...(getState().sessions || []).flatMap((session) => session.runs || [])
20402
+ ])).filter((run) => run.id === runId);
20403
+ const records = targets();
20404
+ const sessions = (getState().sessions || []).filter((session) => (session.runs || []).some((run) => run.id === runId));
20405
+ if (!records.length || !sessions.length) throw reviewPersistenceError();
20406
+ const previous = cloneSnapshot(records[0]);
20407
+ for (const record of records) Object.assign(record, cloneSnapshot(transition));
20408
+ for (const session of sessions) {
20409
+ session.updatedAt = new Date(Math.max(
20410
+ Date.now(),
20411
+ (Date.parse(session.updatedAt) || 0) + 1,
20412
+ (Date.parse(session.lastActivityAt) || 0) + 1
20413
+ )).toISOString();
20414
+ session.lastActivityAt = session.updatedAt;
20415
+ }
20416
+ try {
20417
+ await persist({ preserveRunActionPayload: true, reviewRunIds: [runId] });
20418
+ } catch (error) {
20419
+ for (const record of targets()) {
20420
+ Object.assign(record, cloneSnapshot(previous), { trackedChangeStatus: "needs_review" });
20421
+ for (const field of ["settlement", "settlementFacts"]) {
20422
+ if (!Object.prototype.hasOwnProperty.call(previous, field)) delete record[field];
20423
+ }
20424
+ }
20425
+ throw error;
20426
+ }
20427
+ }
20428
+ function reviewPersistenceError() {
20429
+ return Object.assign(new Error("The review outcome was not durably saved. Check Overleaf before retrying."), {
20430
+ code: "review_state_not_persisted"
20431
+ });
20432
+ }
20191
20433
  async function persistPanelState(input) {
20192
20434
  var state = cloneSnapshot(input.state || {});
20193
20435
  var compactState = cloneSnapshot(input.compactState || {});
@@ -20206,6 +20448,8 @@ ${block}`;
20206
20448
  code: "stale_view"
20207
20449
  });
20208
20450
  }
20451
+ var reviewRuns = (input.reviewRunIds || []).map((id) => (state.sessions || []).flatMap((session) => session.runs || []).find((run) => run.id === id));
20452
+ if (reviewRuns.some((run) => !run)) throw reviewPersistenceError();
20209
20453
  var latestPrefs = typeof Migration.loadPrefs === "function" ? await Migration.loadPrefs(accountScopeId, projectId) : {};
20210
20454
  var prefs = Object.assign(
20211
20455
  {},
@@ -20261,19 +20505,23 @@ ${block}`;
20261
20505
  })
20262
20506
  }), { preserveRunActionPayload: true });
20263
20507
  });
20264
- await input.SessionPersistence.writeSessions({
20508
+ var written = await input.SessionPersistence.writeSessions({
20265
20509
  Migration,
20266
20510
  StorageDb,
20267
20511
  projectId,
20268
20512
  accountScopeId,
20269
20513
  deletedSessionIds,
20270
20514
  sessionRecords,
20515
+ reviewRunIds: input.reviewRunIds,
20271
20516
  queueClaims: persistenceMeta.queueClaims || {},
20272
20517
  queueTombstones,
20273
20518
  writerId: input.persistenceContext?.writerId || ""
20274
20519
  });
20520
+ if (!reviewRuns.every((reviewRun) => (written || []).some((session) => (session.runs || []).some(
20521
+ (run) => run.id === reviewRun.id && run.trackedChangeStatus === reviewRun.trackedChangeStatus
20522
+ )))) throw reviewPersistenceError();
20275
20523
  }
20276
- return Object.freeze({ persistPanelState });
20524
+ return Object.freeze({ persistPanelState, applyReviewTransition });
20277
20525
  });
20278
20526
  }).call(globalThis, void 0, void 0, void 0);
20279
20527
  (function runClassicContentModule63(module, exports, require2) {
@@ -20412,7 +20660,8 @@ ${block}`;
20412
20660
  }
20413
20661
  return Object.freeze({
20414
20662
  createController,
20415
- persistPanelState: PanelState?.persistPanelState || null
20663
+ persistPanelState: PanelState?.persistPanelState || null,
20664
+ applyReviewTransition: PanelState?.applyReviewTransition || null
20416
20665
  });
20417
20666
  });
20418
20667
  }).call(globalThis, void 0, void 0, void 0);
@@ -20428,7 +20677,8 @@ ${block}`;
20428
20677
  container,
20429
20678
  callbacks: options2.callbacks || {},
20430
20679
  i18n: options2.i18n || {},
20431
- dismissInstalled: false
20680
+ dismissInstalled: false,
20681
+ cleanup: []
20432
20682
  };
20433
20683
  container.innerHTML = `
20434
20684
  <div class="codex-diagnostics-wrap">
@@ -20512,6 +20762,11 @@ ${block}`;
20512
20762
  instance.callbacks.onBeforeOpen?.();
20513
20763
  }
20514
20764
  popover.hidden = !open;
20765
+ if (open) {
20766
+ openFloating(instance, popover, 300);
20767
+ } else {
20768
+ hideFloating(popover);
20769
+ }
20515
20770
  button.dataset.active = open ? "true" : "false";
20516
20771
  button.setAttribute("aria-expanded", open ? "true" : "false");
20517
20772
  }
@@ -20522,7 +20777,7 @@ ${block}`;
20522
20777
  if (!popover || !button) {
20523
20778
  return;
20524
20779
  }
20525
- popover.hidden = true;
20780
+ hideFloating(popover);
20526
20781
  button.dataset.active = "false";
20527
20782
  button.setAttribute("aria-expanded", "false");
20528
20783
  }
@@ -20530,7 +20785,7 @@ ${block}`;
20530
20785
  const instance = target?._instance || target;
20531
20786
  const result = instance?.container?.querySelector("[data-diagnostics-result]");
20532
20787
  if (result) {
20533
- result.hidden = true;
20788
+ hideFloating(result);
20534
20789
  }
20535
20790
  }
20536
20791
  function showLoading(target, title, subtitle) {
@@ -20586,6 +20841,65 @@ ${block}`;
20586
20841
  details.open = false;
20587
20842
  details.hidden = !technicalText;
20588
20843
  technical.textContent = technicalText;
20844
+ closeMenu(instance);
20845
+ openFloating(instance, root, 560);
20846
+ }
20847
+ function openFloating(instance, element, preferredWidth) {
20848
+ if (element.isConnected === false) return;
20849
+ element.hidden = false;
20850
+ if (typeof element.showPopover === "function") {
20851
+ element.setAttribute("popover", "manual");
20852
+ try {
20853
+ if (!element.matches(":popover-open")) element.showPopover();
20854
+ } catch (_error) {
20855
+ element.removeAttribute("popover");
20856
+ }
20857
+ }
20858
+ positionFloating(instance, element, preferredWidth);
20859
+ }
20860
+ function hideFloating(element) {
20861
+ if (!element) return;
20862
+ if (typeof element.hidePopover === "function" && element.getAttribute("popover") === "manual") {
20863
+ try {
20864
+ if (element.matches(":popover-open")) element.hidePopover();
20865
+ } catch (_error) {
20866
+ element.removeAttribute("popover");
20867
+ }
20868
+ }
20869
+ element.hidden = true;
20870
+ }
20871
+ function positionFloating(instance, element, preferredWidth) {
20872
+ if (!element || element.hidden) return;
20873
+ const button = instance.container.querySelector("[data-diagnostics-menu]");
20874
+ if (!button?.getBoundingClientRect || !element.getBoundingClientRect) return;
20875
+ if (button.getClientRects && !button.getClientRects().length) {
20876
+ closeMenu(instance);
20877
+ closeResult(instance);
20878
+ return;
20879
+ }
20880
+ const doc = instance.container.ownerDocument || document;
20881
+ const view = doc.defaultView || window;
20882
+ const viewport = view.visualViewport;
20883
+ const width = viewport?.width || view.innerWidth || doc.documentElement?.clientWidth;
20884
+ const height = viewport?.height || view.innerHeight || doc.documentElement?.clientHeight;
20885
+ if (!(width > 0 && height > 0)) return;
20886
+ const margin = 12;
20887
+ const gap = 8;
20888
+ const leftEdge = (viewport?.offsetLeft || 0) + margin;
20889
+ const topEdge = (viewport?.offsetTop || 0) + margin;
20890
+ const rightEdge = leftEdge + Math.max(0, width - margin * 2);
20891
+ const bottomEdge = topEdge + Math.max(0, height - margin * 2);
20892
+ element.style.width = `${Math.min(preferredWidth, Math.max(0, width - margin * 2))}px`;
20893
+ element.style.maxHeight = `${Math.min(640, Math.max(0, height - margin * 2))}px`;
20894
+ const anchor = button.getBoundingClientRect();
20895
+ const bounds = element.getBoundingClientRect();
20896
+ const below = Math.max(0, bottomEdge - anchor.bottom - gap);
20897
+ const above = Math.max(0, anchor.top - topEdge - gap);
20898
+ const desiredTop = bounds.height > below && above > below ? anchor.top - gap - bounds.height : anchor.bottom + gap;
20899
+ element.style.left = `${Math.max(leftEdge, Math.min(anchor.right - bounds.width, rightEdge - bounds.width))}px`;
20900
+ element.style.top = `${Math.max(topEdge, Math.min(desiredTop, bottomEdge - bounds.height))}px`;
20901
+ element.style.right = "auto";
20902
+ element.style.bottom = "auto";
20589
20903
  }
20590
20904
  function renderInstallCommand(instance, container, command) {
20591
20905
  const wrap = document.createElement("div");
@@ -20660,13 +20974,60 @@ ${block}`;
20660
20974
  return;
20661
20975
  }
20662
20976
  instance.dismissInstalled = true;
20663
- document.addEventListener("click", (event) => {
20977
+ const doc = instance.container.ownerDocument || document;
20978
+ const view = doc.defaultView || window;
20979
+ const listen = (target, type, handler, options2) => {
20980
+ if (!target?.addEventListener) return;
20981
+ target.addEventListener(type, handler, options2);
20982
+ instance.cleanup.push(() => target.removeEventListener(type, handler, options2));
20983
+ };
20984
+ listen(doc, "click", (event) => {
20664
20985
  const wrap = instance.container?.querySelector(".codex-diagnostics-wrap");
20665
20986
  if (!wrap || wrap.contains(event.target)) {
20666
20987
  return;
20667
20988
  }
20668
20989
  closeMenu(instance);
20669
20990
  }, true);
20991
+ listen(doc, "keydown", (event) => {
20992
+ if (event.key !== "Escape") return;
20993
+ const menu = instance.container.querySelector("[data-diagnostics-popover]");
20994
+ const result = instance.container.querySelector("[data-diagnostics-result]");
20995
+ if (menu && !menu.hidden) closeMenu(instance);
20996
+ else if (result && !result.hidden) closeResult(instance);
20997
+ else return;
20998
+ event.preventDefault();
20999
+ event.stopPropagation();
21000
+ instance.container.querySelector("[data-diagnostics-menu]")?.focus();
21001
+ }, true);
21002
+ let frame = null;
21003
+ const reposition = () => {
21004
+ frame = null;
21005
+ positionFloating(instance, instance.container.querySelector("[data-diagnostics-popover]"), 300);
21006
+ positionFloating(instance, instance.container.querySelector("[data-diagnostics-result]"), 560);
21007
+ };
21008
+ const schedule = () => {
21009
+ if (frame !== null) return;
21010
+ if (view.requestAnimationFrame) frame = view.requestAnimationFrame(reposition);
21011
+ else reposition();
21012
+ };
21013
+ listen(view, "resize", schedule);
21014
+ listen(doc, "scroll", schedule, true);
21015
+ listen(view.visualViewport, "resize", schedule);
21016
+ listen(view.visualViewport, "scroll", schedule);
21017
+ if (typeof view.ResizeObserver === "function") {
21018
+ const observer = new view.ResizeObserver(schedule);
21019
+ for (const node of [
21020
+ instance.container.closest("#codex-overleaf-panel") || instance.container,
21021
+ instance.container.querySelector("[data-diagnostics-popover]"),
21022
+ instance.container.querySelector("[data-diagnostics-result]")
21023
+ ]) {
21024
+ if (node) observer.observe(node);
21025
+ }
21026
+ instance.cleanup.push(() => observer.disconnect());
21027
+ }
21028
+ instance.cleanup.push(() => {
21029
+ if (frame !== null) view.cancelAnimationFrame?.(frame);
21030
+ });
20670
21031
  }
20671
21032
  const HEALTH_STATES = ["ok", "warn", "fail", "unknown"];
20672
21033
  function updateStatus(target, status = {}) {
@@ -20823,6 +21184,10 @@ ${block}`;
20823
21184
  return key;
20824
21185
  }
20825
21186
  function destroy(instance) {
21187
+ closeMenu(instance);
21188
+ closeResult(instance);
21189
+ for (const cleanup of instance.cleanup || []) cleanup();
21190
+ instance.cleanup = [];
20826
21191
  instance.container.textContent = "";
20827
21192
  }
20828
21193
  window.CodexOverleafDiagnosticsPanel = {
@@ -21290,6 +21655,7 @@ ${block}`;
21290
21655
  const raw = entry?.event || {};
21291
21656
  if (raw.type === "codex.turn.bound") {
21292
21657
  session.codexThreadId = String(raw.detail?.threadId || session.codexThreadId || "");
21658
+ record.codexThreadId = String(raw.detail?.threadId || record.codexThreadId || "");
21293
21659
  record.codexTurnId = String(raw.detail?.turnId || record.codexTurnId || "");
21294
21660
  record.nativeEventSeq = sequence;
21295
21661
  continue;
@@ -29322,6 +29688,8 @@ failedAt: ${new Date(failure.at).toISOString()}`;
29322
29688
  lines.push(`CodeMirror: docLength=${view.docLength || 0}; dispatch=${Boolean(view.hasDispatch)}; source=${view.source || "unknown"}`);
29323
29689
  }
29324
29690
  }
29691
+ const capture = probe?.trackedChangeCapture;
29692
+ if (capture) lines.push(tx("Tracked ledger: ", "\u7559\u75D5\u8BB0\u5F55\uFF1A") + "source=" + capture.source + "; ready=" + capture.ready + "; count=" + capture.count + "; doc=" + (capture.docId || "none") + "; adapter=" + capture.adapter + "; reason=" + (capture.reason || "none"));
29325
29693
  const projectDiagnostics = probe?.projectDiagnostics;
29326
29694
  if (projectDiagnostics) {
29327
29695
  lines.push(`Project records: ${projectDiagnostics.docRecordCount || 0}; roots=${(projectDiagnostics.internalRootKeys || []).slice(0, 6).join(", ") || "none"}`);
@@ -29548,7 +29916,17 @@ failedAt: ${new Date(failure.at).toISOString()}`;
29548
29916
  textarea.remove();
29549
29917
  if (!copied) throw new Error("clipboard_copy_failed");
29550
29918
  }
29551
- return Object.freeze({ configureResultActions });
29919
+ return Object.freeze({ configureResultActions, projectUndoAvailability });
29920
+ }
29921
+ function projectUndoAvailability(run, projectRunSettlement) {
29922
+ const payload = run.recoveryPayload && typeof run.recoveryPayload === "object" ? run.recoveryPayload : run;
29923
+ if (!Array.isArray(payload.appliedOperations)) return projectRunSettlement(run);
29924
+ const appliedOperations = payload.appliedOperations.filter((operation) => operation?.type !== "binary-create" && operation?.type !== "overwrite-binary");
29925
+ if (appliedOperations.length === payload.appliedOperations.length) return projectRunSettlement(run);
29926
+ return projectRunSettlement({
29927
+ ...run,
29928
+ recoveryPayload: { ...payload, appliedOperations }
29929
+ });
29552
29930
  }
29553
29931
  return Object.freeze({ create });
29554
29932
  });
@@ -30580,7 +30958,7 @@ ${conclusion}` : `${label} ${conclusion}`;
30580
30958
  configureLifecycleUndoButton(button, run);
30581
30959
  return;
30582
30960
  }
30583
- const projection = projectRunSettlement(run);
30961
+ const projection = RunResultActions.projectUndoAvailability(run, projectRunSettlement);
30584
30962
  if (!projection.canUndo && run.undoStatus !== "applied") {
30585
30963
  button.hidden = true;
30586
30964
  return;
@@ -30596,11 +30974,11 @@ ${conclusion}` : `${label} ${conclusion}`;
30596
30974
  }
30597
30975
  function configureLifecycleUndoButton(button, run) {
30598
30976
  const status = run.trackedChangeStatus || "";
30977
+ const inFlight = trackedChangeInFlight.get(run.id);
30599
30978
  const projection = projectRunSettlement(run);
30600
30979
  if (status === "needs_review") {
30601
- const inFlight2 = trackedChangeInFlight.get(run.id);
30602
30980
  button.hidden = false;
30603
- button.disabled = !projection.canUndo || inFlight2 === "reject" || inFlight2 === "accept";
30981
+ button.disabled = !projection.canUndo || inFlight === "reject" || inFlight === "accept";
30604
30982
  button.textContent = tr("undoRun");
30605
30983
  button.title = tr("undoRunTitle");
30606
30984
  button.addEventListener("click", (event) => {
@@ -30609,25 +30987,24 @@ ${conclusion}` : `${label} ${conclusion}`;
30609
30987
  });
30610
30988
  return;
30611
30989
  }
30612
- if (status === "rejected") {
30990
+ if (status === "rejected" && !inFlight) {
30613
30991
  button.hidden = false;
30614
30992
  button.disabled = true;
30615
30993
  button.textContent = tr("undoApplied");
30616
30994
  button.title = tr("undoAppliedTitle");
30617
30995
  return;
30618
30996
  }
30619
- if (status === "accepted") {
30997
+ if (status === "accepted" && !inFlight) {
30620
30998
  button.hidden = false;
30621
30999
  button.disabled = true;
30622
31000
  button.textContent = tr("undoRun");
30623
31001
  button.title = tr("undoRunTitle");
30624
31002
  return;
30625
31003
  }
30626
- if (status !== "pending") {
31004
+ if (status !== "pending" && !inFlight) {
30627
31005
  button.hidden = true;
30628
31006
  return;
30629
31007
  }
30630
- const inFlight = trackedChangeInFlight.get(run.id);
30631
31008
  button.hidden = false;
30632
31009
  button.disabled = !projection.canUndo || inFlight === "reject" || inFlight === "accept";
30633
31010
  button.textContent = tr("undoRun");
@@ -30653,12 +31030,12 @@ ${conclusion}` : `${label} ${conclusion}`;
30653
31030
  return;
30654
31031
  }
30655
31032
  const status = run.trackedChangeStatus || "";
31033
+ const inFlight = trackedChangeInFlight.get(run.id);
30656
31034
  const projection = projectRunSettlement(run);
30657
31035
  if (status === "needs_review") {
30658
- const inFlight2 = trackedChangeInFlight.get(run.id);
30659
31036
  button.hidden = false;
30660
- button.disabled = !projection.canAccept || inFlight2 === "accept" || inFlight2 === "reject";
30661
- if (inFlight2 === "accept") {
31037
+ button.disabled = !projection.canAccept || inFlight === "accept" || inFlight === "reject";
31038
+ if (inFlight === "accept") {
30662
31039
  button.textContent = tr("runAcceptTrackedConfirming");
30663
31040
  button.title = tr("runAcceptTrackedConfirming");
30664
31041
  return;
@@ -30668,25 +31045,24 @@ ${conclusion}` : `${label} ${conclusion}`;
30668
31045
  wireAcceptInlineConfirm(button, run.id);
30669
31046
  return;
30670
31047
  }
30671
- if (status === "accepted") {
31048
+ if (status === "accepted" && !inFlight) {
30672
31049
  button.hidden = false;
30673
31050
  button.disabled = true;
30674
31051
  button.textContent = tr("runAcceptTrackedDone");
30675
31052
  button.title = tr("runAcceptTrackedDoneTitle");
30676
31053
  return;
30677
31054
  }
30678
- if (status === "rejected") {
31055
+ if (status === "rejected" && !inFlight) {
30679
31056
  button.hidden = false;
30680
31057
  button.disabled = true;
30681
31058
  button.textContent = tr("runAcceptTracked");
30682
31059
  button.title = tr("runAcceptTrackedTitle");
30683
31060
  return;
30684
31061
  }
30685
- if (status !== "pending") {
31062
+ if (status !== "pending" && !inFlight) {
30686
31063
  button.hidden = true;
30687
31064
  return;
30688
31065
  }
30689
- const inFlight = trackedChangeInFlight.get(run.id);
30690
31066
  button.hidden = false;
30691
31067
  button.disabled = !projection.canAccept || inFlight === "accept" || inFlight === "reject";
30692
31068
  if (inFlight === "accept") {
@@ -31578,19 +31954,26 @@ ${conclusion}` : `${label} ${conclusion}`;
31578
31954
  const source = findSource(previousState, runId);
31579
31955
  if (!source) return failFork("session_fork_source_missing", tr("forkRunUnavailable"));
31580
31956
  const { session, run, runIndex } = source;
31581
- if (run.status === "running" || !session.codexThreadId || !run.codexTurnId) {
31957
+ const draftThreadId = run.codexTurnId && run.interruptedDraft?.turnId === run.codexTurnId ? run.interruptedDraft.threadId : "";
31958
+ const sourceThreadId = String(run.codexThreadId || draftThreadId || session.codexThreadId || "").trim();
31959
+ if (run.status === "running" || !sourceThreadId || !run.codexTurnId) {
31582
31960
  return failFork("session_fork_unavailable", tr("forkRunUnavailable"));
31583
31961
  }
31584
- const response = await sendBackgroundNative({
31585
- method: "codex.thread.fork",
31586
- params: {
31587
- threadId: session.codexThreadId,
31588
- lastTurnId: run.codexTurnId,
31589
- speedTier: session.speedTier,
31590
- loadCodexLocalSkills: previousState.loadCodexLocalSkills !== false,
31591
- loadCodexOverleafSkills: previousState.loadCodexOverleafSkills !== false
31592
- }
31593
- });
31962
+ let response;
31963
+ try {
31964
+ response = await sendBackgroundNative({
31965
+ method: "codex.thread.fork",
31966
+ params: {
31967
+ threadId: sourceThreadId,
31968
+ lastTurnId: run.codexTurnId,
31969
+ speedTier: session.speedTier,
31970
+ loadCodexLocalSkills: previousState.loadCodexLocalSkills !== false,
31971
+ loadCodexOverleafSkills: previousState.loadCodexOverleafSkills !== false
31972
+ }
31973
+ });
31974
+ } catch (error) {
31975
+ return failFork(error?.code || "session_fork_failed", error?.message || tr("forkRunFailed"));
31976
+ }
31594
31977
  if (!response?.ok || !response.result?.threadId) {
31595
31978
  return failFork(response?.error?.code || "session_fork_failed", response?.error?.message || tr("forkRunFailed"));
31596
31979
  }
@@ -32027,11 +32410,45 @@ ${conclusion}` : `${label} ${conclusion}`;
32027
32410
  function formatCompactModelLabel(label) {
32028
32411
  return String(label || "").replace(/^gpt[-\s]*/i, "");
32029
32412
  }
32030
- function retainSelectedModel(models, selectedModel) {
32413
+ function retainSelectedModel(models, selectedModel, options2 = {}) {
32031
32414
  const selectedId = normalizeModelOptionId(selectedModel);
32032
32415
  if (!selectedId) return "";
32416
+ if (options2.preserveUnavailable === true) return selectedId;
32033
32417
  return (Array.isArray(models) ? models : []).some((model) => normalizeModelOptionId(typeof model === "string" ? model : model?.id) === selectedId) ? selectedId : "";
32034
32418
  }
32419
+ function preserveSelectedCapabilities(models, selectedModel, preferences = {}, provisional = false) {
32420
+ const selectedId = normalizeModelOptionId(selectedModel);
32421
+ const saved = preferences || {};
32422
+ return (Array.isArray(models) ? models : []).map((model) => {
32423
+ if (model.id !== selectedId || !provisional && !model.unverified) return model;
32424
+ const reasoningEfforts = normalizeReasoningEffortsForSelect([
32425
+ ...model.reasoningEfforts || [],
32426
+ saved.reasoningEffort
32427
+ ]);
32428
+ const speedTiers = normalizeSpeedTiersForSelect(model.speedTiers);
32429
+ const speed = ["standard", "fast"].includes(saved.speedTier) ? saved.speedTier : "";
32430
+ if (speed && !speedTiers.includes(speed)) speedTiers.push(speed);
32431
+ return {
32432
+ ...model,
32433
+ unverified: true,
32434
+ reasoningEfforts,
32435
+ speedTiers,
32436
+ defaultReasoningEffort: reasoningEfforts.includes(saved.reasoningEffort) ? saved.reasoningEffort : model.defaultReasoningEffort,
32437
+ defaultSpeedTier: speed || model.defaultSpeedTier || "standard"
32438
+ };
32439
+ });
32440
+ }
32441
+ function getRenderedModelEntries(panel) {
32442
+ return Array.from(panel?.querySelector("[data-model]")?.options || []).map((option) => ({
32443
+ id: option.value,
32444
+ label: option.textContent,
32445
+ reasoningEfforts: (option.dataset.reasoningEfforts || "").split(",").filter(Boolean),
32446
+ defaultReasoningEffort: option.dataset.defaultReasoningEffort || "",
32447
+ reasoningPresentation: option.dataset.reasoningPresentation || "",
32448
+ speedTiers: (option.dataset.speedTiers || "standard").split(",").filter(Boolean),
32449
+ defaultSpeedTier: option.dataset.defaultSpeedTier || "standard"
32450
+ }));
32451
+ }
32035
32452
  function shouldUseBuiltInFallback(response) {
32036
32453
  const providerId = normalizeModelOptionId(response?.result?.providerId);
32037
32454
  return response?.ok === true && (!providerId || providerId === "builtin");
@@ -32100,6 +32517,8 @@ ${conclusion}` : `${label} ${conclusion}`;
32100
32517
  normalizeReasoningEffortsForSelect,
32101
32518
  normalizeSpeedTiersForSelect,
32102
32519
  retainSelectedModel,
32520
+ preserveSelectedCapabilities,
32521
+ getRenderedModelEntries,
32103
32522
  shouldUseBuiltInFallback
32104
32523
  };
32105
32524
  return api;
@@ -32222,7 +32641,8 @@ ${conclusion}` : `${label} ${conclusion}`;
32222
32641
  return { stale: false, error: response?.error || { code: "provider_models_unavailable" } };
32223
32642
  }
32224
32643
  const sourceModels = hasDiscoveredModels ? response.result.models : fallbackModels;
32225
- const retainedSelectedModel = retainSelectedModel(sourceModels, currentSelectedModel);
32644
+ const preserveBuiltin = (selection?.providerId || "builtin") === "builtin" && (getState()?.providerId || "builtin") === "builtin";
32645
+ const retainedSelectedModel = retainSelectedModel(sourceModels, currentSelectedModel, { preserveUnavailable: preserveBuiltin });
32226
32646
  const normalized = modelCatalog.normalizeDiscoveredModels({ models: sourceModels, selectedModel: retainedSelectedModel });
32227
32647
  if (normalized.usedFallback && response.result?.providerId && response.result.providerId !== "builtin") {
32228
32648
  applyUnavailableModelOptions({ code: "provider_model_catalog_invalid", message: "The active provider returned no usable models." });
@@ -32231,9 +32651,10 @@ ${conclusion}` : `${label} ${conclusion}`;
32231
32651
  error: { code: "provider_model_catalog_invalid", message: "The active provider returned no usable models." }
32232
32652
  };
32233
32653
  }
32234
- renderModelOptions(normalized.models, retainedSelectedModel, { allowUnknownSelected: false });
32654
+ const renderedModels = preserveBuiltin ? Support.preserveSelectedCapabilities(normalized.models, retainedSelectedModel, getState(), !hasDiscoveredModels || response.result?.source === "fallback") : normalized.models;
32655
+ renderModelOptions(renderedModels, retainedSelectedModel, { allowUnknownSelected: false });
32235
32656
  modelDiscovery = {
32236
- status: hasDiscoveredModels && !normalized.usedFallback ? "discovered" : "fallback",
32657
+ status: hasDiscoveredModels && !normalized.usedFallback && response.result?.source !== "fallback" ? "discovered" : "fallback",
32237
32658
  source: hasDiscoveredModels ? response.result?.source || "unknown" : "fallback",
32238
32659
  fetchedAt: hasDiscoveredModels ? response.result?.fetchedAt || "" : "",
32239
32660
  errorCode: hasDiscoveredModels ? "" : response?.error?.code || "",
@@ -32259,15 +32680,16 @@ ${conclusion}` : `${label} ${conclusion}`;
32259
32680
  }
32260
32681
  }
32261
32682
  function applyUnavailableModelOptions(error) {
32683
+ const selectedModel = resolveSelectedModel();
32262
32684
  const modelSelect = getPanel()?.querySelector("[data-model]");
32263
32685
  if (modelSelect) {
32264
32686
  modelSelect.textContent = "";
32265
32687
  const option = document.createElement("option");
32266
- option.value = resolveSelectedModel();
32688
+ option.value = selectedModel;
32267
32689
  option.textContent = tx("Models unavailable", "\u6A21\u578B\u4E0D\u53EF\u7528");
32268
32690
  option.disabled = true;
32269
32691
  modelSelect.append(option);
32270
- modelSelect.value = "";
32692
+ modelSelect.value = selectedModel;
32271
32693
  modelSelect.disabled = true;
32272
32694
  }
32273
32695
  modelDiscovery = {
@@ -32277,8 +32699,18 @@ ${conclusion}` : `${label} ${conclusion}`;
32277
32699
  errorCode: error?.code || "",
32278
32700
  errorMessage: error?.message || (error ? String(error) : "")
32279
32701
  };
32280
- renderReasoningOptions([]);
32281
- renderSpeedOptions([]);
32702
+ const pendingModels = Support.preserveSelectedCapabilities(
32703
+ [{ id: selectedModel, unverified: true }],
32704
+ selectedModel,
32705
+ getState(),
32706
+ true
32707
+ );
32708
+ renderReasoningOptions(pendingModels);
32709
+ renderSpeedOptions(pendingModels);
32710
+ for (const selector of ["[data-reasoning]", "[data-speed]"]) {
32711
+ const input = getPanel()?.querySelector(selector);
32712
+ if (input) input.disabled = true;
32713
+ }
32282
32714
  renderModelConfigChoices();
32283
32715
  updateModelDisplay();
32284
32716
  }
@@ -32286,9 +32718,9 @@ ${conclusion}` : `${label} ${conclusion}`;
32286
32718
  const modelCatalog = getModelCatalog();
32287
32719
  const fallbackModels = modelCatalog.FALLBACK_MODELS;
32288
32720
  const sourceModels = fallbackModels;
32289
- const retainedSelectedModel = retainSelectedModel(sourceModels, selectedModel);
32721
+ const retainedSelectedModel = retainSelectedModel(sourceModels, selectedModel, { preserveUnavailable: (getState()?.providerId || "builtin") === "builtin" });
32290
32722
  const normalized = modelCatalog.normalizeDiscoveredModels({ models: sourceModels, selectedModel: retainedSelectedModel });
32291
- renderModelOptions(normalized.models, retainedSelectedModel);
32723
+ renderModelOptions(Support.preserveSelectedCapabilities(normalized.models, retainedSelectedModel, getState(), true), retainedSelectedModel);
32292
32724
  modelDiscovery = {
32293
32725
  status: "fallback",
32294
32726
  source: "fallback",
@@ -32723,6 +33155,7 @@ ${conclusion}` : `${label} ${conclusion}`;
32723
33155
  });
32724
33156
  const PROJECT_NAME_CACHE_STORAGE_KEY = "projectNameCacheByAccount";
32725
33157
  let projectNameCacheMirror = {};
33158
+ let dashboardRenderGeneration = 0;
32726
33159
  function loadProjectNameCacheFromStorage() {
32727
33160
  return new Promise(function(resolve) {
32728
33161
  try {
@@ -32806,7 +33239,7 @@ ${conclusion}` : `${label} ${conclusion}`;
32806
33239
  await persistProjectNameCacheToStorage();
32807
33240
  var visibleList = getPanel() && getPanel().querySelector("[data-recent-projects-list]");
32808
33241
  if (visibleList) {
32809
- var rows = visibleList.querySelectorAll("[data-recent-projects-row]");
33242
+ var rows = visibleList.querySelectorAll("[data-project-id]");
32810
33243
  for (var k = 0; k < rows.length; k++) {
32811
33244
  var row = rows[k];
32812
33245
  var rowPid = row.getAttribute("data-project-id");
@@ -32815,6 +33248,7 @@ ${conclusion}` : `${label} ${conclusion}`;
32815
33248
  var cached = lookupProjectName(rowPid);
32816
33249
  if (cached) {
32817
33250
  nameEl.textContent = cached;
33251
+ nameEl.title = cached;
32818
33252
  }
32819
33253
  }
32820
33254
  }
@@ -32883,54 +33317,47 @@ ${conclusion}` : `${label} ${conclusion}`;
32883
33317
  var hr = Math.round(min / 60);
32884
33318
  if (hr < 24) return tx(hr + " hr ago", hr + " \u5C0F\u65F6\u524D");
32885
33319
  var day = Math.round(hr / 24);
32886
- if (day < 30) return tx(day + " day ago", day + " \u5929\u524D");
33320
+ if (day < 30) return tx(day + (day === 1 ? " day ago" : " days ago"), day + " \u5929\u524D");
32887
33321
  var month = Math.round(day / 30);
32888
33322
  if (month < 12) return tx(month + " mo ago", month + " \u4E2A\u6708\u524D");
32889
33323
  var year = Math.round(month / 12);
32890
33324
  return tx(year + " yr ago", year + " \u5E74\u524D");
32891
33325
  }
32892
- function textNode(text, className) {
32893
- var el = document.createElement("span");
32894
- if (className) {
32895
- el.className = className;
32896
- }
32897
- el.textContent = text == null ? "" : String(text);
33326
+ function createViewElement(tag, className, text) {
33327
+ var el = document.createElement(tag);
33328
+ if (className) el.className = className;
33329
+ if (text !== void 0) el.textContent = text;
32898
33330
  return el;
32899
33331
  }
32900
- function renderWelcomeHeader(stats) {
32901
- var el = document.createElement("div");
32902
- el.className = "recent-projects-welcome";
33332
+ function createViewButton(label, onClick, attribute, className) {
33333
+ var button = createViewElement("button", className, label);
33334
+ button.type = "button";
33335
+ if (attribute) button.setAttribute(attribute, "");
33336
+ if (onClick) button.addEventListener("click", onClick);
33337
+ return button;
33338
+ }
33339
+ function textNode(text, className) {
33340
+ return createViewElement("span", className, text == null ? "" : String(text));
33341
+ }
33342
+ function renderWelcomeHeader() {
33343
+ var el = createViewElement("div", "recent-projects-welcome");
32903
33344
  el.setAttribute("data-recent-projects-welcome", "");
32904
- var title = document.createElement("div");
32905
- title.className = "recent-projects-welcome-title";
32906
- title.textContent = tr("recentProjects_welcome");
32907
- var subtitle = document.createElement("div");
32908
- subtitle.className = "recent-projects-welcome-subtitle";
32909
- subtitle.textContent = stats && stats.projectCount ? tr("recentProjects_welcome_stats", { projects: stats.projectCount, sessions: stats.sessionTotal }) : tr("recentProjects_welcome_subtitle");
32910
- el.appendChild(title);
32911
- el.appendChild(subtitle);
33345
+ el.appendChild(textNode("CODEX", "recent-projects-eyebrow"));
33346
+ el.appendChild(textNode(tr("recentProjects_welcome"), "recent-projects-welcome-title"));
33347
+ el.appendChild(textNode(tr("recentProjects_welcome_subtitle"), "recent-projects-welcome-subtitle"));
32912
33348
  return el;
32913
33349
  }
32914
33350
  function renderEmptyState() {
32915
- var el = document.createElement("div");
32916
- el.className = "recent-projects-empty";
33351
+ var el = createViewElement("div", "recent-projects-empty", tr("recentProjects_empty"));
32917
33352
  el.setAttribute("data-recent-projects-empty", "");
32918
- el.textContent = tr("recentProjects_empty");
32919
33353
  return el;
32920
33354
  }
32921
33355
  function renderDegradedState() {
32922
- var el = document.createElement("div");
32923
- el.className = "recent-projects-degraded";
33356
+ var el = createViewElement("div", "recent-projects-degraded");
32924
33357
  el.setAttribute("data-recent-projects-degraded", "");
32925
- var hint = document.createElement("div");
32926
- hint.textContent = tr("recentProjects_degraded_hint");
33358
+ var hint = createViewElement("div", "", tr("recentProjects_degraded_hint"));
32927
33359
  el.appendChild(hint);
32928
- var retry = document.createElement("button");
32929
- retry.type = "button";
32930
- retry.className = "recent-projects-show-all";
32931
- retry.setAttribute("data-recent-projects-retry", "");
32932
- retry.textContent = tr("recentProjects_retryScope");
32933
- retry.addEventListener("click", function() {
33360
+ var retry = createViewButton(tr("recentProjects_retryScope"), function() {
32934
33361
  if (typeof refreshAccountScopeId === "function") {
32935
33362
  Promise.resolve(refreshAccountScopeId()).then(function() {
32936
33363
  return renderRecentProjectsVariant();
@@ -32939,24 +33366,17 @@ ${conclusion}` : `${label} ${conclusion}`;
32939
33366
  } else {
32940
33367
  renderRecentProjectsVariant();
32941
33368
  }
32942
- });
33369
+ }, "data-recent-projects-retry", "recent-projects-show-all");
32943
33370
  el.appendChild(retry);
32944
33371
  return el;
32945
33372
  }
32946
33373
  function renderSettingsEntry(options2) {
32947
33374
  var scope = options2 && options2.scope === "account" ? "account" : "project";
32948
- var entry = document.createElement("button");
32949
- entry.type = "button";
32950
- entry.className = "recent-projects-settings-entry";
32951
- entry.setAttribute("data-recent-projects-settings-entry", "");
32952
- entry.setAttribute("data-settings-scope", scope);
32953
- var label = document.createElement("span");
32954
- label.className = "recent-projects-settings-entry-label";
32955
- label.textContent = tr("recentProjects_settings_entry");
32956
- entry.appendChild(label);
32957
- entry.addEventListener("click", function() {
33375
+ var entry = createViewButton(void 0, function() {
32958
33376
  openSettingsInScope(scope);
32959
- });
33377
+ }, "data-recent-projects-settings-entry", "recent-projects-settings-entry");
33378
+ entry.setAttribute("data-settings-scope", scope);
33379
+ entry.appendChild(textNode(tr("recentProjects_settings_entry"), "recent-projects-settings-entry-label"));
32960
33380
  return entry;
32961
33381
  }
32962
33382
  function openSettingsInScope(scope) {
@@ -32979,10 +33399,8 @@ ${conclusion}` : `${label} ${conclusion}`;
32979
33399
  function renderStatusBadge(status) {
32980
33400
  var safeStatus = typeof status === "string" && STATUS_BADGE_CLASS[status] ? status : "pending";
32981
33401
  var cls = STATUS_BADGE_CLASS[safeStatus];
32982
- var el = document.createElement("span");
32983
- el.className = "recent-projects-row-badge " + cls;
33402
+ var el = textNode(tr("recentProjects_badge_" + safeStatus), "recent-projects-row-badge " + cls);
32984
33403
  el.setAttribute("data-status", safeStatus);
32985
- el.textContent = tr("recentProjects_badge_" + safeStatus);
32986
33404
  return el;
32987
33405
  }
32988
33406
  function isValidProjectId(id) {
@@ -32994,121 +33412,137 @@ ${conclusion}` : `${label} ${conclusion}`;
32994
33412
  }
32995
33413
  window.location.assign("https://www.overleaf.com/project/" + encodeURIComponent(projectId));
32996
33414
  }
33415
+ function renderActionsMenu(buttons) {
33416
+ var menu = createViewElement("details", "recent-projects-menu");
33417
+ var trigger = createViewElement("summary", "", "\u22EF");
33418
+ trigger.title = tr("recentProjects_more");
33419
+ trigger.setAttribute("aria-label", tr("recentProjects_more"));
33420
+ var actions = document.createElement("div");
33421
+ actions.setAttribute("data-recent-menu-actions", "");
33422
+ buttons.forEach(function(button) {
33423
+ actions.appendChild(button);
33424
+ });
33425
+ actions.addEventListener("click", function(event) {
33426
+ if (event.target.closest("button")) {
33427
+ menu.open = false;
33428
+ trigger.focus();
33429
+ }
33430
+ }, true);
33431
+ menu.appendChild(trigger);
33432
+ menu.appendChild(actions);
33433
+ menu.addEventListener("keydown", function(event) {
33434
+ if (event.key === "Escape") {
33435
+ event.preventDefault();
33436
+ event.stopPropagation();
33437
+ menu.open = false;
33438
+ trigger.focus();
33439
+ }
33440
+ });
33441
+ menu.addEventListener("focusout", function(event) {
33442
+ if (event.relatedTarget && !menu.contains(event.relatedTarget)) menu.open = false;
33443
+ });
33444
+ return menu;
33445
+ }
33446
+ function conversationActivity(record) {
33447
+ return String(record && (record.lastActivityAt || record.updatedAt || record.createdAt) || "");
33448
+ }
33449
+ function conversationHasContent(record) {
33450
+ return ["runs", "history", "pendingInputs"].some(function(key) {
33451
+ return Array.isArray(record[key]) && record[key].length > 0;
33452
+ }) || ["task", "safeTaskSummary", "codexThreadId"].some(function(key) {
33453
+ return typeof record[key] === "string" && Boolean(record[key].trim());
33454
+ }) || record.titleSource === "manual" && typeof record.title === "string" && Boolean(record.title.trim());
33455
+ }
33456
+ function conversationStatus(record) {
33457
+ if (!StorageDb || !Array.isArray(record.runs) || !record.runs.length) return "";
33458
+ return settleDashboardRunStatus(StorageDb.derivePrimaryStatusBadge(record), conversationActivity(record));
33459
+ }
33460
+ function projectDisplayName(projectId) {
33461
+ return lookupProjectName(projectId) || (isValidProjectId(projectId) ? tr("recentProjects_unnamed", { prefix: projectId.slice(0, 8) }) : tr("recentProjects_row_projectLinkUnavailable"));
33462
+ }
33463
+ async function loadDashboardConversations(scope) {
33464
+ if (!scope || !StorageDb) throw new Error("Conversation storage is unavailable.");
33465
+ var results = await Promise.all([StorageDb.getAllSessions(), SessionPersistence.loadTombstones()]);
33466
+ var records = results[0];
33467
+ var deletedByProject = results[1] || {};
33468
+ if (!Array.isArray(records)) throw new Error("Conversation history could not be read.");
33469
+ return records.filter(function(record) {
33470
+ if (!record || typeof record.id !== "string" || !record.id || record.accountScopeUnavailable === true) return false;
33471
+ var deletedIds = Object.prototype.hasOwnProperty.call(deletedByProject, record.projectId) ? deletedByProject[record.projectId] : [];
33472
+ return SessionPersistence.isVisibleRecord(record, Array.isArray(deletedIds) ? deletedIds : [], scope);
33473
+ }).sort(function(a, b) {
33474
+ var aTime = Date.parse(conversationActivity(a)) || 0;
33475
+ var bTime = Date.parse(conversationActivity(b)) || 0;
33476
+ return bTime - aTime || String(a.id).localeCompare(String(b.id));
33477
+ });
33478
+ }
33479
+ function dashboardViewOptions() {
33480
+ var root = getPanel() && getPanel().querySelector("[data-recent-projects-root]");
33481
+ var drafts = root && root.querySelector("[data-unstarted-conversations]");
33482
+ return {
33483
+ accountScopeId: root && root.dataset.accountScopeId || "",
33484
+ projectFilter: root && root.dataset.projectFilter || "",
33485
+ showAll: Boolean(root && root.dataset.showAll === "true"),
33486
+ draftsOpen: Boolean(drafts && drafts.open),
33487
+ restoreScrollTop: root ? root.scrollTop : 0
33488
+ };
33489
+ }
33490
+ async function refreshDashboardView() {
33491
+ var panelEl = getPanel();
33492
+ if (panelEl && panelEl.dataset.view === "recent-projects") {
33493
+ await renderRecentProjectsVariant(dashboardViewOptions());
33494
+ }
33495
+ }
33496
+ async function openDashboardConversation(projectId, record, row) {
33497
+ var scope = record.accountScopeId;
33498
+ var panelEl = getPanel();
33499
+ if (!scope || scope !== getCachedAccountScopeId() || !isValidProjectId(projectId) || !panelEl || panelEl.dataset.view !== "recent-projects" || row.getAttribute("aria-busy") === "true") return;
33500
+ row.setAttribute("aria-busy", "true");
33501
+ var resume = row.querySelector("[data-session-resume]");
33502
+ if (resume) resume.disabled = true;
33503
+ try {
33504
+ var records = await loadProjectSessionRecords(projectId);
33505
+ if (scope !== getCachedAccountScopeId() || panelEl !== getPanel() || panelEl.dataset.view !== "recent-projects" || row.isConnected === false) return;
33506
+ var selected = records.find(function(candidate) {
33507
+ return candidate.id === record.id;
33508
+ });
33509
+ if (!selected) {
33510
+ await refreshDashboardView();
33511
+ showPluginToast(tr("recentProjects_conversationGone"), { status: "warning" });
33512
+ return;
33513
+ }
33514
+ await activateSessionAndOpenProject(projectId, selected);
33515
+ } catch (_error) {
33516
+ if (scope === getCachedAccountScopeId() && row.isConnected !== false) {
33517
+ showPluginToast(tr("recentProjects_continueFailed"), { status: "warning" });
33518
+ }
33519
+ } finally {
33520
+ row.removeAttribute("aria-busy");
33521
+ if (resume) resume.disabled = false;
33522
+ }
33523
+ }
32997
33524
  function renderRecentProjectRow(row) {
32998
33525
  var projectId = row && row.projectId;
32999
33526
  var valid = isValidProjectId(projectId);
33000
- var el = document.createElement("button");
33001
- el.type = "button";
33002
- el.className = "recent-projects-row";
33003
- el.setAttribute("data-recent-projects-row", "");
33004
- el.setAttribute("data-project-id", projectId || "");
33005
- if (!valid) {
33006
- el.disabled = true;
33007
- el.setAttribute("aria-disabled", "true");
33008
- }
33009
- var name = lookupProjectName(projectId);
33010
- if (!name) {
33011
- name = isValidProjectId(projectId) ? tr("recentProjects_unnamed", { prefix: projectId.slice(0, 8) }) : tr("recentProjects_row_projectLinkUnavailable");
33012
- }
33013
- var nameNode = textNode(name, "recent-projects-row-name");
33014
- nameNode.title = name;
33015
- el.appendChild(nameNode);
33016
- var rowTime = textNode(formatRelativeTime(row && row.lastActivityAt), "recent-projects-row-time");
33017
- rowTime.setAttribute("data-rel-time", String(row && row.lastActivityAt || ""));
33018
- el.appendChild(rowTime);
33019
- var sessionCount = Number(row && row.sessionCount) || 0;
33020
- if (sessionCount > 1) {
33021
- el.appendChild(textNode(tr("recentProjects_sessionsCount", { count: sessionCount }), "recent-projects-row-sessions"));
33022
- }
33023
- el.appendChild(renderStatusBadge(settleDashboardRunStatus(row && row.primaryStatusBadge, row && row.lastActivityAt)));
33024
- if (valid) {
33025
- el.addEventListener("click", function() {
33026
- openProjectFromRow(projectId);
33527
+ var wrap = createViewElement("div", "recent-projects-row-wrap recent-projects-unavailable-row");
33528
+ wrap.setAttribute("data-project-id", projectId || "");
33529
+ var link = createViewButton(void 0, null, "data-recent-projects-row", "recent-projects-row");
33530
+ link.setAttribute("data-project-id", projectId || "");
33531
+ link.disabled = !valid;
33532
+ link.appendChild(textNode(projectDisplayName(projectId), "recent-projects-row-name"));
33533
+ if (valid) link.addEventListener("click", function() {
33534
+ openProjectFromRow(projectId);
33535
+ });
33536
+ var cleanup = createViewButton(tr(valid ? "recentProjects_clearProject" : "recentProjects_cleanup"), function() {
33537
+ if (valid) projectSessionCleanup.clearProjectSessions(projectId, projectDisplayName(projectId)).catch(function() {
33027
33538
  });
33028
- } else {
33029
- el.appendChild(textNode(tr("recentProjects_row_projectLinkUnavailable"), "recent-projects-row-warning"));
33030
- }
33031
- if (!valid) {
33032
- var deadWrap = document.createElement("div");
33033
- deadWrap.className = "recent-projects-row-wrap";
33034
- var deadHead = document.createElement("div");
33035
- deadHead.className = "recent-projects-row-head";
33036
- var cleanup = document.createElement("button");
33037
- cleanup.type = "button";
33038
- cleanup.className = "recent-projects-row-cleanup";
33039
- cleanup.setAttribute("data-row-cleanup", "");
33040
- cleanup.title = tr("recentProjects_cleanup");
33041
- cleanup.setAttribute("aria-label", tr("recentProjects_cleanup"));
33042
- cleanup.textContent = "\xD7";
33043
- cleanup.addEventListener("click", function() {
33044
- cleanupDeadProjectEntry(projectId).catch(function() {
33045
- });
33539
+ else cleanupDeadProjectEntry(projectId).catch(function() {
33046
33540
  });
33047
- deadHead.appendChild(el);
33048
- deadHead.appendChild(cleanup);
33049
- deadWrap.appendChild(deadHead);
33050
- return deadWrap;
33051
- }
33052
- var wrap = document.createElement("div");
33053
- wrap.className = "recent-projects-row-wrap";
33054
- wrap.setAttribute("data-project-row-wrap", projectId);
33055
- var expand = document.createElement("button");
33056
- expand.type = "button";
33057
- expand.className = "recent-projects-row-expand";
33058
- expand.setAttribute("data-row-expand", "");
33059
- expand.setAttribute("aria-expanded", "false");
33060
- var sessionsElId = "codex-project-sessions-" + String(projectId || "").slice(0, 24);
33061
- expand.setAttribute("aria-controls", sessionsElId);
33062
- expand.title = tr("recentProjects_sessions_toggle");
33063
- expand.setAttribute("aria-label", tr("recentProjects_sessions_toggle"));
33064
- expand.textContent = tr("recentProjects_sessions_action");
33065
- var head = document.createElement("div");
33066
- head.className = "recent-projects-row-head";
33067
- var clear = document.createElement("button");
33068
- clear.type = "button";
33069
- clear.className = "recent-projects-row-clear";
33070
- clear.setAttribute("data-project-clear", "");
33071
- clear.title = tr("recentProjects_clearProject");
33072
- clear.setAttribute("aria-label", tr("recentProjects_clearProject"));
33073
- clear.textContent = "\xD7";
33074
- clear.addEventListener("click", function(event) {
33075
- event.stopPropagation();
33076
- projectSessionCleanup.clearProjectSessions(projectId, name).catch(function() {
33077
- });
33078
- });
33079
- head.appendChild(el);
33080
- head.appendChild(expand);
33081
- head.appendChild(clear);
33082
- var sessionsEl = document.createElement("div");
33083
- sessionsEl.className = "recent-projects-sessions";
33084
- sessionsEl.setAttribute("data-project-sessions", "");
33085
- sessionsEl.id = sessionsElId;
33086
- sessionsEl.setAttribute("role", "region");
33087
- sessionsEl.setAttribute("aria-label", tr("recentProjects_sessions_toggle"));
33088
- sessionsEl.hidden = true;
33089
- expand.addEventListener("click", function() {
33090
- toggleProjectSessions(wrap, projectId);
33091
- });
33092
- wrap.appendChild(head);
33093
- wrap.appendChild(sessionsEl);
33541
+ }, valid ? "data-project-clear" : "data-row-cleanup");
33542
+ wrap.appendChild(link);
33543
+ wrap.appendChild(renderActionsMenu([cleanup]));
33094
33544
  return wrap;
33095
33545
  }
33096
- function toggleProjectSessions(wrap, projectId) {
33097
- var sessionsEl = wrap.querySelector("[data-project-sessions]");
33098
- var expand = wrap.querySelector("[data-row-expand]");
33099
- if (!sessionsEl || !expand) {
33100
- return;
33101
- }
33102
- var opening = sessionsEl.hidden;
33103
- sessionsEl.hidden = !opening;
33104
- expand.setAttribute("aria-expanded", opening ? "true" : "false");
33105
- expand.textContent = tr(opening ? "recentProjects_sessions_action_open" : "recentProjects_sessions_action");
33106
- wrap.setAttribute("data-expanded", opening ? "true" : "false");
33107
- if (opening) {
33108
- renderProjectSessions(sessionsEl, projectId).catch(function() {
33109
- });
33110
- }
33111
- }
33112
33546
  function sessionRecordDisplayTitle(record) {
33113
33547
  if (record && typeof record.title === "string" && record.title.trim()) {
33114
33548
  return record.title.trim();
@@ -33130,87 +33564,90 @@ ${conclusion}` : `${label} ${conclusion}`;
33130
33564
  return String(b.updatedAt || b.createdAt || "").localeCompare(String(a.updatedAt || a.createdAt || ""));
33131
33565
  });
33132
33566
  }
33133
- async function renderProjectSessions(sessionsEl, projectId) {
33134
- sessionsEl.innerHTML = "";
33135
- var sessionsLoading = document.createElement("div");
33136
- sessionsLoading.className = "recent-projects-loading";
33137
- sessionsLoading.textContent = tx("Loading sessions\u2026", "\u6B63\u5728\u52A0\u8F7D\u4F1A\u8BDD\u2026");
33138
- sessionsEl.appendChild(sessionsLoading);
33139
- var records = [];
33140
- try {
33141
- records = await loadProjectSessionRecords(projectId);
33142
- } catch (_error) {
33143
- records = [];
33144
- }
33145
- sessionsLoading.remove();
33146
- if (!records.length) {
33147
- sessionsEl.appendChild(textNode(tr("recentProjects_sessions_empty"), "recent-projects-sessions-empty"));
33148
- return;
33149
- }
33150
- for (var i = 0; i < records.length; i++) {
33151
- sessionsEl.appendChild(renderProjectSessionRow(sessionsEl, projectId, records[i]));
33152
- }
33153
- }
33154
- function renderProjectSessionRow(sessionsEl, projectId, record) {
33567
+ function renderProjectSessionRow(sessionsEl, projectId, record, options2) {
33568
+ var featured = Boolean(options2 && options2.featured);
33155
33569
  var running = Boolean(StorageDb) && settleDashboardRunStatus(StorageDb.derivePrimaryStatusBadge(record), record.updatedAt || record.lastActivityAt) === "running";
33156
- var row = document.createElement("div");
33157
- row.className = "recent-projects-session-row";
33570
+ var row = createViewElement("div", "recent-projects-session-row" + (featured ? " recent-projects-focus" : ""));
33158
33571
  row.setAttribute("data-project-session-row", record.id);
33159
- if (running) {
33160
- row.setAttribute("data-running", "true");
33161
- }
33572
+ row.setAttribute("data-project-id", projectId);
33573
+ if (running) row.setAttribute("data-running", "true");
33162
33574
  var title = textNode(sessionRecordDisplayTitle(record), "recent-projects-session-title");
33163
- title.title = sessionRecordDisplayTitle(record);
33164
- var time = textNode(formatRelativeTime(record.updatedAt || record.createdAt), "recent-projects-session-time");
33165
- time.setAttribute("data-rel-time", String(record.updatedAt || record.createdAt || ""));
33166
- var rename = document.createElement("button");
33167
- rename.type = "button";
33168
- rename.className = "recent-projects-session-action";
33169
- rename.setAttribute("data-session-rename-dash", "");
33170
- rename.title = tr("renameSession");
33171
- rename.setAttribute("aria-label", tr("renameSession"));
33172
- rename.textContent = "\u270E";
33173
- var del = document.createElement("button");
33174
- del.type = "button";
33175
- del.className = "recent-projects-session-action recent-projects-session-action--delete";
33176
- del.setAttribute("data-session-delete-dash", "");
33177
- del.title = tr("deleteSession");
33178
- del.setAttribute("aria-label", tr("deleteSession"));
33179
- del.textContent = "\xD7";
33180
- if (running) {
33181
- rename.disabled = true;
33182
- del.title = tr("recentProjects_zombieRunningNote");
33575
+ title.title = title.textContent;
33576
+ var copy = createViewElement("div", "recent-projects-session-copy");
33577
+ var meta = createViewElement("div", "recent-projects-session-meta");
33578
+ var project = textNode(projectDisplayName(projectId), "recent-projects-row-name");
33579
+ project.title = project.textContent;
33580
+ meta.appendChild(project);
33581
+ var time = textNode(formatRelativeTime(conversationActivity(record)), "recent-projects-session-time");
33582
+ time.setAttribute("data-rel-time", conversationActivity(record));
33583
+ time.title = conversationActivity(record);
33584
+ var status = conversationStatus(record);
33585
+ var footer = featured ? createViewElement("div", "recent-projects-focus-footer") : null;
33586
+ if (featured) {
33587
+ meta.insertBefore(textNode(tr("recentProjects_focusEyebrow"), "recent-projects-focus-eyebrow"), project);
33588
+ copy.appendChild(meta);
33589
+ copy.appendChild(title);
33590
+ footer.appendChild(status ? renderStatusBadge(status) : textNode(tr("recentProjects_savedDraft"), "recent-projects-row-badge"));
33591
+ } else {
33592
+ copy.appendChild(title);
33593
+ copy.appendChild(meta);
33594
+ if (["failed", "needs_review", "interrupted", "stale", "needs_review_after_navigation", "abandoned_after_navigation"].indexOf(status) !== -1) {
33595
+ var attention = textNode(tr("recentProjects_attention"), "recent-projects-session-attention");
33596
+ attention.title = tr("recentProjects_badge_" + status);
33597
+ meta.appendChild(attention);
33598
+ }
33183
33599
  }
33184
- rename.addEventListener("click", function() {
33600
+ meta.appendChild(time);
33601
+ row.appendChild(copy);
33602
+ var rename = createViewButton(tr("renameSession"), function() {
33185
33603
  beginDashboardSessionRename(sessionsEl, projectId, record, row, title);
33186
- });
33187
- del.addEventListener("click", function() {
33604
+ }, "data-session-rename-dash");
33605
+ rename.title = rename.textContent;
33606
+ rename.setAttribute("aria-label", rename.textContent);
33607
+ if (running) rename.disabled = true;
33608
+ var del = createViewButton(tr("deleteSession"), function() {
33188
33609
  deleteDashboardSession(sessionsEl, projectId, record).catch(function() {
33189
33610
  });
33611
+ }, "data-session-delete-dash");
33612
+ del.title = running ? tr("recentProjects_zombieRunningNote") : del.textContent;
33613
+ del.setAttribute("aria-label", del.textContent);
33614
+ var projectConversations = createViewButton(tr("recentProjects_projectConversations"), function() {
33615
+ renderRecentProjectsVariant({ projectFilter: projectId, showAll: true, restoreScrollTop: 0 }).catch(function() {
33616
+ });
33617
+ }, "data-project-conversations");
33618
+ var openProject = createViewButton(tr("recentProjects_openProject"), function() {
33619
+ openProjectFromRow(projectId);
33190
33620
  });
33191
- row.appendChild(title);
33192
- row.appendChild(time);
33193
- row.appendChild(rename);
33194
- row.appendChild(del);
33195
- row.classList.add("recent-projects-session-row--linked");
33196
- row.setAttribute("role", "button");
33197
- row.tabIndex = 0;
33621
+ var clear = createViewButton(tr("recentProjects_clearProject"), function() {
33622
+ projectSessionCleanup.clearProjectSessions(projectId, projectDisplayName(projectId)).catch(function() {
33623
+ });
33624
+ }, "data-project-clear");
33625
+ row.appendChild(renderActionsMenu([rename, del, projectConversations, openProject, clear]));
33198
33626
  var activate = function() {
33199
- activateSessionAndOpenProject(projectId, record).catch(function() {
33627
+ openDashboardConversation(projectId, record, row).catch(function() {
33200
33628
  });
33201
33629
  };
33202
- row.addEventListener("click", function(event) {
33203
- if (event.target.closest("button") || event.target.closest("input")) {
33204
- return;
33205
- }
33206
- activate();
33207
- });
33208
- row.addEventListener("keydown", function(event) {
33209
- if (event.key === "Enter") {
33210
- event.preventDefault();
33211
- activate();
33212
- }
33213
- });
33630
+ if (featured) {
33631
+ row.setAttribute("role", "group");
33632
+ row.setAttribute("aria-label", tr("recentProjects_focusEyebrow"));
33633
+ var resume = createViewButton(tr("recentProjects_continue"), activate, "data-session-resume", "recent-projects-continue");
33634
+ resume.title = tr("recentProjects_continueHint");
33635
+ footer.appendChild(resume);
33636
+ row.appendChild(footer);
33637
+ } else {
33638
+ row.classList.add("recent-projects-session-row--linked");
33639
+ row.setAttribute("role", "button");
33640
+ row.tabIndex = 0;
33641
+ row.addEventListener("click", function(event) {
33642
+ if (!event.target.closest("button, input, details")) activate();
33643
+ });
33644
+ row.addEventListener("keydown", function(event) {
33645
+ if (event.target === row && (event.key === "Enter" || event.key === " ")) {
33646
+ event.preventDefault();
33647
+ activate();
33648
+ }
33649
+ });
33650
+ }
33214
33651
  return row;
33215
33652
  }
33216
33653
  async function activateSessionAndOpenProject(projectId, record) {
@@ -33341,11 +33778,7 @@ ${conclusion}` : `${label} ${conclusion}`;
33341
33778
  } catch (error) {
33342
33779
  showPluginToast(tr("deleteSessionHistoryFailedToast", { message: error.message }), { status: "warning", sticky: true });
33343
33780
  }
33344
- var rootForScroll = getPanel() && getPanel().querySelector("[data-recent-projects-root]");
33345
- await renderRecentProjectsVariant({
33346
- expandProjectId: projectId,
33347
- restoreScrollTop: rootForScroll ? rootForScroll.scrollTop : void 0
33348
- });
33781
+ await refreshDashboardView();
33349
33782
  }
33350
33783
  function beginDashboardSessionRename(sessionsEl, projectId, record, row, titleEl) {
33351
33784
  if (row.querySelector("input")) {
@@ -33358,7 +33791,7 @@ ${conclusion}` : `${label} ${conclusion}`;
33358
33791
  input.className = "recent-projects-session-rename-input";
33359
33792
  input.value = seed;
33360
33793
  titleEl.hidden = true;
33361
- row.insertBefore(input, titleEl);
33794
+ titleEl.parentElement.insertBefore(input, titleEl);
33362
33795
  input.focus();
33363
33796
  input.select();
33364
33797
  var settled = false;
@@ -33420,7 +33853,7 @@ ${conclusion}` : `${label} ${conclusion}`;
33420
33853
  }
33421
33854
  } catch (_error) {
33422
33855
  }
33423
- await renderProjectSessions(sessionsEl, projectId);
33856
+ await refreshDashboardView();
33424
33857
  }
33425
33858
  function ensureRecentProjectsRoot() {
33426
33859
  if (!getPanel()) {
@@ -33430,9 +33863,13 @@ ${conclusion}` : `${label} ${conclusion}`;
33430
33863
  if (existing) {
33431
33864
  return existing;
33432
33865
  }
33433
- var rootEl = document.createElement("section");
33434
- rootEl.className = "recent-projects-root";
33866
+ var rootEl = createViewElement("section", "recent-projects-root");
33435
33867
  rootEl.setAttribute("data-recent-projects-root", "");
33868
+ rootEl.addEventListener("click", function(event) {
33869
+ rootEl.querySelectorAll(".recent-projects-menu[open]").forEach(function(menu) {
33870
+ if (!menu.contains(event.target)) menu.open = false;
33871
+ });
33872
+ });
33436
33873
  getPanel().appendChild(rootEl);
33437
33874
  return rootEl;
33438
33875
  }
@@ -33469,116 +33906,152 @@ ${conclusion}` : `${label} ${conclusion}`;
33469
33906
  return;
33470
33907
  }
33471
33908
  rootEl.appendChild(renderWelcomeHeader());
33472
- var loading = document.createElement("div");
33473
- loading.className = "recent-projects-loading";
33474
- loading.textContent = tx("Loading projects\u2026", "\u6B63\u5728\u52A0\u8F7D\u9879\u76EE\u2026");
33909
+ var loading = createViewElement("div", "recent-projects-loading", tx("Loading projects\u2026", "\u6B63\u5728\u52A0\u8F7D\u9879\u76EE\u2026"));
33475
33910
  rootEl.appendChild(loading);
33476
33911
  }
33477
33912
  async function renderRecentProjectsVariant(options2) {
33478
- if (!getPanel()) {
33479
- return;
33480
- }
33481
- getPanel().dataset.view = "recent-projects";
33913
+ var panelEl = getPanel();
33914
+ var editorMatch = String(window.location && window.location.pathname || "").match(/^\/project\/([a-f0-9]{24})(?:\/|$)/);
33915
+ if (!panelEl || editorMatch && isValidProjectId(editorMatch[1])) return;
33916
+ var previous = dashboardViewOptions();
33917
+ options2 = Object.assign({}, previous, options2 || {});
33918
+ var generation = ++dashboardRenderGeneration;
33919
+ panelEl.dataset.view = "recent-projects";
33482
33920
  startRelativeTimeTicker();
33483
33921
  var rootEl = ensureRecentProjectsRoot();
33484
- if (!rootEl) {
33485
- return;
33486
- }
33487
- rootEl.innerHTML = "";
33488
- rootEl.appendChild(renderWelcomeHeader());
33922
+ if (!rootEl) return;
33489
33923
  var accountScopeId = (window.codexOverleafDeriveAccountScopeId || function() {
33490
33924
  return null;
33491
33925
  })();
33926
+ if (previous.accountScopeId !== accountScopeId) {
33927
+ options2.projectFilter = "";
33928
+ options2.showAll = options2.draftsOpen = false;
33929
+ }
33930
+ var projectFilter = isValidProjectId(options2.projectFilter) ? options2.projectFilter : "";
33931
+ rootEl.dataset.accountScopeId = accountScopeId || "";
33932
+ rootEl.dataset.projectFilter = projectFilter;
33933
+ rootEl.dataset.showAll = options2.showAll ? "true" : "false";
33934
+ rootEl.innerHTML = "";
33935
+ rootEl.appendChild(renderWelcomeHeader());
33492
33936
  if (!accountScopeId) {
33493
33937
  rootEl.appendChild(renderDegradedState());
33494
33938
  rootEl.appendChild(renderSettingsEntry({ scope: "account" }));
33495
- opportunisticEnrichmentFromDom().catch(function() {
33496
- });
33497
33939
  return;
33498
33940
  }
33499
- var listContainer = document.createElement("div");
33500
- listContainer.className = "recent-projects-list";
33941
+ if (projectFilter) {
33942
+ var filterBar = createViewElement("div", "recent-projects-filter");
33943
+ var back = createViewButton(tr("recentProjects_allProjects"), function() {
33944
+ renderRecentProjectsVariant({ projectFilter: "", showAll: false, restoreScrollTop: 0 }).catch(function() {
33945
+ });
33946
+ });
33947
+ filterBar.appendChild(back);
33948
+ var filterName = textNode(projectDisplayName(projectFilter));
33949
+ filterName.title = filterName.textContent;
33950
+ filterBar.appendChild(filterName);
33951
+ rootEl.appendChild(filterBar);
33952
+ }
33953
+ var listContainer = createViewElement("div", "recent-projects-list");
33501
33954
  listContainer.setAttribute("data-recent-projects-list", "");
33502
33955
  rootEl.appendChild(listContainer);
33503
- var listLoading = document.createElement("div");
33504
- listLoading.className = "recent-projects-loading";
33505
- listLoading.textContent = tx("Loading projects\u2026", "\u6B63\u5728\u52A0\u8F7D\u9879\u76EE\u2026");
33506
- listContainer.appendChild(listLoading);
33956
+ var loading = textNode(tr("recentProjects_loadingHistory"), "recent-projects-loading");
33957
+ listContainer.appendChild(loading);
33958
+ function stillCurrent() {
33959
+ return generation === dashboardRenderGeneration && getPanel() === panelEl && panelEl.dataset.view === "recent-projects" && rootEl.isConnected !== false && getCachedAccountScopeId() === accountScopeId;
33960
+ }
33961
+ var records;
33507
33962
  try {
33508
33963
  await loadProjectNameCacheFromStorage();
33964
+ records = await loadDashboardConversations(accountScopeId);
33509
33965
  } catch (_error) {
33966
+ if (!stillCurrent()) return;
33967
+ loading.textContent = tr("recentProjects_loadFailed");
33968
+ var retry = createViewButton(tr("recentProjects_retryScope"), function() {
33969
+ renderRecentProjectsVariant(options2).catch(function() {
33970
+ });
33971
+ }, null, "recent-projects-show-all");
33972
+ listContainer.appendChild(retry);
33973
+ rootEl.appendChild(renderSettingsEntry({ scope: "account" }));
33974
+ return;
33510
33975
  }
33511
- var rows = [];
33512
- var showAll = Boolean(options2 && options2.showAll);
33513
- var pageLimit = 10;
33514
- try {
33515
- if (StorageDb) {
33516
- var deletedSessionIdsByProject = await SessionPersistence.loadTombstones();
33517
- rows = await StorageDb.listRecentProjectsAcrossAccount({
33518
- accountScopeId,
33519
- limit: showAll ? 500 : pageLimit + 1,
33520
- deletedSessionIdsByProject
33976
+ if (!stillCurrent()) return;
33977
+ loading.remove();
33978
+ var validRecords = records.filter(function(record) {
33979
+ return isValidProjectId(record.projectId) && (!projectFilter || record.projectId === projectFilter);
33980
+ });
33981
+ var started = validRecords.filter(conversationHasContent);
33982
+ var unstarted = validRecords.filter(function(record) {
33983
+ return !conversationHasContent(record);
33984
+ });
33985
+ if (started.length) {
33986
+ listContainer.appendChild(renderProjectSessionRow(listContainer, started[0].projectId, started[0], { featured: true }));
33987
+ if (started.length > 1) {
33988
+ var heading = createViewElement("div", "recent-projects-history-heading");
33989
+ heading.appendChild(textNode(tr("recentProjects_historyHeading")));
33990
+ heading.appendChild(textNode(tr("recentProjects_historyHint")));
33991
+ listContainer.appendChild(heading);
33992
+ var historyList = createViewElement("div", "recent-projects-history-list");
33993
+ var history = options2.showAll ? started.slice(1) : started.slice(1, 10);
33994
+ history.forEach(function(record) {
33995
+ historyList.appendChild(renderProjectSessionRow(historyList, record.projectId, record));
33521
33996
  });
33997
+ listContainer.appendChild(historyList);
33998
+ if (!options2.showAll && started.length > 10) {
33999
+ var more = createViewButton(tr("recentProjects_showAllConversations", { count: started.length }), function() {
34000
+ renderRecentProjectsVariant({ showAll: true }).catch(function() {
34001
+ });
34002
+ }, "data-recent-projects-show-all", "recent-projects-show-all");
34003
+ listContainer.appendChild(more);
34004
+ }
33522
34005
  }
33523
- } catch (_error) {
33524
- rows = [];
33525
- }
33526
- listLoading.remove();
33527
- var restoreExpanded = Array.isArray(options2 && options2.restoreExpanded) ? options2.restoreExpanded : [];
33528
- var welcomeSubtitle = rootEl.querySelector(".recent-projects-welcome-subtitle");
33529
- if (welcomeSubtitle && rows && rows.length) {
33530
- var sessionTotal = 0;
33531
- for (var st = 0; st < rows.length; st++) {
33532
- sessionTotal += Number(rows[st].sessionCount) || 1;
33533
- }
33534
- welcomeSubtitle.textContent = tr("recentProjects_welcome_stats", {
33535
- projects: rows.length,
33536
- sessions: sessionTotal
33537
- });
33538
- }
33539
- var hasMore = !showAll && rows && rows.length > pageLimit;
33540
- var visibleRows = hasMore ? rows.slice(0, pageLimit) : rows;
33541
- if (!visibleRows || !visibleRows.length) {
33542
- listContainer.appendChild(renderEmptyState());
33543
34006
  } else {
33544
- for (var i = 0; i < visibleRows.length; i++) {
33545
- listContainer.appendChild(renderRecentProjectRow(visibleRows[i]));
33546
- }
33547
- }
33548
- if (hasMore) {
33549
- var showAllButton = document.createElement("button");
33550
- showAllButton.type = "button";
33551
- showAllButton.className = "recent-projects-show-all";
33552
- showAllButton.setAttribute("data-recent-projects-show-all", "");
33553
- showAllButton.textContent = tx("Show all projects", "\u67E5\u770B\u5168\u90E8\u9879\u76EE");
33554
- showAllButton.addEventListener("click", function() {
33555
- renderRecentProjectsVariant(Object.assign({}, options2, { showAll: true }));
34007
+ var empty = renderEmptyState();
34008
+ if (unstarted.length) empty.textContent = tr("recentProjects_noStarted");
34009
+ listContainer.appendChild(empty);
34010
+ }
34011
+ if (unstarted.length) {
34012
+ let populateDrafts = function() {
34013
+ if (draftsBuilt || !stillCurrent()) return;
34014
+ draftsBuilt = true;
34015
+ drafts.appendChild(textNode(tr("recentProjects_unstartedHint"), "recent-projects-drafts-hint"));
34016
+ unstarted.forEach(function(record) {
34017
+ drafts.appendChild(renderProjectSessionRow(drafts, record.projectId, record));
34018
+ });
34019
+ };
34020
+ var drafts = createViewElement("details", "recent-projects-drafts");
34021
+ drafts.setAttribute("data-unstarted-conversations", "");
34022
+ var summary = createViewElement("summary", "", tr("recentProjects_unstarted", { count: unstarted.length }));
34023
+ drafts.appendChild(summary);
34024
+ var draftsBuilt = false;
34025
+ drafts.addEventListener("toggle", function() {
34026
+ if (drafts.open) populateDrafts();
33556
34027
  });
33557
- listContainer.appendChild(showAllButton);
33558
- }
33559
- rootEl.appendChild(renderSettingsEntry({ scope: "account" }));
33560
- var expandProjectId = options2 && options2.expandProjectId;
33561
- var toReopen = restoreExpanded.slice();
33562
- if (expandProjectId && toReopen.indexOf(expandProjectId) === -1) {
33563
- toReopen.push(expandProjectId);
33564
- }
33565
- for (var reopenIndex = 0; reopenIndex < toReopen.length; reopenIndex++) {
33566
- var wrap = listContainer.querySelector('[data-project-row-wrap="' + toReopen[reopenIndex] + '"]');
33567
- if (wrap) {
33568
- toggleProjectSessions(wrap, toReopen[reopenIndex]);
34028
+ listContainer.appendChild(drafts);
34029
+ if (options2.draftsOpen) {
34030
+ drafts.open = true;
34031
+ populateDrafts();
33569
34032
  }
33570
34033
  }
33571
- if (options2 && Number.isFinite(options2.restoreScrollTop)) {
33572
- rootEl.scrollTop = options2.restoreScrollTop;
33573
- var scrollerParent = rootEl.parentElement;
33574
- if (scrollerParent && scrollerParent.scrollHeight > scrollerParent.clientHeight) {
33575
- scrollerParent.scrollTop = options2.restoreScrollTop;
33576
- }
34034
+ if (!projectFilter) {
34035
+ var invalid = records.filter(function(record) {
34036
+ return !isValidProjectId(record.projectId);
34037
+ });
34038
+ var invalidIds = /* @__PURE__ */ new Set();
34039
+ if (invalid.length) listContainer.appendChild(textNode(tr("recentProjects_unavailableRecords"), "recent-projects-history-heading"));
34040
+ invalid.forEach(function(record) {
34041
+ var id = String(record.projectId || "");
34042
+ if (!invalidIds.has(id)) {
34043
+ invalidIds.add(id);
34044
+ listContainer.appendChild(renderRecentProjectRow({ projectId: record.projectId }));
34045
+ }
34046
+ });
33577
34047
  }
34048
+ rootEl.appendChild(renderSettingsEntry({ scope: "account" }));
34049
+ if (Number.isFinite(options2.restoreScrollTop)) rootEl.scrollTop = options2.restoreScrollTop;
33578
34050
  opportunisticEnrichmentFromDom().catch(function() {
33579
34051
  });
33580
34052
  }
33581
34053
  function renderPerProjectVariant() {
34054
+ dashboardRenderGeneration++;
33582
34055
  if (!getPanel()) {
33583
34056
  return;
33584
34057
  }
@@ -33653,6 +34126,7 @@ ${conclusion}` : `${label} ${conclusion}`;
33653
34126
  let currentOtStatus = "off";
33654
34127
  let otTogglePending = false;
33655
34128
  let otSyncRequestId = 0;
34129
+ let otStartRetryCount = 0;
33656
34130
  let otWarmMirrorProjectId = "";
33657
34131
  let lastExperimentalOtProjectId = "";
33658
34132
  let otWarmMirrorState = {
@@ -33802,6 +34276,7 @@ ${conclusion}` : `${label} ${conclusion}`;
33802
34276
  const projectId = getCurrentProjectId();
33803
34277
  otWarmMirrorState.failureActive = false;
33804
34278
  lastExperimentalOtProjectId = projectId;
34279
+ otStartRetryCount = 0;
33805
34280
  setExperimentalOtEnabledForProject(projectId, checkbox.checked);
33806
34281
  updateExperimentalOtToggleControl(checkbox.checked);
33807
34282
  const savedBadge = getPanel()?.querySelector('details[data-set-group="experimental"] [data-set-saved]');
@@ -33877,8 +34352,10 @@ ${conclusion}` : `${label} ${conclusion}`;
33877
34352
  await pauseOtWarmMirror(pauseAfterStart.reason || "paused");
33878
34353
  return response;
33879
34354
  }
33880
- otWarmMirrorState.failureActive = false;
34355
+ if (status === "starting") recordOtFailure("missing_active_path", "start");
34356
+ else otWarmMirrorState.failureActive = false;
33881
34357
  updateOtStatusDisplay(status);
34358
+ otStartRetryCount = 0;
33882
34359
  scheduleOtEventPolling(projectId, { immediate: true });
33883
34360
  return response;
33884
34361
  }
@@ -33905,23 +34382,31 @@ ${conclusion}` : `${label} ${conclusion}`;
33905
34382
  clearOtEventPolling({ clearPatchQueue: true });
33906
34383
  recordOtFailure(readOtBridgeErrorCode(response, "ot_start_failed"), "start");
33907
34384
  otWarmMirrorState.suspended = true;
33908
- setExperimentalOtEnabledForProject(projectId, false);
33909
- const experimentalOtCheckbox = getPanel()?.querySelector("[data-experimental-ot]");
33910
- if (experimentalOtCheckbox) {
33911
- experimentalOtCheckbox.checked = false;
33912
- }
34385
+ updateExperimentalOtToggleControl(isExperimentalOtEnabledForProject(projectId));
33913
34386
  updateOtStatusDisplay("unavailable");
33914
- saveStateSoon();
33915
34387
  try {
33916
34388
  await callPageBridge("stopOtObserver", { projectId });
33917
34389
  } catch (_error) {
33918
34390
  }
34391
+ scheduleOtStartRetry(projectId, requestId);
34392
+ }
34393
+ function scheduleOtStartRetry(projectId, requestId) {
34394
+ const retryDelays = [1e3, 3e3, 6e3];
34395
+ const canRetry = () => isCurrentOtSync(requestId, projectId) && getCurrentProjectId() === projectId && isExperimentalOtEnabledForProject(projectId) && !getCurrentRunView();
34396
+ if (!canRetry() || otStartRetryCount >= retryDelays.length) return;
34397
+ const delayMs = retryDelays[otStartRetryCount++];
34398
+ otWarmMirrorState.pollTimer = window.setTimeout(() => {
34399
+ otWarmMirrorState.pollTimer = null;
34400
+ if (!canRetry()) return;
34401
+ syncOtWarmMirrorController().catch(() => {
34402
+ if (getCurrentProjectId() === projectId && isExperimentalOtEnabledForProject(projectId)) {
34403
+ updateOtStatusDisplay("unavailable");
34404
+ }
34405
+ });
34406
+ }, delayMs);
33919
34407
  }
33920
34408
  function readOtBridgeStatus(response) {
33921
- if (!response || response.ok === false) {
33922
- return "unavailable";
33923
- }
33924
- return response.state || response.status || response.result?.state || response.result?.status || "unavailable";
34409
+ return otWarmMirrorController.readOtBridgeStatus(response);
33925
34410
  }
33926
34411
  function ensureOtWarmMirrorStateProject(projectId) {
33927
34412
  if (!projectId || otWarmMirrorState.projectId === projectId) {
@@ -33929,6 +34414,7 @@ ${conclusion}` : `${label} ${conclusion}`;
33929
34414
  }
33930
34415
  clearOtEventPolling({ clearPatchQueue: true });
33931
34416
  otWarmMirrorState.projectId = projectId;
34417
+ otStartRetryCount = 0;
33932
34418
  otWarmMirrorState.lastPatchAt = 0;
33933
34419
  otWarmMirrorState.lastErrorCode = "";
33934
34420
  otWarmMirrorState.failureActive = false;
@@ -33992,6 +34478,7 @@ ${conclusion}` : `${label} ${conclusion}`;
33992
34478
  return statusResponse;
33993
34479
  }
33994
34480
  const status = readOtBridgeStatus(statusResponse);
34481
+ if (status === "observing" && otWarmMirrorState.lastFailure?.phase === "start") Object.assign(otWarmMirrorState, { failureActive: false, lastErrorCode: "", lastFailure: null });
33995
34482
  if (["unavailable", "inconsistent"].includes(status)) recordOtFailure(readOtBridgeErrorCode(statusResponse, "ot_observer_unavailable"), "observe");
33996
34483
  if (status) {
33997
34484
  updateOtStatusDisplay(status);
@@ -34022,20 +34509,10 @@ ${conclusion}` : `${label} ${conclusion}`;
34022
34509
  }
34023
34510
  }
34024
34511
  function readOtBridgeEvents(response) {
34025
- if (Array.isArray(response)) {
34026
- return response;
34027
- }
34028
- if (Array.isArray(response?.events)) {
34029
- return response.events;
34030
- }
34031
- if (Array.isArray(response?.result?.events)) {
34032
- return response.result.events;
34033
- }
34034
- return [];
34512
+ return otWarmMirrorController.readOtBridgeEvents(response);
34035
34513
  }
34036
34514
  function readOtBridgeErrorCode(response, fallback) {
34037
- const value = response?.lastErrorCode || response?.reason || response?.error?.code || response?.error || fallback;
34038
- return typeof value === "string" ? value : fallback;
34515
+ return otWarmMirrorController.readOtBridgeErrorCode(response, fallback);
34039
34516
  }
34040
34517
  function recordOtFailure(code, phase) {
34041
34518
  code = otWarmMirrorController.normalizeFailureCode(code);
@@ -34511,6 +34988,7 @@ ${conclusion}` : `${label} ${conclusion}`;
34511
34988
  let tx;
34512
34989
  let getLocale;
34513
34990
  let appendRunEvent;
34991
+ let appendRunRecordEvent;
34514
34992
  let pendingMirrorRefresh = null;
34515
34993
  let appendChangeSummary;
34516
34994
  let appendCompletionReport;
@@ -34557,6 +35035,34 @@ ${conclusion}` : `${label} ${conclusion}`;
34557
35035
  function buildSettlement(input = {}) {
34558
35036
  return writebackSettlement?.settle instanceof Function ? writebackSettlement.settle(input) : null;
34559
35037
  }
35038
+ function invalidateMirrorAfterUndo(runId, projectId, result = {}) {
35039
+ if (!projectId || result?.changedDocument !== true && !result?.applied?.length) return Promise.resolve("not-attempted");
35040
+ const pending = Promise.resolve(pendingMirrorRefresh).catch(() => {
35041
+ }).then(async () => {
35042
+ const response = await sendBackgroundNative({ method: "mirror.invalidate", params: { projectId } });
35043
+ if (!response?.ok || response.result?.invalidated !== true) {
35044
+ throw new Error(response?.error?.message || "Native host did not confirm mirror invalidation.");
35045
+ }
35046
+ if (getCurrentProjectId() === projectId) {
35047
+ resetContextProject();
35048
+ await callPageBridge("invalidateProjectSnapshot", { invalidateFileList: true });
35049
+ }
35050
+ return "invalidated";
35051
+ }).catch((error) => {
35052
+ appendRunRecordEvent?.(runId, {
35053
+ status: "warning",
35054
+ title: tx(
35055
+ `Overleaf undo finished, but local mirror invalidation failed: ${error.message}. Refresh this project or reconnect the native host before running again.`,
35056
+ `Overleaf \u64A4\u9500\u5DF2\u5B8C\u6210\uFF0C\u4F46\u672C\u5730\u955C\u50CF\u5931\u6548\u5904\u7406\u5931\u8D25\uFF1A${error.message}\u3002\u8BF7\u5237\u65B0\u9879\u76EE\u6216\u91CD\u65B0\u8FDE\u63A5 Native Host \u540E\u518D\u8FD0\u884C\u3002`
35057
+ )
35058
+ });
35059
+ return "failed";
35060
+ }).finally(() => {
35061
+ if (pendingMirrorRefresh === pending) pendingMirrorRefresh = null;
35062
+ });
35063
+ pendingMirrorRefresh = pending;
35064
+ return pending;
35065
+ }
34560
35066
  async function applySyncChangesToOverleaf(syncChanges = [], project = {}, options2 = {}) {
34561
35067
  const runMode = options2.mode || getState().mode;
34562
35068
  const runRequireReviewing = typeof options2.requireReviewing === "boolean" ? options2.requireReviewing : getState().requireReviewing === true;
@@ -35209,6 +35715,7 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
35209
35715
  tx,
35210
35716
  getLocale,
35211
35717
  appendRunEvent,
35718
+ appendRunRecordEvent,
35212
35719
  appendChangeSummary,
35213
35720
  appendCompletionReport,
35214
35721
  appendOperationsPreview,
@@ -35254,6 +35761,7 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
35254
35761
  return {
35255
35762
  applySyncChangesToOverleaf,
35256
35763
  resolveCompileLogContext,
35764
+ invalidateMirrorAfterUndo,
35257
35765
  getPendingMirrorRefresh: () => pendingMirrorRefresh
35258
35766
  };
35259
35767
  }
@@ -35261,6 +35769,153 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
35261
35769
  })();
35262
35770
  }).call(globalThis, void 0, void 0, void 0);
35263
35771
  (function runClassicContentModule94(module, exports, require2) {
35772
+ (function(root, factory) {
35773
+ const api = factory();
35774
+ if (typeof module === "object" && module.exports) module.exports = api;
35775
+ if (root) root.CodexOverleafTrackedChangeCaptureController = api;
35776
+ })(typeof window !== "undefined" ? window : globalThis, function() {
35777
+ "use strict";
35778
+ function create(deps = {}) {
35779
+ const now = deps.now || (() => Date.now());
35780
+ const defer = deps.setTimeout || setTimeout;
35781
+ const cancelTimer = deps.clearTimeout || clearTimeout;
35782
+ let timer = null, busy = false, disposed = false;
35783
+ const runs = () => deps.getRuns() || [];
35784
+ const pending = (run) => Array.isArray(run.trackedChangeCaptures) && run.trackedChangeCaptures.some((capture) => capture.state === "pending");
35785
+ const terminal = (run) => ["accepted", "rejected"].includes(run.trackedChangeStatus) || run.undoStatus === "applied" || run.forkSnapshot === true;
35786
+ function buildPostFiles(run) {
35787
+ const expectedFiles = Array.isArray(run?.undoExpectedFiles) ? run.undoExpectedFiles : [];
35788
+ const appliedOperations = Array.isArray(run?.appliedOperations) ? run.appliedOperations : [];
35789
+ if (!expectedFiles.length || !appliedOperations.length) return [];
35790
+ const postFilesByPath = deps.buildExpectedFilesAfterOperations({ files: expectedFiles }, appliedOperations);
35791
+ const expectedPaths = new Set(expectedFiles.map((file) => file?.path).filter(Boolean));
35792
+ return Array.from(postFilesByPath.entries()).filter(([path, content]) => expectedPaths.has(path) && typeof content === "string").map(([path, content]) => ({ path, content }));
35793
+ }
35794
+ function hasLegacyUntrackedCheckpoint(run) {
35795
+ if (!run || run.executionSnapshot || run.trackedChangeStatus || run.undoStatus === "applied" || run.forkSnapshot || run.undoTrackedChanges?.length || run.trackedChangeCaptures?.length) return false;
35796
+ const operations = Array.isArray(run.undoOperations) ? run.undoOperations : [];
35797
+ const before = Array.isArray(run.undoExpectedFiles) ? run.undoExpectedFiles : [];
35798
+ if (!operations.length || !before.length || before.some((file) => !file?.path || typeof file.content !== "string")) return false;
35799
+ const paths = new Set(before.map((file) => file.path));
35800
+ if (paths.size !== before.length || operations.some((op) => op?.type !== "edit" || !paths.has(op.path))) return false;
35801
+ const complete = (files) => Array.isArray(files) && files.length === paths.size && new Set(files.map((file) => file?.path)).size === paths.size && files.every((file) => paths.has(file?.path) && typeof file.content === "string");
35802
+ const storedPost = run.undoBaseFiles;
35803
+ try {
35804
+ const post = buildPostFiles(run);
35805
+ if (!complete(storedPost) || !complete(post)) return false;
35806
+ const postByPath = new Map(post.map((file) => [file.path, file.content]));
35807
+ if (!storedPost.every((file) => postByPath.get(file.path) === file.content)) return false;
35808
+ const restored = deps.buildExpectedFilesAfterOperations({ files: storedPost }, operations);
35809
+ return before.every((file) => restored.get(file.path) === file.content);
35810
+ } catch (_error) {
35811
+ return false;
35812
+ }
35813
+ }
35814
+ function schedule() {
35815
+ if (disposed || timer !== null || busy || !runs().some(pending)) return;
35816
+ timer = defer(() => {
35817
+ timer = null;
35818
+ flush().catch((error) => deps.onError?.(error));
35819
+ }, 500);
35820
+ }
35821
+ function record(run, captures) {
35822
+ if (!Array.isArray(captures) || !captures.length) return;
35823
+ const next = [...run.trackedChangeCaptures || [], ...captures.filter(Boolean)];
35824
+ if (next.every((capture) => capture.state === "observed")) return;
35825
+ run.trackedChangeCaptures = next;
35826
+ deps.notify?.(run, next.some((capture) => capture.state === "pending") ? "pending" : "needs_review", {
35827
+ captures: next.map((capture) => ({ path: capture.path, reason: capture.reason, ...capture.diagnostics }))
35828
+ });
35829
+ deps.persist().then(schedule).catch((error) => deps.onError?.(error));
35830
+ }
35831
+ async function flush() {
35832
+ if (disposed || busy) return;
35833
+ busy = true;
35834
+ try {
35835
+ for (const run of runs().filter(pending)) {
35836
+ if (terminal(run)) {
35837
+ run.trackedChangeCaptures = [];
35838
+ await deps.persist();
35839
+ continue;
35840
+ }
35841
+ if (run.runProjectId !== deps.getProjectId()) continue;
35842
+ const newer = runs().some((other) => other.id !== run.id && other.runProjectId === run.runProjectId && Date.parse(other.startedAt) > Date.parse(run.startedAt));
35843
+ const original = run.trackedChangeCaptures;
35844
+ const captures = original.map((capture) => ({ ...capture }));
35845
+ const postFiles = buildPostFiles(run);
35846
+ for (let i = 0; i < captures.length; i++) {
35847
+ const capture = captures[i];
35848
+ if (capture.state !== "pending") continue;
35849
+ const expected = postFiles.find((file) => file.path === capture.path);
35850
+ if (newer || !expected || now() >= capture.expiresAt || run.status === "cancelled") {
35851
+ capture.state = "needs_review";
35852
+ capture.reason = newer ? "capture_superseded" : !expected ? "capture_baseline_missing" : run.status === "cancelled" ? "capture_cancelled" : "capture_expired";
35853
+ continue;
35854
+ }
35855
+ let response;
35856
+ try {
35857
+ response = await deps.callPageBridge("reconcileTrackedChangeCapture", {
35858
+ runProjectId: run.runProjectId,
35859
+ capture,
35860
+ expectedContent: expected.content
35861
+ });
35862
+ } catch (_error) {
35863
+ response = null;
35864
+ }
35865
+ if (disposed || deps.getProjectId() !== run.runProjectId || !runs().includes(run) || terminal(run) || run.trackedChangeCaptures !== original) return;
35866
+ const superseded = runs().some((other) => other.id !== run.id && other.runProjectId === run.runProjectId && Date.parse(other.startedAt) > Date.parse(run.startedAt));
35867
+ if (superseded || run.status === "cancelled") {
35868
+ captures[i] = { ...capture, state: "needs_review", reason: "capture_superseded" };
35869
+ } else if (response?.capture?.path === capture.path && response.capture.runProjectId === run.runProjectId && ["observed", "pending", "needs_review"].includes(response.capture.state)) {
35870
+ captures[i] = response.capture;
35871
+ } else {
35872
+ captures[i] = { ...capture, state: "needs_review", reason: "capture_bridge_unavailable" };
35873
+ }
35874
+ }
35875
+ if (!runs().includes(run) || terminal(run) || deps.getProjectId() !== run.runProjectId) continue;
35876
+ if (captures.length && captures.every((capture) => capture.state === "observed")) {
35877
+ const refs = deps.normalizeTrackedChanges([
35878
+ ...run.undoTrackedChanges || [],
35879
+ ...captures.flatMap((capture) => capture.refs || [])
35880
+ ]);
35881
+ if (refs.length) {
35882
+ run.undoTrackedChanges = refs;
35883
+ run.trackedChangeStatus = "pending";
35884
+ run.trackedChangeCaptures = [];
35885
+ await deps.persist();
35886
+ deps.refresh(run);
35887
+ deps.notify?.(run, "observed", { count: refs.length });
35888
+ continue;
35889
+ }
35890
+ }
35891
+ const changed = JSON.stringify(original.map(summary)) !== JSON.stringify(captures.map(summary));
35892
+ if (changed) {
35893
+ run.trackedChangeCaptures = captures;
35894
+ await deps.persist();
35895
+ if (!pending(run)) deps.notify?.(run, "needs_review", {
35896
+ reasons: captures.filter((capture) => capture.state !== "observed").map((capture) => capture.reason)
35897
+ });
35898
+ }
35899
+ }
35900
+ } finally {
35901
+ busy = false;
35902
+ if (runs().some((run) => pending(run) && run.runProjectId === deps.getProjectId())) schedule();
35903
+ }
35904
+ }
35905
+ function summary(capture) {
35906
+ return { state: capture.state, reason: capture.reason, refs: (capture.refs || []).map((ref) => ref.key) };
35907
+ }
35908
+ function dispose() {
35909
+ disposed = true;
35910
+ if (timer !== null) cancelTimer(timer);
35911
+ timer = null;
35912
+ }
35913
+ return { record, schedule, flush, dispose, buildPostFiles, hasLegacyUntrackedCheckpoint };
35914
+ }
35915
+ return { create };
35916
+ });
35917
+ }).call(globalThis, void 0, void 0, void 0);
35918
+ (function runClassicContentModule95(module, exports, require2) {
35264
35919
  (function initCodexOverleafRunSettlementPersistence(root, factory) {
35265
35920
  const api = factory();
35266
35921
  if (typeof module === "object" && module.exports) module.exports = api;
@@ -35303,7 +35958,7 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
35303
35958
  return Object.freeze({ create });
35304
35959
  });
35305
35960
  }).call(globalThis, void 0, void 0, void 0);
35306
- (function runClassicContentModule95(module, exports, require2) {
35961
+ (function runClassicContentModule96(module, exports, require2) {
35307
35962
  (function initPostNavigationSettlementPersistence(root, factory) {
35308
35963
  const api = factory();
35309
35964
  if (typeof module === "object" && module.exports) module.exports = api;
@@ -35395,7 +36050,7 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
35395
36050
  return Object.freeze({ persist, persistRequired });
35396
36051
  });
35397
36052
  }).call(globalThis, void 0, void 0, void 0);
35398
- (function runClassicContentModule96(module, exports, require2) {
36053
+ (function runClassicContentModule97(module, exports, require2) {
35399
36054
  (function initCodexOverleafPanelMaintenance() {
35400
36055
  "use strict";
35401
36056
  function create(deps = {}) {
@@ -35506,11 +36161,12 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
35506
36161
  }
35507
36162
  const requestId = ++storageUsageRequestId;
35508
36163
  const projectId = getCurrentProjectId?.();
35509
- const isCurrent = () => requestId === storageUsageRequestId && projectId === getCurrentProjectId?.() && getSettingsPanelInstance()?.container?.querySelector("[data-storage-usage]") === node;
36164
+ const settingsScope = getPanel?.()?.dataset?.settingsScope;
36165
+ const isCurrent = () => requestId === storageUsageRequestId && projectId === getCurrentProjectId?.() && settingsScope === getPanel?.()?.dataset?.settingsScope && getSettingsPanelInstance()?.container?.querySelector("[data-storage-usage]") === node;
35510
36166
  const current = getState();
35511
36167
  const sessionCount = Array.isArray(current?.sessions) ? current.sessions.length : 0;
35512
36168
  const runCount = Array.isArray(current?.runs) ? current.runs.length : 0;
35513
- const counts = tx(
36169
+ const counts = settingsScope === "account" ? tx("All projects in this browser", "\u5F53\u524D\u6D4F\u89C8\u5668\u4E2D\u7684\u6240\u6709\u9879\u76EE") : tx(
35514
36170
  `${sessionCount} loaded session(s) in this project \xB7 ${runCount} run(s) in the active session`,
35515
36171
  `\u5F53\u524D\u9879\u76EE\u5DF2\u52A0\u8F7D ${sessionCount} \u4E2A\u4F1A\u8BDD \xB7 \u5F53\u524D\u4F1A\u8BDD ${runCount} \u8F6E\u8FD0\u884C`
35516
36172
  );
@@ -35726,7 +36382,7 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
35726
36382
  window.CodexOverleafPanelMaintenance = { create };
35727
36383
  })();
35728
36384
  }).call(globalThis, void 0, void 0, void 0);
35729
- (function runClassicContentModule97(module, exports, require2) {
36385
+ (function runClassicContentModule98(module, exports, require2) {
35730
36386
  (function initCodexOverleafUpdateIdle(root) {
35731
36387
  "use strict";
35732
36388
  function create(options2 = {}) {
@@ -35794,7 +36450,7 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
35794
36450
  root.CodexOverleafUpdateIdle = Object.freeze({ create });
35795
36451
  })(typeof window !== "undefined" ? window : globalThis);
35796
36452
  }).call(globalThis, void 0, void 0, void 0);
35797
- (function runClassicContentModule98(module, exports, require2) {
36453
+ (function runClassicContentModule99(module, exports, require2) {
35798
36454
  (function initCodexOverleafUpdateNotice(root, factory) {
35799
36455
  if (typeof module === "object" && module.exports) {
35800
36456
  module.exports = factory(require2("../shared/managedUpdateProjection"));
@@ -36259,7 +36915,7 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
36259
36915
  return Object.freeze({ mount });
36260
36916
  });
36261
36917
  }).call(globalThis, void 0, void 0, void 0);
36262
- (function runClassicContentModule99(module, exports, require2) {
36918
+ (function runClassicContentModule100(module, exports, require2) {
36263
36919
  (function initCodexOverleafContentRuntime(root) {
36264
36920
  "use strict";
36265
36921
  function init(dependencies2) {
@@ -36312,6 +36968,7 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
36312
36968
  "mirror.sync",
36313
36969
  "mirror.patchFiles",
36314
36970
  "mirror.confirmWriteback",
36971
+ "mirror.invalidate",
36315
36972
  "mirror.scanSensitive",
36316
36973
  "codex.history.clearPlugin",
36317
36974
  "skills.list",
@@ -36812,6 +37469,7 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
36812
37469
  RUN_SNAPSHOT_ZIP_TIMEOUT_MS,
36813
37470
  getState: () => state,
36814
37471
  getCurrentRunView: () => currentRunView,
37472
+ appendRunRecordEvent,
36815
37473
  onMirrorRefreshSettled: runSettlementPersistence.settleMirrorRefresh
36816
37474
  });
36817
37475
  const {
@@ -37013,6 +37671,7 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
37013
37671
  },
37014
37672
  saveState,
37015
37673
  saveStateSoon,
37674
+ onProjectFilesReady: refreshHistoricalProjectReferences,
37016
37675
  updateActiveSession,
37017
37676
  callPageBridge,
37018
37677
  getCurrentProjectId,
@@ -38483,15 +39142,19 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
38483
39142
  setRunning(false);
38484
39143
  nativeChannel.clearActiveRequest();
38485
39144
  stopRunElapsedTick();
38486
- if (isExperimentalOtEnabled()) {
38487
- await resumeOtWarmMirror("run-settled");
38488
- }
38489
39145
  await flushQueuedSaveState().catch(() => {
38490
39146
  });
38491
39147
  currentRunView = null;
38492
39148
  runCancellationRequested = false;
38493
39149
  runCancellationController = null;
38494
39150
  activeTurnControl.release(settlement.requestId);
39151
+ if (isExperimentalOtEnabled()) {
39152
+ try {
39153
+ await resumeOtWarmMirror("run-settled");
39154
+ } catch (_error) {
39155
+ updateOtStatusDisplay("unavailable");
39156
+ }
39157
+ }
38495
39158
  if (settlement.requestId) {
38496
39159
  activeTurnControl.acknowledge(settlement.requestId).catch(() => {
38497
39160
  });
@@ -41060,6 +41723,7 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
41060
41723
  title: session.title || "",
41061
41724
  titleSource: session.titleSource || "auto",
41062
41725
  focusFiles: session.focusFiles || [],
41726
+ projectReferenceFiles: session.projectReferenceFiles || [],
41063
41727
  codexThreadId: session.codexThreadId || "",
41064
41728
  createdAt: session.createdAt,
41065
41729
  updatedAt: session.updatedAt,
@@ -41125,6 +41789,7 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
41125
41789
  const urlProjectId = getCurrentProjectId();
41126
41790
  const projectId = projectIdOverride || urlProjectId;
41127
41791
  if (!projectId) {
41792
+ if (options2?.reviewRunIds?.length) throw new Error("Review persistence lost its project.");
41128
41793
  return;
41129
41794
  }
41130
41795
  const liveRunView = readLiveRunViewForSaveStateGuard();
@@ -41133,6 +41798,7 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
41133
41798
  "Skipped saveState: run is in flight on a different project than the current URL; settlement owns persistence.",
41134
41799
  "\u5DF2\u8DF3\u8FC7 saveState\uFF1A\u672C\u6B21\u8FD0\u884C\u6240\u5C5E\u9879\u76EE\u4E0E\u5F53\u524D URL \u4E0D\u4E00\u81F4\uFF0C\u5199\u56DE\u7531 settlement \u8D1F\u8D23\u3002"
41135
41800
  ));
41801
+ if (options2?.reviewRunIds?.length) throw new Error("Review persistence project changed.");
41136
41802
  return;
41137
41803
  }
41138
41804
  const compactState = prepareStateForStorage(state, { onAggressive: notifyAggressiveCompactionOnce });
@@ -41150,6 +41816,7 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
41150
41816
  compactState: compactStateSnapshot,
41151
41817
  projectId,
41152
41818
  deletedSessionIds: options2?.deletedSessionIds,
41819
+ reviewRunIds: options2?.reviewRunIds,
41153
41820
  Migration,
41154
41821
  StorageDb,
41155
41822
  SessionPersistence: Modules.SessionPersistence,
@@ -41175,17 +41842,17 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
41175
41842
  },
41176
41843
  persistScopedState
41177
41844
  );
41178
- if (!committed.ok && (options2?.queueMutation || options2?.queueMutations?.length || committed.reason !== "stale_view")) {
41845
+ if (!committed.ok && (options2?.reviewRunIds?.length || options2?.queueMutation || options2?.queueMutations?.length || committed.reason !== "stale_view")) {
41179
41846
  throw Object.assign(new Error(`Scoped persistence failed: ${committed.reason}`), {
41180
41847
  code: committed.reason
41181
41848
  });
41182
41849
  }
41183
41850
  }
41184
41851
  } catch (error) {
41185
- if (error?.code === "account_scope_unavailable" && !options2?.queueMutation && !(Array.isArray(options2?.queueMutations) && options2.queueMutations.length)) {
41852
+ if (error?.code === "account_scope_unavailable" && !options2?.reviewRunIds?.length && !options2?.queueMutation && !(Array.isArray(options2?.queueMutations) && options2.queueMutations.length)) {
41186
41853
  return;
41187
41854
  }
41188
- if (options2?.queueMutation || options2?.queueMutations?.length) {
41855
+ if (options2?.reviewRunIds?.length || options2?.queueMutation || options2?.queueMutations?.length) {
41189
41856
  throw error;
41190
41857
  }
41191
41858
  let fallbackError = null;
@@ -41284,6 +41951,7 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
41284
41951
  if (isStorageQuotaError(error)) {
41285
41952
  emitStorageQuotaFailure(error, null);
41286
41953
  }
41954
+ if (persistenceOptions?.reviewRunIds?.length) throw error;
41287
41955
  appendPlainLog(tx(`Failed to save session state: ${formatStateSaveError(error)}`, `\u4FDD\u5B58\u4F1A\u8BDD\u72B6\u6001\u5931\u8D25\uFF1A${formatStateSaveError(error)}`));
41288
41956
  }).finally(() => {
41289
41957
  if (saveStateInFlightPromise === runPromise) {
@@ -41330,6 +41998,7 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
41330
41998
  ...current || {},
41331
41999
  ...incoming,
41332
42000
  queueMutation: void 0,
42001
+ reviewRunIds: Array.from(/* @__PURE__ */ new Set([...current?.reviewRunIds || [], ...incoming?.reviewRunIds || []])),
41333
42002
  queueMutations,
41334
42003
  deletedSessionIds: Array.from(/* @__PURE__ */ new Set([
41335
42004
  ...current?.deletedSessionIds || [],
@@ -41371,15 +42040,7 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
41371
42040
  renderSessionList();
41372
42041
  }
41373
42042
  function getRenderedModelEntries() {
41374
- return Array.from(panel?.querySelector("[data-model]")?.options || []).map((option) => ({
41375
- id: option.value,
41376
- label: option.textContent,
41377
- reasoningEfforts: (option.dataset.reasoningEfforts || "").split(",").filter(Boolean),
41378
- defaultReasoningEffort: option.dataset.defaultReasoningEffort || "",
41379
- reasoningPresentation: option.dataset.reasoningPresentation || "",
41380
- speedTiers: (option.dataset.speedTiers || "standard").split(",").filter(Boolean),
41381
- defaultSpeedTier: option.dataset.defaultSpeedTier || "standard"
41382
- }));
42043
+ return Modules.ModelPickerSupport.getRenderedModelEntries(panel);
41383
42044
  }
41384
42045
  async function selectMode(mode) {
41385
42046
  if (!["ask", "auto"].includes(mode)) {
@@ -41500,6 +42161,7 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
41500
42161
  applySessionLabel();
41501
42162
  renderSessionList();
41502
42163
  renderRunHistory(options2);
42164
+ getTrackedChangeCaptureController().schedule();
41503
42165
  renderContextSelection();
41504
42166
  renderContextSummary();
41505
42167
  if (!composerAttachmentsRestoredOnce && Array.isArray(state.composerAttachments) && state.composerAttachments.length && !composerAttachmentController.getAttachmentsForRun?.().length && typeof composerAttachmentController.restorePersistedAttachments === "function") {
@@ -41578,6 +42240,7 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
41578
42240
  undoStatus: "",
41579
42241
  queueItemId,
41580
42242
  nativeRequestId: "",
42243
+ codexThreadId: "",
41581
42244
  codexTurnId: "",
41582
42245
  nativeEventSeq: 0,
41583
42246
  executionSnapshot: executionSnapshot ? RunExecutionSnapshot.cloneSnapshot(executionSnapshot) : void 0,
@@ -41716,6 +42379,7 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
41716
42379
  currentRunView.activeTurn = { threadId, turnId };
41717
42380
  const record = findRunRecord(currentRunView.recordId, currentRunView.sessionId);
41718
42381
  if (record) {
42382
+ record.codexThreadId = threadId;
41719
42383
  record.codexTurnId = turnId;
41720
42384
  record.nativeEventSeq = Math.max(Number(record.nativeEventSeq || 0), Number(journalSeq || 0));
41721
42385
  }
@@ -42029,6 +42693,12 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
42029
42693
  }
42030
42694
  return files;
42031
42695
  }
42696
+ function refreshHistoricalProjectReferences(project) {
42697
+ if (!project?.id || String(project.id) !== getCurrentProjectId()) return;
42698
+ state = updateActiveSession(state, { projectReferenceFiles: captureProjectReferenceFiles(project) });
42699
+ saveStateSoon();
42700
+ if (!currentRunView && !trackedChangeInFlight.size) renderRunHistory({ preserveScroll: true });
42701
+ }
42032
42702
  function persistCurrentProjectReferenceFiles(project) {
42033
42703
  const projectFiles = captureProjectReferenceFiles(project);
42034
42704
  if (currentRunView) {
@@ -42143,6 +42813,7 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
42143
42813
  runProjectId: getRunProjectIdForWriteback(run)
42144
42814
  });
42145
42815
  const undoApplied = isUndoResultEffectivelyApplied(run, result);
42816
+ writebackOrchestrator.invalidateMirrorAfterUndo(runId, getRunProjectIdForWriteback(run), result);
42146
42817
  appendUndoReviewingPolicyEvent(runId, result.reviewingPolicy);
42147
42818
  appendRunRecordEvent(runId, {
42148
42819
  title: tr("undoResult", { applied: result.applied?.length || 0, skipped: result.skipped?.length || 0 }),
@@ -42194,46 +42865,50 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
42194
42865
  status: "running",
42195
42866
  detail: { [tr("detailWillUndo")]: trackedUndo ? formatTrackedChangeFiles(run.undoTrackedChanges) : formatTrackedUndoFiles(run) }
42196
42867
  });
42197
- let result;
42198
42868
  try {
42199
- result = await callPageBridge("rejectTrackedChanges", {
42869
+ const trackedUndoPostFilesAtDispatch = buildTrackedUndoPostFiles(run);
42870
+ const result = await callPageBridge("rejectTrackedChanges", {
42200
42871
  trackedChanges: run.undoTrackedChanges || [],
42201
42872
  expectedFiles: run.undoExpectedFiles || [],
42202
- postFiles: buildTrackedUndoPostFiles(run),
42873
+ postFiles: trackedUndoPostFilesAtDispatch,
42874
+ untrackedUndo: run.executionSnapshot ? run.executionSnapshot.requireReviewing === false && ["submitted", "legacy-captured"].includes(run.executionSnapshot.source) : getTrackedChangeCaptureController().hasLegacyUntrackedCheckpoint(run),
42203
42875
  // Welcome-panel + write-guard:
42204
42876
  // bind the reject to the run's original project. If the user has
42205
42877
  // navigated away the page-side guard refuses with
42206
42878
  // `aborted_project_changed` and the document is left untouched.
42207
42879
  runProjectId: getRunProjectIdForWriteback(run)
42208
42880
  });
42881
+ writebackOrchestrator.invalidateMirrorAfterUndo(runId, getRunProjectIdForWriteback(run), result);
42882
+ appendRunRecordEvent(runId, {
42883
+ title: trackedUndo ? tr("undoTrackedResult", { applied: result.applied?.length || 0, skipped: result.skipped?.length || 0 }) : tr("undoNativeResult", { applied: result.applied?.length || 0, skipped: result.skipped?.length || 0 }),
42884
+ status: result.skipped?.length ? "failed" : "completed",
42885
+ detail: {
42886
+ [trackedUndo ? tr("detailRejected") : tr("detailUndone")]: (result.applied || []).map((item) => ({
42887
+ [tr("detailFile")]: item.trackedChange?.path || tr("unknownFile"),
42888
+ [tr("detailRecord")]: item.trackedChange?.label || item.trackedChange?.id || item.trackedChange?.key
42889
+ })),
42890
+ [tr("detailSkipped")]: (result.skipped || []).map((item) => ({
42891
+ [tr("detailFile")]: item.trackedChange?.path || tr("unknownFile"),
42892
+ [tr("detailRecord")]: item.trackedChange?.label || item.trackedChange?.id || item.trackedChange?.key || "",
42893
+ [tr("detailReason")]: formatBridgeResultReason(item.result, item.trackedChange?.path)
42894
+ }))
42895
+ }
42896
+ });
42897
+ if (lifecycleReject) {
42898
+ WritebackSettlement.attachUndoNotVerifiedFailure(run, result, {
42899
+ buildFailure: buildContentFailure,
42900
+ postFiles: trackedUndoPostFilesAtDispatch
42901
+ });
42902
+ await applyTrackedChangeSettlement(runId, "reject", result);
42903
+ return;
42904
+ }
42905
+ applyLegacyUndoSettlement(runId, result.skipped?.length ? "partial" : "applied", result);
42209
42906
  } finally {
42210
42907
  if (lifecycleReject) {
42211
42908
  trackedChangeInFlight.delete(runId);
42909
+ refreshRunCardControls(runId);
42212
42910
  }
42213
42911
  }
42214
- appendRunRecordEvent(runId, {
42215
- title: trackedUndo ? tr("undoTrackedResult", { applied: result.applied?.length || 0, skipped: result.skipped?.length || 0 }) : tr("undoNativeResult", { applied: result.applied?.length || 0, skipped: result.skipped?.length || 0 }),
42216
- status: result.skipped?.length ? "failed" : "completed",
42217
- detail: {
42218
- [trackedUndo ? tr("detailRejected") : tr("detailUndone")]: (result.applied || []).map((item) => ({
42219
- [tr("detailFile")]: item.trackedChange?.path || tr("unknownFile"),
42220
- [tr("detailRecord")]: item.trackedChange?.label || item.trackedChange?.id || item.trackedChange?.key
42221
- })),
42222
- [tr("detailSkipped")]: (result.skipped || []).map((item) => ({
42223
- [tr("detailFile")]: item.trackedChange?.path || tr("unknownFile"),
42224
- [tr("detailRecord")]: item.trackedChange?.label || item.trackedChange?.id || item.trackedChange?.key || "",
42225
- [tr("detailReason")]: formatBridgeResultReason(item.result, item.trackedChange?.path)
42226
- }))
42227
- }
42228
- });
42229
- if (lifecycleReject) {
42230
- WritebackSettlement.attachUndoNotVerifiedFailure(run, result, {
42231
- buildFailure: buildContentFailure
42232
- });
42233
- applyTrackedChangeSettlement(runId, "reject", result);
42234
- return;
42235
- }
42236
- applyLegacyUndoSettlement(runId, result.skipped?.length ? "partial" : "applied", result);
42237
42912
  }
42238
42913
  async function acceptRun(runId) {
42239
42914
  const run = findRunRecord(runId);
@@ -42257,50 +42932,50 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
42257
42932
  status: "running",
42258
42933
  detail: { [tr("detailAccepted")]: formatTrackedChangeFiles(run.undoTrackedChanges) }
42259
42934
  });
42260
- let result;
42261
42935
  try {
42262
- result = await callPageBridge("acceptTrackedChanges", {
42936
+ const result = await callPageBridge("acceptTrackedChanges", {
42263
42937
  trackedChanges: run.undoTrackedChanges || [],
42264
42938
  expectedFiles: run.undoExpectedFiles || [],
42265
42939
  postFiles: buildTrackedUndoPostFiles(run),
42266
42940
  appliedOperations: Array.isArray(run.appliedOperations) ? run.appliedOperations : [],
42267
42941
  runProjectId: getRunProjectIdForWriteback(run)
42268
42942
  });
42269
- } finally {
42270
- trackedChangeInFlight.delete(runId);
42271
- }
42272
- appendAcceptDiagnosticEvents(runId, Array.isArray(result.diagnostics) ? result.diagnostics : []);
42273
- appendRunRecordEvent(runId, {
42274
- title: tr("runAcceptTrackedResult", { applied: result.applied?.length || 0, skipped: result.skipped?.length || 0 }),
42275
- status: result.skipped?.length ? "failed" : "completed",
42276
- detail: {
42277
- [tr("detailAccepted")]: (result.applied || []).map((item) => ({
42278
- [tr("detailFile")]: item.trackedChange?.path || tr("unknownFile"),
42279
- [tr("detailRecord")]: item.trackedChange?.label || item.trackedChange?.id || item.trackedChange?.key
42280
- })),
42281
- [tr("detailSkipped")]: (result.skipped || []).map((item) => ({
42282
- [tr("detailFile")]: item.trackedChange?.path || tr("unknownFile"),
42283
- [tr("detailRecord")]: item.trackedChange?.label || item.trackedChange?.id || item.trackedChange?.key || "",
42284
- [tr("detailReason")]: formatBridgeResultReason(item.result, item.trackedChange?.path)
42285
- }))
42286
- }
42287
- });
42288
- if (result.ok === false) {
42943
+ appendAcceptDiagnosticEvents(runId, Array.isArray(result.diagnostics) ? result.diagnostics : []);
42289
42944
  appendRunRecordEvent(runId, {
42290
- title: tr("runAcceptTrackedFailed"),
42291
- status: "failed",
42945
+ title: tr("runAcceptTrackedResult", { applied: result.applied?.length || 0, skipped: result.skipped?.length || 0 }),
42946
+ status: result.skipped?.length ? "failed" : "completed",
42292
42947
  detail: {
42293
- [tr("detailReason")]: tr("runAcceptTrackedFailedReason")
42948
+ [tr("detailAccepted")]: (result.applied || []).map((item) => ({
42949
+ [tr("detailFile")]: item.trackedChange?.path || tr("unknownFile"),
42950
+ [tr("detailRecord")]: item.trackedChange?.label || item.trackedChange?.id || item.trackedChange?.key
42951
+ })),
42952
+ [tr("detailSkipped")]: (result.skipped || []).map((item) => ({
42953
+ [tr("detailFile")]: item.trackedChange?.path || tr("unknownFile"),
42954
+ [tr("detailRecord")]: item.trackedChange?.label || item.trackedChange?.id || item.trackedChange?.key || "",
42955
+ [tr("detailReason")]: formatBridgeResultReason(item.result, item.trackedChange?.path)
42956
+ }))
42294
42957
  }
42295
42958
  });
42296
- if (!WritebackSettlement.isSuccessfulTrackedChangeSettlement(result)) {
42297
- refreshRunCardControls(runId);
42959
+ if (result.ok === false) {
42960
+ appendRunRecordEvent(runId, {
42961
+ title: tr("runAcceptTrackedFailed"),
42962
+ status: "failed",
42963
+ detail: {
42964
+ [tr("detailReason")]: tr("runAcceptTrackedFailedReason")
42965
+ }
42966
+ });
42967
+ if (!WritebackSettlement.isSuccessfulTrackedChangeSettlement(result)) {
42968
+ refreshRunCardControls(runId);
42969
+ }
42298
42970
  }
42971
+ WritebackSettlement.attachAcceptNotVerifiedFailure(run, result, {
42972
+ buildFailure: buildContentFailure
42973
+ });
42974
+ await applyTrackedChangeSettlement(runId, "accept", result);
42975
+ } finally {
42976
+ trackedChangeInFlight.delete(runId);
42977
+ refreshRunCardControls(runId);
42299
42978
  }
42300
- WritebackSettlement.attachAcceptNotVerifiedFailure(run, result, {
42301
- buildFailure: buildContentFailure
42302
- });
42303
- applyTrackedChangeSettlement(runId, "accept", result);
42304
42979
  }
42305
42980
  function getRunProjectIdForWriteback(run) {
42306
42981
  return typeof run?.runProjectId === "string" && run.runProjectId ? run.runProjectId : "";
@@ -42331,7 +43006,7 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
42331
43006
  FailureReasons
42332
43007
  );
42333
43008
  }
42334
- function applyTrackedChangeSettlement(runId, kind, result) {
43009
+ async function applyTrackedChangeSettlement(runId, kind, result) {
42335
43010
  const run = findRunRecord(runId);
42336
43011
  const settlement = WritebackSettlement.settleTrackedChangeLifecycle({
42337
43012
  kind,
@@ -42340,12 +43015,14 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
42340
43015
  failureReasons: FailureReasons
42341
43016
  });
42342
43017
  if (settlement.decision === "blocked") {
42343
- refreshRunCardControls(runId);
42344
43018
  return;
42345
43019
  }
42346
- Object.assign(run, WritebackSettlement.applySettlementTransition(run, settlement));
42347
- saveStateSoon();
42348
- refreshRunCardControls(runId);
43020
+ await Modules.ScopedPersistenceCoordinator.applyReviewTransition({
43021
+ getState: () => state,
43022
+ runId,
43023
+ transition: WritebackSettlement.applySettlementTransition(run, settlement),
43024
+ persist: flushQueuedSaveState
43025
+ });
42349
43026
  }
42350
43027
  function getRunUndoCount(run) {
42351
43028
  if (!run) {
@@ -42408,6 +43085,28 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
42408
43085
  function attachVerifiedContentToOperation(operation, result) {
42409
43086
  return WritebackSettlement.attachVerifiedContentToOperation(operation, result);
42410
43087
  }
43088
+ let trackedChangeCaptureController = null;
43089
+ function getTrackedChangeCaptureController() {
43090
+ return trackedChangeCaptureController || (trackedChangeCaptureController = Modules.TrackedChangeCaptureController.create({
43091
+ getRuns: () => (state.sessions || []).flatMap((session) => session.runs || []),
43092
+ getProjectId: () => window.location.pathname.match(/^\/project\/([^/]+)/)?.[1] || "",
43093
+ buildExpectedFilesAfterOperations,
43094
+ normalizeTrackedChanges: normalizeApplyTrackedChanges,
43095
+ callPageBridge,
43096
+ persist: () => flushQueuedSaveState({ preserveRunActionPayload: true }),
43097
+ refresh: (run) => refreshRunCardControls(run.id),
43098
+ notify: (run, kind, detail) => appendRunRecordEvent(run.id, {
43099
+ title: {
43100
+ pending: tx("Files written; confirming tracked changes.", "\u6587\u4EF6\u5DF2\u5199\u5165\uFF0C\u6B63\u5728\u786E\u8BA4\u7559\u75D5\u8BB0\u5F55\u3002"),
43101
+ observed: tx("Tracked changes confirmed; Accept is available.", "\u7559\u75D5\u8BB0\u5F55\u5DF2\u786E\u8BA4\uFF0C\u53EF\u4EE5\u63A5\u53D7\u6539\u52A8\u3002"),
43102
+ needs_review: tx("Tracked changes could not be safely attributed. Review them in Overleaf; Undo is preserved.", "\u7559\u75D5\u5F52\u5C5E\u5C1A\u672A\u786E\u8BA4\uFF0C\u8BF7\u5728 Overleaf \u5BA1\u9605\uFF1B\u539F\u6709\u64A4\u9500\u80FD\u529B\u5DF2\u4FDD\u7559\u3002")
43103
+ }[kind],
43104
+ status: "info",
43105
+ detail
43106
+ }),
43107
+ onError: (error) => appendPlainLog(tx("Tracked-change confirmation unavailable: ", "\u7559\u75D5\u786E\u8BA4\u6682\u4E0D\u53EF\u7528\uFF1A") + error.message)
43108
+ }));
43109
+ }
42411
43110
  function recordUndoFromApply(project, applyResult) {
42412
43111
  const appliedEntries = getAppliedEntries(applyResult);
42413
43112
  if (!currentRunView?.recordId || !appliedEntries.length) {
@@ -42444,6 +43143,7 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
42444
43143
  if (combinedTrackedChanges.length) {
42445
43144
  record.trackedChangeStatus = "pending";
42446
43145
  }
43146
+ getTrackedChangeCaptureController().record(record, applyResult?.trackedChangeCaptures);
42447
43147
  refreshRunCardControls(record.id);
42448
43148
  if (combinedTrackedChanges.length) {
42449
43149
  appendRunEvent({
@@ -42505,28 +43205,10 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
42505
43205
  return WritebackSettlement.normalizeApplyTrackedChanges(changes);
42506
43206
  }
42507
43207
  function selectExpectedFilesForTrackedUndo(project, operations = [], trackedChanges = [], previousExpectedFiles = []) {
42508
- return WritebackSettlement.selectExpectedFilesForTrackedUndo(
42509
- project,
42510
- operations,
42511
- trackedChanges,
42512
- previousExpectedFiles
42513
- );
43208
+ return WritebackSettlement.selectExpectedFilesForTrackedUndo(project, operations, trackedChanges, previousExpectedFiles);
42514
43209
  }
42515
43210
  function buildTrackedUndoPostFiles(run) {
42516
- const expectedFiles = Array.isArray(run?.undoExpectedFiles) ? run.undoExpectedFiles : [];
42517
- const appliedOperations = Array.isArray(run?.appliedOperations) ? run.appliedOperations : [];
42518
- if (!expectedFiles.length || !appliedOperations.length) {
42519
- return [];
42520
- }
42521
- const postFilesByPath = buildExpectedFilesAfterOperations(
42522
- { files: expectedFiles },
42523
- appliedOperations
42524
- );
42525
- const expectedPaths = new Set(expectedFiles.map((file) => file?.path).filter(Boolean));
42526
- return Array.from(postFilesByPath.entries()).filter(([path, content]) => expectedPaths.has(path) && typeof content === "string").map(([path, content]) => ({
42527
- path,
42528
- content
42529
- }));
43211
+ return getTrackedChangeCaptureController().buildPostFiles(run);
42530
43212
  }
42531
43213
  function hasTrackedEditorUndo(run) {
42532
43214
  return buildTrackedUndoPostFiles(run).length > 0;
@@ -43103,7 +43785,7 @@ Reason: ${item.reason || "No reason provided"}`),
43103
43785
  root.CodexOverleafContentRuntime = { init };
43104
43786
  })(typeof window !== "undefined" ? window : globalThis);
43105
43787
  }).call(globalThis, void 0, void 0, void 0);
43106
- (function runClassicContentModule100(module, exports, require2) {
43788
+ (function runClassicContentModule101(module, exports, require2) {
43107
43789
  (function initCodexOverleafLegacyGlobalRegistry(root, factory) {
43108
43790
  "use strict";
43109
43791
  const api = factory();
@@ -43179,6 +43861,7 @@ Reason: ${item.reason || "No reason provided"}`),
43179
43861
  "StorageMigration",
43180
43862
  "Summary",
43181
43863
  "Theme",
43864
+ "TrackedChangeCaptureController",
43182
43865
  "UndoOperations",
43183
43866
  "UpdateIdle",
43184
43867
  "UpdateNotice",