overleaf-review 0.6.1 → 0.6.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.
package/README.md CHANGED
@@ -339,6 +339,17 @@ project, verifies consolidation and protected review state, and checks that exis
339
339
  were unchanged. It does not require or spoof another account. Foreign-author preservation is also
340
340
  covered by the upstream model check. Never run this probe against a manuscript project.
341
341
 
342
+ From v0.6.2, rejection and underlying-text reconstruction account for insertions and deletions
343
+ at the same position, including multiple co-located deletions. The undo first restores selected
344
+ deletions, then removes insertions at their adjusted positions; exact-text and range verification
345
+ remain mandatory. Recreate unsubmitted plans from a fresh snapshot after upgrading, since the
346
+ operation ordering has changed. Do not re-plan to bypass an uncertain earlier submission.
347
+
348
+ `npm run probe:rejection-model` checks this ordering against pinned upstream range logic in both
349
+ tracking modes, including consolidation and reviewed paragraph rewrites. Add `-- --backup <file>`
350
+ to check a saved full snapshot (or a plan containing `backup`) offline. The backup stays local;
351
+ the probe fetches only public upstream code and never connects to an Overleaf project.
352
+
342
353
  ### Push overrides and submission
343
354
 
344
355
  Use `--allow-overlap` only after inspecting those tracked changes. `--unsafe-no-base` is a deliberate
@@ -1285,17 +1285,26 @@ function applyUndo(text, op, changeId) {
1285
1285
  }
1286
1286
  function buildRejectionPlan(currentText, ranges, requestedIds) {
1287
1287
  const requested = new Set(uniqueChangeIds(requestedIds));
1288
- const fragments = ranges.filter((range) => requested.has(range.id));
1289
- fragments.sort((a, b) => b.op.p - a.op.p);
1288
+ const fragments = ranges.map((range, index) => ({ range, index })).filter(({ range }) => requested.has(range.id)).map((fragment) => ({ ...fragment, inverse: inverseOf(fragment.range) }));
1289
+ fragments.sort((a, b) => Number(b.range.op.d !== void 0) - Number(a.range.op.d !== void 0) || b.range.op.p - a.range.op.p || a.index - b.index);
1290
+ const restorations = fragments.filter((fragment) => "i" in fragment.inverse);
1290
1291
  const operations = [];
1291
1292
  let expectedText = currentText;
1292
- for (const range of fragments) {
1293
- const inverse = inverseOf(range);
1293
+ let restorePosition = -1;
1294
+ let restoredAtPosition = 0;
1295
+ for (const { range, inverse: originalInverse } of fragments) {
1296
+ if ("i" in originalInverse && originalInverse.p !== restorePosition) {
1297
+ restorePosition = originalInverse.p;
1298
+ restoredAtPosition = 0;
1299
+ }
1300
+ const shift = "d" in originalInverse ? restorations.reduce((sum, restored) => sum + (restored.range.op.p <= range.op.p ? restored.range.op.d.length : 0), 0) : restoredAtPosition;
1301
+ const inverse = { ...originalInverse, p: originalInverse.p + shift };
1294
1302
  expectedText = applyUndo(expectedText, inverse, range.id);
1295
1303
  operations.push(inverse);
1304
+ if ("i" in inverse) restoredAtPosition += inverse.i.length;
1296
1305
  }
1297
1306
  return {
1298
- changeIds: changeIdsInRanges(fragments),
1307
+ changeIds: changeIdsInRanges(fragments.map((fragment) => fragment.range)),
1299
1308
  fragmentCount: fragments.length,
1300
1309
  operations,
1301
1310
  expectedText
@@ -2299,7 +2308,7 @@ ${doc.localPath} \u2192 ${doc.docPath} (v${doc.liveVersion}): ${doc.ops.length}
2299
2308
  }
2300
2309
  async function push(opts) {
2301
2310
  validatePushOptions(opts);
2302
- const { isReviewedManifest, createReviewedFilePlan, applyReviewedFilePlan, REVIEWED_FILE_KIND } = await import("./reviewed-replace-4PCULETF.js");
2311
+ const { isReviewedManifest, createReviewedFilePlan, applyReviewedFilePlan, REVIEWED_FILE_KIND } = await import("./reviewed-replace-CW447HNG.js");
2303
2312
  if (opts.edits && isReviewedManifest(JSON.parse(readFileSync6(workspaceReadPath(opts.edits), "utf8")))) {
2304
2313
  await createReviewedFilePlan(opts);
2305
2314
  return;
package/dist/cli.js CHANGED
@@ -39,7 +39,7 @@ import {
39
39
  uploadFile,
40
40
  validateSession,
41
41
  workspaceWritePath
42
- } from "./chunk-JRXROCS6.js";
42
+ } from "./chunk-FBISFRYR.js";
43
43
 
44
44
  // src/commands/pull.ts
45
45
  import { writeFileSync, mkdirSync } from "fs";
@@ -34,7 +34,7 @@ import {
34
34
  workspaceRelativePath,
35
35
  workspaceWritePath,
36
36
  writeJsonAtomic
37
- } from "./chunk-JRXROCS6.js";
37
+ } from "./chunk-FBISFRYR.js";
38
38
 
39
39
  // src/commands/reviewed-replace.ts
40
40
  import { readFileSync } from "fs";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "overleaf-review",
3
- "version": "0.6.1",
3
+ "version": "0.6.2",
4
4
  "description": "The missing review layer for Overleaf's Git bridge — sync comments and tracked changes between Overleaf and your local repo.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -44,6 +44,7 @@
44
44
  "probe:review-live": "tsx src/probes/05-review-live.ts",
45
45
  "probe:reviewed-model": "tsx src/probes/06-reviewed-model.ts",
46
46
  "probe:reviewed-live": "tsx src/probes/07-reviewed-live.ts",
47
+ "probe:rejection-model": "tsx src/probes/08-rejection-model.ts",
47
48
  "typecheck": "tsc --noEmit",
48
49
  "test": "tsx --test test/*.test.ts",
49
50
  "check": "npm run typecheck && npm test && npm run build"