@atlaskit/editor-plugin-autocomplete 2.4.0 → 2.5.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 +39 -0
  2. package/afm-cc/tsconfig.json +3 -0
  3. package/afm-products/tsconfig.json +3 -0
  4. package/dist/cjs/analytics/ufo.js +111 -0
  5. package/dist/cjs/pm-plugins/autocomplete-plugin.js +126 -64
  6. package/dist/cjs/pm-plugins/local-slow-lane-client.js +452 -0
  7. package/dist/cjs/pm-plugins/slow-lane-client.js +45 -16
  8. package/dist/cjs/pm-plugins/text-predictor.js +72 -40
  9. package/dist/es2019/analytics/ufo.js +110 -0
  10. package/dist/es2019/pm-plugins/autocomplete-plugin.js +132 -72
  11. package/dist/es2019/pm-plugins/local-slow-lane-client.js +361 -0
  12. package/dist/es2019/pm-plugins/slow-lane-client.js +29 -2
  13. package/dist/es2019/pm-plugins/text-predictor.js +48 -15
  14. package/dist/esm/analytics/ufo.js +105 -0
  15. package/dist/esm/pm-plugins/autocomplete-plugin.js +126 -64
  16. package/dist/esm/pm-plugins/local-slow-lane-client.js +443 -0
  17. package/dist/esm/pm-plugins/slow-lane-client.js +45 -16
  18. package/dist/esm/pm-plugins/text-predictor.js +73 -40
  19. package/dist/types/analytics/ufo.d.ts +38 -0
  20. package/dist/types/pm-plugins/autocomplete-plugin.d.ts +5 -0
  21. package/dist/types/pm-plugins/local-slow-lane-client.d.ts +102 -0
  22. package/dist/types-ts4.5/analytics/ufo.d.ts +38 -0
  23. package/dist/types-ts4.5/pm-plugins/autocomplete-plugin.d.ts +5 -0
  24. package/dist/types-ts4.5/pm-plugins/local-slow-lane-client.d.ts +102 -0
  25. package/package.json +7 -2
  26. package/src/analytics/ufo.ts +132 -0
  27. package/src/pm-plugins/autocomplete-plugin.ts +125 -64
  28. package/src/pm-plugins/local-slow-lane-client.ts +487 -0
  29. package/src/pm-plugins/slow-lane-client.ts +28 -2
  30. package/src/pm-plugins/text-predictor.ts +42 -12
  31. package/tsconfig.app.json +3 -0
@@ -11,6 +11,7 @@ var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/sli
11
11
  var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
12
12
  var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
13
13
  var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
14
+ var _ufo = require("../analytics/ufo");
14
15
  var _l3_vocabulary = _interopRequireDefault(require("./data/l3_vocabulary.json"));
15
16
  var _vocabulary_10k = _interopRequireDefault(require("./data/vocabulary_10k.json"));
16
17
  var _word_index_10k = _interopRequireDefault(require("./data/word_index_10k.json"));
