@atlaskit/editor-plugin-autocomplete 2.2.0 → 2.4.0

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 (65) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/afm-cc/tsconfig.json +0 -1
  3. package/afm-products/tsconfig.json +0 -1
  4. package/autocompletePlugin/package.json +15 -0
  5. package/autocompletePluginType/package.json +15 -0
  6. package/build/tsconfig.json +0 -1
  7. package/dist/cjs/entry-points/autocompletePlugin.js +12 -0
  8. package/dist/cjs/entry-points/autocompletePluginType.js +1 -0
  9. package/dist/cjs/entry-points/src-pm-plugins-autocomplete-plugin.js +18 -0
  10. package/dist/cjs/entry-points/src-pm-plugins-slow-lane-client.js +36 -0
  11. package/dist/cjs/entry-points/src-pm-plugins-text-predictor.js +66 -0
  12. package/dist/cjs/pm-plugins/autocomplete-plugin.js +41 -14
  13. package/dist/cjs/pm-plugins/debug-mode.js +24 -0
  14. package/dist/cjs/pm-plugins/slow-lane-client.js +42 -35
  15. package/dist/cjs/pm-plugins/text-predictor.js +12 -32
  16. package/dist/es2019/entry-points/autocompletePlugin.js +2 -0
  17. package/dist/es2019/entry-points/autocompletePluginType.js +0 -0
  18. package/dist/es2019/entry-points/src-pm-plugins-autocomplete-plugin.js +2 -0
  19. package/dist/es2019/entry-points/src-pm-plugins-slow-lane-client.js +2 -0
  20. package/dist/es2019/entry-points/src-pm-plugins-text-predictor.js +2 -0
  21. package/dist/es2019/pm-plugins/autocomplete-plugin.js +41 -14
  22. package/dist/es2019/pm-plugins/debug-mode.js +18 -0
  23. package/dist/es2019/pm-plugins/slow-lane-client.js +29 -23
  24. package/dist/es2019/pm-plugins/text-predictor.js +6 -26
  25. package/dist/esm/entry-points/autocompletePlugin.js +2 -0
  26. package/dist/esm/entry-points/autocompletePluginType.js +0 -0
  27. package/dist/esm/entry-points/src-pm-plugins-autocomplete-plugin.js +2 -0
  28. package/dist/esm/entry-points/src-pm-plugins-slow-lane-client.js +2 -0
  29. package/dist/esm/entry-points/src-pm-plugins-text-predictor.js +2 -0
  30. package/dist/esm/pm-plugins/autocomplete-plugin.js +41 -14
  31. package/dist/esm/pm-plugins/debug-mode.js +18 -0
  32. package/dist/esm/pm-plugins/slow-lane-client.js +43 -35
  33. package/dist/esm/pm-plugins/text-predictor.js +11 -31
  34. package/dist/types/entry-points/autocompletePlugin.d.ts +1 -0
  35. package/dist/types/entry-points/autocompletePluginType.d.ts +1 -0
  36. package/dist/types/entry-points/src-pm-plugins-autocomplete-plugin.d.ts +2 -0
  37. package/dist/types/entry-points/src-pm-plugins-slow-lane-client.d.ts +2 -0
  38. package/dist/types/entry-points/src-pm-plugins-text-predictor.d.ts +2 -0
  39. package/dist/types/pm-plugins/autocomplete-plugin.d.ts +1 -1
  40. package/dist/types/pm-plugins/debug-mode.d.ts +12 -0
  41. package/dist/types/pm-plugins/text-predictor.d.ts +0 -2
  42. package/dist/types-ts4.5/entry-points/autocompletePlugin.d.ts +1 -0
  43. package/dist/types-ts4.5/entry-points/autocompletePluginType.d.ts +1 -0
  44. package/dist/types-ts4.5/entry-points/src-pm-plugins-autocomplete-plugin.d.ts +2 -0
  45. package/dist/types-ts4.5/entry-points/src-pm-plugins-slow-lane-client.d.ts +2 -0
  46. package/dist/types-ts4.5/entry-points/src-pm-plugins-text-predictor.d.ts +2 -0
  47. package/dist/types-ts4.5/pm-plugins/autocomplete-plugin.d.ts +1 -1
  48. package/dist/types-ts4.5/pm-plugins/debug-mode.d.ts +12 -0
  49. package/dist/types-ts4.5/pm-plugins/text-predictor.d.ts +0 -2
  50. package/package.json +3 -3
  51. package/src/entry-points/autocompletePlugin.tsx +2 -0
  52. package/src/entry-points/autocompletePluginType.ts +2 -0
  53. package/src/entry-points/src-pm-plugins-autocomplete-plugin.ts +7 -0
  54. package/src/entry-points/src-pm-plugins-slow-lane-client.ts +13 -0
  55. package/src/entry-points/src-pm-plugins-text-predictor.ts +14 -0
  56. package/src/pm-plugins/autocomplete-plugin/package.json +5 -5
  57. package/src/pm-plugins/autocomplete-plugin.ts +48 -12
  58. package/src/pm-plugins/debug-mode.ts +18 -0
  59. package/src/pm-plugins/slow-lane-client/package.json +5 -5
  60. package/src/pm-plugins/slow-lane-client.ts +53 -45
  61. package/src/pm-plugins/text-predictor/package.json +5 -5
  62. package/src/pm-plugins/text-predictor.ts +6 -30
  63. package/tsconfig.app.json +3 -1
  64. package/tsconfig.dev.json +2 -1
  65. package/afm-jira/tsconfig.json +0 -30
