codex-overleaf-link 2.4.0 → 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.
@@ -1,4 +1,4 @@
1
- if (typeof document !== 'undefined' && document.documentElement) document.documentElement.setAttribute('data-codex-overleaf-source-digest', "f146d7e07b67706193fa5fcff26d7c9719826ac745aec9b745dd95c52413d27c");
1
+ if (typeof document !== 'undefined' && document.documentElement) document.documentElement.setAttribute('data-codex-overleaf-source-digest', "b19a018ab6eb9c7bdca8cfad8ff6efcc4a30e80f81c3c0a2e89a7f78b6361d97");
2
2
  (() => {
3
3
  // extension/entries/content-entry.mjs
4
4
  (function runClassicContentModule(module, exports, require2) {
@@ -4583,14 +4583,39 @@ ${block}`;
4583
4583
  failureReason_editor_project_id_unavailable_next: "Refresh the Overleaf tab and retry; if it persists, reload the extension.",
4584
4584
  // Welcome-panel + write-guard: Recent-projects
4585
4585
  // variant copy. Spec §5.3–§5.5, §5.8, §5.10 are the source of truth.
4586
- recentProjects_welcome: "Codex Overleaf Link",
4587
- recentProjects_welcome_subtitle: "Open a project from the Overleaf list on the left to start.",
4588
- recentProjects_empty: "You haven't run any Codex sessions yet. Open a project from the Overleaf list on the left to start.",
4589
- recentProjects_sessions_empty: "No sessions in this project yet",
4590
- recentProjects_sessions_toggle: "Show / manage this project's sessions",
4591
- recentProjects_sessions_action: "View runs",
4592
- recentProjects_sessions_action_open: "Hide runs",
4593
- 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",
4594
4619
  recentProjects_clearProject_title: "Delete all Codex sessions for {project}?",
4595
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.",
4596
4621
  recentProjects_clearProject_confirm: "Delete all",
@@ -4606,15 +4631,17 @@ ${block}`;
4606
4631
  recentProjects_degraded: "Sign in to Overleaf to see your recent Codex projects.",
4607
4632
  recentProjects_row_projectLinkUnavailable: "Project link unavailable.",
4608
4633
  recentProjects_settings_entry: "Settings & Diagnostics",
4609
- recentProjects_badge_pending: "pending",
4610
- recentProjects_badge_accepted: "accepted",
4611
- recentProjects_badge_rejected: "rejected",
4612
- recentProjects_badge_needs_review: "pending",
4613
- recentProjects_badge_running: "running",
4614
- recentProjects_badge_completed: "completed",
4615
- recentProjects_badge_failed: "failed",
4616
- recentProjects_badge_interrupted: "interrupted",
4617
- 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",
4618
4645
  recentProjects_unnamed: "Project \xB7 {prefix}",
4619
4646
  recentProjects_zombieRunningNote: "Marked as running, but it may be a leftover from an interrupted run.",
4620
4647
  recentProjects_zombieDeleteConfirm: "This session is marked as running. If no other tab is actually running it, you can delete it anyway.",
@@ -4622,9 +4649,9 @@ ${block}`;
4622
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.",
4623
4650
  recentProjects_sessionsCount: "{count} session(s)",
4624
4651
  recentProjects_welcome_stats: "{projects} projects \xB7 {sessions} Codex sessions",
4625
- recentProjects_badge_background_completed: "completed in background",
4626
- recentProjects_badge_needs_review_after_navigation: "pending",
4627
- 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"
4628
4655
  },
4629
4656
  zh: {
4630
4657
  resizePanel: "\u62D6\u52A8\u8C03\u6574 Codex \u9762\u677F\u5BBD\u5EA6\uFF0C\u53CC\u51FB\u6062\u590D\u9ED8\u8BA4\u5BBD\u5EA6",
@@ -5122,14 +5149,39 @@ ${block}`;
5122
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",
5123
5150
  // Welcome-panel + write-guard: Recent-projects
5124
5151
  // variant copy. 见 spec §5.3–§5.5、§5.8、§5.10。
5125
- recentProjects_welcome: "Codex Overleaf Link",
5126
- recentProjects_welcome_subtitle: "\u4ECE\u5DE6\u4FA7 Overleaf \u9879\u76EE\u5217\u8868\u4E2D\u6253\u5F00\u4EFB\u610F\u9879\u76EE\u5373\u53EF\u5F00\u59CB\u3002",
5127
- 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",
5128
- recentProjects_sessions_empty: "\u8BE5\u9879\u76EE\u8FD8\u6CA1\u6709\u4F1A\u8BDD",
5129
- recentProjects_sessions_toggle: "\u67E5\u770B/\u7BA1\u7406\u8BE5\u9879\u76EE\u7684\u4F1A\u8BDD",
5130
- recentProjects_sessions_action: "\u67E5\u770B\u4F1A\u8BDD",
5131
- recentProjects_sessions_action_open: "\u6536\u8D77\u4F1A\u8BDD",
5132
- 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",
5133
5185
  recentProjects_clearProject_title: "\u5220\u9664 {project} \u7684\u5168\u90E8 Codex \u4F1A\u8BDD\uFF1F",
5134
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",
5135
5187
  recentProjects_clearProject_confirm: "\u5168\u90E8\u5220\u9664",
@@ -5145,15 +5197,17 @@ ${block}`;
5145
5197
  recentProjects_degraded: "\u8BF7\u767B\u5F55 Overleaf \u540E\u67E5\u770B\u4F60\u6700\u8FD1\u7684 Codex \u9879\u76EE\u3002",
5146
5198
  recentProjects_row_projectLinkUnavailable: "\u9879\u76EE\u94FE\u63A5\u4E0D\u53EF\u7528\u3002",
5147
5199
  recentProjects_settings_entry: "\u8BBE\u7F6E\u4E0E\u8BCA\u65AD",
5148
- recentProjects_badge_pending: "\u5F85\u5904\u7406",
5149
- recentProjects_badge_accepted: "\u5DF2\u63A5\u53D7",
5150
- recentProjects_badge_rejected: "\u5DF2\u64A4\u56DE",
5151
- recentProjects_badge_needs_review: "\u5F85\u5904\u7406",
5152
- recentProjects_badge_running: "\u8FD0\u884C\u4E2D",
5153
- recentProjects_badge_completed: "\u5DF2\u5B8C\u6210",
5154
- recentProjects_badge_failed: "\u5931\u8D25",
5155
- recentProjects_badge_interrupted: "\u5DF2\u4E2D\u65AD",
5156
- 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",
5157
5211
  recentProjects_unnamed: "\u9879\u76EE \xB7 {prefix}",
5158
5212
  recentProjects_zombieRunningNote: "\u6807\u8BB0\u4E3A\u8FD0\u884C\u4E2D\uFF0C\u4F46\u53EF\u80FD\u662F\u88AB\u4E2D\u65AD\u540E\u7684\u6B8B\u7559\u3002",
5159
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",
@@ -5161,9 +5215,9 @@ ${block}`;
5161
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",
5162
5216
  recentProjects_sessionsCount: "{count} \u4E2A\u4F1A\u8BDD",
5163
5217
  recentProjects_welcome_stats: "{projects} \u4E2A\u9879\u76EE \xB7 {sessions} \u4E2A Codex \u4F1A\u8BDD",
5164
- recentProjects_badge_background_completed: "\u540E\u53F0\u5DF2\u5B8C\u6210",
5165
- recentProjects_badge_needs_review_after_navigation: "\u5F85\u5904\u7406",
5166
- 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"
5167
5221
  }
5168
5222
  };
5169
5223
  function normalizeLocale(value) {
@@ -9904,6 +9958,56 @@ ${block}`;
9904
9958
  }
9905
9959
  return out;
9906
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
+ }
9907
10011
  function hashString(value) {
9908
10012
  var hash = 2166136261;
9909
10013
  var text = String(value || "");
@@ -9915,6 +10019,7 @@ ${block}`;
9915
10019
  }
9916
10020
  return {
9917
10021
  compactStructuredEventValue,
10022
+ mergeSessionReviewState,
9918
10023
  compactRunsForStorage,
9919
10024
  compactRunActionPayload,
9920
10025
  compactProviderSnapshot,
@@ -11203,6 +11308,7 @@ ${block}`;
11203
11308
  clearStore,
11204
11309
  clearAllStores,
11205
11310
  buildSessionRecord,
11311
+ mergeSessionReviewState: StorageRunActions.mergeSessionReviewState,
11206
11312
  buildTurnRecord,
11207
11313
  buildEventRecord,
11208
11314
  buildArtifactRecord,
@@ -11829,7 +11935,7 @@ ${block}`;
11829
11935
  const MIN_NATIVE_VERSION = "1.0.0";
11830
11936
  const MIN_COMPATIBLE_NATIVE_VERSION = "1.0.0";
11831
11937
  const MIN_COMPATIBLE_EXTENSION_VERSION = "1.0.0";
11832
- const BUILD_TARGET_VERSION = "2.4.0";
11938
+ const BUILD_TARGET_VERSION = "2.4.1";
11833
11939
  const DEFAULT_CHROME_EXTENSION_ID = "illdpneeeopfffmiepaejglgmhpmdhdc";
11834
11940
  const REQUIRED_CAPABILITIES = Object.freeze([
11835
11941
  "bridgePing",
@@ -18059,8 +18165,8 @@ ${block}`;
18059
18165
  <div class="codex-custom-instructions-head">
18060
18166
  <button type="button" data-settings-back title="Back" aria-label="Back">\u2039</button>
18061
18167
  <div>
18062
- <div class="codex-custom-instructions-title" data-i18n="projectSettingsTitle">Project Settings</div>
18063
- <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>
18064
18170
  </div>
18065
18171
  </div>
18066
18172
  <div class="codex-project-settings-status" data-project-settings-status></div>
@@ -18318,6 +18424,7 @@ ${block}`;
18318
18424
  if (!root) {
18319
18425
  return;
18320
18426
  }
18427
+ refreshSettingsScope(instance, root);
18321
18428
  const button = getButton(instance);
18322
18429
  if (button) {
18323
18430
  button.dataset.active = "true";
@@ -18325,6 +18432,17 @@ ${block}`;
18325
18432
  }
18326
18433
  root.querySelector("[data-custom-instructions-input]")?.focus?.();
18327
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
+ }
18328
18446
  function hide(target) {
18329
18447
  const instance = target?._instance || target;
18330
18448
  const root = getRoot(instance);
@@ -18352,6 +18470,7 @@ ${block}`;
18352
18470
  if (!root) {
18353
18471
  return;
18354
18472
  }
18473
+ refreshSettingsScope(instance, root);
18355
18474
  if (Object.prototype.hasOwnProperty.call(state, "customInstructions")) {
18356
18475
  const input = root.querySelector("[data-custom-instructions-input]");
18357
18476
  if (input) {
@@ -20215,7 +20334,12 @@ ${block}`;
20215
20334
  await Promise.all(deletedExisting.map((record) => StorageDb.deleteRecord("sessions", record.id)));
20216
20335
  }
20217
20336
  const existingById = new Map(existingSessions.filter((record) => !deleted.has(record.id)).map((record) => [record.id, record]));
20218
- 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) => {
20219
20343
  const existing = existingById.get(record.id);
20220
20344
  if (!existing) return true;
20221
20345
  return (Date.parse(record.updatedAt || "") || 0) >= (Date.parse(existing.updatedAt || "") || 0);
@@ -20271,6 +20395,41 @@ ${block}`;
20271
20395
  }
20272
20396
  return JSON.parse(JSON.stringify(value));
20273
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
+ }
20274
20433
  async function persistPanelState(input) {
20275
20434
  var state = cloneSnapshot(input.state || {});
20276
20435
  var compactState = cloneSnapshot(input.compactState || {});
@@ -20289,6 +20448,8 @@ ${block}`;
20289
20448
  code: "stale_view"
20290
20449
  });
20291
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();
20292
20453
  var latestPrefs = typeof Migration.loadPrefs === "function" ? await Migration.loadPrefs(accountScopeId, projectId) : {};
20293
20454
  var prefs = Object.assign(
20294
20455
  {},
@@ -20344,19 +20505,23 @@ ${block}`;
20344
20505
  })
20345
20506
  }), { preserveRunActionPayload: true });
20346
20507
  });