@@ -709,38 +710,46 @@ var loadVectorsAsync = exports.loadVectorsAsync = /*#__PURE__*/function () {
709
710
  return _context.abrupt("return");
710
711
  case 5:
711
712
  vectorsLoadStarted = true;
712
- _context.prev = 6;
713
- _context.next = 9;
713
+ (0, _ufo.startExp)(_ufo.EXPERIENCE_NAME.LOAD_VECTORS, 'singleton');
714
+ _context.prev = 7;
715
+ _context.next = 10;
714
716
  return options.getBinaryUrl();
715
- case 9:
717
+ case 10:
716
718
  url = _context.sent;
717
- _context.next = 17;
719
+ _context.next = 19;
718
720
  break;
719
- case 12:
720
- _context.prev = 12;
721
- _context.t0 = _context["catch"](6);
721
+ case 13:
722
+ _context.prev = 13;
723
+ _context.t0 = _context["catch"](7);
722
724
  vectorsLoadStarted = false;
725
+ (0, _ufo.failExp)(_ufo.EXPERIENCE_NAME.LOAD_VECTORS, 'singleton', {
726
+ errorType: 'resolve_url'
727
+ });
723
728
  // eslint-disable-next-line no-console
724
729
  console.warn('[text-predictor] Failed to resolve vectors URL:', _context.t0);
725
730
  return _context.abrupt("return");
726
- case 17:
727
- _context.prev = 17;
728
- _context.next = 20;
731
+ case 19:
732
+ _context.prev = 19;
733
+ _context.next = 22;
729
734
  return fetch(url);
730
- case 20:
735
+ case 22:
731
736
  res = _context.sent;
732
737
  if (res.ok) {
733
- _context.next = 25;
738
+ _context.next = 28;
734
739
  break;
735
740
  }
736
741
  vectorsLoadStarted = false;
742
+ (0, _ufo.failExp)(_ufo.EXPERIENCE_NAME.LOAD_VECTORS, 'singleton', {
743
+ status: res.status,
744
+ errorType: 'http_error'
745
+ });
737
746
  // eslint-disable-next-line no-console
738
747
  console.warn("[text-predictor] Failed to load vectors: ".concat(res.status));
739
748
  return _context.abrupt("return");
740
- case 25:
741
- _context.next = 27;
749
+ case 28:
750
+ _context.next = 30;
742
751
  return res.arrayBuffer();
743
- case 27:
752
+ case 30:
744
753
  buffer = _context.sent;
745
754
  float32 = new Float32Array(buffer);
746
755
  wordIndex = _word_index_10k.default;
@@ -751,6 +760,11 @@ var loadVectorsAsync = exports.loadVectorsAsync = /*#__PURE__*/function () {
751
760
  wordIndex: wordIndex,
752
761
  dim: dim
753
762
  };
763
+ (0, _ufo.succeedExp)(_ufo.EXPERIENCE_NAME.LOAD_VECTORS, 'singleton', {
764
+ wordCount: nWords,
765
+ dim: dim,
766
+ sizeBytes: float32.byteLength
767
+ });
754
768
  if ((0, _debugMode.isAutocompleteDebugEnabled)()) {
755
769
  // eslint-disable-next-line no-console
756
770
  console.log('[text-predictor] Vectors loaded:', {
@@ -759,19 +773,22 @@ var loadVectorsAsync = exports.loadVectorsAsync = /*#__PURE__*/function () {
759
773
  sizeBytes: float32.byteLength
760
774
  });
761
775
  }
762
- _context.next = 40;
776
+ _context.next = 45;
763
777
  break;
764
- case 36:
765
- _context.prev = 36;
766
- _context.t1 = _context["catch"](17);
778
+ case 40:
779
+ _context.prev = 40;
780
+ _context.t1 = _context["catch"](19);
767
781
  vectorsLoadStarted = false;
782
+ (0, _ufo.failExp)(_ufo.EXPERIENCE_NAME.LOAD_VECTORS, 'singleton', {
783
+ errorType: 'network'
784
+ });
768
785
  // eslint-disable-next-line no-console
769
786
  console.warn('[text-predictor] Failed to load vectors:', _context.t1);
770
- case 40:
787
+ case 45:
771
788
  case "end":
772
789
  return _context.stop();
773
790
  }
774
- }, _callee, null, [[6, 12], [17, 36]]);
791
+ }, _callee, null, [[7, 13], [19, 40]]);
775
792
  }));