@@ -142,6 +142,16 @@ const buildSlowLaneText = (docText, context) => {
142
142
  lines.push(`reply ${nextReplyNumber}: ${docText}`);
143
143
  return lines.join('\n');
144
144
  };
145
+ const getTypedLengthForPrediction = textBefore => {
146
+ var _textBefore$match$, _textBefore$match;
147
+ if (isWordBoundary(textBefore)) {
148
+ return 0;
149
+ }
150
+
151
+ // eslint-disable-next-line require-unicode-regexp
152
+ const trailingToken = (_textBefore$match$ = (_textBefore$match = textBefore.match(/([^\s.,;:!?]*)$/)) === null || _textBefore$match === void 0 ? void 0 : _textBefore$match[1]) !== null && _textBefore$match$ !== void 0 ? _textBefore$match$ : '';
153
+ return trailingToken.length;
154
+ };
145
155
  export const createAutocompletePlugin = (options, api) => {
146
156
  let debounceTimer = null;
147
157
  let hasIngestedPage = false;
@@ -159,6 +169,24 @@ export const createAutocompletePlugin = (options, api) => {
159
169
  * re-showing the same suggestion. Resets to null as soon as the text changes.
160
170
  */
161
171
  let dismissedContext = null;
172
+ let lastSuggestionTypedLength = 0;
173
+ let lastSuggestionLength = 0;
174
+ const fireSuggestionInsertedAnalytics = ghostText => {
175
+ var _api$analytics;
176
+ const typedLength = lastSuggestionTypedLength;
177
+ const suggestionLength = lastSuggestionLength || typedLength + ghostText.length;
178
+ 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({
180
+ action: ACTION.SUGGESTION_INSERTED,
181
+ actionSubject: ACTION_SUBJECT.CONTEXTUAL_TYPEAHEAD,
182
+ eventType: EVENT_TYPE.TRACK,
183
+ attributes: {
184
+ suggestionLength,
185
+ typedLength,
186
+ kssDelta
187
+ }
188
+ });
189
+ };
162
190
  const slowLaneClient = createSlowLaneClient({
163
191
  baseUrl: '',
164
192
  endpoint: SLOW_LANE_ENDPOINT
@@ -203,11 +231,14 @@ export const createAutocompletePlugin = (options, api) => {
203
231
  // Tier 1 prediction is synchronous -- no async needed
204
232
  const prediction = predict(textBefore);
205
233
  if (prediction && prediction.length > 0) {
234
+ const typedLength = getTypedLengthForPrediction(textBefore);
235
+ lastSuggestionTypedLength = typedLength;
236
+ lastSuggestionLength = typedLength + prediction.length;
206
237
  showGhostText(view, prediction, selection.from);
207
238
  if (prediction !== lastShownGhostText) {
208
- var _api$analytics;
239
+ var _api$analytics2;
209
240
  lastShownGhostText = prediction;
210
- api === null || api === void 0 ? void 0 : (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : _api$analytics.actions.fireAnalyticsEvent({
241
+ api === null || api === void 0 ? void 0 : (_api$analytics2 = api.analytics) === null || _api$analytics2 === void 0 ? void 0 : _api$analytics2.actions.fireAnalyticsEvent({
211
242
  action: ACTION.SUGGESTION_VIEWED,
212
243
  actionSubject: ACTION_SUBJECT.CONTEXTUAL_TYPEAHEAD,
213
244
  eventType: EVENT_TYPE.TRACK
@@ -286,30 +317,26 @@ export const createAutocompletePlugin = (options, api) => {
286
317
  },
287
318
  handleKeyDown: keydownHandler({
288
319
  Tab: (state, dispatch) => {
320
+ var _pluginState$ghostTex;
321
+ const pluginState = autocompletePluginKey.getState(state);
322
+ const ghostText = (_pluginState$ghostTex = pluginState === null || pluginState === void 0 ? void 0 : pluginState.ghostText) !== null && _pluginState$ghostTex !== void 0 ? _pluginState$ghostTex : '';
289
323
  const accepted = acceptGhostText(state, dispatch);
290
324
  if (accepted) {
291
- var _api$analytics2;
292
325
  justAccepted = true;
293
326
  lastShownGhostText = '';
294
- api === null || api === void 0 ? void 0 : (_api$analytics2 = api.analytics) === null || _api$analytics2 === void 0 ? void 0 : _api$analytics2.actions.fireAnalyticsEvent({
295
- action: ACTION.SUGGESTION_INSERTED,
296
- actionSubject: ACTION_SUBJECT.CONTEXTUAL_TYPEAHEAD,
297
- eventType: EVENT_TYPE.TRACK
298
- });
327
+ fireSuggestionInsertedAnalytics(ghostText);
299
328
  }
300
329
  return accepted;
301
330
  },
302
331
  ArrowRight: (state, dispatch) => {
332
+ var _pluginState$ghostTex2;
333
+ const pluginState = autocompletePluginKey.getState(state);
334
+ const ghostText = (_pluginState$ghostTex2 = pluginState === null || pluginState === void 0 ? void 0 : pluginState.ghostText) !== null && _pluginState$ghostTex2 !== void 0 ? _pluginState$ghostTex2 : '';
303
335
  const accepted = acceptGhostText(state, dispatch);
304
336
  if (accepted) {
305
- var _api$analytics3;
306
337
  justAccepted = true;
307
338
  lastShownGhostText = '';
308
- api === null || api === void 0 ? void 0 : (_api$analytics3 = api.analytics) === null || _api$analytics3 === void 0 ? void 0 : _api$analytics3.actions.fireAnalyticsEvent({
309
- action: ACTION.SUGGESTION_INSERTED,
310
- actionSubject: ACTION_SUBJECT.CONTEXTUAL_TYPEAHEAD,
311
- eventType: EVENT_TYPE.TRACK
312
- });
339
+ fireSuggestionInsertedAnalytics(ghostText);
313
340
  }
314
341
  return accepted;
315
342
  },
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Contextual Typeahead Completions (CTC) debug logging utility.
3
+ *
4
+ * Logs are silent by default. To enable in any environment (dev, staging, prod):
5
+ *
6
+ * localStorage.setItem('atl-ctc-dbg', '1')
7
+ *
8
+ * Then reload the page. To disable:
9
+ *
10
+ * localStorage.removeItem('atl-ctc-dbg')
11
+ */
12
+ export const isAutocompleteDebugEnabled = () => {
13
+ try {
14
+ return typeof localStorage !== 'undefined' && localStorage.getItem('atl-ctc-dbg') === '1';
15
+ } catch {
16
+ return false;
17
+ }
18
+ };
@@ -7,6 +7,8 @@
7
7
  * Response: { semantic_vector: number[], lm_logits: Record<string, number> }
8
8
  */
9
9
 
10
+ import { isAutocompleteDebugEnabled } from './debug-mode';
11
+
10
12
  // ─── Types ───────────────────────────────────────────────────────────────────
11
13
 
12
14
  /** Request payload for typeahead-encodings endpoint. */
@@ -63,16 +65,16 @@ export const createSlowLaneClient = config => {
63
65
  text,
64
66
  session_id: sessionId
65
67
  };
66
-
67
- // Log the exact payload being sent so the comment/reply structure is visible
68
- // eslint-disable-next-line no-console
69
- console.groupCollapsed(`%c[SlowLane] %c📤 Sending context | ${text.length} chars`, 'color: #9c27b0; font-weight: bold;', 'color: inherit;');
70
- text.split('\n').forEach((line, i, arr) => {
68
+ if (isAutocompleteDebugEnabled()) {
69
+ // eslint-disable-next-line no-console
70
+ console.groupCollapsed(`%c[SlowLane] %c📤 Sending context | ${text.length} chars`, 'color: #9c27b0; font-weight: bold;', 'color: inherit;');
71
+ text.split('\n').forEach((line, i, arr) => {
72
+ // eslint-disable-next-line no-console
73
+ console.log(` ${i === arr.length - 1 ? '▶' : ' '} ${line}`);
74
+ });
71
75
  // eslint-disable-next-line no-console
72
- console.log(` ${i === arr.length - 1 ? '▶' : ' '} ${line}`);
73
- });
74
- // eslint-disable-next-line no-console
75
- console.groupEnd();
76
+ console.groupEnd();
77
+ }
76
78
  try {
77
79
  const res = await fetchFn(url, {
78
80
  method: 'POST',
@@ -82,8 +84,10 @@ export const createSlowLaneClient = config => {
82
84
  if (!res.ok) {
83
85
  storedContextVector = null;
84
86
  storedLmLogits = null;
85
- // eslint-disable-next-line no-console
86
- console.log(`%c[SlowLane] %c❌ Request failed (${res.status})`, 'color: #9c27b0; font-weight: bold;', 'color: #f44336;');
87
+ if (isAutocompleteDebugEnabled()) {
88
+ // eslint-disable-next-line no-console
89
+ console.log(`%c[SlowLane] %c❌ Request failed (${res.status})`, 'color: #9c27b0; font-weight: bold;', 'color: #f44336;');
90
+ }
87
91
  return;
88
92
  }
89
93
  const data = await res.json();
@@ -97,16 +101,16 @@ export const createSlowLaneClient = config => {
97
101
  } else {
98
102
  storedLmLogits = null;
99
103
  }
100
-
101
- // Log what came back so it can be correlated with the next prediction group
102
- // eslint-disable-next-line no-console
103
- console.groupCollapsed(`%c[SlowLane] %c📥 Response received`, 'color: #9c27b0; font-weight: bold;', 'color: inherit;');
104
- // eslint-disable-next-line no-console
105
- console.log(storedContextVector ? `✅ semantic_vector: ${storedContextVector.length} dims` : '❌ No semantic_vector');
106
- // eslint-disable-next-line no-console
107
- console.log(storedLmLogits ? `✅ lm_logits: ${Object.keys(storedLmLogits).length} tokens` : '❌ No lm_logits');
108
- // eslint-disable-next-line no-console
109
- console.groupEnd();
104
+ if (isAutocompleteDebugEnabled()) {
105
+ // eslint-disable-next-line no-console
106
+ console.groupCollapsed(`%c[SlowLane] %c📥 Response received`, 'color: #9c27b0; font-weight: bold;', 'color: inherit;');
107
+ // eslint-disable-next-line no-console
108
+ console.log(storedContextVector ? `✅ semantic_vector: ${storedContextVector.length} dims` : '❌ No semantic_vector');
109
+ // eslint-disable-next-line no-console
110
+ console.log(storedLmLogits ? `✅ lm_logits: ${Object.keys(storedLmLogits).length} tokens` : '❌ No lm_logits');
111
+ // eslint-disable-next-line no-console
112
+ console.groupEnd();
113
+ }
110
114
  onUpdate === null || onUpdate === void 0 ? void 0 : onUpdate({
111
115
  textLength: text.length,
112
116
  hasVector: storedContextVector !== null,
@@ -116,8 +120,10 @@ export const createSlowLaneClient = config => {
116
120
  } catch (e) {
117
121
  storedContextVector = null;
118
122
  storedLmLogits = null;
119
- // eslint-disable-next-line no-console
120
- console.log('%c[SlowLane] %c❌ Network error — context cleared', 'color: #9c27b0; font-weight: bold;', 'color: #f44336;');
123
+ if (isAutocompleteDebugEnabled()) {
124
+ // eslint-disable-next-line no-console
125
+ console.log('%c[SlowLane] %c❌ Network error — context cleared', 'color: #9c27b0; font-weight: bold;', 'color: #f44336;');
126
+ }
121
127
  }
122
128
  };
123
129
  const updateContextDebounced = text => {
@@ -21,6 +21,7 @@ import l3VocabularyData from './data/l3_vocabulary.json';
21
21
  import vocabularyData from './data/vocabulary_10k.json';
22
22
  import wordIndexData from './data/word_index_10k.json';
23
23
  // import { rankCandidates, isGrammarAllowed } from './scoring-pipeline';
24
+ import { isAutocompleteDebugEnabled } from './debug-mode';
24
25
  import { rankCandidates, STAGE1_WEIGHT, STAGE2_WEIGHT, MIN_STAGE1_SCORE } from './scoring-pipeline';
25
26
  import { getStoredContextVector, getStoredLmLogits } from './slow-lane-client';
26
27
 
@@ -161,7 +162,7 @@ export const initL3Vocabulary = l3Words => {
161
162
  // loses to any domain word in Stage 1, but still scores above 0.
162
163
  l3Trie.insert(word, L3_BASELINE_FREQ, 0, 0);
163
164
  }
164
- if (debugMode) {
165
+ if (isAutocompleteDebugEnabled()) {
165
166
  // eslint-disable-next-line no-console
166
167
  console.log(`[text-predictor] L3 General English loaded: ${l3Words.length} words`);
167
168
  }
@@ -174,9 +175,7 @@ export const initL3Vocabulary = l3Words => {
174
175
  let isInitialized = false;
175
176
  let vectorStore = null;
176
177
  let vectorsLoadStarted = false;
177
- let debugMode = true;
178
178
  let lastPredictionDebug = null;
179
- let hasLoggedSemanticActive = false;
180
179
 
181
180
  /** Get vector for a word from the store. */
182
181
  const getWordVector = word => {
@@ -263,25 +262,12 @@ const extractPreviousWord = text => {
263
262
 
264
263
  // ─── Debug Helpers ───────────────────────────────────────────────────────────
265
264
 
266
- /** Enable or disable debug logging. Also checks localStorage key `autocomplete-debug`. */
267
- export const setDebugMode = enabled => {
268
- debugMode = enabled;
269
- };
270
-
271
- /** Check localStorage for autocomplete-debug on first access. */
272
- const ensureDebugModeFromStorage = () => {
273
- if (typeof localStorage !== 'undefined' && localStorage.getItem('autocomplete-debug') === '1') {
274
- debugMode = true;
275
- }
276
- };
277
-
278
265
  /**
279
266
  * Get predictor status for debugging.
280
267
  * vectorsLoaded: true when semantic scoring is active
281
268
  * wordCount: number of words in vector store (0 if not loaded)
282
269
  */
283
270
  export const getPredictorStatus = () => {
284
- ensureDebugModeFromStorage();
285
271
  return {
286
272
  vectorsLoaded: vectorStore !== null,
287
273
  wordCount: vectorStore ? Object.keys(vectorStore.wordIndex).length : 0,
@@ -295,7 +281,6 @@ export const getPredictorStatus = () => {
295
281
  * Returns null if no prediction has run yet or debug was off.
296
282
  */
297
283
  export const getLastPredictionDebug = () => {
298
- ensureDebugModeFromStorage();
299
284
  return lastPredictionDebug;
300
285
  };
301
286
  export const initVocabulary = vocabulary => {
@@ -336,7 +321,7 @@ export const ingestDocumentPage = pageContent => {
336
321
  validBoostedWords.add(word);
337
322
  }
338
323
  }
339
- if (debugMode && validBoostedWords.size > 0) {
324
+ if (isAutocompleteDebugEnabled() && validBoostedWords.size > 0) {
340
325
  // eslint-disable-next-line no-console
341
326
  console.groupCollapsed(`%c[L1 Session] %cPrimed ${validBoostedWords.size} valid dictionary words from page`, 'color: #00b8d9; font-weight: bold;', 'color: inherit; font-style: italic;');
342
327
  // eslint-disable-next-line no-console
@@ -346,7 +331,6 @@ export const ingestDocumentPage = pageContent => {
346
331
  }
347
332
  };
348
333
  export const predict = textBefore => {
349
- ensureDebugModeFromStorage();
350
334
  if (!isInitialized) {
351
335
  loadDefaultVocabulary();
352
336
  }
@@ -428,7 +412,7 @@ export const predict = textBefore => {
428
412
  const lmLogits = getStoredLmLogits();
429
413
 
430
414
  // Raw LM Output Logger
431
- if (debugMode && lmLogits && Object.keys(lmLogits).length > 0) {
415
+ if (isAutocompleteDebugEnabled() && lmLogits && Object.keys(lmLogits).length > 0) {
432
416
  const rawLmTop = Object.entries(lmLogits).sort((a, b) => b[1] - a[1]).slice(0, 5).map(([word, score]) => ({
433
417
  Word: word,
434
418
  Prob: Number(score.toFixed(5))
@@ -437,9 +421,6 @@ export const predict = textBefore => {
437
421
  console.log('%c[Raw LM Prediction]🧠', 'color: #e83e8c; font-weight: bold;', rawLmTop);
438
422
  }
439
423
  const mode = contextVector ? 'warm' : 'cold';
440
- if (debugMode && contextVector && !hasLoggedSemanticActive) {
441
- hasLoggedSemanticActive = true;
442
- }
443
424
 
444
425
  // Build ScoringCandidate array from TrieNodes
445
426
  const scoringCandidates = candidates.map(({
@@ -464,7 +445,7 @@ export const predict = textBefore => {
464
445
  } = rankCandidates(scoringCandidates, contextVector, w => getWordVector(w), prefixLmLogits, wordTrie.maxTenantFreq, previousWord);
465
446
  const best = ranked[0];
466
447
  const suggestion = best && best.finalScore >= MIN_SCORE_THRESHOLD ? best.word.slice(currentWord.length) : null;
467
- if (debugMode) {
448
+ if (isAutocompleteDebugEnabled()) {
468
449
  const latencyMs = performance.now() - t0;
469
450
  const tokens = tokenize(trimmed);
470
451
  const contextWords = tokens.slice(-CONTEXT_WORDS);
@@ -612,8 +593,7 @@ export const loadVectorsAsync = async options => {
612
593
  wordIndex,
613
594
  dim
614
595
  };
615
- ensureDebugModeFromStorage();
616
- if (debugMode) {
596
+ if (isAutocompleteDebugEnabled()) {
617
597
  // eslint-disable-next-line no-console
618
598
  console.log('[text-predictor] Vectors loaded:', {
619
599
  wordCount: nWords,
@@ -0,0 +1,2 @@
1
+ /* eslint-disable @atlaskit/editor/no-re-export */
2
+ export { autocompletePlugin } from '../autocompletePlugin';
@@ -0,0 +1,2 @@
1
+ /* eslint-disable @atlaskit/editor/no-re-export */
2
+ export { autocompletePluginKey, createAutocompletePlugin } from '../pm-plugins/autocomplete-plugin';
@@ -0,0 +1,2 @@
1
+ /* eslint-disable @atlaskit/editor/no-re-export */
2
+ export { createSlowLaneClient, getStoredContextVector, getStoredLmLogits, isWordBoundary, setDefaultSlowLaneClient } from '../pm-plugins/slow-lane-client';
@@ -0,0 +1,2 @@
1
+ /* eslint-disable @atlaskit/editor/no-re-export */
2
+ export { getLastPredictionDebug, getPredictorStatus, incrementSessionFreq, ingestDocumentPage, initL3Vocabulary, initVectors, initVocabulary, loadDefaultVocabulary, loadVectorsAsync, predict } from '../pm-plugins/text-predictor';
@@ -141,6 +141,16 @@ var buildSlowLaneText = function buildSlowLaneText(docText, context) {
141
141
  lines.push("reply ".concat(nextReplyNumber, ": ").concat(docText));
142
142
  return lines.join('\n');
143
143
  };
144
+ var getTypedLengthForPrediction = function getTypedLengthForPrediction(textBefore) {
145
+ var _textBefore$match$, _textBefore$match;
146
+ if (isWordBoundary(textBefore)) {
147
+ return 0;
148
+ }
149
+
150
+ // eslint-disable-next-line require-unicode-regexp
151
+ var trailingToken = (_textBefore$match$ = (_textBefore$match = textBefore.match(/([^\s.,;:!?]*)$/)) === null || _textBefore$match === void 0 ? void 0 : _textBefore$match[1]) !== null && _textBefore$match$ !== void 0 ? _textBefore$match$ : '';
152
+ return trailingToken.length;
153
+ };
144
154
  export var createAutocompletePlugin = function createAutocompletePlugin(options, api) {
145
155
  var debounceTimer = null;
146
156
  var hasIngestedPage = false;
@@ -158,6 +168,24 @@ export var createAutocompletePlugin = function createAutocompletePlugin(options,
158
168
  * re-showing the same suggestion. Resets to null as soon as the text changes.
159
169
  */
160
170
  var dismissedContext = null;
171
+ var lastSuggestionTypedLength = 0;
172
+ var lastSuggestionLength = 0;
173
+ var fireSuggestionInsertedAnalytics = function fireSuggestionInsertedAnalytics(ghostText) {
174
+ var _api$analytics;
175
+ var typedLength = lastSuggestionTypedLength;
176
+ var suggestionLength = lastSuggestionLength || typedLength + ghostText.length;
177
+ var kssDelta = suggestionLength - typedLength;
178
+ api === null || api === void 0 || (_api$analytics = api.analytics) === null || _api$analytics === void 0 || _api$analytics.actions.fireAnalyticsEvent({
179
+ action: ACTION.SUGGESTION_INSERTED,
180
+ actionSubject: ACTION_SUBJECT.CONTEXTUAL_TYPEAHEAD,
181
+ eventType: EVENT_TYPE.TRACK,
182
+ attributes: {
183
+ suggestionLength: suggestionLength,
184
+ typedLength: typedLength,
185
+ kssDelta: kssDelta
186
+ }
187
+ });
188
+ };
161
189
  var slowLaneClient = createSlowLaneClient({
162
190
  baseUrl: '',
163
191
  endpoint: SLOW_LANE_ENDPOINT
@@ -198,11 +226,14 @@ export var createAutocompletePlugin = function createAutocompletePlugin(options,
198
226
  // Tier 1 prediction is synchronous -- no async needed
199
227
  var prediction = predict(textBefore);
200
228
  if (prediction && prediction.length > 0) {
229
+ var typedLength = getTypedLengthForPrediction(textBefore);
230
+ lastSuggestionTypedLength = typedLength;
231
+ lastSuggestionLength = typedLength + prediction.length;
201
232
  showGhostText(view, prediction, selection.from);
202
233
  if (prediction !== lastShownGhostText) {
203
- var _api$analytics;
234
+ var _api$analytics2;
204
235
  lastShownGhostText = prediction;
205
- api === null || api === void 0 || (_api$analytics = api.analytics) === null || _api$analytics === void 0 || _api$analytics.actions.fireAnalyticsEvent({
236
+ api === null || api === void 0 || (_api$analytics2 = api.analytics) === null || _api$analytics2 === void 0 || _api$analytics2.actions.fireAnalyticsEvent({
206
237
  action: ACTION.SUGGESTION_VIEWED,
207
238
  actionSubject: ACTION_SUBJECT.CONTEXTUAL_TYPEAHEAD,
208
239
  eventType: EVENT_TYPE.TRACK
@@ -278,30 +309,26 @@ export var createAutocompletePlugin = function createAutocompletePlugin(options,
278
309
  },
279
310
  handleKeyDown: keydownHandler({
280
311
  Tab: function Tab(state, dispatch) {
312
+ var _pluginState$ghostTex;
313
+ var pluginState = autocompletePluginKey.getState(state);
314
+ var ghostText = (_pluginState$ghostTex = pluginState === null || pluginState === void 0 ? void 0 : pluginState.ghostText) !== null && _pluginState$ghostTex !== void 0 ? _pluginState$ghostTex : '';
281
315
  var accepted = acceptGhostText(state, dispatch);
282
316
  if (accepted) {
283
- var _api$analytics2;
284
317
  justAccepted = true;
285
318
  lastShownGhostText = '';
286
- api === null || api === void 0 || (_api$analytics2 = api.analytics) === null || _api$analytics2 === void 0 || _api$analytics2.actions.fireAnalyticsEvent({
287
- action: ACTION.SUGGESTION_INSERTED,
288
- actionSubject: ACTION_SUBJECT.CONTEXTUAL_TYPEAHEAD,
289
- eventType: EVENT_TYPE.TRACK
290
- });
319
+ fireSuggestionInsertedAnalytics(ghostText);
291
320
  }
292
321
  return accepted;
293
322
  },
294
323
  ArrowRight: function ArrowRight(state, dispatch) {
324
+ var _pluginState$ghostTex2;
325
+ var pluginState = autocompletePluginKey.getState(state);
326
+ var ghostText = (_pluginState$ghostTex2 = pluginState === null || pluginState === void 0 ? void 0 : pluginState.ghostText) !== null && _pluginState$ghostTex2 !== void 0 ? _pluginState$ghostTex2 : '';
295
327
  var accepted = acceptGhostText(state, dispatch);
296
328
  if (accepted) {
297
- var _api$analytics3;
298
329
  justAccepted = true;
299
330
  lastShownGhostText = '';
300
- api === null || api === void 0 || (_api$analytics3 = api.analytics) === null || _api$analytics3 === void 0 || _api$analytics3.actions.fireAnalyticsEvent({
301
- action: ACTION.SUGGESTION_INSERTED,
302
- actionSubject: ACTION_SUBJECT.CONTEXTUAL_TYPEAHEAD,
303
- eventType: EVENT_TYPE.TRACK
304
- });
331
+ fireSuggestionInsertedAnalytics(ghostText);
305
332
  }
306
333
  return accepted;
307
334
  },
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Contextual Typeahead Completions (CTC) debug logging utility.
3
+ *
4
+ * Logs are silent by default. To enable in any environment (dev, staging, prod):
5
+ *
6
+ * localStorage.setItem('atl-ctc-dbg', '1')
7
+ *
8
+ * Then reload the page. To disable:
9
+ *
10
+ * localStorage.removeItem('atl-ctc-dbg')
11
+ */
12
+ export var isAutocompleteDebugEnabled = function isAutocompleteDebugEnabled() {
13
+ try {
14
+ return typeof localStorage !== 'undefined' && localStorage.getItem('atl-ctc-dbg') === '1';
15
+ } catch (_unused) {
16
+ return false;
17
+ }
18
+ };
@@ -10,6 +10,8 @@ import _regeneratorRuntime from "@babel/runtime/regenerator";
10
10
  * Response: { semantic_vector: number[], lm_logits: Record<string, number> }
11
11
  */
12
12
 
13
+ import { isAutocompleteDebugEnabled } from './debug-mode';
14
+
13
15
  // ─── Types ───────────────────────────────────────────────────────────────────
14
16
 
15
17
  /** Request payload for typeahead-encodings endpoint. */
@@ -74,37 +76,41 @@ export var createSlowLaneClient = function createSlowLaneClient(config) {
74
76
  payload = {
75
77
  text: text,
76
78
  session_id: sessionId
77
- }; // Log the exact payload being sent so the comment/reply structure is visible
78
- // eslint-disable-next-line no-console
79
- console.groupCollapsed("%c[SlowLane] %c\uD83D\uDCE4 Sending context | ".concat(text.length, " chars"), 'color: #9c27b0; font-weight: bold;', 'color: inherit;');
80
- text.split('\n').forEach(function (line, i, arr) {
79
+ };
80
+ if (isAutocompleteDebugEnabled()) {
81
81
  // eslint-disable-next-line no-console
82
- console.log(" ".concat(i === arr.length - 1 ? '▶' : ' ', " ").concat(line));
83
- });
84
- // eslint-disable-next-line no-console
85
- console.groupEnd();
86
- _context.prev = 7;
87
- _context.next = 10;
82
+ console.groupCollapsed("%c[SlowLane] %c\uD83D\uDCE4 Sending context | ".concat(text.length, " chars"), 'color: #9c27b0; font-weight: bold;', 'color: inherit;');
83
+ text.split('\n').forEach(function (line, i, arr) {
84
+ // eslint-disable-next-line no-console
85
+ console.log(" ".concat(i === arr.length - 1 ? '▶' : ' ', " ").concat(line));
86
+ });
87
+ // eslint-disable-next-line no-console
88
+ console.groupEnd();
89
+ }
90
+ _context.prev = 5;
91
+ _context.next = 8;
88
92
  return fetchFn(url, {
89
93
  method: 'POST',
90
94
  headers: headers,
91
95
  body: JSON.stringify(payload)
92
96
  });
93
- case 10:
97
+ case 8:
94
98
  res = _context.sent;
95
99
  if (res.ok) {
96
- _context.next = 16;
100
+ _context.next = 14;
97
101
  break;
98
102
  }
99
103
  storedContextVector = null;
100
104
  storedLmLogits = null;
101
- // eslint-disable-next-line no-console
102
- console.log("%c[SlowLane] %c\u274C Request failed (".concat(res.status, ")"), 'color: #9c27b0; font-weight: bold;', 'color: #f44336;');
105
+ if (isAutocompleteDebugEnabled()) {
106
+ // eslint-disable-next-line no-console
107
+ console.log("%c[SlowLane] %c\u274C Request failed (".concat(res.status, ")"), 'color: #9c27b0; font-weight: bold;', 'color: #f44336;');
108
+ }
103
109
  return _context.abrupt("return");
104
- case 16:
105
- _context.next = 18;
110
+ case 14:
111
+ _context.next = 16;
106
112
  return res.json();
107
- case 18:
113
+ case 16:
108
114
  data = _context.sent;
109
115
  if (data.semantic_vector && Array.isArray(data.semantic_vector)) {
110
116
  storedContextVector = new Float32Array(data.semantic_vector);
@@ -116,36 +122,38 @@ export var createSlowLaneClient = function createSlowLaneClient(config) {
116
122
  } else {
117
123
  storedLmLogits = null;
118
124
  }
119
-
120
- // Log what came back so it can be correlated with the next prediction group
121
- // eslint-disable-next-line no-console
122
- console.groupCollapsed("%c[SlowLane] %c\uD83D\uDCE5 Response received", 'color: #9c27b0; font-weight: bold;', 'color: inherit;');
123
- // eslint-disable-next-line no-console
124
- console.log(storedContextVector ? "\u2705 semantic_vector: ".concat(storedContextVector.length, " dims") : '❌ No semantic_vector');
125
- // eslint-disable-next-line no-console
126
- console.log(storedLmLogits ? "\u2705 lm_logits: ".concat(Object.keys(storedLmLogits).length, " tokens") : '❌ No lm_logits');
127
- // eslint-disable-next-line no-console
128
- console.groupEnd();
125
+ if (isAutocompleteDebugEnabled()) {
126
+ // eslint-disable-next-line no-console
127
+ console.groupCollapsed("%c[SlowLane] %c\uD83D\uDCE5 Response received", 'color: #9c27b0; font-weight: bold;', 'color: inherit;');
128
+ // eslint-disable-next-line no-console
129
+ console.log(storedContextVector ? "\u2705 semantic_vector: ".concat(storedContextVector.length, " dims") : '❌ No semantic_vector');
130
+ // eslint-disable-next-line no-console
131
+ console.log(storedLmLogits ? "\u2705 lm_logits: ".concat(Object.keys(storedLmLogits).length, " tokens") : '❌ No lm_logits');
132
+ // eslint-disable-next-line no-console
133
+ console.groupEnd();
134
+ }
129
135
  onUpdate === null || onUpdate === void 0 || onUpdate({
130
136
  textLength: text.length,
131
137
  hasVector: storedContextVector !== null,
132
138
  hasLmLogits: storedLmLogits !== null
133
139
  });
134
140
  // eslint-disable-next-line no-unused-vars
135
- _context.next = 33;
141
+ _context.next = 28;
136
142
  break;
137
- case 28:
138
- _context.prev = 28;
139
- _context.t0 = _context["catch"](7);
143
+ case 23:
144
+ _context.prev = 23;
145
+ _context.t0 = _context["catch"](5);
140
146
  storedContextVector = null;
141
147
  storedLmLogits = null;
142
- // eslint-disable-next-line no-console
143
- console.log('%c[SlowLane] %c❌ Network error — context cleared', 'color: #9c27b0; font-weight: bold;', 'color: #f44336;');
144
- case 33:
148
+ if (isAutocompleteDebugEnabled()) {
149
+ // eslint-disable-next-line no-console
150
+ console.log('%c[SlowLane] %c❌ Network error — context cleared', 'color: #9c27b0; font-weight: bold;', 'color: #f44336;');
151
+ }
152
+ case 28:
145
153
  case "end":
146
154
  return _context.stop();
147
155
  }
148
- }, _callee, null, [[7, 28]]);
156
+ }, _callee, null, [[5, 23]]);
149
157
  }));
150
158
  return function doUpdateContext(_x) {
151
159
  return _ref.apply(this, arguments);