20347
- await input.SessionPersistence.writeSessions({
20508
+ var written = await input.SessionPersistence.writeSessions({
20348
20509
  Migration,
20349
20510
  StorageDb,
20350
20511
  projectId,
20351
20512
  accountScopeId,
20352
20513
  deletedSessionIds,
20353
20514
  sessionRecords,
20515
+ reviewRunIds: input.reviewRunIds,
20354
20516
  queueClaims: persistenceMeta.queueClaims || {},
20355
20517
  queueTombstones,
20356
20518
  writerId: input.persistenceContext?.writerId || ""
20357
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();
20358
20523
  }
20359
- return Object.freeze({ persistPanelState });
20524
+ return Object.freeze({ persistPanelState, applyReviewTransition });
20360
20525
  });
20361
20526
  }).call(globalThis, void 0, void 0, void 0);
20362
20527
  (function runClassicContentModule63(module, exports, require2) {
@@ -20495,7 +20660,8 @@ ${block}`;
20495
20660
  }
20496
20661
  return Object.freeze({
20497
20662
  createController,
20498
- persistPanelState: PanelState?.persistPanelState || null
20663
+ persistPanelState: PanelState?.persistPanelState || null,
20664
+ applyReviewTransition: PanelState?.applyReviewTransition || null
20499
20665
  });
20500
20666
  });
20501
20667
  }).call(globalThis, void 0, void 0, void 0);
@@ -30808,11 +30974,11 @@ ${conclusion}` : `${label} ${conclusion}`;
30808
30974
  }
30809
30975
  function configureLifecycleUndoButton(button, run) {
30810
30976
  const status = run.trackedChangeStatus || "";
30977
+ const inFlight = trackedChangeInFlight.get(run.id);
30811
30978
  const projection = projectRunSettlement(run);
30812
30979
  if (status === "needs_review") {
30813
- const inFlight2 = trackedChangeInFlight.get(run.id);
30814
30980
  button.hidden = false;
30815
- button.disabled = !projection.canUndo || inFlight2 === "reject" || inFlight2 === "accept";
30981
+ button.disabled = !projection.canUndo || inFlight === "reject" || inFlight === "accept";
30816
30982
  button.textContent = tr("undoRun");
30817
30983
  button.title = tr("undoRunTitle");
30818
30984
  button.addEventListener("click", (event) => {
@@ -30821,25 +30987,24 @@ ${conclusion}` : `${label} ${conclusion}`;
30821
30987
  });
30822
30988
  return;
30823
30989
  }
30824
- if (status === "rejected") {
30990
+ if (status === "rejected" && !inFlight) {
30825
30991
  button.hidden = false;
30826
30992
  button.disabled = true;
30827
30993
  button.textContent = tr("undoApplied");
30828
30994
  button.title = tr("undoAppliedTitle");
30829
30995
  return;
30830
30996
  }
30831
- if (status === "accepted") {
30997
+ if (status === "accepted" && !inFlight) {
30832
30998
  button.hidden = false;
30833
30999
  button.disabled = true;
30834
31000
  button.textContent = tr("undoRun");
30835
31001
  button.title = tr("undoRunTitle");
30836
31002
  return;
30837
31003
  }
30838
- if (status !== "pending") {
31004
+ if (status !== "pending" && !inFlight) {
30839
31005
  button.hidden = true;
30840
31006
  return;
30841
31007
  }
30842
- const inFlight = trackedChangeInFlight.get(run.id);
30843
31008
  button.hidden = false;
30844
31009
  button.disabled = !projection.canUndo || inFlight === "reject" || inFlight === "accept";
30845
31010
  button.textContent = tr("undoRun");
@@ -30865,12 +31030,12 @@ ${conclusion}` : `${label} ${conclusion}`;
30865
31030
  return;
30866
31031
  }
30867
31032
  const status = run.trackedChangeStatus || "";
31033
+ const inFlight = trackedChangeInFlight.get(run.id);
30868
31034
  const projection = projectRunSettlement(run);
30869
31035
  if (status === "needs_review") {
30870
- const inFlight2 = trackedChangeInFlight.get(run.id);
30871
31036
  button.hidden = false;
30872
- button.disabled = !projection.canAccept || inFlight2 === "accept" || inFlight2 === "reject";
30873
- if (inFlight2 === "accept") {
31037
+ button.disabled = !projection.canAccept || inFlight === "accept" || inFlight === "reject";
31038
+ if (inFlight === "accept") {
30874
31039
  button.textContent = tr("runAcceptTrackedConfirming");
30875
31040
  button.title = tr("runAcceptTrackedConfirming");
30876
31041
  return;
@@ -30880,25 +31045,24 @@ ${conclusion}` : `${label} ${conclusion}`;
30880
31045
  wireAcceptInlineConfirm(button, run.id);
30881
31046
  return;
30882
31047
  }
30883
- if (status === "accepted") {
31048
+ if (status === "accepted" && !inFlight) {
30884
31049
  button.hidden = false;
30885
31050
  button.disabled = true;
30886
31051
  button.textContent = tr("runAcceptTrackedDone");
30887
31052
  button.title = tr("runAcceptTrackedDoneTitle");
30888
31053
  return;
30889
31054
  }
30890
- if (status === "rejected") {
31055
+ if (status === "rejected" && !inFlight) {
30891
31056
  button.hidden = false;
30892
31057
  button.disabled = true;
30893
31058
  button.textContent = tr("runAcceptTracked");
30894
31059
  button.title = tr("runAcceptTrackedTitle");
30895
31060
  return;
30896
31061
  }
30897
- if (status !== "pending") {
31062
+ if (status !== "pending" && !inFlight) {
30898
31063
  button.hidden = true;
30899
31064
  return;
30900
31065
  }
30901
- const inFlight = trackedChangeInFlight.get(run.id);
30902
31066
  button.hidden = false;
30903
31067
  button.disabled = !projection.canAccept || inFlight === "accept" || inFlight === "reject";
30904
31068
  if (inFlight === "accept") {
@@ -32991,6 +33155,7 @@ ${conclusion}` : `${label} ${conclusion}`;
32991
33155
  });
32992
33156
  const PROJECT_NAME_CACHE_STORAGE_KEY = "projectNameCacheByAccount";
32993
33157
  let projectNameCacheMirror = {};
33158
+ let dashboardRenderGeneration = 0;
32994
33159
  function loadProjectNameCacheFromStorage() {
32995
33160
  return new Promise(function(resolve) {
32996
33161
  try {
@@ -33074,7 +33239,7 @@ ${conclusion}` : `${label} ${conclusion}`;
33074
33239
  await persistProjectNameCacheToStorage();
33075
33240
  var visibleList = getPanel() && getPanel().querySelector("[data-recent-projects-list]");
33076
33241
  if (visibleList) {
33077
- var rows = visibleList.querySelectorAll("[data-recent-projects-row]");
33242
+ var rows = visibleList.querySelectorAll("[data-project-id]");
33078
33243
  for (var k = 0; k < rows.length; k++) {
33079
33244
  var row = rows[k];
33080
33245
  var rowPid = row.getAttribute("data-project-id");
@@ -33083,6 +33248,7 @@ ${conclusion}` : `${label} ${conclusion}`;
33083
33248
  var cached = lookupProjectName(rowPid);
33084
33249
  if (cached) {
33085
33250
  nameEl.textContent = cached;
33251
+ nameEl.title = cached;
33086
33252
  }
33087
33253
  }
33088
33254
  }
@@ -33151,54 +33317,47 @@ ${conclusion}` : `${label} ${conclusion}`;
33151
33317
  var hr = Math.round(min / 60);
33152
33318
  if (hr < 24) return tx(hr + " hr ago", hr + " \u5C0F\u65F6\u524D");
33153
33319
  var day = Math.round(hr / 24);
33154
- 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");
33155
33321
  var month = Math.round(day / 30);
33156
33322
  if (month < 12) return tx(month + " mo ago", month + " \u4E2A\u6708\u524D");
33157
33323
  var year = Math.round(month / 12);
33158
33324
  return tx(year + " yr ago", year + " \u5E74\u524D");
33159
33325
  }
33160
- function textNode(text, className) {
33161
- var el = document.createElement("span");
33162
- if (className) {
33163
- el.className = className;
33164
- }
33165
- 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;
33166
33330
  return el;
33167
33331
  }
33168
- function renderWelcomeHeader(stats) {
33169
- var el = document.createElement("div");
33170
- 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");
33171
33344
  el.setAttribute("data-recent-projects-welcome", "");
33172
- var title = document.createElement("div");
33173
- title.className = "recent-projects-welcome-title";
33174
- title.textContent = tr("recentProjects_welcome");
33175
- var subtitle = document.createElement("div");
33176
- subtitle.className = "recent-projects-welcome-subtitle";
33177
- subtitle.textContent = stats && stats.projectCount ? tr("recentProjects_welcome_stats", { projects: stats.projectCount, sessions: stats.sessionTotal }) : tr("recentProjects_welcome_subtitle");
33178
- el.appendChild(title);
33179
- 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"));
33180
33348
  return el;
33181
33349
  }
33182
33350
  function renderEmptyState() {
33183
- var el = document.createElement("div");
33184
- el.className = "recent-projects-empty";
33351
+ var el = createViewElement("div", "recent-projects-empty", tr("recentProjects_empty"));
33185
33352
  el.setAttribute("data-recent-projects-empty", "");
33186
- el.textContent = tr("recentProjects_empty");
33187
33353
  return el;
33188
33354
  }
33189
33355
  function renderDegradedState() {
33190
- var el = document.createElement("div");
33191
- el.className = "recent-projects-degraded";
33356
+ var el = createViewElement("div", "recent-projects-degraded");
33192
33357
  el.setAttribute("data-recent-projects-degraded", "");
33193
- var hint = document.createElement("div");
33194
- hint.textContent = tr("recentProjects_degraded_hint");
33358
+ var hint = createViewElement("div", "", tr("recentProjects_degraded_hint"));
33195
33359
  el.appendChild(hint);
33196
- var retry = document.createElement("button");
33197
- retry.type = "button";
33198
- retry.className = "recent-projects-show-all";
33199
- retry.setAttribute("data-recent-projects-retry", "");
33200
- retry.textContent = tr("recentProjects_retryScope");
33201
- retry.addEventListener("click", function() {
33360
+ var retry = createViewButton(tr("recentProjects_retryScope"), function() {
33202
33361
  if (typeof refreshAccountScopeId === "function") {
33203
33362
  Promise.resolve(refreshAccountScopeId()).then(function() {
33204
33363
  return renderRecentProjectsVariant();
@@ -33207,24 +33366,17 @@ ${conclusion}` : `${label} ${conclusion}`;
33207
33366
  } else {
33208
33367
  renderRecentProjectsVariant();
33209
33368
  }
33210
- });
33369
+ }, "data-recent-projects-retry", "recent-projects-show-all");
33211
33370
  el.appendChild(retry);
33212
33371
  return el;
33213
33372
  }
33214
33373
  function renderSettingsEntry(options2) {
33215
33374
  var scope = options2 && options2.scope === "account" ? "account" : "project";
33216
- var entry = document.createElement("button");
33217
- entry.type = "button";
33218
- entry.className = "recent-projects-settings-entry";
33219
- entry.setAttribute("data-recent-projects-settings-entry", "");
33220
- entry.setAttribute("data-settings-scope", scope);
33221
- var label = document.createElement("span");
33222
- label.className = "recent-projects-settings-entry-label";
33223
- label.textContent = tr("recentProjects_settings_entry");
33224
- entry.appendChild(label);
33225
- entry.addEventListener("click", function() {
33375
+ var entry = createViewButton(void 0, function() {
33226
33376
  openSettingsInScope(scope);
33227
- });
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"));
33228
33380
  return entry;
33229
33381
  }
33230
33382
  function openSettingsInScope(scope) {
@@ -33247,10 +33399,8 @@ ${conclusion}` : `${label} ${conclusion}`;
33247
33399
  function renderStatusBadge(status) {
33248
33400
  var safeStatus = typeof status === "string" && STATUS_BADGE_CLASS[status] ? status : "pending";
33249
33401
  var cls = STATUS_BADGE_CLASS[safeStatus];
33250
- var el = document.createElement("span");
33251
- el.className = "recent-projects-row-badge " + cls;
33402
+ var el = textNode(tr("recentProjects_badge_" + safeStatus), "recent-projects-row-badge " + cls);
33252
33403
  el.setAttribute("data-status", safeStatus);
33253
- el.textContent = tr("recentProjects_badge_" + safeStatus);
33254
33404
  return el;
33255
33405
  }
33256
33406
  function isValidProjectId(id) {
@@ -33262,121 +33412,137 @@ ${conclusion}` : `${label} ${conclusion}`;
33262
33412
  }
33263
33413
  window.location.assign("https://www.overleaf.com/project/" + encodeURIComponent(projectId));
33264
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
+ }
33265
33524
  function renderRecentProjectRow(row) {
33266
33525
  var projectId = row && row.projectId;
33267
33526
  var valid = isValidProjectId(projectId);
33268
- var el = document.createElement("button");
33269
- el.type = "button";
33270
- el.className = "recent-projects-row";
33271
- el.setAttribute("data-recent-projects-row", "");
33272
- el.setAttribute("data-project-id", projectId || "");
33273
- if (!valid) {
33274
- el.disabled = true;
33275
- el.setAttribute("aria-disabled", "true");
33276
- }
33277
- var name = lookupProjectName(projectId);
33278
- if (!name) {
33279
- name = isValidProjectId(projectId) ? tr("recentProjects_unnamed", { prefix: projectId.slice(0, 8) }) : tr("recentProjects_row_projectLinkUnavailable");
33280
- }
33281
- var nameNode = textNode(name, "recent-projects-row-name");
33282
- nameNode.title = name;
33283
- el.appendChild(nameNode);
33284
- var rowTime = textNode(formatRelativeTime(row && row.lastActivityAt), "recent-projects-row-time");
33285
- rowTime.setAttribute("data-rel-time", String(row && row.lastActivityAt || ""));
33286
- el.appendChild(rowTime);
33287
- var sessionCount = Number(row && row.sessionCount) || 0;
33288
- if (sessionCount > 1) {
33289
- el.appendChild(textNode(tr("recentProjects_sessionsCount", { count: sessionCount }), "recent-projects-row-sessions"));
33290
- }
33291
- el.appendChild(renderStatusBadge(settleDashboardRunStatus(row && row.primaryStatusBadge, row && row.lastActivityAt)));
33292
- if (valid) {
33293
- el.addEventListener("click", function() {
33294
- openProjectFromRow(projectId);
33295
- });
33296
- } else {
33297
- el.appendChild(textNode(tr("recentProjects_row_projectLinkUnavailable"), "recent-projects-row-warning"));
33298
- }
33299
- if (!valid) {
33300
- var deadWrap = document.createElement("div");
33301
- deadWrap.className = "recent-projects-row-wrap";
33302
- var deadHead = document.createElement("div");
33303
- deadHead.className = "recent-projects-row-head";
33304
- var cleanup = document.createElement("button");
33305
- cleanup.type = "button";
33306
- cleanup.className = "recent-projects-row-cleanup";
33307
- cleanup.setAttribute("data-row-cleanup", "");
33308
- cleanup.title = tr("recentProjects_cleanup");
33309
- cleanup.setAttribute("aria-label", tr("recentProjects_cleanup"));
33310
- cleanup.textContent = "\xD7";
33311
- cleanup.addEventListener("click", function() {
33312
- cleanupDeadProjectEntry(projectId).catch(function() {
33313
- });
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() {
33314
33538
  });
33315
- deadHead.appendChild(el);
33316
- deadHead.appendChild(cleanup);
33317
- deadWrap.appendChild(deadHead);
33318
- return deadWrap;
33319
- }
33320
- var wrap = document.createElement("div");
33321
- wrap.className = "recent-projects-row-wrap";
33322
- wrap.setAttribute("data-project-row-wrap", projectId);
33323
- var expand = document.createElement("button");
33324
- expand.type = "button";
33325
- expand.className = "recent-projects-row-expand";
33326
- expand.setAttribute("data-row-expand", "");
33327
- expand.setAttribute("aria-expanded", "false");
33328
- var sessionsElId = "codex-project-sessions-" + String(projectId || "").slice(0, 24);
33329
- expand.setAttribute("aria-controls", sessionsElId);
33330
- expand.title = tr("recentProjects_sessions_toggle");
33331
- expand.setAttribute("aria-label", tr("recentProjects_sessions_toggle"));
33332
- expand.textContent = tr("recentProjects_sessions_action");
33333
- var head = document.createElement("div");
33334
- head.className = "recent-projects-row-head";
33335
- var clear = document.createElement("button");
33336
- clear.type = "button";
33337
- clear.className = "recent-projects-row-clear";
33338
- clear.setAttribute("data-project-clear", "");
33339
- clear.title = tr("recentProjects_clearProject");
33340
- clear.setAttribute("aria-label", tr("recentProjects_clearProject"));
33341
- clear.textContent = "\xD7";
33342
- clear.addEventListener("click", function(event) {
33343
- event.stopPropagation();
33344
- projectSessionCleanup.clearProjectSessions(projectId, name).catch(function() {
33539
+ else cleanupDeadProjectEntry(projectId).catch(function() {
33345
33540
  });
33346
- });
33347
- head.appendChild(el);
33348
- head.appendChild(expand);
33349
- head.appendChild(clear);
33350
- var sessionsEl = document.createElement("div");
33351
- sessionsEl.className = "recent-projects-sessions";
33352
- sessionsEl.setAttribute("data-project-sessions", "");
33353
- sessionsEl.id = sessionsElId;
33354
- sessionsEl.setAttribute("role", "region");
33355
- sessionsEl.setAttribute("aria-label", tr("recentProjects_sessions_toggle"));
33356
- sessionsEl.hidden = true;
33357
- expand.addEventListener("click", function() {
33358
- toggleProjectSessions(wrap, projectId);
33359
- });
33360
- wrap.appendChild(head);
33361
- wrap.appendChild(sessionsEl);
33541
+ }, valid ? "data-project-clear" : "data-row-cleanup");
33542
+ wrap.appendChild(link);
33543
+ wrap.appendChild(renderActionsMenu([cleanup]));
33362
33544
  return wrap;
33363
33545
  }
33364
- function toggleProjectSessions(wrap, projectId) {
33365
- var sessionsEl = wrap.querySelector("[data-project-sessions]");
33366
- var expand = wrap.querySelector("[data-row-expand]");
33367
- if (!sessionsEl || !expand) {
33368
- return;
33369
- }
33370
- var opening = sessionsEl.hidden;
33371
- sessionsEl.hidden = !opening;
33372
- expand.setAttribute("aria-expanded", opening ? "true" : "false");
33373
- expand.textContent = tr(opening ? "recentProjects_sessions_action_open" : "recentProjects_sessions_action");
33374
- wrap.setAttribute("data-expanded", opening ? "true" : "false");
33375
- if (opening) {
33376
- renderProjectSessions(sessionsEl, projectId).catch(function() {
33377
- });
33378
- }
33379
- }
33380
33546
  function sessionRecordDisplayTitle(record) {
33381
33547
  if (record && typeof record.title === "string" && record.title.trim()) {
33382
33548
  return record.title.trim();
@@ -33398,87 +33564,90 @@ ${conclusion}` : `${label} ${conclusion}`;
33398
33564
  return String(b.updatedAt || b.createdAt || "").localeCompare(String(a.updatedAt || a.createdAt || ""));
33399
33565
  });
33400
33566
  }
33401
- async function renderProjectSessions(sessionsEl, projectId) {
33402
- sessionsEl.innerHTML = "";
33403
- var sessionsLoading = document.createElement("div");
33404
- sessionsLoading.className = "recent-projects-loading";
33405
- sessionsLoading.textContent = tx("Loading sessions\u2026", "\u6B63\u5728\u52A0\u8F7D\u4F1A\u8BDD\u2026");
33406
- sessionsEl.appendChild(sessionsLoading);
33407
- var records = [];
33408
- try {
33409
- records = await loadProjectSessionRecords(projectId);
33410
- } catch (_error) {
33411
- records = [];
33412
- }
33413
- sessionsLoading.remove();
33414
- if (!records.length) {
33415
- sessionsEl.appendChild(textNode(tr("recentProjects_sessions_empty"), "recent-projects-sessions-empty"));
33416
- return;
33417
- }
33418
- for (var i = 0; i < records.length; i++) {
33419
- sessionsEl.appendChild(renderProjectSessionRow(sessionsEl, projectId, records[i]));
33420
- }
33421
- }
33422
- function renderProjectSessionRow(sessionsEl, projectId, record) {
33567
+ function renderProjectSessionRow(sessionsEl, projectId, record, options2) {
33568
+ var featured = Boolean(options2 && options2.featured);
33423
33569
  var running = Boolean(StorageDb) && settleDashboardRunStatus(StorageDb.derivePrimaryStatusBadge(record), record.updatedAt || record.lastActivityAt) === "running";
33424
- var row = document.createElement("div");
33425
- row.className = "recent-projects-session-row";
33570
+ var row = createViewElement("div", "recent-projects-session-row" + (featured ? " recent-projects-focus" : ""));
33426
33571
  row.setAttribute("data-project-session-row", record.id);
33427
- if (running) {
33428
- row.setAttribute("data-running", "true");
33429
- }
33572
+ row.setAttribute("data-project-id", projectId);
33573
+ if (running) row.setAttribute("data-running", "true");
33430
33574
  var title = textNode(sessionRecordDisplayTitle(record), "recent-projects-session-title");
33431
- title.title = sessionRecordDisplayTitle(record);
33432
- var time = textNode(formatRelativeTime(record.updatedAt || record.createdAt), "recent-projects-session-time");
33433
- time.setAttribute("data-rel-time", String(record.updatedAt || record.createdAt || ""));
33434
- var rename = document.createElement("button");
33435
- rename.type = "button";
33436
- rename.className = "recent-projects-session-action";
33437
- rename.setAttribute("data-session-rename-dash", "");
33438
- rename.title = tr("renameSession");
33439
- rename.setAttribute("aria-label", tr("renameSession"));
33440
- rename.textContent = "\u270E";
33441
- var del = document.createElement("button");
33442
- del.type = "button";
33443
- del.className = "recent-projects-session-action recent-projects-session-action--delete";
33444
- del.setAttribute("data-session-delete-dash", "");
33445
- del.title = tr("deleteSession");
33446
- del.setAttribute("aria-label", tr("deleteSession"));
33447
- del.textContent = "\xD7";
33448
- if (running) {
33449
- rename.disabled = true;
33450
- 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
+ }
33451
33599
  }
33452
- rename.addEventListener("click", function() {
33600
+ meta.appendChild(time);
33601
+ row.appendChild(copy);
33602
+ var rename = createViewButton(tr("renameSession"), function() {
33453
33603
  beginDashboardSessionRename(sessionsEl, projectId, record, row, title);
33454
- });
33455
- 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() {
33456
33609
  deleteDashboardSession(sessionsEl, projectId, record).catch(function() {
33457
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);
33458
33620
  });
33459
- row.appendChild(title);
33460
- row.appendChild(time);
33461
- row.appendChild(rename);
33462
- row.appendChild(del);
33463
- row.classList.add("recent-projects-session-row--linked");
33464
- row.setAttribute("role", "button");
33465
- 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]));
33466
33626
  var activate = function() {
33467
- activateSessionAndOpenProject(projectId, record).catch(function() {
33627
+ openDashboardConversation(projectId, record, row).catch(function() {
33468
33628
  });
33469
33629
  };
33470
- row.addEventListener("click", function(event) {
33471
- if (event.target.closest("button") || event.target.closest("input")) {
33472
- return;
33473
- }
33474
- activate();
33475
- });
33476
- row.addEventListener("keydown", function(event) {
33477
- if (event.key === "Enter") {
33478
- event.preventDefault();
33479
- activate();
33480
- }
33481
- });
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
+ }
33482
33651
  return row;
33483
33652
  }
33484
33653
  async function activateSessionAndOpenProject(projectId, record) {
@@ -33609,11 +33778,7 @@ ${conclusion}` : `${label} ${conclusion}`;
33609
33778
  } catch (error) {
33610
33779
  showPluginToast(tr("deleteSessionHistoryFailedToast", { message: error.message }), { status: "warning", sticky: true });
33611
33780
  }
33612
- var rootForScroll = getPanel() && getPanel().querySelector("[data-recent-projects-root]");
33613
- await renderRecentProjectsVariant({
33614
- expandProjectId: projectId,
33615
- restoreScrollTop: rootForScroll ? rootForScroll.scrollTop : void 0
33616
- });
33781
+ await refreshDashboardView();
33617
33782
  }
33618
33783
  function beginDashboardSessionRename(sessionsEl, projectId, record, row, titleEl) {
33619
33784
  if (row.querySelector("input")) {
@@ -33626,7 +33791,7 @@ ${conclusion}` : `${label} ${conclusion}`;
33626
33791
  input.className = "recent-projects-session-rename-input";
33627
33792
  input.value = seed;
33628
33793
  titleEl.hidden = true;
33629
- row.insertBefore(input, titleEl);
33794
+ titleEl.parentElement.insertBefore(input, titleEl);
33630
33795
  input.focus();
33631
33796
  input.select();
33632
33797
  var settled = false;
@@ -33688,7 +33853,7 @@ ${conclusion}` : `${label} ${conclusion}`;
33688
33853
  }
33689
33854
  } catch (_error) {
33690
33855
  }
33691
- await renderProjectSessions(sessionsEl, projectId);
33856
+ await refreshDashboardView();
33692
33857
  }
33693
33858
  function ensureRecentProjectsRoot() {
33694
33859
  if (!getPanel()) {
@@ -33698,9 +33863,13 @@ ${conclusion}` : `${label} ${conclusion}`;
33698
33863
  if (existing) {
33699
33864
  return existing;
33700
33865
  }
33701
- var rootEl = document.createElement("section");
33702
- rootEl.className = "recent-projects-root";
33866
+ var rootEl = createViewElement("section", "recent-projects-root");
33703
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
+ });
33704
33873
  getPanel().appendChild(rootEl);
33705
33874
  return rootEl;
33706
33875
  }
@@ -33737,116 +33906,152 @@ ${conclusion}` : `${label} ${conclusion}`;
33737
33906
  return;
33738
33907
  }
33739
33908
  rootEl.appendChild(renderWelcomeHeader());
33740
- var loading = document.createElement("div");
33741
- loading.className = "recent-projects-loading";
33742
- 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"));
33743
33910
  rootEl.appendChild(loading);
33744
33911
  }
33745
33912
  async function renderRecentProjectsVariant(options2) {
33746
- if (!getPanel()) {
33747
- return;
33748
- }
33749
- 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";
33750
33920
  startRelativeTimeTicker();
33751
33921
  var rootEl = ensureRecentProjectsRoot();
33752
- if (!rootEl) {
33753
- return;
33754
- }
33755
- rootEl.innerHTML = "";
33756
- rootEl.appendChild(renderWelcomeHeader());
33922
+ if (!rootEl) return;
33757
33923
  var accountScopeId = (window.codexOverleafDeriveAccountScopeId || function() {
33758
33924
  return null;
33759
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());
33760
33936
  if (!accountScopeId) {
33761
33937
  rootEl.appendChild(renderDegradedState());
33762
33938
  rootEl.appendChild(renderSettingsEntry({ scope: "account" }));
33763
- opportunisticEnrichmentFromDom().catch(function() {
33764
- });
33765
33939
  return;
33766
33940
  }
33767
- var listContainer = document.createElement("div");
33768
- 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");
33769
33954
  listContainer.setAttribute("data-recent-projects-list", "");
33770
33955
  rootEl.appendChild(listContainer);
33771
- var listLoading = document.createElement("div");
33772
- listLoading.className = "recent-projects-loading";
33773
- listLoading.textContent = tx("Loading projects\u2026", "\u6B63\u5728\u52A0\u8F7D\u9879\u76EE\u2026");
33774
- 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;
33775
33962
  try {
33776
33963
  await loadProjectNameCacheFromStorage();
33964
+ records = await loadDashboardConversations(accountScopeId);
33777
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;
33778
33975
  }
33779
- var rows = [];
33780
- var showAll = Boolean(options2 && options2.showAll);
33781
- var pageLimit = 10;
33782
- try {
33783
- if (StorageDb) {
33784
- var deletedSessionIdsByProject = await SessionPersistence.loadTombstones();
33785
- rows = await StorageDb.listRecentProjectsAcrossAccount({
33786
- accountScopeId,
33787
- limit: showAll ? 500 : pageLimit + 1,
33788
- 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));
33789
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
+ }
33790
34005
  }
33791
- } catch (_error) {
33792
- rows = [];
33793
- }
33794
- listLoading.remove();
33795
- var restoreExpanded = Array.isArray(options2 && options2.restoreExpanded) ? options2.restoreExpanded : [];
33796
- var welcomeSubtitle = rootEl.querySelector(".recent-projects-welcome-subtitle");
33797
- if (welcomeSubtitle && rows && rows.length) {
33798
- var sessionTotal = 0;
33799
- for (var st = 0; st < rows.length; st++) {
33800
- sessionTotal += Number(rows[st].sessionCount) || 1;
33801
- }
33802
- welcomeSubtitle.textContent = tr("recentProjects_welcome_stats", {
33803
- projects: rows.length,
33804
- sessions: sessionTotal
33805
- });
33806
- }
33807
- var hasMore = !showAll && rows && rows.length > pageLimit;
33808
- var visibleRows = hasMore ? rows.slice(0, pageLimit) : rows;
33809
- if (!visibleRows || !visibleRows.length) {
33810
- listContainer.appendChild(renderEmptyState());
33811
34006
  } else {
33812
- for (var i = 0; i < visibleRows.length; i++) {
33813
- listContainer.appendChild(renderRecentProjectRow(visibleRows[i]));
33814
- }
33815
- }
33816
- if (hasMore) {
33817
- var showAllButton = document.createElement("button");
33818
- showAllButton.type = "button";
33819
- showAllButton.className = "recent-projects-show-all";
33820
- showAllButton.setAttribute("data-recent-projects-show-all", "");
33821
- showAllButton.textContent = tx("Show all projects", "\u67E5\u770B\u5168\u90E8\u9879\u76EE");
33822
- showAllButton.addEventListener("click", function() {
33823
- 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();
33824
34027
  });
33825
- listContainer.appendChild(showAllButton);
33826
- }
33827
- rootEl.appendChild(renderSettingsEntry({ scope: "account" }));
33828
- var expandProjectId = options2 && options2.expandProjectId;
33829
- var toReopen = restoreExpanded.slice();
33830
- if (expandProjectId && toReopen.indexOf(expandProjectId) === -1) {
33831
- toReopen.push(expandProjectId);
33832
- }
33833
- for (var reopenIndex = 0; reopenIndex < toReopen.length; reopenIndex++) {
33834
- var wrap = listContainer.querySelector('[data-project-row-wrap="' + toReopen[reopenIndex] + '"]');
33835
- if (wrap) {
33836
- toggleProjectSessions(wrap, toReopen[reopenIndex]);
34028
+ listContainer.appendChild(drafts);
34029
+ if (options2.draftsOpen) {
34030
+ drafts.open = true;
34031
+ populateDrafts();
33837
34032
  }
33838
34033
  }
33839
- if (options2 && Number.isFinite(options2.restoreScrollTop)) {
33840
- rootEl.scrollTop = options2.restoreScrollTop;
33841
- var scrollerParent = rootEl.parentElement;
33842
- if (scrollerParent && scrollerParent.scrollHeight > scrollerParent.clientHeight) {
33843
- scrollerParent.scrollTop = options2.restoreScrollTop;
33844
- }
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
+ });
33845
34047
  }
34048
+ rootEl.appendChild(renderSettingsEntry({ scope: "account" }));
34049
+ if (Number.isFinite(options2.restoreScrollTop)) rootEl.scrollTop = options2.restoreScrollTop;
33846
34050
  opportunisticEnrichmentFromDom().catch(function() {
33847
34051
  });
33848
34052
  }
33849
34053
  function renderPerProjectVariant() {
34054
+ dashboardRenderGeneration++;
33850
34055
  if (!getPanel()) {
33851
34056
  return;
33852
34057
  }
@@ -35956,11 +36161,12 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
35956
36161
  }
35957
36162
  const requestId = ++storageUsageRequestId;
35958
36163
  const projectId = getCurrentProjectId?.();
35959
- 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;
35960
36166
  const current = getState();
35961
36167
  const sessionCount = Array.isArray(current?.sessions) ? current.sessions.length : 0;
35962
36168
  const runCount = Array.isArray(current?.runs) ? current.runs.length : 0;
35963
- 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(
35964
36170
  `${sessionCount} loaded session(s) in this project \xB7 ${runCount} run(s) in the active session`,
35965
36171
  `\u5F53\u524D\u9879\u76EE\u5DF2\u52A0\u8F7D ${sessionCount} \u4E2A\u4F1A\u8BDD \xB7 \u5F53\u524D\u4F1A\u8BDD ${runCount} \u8F6E\u8FD0\u884C`
35966
36172
  );
@@ -41583,6 +41789,7 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
41583
41789
  const urlProjectId = getCurrentProjectId();
41584
41790
  const projectId = projectIdOverride || urlProjectId;
41585
41791
  if (!projectId) {
41792
+ if (options2?.reviewRunIds?.length) throw new Error("Review persistence lost its project.");
41586
41793
  return;
41587
41794
  }
41588
41795
  const liveRunView = readLiveRunViewForSaveStateGuard();
@@ -41591,6 +41798,7 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
41591
41798
  "Skipped saveState: run is in flight on a different project than the current URL; settlement owns persistence.",
41592
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"
41593
41800
  ));
