@atlaskit/editor-plugin-show-diff 17.1.5 → 17.1.7

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 (40) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/cjs/pm-plugins/calculateDiff/calculateDiffDecorations.js +12 -1
  3. package/dist/cjs/pm-plugins/decorations/createAnchorDecorationWidgets.js +165 -32
  4. package/dist/cjs/pm-plugins/decorations/createBlockChangedDecoration.js +6 -2
  5. package/dist/cjs/pm-plugins/decorations/createChangedRowDecorationWidgets.js +5 -2
  6. package/dist/cjs/pm-plugins/decorations/createContributorTagWidget.js +54 -15
  7. package/dist/cjs/pm-plugins/decorations/createInlineChangedDecoration.js +6 -2
  8. package/dist/cjs/pm-plugins/decorations/createNodeChangedDecorationWidget.js +64 -13
  9. package/dist/cjs/pm-plugins/decorations/decorationKeys.js +8 -3
  10. package/dist/cjs/pm-plugins/decorations/utils/wrapBlockNodeViewStyles.js +8 -3
  11. package/dist/cjs/ui/IndicatorBar/IndicatorBarContentComponent.js +5 -3
  12. package/dist/es2019/pm-plugins/calculateDiff/calculateDiffDecorations.js +12 -1
  13. package/dist/es2019/pm-plugins/decorations/createAnchorDecorationWidgets.js +131 -12
  14. package/dist/es2019/pm-plugins/decorations/createBlockChangedDecoration.js +6 -2
  15. package/dist/es2019/pm-plugins/decorations/createChangedRowDecorationWidgets.js +5 -2
  16. package/dist/es2019/pm-plugins/decorations/createContributorTagWidget.js +54 -15
  17. package/dist/es2019/pm-plugins/decorations/createInlineChangedDecoration.js +6 -2
  18. package/dist/es2019/pm-plugins/decorations/createNodeChangedDecorationWidget.js +66 -16
  19. package/dist/es2019/pm-plugins/decorations/decorationKeys.js +7 -0
  20. package/dist/es2019/pm-plugins/decorations/utils/wrapBlockNodeViewStyles.js +8 -3
  21. package/dist/es2019/ui/IndicatorBar/IndicatorBarContentComponent.js +5 -3
  22. package/dist/esm/pm-plugins/calculateDiff/calculateDiffDecorations.js +12 -1
  23. package/dist/esm/pm-plugins/decorations/createAnchorDecorationWidgets.js +165 -32
  24. package/dist/esm/pm-plugins/decorations/createBlockChangedDecoration.js +6 -2
  25. package/dist/esm/pm-plugins/decorations/createChangedRowDecorationWidgets.js +5 -2
  26. package/dist/esm/pm-plugins/decorations/createContributorTagWidget.js +54 -15
  27. package/dist/esm/pm-plugins/decorations/createInlineChangedDecoration.js +6 -2
  28. package/dist/esm/pm-plugins/decorations/createNodeChangedDecorationWidget.js +67 -16
  29. package/dist/esm/pm-plugins/decorations/decorationKeys.js +8 -3
  30. package/dist/esm/pm-plugins/decorations/utils/wrapBlockNodeViewStyles.js +8 -3
  31. package/dist/esm/ui/IndicatorBar/IndicatorBarContentComponent.js +5 -3
  32. package/dist/types/pm-plugins/decorations/createAnchorDecorationWidgets.d.ts +14 -4
  33. package/dist/types/pm-plugins/decorations/createBlockChangedDecoration.d.ts +2 -1
  34. package/dist/types/pm-plugins/decorations/createChangedRowDecorationWidgets.d.ts +2 -1
  35. package/dist/types/pm-plugins/decorations/createContributorTagWidget.d.ts +24 -6
  36. package/dist/types/pm-plugins/decorations/createInlineChangedDecoration.d.ts +2 -1
  37. package/dist/types/pm-plugins/decorations/createNodeChangedDecorationWidget.d.ts +2 -1
  38. package/dist/types/pm-plugins/decorations/decorationKeys.d.ts +3 -1
  39. package/dist/types/showDiffPluginType.d.ts +1 -0
  40. package/package.json +2 -2
@@ -3,7 +3,7 @@ import { fg } from '@atlaskit/platform-feature-flags/fg';
3
3
  import { ContributorTagController } from '../../ui/ContributorTag/contributorTagController';
