codex-overleaf-link 2.3.0 → 2.3.2

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 (50) hide show
  1. package/README.md +23 -23
  2. package/extension/bootstrap/manifest.template.json +1 -1
  3. package/extension/src/background.js +20 -0
  4. package/extension/src/content/assetTransferBroker.js +169 -0
  5. package/extension/src/content/composerPanel.js +0 -2
  6. package/extension/src/content/contentRuntime.js +135 -111
  7. package/extension/src/content/generated/content.bundle.js +1066 -345
  8. package/extension/src/content/generated/content.bundle.meta.json +7 -3
  9. package/extension/src/content/legacyGlobalRegistry.js +3 -0
  10. package/extension/src/content/markdownText.js +12 -10
  11. package/extension/src/content/mathText.js +155 -9
  12. package/extension/src/content/modelPicker.js +2 -2
  13. package/extension/src/content/modelPickerSupport.js +3 -3
  14. package/extension/src/content/nativeCompatibilityController.js +2 -2
  15. package/extension/src/content/otWarmMirror.js +1 -1
  16. package/extension/src/content/pageBridgeClient.js +1 -0
  17. package/extension/src/content/runResultActions.js +114 -0
  18. package/extension/src/content/runTimelineView.js +23 -0
  19. package/extension/src/content/sessionForkController.js +120 -0
  20. package/extension/src/content/writebackController.js +3 -0
  21. package/extension/src/content/writebackOrchestrator.js +5 -50
  22. package/extension/src/page/binaryAssetUploader.js +343 -0
  23. package/extension/src/page/trackedChangesLifecycle.js +12 -4
  24. package/extension/src/page/writebackRouter.js +6 -2
  25. package/extension/src/pageBridge.js +30 -1
  26. package/extension/src/shared/agentTranscript.js +10 -3
  27. package/extension/src/shared/compatibility.js +4 -2
  28. package/extension/src/shared/i18n.js +18 -4
  29. package/extension/src/shared/models.js +13 -2
  30. package/extension/src/shared/pageRpcContract.js +6 -2
  31. package/extension/src/shared/runExecutionSnapshot.js +14 -4
  32. package/extension/src/shared/runExecutionSnapshotCodec.js +5 -2
  33. package/extension/src/shared/scopedPersistenceQueuePolicy.js +20 -1
  34. package/extension/src/shared/scopedPersistenceReceipt.js +60 -0
  35. package/extension/src/shared/scopedPersistenceTransaction.js +50 -47
  36. package/extension/src/shared/sessionState.js +58 -15
  37. package/extension/src/shared/storageDb.js +6 -5
  38. package/extension/src/shared/storageRunActions.js +11 -1
  39. package/extension/src/shared/writebackOperationEvidence.js +16 -6
  40. package/extension/src/shared/writebackSettlement.js +28 -6
  41. package/extension/styles/panel.css +79 -8
  42. package/native-host/src/codexModels.js +6 -2
  43. package/native-host/src/codexPromptAssembly.js +1 -1
  44. package/native-host/src/codexSessionRunner.js +5 -1
  45. package/native-host/src/codexThreadFork.js +103 -0
  46. package/native-host/src/mirrorWorkspace.js +5 -19
  47. package/native-host/src/nativeAssetTransfer.js +183 -0
  48. package/native-host/src/taskRunnerRuntime.js +33 -81
  49. package/native-host/src/updateManager.js +36 -1
  50. package/package.json +1 -1
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  <h1>Codex Overleaf Link</h1>
4
4
  <p><strong>Empower Overleaf with Codex.</strong></p>
5
5
  <p>
6
- <img src="https://img.shields.io/badge/version-2.3.0-blue" alt="version">
6
+ <img src="https://img.shields.io/badge/version-2.3.2-blue" alt="version">
7
7
  <img src="https://img.shields.io/badge/platform-macOS%20%2F%20Windows%20%2F%20Linux-lightgrey" alt="platform">
8
8
  <img src="https://img.shields.io/badge/chrome-MV3-green" alt="chrome manifest v3">
