@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
@@ -1,3 +1,4 @@
1
+ import { getThirdPartyAgentColor } from '@atlaskit/agent-color/get-third-party-agent-color';
1
2
  import { getParticipantColor } from '@atlaskit/editor-shared-styles/utils';
2
3
  import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
3
4
  import { fg } from '@atlaskit/platform-feature-flags/fg';
@@ -44,48 +45,25 @@ const getAvailableColor = (hashedColor, allocatedColors) => {
44
45
  return isAssignable(hashedColor) ? hashedColor : (_PARTICIPANT_COLOR_SC = PARTICIPANT_COLOR_SCHEMES.find(isAssignable)) !== null && _PARTICIPANT_COLOR_SC !== void 0 ? _PARTICIPANT_COLOR_SC : hashedColor;
45
46
  };
46
47
 
47
- /**
48
- * Normalise the attribution identity once so a valid lookup key always has a valid participant
49
- * colour seed. `userId` is primary; `agentId`, or `agentType` as its fallback, separates shared
50
- * users. The colour seed matches collaboration: the user ID for a human and the agent ID for an
51
- * agent. The agent-type fallback includes the user so different users' agents do not collapse onto
52
- * one colour when NCS omits an agent ID.
53
- */
48
+ /** Account IDs supply both the lookup key and participant colour seed. */
54
49
  const getAttributionIdentity = attribution => {
55
- var _attribution$userId, _attribution$agentId, _attribution$agentTyp;
50
+ var _attribution$userId, _attribution$agentId;
56
51
  const userId = attribution === null || attribution === void 0 ? void 0 : (_attribution$userId = attribution.userId) === null || _attribution$userId === void 0 ? void 0 : _attribution$userId.trim();
57
52
  const agentId = attribution === null || attribution === void 0 ? void 0 : (_attribution$agentId = attribution.agentId) === null || _attribution$agentId === void 0 ? void 0 : _attribution$agentId.trim();
58
- const agentType = attribution === null || attribution === void 0 ? void 0 : (_attribution$agentTyp = attribution.agentType) === null || _attribution$agentTyp === void 0 ? void 0 : _attribution$agentTyp.trim();
59
- let colorSeed;
60
- if (agentId) {
61
- colorSeed = agentId;
62
- } else if (agentType) {
63
- colorSeed = userId ? `user:${userId}|agent:type:${agentType}` : `agent:type:${agentType}`;
64
- } else if (userId) {
65
- colorSeed = userId;
66
- } else {
67
- return undefined;
68
- }
69
- const agentIdentity = agentId || (agentType ? `type:${agentType}` : '');
70
- return {
53
+ const colorSeed = agentId || userId;
54
+ return colorSeed ? {
71
55
  colorSeed,
72
- key: `user:${userId !== null && userId !== void 0 ? userId : ''}|agent:${agentIdentity}`
73
- };
56
+ key: `user:${userId !== null && userId !== void 0 ? userId : ''}|agent:${agentId !== null && agentId !== void 0 ? agentId : ''}`
57
+ } : undefined;
74
58
  };
75
59
 
76
- /** `userId` is primary; `agentId`, or `agentType` as its fallback, separates shared users. */
60
+ /** `userId` is primary; `agentId` separates agents sharing that user. */
77
61
  export const getAttributionKey = attribution => {
78
62
  var _getAttributionIdenti;
79
63
  return (_getAttributionIdenti = getAttributionIdentity(attribution)) === null || _getAttributionIdenti === void 0 ? void 0 : _getAttributionIdenti.key;
80
64
  };
81
65
  const isDiffAgentBrand = agentBrand => agentBrand !== undefined && DIFF_AGENT_BRANDS.has(agentBrand);
82
66
 