4
4
  import { buildCharsByOffset, isWhitespaceChar } from '../utils/charsByOffset';
5
5
  import { clampAnchorPosIntoCell } from './createAnchorDecorationWidgets';
6
- import { buildContributorTagDecorationSpec } from './decorationKeys';
6
+ import { AnchorTypeKey, buildAnchorDecorationKey, buildContributorTagDecorationSpec } from './decorationKeys';
7
7
 
8
8
  /** Marks the host element of one tag, so a tag can be found in the document by its own diff. */
9
9
  export const CONTRIBUTOR_TAG_HOST_ATTRIBUTE = 'data-contributor-tag-host';
@@ -162,13 +162,12 @@ const resolveTagAnchorPos = (doc, from, to) => {
162
162
  };
163
163
 
164
164
  /**
165
- * The position just before the code block enclosing `pos`, or `undefined` when nothing does.
165
+ * Position just before the code block enclosing `pos`, or `undefined` if there is none.
166
166
  *
167
- * `.code-block-content-wrapper` hides its block-axis overflow, so a host left inside the code has
168
- * its tag clipped away. Hosting it before the block puts the tag on the block's top-left corner,
169
- * where a whole-code-block change is tagged too (EDITOR-8766).
167
+ * Code blocks clip/scroll their own content, so a tag hosted inside one gets clipped away. Hoisting
168
+ * it before the block avoids that (EDITOR-9045, EDITOR-8766).
170
169
  */