41801
+ if (options2?.reviewRunIds?.length) throw new Error("Review persistence project changed.");
41594
41802
  return;
41595
41803
  }
41596
41804
  const compactState = prepareStateForStorage(state, { onAggressive: notifyAggressiveCompactionOnce });
@@ -41608,6 +41816,7 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
41608
41816
  compactState: compactStateSnapshot,
41609
41817
  projectId,
41610
41818
  deletedSessionIds: options2?.deletedSessionIds,
41819
+ reviewRunIds: options2?.reviewRunIds,
41611
41820
  Migration,
41612
41821
  StorageDb,
41613
41822
  SessionPersistence: Modules.SessionPersistence,
@@ -41633,17 +41842,17 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
41633
41842
  },
41634
41843
  persistScopedState
41635
41844
  );
41636
- 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")) {
41637
41846
  throw Object.assign(new Error(`Scoped persistence failed: ${committed.reason}`), {
41638
41847
  code: committed.reason
41639
41848
  });
41640
41849
  }
41641
41850
  }
41642
41851
  } catch (error) {
41643
- 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)) {
41644
41853
  return;
41645
41854
  }
41646
- if (options2?.queueMutation || options2?.queueMutations?.length) {
41855
+ if (options2?.reviewRunIds?.length || options2?.queueMutation || options2?.queueMutations?.length) {
41647
41856
  throw error;
41648
41857
  }
41649
41858
  let fallbackError = null;
@@ -41742,6 +41951,7 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
41742
41951
  if (isStorageQuotaError(error)) {
41743
41952
  emitStorageQuotaFailure(error, null);
41744
41953
  }
41954
+ if (persistenceOptions?.reviewRunIds?.length) throw error;
41745
41955
  appendPlainLog(tx(`Failed to save session state: ${formatStateSaveError(error)}`, `\u4FDD\u5B58\u4F1A\u8BDD\u72B6\u6001\u5931\u8D25\uFF1A${formatStateSaveError(error)}`));
41746
41956
  }).finally(() => {
41747
41957
  if (saveStateInFlightPromise === runPromise) {
@@ -41788,6 +41998,7 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
41788
41998
  ...current || {},
41789
41999
  ...incoming,
41790
42000
  queueMutation: void 0,
42001
+ reviewRunIds: Array.from(/* @__PURE__ */ new Set([...current?.reviewRunIds || [], ...incoming?.reviewRunIds || []])),
41791
42002
  queueMutations,
41792
42003
  deletedSessionIds: Array.from(/* @__PURE__ */ new Set([
41793
42004
  ...current?.deletedSessionIds || [],
@@ -42654,11 +42865,9 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
42654
42865
  status: "running",
42655
42866
  detail: { [tr("detailWillUndo")]: trackedUndo ? formatTrackedChangeFiles(run.undoTrackedChanges) : formatTrackedUndoFiles(run) }
42656
42867
  });
42657
- let result;
42658
- let trackedUndoPostFilesAtDispatch;
42659
42868
  try {
42660
- trackedUndoPostFilesAtDispatch = buildTrackedUndoPostFiles(run);
42661
- result = await callPageBridge("rejectTrackedChanges", {
42869
+ const trackedUndoPostFilesAtDispatch = buildTrackedUndoPostFiles(run);
42870
+ const result = await callPageBridge("rejectTrackedChanges", {
42662
42871
  trackedChanges: run.undoTrackedChanges || [],
42663
42872
  expectedFiles: run.undoExpectedFiles || [],
42664
42873
  postFiles: trackedUndoPostFilesAtDispatch,
@@ -42669,36 +42878,37 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
42669
42878
  // `aborted_project_changed` and the document is left untouched.
42670
42879
  runProjectId: getRunProjectIdForWriteback(run)
42671
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);
42672
42906
  } finally {
42673
42907
  if (lifecycleReject) {
42674
42908
  trackedChangeInFlight.delete(runId);
42909
+ refreshRunCardControls(runId);
42675
42910
  }
42676
42911
  }
42677
- writebackOrchestrator.invalidateMirrorAfterUndo(runId, getRunProjectIdForWriteback(run), result);
42678
- appendRunRecordEvent(runId, {
42679
- 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 }),
42680
- status: result.skipped?.length ? "failed" : "completed",
42681
- detail: {
42682
- [trackedUndo ? tr("detailRejected") : tr("detailUndone")]: (result.applied || []).map((item) => ({
42683
- [tr("detailFile")]: item.trackedChange?.path || tr("unknownFile"),
42684
- [tr("detailRecord")]: item.trackedChange?.label || item.trackedChange?.id || item.trackedChange?.key
42685
- })),
42686
- [tr("detailSkipped")]: (result.skipped || []).map((item) => ({
42687
- [tr("detailFile")]: item.trackedChange?.path || tr("unknownFile"),
42688
- [tr("detailRecord")]: item.trackedChange?.label || item.trackedChange?.id || item.trackedChange?.key || "",
42689
- [tr("detailReason")]: formatBridgeResultReason(item.result, item.trackedChange?.path)
42690
- }))
42691
- }
42692
- });
42693
- if (lifecycleReject) {
42694
- WritebackSettlement.attachUndoNotVerifiedFailure(run, result, {
42695
- buildFailure: buildContentFailure,
42696
- postFiles: trackedUndoPostFilesAtDispatch
42697
- });
42698
- applyTrackedChangeSettlement(runId, "reject", result);
42699
- return;
42700
- }
42701
- applyLegacyUndoSettlement(runId, result.skipped?.length ? "partial" : "applied", result);
42702
42912
  }
42703
42913
  async function acceptRun(runId) {
42704
42914
  const run = findRunRecord(runId);
@@ -42722,50 +42932,50 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
42722
42932
  status: "running",
42723
42933
  detail: { [tr("detailAccepted")]: formatTrackedChangeFiles(run.undoTrackedChanges) }
42724
42934
  });
42725
- let result;
42726
42935
  try {
42727
- result = await callPageBridge("acceptTrackedChanges", {
42936
+ const result = await callPageBridge("acceptTrackedChanges", {
42728
42937
  trackedChanges: run.undoTrackedChanges || [],
42729
42938
  expectedFiles: run.undoExpectedFiles || [],
42730
42939
  postFiles: buildTrackedUndoPostFiles(run),
42731
42940
  appliedOperations: Array.isArray(run.appliedOperations) ? run.appliedOperations : [],
42732
42941
  runProjectId: getRunProjectIdForWriteback(run)
42733
42942
  });
42734
- } finally {
42735
- trackedChangeInFlight.delete(runId);
42736
- }
42737
- appendAcceptDiagnosticEvents(runId, Array.isArray(result.diagnostics) ? result.diagnostics : []);
42738
- appendRunRecordEvent(runId, {
42739
- title: tr("runAcceptTrackedResult", { applied: result.applied?.length || 0, skipped: result.skipped?.length || 0 }),
42740
- status: result.skipped?.length ? "failed" : "completed",
42741
- detail: {
42742
- [tr("detailAccepted")]: (result.applied || []).map((item) => ({
42743
- [tr("detailFile")]: item.trackedChange?.path || tr("unknownFile"),
42744
- [tr("detailRecord")]: item.trackedChange?.label || item.trackedChange?.id || item.trackedChange?.key
42745
- })),
42746
- [tr("detailSkipped")]: (result.skipped || []).map((item) => ({
42747
- [tr("detailFile")]: item.trackedChange?.path || tr("unknownFile"),
42748
- [tr("detailRecord")]: item.trackedChange?.label || item.trackedChange?.id || item.trackedChange?.key || "",
42749
- [tr("detailReason")]: formatBridgeResultReason(item.result, item.trackedChange?.path)
42750
- }))
42751
- }
42752
- });
42753
- if (result.ok === false) {
42943
+ appendAcceptDiagnosticEvents(runId, Array.isArray(result.diagnostics) ? result.diagnostics : []);
42754
42944
  appendRunRecordEvent(runId, {
42755
- title: tr("runAcceptTrackedFailed"),
42756
- status: "failed",
42945
+ title: tr("runAcceptTrackedResult", { applied: result.applied?.length || 0, skipped: result.skipped?.length || 0 }),
42946
+ status: result.skipped?.length ? "failed" : "completed",
42757
42947
  detail: {
42758
- [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
+ }))
42759
42957
  }
42760
42958
  });
42761
- if (!WritebackSettlement.isSuccessfulTrackedChangeSettlement(result)) {
42762
- 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
+ }
42763
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);
42764
42978
  }
42765
- WritebackSettlement.attachAcceptNotVerifiedFailure(run, result, {
42766
- buildFailure: buildContentFailure
42767
- });
42768
- applyTrackedChangeSettlement(runId, "accept", result);
42769
42979
  }
42770
42980
  function getRunProjectIdForWriteback(run) {
42771
42981
  return typeof run?.runProjectId === "string" && run.runProjectId ? run.runProjectId : "";
@@ -42796,7 +43006,7 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
42796
43006
  FailureReasons
42797
43007
  );
42798
43008
  }
42799
- function applyTrackedChangeSettlement(runId, kind, result) {
43009
+ async function applyTrackedChangeSettlement(runId, kind, result) {
42800
43010
  const run = findRunRecord(runId);
42801
43011
  const settlement = WritebackSettlement.settleTrackedChangeLifecycle({
42802
43012
  kind,
@@ -42805,12 +43015,14 @@ ${tx("Writeback note: Codex could not confirm any Overleaf write result entries,
42805
43015
  failureReasons: FailureReasons
42806
43016
  });
42807
43017
  if (settlement.decision === "blocked") {
42808
- refreshRunCardControls(runId);
42809
43018
  return;
42810
43019
  }
42811
- Object.assign(run, WritebackSettlement.applySettlementTransition(run, settlement));
42812
- saveStateSoon();
42813
- refreshRunCardControls(runId);
43020
+ await Modules.ScopedPersistenceCoordinator.applyReviewTransition({
43021
+ getState: () => state,
43022
+ runId,
43023
+ transition: WritebackSettlement.applySettlementTransition(run, settlement),
43024
+ persist: flushQueuedSaveState
43025
+ });
42814
43026
  }
42815
43027
  function getRunUndoCount(run) {
42816
43028
  if (!run) {