@atlaskit/editor-plugin-show-diff 28.0.3 → 29.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/dist/cjs/pm-plugins/calculateDiff/calculateDiffDecorations.js +10 -3
  3. package/dist/cjs/pm-plugins/calculateDiff/normalizeTextNodes.js +23 -0
  4. package/dist/cjs/pm-plugins/decorations/colorSchemes/attributions.js +18 -31
  5. package/dist/cjs/pm-plugins/resolveDiffContributors.js +17 -65
  6. package/dist/cjs/showDiffPlugin.js +4 -1
  7. package/dist/cjs/showDiffPluginType.js +2 -3
  8. package/dist/cjs/ui/ContributorTag/contributorAvatarRenderer.js +14 -43
  9. package/dist/cjs/ui/ContributorTag/contributorTagIcons.js +1 -9
  10. package/dist/es2019/pm-plugins/calculateDiff/calculateDiffDecorations.js +10 -3
  11. package/dist/es2019/pm-plugins/calculateDiff/normalizeTextNodes.js +16 -0
  12. package/dist/es2019/pm-plugins/decorations/colorSchemes/attributions.js +20 -31
  13. package/dist/es2019/pm-plugins/resolveDiffContributors.js +17 -67
  14. package/dist/es2019/showDiffPlugin.js +2 -0
  15. package/dist/es2019/showDiffPluginType.js +2 -3
  16. package/dist/es2019/ui/ContributorTag/contributorAvatarRenderer.js +14 -43
  17. package/dist/es2019/ui/ContributorTag/contributorTagIcons.js +1 -9
  18. package/dist/esm/pm-plugins/calculateDiff/calculateDiffDecorations.js +10 -3
  19. package/dist/esm/pm-plugins/calculateDiff/normalizeTextNodes.js +19 -0
  20. package/dist/esm/pm-plugins/decorations/colorSchemes/attributions.js +18 -31
  21. package/dist/esm/pm-plugins/resolveDiffContributors.js +17 -66
  22. package/dist/esm/showDiffPlugin.js +4 -1
  23. package/dist/esm/showDiffPluginType.js +2 -3
  24. package/dist/esm/ui/ContributorTag/contributorAvatarRenderer.js +14 -43
  25. package/dist/esm/ui/ContributorTag/contributorTagIcons.js +1 -9
  26. package/dist/types/pm-plugins/calculateDiff/normalizeTextNodes.d.ts +8 -0
  27. package/dist/types/pm-plugins/decorations/colorSchemes/attributions.d.ts +1 -1
  28. package/dist/types/pm-plugins/main.d.ts +1 -0
  29. package/dist/types/showDiffPluginType.d.ts +6 -5
  30. package/dist/types/ui/ContributorTag/contributorTagIcons.d.ts +2 -2
  31. package/package.json +7 -7
package/CHANGELOG.md CHANGED
@@ -1,5 +1,41 @@
1
1
  # @atlaskit/editor-plugin-show-diff
2
2
 
3
+ ## 29.0.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [`90093ce337081`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/90093ce337081) -
8
+ Version history diff: treat documents that differ only in how adjacent same-mark text nodes are
9
+ split as equal, so changes to migrated legacy-editor content are highlighted (behind
10
+ platform_editor_show_diff_normalise_text_nodes)
11
+
12
+ ## 29.0.0
13
+
14
+ ### Major Changes
15
+
16
+ - [`9094e731e3560`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/9094e731e3560) -
17
+ Remove `agentType` from `DiffStepAttribution`. Agent identity now requires `agentId`; names and
18
+ avatars come from `contributorProfiles` matched by account ID. Branding uses an explicit profile
19
+ `agentBrand`, a known `agentNamedId`, or the profile name. Contributor avatars always use the
20
+ account's `avatarUrl`, including Claude and ChatGPT. Missing or failed agent images use the
21
+ generic agent icon instead of bundled brand SVGs. Migrate callers using `agentType` to supply the
22
+ agent account ID and its profile, including `agentBrand` when explicit branding is required.
23
+ Agents without a profile or known named brand use the generic external-agent label. Attribution
24
+ colours and tags remain controlled by `confluence_ncs_step_diffing_version_history` and
25
+ `platform_editor_show_diff_color_scheme_refactor`.
26
+
27
+ Suggestion diffs supply account profiles and resolved vendor brands while keeping contributor tags
28
+ hidden with `showContributorTags: false`. Other attributed callers retain the existing default tag
29
+ visibility.
30
+
31
+ Suggested edits retain legacy `agentType` as a branding fallback when the display name does not
32
+ identify a known brand. The suggestions adapter resolves this using `@atlaskit/agent-color` before
33
+ calling show-diff; known names take precedence.
34
+
35
+ ### Patch Changes
36
+
37
+ - Updated dependencies
38
+
3
39
  ## 28.0.3
4
40
 
5
41
  ### Patch Changes
@@ -17,6 +17,7 @@ var _document = require("@atlaskit/editor-common/utils/document");
17
17
  var _transform = require("@atlaskit/editor-prosemirror/transform");
18
18
  var _view = require("@atlaskit/editor-prosemirror/view");
19
19
  var _tableMap = require("@atlaskit/editor-tables/table-map");
20
+ var _isExperimentEnabled = require("@atlaskit/platform-feature-experiments/is-experiment-enabled");
20
21
  var _unsafeExpValNoExposure = require("@atlaskit/platform-feature-experiments/unsafe-exp-val-no-exposure");
21
22
  var _fg = require("@atlaskit/platform-feature-flags/fg");
22
23
  var _attributions = require("../decorations/colorSchemes/attributions");
@@ -39,6 +40,7 @@ var _groupChangesByBlock = require("./groupChangesByBlock");
39
40
  var _groupDeletedColumnChanges = require("./groupDeletedColumnChanges");
40
41
  var _isMarkOnlyChange = require("./isMarkOnlyChange");
41
42
  var _isOpenTokenOnlyChange = require("./isOpenTokenOnlyChange");
43
+ var _normalizeTextNodes = require("./normalizeTextNodes");
42
44
  var _optimizeChanges = require("./optimizeChanges");
43
45
  var _selectTokenEncoder2 = require("./selectTokenEncoder");