171
- const resolveCodeBlockStart = (doc, pos) => {
170
+ export const resolveCodeBlockStart = (doc, pos) => {
172
171
  const $pos = doc.resolve(pos);
173
172
  for (let depth = $pos.depth; depth > 0; depth--) {
174
173
  if ($pos.node(depth).type.name === 'codeBlock') {
@@ -179,9 +178,41 @@ const resolveCodeBlockStart = (doc, pos) => {
179
178
  };
180
179
 
181
180
  /**
182
- * A widget hosting the contributor tag of one diff range, placed on the first visible character the
183
- * range highlights or, with `anchorAtRangeStart`, at `from` itself. Either way a host that would
184
- * land inside a code block is hoisted out in front of it; see `resolveCodeBlockStart`.
181
+ * If `pos` is inside a code block: a marker decoration to anchor the hoisted tag against, plus the
182
+ * position just before the block to host that tag at (EDITOR-9045).
183
+ */
184
+ export const resolveHoistedCodeBlockAnchor = (doc, pos, diffId) => {
185
+ const codeBlockStart = resolveCodeBlockStart(doc, pos);
186
+ if (codeBlockStart === undefined) {
187
+ return undefined;
188
+ }
189
+ const anchorName = buildAnchorDecorationKey({
190
+ diffId,
191
+ anchorType: AnchorTypeKey.tag
192
+ });
193
+ const marker = Decoration.widget(pos, () => {
194
+ const el = document.createElement('span');
195
+ el.style.setProperty('anchor-name', `--${anchorName}`);
196
+ return el;
197
+ }, {
198
+ key: `contributor-tag-anchor-${diffId}`,
199
+ side: -1,
200
+ marks: [],
201
+ ignoreSelection: true
202
+ });
203
+ return {
204
+ anchorName,
205
+ codeBlockStart,
206
+ marker
207
+ };
208
+ };
209
+
210
+ /**
211
+ * The contributor tag widget for one diff range, placed on the first visible character the range
212
+ * highlights — or, with `anchorAtRangeStart`, at `from` itself. A host inside a code block is
213
+ * hoisted in front of it instead, anchored via `resolveHoistedCodeBlockAnchor` (EDITOR-9045).
214
+ *
215
+ * Returns one decoration normally, two (marker + tag) when hoisted out of a code block.
185
216
  *
186
217
  * The tag is mounted in `toDOM` and taken down in `destroy`, so it lives exactly as long as the host
187
218
  * element ProseMirror drew it into — see `mountContributorTag`.
@@ -195,20 +226,27 @@ export const createContributorTagWidget = ({
195
226
  diffId,
196
227
  mountContext
197
228
  }) => {
229
+ var _hoisted$anchorName, _hoisted$codeBlockSta;
198
230
  if (!isContributorTagWidgetEnabled()) {
199
231
  return undefined;
200
232
  }
201
- const tagAnchorName = anchorName && supportsAnchorPositioning() ? anchorName : undefined;
202
233
 
203
234
  // Reassigned when ProseMirror redraws this decoration, which it may do more than once for the
204
235
  // same `Decoration` instance.
205
236
  let mount;
206
237
  const anchorPos = anchorAtRangeStart ? from : resolveTagAnchorPos(doc, from, to);
207
- // A change inside a code block is hoisted out of it; anything else keeps its own anchor.
208
- const codeBlockPos = resolveCodeBlockStart(doc, anchorPos);
209
- return Decoration.widget(
238
+ // A change inside a code block is hoisted out of it, anchored to its own marker; anything else
239
+ // keeps its own anchor.
240
+ const hoisted = resolveHoistedCodeBlockAnchor(doc, anchorPos, diffId);
241
+ const effectiveAnchorName = (_hoisted$anchorName = hoisted === null || hoisted === void 0 ? void 0 : hoisted.anchorName) !== null && _hoisted$anchorName !== void 0 ? _hoisted$anchorName : anchorName;
242
+ const tagAnchorName = effectiveAnchorName && supportsAnchorPositioning() ? effectiveAnchorName : undefined;
243
+ const decorations = [];
244
+ if (hoisted) {
245
+ decorations.push(hoisted.marker);
246
+ }
247
+ decorations.push(Decoration.widget(
210
248
  // Keep the host out of the table row's grid (EDITOR-8442).
211
- clampAnchorPosIntoCell(doc, codeBlockPos !== null && codeBlockPos !== void 0 ? codeBlockPos : anchorPos, 1), () => {
249
+ clampAnchorPosIntoCell(doc, (_hoisted$codeBlockSta = hoisted === null || hoisted === void 0 ? void 0 : hoisted.codeBlockStart) !== null && _hoisted$codeBlockSta !== void 0 ? _hoisted$codeBlockSta : anchorPos, 1), () => {
212
250
  const host = buildContributorTagHost(diffId, tagAnchorName);
213
251
  mount = mountContributorTag({
214
252
  anchorName: tagAnchorName,
@@ -229,5 +267,6 @@ export const createContributorTagWidget = ({
229
267
  unmountContributorTag(mount);
230
268
  mount = undefined;
231
269
  }
232
- });
270
+ }));
271
+ return decorations;
233
272
  };
@@ -126,6 +126,7 @@ export const createInlineChangedDecoration = ({
126
126
  colorScheme,
127
127
  isActive = false,
128
128
  isInserted = true,
129
+ leftAnchorId,
129
130
  isAtomicInlineNode = false,
130
131
  shouldHideDeleted = false,
131
132
  showContributorTags = false,
@@ -157,6 +158,7 @@ export const createInlineChangedDecoration = ({
157
158
  colorScheme,
158
159
  decorationType: 'inline',
159
160
  diffId,
161
+ leftAnchorId,
160
162
  isActive,
161
163
  isInserted
162
164
  }))];
@@ -227,6 +229,7 @@ export const createInlineChangedDecoration = ({
227
229
  colorScheme,
228
230
  decorationType: 'inline',
229
231
  diffId,
232
+ leftAnchorId,
230
233
  isActive,
231
234
  isInserted
232
235
  }))];
@@ -262,14 +265,15 @@ export const createInlineChangedDecoration = ({
262
265
  doc,
263
266
  from: anchorFrom,
264
267
  to: anchorTo,
265
- diffId
268
+ diffId,
269
+ leftAnchorId
266
270
  }));
267
271
  }
268
272
 
269
273
  // Pushed after the indicator anchors so the decoration order is unchanged — see `tagWidget` above
270
274
  // for why it is built before them.
271
275
  if (tagWidget) {
272
- decorations.push(tagWidget);
276
+ decorations.push(...tagWidget);
273
277
  }
274
278
  return decorations;
275
279
  };
@@ -3,10 +3,10 @@ import { fg } from '@atlaskit/platform-feature-flags/fg';
3
3
  import { isExtendedEnabled } from '../isExtendedEnabled';
4
4
  import { countEmptyTextBlockOnlySlice } from '../utils/emptyTextBlocks';
5
5
  import { isEmptyParagraphSlice } from '../utils/isEmptyParagraphSlice';
6
- import { createLeftAnchorWidget } from './createAnchorDecorationWidgets';
6
+ import { clampAnchorPosIntoCell, createLeftAnchorWidget } from './createAnchorDecorationWidgets';
7
7
  import { createChangedRowDecorationWidgets } from './createChangedRowDecorationWidgets';
8
- import { createContributorTagHost, unmountContributorTag } from './createContributorTagWidget';
9
- import { AnchorTypeKey, buildDiffDecorationSpec, buildAnchorDecorationKey, scrollMarginTopValue } from './decorationKeys';
8
+ import { createContributorTagHost, resolveHoistedCodeBlockAnchor, unmountContributorTag } from './createContributorTagWidget';
9
+ import { AnchorTypeKey, buildContributorTagDecorationSpec, buildDiffDecorationSpec, buildAnchorDecorationKey, scrollMarginTopValue } from './decorationKeys';
10
10
  import { absorbFirstChildMarginReset } from './utils/absorbFirstChildMarginReset';
11
11
  import { createDeletedLineBreakDecoration } from './utils/createDeletedLineBreakWidget';
12
12
  import { createMarginAbsorber } from './utils/createMarginAbsorber';
@@ -29,6 +29,7 @@ const createTableCellContentWidgets = ({
29
29
  nodeViewSerializer,
30
30
  colorScheme,
31
31
  isInserted,
32
+ leftAnchorId,
32
33
  diffType
33
34
  }) => {
34
35
  // Collect the inner content position of each cell in the new document within
@@ -87,6 +88,7 @@ const createTableCellContentWidgets = ({
87
88
  colorScheme,
88
89
  decorationType: 'widget',
89
90
  diffId: crypto.randomUUID(),
91
+ leftAnchorId,
90
92
  isInserted,
91
93
  diffType,
92
94
  ...(isExtendedEnabled(diffType) && {
@@ -113,6 +115,7 @@ export const createNodeChangedDecorationWidget = ({
113
115
  activeIndexPos,
114
116
  // This is false by default as this is generally used to show deleted content
115
117
  isInserted = false,
118
+ leftAnchorId,
116
119
  showContributorTags = false,
117
120
  showIndicators = false,
118
121
  // When true, render the deleted content *after* (below) the new content instead of
@@ -177,6 +180,7 @@ export const createNodeChangedDecorationWidget = ({
177
180
  nodeViewSerializer,
178
181
  colorScheme,
179
182
  isInserted,
183
+ leftAnchorId,
180
184
  diffType
181
185
  });
182
186
  }
@@ -193,6 +197,7 @@ export const createNodeChangedDecorationWidget = ({
193
197
  colorScheme,
194
198
  isActive,
195
199
  isInserted,
200
+ leftAnchorId,
196
201
  diffType,
197
202
  intl,
198
203
  // Needed for the row's own indicator anchor; this path returns before the
@@ -400,7 +405,13 @@ export const createNodeChangedDecorationWidget = ({
400
405
  }
401
406
  }
402
407
  });
403
- let contributorTagAnchorName;
408
+
409
+ // A change inside a code block hoists its tag out of the block (EDITOR-9045).
410
+ const hoistedCodeBlockAnchor = canTagWidget ? resolveHoistedCodeBlockAnchor(newDoc, safeInsertPos, diffId) : undefined;
411
+ if (hoistedCodeBlockAnchor) {
412
+ decorations.push(hoistedCodeBlockAnchor.marker);
413
+ }
414
+ let contributorTagAnchorName = hoistedCodeBlockAnchor === null || hoistedCodeBlockAnchor === void 0 ? void 0 : hoistedCodeBlockAnchor.anchorName;
404
415
  if (!isInserted && deletedColumns !== null && deletedColumns !== void 0 && deletedColumns.length) {
405
416
  const table = dom.querySelector('table');
406
417
  const firstRow = table === null || table === void 0 ? void 0 : table.rows[0];
@@ -460,6 +471,13 @@ export const createNodeChangedDecorationWidget = ({
460
471
  dom.style.setProperty('anchor-name', `--${buildAnchorDecorationKey({
461
472
  diffId
462
473
  })}`);
474
+ // The tag built below only anchors via `anchor()` if `contributorTagAnchorName` is set; without
475
+ // it the tag falls back to a non-anchored position (EDITOR-9045).
476
+ if (fg('confluence_ncs_step_diffing_version_history')) {
477
+ contributorTagAnchorName ??= buildAnchorDecorationKey({
478
+ diffId
479
+ });
480
+ }
463
481
  }
464
482
 
465
483
  // Taken down in the widget's `destroy` below: `dom` is rebuilt on every recalculation, so the tag
@@ -468,24 +486,55 @@ export const createNodeChangedDecorationWidget = ({
468
486
  if (canTagWidget) {
469
487
  // Lets the contributor tag find the deleted content on hover.
470
488
  dom.setAttribute('data-diff-id', diffId);
471
-
472
- // Hosted at the start of the deleted run rather than on `dom` itself: `dom` is inline, so once
473
- // the deleted content wraps its box is the union of its line fragments.
474
- const tagHost = createContributorTagHost({
475
- anchorName: contributorTagAnchorName,
476
- diffId,
477
- mountContext: tagMountContext
478
- });
479
- if (tagHost) {
480
- dom.prepend(tagHost.host);
481
- tagMount = tagHost.mount;
489
+ if (hoistedCodeBlockAnchor) {
490
+ // Own widget just before the code block, anchored to the marker above, not `dom` (EDITOR-9045).
491
+ let hoistedTagMount;
492
+ decorations.push(Decoration.widget(clampAnchorPosIntoCell(newDoc, hoistedCodeBlockAnchor.codeBlockStart, 1), () => {
493
+ var _tagHost$host;
494
+ const tagHost = createContributorTagHost({
495
+ anchorName: contributorTagAnchorName,
496
+ diffId,
497
+ mountContext: tagMountContext
498
+ });
499
+ hoistedTagMount = tagHost === null || tagHost === void 0 ? void 0 : tagHost.mount;
500
+ return (_tagHost$host = tagHost === null || tagHost === void 0 ? void 0 : tagHost.host) !== null && _tagHost$host !== void 0 ? _tagHost$host : document.createElement('span');
501
+ }, {
502
+ ...buildContributorTagDecorationSpec(diffId),
503
+ side: 1,
504
+ marks: [],
505
+ ignoreSelection: true,
506
+ stopEvent: () => true,
507
+ destroy: () => {
508
+ unmountContributorTag(hoistedTagMount);
509
+ hoistedTagMount = undefined;
510
+ }
511
+ }));
512
+ } else {
513
+ // Hosted at the start of the deleted run rather than on `dom` itself: `dom` is inline, so once
514
+ // the deleted content wraps its box is the union of its line fragments.
515
+ const tagHost = createContributorTagHost({
516
+ anchorName: contributorTagAnchorName,
517
+ diffId,
518
+ mountContext: tagMountContext
519
+ });
520
+ if (tagHost) {
521
+ dom.prepend(tagHost.host);
522
+ tagMount = tagHost.mount;
523
+ }
482
524
  }
483
525
  }
484
526
  if (showIndicators && isExtendedEnabled(diffType)) {
485
527
  const leftAnchor = createLeftAnchorWidget({
486
528
  doc: newDoc,
487
529
  from: safeInsertPos,
488
- diffId
530
+ diffId,
531
+ leftAnchorId,
532
+ measureElement: fg('platform_editor_ai_show_diff_patch_2') ? dom : undefined,
533
+ sliceOverride: fg('platform_editor_ai_show_diff_patch_2') ? slice : undefined,
534
+ underlyingRange: fg('platform_editor_ai_show_diff_patch_2') ? {
535
+ from: change.fromB,
536
+ to: change.toB
537
+ } : undefined
489
538
  });
490
539
  if (leftAnchor) {
491
540
  decorations.push(leftAnchor);
@@ -497,6 +546,7 @@ export const createNodeChangedDecorationWidget = ({
497
546
  colorScheme,
498
547
  decorationType: 'widget',
499
548
  diffId,
549
+ leftAnchorId,
500
550
  isActive,
501
551
  isInserted,
502
552
  diffType,
@@ -73,6 +73,7 @@ export const buildDiffDecorationSpec = ({
73
73
  diffId,
74
74
  isActive,
75
75
  isInserted,
76
+ leftAnchorId,
76
77
  nodeName,
77
78
  side,
78
79
  diffType
@@ -96,6 +97,9 @@ export const buildDiffDecorationSpec = ({
96
97
  ...(isInserted !== undefined ? {
97
98
  isInserted
98
99
  } : {}),
100
+ ...(leftAnchorId ? {
101
+ leftAnchorId
102
+ } : {}),
99
103
  ...(colorScheme ? {
100
104
  colorScheme
101
105
  } : {}),
@@ -154,6 +158,9 @@ export const extractDiffDescriptors = decorations => decorations.find(undefined,
154
158
  colorScheme: spec.colorScheme
155
159
  } : {}),
156
160
  id: spec.diffId,
161
+ ...(spec.leftAnchorId ? {
162
+ leftAnchorId: spec.leftAnchorId
163
+ } : {}),
157
164
  ...(spec.isInserted !== undefined ? {
158
165
  isInserted: spec.isInserted
159
166
  } : {}),
@@ -39,10 +39,15 @@ const getChangedContentStyleNext = (colorScheme, isActive = false, isInserted =
39
39
  if (isExtendedEnabled(diffType) && isInserted) {
40
40
  return buildInsertedInlineStyle(colors, isActive, hideAddedDiffsUnderline);
41
41
  }
42
- if (isActive) {
43
- return buildDeletedInlineContentStyle(colors, 'active');
42
+ const base = buildDeletedInlineContentStyle(colors, isActive ? 'active' : expValEquals('platform_editor_enghealth_a11y_jan_fixes', 'isEnabled', true) ? 'new' : 'default');
43
+
44
+ // Text-like blocks (heading, lists, blockquote) render their deleted content through this
45
+ // getter rather than `getDeletedContentStyle`, so without this they never receive the
46
+ // glyphTint background + underline that plain deleted text already gets.
47
+ if (fg('confluence_ncs_step_diffing_version_history') && colors.deletedInlineTreatment === 'glyphTint' && isExtendedEnabled(diffType)) {
48
+ return base + buildDeletedInlineContentStyleExtended(colors, isActive);
44
49
  }
45
- return buildDeletedInlineContentStyle(colors, expValEquals('platform_editor_enghealth_a11y_jan_fixes', 'isEnabled', true) ? 'new' : 'default');
50
+ return base;
46
51
  };
47
52
  const getChangedNodeStyleNext = (nodeName, colorScheme, isInserted = false, isActive = false, diffType, hideAddedDiffsUnderline = false) => {
48
53
  const colors = getColorScheme(colorScheme);
@@ -5,6 +5,8 @@ import { useSharedPluginStateWithSelector } from '@atlaskit/editor-common/hooks'
5
5
  import { buildAnchorDecorationKey, AnchorTypeKey } from '../../pm-plugins/decorations/decorationKeys';
6
6
  import { IndicatorBar } from './IndicatorBar';
7
7
  const renderIndicatorBar = descriptor => {
8
+ var _descriptor$leftAncho;
9
+ const leftAnchorId = (_descriptor$leftAncho = descriptor.leftAnchorId) !== null && _descriptor$leftAncho !== void 0 ? _descriptor$leftAncho : descriptor.id;
8
10
  switch (descriptor.type) {
9
11
  case 'inline':
10
12
  {
@@ -21,7 +23,7 @@ const renderIndicatorBar = descriptor => {
21
23
  anchorType: AnchorTypeKey.to
22
24
  }),
23
25
  anchorLeft: buildAnchorDecorationKey({
24
- diffId: descriptor.id,
26
+ diffId: leftAnchorId,
25
27
  anchorType: AnchorTypeKey.left
26
28
  })
27
29
  });
@@ -41,7 +43,7 @@ const renderIndicatorBar = descriptor => {
41
43
  anchorTop: blockAnchor,
42
44
  anchorBottom: blockAnchor,
43
45
  anchorLeft: buildAnchorDecorationKey({
44
- diffId: descriptor.id,
46
+ diffId: leftAnchorId,
45
47
  anchorType: AnchorTypeKey.left
46
48
  })
47
49
  });
@@ -59,7 +61,7 @@ const renderIndicatorBar = descriptor => {
59
61
  diffId: descriptor.id
60
62
  }),
61
63
  anchorLeft: buildAnchorDecorationKey({
62
- diffId: descriptor.id,
64
+ diffId: leftAnchorId,
63
65
  anchorType: AnchorTypeKey.left
64
66
  }),
65
67
  anchorLeftFallback: buildAnchorDecorationKey({
@@ -185,6 +185,7 @@ var calculateNodesForBlockDecoration = function calculateNodesForBlockDecoration
185
185
  colorScheme = _ref5.colorScheme,
186
186
  _ref5$isInserted = _ref5.isInserted,
187
187
  isInserted = _ref5$isInserted === void 0 ? true : _ref5$isInserted,
188
+ leftAnchorId = _ref5.leftAnchorId,
188
189
  activeIndexPos = _ref5.activeIndexPos,
189
190
  _ref5$shouldHideDelet = _ref5.shouldHideDeleted,
190
191
  shouldHideDeleted = _ref5$shouldHideDelet === void 0 ? false : _ref5$shouldHideDelet,
@@ -223,6 +224,7 @@ var calculateNodesForBlockDecoration = function calculateNodesForBlockDecoration
223
224
  },
224
225
  colorScheme: colorScheme,
225
226
  isInserted: isInserted,
227
+ leftAnchorId: leftAnchorId,
226
228
  isActive: isActive,
227
229
  shouldHideDeleted: shouldHideDeleted,
228
230
  showContributorTags: showContributorTags,
@@ -264,6 +266,7 @@ var calculateNodesForBlockDecoration = function calculateNodesForBlockDecoration
264
266
  },
265
267
  colorScheme: colorScheme,
266
268
  isInserted: isInserted,
269
+ leftAnchorId: leftAnchorId,
267
270
  isActive: _isActive,
268
271
  shouldHideDeleted: shouldHideDeleted,
269
272
  showContributorTags: showContributorTags,
@@ -546,6 +549,10 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref8
546
549
  });
547
550
  };
548
551
  var createDecorationsForChange = function createDecorationsForChange(change) {
552
+ // Patch 2 makes all decorations generated for one replacement share this anchor. Their own
553
+ // IDs still identify their top/bottom bounds, while this ID makes their bars agree on one
554
+ // left edge. When the gate is off, factories retain their existing per-decoration anchors.
555
+ var leftAnchorId = fg('platform_editor_ai_show_diff_patch_2') ? "replacement-".concat(change.fromA, "-").concat(change.toA, "-").concat(change.fromB, "-").concat(change.toB) : undefined;
549
556
  var changeColorScheme = attributionColors ? getColorSchemeForChange(change, attributedChanges, attributionColors, colorScheme) : colorScheme;
550
557
  var attributionKey = showContributorTags ? getAttributionKeyForChange(change, attributedChanges) : undefined;
551
558
  var isActive = isRangeActive(activeIndexPos, change.fromB, change.toB);
@@ -678,6 +685,7 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref8
678
685
  fromB: from,
679
686
  toB: to
680
687
  },
688
+ leftAnchorId: leftAnchorId,
681
689
  doc: tr.doc,
682
690
  colorScheme: changeColorScheme,
683
691
  isActive: isActive,
@@ -703,6 +711,7 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref8
703
711
  decorations.push.apply(decorations, _toConsumableArray(createInlineChangedDecoration(_objectSpread({
704
712
  attributionKey: attributionKey,
705
713
  change: change,
714
+ leftAnchorId: leftAnchorId,
706
715
  doc: tr.doc,
707
716
  colorScheme: changeColorScheme,
708
717
  isActive: isActive,
@@ -737,7 +746,8 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref8
737
746
  activeIndexPos: activeIndexPos,
738
747
  intl: intl,
739
748
  diffType: diffType,
740
- coarseTableCellsOnly: useCoarseTableDecoration
749
+ coarseTableCellsOnly: useCoarseTableDecoration,
750
+ leftAnchorId: leftAnchorId
741
751
  }))));
742
752
  }
743
753
  if (change.deleted.length > 0 && !isMarkOnly && !hasNoDeletedContent) {
@@ -762,6 +772,7 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref8
762
772
  newDoc: tr.doc,
763
773
  intl: intl,
764
774
  activeIndexPos: activeIndexPos,
775
+ leftAnchorId: leftAnchorId,
765
776
  showContributorTags: showContributorTags,
766
777
  tagMountContext: tagMountContext
767
778
  }, isExtendedEnabled(diffType) && {