9
9
  <img src="https://img.shields.io/badge/node-%3E%3D20-brightgreen" alt="node version">
@@ -38,14 +38,14 @@ One command installs the managed native host **and** managed extension runtime.
38
38
  macOS / Linux:
39
39
 
40
40
  ```bash
41
- CODEX_OVERLEAF_REF=v2.3.0 bash -c "$(curl -fsSL https://raw.githubusercontent.com/Ghqqqq/codex-overleaf-link/v2.3.0/install.sh)"
41
+ CODEX_OVERLEAF_REF=v2.3.2 bash -c "$(curl -fsSL https://raw.githubusercontent.com/Ghqqqq/codex-overleaf-link/v2.3.2/install.sh)"
42
42
  ```
43
43
 
44
44
  Windows PowerShell:
45
45
 
46
46
  ```powershell
47
- iwr https://raw.githubusercontent.com/Ghqqqq/codex-overleaf-link/v2.3.0/install.ps1 -OutFile install.ps1
48
- $env:CODEX_OVERLEAF_REF='v2.3.0'
47
+ iwr https://raw.githubusercontent.com/Ghqqqq/codex-overleaf-link/v2.3.2/install.ps1 -OutFile install.ps1
48
+ $env:CODEX_OVERLEAF_REF='v2.3.2'
49
49
  powershell -ExecutionPolicy Bypass -File install.ps1
50
50
  ```
51
51
 
@@ -56,7 +56,7 @@ Then, in the `chrome://extensions` tab the script opened: enable **Developer mod
56
56
  `npm exec` installs the same managed native host and extension runtime without keeping a source checkout. Use it if you prefer a pinned npm package.
57
57
 
58
58
  ```bash
59
- npm exec --yes codex-overleaf-link@2.3.0 -- install-managed
59
+ npm exec --yes codex-overleaf-link@2.3.2 -- install-managed
60
60
  ```
61
61
 
62
62
  Then, in `chrome://extensions`, enable **Developer mode**, click **Load unpacked**, and select the managed extension path printed by the command. The Release extension zip remains available for explicitly unmanaged/manual installations.
@@ -88,9 +88,9 @@ npm installs, updates, and uninstalls the coordinated managed extension/native p
88
88
 
89
89
  | Action | Command |
90
90
  |--------|---------|
91
- | Install / update | `npm exec --yes codex-overleaf-link@2.3.0 -- install-managed` |
92
- | Diagnose | `npm exec --yes codex-overleaf-link@2.3.0 -- doctor` |
93
- | Uninstall | `npm exec --yes codex-overleaf-link@2.3.0 -- uninstall-managed` |
91
+ | Install / update | `npm exec --yes codex-overleaf-link@2.3.2 -- install-managed` |
92
+ | Diagnose | `npm exec --yes codex-overleaf-link@2.3.2 -- doctor` |
93
+ | Uninstall | `npm exec --yes codex-overleaf-link@2.3.2 -- uninstall-managed` |
94
94
 
95
95
  Use `--extension-id <chrome-extension-id>` only for a custom/dev unpacked extension id that differs from the official bundled id.
96
96
 
@@ -104,13 +104,13 @@ Managed installations discover signed stable updates automatically and apply the
104
104
 
105
105
  v2.2.0 introduces Bootstrap protocol 2. Existing v2.1.x managed installations must run the pinned `install-managed` command once, then reload the extension and Overleaf. Later protocol-2 releases use the in-product signed updater for ordinary runtime, style, vendor, and Native Host feature changes.
106
106
 
107
- The v2.3.0 GitHub Release contains:
107
+ The v2.3.2 GitHub Release contains:
108
108
 