83
- /** Prefers the resolved branded profile over the raw attribution agent type. */
84
- const getAgentBrandForColor = (attribution, contributors) => {
85
- var _contributors, _getAttributionKey;
86
- const agentBrand = contributors === null || contributors === void 0 ? void 0 : (_contributors = contributors[(_getAttributionKey = getAttributionKey(attribution)) !== null && _getAttributionKey !== void 0 ? _getAttributionKey : '']) === null || _contributors === void 0 ? void 0 : _contributors.agentKind;
87
- return isDiffAgentBrand(agentBrand) ? agentBrand : attribution === null || attribution === void 0 ? void 0 : attribution.agentType;
88
- };
89
67
  /**
90
68
  * Public contributor list to internal key-addressed record. A contributor whose attribution carries
91
69
  * no identity is dropped, since it could never match a step. Later entries win.
@@ -131,7 +109,18 @@ export const isAttributionColoringEnabled = stepAttributions => {
131
109
  * a tag only has to name one.
132
110
  */
133
111
  export const isContributorTagsEnabled = (stepAttributions, contributors) => areAttributionColorGatesEnabled() && Boolean(stepAttributions === null || stepAttributions === void 0 ? void 0 : stepAttributions.some(attribution => getAttributionKey(attribution) !== undefined)) && contributors !== undefined && Object.keys(contributors).length > 0;