44
46
  var _simplifyChangesWithAttribution = require("./simplifyChangesWithAttribution");
@@ -406,7 +408,7 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref8
406
408
  var tr = state.tr;
407
409
  var steppedDoc = originalDoc;
408
410
  var attributionColoringEnabled = (0, _attributions.isAttributionColoringEnabled)(stepAttributions);
409
- var showContributorTags = (0, _attributions.isContributorTagsEnabled)(stepAttributions, contributors);
411
+ var showContributorTags = pluginState.showContributorTags !== false && (0, _attributions.isContributorTagsEnabled)(stepAttributions, contributors);
410
412
  // Tags need the attributed changeset even on diffs with too few actors to be worth colouring.
411
413
  var attributedChangesetEnabled = attributionColoringEnabled || showContributorTags;
412
414
  var simplifiedSteps;
@@ -482,6 +484,10 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref8
482
484
  // changes and ignores differences in attributes which don't affect decoration positions
483
485
  if (!(0, _document.areNodesEqualIgnoreAttrs)(steppedDoc, tr.doc)) {
484
486
  var _api$analytics;
487
+ // A document parsed from ADF can hold adjacent text nodes with identical marks, while the
488
+ // editor joins them whenever it rebuilds a node (for example when local ids are assigned on
489
+ // load). Positions are identical either way, so the decorations still apply.
490
+ var recoveredViaTextNodeNormalisation = (0, _isExperimentEnabled.isExperimentEnabled)('platform_editor_show_diff_normalise_text_nodes') && (0, _document.areNodesEqualIgnoreAttrs)((0, _normalizeTextNodes.normalizeTextNodes)(steppedDoc), (0, _normalizeTextNodes.normalizeTextNodes)(tr.doc));
485
491
  var recoveredViaContentEquality = (0, _areDocsEqualByBlockStructureAndText.areDocsEqualByBlockStructureAndText)(steppedDoc, tr.doc);
486
492
  api === null || api === void 0 || (_api$analytics = api.analytics) === null || _api$analytics === void 0 || _api$analytics.actions.fireAnalyticsEvent({
487
493
  eventType: 'track',
@@ -490,10 +496,11 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref8
490
496
  attributes: {
491
497
  docSizeEqual: steppedDoc.nodeSize === tr.doc.nodeSize,
492
498
  colorScheme: colorScheme,
493
- recoveredViaContentEquality: recoveredViaContentEquality
499
+ recoveredViaContentEquality: recoveredViaContentEquality,
500
+ recoveredViaTextNodeNormalisation: recoveredViaTextNodeNormalisation
494
501
  }
495
502
  });
496
- if (!recoveredViaContentEquality) {
503
+ if (!recoveredViaTextNodeNormalisation && !recoveredViaContentEquality) {
497
504
  return {
498
505
  contributorTags: [],
499
506
  decorations: _view.DecorationSet.empty,
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.normalizeTextNodes = void 0;
7
+ var _model = require("@atlaskit/editor-prosemirror/model");
8
+ /**
9
+ * Returns a copy of `node` in which adjacent text nodes with identical marks are joined, the same
10
+ * normalisation ProseMirror applies whenever it rebuilds a node's content. A document parsed straight
11
+ * from ADF keeps such split runs, so two documents with identical content and positions can still
12
+ * differ in child count.
13
+ */
14
+ var _normalizeTextNodes = exports.normalizeTextNodes = function normalizeTextNodes(node) {
15
+ if (node.isText || node.childCount === 0) {
16
+ return node;
17
+ }
18
+ var children = [];
19
+ node.content.forEach(function (child) {
20
+ return children.push(_normalizeTextNodes(child));
21
+ });
22
+ return node.copy(_model.Fragment.fromArray(children));
23
+ };
@@ -9,6 +9,7 @@ var _typeof2 = _interopRequireDefault(require("@babel/runtime/helpers/typeof"));
9
9
  var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
10
10
  var _objectWithoutProperties2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutProperties"));
11
11
  var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
12
+ var _getThirdPartyAgentColor = require("@atlaskit/agent-color/get-third-party-agent-color");
12
13
  var _utils = require("@atlaskit/editor-shared-styles/utils");
13
14
  var _isExperimentEnabled = require("@atlaskit/platform-feature-experiments/is-experiment-enabled");
14
15
  var _fg = require("@atlaskit/platform-feature-flags/fg");
@@ -62,36 +63,19 @@ var getAvailableColor = function getAvailableColor(hashedColor, allocatedColors)
62
63
  return isAssignable(hashedColor) ? hashedColor : (_PARTICIPANT_COLOR_SC = _types.PARTICIPANT_COLOR_SCHEMES.find(isAssignable)) !== null && _PARTICIPANT_COLOR_SC !== void 0 ? _PARTICIPANT_COLOR_SC : hashedColor;
63
64
  };
64
65
 
65
- /**
66
- * Normalise the attribution identity once so a valid lookup key always has a valid participant
67
- * colour seed. `userId` is primary; `agentId`, or `agentType` as its fallback, separates shared
68
- * users. The colour seed matches collaboration: the user ID for a human and the agent ID for an
69
- * agent. The agent-type fallback includes the user so different users' agents do not collapse onto
70
- * one colour when NCS omits an agent ID.
71
- */
66
+ /** Account IDs supply both the lookup key and participant colour seed. */
72
67
  var getAttributionIdentity = function getAttributionIdentity(attribution) {
73
- var _attribution$userId, _attribution$agentId, _attribution$agentTyp;
68
+ var _attribution$userId, _attribution$agentId;
74
69
  var userId = attribution === null || attribution === void 0 || (_attribution$userId = attribution.userId) === null || _attribution$userId === void 0 ? void 0 : _attribution$userId.trim();
75
70
  var agentId = attribution === null || attribution === void 0 || (_attribution$agentId = attribution.agentId) === null || _attribution$agentId === void 0 ? void 0 : _attribution$agentId.trim();
76
- var agentType = attribution === null || attribution === void 0 || (_attribution$agentTyp = attribution.agentType) === null || _attribution$agentTyp === void 0 ? void 0 : _attribution$agentTyp.trim();
77
- var colorSeed;
78
- if (agentId) {
79
- colorSeed = agentId;
80
- } else if (agentType) {
81
- colorSeed = userId ? "user:".concat(userId, "|agent:type:").concat(agentType) : "agent:type:".concat(agentType);
82
- } else if (userId) {
83
- colorSeed = userId;
84
- } else {
85
- return undefined;
86
- }
87
- var agentIdentity = agentId || (agentType ? "type:".concat(agentType) : '');
88
- return {
71
+ var colorSeed = agentId || userId;
72
+ return colorSeed ? {
89
73
  colorSeed: colorSeed,
90
- key: "user:".concat(userId !== null && userId !== void 0 ? userId : '', "|agent:").concat(agentIdentity)
91
- };
74
+ key: "user:".concat(userId !== null && userId !== void 0 ? userId : '', "|agent:").concat(agentId !== null && agentId !== void 0 ? agentId : '')
75
+ } : undefined;
92
76
  };
93
77
 
94
- /** `userId` is primary; `agentId`, or `agentType` as its fallback, separates shared users. */
78
+ /** `userId` is primary; `agentId` separates agents sharing that user. */
95
79
  var getAttributionKey = exports.getAttributionKey = function getAttributionKey(attribution) {
96
80
  var _getAttributionIdenti;
97
81
  return (_getAttributionIdenti = getAttributionIdentity(attribution)) === null || _getAttributionIdenti === void 0 ? void 0 : _getAttributionIdenti.key;
@@ -100,12 +84,6 @@ var isDiffAgentBrand = function isDiffAgentBrand(agentBrand) {
100
84
  return agentBrand !== undefined && _showDiffPluginType.DIFF_AGENT_BRANDS.has(agentBrand);
101
85
  };
102
86
 
103
- /** Prefers the resolved branded profile over the raw attribution agent type. */
104
- var getAgentBrandForColor = function getAgentBrandForColor(attribution, contributors) {
105
- var _contributors, _getAttributionKey;
106
- var agentBrand = contributors === null || contributors === void 0 || (_contributors = contributors[(_getAttributionKey = getAttributionKey(attribution)) !== null && _getAttributionKey !== void 0 ? _getAttributionKey : '']) === null || _contributors === void 0 ? void 0 : _contributors.agentKind;
107
- return isDiffAgentBrand(agentBrand) ? agentBrand : attribution === null || attribution === void 0 ? void 0 : attribution.agentType;
108
- };
109
87
  /**
110
88
  * Public contributor list to internal key-addressed record. A contributor whose attribution carries
111
89
  * no identity is dropped, since it could never match a step. Later entries win.
@@ -165,7 +143,16 @@ var isContributorTagsEnabled = exports.isContributorTagsEnabled = function isCon
165
143
  })) && contributors !== undefined && Object.keys(contributors).length > 0;
166
144
  };
167
145
  var getAttributionParticipantColor = function getAttributionParticipantColor(attribution, identity, contributors) {
168
- return (0, _utils.getParticipantColor)(identity.colorSeed, getAgentBrandForColor(attribution, contributors), attribution === null || attribution === void 0 ? void 0 : attribution.agentNamedId);
146
+ var _ref2, _getThirdPartyAgentCo, _attribution$agentId2;
147
+ var contributor = contributors === null || contributors === void 0 ? void 0 : contributors[identity.key];
148
+ var brand = isDiffAgentBrand(contributor === null || contributor === void 0 ? void 0 : contributor.agentKind) ? contributor === null || contributor === void 0 ? void 0 : contributor.agentKind : undefined;
149
+ var agentName = (_ref2 = brand !== null && brand !== void 0 ? brand : (_getThirdPartyAgentCo = (0, _getThirdPartyAgentColor.getThirdPartyAgentColor)({
150
+ agentNamedId: attribution === null || attribution === void 0 ? void 0 : attribution.agentNamedId,
151
+ agentName: contributor === null || contributor === void 0 ? void 0 : contributor.name
152
+ })) === null || _getThirdPartyAgentCo === void 0 ? void 0 : _getThirdPartyAgentCo.name) !== null && _ref2 !== void 0 ? _ref2 : attribution !== null && attribution !== void 0 && (_attribution$agentId2 = attribution.agentId) !== null && _attribution$agentId2 !== void 0 && _attribution$agentId2.trim() ? (contributor === null || contributor === void 0 ? void 0 : contributor.name) || 'Agent' : undefined;
153
+ return (0, _utils.getParticipantColor)(identity.colorSeed, agentName,
154
+ // Keep an explicitly resolved brand consistent with the contributor's icon.
155
+ brand ? undefined : attribution === null || attribution === void 0 ? void 0 : attribution.agentNamedId);
169
156
  };
170
157
  var createAttributionColorMap = exports.createAttributionColorMap = function createAttributionColorMap(stepAttributions, contributors) {
171
158
  var colors = new Map();
@@ -13,11 +13,6 @@ function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r)
13
13
  function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
14
14
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
15
15
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
16
- /** A brand name, so a literal rather than a translated message. */
17
- var ROVO_AGENT_NAME = 'Rovo';
18
-
19
- /** `agentType` values that render as "Rovo" rather than as a generic external agent. */
20
- var ROVO_AGENT_TYPES = new Set(['convo-ai', 'rovo_chat']);
21
16
  /** `@atlaskit/agent-color` brands show-diff renders with a dedicated presentation. */
22
17
  var SHOW_DIFF_AGENT_BRANDS = {
23
18
  'agent-brand-claude': 'claude',
@@ -35,29 +30,6 @@ var SHOW_DIFF_AGENT_BRANDS = {
35
30
  'agent-brand-antigravity': 'antigravity',
36
31
  'agent-brand-copilot': 'copilot'
37
32
  };
38
-
39
- /**
40
- * Brands that prefer the account's real avatar over their fixed glyph when the invoking profile
41
- * has one. Brands with a fixed glyph (Claude, ChatGPT, Amplitude, Cortex, Databricks, Gemini, Manus,
42
- * Slack) intentionally keep it always (see `AGENT_KIND_ICONS` in `contributorAvatarRenderer.ts`).
43
- */
44
- var AVATAR_ELIGIBLE_BRANDS = new Set(['antigravity', 'copilot', 'figma', 'lovable', 'replit', 'rovo']);
45
-
46
- /** Known external agent types emitted by Confluence's agent identification service that carry
47
- * no `@atlaskit/agent-color` brand — a generic labelled fallback. */
48
- var EXTERNAL_AGENT_NAMES = {
49
- vscode: 'VS Code',
50
- hermes: 'Hermes',
51
- runlayer: 'Runlayer',
52
- zed: 'Zed',
53
- ampcode: 'Ampcode',
54
- devin_cli: 'Devin CLI',
55
- pi_agent: 'Pi Agent'
56
- };
57
- var hasAgentIdentity = function hasAgentIdentity(attribution) {
58
- var _attribution$agentId, _attribution$agentTyp;
59
- return Boolean(((_attribution$agentId = attribution.agentId) === null || _attribution$agentId === void 0 ? void 0 : _attribution$agentId.trim()) || ((_attribution$agentTyp = attribution.agentType) === null || _attribution$agentTyp === void 0 ? void 0 : _attribution$agentTyp.trim()));
60
- };
61
33
  var toProfilesByAccountId = function toProfilesByAccountId(profiles) {
62
34
  var profilesByAccountId = new Map();
63
35
  profiles === null || profiles === void 0 || profiles.forEach(function (profile) {
@@ -78,50 +50,30 @@ var resolveUser = function resolveUser(userId, profilesByAccountId) {
78
50
  } : undefined;
79
51
  };
80
52
 
81
- /** Resolve branding or a profile first, then a known type name or the external-agent fallback. */
53
+ /** Resolve branding from named metadata or the account profile. */
82
54
  var resolveAgent = function resolveAgent(attribution, profilesByAccountId) {
83
- var _attribution$agentTyp2, _attribution$agentId2;
84
- var agentType = (_attribution$agentTyp2 = attribution.agentType) === null || _attribution$agentTyp2 === void 0 ? void 0 : _attribution$agentTyp2.trim().toLowerCase();
85
- // `agentType` may be a named id (e.g. `mcp_lovable_agent`) or a display name (e.g. `lovable`).
86
- var brand = agentType ? (0, _getThirdPartyAgentColor.getThirdPartyAgentColor)({
87
- agentNamedId: agentType,
88
- agentName: agentType
89
- }) : undefined;
90
- var diffBrand = brand ? SHOW_DIFF_AGENT_BRANDS[brand.scheme] : undefined;
91
- var agentId = (_attribution$agentId2 = attribution.agentId) === null || _attribution$agentId2 === void 0 ? void 0 : _attribution$agentId2.trim();
55
+ var _attribution$agentId, _agentProfile$agentBr;
56
+ var agentId = (_attribution$agentId = attribution.agentId) === null || _attribution$agentId === void 0 ? void 0 : _attribution$agentId.trim();
92
57
  var agentProfile = agentId ? profilesByAccountId.get(agentId) : undefined;
93
- if (diffBrand && brand) {
94
- return _objectSpread({
95
- agentKind: diffBrand,
96
- kind: 'agent',
97
- name: brand.name
98
- }, AVATAR_ELIGIBLE_BRANDS.has(diffBrand) && agentProfile !== null && agentProfile !== void 0 && agentProfile.avatarUrl ? {
99
- avatarUrl: agentProfile.avatarUrl
100
- } : {});
101
- }
102
- if (agentProfile) {
103
- return _objectSpread(_objectSpread({}, agentProfile.agentBrand ? {
104
- agentKind: agentProfile.agentBrand
58
+ var brand = (0, _getThirdPartyAgentColor.getThirdPartyAgentColor)({
59
+ agentNamedId: attribution.agentNamedId,
60
+ agentName: agentProfile === null || agentProfile === void 0 ? void 0 : agentProfile.name
61
+ });
62
+ var agentBrand = (_agentProfile$agentBr = agentProfile === null || agentProfile === void 0 ? void 0 : agentProfile.agentBrand) !== null && _agentProfile$agentBr !== void 0 ? _agentProfile$agentBr : brand ? SHOW_DIFF_AGENT_BRANDS[brand.scheme] : undefined;
63
+ if (agentProfile || agentBrand) {
64
+ var _ref, _agentProfile$name;
65
+ return _objectSpread(_objectSpread({}, agentBrand ? {
66
+ agentKind: agentBrand
105
67
  } : {}), {}, {
106
- avatarUrl: agentProfile.avatarUrl,
68
+ avatarUrl: agentProfile === null || agentProfile === void 0 ? void 0 : agentProfile.avatarUrl,
107
69
  kind: 'agent',
108
- name: agentProfile.name
70
+ name: (_ref = (_agentProfile$name = agentProfile === null || agentProfile === void 0 ? void 0 : agentProfile.name) !== null && _agentProfile$name !== void 0 ? _agentProfile$name : brand === null || brand === void 0 ? void 0 : brand.name) !== null && _ref !== void 0 ? _ref : ''
109
71
  });
110
72
  }
111
-
112
- // Unknown types keep an empty name for the tag's localised "External agent" label.
113
- if (agentType && ROVO_AGENT_TYPES.has(agentType)) {
114
- return {
115
- agentKind: 'rovo',
116
- kind: 'agent',
117
- name: ROVO_AGENT_NAME
118
- };
119
- }
120
- var externalName = agentType ? EXTERNAL_AGENT_NAMES[agentType] : undefined;
121
73
  return {
122
74
  agentKind: 'external',
123
75
  kind: 'agent',
124
- name: externalName !== null && externalName !== void 0 ? externalName : ''
76
+ name: ''
125
77
  };
126
78
  };
127
79
 
@@ -144,7 +96,7 @@ var resolveDiffContributors = exports.resolveDiffContributors = function resolve
144
96
  _step;
145
97
  try {
146
98
  for (_iterator.s(); !(_step = _iterator.n()).done;) {
147
- var _stepAttribution$user;
99
+ var _stepAttribution$user, _stepAttribution$agen;
148
100
  var stepAttribution = _step.value.stepAttribution;
149
101
  // An attribution naming nobody (e.g. one carrying only `wasOffline`) has nobody to credit.
150
102
  var actor = stepAttribution ? (0, _attributions.getAttributionKey)(stepAttribution) : undefined;
@@ -152,7 +104,7 @@ var resolveDiffContributors = exports.resolveDiffContributors = function resolve
152
104
  continue;
153
105
  }
154
106
  var userId = (_stepAttribution$user = stepAttribution.userId) === null || _stepAttribution$user === void 0 ? void 0 : _stepAttribution$user.trim();
155
- if (hasAgentIdentity(stepAttribution)) {
107
+ if ((_stepAttribution$agen = stepAttribution.agentId) !== null && _stepAttribution$agen !== void 0 && _stepAttribution$agen.trim()) {
156
108
  var agent = resolveAgent(stepAttribution, profilesByAccountId);
157
109
  if (!userId) {
158
110
  // Agent mode: the agent acted on its own, so it is credited on its own.
@@ -17,7 +17,7 @@ var _main = require("./pm-plugins/main");
17
17
  var _resolveDiffContributors = require("./pm-plugins/resolveDiffContributors");
18
18
  var _revealAnimation = require("./pm-plugins/revealAnimation");
19
19
  var _IndicatorBarContentComponent = require("./ui/IndicatorBar/IndicatorBarContentComponent");
20
- var _excluded = ["stepsWithAttribution", "contributorProfiles"];
20
+ var _excluded = ["stepsWithAttribution", "contributorProfiles", "showContributorTags"];
21
21
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
22
22
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
23
23
  var normalizeShowDiffParams = function normalizeShowDiffParams(params) {
@@ -30,8 +30,11 @@ var normalizeShowDiffParams = function normalizeShowDiffParams(params) {
30
30
  }
31
31
  var stepsWithAttribution = params.stepsWithAttribution,
32
32
  contributorProfiles = params.contributorProfiles,
33
+ _params$showContribut = params.showContributorTags,
34
+ showContributorTags = _params$showContribut === void 0 ? true : _params$showContribut,
33
35
  rest = (0, _objectWithoutProperties2.default)(params, _excluded);
34
36
  return _objectSpread(_objectSpread({}, rest), {}, {
37
+ showContributorTags: showContributorTags,
35
38
  // Resolve at boundary to keep contributor model and attribution format internal.
36
39
  contributors: (0, _attributions.areAttributionColorGatesEnabled)() ? (0, _attributions.resolveContributors)((0, _resolveDiffContributors.resolveDiffContributors)(stepsWithAttribution, contributorProfiles)) : undefined,
37
40
  steps: stepsWithAttribution.map(function (_ref) {
@@ -17,9 +17,8 @@ exports.DIFF_AGENT_BRANDS = exports.ASSERT_DIFF_AGENT_BRANDS_ARE_REGISTERED = vo
17
17
  * Attribution for a ProseMirror step.
18
18
  *
19
19
  * `userId` is the primary actor identity. `agentId` distinguishes multiple agents (or an agent
20
- * from its user) when they share that user ID, with `agentType` used as a fallback when `agentId`
21
- * is empty. `agentNamedId` carries a stable external configuration reference for presentation
22
- * overrides. `wasOffline` preserves additional provenance without affecting identity.
20
+ * from its user) when they share that user ID. `agentNamedId` carries a stable external
21
+ * configuration reference for presentation overrides. `wasOffline` preserves additional provenance without affecting identity.
23
22
  */
24
23
 
25
24
  /** Keeps a step and its attribution coupled through mapping and filtering. */
@@ -9,26 +9,6 @@ var _avatarSizes = require("@atlaskit/avatar/avatar-sizes");
9
9
  var _default = require("@atlaskit/avatar/constants/default");
10
10
  var _cssHelper = require("@atlaskit/editor-common/css-helper");
11
11
  var _contributorTagIcons = require("./contributorTagIcons");
12
- /**
13
- * Agent kinds with a custom contributor-tag icon, used as a fallback glyph when no `avatarUrl` is
14
- * available or its image fails to load (see `showContributorIcon`). Rovo/Figma/Lovable/Replit have
15
- * no entry here — they fall back to the generic `aiAgent` glyph. The declared type stays the wider
16
- * `TagContributor['agentKind']` so indexing below (with `'identified' | 'external'` in play too)
17
- * stays safely partial.
18
- */
19
- var AGENT_KIND_ICONS = {
20
- claude: 'claude',
21
- chatgpt: 'chatgpt'
22
- };
23
-
24
- /**
25
- * Brands that always render their fixed glyph, even when the contributor also carries an
26
- * `avatarUrl`. Rovo/Figma/Lovable/Replit are excluded here and use the real avatar when
27
- * `resolveDiffContributors` supplies one, falling back to the generic `aiAgent` glyph on load
28
- * failure.
29
- */
30
- var FIXED_GLYPH_AGENT_KINDS = new Set(['claude', 'chatgpt']);
31
-
32
12
  /** The size `@atlaskit/avatar`'s `xxsmall` rendered at. */
33
13
  var AVATAR_SIZE = _avatarSizes.AVATAR_SIZES.xxsmall;
34
14
  /** `xxsmall` also carried a `space.025` margin — the same 2px — which is where the ring is painted. */
@@ -129,7 +109,7 @@ var contributorAvatarRenderer = exports.contributorAvatarRenderer = function con
129
109
  var image;
130
110
  var unbindImageError;
131
111
  var showContributorIcon = function showContributorIcon() {
132
- var _unbindImageError, _image, _AGENT_KIND_ICONS, _contributor$agentKin;
112
+ var _unbindImageError, _image;
133
113
  (_unbindImageError = unbindImageError) === null || _unbindImageError === void 0 || _unbindImageError();
134
114
  unbindImageError = undefined;
135
115
  (_image = image) === null || _image === void 0 || _image.remove();
@@ -141,30 +121,21 @@ var contributorAvatarRenderer = exports.contributorAvatarRenderer = function con
141
121
  shape.appendChild(fallback);
142
122
  return;
143
123
  }
144
- var icon = (_AGENT_KIND_ICONS = AGENT_KIND_ICONS[(_contributor$agentKin = contributor.agentKind) !== null && _contributor$agentKin !== void 0 ? _contributor$agentKin : 'external']) !== null && _AGENT_KIND_ICONS !== void 0 ? _AGENT_KIND_ICONS : 'aiAgent';
145
- shape.appendChild((0, _contributorTagIcons.getContributorTagIcon)(icon, doc));
124
+ shape.appendChild((0, _contributorTagIcons.getContributorTagIcon)('aiAgent', doc));
146
125
  };
147
126
  if (contributor.avatarUrl) {
148
- var _contributor$agentKin2;
149
- // Claude/ChatGPT always use their fixed glyph, even when the profile also has an avatar
150
- // URL. Other agents (e.g. Rovo/Figma/Lovable/Replit/Slack) and users prefer the real avatar.
151
- var usesFixedGlyph = isAgent && FIXED_GLYPH_AGENT_KINDS.has((_contributor$agentKin2 = contributor.agentKind) !== null && _contributor$agentKin2 !== void 0 ? _contributor$agentKin2 : 'external');
152
- if (usesFixedGlyph) {
153
- showContributorIcon();
154
- } else {
155
- image = doc.createElement('img');
156
- image.alt = '';
157
- image.decoding = 'async';
158
- image.draggable = false;
159
- image.loading = 'lazy';
160
- image.setAttribute('style', imageStyle);
161
- unbindImageError = (0, _bindEventListener.bind)(image, {
162
- type: 'error',
163
- listener: showContributorIcon
164
- });
165
- shape.appendChild(image);
166
- image.src = contributor.avatarUrl;
167
- }
127
+ image = doc.createElement('img');
128
+ image.alt = '';
129
+ image.decoding = 'async';
130
+ image.draggable = false;
131
+ image.loading = 'lazy';
132
+ image.setAttribute('style', imageStyle);
133
+ unbindImageError = (0, _bindEventListener.bind)(image, {
134
+ type: 'error',
135
+ listener: showContributorIcon
136
+ });
137
+ shape.appendChild(image);
138
+ image.src = contributor.avatarUrl;
168
139
  } else {
169
140
  showContributorIcon();
170
141
  }
@@ -7,7 +7,7 @@ exports.getContributorTagIcon = void 0;
7
7
  /**
8
8
  * The icons a contributor tag can draw, as inline SVG.
9
9
  *
10
- * Neither `@atlaskit/icon`, `@atlaskit/icon-lab` nor `@atlaskit/logo` exposes a raw-SVG entry
10
+ * Neither `@atlaskit/icon` nor `@atlaskit/icon-lab` exposes a raw-SVG entry
11
11
  * point, so the markup is copied here rather than kept as a React island for these glyphs. Keep in
12
12
  * sync with the sources named on each constant.
13
13
  */
@@ -24,17 +24,9 @@ var AI_BOT_ICON_SVG = "<svg width=\"12\" height=\"12\" fill=\"none\" viewBox=\"0
24
24
  * so keep the generated glyph here rather than introducing a React render boundary.
25
25
  */
26
26
  var AI_AGENT_ICON_SVG = "<svg width=\"12\" height=\"12\" fill=\"none\" viewBox=\"0 0 16 16\" role=\"presentation\" style=\"display:block\"><path fill=\"currentcolor\" fill-rule=\"evenodd\" d=\"M6.971.493a2 2 0 0 1 2.058 0l5 3A2 2 0 0 1 15 5.208v5.584a2 2 0 0 1-.971 1.715l-5 3a2 2 0 0 1-2.058 0l-5-3A2 2 0 0 1 1 10.792V5.208a2 2 0 0 1 .971-1.715zm1.286 1.286a.5.5 0 0 0-.514 0l-5 3a.5.5 0 0 0-.243.429v5.584a.5.5 0 0 0 .243.429l1.098.66a4.25 4.25 0 1 1 8.318 0l1.098-.66a.5.5 0 0 0 .243-.429V5.208a.5.5 0 0 0-.243-.429zM8 8.25a2.75 2.75 0 1 0 0 5.5 2.75 2.75 0 0 0 0-5.5\" clip-rule=\"evenodd\"/></svg>";
27
-
28
- /** Claude hexagon lockup from the brand asset: the hexagon and knot glyph both baked in. */
29
- var CLAUDE_ICON_SVG = "<svg width=\"16\" height=\"18\" viewBox=\"0 0 16 18\" fill=\"none\" role=\"presentation\" style=\"display:block\"><path fill=\"#D97757\" d=\"M14.5007 3.28903L9.50384 0.402344C8.5736 -0.13506 7.42701 -0.134016 6.49774 0.405082L1.49458 3.30759C0.569434 3.8443 0 4.83297 0 5.90253V11.1804C0 12.2519 0.571497 13.2421 1.49931 13.7781L6.49931 16.6666C7.42787 17.203 8.57213 17.203 9.50069 16.6666L14.5007 13.7781C15.4285 13.2421 16 12.2519 16 11.1804V5.88671C16 4.8152 15.4285 3.82504 14.5007 3.28903Z\"/><path fill=\"#FAF9F6\" d=\"M5.19791 10.055L7.0123 9.03681L7.04267 8.94806L7.0123 8.89907H6.92357L6.62 8.88034L5.58321 8.85232L4.68418 8.815L3.81317 8.76826L3.59367 8.72158L3.38818 8.4507L3.4092 8.31526L3.59367 8.1915L3.85754 8.21485L4.44133 8.25455L5.317 8.31526L5.95216 8.35262L6.89321 8.4507H7.04267L7.06368 8.38999L7.0123 8.35262L6.97261 8.31526L6.06658 7.70112L5.08582 7.05196L4.5721 6.67834L4.29422 6.48919L4.15411 6.31172L4.09339 5.92409L4.34559 5.64621L4.68418 5.66956L4.77058 5.69291L5.11384 5.95678L5.84708 6.52422L6.80448 7.22943L6.94459 7.34618L7.00063 7.30649L7.00763 7.27846L6.94459 7.17338L6.42385 6.23233L5.86809 5.27492L5.62057 4.87795L5.55519 4.63977C5.53183 4.54169 5.51549 4.45996 5.51549 4.35956L5.80271 3.96959L5.9615 3.91821L6.34446 3.96959L6.50558 4.10969L6.74377 4.65378L7.12907 5.51077L7.72686 6.676L7.90199 7.0216L7.99539 7.34152L8.0304 7.43959H8.09113V7.38354L8.14018 6.72737L8.23124 5.92175L8.31999 4.88496L8.35032 4.59307L8.49511 4.24279L8.78232 4.05365L9.00649 4.16107L9.19098 4.42494L9.16533 4.5954L9.05555 5.30762L8.84073 6.42381L8.70062 7.17105H8.78232L8.87574 7.07764L9.25401 6.57559L9.88922 5.78165L10.1694 5.4664L10.4963 5.11847L10.7065 4.95267H11.1035L11.3953 5.38701L11.2646 5.83535L10.8559 6.35375L10.5173 6.79276L10.0316 7.4466L9.72807 7.96966L9.7561 8.0117L9.82849 8.00469L10.926 7.77118L11.5191 7.66376L12.2266 7.54233L12.5466 7.69178L12.5816 7.84356L12.4555 8.15414L11.6989 8.34095L10.8116 8.51842L9.48985 8.83136L9.4735 8.84303L9.49223 8.86637L10.0877 8.92241L10.3422 8.93639H10.9657L12.1262 9.02283L12.4298 9.2236L12.612 9.46881L12.5816 9.6556L12.1146 9.89382L11.4841 9.74434L10.013 9.3941L9.50858 9.26797H9.43849V9.31004L9.85882 9.721L10.6294 10.4169L11.5938 11.3136L11.6429 11.5354L11.5191 11.7106L11.3884 11.6918L10.5407 11.0544L10.2137 10.7672L9.4735 10.1436H9.42451V10.209L9.59495 10.4589L10.4963 11.8133L10.543 12.2289L10.4776 12.3644L10.2441 12.4461L9.98726 12.3994L9.45952 11.6592L8.91544 10.8255L8.47645 10.0783L8.42271 10.1086L8.16352 12.8991L8.04207 13.0416L7.76188 13.149L7.52837 12.9715L7.40461 12.6843L7.52837 12.1168L7.67781 11.3766L7.79925 10.7881L7.90899 10.0573L7.97438 9.81443L7.96971 9.79808L7.916 9.80507L7.36491 10.5617L6.5266 11.6942L5.86342 12.4041L5.70463 12.4671L5.42909 12.3247L5.45477 12.0702L5.60889 11.8436L6.5266 10.6761L7.08002 9.95217L7.4373 9.53421L7.43497 9.47349H7.41395L4.97607 11.0567L4.54174 11.1127L4.35493 10.9376L4.37828 10.6504L4.46702 10.557L5.20024 10.0526L5.19791 10.055Z\"/></svg>";
30
-
31
- /** ChatGPT hexagon lockup from the brand asset: the hexagon and knot glyph both baked in. */
32
- var CHATGPT_ICON_SVG = "<svg width=\"16\" height=\"18\" viewBox=\"0 0 16 18\" fill=\"none\" role=\"presentation\" style=\"display:block\"><path fill=\"#14120B\" d=\"M14.5007 3.289L9.50384 0.402314C8.5736 -0.13509 7.42701 -0.134047 6.49774 0.405051L1.49458 3.30756C0.569434 3.84427 0 4.83294 0 5.9025V11.1804C0 12.2519 0.571497 13.2421 1.49931 13.7781L6.49931 16.6666C7.42787 17.203 8.57213 17.203 9.50069 16.6666L14.5007 13.7781C15.4285 13.2421 16 12.2519 16 11.1804V5.88668C16 4.81517 15.4285 3.82501 14.5007 3.289Z\"/><path fill=\"white\" d=\"M12.255 7.39338C12.4813 6.54514 12.2618 5.60258 11.5963 4.93713C10.9309 4.2717 9.9884 4.05217 9.1401 4.27852C8.51863 3.65837 7.59268 3.37721 6.68362 3.62082C5.77456 3.86436 5.11322 4.57087 4.88512 5.41867C4.03733 5.64676 3.33082 6.30819 3.08727 7.21717C2.84372 8.12623 3.12482 9.05218 3.74503 9.67365C3.51868 10.5219 3.73823 11.4645 4.40366 12.1299C5.06909 12.7953 6.01159 13.0149 6.85992 12.7885C7.48137 13.4087 8.4074 13.6898 9.31638 13.4463C10.2254 13.2027 10.8868 12.4962 11.1149 11.6485C11.9627 11.4203 12.6692 10.7589 12.9127 9.84993C13.1563 8.94089 12.8752 8.01491 12.255 7.39347V7.39338ZM11.1878 5.34578C11.6387 5.79673 11.8247 6.41226 11.7457 6.99902L9.62799 5.77632C9.53872 5.72478 9.42871 5.72478 9.3395 5.77632L6.85992 7.20789V6.26917C6.85992 6.20333 6.89519 6.1417 6.95226 6.10877L8.84042 5.01867C9.58854 4.60079 10.5519 4.7099 11.1878 5.34578H11.1878ZM8.00001 7.21703L9.14016 7.87529V9.19183L8.00001 9.85009L6.85984 9.19183V7.87529L8.00001 7.21703ZM5.37616 6.04832C5.38831 5.19153 5.96453 4.41177 6.83319 4.17899C7.44917 4.01397 8.07524 4.16069 8.54389 4.52241L6.42609 5.74505C6.33682 5.79656 6.28184 5.89186 6.28184 5.99489V8.85803L5.4689 8.38867C5.41189 8.35573 5.37616 8.29433 5.37616 8.22846V6.04826V6.04832ZM3.64542 7.36674C3.81044 6.75079 4.25056 6.28191 4.79817 6.05694V8.50232C4.79817 8.60534 4.85315 8.70061 4.94241 8.75216L7.42202 10.1837L6.60904 10.6531C6.55203 10.686 6.48098 10.6862 6.42397 10.6533L4.53581 9.56321C3.79984 9.12427 3.41272 8.23534 3.64542 7.36674ZM4.81224 11.7213C4.36129 11.2704 4.17534 10.6549 4.25429 10.0681L6.37201 11.2908C6.4613 11.3423 6.57129 11.3423 6.6605 11.2908L9.1401 9.85923V10.7979C9.1401 10.8638 9.10483 10.9254 9.04774 10.9584L7.15958 12.0485C6.41145 12.4663 5.44809 12.3572 4.81215 11.7213H4.81224ZM10.6238 11.0188C10.6117 11.8756 10.0355 12.6554 9.1668 12.8881C8.55083 13.0532 7.92476 12.9064 7.45613 12.5447L9.57391 11.322C9.6632 11.2705 9.71816 11.1753 9.71816 11.0722V8.20906L10.5311 8.67845C10.5881 8.71139 10.6238 8.77279 10.6238 8.83866V11.0189V11.0188ZM12.3546 9.70035C12.1896 10.3163 11.7494 10.7852 11.2018 11.0101V8.5648C11.2018 8.46178 11.1469 8.36648 11.0576 8.31496L8.57798 6.88339L9.39095 6.41401C9.44796 6.38107 9.51902 6.38085 9.57603 6.41378L11.4642 7.50388C12.2002 7.94285 12.5873 8.83178 12.3546 9.70035Z\"/></svg>";
33
27
  var markup = {
34
28
  aiAgent: AI_AGENT_ICON_SVG,
35
29
  aiBot: AI_BOT_ICON_SVG,
36
- chatgpt: CHATGPT_ICON_SVG,
37
- claude: CLAUDE_ICON_SVG,
38
30
  person: PERSON_ICON_SVG
39
31
  };
40
32
 
@@ -7,6 +7,7 @@ import { areNodesEqualIgnoreAttrs } from '@atlaskit/editor-common/utils/document
7
7
  import { Mapping } from '@atlaskit/editor-prosemirror/transform';
8
8
  import { Decoration, DecorationSet } from '@atlaskit/editor-prosemirror/view';
9
9
  import { TableMap } from '@atlaskit/editor-tables/table-map';
10
+ import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
10
11
  import { UNSAFE_expValNoExposure } from '@atlaskit/platform-feature-experiments/unsafe-exp-val-no-exposure';
11
12
  import { fg } from '@atlaskit/platform-feature-flags/fg';
12
13
  import { createAttributionColorMap, getAttributionKey, getAttributionKeyForChange, getColorSchemeForChange, isAttributionColoringEnabled, isContributorTagsEnabled } from '../decorations/colorSchemes/attributions';
@@ -29,6 +30,7 @@ import { groupChangesByBlock } from './groupChangesByBlock';
29
30
  import { CELL_CONTENT_OFFSET, groupDeletedColumnChanges } from './groupDeletedColumnChanges';
30
31
  import { isMarkOnlyChange } from './isMarkOnlyChange';
31
32
  import { isOpenTokenOnlyChange } from './isOpenTokenOnlyChange';
33
+ import { normalizeTextNodes } from './normalizeTextNodes';
32
34
  import { optimizeChanges } from './optimizeChanges';
33
35
  import { selectTokenEncoder } from './selectTokenEncoder';
34
36
  import { collapseOverlappingChanges, simplifyChangesWithAttribution } from './simplifyChangesWithAttribution';
@@ -371,7 +373,7 @@ const calculateDiffDecorationsInner = ({
371
373
  } = state;
372
374
  let steppedDoc = originalDoc;
373
375
  const attributionColoringEnabled = isAttributionColoringEnabled(stepAttributions);
374
- const showContributorTags = isContributorTagsEnabled(stepAttributions, contributors);
376
+ const showContributorTags = pluginState.showContributorTags !== false && isContributorTagsEnabled(stepAttributions, contributors);
375
377
  // Tags need the attributed changeset even on diffs with too few actors to be worth colouring.
376
378
  const attributedChangesetEnabled = attributionColoringEnabled || showContributorTags;
377
379
  let simplifiedSteps;
@@ -435,6 +437,10 @@ const calculateDiffDecorationsInner = ({
435
437
  // changes and ignores differences in attributes which don't affect decoration positions
436
438
  if (!areNodesEqualIgnoreAttrs(steppedDoc, tr.doc)) {
437
439
  var _api$analytics;
440
+ // A document parsed from ADF can hold adjacent text nodes with identical marks, while the
441
+ // editor joins them whenever it rebuilds a node (for example when local ids are assigned on
442
+ // load). Positions are identical either way, so the decorations still apply.
443
+ const recoveredViaTextNodeNormalisation = isExperimentEnabled('platform_editor_show_diff_normalise_text_nodes') && areNodesEqualIgnoreAttrs(normalizeTextNodes(steppedDoc), normalizeTextNodes(tr.doc));
438
444
  const recoveredViaContentEquality = areDocsEqualByBlockStructureAndText(steppedDoc, tr.doc);
439
445
  api === null || api === void 0 ? void 0 : (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : _api$analytics.actions.fireAnalyticsEvent({
440
446
  eventType: 'track',
@@ -443,10 +449,11 @@ const calculateDiffDecorationsInner = ({
443
449
  attributes: {
444
450
  docSizeEqual: steppedDoc.nodeSize === tr.doc.nodeSize,
445
451
  colorScheme,
446
- recoveredViaContentEquality
452
+ recoveredViaContentEquality,
453
+ recoveredViaTextNodeNormalisation
447
454
  }
448
455
  });
449
- if (!recoveredViaContentEquality) {
456
+ if (!recoveredViaTextNodeNormalisation && !recoveredViaContentEquality) {
450
457
  return {
451
458
  contributorTags: [],
452
459
  decorations: DecorationSet.empty,
@@ -0,0 +1,16 @@
1
+ import { Fragment } from '@atlaskit/editor-prosemirror/model';
2
+
3
+ /**
4
+ * Returns a copy of `node` in which adjacent text nodes with identical marks are joined, the same
5
+ * normalisation ProseMirror applies whenever it rebuilds a node's content. A document parsed straight
6
+ * from ADF keeps such split runs, so two documents with identical content and positions can still
7
+ * differ in child count.
8
+ */
9
+ export const normalizeTextNodes = node => {
10
+ if (node.isText || node.childCount === 0) {
11
+ return node;
12
+ }
13
+ const children = [];
14
+ node.content.forEach(child => children.push(normalizeTextNodes(child)));
15
+ return node.copy(Fragment.fromArray(children));
16
+ };