109
- - `codex-overleaf-link-extension-v2.3.0.zip`: loadable Chrome extension package for manual unpacked installation.
110
- - `codex-overleaf-native-host-v2.3.0.tar.gz`: native host runtime files used by the installer and release verification.
111
- - `codex-overleaf-link-2.3.0.tgz`: npm native host CLI package for pinned install, doctor, and uninstall flows.
112
- - `install.sh`: release-pinned macOS / Linux installer that defaults to `v2.3.0` when run directly from the release artifact.
113
- - `install.ps1`: release-pinned Windows PowerShell installer that defaults to `v2.3.0` when run directly from the release artifact.
109
+ - `codex-overleaf-link-extension-v2.3.2.zip`: loadable Chrome extension package for manual unpacked installation.
110
+ - `codex-overleaf-native-host-v2.3.2.tar.gz`: native host runtime files used by the installer and release verification.
111
+ - `codex-overleaf-link-2.3.2.tgz`: npm native host CLI package for pinned install, doctor, and uninstall flows.
112
+ - `install.sh`: release-pinned macOS / Linux installer that defaults to `v2.3.2` when run directly from the release artifact.
113
+ - `install.ps1`: release-pinned Windows PowerShell installer that defaults to `v2.3.2` when run directly from the release artifact.
114
114
  - `uninstall-native-host.mjs`: native host uninstaller that removes the Chrome Native Messaging manifest, bridge executable, and runtime copy.
115
115
  - `nativeHostPlatform.js`, `manifest.js`, `runtimeInstaller.js`: helper files required by the loose uninstaller asset.
116
116
  - `SHA256SUMS` and `release-manifest.json`: checksum and artifact metadata for release verification.
@@ -121,7 +121,7 @@ The v2.3.0 GitHub Release contains:
121
121
  Remove the native host (use `--browser chromium` on Linux Chromium):
122
122
 
123
123
  ```bash
124
- npm exec --yes codex-overleaf-link@2.3.0 -- uninstall-managed
124
+ npm exec --yes codex-overleaf-link@2.3.2 -- uninstall-managed
125
125
  ```
126
126
 
127
127
  The same command works on Windows PowerShell. If you installed from a manual checkout or source installer, you can also run `npm run uninstall:native` inside the repo, use `node ~/.codex-overleaf/source/scripts/uninstall-native-host.mjs` on macOS / Linux, or use `node $env:LOCALAPPDATA\CodexOverleaf\source\scripts\uninstall-native-host.mjs` on Windows PowerShell.
@@ -156,13 +156,13 @@ Then remove the extension from `chrome://extensions`. To delete local data: on m
156
156
  Linux Chromium install or update:
157
157
 
158
158
  ```bash
159
- CODEX_OVERLEAF_REF=v2.3.0 bash -c "$(curl -fsSL https://raw.githubusercontent.com/Ghqqqq/codex-overleaf-link/v2.3.0/install.sh)" -- --browser chromium
159
+ CODEX_OVERLEAF_REF=v2.3.2 bash -c "$(curl -fsSL https://raw.githubusercontent.com/Ghqqqq/codex-overleaf-link/v2.3.2/install.sh)" -- --browser chromium
160
160
  ```
161
161
 
162
162
  Linux Chromium uninstall:
163
163
 
164
164
  ```bash
165
- npm exec --yes codex-overleaf-link@2.3.0 -- uninstall-managed --browser chromium
165
+ npm exec --yes codex-overleaf-link@2.3.2 -- uninstall-managed --browser chromium
166
166
  ```
167
167
 
168
168
  ## Features