134
- const getAttributionParticipantColor = (attribution, identity, contributors) => getParticipantColor(identity.colorSeed, getAgentBrandForColor(attribution, contributors), attribution === null || attribution === void 0 ? void 0 : attribution.agentNamedId);
112
+ const getAttributionParticipantColor = (attribution, identity, contributors) => {
113
+ var _ref, _getThirdPartyAgentCo, _attribution$agentId2;
114
+ const contributor = contributors === null || contributors === void 0 ? void 0 : contributors[identity.key];
115
+ const brand = isDiffAgentBrand(contributor === null || contributor === void 0 ? void 0 : contributor.agentKind) ? contributor === null || contributor === void 0 ? void 0 : contributor.agentKind : undefined;
116
+ const agentName = (_ref = brand !== null && brand !== void 0 ? brand : (_getThirdPartyAgentCo = getThirdPartyAgentColor({
117
+ agentNamedId: attribution === null || attribution === void 0 ? void 0 : attribution.agentNamedId,
118
+ agentName: contributor === null || contributor === void 0 ? void 0 : contributor.name
119
+ })) === null || _getThirdPartyAgentCo === void 0 ? void 0 : _getThirdPartyAgentCo.name) !== null && _ref !== void 0 ? _ref : 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;
120
+ return getParticipantColor(identity.colorSeed, agentName,
121
+ // Keep an explicitly resolved brand consistent with the contributor's icon.
122
+ brand ? undefined : attribution === null || attribution === void 0 ? void 0 : attribution.agentNamedId);
123
+ };
135
124
  export const createAttributionColorMap = (stepAttributions, contributors) => {
136
125
  const colors = new Map();
137
126
  const allocatedColors = new Set();
@@ -1,11 +1,5 @@
1
1
  import { getThirdPartyAgentColor } from '@atlaskit/agent-color/get-third-party-agent-color';
2
2
  import { getAttributionKey } from './decorations/colorSchemes/attributions';
3
-
4
- /** A brand name, so a literal rather than a translated message. */
5
- const ROVO_AGENT_NAME = 'Rovo';
6
-
7
- /** `agentType` values that render as "Rovo" rather than as a generic external agent. */
8
- const ROVO_AGENT_TYPES = new Set(['convo-ai', 'rovo_chat']);
9
3
  /** `@atlaskit/agent-color` brands show-diff renders with a dedicated presentation. */
10
4
  const SHOW_DIFF_AGENT_BRANDS = {
11
5
  'agent-brand-claude': 'claude',
@@ -23,29 +17,6 @@ const SHOW_DIFF_AGENT_BRANDS = {
23
17
  'agent-brand-antigravity': 'antigravity',
24
18
  'agent-brand-copilot': 'copilot'
25
19
  };
26
-
27
- /**
28
- * Brands that prefer the account's real avatar over their fixed glyph when the invoking profile
29
- * has one. Brands with a fixed glyph (Claude, ChatGPT, Amplitude, Cortex, Databricks, Gemini, Manus,
30
- * Slack) intentionally keep it always (see `AGENT_KIND_ICONS` in `contributorAvatarRenderer.ts`).
31
- */
32
- const AVATAR_ELIGIBLE_BRANDS = new Set(['antigravity', 'copilot', 'figma', 'lovable', 'replit', 'rovo']);
33
-
34
- /** Known external agent types emitted by Confluence's agent identification service that carry
35
- * no `@atlaskit/agent-color` brand — a generic labelled fallback. */
36
- const EXTERNAL_AGENT_NAMES = {
37
- vscode: 'VS Code',
38
- hermes: 'Hermes',
39
- runlayer: 'Runlayer',
40
- zed: 'Zed',
41
- ampcode: 'Ampcode',
42
- devin_cli: 'Devin CLI',
43
- pi_agent: 'Pi Agent'
44
- };
45
- const hasAgentIdentity = attribution => {
46
- var _attribution$agentId, _attribution$agentTyp;
47
- 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()));
48
- };
49
20
  const toProfilesByAccountId = profiles => {
50
21
  const profilesByAccountId = new Map();
51
22
  profiles === null || profiles === void 0 ? void 0 : profiles.forEach(profile => {
@@ -66,52 +37,31 @@ const resolveUser = (userId, profilesByAccountId) => {
66
37
  } : undefined;
67
38
  };
68
39
 
69
- /** Resolve branding or a profile first, then a known type name or the external-agent fallback. */
40
+ /** Resolve branding from named metadata or the account profile. */
70
41
  const resolveAgent = (attribution, profilesByAccountId) => {
71
- var _attribution$agentTyp2, _attribution$agentId2;
72
- const agentType = (_attribution$agentTyp2 = attribution.agentType) === null || _attribution$agentTyp2 === void 0 ? void 0 : _attribution$agentTyp2.trim().toLowerCase();
73
- // `agentType` may be a named id (e.g. `mcp_lovable_agent`) or a display name (e.g. `lovable`).
74
- const brand = agentType ? getThirdPartyAgentColor({
75
- agentNamedId: agentType,
76
- agentName: agentType
77
- }) : undefined;
78
- const diffBrand = brand ? SHOW_DIFF_AGENT_BRANDS[brand.scheme] : undefined;
79
- const agentId = (_attribution$agentId2 = attribution.agentId) === null || _attribution$agentId2 === void 0 ? void 0 : _attribution$agentId2.trim();
42
+ var _attribution$agentId, _agentProfile$agentBr;
43
+ const agentId = (_attribution$agentId = attribution.agentId) === null || _attribution$agentId === void 0 ? void 0 : _attribution$agentId.trim();
80
44
  const agentProfile = agentId ? profilesByAccountId.get(agentId) : undefined;
81
- if (diffBrand && brand) {
82
- return {
83
- agentKind: diffBrand,
84
- kind: 'agent',
85
- name: brand.name,
86
- ...(AVATAR_ELIGIBLE_BRANDS.has(diffBrand) && agentProfile !== null && agentProfile !== void 0 && agentProfile.avatarUrl ? {
87
- avatarUrl: agentProfile.avatarUrl
88
- } : {})
89
- };
90
- }
91
- if (agentProfile) {
45
+ const brand = getThirdPartyAgentColor({
46
+ agentNamedId: attribution.agentNamedId,
47
+ agentName: agentProfile === null || agentProfile === void 0 ? void 0 : agentProfile.name
48
+ });
49
+ const 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;
50
+ if (agentProfile || agentBrand) {
51
+ var _ref, _agentProfile$name;
92
52
  return {
93
- ...(agentProfile.agentBrand ? {
94
- agentKind: agentProfile.agentBrand
53
+ ...(agentBrand ? {
54
+ agentKind: agentBrand
95
55
  } : {}),
96
- avatarUrl: agentProfile.avatarUrl,
97
- kind: 'agent',
98
- name: agentProfile.name
99
- };
100
- }
101
-
102
- // Unknown types keep an empty name for the tag's localised "External agent" label.
103
- if (agentType && ROVO_AGENT_TYPES.has(agentType)) {
104
- return {
105
- agentKind: 'rovo',
56
+ avatarUrl: agentProfile === null || agentProfile === void 0 ? void 0 : agentProfile.avatarUrl,
106
57
  kind: 'agent',
107
- name: ROVO_AGENT_NAME
58
+ 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 : ''
108
59
  };
109
60
  }
110
- const externalName = agentType ? EXTERNAL_AGENT_NAMES[agentType] : undefined;
111
61
  return {
112
62
  agentKind: 'external',
113
63
  kind: 'agent',
114
- name: externalName !== null && externalName !== void 0 ? externalName : ''
64
+ name: ''
115
65
  };
116
66
  };
117
67
 
@@ -133,14 +83,14 @@ export const resolveDiffContributors = (stepsWithAttribution, profiles) => {
133
83
  for (const {
134
84
  stepAttribution
135
85
  } of stepsWithAttribution) {
136
- var _stepAttribution$user;
86
+ var _stepAttribution$user, _stepAttribution$agen;
137
87
  // An attribution naming nobody (e.g. one carrying only `wasOffline`) has nobody to credit.
138
88
  const actor = stepAttribution ? getAttributionKey(stepAttribution) : undefined;
139
89
  if (!stepAttribution || !actor || creditedActors.has(actor)) {
140
90
  continue;
141
91
  }
142
92
  const userId = (_stepAttribution$user = stepAttribution.userId) === null || _stepAttribution$user === void 0 ? void 0 : _stepAttribution$user.trim();
143
- if (hasAgentIdentity(stepAttribution)) {
93
+ if ((_stepAttribution$agen = stepAttribution.agentId) !== null && _stepAttribution$agen !== void 0 && _stepAttribution$agen.trim()) {
144
94
  const agent = resolveAgent(stepAttribution, profilesByAccountId);
145
95
  if (!userId) {
146
96
  // Agent mode: the agent acted on its own, so it is credited on its own.
@@ -20,10 +20,12 @@ const normalizeShowDiffParams = params => {
20
20
  const {
21
21
  stepsWithAttribution,
22
22
  contributorProfiles,
23
+ showContributorTags = true,
23
24
  ...rest
24
25
  } = params;
25
26
  return {
26
27
  ...rest,
28
+ showContributorTags,
27
29
  // Resolve at boundary to keep contributor model and attribution format internal.
28
30
  contributors: areAttributionColorGatesEnabled() ? resolveContributors(resolveDiffContributors(stepsWithAttribution, contributorProfiles)) : undefined,
29
31
  steps: stepsWithAttribution.map(({
@@ -11,9 +11,8 @@
11
11
  * Attribution for a ProseMirror step.
12
12
  *
13
13
  * `userId` is the primary actor identity. `agentId` distinguishes multiple agents (or an agent
14
- * from its user) when they share that user ID, with `agentType` used as a fallback when `agentId`
15
- * is empty. `agentNamedId` carries a stable external configuration reference for presentation
16
- * overrides. `wasOffline` preserves additional provenance without affecting identity.
14
+ * from its user) when they share that user ID. `agentNamedId` carries a stable external
15
+ * configuration reference for presentation overrides. `wasOffline` preserves additional provenance without affecting identity.
17
16
  */
18
17
 
19
18
  /** Keeps a step and its attribution coupled through mapping and filtering. */
@@ -4,26 +4,6 @@ import { BORDER_WIDTH } from '@atlaskit/avatar/constants/default';
4
4
  import { convertToInlineCss } from '@atlaskit/editor-common/css-helper';
5
5
  import { getContributorTagIcon } from './contributorTagIcons';
6
6
 
7
- /**
8
- * Agent kinds with a custom contributor-tag icon, used as a fallback glyph when no `avatarUrl` is
9
- * available or its image fails to load (see `showContributorIcon`). Rovo/Figma/Lovable/Replit have
10
- * no entry here — they fall back to the generic `aiAgent` glyph. The declared type stays the wider
11
- * `TagContributor['agentKind']` so indexing below (with `'identified' | 'external'` in play too)
12
- * stays safely partial.
13
- */
14
- const AGENT_KIND_ICONS = {
15
- claude: 'claude',
16
- chatgpt: 'chatgpt'
17
- };
18
-
19
- /**
20
- * Brands that always render their fixed glyph, even when the contributor also carries an
21
- * `avatarUrl`. Rovo/Figma/Lovable/Replit are excluded here and use the real avatar when
22
- * `resolveDiffContributors` supplies one, falling back to the generic `aiAgent` glyph on load
23
- * failure.
24
- */
25
- const FIXED_GLYPH_AGENT_KINDS = new Set(['claude', 'chatgpt']);
26
-
27
7
  /** The size `@atlaskit/avatar`'s `xxsmall` rendered at. */
28
8
  const AVATAR_SIZE = AVATAR_SIZES.xxsmall;
29
9
  /** `xxsmall` also carried a `space.025` margin — the same 2px — which is where the ring is painted. */
@@ -125,7 +105,7 @@ export const contributorAvatarRenderer = ({
125
105
  let image;
126
106
  let unbindImageError;
127
107
  const showContributorIcon = () => {
128
- var _unbindImageError, _image, _AGENT_KIND_ICONS, _contributor$agentKin;
108
+ var _unbindImageError, _image;
129
109
  (_unbindImageError = unbindImageError) === null || _unbindImageError === void 0 ? void 0 : _unbindImageError();
130
110
  unbindImageError = undefined;
131
111
  (_image = image) === null || _image === void 0 ? void 0 : _image.remove();
@@ -137,30 +117,21 @@ export const contributorAvatarRenderer = ({
137
117
  shape.appendChild(fallback);
138
118
  return;
139
119
  }
140
- const 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';
141
- shape.appendChild(getContributorTagIcon(icon, doc));
120
+ shape.appendChild(getContributorTagIcon('aiAgent', doc));
142
121
  };
143
122
  if (contributor.avatarUrl) {
144
- var _contributor$agentKin2;
145
- // Claude/ChatGPT always use their fixed glyph, even when the profile also has an avatar
146
- // URL. Other agents (e.g. Rovo/Figma/Lovable/Replit/Slack) and users prefer the real avatar.
147
- const usesFixedGlyph = isAgent && FIXED_GLYPH_AGENT_KINDS.has((_contributor$agentKin2 = contributor.agentKind) !== null && _contributor$agentKin2 !== void 0 ? _contributor$agentKin2 : 'external');
148
- if (usesFixedGlyph) {
149
- showContributorIcon();
150
- } else {
151
- image = doc.createElement('img');
152
- image.alt = '';
153
- image.decoding = 'async';
154
- image.draggable = false;
155
- image.loading = 'lazy';
156
- image.setAttribute('style', imageStyle);
157
- unbindImageError = bind(image, {
158
- type: 'error',
159
- listener: showContributorIcon
160
- });
161
- shape.appendChild(image);
162
- image.src = contributor.avatarUrl;
163
- }
123
+ image = doc.createElement('img');
124
+ image.alt = '';
125
+ image.decoding = 'async';
126
+ image.draggable = false;
127
+ image.loading = 'lazy';
128
+ image.setAttribute('style', imageStyle);
129
+ unbindImageError = bind(image, {
130
+ type: 'error',
131
+ listener: showContributorIcon
132
+ });
133
+ shape.appendChild(image);
134
+ image.src = contributor.avatarUrl;
164
135
  } else {
165
136
  showContributorIcon();
166
137
  }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * The icons a contributor tag can draw, as inline SVG.
3
3
  *
4
- * Neither `@atlaskit/icon`, `@atlaskit/icon-lab` nor `@atlaskit/logo` exposes a raw-SVG entry
4
+ * Neither `@atlaskit/icon` nor `@atlaskit/icon-lab` exposes a raw-SVG entry
5
5
  * point, so the markup is copied here rather than kept as a React island for these glyphs. Keep in
6
6
  * sync with the sources named on each constant.
7
7
  */
@@ -18,17 +18,9 @@ const AI_BOT_ICON_SVG = `<svg width="12" height="12" fill="none" viewBox="0 0 16
18
18
  * so keep the generated glyph here rather than introducing a React render boundary.
19
19
  */
20
20
  const 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>`;
21
-
22
- /** Claude hexagon lockup from the brand asset: the hexagon and knot glyph both baked in. */
23
- const 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>`;
24
-
25
- /** ChatGPT hexagon lockup from the brand asset: the hexagon and knot glyph both baked in. */
26
- const 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>`;
27
21
  const markup = {
28
22
  aiAgent: AI_AGENT_ICON_SVG,
29
23
  aiBot: AI_BOT_ICON_SVG,
30
- chatgpt: CHATGPT_ICON_SVG,
31
- claude: CLAUDE_ICON_SVG,
32
24
  person: PERSON_ICON_SVG
33
25
  };
34
26
 
@@ -15,6 +15,7 @@ import { areNodesEqualIgnoreAttrs } from '@atlaskit/editor-common/utils/document
15
15
  import { Mapping } from '@atlaskit/editor-prosemirror/transform';
16
16
  import { Decoration, DecorationSet } from '@atlaskit/editor-prosemirror/view';
17
17
  import { TableMap } from '@atlaskit/editor-tables/table-map';
18
+ import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
18
19
  import { UNSAFE_expValNoExposure } from '@atlaskit/platform-feature-experiments/unsafe-exp-val-no-exposure';
19
20
  import { fg } from '@atlaskit/platform-feature-flags/fg';
20
21
  import { createAttributionColorMap, getAttributionKey, getAttributionKeyForChange, getColorSchemeForChange, isAttributionColoringEnabled, isContributorTagsEnabled } from '../decorations/colorSchemes/attributions';
@@ -37,6 +38,7 @@ import { groupChangesByBlock } from './groupChangesByBlock';
37
38
  import { CELL_CONTENT_OFFSET, groupDeletedColumnChanges } from './groupDeletedColumnChanges';
38
39
  import { isMarkOnlyChange } from './isMarkOnlyChange';
39
40
  import { isOpenTokenOnlyChange } from './isOpenTokenOnlyChange';
41
+ import { normalizeTextNodes } from './normalizeTextNodes';
40
42
  import { optimizeChanges } from './optimizeChanges';
41
43
  import { selectTokenEncoder } from './selectTokenEncoder';
42
44
  import { collapseOverlappingChanges, simplifyChangesWithAttribution } from './simplifyChangesWithAttribution';
@@ -399,7 +401,7 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref8
399
401
  var tr = state.tr;
400
402
  var steppedDoc = originalDoc;
401
403
  var attributionColoringEnabled = isAttributionColoringEnabled(stepAttributions);
402
- var showContributorTags = isContributorTagsEnabled(stepAttributions, contributors);
404
+ var showContributorTags = pluginState.showContributorTags !== false && isContributorTagsEnabled(stepAttributions, contributors);
403
405
  // Tags need the attributed changeset even on diffs with too few actors to be worth colouring.
404
406
  var attributedChangesetEnabled = attributionColoringEnabled || showContributorTags;
405
407
  var simplifiedSteps;
@@ -475,6 +477,10 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref8
475
477
  // changes and ignores differences in attributes which don't affect decoration positions
476
478
  if (!areNodesEqualIgnoreAttrs(steppedDoc, tr.doc)) {
477
479
  var _api$analytics;
480
+ // A document parsed from ADF can hold adjacent text nodes with identical marks, while the
481
+ // editor joins them whenever it rebuilds a node (for example when local ids are assigned on
482
+ // load). Positions are identical either way, so the decorations still apply.
483
+ var recoveredViaTextNodeNormalisation = isExperimentEnabled('platform_editor_show_diff_normalise_text_nodes') && areNodesEqualIgnoreAttrs(normalizeTextNodes(steppedDoc), normalizeTextNodes(tr.doc));
478
484
  var recoveredViaContentEquality = areDocsEqualByBlockStructureAndText(steppedDoc, tr.doc);
479
485
  api === null || api === void 0 || (_api$analytics = api.analytics) === null || _api$analytics === void 0 || _api$analytics.actions.fireAnalyticsEvent({
480
486
  eventType: 'track',
@@ -483,10 +489,11 @@ var calculateDiffDecorationsInner = function calculateDiffDecorationsInner(_ref8
483
489
  attributes: {
484
490
  docSizeEqual: steppedDoc.nodeSize === tr.doc.nodeSize,
485
491
  colorScheme: colorScheme,
486
- recoveredViaContentEquality: recoveredViaContentEquality
492
+ recoveredViaContentEquality: recoveredViaContentEquality,
493
+ recoveredViaTextNodeNormalisation: recoveredViaTextNodeNormalisation
487
494
  }
488
495
  });
489
- if (!recoveredViaContentEquality) {
496
+ if (!recoveredViaTextNodeNormalisation && !recoveredViaContentEquality) {
490
497
  return {
491
498
  contributorTags: [],
492
499
  decorations: DecorationSet.empty,
@@ -0,0 +1,19 @@
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
+ var _normalizeTextNodes = function normalizeTextNodes(node) {
10
+ if (node.isText || node.childCount === 0) {
11
+ return node;
12
+ }
13
+ var children = [];
14
+ node.content.forEach(function (child) {
15
+ return children.push(_normalizeTextNodes(child));
16
+ });
17
+ return node.copy(Fragment.fromArray(children));
18
+ };
19
+ export { _normalizeTextNodes as normalizeTextNodes };
@@ -8,6 +8,7 @@ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t =
8
8
  function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; }
9
9
  function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
10
10
  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; }
11
+ import { getThirdPartyAgentColor } from '@atlaskit/agent-color/get-third-party-agent-color';
11
12
  import { getParticipantColor } from '@atlaskit/editor-shared-styles/utils';
12
13
  import { isExperimentEnabled } from '@atlaskit/platform-feature-experiments/is-experiment-enabled';
13
14
  import { fg } from '@atlaskit/platform-feature-flags/fg';
@@ -56,36 +57,19 @@ var getAvailableColor = function getAvailableColor(hashedColor, allocatedColors)
56
57
  return isAssignable(hashedColor) ? hashedColor : (_PARTICIPANT_COLOR_SC = PARTICIPANT_COLOR_SCHEMES.find(isAssignable)) !== null && _PARTICIPANT_COLOR_SC !== void 0 ? _PARTICIPANT_COLOR_SC : hashedColor;
57
58
  };
58
59
 
59
- /**
60
- * Normalise the attribution identity once so a valid lookup key always has a valid participant
61
- * colour seed. `userId` is primary; `agentId`, or `agentType` as its fallback, separates shared
62
- * users. The colour seed matches collaboration: the user ID for a human and the agent ID for an
63
- * agent. The agent-type fallback includes the user so different users' agents do not collapse onto
64
- * one colour when NCS omits an agent ID.
65
- */
60
+ /** Account IDs supply both the lookup key and participant colour seed. */
66
61
  var getAttributionIdentity = function getAttributionIdentity(attribution) {
67
- var _attribution$userId, _attribution$agentId, _attribution$agentTyp;
62
+ var _attribution$userId, _attribution$agentId;
68
63
  var userId = attribution === null || attribution === void 0 || (_attribution$userId = attribution.userId) === null || _attribution$userId === void 0 ? void 0 : _attribution$userId.trim();
69
64
  var agentId = attribution === null || attribution === void 0 || (_attribution$agentId = attribution.agentId) === null || _attribution$agentId === void 0 ? void 0 : _attribution$agentId.trim();
70
- var agentType = attribution === null || attribution === void 0 || (_attribution$agentTyp = attribution.agentType) === null || _attribution$agentTyp === void 0 ? void 0 : _attribution$agentTyp.trim();
71
- var colorSeed;
72
- if (agentId) {
73
- colorSeed = agentId;
74
- } else if (agentType) {
75
- colorSeed = userId ? "user:".concat(userId, "|agent:type:").concat(agentType) : "agent:type:".concat(agentType);
76
- } else if (userId) {
77
- colorSeed = userId;
78
- } else {
79
- return undefined;
80
- }
81
- var agentIdentity = agentId || (agentType ? "type:".concat(agentType) : '');
82
- return {
65
+ var colorSeed = agentId || userId;
66
+ return colorSeed ? {
83
67
  colorSeed: colorSeed,
84
- key: "user:".concat(userId !== null && userId !== void 0 ? userId : '', "|agent:").concat(agentIdentity)
85
- };
68
+ key: "user:".concat(userId !== null && userId !== void 0 ? userId : '', "|agent:").concat(agentId !== null && agentId !== void 0 ? agentId : '')
69
+ } : undefined;
86
70
  };
87
71
 
88
- /** `userId` is primary; `agentId`, or `agentType` as its fallback, separates shared users. */
72
+ /** `userId` is primary; `agentId` separates agents sharing that user. */
89
73
  export var getAttributionKey = function getAttributionKey(attribution) {
90
74
  var _getAttributionIdenti;
91
75
  return (_getAttributionIdenti = getAttributionIdentity(attribution)) === null || _getAttributionIdenti === void 0 ? void 0 : _getAttributionIdenti.key;
@@ -94,12 +78,6 @@ var isDiffAgentBrand = function isDiffAgentBrand(agentBrand) {
94
78
  return agentBrand !== undefined && DIFF_AGENT_BRANDS.has(agentBrand);
95
79
  };
96
80
 
97
- /** Prefers the resolved branded profile over the raw attribution agent type. */
98
- var getAgentBrandForColor = function getAgentBrandForColor(attribution, contributors) {
99
- var _contributors, _getAttributionKey;
100
- 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;
101
- return isDiffAgentBrand(agentBrand) ? agentBrand : attribution === null || attribution === void 0 ? void 0 : attribution.agentType;
102
- };
103
81
  /**
104
82
  * Public contributor list to internal key-addressed record. A contributor whose attribution carries
105
83
  * no identity is dropped, since it could never match a step. Later entries win.
@@ -159,7 +137,16 @@ export var isContributorTagsEnabled = function isContributorTagsEnabled(stepAttr
159
137
  })) && contributors !== undefined && Object.keys(contributors).length > 0;
160
138
  };
161
139
  var getAttributionParticipantColor = function getAttributionParticipantColor(attribution, identity, contributors) {
162
- return getParticipantColor(identity.colorSeed, getAgentBrandForColor(attribution, contributors), attribution === null || attribution === void 0 ? void 0 : attribution.agentNamedId);
140
+ var _ref2, _getThirdPartyAgentCo, _attribution$agentId2;
141
+ var contributor = contributors === null || contributors === void 0 ? void 0 : contributors[identity.key];
142
+ var brand = isDiffAgentBrand(contributor === null || contributor === void 0 ? void 0 : contributor.agentKind) ? contributor === null || contributor === void 0 ? void 0 : contributor.agentKind : undefined;
143
+ var agentName = (_ref2 = brand !== null && brand !== void 0 ? brand : (_getThirdPartyAgentCo = getThirdPartyAgentColor({
144
+ agentNamedId: attribution === null || attribution === void 0 ? void 0 : attribution.agentNamedId,
145
+ agentName: contributor === null || contributor === void 0 ? void 0 : contributor.name
146
+ })) === 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;
147
+ return getParticipantColor(identity.colorSeed, agentName,
148
+ // Keep an explicitly resolved brand consistent with the contributor's icon.
149
+ brand ? undefined : attribution === null || attribution === void 0 ? void 0 : attribution.agentNamedId);
163
150
  };
164
151
  export var createAttributionColorMap = function createAttributionColorMap(stepAttributions, contributors) {
165
152
  var colors = new Map();