776
793
  return function loadVectorsAsync(_x) {
777
794
  return _ref6.apply(this, arguments);
@@ -781,24 +798,39 @@ var initVectors = exports.initVectors = function initVectors(store) {
781
798
  vectorStore = store;
782
799
  };
783
800
  var loadDefaultVocabulary = exports.loadDefaultVocabulary = function loadDefaultVocabulary() {
784
- // 1. Load the Atlassian Domain (L2)
785
- var data = _vocabulary_10k.default;
786
- var terms = Object.entries(data.words).map(function (_ref7) {
787
- var _ref8 = (0, _slicedToArray2.default)(_ref7, 2),
788
- word = _ref8[0],
789
- stats = _ref8[1];
790
- return {
791
- word: word,
792
- freq: stats.freq,
793
- docFreq: stats.doc_freq,
794
- authorFreq: stats.author_freq
795
- };
796
- });
797
- initVocabulary({
798
- terms: terms
799
- });
801
+ if (isInitialized) {
802
+ return;
803
+ }
804
+ (0, _ufo.startExp)(_ufo.EXPERIENCE_NAME.LOAD_VOCABULARY, 'singleton');
805
+ try {
806
+ // 1. Load the Atlassian Domain (L2)
807
+ var data = _vocabulary_10k.default;
808
+ var terms = Object.entries(data.words).map(function (_ref7) {
809
+ var _ref8 = (0, _slicedToArray2.default)(_ref7, 2),
810
+ word = _ref8[0],
811
+ stats = _ref8[1];
812
+ return {
813
+ word: word,
814
+ freq: stats.freq,
815
+ docFreq: stats.doc_freq,
816
+ authorFreq: stats.author_freq
817
+ };
818
+ });
819
+ initVocabulary({
820
+ terms: terms
821
+ });
800
822
 
801
- // 2. Load General English (L3)
802
- var l3Words = _l3_vocabulary.default;
803
- initL3Vocabulary(l3Words);
823
+ // 2. Load General English (L3)
824
+ var l3Words = _l3_vocabulary.default;
825
+ initL3Vocabulary(l3Words);
826
+ (0, _ufo.succeedExp)(_ufo.EXPERIENCE_NAME.LOAD_VOCABULARY, 'singleton', {
827
+ l2WordCount: terms.length,
828
+ l3WordCount: l3Words.length
829
+ });
830
+ } catch (e) {
831
+ (0, _ufo.failExp)(_ufo.EXPERIENCE_NAME.LOAD_VOCABULARY, 'singleton', {
832
+ errorType: 'parse_error'
833
+ });
834
+ throw e;
835
+ }
804
836
  };
@@ -0,0 +1,110 @@
1
+ /**
2
+ * UFO experience tracking helpers for `@atlaskit/editor-plugin-autocomplete`.
3
+ *
4
+ * Pattern follows `packages/linking-platform/smart-card/src/state/analytics/ufoExperiences.ts`
5
+ * (ConcurrentExperience keyed per request/instance) plus a try/catch safety wrap
6
+ * inspired by `packages/editor/collab-provider/src/analytics/ufo.ts` so a UFO
7
+ * runtime error can never break the autocomplete feature.
8
+ *
9
+ * Experience names must be registered in DataPortal's `task` attribute before
10
+ * they can power FE Reliability SLOs:
11
+ * https://hello.atlassian.net/wiki/spaces/AA6/pages/3961393753
12
+ */
13
+
14
+ import { ConcurrentExperience, ExperiencePerformanceTypes, ExperienceTypes } from '@atlaskit/ufo';
15
+
16
+ /**
17
+ * Experience name strings are surfaced downstream by the UFO pipeline as
18
+ * `platform.fe.<type>.<platform.component>.<name>` (e.g.
19
+ * `platform.fe.operation.editor-plugin-autocomplete.slow-lane-fetch`), so
20
+ * names here should be short, dot-free, and must not repeat the component
21
+ * name. This matches the convention used by `@atlaskit/emoji`'s
22
+ * `ufoExperiences` map (e.g. `'emoji-rendered'`).
23
+ */
24
+ /**
25
+ * Only experiences with meaningful async work and a real success/failure
26
+ * outcome belong on UFO (which measures latency and success rate for FE
27
+ * Reliability SLOs). Per-keystroke suggestion lifecycle counters (viewed /
28
+ * inserted / dismissed) are tracked via analytics-next instead — they would
29
+ * otherwise emit zero-duration events on every word boundary.
30
+ */
31
+ export const EXPERIENCE_NAME = {
32
+ SLOW_LANE_FETCH: 'slow-lane-fetch',
33
+ LOAD_VOCABULARY: 'load-vocabulary',
34
+ LOAD_VECTORS: 'load-vectors'
35
+ };
36
+ const isUfoEnabled = () => {
37
+ var _process, _process$env;
38
+ return !((_process = process) !== null && _process !== void 0 && (_process$env = _process.env) !== null && _process$env !== void 0 && _process$env.REACT_SSR);
39
+ };
40
+ const operationConfig = {
41
+ platform: {
42
+ component: 'editor-plugin-autocomplete'
43
+ },
44
+ type: ExperienceTypes.Operation,
45
+ performanceType: ExperiencePerformanceTypes.Custom,
46
+ performanceConfig: {
47
+ histogram: {
48
+ [ExperiencePerformanceTypes.Custom]: {
49
+ duration: '50_100_250_500_1000_2000_4000'
50
+ }
51
+ }
52
+ }
53
+ };
54
+ const experiences = {
55
+ [EXPERIENCE_NAME.SLOW_LANE_FETCH]: new ConcurrentExperience(EXPERIENCE_NAME.SLOW_LANE_FETCH, operationConfig),
56
+ [EXPERIENCE_NAME.LOAD_VOCABULARY]: new ConcurrentExperience(EXPERIENCE_NAME.LOAD_VOCABULARY, operationConfig),
57
+ [EXPERIENCE_NAME.LOAD_VECTORS]: new ConcurrentExperience(EXPERIENCE_NAME.LOAD_VECTORS, operationConfig)
58
+ };
59
+ export const startExp = (name, id, metadata) => {
60
+ if (!isUfoEnabled()) {
61
+ return;
62
+ }
63
+ try {
64
+ const instance = experiences[name].getInstance(id);
65
+ instance.start();
66
+ if (metadata) {
67
+ instance.addMetadata(metadata);
68
+ }
69
+ } catch {
70
+ // UFO errors must never break the plugin
71
+ }
72
+ };
73
+ export const succeedExp = (name, id, metadata) => {
74
+ if (!isUfoEnabled()) {
75
+ return;
76
+ }
77
+ try {
78
+ experiences[name].getInstance(id).success({
79
+ metadata
80
+ });
81
+ } catch {
82
+ // UFO errors must never break the plugin
83
+ }
84
+ };
85
+ export const failExp = (name, id, metadata) => {
86
+ if (!isUfoEnabled()) {
87
+ return;
88
+ }
89
+ try {
90
+ experiences[name].getInstance(id).failure({
91
+ metadata
92
+ });
93
+ } catch {
94
+ // UFO errors must never break the plugin
95
+ }
96
+ };
97
+ export const abortExp = (name, id, reason) => {
98
+ if (!isUfoEnabled()) {
99
+ return;
100
+ }
101
+ try {
102
+ experiences[name].getInstance(id).abort(reason ? {
103
+ metadata: {
104
+ reason
105
+ }
106
+ } : undefined);
107
+ } catch {
108
+ // UFO errors must never break the plugin
109
+ }
110
+ };
@@ -1,14 +1,16 @@
1
1
  import { ACTION, ACTION_SUBJECT, EVENT_TYPE } from '@atlaskit/editor-common/analytics';
2
+ import { logException } from '@atlaskit/editor-common/monitoring';
2
3
  import { SafePlugin } from '@atlaskit/editor-common/safe-plugin';
3
4
  import { keydownHandler } from '@atlaskit/editor-prosemirror/keymap';
4
5
  import { PluginKey } from '@atlaskit/editor-prosemirror/state';
5
6
  import { DecorationSet } from '@atlaskit/editor-prosemirror/view';
6
7
  import { createGhostTextDecorationSet } from './ghost-text-decoration';
8
+ import { createLocalSlowLaneClient } from './local-slow-lane-client';
7
9
  import { createSlowLaneClient, setDefaultSlowLaneClient, isWordBoundary } from './slow-lane-client';
8
10
  import { predict, loadDefaultVocabulary, loadVectorsAsync, incrementSessionFreq, ingestDocumentPage } from './text-predictor';
9
- const SLOW_LANE_ENDPOINT = '/gateway/api/v1/autocomplete/typeahead-encodings';
10
11
  export const autocompletePluginKey = new PluginKey('autocomplete');
11
12
  const DEBOUNCE_MS = 150;
13
+ const hasDestroy = client => 'destroy' in client && typeof client.destroy === 'function';
12
14
  const createInitialState = () => ({
13
15
  ghostText: '',
14
16
  ghostPosition: -1,
@@ -61,17 +63,23 @@ const setAutocompleteMeta = (tr, meta) => {
61
63
  */
62
64
  let lastShownGhostText = '';
63
65
  const showGhostText = (view, text, position) => {
64
- const {
65
- state,
66
- dispatch
67
- } = view;
68
- const decorationSet = createGhostTextDecorationSet(state, position, text);
69
- const tr = setAutocompleteMeta(state.tr, {
70
- ghostText: text,
71
- ghostPosition: position,
72
- decorationSet
73
- });
74
- dispatch(tr);
66
+ try {
67
+ const {
68
+ state,
69
+ dispatch
70
+ } = view;
71
+ const decorationSet = createGhostTextDecorationSet(state, position, text);
72
+ const tr = setAutocompleteMeta(state.tr, {
73
+ ghostText: text,
74
+ ghostPosition: position,
75
+ decorationSet
76
+ });
77
+ dispatch(tr);
78
+ } catch (error) {
79
+ logException(error, {
80
+ location: 'editor-plugin-autocomplete/showGhostText'
81
+ });
82
+ }
75
83
  };
76
84
 
77
85
  /**
@@ -83,12 +91,19 @@ const clearGhostText = (state, dispatch) => {
83
91
  return false;
84
92
  }
85
93
  if (dispatch) {
86
- const tr = setAutocompleteMeta(state.tr, {
87
- ghostText: '',
88
- ghostPosition: -1,
89
- decorationSet: DecorationSet.empty
90
- });
91
- dispatch(tr);
94
+ try {
95
+ const tr = setAutocompleteMeta(state.tr, {
96
+ ghostText: '',
97
+ ghostPosition: -1,
98
+ decorationSet: DecorationSet.empty
99
+ });
100
+ dispatch(tr);
101
+ } catch (error) {
102
+ logException(error, {
103
+ location: 'editor-plugin-autocomplete/clearGhostText'
104
+ });
105
+ return false;
106
+ }
92
107
  }
93
108
  return true;
94
109
  };
@@ -102,17 +117,24 @@ const acceptGhostText = (state, dispatch) => {
102
117
  return false;
103
118
  }
104
119
  if (dispatch) {
105
- const {
106
- ghostText,
107
- ghostPosition
108
- } = pluginState;
109
- let tr = state.tr.insertText(ghostText, ghostPosition);
110
- tr = setAutocompleteMeta(tr, {
111
- ghostText: '',
112
- ghostPosition: -1,
113
- decorationSet: DecorationSet.empty
114
- });
115
- dispatch(tr);
120
+ try {
121
+ const {
122
+ ghostText,
123
+ ghostPosition
124
+ } = pluginState;
125
+ let tr = state.tr.insertText(ghostText, ghostPosition);
126
+ tr = setAutocompleteMeta(tr, {
127
+ ghostText: '',
128
+ ghostPosition: -1,
129
+ decorationSet: DecorationSet.empty
130
+ });
131
+ dispatch(tr);
132
+ } catch (error) {
133
+ logException(error, {
134
+ location: 'editor-plugin-autocomplete/acceptGhostText'
135
+ });
136
+ return false;
137
+ }
116
138
  }
117
139
  return true;
118
140
  };
@@ -171,12 +193,23 @@ export const createAutocompletePlugin = (options, api) => {
171
193
  let dismissedContext = null;
172
194
  let lastSuggestionTypedLength = 0;
173
195
  let lastSuggestionLength = 0;
174
- const fireSuggestionInsertedAnalytics = ghostText => {
196
+ const fireSuggestionDismissedAnalytics = reason => {
175
197
  var _api$analytics;
198
+ api === null || api === void 0 ? void 0 : (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : _api$analytics.actions.fireAnalyticsEvent({
199
+ action: ACTION.SUGGESTION_DISMISSED,
200
+ actionSubject: ACTION_SUBJECT.CONTEXTUAL_TYPEAHEAD,
201
+ eventType: EVENT_TYPE.TRACK,
202
+ attributes: {
203
+ reason
204
+ }
205
+ });
206
+ };
207
+ const fireSuggestionInsertedAnalytics = ghostText => {
208
+ var _api$analytics2;
176
209
  const typedLength = lastSuggestionTypedLength;
177
210
  const suggestionLength = lastSuggestionLength || typedLength + ghostText.length;
178
211
  const kssDelta = suggestionLength - typedLength;
179
- api === null || api === void 0 ? void 0 : (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : _api$analytics.actions.fireAnalyticsEvent({
212
+ api === null || api === void 0 ? void 0 : (_api$analytics2 = api.analytics) === null || _api$analytics2 === void 0 ? void 0 : _api$analytics2.actions.fireAnalyticsEvent({
180
213
  action: ACTION.SUGGESTION_INSERTED,
181
214
  actionSubject: ACTION_SUBJECT.CONTEXTUAL_TYPEAHEAD,
182
215
  eventType: EVENT_TYPE.TRACK,
@@ -187,9 +220,11 @@ export const createAutocompletePlugin = (options, api) => {
187
220
  }
188
221
  });
189
222
  };
190
- const slowLaneClient = createSlowLaneClient({
223
+ const slowLaneClient = options !== null && options !== void 0 && options.useLocalModel ? createLocalSlowLaneClient({
224
+ debounceMs: 300
225
+ }) : createSlowLaneClient({
191
226
  baseUrl: '',
192
- endpoint: SLOW_LANE_ENDPOINT
227
+ debounceMs: 300
193
228
  });
194
229
  setDefaultSlowLaneClient(slowLaneClient);
195
230
 
@@ -203,47 +238,53 @@ export const createAutocompletePlugin = (options, api) => {
203
238
  clearTimeout(debounceTimer);
204
239
  }
205
240
  debounceTimer = setTimeout(() => {
206
- const {
207
- state
208
- } = view;
209
- const {
210
- selection
211
- } = state;
241
+ try {
242
+ const {
243
+ state
244
+ } = view;
245
+ const {
246
+ selection
247
+ } = state;
212
248
 
213
- // Only predict for cursor selections (not range selections)
214
- if (!selection.empty) {
215
- return;
216
- }
217
- const textBefore = getTextBeforeCursor(state);
249
+ // Only predict for cursor selections (not range selections)
250
+ if (!selection.empty) {
251
+ return;
252
+ }
253
+ const textBefore = getTextBeforeCursor(state);
218
254
 
219
- // Suppress re-showing the same suggestion the user just dismissed.
220
- // Once the text context changes (user types or deletes), this clears automatically.
221
- if (textBefore === dismissedContext) {
222
- return;
223
- }
224
- dismissedContext = null;
255
+ // Suppress re-showing the same suggestion the user just dismissed.
256
+ // Once the text context changes (user types or deletes), this clears automatically.
257
+ if (textBefore === dismissedContext) {
258
+ return;
259
+ }
260
+ dismissedContext = null;
225
261
 
226
- // Don't predict if there's not enough context
227
- if (textBefore.trim().length < 3) {
228
- return;
229
- }
262
+ // Don't predict if there's not enough context
263
+ if (textBefore.trim().length < 3) {
264
+ return;
265
+ }
230
266
 
231
- // Tier 1 prediction is synchronous -- no async needed
232
- const prediction = predict(textBefore);
233
- if (prediction && prediction.length > 0) {
234
- const typedLength = getTypedLengthForPrediction(textBefore);
235
- lastSuggestionTypedLength = typedLength;
236
- lastSuggestionLength = typedLength + prediction.length;
237
- showGhostText(view, prediction, selection.from);
238
- if (prediction !== lastShownGhostText) {
239
- var _api$analytics2;
240
- lastShownGhostText = prediction;
241
- api === null || api === void 0 ? void 0 : (_api$analytics2 = api.analytics) === null || _api$analytics2 === void 0 ? void 0 : _api$analytics2.actions.fireAnalyticsEvent({
242
- action: ACTION.SUGGESTION_VIEWED,
243
- actionSubject: ACTION_SUBJECT.CONTEXTUAL_TYPEAHEAD,
244
- eventType: EVENT_TYPE.TRACK
245
- });
267
+ // Tier 1 prediction is synchronous -- no async needed
268
+ const prediction = predict(textBefore);
269
+ if (prediction && prediction.length > 0) {
270
+ const typedLength = getTypedLengthForPrediction(textBefore);
271
+ lastSuggestionTypedLength = typedLength;
272
+ lastSuggestionLength = typedLength + prediction.length;
273
+ showGhostText(view, prediction, selection.from);
274
+ if (prediction !== lastShownGhostText) {
275
+ var _api$analytics3;
276
+ lastShownGhostText = prediction;
277
+ api === null || api === void 0 ? void 0 : (_api$analytics3 = api.analytics) === null || _api$analytics3 === void 0 ? void 0 : _api$analytics3.actions.fireAnalyticsEvent({
278
+ action: ACTION.SUGGESTION_VIEWED,
279
+ actionSubject: ACTION_SUBJECT.CONTEXTUAL_TYPEAHEAD,
280
+ eventType: EVENT_TYPE.TRACK
281
+ });
282
+ }
246
283
  }
284
+ } catch (error) {
285
+ logException(error, {
286
+ location: 'editor-plugin-autocomplete/schedulePrediction'
287
+ });
247
288
  }
248
289
  }, DEBOUNCE_MS);
249
290
  };
@@ -344,6 +385,7 @@ export const createAutocompletePlugin = (options, api) => {
344
385
  const didClear = clearGhostText(state, dispatch);
345
386
  if (didClear) {
346
387
  dismissedContext = getTextBeforeCursor(state);
388
+ fireSuggestionDismissedAnalytics('escape');
347
389
  }
348
390
  return didClear;
349
391
  }
@@ -353,14 +395,25 @@ export const createAutocompletePlugin = (options, api) => {
353
395
  const pluginState = autocompletePluginKey.getState(view.state);
354
396
  if (pluginState !== null && pluginState !== void 0 && pluginState.ghostText) {
355
397
  clearGhostText(view.state, view.dispatch);
398
+ fireSuggestionDismissedAnalytics('blur');
356
399
  }
357
400
  return false;
358
401
  },
359
402
  focus: () => {
360
- loadDefaultVocabulary();
403
+ try {
404
+ loadDefaultVocabulary();
405
+ } catch (error) {
406
+ logException(error, {
407
+ location: 'editor-plugin-autocomplete/loadDefaultVocabulary'
408
+ });
409
+ }
361
410
  loadVectorsAsync({
362
411
  getBinaryUrl: options === null || options === void 0 ? void 0 : options.getVectorsBinaryUrl
363
- }).catch(() => {});
412
+ }).catch(error => {
413
+ logException(error, {
414
+ location: 'editor-plugin-autocomplete/loadVectorsAsync'
415
+ });
416
+ });
364
417
  if (!hasIngestedPage) {
365
418
  hasIngestedPage = true;
366
419
  if (options !== null && options !== void 0 && options.getContext) {
@@ -377,7 +430,11 @@ export const createAutocompletePlugin = (options, api) => {
377
430
  ingestDocumentPage(context.parentCommentContent);
378
431
  }
379
432
  (_context$siblingComme4 = context.siblingCommentsContents) === null || _context$siblingComme4 === void 0 ? void 0 : _context$siblingComme4.forEach(ingestDocumentPage);
380
- }).catch(() => {});
433
+ }).catch(error => {
434
+ logException(error, {
435
+ location: 'editor-plugin-autocomplete/getContext'
436
+ });
437
+ });
381
438
  }
382
439
  }
383
440
  return false;
@@ -413,6 +470,9 @@ export const createAutocompletePlugin = (options, api) => {
413
470
  if (debounceTimer) {
414
471
  clearTimeout(debounceTimer);
415
472
  }
473
+ if (hasDestroy(slowLaneClient)) {
474
+ slowLaneClient.destroy();
475
+ }
416
476
  setDefaultSlowLaneClient(null);
417
477
  }
418
478
  })