@@ -298,7 +298,7 @@ Composer attachments are turn-scoped Codex context. Limits are 8 attachments per
298
298
  Re-run any [native host installer](#install), reload the extension in `chrome://extensions`, then refresh the Overleaf tab. This also fixes extension/native version mismatch and native protocol mismatch.
299
299
 
300
300
  ```bash
301
- npm exec --yes codex-overleaf-link@2.3.0 -- install-managed
301
+ npm exec --yes codex-overleaf-link@2.3.2 -- install-managed
302
302
  ```
303
303
 
304
304
  **The Windows popup or panel shows a Bash recovery command**
@@ -347,8 +347,8 @@ Use this matrix for release-candidate signoff and compatibility reports. Record
347
347
  | Browser/channel/version | Google Chrome channel and version. | Google Chrome channel and version. | Google Chrome channel and version. | Chromium channel/package and version. |
348
348
  | Install mode | Manual unpacked extension from GitHub Release zip or checkout. | Manual unpacked extension from GitHub Release zip or checkout. | Manual unpacked extension from GitHub Release zip or checkout. | Manual unpacked extension from GitHub Release zip or checkout; native host installed with `--browser chromium`. |
349
349
  | Extension id | Bundled id `illdpneeeopfffmiepaejglgmhpmdhdc`, or actual custom id passed with `--extension-id`. | Bundled id `illdpneeeopfffmiepaejglgmhpmdhdc`, or actual custom id passed with `--extension-id`. | Bundled id `illdpneeeopfffmiepaejglgmhpmdhdc`, or actual custom id passed with `--extension-id`. | Bundled id `illdpneeeopfffmiepaejglgmhpmdhdc`, or actual custom id passed with `--extension-id`. |
350
- | Installer/update command | `npm exec --yes codex-overleaf-link@2.3.0 -- install-managed` | `npm exec --yes codex-overleaf-link@2.3.0 -- install-managed` | `npm exec --yes codex-overleaf-link@2.3.0 -- install-managed` | `npm exec --yes codex-overleaf-link@2.3.0 -- install-managed --browser chromium` |
351
- | Uninstall command | `npm exec --yes codex-overleaf-link@2.3.0 -- uninstall-managed` | `npm exec --yes codex-overleaf-link@2.3.0 -- uninstall-managed` | `npm exec --yes codex-overleaf-link@2.3.0 -- uninstall-managed` | `npm exec --yes codex-overleaf-link@2.3.0 -- uninstall-managed --browser chromium` |
350
+ | Installer/update command | `npm exec --yes codex-overleaf-link@2.3.2 -- install-managed` | `npm exec --yes codex-overleaf-link@2.3.2 -- install-managed` | `npm exec --yes codex-overleaf-link@2.3.2 -- install-managed` | `npm exec --yes codex-overleaf-link@2.3.2 -- install-managed --browser chromium` |
351
+ | Uninstall command | `npm exec --yes codex-overleaf-link@2.3.2 -- uninstall-managed` | `npm exec --yes codex-overleaf-link@2.3.2 -- uninstall-managed` | `npm exec --yes codex-overleaf-link@2.3.2 -- uninstall-managed` | `npm exec --yes codex-overleaf-link@2.3.2 -- uninstall-managed --browser chromium` |
352
352
  | Manifest/registry path | `~/Library/Application Support/Google/Chrome/NativeMessagingHosts/com.codex.overleaf.json` | `HKCU\Software\Google\Chrome\NativeMessagingHosts\com.codex.overleaf` -> `%LOCALAPPDATA%\CodexOverleaf\native-host-runtime\com.codex.overleaf.json` | `~/.config/google-chrome/NativeMessagingHosts/com.codex.overleaf.json` | `~/.config/chromium/NativeMessagingHosts/com.codex.overleaf.json` |
353
353
  | Bridge/runtime/source path | Bridge `~/.codex-overleaf/codex-overleaf-bridge`; runtime `~/.codex-overleaf/native-host-runtime`; source `~/.codex-overleaf/source`. | Bridge `%LOCALAPPDATA%\CodexOverleaf\codex-overleaf-bridge.cmd`; runtime `%LOCALAPPDATA%\CodexOverleaf\native-host-runtime`; source `%LOCALAPPDATA%\CodexOverleaf\source`. | Bridge `~/.codex-overleaf/codex-overleaf-bridge`; runtime `~/.codex-overleaf/native-host-runtime`; source `~/.codex-overleaf/source`. | Bridge `~/.codex-overleaf/codex-overleaf-bridge`; runtime `~/.codex-overleaf/native-host-runtime`; source `~/.codex-overleaf/source`. |
354
354
  | Node/Git/Codex/TeX | Node.js >= 20; Git; Codex CLI installed and logged in; TeX optional. | Node.js >= 20; Git; Codex CLI installed and logged in; TeX optional. | Node.js >= 20; Git; Codex CLI installed and logged in; TeX optional. | Node.js >= 20; Git; Codex CLI installed and logged in; TeX optional. |
@@ -384,7 +384,7 @@ Contributions are welcome. Please open an issue before submitting large changes
384
384
  The recommended v1.9 installation owns one stable unpacked-extension directory and a versioned native-host directory:
385
385
 
386
386
  ```bash
387
- npm exec --yes codex-overleaf-link@2.3.0 -- install-managed
387
+ npm exec --yes codex-overleaf-link@2.3.2 -- install-managed
388
388
  ```
389
389
 
390
390
  Load the printed extension path once from `chrome://extensions`. Starting with the next signed stable release, Codex Overleaf Link checks GitHub Releases automatically, downloads and verifies the coordinated extension/native-host bundle, waits until every connected Overleaf tab is saved and idle, and then updates both components together. A failed health check restores the previous version automatically.
@@ -394,11 +394,11 @@ The stable updater ignores drafts and prereleases, performs no telemetry, and ca
394
394
  The legacy native-only installer remains available for source or unmanaged extension directories:
395
395
 
396
396
  ```bash
397
- npm exec --yes codex-overleaf-link@2.3.0 -- install-native
397
+ npm exec --yes codex-overleaf-link@2.3.2 -- install-native
398
398
  ```
399
399
 
400
400
  Legacy native-only installations can be removed with:
401
401
 
402
402
  ```bash
403
- npm exec --yes codex-overleaf-link@2.3.0 -- uninstall-native
403
+ npm exec --yes codex-overleaf-link@2.3.2 -- uninstall-native
404
404
  ```
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "Codex Overleaf Link",
4
- "version": "2.3.0",
4
+ "version": "2.3.2",
5
5
  "description": "Connect the active Overleaf project to a local Codex bridge.",
6
6
  "icons": {
7
7
  "16": "assets/icons/icon16.png",
@@ -21,6 +21,7 @@ if (!globalThis.CodexOverleafNativeRequestIdentity) {
21
21
 
22
22
  const HOST_NAME = 'com.codex.overleaf';
23
23
  const MANAGED_UPDATE_STATE_KEY = 'codex-overleaf-managed-update-state-v1';
24
+ const MANAGED_UPDATE_CONSENT_KEY = 'codex-overleaf-update-consent-v1';
24
25
  const MANAGED_UPDATE_TABS_KEY = 'codex-overleaf-managed-update-tabs-v1';
25
26
  const MANAGED_OVERLEAF_MATCHES = [
26
27
  'https://www.overleaf.com/project',
@@ -406,6 +407,25 @@ if (!globalThis.CodexOverleafNativeRequestIdentity) {
406
407
  }
407
408
 
408
409
  const transaction = response.result?.transaction || null;
410
+ if (transaction?.state === 'superseded') {
411
+ const activeVersion = response.result?.activeVersion || chrome.runtime.getManifest().version;
412
+ await chrome.storage.local.remove([
413
+ MANAGED_UPDATE_CONSENT_KEY,
414
+ MANAGED_UPDATE_TABS_KEY
415
+ ]);
416
+ await setManagedUpdateState({
417
+ state: 'idle',
418
+ managed: true,
419
+ currentVersion: activeVersion,
420
+ latestVersion: activeVersion,
421
+ transactionId: '',
422
+ blocker: '',
423
+ blockers: [],
424
+ code: '',
425
+ message: ''
426
+ });
427
+ return;
428
+ }
409
429
  const state = await getManagedUpdateState();
410
430
  const reconciled = ManagedUpdateProjection.reconcile(state, transaction, {
411
431
  currentVersion: chrome.runtime.getManifest().version
@@ -0,0 +1,169 @@
1
+ (function initCodexOverleafAssetTransferBroker(root, factory) {
2
+ const api = factory();
3
+ if (typeof module === 'object' && module.exports) module.exports = api;
4
+ if (root) root.CodexOverleafAssetTransferBroker = api;
5
+ })(typeof window !== 'undefined' ? window : globalThis, function assetTransferBrokerFactory() {
6
+ 'use strict';
7
+
8
+ const BINARY_TYPES = new Set(['binary-create', 'overwrite-binary']);
9
+
10
+ function create(deps = {}) {
11
+ const { callPageBridge, sendBackgroundNative } = deps;
12
+
13
+ async function applyOperations(input = {}) {
14
+ const results = [];
15
+ let textBatch = [];
16
+ async function flushTextBatch() {
17
+ if (!textBatch.length) return;
18
+ results.push(await callPageBridge('applyOperations', {
19
+ operations: textBatch,
20
+ baseFiles: input.baseFiles || [],
21
+ requireReviewing: input.requireReviewing === true,
22
+ requireEditing: input.requireEditing === true,
23
+ runProjectId: input.runProjectId || ''
24
+ }));
25
+ textBatch = [];
26
+ }
27
+ for (const operation of Array.isArray(input.operations) ? input.operations : []) {
28
+ if (!BINARY_TYPES.has(operation?.type)) {
29
+ textBatch.push(operation);
30
+ continue;
31
+ }
32
+ await flushTextBatch();
33
+ results.push(await applyBinaryOperation(operation, input));
34
+ }
35
+ await flushTextBatch();
36
+ return mergeApplyResults(results);
37
+ }
38
+
39
+ async function applyBinaryOperation(operation, input) {
40
+ const transferId = `asset_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
41
+ const assetRef = operation.assetRef || null;
42
+ const safeOperation = sanitizeOperation(operation);
43
+ let pageBegun = false;
44
+ try {
45
+ const begin = await callPageBridge('binaryUploadBegin', {
46
+ transferId,
47
+ path: operation.path,
48
+ size: Number(operation.size || assetRef?.size || 0),
49
+ sha256: assetRef?.sha256 || operation.sha256 || '',
50
+ mimeType: assetRef?.mimeType || operation.mimeType || 'application/octet-stream',
51
+ overwrite: operation.type === 'overwrite-binary',
52
+ runProjectId: input.runProjectId || ''
53
+ });
54
+ if (!begin?.ok) throw responseError(begin, 'binary_upload_begin_failed');
55
+ pageBegun = true;
56
+ if (assetRef?.token) {
57
+ await streamNativeAsset(assetRef, transferId, input.runProjectId || '');
58
+ } else if (typeof operation.contentBase64 === 'string' && operation.contentBase64) {
59
+ const append = await callPageBridge('binaryUploadAppend', {
60
+ transferId,
61
+ offset: 0,
62
+ contentBase64: operation.contentBase64,
63
+ runProjectId: input.runProjectId || ''
64
+ });
65
+ if (!append?.ok) throw responseError(append, 'binary_upload_chunk_failed');
66
+ } else {
67
+ throw codedError('binary_asset_payload_missing', 'Binary asset payload is unavailable.');
68
+ }
69
+ const committed = await callPageBridge('binaryUploadCommit', {
70
+ transferId,
71
+ runProjectId: input.runProjectId || ''
72
+ });
73
+ if (!committed?.ok) throw responseError(committed, committed?.code || 'binary_upload_failed');
74
+ return { ok: true, applied: [{ operation: safeOperation, result: committed }], skipped: [] };
75
+ } catch (error) {
76
+ if (pageBegun) {
77
+ await callPageBridge('binaryUploadAbort', { transferId, runProjectId: input.runProjectId || '' }).catch(() => null);
78
+ }
79
+ const code = error.code || 'binary_upload_failed';
80
+ return {
81
+ ok: false,
82
+ applied: [],
83
+ skipped: [{
84
+ operation: safeOperation,
85
+ path: operation.path,
86
+ result: {
87
+ ok: false,
88
+ code,
89
+ reason: error.message || 'Binary asset upload failed.',
90
+ changedDocument: false,
91
+ failure: { code, stage: 'write', file: operation.path, changedDocument: false }
92
+ }
93
+ }]
94
+ };
95
+ } finally {
96
+ if (assetRef?.token) {
97
+ await sendBackgroundNative({ method: 'asset.release', params: { token: assetRef.token } }).catch(() => null);
98
+ }
99
+ }
100
+ }
101
+
102
+ async function streamNativeAsset(assetRef, transferId, runProjectId) {
103
+ let offset = 0;
104
+ while (offset < Number(assetRef.size || 0)) {
105
+ const response = await sendBackgroundNative({
106
+ method: 'asset.readChunk',
107
+ params: { token: assetRef.token, offset, length: assetRef.chunkSize || 196608 }
108
+ });
109
+ if (!response?.ok) throw responseError(response, response?.error?.code || 'asset_chunk_read_failed');
110
+ const chunk = response.result || {};
111
+ if (chunk.offset !== offset || typeof chunk.contentBase64 !== 'string' || chunk.nextOffset <= offset) {
112
+ throw codedError('asset_chunk_sequence_invalid', 'Native asset chunks arrived out of sequence.');
113
+ }
114
+ const appended = await callPageBridge('binaryUploadAppend', {
115
+ transferId,
116
+ offset,
117
+ contentBase64: chunk.contentBase64,
118
+ runProjectId
119
+ });
120
+ if (!appended?.ok) throw responseError(appended, appended?.code || 'binary_upload_chunk_failed');
121
+ offset = chunk.nextOffset;
122
+ if (chunk.eof) break;
123
+ }
124
+ if (offset !== Number(assetRef.size || 0)) {
125
+ throw codedError('asset_transfer_incomplete', 'Native asset transfer ended before the expected size.');
126
+ }
127
+ }
128
+
129
+ return { applyOperations };
130
+ }
131
+
132
+ function mergeApplyResults(results) {
133
+ const merged = { ok: true, applied: [], skipped: [], trackedChanges: [] };
134
+ for (const result of results) {
135
+ merged.applied.push(...(Array.isArray(result?.applied) ? result.applied : []));
136
+ merged.skipped.push(...(Array.isArray(result?.skipped) ? result.skipped : []));
137
+ // Text writeback returns the Reviewing references captured by the page
138
+ // bridge. They are lifecycle data, not diagnostics: dropping them makes
139
+ // recordUndoFromApply classify a successful Track write as legacy Undo,
140
+ // which hides Accept and prevents the lifecycle from surviving reload.
141
+ merged.trackedChanges.push(...(Array.isArray(result?.trackedChanges) ? result.trackedChanges : []));
142
+ if (result?.ok !== true) merged.ok = false;
143
+ }
144
+ if (merged.skipped.length) merged.ok = false;
145
+ return merged;
146
+ }
147
+
148
+ function sanitizeOperation(operation) {
149
+ const { contentBase64, assetRef, ...safe } = operation || {};
150
+ if (assetRef) {
151
+ safe.asset = {
152
+ path: assetRef.path || safe.path,
153
+ size: assetRef.size,
154
+ sha256: assetRef.sha256,
155
+ mimeType: assetRef.mimeType
156
+ };
157
+ }
158
+ return safe;
159
+ }
160
+
161
+ function responseError(response, fallbackCode) {
162
+ return codedError(
163
+ response?.code || response?.error?.code || fallbackCode,
164
+ response?.reason || response?.error?.message || response?.error || 'Asset transfer failed.'
165
+ );
166
+ }
167
+ function codedError(code, message) { const error = new Error(String(message || code)); error.code = code; return error; }
168
+ return { create, mergeApplyResults };
169
+ });
@@ -29,12 +29,10 @@
29
29
  <span class="codex-mode-label" data-i18n="mode">Mode</span>
30
30
  <div class="codex-mode-switch" role="group" aria-label="Write mode">
31
31
  <button type="button" data-mode-choice="ask" aria-pressed="false" title="Read and analyze only. Do not write to Overleaf.">Ask</button>
32
- <button type="button" data-mode-choice="confirm" aria-pressed="false" title="Show a change plan first, then write after approval.">Suggest</button>
33
32
  <button type="button" data-mode-choice="auto" aria-pressed="false" title="Write directly after authorization. Deletes still require confirmation.">Auto</button>
34
33
  </div>
35
34
  <select data-mode aria-label="Mode" hidden>
36
35
  <option value="ask">Ask</option>
37
- <option value="confirm">Suggest</option>
38
36
  <option value="auto">Auto</option>
39
37
  </select>
40
38
  </div>